ffmpeg-skill 3.8.7 → 3.8.9

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.
package/scripts/sync.py CHANGED
@@ -33,7 +33,7 @@ import sys
33
33
  from typing import Dict, List
34
34
 
35
35
  from _common.runner import fail_if_classified
36
- from _common import video_args, add_common, apply_common, emit, aac_args, audio_codec_for, default_output, die, ffmpeg_base, info, probe, require_tool, run, run_analysis, x264_args, decode_pcm_mono, rms_envelope
36
+ from _common import STATE, video_args, add_common, apply_common, emit, aac_args, audio_codec_for, default_output, die, ffmpeg_base, info, probe, require_tool, run, run_analysis, x264_args, decode_pcm_mono, rms_envelope
37
37
  from _common.emit import record_skip, warn
38
38
  from _common.decision import SEEK_MARGIN, frame_rate, grid_at_or_after, length_output_t, silence_filter, stream_starts
39
39
  from _common.timeline import output_timecode_args
@@ -259,8 +259,13 @@ def measure_one(reference: str, path: str, args) -> Dict:
259
259
  offset = offset + (drift_ratio - 1.0) * head_mid
260
260
  drift_info = {"residual_at_end_seconds": round(residual, 4), "measured_over_seconds": round(elapsed, 2),
261
261
  "drift_ppm": round((drift_ratio - 1) * 1e6, 1),
262
- "meaning": "second file runs %.1f ppm %s (%.3fs over %.0fs); it will be resampled to match" % (
263
- abs(drift_ratio - 1) * 1e6, "long/slow" if drift_ratio > 1 else "short/fast", abs(residual), elapsed),
262
+ # a measure-only run writes nothing (#403: it said "will be resampled")
263
+ "meaning": "second file runs %.1f ppm %s (%.3fs over %.0fs); %s" % (
264
+ abs(drift_ratio - 1) * 1e6, "long/slow" if drift_ratio > 1 else "short/fast", abs(residual), elapsed,
265
+ ("it would be resampled to match in the file this run plans (--dry-run writes nothing)" if STATE.dry_run
266
+ else "it is resampled to match in the file this run writes")
267
+ if args.replace_audio or args.trim_second else
268
+ "nothing is resampled in this measure-only run (--replace-audio or --trim-second writes the corrected file)"),
264
269
  "confidence": round(end_score, 3)}
265
270
  else:
266
271
  skipped_reason = "low_confidence"
@@ -36,7 +36,7 @@ import os
36
36
  import sys
37
37
 
38
38
  from _platforms import PLATFORMS, PLATFORM_CHOICES, resolve as resolve_platform
39
- from _common.probe import SILENT_PEAK_DBFS, square_pixels, square_pixels_note
39
+ from _common.probe import SILENT_PEAK_DBFS, refuse_not_a_still, square_pixels, square_pixels_note
40
40
  from _common import still_input_args # noqa: E402
41
41
  from _common import (add_common, apply_common, aac_args, default_output, die, draws_chroma, emit, ffmpeg_base,
42
42
  info, load_brand, pad_filters, probe, rgb_chain_to_bt709, run, STATE, validate_color, video_args, X264_PRESETS,
@@ -119,10 +119,13 @@ def main() -> int:
119
119
  "the image locally and pass its path", kind="input")
120
120
  if not os.path.exists(args.image):
121
121
  die(f"--image file not found: {args.image}", kind="input")
122
- if not STATE.dry_run:
123
- plate = probe(args.image, role="input")
124
- if not (plate.get("video") or {}).get("width"):
125
- die(f"--image is not an image ffmpeg can decode: {args.image}", kind="input")
122
+ # read under --dry-run too (the file exists, checked above): a plate the real run refuses
123
+ # is refused by the plan (#404: a dry run completed on a video given as --image)
124
+ plate = probe(args.image, role="input")
125
+ if not (plate.get("video") or {}).get("width"):
126
+ die(f"--image is not an image ffmpeg can decode: {args.image}", kind="input")
127
+ refuse_not_a_still(args.image, plate, "--image",
128
+ "`look.py FILE --at T` writes one frame of a video as a PNG")
126
129
  if args.background != "black":
127
130
  die("--image and a non-default --background exclude each other: the plate is either "
128
131
  "the picture or the colour", kind="input")
@@ -375,8 +378,12 @@ def _child(script_name: str, argv: "list") -> None:
375
378
  extra = {"hint": err["hint"]} if err.get("hint") else {}
376
379
  if doc.get("encoder"):
377
380
  extra["encoder"] = doc["encoder"]
381
+ # the stage's own reason and details ride along (#402: a caller keyed on error.reason saw
382
+ # filter_missing from caption.py and graphics.py run by hand, and nothing from here)
383
+ why = {k: err[k] for k in ("reason", "details") if err.get(k) is not None}
378
384
  die(f"{script_name} failed: {err.get('message') or (proc.stderr.strip().splitlines() or ['?'])[-1][:300]}",
379
- code=int(doc.get("exit_code") or 1), kind=err.get("kind") or "input", stage=script_name, **extra)
385
+ code=int(doc.get("exit_code") or 1), kind=err.get("kind") or "input", stage=script_name,
386
+ error_extra=why or None, **extra)
380
387
 
381
388
  if __name__ == "__main__":
382
389
  sys.exit(main())
@@ -6,6 +6,7 @@
6
6
  "src": "$INPUT"
7
7
  }
8
8
  ],
9
+ "brand": "$BRAND",
9
10
  "audiogram": {
10
11
  "image": "$IMAGE",
11
12
  "style": "waveform",
@@ -17,6 +18,14 @@
17
18
  "position": "bottom",
18
19
  "margin": 14
19
20
  },
21
+ "graphics": [
22
+ {
23
+ "template": "lower-third",
24
+ "name": "$TITLE",
25
+ "start": 1,
26
+ "end": 6
27
+ }
28
+ ],
20
29
  "loudness": {
21
30
  "lufs": -14,
22
31
  "tp": -1