Refactor AnimeList client

This commit is contained in:
Reinhard Pointner 2019-06-10 22:18:59 +07:00
parent ff422c39fa
commit 4a19490f01
4 changed files with 164 additions and 103 deletions

View File

@ -65,7 +65,7 @@ import net.filebot.mediainfo.MediaInfoException;
import net.filebot.similarity.Normalization;
import net.filebot.similarity.SimilarityComparator;
import net.filebot.util.FileUtilities;
import net.filebot.web.AnimeLists;
import net.filebot.web.AnimeList;
import net.filebot.web.AudioTrack;
import net.filebot.web.Episode;
import net.filebot.web.EpisodeFormat;
@ -797,7 +797,7 @@ public class MediaBindingBean {
}
if (AniDB.getIdentifier().equals(e.getSeriesInfo().getDatabase())) {
return AnimeLists.AniDB.map(e, AnimeLists.TheTVDB).map(this::createBindingObject).orElse(null); // map AniDB to TheTVDB bindings
return new AnimeList().map(e, AnimeList.getDB(e), AnimeList.DB.TheTVDB).map(this::createBindingObject).orElse(null); // map AniDB to TheTVDB bindings
}
return createBindingObject(fetchEpisode(e, SortOrder.Airdate, null));
@ -1173,17 +1173,15 @@ public class MediaBindingBean {
});
}
@Define("AnimeLists")
@Define("AnimeList")
public DynamicBindings getAnimeLists() {
return new DynamicBindings(AnimeLists::names, k -> {
return new DynamicBindings(AnimeList.DB::names, k -> {
if (infoObject instanceof Episode) {
Episode e = getEpisode();
AnimeLists origin = AnimeLists.forName(e.getSeriesInfo().getDatabase());
AnimeLists destination = AnimeLists.forName(k);
if (origin == destination) {
if (AnimeList.getDB(e) == AnimeList.getDB(k)) {
return e;
}
return origin.map(e, destination).orElse(e);
return new AnimeList().map(e, AnimeList.getDB(e), AnimeList.getDB(k)).orElse(e);
}
return undefined(k);
});
@ -1197,7 +1195,6 @@ public class MediaBindingBean {
debug.warning("Failed to retrieve primary series info: " + e); // default to seriesInfo property
}
}
return getSeriesInfo();
}

View File

@ -12,8 +12,9 @@ import java.io.StringWriter;
import java.net.URL;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import javax.xml.bind.JAXBContext;
@ -31,19 +32,27 @@ import net.filebot.CacheType;
import net.filebot.Resource;
import net.filebot.WebServices;
public enum AnimeLists {
public class AnimeList {
AniDB, TheTVDB;
private Model model;
public Optional<Episode> map(Episode episode, AnimeLists destination) throws Exception {
public AnimeList() throws Exception {
this.model = MODEL.get();
}
public Model getModel() {
return model;
}
public Optional<Episode> map(Episode episode, DB source, DB destination) throws Exception {
int id = episode.getSeriesInfo().getId();
int series = getSeasonNumber(episode);
int series = getSeasonNumber(source, episode);
return find(id, series).map(a -> {
return find(source, id, series).map(a -> {
// auto-align mode
if (a.defaulttvdbseason == null) {
try {
return destination.mapAutoAligned(a, episode);
return mapAutoAligned(destination, a, episode);
} catch (Exception e) {
debug.warning(e::toString);
}
@ -51,76 +60,94 @@ public enum AnimeLists {
}
// offset mode
int s = getSeasonNumber(episode);
int e = getEpisodeNumber(episode);
int s = getSeasonNumber(source, episode);
int e = episode.isSpecial() ? episode.getSpecial() : episode.getEpisode();
// check explicit episode mapping
if (a.mapping != null) {
for (Mapping m : a.mapping) {
if (s == getSeason(m)) {
Optional<Integer> episodeMapping = destination.getEpisodeNumber(m, e);
if (s == getSeason(source, m)) {
Optional<Integer> episodeMapping = getEpisodeNumber(source, m, e);
if (episodeMapping.isPresent()) {
return destination.derive(a, episode, destination.getSeason(m), episodeMapping.get());
return derive(destination, a, episode, getSeason(destination, m), episodeMapping.get());
}
}
}
}
// apply default season
s = destination.getSeason(a, episode);
s = getSeason(destination, a, episode);
// apply episode offset
e += destination.getEpisodeNumberOffset(a);
e += getEpisodeNumberOffset(destination, a);
return destination.derive(a, episode, s, e);
return derive(destination, a, episode, s, e);
}).findFirst();
}
private Episode derive(Entry a, Episode episode, int s, int e) {
private Episode derive(DB db, Entry a, Episode episode, int s, int e) {
if (s == 0) {
// special
return this == AniDB ? episode.derive(a.name, null, null, null, e) : episode.deriveSpecial(e);
switch (db) {
case AniDB:
return episode.derive(a.name, null, null, null, e);
default:
return episode.deriveSpecial(e);
}
} else {
// regular
return this == AniDB ? episode.derive(a.name, null, e, null, null) : episode.derive(s, e);
switch (db) {
case AniDB:
return episode.derive(a.name, null, e, null, null);
default:
return episode.derive(s, e);
}
}
}
public Optional<Integer> map(int id, int s, AnimeLists destination) throws Exception {
return find(id, s).map(destination::getId).findFirst();
public Optional<Integer> map(int id, int s, DB source, DB destination) throws Exception {
return find(source, id, s).map(a -> getId(destination, a)).findFirst();
}
protected Episode mapAutoAligned(Entry a, Episode episode) throws Exception {
switch (this) {
case TheTVDB:
return WebServices.TheTVDB.getEpisodeList(a.tvdbid, SortOrder.Airdate, Locale.ENGLISH).stream().filter(e -> {
return episode.getEpisode() != null && episode.getEpisode().equals(e.getAbsolute());
}).findFirst().orElse(null);
protected Episode mapAutoAligned(DB db, Entry a, Episode episode) throws Exception {
switch (db) {
case AniDB:
return WebServices.AniDB.getEpisodeList(a.anidbid, SortOrder.Absolute, Locale.ENGLISH).stream().filter(e -> {
return episode.getAbsolute() != null && episode.getAbsolute().equals(e.getEpisode());
}).findFirst().orElse(null);
default:
return WebServices.TheTVDB.getEpisodeList(a.tvdbid, SortOrder.Airdate, Locale.ENGLISH).stream().filter(e -> {
return episode.getEpisode() != null && episode.getEpisode().equals(e.getAbsolute());
}).findFirst().orElse(null);
}
return null;
}
protected Optional<Integer> getEpisodeNumber(Mapping m, Integer e) {
protected Optional<Integer> getEpisodeNumber(DB db, Mapping m, Integer e) {
if (m.numbers != null) {
switch (this) {
case TheTVDB:
return Optional.ofNullable(m.numbers.get(e));
switch (db) {
case AniDB:
return m.numbers.entrySet().stream().filter(it -> e.equals(it.getValue())).map(it -> it.getKey()).findFirst();
return stream(m.numbers).filter(i -> e == i[0]).map(i -> i[1]).findFirst();
default:
return stream(m.numbers).filter(i -> e == i[1]).map(i -> i[0]).findFirst();
}
}
return Optional.empty();
}
protected int getEpisodeNumberOffset(Entry a) {
return a.episodeoffset == null ? 0 : this == TheTVDB ? a.episodeoffset : -a.episodeoffset;
protected int getEpisodeNumberOffset(DB db, Entry a) {
if (a.episodeoffset == null) {
return 0;
}
switch (db) {
case AniDB:
return -a.episodeoffset;
default:
return a.episodeoffset;
}
}
protected int getSeasonNumber(Episode e) {
protected int getSeasonNumber(DB db, Episode e) {
// special episode
if (e.isSpecial()) {
return 0;
@ -128,39 +155,64 @@ public enum AnimeLists {
// regular absolute episode
if (e.getSeason() == null) {
return this == AniDB ? 1 : -1;
switch (db) {
case AniDB:
return 1;
default:
return -1;
}
}
// regular SxE episode
return e.getSeason();
}
protected int getEpisodeNumber(Episode e) {
return e.isSpecial() ? e.getSpecial() : e.getEpisode();
protected int getSeason(DB db, Mapping m) {
switch (db) {
case AniDB:
return m.anidbseason;
default:
return m.tvdbseason;
}
}
protected int getSeason(Mapping m) {
return this == AniDB ? m.anidbseason : m.tvdbseason;
protected int getSeason(DB db, Entry a, Episode e) {
if (e.isSpecial()) {
return 0;
}
switch (db) {
case AniDB:
return 1;
default:
return a.defaulttvdbseason;
}
}
protected int getSeason(Entry a, Episode e) {
return e.isSpecial() ? 0 : this == AniDB ? 1 : a.defaulttvdbseason;
}
protected int getId(Entry a) {
return this == AniDB ? a.anidbid : a.tvdbid;
protected int getId(DB db, Entry a) {
switch (db) {
case AniDB:
return a.anidbid;
default:
return a.tvdbid;
}
}
protected boolean isValid(Entry a) {
return a.anidbid != null && a.tvdbid != null;
}
public Stream<Entry> find(int id) throws Exception {
return stream(MODEL.get().anime).filter(this::isValid).filter(a -> id == getId(a));
public Stream<Entry> find(DB db, int id) throws Exception {
return stream(model.anime).filter(this::isValid).filter(a -> id == getId(db, a));
}
public Stream<Entry> find(int id, int s) throws Exception {
return this == AniDB ? find(id) : find(id).filter(a -> a.defaulttvdbseason == null || s == a.defaulttvdbseason);
public Stream<Entry> find(DB db, int id, int s) throws Exception {
switch (db) {
case AniDB:
return find(db, id);
default:
return find(db, id).filter(a -> a.defaulttvdbseason == null || s == a.defaulttvdbseason);
}
}
protected static Cache getCache() {
@ -173,13 +225,39 @@ public enum AnimeLists {
// NOTE: GitHub only supports If-None-Match (If-Modified-Since is ignored)
Cache cache = getCache();
return cache.bytes(file, AnimeLists::getResource).fetch(fetchIfNoneMatch(url -> file, cache)).expire(Cache.ONE_MONTH).get();
return cache.bytes(file, AnimeList::getResource).fetch(fetchIfNoneMatch(url -> file, cache)).expire(Cache.ONE_MONTH).get();
}
protected static URL getResource(String file) throws Exception {
return new URL("https://raw.githubusercontent.com/ScudLee/anime-lists/master/" + file);
}
public static DB getDB(Episode e) {
return DB.forName(e.getSeriesInfo().getDatabase());
}
public static DB getDB(String s) {
return DB.forName(s);
}
public enum DB {
AniDB, TheTVDB;
public static List<String> names() {
return stream(values()).map(Enum::name).collect(toList());
}
public static DB forName(String name) {
for (DB db : values()) {
if (db.name().equalsIgnoreCase(name)) {
return db;
}
}
throw new IllegalArgumentException(String.format("%s not in %s", name, asList(values())));
}
}
@XmlRootElement(name = "anime-list")
public static class Model {
@ -242,12 +320,39 @@ public enum AnimeLists {
@XmlJavaTypeAdapter(NumberMapAdapter.class)
@XmlValue
public Map<Integer, Integer> numbers;
public int[][] numbers;
@Override
public String toString() {
return marshal(this, Mapping.class);
}
}
private static class NumberAdapter extends XmlAdapter<String, Integer> {
@Override
public Integer unmarshal(String s) throws Exception {
return matchInteger(s);
}
@Override
public String marshal(Integer i) throws Exception {
return Objects.toString(i, "");
}
}
private static class NumberMapAdapter extends XmlAdapter<String, int[][]> {
@Override
public int[][] unmarshal(String s) throws Exception {
return tokenize(s, SEMICOLON).map(m -> matchIntegers(m)).filter(m -> m.size() == 2).map(m -> m.stream().mapToInt(i -> i).toArray()).toArray(int[][]::new);
}
@Override
public String marshal(int[][] m) throws Exception {
return stream(m).map(e -> join(IntStream.of(e).boxed(), "-")).collect(joining(";"));
}
}
private static <T> T unmarshal(byte[] bytes, Class<T> type) throws Exception {
@ -267,44 +372,4 @@ public enum AnimeLists {
}
}
private static class NumberAdapter extends XmlAdapter<String, Integer> {
@Override
public Integer unmarshal(String s) throws Exception {
return matchInteger(s);
}
@Override
public String marshal(Integer i) throws Exception {
return String.valueOf(i);
}
}
private static class NumberMapAdapter extends XmlAdapter<String, Map<Integer, Integer>> {
@Override
public Map<Integer, Integer> unmarshal(String s) throws Exception {
return tokenize(s, SEMICOLON).map(m -> matchIntegers(m)).filter(m -> m.size() >= 2).collect(toMap(m -> m.get(0), m -> m.get(1)));
}
@Override
public String marshal(Map<Integer, Integer> m) throws Exception {
return m.entrySet().stream().map(e -> join(Stream.of(e.getKey(), e.getValue()), "-")).collect(joining(";"));
}
}
public static List<String> names() {
return stream(values()).map(Enum::name).collect(toList());
}
public static AnimeLists forName(String name) {
for (AnimeLists db : values()) {
if (db.name().equalsIgnoreCase(name)) {
return db;
}
}
throw new IllegalArgumentException(String.format("%s not in %s", name, asList(values())));
}
}

View File

@ -55,7 +55,7 @@ public final class EpisodeUtilities {
if (episode.isAnime() && episode.isRegular()) {
return mapEpisode(episode, e -> {
try {
return AnimeLists.forName(e.getSeriesInfo().getDatabase()).map(e, AnimeLists.TheTVDB).orElse(e);
return new AnimeList().map(e, AnimeList.getDB(e), AnimeList.DB.TheTVDB).orElse(e);
} catch (Exception ioe) {
debug.warning(ioe::toString);
return e;

View File

@ -194,7 +194,6 @@ public enum XEM {
return db;
}
}
throw new IllegalArgumentException(String.format("%s not in %s", name, asList(values())));
}