* added episode list support for IMDb
This commit is contained in:
parent
90c8af354d
commit
df143e0305
Binary file not shown.
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
Binary file not shown.
After Width: | Height: | Size: 308 B |
|
@ -16,10 +16,10 @@ public class SeasonEpisodeMatcher {
|
||||||
public SeasonEpisodeMatcher() {
|
public SeasonEpisodeMatcher() {
|
||||||
patterns = new SeasonEpisodePattern[3];
|
patterns = new SeasonEpisodePattern[3];
|
||||||
|
|
||||||
// match patterns like S01E01, s01e02, ... [s01]_[e02], s01.e02, ...
|
// match patterns like S01E01, s01e02, ... [s01]_[e02], s01.e02, s01e02a, ...
|
||||||
patterns[0] = new SeasonEpisodePattern("(?<!\\p{Alnum})[Ss](\\d{1,2})[^\\p{Alnum}]{0,3}[Ee](\\d{1,3})(?!\\p{Digit})");
|
patterns[0] = new SeasonEpisodePattern("(?<!\\p{Alnum})[Ss](\\d{1,2})[^\\p{Alnum}]{0,3}[Ee](\\d{1,3})(?!\\p{Digit})");
|
||||||
|
|
||||||
// match patterns like 1x01, 1.02, ... 10x01, 10.02, ...
|
// match patterns like 1x01, 1.02, ..., 1x01a, 10x01, 10.02, ...
|
||||||
patterns[1] = new SeasonEpisodePattern("(?<!\\p{Alnum})(\\d{1,2})[x\\.](\\d{1,3})(?!\\p{Digit})");
|
patterns[1] = new SeasonEpisodePattern("(?<!\\p{Alnum})(\\d{1,2})[x\\.](\\d{1,3})(?!\\p{Digit})");
|
||||||
|
|
||||||
// match patterns like 01, 102, 1003 (enclosed in separators)
|
// match patterns like 01, 102, 1003 (enclosed in separators)
|
||||||
|
|
|
@ -30,6 +30,7 @@ import net.sourceforge.filebot.ui.transfer.SaveAction;
|
||||||
import net.sourceforge.filebot.web.AnidbClient;
|
import net.sourceforge.filebot.web.AnidbClient;
|
||||||
import net.sourceforge.filebot.web.Episode;
|
import net.sourceforge.filebot.web.Episode;
|
||||||
import net.sourceforge.filebot.web.EpisodeListClient;
|
import net.sourceforge.filebot.web.EpisodeListClient;
|
||||||
|
import net.sourceforge.filebot.web.IMDbClient;
|
||||||
import net.sourceforge.filebot.web.SearchResult;
|
import net.sourceforge.filebot.web.SearchResult;
|
||||||
import net.sourceforge.filebot.web.TVDotComClient;
|
import net.sourceforge.filebot.web.TVDotComClient;
|
||||||
import net.sourceforge.filebot.web.TVRageClient;
|
import net.sourceforge.filebot.web.TVRageClient;
|
||||||
|
@ -74,6 +75,7 @@ public class EpisodeListPanel extends AbstractSearchPanel<EpisodeListClient, Epi
|
||||||
engines.add(new TVRageClient());
|
engines.add(new TVRageClient());
|
||||||
engines.add(new AnidbClient());
|
engines.add(new AnidbClient());
|
||||||
engines.add(new TVDotComClient());
|
engines.add(new TVDotComClient());
|
||||||
|
engines.add(new IMDbClient());
|
||||||
engines.add(new TheTVDBClient(Settings.userRoot().get("thetvdb.apikey")));
|
engines.add(new TheTVDBClient(Settings.userRoot().get("thetvdb.apikey")));
|
||||||
|
|
||||||
return engines;
|
return engines;
|
||||||
|
@ -220,9 +222,10 @@ public class EpisodeListPanel extends AbstractSearchPanel<EpisodeListClient, Epi
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public URI getLink() {
|
public URI getLink() {
|
||||||
if (request.getSeason() != ALL_SEASONS)
|
if (request.getSeason() != ALL_SEASONS) {
|
||||||
return request.getClient().getEpisodeListLink(getSearchResult(), request.getSeason());
|
return request.getClient().getEpisodeListLink(getSearchResult(), request.getSeason());
|
||||||
else
|
}
|
||||||
|
|
||||||
return request.getClient().getEpisodeListLink(getSearchResult());
|
return request.getClient().getEpisodeListLink(getSearchResult());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -79,7 +79,7 @@ class RenameListCellRenderer extends DefaultFancyListCellRenderer {
|
||||||
|
|
||||||
String extension = FileUtilities.getExtension(file);
|
String extension = FileUtilities.getExtension(file);
|
||||||
|
|
||||||
if (!extension.isEmpty())
|
if (extension != null)
|
||||||
return extension;
|
return extension;
|
||||||
|
|
||||||
// some file with no extension
|
// some file with no extension
|
||||||
|
|
|
@ -42,6 +42,7 @@ import net.sourceforge.filebot.ui.SelectDialog;
|
||||||
import net.sourceforge.filebot.web.AnidbClient;
|
import net.sourceforge.filebot.web.AnidbClient;
|
||||||
import net.sourceforge.filebot.web.Episode;
|
import net.sourceforge.filebot.web.Episode;
|
||||||
import net.sourceforge.filebot.web.EpisodeListClient;
|
import net.sourceforge.filebot.web.EpisodeListClient;
|
||||||
|
import net.sourceforge.filebot.web.IMDbClient;
|
||||||
import net.sourceforge.filebot.web.SearchResult;
|
import net.sourceforge.filebot.web.SearchResult;
|
||||||
import net.sourceforge.filebot.web.TVDotComClient;
|
import net.sourceforge.filebot.web.TVDotComClient;
|
||||||
import net.sourceforge.filebot.web.TVRageClient;
|
import net.sourceforge.filebot.web.TVRageClient;
|
||||||
|
@ -143,6 +144,7 @@ public class RenamePanel extends JComponent {
|
||||||
actionPopup.add(new AutoFetchEpisodeListAction(new TVRageClient()));
|
actionPopup.add(new AutoFetchEpisodeListAction(new TVRageClient()));
|
||||||
actionPopup.add(new AutoFetchEpisodeListAction(new AnidbClient()));
|
actionPopup.add(new AutoFetchEpisodeListAction(new AnidbClient()));
|
||||||
actionPopup.add(new AutoFetchEpisodeListAction(new TVDotComClient()));
|
actionPopup.add(new AutoFetchEpisodeListAction(new TVDotComClient()));
|
||||||
|
actionPopup.add(new AutoFetchEpisodeListAction(new IMDbClient()));
|
||||||
actionPopup.add(new AutoFetchEpisodeListAction(new TheTVDBClient(Settings.userRoot().get("thetvdb.apikey"))));
|
actionPopup.add(new AutoFetchEpisodeListAction(new TheTVDBClient(Settings.userRoot().get("thetvdb.apikey"))));
|
||||||
|
|
||||||
actionPopup.addSeparator();
|
actionPopup.addSeparator();
|
||||||
|
|
|
@ -141,7 +141,7 @@ public class AnidbClient implements EpisodeListClient {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public URI getEpisodeListLink(SearchResult searchResult, int season) {
|
public URI getEpisodeListLink(SearchResult searchResult, int season) {
|
||||||
throw new UnsupportedOperationException();
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,162 @@
|
||||||
|
|
||||||
|
package net.sourceforge.filebot.web;
|
||||||
|
|
||||||
|
|
||||||
|
import static net.sourceforge.filebot.web.WebRequest.getHtmlDocument;
|
||||||
|
import static net.sourceforge.tuned.XPathUtilities.getAttribute;
|
||||||
|
import static net.sourceforge.tuned.XPathUtilities.selectNodes;
|
||||||
|
import static net.sourceforge.tuned.XPathUtilities.selectString;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLConnection;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Scanner;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javax.swing.Icon;
|
||||||
|
|
||||||
|
import net.sourceforge.filebot.ResourceManager;
|
||||||
|
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
import org.xml.sax.SAXException;
|
||||||
|
|
||||||
|
|
||||||
|
public class IMDbClient implements EpisodeListClient {
|
||||||
|
|
||||||
|
private static final String host = "www.imdb.com";
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "IMDb";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Icon getIcon() {
|
||||||
|
return ResourceManager.getIcon("search.imdb");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasSingleSeasonSupport() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<SearchResult> search(String query) throws IOException, SAXException {
|
||||||
|
|
||||||
|
URL searchUrl = new URL("http", host, "/find?s=tt&q=" + URLEncoder.encode(query, "UTF-8"));
|
||||||
|
|
||||||
|
Document dom = getHtmlDocument(openConnection(searchUrl));
|
||||||
|
|
||||||
|
List<Node> nodes = selectNodes("//TABLE//A[following-sibling::SMALL[contains(.,'TV series')]]", dom);
|
||||||
|
|
||||||
|
List<SearchResult> results = new ArrayList<SearchResult>(nodes.size());
|
||||||
|
|
||||||
|
for (Node node : nodes) {
|
||||||
|
String name = removeQuotationMarks(node.getTextContent().trim());
|
||||||
|
String year = node.getNextSibling().getTextContent().trim();
|
||||||
|
String href = getAttribute("href", node);
|
||||||
|
|
||||||
|
String nameAndYear = String.format("%s %s", name, year).trim();
|
||||||
|
int imdbId = new Scanner(href).useDelimiter("\\D+").nextInt();
|
||||||
|
|
||||||
|
results.add(new MovieDescriptor(nameAndYear, imdbId));
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Episode> getEpisodeList(SearchResult searchResult) throws IOException, SAXException {
|
||||||
|
Document dom = getHtmlDocument(openConnection(getEpisodeListLink(searchResult).toURL()));
|
||||||
|
|
||||||
|
String seriesName = removeQuotationMarks(selectString("//H1/A", dom));
|
||||||
|
|
||||||
|
List<Node> nodes = selectNodes("//TABLE//H3/A[preceding-sibling::text()]", dom);
|
||||||
|
|
||||||
|
List<Episode> episodes = new ArrayList<Episode>(nodes.size());
|
||||||
|
|
||||||
|
for (Node node : nodes) {
|
||||||
|
String title = node.getTextContent().trim();
|
||||||
|
|
||||||
|
Scanner numberScanner = new Scanner(node.getPreviousSibling().getTextContent()).useDelimiter("\\D+");
|
||||||
|
String season = numberScanner.next();
|
||||||
|
String episode = numberScanner.next();
|
||||||
|
|
||||||
|
episodes.add(new Episode(seriesName, season, episode, title));
|
||||||
|
}
|
||||||
|
|
||||||
|
return episodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Episode> getEpisodeList(SearchResult searchResult, int season) throws Exception {
|
||||||
|
|
||||||
|
List<Episode> episodes = new ArrayList<Episode>(25);
|
||||||
|
|
||||||
|
// remember max. season, so we can throw a proper exception, in case an illegal season number was requested
|
||||||
|
int maxSeason = 0;
|
||||||
|
|
||||||
|
// filter given season from all seasons
|
||||||
|
for (Episode episode : getEpisodeList(searchResult)) {
|
||||||
|
try {
|
||||||
|
int seasonNumber = Integer.parseInt(episode.getSeasonNumber());
|
||||||
|
|
||||||
|
if (season == seasonNumber) {
|
||||||
|
episodes.add(episode);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (seasonNumber > maxSeason) {
|
||||||
|
maxSeason = seasonNumber;
|
||||||
|
}
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
Logger.getLogger(getClass().getName()).log(Level.WARNING, "Illegal season number", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (episodes.isEmpty()) {
|
||||||
|
throw new SeasonOutOfBoundsException(searchResult.getName(), season, maxSeason);
|
||||||
|
}
|
||||||
|
|
||||||
|
return episodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected URLConnection openConnection(URL url) throws IOException {
|
||||||
|
URLConnection connection = url.openConnection();
|
||||||
|
|
||||||
|
// IMDb refuses default user agent (Java/1.6.0_12)
|
||||||
|
connection.addRequestProperty("User-Agent", "Scraper");
|
||||||
|
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected String removeQuotationMarks(String name) {
|
||||||
|
return name.replaceAll("^\"|\"$", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public URI getEpisodeListLink(SearchResult searchResult) {
|
||||||
|
return URI.create("http://" + host + String.format("/title/tt%07d/episodes", ((MovieDescriptor) searchResult).getImdbId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public URI getEpisodeListLink(SearchResult searchResult, int season) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -117,7 +117,7 @@ public class SubsceneSubtitleClient implements SubtitleClient {
|
||||||
Document subtitleListDocument = getSubtitleListDocument(subtitleListUrl, languageFilter);
|
Document subtitleListDocument = getSubtitleListDocument(subtitleListUrl, languageFilter);
|
||||||
|
|
||||||
// let's update language filters if they are not known yet
|
// let's update language filters if they are not known yet
|
||||||
if (languageFilterMap.isEmpty()) {
|
if (languageName != null && languageFilter == null) {
|
||||||
synchronized (languageFilterMap) {
|
synchronized (languageFilterMap) {
|
||||||
languageFilterMap.putAll(getLanguageFilterMap(subtitleListDocument));
|
languageFilterMap.putAll(getLanguageFilterMap(subtitleListDocument));
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ import static net.sourceforge.tuned.XPathUtilities.selectString;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -205,11 +204,7 @@ public class TheTVDBClient implements EpisodeListClient {
|
||||||
public URI getEpisodeListLink(SearchResult searchResult) {
|
public URI getEpisodeListLink(SearchResult searchResult) {
|
||||||
int seriesId = ((TheTVDBSearchResult) searchResult).getSeriesId();
|
int seriesId = ((TheTVDBSearchResult) searchResult).getSeriesId();
|
||||||
|
|
||||||
try {
|
return URI.create("http://" + host + "/?tab=seasonall&id=" + seriesId);
|
||||||
return new URI("http://" + host + "/?tab=seasonall&id=" + seriesId);
|
|
||||||
} catch (URISyntaxException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -230,11 +225,9 @@ public class TheTVDBClient implements EpisodeListClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
return new URI("http://" + host + "/?tab=season&seriesid=" + seriesId + "&seasonid=" + seasonId);
|
return new URI("http://" + host + "/?tab=season&seriesid=" + seriesId + "&seasonid=" + seasonId);
|
||||||
} catch (IOException e) {
|
} catch (Exception e) {
|
||||||
// log and ignore any IOException
|
// log and ignore any IOException
|
||||||
Logger.getLogger(getClass().getName()).log(Level.WARNING, "Failed to retrieve season id", e);
|
Logger.getLogger(getClass().getName()).log(Level.WARNING, "Failed to retrieve season id", e);
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
|
||||||
|
package net.sourceforge.filebot.web;
|
||||||
|
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
|
||||||
|
public class IMDbClientTest {
|
||||||
|
|
||||||
|
private final IMDbClient imdb = new IMDbClient();
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void search() throws Exception {
|
||||||
|
List<SearchResult> results = imdb.search("battlestar");
|
||||||
|
|
||||||
|
MovieDescriptor movie = (MovieDescriptor) results.get(0);
|
||||||
|
|
||||||
|
assertEquals("Battlestar Galactica (2004)", movie.getName());
|
||||||
|
assertEquals(407362, movie.getImdbId(), 0);
|
||||||
|
|
||||||
|
assertEquals(6, results.size(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getEpisodeList() throws Exception {
|
||||||
|
List<Episode> list = imdb.getEpisodeList(new MovieDescriptor("Buffy", 118276));
|
||||||
|
|
||||||
|
assertEquals(145, list.size());
|
||||||
|
|
||||||
|
Episode first = list.get(0);
|
||||||
|
|
||||||
|
assertEquals("Buffy the Vampire Slayer", first.getSeriesName());
|
||||||
|
assertEquals("Unaired Pilot", first.getTitle());
|
||||||
|
assertEquals("0", first.getEpisodeNumber());
|
||||||
|
assertEquals("1", first.getSeasonNumber());
|
||||||
|
|
||||||
|
Episode last = list.get(144);
|
||||||
|
|
||||||
|
assertEquals("Buffy the Vampire Slayer", last.getSeriesName());
|
||||||
|
assertEquals("Chosen", last.getTitle());
|
||||||
|
assertEquals("22", last.getEpisodeNumber());
|
||||||
|
assertEquals("7", last.getSeasonNumber());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getEpisodeListWithUnknownSeason() throws Exception {
|
||||||
|
List<Episode> list = imdb.getEpisodeList(new MovieDescriptor("Mushishi", 807832));
|
||||||
|
|
||||||
|
assertEquals(26, list.size());
|
||||||
|
|
||||||
|
Episode first = list.get(0);
|
||||||
|
|
||||||
|
assertEquals("Mushishi", first.getSeriesName());
|
||||||
|
assertEquals("Midori no za", first.getTitle());
|
||||||
|
assertEquals("1", first.getEpisodeNumber());
|
||||||
|
assertEquals("1", first.getSeasonNumber());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getEpisodeListLink() throws Exception {
|
||||||
|
assertEquals("http://www.imdb.com/title/tt0407362/episodes", imdb.getEpisodeListLink(new MovieDescriptor("Battlestar Galactica", 407362)).toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void removeQuotationMarks() throws Exception {
|
||||||
|
assertEquals("test", imdb.removeQuotationMarks("\"test\""));
|
||||||
|
|
||||||
|
assertEquals("inner \"quotation marks\"", imdb.removeQuotationMarks("\"inner \"quotation marks\"\""));
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,7 +8,7 @@ import org.junit.runners.Suite.SuiteClasses;
|
||||||
|
|
||||||
|
|
||||||
@RunWith(Suite.class)
|
@RunWith(Suite.class)
|
||||||
@SuiteClasses( { TVDotComClientTest.class, AnidbClientTest.class, TVRageClientTest.class, TheTVDBClientTest.class, SubsceneSubtitleClientTest.class, SubtitleSourceClientTest.class, OpenSubtitlesHasherTest.class })
|
@SuiteClasses( { TVDotComClientTest.class, AnidbClientTest.class, TVRageClientTest.class, TheTVDBClientTest.class, IMDbClientTest.class, SubsceneSubtitleClientTest.class, SubtitleSourceClientTest.class, OpenSubtitlesHasherTest.class })
|
||||||
public class WebTestSuite {
|
public class WebTestSuite {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue