FFmpeg Python: полный обзор возможностей для обработки видео в Python

онлайн тренажер по питону
Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Что такое 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 для более глубокого понимания возможностей, и не забывайте о важности тестирования и оптимизации производительности в ваших проектах.

Новости