diff --git a/internal/api/resolver_model_scene.go b/internal/api/resolver_model_scene.go index 47a4d0382..a5c70fadc 100644 --- a/internal/api/resolver_model_scene.go +++ b/internal/api/resolver_model_scene.go @@ -179,13 +179,13 @@ func (r *sceneResolver) Paths(ctx context.Context, obj *models.Scene) (*ScenePat baseURL, _ := ctx.Value(BaseURLCtxKey).(string) config := manager.GetInstance().Config builder := urlbuilders.NewSceneURLBuilder(baseURL, obj.ID) - builder.APIKey = config.GetAPIKey() screenshotPath := builder.GetScreenshotURL(obj.UpdatedAt) previewPath := builder.GetStreamPreviewURL() - streamPath := builder.GetStreamURL().String() + streamPath := builder.GetStreamURL(config.GetAPIKey()).String() webpPath := builder.GetStreamPreviewImageURL() - vttPath := builder.GetSpriteVTTURL() - spritePath := builder.GetSpriteURL() + objHash := obj.GetHash(config.GetVideoFileNamingAlgorithm()) + vttPath := builder.GetSpriteVTTURL(objHash) + spritePath := builder.GetSpriteURL(objHash) chaptersVttPath := builder.GetChaptersVTTURL() funscriptPath := builder.GetFunscriptURL() captionBasePath := builder.GetCaptionURL() @@ -371,9 +371,9 @@ func (r *sceneResolver) SceneStreams(ctx context.Context, obj *models.Scene) ([] baseURL, _ := ctx.Value(BaseURLCtxKey).(string) builder := urlbuilders.NewSceneURLBuilder(baseURL, obj.ID) - builder.APIKey = config.GetAPIKey() + apiKey := config.GetAPIKey() - return manager.GetSceneStreamPaths(obj, builder.GetStreamURL(), config.GetMaxStreamingTranscodeSize()) + return manager.GetSceneStreamPaths(obj, builder.GetStreamURL(apiKey), config.GetMaxStreamingTranscodeSize()) } func (r *sceneResolver) Interactive(ctx context.Context, obj *models.Scene) (bool, error) { diff --git a/internal/api/resolver_query_scene.go b/internal/api/resolver_query_scene.go index f4dea464e..120998d71 100644 --- a/internal/api/resolver_query_scene.go +++ b/internal/api/resolver_query_scene.go @@ -7,7 +7,6 @@ import ( "github.com/stashapp/stash/internal/api/urlbuilders" "github.com/stashapp/stash/internal/manager" - "github.com/stashapp/stash/internal/manager/config" "github.com/stashapp/stash/pkg/models" ) @@ -32,8 +31,11 @@ func (r *queryResolver) SceneStreams(ctx context.Context, id *string) ([]*manage return nil, errors.New("nil scene") } + config := manager.GetInstance().Config + baseURL, _ := ctx.Value(BaseURLCtxKey).(string) builder := urlbuilders.NewSceneURLBuilder(baseURL, scene.ID) + apiKey := config.GetAPIKey() - return manager.GetSceneStreamPaths(scene, builder.GetStreamURL(), config.GetInstance().GetMaxStreamingTranscodeSize()) + return manager.GetSceneStreamPaths(scene, builder.GetStreamURL(apiKey), config.GetMaxStreamingTranscodeSize()) } diff --git a/internal/api/routes_scene.go b/internal/api/routes_scene.go index f4162b396..112d4dbf9 100644 --- a/internal/api/routes_scene.go +++ b/internal/api/routes_scene.go @@ -68,7 +68,9 @@ func (rs sceneRoutes) Routes() chi.Router { r.Get("/screenshot", rs.Screenshot) r.Get("/preview", rs.Preview) r.Get("/webp", rs.Webp) - r.Get("/vtt/chapter", rs.ChapterVtt) + r.Get("/vtt/chapter", rs.VttChapter) + r.Get("/vtt/thumbs", rs.VttThumbs) + r.Get("/vtt/sprite", rs.VttSprite) r.Get("/funscript", rs.Funscript) r.Get("/interactive_heatmap", rs.InteractiveHeatmap) r.Get("/caption", rs.CaptionLang) @@ -77,8 +79,8 @@ func (rs sceneRoutes) Routes() chi.Router { r.Get("/scene_marker/{sceneMarkerId}/preview", rs.SceneMarkerPreview) r.Get("/scene_marker/{sceneMarkerId}/screenshot", rs.SceneMarkerScreenshot) }) - r.With(rs.SceneCtx).Get("/{sceneId}_thumbs.vtt", rs.VttThumbs) - r.With(rs.SceneCtx).Get("/{sceneId}_sprite.jpg", rs.VttSprite) + r.Get("/{sceneHash}_thumbs.vtt", rs.VttThumbs) + r.Get("/{sceneHash}_sprite.jpg", rs.VttSprite) return r } @@ -87,11 +89,9 @@ func (rs sceneRoutes) Routes() chi.Router { func (rs sceneRoutes) StreamDirect(w http.ResponseWriter, r *http.Request) { scene := r.Context().Value(sceneKey).(*models.Scene) + sceneHash := scene.GetHash(config.GetInstance().GetVideoFileNamingAlgorithm()) - fileNamingAlgo := config.GetInstance().GetVideoFileNamingAlgorithm() - hash := scene.GetHash(fileNamingAlgo) - - filepath := manager.GetInstance().Paths.Scene.GetStreamPath(scene.Path, hash) + filepath := manager.GetInstance().Paths.Scene.GetStreamPath(scene.Path, sceneHash) streamRequestCtx := ffmpeg.NewStreamRequestContext(w, r) // #2579 - hijacking and closing the connection here causes video playback to fail in Safari @@ -229,8 +229,7 @@ func (rs sceneRoutes) streamSegment(w http.ResponseWriter, r *http.Request, stre logger.Warnf("[transcode] error parsing query form: %v", err) } - fileNamingAlgo := config.GetInstance().GetVideoFileNamingAlgorithm() - hash := scene.GetHash(fileNamingAlgo) + sceneHash := scene.GetHash(config.GetInstance().GetVideoFileNamingAlgorithm()) segment := chi.URLParam(r, "segment") resolution := r.Form.Get("resolution") @@ -239,7 +238,7 @@ func (rs sceneRoutes) streamSegment(w http.ResponseWriter, r *http.Request, stre StreamType: streamType, VideoFile: f, Resolution: resolution, - Hash: hash, + Hash: sceneHash, Segment: segment, } @@ -258,7 +257,8 @@ func (rs sceneRoutes) Screenshot(w http.ResponseWriter, r *http.Request) { func (rs sceneRoutes) Preview(w http.ResponseWriter, r *http.Request) { scene := r.Context().Value(sceneKey).(*models.Scene) - filepath := manager.GetInstance().Paths.Scene.GetVideoPreviewPath(scene.GetHash(config.GetInstance().GetVideoFileNamingAlgorithm())) + sceneHash := scene.GetHash(config.GetInstance().GetVideoFileNamingAlgorithm()) + filepath := manager.GetInstance().Paths.Scene.GetVideoPreviewPath(sceneHash) serveFileNoCache(w, r, filepath) } @@ -272,7 +272,8 @@ func serveFileNoCache(w http.ResponseWriter, r *http.Request, filepath string) { func (rs sceneRoutes) Webp(w http.ResponseWriter, r *http.Request) { scene := r.Context().Value(sceneKey).(*models.Scene) - filepath := manager.GetInstance().Paths.Scene.GetWebpPreviewPath(scene.GetHash(config.GetInstance().GetVideoFileNamingAlgorithm())) + sceneHash := scene.GetHash(config.GetInstance().GetVideoFileNamingAlgorithm()) + filepath := manager.GetInstance().Paths.Scene.GetWebpPreviewPath(sceneHash) http.ServeFile(w, r, filepath) } @@ -308,7 +309,7 @@ func (rs sceneRoutes) getChapterVttTitle(ctx context.Context, marker *models.Sce return &title, nil } -func (rs sceneRoutes) ChapterVtt(w http.ResponseWriter, r *http.Request) { +func (rs sceneRoutes) VttChapter(w http.ResponseWriter, r *http.Request) { scene := r.Context().Value(sceneKey).(*models.Scene) var sceneMarkers []*models.SceneMarker readTxnErr := txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error { @@ -350,6 +351,32 @@ func (rs sceneRoutes) ChapterVtt(w http.ResponseWriter, r *http.Request) { _, _ = w.Write([]byte(vtt)) } +func (rs sceneRoutes) VttThumbs(w http.ResponseWriter, r *http.Request) { + scene, ok := r.Context().Value(sceneKey).(*models.Scene) + var sceneHash string + if ok && scene != nil { + sceneHash = scene.GetHash(config.GetInstance().GetVideoFileNamingAlgorithm()) + } else { + sceneHash = chi.URLParam(r, "sceneHash") + } + w.Header().Set("Content-Type", "text/vtt") + filepath := manager.GetInstance().Paths.Scene.GetSpriteVttFilePath(sceneHash) + http.ServeFile(w, r, filepath) +} + +func (rs sceneRoutes) VttSprite(w http.ResponseWriter, r *http.Request) { + scene, ok := r.Context().Value(sceneKey).(*models.Scene) + var sceneHash string + if ok && scene != nil { + sceneHash = scene.GetHash(config.GetInstance().GetVideoFileNamingAlgorithm()) + } else { + sceneHash = chi.URLParam(r, "sceneHash") + } + w.Header().Set("Content-Type", "image/jpeg") + filepath := manager.GetInstance().Paths.Scene.GetSpriteImageFilePath(sceneHash) + http.ServeFile(w, r, filepath) +} + func (rs sceneRoutes) Funscript(w http.ResponseWriter, r *http.Request) { s := r.Context().Value(sceneKey).(*models.Scene) funscript := video.GetFunscriptPath(s.Path) @@ -358,8 +385,9 @@ func (rs sceneRoutes) Funscript(w http.ResponseWriter, r *http.Request) { func (rs sceneRoutes) InteractiveHeatmap(w http.ResponseWriter, r *http.Request) { scene := r.Context().Value(sceneKey).(*models.Scene) + sceneHash := scene.GetHash(config.GetInstance().GetVideoFileNamingAlgorithm()) w.Header().Set("Content-Type", "image/png") - filepath := manager.GetInstance().Paths.Scene.GetInteractiveHeatmapPath(scene.GetHash(config.GetInstance().GetVideoFileNamingAlgorithm())) + filepath := manager.GetInstance().Paths.Scene.GetInteractiveHeatmapPath(sceneHash) http.ServeFile(w, r, filepath) } @@ -423,22 +451,9 @@ func (rs sceneRoutes) CaptionLang(w http.ResponseWriter, r *http.Request) { rs.Caption(w, r, l, ext) } -func (rs sceneRoutes) VttThumbs(w http.ResponseWriter, r *http.Request) { - scene := r.Context().Value(sceneKey).(*models.Scene) - w.Header().Set("Content-Type", "text/vtt") - filepath := manager.GetInstance().Paths.Scene.GetSpriteVttFilePath(scene.GetHash(config.GetInstance().GetVideoFileNamingAlgorithm())) - http.ServeFile(w, r, filepath) -} - -func (rs sceneRoutes) VttSprite(w http.ResponseWriter, r *http.Request) { - scene := r.Context().Value(sceneKey).(*models.Scene) - w.Header().Set("Content-Type", "image/jpeg") - filepath := manager.GetInstance().Paths.Scene.GetSpriteImageFilePath(scene.GetHash(config.GetInstance().GetVideoFileNamingAlgorithm())) - http.ServeFile(w, r, filepath) -} - func (rs sceneRoutes) SceneMarkerStream(w http.ResponseWriter, r *http.Request) { scene := r.Context().Value(sceneKey).(*models.Scene) + sceneHash := scene.GetHash(config.GetInstance().GetVideoFileNamingAlgorithm()) sceneMarkerID, _ := strconv.Atoi(chi.URLParam(r, "sceneMarkerId")) var sceneMarker *models.SceneMarker readTxnErr := txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error { @@ -460,12 +475,13 @@ func (rs sceneRoutes) SceneMarkerStream(w http.ResponseWriter, r *http.Request) return } - filepath := manager.GetInstance().Paths.SceneMarkers.GetVideoPreviewPath(scene.GetHash(config.GetInstance().GetVideoFileNamingAlgorithm()), int(sceneMarker.Seconds)) + filepath := manager.GetInstance().Paths.SceneMarkers.GetVideoPreviewPath(sceneHash, int(sceneMarker.Seconds)) http.ServeFile(w, r, filepath) } func (rs sceneRoutes) SceneMarkerPreview(w http.ResponseWriter, r *http.Request) { scene := r.Context().Value(sceneKey).(*models.Scene) + sceneHash := scene.GetHash(config.GetInstance().GetVideoFileNamingAlgorithm()) sceneMarkerID, _ := strconv.Atoi(chi.URLParam(r, "sceneMarkerId")) var sceneMarker *models.SceneMarker readTxnErr := txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error { @@ -487,7 +503,7 @@ func (rs sceneRoutes) SceneMarkerPreview(w http.ResponseWriter, r *http.Request) return } - filepath := manager.GetInstance().Paths.SceneMarkers.GetWebpPreviewPath(scene.GetHash(config.GetInstance().GetVideoFileNamingAlgorithm()), int(sceneMarker.Seconds)) + filepath := manager.GetInstance().Paths.SceneMarkers.GetWebpPreviewPath(sceneHash, int(sceneMarker.Seconds)) // If the image doesn't exist, send the placeholder exists, _ := fsutil.FileExists(filepath) @@ -503,6 +519,7 @@ func (rs sceneRoutes) SceneMarkerPreview(w http.ResponseWriter, r *http.Request) func (rs sceneRoutes) SceneMarkerScreenshot(w http.ResponseWriter, r *http.Request) { scene := r.Context().Value(sceneKey).(*models.Scene) + sceneHash := scene.GetHash(config.GetInstance().GetVideoFileNamingAlgorithm()) sceneMarkerID, _ := strconv.Atoi(chi.URLParam(r, "sceneMarkerId")) var sceneMarker *models.SceneMarker readTxnErr := txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error { @@ -524,7 +541,7 @@ func (rs sceneRoutes) SceneMarkerScreenshot(w http.ResponseWriter, r *http.Reque return } - filepath := manager.GetInstance().Paths.SceneMarkers.GetScreenshotPath(scene.GetHash(config.GetInstance().GetVideoFileNamingAlgorithm()), int(sceneMarker.Seconds)) + filepath := manager.GetInstance().Paths.SceneMarkers.GetScreenshotPath(sceneHash, int(sceneMarker.Seconds)) // If the image doesn't exist, send the placeholder exists, _ := fsutil.FileExists(filepath) @@ -542,28 +559,16 @@ func (rs sceneRoutes) SceneMarkerScreenshot(w http.ResponseWriter, r *http.Reque func (rs sceneRoutes) SceneCtx(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - sceneIdentifierQueryParam := chi.URLParam(r, "sceneId") - sceneID, _ := strconv.Atoi(sceneIdentifierQueryParam) + sceneID, err := strconv.Atoi(chi.URLParam(r, "sceneId")) + if err != nil { + http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) + return + } var scene *models.Scene _ = txn.WithReadTxn(r.Context(), rs.txnManager, func(ctx context.Context) error { qb := rs.sceneFinder - if sceneID == 0 { - var scenes []*models.Scene - // determine checksum/os by the length of the query param - if len(sceneIdentifierQueryParam) == 32 { - scenes, _ = qb.FindByChecksum(ctx, sceneIdentifierQueryParam) - - } else { - scenes, _ = qb.FindByOSHash(ctx, sceneIdentifierQueryParam) - } - - if len(scenes) > 0 { - scene = scenes[0] - } - } else { - scene, _ = qb.Find(ctx, sceneID) - } + scene, _ = qb.Find(ctx, sceneID) if scene != nil { if err := scene.LoadPrimaryFile(ctx, rs.fileFinder); err != nil { diff --git a/internal/api/urlbuilders/scene.go b/internal/api/urlbuilders/scene.go index 014daec95..ae4fce1c0 100644 --- a/internal/api/urlbuilders/scene.go +++ b/internal/api/urlbuilders/scene.go @@ -10,7 +10,6 @@ import ( type SceneURLBuilder struct { BaseURL string SceneID string - APIKey string } func NewSceneURLBuilder(baseURL string, sceneID int) SceneURLBuilder { @@ -20,16 +19,16 @@ func NewSceneURLBuilder(baseURL string, sceneID int) SceneURLBuilder { } } -func (b SceneURLBuilder) GetStreamURL() *url.URL { +func (b SceneURLBuilder) GetStreamURL(apiKey string) *url.URL { u, err := url.Parse(fmt.Sprintf("%s/scene/%s/stream", b.BaseURL, b.SceneID)) if err != nil { // shouldn't happen panic(err) } - if b.APIKey != "" { + if apiKey != "" { v := u.Query() - v.Set("apikey", b.APIKey) + v.Set("apikey", apiKey) u.RawQuery = v.Encode() } return u @@ -43,12 +42,12 @@ func (b SceneURLBuilder) GetStreamPreviewImageURL() string { return b.BaseURL + "/scene/" + b.SceneID + "/webp" } -func (b SceneURLBuilder) GetSpriteVTTURL() string { - return b.BaseURL + "/scene/" + b.SceneID + "_thumbs.vtt" +func (b SceneURLBuilder) GetSpriteVTTURL(checksum string) string { + return b.BaseURL + "/scene/" + checksum + "_thumbs.vtt" } -func (b SceneURLBuilder) GetSpriteURL() string { - return b.BaseURL + "/scene/" + b.SceneID + "_sprite.jpg" +func (b SceneURLBuilder) GetSpriteURL(checksum string) string { + return b.BaseURL + "/scene/" + checksum + "_sprite.jpg" } func (b SceneURLBuilder) GetScreenshotURL(updateTime time.Time) string { diff --git a/ui/v2.5/src/docs/en/Changelog/v0200.md b/ui/v2.5/src/docs/en/Changelog/v0200.md index a2fed7569..e75ffb2d9 100644 --- a/ui/v2.5/src/docs/en/Changelog/v0200.md +++ b/ui/v2.5/src/docs/en/Changelog/v0200.md @@ -17,6 +17,7 @@ * Overhauled and improved HLS streaming. ([#3274](https://github.com/stashapp/stash/pull/3274)) ### 🐛 Bug fixes +* Fixed sprites not being displayed for scenes with numeric-only hashes. ([#3513](https://github.com/stashapp/stash/pull/3513)) * Fixed Save button being disabled when stting Tag image. ([#3509](https://github.com/stashapp/stash/pull/3509)) * Fixed incorrect performer with identical name being matched when scraping from stash-box. ([#3488](https://github.com/stashapp/stash/pull/3488)) * Fixed scene cover not being included when submitting file-less scenes to stash-box. ([#3465](https://github.com/stashapp/stash/pull/3465))