Статус: В розробці в рамках lucidRAG. Источник: github.comM SK1scottgal/lucidrag
Де це підходить?: VideoSummarizer - це оркестр lucidRAG сім 'я,, яка об' єднує три труби в єдиний аналіз відеоігрівного двигунаM SK1
Все йде так само Снижена модель RAG: виокремлює сигнали, як тільки МSK1 зберігає дані МSK2 синтезує з обмеженим входом LLM .
Обробка двогодинної кадру фільму з вбудовами CLIP займатиме багато годин і коштуватиме сотні доларів в обчисленнях.
VideoSummarizer вирішує це за допомогою трьох ключових оптимацій:
Результатом є: : час перегляду фільмів в МSK1 годині, а не годині. ImageSummarizer і AudioSummarizer, але з 'єднане в об' єднаний канал відеоаналізуM SK1
Погляньмо на сутність Відео - кадри + аудіо МSK1 текст МSK2 обробка кожного домена за допомогою спеціалізованих інструментів , поєднання результатів у послідовні сцени
- Спочатку - структура процесу (різанняМSK1 ЯM SK2фрази МSK3 аудіо-сегменти )
- Одного разу виділити рухові сигнали МSK0введення, транскрипції
Термінологія:
(start_time, end_time) + сигналиНайкращі моделі ML
Цей artykuł стосується:
Подібні статті:
Оцінки: Числа, вимірювані на AMD 9950X МSK2 ядро МSK3 / NVIDIA AM SK5 | | МSK6 ГБ | ) ♪ / |96 ГB оперативної пам 'яті
Типовий фільм містить:
( Ніхто не робить цьогоM SK1 але він визначає масштаб):
Навіть з вилученням ключів кадру (стверджуємо , МSK2 кадри МSK3 що M' все ще МСК5 секунди послідовного висновку CLIP
Традиційний підхід: МSK1Извадити клавіатуруM SK2 відправити до Vision LLM, сподіватися на найкращуMSC4
Проблема: Це загоряє обчислення над надлишками кадрів (багато клавішних кадрів візуально схожі МSK2 обробляє їх серійно МSK3 ГПЗ позадіяний між кадрами мSK4 і пропустив аудіо M/ текстові сигнали повністю МСК6
Рішення: Мультифріалізація МSK1 етапи очистки МSK2 переробка пакетів , і конструкція трубопроводу
Втілення VideoSummarizer Reduced RAG для відео з трьома кадрами
flowchart TB
subgraph Input["Video File (.mp4, .mkv, etc.)"]
V[Video Stream]
A[Audio Stream]
end
subgraph Stage1["Stage 1: Structural Analysis"]
N[NormalizeWave<br/>FFprobe metadata]
SD[ShotDetectionWave<br/>Scene cuts via FFmpeg]
KE[KeyframeExtractionWave<br/>I-frame + dedup]
end
subgraph Stage2["Stage 2: Content Extraction"]
IS[ImageSummarizer<br/>CLIP, OCR, Vision]
AS[AudioSummarizer<br/>Whisper, Diarization]
NER[NER Service<br/>Entity extraction]
end
subgraph Stage3["Stage 3: Scene Assembly"]
SC[SceneClusteringWave<br/>CLIP similarity]
EV[EvidenceGenerationWave<br/>RAG chunks]
end
V --> N --> SD --> KE
KE --> IS
A --> AS
AS --> NER
IS --> SC
NER --> SC
SC --> EV
style Stage1 stroke:#22c55e,stroke-width:2px
style Stage2 stroke:#3b82f6,stroke-width:2px
style Stage3 stroke:#8b5cf6,stroke-width:2px
Перед тим, як перейти до втілення, ось що ви отримаєте:
| МSK0 Артефакт | Ключові поля МSK2 Źródło | |
|---|---|---|
| Сcèна | id, start_time, end_time, key_terms[], speaker_ids[], embedding[512] |
SceneClusteringWave |
| Збиття | id, start_time, end_time, cut_type, keyframe_path |
ShotDetectionWave МSK1 |
| Вираження | id, text, start_time, end_time, speaker_id, confidence |
Переписова хвиля МSK1 |
| TextTrack | id, text, start_time, text_type МСК0головокМSK1кредиту/субголовкуМСК3окрМ СК4 MСК5 субтитExtractionWave МСК6 |
|
| Кліфрейм | id, timestamp, frame_path, dhash, clip_embedding[512] |
KeyframeExtractionWave |
Кожен артефакт містить походження: джерело хвилі МSK1 тайм-показування обробки МSK2 показник довіри . Це \ "\ ledger доказів ", на якому базуються RAG пошуки
VideoSummarizer використовує архітектура хвиль на основі сигналу- де кожна хвиля явно заявляє про свої сигнали
public interface ISignalAwareVideoWave
{
/// <summary>Signals this wave requires before it can run.</summary>
IReadOnlyList<string> RequiredSignals { get; }
/// <summary>Signals this wave can optionally use if available.</summary>
IReadOnlyList<string> OptionalSignals { get; }
/// <summary>Signals this wave emits on successful completion.</summary>
IReadOnlyList<string> EmittedSignals { get; }
/// <summary>Cache keys this wave produces for downstream waves.</summary>
IReadOnlyList<string> CacheEmits { get; }
/// <summary>Cache keys this wave consumes from upstream waves.</summary>
IReadOnlyList<string> CacheUses { get; }
}
Це дозволяє динамічна координація хвиль:
Видобуток ключів застосовується як 7 гранулові хвилі для кращого паралелізму та ефективностіキャッシュуM SK1
| хвиля МSK1 пріоритет | вимагає МSK3 сигнали M | час МСК5 | |
|---|---|---|---|
| Нормалізувати хвиля | 1000 | - | video.duration, video.fps, video.normalized МSK0 ~2s МSK2 |
| FFmpegShotDetectionWave | 900 | video.normalized |
shots.detected, shots.count МSK0 ~5-10s МSK2 |
| Індикційна хвиля | 850 | video.normalized |
keyframes.iframes_detected, keyframes.iframes_count МSK0 ~3s МSK2 |
| Сила вибору кадрів | 840 | shots.detected, keyframes.iframes_detected |
keyframes.selected, keyframes.selected_count МSK0 ~1s МSK2 |
| Двухвирусна хвиля | 830 | keyframes.selected |
keyframes.thumbnails_extracted МSK0 ~5s МSK2 |
| KeyframeDeduplicationWave | 820 | keyframes.thumbnails_extracted |
keyframes.deduplicated, keyframes.duplicates_skipped МSK0 ~1s МSK2 |
| KeyframeFullResExtractionWave | 810 | keyframes.deduplicated |
keyframes.extracted, keyframes.count МSK0 ~10s МSK2 |
| ClipEmbeddingWave | 800 | keyframes.extracted |
clip.embeddings_ready, clip.embeddings_count МSK0 ~30s МSK2 |
| Процедура аналізу зображення | 790 | keyframes.deduplicated |
keyframes.analyzed, ocr.extracted МSK0 ~60s МSK2 |
| TitleCreditsDetectionWave | 750 | shots.detected |
title.detected, credits.detected МSK0 ~5s МSK2 |
| АудіоExtractionWave | 650 | video.normalized |
audio.extracted, audio.path МSK0 ~30s МSK2 |
| Переписова хвиля | 600 | audio.extracted |
transcription.complete, transcription.utterance_count МSK0 ~120s МSK2 |
| ЗаголовокExtractionWave | 550 | video.normalized |
subtitles.extracted МSK0 ~2s МSK2 |
| РозділExtractionWave | 500 | video.normalized |
chapters.extracted МSK0 ~1s МSK2 |
| Сcèна-кластерна хвиля | 400 | shots.detected |
scenes.detected, scene.count МSK0 ~5s МSK2 |
| Сила генерації доказів | 100 | scenes.detected |
evidence.generated МSK0 ~2s МSK2 |
Примітки:
keyframes.deduplicated (не повнийM SK1res): ОCR працює на миниатюрах ; візуальне субтитриювання використовує повну МSK4res, коли доступно за допомогою маршрутизуванняЗагалом за 2-години фільмуМSK0 ~10-15 хвилини МSK2 проти . годин без оптимізації
Сигнали визначаються як константи консистенції:
public static class VideoSignals
{
// NormalizeWave signals
public const string VideoDuration = "video.duration";
public const string VideoFps = "video.fps";
public const string VideoNormalized = "video.normalized";
// Shot detection signals
public const string ShotsDetected = "shots.detected";
public const string ShotsCount = "shots.count";
// Keyframe signals
public const string IframesDetected = "keyframes.iframes_detected";
public const string KeyframesSelected = "keyframes.selected";
public const string KeyframesDeduplicated = "keyframes.deduplicated";
public const string KeyframesExtracted = "keyframes.extracted";
// CLIP embedding signals
public const string ClipEmbeddingsReady = "clip.embeddings_ready";
// Scene clustering signals
public const string ScenesDetected = "scenes.detected";
public const string SceneCount = "scene.count";
// Transcription signals
public const string TranscriptionComplete = "transcription.complete";
}
VideoSummarizer використовує здатність-засаджена архітектура: розпізнати GPU один раз на старті , завантажити моделі неохоче МSK2 працювати маршрутом до доступних компонентів .
Моделі визначаються в models.yamlніяких магічних стрічок в коді:
# models.yaml (excerpt)
models:
clip-vit-b32:
name: "CLIP ViT-B/32"
download_url: "https://huggingface.co/openai/clip-vit-base-patch32/resolve/main/onnx/visual_model.onnx"
preferred_providers: [CUDAExecutionProvider, DmlExecutionProvider, CPUExecutionProvider]
components:
ClipEmbeddingWave:
models: [clip-vit-b32]
fallback_chain: [ImageAnalysisWave]
// Type-safe constants (no raw strings)
await coordinator.EnsureModelAsync(ModelIds.ClipVitB32);
await coordinator.ActivateWaveAsync(ComponentIds.TranscriptionWave);
// Route with fallback
var route = await coordinator.RouteWorkAsync(new[]
{
ComponentIds.ClipEmbeddingWave, // Primary (GPU)
ComponentIds.ImageAnalysisWave // Fallback (CPU)
});
Ограничення швидкості, оцінка часуM SK1 та пристосований зворотній тиск дають інтерфейсу змогу реагувати, водночас збільшуючи пропускну спроможність:
// Time estimation from actual data
var estimator = CapabilityAtoms.CreateTimeEstimator();
using (estimator.Time("clip_embedding")) { await ProcessAsync(); }
var eta = estimator.GetEstimate("clip_embedding", remaining: 50);
// eta.Estimated, eta.Optimistic, eta.Pessimistic, eta.Confidence
Доки системи повних можливостей: Подивіться
Mostlylucid.Summarizer.Core/Capabilities/для розпізнавання ҐПУ, сигналова паббаM SK1 суборівникиМSK2 контролери зворотнього тиску МSK3 та дизайн сітки
Перед запуском дорогих вбудованих CLIP, VideoSummarizer відфільтрує візуально схожі кадри, використовуючи різниця hash (dHashM SK1.
public class KeyframeDeduplicationService
{
// dHash parameters: 9x8 grayscale = 64 bits
private const int HashWidth = 9;
private const int HashHeight = 8;
private const int DefaultHammingThreshold = 10;
public async Task<ulong> ComputeDHashAsync(string imagePath, CancellationToken ct)
{
using var image = Image.Load<Rgba32>(imagePath);
// Resize to 9x8 (one extra column for gradient comparison)
image.Mutate(x => x
.Resize(HashWidth, HashHeight)
.Grayscale());
ulong hash = 0;
int bit = 0;
// Compare adjacent pixels horizontally
for (int y = 0; y < HashHeight; y++)
{
for (int x = 0; x < HashWidth - 1; x++)
{
var left = image[x, y].R;
var right = image[x + 1, y].R;
// Set bit if left pixel is brighter than right
if (left > right)
{
hash |= (1UL << bit);
}
bit++;
}
}
return hash;
}
public static int HammingDistance(ulong a, ulong b) =>
BitOperations.PopCount(a ^ b);
}
Ось вихідний код:
Input: 50 keyframe candidates (from codec I-frames)
Deduplication (Hamming threshold 10):
Frame 0: hash=0x8f3a2c1d → KEEP (first frame)
Frame 1: hash=0x8f3a2c1e → SKIP (distance=1 from frame 0)
Frame 2: hash=0x8f3a2c1f → SKIP (distance=2 from frame 0)
Frame 3: hash=0xc7e1b4a2 → KEEP (distance=28 from frame 0)
...
Result: 50 → 30 frames (40% reduction)
Processing saved: ~8 seconds of CLIP inference
Чому це важливо:
Замість того, щоб обробляти одне зображення в один раз, VideoSummarizer пакети M SK1 зображення на GPU pass.
public class BatchClipEmbeddingService
{
private const int ClipImageSize = 224;
private const int DefaultBatchSize = 8; // 8 images per GPU pass
public async Task<Dictionary<int, float[]>> GenerateBatchEmbeddingsAsync(
Dictionary<int, string> framePaths,
int batchSize = DefaultBatchSize,
CancellationToken ct = default)
{
var session = await GetOrLoadClipModelAsync(ct);
var results = new Dictionary<int, float[]>();
// Pre-index batch for O(1) lookup (not batch.IndexOf!)
var batches = framePaths
.Select((kvp, idx) => (idx, kvp.Key, kvp.Value))
.Chunk(batchSize);
foreach (var batch in batches)
{
// Create batch tensor [batchSize, 3, 224, 224]
var tensor = new DenseTensor<float>(new[] { batch.Length, 3, ClipImageSize, ClipImageSize });
// Preprocess images in parallel (simplified; production uses vectorised span copy)
Parallel.ForEach(batch, item =>
{
var (batchIdx, frameIndex, path) = item;
var localIdx = batchIdx % batchSize;
PreprocessImageToTensor(path, tensor, localIdx); // ImageSharp pixel buffers
});
// Single GPU pass for entire batch
var inputs = new List<NamedOnnxValue>
{
NamedOnnxValue.CreateFromTensor("input", tensor)
};
using var outputResults = session.Run(inputs);
// Extract embeddings from batch output...
}
return results;
}
}
Порівняння ефективності:
Input: 30 keyframes (after deduplication)
Serial processing (1 frame at a time):
30 × 200ms = 6,000ms (6.0 seconds)
Batch processing (8 frames per pass):
4 batches × 350ms = 1,400ms (1.4 seconds)
Speedup: 4.3x
Чому працевлаштування пакетів працює:
[8, 3, 224, 224] використовує таку ж пам 'ять, як і один зображень (almostM SK1VideoSummarizer не перетворює ImageSum marizer чи AudioSommarizerit ланцюги вони.
Видобуток ключів розділяється на 7 гранулярні хвилі МSK1 бачите таблицю хвиль вище МSK2 Тут ' є модель координації, яка показує, як вони з 'єднуються
// IFrameDetectionWave → KeyframeSelectionWave → ThumbnailExtractionWave
// → KeyframeDeduplicationWave → KeyframeFullResExtractionWave → ClipEmbeddingWave
// ClipEmbeddingWave coordinates with ImageSummarizer
public class ClipEmbeddingWave : IVideoWave, ISignalAwareVideoWave
{
public IReadOnlyList<string> RequiredSignals => [VideoSignals.KeyframesExtracted];
public IReadOnlyList<string> EmittedSignals => [VideoSignals.ClipEmbeddingsReady];
public async Task ProcessAsync(VideoContext context, CancellationToken ct)
{
var keyframes = context.GetCached<Dictionary<int, string>>("keyframes.paths");
// Batch CLIP embedding (3-5x faster than serial)
var embeddings = await _batchClipService.GenerateBatchEmbeddingsAsync(
keyframes, batchSize: 8, ct);
foreach (var (frameIndex, embedding) in embeddings)
context.KeyframeEmbeddings[frameIndex] = embedding;
}
}
// ImageAnalysisWave runs ImageSummarizer on deduplicated frames
public class ImageAnalysisWave : IVideoWave, ISignalAwareVideoWave
{
public IReadOnlyList<string> RequiredSignals => [VideoSignals.KeyframesDeduplicated];
public async Task ProcessAsync(VideoContext context, CancellationToken ct)
{
var keyframePaths = context.GetCached<List<string>>("keyframes.deduplicated_paths");
foreach (var path in keyframePaths)
{
// Run ImageSummarizer for OCR, vision, captions
var result = await _imageOrchestrator.AnalyzeAsync(path, ct);
context.SetCached($"image_analysis.{Path.GetFileName(path)}", result);
}
}
}
Видобуток аудіо та транскрипція - тепер окремі сигнали
// AudioExtractionWave runs first (extracts audio track from video)
public class AudioExtractionWave : IVideoWave, ISignalAwareVideoWave
{
public IReadOnlyList<string> RequiredSignals => [VideoSignals.VideoNormalized];
public IReadOnlyList<string> EmittedSignals => ["audio.extracted", "audio.path"];
public async Task ProcessAsync(VideoContext context, CancellationToken ct)
{
var audioPath = await _ffmpegService.ExtractAudioAsync(
context.VideoPath, context.WorkingDirectory, ct);
context.SetCached("audio.path", audioPath);
}
}
// TranscriptionWave depends on audio.extracted signal
public class TranscriptionWave : IVideoWave, ISignalAwareVideoWave
{
public IReadOnlyList<string> RequiredSignals => ["audio.extracted"];
public IReadOnlyList<string> EmittedSignals => [
VideoSignals.TranscriptionComplete,
"transcription.utterance_count"
];
public async Task ProcessAsync(VideoContext context, CancellationToken ct)
{
var audioPath = context.GetCached<string>("audio.path");
// Run AudioSummarizer pipeline (Whisper + diarization)
var audioProfile = await _audioOrchestrator.AnalyzeAsync(audioPath, ct);
// Extract utterances with speaker info
var turns = audioProfile.GetValue<List<SpeakerTurn>>("speaker.turns");
foreach (var turn in turns ?? [])
{
context.Utterances.Add(new Utterance
{
Id = Guid.NewGuid(),
Text = turn.Text,
StartTime = turn.StartSeconds,
EndTime = turn.EndSeconds,
SpeakerId = turn.SpeakerId,
Confidence = turn.Confidence
});
}
// Run NER on full transcript for entity extraction
var transcript = audioProfile.GetValue<string>("transcription.full_text");
if (!string.IsNullOrEmpty(transcript))
{
var entities = await _nerService.ExtractEntitiesAsync(transcript, ct);
context.SetCached("transcript_entities", entities);
// Emit entity signals by type (PER, ORG, LOC, MISC)
foreach (var group in entities.GroupBy(e => e.Type))
{
context.AddSignal($"transcript.entities.{group.Key.ToLowerInvariant()}",
group.Select(e => e.Text).Distinct().ToList());
}
}
}
}
VideoSummarizer виділяє названі сутності з транскрипцій, використовуючи BERT-based NER
public class OnnxNerService
{
// Model: dslim/bert-base-NER (ONNX exported)
// Entities: PER (Person), ORG (Organization), LOC (Location), MISC (Miscellaneous)
public async Task<List<EntitySpan>> ExtractEntitiesAsync(string text, CancellationToken ct)
{
var entities = new List<EntitySpan>();
// Chunk long text (BERT max 512 tokens)
foreach (var chunk in ChunkText(text, maxTokens: 400, overlap: 50))
{
// Tokenize with WordPiece
var tokens = _tokenizer.Tokenize(chunk);
// Run ONNX inference
var inputs = PrepareInputs(tokens);
using var results = _session.Run(inputs);
// Decode BIO tags
var predictions = DecodePredictions(results);
var chunkEntities = ExtractEntitySpans(tokens, predictions);
entities.AddRange(chunkEntities);
}
// Deduplicate entities
return entities
.GroupBy(e => (e.Text.ToLowerInvariant(), e.Type))
.Select(g => g.First())
.ToList();
}
}
Ось вихідний код:
Transcript: "Today we're speaking with John Smith from Microsoft about
their new AI lab in Seattle. The project, codenamed Phoenix, builds
on research from Stanford University."
Entities extracted:
PER: John Smith
ORG: Microsoft, Stanford University
LOC: Seattle
MISC: Phoenix
Signals emitted:
transcript.entities.per = ["John Smith"]
transcript.entities.org = ["Microsoft", "Stanford University"]
transcript.entities.loc = ["Seattle"]
transcript.entities.misc = ["Phoenix"]
Чому NER важливо для відео:
Використовуючи вбудовані CLIP лише для виявлення сцени, ' не працює добре; кільці кадри є розсіяними по дизайну ( одна зміна на кадрі МSK2 але кадри щільні ♫ . З вбудованими в МSK4 для ♫ МSK5 ♫ кадрів ♫(~2% об 'ємом ♫
VideoSummarizer використовує мульти---сигнальний підхід що об 'єднує 4 вагані сигнали для надійного виявлення кордону сцениM SK1
public class SceneClusteringWave : IVideoWave, ISignalAwareVideoWave
{
// Signal weights for boundary scoring
private const double EmbeddingWeight = 0.4; // CLIP embedding dissimilarity
private const double TranscriptWeight = 0.3; // Semantic shift in transcript
private const double CutTypeWeight = 0.2; // Fade/dissolve detection
private const double TemporalWeight = 0.1; // Time since last scene
// Temporal constraints
private const double MinSceneDuration = 15.0; // Don't split scenes < 15s
private const double MaxSceneDuration = 300.0; // Force split at 5 minutes
private const double TargetSceneDuration = 90.0; // Prefer ~90s scenes
public IReadOnlyList<string> RequiredSignals => [VideoSignals.ShotsDetected];
public IReadOnlyList<string> OptionalSignals => [
VideoSignals.ClipEmbeddingsReady,
VideoSignals.TranscriptionComplete,
VideoSignals.KeyframesDeduplicated
];
public IReadOnlyList<string> EmittedSignals => [
VideoSignals.ScenesDetected,
"scene.count",
"scene.avg_duration",
"scene.clustering_method"
];
private List<(int shotIndex, double score)> ComputeBoundaryScores(VideoContext context)
{
var shots = context.Shots.OrderBy(s => s.StartTime).ToList();
var scores = new List<(int, double)>();
// Build embedding map with nearest-neighbor interpolation
var shotEmbeddings = PropagateEmbeddingsToNearbyShots(context, shots);
// Build transcript windows for semantic shift detection
var transcriptWindows = BuildTranscriptWindows(context, shots, windowSeconds: 10);
for (int i = 0; i < shots.Count - 1; i++)
{
double score = 0;
var currentShot = shots[i];
var nextShot = shots[i + 1];
// 1. Embedding dissimilarity (40%)
if (shotEmbeddings.TryGetValue(i, out var currentEmbed) &&
shotEmbeddings.TryGetValue(i + 1, out var nextEmbed))
{
var similarity = CosineSimilarity(currentEmbed, nextEmbed);
score += (1.0 - similarity) * EmbeddingWeight;
}
// 2. Transcript semantic shift (30%)
if (transcriptWindows.TryGetValue(i, out var currentWords) &&
transcriptWindows.TryGetValue(i + 1, out var nextWords))
{
var overlap = currentWords.Intersect(nextWords).Count();
var union = currentWords.Union(nextWords).Count();
var jaccard = union > 0 ? (double)overlap / union : 0;
score += (1.0 - jaccard) * TranscriptWeight;
}
// 3. Cut type signal (20%) - fades/dissolves suggest scene boundaries
if (currentShot.CutType is "fade" or "dissolve")
{
score += CutTypeWeight;
}
// 4. Temporal pressure (10%) - encourage splits near target duration
var timeSinceLastScene = currentShot.EndTime - GetLastSceneBoundary();
if (timeSinceLastScene > TargetSceneDuration)
{
var pressure = Math.Min(1.0, (timeSinceLastScene - TargetSceneDuration) / 60);
score += pressure * TemporalWeight;
}
scores.Add((i, score));
}
return scores;
}
}
Найближчий-Поміжній Embedding Propagation: Лише МSK1 кадрів мають прямі вбудовані CLIPM SK2 Цей новий підхід розповсюджує вбудовання на близькі кадри за МSK3 секунд, використовуючи теорію пристосованості до часу .
Перепис Semantic Windows: Збудовує МSK1секундні словні вікна навколо кожного кадру і визначає семантичні зміни за допомогою відстані від Джаккарда - дешевий прокси семантичного дрейфу МSK2нижча перехрестя = зміна тем СМСК4 БМ МСК5 перехрестя або вбудований дрейф можна використати, коли це доступно ММСК6
Усвідомлення типу розрізу: Згасання МSK1 доM SK2чорні та розчинні переходи сильно показують межі сцени МSK3 підвищує показник граничного результату .
Пристосовальне обмеження: Замість фіксованої початкової позначкиM SK1 вибирає межі з верхньої частини МSK2 балів
Тиморальні обмеження: Встановлює мінімальну МSK1 сцени і силує кордони на МSK2максимальний час .
Наприклад:
Input: 1881 shots from a 2-hour movie
39 keyframes with CLIP embeddings
2302 utterances from transcript
Boundary scoring per shot:
Shot 45-46: embedding=0.15, transcript=0.32, cut=0.0, temporal=0.0 → score=0.156
Shot 46-47: embedding=0.08, transcript=0.12, cut=0.0, temporal=0.0 → score=0.068
Shot 47-48: embedding=0.35, transcript=0.41, cut=0.2, temporal=0.05 → score=0.388 ← BOUNDARY
...
Adaptive threshold (top 25%): 0.25
Natural boundaries found: 45
Output: 47 scenes (avg 2.6 minutes per scene)
- Min scene: 15.2s
- Max scene: 298.4s
- Total coverage: 100%
Signals:
scenes.detected = true
scene.count = 47
scene.avg_duration = 156.3
scene.clustering_method = "multi_signal_weighted"
VideoSummarizer розширює контракт із ImageSum marizer і AudioSumMarizer:
public record VideoSignal
{
public required string Key { get; init; } // "scene.count", "transcript.entities.per"
public object? Value { get; init; }
public double Confidence { get; init; } = 1.0;
public required string Source { get; init; } // "SceneClusteringWave"
// Video-specific: time range
public double? StartTime { get; init; }
public double? EndTime { get; init; }
public DateTime Timestamp { get; init; }
public Dictionary<string, object>? Metadata { get; init; }
public List<string>? Tags { get; init; } // ["visual", "scene"]
}
public static class VideoSignalTags
{
public const string Visual = "visual";
public const string Audio = "audio";
public const string Speech = "speech";
public const string Ocr = "ocr";
public const string Motion = "motion";
public const string Scene = "scene";
public const string Shot = "shot";
public const string Metadata = "metadata";
}
Випущені ключові сигнали:
| Сигнал | Źródło МSK2 Описание МSK3 | |
|---|---|---|
video.duration |
Нормалізувати хвилі МSK1 Общая тривалість в секундах МSK2 | |
video.resolution |
Нормалізувати хвилі | Ширина МSK2 Высота |
video.fps |
Нормалізуюча хвиля МSK1 Рабочна швидкість МSK2 | |
shots.count |
ШотDetectionWave МSK1 Число виявлених вистрілів МSK2 | |
keyframes.count |
KeyframeExtractionWave | |
keyframes.duplicates_skipped |
KeyframeExtractionWave | |
scene.count |
SceneClusteringWave | |
transcript.entities.per |
Транскрипційна хвиля | Назви людей з NER МSK2 |
transcript.entities.org |
TranscriptionWave | Назви організації МSK2 |
transcript.word_count |
Транскрипційна хвиля МSK1 Перечень слів в транскрипції МSK2 |
І VideoPipeline конвертує відеосигнал ContentChunk для індексування РАГ:
public class VideoPipeline : PipelineBase
{
public override string PipelineId => "video";
public override IReadOnlySet<string> SupportedExtensions => new HashSet<string>
{
".mp4", ".mkv", ".avi", ".mov", ".wmv", ".webm", ".flv", ".m4v", ".mpeg", ".mpg"
};
private List<ContentChunk> BuildContentChunks(VideoContext context, string filePath)
{
var chunks = new List<ContentChunk>();
// 1. Scene-based chunks (best for video retrieval)
foreach (var scene in context.Scenes)
{
var sceneText = BuildSceneText(context, scene);
var embedding = context.GetCached<float[]>($"scene_centroid.{scene.Id}");
chunks.Add(new ContentChunk
{
Text = sceneText,
ContentType = ContentType.Summary,
Embedding = embedding, // Proper vector column, not metadata
Metadata = new Dictionary<string, object?>
{
["source"] = "video_scene",
["scene_id"] = scene.Id,
["key_terms"] = scene.KeyTerms,
["speakers"] = scene.SpeakerIds,
["start_time"] = scene.StartTime,
["end_time"] = scene.EndTime
}
});
}
// 2. Transcript chunks (1-minute windows)
var transcriptChunks = BuildTranscriptChunks(context, filePath);
chunks.AddRange(transcriptChunks);
// 3. Text track chunks (on-screen text/subtitles)
foreach (var textTrack in context.TextTracks)
{
chunks.Add(new ContentChunk
{
Text = $"On-screen text: {textTrack.Text}",
ContentType = ContentType.ImageOcr,
Metadata = new Dictionary<string, object?>
{
["source"] = "video_ocr",
["text_type"] = textTrack.TextType.ToString(),
["start_time"] = textTrack.StartTime
}
});
}
return chunks;
}
private string BuildSceneText(VideoContext context, SceneSegment scene)
{
var parts = new List<string>();
if (!string.IsNullOrEmpty(scene.Label))
parts.Add($"Scene: {scene.Label}");
parts.Add($"[{FormatTime(scene.StartTime)} - {FormatTime(scene.EndTime)}]");
if (scene.KeyTerms.Count > 0)
parts.Add($"Topics: {string.Join(", ", scene.KeyTerms)}");
// Add utterances in this scene
var sceneUtterances = context.Utterances
.Where(u => u.StartTime >= scene.StartTime && u.EndTime <= scene.EndTime)
.OrderBy(u => u.StartTime);
if (sceneUtterances.Any())
parts.Add($"Speech: {string.Join(" ", sceneUtterances.Select(u => u.Text))}");
return string.Join("\n", parts);
}
}
Ось приклад виjścia для фільму:
{
"chunks": [
{
"text": "Scene: Opening montage\n[0:00 - 2:34]\nTopics: city, night, traffic\nSpeech: The year is 2049. The world has changed.",
"contentType": "Summary",
"metadata": {
"source": "video_scene",
"scene_id": "abc123",
"key_terms": ["city", "night", "traffic"],
"start_time": 0.0,
"end_time": 154.0
}
},
{
"text": "The detective arrived at the crime scene. Forensics had already processed the area.",
"contentType": "Transcript",
"metadata": {
"source": "video_transcript",
"time_window": "2:34 - 3:34",
"utterance_count": 4
}
},
{
"text": "On-screen text: LOS ANGELES 2049",
"contentType": "ImageOcr",
"metadata": {
"source": "video_ocr",
"text_type": "Title"
}
}
]
}
| Стадія МSK1 Час | Записки МSK3 | ||
|---|---|---|---|
| Ф.Ф.пробові метадані | |||
| Виявление вистрелу МSK1 МSK2s | FFmpeg сценічний фильтр M | ||
| Видобуток ключових кадрів | |||
| deduplication dHash | |||
| Засадка пакету CLIP | |||
| ImageSummarizer OCR МSK1 ~120s МSK3 \50 клавіатурні кадри з текстом | |||
| Видобуток аудіо МSK1 МSK2s | FFmpeg | ||
| МSK0 Перепис шепоту | МSK2s | ♫ ♫ МSK4 ♫ годинами мовлення ♫ | ♫ |
| Голосова диарізація МSK1 МSK2s | ECAPAM SK4TDNN мSK5 | ||
| Видобуток NER МSK1 МSK2s | BERTM SK4NER на транскрипції M | ||
| кластерування сцен МSK1 МSK2s | | | ||
| генерування доказів | |||
| Уся | ~8-10 хвилин |
| МSK0 Оптимізація | Заощадження МSK2 | |
|---|---|---|
| відокремлення від dHash | МSK2 кадри відфільтровані МSK3 ~24s CLIP зачувані ♫ | ♫ |
| Batch CLIP | ||
| Конструкція трубопроводу МSK1 Повторює зображенняСуммарізераM SK2АудіоСаммаризерові хвилі | ||
| Кількість заощаджень | ~3-4 хвилин |
| Komponenт МSK1 Пам 'ять МSK2 | |
|---|---|
| CLIP ViT | |
| Основа для шепоту | |
| МSK0 ECAPA-TDNN МSK2 M SK3MB | |
| МSK0 BERT-NER M SK2 МSK3MB | |
| Пік | ~1.5ГБ |
VideoSummarizer записує як IPipeline для автоматичної маршрутізації:
// In Program.cs
builder.Services.AddDocSummarizer(builder.Configuration.GetSection("DocSummarizer"));
builder.Services.AddDocSummarizerImages(builder.Configuration.GetSection("Images"));
builder.Services.AddVideoSummarizer(); // NEW
builder.Services.AddPipelineRegistry(); // Must be last
// Auto-routing by extension
var registry = services.GetRequiredService<IPipelineRegistry>();
var pipeline = registry.FindForFile("movie.mp4"); // Returns VideoPipeline
var result = await pipeline.ProcessAsync("movie.mp4");
Поддерживані rozszerzenia:
.mp4, .mkv, .avi, .mov, .wmv, .webm, .flv, .m4v, .mpeg, .mpgVideoSummarizer показує, що з 'єднання трубопроводу шкалки:
Результатом є те, що часовий фільм ":" стає структурованим свідченням сигналу з сценами, транскрипціями, об 'єктами, МSK4 та вбудовами для RAG запитів, таких як МSK5 .
Снижена модель RAG для відео:
Ingestion: Video → 16 waves → Signals + Evidence (scenes, transcripts, entities)
Storage: Signals (indexed) + Embeddings (CLIP, voice) + Evidence (chunks)
Query: Filter (SQL) → Search (BM25 + vector) → Synthesize (LLM, ~5 results)
Система можливостей:
Startup: Detect GPU → Load ModelManifest (YAML) → Initialize SignalSink
Activation: Component requests model → Lazy download → Signal "ModelAvailable"
Routing: Route to best provider → Fallback chain → Backpressure control
Atoms: Rate limiting + Time estimation + Pipeline balancing
Це Стримана неясність на шкалі:
LLM працює на комп 'ютерних свідченнях, а не на відео.
Корейні візерунки:
Reduced RAG Implementations:
| частина МSK1 візерунок МSK2 фокус | ||
|---|---|---|
| 1 | Стримана неясність | Едина компонента МSK1 |
| 2 | Обмежений фузій МСМ | Багато компонентів МSK1 |
| 3 | Перетягування контексту МSK0 Час / пам 'ять | |
| 4 | Інтелект зображення | Архітектура хвиль |
| 4.1 | Трійковий OCR провід | OCRM SK1 Моделі ONNX, плівкові смуги |
| 4.2 | AudioSummarizer | Судовий аудіоM SK1 диарізація спика МSK2 |
| 4.3 | Відеозберегатель ( цей artykuł ) | Відеоорганізація, пакет CLIPM SK1 NER |
Далі: Мультифікатор МSK1модальний граф RAG з lucidRAG, що об 'єднує всі чотири підсумовники в єдиний граф знань, з' єднаним між собою
Усі частини йдуть за однаковим інваріантом: ймовірнісні компоненти запропонують; детерміністичні системи тривають.
© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.