Add per-disk profiles with video transcoding support

Each disk stores .jukebox/profile.json with copy parameters (dest
folder, overwrite mode, file select, reserve space, auto-copy) and
optional transcoding limits for the target player (codec, resolution,
bitrate, FPS, audio channels, output format).

On copy, video files are probed with ffprobe; if they exceed the
profile limits they are transcoded via ffmpeg (-threads 0 for full
CPU usage), otherwise copied as-is. Scale filter never upscales.

New: internal/disk/profile.go, internal/transcoder/{detect,transcoder}.go
API: GET/PUT /api/disks/profile?mount_path=
UI: disk profile panel in dashboard for known disks
Dockerfile: adds ffmpeg to the runtime image

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-21 20:52:46 +03:00
parent 6953c151fe
commit 9fd02fb5bf
12 changed files with 718 additions and 28 deletions

View File

@@ -126,7 +126,7 @@ func main() {
switch ev.Info.State {
case disk.DiskKnown:
openDiskDB(ev.Info)
if watcherReady && ev.Prev.State != disk.DiskKnown && cfg.AutoCopy {
if watcherReady && ev.Prev.State != disk.DiskKnown {
triggerAutoCopy(cp, cfg, ev.Info)
}
case disk.DiskForeign:
@@ -197,6 +197,15 @@ func main() {
}
func triggerAutoCopy(cp *copier.Copier, cfg *config.Config, info disk.DiskInfo) {
// Используем AutoCopy из профиля диска, если он есть; иначе — из глобального config
autoCopy := cfg.AutoCopy
if info.Profile != nil {
autoCopy = info.Profile.AutoCopy
}
if !autoCopy {
return
}
hasEnabledSources := false
for _, s := range cfg.Sources {
if s.Enabled {
@@ -207,18 +216,31 @@ func triggerAutoCopy(cp *copier.Copier, cfg *config.Config, info disk.DiskInfo)
if !hasEnabledSources {
return
}
opts := copier.Options{
DiskID: info.DiskID,
MountPath: info.MountPath,
MediaPath: cfg.MediaPath,
SourceRules: cfg.Sources,
AllowedExtensions: cfg.EffectiveAllowedExtensions(),
OverwriteMode: cfg.OverwriteMode,
}
if p := info.Profile; p != nil {
opts.DestFolder = p.DestFolder
opts.ReserveFreeGB = p.ReserveFreeGB
opts.FileSelectMode = config.FileSelectMode(p.FileSelectMode)
opts.Transcode = p.Transcode
if p.OverwriteMode != "" {
opts.OverwriteMode = config.OverwriteMode(p.OverwriteMode)
}
} else {
opts.DestFolder = cfg.DestFolder
opts.ReserveFreeGB = cfg.ReserveFreeGB
opts.FileSelectMode = cfg.FileSelectMode
}
go func() {
_, err := cp.Start(context.Background(), copier.Options{
DiskID: info.DiskID,
MountPath: info.MountPath,
MediaPath: cfg.MediaPath,
DestFolder: cfg.DestFolder,
SourceRules: cfg.Sources,
AllowedExtensions: cfg.EffectiveAllowedExtensions(),
ReserveFreeGB: cfg.ReserveFreeGB,
OverwriteMode: cfg.OverwriteMode,
FileSelectMode: cfg.FileSelectMode,
})
_, err := cp.Start(context.Background(), opts)
if err != nil {
log.Printf("auto-copy: %v", err)
}