mirror of
https://github.com/truewhile/MeBox.git
synced 2026-09-30 03:36:37 +08:00
188 lines
5.7 KiB
Go
188 lines
5.7 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"path"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
func strmTreeRelativeSource(source, sourceRoot string) string {
|
|
source = normalizeSTRMTreeSource(source)
|
|
root := normalizeRemotePath(sourceRoot)
|
|
if root != "/" && strings.HasPrefix(source, root+"/") {
|
|
return strings.TrimPrefix(strings.TrimPrefix(source, root), "/")
|
|
}
|
|
return strings.TrimPrefix(source, "/")
|
|
}
|
|
|
|
func strmTreeCloudRef(source, sourceRoot string) string {
|
|
source = normalizeSTRMTreeSource(source)
|
|
if strings.HasPrefix(source, "/") {
|
|
return source
|
|
}
|
|
if strings.TrimSpace(sourceRoot) != "" {
|
|
return joinRemotePath(sourceRoot, source)
|
|
}
|
|
return normalizeRemotePath(source)
|
|
}
|
|
|
|
func strmTreeOutputRelativePath(source string) (string, error) {
|
|
return strmTreeOutputRelativePathWithLinkExtension(source, videoExtensions, ".strm", false)
|
|
}
|
|
|
|
func (s *STRMService) strmTreeRecognizedOutputRelativePath(ctx context.Context, source, sourceRoot string) (string, error) {
|
|
source = normalizeSTRMTreeSource(source)
|
|
season, episode := ParseEpisode(source)
|
|
title, year := s.strmTreeCleanQuery(ctx, source)
|
|
if season > 0 || episode > 0 {
|
|
show := s.strmTreeRecognizedSeriesTitle(ctx, source, sourceRoot, title)
|
|
if show == "" {
|
|
return "", errors.New("empty recognized series title")
|
|
}
|
|
if season <= 0 {
|
|
season = 1
|
|
}
|
|
if episode <= 0 {
|
|
return "", errors.New("missing recognized episode number")
|
|
}
|
|
name := fmt.Sprintf("%s S%02dE%02d.strm", show, season, episode)
|
|
return filepath.Join(sanitizeFilename(show), fmt.Sprintf("Season %02d", season), sanitizeFilename(name)), nil
|
|
}
|
|
if title == "" {
|
|
return "", errors.New("empty recognized movie title")
|
|
}
|
|
movie := sanitizeFilename(titleCaseWords(title))
|
|
if movie == "" {
|
|
return "", errors.New("empty recognized movie filename")
|
|
}
|
|
folder := movie
|
|
if year > 0 && !strings.Contains(folder, strconv.Itoa(year)) {
|
|
folder = fmt.Sprintf("%s (%d)", movie, year)
|
|
}
|
|
return filepath.Join(sanitizeFilename(folder), sanitizeFilename(folder)+".strm"), nil
|
|
}
|
|
|
|
func (s *STRMService) strmTreeRecognizedSeriesTitle(ctx context.Context, source, sourceRoot, fallback string) string {
|
|
rel := strmTreeRelativeSource(source, sourceRoot)
|
|
parts := strings.Split(strings.Trim(strings.ReplaceAll(rel, "\\", "/"), "/"), "/")
|
|
if len(parts) >= 2 {
|
|
dir := parts[len(parts)-2]
|
|
if strings.TrimSpace(dir) != "" && !seriesSeasonDirRE.MatchString(dir) {
|
|
if title, _ := s.strmTreeCleanQuery(ctx, dir); title != "" {
|
|
return sanitizeFilename(titleCaseWords(title))
|
|
}
|
|
return sanitizeFilename(strings.TrimSpace(dir))
|
|
}
|
|
}
|
|
if len(parts) >= 3 {
|
|
dir := parts[len(parts)-3]
|
|
if title, _ := s.strmTreeCleanQuery(ctx, dir); title != "" {
|
|
return sanitizeFilename(titleCaseWords(title))
|
|
}
|
|
return sanitizeFilename(strings.TrimSpace(dir))
|
|
}
|
|
if fallback != "" {
|
|
return sanitizeFilename(titleCaseWords(fallback))
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (s *STRMService) strmTreeCleanQuery(ctx context.Context, raw string) (string, int) {
|
|
if s == nil {
|
|
return CleanQuery(raw)
|
|
}
|
|
return CleanQueryWithRecognition(ctx, s.repo, raw)
|
|
}
|
|
|
|
func strmTreeOutputSubtitleLinkRelativePath(source string) (string, error) {
|
|
return strmTreeOutputRelativePathWithLinkExtension(source, strmTreeSubtitleExtensions, ".strm", true)
|
|
}
|
|
|
|
func strmTreeOutputRelativePathWithLinkExtension(source string, allowedExtensions map[string]struct{}, linkExtension string, appendLinkExtension bool) (string, error) {
|
|
parts := strings.Split(strings.Trim(strings.ReplaceAll(source, "\\", "/"), "/"), "/")
|
|
if len(parts) == 0 {
|
|
return "", errors.New("empty source path")
|
|
}
|
|
out := make([]string, 0, len(parts))
|
|
for i, part := range parts {
|
|
part = strings.TrimSpace(part)
|
|
if part == "" || part == "." || part == ".." {
|
|
return "", fmt.Errorf("unsafe path segment %q", part)
|
|
}
|
|
if i == len(parts)-1 {
|
|
ext := strings.ToLower(path.Ext(part))
|
|
if _, ok := allowedExtensions[ext]; !ok {
|
|
return "", fmt.Errorf("unsupported media extension %q", ext)
|
|
}
|
|
if linkExtension != "" {
|
|
if appendLinkExtension {
|
|
part += linkExtension
|
|
} else {
|
|
part = strings.TrimSuffix(part, path.Ext(part)) + linkExtension
|
|
}
|
|
}
|
|
}
|
|
safe := sanitizeFilename(part)
|
|
if safe == "" {
|
|
return "", errors.New("empty sanitized path segment")
|
|
}
|
|
out = append(out, safe)
|
|
}
|
|
return filepath.Join(out...), nil
|
|
}
|
|
|
|
func strmTreeSubtitleMatchesVideo(subtitle strmTreeSource, videos []strmTreeSource) bool {
|
|
subDir, subBase := strmTreeDirAndBase(subtitle.Path)
|
|
if subBase == "" {
|
|
return false
|
|
}
|
|
for _, video := range videos {
|
|
if video.Kind != "" && video.Kind != strmTreeSourceKindVideo {
|
|
continue
|
|
}
|
|
if !strings.EqualFold(subtitle.Provider, video.Provider) {
|
|
continue
|
|
}
|
|
videoDir, videoBase := strmTreeDirAndBase(video.Path)
|
|
if !strings.EqualFold(subDir, videoDir) || videoBase == "" {
|
|
continue
|
|
}
|
|
if strings.EqualFold(subBase, videoBase) || strings.HasPrefix(strings.ToLower(subBase), strings.ToLower(videoBase)+".") {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func strmTreeDirAndBase(source string) (string, string) {
|
|
source = normalizeSTRMTreeSource(source)
|
|
dir := path.Dir(source)
|
|
name := path.Base(source)
|
|
base := strings.TrimSuffix(name, path.Ext(name))
|
|
return strings.ToLower(strings.Trim(dir, "/")), strings.ToLower(base)
|
|
}
|
|
|
|
func strmTreeOutputPrefixPath(prefix string) (string, error) {
|
|
parts := strings.Split(strings.Trim(strings.ReplaceAll(prefix, "\\", "/"), "/"), "/")
|
|
out := make([]string, 0, len(parts))
|
|
for _, part := range parts {
|
|
part = strings.TrimSpace(part)
|
|
if part == "" {
|
|
continue
|
|
}
|
|
if part == "." || part == ".." {
|
|
return "", fmt.Errorf("unsafe output prefix segment %q", part)
|
|
}
|
|
safe := sanitizeFilename(part)
|
|
if safe == "" {
|
|
return "", errors.New("empty sanitized output prefix segment")
|
|
}
|
|
out = append(out, safe)
|
|
}
|
|
return filepath.Join(out...), nil
|
|
}
|