Showing posts with label FFMpeg. Show all posts
Showing posts with label FFMpeg. Show all posts

Tuesday, March 24, 2026

Extending Video Introduction

 Sometimes it's useful to slow, or extend the introduction of a video.  For example, suppose you're utilizing a slow video transition (like a fade out/in effect) but don't want to miss the beginning frames of the second video.  By extracting the first frame of the video, elongating it to X seconds we can preserve the transition effect without losing video content.


Let's take a peek at how to accomplish this;


$ cat -n Makefile
     1    all: postVideo.mp4
     2   
     3    video.mp4: BigBuckBunny.mp4
     4        ${SH} ffmpeg -i $< -codec copy -strict -2 -t 10 $@
     5   
     6    image.jpg: video.mp4
     7        ${SH} ffmpeg -i $< -vf "select=eq(n\,0)" -q:v 3 $@
     8        ${SH} display $@
     9   
    10    preVid.mp4: image.jpg
    11        ${SH} ffmpeg -loop 1 -i $< -f lavfi -i aevalsrc=0 -t 3 $@
    12   
    13    postVideo.mp4: preVid.mp4 video.mp4
    14    #    ${SH} ffmpeg -i preVid.mp4 -i video.mp4 -filter_complex "[0:v] [0:a] [1:v] [1:a] concat=n=2:v=1:a=1 [vv] [aa]" -map "[vv]" -map "[aa]" $@
    15        ${SH} ffmpeg -i preVid.mp4 -i video.mp4 -filter_complex "[0:v] [0:a] [1:v] [1:a] concat=n=2:v=1:a=1 [vo] [ao]" -map "[vo]" -map "[ao]" $@
    16   
    17    clean:
    18        ${RM} *.jpg
    19        ${SH} find . -name "*.mp4" -not -name "BigBuckBunny.mp4" -delete

The input video (video.mp4) is generated by grabbing the first 10 seconds of BigBuckBunny.mp4; refer lines 3-4

Then, the first frame of the input video (video.mp4) is extracted and saved as image.jpg (ref lines 6-8)

Then, a new introduction video clip is generated by converting the image into a video clip of X seconds; ref lines 10-11).  Note, a null audio track is created to preserve the audio in the outgoing video.

Lastly, a new video is created by concatenating the intro clip with the original clip, the result as a video with extended first frame. 

One other trick worth mentioning with this makefile, I frequently want a clean target to clobber any video files created along the way.  However, you don't want to delete the original source file, this can be accomplished by utilizing a find+not condition; ref line 19.  This proves useful for many projects.

And, just like that, you've got a video with an elongated intro.

Monday, April 11, 2022

Extracting Video Information w/FFProbe



It’s an interesting thing about tools, when they deliver what you need from them you’re often uninterested in ’how the sausage is made’, but digging into the details often re-enforces your understanding in the end. Kinda like eating your broccoli, its oftentimes good for you, and likely you’ll be better off having done it.

There is just shy of a bazillion things you can learn about FFmpeg and the video/audio domain, we’re going to spend just a little bit of time trying to understand some of the details readily available to us and hopefully understand the tooling and domain just a little bit more than when we started.

Saddle up, grab a beer and ’read on’ fellow digital cowboy. FFmpeg typically comes paired with a useful utility called ffprobe, a media prober, which we’ll use to examine media files and pull out interesting nuggets of information.

FFprobe, like FFmpeg, is pretty verbose when run writing a ton of debug information to stderr. This proves useful when it’s needed, but burdensome when not. For our uses we will quiet the utilities down by specifying -loglevel quiet. 

Let’s start by examining our media files container.
$ ffprobe - loglevel quiet - show_format BigBuckBunny . mp4
[ FORMAT ]
filename = BigBuckBunny . mp4
nb_streams =2
nb_programs =0
format_name = matroska , webm
format_long_name = Matroska / WebM
start_time = -0.007000
duration =596.501000
size =107903686
bit_rate =1447155
probe_score =100
TAG : C OMPATI BLE_BR ANDS = iso6avc1mp41
TAG : MAJOR_BRAND = dash
TAG : MINOR_VERSION =0
TAG : ENCODER = Lavf56 .40.101
[/ FORMAT ]

As you’re likely aware, a media container is simply a file that contains the video(s), audio(s), and subtitle(s). Media-wide properties, like file size, tags, length....are often available as well as user-defined tags (like GPS, date,...). By default, show format will show all properties of the media  container.Sometimes, you may wish to limit the fields to ones you’re particularly interested in, like duration and size. You’ll notice the user-tags are displayed despite not being specified, i’ve not found a way to suppress them directly so they can simply be ignored.

$ ffprobe - loglevel quiet - show_format BigBuckBunny .
mp4 - show_entries format = duration , size
[ FORMAT ]
duration =596.501000
size =107903686
TAG : COMPATIBLE_BRANDS = iso6avc1mp41
TAG : MAJOR_BRAND = dash
TAG : MINOR_VERSION =0
TAG : ENCODER = Lavf56 .40.101
[/ FORMAT ]

Cool, but not particularly interesting, and really nothing a filemanager couldn’t show you with a simple right-click.

Let’s dig a bit deeper by examining the video/audio frames. By specifying the show frames we can extract debug information for each frame in the media file. Let’s peek at the first few dozen lines.

$ ffprobe - loglevel quiet - show_frames BigBuckBunny .
mp4
[ FRAME ]
media_type = video
stream_index =0
key_frame =1
pkt_pts =0
pkt_pts_time =0.000000
pkt_dts =0
pkt_dts_time =0.000000
best_effort_timestamp =0
best_effort_timestamp_time =0.000000
pkt_duration =41
pkt_durat ion_time =0.041000
pkt_pos =1111
pkt_size =208
width =1280
height =720
pix_fmt = yuv420p
sample_aspect_ratio =1:1
pict_type = I
coded_picture_number =0
display_picture_number =0
interlaced_frame =0
top_field_first =0
repeat_pict =0
color_range = unknown
color_space = unknown
color_primaries = unknown
color_transfer = unknown
chroma_location = left
[/FRAME ]
[ FRAME ]
media_type = audio
stream_index =1
key_frame =1
pkt_pts =0
pkt_pts_time =0.000000
pkt_dts =0
pkt_dts_time =0.000000
best_effort_timestamp = -7
best_effort_timestamp_time = -0.007000
pkt_duration =13
pkt_duration_time =0.013000
pkt_pos =1368
pkt_size =3
sample_fmt = fltp
nb_samples =648
channels =2
channel_layout = stereo
[/ FRAME ]

Notice that this snippet contains two frames, one video and one audio, each
with a set of media-specific fields. Collectively, we’re left with the follow-
ing collection of fields: best effort timestamp, best effort timestamp time, chan-
nel layout, channels, chroma location, coded picture number, color primaries,
color range, color space, color transfer, display picture number, height, inter-
laced frame, key frame, media type, nb samples, pict type, pix fmt, pkt dts, pkt dts time,
pkt duration, pkt duration time, pkt pos, pkt pts, pkt pts time, pkt size, repeat pict,
sample aspect ratio, sample fmt, stream index, top field first, width.

A dilligent and motivated reader could spend time investigating each field, but I’m more of a pass/fail kinda guy so we’ll limit our interest in a few relevant fields and briefly discuss the relevance of others.
Each packet specifies a media type, audio or video. Let’s focus on video frames for now, we can select only video streams to simplify our review.

$ ffprobe - loglevel quiet - select_streams V -
show_frames BigBuckBunny . mp4[ FRAME ]
media_type = video
stream_index =0
key_frame =1
pkt_pts =0
pkt_pts_time =0.000000
pkt_dts =0
pkt_dts_time =0.000000
best_effort_timestamp =0
best_effort_timestamp_time =0.000000
pkt_duration =41
pkt_durat ion_ti me =0.041000
pkt_pos =1111
pkt_size =208
width =1280
height =720
pix_fmt = yuv420p
sample_aspect_ratio =1:1
pict_type = I
coded_picture_number =0
display_picture_number =0
interlaced_frame =0
top_field_first =0
repeat_pict =0
color_range = unknown
color_space = unknown
color_primaries = unknown
color_transfer = unknown
chroma_location = left
[/ FRAME ]
[ FRAME ]
media_type = video
stream_index =0
key_frame =0
pkt_pts =42
pkt_pts_time =0.042000
pkt_dts =42
pkt_dts_time =0.042000
best_effort_timestamp =42
best_effort_timestamp_time =0.042000
pkt_duration =41
pkt_duration_time =0.041000
pkt_pos =1325
pkt_size =37
width =1280
height =720pix_fmt = yuv420p
sample_aspect_ratio =1:1
pict_type = P

Packet Fields You’ll notice there are a number of packet-wise fields (8 specifically), remember even though we are inspecting a file many video/audio protocols support streaming and are more relevant for such purposes. Despite being named with packet-prefix/suffix, they are often relevant for files as well, so don’t simply disregard.

PictureType Field The pict type field can be particularly interesting for those interested in video compression. Video picture types are often referred to as I-frames, B-frames, or P-frames; each having to do with video compression. I-frames are modestly compressed and are considered self-contained, not requiring other frames to decode. P-frames and B-frames (bi-directional) however employ a higher level of compression by capitalizing on similarity with the previous or following frames. For example, rather than compress an entire video frame, if two video frames are similar differing slightly in specific regions we can focus our compression/storage to the regions of change and greatly increase our com-
pression as a result. That’s precisely the relevance of P-frames and B-frames. P-frames use data from the previous frame, storing/compressing what’s different rather than the entire frame. B-frames extend on this by utilizing the previous frame and the following frame. Pretty neat, huh?

Timestamp Fields Two particularly interesting fields are decoding time stamp (DTS) and presentation time stamp (PTS). These timestamps are particularly interesting when you wish to modify the playback speed of a video. The presentation time stamp (PTS) indicates at what respective time the frame should
be ’presented’, or displayed. At 3 minutes, 30.01 seconds into the movie, what frame(s) should pop up for the viewer? Adjusting the PTS of a file therefore can shift, speed up/down or simply alter when the frame is presented. Halving the PTS will speed up a video, doubling the PTS will slow it down. Relatively simple.

The decoding time stamp (DTS) however is often identical (or similar) to the PTS, but not necessarily. Why would we possibly need yet another timestamp? It all comes back to compression, let’s say a sequence of video frames come in the form of I-frames, P-frames and B-frames: I P B B... The I-frame is self contained, the following P-frame (which is dependent on the previous frame) can rely on the previous frame being decompressed before hand (because the previous frame PTS ¡ current frame PTS), but B-frames throw a wrench into the mix. B-frames are reliant on the previous and the next frame, so both those frames must be decompressed before the B-frame can be decompressed. As a general rule, PTS and DTS time stamps tend to only differ when a stream has B-frames in it. The first 30 frames, roughly the first second of our video, are aseries of I,P,B frames.

$ ffprobe -loglevel quiet -select_streams V -show_frames -show_entries frame=pict_type BigBuckBunny.mp4
[ FRAME ]
pict_type = I
[/ FRAME ]
[ FRAME ]
pict_type = P
[/ FRAME ]
[ FRAME ]
pict_type = P
[/ FRAME ]
[ FRAME ]
pict_type = P
[/ FRAME ]
[ FRAME ]
pict_type = P
[/ FRAME ]
[ FRAME ]
pict_type = B
[/ FRAME ]
[ FRAME ]
pict_type = B
[/ FRAME ]
[ FRAME ]
pict_type = P
[/ FRAME ]
[ FRAME ]
pict_type = B
[/ FRAME ]
[ FRAME ]
pict_type = P
[/ FRAME ]

Lovely, right?

Another pair of timestamps, a bit less relevant, are ’best effort’ timestamps (best effort timestamp,best effort timestamp time). These tend to only be relevant for streams that only specify a DTS timestamp (e.g. no PTS). Literally attempting to provide a guess for a PTS-like value, enforcing a monotonicly
increasing timestamp) derived from available timestamp values. 

Video Size Fields So riddle me this, why does each video stream have width/height fields? Wouldn’t that be better suited in the container? One, uniformly sized video file, right? Nope. A container often contains a number of audio tracks (alternative languages, director commentary,...), similarly a number of subtitles for a variety of languages. While not overly-common, a container can providemultiple video streams as well, alternative angles, 360-degree video, picture-in-picture.... Each video stream therefore requires an independent sizing fields to properly display.

So, that's it, that's all I got for now.  I feel like I better understand some of the fields, specifically DTS/PTS and a clearer understanding of the various compression frames.  Hope it was equally useful to you.

Cheers.

Thursday, March 31, 2022

FFmpeg - Concatenating Videos with Blend Transition


 

I received an anonymous comment on a past post FFMpeg Blending specifically asking how two video files could be concatenated with a blending effect transition.  

Note: anonymous commenter/reader, if you find yourself returning and find this post useful please drop a quick comment so I know the effort in addressing your question wasn't a lost. Thanks!

In past posts I feel we've covered most of the necessary filtering examples as well as concatenation of videos, it's a not-so-simple matter of putting them all together.  Let's start with constraints/complications that can bite you.


Constraints/Complications

Input Files

If your input files come from a uniform source (e.g. your phone, video camera, snippets from a larger video file) you may not have to worry about these factors.  For this example however, we will grab a couple videos from YouTube from different sources so we will have to address some issues.  

In general, your input files need to be similarly formatted.  Similar frame rates, video resolutions and audio tracks.  I've wasted more hours than I care to mention forgetting to take care in this step, let that be a word of caution to emphasize take a few minutes to check this beforehand to avoid making the mistakes I've made.  Best-case, if your formats don't match you get slapped in the face with an error from FFmpeg, worse case it succeeds in delivering a video that looks like garbage leaving you with an Easter Egg Hunt in finding the issue.  When in doubt, enforce some standard format prior to doing anything more sophisticated.

Let's start with video resolution; your input files (for concatenation and blends) need to be identically sized, not kinda similarly sized, but identically sized.  Source files that have different aspect ratios are particularly problematic because resizing them may not give you the required sizing leaving you to crop/pad accordingly.  

A consistent frame rate (FPS) is necessary for time-based filters, like the blending, make sure your frame rate (FPS) is identical for both input videos, otherwise you'll encounter unexpected results that may resemble this;

A consistent auto track existence; just shy of a bazillion times I've attempted to concatenate two videos only to find my audio tracks absent/stalling.  For example, applying an image snippet before/after a video results in stalling or absent audio until I relearn that I need to apply an empty audio track to the image video, otherwise concatenation doesn't know how to properly handle concatenation of one video with audio and another without.  All your source media for concatenation should all have an audio track or none have an audio tracks.  Mixing and matching will only give you headaches in the end.


Example

Let's move on to our example.

Let's snag two videos from YouTube

$ youtube-dl -f mp4 -o yt-stingray.mp4 https://www.youtube.com/watch?v=aXTk9VPZ4Gg


$  youtube-dl -f mp4 -o $@ https://www.youtube.com/watch?v=_oHpWw7L3d4

Examining these files, you'll find they are aren't uniform in resolution or framerate.
$ ffprobe -i yt-stingray.mp4 
ffprobe version 4.2.2 Copyright (c) 2007-2019 the FFmpeg developers
  built with gcc 5.4.0 (Ubuntu 5.4.0-6ubuntu1~16.04.12) 20160609
  configuration: --enable-libx264 --enable-nonfree --enable-gpl --enable-libfreetype --enable-libmp3lame --enable-libzmq
  libavutil      56. 31.100 / 56. 31.100
  libavcodec     58. 54.100 / 58. 54.100
  libavformat    58. 29.100 / 58. 29.100
  libavdevice    58.  8.100 / 58.  8.100
  libavfilter     7. 57.100 /  7. 57.100
  libswscale      5.  5.100 /  5.  5.100
  libswresample   3.  5.100 /  3.  5.100
  libpostproc    55.  5.100 / 55.  5.100
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'yt-stingray.mp4':
  Metadata:
    major_brand     : mp42
    minor_version   : 0
    compatible_brands: isommp42
    creation_time   : 2019-05-22T15:56:25.000000Z
  Duration: 00:47:09.44, start: 0.000000, bitrate: 397 kb/s
    Stream #0:0(und): Video: h264 (Constrained Baseline) (avc1 / 0x31637661), yuv420p(tv, smpte170m), 480x360 [SAR 1:1 DAR 4:3], 299 kb/s, 23.98 fps, 23.98 tbr, 24k tbn, 47.95 tbc (default)
    Metadata:
      creation_time   : 2019-05-22T15:56:25.000000Z
      handler_name    : ISO Media file produced by Google Inc. Created on: 05/22/2019.
    Stream #0:1(eng): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 95 kb/s (default)
    Metadata:
      creation_time   : 2019-05-22T15:56:25.000000Z
      handler_name    : ISO Media file produced by Google Inc. Created on: 05/22/2019.

$ ffprobe -i yt-hardcastle01.mp4 
ffprobe version 4.2.2 Copyright (c) 2007-2019 the FFmpeg developers
  built with gcc 5.4.0 (Ubuntu 5.4.0-6ubuntu1~16.04.12) 20160609
  configuration: --enable-libx264 --enable-nonfree --enable-gpl --enable-libfreetype --enable-libmp3lame --enable-libzmq
  libavutil      56. 31.100 / 56. 31.100
  libavcodec     58. 54.100 / 58. 54.100
  libavformat    58. 29.100 / 58. 29.100
  libavdevice    58.  8.100 / 58.  8.100
  libavfilter     7. 57.100 /  7. 57.100
  libswscale      5.  5.100 /  5.  5.100
  libswresample   3.  5.100 /  3.  5.100
  libpostproc    55.  5.100 / 55.  5.100
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'yt-hardcastle01.mp4':
  Metadata:
    major_brand     : mp42
    minor_version   : 0
    compatible_brands: isommp42
    encoder         : Google
  Duration: 00:01:48.62, start: 0.000000, bitrate: 1362 kb/s
    Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p(tv, bt709), 1280x720 [SAR 1:1 DAR 16:9], 1232 kb/s, 29.97 fps, 29.97 tbr, 30k tbn, 59.94 tbc (default)
    Metadata:
      handler_name    : ISO Media file produced by Google Inc.
    Stream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 127 kb/s (default)
    Metadata:
      handler_name    : ISO Media file produced by Google Inc.

Concatenation 

Even with differing video resolutions and framerates, you can find reasonable success with simple concatenating of the video files.  FFmpeg will adopt the video resolution and framerate of the 1st input file and apply it throughout the output file.  Swapping the order of the above files results in the end resolution of the output file.
$ cat videoXX.mp4.txt
file 'yt-stingray.mp4'
file 'yt-hardcastle01.mp4'

$ ffmpeg -y -f concat -i videoXX.mp4.txt videoXX.mp4

This concatenation order results in 480x360 23.98 fps, while reversing the file order results in 1280x720 29.97 fps; often leaving you scratch your head.

Sometimes this will give you the result you're looking for, other times it may not.  I find it's in my best interest to do the scaling prior to concatenation, cropping and/or padding as I prefer.  Additionally, becoming overly reliant on FFmpeg 'doing it for you' avoids understanding what's going on only to bite you in the butt when you try something more complicated (e.g. like blending).


Blending

The blending effect we will be utilizing is a pixel-wide operations, one-by-one each frame is generated by 'blending' the corresponding pixels from each input file into the pixel in the destination frame.  If the two input media aren't identically scaled the blending falls apart because it lacks corresponding pixels.  FFmpeg errors out with an error resembling this;

[Parsed_amerge_0 @ 0x2e8b1c0] No channel layout for input 1
[Parsed_amerge_0 @ 0x2e8b1c0] Input channel layouts overlap: output layout will be determined by the number of distinct input channels
[Parsed_blend_2 @ 0x2dc7c80] First input link top parameters (size 480x360) do not match the corresponding second input link bottom parameters (size 1280x720)
[Parsed_blend_2 @ 0x2dc7c80] Failed to configure output pad on Parsed_blend_2
Error reinitializing filters!
Failed to inject frame into filter network: Invalid argument
Error while processing the decoded data for stream #1:0
Conversion failed!

This is FFmpegs way of saying that your input videos aren't uniformly sized.

Scaling input files with a variety of aspect ratios is always challenging, I find for me scaling to a specific video size w/optional padding seems to work well for me.

Let's scale our input videos to 1280x720, applying padding if necessary, and force a uniform 30/1 frame rate;

$ ffmpeg -i yt-stingray.mp4 -vf "scale=-1:720,pad=1280:ih:(ow-iw)/2" -r 30/1-strict -2 yt-stingray-scaled.mp4
$ ffmpeg -i yt-hardcastle.mp4 -vf "scale=-1:720,pad=1280:ih:(ow-iw)/2" -r 30/1 -strict -2 yt-hardcastle-scaled.mp4

We are left with uniformly 1280x720 30/1 fps videos that can be blended.

Before we demonstrate the use of the blending filter, simple blending 'generally' begins being applied right away, not quite what we want if we want to play the first video until near completion, then blend into the next video.  While you absolutely can do it in a single sequence of commands, it would require adjusting the 1st video presentation time stamp (PTS) to start immediately, adjust the 2nd video PTS to nearly the duration of video1 (play this, then this) and then apply blending at the end of video1.  Personally, that's a recipe for a migraine, so an alternative is to break the two videos into segments and stitch them back together, so that's the tactic we'll use in our example.

Since these video source files are so large, let's shrink them down a bit for our example.  Let's grab 10 sec intervals to work with as they'll be processed quicker and a bit easier to understand.

$ ffmpeg -i yt-stingray-scaled.mp4 -ss 4 -t 10 -strict -2 clip01-scaled.mp4
$ ffmpeg -i yt-hardcastle-scaled.mp4 -ss 4 -t 10 -strict -2 clip02-scaled.mp4

So, we are looking for an end effect of playing the first 8 seconds of clip01-scaled.mp4, then apply a 2-sec blending transition, then continue to play video clip02-scaled.mp4.  That's our bogey.

We can achieve this by splitting the videos into 4 segments;
  • segment1.mp4 : first 8 seconds of clip01-scaled.mp4
  • segment2.mp4 : last 2 seconds of clip01-scaled.mp4
  • segment3.mp4 : first 2 seconds of clip02-scaled.mp4
  • segment4.mp4 : last 8 seconds of clip02-scaled.mp4
With these 4 segments, we will apply the blending transition to segment2 & segment3, then restitch the blended clip between segment1 and segment4.  That's the plan.  Let's walk thru that.

$ ffmpeg -i clip01-scaled.mp4 -t 8.032000 -target ntsc-dvd -strict -2 segment1.mp4
$ ffmpeg -i clip01-scaled.mp4 -ss 8.032000 -target ntsc-dvd -strict -2 segment2.mp4
$ ffmpeg -i clip02-scaled.mp4 -ss 0 -t 2 -target ntsc-dvd -strict -2 segment3.mp4
$ ffmpeg -i clip02-scaled.mp4 -ss 2 -target ntsc-dvd -strict -2 segment4.mp4

Let's blend segment2 and segment3; at the beginning of the output file we are attempting to utilize video1 frame blend factor of 100%, video2 frame of 0%, at the end of the clip reversing to 0% video1, 100% video2.  A time-based linear progression over the duration of the video clip.

$ ffmpeg -i segment2.mp4 -i segment3.mp4 -filter_complex "[0:v]setpts=PTS-STARTPTS[v0],[1:v]setpts=PTS-STARTPTS[v1],[v0][v1]blend=all_expr='A*(1-(T/2))+B*(T/2)'" -filter_complex "amerge=inputs=2" -ac 2 -shortest -target ntsc-dvd segment2a.mp4

It's possible that the input files have a non-zero PTS start time, which is later used (as time variable T), so we're forcing both video file PTS values to start at zero.  That way, the timestamp from both videos is uniform.  The blend filter A*factor1+B*factor2, each factor in the range of [0,1].  The amerge command similarly attempts to blend both audio tracks together.

With a clip duration of 2 seconds, T/2 is a linear progression from 0 to 1 working well for our 2nd video blending factor.  Wishing a decreasing blending factor for the 1st video (1-(T/2)) works.  Pay specific attention to the video duration component (e.g. 2), it needs to align with the length of the videos, otherwise the blending factors will exceed [0,1] and give you an effect that can only compare to dropping acid.  The ''-shortest' option may, or may not, be necessary, your mileage may vary.  I've added because without it sometimes I observe a slight delay in the re-assembled video.

Reassembling Video Clips

Let's reassemble our intro, outtro and blended clips into our final video.

$ cat video.mp4.txt 
file 'segment1.mp4'
file 'segment2a.mp4'
file 'segment4.mp4'

$ ffmpeg -y -f concat -i video.mp4.txt video.mp4

We're left with our final video.




The full Makefile, for those interested in re-creating a little less manually and may ease applying to your own source files.  Notice the ffprobe commands are used to automate the extraction of video durations, the TransitionDuration=2 implies a 2-second blending duration between the two videos.

$ cat -n Makefile 
     1 TransitionDuration=2
     2 all: video.mp4
     3
     4 segment1.mp4 : clip01-scaled.mp4
     5 ${SH} ffmpeg -i $< -t $(shell echo $(shell ffprobe -loglevel quiet -show_format $< -show_entries format=duration | grep duration | cut -f 2 -d '=') -${TransitionDuration} | bc) -target ntsc-dvd -strict -2 $@
     6
     7 segment2.mp4 : clip01-scaled.mp4
     8 ${SH} ffmpeg -i $< -ss $(shell echo $(shell ffprobe -loglevel quiet -show_format $< -show_entries format=duration | grep duration | cut -f 2 -d '=') -${TransitionDuration} | bc) -target ntsc-dvd -strict -2 $@
     9
    10 segment3.mp4 : clip02-scaled.mp4
    11 ${SH} ffmpeg -i $< -ss 0 -t $(shell echo ${TransitionDuration} -1 | bc) -target ntsc-dvd -strict -2 $@
    12
    13 segment4.mp4 : clip02-scaled.mp4
    14 ${SH} ffmpeg -i $< -ss ${TransitionDuration} -target ntsc-dvd -strict -2 $@
    15
    16 segment2a.mp4 : segment2.mp4 segment3.mp4
    17 ${SH} ffmpeg -i $(shell echo $^ | cut -f 1 -d ' ') -i $(shell echo $^ | cut -f 2 -d ' ') -filter_complex "[0:v]setpts=PTS-STARTPTS[v0],[1:v]setpts=PTS-STARTPTS[v1],[v0][v1]blend=all_expr='A*(1-(T/${TransitionDuration}))+B*(T/${TransitionDuration})'" -filter_complex "amerge=inputs=2" -ac 2 -shortest -target ntsc-dvd $@
    18
    19 video.mp4: segment1.mp4 segment2a.mp4 segment4.mp4
    20 ${SH} rm $@.txt | true
    21 ${SH} for f in $^; do echo "file '$$f'" >> $@.txt; done
    22 ${SH} ffmpeg -y -f concat -i $@.txt $@
    23 ${SH} mplayer $@
    24
    25 clip01.mp4: yt-stingray.mp4
    26 ${SH} ffmpeg -i $< -ss 4 -t 10 -strict -2 $@
    27
    28 clip02.mp4: yt-hardcastle01.mp4
    29 ${SH} ffmpeg -i $< -ss 9 -t 10 -strict -2 $@
    30
    31 yt-stingray.mp4:
    32 ${SH} youtube-dl -f mp4 -o $@ https://www.youtube.com/watch?v=aXTk9VPZ4Gg
    33
    34 yt-hardcastle01.mp4:
    35 ${SH} youtube-dl -f mp4 -o $@ https://www.youtube.com/watch?v=_oHpWw7L3d4
    36
    37 %-scaled.mp4: %.mp4
    38 ${SH} ffmpeg -i $< -vf "scale=-1:720,pad=1280:ih:(ow-iw)/2" -strict -2 $@
    39
    40
    41 clean:
    42 ${RM} *.mp4


Cheers.




Saturday, March 13, 2021

How Do You Process Large Number Of Files with FFmpeg?

Photo by Graham Walker from Pexels


Suppose you have a large number of media files you wish to convert to an alternative container or format.  This post will focus on how you author a single, specifying a FFmpeg command, to be run on all the files resulted from a find command.

You could absolutely author a quick bash file, feel free to do so, but you may find this is quicker and easier;

I'd recommend you take a peek at this post XXXX as it lays out the background on utilizing Find/Exec and leads up to the final command.

This command will find all AVI containers and reformat into an MP4 container.  It shows the simplest of file conversions, it can be expanded as needed, perhaps to provide a consistent encoding scheme for all files in a subdir.

$ find . -name *.avi -exec sh -c ffmpeg -i "$0" -acodec copy "${0%.avi}.mp4" {} ;


For more details, refer to FFmpeg Official Site: http://ffmpeg.org/

 

Hope this helps.

Cheers

Making Videos Look Old Timey


 

This post recently came across my news feed and outlines a means to convert a modern video to look vintage or 'old timey'.  This post will summarize the method and offer a makefile that performs the transformation.


The general effect takes three steps: 1) downsample the frame rate, 2) color adjust to an older style, 3) grab a vcr noise overlay, scale it and apply it.

$ cat -n Makefile
     1    all: sideBySide.mp4
     2    # https://ottverse.com/create-vintage-videos-using-ffmpeg/
     3   
     4    %.ts: %
     5        ${SH} ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 $< > $@
     6   
     7    %.size: %
     8        ${SH} ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=s=x:p=0 $< > $@
     9   
    10    input.mp4:
    11        ${SH} youtube-dl -f mp4 -o $@ https://www.youtube.com/watch?v=uvy02Cv7DIc
    12   
    13    adjustFps.mp4: input.mp4
    14        ${SH} ffmpeg -i $< -filter:v fps=fps=10 -strict -2 $@
    15   
    16    vintageFilter.mp4: adjustFps.mp4
    17        ${SH} ffmpeg -i $< -vf curves=vintage -strict -2 $@
    18   
    19    vcrOverlay.mp4:
    20        ${SH} youtube-dl -f mp4 https://www.youtube.com/watch?v=J_MZb7qTenE -o $@
    21   
    22    overlay.mp4: vcrOverlay.mp4 input.mp4.ts vcrOverlay.mp4.ts input.mp4.size
    23        ${RM} list.txt
    24        ${SH} bash -c "for i in {1..$(shell echo `cat $(shell echo $^ | cut -f 2 -d ' ')` / `cat $(shell echo $^ | cut -f 3 -d ' ')` + 1 | bc)}; do printf \"file '%s'\n\" $< >> list.txt; done"
    25        ${SH} ffmpeg -f concat -i list.txt -vf scale=$(shell cat $(shell echo $^ | cut -f 4 -d ' ') | sed -e "s/x/:/g"),setsar=1:1 -an $@
    26   
    27    video.mp4: overlay.mp4 vintageFilter.mp4
    28        ${SH} ffmpeg -i $(shell echo $^ | cut -f 1 -d ' ') -i $(shell echo $^ | cut -f 2 -d ' ') \
    29                         -filter_complex "[0]format=rgba,colorchannelmixer=aa=0.25[fg]; [1][fg]overlay[out]" \
    30                         -map [out] -pix_fmt yuv420p -c:v libx264 -crf 18 $@
    31   
    32    sideBySide.mp4: input.mp4 video.mp4
    33        ${SH} ffmpeg -y -i $(shell echo $^ | cut -f 1 -d ' ') -i $(shell echo $^ | cut -f 2 -d ' ')  \
    34                         -filter_complex '[0:v]pad=iw*2:ih[int];[int][1:v]overlay=W/2:0[vid]' -map [vid] \
    35                         -map 0:a -acodec copy -shortest $@
    36   
    37    clean:
    38        ${RM} *.mp4 *.ts list.txt *.size


The end result should give you something of the form of this:



For more details, refer to FFmpeg Official Site: http://ffmpeg.org/

 

Side By Side Video Makefile

Photo by Anthony from Pexels

 

Creating a side-by-side video from two input files requires knowledge of the input file dimensions, mainly to extend the overlay to a proper size and applying the overlay.  Most often, this is manually specified in the command line, but with some constraints some of it can be automated in a makefile.


If the left-most video dimensions are larger than the right, the following makefile will place the videos side-by-side and pull the audio from the left-most file.

$ cat Makefile 

all: sideBySide.mp4


left.mp4: ../big_buck_bunny_1080p_h264.mov

${SH} ffmpeg -i $<  -filter:v "crop=1024:768:0:0" -ss 60 -t 10 $@


right.mp4: ../big_buck_bunny_1080p_h264.mov

${SH} ffmpeg -i $<  -filter:v "crop=1280:720:0:0" -t 15 $@


%.size: %

${SH} ffprobe -v error -show_entries stream=width,height $< > $@


#--generate side-by-side, assumes left image is larger (or equally large) to right image

sideBySide.mp4: left.mp4 right.mp4 left.mp4.info right.mp4.info

${SH} export left=$(shell echo $^ | cut -d ' ' -f 1); \

      export right=$(shell echo $^ | cut -d ' ' -f 2); \

      export W1=$(shell grep "^width=" left.mp4.info | cut -f 2 -d '='); \

      export H1=$(shell grep "^height=" left.mp4.info | cut -f 2 -d '='); \

      export W2=$(shell grep "^width=" right.mp4.info | cut -f 2 -d '='); \

      export H2=$(shell grep "^height=" right.mp4.info | cut -f 2 -d '='); \

      export Y=$(shell echo '\($$H1-$$H2\)/2'); \

      ffmpeg -i $$left -i $$right -filter_complex "[0:v]pad=$$W1+$$W2:0:color=gray[int];\

                     [int][1:v]overlay=$$W1:0+$$Y[vid]" -map "[vid]" -map 0:a -acodec copy $@


%.info: %

${SH} ffprobe -v error -show_format -show_streams $< > $@


clean:

${RM} *.mp4 *.info


For more details, refer to FFmpeg Official Site: http://ffmpeg.org/

Monday, November 2, 2020

FFmpeg Commands -- Blog Content Summary

Over the past couple years this blog contains a variety of content that interests me.  While not intended, a considerable amount of this blog revolves around using FFmpeg.  I feel drawn to this particular utility for a number of reasons I guess:

  • it is primarily a command-line interface; offering a great deal of flexibility
  • while powerful and popular, I find it lacks sufficient examples and documentation in demonstrating its use, a significant learning curve for the new user
  • I have a history of interest in image processing and computer vision which aligns with, or compliments, these interests
  • I have an interest in polishing and refining personal media content and this tool hits all the buttons to do so
I thought I'd touch on some of the areas of content, specifically to see what areas I've covered and plan areas that may be worth focusing on.  I have a starting-point for a more comprehensive tutorial on FFmpeg that I am preparing for a future presentation, perhaps YouTube or a Meet-Up.  Stay tuned.

Setup/Configuration/Installation

My daily drivers tend to be Linux-based workstations and laptops, so my setup/configuration instructions tend to follow:

Convert Images to Video

Nearly everyone on planet Earth now carries around a high-definition camera in their pocket.  We snap hundreds, if not thousands, of photos over the course of the year.  It's pretty common to want to take a series of images and transform it into a slide-show to amazing and impress your friends and family.  Here are but a few resources to do just that.

Clipping Time Segments from Video

First order video processing is trimming out uninteresting time segments.  Take a raw video, clip out interesting bits and concatenate them together will set you on course for a better video presentation.

Concatenating Videos

Cropping Video

Whether its clipping out a noisy background or simply directing the viewers attention, often you want to crop out the point-of-interest from a wide-angle shot.

Padding/Extending Video

Sometimes, just the opposite is needed, taking a video of arbitrary size and making it bigger to accommodate an additional visual (e.g. overlay, video, graph,...)

Zooming In/Out of Video

A static shot can still draw the viewers attention by zooming in/out to draw the viewers focus to a narrower or broader aspect.

Applying Overlay to Video

Like frosting on a cake, adding overlays upon a video makes a good product even better (image, text or subvideo).

Fading In/Out of Video

We've all seen it, a scene transition from one location or another, typically demonstrated by fading out of the first scene (e.g. fade-to-black) and fading into the next (e.g fade-from-black).

Video Blending

Sometimes we wish the resultant video to be comprised of multiple semi-transparent sources, sometimes referred to as cross-fading.  Dynamically applying a weighting factor from multiple sources can give you some pretty dramatic effects.

Video Scene Transitions

I've authored a series of posts which provide a variety of scene transitions, introducing a new cut scene by moving in the destination scene in some manner (e.g. wipes, curtain-call,...)

Video Blurring

Whether it be a license plate, a disruptive background, or subjects who rather not be in your video, there is often a need for applying a blur to a video or video areas.

Video Stablilization

Shaky hand?  No tripod?  This video filter can remove the side-effects of a shaky videographer.

Creating Cartoon from Video

Can you take a real video and convert it into something more cartoon-like?  We spent some time doing just that.
ZeroMq (e.g. 0mq, zmq) is integrated into FFmpeg and can be used to dynamically modify video filters on the fly.
I particularly find that make and video processing make a good pairing, especially when the video processing utility is command-line based.  These two utilities play very well together.

Monday, September 14, 2020

Atypical Uses for Makefiles


Makefiles traditionally center themselves in a build process.  While they have taken a backseat to more modern build utilities, oftentimes they are simply that....in the backseat.  Eclipse, for example, autogenerated makefiles as part of a project build.  Why?  Because when properly done, makefiles are extraordinarily powerful.  Its dependency engine can parse large projects and selectively execute only what needs to be rebuilt.  Done poorly, you're at the mercy of 'make clean; make all' sequences to make sure everything is built up-to-date.

Aside from it's power as a build utility, make can be useful for other purposes as well.  Over the past couple years I've extended my use of make into a few additional areas where I find the dependency engine to prove to be very useful.  This post will touch on a few.


Quick Introduction to Makefiles

Whether you are familiar with makefiles or not, consider taking a peek at this past post.  While it touches on the general syntax and utility of make, in the later sections it shows some atypical uses.  As a means to demonstrate the dependency engine, it shows how Imagemagick can be used to transform images of varying file types.  A daisy-chained dependency chain can be created by converting an image through a series of steps JPG => PNG => GIF => JP2 => XWD.  Perhaps unpractical, but it shows an atypical use of make and it provides a simple and visible execution of the dependency engine.

Poor Man's Parallelism

I'm particularly proud of this usage, to my knowledge no one else has ever recommended this as a use of make.  Most, familiar with make, know that it can be multi-threaded and the dependency engine fires off the specified number of threads to speed up the process.  Given a large text file, make can provide an easy means to parallelize the process.  The trick; split the file into segments, process each segment, then join the results.  In ~30 lines of code, you can greatly improve the execution time for simple parallel processing, all enabled by the sophisticated dependency engine at the core of make.  Take a peek at the details here.

FFmpeg

While I've toyed to a good degree with bash scripts, python scripts and a variety of other shell utilities I've come home to make as the best tool for FFmpeg-based tasks.  Video conversions and/or modifications lend themselves nicely to makefiles.  Suppose you have a large directory full of images and you wish to create a slideshow....this post is a good starting point to showing you how makefiles can make that an easy process.  Want to take 6 hours of raw camera footage and transform it into something worth watchable....this post shows how that can be done.  Want to download a Youtube video and apply a series of transformations to create some completely new content.....this post can give you a head start.

Some of the beauties that were created by these posts;


Shell Script Replacements

Makefiles have become my adhoc replacements for simple shell scripts.  Certainly, they are limited to execute simplistic series of commands and are no substitute for sophisticated needs, but chances are if you have to execute a series of shell commands in a pre-defined order a makefile will scratch that itch.  Sophisticated recipes can become clumsy, and I've found myself authoring some ugly, ugly, ugly recipes, but recently I found a workaround.  Suppose you have a series of 5 tightly-bound commands and find it difficult to define as a recipe, a trick I've found useful is creating a shell script by means of a recipe, then using it in another target.

all: somethingCool.sh
        ${SH} ./$<

somethingCool.sh:
        ${SH} echo "echo 'doing something cool'" > $@
        ${SH} echo "sleep 1" >> $@
        ${SH} chmod a+x $@

clean:
        ${RM} somethingCool.sh

Notice the helper shell script is created by make as a dependency of the all target, used as needed and can be safely removed, recreated when later needed.  Certainly, this falls apart if the creation of the shell script by make is unnecessarily complex, but in many of my projects the only file under version control is the makefile, the rest are created as part of the project and removed by the clean target.  Nice and clean.  Rapid prototyping an alternative script can be done by a new target and once you get the hang of it can prove to be quite powerful.  The make, make run, make clean becomes the holy trinity of rapid prototyping.

I've found adhoc usage reports, grabbing debug logs, grepping for significant events, sorting and tallying event counts really align with raw, events, report targets and recipes.

Wednesday, September 9, 2020

Cartoonize Video with FFmpeg -- Take 2

 Some time back, I posted a rough attempt to convert a video into a cartoon-style equivalent; here.  That technique converted the video into a series of still images and ran each through ImageMagick sketch filter.  The result looked something like this:




Since then, I play with this time and time again, revising it to something like this:



Again, recently I thought of another technique.  Often, cartoon coloring schemes look 'flattened', meaning red'ish elements are colored red, blue'ish colored blue, so on and so forth.  The 'gex' filter can perform such a conversion pixel-by-pixel and seems promising.  It would require applying a series of filters, for each color so I thought I'd give it a try on simple grayscale first...if it showed promise then expand on a colorized equivalent.  Here is what I got, seems promising:

The technique, in a nutshell, is to convert the video to grayscale, then perform a selective filter on each pixel.  Pixels within the [0,50] luminosity range get assigned 0, pixels within the (50,100) range get assigned 50;
[0,50] = 0
(50,100] = 50
(100,150] = 100
(150,200] = 150
(200,255] = 200

The filter can be a mouthful, taking the form:
$ ffmpeg -i input.mp4 -filter_complex "format=gray,geq=lum_expr='if(lte(lum(X,Y),50),0,if(lte(lum(X,Y),100),50,if(lte(lum(X,Y),150),100,if(lte(lum(X,Y),200),150,if(lte(lum(X,Y),255),200,0)))))'" -acodec copy cartoonX.mp4

Notice, it's a chain of less-than-equal-to conditions of the form if(condition,X,Y) which means if condition set pixel to X, otherwise Y.  To execute the series of else conditions, a nesting of alternative conditions are replaced for Y.  Ugly, but effective and given it doesn't require converting a video to frames and back again a ton faster; still slow'ish, but way faster than the alternative.

Seems to me that this shows a good deal of promise.  The result is dark'ish, but that could be addressed by migrating the pixel assignments to the ceiling of the range, or perhaps applying lightening filter pre-filtering could also address it.  Seems plausible for converting a video to a grayscale cartoon'ish equivalent, using the same technique for color seems like a good next step.




Monday, August 17, 2020

Flaunt Your Font Want

 

Last night, my wife and I rewatched Jurassic Park, later that night I drifted into a blissful slumber.  My unconscious mind wandered vastly into the future where a futuristic archaeologist chipping away an amber-encased USB device, having been perfectly preserved for 3000 years.  The contents, all my life's work; software projects, audio, video and including a copy of this blog post.  The archaeologist releasing the USB device from it's amber container goes deep into the cellar of cellars to retrieve a primitive device capable of reading this archaic media.  Amazingly, all my life's work comes to life once again, a group of interested historians eagerly scan the work only to soon show disappointment.  "Arial?  A dull, Arial font?  Clearly this work is of no substance." as they close the computer, remove the USB device and promptly toss it into the nearest waste basket.  A record of my life for the 'after people' only to be discarded like common trash do to lack of creativity in the font game.  No, no I say, we'll step up our font game now and forever for myself, for you as well as for the after people.

I've never really had much of a need for non-system fonts.  Most user interfaces I've developed were for engineers who cared little for cosmetics, have yet to ever work with a Ux designer nor a graphics designer in any amount of detail.  The default system fonts as a result have always been 'good enough' as a result.  In fact, font files have always been a mystery to me, never spending any time thinking about them at all.

But not today, today we're gonna spend a little time understanding what they are and if you're like me you may even be surprised.  After decades of not caring about fonts, why today?  Why now?  Whelp, as I've been creating images and video content it becomes valuable to provide some form of text overlay.  I've always used the system fonts, but suppose you are tweaking a military-style video you'd likely want to add a military-style font.  That opens the door to understanding where to find and install custom fonts to suit all your creative needs.

 
vs.

So the remainder of this post will touch on how to download and use custom fonts in images, but they can easily be used in video, slideshows,....

For years, font files were simply 'magic', but let's look at one briefly.  Smarter folks may already know this, but a font ttf file can be viewed as an image; behold, FreeeSerif.ttf

$ display /usr/share/fonts/truetype/freefont/FreeSerif.ttf


The image shows the alphabet, numerics and special characters.  It then demonstrates a variety of the fonts at differing font sizes.  This can give you a feeling as to whether the font strums your creative chord.

So, where does our search begin when seeking the elusive font?  Here is a good start, and the source we'll be using in the rest of the post: www.1001freefonts.com

Assuming that we will download, extract and install the fonts to a local directory, the process takes the form:

$ wget -P TEMP https://www.1001freefonts.com/d/4018/eraser-dust.zip

$ cd TEMP; 

$ unzip -o eraser-dust.zip; 

$ mv EraserDust.ttf ../EraserDust.ttf

$ rm -rf TEMP;

The result, a brand new shiny EraserDust.ttf font file just waiting for us to use.


Using ImageMagick, we can create an image using the new font with some meaningful text:

$ convert -background None -fill white -font ./EraserDust.ttf -pointsize 96 label:"EraserDust.ttf"  -rotate -15 foo.png

$ display foo.png


Now, you can make your own thumbnails, augment video, update documents with your newly acquired custom font.  Imagine how much more fun your engineering documentation can be using a whimsical font style :thumbsup


With our new found powers, let's download and install a series of fonts, use Imagemagick to create an image showcasing the new font, then slap them all into a video displaying each font for 1 second.  I find a makefile suites this purpose pretty well, the chained commands for the video mp4 target is a bit clumsy as a series of shell commands, but it's good enough to convey the steps.

$ cat -n Makefile 
     1 all: background.png video.mp4
     2
     3 video.mp4: FridayStroke.otf TopSecret.ttf BostonTraffic.ttf Camouflage.ttf DrippingMarker.ttf EraserDust.ttf SnackerComic.ttf
     4 ${SH} i=0; for f in `echo $^`; do \
     5 echo $$i; \
     6 echo $$f; convert -background None -fill white -font ./$$f -pointsize 96 label:"$$f"  -rotate -15 overlay.png; \
     7 composite -gravity center overlay.png background.png frame-$$i.png ; \
     8 i=$$((i+1)); \
     9 done
    10 ${SH} ffmpeg -r 1 -i frame-%d.png $@
    11
    12 background.webp:
    13 ${SH} wget -O $@ https://i.vimeocdn.com/video/521265093.webp?mw=1000
    14
    15 background.png: background.webp
    16 ${SH} ffmpeg -i $< -vframes 1 $@
    17
    18 SnackerComic.ttf:
    19 ${SH} wget https://www.1001freefonts.com/d/4631/snacker-comic.zip -P TEMP
    20 ${SH} cd TEMP; unzip -o *.zip; mv *.ttf ../$@
    21 ${RM} -rf TEMP
    22
    23 FridayStroke.otf:
    24 ${SH} wget https://www.1001freefonts.com/d/26677/the-friday-stroke.zip -P TEMP
    25 ${SH} cd TEMP; unzip -o the-friday-stroke.zip; mv The\ Friday\ Stroke\ Font\ by\ 7NTypes.otf ../$@
    26 ${RM} -rf TEMP
    27
    28 TopSecret.ttf:
    29 ${SH} wget https://www.1001freefonts.com/d/7052/top-secret.zip -P TEMP
    30 ${SH} cd TEMP; unzip -o top-secret.zip; mv Top\ Secret.ttf ../$@
    31 ${RM} -rf TEMP
    32
    33 BostonTraffic.ttf:
    34 ${SH} wget https://www.1001freefonts.com/d/3269/boston-traffic.zip -P TEMP
    35 ${SH} cd TEMP; unzip -o boston-traffic.zip; mv boston.ttf ../$@
    36 ${RM} -rf TEMP
    37
    38 Camouflage.ttf:
    39 ${SH} wget https://www.1001freefonts.com/d/12584/camouflage.zip -P TEMP
    40 ${SH} cd TEMP; unzip -o camouflage.zip; mv CamouflageW.ttf ../$@
    41 ${RM} -rf TEMP
    42
    43 DrippingMarker.ttf:
    44 ${SH} wget https://www.1001freefonts.com/d/14693/a-dripping-marker.zip -P TEMP
    45 ${SH} cd TEMP; unzip -o a-dripping-marker.zip; mv adrip1.ttf ../$@
    46 ${RM} -rf TEMP
    47
    48 EraserDust.ttf:
    49 ${SH} wget -P TEMP https://www.1001freefonts.com/d/4018/eraser-dust.zip
    50 ${SH} cd TEMP; unzip -o eraser-dust.zip; mv EraserDust.ttf ../$@
    51 ${RM} -rf TEMP
    52
    53 clean:
    54 ${RM} *.otf *.ttf *.png *.mp4 *.webp
    55

Lines 18-51 has a series of font targets, each downloading the font, extracting it in a disposable directory, moving the TTF file, then cleaning up.  

Lines 3-10 do the meat of the work, first defining each font file as a dependency (the make engine satisfying the dependency by executing the necessary recipe), then create a transparent overlay with the font name, slapping that over a background image and repeating for font.  
Finally, line 10 converts the images into a video slide show, 1 second per slide.



Now your amber-encased creative works will have some flair.