kkroening / ffmpeg-python

Python bindings for FFmpeg - with complex filtering support
Apache License 2.0
10.12k stars 893 forks source link

How to add "anullsrc" to a video stream prior to concatenation. #303

Open cbitterfield opened 4 years ago

cbitterfield commented 4 years ago

I am trying to convert DVD (VOB) files into an MP4. The first 2 files, don't have an audio track.

What is the proper syntax for adding an "anullsrc" audio track to video streams that do not have one, prior to concatenation?

ffmpeg command generate:

ffmpeg -i /Volumes/My Passport/Project/DVD_Files/VIDEO_TS/VIDEO_TS.VOB -i /Volumes/My Passport/Project/DVD_Files/VIDEO_TS/VTS_01_0.VOB -i /Volumes/My Passport/Project/DVD_Files/VIDEO_TS/VTS_01_1.VOB -i /Volumes/My Passport/Project/DVD_Files/VIDEO_TS/VTS_01_2.VOB -i /Volumes/My Passport/Project/DVD_Files/VIDEO_TS/VTS_01_3.VOB -i /Volumes/My Passport/Project/DVD_Files/VIDEO_TS/VTS_01_4.VOB -i /Volumes/My Passport/Project/DVD_Files/VIDEO_TS/VTS_02_0.VOB -i /Volumes/My Passport/Project/DVD_Files/VIDEO_TS/VTS_02_1.VOB -filter_complex [0:a]nullsrc=r=480000[s0];[0:v][s0]concat=n=2[s1];[1:a]nullsrc=r=480000[s2];[1:v][s2]concat=n=2[s3];[s1][s3][2][3][4][5][6][7]concat=n=8[s4] -map [s4] /tmp/test.mp4
import os
import ffmpeg
global_args = {
    'vcodec'    : 'h264_videotoolbox',
    'acodec'    : 'aac',
    'vb'        : '7750k',
    'ab'        : '192k',
    }

concat_args = {
    'v' : 1,
    'a' : 1
}

input_args = {
}

nullsrc = {
    'filter_name' : 'nullsrc',
    'r'           : 480000

}

# Path shorted for readability (DIRECTORY is set)
# python.ffmpeg set up this way for debugging purposes.

vob_stream = list()
file1 = (ffmpeg.input(DIRECTORY + '/VIDEO_TS/VIDEO_TS.VOB'))
file2 = (ffmpeg.input(DIRECTORY + '/VIDEO_TS/VTS_01_0.VOB'))
file3 = (ffmpeg.input(DIRECTORY + '/VIDEO_TS/VTS_01_1.VOB'))
file4 = (ffmpeg.input(DIRECTORY + '/VIDEO_TS/VTS_01_2.VOB'))
file5 = (ffmpeg.input(DIRECTORY + '/VIDEO_TS/VTS_01_3.VOB'))
file6 = (ffmpeg.input(DIRECTORY + '/VIDEO_TS/VTS_01_4.VOB'))
file7 = (ffmpeg.input(DIRECTORY + '/VIDEO_TS/VTS_02_0.VOB'))
file8 = (ffmpeg.input(DIRECTORY + '/VIDEO_TS/VTS_02_1.VOB'))

video1 = file1.video
audio1 = file1.audio.filter(**nullsrc)
media1 = ffmpeg.concat(video1,audio1)

video2 = file2.video
audio2 = file2.audio.filter(**nullsrc)
media2 = ffmpeg.concat(video2,audio2)

merged = ffmpeg.concat(media1,media2,file3,file4,file5,file6,file7,file8)
output = ffmpeg.output(merged,mp4_out)
output.run()

This produces the following errors.

Input #0, mpeg, from '/Volumes/My Passport/Project/DVD_Files/VIDEO_TS/VIDEO_TS.VOB':
  Duration: 00:00:00.03, start: 0.060000, bitrate: 1964 kb/s
    Stream #0:0[0x1bf]: Data: dvd_nav_packet
    Stream #0:1[0x1e0]: Video: mpeg2video (Main), yuv420p(tv, smpte170m, progressive), 720x480 [SAR 8:9 DAR 4:3], 9000 kb/s, 29.97 tbr, 90k tbn, 59.94 tbc
Input #1, mpeg, from '/Volumes/My Passport/Project/DVD_Files/VIDEO_TS/VTS_01_0.VOB':
  Duration: 00:00:00.11, start: 0.060000, bitrate: 10443 kb/s
    Stream #1:0[0x1bf]: Data: dvd_nav_packet
    Stream #1:1[0x1e0]: Video: mpeg2video (Main), yuv420p(tv, smpte170m, progressive), 720x480 [SAR 8:9 DAR 4:3], 9000 kb/s, 29.97 tbr, 90k tbn, 59.94 tbc
    Stream #1:2[0x20]: Subtitle: dvd_subtitle
Input #2, mpeg, from '/Volumes/My Passport/Project/DVD_Files/VIDEO_TS/VTS_01_1.VOB':
  Duration: 00:17:42.16, start: 0.060000, bitrate: 8086 kb/s
    Stream #2:0[0x1bf]: Data: dvd_nav_packet
    Stream #2:1[0x1e0]: Video: mpeg2video (Main), yuv420p(tv, bottom first), 720x480 [SAR 8:9 DAR 4:3], 7758 kb/s, 29.97 fps, 29.97 tbr, 90k tbn, 59.94 tbc
    Stream #2:2[0x80]: Audio: ac3, 48000 Hz, stereo, fltp, 192 kb/s
[mpeg2video @ 0x7fe489829c00] Invalid frame dimensions 0x0.
    Last message repeated 13 times
Input #3, mpeg, from '/Volumes/My Passport/Project/DVD_Files/VIDEO_TS/VTS_01_2.VOB':
  Duration: 00:17:41.70, start: 1062.172000, bitrate: 8090 kb/s
    Stream #3:0[0x1e0]: Video: mpeg2video (Main), yuv420p(tv, bottom first), 720x480 [SAR 8:9 DAR 4:3], 7758 kb/s, 29.97 fps, 29.97 tbr, 90k tbn, 59.94 tbc
    Stream #3:1[0x80]: Audio: ac3, 48000 Hz, stereo, fltp, 192 kb/s
    Stream #3:2[0x1bf]: Data: dvd_nav_packet
[mpeg2video @ 0x7fe489027e00] Invalid frame dimensions 0x0.
    Last message repeated 7 times
Input #4, mpeg, from '/Volumes/My Passport/Project/DVD_Files/VIDEO_TS/VTS_01_3.VOB':
  Duration: 00:17:41.91, start: 2123.932000, bitrate: 8088 kb/s
    Stream #4:0[0x1e0]: Video: mpeg2video (Main), yuv420p(tv, bottom first), 720x480 [SAR 8:9 DAR 4:3], 7758 kb/s, 29.97 fps, 29.97 tbr, 90k tbn, 59.94 tbc
    Stream #4:1[0x80]: Audio: ac3, 48000 Hz, stereo, fltp, 192 kb/s
    Stream #4:2[0x1bf]: Data: dvd_nav_packet
[mpeg2video @ 0x7fe48d007c00] Invalid frame dimensions 0x0.
    Last message repeated 13 times
Input #5, mpeg, from '/Volumes/My Passport/Project/DVD_Files/VIDEO_TS/VTS_01_4.VOB':
  Duration: 00:04:08.42, start: 3185.788000, bitrate: 8088 kb/s
    Stream #5:0[0x1e0]: Video: mpeg2video (Main), yuv420p(tv, bottom first), 720x480 [SAR 8:9 DAR 4:3], 7758 kb/s, 29.97 fps, 29.97 tbr, 90k tbn, 59.94 tbc
    Stream #5:1[0x80]: Audio: ac3, 48000 Hz, stereo, fltp, 192 kb/s
    Stream #5:2[0x1bf]: Data: dvd_nav_packet
Input #6, mpeg, from '/Volumes/My Passport/Project/DVD_Files/VIDEO_TS/VTS_02_0.VOB':
  Duration: 00:00:00.03, start: 0.060000, bitrate: 1964 kb/s
    Stream #6:0[0x1bf]: Data: dvd_nav_packet
    Stream #6:1[0x1e0]: Video: mpeg2video (Main), yuv420p(tv, smpte170m, progressive), 720x480 [SAR 8:9 DAR 4:3], 9000 kb/s, 29.97 tbr, 90k tbn, 59.94 tbc
Input #7, mpeg, from '/Volumes/My Passport/Project/DVD_Files/VIDEO_TS/VTS_02_1.VOB':
  Duration: 00:00:32.38, start: 0.060000, bitrate: 8076 kb/s
    Stream #7:0[0x1bf]: Data: dvd_nav_packet
    Stream #7:1[0x1e0]: Video: mpeg2video (Main), yuv420p(tv, bottom first), 720x480 [SAR 8:9 DAR 4:3], 7758 kb/s, 29.97 fps, 29.97 tbr, 90k tbn, 59.94 tbc
    Stream #7:2[0x80]: Audio: ac3, 48000 Hz, stereo, fltp, 192 kb/s
[AVFilterGraph @ 0x7fe488d06900] Too many inputs specified for the "nullsrc" filter.

python-ffmpeg

g4xx commented 4 years ago

I've the exact same problem and after some research I think that this kind of filters (judging by their syntax in vanilla ffmpeg) are only operating on inputs (-i parameter). Here is an example usage for such filter:

ffmpeg -f lavfi -i anullsrc=channel_layout=stereo:sample_rate=44100 -i video.mov \ -shortest -c:v copy -c:a aac output.mov

Basically when you use audio.filter() you operate on some input that is defined as audio stream. That stream can only be a file from your PC that you provide file path for, or a stream taken out of your video.

It is worth to mention that there is #62 that I think would solve that problem but somehow it got stuck. It would be really nice if @kkroening could say anything about that.

I think the best way to solve that for now is to provide some dummy audio file and expanding its range to fit the video length.

kkroening commented 4 years ago

Hmm, yeah we really should get #62 mergeable since it's a commonly requested feature. There are a few changes that are still needed on that branch, so I'll bump up the priority of it in my TODO list and try to get to it soon 🙂

jbitton commented 4 years ago

Hey @kkroening! Any updates on this? Having issues using anullsrc too.

theumairriaz commented 4 years ago

@kkroening @jbitton here is a code snippet for adding null audio

def generate_empty_audio(duration):

    """
    it is used to generate an empty audio

    Parameters
    ----------
    duration : int/float
            duration for the blank audio

        Return
        ------
        string

        it will return the path of the generated empty audio

    """

    try:
        path = settings.MEDIA_ROOT + f"{str(uuid4())}_audio.aac"
        stream = ffmpeg.input("anullsrc=cl=stereo:r=44100").output(path)
        compiled = ffmpeg.compile(stream.audio, overwrite_output=True)
        ffmpegargs = patch_audio(compiled, duration)
        subprocess.run(ffmpegargs, stdout=subprocess.PIPE)
        empty_audio.append(path)
        return path
    except ffmpeg.Error as e:
        print(e.stderr)
        raise Exception(str(e))

def patch_audio(arglist, duration):

    """
        to make the command of ffmpeg for creation of blank audio

        Parameters
        ----------
        arglist : list
            list of commands created by the python-ffmpeg
        duration : int/float
            duration for the blank audio

        Return
        ------
        list

        it will return the list of command that would be compiled in the process.run
    """

    try:
        for i, arg in enumerate(arglist):
            if arg == 'anullsrc=cl=stereo:r=44100':
                return arglist[:i-1] + ['-f', 'lavfi'] + ['-t', f"{duration}"] + arglist[i-1:]
        return arglist
    except Exception as identifier:
        raise Exception(str(identifier))

If you guys just want to add the pause in the start you can use for video set_pts(f"PTS-STARTPTS"+{<time in seconds here>/TB}) and for audio filter_('adelay', <time in miliseconds>)

joceyngan commented 2 years ago

audio_cmd = 'anullsrc=r=16000:cl=mono:d={}'.format(duration) audio_stream = ffmpeg.input(audio_cmd, f='lavfi')

I add anullsrc in ffmpeg-python like above and works for me, in case anyone is still looking for solution to this in 2022