def test_ffmpeg_parse_infos(): d=ffmpeg_parse_infos("media/big_buck_bunny_432_433.webm") assert d['duration'] == 1.0 d=ffmpeg_parse_infos("media/pigs_in_a_polka.gif") assert d['video_size'] == [314, 273] assert d['duration'] == 3.0
def test_ffmpeg_parse_infos(): d=ffmpeg_parse_infos("media/big_buck_bunny_432_433.webm") assert d['duration'] == 1.0 d=ffmpeg_parse_infos("media/pigs_in_a_polka.gif") assert d['video_size'] == [314, 273] assert d['duration'] == 3.0 assert not d['audio_found'] d=ffmpeg_parse_infos("media/video_with_failing_audio.mp4") assert d['audio_found'] assert d['audio_fps'] == 44100 d=ffmpeg_parse_infos("media/crunching.mp3") assert d['audio_found'] assert d['audio_fps'] == 48000
def test_ffmpeg_parse_infos_decode_file(decode_file, expected_duration): """Test `decode_file` argument of `ffmpeg_parse_infos` function.""" d = ffmpeg_parse_infos("media/big_buck_bunny_0_30.webm", decode_file=decode_file) assert d["duration"] == expected_duration # check metadata is fine assert len(d["metadata"]) == 1 # check input assert len(d["inputs"]) == 1 # check streams streams = d["inputs"][0]["streams"] assert len(streams) == 2 assert streams[0]["stream_type"] == "video" assert streams[0]["stream_number"] == 0 assert streams[0]["fps"] == 24 assert streams[0]["size"] == [1280, 720] assert streams[0]["default"] is True assert streams[0]["language"] is None assert streams[1]["stream_type"] == "audio" assert streams[1]["stream_number"] == 1 assert streams[1]["fps"] == 44100 assert streams[1]["default"] is True assert streams[1]["language"] is None
def test_ffmpeg_parse_infos(): d=ffmpeg_parse_infos("media/big_buck_bunny_432_433.webm") assert d['duration'] == 1.0 d=ffmpeg_parse_infos("media/pigs_in_a_polka.gif") assert d['video_size'] == [314, 273] assert d['duration'] == 3.0 assert not d['audio_found'] d=ffmpeg_parse_infos("media/video_with_failing_audio.mp4") assert d['audio_found'] assert d['audio_fps'] == 44100 d=ffmpeg_parse_infos("media/crunching.mp3") assert d['audio_found'] assert d['audio_fps'] == 48000
def copy(video, output, fps, verbose): """Create a copy of a video with different settings. Currently, this can be used to change the frame rate, but hopefully this will later support other tasks like changing the resolution.""" import subprocess from moviepy.video.io.ffmpeg_reader import ffmpeg_parse_infos from moviepy.config import get_setting if verbose: logging.getLogger().setLevel(logging.INFO) if fps is None: raise click.BadParameter( 'copy currently only supports changing frame rate.') original_fps = ffmpeg_parse_infos(video)['video_fps'] fps_scale = original_fps / fps cmd = [ get_setting("FFMPEG_BINARY"), "-i", str(video), "-vf", 'setpts={}*PTS'.format(fps_scale), '-r', str(fps), str(output) ] logging.info('Running command: {}'.format(' '.join(cmd))) try: output = subprocess.check_output(cmd, stderr=subprocess.PIPE) except subprocess.CalledProcessError as e: logging.exception('[vid] Command returned an error: ') logging.fatal(e.decode('utf8')) return
def __init__(self, filename, buffersize, print_infos=False, fps=44100, nbytes=2, nchannels=2): self.filename = filename self.nbytes = nbytes self.fps = fps self.f = "s%dle" % (8 * nbytes) self.acodec = "pcm_s%dle" % (8 * nbytes) self.nchannels = nchannels infos = ffmpeg_parse_infos(filename) self.duration = infos["duration"] if "video_duration" in infos: self.duration = infos["video_duration"] else: self.duration = infos["duration"] self.bitrate = infos["audio_bitrate"] self.infos = infos self.proc = None self.nframes = int(self.fps * self.duration) self.buffersize = min(self.nframes + 1, buffersize) self.buffer = None self.buffer_startframe = 1 self.initialize() self.buffer_around(1)
def get_sampling_rate(filename): ''' Use moviepy/FFMPEG to get the sampling rate ''' infos = ffmpeg_parse_infos(filename) fps = infos.get('audio_fps', 44100) if fps == 'unknown': fps = 44100 return fps
def __init__( self, filename, buffersize, decode_file=False, print_infos=False, fps=44100, nbytes=2, nchannels=2, ): # TODO bring FFMPEG_AudioReader more in line with FFMPEG_VideoReader # E.g. here self.pos is still 1-indexed. # (or have them inherit from a shared parent class) self.filename = filename self.nbytes = nbytes self.fps = fps self.format = "s%dle" % (8 * nbytes) self.codec = "pcm_s%dle" % (8 * nbytes) self.nchannels = nchannels infos = ffmpeg_parse_infos(filename, decode_file=decode_file) self.duration = infos["duration"] self.bitrate = infos["audio_bitrate"] self.infos = infos self.proc = None self.n_frames = int(self.fps * self.duration) self.buffersize = min(self.n_frames + 1, buffersize) self.buffer = None self.buffer_startframe = 1 self.initialize() self.buffer_around(1)
def get_sampling_rate(filename): ''' Use moviepy/FFMPEG to get the sampling rate ''' infos = ffmpeg_parse_infos(filename) fps = infos.get('audio_fps', 44100) if fps == 'unknown': fps = 44100 return fps
def __init__(self, filename, buffersize, print_infos=False, fps=44100, nbytes=2, nchannels=2): self.filename = filename self.nbytes = nbytes self.fps = fps self.f = 's%dle' % (8 * nbytes) self.acodec = 'pcm_s%dle' % (8 * nbytes) self.nchannels = nchannels infos = ffmpeg_parse_infos(filename) self.duration = infos['duration'] if 'video_duration' in infos: self.duration = infos['video_duration'] else: self.duration = infos['duration'] self.infos = infos self.proc = None self.nframes = int(self.fps * self.duration) self.buffersize = min(self.nframes + 1, buffersize) self.buffer = None self.buffer_startframe = 1 self.initialize() self.buffer_around(1)
def dump_frames(video_path, output_directory, frames_per_second, file_logger_name): """Dump frames at frames_per_second from a video to output_directory. If frames_per_second is None, the clip's fps attribute is used instead.""" if not os.path.isdir(output_directory): os.mkdir(output_directory) file_logger = logging.getLogger(file_logger_name) try: video_info = ffmpeg_parse_infos(video_path) video_fps = video_info['video_fps'] video_duration = video_info['duration'] except OSError as e: logging.error('Unable to open video (%s), skipping.' % video_path) logging.exception('Exception:') return info_path = '{}/info.json'.format(output_directory) name_format = '{}/frame%04d.png'.format(output_directory) extract_all_frames = frames_per_second is None if extract_all_frames: frames_per_second = video_fps frames_already_dumped_helper = lambda log_reason: \ frames_already_dumped(video_path, output_directory, frames_per_second, info_path, name_format, video_duration, log_reason) if frames_already_dumped_helper(False): file_logger.info('Frames for {} exist, skipping...'.format(video_path)) return successfully_wrote_images = False try: if extract_all_frames: cmd = ['ffmpeg', '-i', video_path, name_format] else: cmd = ['ffmpeg', '-i', video_path, '-vf', 'fps={}'.format(frames_per_second), name_format] subprocess.check_output(cmd, stderr=subprocess.STDOUT) successfully_wrote_images = True except subprocess.CalledProcessError as e: logging.error("Failed to dump images for %s", video_path) logging.error(e) logging.error(e.output.decode('utf-8')) if successfully_wrote_images: info = {'frames_per_second': frames_per_second, 'input_video_path': os.path.abspath(video_path)} with open(info_path, 'w') as info_file: json.dump(info, info_file) if not frames_already_dumped_helper(True): logging.error( "Images for {} don't seem to be dumped properly!".format( video_path))
def get_duration_definition(video_name): """ 获取视频时长与清晰度 :param video_name: :return: """ video_info = ffmpeg_parse_infos(video_name) duration = int(video_info['duration']) # unit second video_size = video_info['video_size'] return duration, video_size
def video_info(video): from moviepy.video.io.ffmpeg_reader import ffmpeg_parse_infos if isinstance(video, Path): video = str(video) info = ffmpeg_parse_infos(video) return { 'duration': info['duration'], 'fps': info['video_fps'], 'size': info['video_size'] # (width, height) }
def test_ffmpeg_parse_infos(): d = ffmpeg_parse_infos("media/big_buck_bunny_432_433.webm") assert d["duration"] == 1.0 d = ffmpeg_parse_infos("media/pigs_in_a_polka.gif") assert d["video_size"] == [314, 273] assert d["duration"] == 3.0 assert not d["audio_found"] d = ffmpeg_parse_infos("media/video_with_failing_audio.mp4") assert d["audio_found"] assert d["audio_fps"] == 44100 d = ffmpeg_parse_infos("media/crunching.mp3") assert d["audio_found"] assert d["audio_fps"] == 48000 d = ffmpeg_parse_infos("tests/resource/sintel_with_15_chapters.mp4") assert d["audio_bitrate"] assert d["video_bitrate"]
def test_correct_video_rotation(util): """See https://github.com/Zulko/moviepy/pull/577""" clip = VideoFileClip("media/rotated-90-degrees.mp4").subclip(0.2, 0.4) corrected_rotation_filename = os.path.join( util.TMP_DIR, "correct_video_rotation.mp4", ) clip.write_videofile(corrected_rotation_filename) d = ffmpeg_parse_infos(corrected_rotation_filename) assert "video_rotation" not in d assert d["video_size"] == [1080, 1920]
def test_ffmpeg_parse_infos_chapters(): """Check that `ffmpeg_parse_infos` can parse chapters with their metadata.""" d = ffmpeg_parse_infos("media/sintel_with_14_chapters.mp4") chapters = d["inputs"][0]["chapters"] num_chapters_expected = 14 assert len(chapters) == num_chapters_expected for num in range(0, len(chapters)): assert chapters[num]["chapter_number"] == num assert chapters[num]["end"] == (num + 1) / 10 assert chapters[num]["start"] == num / 10 assert chapters[num]["metadata"]["title"] assert isinstance(chapters[num]["metadata"]["title"], str)
def info(video): """Report video duration, fps, and resolution.""" from moviepy.video.io.ffmpeg_reader import ffmpeg_parse_infos info = ffmpeg_parse_infos(video) output = { 'Path': Path(video).resolve(), 'Duration': info['duration'], 'FPS': info['video_fps'], 'Resolution': '{w}x{h}'.format(w=info["video_size"][0], h=info["video_size"][1]) } max_width = max(len(x) for x in info) for key, value in output.items(): # Right align all names for pretty output. key_pretty = key.rjust(max_width) print(f"{key_pretty}: {value}")
def test_ffmpeg_parse_infos_metadata_with_attached_pic(): """Check that the parser can parse audios with attached pictures. Currently, does not distinguish if the video found is an attached picture, this test serves mainly to ensure that #1487 issue does not happen again: """ d = ffmpeg_parse_infos("media/with-attached-pic.mp3") assert d["audio_bitrate"] == 320 assert d["audio_found"] assert d["audio_fps"] == 44100 assert len(d["inputs"]) == 1 streams = d["inputs"][0]["streams"] assert len(streams) == 2 assert streams[0]["stream_type"] == "audio" assert streams[1]["stream_type"] == "video" assert len(d["metadata"].keys()) == 7
def __init__(self, filename, buffersize, print_infos=False, fps=44100, nbytes=2, nchannels=2): self.filename = filename self.nbytes = nbytes self.fps = fps self.f = 's%dle'%(8*nbytes) self.acodec = 'pcm_s%dle'%(8*nbytes) self.nchannels = nchannels infos = ffmpeg_parse_infos(filename) self.duration = infos['duration'] if 'video_duration' in infos: self.duration = infos['video_duration'] else: self.duration = infos['duration'] self.infos = infos self.proc = None self.buffersize=buffersize self.buffer= None self.buffer_startframe = 1 self.initialize() self.buffer_around(1)
def __init__(self, filename, buffersize, print_infos=False, fps=44100, nbytes=2, nchannels=2): self.filename = filename self.nbytes = nbytes self.fps = fps self.f = "s%dle" % (8 * nbytes) self.acodec = "pcm_s%dle" % (8 * nbytes) self.nchannels = nchannels infos = ffmpeg_parse_infos(filename) self.duration = infos["duration"] if "video_duration" in infos: self.duration = infos["video_duration"] else: self.duration = infos["duration"] self.infos = infos self.proc = None self.nframes = int(self.fps * self.duration) self.buffersize = min(self.nframes + 1, buffersize) self.buffer = None self.buffer_startframe = 1 self.initialize() self.buffer_around(1)
def __init__(self, video_file, width): ''' Creates the video object. ''' video_info = ffmpeg_parse_infos(video_file) # Full path the video. self.file = video_file # Moviepy VideoFileClip object. self.video = VideoFileClip(video_file).resize(width=width) # Length in seconds of the video. self.runtime = video_info['duration'] # Get the video's file name. file_name, file_extension = os.path.splitext(video_file) self.video_name = file_name.split('/')[-1].replace(' ', '').translate(None, '[]') # The video's resized height and width. self.width = width self.height = float(video_info['video_size'][1]) / float(video_info['video_size'][0]) * width
def test_ffmpeg_parse_infos_for_i926(): d = ffmpeg_parse_infos("media/sintel_with_15_chapters.mp4") assert d["audio_found"]
def test_ffmpeg_parse_infos_multiple_audio_streams(): """Check that ``ffmpeg_parse_infos`` can parse multiple audio streams.""" # Create two mono audio files clip_440_filepath = os.path.join( TMP_DIR, "ffmpeg_parse_infos_multiple_streams_440.mp3" ) clip_880_filepath = os.path.join( TMP_DIR, "ffmpeg_parse_infos_multiple_streams_880.mp3" ) multiple_streams_filepath = os.path.join( TMP_DIR, "ffmpeg_parse_infos_multiple_streams.mp4" ) make_frame_440 = lambda t: np.array( [ np.sin(440 * 2 * np.pi * t), ] ) make_frame_880 = lambda t: np.array( [ np.sin(880 * 2 * np.pi * t), ] ) clip_440 = AudioClip(make_frame_440, fps=22050, duration=0.01) clip_880 = AudioClip(make_frame_880, fps=22050, duration=0.01) clip_440.write_audiofile(clip_440_filepath) clip_880.write_audiofile(clip_880_filepath) # create a MP4 file with multiple streams cmd = [ FFMPEG_BINARY, "-y", "-i", clip_440_filepath, "-i", clip_880_filepath, "-map", "0:a:0", "-map", "0:a:0", multiple_streams_filepath, ] with open(os.devnull, "w") as stderr: subprocess.check_call(cmd, stderr=stderr) # check that `ffmpeg_parse_infos` can parse all the streams data d = ffmpeg_parse_infos(multiple_streams_filepath) # number of inputs and streams assert len(d["inputs"]) == 1 assert len(d["inputs"][0]["streams"]) == 2 default_stream = d["inputs"][0]["streams"][0] ignored_stream = d["inputs"][0]["streams"][1] # default, only the first assert default_stream["default"] assert not ignored_stream["default"] # streams and inputs numbers assert default_stream["stream_number"] == 0 assert ignored_stream["stream_number"] == 1 assert default_stream["input_number"] == 0 assert ignored_stream["input_number"] == 0 # stream type assert default_stream["stream_type"] == "audio" assert ignored_stream["stream_type"] == "audio" # cleanup for filepath in [clip_440_filepath, clip_880_filepath, multiple_streams_filepath]: os.remove(filepath) close_all_clips(locals())
def test_ffmpeg_parse_infos_for_i926(): d = ffmpeg_parse_infos("tests/resource/sintel_with_15_chapters.mp4") assert d['audio_found']
def test_ffmpeg_parse_infos_video_nframes(): d = ffmpeg_parse_infos("media/big_buck_bunny_0_30.webm") assert d["video_n_frames"] == 720 d = ffmpeg_parse_infos("media/bitmap.mp4") assert d["video_n_frames"] == 5
def test_ffmpeg_parse_infos_for_i926(): d = ffmpeg_parse_infos("tests/resource/sintel_with_15_chapters.mp4") assert d['audio_found']
def test_ffmpeg_parse_infos_metadata(): """Check that `ffmpeg_parse_infos` is able to retrieve metadata from files.""" filepath = os.path.join(TMP_DIR, "ffmpeg_parse_infos_metadata.mkv") if os.path.isfile(filepath): os.remove(filepath) # create video with 2 streams, video and audio audioclip = AudioClip( lambda t: np.sin(440 * 2 * np.pi * t), fps=22050 ).with_duration(1) videoclip = BitmapClip([["RGB"]], fps=1).with_duration(1).with_audio(audioclip) # build metadata key-value pairs which will be passed to ``ffmpeg_params`` # argument of ``videoclip.write_videofile`` metadata = { "file": { "title": "Fóò", "comment": "bar", "description": "BAZ", "synopsis": "Testing", }, "video": { "author": "Querty", "title": "hello", "description": "asdf", }, "audio": {"track": "1", "title": "wtr", "genre": "lilihop"}, } ffmpeg_params = [] for metadata_type, data in metadata.items(): option = "-metadata" if metadata_type in ["video", "audio"]: option += ":s:%s:0" % ("v" if metadata_type == "video" else "a") for field, value in data.items(): ffmpeg_params.extend([option, f"{field}={value}"]) languages = { "audio": "eng", "video": "spa", } ffmpeg_params.extend( [ "-metadata:s:a:0", "language=" + languages["audio"], "-metadata:s:v:0", "language=" + languages["video"], ] ) # create file with metadata included videoclip.write_videofile(filepath, codec="libx264", ffmpeg_params=ffmpeg_params) # get information about created file d = ffmpeg_parse_infos(filepath) def get_value_from_dict_using_lower_key(field, dictionary): """Obtains a value from a dictionary using a key, no matter if the key is uppercased in the dictionary. This function is needed because some media containers convert to uppercase metadata field names. """ value = None for d_field, d_value in dictionary.items(): if str(d_field).lower() == field: value = d_value break return value # assert file metadata for field, value in metadata["file"].items(): assert get_value_from_dict_using_lower_key(field, d["metadata"]) == value # assert streams metadata streams = {"audio": None, "video": None} for stream in d["inputs"][0]["streams"]: streams[stream["stream_type"]] = stream for stream_type, stream in streams.items(): for field, value in metadata[stream_type].items(): assert ( get_value_from_dict_using_lower_key(field, stream["metadata"]) == value ) # assert stream languages for stream_type, stream in streams.items(): assert stream["language"] == languages[stream_type] os.remove(filepath) close_all_clips(locals())
def dump_frames(video_path, output_dir, fps, logger_name=None): """Dump frames at frames_per_second from a video to output_dir. If frames_per_second is None, the clip's fps attribute is used instead.""" output_dir.mkdir(exist_ok=True, parents=True) if logger_name: file_logger = logging.getLogger(logger_name) else: file_logger = logging.root try: video_info = ffmpeg_parse_infos(video_path) video_fps = video_info['video_fps'] except OSError: logging.error('Unable to open video (%s), skipping.' % video_path) logging.exception('Exception:') return except KeyError: logging.error( 'Unable to extract metadata about video (%s), skipping.' % video_path) logging.exception('Exception:') return info_path = '{}/info.json'.format(output_dir) name_format = '{}/frame%04d.png'.format(output_dir) if fps is None or fps == 0: fps = video_fps # Extract all frames are_frames_dumped_wrapper = functools.partial( are_frames_dumped, video_path=video_path, output_dir=output_dir, expected_fps=fps, expected_info_path=info_path, expected_name_format=name_format) if are_frames_dumped_wrapper(log_reason=False): file_logger.info('Frames for {} exist, skipping...'.format(video_path)) return successfully_wrote_images = False try: if fps == video_fps: cmd = ['ffmpeg', '-i', video_path, name_format] else: cmd = [ 'ffmpeg', '-i', video_path, '-vf', 'fps={}'.format(fps), name_format ] subprocess.check_output(cmd, stderr=subprocess.STDOUT) successfully_wrote_images = True except subprocess.CalledProcessError as e: logging.error("Failed to dump images for %s", video_path) logging.error(e) logging.error(e.output.decode('utf-8')) if successfully_wrote_images: info = { 'frames_per_second': fps, 'input_video_path': os.path.abspath(video_path) } with open(info_path, 'w') as info_file: json.dump(info, info_file) if not are_frames_dumped_wrapper(log_reason=True): logging.error( "Images for {} don't seem to be dumped properly!".format( video_path))
def dump_frames(video_path, output_dir, fps, extension='.jpg', jpeg_qscale=2): """Dump frames at frames_per_second from a video to output_dir. If frames_per_second is None, the clip's fps attribute is used instead.""" output_dir.mkdir(exist_ok=True, parents=True) if extension[0] != '.': extension = f'.{extension}' try: video_info = ffmpeg_parse_infos(str(video_path)) video_fps = video_info['video_fps'] except OSError: logging.exception('Unable to open video (%s), skipping.' % video_path) raise except KeyError: logging.error( 'Unable to extract metadata about video (%s), skipping.' % video_path) logging.exception('Exception:') return info_path = '{}/info.json'.format(output_dir) name_format = '{}/frame%04d{}'.format(output_dir, extension) if fps is None or fps == 0: fps = video_fps # Extract all frames are_frames_dumped_wrapper = functools.partial( are_frames_dumped, video_path=video_path, output_dir=output_dir, expected_fps=fps, expected_info_path=info_path, expected_name_format=name_format) if extension.lower() in ('.jpg', '.jpeg'): qscale = ['-qscale:v', str(jpeg_qscale)] else: qscale = [] if are_frames_dumped_wrapper(log_reason=False): return successfully_wrote_images = False try: if fps == video_fps: cmd = ['ffmpeg', '-i', str(video_path)] + qscale + [name_format] else: cmd = ['ffmpeg', '-i', str(video_path) ] + qscale + ['-vf', 'fps={}'.format(fps), name_format] subprocess.check_output(cmd, stderr=subprocess.STDOUT) successfully_wrote_images = True except subprocess.CalledProcessError as e: logging.exception("Failed to dump images for %s", video_path) logging.error(e.output.decode('utf-8')) raise if successfully_wrote_images: info = { 'frames_per_second': fps, 'input_video_path': os.path.abspath(video_path) } with open(info_path, 'w') as info_file: json.dump(info, info_file) if not are_frames_dumped_wrapper(log_reason=True): logging.warning( "Images for {} don't seem to be dumped properly!".format( video_path))
def test_ffmpeg_parse_infos_duration(): infos = ffmpeg_parse_infos("media/big_buck_bunny_0_30.webm") assert infos["video_nframes"] == 720 infos = ffmpeg_parse_infos("media/bitmap.mp4") assert infos["video_nframes"] == 5
def test_ffmpeg_parse_video_rotation(): d = ffmpeg_parse_infos("media/rotated-90-degrees.mp4") assert d["video_rotation"] == 90 assert d["video_size"] == [1920, 1080]
def html_embed(clip, filetype=None, maxduration=60, rd_kwargs=None, center=True, **html_kwargs): """Returns HTML5 code embedding the clip. Parameters ---------- clip : moviepy.Clip.Clip Either a file name, or a clip to preview. Either an image, a sound or a video. Clips will actually be written to a file and embedded as if a filename was provided. filetype : str, optional One of 'video','image','audio'. If None is given, it is determined based on the extension of ``filename``, but this can bug. maxduration : float, optional An error will be raised if the clip's duration is more than the indicated value (in seconds), to avoid spoiling the browser's cache and the RAM. rd_kwargs : dict, optional Keyword arguments for the rendering, like ``dict(fps=15, bitrate="50k")``. Allow you to give some options to the render process. You can, for example, disable the logger bar passing ``dict(logger=None)``. center : bool, optional If true (default), the content will be wrapped in a ``<div align=middle>`` HTML container, so the content will be displayed at the center. html_kwargs Allow you to give some options, like ``width=260``, ``autoplay=True``, ``loop=1`` etc. Examples -------- >>> from moviepy.editor import * >>> # later ... >>> html_embed(clip, width=360) >>> html_embed(clip.audio) >>> clip.write_gif("test.gif") >>> html_embed('test.gif') >>> clip.save_frame("first_frame.jpeg") >>> html_embed("first_frame.jpeg") """ if rd_kwargs is None: # pragma: no cover rd_kwargs = {} if "Clip" in str(clip.__class__): TEMP_PREFIX = "__temp__" if isinstance(clip, ImageClip): filename = TEMP_PREFIX + ".png" kwargs = {"filename": filename, "with_mask": True} argnames = inspect.getfullargspec(clip.save_frame).args kwargs.update({ key: value for key, value in rd_kwargs.items() if key in argnames }) clip.save_frame(**kwargs) elif isinstance(clip, VideoClip): filename = TEMP_PREFIX + ".mp4" kwargs = {"filename": filename, "preset": "ultrafast"} kwargs.update(rd_kwargs) clip.write_videofile(**kwargs) elif isinstance(clip, AudioClip): filename = TEMP_PREFIX + ".mp3" kwargs = {"filename": filename} kwargs.update(rd_kwargs) clip.write_audiofile(**kwargs) else: raise ValueError( "Unknown class for the clip. Cannot embed and preview.") return html_embed( filename, maxduration=maxduration, rd_kwargs=rd_kwargs, center=center, **html_kwargs, ) filename = clip options = " ".join( ["%s='%s'" % (str(k), str(v)) for k, v in html_kwargs.items()]) name, ext = os.path.splitext(filename) ext = ext[1:] if filetype is None: ext = filename.split(".")[-1].lower() if ext == "gif": filetype = "image" elif ext in extensions_dict: filetype = extensions_dict[ext]["type"] else: raise ValueError( "No file type is known for the provided file. Please provide " "argument `filetype` (one of 'image', 'video', 'sound') to the " "ipython display function.") if filetype == "video": # The next lines set the HTML5-cvompatible extension and check that the # extension is HTML5-valid exts_htmltype = {"mp4": "mp4", "webm": "webm", "ogv": "ogg"} allowed_exts = " ".join(exts_htmltype.keys()) try: ext = exts_htmltype[ext] except Exception: raise ValueError("This video extension cannot be displayed in the " "IPython Notebook. Allowed extensions: " + allowed_exts) if filetype in ["audio", "video"]: duration = ffmpeg_parse_infos(filename, decode_file=True)["duration"] if duration > maxduration: raise ValueError(( "The duration of video %s (%.1f) exceeds the 'maxduration'" " attribute. You can increase 'maxduration', by passing" " 'maxduration' parameter to ipython_display function." " But note that embedding large videos may take all the memory" " away!") % (filename, duration)) with open(filename, "rb") as file: data = b64encode(file.read()).decode("utf-8") template = templates[filetype] result = template % {"data": data, "options": options, "ext": ext} if center: result = r"<div align=middle>%s</div>" % result return result
def html_embed(clip, filetype=None, maxduration=60, rd_kwargs=None, center=True, **html_kwargs): """Returns HTML5 code embedding the clip clip Either a file name, or a clip to preview. Either an image, a sound or a video. Clips will actually be written to a file and embedded as if a filename was provided. filetype One of 'video','image','audio'. If None is given, it is determined based on the extension of ``filename``, but this can bug. rd_kwargs Keyword arguments for the rendering, like ``{'fps':15, 'bitrate':'50k'}`` html_kwargs Allow you to give some options, like ``width=260``, ``autoplay=True``, ``loop=1`` etc. Examples -------- TODO Create example based on ipython_display examples """ if rd_kwargs is None: rd_kwargs = {} if "Clip" in str(clip.__class__): TEMP_PREFIX = "__temp__" if isinstance(clip, ImageClip): filename = TEMP_PREFIX + ".png" kwargs = {"filename": filename, "with_mask": True} kwargs.update(rd_kwargs) clip.save_frame(**kwargs) elif isinstance(clip, VideoClip): filename = TEMP_PREFIX + ".mp4" kwargs = {"filename": filename, "preset": "ultrafast"} kwargs.update(rd_kwargs) clip.write_videofile(**kwargs) elif isinstance(clip, AudioClip): filename = TEMP_PREFIX + ".mp3" kwargs = {"filename": filename} kwargs.update(rd_kwargs) clip.write_audiofile(**kwargs) else: raise ValueError( "Unknown class for the clip. Cannot embed and preview.") return html_embed( filename, maxduration=maxduration, rd_kwargs=rd_kwargs, center=center, **html_kwargs, ) filename = clip options = " ".join( ["%s='%s'" % (str(k), str(v)) for k, v in html_kwargs.items()]) name, ext = os.path.splitext(filename) ext = ext[1:] if filetype is None: ext = filename.split(".")[-1].lower() if ext == "gif": filetype = "image" elif ext in extensions_dict: filetype = extensions_dict[ext]["type"] else: raise ValueError( "No file type is known for the provided file. Please provide " "argument `filetype` (one of 'image', 'video', 'sound') to the " "ipython display function.") if filetype == "video": # The next lines set the HTML5-cvompatible extension and check that the # extension is HTML5-valid exts_htmltype = {"mp4": "mp4", "webm": "webm", "ogv": "ogg"} allowed_exts = " ".join(exts_htmltype.keys()) try: ext = exts_htmltype[ext] except Exception: raise ValueError("This video extension cannot be displayed in the " "IPython Notebook. Allowed extensions: " + allowed_exts) if filetype in ["audio", "video"]: duration = ffmpeg_parse_infos(filename, decode_file=True)["duration"] if duration > maxduration: raise ValueError(( "The duration of video %s (%.1f) exceeds the 'maxduration'" " attribute. You can increase 'maxduration', by passing" " 'maxduration' parameter to ipython_display function." " But note that embedding large videos may take all the memory" " away!") % (filename, duration)) with open(filename, "rb") as file: data = b64encode(file.read()).decode("utf-8") template = templates[filetype] result = template % {"data": data, "options": options, "ext": ext} if center: result = r"<div align=middle>%s</div>" % result return result
def write_video(self, input_filename, output_filename, silence_list, progress_bar_logger): self.video_generation_progress.emit(0) videoclip = VideoFileClip(input_filename) # Choose output audio/video bitrate based on values for input video # (Note : in fact specifying bitrate is only relevant for some codecs, e.g. libx264) output_video_bitrate = '24000k' output_audio_bitrate = '190k' tmpfilename = "tmpfile.out" os.system("ffmpeg -i \"%s\" 2> %s" % (input_filename, tmpfilename)) tmpfile = open(tmpfilename, 'r') lines = tmpfile.readlines() tmpfile.close() os.remove(tmpfilename) for line in lines: line = line.strip() if line.startswith('Stream #0:0'): search = re.search('(\d+ kb/s)', line) if search is not None: output_video_bitrate = search.group(1) output_video_bitrate = output_video_bitrate[:-3] else: print("Note: took default video bitrate") if line.startswith('Stream #0:1'): search = re.search('(\d+ kb/s)', line) if search is not None: output_audio_bitrate = search.group(1) output_audio_bitrate = output_audio_bitrate[:-3] else: print("Note: took default audio bitrate") # Choose output audio fps based on value for input video output_audio_fps = 48000 infos = ffmpeg_parse_infos(input_filename) if 'audio_fps' in infos: output_audio_fps = infos['audio_fps'] # Remove pseudo-silences clean_silence_list = [] for silence in silence_list: if silence.start_cut() < silence.stop_cut(): clean_silence_list.append(silence) n_sil = len(clean_silence_list) # Subclip before the first silence (if exist) subclips = [] start_time = 0 if n_sil > 0: stop_time = clean_silence_list[0].start_cut() else: stop_time = videoclip.duration if start_time < stop_time: subclips.append(videoclip.subclip(start_time, stop_time)) # Subclips between silences for i in range(n_sil - 1): start_time = clean_silence_list[i].stop_cut() stop_time = clean_silence_list[i + 1].start_cut() subclips.append(videoclip.subclip(start_time, stop_time)) val = i / (n_sil - 1) * 11 self.video_generation_progress.emit(val) # Subclip after last silence (if exist) if n_sil > 0: start_time = clean_silence_list[n_sil - 1].stop_cut() stop_time = videoclip.duration if start_time != stop_time: subclips.append(videoclip.subclip(start_time, stop_time)) self.video_generation_progress.emit(11) # Create final clip final_clip = concatenate_videoclips(subclips) final_clip.write_videofile( output_filename, fps=videoclip.fps, audio_fps=output_audio_fps, bitrate=output_video_bitrate, audio_bitrate=output_audio_bitrate, preset='ultrafast', # 'placebo' = very slow but smaller file size, # 'ultrafast' = a lot faster but silghtly # higher file size (and same quality) logger=progress_bar_logger) self.video_generation_progress.emit(99) videoclip.close() self.video_generation_progress.emit(100)
def dump_frames(video_path, output_directory, frames_per_second, file_logger_name): """Dump frames at frames_per_second from a video to output_directory. If frames_per_second is None, the clip's fps attribute is used instead.""" if not os.path.isdir(output_directory): os.mkdir(output_directory) file_logger = logging.getLogger(file_logger_name) try: video_info = ffmpeg_parse_infos(video_path) video_fps = video_info['video_fps'] video_duration = video_info['duration'] except OSError as e: logging.error('Unable to open video (%s), skipping.' % video_path) logging.exception('Exception:') return except KeyError as e: logging.error('Unable to extract metadata about video (%s), skipping.' % video_path) logging.exception('Exception:') return info_path = '{}/info.json'.format(output_directory) name_format = '{}/frame%04d.png'.format(output_directory) extract_all_frames = frames_per_second is None if extract_all_frames: frames_per_second = video_fps frames_already_dumped_helper = lambda log_reason: \ frames_already_dumped(video_path, output_directory, frames_per_second, info_path, name_format, log_reason) if frames_already_dumped_helper(False): file_logger.info('Frames for {} exist, skipping...'.format(video_path)) return successfully_wrote_images = False try: if extract_all_frames: cmd = ['ffmpeg', '-i', video_path, name_format] else: cmd = ['ffmpeg', '-i', video_path, '-vf', 'fps={}'.format(frames_per_second), name_format] subprocess.check_output(cmd, stderr=subprocess.STDOUT) successfully_wrote_images = True except subprocess.CalledProcessError as e: logging.error("Failed to dump images for %s", video_path) logging.error(e) logging.error(e.output.decode('utf-8')) if successfully_wrote_images: info = {'frames_per_second': frames_per_second, 'input_video_path': os.path.abspath(video_path)} with open(info_path, 'w') as info_file: json.dump(info, info_file) if not frames_already_dumped_helper(True): logging.error( "Images for {} don't seem to be dumped properly!".format( video_path))
def get_sampling_rate(filename): ''' Use moviepy/FFMPEG to get the sampling rate ''' infos = ffmpeg_parse_infos(filename) return infos.get('audio_fps', 44100)
def main(): """ The main function of the application. Check the input file for correctness. Creates the main window, the thread pool, etc. Connects all handlers :return: None """ if len(sys.argv) != 2: sys.exit("Usage: ./main.py <video file>") if not os.path.isfile(sys.argv[1]): sys.exit("Video file is not found") orig_filename = os.path.abspath(sys.argv[1]) splitted = os.path.splitext(orig_filename) result_filename = splitted[0] + "_edited" + splitted[1] try: ffmpeg_parse_infos(orig_filename) except IOError: sys.exit("Can't detect valid video in specified file") app = QtGui.QApplication([]) app.setApplicationName("Title machine") window = QtGui.QMainWindow() ui = Ui_MainWindow() ui.setupUi(window) ui.video_seek = SeekSlider(ui.layoutWidget) ui.video_seek.setIconVisible(False) ui.video_seek.setPageStep(1000) ui.video_seek.setSingleStep(200) ui.v_layout_1.addWidget(ui.video_seek) ui.video_canvas = VideoCanvas() ui.video_canvas.set_source(orig_filename) ui.video_seek.setMediaObject(ui.video_canvas.media) ui.v_layout_1.insertWidget(0, ui.video_canvas) pool = Pool(1) log_watcher = QtCore.QFileSystemWatcher([os.path.dirname(result_filename)]) ui.button_process.clicked.connect( on_process_clicked(ui, orig_filename, result_filename, pool)) ui.button_add_caption.clicked.connect(on_add_caption_clicked(ui)) ui.button_remove_caption.clicked.connect(on_remove_caption_clicked(ui)) ui.table_captions.cellChanged.connect(on_tablewidget_cell_changed(ui)) ui.button_play_pause.clicked.connect(on_button_play_pause_clicked(ui)) ui.video_canvas.media.stateChanged.connect(on_video_state_changed(ui)) ui.video_canvas.media.tick.connect(on_video_tick(ui)) ui.video_canvas.media.totalTimeChanged.connect(on_video_total_time_changed) log_watcher.directoryChanged.connect(on_log_dir_changed(log_watcher, result_filename)) log_watcher.fileChanged.connect(on_log_file_changed(ui, log_watcher)) app.lastWindowClosed.connect(on_app_quit(ui, pool)) ui.textbrowser_log.insertPlainText("Welcome to Title Machine!") ui.video_canvas.media.pause() window.show() app.exec_()
def get_audio_sampling_rate(self, filename: str): infos = ffmpeg_parse_infos(filename) fps = infos.get('audio_fps', 44100) if fps == 'unknown': fps = 44100 return fps