video-highlights

Нарезка хайлайтов: LLM + FFmpeg, авто/ручная генерация клипов

Business
Source: ДобинДом
Files: 7
Stack:
DjangoDRFFFmpegLLM API

Specification

video-highlights

Назначение

Автоматическая нарезка видео на короткие клипы (хайлайты): LLM анализирует транскрипцию, находит интересные моменты, FFmpeg нарезает клипы с fade-эффектами. Поддержка ручной нарезки, субтитров (SRT), внешних видео (YouTube, etc.).

Контракты

Модели данных

class HighlightSettings(Singleton):
    prompt: TextField                        # Промпт для LLM (с {count}, {min_duration}, {max_duration})
    llm_provider: CharField                  # 'chatgpt' | 'deepseek' | 'claude'
    llm_model: CharField                     # default: 'gpt-4o'
    temperature: FloatField                  # default: 0.7
    max_tokens: IntegerField                 # default: 4096
    highlights_count: IntegerField           # Сколько хайлайтов генерировать (default: 5)
    clip_duration_min: IntegerField          # Мин. длительность клипа в сек (default: 10)
    clip_duration_max: IntegerField          # Макс. длительность клипа в сек (default: 25)
    fade_enabled: BooleanField               # Fade in/out эффекты
    fade_duration: FloatField                # Длительность fade (default: 0.5)
    auto_generate: BooleanField              # Авто-генерация для новых видео
    video_quality: CharField                 # 'high'(CRF 18) | 'medium'(CRF 23) | 'low'(CRF 28)
    show_on_landing: BooleanField            # Показывать хайлайты на лендинге

class HighlightJob:
    broadcast: ForeignKey(ArchivedBroadcast, null=True)
    external_video: ForeignKey(ExternalVideo, null=True)
    status: CharField                        # 'pending' | 'transcribing' | 'analyzing' | 'downloading'
                                             # | 'cutting' | 'uploading' | 'completed' | 'failed'
    progress: TextField
    error: TextField
    settings_snapshot: JSONField              # Снимок настроек на момент запуска
    llm_response: JSONField                  # Ответ LLM (список моментов)
    completed_at: DateTimeField(null=True)

class ExternalVideo:
    source_url: URLField(unique=True)        # URL из YouTube, Vimeo, etc.
    platform: CharField                      # 'youtube' | 'vimeo' | 'other'
    title: CharField(300)
    duration: FloatField(null=True)
    video_file: FileField                    # Скачанное видео
    thumbnail: ImageField
    transcription_text: TextField
    status: CharField                        # 'pending' | 'downloading' | 'transcribing' | 'ready' | 'failed'

class ManualHighlightJob:
    broadcast: ForeignKey(ArchivedBroadcast, null=True)
    external_video: ForeignKey(ExternalVideo, null=True)
    title: CharField(200)
    description: TextField
    start_time: FloatField                   # Начало (сек)
    end_time: FloatField                     # Конец (сек)
    crop_factor: CharField                   # '16:9' | '9:16' | '1:1' | '4:5'
    fade_enabled: BooleanField
    fade_duration: FloatField
    subtitles_enabled: BooleanField          # Вшить субтитры
    status: CharField                        # same as HighlightJob
    highlight: ForeignKey(Highlight, null=True)  # Результат

class Highlight:
    job: ForeignKey(HighlightJob, null=True)
    broadcast: ForeignKey(ArchivedBroadcast, null=True)
    external_video: ForeignKey(ExternalVideo, null=True)
    title: CharField(200)
    description: TextField
    start_time: FloatField
    end_time: FloatField
    video_file: FileField                    # Нарезанный клип (MP4)
    srt_file: FileField                      # Субтитры (SRT)
    thumbnail: ImageField                    # Превью
    duration: FloatField
    file_size: IntegerField
    featured: BooleanField                   # Показывать на лендинге
    order: IntegerField

API

МетодПутьОписание

|-------|------|----------|

GET/admin/highlights/Список хайлайтов (фильтры: broadcast_id, featured, job_id)
DELETE/admin/highlights/{id}/Удаление хайлайта + файлов
GET/admin/highlights/{id}/download/Скачивание видео или SRT
POST/admin/highlights/generate/Запуск генерации (broadcast_id или external_video_id)
GET/admin/highlights/jobs/Список заданий (фильтры: broadcast_id, status)
GET/admin/highlights/jobs/{id}/Детали задания
DELETE/admin/highlights/jobs/{id}/Удаление задания + всех клипов
GET/PUT/admin/highlights/settings/Настройки генерации
GET/highlights/featured/Публичный endpoint для лендинга (без auth)

Пайплайн генерации

Получение транскрипции (Whisper API / из VideoTranscription)
   ↓
Анализ LLM → JSON: [{title, description, start_time, end_time}, ...]
   - Валидация: длительность в пределах min/max
   - Удаление пересекающихся моментов
   ↓
Скачивание видео (HLS → FFmpeg → MP4)
   ↓
Нарезка клипов (FFmpeg):
   - libx264, CRF 18/23/28
   - Опционально: fade in/out (video + audio)
   - Извлечение thumbnail (JPG)
   ↓
Загрузка в хранилище (S3 / локальное)
   ↓
Создание записей Highlight в БД

FFmpeg команды

Нарезка клипа с fade
ffmpeg -ss {start} -i input.mp4 -t {duration} \
  -vf "fade=t=in:st=0:d=0.5,fade=t=out:st={fade_out_start}:d=0.5" \
  -af "afade=t=in:st=0:d=0.5,afade=t=out:st={fade_out_start}:d=0.5" \
  -c:v libx264 -crf 18 -preset medium \
  -c:a aac -b:a 128k -movflags +faststart \
  -y output.mp4

Извлечение thumbnail
ffmpeg -ss {mid_time} -i clip.mp4 -vframes 1 -q:v 2 -y thumb.jpg

Скачивание из HLS
ffmpeg -i "https://..." -c copy -y source.mp4

Промпт по умолчанию

Ты получишь транскрипцию видео с таймкодами. Найди {count} самых интересных,
эмоциональных или полезных моментов длительностью от {min_duration} до {max_duration} секунд.

Для каждого момента верни JSON-массив объектов с полями:
title: короткое название момента (до 60 символов)
description: описание в 1 предложение
start_time: начало в секундах (float)
end_time: конец в секундах (float)

Критерии выбора:
Законченная мысль, ценный совет или яркая эмоция
Избегай пауз, вводных слов, обрывов фраз
Моменты не должны пересекаться по времени

Верни ТОЛЬКО JSON-массив, без markdown-обёртки.

Зависимости

FFmpeg (системный, для нарезки видео)
LLM API (GPT-4o, DeepSeek, Claude)
Whisper API (для транскрипции, если нет готовой)
Django + DRF
Celery/Huey (фоновые задачи)
django-solo (singleton settings)
Pydantic (валидация ответа LLM)
S3-совместимое хранилище (или локальное)
React + TanStack Query (админ-панель)

Реализации

СтекПутьИсточникСтатус

|------|------|----------|--------|

Django + React (TypeScript)implementations/django-react/ДобинДомпроверен

Адаптация

**LLM провайдер** — заменить AIProviderFactory на свой (OpenAI SDK, Anthropic SDK, etc.).
**Промпт** — настраивается через админ-панель (HighlightSettings.prompt).
**Видеохостинг** — заменить `get_hls_link()` на свой источник видео.
**Хранилище** — S3, local storage, или другой backend Django FileField.
**Транскрипция** — `fetch_or_create_transcription()` — подключить свой сервис.
**Форматы** — crop_factor (16:9, 9:16, 1:1, 4:5) для YouTube, Reels, Instagram.
**Качество** — CRF 18 (high) / 23 (medium) / 28 (low) в настройках.
**Внешние видео** — опционально: убрать ExternalVideo если не нужна обработка YouTube/Vimeo.