Skip to content

Commit

Permalink
Add support for retrieve clip frames number using 'clip.n_frames' (#1471
Browse files Browse the repository at this point in the history
)

* Add support for retrieve the number of frames of a clip using 'clip.nframes'

* Add CHANGELOG entry

* Improve CHANGELOG entry

* Replace all 'nframes' by 'n_frames'

* Document minor change
  • Loading branch information
mondeja authored Jan 20, 2021
1 parent 22e207e commit a378771
Show file tree
Hide file tree
Showing 7 changed files with 33 additions and 15 deletions.
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

0 comments on commit a378771

Please sign in to comment.