moonlantern1 commited on
Commit
abff8d2
·
verified ·
1 Parent(s): 5501e35

Prevent visual looping in Classic render

Browse files
Files changed (1) hide show
  1. src/lib/server/serverClipRenderer.ts +63 -21
src/lib/server/serverClipRenderer.ts CHANGED
@@ -25,7 +25,6 @@ const WORK_DIR = path.join(process.cwd(), '.local-review-data', 'server-renders'
25
  const VIDEO_WIDTH = 1080;
26
  const VIDEO_HEIGHT = 1920;
27
  const VIDEO_FPS = 24;
28
- const MIN_VIDEO_CLIP_SECONDS = 5;
29
  const MAX_VIDEO_CLIP_SECONDS = 5;
30
  const MAX_AUDIO_CLIP_SECONDS = 5;
31
  const FINAL_VIDEO_MIN_SECONDS = 12;
@@ -213,6 +212,12 @@ function maxSecondsForStep(step: number) {
213
  return STEP_VIDEO_MAX_SECONDS[step] ?? MAX_VIDEO_CLIP_SECONDS;
214
  }
215
 
 
 
 
 
 
 
216
  function addSegment(segments: VideoSegment[], source: PreparedClip | null, duration: number) {
217
  if (!source || duration < 0.25) return;
218
  segments.push({
@@ -221,16 +226,38 @@ function addSegment(segments: VideoSegment[], source: PreparedClip | null, durat
221
  });
222
  }
223
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
  function buildLinearSegments(videoClips: PreparedClip[]) {
225
  const segments: VideoSegment[] = [];
226
  let remaining = FINAL_VIDEO_MAX_SECONDS;
227
 
228
  for (const source of videoClips) {
229
  if (remaining <= 0.25) break;
230
- const stepMax = maxSecondsForStep(source.step);
231
- const fallbackDuration = Math.min(MIN_VIDEO_CLIP_SECONDS, stepMax);
232
- const sourceDuration =
233
- source.duration > 0 ? Math.min(source.duration, stepMax) : fallbackDuration;
234
  const duration = Math.min(sourceDuration, remaining);
235
  addSegment(segments, source, duration);
236
  remaining -= duration;
@@ -255,11 +282,13 @@ function buildVoiceAwareSegments({
255
  const actionShot = clipByStep(videoClips, 3) ?? videoClips[2] ?? wideShot;
256
  const reactionShot =
257
  clipByStep(videoClips, 4) ??
 
258
  (videoClips.length > 3 ? videoClips[videoClips.length - 1]! : null);
259
 
260
- const orderAudio = clipByStep(audioClips, 5) ?? audioClips[0] ?? null;
261
  const likedAudio =
262
  clipByStep(audioClips, 6) ??
 
263
  audioClips.find((clip) => clip !== orderAudio) ??
264
  null;
265
  const recommendationAudio =
@@ -292,20 +321,34 @@ function buildVoiceAwareSegments({
292
  const likedSeconds = Math.max(0, mainVoiceSeconds - orderSeconds);
293
 
294
  const segments: VideoSegment[] = [];
 
295
 
296
  if (closeShot === wideShot || orderSeconds < 2.5) {
297
- addSegment(segments, closeShot, orderSeconds);
298
  } else {
299
  const closeSeconds = clamp(orderSeconds * 0.58, 1.25, orderSeconds - 0.75);
300
- addSegment(segments, closeShot, closeSeconds);
301
- addSegment(segments, wideShot, orderSeconds - closeSeconds);
302
  }
303
 
304
- addSegment(segments, actionShot, likedSeconds);
305
-
306
- addSegment(segments, wideShot, recommendationSeconds);
307
-
308
- addSegment(segments, reactionShot, reactionSeconds);
 
 
 
 
 
 
 
 
 
 
 
 
 
309
 
310
  return segments.length > 0 ? segments : buildLinearSegments(videoClips);
311
  }
@@ -379,14 +422,15 @@ export async function renderClipsOnServer(input: ServerRenderInput): Promise<Ser
379
  renderTargetSeconds: voiceoverTargetSeconds,
380
  })
381
  : buildLinearSegments(preparedVideoClips);
 
 
 
 
382
  const renderTargetSeconds = hasVoiceover
383
- ? voiceoverTargetSeconds
384
  : Math.max(
385
  1,
386
- Math.min(
387
- FINAL_VIDEO_MAX_SECONDS,
388
- videoSegments.reduce((total, segment) => total + segment.duration, 0),
389
- ),
390
  );
391
 
392
  if (videoSegments.length === 0) {
@@ -395,8 +439,6 @@ export async function renderClipsOnServer(input: ServerRenderInput): Promise<Ser
395
 
396
  const inputArgs = [
397
  ...videoSegments.flatMap((segment) => [
398
- '-stream_loop',
399
- '-1',
400
  '-t',
401
  formatDuration(segment.duration),
402
  '-i',
 
25
  const VIDEO_WIDTH = 1080;
26
  const VIDEO_HEIGHT = 1920;
27
  const VIDEO_FPS = 24;
 
28
  const MAX_VIDEO_CLIP_SECONDS = 5;
29
  const MAX_AUDIO_CLIP_SECONDS = 5;
30
  const FINAL_VIDEO_MIN_SECONDS = 12;
 
212
  return STEP_VIDEO_MAX_SECONDS[step] ?? MAX_VIDEO_CLIP_SECONDS;
213
  }
214
 
215
+ function usableClipDuration(source: PreparedClip) {
216
+ const stepMax = maxSecondsForStep(source.step);
217
+ if (source.duration > 0) return Math.min(source.duration, stepMax);
218
+ return stepMax;
219
+ }
220
+
221
  function addSegment(segments: VideoSegment[], source: PreparedClip | null, duration: number) {
222
  if (!source || duration < 0.25) return;
223
  segments.push({
 
226
  });
227
  }
228
 
229
+ function createClipBudgets(videoClips: PreparedClip[]) {
230
+ const budgets = new Map<string, number>();
231
+ for (const clip of videoClips) {
232
+ budgets.set(clip.path, Math.max(budgets.get(clip.path) ?? 0, usableClipDuration(clip)));
233
+ }
234
+ return budgets;
235
+ }
236
+
237
+ function addSegmentWithBudget(
238
+ segments: VideoSegment[],
239
+ budgets: Map<string, number>,
240
+ source: PreparedClip | null,
241
+ duration: number,
242
+ ) {
243
+ if (!source || duration < 0.25) return 0;
244
+
245
+ const remaining = budgets.get(source.path) ?? usableClipDuration(source);
246
+ const actualDuration = Math.min(duration, remaining);
247
+ if (actualDuration < 0.25) return 0;
248
+
249
+ budgets.set(source.path, Math.max(0, remaining - actualDuration));
250
+ addSegment(segments, source, actualDuration);
251
+ return actualDuration;
252
+ }
253
+
254
  function buildLinearSegments(videoClips: PreparedClip[]) {
255
  const segments: VideoSegment[] = [];
256
  let remaining = FINAL_VIDEO_MAX_SECONDS;
257
 
258
  for (const source of videoClips) {
259
  if (remaining <= 0.25) break;
260
+ const sourceDuration = usableClipDuration(source);
 
 
 
261
  const duration = Math.min(sourceDuration, remaining);
262
  addSegment(segments, source, duration);
263
  remaining -= duration;
 
282
  const actionShot = clipByStep(videoClips, 3) ?? videoClips[2] ?? wideShot;
283
  const reactionShot =
284
  clipByStep(videoClips, 4) ??
285
+ videoClips.find((clip) => clip.step >= 6) ??
286
  (videoClips.length > 3 ? videoClips[videoClips.length - 1]! : null);
287
 
288
+ const orderAudio = clipByStep(audioClips, 5) ?? clipByStep(audioClips, 4) ?? audioClips[0] ?? null;
289
  const likedAudio =
290
  clipByStep(audioClips, 6) ??
291
+ clipByStep(audioClips, 5) ??
292
  audioClips.find((clip) => clip !== orderAudio) ??
293
  null;
294
  const recommendationAudio =
 
321
  const likedSeconds = Math.max(0, mainVoiceSeconds - orderSeconds);
322
 
323
  const segments: VideoSegment[] = [];
324
+ const budgets = createClipBudgets(videoClips);
325
 
326
  if (closeShot === wideShot || orderSeconds < 2.5) {
327
+ addSegmentWithBudget(segments, budgets, closeShot, orderSeconds);
328
  } else {
329
  const closeSeconds = clamp(orderSeconds * 0.58, 1.25, orderSeconds - 0.75);
330
+ addSegmentWithBudget(segments, budgets, closeShot, closeSeconds);
331
+ addSegmentWithBudget(segments, budgets, wideShot, orderSeconds - closeSeconds);
332
  }
333
 
334
+ addSegmentWithBudget(segments, budgets, actionShot, likedSeconds);
335
+
336
+ let remainingRecommendationSeconds = recommendationSeconds;
337
+ remainingRecommendationSeconds -= addSegmentWithBudget(
338
+ segments,
339
+ budgets,
340
+ wideShot,
341
+ remainingRecommendationSeconds,
342
+ );
343
+ remainingRecommendationSeconds -= addSegmentWithBudget(
344
+ segments,
345
+ budgets,
346
+ actionShot,
347
+ remainingRecommendationSeconds,
348
+ );
349
+ addSegmentWithBudget(segments, budgets, closeShot, remainingRecommendationSeconds);
350
+
351
+ addSegmentWithBudget(segments, budgets, reactionShot, reactionSeconds);
352
 
353
  return segments.length > 0 ? segments : buildLinearSegments(videoClips);
354
  }
 
422
  renderTargetSeconds: voiceoverTargetSeconds,
423
  })
424
  : buildLinearSegments(preparedVideoClips);
425
+ const visualDurationSeconds = videoSegments.reduce(
426
+ (total, segment) => total + segment.duration,
427
+ 0,
428
+ );
429
  const renderTargetSeconds = hasVoiceover
430
+ ? Math.max(1, Math.min(voiceoverTargetSeconds, visualDurationSeconds))
431
  : Math.max(
432
  1,
433
+ Math.min(FINAL_VIDEO_MAX_SECONDS, visualDurationSeconds),
 
 
 
434
  );
435
 
436
  if (videoSegments.length === 0) {
 
439
 
440
  const inputArgs = [
441
  ...videoSegments.flatMap((segment) => [
 
 
442
  '-t',
443
  formatDuration(segment.duration),
444
  '-i',