+ try to auto-detect name from imdb/thetvdb ID if possible
This commit is contained in:
parent
c37c38c2c7
commit
c1ed273158
|
@ -95,12 +95,12 @@ public class CmdlineOperations implements CmdlineInterface {
|
||||||
int cws = 0; // common word sequence
|
int cws = 0; // common word sequence
|
||||||
double max = mediaFiles.size();
|
double max = mediaFiles.size();
|
||||||
|
|
||||||
|
SeriesNameMatcher nameMatcher = new SeriesNameMatcher();
|
||||||
Collection<String> cwsList = emptySet();
|
Collection<String> cwsList = emptySet();
|
||||||
if (max >= 5) {
|
if (max >= 5) {
|
||||||
cwsList = detectSeriesNames(mediaFiles);
|
cwsList = nameMatcher.matchAll(mediaFiles.toArray(new File[0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
SeriesNameMatcher nameMatcher = new SeriesNameMatcher();
|
|
||||||
for (File f : mediaFiles) {
|
for (File f : mediaFiles) {
|
||||||
// count SxE matches
|
// count SxE matches
|
||||||
if (nameMatcher.matchBySeasonEpisodePattern(f.getName()) != null) {
|
if (nameMatcher.matchBySeasonEpisodePattern(f.getName()) != null) {
|
||||||
|
@ -306,7 +306,7 @@ public class CmdlineOperations implements CmdlineInterface {
|
||||||
for (File subtitleFile : subtitleFiles) {
|
for (File subtitleFile : subtitleFiles) {
|
||||||
// check if subtitle corresponds to a movie file (same name, different extension)
|
// check if subtitle corresponds to a movie file (same name, different extension)
|
||||||
for (int i = 0; i < movieDescriptors.length; i++) {
|
for (int i = 0; i < movieDescriptors.length; i++) {
|
||||||
if (movieDescriptors != null) {
|
if (movieDescriptors[i] != null) {
|
||||||
if (isDerived(subtitleFile, movieFiles[i])) {
|
if (isDerived(subtitleFile, movieFiles[i])) {
|
||||||
File movieDestination = renameMap.get(movieFiles[i]);
|
File movieDestination = renameMap.get(movieFiles[i]);
|
||||||
File subtitleDestination = new File(movieDestination.getParentFile(), getName(movieDestination) + "." + getExtension(subtitleFile));
|
File subtitleDestination = new File(movieDestination.getParentFile(), getName(movieDestination) + "." + getExtension(subtitleFile));
|
||||||
|
@ -568,21 +568,9 @@ public class CmdlineOperations implements CmdlineInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private Collection<String> detectQuery(Collection<File> mediaFiles, boolean strict) throws Exception {
|
private List<String> detectQuery(Collection<File> mediaFiles, boolean strict) throws Exception {
|
||||||
Collection<String> names = new LinkedHashSet<String>();
|
|
||||||
|
|
||||||
// detect by imdb id from nfo file in the same folder
|
|
||||||
for (List<File> file : mapByFolder(mediaFiles).values()) {
|
|
||||||
for (int imdbid : grepImdbIdFor(file.get(0))) {
|
|
||||||
Movie movie = WebServices.TMDb.getMovieDescriptor(imdbid, Locale.ENGLISH);
|
|
||||||
if (movie != null) {
|
|
||||||
names.add(movie.getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// detect series name by common word sequence
|
// detect series name by common word sequence
|
||||||
names.addAll(detectSeriesNames(mediaFiles));
|
List<String> names = detectSeriesNames(mediaFiles);
|
||||||
|
|
||||||
if (names.isEmpty() || (strict && names.size() > 1)) {
|
if (names.isEmpty() || (strict && names.size() > 1)) {
|
||||||
throw new Exception("Unable to auto-select query: " + names);
|
throw new Exception("Unable to auto-select query: " + names);
|
||||||
|
|
|
@ -5,55 +5,125 @@ package net.sourceforge.filebot.mediainfo;
|
||||||
import static java.util.ResourceBundle.*;
|
import static java.util.ResourceBundle.*;
|
||||||
import static java.util.concurrent.TimeUnit.*;
|
import static java.util.concurrent.TimeUnit.*;
|
||||||
import static java.util.regex.Pattern.*;
|
import static java.util.regex.Pattern.*;
|
||||||
|
import static net.sourceforge.tuned.FileUtilities.*;
|
||||||
import static net.sourceforge.tuned.StringUtilities.*;
|
import static net.sourceforge.tuned.StringUtilities.*;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Scanner;
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import net.sourceforge.filebot.MediaTypes;
|
import net.sourceforge.filebot.MediaTypes;
|
||||||
|
import net.sourceforge.filebot.WebServices;
|
||||||
import net.sourceforge.filebot.similarity.SeriesNameMatcher;
|
import net.sourceforge.filebot.similarity.SeriesNameMatcher;
|
||||||
import net.sourceforge.filebot.web.CachedResource;
|
import net.sourceforge.filebot.web.CachedResource;
|
||||||
|
import net.sourceforge.filebot.web.Movie;
|
||||||
|
import net.sourceforge.filebot.web.SearchResult;
|
||||||
|
import net.sourceforge.filebot.web.TheTVDBClient.TheTVDBSearchResult;
|
||||||
|
|
||||||
|
|
||||||
public class ReleaseInfo {
|
public class ReleaseInfo {
|
||||||
|
|
||||||
public static Collection<String> detectSeriesNames(Collection<File> files) throws IOException {
|
public static List<String> detectSeriesNames(Collection<File> files) throws Exception {
|
||||||
SeriesNameMatcher matcher = new SeriesNameMatcher();
|
ReleaseInfo releaseInfo = new ReleaseInfo();
|
||||||
ReleaseInfo cleaner = new ReleaseInfo();
|
|
||||||
|
// don't allow duplicates
|
||||||
|
Map<String, String> names = new LinkedHashMap<String, String>();
|
||||||
|
|
||||||
|
for (SearchResult it : releaseInfo.lookupNameByInfoFile(files, Locale.ENGLISH)) {
|
||||||
|
names.put(it.getName().toLowerCase(), it.getName());
|
||||||
|
}
|
||||||
|
|
||||||
// match common word sequence and clean detected word sequence from unwanted elements
|
// match common word sequence and clean detected word sequence from unwanted elements
|
||||||
Collection<String> names = matcher.matchAll(files.toArray(new File[files.size()]));
|
Collection<String> matches = new SeriesNameMatcher().matchAll(files.toArray(new File[files.size()]));
|
||||||
return new LinkedHashSet<String>(cleaner.cleanRG(names));
|
for (String it : releaseInfo.cleanRG(matches)) {
|
||||||
|
names.put(it.toLowerCase(), it);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ArrayList<String>(names.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static Set<Integer> grepImdbIdFor(File movieFile) throws IOException {
|
public static Set<Integer> grepImdbIdFor(File file) throws Exception {
|
||||||
|
ReleaseInfo releaseInfo = new ReleaseInfo();
|
||||||
Set<Integer> collection = new LinkedHashSet<Integer>();
|
Set<Integer> collection = new LinkedHashSet<Integer>();
|
||||||
File movieFolder = movieFile.getParentFile(); // lookup imdb id from nfo files in this folder
|
|
||||||
|
|
||||||
for (File file : movieFolder.listFiles(MediaTypes.getDefaultFilter("application/nfo"))) {
|
for (File nfo : file.getParentFile().listFiles(MediaTypes.getDefaultFilter("application/nfo"))) {
|
||||||
Scanner scanner = new Scanner(new FileInputStream(file), "UTF-8");
|
String text = new String(readFile(nfo), "UTF-8");
|
||||||
|
collection.addAll(releaseInfo.grepImdbId(text));
|
||||||
try {
|
}
|
||||||
// scan for imdb id patterns like tt1234567
|
|
||||||
String imdb = null;
|
return collection;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Set<SearchResult> lookupNameByInfoFile(Collection<File> files, Locale language) throws Exception {
|
||||||
|
Set<SearchResult> names = new LinkedHashSet<SearchResult>();
|
||||||
|
|
||||||
|
// search for id in sibling nfo files
|
||||||
|
for (File folder : mapByFolder(files).keySet()) {
|
||||||
|
for (File nfo : folder.listFiles(MediaTypes.getDefaultFilter("application/nfo"))) {
|
||||||
|
String text = new String(readFile(nfo), "UTF-8");
|
||||||
|
|
||||||
while ((imdb = scanner.findWithinHorizon("(?<=tt)\\d{7}", 64 * 1024)) != null) {
|
for (int imdbid : grepImdbId(text)) {
|
||||||
collection.add(Integer.parseInt(imdb));
|
Movie movie = WebServices.OpenSubtitles.getMovieDescriptor(imdbid, language); // movies and tv shows
|
||||||
|
if (movie != null) {
|
||||||
|
names.add(movie);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
scanner.close();
|
for (int tvdbid : grepTheTvdbId(text)) {
|
||||||
|
TheTVDBSearchResult series = WebServices.TheTVDB.lookup(tvdbid, language); // just tv shows
|
||||||
|
if (series != null) {
|
||||||
|
names.add(series);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Set<Integer> grepImdbId(CharSequence text) {
|
||||||
|
// scan for imdb id patterns like tt1234567
|
||||||
|
Matcher imdbMatch = Pattern.compile("(?<=tt)\\d{7}").matcher(text);
|
||||||
|
Set<Integer> collection = new LinkedHashSet<Integer>();
|
||||||
|
|
||||||
|
while (imdbMatch.find()) {
|
||||||
|
collection.add(Integer.parseInt(imdbMatch.group()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return collection;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Set<Integer> grepTheTvdbId(CharSequence text) {
|
||||||
|
// scan for thetvdb id patterns like http://www.thetvdb.com/?tab=series&id=78874&lid=14
|
||||||
|
Set<Integer> collection = new LinkedHashSet<Integer>();
|
||||||
|
for (String token : Pattern.compile("[\\s\"<>|]+").split(text)) {
|
||||||
|
try {
|
||||||
|
URL url = new URL(token);
|
||||||
|
if (url.getHost().contains("thetvdb")) {
|
||||||
|
Matcher idMatch = Pattern.compile("(?<=(^|\\W)id=)\\d+").matcher(url.getQuery());
|
||||||
|
while (idMatch.find()) {
|
||||||
|
collection.add(Integer.parseInt(idMatch.group()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (MalformedURLException e) {
|
||||||
|
// parse for thetvdb urls, ignore everything else
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -200,8 +200,8 @@ public class SeriesNameMatcher {
|
||||||
|
|
||||||
protected String normalize(String name) {
|
protected String normalize(String name) {
|
||||||
// remove group names and checksums, any [...] or (...)
|
// remove group names and checksums, any [...] or (...)
|
||||||
name = name.replaceAll("\\([^\\(]*\\)", "");
|
name = name.replaceAll("\\([^\\(]*\\)", " ");
|
||||||
name = name.replaceAll("\\[[^\\[]*\\]", "");
|
name = name.replaceAll("\\[[^\\[]*\\]", " ");
|
||||||
|
|
||||||
// remove/normalize special characters
|
// remove/normalize special characters
|
||||||
name = name.replaceAll("['`´]+", "");
|
name = name.replaceAll("['`´]+", "");
|
||||||
|
|
|
@ -40,7 +40,7 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
|
||||||
|
|
||||||
private final String apikey;
|
private final String apikey;
|
||||||
|
|
||||||
|
|
||||||
public TheTVDBClient(String apikey) {
|
public TheTVDBClient(String apikey) {
|
||||||
if (apikey == null)
|
if (apikey == null)
|
||||||
throw new NullPointerException("apikey must not be null");
|
throw new NullPointerException("apikey must not be null");
|
||||||
|
@ -48,37 +48,37 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
|
||||||
this.apikey = apikey;
|
this.apikey = apikey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return "TheTVDB";
|
return "TheTVDB";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Icon getIcon() {
|
public Icon getIcon() {
|
||||||
return ResourceManager.getIcon("search.thetvdb");
|
return ResourceManager.getIcon("search.thetvdb");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasSingleSeasonSupport() {
|
public boolean hasSingleSeasonSupport() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasLocaleSupport() {
|
public boolean hasLocaleSupport() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ResultCache getCache() {
|
public ResultCache getCache() {
|
||||||
return new ResultCache(host, CacheManager.getInstance().getCache("web-datasource"));
|
return new ResultCache(host, CacheManager.getInstance().getCache("web-datasource"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<SearchResult> fetchSearchResult(String query, Locale language) throws Exception {
|
public List<SearchResult> fetchSearchResult(String query, Locale language) throws Exception {
|
||||||
// perform online search
|
// perform online search
|
||||||
|
@ -100,7 +100,7 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
|
||||||
return new ArrayList<SearchResult>(resultSet.values());
|
return new ArrayList<SearchResult>(resultSet.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Episode> fetchEpisodeList(SearchResult searchResult, Locale language) throws Exception {
|
public List<Episode> fetchEpisodeList(SearchResult searchResult, Locale language) throws Exception {
|
||||||
TheTVDBSearchResult series = (TheTVDBSearchResult) searchResult;
|
TheTVDBSearchResult series = (TheTVDBSearchResult) searchResult;
|
||||||
|
@ -160,7 +160,7 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
|
||||||
return episodes;
|
return episodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public Document getSeriesRecord(TheTVDBSearchResult searchResult, Locale language) throws Exception {
|
public Document getSeriesRecord(TheTVDBSearchResult searchResult, Locale language) throws Exception {
|
||||||
URL seriesRecord = getResource(MirrorType.ZIP, "/api/" + apikey + "/series/" + searchResult.getSeriesId() + "/all/" + language.getLanguage() + ".zip");
|
URL seriesRecord = getResource(MirrorType.ZIP, "/api/" + apikey + "/series/" + searchResult.getSeriesId() + "/all/" + language.getLanguage() + ".zip");
|
||||||
|
|
||||||
|
@ -183,7 +183,22 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public TheTVDBSearchResult lookup(int id, Locale language) throws Exception {
|
||||||
|
try {
|
||||||
|
URL baseRecordLocation = getResource(MirrorType.XML, "/api/" + apikey + "/series/" + id + "/all/" + language.getLanguage() + ".xml");
|
||||||
|
Document baseRecord = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(baseRecordLocation.openStream());
|
||||||
|
|
||||||
|
String name = selectString("//SeriesName", baseRecord);
|
||||||
|
return new TheTVDBSearchResult(name, id);
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
// illegal series id
|
||||||
|
Logger.getLogger(getClass().getName()).log(Level.WARNING, "Failed to retrieve base series record", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public URI getEpisodeListLink(SearchResult searchResult) {
|
public URI getEpisodeListLink(SearchResult searchResult) {
|
||||||
int seriesId = ((TheTVDBSearchResult) searchResult).getSeriesId();
|
int seriesId = ((TheTVDBSearchResult) searchResult).getSeriesId();
|
||||||
|
@ -191,7 +206,7 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
|
||||||
return URI.create("http://" + host + "/?tab=seasonall&id=" + seriesId);
|
return URI.create("http://" + host + "/?tab=seasonall&id=" + seriesId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public URI getEpisodeListLink(SearchResult searchResult, int season) {
|
public URI getEpisodeListLink(SearchResult searchResult, int season) {
|
||||||
int seriesId = ((TheTVDBSearchResult) searchResult).getSeriesId();
|
int seriesId = ((TheTVDBSearchResult) searchResult).getSeriesId();
|
||||||
|
@ -210,7 +225,7 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected String getMirror(MirrorType mirrorType) throws Exception {
|
protected String getMirror(MirrorType mirrorType) throws Exception {
|
||||||
synchronized (mirrors) {
|
synchronized (mirrors) {
|
||||||
if (mirrors.isEmpty()) {
|
if (mirrors.isEmpty()) {
|
||||||
|
@ -253,7 +268,7 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected URL getResource(MirrorType mirrorType, String path) throws Exception {
|
protected URL getResource(MirrorType mirrorType, String path) throws Exception {
|
||||||
// use default server
|
// use default server
|
||||||
if (mirrorType == null)
|
if (mirrorType == null)
|
||||||
|
@ -263,34 +278,34 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
|
||||||
return new URL(getMirror(mirrorType) + path);
|
return new URL(getMirror(mirrorType) + path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static class TheTVDBSearchResult extends SearchResult {
|
public static class TheTVDBSearchResult extends SearchResult {
|
||||||
|
|
||||||
protected int seriesId;
|
protected int seriesId;
|
||||||
|
|
||||||
|
|
||||||
protected TheTVDBSearchResult() {
|
protected TheTVDBSearchResult() {
|
||||||
// used by serializer
|
// used by serializer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public TheTVDBSearchResult(String seriesName, int seriesId) {
|
public TheTVDBSearchResult(String seriesName, int seriesId) {
|
||||||
super(seriesName);
|
super(seriesName);
|
||||||
this.seriesId = seriesId;
|
this.seriesId = seriesId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public int getSeriesId() {
|
public int getSeriesId() {
|
||||||
return seriesId;
|
return seriesId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return seriesId;
|
return seriesId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object object) {
|
public boolean equals(Object object) {
|
||||||
if (object instanceof TheTVDBSearchResult) {
|
if (object instanceof TheTVDBSearchResult) {
|
||||||
|
@ -302,7 +317,7 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected static enum MirrorType {
|
protected static enum MirrorType {
|
||||||
XML(1),
|
XML(1),
|
||||||
BANNER(2),
|
BANNER(2),
|
||||||
|
@ -310,12 +325,12 @@ public class TheTVDBClient extends AbstractEpisodeListProvider {
|
||||||
|
|
||||||
private final int bitMask;
|
private final int bitMask;
|
||||||
|
|
||||||
|
|
||||||
private MirrorType(int bitMask) {
|
private MirrorType(int bitMask) {
|
||||||
this.bitMask = bitMask;
|
this.bitMask = bitMask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static EnumSet<MirrorType> fromTypeMask(int typeMask) {
|
public static EnumSet<MirrorType> fromTypeMask(int typeMask) {
|
||||||
// initialize enum set with all types
|
// initialize enum set with all types
|
||||||
EnumSet<MirrorType> enumSet = EnumSet.allOf(MirrorType.class);
|
EnumSet<MirrorType> enumSet = EnumSet.allOf(MirrorType.class);
|
||||||
|
|
Loading…
Reference in New Issue