Files
ds-galo/backend/internal/video/toolchain.go
T
iu d38ede0201 feat(video): add video upload with remux/transcode playback
Add video as a media kind in photos: streaming upload with per-kind
limits, ffprobe metadata, poster frame reusing the image variant
pipeline, and a playback object (remux when web-compatible, else
h264/yuv420p/aac transcode). Video jobs run on a separate pool with a
long lease. Frontend gains a video viewer, grid badge/duration and
video upload.
2026-10-11 20:37:53 +03:00

78 lines
1.9 KiB
Go

// Package video prepares uploaded video for browser playback using the external
// ffmpeg/ffprobe toolchain. Availability is detected at startup; when the tools
// are absent the rest of the application keeps working and video upload is
// disabled.
package video
import (
"os/exec"
"galo/internal/config"
)
// Toolchain locates the external video tools and reports whether they are
// usable. A nil Toolchain is treated as unavailable.
type Toolchain struct {
ffmpegPath string
ffprobePath string
tempDir string
available bool
}
// Detect resolves the configured tool paths and records whether both binaries
// are present. Missing tools are not an error: video is simply unavailable.
func Detect(cfg config.Video) *Toolchain {
t := &Toolchain{tempDir: cfg.TempDir}
if p, err := exec.LookPath(cfg.FFmpegPath); err == nil {
t.ffmpegPath = p
} else {
t.ffmpegPath = cfg.FFmpegPath
}
if p, err := exec.LookPath(cfg.FFprobePath); err == nil {
t.ffprobePath = p
} else {
t.ffprobePath = cfg.FFprobePath
}
t.available = t.ffmpegPath != "" && t.ffprobePath != "" &&
isExecutable(t.ffmpegPath) && isExecutable(t.ffprobePath)
return t
}
func isExecutable(path string) bool {
if path == "" {
return false
}
if _, err := exec.LookPath(path); err == nil {
return true
}
return false
}
// Available reports whether both ffmpeg and ffprobe are usable.
func (t *Toolchain) Available() bool { return t != nil && t.available }
// FFmpegPath returns the resolved ffmpeg binary path.
func (t *Toolchain) FFmpegPath() string {
if t == nil {
return ""
}
return t.ffmpegPath
}
// FFprobePath returns the resolved ffprobe binary path.
func (t *Toolchain) FFprobePath() string {
if t == nil {
return ""
}
return t.ffprobePath
}
// TempDir returns the configured temporary directory (empty means the system
// default).
func (t *Toolchain) TempDir() string {
if t == nil {
return ""
}
return t.tempDir
}