Package net.pms.util

Source Code of net.pms.util.OpenSubtitle

package net.pms.util;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.URL;
import java.net.URLConnection;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.LongBuffer;
import java.util.Map;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.GZIPInputStream;
import net.pms.PMS;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sun.net.www.protocol.http.HttpURLConnection;

public class OpenSubtitle {
  private static final Logger LOGGER = LoggerFactory.getLogger(OpenSubtitle.class);
  private static final String SUB_DIR = "subs";
  private static final String UA = "OSTestUserAgent";
  private static final long TOKEN_AGE_TIME = 10 * 60 * 1000; // 10 mins
  //private static final long SUB_FILE_AGE = 14 * 24 * 60 * 60 * 1000; // two weeks

  /**
   * Size of the chunks that will be hashed in bytes (64 KB)
   */
  private static final int HASH_CHUNK_SIZE = 64 * 1024;

  private static final String OPENSUBS_URL = "http://api.opensubtitles.org/xml-rpc";
  private static String token = null;
  private static long tokenAge;

  public static String computeHash(File file) throws IOException {
    long size = file.length();
    FileInputStream fis = new FileInputStream(file);
    return computeHash(fis, size);
  }

  public static String computeHash(InputStream stream, long length) throws IOException {

    int chunkSizeForFile = (int) Math.min(HASH_CHUNK_SIZE, length);

    // Buffer that will contain the head and the tail chunk, chunks will overlap if length is smaller than two chunks
    byte[] chunkBytes = new byte[(int) Math.min(2 * HASH_CHUNK_SIZE, length)];
    long head;
    long tail;
    try (DataInputStream in = new DataInputStream(stream)) {
      // First chunk
      in.readFully(chunkBytes, 0, chunkSizeForFile);

      long position = chunkSizeForFile;
      long tailChunkPosition = length - chunkSizeForFile;

      // Seek to position of the tail chunk, or not at all if length is smaller than two chunks
      while (position < tailChunkPosition && (position += in.skip(tailChunkPosition - position)) >= 0);

      // Second chunk, or the rest of the data if length is smaller than two chunks
      in.readFully(chunkBytes, chunkSizeForFile, chunkBytes.length - chunkSizeForFile);

      head = computeHashForChunk(ByteBuffer.wrap(chunkBytes, 0, chunkSizeForFile));
      tail = computeHashForChunk(ByteBuffer.wrap(chunkBytes, chunkBytes.length - chunkSizeForFile, chunkSizeForFile));
    }
    return String.format("%016x", length + head + tail);
  }

  private static long computeHashForChunk(ByteBuffer buffer) {

    LongBuffer longBuffer = buffer.order(ByteOrder.LITTLE_ENDIAN).asLongBuffer();
    long hash = 0;

    while (longBuffer.hasRemaining()) {
      hash += longBuffer.get();
    }

    return hash;
  }

  public static String postPage(URLConnection connection, String query) throws IOException {
    connection.setDoOutput(true);
    connection.setDoInput(true);
    connection.setUseCaches(false);
    connection.setDefaultUseCaches(false);
    connection.setRequestProperty("Content-Type", "text/xml");
    connection.setRequestProperty("Content-Length", "" + query.length());
    ((HttpURLConnection)connection).setRequestMethod("POST");
    //LOGGER.debug("opensub query "+query);
    // open up the output stream of the connection
    if (!StringUtils.isEmpty(query)) {
      try (DataOutputStream output = new DataOutputStream(connection.getOutputStream())) {
        output.writeBytes(query);
        output.flush();
      }
    }

    StringBuilder page;
    try (BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
      page = new StringBuilder();
      String str;
      while ((str = in.readLine()) != null) {
        page.append(str.trim());
        page.append("\n");
      }
    }
    //LOGGER.debug("opensubs result page "+page.toString());
    return page.toString();
  }

  private static boolean tokenIsYoung() {
    long now = System.currentTimeMillis();
    return ((now - tokenAge) < TOKEN_AGE_TIME);
  }

  private static void login() throws IOException {
    if ((token != null) && tokenIsYoung()) {
      return;
    }
    URL url = new URL(OPENSUBS_URL);
    String req = "<methodCall>\n<methodName>LogIn</methodName>\n<params>\n<param>\n<value><string/></value>\n</param>\n" +
      "<param>\n" +
      "<value><string/></value>\n</param>\n<param>\n<value><string/></value>\n" +
      "</param>\n<param>\n<value><string>" + UA + "</string></value>\n</param>\n" +
      "</params>\n" +
      "</methodCall>\n";
    Pattern re = Pattern.compile("token.*?<string>([^<]+)</string>", Pattern.DOTALL);
    Matcher m = re.matcher(postPage(url.openConnection(), req));
    if (m.find()) {
      token = m.group(1);
      tokenAge = System.currentTimeMillis();
    }
  }

  public static String fetchImdbId(File f) throws IOException {
    return fetchImdbId(getHash(f));
  }

  public static String fetchImdbId(String hash) throws IOException {
    LOGGER.debug("fetch imdbid for hash " + hash);
    login();
    if (token == null) {
      return "";
    }
    URL url = new URL(OPENSUBS_URL);
    String req = "<methodCall>\n<methodName>CheckMovieHash2</methodName>\n" +
      "<params>\n<param>\n<value><string>" + token + "</string></value>\n</param>\n" +
      "<param>\n<value>\n<array>\n<data>\n<value><string>" + hash + "</string></value>\n" +
      "</data>\n</array>\n</value>\n</param>" +
      "</params>\n</methodCall>\n";
    Pattern re = Pattern.compile("MovieImdbID.*?<string>([^<]+)</string>", Pattern.DOTALL);
    Matcher m = re.matcher(postPage(url.openConnection(), req));
    if (m.find()) {
      return m.group(1);
    }
    return "";
  }

  public static String getHash(File f) throws IOException {
    LOGGER.debug("get hash of " + f);
    String hash = ImdbUtil.extractOSHash(f);
    if (!StringUtils.isEmpty(hash)) {
      return hash;
    }
    return computeHash(f);
  }

  public static Map<String, Object> findSubs(File f) throws IOException {
    Map<String, Object> res = findSubs(getHash(f), f.length());
    if (res.isEmpty()) { // no good on hash! try imdb
      String imdb = ImdbUtil.extractImdb(f);
      if (StringUtils.isEmpty(imdb)) {
        imdb = fetchImdbId(f);
      }
      res = findSubs(imdb);
    }
    if (res.isEmpty()) { // final try, use the name
      res = querySubs(f.getName());
    }
    return res;

  }

  public static Map<String, Object> findSubs(String hash, long size) throws IOException {
    return findSubs(hash, size, null, null);
  }

  public static Map<String, Object> findSubs(String imdb) throws IOException {
    return findSubs(null, 0, imdb, null);
  }

  public static Map<String, Object> querySubs(String query) throws IOException {
    return findSubs(null, 0, null, query);
  }

  public static Map<String, Object> findSubs(String hash, long size, String imdb, String query) throws IOException {
    login();
    TreeMap<String, Object> res = new TreeMap<>();
    if (token == null) {
      return res;
    }
    String lang = iso639(PMS.getConfiguration().getSubtitlesLanguages());
    URL url = new URL(OPENSUBS_URL);
    String hashStr = "";
    String imdbStr = "";
    String qStr = "";
    if (!StringUtils.isEmpty(hash)) {
      hashStr = "<member><name>moviehash</name><value><string>" + hash + "</string></value></member>\n" +
        "<member><name>moviebytesize</name><value><double>" + size + "</double></value></member>\n";
    } else if (!StringUtils.isEmpty(imdb)) {
      imdbStr = "<member><name>imdbid</name><value><string>" + imdb + "</string></value></member>\n";
    } else if (!StringUtils.isEmpty(query)) {
      qStr = "<member><name>query</name><value><string>" + query + "</string></value></member>\n";
    } else {
      return res;
    }
    String req = "<methodCall>\n<methodName>SearchSubtitles</methodName>\n" +
      "<params>\n<param>\n<value><string>" + token + "</string></value>\n</param>\n" +
      "<param>\n<value>\n<array>\n<data>\n<value><struct><member><name>sublanguageid" +
      "</name><value><string>" + lang + "</string></value></member>" +
      hashStr + imdbStr + qStr + "\n" +
      "</struct></value></data>\n</array>\n</value>\n</param>" +
      "</params>\n</methodCall>\n";
    Pattern re = Pattern.compile("SubFileName</name>.*?<string>([^<]+)</string>.*?SubLanguageID</name>.*?<string>([^<]+)</string>.*?SubDownloadLink</name>.*?<string>([^<]+)</string>", Pattern.DOTALL);
    String page = postPage(url.openConnection(), req);
    Matcher m = re.matcher(page);
    while (m.find()) {
      LOGGER.debug("found subtitle " + m.group(2) + " name " + m.group(1) + " zip " + m.group(3));
      res.put(m.group(2) + ":" + m.group(1), m.group(3));
      if (res.size() > PMS.getConfiguration().liveSubtitlesLimit()) {
        // limit the number of hits somewhat
        break;
      }
    }
    return res;
  }

  private static String iso639(String s) {
    String[] tmp = s.split(",");
    StringBuilder res = new StringBuilder();
    String sep = "";
    for (String tmp1 : tmp) {
      res.append(sep).append(Iso639.getISO639_2Code(tmp1));
      sep = ",";
    }
    if (StringUtils.isNotEmpty(res)) {
      return res.toString();
    }
    return s;
  }

  public static String subFile(String name) {
    String dir = PMS.getConfiguration().getDataFile(SUB_DIR);
    File path = new File(dir);
    if (!path.exists()) {
      path.mkdirs();
    }
    return path.getAbsolutePath() + File.separator + name + ".srt";
  }

  public static String fetchSubs(String url) throws FileNotFoundException, IOException {
    return fetchSubs(url, subFile(String.valueOf(System.currentTimeMillis())));
  }

  public static String fetchSubs(String url, String outName) throws FileNotFoundException, IOException {
    login();
    if (token == null) {
      return "";
    }
    if (StringUtils.isEmpty(outName)) {
      outName = subFile(String.valueOf(System.currentTimeMillis()));
    }
    File f = new File(outName);
    URL u = new URL(url);
    URLConnection connection = u.openConnection();
    connection.setDoInput(true);
    connection.setDoOutput(true);
    InputStream in = connection.getInputStream();
    OutputStream out;
    try (GZIPInputStream gzipInputStream = new GZIPInputStream(in)) {
      out = new FileOutputStream(f);
      byte[] buf = new byte[4096];
      int len;
      while ((len = gzipInputStream.read(buf)) > 0) {
        out.write(buf, 0, len);
      }
    }
    out.close();
    if (!PMS.getConfiguration().isLiveSubtitlesKeep()) {
      PMS.get().addTempFile(f);
    }
    return f.getAbsolutePath();
  }

  public static String getLang(String str) {
    String[] tmp = str.split(":", 2);
    if (tmp.length > 1) {
      return tmp[0];
    }
    return "";
  }

  public static String getName(String str) {
    String[] tmp = str.split(":", 2);
    if (tmp.length > 1) {
      return tmp[1];
    }
    return str;
  }

  public static void convert() {
    if (PMS.getConfiguration().isLiveSubtitlesKeep()) {
      return;
    }
    File path = new File(PMS.getConfiguration().getDataFile(SUB_DIR));
    if (!path.exists()) {
      // no path nothing to do
      return;
    }
    File[] files = path.listFiles();
    for (File file : files) {
      PMS.get().addTempFile(file);
    }
  }
}
TOP

Related Classes of net.pms.util.OpenSubtitle

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.