video-highlights
Нарезка хайлайтов: LLM + FFmpeg, авто/ручная генерация клипов
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: IntegerFieldAPI
| Метод | Путь | Описание |
|---|
|-------|------|----------|
| 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.