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:
parent
08ba1e1c05
commit
83d10ea00e
12 changed files with 83 additions and 19 deletions
|
@ -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: "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_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
|
||||
## Share hardware devices with FFmpeg and TensorFlow (optional):
|
||||
# devices:
|
||||
|
|
|
@ -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.
|
||||
func (c *Config) FFmpegBitrateExceeded(mbit float64) bool {
|
||||
if mbit <= 0 {
|
||||
|
@ -67,14 +81,15 @@ func (c *Config) FFmpegMapAudio() string {
|
|||
}
|
||||
|
||||
// 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.
|
||||
opt := ffmpeg.Options{
|
||||
Bin: c.FFmpegBin(),
|
||||
Encoder: encoder,
|
||||
Bitrate: bitrate,
|
||||
MapVideo: c.FFmpegMapVideo(),
|
||||
MapAudio: c.FFmpegMapAudio(),
|
||||
Bin: c.FFmpegBin(),
|
||||
Encoder: encoder,
|
||||
Bitrate: bitrate,
|
||||
MapVideo: c.FFmpegMapVideo(),
|
||||
MapAudio: c.FFmpegMapAudio(),
|
||||
Resolution: resolution,
|
||||
}
|
||||
|
||||
// Check
|
||||
|
|
|
@ -43,6 +43,17 @@ func TestConfig_FFmpegBitrate(t *testing.T) {
|
|||
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) {
|
||||
c := NewConfig(CliTestContext())
|
||||
c.options.FFmpegBitrate = 0
|
||||
|
@ -77,7 +88,8 @@ func TestConfig_FFmpegMapAudio(t *testing.T) {
|
|||
func TestConfig_FFmpegOptions(t *testing.T) {
|
||||
c := NewConfig(CliTestContext())
|
||||
bitrate := "25M"
|
||||
opt, err := c.FFmpegOptions(ffmpeg.SoftwareEncoder, bitrate)
|
||||
resolution := "1080"
|
||||
opt, err := c.FFmpegOptions(ffmpeg.SoftwareEncoder, bitrate, resolution)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, c.FFmpegBin(), opt.Bin)
|
||||
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, c.FFmpegMapVideo(), opt.MapVideo)
|
||||
assert.Equal(t, c.FFmpegMapAudio(), opt.MapAudio)
|
||||
assert.Equal(t, resolution, opt.Resolution)
|
||||
}
|
||||
|
|
|
@ -580,6 +580,12 @@ var Flags = CliFlags{
|
|||
Value: 50,
|
||||
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{
|
||||
Name: "ffmpeg-map-video",
|
||||
Usage: "video `STREAMS` that should be transcoded",
|
||||
|
|
|
@ -129,6 +129,7 @@ type Options struct {
|
|||
FFmpegBin string `yaml:"FFmpegBin" json:"-" flag:"ffmpeg-bin"`
|
||||
FFmpegEncoder string `yaml:"FFmpegEncoder" json:"FFmpegEncoder" flag:"ffmpeg-encoder"`
|
||||
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"`
|
||||
FFmpegMapAudio string `yaml:"FFmpegMapAudio" json:"FFmpegMapAudio" flag:"ffmpeg-map-audio"`
|
||||
ExifToolBin string `yaml:"ExifToolBin" json:"-" flag:"exiftool-bin"`
|
||||
|
|
|
@ -184,6 +184,7 @@ func (c *Config) Report() (rows [][]string, cols []string) {
|
|||
{"ffmpeg-bin", c.FFmpegBin()},
|
||||
{"ffmpeg-encoder", c.FFmpegEncoder().String()},
|
||||
{"ffmpeg-bitrate", fmt.Sprintf("%d", c.FFmpegBitrate())},
|
||||
{"ffmpeg-resolution", fmt.Sprintf("%d", c.FFmpegResolution())},
|
||||
{"ffmpeg-map-video", c.FFmpegMapVideo()},
|
||||
{"ffmpeg-map-audio", c.FFmpegMapAudio()},
|
||||
{"exiftool-bin", c.ExifToolBin()},
|
||||
|
|
|
@ -2,9 +2,10 @@ package ffmpeg
|
|||
|
||||
// Options represents transcoding options.
|
||||
type Options struct {
|
||||
Bin string
|
||||
Encoder AvcEncoder
|
||||
Bitrate string
|
||||
MapVideo string
|
||||
MapAudio string
|
||||
Bin string
|
||||
Encoder AvcEncoder
|
||||
Bitrate string
|
||||
MapVideo string
|
||||
MapAudio string
|
||||
Resolution string
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ func AvcConvertCommand(fileName, avcName string, opt Options) (result *exec.Cmd,
|
|||
"-i", fileName,
|
||||
"-movflags", "faststart",
|
||||
"-pix_fmt", "yuv420p",
|
||||
// "-vf", "scale='-2:trunc("+opt.Resolution+"/2)*2'",
|
||||
"-vf", "scale=trunc(iw/2)*2:trunc(ih/2)*2",
|
||||
"-f", "mp4",
|
||||
"-y",
|
||||
|
@ -48,7 +49,7 @@ func AvcConvertCommand(fileName, avcName string, opt Options) (result *exec.Cmd,
|
|||
"-qsv_device", "/dev/dri/renderD128",
|
||||
"-i", fileName,
|
||||
"-c:a", "aac",
|
||||
"-vf", format,
|
||||
"-vf", "\"scale='-2:"+opt.Resolution+"',"+format+"\"",
|
||||
"-c:v", opt.Encoder.String(),
|
||||
"-map", opt.MapVideo,
|
||||
"-map", opt.MapAudio,
|
||||
|
@ -71,7 +72,7 @@ func AvcConvertCommand(fileName, avcName string, opt Options) (result *exec.Cmd,
|
|||
"-map", opt.MapVideo,
|
||||
"-map", opt.MapAudio,
|
||||
"-c:a", "aac",
|
||||
"-vf", format,
|
||||
"-vf", "\"scale='-2:"+opt.Resolution+"',"+format+"\"",
|
||||
"-profile", "high",
|
||||
"-level", "51",
|
||||
"-vsync", "vfr",
|
||||
|
@ -89,7 +90,7 @@ func AvcConvertCommand(fileName, avcName string, opt Options) (result *exec.Cmd,
|
|||
"-hwaccel", "vaapi",
|
||||
"-i", fileName,
|
||||
"-c:a", "aac",
|
||||
"-vf", format,
|
||||
"-vf", "\"scale='-2:"+opt.Resolution+"',"+format+"\"",
|
||||
"-c:v", opt.Encoder.String(),
|
||||
"-map", opt.MapVideo,
|
||||
"-map", opt.MapAudio,
|
||||
|
@ -103,6 +104,7 @@ func AvcConvertCommand(fileName, avcName string, opt Options) (result *exec.Cmd,
|
|||
|
||||
case NvidiaEncoder:
|
||||
// ffmpeg -hide_banner -h encoder=h264_nvenc
|
||||
format := "format=yuv420p"
|
||||
result = exec.Command(
|
||||
opt.Bin,
|
||||
"-hwaccel", "auto",
|
||||
|
@ -115,7 +117,7 @@ func AvcConvertCommand(fileName, avcName string, opt Options) (result *exec.Cmd,
|
|||
"-preset", "15",
|
||||
"-pixel_format", "yuv420p",
|
||||
"-gpu", "any",
|
||||
"-vf", "format=yuv420p",
|
||||
"-vf", "\"scale='-2:"+opt.Resolution+"',"+format+"\"",
|
||||
"-rc:v", "constqp",
|
||||
"-cq", "0",
|
||||
"-tune", "2",
|
||||
|
@ -139,7 +141,7 @@ func AvcConvertCommand(fileName, avcName string, opt Options) (result *exec.Cmd,
|
|||
"-map", opt.MapVideo,
|
||||
"-map", opt.MapAudio,
|
||||
"-c:a", "aac",
|
||||
"-vf", format,
|
||||
"-vf", "\"scale='-2:"+opt.Resolution+"',"+format+"\"",
|
||||
"-num_output_buffers", "72",
|
||||
"-num_capture_buffers", "64",
|
||||
"-max_muxing_queue_size", "1024",
|
||||
|
@ -161,7 +163,7 @@ func AvcConvertCommand(fileName, avcName string, opt Options) (result *exec.Cmd,
|
|||
"-map", opt.MapVideo,
|
||||
"-map", opt.MapAudio,
|
||||
"-c:a", "aac",
|
||||
"-vf", format,
|
||||
"-vf", "\"scale='-2:"+opt.Resolution+"',"+format+"\"",
|
||||
"-max_muxing_queue_size", "1024",
|
||||
"-crf", "23",
|
||||
"-vsync", "vfr",
|
||||
|
|
|
@ -150,7 +150,7 @@ func (c *Convert) AvcConvertCommand(f *MediaFile, avcName string, encoder ffmpeg
|
|||
// Transcode all other formats with FFmpeg.
|
||||
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)
|
||||
} else {
|
||||
return ffmpeg.AvcConvertCommand(fileName, avcName, opt)
|
||||
|
@ -178,3 +178,24 @@ func (c *Convert) AvcBitrate(f *MediaFile) string {
|
|||
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -87,6 +87,7 @@ services:
|
|||
## Hardware Video Transcoding:
|
||||
# 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_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):
|
||||
# PHOTOPRISM_UID: 1000
|
||||
# PHOTOPRISM_GID: 1000
|
||||
|
|
|
@ -78,6 +78,7 @@ services:
|
|||
## Hardware Video Transcoding:
|
||||
# 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_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):
|
||||
# PHOTOPRISM_UID: 1000
|
||||
# PHOTOPRISM_GID: 1000
|
||||
|
|
|
@ -83,6 +83,7 @@ services:
|
|||
## see https://docs.photoprism.app/getting-started/advanced/transcoding/#nvidia-container-toolkit
|
||||
PHOTOPRISM_FFMPEG_ENCODER: "nvidia"
|
||||
PHOTOPRISM_FFMPEG_BITRATE: "50"
|
||||
PHOTOPRISM_FFMPEG_RESOLUTION: "2160"
|
||||
NVIDIA_VISIBLE_DEVICES: "all"
|
||||
NVIDIA_DRIVER_CAPABILITIES: "compute,video,utility"
|
||||
## Run as a non-root user after initialization (supported: 0, 33, 50-99, 500-600, and 900-1200):
|
||||
|
|
Loading…
Reference in a new issue