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.
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user