mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 04:14:39 +03:00
Add video codec profiles (#5154)
This commit is contained in:
@@ -1,25 +1,32 @@
|
|||||||
package ffmpeg
|
package ffmpeg
|
||||||
|
|
||||||
type VideoCodec string
|
type VideoCodec struct {
|
||||||
|
Name string // The full name of the codec including profile/quality
|
||||||
|
CodeName string // The core codec name without profile/quality suffix
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeVideoCodec(name string, codename string) VideoCodec {
|
||||||
|
return VideoCodec{name, codename}
|
||||||
|
}
|
||||||
|
|
||||||
func (c VideoCodec) Args() []string {
|
func (c VideoCodec) Args() []string {
|
||||||
if c == "" {
|
if c.CodeName == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return []string{"-c:v", string(c)}
|
return []string{"-c:v", string(c.CodeName)}
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// Software codec's
|
// Software codec's
|
||||||
VideoCodecLibX264 VideoCodec = "libx264"
|
VideoCodecLibX264 = makeVideoCodec("x264", "libx264")
|
||||||
VideoCodecLibWebP VideoCodec = "libwebp"
|
VideoCodecLibWebP = makeVideoCodec("WebP", "libwebp")
|
||||||
VideoCodecBMP VideoCodec = "bmp"
|
VideoCodecBMP = makeVideoCodec("BMP", "bmp")
|
||||||
VideoCodecMJpeg VideoCodec = "mjpeg"
|
VideoCodecMJpeg = makeVideoCodec("Jpeg", "mjpeg")
|
||||||
VideoCodecVP9 VideoCodec = "libvpx-vp9"
|
VideoCodecVP9 = makeVideoCodec("VPX-VP9", "libvpx-vp9")
|
||||||
VideoCodecVPX VideoCodec = "libvpx"
|
VideoCodecVPX = makeVideoCodec("VPX-VP8", "libvpx")
|
||||||
VideoCodecLibX265 VideoCodec = "libx265"
|
VideoCodecLibX265 = makeVideoCodec("x265", "libx265")
|
||||||
VideoCodecCopy VideoCodec = "copy"
|
VideoCodecCopy = makeVideoCodec("Copy", "copy")
|
||||||
)
|
)
|
||||||
|
|
||||||
type AudioCodec string
|
type AudioCodec string
|
||||||
|
|||||||
@@ -15,16 +15,18 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
// Hardware codec's
|
// Hardware codec's
|
||||||
VideoCodecN264 VideoCodec = "h264_nvenc"
|
VideoCodecN264 = makeVideoCodec("H264 NVENC", "h264_nvenc")
|
||||||
VideoCodecI264 VideoCodec = "h264_qsv"
|
VideoCodecN264H = makeVideoCodec("H264 NVENC HQ profile", "h264_nvenc")
|
||||||
VideoCodecA264 VideoCodec = "h264_amf"
|
VideoCodecI264 = makeVideoCodec("H264 Intel Quick Sync Video (QSV)", "h264_qsv")
|
||||||
VideoCodecM264 VideoCodec = "h264_videotoolbox"
|
VideoCodecI264C = makeVideoCodec("H264 Intel Quick Sync Video (QSV) Compatibility profile", "h264_qsv")
|
||||||
VideoCodecV264 VideoCodec = "h264_vaapi"
|
VideoCodecA264 = makeVideoCodec("H264 Advanced Media Framework (AMF)", "h264_amf")
|
||||||
VideoCodecR264 VideoCodec = "h264_v4l2m2m"
|
VideoCodecM264 = makeVideoCodec("H264 VideoToolbox", "h264_videotoolbox")
|
||||||
VideoCodecO264 VideoCodec = "h264_omx"
|
VideoCodecV264 = makeVideoCodec("H264 VAAPI", "h264_vaapi")
|
||||||
VideoCodecIVP9 VideoCodec = "vp9_qsv"
|
VideoCodecR264 = makeVideoCodec("H264 V4L2M2M", "h264_v4l2m2m")
|
||||||
VideoCodecVVP9 VideoCodec = "vp9_vaapi"
|
VideoCodecO264 = makeVideoCodec("H264 OMX", "h264_omx")
|
||||||
VideoCodecVVPX VideoCodec = "vp8_vaapi"
|
VideoCodecIVP9 = makeVideoCodec("VP9 Intel Quick Sync Video (QSV)", "vp9_qsv")
|
||||||
|
VideoCodecVVP9 = makeVideoCodec("VP9 VAAPI", "vp9_vaapi")
|
||||||
|
VideoCodecVVPX = makeVideoCodec("VP8 VAAPI", "vp8_vaapi")
|
||||||
)
|
)
|
||||||
|
|
||||||
const minHeight int = 480
|
const minHeight int = 480
|
||||||
@@ -33,9 +35,12 @@ const minHeight int = 480
|
|||||||
func (f *FFMpeg) InitHWSupport(ctx context.Context) {
|
func (f *FFMpeg) InitHWSupport(ctx context.Context) {
|
||||||
var hwCodecSupport []VideoCodec
|
var hwCodecSupport []VideoCodec
|
||||||
|
|
||||||
|
// Note that the first compatible codec is returned, so order is important
|
||||||
for _, codec := range []VideoCodec{
|
for _, codec := range []VideoCodec{
|
||||||
|
VideoCodecN264H,
|
||||||
VideoCodecN264,
|
VideoCodecN264,
|
||||||
VideoCodecI264,
|
VideoCodecI264,
|
||||||
|
VideoCodecI264C,
|
||||||
VideoCodecV264,
|
VideoCodecV264,
|
||||||
VideoCodecR264,
|
VideoCodecR264,
|
||||||
VideoCodecIVP9,
|
VideoCodecIVP9,
|
||||||
@@ -79,7 +84,7 @@ func (f *FFMpeg) InitHWSupport(ctx context.Context) {
|
|||||||
|
|
||||||
outstr := fmt.Sprintf("[InitHWSupport] Supported HW codecs [%d]:\n", len(hwCodecSupport))
|
outstr := fmt.Sprintf("[InitHWSupport] Supported HW codecs [%d]:\n", len(hwCodecSupport))
|
||||||
for _, codec := range hwCodecSupport {
|
for _, codec := range hwCodecSupport {
|
||||||
outstr += fmt.Sprintf("\t%s\n", codec)
|
outstr += fmt.Sprintf("\t%s - %s\n", codec.Name, codec.CodeName)
|
||||||
}
|
}
|
||||||
logger.Info(outstr)
|
logger.Info(outstr)
|
||||||
|
|
||||||
@@ -128,7 +133,8 @@ func (f *FFMpeg) hwCanFullHWTranscode(ctx context.Context, codec VideoCodec, vf
|
|||||||
// Prepend input for hardware encoding only
|
// Prepend input for hardware encoding only
|
||||||
func (f *FFMpeg) hwDeviceInit(args Args, toCodec VideoCodec, fullhw bool) Args {
|
func (f *FFMpeg) hwDeviceInit(args Args, toCodec VideoCodec, fullhw bool) Args {
|
||||||
switch toCodec {
|
switch toCodec {
|
||||||
case VideoCodecN264:
|
case VideoCodecN264,
|
||||||
|
VideoCodecN264H:
|
||||||
args = append(args, "-hwaccel_device")
|
args = append(args, "-hwaccel_device")
|
||||||
args = append(args, "0")
|
args = append(args, "0")
|
||||||
if fullhw {
|
if fullhw {
|
||||||
@@ -150,6 +156,7 @@ func (f *FFMpeg) hwDeviceInit(args Args, toCodec VideoCodec, fullhw bool) Args {
|
|||||||
args = append(args, "vaapi")
|
args = append(args, "vaapi")
|
||||||
}
|
}
|
||||||
case VideoCodecI264,
|
case VideoCodecI264,
|
||||||
|
VideoCodecI264C,
|
||||||
VideoCodecIVP9:
|
VideoCodecIVP9:
|
||||||
if fullhw {
|
if fullhw {
|
||||||
args = append(args, "-hwaccel")
|
args = append(args, "-hwaccel")
|
||||||
@@ -187,12 +194,13 @@ func (f *FFMpeg) hwFilterInit(toCodec VideoCodec, fullhw bool) VideoFilter {
|
|||||||
videoFilter = videoFilter.Append("format=nv12")
|
videoFilter = videoFilter.Append("format=nv12")
|
||||||
videoFilter = videoFilter.Append("hwupload")
|
videoFilter = videoFilter.Append("hwupload")
|
||||||
}
|
}
|
||||||
case VideoCodecN264:
|
case VideoCodecN264, VideoCodecN264H:
|
||||||
if !fullhw {
|
if !fullhw {
|
||||||
videoFilter = videoFilter.Append("format=yuv420p")
|
videoFilter = videoFilter.Append("format=nv12")
|
||||||
videoFilter = videoFilter.Append("hwupload_cuda")
|
videoFilter = videoFilter.Append("hwupload_cuda")
|
||||||
}
|
}
|
||||||
case VideoCodecI264,
|
case VideoCodecI264,
|
||||||
|
VideoCodecI264C,
|
||||||
VideoCodecIVP9:
|
VideoCodecIVP9:
|
||||||
if !fullhw {
|
if !fullhw {
|
||||||
videoFilter = videoFilter.Append("hwupload=extra_hw_frames=64")
|
videoFilter = videoFilter.Append("hwupload=extra_hw_frames=64")
|
||||||
@@ -268,7 +276,7 @@ func (f *FFMpeg) hwCodecFilter(args VideoFilter, codec VideoCodec, vf *models.Vi
|
|||||||
// Apply format switching if applicable
|
// Apply format switching if applicable
|
||||||
func (f *FFMpeg) hwApplyFullHWFilter(args VideoFilter, codec VideoCodec, fullhw bool) VideoFilter {
|
func (f *FFMpeg) hwApplyFullHWFilter(args VideoFilter, codec VideoCodec, fullhw bool) VideoFilter {
|
||||||
switch codec {
|
switch codec {
|
||||||
case VideoCodecN264:
|
case VideoCodecN264, VideoCodecN264H:
|
||||||
if fullhw && f.version.Gteq(FFMpegVersion{major: 5}) { // Added in FFMpeg 5
|
if fullhw && f.version.Gteq(FFMpegVersion{major: 5}) { // Added in FFMpeg 5
|
||||||
args = args.Append("scale_cuda=format=yuv420p")
|
args = args.Append("scale_cuda=format=yuv420p")
|
||||||
}
|
}
|
||||||
@@ -276,7 +284,7 @@ func (f *FFMpeg) hwApplyFullHWFilter(args VideoFilter, codec VideoCodec, fullhw
|
|||||||
if fullhw && f.version.Gteq(FFMpegVersion{major: 3, minor: 1}) { // Added in FFMpeg 3.1
|
if fullhw && f.version.Gteq(FFMpegVersion{major: 3, minor: 1}) { // Added in FFMpeg 3.1
|
||||||
args = args.Append("scale_vaapi=format=nv12")
|
args = args.Append("scale_vaapi=format=nv12")
|
||||||
}
|
}
|
||||||
case VideoCodecI264, VideoCodecIVP9:
|
case VideoCodecI264, VideoCodecI264C, VideoCodecIVP9:
|
||||||
if fullhw && f.version.Gteq(FFMpegVersion{major: 3, minor: 3}) { // Added in FFMpeg 3.3
|
if fullhw && f.version.Gteq(FFMpegVersion{major: 3, minor: 3}) { // Added in FFMpeg 3.3
|
||||||
args = args.Append("scale_qsv=format=nv12")
|
args = args.Append("scale_qsv=format=nv12")
|
||||||
}
|
}
|
||||||
@@ -290,7 +298,7 @@ func (f *FFMpeg) hwApplyScaleTemplate(sargs string, codec VideoCodec, match []in
|
|||||||
var template string
|
var template string
|
||||||
|
|
||||||
switch codec {
|
switch codec {
|
||||||
case VideoCodecN264:
|
case VideoCodecN264, VideoCodecN264H:
|
||||||
template = "scale_cuda=$value"
|
template = "scale_cuda=$value"
|
||||||
if fullhw && f.version.Gteq(FFMpegVersion{major: 5}) { // Added in FFMpeg 5
|
if fullhw && f.version.Gteq(FFMpegVersion{major: 5}) { // Added in FFMpeg 5
|
||||||
template += ":format=yuv420p"
|
template += ":format=yuv420p"
|
||||||
@@ -300,7 +308,7 @@ func (f *FFMpeg) hwApplyScaleTemplate(sargs string, codec VideoCodec, match []in
|
|||||||
if fullhw && f.version.Gteq(FFMpegVersion{major: 3, minor: 1}) { // Added in FFMpeg 3.1
|
if fullhw && f.version.Gteq(FFMpegVersion{major: 3, minor: 1}) { // Added in FFMpeg 3.1
|
||||||
template += ":format=nv12"
|
template += ":format=nv12"
|
||||||
}
|
}
|
||||||
case VideoCodecI264, VideoCodecIVP9:
|
case VideoCodecI264, VideoCodecI264C, VideoCodecIVP9:
|
||||||
template = "scale_qsv=$value"
|
template = "scale_qsv=$value"
|
||||||
if fullhw && f.version.Gteq(FFMpegVersion{major: 3, minor: 3}) { // Added in FFMpeg 3.3
|
if fullhw && f.version.Gteq(FFMpegVersion{major: 3, minor: 3}) { // Added in FFMpeg 3.3
|
||||||
template += ":format=nv12"
|
template += ":format=nv12"
|
||||||
@@ -312,7 +320,7 @@ func (f *FFMpeg) hwApplyScaleTemplate(sargs string, codec VideoCodec, match []in
|
|||||||
}
|
}
|
||||||
|
|
||||||
// BUG: [scale_qsv]: Size values less than -1 are not acceptable.
|
// BUG: [scale_qsv]: Size values less than -1 are not acceptable.
|
||||||
isIntel := codec == VideoCodecI264 || codec == VideoCodecIVP9
|
isIntel := codec == VideoCodecI264 || codec == VideoCodecI264C || codec == VideoCodecIVP9
|
||||||
// BUG: scale_vt doesn't call ff_scale_adjust_dimensions, thus cant accept negative size values
|
// BUG: scale_vt doesn't call ff_scale_adjust_dimensions, thus cant accept negative size values
|
||||||
isApple := codec == VideoCodecM264
|
isApple := codec == VideoCodecM264
|
||||||
return VideoFilter(templateReplaceScale(sargs, template, match, vf, isIntel || isApple))
|
return VideoFilter(templateReplaceScale(sargs, template, match, vf, isIntel || isApple))
|
||||||
@@ -322,7 +330,9 @@ func (f *FFMpeg) hwApplyScaleTemplate(sargs string, codec VideoCodec, match []in
|
|||||||
func (f *FFMpeg) hwCodecMaxRes(codec VideoCodec) (int, int) {
|
func (f *FFMpeg) hwCodecMaxRes(codec VideoCodec) (int, int) {
|
||||||
switch codec {
|
switch codec {
|
||||||
case VideoCodecN264,
|
case VideoCodecN264,
|
||||||
VideoCodecI264:
|
VideoCodecN264H,
|
||||||
|
VideoCodecI264,
|
||||||
|
VideoCodecI264C:
|
||||||
return 4096, 4096
|
return 4096, 4096
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -345,7 +355,9 @@ func (f *FFMpeg) hwCodecHLSCompatible() *VideoCodec {
|
|||||||
for _, element := range f.hwCodecSupport {
|
for _, element := range f.hwCodecSupport {
|
||||||
switch element {
|
switch element {
|
||||||
case VideoCodecN264,
|
case VideoCodecN264,
|
||||||
|
VideoCodecN264H,
|
||||||
VideoCodecI264,
|
VideoCodecI264,
|
||||||
|
VideoCodecI264C,
|
||||||
VideoCodecV264,
|
VideoCodecV264,
|
||||||
VideoCodecR264,
|
VideoCodecR264,
|
||||||
VideoCodecM264: // Note that the Apple encoder sucks at startup, thus HLS quality is crap
|
VideoCodecM264: // Note that the Apple encoder sucks at startup, thus HLS quality is crap
|
||||||
@@ -360,7 +372,9 @@ func (f *FFMpeg) hwCodecMP4Compatible() *VideoCodec {
|
|||||||
for _, element := range f.hwCodecSupport {
|
for _, element := range f.hwCodecSupport {
|
||||||
switch element {
|
switch element {
|
||||||
case VideoCodecN264,
|
case VideoCodecN264,
|
||||||
|
VideoCodecN264H,
|
||||||
VideoCodecI264,
|
VideoCodecI264,
|
||||||
|
VideoCodecI264C,
|
||||||
VideoCodecM264:
|
VideoCodecM264:
|
||||||
return &element
|
return &element
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,12 +45,31 @@ func CodecInit(codec VideoCodec) (args Args) {
|
|||||||
"-rc", "vbr",
|
"-rc", "vbr",
|
||||||
"-cq", "15",
|
"-cq", "15",
|
||||||
)
|
)
|
||||||
case VideoCodecI264:
|
case VideoCodecN264H:
|
||||||
|
args = append(args,
|
||||||
|
"-profile", "p7",
|
||||||
|
"-tune", "hq",
|
||||||
|
"-profile", "high",
|
||||||
|
"-rc", "vbr",
|
||||||
|
"-rc-lookahead", "60",
|
||||||
|
"-surfaces", "64",
|
||||||
|
"-spatial-aq", "1",
|
||||||
|
"-aq-strength", "15",
|
||||||
|
"-cq", "15",
|
||||||
|
"-coder", "cabac",
|
||||||
|
"-b_ref_mode", "middle",
|
||||||
|
)
|
||||||
|
case VideoCodecI264, VideoCodecIVP9:
|
||||||
args = append(args,
|
args = append(args,
|
||||||
"-global_quality", "20",
|
"-global_quality", "20",
|
||||||
"-preset", "faster",
|
"-preset", "faster",
|
||||||
)
|
)
|
||||||
case VideoCodecV264:
|
case VideoCodecI264C:
|
||||||
|
args = append(args,
|
||||||
|
"-q", "20",
|
||||||
|
"-preset", "faster",
|
||||||
|
)
|
||||||
|
case VideoCodecV264, VideoCodecVVP9:
|
||||||
args = append(args,
|
args = append(args,
|
||||||
"-qp", "20",
|
"-qp", "20",
|
||||||
)
|
)
|
||||||
@@ -67,15 +86,6 @@ func CodecInit(codec VideoCodec) (args Args) {
|
|||||||
"-preset", "superfast",
|
"-preset", "superfast",
|
||||||
"-crf", "25",
|
"-crf", "25",
|
||||||
)
|
)
|
||||||
case VideoCodecIVP9:
|
|
||||||
args = append(args,
|
|
||||||
"-global_quality", "20",
|
|
||||||
"-preset", "faster",
|
|
||||||
)
|
|
||||||
case VideoCodecVVP9:
|
|
||||||
args = append(args,
|
|
||||||
"-qp", "20",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return args
|
return args
|
||||||
|
|||||||
@@ -24,13 +24,13 @@ func (o *ScreenshotOptions) setDefaults() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ScreenshotOutputType struct {
|
type ScreenshotOutputType struct {
|
||||||
codec ffmpeg.VideoCodec
|
codec *ffmpeg.VideoCodec
|
||||||
format ffmpeg.Format
|
format ffmpeg.Format
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t ScreenshotOutputType) Args() []string {
|
func (t ScreenshotOutputType) Args() []string {
|
||||||
var ret []string
|
var ret []string
|
||||||
if t.codec != "" {
|
if t.codec != nil {
|
||||||
ret = append(ret, t.codec.Args()...)
|
ret = append(ret, t.codec.Args()...)
|
||||||
}
|
}
|
||||||
if t.format != "" {
|
if t.format != "" {
|
||||||
@@ -45,7 +45,7 @@ var (
|
|||||||
format: "image2",
|
format: "image2",
|
||||||
}
|
}
|
||||||
ScreenshotOutputTypeBMP = ScreenshotOutputType{
|
ScreenshotOutputTypeBMP = ScreenshotOutputType{
|
||||||
codec: ffmpeg.VideoCodecBMP,
|
codec: &ffmpeg.VideoCodecBMP,
|
||||||
format: "rawvideo",
|
format: "rawvideo",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ type SpliceOptions struct {
|
|||||||
OutputPath string
|
OutputPath string
|
||||||
Format ffmpeg.Format
|
Format ffmpeg.Format
|
||||||
|
|
||||||
VideoCodec ffmpeg.VideoCodec
|
VideoCodec *ffmpeg.VideoCodec
|
||||||
VideoArgs ffmpeg.Args
|
VideoArgs ffmpeg.Args
|
||||||
|
|
||||||
AudioCodec ffmpeg.AudioCodec
|
AudioCodec ffmpeg.AudioCodec
|
||||||
@@ -45,11 +45,11 @@ func Splice(concatFile string, options SpliceOptions) ffmpeg.Args {
|
|||||||
args = args.Overwrite()
|
args = args.Overwrite()
|
||||||
|
|
||||||
// if video codec is not provided, then use copy
|
// if video codec is not provided, then use copy
|
||||||
if options.VideoCodec == "" {
|
if options.VideoCodec == nil {
|
||||||
options.VideoCodec = ffmpeg.VideoCodecCopy
|
options.VideoCodec = &ffmpeg.VideoCodecCopy
|
||||||
}
|
}
|
||||||
|
|
||||||
args = args.VideoCodec(options.VideoCodec)
|
args = args.VideoCodec(*options.VideoCodec)
|
||||||
args = args.AppendArgs(options.VideoArgs)
|
args = args.AppendArgs(options.VideoArgs)
|
||||||
|
|
||||||
// if audio codec is not provided, then use copy
|
// if audio codec is not provided, then use copy
|
||||||
|
|||||||
Reference in New Issue
Block a user