Java tutorial
/* * Copyright (c) 2004-2016 YAMJ Members * https://github.com/orgs/YAMJ/people * * This file is part of the Yet Another Movie Jukebox (YAMJ) project. * * YAMJ is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * any later version. * * YAMJ is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with YAMJ. If not, see <http://www.gnu.org/licenses/>. * * Web: https://github.com/YAMJ/yamj-v2 * */ package com.moviejukebox.plugin; import com.moviejukebox.model.Movie; import com.moviejukebox.model.MovieFile; import com.moviejukebox.model.enumerations.DirtyFlag; import com.moviejukebox.tools.*; import java.io.*; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLConnection; import java.nio.ByteOrder; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Scanner; import java.util.zip.DeflaterOutputStream; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.math.NumberUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Based on some code from the opensubtitles.org subtitle upload java applet */ public class OpenSubtitlesPlugin { private static final Logger LOG = LoggerFactory.getLogger(OpenSubtitlesPlugin.class); private static final String OS_DB_SERVER = "http://api.opensubtitles.org/xml-rpc"; private static String loginToken = ""; // Literals private static final String OS_USER_AGENT = "moviejukebox 1.0.15"; private static final String OS_METHOD_START = "<?xml version=\"1.0\" encoding=\"utf-8\"?><methodCall><methodName>"; private static final String OS_METHOD_END = "</methodName><params><param><value><string>"; private static final String OS_PARAM = "</string></value></param><param><value><struct>"; private static final String OS_MEMBER = "</struct></value></member>"; private static final String OS_PARAMS = "</struct></value></param></params></methodCall>"; // Properties private static final boolean IS_ENABLED = PropertiesUtil.getBooleanProperty("opensubtitles.enable", Boolean.TRUE); private static final String SUB_LANGUAGE_ID = PropertiesUtil.getProperty("opensubtitles.language", ""); private static final String OS_USERNAME = PropertiesUtil.getProperty("opensubtitles.username", ""); private static final String OS_PASSWORD = PropertiesUtil.getProperty("opensubtitles.password", ""); static { if (IS_ENABLED) { // Check if subtitle language was selected if (StringUtils.isNotBlank(SUB_LANGUAGE_ID)) { // Part of opensubtitles.org protocol requirements LOG.info("Subtitles service allowed by www.OpenSubtitles.org"); // Login to opensubtitles.org system logIn(); } else { LOG.debug("No language selected in properties file"); } } else { LOG.trace("Plugin disabled."); } } /** * Login to OpenSubtitles */ private static void logIn() { try { if (StringUtils.isBlank(loginToken)) { String[] parm = { OS_USERNAME, OS_PASSWORD, "", OS_USER_AGENT }; String xml = generateXMLRPC("LogIn", parm); String ret = sendRPC(xml); getValue("status", ret); loginToken = getValue("token", ret); if (StringUtils.isBlank(loginToken)) { LOG.error("Login error.\n", ret); } else { LOG.debug("Login successful."); } // String l1 = login.equals("") ? "Anonymous" : login; } } catch (Exception ex) { LOG.error("Login Failed: {}", ex.getMessage()); } } /** * Logout of OpenSubtitles */ public static void logOut() { // Check if plugin is enabled, subtitle language was selected and that the login was successful if (IS_ENABLED && StringUtils.isNotBlank(SUB_LANGUAGE_ID) && StringUtils.isNotBlank(loginToken)) { try { String[] p1 = { loginToken }; String xml = generateXMLRPC("LogOut", p1); sendRPC(xml); } catch (Exception error) { LOG.error("Logout Failed"); } } } /** * Get subtitles for the video * * @param movie */ public void generate(Movie movie) { if (!IS_ENABLED) { return; } if (StringTools.isNotValidString(movie.getSubtitles()) || "NO".equalsIgnoreCase(movie.getSubtitles()) || movie.isTVShow()) { // Check if subtitle language was selected if (StringUtils.isBlank(SUB_LANGUAGE_ID)) { return; } // Check to see if we scrape the library, if we don't then skip the download if (!movie.isScrapeLibrary()) { LOG.debug("Skipped for {} due to scrape library flag", movie.getTitle()); return; } // Check that the login was successful if (StringUtils.isBlank(loginToken)) { LOG.debug("Login failed"); return; } // Check if all files have subtitle boolean allSubtitleExist = Boolean.TRUE; boolean allSubtitleExchange = Boolean.TRUE; // Go over all the movie files and check subtitle status for (MovieFile mf : movie.getMovieFiles()) { if (!mf.isSubtitlesExchange()) { allSubtitleExchange = Boolean.FALSE; } // Check if this movie already have subtitles for it if (mf.getFile() == null) { // The file pointer doesn't exist, so skip the file continue; } String path = mf.getFile().getAbsolutePath(); int index = path.lastIndexOf('.'); String basename = path.substring(0, index + 1); File subtitleFile = FileTools.fileCache.getFile(basename + "srt"); if (!subtitleFile.exists()) { allSubtitleExchange = Boolean.FALSE; allSubtitleExist = Boolean.FALSE; break; } } if (allSubtitleExchange) { LOG.debug("All subtitles exist for {}", movie.getTitle()); // Don't return yet, we might want to upload the files. //return; } // Check if all files have subtitle , or this is a tv show, that each episode is by itself if (!allSubtitleExist || movie.getMovieType().equals(Movie.TYPE_TVSHOW)) { // Go over all the movie files for (MovieFile mf : movie.getMovieFiles()) { // Check if this movie already have subtitles for it String path = mf.getFile().getAbsolutePath(); int index = path.lastIndexOf('.'); String basename = path.substring(0, index + 1); File subtitleFile = FileTools.fileCache.getFile(basename + "srt"); if (!subtitleFile.exists()) { if (subtitleDownload(movie, mf.getFile(), subtitleFile) == Boolean.TRUE) { movie.setDirty(DirtyFlag.INFO, Boolean.TRUE); mf.setSubtitlesExchange(Boolean.TRUE); } } else if (!mf.isSubtitlesExchange()) { File[] movieFileArray = new File[1]; File[] subtitleFileArray = new File[1]; movieFileArray[0] = mf.getFile(); subtitleFileArray[0] = subtitleFile; if (subtitleUpload(movie, movieFileArray, subtitleFileArray) == Boolean.TRUE) { movie.setDirty(DirtyFlag.INFO, Boolean.TRUE); mf.setSubtitlesExchange(Boolean.TRUE); } } } } else { // Upload all movie files as a group File[] movieFileArray = new File[movie.getMovieFiles().size()]; File[] subtitleFileArray = new File[movie.getMovieFiles().size()]; int i = 0; // Go over all the movie files for (MovieFile mf : movie.getMovieFiles()) { // Check if this movie already have subtitles for it String path = mf.getFile().getAbsolutePath(); int index = path.lastIndexOf('.'); String basename = path.substring(0, index + 1); File subtitleFile = new File(basename + "srt"); movieFileArray[i] = mf.getFile(); subtitleFileArray[i] = subtitleFile; i++; } if (subtitleUpload(movie, movieFileArray, subtitleFileArray) == Boolean.TRUE) { movie.setDirty(DirtyFlag.INFO, Boolean.TRUE); // Go over all the movie files and mark the exchange for (MovieFile mf : movie.getMovieFiles()) { mf.setSubtitlesExchange(Boolean.TRUE); } } } } else { LOG.debug("Skipping subtitle download for {}, subtitles already exist: {}", movie.getTitle(), movie.getSubtitles()); } } private static boolean subtitleDownload(Movie movie, File movieFile, File subtitleFile) { try { String ret; String xml; String moviehash = getHash(movieFile); String moviebytesize = String.valueOf(movieFile.length()); xml = generateXMLRPCSS(moviehash, moviebytesize); ret = sendRPC(xml); String subDownloadLink = getValue("SubDownloadLink", ret); if (StringUtils.isBlank(subDownloadLink)) { String moviefilename = movieFile.getName(); // Do not search by file name for BD rip files in the format 0xxxx.m2ts if (!(moviefilename.toUpperCase().endsWith(".M2TS") && moviefilename.startsWith("0"))) { // Try to find the subtitle using file name String subfilename = subtitleFile.getName(); int index = subfilename.lastIndexOf('.'); String query = subfilename.substring(0, index); xml = generateXMLRPCSS(query); ret = sendRPC(xml); subDownloadLink = getValue("SubDownloadLink", ret); } } if (StringUtils.isBlank(subDownloadLink)) { LOG.debug("Subtitle not found for {}", movieFile.getName()); return Boolean.FALSE; } LOG.debug("Download subtitle for {}", movie.getBaseName()); URL url = new URL(subDownloadLink); HttpURLConnection connection = (HttpURLConnection) (url .openConnection(YamjHttpClientBuilder.getProxy())); connection.setRequestProperty("Connection", "Close"); try (InputStream inputStream = connection.getInputStream()) { int code = connection.getResponseCode(); if (code != HttpURLConnection.HTTP_OK) { LOG.error("Download Failed"); return Boolean.FALSE; } FileTools.copy(inputStream, new FileOutputStream(subtitleFile)); } finally { connection.disconnect(); } String subLanguageID = getValue("SubLanguageID", ret); if (StringUtils.isNotBlank(subLanguageID)) { SubtitleTools.addMovieSubtitle(movie, subLanguageID); } else { SubtitleTools.addMovieSubtitle(movie, "YES"); } return Boolean.TRUE; } catch (Exception ex) { LOG.error("Download Exception (Movie Not Found)", ex); return Boolean.FALSE; } } private static boolean subtitleUpload(Movie movie, File[] movieFile, File[] subtitleFile) { try { String ret; String xml; String idmovieimdb = movie.getId(ImdbPlugin.IMDB_PLUGIN_ID); if (StringUtils.isNotBlank(idmovieimdb) && idmovieimdb.length() >= 6) { idmovieimdb = String.valueOf(NumberUtils.toInt(idmovieimdb.substring(2))); } String[] subfilename = new String[movieFile.length]; String[] subhash = new String[movieFile.length]; String[] subcontent = new String[movieFile.length]; String[] moviehash = new String[movieFile.length]; String[] moviebytesize = new String[movieFile.length]; String[] movietimems = new String[movieFile.length]; String[] movieframes = new String[movieFile.length]; String[] moviefps = new String[movieFile.length]; String[] moviefilename = new String[movieFile.length]; for (int i = 0; i < movieFile.length; i++) { subfilename[i] = subtitleFile[i].getName(); subhash[i] = ""; subcontent[i] = ""; moviehash[i] = getHash(movieFile[i]); moviebytesize[i] = String.valueOf(movieFile[i].length()); movietimems[i] = ""; movieframes[i] = ""; moviefps[i] = String.valueOf(movie.getFps()); moviefilename[i] = movieFile[i].getName(); byte[] s; try (FileInputStream fisSubtitleFile = new FileInputStream(subtitleFile[i])) { s = new byte[fisSubtitleFile.available()]; fisSubtitleFile.read(s); } MessageDigest md = MessageDigest.getInstance("MD5"); md.update(s); subhash[i] = hashstring(md.digest()); try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); DeflaterOutputStream deflaterOS = new DeflaterOutputStream(baos)) { deflaterOS.write(s); deflaterOS.finish(); subcontent[i] = tuBase64(baos.toByteArray()); } } // Check if upload of this subtitle is required xml = generateXMLRPCTUS(subhash, subfilename, moviehash, moviebytesize, movietimems, movieframes, moviefps, moviefilename); ret = sendRPC(xml); String alreadyindb = getIntValue("alreadyindb", ret); if (!"0".equals(alreadyindb)) { LOG.debug("Subtitle already in db for {}", movie.getBaseName()); return Boolean.TRUE; } LOG.debug("Upload Subtitle for {}", movie.getBaseName()); // Upload the subtitle xml = generateXMLRPCUS(idmovieimdb, subhash, subcontent, subfilename, moviehash, moviebytesize, movietimems, movieframes, moviefps, moviefilename); sendRPC(xml); return Boolean.TRUE; } catch (NumberFormatException | IOException | NoSuchAlgorithmException ex) { LOG.error("Upload Failed: {}", ex.getMessage()); return Boolean.FALSE; } } private static String generateXMLRPCSS(String moviehash, String moviebytesize) { StringBuilder sb = new StringBuilder(OS_METHOD_START); sb.append("SearchSubtitles"); sb.append(OS_METHOD_END); sb.append(loginToken); sb.append(OS_PARAM); sb.append("<member><value><struct>"); sb.append(addymember("sublanguageid", SUB_LANGUAGE_ID)); sb.append(addymember("moviehash", moviehash)); sb.append(addymember("moviebytesize", moviebytesize)); sb.append(OS_MEMBER); sb.append(OS_PARAMS); return sb.toString(); } private static String generateXMLRPCSS(String query) { StringBuilder sb = new StringBuilder(OS_METHOD_START); sb.append("SearchSubtitles"); sb.append(OS_METHOD_END); sb.append(loginToken); sb.append(OS_PARAM); sb.append("<member><value><struct>"); sb.append(addymember("sublanguageid", SUB_LANGUAGE_ID)); sb.append(addymember("query", query)); sb.append(OS_MEMBER); sb.append(OS_PARAMS); return sb.toString(); } private static String generateXMLRPCUS(String idmovieimdb, String[] subhash, String[] subcontent, String[] subfilename, String[] moviehash, String[] moviebytesize, String[] movietimems, String[] movieframes, String[] moviefps, String[] moviefilename) { StringBuilder sb = new StringBuilder(OS_METHOD_START); sb.append("UploadSubtitles"); sb.append(OS_METHOD_END); sb.append(loginToken); sb.append(OS_PARAM); for (int i = 0; i < subhash.length; i++) { sb.append("<member><name>"); sb.append("cd"); sb.append(i + 1); sb.append("</name><value><struct>"); sb.append(addymember("movietimems", movietimems[i])); sb.append(addymember("moviebytesize", moviebytesize[i])); sb.append(addymember("subfilename", subfilename[i])); sb.append(addymember("subcontent", subcontent[i])); sb.append(addymember("subhash", subhash[i])); sb.append(addymember("movieframes", movieframes[i])); sb.append(addymember("moviefps", moviefps[i])); sb.append(addymember("moviefilename", moviefilename[i])); sb.append(addymember("moviehash", moviehash[i])); sb.append(OS_MEMBER); } sb.append("<member><name>baseinfo</name><value><struct>"); sb.append(addymember("sublanguageid", SUB_LANGUAGE_ID)); sb.append(addymember("idmovieimdb", idmovieimdb)); sb.append(addymember("subauthorcomment", "")); sb.append(addymember("movieaka", "")); sb.append(addymember("moviereleasename", "")); sb.append(OS_MEMBER); sb.append(OS_PARAMS); return sb.toString(); } private static String generateXMLRPCTUS(String[] subhash, String[] subfilename, String[] moviehash, String[] moviebytesize, String[] movietimems, String[] movieframes, String[] moviefps, String[] moviefilename) { StringBuilder sb = new StringBuilder(OS_METHOD_START); sb.append("TryUploadSubtitles"); sb.append(OS_METHOD_END); sb.append(loginToken); sb.append(OS_PARAM); for (int i = 0; i < subhash.length; i++) { sb.append("<member><name>"); sb.append("cd"); sb.append(i + 1); sb.append("</name><value><struct>"); sb.append(addymember("movietimems", movietimems[i])); sb.append(addymember("moviebytesize", moviebytesize[i])); sb.append(addymember("subfilename", subfilename[i])); sb.append(addymember("subhash", subhash[i])); sb.append(addymember("movieframes", movieframes[i])); sb.append(addymember("moviefps", moviefps[i])); sb.append(addymember("moviefilename", moviefilename[i])); sb.append(addymember("moviehash", moviehash[i])); sb.append(OS_MEMBER); } sb.append(OS_PARAMS); return sb.toString(); } private static String sendRPC(String xml) throws IOException { StringBuilder str = new StringBuilder(); String strona = OS_DB_SERVER; String logowanie = xml; URL url = new URL(strona); URLConnection connection = url.openConnection(); connection.setRequestProperty("Connection", "Close"); // connection.setRequestProperty("Accept","text/html"); connection.setRequestProperty("Content-Type", "text/xml"); connection.setDoOutput(Boolean.TRUE); connection.getOutputStream().write(logowanie.getBytes("UTF-8")); try (Scanner in = new Scanner(connection.getInputStream())) { while (in.hasNextLine()) { str.append(in.nextLine()); } } ((HttpURLConnection) connection).disconnect(); return str.toString(); } private static String getValue(String find, String xml) { int a = xml.indexOf(find); if (a != -1) { int b = xml.indexOf("<string>", a); int c = xml.indexOf("</string>", b); if ((b != -1) && (c != -1)) { return xml.substring(b + 8, c); } } return ""; } private static String getIntValue(String find, String xml) { String str = ""; int a = xml.indexOf(find); if (a != -1) { int b = xml.indexOf("<int>", a); int c = xml.indexOf("</int>", b); if ((b != -1) && (c != -1)) { return xml.substring(b + 5, c); } } return str; } private static String hashstring(byte[] arayhash) { StringBuilder str = new StringBuilder(); String hex = "0123456789abcdef"; for (int i = 0; i < arayhash.length; i++) { int m = arayhash[i] & 0xff; str.append(hex.charAt(m >> 4) + hex.charAt(m & 0xf)); } return str.toString(); } @SuppressWarnings("unused") private static String generateXMLRPCDetectLang(String body) { StringBuilder sb = new StringBuilder(OS_METHOD_START); sb.append("DetectLanguage"); sb.append("</methodName><params>"); sb.append("<param><value><string>"); sb.append(loginToken); sb.append("</string></value></param>"); sb.append("<param><value>"); sb.append(body); sb.append(" </value></param>"); sb.append("</params></methodCall>"); return sb.toString(); } private static String generateXMLRPC(String procname, String[] s) { StringBuilder str = new StringBuilder(); str.append(OS_METHOD_START); str.append(procname).append("</methodName><params>"); for (String item : s) { str.append("<param><value><string>").append(item).append("</string></value></param>"); } str.append("</params></methodCall>"); return str.toString(); } private static String addEncje(String inputString) { String cleanString = inputString.replace("&", "&"); cleanString = cleanString.replace("<", "<"); cleanString = cleanString.replace(">", ">"); cleanString = cleanString.replace("'", "'"); cleanString = cleanString.replace("\"", """); return cleanString; } private static String tuBase64(byte[] s) { // You may use this for lower applet size // return new sun.misc.BASE64Encoder().encode(s); // char tx; // long mili = Calendar.getInstance().getTimeInMillis(); String t = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; char[] t2 = t.toCharArray(); char[] wynik = new char[(s.length / 3 + 1) * 4]; int tri; for (int i = 0; i < (s.length / 3 + 1); i++) { tri = 0; int iii = i * 3; try { tri |= (s[iii] & 0xff) << 16; tri |= (s[iii + 1] & 0xff) << 8; tri |= (s[iii + 2] & 0xff); } catch (Exception ex) { LOG.trace("Failed to convert string in tuBase64", ex); } for (int j = 0; j < 4; j++) { wynik[i * 4 + j] = (iii * 8 + j * 6 >= s.length * 8) ? '=' : t2[(tri >> 6 * (3 - j)) & 0x3f]; } // if((i+1) % 19 ==0 ) str +="\n"; } String str = new String(wynik); if (str.endsWith("====")) { str = str.substring(0, str.length() - 4); } return str; } private static String addymember(String name, String value) { return "<member><name>" + name + "</name><value><string>" + OpenSubtitlesPlugin.addEncje(value) + "</string></value></member>"; } private static String getHash(File f) throws IOException { String s; try ( // Open the file and then get a channel from the stream FileInputStream fis = new FileInputStream(f); FileChannel fc = fis.getChannel()) { long sz = fc.size(); if (sz < 65536) { fc.close(); fis.close(); return "NoHash"; } MappedByteBuffer bb = fc.map(FileChannel.MapMode.READ_ONLY, 0, 65536); long sum = sz; bb.order(ByteOrder.LITTLE_ENDIAN); for (int i = 0; i < 65536 / 8; i++) { sum += bb.getLong();// sum(bb); } bb = fc.map(FileChannel.MapMode.READ_ONLY, sz - 65536, 65536); bb.order(ByteOrder.LITTLE_ENDIAN); for (int i = 0; i < 65536 / 8; i++) { sum += bb.getLong();// sum(bb); } sum = sum & 0xffffffffffffffffL; s = String.format("%016x", sum); } return s; } }