ffmpeg/bash - mapping code for several output - bash

I use ffmpeg on a RaspberryPi3 (99% sure it is a pre-compiled version which I got from https://johnvansickle.com/ffmpeg/ as other methods to install failed for me)
Starting point:
Several multimedia files, each with a single video track, one or more audio tracks, and sometimes plenty subtitles.
My objective:
A bash script taking 3 inputs: respectively track number for video (mostly 0), track number for audio (mostly 1 but can be 2 or..) and track number for subtitles (mostly 2 but can also be 3 or 7 or..).
As output, I want:
a) a new video file with selected tracks (video, single audio which is re-encoded, single subtitle)
b) a new separate srt file with only selected subtitle.
My code was already working for a) but can't seem to get it to work for b)
See below. It does generate the video I want with right selection of tracks.
But its fails to write the right srt file: seems to always default to first subtitle track ?
Is my mapping incorrect, or is this a bash coding issue ?
The file is called Remux.sh, and called with arguments so for example:
./Remux.sh 0 1 5
#!/bin/bash
PATH="/mnt/USB1/Series/_Remux/"
for fullfile in "$PATH"*.mkv
do
newname="${fullfile%}_converted.mkv"
srtname="${fullfile%}_converted.srt"
/usr/local/bin/ffmpeg -i "${fullfile}" -map 0:$1 -map 0:$2 -map 0:$3 \
-c:v copy -c:s copy -c:a ac3 -b:a 640k "${newname}" \
-c:s copy "$srtname"
done
Edit: many thanks #llogan for the pointer ! it's now working with the mapping addition :)

Each output must have its own -map options. Otherwise, an output with no -map options will use the default stream selection behavior which chooses only 1 stream per stream type.
#!/bin/bash
PATH="/mnt/USB1/Series/_Remux/"
for fullfile in "$PATH"*.mkv
do
newname="${fullfile%}_converted.mkv"
srtname="${fullfile%}_converted.srt"
/usr/local/bin/ffmpeg -i "${fullfile}" -map 0:$1 -map 0:$2 -map 0:$3 \
-c:v copy -c:s copy -c:a ac3 -b:a 640k "${newname}" \
-map 0:$3 -c:s copy "$srtname"
done
You may find it helpful to use stream specifiers in your -map options, such as -map 0:s:1 for subtitle stream #2 (index starts at 0). See FFmpeg Wiki: Map.
Recommend you paste your script into shellcheck.net for additional Bash suggestions.

Related

With ffmpeg, how to double a stream while applying a filter to the second copy?

I'm using Plex, which always considers the first audio stream, but is also failing to decode surround media files on my stereo system (my laptop).
For that reason, I intend to downmix (following a formula suggested in this answer, which is an improvement over -ac 2) a bunch of files so I have:
Stream 0:0 > Video
Stream 0:1 > 2.0 Filtered audio
Stream 0:2 > 5.1 Original audio
The problem is that while my downmixing requires filtering, the doubling of a stream requires copying and I found out that these 2 doesn't seem to go together, although it seems to me this could be done because while the filtering and copying are applied for the same input stream, they're not for the same output stream.
Here's my currently failing command:
ffmpeg -i "INPUT.mkv" -map 0:v -c:v copy -map 0:a:0 -c:a:0 ac3 -vol 425 -filter_complex "[0:a:0]pan=stereo|FL=0.5*FC+0.707*FL+0.707*BL+0.5*LFE|FR=0.5*FC+0.707*FR+0.707*BR+0.5*LFE" -map 0:a:0 -c:a:0 copy "OUTPUT.mkv"
Is there a way to do it?
So, you want to map 5.1 audio to 2 output audio streams: filtered 2.0 and original 5.1. You are on a right track but mixed up in the stream specifiers. Try this:
ffmpeg -i "INPUT.mkv" \
-filter_complex "[0:a:0]pan=stereo|FL=0.5*FC+0.707*FL+0.707*BL+0.5*LFE|FR=0.5*FC+0.707*FR+0.707*BR+0.5*LFE,volume=1.66[filtered]" \
-map 0:v -c:v copy \
-map [filtered] -c:a:0 ac3 \
-map 0:a:0 -c:a:1 copy \
"OUTPUT.mkv"
Changes: (1) named filter output as [filtered], (2) 2nd output audio stream should be a:1, (3) moved volume setting to complex_filter
Edit Note: Reflects Fabio and Lex's comments to the answer

concatenate audio files with an image

I am trying to concatenate multiple audio files and a single image into one video file using one command.
I have list of mp3 files and a playlist file (.m3u) in a direcotry.
I managed to do this but my solution is bad:
reading the playlist file and creating a new .txt in the ffmpeg required format
concatenating the audio files using the .txt into an .mp3
concatenating the large audio file and the static image into a video
This creates 2 unnecessary files that I have to delete.
I tried a different command
ffmpeg -loop 1 -framerate 1 -i myImage.jpg -i file1.mp3 -i file2.mp3 -i file3.mp3 -filter_complex '[0:0][1:0][2:0]concat=n=3:v=0:a=1' -tune stillimage -shortest output.mp4
however im getting a Error initializing complex filters.
Invalid argument error
Another kick in the nuts is that the system im working on has spaces in the folder names.
i tried using -i "concat:file1.mp3|file2.mp3|..." however i cannot use double quote marks to quote out the path so I get an invalid argument error.
Thank you very much for your help.
Method 1: concat demuxer
Make input.txt containing the following:
file 'file1.mp3'
file 'file2.mp3'
file 'file3.mp3'
Run ffmpeg:
ffmpeg -loop 1 -framerate 1 -i myImage.jpg -f concat -i input.txt -filter_complex "[0]scale='iw-mod(iw,2)':'ih-mod(ih,2)',format=yuv420p[v]" -map "[v]" -r 15 -tune stillimage -map 1:a -shortest -movflags +faststart output.mp4
All MP3 files being input to the concat demuxer must have the same channel layout and sample rate. If they do not then convert them using the -ac and -ar options so they are all the same.
Method 2: concat filter
Update: There seems to be a bug with -shortest not working with the concat filter (I keep forgetting about that). See the method above using the concat demuxer, or replace -shortest with -t. The value for -t should equal the total duration of all three MP3 files.
ffmpeg -loop 1 -framerate 1 -i myImage.jpg -i file1.mp3 -i file2.mp3 -i file3.mp3 -filter_complex "[0]scale='iw-mod(iw,2)':'ih-mod(ih,2)',format=yuv420p[v];[1:a][2:a][3:a]concat=n=3:v=0:a=1[a]" -map "[v]" -r 15 -map "[a]" -tune stillimage -shortest -movflags +faststart output.mp4
Option descriptions
scale filter makes image have even width and height which is required when outputting YUV 4:2:0 with libx264.
format filter sets chroma subsampling to 4:2:0, otherwise libx264 will try to limit subsampling, but most players can only handle 4:2:0.
concat filter is accepting file1.mp3, file2.mp3, and file3.mp3 as inputs. Your original command was trying to concat the video to the audio resulting in Invalid argument.
-map "[v]" chooses the video output from -filter_complex.
-r 15 sets output frame rate to 15 because most players can't handle 1 fps. This is faster than setting -framerate 15.
-map "[a]" chooses the audio output from -filter_complex.
-map 1:a chooses the audio from input #1 (the second input as counting starts from 0).
-movflags +faststart after encoding finishes this option moves some data from the end of the MP4 output file to the beginning. This allows playback to begin faster otherwise the complete file will have to be downloaded first.

Use ffmpeg to add multiple subtitles separately to a video

I am trying to add multiple languages of subtitles to a video using ffmpeg. I succeeded in adding 1 language, but can't seem to add a second one.
I use this simple script to add english subtitles to my video.
ffmpeg -i %1 -i subs_eng.srt -map 0 -vcodec copy -acodec copy -scodec subrip -metadata:s:s:0 language=English "%~n1"_eng.mkv
In addition, I run another script to add the Dutch subtitles.
ffmpeg -i %1 -i subs_nl.srt -map 0? -vcodec copy -acodec copy -scodec subrip -metadata:s:s:1 language=Dutch "%~n1"_nl.mkv
But whenever I add the second language, it doesn't seem to do anything. The command terminal shows that ffmpeg is processing the video, but there is only 1 subtitle language available in vlc media player (the first one).
I really want to be able to add it in 2 takes rather than in the same script, as I don't have both languages for all of my video's.
Without -map for the subtitle stream, ffmpeg will select only one subtitle stream from among all inputs.
ffmpeg -i %1 -i subs_nl.srt -map 0 -map 1 -vcodec copy -acodec copy -c:s:0 copy -c:s:1 subrip -metadata:s:s:1 language=Dutch "%~n1"_nl.mkv
I set codec mode for the existing subtitle stream to copy and subrip for only the new one. This assumes you muxed exactly one subtitle stream earlier.
It is easier in fact. You can add subtitles to a file without removing old ones by
ffmpeg -i input.mkv -i input.srt -map 0 -map 1 -c copy output.mkv
-map x selects all streams from a file so all streams from both files get into the output file. If you add more input subtitle tracks, you need to supply -map 2, -map 3 and so on. See the very conscious and simple Map documentation.
Now more tricky is if you want to properly label these subtitles. You can add
-metadata:s:s:0 language=heb -metadata:s:s:0 handler_name=Hebrew -metadata:s:s:0 title=Hebrew
-metadata:s:s:1 language=eng -metadata:s:s:1 handler_name=English -metadata:s:s:1 title=English
But you need to know the final mapping which will depend on original files ab/presence of subtitles.
Credits to eladkarako.

Audio conversion and filter application using ffmpeg with 1 command

I have an mp3 file I need to convert to wav, and apply loudnorm filter afterwards. So far i've been doing it with 2 commands.
ffmpeg -i input.mp3 output.wav
and
ffmpeg -y -nostdin -i output.wav -filter_complex "[0:0]FILTER PARAMETER" -map_metadata 0 -map_metadata:s:a:0 0:s:a:0 -map_chapters 0 -map [norm0] -c:a pcm_s16le -ar 1500 -c:s copy out2.wav
Is there anyway to use 1 command instead of 2? I tried converting straight from mp3 to out2.wav by changing output.wav to input.mp3, but when the resulting file is compared with diff, they are different compared to the 2 step process.
I'm fairly new to ffmpeg, and have spent hours on the documentation without really knowing how to accomplish what i'm trying to do.

ffmpeg command to merge file with good quality

I am looking for a better command that can merge both audio & video files into one with a better quality.
I found this command from Muaz Khan's WebRTC APIs.
ffmpeg -i {$audioFile} -i {$videoFile} -map 0:0 -map 1:0 {$mergedFileName}
Later on server i had to add "-strict -2" with this command as on server it says that above command is experimental if I still want to use it you should add "-strict -2" with it.
It is working well but my video file (.webm) with size 2.2MB and audio file (.wav) with size 1.5MB was merged into a new file (.webm) with size 422.5KB. This new video file is having lag.
Also I want the meta information for duration of video is already written on the resulting video file.
Is there any command which can give the merged file without lagging and both video and audio of the new file are of good quality ?
Use
ffmpeg -i {$audioFile} -i {$videoFile} -map 0:0 -c:a libopus -map 1:0 -c:v copy {$mergedFileName}
This will encode only the audio, leaving the video intact. Use libvorbis if libopus isn't present in your FFmpeg.

Resources