Allow configuration of ffmpeg args (#3216)

* Allow configuration of ffmpeg args
* Add UI settings for ffmpeg config
* Add changelog entry
* Add documentation in manual
This commit is contained in:
WithoutPants 2023-01-27 11:31:11 +11:00 committed by GitHub
parent a36b895e4b
commit b67abb89ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 204 additions and 26 deletions

View File

@ -46,6 +46,10 @@ fragment ConfigGeneralData on ConfigGeneralResult {
api_key
}
pythonPath
transcodeInputArgs
transcodeOutputArgs
liveTranscodeInputArgs
liveTranscodeOutputArgs
}
fragment ConfigInterfaceData on ConfigInterfaceResult {

View File

@ -69,6 +69,21 @@ input ConfigGeneralInput {
maxTranscodeSize: StreamingResolutionEnum
"""Max streaming transcode size"""
maxStreamingTranscodeSize: StreamingResolutionEnum
"""ffmpeg transcode input args - injected before input file
These are applied to generated transcodes (previews and transcodes)"""
transcodeInputArgs: [String!]
"""ffmpeg transcode output args - injected before output file
These are applied to generated transcodes (previews and transcodes)"""
transcodeOutputArgs: [String!]
"""ffmpeg stream input args - injected before input file
These are applied when live transcoding"""
liveTranscodeInputArgs: [String!]
"""ffmpeg stream output args - injected before output file
These are applied when live transcoding"""
liveTranscodeOutputArgs: [String!]
"""Write image thumbnails to disk when generating on the fly"""
writeImageThumbnails: Boolean
"""Username"""
@ -152,6 +167,21 @@ type ConfigGeneralResult {
maxTranscodeSize: StreamingResolutionEnum
"""Max streaming transcode size"""
maxStreamingTranscodeSize: StreamingResolutionEnum
"""ffmpeg transcode input args - injected before input file
These are applied to generated transcodes (previews and transcodes)"""
transcodeInputArgs: [String!]!
"""ffmpeg transcode output args - injected before output file
These are applied to generated transcodes (previews and transcodes)"""
transcodeOutputArgs: [String!]!
"""ffmpeg stream input args - injected before input file
These are applied when live transcoding"""
liveTranscodeInputArgs: [String!]!
"""ffmpeg stream output args - injected before output file
These are applied when live transcoding"""
liveTranscodeOutputArgs: [String!]!
"""Write image thumbnails to disk when generating on the fly"""
writeImageThumbnails: Boolean!
"""API Key"""

View File

@ -280,6 +280,19 @@ func (r *mutationResolver) ConfigureGeneral(ctx context.Context, input ConfigGen
c.Set(config.PythonPath, input.PythonPath)
}
if input.TranscodeInputArgs != nil {
c.Set(config.TranscodeInputArgs, input.TranscodeInputArgs)
}
if input.TranscodeOutputArgs != nil {
c.Set(config.TranscodeOutputArgs, input.TranscodeOutputArgs)
}
if input.LiveTranscodeInputArgs != nil {
c.Set(config.LiveTranscodeInputArgs, input.LiveTranscodeInputArgs)
}
if input.LiveTranscodeOutputArgs != nil {
c.Set(config.LiveTranscodeOutputArgs, input.LiveTranscodeOutputArgs)
}
if err := c.Write(); err != nil {
return makeConfigGeneralResult(), err
}

View File

@ -123,6 +123,10 @@ func makeConfigGeneralResult() *ConfigGeneralResult {
ScraperCDPPath: &scraperCDPPath,
StashBoxes: config.GetStashBoxes(),
PythonPath: config.GetPythonPath(),
TranscodeInputArgs: config.GetTranscodeInputArgs(),
TranscodeOutputArgs: config.GetTranscodeOutputArgs(),
LiveTranscodeInputArgs: config.GetLiveTranscodeInputArgs(),
LiveTranscodeOutputArgs: config.GetLiveTranscodeOutputArgs(),
}
}

View File

@ -185,6 +185,8 @@ func (rs sceneRoutes) streamTranscode(w http.ResponseWriter, r *http.Request, st
width := f.Width
height := f.Height
config := config.GetInstance()
options := ffmpeg.TranscodeStreamOptions{
Input: f.Path,
Codec: streamFormat,
@ -194,7 +196,9 @@ func (rs sceneRoutes) streamTranscode(w http.ResponseWriter, r *http.Request, st
VideoHeight: height,
StartTime: ss,
MaxTranscodeSize: config.GetInstance().GetMaxStreamingTranscodeSize().GetMaxResolution(),
MaxTranscodeSize: config.GetMaxStreamingTranscodeSize().GetMaxResolution(),
ExtraInputArgs: config.GetLiveTranscodeInputArgs(),
ExtraOutputArgs: config.GetLiveTranscodeOutputArgs(),
}
if requestedSize != "" {

View File

@ -60,6 +60,12 @@ const (
MaxTranscodeSize = "max_transcode_size"
MaxStreamingTranscodeSize = "max_streaming_transcode_size"
// ffmpeg extra args options
TranscodeInputArgs = "ffmpeg.transcode.input_args"
TranscodeOutputArgs = "ffmpeg.transcode.output_args"
LiveTranscodeInputArgs = "ffmpeg.live_transcode.input_args"
LiveTranscodeOutputArgs = "ffmpeg.live_transcode.output_args"
ParallelTasks = "parallel_tasks"
parallelTasksDefault = 1
@ -786,6 +792,22 @@ func (i *Instance) GetMaxStreamingTranscodeSize() models.StreamingResolutionEnum
return models.StreamingResolutionEnum(ret)
}
func (i *Instance) GetTranscodeInputArgs() []string {
return i.getStringSlice(TranscodeInputArgs)
}
func (i *Instance) GetTranscodeOutputArgs() []string {
return i.getStringSlice(TranscodeOutputArgs)
}
func (i *Instance) GetLiveTranscodeInputArgs() []string {
return i.getStringSlice(LiveTranscodeInputArgs)
}
func (i *Instance) GetLiveTranscodeOutputArgs() []string {
return i.getStringSlice(LiveTranscodeOutputArgs)
}
// IsWriteImageThumbnails returns true if image thumbnails should be written
// to disk after generating on the fly.
func (i *Instance) IsWriteImageThumbnails() bool {

View File

@ -75,9 +75,10 @@ func NewSpriteGenerator(videoFile ffmpeg.VideoFile, videoChecksum string, imageO
SlowSeek: slowSeek,
Columns: cols,
g: &generate.Generator{
Encoder: instance.FFMPEG,
LockManager: instance.ReadLockManager,
ScenePaths: instance.Paths.Scene,
Encoder: instance.FFMPEG,
FFMpegConfig: instance.Config,
LockManager: instance.ReadLockManager,
ScenePaths: instance.Paths.Scene,
},
}, nil
}

View File

@ -292,9 +292,10 @@ type coverGenerator struct {
func (g *coverGenerator) GenerateCover(ctx context.Context, scene *models.Scene, f *file.VideoFile) error {
gg := generate.Generator{
Encoder: instance.FFMPEG,
LockManager: instance.ReadLockManager,
ScenePaths: instance.Paths.Scene,
Encoder: instance.FFMPEG,
FFMpegConfig: instance.Config,
LockManager: instance.ReadLockManager,
ScenePaths: instance.Paths.Scene,
}
return gg.Screenshot(ctx, f.Path, scene.GetHash(instance.Config.GetVideoFileNamingAlgorithm()), f.Width, f.Duration, generate.ScreenshotOptions{})

View File

@ -102,11 +102,12 @@ func (j *GenerateJob) Execute(ctx context.Context, progress *job.Progress) {
}
g := &generate.Generator{
Encoder: instance.FFMPEG,
LockManager: instance.ReadLockManager,
MarkerPaths: instance.Paths.SceneMarkers,
ScenePaths: instance.Paths.Scene,
Overwrite: j.overwrite,
Encoder: instance.FFMPEG,
FFMpegConfig: instance.Config,
LockManager: instance.ReadLockManager,
MarkerPaths: instance.Paths.SceneMarkers,
ScenePaths: instance.Paths.Scene,
Overwrite: j.overwrite,
}
if err := j.txnManager.WithReadTxn(ctx, func(ctx context.Context) error {

View File

@ -44,10 +44,11 @@ func (t *GenerateScreenshotTask) Start(ctx context.Context) {
logger.Debugf("Creating screenshot for %s", scenePath)
g := generate.Generator{
Encoder: instance.FFMPEG,
LockManager: instance.ReadLockManager,
ScenePaths: instance.Paths.Scene,
Overwrite: true,
Encoder: instance.FFMPEG,
FFMpegConfig: instance.Config,
LockManager: instance.ReadLockManager,
ScenePaths: instance.Paths.Scene,
Overwrite: true,
}
if err := g.Screenshot(context.TODO(), videoFile.Path, checksum, videoFile.Width, videoFile.Duration, generate.ScreenshotOptions{

View File

@ -445,11 +445,12 @@ func (g *sceneGenerators) Generate(ctx context.Context, s *models.Scene, f *file
options := getGeneratePreviewOptions(GeneratePreviewOptionsInput{})
g := &generate.Generator{
Encoder: instance.FFMPEG,
LockManager: instance.ReadLockManager,
MarkerPaths: instance.Paths.SceneMarkers,
ScenePaths: instance.Paths.Scene,
Overwrite: overwrite,
Encoder: instance.FFMPEG,
FFMpegConfig: instance.Config,
LockManager: instance.ReadLockManager,
MarkerPaths: instance.Paths.SceneMarkers,
ScenePaths: instance.Paths.Scene,
Overwrite: overwrite,
}
taskPreview := GeneratePreviewTask{

View File

@ -144,11 +144,17 @@ type TranscodeStreamOptions struct {
// in some videos where the audio codec is not supported by ffmpeg
// ffmpeg fails if you try to transcode the audio
VideoOnly bool
// arguments added before the input argument
ExtraInputArgs []string
// arguments added before the output argument
ExtraOutputArgs []string
}
func (o TranscodeStreamOptions) getStreamArgs() Args {
var args Args
args = append(args, "-hide_banner")
args = append(args, o.ExtraInputArgs...)
args = args.LogLevel(LogLevelError)
if o.StartTime != 0 {
@ -184,6 +190,8 @@ func (o TranscodeStreamOptions) getStreamArgs() Args {
"-ac", "2",
)
args = append(args, o.ExtraOutputArgs...)
args = args.Format(o.Codec.format)
args = args.Output("pipe:")

View File

@ -21,6 +21,11 @@ type TranscodeOptions struct {
// Verbosity is the logging verbosity. Defaults to LogLevelError if not set.
Verbosity ffmpeg.LogLevel
// arguments added before the input argument
ExtraInputArgs []string
// arguments added before the output argument
ExtraOutputArgs []string
}
func (o *TranscodeOptions) setDefaults() {
@ -59,6 +64,7 @@ func Transcode(input string, options TranscodeOptions) ffmpeg.Args {
var args ffmpeg.Args
args = args.LogLevel(options.Verbosity).Overwrite()
args = append(args, options.ExtraInputArgs...)
if options.XError {
args = args.XError()
@ -92,6 +98,8 @@ func Transcode(input string, options TranscodeOptions) ffmpeg.Args {
}
args = args.AppendArgs(options.AudioArgs)
args = append(args, options.ExtraOutputArgs...)
args = args.Format(options.Format)
args = args.Output(options.OutputPath)

View File

@ -47,12 +47,18 @@ type ScenePaths interface {
GetTranscodePath(checksum string) string
}
type FFMpegConfig interface {
GetTranscodeInputArgs() []string
GetTranscodeOutputArgs() []string
}
type Generator struct {
Encoder ffmpeg.FFMpeg
LockManager *fsutil.ReadLockManager
MarkerPaths MarkerPaths
ScenePaths ScenePaths
Overwrite bool
Encoder ffmpeg.FFMpeg
FFMpegConfig FFMpegConfig
LockManager *fsutil.ReadLockManager
MarkerPaths MarkerPaths
ScenePaths ScenePaths
Overwrite bool
}
type generateFn func(lockCtx *fsutil.LockContext, tmpFn string) error

View File

@ -199,6 +199,9 @@ func (g Generator) previewVideoChunk(lockCtx *fsutil.LockContext, fn string, opt
VideoCodec: ffmpeg.VideoCodecLibX264,
VideoArgs: videoArgs,
ExtraInputArgs: g.FFMpegConfig.GetTranscodeInputArgs(),
ExtraOutputArgs: g.FFMpegConfig.GetTranscodeOutputArgs(),
}
if options.Audio {
@ -299,6 +302,9 @@ func (g Generator) previewVideoToImage(input string) generateFn {
VideoCodec: ffmpeg.VideoCodecLibWebP,
VideoArgs: videoArgs,
ExtraInputArgs: g.FFMpegConfig.GetTranscodeInputArgs(),
ExtraOutputArgs: g.FFMpegConfig.GetTranscodeOutputArgs(),
}
args := transcoder.Transcode(input, encodeOptions)

View File

@ -86,6 +86,9 @@ func (g Generator) transcode(input string, options TranscodeOptions) generateFn
VideoCodec: ffmpeg.VideoCodecLibX264,
VideoArgs: videoArgs,
AudioCodec: ffmpeg.AudioCodecAAC,
ExtraInputArgs: g.FFMpegConfig.GetTranscodeInputArgs(),
ExtraOutputArgs: g.FFMpegConfig.GetTranscodeOutputArgs(),
})
return g.generate(lockCtx, args)
@ -117,6 +120,9 @@ func (g Generator) transcodeVideo(input string, options TranscodeOptions) genera
VideoCodec: ffmpeg.VideoCodecLibX264,
VideoArgs: videoArgs,
AudioArgs: audioArgs,
ExtraInputArgs: g.FFMpegConfig.GetTranscodeInputArgs(),
ExtraOutputArgs: g.FFMpegConfig.GetTranscodeOutputArgs(),
})
return g.generate(lockCtx, args)

View File

@ -7,6 +7,7 @@ import {
ModalSetting,
NumberSetting,
SelectSetting,
StringListSetting,
StringSetting,
} from "./Inputs";
import { SettingStateContext } from "./context";
@ -227,6 +228,36 @@ export const SettingsConfigurationPanel: React.FC = () => {
</option>
))}
</SelectSetting>
<StringListSetting
id="transcode-input-args"
headingID="config.general.ffmpeg.transcode.input_args.heading"
subHeadingID="config.general.ffmpeg.transcode.input_args.desc"
onChange={(v) => saveGeneral({ transcodeInputArgs: v })}
value={general.transcodeInputArgs ?? []}
/>
<StringListSetting
id="transcode-output-args"
headingID="config.general.ffmpeg.transcode.output_args.heading"
subHeadingID="config.general.ffmpeg.transcode.output_args.desc"
onChange={(v) => saveGeneral({ transcodeOutputArgs: v })}
value={general.transcodeOutputArgs ?? []}
/>
<StringListSetting
id="live-transcode-input-args"
headingID="config.general.ffmpeg.live_transcode.input_args.heading"
subHeadingID="config.general.ffmpeg.live_transcode.input_args.desc"
onChange={(v) => saveGeneral({ liveTranscodeInputArgs: v })}
value={general.liveTranscodeInputArgs ?? []}
/>
<StringListSetting
id="live-transcode-output-args"
headingID="config.general.ffmpeg.live_transcode.output_args.heading"
subHeadingID="config.general.ffmpeg.live_transcode.output_args.desc"
onChange={(v) => saveGeneral({ liveTranscodeOutputArgs: v })}
value={general.liveTranscodeOutputArgs ?? []}
/>
</SettingSection>
<SettingSection headingID="config.general.parallel_scan_head">

View File

@ -2,6 +2,7 @@
* Performer autotagging does not currently match on performer aliases. This will be addressed when finer control over the matching is implemented.
### ✨ New Features
* Added support for injecting arguments into `ffmpeg` during generation and live-transcoding. ([#3216](https://github.com/stashapp/stash/pull/3216))
* Added URL and Date fields to Images. ([#3015](https://github.com/stashapp/stash/pull/3015))
* Added support for plugins to add injected CSS and Javascript to the UI. ([#3195](https://github.com/stashapp/stash/pull/3195))
* Added disambiguation field to Performers, to differentiate between performers with the same name. ([#3113](https://github.com/stashapp/stash/pull/3113))

View File

@ -77,6 +77,14 @@ This setting can be used to increase/decrease overall CPU utilisation in two sce
Note: If this is set too high it will decrease overall performance and causes failures (out of memory).
## ffmpeg arguments
Additional arguments can be injected into ffmpeg when generating previews and sprites, and when live-transcoding videos.
The ffmpeg arguments configuration is split into `Input` and `Output` arguments. Input arguments are injected before the input file argument, and output arguments are injected before the output file argument.
Arguments are accepted as a list of strings. Each string is a separate argument. For example, a single argument of `-foo bar` would be treated as a single argument `"-foo bar"`. The correct way to pass this argument would be to split it into two separate arguments: `"-foo", "bar"`.
## Scraping
### User Agent string

View File

@ -271,6 +271,28 @@
"excluded_image_gallery_patterns_head": "Excluded Image/Gallery Patterns",
"excluded_video_patterns_desc": "Regexps of video files/paths to exclude from Scan and add to Clean",
"excluded_video_patterns_head": "Excluded Video Patterns",
"ffmpeg": {
"transcode": {
"input_args": {
"heading": "FFmpeg Transcode Input Args",
"desc": "Advanced: Additional arguments to pass to ffmpeg before the input field when generating video."
},
"output_args": {
"heading": "FFmpeg Transcode Output Args",
"desc": "Advanced: Additional arguments to pass to ffmpeg before the output field when generating video."
}
},
"live_transcode": {
"input_args": {
"heading": "FFmpeg LiveTranscode Input Args",
"desc": "Advanced: Additional arguments to pass to ffmpeg before the input field when live transcoding video."
},
"output_args": {
"heading": "FFmpeg Live Transcode Output Args",
"desc": "Advanced: Additional arguments to pass to ffmpeg before the output field when live transcoding video."
}
}
},
"gallery_ext_desc": "Comma-delimited list of file extensions that will be identified as gallery zip files.",
"gallery_ext_head": "Gallery zip Extensions",
"generated_file_naming_hash_desc": "Use MD5 or oshash for generated file naming. Changing this requires that all scenes have the applicable MD5/oshash value populated. After changing this value, existing generated files will need to be migrated or regenerated. See Tasks page for migration.",