2019-10-24 07:18:53 +00:00
|
|
|
package manager
|
|
|
|
|
|
|
|
import (
|
2020-07-23 01:56:08 +00:00
|
|
|
"fmt"
|
2019-10-24 07:18:53 +00:00
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"strconv"
|
|
|
|
|
|
|
|
"github.com/jmoiron/sqlx"
|
2020-01-31 22:19:15 +00:00
|
|
|
|
2020-07-23 01:56:08 +00:00
|
|
|
"github.com/stashapp/stash/pkg/ffmpeg"
|
2019-10-24 07:18:53 +00:00
|
|
|
"github.com/stashapp/stash/pkg/logger"
|
2020-08-06 01:21:14 +00:00
|
|
|
"github.com/stashapp/stash/pkg/manager/config"
|
2019-10-24 07:18:53 +00:00
|
|
|
"github.com/stashapp/stash/pkg/models"
|
|
|
|
"github.com/stashapp/stash/pkg/utils"
|
|
|
|
)
|
|
|
|
|
2020-08-06 01:21:14 +00:00
|
|
|
// DestroyScene deletes a scene and its associated relationships from the
|
|
|
|
// database.
|
2020-01-31 22:19:15 +00:00
|
|
|
func DestroyScene(sceneID int, tx *sqlx.Tx) error {
|
2019-10-24 07:18:53 +00:00
|
|
|
qb := models.NewSceneQueryBuilder()
|
|
|
|
jqb := models.NewJoinsQueryBuilder()
|
2020-01-31 22:19:15 +00:00
|
|
|
|
2019-10-24 07:18:53 +00:00
|
|
|
_, err := qb.Find(sceneID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := jqb.DestroyScenesTags(sceneID, tx); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := jqb.DestroyPerformersScenes(sceneID, tx); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := jqb.DestroyScenesMarkers(sceneID, tx); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := jqb.DestroyScenesGalleries(sceneID, tx); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := qb.Destroy(strconv.Itoa(sceneID), tx); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-01-31 22:19:15 +00:00
|
|
|
|
2019-10-24 07:18:53 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-08-06 01:21:14 +00:00
|
|
|
// DeleteGeneratedSceneFiles deletes generated files for the provided scene.
|
|
|
|
func DeleteGeneratedSceneFiles(scene *models.Scene, fileNamingAlgo models.HashAlgorithm) {
|
|
|
|
sceneHash := scene.GetHash(fileNamingAlgo)
|
|
|
|
|
|
|
|
if sceneHash == "" {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
markersFolder := filepath.Join(GetInstance().Paths.Generated.Markers, sceneHash)
|
2019-10-24 07:18:53 +00:00
|
|
|
|
|
|
|
exists, _ := utils.FileExists(markersFolder)
|
|
|
|
if exists {
|
|
|
|
err := os.RemoveAll(markersFolder)
|
|
|
|
if err != nil {
|
2020-08-06 01:21:14 +00:00
|
|
|
logger.Warnf("Could not delete folder %s: %s", markersFolder, err.Error())
|
2019-10-24 07:18:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-06 01:21:14 +00:00
|
|
|
thumbPath := GetInstance().Paths.Scene.GetThumbnailScreenshotPath(sceneHash)
|
2019-10-24 07:18:53 +00:00
|
|
|
exists, _ = utils.FileExists(thumbPath)
|
|
|
|
if exists {
|
|
|
|
err := os.Remove(thumbPath)
|
|
|
|
if err != nil {
|
|
|
|
logger.Warnf("Could not delete file %s: %s", thumbPath, err.Error())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-06 01:21:14 +00:00
|
|
|
normalPath := GetInstance().Paths.Scene.GetScreenshotPath(sceneHash)
|
2019-10-24 07:18:53 +00:00
|
|
|
exists, _ = utils.FileExists(normalPath)
|
|
|
|
if exists {
|
|
|
|
err := os.Remove(normalPath)
|
|
|
|
if err != nil {
|
|
|
|
logger.Warnf("Could not delete file %s: %s", normalPath, err.Error())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-06 01:21:14 +00:00
|
|
|
streamPreviewPath := GetInstance().Paths.Scene.GetStreamPreviewPath(sceneHash)
|
2019-10-24 07:18:53 +00:00
|
|
|
exists, _ = utils.FileExists(streamPreviewPath)
|
|
|
|
if exists {
|
|
|
|
err := os.Remove(streamPreviewPath)
|
|
|
|
if err != nil {
|
|
|
|
logger.Warnf("Could not delete file %s: %s", streamPreviewPath, err.Error())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-06 01:21:14 +00:00
|
|
|
streamPreviewImagePath := GetInstance().Paths.Scene.GetStreamPreviewImagePath(sceneHash)
|
2019-10-24 07:18:53 +00:00
|
|
|
exists, _ = utils.FileExists(streamPreviewImagePath)
|
|
|
|
if exists {
|
|
|
|
err := os.Remove(streamPreviewImagePath)
|
|
|
|
if err != nil {
|
|
|
|
logger.Warnf("Could not delete file %s: %s", streamPreviewImagePath, err.Error())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-06 01:21:14 +00:00
|
|
|
transcodePath := GetInstance().Paths.Scene.GetTranscodePath(sceneHash)
|
2019-10-24 07:18:53 +00:00
|
|
|
exists, _ = utils.FileExists(transcodePath)
|
|
|
|
if exists {
|
|
|
|
// kill any running streams
|
|
|
|
KillRunningStreams(transcodePath)
|
|
|
|
|
|
|
|
err := os.Remove(transcodePath)
|
|
|
|
if err != nil {
|
|
|
|
logger.Warnf("Could not delete file %s: %s", transcodePath, err.Error())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-06 01:21:14 +00:00
|
|
|
spritePath := GetInstance().Paths.Scene.GetSpriteImageFilePath(sceneHash)
|
2019-10-24 07:18:53 +00:00
|
|
|
exists, _ = utils.FileExists(spritePath)
|
|
|
|
if exists {
|
|
|
|
err := os.Remove(spritePath)
|
|
|
|
if err != nil {
|
|
|
|
logger.Warnf("Could not delete file %s: %s", spritePath, err.Error())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-06 01:21:14 +00:00
|
|
|
vttPath := GetInstance().Paths.Scene.GetSpriteVttFilePath(sceneHash)
|
2019-10-24 07:18:53 +00:00
|
|
|
exists, _ = utils.FileExists(vttPath)
|
|
|
|
if exists {
|
|
|
|
err := os.Remove(vttPath)
|
|
|
|
if err != nil {
|
|
|
|
logger.Warnf("Could not delete file %s: %s", vttPath, err.Error())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-06 01:21:14 +00:00
|
|
|
// DeleteSceneMarkerFiles deletes generated files for a scene marker with the
|
|
|
|
// provided scene and timestamp.
|
|
|
|
func DeleteSceneMarkerFiles(scene *models.Scene, seconds int, fileNamingAlgo models.HashAlgorithm) {
|
|
|
|
videoPath := GetInstance().Paths.SceneMarkers.GetStreamPath(scene.GetHash(fileNamingAlgo), seconds)
|
|
|
|
imagePath := GetInstance().Paths.SceneMarkers.GetStreamPreviewImagePath(scene.GetHash(fileNamingAlgo), seconds)
|
2020-04-09 22:39:41 +00:00
|
|
|
|
|
|
|
exists, _ := utils.FileExists(videoPath)
|
|
|
|
if exists {
|
|
|
|
err := os.Remove(videoPath)
|
|
|
|
if err != nil {
|
|
|
|
logger.Warnf("Could not delete file %s: %s", videoPath, err.Error())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
exists, _ = utils.FileExists(imagePath)
|
|
|
|
if exists {
|
|
|
|
err := os.Remove(imagePath)
|
|
|
|
if err != nil {
|
|
|
|
logger.Warnf("Could not delete file %s: %s", videoPath, err.Error())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-06 01:21:14 +00:00
|
|
|
// DeleteSceneFile deletes the scene video file from the filesystem.
|
2019-10-24 07:18:53 +00:00
|
|
|
func DeleteSceneFile(scene *models.Scene) {
|
|
|
|
// kill any running encoders
|
|
|
|
KillRunningStreams(scene.Path)
|
|
|
|
|
|
|
|
err := os.Remove(scene.Path)
|
|
|
|
if err != nil {
|
|
|
|
logger.Warnf("Could not delete file %s: %s", scene.Path, err.Error())
|
|
|
|
}
|
2020-01-31 22:19:15 +00:00
|
|
|
}
|
2020-07-23 01:56:08 +00:00
|
|
|
|
|
|
|
func GetSceneFileContainer(scene *models.Scene) (ffmpeg.Container, error) {
|
|
|
|
var container ffmpeg.Container
|
|
|
|
if scene.Format.Valid {
|
|
|
|
container = ffmpeg.Container(scene.Format.String)
|
|
|
|
} else { // container isn't in the DB
|
|
|
|
// shouldn't happen, fallback to ffprobe
|
|
|
|
tmpVideoFile, err := ffmpeg.NewVideoFile(GetInstance().FFProbePath, scene.Path)
|
|
|
|
if err != nil {
|
|
|
|
return ffmpeg.Container(""), fmt.Errorf("error reading video file: %s", err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
container = ffmpeg.MatchContainer(tmpVideoFile.Container, scene.Path)
|
|
|
|
}
|
|
|
|
|
|
|
|
return container, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func GetSceneStreamPaths(scene *models.Scene, directStreamURL string) ([]*models.SceneStreamEndpoint, error) {
|
|
|
|
if scene == nil {
|
|
|
|
return nil, fmt.Errorf("nil scene")
|
|
|
|
}
|
|
|
|
|
|
|
|
var ret []*models.SceneStreamEndpoint
|
|
|
|
mimeWebm := ffmpeg.MimeWebm
|
|
|
|
mimeHLS := ffmpeg.MimeHLS
|
|
|
|
mimeMp4 := ffmpeg.MimeMp4
|
|
|
|
|
|
|
|
labelWebm := "webm"
|
|
|
|
labelHLS := "HLS"
|
|
|
|
|
|
|
|
// direct stream should only apply when the audio codec is supported
|
|
|
|
audioCodec := ffmpeg.MissingUnsupported
|
|
|
|
if scene.AudioCodec.Valid {
|
|
|
|
audioCodec = ffmpeg.AudioCodec(scene.AudioCodec.String)
|
|
|
|
}
|
|
|
|
container, err := GetSceneFileContainer(scene)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-08-06 01:21:14 +00:00
|
|
|
if HasTranscode(scene, config.GetVideoFileNamingAlgorithm()) || ffmpeg.IsValidAudioForContainer(audioCodec, container) {
|
2020-07-23 01:56:08 +00:00
|
|
|
label := "Direct stream"
|
|
|
|
ret = append(ret, &models.SceneStreamEndpoint{
|
|
|
|
URL: directStreamURL,
|
|
|
|
MimeType: &mimeMp4,
|
|
|
|
Label: &label,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// only add mkv stream endpoint if the scene container is an mkv already
|
|
|
|
if container == ffmpeg.Matroska {
|
|
|
|
label := "mkv"
|
|
|
|
ret = append(ret, &models.SceneStreamEndpoint{
|
|
|
|
URL: directStreamURL + ".mkv",
|
|
|
|
// set mkv to mp4 to trick the client, since many clients won't try mkv
|
|
|
|
MimeType: &mimeMp4,
|
|
|
|
Label: &label,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
defaultStreams := []*models.SceneStreamEndpoint{
|
|
|
|
{
|
|
|
|
URL: directStreamURL + ".webm",
|
|
|
|
MimeType: &mimeWebm,
|
|
|
|
Label: &labelWebm,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
URL: directStreamURL + ".m3u8",
|
|
|
|
MimeType: &mimeHLS,
|
|
|
|
Label: &labelHLS,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = append(ret, defaultStreams...)
|
|
|
|
|
|
|
|
// TODO - at some point, look at streaming at various resolutions
|
|
|
|
|
|
|
|
return ret, nil
|
|
|
|
}
|
|
|
|
|
2020-08-06 01:21:14 +00:00
|
|
|
// HasTranscode returns true if a transcoded video exists for the provided
|
|
|
|
// scene. It will check using the OSHash of the scene first, then fall back
|
|
|
|
// to the checksum.
|
|
|
|
func HasTranscode(scene *models.Scene, fileNamingAlgo models.HashAlgorithm) bool {
|
2020-07-23 01:56:08 +00:00
|
|
|
if scene == nil {
|
2020-08-06 01:21:14 +00:00
|
|
|
return false
|
2020-07-23 01:56:08 +00:00
|
|
|
}
|
2020-08-06 01:21:14 +00:00
|
|
|
|
|
|
|
sceneHash := scene.GetHash(fileNamingAlgo)
|
|
|
|
if sceneHash == "" {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
transcodePath := instance.Paths.Scene.GetTranscodePath(sceneHash)
|
|
|
|
ret, _ := utils.FileExists(transcodePath)
|
|
|
|
return ret
|
2020-07-23 01:56:08 +00:00
|
|
|
}
|