ffmpeg-skill 0.8.3 → 0.8.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ffmpeg-skill",
3
- "version": "0.8.3",
3
+ "version": "0.8.4",
4
4
  "description": "Agent Skill that lets coding agents (Claude Code, Cursor, Codex) do professional video editing with local FFmpeg: MCP server, batch processing, declarative project rendering, brand kits, motion-graphics templates, HTML delivery reports, scene detection, delivery checks, cut, silence removal, transitions, multicam, captions, sync with drift correction, HDR to SDR, LUTs, audio clean-up and ducking, loudness, platform exports. No API keys, no cloud, no dependencies.",
5
5
  "keywords": ["ffmpeg", "video", "agent-skill", "claude-code", "cursor", "codex", "skill", "video-editing"],
6
6
  "license": "MIT",
@@ -37,6 +37,9 @@ def die(msg: str, code: int = 1) -> "None":
37
37
 
38
38
 
39
39
  def info(msg: str) -> None:
40
+ # under --dry-run nothing is written; do not let scripts claim otherwise
41
+ if msg.startswith("wrote ") and STATE.dry_run:
42
+ msg = "[dry-run] would write " + msg[len("wrote "):]
40
43
  sys.stderr.write(f"{msg}\n")
41
44
 
42
45
 
@@ -15,7 +15,7 @@ import argparse
15
15
  import sys
16
16
  from typing import List, Optional
17
17
 
18
- from _common import load_brand, video_args, add_common, apply_common, emit, aac_args, cfr_args, default_output, die, escape_drawtext, escape_filter_path, ffmpeg_base, info, parse_time, probe, run, x264_args
18
+ from _common import STATE, load_brand, video_args, add_common, apply_common, emit, aac_args, cfr_args, default_output, die, escape_drawtext, escape_filter_path, ffmpeg_base, info, parse_time, probe, run, x264_args
19
19
 
20
20
  POS = {
21
21
  "top-left": ("{m}", "{m}"),
@@ -150,11 +150,13 @@ def main() -> int:
150
150
  chain.append(f"scale={args.scale}:-1")
151
151
  if args.opacity < 1:
152
152
  chain.append(f"colorchannelmixer=aa={args.opacity:g}")
153
- if args.fade > 0 and (start is not None or end is not None):
153
+ if args.fade > 0:
154
+ # no --start/--end: fade in at 0 and out at the end of the video
154
155
  s = start if start is not None else 0.0
156
+ e = end if end is not None else (meta.get("duration") or 0.0)
155
157
  chain.append(f"fade=t=in:st={s:.3f}:d={args.fade:g}:alpha=1")
156
- if end is not None:
157
- chain.append(f"fade=t=out:st={end - args.fade:.3f}:d={args.fade:g}:alpha=1")
158
+ if e > args.fade:
159
+ chain.append(f"fade=t=out:st={e - args.fade:.3f}:d={args.fade:g}:alpha=1")
158
160
  x, y = position_exprs(args.position, args.margin, text_mode=False)
159
161
  ov = f"overlay={x}:{y}:format=auto"
160
162
  if enable:
@@ -171,7 +173,8 @@ def main() -> int:
171
173
  opts.append(f"fontfile={escape_filter_path(args.font_file)}")
172
174
  else:
173
175
  opts.append(f"font='{args.font}'")
174
- alpha = alpha_expr(args.opacity, start, end, args.fade)
176
+ alpha = alpha_expr(args.opacity, start if start is not None else (0.0 if args.fade > 0 else None),
177
+ end if end is not None else ((meta.get("duration") or None) if args.fade > 0 else None), args.fade)
175
178
  opts.append(f"fontcolor={args.font_color}")
176
179
  if alpha != "1":
177
180
  opts.append(f"alpha='{alpha}'")
@@ -185,8 +188,9 @@ def main() -> int:
185
188
  cmd += aac_args() if meta.get("audio") else ["-an"]
186
189
  cmd.append(output)
187
190
  run(cmd)
188
- result = probe(output)
189
- info(f"wrote {output} ({result['duration']:.3f}s)")
191
+ if not STATE.dry_run:
192
+ result = probe(output)
193
+ info(f"wrote {output} ({result['duration']:.3f}s)")
190
194
  emit(output)
191
195
  return 0
192
196