207 lines
8.4 KiB
Markdown
207 lines
8.4 KiB
Markdown
Video editing from the command line
|
|
|
|
I previously posted about [capturing video in
|
|
Linux](converting-vhs-and-dv-to-modern-formats---part-1.html). While this isn't
|
|
exactly part 2 of that post there is enough crossover to warrant a link. This
|
|
post is about how I have strangely found video editing to be much easier from
|
|
the command line than a gui app.
|
|
|
|
---
|
|
|
|
Firstly, there isn't a shortage of gui apps for editing video on Linux for very
|
|
basic editing. My requirements are very simple. I need to be able to cut and
|
|
join various video files, and product 2 output files. A file for hosting on the
|
|
web and a file for archival purposes.
|
|
|
|
Problem 1 - Audio Sync
|
|
----------------------
|
|
|
|
I started out myself with [pitivi](http://www.pitivi.org/) and tried a few others.
|
|
My trouble with each editor was speed and accuracy (also my brain). On some
|
|
videos I had to adjust the audio sync and was driven mad by moving audio tracks
|
|
around. I had alot of trouble knowing if the audio was starting too early or too
|
|
late and which way to move the audio to correct it.
|
|
|
|
With a gui editor, this involves click and dragging an audio track one way or
|
|
another (for accuracy, you have to zoom in very far). Once zoomed in and you
|
|
move the track, you then need to zoom out, place the play head, click play and
|
|
then make a judgement if you made it better or worse.
|
|
|
|
For some reason, my brain could never tell exactly which way to move the audio
|
|
until it was way too far in the wrong direction. This was a source of
|
|
frustration that I initially solved using ffmpeg on the command line.
|
|
|
|
Using ffmpeg I wrote a very simple sh script to output a small time slice of
|
|
the video many times with different audio offsets. Then it was simply a matter
|
|
of watching each video and picking the right one. The resulting audio offset
|
|
that was used for that video can then be used on the entire clip.
|
|
|
|
Firstly, start by creating a small snippet of video where you will clearly be
|
|
able to see if the video is synced up.
|
|
|
|
ffmpeg -i bigclip.mp4 -c copy -ss 00:01:55 -to 00:02:00 Small.mp4
|
|
|
|
Now we will use the following script
|
|
|
|
#!/bin/sh
|
|
|
|
# fixaudiodelay.sh
|
|
|
|
Offset="${2}"
|
|
inputFile="${1}"
|
|
outputFile="${inputFile%%.*}-delayed${Offset}.mp4"
|
|
out="${3:-$outputFile}"
|
|
ffmpeg -i "${inputFile}" -itsoffset "${Offset}" \
|
|
-i "${inputFile}" -map 0:v -map 1:a -c copy "${out}"
|
|
|
|
The script will take parameter 1 is the clip, and parameter 2 is the audio
|
|
offset. We can use `seq` to generate a list of offsets to try and `xargs` to run
|
|
them.
|
|
|
|
seq -2.0 .1 2.0 | xargs -n1 ./fixaudiodelay.sh Small.mp4
|
|
|
|
The result is 41 clips with .1 second offset difference. Play them all and find
|
|
the best offset. Then when you could use `./fixaudiodelay.sh BigClip.mp4 -1.7`
|
|
(or whatever was the best offset) to correct the entire file.
|
|
|
|
Problem 2 - Perfect splitting
|
|
-----------------------------
|
|
|
|
The second problem I had with gui editors, is that many home videos are
|
|
completely different scenes butted up against each other on the tape. When
|
|
transferring these to a new modern format, we want our audience to be able to
|
|
skip the scenes.
|
|
|
|
With the gui editors, again we have the problem of accuracy along with the
|
|
inability to at once, save all the clips from the single file. Say you have 4
|
|
distinct clips in a file. How can you mark each clip as a separate file then
|
|
press encode and walk away? You can't you would have to create 3 separate
|
|
projects and encode each one separately.
|
|
|
|
Solving it with ffmpeg is easier than you might think. When I showed cutting a
|
|
clip earlier, you can use the `-ss` parameter to declare where to start from in
|
|
the clip, and the `-to` parameter to declare the end. How I've solved it is like
|
|
this.
|
|
|
|
1. Create a track file which lists all the clips start, end and titles. I chose
|
|
to separate each clip by a line, and each parameter by a `-` dash. Eg
|
|
|
|
00:00:00-00:10:05-SnowTrip
|
|
00:10:05.6-00:13:02-PlayingAtThePark
|
|
|
|
2. I came up with the start and end positions, simply by marking down the times
|
|
while watching the clip using mpv or vlc. However, the proof is in the
|
|
pudding, so I use ffmpeg with some parameters to produce test video files.
|
|
These won't be good enough quality to upload to the web, but are sufficient
|
|
for verifying the time stamps and are very quick to encode and see the
|
|
results.
|
|
|
|
**Note** _You can't use the `copy` codec when cutting clips. As the keyframes
|
|
are not recreated_
|
|
|
|
while IFS=- read START END TITLE
|
|
do
|
|
OUTFILE="${TITLE}-Test.mp4"
|
|
ffmpeg -nostdin -i BigClip.mp4 -c:v libx264 \
|
|
-c:a aac -ss $START -to $END -preset ultrafast \
|
|
$OUTFILE
|
|
done < tracks.txt
|
|
|
|
3. After viewing the resulting files, I can make minute adjustments to the track
|
|
file so the clips are perfect. Then I adjust for a better file for the web by
|
|
changing the preset to `veryslow` and adding the following parameters
|
|
|
|
-crf 23 -movflags faststart
|
|
|
|
4. Additionally in some cases, I add some video filters. I've found the
|
|
following work well:
|
|
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<td>Desired affect</td>
|
|
<td>Parameter</td>
|
|
<tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td>Increase volume</td>
|
|
<td>-filter:a "volume=2.0"</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Deinterlace</td>
|
|
<td>-vf "yadif"</td>
|
|
</tr>
|
|
<tr>
|
|
<td>Archival Quality</td>
|
|
<td>Replace libx264 with libx265</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
Problem 3 - Joining videos
|
|
--------------------------
|
|
|
|
The last problem I will discuss may only be a problem on Linux. While many of my
|
|
video files are converted from VHS using a composite to usb converter, some are
|
|
coming from DV tape. DV Tape stores it's data digitally in Mpeg-2 on a magnetic
|
|
tape. On Linux, I've been able to transfer these files using FireWire and a
|
|
command line tool `dvgrab`. The resultant content is many 1gb .dv files.
|
|
|
|
While these files can individually be processed by ffmpeg, it is more ideal to
|
|
join them into a single clip for processing so that you can grab clips that span
|
|
multiple files and use a single set of timestamps for processing. To adjust
|
|
ffmpeg for this use, we use another file which tells ffmpeg all the files to
|
|
concatenate.
|
|
|
|
Use the following sh to generate a file that ffmpeg can understand:
|
|
|
|
for i in *.dv; do echo "file '$i'" >> files.txt; done
|
|
|
|
**TIP** _Did you know bash has a shorthand non-posix for loop?_
|
|
|
|
for i in *.dv; { echo "file '$i' >> files.txt; }
|
|
|
|
**NOTE** _The space after the `{` is needed_
|
|
|
|
Now use the resultant files.txt to create a single test clip of the whole lot:
|
|
|
|
ffmpeg -f concat -safe 0 -i files.txt \
|
|
-c:v libx264 -c:a aac -crf 35 \
|
|
-preset ultrafast testconcat.mp4
|
|
|
|
You can now use this file to create timestamps relative to the whole
|
|
concatenated lot.
|
|
|
|
Problem 4 - LP vs SP
|
|
--------------------
|
|
|
|
DV Camera's in the early 2000s started featuring a LP (Long Play) feature which
|
|
allowed them to drop the sample rate on the audio (and maybe video?) significantly
|
|
in order to fit more on the tape. This introduces a problem for ffmpeg.
|
|
|
|
If multiple clips on a single tape switch between LP and SP (Short Play or
|
|
standard play) then ffmpeg would only **see** what it encountered when it
|
|
first probed the file. The result could be video/audio playing either very fast
|
|
(chipmunk voice) or very slow. The trick here is to slice the .dv file far
|
|
enough in that ffmpeg's probe matches the clip of audio you begin to transcode.
|
|
It is not enough to simple specify the start position using `-ss`.
|
|
As ffmpeg will probe the start of the file, not the start specified by
|
|
`-ss`.
|
|
|
|
So we come now to the next tool in the unix tool belt `dd`. We can use `dd` to copy
|
|
a file but tell it to **skip** x bytes. Eg
|
|
|
|
dd if=dvgrab-001.dv of=testdv.dv bs=1M skip=100
|
|
|
|
Using `bs=1M` and `skip=100` will create a new file that will skip roughly 100
|
|
megabytes of video at the start. Depending on where you clip begins/ends, you
|
|
will have to fiddle with the `skip` option and if you need more granular
|
|
control, you could reduce the `bs` (byte size) down into the kilobyte range.
|
|
|
|
If the resulting output file starts at the clip you are wanting to produce, you
|
|
could now replace the original dvgrab file from your tracks list file with the
|
|
newly generated file.
|
|
|
|
Tags: ffmpeg, dd, cli
|