From c4bf29208564e6b736012addf162f28d373c5140 Mon Sep 17 00:00:00 2001 From: evgeny Date: Wed, 21 Oct 2026 23:58:55 +0200 Subject: [PATCH] Specify video publication, remux and completion contracts --- src/video/video.h | 52 +++++++++++++++++++++++++++++++---------------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/src/video/video.h b/src/video/video.h index 999b8158..e1bdb7bd 100644 --- a/src/video/video.h +++ b/src/video/video.h @@ -1,4 +1,10 @@ -// video.h — пробинг и транскодирование видео (FFmpeg, опционально) +/* Подготовка локальных видеофайлов через FFmpeg: метаданные, JPEG и MP4 H.264/AAC. + * probe/get_info/thumbnail выполняют синхронное чтение; запускать вне GUI/uasync при медленном I/O. + * transcode_start передаёт работу media_async; публикация готового файла и done — в потоке uasync. + * Исходник должен оставаться неизменным до done. Назначение — новое имя или обычный файл, без ссылок. + * Один исходный и выходной файл, в том числе через hardlink, запрещены. Ошибка/отмена сохраняет старый dst. + * Подготовка пишет закрытый проверенный временный файл рядом с dst, затем заменяет конечное имя. + * В процессе допускается одна задача до её completion; thumbnail/probe независимы от этого лимита. */ #ifndef VIDEO_H #define VIDEO_H @@ -27,60 +33,70 @@ int video_codec_log_level(int level); #define VIDEO_TARGET_MAX_HEIGHT 720 #define VIDEO_TARGET_MAX_BITRATE 2000000 // 2 Mbit (только видео) #define VIDEO_TARGET_AUDIO_BITRATE 128000 // AAC 128 kbit +#define VIDEO_PATH_MAX 1024 // Размер буфера пути с завершающим NUL; длинные пути отклоняются. /* ── информация о видеофайле ── */ struct video_info { int is_video; // 1 = открыт и есть видеопоток - int width; - int height; - int64_t bitrate; // bitrate видеопотока (или контейнера) - int64_t duration_ms; - int has_audio; + int width; // Ширина выбранного видеопотока в пикселях. + int height; // Высота выбранного видеопотока в пикселях. + int64_t bitrate; // Средний bitrate выбранного видеопотока, бит/с; 0 — неизвестен. + int64_t duration_ms; // Длительность контейнера/видеопотока, мс; 0 — неизвестна. + int has_audio; // Есть выбранный аудиопоток; отсутствие декодера не означает отсутствие аудио. + int is_yuv420p; // Видео имеет 8-битный YUV420P; иначе требуется перекодирование. + int64_t audio_bitrate; // Средний bitrate выбранного аудиопотока; 0 — неизвестен/нет аудио. + char audio_codec[32]; // Имя аудиокодека, например "aac"; пусто без аудио. char codec[32]; // напр. "h264", "hevc", "mpeg4" char container[32]; // короткое имя контейнера (ifmt->name) }; /* - * Быстрый пробинг (читает только заголовок). Вызывается из любого потока - * (в т.ч. из GUI-потока Qt) — thread-safe, не блокирует надолго. - * Возвращает 1 если файл содержит ffmpeg-совместимый видеопоток, иначе 0. + * Синхронный пробинг: FFmpeg может читать/анализировать пакеты, ограничения времени нет. + * Возвращает 1 при наличии видеопотока, кроме attached picture; иначе 0, включая сборку без FFmpeg. */ int video_probe(const char* path); -/* Полное чтение метаданных. Заполняет info. Возвращает 0 при успехе. */ +/* Синхронно прочитать метаданные выбранных потоков. path/out обязательны; 0 — успех, -1 — ошибка. + * out обнуляется до работы, в том числе без FFmpeg; при ошибке содержимое не использовать. */ int video_get_info(const char* path, struct video_info* out); /* - * Решает, нужен ли транскод: 0 = уже подходит (слать как есть), - * 1 = нужен транскод. Вызывается из любого потока. + * 0 — достаточно remux MOV/MP4/Matroska в MP4; 1 — нужно перекодирование выбранных потоков. + * Проверяет известные bitrate, размеры, H.264/YUV420P и AAC при наличии аудио. + * NULL, неизвестный профиль или сборка без FFmpeg — 1. Выход start всегда формируется как MP4. */ int video_needs_transcode(const struct video_info* info); /* * Запуск транскодирования. Вызывается из uasync-потока. - * Создаёт отдельный pthread, который либо перекодирует src->dst в целевой - * профиль, либо (если транскод не нужен) копирует src->dst как есть. + * owner/ua/src/dst/done обязательны; owner обслуживается только в этом uasync-потоке. + * Worker либо перекодирует видео/аудио, либо выполняет remux без изменения сжатых кадров. * Прогресс обновляется атомарно, читается через video_transcode_get_progress(). * При возврате 0 done(arg, err) вызывается ровно один раз в uasync-потоке, * в том числе при отмене owner. Callback может выполниться внутри start. * При возврате -1 callback не вызывается, arg остаётся у вызывающего. + * done: err=0 — dst опубликован, info заимствован только на время callback; при ошибке info=NULL. + * err=-1 — подготовка/запуск/публикация не удалась, MEDIA_ASYNC_CANCELLED (-2) — отмена owner. + * destroy owner ждёт worker, затем отменяет публикацию и вызывает done; мгновенного прерывания кодека нет. */ struct media_async; int video_transcode_start(struct media_async* owner, struct UASYNC* ua, const char* src, const char* dst, void (*done)(void* arg, int err, const struct video_info* info), void* arg); -/* Прогресс активного транскода в промилле 0..1000, -1 если транскода нет. */ +/* Глобальный прогресс 0..1000; 1000 — временный файл подготовлен, публикация ещё может ожидаться. + * -1 — активной задачи нет. Читать можно из любого потока; active/progress не являются единым снимком. */ int video_transcode_get_progress(void); -/* 1 = транскод выполняется прямо сейчас. */ +/* 1 — задача занята работой/ожиданием completion; 0 — можно запускать следующую. */ int video_transcode_is_active(void); /* * Извлечь превью: декодирует первый видеокадр, масштабирует до max_w по ширине - * и сохраняет JPEG в jpg_out. Вызывается из любого потока (thread-safe). - * Возвращает 0 при успехе. + * и сохраняет JPEG в jpg_out. Синхронно; параллельные вызовы используют разные назначения. + * max_w<=0 означает 320; допустимый явный предел >=2, размеры округляются до чётных. + * Правила файлов/длины путей — как у start; 0 — опубликован JPEG, -1 — ошибка/нет FFmpeg. */ int video_extract_thumbnail(const char* src, const char* jpg_out, int max_w);