mirror of
https://github.com/mitb-archive/filebot
synced 2024-12-22 15:58:52 -05:00
* 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 |
BIN
source/net/sourceforge/filebot/resources/search.imdb.png
Normal file
BIN
source/net/sourceforge/filebot/resources/search.imdb.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 308 B |
@ -16,10 +16,10 @@ public class SeasonEpisodeMatcher {
|
||||
public SeasonEpisodeMatcher() {
|
||||
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})");
|
||||
|
||||
// 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})");
|
||||
|
||||
// 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.Episode;
|
||||
import net.sourceforge.filebot.web.EpisodeListClient;
|
||||
import net.sourceforge.filebot.web.IMDbClient;
|
||||
import net.sourceforge.filebot.web.SearchResult;
|
||||
import net.sourceforge.filebot.web.TVDotComClient;
|
||||
import net.sourceforge.filebot.web.TVRageClient;
|
||||
@ -74,6 +75,7 @@ public class EpisodeListPanel extends AbstractSearchPanel<EpisodeListClient, Epi
|
||||
engines.add(new TVRageClient());
|
||||
engines.add(new AnidbClient());
|
||||
engines.add(new TVDotComClient());
|
||||
engines.add(new IMDbClient());
|
||||
engines.add(new TheTVDBClient(Settings.userRoot().get("thetvdb.apikey")));
|
||||
|
||||
return engines;
|
||||
@ -220,10 +222,11 @@ public class EpisodeListPanel extends AbstractSearchPanel<EpisodeListClient, Epi
|
||||
|
||||
@Override
|
||||
public URI getLink() {
|
||||
if (request.getSeason() != ALL_SEASONS)
|
||||
if (request.getSeason() != ALL_SEASONS) {
|
||||
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);
|
||||
|
||||
if (!extension.isEmpty())
|
||||
if (extension != null)
|
||||
return 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.Episode;
|
||||
import net.sourceforge.filebot.web.EpisodeListClient;
|
||||
import net.sourceforge.filebot.web.IMDbClient;
|
||||
import net.sourceforge.filebot.web.SearchResult;
|
||||
import net.sourceforge.filebot.web.TVDotComClient;
|
||||
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 AnidbClient()));
|
||||
actionPopup.add(new AutoFetchEpisodeListAction(new TVDotComClient()));
|
||||
actionPopup.add(new AutoFetchEpisodeListAction(new IMDbClient()));
|
||||
actionPopup.add(new AutoFetchEpisodeListAction(new TheTVDBClient(Settings.userRoot().get("thetvdb.apikey"))));
|
||||
|
||||
actionPopup.addSeparator();
|
||||
|
@ -141,7 +141,7 @@ public class AnidbClient implements EpisodeListClient {
|
||||
|
||||
@Override
|
||||
public URI getEpisodeListLink(SearchResult searchResult, int season) {
|
||||
throw new UnsupportedOperationException();
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
162
source/net/sourceforge/filebot/web/IMDbClient.java
Normal file
162
source/net/sourceforge/filebot/web/IMDbClient.java
Normal file
@ -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);
|
||||
|
||||
// let's update language filters if they are not known yet
|
||||
if (languageFilterMap.isEmpty()) {
|
||||
if (languageName != null && languageFilter == null) {
|
||||
synchronized (languageFilterMap) {
|
||||
languageFilterMap.putAll(getLanguageFilterMap(subtitleListDocument));
|
||||
}
|
||||
|
@ -10,7 +10,6 @@ import static net.sourceforge.tuned.XPathUtilities.selectString;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.ArrayList;
|
||||
@ -205,11 +204,7 @@ public class TheTVDBClient implements EpisodeListClient {
|
||||
public URI getEpisodeListLink(SearchResult searchResult) {
|
||||
int seriesId = ((TheTVDBSearchResult) searchResult).getSeriesId();
|
||||
|
||||
try {
|
||||
return new URI("http://" + host + "/?tab=seasonall&id=" + seriesId);
|
||||
} catch (URISyntaxException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return URI.create("http://" + host + "/?tab=seasonall&id=" + seriesId);
|
||||
}
|
||||
|
||||
|
||||
@ -230,11 +225,9 @@ public class TheTVDBClient implements EpisodeListClient {
|
||||
}
|
||||
|
||||
return new URI("http://" + host + "/?tab=season&seriesid=" + seriesId + "&seasonid=" + seasonId);
|
||||
} catch (IOException e) {
|
||||
} catch (Exception e) {
|
||||
// log and ignore any IOException
|
||||
Logger.getLogger(getClass().getName()).log(Level.WARNING, "Failed to retrieve season id", e);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
79
test/net/sourceforge/filebot/web/IMDbClientTest.java
Normal file
79
test/net/sourceforge/filebot/web/IMDbClientTest.java
Normal file
@ -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)
|
||||
@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 {
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user