diff --git a/services/tiktak/video/preview.go b/services/tiktak/video/preview.go index 443355b..2949a33 100644 --- a/services/tiktak/video/preview.go +++ b/services/tiktak/video/preview.go @@ -14,21 +14,19 @@ import ( ) var ( - ProcessingDeadline = 2 * time.Second - // 75 concurrent ffmpeg processes are enough to swap the box out on their - // own, bomb or no bomb. - semaphore = make(chan struct{}, 16) + // handleCreate даёт 7с на всё: probe + ffmpeg + GetDuration для субтитров. + // 1.0 + 2.5 + 2.5 = 6.0, секунда про запас. + ProcessingDeadline = 2500 * time.Millisecond + // Header-only ffprobe; cheap, но не должен отъедать бюджет у кодека. + ProbeDeadline = 1 * time.Second + semaphore = make(chan struct{}, 48) ) func generatePreview(ctx context.Context, inp string, out string) error { - // Reject decompression bombs before ffmpeg expands a frame into memory. - if err := checkGeometry(ctx, inp); err != nil { - return err - } // -max_alloc caps a single ffmpeg allocation: a 1080p frame needs ~8 MB, a // 16383x16383 bomb needs ~1 GB. Second line of defence behind checkGeometry, // and it works even when the probe told us nothing. - cmd := exec.CommandContext(ctx, "ffmpeg", "-y", "-threads", "1", + cmd := exec.CommandContext(ctx, "ffmpeg", "-y", "-max_alloc", "134217728", "-i", inp, "-vframes", "1", out) s := strings.Builder{} cmd.Stdout = &s @@ -43,12 +41,20 @@ func generatePreview(ctx context.Context, inp string, out string) error { func GeneratePreview(ctx context.Context, inp string, out string) error { select { case semaphore <- struct{}{}: - ctx, cancel := context.WithTimeout(ctx, ProcessingDeadline) - defer func() { - cancel() - <-semaphore - }() - return generatePreview(ctx, inp, out) + defer func() { <-semaphore }() + + // Probe gets its OWN budget. Sharing ProcessingDeadline with ffmpeg + // starved the encode and killed legit uploads ("cannot create video"). + pctx, pcancel := context.WithTimeout(ctx, ProbeDeadline) + err := checkGeometry(pctx, inp) + pcancel() + if err != nil { + return err + } + + fctx, fcancel := context.WithTimeout(ctx, ProcessingDeadline) + defer fcancel() + return generatePreview(fctx, inp, out) case <-ctx.Done(): return ctx.Err() }