mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-05 21:06:38 +08:00
refactor cloud metadata helpers by topic
This commit is contained in:
@@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/ShukeBta/MediaStationGo/internal/service/cloud"
|
"github.com/ShukeBta/MediaStationGo/internal/service/cloud"
|
||||||
@@ -93,86 +92,6 @@ func (s *ScannerService) cloudDirectoryMetadata(ctx context.Context, typ, displa
|
|||||||
return meta
|
return meta
|
||||||
}
|
}
|
||||||
|
|
||||||
func cloudFileJSONIsEpisodeMetadata(seriesLike bool, parsedSeason, parsedEpisode int, meta *LocalMetadata) bool {
|
|
||||||
if !seriesLike || meta == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return parsedSeason > 0 ||
|
|
||||||
parsedEpisode > 0 ||
|
|
||||||
meta.SeasonNum > 0 ||
|
|
||||||
meta.EpisodeNum > 0 ||
|
|
||||||
strings.TrimSpace(meta.EpisodeTitle) != ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func mergeCloudEpisodeMetadata(dst, episode *LocalMetadata) *LocalMetadata {
|
|
||||||
if episode == nil {
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
if dst == nil {
|
|
||||||
dst = &LocalMetadata{}
|
|
||||||
}
|
|
||||||
mergeCloudEpisodeIdentity(dst, episode)
|
|
||||||
mergeCloudEpisodeDisplay(dst, episode)
|
|
||||||
mergeCloudEpisodeNumbersAndTaxonomy(dst, episode)
|
|
||||||
dst.NSFW = dst.NSFW || episode.NSFW
|
|
||||||
dst.HasNFO = dst.HasNFO || episode.HasNFO
|
|
||||||
dst.HasArtwork = dst.HasArtwork || episode.HasArtwork
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
func mergeCloudEpisodeIdentity(dst, episode *LocalMetadata) {
|
|
||||||
showTitle := ""
|
|
||||||
if episode.EpisodeTitle != "" && episode.Title != "" && !strings.EqualFold(episode.Title, episode.EpisodeTitle) {
|
|
||||||
showTitle = episode.Title
|
|
||||||
}
|
|
||||||
if showTitle != "" {
|
|
||||||
dst.Title = showTitle
|
|
||||||
}
|
|
||||||
episodeTitle := strings.TrimSpace(episode.EpisodeTitle)
|
|
||||||
if episodeTitle == "" && (episode.SeasonNum > 0 || episode.EpisodeNum > 0) {
|
|
||||||
episodeTitle = strings.TrimSpace(episode.Title)
|
|
||||||
}
|
|
||||||
if episodeTitle != "" && !strings.EqualFold(episodeTitle, strings.TrimSpace(dst.Title)) {
|
|
||||||
dst.EpisodeTitle = episodeTitle
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func mergeCloudEpisodeDisplay(dst, episode *LocalMetadata) {
|
|
||||||
if dst.Year == 0 && episode.Year > 0 {
|
|
||||||
dst.Year = episode.Year
|
|
||||||
}
|
|
||||||
if episode.Overview != "" {
|
|
||||||
dst.Overview = episode.Overview
|
|
||||||
}
|
|
||||||
if episode.Rating > 0 {
|
|
||||||
dst.Rating = episode.Rating
|
|
||||||
}
|
|
||||||
if episode.PosterURL != "" {
|
|
||||||
dst.PosterURL = episode.PosterURL
|
|
||||||
}
|
|
||||||
if episode.BackdropURL != "" {
|
|
||||||
dst.BackdropURL = episode.BackdropURL
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func mergeCloudEpisodeNumbersAndTaxonomy(dst, episode *LocalMetadata) {
|
|
||||||
if episode.SeasonNum > 0 {
|
|
||||||
dst.SeasonNum = episode.SeasonNum
|
|
||||||
}
|
|
||||||
if episode.EpisodeNum > 0 {
|
|
||||||
dst.EpisodeNum = episode.EpisodeNum
|
|
||||||
}
|
|
||||||
if dst.Genres == "" && episode.Genres != "" {
|
|
||||||
dst.Genres = episode.Genres
|
|
||||||
}
|
|
||||||
if dst.Countries == "" && episode.Countries != "" {
|
|
||||||
dst.Countries = episode.Countries
|
|
||||||
}
|
|
||||||
if dst.Languages == "" && episode.Languages != "" {
|
|
||||||
dst.Languages = episode.Languages
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ScannerService) cloudFileMetadata(ctx context.Context, typ, displayPath, fileName string, sidecars cloudSidecarSet, inherited *LocalMetadata, seriesLike bool) *LocalMetadata {
|
func (s *ScannerService) cloudFileMetadata(ctx context.Context, typ, displayPath, fileName string, sidecars cloudSidecarSet, inherited *LocalMetadata, seriesLike bool) *LocalMetadata {
|
||||||
season, episode := ParseEpisode(displayPath)
|
season, episode := ParseEpisode(displayPath)
|
||||||
seriesLike = seriesLike || season > 0 || episode > 0
|
seriesLike = seriesLike || season > 0 || episode > 0
|
||||||
@@ -247,307 +166,3 @@ func (s *ScannerService) readCloudNFO(ctx context.Context, typ, ref string, seri
|
|||||||
meta := metadataFromDoc(&doc, "", seriesLike)
|
meta := metadataFromDoc(&doc, "", seriesLike)
|
||||||
return meta, &doc, nil
|
return meta, &doc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyCloudDirectoryArtwork(typ, displayDir string, sidecars cloudSidecarSet, meta *LocalMetadata) *LocalMetadata {
|
|
||||||
if meta == nil {
|
|
||||||
meta = &LocalMetadata{}
|
|
||||||
}
|
|
||||||
if meta.PosterURL == "" {
|
|
||||||
if ref := firstCloudImageRef(sidecars, cloudPosterNameCandidates(cloudDirectoryArtworkBases(displayDir), "poster", "folder", "cover", "show", "tvshow")...); ref != "" {
|
|
||||||
meta.PosterURL = cloudPlaybackURL(typ, ref)
|
|
||||||
meta.HasArtwork = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if meta.BackdropURL == "" {
|
|
||||||
if ref := firstCloudImageRef(sidecars, cloudBackdropNameCandidates(cloudDirectoryArtworkBases(displayDir), "fanart", "backdrop", "background", "landscape")...); ref != "" {
|
|
||||||
meta.BackdropURL = cloudPlaybackURL(typ, ref)
|
|
||||||
meta.HasArtwork = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return meta
|
|
||||||
}
|
|
||||||
|
|
||||||
func applyCloudFileArtwork(typ string, sidecars cloudSidecarSet, displayPath, fileName, base string, meta *LocalMetadata) *LocalMetadata {
|
|
||||||
if meta == nil {
|
|
||||||
meta = &LocalMetadata{}
|
|
||||||
}
|
|
||||||
bases := cloudFileArtworkBases(displayPath, fileName, base)
|
|
||||||
if meta.PosterURL == "" {
|
|
||||||
if ref := firstCloudImageRef(sidecars, cloudPosterNameCandidates(bases, "poster", "folder", "cover", "movie", "show", "thumb")...); ref != "" {
|
|
||||||
meta.PosterURL = cloudPlaybackURL(typ, ref)
|
|
||||||
meta.HasArtwork = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if meta.BackdropURL == "" {
|
|
||||||
if ref := firstCloudImageRef(sidecars, cloudBackdropNameCandidates(bases, "fanart", "backdrop", "background", "landscape")...); ref != "" {
|
|
||||||
meta.BackdropURL = cloudPlaybackURL(typ, ref)
|
|
||||||
meta.HasArtwork = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return meta
|
|
||||||
}
|
|
||||||
|
|
||||||
func cloudShowNFOCandidates(displayDir string) []string {
|
|
||||||
names := []string{"tvshow.nfo", "series.nfo", "show.nfo", "movie.nfo"}
|
|
||||||
base := strings.TrimSpace(pathBaseSlash(displayDir))
|
|
||||||
if base != "" {
|
|
||||||
names = append(names, base+".nfo")
|
|
||||||
}
|
|
||||||
return names
|
|
||||||
}
|
|
||||||
|
|
||||||
func cloudDirectoryJSONCandidates(displayDir string) []string {
|
|
||||||
names := []string{"movie.json", "metadata.json", "tvshow.json", "series.json", "show.json"}
|
|
||||||
base := strings.TrimSpace(pathBaseSlash(displayDir))
|
|
||||||
if base != "" {
|
|
||||||
names = append(names, base+".json", base+"-metadata.json", base+".metadata.json", base+"-mediainfo.json", base+".mediainfo.json")
|
|
||||||
}
|
|
||||||
return names
|
|
||||||
}
|
|
||||||
|
|
||||||
func cloudFileJSONCandidates(fileName, base string) []string {
|
|
||||||
if base == "" {
|
|
||||||
base = strings.ToLower(strings.TrimSpace(strings.TrimSuffix(fileName, filepath.Ext(fileName))))
|
|
||||||
}
|
|
||||||
cleanBases := cloudCleanArtworkBases(fileName)
|
|
||||||
bases := uniqueCloudArtworkNames(append([]string{base}, cleanBases...)...)
|
|
||||||
out := make([]string, 0, len(bases)*5+2)
|
|
||||||
for _, value := range bases {
|
|
||||||
out = append(out, value+".json", value+"-metadata.json", value+".metadata.json", value+"-mediainfo.json", value+".mediainfo.json")
|
|
||||||
}
|
|
||||||
return append(out, "movie.json", "metadata.json")
|
|
||||||
}
|
|
||||||
|
|
||||||
func cloudJSONRefByName(sidecars cloudSidecarSet, name string) string {
|
|
||||||
name = normalizeCloudArtworkName(name)
|
|
||||||
if name == "" || isHTTPURL(name) {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
if ref := sidecars.jsonByName[strings.ToLower(name)]; ref != "" {
|
|
||||||
return ref
|
|
||||||
}
|
|
||||||
base := strings.TrimSuffix(name, filepath.Ext(name))
|
|
||||||
return sidecars.jsonByBase[strings.ToLower(base)]
|
|
||||||
}
|
|
||||||
|
|
||||||
func cloudFileArtworkBases(displayPath, fileName, base string) []string {
|
|
||||||
return uniqueCloudArtworkNames(append(
|
|
||||||
[]string{base},
|
|
||||||
append(cloudCleanArtworkBases(fileName), cloudDirectoryArtworkBases(pathDirSlash(displayPath))...)...,
|
|
||||||
)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func cloudDirectoryArtworkBases(displayDir string) []string {
|
|
||||||
base := pathBaseSlash(displayDir)
|
|
||||||
return uniqueCloudArtworkNames(append([]string{base}, cloudCleanArtworkBases(base)...)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func cloudCleanArtworkBases(value string) []string {
|
|
||||||
title, year := CleanQuery(value)
|
|
||||||
title = strings.TrimSpace(title)
|
|
||||||
if title == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
out := []string{title}
|
|
||||||
if year > 0 {
|
|
||||||
yearText := strconv.Itoa(year)
|
|
||||||
out = append(out,
|
|
||||||
title+" ("+yearText+")",
|
|
||||||
title+"."+yearText,
|
|
||||||
title+" "+yearText,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
func cloudPosterNameCandidates(bases []string, fallback ...string) []string {
|
|
||||||
out := make([]string, 0, len(bases)*7+len(fallback))
|
|
||||||
for _, base := range bases {
|
|
||||||
base = strings.TrimSpace(base)
|
|
||||||
if base == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
out = append(out, base, base+"-poster", base+".poster", base+"-cover", base+".cover", base+"-thumb", base+".thumb")
|
|
||||||
}
|
|
||||||
return append(out, fallback...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func cloudBackdropNameCandidates(bases []string, fallback ...string) []string {
|
|
||||||
out := make([]string, 0, len(bases)*6+len(fallback))
|
|
||||||
for _, base := range bases {
|
|
||||||
base = strings.TrimSpace(base)
|
|
||||||
if base == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
out = append(out, base+"-fanart", base+".fanart", base+"-backdrop", base+".backdrop", base+"-background", base+".background")
|
|
||||||
}
|
|
||||||
return append(out, fallback...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func uniqueCloudArtworkNames(values ...string) []string {
|
|
||||||
out := make([]string, 0, len(values))
|
|
||||||
seen := map[string]struct{}{}
|
|
||||||
for _, value := range values {
|
|
||||||
value = strings.TrimSpace(value)
|
|
||||||
if value == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
key := strings.ToLower(value)
|
|
||||||
if _, ok := seen[key]; ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
seen[key] = struct{}{}
|
|
||||||
out = append(out, value)
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
func mergeCloudMetadata(dst, src *LocalMetadata) *LocalMetadata {
|
|
||||||
if src == nil {
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
if dst == nil {
|
|
||||||
return cloneLocalMetadata(src)
|
|
||||||
}
|
|
||||||
if src.Title != "" {
|
|
||||||
dst.Title = src.Title
|
|
||||||
}
|
|
||||||
if src.OriginalName != "" {
|
|
||||||
dst.OriginalName = src.OriginalName
|
|
||||||
}
|
|
||||||
if src.EpisodeTitle != "" {
|
|
||||||
dst.EpisodeTitle = src.EpisodeTitle
|
|
||||||
}
|
|
||||||
if src.AdultCode != "" {
|
|
||||||
dst.AdultCode = src.AdultCode
|
|
||||||
}
|
|
||||||
if src.Year > 0 {
|
|
||||||
dst.Year = src.Year
|
|
||||||
}
|
|
||||||
if src.Overview != "" {
|
|
||||||
dst.Overview = src.Overview
|
|
||||||
}
|
|
||||||
if src.Rating > 0 {
|
|
||||||
dst.Rating = src.Rating
|
|
||||||
}
|
|
||||||
if src.PosterURL != "" {
|
|
||||||
dst.PosterURL = src.PosterURL
|
|
||||||
}
|
|
||||||
if src.BackdropURL != "" {
|
|
||||||
dst.BackdropURL = src.BackdropURL
|
|
||||||
}
|
|
||||||
if src.TMDbID > 0 {
|
|
||||||
dst.TMDbID = src.TMDbID
|
|
||||||
}
|
|
||||||
if src.BangumiID > 0 {
|
|
||||||
dst.BangumiID = src.BangumiID
|
|
||||||
}
|
|
||||||
if src.DoubanID != "" {
|
|
||||||
dst.DoubanID = src.DoubanID
|
|
||||||
}
|
|
||||||
if src.TheTVDBID != "" {
|
|
||||||
dst.TheTVDBID = src.TheTVDBID
|
|
||||||
}
|
|
||||||
if src.SeasonNum > 0 || src.EpisodeNum > 0 {
|
|
||||||
dst.SeasonNum = src.SeasonNum
|
|
||||||
}
|
|
||||||
if src.EpisodeNum > 0 {
|
|
||||||
dst.EpisodeNum = src.EpisodeNum
|
|
||||||
}
|
|
||||||
if src.Genres != "" {
|
|
||||||
dst.Genres = src.Genres
|
|
||||||
}
|
|
||||||
if src.Countries != "" {
|
|
||||||
dst.Countries = src.Countries
|
|
||||||
}
|
|
||||||
if src.Languages != "" {
|
|
||||||
dst.Languages = src.Languages
|
|
||||||
}
|
|
||||||
dst.NSFW = dst.NSFW || src.NSFW
|
|
||||||
dst.HasNFO = dst.HasNFO || src.HasNFO
|
|
||||||
dst.HasArtwork = dst.HasArtwork || src.HasArtwork
|
|
||||||
dst.PathHint = dst.PathHint || src.PathHint
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
func mergeCloudPathHintMetadata(dst, hint *LocalMetadata) *LocalMetadata {
|
|
||||||
if hint == nil {
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
if dst == nil || !dst.HasNFO {
|
|
||||||
return mergeCloudMetadata(dst, hint)
|
|
||||||
}
|
|
||||||
if dst.Title == "" {
|
|
||||||
dst.Title = hint.Title
|
|
||||||
}
|
|
||||||
if dst.OriginalName == "" {
|
|
||||||
dst.OriginalName = hint.OriginalName
|
|
||||||
}
|
|
||||||
if dst.Year == 0 {
|
|
||||||
dst.Year = hint.Year
|
|
||||||
}
|
|
||||||
if dst.TMDbID == 0 {
|
|
||||||
dst.TMDbID = hint.TMDbID
|
|
||||||
}
|
|
||||||
if dst.BangumiID == 0 {
|
|
||||||
dst.BangumiID = hint.BangumiID
|
|
||||||
}
|
|
||||||
if dst.DoubanID == "" {
|
|
||||||
dst.DoubanID = hint.DoubanID
|
|
||||||
}
|
|
||||||
if dst.TheTVDBID == "" {
|
|
||||||
dst.TheTVDBID = hint.TheTVDBID
|
|
||||||
}
|
|
||||||
dst.PathHint = dst.PathHint || hint.PathHint
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
func cloneLocalMetadata(src *LocalMetadata) *LocalMetadata {
|
|
||||||
if src == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
cp := *src
|
|
||||||
return &cp
|
|
||||||
}
|
|
||||||
|
|
||||||
func cloudMetadataUseful(meta *LocalMetadata) bool {
|
|
||||||
return meta != nil && (meta.HasNFO || meta.HasArtwork || localHasDescriptiveMetadata(meta))
|
|
||||||
}
|
|
||||||
|
|
||||||
func cloudPlaybackURL(typ, ref string) string {
|
|
||||||
return CloudArtworkURL(typ, ref)
|
|
||||||
}
|
|
||||||
|
|
||||||
func joinCloudDisplayPath(parent, child string) string {
|
|
||||||
parent = strings.Trim(strings.ReplaceAll(strings.TrimSpace(parent), "\\", "/"), "/")
|
|
||||||
child = strings.Trim(strings.ReplaceAll(strings.TrimSpace(child), "\\", "/"), "/")
|
|
||||||
switch {
|
|
||||||
case parent == "":
|
|
||||||
return child
|
|
||||||
case child == "":
|
|
||||||
return parent
|
|
||||||
default:
|
|
||||||
return parent + "/" + child
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func pathBaseSlash(value string) string {
|
|
||||||
value = strings.Trim(strings.ReplaceAll(strings.TrimSpace(value), "\\", "/"), "/")
|
|
||||||
if value == "" {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
parts := strings.Split(value, "/")
|
|
||||||
return parts[len(parts)-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
func pathDirSlash(value string) string {
|
|
||||||
value = strings.Trim(strings.ReplaceAll(strings.TrimSpace(value), "\\", "/"), "/")
|
|
||||||
if value == "" {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
idx := strings.LastIndex(value, "/")
|
|
||||||
if idx < 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return value[:idx]
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -24,6 +24,45 @@ func applyCloudNFOArtwork(typ string, sidecars cloudSidecarSet, meta *LocalMetad
|
|||||||
return meta
|
return meta
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func applyCloudDirectoryArtwork(typ, displayDir string, sidecars cloudSidecarSet, meta *LocalMetadata) *LocalMetadata {
|
||||||
|
if meta == nil {
|
||||||
|
meta = &LocalMetadata{}
|
||||||
|
}
|
||||||
|
if meta.PosterURL == "" {
|
||||||
|
if ref := firstCloudImageRef(sidecars, cloudPosterNameCandidates(cloudDirectoryArtworkBases(displayDir), "poster", "folder", "cover", "show", "tvshow")...); ref != "" {
|
||||||
|
meta.PosterURL = cloudPlaybackURL(typ, ref)
|
||||||
|
meta.HasArtwork = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if meta.BackdropURL == "" {
|
||||||
|
if ref := firstCloudImageRef(sidecars, cloudBackdropNameCandidates(cloudDirectoryArtworkBases(displayDir), "fanart", "backdrop", "background", "landscape")...); ref != "" {
|
||||||
|
meta.BackdropURL = cloudPlaybackURL(typ, ref)
|
||||||
|
meta.HasArtwork = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return meta
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyCloudFileArtwork(typ string, sidecars cloudSidecarSet, displayPath, fileName, base string, meta *LocalMetadata) *LocalMetadata {
|
||||||
|
if meta == nil {
|
||||||
|
meta = &LocalMetadata{}
|
||||||
|
}
|
||||||
|
bases := cloudFileArtworkBases(displayPath, fileName, base)
|
||||||
|
if meta.PosterURL == "" {
|
||||||
|
if ref := firstCloudImageRef(sidecars, cloudPosterNameCandidates(bases, "poster", "folder", "cover", "movie", "show", "thumb")...); ref != "" {
|
||||||
|
meta.PosterURL = cloudPlaybackURL(typ, ref)
|
||||||
|
meta.HasArtwork = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if meta.BackdropURL == "" {
|
||||||
|
if ref := firstCloudImageRef(sidecars, cloudBackdropNameCandidates(bases, "fanart", "backdrop", "background", "landscape")...); ref != "" {
|
||||||
|
meta.BackdropURL = cloudPlaybackURL(typ, ref)
|
||||||
|
meta.HasArtwork = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return meta
|
||||||
|
}
|
||||||
|
|
||||||
func firstCloudImageRef(sidecars cloudSidecarSet, names ...string) string {
|
func firstCloudImageRef(sidecars cloudSidecarSet, names ...string) string {
|
||||||
for _, name := range names {
|
for _, name := range names {
|
||||||
if ref := cloudImageRefByName(sidecars, name); ref != "" {
|
if ref := cloudImageRefByName(sidecars, name); ref != "" {
|
||||||
|
|||||||
@@ -0,0 +1,122 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func cloudShowNFOCandidates(displayDir string) []string {
|
||||||
|
names := []string{"tvshow.nfo", "series.nfo", "show.nfo", "movie.nfo"}
|
||||||
|
base := strings.TrimSpace(pathBaseSlash(displayDir))
|
||||||
|
if base != "" {
|
||||||
|
names = append(names, base+".nfo")
|
||||||
|
}
|
||||||
|
return names
|
||||||
|
}
|
||||||
|
|
||||||
|
func cloudDirectoryJSONCandidates(displayDir string) []string {
|
||||||
|
names := []string{"movie.json", "metadata.json", "tvshow.json", "series.json", "show.json"}
|
||||||
|
base := strings.TrimSpace(pathBaseSlash(displayDir))
|
||||||
|
if base != "" {
|
||||||
|
names = append(names, base+".json", base+"-metadata.json", base+".metadata.json", base+"-mediainfo.json", base+".mediainfo.json")
|
||||||
|
}
|
||||||
|
return names
|
||||||
|
}
|
||||||
|
|
||||||
|
func cloudFileJSONCandidates(fileName, base string) []string {
|
||||||
|
if base == "" {
|
||||||
|
base = strings.ToLower(strings.TrimSpace(strings.TrimSuffix(fileName, filepath.Ext(fileName))))
|
||||||
|
}
|
||||||
|
cleanBases := cloudCleanArtworkBases(fileName)
|
||||||
|
bases := uniqueCloudArtworkNames(append([]string{base}, cleanBases...)...)
|
||||||
|
out := make([]string, 0, len(bases)*5+2)
|
||||||
|
for _, value := range bases {
|
||||||
|
out = append(out, value+".json", value+"-metadata.json", value+".metadata.json", value+"-mediainfo.json", value+".mediainfo.json")
|
||||||
|
}
|
||||||
|
return append(out, "movie.json", "metadata.json")
|
||||||
|
}
|
||||||
|
|
||||||
|
func cloudJSONRefByName(sidecars cloudSidecarSet, name string) string {
|
||||||
|
name = normalizeCloudArtworkName(name)
|
||||||
|
if name == "" || isHTTPURL(name) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if ref := sidecars.jsonByName[strings.ToLower(name)]; ref != "" {
|
||||||
|
return ref
|
||||||
|
}
|
||||||
|
base := strings.TrimSuffix(name, filepath.Ext(name))
|
||||||
|
return sidecars.jsonByBase[strings.ToLower(base)]
|
||||||
|
}
|
||||||
|
|
||||||
|
func cloudFileArtworkBases(displayPath, fileName, base string) []string {
|
||||||
|
return uniqueCloudArtworkNames(append(
|
||||||
|
[]string{base},
|
||||||
|
append(cloudCleanArtworkBases(fileName), cloudDirectoryArtworkBases(pathDirSlash(displayPath))...)...,
|
||||||
|
)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cloudDirectoryArtworkBases(displayDir string) []string {
|
||||||
|
base := pathBaseSlash(displayDir)
|
||||||
|
return uniqueCloudArtworkNames(append([]string{base}, cloudCleanArtworkBases(base)...)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cloudCleanArtworkBases(value string) []string {
|
||||||
|
title, year := CleanQuery(value)
|
||||||
|
title = strings.TrimSpace(title)
|
||||||
|
if title == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := []string{title}
|
||||||
|
if year > 0 {
|
||||||
|
yearText := strconv.Itoa(year)
|
||||||
|
out = append(out,
|
||||||
|
title+" ("+yearText+")",
|
||||||
|
title+"."+yearText,
|
||||||
|
title+" "+yearText,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func cloudPosterNameCandidates(bases []string, fallback ...string) []string {
|
||||||
|
out := make([]string, 0, len(bases)*7+len(fallback))
|
||||||
|
for _, base := range bases {
|
||||||
|
base = strings.TrimSpace(base)
|
||||||
|
if base == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out = append(out, base, base+"-poster", base+".poster", base+"-cover", base+".cover", base+"-thumb", base+".thumb")
|
||||||
|
}
|
||||||
|
return append(out, fallback...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cloudBackdropNameCandidates(bases []string, fallback ...string) []string {
|
||||||
|
out := make([]string, 0, len(bases)*6+len(fallback))
|
||||||
|
for _, base := range bases {
|
||||||
|
base = strings.TrimSpace(base)
|
||||||
|
if base == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out = append(out, base+"-fanart", base+".fanart", base+"-backdrop", base+".backdrop", base+"-background", base+".background")
|
||||||
|
}
|
||||||
|
return append(out, fallback...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func uniqueCloudArtworkNames(values ...string) []string {
|
||||||
|
out := make([]string, 0, len(values))
|
||||||
|
seen := map[string]struct{}{}
|
||||||
|
for _, value := range values {
|
||||||
|
value = strings.TrimSpace(value)
|
||||||
|
if value == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
key := strings.ToLower(value)
|
||||||
|
if _, ok := seen[key]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[key] = struct{}{}
|
||||||
|
out = append(out, value)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
func cloudFileJSONIsEpisodeMetadata(seriesLike bool, parsedSeason, parsedEpisode int, meta *LocalMetadata) bool {
|
||||||
|
if !seriesLike || meta == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return parsedSeason > 0 ||
|
||||||
|
parsedEpisode > 0 ||
|
||||||
|
meta.SeasonNum > 0 ||
|
||||||
|
meta.EpisodeNum > 0 ||
|
||||||
|
strings.TrimSpace(meta.EpisodeTitle) != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeCloudEpisodeMetadata(dst, episode *LocalMetadata) *LocalMetadata {
|
||||||
|
if episode == nil {
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
if dst == nil {
|
||||||
|
dst = &LocalMetadata{}
|
||||||
|
}
|
||||||
|
mergeCloudEpisodeIdentity(dst, episode)
|
||||||
|
mergeCloudEpisodeDisplay(dst, episode)
|
||||||
|
mergeCloudEpisodeNumbersAndTaxonomy(dst, episode)
|
||||||
|
dst.NSFW = dst.NSFW || episode.NSFW
|
||||||
|
dst.HasNFO = dst.HasNFO || episode.HasNFO
|
||||||
|
dst.HasArtwork = dst.HasArtwork || episode.HasArtwork
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeCloudEpisodeIdentity(dst, episode *LocalMetadata) {
|
||||||
|
showTitle := ""
|
||||||
|
if episode.EpisodeTitle != "" && episode.Title != "" && !strings.EqualFold(episode.Title, episode.EpisodeTitle) {
|
||||||
|
showTitle = episode.Title
|
||||||
|
}
|
||||||
|
if showTitle != "" {
|
||||||
|
dst.Title = showTitle
|
||||||
|
}
|
||||||
|
episodeTitle := strings.TrimSpace(episode.EpisodeTitle)
|
||||||
|
if episodeTitle == "" && (episode.SeasonNum > 0 || episode.EpisodeNum > 0) {
|
||||||
|
episodeTitle = strings.TrimSpace(episode.Title)
|
||||||
|
}
|
||||||
|
if episodeTitle != "" && !strings.EqualFold(episodeTitle, strings.TrimSpace(dst.Title)) {
|
||||||
|
dst.EpisodeTitle = episodeTitle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeCloudEpisodeDisplay(dst, episode *LocalMetadata) {
|
||||||
|
if dst.Year == 0 && episode.Year > 0 {
|
||||||
|
dst.Year = episode.Year
|
||||||
|
}
|
||||||
|
if episode.Overview != "" {
|
||||||
|
dst.Overview = episode.Overview
|
||||||
|
}
|
||||||
|
if episode.Rating > 0 {
|
||||||
|
dst.Rating = episode.Rating
|
||||||
|
}
|
||||||
|
if episode.PosterURL != "" {
|
||||||
|
dst.PosterURL = episode.PosterURL
|
||||||
|
}
|
||||||
|
if episode.BackdropURL != "" {
|
||||||
|
dst.BackdropURL = episode.BackdropURL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeCloudEpisodeNumbersAndTaxonomy(dst, episode *LocalMetadata) {
|
||||||
|
if episode.SeasonNum > 0 {
|
||||||
|
dst.SeasonNum = episode.SeasonNum
|
||||||
|
}
|
||||||
|
if episode.EpisodeNum > 0 {
|
||||||
|
dst.EpisodeNum = episode.EpisodeNum
|
||||||
|
}
|
||||||
|
if dst.Genres == "" && episode.Genres != "" {
|
||||||
|
dst.Genres = episode.Genres
|
||||||
|
}
|
||||||
|
if dst.Countries == "" && episode.Countries != "" {
|
||||||
|
dst.Countries = episode.Countries
|
||||||
|
}
|
||||||
|
if dst.Languages == "" && episode.Languages != "" {
|
||||||
|
dst.Languages = episode.Languages
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
func mergeCloudMetadata(dst, src *LocalMetadata) *LocalMetadata {
|
||||||
|
if src == nil {
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
if dst == nil {
|
||||||
|
return cloneLocalMetadata(src)
|
||||||
|
}
|
||||||
|
if src.Title != "" {
|
||||||
|
dst.Title = src.Title
|
||||||
|
}
|
||||||
|
if src.OriginalName != "" {
|
||||||
|
dst.OriginalName = src.OriginalName
|
||||||
|
}
|
||||||
|
if src.EpisodeTitle != "" {
|
||||||
|
dst.EpisodeTitle = src.EpisodeTitle
|
||||||
|
}
|
||||||
|
if src.AdultCode != "" {
|
||||||
|
dst.AdultCode = src.AdultCode
|
||||||
|
}
|
||||||
|
if src.Year > 0 {
|
||||||
|
dst.Year = src.Year
|
||||||
|
}
|
||||||
|
if src.Overview != "" {
|
||||||
|
dst.Overview = src.Overview
|
||||||
|
}
|
||||||
|
if src.Rating > 0 {
|
||||||
|
dst.Rating = src.Rating
|
||||||
|
}
|
||||||
|
if src.PosterURL != "" {
|
||||||
|
dst.PosterURL = src.PosterURL
|
||||||
|
}
|
||||||
|
if src.BackdropURL != "" {
|
||||||
|
dst.BackdropURL = src.BackdropURL
|
||||||
|
}
|
||||||
|
if src.TMDbID > 0 {
|
||||||
|
dst.TMDbID = src.TMDbID
|
||||||
|
}
|
||||||
|
if src.BangumiID > 0 {
|
||||||
|
dst.BangumiID = src.BangumiID
|
||||||
|
}
|
||||||
|
if src.DoubanID != "" {
|
||||||
|
dst.DoubanID = src.DoubanID
|
||||||
|
}
|
||||||
|
if src.TheTVDBID != "" {
|
||||||
|
dst.TheTVDBID = src.TheTVDBID
|
||||||
|
}
|
||||||
|
if src.SeasonNum > 0 || src.EpisodeNum > 0 {
|
||||||
|
dst.SeasonNum = src.SeasonNum
|
||||||
|
}
|
||||||
|
if src.EpisodeNum > 0 {
|
||||||
|
dst.EpisodeNum = src.EpisodeNum
|
||||||
|
}
|
||||||
|
if src.Genres != "" {
|
||||||
|
dst.Genres = src.Genres
|
||||||
|
}
|
||||||
|
if src.Countries != "" {
|
||||||
|
dst.Countries = src.Countries
|
||||||
|
}
|
||||||
|
if src.Languages != "" {
|
||||||
|
dst.Languages = src.Languages
|
||||||
|
}
|
||||||
|
dst.NSFW = dst.NSFW || src.NSFW
|
||||||
|
dst.HasNFO = dst.HasNFO || src.HasNFO
|
||||||
|
dst.HasArtwork = dst.HasArtwork || src.HasArtwork
|
||||||
|
dst.PathHint = dst.PathHint || src.PathHint
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeCloudPathHintMetadata(dst, hint *LocalMetadata) *LocalMetadata {
|
||||||
|
if hint == nil {
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
if dst == nil || !dst.HasNFO {
|
||||||
|
return mergeCloudMetadata(dst, hint)
|
||||||
|
}
|
||||||
|
if dst.Title == "" {
|
||||||
|
dst.Title = hint.Title
|
||||||
|
}
|
||||||
|
if dst.OriginalName == "" {
|
||||||
|
dst.OriginalName = hint.OriginalName
|
||||||
|
}
|
||||||
|
if dst.Year == 0 {
|
||||||
|
dst.Year = hint.Year
|
||||||
|
}
|
||||||
|
if dst.TMDbID == 0 {
|
||||||
|
dst.TMDbID = hint.TMDbID
|
||||||
|
}
|
||||||
|
if dst.BangumiID == 0 {
|
||||||
|
dst.BangumiID = hint.BangumiID
|
||||||
|
}
|
||||||
|
if dst.DoubanID == "" {
|
||||||
|
dst.DoubanID = hint.DoubanID
|
||||||
|
}
|
||||||
|
if dst.TheTVDBID == "" {
|
||||||
|
dst.TheTVDBID = hint.TheTVDBID
|
||||||
|
}
|
||||||
|
dst.PathHint = dst.PathHint || hint.PathHint
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
func cloneLocalMetadata(src *LocalMetadata) *LocalMetadata {
|
||||||
|
if src == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
cp := *src
|
||||||
|
return &cp
|
||||||
|
}
|
||||||
|
|
||||||
|
func cloudMetadataUseful(meta *LocalMetadata) bool {
|
||||||
|
return meta != nil && (meta.HasNFO || meta.HasArtwork || localHasDescriptiveMetadata(meta))
|
||||||
|
}
|
||||||
|
|
||||||
|
func cloudPlaybackURL(typ, ref string) string {
|
||||||
|
return CloudArtworkURL(typ, ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
func joinCloudDisplayPath(parent, child string) string {
|
||||||
|
parent = strings.Trim(strings.ReplaceAll(strings.TrimSpace(parent), "\\", "/"), "/")
|
||||||
|
child = strings.Trim(strings.ReplaceAll(strings.TrimSpace(child), "\\", "/"), "/")
|
||||||
|
switch {
|
||||||
|
case parent == "":
|
||||||
|
return child
|
||||||
|
case child == "":
|
||||||
|
return parent
|
||||||
|
default:
|
||||||
|
return parent + "/" + child
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func pathBaseSlash(value string) string {
|
||||||
|
value = strings.Trim(strings.ReplaceAll(strings.TrimSpace(value), "\\", "/"), "/")
|
||||||
|
if value == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
parts := strings.Split(value, "/")
|
||||||
|
return parts[len(parts)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func pathDirSlash(value string) string {
|
||||||
|
value = strings.Trim(strings.ReplaceAll(strings.TrimSpace(value), "\\", "/"), "/")
|
||||||
|
if value == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
idx := strings.LastIndex(value, "/")
|
||||||
|
if idx < 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return value[:idx]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user