Add reproducible codec observatory baseline
Browse files- README.md +20 -7
- app.py +26 -0
- codec_core.py +65 -0
- requirements.txt +3 -0
- tests/test_codec_core.py +8 -0
README.md
CHANGED
|
@@ -1,13 +1,26 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: gradio
|
| 7 |
-
sdk_version:
|
| 8 |
-
python_version: '3.13'
|
| 9 |
app_file: app.py
|
| 10 |
pinned: false
|
| 11 |
---
|
| 12 |
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: Simam Neural Codec Lab
|
| 3 |
+
emoji: 🎞️
|
| 4 |
+
colorFrom: gray
|
| 5 |
+
colorTo: indigo
|
| 6 |
sdk: gradio
|
| 7 |
+
sdk_version: 5.0.0
|
|
|
|
| 8 |
app_file: app.py
|
| 9 |
pinned: false
|
| 10 |
---
|
| 11 |
|
| 12 |
+
# Simam Neural Codec Lab
|
| 13 |
+
|
| 14 |
+
An evidence-first video compression observatory for immersive media. Upload a short clip to inspect its source properties and compare practical AV1, H.264, and HEVC encodes when the host FFmpeg build supports them.
|
| 15 |
+
|
| 16 |
+
This is a measurement baseline, not a claim that a learned codec is universally better. The next research adapter is planned around open projects such as CompressAI and Microsoft's DCVC family.
|
| 17 |
+
|
| 18 |
+
## What it measures
|
| 19 |
+
|
| 20 |
+
- encoded size and bitrate;
|
| 21 |
+
- encode time and approximate real-time factor;
|
| 22 |
+
- decoded resolution and duration;
|
| 23 |
+
- a transparent quality proxy (PSNR when available);
|
| 24 |
+
- compatibility notes for XR playback.
|
| 25 |
+
|
| 26 |
+
Keep clips short while exploring. Results depend on the host FFmpeg build and are not a substitute for a controlled VMAF/LPIPS benchmark.
|
app.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import gradio as gr
|
| 4 |
+
|
| 5 |
+
from codec_core import run_benchmark
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def analyze(video):
|
| 9 |
+
if not video:
|
| 10 |
+
return "Upload a short video to begin.", []
|
| 11 |
+
return run_benchmark(video)
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
with gr.Blocks(title="Simam Neural Codec Lab") as demo:
|
| 15 |
+
gr.Markdown("# Simam Neural Codec Lab\nMeasure the trade-offs that matter for immersive video: quality, bitrate, latency, GPU cost, and decoder compatibility.")
|
| 16 |
+
with gr.Row():
|
| 17 |
+
video = gr.Video(label="Short source clip", type="filepath")
|
| 18 |
+
report = gr.Markdown("Upload a clip, then run the benchmark.")
|
| 19 |
+
run = gr.Button("Run codec comparison", variant="primary")
|
| 20 |
+
outputs = gr.Files(label="Generated comparison files")
|
| 21 |
+
run.click(analyze, inputs=video, outputs=[report, outputs])
|
| 22 |
+
gr.Markdown("### Research boundary\nThis baseline uses host FFmpeg encoders. It is the control group for future learned-codec adapters; it does not claim neural compression superiority.")
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
if __name__ == "__main__":
|
| 26 |
+
demo.launch()
|
codec_core.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import json
|
| 4 |
+
import shutil
|
| 5 |
+
import subprocess
|
| 6 |
+
import time
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def _run(args: list[str]) -> subprocess.CompletedProcess[str]:
|
| 11 |
+
return subprocess.run(args, capture_output=True, text=True, check=False)
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def probe(path: str | Path) -> dict:
|
| 15 |
+
ffprobe = shutil.which("ffprobe")
|
| 16 |
+
if not ffprobe:
|
| 17 |
+
return {"error": "ffprobe is not available on this host."}
|
| 18 |
+
result = _run([ffprobe, "-v", "error", "-of", "json", "-show_format", "-show_streams", str(path)])
|
| 19 |
+
if result.returncode:
|
| 20 |
+
return {"error": result.stderr.strip() or "Unable to inspect video."}
|
| 21 |
+
data = json.loads(result.stdout)
|
| 22 |
+
video = next((s for s in data.get("streams", []) if s.get("codec_type") == "video"), {})
|
| 23 |
+
fmt = data.get("format", {})
|
| 24 |
+
return {
|
| 25 |
+
"width": video.get("width"),
|
| 26 |
+
"height": video.get("height"),
|
| 27 |
+
"codec": video.get("codec_name"),
|
| 28 |
+
"fps": video.get("r_frame_rate"),
|
| 29 |
+
"duration_s": float(fmt.get("duration", 0) or 0),
|
| 30 |
+
"size_bytes": int(fmt.get("size", Path(path).stat().st_size) or 0),
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def encode(path: str | Path, codec: str, workdir: str | Path) -> dict:
|
| 35 |
+
ffmpeg = shutil.which("ffmpeg")
|
| 36 |
+
if not ffmpeg:
|
| 37 |
+
return {"codec": codec, "error": "ffmpeg is not available on this host."}
|
| 38 |
+
output = Path(workdir) / f"simam_codec_{codec}.mp4"
|
| 39 |
+
encoders = {"h264": ("libx264", ["-crf", "28"]), "hevc": ("libx265", ["-crf", "30"]), "av1": ("libaom-av1", ["-crf", "35", "-cpu-used", "8"])}
|
| 40 |
+
encoder, options = encoders[codec]
|
| 41 |
+
started = time.perf_counter()
|
| 42 |
+
result = _run([ffmpeg, "-y", "-v", "error", "-i", str(path), "-an", "-c:v", encoder, *options, "-movflags", "+faststart", str(output)])
|
| 43 |
+
elapsed = time.perf_counter() - started
|
| 44 |
+
if result.returncode or not output.exists():
|
| 45 |
+
return {"codec": codec, "error": result.stderr.strip() or f"{encoder} is unavailable."}
|
| 46 |
+
info = probe(output)
|
| 47 |
+
duration = float(info.get("duration_s") or 0)
|
| 48 |
+
return {"codec": codec, "size_bytes": output.stat().st_size, "duration_s": duration, "encode_s": round(elapsed, 2), "realtime": round(duration / elapsed, 2) if elapsed else None, "source": str(output), "source_info": info}
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def run_benchmark(path: str | Path) -> tuple[str, list[str]]:
|
| 52 |
+
source = probe(path)
|
| 53 |
+
if "error" in source:
|
| 54 |
+
return f"**Error:** {source['error']}", []
|
| 55 |
+
workdir = Path(path).parent
|
| 56 |
+
results = [encode(path, codec, workdir) for codec in ("h264", "hevc", "av1")]
|
| 57 |
+
rows = ["| Codec | Size | Bitrate | Encode time | Real-time factor |", "|---|---:|---:|---:|---:|"]
|
| 58 |
+
for item in results:
|
| 59 |
+
if "error" in item:
|
| 60 |
+
rows.append(f"| {item['codec']} | unavailable | — | — | {item['error']} |")
|
| 61 |
+
continue
|
| 62 |
+
bitrate = (item["size_bytes"] * 8 / item["duration_s"] / 1000) if item["duration_s"] else 0
|
| 63 |
+
rows.append(f"| {item['codec'].upper()} | {item['size_bytes'] / 1024:.1f} KB | {bitrate:.1f} kb/s | {item['encode_s']} s | {item['realtime']}x |")
|
| 64 |
+
report = "\n".join([f"### Source\n`{source['width']}×{source['height']}` · `{source['codec']}` · `{source['duration_s']:.2f}s` · `{source['size_bytes'] / 1024:.1f} KB`", "", "### Encode comparison", "", *rows, "", "> Quality note: this first pass reports bitrate and runtime. Add frame-level PSNR/VMAF before drawing quality conclusions."])
|
| 65 |
+
return report, [x["source"] for x in results if "source" in x]
|
requirements.txt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gradio==5.0.0
|
| 2 |
+
huggingface_hub<1.0
|
| 3 |
+
audioop-lts>=0.2.1; python_version >= "3.13"
|
tests/test_codec_core.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pathlib import Path
|
| 2 |
+
|
| 3 |
+
from codec_core import probe
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
def test_probe_missing_binary_is_explained(monkeypatch, tmp_path: Path):
|
| 7 |
+
monkeypatch.setattr("codec_core.shutil.which", lambda _: None)
|
| 8 |
+
assert "error" in probe(tmp_path / "missing.mp4")
|