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

View File

@ -11,7 +11,7 @@ import (
)
type PreviewGenerator struct {
generator *Generator
Info *GeneratorInfo
VideoFilename string
ImageFilename string
@ -23,7 +23,7 @@ func NewPreviewGenerator(videoFile ffmpeg.VideoFile, videoFilename string, image
if !exists {
return nil, err
}
generator, err := newGenerator(videoFile)
generator, err := newGeneratorInfo(videoFile)
if err != nil {
return nil, err
}
@ -33,16 +33,16 @@ func NewPreviewGenerator(videoFile ffmpeg.VideoFile, videoFilename string, image
}
return &PreviewGenerator{
generator: generator,
VideoFilename: videoFilename,
ImageFilename: imageFilename,
Info: generator,
VideoFilename: videoFilename,
ImageFilename: imageFilename,
OutputDirectory: outputDirectory,
}, nil
}
func (g *PreviewGenerator) Generate() error {
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)
if err := g.generateConcatFile(); err != nil {
@ -65,7 +65,7 @@ func (g *PreviewGenerator) generateConcatFile() error {
defer f.Close()
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)
filename := "preview"+num+".mp4"
_, _ = w.WriteString(fmt.Sprintf("file '%s'\n", filename))
@ -80,8 +80,8 @@ func (g *PreviewGenerator) generateVideo(encoder *ffmpeg.Encoder) error {
return nil
}
stepSize := int(g.generator.VideoFile.Duration / float64(g.generator.ChunkCount))
for i := 0; i < g.generator.ChunkCount; i++ {
stepSize := int(g.Info.VideoFile.Duration / float64(g.Info.ChunkCount))
for i := 0; i < g.Info.ChunkCount; i++ {
time := i * stepSize
num := fmt.Sprintf("%.3d", i)
filename := "preview"+num+".mp4"
@ -92,11 +92,11 @@ func (g *PreviewGenerator) generateVideo(encoder *ffmpeg.Encoder) error {
Width: 640,
OutputPath: chunkOutputPath,
}
encoder.ScenePreviewVideoChunk(g.generator.VideoFile, options)
encoder.ScenePreviewVideoChunk(g.Info.VideoFile, options)
}
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)
return nil
}
@ -110,7 +110,7 @@ func (g *PreviewGenerator) generateImage(encoder *ffmpeg.Encoder) error {
videoPreviewPath := path.Join(g.OutputDirectory, g.VideoFilename)
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
} else {
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 {
go func() {
wg.Done() // TODO
}()
task := GenerateMarkersTask{Scene: scene}
go task.Start(&wg)
}
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()
}