[Python] How to render two videos with alpha channel in real time in pygame?

Discussão em 'Python' iniciado por Stack, Dezembro 21, 2024.

  1. Stack

    Stack Membro Participativo

    I need to play two videos with synched sound in real-time with Pygame. Pygame does not currently support video streams, so I am using a ffmpeg subprocess. The first video is a prores422_hq. This is a background video with no alpha channel. The second video is a prores4444 overlay video with an alpha channel, and it needs to be played in real-tim on top of the first video (with transparency). All of this needs synched sound from the first base video only.

    I have tried many libraries, including pymovie pyav and opencv. The best result so far is to use a subprocess with ffmpeg.

    ffmpeg -i testing/stefano_prores422_hq.mov -stream_loop -1 -i testing/key_prores4444.mov -filter_complex "[1:v]format=rgba,colorchannelmixer=aa=1.0[overlay];[0:v][overlay]overlay" -f nut pipe:1 | ffplay -

    When running this in the terminal and playing with ffplay, everything is perfect, the overlay looks good, no dropped frames, and the sound is in synch.

    However, trying to feed that to pygame via a subprocess creates either video delays and drop frames or audio not in synch.

    EXAMPLE ONE:

    # SOUND IS NOT SYNCHED - sound is played via ffplay
    import pygame
    import subprocess
    import numpy as np
    import sys

    def main():
    pygame.init()
    screen_width, screen_height = 1920, 1080
    screen = pygame.display.set_mode((screen_width, screen_height))
    pygame.display.set_caption("PyGame + FFmpeg Overlay with Audio")
    clock = pygame.time.Clock()

    # LAUNCH AUDIO-ONLY SUBPROCESS
    audio_cmd = [
    "ffplay",
    "-nodisp", # no video window
    "-autoexit", # exit when video ends
    "-loglevel", "quiet",
    "testing/stefano_prores422_hq.mov"
    ]
    audio_process = subprocess.Popen(audio_cmd)

    # LAUNCH VIDEO-OVERLAY SUBPROCESS
    ffmpeg_command = [
    "ffmpeg",
    "-re", # read at native frame rate
    "-i", "testing/stefano_prores422_hq.mov",
    "-stream_loop", "-1", # loop alpha video
    "-i", "testing/key_prores4444.mov",
    "-filter_complex",
    "[1:v]format=rgba,colorchannelmixer=aa=1.0[overlay];" # ensure alpha channel
    "[0:v][overlay]overlay", # overlay second input onto first
    "-f", "rawvideo", # output raw video
    "-pix_fmt", "rgba", # RGBA format
    "pipe:1" # write to STDOUT
    ]
    video_process = subprocess.Popen(
    ffmpeg_command,
    stdout=subprocess.PIPE,
    stderr=subprocess.DEVNULL
    )
    frame_size = screen_width * screen_height * 4 # RGBA = 4 bytes/pixel
    running = True
    while running:
    for event in pygame.event.get():
    if event.type == pygame.QUIT:
    running = False
    break

    raw_frame = video_process.stdout.read(frame_size)

    if len(raw_frame) < frame_size:
    running = False
    break
    # Convert raw bytes -> NumPy array -> PyGame surface
    frame_array = np.frombuffer(raw_frame, dtype=np.uint8)
    frame_array = frame_array.reshape((screen_height, screen_width, 4))
    frame_surface = pygame.image.frombuffer(frame_array.tobytes(),
    (screen_width, screen_height),
    "RGBA")
    screen.blit(frame_surface, (0, 0))
    pygame.display.flip()
    clock.tick(25)
    video_process.terminate()
    video_process.wait()
    audio_process.terminate()
    audio_process.wait()
    pygame.quit()
    sys.exit()

    if __name__ == "__main__":
    main()



    EXAMPLE TWO

    # NO VIDEO OVERLAY - SOUND SYNCHED
    import ffmpeg
    import pygame
    import sys
    import numpy as np
    import tempfile
    import os

    def extract_audio(input_file, output_file):
    """Extract audio from video file to temporary WAV file"""
    (
    ffmpeg
    .input(input_file)
    .output(output_file, acodec='pcm_s16le', ac=2, ar='44100')
    .overwrite_output()
    .run(capture_stdout=True, capture_stderr=True)
    )

    def get_video_fps(input_file):
    probe = ffmpeg.probe(input_file)
    video_info = next(s for s in probe['streams'] if s['codec_type'] == 'video')
    fps_str = video_info.get('r_frame_rate', '25/1')
    num, den = map(int, fps_str.split('/'))
    return num / den

    input_file = "testing/stefano_prores422_hq.mov"

    # Create temporary WAV file
    temp_audio = tempfile.NamedTemporaryFile(suffix='.wav', delete=False)
    temp_audio.close()
    extract_audio(input_file, temp_audio.name)

    probe = ffmpeg.probe(input_file)
    video_info = next(s for s in probe['streams'] if s['codec_type'] == 'video')
    width = int(video_info['width'])
    height = int(video_info['height'])
    fps = get_video_fps(input_file)

    process = (
    ffmpeg
    .input(input_file)
    .output('pipe:', format='rawvideo', pix_fmt='rgb24')
    .run_async(pipe_stdout=True)
    )

    pygame.init()
    pygame.mixer.init(frequency=44100, size=-16, channels=2, buffer=4096)
    clock = pygame.time.Clock()
    screen = pygame.display.set_mode((width, height))

    pygame.mixer.music.load(temp_audio.name)
    pygame.mixer.music.play()

    frame_count = 0
    start_time = pygame.time.get_ticks()

    while True:
    for event in pygame.event.get():
    if event.type == pygame.QUIT:
    pygame.mixer.music.stop()
    os.unlink(temp_audio.name)
    sys.exit()

    in_bytes = process.stdout.read(width * height * 3)
    if not in_bytes:
    break

    # Calculate timing for synchronization
    expected_frame_time = frame_count * (1000 / fps)
    actual_time = pygame.time.get_ticks() - start_time

    if actual_time < expected_frame_time:
    pygame.time.wait(int(expected_frame_time - actual_time))

    in_frame = (
    np.frombuffer(in_bytes, dtype="uint8")
    .reshape([height, width, 3])
    )
    out_frame = pygame.surfarray.make_surface(np.transpose(in_frame, (1, 0, 2)))
    screen.blit(out_frame, (0, 0))
    pygame.display.flip()

    frame_count += 1

    pygame.mixer.music.stop()
    process.wait()
    pygame.quit()
    os.unlink(temp_audio.name)


    I also tried using pygame mixer and a separate mp3 audio file, but that didn't work either. Any help on how to synch the sound while keeping the playback of both videos to 25 FPS would be greatly appreciated!!!

    Continue reading...

Compartilhe esta Página