ffmpeg-skill 2.24.1 → 3.0.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.
package/scripts/cut.py CHANGED
@@ -13,15 +13,16 @@ WAV), -o out.m4a writes AAC; an audio extension on a video input drops the
13
13
  picture (mp4 -> wav extraction). The result reports `precision`
14
14
  (packet / sample / codec_frame / frame) and the measured duration error, plus
15
15
  `mode` (copy / accurate / hybrid -- "hybrid" means a lossless cut silently
16
- re-encoded because the keyframe snap exceeded --tolerance), `keyframe_snapped`
17
- (a stream copy end to end), `start_snapped` (measured: a copied start moved past a frame),
16
+ re-encoded because the keyframe snap exceeded --tolerance), `keyframe_snapped` and
17
+ `start_snapped` (measured: a copied start moved past a frame; equal since 3.0.0),
18
18
  `reencode_reason`, `requested_start`/`requested_end` (or `requested_segments` for --segments),
19
19
  `requested_duration`, `output_duration` and `duration_delta_seconds` -- so a
20
20
  caller never has to trust "it probably cut where I asked" on faith.
21
21
 
22
- A single-segment .mp4/.mov copy shifts its timestamps to zero (-avoid_negative_ts
23
- make_zero), so the picture starts at the keyframe; --edit-list keeps the MP4 edit
24
- list instead, which hides the keyframe's pre-roll so the picture starts at --start.
22
+ A single-segment .mp4/.mov copy keeps the MP4 edit list (the default since 3.0.0),
23
+ which hides the keyframe's pre-roll so the picture starts at --start; --no-edit-list
24
+ shifts its timestamps to zero instead (-avoid_negative_ts make_zero, every 2.x
25
+ release's default), so the picture starts at the keyframe.
25
26
  An --accurate re-encode of a video does not: make_zero moved the picture by the
26
27
  AAC priming (about 21 ms, and further when a B-frame's decode time is negative)
27
28
  while the sound stayed at 0. A start that lands on a frame keeps that frame at 0.
@@ -29,14 +30,16 @@ A start between frames begins on the next frame and may keep up to one frame of
29
30
  timestamp. The MP4 edit list hides the encoder priming, and a source whose audio
30
31
  starts late keeps that offset.
31
32
 
32
- A re-encode uses x264 for an SDR source and x265 Main10 for an HDR or BT.2020 one, as every
33
- editing tool does; --keep-hevc re-encodes an SDR HEVC source as HEVC (x265 8-bit BT.709, or
34
- VideoToolbox under --hw), so a trimmed phone clip keeps its codec. --codec overrides both.
33
+ A re-encode keeps an SDR HEVC source HEVC (x265 8-bit BT.709, or VideoToolbox under --hw;
34
+ the default since 3.0.0), so a trimmed phone clip keeps its codec; --no-keep-hevc uses x264
35
+ as every 2.x release did. Other SDR sources get x264 and an HDR or BT.2020 one x265 Main10,
36
+ as every editing tool does. --codec overrides all of these.
35
37
 
36
- A source whose nominal and average frame rates differ is taken for variable-frame-rate and
37
- re-encoded, as --accurate (--vfr-guard average, the default); its sampled packet timing is
38
- reported in `vfr_check`. --vfr-guard sampled re-encodes only when that sampled timing is
39
- variable or too short to judge, and --vfr-guard off keeps the copy with a note.
38
+ The VFR guard samples a video's packet timing (`vfr_check`) and re-encodes, as --accurate,
39
+ only when it is variable or too short to judge (--vfr-guard sampled, the default since
40
+ 3.0.0). --vfr-guard average is every 2.x release's: a source whose nominal and average frame
41
+ rates differ is taken for variable-frame-rate and re-encoded. --vfr-guard off keeps the copy
42
+ with a note.
40
43
 
41
44
  Examples:
42
45
  python3 cut.py input.mp4 --start 00:00:10 --end 00:00:25
@@ -173,11 +176,10 @@ def least_exact(precisions: List[str]) -> str:
173
176
  def report_precision(meta: dict, dst: str, reencoded: bool, outcomes: List[dict]) -> dict:
174
177
  """The run's precision keys from its segments' outcomes.
175
178
 
176
- precision main's formula, unchanged: precision_of() for the whole run, so `packet` only
177
- when nothing re-encoded (the join included)
178
- keyframe_snapped precision == "packet": the run is a stream copy end to end, which lands on
179
- keyframes and packets. Kept with its 2.x meaning; whether a copied START
180
- really moved is start_snapped
179
+ precision since 3.0.0, the least exact segment's precision (least_exact_precision);
180
+ 2.x reported precision_of() for the whole run
181
+ keyframe_snapped since 3.0.0, start_snapped (a copied start really moved); 2.x reported
182
+ "the run is a stream copy end to end" (precision_of() == "packet")
181
183
  start_snapped measured: true when a copied segment's picture starts more than a frame from
182
184
  its requested start; false when every start is where asked (an edit list, a
183
185
  start on a keyframe, an audio-only copy's packet seek) or everything
@@ -186,7 +188,6 @@ def report_precision(meta: dict, dst: str, reencoded: bool, outcomes: List[dict]
186
188
  an audio-only --segments join of copied and re-encoded parts (a video join
187
189
  re-cuts every segment when any part re-encodes)
188
190
  segment_precision each segment's, for --segments (null for one segment)"""
189
- precision = precision_of(meta, dst, reencoded)
190
191
  snaps = [o.get("start_snapped") for o in outcomes]
191
192
  if any(s is True for s in snaps):
192
193
  start_snapped = True
@@ -194,8 +195,11 @@ def report_precision(meta: dict, dst: str, reencoded: bool, outcomes: List[dict]
194
195
  start_snapped = None
195
196
  else:
196
197
  start_snapped = False
197
- return {"precision": precision, "keyframe_snapped": precision == "packet", "start_snapped": start_snapped,
198
- "least_exact_precision": least_exact([o["precision"] for o in outcomes]),
198
+ least = least_exact([o["precision"] for o in outcomes])
199
+ # 3.0.0: `precision` is the least exact segment's and `keyframe_snapped` says whether a copied
200
+ # start really moved (2.x: the run-level formula, and "the run is a stream copy end to end")
201
+ return {"precision": least, "keyframe_snapped": start_snapped, "start_snapped": start_snapped,
202
+ "least_exact_precision": least,
199
203
  "segment_precision": [o["precision"] for o in outcomes] if len(outcomes) > 1 else None}
200
204
 
201
205
 
@@ -1308,13 +1312,24 @@ def main() -> int:
1308
1312
  help="the inverse of --segments: drop these ranges and keep the rest, joined in order "
1309
1313
  "(e.g. '1:20-2:05,10:00-10:30'); overlapping ranges merge")
1310
1314
  ap.add_argument("--accurate", action="store_true", help="always re-encode for frame-accurate (video) / sample-accurate (audio) cuts (default: lossless -c copy, re-encoding only when the keyframe snap exceeds --tolerance)")
1311
- ap.add_argument("--edit-list", action="store_true", help="a single-segment .mp4/.mov stream copy keeps the MP4 edit list that hides the keyframe's pre-roll, so the picture starts at --start (default: -avoid_negative_ts make_zero, the picture starts at the keyframe); a player that ignores edit lists shows the pre-roll")
1312
- ap.add_argument("--keep-hevc", action="store_true", help="a re-encode of an SDR HEVC source stays HEVC (x265 8-bit BT.709; VideoToolbox under --hw) instead of x264 (default: x264 for SDR, x265 Main10 for HDR); --codec overrides it")
1313
- ap.add_argument("--vfr-guard", choices=["average", "sampled", "off"], default="average",
1314
- help="what keeps a variable-frame-rate source from a stream copy (default average: a nominal rate "
1315
- "that differs from the average re-encodes, as --accurate, and the sampled frame timing is "
1316
- "reported; sampled: re-encode only when sampled packet timestamps are variable or too few "
1317
- "to judge; off: measure and keep the copy, with a note)")
1315
+ el = ap.add_mutually_exclusive_group()
1316
+ el.add_argument("--edit-list", action="store_true",
1317
+ help="a single-segment .mp4/.mov stream copy keeps the MP4 edit list that hides the keyframe's pre-roll, "
1318
+ "so the picture starts at --start (the default since 3.0.0); a player that ignores edit lists shows the pre-roll")
1319
+ el.add_argument("--no-edit-list", action="store_true",
1320
+ help="shift the copy's timestamps instead (-avoid_negative_ts make_zero, every 2.x release's default): "
1321
+ "the picture starts at the keyframe before --start")
1322
+ kh = ap.add_mutually_exclusive_group()
1323
+ kh.add_argument("--keep-hevc", action="store_true",
1324
+ help="a re-encode of an SDR HEVC source stays HEVC (x265 8-bit BT.709; VideoToolbox under --hw); "
1325
+ "the default since 3.0.0; --codec overrides it")
1326
+ kh.add_argument("--no-keep-hevc", action="store_true",
1327
+ help="re-encode an SDR HEVC source with x264, as every 2.x release did (HDR still gets x265 Main10)")
1328
+ ap.add_argument("--vfr-guard", choices=["average", "sampled", "off"], default="sampled",
1329
+ help="what keeps a variable-frame-rate source from a stream copy (default sampled since 3.0.0: re-encode "
1330
+ "only when sampled packet timestamps are variable or too few to judge; average, every 2.x "
1331
+ "release's default: a nominal rate that differs from the average re-encodes, as --accurate, "
1332
+ "and the sampled timing is reported; off: measure and keep the copy, with a note)")
1318
1333
  ap.add_argument("--tolerance", type=float, default=0.5, help="max seconds a lossless cut may deviate before re-encoding kicks in (default 0.5, -1 = never for a "
1319
1334
  "keyframe snap; a --segments join is still re-cut for open GOPs, a later B-frame .mp4/.mov "
1320
1335
  "segment starting between keyframes, or a failed join check)")
@@ -1340,7 +1355,9 @@ def main() -> int:
1340
1355
  args = ap.parse_args()
1341
1356
  apply_common(args)
1342
1357
  global KEEP_HEVC
1343
- KEEP_HEVC = args.keep_hevc
1358
+ # 3.0.0 defaults: keep HEVC and the edit list unless the --no- form is given (--edit-list and
1359
+ # --keep-hevc stay accepted, and now say what happens anyway)
1360
+ KEEP_HEVC = not args.no_keep_hevc
1344
1361
 
1345
1362
  meta = probe(args.input)
1346
1363
  total = meta.get("duration") or 0.0
@@ -1492,7 +1509,7 @@ def main() -> int:
1492
1509
  join_check = None
1493
1510
  if len(segments) == 1:
1494
1511
  outcomes.append(cut_one(args.input, segments[0][0], segments[0][1], output, args.accurate, args.crf, args.preset, args.tolerance, meta,
1495
- edit_list_ok=args.edit_list))
1512
+ edit_list_ok=not args.no_edit_list))
1496
1513
  elif hold_needed or args.accurate:
1497
1514
  # Straight to one encode through the concat filter. A held frame needs the re-cut (no part
1498
1515
  # can carry it into a copy join); and when every part re-encodes anyway, encoding each on