fix(media-assets): download stable release bundle
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -86,15 +86,16 @@ send_prompt_via_composer(page, "第一行\n第二行")
|
||||
|
||||
读取背景音乐、字体、水印和 ffmpeg 工具。
|
||||
|
||||
如果目录不存在,会优先通过 ZIP 下载公开仓库:
|
||||
如果目录不存在,会优先下载稳定 release bundle(不依赖用户电脑安装 Git):
|
||||
|
||||
`https://git.jc2009.com/client-commons/media-assets/archive/main.zip`
|
||||
`https://git.jc2009.com/client-commons/media-assets/releases/download/vlatest/media-assets.zip`
|
||||
|
||||
这不依赖用户电脑安装 Git。
|
||||
bundle 内含背景音乐、字体与水印;**不包含** ffmpeg 二进制。ffmpeg / ffprobe 仍由 bundle 内 `manifest.json` 的 `tools.ffmpeg` 下载源单独拉取。
|
||||
|
||||
可通过环境变量覆盖:
|
||||
|
||||
`MEDIA_ASSETS_ROOT=/path/to/custom/media-assets`
|
||||
- `MEDIA_ASSETS_ROOT=/path/to/custom/media-assets` — 本地资源目录
|
||||
- `MEDIA_ASSETS_BUNDLE_URL=https://example.com/media-assets.zip` — bundle 下载地址
|
||||
|
||||
### 录屏合成(`screencast`)
|
||||
|
||||
|
||||
@@ -18,9 +18,10 @@ from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
MEDIA_ASSETS_ZIP_URL = (
|
||||
"https://git.jc2009.com/client-commons/media-assets/archive/main.zip"
|
||||
MEDIA_ASSETS_BUNDLE_URL = (
|
||||
"https://git.jc2009.com/client-commons/media-assets/releases/download/vlatest/media-assets.zip"
|
||||
)
|
||||
MEDIA_ASSETS_ZIP_URL = MEDIA_ASSETS_BUNDLE_URL
|
||||
MEDIA_ASSETS_GIT_URL = "https://git.jc2009.com/client-commons/media-assets"
|
||||
|
||||
_WIN_DEFAULT_DATA_ROOT = Path(r"D:\jiangchang-data")
|
||||
@@ -109,6 +110,12 @@ def _env_dict(env: dict[str, str] | None) -> dict[str, str]:
|
||||
return env if env is not None else dict(os.environ)
|
||||
|
||||
|
||||
def _media_assets_bundle_url(env: dict[str, str] | None = None) -> str:
|
||||
env_map = _env_dict(env)
|
||||
override = (env_map.get("MEDIA_ASSETS_BUNDLE_URL") or "").strip()
|
||||
return override or MEDIA_ASSETS_BUNDLE_URL
|
||||
|
||||
|
||||
def _resolve_jiangchang_data_root(
|
||||
data_root: str | Path | None = None,
|
||||
env: dict[str, str] | None = None,
|
||||
@@ -474,12 +481,16 @@ def _try_download_via_zip(
|
||||
shared: Path,
|
||||
target: Path,
|
||||
warnings: list[str],
|
||||
*,
|
||||
env: dict[str, str] | None = None,
|
||||
url: str | None = None,
|
||||
) -> bool:
|
||||
download_root = _download_dir(shared)
|
||||
download_root.mkdir(parents=True, exist_ok=True)
|
||||
zip_path = download_root / "media-assets.zip"
|
||||
extracted = download_root / "extracted"
|
||||
staging = download_root / "staging"
|
||||
bundle_url = url or _media_assets_bundle_url(env)
|
||||
|
||||
try:
|
||||
if staging.exists():
|
||||
@@ -487,7 +498,7 @@ def _try_download_via_zip(
|
||||
if extracted.exists():
|
||||
shutil.rmtree(extracted, ignore_errors=True)
|
||||
|
||||
_download_zip(MEDIA_ASSETS_ZIP_URL, zip_path)
|
||||
_download_zip(bundle_url, zip_path)
|
||||
_extract_zip(zip_path, extracted)
|
||||
|
||||
found = _find_media_assets_root_in_tree(extracted)
|
||||
@@ -565,10 +576,15 @@ def _needs_media_repo_content(status: MediaAssetsStatus) -> bool:
|
||||
)
|
||||
|
||||
|
||||
def _maybe_repair_music_content(root: Path, status: MediaAssetsStatus) -> MediaAssetsStatus:
|
||||
def _maybe_repair_music_content(
|
||||
root: Path,
|
||||
status: MediaAssetsStatus,
|
||||
*,
|
||||
env: dict[str, str] | None = None,
|
||||
) -> MediaAssetsStatus:
|
||||
if not _needs_music_content_repair(status):
|
||||
return status
|
||||
if _media_assets_root_overridden(None):
|
||||
if _media_assets_root_overridden(env):
|
||||
return status
|
||||
|
||||
warnings: list[str] = []
|
||||
@@ -581,7 +597,7 @@ def _maybe_repair_music_content(root: Path, status: MediaAssetsStatus) -> MediaA
|
||||
return _maybe_ensure_ffmpeg_tools(root, refreshed)
|
||||
|
||||
shared = _shared_parent(root)
|
||||
if _try_download_via_zip(shared, root, warnings):
|
||||
if _try_download_via_zip(shared, root, warnings, env=env):
|
||||
refreshed = _inspect_media_assets(root, source="zip")
|
||||
for warning in warnings:
|
||||
if warning not in refreshed.warnings:
|
||||
@@ -631,12 +647,13 @@ def ensure_media_assets(
|
||||
shared.mkdir(parents=True, exist_ok=True)
|
||||
warnings: list[str] = []
|
||||
|
||||
if _try_download_via_zip(shared, root, warnings):
|
||||
if _try_download_via_zip(shared, root, warnings, env=env_map):
|
||||
return _maybe_ensure_ffmpeg_tools(
|
||||
root,
|
||||
_maybe_repair_music_content(
|
||||
root,
|
||||
_inspect_media_assets(root, source="zip"),
|
||||
env=env_map,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -650,6 +667,7 @@ def ensure_media_assets(
|
||||
root,
|
||||
source="git" if _is_git_repo(root) else "local",
|
||||
),
|
||||
env=env_map,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -657,7 +675,7 @@ def ensure_media_assets(
|
||||
for warning in warnings:
|
||||
if warning not in final.warnings:
|
||||
final.warnings.append(warning)
|
||||
repaired = _maybe_repair_music_content(root, final)
|
||||
repaired = _maybe_repair_music_content(root, final, env=env_map)
|
||||
for warning in warnings:
|
||||
if warning not in repaired.warnings:
|
||||
repaired.warnings.append(warning)
|
||||
|
||||
@@ -58,6 +58,56 @@ def _install_tools_from_manifest(root: Path, warnings: list[str]) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
def _build_archive_zip(dest: Path, inner_name: str) -> None:
|
||||
manifest = _sample_manifest()
|
||||
with zipfile.ZipFile(dest, "w") as zf:
|
||||
zf.writestr(f"{inner_name}/README.md", "# media-assets\n")
|
||||
zf.writestr(f"{inner_name}/manifest.json", json.dumps(manifest, ensure_ascii=False))
|
||||
zf.writestr(f"{inner_name}/music/calm/track.mp3", b"x" * 256)
|
||||
zf.writestr(f"{inner_name}/fonts/.keep", "")
|
||||
zf.writestr(f"{inner_name}/watermark/.keep", "")
|
||||
|
||||
|
||||
def test_default_bundle_url_is_release() -> None:
|
||||
url = ma._media_assets_bundle_url()
|
||||
assert url == ma.MEDIA_ASSETS_BUNDLE_URL
|
||||
assert url.endswith("/releases/download/vlatest/media-assets.zip")
|
||||
assert "/archive/main.zip" not in url
|
||||
|
||||
|
||||
def test_bundle_url_env_override() -> None:
|
||||
override = "https://example.com/media-assets.zip"
|
||||
assert ma._media_assets_bundle_url(env={"MEDIA_ASSETS_BUNDLE_URL": override}) == override
|
||||
|
||||
|
||||
def test_zip_download_uses_bundle_url_override(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
data_root = tmp_path / "data"
|
||||
shared = data_root / "shared"
|
||||
target = shared / "media-assets"
|
||||
archive = tmp_path / "media-assets.zip"
|
||||
override = "https://example.com/media-assets.zip"
|
||||
|
||||
_build_archive_zip(archive, "media-assets")
|
||||
captured_urls: list[str] = []
|
||||
|
||||
def _fake_download(url: str, dest: Path) -> None:
|
||||
captured_urls.append(url)
|
||||
dest.write_bytes(archive.read_bytes())
|
||||
|
||||
monkeypatch.setattr(ma, "_download_zip", _fake_download)
|
||||
monkeypatch.setattr(ma, "_find_git_executable", lambda: None)
|
||||
monkeypatch.setattr(ma, "_try_download_ffmpeg_tools", _install_tools_from_manifest)
|
||||
|
||||
env = {
|
||||
"JIANGCHANG_DATA_ROOT": str(data_root),
|
||||
"MEDIA_ASSETS_BUNDLE_URL": override,
|
||||
}
|
||||
status = ma.ensure_media_assets(env=env)
|
||||
assert captured_urls == [override]
|
||||
assert target.is_dir()
|
||||
assert status.ready is True
|
||||
|
||||
|
||||
def test_media_assets_root_env_priority(tmp_path: Path) -> None:
|
||||
custom = tmp_path / "custom-media"
|
||||
custom.mkdir()
|
||||
@@ -132,23 +182,18 @@ def test_pick_background_music_stable_first(tmp_path: Path, monkeypatch: pytest.
|
||||
assert picked == music / "b-upbeat.mp3"
|
||||
|
||||
|
||||
def _build_archive_zip(dest: Path, inner_name: str) -> None:
|
||||
manifest = _sample_manifest()
|
||||
with zipfile.ZipFile(dest, "w") as zf:
|
||||
zf.writestr(f"{inner_name}/README.md", "# media-assets\n")
|
||||
zf.writestr(f"{inner_name}/manifest.json", json.dumps(manifest, ensure_ascii=False))
|
||||
zf.writestr(f"{inner_name}/music/calm/track.mp3", b"x" * 256)
|
||||
zf.writestr(f"{inner_name}/fonts/.keep", "")
|
||||
zf.writestr(f"{inner_name}/watermark/.keep", "")
|
||||
|
||||
|
||||
def test_zip_wrapper_layout_detected(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
@pytest.mark.parametrize("inner_name", ["media-assets", "media-assets-main"])
|
||||
def test_zip_wrapper_layout_detected(
|
||||
tmp_path: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
inner_name: str,
|
||||
) -> None:
|
||||
data_root = tmp_path / "data"
|
||||
shared = data_root / "shared"
|
||||
target = shared / "media-assets"
|
||||
archive = tmp_path / "media-assets.zip"
|
||||
|
||||
_build_archive_zip(archive, "media-assets-main")
|
||||
_build_archive_zip(archive, inner_name)
|
||||
|
||||
def _fake_download(url: str, dest: Path) -> None:
|
||||
dest.write_bytes(archive.read_bytes())
|
||||
@@ -226,6 +271,27 @@ def test_probe_media_assets_does_not_download(tmp_path: Path, monkeypatch: pytes
|
||||
assert status.ffmpeg_path is None
|
||||
|
||||
|
||||
def test_lfs_pointer_warning_in_probe_and_ensure(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
root = tmp_path / "data" / "shared" / "media-assets"
|
||||
_make_complete_assets(root)
|
||||
music = root / "music"
|
||||
(music / "calm" / "track.mp3").unlink()
|
||||
(music / "lfs-only.mp3").write_text(
|
||||
"version https://git-lfs.github.com/spec/v1\noid sha256:abc\nsize 123\n"
|
||||
)
|
||||
|
||||
monkeypatch.setattr(ma, "_try_download_via_zip", lambda *a, **k: False)
|
||||
monkeypatch.setattr(ma, "_try_download_via_git", lambda *a, **k: False)
|
||||
|
||||
probed = ma.probe_media_assets(env={"JIANGCHANG_DATA_ROOT": str(tmp_path / "data")})
|
||||
assert "background_music_lfs_pointer_only" in probed.warnings
|
||||
assert probed.music_ready is False
|
||||
|
||||
ensured = ma.ensure_media_assets(env={"JIANGCHANG_DATA_ROOT": str(tmp_path / "data")})
|
||||
assert "background_music_lfs_pointer_only" in ensured.warnings
|
||||
assert ensured.ready is False
|
||||
|
||||
|
||||
def test_pick_background_music_skips_lfs_pointer(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
root = tmp_path / "data" / "shared" / "media-assets"
|
||||
_make_complete_assets(root, with_ffmpeg=True, with_ffprobe=True)
|
||||
|
||||
Reference in New Issue
Block a user