ffmpeg-skill 3.6.1 → 3.6.3
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/README.md +7 -5
- package/SKILL.md +1 -0
- package/docs/contract.md +37 -8
- package/mcp/server.py +28 -13
- package/package.json +1 -1
- package/references/gotchas.md +2 -1
- package/references/scripts.md +40 -14
- package/scripts/_ass_overlay.py +234 -1
- package/scripts/_common/__init__.py +4 -4
- package/scripts/_common/asr.py +3 -0
- package/scripts/_common/color.py +55 -0
- package/scripts/_common/decision.py +80 -0
- package/scripts/_common/emit.py +8 -0
- package/scripts/_common/emoji.py +76 -9
- package/scripts/_common/probe.py +2 -1
- package/scripts/_common/runner.py +148 -7
- package/scripts/_common/timeline.py +39 -0
- package/scripts/_common/wrap.py +92 -42
- package/scripts/_contract.py +46 -15
- package/scripts/audio.py +9 -3
- package/scripts/batch.py +149 -11
- package/scripts/broll.py +2 -2
- package/scripts/caption.py +44 -19
- package/scripts/check.py +1 -1
- package/scripts/color.py +2 -42
- package/scripts/cut.py +37 -9
- package/scripts/fit.py +25 -3
- package/scripts/graphics.py +11 -6
- package/scripts/join.py +135 -14
- package/scripts/look.py +31 -12
- package/scripts/multicam.py +6 -1
- package/scripts/overlay.py +20 -7
- package/scripts/redact.py +2 -2
- package/scripts/render.py +30 -23
- package/scripts/silence.py +6 -3
- package/scripts/speedramp.py +6 -2
- package/scripts/sync.py +28 -4
package/scripts/speedramp.py
CHANGED
|
@@ -19,7 +19,7 @@ import argparse
|
|
|
19
19
|
import sys
|
|
20
20
|
from typing import List, Tuple
|
|
21
21
|
|
|
22
|
-
from _common.decision import av_trim, frame_rate, grid_at_or_after, stream_starts
|
|
22
|
+
from _common.decision import av_trim, frame_durations, frame_rate, grid_at_or_after, stream_starts
|
|
23
23
|
from _common import add_common, apply_common, aac_args, default_output, die, emit, ffmpeg_base, info, probe, run, video_args, X264_PRESETS, fmt_secs, time_arg
|
|
24
24
|
|
|
25
25
|
MAX_SPEED = 20.0
|
|
@@ -106,7 +106,11 @@ def main() -> int:
|
|
|
106
106
|
die(f"--segment {start:g}-{end:g}:{factor:g} holds no frame: both ends fall on the frame at "
|
|
107
107
|
f"{bounds[i]:.3f}s; widen it or merge it into a neighbour")
|
|
108
108
|
vtrim, atrim = av_trim(bounds[i], seg_end, rate)
|
|
109
|
-
|
|
109
|
+
# the closing fps puts the sped-up frames back on the source's grid with its frame
|
|
110
|
+
# duration: concat starts the next segment after the last frame's duration, which setpts
|
|
111
|
+
# leaves at the unscaled 1/rate, so every 2x segment pushed the rest of the picture two
|
|
112
|
+
# frames late against the sound (and added those frames)
|
|
113
|
+
vparts.append(f"[0:v]{vtrim},setpts=PTS/{factor:.6f},{frame_durations(meta, rate)}[{vlabel}]")
|
|
110
114
|
labels.append(f"[{vlabel}]")
|
|
111
115
|
if has_audio:
|
|
112
116
|
aparts.append(f"[0:a]{atrim},{atempo_chain(factor)}[{alabel}]")
|
package/scripts/sync.py
CHANGED
|
@@ -35,7 +35,8 @@ from typing import Dict, List
|
|
|
35
35
|
from _common.runner import fail_if_classified
|
|
36
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
|
|
37
37
|
from _common.emit import record_skip, warn
|
|
38
|
-
from _common.decision import stream_starts
|
|
38
|
+
from _common.decision import SEEK_MARGIN, frame_rate, grid_at_or_after, stream_starts
|
|
39
|
+
from _common.timeline import output_timecode_args
|
|
39
40
|
|
|
40
41
|
SR = 8000 # decode sample rate
|
|
41
42
|
|
|
@@ -385,7 +386,12 @@ def main() -> int:
|
|
|
385
386
|
cmd = cmd[:-1] + video_args(probe(args.reference) if args.replace_audio else probe(args.second), args.crf) + [output]
|
|
386
387
|
run(cmd)
|
|
387
388
|
else:
|
|
388
|
-
|
|
389
|
+
# A picture is never stream-copied here: a copy starts at the keyframe at or before -ss
|
|
390
|
+
# and -avoid_negative_ts make_zero shows the frames from it, so the output started
|
|
391
|
+
# early by up to a GOP (a 5 s GOP: nothing trimmed at all) and was reported as synced.
|
|
392
|
+
# Even a keyframe on the trim point moved the picture by its B-frame delay (+69 ms).
|
|
393
|
+
# The re-encode below starts on the frame itself; a sound-only file is still copied.
|
|
394
|
+
if head_trim > 0 and not drift_af and not has_video:
|
|
389
395
|
cmd = ffmpeg_base() + ["-ss", f"{head_trim:.4f}", "-i", args.second, "-c", "copy"]
|
|
390
396
|
if os.path.splitext(output)[1].lower() in (".mp4", ".mov", ".m4v"):
|
|
391
397
|
cmd += ["-movflags", "+faststart"]
|
|
@@ -396,10 +402,20 @@ def main() -> int:
|
|
|
396
402
|
cmd = ffmpeg_base() + ["-ss", f"{head_trim:.4f}", "-i", args.second] + (video_args(probe(args.reference) if args.replace_audio else probe(args.second), args.crf) if has_video else []) + audio_codec_for(output) + [output]
|
|
397
403
|
run(cmd)
|
|
398
404
|
else:
|
|
405
|
+
# Seek SEEK_MARGIN early and drop the margin on the output side, as cut.py's
|
|
406
|
+
# re-encode does: an input seek straight to head_trim can land (the MP4 demuxer
|
|
407
|
+
# seeks over raw decode times) on a keyframe whose picture comes after it, and a
|
|
408
|
+
# B-frame HEVC file lost the frames before it. The output-side -ss decodes and
|
|
409
|
+
# drops the margin from both streams alike; it counts on the output's clock, which
|
|
410
|
+
# a drift fix runs faster by drift_ratio (setpts and asetrate alike).
|
|
399
411
|
cmd = ffmpeg_base()
|
|
412
|
+
out_seek: List[str] = []
|
|
400
413
|
if head_trim > 0:
|
|
401
|
-
|
|
402
|
-
|
|
414
|
+
m = min(SEEK_MARGIN, head_trim)
|
|
415
|
+
cmd += ["-ss", f"{head_trim - m:.6f}"]
|
|
416
|
+
if m > 0:
|
|
417
|
+
out_seek = ["-ss", f"{m / (drift_ratio if drift_af else 1.0):.6f}"]
|
|
418
|
+
cmd += ["-i", args.second] + out_seek
|
|
403
419
|
af_parts = on_clock + drift_af
|
|
404
420
|
if delay_ms > 0:
|
|
405
421
|
af_parts.append(f"adelay={delay_ms}:all=1")
|
|
@@ -412,6 +428,14 @@ def main() -> int:
|
|
|
412
428
|
if vf:
|
|
413
429
|
cmd += ["-vf", ",".join(vf)]
|
|
414
430
|
cmd += video_args(probe(args.reference) if args.replace_audio else probe(args.second), args.crf)
|
|
431
|
+
# the output's first frame is the second file's frame at head_trim, or (padded)
|
|
432
|
+
# one `offset` before its first: its start timecode moves with it
|
|
433
|
+
v0, rate = stream_starts(second_meta)[0], frame_rate(second_meta)
|
|
434
|
+
# (a trim before a picture that starts after the file's origin starts on its
|
|
435
|
+
# first frame; only the padded head counts back before it)
|
|
436
|
+
first = (grid_at_or_after(max(round(head_trim, 4), v0), v0, rate) - v0 if head_trim > 0
|
|
437
|
+
else -offset if delay_ms > 0 else 0.0)
|
|
438
|
+
cmd += output_timecode_args(second_meta, first)
|
|
415
439
|
if af_parts:
|
|
416
440
|
cmd += ["-af", ",".join(af_parts)]
|
|
417
441
|
cmd += audio_codec_for(output) + [output]
|