@@ -270,6 +270,8 @@ proc makeNewVideoFrames*(output: var OutputContainer, tl: v3, args: mainArgs):
270
270
var frameIndex = - 1
271
271
var frame: ptr AVFrame = av_frame_clone(nullFrame)
272
272
var objList: seq [VideoFrame] = @ []
273
+ var lastProcessedFrame: ptr AVFrame = nil
274
+ var lastFrameIndex = - 1
273
275
274
276
return (encoderCtx, outputStream, iterator (): (ptr AVFrame, int ) =
275
277
# Process each frame in timeline order like Python version
@@ -279,21 +281,34 @@ proc makeNewVideoFrames*(output: var OutputContainer, tl: v3, args: mainArgs):
279
281
for layer in tl.v:
280
282
for obj in layer.clips:
281
283
if index >= obj.start and index < (obj.start + obj.dur):
282
- let i = int (round(float (obj.offset + index - obj.start) * obj.speed))
284
+ # Convert timeline position from target framerate to source framerate
285
+ let timelinePos = obj.offset + index - obj.start
286
+ let srcStream = cns[obj.src].video[0 ]
287
+ let srcTb = srcStream.avg_frame_rate
288
+ let sourceFramePos = int (round(float (timelinePos) * srcTb.float / tl.tb.float ))
289
+ let i = int (round(float (sourceFramePos) * obj.speed))
283
290
objList.add VideoFrame(index: i, src: obj.src)
284
291
285
292
if tl.chunks.isSome:
286
- # When there can be valid gaps in the timeline.
293
+ # When there can be valid gaps in the timeline and no objects for this frame .
287
294
frame = av_frame_clone(nullFrame)
288
- # else, use the last frame
295
+ # else, use the last frame or process objects
289
296
290
297
for obj in objList:
298
+ # Check if we can reuse the last processed frame
299
+ if obj.index == lastFrameIndex and lastProcessedFrame != nil :
300
+ frame = av_frame_clone(lastProcessedFrame)
301
+ continue
302
+
291
303
var myStream: ptr AVStream = cns[obj.src].video[0 ]
292
304
if frameIndex > obj.index:
293
305
debug(& " Seek: { frameIndex} -> 0 " )
294
306
cns[obj.src].seek(0 )
295
307
avcodec_flush_buffers(decoders[obj.src])
296
308
309
+ # obj.index is already in source frame coordinates, no conversion needed
310
+ let srcTb = myStream.avg_frame_rate
311
+
297
312
while frameIndex < obj.index:
298
313
# Check if skipping ahead is worth it.
299
314
if obj.index - frameIndex > seekCost[obj.src] and frameIndex > seekThreshold:
@@ -307,7 +322,7 @@ proc makeNewVideoFrames*(output: var OutputContainer, tl: v3, args: mainArgs):
307
322
var foundFrame = false
308
323
for decodedFrame in cns[obj.src].flushDecode(myStream.index.cint , decoder, frame):
309
324
frame = decodedFrame
310
- frameIndex = int (round(decodedFrame.time(myStream.time_base) * tl.tb .float ))
325
+ frameIndex = int (round(decodedFrame.time(myStream.time_base) * srcTb .float ))
311
326
foundFrame = true
312
327
break
313
328
@@ -353,9 +368,19 @@ proc makeNewVideoFrames*(output: var OutputContainer, tl: v3, args: mainArgs):
353
368
frame.pts = index.int64
354
369
frame.time_base = av_inv_q(tl.tb)
355
370
frame.duration = index.int64
371
+
372
+ # Update cache for frame reuse BEFORE yielding (which will unref the frame)
373
+ if objList.len > 0 :
374
+ if lastProcessedFrame != nil :
375
+ av_frame_free(addr lastProcessedFrame)
376
+ lastProcessedFrame = av_frame_clone(frame)
377
+ lastFrameIndex = objList[0 ].index
378
+
356
379
yield (frame, index)
357
380
358
381
if scaleGraph != nil :
359
382
scaleGraph.cleanup()
360
383
av_frame_free(addr nullFrame)
384
+ if lastProcessedFrame != nil :
385
+ av_frame_free(addr lastProcessedFrame)
361
386
debug(& " Total frames saved seeking: { framesSaved} " ))
0 commit comments