Files
ALPHA-TRAIN2/services/tiktak/video/preview.go
T
bobiqqandClaude Opus 5 e9e8f168fc
build-and-push / detect (push) Successful in 11s
build-and-push / build (${{ fromJSON(needs.detect.outputs.services) }}) (push) Successful in 31s
tiktak: откатить -threads 1, развести бюджеты probe и ffmpeg
MUMBLE "couldn't create video" -- моя регрессия из 576baa3/753d21c. В логах
у части загрузок есть Stream mapping и "Press [q]", но нет "Output #0":
ffmpeg не доработал, его снял дедлайн.

Две причины:
- "-threads 1" душил многопоточное декодирование vp9. 12-секундное 640x360
  в 2 секунды одним потоком не влезает.
- checkGeometry вызывал ffprobe ВНУТРИ того же 2-секундного контекста, что
  и кодек, отбирая у него время.

- убран -threads 1, -max_alloc оставлен (на скорость не влияет);
- probe получил свой бюджет ProbeDeadline=1s, ffmpeg -- полные
  ProcessingDeadline=2.5s. Сумма с GetDuration укладывается в 7с
  контекста handleCreate с запасом;
- семафор 16 -> 48: под флудом 16 слотов создавали очередь, и чекер
  выпадал по таймауту ещё до обработки. Бомбы теперь отсекаются дёшево,
  слоты освобождаются быстро.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 13:49:22 +03:00

95 lines
2.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package video
import (
"context"
"fmt"
"github.com/disintegration/imaging"
"image"
"image/png"
"io"
"os"
"os/exec"
"strings"
"time"
)
var (
// 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 {
// -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",
"-max_alloc", "134217728", "-i", inp, "-vframes", "1", out)
s := strings.Builder{}
cmd.Stdout = &s
cmd.Stderr = &s
err := cmd.Run()
if err != nil {
fmt.Println(s.String())
}
return err
}
func GeneratePreview(ctx context.Context, inp string, out string) error {
select {
case semaphore <- struct{}{}:
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()
}
}
func Blur(inp string, out string) error {
f, err := os.Open(inp)
if err != nil {
return err
}
defer f.Close()
// Same guard on the decode side: imaging.Blur keeps several copies of the
// bitmap alive, so an oversized preview is an OOM all by itself.
cfg, _, err := image.DecodeConfig(f)
if err != nil {
return err
}
if cfg.Width*cfg.Height > MaxPixels {
return fmt.Errorf("preview %dx%d is too large to blur", cfg.Width, cfg.Height)
}
if _, err := f.Seek(0, io.SeekStart); err != nil {
return err
}
img, _, err := image.Decode(f)
if err != nil {
return err
}
outimg := imaging.Blur(img, 5)
outfile, err := os.Create(out)
if err != nil {
return err
}
defer outfile.Close()
return png.Encode(outfile, outimg)
}