From ecc42e4e241796f0f02a4d9bc06d004fced8e77f Mon Sep 17 00:00:00 2001 From: JoeSmithStarkers <60503487+JoeSmithStarkers@users.noreply.github.com> Date: Mon, 17 Aug 2020 09:21:58 +1000 Subject: [PATCH] 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. --- .gitignore | 4 ++ pkg/ffmpeg/encoder_scene_preview_chunk.go | 58 ++++++++++++++++--- pkg/ffmpeg/encoder_screenshot.go | 2 +- pkg/manager/generator_preview.go | 17 ++++-- pkg/manager/manager.go | 4 +- .../components/Changelog/versions/v030.tsx | 1 + 6 files changed, 72 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index 93e47fa3e..822b8ce50 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/pkg/ffmpeg/encoder_scene_preview_chunk.go b/pkg/ffmpeg/encoder_scene_preview_chunk.go index e68ff5872..3251e8e44 100644 --- a/pkg/ffmpeg/encoder_scene_preview_chunk.go +++ b/pkg/ffmpeg/encoder_scene_preview_chunk.go @@ -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 { diff --git a/pkg/ffmpeg/encoder_screenshot.go b/pkg/ffmpeg/encoder_screenshot.go index bd1926ba3..bd52273c8 100644 --- a/pkg/ffmpeg/encoder_screenshot.go +++ b/pkg/ffmpeg/encoder_screenshot.go @@ -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), diff --git a/pkg/manager/generator_preview.go b/pkg/manager/generator_preview.go index f167e7aa3..f320a0a44 100644 --- a/pkg/manager/generator_preview.go +++ b/pkg/manager/generator_preview.go @@ -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 } diff --git a/pkg/manager/manager.go b/pkg/manager/manager.go index 0d594eba7..cedcc278a 100644 --- a/pkg/manager/manager.go +++ b/pkg/manager/manager.go @@ -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 } diff --git a/ui/v2.5/src/components/Changelog/versions/v030.tsx b/ui/v2.5/src/components/Changelog/versions/v030.tsx index 3a8261d31..c8b02d2bc 100644 --- a/ui/v2.5/src/components/Changelog/versions/v030.tsx +++ b/ui/v2.5/src/components/Changelog/versions/v030.tsx @@ -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.