Fixing black formatting
This commit is contained in:
+3
-1
@@ -38,7 +38,9 @@ def get_ffmpeg_thread_count() -> int:
|
||||
cfg.read(config_path)
|
||||
if cfg.has_option("performance", "cpu_limit"):
|
||||
try:
|
||||
_perf_config["cpu_limit"] = float(cfg.get("performance", "cpu_limit"))
|
||||
_perf_config["cpu_limit"] = float(
|
||||
cfg.get("performance", "cpu_limit")
|
||||
)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
|
||||
+109
-37
@@ -241,7 +241,9 @@ Examples:
|
||||
args.res,
|
||||
)
|
||||
elif action == "trim":
|
||||
return cmd_trim(project_path, args.verbose, args.force, args.threshold, args.res)
|
||||
return cmd_trim(
|
||||
project_path, args.verbose, args.force, args.threshold, args.res
|
||||
)
|
||||
elif action == "transcode":
|
||||
return cmd_transcode(
|
||||
project_path,
|
||||
@@ -449,14 +451,19 @@ def _import_shared_audio(
|
||||
if added > 0:
|
||||
with open(audio_json_path, "w", encoding="utf-8") as fh:
|
||||
json.dump(existing, fh, indent=2)
|
||||
print(f" Updated {audio_json_path.relative_to(project_path)} (+{added} shared audio files)")
|
||||
print(
|
||||
f" Updated {audio_json_path.relative_to(project_path)} (+{added} shared audio files)"
|
||||
)
|
||||
else:
|
||||
if verbose:
|
||||
print(f" No new shared audio files to add")
|
||||
|
||||
|
||||
def _probe_audio_durations(
|
||||
project_path: Path, config, force: bool, verbose: bool,
|
||||
project_path: Path,
|
||||
config,
|
||||
force: bool,
|
||||
verbose: bool,
|
||||
shared_assets_dir: Optional[Path] = None,
|
||||
) -> None:
|
||||
"""Probe and cache audio file durations into audio.json.
|
||||
@@ -822,7 +829,9 @@ def _generate_slides_json(directory: Path, verbose: bool) -> None:
|
||||
# Write slides.json only if content changed
|
||||
output_path = directory / "slides.json"
|
||||
new_content = json.dumps(sorted_slides, indent=2)
|
||||
existing_content = output_path.read_text(encoding="utf-8") if output_path.exists() else None
|
||||
existing_content = (
|
||||
output_path.read_text(encoding="utf-8") if output_path.exists() else None
|
||||
)
|
||||
if new_content != existing_content:
|
||||
with open(output_path, "w", encoding="utf-8") as f:
|
||||
f.write(new_content)
|
||||
@@ -1491,12 +1500,16 @@ def cmd_preprocess(
|
||||
seg_id, seg_source = future.result()
|
||||
completed += 1
|
||||
print(f" Completed: {seg_id} ({completed}/{len(segments_to_process)})")
|
||||
output_path = (cache_narration_dir or narration_dir) / seg_source.output_file
|
||||
output_path = (
|
||||
cache_narration_dir or narration_dir
|
||||
) / seg_source.output_file
|
||||
if output_path.exists():
|
||||
successfully_processed.append((seg_id, seg_source))
|
||||
else:
|
||||
for segment_id, segment_source in segments_to_process:
|
||||
_out_full = (cache_narration_dir or narration_dir) / segment_source.output_file
|
||||
_out_full = (
|
||||
cache_narration_dir or narration_dir
|
||||
) / segment_source.output_file
|
||||
print(f"\n Processing: {segment_id}")
|
||||
print(f" Source: {segment_source.source_file}")
|
||||
print(f" Output: {_out_full}")
|
||||
@@ -1510,7 +1523,9 @@ def cmd_preprocess(
|
||||
gnommo_scratch,
|
||||
res=res,
|
||||
)
|
||||
output_path = (cache_narration_dir or narration_dir) / segment_source.output_file
|
||||
output_path = (
|
||||
cache_narration_dir or narration_dir
|
||||
) / segment_source.output_file
|
||||
if output_path.exists():
|
||||
successfully_processed.append((segment_id, segment_source))
|
||||
|
||||
@@ -1575,7 +1590,13 @@ def cmd_preprocess(
|
||||
continue
|
||||
print(f" Processing: {video_id}")
|
||||
preprocess_video(
|
||||
videos_dir, video_id, video_source, verbose, force, gnommo_scratch, res=res
|
||||
videos_dir,
|
||||
video_id,
|
||||
video_source,
|
||||
verbose,
|
||||
force,
|
||||
gnommo_scratch,
|
||||
res=res,
|
||||
)
|
||||
|
||||
print("\nPreprocessing complete.")
|
||||
@@ -1631,7 +1652,11 @@ def cmd_trim(
|
||||
for search_dir in (raw_dir, compressed_dir):
|
||||
if search_dir.exists():
|
||||
for f in search_dir.iterdir():
|
||||
if f.is_file() and f.suffix.lower() in _video_exts and not f.name.startswith("."):
|
||||
if (
|
||||
f.is_file()
|
||||
and f.suffix.lower() in _video_exts
|
||||
and not f.name.startswith(".")
|
||||
):
|
||||
stem = f.stem
|
||||
if stem.endswith("_compressed"):
|
||||
stem = stem[: -len("_compressed")]
|
||||
@@ -1658,7 +1683,11 @@ def cmd_trim(
|
||||
print(f" {seg_id}: source file not found, skipping")
|
||||
continue
|
||||
|
||||
print(f" {seg_id}: analysing {source_path.parent.name}/{source_path.name}...", end="", flush=True)
|
||||
print(
|
||||
f" {seg_id}: analysing {source_path.parent.name}/{source_path.name}...",
|
||||
end="",
|
||||
flush=True,
|
||||
)
|
||||
first_sound, last_sound = detect_silence_bounds(
|
||||
source_path, noise_threshold_db=threshold_db, verbose=verbose
|
||||
)
|
||||
@@ -2137,7 +2166,7 @@ def cmd_stitch(
|
||||
# per-project settings instead of hardcoded defaults.
|
||||
_loudnorm_cfg = None
|
||||
if config and config.default_filters:
|
||||
for _f in (config.default_filters.get("talkinghead") or []):
|
||||
for _f in config.default_filters.get("talkinghead") or []:
|
||||
if isinstance(_f, dict) and _f.get("type") == "audio_normalize":
|
||||
_loudnorm_cfg = _f
|
||||
break
|
||||
@@ -2157,7 +2186,9 @@ def cmd_stitch(
|
||||
|
||||
# Always update the MAIN videos.json (parent of subdir when using low/tiny res)
|
||||
# Downscaled dirs only affect file paths, not JSON metadata updates
|
||||
main_videos_dir = videos_dir_out.parent if (res != "full" and not cache_root) else videos_dir
|
||||
main_videos_dir = (
|
||||
videos_dir_out.parent if (res != "full" and not cache_root) else videos_dir
|
||||
)
|
||||
videos_json_path = main_videos_dir / "videos.json"
|
||||
if True: # Always update JSON regardless of proxy mode
|
||||
existing_videos: dict = {}
|
||||
@@ -2278,10 +2309,18 @@ def _print_render_plan_details(plan, marker_timings, slides: dict) -> None:
|
||||
marker_id.startswith(p)
|
||||
for p in (
|
||||
"video:",
|
||||
"vft:", "vfb:", "vf2t:", "vf2b:",
|
||||
"vst:", "vsb:",
|
||||
"vftp:", "vfbp:", "vf2tp:", "vf2bp:",
|
||||
"vstp:", "vsbp:",
|
||||
"vft:",
|
||||
"vfb:",
|
||||
"vf2t:",
|
||||
"vf2b:",
|
||||
"vst:",
|
||||
"vsb:",
|
||||
"vftp:",
|
||||
"vfbp:",
|
||||
"vf2tp:",
|
||||
"vf2bp:",
|
||||
"vstp:",
|
||||
"vsbp:",
|
||||
)
|
||||
):
|
||||
aligned_count += 1
|
||||
@@ -2289,10 +2328,18 @@ def _print_render_plan_details(plan, marker_timings, slides: dict) -> None:
|
||||
len(p)
|
||||
for p in (
|
||||
"video:",
|
||||
"vft:", "vfb:", "vf2t:", "vf2b:",
|
||||
"vst:", "vsb:",
|
||||
"vftp:", "vfbp:", "vf2tp:", "vf2bp:",
|
||||
"vstp:", "vsbp:",
|
||||
"vft:",
|
||||
"vfb:",
|
||||
"vf2t:",
|
||||
"vf2b:",
|
||||
"vst:",
|
||||
"vsb:",
|
||||
"vftp:",
|
||||
"vfbp:",
|
||||
"vf2tp:",
|
||||
"vf2bp:",
|
||||
"vstp:",
|
||||
"vsbp:",
|
||||
)
|
||||
if marker_id.startswith(p)
|
||||
)
|
||||
@@ -2515,7 +2562,9 @@ def _chunked_render(
|
||||
import math
|
||||
|
||||
# Split slide IDs into groups of chunk_size
|
||||
groups = [slide_ids[i : i + chunk_size] for i in range(0, len(slide_ids), chunk_size)]
|
||||
groups = [
|
||||
slide_ids[i : i + chunk_size] for i in range(0, len(slide_ids), chunk_size)
|
||||
]
|
||||
print(
|
||||
f"\n Auto-chunking: {len(slide_ids)} slides → {len(groups)} chunks of ≤{chunk_size}"
|
||||
)
|
||||
@@ -2549,7 +2598,9 @@ def _chunked_render(
|
||||
chunk_paths.append(chunk_path)
|
||||
|
||||
if dry_run:
|
||||
print(f"\n [dry-run] Would concatenate {len(chunk_paths)} chunks → {final_output}")
|
||||
print(
|
||||
f"\n [dry-run] Would concatenate {len(chunk_paths)} chunks → {final_output}"
|
||||
)
|
||||
return 0
|
||||
|
||||
# Concatenate chunks
|
||||
@@ -2560,10 +2611,16 @@ def _chunked_render(
|
||||
f.write(f"file '{p.resolve()}'\n")
|
||||
|
||||
concat_cmd = [
|
||||
"ffmpeg", "-y",
|
||||
"-f", "concat", "-safe", "0",
|
||||
"-i", str(concat_list),
|
||||
"-c", "copy",
|
||||
"ffmpeg",
|
||||
"-y",
|
||||
"-f",
|
||||
"concat",
|
||||
"-safe",
|
||||
"0",
|
||||
"-i",
|
||||
str(concat_list),
|
||||
"-c",
|
||||
"copy",
|
||||
str(final_output),
|
||||
]
|
||||
result = subprocess.run(concat_cmd, capture_output=True, text=True)
|
||||
@@ -2639,9 +2696,7 @@ def cmd_render(
|
||||
|
||||
# ETL: project shorthand marker semantics (cutout/layer) into videos.json
|
||||
# before parse_videos reads it, so the render pass is purely data-driven.
|
||||
_project_markers_to_videos(
|
||||
markers, project_path / config.videos_path, config
|
||||
)
|
||||
_project_markers_to_videos(markers, project_path / config.videos_path, config)
|
||||
|
||||
# Override resolution for preview modes
|
||||
if res != "full":
|
||||
@@ -2676,7 +2731,11 @@ def cmd_render(
|
||||
# the renderer doesn't re-resolve it via the local (missing) videos_dir.
|
||||
if "narration_combined" in videos:
|
||||
videos["narration_combined"].source_file = str(resolved_combined)
|
||||
if "narration_combined" in videos and resolved_combined and resolved_combined.exists():
|
||||
if (
|
||||
"narration_combined" in videos
|
||||
and resolved_combined
|
||||
and resolved_combined.exists()
|
||||
):
|
||||
# New workflow: narration_combined was created by 'gnommo concat' and is in videos.json
|
||||
# This entry has the correct volume setting from videos.json
|
||||
transcript_path = resolved_combined.with_suffix(".transcript.json")
|
||||
@@ -2789,8 +2848,6 @@ def cmd_render(
|
||||
if plan.time_offset > 0:
|
||||
print(f" Time offset: {plan.time_offset:.1f}s (partial render)")
|
||||
|
||||
|
||||
|
||||
# Print detailed render plan with alignment info
|
||||
_print_render_plan_details(plan, marker_timings, slides)
|
||||
if plan.audio_events:
|
||||
@@ -2873,12 +2930,20 @@ def cmd_render(
|
||||
|
||||
# Check if chunked rendering is needed (avoids filter graph OOM on long videos)
|
||||
from .cache import get_render_chunk_size
|
||||
|
||||
_chunk_size = chunk_slides or get_render_chunk_size() or 0
|
||||
_slide_ids = [e.slide_id for e in plan.slide_events]
|
||||
if _chunk_size > 0 and not slide_range and len(_slide_ids) > _chunk_size:
|
||||
return _chunked_render(
|
||||
project_path, verbose, dry_run, res, force,
|
||||
_chunk_size, _slide_ids, out_dir, output_path,
|
||||
project_path,
|
||||
verbose,
|
||||
dry_run,
|
||||
res,
|
||||
force,
|
||||
_chunk_size,
|
||||
_slide_ids,
|
||||
out_dir,
|
||||
output_path,
|
||||
)
|
||||
|
||||
plan.output_path = output_path
|
||||
@@ -2979,7 +3044,10 @@ def cmd_transcribe(
|
||||
video_id, video_source = result
|
||||
video_path = videos_dir / video_source.source_file
|
||||
|
||||
if not video_path.exists() and video_source.source_file == "narration_combined.mov":
|
||||
if (
|
||||
not video_path.exists()
|
||||
and video_source.source_file == "narration_combined.mov"
|
||||
):
|
||||
found = _resolve_narration_combined(project_path, videos_dir, config)
|
||||
if found:
|
||||
video_path = found
|
||||
@@ -3742,7 +3810,9 @@ def cmd_extract_audio(
|
||||
# Handle --combined mode: extract from narration_combined.mov
|
||||
if combined:
|
||||
videos, videos_dir = parse_videos(project_path, config)
|
||||
combined_path = _resolve_narration_combined(project_path, videos_dir, config) or (videos_dir / "narration_combined.mov")
|
||||
combined_path = _resolve_narration_combined(
|
||||
project_path, videos_dir, config
|
||||
) or (videos_dir / "narration_combined.mov")
|
||||
|
||||
if not combined_path.exists():
|
||||
print(
|
||||
@@ -3907,7 +3977,9 @@ def cmd_master(
|
||||
videos, videos_dir = parse_videos(project_path, config)
|
||||
|
||||
# Find narration_combined.mov
|
||||
combined_path = _resolve_narration_combined(project_path, videos_dir, config) or (videos_dir / "narration_combined.mov")
|
||||
combined_path = _resolve_narration_combined(project_path, videos_dir, config) or (
|
||||
videos_dir / "narration_combined.mov"
|
||||
)
|
||||
if not combined_path.exists():
|
||||
print(
|
||||
f"Error: narration_combined.mov not found at {combined_path}",
|
||||
|
||||
+5
-1
@@ -501,7 +501,11 @@ def parse_videos(
|
||||
|
||||
# Handle skip/take - can use begin/end as user-friendly alternatives
|
||||
skip = float(video_data.get("skip") or 0.0)
|
||||
take = float(video_data["take"]) if video_data.get("take") not in (None, "") else None
|
||||
take = (
|
||||
float(video_data["take"])
|
||||
if video_data.get("take") not in (None, "")
|
||||
else None
|
||||
)
|
||||
|
||||
# Convert begin/end to skip/take if provided
|
||||
if "begin" in video_data and video_data["begin"]:
|
||||
|
||||
@@ -18,9 +18,11 @@ from .models import (
|
||||
)
|
||||
from typing import Union, Optional
|
||||
|
||||
|
||||
def _tc() -> str:
|
||||
"""Return FFmpeg thread count string from ~/.gnommo.conf [performance] cpu_limit."""
|
||||
from .cache import get_ffmpeg_thread_count
|
||||
|
||||
return str(get_ffmpeg_thread_count())
|
||||
|
||||
|
||||
@@ -784,7 +786,10 @@ def apply_combined_video_filters(
|
||||
|
||||
cmd.extend(
|
||||
[
|
||||
"-probesize", "50000000", "-analyzeduration", "50000000",
|
||||
"-probesize",
|
||||
"50000000",
|
||||
"-analyzeduration",
|
||||
"50000000",
|
||||
"-i",
|
||||
str(input_path),
|
||||
"-vf",
|
||||
|
||||
+7
-2
@@ -307,6 +307,7 @@ def build_ffmpeg_command(plan: RenderPlan, output_path: Path) -> list[str]:
|
||||
# in the filter graph (one per video layer) spawns one swscaler thread per CPU core,
|
||||
# causing OOM on Apple Silicon where av_cpu_count() returns 10-11.
|
||||
from .cache import get_ffmpeg_thread_count
|
||||
|
||||
_tc = str(get_ffmpeg_thread_count())
|
||||
cmd.extend(["-threads", _tc, "-filter_threads", _tc])
|
||||
|
||||
@@ -463,7 +464,9 @@ def build_ffmpeg_command(plan: RenderPlan, output_path: Path) -> list[str]:
|
||||
for event in plan.audio_events:
|
||||
if event.audio_id not in audio_inputs:
|
||||
if event.audio_def.is_shared and plan.shared_assets_dir:
|
||||
audio_path = plan.shared_assets_dir / "media" / "audio" / event.audio_def.file
|
||||
audio_path = (
|
||||
plan.shared_assets_dir / "media" / "audio" / event.audio_def.file
|
||||
)
|
||||
else:
|
||||
audio_path = audio_dir / event.audio_def.file
|
||||
audio_path, _ = resolve_with_cache(audio_path, project_path)
|
||||
@@ -933,7 +936,9 @@ def build_filter_complex(
|
||||
plan.narration_pauses, plan.total_duration
|
||||
)
|
||||
|
||||
for seg_idx, (src_start, src_end, out_start, out_end) in enumerate(segments):
|
||||
for seg_idx, (src_start, src_end, out_start, out_end) in enumerate(
|
||||
segments
|
||||
):
|
||||
seg_label = f"av{i}_seg{seg_idx}"
|
||||
pts_offset = out_start
|
||||
filters.append(
|
||||
|
||||
+30
-7
@@ -315,7 +315,9 @@ def _fuzzy_match_ratio(
|
||||
|
||||
words_to_check = min(len(phrase_words), window_size)
|
||||
# Window only needs to cover pre_filler + phrase words + inter_filler slack
|
||||
transcript_end = min(start_idx + pre_filler + words_to_check + inter_filler, len(transcription))
|
||||
transcript_end = min(
|
||||
start_idx + pre_filler + words_to_check + inter_filler, len(transcription)
|
||||
)
|
||||
|
||||
transcript_words = [
|
||||
_normalize_token(transcription[j].word)
|
||||
@@ -529,7 +531,10 @@ def align_markers_to_transcription(
|
||||
else:
|
||||
prev_idx = seen[timing.marker_id]
|
||||
prev = deduped[prev_idx]
|
||||
if prev.context == "(after previous)" and timing.context != "(after previous)":
|
||||
if (
|
||||
prev.context == "(after previous)"
|
||||
and timing.context != "(after previous)"
|
||||
):
|
||||
deduped[prev_idx] = MarkerTiming(
|
||||
marker_id=prev.marker_id,
|
||||
timestamp=timing.timestamp,
|
||||
@@ -651,8 +656,20 @@ def build_render_plan(
|
||||
# Before extracting video events, resolve any referenced videos that are missing
|
||||
# from the project's videos.json by looking them up in shared_assets/videos.json.
|
||||
_VIDEO_MARKER_PREFIXES = (
|
||||
"video:", "narration:", "vft:", "vfb:", "vf2t:", "vf2b:", "vst:", "vsb:",
|
||||
"vftp:", "vfbp:", "vf2tp:", "vf2bp:", "vstp:", "vsbp:",
|
||||
"video:",
|
||||
"narration:",
|
||||
"vft:",
|
||||
"vfb:",
|
||||
"vf2t:",
|
||||
"vf2b:",
|
||||
"vst:",
|
||||
"vsb:",
|
||||
"vftp:",
|
||||
"vfbp:",
|
||||
"vf2tp:",
|
||||
"vf2bp:",
|
||||
"vstp:",
|
||||
"vsbp:",
|
||||
)
|
||||
missing_video_ids = [
|
||||
timing.marker_id[len(prefix) :]
|
||||
@@ -676,6 +693,7 @@ def build_render_plan(
|
||||
)
|
||||
if video_warnings:
|
||||
import sys
|
||||
|
||||
print("\nWarnings:", file=sys.stderr)
|
||||
for w in video_warnings:
|
||||
print(f" ⚠ {w}", file=sys.stderr)
|
||||
@@ -780,7 +798,10 @@ def build_render_plan(
|
||||
videos.update(found)
|
||||
still_missing = [vid_id for vid_id in config.outro if vid_id not in videos]
|
||||
for vid_id in still_missing:
|
||||
print(f" WARNING: outro video '{vid_id}' not found in videos.json or shared_assets — skipped", flush=True)
|
||||
print(
|
||||
f" WARNING: outro video '{vid_id}' not found in videos.json or shared_assets — skipped",
|
||||
flush=True,
|
||||
)
|
||||
|
||||
# Build outro events (plays after narration ends)
|
||||
outro_events = _extract_outro_events(
|
||||
@@ -997,7 +1018,9 @@ def _extract_video_events(
|
||||
mid = timing.marker_id
|
||||
|
||||
# --- shorthand markers (vft:/vfb:/vst:/vsb: and pause variants) ---
|
||||
shorthand_match = next((p for p in _SHORTHAND_PREFIXES if mid.startswith(p)), None)
|
||||
shorthand_match = next(
|
||||
(p for p in _SHORTHAND_PREFIXES if mid.startswith(p)), None
|
||||
)
|
||||
if shorthand_match:
|
||||
video_id = mid[len(shorthand_match) :]
|
||||
if video_id not in videos:
|
||||
@@ -1052,7 +1075,7 @@ def _extract_video_events(
|
||||
video_markers.append((timing.timestamp, video_id, "narration", False))
|
||||
|
||||
events: list[VideoEvent] = []
|
||||
for (start_time, video_id, marker_type, pause_narration) in video_markers:
|
||||
for start_time, video_id, marker_type, pause_narration in video_markers:
|
||||
video_source = videos[video_id]
|
||||
|
||||
# Read cutout and layer directly from videos.json (projected by ETL)
|
||||
|
||||
Reference in New Issue
Block a user