Zulko / moviepy

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

Ver Very very Slow #2231

Open sysmaya opened 1 month ago

sysmaya commented 1 month ago

What an absurdly slow and exasperating thing. 2 hours to make a 5 minute video?? There are faster alternatives: FFmpeg only VidGear

Expected Behavior

You expect a 5-minute video to take less than 10 minutes to complete.

Actual Behavior

2 long hours waiting to build the video

Steps to Reproduce the Problem

Get started by downloading MoviePY Upload a 5 minute wav audio place some image clips and 50 text subtitles

steinathan commented 1 month ago

Very slow indeed, found an alternative yet?

sysmaya commented 1 month ago

Very slow indeed, found an alternative yet?

I'm writing my own functions to use ffmpeg, they are 10 times faster. But it's tedious.

def ffmpeg_image_overlay( start_time, duration, x='(W-w)/2', y='(H-h)/2'):
    """
    Generates an overlay filter for FFmpeg.
    Args:
        image_file (str): Path to the image file.
        start_time (float): Start time in seconds to display the image.
        duration (float): Duration in seconds to display the image.
        x (str): Expression for the horizontal position of the image.
        y (str): Expression for the vertical position of the image.
    Returns:
        str: Overlay filter for FFmpeg.
    """
    return f"[0:v][1:v] overlay={x}:{y}:enable='between(t,{start_time},{start_time + duration})'"
def ffmpeg_run_command(video_input, video_output, filtro, imageFile=''):
    """
    Run the FFmpeg command with the provided filter.    
    Args:
        video_input (str): Path of the input video file.
        video_output (str): Path to the output video file.
        filter (str): Command or FFmpeg filter to apply to the video.    
    Returns:
        bool: True if the command succeeds, False if it fails.
    """
    inicioExec = time.time()
    filtro = re.sub(r'\s+', ' ', filtro)
    lentxt = len(filtro)

    caracteres = string.ascii_uppercase + string.digits
    nombreTMP = ''.join(random.choice(caracteres) for _ in range(12))
    nombreTMP = nombreTMP + '.mp4'

    ffmpeg_path = r'C:\\ffmpeg\\bin\\ffmpeg.exe'
    directorio_actual = os.path.dirname(os.path.abspath(__file__))
    video_input = os.path.join(directorio_actual, video_input)

    if imageFile == '' :
        comando = [
            ffmpeg_path,
            '-i', video_input,              # Input del video
            '-vf', filtro,                  # Filtro de video para superponer el texto
            '-codec:a', 'copy',             # Copiar el audio sin modificar
            '-stats',                       # Muestra el banner de avance del proceso
            nombreTMP                       # Archivo de salida
        ]
    else :
        comando = [
            ffmpeg_path,
            '-i', video_input,              # Input del video
            '-i', imageFile,                # Input de la imagen
            '-filter_complex', filtro,      # Filtro de video para superponer la imagen
            '-pix_fmt', 'yuv420p',          # Formato de píxeles
            '-codec:a', 'copy',             # Copiar el audio sin modificar
            '-v', 'quiet',                  # Oculta todos los mensajes excepto el banner de progreso
            '-stats',                       # Muestra el banner de avance del proceso
            nombreTMP                       # Archivo de salida
        ]

    try:
        subprocess.run(comando, check=True)
        duracion_ejecucion = time.time() - inicioExec
        minutos = int(duracion_ejecucion // 60)
        segundos = int(duracion_ejecucion % 60)
        milisegundos = int((duracion_ejecucion - int(duracion_ejecucion)) * 1000)
        print(f"run_ffmpeg() OK: {minutos}:{segundos}:{milisegundos}")

        if os.path.exists(video_output): os.remove(video_output)   
        os.rename(nombreTMP, video_output)
        return True
    except subprocess.CalledProcessError as e:
        print(f"run_ffmpeg() Error: {e}")
        return False
ericmadureira commented 4 weeks ago

Hi everyone, haven't used the lib yet but this issue caught my attention. Is rendering really taking that long? I'm looking for "video edit via code" libs to automate video editing.

steinathan commented 4 weeks ago

It is slow, but thats just because of ffmpeg - moviepy don't efficiently make use of ffmpeg, but if you're on Mac, it can get 30% faster by using hevc_videotoolbox decoders

I'm currently using moviepy in my app: https://dub.sh/voidface

ericmadureira commented 4 weeks ago

Have you heard of https://www.remotion.dev/ or https://re.video/? I'm reading about them. I'm interested in editing content faster without using GUI programs like capcut, etc.

steinathan commented 4 weeks ago

Yes heard about them but haven't used them, if you're just looking for editing and cool with react then remotion is the way to go - you'd still need a headless browser to render the video - but it's faster

My use case is mainly automation and Python - sad they're no alternatives to moviepy that's faster

JoelOnyedika commented 2 weeks ago

NGL, moviepy is slow like crazy. I am planning to use it in my saas project which uses ReactJS on the frontend, Django on the backend, and Moviepy + Flask for the video processsing and is to be deployed on a VPS, but i think i will be forced to move to ffmpeg because Moviepy is way too slow and seriously it is way too much bugs. Moviepy cant work with the latest ffmpeg binary. It just breaks, which is the worst nightmare on production.

And well i still dont get the Python community, moviepy is the best video processing libary for Python which is basically the go to language for automation and still yet it has little to no support. Thats just unfrortunate, real unfortunate.

steinathan commented 2 weeks ago

@JoelOnyedika na true shaa, moviepy dey slow but I'm able to use the latest ffmpeg with the dev branch

Nevertheless, I did a comparison of raw ffmpeg with moviepy and the speed was almost the same

My use case is mainly, adding watermark, resizing, adding audio, cropping and it get almost the same speed

So I copied the ffmpeg commands moviepy generates and ran it outside moviepy and got the same results

The only part that seems slower a bit is the write_videofile which I'm running asynchronous with 'asyncio.to_thread'

Check out my repo https://github.com/steinathan/reelsmaker

[edit] FFMPEG is a lot faster if you're copying codecs

JoelOnyedika commented 2 weeks ago

Okay, thanks a lot. But what do you mean by you are usin the dev branch. Please drop a link

MitchMunn commented 1 week ago

You should clone moviepy from source and build it. The latest version is from 2020, meaning if you just pip install moviepy it will get this version. I believe some work has been done to improve the write time since.

Also, don't use the method='compose' if you are concatenating video files - especially if you have them nested.

JoelOnyedika commented 1 day ago

@steinathan Yo bro, do you know how to do the progressive text highlighting synchronized with text-to-speech narration. This effect is often called "karaoke-style highlighting" or "progressive text highlighting" and is commonly used in educational videos and lyric videos.

I have been trying to do something like this in moviepy but i cannot get it right. Do you know a logic i can use to get it right here is a sample video https://rpie.b-cdn.net/wp-content/uploads/2023/11/telegram-cloud-document-2-5240322943975701817.mp4

steinathan commented 1 day ago

I gave up on moviepy to use casual FFMPEG with ffmpeg-python

but you can do that with the combination of these two libs (ASS subtitle) but bro to bro - thats what ive been using, its very manual so im finally switching to remotion.dev because it just CSS


# convert casual SRT to ASS (if you have srt before)
subs = pysubs2.load(
    "/tmp/vid_r70pddalx9j4z1qjwfc0okd.srt"
)
subs.info["PlayResX"] = 1280  # type: ignore
subs.info["PlayResY"] = 720  # type: ignore

subs.styles["Default"] = pysubs2.SSAStyle(
    bold=True,
    fontsize=30,
    fontname="Luckiest Guy",
    shadow=5.0,
    alignment=pysubs2.Alignment.BOTTOM_CENTER,
)

out = "/tmp/subtitles.ass"
# save so we can work with PyonFX
subs.save(out)

# Load the .ass file
io = Ass(out)

# Get the events (lines) in the script
meta, styles, lines = io.get_data()
def sub(line: Line, l: Line):
   # TODO: add kareoke effects
    l.text = "{\\fad(%d,%d)}%s" % (300, 200, line.text)
    io.write_line(l)

for line in lines:
    sub(line, line.copy())

io.path_output = out
io.save()

burn it

./ffmpeg -i input.mp4 -vf "ass=subtitles.ass" -c:v libx264 -crf 23 -preset medium -c:a copy output.mp4 -y 

https://github.com/CoffeeStraw/PyonFX https://pysubs2.readthedocs.io/ https://aegisub.org/

image

JoelOnyedika commented 1 day ago

Damn. Its so manual. Thanks btw

sysmaya commented 1 day ago

This video was made with moviepy, but only for the transitions, the text was made with pure ffmpeg, the same as the curtain and the initial video, they were coupled with ffmpeg. https://www.youtube.com/watch?v=RQ_aRkg009s