Multimedia File Management

Multimedia File Management

Bitnesia • Aug 30, 2026 93 •ID

Chapter 13 equipped us with four daily automation patterns: bulk renaming, backups, file organization, and permanent aliases/functions. This chapter continues in the same direction with a more specific focus on the file types that most frequently clutter the User folder, namely photos and videos. Media collected from smartphone cameras or screen recording footage quickly accumulates in inconsistent formats and sizes, and organizing them one by one through a GUI application is clearly impractical for hundreds of files at once. This chapter addresses three core needs: batch converting and resizing images/videos using ffmpeg and ImageMagick, compressing/extracting archives using tar, zip, and gzip, concluding with a case study of an organizer script that combines everything to tidy up media collections automatically.

14.1 Batch Conversion of Images and Videos

This section covers how to convert and resize multiple image or video files simultaneously using the two most common external tools for this purpose: ffmpeg for video/audio, and ImageMagick for images.

14.1.1 Installing ffmpeg and ImageMagick

ffmpeg and ImageMagick not part of Bash or system-default coreutils, so both must be installed separately via a package manager before they can be used in scripts. In accordance with the distribution context established in Chapter 1.3, installation methods vary depending on the system used.

# Debian/Ubuntu
sudo apt update && sudo apt install ffmpeg imagemagick

# Fedora
sudo dnf install ffmpeg imagemagick

# Arch Linux
sudo pacman -S ffmpeg imagemagick

# macOS (via Homebrew, Chapter 1.3.2)
brew install ffmpeg imagemagick

Verify the installation using the version option of each tool before proceeding to the next practical steps.

ffmpeg -version
magick -version   # ImageMagick 7
convert -version  # ImageMagick 6

The difference between magick and convert above is not a typo. ImageMagick version 6 provides several separate commands such as convert, mogrify, and identify, whereas ImageMagick version 7 consolidates everything under a single umbrella command magick. The convert command in ImageMagick 7 still functions for backward compatibility, but it is deprecated and triggers a warning message whenever used, according to official discussions by the ImageMagick team. Because stable distributions like Ubuntu 22.04 LTS or Debian 11/12 still package ImageMagick 6, while newer installations (including via Homebrew on macOS) generally use ImageMagick 7, portable scripts should first detect which command is available rather than assuming one.

if command -v magick &> /dev/null; then
    IM_CONVERT="magick"
    IM_MOGRIFY="magick mogrify"
else
    IM_CONVERT="convert"
    IM_MOGRIFY="mogrify"
fi

The builtin command -v above, according to the GNU Bash Reference Manual, checks whether a name is recognized as a command without actually executing it, returning nothing if not found. This pairs well with exit status testing using the &&/|| operators from Chapter 6.4.2. This version detection pattern will be used again in the case study in Section 14.3.

14.1.2 Batch Video Conversion with ffmpeg

The most basic syntax of ffmpeg, according to its official documentation, is ffmpeg -i input output: ffmpeg automatically detects the input format and infers the output format from the destination file extension. Combining this syntax with a for loop from Chapter 7.1 converts all videos in a directory at once.

#!/bin/bash
for file in *.mov; do
    [[ -f "$file" ]] || continue
    ffmpeg -n -i "$file" -c:v libx264 -crf 23 -c:a aac "${file%.mov}.mp4"
done

${file%.mov} strips the .mov extension from the filename using parameter expansion, as explained in Chapter 13.1.2, and .mp4 is appended as the new extension on the output filename. The -c:v libx264 option selects the codec, the component that determines how video data is compressed and decompressed (in this case H.264 video via the libx264 library), and -c:a aac selects the AAC audio codec, a combination broadly compatible across modern media players. The -crf 23 (Constant Rate Factor) option controls the balance between quality and file size for libx264: the scale ranges from 0 to 51, where lower numbers yield higher quality and larger file sizes, with 23 serving as the default value when omitted.

The -n option placed before -i in the example above is not just a safety precaution like mv -n in Chapter 13.1.4; it is critical for unattended scripts. By default, if an output file already exists, ffmpeg prompts interactively with Overwrite? [y/N] and awaits input from stdin. Inside a for loop executed via a script without an interactive terminal, this prompt can cause the process to stall without visual progress. The -n option forces ffmpeg to skip existing output files immediately without asking, while the -y option does the opposite, overwriting immediately without confirmation. One of these options must be explicitly declared in every batch script instead of relying on default behavior.

If the objective is simply to change the container format without modifying video quality or size, such as converting from .mkv to .mp4, the option -c:v copy -c:a copy performs a stream copy: copying video and audio streams as-is without re-encoding, which is significantly faster than converting codecs.

for file in *.mkv; do
    [[ -f "$file" ]] || continue
    ffmpeg -n -i "$file" -c:v copy -c:a copy "${file%.mkv}.mp4"
done

14.1.3 Batch Image Resizing and Conversion

For images, ImageMagick provides two distinct approaches: convert/magick, which writes results to a new file, and mogrify, which modifies the original file directly in place. Understanding this distinction is essential before implementation because of the risk of overwriting original files.

# Writing to a new file (safe, original file untouched)
magick foto.png -resize 1920x1080 foto-kecil.jpg

# Modifying original file in place, without a backup copy
mogrify -resize 1920x1080 foto.png

According to official ImageMagick documentation, mogrify is explicitly designed to overwrite source files directly, whereas convert (or magick without subcommands in version 7) writes to a separate target file. Due to its destructive nature, mogrify provides a -path option to direct output to another directory, and a -format option to alter the output extension simultaneously, keeping original files intact.

mkdir -p me-resized
mogrify -path me-resized -format jpg -resize 1920x1080 *.png

The command above reads all .png files in the current directory, resizes them to a maximum of 1920x1080 pixels while preserving the aspect ratio (the default behavior of ImageMagick resize geometry unless appended with !), and saves the results as new .jpg files in the me-resized directory without modifying the original .png files. A safe practice for batch operations like this follows the dry-run principle from Chapter 13.1.4: test first on a copy of a small folder containing sample files, and execute across the full collection only after verifying expected output.

14.1.4 Automatic Renaming During Conversion

Batch conversion presents an optimal opportunity to sanitize filenames simultaneously, combining the for loop technique from 14.1.2/14.1.3 with parameter expansion from Chapter 13.1.2 and date command substitution from Chapter 13.2.3.

#!/bin/bash
date_str="$(date +%Y%m%d)"
for file in *.mov; do
    [[ -f "$file" ]] || continue
    base_name="${file%.mov}"
    ffmpeg -n -i "$file" -c:v libx264 -crf 23 -c:a aac "${date_str}_${base_name}.mp4"
done

The ${date_str}_${base_name}.mp4 pattern prepends the conversion date prefix to each generated file, simplifying chronological sorting in any file manager without opening files individually to check creation dates. If source filenames contain spaces or inconsistent characters, insert a normalization step using sed as discussed in Chapter 13.1.3 before using the name as the output filename.

14.1.5 Troubleshooting Unsupported Formats

The .heic image format, default on iPhone cameras since iOS 11, often poses a hidden pitfall for ImageMagick. According to issue notes in the official Homebrew GitHub repository, HEIC support relies on the libheif delegate library, which is not always included depending on the installation source: official imagemagick packages in Ubuntu/Debian repositories historically did not always include libheif by default, whereas the Homebrew formula for imagemagick on macOS currently sets libheif as a standard dependency. Always verify format support before relying on it in production scripts.

magick -list format | grep -i heic   # ImageMagick 7
identify -list format | grep -i heic # ImageMagick 6

If neither command outputs anything, HEIC support is missing from the current installation. On Linux, the most practical approach is installing libheif via additional distribution repositories (such as third-party PPAs for older Ubuntu versions) or upgrading to a newer distribution release that packages it, rather than recompiling ImageMagick from source. Scripts converting these formats should not assume conversion always succeeds; the exit status $? from Chapter 6.4.1 after calling magick/mogrify can be evaluated to detect failures early, rather than discovering unconverted files after the entire batch finishes.

14.2 Archive Compression and Extraction

This section covers three complementary compression/archiving tools: gzip for single-file compression, zip/unzip for cross-platform archive sharing, and tar for extraction (its archive creation having been introduced in Chapter 13.2.3).

14.2.1 gzip for Single File Compression

gzip differs from tar: gzip compresses only one file per operation and cannot bundle multiple files into a single archive like tar. For this reason, combining tar + gzip (the -z option in Chapter 13.2.3) is widely used for full-directory archiving.

gzip raw-photo.raw

Note that by default, according to official documentation, gzip behavior is destructive: gzip automatically removes the original raw-photo.raw file and replaces it with raw-photo.raw.gz, without confirmation or backup. To retain the original file, pass the -k (--keep) option.

gzip -k raw-photo.raw

To restore compressed files, use the -d (--decompress) option or the equivalent gunzip command.

gzip -d raw-photo.raw.gz
# equivalent to:
gunzip raw-photo.raw.gz

14.2.2 Cross-Platform zip and unzip

zip packages multiple files into a single archive like tar, but offers native compatibility across Windows and macOS without additional tools, making it a safer choice than .tar.gz when sharing archives with users unfamiliar with Linux terminals. Like ffmpeg and ImageMagick, zip/unzip are not built into Bash and must be installed via package manager if not present (sudo apt install zip unzip on Debian/Ubuntu, adapted for other distros as in Section 14.1.1).

zip -r important-docs.zip ~/Documents/

The -r (recursive) option is required to archive directory contents and subdirectories; without -r, zip reports an error when given a directory path. Viewing archive contents without extracting, a verification habit similar to tar -t in Chapter 13.2.4, uses the -l option.

unzip -l important-docs.zip

During extraction, if files in the archive conflict with existing filenames in the destination folder, unzip pauses by default with interactive prompts for each collision. This behavior creates bottlenecks in automated scripts, similar to ffmpeg overwrite prompts in Section 14.1.2. The -o option overwrites all conflicts without asking, while -n skips existing files without asking; one of these options should be explicitly selected in scripts.

unzip -n important-docs.zip -d ~/Extracted-Docs/

14.2.3 Extracting tar Archives

Chapter 13.2.3 addressed tar archive creation using the -czf options. Extracting archives uses -x (extract) instead of -c, while remaining syntax remains identical.

tar -xzf documents-20260830-1200.tar.gz -C ~/Extracted-Docs/

The -C option here serves the same purpose as during archive creation in Chapter 13.2.3: directing tar to change working directory first, ensuring extracted files land in the target folder rather than expanding into the current working directory. The pre-extraction verification habit remains identical to Chapter 13.2.4, using -t to inspect archive contents beforehand.

tar -tzf documents-20260830-1200.tar.gz

14.2.4 Selecting the Appropriate Archive Format

These three tools complement each other rather than replace one another; each suits specific use cases, summarized in the following table.

ToolBest Used ForCross-OS Compatibility
gzipSingle file compression (logs, database dumps, etc.)Requires third-party tools on Windows
tar + gzipDirectory snapshots on Linux/macOS, server backupsRequires third-party tools on Windows
zipArchives shared with non-Linux usersNatively supported on Windows & macOS

In practice, the standard combination for desktop users is tar.gz for personal backups within Linux/macOS environments (continuing from Chapter 13.2), switching to zip when sending archives to Windows users without requiring additional software installation.

14.3 Case Study: Media Downloader and Organizer

This case study combines the techniques in this chapter into an organizer script for downloaded or imported media folders, such as a browser Downloads directory or copied SD card contents. Note that this script does not execute network downloads; network fetching is covered when curl/wget are introduced in Chapter 22.1. The focus here remains strictly on local disk media.

14.3.1 Organizer Script Design

The script performs five sequential steps: categorizing files by extension using associative arrays (following Chapter 13.3.2 patterns), converting images and videos to standard formats using techniques from Section 14.1, adding date prefixes as in Section 14.1.4, moving processed source files to an Originals directory to prevent re-processing on subsequent runs, and archiving outputs using zip from Section 14.2.2. Source directory arguments are parsed using getopts (from Chapter 12.2) to ensure portability across directories without script modification.

14.3.2 Full Script Implementation

#!/bin/bash
set -uo pipefail

usage() {
    echo "Usage: $0 -s SOURCE_FOLDER [-a]" >&2
    echo "  -s SOURCE_FOLDER  directory containing media to organize" >&2
    echo "  -a                archive output into a single zip file" >&2
    exit 1
}

archive=0
while getopts ":s:ah" opt; do
    case "$opt" in
        s) source="$OPTARG" ;;
        a) archive=1 ;;
        h) usage ;;
        *) usage ;;
    esac
done

[[ -n "${source:-}" && -d "$source" ]] || usage

if command -v magick &> /dev/null; then
    IM_MOGRIFY="magick mogrify"
else
    IM_MOGRIFY="mogrify"
fi

date_str="$(date +%Y%m%d)"
mkdir -p "$source/Photos" "$source/Videos" "$source/Originals"

declare -A categories=(
    ["jpg"]="Photos" ["jpeg"]="Photos" ["png"]="Photos" ["heic"]="Photos"
    ["mov"]="Videos" ["mp4"]="Videos" ["mkv"]="Videos"
)

for file in "$source"/*; do
    [[ -f "$file" ]] || continue

    filename=$(basename "$file")
    extension="${filename##*.}"
    extension="${extension,,}"
    target="${categories[$extension]:-}"

    [[ -n "$target" ]] || continue

    if [[ "$target" == "Photos" && "$extension" != "jpg" ]]; then
        $IM_MOGRIFY -path "$source/Photos" -format jpg "$file" || continue
        mv -n -- "$source/Photos/${filename%.*}.jpg" \
            "$source/Photos/${date_str}_${filename%.*}.jpg"
        mv -n -- "$file" "$source/Originals/$filename"
    elif [[ "$target" == "Videos" && "$extension" != "mp4" ]]; then
        ffmpeg -n -i "$file" -c:v libx264 -crf 23 -c:a aac \
            "$source/Videos/${date_str}_${filename%.*}.mp4" || continue
        mv -n -- "$file" "$source/Originals/$filename"
    else
        mv -n -- "$file" "$source/$target/${date_str}_${filename}"
    fi
done

if [[ "$archive" -eq 1 ]]; then
    zip -r "$source/media-${date_str}.zip" "$source/Photos" "$source/Videos"
fi

echo "Complete. Outputs located in $source/Photos and $source/Videos."

set -uo pipefail on line two ensures script execution terminates immediately if an unassigned variable is referenced, following debugging practices detailed in Chapter 25.1. The optstring ":s:ah" in getopts (from Chapter 12.2.1) registers three options: s is followed by a colon to specify a required argument (stored in OPTARG), while a and h act as flag options. The leading colon enables silent error mode from Chapter 12.2.3, catching unrecognized flags or missing arguments in the *) case branch rather than emitting default getopts warnings. The validation line [[ -n "${source:-}" && -d "$source" ]] || usage verifies that the source folder argument was provided via -s and exists on disk before proceeding, using default parameter expansion from Chapter 6.4.4 to prevent errors if -s is omitted.

The variable $IM_MOGRIFY inside the loop is deliberately unquoted, representing an exception to the rule requiring double quotes around variables emphasized since Chapter 3.4 and revisited in Chapter 6.2.4. Its value contains two distinct words: magick mogrify (Section 14.1.1). Leaving it unquoted allows Bash to execute word splitting (Chapter 7.1.5), resolving to command magick with first argument mogrify. Quoting it as "$IM_MOGRIFY" would cause Bash to search for a single command executable containing spaces, failing execution. The three-way conditional branch inside the loop selects conversion logic: non-JPEG images convert via $IM_MOGRIFY, non-MP4 videos convert via ffmpeg, and standard formats transfer directly with a date prefix. The parameter expansion pattern ${filename%.*} strips the final file extension, appending new extensions (.jpg or .mp4) cleanly.

The || continue clause following $IM_MOGRIFY and ffmpeg applies troubleshooting guidelines from 14.1.5: if conversion fails (e.g., unsupported .heic format in the ImageMagick build), non-zero exit statuses skip remaining loop commands via continue (Chapter 7.3.2) without moving failed files to Originals. Unconverted files remain in place in the source folder for manual inspection rather than being misplaced or silently misclassified as completed. Line mv -n -- "$file" "$source/Originals/$filename" executes only upon successful conversion, preserving source copies in case output verification is needed and preventing re-processing on subsequent script runs.

14.3.3 Verification and Further Development

Grant execution permissions and run the script as demonstrated in Chapter 1.4.3, then verify output structure using ls.

chmod +x media-organizer.sh
./media-organizer.sh -s ~/Downloads -a

ls ~/Downloads/Photos ~/Downloads/Videos ~/Downloads/Originals
unzip -l ~/Downloads/media-"$(date +%Y%m%d)".zip

Execute dry-run testing on sample copies before processing live Downloads directories, adhering to safety practices established in Chapter 13.1.4. If test folders contain .heic media, verify codec support using Section 14.1.5 commands to prevent silent batch failures. Note that video conversion via ffmpeg requires substantial processing time for large files or large file counts; extended processing duration does not indicate a stalled script. Monitor active CPU workloads via top/htop (covered in Chapter 17.2) to confirm active operation.

An extension to this implementation includes automated rotation, removing files in Photos/Videos older than a specified threshold using find -mtime patterns from Chapter 13.2.4. Scheduled automated execution is detailed in Chapter 15.1. The three multimedia capabilities in this chapter, batch conversion, archive management, and automated organization, round out core daily automation workflows begun in Chapter 13. Chapter 15 expands into desktop productivity: notifications, desktop application integration, and dotfile deployment for environment setup.