FFmpeg: Allow selection of specific video and audio streams #3284
Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
parent
9ab833c2ec
commit
157c6c723a
|
@ -1,6 +1,10 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import "github.com/photoprism/photoprism/internal/ffmpeg"
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/photoprism/photoprism/internal/ffmpeg"
|
||||||
|
)
|
||||||
|
|
||||||
// FFmpegBin returns the ffmpeg executable file name.
|
// FFmpegBin returns the ffmpeg executable file name.
|
||||||
func (c *Config) FFmpegBin() string {
|
func (c *Config) FFmpegBin() string {
|
||||||
|
@ -46,3 +50,46 @@ func (c *Config) FFmpegBitrateExceeded(mbit float64) bool {
|
||||||
return mbit > float64(max)
|
return mbit > float64(max)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FFmpegMapVideo returns the video streams to be transcoded as string.
|
||||||
|
func (c *Config) FFmpegMapVideo() string {
|
||||||
|
if c.options.FFmpegMapVideo == "" {
|
||||||
|
return ffmpeg.MapVideoDefault
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.options.FFmpegMapVideo
|
||||||
|
}
|
||||||
|
|
||||||
|
// FFmpegMapAudio returns the audio streams to be transcoded as string.
|
||||||
|
func (c *Config) FFmpegMapAudio() string {
|
||||||
|
if c.options.FFmpegMapAudio == "" {
|
||||||
|
return ffmpeg.MapAudioDefault
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.options.FFmpegMapAudio
|
||||||
|
}
|
||||||
|
|
||||||
|
// FFmpegOptions returns the FFmpeg transcoding options.
|
||||||
|
func (c *Config) FFmpegOptions(encoder ffmpeg.AvcEncoder, bitrate 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(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check
|
||||||
|
if opt.Bin == "" {
|
||||||
|
return opt, fmt.Errorf("ffmpeg is not installed")
|
||||||
|
} else if c.DisableFFmpeg() {
|
||||||
|
return opt, fmt.Errorf("ffmpeg is disabled")
|
||||||
|
} else if bitrate == "" {
|
||||||
|
return opt, fmt.Errorf("bitrate must not be empty")
|
||||||
|
} else if encoder.String() == "" {
|
||||||
|
return opt, fmt.Errorf("encoder must not be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
return opt, nil
|
||||||
|
}
|
||||||
|
|
|
@ -63,3 +63,27 @@ func TestConfig_FFmpegBitrateExceeded(t *testing.T) {
|
||||||
assert.False(t, c.FFmpegBitrateExceeded(1.05))
|
assert.False(t, c.FFmpegBitrateExceeded(1.05))
|
||||||
assert.False(t, c.FFmpegBitrateExceeded(2.05))
|
assert.False(t, c.FFmpegBitrateExceeded(2.05))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConfig_FFmpegMapVideo(t *testing.T) {
|
||||||
|
c := NewConfig(CliTestContext())
|
||||||
|
assert.Equal(t, ffmpeg.MapVideoDefault, c.FFmpegMapVideo())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfig_FFmpegMapAudio(t *testing.T) {
|
||||||
|
c := NewConfig(CliTestContext())
|
||||||
|
assert.Equal(t, ffmpeg.MapAudioDefault, c.FFmpegMapAudio())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfig_FFmpegOptions(t *testing.T) {
|
||||||
|
c := NewConfig(CliTestContext())
|
||||||
|
bitrate := "25M"
|
||||||
|
opt, err := c.FFmpegOptions(ffmpeg.SoftwareEncoder, bitrate)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, c.FFmpegBin(), opt.Bin)
|
||||||
|
assert.Equal(t, ffmpeg.SoftwareEncoder, opt.Encoder)
|
||||||
|
assert.Equal(t, bitrate, opt.Bitrate)
|
||||||
|
assert.Equal(t, ffmpeg.MapVideoDefault, opt.MapVideo)
|
||||||
|
assert.Equal(t, ffmpeg.MapAudioDefault, opt.MapAudio)
|
||||||
|
assert.Equal(t, c.FFmpegMapVideo(), opt.MapVideo)
|
||||||
|
assert.Equal(t, c.FFmpegMapAudio(), opt.MapAudio)
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@ package config
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/photoprism/photoprism/internal/ffmpeg"
|
||||||
|
|
||||||
"github.com/klauspost/cpuid/v2"
|
"github.com/klauspost/cpuid/v2"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
|
|
||||||
|
@ -566,6 +568,18 @@ var Flags = CliFlags{
|
||||||
Value: 50,
|
Value: 50,
|
||||||
EnvVar: "PHOTOPRISM_FFMPEG_BITRATE",
|
EnvVar: "PHOTOPRISM_FFMPEG_BITRATE",
|
||||||
}}, {
|
}}, {
|
||||||
|
Flag: cli.StringFlag{
|
||||||
|
Name: "ffmpeg-map-video",
|
||||||
|
Usage: "video `STREAMS` that should be transcoded",
|
||||||
|
Value: ffmpeg.MapVideoDefault,
|
||||||
|
EnvVar: "PHOTOPRISM_FFMPEG_MAP_VIDEO",
|
||||||
|
}}, {
|
||||||
|
Flag: cli.StringFlag{
|
||||||
|
Name: "ffmpeg-map-audio",
|
||||||
|
Usage: "audio `STREAMS` that should be transcoded",
|
||||||
|
Value: ffmpeg.MapAudioDefault,
|
||||||
|
EnvVar: "PHOTOPRISM_FFMPEG_MAP_AUDIO",
|
||||||
|
}}, {
|
||||||
Flag: cli.StringFlag{
|
Flag: cli.StringFlag{
|
||||||
Name: "exiftool-bin",
|
Name: "exiftool-bin",
|
||||||
Usage: "ExifTool `COMMAND` for extracting metadata",
|
Usage: "ExifTool `COMMAND` for extracting metadata",
|
||||||
|
|
|
@ -125,6 +125,8 @@ 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"`
|
||||||
|
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"`
|
ExifToolBin string `yaml:"ExifToolBin" json:"-" flag:"exiftool-bin"`
|
||||||
DarktableBin string `yaml:"DarktableBin" json:"-" flag:"darktable-bin"`
|
DarktableBin string `yaml:"DarktableBin" json:"-" flag:"darktable-bin"`
|
||||||
DarktableCachePath string `yaml:"DarktableCachePath" json:"-" flag:"darktable-cache-path"`
|
DarktableCachePath string `yaml:"DarktableCachePath" json:"-" flag:"darktable-cache-path"`
|
||||||
|
|
|
@ -176,6 +176,8 @@ 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-map-video", c.FFmpegMapVideo()},
|
||||||
|
{"ffmpeg-map-audio", c.FFmpegMapAudio()},
|
||||||
{"exiftool-bin", c.ExifToolBin()},
|
{"exiftool-bin", c.ExifToolBin()},
|
||||||
{"darktable-bin", c.DarktableBin()},
|
{"darktable-bin", c.DarktableBin()},
|
||||||
{"darktable-cache-path", c.DarktableCachePath()},
|
{"darktable-cache-path", c.DarktableCachePath()},
|
||||||
|
|
10
internal/ffmpeg/config.go
Normal file
10
internal/ffmpeg/config.go
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
package ffmpeg
|
||||||
|
|
||||||
|
// Options represents transcoding options.
|
||||||
|
type Options struct {
|
||||||
|
Bin string
|
||||||
|
Encoder AvcEncoder
|
||||||
|
Bitrate string
|
||||||
|
MapVideo string
|
||||||
|
MapAudio string
|
||||||
|
}
|
|
@ -8,7 +8,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// AvcConvertCommand returns the command for converting video files to MPEG-4 AVC.
|
// AvcConvertCommand returns the command for converting video files to MPEG-4 AVC.
|
||||||
func AvcConvertCommand(fileName, avcName, ffmpegBin, bitrate string, encoder AvcEncoder) (result *exec.Cmd, useMutex bool, err error) {
|
func AvcConvertCommand(fileName, avcName string, opt Options) (result *exec.Cmd, useMutex bool, err error) {
|
||||||
if fileName == "" {
|
if fileName == "" {
|
||||||
return nil, false, fmt.Errorf("empty input filename")
|
return nil, false, fmt.Errorf("empty input filename")
|
||||||
} else if avcName == "" {
|
} else if avcName == "" {
|
||||||
|
@ -21,7 +21,7 @@ func AvcConvertCommand(fileName, avcName, ffmpegBin, bitrate string, encoder Avc
|
||||||
// Don't use hardware transcoding for animated images.
|
// Don't use hardware transcoding for animated images.
|
||||||
if fs.TypeAnimated[fs.FileType(fileName)] != "" {
|
if fs.TypeAnimated[fs.FileType(fileName)] != "" {
|
||||||
result = exec.Command(
|
result = exec.Command(
|
||||||
ffmpegBin,
|
opt.Bin,
|
||||||
"-i", fileName,
|
"-i", fileName,
|
||||||
"-movflags", "faststart",
|
"-movflags", "faststart",
|
||||||
"-pix_fmt", "yuv420p",
|
"-pix_fmt", "yuv420p",
|
||||||
|
@ -35,27 +35,27 @@ func AvcConvertCommand(fileName, avcName, ffmpegBin, bitrate string, encoder Avc
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display encoder info.
|
// Display encoder info.
|
||||||
if encoder != SoftwareEncoder {
|
if opt.Encoder != SoftwareEncoder {
|
||||||
log.Infof("convert: ffmpeg encoder %s selected", string(encoder))
|
log.Infof("convert: ffmpeg encoder %s selected", opt.Encoder.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
switch encoder {
|
switch opt.Encoder {
|
||||||
case IntelEncoder:
|
case IntelEncoder:
|
||||||
// ffmpeg -hide_banner -h encoder=h264_qsv
|
// ffmpeg -hide_banner -h encoder=h264_qsv
|
||||||
format := "format=rgb32"
|
format := "format=rgb32"
|
||||||
result = exec.Command(
|
result = exec.Command(
|
||||||
ffmpegBin,
|
opt.Bin,
|
||||||
"-qsv_device", "/dev/dri/renderD128",
|
"-qsv_device", "/dev/dri/renderD128",
|
||||||
"-i", fileName,
|
"-i", fileName,
|
||||||
"-c:a", "aac",
|
"-c:a", "aac",
|
||||||
"-vf", format,
|
"-vf", format,
|
||||||
"-c:v", string(encoder),
|
"-c:v", opt.Encoder.String(),
|
||||||
"-map", "0:v:0",
|
"-map", opt.MapVideo,
|
||||||
"-map", "0:a:0?",
|
"-map", opt.MapAudio,
|
||||||
"-vsync", "vfr",
|
"-vsync", "vfr",
|
||||||
"-r", "30",
|
"-r", "30",
|
||||||
"-b:v", bitrate,
|
"-b:v", opt.Bitrate,
|
||||||
"-bitrate", bitrate,
|
"-bitrate", opt.Bitrate,
|
||||||
"-f", "mp4",
|
"-f", "mp4",
|
||||||
"-y",
|
"-y",
|
||||||
avcName,
|
avcName,
|
||||||
|
@ -65,18 +65,18 @@ func AvcConvertCommand(fileName, avcName, ffmpegBin, bitrate string, encoder Avc
|
||||||
// ffmpeg -hide_banner -h encoder=h264_videotoolbox
|
// ffmpeg -hide_banner -h encoder=h264_videotoolbox
|
||||||
format := "format=yuv420p"
|
format := "format=yuv420p"
|
||||||
result = exec.Command(
|
result = exec.Command(
|
||||||
ffmpegBin,
|
opt.Bin,
|
||||||
"-i", fileName,
|
"-i", fileName,
|
||||||
"-c:v", string(encoder),
|
"-c:v", opt.Encoder.String(),
|
||||||
"-map", "0:v:0",
|
"-map", opt.MapVideo,
|
||||||
"-map", "0:a:0?",
|
"-map", opt.MapAudio,
|
||||||
"-c:a", "aac",
|
"-c:a", "aac",
|
||||||
"-vf", format,
|
"-vf", format,
|
||||||
"-profile", "high",
|
"-profile", "high",
|
||||||
"-level", "51",
|
"-level", "51",
|
||||||
"-vsync", "vfr",
|
"-vsync", "vfr",
|
||||||
"-r", "30",
|
"-r", "30",
|
||||||
"-b:v", bitrate,
|
"-b:v", opt.Bitrate,
|
||||||
"-f", "mp4",
|
"-f", "mp4",
|
||||||
"-y",
|
"-y",
|
||||||
avcName,
|
avcName,
|
||||||
|
@ -85,17 +85,17 @@ func AvcConvertCommand(fileName, avcName, ffmpegBin, bitrate string, encoder Avc
|
||||||
case VAAPIEncoder:
|
case VAAPIEncoder:
|
||||||
format := "format=nv12,hwupload"
|
format := "format=nv12,hwupload"
|
||||||
result = exec.Command(
|
result = exec.Command(
|
||||||
ffmpegBin,
|
opt.Bin,
|
||||||
"-hwaccel", "vaapi",
|
"-hwaccel", "vaapi",
|
||||||
"-i", fileName,
|
"-i", fileName,
|
||||||
"-c:a", "aac",
|
"-c:a", "aac",
|
||||||
"-vf", format,
|
"-vf", format,
|
||||||
"-c:v", string(encoder),
|
"-c:v", opt.Encoder.String(),
|
||||||
"-map", "0:v:0",
|
"-map", opt.MapVideo,
|
||||||
"-map", "0:a:0?",
|
"-map", opt.MapAudio,
|
||||||
"-vsync", "vfr",
|
"-vsync", "vfr",
|
||||||
"-r", "30",
|
"-r", "30",
|
||||||
"-b:v", bitrate,
|
"-b:v", opt.Bitrate,
|
||||||
"-f", "mp4",
|
"-f", "mp4",
|
||||||
"-y",
|
"-y",
|
||||||
avcName,
|
avcName,
|
||||||
|
@ -104,13 +104,13 @@ func AvcConvertCommand(fileName, avcName, ffmpegBin, bitrate string, encoder Avc
|
||||||
case NvidiaEncoder:
|
case NvidiaEncoder:
|
||||||
// ffmpeg -hide_banner -h encoder=h264_nvenc
|
// ffmpeg -hide_banner -h encoder=h264_nvenc
|
||||||
result = exec.Command(
|
result = exec.Command(
|
||||||
ffmpegBin,
|
opt.Bin,
|
||||||
"-hwaccel", "auto",
|
"-hwaccel", "auto",
|
||||||
"-i", fileName,
|
"-i", fileName,
|
||||||
"-pix_fmt", "yuv420p",
|
"-pix_fmt", "yuv420p",
|
||||||
"-c:v", string(encoder),
|
"-c:v", opt.Encoder.String(),
|
||||||
"-map", "0:v:0",
|
"-map", opt.MapVideo,
|
||||||
"-map", "0:a:0?",
|
"-map", opt.MapAudio,
|
||||||
"-c:a", "aac",
|
"-c:a", "aac",
|
||||||
"-preset", "15",
|
"-preset", "15",
|
||||||
"-pixel_format", "yuv420p",
|
"-pixel_format", "yuv420p",
|
||||||
|
@ -120,7 +120,7 @@ func AvcConvertCommand(fileName, avcName, ffmpegBin, bitrate string, encoder Avc
|
||||||
"-cq", "0",
|
"-cq", "0",
|
||||||
"-tune", "2",
|
"-tune", "2",
|
||||||
"-r", "30",
|
"-r", "30",
|
||||||
"-b:v", bitrate,
|
"-b:v", opt.Bitrate,
|
||||||
"-profile:v", "1",
|
"-profile:v", "1",
|
||||||
"-level:v", "auto",
|
"-level:v", "auto",
|
||||||
"-coder:v", "1",
|
"-coder:v", "1",
|
||||||
|
@ -133,11 +133,11 @@ func AvcConvertCommand(fileName, avcName, ffmpegBin, bitrate string, encoder Avc
|
||||||
// ffmpeg -hide_banner -h encoder=h264_v4l2m2m
|
// ffmpeg -hide_banner -h encoder=h264_v4l2m2m
|
||||||
format := "format=yuv420p"
|
format := "format=yuv420p"
|
||||||
result = exec.Command(
|
result = exec.Command(
|
||||||
ffmpegBin,
|
opt.Bin,
|
||||||
"-i", fileName,
|
"-i", fileName,
|
||||||
"-c:v", string(encoder),
|
"-c:v", opt.Encoder.String(),
|
||||||
"-map", "0:v:0",
|
"-map", opt.MapVideo,
|
||||||
"-map", "0:a:0?",
|
"-map", opt.MapAudio,
|
||||||
"-c:a", "aac",
|
"-c:a", "aac",
|
||||||
"-vf", format,
|
"-vf", format,
|
||||||
"-num_output_buffers", "72",
|
"-num_output_buffers", "72",
|
||||||
|
@ -146,7 +146,7 @@ func AvcConvertCommand(fileName, avcName, ffmpegBin, bitrate string, encoder Avc
|
||||||
"-crf", "23",
|
"-crf", "23",
|
||||||
"-vsync", "vfr",
|
"-vsync", "vfr",
|
||||||
"-r", "30",
|
"-r", "30",
|
||||||
"-b:v", bitrate,
|
"-b:v", opt.Bitrate,
|
||||||
"-f", "mp4",
|
"-f", "mp4",
|
||||||
"-y",
|
"-y",
|
||||||
avcName,
|
avcName,
|
||||||
|
@ -155,18 +155,18 @@ func AvcConvertCommand(fileName, avcName, ffmpegBin, bitrate string, encoder Avc
|
||||||
default:
|
default:
|
||||||
format := "format=yuv420p"
|
format := "format=yuv420p"
|
||||||
result = exec.Command(
|
result = exec.Command(
|
||||||
ffmpegBin,
|
opt.Bin,
|
||||||
"-i", fileName,
|
"-i", fileName,
|
||||||
"-c:v", string(encoder),
|
"-c:v", opt.Encoder.String(),
|
||||||
"-map", "0:v:0",
|
"-map", opt.MapVideo,
|
||||||
"-map", "0:a:0?",
|
"-map", opt.MapAudio,
|
||||||
"-c:a", "aac",
|
"-c:a", "aac",
|
||||||
"-vf", format,
|
"-vf", format,
|
||||||
"-max_muxing_queue_size", "1024",
|
"-max_muxing_queue_size", "1024",
|
||||||
"-crf", "23",
|
"-crf", "23",
|
||||||
"-vsync", "vfr",
|
"-vsync", "vfr",
|
||||||
"-r", "30",
|
"-r", "30",
|
||||||
"-b:v", bitrate,
|
"-b:v", opt.Bitrate,
|
||||||
"-f", "mp4",
|
"-f", "mp4",
|
||||||
"-y",
|
"-y",
|
||||||
avcName,
|
avcName,
|
||||||
|
|
6
internal/ffmpeg/defaults.go
Normal file
6
internal/ffmpeg/defaults.go
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
package ffmpeg
|
||||||
|
|
||||||
|
const (
|
||||||
|
MapVideoDefault = "0:v:0"
|
||||||
|
MapAudioDefault = "0:a:0?"
|
||||||
|
)
|
|
@ -134,14 +134,10 @@ func (c *Convert) ToAvc(f *MediaFile, encoder ffmpeg.AvcEncoder, noMutex, force
|
||||||
func (c *Convert) AvcConvertCommand(f *MediaFile, avcName string, encoder ffmpeg.AvcEncoder) (result *exec.Cmd, useMutex bool, err error) {
|
func (c *Convert) AvcConvertCommand(f *MediaFile, avcName string, encoder ffmpeg.AvcEncoder) (result *exec.Cmd, useMutex bool, err error) {
|
||||||
fileExt := f.Extension()
|
fileExt := f.Extension()
|
||||||
fileName := f.FileName()
|
fileName := f.FileName()
|
||||||
bitrate := c.AvcBitrate(f)
|
|
||||||
ffmpegBin := c.conf.FFmpegBin()
|
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case fileName == "":
|
case fileName == "":
|
||||||
return nil, false, fmt.Errorf("convert: %s video filename is empty - possible bug", f.FileType())
|
return nil, false, fmt.Errorf("convert: %s video filename is empty - possible bug", f.FileType())
|
||||||
case bitrate == "":
|
|
||||||
return nil, false, fmt.Errorf("convert: transcoding bitrate is empty - possible bug")
|
|
||||||
case !f.IsAnimated():
|
case !f.IsAnimated():
|
||||||
return nil, false, fmt.Errorf("convert: file type %s of %s cannot be transcoded", f.FileType(), clean.Log(f.BaseName()))
|
return nil, false, fmt.Errorf("convert: file type %s of %s cannot be transcoded", f.FileType(), clean.Log(f.BaseName()))
|
||||||
}
|
}
|
||||||
|
@ -152,13 +148,13 @@ func (c *Convert) AvcConvertCommand(f *MediaFile, avcName string, encoder ffmpeg
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transcode all other formats with FFmpeg.
|
// Transcode all other formats with FFmpeg.
|
||||||
if ffmpegBin == "" {
|
var opt ffmpeg.Options
|
||||||
return nil, false, fmt.Errorf("convert: ffmpeg must be installed to transcode %s", clean.Log(f.BaseName()))
|
|
||||||
} else if c.conf.DisableFFmpeg() {
|
|
||||||
return nil, false, fmt.Errorf("convert: ffmpeg must be enabled to transcode %s", clean.Log(f.BaseName()))
|
|
||||||
}
|
|
||||||
|
|
||||||
return ffmpeg.AvcConvertCommand(fileName, avcName, ffmpegBin, c.AvcBitrate(f), encoder)
|
if opt, err = c.conf.FFmpegOptions(encoder, c.AvcBitrate(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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AvcBitrate returns the ideal AVC encoding bitrate in megabits per second.
|
// AvcBitrate returns the ideal AVC encoding bitrate in megabits per second.
|
||||||
|
|
Loading…
Reference in a new issue