2019-02-10 09:44:12 +00:00
|
|
|
package manager
|
|
|
|
|
|
|
|
import (
|
2019-02-10 17:33:18 +00:00
|
|
|
"path/filepath"
|
2019-02-10 09:44:12 +00:00
|
|
|
"strconv"
|
|
|
|
"sync"
|
2019-11-07 04:35:04 +00:00
|
|
|
|
|
|
|
"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 {
|
2020-08-06 01:21:14 +00:00
|
|
|
Scene *models.Scene
|
|
|
|
Marker *models.SceneMarker
|
|
|
|
Overwrite bool
|
|
|
|
fileNamingAlgorithm models.HashAlgorithm
|
2019-02-10 09:44:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (t *GenerateMarkersTask) Start(wg *sync.WaitGroup) {
|
2019-02-10 20:15:36 +00:00
|
|
|
defer wg.Done()
|
|
|
|
|
2020-07-19 01:59:18 +00:00
|
|
|
if t.Scene != nil {
|
|
|
|
t.generateSceneMarkers()
|
|
|
|
}
|
|
|
|
|
|
|
|
if t.Marker != nil {
|
|
|
|
qb := models.NewSceneQueryBuilder()
|
|
|
|
scene, err := qb.Find(int(t.Marker.SceneID.Int64))
|
|
|
|
if err != nil {
|
|
|
|
logger.Errorf("error finding scene for marker: %s", err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
videoFile, err := ffmpeg.NewVideoFile(instance.FFProbePath, t.Scene.Path)
|
|
|
|
if err != nil {
|
|
|
|
logger.Errorf("error reading video file: %s", err.Error())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
t.generateMarker(videoFile, scene, t.Marker)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *GenerateMarkersTask) generateSceneMarkers() {
|
2019-02-10 09:44:12 +00:00
|
|
|
qb := models.NewSceneMarkerQueryBuilder()
|
|
|
|
sceneMarkers, _ := qb.FindBySceneID(t.Scene.ID, nil)
|
|
|
|
if len(sceneMarkers) == 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-03-23 14:56:59 +00:00
|
|
|
videoFile, err := ffmpeg.NewVideoFile(instance.FFProbePath, t.Scene.Path)
|
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)
|
|
|
|
utils.EnsureDir(markersFolder)
|
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
|
|
|
|
2020-07-19 01:59:18 +00:00
|
|
|
t.generateMarker(videoFile, t.Scene, sceneMarker)
|
|
|
|
}
|
|
|
|
}
|
2019-02-10 09:44:12 +00:00
|
|
|
|
2020-07-19 01:59:18 +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)
|
2020-07-19 01:59:18 +00:00
|
|
|
seconds := int(sceneMarker.Seconds)
|
2020-08-06 01:21:14 +00:00
|
|
|
|
|
|
|
videoExists := t.videoExists(sceneHash, seconds)
|
|
|
|
imageExists := t.imageExists(sceneHash, seconds)
|
|
|
|
|
2020-07-19 01:59:18 +00:00
|
|
|
baseFilename := strconv.Itoa(seconds)
|
|
|
|
|
|
|
|
options := ffmpeg.SceneMarkerOptions{
|
|
|
|
ScenePath: scene.Path,
|
|
|
|
Seconds: seconds,
|
|
|
|
Width: 640,
|
|
|
|
}
|
|
|
|
|
|
|
|
encoder := ffmpeg.NewEncoder(instance.FFMPEGPath)
|
|
|
|
|
|
|
|
if t.Overwrite || !videoExists {
|
2020-08-06 01:21:14 +00:00
|
|
|
videoFilename := baseFilename + ".mp4"
|
|
|
|
videoPath := instance.Paths.SceneMarkers.GetStreamPath(sceneHash, seconds)
|
|
|
|
|
2020-07-19 01:59:18 +00:00
|
|
|
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 {
|
2020-08-21 07:57:07 +00:00
|
|
|
_ = utils.SafeMove(options.OutputPath, videoPath)
|
2020-07-19 01:59:18 +00:00
|
|
|
logger.Debug("created marker video: ", videoPath)
|
2019-02-10 09:44:12 +00:00
|
|
|
}
|
2020-07-19 01:59:18 +00:00
|
|
|
}
|
2019-02-10 09:44:12 +00:00
|
|
|
|
2020-07-19 01:59:18 +00:00
|
|
|
if t.Overwrite || !imageExists {
|
2020-08-06 01:21:14 +00:00
|
|
|
imageFilename := baseFilename + ".webp"
|
|
|
|
imagePath := instance.Paths.SceneMarkers.GetStreamPreviewImagePath(sceneHash, seconds)
|
|
|
|
|
2020-07-19 01:59:18 +00:00
|
|
|
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 {
|
2020-08-21 07:57:07 +00:00
|
|
|
_ = 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
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-11-15 17:23:58 +00:00
|
|
|
|
|
|
|
func (t *GenerateMarkersTask) isMarkerNeeded() int {
|
|
|
|
markers := 0
|
|
|
|
qb := models.NewSceneMarkerQueryBuilder()
|
|
|
|
sceneMarkers, _ := qb.FindBySceneID(t.Scene.ID, nil)
|
|
|
|
if len(sceneMarkers) == 0 {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
2020-08-06 01:21:14 +00:00
|
|
|
sceneHash := t.Scene.GetHash(t.fileNamingAlgorithm)
|
2019-11-15 17:23:58 +00:00
|
|
|
for _, sceneMarker := range sceneMarkers {
|
|
|
|
seconds := int(sceneMarker.Seconds)
|
|
|
|
|
2020-08-06 01:21:14 +00:00
|
|
|
if t.Overwrite || !t.markerExists(sceneHash, seconds) {
|
2019-11-15 17:23:58 +00:00
|
|
|
markers++
|
|
|
|
}
|
|
|
|
}
|
2020-07-19 01:59:18 +00:00
|
|
|
|
2019-11-15 17:23:58 +00:00
|
|
|
return markers
|
|
|
|
}
|
2020-08-06 01:21:14 +00:00
|
|
|
|
|
|
|
func (t *GenerateMarkersTask) markerExists(sceneChecksum string, seconds int) bool {
|
|
|
|
if sceneChecksum == "" {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
videoPath := instance.Paths.SceneMarkers.GetStreamPath(sceneChecksum, seconds)
|
|
|
|
imagePath := instance.Paths.SceneMarkers.GetStreamPreviewImagePath(sceneChecksum, seconds)
|
|
|
|
videoExists, _ := utils.FileExists(videoPath)
|
|
|
|
imageExists, _ := utils.FileExists(imagePath)
|
|
|
|
|
|
|
|
return videoExists && imageExists
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|