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>
71 lines
2.0 KiB
Go
71 lines
2.0 KiB
Go
package disk
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
)
|
|
|
|
type DiskProfile struct {
|
|
DestFolder string `json:"dest_folder"`
|
|
OverwriteMode string `json:"overwrite_mode"`
|
|
FileSelectMode string `json:"file_select_mode"`
|
|
ReserveFreeGB float64 `json:"reserve_free_gb"`
|
|
AutoCopy bool `json:"auto_copy"`
|
|
|
|
// nil = не транскодировать видео
|
|
Transcode *TranscodeProfile `json:"transcode,omitempty"`
|
|
}
|
|
|
|
type TranscodeProfile struct {
|
|
VideoCodec string `json:"video_codec"` // "h264" | "h265" | "mpeg4"
|
|
MaxResolution string `json:"max_resolution"` // "480p" | "720p" | "1080p"
|
|
MaxVideoBitrate string `json:"max_video_bitrate"` // "1000k" | "2000k" | "" (без лимита)
|
|
MaxFPS int `json:"max_fps"` // 0=без лимита | 24 | 25 | 30
|
|
AudioCodec string `json:"audio_codec"` // "aac" | "mp3"
|
|
MaxAudioBitrate string `json:"max_audio_bitrate"` // "128k" | "192k" | ""
|
|
MaxAudioChannels int `json:"max_audio_channels"` // 2=стерео | 6=5.1
|
|
OutputFormat string `json:"output_format"` // "mp4" | "mkv" | "avi"
|
|
}
|
|
|
|
const profileFile = "profile.json"
|
|
|
|
func profilePath(mountPath string) string {
|
|
return filepath.Join(mountPath, MarkerDir, profileFile)
|
|
}
|
|
|
|
func LoadProfile(mountPath string) (*DiskProfile, error) {
|
|
data, err := os.ReadFile(profilePath(mountPath))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var p DiskProfile
|
|
if err := json.Unmarshal(data, &p); err != nil {
|
|
return nil, err
|
|
}
|
|
return &p, nil
|
|
}
|
|
|
|
func SaveProfile(mountPath string, p *DiskProfile) error {
|
|
data, err := json.MarshalIndent(p, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
path := profilePath(mountPath)
|
|
tmp := path + ".tmp"
|
|
if err := os.WriteFile(tmp, data, 0o644); err != nil {
|
|
return err
|
|
}
|
|
return os.Rename(tmp, path)
|
|
}
|
|
|
|
func DefaultProfile() *DiskProfile {
|
|
return &DiskProfile{
|
|
DestFolder: "media",
|
|
OverwriteMode: "skip",
|
|
FileSelectMode: "new",
|
|
ReserveFreeGB: 2.0,
|
|
AutoCopy: false,
|
|
}
|
|
}
|