I use Linux as my daily driver and have done so for the past 6 years. I believe it's the best operating system. So I figured out the best way to encode on it.
FFmpeg is my tool of choice. Previously, I used to have a bunch of commands to do one encode, which also generated a bunch of intermediate, temporary files, but when it came time to encode
[5036] Linux Cyber Shadow by keylie in 42:24.12, I figured that I could just use one script and thus have only one file generated, which would be most convenient.
So here's the encode script I used:
Download encode.shLanguage: shell
#!/usr/bin/env bash
ffmpeg \
\
-t 2 -f lavfi -i anullsrc \
\
-t 2 -loop 1 -r 60 -i /B/P/tasencodelogos/logo_16to9_3840x2160.png \
\
-t 49:01.500 -i 'cybershadow_dump.avi' \
\
-qp 0 -pix_fmt yuv420p \
-filter_complex '[2]ass=cybershadow_dump.ass,scale=3840:2160:flags=neighbor[v];[1]setsar=1[im];[im][0][v]concat=n=2:v=1:a=1' \
-c:a flac \
\
cybershadow_encode.mkv
Make sure to
chmod +x ./encode.sh before running it with
./encode.sh (you'll want to change the path to the logo
.png too). For the subtitles (
cybershadow_dump.ass), I used Aegisub (which on Fedora can be installed with
sudo dnf install aegisub). For the dump (
cybershadow_dump.avi), I dumped with UTVideo for the video codec and FLAC for the audio codec.
Now this might look a bit complicated, but this is actually as simple as I could make it. In shell scripting, ending a line with a backslash (
\) escapes the newline so the current line actually continues to the next line, and I use that to make the
ffmpeg invocation be spread out across multiple lines. If it was one line, it would be
ffmpeg -t 2 -f lavfi -i anullsrc -t 2 -loop 1 -r 60 -i /B/P/tasencodelogos/logo_16to9_3840x2160.png -t 49:01.500 -i 'cybershadow_dump.avi' -qp 0 -pix_fmt yuv420p -filter_complex '[2]ass=cybershadow_dump.ass,scale=3840:2160:flags=neighbor[v];[1]setsar=1[im];[im][0][v]concat=n=2:v=1:a=1' -c:a flac cybershadow_encode.mkv
which is harder to read.
So here's what each line does:
- -t 2 -f lavfi -i anullsrc:
FFmpeg takes a series of inputs. Each input in FFmpeg is specified with -i, followed by the name (in most cases this will be a filename on your disk), and the options preceding it are specifying information about that input.
In this case, -t 2 means to only take 2 seconds of the input, and -f lavfi specifies that the format is lavfi. With -i anullsrc, this means to create an input that is 2 seconds of blank audio. As you may have guessed, this is needed to place 2 seconds of blank audio underneath the 2 seconds of logo, which will be the next input.
- -t 2 -loop 1 -r 60 -i /B/P/tasencodelogos/logo_16to9_3840x2160.png:
Again, -t 2 specifies a length of only 2 seconds. -loop 1 means that the resulting video should loop (1 meaning 'true', as opposed to 0). -r 60 specifies that the framerate should be 60 frames per second (matching the dump of Cyber Shadow), and of course -i is followed by the path to the image. As you may have guessed, the image is my logo file.
- -t 49:01.500 -i 'cybershadow_dump.avi':
This is the dump itself. -t 49:01.500 means to cut it off at timecode 49:01.500 (which is right when the ending music loops in the dump).
- -qp 0 -pix_fmt yuv420p:
-qp 0 means that the quality should be lossless (if it was higher, it would be more lossy). This is needed because we haven't specified a video codec for the output file, so FFmpeg will default to x264. -pix_fmt yuv420p specifies that the pixel format should be YUV420p.
- -filter_complex '[2]ass=cybershadow_dump.ass,scale=3840:2160:flags=neighbor[v];[1]setsar=1[im];[im][0][v]concat=n=2:v=1:a=1':
Okay, this is the big line. -filter_complex is basically just a grab bag of letting you use every filter at once. It's what lets me put this all in one script instead of having to have multiple scripts and a bunch of intermediate temporary files laying around.
Each entry in the following string is separated with a semicolon (;), and follows the format [input]filter[output]. There can be multiple inputs, and multiple filters are separated by commas (,). So let's look at each entry:
- [2]ass=cybershadow_dump.ass,scale=3840:2160:flags=neighbor[v]:
This takes the stream named 2, hardcodes the subtitles (from cybershadow_dump.ass), then scales it up to 4K resolution (I dumped the game at 1x native resolution to save disk space), and puts the result in a stream named v.
In this case, 2 refers to the third -i input from earlier, and FFmpeg automatically numbers each -i input for you (starting from 0).
- [1]setsar=1[im]:
This takes the 1 stream (the logo .png), sets its sample aspect ratio to 1, and then puts it in a stream named im. I'm not actually sure why this is here, but it's probably needed for concatenation.
- [im][0][v]concat=n=2:v=1:a=1:
This takes the im, 0, and v streams (the im and v streams are the same streams we created in previous entries), and concatenates them. n=2 because there are two segments to concatenate (the logo and the dump), and v=1:a=1 because there should only be 1 video and 1 audio track in the end. There is no stream specified at the end because this will end up being the output of the entire FFmpeg command itself.
- -c:a flac:
Sets the audio codec to FLAC.
- cybershadow_encode.mkv:
If there's an argument that isn't preceded by a flag, FFmpeg will interpret it as the name of the output file. So this is the output file.
With this, I could just let my computer encode the file and then upload the file straight to YouTube when it was done. Of course, I tested it by doing
-t 30 just to make sure everything encoded fine, before I did the full encode (which took ~2 hours).
Here's something slightly more complex, the script I used to make the downloadable uploaded to
archive.org:
Download downloadable.shLanguage: shell
#!/usr/bin/env bash
ffmpeg \
\
-t 2 -f lavfi -i anullsrc \
\
-t 2 -loop 1 -r 60 -i /B/P/tasencodelogos/logo_16to9_800x450.png \
\
-t 49:01.500 -i 'cybershadow_dump.avi' \
\
-f srt -i CyberShadow.srt
\
-crf 20 -pix_fmt yuv420p -x264opts keyint=600:merange=64:colormatrix=smpte170m \
-filter_complex '[0]aresample=48000,atrim=start_sample=5060[a];[2]ass=cybershadow_dump.ass,scale=800:450:flags=neighbor[v];[1]setsar=1[im];[im][a][v]concat=n=2:v=1:a=1' \
-c:a aac -vbr 2 \
-c:s mov_text \
\
cybershadow-tas-keylie.mp4
It's similar to the previous script, except there's now a fourth input (the commentary subtitles, named
CyberShadow.srt) which is embedded using
-c:s mov_text. There's also extra filters on the audio and extra options for the video to cut down on file size.
For my future encodes, I'm going to just make copies of these scripts and then tweak things as needed, which is the simplest thing that works for me.