From b76beb48a58a9780a0d9b1ae1f2274b36684bee0 Mon Sep 17 00:00:00 2001 From: Xiaotian Huo Date: Thu, 6 Apr 2017 16:06:54 -0400 Subject: [PATCH] issue-212: add rotation info from metadata (#529) * issue-212: add rotation info from metadata * fix typo: info -> infos * assign rotation to VideoFileClip and write test for PR * change assignment to equality statement. --- moviepy/video/io/VideoFileClip.py | 37 ++++++++++++++++--------------- moviepy/video/io/ffmpeg_reader.py | 21 +++++++++++++++--- tests/test_PR.py | 4 ++++ 3 files changed, 41 insertions(+), 21 deletions(-) diff --git a/moviepy/video/io/VideoFileClip.py b/moviepy/video/io/VideoFileClip.py index eec3b90ec..410be1b51 100644 --- a/moviepy/video/io/VideoFileClip.py +++ b/moviepy/video/io/VideoFileClip.py @@ -8,27 +8,27 @@ class VideoFileClip(VideoClip): """ - + A video clip originating from a movie file. For instance: :: - + >>> clip = VideoFileClip("myHolidays.mp4") >>> clip2 = VideoFileClip("myMaskVideo.avi") - - + + Parameters ------------ - + filename: The name of the video file. It can have any extension supported by ffmpeg: .ogv, .mp4, .mpeg, .avi, .mov etc. - + has_mask: Set this to 'True' if there is a mask included in the videofile. Video files rarely contain masks, but some video codecs enable that. For istance if you have a MoviePy VideoClip with a mask you - can save it to a videofile with a mask. (see also + can save it to a videofile with a mask. (see also ``VideoClip.write_videofile`` for more details). - + audio: Set to `False` if the clip doesn't have any audio or if you do not wish to read the audio. @@ -43,24 +43,24 @@ class VideoFileClip(VideoClip): The algorithm used for resizing. Default: "bicubic", other popular options include "bilinear" and "fast_bilinear". For more information, see https://ffmpeg.org/ffmpeg-scaler.html - + fps_source: The fps value to collect from the metadata. Set by default to 'tbr', but can be set to 'fps', which may be helpful if importing slow-motion videos that get messed up otherwise. - + Attributes ----------- - + filename: Name of the original video file. - + fps: Frames per second in the original file. - + Read docstrings for Clip() and VideoClip() for other, more generic, attributes. - + """ def __init__(self, filename, has_mask=False, @@ -68,7 +68,7 @@ def __init__(self, filename, has_mask=False, target_resolution=None, resize_algorithm='bicubic', audio_fps=44100, audio_nbytes=2, verbose=False, fps_source='tbr'): - + VideoClip.__init__(self) # Make a reader @@ -82,10 +82,11 @@ def __init__(self, filename, has_mask=False, # Make some of the reader's attributes accessible from the clip self.duration = self.reader.duration self.end = self.reader.duration - + self.fps = self.reader.fps self.size = self.reader.size - + self.rotation = self.reader.rotation + self.filename = self.reader.filename if has_mask: @@ -99,7 +100,7 @@ def __init__(self, filename, has_mask=False, else: self.make_frame = lambda t: self.reader.get_frame(t) - + # Make a reader for the audio, if any. if audio and self.reader.infos['audio_found']: diff --git a/moviepy/video/io/ffmpeg_reader.py b/moviepy/video/io/ffmpeg_reader.py index c6f523819..56a08dd10 100644 --- a/moviepy/video/io/ffmpeg_reader.py +++ b/moviepy/video/io/ffmpeg_reader.py @@ -32,6 +32,7 @@ def __init__(self, filename, print_infos=False, bufsize = None, fps_source) self.fps = infos['video_fps'] self.size = infos['video_size'] + self.rotation = infos['video_rotation'] if target_resolution: # revert the order, as ffmpeg used (width, height) @@ -40,7 +41,7 @@ def __init__(self, filename, print_infos=False, bufsize = None, if None in target_resolution: ratio = 1 for idx, target in enumerate(target_resolution): - if target: + if target: ratio = target / self.size[idx] self.size = (int(self.size[0] * ratio), int(self.size[1] * ratio)) else: @@ -159,12 +160,12 @@ def get_frame(self, t): """ # these definitely need to be rechecked sometime. Seems to work. - + # I use that horrible '+0.00001' hack because sometimes due to numerical # imprecisions a 3.0 can become a 2.99999999... which makes the int() # go to the previous integer. This makes the fetching more robust in the # case where you get the nth frame by writing get_frame(n/fps). - + pos = int(self.fps*t + 0.00001)+1 if pos == self.pos: @@ -359,6 +360,20 @@ def get_fps(): # of frames, as follows: # >>> result['video_duration'] = result['video_nframes'] / result['video_fps'] + # get the video rotation info. + try: + rotation_lines = [l for l in lines if 'rotate :' in l and re.search('\d+$', l)] + if len(rotation_lines): + rotation_line = rotation_lines[0] + match = re.search('\d+$', rotation_line) + result['video_rotation'] = int(rotation_line[match.start() : match.end()]) + else: + result['video_rotation'] = 0 + except: + raise IOError(("MoviePy error: failed to read video rotation in file %s.\n" + "Here are the file infos returned by ffmpeg:\n\n%s")%( + filename, infos)) + lines_audio = [l for l in lines if ' Audio: ' in l] diff --git a/tests/test_PR.py b/tests/test_PR.py index 7ef67868e..790c65c42 100644 --- a/tests/test_PR.py +++ b/tests/test_PR.py @@ -89,5 +89,9 @@ def test_PR_515(): clip = VideoFileClip("media/fire2.mp4", fps_source='fps') assert clip.fps == 10.51 +def test_PR_529(): + video_clip = VideoFileClip("media/fire2.mp4") + assert video_clip.rotation ==180 + if __name__ == '__main__': pytest.main()