diff --git a/source/net/sourceforge/filebot/format/EpisodeFormatBindingBean.java b/source/net/sourceforge/filebot/format/EpisodeFormatBindingBean.java index 2746b0b2..c933bf5f 100644 --- a/source/net/sourceforge/filebot/format/EpisodeFormatBindingBean.java +++ b/source/net/sourceforge/filebot/format/EpisodeFormatBindingBean.java @@ -57,13 +57,13 @@ public class EpisodeFormatBindingBean { @Define("s") public String getSeasonNumber() { - return episode.getSeasonNumber(); + return episode.getSeason(); } @Define("e") public String getEpisodeNumber() { - return episode.getEpisodeNumber(); + return episode.getEpisode(); } @@ -123,29 +123,31 @@ public class EpisodeFormatBindingBean { @Define("crc32") public String getCRC32() throws IOException, InterruptedException { - if (mediaFile != null) { - // try to get checksum from file name - String checksum = FileBotUtilities.getEmbeddedChecksum(mediaFile.getName()); - - if (checksum != null) - return checksum; - - // try to get checksum from sfv file - checksum = getChecksumFromSfvFile(mediaFile); - - if (checksum != null) - return checksum; - - // calculate checksum from file - return crc32(mediaFile); - } + // make sure media file is defined + checkMediaFile(); - return null; + // try to get checksum from file name + String checksum = FileBotUtilities.getEmbeddedChecksum(mediaFile.getName()); + + if (checksum != null) + return checksum; + + // try to get checksum from sfv file + checksum = getChecksumFromSfvFile(mediaFile); + + if (checksum != null) + return checksum; + + // calculate checksum from file + return crc32(mediaFile); } @Define("ext") public String getContainerExtension() { + // make sure media file is defined + checkMediaFile(); + // file extension return FileUtilities.getExtension(mediaFile); } @@ -193,12 +195,21 @@ public class EpisodeFormatBindingBean { } - private synchronized MediaInfo getMediaInfo() { - if (mediaFile == null) { - throw new NullPointerException("Media file is null"); - } + private void checkMediaFile() { + // make sure file is not null + if (mediaFile == null) + throw new NullPointerException("Media file is not defined"); + // file may not exist at this point but if an existing file is required, + // an exception will be thrown later anyway + } + + + private synchronized MediaInfo getMediaInfo() { if (mediaInfo == null) { + // make sure media file is defined + checkMediaFile(); + mediaInfo = new MediaInfo(); if (!mediaInfo.open(mediaFile)) { diff --git a/source/net/sourceforge/filebot/format/ExpressionFormat.global.js b/source/net/sourceforge/filebot/format/ExpressionFormat.global.js index aaee0cb8..159cd18e 100644 --- a/source/net/sourceforge/filebot/format/ExpressionFormat.global.js +++ b/source/net/sourceforge/filebot/format/ExpressionFormat.global.js @@ -1,4 +1,6 @@ - +/** + * Convenience method to pad strings or numbers with given characters ('0' by default) + */ String.prototype.pad = Number.prototype.pad = function(length, padding) { var s = this.toString(); @@ -13,6 +15,9 @@ String.prototype.pad = Number.prototype.pad = function(length, padding) { } +/** + * Convenience method to replace space characters with a given characters + */ String.prototype.space = function(replacement) { return this.replace(/\s/g, replacement); } diff --git a/source/net/sourceforge/filebot/format/ExpressionFormat.java b/source/net/sourceforge/filebot/format/ExpressionFormat.java index a6fbe3f2..360dff14 100644 --- a/source/net/sourceforge/filebot/format/ExpressionFormat.java +++ b/source/net/sourceforge/filebot/format/ExpressionFormat.java @@ -18,6 +18,7 @@ import java.security.PrivilegedExceptionAction; import java.security.ProtectionDomain; import java.text.FieldPosition; import java.text.Format; +import java.text.NumberFormat; import java.text.ParsePosition; import java.util.ArrayList; import java.util.List; @@ -125,6 +126,11 @@ public class ExpressionFormat extends Format { Object value = ((CompiledScript) snipped).eval(context); if (value != null) { + if (value instanceof Double && value.equals(Math.floor((Double) value))) { + // value is really an integer, not a decimal (number literals -1, 0 and 1 are interpreted as decimal by rhino) + value = NumberFormat.getIntegerInstance().format(value); + } + sb.append(value); } } catch (ScriptException e) { diff --git a/source/net/sourceforge/filebot/resources/action.extension.include.png b/source/net/sourceforge/filebot/resources/action.extension.override.png similarity index 100% rename from source/net/sourceforge/filebot/resources/action.extension.include.png rename to source/net/sourceforge/filebot/resources/action.extension.override.png diff --git a/source/net/sourceforge/filebot/similarity/SeasonEpisodeMatcher.java b/source/net/sourceforge/filebot/similarity/SeasonEpisodeMatcher.java index 1591c26b..de2edd83 100644 --- a/source/net/sourceforge/filebot/similarity/SeasonEpisodeMatcher.java +++ b/source/net/sourceforge/filebot/similarity/SeasonEpisodeMatcher.java @@ -64,6 +64,8 @@ public class SeasonEpisodeMatcher { public static class SxE { + public static final int UNDEFINED = -1; + public final int season; public final int episode; @@ -81,7 +83,11 @@ public class SeasonEpisodeMatcher { protected int parse(String number) { - return number == null || number.isEmpty() ? 0 : Integer.parseInt(number); + try { + return Integer.parseInt(number); + } catch (Exception e) { + return UNDEFINED; + } } diff --git a/source/net/sourceforge/filebot/similarity/SeasonEpisodeSimilarityMetric.java b/source/net/sourceforge/filebot/similarity/SeasonEpisodeSimilarityMetric.java index 049a7cc5..1e60b0d1 100644 --- a/source/net/sourceforge/filebot/similarity/SeasonEpisodeSimilarityMetric.java +++ b/source/net/sourceforge/filebot/similarity/SeasonEpisodeSimilarityMetric.java @@ -27,12 +27,12 @@ public class SeasonEpisodeSimilarityMetric implements SimilarityMetric { for (SxE sxe1 : sxeVector1) { for (SxE sxe2 : sxeVector2) { - if (sxe1.episode == sxe2.episode) { - if (sxe1.season == sxe2.season) { - // vectors have at least one perfect episode match in common - return 1; - } - + if (sxe1.episode == sxe2.episode && sxe1.season == sxe2.season) { + // vectors have at least one perfect episode match in common + return 1; + } + + if (sxe1.episode == sxe2.episode || sxe1.season == sxe2.season) { // at least we have a partial match similarity = 0.5f; } diff --git a/source/net/sourceforge/filebot/ui/EpisodeFormatDialog.java b/source/net/sourceforge/filebot/ui/EpisodeFormatDialog.java index 5fa52467..09536175 100644 --- a/source/net/sourceforge/filebot/ui/EpisodeFormatDialog.java +++ b/source/net/sourceforge/filebot/ui/EpisodeFormatDialog.java @@ -54,7 +54,7 @@ import net.sourceforge.filebot.Settings; import net.sourceforge.filebot.format.EpisodeFormatBindingBean; import net.sourceforge.filebot.format.ExpressionFormat; import net.sourceforge.filebot.web.Episode; -import net.sourceforge.filebot.web.Episode.EpisodeFormat; +import net.sourceforge.filebot.web.EpisodeFormat; import net.sourceforge.tuned.DefaultThreadFactory; import net.sourceforge.tuned.ExceptionUtilities; import net.sourceforge.tuned.ui.GradientStyle; @@ -392,10 +392,13 @@ public class EpisodeFormatDialog extends JDialog { try { preview.setText(get()); - // check internal script exception and empty output + // check internal script exception if (format.scriptException() != null) { throw format.scriptException(); - } else if (get().trim().isEmpty()) { + } + + // check empty output + if (get().trim().isEmpty()) { throw new RuntimeException("Formatted value is empty"); } @@ -406,7 +409,7 @@ public class EpisodeFormatDialog extends JDialog { status.setIcon(ResourceManager.getIcon("status.warning")); status.setVisible(true); } finally { - preview.setVisible(true); + preview.setVisible(preview.getText().trim().length() > 0); editor.setForeground(defaultColor); progressIndicatorTimer.stop(); diff --git a/source/net/sourceforge/filebot/ui/panel/rename/AutoFetchEpisodeListMatcher.java b/source/net/sourceforge/filebot/ui/panel/rename/AutoFetchEpisodeListMatcher.java index 4a2a9656..5b56d8e0 100644 --- a/source/net/sourceforge/filebot/ui/panel/rename/AutoFetchEpisodeListMatcher.java +++ b/source/net/sourceforge/filebot/ui/panel/rename/AutoFetchEpisodeListMatcher.java @@ -81,12 +81,14 @@ class AutoFetchEpisodeListMatcher extends SwingWorker> public Collection call() throws Exception { Collection results = provider.search(seriesName); - if (results.size() > 0) { - SearchResult selectedSearchResult = selectSearchResult(seriesName, results); - - if (selectedSearchResult != null) { - return provider.getEpisodeList(selectedSearchResult); - } + if (results.isEmpty()) { + throw new RuntimeException(String.format("'%s' has not been found.", seriesName)); + } + + SearchResult selectedSearchResult = selectSearchResult(seriesName, results); + + if (selectedSearchResult != null) { + return provider.getEpisodeList(selectedSearchResult); } return Collections.emptyList(); diff --git a/source/net/sourceforge/filebot/ui/panel/rename/EpisodeExpressionFormatter.java b/source/net/sourceforge/filebot/ui/panel/rename/EpisodeExpressionFormatter.java index 61d2a209..71e31281 100644 --- a/source/net/sourceforge/filebot/ui/panel/rename/EpisodeExpressionFormatter.java +++ b/source/net/sourceforge/filebot/ui/panel/rename/EpisodeExpressionFormatter.java @@ -10,7 +10,7 @@ import net.sourceforge.filebot.format.EpisodeFormatBindingBean; import net.sourceforge.filebot.format.ExpressionFormat; import net.sourceforge.filebot.similarity.Match; import net.sourceforge.filebot.web.Episode; -import net.sourceforge.filebot.web.Episode.EpisodeFormat; +import net.sourceforge.filebot.web.EpisodeFormat; public class EpisodeExpressionFormatter extends ExpressionFormat implements MatchFormatter { diff --git a/source/net/sourceforge/filebot/ui/panel/rename/MatchAction.java b/source/net/sourceforge/filebot/ui/panel/rename/MatchAction.java index c56771a0..71d09816 100644 --- a/source/net/sourceforge/filebot/ui/panel/rename/MatchAction.java +++ b/source/net/sourceforge/filebot/ui/panel/rename/MatchAction.java @@ -79,13 +79,8 @@ class MatchAction extends AbstractAction { if (o instanceof Episode) { Episode episode = (Episode) o; - try { - // create SxE from episode - return Collections.singleton(new SxE(episode.getSeasonNumber(), episode.getEpisodeNumber())); - } catch (NumberFormatException e) { - // some kind of special episode, no SxE - return null; - } + // create SxE from episode + return Collections.singleton(new SxE(episode.getSeason(), episode.getEpisode())); } return super.parse(o); diff --git a/source/net/sourceforge/filebot/ui/panel/rename/RenamePanel.java b/source/net/sourceforge/filebot/ui/panel/rename/RenamePanel.java index 1e77f40b..bc3bbd13 100644 --- a/source/net/sourceforge/filebot/ui/panel/rename/RenamePanel.java +++ b/source/net/sourceforge/filebot/ui/panel/rename/RenamePanel.java @@ -180,12 +180,12 @@ public class RenamePanel extends JComponent { protected ActionPopup createSettingsPopup() { - ActionPopup actionPopup = new ActionPopup("Settings", ResourceManager.getIcon("action.rename.small")); + ActionPopup actionPopup = new ActionPopup("Rename Settings", ResourceManager.getIcon("action.rename.small")); actionPopup.addDescription(new JLabel("Extension:")); actionPopup.add(new PreserveExtensionAction(true, "Preserve", ResourceManager.getIcon("action.extension.preserve"))); - actionPopup.add(new PreserveExtensionAction(false, "Include", ResourceManager.getIcon("action.extension.include"))); + actionPopup.add(new PreserveExtensionAction(false, "Override", ResourceManager.getIcon("action.extension.override"))); return actionPopup; } diff --git a/source/net/sourceforge/filebot/web/AnidbClient.java b/source/net/sourceforge/filebot/web/AnidbClient.java index bc5cb762..70b3a033 100644 --- a/source/net/sourceforge/filebot/web/AnidbClient.java +++ b/source/net/sourceforge/filebot/web/AnidbClient.java @@ -113,7 +113,7 @@ public class AnidbClient implements EpisodeListProvider { // if number does not match, episode is probably some kind of special (S1, S2, ...) if (number.matches("\\d+")) { // no seasons for anime - episodes.add(new Episode(searchResult.getName(), number, title)); + episodes.add(new Episode(searchResult.getName(), null, number, title)); } } diff --git a/source/net/sourceforge/filebot/web/Episode.java b/source/net/sourceforge/filebot/web/Episode.java index a8922475..12e3037c 100644 --- a/source/net/sourceforge/filebot/web/Episode.java +++ b/source/net/sourceforge/filebot/web/Episode.java @@ -3,42 +3,54 @@ package net.sourceforge.filebot.web; import java.io.Serializable; -import java.text.FieldPosition; -import java.text.Format; -import java.text.ParseException; -import java.text.ParsePosition; -import java.util.regex.Matcher; -import java.util.regex.Pattern; public class Episode implements Serializable { - private String seriesName; - private String seasonNumber; - private String episodeNumber; - private String title; + private final String seriesName; + private final String season; + private final String episode; + private final String title; - public Episode(String seriesName, String seasonNumber, String episodeNumber, String title) { + public Episode(String seriesName, Integer season, Integer episode, String title) { + this(seriesName, season.toString(), episode.toString(), title); + } + + + public Episode(String seriesName, String season, String episode, String title) { this.seriesName = seriesName; - this.seasonNumber = seasonNumber; - this.episodeNumber = episodeNumber; + this.season = season; + this.episode = episode; this.title = title; } - public Episode(String seriesName, String episodeNumber, String title) { - this(seriesName, null, episodeNumber, title); + public String getEpisode() { + return episode; } - public String getEpisodeNumber() { - return episodeNumber; + public Integer getEpisodeNumber() { + try { + return Integer.valueOf(episode); + } catch (NumberFormatException e) { + return null; + } } - public String getSeasonNumber() { - return seasonNumber; + public String getSeason() { + return season; + } + + + public Integer getSeasonNumber() { + try { + return Integer.valueOf(season); + } catch (NumberFormatException e) { + return null; + } } @@ -57,66 +69,4 @@ public class Episode implements Serializable { return EpisodeFormat.getInstance().format(this); } - - public static class EpisodeFormat extends Format { - - private static final EpisodeFormat instance = new EpisodeFormat(); - - - public static EpisodeFormat getInstance() { - return instance; - } - - - @Override - public StringBuffer format(Object obj, StringBuffer sb, FieldPosition pos) { - Episode episode = (Episode) obj; - - sb.append(episode.getSeriesName()).append(" - "); - - if (episode.getSeasonNumber() != null) { - sb.append(episode.getSeasonNumber()).append('x'); - } - - try { - // try to format episode number - sb.append(String.format("%02d", Integer.parseInt(episode.getEpisodeNumber()))); - } catch (NumberFormatException e) { - // use episode "number" as is - sb.append(episode.getEpisodeNumber()); - } - - return sb.append(" - ").append(episode.getTitle()); - } - - - @Override - public Episode parseObject(String source, ParsePosition pos) { - Pattern pattern = Pattern.compile("(.*) - (?:(\\w+?)x)?(\\w+)? - (.*)"); - - Matcher matcher = pattern.matcher(source).region(pos.getIndex(), source.length()); - - if (!matcher.matches()) { - pos.setErrorIndex(matcher.regionStart()); - return null; - } - - // episode number must not be null - if (matcher.group(3) == null) { - pos.setErrorIndex(matcher.start(3)); - return null; - } - - pos.setIndex(matcher.end()); - return new Episode(matcher.group(1), matcher.group(2), matcher.group(3), matcher.group(4)); - } - - - @Override - public Episode parseObject(String source) throws ParseException { - return (Episode) super.parseObject(source); - } - - } - } diff --git a/source/net/sourceforge/filebot/web/EpisodeFormat.java b/source/net/sourceforge/filebot/web/EpisodeFormat.java new file mode 100644 index 00000000..abc651b4 --- /dev/null +++ b/source/net/sourceforge/filebot/web/EpisodeFormat.java @@ -0,0 +1,69 @@ + +package net.sourceforge.filebot.web; + + +import java.text.FieldPosition; +import java.text.Format; +import java.text.ParseException; +import java.text.ParsePosition; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + + +public class EpisodeFormat extends Format { + + private static final EpisodeFormat instance = new EpisodeFormat(); + + + public static EpisodeFormat getInstance() { + return instance; + } + + + @Override + public StringBuffer format(Object obj, StringBuffer sb, FieldPosition pos) { + Episode episode = (Episode) obj; + + sb.append(episode.getSeriesName()).append(" - "); + + if (episode.getSeason() != null) { + sb.append(episode.getSeason()).append('x'); + } + + Integer episodeNumber = episode.getEpisodeNumber(); + + // try to format episode number, or use episode "number" string as is + sb.append(episodeNumber != null ? String.format("%02d", episodeNumber) : episode.getEpisode()); + + return sb.append(" - ").append(episode.getTitle()); + } + + + @Override + public Episode parseObject(String source, ParsePosition pos) { + Pattern pattern = Pattern.compile("(.*) - (?:(\\w+?)x)?(\\w+)? - (.*)"); + + Matcher matcher = pattern.matcher(source).region(pos.getIndex(), source.length()); + + if (!matcher.matches()) { + pos.setErrorIndex(matcher.regionStart()); + return null; + } + + // episode number must not be null + if (matcher.group(3) == null) { + pos.setErrorIndex(matcher.start(3)); + return null; + } + + pos.setIndex(matcher.end()); + return new Episode(matcher.group(1), matcher.group(2), matcher.group(3), matcher.group(4)); + } + + + @Override + public Episode parseObject(String source) throws ParseException { + return (Episode) super.parseObject(source); + } + +} diff --git a/source/net/sourceforge/filebot/web/EpisodeListUtilities.java b/source/net/sourceforge/filebot/web/EpisodeListUtilities.java index a2962ca1..8162c4fd 100644 --- a/source/net/sourceforge/filebot/web/EpisodeListUtilities.java +++ b/source/net/sourceforge/filebot/web/EpisodeListUtilities.java @@ -15,7 +15,7 @@ public final class EpisodeListUtilities { // filter given season from all seasons for (Episode episode : episodes) { try { - if (season == Integer.parseInt(episode.getSeasonNumber())) { + if (season == Integer.parseInt(episode.getSeason())) { results.add(episode); } } catch (NumberFormatException e) { @@ -33,7 +33,7 @@ public final class EpisodeListUtilities { // filter given season from all seasons for (Episode episode : episodes) { try { - lastSeason = Math.max(lastSeason, Integer.parseInt(episode.getSeasonNumber())); + lastSeason = Math.max(lastSeason, Integer.parseInt(episode.getSeason())); } catch (NumberFormatException e) { // ignore illegal episodes } diff --git a/source/net/sourceforge/filebot/web/IMDbClient.java b/source/net/sourceforge/filebot/web/IMDbClient.java index a95c71f6..5f72b790 100644 --- a/source/net/sourceforge/filebot/web/IMDbClient.java +++ b/source/net/sourceforge/filebot/web/IMDbClient.java @@ -18,6 +18,8 @@ import java.net.URLEncoder; import java.util.ArrayList; import java.util.List; import java.util.Scanner; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.swing.Icon; @@ -68,9 +70,16 @@ public class IMDbClient implements EpisodeListProvider { 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)); + results.add(new MovieDescriptor(nameAndYear, getImdbId(href))); + } + + // we might have been redirected to the movie page + if (results.isEmpty()) { + String name = removeQuotationMarks(selectString("//H1/text()", dom)); + String url = selectString("//LINK[@rel='canonical']/@href", dom); + + results.add(new MovieDescriptor(name, getImdbId(url))); } return results; @@ -129,6 +138,30 @@ public class IMDbClient implements EpisodeListProvider { } + protected int getImdbId(String link) { + try { + // try to extract path + link = new URI(link).getPath(); + } catch (URISyntaxException e) { + // cannot extract path component, just move on + } + + Matcher matcher = Pattern.compile("tt(\\d{7})").matcher(link); + + String imdbId = null; + + // find last match + while (matcher.find()) { + imdbId = matcher.group(1); + } + + if (imdbId == null) + throw new IllegalArgumentException(String.format("Cannot find imdb id: %s", link)); + + return Integer.parseInt(imdbId); + } + + @Override public URI getEpisodeListLink(SearchResult searchResult) { return getEpisodeListLink(searchResult, 0); diff --git a/source/net/sourceforge/filebot/web/TVDotComClient.java b/source/net/sourceforge/filebot/web/TVDotComClient.java index 2f0f85c7..2e1a4171 100644 --- a/source/net/sourceforge/filebot/web/TVDotComClient.java +++ b/source/net/sourceforge/filebot/web/TVDotComClient.java @@ -144,23 +144,34 @@ public class TVDotComClient implements EpisodeListProvider { List nodes = selectNodes("id('episode_guide_list')//*[@class='info']", dom); - Pattern seasonEpisodePattern = Pattern.compile("Season (\\d+), Episode (\\d+)"); + Pattern episodePattern = Pattern.compile("Season (\\d+). Episode (\\d+)"); + Pattern specialPattern = Pattern.compile("Special. Season (\\d+)"); List episodes = new ArrayList(nodes.size()); for (Node node : nodes) { - String meta = selectString("./*[@class='meta']", node); + String title = selectString("./H3/A/text()", node); + String meta = selectString("./*[@class='meta']", node).replaceAll("\\p{Space}+", " "); - // normalize space and then match season and episode numbers - Matcher matcher = seasonEpisodePattern.matcher(meta.replaceAll("\\p{Space}+", " ")); + String season = null; + String episode = null; - if (matcher.find()) { - String title = selectString("./H3/A/text()", node); - String season = matcher.group(1); - String episode = matcher.group(2); - - episodes.add(new Episode(searchResult.getName(), season, episode, title)); + Matcher matcher; + + if ((matcher = episodePattern.matcher(meta)).find()) { + // matches episode + season = matcher.group(1); + episode = matcher.group(2); + } else if ((matcher = specialPattern.matcher(meta)).find()) { + // matches special + season = matcher.group(1); + episode = "Special"; + } else { + // no episode match + continue; } + + episodes.add(new Episode(searchResult.getName(), season, episode, title)); } return episodes; diff --git a/source/net/sourceforge/filebot/web/TVRageClient.java b/source/net/sourceforge/filebot/web/TVRageClient.java index 16814ac4..2fa11e9c 100644 --- a/source/net/sourceforge/filebot/web/TVRageClient.java +++ b/source/net/sourceforge/filebot/web/TVRageClient.java @@ -5,6 +5,7 @@ package net.sourceforge.filebot.web; import static net.sourceforge.filebot.web.EpisodeListUtilities.filterBySeason; import static net.sourceforge.filebot.web.EpisodeListUtilities.getLastSeason; import static net.sourceforge.filebot.web.WebRequest.getDocument; +import static net.sourceforge.tuned.XPathUtilities.getAttribute; import static net.sourceforge.tuned.XPathUtilities.getTextContent; import static net.sourceforge.tuned.XPathUtilities.selectNodes; import static net.sourceforge.tuned.XPathUtilities.selectString; @@ -94,14 +95,20 @@ public class TVRageClient implements EpisodeListProvider { Document dom = getDocument(episodeListUrl); String seriesName = selectString("Show/name", dom); - List nodes = selectNodes("Show/Episodelist/Season/episode", dom); - List episodes = new ArrayList(nodes.size()); + List episodes = new ArrayList(25); - for (Node node : nodes) { + // episodes and specials + for (Node node : selectNodes("//episode", dom)) { String title = getTextContent("title", node); String episodeNumber = getTextContent("seasonnum", node); - String seasonNumber = node.getParentNode().getAttributes().getNamedItem("no").getTextContent(); + String seasonNumber = getAttribute("no", node.getParentNode()); + + // check if we have season and episode number, if not it must be a special episode + if (episodeNumber == null || seasonNumber == null) { + episodeNumber = "Special"; + seasonNumber = getTextContent("season", node); + } episodes.add(new Episode(seriesName, seasonNumber, episodeNumber, title)); } diff --git a/source/net/sourceforge/filebot/web/TheTVDBClient.java b/source/net/sourceforge/filebot/web/TheTVDBClient.java index f816692b..93a0e947 100644 --- a/source/net/sourceforge/filebot/web/TheTVDBClient.java +++ b/source/net/sourceforge/filebot/web/TheTVDBClient.java @@ -144,6 +144,13 @@ public class TheTVDBClient implements EpisodeListProvider { String episodeNumber = getTextContent("EpisodeNumber", node); String seasonNumber = getTextContent("SeasonNumber", node); + if (seasonNumber.equals("0")) { + String airsBefore = getTextContent("airsbefore_season", node); + + seasonNumber = airsBefore.isEmpty() ? null : airsBefore; + episodeNumber = "Special"; + } + episodes.add(new Episode(seriesName, seasonNumber, episodeNumber, episodeName)); if (episodeNumber.equals("1")) { diff --git a/source/net/sourceforge/tuned/XPathUtilities.java b/source/net/sourceforge/tuned/XPathUtilities.java index b1c5398f..6cd38746 100644 --- a/source/net/sourceforge/tuned/XPathUtilities.java +++ b/source/net/sourceforge/tuned/XPathUtilities.java @@ -77,7 +77,12 @@ public final class XPathUtilities { public static String getAttribute(String attribute, Node node) { - return node.getAttributes().getNamedItem(attribute).getNodeValue().trim(); + Node attributeNode = node.getAttributes().getNamedItem(attribute); + + if (attributeNode != null) + return attributeNode.getNodeValue().trim(); + + return null; } diff --git a/test/net/sourceforge/filebot/ArgumentBeanTest.java b/test/net/sourceforge/filebot/ArgumentBeanTest.java index c212ee73..c219ac5e 100644 --- a/test/net/sourceforge/filebot/ArgumentBeanTest.java +++ b/test/net/sourceforge/filebot/ArgumentBeanTest.java @@ -15,7 +15,7 @@ public class ArgumentBeanTest { @Test public void clear() throws Exception { - ArgumentBean bean = parse("-clear", "--analyze", "One Piece", "Naruto"); + ArgumentBean bean = parse("-clear", "--sfv", "One Piece", "Naruto"); assertTrue(bean.clear()); assertFalse(bean.help()); diff --git a/test/net/sourceforge/filebot/similarity/SeasonEpisodeMatcherTest.java b/test/net/sourceforge/filebot/similarity/SeasonEpisodeMatcherTest.java index f04e7860..bddc84ef 100644 --- a/test/net/sourceforge/filebot/similarity/SeasonEpisodeMatcherTest.java +++ b/test/net/sourceforge/filebot/similarity/SeasonEpisodeMatcherTest.java @@ -2,7 +2,9 @@ package net.sourceforge.filebot.similarity; +import static net.sourceforge.filebot.similarity.SeasonEpisodeMatcher.SxE.UNDEFINED; import static org.junit.Assert.assertEquals; +import net.sourceforge.filebot.similarity.SeasonEpisodeMatcher.SxE; import org.junit.Test; @@ -15,52 +17,52 @@ public class SeasonEpisodeMatcherTest { @Test public void patternPrecedence() { // S01E01 pattern has highest precedence - assertEquals("1x03", matcher.match("Test.101.1x02.S01E03").get(0).toString()); + assertEquals(new SxE(1, 3), matcher.match("Test.101.1x02.S01E03").get(0)); // multiple values - assertEquals("1x02", matcher.match("Test.42.s01e01.s01e02.300").get(1).toString()); + assertEquals(new SxE(1, 2), matcher.match("Test.42.s01e01.s01e02.300").get(1)); } @Test public void pattern_1x01() { - assertEquals("1x01", matcher.match("1x01").get(0).toString()); + assertEquals(new SxE(1, 1), matcher.match("1x01").get(0)); // test multiple matches - assertEquals("1x02", matcher.match("Test - 1x01 and 1.02 - Multiple MatchCollection").get(1).toString()); + assertEquals(new SxE(1, 2), matcher.match("Test - 1x01 and 1.02 - Multiple MatchCollection").get(1)); // test high values - assertEquals("12x345", matcher.match("Test - 12x345 - High Values").get(0).toString()); + assertEquals(new SxE(12, 345), matcher.match("Test - 12x345 - High Values").get(0)); // test lookahead and lookbehind - assertEquals("1x03", matcher.match("Test_-_103_[1280x720]").get(0).toString()); + assertEquals(new SxE(1, 3), matcher.match("Test_-_103_[1280x720]").get(0)); } @Test public void pattern_S01E01() { - assertEquals("1x01", matcher.match("S01E01").get(0).toString()); + assertEquals(new SxE(1, 1), matcher.match("S01E01").get(0)); // test multiple matches - assertEquals("1x02", matcher.match("S01E01 and S01E02 - Multiple MatchCollection").get(1).toString()); + assertEquals(new SxE(1, 2), matcher.match("S01E01 and S01E02 - Multiple MatchCollection").get(1)); // test separated values - assertEquals("1x03", matcher.match("[s01]_[e03]").get(0).toString()); + assertEquals(new SxE(1, 3), matcher.match("[s01]_[e03]").get(0)); // test high values - assertEquals("12x345", matcher.match("Test - S12E345 - High Values").get(0).toString()); + assertEquals(new SxE(12, 345), matcher.match("Test - S12E345 - High Values").get(0)); } @Test public void pattern_101() { - assertEquals("1x01", matcher.match("Test.101").get(0).toString()); + assertEquals(new SxE(1, 1), matcher.match("Test.101").get(0)); // test 2-digit number - assertEquals("0x02", matcher.match("02").get(0).toString()); + assertEquals(new SxE(UNDEFINED, 2), matcher.match("02").get(0)); // test high values - assertEquals("10x01", matcher.match("[Test]_1001_High_Values").get(0).toString()); + assertEquals(new SxE(10, 1), matcher.match("[Test]_1001_High_Values").get(0)); // first two digits <= 29 assertEquals(null, matcher.match("The 4400")); diff --git a/test/net/sourceforge/filebot/similarity/SeasonEpisodeSimilarityMetricTest.java b/test/net/sourceforge/filebot/similarity/SeasonEpisodeSimilarityMetricTest.java index e591164c..a6a3a543 100644 --- a/test/net/sourceforge/filebot/similarity/SeasonEpisodeSimilarityMetricTest.java +++ b/test/net/sourceforge/filebot/similarity/SeasonEpisodeSimilarityMetricTest.java @@ -20,8 +20,8 @@ public class SeasonEpisodeSimilarityMetricTest { // multiple pattern matches, single episode match assertEquals(1.0, metric.getSimilarity("1x02a", "101 102 103"), 0); - // multiple pattern matches, no episode match - assertEquals(0.0, metric.getSimilarity("1x03b", "104 105 106"), 0); + // multiple pattern matches, only partial match (season) + assertEquals(0.5, metric.getSimilarity("1x03b", "104 105 106"), 0); // no pattern match, no episode match assertEquals(0.0, metric.getSimilarity("abc", "xyz"), 0); diff --git a/test/net/sourceforge/filebot/web/AnidbClientTest.java b/test/net/sourceforge/filebot/web/AnidbClientTest.java index d4d7f4c9..eb9855da 100644 --- a/test/net/sourceforge/filebot/web/AnidbClientTest.java +++ b/test/net/sourceforge/filebot/web/AnidbClientTest.java @@ -67,8 +67,8 @@ public class AnidbClientTest { assertEquals("Monster", first.getSeriesName()); assertEquals("Herr Dr. Tenma", first.getTitle()); - assertEquals("1", first.getEpisodeNumber()); - assertEquals(null, first.getSeasonNumber()); + assertEquals("1", first.getEpisode()); + assertEquals(null, first.getSeason()); } @@ -82,8 +82,8 @@ public class AnidbClientTest { assertEquals("Juuni Kokuki", first.getSeriesName()); assertEquals("Shadow of the Moon, The Sea of Shadow - Chapter 1", first.getTitle()); - assertEquals("1", first.getEpisodeNumber()); - assertEquals(null, first.getSeasonNumber()); + assertEquals("1", first.getEpisode()); + assertEquals(null, first.getSeason()); } diff --git a/test/net/sourceforge/filebot/web/IMDbClientTest.java b/test/net/sourceforge/filebot/web/IMDbClientTest.java index 4a8bc1d0..9f0c65e5 100644 --- a/test/net/sourceforge/filebot/web/IMDbClientTest.java +++ b/test/net/sourceforge/filebot/web/IMDbClientTest.java @@ -27,6 +27,20 @@ public class IMDbClientTest { } + @Test + public void searchResultPageRedirect() throws Exception { + List results = imdb.search("my name is earl"); + + // exactly one search result + assertEquals(1, results.size(), 0); + + MovieDescriptor movie = (MovieDescriptor) results.get(0); + + assertEquals("My Name Is Earl", movie.getName()); + assertEquals(460091, movie.getImdbId(), 0); + } + + @Test public void getEpisodeList() throws Exception { List list = imdb.getEpisodeList(new MovieDescriptor("Buffy", 118276)); @@ -37,15 +51,15 @@ public class IMDbClientTest { assertEquals("Buffy the Vampire Slayer", first.getSeriesName()); assertEquals("Unaired Pilot", first.getTitle()); - assertEquals("0", first.getEpisodeNumber()); - assertEquals("1", first.getSeasonNumber()); + assertEquals("0", first.getEpisode()); + assertEquals("1", first.getSeason()); Episode last = list.get(144); assertEquals("Buffy the Vampire Slayer", last.getSeriesName()); assertEquals("Chosen", last.getTitle()); - assertEquals("22", last.getEpisodeNumber()); - assertEquals("7", last.getSeasonNumber()); + assertEquals("22", last.getEpisode()); + assertEquals("7", last.getSeason()); } @@ -59,8 +73,8 @@ public class IMDbClientTest { assertEquals("Mushishi", first.getSeriesName()); assertEquals("Midori no za", first.getTitle()); - assertEquals("1", first.getEpisodeNumber()); - assertEquals("1", first.getSeasonNumber()); + assertEquals("1", first.getEpisode()); + assertEquals("1", first.getSeason()); } diff --git a/test/net/sourceforge/filebot/web/TVDotComClientTest.java b/test/net/sourceforge/filebot/web/TVDotComClientTest.java index a9ba8a97..1657676c 100644 --- a/test/net/sourceforge/filebot/web/TVDotComClientTest.java +++ b/test/net/sourceforge/filebot/web/TVDotComClientTest.java @@ -49,8 +49,8 @@ public class TVDotComClientTest { assertEquals("Buffy the Vampire Slayer", chosen.getSeriesName()); assertEquals("Chosen", chosen.getTitle()); - assertEquals("22", chosen.getEpisodeNumber()); - assertEquals("7", chosen.getSeasonNumber()); + assertEquals("22", chosen.getEpisode()); + assertEquals("7", chosen.getSeason()); } @@ -65,8 +65,8 @@ public class TVDotComClientTest { assertEquals("Buffy the Vampire Slayer", first.getSeriesName()); assertEquals("Welcome to the Hellmouth (1)", first.getTitle()); - assertEquals("1", first.getEpisodeNumber()); - assertEquals("1", first.getSeasonNumber()); + assertEquals("1", first.getEpisode()); + assertEquals("1", first.getSeason()); } @@ -81,8 +81,8 @@ public class TVDotComClientTest { assertEquals("Firefly", fourth.getSeriesName()); assertEquals("Jaynestown", fourth.getTitle()); - assertEquals("4", fourth.getEpisodeNumber()); - assertEquals("1", fourth.getSeasonNumber()); + assertEquals("4", fourth.getEpisode()); + assertEquals("1", fourth.getSeason()); } @@ -104,8 +104,8 @@ public class TVDotComClientTest { assertEquals("Lost", episode.getSeriesName()); assertEquals("Exposé", episode.getTitle()); - assertEquals("14", episode.getEpisodeNumber()); - assertEquals("3", episode.getSeasonNumber()); + assertEquals("14", episode.getEpisode()); + assertEquals("3", episode.getSeason()); } diff --git a/test/net/sourceforge/filebot/web/TVRageClientTest.java b/test/net/sourceforge/filebot/web/TVRageClientTest.java index aee409ae..cd6f873d 100644 --- a/test/net/sourceforge/filebot/web/TVRageClientTest.java +++ b/test/net/sourceforge/filebot/web/TVRageClientTest.java @@ -43,8 +43,8 @@ public class TVRageClientTest { assertEquals("Buffy the Vampire Slayer", chosen.getSeriesName()); assertEquals("Chosen", chosen.getTitle()); - assertEquals("22", chosen.getEpisodeNumber()); - assertEquals("7", chosen.getSeasonNumber()); + assertEquals("22", chosen.getEpisode()); + assertEquals("7", chosen.getSeason()); } @@ -58,8 +58,8 @@ public class TVRageClientTest { assertEquals("Buffy the Vampire Slayer", first.getSeriesName()); assertEquals("Unaired Pilot", first.getTitle()); - assertEquals("00", first.getEpisodeNumber()); - assertEquals("0", first.getSeasonNumber()); + assertEquals("00", first.getEpisode()); + assertEquals("0", first.getSeason()); } diff --git a/test/net/sourceforge/filebot/web/TheTVDBClientTest.java b/test/net/sourceforge/filebot/web/TheTVDBClientTest.java index c32a47b2..c7759ce5 100644 --- a/test/net/sourceforge/filebot/web/TheTVDBClientTest.java +++ b/test/net/sourceforge/filebot/web/TheTVDBClientTest.java @@ -72,8 +72,8 @@ public class TheTVDBClientTest { assertEquals("Buffy the Vampire Slayer", first.getSeriesName()); assertEquals("Unaired Pilot", first.getTitle()); - assertEquals("1", first.getEpisodeNumber()); - assertEquals("0", first.getSeasonNumber()); + assertEquals("Special", first.getEpisode()); + assertEquals("1", first.getSeason()); } @@ -87,8 +87,8 @@ public class TheTVDBClientTest { assertEquals("Buffy the Vampire Slayer", chosen.getSeriesName()); assertEquals("Chosen", chosen.getTitle()); - assertEquals("22", chosen.getEpisodeNumber()); - assertEquals("7", chosen.getSeasonNumber()); + assertEquals("22", chosen.getEpisode()); + assertEquals("7", chosen.getSeason()); }