diff --git a/graphql/schema/types/scene.graphql b/graphql/schema/types/scene.graphql index 27fd2b52f..cfad136ee 100644 --- a/graphql/schema/types/scene.graphql +++ b/graphql/schema/types/scene.graphql @@ -15,6 +15,7 @@ type ScenePathsType { stream: String # Resolver webp: String # Resolver vtt: String # Resolver + chapters_vtt: String @deprecated sprite: String # Resolver funscript: String # Resolver interactive_heatmap: String # Resolver diff --git a/internal/api/resolver_model_scene.go b/internal/api/resolver_model_scene.go index 7f0d64d98..df58e8e86 100644 --- a/internal/api/resolver_model_scene.go +++ b/internal/api/resolver_model_scene.go @@ -186,6 +186,7 @@ func (r *sceneResolver) Paths(ctx context.Context, obj *models.Scene) (*ScenePat webpPath := builder.GetStreamPreviewImageURL() vttPath := builder.GetSpriteVTTURL() spritePath := builder.GetSpriteURL() + chaptersVttPath := builder.GetChaptersVTTURL() funscriptPath := builder.GetFunscriptURL() captionBasePath := builder.GetCaptionURL() interactiveHeatmap := builder.GetInteractiveHeatmapURL() @@ -196,6 +197,7 @@ func (r *sceneResolver) Paths(ctx context.Context, obj *models.Scene) (*ScenePat Stream: &streamPath, Webp: &webpPath, Vtt: &vttPath, + ChaptersVtt: &chaptersVttPath, Sprite: &spritePath, Funscript: &funscriptPath, InteractiveHeatmap: &interactiveHeatmap, diff --git a/internal/api/routes_scene.go b/internal/api/routes_scene.go index b2a50c07f..27693a517 100644 --- a/internal/api/routes_scene.go +++ b/internal/api/routes_scene.go @@ -65,6 +65,7 @@ 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("/funscript", rs.Funscript) r.Get("/interactive_heatmap", rs.InteractiveHeatmap) r.Get("/caption", rs.CaptionLang) @@ -257,6 +258,80 @@ func (rs sceneRoutes) Webp(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, filepath) } +func (rs sceneRoutes) getChapterVttTitle(ctx context.Context, marker *models.SceneMarker) (*string, error) { + if marker.Title != "" { + return &marker.Title, nil + } + + var title string + if err := txn.WithTxn(ctx, rs.txnManager, func(ctx context.Context) error { + qb := rs.tagFinder + primaryTag, err := qb.Find(ctx, marker.PrimaryTagID) + if err != nil { + return err + } + + title = primaryTag.Name + + tags, err := qb.FindBySceneMarkerID(ctx, marker.ID) + if err != nil { + return err + } + + for _, t := range tags { + title += ", " + t.Name + } + + return nil + }); err != nil { + return nil, err + } + + return &title, nil +} + +func (rs sceneRoutes) ChapterVtt(w http.ResponseWriter, r *http.Request) { + scene := r.Context().Value(sceneKey).(*models.Scene) + var sceneMarkers []*models.SceneMarker + readTxnErr := txn.WithTxn(r.Context(), rs.txnManager, func(ctx context.Context) error { + var err error + sceneMarkers, err = rs.sceneMarkerFinder.FindBySceneID(ctx, scene.ID) + return err + }) + if errors.Is(readTxnErr, context.Canceled) { + return + } + if readTxnErr != nil { + logger.Warnf("read transaction error on fetch scene markers: %v", readTxnErr) + http.Error(w, readTxnErr.Error(), http.StatusInternalServerError) + return + } + + vttLines := []string{"WEBVTT", ""} + for i, marker := range sceneMarkers { + vttLines = append(vttLines, strconv.Itoa(i+1)) + time := utils.GetVTTTime(marker.Seconds) + vttLines = append(vttLines, time+" --> "+time) + + vttTitle, err := rs.getChapterVttTitle(r.Context(), marker) + if errors.Is(err, context.Canceled) { + return + } + if err != nil { + logger.Warnf("read transaction error on fetch scene marker title: %v", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + vttLines = append(vttLines, *vttTitle) + vttLines = append(vttLines, "") + } + vtt := strings.Join(vttLines, "\n") + + w.Header().Set("Content-Type", "text/vtt") + _, _ = w.Write([]byte(vtt)) +} + func (rs sceneRoutes) Funscript(w http.ResponseWriter, r *http.Request) { s := r.Context().Value(sceneKey).(*models.Scene) funscript := video.GetFunscriptPath(s.Path) diff --git a/internal/api/urlbuilders/scene.go b/internal/api/urlbuilders/scene.go index 732b2746f..014daec95 100644 --- a/internal/api/urlbuilders/scene.go +++ b/internal/api/urlbuilders/scene.go @@ -55,6 +55,10 @@ func (b SceneURLBuilder) GetScreenshotURL(updateTime time.Time) string { return b.BaseURL + "/scene/" + b.SceneID + "/screenshot?" + strconv.FormatInt(updateTime.Unix(), 10) } +func (b SceneURLBuilder) GetChaptersVTTURL() string { + return b.BaseURL + "/scene/" + b.SceneID + "/vtt/chapter" +} + func (b SceneURLBuilder) GetSceneMarkerStreamURL(sceneMarkerID int) string { return b.BaseURL + "/scene/" + b.SceneID + "/scene_marker/" + strconv.Itoa(sceneMarkerID) + "/stream" }