();
+
+ webservice.logOut(session, null, error);
+
+ // abort if something went wrong
+ checkError(error);
+
+ // stop session
+ this.session = null;
+
+ // cancel timer
+ logoutTimer.cancel();
+ }
+ }
+
+
+ protected void checkError(Holder> error) throws WebServiceException {
+ if (error.value != null) {
+ throw new WebServiceException("Login failed: " + error.value);
+ }
+ }
+
+
+ protected final Timer logoutTimer = new Timer() {
+
+ @Override
+ public void run() {
+ logout();
+ }
+ };
+
+}
diff --git a/source/net/sourceforge/filebot/web/SublightSubtitleDescriptor.java b/source/net/sourceforge/filebot/web/SublightSubtitleDescriptor.java
new file mode 100644
index 00000000..4ec4f69d
--- /dev/null
+++ b/source/net/sourceforge/filebot/web/SublightSubtitleDescriptor.java
@@ -0,0 +1,60 @@
+
+package net.sourceforge.filebot.web;
+
+
+import net.sourceforge.tuned.DownloadTask;
+import net.sublight.webservice.Subtitle;
+
+
+public class SublightSubtitleDescriptor implements SubtitleDescriptor {
+
+ private final Subtitle subtitle;
+
+
+ public SublightSubtitleDescriptor(Subtitle subtitle) {
+ this.subtitle = subtitle;
+ }
+
+
+ @Override
+ public String getName() {
+ // use release name by default
+ String releaseName = subtitle.getRelease();
+
+ if (releaseName == null || releaseName.isEmpty()) {
+ // create name from subtitle information (name, season, episode, ...)
+ String season = subtitle.getSeason() != null ? subtitle.getSeason().toString() : null;
+ String episode = subtitle.getEpisode() != null ? subtitle.getEpisode().toString() : null;
+
+ return EpisodeFormat.getInstance().format(new Episode(subtitle.getTitle(), season, episode, null));
+ }
+
+ return releaseName;
+ }
+
+
+ @Override
+ public String getArchiveType() {
+ return subtitle.getSubtitleType().value().toLowerCase();
+ }
+
+
+ @Override
+ public String getLanguageName() {
+ return subtitle.getLanguage().value();
+ }
+
+
+ @Override
+ public DownloadTask createDownloadTask() {
+ // TODO support
+ return new DownloadTask(null);
+ }
+
+
+ @Override
+ public String toString() {
+ return String.format("%s [%s]", getName(), getLanguageName());
+ }
+
+}
diff --git a/source/net/sourceforge/filebot/web/SublightVideoHasher.java b/source/net/sourceforge/filebot/web/SublightVideoHasher.java
new file mode 100644
index 00000000..d674d00f
--- /dev/null
+++ b/source/net/sourceforge/filebot/web/SublightVideoHasher.java
@@ -0,0 +1,124 @@
+
+package net.sourceforge.filebot.web;
+
+
+import static java.lang.Math.*;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileChannel.MapMode;
+import java.security.MessageDigest;
+import java.util.Formatter;
+import java.util.concurrent.TimeUnit;
+
+import net.sourceforge.filebot.mediainfo.MediaInfo;
+import net.sourceforge.filebot.mediainfo.MediaInfo.StreamKind;
+
+
+/**
+ * Compute special hash used by Sublight to identify video files.
+ *
+ *
+ * The hash is divided into 5 sections:
+ * 1 byte : reserved
+ * 2 bytes: video duration in seconds
+ * 6 bytes: file size in bytes
+ * 16 bytes: MD5 hash of the first 5 MB
+ * 1 byte: control byte, sum of all other bytes
+ *
+ */
+public final class SublightVideoHasher {
+
+
+ public static String computeHash(File file) throws IOException {
+ byte[][] hash = new byte[4][];
+
+ // 1 byte = 0 (reserved)
+ hash[0] = new byte[] { 0 };
+
+ // 2 bytes (video duration in seconds)
+ hash[1] = getTrailingBytes(getDuration(file, TimeUnit.SECONDS), 2);
+
+ // 6 bytes (file size in bytes)
+ hash[2] = getTrailingBytes(file.length(), 6);
+
+ // 16 bytes (md5 hash of the first 5 MB)
+ hash[3] = getHeadMD5(file, 5 * 1024 * 1024);
+
+ // format and sum
+ Formatter hex = new Formatter(new StringBuilder(52));
+ byte sum = 0;
+
+ for (byte[] group : hash) {
+ for (byte b : group) {
+ hex.format("%02x", b);
+ sum += b;
+ }
+ }
+
+ // 1 byte (control byte)
+ hex.format("%02x", sum);
+
+ // done
+ return hex.out().toString();
+ }
+
+
+ protected static byte[] getTrailingBytes(long value, int n) {
+ byte[] bytes = BigInteger.valueOf(value).toByteArray();
+
+ // bytes will be initialized with 0
+ byte[] trailingBytes = new byte[n];
+
+ // copy the least significant n bytes to the new array
+ System.arraycopy(bytes, max(0, bytes.length - n), trailingBytes, max(0, n - bytes.length), min(n, bytes.length));
+
+ return trailingBytes;
+ }
+
+
+ protected static long getDuration(File file, TimeUnit unit) throws IOException {
+ try {
+ MediaInfo mediaInfo = new MediaInfo();
+
+ if (!mediaInfo.open(file))
+ throw new IllegalArgumentException("Failed to open file: " + file);
+
+ // get media info
+ String duration = mediaInfo.get(StreamKind.General, 0, "Duration");
+
+ // close handle
+ mediaInfo.close();
+
+ // convert from milliseconds to given unit
+ return unit.convert(Long.parseLong(duration), TimeUnit.MILLISECONDS);
+ } catch (Exception e) {
+ throw new IOException("Failed to get video duration", e);
+ }
+ }
+
+
+ protected static byte[] getHeadMD5(File file, long chunkSize) throws IOException {
+ try {
+ MessageDigest md5 = MessageDigest.getInstance("MD5");
+
+ FileChannel channel = new FileInputStream(file).getChannel();
+
+ try {
+ // calculate md5
+ md5.update(channel.map(MapMode.READ_ONLY, 0, min(channel.size(), chunkSize)));
+ } finally {
+ // close channel
+ channel.close();
+ }
+
+ return md5.digest();
+ } catch (Exception e) {
+ throw new IOException("Failed to calculate md5 hash", e);
+ }
+ }
+
+}
diff --git a/test/net/sourceforge/filebot/web/IMDbClientTest.java b/test/net/sourceforge/filebot/web/IMDbClientTest.java
index bd3b03d3..954f7f1c 100644
--- a/test/net/sourceforge/filebot/web/IMDbClientTest.java
+++ b/test/net/sourceforge/filebot/web/IMDbClientTest.java
@@ -87,8 +87,8 @@ public class IMDbClientTest {
@Test
public void removeQuotationMarks() throws Exception {
- assertEquals("test", imdb.removeQuotationMarks("\"test\""));
+ assertEquals("test", imdb.normalizeName("\"test\""));
- assertEquals("inner \"quotation marks\"", imdb.removeQuotationMarks("\"inner \"quotation marks\"\""));
+ assertEquals("inner \"quotation marks\"", imdb.normalizeName("\"inner \"quotation marks\"\""));
}
}
diff --git a/test/net/sourceforge/filebot/web/OpenSubtitlesHasherTest.java b/test/net/sourceforge/filebot/web/OpenSubtitlesHasherTest.java
index 5081caa6..242c440d 100644
--- a/test/net/sourceforge/filebot/web/OpenSubtitlesHasherTest.java
+++ b/test/net/sourceforge/filebot/web/OpenSubtitlesHasherTest.java
@@ -9,7 +9,6 @@ import java.io.FileInputStream;
import java.util.Arrays;
import java.util.Collection;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -17,13 +16,12 @@ import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
-@Ignore("No test data")
public class OpenSubtitlesHasherTest {
private String expectedHash;
private File file;
-
+
public OpenSubtitlesHasherTest(String expectedHash, File file) {
this.file = file;
this.expectedHash = expectedHash;
diff --git a/test/net/sourceforge/filebot/web/SublightSubtitleClientTest.java b/test/net/sourceforge/filebot/web/SublightSubtitleClientTest.java
new file mode 100644
index 00000000..140e6b22
--- /dev/null
+++ b/test/net/sourceforge/filebot/web/SublightSubtitleClientTest.java
@@ -0,0 +1,91 @@
+
+package net.sourceforge.filebot.web;
+
+
+import static org.junit.Assert.*;
+
+import java.util.List;
+
+import net.sublight.webservice.Subtitle;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+
+public class SublightSubtitleClientTest {
+
+ private static SublightSubtitleClient client = new SublightSubtitleClient("Test;0.0");
+
+
+ @BeforeClass
+ public static void login() {
+ // login manually
+ client.login();
+ }
+
+
+ @Test
+ public void search() {
+ List list = client.search("babylon 5");
+
+ MovieDescriptor sample = (MovieDescriptor) list.get(0);
+
+ // check sample entry
+ assertEquals("Babylon 5", sample.getName());
+ assertEquals(105946, sample.getImdbId());
+
+ // check size
+ assertEquals(8, list.size());
+ }
+
+
+ @Test
+ public void getSubtitleListEnglish() {
+ List list = client.getSubtitleList(new MovieDescriptor("Heroes", 2006, 813715), "English");
+
+ SubtitleDescriptor sample = list.get(0);
+
+ assertTrue(sample.getName().startsWith("Heroes"));
+ assertEquals("English", sample.getLanguageName());
+
+ // check size
+ assertTrue(list.size() > 45);
+ }
+
+
+ @Test
+ public void getSubtitleListAllLanguages() {
+ List list = client.getSubtitleList(new MovieDescriptor("Babylon 5", 1994, 105946), null);
+
+ SubtitleDescriptor sample = list.get(0);
+
+ assertEquals("Babylon.5.S01E01.Midnight.on.the.Firing.Line.AC3.DVDRip.DivX-AMC", sample.getName());
+ assertEquals("Slovenian", sample.getLanguageName());
+
+ // check size
+ assertTrue(list.size() > 45);
+ }
+
+
+ @Test
+ public void getSubtitleListVideoHash() {
+ List list = client.getSubtitleList("000a20000045eacfebd3c2c83bfb4ea1598b14e9be7db38316fd", null, null, "English");
+
+ Subtitle sample = list.get(0);
+
+ assertEquals("Terminator: The Sarah Connor Chronicles", sample.getTitle());
+ assertEquals(2, sample.getSeason(), 0);
+ assertEquals(22, sample.getEpisode(), 0);
+ assertEquals("Terminator.The.Sarah.Connor.Chronicles.S02E22.HDTV.XviD-2HD", sample.getRelease());
+ assertTrue(sample.isIsLinked());
+ }
+
+
+ @AfterClass
+ public static void logout() {
+ // logout manually
+ client.logout();
+ }
+
+}
diff --git a/test/net/sourceforge/filebot/web/WebTestSuite.java b/test/net/sourceforge/filebot/web/WebTestSuite.java
index 37993dc0..188e5711 100644
--- a/test/net/sourceforge/filebot/web/WebTestSuite.java
+++ b/test/net/sourceforge/filebot/web/WebTestSuite.java
@@ -8,7 +8,7 @@ import org.junit.runners.Suite.SuiteClasses;
@RunWith(Suite.class)
-@SuiteClasses( { TVDotComClientTest.class, AnidbClientTest.class, TVRageClientTest.class, TheTVDBClientTest.class, IMDbClientTest.class, SubsceneSubtitleClientTest.class, SubtitleSourceClientTest.class, OpenSubtitlesHasherTest.class })
+@SuiteClasses( { TVDotComClientTest.class, AnidbClientTest.class, TVRageClientTest.class, TheTVDBClientTest.class, IMDbClientTest.class, SubsceneSubtitleClientTest.class, SubtitleSourceClientTest.class, SublightSubtitleClientTest.class })
public class WebTestSuite {
}