Что такое ffmpeg-python и зачем она нужна
В современном мире обработки мультимедиа задач становится всё больше: от простой конвертации форматов до сложных трансляций, фильтрации и аналитики видео. Библиотека ffmpeg-python (официальное название пакета) позволяет разработчикам использовать мощь команды FFmpeg напрямую внутри Python-скриптов, сохраняя при этом гибкость и читаемость кода.
FFmpeg-python — это Python-обёртка над популярной библиотекой FFmpeg, которая предоставляет удобный интерфейс для работы с мультимедийными файлами. Она позволяет выполнять практически любые операции с видео, аудио и изображениями: конвертировать форматы, применять фильтры, изменять разрешение, добавлять эффекты, создавать трансляции и многое другое.
Преимущества ffmpeg-python
Библиотека обладает рядом неоспоримых преимуществ:
Pythonic-синтаксис: Привычный для Python разработчиков способ написания кода с использованием цепочек методов и понятных параметров.
Полный доступ к функциям FFmpeg: Все возможности мощной библиотеки FFmpeg доступны через Python API.
Гибкость в построении команд: Возможность создавать сложные графы обработки с несколькими входами и выходами.
Интеграция с экосистемой Python: Легко комбинируется с NumPy, OpenCV, Pillow и другими популярными библиотеками.
Отличная производительность: Использует оптимизированные алгоритмы FFmpeg для быстрой обработки медиафайлов.
Установка и базовая настройка
Системные требования
Перед тем как начать, убедитесь, что у вас установлены:
- Сам FFmpeg (версия не ниже 4.0 рекомендуется)
- Python 3.6+ (совместимо с Python 3.10/3.11)
Установка библиотеки
# Установка Python-обёртки ffmpeg-python
pip install ffmpeg-python
# Установка FFmpeg на Ubuntu/Debian
sudo apt update && sudo apt install ffmpeg
# Установка FFmpeg на macOS через Homebrew
brew install ffmpeg
# Проверяем корректность установки
ffmpeg -version
python -c "import ffmpeg; print('ffmpeg-python установлен успешно')"
Примечание: для Windows скачайте сборку FFmpeg с официального сайта и добавьте путь в системную переменную PATH.
Проверка установки
После установки рекомендуется проверить работоспособность:
import ffmpeg
import subprocess
# Проверим версию FFmpeg
result = subprocess.run(['ffmpeg', '-version'], capture_output=True, text=True)
print(result.stdout.split('\n')[0])
# Простой тест библиотеки
try:
stream = ffmpeg.input('test.mp4')
print("ffmpeg-python работает корректно")
except Exception as e:
print(f"Ошибка: {e}")
Архитектура и основные концепции
Потоки (Streams)
Основная концепция ffmpeg-python — это потоки данных. Каждый поток представляет собой источник или назначение мультимедийных данных. Потоки могут быть:
- Входными (input streams) — читают данные из файлов, URL или устройств
- Выходными (output streams) — записывают обработанные данные
- Промежуточными — результат применения фильтров
Фильтры
Фильтры — это операции, которые преобразуют мультимедийные данные. Они могут изменять видео (масштабирование, обрезка, эффекты), аудио (громкость, эквалайзер) или работать с метаданными.
Граф обработки
FFmpeg строит граф обработки, где узлы представляют фильтры, а рёбра — потоки данных между ними. Это позволяет создавать сложные цепочки преобразований.
Полное описание методов и функций библиотеки
Основные функции создания потоков
**ffmpeg.input(source, kwargs) — создаёт входной поток для чтения данных из различных источников. Может читать из файлов, URL, устройств или стандартного ввода.
**ffmpeg.output(stream, target, kwargs) — определяет выходной файл или поток для записи обработанных данных.
**ffmpeg.probe(source, kwargs) — анализирует медиафайл и возвращает подробную информацию о его характеристиках.
Функции для работы с несколькими потоками
*ffmpeg.concat(streams, v=1, a=1) — объединяет несколько видео- или аудиопотоков в один.
*ffmpeg.join(streams) — соединяет потоки для параллельной обработки.
*ffmpeg.merge_outputs(outputs) — объединяет несколько выходных потоков в одну команду.
Глобальные настройки
*ffmpeg.global_args(flags) — устанавливает глобальные опции FFmpeg, такие как уровень логирования или режим перезаписи файлов.
Методы потоков
Каждый поток обладает набором методов для его модификации:
**.filter(name, *args, kwargs) — применяет указанный фильтр к потоку.
.overlay(overlay_stream, x, y) — накладывает один видеопоток на другой в указанных координатах.
.audio — выбирает только аудиодорожку из потока.
.video — выбирает только видеодорожку из потока.
**.run(cmd=None, capture_stdout=False, capture_stderr=False, kwargs) — выполняет сконструированную команду FFmpeg.
.compile(cmd='ffmpeg', overwrite_output=False) — компилирует поток в список аргументов командной строки без выполнения.
.get_args() — возвращает список аргументов, которые будут переданы FFmpeg.
Таблица методов и функций ffmpeg-python
| Категория | Метод/Функция | Описание | Основные параметры |
|---|---|---|---|
| Создание потоков | ffmpeg.input() |
Создание входного потока | source, ss, t, format, framerate |
ffmpeg.output() |
Создание выходного потока | target, vcodec, acodec, format |
|
ffmpeg.probe() |
Анализ медиафайла | source, select_streams |
|
| Объединение потоков | ffmpeg.concat() |
Конкатенация потоков | v (видео), a (аудио), unsafe |
ffmpeg.join() |
Соединение потоков | Потоки для объединения | |
ffmpeg.merge_outputs() |
Слияние выходов | Несколько выходных потоков | |
| Фильтрация | .filter() |
Применение фильтра | name, позиционные и именованные аргументы |
.video |
Выбор видеопотока | Без параметров | |
.audio |
Выбор аудиопотока | Без параметров | |
| Наложение | .overlay() |
Наложение видео | overlay_stream, x, y |
| Выполнение | .run() |
Запуск команды | capture_stdout, capture_stderr, overwrite_output |
.compile() |
Компиляция в команду | cmd, overwrite_output |
|
.get_args() |
Получение аргументов | Без параметров | |
| Настройки | ffmpeg.global_args() |
Глобальные параметры | Флаги FFmpeg |
Детальный разбор ключевых методов
ffmpeg.input() — создание входного потока
Метод input() поддерживает множество параметров для точной настройки чтения данных:
import ffmpeg
# Базовое использование
stream = ffmpeg.input('video.mp4')
# С указанием временного диапазона
stream = ffmpeg.input('video.mp4', ss=10, t=30) # с 10 по 40 секунду
# Чтение с URL
stream = ffmpeg.input('http://example.com/stream.m3u8')
# Захват с веб-камеры (Linux)
stream = ffmpeg.input('/dev/video0', format='v4l2', framerate=30, video_size='1280x720')
# Чтение изображений по шаблону
stream = ffmpeg.input('img%03d.png', format='image2', framerate=1)
Ключевые параметры input():
ss— начальная позиция (в секундах или формате HH:MM:SS)t— длительность чтенияformat— принудительное указание форматаframerate— частота кадров для входаvideo_size— размер видео для устройств захватаloop— зацикливание входа
ffmpeg.output() — настройка вывода
Метод output() предоставляет широкие возможности для настройки результирующего файла:
# Базовый вывод
ffmpeg.output(stream, 'output.mp4')
# С указанием кодеков
ffmpeg.output(stream, 'output.mp4', vcodec='libx264', acodec='aac')
# Настройка битрейта и качества
ffmpeg.output(stream, 'output.mp4',
video_bitrate='2M',
audio_bitrate='128k',
crf=23)
# Вывод в несколько форматов одновременно
ffmpeg.output(stream, 'output.mp4', vcodec='libx264')
ffmpeg.output(stream, 'output.webm', vcodec='libvpx-vp9')
Важные параметры output():
vcodec,acodec— видео и аудио кодекиvideo_bitrate,audio_bitrate— битрейт потоковcrf— константа качества (0-51, меньше = лучше)preset— скорость кодирования vs качествоformat— выходной формат контейнера
Фильтры — сердце обработки
Система фильтров позволяет выполнять практически любые преобразования:
# Изменение размера
stream = ffmpeg.input('input.mp4').filter('scale', 1920, 1080)
# Обрезка видео
stream = ffmpeg.input('input.mp4').filter('crop', 640, 480, 100, 50)
# Поворот на 90 градусов
stream = ffmpeg.input('input.mp4').filter('transpose', 1)
# Изменение скорости воспроизведения
stream = ffmpeg.input('input.mp4').filter('setpts', '0.5*PTS')
# Наложение текста
stream = ffmpeg.input('input.mp4').filter('drawtext',
text='Sample Text',
x=10, y=10,
fontsize=24,
fontcolor='white')
Расширенные примеры применения
Трансляция и стриминг в реальном времени
Современные стриминговые платформы требуют качественного контента в реальном времени:
import ffmpeg
# Основная настройка для стриминга
def setup_streaming():
return (
ffmpeg
.input('/dev/video0', format='v4l2', framerate=30, video_size='1920x1080')
.output(
'rtmp://live.twitch.tv/app/YOUR_STREAM_KEY',
vcodec='libx264',
preset='veryfast',
tune='zerolatency',
maxrate='3000k',
bufsize='6000k',
pix_fmt='yuv420p',
g=50,
acodec='aac',
audio_bitrate='160k',
ar=44100,
format='flv'
)
.global_args('-re') # реальное время
.run()
)
# Стриминг с веб-камеры и микрофона
def stream_webcam_with_audio():
video = ffmpeg.input('/dev/video0', format='v4l2', framerate=25, video_size='1280x720')
audio = ffmpeg.input('pulse', format='pulse') # Linux PulseAudio
return (
ffmpeg
.output(
video, audio,
'rtmp://live.youtube.com/live2/YOUR_STREAM_KEY',
vcodec='libx264',
acodec='aac',
preset='fast',
crf=28,
format='flv'
)
.global_args('-re')
.run()
)
Ключевые параметры для стриминга:
-re— чтение в реальном времениpreset='veryfast'— быстрое кодированиеtune='zerolatency'— минимальная задержкаmaxrate,bufsize— контроль битрейта
Пакетная обработка и параллельные задачи
При работе с большим количеством файлов эффективность критически важна:
import os
import ffmpeg
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
from pathlib import Path
import logging
# Настройка логирования
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def convert_video(input_path, output_dir, target_format='mp4', quality='medium'):
"""Конвертация одного видеофайла"""
input_file = Path(input_path)
output_file = Path(output_dir) / f"{input_file.stem}.{target_format}"
quality_settings = {
'high': {'crf': 18, 'preset': 'slow'},
'medium': {'crf': 23, 'preset': 'medium'},
'low': {'crf': 28, 'preset': 'fast'}
}
settings = quality_settings.get(quality, quality_settings['medium'])
try:
(
ffmpeg
.input(str(input_file))
.output(
str(output_file),
vcodec='libx264',
acodec='aac',
**settings
)
.run(overwrite_output=True, quiet=True)
)
logger.info(f"Успешно конвертирован: {input_file.name}")
return True
except ffmpeg.Error as e:
logger.error(f"Ошибка конвертации {input_file.name}: {e}")
return False
def batch_convert(input_dir, output_dir, max_workers=4, target_format='mp4'):
"""Пакетная конвертация с параллельной обработкой"""
input_path = Path(input_dir)
output_path = Path(output_dir)
output_path.mkdir(exist_ok=True)
# Поддерживаемые форматы
video_extensions = ['.mp4', '.avi', '.mkv', '.mov', '.wmv', '.flv', '.webm']
video_files = [
f for f in input_path.iterdir()
if f.suffix.lower() in video_extensions
]
logger.info(f"Найдено {len(video_files)} видеофайлов для обработки")
with ThreadPoolExecutor(max_workers=max_workers) as executor:
futures = [
executor.submit(convert_video, str(video_file), str(output_path), target_format)
for video_file in video_files
]
results = [future.result() for future in futures]
successful = sum(results)
logger.info(f"Успешно обработано {successful} из {len(video_files)} файлов")
# Использование
if __name__ == "__main__":
batch_convert("input_videos", "output_videos", max_workers=4)
Сложные графы обработки с множественными фильтрами
Создание профессиональных эффектов требует комбинирования нескольких фильтров:
import ffmpeg
def create_professional_video_effect(input_path, output_path):
"""Создание профессионального видеоэффекта"""
# Основной видеопоток
main_video = ffmpeg.input(input_path)
# Применяем последовательность фильтров
processed = (
main_video
.filter('scale', 1920, 1080) # Стандартизируем размер
.filter('fps', fps=24, round='up') # Устанавливаем 24 fps
.filter('eq', contrast=1.2, brightness=0.1, saturation=1.3) # Цветокоррекция
.filter('unsharp', 5, 5, 0.8, 3, 3, 0.4) # Повышение резкости
.filter('drawtext',
text='Professional Edit',
x='(w-text_w)/2',
y='h-th-20',
fontsize=48,
fontcolor='white',
shadowcolor='black',
shadowx=3,
shadowy=3,
enable=f'between(t,0,5)') # Показываем текст первые 5 секунд
)
# Создаем fade эффекты
with_fades = (
processed
.filter('fade', type='in', duration=1) # Появление
.filter('fade', type='out', start_time=25, duration=2) # Исчезновение
)
# Финальный вывод
(
ffmpeg
.output(with_fades, output_path,
vcodec='libx264',
acodec='aac',
crf=18,
preset='slow',
pix_fmt='yuv420p')
.run(overwrite_output=True)
)
def create_video_montage(clips, output_path):
"""Создание видеомонтажа из нескольких клипов"""
# Нормализуем все клипы к одному размеру и fps
normalized_clips = []
for clip_path in clips:
clip = (
ffmpeg
.input(clip_path)
.filter('scale', 1920, 1080)
.filter('fps', fps=30)
.filter('setsar', 1) # Устанавливаем соотношение сторон пикселя
)
normalized_clips.append(clip)
# Объединяем клипы
concatenated = ffmpeg.concat(*normalized_clips, v=1, a=1)
# Добавляем переходы между клипами
with_transitions = (
concatenated
.filter('fade', type='in', duration=0.5)
.filter('fade', type='out', start_time=28, duration=0.5)
)
# Выходной файл
(
ffmpeg
.output(with_transitions, output_path,
vcodec='libx264',
acodec='aac',
crf=20,
preset='medium')
.run(overwrite_output=True)
)
# Пример использования
create_professional_video_effect('raw_video.mp4', 'processed_video.mp4')
create_video_montage(['clip1.mp4', 'clip2.mp4', 'clip3.mp4'], 'montage.mp4')
Генерация превью и создание thumbnail
Создание привлекательных превью — важная часть работы с видеоконтентом:
import ffmpeg
import os
from pathlib import Path
def generate_thumbnails(video_path, output_dir, count=9, quality='high'):
"""Генерация сетки превью кадров"""
# Получаем информацию о видео
probe = ffmpeg.probe(video_path)
duration = float(probe['streams'][0]['duration'])
# Создаем директорию для превью
thumb_dir = Path(output_dir)
thumb_dir.mkdir(exist_ok=True)
# Генерируем кадры через равные интервалы
interval = duration / (count + 1)
for i in range(1, count + 1):
timestamp = interval * i
thumb_path = thumb_dir / f'thumb_{i:02d}.jpg'
(
ffmpeg
.input(video_path, ss=timestamp)
.output(str(thumb_path),
vframes=1,
vf='scale=320:240',
q=2 if quality == 'high' else 5)
.run(overwrite_output=True, quiet=True)
)
print(f"Создано {count} превью в {output_dir}")
def create_video_grid(video_path, output_path, rows=3, cols=3):
"""Создание сетки превью из одного видео"""
probe = ffmpeg.probe(video_path)
duration = float(probe['streams'][0]['duration'])
total_thumbs = rows * cols
interval = duration / (total_thumbs + 1)
# Создаем временные кадры
temp_frames = []
for i in range(1, total_thumbs + 1):
timestamp = interval * i
temp_path = f'temp_frame_{i:02d}.jpg'
(
ffmpeg
.input(video_path, ss=timestamp)
.output(temp_path, vframes=1, vf='scale=320:240')
.run(overwrite_output=True, quiet=True)
)
temp_frames.append(temp_path)
# Создаем сетку изображений
# Формируем строки
rows_streams = []
for row in range(rows):
row_frames = temp_frames[row*cols:(row+1)*cols]
row_inputs = [ffmpeg.input(frame) for frame in row_frames]
row_stream = ffmpeg.filter(row_inputs, 'hstack', inputs=cols)
rows_streams.append(row_stream)
# Объединяем строки вертикально
grid = ffmpeg.filter(rows_streams, 'vstack', inputs=rows)
(
ffmpeg
.output(grid, output_path, q=2)
.run(overwrite_output=True)
)
# Очищаем временные файлы
for temp_file in temp_frames:
os.remove(temp_file)
print(f"Создана сетка превью: {output_path}")
def create_animated_gif(video_path, output_path, start_time=0, duration=3, fps=10, width=480):
"""Создание анимированного GIF из видео"""
(
ffmpeg
.input(video_path, ss=start_time, t=duration)
.output(output_path,
vf=f'fps={fps},scale={width}:-1:flags=lanczos,palettegen=reserve_transparent=0',
y=None) # Не перезаписывать автоматически
.run(overwrite_output=True)
)
# Создаем палитру для лучшего качества GIF
palette_path = output_path.replace('.gif', '_palette.png')
(
ffmpeg
.input(video_path, ss=start_time, t=duration)
.output(palette_path, vf=f'fps={fps},scale={width}:-1:flags=lanczos,palettegen')
.run(overwrite_output=True, quiet=True)
)
# Создаем финальный GIF с палитрой
video_input = ffmpeg.input(video_path, ss=start_time, t=duration)
palette_input = ffmpeg.input(palette_path)
(
ffmpeg
.output(video_input, palette_input, output_path,
filter_complex=f'[0:v]fps={fps},scale={width}:-1:flags=lanczos[v];[v][1:v]paletteuse')
.run(overwrite_output=True)
)
# Удаляем временную палитру
os.remove(palette_path)
print(f"Создан анимированный GIF: {output_path}")
# Примеры использования
generate_thumbnails('video.mp4', 'thumbnails', count=12)
create_video_grid('video.mp4', 'grid_preview.jpg', rows=3, cols=4)
create_animated_gif('video.mp4', 'preview.gif', start_time=10, duration=5)
Работа с субтитрами и метаданными
Извлечение и обработка субтитров
Субтитры — важная часть современного видеоконтента:
import ffmpeg
import json
from pathlib import Path
def extract_subtitles(video_path, output_dir=None):
"""Извлечение всех субтитров из видеофайла"""
if output_dir is None:
output_dir = Path(video_path).parent
else:
output_dir = Path(output_dir)
output_dir.mkdir(exist_ok=True)
# Анализируем файл для поиска субтитров
probe = ffmpeg.probe(video_path)
subtitle_streams = [
stream for stream in probe['streams']
if stream['codec_type'] == 'subtitle'
]
if not subtitle_streams:
print("Субтитры в файле не найдены")
return []
extracted_files = []
for i, stream in enumerate(subtitle_streams):
# Определяем формат субтитров
codec = stream.get('codec_name', 'unknown')
language = stream.get('tags', {}).get('language', 'unknown')
# Выбираем расширение файла
extension_map = {
'subrip': 'srt',
'ass': 'ass',
'ssa': 'ssa',
'webvtt': 'vtt',
'mov_text': 'srt'
}
extension = extension_map.get(codec, 'srt')
# Формируем имя выходного файла
base_name = Path(video_path).stem
subtitle_file = output_dir / f"{base_name}_{language}_{i}.{extension}"
try:
(
ffmpeg
.input(video_path)
.output(str(subtitle_file),
map=f'0:s:{i}',
codec='copy' if extension != 'srt' else 'srt')
.run(overwrite_output=True, quiet=True)
)
extracted_files.append(str(subtitle_file))
print(f"Извлечены субтитры: {subtitle_file}")
except ffmpeg.Error as e:
print(f"Ошибка извлечения субтитров {i}: {e}")
return extracted_files
def embed_subtitles(video_path, subtitle_path, output_path, language='eng'):
"""Встраивание внешних субтитров в видеофайл"""
video = ffmpeg.input(video_path)
subtitles = ffmpeg.input(subtitle_path)
(
ffmpeg
.output(video, subtitles, output_path,
codec='copy',
**{
'c:s': 'mov_text', # Кодек для субтитров
'metadata:s:s:0': f'language={language}',
'map': '0', # Видео и аудио из первого входа
'map': '1' # Субтитры из второго входа
})
.run(overwrite_output=True)
)
print(f"Субтитры встроены в {output_path}")
def burn_subtitles(video_path, subtitle_path, output_path, font_size=24, font_color='white'):
"""Наложение субтитров поверх видео (burning)"""
(
ffmpeg
.input(video_path)
.output(output_path,
vf=f"subtitles='{subtitle_path}':force_style='FontSize={font_size},PrimaryColour=&H00{font_color}'",
codec='libx264',
acodec='copy')
.run(overwrite_output=True)
)
print(f"Субтитры наложены на видео: {output_path}")
Работа с метаданными
Метаданные содержат важную информацию о медиафайлах:
def extract_metadata(video_path, output_file=None):
"""Извлечение подробных метаданных"""
probe_data = ffmpeg.probe(video_path)
# Структурируем информацию
metadata = {
'format': probe_data.get('format', {}),
'streams': []
}
for stream in probe_data.get('streams', []):
stream_info = {
'index': stream.get('index'),
'codec_type': stream.get('codec_type'),
'codec_name': stream.get('codec_name'),
'duration': stream.get('duration'),
'tags': stream.get('tags', {})
}
# Добавляем специфичную для видео информацию
if stream['codec_type'] == 'video':
stream_info.update({
'width': stream.get('width'),
'height': stream.get('height'),
'avg_frame_rate': stream.get('avg_frame_rate'),
'pix_fmt': stream.get('pix_fmt')
})
# Добавляем специфичную для аудио информацию
elif stream['codec_type'] == 'audio':
stream_info.update({
'sample_rate': stream.get('sample_rate'),
'channels': stream.get('channels'),
'channel_layout': stream.get('channel_layout')
})
metadata['streams'].append(stream_info)
if output_file:
with open(output_file, 'w', encoding='utf-8') as f:
json.dump(metadata, f, indent=2, ensure_ascii=False)
print(f"Метаданные сохранены в {output_file}")
return metadata
def update_metadata(video_path, output_path, metadata_dict):
"""Обновление метаданных видеофайла"""
# Формируем аргументы метаданных
metadata_args = {}
for key, value in metadata_dict.items():
metadata_args[f'metadata:{key}'] = str(value)
(
ffmpeg
.input(video_path)
.output(output_path, codec='copy', **metadata_args)
.run(overwrite_output=True)
)
print(f"Метаданные обновлены в {output_path}")
# Примеры использования метаданных
def set_video_metadata(video_path, output_path, title=None, artist=None, album=None, year=None):
"""Установка основных метаданных видео"""
metadata = {}
if title:
metadata['title'] = title
if artist:
metadata['artist'] = artist
if album:
metadata['album'] = album
if year:
metadata['year'] = year
update_metadata(video_path, output_path, metadata)
# Примеры использования
extract_subtitles('movie.mkv', 'subtitles')
embed_subtitles('video.mp4', 'subtitles.srt', 'video_with_subs.mp4', 'rus')
metadata = extract_metadata('video.mp4', 'video_metadata.json')
set_video_metadata('input.mp4', 'output.mp4',
title='My Video', artist='Creator', year='2024')
Оптимизация производительности и отладка
Мониторинг прогресса выполнения
При работе с большими файлами важно отслеживать прогресс:
import ffmpeg
import subprocess
import re
import threading
from datetime import timedelta
def run_with_progress(stream, duration=None):
"""Запуск FFmpeg с отображением прогресса"""
cmd = stream.compile()
# Получаем длительность, если не передана
if duration is None and len(cmd) > 1:
try:
probe = ffmpeg.probe(cmd[cmd.index('-i') + 1])
duration = float(probe['format']['duration'])
except:
duration = None
process = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
def monitor_progress():
while True:
output = process.stderr.readline()
if output == '' and process.poll() is not None:
break
if output and duration:
# Ищем время в выводе FFmpeg
time_match = re.search(r'time=(\d+):(\d+):(\d+)\.(\d+)', output)
if time_match:
hours, minutes, seconds, ms = map(int, time_match.groups())
current_time = hours * 3600 + minutes * 60 + seconds + ms / 100
progress = (current_time / duration) * 100
print(f"\rПрогресс: {progress:.1f}% ({current_time:.1f}s / {duration:.1f}s)", end='')
# Запускаем мониторинг в отдельном потоке
monitor_thread = threading.Thread(target=monitor_progress)
monitor_thread.daemon = True
monitor_thread.start()
process.wait()
print("\nОбработка завершена")
return process.returncode
# Пример использования
stream = (
ffmpeg
.input('large_video.mp4')
.output('compressed.mp4', vcodec='libx264', crf=28)
)
run_with_progress(stream)
Настройки для оптимальной производительности
def optimize_for_speed(input_path, output_path):
"""Оптимизация для максимальной скорости обработки"""
(
ffmpeg
.input(input_path)
.output(output_path,
vcodec='libx264',
preset='ultrafast', # Максимальная скорость
crf=28, # Приемлемое качество
threads=0, # Использовать все доступные ядра
acodec='copy') # Копировать аудио без перекодирования
.run(overwrite_output=True)
)
def optimize_for_quality(input_path, output_path):
"""Оптимизация для максимального качества"""
(
ffmpeg
.input(input_path)
.output(output_path,
vcodec='libx264',
preset='veryslow', # Максимальное качество
crf=18, # Высокое качество
pix_fmt='yuv420p',
acodec='aac',
audio_bitrate='320k',
ar=48000)
.run(overwrite_output=True)
)
def get_hardware_acceleration():
"""Проверка доступности аппаратного ускорения"""
try:
# Проверяем NVIDIA NVENC
result = subprocess.run(['ffmpeg', '-encoders'],
capture_output=True, text=True)
accelerations = []
if 'h264_nvenc' in result.stdout:
accelerations.append('nvenc')
if 'h264_vaapi' in result.stdout:
accelerations.append('vaapi')
if 'h264_videotoolbox' in result.stdout:
accelerations.append('videotoolbox')
return accelerations
except:
return []
def encode_with_gpu(input_path, output_path):
"""Кодирование с использованием GPU"""
accelerations = get_hardware_acceleration()
if 'nvenc' in accelerations:
# NVIDIA GPU
(
ffmpeg
.input(input_path)
.output(output_path,
vcodec='h264_nvenc',
preset='fast',
cq=23,
acodec='copy')
.run(overwrite_output=True)
)
print("Использовано NVIDIA GPU ускорение")
elif 'vaapi' in accelerations:
# Intel/AMD GPU (Linux)
(
ffmpeg
.input(input_path, hwaccel='vaapi',
hwaccel_device='/dev/dri/renderD128')
.output(output_path,
vcodec='h264_vaapi',
vf='format=nv12,hwupload',
cq=23,
acodec='copy')
.run(overwrite_output=True)
)
print("Использовано VAAPI ускорение")
else:
print("Аппаратное ускорение недоступно, используем CPU")
optimize_for_speed(input_path, output_path)
Обработка ошибок и отладка
import ffmpeg
import logging
# Настройка детального логирования
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
def safe_conversion(input_path, output_path, max_retries=3):
"""Безопасная конвертация с обработкой ошибок и повторными попытками"""
for attempt in range(max_retries):
try:
logger.info(f"Попытка {attempt + 1} конвертации {input_path}")
# Проверяем входной файл
probe = ffmpeg.probe(input_path)
logger.debug(f"Входной файл: {probe['format']}")
# Выполняем конвертацию
(
ffmpeg
.input(input_path)
.output(output_path, vcodec='libx264', acodec='aac')
.global_args('-loglevel', 'verbose')
.run(overwrite_output=True)
)
logger.info(f"Успешная конвертация: {output_path}")
return True
except ffmpeg.Error as e:
error_message = e.stderr.decode() if e.stderr else str(e)
logger.error(f"Ошибка FFmpeg (попытка {attempt + 1}): {error_message}")
if attempt == max_retries - 1:
logger.error("Все попытки исчерпаны")
return False
except Exception as e:
logger.error(f"Неожиданная ошибка: {e}")
return False
return False
def debug_command(stream):
"""Отладка команды перед выполнением"""
cmd = stream.compile()
logger.debug("Команда FFmpeg:")
logger.debug(" ".join(cmd))
# Можно сохранить команду в файл для ручной проверки
with open('debug_command.txt', 'w') as f:
f.write(" ".join(cmd))
return cmd
# Пример отладки
stream = (
ffmpeg
.input('input.mp4')
.output('output.mp4', vcodec='libx264')
)
debug_command(stream)
safe_conversion('input.mp4', 'output.mp4')
Интеграция с другими библиотеками Python
Работа с OpenCV
Комбинирование ffmpeg-python с OpenCV открывает широкие возможности:
import ffmpeg
import cv2
import numpy as np
from pathlib import Path
def extract_frames_to_opencv(video_path, start_time=0, duration=None):
"""Извлечение кадров видео для обработки в OpenCV"""
# Настраиваем входной поток
input_args = {'ss': start_time}
if duration:
input_args['t'] = duration
# Получаем информацию о видео
probe = ffmpeg.probe(video_path)
width = int(probe['streams'][0]['width'])
height = int(probe['streams'][0]['height'])
# Читаем видео как поток байтов
process = (
ffmpeg
.input(video_path, **input_args)
.output('pipe:', format='rawvideo', pix_fmt='bgr24')
.run_async(pipe_stdout=True, pipe_stderr=True)
)
frames = []
frame_size = width * height * 3 # 3 байта на пиксель для BGR
while True:
in_bytes = process.stdout.read(frame_size)
if not in_bytes:
break
# Конвертируем в numpy array
frame = np.frombuffer(in_bytes, np.uint8).reshape([height, width, 3])
frames.append(frame)
process.wait()
return frames
def apply_opencv_processing(input_path, output_path, processing_func):
"""Применение OpenCV обработки к видео через ffmpeg"""
# Получаем параметры видео
probe = ffmpeg.probe(input_path)
width = int(probe['streams'][0]['width'])
height = int(probe['streams'][0]['height'])
fps = eval(probe['streams'][0]['avg_frame_rate'])
# Создаем процесс чтения
input_process = (
ffmpeg
.input(input_path)
.output('pipe:', format='rawvideo', pix_fmt='bgr24')
.run_async(pipe_stdout=True)
)
# Создаем процесс записи
output_process = (
ffmpeg
.input('pipe:', format='rawvideo', pix_fmt='bgr24',
s=f'{width}x{height}', r=fps)
.output(output_path, vcodec='libx264', pix_fmt='yuv420p')
.overwrite_output()
.run_async(pipe_stdin=True)
)
frame_size = width * height * 3
while True:
in_bytes = input_process.stdout.read(frame_size)
if not in_bytes:
break
# Конвертируем в OpenCV формат
frame = np.frombuffer(in_bytes, np.uint8).reshape([height, width, 3])
# Применяем пользовательскую обработку
processed_frame = processing_func(frame)
# Отправляем обработанный кадр
output_process.stdin.write(processed_frame.tobytes())
input_process.wait()
output_process.stdin.close()
output_process.wait()
# Пример функции обработки
def add_edge_detection(frame):
"""Добавление детекции краев"""
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray, 100, 200)
edges_colored = cv2.cvtColor(edges, cv2.COLOR_GRAY2BGR)
# Смешиваем с оригиналом
return cv2.addWeighted(frame, 0.7, edges_colored, 0.3, 0)
# Использование
apply_opencv_processing('input.mp4', 'output_edges.mp4', add_edge_detection)
Интеграция с NumPy и научными библиотеками
import ffmpeg
import numpy as np
import matplotlib.pyplot as plt
from scipy import signal
import librosa
def analyze_video_audio(video_path):
"""Анализ аудиодорожки видео с помощью научных библиотек"""
# Извлекаем аудио как numpy array
out, _ = (
ffmpeg
.input(video_path)
.output('-', format='wav')
.run(capture_stdout=True)
)
# Конвертируем в numpy array
audio_data = np.frombuffer(out, np.int16)
# Анализируем с помощью librosa
y = audio_data.astype(np.float32) / 32768.0 # Нормализация
sr = 44100 # Частота дискретизации
# Извлекаем характеристики
tempo, beats = librosa.beat.beat_track(y=y, sr=sr)
spectral_centroids = librosa.feature.spectral_centroid(y=y, sr=sr)[0]
mfcc = librosa.feature.mfcc(y=y, sr=sr, n_mfcc=13)
return {
'tempo': tempo,
'beats': beats,
'spectral_centroids': spectral_centroids,
'mfcc': mfcc,
'raw_audio': y
}
def create_visualization_video(audio_features, output_path, duration):
"""Создание видео с визуализацией аудиоанализа"""
fps = 30
total_frames = int(duration * fps)
# Создаем кадры с matplotlib
frames = []
for frame_idx in range(total_frames):
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 6))
# График спектрального центроида
time_idx = int(frame_idx * len(audio_features['spectral_centroids']) / total_frames)
ax1.plot(audio_features['spectral_centroids'][:time_idx])
ax1.set_title('Spectral Centroid')
ax1.set_xlim(0, len(audio_features['spectral_centroids']))
# MFCC heatmap
ax2.imshow(audio_features['mfcc'][:, :time_idx], aspect='auto', origin='lower')
ax2.set_title('MFCC')
# Сохраняем кадр
plt.tight_layout()
fig.canvas.draw()
frame = np.frombuffer(fig.canvas.tostring_rgb(), dtype=np.uint8)
frame = frame.reshape(fig.canvas.get_width_height()[::-1] + (3,))
frames.append(frame)
plt.close(fig)
# Создаем видео из кадров
height, width, _ = frames[0].shape
process = (
ffmpeg
.input('pipe:', format='rawvideo', pix_fmt='rgb24',
s=f'{width}x{height}', r=fps)
.output(output_path, vcodec='libx264', pix_fmt='yuv420p')
.overwrite_output()
.run_async(pipe_stdin=True)
)
for frame in frames:
process.stdin.write(frame.tobytes())
process.stdin.close()
process.wait()
# Использование
audio_features = analyze_video_audio('music_video.mp4')
create_visualization_video(audio_features, 'visualization.mp4', 30)
Часто задаваемые вопросы
Как конвертировать видео в другой формат с сохранением качества?
Для конвертации с максимальным сохранением качества используйте параметр CRF (Constant Rate Factor):
(
ffmpeg
.input('input.avi')
.output('output.mp4', vcodec='libx264', crf=18, preset='slow')
.run(overwrite_output=True)
)
Значение CRF от 18 до 23 обеспечивает высокое качество, а preset='slow' улучшает сжатие.
Как уменьшить размер видеофайла без существенной потери качества?
Используйте двухпроходное кодирование с целевым битрейтом:
# Первый проход
(
ffmpeg
.input('input.mp4')
.output('output.mp4', vcodec='libx264', b='2M', passlogfile='pass')
.global_args('-pass', '1', '-f', 'null')
.run(overwrite_output=True)
)
# Второй проход
(
ffmpeg
.input('input.mp4')
.output('output.mp4', vcodec='libx264', b='2M', passlogfile='pass')
.global_args('-pass', '2')
.run(overwrite_output=True)
)
Как объединить несколько видео в один файл?
Существует несколько способов в зависимости от требований:
# Простое объединение (файлы должны иметь одинаковые параметры)
video1 = ffmpeg.input('video1.mp4')
video2 = ffmpeg.input('video2.mp4')
video3 = ffmpeg.input('video3.mp4')
(
ffmpeg
.concat(video1, video2, video3, v=1, a=1)
.output('combined.mp4')
.run(overwrite_output=True)
)
# Объединение с нормализацией параметров
inputs = []
for video_file in ['video1.mp4', 'video2.mp4', 'video3.mp4']:
inputs.append(
ffmpeg.input(video_file).filter('scale', 1920, 1080).filter('fps', fps=30)
)
(
ffmpeg
.concat(*inputs, v=1, a=1)
.output('normalized_combined.mp4')
.run(overwrite_output=True)
)
Как извлечь аудио из видеофайла?
(
ffmpeg
.input('video.mp4')
.output('audio.mp3', acodec='mp3', audio_bitrate='320k')
.run(overwrite_output=True)
)
# Или без перекодирования (если аудио уже в нужном формате)
(
ffmpeg
.input('video.mp4')
.output('audio.aac', vn=None, acodec='copy')
.run(overwrite_output=True)
)
Как создать видео из последовательности изображений?
(
ffmpeg
.input('image_%03d.jpg', framerate=24)
.output('slideshow.mp4', vcodec='libx264', pix_fmt='yuv420p')
.run(overwrite_output=True)
)
# С добавлением аудио
video = ffmpeg.input('image_%03d.jpg', framerate=24)
audio = ffmpeg.input('background_music.mp3')
(
ffmpeg
.output(video, audio, 'slideshow_with_music.mp4',
vcodec='libx264', acodec='aac', shortest=None)
.run(overwrite_output=True)
)
Как изменить разрешение видео?
# Масштабирование с сохранением пропорций
(
ffmpeg
.input('input.mp4')
.filter('scale', 1920, -1) # -1 автоматически вычисляет высоту
.output('output_1920.mp4')
.run(overwrite_output=True)
)
# Принудительное изменение размера (может исказить пропорции)
(
ffmpeg
.input('input.mp4')
.filter('scale', 1280, 720)
.output('output_720p.mp4')
.run(overwrite_output=True)
)
# Умное масштабирование с добавлением полос
(
ffmpeg
.input('input.mp4')
.filter('scale', 1920, 1080, force_original_aspect_ratio='decrease')
.filter('pad', 1920, 1080, '(ow-iw)/2', '(oh-ih)/2', color='black')
.output('output_letterbox.mp4')
.run(overwrite_output=True)
)
Как обрезать видео по времени?
# Обрезка с указанием начального времени и длительности
(
ffmpeg
.input('input.mp4', ss=30, t=60) # с 30 секунды, длительность 60 секунд
.output('trimmed.mp4', codec='copy') # copy для быстрой обрезки без перекодирования
.run(overwrite_output=True)
)
# Обрезка с указанием начала и конца
(
ffmpeg
.input('input.mp4', ss='00:01:30', to='00:03:45')
.output('trimmed2.mp4', codec='copy')
.run(overwrite_output=True)
)
Как добавить водяной знак на видео?
main_video = ffmpeg.input('video.mp4')
watermark = ffmpeg.input('watermark.png')
(
ffmpeg
.overlay(main_video, watermark,
x='main_w-overlay_w-10', # 10 пикселей от правого края
y='10') # 10 пикселей от верхнего края
.output('watermarked.mp4')
.run(overwrite_output=True)
)
# Полупрозрачный водяной знак
watermark_with_alpha = (
ffmpeg
.input('watermark.png')
.filter('format', 'rgba')
.filter('colorchannelmixer', aa=0.5) # 50% прозрачности
)
(
ffmpeg
.overlay(main_video, watermark_with_alpha, x=10, y=10)
.output('transparent_watermark.mp4')
.run(overwrite_output=True)
)
Заключение
Библиотека ffmpeg-python представляет собой мощный инструмент для работы с мультимедийными данными в экосистеме Python. Она успешно объединяет функциональность профессионального инструмента FFmpeg с удобством и читаемостью Python-кода.
Ключевые преимущества ffmpeg-python:
Универсальность — поддержка всех основных форматов видео, аудио и изображений, а также возможность работы с потоками в реальном времени.
Производительность — использование оптимизированных алгоритмов FFmpeg обеспечивает высокую скорость обработки даже больших файлов.
Гибкость — возможность создания сложных графов обработки с несколькими входами, выходами и цепочками фильтров.
Интеграция — seamless работа с другими популярными Python библиотеками, такими как NumPy, OpenCV, и matplotlib.
Масштабируемость — поддержка параллельной обработки и пакетных операций для промышленного использования.
Области применения:
- Медиапроизводство — монтаж, цветокоррекция, добавление эффектов
- Стриминг и трансляции — создание live-потоков и обработка в реальном времени
- Автоматизация — пакетная обработка больших объемов контента
- Веб-разработка — создание превью, конвертация форматов для различных устройств
- Научные исследования — анализ мультимедийных данных, компьютерное зрение
- Архивирование — конвертация и сжатие для долгосрочного хранения
Освоив принципы работы с ffmpeg-python, описанные в этой статье, вы получите в свое распоряжение профессиональный инструментарий для решения практически любых задач обработки мультимедиа. Начиная с простых операций конвертации и заканчивая созданием сложных автоматизированных pipeline для обработки контента в промышленных масштабах.
Помните, что эффективное использование библиотеки требует понимания как основ работы с FFmpeg, так и специфики Python-разработки. Экспериментируйте с различными фильтрами и параметрами, изучайте документацию FFmpeg для более глубокого понимания возможностей, и не забывайте о важности тестирования и оптимизации производительности в ваших проектах.
Настоящее и будущее развития ИИ: классической математики уже недостаточно
Эксперты предупредили о рисках фейковой благотворительности с помощью ИИ
В России разработали универсального ИИ-агента для роботов и индустриальных процессов