# Using CLI tools to manage audio, video, images and PDF files

I'm going to cut straight to the chase here, as the main reason for this post isn't to compare and contrast available tools and so on, but rather to highlight a few useful commands that often come in handy for various situations. This is more like a quick reference for some specific tasks. The focus here is on the following popular tools, which I use quite often:

*   [FFmpeg](https://www.ffmpeg.org/): A complete, cross-platform solution to record, convert and stream audio and video
    
*   [Gifsicle](https://www.lcdf.org/gifsicle/): for creating, editing, and getting information about GIF images and animations
    
*   [ImageMagick](https://imagemagick.org/): create, edit, compose, or convert digital images
    
*   [PDFtk](https://www.pdflabs.com/tools/pdftk-the-pdf-toolkit/): for doing everyday things with PDF documents.
    
*   [OCRmyPDF](https://github.com/ocrmypdf/OCRmyPDF): adds an [OCR](https://en.wikipedia.org/wiki/Optical_character_recognition) text layer to scanned PDF files, allowing them to be searched
    
*   [ghostscript](https://www.ghostscript.com/): an interpreter for PostScript® and Portable Document Format (PDF) files.
    
*   [mozjpeg](https://github.com/mozilla/mozjpeg): improves JPEG compression efficiency achieving higher visual quality and smaller file sizes at the same time
    
*   [pngquant](https://pngquant.org/): a utility and library for lossy compression of PNG images. The conversion reduces file sizes significantly (often as much as 70%) and preserves **full alpha transparency**.
    
*   [svgo](https://github.com/svg/svgo): a Node.js-based tool for optimizing SVG vector graphics files.
    

This is a work in progress. I'll be updating the commands from time to time, as I encounter them.

## FFmpeg

### convert video from x to y

Generally speaking, you should be able to convert a video from one format to another by using the following convention:

```bash
# x and y here are the file extensions, for instance; mp4, mkv, ogv
ffmpeg -i input.x -c copy output.y
```

If you encounter any issues, a quick Google search for **How to convert x to y using FFmpeg** will give you the solution.

**Reference(s)**

*   [webm to mp4 conversion using ffmpeg](https://stackoverflow.com/questions/18123376/webm-to-mp4-conversion-using-ffmpeg)
    

### compress video (reduce video size, without a *noticeable* reduction in quality)

Here, I'm using mp4 videos. If you have other video types (mkv, ogv, webm, etc. then you might need to convert those to mp4 first

```bash
# using the libx265 codec
ffmpeg -i input.mp4 -vcodec libx265 -crf 28 output.mp4

# if you don't have libx265, you can use the libx264 codec
ffmpeg -i input.mp4 -vcodec libx264 -crf 28 output.mp4
```

> For more info & technical details, see this **Unix & Linux Stack Exchange** post: [How can I reduce a video's size with ffmpeg?](https://unix.stackexchange.com/questions/28803/how-can-i-reduce-a-videos-size-with-ffmpeg)

### speed up a video

I find this useful in situations where I record a screen capture (no audio) while doing a demo, and I end up with, say, a 5-minute video that has several periods of no activity (e.g. waiting for a process to complete). I could speed up the video and hence reduce its overall time to, say 2.5 minutes:

```bash
# the 0.5 here means we're reducing the video to half the original time.
# adjust this to suit your preference. If the value is above 1.0, then you're making your video slower
ffmpeg -i input.mp4 -filter:v "setpts=0.5*PTS" output.mp4
```

If you wanna speed up video and audio at the same time:

```bash
# note here that the audio tempo is the inverse of the factor modifying frame timestamps. In English,
# if you're speeding up the video 2x, divide 1 by 2 to get 0.5 for the video, then
# use 2.0 as the factor for the audio tempo (this is 1 divided by video factor)
ffmpeg -i input.mp4 -filter_complex "[0:v]setpts=0.5*PTS[v];[0:a]atempo=2.0[a]" -map "[v]" -map "[a]" output.mp4
```

For more info and technical details, see

*   [How to speed up / slow down a video](https://trac.ffmpeg.org/wiki/How%20to%20speed%20up%20/%20slow%20down%20a%20video) on the FFmpeg Bug Tracker and Wiki.
    
*   [Speed up video x1.5 but keep all frames](https://superuser.com/questions/1247462/speed-up-video-x1-5-but-keep-all-frames) on superuser.com.
    

### concatenate video / audio

The assumption here is that you want to concatenate files with the same codecs. If this isn't the case, see [this page](https://trac.ffmpeg.org/wiki/Concatenate) on the FFmpeg Bug Tracker and Wiki.

What you wanna do is create a text file with all the files (whose paths can be either relative or absolute) you want to have concatenated in the following form:

```plaintext
file '/path/to/file1.mp3'
file '/path/to/file2.mp3'
file '/path/to/file3.mp3'
```

It is possible to generate this file with a bash for loop, or using `printf`. **Either** of the following would generate a file containing every \*.mp3 in the working directory:

```bash
# with a bash for loop
for f in *.mp3; do echo "file '$f'" >> text_file.txt; done

# or with printf
printf "file '%s'\n" *.mp3 > mylist.txt
```

Then, using FFmpeg:

```bash
# The -safe 0 is not required if the paths are relative
ffmpeg -f concat -safe 0 -i text_file.txt -c copy output.mp3
```

For more info and technical details, see

*   [Concatenating media files](https://trac.ffmpeg.org/wiki/Concatenate) on Stackoverflow
    
*   [How to concatenate two MP4 files using FFmpeg?](https://stackoverflow.com/questions/7333232/how-to-concatenate-two-mp4-files-using-ffmpeg) on the FFmpeg Bug Tracker and Wiki
    

### extract audio from video

If we don't wanna re-encode, then we first have to run `ffprobe` on the video file to list available audio streams and their types:

```console
❯ ffprobe video.mp4
ffprobe version 5.1.2 Copyright (c) 2007-2022 the FFmpeg developers
  built with gcc 12 (SUSE Linux)
  configuration: --prefix=/usr --libdir=/usr/lib64 --shlibdir=/usr/lib64 --incdir=/usr/include/ffmpeg --extra-cflags='-O2 -Wall -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3 -fstack-protector-strong -funwind-tables -fasynchronous-unwind-tables -fstack-clash-protection -Werror=return-type -flto=auto -ffat-lto-objects -g' --optflags='-O2 -Wall -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3 -fstack-protector-strong -funwind-tables -fasynchronous-unwind-tables -fstack-clash-protection -Werror=return-type -flto=auto -ffat-lto-objects -g' --disable-htmlpages --enable-pic --disable-stripping --enable-shared --disable-static --enable-gpl --enable-version3 --disable-openssl --enable-gnutls --enable-ladspa --enable-libshaderc --enable-vulkan --enable-libaom --enable-libass --enable-libbluray --enable-libbs2b --enable-libcdio --enable-libcodec2 --enable-libdav1d --enable-libdc1394 --enable-libdrm --enable-libfontconfig --enable-libfreetype --enable-libfribidi --enable-libgsm --enable-libjack --enable-libjxl --enable-librist --enable-libmp3lame --enable-libmysofa --enable-libopenjpeg --enable-libopenmpt --enable-libopenh264-dlopen --enable-libopus --enable-libpulse --enable-librav1e --enable-librubberband --enable-libsvtav1 --enable-libsoxr --enable-libspeex --enable-libssh --enable-libsrt --enable-libtheora --enable-libtwolame --enable-libvidstab --enable-libvmaf --enable-libvorbis --enable-libvpx --enable-libwebp --enable-libxml2 --enable-libzimg --enable-libzmq --enable-libzvbi --enable-lto --enable-lv2 --enable-libmfx --enable-vaapi --enable-vdpau --enable-version3 --enable-libfdk-aac-dlopen --enable-nonfree --enable-libvo-amrwbenc --enable-libx264 --enable-libx265 --enable-libxvid
  libavutil      57. 28.100 / 57. 28.100
  libavcodec     59. 37.100 / 59. 37.100
  libavformat    59. 27.100 / 59. 27.100
  libavdevice    59.  7.100 / 59.  7.100
  libavfilter     8. 44.100 /  8. 44.100
  libswscale      6.  7.100 /  6.  7.100
  libswresample   4.  7.100 /  4.  7.100
  libpostproc    56.  6.100 / 56.  6.100
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'video.mp4':
  Metadata:
    major_brand     : isom
    minor_version   : 512
    compatible_brands: isomiso2avc1mp41
    encoder         : Lavf58.29.100
  Duration: 00:02:32.96, start: 0.000000, bitrate: 452 kb/s
  Stream #0:0[0x1](und): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 126 kb/s (default)
    Metadata:
      handler_name    : SoundHandler
      vendor_id       : [0][0][0][0]
  Stream #0:1[0x2](und): Video: h264 (High) (avc1 / 0x31637661), yuv420p(progressive), 1280x720, 317 kb/s, 30 fps, 30 tbr, 15360 tbn (default)
    Metadata:
      handler_name    : VideoHandler
      vendor_id       : [0][0][0][0]
```

From above, we see that the audio stream format is [aac](https://en.wikipedia.org/wiki/Advanced_Audio_Coding). Then we can go ahead and

```bash
ffmpeg -i video.mp4 -map 0:a -acodec copy audio.aac
```

However, if we want to extract the audio in a specific format, say mp3, then we'll have to re-encode:

```bash
# here we're using libmp3lame <https://www.ffmpeg.org/ffmpeg-codecs.html#libmp3lame-1> for mp3 encoding
ffmpeg -i video.mp4 -map 0:a -acodec libmp3lame audio.mp3
```

### compress audio

You have an hour-long 320kbps mp3 file weighing in at 144Mb. Let's say you want to reduce the target [bitrate](https://en.wikipedia.org/wiki/Bit_rate#MP3) to 96kbps:

```bash
# -q:a 7 gives you an average bitrate of 100kbps
# for reference, see <https://trac.ffmpeg.org/wiki/Encode/MP3>
ffmpeg -i input.mp3 -codec:a libmp3lame -q:a 7 output.mp3
```

That should give you a resulting file ~45Mb! You'll have to play the file to determine if you are happy with the quality. Note that audio quality improves with increasing bitrate. Here's a quick guide (taken from [wikipedia.org/wiki/Bit\_rate#MP3](https://en.wikipedia.org/wiki/Bit_rate#MP3)):

*   32 kbps – generally acceptable only for speech
    
*   96kbps – generally used for speech or low-quality streaming
    
*   128 or 160 kbps – mid-range bitrate quality
    
*   192 kbps – medium-quality bitrate
    
*   256 kbps – a commonly used high-quality bitrate
    
*   320 kbps – highest level supported by the [MP3](https://en.wikipedia.org/wiki/MP3) standard
    

**Note(s)**

*   For additional compression, you could mix down to [mono](https://en.wikipedia.org/wiki/Monaural) (use 1 audio channel) and set the [sample rate](https://manual.audacityteam.org/man/sample_rates.html) to 22050 Hz (which is half the standard 44.1kHz) by using `-ac 1` for the former and `-ar 22050` for the latter.
    
*   Refer to the [FFmpeg MP3 Encoding Guide](https://trac.ffmpeg.org/wiki/Encode/MP3) for more info and technical details
    
*   Also, check out [Manipulating audio channels](https://trac.ffmpeg.org/wiki/AudioChannelManipulation#a2stereostereo) on the FFmpeg Bug Tracker and Wiki
    

### extract part of a media file

Say you have a 45min audio file and you only need the part between 2min and 44min:

```bash
# time format is HH:MM:SS or HH:MM:SS.xx, where xx expresses decimal value for SS
# see <https://ffmpeg.org/ffmpeg-utils.html#Time-duration>
ffmpeg -i input.mp3 -ss 00:02:00 -to 00:44:00.00 [other options here] output.mp3
```

## Gifsicle

### compress a GIF file

```bash
gifsicle -O3 --colors 256 --lossy=30 -o output.gif input.gif
```

**Reference**: [Optimize animated GIF size in command-line](https://superuser.com/questions/1107200/optimize-animated-gif-size-in-command-line) on superuser.com

## Imagemagick

### resize images

Using image width as a reference

```bash
# we want to resize the image, keeping the width at 1280px
convert -resize 1280x input.jpg output.jpg
```

Using image height as a reference

```bash
# we want to resize the image, keeping the height at 1280px
convert -resize x1280 input.jpg output.jpg
```

### convert X to Y

Convert, say, a JPG file to PNG

```bash
convert image.png image.jpg
```

Convert PNG with transparency to JPG

```bash
# -flatten, by default, results in a white background
# see <http://www.imagemagick.org/script/command-line-options.php#flatten>
convert image.png -flatten image.jpg
```

Converting all pixels of a given color to transparent

```bash
# we want to make white transparent
convert image.jpg -fuzz 5% -transparent white image.png
```

**Some References**

*   [Fred's ImageMagick Tidbits](http://www.fmwconcepts.com/imagemagick/tidbits/image.php#alpha)
    
*   [Convert PNG with transparency to JPG (Stackoverflow)](https://stackoverflow.com/questions/47954470/convert-png-with-transparency-to-jpg)
    
*   \[ImageMagick Snippets by Mohamed A. Hassan\](https://gist.github.com/MohamedAlaa/d9b54cd856b3edce1510\]
    

## mozjpeg

### compress JPEG images

Batch compress a bunch of JPG files in the current directory

```bash
for img in *.jpg; do mozjpeg -outfile /path/to/output/directory/$img $img; done
```

## pngquant

### compress PNG images

```bash
# this will compress image.png file and save it as a new file with name "image-fs8.png"
pngquant image.png

# change output filename suffix. This will result in a new file image.min.png
pngquant image.png --ext .min.png

# save converted files in different location, instead of current directory
pngquant image.png --output path/to/output/directory/image.png

# strip Image metadata
pngquant --strip image.png

# skip saving files if the size of compressed files are larger than original files
pngquant --skip-if-larger image.png

# specify min/max quality
# set image quality in range 0 (worst) to 100 (perfect)
# here we set the minimum image quality as 60 and maximum quality as 80
pngquant --quality=60-80 image.png
```

## PDFtk

### concatenate various PDF files

```bash
# this concatenates all pdf files in current directory, saving them as filename.pdf
pdftk *.pdf cat output filename.pdf

# concatenate specific PDF files
pdftk file1.pdf file2 cat output filename.pdf

# if you want to see what's happening,
# add the verbose option at the end of your command. This is true for any pdftk operation
pdftk *.pdf cat output filename.pdf verbose
```

### rotate PDF

```bash
# rotate page 1 by 90 degrees clockwise
pdftk input.pdf cat 1east output output.pdf

# rotate page 1 by 90 degrees anti-clockwise
pdftk input.pdf cat 1west output output.pdf

# rotate page 1 by 180 degrees
pdftk input.pdf cat 1south output output.pdf

# rotate all pages 90 degrees clockwise
pdftk input.pdf cat 1-endeast output output.pdf

# rotate all pages 90 degrees anti-clockwise
pdftk input.pdf cat 1-endwest output output.pdf

# rotate all pages 180 degrees
pdftk input.pdf cat 1-endsouth output output.pdf
```

### encrypt PDF

```bash
# No. 1: Encrypt a PDF using 128-bit strength (the default), withhold all permissions (the default)
pdftk file1.pdf output file1.128.pdf owner_pw your_password

# No. 2: Same as above, except printing is allowed
pdftk file1.pdf output file1.128.pdf owner_pw foo owner_pw your_password allow printing

No. 3: Same as No. 1, except password baz must also be used to open output PDF
pdftk file1.pdf output file1.128.pdf owner_pw foo user_pw baz
```

### extract pages from PDF

```bash
# extract page 1, 4 and 5 from input.pdf, save them as one document, output.pdf
pdftk input.pdf cat 1 4 5 output output.pdf verbose

# same as above, but as separate files
for pages in {1,4,5};do pdftk input.pdf cat $pages output extracted-$pages.pdf verbose;done

# you can also extract a range, like page 3 to 15, or page 19 to the end of the document, or both:
pdftk input.pdf cat 3-15 output output.pdf verbose
pdftk input.pdf cat 19-end output output.pdf verbose
pdftk input.pdf cat 3-15 19-end output output.pdf verbose

# in one operation, you can extract pages from separate documents and combine them as one
# here, we have two files, file1.pdf and file2.pdf, and we're
# extracting pages 1-3 from file1.pdf, and extracting pages 4 to the end from file2.pdf
pdftk A=file1.pdf B=file2.pdf cat A1-3 B4-end output newfile.pdf verbose
```

### "stamp" a PDF file with another PDF file

There are many practical use cases for this. For example, you want to add a watermark to a PDF, or you want to add a signature to a PDF.

```bash
# this usually works best if both files are of the same size
pdftk page_to_be_stamped.pdf stamp stamp.pdf output stamped.pdf

# same as above, but for a multi-page document
# see <https://www.pdflabs.com/docs/pdftk-man-page/#dest-op-multistamp>
pdftk document_to_be_stamped.pdf multistamp stamp.pdf output stamped.pdf
```

## Ghostscript

### compress pdf

Ghostscript is super powerful, and to be honest, the first time I encountered it I felt intimidated by the myriad of options available. Fortunately, [Alfred Klomp](https://github.com/aklomp) has written an excellent wrapper around Ghostscript to reduce a PDF's file size. It comes as a BASH script called [shrinkpdf](https://github.com/aklomp/shrinkpdf), and has proved to be very handy for me over the years. I have even included it as part of my [BASH setup script for Ubuntu servers](https://github.com/engineervix/ubuntu-server-setup)

I normally add the script in a `bin` directory inside my `$HOME` directory, and ensure that

1.  the script is executable (`chmod +x ~/bin/shrinkpdf`)
    
2.  `~/bin/` is on my `$PATH` (`export PATH="$PATH:$HOME/bin"`)
    

Then use it as follows:

```bash
shrinkpdf -o output.pdf input.pdf

# And an output resolution in DPI (default is 72 DPI) with the -r option
shrinkpdf -r 150 -o output.pdf input.pdf
```

## OCRmyPDF

Add an OCR layer and convert to [PDF/A](https://en.wikipedia.org/wiki/PDF/A)

```bash
ocrmypdf input.pdf output.pdf
```

Add an OCR layer and output a standard PDF

```bash
ocrmypdf --output-type pdf input.pdf output.pdf
```

Correct page rotation

```bash
ocrmypdf --rotate-pages input.pdf output.pdf
```

Produce PDF and text file containing OCR text

```bash
ocrmypdf --sidecar output.txt input.pdf output.pdf
```

OCRmyPDF can also convert single images to PDFs on its own

```bash
# If the resolution (dots per inch, DPI) of an image is not set or is incorrect,
# it can be overridden with --image-dpi, e.g. --image-dpi 300
ocrmypdf image.png myfile.pdf
```

If you have multiple images, use [img2pdf](https://gitlab.mister-muffin.de/josch/img2pdf) to convert the images to PDF. Here's an example, where you convert your images to PDFs, and then pipe the results to run `ocrmypdf`. The `-` tells OCRmyPDF to read standard input:

```bash
img2pdf my-images*.jpg | ocrmypdf - myfile.pdf
```

## SVGO

Compress a single SVG file

```bash
# you can skip the optional -i argument
svgo -i input.svg -o output.svg
```

Compress multiple SVG files in a directory

```bash
svgo -f path/to/dir/with/svg/files -o path/to/dir/with/svg/output
```

* * *

It's worth mentioning that some of these commands are too verbose, and it's not easy to remember all the arguments and options for some of them. In such cases, I often find it useful to have [BASH functions](https://linuxize.com/post/how-to-create-bash-aliases/#creating-bash-aliases-with-arguments-bash-functions) for some of these. For instance, I have one for encrypting PDFs:

```bash
# encrypt pdf, allow printing
encrypt_pdf() {
  encrypted_pdf="${1%.pdf}.128.pdf"
  pdftk "$1" output ${encrypted_pdf} owner_pw "$2" allow printing verbose

  # rename the files after encryption
  mv -v "$1" "${1%.pdf}_src.pdf"
  mv -v ${encrypted_pdf} "${encrypted_pdf%.128.pdf}.pdf"
}
```

I call it like this:

```bash
encrypt_pdf filename.pdf $(openssl rand -base64 12)
```

Hope you found this post useful! If you have any cool tricks and time-saving techniques, please share them in the comments!

* * *
