ffmpeg-skill 3.1.0 → 3.1.2

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.
@@ -32,7 +32,7 @@ import sys
32
32
  from typing import List, Optional, Tuple
33
33
 
34
34
  from _platforms import PLATFORMS, PLATFORM_CHOICES, safe_margins_px, resolve as resolve_platform
35
- from _common import (aac_args, add_common, brand_caption_style, script_font_for_text, apply_common, cfr_args,
35
+ from _common import (require_filter, aac_args, add_common, brand_caption_style, script_font_for_text, apply_common, cfr_args,
36
36
  color_hex, default_output, die, draws_chroma, drawn_chain, emit, escape_drawtext, escape_filter_path,
37
37
  ffmpeg_base, info, label_chain, load_brand, parse_time, probe, rgb_chain_to_bt709, run, run_keeping_subtitles, video_args,
38
38
  drawtext_boxborderw, drawtext_center_align, X264_PRESETS, time_arg, fmt_secs, STATE, drawtext_text_opts,
@@ -237,13 +237,18 @@ def main(force_route: Optional[str] = None) -> int:
237
237
  emoji_mode = None
238
238
  emoji_overlays: List[dict] = []
239
239
  if has_emoji(all_text):
240
- support = emoji_support(emoji_assets, probe=True)
240
+ _ff = args.font_file or brand.get("font_file")
241
+ support = emoji_support(emoji_assets, probe=True, fonts_dir=os.path.dirname(os.path.abspath(_ff)) if _ff else None)
241
242
  emoji_mode = support["mode"] if args.emoji == "auto" else args.emoji
242
243
  if args.emoji == "color" and not support["libass_color"]:
243
244
  die("--emoji color: this ffmpeg renders emoji monochrome through libass "
244
245
  f"({support['detail']}) -- pass --emoji-assets DIR for colour, or --emoji mono", kind="input")
245
246
  if args.emoji == "png" and not emoji_assets:
246
247
  die("--emoji png: no emoji assets directory resolved -- " + EMOJI_ASSET_HINT, kind="input")
248
+ if args.emoji == "mono" and support.get("libass_glyph") is False:
249
+ die("--emoji mono: this ffmpeg draws no emoji glyph through libass "
250
+ f"({support['detail']}), so they would come out as empty boxes -- pass --emoji-assets DIR "
251
+ "for colour, or --emoji none", kind="input")
247
252
  if args.text_render == "drawtext" and shaping:
248
253
  die(f"{LANGUAGE_NAMES.get(_script, _script)} text cannot be shaped by drawtext on any ffmpeg "
249
254
  "build (the marks are reordered by harfbuzz, which drawtext does not use): drop "
@@ -642,6 +647,7 @@ def main(force_route: Optional[str] = None) -> int:
642
647
  "assets": emoji_assets, "missing": missing,
643
648
  "overlays": len(emoji_overlays)}
644
649
  ass_path = args.write_ass or os.path.splitext(output)[0] + "_gfx.ass"
650
+ require_filter("ass", "drawing this text through libass") # before the sidecar is written
645
651
  if STATE.dry_run:
646
652
  info(f"[dry-run] would write {ass_path} ({len(elements)} text elements)")
647
653
  else:
@@ -682,7 +688,7 @@ def main(force_route: Optional[str] = None) -> int:
682
688
  chains = list(fc) if fc else [f"[0:v]{text or 'null'}[vout]"]
683
689
  last = "vout"
684
690
  if route == "ass":
685
- vf = f"ass={escape_filter_path(ass_path)}"
691
+ vf = f"ass=filename={escape_filter_path(ass_path)}"
686
692
  fdir = ass_fonts_dir()
687
693
  if fdir:
688
694
  vf += f":fontsdir={escape_filter_path(fdir)}"
package/scripts/look.py CHANGED
@@ -19,6 +19,8 @@ from typing import List, Optional
19
19
  from _platforms import PLATFORMS, PLATFORM_CHOICES, resolve as resolve_platform
20
20
  from _common import STATE, require_tool, add_common, apply_common, default_font_file, die, emit, escape_drawtext, ffmpeg_base, info, parse_time, probe, run, run_analysis, time_arg
21
21
  from _common import drawtext_fontfile # noqa: E402
22
+ from _common import ffmpeg_filters, filter_missing_hint # noqa: E402
23
+ from _common.emit import note, record_fallback # noqa: E402
22
24
 
23
25
  FONT = "fontcolor=white:fontsize=h/18:box=1:boxcolor=black@0.55:boxborderw=6:x=8:y=8"
24
26
 
@@ -162,6 +164,14 @@ def main() -> int:
162
164
  # a resolvable font file, given as fontfile=, is the only form confirmed not to crash drawtext's
163
165
  # own fontconfig resolution on some real Windows ffmpeg builds (#100); font= is the fallback when
164
166
  # nothing can be resolved, unchanged from before this existed.
167
+ if not args.no_timecode and ffmpeg_filters() and "drawtext" not in ffmpeg_filters():
168
+ # a build without drawtext (Homebrew's plain ffmpeg) cannot stamp the timecodes, but the
169
+ # sheet itself only needs tile: draw it without them and say so, rather than failing the
170
+ # step every picture change ends with (#394)
171
+ args.no_timecode = True
172
+ record_fallback("this ffmpeg build has no drawtext filter, so the sheet has no timecode stamps "
173
+ "(the same as --no-timecode)", "look_no_drawtext")
174
+ note("no timecode stamps: this ffmpeg build has no drawtext filter (" + filter_missing_hint("drawtext") + ")")
165
175
  if args.no_timecode:
166
176
  font_prefix, tc = "", "" # nothing is drawn, so no font is resolved or copied
167
177
  else:
@@ -246,8 +246,12 @@ def resolve_filler(args, meta):
246
246
  # faster-whisper word_timestamps=True, openai-whisper --word_timestamps True). An SRT
247
247
  # cannot answer this question: a cue has a start and an end, a word does not.
248
248
  # The call's own result, not module state: engine, routing and notes belong to this run.
249
+ # Whisper leaves "uh"/"um" out of its text unless the context shows them: prime it with the
250
+ # filler list of the language asked for (English under auto, where most filler cuts are)
251
+ primer = FILLER_WORDS.get(args.filler_lang if args.filler_lang != "auto" else "en") or []
252
+ prompt = (", ".join(sorted(primer)[:8]).capitalize() + ". So, I mean...") if primer else None
249
253
  asr_run = _asr.transcribe_words_result(args.input, args.filler_lang if args.filler_lang != "auto" else None,
250
- model=args.model, engine=args.engine)
254
+ model=args.model, engine=args.engine, prompt=prompt)
251
255
  words, engine = asr_run.words, asr_run.engine
252
256
  transcription = dict(asr_run.facts)
253
257
  args._asr_notes = list(asr_run.notes)