stash/pkg/manager/task_generate_markers.go

242 lines
7.0 KiB
Go
Raw Normal View History

2019-02-10 09:44:12 +00:00
package manager
import (
"context"
"fmt"
2019-02-10 17:33:18 +00:00
"path/filepath"
2019-02-10 09:44:12 +00:00
"strconv"
"github.com/stashapp/stash/pkg/ffmpeg"
"github.com/stashapp/stash/pkg/logger"
"github.com/stashapp/stash/pkg/models"
"github.com/stashapp/stash/pkg/utils"
2019-02-10 09:44:12 +00:00
)
type GenerateMarkersTask struct {
TxnManager models.TransactionManager
2020-08-06 01:21:14 +00:00
Scene *models.Scene
Marker *models.SceneMarker
Overwrite bool
fileNamingAlgorithm models.HashAlgorithm
ImagePreview bool
Screenshot bool
2019-02-10 09:44:12 +00:00
}
func (t *GenerateMarkersTask) GetDescription() string {
if t.Scene != nil {
return fmt.Sprintf("Generating markers for %s", t.Scene.Path)
} else if t.Marker != nil {
return fmt.Sprintf("Generating marker preview for marker ID %d", t.Marker.ID)
}
return "Generating markers"
}
func (t *GenerateMarkersTask) Start(ctx context.Context) {
if t.Scene != nil {
t.generateSceneMarkers()
}
if t.Marker != nil {
var scene *models.Scene
if err := t.TxnManager.WithReadTxn(context.TODO(), func(r models.ReaderRepository) error {
var err error
scene, err = r.Scene().Find(int(t.Marker.SceneID.Int64))
return err
}); err != nil {
logger.Errorf("error finding scene for marker: %s", err.Error())
return
}
if scene == nil {
logger.Errorf("scene not found for id %d", t.Marker.SceneID.Int64)
return
}
ffprobe := instance.FFProbe
videoFile, err := ffprobe.NewVideoFile(t.Scene.Path, false)
if err != nil {
logger.Errorf("error reading video file: %s", err.Error())
return
}
t.generateMarker(videoFile, scene, t.Marker)
}
}
func (t *GenerateMarkersTask) generateSceneMarkers() {
var sceneMarkers []*models.SceneMarker
if err := t.TxnManager.WithReadTxn(context.TODO(), func(r models.ReaderRepository) error {
var err error
sceneMarkers, err = r.SceneMarker().FindBySceneID(t.Scene.ID)
return err
}); err != nil {
logger.Errorf("error getting scene markers: %s", err.Error())
return
}
2019-02-10 09:44:12 +00:00
if len(sceneMarkers) == 0 {
return
}
ffprobe := instance.FFProbe
videoFile, err := ffprobe.NewVideoFile(t.Scene.Path, false)
2019-02-10 09:44:12 +00:00
if err != nil {
logger.Errorf("error reading video file: %s", err.Error())
return
}
2020-08-06 01:21:14 +00:00
sceneHash := t.Scene.GetHash(t.fileNamingAlgorithm)
2019-02-10 09:44:12 +00:00
// Make the folder for the scenes markers
2020-08-06 01:21:14 +00:00
markersFolder := filepath.Join(instance.Paths.Generated.Markers, sceneHash)
Lint checks phase 2 (#1747) * Log 3 unchecked errors Rather than ignore errors, log them at the WARNING log level. The server has been functioning without these, so assume they are not at the ERROR level. * Log errors in concurrency test If we can't initialize the configuration, treat the test as a failure. * Undo the errcheck on configurations for now. * Handle unchecked errors in pkg/manager * Resolve unchecked errors * Handle DLNA/DMS unchecked errors * Handle error checking in concurrency test Generalize config initialization, so we can initialize a configuration without writing it to disk. Use this in the test case, since otherwise the test fails to write. * Handle the remaining unchecked errors * Heed gosimple in update test * Use one-line if-initializer statements While here, fix a wrong variable capture error. * testing.T doesn't support %w use %v instead which is supported. * Remove unused query builder functions The Int/String criterion handler functions are now generalized. Thus, there's no need to keep these functions around anymore. * Mark filterBuilder.addRecursiveWith nolint The function is useful in the future and no other refactors are looking nice. Keep the function around, but tell the linter to ignore it. * Remove utils.Btoi There are no users of this utility function * Return error on scan failure If we fail to scan the row when looking for the unique checksum index, then report the error upwards. * Fix comments on exported functions * Fix typos * Fix startup error
2021-09-23 07:15:50 +00:00
if err := utils.EnsureDir(markersFolder); err != nil {
logger.Warnf("could not create the markers folder (%v): %v", markersFolder, err)
}
2019-02-10 09:44:12 +00:00
for i, sceneMarker := range sceneMarkers {
index := i + 1
2020-08-06 01:21:14 +00:00
logger.Progressf("[generator] <%s> scene marker %d of %d", sceneHash, index, len(sceneMarkers))
2019-02-10 09:44:12 +00:00
t.generateMarker(videoFile, t.Scene, sceneMarker)
}
}
2019-02-10 09:44:12 +00:00
func (t *GenerateMarkersTask) generateMarker(videoFile *ffmpeg.VideoFile, scene *models.Scene, sceneMarker *models.SceneMarker) {
2020-08-06 01:21:14 +00:00
sceneHash := t.Scene.GetHash(t.fileNamingAlgorithm)
seconds := int(sceneMarker.Seconds)
2020-08-06 01:21:14 +00:00
videoExists := t.videoExists(sceneHash, seconds)
imageExists := !t.ImagePreview || t.imageExists(sceneHash, seconds)
screenshotExists := !t.Screenshot || t.screenshotExists(sceneHash, seconds)
2020-08-06 01:21:14 +00:00
baseFilename := strconv.Itoa(seconds)
options := ffmpeg.SceneMarkerOptions{
ScenePath: scene.Path,
Seconds: seconds,
Width: 640,
Audio: instance.Config.GetPreviewAudio(),
}
encoder := instance.FFMPEG
if t.Overwrite || !videoExists {
2020-08-06 01:21:14 +00:00
videoFilename := baseFilename + ".mp4"
videoPath := instance.Paths.SceneMarkers.GetStreamPath(sceneHash, seconds)
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 {
_ = utils.SafeMove(options.OutputPath, videoPath)
logger.Debug("created marker video: ", videoPath)
2019-02-10 09:44:12 +00:00
}
}
2019-02-10 09:44:12 +00:00
if t.ImagePreview && (t.Overwrite || !imageExists) {
2020-08-06 01:21:14 +00:00
imageFilename := baseFilename + ".webp"
imagePath := instance.Paths.SceneMarkers.GetStreamPreviewImagePath(sceneHash, seconds)
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 {
_ = utils.SafeMove(options.OutputPath, imagePath)
2020-08-06 01:21:14 +00:00
logger.Debug("created marker image: ", imagePath)
2019-02-10 09:44:12 +00:00
}
}
if t.Screenshot && (t.Overwrite || !screenshotExists) {
screenshotFilename := baseFilename + ".jpg"
screenshotPath := instance.Paths.SceneMarkers.GetStreamScreenshotPath(sceneHash, seconds)
screenshotOptions := ffmpeg.ScreenshotOptions{
OutputPath: instance.Paths.Generated.GetTmpPath(screenshotFilename), // tmp output in case the process ends abruptly
Quality: 2,
Width: videoFile.Width,
Time: float64(seconds),
}
if err := encoder.Screenshot(*videoFile, screenshotOptions); err != nil {
logger.Errorf("[generator] failed to generate marker screenshot: %s", err)
} else {
_ = utils.SafeMove(screenshotOptions.OutputPath, screenshotPath)
logger.Debug("created marker screenshot: ", screenshotPath)
}
}
2019-02-10 09:44:12 +00:00
}
func (t *GenerateMarkersTask) markersNeeded() int {
markers := 0
var sceneMarkers []*models.SceneMarker
if err := t.TxnManager.WithReadTxn(context.TODO(), func(r models.ReaderRepository) error {
var err error
sceneMarkers, err = r.SceneMarker().FindBySceneID(t.Scene.ID)
return err
}); err != nil {
logger.Errorf("errror finding scene markers: %s", err.Error())
return 0
}
if len(sceneMarkers) == 0 {
return 0
}
2020-08-06 01:21:14 +00:00
sceneHash := t.Scene.GetHash(t.fileNamingAlgorithm)
for _, sceneMarker := range sceneMarkers {
seconds := int(sceneMarker.Seconds)
2020-08-06 01:21:14 +00:00
if t.Overwrite || !t.markerExists(sceneHash, seconds) {
markers++
}
}
return markers
}
2020-08-06 01:21:14 +00:00
func (t *GenerateMarkersTask) markerExists(sceneChecksum string, seconds int) bool {
if sceneChecksum == "" {
return false
}
videoExists := t.videoExists(sceneChecksum, seconds)
imageExists := !t.ImagePreview || t.imageExists(sceneChecksum, seconds)
screenshotExists := !t.Screenshot || t.screenshotExists(sceneChecksum, seconds)
2020-08-06 01:21:14 +00:00
return videoExists && imageExists && screenshotExists
2020-08-06 01:21:14 +00:00
}
func (t *GenerateMarkersTask) videoExists(sceneChecksum string, seconds int) bool {
if sceneChecksum == "" {
return false
}
videoPath := instance.Paths.SceneMarkers.GetStreamPath(sceneChecksum, seconds)
videoExists, _ := utils.FileExists(videoPath)
return videoExists
}
func (t *GenerateMarkersTask) imageExists(sceneChecksum string, seconds int) bool {
if sceneChecksum == "" {
return false
}
imagePath := instance.Paths.SceneMarkers.GetStreamPreviewImagePath(sceneChecksum, seconds)
imageExists, _ := utils.FileExists(imagePath)
return imageExists
}
func (t *GenerateMarkersTask) screenshotExists(sceneChecksum string, seconds int) bool {
if sceneChecksum == "" {
return false
}
screenshotPath := instance.Paths.SceneMarkers.GetStreamScreenshotPath(sceneChecksum, seconds)
screenshotExists, _ := utils.FileExists(screenshotPath)
return screenshotExists
}