zigford.org/video-editing-from-the-command-line.html
2020-07-21 06:49:32 +10:00

209 lines
11 KiB
HTML

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"><head>
<meta http-equiv="Content-type" content="text/html;charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="main.css" type="text/css" />
<link rel="stylesheet" href="blog.css" type="text/css" />
<link rel="alternate" type="application/rss+xml" title="Subscribe to this page..." href="feed.rss" />
<title>Video editing from the command line</title>
</head><body>
<div id="divbodyholder">
<div class="headerholder"><div class="header">
<div id="title">
<h1 class="nomargin"><a class="ablack" href="http://zigford.org/index.html">zigford.org</a></h1>
<div id="description"><a href="about.html">About</a><a href="links.html"> | Links</a><a href="scripts.html"> | Scripts</a><br>Sharing linux/windows scripts and tips</br></div>
</div></div></div>
<div id="divbody"><div class="content">
<!-- entry begin -->
<h3><a class="ablack" href="video-editing-from-the-command-line.html">
Video editing from the command line
</a></h3>
<!-- bashblog_timestamp: #201910051203.16# -->
<div class="subtitle">October 05, 2019 &mdash;
Jesse Harris
</div>
<!-- text begin -->
<p>I previously posted about <a href="converting-vhs-and-dv-to-modern-formats---part-1.html">capturing video in
Linux</a>. 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.</p>
<hr />
<p>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.</p>
<h2 id="problem-1-audio-sync">Problem 1 - Audio Sync</h2>
<p>I started out myself with <a href="http://www.pitivi.org/">pitivi</a> 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.</p>
<p>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.</p>
<p>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.</p>
<p>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.</p>
<p>Firstly, start by creating a small snippet of video where you will clearly be
able to see if the video is synced up.</p>
<pre><code> ffmpeg -i bigclip.mp4 -c copy -ss 00:01:55 -to 00:02:00 Small.mp4
</code></pre>
<p>Now we will use the following script</p>
<pre><code> #!/bin/sh
# fixaudiodelay.sh
Offset=&quot;${2}&quot;
inputFile=&quot;${1}&quot;
outputFile=&quot;${inputFile%%.*}-delayed${Offset}.mp4&quot;
out=&quot;${3:-$outputFile}&quot;
ffmpeg -i &quot;${inputFile}&quot; -itsoffset &quot;${Offset}&quot; \
-i &quot;${inputFile}&quot; -map 0:v -map 1:a -c copy &quot;${out}&quot;
</code></pre>
<p>The script will take parameter 1 is the clip, and parameter 2 is the audio
offset. We can use <code>seq</code> to generate a list of offsets to try and <code>xargs</code> to run
them.</p>
<pre><code> seq -2.0 .1 2.0 | xargs -n1 ./fixaudiodelay.sh Small.mp4
</code></pre>
<p>The result is 41 clips with .1 second offset difference. Play them all and find
the best offset. Then when you could use <code>./fixaudiodelay.sh BigClip.mp4 -1.7</code>
(or whatever was the best offset) to correct the entire file.</p>
<h2 id="problem-2-perfect-splitting">Problem 2 - Perfect splitting</h2>
<p>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.</p>
<p>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.</p>
<p>Solving it with ffmpeg is easier than you might think. When I showed cutting a
clip earlier, you can use the <code>-ss</code> parameter to declare where to start from in
the clip, and the <code>-to</code> parameter to declare the end. How I've solved it is like
this.</p>
<ol>
<li><p>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 <code>-</code> dash. Eg</p>
<pre><code> 00:00:00-00:10:05-SnowTrip
00:10:05.6-00:13:02-PlayingAtThePark
</code></pre>
</li>
<li><p>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.</p>
<p><strong>Note</strong> <em>You can't use the <code>copy</code> codec when cutting clips. As the keyframes
are not recreated</em></p>
<pre><code> while IFS=- read START END TITLE
do
OUTFILE=&quot;${TITLE}-Test.mp4&quot;
ffmpeg -nostdin -i BigClip.mp4 -c:v libx264 \
-c:a aac -ss $START -to $END -preset ultrafast \
$OUTFILE
done &lt; tracks.txt
</code></pre>
</li>
<li><p>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 <code>veryslow</code> and adding the following parameters</p>
<pre><code> -crf 23 -movflags faststart
</code></pre>
</li>
<li><p>Additionally in some cases, I add some video filters. I've found the
following work well:</p>
<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>
</li>
</ol>
<h2 id="problem-3-joining-videos">Problem 3 - Joining videos</h2>
<p>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 <code>dvgrab</code>. The resultant content is many 1gb .dv files.</p>
<p>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.</p>
<p>Use the following sh to generate a file that ffmpeg can understand:</p>
<pre><code> for i in *.dv; do echo &quot;file '$i'&quot; &gt;&gt; files.txt; done
</code></pre>
<p><strong>TIP</strong> <em>Did you know bash has a shorthand non-posix for loop?</em></p>
<pre><code> for i in *.dv; { echo &quot;file '$i' &gt;&gt; files.txt; }
</code></pre>
<p><strong>NOTE</strong> <em>The space after the <code>{</code> is needed</em></p>
<p>Now use the resultant files.txt to create a single test clip of the whole lot:</p>
<pre><code> ffmpeg -f concat -safe 0 -i files.txt \
-c:v libx264 -c:a aac -crf 35 \
-preset ultrafast testconcat.mp4
</code></pre>
<p>You can now use this file to create timestamps relative to the whole
concatenated lot.</p>
<h2 id="problem-4-lp-vs-sp">Problem 4 - LP vs SP</h2>
<p>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.</p>
<p>If multiple clips on a single tape switch between LP and SP (Short Play or
standard play) then ffmpeg would only <strong>see</strong> 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 <code>-ss</code>.
As ffmpeg will probe the start of the file, not the start specified by
<code>-ss</code>.</p>
<p>So we come now to the next tool in the unix tool belt <code>dd</code>. We can use <code>dd</code> to copy
a file but tell it to <strong>skip</strong> x bytes. Eg</p>
<pre><code> dd if=dvgrab-001.dv of=testdv.dv bs=1M skip=100
</code></pre>
<p>Using <code>bs=1M</code> and <code>skip=100</code> 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 <code>skip</code> option and if you need more granular
control, you could reduce the <code>bs</code> (byte size) down into the kilobyte range.</p>
<p>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.</p>
<p>Tags: <a href='tag_ffmpeg.html'>ffmpeg</a>, <a href='tag_dd.html'>dd</a>, <a href='tag_cli.html'>cli</a></p>
<!-- text end -->
<!-- entry end -->
</div>
<div id="footer">&copy <a href="http://twitter.com/zigford_org">Jesse Harris</a> &mdash; <a href="mailto:jesse&#64;zigford&#46;org">jesse&#64;zigford&#46;org</a><br/>
Generated with <a href="https://github.com/cfenollosa/bashblog">bashblog</a>, a single bash script to easily create blogs like this one</div>
</div></div>
</body></html>