mirror of https://github.com/stashapp/stash.git
Preview generation fallback (#725)
* Added preview generation fallback feature. When a preview generation fails (often for wmv/avi files), the new code tries with less stricted (no xerror) and more time consuming options (slow+fast seek). Fix a minor issue when stash downloads ffmpeg/ffprobe, but doesn't re-detect their paths.
This commit is contained in:
parent
44c32a91d3
commit
ecc42e4e24
|
@ -48,6 +48,9 @@ ui/v2.5/src/core/generated-*.tsx
|
|||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
|
||||
# Goland Junk
|
||||
pkg/pkg
|
||||
|
||||
####
|
||||
# Random
|
||||
####
|
||||
|
@ -58,3 +61,4 @@ node_modules
|
|||
|
||||
stash
|
||||
dist
|
||||
.DS_Store
|
||||
|
|
|
@ -14,12 +14,51 @@ type ScenePreviewChunkOptions struct {
|
|||
OutputPath string
|
||||
}
|
||||
|
||||
func (e *Encoder) ScenePreviewVideoChunk(probeResult VideoFile, options ScenePreviewChunkOptions, preset string) {
|
||||
func (e *Encoder) ScenePreviewVideoChunk(probeResult VideoFile, options ScenePreviewChunkOptions, preset string, fallback bool) error {
|
||||
var fastSeek float64
|
||||
var slowSeek float64
|
||||
fallbackMinSlowSeek := 20.0
|
||||
|
||||
args := []string{
|
||||
"-v", "error",
|
||||
"-xerror",
|
||||
"-ss", strconv.FormatFloat(options.StartTime, 'f', 2, 64),
|
||||
"-i", probeResult.Path,
|
||||
}
|
||||
|
||||
// Non-fallback: enable xerror.
|
||||
// "-xerror" causes ffmpeg to fail on warnings, often the preview is fine but could be broken.
|
||||
if !fallback {
|
||||
args = append(args, "-xerror")
|
||||
fastSeek = options.StartTime
|
||||
slowSeek = 0
|
||||
} else {
|
||||
// In fallback mode, disable "-xerror" and try a combination of fast/slow seek instead of just fastseek
|
||||
// Commonly with avi/wmv ffmpeg doesn't seem to always predict the right start point to begin decoding when
|
||||
// using fast seek. If you force ffmpeg to decode more, it avoids the "blocky green artifact" issue.
|
||||
if options.StartTime > fallbackMinSlowSeek {
|
||||
// Handle seeks longer than fallbackMinSlowSeek with fast/slow seeks
|
||||
// Allow for at least fallbackMinSlowSeek seconds of slow seek
|
||||
fastSeek = options.StartTime - fallbackMinSlowSeek
|
||||
slowSeek = fallbackMinSlowSeek
|
||||
} else {
|
||||
// Handle seeks shorter than fallbackMinSlowSeek with only slow seeks.
|
||||
slowSeek = options.StartTime
|
||||
fastSeek = 0
|
||||
}
|
||||
}
|
||||
|
||||
if fastSeek > 0 {
|
||||
args = append(args, "-ss")
|
||||
args = append(args, strconv.FormatFloat(fastSeek, 'f', 2, 64))
|
||||
}
|
||||
|
||||
args = append(args, "-i")
|
||||
args = append(args, probeResult.Path)
|
||||
|
||||
if slowSeek > 0 {
|
||||
args = append(args, "-ss")
|
||||
args = append(args, strconv.FormatFloat(slowSeek, 'f', 2, 64))
|
||||
}
|
||||
|
||||
args2 := []string{
|
||||
"-t", strconv.FormatFloat(options.Duration, 'f', 2, 64),
|
||||
"-max_muxing_queue_size", "1024", // https://trac.ffmpeg.org/ticket/6375
|
||||
"-y",
|
||||
|
@ -36,10 +75,14 @@ func (e *Encoder) ScenePreviewVideoChunk(probeResult VideoFile, options ScenePre
|
|||
"-strict", "-2",
|
||||
options.OutputPath,
|
||||
}
|
||||
_, _ = e.run(probeResult, args)
|
||||
|
||||
finalArgs := append(args, args2...)
|
||||
|
||||
_, err := e.run(probeResult, finalArgs)
|
||||
return err
|
||||
}
|
||||
|
||||
func (e *Encoder) ScenePreviewVideoChunkCombine(probeResult VideoFile, concatFilePath string, outputPath string) {
|
||||
func (e *Encoder) ScenePreviewVideoChunkCombine(probeResult VideoFile, concatFilePath string, outputPath string) error {
|
||||
args := []string{
|
||||
"-v", "error",
|
||||
"-f", "concat",
|
||||
|
@ -48,7 +91,8 @@ func (e *Encoder) ScenePreviewVideoChunkCombine(probeResult VideoFile, concatFil
|
|||
"-c", "copy",
|
||||
outputPath,
|
||||
}
|
||||
_, _ = e.run(probeResult, args)
|
||||
_, err := e.run(probeResult, args)
|
||||
return err
|
||||
}
|
||||
|
||||
func (e *Encoder) ScenePreviewVideoToImage(probeResult VideoFile, width int, videoPreviewPath string, outputPath string) error {
|
||||
|
|
|
@ -21,7 +21,7 @@ func (e *Encoder) Screenshot(probeResult VideoFile, options ScreenshotOptions) e
|
|||
"-v", options.Verbosity,
|
||||
"-ss", fmt.Sprintf("%v", options.Time),
|
||||
"-y",
|
||||
"-i", probeResult.Path, // TODO: Wrap in quotes?
|
||||
"-i", probeResult.Path,
|
||||
"-vframes", "1",
|
||||
"-q:v", fmt.Sprintf("%v", options.Quality),
|
||||
"-vf", fmt.Sprintf("scale=%v:-1", options.Width),
|
||||
|
|
|
@ -62,8 +62,11 @@ func (g *PreviewGenerator) Generate() error {
|
|||
}
|
||||
|
||||
if g.GenerateVideo {
|
||||
if err := g.generateVideo(&encoder); err != nil {
|
||||
return err
|
||||
if err := g.generateVideo(&encoder, false); err != nil {
|
||||
logger.Warnf("[generator] failed generating scene preview, trying fallback")
|
||||
if err := g.generateVideo(&encoder, true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if g.GenerateImage {
|
||||
|
@ -90,7 +93,7 @@ func (g *PreviewGenerator) generateConcatFile() error {
|
|||
return w.Flush()
|
||||
}
|
||||
|
||||
func (g *PreviewGenerator) generateVideo(encoder *ffmpeg.Encoder) error {
|
||||
func (g *PreviewGenerator) generateVideo(encoder *ffmpeg.Encoder, fallback bool) error {
|
||||
outputPath := filepath.Join(g.OutputDirectory, g.VideoFilename)
|
||||
outputExists, _ := utils.FileExists(outputPath)
|
||||
if !g.Overwrite && outputExists {
|
||||
|
@ -111,11 +114,15 @@ func (g *PreviewGenerator) generateVideo(encoder *ffmpeg.Encoder) error {
|
|||
Width: 640,
|
||||
OutputPath: chunkOutputPath,
|
||||
}
|
||||
encoder.ScenePreviewVideoChunk(g.Info.VideoFile, options, g.PreviewPreset)
|
||||
if err := encoder.ScenePreviewVideoChunk(g.Info.VideoFile, options, g.PreviewPreset, fallback); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
videoOutputPath := filepath.Join(g.OutputDirectory, g.VideoFilename)
|
||||
encoder.ScenePreviewVideoChunkCombine(g.Info.VideoFile, g.getConcatFilePath(), videoOutputPath)
|
||||
if err := encoder.ScenePreviewVideoChunkCombine(g.Info.VideoFile, g.getConcatFilePath(), videoOutputPath); err != nil {
|
||||
return err
|
||||
}
|
||||
logger.Debug("created video preview: ", videoOutputPath)
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -145,10 +145,12 @@ The FFMPEG and FFProbe binaries should be placed in %s
|
|||
The error was: %s
|
||||
`
|
||||
logger.Fatalf(msg, configDirectory, err)
|
||||
} else {
|
||||
// After download get new paths for ffmpeg and ffprobe
|
||||
ffmpegPath, ffprobePath = ffmpeg.GetPaths(configDirectory)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: is this valid after download?
|
||||
instance.FFMPEGPath = ffmpegPath
|
||||
instance.FFProbePath = ffprobePath
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ const markup = `
|
|||
* Add support for parent/child studios.
|
||||
|
||||
### 🎨 Improvements
|
||||
* Make preview generation more fault-tolerant.
|
||||
* Allow clearing of images and querying on missing images.
|
||||
* Allow free-editing of scene movie number.
|
||||
* Allow adding performers and studios from selectors.
|
||||
|
|
Loading…
Reference in New Issue