stash/internal/manager/generator_preview.go

175 lines
4.9 KiB
Go
Raw Normal View History

2019-02-10 05:30:54 +00:00
package manager
import (
"bufio"
"fmt"
"os"
"path/filepath"
2019-02-14 23:42:52 +00:00
"github.com/stashapp/stash/pkg/ffmpeg"
"github.com/stashapp/stash/pkg/fsutil"
2019-02-14 23:42:52 +00:00
"github.com/stashapp/stash/pkg/logger"
2019-02-10 05:30:54 +00:00
)
type PreviewGenerator struct {
2019-02-10 09:44:12 +00:00
Info *GeneratorInfo
2019-02-10 05:30:54 +00:00
VideoChecksum string
VideoFilename string
ImageFilename string
2019-02-10 05:30:54 +00:00
OutputDirectory string
GenerateVideo bool
GenerateImage bool
PreviewPreset string
Overwrite bool
2019-02-10 05:30:54 +00:00
}
func NewPreviewGenerator(videoFile ffmpeg.VideoFile, videoChecksum string, videoFilename string, imageFilename string, outputDirectory string, generateVideo bool, generateImage bool, previewPreset string) (*PreviewGenerator, error) {
exists, err := fsutil.FileExists(videoFile.Path)
2019-02-10 05:30:54 +00:00
if !exists {
return nil, err
}
2019-02-10 09:44:12 +00:00
generator, err := newGeneratorInfo(videoFile)
2019-02-10 05:30:54 +00:00
if err != nil {
return nil, err
}
generator.ChunkCount = 12 // 12 segments to the preview
return &PreviewGenerator{
2019-02-10 09:44:12 +00:00
Info: generator,
VideoChecksum: videoChecksum,
2019-02-10 09:44:12 +00:00
VideoFilename: videoFilename,
ImageFilename: imageFilename,
2019-02-10 05:30:54 +00:00
OutputDirectory: outputDirectory,
GenerateVideo: generateVideo,
GenerateImage: generateImage,
PreviewPreset: previewPreset,
2019-02-10 05:30:54 +00:00
}, nil
}
func (g *PreviewGenerator) Generate() error {
2019-02-10 09:44:12 +00:00
logger.Infof("[generator] generating scene preview for %s", g.Info.VideoFile.Path)
if err := g.Info.configure(); err != nil {
return err
}
encoder := instance.FFMPEG
if g.GenerateVideo {
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
}
}
2019-02-10 05:30:54 +00:00
}
if g.GenerateImage {
if err := g.generateImage(&encoder); err != nil {
return err
}
2019-02-10 05:30:54 +00:00
}
return nil
}
func (g *PreviewGenerator) generateConcatFile() error {
f, err := os.Create(g.getConcatFilePath())
if err != nil {
return err
}
defer f.Close()
w := bufio.NewWriter(f)
2019-02-10 09:44:12 +00:00
for i := 0; i < g.Info.ChunkCount; i++ {
2019-02-10 05:30:54 +00:00
num := fmt.Sprintf("%.3d", i)
filename := "preview_" + g.VideoChecksum + "_" + num + ".mp4"
2019-02-10 05:30:54 +00:00
_, _ = w.WriteString(fmt.Sprintf("file '%s'\n", filename))
}
return w.Flush()
}
func (g *PreviewGenerator) generateVideo(encoder *ffmpeg.Encoder, fallback bool) error {
2019-02-10 17:33:18 +00:00
outputPath := filepath.Join(g.OutputDirectory, g.VideoFilename)
outputExists, _ := fsutil.FileExists(outputPath)
if !g.Overwrite && outputExists {
2019-02-10 05:30:54 +00:00
return nil
}
err := g.generateConcatFile()
if err != nil {
return err
}
var tmpFiles []string // a list of tmp files used during the preview generation
tmpFiles = append(tmpFiles, g.getConcatFilePath()) // add concat filename to tmpFiles
defer func() { removeFiles(tmpFiles) }() // remove tmpFiles when done
2019-02-10 05:30:54 +00:00
stepSize, offset := g.Info.getStepSizeAndOffset()
durationSegment := g.Info.ChunkDuration
if durationSegment < 0.75 { // a very short duration can create files without a video stream
durationSegment = 0.75 // use 0.75 in that case
logger.Warnf("[generator] Segment duration (%f) too short.Using 0.75 instead.", g.Info.ChunkDuration)
}
includeAudio := g.Info.Audio
2019-02-10 09:44:12 +00:00
for i := 0; i < g.Info.ChunkCount; i++ {
time := offset + (float64(i) * stepSize)
2019-02-10 05:30:54 +00:00
num := fmt.Sprintf("%.3d", i)
filename := "preview_" + g.VideoChecksum + "_" + num + ".mp4"
2019-02-10 05:30:54 +00:00
chunkOutputPath := instance.Paths.Generated.GetTmpPath(filename)
tmpFiles = append(tmpFiles, chunkOutputPath) // add chunk filename to tmpFiles
2019-02-10 05:30:54 +00:00
options := ffmpeg.ScenePreviewChunkOptions{
StartTime: time,
Duration: durationSegment,
Width: 640,
2019-02-10 05:30:54 +00:00
OutputPath: chunkOutputPath,
Audio: includeAudio,
2019-02-10 05:30:54 +00:00
}
if err := encoder.ScenePreviewVideoChunk(g.Info.VideoFile, options, g.PreviewPreset, fallback); err != nil {
return err
}
2019-02-10 05:30:54 +00:00
}
2019-02-10 17:33:18 +00:00
videoOutputPath := filepath.Join(g.OutputDirectory, g.VideoFilename)
if err := encoder.ScenePreviewVideoChunkCombine(g.Info.VideoFile, g.getConcatFilePath(), videoOutputPath); err != nil {
return err
}
2019-02-10 05:30:54 +00:00
logger.Debug("created video preview: ", videoOutputPath)
return nil
}
func (g *PreviewGenerator) generateImage(encoder *ffmpeg.Encoder) error {
2019-02-10 17:33:18 +00:00
outputPath := filepath.Join(g.OutputDirectory, g.ImageFilename)
outputExists, _ := fsutil.FileExists(outputPath)
if !g.Overwrite && outputExists {
2019-02-10 05:30:54 +00:00
return nil
}
2019-02-10 17:33:18 +00:00
videoPreviewPath := filepath.Join(g.OutputDirectory, g.VideoFilename)
2019-02-10 05:30:54 +00:00
tmpOutputPath := instance.Paths.Generated.GetTmpPath(g.ImageFilename)
2019-02-10 09:44:12 +00:00
if err := encoder.ScenePreviewVideoToImage(g.Info.VideoFile, 640, videoPreviewPath, tmpOutputPath); err != nil {
2019-02-10 05:30:54 +00:00
return err
}
if err := fsutil.SafeMove(tmpOutputPath, outputPath); err != nil {
return err
}
logger.Debug("created video preview image: ", outputPath)
2019-02-10 05:30:54 +00:00
return nil
}
func (g *PreviewGenerator) getConcatFilePath() string {
return instance.Paths.Generated.GetTmpPath(fmt.Sprintf("files_%s.txt", g.VideoChecksum))
}
func removeFiles(list []string) {
for _, f := range list {
if err := os.Remove(f); err != nil {
logger.Warnf("[generator] Delete error: %s", err)
}
}
}