com.moviejukebox.plugin.trailer.AppleTrailersPlugin.java Source code

Java tutorial

Introduction

Here is the source code for com.moviejukebox.plugin.trailer.AppleTrailersPlugin.java

Source

/*
 *      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.trailer;

import com.moviejukebox.model.ExtraFile;
import com.moviejukebox.model.Movie;
import com.moviejukebox.tools.*;
import java.io.IOException;
import java.io.InputStream;
import java.net.*;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class AppleTrailersPlugin extends TrailerPlugin {

    private static final Logger LOG = LoggerFactory.getLogger(AppleTrailersPlugin.class);
    private static final String CONFIG_RESOLUTION = PropertiesUtil.getProperty("appletrailers.resolution", "");
    private static final boolean CONFIG_DOWNLOAD = PropertiesUtil.getBooleanProperty("appletrailers.download",
            Boolean.FALSE);
    private static final String CONFIG_TYPES = PropertiesUtil.getProperty("appletrailers.trailertypes",
            "tlr,clip,tsr,30sec,640w");
    private static final int CONFIG_MAX = PropertiesUtil.getIntProperty("appletrailers.max", 0);
    private static final boolean CONFIG_TYPES_INCLUDE = PropertiesUtil
            .getBooleanProperty("appletrailers.typesinclude", Boolean.TRUE);
    private static final String CONFIG_REPLACE_URL = PropertiesUtil.getProperty("appletrailers.replaceurl",
            "www.apple.com");
    private static final String EXTENSIONS = "mov|m4v";
    private static final String SUBDOMAINS = "movies|images|trailers";
    private static final Pattern TRAILER_URL_PATTERN = Pattern
            .compile("http://(" + SUBDOMAINS + ").apple.com/movies/[^\\\"]+\\.(" + EXTENSIONS + ")");
    private static final String[] RESOLUTION_ARRAY = { "1080p", "720p", "480p", "640", "480" };

    public AppleTrailersPlugin() {
        super();
        trailersPluginName = "AppleTrailers";
    }

    @Override
    public final boolean generate(Movie movie) {
        // Check if trailer resolution was selected
        if (StringUtils.isBlank(CONFIG_RESOLUTION)) {
            return false;
        }

        String movieName = movie.getOriginalTitle();

        String trailerPageUrl = getTrailerPageUrl(movieName);

        movie.setTrailerLastScan(new Date().getTime()); // Set the last scan to now

        if (Movie.UNKNOWN.equalsIgnoreCase(trailerPageUrl)) {
            LOG.debug("Trailer not found for {}", movie.getBaseName());
            return false;
        }

        Set<String> trailersUrl = new LinkedHashSet<>();
        Set<String> bestTrailersUrl = new LinkedHashSet<>();

        getTrailerSubUrl(trailerPageUrl, trailersUrl);

        selectBestTrailer(trailersUrl, bestTrailersUrl);

        int trailerDownloadCnt = 0;

        if (bestTrailersUrl.isEmpty()) {
            LOG.debug("No trailers found for {}", movie.getBaseName());
            return false;
        }

        boolean isExchangeOk = false;

        for (String trailerRealUrl : bestTrailersUrl) {

            if (trailerDownloadCnt >= CONFIG_MAX) {
                LOG.debug("Downloaded maximum of {} trailer", CONFIG_MAX, CONFIG_MAX == 1 ? "" : "s");
                break;
            }

            // Add the trailer URL to the movie
            ExtraFile extra = new ExtraFile();
            extra.setTitle("TRAILER-" + getTrailerTitle(trailerRealUrl));

            // Is the found trailer one of the types to download/link to?
            if (!isValidTrailer(getFilenameFromUrl(trailerRealUrl))) {
                LOG.debug("Trailer skipped: {}", getFilenameFromUrl(trailerRealUrl));
                continue; // Quit the rest of the trailer loop.
            }

            // Issue with the naming of URL for trailer download
            // See: http://www.hd-trailers.net/blog/how-to-download-hd-trailers-from-apple/
            trailerRealUrl = trailerRealUrl.replace("www.apple.com", CONFIG_REPLACE_URL);
            trailerRealUrl = trailerRealUrl.replace("images.apple.com", CONFIG_REPLACE_URL);
            trailerRealUrl = trailerRealUrl.replace("movies.apple.com", CONFIG_REPLACE_URL);

            LOG.debug("Trailer found for {} ({})", movie.getBaseName(), getFilenameFromUrl(trailerRealUrl));
            trailerDownloadCnt++;

            // Check if we need to download the trailer, or just link to it
            if (CONFIG_DOWNLOAD) {
                String trailerAppleName = getFilenameFromUrl(trailerRealUrl);
                trailerAppleName = trailerAppleName.substring(0, trailerAppleName.lastIndexOf('.'));
                isExchangeOk = downloadTrailer(movie, trailerRealUrl, trailerAppleName, extra);
            } else {
                // Just link to the trailer
                int underscore = trailerRealUrl.lastIndexOf('_');
                if (underscore > 0 && "h".equals(trailerRealUrl.substring(underscore + 1, underscore + 2))) {
                    // remove the "h" from the trailer URL for streaming
                    trailerRealUrl = trailerRealUrl.substring(0, underscore + 1)
                            + trailerRealUrl.substring(underscore + 2);
                }
                extra.setFilename(trailerRealUrl);
                movie.addExtraFile(extra);
                isExchangeOk = true;
            }
        }

        return isExchangeOk;
    }

    @Override
    public String getName() {
        return "apple";
    }

    private String getTrailerPageUrl(String movieName) {
        String doubleQuoteComma = "\",";
        String titleKey = "\"title\":\"";
        String locationKey = "\"location\":\"";

        try {
            String searchURL = "http://trailers.apple.com/trailers/home/scripts/quickfind.php?callback=searchCallback&q="
                    + URLEncoder.encode(movieName, "UTF-8");

            String xml = httpClient.request(searchURL);

            int index = 0;
            int endIndex;
            while (true) {
                index = xml.indexOf(titleKey, index);
                if (index == -1) {
                    break;
                }

                index += titleKey.length();

                endIndex = xml.indexOf(doubleQuoteComma, index);
                if (endIndex == -1) {
                    break;
                }

                String trailerTitle = decodeEscapeICU(xml.substring(index, endIndex));

                index = endIndex + doubleQuoteComma.length();

                index = xml.indexOf(locationKey, index);
                if (index == -1) {
                    break;
                }

                index += locationKey.length();

                endIndex = xml.indexOf(doubleQuoteComma, index);
                if (endIndex == -1) {
                    break;
                }

                String trailerLocation = decodeEscapeICU(xml.substring(index, endIndex));

                index = endIndex + doubleQuoteComma.length();

                if (trailerTitle.equalsIgnoreCase(movieName)) {
                    String trailerUrl;

                    int itmsIndex = trailerLocation.indexOf("itms://");
                    if (itmsIndex == -1) {
                        // Convert relative URL to absolute URL - some urls are already absolute, and some relative
                        trailerUrl = getAbsUrl("http://www.apple.com/trailers/", trailerLocation);
                    } else {
                        trailerUrl = "http" + trailerLocation.substring(itmsIndex + 4);
                    }

                    return trailerUrl;
                }
            }

        } catch (IOException error) {
            LOG.error("Failed retreiving trailer for movie : {}", movieName);
            LOG.error(SystemTools.getStackTrace(error));
            return Movie.UNKNOWN;
        }

        return Movie.UNKNOWN;
    }

    private void getTrailerSubUrl(String trailerPageUrl, Set<String> trailersUrl) {
        try {

            String xml = httpClient.request(trailerPageUrl);

            // Try to find the movie link on the main page
            getTrailerMovieUrl(xml, trailersUrl);

            // New URL
            String trailerPageUrlNew = trailerPageUrl.replace("//www.apple.com/", "//trailers.apple.com/");

            String trailerPageUrlWebInc = getAbsUrl(trailerPageUrlNew, "includes/playlists/web.inc");
            String xmlWebInc = getSubPage(trailerPageUrlWebInc);
            // Try to find the movie link on the WebInc page
            getTrailerMovieUrl(xmlWebInc, trailersUrl);

            // Search for the 'HD' Page
            String trailerPageUrlHD = getAbsUrl(trailerPageUrlNew, "hd");
            String xmlHD = getSubPage(trailerPageUrlHD);

            // Only search if the HD URL is valid
            if (StringTools.isValidString(xmlHD)) {
                // Try to find the movie link on the HD page
                getTrailerMovieUrl(xmlHD, trailersUrl);

                String trailerPageUrlHDWebInc = getAbsUrl(trailerPageUrlHD, "includes/playlists/web.inc");
                String xmlHDWebInc = getSubPage(trailerPageUrlHDWebInc);
                // Try to find the movie link on the WebInc HD page
                getTrailerMovieUrl(xmlHDWebInc, trailersUrl);
            } else {
                LOG.debug("No valid HD URL found for {}", trailerPageUrl);
            }
        } catch (IOException ex) {
            LOG.error("Error : {}", ex.getMessage());
            LOG.error(SystemTools.getStackTrace(ex));
        }
    }

    /**
     * Get sub page url - if error return empty page
     *
     * @param url
     * @return
     */
    private String getSubPage(String url) {
        try {
            return httpClient.request(url);
        } catch (IOException ex) {
            return StringUtils.EMPTY;
        }
    }

    private static void getTrailerMovieUrl(String xml, Set<String> trailersUrl) {
        Matcher m = TRAILER_URL_PATTERN.matcher(xml);
        while (m.find()) {
            String movieUrl = m.group();
            trailersUrl.add(movieUrl);
        }
    }

    private static void selectBestTrailer(Set<String> trailersUrl, Set<String> bestTrailersUrl) {

        boolean startSearch = false;

        for (String resolution : RESOLUTION_ARRAY) {
            if (CONFIG_RESOLUTION.equals(resolution)) {
                startSearch = true;
            }
            if (startSearch) {
                for (String curURL : trailersUrl) {
                    // Search for a specific resolution
                    if (curURL.contains(resolution)) {
                        addTailerRealUrl(bestTrailersUrl, curURL);
                    }
                }
            }

            if (!bestTrailersUrl.isEmpty()) {
                break;
            }
        }
    }

    private static void addTailerRealUrl(Set<String> bestTrailersUrl, String trailerUrl) {
        String trailerRealUrl = getTrailerRealUrl(trailerUrl);
        bestTrailersUrl.add(trailerRealUrl);
    }

    private static String getTrailerRealUrl(String trailerUrl) {
        try {
            URL url = new URL(trailerUrl);
            HttpURLConnection connection = (HttpURLConnection) (url
                    .openConnection(YamjHttpClientBuilder.getProxy()));
            try (InputStream inputStream = connection.getInputStream()) {
                byte[] buf = new byte[1024];
                int len;
                len = inputStream.read(buf);

                // Check if too much data read, that this is the real url already
                if (len == 1024) {
                    return trailerUrl;
                }

                String mov = new String(buf);

                int pos = 44;
                StringBuilder realUrl = new StringBuilder();

                while (mov.charAt(pos) != 0) {
                    realUrl.append(mov.charAt(pos));

                    pos++;
                }

                return getAbsUrl(trailerUrl, realUrl.toString());
            }
        } catch (IOException ex) {
            LOG.error("Error : {}", ex.getMessage());
            LOG.error(SystemTools.getStackTrace(ex));
            return Movie.UNKNOWN;
        }
    }

    private static String getTrailerTitle(String url) {
        int start = url.lastIndexOf('/');
        int end = url.indexOf(".mov", start);

        if ((start == -1) || (end == -1)) {
            return Movie.UNKNOWN;
        }

        StringBuilder title = new StringBuilder();

        for (int i = start + 1; i < end; i++) {
            if ((url.charAt(i) == '-') || (url.charAt(i) == '_')) {
                title.append(' ');
            } else {
                if (i == start + 1) {
                    title.append(Character.toUpperCase(url.charAt(i)));
                } else {
                    title.append(url.charAt(i));
                }
            }
        }

        return title.toString();
    }

    private static String getAbsUrl(String baseUrl, String relativeUrl) {
        try {
            URL baseURL = new URL(baseUrl);
            URL absURL = new URL(baseURL, relativeUrl);
            return absURL.toString();
        } catch (MalformedURLException ex) {
            return Movie.UNKNOWN;
        }
    }

    private static String decodeEscapeICU(String str) {
        StringBuilder newString = new StringBuilder();

        int loop = 0;
        while (loop < str.length()) {
            // Check ICU escaping
            if ((str.charAt(loop) == '%') && (loop + 5 < str.length()) && (str.charAt(loop + 1) == 'u')) {

                String value = str.substring(loop + 2, loop + 6);
                int intValue = Integer.parseInt(value, 16);

                // fix for ' char
                if (intValue == 0x2019) {
                    intValue = 0x0027;
                }

                char c = (char) intValue;

                newString.append(c);
                loop += 6;
            } else {
                if (str.charAt(loop) == '\\') {
                    loop++;
                } else {
                    newString.append(str.charAt(loop));
                    loop++;
                }
            }
        }

        return newString.toString();
    }

    /**
     * Extract the filename from the URL
     *
     * @param fullUrl
     * @return
     */
    private static String getFilenameFromUrl(String fullUrl) {
        int nameStart = fullUrl.lastIndexOf('/') + 1;
        return fullUrl.substring(nameStart);
    }

    /**
     * Check the trailer filename against the valid trailer types from
     * appletrailers.trailertypes
     *
     * @param trailerFilename
     * @return
     */
    private static boolean isValidTrailer(String trailerFilename) {
        // Set the default response for the validTrailer
        boolean validTrailer = !CONFIG_TYPES_INCLUDE;

        for (String ttype : CONFIG_TYPES.split(",")) {
            if (trailerFilename.lastIndexOf(ttype) > 0) {
                /*
                 If the type is found, and configTypesInclude, then this is a valid trailer, otherwise it's invalid and should be excluded
                 */
                validTrailer = CONFIG_TYPES_INCLUDE;
            }
        }

        return validTrailer;
    }
}