manifest.py
python
sha256:61100cca63d948098d334e1b600d8fea514568a1c26ef357bf8b6380fbd8a217
chore(timeline): remove unused RationalRate import in entity.py
Human
minor
⚠ breaking
8 days ago
| 1 | """Timeline sidecar manifest (mirrors ``muse.plugins.midi.manifest``).""" |
| 2 | |
| 3 | from __future__ import annotations |
| 4 | |
| 5 | import json |
| 6 | import logging |
| 7 | import pathlib |
| 8 | from typing import Literal, TypedDict |
| 9 | |
| 10 | from muse._version import __version__ |
| 11 | from muse.core.types import JsonObject, Manifest, load_json_file |
| 12 | |
| 13 | from muse.plugins.timeline.entity import IdentifiedProject |
| 14 | |
| 15 | logger = logging.getLogger(__name__) |
| 16 | |
| 17 | type _ProjectMap = dict[str, "TimelineProjectEntry"] |
| 18 | type _SequenceMap = dict[str, "TimelineSequenceEntry"] |
| 19 | type _TrackMap = dict[str, "TimelineTrackEntry"] |
| 20 | |
| 21 | |
| 22 | class TimelineClipEntry(TypedDict): |
| 23 | clip_id: str |
| 24 | name: str |
| 25 | source_media_id: str |
| 26 | occurrence: int |
| 27 | |
| 28 | |
| 29 | class TimelineTrackEntry(TypedDict): |
| 30 | track_identity: str |
| 31 | kind: Literal["video", "audio", "subtitle"] |
| 32 | ordinal: int |
| 33 | name: str |
| 34 | clips: list[TimelineClipEntry] |
| 35 | |
| 36 | |
| 37 | class TimelineSequenceEntry(TypedDict): |
| 38 | sequence_identity: str |
| 39 | name: str |
| 40 | ordinal: int |
| 41 | tracks: _TrackMap |
| 42 | |
| 43 | |
| 44 | class TimelineProjectEntry(TypedDict): |
| 45 | project_key: str |
| 46 | name: str |
| 47 | content_id: str |
| 48 | sequences: _SequenceMap |
| 49 | |
| 50 | |
| 51 | class TimelineManifest(TypedDict): |
| 52 | domain: Literal["timeline"] |
| 53 | schema_version: str |
| 54 | snapshot_id: str |
| 55 | files: Manifest |
| 56 | projects: _ProjectMap |
| 57 | |
| 58 | |
| 59 | def _timeline_manifests_dir(root: pathlib.Path) -> pathlib.Path: |
| 60 | return root / ".muse" / "timeline_manifests" |
| 61 | |
| 62 | |
| 63 | def build_timeline_manifest( |
| 64 | files: Manifest, |
| 65 | projects: dict[str, IdentifiedProject], |
| 66 | project_hashes: dict[str, str], |
| 67 | snapshot_id: str = "", |
| 68 | ) -> TimelineManifest: |
| 69 | projects_out: _ProjectMap = {} |
| 70 | for path, identified in sorted(projects.items()): |
| 71 | seq_map: _SequenceMap = {} |
| 72 | for id_seq in identified.sequences: |
| 73 | track_map: _TrackMap = {} |
| 74 | for id_tr in id_seq.tracks: |
| 75 | track_map[id_tr.track_identity] = TimelineTrackEntry( |
| 76 | track_identity=id_tr.track_identity, |
| 77 | kind=id_tr.kind, |
| 78 | ordinal=id_tr.track.ordinal, |
| 79 | name=id_tr.track.name, |
| 80 | clips=[ |
| 81 | TimelineClipEntry( |
| 82 | clip_id=ic.clip_id, |
| 83 | name=ic.clip.name, |
| 84 | source_media_id=ic.clip.source_media_id, |
| 85 | occurrence=ic.occurrence, |
| 86 | ) |
| 87 | for ic in id_tr.clips |
| 88 | ], |
| 89 | ) |
| 90 | seq_map[id_seq.sequence_identity] = TimelineSequenceEntry( |
| 91 | sequence_identity=id_seq.sequence_identity, |
| 92 | name=id_seq.sequence.name, |
| 93 | ordinal=id_seq.sequence.ordinal, |
| 94 | tracks=track_map, |
| 95 | ) |
| 96 | projects_out[path] = TimelineProjectEntry( |
| 97 | project_key=identified.project.project_key, |
| 98 | name=identified.project.name, |
| 99 | content_id=project_hashes[path], |
| 100 | sequences=seq_map, |
| 101 | ) |
| 102 | return TimelineManifest( |
| 103 | domain="timeline", |
| 104 | schema_version=__version__, |
| 105 | snapshot_id=snapshot_id, |
| 106 | files=files, |
| 107 | projects=projects_out, |
| 108 | ) |
| 109 | |
| 110 | |
| 111 | def write_timeline_manifest( |
| 112 | repo_root: pathlib.Path, |
| 113 | manifest: TimelineManifest, |
| 114 | ) -> pathlib.Path: |
| 115 | snapshot_id = manifest.get("snapshot_id", "") |
| 116 | if not snapshot_id: |
| 117 | raise ValueError("TimelineManifest.snapshot_id must be non-empty") |
| 118 | path = _timeline_manifests_dir(repo_root) / f"{snapshot_id}.json" |
| 119 | path.parent.mkdir(parents=True, exist_ok=True) |
| 120 | path.write_text(f"{json.dumps(manifest, indent=2, sort_keys=True)}\n") |
| 121 | logger.debug("Timeline manifest written: %s", snapshot_id[:8]) |
| 122 | return path |
| 123 | |
| 124 | |
| 125 | def _manifest_files(raw: JsonObject) -> Manifest: |
| 126 | files_in = raw.get("files") |
| 127 | files: Manifest = {} |
| 128 | if not isinstance(files_in, dict): |
| 129 | return files |
| 130 | for path_key, digest in files_in.items(): |
| 131 | if isinstance(path_key, str) and isinstance(digest, str): |
| 132 | files[path_key] = digest |
| 133 | return files |
| 134 | |
| 135 | |
| 136 | def read_timeline_manifest( |
| 137 | repo_root: pathlib.Path, |
| 138 | snapshot_id: str, |
| 139 | ) -> TimelineManifest | None: |
| 140 | path = _timeline_manifests_dir(repo_root) / f"{snapshot_id}.json" |
| 141 | if not path.exists(): |
| 142 | return None |
| 143 | raw = load_json_file(path) |
| 144 | if raw is None or not isinstance(raw, dict): |
| 145 | return None |
| 146 | if raw.get("domain") != "timeline": |
| 147 | return None |
| 148 | schema_version = raw.get("schema_version") |
| 149 | snap_id = raw.get("snapshot_id") |
| 150 | if not isinstance(schema_version, str) or not isinstance(snap_id, str): |
| 151 | return None |
| 152 | return TimelineManifest( |
| 153 | domain="timeline", |
| 154 | schema_version=schema_version, |
| 155 | snapshot_id=snap_id, |
| 156 | files=_manifest_files(raw), |
| 157 | projects={}, |
| 158 | ) |
File History
1 commit
sha256:61100cca63d948098d334e1b600d8fea514568a1c26ef357bf8b6380fbd8a217
chore(timeline): remove unused RationalRate import in entity.py
Human
minor
⚠
8 days ago