884 lines
32 KiB
Python
884 lines
32 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import contextlib
|
|
import os
|
|
import shutil
|
|
import sys
|
|
import tempfile
|
|
from collections.abc import Iterable, Sequence
|
|
from pathlib import Path
|
|
|
|
VIDEO_EXTENSIONS = (".mp4", ".avi", ".mov", ".mkv", ".webm", ".flv")
|
|
# Additional artefact types stored under the same reference folders. Latent
|
|
# tensors (`.pt`) back the latent-slice regression tests used by flaky
|
|
# pixel-space models (e.g. LTX-2 distilled). Keeping them in the same upload
|
|
# flow means seeding a new test only requires one HF round-trip.
|
|
LATENT_EXTENSIONS = (".pt", )
|
|
REFERENCE_EXTENSIONS = VIDEO_EXTENSIONS + LATENT_EXTENSIONS + (".png", )
|
|
HF_TOKEN_ENV_KEYS = ("HF_API_KEY", "HUGGINGFACE_HUB_TOKEN", "HF_TOKEN")
|
|
HF_REPO_ENV_KEY = "FASTVIDEO_SSIM_REFERENCE_HF_REPO"
|
|
HF_REPO_TYPE_ENV_KEY = "FASTVIDEO_SSIM_REFERENCE_HF_REPO_TYPE"
|
|
BOOTSTRAP_ENV_KEY = "FASTVIDEO_SSIM_BOOTSTRAP_MODE"
|
|
|
|
DEFAULT_REPO_ID = "FastVideo/ssim-reference-videos"
|
|
DEFAULT_REPO_TYPE = "dataset"
|
|
DEFAULT_OUTPUT_QUALITY_TIER = "default"
|
|
FULL_OUTPUT_QUALITY_TIER = "full_quality"
|
|
ALL_OUTPUT_QUALITY_TIERS = "all"
|
|
REFERENCE_VIDEOS_DIRNAME = "reference_videos"
|
|
DRAFTS_DIRNAME = "drafts"
|
|
DEFAULT_DEVICE_REFERENCE_FOLDER = "L40S_reference_videos"
|
|
QUALITY_TIERS = (
|
|
DEFAULT_OUTPUT_QUALITY_TIER,
|
|
FULL_OUTPUT_QUALITY_TIER,
|
|
)
|
|
|
|
|
|
def _ssim_dir() -> Path:
|
|
return Path(__file__).resolve().parent
|
|
|
|
|
|
def _default_repo_id() -> str:
|
|
return os.environ.get(HF_REPO_ENV_KEY, DEFAULT_REPO_ID)
|
|
|
|
|
|
def _default_repo_type() -> str:
|
|
return os.environ.get(HF_REPO_TYPE_ENV_KEY, DEFAULT_REPO_TYPE)
|
|
|
|
|
|
def _iter_reference_files(root: Path) -> Iterable[Path]:
|
|
"""Yield supported references under `root`.
|
|
|
|
Used by copy-local and the "has local references" marker probe so that
|
|
image- and latent-only tests (no mp4) still satisfy readiness checks.
|
|
"""
|
|
for path in root.rglob("*"):
|
|
if path.is_file() and path.suffix.lower() in REFERENCE_EXTENSIONS:
|
|
yield path
|
|
|
|
|
|
def _discover_reference_dirs(base_dir: Path) -> list[Path]:
|
|
if not base_dir.exists():
|
|
return []
|
|
dirs = [p for p in base_dir.iterdir() if p.is_dir() and p.name.endswith("_reference_videos")]
|
|
return sorted(dirs)
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def _exclusive_download_lock(lock_path: Path):
|
|
"""Best-effort cross-process lock for reference video download/copy."""
|
|
lock_path.parent.mkdir(parents=True, exist_ok=True)
|
|
with lock_path.open("a", encoding="utf-8") as lock_file:
|
|
if os.name == "posix":
|
|
import fcntl
|
|
|
|
fcntl.flock(lock_file.fileno(), fcntl.LOCK_EX)
|
|
try:
|
|
yield
|
|
finally:
|
|
if os.name == "posix":
|
|
import fcntl
|
|
|
|
fcntl.flock(lock_file.fileno(), fcntl.LOCK_UN)
|
|
|
|
|
|
def _merge_downloaded_reference_content(
|
|
staging_dir: Path,
|
|
local_dir: Path,
|
|
) -> int:
|
|
copied_roots = 0
|
|
|
|
tier_root = staging_dir / REFERENCE_VIDEOS_DIRNAME
|
|
if tier_root.exists():
|
|
dst_tier_root = local_dir / REFERENCE_VIDEOS_DIRNAME
|
|
shutil.copytree(tier_root, dst_tier_root, dirs_exist_ok=True)
|
|
copied_roots += 1
|
|
|
|
# Legacy default layout support: <staging>/<GPU>_reference_videos/...
|
|
legacy_reference_dirs = _discover_reference_dirs(staging_dir)
|
|
for legacy_dir in legacy_reference_dirs:
|
|
dst_legacy_dir = local_dir / legacy_dir.name
|
|
shutil.copytree(legacy_dir, dst_legacy_dir, dirs_exist_ok=True)
|
|
copied_roots += 1
|
|
|
|
return copied_roots
|
|
|
|
|
|
def _resolve_device_reference_folder(
|
|
*,
|
|
explicit_device_folder: str | None,
|
|
reference_dir: Path | None,
|
|
) -> str:
|
|
if explicit_device_folder and explicit_device_folder.strip():
|
|
return explicit_device_folder.strip()
|
|
if reference_dir is not None and reference_dir.name.endswith("_reference_videos"):
|
|
return reference_dir.name
|
|
return DEFAULT_DEVICE_REFERENCE_FOLDER
|
|
|
|
|
|
def _reference_tier_root(base_dir: Path, quality_tier: str) -> Path:
|
|
return base_dir / REFERENCE_VIDEOS_DIRNAME / quality_tier
|
|
|
|
|
|
def _resolve_quality_tiers(quality_tier: str) -> list[str]:
|
|
if quality_tier == ALL_OUTPUT_QUALITY_TIERS:
|
|
return list(QUALITY_TIERS)
|
|
if quality_tier not in QUALITY_TIERS:
|
|
raise ValueError(f"Unsupported quality tier: {quality_tier}")
|
|
return [quality_tier]
|
|
|
|
|
|
def _discover_reference_dirs_for_tier(
|
|
base_dir: Path,
|
|
quality_tier: str,
|
|
) -> list[Path]:
|
|
tier_root = _reference_tier_root(base_dir, quality_tier)
|
|
tier_dirs = _discover_reference_dirs(tier_root)
|
|
if tier_dirs:
|
|
return tier_dirs
|
|
if quality_tier == DEFAULT_OUTPUT_QUALITY_TIER:
|
|
# Keep supporting legacy default refs at <ssim_dir>/<GPU>_reference_videos.
|
|
return _discover_reference_dirs(base_dir)
|
|
return []
|
|
|
|
|
|
def _has_local_reference_videos(base_dir: Path, quality_tier: str) -> bool:
|
|
marker_path = base_dir / REFERENCE_VIDEOS_DIRNAME / quality_tier / f".download_complete_{quality_tier}"
|
|
if not marker_path.exists():
|
|
return False
|
|
tier_root = _reference_tier_root(base_dir, quality_tier)
|
|
for ref_dir in _discover_reference_dirs(tier_root):
|
|
for _ in _iter_reference_files(ref_dir):
|
|
return True
|
|
return False
|
|
|
|
|
|
def _get_hf_token() -> str | None:
|
|
for key in HF_TOKEN_ENV_KEYS:
|
|
value = os.environ.get(key, "").strip()
|
|
if value:
|
|
return value
|
|
|
|
return None
|
|
|
|
|
|
def _load_hf_sdk():
|
|
try:
|
|
from huggingface_hub import HfApi, snapshot_download
|
|
except ImportError as exc:
|
|
raise RuntimeError(
|
|
"huggingface_hub is required for download/upload.\nInstall with: uv pip install huggingface_hub") from exc
|
|
return HfApi, snapshot_download
|
|
|
|
|
|
def copy_generated_to_reference(
|
|
generated_dir: Path,
|
|
reference_dir: Path,
|
|
*,
|
|
dry_run: bool = False,
|
|
) -> int:
|
|
if not generated_dir.exists():
|
|
raise FileNotFoundError(f"Generated directory not found: {generated_dir}")
|
|
|
|
copied = 0
|
|
for ref_file in _iter_reference_files(generated_dir):
|
|
rel = ref_file.relative_to(generated_dir)
|
|
dst = reference_dir / rel
|
|
if dry_run:
|
|
print(f"[dry-run] {ref_file} -> {dst}")
|
|
else:
|
|
dst.parent.mkdir(parents=True, exist_ok=True)
|
|
shutil.copy2(ref_file, dst)
|
|
print(f"Copied: {rel}")
|
|
copied += 1
|
|
return copied
|
|
|
|
|
|
def download_reference_videos(
|
|
*,
|
|
repo_id: str,
|
|
repo_type: str,
|
|
local_dir: Path,
|
|
quality_tiers: Sequence[str] | None = None,
|
|
device_folders: Sequence[str] | None = None,
|
|
token: str | None = None,
|
|
) -> None:
|
|
_, snapshot_download = _load_hf_sdk()
|
|
|
|
local_dir.mkdir(parents=True, exist_ok=True)
|
|
tiers = tuple(quality_tiers or QUALITY_TIERS)
|
|
for quality_tier in tiers:
|
|
if quality_tier not in QUALITY_TIERS:
|
|
raise ValueError(f"Unsupported quality tier: {quality_tier}")
|
|
allow_patterns: list[str] = []
|
|
for quality_tier in tiers:
|
|
tier_prefix = f"{REFERENCE_VIDEOS_DIRNAME}/{quality_tier}"
|
|
if device_folders:
|
|
allow_patterns.extend(f"{tier_prefix}/{folder}/**" for folder in device_folders)
|
|
else:
|
|
allow_patterns.append(f"{tier_prefix}/*_reference_videos/**")
|
|
|
|
# Keep compatibility for older HF layout where default refs lived at
|
|
# repo root as <GPU>_reference_videos.
|
|
if quality_tier == DEFAULT_OUTPUT_QUALITY_TIER:
|
|
if device_folders:
|
|
allow_patterns.extend(f"{folder}/**" for folder in device_folders)
|
|
else:
|
|
allow_patterns.append("*_reference_videos/**")
|
|
|
|
# Work around long-path issues in some huggingface_hub versions:
|
|
# avoid `local_dir` mode and download into a short cache dir instead,
|
|
# then merge files into local_dir.
|
|
with tempfile.TemporaryDirectory(prefix="fv2-ssim-cache-") as tmp_cache_dir:
|
|
snapshot_path = snapshot_download(
|
|
repo_id=repo_id,
|
|
repo_type=repo_type,
|
|
cache_dir=tmp_cache_dir,
|
|
token=token,
|
|
allow_patterns=allow_patterns,
|
|
)
|
|
staging_dir = Path(snapshot_path)
|
|
copied_roots = _merge_downloaded_reference_content(
|
|
staging_dir=staging_dir,
|
|
local_dir=local_dir,
|
|
)
|
|
if copied_roots == 0:
|
|
raise RuntimeError(
|
|
"HF download completed but no reference_videos content was found in downloaded artifacts.")
|
|
for quality_tier in tiers:
|
|
marker_path = local_dir / REFERENCE_VIDEOS_DIRNAME / quality_tier / f".download_complete_{quality_tier}"
|
|
marker_path.parent.mkdir(parents=True, exist_ok=True)
|
|
marker_path.touch()
|
|
|
|
|
|
def upload_reference_videos(
|
|
*,
|
|
repo_id: str,
|
|
repo_type: str,
|
|
reference_dirs_by_tier: Sequence[tuple[str, Path]],
|
|
token: str,
|
|
private: bool,
|
|
model_id: str | None = None,
|
|
force: bool = False,
|
|
) -> None:
|
|
HfApi, _ = _load_hf_sdk()
|
|
api = HfApi(token=token)
|
|
api.create_repo(
|
|
repo_id=repo_id,
|
|
repo_type=repo_type,
|
|
private=private,
|
|
exist_ok=True,
|
|
)
|
|
|
|
try:
|
|
existing_repo_files = set(api.list_repo_files(repo_id=repo_id, repo_type=repo_type))
|
|
except Exception:
|
|
# Fresh repo or list failure — treat as empty so upload can proceed.
|
|
existing_repo_files = set()
|
|
|
|
for quality_tier, reference_dir in reference_dirs_by_tier:
|
|
if not reference_dir.exists():
|
|
raise FileNotFoundError(f"Reference directory not found: {reference_dir}")
|
|
base_in_repo = f"{REFERENCE_VIDEOS_DIRNAME}/{quality_tier}/{reference_dir.name}"
|
|
if model_id:
|
|
folder_path = reference_dir / model_id
|
|
if not folder_path.exists():
|
|
raise FileNotFoundError(f"Model subfolder not found for upload: {folder_path}")
|
|
path_in_repo = f"{base_in_repo}/{model_id}"
|
|
else:
|
|
folder_path = reference_dir
|
|
path_in_repo = base_in_repo
|
|
|
|
conflicts = sorted(f for f in existing_repo_files if f.startswith(f"{path_in_repo}/") or f == path_in_repo)
|
|
if conflicts and not force:
|
|
preview = "\n".join(f" - {c}" for c in conflicts[:10])
|
|
more = f"\n ... and {len(conflicts) - 10} more" if len(conflicts) > 10 else ""
|
|
raise RuntimeError(f"Refusing to overwrite existing HF files under {path_in_repo} "
|
|
f"({len(conflicts)} file(s) already present):\n{preview}{more}\n"
|
|
f"Re-run with --force to overwrite.")
|
|
|
|
target_desc = f"{reference_dir.name}/{model_id}" if model_id else reference_dir.name
|
|
print(f"Uploading {target_desc} ({quality_tier}) to {repo_id}/{path_in_repo} ...")
|
|
api.upload_folder(
|
|
repo_id=repo_id,
|
|
repo_type=repo_type,
|
|
folder_path=str(folder_path),
|
|
path_in_repo=path_in_repo,
|
|
token=token,
|
|
)
|
|
|
|
|
|
def _reference_folder_repo_relative(reference_folder: Path, base_dir: Path | None = None) -> Path:
|
|
resolved_reference_folder = reference_folder.resolve()
|
|
resolved_base_dir = (base_dir or _ssim_dir()).resolve()
|
|
tiered_root = resolved_base_dir / REFERENCE_VIDEOS_DIRNAME
|
|
try:
|
|
relative = resolved_reference_folder.relative_to(tiered_root)
|
|
except ValueError:
|
|
try:
|
|
legacy_relative = resolved_reference_folder.relative_to(resolved_base_dir)
|
|
except ValueError as exc:
|
|
raise RuntimeError(
|
|
f"Reference folder {reference_folder} is not under SSIM directory {resolved_base_dir}") from exc
|
|
if not legacy_relative.parts or not legacy_relative.parts[0].endswith("_reference_videos"):
|
|
raise RuntimeError(
|
|
f"Reference folder {reference_folder} does not match reference_videos/<tier>/<device>/<model>/<backend>"
|
|
)
|
|
relative = Path(DEFAULT_OUTPUT_QUALITY_TIER, *legacy_relative.parts)
|
|
|
|
if len(relative.parts) < 4:
|
|
raise RuntimeError(f"Reference folder {reference_folder} does not include <quality>/<device>/<model>/<backend>")
|
|
return relative
|
|
|
|
|
|
def upload_draft_reference_artifact(
|
|
*,
|
|
repo_id: str,
|
|
repo_type: str,
|
|
generated_artifact_path: Path,
|
|
reference_folder: Path,
|
|
token: str | None = None,
|
|
base_dir: Path | None = None,
|
|
private: bool = False,
|
|
) -> str:
|
|
resolved_token = token or _get_hf_token()
|
|
if resolved_token is None:
|
|
raise RuntimeError("Hugging Face API key is required for SSIM bootstrap draft upload. "
|
|
"Set HF_API_KEY, HUGGINGFACE_HUB_TOKEN, or HF_TOKEN.")
|
|
if not generated_artifact_path.exists():
|
|
raise FileNotFoundError(f"Generated artifact not found: {generated_artifact_path}")
|
|
|
|
relative_reference_folder = _reference_folder_repo_relative(
|
|
reference_folder=reference_folder,
|
|
base_dir=base_dir,
|
|
)
|
|
path_in_repo = f"{DRAFTS_DIRNAME}/{relative_reference_folder.as_posix()}/{generated_artifact_path.name}"
|
|
|
|
HfApi, _ = _load_hf_sdk()
|
|
api = HfApi(token=resolved_token)
|
|
api.create_repo(
|
|
repo_id=repo_id,
|
|
repo_type=repo_type,
|
|
private=private,
|
|
exist_ok=True,
|
|
)
|
|
print(f"Uploading SSIM draft reference to {repo_id}/{path_in_repo} ...")
|
|
api.upload_file(
|
|
repo_id=repo_id,
|
|
repo_type=repo_type,
|
|
path_or_fileobj=str(generated_artifact_path),
|
|
path_in_repo=path_in_repo,
|
|
token=resolved_token,
|
|
)
|
|
print("Draft reference uploaded. Review before promotion.")
|
|
return path_in_repo
|
|
|
|
|
|
def promote_draft_references(
|
|
*,
|
|
repo_id: str,
|
|
repo_type: str,
|
|
quality_tier: str,
|
|
device_folder: str,
|
|
model_id: str,
|
|
token: str,
|
|
attention_backend: str | None = None,
|
|
private: bool = False,
|
|
force: bool = False,
|
|
) -> None:
|
|
HfApi, snapshot_download = _load_hf_sdk()
|
|
api = HfApi(token=token)
|
|
api.create_repo(
|
|
repo_id=repo_id,
|
|
repo_type=repo_type,
|
|
private=private,
|
|
exist_ok=True,
|
|
)
|
|
draft_prefix_parts = [
|
|
DRAFTS_DIRNAME,
|
|
quality_tier,
|
|
device_folder,
|
|
model_id,
|
|
]
|
|
reference_prefix_parts = [
|
|
REFERENCE_VIDEOS_DIRNAME,
|
|
quality_tier,
|
|
device_folder,
|
|
model_id,
|
|
]
|
|
if attention_backend:
|
|
draft_prefix_parts.append(attention_backend)
|
|
reference_prefix_parts.append(attention_backend)
|
|
draft_prefix = "/".join(draft_prefix_parts)
|
|
reference_prefix = "/".join(reference_prefix_parts)
|
|
|
|
try:
|
|
existing_repo_files = set(api.list_repo_files(repo_id=repo_id, repo_type=repo_type))
|
|
except Exception:
|
|
# Fresh repo or list failure — treat as empty so promotion can report
|
|
# a clear "no drafts found" error instead of surfacing HF internals.
|
|
existing_repo_files = set()
|
|
draft_files = sorted(f for f in existing_repo_files if f.startswith(f"{draft_prefix}/"))
|
|
if not draft_files:
|
|
raise RuntimeError(f"No draft references found under {repo_id}/{draft_prefix}")
|
|
|
|
conflicts = sorted(f for f in existing_repo_files if f.startswith(f"{reference_prefix}/") or f == reference_prefix)
|
|
if conflicts and not force:
|
|
preview = "\n".join(f" - {c}" for c in conflicts[:10])
|
|
more = f"\n ... and {len(conflicts) - 10} more" if len(conflicts) > 10 else ""
|
|
raise RuntimeError(f"Refusing to overwrite existing HF files under {reference_prefix} "
|
|
f"({len(conflicts)} file(s) already present):\n{preview}{more}\n"
|
|
f"Re-run with --force to overwrite.")
|
|
|
|
with tempfile.TemporaryDirectory(prefix="fv2-ssim-draft-") as tmp_cache_dir:
|
|
snapshot_path = Path(
|
|
snapshot_download(
|
|
repo_id=repo_id,
|
|
repo_type=repo_type,
|
|
cache_dir=tmp_cache_dir,
|
|
token=token,
|
|
allow_patterns=[f"{draft_prefix}/**"],
|
|
))
|
|
draft_root = snapshot_path / draft_prefix
|
|
if not draft_root.exists():
|
|
raise RuntimeError(f"Downloaded draft path is missing: {draft_root}")
|
|
print(f"Promoting {len(draft_files)} draft reference file(s) to {repo_id}/{reference_prefix} ...")
|
|
api.upload_folder(
|
|
repo_id=repo_id,
|
|
repo_type=repo_type,
|
|
folder_path=str(draft_root),
|
|
path_in_repo=reference_prefix,
|
|
token=token,
|
|
)
|
|
|
|
|
|
def _resolve_upload_reference_dirs(
|
|
*,
|
|
base_dir: Path,
|
|
quality_tier: str,
|
|
explicit_reference_dirs: Sequence[str],
|
|
device_folders: Sequence[str] | None = None,
|
|
) -> list[tuple[str, Path]]:
|
|
tiers = _resolve_quality_tiers(quality_tier)
|
|
selected_device_folders = set(device_folders or [])
|
|
|
|
if explicit_reference_dirs:
|
|
if len(tiers) != 1:
|
|
raise RuntimeError("--reference-dir requires a single --quality-tier (default or full_quality).")
|
|
resolved_tier = tiers[0]
|
|
return [(resolved_tier, Path(p).resolve()) for p in explicit_reference_dirs]
|
|
|
|
resolved: list[tuple[str, Path]] = []
|
|
for tier in tiers:
|
|
for reference_dir in _discover_reference_dirs_for_tier(base_dir, tier):
|
|
if selected_device_folders and reference_dir.name not in selected_device_folders:
|
|
continue
|
|
resolved.append((tier, reference_dir.resolve()))
|
|
return resolved
|
|
|
|
|
|
def ensure_reference_videos_available(
|
|
*,
|
|
local_dir: Path | None = None,
|
|
repo_id: str | None = None,
|
|
repo_type: str | None = None,
|
|
quality_tier: str = DEFAULT_OUTPUT_QUALITY_TIER,
|
|
) -> bool:
|
|
"""Return True if downloaded from HF, False if already present locally."""
|
|
if quality_tier not in QUALITY_TIERS:
|
|
raise ValueError(f"Unsupported quality tier: {quality_tier}")
|
|
target_dir = local_dir or _ssim_dir()
|
|
lock_path = target_dir / ".reference_videos_download.lock"
|
|
with _exclusive_download_lock(lock_path):
|
|
if _has_local_reference_videos(target_dir, quality_tier):
|
|
print(f"Reference videos ({quality_tier}) already available at {target_dir}")
|
|
return False
|
|
|
|
resolved_repo_id = repo_id or _default_repo_id()
|
|
resolved_repo_type = repo_type or _default_repo_type()
|
|
if not resolved_repo_id:
|
|
raise RuntimeError(
|
|
f"No local reference videos found and no HF repo configured.\nSet {HF_REPO_ENV_KEY} or pass --repo-id.")
|
|
|
|
print(f"Repo ID: {resolved_repo_id}")
|
|
print(f"Quality tier: {quality_tier}")
|
|
print(f"No local {quality_tier} reference videos found under {target_dir}. Starting download...")
|
|
try:
|
|
download_reference_videos(
|
|
repo_id=resolved_repo_id,
|
|
repo_type=resolved_repo_type,
|
|
local_dir=target_dir,
|
|
quality_tiers=[quality_tier],
|
|
)
|
|
print(f"Download completed for {quality_tier} reference videos.")
|
|
except Exception as exc:
|
|
print(f"ERROR: Failed to download {quality_tier} reference videos from {resolved_repo_id}.")
|
|
print(f"Suggested command to retry: "
|
|
f"python fastvideo/tests/ssim/reference_videos_cli.py download "
|
|
f"--quality-tier {quality_tier}")
|
|
raise
|
|
|
|
if not _has_local_reference_videos(target_dir, quality_tier):
|
|
raise RuntimeError(f"HF download completed but no {quality_tier} *_reference_videos content found.")
|
|
return True
|
|
|
|
|
|
def _build_parser() -> argparse.ArgumentParser:
|
|
parser = argparse.ArgumentParser(
|
|
prog="reference_videos_cli",
|
|
description=("Manage SSIM reference videos locally and on Hugging Face.\n"
|
|
"This tool can copy generated videos into reference folders,\n"
|
|
"download references from a public HF repo, and upload updates."),
|
|
epilog=("Examples:\n"
|
|
" # Copy default generated videos into local L40S refs\n"
|
|
" python fastvideo/tests/ssim/reference_videos_cli.py copy-local \\\n"
|
|
" --quality-tier default \\\n"
|
|
" --device-folder L40S_reference_videos \\\n"
|
|
" --reference-dir fastvideo/tests/ssim/reference_videos/default/L40S_reference_videos\n\n"
|
|
" # Download both default and full-quality references from HF\n"
|
|
" python fastvideo/tests/ssim/reference_videos_cli.py download \\\n"
|
|
" --repo-id FastVideo/ssim-reference-videos\n\n"
|
|
" # Upload both tiers and all GPU folders (fails if token is unset)\n"
|
|
" python fastvideo/tests/ssim/reference_videos_cli.py upload \\\n"
|
|
" --repo-id FastVideo/ssim-reference-videos"),
|
|
formatter_class=argparse.RawTextHelpFormatter,
|
|
)
|
|
|
|
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
|
|
copy_parser = subparsers.add_parser(
|
|
"copy-local",
|
|
help="Copy generated videos into a local reference folder.",
|
|
)
|
|
copy_parser.add_argument(
|
|
"--quality-tier",
|
|
choices=(DEFAULT_OUTPUT_QUALITY_TIER, FULL_OUTPUT_QUALITY_TIER),
|
|
default=DEFAULT_OUTPUT_QUALITY_TIER,
|
|
help="Generated quality tier to copy from.",
|
|
)
|
|
copy_parser.add_argument(
|
|
"--generated-dir",
|
|
type=Path,
|
|
default=None,
|
|
help=("Source generated directory. Default: <ssim_dir>/generated_videos/<quality-tier>/<device-folder>"),
|
|
)
|
|
copy_parser.add_argument(
|
|
"--device-folder",
|
|
default=None,
|
|
help=("GPU folder name (e.g., H200_reference_videos) used to build default generated/reference paths."),
|
|
)
|
|
copy_parser.add_argument(
|
|
"--reference-dir",
|
|
type=Path,
|
|
default=None,
|
|
help="Destination reference directory.",
|
|
)
|
|
copy_parser.add_argument(
|
|
"--dry-run",
|
|
action="store_true",
|
|
help="Print copy operations without writing files.",
|
|
)
|
|
|
|
download_parser = subparsers.add_parser(
|
|
"download",
|
|
help="Download reference videos from Hugging Face.",
|
|
)
|
|
download_parser.add_argument(
|
|
"--repo-id",
|
|
default=_default_repo_id(),
|
|
help=f"HF repo id. Default: env {HF_REPO_ENV_KEY} or {DEFAULT_REPO_ID}",
|
|
)
|
|
download_parser.add_argument(
|
|
"--repo-type",
|
|
default=_default_repo_type(),
|
|
help=f"HF repo type. Default: env {HF_REPO_TYPE_ENV_KEY} or {DEFAULT_REPO_TYPE}",
|
|
)
|
|
download_parser.add_argument(
|
|
"--local-dir",
|
|
type=Path,
|
|
default=_ssim_dir(),
|
|
help=("Local SSIM directory where references are stored under "
|
|
"reference_videos/<quality-tier>/<GPU>_reference_videos."),
|
|
)
|
|
download_parser.add_argument(
|
|
"--quality-tier",
|
|
choices=(
|
|
DEFAULT_OUTPUT_QUALITY_TIER,
|
|
FULL_OUTPUT_QUALITY_TIER,
|
|
ALL_OUTPUT_QUALITY_TIERS,
|
|
),
|
|
default=ALL_OUTPUT_QUALITY_TIERS,
|
|
help="Quality tier to download (default: all).",
|
|
)
|
|
download_parser.add_argument(
|
|
"--device-folder",
|
|
action="append",
|
|
default=[],
|
|
help="Specific reference folder to fetch (repeatable), e.g. L40S_reference_videos",
|
|
)
|
|
|
|
upload_parser = subparsers.add_parser(
|
|
"upload",
|
|
help="Upload local reference folders to Hugging Face.",
|
|
)
|
|
upload_parser.add_argument(
|
|
"--repo-id",
|
|
default=_default_repo_id(),
|
|
help=f"HF repo id. Default: env {HF_REPO_ENV_KEY} or {DEFAULT_REPO_ID}",
|
|
)
|
|
upload_parser.add_argument(
|
|
"--repo-type",
|
|
default=_default_repo_type(),
|
|
help=f"HF repo type. Default: env {HF_REPO_TYPE_ENV_KEY} or {DEFAULT_REPO_TYPE}",
|
|
)
|
|
upload_parser.add_argument(
|
|
"--reference-dir",
|
|
action="append",
|
|
default=[],
|
|
help=("Reference directory to upload (repeatable). "
|
|
"Requires single --quality-tier. "
|
|
"Default: discover folders under --base-dir for chosen tier(s)."),
|
|
)
|
|
upload_parser.add_argument(
|
|
"--quality-tier",
|
|
choices=(
|
|
DEFAULT_OUTPUT_QUALITY_TIER,
|
|
FULL_OUTPUT_QUALITY_TIER,
|
|
ALL_OUTPUT_QUALITY_TIERS,
|
|
),
|
|
default=ALL_OUTPUT_QUALITY_TIERS,
|
|
help="Quality tier to upload (default: all).",
|
|
)
|
|
upload_parser.add_argument(
|
|
"--device-folder",
|
|
action="append",
|
|
default=[],
|
|
help=("Specific GPU reference folder to upload (repeatable), e.g. H200_reference_videos."),
|
|
)
|
|
upload_parser.add_argument(
|
|
"--base-dir",
|
|
type=Path,
|
|
default=_ssim_dir(),
|
|
help=("Base SSIM directory that contains reference_videos/<quality-tier>/<GPU>_reference_videos."),
|
|
)
|
|
upload_parser.add_argument(
|
|
"--private",
|
|
action="store_true",
|
|
help="Create/use a private repo instead of public.",
|
|
)
|
|
upload_parser.add_argument(
|
|
"--model-id",
|
|
default=None,
|
|
help=("Restrict upload to a single model subfolder "
|
|
"(reference_videos/<tier>/<device>/<model_id>). "
|
|
"Use when seeding references for a single new test."),
|
|
)
|
|
upload_parser.add_argument(
|
|
"--force",
|
|
action="store_true",
|
|
help=("Allow overwriting files that already exist at the target path on "
|
|
"Hugging Face. Off by default so seeding a new test cannot "
|
|
"clobber existing references."),
|
|
)
|
|
|
|
promote_parser = subparsers.add_parser(
|
|
"promote-draft",
|
|
help="Promote reviewed draft references into the canonical HF reference layout.",
|
|
)
|
|
promote_parser.add_argument(
|
|
"--repo-id",
|
|
default=_default_repo_id(),
|
|
help=f"HF repo id. Default: env {HF_REPO_ENV_KEY} or {DEFAULT_REPO_ID}",
|
|
)
|
|
promote_parser.add_argument(
|
|
"--repo-type",
|
|
default=_default_repo_type(),
|
|
help=f"HF repo type. Default: env {HF_REPO_TYPE_ENV_KEY} or {DEFAULT_REPO_TYPE}",
|
|
)
|
|
promote_parser.add_argument(
|
|
"--quality-tier",
|
|
choices=(
|
|
DEFAULT_OUTPUT_QUALITY_TIER,
|
|
FULL_OUTPUT_QUALITY_TIER,
|
|
),
|
|
default=DEFAULT_OUTPUT_QUALITY_TIER,
|
|
help="Quality tier to promote.",
|
|
)
|
|
promote_parser.add_argument(
|
|
"--device-folder",
|
|
default=DEFAULT_DEVICE_REFERENCE_FOLDER,
|
|
help="GPU reference folder to promote, e.g. L40S_reference_videos.",
|
|
)
|
|
promote_parser.add_argument(
|
|
"--model-id",
|
|
required=True,
|
|
help="Model subfolder to promote from drafts.",
|
|
)
|
|
promote_parser.add_argument(
|
|
"--attention-backend",
|
|
default=None,
|
|
help="Optional backend subfolder to promote. Default promotes all backends under the model.",
|
|
)
|
|
promote_parser.add_argument(
|
|
"--private",
|
|
action="store_true",
|
|
help="Create/use a private repo instead of public.",
|
|
)
|
|
promote_parser.add_argument(
|
|
"--force",
|
|
action="store_true",
|
|
help="Allow overwriting existing canonical references. Off by default.",
|
|
)
|
|
|
|
ensure_parser = subparsers.add_parser(
|
|
"ensure",
|
|
help="Download refs only when the selected local quality tier is missing.",
|
|
)
|
|
ensure_parser.add_argument(
|
|
"--repo-id",
|
|
default=_default_repo_id(),
|
|
help=f"HF repo id. Default: env {HF_REPO_ENV_KEY} or {DEFAULT_REPO_ID}",
|
|
)
|
|
ensure_parser.add_argument(
|
|
"--repo-type",
|
|
default=_default_repo_type(),
|
|
help=f"HF repo type. Default: env {HF_REPO_TYPE_ENV_KEY} or {DEFAULT_REPO_TYPE}",
|
|
)
|
|
ensure_parser.add_argument(
|
|
"--local-dir",
|
|
type=Path,
|
|
default=_ssim_dir(),
|
|
help=("Local SSIM directory that should contain reference_videos/<quality-tier>/<GPU>_reference_videos."),
|
|
)
|
|
ensure_parser.add_argument(
|
|
"--quality-tier",
|
|
choices=(
|
|
DEFAULT_OUTPUT_QUALITY_TIER,
|
|
FULL_OUTPUT_QUALITY_TIER,
|
|
),
|
|
default=DEFAULT_OUTPUT_QUALITY_TIER,
|
|
help="Quality tier to ensure locally.",
|
|
)
|
|
|
|
return parser
|
|
|
|
|
|
def main(argv: Sequence[str] | None = None) -> int:
|
|
parser = _build_parser()
|
|
args = parser.parse_args(argv)
|
|
|
|
if args.command == "copy-local":
|
|
device_folder = _resolve_device_reference_folder(
|
|
explicit_device_folder=args.device_folder,
|
|
reference_dir=args.reference_dir,
|
|
)
|
|
generated_dir = args.generated_dir
|
|
if generated_dir is None:
|
|
tier_root = _ssim_dir() / "generated_videos" / args.quality_tier
|
|
tier_device_dir = tier_root / device_folder
|
|
if tier_device_dir.exists():
|
|
generated_dir = tier_device_dir
|
|
else:
|
|
# Backward compatibility for pre-device-folder layout.
|
|
generated_dir = tier_root
|
|
reference_dir = (args.reference_dir if args.reference_dir is not None else
|
|
(_ssim_dir() / REFERENCE_VIDEOS_DIRNAME / args.quality_tier / device_folder))
|
|
copied = copy_generated_to_reference(
|
|
generated_dir=generated_dir,
|
|
reference_dir=reference_dir,
|
|
dry_run=args.dry_run,
|
|
)
|
|
print(f"Done. {'Would copy' if args.dry_run else 'Copied'} {copied} video files.")
|
|
if not args.dry_run and copied > 0:
|
|
marker = (_ssim_dir() / REFERENCE_VIDEOS_DIRNAME / args.quality_tier /
|
|
f".download_complete_{args.quality_tier}")
|
|
marker.parent.mkdir(parents=True, exist_ok=True)
|
|
marker.touch()
|
|
return 0
|
|
|
|
if args.command == "download":
|
|
quality_tiers = _resolve_quality_tiers(args.quality_tier)
|
|
download_reference_videos(
|
|
repo_id=args.repo_id,
|
|
repo_type=args.repo_type,
|
|
local_dir=args.local_dir,
|
|
quality_tiers=quality_tiers,
|
|
device_folders=args.device_folder or None,
|
|
)
|
|
print("Download complete for quality tier(s): " + ", ".join(quality_tiers) + ".")
|
|
return 0
|
|
|
|
if args.command == "upload":
|
|
token = _get_hf_token()
|
|
if token is None:
|
|
raise RuntimeError(
|
|
"Hugging Face API key is required for upload. Set HF_API_KEY, HUGGINGFACE_HUB_TOKEN, or HF_TOKEN.")
|
|
|
|
reference_dirs_by_tier = _resolve_upload_reference_dirs(
|
|
base_dir=args.base_dir.resolve(),
|
|
quality_tier=args.quality_tier,
|
|
explicit_reference_dirs=args.reference_dir,
|
|
device_folders=args.device_folder or None,
|
|
)
|
|
if not reference_dirs_by_tier:
|
|
raise RuntimeError(
|
|
f"No *_reference_videos directories found for the selected quality tier(s) under {args.base_dir}")
|
|
|
|
upload_reference_videos(
|
|
repo_id=args.repo_id,
|
|
repo_type=args.repo_type,
|
|
reference_dirs_by_tier=reference_dirs_by_tier,
|
|
token=token,
|
|
private=args.private,
|
|
model_id=args.model_id,
|
|
force=args.force,
|
|
)
|
|
print("Upload complete.")
|
|
return 0
|
|
|
|
if args.command == "promote-draft":
|
|
token = _get_hf_token()
|
|
if token is None:
|
|
raise RuntimeError("Hugging Face API key is required for promote-draft. "
|
|
"Set HF_API_KEY, HUGGINGFACE_HUB_TOKEN, or HF_TOKEN.")
|
|
promote_draft_references(
|
|
repo_id=args.repo_id,
|
|
repo_type=args.repo_type,
|
|
quality_tier=args.quality_tier,
|
|
device_folder=args.device_folder,
|
|
model_id=args.model_id,
|
|
attention_backend=args.attention_backend,
|
|
token=token,
|
|
private=args.private,
|
|
force=args.force,
|
|
)
|
|
print("Promotion complete.")
|
|
return 0
|
|
|
|
if args.command == "ensure":
|
|
downloaded = ensure_reference_videos_available(
|
|
local_dir=args.local_dir,
|
|
repo_id=args.repo_id,
|
|
repo_type=args.repo_type,
|
|
quality_tier=args.quality_tier,
|
|
)
|
|
if downloaded:
|
|
print(f"{args.quality_tier} reference videos were missing and have been downloaded.")
|
|
else:
|
|
print(f"{args.quality_tier} reference videos already exist locally.")
|
|
return 0
|
|
|
|
parser.error(f"Unknown command: {args.command}")
|
|
return 2
|
|
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
raise SystemExit(main())
|
|
except KeyboardInterrupt:
|
|
print("Interrupted.", file=sys.stderr)
|
|
raise SystemExit(130)
|