Batch process audio (FLAC) with cover to Video (Script/MLT/Melt/XML)

I have a lot of tracks from a videogame OST that I would like to upload to Youtube.
I already screenshotted covers in the .PNG format.
I have already used Audio to Video, convert MP3 with image to MP4 - Online Converter and uploaded the videos, but the audio quality seems significantly worse (locally too, not because of Youtube).
Now the website pretty much has exactly the functionality I want, except for the lowered audio quality, so I am wondering, if it’s possible to have Shotcut (or another program) convert an image and audio to video.
The reason I want a script is, because the resolution of the images differ and Shotcut doesn’t automatically detect the resolution/aspect ratio (at least not the wonky resolutions the screenshots have).
Also while I’m at it I would love the script to use one base folder with sub folders for tracks that share covers and render all tracks (with the correct cover) to video into the output folder.
If such a script exists already, let me know, otherwise please tell me if it’s even possible to do the desired thing by writing a script and how to do so.

Folder structure:

Base
\-- Super Mario
    \-- CoverArt.png
        Super Mario - Track 1.flac
        Super Mario - Track 2.flac
\-- Sonic the Hedgehog
    \-- CoverArt.png
        Sonic the Hedgehog - Track 1.flac
        Sonic the Hedgehog - Track 2.flac
\-- Output
    \-- Super Mario - Track 1.mkv
        Super Mario - Track 2.mkv
        Sonic the Hedgehog - Track 1.mkv
        Sonic the Hedgehog - Track 2.mkv

Make sure an empty “Output” folder exists. Then run this command from the “Base” folder:

For /R %F In (*.flac) Do (
	ffmpeg ^
	-loop true -i "%~dpFCoverArt.png" ^
	-i "%F" ^
	-map 0:v:0 -map 1:a:0 -shortest ^
	-filter:v fps=24,scale="w='if(gt(a,16/9),1920,-1)':h='if(gt(a,16/9),-1,1080)':flags=bicubic+accurate_rnd+full_chroma_inp+full_chroma_int",pad=w=1920:h=1080:x=-1:y=-1:color=black,setdar=16/9,scale=out_color_matrix=bt709:out_range=mpeg,format=yuv420p ^
	-pix_fmt yuv420p -colorspace bt709 -color_primaries bt709 -color_trc bt709 -color_range mpeg ^
	-r 24 -vsync cfr ^
	-c:v libx264 -crf 27 -g 120 -bf 0 -preset veryfast -movflags +faststart+write_colr ^
	-c:a copy ^
	-f matroska ^
	-y ^
	"Output\%~nF.mkv"
)

Thank you for the quick reply! I’ll try it out tomorrow as it’s pretty late rn.

One immediate criticism:
Would it be possible to read image size from the cover art to use as resolution/aspect ratio for the videos?

Simply remove the first scale filter, and the pad and setdar filters. However, a side effect of non-standard sizes is that your source images for cover art must have dimensions that are even numbers. FFmpeg will fail to encode a 1537x855 image file into a video. This is the nature of 4:2:0 video and not a problem with the script.

Also, if any cover art is smaller than 1920x1080, your video may be considered SD quality by YouTube and get a seriously low bitrate to your audience, which would probably cause lower audio quality too. The reason I put the scale filter in there is to guarantee you got at least the Full HD bitrate.

Maybe the best compromise is to leave in the first scale filter to guarantee at least HD dimensions, then remove the pad and setdar filters so that no letterboxing gets added.

You should probably have explained, that you need to install ffmpeg for this command to work.
This is my version of the command for now:

 For /R %F In (*.flac) Do (
	"C:\Program Files\ffmpeg-4.4-essentials_build\bin\ffmpeg.exe" ^
	-loop true -i "%~dpFcover.png" ^
	-i "%F" ^
	-map 0:v:0 -map 1:a:0 -shortest ^
	-filter:v fps=1,format=yuv420p ^
	-pix_fmt yuv420p -colorspace bt709 -color_primaries bt709 -color_trc bt709 -color_range mpeg ^
	-r 24 -vsync cfr ^
	-c:v libx264 -crf 27 -g 120 -bf 0 -preset veryfast -movflags +faststart+write_colr ^
	-c:a copy ^
	-f matroska ^
	-y ^
	"Output\%~nF.mkv"
)

Is there a way for it to just use any .png file that’s in the folder? I don’t really wan’t to rename all of the pictures.
Also how do I change the format to mp4?
Another problem is that putting the command into cmd works (after cd [directory]), but a .bat file does nothing.
And one last request, I would also like to upload a video containing all OSTs, all flacs have a number at the start of their names and there’s a space between the number and the track name (they’re also in one big folder). I either want to import the flac files into Shotcut or process them with ffmpeg (just one cover).

Either ways, you’ve already helped a lot, so big thank you!

Your script has fps=1 in the filter section, but still has -r 24 later on, which means “frame rate 24fps”. These two lines are in conflict with each other. They should be set to the same thing.

The reason I chose 24 is because YouTube sometimes has problems with videos that are lower than 8fps. File size is not a concern even at 24fps because compression is so good on static images. May as well play it safe and avoid unnecessary problems.

Yes, with a second FOR statement that scans for PNG files. Put all of this into a batch file:

@Echo Off
SetLocal EnableExtensions EnableDelayedExpansion

For /R %%F In (*.flac) Do (
	Call :FirstPNG "%%~dpF"

	ffmpeg ^
	-loop true -i "!_FirstPNG!" ^
	-i "%%F" ^
	-map 0:v:0 -map 1:a:0 -shortest ^
	-filter:v fps=24,scale=out_color_matrix=bt709:out_range=mpeg,format=yuv420p ^
	-pix_fmt yuv420p -colorspace bt709 -color_primaries bt709 -color_trc bt709 -color_range mpeg ^
	-r 24 -vsync cfr ^
	-c:v libx264 -crf 27 -g 120 -bf 0 -preset veryfast -movflags +faststart+write_colr ^
	-c:a alac ^
	-f mp4 ^
	-y ^
	"Output\%%~nF.mp4"
)

rem Cleanup
Set _FirstPNG=
EndLocal
Exit /B 0

rem Subroutine to get first PNG in folder
:FirstPNG
For %%P In ("%~1*.png") Do (
	Set _FirstPNG=%%P
	GoTo :EOF
)

Notice the change from %VAR% to !VAR! in the ffmpeg call, which returns the run-time value of a variable rather than the parse-time value. More information about this can be found by typing help set at a Command Prompt.

The idea was to provide YouTube with the original FLAC for maximum quality. YouTube accepts FLAC in MKV. But FLAC cannot go into MP4. Since you prefer MP4, I changed the audio codec to ALAC (Apple Lossless) using -c:a alac and changed the output filename to an MP4 extension. This should keep audio quality extremely high rather than transcoding to AC-3, or worse, AAC.

This is explained very clearly in the help for documentation at a Command Prompt. In a batch file, the percent character is used to mark a variable name. To use a file-cursor variable within a FOR statement, use an escaped percent sign, such as For %%F In (*.flac) Do ...

The easiest way is to make all the individual image/audio MKV or MP4 files first. Then dump all the individual track filenames into a text file like this:

cd Output
dir /b *.mp4 > filelist.txt

Then use that filelist.txt to invoke ffmpeg’s concat muxer which basically stitches a bunch of videos together into one huge output video, with the obvious caveat being that all input videos must be of the same format.

See more about using concat here:

If cover art changing is a problem, then the alternative is to concat all the FLAC files together into one giant FLAC file, then use the original ffmpeg command to put a single artwork on the giant FLAC file.

1 Like

Thank you for the help, I’ll test it out once I have time. One thing is though, that in the official ffmpeg documentation it says:

  • To force the frame rate of the input file (valid for raw formats only) to 1 fps and the frame rate of the output file to 24 fps:

ffmpeg -r 1 -i input.m2v -r 24 output.avi

I just noticed it was way faster to render

It tells you how to force a mismatch if you need one, for illustrative purposes. This is useful when parsing raw video formats that don’t store the frame rate in their metadata and you have to provide that information manually (often because raw formats have no metadata). Notice that the documentation’s -r 1 occurs before the input file as opposed to before the output file.

You don’t need a mismatch for your task, so this doesn’t apply.

When I do your step with the filelist.txt, the order of the tracks is sorted completely wrong.
It sorts them like this:

1
[10-19]
2
[20-29]
3
[…]
Do you know how to change the command, so this doesn’t happen?

The filelist is sorted alphabetically, not numerically. There is no option to sort numerically from Command Prompt.

The easy fix is to rename the FLAC files to use two-digit numbering, like “Track 01.flac” instead of “Track 1.flac”. Here is a batch file to rename one-digit filenames into two-digit filenames. Run it from the Base folder.

@Echo Off
SetLocal EnableExtensions EnableDelayedExpansion

For /R %%F In ("* ?.flac") Do (
	Set _NewName=%%~nxF
	Set _NewName=!_NewName:~0,-6!0!_NewName:~-6!
	Ren "%%F" "!_NewName!"
)

Set _NewName=
EndLocal

If you try to edit this batch file to rename .mp4 files instead of FLAC files, remember to change the -6 lengths into -5, because “N.mp4” is one character shorter than “N.flac”

More information on variable substrings can be found in help set from the Command Prompt.

The only problem is that I have the track name after it (i.e. "4 title name.flac), so the length isn’t consistent. Is there no way to detect the space after the number?
Otherwise I’ll just do it manually.

This doesn’t even require a batch file. Run:

For /R %F In ("? *.flac") Do (
	Ren "%F" "0%~nxF"
)

Im having problems with concat. All filenames contain spaces and even a script to put quotes around them doesn’t work:

@Echo Off
SetLocal EnableExtensions EnableDelayedExpansion
for /f “delims=” %%a in (‘dir /b *.flac’) do (@echo ‘%%~nxa’ >> filelist.txt)
endlocal

But concat says: Line 1: unknown keyword '‘01’
here is the concat script I’m using:

@Echo Off
SetLocal EnableExtensions EnableDelayedExpansion
For %%F In (*.flac) Do (
“C:\Program Files\ffmpeg-4.4-essentials_build\bin\ffmpeg.exe” ^
-f concat ^
-safe 0 ^
-i filelist.txt ^
-c ^
copy “combined.flac”
)
pause
EndLocal
Exit /B 0

btw I did it without quotes first and it didn’t work either

Edit: I just realised you have to put file in front, should’ve read the linked post, however now it prompts me to overwrite output.flac each time

The -y option in FFmpeg means overwrite without prompting.

So it’s all working now?

I don’t think I explained it properly, I just added the -y tag, but the output (combined.flac) is just the first track for some reason

The For loop is unnecessary. The list of files is already in filelist.txt (the list doesn’t have to be generated by FOR anymore, nor are you referencing %%F anywhere in your FFmpeg command). The files will be compiled into one output file by a single FFmpeg command. Simply remove the outer For loop from your batch file.

If the output has only the first track, then there is either a problem with the filelist, or concat may require a “real” container format, of which .flac isn’t a fully-featured one. You could try compiling the filelist into an MKV container instead of .flac as an intermediate step if you’re sure the filelist is good. concat should work fine with MKV.

I got my result, still have some questions

I just used this script

and used concat to combine the mp4s.
(Note: I rendered in 60fps because I noticed lower framerate=longer silence at the end of each video. Also I had to run concat in cmd, I don’t know why it doesn’t work in batch. Lastly I had to remove apostrophes from the filenames, ideally the script that makes the filelist.txt should automatically add escape characters)

Bonus question:

I know there’s a way to write file attributes to a text file, but do you know how to get timestamps for the combined video? It might be possible by combining the length’s of each video (from before the concat) into a variable and writing the variable to a text file each time another length is added

Thank you for helping

concat works fine in batch, so something else must have been wrong.

That’s possible to do:

@Echo Off
SetLocal EnableExtensions EnableDelayedExpansion

Type Nul > filelist.txt

For %%F In (*.mp4) Do (
	Set _Filename=%%~nxF
	Echo file '!_Filename:'='\''!'>> filelist.txt
)

Set _Filename=
EndLocal

The rules for escaping filenames in a concat filelist are described here:
FFmpeg Utilities Documentation
Filename quoting in ffmpeg concat - Super User

There is a separate command called ffprobe which can return the durations of each video. These durations can be added up by a script like Python or PowerShell. Or they can be dumped into a CSV-like file that can be brought into a spreadsheet and cleaned up for export there. Batch files are not encouraged here because the Set /A Var=mathExpression syntax doesn’t support decimals, which would be necessary to prevent accumulated rounding error for long videos such as yours.

Here is a sample ffprobe command to return the duration of a video inside a FOR loop:

	ffprobe ^
	-hide_banner ^
	-i "%%F" ^
	-select_streams a:0 ^
	-show_entries stream=duration ^
	-print_format default=nokey=1:noprint_wrappers=1 ^
	> "!_TmpFile!" ^
	2> Nul

That’s the format for dumping the duration into a temp file that can be picked up by a calling script. If the script can read the output directly, then omit the > "!_TmpFile!" part.

The success of the probe depends greatly on the type of file being probed. It may be better to use -select_streams v:0 to get the video stream rather than the audio stream, depending on the specific codecs involved. You’ll have to customize this code to your environment for reliable results.

If this whole workflow is a rare task, then just pulling the durations into a spreadsheet and copy-paste to the YouTube video description could be the fastest way.

The video description format for YouTube chapter markers can be found here:
Video Chapters - YouTube Help

This is the concat.bat, it worked in cmd only

@Echo Off
SetLocal EnableExtensions EnableDelayedExpansion

“C:\Program Files\ffmpeg-4.4-essentials_build\bin\ffmpeg.exe” ^
-f concat ^
-safe 0 ^
-i filelist.txt ^
-c ^
copy combined.mp4

EndLocal
Exit /B 0

I’m not sure how to use do a for loop in Powershell, this is the code I have so far. I don’t know how to properly tell it to use -Filter *.mp4, something else is probably wrong too

foreach ($F in Get-ChildItem -Filter *.mp4) {
“C:\Program Files\ffmpeg-4.4-essentials_build\bin\ffprobe.exe” ^
-hide_banner ^
-i “$F” ^
-select_streams v:0 ^
-show_entries stream=duration ^
-print_format default=nokey=1:noprint_wrappers=1
> timestamps.txt ^
2> Nul
}

Lastly the filelister script is good, except one filename contains two exclamation marks like "01 hey! hey! 123" and the filelist.txt will only contain '01 hey'. I already tried manually correcting it with both '01 hey! hey! 123' and '01 hey\! hey\! 123' but neither worked. So I just changed the file name for now.

Thank you for the help