--- remove Subscene/Sublight from codebase
This commit is contained in:
parent
d66fd0f141
commit
1804d12def
Binary file not shown.
|
@ -20,6 +20,4 @@ themoviedb.apikey: 5a6edae568130bf10617b6d45be99f13
|
|||
serienjunkies.apikey: 9fbhw9uebfiwvbefzuwv
|
||||
fanart.tv.apikey: 780b986b22c35e6f7a134a2f392c2deb
|
||||
acoustid.apikey: 0B3qZnQc
|
||||
pushover.apikey: wcckDz3oygHSU2SdIptvnHxJ92SQKK
|
||||
sublight.clientid: FileBot2
|
||||
sublight.apikey: 79f7a868-c28c-446f-a58e-3637ca24c87a
|
||||
pushover.apikey: wcckDz3oygHSU2SdIptvnHxJ92SQKK
|
|
@ -61,7 +61,6 @@ public final class WebServices {
|
|||
|
||||
// subtitle dbs
|
||||
public static final OpenSubtitlesClient OpenSubtitles = new OpenSubtitlesClient(String.format("%s %s", getApplicationName(), getApplicationVersion()));
|
||||
//TODO remove Subscene/Sublight from codebase
|
||||
|
||||
// misc
|
||||
public static final FanartTV FanartTV = new FanartTV(Settings.getApplicationProperty("fanart.tv.apikey"));
|
||||
|
|
|
@ -1,449 +0,0 @@
|
|||
|
||||
package net.sourceforge.filebot.web;
|
||||
|
||||
|
||||
import static java.lang.Math.*;
|
||||
import static java.util.Collections.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.net.URI;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.swing.Icon;
|
||||
import javax.xml.ws.Holder;
|
||||
import javax.xml.ws.WebServiceException;
|
||||
|
||||
import net.sourceforge.filebot.ResourceManager;
|
||||
import net.sourceforge.tuned.Timer;
|
||||
import net.sublight.webservice.ArrayOfGenre;
|
||||
import net.sublight.webservice.ArrayOfIMDB;
|
||||
import net.sublight.webservice.ArrayOfRelease;
|
||||
import net.sublight.webservice.ArrayOfString;
|
||||
import net.sublight.webservice.ArrayOfSubtitle;
|
||||
import net.sublight.webservice.ArrayOfSubtitleLanguage;
|
||||
import net.sublight.webservice.ClientInfo;
|
||||
import net.sublight.webservice.Genre;
|
||||
import net.sublight.webservice.IMDB;
|
||||
import net.sublight.webservice.Release;
|
||||
import net.sublight.webservice.Sublight;
|
||||
import net.sublight.webservice.SublightSoap;
|
||||
import net.sublight.webservice.Subtitle;
|
||||
import net.sublight.webservice.SubtitleLanguage;
|
||||
|
||||
|
||||
public class SublightSubtitleClient implements SubtitleProvider, VideoHashSubtitleService {
|
||||
|
||||
private static final String iid = "25f30171-518c-463b-a310-b9f8e1eddb40";
|
||||
|
||||
private final ClientInfo clientInfo = new ClientInfo();
|
||||
|
||||
private String username;
|
||||
private String passwordHash;
|
||||
|
||||
private SublightSoap webservice;
|
||||
private String session;
|
||||
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Sublight";
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public URI getLink() {
|
||||
return URI.create("http://www.sublight.si");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Icon getIcon() {
|
||||
return ResourceManager.getIcon("search.sublight");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<SearchResult> search(String query) throws WebServiceException {
|
||||
// require login
|
||||
login();
|
||||
|
||||
Holder<ArrayOfIMDB> response = new Holder<ArrayOfIMDB>();
|
||||
Holder<String> error = new Holder<String>();
|
||||
|
||||
webservice.findIMDB(query, null, null, response, error);
|
||||
|
||||
// abort if something went wrong
|
||||
checkError(error);
|
||||
|
||||
List<SearchResult> results = new ArrayList<SearchResult>();
|
||||
|
||||
if (response.value != null) {
|
||||
for (IMDB imdb : response.value.getIMDB()) {
|
||||
// remove classifier (e.g. tt0436992 -> 0436992)
|
||||
int id = Integer.parseInt(imdb.getId().substring(2));
|
||||
|
||||
results.add(new Movie(imdb.getTitle(), imdb.getYear(), id, -1));
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<SubtitleDescriptor> getSubtitleList(SearchResult searchResult, String languageName) throws WebServiceException {
|
||||
Movie movie = (Movie) searchResult;
|
||||
|
||||
List<SubtitleDescriptor> subtitles = new ArrayList<SubtitleDescriptor>();
|
||||
|
||||
// retrieve subtitles by name and year
|
||||
for (Subtitle subtitle : getSubtitleList(null, movie.getName(), movie.getYear(), languageName)) {
|
||||
subtitles.add(new SublightSubtitleDescriptor(subtitle, this));
|
||||
}
|
||||
|
||||
return subtitles;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Map<File, List<SubtitleDescriptor>> getSubtitleList(File[] files, final String languageName) throws Exception {
|
||||
Map<File, List<SubtitleDescriptor>> subtitles = new HashMap<File, List<SubtitleDescriptor>>(files.length);
|
||||
|
||||
ExecutorService executor = Executors.newFixedThreadPool(min(files.length, 10));
|
||||
List<Future<List<SubtitleDescriptor>>> requests = new ArrayList<Future<List<SubtitleDescriptor>>>();
|
||||
|
||||
try {
|
||||
// queue and execute requests
|
||||
for (int i = 0; i < files.length; i++) {
|
||||
Future<List<SubtitleDescriptor>> request = null;
|
||||
|
||||
try {
|
||||
// make call interruptible
|
||||
if (Thread.interrupted())
|
||||
throw new InterruptedException();
|
||||
|
||||
// compute video hash and execute query in parallel
|
||||
final String videoHash = SublightVideoHasher.computeHash(files[i]);
|
||||
|
||||
request = executor.submit(new Callable<List<SubtitleDescriptor>>() {
|
||||
|
||||
@Override
|
||||
public List<SubtitleDescriptor> call() throws Exception {
|
||||
return getSubtitleList(videoHash, languageName);
|
||||
}
|
||||
});
|
||||
} catch (IOException e) {
|
||||
Logger.getLogger(SublightSubtitleClient.class.getName()).log(Level.WARNING, "Error computing video hash: " + e.getMessage());
|
||||
}
|
||||
|
||||
requests.add(i, request);
|
||||
}
|
||||
|
||||
// collect results
|
||||
for (int i = 0; i < files.length; i++) {
|
||||
List<SubtitleDescriptor> response = emptyList();
|
||||
|
||||
if (requests.get(i) != null) {
|
||||
response = requests.get(i).get();
|
||||
}
|
||||
|
||||
subtitles.put(files[i], response);
|
||||
}
|
||||
|
||||
return subtitles;
|
||||
} finally {
|
||||
// shutdown after all tasks are done or an exception was thrown
|
||||
executor.shutdownNow();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public List<SubtitleDescriptor> getSubtitleList(String videoHash, String languageName) throws WebServiceException {
|
||||
List<SubtitleDescriptor> subtitles = new ArrayList<SubtitleDescriptor>();
|
||||
|
||||
// retrieve subtitles by video hash
|
||||
for (Subtitle subtitle : getSubtitleList(videoHash, null, null, languageName)) {
|
||||
// only keep linked subtitles
|
||||
if (subtitle.isIsLinked()) {
|
||||
subtitles.add(new SublightSubtitleDescriptor(subtitle, this));
|
||||
}
|
||||
}
|
||||
|
||||
return subtitles;
|
||||
}
|
||||
|
||||
|
||||
public List<Subtitle> getSubtitleList(String videoHash, String name, Integer year, String languageName) throws WebServiceException {
|
||||
// require login
|
||||
login();
|
||||
|
||||
// given language or all languages
|
||||
ArrayOfSubtitleLanguage languages = new ArrayOfSubtitleLanguage();
|
||||
|
||||
if (languageName != null) {
|
||||
// given language
|
||||
languages.getSubtitleLanguage().add(getSubtitleLanguage(languageName));
|
||||
} else {
|
||||
// all languages
|
||||
Collections.addAll(languages.getSubtitleLanguage(), SubtitleLanguage.values());
|
||||
}
|
||||
|
||||
// hash singleton array
|
||||
ArrayOfString videoHashes = new ArrayOfString();
|
||||
videoHashes.getString().add(videoHash);
|
||||
|
||||
// all genres
|
||||
ArrayOfGenre genres = new ArrayOfGenre();
|
||||
Collections.addAll(genres.getGenre(), Genre.values());
|
||||
|
||||
// response holders
|
||||
Holder<ArrayOfSubtitle> subtitles = new Holder<ArrayOfSubtitle>();
|
||||
Holder<ArrayOfRelease> releases = new Holder<ArrayOfRelease>();
|
||||
Holder<String> error = new Holder<String>();
|
||||
|
||||
webservice.searchSubtitles4(session, videoHashes, name, year, null, null, languages, genres, null, null, null, subtitles, releases, null, error);
|
||||
|
||||
// abort if something went wrong
|
||||
checkError(error);
|
||||
|
||||
// return empty list if response is empty
|
||||
if (subtitles.value == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
// map all release names by subtitle id
|
||||
if (releases.value != null) {
|
||||
Map<String, String> releaseNameBySubtitleID = new HashMap<String, String>();
|
||||
|
||||
// map release names by subtitle id
|
||||
for (Release release : releases.value.getRelease()) {
|
||||
releaseNameBySubtitleID.put(release.getSubtitleID(), release.getName());
|
||||
}
|
||||
|
||||
// set release names
|
||||
for (Subtitle subtitle : subtitles.value.getSubtitle()) {
|
||||
subtitle.setRelease(releaseNameBySubtitleID.get(subtitle.getSubtitleID()));
|
||||
}
|
||||
}
|
||||
|
||||
return subtitles.value.getSubtitle();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean publishSubtitle(int imdbid, String languageName, File[] videoFile, File[] subtitleFile) throws Exception {
|
||||
//TODO implement upload feature
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public void publishSubtitle(int imdbid, String videoHash, String languageName, String releaseName, byte[] data) {
|
||||
// require login
|
||||
login();
|
||||
|
||||
Subtitle subtitle = new Subtitle();
|
||||
subtitle.setIMDB(String.format("http://www.imdb.com/title/tt%07d", imdbid));
|
||||
subtitle.setLanguage(getSubtitleLanguage(languageName));
|
||||
subtitle.setRelease(releaseName);
|
||||
|
||||
Holder<Boolean> result = new Holder<Boolean>();
|
||||
Holder<String> subid = new Holder<String>();
|
||||
Holder<String> error = new Holder<String>();
|
||||
|
||||
// upload subtitle
|
||||
webservice.publishSubtitle2(session, subtitle, data, result, subid, null, error);
|
||||
|
||||
// abort if something went wrong
|
||||
checkError(error);
|
||||
|
||||
// link subtitle to video file
|
||||
webservice.addHashLink3(session, subid.value, videoHash, null, null, error);
|
||||
|
||||
// abort if something went wrong
|
||||
checkError(error);
|
||||
}
|
||||
|
||||
|
||||
protected Map<String, SubtitleLanguage> getLanguageAliasMap() {
|
||||
Map<String, SubtitleLanguage> languages = new HashMap<String, SubtitleLanguage>(4);
|
||||
|
||||
// insert special some additional special handling
|
||||
languages.put("Brazilian", SubtitleLanguage.PORTUGUESE_BRAZIL);
|
||||
languages.put("Bosnian", SubtitleLanguage.BOSNIAN_LATIN);
|
||||
languages.put("Serbian", SubtitleLanguage.SERBIAN_LATIN);
|
||||
|
||||
return languages;
|
||||
}
|
||||
|
||||
|
||||
protected SubtitleLanguage getSubtitleLanguage(String languageName) {
|
||||
// check subtitle language enum
|
||||
for (SubtitleLanguage language : SubtitleLanguage.values()) {
|
||||
if (language.value().equalsIgnoreCase(languageName))
|
||||
return language;
|
||||
}
|
||||
|
||||
// check alias list
|
||||
for (Entry<String, SubtitleLanguage> alias : getLanguageAliasMap().entrySet()) {
|
||||
if (alias.getKey().equalsIgnoreCase(languageName))
|
||||
return alias.getValue();
|
||||
}
|
||||
|
||||
// illegal language name
|
||||
throw new IllegalArgumentException("Illegal language: " + languageName);
|
||||
}
|
||||
|
||||
|
||||
protected String getLanguageName(SubtitleLanguage language) {
|
||||
// check alias list first
|
||||
for (Entry<String, SubtitleLanguage> alias : getLanguageAliasMap().entrySet()) {
|
||||
if (language == alias.getValue())
|
||||
return alias.getKey();
|
||||
}
|
||||
|
||||
// use language value by default
|
||||
return language.value();
|
||||
}
|
||||
|
||||
|
||||
protected synchronized byte[] getZipArchive(Subtitle subtitle) throws WebServiceException, InterruptedException {
|
||||
// require login
|
||||
login();
|
||||
|
||||
Holder<String> ticket = new Holder<String>();
|
||||
Holder<Short> que = new Holder<Short>();
|
||||
Holder<byte[]> data = new Holder<byte[]>();
|
||||
Holder<String> error = new Holder<String>();
|
||||
|
||||
webservice.getDownloadTicket2(session, null, subtitle.getSubtitleID(), null, ticket, que, null, error);
|
||||
|
||||
// abort if something went wrong
|
||||
checkError(error);
|
||||
|
||||
// wait x seconds as specified by the download ticket response, download ticket is not valid until then
|
||||
Thread.sleep(que.value * 1000);
|
||||
|
||||
webservice.downloadByID4(session, subtitle.getSubtitleID(), -1, false, ticket.value, null, data, null, error);
|
||||
|
||||
// abort if something went wrong
|
||||
checkError(error);
|
||||
|
||||
// return zip file bytes
|
||||
return data.value;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public URI getSubtitleListLink(SearchResult searchResult, String languageName) {
|
||||
// note that sublight can only be accessed via the soap API
|
||||
return URI.create("http://www.sublight.si/SearchSubtitles.aspx");
|
||||
}
|
||||
|
||||
|
||||
public synchronized void setClient(String id, String key) {
|
||||
clientInfo.setClientId(id);
|
||||
clientInfo.setApiKey(key);
|
||||
}
|
||||
|
||||
|
||||
public synchronized void setUser(String username, String password) {
|
||||
this.username = username;
|
||||
this.passwordHash = password != null && password.length() > 0 ? getPasswordHash(password) : null;
|
||||
}
|
||||
|
||||
|
||||
public String getPasswordHash(String password) {
|
||||
try {
|
||||
MessageDigest digest = MessageDigest.getInstance("MD5");
|
||||
digest.update(password.getBytes("UTF-16LE"));
|
||||
return String.format("%032x", new BigInteger(1, digest.digest()));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public synchronized void login() throws WebServiceException {
|
||||
if (clientInfo.getClientId() == null || clientInfo.getClientId().isEmpty()) {
|
||||
throw new IllegalStateException("Sublight login has not been configured");
|
||||
}
|
||||
|
||||
if (webservice == null) {
|
||||
// lazy initialize because all the JAX-WS class loading can take quite some time
|
||||
webservice = new Sublight().getSublightSoap();
|
||||
}
|
||||
|
||||
if (session == null) {
|
||||
// args contains only iid
|
||||
ArrayOfString args = new ArrayOfString();
|
||||
args.getString().add(iid);
|
||||
|
||||
Holder<String> session = new Holder<String>();
|
||||
Holder<String> error = new Holder<String>();
|
||||
|
||||
if (username == null || username.isEmpty()) {
|
||||
webservice.logInAnonymous4(clientInfo, args, session, null, error);
|
||||
} else {
|
||||
webservice.logIn6(username, passwordHash, clientInfo, args, session, null, null, null, null, error);
|
||||
}
|
||||
|
||||
// abort if something went wrong
|
||||
checkError(error);
|
||||
|
||||
// start session
|
||||
this.session = session.value;
|
||||
}
|
||||
|
||||
// reset timer
|
||||
logoutTimer.set(10, TimeUnit.MINUTES, true);
|
||||
}
|
||||
|
||||
|
||||
protected synchronized void logout() throws WebServiceException {
|
||||
if (session != null) {
|
||||
Holder<String> error = new Holder<String>();
|
||||
|
||||
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("Response indicates error: " + error.value);
|
||||
}
|
||||
}
|
||||
|
||||
protected final Timer logoutTimer = new Timer() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
logout();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
|
@ -1,144 +0,0 @@
|
|||
|
||||
package net.sourceforge.filebot.web;
|
||||
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Formatter;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
import net.sourceforge.tuned.ByteBufferOutputStream;
|
||||
import net.sublight.webservice.Subtitle;
|
||||
|
||||
|
||||
public class SublightSubtitleDescriptor implements SubtitleDescriptor {
|
||||
|
||||
private final Subtitle subtitle;
|
||||
private final SublightSubtitleClient source;
|
||||
|
||||
private final String name;
|
||||
private final String languageName;
|
||||
|
||||
|
||||
public SublightSubtitleDescriptor(Subtitle subtitle, SublightSubtitleClient source) {
|
||||
this.subtitle = subtitle;
|
||||
this.source = source;
|
||||
|
||||
this.name = getName(subtitle);
|
||||
this.languageName = source.getLanguageName(subtitle.getLanguage());
|
||||
}
|
||||
|
||||
|
||||
private String getName(Subtitle subtitle) {
|
||||
String releaseName = subtitle.getRelease();
|
||||
|
||||
// check if release name contains sufficient information to be used as display name
|
||||
if (releaseName != null && !releaseName.isEmpty()) {
|
||||
boolean isValid = true;
|
||||
|
||||
if (subtitle.getSeason() != null) {
|
||||
isValid &= releaseName.contains(subtitle.getSeason().toString());
|
||||
}
|
||||
|
||||
if (subtitle.getEpisode() != null) {
|
||||
isValid &= releaseName.contains(subtitle.getEpisode().toString());
|
||||
}
|
||||
|
||||
if (isValid) {
|
||||
return releaseName;
|
||||
}
|
||||
}
|
||||
|
||||
// format proper display name
|
||||
Formatter builder = new Formatter(new StringBuilder(subtitle.getTitle()));
|
||||
|
||||
if (subtitle.getSeason() != null || subtitle.getEpisode() != null) {
|
||||
builder.format(" - S%02dE%02d", subtitle.getSeason(), subtitle.getEpisode());
|
||||
}
|
||||
|
||||
if (subtitle.getRelease() != null && !subtitle.getRelease().isEmpty()) {
|
||||
builder.format(" (%s)", subtitle.getRelease());
|
||||
}
|
||||
|
||||
return builder.out().toString();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getLanguageName() {
|
||||
return languageName;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return subtitle.getSubtitleType().value().toLowerCase();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public long getLength() {
|
||||
return subtitle.getSize();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ByteBuffer fetch() throws Exception {
|
||||
byte[] archive = source.getZipArchive(subtitle);
|
||||
|
||||
// the zip archive will contain exactly one subtitle
|
||||
ZipInputStream stream = new ZipInputStream(new ByteArrayInputStream(archive));
|
||||
|
||||
try {
|
||||
// move to subtitle entry
|
||||
ZipEntry entry = stream.getNextEntry();
|
||||
|
||||
ByteBufferOutputStream buffer = new ByteBufferOutputStream(entry.getSize());
|
||||
|
||||
// read subtitle data
|
||||
buffer.transferFully(stream);
|
||||
|
||||
// return plain subtitle data
|
||||
return buffer.getByteBuffer();
|
||||
} finally {
|
||||
stream.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getPath() {
|
||||
return String.format("%s.%s", getName(), getType());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return subtitle.getSubtitleID().hashCode();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
if (object instanceof SublightSubtitleDescriptor) {
|
||||
SublightSubtitleDescriptor other = (SublightSubtitleDescriptor) object;
|
||||
return subtitle.getSubtitleID().equals(other.subtitle.getSubtitleID());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%s [%s]", getName(), getLanguageName());
|
||||
}
|
||||
|
||||
}
|
|
@ -1,124 +0,0 @@
|
|||
|
||||
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.MediaInfoException;
|
||||
import net.sourceforge.filebot.mediainfo.MediaInfo.StreamKind;
|
||||
|
||||
|
||||
/**
|
||||
* Compute special hash used by <a href="http://www.subtitles-on.net">Sublight</a> to identify video files.
|
||||
*
|
||||
* <pre>
|
||||
* 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
|
||||
* </pre>
|
||||
*/
|
||||
public final class SublightVideoHasher {
|
||||
|
||||
public static String computeHash(File file) throws IOException, MediaInfoException {
|
||||
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, MediaInfoException {
|
||||
MediaInfo mediaInfo = new MediaInfo();
|
||||
|
||||
if (!mediaInfo.open(file))
|
||||
throw new IOException("Failed to open file: " + file);
|
||||
|
||||
// get media info
|
||||
String duration = mediaInfo.get(StreamKind.General, 0, "Duration");
|
||||
|
||||
// close handle
|
||||
mediaInfo.close();
|
||||
|
||||
// sanity check
|
||||
if (duration.isEmpty())
|
||||
throw new IOException("Failed to read video duration");
|
||||
|
||||
// convert from milliseconds to given unit
|
||||
return unit.convert(Long.parseLong(duration), TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,199 +0,0 @@
|
|||
|
||||
package net.sourceforge.filebot.web;
|
||||
|
||||
|
||||
import static net.sourceforge.filebot.web.WebRequest.*;
|
||||
import static net.sourceforge.tuned.XPathUtilities.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import net.sourceforge.filebot.Cache;
|
||||
import net.sourceforge.filebot.ResourceManager;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
|
||||
public class SubsceneSubtitleClient implements SubtitleProvider {
|
||||
|
||||
private static final String host = "subscene.com";
|
||||
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Subscene";
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public URI getLink() {
|
||||
return URI.create("http://subscene.com");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Icon getIcon() {
|
||||
return ResourceManager.getIcon("search.subscene");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<SearchResult> search(String query) throws IOException, SAXException {
|
||||
URL searchUrl = new URL("http", host, "/subtitles/title.aspx?q=" + encode(query, true));
|
||||
Document dom = getHtmlDocument(searchUrl);
|
||||
|
||||
List<Node> nodes = selectNodes("//H2[text()='Close']//following::DIV[@class='title']//A", dom);
|
||||
List<SearchResult> searchResults = new ArrayList<SearchResult>(nodes.size());
|
||||
|
||||
Pattern titleSuffixPattern = Pattern.compile("\\s-\\s([^-]+)[(](\\d{4})[)]$");
|
||||
|
||||
for (Node node : nodes) {
|
||||
String title = getTextContent(node);
|
||||
String href = getAttribute("href", node);
|
||||
|
||||
// simplified name for easy matching
|
||||
String shortName = titleSuffixPattern.matcher(title).replaceFirst("");
|
||||
|
||||
try {
|
||||
searchResults.add(new SubsceneSearchResult(shortName, title, new URL("http", host, href)));
|
||||
} catch (MalformedURLException e) {
|
||||
Logger.getLogger(getClass().getName()).log(Level.WARNING, "Invalid href: " + href, e);
|
||||
}
|
||||
}
|
||||
|
||||
return searchResults;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<SubtitleDescriptor> getSubtitleList(SearchResult searchResult, String languageName) throws Exception {
|
||||
URL subtitleListUrl = getSubtitleListLink(searchResult, languageName).toURL();
|
||||
|
||||
String filter = getLanguageFilter(languageName);
|
||||
Document dom = getSubtitleListDocument(subtitleListUrl, filter);
|
||||
|
||||
List<Node> rows = selectNodes("//TD[@class='a1']", dom);
|
||||
List<SubtitleDescriptor> subtitles = new ArrayList<SubtitleDescriptor>();
|
||||
for (Node row : rows) {
|
||||
try {
|
||||
List<Node> fields = selectNodes(".//SPAN", row);
|
||||
String language = getTextContent(fields.get(0));
|
||||
|
||||
if (languageName == null || language.equalsIgnoreCase(languageName)) {
|
||||
String name = getTextContent(fields.get(1));
|
||||
String href = selectString(".//A/@href", row);
|
||||
URL subtitlePage = new URL(subtitleListUrl.getProtocol(), subtitleListUrl.getHost(), href);
|
||||
subtitles.add(new SubsceneSubtitleDescriptor(name, language, subtitlePage));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Logger.getLogger(getClass().getName()).log(Level.WARNING, "Cannot parse subtitle node", e);
|
||||
}
|
||||
}
|
||||
|
||||
return subtitles;
|
||||
}
|
||||
|
||||
|
||||
protected Document getSubtitleListDocument(URL subtitleListUrl, String languageFilter) throws IOException, SAXException {
|
||||
URLConnection connection = subtitleListUrl.openConnection();
|
||||
|
||||
if (languageFilter != null) {
|
||||
connection.addRequestProperty("Cookie", "Filter=" + languageFilter);
|
||||
}
|
||||
|
||||
return getHtmlDocument(connection);
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected String getLanguageFilter(String languageName) throws IOException, SAXException {
|
||||
if (languageName == null || languageName.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// try cache first
|
||||
Cache cache = Cache.getCache("web-datasource-lv2");
|
||||
String cacheKey = getClass().getName() + ".languageFilter";
|
||||
|
||||
Map<String, String> filters = cache.get(cacheKey, Map.class);
|
||||
|
||||
if (filters != null) {
|
||||
return filters.get(languageName.toLowerCase());
|
||||
}
|
||||
|
||||
// fetch new language filter data
|
||||
filters = getLanguageFilterMap();
|
||||
|
||||
// update cache after sanity check
|
||||
if (filters.size() > 42) {
|
||||
cache.put(cacheKey, filters);
|
||||
} else {
|
||||
Logger.getLogger(getClass().getName()).log(Level.WARNING, "Failed to scrape language filters: " + filters);
|
||||
}
|
||||
|
||||
return filters.get(languageName.toLowerCase());
|
||||
}
|
||||
|
||||
|
||||
protected Map<String, String> getLanguageFilterMap() throws IOException, SAXException {
|
||||
Map<String, String> filters = new HashMap<String, String>(50);
|
||||
|
||||
Document dom = getHtmlDocument(new URL("http://subscene.com/filter"));
|
||||
List<Node> checkboxes = selectNodes("//INPUT[@type='checkbox']", dom);
|
||||
|
||||
for (Node checkbox : checkboxes) {
|
||||
String filter = getAttribute("value", checkbox);
|
||||
if (filter != null) {
|
||||
String name = selectString("./following::LABEL", checkbox);
|
||||
filters.put(name.toLowerCase(), filter);
|
||||
}
|
||||
}
|
||||
|
||||
return filters;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public URI getSubtitleListLink(SearchResult searchResult, String languageName) {
|
||||
return ((HyperLink) searchResult).getURI();
|
||||
}
|
||||
|
||||
|
||||
public static class SubsceneSearchResult extends HyperLink {
|
||||
|
||||
private String shortName;
|
||||
|
||||
|
||||
public SubsceneSearchResult(String shortName, String title, URL url) {
|
||||
super(title, url);
|
||||
this.shortName = shortName;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return shortName;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.getName();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,96 +0,0 @@
|
|||
|
||||
package net.sourceforge.filebot.web;
|
||||
|
||||
|
||||
import static java.util.Collections.*;
|
||||
import static net.sourceforge.tuned.XPathUtilities.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
|
||||
public class SubsceneSubtitleDescriptor implements SubtitleDescriptor {
|
||||
|
||||
private String title;
|
||||
private String language;
|
||||
|
||||
private URL subtitlePage;
|
||||
|
||||
|
||||
public SubsceneSubtitleDescriptor(String title, String language, URL subtitlePage) {
|
||||
this.title = title;
|
||||
this.language = language;
|
||||
this.subtitlePage = subtitlePage;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return title;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getLanguageName() {
|
||||
return language;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ByteBuffer fetch() throws Exception {
|
||||
return WebRequest.fetch(getDownloadLink(), 0, singletonMap("Referer", subtitlePage.toString()));
|
||||
}
|
||||
|
||||
|
||||
private URL getDownloadLink() throws IOException, SAXException {
|
||||
Document page = WebRequest.getHtmlDocument(subtitlePage);
|
||||
String file = selectString("id('downloadButton')/@href", page);
|
||||
return new URL(subtitlePage.getProtocol(), subtitlePage.getHost(), file);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getPath() {
|
||||
return getName();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public long getLength() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return subtitlePage.getPath().hashCode();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
if (object instanceof SubsceneSubtitleDescriptor) {
|
||||
SubsceneSubtitleDescriptor other = (SubsceneSubtitleDescriptor) object;
|
||||
return subtitlePage.getPath().equals(other.getPath());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%s [%s]", getName(), getLanguageName());
|
||||
}
|
||||
|
||||
}
|
|
@ -1,108 +0,0 @@
|
|||
|
||||
package net.sourceforge.filebot.web;
|
||||
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.util.List;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
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();
|
||||
|
||||
|
||||
@BeforeClass
|
||||
public static void login() {
|
||||
client.setClient("SublightCmd", "12c72276-b95f-4144-bb2a-879775c71437");
|
||||
client.setUser("filebot-test", "correcthorsebatterystaple");
|
||||
client.login();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void search() {
|
||||
List<SearchResult> list = client.search("babylon 5");
|
||||
|
||||
Movie sample = (Movie) 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<SubtitleDescriptor> list = client.getSubtitleList(new Movie("Heroes", 2006, 813715, -1), "English");
|
||||
|
||||
SubtitleDescriptor sample = list.get(0);
|
||||
assertEquals("English", sample.getLanguageName());
|
||||
|
||||
// check size
|
||||
assertTrue(list.size() > 45);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void getSubtitleListAllLanguages() {
|
||||
List<SubtitleDescriptor> list = client.getSubtitleList(new Movie("Terminator 2", 1991, 103064, -1), "Croatian");
|
||||
|
||||
SubtitleDescriptor sample = list.get(0);
|
||||
|
||||
assertEquals("Terminator.2-Judgment.Day[1991]DvDrip-aXXo", sample.getName());
|
||||
assertEquals("Croatian", sample.getLanguageName());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void getSubtitleListVideoHash() throws Exception {
|
||||
List<Subtitle> list = client.getSubtitleList("001c6e0000320458004ee6f6859e5b7844767d44336e5624edbb", null, null, "English");
|
||||
|
||||
Subtitle sample = list.get(0);
|
||||
assertEquals("Jurassic Park", sample.getTitle());
|
||||
assertEquals("Jurassic.Park[1993]DvDrip-aXXo", sample.getRelease());
|
||||
assertEquals(true, sample.isIsLinked());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void getZipArchive() throws Exception {
|
||||
Subtitle subtitle = new Subtitle();
|
||||
subtitle.setSubtitleID("1b4e9868-dded-49d0-b6e2-2d145328f6d4");
|
||||
|
||||
byte[] zip = client.getZipArchive(subtitle);
|
||||
|
||||
// read first zip entry
|
||||
ZipInputStream zipInputStream = new ZipInputStream(new ByteArrayInputStream(zip));
|
||||
|
||||
try {
|
||||
ZipEntry entry = zipInputStream.getNextEntry();
|
||||
|
||||
assertEquals("Terminator The Sarah Connor Chronicles.srt", entry.getName());
|
||||
assertEquals(38959, entry.getSize(), 0);
|
||||
} finally {
|
||||
zipInputStream.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@AfterClass
|
||||
public static void logout() {
|
||||
// logout manually
|
||||
client.logout();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,113 +0,0 @@
|
|||
|
||||
package net.sourceforge.filebot.web;
|
||||
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.net.URL;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import net.sourceforge.filebot.vfs.ArchiveType;
|
||||
import net.sourceforge.filebot.vfs.MemoryFile;
|
||||
import net.sourceforge.filebot.web.SubsceneSubtitleClient.SubsceneSearchResult;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
|
||||
public class SubsceneSubtitleClientTest {
|
||||
|
||||
/**
|
||||
* Twin Peaks - First Season, ~ 15 subtitles
|
||||
*/
|
||||
private static HyperLink twinpeaksSearchResult;
|
||||
|
||||
/**
|
||||
* Lost - Fourth Season, ~ 430 subtitles
|
||||
*/
|
||||
private static HyperLink lostSearchResult;
|
||||
|
||||
|
||||
@BeforeClass
|
||||
public static void setUpBeforeClass() throws Exception {
|
||||
twinpeaksSearchResult = new SubsceneSearchResult("Twin Peaks", "Twin Peaks - First Season (1990)", new URL("http://subscene.com/subtitles/twin-peaks-first-season"));
|
||||
lostSearchResult = new SubsceneSearchResult("Lost", "Lost - Fourth Season (2008)", new URL("http://subscene.com/subtitles/lost-fourth-season"));
|
||||
}
|
||||
|
||||
private SubsceneSubtitleClient subscene = new SubsceneSubtitleClient();
|
||||
|
||||
|
||||
@Test
|
||||
public void search() throws Exception {
|
||||
List<SearchResult> results = subscene.search("twin peaks");
|
||||
|
||||
SubsceneSearchResult result = (SubsceneSearchResult) results.get(0);
|
||||
assertEquals(twinpeaksSearchResult.toString(), result.toString());
|
||||
assertEquals(twinpeaksSearchResult.getURL().toString(), result.getURL().toString());
|
||||
assertEquals(twinpeaksSearchResult.getName(), result.getName());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void search2() throws Exception {
|
||||
List<SearchResult> results = subscene.search("firefly");
|
||||
|
||||
SubsceneSearchResult result = (SubsceneSearchResult) results.get(0);
|
||||
assertEquals("Firefly - The Complete Series (2002)", result.toString());
|
||||
assertEquals("Firefly", result.getName());
|
||||
assertEquals("http://subscene.com/subtitles/firefly-the-complete-series", result.getURL().toString());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void getSubtitleListSearchResult() throws Exception {
|
||||
List<SubtitleDescriptor> subtitleList = subscene.getSubtitleList(twinpeaksSearchResult, "Italian");
|
||||
assertEquals(10, subtitleList.size());
|
||||
|
||||
SubtitleDescriptor subtitle = subtitleList.get(0);
|
||||
assertEquals("Twin-Peaks-S01E00-Pilot-eAlternate-ita sub by IScrew [www.ITALIANSHARE.net]", subtitle.getName());
|
||||
assertEquals("Italian", subtitle.getLanguageName());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void getSubtitleListSearchResultMany() throws Exception {
|
||||
List<SubtitleDescriptor> subtitleList = subscene.getSubtitleList(lostSearchResult, "Japanese");
|
||||
|
||||
// lots of subtitles, but only a few Japanese ones
|
||||
assertEquals(16, subtitleList.size());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void getLanguageFilterMap() throws Exception {
|
||||
Map<String, String> filters = subscene.getLanguageFilterMap();
|
||||
|
||||
assertEquals("1", filters.get("albanian"));
|
||||
assertEquals("13", filters.get("english"));
|
||||
assertEquals("17", filters.get("finnish"));
|
||||
assertEquals("45", filters.get("vietnamese"));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void getSubtitleListLink() throws Exception {
|
||||
assertEquals(twinpeaksSearchResult.getURL().toString(), subscene.getSubtitleListLink(twinpeaksSearchResult, null).toURL().toString());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void downloadSubtitleArchive() throws Exception {
|
||||
SearchResult selectedResult = subscene.search("firefly").get(0);
|
||||
SubtitleDescriptor subtitleDescriptor = subscene.getSubtitleList(selectedResult, "English").get(0);
|
||||
assertEquals("Firefly The Complete Series", subtitleDescriptor.getName());
|
||||
|
||||
ByteBuffer data = subtitleDescriptor.fetch();
|
||||
Iterable<MemoryFile> archive = ArchiveType.RAR.fromData(data);
|
||||
MemoryFile file = archive.iterator().next();
|
||||
assertEquals("Firefly - 1x01 - Serenity.srt", file.getName());
|
||||
}
|
||||
|
||||
}
|
|
@ -1,15 +1,11 @@
|
|||
|
||||
package net.sourceforge.filebot.web;
|
||||
|
||||
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Suite;
|
||||
import org.junit.runners.Suite.SuiteClasses;
|
||||
|
||||
|
||||
@RunWith(Suite.class)
|
||||
@SuiteClasses( { AnidbClientTest.class, TVRageClientTest.class, TheTVDBClientTest.class, SerienjunkiesClientTest.class, TMDbClientTest.class, IMDbClientTest.class, SubsceneSubtitleClientTest.class, SublightSubtitleClientTest.class,
|
||||
OpenSubtitlesXmlRpcTest.class })
|
||||
@SuiteClasses({ AnidbClientTest.class, TVRageClientTest.class, TheTVDBClientTest.class, SerienjunkiesClientTest.class, TMDbClientTest.class, IMDbClientTest.class, OpenSubtitlesXmlRpcTest.class })
|
||||
public class WebTestSuite {
|
||||
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue