ffmpeg-skill 2.6.0 → 2.8.0

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.
@@ -20,7 +20,8 @@ from typing import List, Tuple
20
20
  # `detect` moved into _common/probe.py in 1.16.0 so metadata.py --auto-chapters can measure the
21
21
  # same silences without importing this tool; the body is unchanged and the name still lives here.
22
22
  from _common import (filler_spans, FILLER_WORDS, FILLER_AMBIGUOUS, FILLER_DISCOURSE_MARKERS,
23
- FILLER_PAD, transcribe_words, read_text_or_die)
23
+ FILLER_PAD, read_text_or_die)
24
+ from _common import asr as _asr
24
25
  from _common import detect_silences as detect, STATE, video_args, add_common, apply_common, audio_codec_for, cfr_args, default_output, die, emit, ffmpeg_base, info, is_audio_output, print_json, probe, run, X264_PRESETS, measured_level_dbfs, fmt_secs
25
26
 
26
27
 
@@ -99,7 +100,7 @@ def resolve_filler(args, meta):
99
100
  die("--filler needs word timings: pass --words transcript.json (a whisper JSON with word "
100
101
  "timestamps) or --transcribe. There is no way to find a filler word without them -- "
101
102
  "cutting the short quiet blips instead would remove real speech.", kind="input")
102
- source, engine, raw = None, None, None
103
+ source, engine, raw, transcription = None, None, None, {}
103
104
  if args.words:
104
105
  try:
105
106
  raw = json.loads(read_text_or_die(args.words, "--words"))
@@ -118,14 +119,20 @@ def resolve_filler(args, meta):
118
119
  '{"words": [{"word": ..., "start": ..., "end": ...}]} (or the same inside '
119
120
  '"segments").', kind="input")
120
121
  else:
121
- # No pre-check for an installed engine here: transcribe_words() probes for one and
122
+ # No pre-check for an installed engine here: transcribe_words_result() probes for one and
122
123
  # raises the same die_no_engine() refusal when there is none. Two places deciding "is
123
124
  # whisper here" is two places to disagree.
124
125
  # The engine is driven with ITS word-timestamp option (whisper.cpp --output-json-full,
125
126
  # faster-whisper word_timestamps=True, openai-whisper --word_timestamps True). An SRT
126
127
  # cannot answer this question: a cue has a start and an end, a word does not.
127
- words, engine = transcribe_words(args.input, args.filler_lang if args.filler_lang != "auto" else None)
128
- source = f"whisper:{engine}" if engine else "whisper"
128
+ # The call's own result, not module state: engine, routing and notes belong to this run.
129
+ asr_run = _asr.transcribe_words_result(args.input, args.filler_lang if args.filler_lang != "auto" else None,
130
+ engine=args.engine)
131
+ words, engine = asr_run.words, asr_run.engine
132
+ transcription = dict(asr_run.facts)
133
+ args._asr_notes = list(asr_run.notes)
134
+ family = "parakeet" if engine in _asr.PARAKEET_ENGINES else "whisper"
135
+ source = f"{family}:{engine}" if engine else "whisper"
129
136
  if not words:
130
137
  die(f"{engine or 'the local engine'} ran but produced no word-level timings, so there "
131
138
  "is nothing for --filler to cut on. Some builds do not support word timestamps. "
@@ -168,6 +175,7 @@ def resolve_filler(args, meta):
168
175
  "disfluency -- this will cut real sentences.")
169
176
  block = {
170
177
  "lang": lang, "source": source, "engine": engine,
178
+ **({"transcription": transcription} if engine and transcription else {}),
171
179
  "words": sorted(wordlist), "removed": [dict(s) for s in spans],
172
180
  "removed_count": len(spans),
173
181
  "removed_seconds": round(sum(s["end"] - s["start"] for s in spans), 3),
@@ -235,8 +243,11 @@ def main() -> int:
235
243
  fil.add_argument("--words", metavar="FILE",
236
244
  help="a whisper JSON with word-level timings, for --filler")
237
245
  fil.add_argument("--transcribe", action="store_true",
238
- help="produce the word timings with a local whisper (never required; the same "
246
+ help="produce the word timings with a local speech engine, Parakeet or whisper (never required; the same "
239
247
  "bridge caption.py uses)")
248
+ fil.add_argument("--engine", choices=_asr.ENGINE_CHOICES, default=None,
249
+ help="speech engine for --transcribe (default: $FFMPEG_SKILL_ASR_ENGINE, else auto = Parakeet "
250
+ "for English speech when installed, else whisper)")
240
251
  fil.add_argument("--filler-list", action="store_true",
241
252
  help="report what --filler would remove and write nothing")
242
253
  fil.add_argument("--max-cuts", type=int, default=400,
@@ -311,6 +322,10 @@ def main() -> int:
311
322
  f"({filler_info['lang']}, list {filler_info['list']})")
312
323
  for warning in filler_info.get("warnings") or []:
313
324
  info("warning: " + warning)
325
+ if getattr(args, "_asr_notes", None):
326
+ # an English-only transcript made on an assumption (asr.assumed_english_note): the
327
+ # bridge already logged the warning; the result document carries it at the top level
328
+ summary["notes"] = list(args._asr_notes)
314
329
  if len(keeps) > args.max_cuts:
315
330
  die(f"{len(keeps)} keep ranges is above --max-cuts {args.max_cuts}: the filter graph grows "
316
331
  "with every range and a graph this size is slow and fragile. Raise --max-cuts if you "