From 338335b988babed6499fa504d15070f0067b4ee2 Mon Sep 17 00:00:00 2001 From: vermont Date: Fri, 27 Jan 2023 20:47:36 -0500 Subject: [PATCH 1/2] ffmpeg_reader: set default streams automatically if unspecified fix #869 #899 --- moviepy/video/io/ffmpeg_reader.py | 64 ++++++++++++++++--------------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/moviepy/video/io/ffmpeg_reader.py b/moviepy/video/io/ffmpeg_reader.py index b7be97177..1e8618849 100644 --- a/moviepy/video/io/ffmpeg_reader.py +++ b/moviepy/video/io/ffmpeg_reader.py @@ -343,8 +343,9 @@ def _reset_state(self): # should be ignored self._inside_output = False - # flag which indicates that a default stream has not been found yet - self._default_stream_found = False + # map from stream type to default stream + # if a default stream is not indicated, pick the first one available + self._default_streams = {} # current input file, stream and chapter, which will be built at runtime self._current_input_file = {"streams": []} @@ -443,20 +444,13 @@ def parse(self): "stream_number": stream_number, "stream_type": stream_type_lower, "language": language, - "default": not self._default_stream_found - or line.endswith("(default)"), + "default": (stream_type_lower not in self._default_streams) and line.endswith("(default)"), } - self._default_stream_found = True # for default streams, set their numbers globally, so it's # easy to get without iterating all if self._current_stream["default"]: - self.result[ - f"default_{stream_type_lower}_input_number" - ] = input_number - self.result[ - f"default_{stream_type_lower}_stream_number" - ] = stream_number + self._default_streams[stream_type_lower] = self._current_stream # exit chapter if self._current_chapter: @@ -475,13 +469,13 @@ def parse(self): input_number ] - # add new input file to self.result + # add new input file to result self.result["inputs"].append(self._current_input_file) self._current_input_file = {"input_number": input_number} # parse relevant data by stream type try: - global_data, stream_data = self.parse_data_by_stream_type( + stream_data = self.parse_data_by_stream_type( stream_type, line ) except NotImplementedError as exc: @@ -489,7 +483,6 @@ def parse(self): f"{str(exc)}\nffmpeg output:\n\n{self.infos}", UserWarning ) else: - self.result.update(global_data) self._current_stream.update(stream_data) elif line.startswith(" Metadata:"): # enter group " Metadata:" @@ -552,7 +545,7 @@ def parse(self): self._last_metadata_field_added = field self._current_chapter["metadata"][field] = value - # last input file, must be included in self.result + # last input file, must be included in the result if self._current_input_file: self._current_input_file["streams"].append(self._current_stream) # include their chapters, if there are @@ -562,6 +555,27 @@ def parse(self): ] self.result["inputs"].append(self._current_input_file) + # set any missing default automatically + for stream in self._current_input_file["streams"]: + if stream["stream_type"] not in self._default_streams: + self._default_streams[stream["stream_type"]] = stream + stream["default"] = True + + # set some global info based on the defaults + for stream_type_lower, stream_data in self._default_streams.items(): + self.result[f"default_{stream_type_lower}_input_number"] = stream_data["input_number"] + self.result[f"default_{stream_type_lower}_stream_number"] = stream_data["stream_number"] + + if stream_type_lower == "audio": + self.result["audio_found"] = True + self.result["audio_fps"] = stream_data["fps"] + self.result["audio_bitrate"] = stream_data["bitrate"] + elif stream_type_lower == "video": + self.result["video_found"] = True + self.result["video_size"] = stream_data.get("size", None) + self.result["video_bitrate"] = stream_data.get("bitrate", None) + self.result["video_fps"] = stream_data["fps"] + # some video duration utilities if self.result["video_found"] and self.check_duration: self.result["video_n_frames"] = int( @@ -599,7 +613,7 @@ def parse_data_by_stream_type(self, stream_type, line): return { "Audio": self.parse_audio_stream_data, "Video": self.parse_video_stream_data, - "Data": lambda _line: ({}, {}), + "Data": lambda _line: {}, }[stream_type](line) except KeyError: raise NotImplementedError( @@ -609,7 +623,7 @@ def parse_data_by_stream_type(self, stream_type, line): def parse_audio_stream_data(self, line): """Parses data from "Stream ... Audio" line.""" - global_data, stream_data = ({"audio_found": True}, {}) + stream_data = {} try: stream_data["fps"] = int(re.search(r" (\d+) Hz", line).group(1)) except (AttributeError, ValueError): @@ -620,14 +634,11 @@ def parse_audio_stream_data(self, line): stream_data["bitrate"] = ( int(match_audio_bitrate.group(1)) if match_audio_bitrate else None ) - if self._current_stream["default"]: - global_data["audio_fps"] = stream_data["fps"] - global_data["audio_bitrate"] = stream_data["bitrate"] - return (global_data, stream_data) + return stream_data def parse_video_stream_data(self, line): """Parses data from "Stream ... Video" line.""" - global_data, stream_data = ({"video_found": True}, {}) + stream_data = {} try: match_video_size = re.search(r" (\d+)x(\d+)[,\s]", line) @@ -679,14 +690,7 @@ def parse_video_stream_data(self, line): fps = x * coef stream_data["fps"] = fps - if self._current_stream["default"] or "video_size" not in self.result: - global_data["video_size"] = stream_data.get("size", None) - if self._current_stream["default"] or "video_bitrate" not in self.result: - global_data["video_bitrate"] = stream_data.get("bitrate", None) - if self._current_stream["default"] or "video_fps" not in self.result: - global_data["video_fps"] = stream_data["fps"] - - return (global_data, stream_data) + return stream_data def parse_fps(self, line): """Parses number of FPS from a line of the ``ffmpeg -i`` command output.""" From 858584deaeba58355359e8a72acfad866874b02a Mon Sep 17 00:00:00 2001 From: vermont Date: Sat, 9 Mar 2024 13:57:13 -0500 Subject: [PATCH 2/2] Fix formatting. --- examples/soundtrack.py | 1 + moviepy/Clip.py | 1 + moviepy/audio/fx/all/__init__.py | 1 + moviepy/decorators.py | 1 + moviepy/tools.py | 1 + moviepy/video/fx/all/__init__.py | 1 + moviepy/video/io/ffmpeg_reader.py | 16 ++++++++++------ moviepy/video/tools/credits.py | 1 + tests/test_VideoFileClip.py | 1 + 9 files changed, 18 insertions(+), 6 deletions(-) diff --git a/examples/soundtrack.py b/examples/soundtrack.py index 48adbb998..c2efd8c05 100644 --- a/examples/soundtrack.py +++ b/examples/soundtrack.py @@ -1,4 +1,5 @@ """A simple test script on how to put a soundtrack to a movie.""" + from moviepy import * diff --git a/moviepy/Clip.py b/moviepy/Clip.py index 5c2d88b2d..63857296f 100644 --- a/moviepy/Clip.py +++ b/moviepy/Clip.py @@ -1,6 +1,7 @@ """Implements the central object of MoviePy, the Clip, and all the methods that are common to the two subclasses of Clip, VideoClip and AudioClip. """ + import copy as _copy from functools import reduce from numbers import Real diff --git a/moviepy/audio/fx/all/__init__.py b/moviepy/audio/fx/all/__init__.py index c6cadf1c6..d51e65959 100644 --- a/moviepy/audio/fx/all/__init__.py +++ b/moviepy/audio/fx/all/__init__.py @@ -4,6 +4,7 @@ Use the fx method directly from the clip instance (e.g. ``clip.audio_normalize(...)``) or import the function from moviepy.audio.fx instead. """ + import warnings from .. import * # noqa 401,F403 diff --git a/moviepy/decorators.py b/moviepy/decorators.py index 621e35853..e080ab3de 100644 --- a/moviepy/decorators.py +++ b/moviepy/decorators.py @@ -1,4 +1,5 @@ """Decorators used by moviepy.""" + import inspect import os diff --git a/moviepy/tools.py b/moviepy/tools.py index a260e75bf..3dda375eb 100644 --- a/moviepy/tools.py +++ b/moviepy/tools.py @@ -1,4 +1,5 @@ """Misc. useful functions that can be used at many places in the program.""" + import os import subprocess as sp import warnings diff --git a/moviepy/video/fx/all/__init__.py b/moviepy/video/fx/all/__init__.py index 34b0627dd..0e4d6f2f0 100644 --- a/moviepy/video/fx/all/__init__.py +++ b/moviepy/video/fx/all/__init__.py @@ -4,6 +4,7 @@ Use the fx method directly from the clip instance (e.g. ``clip.resize(...)``) or import the function from moviepy.video.fx instead. """ + import warnings from moviepy.video.fx import * # noqa F401,F403 diff --git a/moviepy/video/io/ffmpeg_reader.py b/moviepy/video/io/ffmpeg_reader.py index 1e8618849..a3be4cb55 100644 --- a/moviepy/video/io/ffmpeg_reader.py +++ b/moviepy/video/io/ffmpeg_reader.py @@ -1,4 +1,5 @@ """Implements all the functions to read a video or a picture using ffmpeg.""" + import os import re import subprocess as sp @@ -444,7 +445,8 @@ def parse(self): "stream_number": stream_number, "stream_type": stream_type_lower, "language": language, - "default": (stream_type_lower not in self._default_streams) and line.endswith("(default)"), + "default": (stream_type_lower not in self._default_streams) + and line.endswith("(default)"), } # for default streams, set their numbers globally, so it's @@ -475,9 +477,7 @@ def parse(self): # parse relevant data by stream type try: - stream_data = self.parse_data_by_stream_type( - stream_type, line - ) + stream_data = self.parse_data_by_stream_type(stream_type, line) except NotImplementedError as exc: warnings.warn( f"{str(exc)}\nffmpeg output:\n\n{self.infos}", UserWarning @@ -563,8 +563,12 @@ def parse(self): # set some global info based on the defaults for stream_type_lower, stream_data in self._default_streams.items(): - self.result[f"default_{stream_type_lower}_input_number"] = stream_data["input_number"] - self.result[f"default_{stream_type_lower}_stream_number"] = stream_data["stream_number"] + self.result[f"default_{stream_type_lower}_input_number"] = stream_data[ + "input_number" + ] + self.result[f"default_{stream_type_lower}_stream_number"] = stream_data[ + "stream_number" + ] if stream_type_lower == "audio": self.result["audio_found"] = True diff --git a/moviepy/video/tools/credits.py b/moviepy/video/tools/credits.py index f5b04025d..a7bf9fb5c 100644 --- a/moviepy/video/tools/credits.py +++ b/moviepy/video/tools/credits.py @@ -1,6 +1,7 @@ """Contains different functions to make end and opening credits, even though it is difficult to fill everyone needs in this matter. """ + from moviepy.decorators import convert_path_to_string from moviepy.video.compositing.CompositeVideoClip import CompositeVideoClip from moviepy.video.fx.resize import resize diff --git a/tests/test_VideoFileClip.py b/tests/test_VideoFileClip.py index e92f78e04..bd8f996e1 100644 --- a/tests/test_VideoFileClip.py +++ b/tests/test_VideoFileClip.py @@ -1,4 +1,5 @@ """Video file clip tests meant to be run with pytest.""" + import copy import os