unrender 0.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- unrender/__init__.py +15 -0
- unrender/__main__.py +4 -0
- unrender/adapters/__init__.py +33 -0
- unrender/adapters/artifacts.py +32 -0
- unrender/adapters/paths.py +105 -0
- unrender/adapters/rows.py +96 -0
- unrender/adapters/stems.py +143 -0
- unrender/cli/__init__.py +5 -0
- unrender/cli/commands/__init__.py +39 -0
- unrender/cli/commands/audio.py +283 -0
- unrender/cli/commands/context.py +342 -0
- unrender/cli/commands/export.py +13 -0
- unrender/cli/commands/face.py +54 -0
- unrender/cli/commands/health.py +64 -0
- unrender/cli/commands/labels.py +33 -0
- unrender/cli/commands/timeline.py +57 -0
- unrender/cli/commands/voice.py +116 -0
- unrender/cli/helpers.py +53 -0
- unrender/cli/main.py +39 -0
- unrender/cli/parser.py +411 -0
- unrender/cloning/__init__.py +5 -0
- unrender/cloning/voice.py +476 -0
- unrender/dialogue/__init__.py +27 -0
- unrender/dialogue/clusters.py +344 -0
- unrender/dialogue/dub_script.py +326 -0
- unrender/dialogue/lines.py +684 -0
- unrender/dialogue/plan_writers.py +108 -0
- unrender/dialogue/transcription.py +266 -0
- unrender/exports/__init__.py +5 -0
- unrender/exports/artifacts.py +84 -0
- unrender/identity/__init__.py +27 -0
- unrender/identity/face.py +551 -0
- unrender/identity/resolution.py +203 -0
- unrender/identity/voice.py +410 -0
- unrender/io/__init__.py +1 -0
- unrender/io/csv.py +14 -0
- unrender/io/json.py +14 -0
- unrender/manifests/__init__.py +37 -0
- unrender/manifests/fingerprints.py +94 -0
- unrender/manifests/loaders.py +259 -0
- unrender/manifests/models.py +58 -0
- unrender/manifests/store.py +50 -0
- unrender/media/__init__.py +1 -0
- unrender/media/audio.py +143 -0
- unrender/media/ffmpeg.py +229 -0
- unrender/media/names.py +8 -0
- unrender/media/timecode.py +44 -0
- unrender/project/__init__.py +17 -0
- unrender/project/config.py +149 -0
- unrender/project/paths.py +177 -0
- unrender/py.typed +0 -0
- unrender/separation/__init__.py +20 -0
- unrender/separation/audioshake.py +367 -0
- unrender/separation/audioshake_client.py +306 -0
- unrender/separation/bandit.py +448 -0
- unrender/separation/models.py +25 -0
- unrender/shots/__init__.py +15 -0
- unrender/shots/dx.py +128 -0
- unrender/shots/stems.py +217 -0
- unrender/speakers/__init__.py +33 -0
- unrender/speakers/labeling.py +225 -0
- unrender/speakers/registry.py +219 -0
- unrender/timeline/__init__.py +20 -0
- unrender/timeline/builder.py +485 -0
- unrender/timeline/media.py +130 -0
- unrender/timeline/sources.py +276 -0
- unrender-0.2.1.dist-info/METADATA +478 -0
- unrender-0.2.1.dist-info/RECORD +73 -0
- unrender-0.2.1.dist-info/WHEEL +5 -0
- unrender-0.2.1.dist-info/entry_points.txt +2 -0
- unrender-0.2.1.dist-info/licenses/LICENSE +201 -0
- unrender-0.2.1.dist-info/licenses/NOTICE +18 -0
- unrender-0.2.1.dist-info/top_level.txt +1 -0
unrender/__init__.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Unrender timeline reconstruction pipeline."""
|
|
2
|
+
|
|
3
|
+
from unrender.manifests import DialogueLineRecord, ManifestStore, ShotRecord, VoiceInput
|
|
4
|
+
from unrender.project import RunPaths
|
|
5
|
+
|
|
6
|
+
__version__ = "0.2.1"
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"DialogueLineRecord",
|
|
10
|
+
"ManifestStore",
|
|
11
|
+
"RunPaths",
|
|
12
|
+
"ShotRecord",
|
|
13
|
+
"VoiceInput",
|
|
14
|
+
"__version__",
|
|
15
|
+
]
|
unrender/__main__.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from unrender.adapters.artifacts import export_artifacts_to_paths
|
|
4
|
+
from unrender.adapters.paths import (
|
|
5
|
+
ArtifactExportPaths,
|
|
6
|
+
ExternalProject,
|
|
7
|
+
ExternalShotPaths,
|
|
8
|
+
project_from_mapping,
|
|
9
|
+
write_project_config,
|
|
10
|
+
)
|
|
11
|
+
from unrender.adapters.rows import DEFAULT_ROW_ALIASES, rows_to_shots, smpte_to_seconds
|
|
12
|
+
from unrender.adapters.stems import (
|
|
13
|
+
StemDestination,
|
|
14
|
+
StemDestinationPolicy,
|
|
15
|
+
media_dir_destination_policy,
|
|
16
|
+
plan_voice_promotions,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"DEFAULT_ROW_ALIASES",
|
|
21
|
+
"ArtifactExportPaths",
|
|
22
|
+
"ExternalProject",
|
|
23
|
+
"ExternalShotPaths",
|
|
24
|
+
"StemDestination",
|
|
25
|
+
"StemDestinationPolicy",
|
|
26
|
+
"export_artifacts_to_paths",
|
|
27
|
+
"media_dir_destination_policy",
|
|
28
|
+
"plan_voice_promotions",
|
|
29
|
+
"project_from_mapping",
|
|
30
|
+
"rows_to_shots",
|
|
31
|
+
"smpte_to_seconds",
|
|
32
|
+
"write_project_config",
|
|
33
|
+
]
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import shutil
|
|
4
|
+
import tempfile
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from unrender.adapters.paths import ArtifactExportPaths
|
|
8
|
+
from unrender.exports import export_artifacts
|
|
9
|
+
from unrender.project import RunPaths
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def export_artifacts_to_paths(
|
|
13
|
+
run: RunPaths,
|
|
14
|
+
paths: ArtifactExportPaths,
|
|
15
|
+
*,
|
|
16
|
+
project_name: str = "unrender",
|
|
17
|
+
) -> ArtifactExportPaths:
|
|
18
|
+
del project_name
|
|
19
|
+
with tempfile.TemporaryDirectory(prefix="unrender-exports-") as temp_dir:
|
|
20
|
+
export_dir = export_artifacts(run, Path(temp_dir))
|
|
21
|
+
copies = {
|
|
22
|
+
"face_speaker_detection.json": paths.face_speaker_detection,
|
|
23
|
+
"voice_speaker_detection.json": paths.voice_speaker_detection,
|
|
24
|
+
"speaker_resolution_plan.json": paths.speaker_resolution_plan,
|
|
25
|
+
"shot_stem_plan.json": paths.shot_stem_plan,
|
|
26
|
+
}
|
|
27
|
+
for name, destination in copies.items():
|
|
28
|
+
source = export_dir / name
|
|
29
|
+
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
30
|
+
if source.exists():
|
|
31
|
+
shutil.copy2(source, destination)
|
|
32
|
+
return paths
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Mapping, Sequence
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from unrender.manifests import write_json
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(frozen=True)
|
|
12
|
+
class ExternalProject:
|
|
13
|
+
name: str
|
|
14
|
+
root_dir: Path
|
|
15
|
+
data_dir: Path
|
|
16
|
+
run_dir: Path
|
|
17
|
+
fps: float
|
|
18
|
+
speakers_config: Mapping[str, Any] | Sequence[str] | None = None
|
|
19
|
+
stem_map: Mapping[str, str] | None = None
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass(frozen=True)
|
|
23
|
+
class ExternalShotPaths:
|
|
24
|
+
shot_id: str
|
|
25
|
+
shot_dir: Path
|
|
26
|
+
media_dir: Path
|
|
27
|
+
video_path: Path | None = None
|
|
28
|
+
target_face_box: tuple[int, int, int, int] | None = None
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass(frozen=True)
|
|
32
|
+
class ArtifactExportPaths:
|
|
33
|
+
face_speaker_detection: Path
|
|
34
|
+
voice_speaker_detection: Path
|
|
35
|
+
speaker_resolution_plan: Path
|
|
36
|
+
shot_stem_plan: Path
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def project_from_mapping(
|
|
40
|
+
config: Mapping[str, Any],
|
|
41
|
+
*,
|
|
42
|
+
name: str | None = None,
|
|
43
|
+
root_key: str = "root_dir",
|
|
44
|
+
root_dir: Path | None = None,
|
|
45
|
+
data_dir: Path | None = None,
|
|
46
|
+
run_dir: Path | None = None,
|
|
47
|
+
fps: float | None = None,
|
|
48
|
+
speakers_config: Mapping[str, Any] | Sequence[str] | None = None,
|
|
49
|
+
stem_map: Mapping[str, str] | None = None,
|
|
50
|
+
) -> ExternalProject:
|
|
51
|
+
root = root_dir or _path_value(config, root_key) or Path.cwd()
|
|
52
|
+
resolved_name = name or str(config.get("name") or config.get("project") or root.name)
|
|
53
|
+
resolved_data = data_dir or _path_value(config, "data_dir") or root / "_data"
|
|
54
|
+
resolved_run = run_dir or _path_value(config, "run_dir") or resolved_data / "unrender"
|
|
55
|
+
resolved_fps = fps if fps is not None else float(config.get("fps") or 24.0)
|
|
56
|
+
return ExternalProject(
|
|
57
|
+
name=resolved_name,
|
|
58
|
+
root_dir=root.expanduser(),
|
|
59
|
+
data_dir=resolved_data.expanduser(),
|
|
60
|
+
run_dir=resolved_run.expanduser(),
|
|
61
|
+
fps=resolved_fps,
|
|
62
|
+
speakers_config=speakers_config if speakers_config is not None else config.get("speakers"),
|
|
63
|
+
stem_map=stem_map if stem_map is not None else _string_mapping(config.get("stem_map")),
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def write_project_config(
|
|
68
|
+
project: ExternalProject,
|
|
69
|
+
*,
|
|
70
|
+
shots_path: Path,
|
|
71
|
+
config_path: Path | None = None,
|
|
72
|
+
extra_paths: Mapping[str, str] | None = None,
|
|
73
|
+
) -> Path:
|
|
74
|
+
out_path = config_path or project.data_dir / f"{project.name}.json"
|
|
75
|
+
payload: dict[str, Any] = {
|
|
76
|
+
"speakers": project.speakers_config or {},
|
|
77
|
+
"paths": {
|
|
78
|
+
"run_dir": str(project.run_dir),
|
|
79
|
+
"shots": str(shots_path),
|
|
80
|
+
**dict(extra_paths or {}),
|
|
81
|
+
},
|
|
82
|
+
}
|
|
83
|
+
if project.stem_map:
|
|
84
|
+
payload["stem_map"] = dict(project.stem_map)
|
|
85
|
+
write_json(out_path, payload)
|
|
86
|
+
return out_path
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def media_dir_destination_policy(*args: Any, **kwargs: Any):
|
|
90
|
+
from unrender.adapters.stems import media_dir_destination_policy as _policy
|
|
91
|
+
|
|
92
|
+
return _policy(*args, **kwargs)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _path_value(config: Mapping[str, Any], key: str) -> Path | None:
|
|
96
|
+
value = config.get(key)
|
|
97
|
+
if value in (None, ""):
|
|
98
|
+
return None
|
|
99
|
+
return Path(str(value))
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _string_mapping(value: Any) -> dict[str, str] | None:
|
|
103
|
+
if not isinstance(value, Mapping):
|
|
104
|
+
return None
|
|
105
|
+
return {str(key): str(path) for key, path in value.items() if path not in (None, "")}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable, Mapping, Sequence
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from unrender.adapters.paths import ExternalShotPaths
|
|
8
|
+
from unrender.manifests import ShotRecord
|
|
9
|
+
from unrender.media.timecode import smpte_to_seconds
|
|
10
|
+
|
|
11
|
+
__all__ = ["DEFAULT_ROW_ALIASES", "rows_to_shots", "smpte_to_seconds"]
|
|
12
|
+
|
|
13
|
+
DEFAULT_ROW_ALIASES: dict[str, tuple[str, ...]] = {
|
|
14
|
+
"shot_id": ("shot_id", "id", "number", "Shot ID"),
|
|
15
|
+
"start_sec": ("start_sec", "start_seconds"),
|
|
16
|
+
"end_sec": ("end_sec", "end_seconds"),
|
|
17
|
+
"start_tc": ("start_tc", "start", "start_timecode"),
|
|
18
|
+
"end_tc": ("end_tc", "end", "end_timecode"),
|
|
19
|
+
"speaker": ("speaker", "on_screen_speaker", "character"),
|
|
20
|
+
"video_path": ("video_path", "path", "file_path"),
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def rows_to_shots(
|
|
25
|
+
rows: Sequence[Mapping[str, Any]],
|
|
26
|
+
*,
|
|
27
|
+
fps: float,
|
|
28
|
+
resolve_shot_paths: Callable[[Mapping[str, Any]], ExternalShotPaths | None],
|
|
29
|
+
aliases: Mapping[str, Sequence[str]] | None = None,
|
|
30
|
+
parse_timecode: Callable[[str, float], float | None] | None = None,
|
|
31
|
+
) -> list[ShotRecord]:
|
|
32
|
+
merged_aliases = _merged_aliases(aliases)
|
|
33
|
+
parser = parse_timecode or smpte_to_seconds
|
|
34
|
+
shots: list[ShotRecord] = []
|
|
35
|
+
for row in rows:
|
|
36
|
+
shot_id = str(_value(row, merged_aliases["shot_id"]) or "").strip()
|
|
37
|
+
if not shot_id:
|
|
38
|
+
continue
|
|
39
|
+
paths = resolve_shot_paths(row)
|
|
40
|
+
if paths is None:
|
|
41
|
+
continue
|
|
42
|
+
start_sec = _seconds(row, merged_aliases, "start", fps=fps, parse_timecode=parser)
|
|
43
|
+
end_sec = _seconds(row, merged_aliases, "end", fps=fps, parse_timecode=parser)
|
|
44
|
+
video_path = paths.video_path or _path_from_row(row, merged_aliases["video_path"])
|
|
45
|
+
shots.append(
|
|
46
|
+
ShotRecord(
|
|
47
|
+
shot_id=shot_id,
|
|
48
|
+
video_path=video_path or paths.shot_dir,
|
|
49
|
+
existing_speaker=str(_value(row, merged_aliases["speaker"]) or "").strip(),
|
|
50
|
+
start_sec=start_sec,
|
|
51
|
+
end_sec=end_sec,
|
|
52
|
+
target_face_box=paths.target_face_box,
|
|
53
|
+
)
|
|
54
|
+
)
|
|
55
|
+
return shots
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _seconds(
|
|
59
|
+
row: Mapping[str, Any],
|
|
60
|
+
aliases: Mapping[str, Sequence[str]],
|
|
61
|
+
prefix: str,
|
|
62
|
+
*,
|
|
63
|
+
fps: float,
|
|
64
|
+
parse_timecode: Callable[[str, float], float | None],
|
|
65
|
+
) -> float | None:
|
|
66
|
+
seconds_value = _value(row, aliases[f"{prefix}_sec"])
|
|
67
|
+
if seconds_value not in (None, ""):
|
|
68
|
+
return float(seconds_value)
|
|
69
|
+
tc_value = _value(row, aliases[f"{prefix}_tc"])
|
|
70
|
+
if tc_value in (None, ""):
|
|
71
|
+
return None
|
|
72
|
+
return parse_timecode(str(tc_value), fps)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _path_from_row(row: Mapping[str, Any], aliases: Sequence[str]) -> Path | None:
|
|
76
|
+
value = _value(row, aliases)
|
|
77
|
+
if value in (None, ""):
|
|
78
|
+
return None
|
|
79
|
+
return Path(str(value)).expanduser()
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _value(row: Mapping[str, Any], aliases: Sequence[str]) -> Any:
|
|
83
|
+
for key in aliases:
|
|
84
|
+
if key in row and row[key] not in (None, ""):
|
|
85
|
+
return row[key]
|
|
86
|
+
return None
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _merged_aliases(
|
|
90
|
+
aliases: Mapping[str, Sequence[str]] | None,
|
|
91
|
+
) -> dict[str, tuple[str, ...]]:
|
|
92
|
+
out = dict(DEFAULT_ROW_ALIASES)
|
|
93
|
+
for key, values in (aliases or {}).items():
|
|
94
|
+
defaults = out.get(key, ())
|
|
95
|
+
out[key] = tuple(dict.fromkeys((*values, *defaults)))
|
|
96
|
+
return out
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable, Mapping, Sequence
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from unrender.adapters.paths import ExternalShotPaths
|
|
9
|
+
from unrender.manifests import ShotRecord, VoiceInput
|
|
10
|
+
from unrender.media.names import safe_name
|
|
11
|
+
from unrender.speakers import canonical_speaker_name, speaker_key
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class StemDestination:
|
|
16
|
+
path: Path
|
|
17
|
+
external_value: str | None = None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
StemDestinationPolicy = Callable[[ShotRecord, str, Mapping[str, Any]], StemDestination]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def media_dir_destination_policy(
|
|
24
|
+
shot_paths_by_id: Mapping[str, ExternalShotPaths],
|
|
25
|
+
*,
|
|
26
|
+
filename_template: str = "{shot_dir_name}_{speaker_key}_stem.wav",
|
|
27
|
+
) -> StemDestinationPolicy:
|
|
28
|
+
def destination(shot: ShotRecord, speaker: str, mapping: Mapping[str, Any]) -> StemDestination:
|
|
29
|
+
paths = shot_paths_by_id[shot.shot_id]
|
|
30
|
+
speaker_id = speaker_key(speaker) or safe_name(speaker, fallback="speaker")
|
|
31
|
+
filename = filename_template.format(
|
|
32
|
+
shot_id=shot.shot_id,
|
|
33
|
+
shot_dir_name=paths.shot_dir.name,
|
|
34
|
+
speaker=canonical_speaker_name(speaker),
|
|
35
|
+
speaker_key=speaker_id,
|
|
36
|
+
source_group=str(mapping.get("source_group") or ""),
|
|
37
|
+
line_id=str(mapping.get("line_id") or ""),
|
|
38
|
+
)
|
|
39
|
+
path = paths.media_dir / filename
|
|
40
|
+
return StemDestination(path=path, external_value=str(path))
|
|
41
|
+
|
|
42
|
+
return destination
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def plan_voice_promotions(
|
|
46
|
+
*,
|
|
47
|
+
rows: Sequence[Mapping[str, Any]],
|
|
48
|
+
clips: Sequence[VoiceInput],
|
|
49
|
+
shot_paths_by_id: Mapping[str, ExternalShotPaths],
|
|
50
|
+
speaker_db_path: Path,
|
|
51
|
+
sim_threshold: float = 0.65,
|
|
52
|
+
rms_threshold_db: float = -55.0,
|
|
53
|
+
destination_policy: StemDestinationPolicy,
|
|
54
|
+
aliases: Mapping[str, Sequence[str]] | None = None,
|
|
55
|
+
) -> list[dict[str, Any]]:
|
|
56
|
+
del speaker_db_path, sim_threshold, rms_threshold_db
|
|
57
|
+
shot_aliases: tuple[str, ...] = ("shot_id", "id", "number", "Shot ID")
|
|
58
|
+
speaker_aliases: tuple[str, ...] = ("speaker", "on_screen_speaker", "character")
|
|
59
|
+
if aliases:
|
|
60
|
+
shot_aliases = tuple(aliases.get("shot_id", shot_aliases))
|
|
61
|
+
speaker_aliases = tuple(aliases.get("speaker", speaker_aliases))
|
|
62
|
+
clips_by_shot: dict[str, list[VoiceInput]] = {}
|
|
63
|
+
for clip in clips:
|
|
64
|
+
clips_by_shot.setdefault(clip.shot_id, []).append(clip)
|
|
65
|
+
|
|
66
|
+
plan: list[dict[str, Any]] = []
|
|
67
|
+
for row in rows:
|
|
68
|
+
shot_id = str(_first(row, shot_aliases) or "").strip()
|
|
69
|
+
speaker = canonical_speaker_name(str(_first(row, speaker_aliases) or ""))
|
|
70
|
+
if not shot_id or not speaker:
|
|
71
|
+
continue
|
|
72
|
+
shot_paths = shot_paths_by_id.get(shot_id)
|
|
73
|
+
candidates = clips_by_shot.get(shot_id, [])
|
|
74
|
+
if shot_paths is None:
|
|
75
|
+
plan.append(_promotion_row(shot_id, speaker, "", "", 0.0, "missing_shot_paths"))
|
|
76
|
+
continue
|
|
77
|
+
if len(candidates) != 1:
|
|
78
|
+
plan.append(
|
|
79
|
+
_promotion_row(
|
|
80
|
+
shot_id,
|
|
81
|
+
speaker,
|
|
82
|
+
"",
|
|
83
|
+
"",
|
|
84
|
+
0.0,
|
|
85
|
+
"needs_review" if candidates else "no_candidate",
|
|
86
|
+
candidate_count=len(candidates),
|
|
87
|
+
)
|
|
88
|
+
)
|
|
89
|
+
continue
|
|
90
|
+
clip = candidates[0]
|
|
91
|
+
shot = ShotRecord(
|
|
92
|
+
shot_id=shot_id,
|
|
93
|
+
video_path=shot_paths.video_path or shot_paths.shot_dir,
|
|
94
|
+
existing_speaker=speaker,
|
|
95
|
+
)
|
|
96
|
+
destination = destination_policy(
|
|
97
|
+
shot,
|
|
98
|
+
speaker,
|
|
99
|
+
{"source_group": clip.source_group, "clip_id": clip.clip_id},
|
|
100
|
+
)
|
|
101
|
+
plan.append(
|
|
102
|
+
_promotion_row(
|
|
103
|
+
shot_id,
|
|
104
|
+
speaker,
|
|
105
|
+
str(clip.path),
|
|
106
|
+
str(destination.path),
|
|
107
|
+
1.0,
|
|
108
|
+
"accepted",
|
|
109
|
+
reason="single speaker with one voiced candidate",
|
|
110
|
+
candidate_count=1,
|
|
111
|
+
)
|
|
112
|
+
)
|
|
113
|
+
return plan
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _first(row: Mapping[str, Any], aliases: Sequence[str]) -> Any:
|
|
117
|
+
for key in aliases:
|
|
118
|
+
if row.get(key) not in (None, ""):
|
|
119
|
+
return row[key]
|
|
120
|
+
return None
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _promotion_row(
|
|
124
|
+
shot_id: str,
|
|
125
|
+
speaker: str,
|
|
126
|
+
source_path: str,
|
|
127
|
+
target_path: str,
|
|
128
|
+
score: float,
|
|
129
|
+
status: str,
|
|
130
|
+
*,
|
|
131
|
+
reason: str = "",
|
|
132
|
+
candidate_count: int = 0,
|
|
133
|
+
) -> dict[str, Any]:
|
|
134
|
+
return {
|
|
135
|
+
"shot_id": shot_id,
|
|
136
|
+
"speaker": speaker,
|
|
137
|
+
"source_path": source_path,
|
|
138
|
+
"target_path": target_path,
|
|
139
|
+
"score": score,
|
|
140
|
+
"status": status,
|
|
141
|
+
"reason": reason,
|
|
142
|
+
"candidate_count": candidate_count,
|
|
143
|
+
}
|
unrender/cli/__init__.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from unrender.cli.commands.audio import (
|
|
4
|
+
_audio_build_clips,
|
|
5
|
+
_audio_map_dialogue,
|
|
6
|
+
_audio_resolve_clips,
|
|
7
|
+
_audio_separate,
|
|
8
|
+
_audio_shot_dx,
|
|
9
|
+
_audio_transcribe_lines,
|
|
10
|
+
)
|
|
11
|
+
from unrender.cli.commands.context import _projects_from_args
|
|
12
|
+
from unrender.cli.commands.export import _export_artifacts
|
|
13
|
+
from unrender.cli.commands.face import _face_build, _shots_match
|
|
14
|
+
from unrender.cli.commands.health import _doctor, _status
|
|
15
|
+
from unrender.cli.commands.labels import _labels_apply, _labels_interactive, _labels_template
|
|
16
|
+
from unrender.cli.commands.timeline import _timeline_build
|
|
17
|
+
from unrender.cli.commands.voice import _voice_build, _voice_clone, _voice_match
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"_audio_build_clips",
|
|
21
|
+
"_audio_map_dialogue",
|
|
22
|
+
"_audio_resolve_clips",
|
|
23
|
+
"_audio_separate",
|
|
24
|
+
"_audio_shot_dx",
|
|
25
|
+
"_audio_transcribe_lines",
|
|
26
|
+
"_doctor",
|
|
27
|
+
"_export_artifacts",
|
|
28
|
+
"_face_build",
|
|
29
|
+
"_labels_apply",
|
|
30
|
+
"_labels_interactive",
|
|
31
|
+
"_labels_template",
|
|
32
|
+
"_projects_from_args",
|
|
33
|
+
"_shots_match",
|
|
34
|
+
"_status",
|
|
35
|
+
"_timeline_build",
|
|
36
|
+
"_voice_build",
|
|
37
|
+
"_voice_clone",
|
|
38
|
+
"_voice_match",
|
|
39
|
+
]
|