Config: Add video transcoding resolution option (#3498)

* Adds resolution limit

* Fixes 2 video filters and makes the env variable work

* Adds FFMPEG_RESOLUTION to all the docker-compose files
This commit is contained in:
Lukas 2023-06-28 15:22:52 +02:00 committed by GitHub
parent 08ba1e1c05
commit 83d10ea00e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 83 additions and 19 deletions

View file

@ -109,6 +109,7 @@ services:
# PHOTOPRISM_FFMPEG_ENCODER: "nvidia" # FFmpeg encoder ("software", "intel", "nvidia", "apple", "raspberry", "vaapi") Intel: "intel" for Broadwell or later and "vaapi" for Haswell or earlier # PHOTOPRISM_FFMPEG_ENCODER: "nvidia" # FFmpeg encoder ("software", "intel", "nvidia", "apple", "raspberry", "vaapi") Intel: "intel" for Broadwell or later and "vaapi" for Haswell or earlier
# PHOTOPRISM_FFMPEG_ENCODER: "intel" # FFmpeg encoder ("software", "intel", "nvidia", "apple", "raspberry", "vaapi") Intel: "intel" for Broadwell or later and "vaapi" for Haswell or earlier` # PHOTOPRISM_FFMPEG_ENCODER: "intel" # FFmpeg encoder ("software", "intel", "nvidia", "apple", "raspberry", "vaapi") Intel: "intel" for Broadwell or later and "vaapi" for Haswell or earlier`
# PHOTOPRISM_FFMPEG_BITRATE: "32" # FFmpeg encoding bitrate limit in Mbit/s (default: 50) # PHOTOPRISM_FFMPEG_BITRATE: "32" # FFmpeg encoding bitrate limit in Mbit/s (default: 50)
# PHOTOPRISM_FFMPEG_RESOLUTION: "1080" # FFmpeg encoding resolution limit in pixel height (default: 2160)
# LIBVA_DRIVER_NAME: "i965" # For Intel architectures Haswell and older which do not support QSV yet but use VAAPI instead # LIBVA_DRIVER_NAME: "i965" # For Intel architectures Haswell and older which do not support QSV yet but use VAAPI instead
## Share hardware devices with FFmpeg and TensorFlow (optional): ## Share hardware devices with FFmpeg and TensorFlow (optional):
# devices: # devices:

View file

@ -37,6 +37,20 @@ func (c *Config) FFmpegBitrate() int {
} }
} }
// FFmpegResolution returns the ffmpeg resolution limit in pixel height. Goes from 144p to 8k.
func (c *Config) FFmpegResolution() int {
switch {
case c.options.FFmpegResolution <= 0:
return 4320
case c.options.FFmpegResolution <= 144:
return 144
case c.options.FFmpegBitrate >= 4320:
return 4320
default:
return c.options.FFmpegBitrate
}
}
// FFmpegBitrateExceeded tests if the ffmpeg bitrate limit is exceeded. // FFmpegBitrateExceeded tests if the ffmpeg bitrate limit is exceeded.
func (c *Config) FFmpegBitrateExceeded(mbit float64) bool { func (c *Config) FFmpegBitrateExceeded(mbit float64) bool {
if mbit <= 0 { if mbit <= 0 {
@ -67,14 +81,15 @@ func (c *Config) FFmpegMapAudio() string {
} }
// FFmpegOptions returns the FFmpeg transcoding options. // FFmpegOptions returns the FFmpeg transcoding options.
func (c *Config) FFmpegOptions(encoder ffmpeg.AvcEncoder, bitrate string) (ffmpeg.Options, error) { func (c *Config) FFmpegOptions(encoder ffmpeg.AvcEncoder, bitrate string, resolution string) (ffmpeg.Options, error) {
// Transcode all other formats with FFmpeg. // Transcode all other formats with FFmpeg.
opt := ffmpeg.Options{ opt := ffmpeg.Options{
Bin: c.FFmpegBin(), Bin: c.FFmpegBin(),
Encoder: encoder, Encoder: encoder,
Bitrate: bitrate, Bitrate: bitrate,
MapVideo: c.FFmpegMapVideo(), MapVideo: c.FFmpegMapVideo(),
MapAudio: c.FFmpegMapAudio(), MapAudio: c.FFmpegMapAudio(),
Resolution: resolution,
} }
// Check // Check

View file

@ -43,6 +43,17 @@ func TestConfig_FFmpegBitrate(t *testing.T) {
assert.Equal(t, 800, c.FFmpegBitrate()) assert.Equal(t, 800, c.FFmpegBitrate())
} }
func TestConfig_FFmpegResolution(t *testing.T) {
c := NewConfig(CliTestContext())
assert.Equal(t, 144, c.FFmpegResolution())
c.options.FFmpegResolution = 1920
assert.Equal(t, 1920, c.FFmpegResolution())
c.options.FFmpegResolution = 8640
assert.Equal(t, 4320, c.FFmpegResolution())
}
func TestConfig_FFmpegBitrateExceeded(t *testing.T) { func TestConfig_FFmpegBitrateExceeded(t *testing.T) {
c := NewConfig(CliTestContext()) c := NewConfig(CliTestContext())
c.options.FFmpegBitrate = 0 c.options.FFmpegBitrate = 0
@ -77,7 +88,8 @@ func TestConfig_FFmpegMapAudio(t *testing.T) {
func TestConfig_FFmpegOptions(t *testing.T) { func TestConfig_FFmpegOptions(t *testing.T) {
c := NewConfig(CliTestContext()) c := NewConfig(CliTestContext())
bitrate := "25M" bitrate := "25M"
opt, err := c.FFmpegOptions(ffmpeg.SoftwareEncoder, bitrate) resolution := "1080"
opt, err := c.FFmpegOptions(ffmpeg.SoftwareEncoder, bitrate, resolution)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, c.FFmpegBin(), opt.Bin) assert.Equal(t, c.FFmpegBin(), opt.Bin)
assert.Equal(t, ffmpeg.SoftwareEncoder, opt.Encoder) assert.Equal(t, ffmpeg.SoftwareEncoder, opt.Encoder)
@ -86,4 +98,5 @@ func TestConfig_FFmpegOptions(t *testing.T) {
assert.Equal(t, ffmpeg.MapAudioDefault, opt.MapAudio) assert.Equal(t, ffmpeg.MapAudioDefault, opt.MapAudio)
assert.Equal(t, c.FFmpegMapVideo(), opt.MapVideo) assert.Equal(t, c.FFmpegMapVideo(), opt.MapVideo)
assert.Equal(t, c.FFmpegMapAudio(), opt.MapAudio) assert.Equal(t, c.FFmpegMapAudio(), opt.MapAudio)
assert.Equal(t, resolution, opt.Resolution)
} }

View file

@ -580,6 +580,12 @@ var Flags = CliFlags{
Value: 50, Value: 50,
EnvVar: EnvVar("FFMPEG_BITRATE"), EnvVar: EnvVar("FFMPEG_BITRATE"),
}}, { }}, {
Flag: cli.IntFlag{
Name: "ffmpeg-resolution",
Usage: "maximum FFmpeg encoding `RESOLUTION` (height)",
Value: 2160,
EnvVar: EnvVar("FFMPEG_RESOLUTION"),
}}, {
Flag: cli.StringFlag{ Flag: cli.StringFlag{
Name: "ffmpeg-map-video", Name: "ffmpeg-map-video",
Usage: "video `STREAMS` that should be transcoded", Usage: "video `STREAMS` that should be transcoded",

View file

@ -129,6 +129,7 @@ type Options struct {
FFmpegBin string `yaml:"FFmpegBin" json:"-" flag:"ffmpeg-bin"` FFmpegBin string `yaml:"FFmpegBin" json:"-" flag:"ffmpeg-bin"`
FFmpegEncoder string `yaml:"FFmpegEncoder" json:"FFmpegEncoder" flag:"ffmpeg-encoder"` FFmpegEncoder string `yaml:"FFmpegEncoder" json:"FFmpegEncoder" flag:"ffmpeg-encoder"`
FFmpegBitrate int `yaml:"FFmpegBitrate" json:"FFmpegBitrate" flag:"ffmpeg-bitrate"` FFmpegBitrate int `yaml:"FFmpegBitrate" json:"FFmpegBitrate" flag:"ffmpeg-bitrate"`
FFmpegResolution int `yaml:"FFmpegResolution" json:"FFmpegResolution" flag:"ffmpeg-resolution"`
FFmpegMapVideo string `yaml:"FFmpegMapVideo" json:"FFmpegMapVideo" flag:"ffmpeg-map-video"` FFmpegMapVideo string `yaml:"FFmpegMapVideo" json:"FFmpegMapVideo" flag:"ffmpeg-map-video"`
FFmpegMapAudio string `yaml:"FFmpegMapAudio" json:"FFmpegMapAudio" flag:"ffmpeg-map-audio"` FFmpegMapAudio string `yaml:"FFmpegMapAudio" json:"FFmpegMapAudio" flag:"ffmpeg-map-audio"`
ExifToolBin string `yaml:"ExifToolBin" json:"-" flag:"exiftool-bin"` ExifToolBin string `yaml:"ExifToolBin" json:"-" flag:"exiftool-bin"`

View file

@ -184,6 +184,7 @@ func (c *Config) Report() (rows [][]string, cols []string) {
{"ffmpeg-bin", c.FFmpegBin()}, {"ffmpeg-bin", c.FFmpegBin()},
{"ffmpeg-encoder", c.FFmpegEncoder().String()}, {"ffmpeg-encoder", c.FFmpegEncoder().String()},
{"ffmpeg-bitrate", fmt.Sprintf("%d", c.FFmpegBitrate())}, {"ffmpeg-bitrate", fmt.Sprintf("%d", c.FFmpegBitrate())},
{"ffmpeg-resolution", fmt.Sprintf("%d", c.FFmpegResolution())},
{"ffmpeg-map-video", c.FFmpegMapVideo()}, {"ffmpeg-map-video", c.FFmpegMapVideo()},
{"ffmpeg-map-audio", c.FFmpegMapAudio()}, {"ffmpeg-map-audio", c.FFmpegMapAudio()},
{"exiftool-bin", c.ExifToolBin()}, {"exiftool-bin", c.ExifToolBin()},

View file

@ -2,9 +2,10 @@ package ffmpeg
// Options represents transcoding options. // Options represents transcoding options.
type Options struct { type Options struct {
Bin string Bin string
Encoder AvcEncoder Encoder AvcEncoder
Bitrate string Bitrate string
MapVideo string MapVideo string
MapAudio string MapAudio string
Resolution string
} }

View file

@ -25,6 +25,7 @@ func AvcConvertCommand(fileName, avcName string, opt Options) (result *exec.Cmd,
"-i", fileName, "-i", fileName,
"-movflags", "faststart", "-movflags", "faststart",
"-pix_fmt", "yuv420p", "-pix_fmt", "yuv420p",
// "-vf", "scale='-2:trunc("+opt.Resolution+"/2)*2'",
"-vf", "scale=trunc(iw/2)*2:trunc(ih/2)*2", "-vf", "scale=trunc(iw/2)*2:trunc(ih/2)*2",
"-f", "mp4", "-f", "mp4",
"-y", "-y",
@ -48,7 +49,7 @@ func AvcConvertCommand(fileName, avcName string, opt Options) (result *exec.Cmd,
"-qsv_device", "/dev/dri/renderD128", "-qsv_device", "/dev/dri/renderD128",
"-i", fileName, "-i", fileName,
"-c:a", "aac", "-c:a", "aac",
"-vf", format, "-vf", "\"scale='-2:"+opt.Resolution+"',"+format+"\"",
"-c:v", opt.Encoder.String(), "-c:v", opt.Encoder.String(),
"-map", opt.MapVideo, "-map", opt.MapVideo,
"-map", opt.MapAudio, "-map", opt.MapAudio,
@ -71,7 +72,7 @@ func AvcConvertCommand(fileName, avcName string, opt Options) (result *exec.Cmd,
"-map", opt.MapVideo, "-map", opt.MapVideo,
"-map", opt.MapAudio, "-map", opt.MapAudio,
"-c:a", "aac", "-c:a", "aac",
"-vf", format, "-vf", "\"scale='-2:"+opt.Resolution+"',"+format+"\"",
"-profile", "high", "-profile", "high",
"-level", "51", "-level", "51",
"-vsync", "vfr", "-vsync", "vfr",
@ -89,7 +90,7 @@ func AvcConvertCommand(fileName, avcName string, opt Options) (result *exec.Cmd,
"-hwaccel", "vaapi", "-hwaccel", "vaapi",
"-i", fileName, "-i", fileName,
"-c:a", "aac", "-c:a", "aac",
"-vf", format, "-vf", "\"scale='-2:"+opt.Resolution+"',"+format+"\"",
"-c:v", opt.Encoder.String(), "-c:v", opt.Encoder.String(),
"-map", opt.MapVideo, "-map", opt.MapVideo,
"-map", opt.MapAudio, "-map", opt.MapAudio,
@ -103,6 +104,7 @@ func AvcConvertCommand(fileName, avcName string, opt Options) (result *exec.Cmd,
case NvidiaEncoder: case NvidiaEncoder:
// ffmpeg -hide_banner -h encoder=h264_nvenc // ffmpeg -hide_banner -h encoder=h264_nvenc
format := "format=yuv420p"
result = exec.Command( result = exec.Command(
opt.Bin, opt.Bin,
"-hwaccel", "auto", "-hwaccel", "auto",
@ -115,7 +117,7 @@ func AvcConvertCommand(fileName, avcName string, opt Options) (result *exec.Cmd,
"-preset", "15", "-preset", "15",
"-pixel_format", "yuv420p", "-pixel_format", "yuv420p",
"-gpu", "any", "-gpu", "any",
"-vf", "format=yuv420p", "-vf", "\"scale='-2:"+opt.Resolution+"',"+format+"\"",
"-rc:v", "constqp", "-rc:v", "constqp",
"-cq", "0", "-cq", "0",
"-tune", "2", "-tune", "2",
@ -139,7 +141,7 @@ func AvcConvertCommand(fileName, avcName string, opt Options) (result *exec.Cmd,
"-map", opt.MapVideo, "-map", opt.MapVideo,
"-map", opt.MapAudio, "-map", opt.MapAudio,
"-c:a", "aac", "-c:a", "aac",
"-vf", format, "-vf", "\"scale='-2:"+opt.Resolution+"',"+format+"\"",
"-num_output_buffers", "72", "-num_output_buffers", "72",
"-num_capture_buffers", "64", "-num_capture_buffers", "64",
"-max_muxing_queue_size", "1024", "-max_muxing_queue_size", "1024",
@ -161,7 +163,7 @@ func AvcConvertCommand(fileName, avcName string, opt Options) (result *exec.Cmd,
"-map", opt.MapVideo, "-map", opt.MapVideo,
"-map", opt.MapAudio, "-map", opt.MapAudio,
"-c:a", "aac", "-c:a", "aac",
"-vf", format, "-vf", "\"scale='-2:"+opt.Resolution+"',"+format+"\"",
"-max_muxing_queue_size", "1024", "-max_muxing_queue_size", "1024",
"-crf", "23", "-crf", "23",
"-vsync", "vfr", "-vsync", "vfr",

View file

@ -150,7 +150,7 @@ func (c *Convert) AvcConvertCommand(f *MediaFile, avcName string, encoder ffmpeg
// Transcode all other formats with FFmpeg. // Transcode all other formats with FFmpeg.
var opt ffmpeg.Options var opt ffmpeg.Options
if opt, err = c.conf.FFmpegOptions(encoder, c.AvcBitrate(f)); err != nil { if opt, err = c.conf.FFmpegOptions(encoder, c.AvcBitrate(f), c.AvcResolution(f)); err != nil {
return nil, false, fmt.Errorf("convert: failed to transcode %s (%s)", clean.Log(f.BaseName()), err) return nil, false, fmt.Errorf("convert: failed to transcode %s (%s)", clean.Log(f.BaseName()), err)
} else { } else {
return ffmpeg.AvcConvertCommand(fileName, avcName, opt) return ffmpeg.AvcConvertCommand(fileName, avcName, opt)
@ -178,3 +178,24 @@ func (c *Convert) AvcBitrate(f *MediaFile) string {
return fmt.Sprintf("%dM", bitrate) return fmt.Sprintf("%dM", bitrate)
} }
// AvcResolution returns the resolution to use for transcoding.
func (c *Convert) AvcResolution(f *MediaFile) string {
const defaultResolution = "2160"
if f == nil {
return defaultResolution
}
limit := c.conf.FFmpegResolution()
resolution := f.height
if resolution <= 144 {
return defaultResolution
} else if resolution > limit {
resolution = limit
}
return fmt.Sprintf("%d", resolution)
}

View file

@ -87,6 +87,7 @@ services:
## Hardware Video Transcoding: ## Hardware Video Transcoding:
# PHOTOPRISM_FFMPEG_ENCODER: "raspberry" # FFmpeg encoder ("software", "intel", "nvidia", "apple", "raspberry") # PHOTOPRISM_FFMPEG_ENCODER: "raspberry" # FFmpeg encoder ("software", "intel", "nvidia", "apple", "raspberry")
# PHOTOPRISM_FFMPEG_BITRATE: "32" # FFmpeg encoding bitrate limit in Mbit/s (default: 50) # PHOTOPRISM_FFMPEG_BITRATE: "32" # FFmpeg encoding bitrate limit in Mbit/s (default: 50)
# PHOTOPRISM_FFMPEG_RESOLUTION: "1080" # FFmpeg encoding resolution limit in pixel height (default: 2160)
## Run as a non-root user after initialization (supported: 0, 33, 50-99, 500-600, and 900-1200): ## Run as a non-root user after initialization (supported: 0, 33, 50-99, 500-600, and 900-1200):
# PHOTOPRISM_UID: 1000 # PHOTOPRISM_UID: 1000
# PHOTOPRISM_GID: 1000 # PHOTOPRISM_GID: 1000

View file

@ -78,6 +78,7 @@ services:
## Hardware Video Transcoding: ## Hardware Video Transcoding:
# PHOTOPRISM_FFMPEG_ENCODER: "software" # FFmpeg encoder ("software", "intel", "nvidia", "apple", "raspberry") # PHOTOPRISM_FFMPEG_ENCODER: "software" # FFmpeg encoder ("software", "intel", "nvidia", "apple", "raspberry")
# PHOTOPRISM_FFMPEG_BITRATE: "32" # FFmpeg encoding bitrate limit in Mbit/s (default: 50) # PHOTOPRISM_FFMPEG_BITRATE: "32" # FFmpeg encoding bitrate limit in Mbit/s (default: 50)
# PHOTOPRISM_FFMPEG_RESOLUTION: "1080" # FFmpeg encoding resolution limit in pixel height (default: 2160)
## Run as a non-root user after initialization (supported: 0, 33, 50-99, 500-600, and 900-1200): ## Run as a non-root user after initialization (supported: 0, 33, 50-99, 500-600, and 900-1200):
# PHOTOPRISM_UID: 1000 # PHOTOPRISM_UID: 1000
# PHOTOPRISM_GID: 1000 # PHOTOPRISM_GID: 1000

View file

@ -83,6 +83,7 @@ services:
## see https://docs.photoprism.app/getting-started/advanced/transcoding/#nvidia-container-toolkit ## see https://docs.photoprism.app/getting-started/advanced/transcoding/#nvidia-container-toolkit
PHOTOPRISM_FFMPEG_ENCODER: "nvidia" PHOTOPRISM_FFMPEG_ENCODER: "nvidia"
PHOTOPRISM_FFMPEG_BITRATE: "50" PHOTOPRISM_FFMPEG_BITRATE: "50"
PHOTOPRISM_FFMPEG_RESOLUTION: "2160"
NVIDIA_VISIBLE_DEVICES: "all" NVIDIA_VISIBLE_DEVICES: "all"
NVIDIA_DRIVER_CAPABILITIES: "compute,video,utility" NVIDIA_DRIVER_CAPABILITIES: "compute,video,utility"
## Run as a non-root user after initialization (supported: 0, 33, 50-99, 500-600, and 900-1200): ## Run as a non-root user after initialization (supported: 0, 33, 50-99, 500-600, and 900-1200):