Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for retrieve clip frames number using 'clip.n_frames' #1471

Merged
merged 5 commits into from
Jan 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added <!-- for new features -->
- Support for `copy.copy(clip)` and `copy.deepcopy(clip)` with same behaviour as `clip.copy()` [\#1442](https://github.com/Zulko/moviepy/pull/1442)
- `audio.fx.multiply_stereo_volume` to control volume by audio channels [\#1424](https://github.com/Zulko/moviepy/pull/1424)
- Support for retrieve clip frames number using `clip.n_frames` [\#1471](https://github.com/Zulko/moviepy/pull/1471)

### Changed <!-- for changes in existing functionality -->
- Lots of method and parameter names have been changed. This will be explained better in the documentation soon. See https://github.com/Zulko/moviepy/pull/1170 for more information. [\#1170](https://github.com/Zulko/moviepy/pull/1170)
- Changed recommended import from `import moviepy.editor` to `import moviepy`. This change is fully backwards compatible [\#1340](https://github.com/Zulko/moviepy/pull/1340)
- Renamed `audio.fx.volumex` to `audio.fx.multiply_volume` [\#1424](https://github.com/Zulko/moviepy/pull/1424)
- Renamed `cols_widths` argument of `clips_array` function by `cols_heights` [\#1465](https://github.com/Zulko/moviepy/pull/1465)
- `video_nframes` attribute of dictionary returned from `ffmpeg_parse_infos` renamed to `video_n_frames` [\#1471](https://github.com/Zulko/moviepy/pull/1471)

### Deprecated <!-- for soon-to-be removed features -->
- `moviepy.video.fx.all` and `moviepy.audio.fx.all`. Use the fx method directly from the clip instance or import the fx function from `moviepy.video.fx` and `moviepy.audio.fx`. [\#1105](https://github.com/Zulko/moviepy/pull/1105)
Expand Down
7 changes: 3 additions & 4 deletions docs/examples/quick_recipes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,8 @@ Getting the average frame of a video

from moviepy import VideoFileClip, ImageClip
clip = VideoFileClip("video.mp4")
fps= 1.0 # take one frame per second
nframes = clip.duration*fps # total number of frames used
total_image = sum(clip.iter_frames(fps,dtype=float,logger='bar'))
average_image = ImageClip(total_image/ nframes)
fps = 1.0 # take one frame per second
total_image = sum(clip.iter_frames(fps, dtype=float, logger='bar'))
average_image = ImageClip(total_image / clip.n_frames)
average_image.save_frame("average_test.png")

6 changes: 3 additions & 3 deletions moviepy/audio/io/readers.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ def __init__(
self.infos = infos
self.proc = None

self.nframes = int(self.fps * self.duration)
self.buffersize = min(self.nframes + 1, buffersize)
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()
Expand Down Expand Up @@ -218,7 +218,7 @@ def get_frame(self, tt):
else:

ind = int(self.fps * tt)
if ind < 0 or ind > self.nframes: # out of time: return 0
if ind < 0 or ind > self.n_frames: # out of time: return 0
return np.zeros(self.nchannels)

if not (0 <= (ind - self.buffer_startframe) < len(self.buffer)):
Expand Down
10 changes: 10 additions & 0 deletions moviepy/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,16 @@ def requires_duration(func, clip, *args, **kwargs):
return func(clip, *args, **kwargs)


@decorator.decorator
def requires_fps(func, clip, *args, **kwargs):
""" Raise an error if the clip has no fps."""

if not hasattr(clip, "fps") or clip.fps is None:
raise ValueError("Attribute 'fps' not set")
else:
return func(clip, *args, **kwargs)


@decorator.decorator
def audio_video_fx(func, clip, *args, **kwargs):
"""Use an audio function on a video/audio clip
Expand Down
7 changes: 7 additions & 0 deletions moviepy/video/VideoClip.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
convert_parameter_to_seconds,
outplace,
requires_duration,
requires_fps,
use_clip_fps_by_default,
)
from moviepy.tools import (
Expand Down Expand Up @@ -122,6 +123,12 @@ def h(self):
def aspect_ratio(self):
return self.w / float(self.h)

@property
@requires_duration
@requires_fps
def n_frames(self):
return int(self.duration * self.fps)

def __copy__(self):
"""Mixed copy of the clip.

Expand Down
12 changes: 6 additions & 6 deletions moviepy/video/io/ffmpeg_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def __init__(

self.duration = infos["video_duration"]
self.ffmpeg_duration = infos["duration"]
self.nframes = infos["video_nframes"]
self.n_frames = infos["video_n_frames"]
self.bitrate = infos["video_bitrate"]

self.infos = infos
Expand Down Expand Up @@ -155,7 +155,7 @@ def read_frame(self):
nbytes,
len(s),
self.pos,
self.nframes,
self.n_frames,
1.0 * self.pos / self.fps,
self.duration,
),
Expand Down Expand Up @@ -287,7 +287,7 @@ def ffmpeg_parse_infos(
"""Get file infos using ffmpeg.

Returns a dictionnary with the fields:
"video_found", "video_fps", "duration", "video_nframes",
"video_found", "video_fps", "duration", "video_n_frames",
"video_duration", "video_bitrate","audio_found", "audio_fps", "audio_bitrate"

"video_duration" is slightly smaller than "duration" to avoid
Expand Down Expand Up @@ -420,14 +420,14 @@ def get_fps():
result["video_fps"] = x * coef

if check_duration:
result["video_nframes"] = int(result["duration"] * result["video_fps"])
result["video_n_frames"] = int(result["duration"] * result["video_fps"])
result["video_duration"] = result["duration"]
else:
result["video_nframes"] = 1
result["video_n_frames"] = 1
result["video_duration"] = None
# We could have also recomputed the duration from the number
# of frames, as follows:
# >>> result['video_duration'] = result['video_nframes'] / result['video_fps']
# >>> result['video_duration'] = result['video_n_frames'] / result['video_fps']

# get the video rotation info.
try:
Expand Down
4 changes: 2 additions & 2 deletions tests/test_ffmpeg_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ def test_ffmpeg_parse_infos():

def test_ffmpeg_parse_infos_duration():
infos = ffmpeg_parse_infos("media/big_buck_bunny_0_30.webm")
assert infos["video_nframes"] == 720
assert infos["video_n_frames"] == 720

infos = ffmpeg_parse_infos("media/bitmap.mp4")
assert infos["video_nframes"] == 5
assert infos["video_n_frames"] == 5


def test_ffmpeg_parse_infos_for_i926():
Expand Down