Updated 2022-05-21 0
Viewed 7 times
0

At my python application I do video processing using ffmpeg. In order to process multiple output streams from one input stream (Adaptive HLS encoding) I have a function that also respects SDR/HDR and enconding from SD up to 4K. Each stream that needs to be reencoded for compatibility or size resons gets added to a list of jobs. Later on I build a command

def build_command(job):
    command = f'{options["path_ffmpeg"]} -i "{job["config"]["input"]}"'

    for stream in [*job['video'], *job['audio'], *job['audioOnly']]:
        name = build_name(stream)
        path = job['config']['output_dir'] + '/' + name
        command += f' -map 0:{stream["index"]} {build_command_encode(stream, job["config"])} -map_metadata -1 -map_chapters -1 -f hls -hls_time 6 -hls_list_size 0 -hls_segment_filename "{path}/f-%04d.m4s" -hls_fmp4_init_filename "init-{name}.m4s" -hls_segment_type fmp4 -movflags frag_keyframe -hls_flags independent_segments "{path}/master.m3u8"'

    for sub in job['subtitle']:
        command += f' -map 0:{sub["index"]} -map_metadata -1 -map_chapters -1 "{job["config"]["output_dir"] + "/" + build_name(sub)}/subs.vtt"'

    return command

Whats strange here is the fact that since I implemented adaptive streaming, meaning I have multiple versions of the video stream output I run into the following problem with ffmpeg and I have no idea why that happends:

s-vtt-de-f/subs.vtt' already exists. Overwrite? [y/N] Not overwriting - exiting

Basically this error exists since I have more than 1 element in [*job['video']

The Job itself runs trough the following flow:

create_directories(job)
create_master_playlist(job)
create_subtitles_master(job)
command = build_command(job)

print(f'The job element is: {job}')

try:
    exec_command(command)
except CalledProcessError as err:
    log('DEBUG', f'FFMPEG error: {err.stderr}')
    raise VodProcessingException("FFmpeg Error")

after_work(job)

As you can See, I only trigger the Command once while the Job print statement contains:

The job element is: {'config': {'input': '/tmp/VODProcessing/testclip.mp4', 'output': 'hls', 'output_dir': '/tmp/VODProcessing/output/testclip', 'output_options': {'segment_time': 6, 'stream_bandwidth_multiplier': 1.1}}, 'duration': 2992, 'video': [{'index': 0, 'type': 'video', 'codec': 'h264', 'width': 3840, 'height': 2160, 'bitrate': 10485760, 'hls': {'codec': 'avc1.640033', 'codec_name': 'h264'}, 'options': {'mode': 2}}, {'index': 0, 'type': 'video', 'codec': 'h264', 'width': 1920, 'height': 1080, 'bitrate': 5242880, 'hls': {'codec': 'avc1.640029', 'codec_name': 'h264'}, 'options': {'mode': 1}}, {'index': 0, 'type': 'video', 'codec': 'h264', 'width': 1280, 'height': 720, 'bitrate': 2621440, 'hls': {'codec': 'avc1.64001f', 'codec_name': 'h264'}, 'options': {'mode': 0}}], 'audio': [{'index': 1, 'type': 'audio', 'codec': 'copy', 'channels': 6, 'bitrate': 256000, 'hls': {'codec': 'ec-3', 'codec_name': 'eac3', 'name': 'Deutsch', 'language': 'de', 'group_id': [0], 'auto_select': True, 'default': False}, 'options': {'mode': 0}}, {'index': 2, 'type': 'audio', 'codec': 'copy', 'channels': 6, 'bitrate': 256000, 'hls': {'codec': 'ec-3', 'codec_name': 'eac3', 'name': 'English', 'language': 'en', 'group_id': [0], 'auto_select': True, 'default': False}, 'options': {'mode': 0}}, {'index': 1, 'type': 'audio', 'codec': 'aac', 'channels': 2, 'bitrate': 256000, 'hls': {'codec': 'mp4a.40.2', 'codec_name': 'aac', 'name': 'Deutsch', 'language': 'de', 'group_id': [1], 'auto_select': True, 'default': False}, 'options': {'mode': 0}}, {'index': 2, 'type': 'audio', 'codec': 'aac', 'channels': 2, 'bitrate': 256000, 'hls': {'codec': 'mp4a.40.2', 'codec_name': 'aac', 'name': 'English', 'language': 'en', 'group_id': [1], 'auto_select': True, 'default': False}, 'options': {'mode': 0}}], 'subtitle': [{'index': 3, 'type': 'subtitle', 'codec': 'copy', 'hls': {'codec_name': 'vtt', 'name': 'Deutsch (Forced)', 'language': 'de', 'group_id': [0], 'auto_select': True, 'default': False, 'forced': True}, 'options': {'after': 0}}, {'index': 4, 'type': 'subtitle', 'codec': 'copy', 'hls': {'codec_name': 'vtt', 'name': 'Deutsch', 'language': 'de', 'group_id': [0], 'auto_select': True, 'default': False, 'forced': False}, 'options': {'after': 0}}, {'index': 5, 'type': 'subtitle', 'codec': 'copy', 'hls': {'codec_name': 'vtt', 'name': 'English', 'language': 'en', 'group_id': [0], 'auto_select': True, 'default': False, 'forced': False}, 'options': {'after': 0}}], 'audioOnly': []}

The only point where I can image the error also occours is at the point where I build the actual job list for the video input stream, To me this looks ugly. But I am also not sure how else to create the jobs

def process_video(fileInfo, job):

    # Core error: Jobs getting created twice
    if len(fileInfo['stream']['video']) == 0:
        return

    # Video Input
    video = fileInfo['stream']['video'][0]

    # SD up to 1080p
    if video['width'] <= 1920 and video['codec'] == 'h264':
        print("h264 Encoder Selected")
        video_job_mid = build_video(video)
        process_video_h264_mid(video_job_mid)
        video_job_mid['width'] = 1920
        video_job_mid['height'] = process_video_height(video, video_job_mid['width'])
        video_job_mid['options']['mode'] = 1
        job['video'].append(video_job_mid)
        #
        if options['adaptive_versioning']:
            video_job_low = build_video(video)
            process_video_h264_low(video_job_low)
            video_job_low['width'] = 1280
            video_job_low['height'] = process_video_height(video, video_job_low['width'])
            video_job_low['options']['mode'] = 0
            job['video'].append(video_job_low)

    # 4k HDR/SDR
    elif video['width'] > 1920 and video['codec'] == 'hevc' and video['format'] == 'yuv420p10le' or video['color'] == 'bt2020':
        print("4k HDR/SDR Encoder Selected")
        video_job_hdr = build_video(video)
        process_video_h265(video_job_hdr)
        video_job_hdr['options']['mode'] = 3
        job['video'].append(video_job_hdr)
        video_job_high = build_video(video)
        process_video_h264_high(video_job_high)
        video_job_high['options']['mode'] = 2
        job['video'].append(video_job_high)
        if options['adaptive_versioning']:
            video_job_mid = build_video(video)
            process_video_h264_mid(video_job_mid)
            video_job_mid['width'] = 1920
            video_job_mid['height'] = process_video_height(video, video_job_mid['width'])
            video_job_mid['options']['mode'] = 1
            job['video'].append(video_job_mid)
            #
            video_job_low = build_video(video)
            process_video_h264_low(video_job_low)
            video_job_low['width'] = 1280
            video_job_low['height'] = process_video_height(video, video_job_low['width'])
            video_job_low['options']['mode'] = 0
            job['video'].append(video_job_low)

Could it be that my issue is solved as soon as I only work with build_video(video) at one position instead of that many?

P.S. Sorry for the long read, but this issue is kinda deep

🔴 No definitive solution yet