Added scene marker generator

This commit is contained in:
Stash Dev
2019-02-10 01:44:12 -08:00
parent 769e1b3e9a
commit 44216edbb7
5 changed files with 155 additions and 19 deletions

58
ffmpeg/encoder_marker.go Normal file
View File

@@ -0,0 +1,58 @@
package ffmpeg
import (
"fmt"
"strconv"
)
type SceneMarkerOptions struct {
ScenePath string
Seconds int
Width int
OutputPath string
}
func (e *Encoder) SceneMarkerVideo(probeResult VideoFile, options SceneMarkerOptions) error {
args := []string{
"-v", "quiet",
"-ss", strconv.Itoa(options.Seconds),
"-t", "20",
"-i", probeResult.Path,
"-c:v", "libx264",
"-profile:v", "high",
"-level", "4.2",
"-preset", "veryslow",
"-crf", "24",
"-movflags", "+faststart",
"-threads", "4",
"-vf", fmt.Sprintf("scale=%v:-2", options.Width),
"-sws_flags", "lanczos",
"-c:a", "aac",
"-b:a", "64k",
"-strict", "-2",
options.OutputPath,
}
_, err := e.run(probeResult, args)
return err
}
func (e *Encoder) SceneMarkerImage(probeResult VideoFile, options SceneMarkerOptions) error {
args := []string{
"-v", "quiet",
"-ss", strconv.Itoa(options.Seconds),
"-t", "5",
"-i", probeResult.Path,
"-c:v", "libwebp",
"-lossless", "1",
"-q:v", "70",
"-compression_level", "6",
"-preset", "default",
"-loop", "0",
"-threads", "4",
"-vf", fmt.Sprintf("scale=%v:-2,fps=12", options.Width),
"-an",
options.OutputPath,
}
_, err := e.run(probeResult, args)
return err
}

View File

@@ -9,7 +9,7 @@ import (
"strconv" "strconv"
) )
type Generator struct { type GeneratorInfo struct {
ChunkCount int ChunkCount int
FrameRate float64 FrameRate float64
NumberOfFrames int NumberOfFrames int
@@ -18,18 +18,18 @@ type Generator struct {
VideoFile ffmpeg.VideoFile VideoFile ffmpeg.VideoFile
} }
func newGenerator(videoFile ffmpeg.VideoFile) (*Generator, error) { func newGeneratorInfo(videoFile ffmpeg.VideoFile) (*GeneratorInfo, error) {
exists, err := utils.FileExists(videoFile.Path) exists, err := utils.FileExists(videoFile.Path)
if !exists { if !exists {
logger.Errorf("video file not found") logger.Errorf("video file not found")
return nil, err return nil, err
} }
generator := &Generator{VideoFile: videoFile} generator := &GeneratorInfo{VideoFile: videoFile}
return generator, nil return generator, nil
} }
func (g *Generator) configure() error { func (g *GeneratorInfo) configure() error {
videoStream := g.VideoFile.VideoStream videoStream := g.VideoFile.VideoStream
if videoStream == nil { if videoStream == nil {
return fmt.Errorf("missing video stream") return fmt.Errorf("missing video stream")

View File

@@ -11,7 +11,7 @@ import (
) )
type PreviewGenerator struct { type PreviewGenerator struct {
generator *Generator Info *GeneratorInfo
VideoFilename string VideoFilename string
ImageFilename string ImageFilename string
@@ -23,7 +23,7 @@ func NewPreviewGenerator(videoFile ffmpeg.VideoFile, videoFilename string, image
if !exists { if !exists {
return nil, err return nil, err
} }
generator, err := newGenerator(videoFile) generator, err := newGeneratorInfo(videoFile)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -33,7 +33,7 @@ func NewPreviewGenerator(videoFile ffmpeg.VideoFile, videoFilename string, image
} }
return &PreviewGenerator{ return &PreviewGenerator{
generator: generator, Info: generator,
VideoFilename: videoFilename, VideoFilename: videoFilename,
ImageFilename: imageFilename, ImageFilename: imageFilename,
OutputDirectory: outputDirectory, OutputDirectory: outputDirectory,
@@ -42,7 +42,7 @@ func NewPreviewGenerator(videoFile ffmpeg.VideoFile, videoFilename string, image
func (g *PreviewGenerator) Generate() error { func (g *PreviewGenerator) Generate() error {
instance.Paths.Generated.EmptyTmpDir() instance.Paths.Generated.EmptyTmpDir()
logger.Infof("[generator] generating scene preview for %s", g.generator.VideoFile.Path) logger.Infof("[generator] generating scene preview for %s", g.Info.VideoFile.Path)
encoder := ffmpeg.NewEncoder(instance.Paths.FixedPaths.FFMPEG) encoder := ffmpeg.NewEncoder(instance.Paths.FixedPaths.FFMPEG)
if err := g.generateConcatFile(); err != nil { if err := g.generateConcatFile(); err != nil {
@@ -65,7 +65,7 @@ func (g *PreviewGenerator) generateConcatFile() error {
defer f.Close() defer f.Close()
w := bufio.NewWriter(f) w := bufio.NewWriter(f)
for i := 0; i < g.generator.ChunkCount; i++ { for i := 0; i < g.Info.ChunkCount; i++ {
num := fmt.Sprintf("%.3d", i) num := fmt.Sprintf("%.3d", i)
filename := "preview"+num+".mp4" filename := "preview"+num+".mp4"
_, _ = w.WriteString(fmt.Sprintf("file '%s'\n", filename)) _, _ = w.WriteString(fmt.Sprintf("file '%s'\n", filename))
@@ -80,8 +80,8 @@ func (g *PreviewGenerator) generateVideo(encoder *ffmpeg.Encoder) error {
return nil return nil
} }
stepSize := int(g.generator.VideoFile.Duration / float64(g.generator.ChunkCount)) stepSize := int(g.Info.VideoFile.Duration / float64(g.Info.ChunkCount))
for i := 0; i < g.generator.ChunkCount; i++ { for i := 0; i < g.Info.ChunkCount; i++ {
time := i * stepSize time := i * stepSize
num := fmt.Sprintf("%.3d", i) num := fmt.Sprintf("%.3d", i)
filename := "preview"+num+".mp4" filename := "preview"+num+".mp4"
@@ -92,11 +92,11 @@ func (g *PreviewGenerator) generateVideo(encoder *ffmpeg.Encoder) error {
Width: 640, Width: 640,
OutputPath: chunkOutputPath, OutputPath: chunkOutputPath,
} }
encoder.ScenePreviewVideoChunk(g.generator.VideoFile, options) encoder.ScenePreviewVideoChunk(g.Info.VideoFile, options)
} }
videoOutputPath := path.Join(g.OutputDirectory, g.VideoFilename) videoOutputPath := path.Join(g.OutputDirectory, g.VideoFilename)
encoder.ScenePreviewVideoChunkCombine(g.generator.VideoFile, g.getConcatFilePath(), videoOutputPath) encoder.ScenePreviewVideoChunkCombine(g.Info.VideoFile, g.getConcatFilePath(), videoOutputPath)
logger.Debug("created video preview: ", videoOutputPath) logger.Debug("created video preview: ", videoOutputPath)
return nil return nil
} }
@@ -110,7 +110,7 @@ func (g *PreviewGenerator) generateImage(encoder *ffmpeg.Encoder) error {
videoPreviewPath := path.Join(g.OutputDirectory, g.VideoFilename) videoPreviewPath := path.Join(g.OutputDirectory, g.VideoFilename)
tmpOutputPath := instance.Paths.Generated.GetTmpPath(g.ImageFilename) tmpOutputPath := instance.Paths.Generated.GetTmpPath(g.ImageFilename)
if err := encoder.ScenePreviewVideoToImage(g.generator.VideoFile, 640, videoPreviewPath, tmpOutputPath); err != nil { if err := encoder.ScenePreviewVideoToImage(g.Info.VideoFile, 640, videoPreviewPath, tmpOutputPath); err != nil {
return err return err
} else { } else {
if err := os.Rename(tmpOutputPath, outputPath); err != nil { if err := os.Rename(tmpOutputPath, outputPath); err != nil {

View File

@@ -120,9 +120,8 @@ func (s *singleton) Generate(sprites bool, previews bool, markers bool, transcod
} }
if markers { if markers {
go func() { task := GenerateMarkersTask{Scene: scene}
wg.Done() // TODO go task.Start(&wg)
}()
} }
if transcodes { if transcodes {

View File

@@ -0,0 +1,79 @@
package manager
import (
"github.com/stashapp/stash/ffmpeg"
"github.com/stashapp/stash/logger"
"github.com/stashapp/stash/models"
"github.com/stashapp/stash/utils"
"os"
"path"
"strconv"
"sync"
)
type GenerateMarkersTask struct {
Scene models.Scene
}
func (t *GenerateMarkersTask) Start(wg *sync.WaitGroup) {
instance.Paths.Generated.EmptyTmpDir()
qb := models.NewSceneMarkerQueryBuilder()
sceneMarkers, _ := qb.FindBySceneID(t.Scene.ID, nil)
if len(sceneMarkers) == 0 {
wg.Done()
return
}
videoFile, err := ffmpeg.NewVideoFile(instance.Paths.FixedPaths.FFProbe, t.Scene.Path)
if err != nil {
logger.Errorf("error reading video file: %s", err.Error())
wg.Done()
return
}
// Make the folder for the scenes markers
markersFolder := path.Join(instance.Paths.Generated.Markers, t.Scene.Checksum)
_ = utils.EnsureDir(markersFolder)
encoder := ffmpeg.NewEncoder(instance.Paths.FixedPaths.FFMPEG)
for i, sceneMarker := range sceneMarkers {
index := i + 1
logger.Progressf("[generator] <%s> scene marker %d of %d", t.Scene.Checksum, index, len(sceneMarkers))
seconds := int(sceneMarker.Seconds)
baseFilename := strconv.Itoa(seconds)
videoFilename := baseFilename + ".mp4"
imageFilename := baseFilename + ".webp"
videoPath := instance.Paths.SceneMarkers.GetStreamPath(t.Scene.Checksum, seconds)
imagePath := instance.Paths.SceneMarkers.GetStreamPreviewImagePath(t.Scene.Checksum, seconds)
videoExists, _ := utils.FileExists(videoPath)
imageExists, _ := utils.FileExists(imagePath)
options := ffmpeg.SceneMarkerOptions{
ScenePath: t.Scene.Path,
Seconds: seconds,
Width: 640,
}
if !videoExists {
options.OutputPath = instance.Paths.Generated.GetTmpPath(videoFilename) // tmp output in case the process ends abruptly
if err := encoder.SceneMarkerVideo(*videoFile, options); err != nil {
logger.Errorf("[generator] failed to generate marker video: %s", err)
} else {
_ = os.Rename(options.OutputPath, videoPath)
logger.Debug("created marker video: ", videoPath)
}
}
if !imageExists {
options.OutputPath = instance.Paths.Generated.GetTmpPath(imageFilename) // tmp output in case the process ends abruptly
if err := encoder.SceneMarkerImage(*videoFile, options); err != nil {
logger.Errorf("[generator] failed to generate marker image: %s", err)
} else {
_ = os.Rename(options.OutputPath, imagePath)
logger.Debug("created marker image: ", videoPath)
}
}
}
wg.Done()
}