Skip to content

Commit 9e260a5

Browse files
Merge pull request #106 from cbusillo/scene-aware-engine-bakeoff
Add scene-aware engine bakeoff planning
2 parents d38aa63 + 9cd8289 commit 9e260a5

4 files changed

Lines changed: 531 additions & 0 deletions

File tree

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,20 @@ saved to the folder profile, `Queue Folder Encode` is unlocked so the real
112112
folder job can enter the encode queue without letting a stale unsaved preview
113113
slip into production work.
114114

115+
For scene-aware engine research, generate a repeatable bakeoff plan from an
116+
existing manifest instead of replacing the production engine path directly:
117+
118+
```bash
119+
uv run mediaforce bakeoff path/to/run-manifest.json --all \
120+
--output ~/Desktop/mediaforce-bakeoff.json
121+
```
122+
123+
The bakeoff plan carries the same size-first defaults and per-item resolved
124+
policy used by Folder Studio, then lays out candidate commands and tool
125+
requirements for the current `ab-av1` path plus Av1an, Xav, and Auto-Boost. Use
126+
the plan to collect output size, runtime, selected CRF or quantizer, metric
127+
score, and review artifacts before choosing a production engine migration.
128+
115129
You can run Mediaforce either directly with `python3` or through `uv`:
116130

117131
```bash

mediaforce/cli.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from mediaforce.core.db_tables import run_manifests as run_manifests_table
1111
from mediaforce.execution import describe_item_plan, encode_manifest_items, promote_manifest_items, \
1212
validate_manifest_items
13+
from mediaforce.encoding.bakeoff import DEFAULT_BAKEOFF_ENGINES, build_bakeoff_plan, write_bakeoff_plan
1314
from mediaforce.library.folder_profiles import inspect_prefix
1415
from mediaforce.library.planner import recommend_item
1516
from mediaforce.library.run_manifests import build_run_manifest as build_db_run_manifest, \
@@ -98,6 +99,32 @@ def build_parser() -> argparse.ArgumentParser:
9899
_add_manifest_selection_args(compare_parser, require_manifest=False)
99100
_add_compare_clip_args(compare_parser)
100101

102+
bakeoff_parser = subparsers.add_parser("bakeoff", help="Write a scene-aware engine bakeoff plan")
103+
_add_manifest_selection_args(bakeoff_parser, require_manifest=False)
104+
bakeoff_parser.add_argument(
105+
"--engine",
106+
action="append",
107+
choices=DEFAULT_BAKEOFF_ENGINES,
108+
default=[],
109+
help="Candidate engine to include; defaults to all candidates",
110+
)
111+
bakeoff_parser.add_argument(
112+
"--output",
113+
type=Path,
114+
help="Write the bakeoff plan JSON to an explicit path",
115+
)
116+
bakeoff_parser.add_argument(
117+
"--artifact-dir",
118+
type=Path,
119+
help="Directory where bakeoff artifacts should be written by the candidate commands",
120+
)
121+
bakeoff_parser.add_argument(
122+
"--clip-duration",
123+
type=float,
124+
default=20.0,
125+
help="Review clip duration expected from each candidate engine",
126+
)
127+
101128
return parser
102129

103130

@@ -293,6 +320,29 @@ def main(argv: Sequence[str] | None = None) -> int:
293320
)
294321
return 0
295322

323+
if args.command == "bakeoff":
324+
manifest_path = _resolve_manifest_path(connection, args.manifest)
325+
manifest = _load_manifest(manifest_path)
326+
indexes = _resolve_indexes(manifest, args)
327+
artifact_dir = args.artifact_dir or config.paths.review_dir / "engine-bakeoff"
328+
plan = build_bakeoff_plan(
329+
config,
330+
manifest,
331+
indexes=indexes,
332+
engines=args.engine or None,
333+
output_dir=artifact_dir,
334+
clip_duration_seconds=args.clip_duration,
335+
)
336+
output_path = args.output or config.paths.run_manifest_dir / f"bakeoff-{manifest.get('run_id', 'latest')}.json"
337+
write_bakeoff_plan(plan, output_path)
338+
print(f"bakeoff plan {output_path}")
339+
for item in plan["items"]:
340+
print(
341+
f" item {item['index']}: target={_format_optional_size(item['target_size_bytes'])} "
342+
f"runtime={item['duration_seconds']:.0f}s engines={len(item['engines'])}"
343+
)
344+
return 0
345+
296346
return 1
297347

298348

@@ -586,6 +636,12 @@ def _format_size(size_bytes: int) -> str:
586636
return f"{size_bytes}B"
587637

588638

639+
def _format_optional_size(size_bytes: int | None) -> str:
640+
if size_bytes is None:
641+
return "unset"
642+
return _format_size(size_bytes)
643+
644+
589645
def _format_signed_bytes(size_bytes: int) -> str:
590646
prefix = "+" if size_bytes >= 0 else "-"
591647
return f"{prefix}{_format_size(abs(size_bytes))}"

0 commit comments

Comments
 (0)