Spaces:
Sleeping
Sleeping
| """Generate the precomputed demo artifacts in ``data/sample_outputs/``. | |
| This does NOT call the heavy CV/ASR models. Instead it pairs hand-authored, | |
| realistic transcripts with deterministic synthetic visual streams and then runs | |
| them through the **real** bookmark, fusion and metrics logic. | |
| That gives us: | |
| * internally consistent artifacts (timeline aligns with transcript), | |
| * a genuine exercise of the production code paths, | |
| * stable, license-free demo data with no large video committed to git. | |
| The script is data-driven: add another entry to ``VIDEO_SPECS`` to publish a new | |
| sample. Two samples ship by default: | |
| demo_001 — Thai-English ML deployment tutorial (code-switching) | |
| demo_002 — English-only "Intro to MLOps" slide-heavy explainer | |
| Run: python -m scripts.generate_sample_outputs | |
| """ | |
| from __future__ import annotations | |
| from dataclasses import dataclass, field | |
| from typing import Dict, List, Set, Tuple | |
| from src.bookmark_generation import generate_bookmarks | |
| from src.config import CONFIG | |
| from src.metrics import MetricsTracker | |
| from src.storage import write_json, sample_path | |
| from src.timeline_fusion import fuse_timeline | |
| from src.utils import format_timestamp | |
| FRAME_INTERVAL_SEC = 2.0 | |
| _SCREEN_LIKE = {"tv", "laptop", "cell phone"} | |
| # --------------------------------------------------------------------------- # | |
| # Spec model | |
| # --------------------------------------------------------------------------- # | |
| class VideoSpec: | |
| video_id: str | |
| title: str | |
| duration_sec: float | |
| # (start, end, language, text) | |
| segments: List[Tuple[float, float, str, str]] | |
| # (start, end, [objects present]) | |
| visual_sections: List[Tuple[float, float, List[str]]] | |
| scene_change_times: Set[int] | |
| stage_timings: Dict[str, float] | |
| search_latencies_ms: List[float] | |
| fps: float = 30.0 | |
| width: int = 1280 | |
| height: int = 720 | |
| source: str = "Synthetic demo content (no copyrighted video committed)." | |
| default_language: str = "en" | |
| extra_meta: Dict[str, object] = field(default_factory=dict) | |
| # --------------------------------------------------------------------------- # | |
| # Builders (parameterized by spec) | |
| # --------------------------------------------------------------------------- # | |
| def build_transcript(spec: VideoSpec) -> List[Dict[str, object]]: | |
| return [ | |
| {"id": i, "start_sec": s, "end_sec": e, "text": text, "language": lang} | |
| for i, (s, e, lang, text) in enumerate(spec.segments) | |
| ] | |
| def _objects_at(spec: VideoSpec, t: float) -> List[str]: | |
| for s, e, objs in spec.visual_sections: | |
| if s <= t < e: | |
| return objs | |
| return ["person"] | |
| def _conf_for(label: str, t: float) -> float: | |
| """Deterministic, plausible confidence in a sensible per-class range.""" | |
| base = {"person": 0.90, "laptop": 0.80, "tv": 0.74, "cell phone": 0.66}.get( | |
| label, 0.70 | |
| ) | |
| jitter = ((int(t) * 7 + len(label)) % 9) / 100.0 # 0.00..0.08, deterministic | |
| return round(min(0.99, base + jitter), 3) | |
| def build_visual_events(spec: VideoSpec) -> List[Dict[str, object]]: | |
| events: List[Dict[str, object]] = [] | |
| t = 0.0 | |
| while t <= spec.duration_sec: | |
| objs = _objects_at(spec, t) | |
| confidence = {o: _conf_for(o, t) for o in objs} | |
| ordered = sorted(confidence, key=confidence.get, reverse=True) | |
| if any(o in _SCREEN_LIKE for o in ordered) and "screen" not in ordered: | |
| ordered = ordered + ["screen"] | |
| scene_change = round(t) in spec.scene_change_times | |
| events.append( | |
| { | |
| "time_sec": round(t, 3), | |
| "time_label": format_timestamp(t), | |
| "visual_events": ordered, | |
| "confidence": confidence, | |
| "scene_change": scene_change, | |
| "method": "yolo11n.pt+scene (precomputed sample)", | |
| } | |
| ) | |
| t += FRAME_INTERVAL_SEC | |
| return events | |
| def build_metrics( | |
| spec: VideoSpec, n_frames: int, n_segments: int, n_visual: int, n_bm: int | |
| ) -> Dict: | |
| tracker = MetricsTracker(video_id=spec.video_id) | |
| tracker.stages_sec = dict(spec.stage_timings) | |
| for ms in spec.search_latencies_ms: | |
| tracker.record_search_latency(ms) | |
| tracker.count("frames_processed", n_frames) | |
| tracker.count("transcript_segments", n_segments) | |
| tracker.count("visual_events_detected", n_visual) | |
| tracker.count("bookmarks", n_bm) | |
| doc = tracker.to_dict() | |
| doc["video_metadata"] = { | |
| "video_id": spec.video_id, | |
| "title": spec.title, | |
| "duration_sec": spec.duration_sec, | |
| "fps": spec.fps, | |
| "width": spec.width, | |
| "height": spec.height, | |
| "frame_count": int(spec.duration_sec * spec.fps), | |
| "source": spec.source, | |
| } | |
| doc["config"] = { | |
| "frame_interval_sec": FRAME_INTERVAL_SEC, | |
| "asr_model_size": "base", | |
| "visual_model": "yolo11n.pt", | |
| "device": "Apple M4 CPU (int8)", | |
| } | |
| return doc | |
| def generate_one(spec: VideoSpec) -> None: | |
| transcript = build_transcript(spec) | |
| visual_events = build_visual_events(spec) | |
| bookmarks = generate_bookmarks(transcript, visual_events, config=CONFIG) | |
| timeline = fuse_timeline( | |
| video_id=spec.video_id, | |
| duration_sec=spec.duration_sec, | |
| transcript=transcript, | |
| visual_events=visual_events, | |
| bookmarks=bookmarks, | |
| config=CONFIG, | |
| ) | |
| n_visual = sum(len(v["visual_events"]) for v in visual_events) | |
| metrics = build_metrics( | |
| spec, | |
| n_frames=len(visual_events), | |
| n_segments=len(transcript), | |
| n_visual=n_visual, | |
| n_bm=len(bookmarks), | |
| ) | |
| write_json(sample_path(spec.video_id, "transcript"), transcript) | |
| write_json(sample_path(spec.video_id, "visual_events"), visual_events) | |
| write_json(sample_path(spec.video_id, "bookmarks"), bookmarks) | |
| write_json(sample_path(spec.video_id, "timeline"), timeline) | |
| write_json(sample_path(spec.video_id, "metrics"), metrics) | |
| print( | |
| f" {spec.video_id}: segments={len(transcript)} frames={len(visual_events)} " | |
| f"bookmarks={len(bookmarks)} visual_events={n_visual}" | |
| ) | |
| # --------------------------------------------------------------------------- # | |
| # Sample 1 — demo_001: Thai-English ML deployment tutorial (code-switching) | |
| # --------------------------------------------------------------------------- # | |
| DEMO_001 = VideoSpec( | |
| video_id="demo_001", | |
| title="Deploying ML Models: Model Serving & API Deployment (demo)", | |
| duration_sec=184.0, | |
| default_language="th", | |
| segments=[ | |
| (0.0, 6.2, "th", "สวัสดีครับ วันนี้เราจะมาเรียนรู้เรื่อง model serving และ API deployment กันนะครับ"), | |
| (6.2, 13.5, "th", "ก่อนอื่นเรามาดูภาพรวมของ pipeline ที่เราจะ build กันก่อน"), | |
| (13.5, 21.0, "en", "We start by training a simple machine learning model, then we save it to disk."), | |
| (21.0, 29.4, "th", "ขั้นตอนต่อไปคือการสร้าง REST API ด้วย FastAPI เพื่อให้ model ของเราพร้อมใช้งาน"), | |
| (29.4, 37.8, "en", "Here you can see the project structure on my screen, with the model file and the server code."), | |
| (37.8, 46.0, "th", "เรามาเขียน endpoint แรกกันครับ ชื่อว่า slash predict สำหรับรับ input"), | |
| (46.0, 54.5, "en", "The predict endpoint takes a JSON payload and returns the model prediction."), | |
| (54.5, 63.0, "th", "ตรงนี้สำคัญมากนะครับ เราต้อง validate input ก่อนเสมอเพื่อความปลอดภัย"), | |
| (63.0, 72.2, "en", "Now let's talk about Docker. We containerize the application so it runs anywhere."), | |
| (72.2, 80.5, "th", "ผมจะเขียน Dockerfile แบบง่าย ๆ ให้ดูนะครับ เริ่มจาก base image ของ Python"), | |
| (80.5, 89.0, "en", "After building the image, we run the container and expose port eight thousand."), | |
| (89.0, 98.3, "th", "คำถามที่หลายคนสงสัยคือ แล้วเราจะ deploy ขึ้น production ยังไง?"), | |
| (98.3, 107.0, "en", "We can deploy this container to a cloud service or to Hugging Face Spaces for a demo."), | |
| (107.0, 116.4, "th", "เรื่องของ latency ก็สำคัญครับ เราควรวัด response time ของ API เสมอ"), | |
| (116.4, 125.0, "en", "Let me show you how to add simple metrics logging to track p50 and p95 latency."), | |
| (125.0, 134.7, "th", "ต่อไปเป็นเรื่อง monitoring เราจะใช้ log file เก็บ metric แบบง่าย ๆ ก่อน"), | |
| (134.7, 144.0, "en", "Finally, remember to add a health check endpoint so the platform knows the service is alive."), | |
| (144.0, 153.5, "th", "สรุปนะครับ วันนี้เราได้เรียนรู้เรื่อง model serving, API, Docker และ deployment"), | |
| (153.5, 162.8, "en", "In summary, you now have a complete pipeline from model to a deployed API."), | |
| (162.8, 172.0, "th", "ถ้าชอบ video นี้ อย่าลืมกด like และ subscribe เพื่อไม่พลาด content ใหม่ ๆ นะครับ"), | |
| (172.0, 184.0, "en", "Thank you for watching, and see you in the next tutorial. Goodbye!"), | |
| ], | |
| visual_sections=[ | |
| (0.0, 13.0, ["person"]), | |
| (13.0, 29.0, ["person", "tv"]), | |
| (29.0, 63.0, ["person", "laptop", "tv"]), | |
| (63.0, 90.0, ["person", "laptop"]), | |
| (90.0, 134.0, ["person", "laptop", "tv"]), | |
| (134.0, 153.0, ["person", "laptop"]), | |
| (153.0, 162.0, ["person"]), | |
| (162.0, 184.0, ["person", "cell phone"]), | |
| ], | |
| scene_change_times={13, 29, 63, 90, 116, 134, 153, 162}, | |
| stage_timings={ | |
| "frame_extraction": 3.18, | |
| "audio_extraction": 1.05, | |
| "visual_analysis": 22.41, | |
| "asr": 39.76, | |
| "bookmark_generation": 0.012, | |
| "timeline_fusion": 0.031, | |
| }, | |
| search_latencies_ms=[3.1, 2.4, 4.8, 2.9, 3.6, 5.2, 2.7, 3.3, 4.1, 6.0], | |
| ) | |
| # --------------------------------------------------------------------------- # | |
| # Sample 2 — demo_002: English-only "Intro to MLOps" slide-heavy explainer | |
| # --------------------------------------------------------------------------- # | |
| DEMO_002 = VideoSpec( | |
| video_id="demo_002", | |
| title="Intro to MLOps: Datasets, Training, Serving & Monitoring (demo)", | |
| duration_sec=150.0, | |
| default_language="en", | |
| segments=[ | |
| (0.0, 7.0, "en", "Welcome to this short introduction to MLOps, the practice of taking machine learning models to production."), | |
| (7.0, 16.0, "en", "In this overview you'll see the full pipeline on the slides: data, training, serving, and monitoring."), | |
| (16.0, 25.0, "en", "Everything starts with a good dataset. We collect, clean, and label our training data."), | |
| (25.0, 35.0, "en", "On screen you can see the dataset summary, with the number of samples and the class balance."), | |
| (35.0, 45.0, "en", "Next we move on to training. We fit a simple model and track its accuracy on a validation set."), | |
| (45.0, 55.0, "en", "Here is the training curve. Notice how the validation loss flattens after a few epochs."), | |
| (55.0, 65.0, "en", "Once training is done, we save the model and wrap it in an inference function."), | |
| (65.0, 75.0, "en", "Now let's talk about serving. We expose the model through a REST API so other services can call it."), | |
| (75.0, 85.0, "en", "The API has a predict endpoint that takes a JSON request and returns the model's prediction."), | |
| (85.0, 95.0, "en", "How do we ship this to production reliably? We package everything into a Docker container."), | |
| (95.0, 105.0, "en", "After deployment, monitoring becomes critical. We track latency, errors, and prediction quality."), | |
| (105.0, 115.0, "en", "Let me show you the metrics dashboard, with p50 and p95 latency for each endpoint."), | |
| (115.0, 125.0, "en", "We also add a health check endpoint so the platform can restart the service if it fails."), | |
| (125.0, 135.0, "en", "A quick question: what happens when the data distribution changes over time? That is called drift."), | |
| (135.0, 145.0, "en", "To summarize, MLOps connects data, training, serving, deployment, and monitoring into one pipeline."), | |
| (145.0, 150.0, "en", "Thanks for watching this overview. In the next video we'll build the API step by step."), | |
| ], | |
| visual_sections=[ | |
| (0.0, 16.0, ["person", "tv"]), | |
| (16.0, 35.0, ["person", "tv"]), | |
| (35.0, 65.0, ["person", "laptop", "tv"]), | |
| (65.0, 95.0, ["person", "laptop"]), | |
| (95.0, 135.0, ["person", "tv"]), | |
| (135.0, 150.0, ["person"]), | |
| ], | |
| scene_change_times={16, 35, 65, 95, 115, 135}, | |
| stage_timings={ | |
| "frame_extraction": 2.61, | |
| "audio_extraction": 0.88, | |
| "visual_analysis": 18.24, | |
| "asr": 31.07, | |
| "bookmark_generation": 0.010, | |
| "timeline_fusion": 0.026, | |
| }, | |
| search_latencies_ms=[2.2, 3.0, 2.6, 4.1, 3.4, 2.8, 3.9, 2.5, 3.1, 4.6], | |
| ) | |
| VIDEO_SPECS = [DEMO_001, DEMO_002] | |
| def main() -> None: | |
| print(f"Writing sample outputs to {CONFIG.sample_outputs_dir}") | |
| for spec in VIDEO_SPECS: | |
| generate_one(spec) | |
| if __name__ == "__main__": | |
| main() | |