1
0
mirror of https://github.com/mitb-archive/filebot synced 2024-08-13 17:03:45 -04:00

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

View File

@ -12,8 +12,9 @@ import java.io.StringWriter;
import java.net.URL; import java.net.URL;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.stream.IntStream;
import java.util.stream.Stream; import java.util.stream.Stream;
import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBContext;
@ -31,19 +32,27 @@ import net.filebot.CacheType;
import net.filebot.Resource; import net.filebot.Resource;
import net.filebot.WebServices; 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 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 // auto-align mode
if (a.defaulttvdbseason == null) { if (a.defaulttvdbseason == null) {
try { try {
return destination.mapAutoAligned(a, episode); return mapAutoAligned(destination, a, episode);
} catch (Exception e) { } catch (Exception e) {
debug.warning(e::toString); debug.warning(e::toString);
} }
@ -51,76 +60,94 @@ public enum AnimeLists {
} }
// offset mode // offset mode
int s = getSeasonNumber(episode); int s = getSeasonNumber(source, episode);
int e = getEpisodeNumber(episode); int e = episode.isSpecial() ? episode.getSpecial() : episode.getEpisode();
// check explicit episode mapping // check explicit episode mapping
if (a.mapping != null) { if (a.mapping != null) {
for (Mapping m : a.mapping) { for (Mapping m : a.mapping) {
if (s == getSeason(m)) { if (s == getSeason(source, m)) {
Optional<Integer> episodeMapping = destination.getEpisodeNumber(m, e); Optional<Integer> episodeMapping = getEpisodeNumber(source, m, e);
if (episodeMapping.isPresent()) { 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 // apply default season
s = destination.getSeason(a, episode); s = getSeason(destination, a, episode);
// apply episode offset // 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(); }).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) { if (s == 0) {
// special // 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 { } else {
// regular // 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 { public Optional<Integer> map(int id, int s, DB source, DB destination) throws Exception {
return find(id, s).map(destination::getId).findFirst(); return find(source, id, s).map(a -> getId(destination, a)).findFirst();
} }
protected Episode mapAutoAligned(Entry a, Episode episode) throws Exception { protected Episode mapAutoAligned(DB db, Entry a, Episode episode) throws Exception {
switch (this) { switch (db) {
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);
case AniDB: case AniDB:
return WebServices.AniDB.getEpisodeList(a.anidbid, SortOrder.Absolute, Locale.ENGLISH).stream().filter(e -> { return WebServices.AniDB.getEpisodeList(a.anidbid, SortOrder.Absolute, Locale.ENGLISH).stream().filter(e -> {
return episode.getAbsolute() != null && episode.getAbsolute().equals(e.getEpisode()); return episode.getAbsolute() != null && episode.getAbsolute().equals(e.getEpisode());
}).findFirst().orElse(null); }).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) { if (m.numbers != null) {
switch (this) { switch (db) {
case TheTVDB:
return Optional.ofNullable(m.numbers.get(e));
case AniDB: 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(); return Optional.empty();
} }
protected int getEpisodeNumberOffset(Entry a) { protected int getEpisodeNumberOffset(DB db, Entry a) {
return a.episodeoffset == null ? 0 : this == TheTVDB ? a.episodeoffset : -a.episodeoffset; 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 // special episode
if (e.isSpecial()) { if (e.isSpecial()) {
return 0; return 0;
@ -128,39 +155,64 @@ public enum AnimeLists {
// regular absolute episode // regular absolute episode
if (e.getSeason() == null) { if (e.getSeason() == null) {
return this == AniDB ? 1 : -1; switch (db) {
case AniDB:
return 1;
default:
return -1;
}
} }
// regular SxE episode // regular SxE episode
return e.getSeason(); return e.getSeason();
} }
protected int getEpisodeNumber(Episode e) { protected int getSeason(DB db, Mapping m) {
return e.isSpecial() ? e.getSpecial() : e.getEpisode(); switch (db) {
case AniDB:
return m.anidbseason;
default:
return m.tvdbseason;
}
} }
protected int getSeason(Mapping m) { protected int getSeason(DB db, Entry a, Episode e) {
return this == AniDB ? m.anidbseason : m.tvdbseason; if (e.isSpecial()) {
return 0;
}
switch (db) {
case AniDB:
return 1;
default:
return a.defaulttvdbseason;
}
} }
protected int getSeason(Entry a, Episode e) { protected int getId(DB db, Entry a) {
return e.isSpecial() ? 0 : this == AniDB ? 1 : a.defaulttvdbseason; switch (db) {
} case AniDB:
return a.anidbid;
protected int getId(Entry a) { default:
return this == AniDB ? a.anidbid : a.tvdbid; return a.tvdbid;
}
} }
protected boolean isValid(Entry a) { protected boolean isValid(Entry a) {
return a.anidbid != null && a.tvdbid != null; return a.anidbid != null && a.tvdbid != null;
} }
public Stream<Entry> find(int id) throws Exception { public Stream<Entry> find(DB db, int id) throws Exception {
return stream(MODEL.get().anime).filter(this::isValid).filter(a -> id == getId(a)); return stream(model.anime).filter(this::isValid).filter(a -> id == getId(db, a));
} }
public Stream<Entry> find(int id, int s) throws Exception { public Stream<Entry> find(DB db, int id, int s) throws Exception {
return this == AniDB ? find(id) : find(id).filter(a -> a.defaulttvdbseason == null || s == a.defaulttvdbseason); 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() { protected static Cache getCache() {
@ -173,13 +225,39 @@ public enum AnimeLists {
// NOTE: GitHub only supports If-None-Match (If-Modified-Since is ignored) // NOTE: GitHub only supports If-None-Match (If-Modified-Since is ignored)
Cache cache = getCache(); 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 { protected static URL getResource(String file) throws Exception {
return new URL("https://raw.githubusercontent.com/ScudLee/anime-lists/master/" + file); 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") @XmlRootElement(name = "anime-list")
public static class Model { public static class Model {
@ -242,12 +320,39 @@ public enum AnimeLists {
@XmlJavaTypeAdapter(NumberMapAdapter.class) @XmlJavaTypeAdapter(NumberMapAdapter.class)
@XmlValue @XmlValue
public Map<Integer, Integer> numbers; public int[][] numbers;
@Override @Override
public String toString() { public String toString() {
return marshal(this, Mapping.class); 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 { 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()) { if (episode.isAnime() && episode.isRegular()) {
return mapEpisode(episode, e -> { return mapEpisode(episode, e -> {
try { 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) { } catch (Exception ioe) {
debug.warning(ioe::toString); debug.warning(ioe::toString);
return e; return e;

View File

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