@@ -56,7 +56,15 @@ def getMarkerPairSettings( # noqa: PLR0912
56
56
mps ["titlePrefix" ] = cleanFileName (mps ["titlePrefix" ])
57
57
titlePrefix = f'{ mps ["titlePrefix" ] + "-" if "titlePrefix" in mps else "" } '
58
58
mp ["fileNameStem" ] = f'{ titlePrefix } { mps ["titleSuffix" ]} -{ markerPairIndex + 1 } '
59
- mp ["fileNameSuffix" ] = "mp4" if mps ["videoCodec" ] == "h264" else "webm"
59
+
60
+ if mps ["fastTrim" ]:
61
+ if mps ["inputVideo" ]:
62
+ mp ["fileNameSuffix" ] = Path (mps ["inputVideo" ]).suffix .removeprefix ("." )
63
+ else :
64
+ mp ["fileNameSuffix" ] = mps ["ext" ]
65
+ else :
66
+ mp ["fileNameSuffix" ] = "mp4" if mps ["videoCodec" ] == "h264" else "webm"
67
+
60
68
mp ["fileName" ] = f'{ mp ["fileNameStem" ]} .{ mp ["fileNameSuffix" ]} '
61
69
mp ["filePath" ] = f'{ cp .clipsPath } /{ mp ["fileName" ]} '
62
70
mp ["exists" ] = checkClipExists (
@@ -237,6 +245,63 @@ def findVideoPart(mp: DictStrAny, mps: DictStrAny) -> Optional[DictStrAny]:
237
245
return None
238
246
239
247
248
+ FFMPEG_NETWORK_INPUT_FLAGS = (
249
+ r"-reconnect 1 -reconnect_at_eof 1 -reconnect_streamed 1 -reconnect_delay_max 5"
250
+ )
251
+
252
+
253
+ def fastTrimClip (
254
+ cs : ClipperState ,
255
+ markerPairIndex : int ,
256
+ mp : DictStrAny ,
257
+ mps : DictStrAny ,
258
+ ) -> Optional [Dict [str , Any ]]:
259
+ settings = cs .settings
260
+ cp = cs .clipper_paths
261
+ inputs = ""
262
+
263
+ if mp ["isVariableSpeed" ] or mps ["loop" ] != "none" :
264
+ mps ["audio" ] = False
265
+
266
+ if mps ["audio" ]:
267
+ aStart = mp ["start" ] + mps ["audioDelay" ]
268
+ aEnd = mp ["end" ] + mps ["audioDelay" ]
269
+ # aDuration = aEnd - aStart
270
+ # ffplay previewing does not support multiple inputs
271
+ # if an input video is provided or previewing is on, there is only one input
272
+ if not mps ["inputVideo" ] and not settings ["preview" ]:
273
+ inputs += FFMPEG_NETWORK_INPUT_FLAGS
274
+ inputs += f' -ss { aStart } -to { aEnd } -i "{ mps ["audioDownloadURL" ]} " '
275
+ # when streaming the required chunks from the internet the video and audio inputs are separate
276
+ else :
277
+ mps ["audio" ] = False
278
+ logger .warning (
279
+ "Audio disabled when previewing without an input video over non-dash protocol." ,
280
+ )
281
+
282
+ if not mps ["inputVideo" ]:
283
+ inputs += FFMPEG_NETWORK_INPUT_FLAGS
284
+
285
+ videoStart = mp ["start" ]
286
+ videoEnd = mp ["end" ]
287
+ if mps ["inputVideo" ]:
288
+ inputs += f' -ss { videoStart } -to { videoEnd } -i "{ mps ["inputVideo" ]} " '
289
+ elif mps ["videoType" ] != "multi_video" :
290
+ inputs += f' -ss { videoStart } -to { videoEnd } -i "{ mps ["videoDownloadURL" ]} " '
291
+ elif "videoPart" in mp :
292
+ videoPart = mp ["videoPart" ]
293
+ inputs += f' -ss { videoStart } -to { videoEnd } -i "{ videoPart ["url" ]} " '
294
+ else :
295
+ logger .error (
296
+ f'Failed to generate: "{ mp ["fileName" ]} ". The marker pair defines a clip that spans multiple video parts which is not currently supported.' ,
297
+ )
298
+ return None
299
+
300
+ ffmpegCommand = getFfmpegCommandFastTrim (cp , inputs , mp , mps )
301
+
302
+ return runffmpegCommand (settings , [ffmpegCommand ], markerPairIndex , mp )
303
+
304
+
240
305
def makeClip (cs : ClipperState , markerPairIndex : int ) -> Optional [Dict [str , Any ]]: # noqa: PLR0912
241
306
settings = cs .settings
242
307
cp = cs .clipper_paths
@@ -246,22 +311,27 @@ def makeClip(cs: ClipperState, markerPairIndex: int) -> Optional[Dict[str, Any]]
246
311
if mp ["exists" ] and not mps ["overwrite" ]:
247
312
return {** (settings ["markerPairs" ][markerPairIndex ]), ** mp }
248
313
314
+ if mps ["fastTrim" ]:
315
+ logger .notice (
316
+ f"Fast-trim enabled for marker pair { markerPairIndex } . Features that require re-encoding (including crop and speed) will be disabled." ,
317
+ )
318
+ return fastTrimClip (cs , markerPairIndex , mp , mps )
319
+
249
320
inputs = ""
250
321
audio_filter = ""
251
322
video_filter = ""
252
323
253
324
if mp ["isVariableSpeed" ] or mps ["loop" ] != "none" :
254
325
mps ["audio" ] = False
255
326
256
- inputFlags = r"-reconnect 1 -reconnect_at_eof 1 -reconnect_streamed 1 -reconnect_delay_max 5"
257
327
if mps ["audio" ]:
258
328
aStart = mp ["start" ] + mps ["audioDelay" ]
259
329
aEnd = mp ["end" ] + mps ["audioDelay" ]
260
330
aDuration = aEnd - aStart
261
331
# ffplay previewing does not support multiple inputs
262
332
# if an input video is provided or previewing is on, there is only one input
263
333
if not mps ["inputVideo" ] and not settings ["preview" ]:
264
- inputs += inputFlags
334
+ inputs += FFMPEG_NETWORK_INPUT_FLAGS
265
335
inputs += f' -ss { aStart } -to { aEnd } -i "{ mps ["audioDownloadURL" ]} " '
266
336
267
337
# preview mode does not start each clip at time 0 unlike encoding mode
@@ -283,7 +353,7 @@ def makeClip(cs: ClipperState, markerPairIndex: int) -> Optional[Dict[str, Any]]
283
353
audio_filter += f',{ mps ["extraAudioFilters" ]} '
284
354
285
355
if not mps ["inputVideo" ]:
286
- inputs += inputFlags
356
+ inputs += FFMPEG_NETWORK_INPUT_FLAGS
287
357
288
358
if mps ["inputVideo" ]:
289
359
inputs += f' -ss { mp ["start" ]} -i "{ mps ["inputVideo" ]} " '
@@ -309,7 +379,16 @@ def makeClip(cs: ClipperState, markerPairIndex: int) -> Optional[Dict[str, Any]]
309
379
+ f'({ mps ["targetSize" ]} MB / ~{ round (mp ["outputDuration" ],3 )} s).' ,
310
380
)
311
381
312
- ffmpegCommand = getFfmpegCommand (audio_filter , cbr , cp , inputs , mp , mps , qmax , qmin )
382
+ ffmpegCommand = getFfmpegCommandWithoutVideoFilter (
383
+ audio_filter ,
384
+ cbr ,
385
+ cp ,
386
+ inputs ,
387
+ mp ,
388
+ mps ,
389
+ qmax ,
390
+ qmin ,
391
+ )
313
392
314
393
if not mps ["preview" ]:
315
394
video_filter += f'trim=0:{ mp ["duration" ]} '
@@ -574,7 +653,7 @@ def makeClip(cs: ClipperState, markerPairIndex: int) -> Optional[Dict[str, Any]]
574
653
return runffmpegCommand (settings , ffmpegCommands , markerPairIndex , mp )
575
654
576
655
577
- def getFfmpegCommand (
656
+ def getFfmpegCommandWithoutVideoFilter (
578
657
audio_filter : str ,
579
658
cbr : Optional [int ],
580
659
cp : ClipperPaths ,
@@ -616,6 +695,36 @@ def getFfmpegCommand(
616
695
)
617
696
618
697
698
+ def getFfmpegCommandFastTrim (
699
+ cp : ClipperPaths ,
700
+ inputs : str ,
701
+ mp : DictStrAny ,
702
+ mps : DictStrAny ,
703
+ ) -> str :
704
+ overwriteArg = " -y " if mps ["overwrite" ] else " -n "
705
+
706
+ return " " .join (
707
+ (
708
+ cp .ffmpegPath ,
709
+ overwriteArg ,
710
+ f"-hide_banner" ,
711
+ getFfmpegHeaders (mps ["platform" ]),
712
+ inputs ,
713
+ f"-benchmark" ,
714
+ # f'-loglevel 56',
715
+ f"-c copy" ,
716
+ (
717
+ f'-metadata title="{ mps ["videoTitle" ]} "'
718
+ if not mps ["removeMetadata" ]
719
+ else "-map_metadata -1"
720
+ ),
721
+ f"" if mps ["audio" ] else "-an" ,
722
+ f'{ mps ["extraFfmpegArgs" ]} ' ,
723
+ f'{ mp ["filePath" ]} ' " " ,
724
+ ),
725
+ )
726
+
727
+
619
728
def getFfmpegVideoCodec (
620
729
videoCodec : str ,
621
730
cbr : Optional [int ],
0 commit comments