Files
plezy/lib/models/trackers/anime_lists_mapping.dart
T

187 lines
5.5 KiB
Dart

enum AnimeListProvider { tvdb, tmdb }
enum AnimeListMatchKind { explicit, range, defaultMapping }
class AnimeListSeasonRef {
final int? number;
final bool isAbsolute;
const AnimeListSeasonRef.number(this.number) : isAbsolute = false;
const AnimeListSeasonRef.absolute() : number = null, isAbsolute = true;
}
class AnimeListEntry {
final int anidbId;
final String? name;
final String? rawTvdbId;
final int? tvdbId;
final AnimeListSeasonRef? defaultTvdbSeason;
final int episodeOffset;
final int? tmdbTvId;
final AnimeListSeasonRef? tmdbSeason;
final int tmdbOffset;
final List<int> tmdbMovieIds;
final List<String> imdbIds;
final List<AnimeListEpisodeMapping> mappings;
const AnimeListEntry({
required this.anidbId,
this.name,
this.rawTvdbId,
this.tvdbId,
this.defaultTvdbSeason,
this.episodeOffset = 0,
this.tmdbTvId,
this.tmdbSeason,
this.tmdbOffset = 0,
this.tmdbMovieIds = const [],
this.imdbIds = const [],
this.mappings = const [],
});
List<AnimeEpisodeMatch> resolveEpisode({
required AnimeListProvider provider,
required int externalSeason,
required int externalEpisode,
}) {
final explicit = <AnimeEpisodeMatch>[];
for (final mapping in mappings) {
if (!mapping.matchesProviderSeason(provider, externalSeason)) continue;
for (final item in mapping.explicit) {
if (item.externalEpisodes.contains(externalEpisode)) {
explicit.add(
AnimeEpisodeMatch(
anidbId: anidbId,
anidbSeason: mapping.anidbSeason,
anidbEpisode: item.anidbEpisode,
provider: provider,
externalSeason: externalSeason,
externalEpisode: externalEpisode,
kind: AnimeListMatchKind.explicit,
),
);
}
}
}
if (explicit.isNotEmpty) return explicit;
final ranges = <AnimeEpisodeMatch>[];
for (final mapping in mappings) {
if (!mapping.matchesProviderSeason(provider, externalSeason)) continue;
final start = mapping.start;
if (start == null) continue;
final anidbEpisode = externalEpisode - mapping.offset;
if (anidbEpisode < start) continue;
final end = mapping.end;
if (end != null && anidbEpisode > end) continue;
if (anidbEpisode <= 0) continue;
ranges.add(
AnimeEpisodeMatch(
anidbId: anidbId,
anidbSeason: mapping.anidbSeason,
anidbEpisode: anidbEpisode,
provider: provider,
externalSeason: externalSeason,
externalEpisode: externalEpisode,
kind: AnimeListMatchKind.range,
rangeStart: start,
rangeEnd: end,
),
);
}
if (ranges.isNotEmpty) return ranges;
final defaultSeason = switch (provider) {
AnimeListProvider.tvdb => defaultTvdbSeason,
AnimeListProvider.tmdb => tmdbSeason,
};
final defaultOffset = switch (provider) {
AnimeListProvider.tvdb => episodeOffset,
AnimeListProvider.tmdb => tmdbOffset,
};
if (defaultSeason == null || defaultSeason.isAbsolute || defaultSeason.number != externalSeason) return const [];
final anidbEpisode = externalEpisode - defaultOffset;
if (anidbEpisode <= 0) return const [];
return [
AnimeEpisodeMatch(
anidbId: anidbId,
anidbSeason: 1,
anidbEpisode: anidbEpisode,
provider: provider,
externalSeason: externalSeason,
externalEpisode: externalEpisode,
kind: AnimeListMatchKind.defaultMapping,
),
];
}
bool mapsSeason({required AnimeListProvider provider, required int externalSeason}) {
final defaultSeason = switch (provider) {
AnimeListProvider.tvdb => defaultTvdbSeason,
AnimeListProvider.tmdb => tmdbSeason,
};
if (defaultSeason != null && !defaultSeason.isAbsolute && defaultSeason.number == externalSeason) return true;
return mappings.any((mapping) => mapping.matchesProviderSeason(provider, externalSeason));
}
}
class AnimeListEpisodeMapping {
final int anidbSeason;
final AnimeListProvider provider;
final int externalSeason;
final int? start;
final int? end;
final int offset;
final List<AnimeListExplicitEpisodeMapping> explicit;
const AnimeListEpisodeMapping({
required this.anidbSeason,
required this.provider,
required this.externalSeason,
this.start,
this.end,
this.offset = 0,
this.explicit = const [],
});
bool matchesProviderSeason(AnimeListProvider provider, int season) =>
this.provider == provider && externalSeason == season;
}
class AnimeListExplicitEpisodeMapping {
final int anidbEpisode;
final List<int> externalEpisodes;
const AnimeListExplicitEpisodeMapping({required this.anidbEpisode, required this.externalEpisodes});
}
class AnimeEpisodeMatch {
final int anidbId;
final int anidbSeason;
final int anidbEpisode;
final AnimeListProvider provider;
final int externalSeason;
final int externalEpisode;
final AnimeListMatchKind kind;
final int? rangeStart;
final int? rangeEnd;
const AnimeEpisodeMatch({
required this.anidbId,
required this.anidbSeason,
required this.anidbEpisode,
required this.provider,
required this.externalSeason,
required this.externalEpisode,
required this.kind,
this.rangeStart,
this.rangeEnd,
});
bool sameAnimeEntry(AnimeEpisodeMatch other) => anidbId == other.anidbId && anidbSeason == other.anidbSeason;
bool sameEpisode(AnimeEpisodeMatch other) =>
sameAnimeEntry(other) && anidbEpisode == other.anidbEpisode && kind == other.kind;
}