mirror of
https://github.com/truewhile/MeBox.git
synced 2026-09-29 11:36:36 +08:00
197 lines
5.1 KiB
Go
197 lines
5.1 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/url"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/golang-jwt/jwt/v5"
|
|
"go.uber.org/zap"
|
|
|
|
"github.com/ShukeBta/MediaStationGo/internal/model"
|
|
)
|
|
|
|
func (s *STRMService) strmPlaybackURL(ctx context.Context, media model.Media, baseURL, playbackToken string) string {
|
|
if media.ID == "" {
|
|
return ""
|
|
}
|
|
query := url.Values{}
|
|
token := strings.TrimSpace(playbackToken)
|
|
if token == "" {
|
|
token = s.defaultSTRMPlaybackToken(ctx)
|
|
}
|
|
if token != "" {
|
|
query.Set("token", token)
|
|
}
|
|
return buildAbsoluteSTRMAPIURL(firstNonEmpty(baseURL, PublicServerURL(ctx, s.repo, s.cfg)), "/api/stream/"+url.PathEscape(media.ID), query)
|
|
}
|
|
|
|
func (s *STRMService) defaultSTRMPlaybackToken(ctx context.Context) string {
|
|
if s == nil || s.repo == nil || s.repo.User == nil || s.cfg == nil || strings.TrimSpace(s.cfg.Secrets.JWTSecret) == "" {
|
|
return ""
|
|
}
|
|
admin, err := s.repo.User.FirstAdmin(ctx)
|
|
if err != nil || admin == nil {
|
|
if err != nil && s.log != nil {
|
|
s.log.Warn("generate strm playback token failed", zap.Error(err))
|
|
}
|
|
return ""
|
|
}
|
|
token, err := signSTRMPlaybackToken(admin, s.cfg.Secrets.JWTSecret)
|
|
if err != nil {
|
|
if s.log != nil {
|
|
s.log.Warn("sign strm playback token failed", zap.Error(err))
|
|
}
|
|
return ""
|
|
}
|
|
return token
|
|
}
|
|
|
|
func signSTRMPlaybackToken(u *model.User, secret string) (string, error) {
|
|
if u == nil || strings.TrimSpace(u.ID) == "" || strings.TrimSpace(secret) == "" {
|
|
return "", ErrSTRMURLInvalid
|
|
}
|
|
claims := Claims{
|
|
UserID: u.ID,
|
|
Role: u.Role,
|
|
Tier: u.Tier,
|
|
RegisteredClaims: jwt.RegisteredClaims{
|
|
IssuedAt: jwt.NewNumericDate(time.Now()),
|
|
ExpiresAt: jwt.NewNumericDate(time.Now().Add(EmbyTokenDuration)),
|
|
Issuer: "mediastationgo",
|
|
Subject: u.ID,
|
|
},
|
|
}
|
|
t := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
|
return t.SignedString([]byte(secret))
|
|
}
|
|
|
|
func (s *STRMService) strmRelativePath(lib model.Library, media model.Media) string {
|
|
title := strings.TrimSpace(media.Title)
|
|
if title == "" {
|
|
title = strings.TrimSuffix(filepath.Base(media.Path), filepath.Ext(media.Path))
|
|
}
|
|
if title == "" {
|
|
return ""
|
|
}
|
|
seriesLike := isSeriesLibraryType(lib.Type) || media.SeasonNum > 0 || media.EpisodeNum > 0
|
|
if seriesLike {
|
|
show := inferSeriesNameFromPath(media.Path)
|
|
if show == "" {
|
|
show = title
|
|
}
|
|
season := media.SeasonNum
|
|
episode := media.EpisodeNum
|
|
if season <= 0 || episode <= 0 {
|
|
parsedSeason, parsedEpisode := ParseEpisode(media.Path)
|
|
if season <= 0 {
|
|
season = parsedSeason
|
|
}
|
|
if episode <= 0 {
|
|
episode = parsedEpisode
|
|
}
|
|
}
|
|
if season <= 0 {
|
|
season = 1
|
|
}
|
|
name := strings.TrimSuffix(filepath.Base(media.Path), filepath.Ext(media.Path))
|
|
if episode > 0 {
|
|
name = fmt.Sprintf("%s - S%02dE%02d", show, season, episode)
|
|
} else if strings.TrimSpace(name) == "" {
|
|
name = title
|
|
}
|
|
return filepath.Join(sanitizeFilename(show), fmt.Sprintf("Season %02d", season), sanitizeFilename(name)+".strm")
|
|
}
|
|
folder := title
|
|
if media.Year > 0 && !strings.Contains(folder, strconv.Itoa(media.Year)) {
|
|
folder = fmt.Sprintf("%s (%d)", folder, media.Year)
|
|
}
|
|
safe := sanitizeFilename(folder)
|
|
return filepath.Join(safe, safe+".strm")
|
|
}
|
|
|
|
func (s *STRMService) strmTreeRelativePath(media model.Media) string {
|
|
parts := strmLibraryPathParts(media.Path)
|
|
if len(parts) == 0 {
|
|
return ""
|
|
}
|
|
parts = strmDropCategoryPrefix(parts)
|
|
if len(parts) == 0 {
|
|
return ""
|
|
}
|
|
last := parts[len(parts)-1]
|
|
ext := filepath.Ext(last)
|
|
if ext == "" {
|
|
return ""
|
|
}
|
|
parts[len(parts)-1] = strings.TrimSuffix(last, ext) + ".strm"
|
|
clean := make([]string, 0, len(parts))
|
|
for _, part := range parts {
|
|
if safe := sanitizeFilename(part); safe != "" {
|
|
clean = append(clean, safe)
|
|
}
|
|
}
|
|
if len(clean) == 0 {
|
|
return ""
|
|
}
|
|
return filepath.Join(clean...)
|
|
}
|
|
|
|
func strmDropCategoryPrefix(parts []string) []string {
|
|
if len(parts) == 0 {
|
|
return nil
|
|
}
|
|
for i, part := range parts {
|
|
if strmCanonicalRoot(part) == "" && strmCategoryRoot(part) == "" {
|
|
continue
|
|
}
|
|
next := i + 1
|
|
if strmCanonicalRoot(part) != "" && next < len(parts) && strmCategoryRoot(parts[next]) != "" {
|
|
next++
|
|
}
|
|
if next < len(parts) {
|
|
return append([]string(nil), parts[next:]...)
|
|
}
|
|
}
|
|
return append([]string(nil), parts...)
|
|
}
|
|
|
|
func absolutizeSTRMURL(raw, baseURL string) string {
|
|
raw = strings.TrimSpace(raw)
|
|
if raw == "" || strings.HasPrefix(raw, "//") {
|
|
return raw
|
|
}
|
|
u, err := url.Parse(raw)
|
|
if err == nil && u.IsAbs() {
|
|
return raw
|
|
}
|
|
return buildAbsoluteSTRMAPIURL(baseURL, raw, nil)
|
|
}
|
|
|
|
func buildAbsoluteSTRMAPIURL(baseURL, apiPath string, query url.Values) string {
|
|
apiPath = "/" + strings.TrimLeft(strings.TrimSpace(apiPath), "/")
|
|
if query != nil && len(query) > 0 {
|
|
apiPath += "?" + query.Encode()
|
|
}
|
|
baseURL = strings.TrimRight(strings.TrimSpace(baseURL), "/")
|
|
if baseURL == "" {
|
|
return apiPath
|
|
}
|
|
base, err := url.Parse(baseURL)
|
|
if err != nil || base.Scheme == "" || base.Host == "" {
|
|
return apiPath
|
|
}
|
|
target, err := url.Parse(apiPath)
|
|
if err != nil {
|
|
return apiPath
|
|
}
|
|
base.Path = strings.TrimRight(base.Path, "/") + "/" + strings.TrimLeft(target.Path, "/")
|
|
base.RawQuery = target.RawQuery
|
|
base.Fragment = ""
|
|
return base.String()
|
|
}
|