Zulko / moviepy

Video editing with Python
https://zulko.github.io/moviepy/
MIT License
12.07k stars 1.51k forks source link

Decorator not working correctly fps remains to be None and cause TypeError #2158

Open juexZZ opened 2 months ago

juexZZ commented 2 months ago

I was trying to run in my ipython notebook:

rendered_clip = ImageSequenceClip(cache_video, fps=25)
display(rendered_clip.ipython_display(autoplay=1, loop=1))

which will call function from the the parent class: VideoClip.write_videofile:

@requires_duration
    @use_clip_fps_by_default
    @convert_masks_to_RGB
    def write_videofile(self, filename, fps=None, codec=None,
                        bitrate=None, audio=True, audio_fps=44100,
                        preset="medium",
                        audio_nbytes=4, audio_codec=None,
                        audio_bitrate=None, audio_bufsize=2000,
                        temp_audiofile=None,
                        rewrite_audio=True, remove_temp=True,
                        write_logfile=False, verbose=True,
                        threads=None, ffmpeg_params=None,
                        logger='bar'):
        """Write the clip to a videofile.

Expected Behavior

Because of the decorator: @use_clip_fps_by_default, even though fps is not passed as an argument to this function, it should be given the value from the attribute clip.fps which is 25 in my case, and it should generate a video with that FPS.

Actual Behavior

However, I got the Type Error saying that fps is in fact Nonetype when passing to ffmepg. Part of the traceback:

File ~/anaconda3/envs/myenv/lib/python3.11/site-packages/moviepy/video/io/ffmpeg_writer.py:86, in FFMPEG_VideoWriter.__init__(self, filename, size, fps, codec, audiofile, preset, bitrate, withmask, logfile, threads, ffmpeg_params)
     75 self.ext = self.filename.split(".")[-1]
     77 # order is important
     78 cmd = [
     79     get_setting("FFMPEG_BINARY"),
     80     '-y',
     81     '-loglevel', 'error' if logfile == sp.PIPE else 'info',
     82     '-f', 'rawvideo',
     83     '-vcodec', 'rawvideo',
     84     '-s', '%dx%d' % (size[0], size[1]),
     85     '-pix_fmt', 'rgba' if withmask else 'rgb24',
---> 86     '-r', '%.02f' % fps,
     87     '-i', '-', '-an',
     88 ]
     89 if audiofile is not None:
     90     cmd.extend([
     91         '-i', audiofile,
     92         '-acodec', 'copy'
     93     ])

TypeError: must be real number, not NoneType

Upon checking the execution of the decorator use_clip_fps_by_default, I found that in this case fps doesn't show up in neither the names = func_code.co_varnames[1:] or the kwargs, so neither of the lines:

new_a = [fun(arg) if (name=='fps') else arg
             for (arg, name) in zip(a, names)]
    new_kw = {k: fun(v) if k=='fps' else v
             for (k,v) in k.items()}

will call function fun and handle the None type FPS. So I had to hardcode and add the following to the end of the function to make it work:

if 'fps' not in names and ('fps' not in k or k['fps'] is None):
        if hasattr(clip, 'fps') and clip.fps is not None:
            new_kw['fps'] = clip.fps
        else:
            raise AttributeError("No 'fps' (frames per second) attribute specified"
            " for function %s and the clip has no 'fps' attribute. Either"
            " provide e.g. fps=24 in the arguments of the function, or define"
            " the clip's fps with `clip.fps=24`" % f.__name__)

Steps to Reproduce the Problem

Specifications

allenli0413 commented 1 week ago

My fix works only with python 3.8 It works for me.