mirror of
https://github.com/stashapp/stash.git
synced 2026-05-09 05:05:29 +02:00
- Removed funscripts, they are for interactive - updated the scanner to correctly create `audio_files` row - Adding Audio to `paths` - Updated sqlite to add AudioFile Need to update mutations next
150 lines
3.6 KiB
Go
150 lines
3.6 KiB
Go
// TODO(audio): update this file
|
|
package api
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
|
|
"github.com/stashapp/stash/internal/manager"
|
|
"github.com/stashapp/stash/pkg/file/video"
|
|
"github.com/stashapp/stash/pkg/logger"
|
|
"github.com/stashapp/stash/pkg/models"
|
|
"github.com/stashapp/stash/pkg/utils"
|
|
)
|
|
|
|
type AudioFinder interface {
|
|
models.AudioGetter
|
|
|
|
FindByChecksum(ctx context.Context, checksum string) ([]*models.Audio, error)
|
|
FindByOSHash(ctx context.Context, oshash string) ([]*models.Audio, error)
|
|
}
|
|
|
|
type audioRoutes struct {
|
|
routes
|
|
audioFinder AudioFinder
|
|
fileGetter models.FileGetter
|
|
captionFinder CaptionFinder
|
|
}
|
|
|
|
func (rs audioRoutes) Routes() chi.Router {
|
|
r := chi.NewRouter()
|
|
|
|
r.Route("/{audioId}", func(r chi.Router) {
|
|
r.Use(rs.AudioCtx)
|
|
|
|
// streaming endpoints
|
|
r.Get("/stream", rs.StreamDirect)
|
|
r.Get("/caption", rs.CaptionLang)
|
|
})
|
|
|
|
return r
|
|
}
|
|
|
|
func (rs audioRoutes) StreamDirect(w http.ResponseWriter, r *http.Request) {
|
|
audio := r.Context().Value(audioKey).(*models.Audio)
|
|
ss := manager.AudioServer{
|
|
TxnManager: rs.txnManager,
|
|
}
|
|
ss.StreamAudioDirect(audio, w, r)
|
|
}
|
|
|
|
func (rs audioRoutes) Caption(w http.ResponseWriter, r *http.Request, lang string, ext string) {
|
|
s := r.Context().Value(audioKey).(*models.Audio)
|
|
|
|
var captions []*models.VideoCaption
|
|
readTxnErr := rs.withReadTxn(r, func(ctx context.Context) error {
|
|
var err error
|
|
primaryFile := s.Files.Primary()
|
|
if primaryFile == nil {
|
|
return nil
|
|
}
|
|
|
|
captions, err = rs.captionFinder.GetCaptions(ctx, primaryFile.Base().ID)
|
|
|
|
return err
|
|
})
|
|
if errors.Is(readTxnErr, context.Canceled) {
|
|
return
|
|
}
|
|
if readTxnErr != nil {
|
|
logger.Warnf("read transaction error on fetch audio captions: %v", readTxnErr)
|
|
http.Error(w, readTxnErr.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
for _, caption := range captions {
|
|
if lang != caption.LanguageCode || ext != caption.CaptionType {
|
|
continue
|
|
}
|
|
|
|
sub, err := video.ReadSubs(caption.Path(s.Path))
|
|
if err != nil {
|
|
logger.Warnf("error while reading subs: %v", err)
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
|
|
err = sub.WriteToWebVTT(&buf)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "text/vtt")
|
|
utils.ServeStaticContent(w, r, buf.Bytes())
|
|
return
|
|
}
|
|
}
|
|
|
|
func (rs audioRoutes) CaptionLang(w http.ResponseWriter, r *http.Request) {
|
|
// serve caption based on lang query param, if provided
|
|
if err := r.ParseForm(); err != nil {
|
|
logger.Warnf("[caption] error parsing query form: %v", err)
|
|
}
|
|
|
|
l := r.Form.Get("lang")
|
|
ext := r.Form.Get("type")
|
|
rs.Caption(w, r, l, ext)
|
|
}
|
|
|
|
func (rs audioRoutes) AudioCtx(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
audioID, err := strconv.Atoi(chi.URLParam(r, "audioId"))
|
|
if err != nil {
|
|
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
var audio *models.Audio
|
|
_ = rs.withReadTxn(r, func(ctx context.Context) error {
|
|
qb := rs.audioFinder
|
|
audio, _ = qb.Find(ctx, audioID)
|
|
|
|
if audio != nil {
|
|
if err := audio.LoadPrimaryFile(ctx, rs.fileGetter); err != nil {
|
|
if !errors.Is(err, context.Canceled) {
|
|
logger.Errorf("error loading primary file for audio %d: %v", audioID, err)
|
|
}
|
|
// set audio to nil so that it doesn't try to use the primary file
|
|
audio = nil
|
|
}
|
|
}
|
|
|
|
return nil
|
|
})
|
|
if audio == nil {
|
|
http.Error(w, http.StatusText(404), 404)
|
|
return
|
|
}
|
|
|
|
ctx := context.WithValue(r.Context(), audioKey, audio)
|
|
next.ServeHTTP(w, r.WithContext(ctx))
|
|
})
|
|
}
|