package service import ( "context" "fmt" "net/url" "sort" "strings" "github.com/ShukeBta/MediaStationGo/internal/model" ) func (e *EmbyService) mediaSourcesForItem(ctx context.Context, m *model.Media, asEmbedded, directOnly bool) []map[string]any { siblings := e.mediaVersionSiblings(ctx, m) if len(siblings) == 0 { return []map[string]any{e.mediaSource(ctx, m, asEmbedded, directOnly)} } sources := make([]map[string]any, 0, len(siblings)) for i := range siblings { media := siblings[i] sources = append(sources, e.mediaSource(ctx, &media, asEmbedded, directOnly)) } return sources } func (e *EmbyService) mediaVersionSiblings(ctx context.Context, m *model.Media) []model.Media { if e == nil || e.repo == nil || e.repo.DB == nil || m == nil || strings.TrimSpace(m.ID) == "" { return nil } libraryIDs := e.mergedLibraryIDs(ctx, m.LibraryID) if len(libraryIDs) == 0 { libraryIDs = []string{m.LibraryID} } q := e.repo.DB.WithContext(ctx).Model(&model.Media{}). Where("library_id IN ?", libraryIDs). Where("season_num = ? AND episode_num = ?", m.SeasonNum, m.EpisodeNum) if m.TMDbID > 0 { q = q.Where("tm_db_id = ?", m.TMDbID) } else if m.BangumiID > 0 { q = q.Where("bangumi_id = ?", m.BangumiID) } else { title := strings.TrimSpace(m.Title) if title == "" { title = strings.TrimSpace(m.OriginalName) } if title == "" { return []model.Media{*m} } q = q.Where("LOWER(title) = ?", strings.ToLower(title)) if m.Year > 0 { q = q.Where("year = ?", m.Year) } } var rows []model.Media if err := q.Find(&rows).Error; err != nil || len(rows) == 0 { return []model.Media{*m} } rows = e.collapseExactPathRows(rows) sort.SliceStable(rows, func(i, j int) bool { if rows[i].ID == m.ID { return true } if rows[j].ID == m.ID { return false } return preferMediaVersion(rows[i], rows[j]) }) return rows } func (e *EmbyService) collapseExactPathRows(rows []model.Media) []model.Media { if len(rows) < 2 { return rows } out := rows[:0] seen := map[string]struct{}{} for _, row := range rows { path := strings.TrimSpace(row.Path) if path != "" { if _, ok := seen[path]; ok { continue } seen[path] = struct{}{} } out = append(out, row) } return out } func (e *EmbyService) mediaVersionKey(ctx context.Context, m *model.Media) string { if e == nil || m == nil { return "" } ids := e.mergedLibraryIDs(ctx, m.LibraryID) sort.Strings(ids) libraryGroup := strings.Join(ids, ",") if libraryGroup == "" { libraryGroup = strings.TrimSpace(m.LibraryID) } if m.TMDbID > 0 { return fmt.Sprintf("%s|tmdb:%d|s:%d|e:%d", libraryGroup, m.TMDbID, m.SeasonNum, m.EpisodeNum) } if m.BangumiID > 0 { return fmt.Sprintf("%s|bangumi:%d|s:%d|e:%d", libraryGroup, m.BangumiID, m.SeasonNum, m.EpisodeNum) } title := strings.ToLower(strings.TrimSpace(m.Title)) if title == "" { title = strings.ToLower(strings.TrimSpace(m.OriginalName)) } if title == "" { return "" } return fmt.Sprintf("%s|title:%s|y:%d|s:%d|e:%d", libraryGroup, title, m.Year, m.SeasonNum, m.EpisodeNum) } func preferMediaVersion(candidate, current model.Media) bool { candidateCloud := strings.TrimSpace(candidate.STRMURL) != "" || strings.HasPrefix(strings.ToLower(strings.TrimSpace(candidate.Path)), "cloud://") currentCloud := strings.TrimSpace(current.STRMURL) != "" || strings.HasPrefix(strings.ToLower(strings.TrimSpace(current.Path)), "cloud://") if candidateCloud != currentCloud { return !candidateCloud } if candidate.Width != current.Width { return candidate.Width > current.Width } if candidate.SizeBytes != current.SizeBytes { return candidate.SizeBytes > current.SizeBytes } return candidate.CreatedAt.After(current.CreatedAt) } func embySTRMStreamURL(mediaID string) string { return "/api/stream/" + url.PathEscape(strings.TrimSpace(mediaID)) } func embyDirectStreamURL(mediaID, container string) string { mediaID = strings.TrimSpace(mediaID) container = strings.Trim(strings.ToLower(container), ". ") if container == "" || container == "strm" { return "/Videos/" + mediaID + "/stream" } return "/Videos/" + mediaID + "/stream." + container } func (e *EmbyService) mediaStreams(m *model.Media) []map[string]any { streams := []map[string]any{} if m.VideoCodec != "" || m.Width > 0 { streams = append(streams, map[string]any{ "Codec": m.VideoCodec, "Type": "Video", "Index": 0, "Width": m.Width, "Height": m.Height, "AspectRatio": "", "IsDefault": true, "IsForced": false, "IsExternal": false, "DisplayTitle": fmt.Sprintf("%dx%d %s", m.Width, m.Height, m.VideoCodec), }) } if m.AudioCodec != "" { streams = append(streams, map[string]any{ "Codec": m.AudioCodec, "Type": "Audio", "Index": 1, "IsDefault": true, "IsForced": false, "IsExternal": false, }) } if len(streams) == 0 { streams = append(streams, map[string]any{ "Codec": "unknown", "Type": "Video", "Index": 0, "IsDefault": true, "IsForced": false, "IsExternal": false, "DisplayTitle": "Video", }) } return streams }