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:
parent
ff422c39fa
commit
4a19490f01
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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())));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
@ -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;
|
||||||
|
@ -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())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user