Files
silo-server/internal/historyimport/emby_provider.go
T

201 lines
5.1 KiB
Go

package historyimport
import (
"context"
"strings"
)
type Provider interface {
Fetch(ctx context.Context) ([]Record, []string, error)
}
type EmbyProvider struct {
client *EmbyClient
auth embyLocalAuth
}
func NewEmbyProvider(client *EmbyClient, auth embyLocalAuth) *EmbyProvider {
return &EmbyProvider{client: client, auth: auth}
}
func (p *EmbyProvider) Fetch(ctx context.Context) ([]Record, []string, error) {
playedItems, err := p.client.FetchItems(ctx, p.auth, "IsPlayed")
if err != nil {
return nil, nil, err
}
resumableItems, err := p.client.FetchItems(ctx, p.auth, "IsResumable")
if err != nil {
return nil, nil, err
}
seriesMeta, err := p.fetchSeriesMetadata(ctx, append(playedItems, resumableItems...))
if err != nil {
return nil, nil, err
}
merged := make(map[string]Record, len(playedItems)+len(resumableItems))
for _, item := range append(playedItems, resumableItems...) {
record := normalizeEmbyItem(item, seriesMeta[item.SeriesID])
if record.ExternalID == "" {
record.ExternalID = item.ID
}
existing, ok := merged[record.ExternalID]
if !ok {
merged[record.ExternalID] = record
continue
}
merged[record.ExternalID] = mergeRecords(existing, record)
}
records := make([]Record, 0, len(merged))
for _, record := range merged {
records = append(records, record)
}
return records, nil, nil
}
func (p *EmbyProvider) fetchSeriesMetadata(ctx context.Context, items []embyItem) (map[string]embyItem, error) {
seriesIDs := make([]string, 0)
seen := make(map[string]struct{})
for _, item := range items {
if strings.ToLower(item.Type) != "episode" || strings.TrimSpace(item.SeriesID) == "" {
continue
}
if _, ok := seen[item.SeriesID]; ok {
continue
}
seen[item.SeriesID] = struct{}{}
seriesIDs = append(seriesIDs, item.SeriesID)
}
if len(seriesIDs) == 0 {
return map[string]embyItem{}, nil
}
seriesItems, err := p.client.FetchItemsByIDs(ctx, p.auth, seriesIDs, "Series")
if err != nil {
return nil, err
}
result := make(map[string]embyItem, len(seriesItems))
for _, item := range seriesItems {
result[item.ID] = item
}
return result, nil
}
func normalizeEmbyItem(item embyItem, series embyItem) Record {
record := Record{
ExternalID: item.ID,
Title: item.Name,
Year: item.ProductionYear,
Played: item.UserData.Played,
PlayCount: item.UserData.PlayCount,
PositionSeconds: ticksToSeconds(item.UserData.PlaybackPositionTicks),
DurationSeconds: ticksToSeconds(item.RunTimeTicks),
}
if item.UserData.LastPlayedDate != nil {
record.LastPlayedAt = item.UserData.LastPlayedDate
record.UpdatedAt = item.UserData.LastPlayedDate.UTC()
}
record.IMDbID = providerID(item.ProviderIDs, "imdb")
record.TMDBID = providerID(item.ProviderIDs, "tmdb")
record.TVDBID = providerID(item.ProviderIDs, "tvdb")
switch strings.ToLower(item.Type) {
case "movie":
record.Kind = KindMovie
case "episode":
record.Kind = KindEpisode
record.SeriesTitle = item.SeriesName
if record.SeriesTitle == "" {
record.SeriesTitle = series.Name
}
record.SeriesIMDbID = providerID(series.ProviderIDs, "imdb")
record.SeriesTMDBID = providerID(series.ProviderIDs, "tmdb")
record.SeriesTVDBID = providerID(series.ProviderIDs, "tvdb")
record.SeriesYear = series.ProductionYear
record.SeasonNumber = item.ParentIndexNumber
record.EpisodeNumber = item.IndexNumber
default:
record.Kind = strings.ToLower(item.Type)
}
return record
}
func mergeRecords(a, b Record) Record {
result := a
if b.Played {
result.Played = true
}
if b.PlayCount > result.PlayCount {
result.PlayCount = b.PlayCount
}
if b.PositionSeconds > result.PositionSeconds {
result.PositionSeconds = b.PositionSeconds
}
if b.DurationSeconds > 0 {
result.DurationSeconds = b.DurationSeconds
}
if b.LastPlayedAt != nil && (result.LastPlayedAt == nil || b.LastPlayedAt.After(*result.LastPlayedAt)) {
result.LastPlayedAt = b.LastPlayedAt
}
if b.UpdatedAt.After(result.UpdatedAt) {
result.UpdatedAt = b.UpdatedAt
}
if result.Title == "" {
result.Title = b.Title
}
if result.Year == 0 {
result.Year = b.Year
}
if result.Kind == "" {
result.Kind = b.Kind
}
if result.IMDbID == "" {
result.IMDbID = b.IMDbID
}
if result.TMDBID == "" {
result.TMDBID = b.TMDBID
}
if result.TVDBID == "" {
result.TVDBID = b.TVDBID
}
if result.SeriesTitle == "" {
result.SeriesTitle = b.SeriesTitle
}
if result.SeriesYear == 0 {
result.SeriesYear = b.SeriesYear
}
if result.SeriesIMDbID == "" {
result.SeriesIMDbID = b.SeriesIMDbID
}
if result.SeriesTMDBID == "" {
result.SeriesTMDBID = b.SeriesTMDBID
}
if result.SeriesTVDBID == "" {
result.SeriesTVDBID = b.SeriesTVDBID
}
if result.SeasonNumber == 0 {
result.SeasonNumber = b.SeasonNumber
}
if result.EpisodeNumber == 0 {
result.EpisodeNumber = b.EpisodeNumber
}
return result
}
func providerID(ids map[string]string, key string) string {
for k, v := range ids {
if strings.EqualFold(k, key) {
return strings.TrimSpace(v)
}
}
return ""
}
func ticksToSeconds(ticks int64) float64 {
if ticks <= 0 {
return 0
}
return float64(ticks) / 10_000_000
}