From d88747cc3531a5e25b3e280caf2fff0dd3c9933e Mon Sep 17 00:00:00 2001 From: vermont Date: Fri, 27 Jan 2023 20:50:23 -0500 Subject: [PATCH 1/4] Fix ffmpeg seeking behavior for better frame-exactitude (cherry picked from commit ef3a16ad81162bdccff1edbf983cce103e9134d7) --- moviepy/video/io/ffmpeg_reader.py | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/moviepy/video/io/ffmpeg_reader.py b/moviepy/video/io/ffmpeg_reader.py index b7be97177..10382c3c2 100644 --- a/moviepy/video/io/ffmpeg_reader.py +++ b/moviepy/video/io/ffmpeg_reader.py @@ -82,6 +82,27 @@ def initialize(self, start_time=0): """ self.close(delete_lastread=False) # if any + # self.pos represents the (0-indexed) index of the frame that is next in line + # to be read by self.read_frame(). + # Eg when self.pos is 1, the 2nd frame will be read next. + self.pos = self.get_frame_number(start_time) + + # Getting around a difference between ffmpeg and moviepy seeking: + # "moviepy seek" means "get the frame displayed at time t" + # Hence given a 29.97 FPS video, seeking to .01s means "get frame 0". + # "ffmpeg seek" means "skip all frames until you reach time t". + # This time, seeking to .01s means "get frame 1". Surprise! + # + # (In 30fps, timestamps like 2.0s, 3.5s will give the same frame output + # under both rules, for the timestamp can be represented exactly in + # decimal.) + # + # So we'll subtract an epsilon from the timestamp given to ffmpeg. + if self.pos != 0: + start_time = self.pos * (1 / self.fps) - 0.00001 + else: + start_time = 0.0 + if start_time != 0: offset = min(1, start_time) i_arg = [ @@ -123,11 +144,6 @@ def initialize(self, start_time=0): } ) self.proc = sp.Popen(cmd, **popen_params) - - # self.pos represents the (0-indexed) index of the frame that is next in line - # to be read by self.read_frame(). - # Eg when self.pos is 1, the 2nd frame will be read next. - self.pos = self.get_frame_number(start_time) self.lastread = self.read_frame() def skip_frames(self, n=1): From 1da7b8dde90e0ab8e0bae47ce5fcae9fc891a8ff Mon Sep 17 00:00:00 2001 From: vermont Date: Sat, 28 Jan 2023 14:52:24 -0500 Subject: [PATCH 2/4] Fix typo in ffmpeg_reader, lastread -> last_read (cherry picked from commit ec19ceb40461cb48b9da0237e101b29fd18c2284) --- moviepy/video/io/ffmpeg_reader.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/moviepy/video/io/ffmpeg_reader.py b/moviepy/video/io/ffmpeg_reader.py index 10382c3c2..0fe657d91 100644 --- a/moviepy/video/io/ffmpeg_reader.py +++ b/moviepy/video/io/ffmpeg_reader.py @@ -80,7 +80,7 @@ def initialize(self, start_time=0): Sets self.pos to the appropriate value (1 if start_time == 0 because it pre-reads the first frame). """ - self.close(delete_lastread=False) # if any + self.close(delete_last_read=False) # if any # self.pos represents the (0-indexed) index of the frame that is next in line # to be read by self.read_frame(). @@ -144,7 +144,7 @@ def initialize(self, start_time=0): } ) self.proc = sp.Popen(cmd, **popen_params) - self.lastread = self.read_frame() + self.last_read = self.read_frame() def skip_frames(self, n=1): """Reads and throws away n frames""" @@ -159,7 +159,7 @@ def read_frame(self): """ Reads the next frame from the file. Note that upon (re)initialization, the first frame will already have been read - and stored in ``self.lastread``. + and stored in ``self.last_read``. """ w, h = self.size nbytes = self.depth * w * h @@ -234,7 +234,7 @@ def get_frame(self, t): elif (pos < self.pos) or (pos > self.pos + 100): # We can't just skip forward to `pos` or it would take too long self.initialize(t) - return self.lastread + return self.last_read else: # If pos == self.pos + 1, this line has no effect self.skip_frames(pos - self.pos - 1) @@ -249,7 +249,7 @@ def get_frame_number(self, t): # are getting the nth frame by writing get_frame(n/fps). return int(self.fps * t + 0.00001) - def close(self, delete_lastread=True): + def close(self, delete_last_read=True): """Closes the reader terminating the process, if is still open.""" if self.proc: if self.proc.poll() is None: @@ -258,7 +258,7 @@ def close(self, delete_lastread=True): self.proc.stderr.close() self.proc.wait() self.proc = None - if delete_lastread and hasattr(self, "last_read"): + if delete_last_read and hasattr(self, "last_read"): del self.last_read def __del__(self): From dbc0d352a2c41bc9be24e163b962531ec3cab9ed Mon Sep 17 00:00:00 2001 From: vermont Date: Wed, 14 Feb 2024 06:16:41 -0500 Subject: [PATCH 3/4] Support old "lastread" name as property, for existing libraries that use it. --- moviepy/video/io/ffmpeg_reader.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/moviepy/video/io/ffmpeg_reader.py b/moviepy/video/io/ffmpeg_reader.py index 0fe657d91..a40af762e 100644 --- a/moviepy/video/io/ffmpeg_reader.py +++ b/moviepy/video/io/ffmpeg_reader.py @@ -249,6 +249,13 @@ def get_frame_number(self, t): # are getting the nth frame by writing get_frame(n/fps). return int(self.fps * t + 0.00001) + @property + def lastread(self): + """Support old name of the "last_read" attribute, which may be used + by existing libraries, such as scenedetect. + """ + return self.last_read + def close(self, delete_last_read=True): """Closes the reader terminating the process, if is still open.""" if self.proc: From 436fb0824eda07b425029f425dd2f075880c879f Mon Sep 17 00:00:00 2001 From: vermont Date: Sat, 9 Mar 2024 14:02:04 -0500 Subject: [PATCH 4/4] Fix formatting. --- 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 | 13 +++++++------ moviepy/video/tools/credits.py | 1 + 7 files changed, 13 insertions(+), 6 deletions(-) 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 a40af762e..1dc1acd59 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 @@ -474,12 +475,12 @@ def parse(self): # 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.result[f"default_{stream_type_lower}_input_number"] = ( + input_number + ) + self.result[f"default_{stream_type_lower}_stream_number"] = ( + stream_number + ) # exit chapter if self._current_chapter: 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