
Дисклеймер
Я не аудио-, видео-, картино-фил.
Методы сжатия используемые в этой статье с потерей качества. Но на мой личный взгляд почти незаметно. У вас может быть на этот счет другое мнение.
В статье могут присутствовать не точности.
Статья не покрывает все параметры и тонкости использования
ffmpeg
,magick
иfish
, за дополнительной информацией обращайтесь к их документациям или другим ресурсам.Документация
ffmpeg
Документация
magick
Документация
fish
Предисловие
Моя мотивация. Со временем у меня собралось огромное количество видео, картинок и музыки >100 ГБ и мне нужно это хранить и перемещать. И так как для меня увеличить электронное пространство не вариант, следовательно я решил сжать сами медиа файлы.
TL;DR
Вы наверняка имеете медиа файлы закодированные не самым эффективным кодеком (H.264, JPEG, MP3). И вы можете сократить их размер вплоть до 75% перекодировав медиа современными альтернативами (H.265, AVIF, OPUS).
Использовать для этого можно консольные утилиты ffmpeg
и magick
.
Вот скрипт который перекодирует все медиа файлы в директории современными кодеками.
Скрипт для конвертации медиа файлов
# ===================== Утилита сжатия медиа =============================
# Полнофункциональный инструмент для сжатия медиафайлов, поддерживающий аудио, видео и изображения
# Использует современные кодеки (Opus, HEVC, AVIF) с разумными настройками по умолчанию для качества/размера
# ==============================================================================
function compress_media
# ===================== КОНСТАНТЫ ============================
# ВНИМАНИЕ: Порядок имеет значение - индексы должны совпадать между этими двумя массивами
set FILE_TYPES audio video image # Поддерживаемые типы медиа
set FILE_TYPE_EXTENSIONS opus mp4 avif # Соответствующие расширения файлов для типов медиа
# ПРИМЕЧАНИЕ: Вы можете использовать разные команды для ffmpeg
# - ffmpeg - подробный вывод. Измените функцию compress_file для управления уровнем логирования
# - ffmpeg-bar - индикатор прогресса
set -g ffmpeg ffmpeg-bar
# ===================== ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ============================
function bold -d "Применить жирное оформление к тексту"
if not count $argv >/dev/null # Хак: Чтение из pipe в argv
read --null --list --delimiter=\n argv
end
echo "\e[1m$argv\e[22m"
end
function yellow -d "Применить желтый цвет к тексту"
if not count $argv >/dev/null # Хак: Чтение из pipe в argv
read --null --list --delimiter=\n argv
end
echo "\e[0;33m$argv\e[39m"
end
function red -d "Применить красный цвет к тексту"
if not count $argv >/dev/null # Хак: Чтение из pipe в argv
read --null --list --delimiter=\n argv
end
echo "\e[0;31m$argv\e[39m"
end
function file_type -a file -d "Определить категорию типа файла из mime-типа"
set -l mime_type (file --mime-type -b "$file")
echo (string split '/' $mime_type)[1]
end
function compress_file -a input -a output -d "Сжать файл"
switch (file_type $input)
case audio
# Сжатие аудио Opus с переменным битрейтом
# ПРИМЕЧАНИЕ: Битрейт 64k обеспечивает хорошее качество для большинства контента
$ffmpeg -i "$input" \
-c:a libopus -b:a 64k -vbr on \
-y "$output"
case video
# ПРОИЗВОДИТЕЛЬНОСТЬ:
# - `nice -n 20 ` - снижение приоритета процесса
# - `cpulimit -l 2` - ограничение использования ЦП. Для меня это занимает 90% ЦП
sudo nice -n 20 cpulimit -l 2 -i \
$ffmpeg -i "$input" \
-c:v libx265 -crf 31 -vf "scale='min(iw,1024)':-2" \
-pix_fmt yuv420p \
-c:a aac -b:a 128k -vtag hvc1 \
-y "$output"
# Если вы используете ffmpeg вместо ffmpeg-bar.
# Добавьте эти опции для уменьшения вывода логов
# -loglevel error -x265-params log-level=error
case image
magick $input $output
end
tput smam # ВНИМАНИЕ: Ручной вызов tput для исправления отключения обертывания терминала из ffmpeg-bar
end
function is_compressed -a item -d "Проверить, содержит ли имя файла маркер '.compressed.'"
string match -q '*.compressed.*' (path basename $item)
end
# ===================== Аргументы командной строки ============================
argparse \
h/help \
'm/media-type=+' \
't/target=' \
k/keep-original \
only-media \
-- $argv
if not string match -rq "GNU findutils" (find --version 2>/dev/null) # Требуется GNU find, а не BSD
echo -e (red "Требуется GNU find, а не BSD.")
return 1
end
if set -q _flag_h # Отобразить сообщение справки, если true
echo "\
compress_media - Сжатие аудио, видео или изображений в указанных директориях
Использование:
compress_media [ОПЦИИ]... [ИСТОЧНИКИ]...
Описание:
Рекурсивно сжимает медиафайлы (аудио/видео/изображения) в указанных директориях.
По умолчанию заменяет оригиналы сжатыми версиями (перемещает в корзину, если не используется -k).
Используйте --target для сохранения структуры директорий в другом месте.
Опции:
-h, --help Показать это сообщение справки и выйти
-m, --media-type TYPE ОБЯЗАТЕЛЬНО. Тип медиа для обработки (audio|video|image|all)
-t, --target DIR Директория вывода для сжатых файлов (сохраняет структуру). Относительный путь должен начинаться с `./`, иначе это будет `/`.
-k, --keep-original Сохранить оригинальные файлы (не перемещать в корзину)
--only-media Обрабатывать только медиафайлы (не копировать другие файлы в целевую директорию)
Поведение:
- При использовании --target:
* Создает зеркальную структуру директорий в целевом месте
* Копирует немедиафайлы без изменений, если не указано --only-media
* Хранит сжатые версии медиафайлов в соответствующих директориях
- Без --target:
* Сжимает файлы на месте
* Оригинальные файлы перемещаются в корзину, если не указано --keep-original
Поддерживаемые форматы:
- audio: конвертируется в OPUS (64kbit/s, VBR)
- video: конвертируется в H.265 MP4 (CRF 31, ширина 1024px)
- image: конвертируется в AVIF с использованием настроек ImageMagick по умолчанию
Примеры:
1. Сжатие аудиофайлов в текущей директории:
compress_media -m audio
2. Сжатие видео в ~/Videos, сохранение оригиналов, вывод в ~/Compressed:
compress_media -m video -t ~/Compressed -k ~/Videos
3. Сжатие изображений в ~/Pictures, обработка только медиафайлов:
compress_media -m image --only-media -t ~/Compressed_Images ~/Pictures
Примечания:
- Требуется ffmpeg, ImageMagick, trash и coreutils (для определения типа файла)
- Вы должны использовать GNU find на MacOS. Сделайте `alias find=gfind` перед
запуском скрипта
- Сжатие видео использует ограничение ЦП для стабильности
- Сжатые файлы получают расширение .compressed перед фактическим расширением
"
return 0
end
if not set -q _flag_m # Флаг media-type обязателен
echo (red Ошибка: опция m/media-type не указана)
return 1
else
for v in $_flag_m # Проверка значения флага media-type
set ALLOWED_VALUES $FILE_TYPES all
if not contains $v $ALLOWED_VALUES
echo (red "Флаг media-type может содержать только значения \
из \"$ALLOWED_VALUES\", но не \"$v\".")
return 1
end
end
contains all "$_flag_m" # Если одно из значений "all"
and set _flag_m $FILE_TYPES # то установить все типы файлов
end
# ===================== Обработка источника и цели ===================================
if set -q argv[1] # Если есть позиционные аргументы, то они являются источниками
set SOURCES $argv
else # Иначе текущая директория является источником
set SOURCES .
end
for source in $SOURCES # Проверка существования исходной директории
if not test -d $source
echo Исходная директория $source не существует.
exit 1
end
end
if set -q _flag_t # Создать целевую директорию, если указана и не существует
set TARGET $_flag_t
if not test -d $TARGET
mkdir $TARGET
end
end
# ===================== Основной цикл обработки ==============================
for source in $SOURCES
# Найти все файлы, исключив целевую директорию
# Хак: `tail -n +2` обрезает исходную директорию (первую строку)
for item in (find $source -path $TARGET -prune -o -print | tail -n +2)
# ============= Обработка целевой директории =======================
if set -q TARGET
set target_item (string replace -r "^$source/?" "$TARGET/" "$item") # Путь к элементу относительно целевой директории вместо исходной
test $item -ef $TARGET; and continue # Хак: `find` также перечисляет целевую директорию, если она в источнике, поэтому пропустить её
if test -d "$item" # Директория
and not set -q _flag_only_media
echo -en "Создание директории: $(bold $target_item)"
test -d $target_item # Создать директорию, если не существует
and echo -e \t(yellow Директория (bold $target_item) уже создана)
or begin
echo
mkdir -p $target_item
end
else if contains (file_type $item) $_flag_m # Не сжатый медиафайл
and not is_compressed $item
set output_extension compressed.$FILE_TYPE_EXTENSIONS[(contains -i (file_type $item) $FILE_TYPES)]
set output (path change-extension $output_extension $target_item)
set -q _flag_only_media # Хак: Убедиться, что целевая директория существует, если установлен флаг only media. Поскольку изначально структура директорий не создается
and mkdir -p (path dirname $output)
echo -en Обработка файла (bold $item) в (bold $output)
test -e $output
and echo -e \t(yellow Файл (bold $output) уже существует)
or begin
echo
compress_file $item $output
end
else if not set -q _flag_only_media # Немедиа или сжатый медиафайл
echo -ne "Копирование файла: $(bold $item) в $(path dirname $target_item | bold)"
test -e $target_item
and echo -e \t(yellow Файл (bold $target_item) уже существует)
or begin
echo
cp "$item" "$target_item"
end
end
# ============= Обработка на месте ===============================
else if contains (file_type $item) $_flag_m # Не сжатый медиафайл
and not is_compressed $item
set output_extension compressed.$FILE_TYPE_EXTENSIONS[(contains -i (file_type $item) $FILE_TYPES)]
set output (path change-extension $output_extension $item)
echo -ne Обработка файла (bold $item) в (path basename $output | bold)
test -e $output
and echo -e \t(yellow Файл (bold $item) уже существует)
or begin
echo
compress_file $item $output
and not set -q _flag_k
and begin
echo Перемещение $item в корзину
trash $item # MacOS
# trash-put $item # Linux
end
end
end
end
end
end
На английском
# ===================== Media Compression Utility =============================
# A comprehensive media compression tool supporting audio, video and image files
# Uses modern codecs (Opus, HEVC, AVIF) with sensible defaults for quality/size
# ==============================================================================
function compress_media
# ===================== CONSTANTS ============================
# WARN: Order matters - indexes must match between these two arrays
set FILE_TYPES audio video image # Supported media types
set FILE_TYPE_EXTENSIONS opus mp4 avif # Corresponding to media types file extensions
# NOTE: You can use different command for ffmpeg
# - ffmpeg - verbose output. Change compress_file function to controll loglevel
# - ffmpeg-bar - progress bar
set -g ffmpeg ffmpeg-bar
# ===================== HELPER FUNCTIONS ============================
function bold -d "Apply bold styling to text"
if not count $argv >/dev/null # HACK: Read pipe to argv
read --null --list --delimiter=\n argv
end
echo "\e[1m$argv\e[22m"
end
function yellow -d "Apply yellow color to text"
if not count $argv >/dev/null # HACK: Read pipe to argv
read --null --list --delimiter=\n argv
end
echo "\e[0;33m$argv\e[39m"
end
function red -d "Apply red color to text"
if not count $argv >/dev/null # HACK: Read pipe to argv
read --null --list --delimiter=\n argv
end
echo "\e[0;31m$argv\e[39m"
end
function file_type -a file -d "Determine file type category from mime-type"
set -l mime_type (file --mime-type -b "$file")
echo (string split '/' $mime_type)[1]
end
function compress_file -a input -a output -d "Compress file"
switch (file_type $input)
case audio
# Opus audio compression with variable bitrate
# NOTE: 64k bitrate provides good quality for most content
$ffmpeg -i "$input" \
-c:a libopus -b:a 64k -vbr on \
-y "$output"
case video
# PERF:
# - `nice -n 20 ` - lower priority of process
# - `cpulimit -l 2` - limit CPU usage. For me it take 90% of CPU
sudo nice -n 20 cpulimit -l 2 -i \
$ffmpeg -i "$input" \
-c:v libx265 -crf 31 -vf "scale='min(iw,1024)':-2" \
-pix_fmt yuv420p \
-c:a aac -b:a 128k -vtag hvc1 \
-y "$output"
# If you use use ffmpeg instead ffmpeg-bar.
# Add this options to reduce log output
# -loglevel error -x265-params log-level=error
case image
magick $input $output
end
tput smam # WARN: Manual tput call to fix disabling terminal wrapping from ffmpeg-bar
end
function is_compressed -a item -d "Check if filename contains '.compressed.' marker"
string match -q '*.compressed.*' (path basename $item)
end
# ===================== Command Line Arguments ============================
argparse \
h/help \
'm/media-type=+' \
't/target=' \
k/keep-original \
only-media \
-- $argv
if not string match -rq "GNU findutils" (find --version 2>/dev/null) # Require GNU find not BSD
echo -e (red "Required GNU find not BSD.")
return 1
end
if set -q _flag_h # Display help message if true
echo "\
compress_media - Compress audio, video, or image files in specified directories
Usage:
compress_media [OPTIONS]... [SOURCES]...
Description:
Recursively compresses media files (audio/video/image) in specified directories.
By default replaces originals with compressed versions (moved to trash unless -k is used).
Use --target to preserve directory structure in a different location.
Options:
-h, --help Show this help message and exit
-m, --media-type TYPE REQUIRED. Media type to process (audio|video|image|all)
-t, --target DIR Output directory for compressed files (preserves structure). Relative path must start with `./` otherwise it will be `/`.
-k, --keep-original Keep original files (don't move to trash)
--only-media Only process media files (don't copy other files to target)
Behavior:
- When using --target:
* Creates mirrored directory structure in target location
* Copies non-media files unchanged unless --only-media is specified
* Stores compressed versions of media files in corresponding directories
- Without --target:
* Compresses files in-place
* Original files are moved to trash unless --keep-original is specified
Supported Formats:
- audio: converts to OPUS (64kbit/s, VBR)
- video: converts to H.265 MP4 (CRF 31, 1024px width)
- image: converts to AVIF using ImageMagick default settings
Examples:
1. Compress audio files in current directory:
compress_media -m audio
2. Compress videos in ~/Videos, keep originals, output to ~/Compressed:
compress_media -m video -t ~/Compressed -k ~/Videos
3. Compress images in ~/Pictures, only process media files:
compress_media -m image --only-media -t ~/Compressed_Images ~/Pictures
Notes:
- Requires ffmpeg, ImageMagick, trash, and coreutils (for file type detection)
- You must use GNU find on MacOS. Make `alias find=gfind` before
running script
- Video compression uses CPU limiting for stability
- Compressed files get .compressed extension before the actual extension
"
return 0
end
if not set -q _flag_m # Flag media-type required
echo (red Error: option m/media-type not specified)
return 1
else
for v in $_flag_m # Validate value of flag media-type
set ALLOWED_VALUES $FILE_TYPES all
if not contains $v $ALLOWED_VALUES
echo (red Flag media-type can contain only values \
from \"$ALLOWED_VALUES\", but not \"$v\".)
return 1
end
end
contains all "$_flag_m" # If one of the value "all"
and set _flag_m $FILE_TYPES # then set to all file types
end
# ===================== Source and Target Handling ===================================
if set -q argv[1] # If there are positional args, then they are sources
set SOURCES $argv
else # Else current dir is source
set SOURCES .
end
for source in $SOURCES # Verify source dir existance
if not test -d $source
echo Source directory $source not exist.
exit 1
end
end
if set -q _flag_t # Create target directory if specified and doesn't exist
set TARGET $_flag_t
if not test -d $TARGET
mkdir $TARGET
end
end
# ===================== Main Processing Loop ==============================
for source in $SOURCES
# Find all files while excluding target directory
# HACK: `tail -n +2` trim source dir (first line)
for item in (find $source -path $TARGET -prune -o -print | tail -n +2)
# ============= Target Directory Processing =======================
if set -q TARGET
set target_item (string replace -r "^$source/?" "$TARGET/" "$item") # Path to item relative to target dir instead of source
test $item -ef $TARGET; and continue # HACK: `find` also list target dir if it in source, so skip it
if test -d "$item" # Dir
and not set -q _flag_only_media
echo -en "Creating directory: $(bold $target_item)"
test -d $target_item # Create dir if not exist
and echo -e \t(yellow Directory (bold $target_item) already created)
or begin
echo
mkdir -p $target_item
end
else if contains (file_type $item) $_flag_m # Not compressed Media
and not is_compressed $item
set output_extension compressed.$FILE_TYPE_EXTENSIONS[(contains -i (file_type $item) $FILE_TYPES)]
set output (path change-extension $output_extension $target_item)
set -q _flag_only_media # HACK: Ensure output directory exists, when flag only media. Cause initialy dir structure not created
and mkdir -p (path dirname $output)
echo -en Processing file (bold $item) into (bold $output)
test -e $output
and echo -e \t(yellow File (bold $output) already exist)
or begin
echo
compress_file $item $output
end
else if not set -q _flag_only_media # Non-media or compressed media
echo -ne "Copying file: $(bold $item) to $(path dirname $target_item | bold)"
test -e $target_item
and echo -e \t(yellow File (bold $target_item) already exist)
or begin
echo
cp "$item" "$target_item"
end
end
# ============= In-place Processing ===============================
else if contains (file_type $item) $_flag_m # Not compressed media
and not is_compressed $item
set output_extension compressed.$FILE_TYPE_EXTENSIONS[(contains -i (file_type $item) $FILE_TYPES)]
set output (path change-extension $output_extension $item)
echo -ne Processing file (bold $item) into (path basename $output | bold)
test -e $output
and echo -e \t(yellow File (bold $item) already exist)
or begin
echo
compress_file $item $output
and not set -q _flag_k
and begin
echo Move $item to trash
trash $item # MacOS
# trash-put $item # Linux
end
end
end
end
end
end
Установка зависимостей
### Debian/Ubuntu (apt)
sudo apt update && sudo apt install -y \
ffmpeg imagemagick file trash-cli cpulimit findutils coreutils \
ffmpeg-bar fish
### macOS (Homebrew)
brew install \
ffmpeg imagemagick findutils coreutils cpulimit trash \
ffmpeg-bar fish
# For GNU find (required)
echo "alias find=gfind" >> ~/.config/fish/config.fish
Введение
В настоящее время до сих пор распространены не эффективные медиа форматы, которым больше 20 лет. Такие как: H.264 (AVC), JPEG, MP3. Хотя есть современные более эффективные альтернативы, с хорошей совместимостью. Такие как: H.265 (HEVC), AVIF, AAC.
В данной статье представлены способы перекодирования медиа файлов в более современные с потерей качества (на мой глаз и слух не заметных), используя утилиты командной строки, такие как ffmpeg
и magick
. Так же написан скрипт для оболочки fish для конвертации всего медиа из директорий.
Сведения об ffmpeg
FFmpeg (Fast Forward Moving Picture Experts Group) — инструмент для обработки мультимедийных данных, который позволяет кодировать, декодировать, транскодировать, мультиплексировать, демультиплексировать, потоково передавать, фильтровать и проигрывать практически любые аудио- и видео-форматы.
Установка:
Windows: Следуйте инструкциям на страницах Download FFmpeg или Как установить программу FFmpeg в Windows.
macOS:
brew install ffmpeg
Linux:
sudo apt-get install ffmpeg # Ubuntu/Debian sudo dnf install ffmpeg # Fedora sudo pacman -S ffmpeg # Arch
Синопсис:
ffmpeg [global_options] {[input_file_options] -i input_url} ... {[output_file_options] output_url} ...
Общие параметры
-i url
- Указывает URL входного файла.-y
- Перезаписывает выходные файлы без запроса.-n
- Не перезаписывает выходные файлы и немедленно завершает работу, если указанный выходной файл уже существует.-c[:stream_specifier] codec
или-codec[:stream_specifier] codec
- Выбирает кодек (кодировщик при использовании перед выходным файлом или декодер при использовании перед входным файлом) для одного или нескольких потоков.codec
— это имя декодера/кодировщика или специальное значениеcopy
(только для выхода), указывающее, что поток не должен быть перекодирован. Для каждого потока применяется последняя соответствующая опция-c
.-filter[:stream_specifier] filtergraph
- Создает фильтр-граф, указанный вfiltergraph
, и использует его для фильтрации потока.
Фильтры:
scale
- Изменение размера (масштабирование) входного видео. Этот фильтр обеспечивает соответствие соотношения сторон отображения выходного видео входному видео, корректируя соотношение сторон выходного образца.Опции:
width, w: Установить ширину выходного видео.
height, h: Установить высоту выходного видео.
Значения по умолчанию — это размеры входного изображения. Если
width
(w
) илиheight
(h
) установлены в0
, используется ширина или высота входного изображения для выходного изображения. Если одно из значений установлено в-n
(гдеn >= 1
), фильтр сохранит соотношение сторон входного изображения на основе другого указанного размера и обеспечит, чтобы рассчитанный размер был кратенn
. Если оба значения равны-n
, поведение такое же, как если бы оба были установлены в0
.
Константы для выражений размеров: Значения опций
w
иh
являются выражениями, содержащими следующие константы:in_w, in_h: Ширина и высота входного изображения.
iw, ih: То же, что и
in_w
иin_h
.out_w, out_h: Ширина и высота выходного (масштабированного) изображения.
ow, oh: То же, что и
out_w
иout_h
.a: То же, что и
iw / ih
.
Примеры:
# Масштабирование до определенного размера. Масштабировать входное видео до размера 200x100: scale=w=200:h=100 # Или scale=200:100 # Или scale=200x100 # Аббревиатура размера. Указать аббревиатуру размера для выходного размера: scale=qcif # Или scale=size=qcif # Масштабирование в 2 раза. Масштабировать входное изображение в 2 раза: scale=w=2*iw:h=2*ih # Или scale=2*in_w:2*in_h # Масштабирование до половины размера. Масштабировать входное изображение до половины размера: scale=w=iw/2:h=ih/2 # Увеличение ширины и установка высоты в тот же размер: scale=3/2*iw:ow # Увеличение высоты и установка ширины в 3/2 от высоты: scale=w=3/2*oh:h=3/5*ih # Сохранение соотношения сторон. Увеличить ширину до максимум 500 пикселей, сохраняя то же соотношение сторон, что и у входного изображения: scale=w='min(500\, iw*3/2):h=-1'
fps
- Преобразует видео в указанную постоянную частоту кадров, дублируя или удаляя кадры по мере необходимости.Параметры:
fps
- Желаемая частота кадров выходного видео. Принимает выражения, содержащие следующие константы:source_fps: Частота кадров входного видео.
ntsc: Частота кадров NTSC 30000/1001.
pal: Частота кадров PAL 25.0.
film: Частота кадров фильма 24.0.
ntsc_film: Частота кадров NTSC-фильма 24000/1001.
По умолчанию используется значение 25.
Примеры:
# Установить FPS в 25 fps=fps=25
Примеры использования:
ffmpeg -i input.mp4 -c:v libx264 -c:a aac output.mkv # Конвертация MP4 в MKV с использованием кодеков libx264 и aac
ffmpeg -i input.mp4 -q:a 0 -map a output.mp3 # Извлечение аудиодорожки в формате MP3 с высоким качеством
ffmpeg -i input.mp4 -ss 00:01:00 -to 00:02:00 -c copy output.mp4 # Обрезка видео с 1 минуты до 2 минут без перекодирования
ffmpeg -i input.mp4 -vf "scale=1280:720" output.mp4 # Изменение разрешения видео до 1280x720
ffmpeg -i input.mp4 -i watermark.png -filter_complex "overlay=10:10" output.mp4 # Наложение водяного знака на видео в позиции (10,10)
Сведения об magick
magick
— инструмент для обработки изображений, который позволяет выполнять широкий спектр операций с изображениями, включая конвертацию форматов, редактирование, применение фильтров, создание анимаций и многое другое.
Установка:
Windows: Скачайте установочный файл с официального сайта ImageMagick и следуйте инструкциям установки.
macOS: Используйте Homebrew для установки:
brew install imagemagick
Linux: Установите из пакетного менеджера вашего дистрибутива. Например, для Ubuntu:
sudo apt-get install imagemagick
Синопсис:
magick [input-filename...] [settings...] [operators...] [sequence-operators...] [stacks...] [output-filename]
Общие параметры:
input-filename
: Указывает входной файл или файлы.settings
: Настройки, которые применяются к изображению.operators
: Операторы, которые выполняют различные операции с изображением.sequence-operators
: Операторы, которые применяются к последовательности изображений.stacks
: Операторы, которые работают с наборами изображений.output-filename
: Указывает выходной файл.
Примеры использования:
magick input.jpg -resize 800x600 output.jpg # Изменение размера изображения до 800x600 пикселей
magick input.png -rotate 90 output.png # Поворот изображения на 90 градусов
magick input.jpg -gravity center -crop 300x300+0+0 +repage output.jpg # Обрезка изображения до размера 300x300 пикселей с центрированием
magick input.jpg -blur 0x8 output.jpg # Применение размытия к изображению
magick input1.jpg input2.jpg -append output.jpg # Объединение двух изображений по вертикали
magick input.gif -coalesce -gravity center -background none -extent 320x240 output.gif # Изменение размера анимации GIF до 320x240 пикселей с центрированием
Сведения об fish
Fish (Friendly Interactive SHell) — современная интерактивная оболочка командной строки с подсветкой синтаксиса, умным автодополнением, встроенной справкой и удобным скриптовым языком. Отличается дружелюбным интерфейсом и простотой настройки.
Установка:
Windows:
Через WSL (рекомендуется):
sudo apt-get install fish
Нативно: через Chocolatey (требует прав администратора):
choco install fish
macOS:
brew install fish
Linux:
sudo apt-get install fish # Ubuntu/Debian sudo dnf install fish # Fedora sudo pacman -S fish # Arch
Синопсис:
fish [options] [cmd]
Основные параметры:
-c "cmd"
— выполнить команду и завершить работу.-d level
— включить отладку (например,-d 3
).-i
— запустить интерактивный режим (по умолчанию).-v
или--version
— вывести версию.-N
— отключить автодополнение.--profile=file
— записать статистику производительности в файл.
Примеры использования:
# Запуск интерактивной оболочки
fish
# Выполнение одной команды
fish -c "echo Hello World && ls -l"
# Настройка fish как оболочки по умолчанию
chsh -s (which fish)
# Создание функции в fish
function greet
echo "Привет, $argv!"
end
greet "Пользователь" # Вывод: Привет, Пользователь!
# Работа с переменными
set name "Fish"
echo $name # Вывод: Fish
Конфигурация. Файл настроек: ~/.config/fish/config.fish
.
Для быстрой настройки используйте веб-интерфейс:
fish_config
Как использовать методы в этой статье и теория
Вкратце: Если не обязательно сохранять исходное качества, можно перекодировать с в более высоком качестве и удалить исходные файлы. Иначе кодируйте как хотите и переносите исходники в холодное хранилище.
Форматы с потерями и без потерь
Форматы без потерь сохраняют все исходные данные из исходного файла. Это означает, что при сжатии не теряется никакая информация, что гарантирует возможность точного восстановления файла в его первоначальном виде. Распространенные форматы без потерь включают:
Изображения: PNG, BMP, TIFF
Аудио: WAV, FLAC, ALAC
Видео: AVI (несжатый), Huffyuv
Используются для:
Сохранения качества. Исходные файлы без потерь обеспечивают сохранение первоначального качества медиа. Это критично для профессиональной работы, где любое ухудшение качества может быть недопустимым.
Гибкости при редактировании. Файлы без потерь обеспечивают большую гибкость при редактировании. Например, при редактировании видео форматы без потерь позволяют проводить несколько раундов редактирования без накопления артефактов сжатия.
Архивных целей. Для долговременного хранения и архивирования предпочтительны форматы без потерь, так как они гарантируют, что медиа можно будет восстановить в первоначальном качестве даже спустя годы.
Форматы с потерями достигают более высоких степеней сжатия, отбрасывая часть исходных данных. Хотя это приводит к уменьшению размера файлов, оно также вносит определенный уровень ухудшения качества. Распространенные форматы с потерями включают:
Изображения: JPEG, GIF
Аудио: MP3, AAC, OGG
Видео: MP4 (H.264), MKV (H.265), WebM (VP9)
Используются для:
Распространения: Форматы с потерями идеально подходят для распространения медиа через интернет или на физических носителях благодаря их меньшему размеру. Это делает их более доступными и быстрыми для загрузки или потоковой передачи.
Эффективности хранения: Для личного или некритического использования форматы с потерями могут значительно экономить место на хранилище. Это особенно полезно для устройств с ограниченной емкостью хранения.
Совместимости: Некоторые устройства и платформы могут не поддерживать форматы без потерь, делая форматы с потерями необходимым выбором по соображениям совместимости.
Плохие практики перекодирования
Отсутствие тестирования закодированных файлов: поздно и не вовремя выявляются проблемы.
Перекодирование из в формат с потерями: деградация качества файла усугубляется с каждым перекодированием.
Чрезмерное сжатие: значительное ухудшение качества.
Использование форматов с потерями для профессиональной работы: Форматы с потерями (такие как MP3, AAC, JPEG) подходят для потребителей, но не для профессионального редактирования или архивирования, так как удаляют часть данных. Для этих целей лучше использовать форматы без потерь (такие как FLAC, PNG)
Игнорирование совместимости с целевой платформой: использование на разных платформах может быть затруднено или невозможно.
Сжатие аудио
Есть два кодека эффективнее чем MP3 это AAC и OPUS.
Общие параметры аудио кодировщиков
-b:a
- Устанавливает битрейт в битах/с. Если включен режим VBR, эта опция игнорируется.-c:a
- Устанавливает аудио-кодек.-q:a
- Устанавливает качество для режима переменного битрейта (специфично для кодека, VBR).
Перекодирование в AAC
Кодировщики:
Fraunhofer FDK AAC
libfdk_aac
audiotoolbox Encoder
aac_at
(обычно доступен только на MacOS)Native FFmpeg AAC Encoder
aac
(По умолчанию)
Профили:
aac_low
(1): Low Complexity AAC (LC-AAC) (По умолчанию): Высокая совместимость, низкое потребление процессорной мощности. Аудио приемлемо звучит с битрейтом 128k.aac_he
(4): High Efficiency AAC (HE-AAC): Низкая совместимость, высокое потребление процессорной мощности, хорошо звучит на низких битрейтах. Аудио приемлемо звучит с битрейтом 64k. Доступный диапазон битрейта 20 КБИТ/СЕК ≤ 80 КБИТ/СЕК.aac_he_v2
(28): High Efficiency AAC version 2 (HE-AACv2): Улучшенная версия HE-AAC, лучшее качество звука на низких битрейтах. Аудио приемлемо звучит с битрейтом 32k. Доступный диапазон битрейта 20 КБИТ/СЕК ≤ 48 КБИТ/СЕК.
Параметры
-profile:a
- Устанавливает аудио профиль.-b:a
. Если битрейт не указан явно, он автоматически устанавливается на подходящее значение в зависимости от выбранного профиля.
Параметры aac_at
-aac_at_mode
- Устанавливает режим управления битрейтом. Возможные значения варьируются от -1 до 3:auto
(-1
): VBR, если указано глобальное качество; CBR в противном случае (По умолчанию)cbr
(0
): Постоянный битрейтabr
(1
): Средний битрейт в долгосрочной перспективеcvbr
(2
): Ограниченный переменный битрейтvbr
(3
): Переменный битрейт
Параметры libfdk_aac
-vbr
- Устанавливает режим VBR, от 1 (низшее качество) до 5 (высокое качество). Значение 0 отключает VBR, и включается CBR (постоянный битрейт). В настоящее время только профильaac_low
поддерживает кодирование VBR.0
: отключить VBR (По умолчанию)1
: 32 кбит/с на канал2
: 40 кбит/с на канал3
: 48-56 кбит/с на канал4
: 64 кбит/с на канал5
: около 80-96 кбит/с на канал
Перекодирование в OPUS
При малых битрейтах OPUS звучит хорошо только в стерео, в моно звучит хуже чем AAC.
Кодировщик доступен только один libopus
. Он не поддерживает кодирование, основанное на качестве.
Параметры libopus
-vbr
- Устанавливает режим VBR. Опция-vbr
в FFmpeg имеет следующие допустимые аргументы, с эквивалентными опциями в opusenc в скобках:off (hard-cbr)
: Использует кодирование с постоянным битрейтом.on (vbr)
: Использует кодирование с переменным битрейтом (по умолчанию).constrained (cvbr)
: Использует кодирование с ограниченным переменным битрейтом.
Пример команд
# Явное указание кодека aac (aac), который используется по умолчанию
ffmpeg -i input.mp3 -c:a aac output.m4a
# Использование кодека libopus (opus)
ffmpeg -i input.mp3 -c:a libopus output.m4a
# Использование кодека libfdk_aac (aac)
ffmpeg -i input.mp3 -c:a libfdk_aac output.m4a
# Использование профиля AAC HE первой версии
ffmpeg -i input.mp3 -c:a libfdk_aac -profile:a aac_he -b:a 64k output.m4a
# Использование профиля AAC HE второй версии
ffmpeg -i input.mp3 -c:a libfdk_aac -profile:a aac_he_v2 -b:a 32k output.m4a
# Использование профиля AAC HE первой версии для кодека aac_at (aac)
ffmpeg -i input.mp3 -c:a aac_at -profile:a 4 -b:a 64k output.m4a
# Использование профиля AAC HE второй версии для кодека aac_at (aac)
ffmpeg -i input.mp3 -c:a aac_at -profile:a 28 -b:a 32k output.m4a
# Указание качества 10 (0-14, 0 - самое высокое качество)
ffmpeg -i input.mp3 -q:a 10 output.m4a
# Указание битрейта
ffmpeg -i input.mp3 -b:a 128k output.m4a
Таблица сравнения кодировщиков
Оригинальный файл | Кодек | Качество | Битрейт (КБИТ/СЕК) | Режим кодирования | Размер (МБ) | Относительный размер (%) | Качество на мой слух |
---|---|---|---|---|---|---|---|
11 - Drop Dead.mp3 | mp3 | 320 | 7.221 | ||||
aac | 20.7 | cbr | 0.468 | 6 | Ужасно | ||
aac | 20.7 | abr | 0.468 | 6 | Ужасно | ||
aac | 20.7 | cvbr | 0.468 | 6 | Плохо | ||
opus | 15.3 | cbr | 0.346 | 4 | Плохо | ||
opus | 15.3 | cvbr | 0.346 | 4 | Плохо | ||
opus | 15.3 | vbr | 0.346 | 4 | Плохо | ||
aac | 32.7 | cbr | 0.739 | 10 | Средне | ||
aac | 32.7 | abr | 0.739 | 10 | Лучше среднего | ||
aac | 32.7 | cvbr | 0.739 | 10 | Похоже на оригинал | ||
opus | 31.9 | cbr | 0.720 | 9 | Лучше среднего | ||
opus | 31.9 | cvbr | 0.720 | 9 | Похоже на оригинал | ||
opus | 31.9 | vbr | 0.720 | 9 | Похоже на оригинал | ||
aac | 48.7 | cbr | 1.100 | 15 | Похоже на оригинал | ||
aac | 48.7 | abr | 1.100 | 15 | Похоже на оригинал | ||
aac | 48.7 | cvbr | 1.100 | 15 | Похоже на оригинал | ||
opus | 48.1 | cbr | 1.085 | 15 | Похоже на оригинал | ||
opus | 48.1 | cvbr | 1.085 | 15 | Похоже на оригинал | ||
opus | 48.1 | vbr | 1.085 | 15 | Похоже на оригинал | ||
aac | 48.7 | cbr | 1.100 | 15 | Идентично | ||
aac | 48.7 | abr | 1.100 | 15 | Идентично | ||
aac | 48.7 | cvbr | 1.100 | 15 | Идентично | ||
opus | 64.0 | cbr | 1.443 | 19 | Идентично | ||
opus | 64.0 | cvbr | 1.443 | 19 | Идентично | ||
opus | 64.0 | vbr | 1.443 | 19 | Идентично | ||
aac | 48.7 | cbr | 1.100 | 15 | Идентично | ||
aac | 48.7 | abr | 1.100 | 15 | Идентично | ||
aac | 48.7 | cvbr | 1.100 | 15 | Идентично | ||
opus | 96.2 | cbr | 2.170 | 30 | Идентично | ||
opus | 96.2 | cvbr | 2.170 | 30 | Идентично | ||
opus | 96.2 | vbr | 2.170 | 30 | Идентично | ||
aac | 0 | 118 | vbr | 2.660 | 36 | Идентично | |
aac | 7 | 52.4 | vbr | 1.184 | 16 | Похоже на оригинал | |
aac | 14 | 19.8 | vbr | 0.448 | 6 | Ужасно | |
3 - This Is Halloween.mp3 | mp3 | 192 | 4.647 | ||||
aac | 20.7 | cbr | 0.502 | 10 | Ужасно | ||
aac | 20.7 | abr | 0.502 | 10 | Ужасно | ||
aac | 20.7 | cvbr | 0.502 | 10 | Плохо | ||
opus | 14.4 | cbr | 0.349 | 7 | Плохо | ||
opus | 14.4 | cvbr | 0.349 | 7 | Плохо | ||
opus | 14.4 | vbr | 0.349 | 7 | Плохо | ||
aac | 32.7 | cbr | 0.793 | 17 | Средне | ||
aac | 32.7 | abr | 0.793 | 17 | Лучше среднего | ||
aac | 32.7 | cvbr | 0.793 | 17 | Похоже на оригинал | ||
opus | 30.6 | cbr | 0.741 | 15 | Лучше среднего | ||
opus | 30.6 | cvbr | 0.741 | 15 | Похоже на оригинал | ||
opus | 30.6 | vbr | 0.741 | 15 | Похоже на оригинал | ||
aac | 48.7 | cbr | 1.180 | 25 | Похоже на оригинал | ||
aac | 48.7 | abr | 1.180 | 25 | Похоже на оригинал | ||
aac | 48.7 | cvbr | 1.180 | 25 | Похоже на оригинал | ||
opus | 46.3 | cbr | 1.120 | 24 | Похоже на оригинал | ||
opus | 46.3 | cvbr | 1.120 | 24 | Похоже на оригинал | ||
opus | 46.3 | vbr | 1.120 | 24 | Похоже на оригинал | ||
aac | 48.7 | cbr | 1.180 | 25 | Идентично | ||
aac | 48.7 | abr | 1.180 | 25 | Идентично | ||
aac | 48.7 | cvbr | 1.180 | 25 | Идентично | ||
opus | 61.9 | cbr | 1.497 | 32 | Идентично | ||
opus | 61.9 | cvbr | 1.497 | 32 | Идентично | ||
opus | 61.9 | vbr | 1.497 | 32 | Идентично | ||
aac | 48.7 | cbr | 1.180 | 25 | Идентично | ||
aac | 48.7 | abr | 1.180 | 25 | Идентично | ||
aac | 48.7 | cvbr | 1.180 | 25 | Идентично | ||
opus | 94.3 | cbr | 2.282 | 49 | Идентично | ||
opus | 94.3 | cvbr | 2.282 | 49 | Идентично | ||
opus | 94.3 | vbr | 2.282 | 49 | Идентично | ||
aac | 0 | 117 | vbr | 2.844 | 61 | Идентично | |
aac | 7 | 51.4 | vbr | 1.244 | 26 | Похоже на оригинал | |
aac | 14 | 19.9 | vbr | 0.483 | 10 | Ужасно |
Спектрограмма (Рекомендую смотреть в отдельном окне)

Скрипт использованный для тестирования
#!/usr/bin/env fish
# ========== Configuration Variables ==========
set COMPRESSED ./compressed # Output directory for processed files
set SOURCE ./original # Input directory with source audio files
set CSV_TABLE encoding_info.csv # CSV table for storing info about source and compressed files
set BITRATES 16k 32k 48k 64k 96k # Target bitrates for encoding
set AAC_MODES cbr abr cvbr # AAC encoding modes
set AAC_QUALITY_VALUES 0 7 14 # AAC quality levels (0-14 scale)
set OPUS_MODES off constrained on # Opus encoding modes
set OPUS_MODES_STR cbr cvbr vbr # Human-friendly Opus mode names
# ========== Helper Functions ==========
function get_file_size -d "Calculate file size in MB"
math (stat -f %z "$argv") / 1024 / 1024
end
function get_codec -d "Detect audio codec"
set file $argv
ffprobe -v error -select_streams a:0 -show_entries stream=codec_name \
-of default=noprint_wrappers=1:nokey=1 $file
end
function get_bitrate -a file -d "Get audio bitrate in kbps"
set bitrate (mediainfo "$file" | grep "Overall bit rate" | string split : | string trim | tail -n 1)
echo "$bitrate"
end
# ========== Main Encoding Function ==========
function my_ffmpeg -d "Convert input and write record in csv table. -f <aac | opus>, only one option allowed -b or -a"
# Parse command-line arguments for encoding parameters
argparse \
'i/input=' \
'f/format=' \
'p/profile=' \
'c/codec=' \
'b/bitrate=' \
'q/quality=' \
'm/mode=' \
-- $argv
or return 1 # Exit if argument parsing fails
# Generate output filename with parameters
set output (path change-extension '' $_flag_i)
if set -q _flag_b
set output "$output"_b=$_flag_b
set option_b_q -b:a $_flag_b
else if set -q _flag_q
set output "$output"_q=$_flag_q
set option_b_q -q:a $_flag_q
end
set output "$output"_mode="$_flag_m"
# Format-specific configuration and output file extension
switch $_flag_f
case opus
set output "$output".opus
set option_mode -aac_at_mode $_flag_m
set field_mode $OPUS_MODES_STR[(contains -i $_flag_m $OPUS_MODES)]
case aac
set output "$output".m4a
set option_mode -vbr $_flag_m
set option_profile -profile:a $_flag_p
set field_mode $_flag_m
end
# Execute FFmpeg encoding command
echo "Conversion of $_flag_i to format $_flag_f"
ffmpeg -loglevel error -i "$_flag_i" \
-c:a $_flag_c $option_profile $option_b_q $option_mode \
-y \
$output
and begin # Record encoding results if successful
set size (get_file_size $output)
set source_size (get_file_size $_flag_i)
set relative_size (math -s 0 $size / $source_size \* 100)%
set target_bitrate (get_bitrate $output)
or set target_bitrate $_flag_b
if set -q _flag_b # Handle different quality/bitrate displays
set field_b_q $target_bitrate
else if set -q _flag_q
set field_b_q $_flag_q \($target_bitrate\)
end
# Append results to CSV file
echo ",$_flag_f,$field_b_q,$field_mode,$size,$relative_size,$output" >>$CSV_TABLE
end
or echo "Conversion of $_flag_i failed" # Error handling
end
# ========== Main Script Execution ==========
cd (dirname (status -f)) # Switch to script directory
# Validate directory structure
if not test -d $SOURCE
echo "Original directory not exist."
exit 1
end
if not test -d $COMPRESSED
mkdir $COMPRESSED
end
# Clean and prepare output directory
rm -r $COMPRESSED/*
cp -a $SOURCE/. $COMPRESSED
# Initialize CSV report
set files (find $COMPRESSED -type f -name '*.mp3')
echo "Original File,Codec,Bitrate or Quality,Encoding Mode,File Size,Relative size,Output File" >$CSV_TABLE
# Process all audio files
for file in $files
# Record source file metadata
set source_name (path basename $file)
set source_bitrate (get_bitrate $file)
set source_codec (get_codec $file)
set source_size (get_file_size $file)
echo "$source_name,$source_codec,$source_bitrate,,$source_size,," >>$CSV_TABLE
# Encode with different bitrates and modes
for bitrate in $BITRATES
# AAC encoding variants
for mode in $AAC_MODES
my_ffmpeg -i "$file" -f aac -c aac_at -b $bitrate -m $mode -p 28
end
# Opus encoding variants
for mode in $OPUS_MODES
my_ffmpeg -i "$file" -f opus -c libopus -b $bitrate -m $mode
end
end
# Additional AAC quality-based encodings
for q in $AAC_QUALITY_VALUES
my_ffmpeg -i "$file" -f aac -c aac_at -q $q -m vbr -p 28
end
end
# ========== Spectrogram Generation ==========
for audio in (find $COMPRESSED -type f \( -name '*.mp3' -o -name '*.m4a' -o -name '*.opus' \))
set spectogram $audio.png
echo "Generating spectrogram for $audio"
set spectrogram_options -n remix 1,2 spectrogram -x 200 -Y 130 -t "$(path basename $audio)"
# Try direct conversion first, fallback to WAV if needed
sox "$audio" $spectrogram_options -o "$spectogram" || begin
set tmp_wav (path change-extension wav $audio)
ffmpeg -loglevel error -i "$audio" "$tmp_wav"
sox "$tmp_wav" $spectrogram_options -o "$spectogram"
rm $tmp_wav
end
end
# ========== Spectrogram Aggregation ==========
# Combine individual spectrograms into composite images
set source_files (csvcut -x -c "Original File" $CSV_TABLE | tail -n +2)
for source_file in $source_files
set file (path change-extension '' $source_file)
set spectograms (gfind $COMPRESSED/ -type f -iregex ".*/"$file"\(_.*\|\.mp3\)?.png" | sort -V)
magick $spectograms -append "$COMPRESSED/spectogram_$file.png"
rm $spectograms
end
# Create final combined visualization
set spectograms (gfind $COMPRESSED/ -type f -name "spectogram_*.png" | sort -V)
magick $spectograms +append "$COMPRESSED/spectogram.avif"
rm $spectograms
Заключение
Какой формат выбрать? Выбирайте OPUS для наивысшей эффективности, а AAC для совместимости.
Какой битрейт выбрать?
Для HE-AACv2 и OPUS: От
32k
до96k
. Рекомендую64k
, но48k
тоже может звучать приемлемо.Для HE-AAC: От
64k
до160k
. Рекомендую128k
.
MP3 | AAC | HE-AAC | HE-AAC v2 | Opus |
---|---|---|---|---|
32 кбит/с | 32 кбит/с | 16 кбит/с | 8 кбит/с | 8 кбит/с |
64 кбит/с | 64 кбит/с | 32 кбит/с | 16 кбит/с | 16 кбит/с |
96 кбит/с | 96 кбит/с | 48 кбит/с | 24 кбит/с | 24 кбит/с |
128 кбит/с | 128 кбит/с | 64 кбит/с | 32 кбит/с | 32 кбит/с |
160 кбит/с | 160 кбит/с | 80 кбит/с | 40 кбит/с | 40 кбит/с |
192 кбит/с | 192 кбит/с | 96 кбит/с | 48 кбит/с | 48 кбит/с |
256 кбит/с | 256 кбит/с | 128 кбит/с | 64 кбит/с | 64 кбит/с |
320 кбит/с | 320 кбит/с | 160 кбит/с | 80 кбит/с | 80 кбит/с |
Таблица. Приблизительное сопоставление битрейта MP3 к AAC и OPUS
Какой кодек AAC выбрать? Если доступен выбирайте aac_at
, иначе libfdk_aac
.
Для AAC-LC:
aac_at
≥libfdk_aac
> (aac
).Для HE-AAC неясно, что лучше:
aac_at
илиlibfdk_aac
.
Какой профиль AAC выбрать? Выбирайте HE-AACv2.
Итог: Сжал 41 ГБ аудио в 8 ГБ (в 5 раз), при этом без заметной потери качества, переместив оригиналы в холодное хранилище.
Использованная команда:
# Сжатие аудио Opus с переменным битрейтом
# ПРИМЕЧАНИЕ: Битрейт 64k обеспечивает хорошее качество для большинства контента
$ffmpeg -i "$input" \
-c:a libopus -b:a 64k -vbr on \
-y "$output"
Сжатие видео
Есть два кодека эффективнее чем H.264/AVC это AV1 и H.265/HEVC.
Общие параметры видео кодеков
-c:v
- Устанавливает видео-кодек.-crf
- Устанавливает постоянный коэффициент скорости (Constant Rate Factor), где меньшие значения значат большее качество, а большие худшее.-preset
- Устанавливает пресет, балансирующую скорость кодирования и эффективность сжатия.-filter:v
(-vf
) - Фильтры:scale
- Изменяет разрешение видео.fps
- Изменяет количество кадров видео.
Перекодирование в AV1
Кодировщик доступен только один SVT-AV1 Encoder libsvtav1
.
Параметры libsvtav1
-preset <0..13>
(По умолчанию0
)-crf <0..63.0>
(По умолчанию0
)
Перекодирование в H.265/HEVC
Кодировщик доступен только один H.265/HEVC Encoder libx265
.
Параметры libx265
-preset
ultrafast
superfast
veryfast
faster
fast
medium
(по умолчанию)slow
slower
veryslow
placebo
-crf <0..51.0>
(По умолчанию28
)
Пример команд
# Использование кодека libsvtav1
ffmpeg -i input.mp4 -c:v libsvtav1 output.mkv
# Использование значение пресета 8
ffmpeg -i input.mp4 -c:v libsvtav1 -preset 8 output.mkv
# Использование значение CRF 28
ffmpeg -i input.mp4 -c:v libsvtav1 -crf 28 output.mkv
# Использование кодека libx265
ffmpeg -i input.mp4 -c:v libx265 output.mkv
# Использование значение пресета slow
ffmpeg -i input.mp4 -c:v libx265 -preset slow output.mkv
# Использование значение CRF 18
ffmpeg -i input.mp4 -c:v libx265 -preset veryslow -crf 18 output.mkv
# Изменение разрешения видео до 1280x720 и частоты кадров до 30 fps с
# использованием кодека libx265 и пресета slow
ffmpeg -i input.mp4 -vf "scale=1280:720,fps=30" -c:v libx265 -preset slow output.mkv
# Изменение разрешения видео до половины исходного разрешения
ffmpeg -i input.mp4 -vf "scale=iw/2:ih/2" output.mkv
# Изменение разрешения видео до максимальной ширины 1280, сохраняя соотношение сторон
ffmpeg -i input.mp4 -vf "scale='if(gt(iw,1280),1280,-1)':'if(gt(ih,720),720,-1)'" output.mkv
# Изменение частоты кадров до половины исходной частоты кадров
ffmpeg -i input.mp4 -vf "fps=source_fps/2" output.mkv
# Изменение разрешения видео до минимальной ширины 640 и частоты кадров до 24 fps
ffmpeg -i input.mp4 -vf "scale='if(lt(iw,640),640,iw)':'if(lt(ih,480),480,ih)',fps=24" output.mkv
Таблица сравнения кодеков
Имя | Кодек | Пресет | CRF | Масштаб | FPS | Время перекодирования (сек) | Размер (МБ) | Относительный размер (%) | PSNR | SSIM | VMAF |
---|---|---|---|---|---|---|---|---|---|---|---|
real ai spirited away | h264 | 720x1280 | 30/1 | 5.888 | 100 | ||||||
h265 | fast | 28 | 720x1280 (1) | 30/1 (1) | 2 | 0.567 | 9 | 42.186 | 0.982 | 87.448 | |
av1 | 10 | 34 | 720x1280 (1) | 30/1 (1) | 1 | 0.689 | 11 | 43.101 | 0.984 | 89.311 | |
h265 | fast | 28 | 720x1280 (1) | 15/1 (0.5) | 1 | 0.557 | 9 | 19.990 | 0.655 | 17.964 | |
av1 | 10 | 34 | 720x1280 (1) | 15/1 (0.5) | 1 | 0.523 | 8 | 18.815 | 0.632 | 13.923 | |
h265 | medium | 28 | 720x1280 (1) | 30/1 (1) | 2 | 0.600 | 10 | 42.569 | 0.983 | 88.203 | |
av1 | 8 | 34 | 720x1280 (1) | 30/1 (1) | 2 | 0.744 | 12 | 42.214 | 0.967 | 86.266 | |
h265 | medium | 28 | 720x1280 (1) | 15/1 (0.5) | 2 | 0.575 | 9 | 20.007 | 0.655 | 18.118 | |
av1 | 8 | 34 | 720x1280 (1) | 15/1 (0.5) | 1 | 0.538 | 9 | 19.572 | 0.646 | 16.711 | |
h265 | slow | 28 | 720x1280 (1) | 30/1 (1) | 4 | 0.639 | 10 | 43.313 | 0.985 | 90.463 | |
av1 | 6 | 34 | 720x1280 (1) | 30/1 (1) | 3 | 0.761 | 12 | 43.542 | 0.981 | 89.484 | |
h265 | slow | 28 | 720x1280 (1) | 15/1 (0.5) | 3 | 0.605 | 10 | 19.814 | 0.650 | 17.502 | |
av1 | 6 | 34 | 720x1280 (1) | 15/1 (0.5) | 2 | 0.541 | 9 | 19.093 | 0.636 | 15.308 | |
h265 | fast | 31 | 720x1280 (1) | 30/1 (1) | 2 | 0.419 | 7 | 40.614 | 0.975 | 83.179 | |
av1 | 10 | 38 | 720x1280 (1) | 30/1 (1) | 0 | 0.553 | 9 | 41.504 | 0.973 | 85.313 | |
h265 | fast | 31 | 720x1280 (1) | 15/1 (0.5) | 2 | 0.410 | 6 | 19.925 | 0.655 | 17.683 | |
av1 | 10 | 38 | 720x1280 (1) | 15/1 (0.5) | 1 | 0.420 | 7 | 19.875 | 0.653 | 17.371 | |
h265 | medium | 31 | 720x1280 (1) | 30/1 (1) | 2 | 0.436 | 7 | 40.824 | 0.977 | 83.759 | |
av1 | 8 | 38 | 720x1280 (1) | 30/1 (1) | 1 | 0.599 | 10 | 43.170 | 0.985 | 89.948 | |
h265 | medium | 31 | 720x1280 (1) | 15/1 (0.5) | 1 | 0.420 | 7 | 19.870 | 0.653 | 17.678 | |
av1 | 8 | 38 | 720x1280 (1) | 15/1 (0.5) | 1 | 0.430 | 7 | 18.906 | 0.635 | 14.490 | |
h265 | slow | 31 | 720x1280 (1) | 30/1 (1) | 5 | 0.463 | 7 | 41.522 | 0.979 | 86.903 | |
av1 | 6 | 38 | 720x1280 (1) | 30/1 (1) | 3 | 0.609 | 10 | 43.476 | 0.986 | 90.413 | |
h265 | slow | 31 | 720x1280 (1) | 15/1 (0.5) | 3 | 0.439 | 7 | 19.920 | 0.655 | 17.963 | |
av1 | 6 | 38 | 720x1280 (1) | 15/1 (0.5) | 2 | 0.437 | 7 | 19.327 | 0.641 | 15.881 | |
h265 | fast | 28 | 360x640 (0.5) | 30/1 (1) | 1 | 0.263 | 4 | 37.427 | 0.960 | 70.115 | |
av1 | 10 | 34 | 360x640 (0.5) | 30/1 (1) | 0 | 0.372 | 6 | 38.761 | 0.970 | 77.434 | |
h265 | fast | 28 | 360x640 (0.5) | 15/1 (0.5) | 1 | 0.259 | 4 | 19.747 | 0.656 | 16.204 | |
av1 | 10 | 34 | 360x640 (0.5) | 15/1 (0.5) | 1 | 0.286 | 4 | 19.765 | 0.655 | 16.061 | |
h265 | medium | 28 | 360x640 (0.5) | 30/1 (1) | 1 | 0.278 | 4 | 37.619 | 0.962 | 71.487 | |
av1 | 8 | 34 | 360x640 (0.5) | 30/1 (1) | 1 | 0.363 | 6 | 38.978 | 0.972 | 78.936 | |
h265 | medium | 28 | 360x640 (0.5) | 15/1 (0.5) | 1 | 0.272 | 4 | 19.696 | 0.654 | 16.120 | |
av1 | 8 | 34 | 360x640 (0.5) | 15/1 (0.5) | 1 | 0.275 | 4 | 18.785 | 0.637 | 13.222 | |
h265 | slow | 28 | 360x640 (0.5) | 30/1 (1) | 3 | 0.290 | 4 | 38.035 | 0.964 | 74.995 | |
av1 | 6 | 34 | 360x640 (0.5) | 30/1 (1) | 1 | 0.360 | 6 | 39.145 | 0.974 | 79.656 | |
h265 | slow | 28 | 360x640 (0.5) | 15/1 (0.5) | 1 | 0.278 | 4 | 19.666 | 0.653 | 16.152 | |
av1 | 6 | 34 | 360x640 (0.5) | 15/1 (0.5) | 1 | 0.272 | 4 | 19.637 | 0.653 | 16.043 | |
h265 | fast | 31 | 360x640 (0.5) | 30/1 (1) | 1 | 0.209 | 3 | 36.289 | 0.949 | 63.080 | |
av1 | 10 | 38 | 360x640 (0.5) | 30/1 (1) | 0 | 0.319 | 5 | 38.362 | 0.967 | 75.674 | |
h265 | fast | 31 | 360x640 (0.5) | 15/1 (0.5) | 1 | 0.207 | 3 | 19.591 | 0.654 | 15.332 | |
av1 | 10 | 38 | 360x640 (0.5) | 15/1 (0.5) | 1 | 0.244 | 4 | 19.732 | 0.655 | 15.904 | |
h265 | medium | 31 | 360x640 (0.5) | 30/1 (1) | 1 | 0.216 | 3 | 36.408 | 0.951 | 64.262 | |
av1 | 8 | 38 | 360x640 (0.5) | 30/1 (1) | 1 | 0.308 | 5 | 38.502 | 0.969 | 77.035 | |
h265 | medium | 31 | 360x640 (0.5) | 15/1 (0.5) | 1 | 0.212 | 3 | 19.713 | 0.657 | 15.761 | |
av1 | 8 | 38 | 360x640 (0.5) | 15/1 (0.5) | 1 | 0.234 | 3 | 19.805 | 0.659 | 16.189 | |
h265 | slow | 31 | 360x640 (0.5) | 30/1 (1) | 2 | 0.223 | 3 | 36.799 | 0.953 | 68.217 | |
av1 | 6 | 38 | 360x640 (0.5) | 30/1 (1) | 2 | 0.304 | 5 | 38.762 | 0.971 | 77.960 | |
h265 | slow | 31 | 360x640 (0.5) | 15/1 (0.5) | 1 | 0.216 | 3 | 19.590 | 0.654 | 15.642 | |
av1 | 6 | 38 | 360x640 (0.5) | 15/1 (0.5) | 1 | 0.234 | 3 | 19.228 | 0.644 | 14.684 | |
mini hogwarts | h264 | 832x1278 | 30/1 | 7.486 | 100 | ||||||
h265 | fast | 28 | 832x1278 (1) | 30/1 (1) | 3 | 0.641 | 8 | 43.339 | 0.985 | 84.796 | |
av1 | 10 | 34 | 832x1278 (1) | 30/1 (1) | 1 | 0.871 | 11 | 44.324 | 0.987 | 89.931 | |
h265 | fast | 28 | 832x1278 (1) | 15/1 (0.5) | 2 | 0.594 | 7 | 23.479 | 0.870 | 4.036 | |
av1 | 10 | 34 | 832x1278 (1) | 15/1 (0.5) | 1 | 0.633 | 8 | 22.457 | 0.844 | 3.272 | |
h265 | medium | 28 | 832x1278 (1) | 30/1 (1) | 4 | 0.671 | 8 | 43.694 | 0.986 | 86.123 | |
av1 | 8 | 34 | 832x1278 (1) | 30/1 (1) | 4 | 0.811 | 10 | 45.010 | 0.988 | 90.853 | |
h265 | medium | 28 | 832x1278 (1) | 15/1 (0.5) | 2 | 0.606 | 8 | 23.475 | 0.870 | 4.008 | |
av1 | 8 | 34 | 832x1278 (1) | 15/1 (0.5) | 3 | 0.570 | 7 | 22.650 | 0.852 | 3.482 | |
h265 | slow | 28 | 832x1278 (1) | 30/1 (1) | 10 | 0.749 | 9 | 44.527 | 0.987 | 89.510 | |
av1 | 6 | 34 | 832x1278 (1) | 30/1 (1) | 10 | 0.823 | 10 | 44.039 | 0.974 | 85.906 | |
h265 | slow | 28 | 832x1278 (1) | 15/1 (0.5) | 7 | 0.663 | 8 | 23.369 | 0.868 | 4.015 | |
av1 | 6 | 34 | 832x1278 (1) | 15/1 (0.5) | 7 | 0.572 | 7 | 22.755 | 0.855 | 3.575 | |
h265 | fast | 31 | 832x1278 (1) | 30/1 (1) | 4 | 0.451 | 6 | 41.952 | 0.981 | 79.255 | |
av1 | 10 | 38 | 832x1278 (1) | 30/1 (1) | 2 | 0.652 | 8 | 43.347 | 0.984 | 86.802 | |
h265 | fast | 31 | 832x1278 (1) | 15/1 (0.5) | 2 | 0.425 | 5 | 23.370 | 0.869 | 3.884 | |
av1 | 10 | 38 | 832x1278 (1) | 15/1 (0.5) | 1 | 0.482 | 6 | 23.554 | 0.871 | 4.061 | |
h265 | medium | 31 | 832x1278 (1) | 30/1 (1) | 3 | 0.467 | 6 | 41.869 | 0.981 | 79.842 | |
av1 | 8 | 38 | 832x1278 (1) | 30/1 (1) | 5 | 0.611 | 8 | 44.064 | 0.987 | 88.228 | |
h265 | medium | 31 | 832x1278 (1) | 15/1 (0.5) | 2 | 0.429 | 5 | 23.280 | 0.867 | 3.824 | |
av1 | 8 | 38 | 832x1278 (1) | 15/1 (0.5) | 3 | 0.434 | 5 | 23.443 | 0.872 | 4.000 | |
h265 | slow | 31 | 832x1278 (1) | 30/1 (1) | 10 | 0.518 | 6 | 42.844 | 0.983 | 84.439 | |
av1 | 6 | 38 | 832x1278 (1) | 30/1 (1) | 13 | 0.621 | 8 | 44.742 | 0.988 | 89.565 | |
h265 | slow | 31 | 832x1278 (1) | 15/1 (0.5) | 6 | 0.466 | 6 | 23.346 | 0.868 | 3.914 | |
av1 | 6 | 38 | 832x1278 (1) | 15/1 (0.5) | 7 | 0.438 | 5 | 22.750 | 0.855 | 3.544 | |
h265 | fast | 28 | 416x640 (0.5) | 30/1 (1) | 2 | 0.265 | 3 | 39.338 | 0.974 | 64.721 | |
av1 | 10 | 34 | 416x640 (0.5) | 30/1 (1) | 0 | 0.388 | 5 | 40.029 | 0.977 | 71.285 | |
h265 | fast | 28 | 416x640 (0.5) | 15/1 (0.5) | 1 | 0.252 | 3 | 23.366 | 0.871 | 3.197 | |
av1 | 10 | 34 | 416x640 (0.5) | 15/1 (0.5) | 1 | 0.288 | 3 | 23.450 | 0.872 | 3.385 | |
h265 | medium | 28 | 416x640 (0.5) | 30/1 (1) | 1 | 0.268 | 3 | 39.197 | 0.973 | 64.748 | |
av1 | 8 | 34 | 416x640 (0.5) | 30/1 (1) | 2 | 0.335 | 4 | 40.391 | 0.979 | 72.749 | |
h265 | medium | 28 | 416x640 (0.5) | 15/1 (0.5) | 1 | 0.255 | 3 | 23.272 | 0.869 | 3.132 | |
av1 | 8 | 34 | 416x640 (0.5) | 15/1 (0.5) | 1 | 0.254 | 3 | 23.454 | 0.874 | 3.443 | |
h265 | slow | 28 | 416x640 (0.5) | 30/1 (1) | 3 | 0.290 | 3 | 39.804 | 0.976 | 69.580 | |
av1 | 6 | 34 | 416x640 (0.5) | 30/1 (1) | 4 | 0.332 | 4 | 40.611 | 0.980 | 74.124 | |
h265 | slow | 28 | 416x640 (0.5) | 15/1 (0.5) | 2 | 0.271 | 3 | 23.343 | 0.870 | 3.252 | |
av1 | 6 | 34 | 416x640 (0.5) | 15/1 (0.5) | 2 | 0.252 | 3 | 23.450 | 0.874 | 3.450 | |
h265 | fast | 31 | 416x640 (0.5) | 30/1 (1) | 1 | 0.204 | 2 | 38.239 | 0.968 | 57.526 | |
av1 | 10 | 38 | 416x640 (0.5) | 30/1 (1) | 1 | 0.304 | 4 | 39.529 | 0.975 | 67.564 | |
h265 | fast | 31 | 416x640 (0.5) | 15/1 (0.5) | 1 | 0.195 | 2 | 23.364 | 0.872 | 3.064 | |
av1 | 10 | 38 | 416x640 (0.5) | 15/1 (0.5) | 1 | 0.229 | 3 | 23.364 | 0.871 | 3.192 | |
h265 | medium | 31 | 416x640 (0.5) | 30/1 (1) | 1 | 0.205 | 2 | 38.064 | 0.967 | 57.008 | |
av1 | 8 | 38 | 416x640 (0.5) | 30/1 (1) | 2 | 0.267 | 3 | 39.797 | 0.977 | 69.214 | |
h265 | medium | 31 | 416x640 (0.5) | 15/1 (0.5) | 1 | 0.195 | 2 | 23.277 | 0.870 | 2.930 | |
av1 | 8 | 38 | 416x640 (0.5) | 15/1 (0.5) | 1 | 0.206 | 2 | 23.455 | 0.875 | 3.334 | |
h265 | slow | 31 | 416x640 (0.5) | 30/1 (1) | 2 | 0.216 | 2 | 38.754 | 0.970 | 63.000 | |
av1 | 6 | 38 | 416x640 (0.5) | 30/1 (1) | 3 | 0.267 | 3 | 40.139 | 0.978 | 71.137 | |
h265 | slow | 31 | 416x640 (0.5) | 15/1 (0.5) | 2 | 0.206 | 2 | 23.336 | 0.871 | 3.073 | |
av1 | 6 | 38 | 416x640 (0.5) | 15/1 (0.5) | 3 | 0.205 | 2 | 23.363 | 0.873 | 3.323 |
Как интерпретировать метрики
PSNR (Peak Signal-to-Noise Ratio) — метрика, измеряющая отношение максимального возможного значения сигнала к мощности коррумпирующего шума. Большие значения указывают на лучшее качество сжатия.
SSIM (Structural Similarity Index) — метрика, измеряющая структурное сходство между оригинальным и сжатым изображением. Значения ближе к 1 указывают на большее сходство, то есть лучшее качество сжатия.
VMAF (Video Multimethod Assessment Fusion) — метрика, объединяющая несколько методов оценки качества видео для получения более точной оценки. Большие значения указывают на лучшее качество сжатия.
Скрипт использованный для тестирования
#!/usr/bin/env fish
# Переход в директорию, где находится скрипт
cd (dirname (status -f))
# Установка переменных для сжатых и оригинальных файлов
set COMPRESSED ./compressed
set ORIGINAL ./original
# Проверка существования оригинальной директории
if not test -d $ORIGINAL
echo "Оригинальная директория не существует."
exit 1
end
# Создание директории для сжатых файлов, если она не существует
if not test -d $COMPRESSED
mkdir $COMPRESSED
end
# Очистка директории для сжатых файлов
rm -r $COMPRESSED/*
# Копирование всех файлов из оригинальной директории в директорию для сжатых файлов
cp -a $ORIGINAL/. $COMPRESSED
# Установка значений CRF для кодеков H.265 и AV1
set crf_h265 28 31
set crf_av1 34 38
# Установка значений масштабирования
set scale_values 1 0.5
# Установка пресетов для кодеков H.265 и AV1
set preset_h265 fast medium slow # veryslow
set preset_av1 10 8 6 # 2 0
# Установка кодеков и их соответствующих энкодеров
set codec h265 av1
set encoder libx265 libsvtav1
# Установка значений масштабирования FPS
set fps_scales 1 0.5
# Установка времени для кодирования
set time 0:05
# Создание псевдонимов для получения FPS, кодека и разрешения видео
alias get_fps="ffprobe -v error -select_streams v:0 -show_entries stream=r_frame_rate -of default=noprint_wrappers=1:nokey=1"
alias get_codec="ffprobe -v error -select_streams v:0 -show_entries stream=codec_name -of default=noprint_wrappers=1:nokey=1"
alias get_resolution="ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=s=x:p=0"
# Функция для получения размера файла в МБ
function get_file_size
math (stat -f %z "$argv") / 1024 / 1024
end
# Подготовка заголовка для CSV файла с логами сжатия
echo "Name,Codec,Preset,CRF,Scale,FPS,Duration (s),Size (MB),Relative file size (%), PSNR,SSIM,VMAF,Filename" >compression_log.csv
# Цикл по расширениям видеофайлов
for ext in mov mp4
# Цикл по файлам в директории для сжатых файлов
for file in (gfind $COMPRESSED -type f -not -name "*_*.*" -iname "*.$ext")
# Получение FPS, кодека, разрешения и размера исходного файла
set source_fps (get_fps "$file")
set source_codec (get_codec "$file")
set source_resolution (get_resolution "$file")
set source_file_size (get_file_size "$file")
set name (path basename $file | path change-extension '')
# Запись информации об исходном файле в CSV файл
echo "$name,$source_codec,,,$source_resolution,$source_fps,,$source_file_size,100%,,,,$file" >>compression_log.csv
# Цикл по значениям масштабирования
for scale in $scale_values
# Цикл по значениям CRF для H.265
for crf_idx in (seq 1 (count $crf_h265))
# Цикл по пресетам для H.265
for preset_idx in (seq 1 (count $preset_h265))
# Цикл по значениям масштабирования FPS
for fps_scale in $fps_scales
# Вычисление нового FPS
set fps (math $source_fps \* $fps_scale)
# Цикл по кодекам
for encoder_idx in (seq 1 (count $codec))
# Установка кодека и энкодера
set cod $codec[$encoder_idx]
set enco $encoder[$encoder_idx]
# Установка параметров в зависимости от кодека
switch $cod
case h265
set crf $crf_h265[$crf_idx]
set preset $preset_h265[$preset_idx]
set vtag -vtag hvc1
case av1
set crf $crf_av1[$crf_idx]
set preset $preset_av1[$preset_idx]
set vtag
end
# Формирование нового имени файла
set new_file (path change-extension '' $file)_"$cod"_crf="$crf"_preset="$preset"_scale="$scale"_fps="$fps".mp4
set start_time (date +%s)
# Кодирование видео с указанными параметрами
ffmpeg-bar -i "$file" -t $time \
-c:v "$enco" -crf $crf \
-vf "fps=source_fps*$fps_scale,scale=iw*$scale:-2" \
$vtag -preset "$preset" \
-hide_banner -loglevel error \
"$new_file"
# Вычисление времени кодирования
set end_time (date +%s)
set duration (math $end_time-$start_time)
# Получение размера нового файла и относительного размера
set file_size (get_file_size "$new_file")
set relative_file_size (math -s 0 $file_size / $source_file_size \* 100)%
# Получение разрешения и FPS нового файла
set output_resolution (get_resolution "$new_file")
set output_fps (get_fps "$new_file")
# Вычисление метрик качества
ffmpeg-quality-metrics $new_file $file --metrics psnr ssim vmaf -p -r (string sub $output_fps -e -2) >log.json
# Получение значений метрик качества
set psnr_value (jq '.global.psnr.psnr_avg.average' log.json)
set ssim_value (jq '.global.ssim.ssim_avg.average' log.json)
set vmaf_value (jq '.global.vmaf.vmaf.average' log.json)
# Запись результатов в CSV файл
echo ",$cod,$preset,$crf,$output_resolution ($scale),$output_fps ($fps_scale),$duration,$file_size,$relative_file_size,$psnr_value,$ssim_value,$vmaf_value,$new_file" >>compression_log.csv
end
end
end
end
end
# Удаление временных файлов
rm {psnr,ssim,vmaf}_stats.log "$file"
end
end
Заключение
Какой кодек выбрать? Если ваша целевая платформа поддерживает AV1, то выбирайте его. Так как формат AV1 имеет меньшую поддержку, но большую эффективность сжатия чем H.265/HEVC.
Параметр |
|
|
---|---|---|
Уменьшение fps в n раз | 1 + 0.05n | 1 + [0.05, 0.15]n |
Уменьшение scale в n раз | n | n |
Увеличение crf на n | 1 + ≈0.025n | 1 + ≈0.025n |
Увеличение preset на n | 1 - ≈0.025n | 1 - ≈0.025n |
Таблица: Насколько сильно изменение параметра влияет на результирующий размер файла (От самого качественного до не качественного). Как понимать? В ячейке написано число на которое нужно делить размер файла, то есть $\frac {\text{размер файла}}{\text{значение в ячейке или } f(n)}$
Какое разрешение scale
выбрать? Самое маленькое, которое вас удовлетворяет. Так его оно прямо пропорционально размеру файла.
Какое количество кадров fps
выбрать? Исходное, так как имеет минимальное влияние на размер файла.
Какой пресет -preset
выбрать? medium
(libx265
) или 8
(libsvtav1
). Но можете выбрать самый медленный, который способны терпеть. Так как время перекодирования быстро увеличивается в несколько раз. Игнорируйте placebo
(libx265
) или 0
(libsvtav1
), так как они дают незначительные улучшения за значительное увеличение времени кодирования.
Какое значение CRF -crf
выбрать? Значения CRF различаются в зависимости от выбранного пресета. Например для кодировщика libx265
пресет slower
генерирует больше сжатия/бит, но может увеличить размер файла по сравнению с medium
с одним и тем же значением CRF. Если сравнить ultrafast
с veryslow
при одинаковом значении CRF, veryslow
может создать файл большего размера с лучшим общим сжатием. Например, если -preset ultrafast
с -crf 15
генерирует файл сравнимого размера с -preset veryslow
с -crf 20
, файл с пресетом veryslow
будет иметь лучшее качество при том же размере файла.
libx265
: от23
до31
приmedium
.libsvtav1
: от30
до38
при8
.
В итоге: Я ужал видео разной степени сжатости в сумме на 30 ГБ в 12 ГБ (почти в 3 раза), без заметной потери в качестве, не сохраняя оригиналы.
Использованная команда:
# ПРОИЗВОДИТЕЛЬНОСТЬ:
# - `nice -n 20 ` - снижение приоритета процесса
# - `cpulimit -l 2` - ограничение использования ЦП. Для меня это занимает 90% ЦП
sudo nice -n 20 cpulimit -l 2 -i \
$ffmpeg -i "$input" \
-c:v libx265 -crf 31 -vf "scale='min(iw,1024)':-2" \
-pix_fmt yuv420p \
-c:a aac -b:a 128k -vtag hvc1 \
-y "$output"
# Если вы используете ffmpeg вместо ffmpeg-bar.
# Добавьте эти опции для уменьшения вывода логов
# -loglevel error -x265-params log-level=error
Сжатие изображений
Есть один кодек эффективнее JPG это AVIF.
Общие параметры для изображений
-quality <0-100>
- Устанавливает качество изображения, где меньшие значения означают меньшее качество, а большие — лучшее.
Примеры команд
# Конвертация JPG в AVIF
magick input.jpg output.avif
# Конвертация BMP в AVIF с качеством 80
magick input.bmp -quality 80 output.avif
Таблица сравнения кодеков
Имя | Формат | Качество | Размер (МБ) | Относительный размер (%) | AE | DSSIM | FUZZ | MAE | MEPP | MSE | NCC | PAE | PHASH | PSNR | RMSE | SSIM |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
eye | avif | 0,068 | 14 | 503 953 | 0,044 | 1 570,390 | 1 178,290 | 1 791 120 000 | 37,631 | 0,994 | 13 107 | 1,591 | 32,409 | 1 570,390 | 0,912 | |
eye | avif | 0 | 0,068 | 14 | 503 953 | 0,044 | 1 570,390 | 1 178,290 | 1 791 120 000 | 37,631 | 0,994 | 13 107 | 1,591 | 32,409 | 1 570,390 | 0,912 |
eye | avif | 1 | 0,005 | 1 | 506 565 | 0,239 | 5 232,840 | 3 679,830 | 5 593 710 000 | 417,832 | 0,935 | 53 713 | 13,912 | 21,955 | 5 232,840 | 0,522 |
eye | avif | 25 | 0,024 | 5 | 504 862 | 0,110 | 2 833,160 | 2 066,130 | 3 140 730 000 | 122,481 | 0,981 | 25 957 | 3,983 | 27,284 | 2 833,160 | 0,780 |
eye | avif | 50 | 0,068 | 14 | 503 953 | 0,044 | 1 570,390 | 1 178,290 | 1 791 120 000 | 37,631 | 0,994 | 13 107 | 1,591 | 32,409 | 1 570,390 | 0,912 |
eye | avif | 75 | 0,140 | 29 | 497 317 | 0,017 | 909,873 | 678,263 | 1 031 030 000 | 12,632 | 0,998 | 8 224 | 1,041 | 37,150 | 909,873 | 0,966 |
eye | avif | 100 | 0,438 | 92 | 377 031 | 0,003 | 453,631 | 262,135 | 398 471 000 | 3,140 | 0,999 | 5 911 | 1,313 | 43,195 | 453,631 | 0,994 |
eye | webp | 0,676 | 142 | 0 | 0,000 | 0,000 | 0,000 | 0 | 0,000 | 1,000 | 0 | 0,000 | 0,000 | 0,000 | 1,000 | |
eye | webp | 0 | 0,097 | 20 | 503 328 | 0,033 | 1 389,280 | 1 034,390 | 1 572 370 000 | 29,451 | 0,995 | 10 023 | 2,106 | 33,474 | 1 389,280 | 0,935 |
eye | webp | 1 | 0,018 | 3 | 506 333 | 0,162 | 3 744,970 | 2 637,210 | 4 008 830 000 | 214,005 | 0,967 | 32 639 | 4,981 | 24,860 | 3 744,970 | 0,677 |
eye | webp | 25 | 0,046 | 9 | 505 148 | 0,077 | 2 379,720 | 1 717,390 | 2 610 600 000 | 86,412 | 0,987 | 20 560 | 2,679 | 28,799 | 2 379,720 | 0,846 |
eye | webp | 50 | 0,071 | 14 | 504 073 | 0,050 | 1 782,190 | 1 314,580 | 1 998 290 000 | 48,466 | 0,992 | 14 649 | 1,538 | 31,310 | 1 782,190 | 0,901 |
eye | webp | 75 | 0,097 | 20 | 503 328 | 0,033 | 1 389,280 | 1 034,390 | 1 572 370 000 | 29,451 | 0,995 | 10 023 | 2,106 | 33,474 | 1 389,280 | 0,935 |
eye | webp | 100 | 0,676 | 142 | 0 | 0,000 | 0,000 | 0,000 | 0 | 0,000 | 1,000 | 0 | 0,000 | 0,000 | 0,000 | 1,000 |
eye | jxl | 0,149 | 31 | 500 688 | 0,022 | 1 129,770 | 771,809 | 1 173 230 000 | 19,476 | 0,996 | 18 247 | 0,646 | 35,270 | 1 129,770 | 0,957 | |
eye | jxl | 0 | 0,149 | 31 | 500 688 | 0,022 | 1 129,770 | 771,809 | 1 173 230 000 | 19,476 | 0,996 | 18 247 | 0,646 | 35,270 | 1 129,770 | 0,957 |
eye | jxl | 1 | 0,149 | 31 | 500 688 | 0,022 | 1 129,770 | 771,809 | 1 173 230 000 | 19,476 | 0,996 | 18 247 | 0,646 | 35,270 | 1 129,770 | 0,957 |
eye | jxl | 25 | 0,032 | 6 | 503 549 | 0,109 | 2 964,920 | 2 048,390 | 3 113 760 000 | 134,138 | 0,978 | 33 924 | 1,670 | 26,889 | 2 964,920 | 0,783 |
eye | jxl | 50 | 0,043 | 9 | 503 385 | 0,086 | 2 537,160 | 1 775,410 | 2 698 800 000 | 98,225 | 0,984 | 24 672 | 4,479 | 28,242 | 2 537,160 | 0,829 |
eye | jxl | 75 | 0,077 | 16 | 502 710 | 0,050 | 1 827,870 | 1 294,900 | 1 968 380 000 | 50,982 | 0,991 | 19 275 | 2,444 | 31,091 | 1 827,870 | 0,900 |
eye | jxl | 100 | 0,613 | 129 | 0 | 0,000 | 0,000 | 0,000 | 0 | 0,000 | 1,000 | 0 | 0,000 | 0,000 | 0,000 | 1,000 |
eye | heic | 0,106 | 22 | 499 596 | 0,024 | 1 155,190 | 857,095 | 1 302 870 000 | 20,363 | 0,997 | 12 079 | 3,690 | 35,076 | 1 155,190 | 0,952 | |
eye | heic | 0 | 0,106 | 22 | 499 596 | 0,024 | 1 155,190 | 857,095 | 1 302 870 000 | 20,363 | 0,997 | 12 079 | 3,690 | 35,076 | 1 155,190 | 0,952 |
eye | heic | 1 | 0,004 | 0 | 505 350 | 0,256 | 5 602,910 | 3 890,270 | 5 913 600 000 | 479,020 | 0,925 | 53 456 | 6,704 | 21,361 | 5 602,910 | 0,487 |
eye | heic | 25 | 0,026 | 5 | 504 647 | 0,104 | 2 785,230 | 2 016,690 | 3 065 580 000 | 118,372 | 0,982 | 23 901 | 9,343 | 27,432 | 2 785,230 | 0,791 |
eye | heic | 50 | 0,106 | 22 | 499 596 | 0,024 | 1 155,190 | 857,095 | 1 302 870 000 | 20,363 | 0,997 | 12 079 | 3,690 | 35,076 | 1 155,190 | 0,952 |
eye | heic | 75 | 0,275 | 58 | 458 907 | 0,005 | 547,403 | 369,373 | 561 484 000 | 4,572 | 0,999 | 7 196 | 0,950 | 41,563 | 547,403 | 0,989 |
eye | heic | 100 | 0,461 | 97 | 377 031 | 0,003 | 453,631 | 262,135 | 398 471 000 | 3,140 | 0,999 | 5 911 | 1,313 | 43,195 | 453,631 | 0,994 |
Как интерпретировать метрики
AE (Absolute Error) — абсолютная ошибка между оригинальным и сжатым изображением. Меньшие значения указывают на лучшее качество сжатия.
DSSIM (Structural Dissimilarity Index) — метрика, измеряющая структурное несходство между изображениями. Значения ближе к 0 указывают на большее сходство, то есть лучшее качество сжатия.
FUZZ — метрика, измеряющая степень “размытости” или нечеткости изображения. Меньшие значения указывают на меньшую размытость и лучшее качество сжатия.
MAE (Mean Absolute Error) — средняя абсолютная ошибка между пикселями оригинального и сжатого изображений. Меньшие значения указывают на лучшее качество сжатия.
MEPP (Mean Edge Pixel Percentage) — средний процент пикселей, расположенных на границах объектов. Значения ближе к оригинальному изображению указывают на лучшее сохранение деталей.
MSE (Mean Squared Error) — средняя квадратичная ошибка между пикселями оригинального и сжатого изображений. Меньшие значения указывают на лучшее качество сжатия.
NCC (Normalized Cross-Correlation) — нормализованная кросс-корреляция между оригинальным и сжатым изображениями. Значения ближе к 1 указывают на большее сходство и лучшее качество сжатия.
PAE (Peak Absolute Error) — максимальная абсолютная ошибка между пикселями оригинального и сжатого изображений. Меньшие значения указывают на лучшее качество сжатия.
PHASH (Perceptual Hash) — перцептивный хеш, измеряющий визуальное сходство между изображениями. Меньшие значения указывают на большее сходство и лучшее качество сжатия.
PSNR (Peak Signal-to-Noise Ratio) — пиковое соотношение сигнал/шум, измеряющее качество сжатия. Большие значения указывают на лучшее качество сжатия.
RMSE (Root Mean Squared Error) — среднеквадратичная ошибка между пикселями оригинального и сжатого изображений. Меньшие значения указывают на лучшее качество сжатия.
SSIM (Structural Similarity Index) — структурный индекс сходства, измеряющий визуальное качество сжатия. Значения ближе к 1 указывают на лучшее качество сжатия.
Скрипт использованный для тестирования
#!/usr/bin/env fish
# Переход в директорию, где находится скрипт
cd (dirname (status -f))
# Установка переменных для сжатых и оригинальных файлов
set COMPRESSED ./compressed
set ORIGINAL ./original
# Проверка существования оригинальной директории
if not test -d $ORIGINAL
echo "Оригинальная директория не существует."
exit 1
end
# Создание директории для сжатых файлов, если она не существует
if not test -d $COMPRESSED
mkdir $COMPRESSED
end
# Очистка директории для сжатых файлов
rm -r $COMPRESSED/*
# Копирование всех файлов из оригинальной директории в директорию для сжатых файлов
cp -a $ORIGINAL/. $COMPRESSED
# Установка значений качества для сжатия
set quality_values "" 0 1 25 50 75 100
# Установка форматов изображений для сжатия
set formats avif webp jxl heic
# Функция для получения размера файла в МБ
function get_file_size
math (stat -f %z "$argv") / 1024 / 1024
end
# Установка метрик для оценки качества сжатия
set metrics AE DSSIM FUZZ MAE MEPP MSE NCC PAE PHASH PSNR RMSE SSIM
# Объединение метрик в одну строку, разделенную запятыми
set joined_metrics (string join "," $metrics)
# Подготовка заголовка для CSV файла с логами сжатия
echo "Name,Format,Quality,Size (MB),Relative Size,$joined_metrics,Filename" >compression_log.csv
# Цикл по расширениям изображений
for ext in png jpg jpeg
# Цикл по файлам в директории для сжатых файлов
for file in (gfind $COMPRESSED -type f -not -name "*_*.*" -iname "*.$ext")
# Получение размера оригинального файла
set original_size (get_file_size "$file")
# Цикл по форматам изображений
for format in $formats
# Цикл по значениям качества
for quality in $quality_values
# Формирование нового имени файла
set new_file (path change-extension '' $file)_"$format"_quality="$quality".$format
set start_time (date +%s)
# Сжатие изображения с указанным качеством
if test -n "$quality"
magick "$file" -quality $quality "$new_file"
else
magick "$file" "$new_file"
end
# Вычисление времени сжатия
set end_time (date +%s)
set duration (math $end_time-$start_time)
# Получение размера нового файла
set file_size (get_file_size "$new_file")
# Вычисление относительного размера нового файла
set relative_size (math -s 0 $file_size / $original_size \* 100)%
# Вычисление метрик качества
set -e metrics_value
for metric in $metrics
set metric_value (compare -metric $metric "$new_file" "$file" null: 2>&1 | awk '{print $1}')
set metrics_value $metrics_value $metric_value
end
# Запись результатов в CSV файл
set name (path basename $file | path change-extension '')
set metrics_value_joined (string join "," $metrics_value)
echo "$name,$format,$quality,$file_size,$relative_size,$metrics_value_joined,$new_file" >>compression_log.csv
end
end
end
end
Заключение
Какое качество -quality
выбрать? Никакое. Качество картинки и так нормальное.
В итоге: Я ужал изображения в сумме на 10 ГБ в 1 ГБ (в 10 раз), без заметной потери в качестве, без сохранения оригиналов.
Использованная команда:
magick $input $output
Вывод
Существуют более эффективные альтернативы распространенным сейчас кодекам, которыми можно уже пользоваться или ждать пока они распространятся.
Используя их я смог уменьшить размер медиа файлов на горячем хранилище с ≈150 ГБ в ≈30 ГБ (5 раз). Уменьшив головную боль из за неопределенности определения, что мне нужно, а что можно переместить в холодное хранилище.
case audio
# Сжатие аудио Opus с переменным битрейтом
# ПРИМЕЧАНИЕ: Битрейт 64k обеспечивает хорошее качество для большинства контента
$ffmpeg -i "$input" \
-c:a libopus -b:a 64k -vbr on \
-y "$output"
case video
# ПРОИЗВОДИТЕЛЬНОСТЬ:
# - `nice -n 20 ` - снижение приоритета процесса
# - `cpulimit -l 2` - ограничение использования ЦП. Для меня это занимает 90% ЦП
sudo nice -n 20 cpulimit -l 2 -i \
$ffmpeg -i "$input" \
-c:v libx265 -crf 31 -vf "scale='min(iw,1024)':-2" \
-pix_fmt yuv420p \
-c:a aac -b:a 128k -vtag hvc1 \
-y "$output"
# Если вы используете ffmpeg вместо ffmpeg-bar.
# Добавьте эти опции для уменьшения вывода логов
# -loglevel error -x265-params log-level=error
case image
magick $input $output
Как можно использовать это
Хранение медиа на устройствах с ограниченным цифровым хранилищем (такие как телефоны)
Оттягивание той черты когда придется выгружать данные с горячего хранилища в холодное
Дальше сами…
Скрипт для конвертации медиа файлов
# ===================== Утилита сжатия медиа =============================
# Полнофункциональный инструмент для сжатия медиафайлов, поддерживающий аудио, видео и изображения
# Использует современные кодеки (Opus, HEVC, AVIF) с разумными настройками по умолчанию для качества/размера
# ==============================================================================
function compress_media
# ===================== КОНСТАНТЫ ============================
# ВНИМАНИЕ: Порядок имеет значение - индексы должны совпадать между этими двумя массивами
set FILE_TYPES audio video image # Поддерживаемые типы медиа
set FILE_TYPE_EXTENSIONS opus mp4 avif # Соответствующие расширения файлов для типов медиа
# ПРИМЕЧАНИЕ: Вы можете использовать разные команды для ffmpeg
# - ffmpeg - подробный вывод. Измените функцию compress_file для управления уровнем логирования
# - ffmpeg-bar - индикатор прогресса
set -g ffmpeg ffmpeg-bar
# ===================== ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ============================
function bold -d "Применить жирное оформление к тексту"
if not count $argv >/dev/null # Хак: Чтение из pipe в argv
read --null --list --delimiter=\n argv
end
echo "\e[1m$argv\e[22m"
end
function yellow -d "Применить желтый цвет к тексту"
if not count $argv >/dev/null # Хак: Чтение из pipe в argv
read --null --list --delimiter=\n argv
end
echo "\e[0;33m$argv\e[39m"
end
function red -d "Применить красный цвет к тексту"
if not count $argv >/dev/null # Хак: Чтение из pipe в argv
read --null --list --delimiter=\n argv
end
echo "\e[0;31m$argv\e[39m"
end
function file_type -a file -d "Определить категорию типа файла из mime-типа"
set -l mime_type (file --mime-type -b "$file")
echo (string split '/' $mime_type)[1]
end
function compress_file -a input -a output -d "Сжать файл"
switch (file_type $input)
case audio
# Сжатие аудио Opus с переменным битрейтом
# ПРИМЕЧАНИЕ: Битрейт 64k обеспечивает хорошее качество для большинства контента
$ffmpeg -i "$input" \
-c:a libopus -b:a 64k -vbr on \
-y "$output"
case video
# ПРОИЗВОДИТЕЛЬНОСТЬ:
# - `nice -n 20 ` - снижение приоритета процесса
# - `cpulimit -l 2` - ограничение использования ЦП. Для меня это занимает 90% ЦП
sudo nice -n 20 cpulimit -l 2 -i \
$ffmpeg -i "$input" \
-c:v libx265 -crf 31 -vf "scale='min(iw,1024)':-2" \
-pix_fmt yuv420p \
-c:a aac -b:a 128k -vtag hvc1 \
-y "$output"
# Если вы используете ffmpeg вместо ffmpeg-bar.
# Добавьте эти опции для уменьшения вывода логов
# -loglevel error -x265-params log-level=error
case image
magick $input $output
end
tput smam # ВНИМАНИЕ: Ручной вызов tput для исправления отключения обертывания терминала из ffmpeg-bar
end
function is_compressed -a item -d "Проверить, содержит ли имя файла маркер '.compressed.'"
string match -q '*.compressed.*' (path basename $item)
end
# ===================== Аргументы командной строки ============================
argparse \
h/help \
'm/media-type=+' \
't/target=' \
k/keep-original \
only-media \
-- $argv
if not string match -rq "GNU findutils" (find --version 2>/dev/null) # Требуется GNU find, а не BSD
echo -e (red "Требуется GNU find, а не BSD.")
return 1
end
if set -q _flag_h # Отобразить сообщение справки, если true
echo "\
compress_media - Сжатие аудио, видео или изображений в указанных директориях
Использование:
compress_media [ОПЦИИ]... [ИСТОЧНИКИ]...
Описание:
Рекурсивно сжимает медиафайлы (аудио/видео/изображения) в указанных директориях.
По умолчанию заменяет оригиналы сжатыми версиями (перемещает в корзину, если не используется -k).
Используйте --target для сохранения структуры директорий в другом месте.
Опции:
-h, --help Показать это сообщение справки и выйти
-m, --media-type TYPE ОБЯЗАТЕЛЬНО. Тип медиа для обработки (audio|video|image|all)
-t, --target DIR Директория вывода для сжатых файлов (сохраняет структуру). Относительный путь должен начинаться с `./`, иначе это будет `/`.
-k, --keep-original Сохранить оригинальные файлы (не перемещать в корзину)
--only-media Обрабатывать только медиафайлы (не копировать другие файлы в целевую директорию)
Поведение:
- При использовании --target:
* Создает зеркальную структуру директорий в целевом месте
* Копирует немедиафайлы без изменений, если не указано --only-media
* Хранит сжатые версии медиафайлов в соответствующих директориях
- Без --target:
* Сжимает файлы на месте
* Оригинальные файлы перемещаются в корзину, если не указано --keep-original
Поддерживаемые форматы:
- audio: конвертируется в OPUS (64kbit/s, VBR)
- video: конвертируется в H.265 MP4 (CRF 31, ширина 1024px)
- image: конвертируется в AVIF с использованием настроек ImageMagick по умолчанию
Примеры:
1. Сжатие аудиофайлов в текущей директории:
compress_media -m audio
2. Сжатие видео в ~/Videos, сохранение оригиналов, вывод в ~/Compressed:
compress_media -m video -t ~/Compressed -k ~/Videos
3. Сжатие изображений в ~/Pictures, обработка только медиафайлов:
compress_media -m image --only-media -t ~/Compressed_Images ~/Pictures
Примечания:
- Требуется ffmpeg, ImageMagick, trash и coreutils (для определения типа файла)
- Вы должны использовать GNU find на MacOS. Сделайте `alias find=gfind` перед
запуском скрипта
- Сжатие видео использует ограничение ЦП для стабильности
- Сжатые файлы получают расширение .compressed перед фактическим расширением
"
return 0
end
if not set -q _flag_m # Флаг media-type обязателен
echo (red Ошибка: опция m/media-type не указана)
return 1
else
for v in $_flag_m # Проверка значения флага media-type
set ALLOWED_VALUES $FILE_TYPES all
if not contains $v $ALLOWED_VALUES
echo (red "Флаг media-type может содержать только значения \
из \"$ALLOWED_VALUES\", но не \"$v\".")
return 1
end
end
contains all "$_flag_m" # Если одно из значений "all"
and set _flag_m $FILE_TYPES # то установить все типы файлов
end
# ===================== Обработка источника и цели ===================================
if set -q argv[1] # Если есть позиционные аргументы, то они являются источниками
set SOURCES $argv
else # Иначе текущая директория является источником
set SOURCES .
end
for source in $SOURCES # Проверка существования исходной директории
if not test -d $source
echo Исходная директория $source не существует.
exit 1
end
end
if set -q _flag_t # Создать целевую директорию, если указана и не существует
set TARGET $_flag_t
if not test -d $TARGET
mkdir $TARGET
end
end
# ===================== Основной цикл обработки ==============================
for source in $SOURCES
# Найти все файлы, исключив целевую директорию
# Хак: `tail -n +2` обрезает исходную директорию (первую строку)
for item in (find $source -path $TARGET -prune -o -print | tail -n +2)
# ============= Обработка целевой директории =======================
if set -q TARGET
set target_item (string replace -r "^$source/?" "$TARGET/" "$item") # Путь к элементу относительно целевой директории вместо исходной
test $item -ef $TARGET; and continue # Хак: `find` также перечисляет целевую директорию, если она в источнике, поэтому пропустить её
if test -d "$item" # Директория
and not set -q _flag_only_media
echo -en "Создание директории: $(bold $target_item)"
test -d $target_item # Создать директорию, если не существует
and echo -e \t(yellow Директория (bold $target_item) уже создана)
or begin
echo
mkdir -p $target_item
end
else if contains (file_type $item) $_flag_m # Не сжатый медиафайл
and not is_compressed $item
set output_extension compressed.$FILE_TYPE_EXTENSIONS[(contains -i (file_type $item) $FILE_TYPES)]
set output (path change-extension $output_extension $target_item)
set -q _flag_only_media # Хак: Убедиться, что целевая директория существует, если установлен флаг only media. Поскольку изначально структура директорий не создается
and mkdir -p (path dirname $output)
echo -en Обработка файла (bold $item) в (bold $output)
test -e $output
and echo -e \t(yellow Файл (bold $output) уже существует)
or begin
echo
compress_file $item $output
end
else if not set -q _flag_only_media # Немедиа или сжатый медиафайл
echo -ne "Копирование файла: $(bold $item) в $(path dirname $target_item | bold)"
test -e $target_item
and echo -e \t(yellow Файл (bold $target_item) уже существует)
or begin
echo
cp "$item" "$target_item"
end
end
# ============= Обработка на месте ===============================
else if contains (file_type $item) $_flag_m # Не сжатый медиафайл
and not is_compressed $item
set output_extension compressed.$FILE_TYPE_EXTENSIONS[(contains -i (file_type $item) $FILE_TYPES)]
set output (path change-extension $output_extension $item)
echo -ne Обработка файла (bold $item) в (path basename $output | bold)
test -e $output
and echo -e \t(yellow Файл (bold $item) уже существует)
or begin
echo
compress_file $item $output
and not set -q _flag_k
and begin
echo Перемещение $item в корзину
trash $item # MacOS
# trash-put $item # Linux
end
end
end
end
end
end
На английском
# ===================== Media Compression Utility =============================
# A comprehensive media compression tool supporting audio, video and image files
# Uses modern codecs (Opus, HEVC, AVIF) with sensible defaults for quality/size
# ==============================================================================
function compress_media
# ===================== CONSTANTS ============================
# WARN: Order matters - indexes must match between these two arrays
set FILE_TYPES audio video image # Supported media types
set FILE_TYPE_EXTENSIONS opus mp4 avif # Corresponding to media types file extensions
# NOTE: You can use different command for ffmpeg
# - ffmpeg - verbose output. Change compress_file function to controll loglevel
# - ffmpeg-bar - progress bar
set -g ffmpeg ffmpeg-bar
# ===================== HELPER FUNCTIONS ============================
function bold -d "Apply bold styling to text"
if not count $argv >/dev/null # HACK: Read pipe to argv
read --null --list --delimiter=\n argv
end
echo "\e[1m$argv\e[22m"
end
function yellow -d "Apply yellow color to text"
if not count $argv >/dev/null # HACK: Read pipe to argv
read --null --list --delimiter=\n argv
end
echo "\e[0;33m$argv\e[39m"
end
function red -d "Apply red color to text"
if not count $argv >/dev/null # HACK: Read pipe to argv
read --null --list --delimiter=\n argv
end
echo "\e[0;31m$argv\e[39m"
end
function file_type -a file -d "Determine file type category from mime-type"
set -l mime_type (file --mime-type -b "$file")
echo (string split '/' $mime_type)[1]
end
function compress_file -a input -a output -d "Compress file"
switch (file_type $input)
case audio
# Opus audio compression with variable bitrate
# NOTE: 64k bitrate provides good quality for most content
$ffmpeg -i "$input" \
-c:a libopus -b:a 64k -vbr on \
-y "$output"
case video
# PERF:
# - `nice -n 20 ` - lower priority of process
# - `cpulimit -l 2` - limit CPU usage. For me it take 90% of CPU
sudo nice -n 20 cpulimit -l 2 -i \
$ffmpeg -i "$input" \
-c:v libx265 -crf 31 -vf "scale='min(iw,1024)':-2" \
-pix_fmt yuv420p \
-c:a aac -b:a 128k -vtag hvc1 \
-y "$output"
# If you use use ffmpeg instead ffmpeg-bar.
# Add this options to reduce log output
# -loglevel error -x265-params log-level=error
case image
magick $input $output
end
tput smam # WARN: Manual tput call to fix disabling terminal wrapping from ffmpeg-bar
end
function is_compressed -a item -d "Check if filename contains '.compressed.' marker"
string match -q '*.compressed.*' (path basename $item)
end
# ===================== Command Line Arguments ============================
argparse \
h/help \
'm/media-type=+' \
't/target=' \
k/keep-original \
only-media \
-- $argv
if not string match -rq "GNU findutils" (find --version 2>/dev/null) # Require GNU find not BSD
echo -e (red "Required GNU find not BSD.")
return 1
end
if set -q _flag_h # Display help message if true
echo "\
compress_media - Compress audio, video, or image files in specified directories
Usage:
compress_media [OPTIONS]... [SOURCES]...
Description:
Recursively compresses media files (audio/video/image) in specified directories.
By default replaces originals with compressed versions (moved to trash unless -k is used).
Use --target to preserve directory structure in a different location.
Options:
-h, --help Show this help message and exit
-m, --media-type TYPE REQUIRED. Media type to process (audio|video|image|all)
-t, --target DIR Output directory for compressed files (preserves structure). Relative path must start with `./` otherwise it will be `/`.
-k, --keep-original Keep original files (don't move to trash)
--only-media Only process media files (don't copy other files to target)
Behavior:
- When using --target:
* Creates mirrored directory structure in target location
* Copies non-media files unchanged unless --only-media is specified
* Stores compressed versions of media files in corresponding directories
- Without --target:
* Compresses files in-place
* Original files are moved to trash unless --keep-original is specified
Supported Formats:
- audio: converts to OPUS (64kbit/s, VBR)
- video: converts to H.265 MP4 (CRF 31, 1024px width)
- image: converts to AVIF using ImageMagick default settings
Examples:
1. Compress audio files in current directory:
compress_media -m audio
2. Compress videos in ~/Videos, keep originals, output to ~/Compressed:
compress_media -m video -t ~/Compressed -k ~/Videos
3. Compress images in ~/Pictures, only process media files:
compress_media -m image --only-media -t ~/Compressed_Images ~/Pictures
Notes:
- Requires ffmpeg, ImageMagick, trash, and coreutils (for file type detection)
- You must use GNU find on MacOS. Make `alias find=gfind` before
running script
- Video compression uses CPU limiting for stability
- Compressed files get .compressed extension before the actual extension
"
return 0
end
if not set -q _flag_m # Flag media-type required
echo (red Error: option m/media-type not specified)
return 1
else
for v in $_flag_m # Validate value of flag media-type
set ALLOWED_VALUES $FILE_TYPES all
if not contains $v $ALLOWED_VALUES
echo (red Flag media-type can contain only values \
from \"$ALLOWED_VALUES\", but not \"$v\".)
return 1
end
end
contains all "$_flag_m" # If one of the value "all"
and set _flag_m $FILE_TYPES # then set to all file types
end
# ===================== Source and Target Handling ===================================
if set -q argv[1] # If there are positional args, then they are sources
set SOURCES $argv
else # Else current dir is source
set SOURCES .
end
for source in $SOURCES # Verify source dir existance
if not test -d $source
echo Source directory $source not exist.
exit 1
end
end
if set -q _flag_t # Create target directory if specified and doesn't exist
set TARGET $_flag_t
if not test -d $TARGET
mkdir $TARGET
end
end
# ===================== Main Processing Loop ==============================
for source in $SOURCES
# Find all files while excluding target directory
# HACK: `tail -n +2` trim source dir (first line)
for item in (find $source -path $TARGET -prune -o -print | tail -n +2)
# ============= Target Directory Processing =======================
if set -q TARGET
set target_item (string replace -r "^$source/?" "$TARGET/" "$item") # Path to item relative to target dir instead of source
test $item -ef $TARGET; and continue # HACK: `find` also list target dir if it in source, so skip it
if test -d "$item" # Dir
and not set -q _flag_only_media
echo -en "Creating directory: $(bold $target_item)"
test -d $target_item # Create dir if not exist
and echo -e \t(yellow Directory (bold $target_item) already created)
or begin
echo
mkdir -p $target_item
end
else if contains (file_type $item) $_flag_m # Not compressed Media
and not is_compressed $item
set output_extension compressed.$FILE_TYPE_EXTENSIONS[(contains -i (file_type $item) $FILE_TYPES)]
set output (path change-extension $output_extension $target_item)
set -q _flag_only_media # HACK: Ensure output directory exists, when flag only media. Cause initialy dir structure not created
and mkdir -p (path dirname $output)
echo -en Processing file (bold $item) into (bold $output)
test -e $output
and echo -e \t(yellow File (bold $output) already exist)
or begin
echo
compress_file $item $output
end
else if not set -q _flag_only_media # Non-media or compressed media
echo -ne "Copying file: $(bold $item) to $(path dirname $target_item | bold)"
test -e $target_item
and echo -e \t(yellow File (bold $target_item) already exist)
or begin
echo
cp "$item" "$target_item"
end
end
# ============= In-place Processing ===============================
else if contains (file_type $item) $_flag_m # Not compressed media
and not is_compressed $item
set output_extension compressed.$FILE_TYPE_EXTENSIONS[(contains -i (file_type $item) $FILE_TYPES)]
set output (path change-extension $output_extension $item)
echo -ne Processing file (bold $item) into (path basename $output | bold)
test -e $output
and echo -e \t(yellow File (bold $item) already exist)
or begin
echo
compress_file $item $output
and not set -q _flag_k
and begin
echo Move $item to trash
trash $item # MacOS
# trash-put $item # Linux
end
end
end
end
end
end
Установка зависимостей
### Debian/Ubuntu (apt)
sudo apt update && sudo apt install -y \
ffmpeg imagemagick file trash-cli cpulimit findutils coreutils \
ffmpeg-bar fish
### macOS (Homebrew)
brew install \
ffmpeg imagemagick findutils coreutils cpulimit trash \
ffmpeg-bar fish
# For GNU find (required)
echo "alias find=gfind" >> ~/.config/fish/config.fish
Источники
Отзывы, предложения и размышления
Можете использовать для обратной связи мою почту dalokoschestmat@gmail.com или комментарии
Обнаружили ошибку? Если вы заметили ошибку или неточность, пожалуйста, сообщите нам об этом в комментариях.
Что то было непонятно и размыто? Если в статье что-то осталось неясным или упомянуто вкратце из за чего трудно понятен смысл, дайте нам знать.
Код может более элегантным? Если вы можете написать более элегантное решение — пишите!
Общие мысли? Если у вас есть какие-то идеи, мысли или предложения, которые помогут статье, поделитесь ими.
Важно: Если у вас есть какое-то негативное ощущение от статьи выражайте это конструктивной критикой, если решились выражать. От 3 язвительных слов мало кто-то что осознает. Так же постарайтесь не писать в стиле претензии-вопроса, а больше в стиле предложения или вопроса.