chore: publish stable media assets bundle
Some checks failed
release / release (push) Failing after 19s
Some checks failed
release / release (push) Failing after 19s
Add CI validation, zip packaging, and vlatest release publishing for user-facing media-assets.zip downloads. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
104
scripts/validate_bundle.py
Normal file
104
scripts/validate_bundle.py
Normal file
@@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Validate a media-assets bundle directory before packaging or release."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
LFS_POINTER_PREFIX = b"version https://git-lfs.github.com/spec/v1"
|
||||
MIN_MP3_BYTES = 100 * 1024
|
||||
|
||||
|
||||
def _fail(message: str) -> None:
|
||||
print(f"ERROR: {message}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def _is_lfs_pointer(path: Path) -> bool:
|
||||
with path.open("rb") as handle:
|
||||
header = handle.read(256)
|
||||
return header.startswith(LFS_POINTER_PREFIX)
|
||||
|
||||
|
||||
def _require_path(root: Path, relative: str, *, must_be_dir: bool = False) -> Path:
|
||||
target = root / relative
|
||||
if not target.exists():
|
||||
_fail(f"missing required path: {relative}")
|
||||
if must_be_dir and not target.is_dir():
|
||||
_fail(f"expected directory: {relative}")
|
||||
if not must_be_dir and not target.is_file():
|
||||
_fail(f"expected file: {relative}")
|
||||
return target
|
||||
|
||||
|
||||
def _validate_manifest(manifest_path: Path) -> None:
|
||||
try:
|
||||
data = json.loads(manifest_path.read_text(encoding="utf-8"))
|
||||
except json.JSONDecodeError as exc:
|
||||
_fail(f"manifest.json is not valid JSON: {exc}")
|
||||
|
||||
try:
|
||||
win32 = data["tools"]["ffmpeg"]["win32-x64"]
|
||||
urls = win32["urls"]
|
||||
archive_paths = win32["archive_paths"]
|
||||
except (KeyError, TypeError) as exc:
|
||||
_fail(f"manifest.json missing required tools.ffmpeg.win32-x64 fields: {exc}")
|
||||
|
||||
if not isinstance(urls, list) or not urls:
|
||||
_fail("manifest.json tools.ffmpeg.win32-x64.urls must be a non-empty array")
|
||||
|
||||
for key in ("ffmpeg", "ffprobe"):
|
||||
if key not in archive_paths or not archive_paths[key]:
|
||||
_fail(f"manifest.json missing tools.ffmpeg.win32-x64.archive_paths.{key}")
|
||||
|
||||
|
||||
def validate_bundle(root: Path) -> list[tuple[Path, int]]:
|
||||
root = root.resolve()
|
||||
if not root.is_dir():
|
||||
_fail(f"bundle root is not a directory: {root}")
|
||||
|
||||
_require_path(root, "README.md")
|
||||
manifest_path = _require_path(root, "manifest.json")
|
||||
_require_path(root, "music", must_be_dir=True)
|
||||
_require_path(root, "fonts", must_be_dir=True)
|
||||
_require_path(root, "watermark", must_be_dir=True)
|
||||
|
||||
mp3_files = sorted(root.joinpath("music").rglob("*.mp3"))
|
||||
if not mp3_files:
|
||||
_fail("music/ must contain at least one .mp3 file")
|
||||
|
||||
mp3_stats: list[tuple[Path, int]] = []
|
||||
for mp3_path in mp3_files:
|
||||
size = mp3_path.stat().st_size
|
||||
if _is_lfs_pointer(mp3_path):
|
||||
rel = mp3_path.relative_to(root)
|
||||
_fail(f"{rel} is a Git LFS pointer, not a real mp3 file")
|
||||
if size <= MIN_MP3_BYTES:
|
||||
rel = mp3_path.relative_to(root)
|
||||
_fail(f"{rel} is too small ({size} bytes); expected > {MIN_MP3_BYTES} bytes")
|
||||
mp3_stats.append((mp3_path.relative_to(root), size))
|
||||
|
||||
_validate_manifest(manifest_path)
|
||||
return mp3_stats
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
args = argv if argv is not None else sys.argv[1:]
|
||||
if len(args) != 1:
|
||||
print("Usage: python scripts/validate_bundle.py <bundle-root>", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
root = Path(args[0])
|
||||
mp3_stats = validate_bundle(root)
|
||||
|
||||
print("OK: media-assets bundle validation passed")
|
||||
print(f"mp3 count: {len(mp3_stats)}")
|
||||
for rel_path, size in mp3_stats:
|
||||
print(f" - {rel_path.as_posix()}: {size} bytes")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user