mirror of https://github.com/stashapp/stash.git
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:
parent
a36b895e4b
commit
b67abb89ff
|
@ -46,6 +46,10 @@ fragment ConfigGeneralData on ConfigGeneralResult {
|
|||
api_key
|
||||
}
|
||||
pythonPath
|
||||
transcodeInputArgs
|
||||
transcodeOutputArgs
|
||||
liveTranscodeInputArgs
|
||||
liveTranscodeOutputArgs
|
||||
}
|
||||
|
||||
fragment ConfigInterfaceData on ConfigInterfaceResult {
|
||||
|
|
|
@ -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"""
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 != "" {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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{})
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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:")
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.",
|
||||
|
|
Loading…
Reference in New Issue