Search in sources :

Example 1 with DLNAMediaSubtitle

use of net.pms.dlna.DLNAMediaSubtitle in project UniversalMediaServer by UniversalMediaServer.

the class FileUtil method browseFolderForSubtitles.

private static boolean browseFolderForSubtitles(File subFolder, File file, DLNAMediaInfo media, final boolean useCache) {
    boolean found = false;
    final Set<String> supported = SubtitleType.getSupportedFileExtensions();
    File[] allSubs = null;
    // TODO This caching scheme is very restrictive locking the whole cache
    // while populating a single folder. A more effective solution should
    // be implemented.
    subtitleCacheLock.lock();
    try {
        if (useCache) {
            allSubs = subtitleCache.get(subFolder);
        }
        if (allSubs == null) {
            allSubs = subFolder.listFiles(new FilenameFilter() {

                @Override
                public boolean accept(File dir, String name) {
                    String ext = FilenameUtils.getExtension(name).toLowerCase();
                    if ("sub".equals(ext)) {
                        // they'll come in unambiguously as vobsub via the idx file anyway
                        return replaceExtension(new File(dir, name), "idx", true, true) == null;
                    }
                    return supported.contains(ext);
                }
            });
            if (allSubs != null) {
                subtitleCache.put(subFolder, allSubs);
            }
        }
    } finally {
        subtitleCacheLock.unlock();
    }
    String fileName = getFileNameWithoutExtension(file.getName()).toLowerCase();
    if (allSubs != null) {
        for (File f : allSubs) {
            if (f.isFile() && !f.isHidden()) {
                String fName = f.getName().toLowerCase();
                for (String ext : supported) {
                    if (fName.length() > ext.length() && fName.startsWith(fileName) && endsWithIgnoreCase(fName, "." + ext)) {
                        int a = fileName.length();
                        int b = fName.length() - ext.length() - 1;
                        String code = "";
                        if (a <= b) {
                            // handling case with several dots: <video>..<extension>
                            code = fName.substring(a, b);
                        }
                        if (code.startsWith(".")) {
                            code = code.substring(1);
                        }
                        boolean exists = false;
                        if (media != null) {
                            for (DLNAMediaSubtitle sub : media.getSubtitleTracksList()) {
                                if (f.equals(sub.getExternalFile())) {
                                    exists = true;
                                } else if (equalsIgnoreCase(ext, "idx") && sub.getType() == SubtitleType.MICRODVD) {
                                    // sub+idx => VOBSUB
                                    sub.setType(SubtitleType.VOBSUB);
                                    exists = true;
                                } else if (equalsIgnoreCase(ext, "sub") && sub.getType() == SubtitleType.VOBSUB) {
                                    // VOBSUB
                                    try {
                                        sub.setExternalFile(f, null);
                                    } catch (FileNotFoundException ex) {
                                        LOGGER.warn("File not found during external subtitles scan: {}", ex.getMessage());
                                        LOGGER.trace("", ex);
                                    }
                                    exists = true;
                                }
                            }
                        }
                        if (!exists) {
                            String forcedLang = null;
                            DLNAMediaSubtitle sub = new DLNAMediaSubtitle();
                            // fake id, not used
                            sub.setId(100 + (media == null ? 0 : media.getSubtitleTracksList().size()));
                            if (code.length() == 0 || !Iso639.codeIsValid(code)) {
                                sub.setLang(DLNAMediaSubtitle.UND);
                                sub.setType(SubtitleType.valueOfFileExtension(ext));
                                if (code.length() > 0) {
                                    sub.setSubtitlesTrackTitleFromMetadata(code);
                                    if (sub.getSubtitlesTrackTitleFromMetadata().contains("-")) {
                                        String flavorLang = sub.getSubtitlesTrackTitleFromMetadata().substring(0, sub.getSubtitlesTrackTitleFromMetadata().indexOf('-'));
                                        String flavorTitle = sub.getSubtitlesTrackTitleFromMetadata().substring(sub.getSubtitlesTrackTitleFromMetadata().indexOf('-') + 1);
                                        if (Iso639.codeIsValid(flavorLang)) {
                                            sub.setLang(flavorLang);
                                            sub.setSubtitlesTrackTitleFromMetadata(flavorTitle);
                                            forcedLang = flavorLang;
                                        }
                                    }
                                }
                            } else {
                                sub.setLang(code);
                                sub.setType(SubtitleType.valueOfFileExtension(ext));
                                forcedLang = code;
                            }
                            try {
                                sub.setExternalFile(f, forcedLang);
                            } catch (FileNotFoundException ex) {
                                LOGGER.warn("File not found during external subtitles scan: {}", ex.getMessage());
                                LOGGER.trace("", ex);
                            }
                            found = true;
                            if (media != null) {
                                media.getSubtitleTracksList().add(sub);
                            }
                        }
                    }
                }
            }
        }
    }
    return found;
}
Also used : DLNAMediaSubtitle(net.pms.dlna.DLNAMediaSubtitle)

Example 2 with DLNAMediaSubtitle

use of net.pms.dlna.DLNAMediaSubtitle in project UniversalMediaServer by UniversalMediaServer.

the class Request method answer.

/**
 * Construct a proper HTTP response to a received request. After the response has been
 * created, it is sent and the resulting {@link ChannelFuture} object is returned.
 * See <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html">RFC-2616</a>
 * for HTTP header field definitions.
 * @param output The {@link HttpResponse} object that will be used to construct the response.
 * @param startStopListenerDelegate The {@link StartStopListenerDelegate} object that is used
 * 			to notify plugins that the {@link DLNAResource} is about to start playing.
 * @throws IOException
 */
public void answer(OutputStream output, StartStopListenerDelegate startStopListenerDelegate) throws IOException {
    this.output = output;
    List<String> responseHeader = new ArrayList<>();
    // 0 and above are valid Content-Length values, -1 means omit
    long CLoverride = -2;
    if (lowRange != 0 || highRange != 0) {
        appendToHeader(responseHeader, http10 ? HTTP_206_OK_10 : HTTP_206_OK);
    } else {
        if (soapaction != null && soapaction.contains("X_GetFeatureList")) {
            // If we don't return a 500 error, Samsung 2012 TVs time out.
            appendToHeader(responseHeader, http10 ? HTTP_500_10 : HTTP_500);
        } else {
            appendToHeader(responseHeader, http10 ? HTTP_200_OK_10 : HTTP_200_OK);
        }
    }
    StringBuilder response = new StringBuilder();
    DLNAResource dlna = null;
    boolean xbox360 = (mediaRenderer == null ? false : mediaRenderer.isXbox360());
    // Samsung 2012 TVs have a problematic preceding slash that needs to be removed.
    if (argument.startsWith("/")) {
        LOGGER.trace("Stripping preceding slash from: " + argument);
        argument = argument.substring(1);
    }
    if ((method.equals("GET") || method.equals("HEAD")) && argument.startsWith("console/")) {
        // Request to output a page to the HTML console.
        appendToHeader(responseHeader, "Content-Type: text/html");
        response.append(HTMLConsole.servePage(argument.substring(8)));
    } else if ((method.equals("GET") || method.equals("HEAD")) && argument.startsWith("get/")) {
        // Request to retrieve a file
        /**
         * Skip the leading "get/"
         * e.g. "get/0$1$5$3$4/Foo.mp4" -> "0$1$5$3$4/Foo.mp4"
         *
         * ExSport: I spotted on Android it is asking for "/get/0$2$4$2$1$3" which generates exception with response:
         * "Http: Response, HTTP/1.1, Status: Internal server error, URL: /get/0$2$4$2$1$3"
         * This should fix it
         */
        // Note: we intentionally include the trailing filename here because it may
        // be used to reconstruct lost Temp items.
        String id = argument.substring(argument.indexOf("get/") + 4);
        // Some clients escape the separators in their request: unescape them.
        id = id.replace("%24", "$");
        // Retrieve the DLNAresource itself.
        dlna = PMS.get().getRootFolder(mediaRenderer).getDLNAResource(id, mediaRenderer);
        String fileName = id.substring(id.indexOf('/') + 1);
        if (transferMode != null) {
            appendToHeader(responseHeader, "TransferMode.DLNA.ORG: " + transferMode);
        }
        if (dlna != null && dlna.isFolder() && !fileName.startsWith("thumbnail0000")) {
            // if we found a folder we MUST be asked for thumbnails
            // otherwise this is not allowed
            dlna = null;
        }
        if (dlna != null) {
            // DLNAresource was found.
            if (fileName.startsWith("thumbnail0000")) {
                // This is a request for a thumbnail file.
                DLNAImageProfile imageProfile = ImagesUtil.parseThumbRequest(fileName);
                appendToHeader(responseHeader, "Content-Type: " + imageProfile.getMimeType());
                appendToHeader(responseHeader, "Accept-Ranges: bytes");
                appendToHeader(responseHeader, "Expires: " + getFUTUREDATE() + " GMT");
                appendToHeader(responseHeader, "Connection: keep-alive");
                DLNAThumbnailInputStream thumbInputStream;
                if (!configuration.isShowCodeThumbs() && !dlna.isCodeValid(dlna)) {
                    thumbInputStream = dlna.getGenericThumbnailInputStream(null);
                } else {
                    dlna.checkThumbnail();
                    thumbInputStream = dlna.fetchThumbnailInputStream();
                }
                if (dlna instanceof RealFile && FullyPlayed.isFullyPlayedThumbnail(((RealFile) dlna).getFile())) {
                    thumbInputStream = FullyPlayed.addFullyPlayedOverlay(thumbInputStream);
                }
                inputStream = thumbInputStream.transcode(imageProfile, mediaRenderer != null ? mediaRenderer.isThumbnailPadding() : false);
                if (contentFeatures != null) {
                    appendToHeader(responseHeader, "ContentFeatures.DLNA.ORG: " + dlna.getDlnaContentFeatures(imageProfile, true));
                }
                if (inputStream != null && (lowRange > 0 || highRange > 0)) {
                    if (lowRange > 0) {
                        inputStream.skip(lowRange);
                    }
                    inputStream = DLNAResource.wrap(inputStream, highRange, lowRange);
                }
                appendToHeader(responseHeader, "Accept-Ranges: bytes");
                appendToHeader(responseHeader, "Connection: keep-alive");
            } else if (dlna.getMedia() != null && dlna.getMedia().getMediaType() == MediaType.IMAGE && dlna.isCodeValid(dlna)) {
                // This is a request for an image
                DLNAImageProfile imageProfile = ImagesUtil.parseImageRequest(fileName, null);
                if (imageProfile == null) {
                    // Parsing failed for some reason, we'll have to pick a profile
                    if (dlna.getMedia().getImageInfo() != null && dlna.getMedia().getImageInfo().getFormat() != null) {
                        switch(dlna.getMedia().getImageInfo().getFormat()) {
                            case GIF:
                                imageProfile = DLNAImageProfile.GIF_LRG;
                                break;
                            case PNG:
                                imageProfile = DLNAImageProfile.PNG_LRG;
                                break;
                            default:
                                imageProfile = DLNAImageProfile.JPEG_LRG;
                        }
                    } else {
                        imageProfile = DLNAImageProfile.JPEG_LRG;
                    }
                }
                appendToHeader(responseHeader, "Content-Type: " + imageProfile.getMimeType());
                appendToHeader(responseHeader, "Accept-Ranges: bytes");
                appendToHeader(responseHeader, "Expires: " + getFUTUREDATE() + " GMT");
                appendToHeader(responseHeader, "Connection: keep-alive");
                try {
                    InputStream imageInputStream;
                    if (dlna.getPlayer() instanceof ImagePlayer) {
                        ProcessWrapper transcodeProcess = dlna.getPlayer().launchTranscode(dlna, dlna.getMedia(), new OutputParams(configuration));
                        imageInputStream = transcodeProcess != null ? transcodeProcess.getInputStream(0) : null;
                    } else {
                        imageInputStream = dlna.getInputStream();
                    }
                    if (imageInputStream == null) {
                        LOGGER.warn("Input stream returned for \"{}\" was null, no image will be sent to renderer", fileName);
                    } else {
                        inputStream = DLNAImageInputStream.toImageInputStream(imageInputStream, imageProfile, false);
                        if (contentFeatures != null) {
                            appendToHeader(responseHeader, "ContentFeatures.DLNA.ORG: " + dlna.getDlnaContentFeatures(imageProfile, false));
                        }
                        if (inputStream != null && (lowRange > 0 || highRange > 0)) {
                            if (lowRange > 0) {
                                inputStream.skip(lowRange);
                            }
                            inputStream = DLNAResource.wrap(inputStream, highRange, lowRange);
                        }
                        appendToHeader(responseHeader, "Accept-Ranges: bytes");
                        appendToHeader(responseHeader, "Connection: keep-alive");
                    }
                } catch (IOException e) {
                    appendToHeader(responseHeader, "Content-Length: 0");
                    appendToHeader(responseHeader, "");
                    responseHeader.set(0, http10 ? HTTP_415_UNSUPPORTED_MEDIA_TYPE_10 : HTTP_415_UNSUPPORTED_MEDIA_TYPE);
                    sendHeader(responseHeader);
                    LOGGER.debug("Could not send image \"{}\": {}", dlna.getName(), e.getMessage() != null ? e.getMessage() : e.getClass().getSimpleName());
                    LOGGER.trace("", e);
                    return;
                }
            } else if (dlna.getMedia() != null && fileName.contains("subtitle0000") && dlna.isCodeValid(dlna)) {
                // This is a request for a subtitles file
                appendToHeader(responseHeader, "Content-Type: text/plain");
                appendToHeader(responseHeader, "Expires: " + getFUTUREDATE() + " GMT");
                DLNAMediaSubtitle sub = dlna.getMediaSubtitle();
                if (sub != null) {
                    // http://www.ps3mediaserver.org/forum/viewtopic.php?f=3&t=15805&p=75534#p75534
                    if (sub.isExternal()) {
                        try {
                            if (sub.getType() == SubtitleType.SUBRIP && mediaRenderer.isRemoveTagsFromSRTsubs()) {
                                // remove tags from .srt subs when renderer doesn't support them
                                inputStream = SubtitleUtils.removeSubRipTags(sub.getExternalFile());
                            } else {
                                inputStream = new FileInputStream(sub.getExternalFile());
                            }
                            LOGGER.trace("Loading external subtitles file: {}", sub);
                        } catch (IOException ioe) {
                            LOGGER.debug("Couldn't load external subtitles file: {}\nCause: {}", sub, ioe.getMessage());
                            LOGGER.trace("", ioe);
                        }
                    } else {
                        LOGGER.trace("Not loading external subtitles file because it is embedded: {}", sub);
                    }
                } else {
                    LOGGER.trace("Not loading external subtitles because dlna.getMediaSubtitle() returned null");
                }
            } else if (dlna.isCodeValid(dlna)) {
                // This is a request for a regular file.
                DLNAResource.Rendering origRendering = null;
                if (!mediaRenderer.equals(dlna.getDefaultRenderer())) {
                    // Adjust rendering details for this renderer
                    origRendering = dlna.updateRendering(mediaRenderer);
                }
                String name = dlna.getDisplayName(mediaRenderer);
                if (dlna.isNoName()) {
                    name = dlna.getName() + " " + dlna.getDisplayName(mediaRenderer);
                }
                inputStream = dlna.getInputStream(Range.create(lowRange, highRange, timeseek, timeRangeEnd), mediaRenderer);
                if (dlna.isResume()) {
                    // Update timeseek to possibly adjusted resume time
                    timeseek = dlna.getResume().getTimeOffset() / (double) 1000;
                }
                if (inputStream == null) {
                    // No inputStream indicates that transcoding / remuxing probably crashed.
                    LOGGER.error("There is no inputstream to return for " + name);
                } else {
                    startStopListenerDelegate.start(dlna);
                    appendToHeader(responseHeader, "Content-Type: " + getRendererMimeType(dlna.mimeType(), mediaRenderer, dlna.getMedia()));
                    if (dlna.getMedia() != null && !configuration.isDisableSubtitles() && dlna.getMediaSubtitle() != null && dlna.getMediaSubtitle().isStreamable()) {
                        // Some renderers (like Samsung devices) allow a custom header for a subtitle URL
                        String subtitleHttpHeader = mediaRenderer.getSubtitleHttpHeader();
                        if (isNotBlank(subtitleHttpHeader)) {
                            // Device allows a custom subtitle HTTP header; construct it
                            DLNAMediaSubtitle sub = dlna.getMediaSubtitle();
                            String subtitleUrl;
                            String subExtension = sub.getType().getExtension();
                            if (isNotBlank(subExtension)) {
                                subExtension = "." + subExtension;
                            }
                            subtitleUrl = "http://" + PMS.get().getServer().getHost() + ':' + PMS.get().getServer().getPort() + "/get/" + id.substring(0, id.indexOf('/')) + "/subtitle0000" + subExtension;
                            appendToHeader(responseHeader, subtitleHttpHeader + ": " + subtitleUrl);
                        } else {
                            LOGGER.trace("Did not send subtitle headers because mediaRenderer.getSubtitleHttpHeader() returned {}", subtitleHttpHeader == null ? "null" : "\"" + subtitleHttpHeader + "\"");
                        }
                    } else if (LOGGER.isTraceEnabled()) {
                        ArrayList<String> reasons = new ArrayList<>();
                        if (dlna.getMedia() == null) {
                            reasons.add("dlna.getMedia() is null");
                        }
                        if (configuration.isDisableSubtitles()) {
                            reasons.add("configuration.isDisabledSubtitles() is true");
                        }
                        if (dlna.getMediaSubtitle() == null) {
                            reasons.add("dlna.getMediaSubtitle() is null");
                        } else if (!dlna.getMediaSubtitle().isStreamable()) {
                            reasons.add("dlna.getMediaSubtitle().isStreamable() is false");
                        }
                        LOGGER.trace("Did not send subtitle headers because {}", StringUtil.createReadableCombinedString(reasons));
                    }
                    // Response generation:
                    // We use -1 for arithmetic convenience but don't send it as a value.
                    // If Content-Length < 0 we omit it, for Content-Range we use '*' to signify unspecified.
                    boolean chunked = mediaRenderer.isChunkedTransfer();
                    // Determine the total size. Note: when transcoding the length is
                    // not known in advance, so DLNAMediaInfo.TRANS_SIZE will be returned instead.
                    long totalsize = dlna.length(mediaRenderer);
                    if (chunked && totalsize == DLNAMediaInfo.TRANS_SIZE) {
                        // In chunked mode we try to avoid arbitrary values.
                        totalsize = -1;
                    }
                    long remaining = totalsize - lowRange;
                    long requested = highRange - lowRange;
                    if (requested != 0) {
                        // Determine the range (i.e. smaller of known or requested bytes)
                        long bytes = remaining > -1 ? remaining : inputStream.available();
                        if (requested > 0 && bytes > requested) {
                            bytes = requested + 1;
                        }
                        // Calculate the corresponding highRange (this is usually redundant).
                        highRange = lowRange + bytes - (bytes > 0 ? 1 : 0);
                        LOGGER.trace((chunked ? "Using chunked response. " : "") + "Sending " + bytes + " bytes.");
                        appendToHeader(responseHeader, "Content-Range: bytes " + lowRange + "-" + (highRange > -1 ? highRange : "*") + "/" + (totalsize > -1 ? totalsize : "*"));
                        // mode if the request is open-ended and totalsize is unknown we omit it.
                        if (chunked && requested < 0 && totalsize < 0) {
                            CLoverride = -1;
                        } else {
                            CLoverride = bytes;
                        }
                    } else {
                        // Content-Length refers to the total remaining size of the stream here.
                        CLoverride = remaining;
                    }
                    if (contentFeatures != null) {
                        appendToHeader(responseHeader, "ContentFeatures.DLNA.ORG: " + dlna.getDlnaContentFeatures(mediaRenderer));
                    }
                    if (dlna.getPlayer() == null || xbox360) {
                        appendToHeader(responseHeader, "Accept-Ranges: bytes");
                    }
                    appendToHeader(responseHeader, "Connection: keep-alive");
                }
                if (origRendering != null) {
                    // Restore original rendering details
                    dlna.updateRendering(origRendering);
                }
            }
        }
    } else if ((method.equals("GET") || method.equals("HEAD")) && (argument.toLowerCase().endsWith(".png") || argument.toLowerCase().endsWith(".jpg") || argument.toLowerCase().endsWith(".jpeg"))) {
        if (argument.toLowerCase().endsWith(".png")) {
            appendToHeader(responseHeader, "Content-Type: image/png");
        } else {
            appendToHeader(responseHeader, "Content-Type: image/jpeg");
        }
        appendToHeader(responseHeader, "Accept-Ranges: bytes");
        appendToHeader(responseHeader, "Connection: keep-alive");
        appendToHeader(responseHeader, "Expires: " + getFUTUREDATE() + " GMT");
        inputStream = getResourceInputStream(argument);
    } else if ((method.equals("GET") || method.equals("HEAD")) && (argument.equals("description/fetch") || argument.endsWith("1.0.xml"))) {
        appendToHeader(responseHeader, CONTENT_TYPE);
        appendToHeader(responseHeader, "Cache-Control: no-cache");
        appendToHeader(responseHeader, "Expires: 0");
        appendToHeader(responseHeader, "Accept-Ranges: bytes");
        appendToHeader(responseHeader, "Connection: keep-alive");
        inputStream = getResourceInputStream((argument.equals("description/fetch") ? "PMS.xml" : argument));
        if (argument.equals("description/fetch")) {
            byte[] b = new byte[inputStream.available()];
            inputStream.read(b);
            String s = new String(b, StandardCharsets.UTF_8);
            // .substring(0, PMS.get().usn().length()-2));
            s = s.replace("[uuid]", PMS.get().usn());
            if (PMS.get().getServer().getHost() != null) {
                s = s.replace("[host]", PMS.get().getServer().getHost());
                s = s.replace("[port]", "" + PMS.get().getServer().getPort());
            }
            if (xbox360) {
                LOGGER.debug("DLNA changes for Xbox 360");
                s = s.replace("Universal Media Server", configuration.getServerDisplayName() + " : Windows Media Connect");
                s = s.replace("<modelName>UMS</modelName>", "<modelName>Windows Media Connect</modelName>");
                s = s.replace("<serviceList>", "<serviceList>" + CRLF + "<service>" + CRLF + "<serviceType>urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1</serviceType>" + CRLF + "<serviceId>urn:microsoft.com:serviceId:X_MS_MediaReceiverRegistrar</serviceId>" + CRLF + "<SCPDURL>/upnp/mrr/scpd</SCPDURL>" + CRLF + "<controlURL>/upnp/mrr/control</controlURL>" + CRLF + "</service>" + CRLF);
            } else {
                s = s.replace("Universal Media Server", configuration.getServerDisplayName());
            }
            inputStream = new ByteArrayInputStream(s.getBytes(StandardCharsets.UTF_8));
        }
    } else if (method.equals("POST") && (argument.contains("MS_MediaReceiverRegistrar_control") || argument.contains("mrr/control"))) {
        appendToHeader(responseHeader, CONTENT_TYPE_UTF8);
        response.append(HTTPXMLHelper.XML_HEADER);
        response.append(CRLF);
        response.append(HTTPXMLHelper.SOAP_ENCODING_HEADER);
        response.append(CRLF);
        if (soapaction != null && soapaction.contains("IsAuthorized")) {
            response.append(HTTPXMLHelper.XBOX_360_2);
            response.append(CRLF);
        } else if (soapaction != null && soapaction.contains("IsValidated")) {
            response.append(HTTPXMLHelper.XBOX_360_1);
            response.append(CRLF);
        }
        response.append(HTTPXMLHelper.SOAP_ENCODING_FOOTER);
        response.append(CRLF);
    } else if (method.equals("POST") && argument.endsWith("upnp/control/connection_manager")) {
        appendToHeader(responseHeader, CONTENT_TYPE_UTF8);
        if (soapaction != null && soapaction.contains("ConnectionManager:1#GetProtocolInfo")) {
            response.append(HTTPXMLHelper.XML_HEADER);
            response.append(CRLF);
            response.append(HTTPXMLHelper.SOAP_ENCODING_HEADER);
            response.append(CRLF);
            response.append(HTTPXMLHelper.PROTOCOLINFO_RESPONSE);
            response.append(CRLF);
            response.append(HTTPXMLHelper.SOAP_ENCODING_FOOTER);
            response.append(CRLF);
        }
    } else if (method.equals("POST") && argument.endsWith("upnp/control/content_directory")) {
        appendToHeader(responseHeader, CONTENT_TYPE_UTF8);
        if (soapaction != null && soapaction.contains("ContentDirectory:1#GetSystemUpdateID")) {
            response.append(HTTPXMLHelper.XML_HEADER);
            response.append(CRLF);
            response.append(HTTPXMLHelper.SOAP_ENCODING_HEADER);
            response.append(CRLF);
            response.append(HTTPXMLHelper.GETSYSTEMUPDATEID_HEADER);
            response.append(CRLF);
            response.append("<Id>").append(DLNAResource.getSystemUpdateId()).append("</Id>");
            response.append(CRLF);
            response.append(HTTPXMLHelper.GETSYSTEMUPDATEID_FOOTER);
            response.append(CRLF);
            response.append(HTTPXMLHelper.SOAP_ENCODING_FOOTER);
            response.append(CRLF);
        } else if (soapaction != null && soapaction.contains("ContentDirectory:1#GetSortCapabilities")) {
            response.append(HTTPXMLHelper.XML_HEADER);
            response.append(CRLF);
            response.append(HTTPXMLHelper.SOAP_ENCODING_HEADER);
            response.append(CRLF);
            response.append(HTTPXMLHelper.SORTCAPS_RESPONSE);
            response.append(CRLF);
            response.append(HTTPXMLHelper.SOAP_ENCODING_FOOTER);
            response.append(CRLF);
        } else if (soapaction != null && soapaction.contains("ContentDirectory:1#X_GetFeatureList")) {
            // Added for Samsung 2012 TVs
            response.append(HTTPXMLHelper.XML_HEADER);
            response.append(CRLF);
            response.append(HTTPXMLHelper.SOAP_ENCODING_HEADER);
            response.append(CRLF);
            response.append(HTTPXMLHelper.SAMSUNG_ERROR_RESPONSE);
            response.append(CRLF);
            response.append(HTTPXMLHelper.SOAP_ENCODING_FOOTER);
            response.append(CRLF);
        } else if (soapaction != null && soapaction.contains("ContentDirectory:1#GetSearchCapabilities")) {
            response.append(HTTPXMLHelper.XML_HEADER);
            response.append(CRLF);
            response.append(HTTPXMLHelper.SOAP_ENCODING_HEADER);
            response.append(CRLF);
            response.append(HTTPXMLHelper.SEARCHCAPS_RESPONSE);
            response.append(CRLF);
            response.append(HTTPXMLHelper.SOAP_ENCODING_FOOTER);
            response.append(CRLF);
        } else if (soapaction != null && (soapaction.contains("ContentDirectory:1#Browse") || soapaction.contains("ContentDirectory:1#Search"))) {
            objectID = getEnclosingValue(content, "<ObjectID", "</ObjectID>");
            String containerID = null;
            if ((objectID == null || objectID.length() == 0)) {
                containerID = getEnclosingValue(content, "<ContainerID", "</ContainerID>");
                if (containerID == null || (xbox360 && !containerID.contains("$"))) {
                    objectID = "0";
                } else {
                    objectID = containerID;
                    containerID = null;
                }
            }
            Object sI = getEnclosingValue(content, "<StartingIndex", "</StartingIndex>");
            Object rC = getEnclosingValue(content, "<RequestedCount", "</RequestedCount>");
            browseFlag = getEnclosingValue(content, "<BrowseFlag", "</BrowseFlag>");
            if (sI != null) {
                startingIndex = Integer.parseInt(sI.toString());
            }
            if (rC != null) {
                requestCount = Integer.parseInt(rC.toString());
            }
            response.append(HTTPXMLHelper.XML_HEADER);
            response.append(CRLF);
            response.append(HTTPXMLHelper.SOAP_ENCODING_HEADER);
            response.append(CRLF);
            if (soapaction.contains("ContentDirectory:1#Search")) {
                response.append(HTTPXMLHelper.SEARCHRESPONSE_HEADER);
            } else {
                response.append(HTTPXMLHelper.BROWSERESPONSE_HEADER);
            }
            response.append(CRLF);
            response.append(HTTPXMLHelper.RESULT_HEADER);
            response.append(HTTPXMLHelper.DIDL_HEADER);
            boolean browseDirectChildren = browseFlag != null && browseFlag.equals("BrowseDirectChildren");
            if (soapaction.contains("ContentDirectory:1#Search")) {
                browseDirectChildren = true;
            }
            // Xbox 360 virtual containers ... d'oh!
            String searchCriteria = null;
            if (xbox360 && configuration.getUseCache() && PMS.get().getLibrary() != null && containerID != null) {
                if (containerID.equals("7") && PMS.get().getLibrary().getAlbumFolder() != null) {
                    objectID = PMS.get().getLibrary().getAlbumFolder().getResourceId();
                } else if (containerID.equals("6") && PMS.get().getLibrary().getArtistFolder() != null) {
                    objectID = PMS.get().getLibrary().getArtistFolder().getResourceId();
                } else if (containerID.equals("5") && PMS.get().getLibrary().getGenreFolder() != null) {
                    objectID = PMS.get().getLibrary().getGenreFolder().getResourceId();
                } else if (containerID.equals("F") && PMS.get().getLibrary().getPlaylistFolder() != null) {
                    objectID = PMS.get().getLibrary().getPlaylistFolder().getResourceId();
                } else if (containerID.equals("4") && PMS.get().getLibrary().getAllFolder() != null) {
                    objectID = PMS.get().getLibrary().getAllFolder().getResourceId();
                } else if (containerID.equals("1")) {
                    String artist = getEnclosingValue(content, "upnp:artist = &quot;", "&quot;)");
                    if (artist != null) {
                        objectID = PMS.get().getLibrary().getArtistFolder().getResourceId();
                        searchCriteria = artist;
                    }
                }
            } else if (soapaction.contains("ContentDirectory:1#Search")) {
                searchCriteria = getEnclosingValue(content, "<SearchCriteria", "</SearchCriteria>");
            }
            List<DLNAResource> files = PMS.get().getRootFolder(mediaRenderer).getDLNAResources(objectID, browseDirectChildren, startingIndex, requestCount, mediaRenderer, searchCriteria);
            if (searchCriteria != null && files != null) {
                UMSUtils.postSearch(files, searchCriteria);
                if (xbox360) {
                    if (files.size() > 0) {
                        files = files.get(0).getChildren();
                    }
                }
            }
            int minus = 0;
            if (files != null) {
                for (DLNAResource uf : files) {
                    if (xbox360 && containerID != null) {
                        uf.setFakeParentId(containerID);
                    }
                    if (uf.isCompatible(mediaRenderer) && (uf.getPlayer() == null || uf.getPlayer().isPlayerCompatible(mediaRenderer)) || // all possible combination not only those supported by renderer because the renderer setting could be wrong.
                    files.get(0).getParent() instanceof FileTranscodeVirtualFolder) {
                        response.append(uf.getDidlString(mediaRenderer));
                    } else {
                        minus++;
                    }
                }
            }
            response.append(HTTPXMLHelper.DIDL_FOOTER);
            response.append(HTTPXMLHelper.RESULT_FOOTER);
            response.append(CRLF);
            int filessize = 0;
            if (files != null) {
                filessize = files.size();
            }
            response.append("<NumberReturned>").append(filessize - minus).append("</NumberReturned>");
            response.append(CRLF);
            DLNAResource parentFolder = null;
            if (files != null && filessize > 0) {
                parentFolder = files.get(0).getParent();
            } else {
                parentFolder = PMS.get().getRootFolder(mediaRenderer).getDLNAResource(objectID, mediaRenderer);
            }
            if (browseDirectChildren && mediaRenderer.isUseMediaInfo() && mediaRenderer.isDLNATreeHack()) {
                // with the new parser, files are parsed and analyzed *before*
                // creating the DLNA tree, every 10 items (the ps3 asks 10 by 10),
                // so we do not know exactly the total number of items in the DLNA folder to send
                // (regular files, plus the #transcode folder, maybe the #imdb one, also files can be
                // invalidated and hidden if format is broken or encrypted, etc.).
                // let's send a fake total size to force the renderer to ask following items
                // returns 11 when 10 asked
                int totalCount = startingIndex + requestCount + 1;
                // If no more elements, send the startingIndex
                if (filessize - minus <= 0) {
                    totalCount = startingIndex;
                }
                response.append("<TotalMatches>").append(totalCount).append("</TotalMatches>");
            } else if (browseDirectChildren) {
                response.append("<TotalMatches>").append(((parentFolder != null) ? parentFolder.childrenNumber() : filessize) - minus).append("</TotalMatches>");
            } else {
                // From upnp spec: If BrowseMetadata is specified in the BrowseFlags then TotalMatches = 1
                response.append("<TotalMatches>1</TotalMatches>");
            }
            response.append(CRLF);
            response.append("<UpdateID>");
            if (parentFolder != null) {
                response.append(parentFolder.getUpdateId());
            } else {
                response.append('1');
            }
            response.append("</UpdateID>");
            response.append(CRLF);
            if (soapaction.contains("ContentDirectory:1#Search")) {
                response.append(HTTPXMLHelper.SEARCHRESPONSE_FOOTER);
            } else {
                response.append(HTTPXMLHelper.BROWSERESPONSE_FOOTER);
            }
            response.append(CRLF);
            response.append(HTTPXMLHelper.SOAP_ENCODING_FOOTER);
            response.append(CRLF);
        }
    } else if (method.equals("SUBSCRIBE")) {
        if (soapaction == null) {
            // Ignore this
            return;
        }
        appendToHeader(responseHeader, CONTENT_TYPE_UTF8);
        appendToHeader(responseHeader, "Content-Length: 0");
        appendToHeader(responseHeader, "Connection: close");
        appendToHeader(responseHeader, "SID: " + PMS.get().usn());
        appendToHeader(responseHeader, "Server: " + PMS.get().getServerName());
        appendToHeader(responseHeader, "Timeout: Second-1800");
        appendToHeader(responseHeader, "");
        sendHeader(responseHeader);
        responseHeader.clear();
        if (soapaction != null) {
            String cb = soapaction.replace("<", "").replace(">", "");
            try {
                URL soapActionUrl = new URL(cb);
                String addr = soapActionUrl.getHost();
                int port = soapActionUrl.getPort();
                try (Socket sock = new Socket(addr, port)) {
                    OutputStream out = sock.getOutputStream();
                    sendLine(out, "NOTIFY /" + argument + " HTTP/1.1");
                    sendLine(out, "SID: " + PMS.get().usn());
                    sendLine(out, "SEQ: " + 0);
                    sendLine(out, "NT: upnp:event");
                    sendLine(out, "NTS: upnp:propchange");
                    sendLine(out, "HOST: " + addr + ":" + port);
                    sendLine(out, CONTENT_TYPE_UTF8);
                }
            } catch (MalformedURLException ex) {
                LOGGER.debug("Cannot parse address and port from soap action \"" + soapaction + "\"", ex);
            }
        } else {
            LOGGER.debug("Expected soap action in request");
        }
        if (argument.contains("connection_manager")) {
            response.append(HTTPXMLHelper.eventHeader("urn:schemas-upnp-org:service:ConnectionManager:1"));
            response.append(HTTPXMLHelper.eventProp("SinkProtocolInfo"));
            response.append(HTTPXMLHelper.eventProp("SourceProtocolInfo"));
            response.append(HTTPXMLHelper.eventProp("CurrentConnectionIDs"));
            response.append(HTTPXMLHelper.EVENT_FOOTER);
        } else if (argument.contains("content_directory")) {
            response.append(HTTPXMLHelper.eventHeader("urn:schemas-upnp-org:service:ContentDirectory:1"));
            response.append(HTTPXMLHelper.eventProp("TransferIDs"));
            response.append(HTTPXMLHelper.eventProp("ContainerUpdateIDs"));
            response.append(HTTPXMLHelper.eventProp("SystemUpdateID", "" + DLNAResource.getSystemUpdateId()));
            response.append(HTTPXMLHelper.EVENT_FOOTER);
        }
    }
    appendToHeader(responseHeader, "Server: " + PMS.get().getServerName());
    if (response.length() > 0) {
        // A response message was constructed; convert it to data ready to be sent.
        byte[] responseData = response.toString().getBytes("UTF-8");
        appendToHeader(responseHeader, "Content-Length: " + responseData.length);
        appendToHeader(responseHeader, "");
        sendHeader(responseHeader);
        // HEAD requests only require headers to be set, no need to set contents.
        if (!method.equals("HEAD")) {
            output.write(responseData);
        // LOGGER.trace(response.toString());
        }
    } else if (inputStream != null) {
        if (CLoverride > -2) {
            // Content-Length override has been set, send or omit as appropriate
            if (CLoverride > -1 && CLoverride != DLNAMediaInfo.TRANS_SIZE) {
                // Since PS3 firmware 2.50, it is wiser not to send an arbitrary Content-Length,
                // as the PS3 will display a network error and request the last seconds of the
                // transcoded video. Better to send no Content-Length at all.
                appendToHeader(responseHeader, "Content-Length: " + CLoverride);
            }
        } else {
            int cl = inputStream.available();
            LOGGER.trace("Available Content-Length: " + cl);
            appendToHeader(responseHeader, "Content-Length: " + cl);
        }
        if (timeseek > 0 && dlna != null) {
            // Add timeseek information headers.
            String timeseekValue = StringUtil.formatDLNADuration(timeseek);
            String timetotalValue = dlna.getMedia().getDurationString();
            appendToHeader(responseHeader, "TimeSeekRange.dlna.org: npt=" + timeseekValue + "-" + timetotalValue + "/" + timetotalValue);
            appendToHeader(responseHeader, "X-Seek-Range: npt=" + timeseekValue + "-" + timetotalValue + "/" + timetotalValue);
        }
        // Send the response headers to the client.
        appendToHeader(responseHeader, "");
        sendHeader(responseHeader);
        long sendB = 0;
        if (lowRange != DLNAMediaInfo.ENDFILE_POS && !method.equals("HEAD")) {
            // , ((lowRange > 0 && highRange > 0)?(highRange-lowRange):-1)
            sendB = sendBytes(inputStream);
        }
        if (sendB > -1) {
            LOGGER.trace("Sending stream: {} bytes of {}", sendB, argument);
        } else {
            // Premature end
            startStopListenerDelegate.stop();
        }
    } else {
        // inputStream is null
        appendToHeader(responseHeader, "Content-Length: 0");
        appendToHeader(responseHeader, "");
        responseHeader.set(0, http10 ? HTTP_204_NO_CONTENT_10 : HTTP_204_NO_CONTENT);
        sendHeader(responseHeader);
    }
    if (LOGGER.isTraceEnabled()) {
        // Log trace information
        StringBuilder header = new StringBuilder();
        for (int i = 0; i < responseHeader.size(); i++) {
            if (isNotBlank(responseHeader.get(i))) {
                header.append("  ").append(responseHeader.get(i)).append("\n");
            }
        }
        String rendererName;
        if (mediaRenderer != null) {
            if (isNotBlank(mediaRenderer.getRendererName())) {
                if (isBlank(mediaRenderer.getConfName()) || mediaRenderer.getRendererName().equals(mediaRenderer.getConfName())) {
                    rendererName = mediaRenderer.getRendererName();
                } else {
                    rendererName = mediaRenderer.getRendererName() + " [" + mediaRenderer.getConfName() + "]";
                }
            } else if (isNotBlank(mediaRenderer.getConfName())) {
                rendererName = mediaRenderer.getConfName();
            } else {
                rendererName = "Unnamed";
            }
        } else {
            rendererName = "Unknown";
        }
        if (method.equals("HEAD")) {
            LOGGER.trace("HEAD only response sent to {}:\n\nHEADER:\n{}", rendererName, header);
        } else {
            String formattedResponse = null;
            if (isNotBlank(response)) {
                try {
                    formattedResponse = StringUtil.prettifyXML(response.toString(), 4);
                } catch (SAXException | ParserConfigurationException | XPathExpressionException | TransformerException e) {
                    formattedResponse = "  Content isn't valid XML, using text formatting: " + e.getMessage() + "\n";
                    formattedResponse += "    " + response.toString().replaceAll("\n", "\n    ");
                }
            }
            if (isNotBlank(formattedResponse)) {
                LOGGER.trace("Response sent to {}:\n\nHEADER:\n{}\nCONTENT:\n{}", rendererName, header, formattedResponse);
                Matcher matcher = DIDL_PATTERN.matcher(response);
                if (matcher.find()) {
                    try {
                        LOGGER.trace("The unescaped <Result> sent to {} is:\n{}", mediaRenderer.getConfName(), StringUtil.prettifyXML(StringEscapeUtils.unescapeXml(matcher.group(1)), 2));
                    } catch (SAXException | ParserConfigurationException | XPathExpressionException | TransformerException e) {
                        LOGGER.warn("Failed to prettify DIDL-Lite document: {}", e.getMessage());
                        LOGGER.trace("", e);
                    }
                }
            } else if (inputStream != null && !responseHeader.contains("Content-Length: 0")) {
                LOGGER.trace("Transfer response sent to {}:\n\nHEADER:\n{}", rendererName, header);
            } else {
                LOGGER.trace("Empty response sent to {}:\n\nHEADER:\n{}", rendererName, header);
            }
        }
    }
}
Also used : ImagePlayer(net.pms.encoders.ImagePlayer) MalformedURLException(java.net.MalformedURLException) Matcher(java.util.regex.Matcher) XPathExpressionException(javax.xml.xpath.XPathExpressionException) ArrayList(java.util.ArrayList) URL(java.net.URL) SAXException(org.xml.sax.SAXException) DLNAImageProfile(net.pms.dlna.DLNAImageProfile) ProcessWrapper(net.pms.io.ProcessWrapper) RealFile(net.pms.dlna.RealFile) ParserConfigurationException(javax.xml.parsers.ParserConfigurationException) OutputParams(net.pms.io.OutputParams) TransformerException(javax.xml.transform.TransformerException) DLNAMediaSubtitle(net.pms.dlna.DLNAMediaSubtitle) DLNAImageInputStream(net.pms.dlna.DLNAImageInputStream) DLNAThumbnailInputStream(net.pms.dlna.DLNAThumbnailInputStream) FileTranscodeVirtualFolder(net.pms.dlna.FileTranscodeVirtualFolder) DLNAResource(net.pms.dlna.DLNAResource) DLNAThumbnailInputStream(net.pms.dlna.DLNAThumbnailInputStream) Socket(java.net.Socket)

Example 3 with DLNAMediaSubtitle

use of net.pms.dlna.DLNAMediaSubtitle in project UniversalMediaServer by UniversalMediaServer.

the class Player method setSubtitleOutputParameters.

/**
 * This method populates the supplied {@link OutputParams} object with the correct subtitles (sid)
 * based on the given filename, its MediaInfo metadata and PMS configuration settings.
 *
 * TODO: Rewrite this crazy method to be more concise and logical.
 *
 * @param fileName
 * The file name used to determine the availability of subtitles.
 * @param media
 * The MediaInfo metadata for the file.
 * @param params
 * The parameters to populate.
 */
public static void setSubtitleOutputParameters(String fileName, DLNAMediaInfo media, OutputParams params) {
    // Use device-specific pms conf
    PmsConfiguration configuration = PMS.getConfiguration(params);
    String currentLang = null;
    DLNAMediaSubtitle matchedSub = null;
    if (params.aid != null) {
        currentLang = params.aid.getLang();
    }
    if (params.sid != null && params.sid.getId() == -1) {
        LOGGER.trace("Don't want subtitles!");
        params.sid = null;
        return;
    }
    /**
     * Check for live subtitles
     */
    if (params.sid != null && !StringUtils.isEmpty(params.sid.getLiveSubURL())) {
        LOGGER.debug("Live subtitles " + params.sid.getLiveSubURL());
        try {
            matchedSub = params.sid;
            String file = OpenSubtitle.fetchSubs(matchedSub.getLiveSubURL(), matchedSub.getLiveSubFile());
            if (!StringUtils.isEmpty(file)) {
                matchedSub.setExternalFile(new File(file), null);
                params.sid = matchedSub;
                return;
            }
        } catch (IOException e) {
        }
    }
    StringTokenizer st = new StringTokenizer(configuration.getAudioSubLanguages(), ";");
    /**
     * Check for external and internal subtitles matching the user's language
     * preferences
     */
    boolean matchedInternalSubtitles = false;
    boolean matchedExternalSubtitles = false;
    while (st.hasMoreTokens()) {
        String pair = st.nextToken();
        if (pair.contains(",")) {
            String audio = pair.substring(0, pair.indexOf(','));
            String sub = pair.substring(pair.indexOf(',') + 1);
            audio = audio.trim();
            sub = sub.trim();
            LOGGER.trace("Searching for a match for: " + currentLang + " with " + audio + " and " + sub);
            if (Iso639.isCodesMatching(audio, currentLang) || (currentLang != null && audio.equals("*"))) {
                if (sub.equals("off")) {
                    /**
                     * Ignore the "off" language for external subtitles if the user setting is enabled
                     * TODO: Prioritize multiple external subtitles properly instead of just taking the first one we load
                     */
                    if (configuration.isForceExternalSubtitles()) {
                        for (DLNAMediaSubtitle present_sub : media.getSubtitleTracksList()) {
                            if (present_sub.getExternalFile() != null) {
                                matchedSub = present_sub;
                                matchedExternalSubtitles = true;
                                LOGGER.trace("Ignoring the \"off\" language because there are external subtitles");
                                break;
                            }
                        }
                    }
                    if (!matchedExternalSubtitles) {
                        matchedSub = new DLNAMediaSubtitle();
                        matchedSub.setLang("off");
                    }
                } else {
                    for (DLNAMediaSubtitle present_sub : media.getSubtitleTracksList()) {
                        if (present_sub.matchCode(sub) || sub.equals("*")) {
                            if (present_sub.getExternalFile() != null) {
                                if (configuration.isAutoloadExternalSubtitles()) {
                                    // Subtitle is external and we want external subtitles, look no further
                                    matchedSub = present_sub;
                                    LOGGER.trace("Matched external subtitles track: " + matchedSub);
                                    break;
                                } else {
                                    // Subtitle is external but we do not want external subtitles, keep searching
                                    LOGGER.trace("External subtitles ignored because of user setting: " + present_sub);
                                }
                            } else if (!matchedInternalSubtitles) {
                                matchedSub = present_sub;
                                LOGGER.trace("Matched internal subtitles track: " + matchedSub);
                                if (configuration.isAutoloadExternalSubtitles()) {
                                    // Subtitle is internal and we will wait to see if an external one is available instead
                                    matchedInternalSubtitles = true;
                                } else {
                                    // Subtitle is internal and we will use it
                                    break;
                                }
                            }
                        }
                    }
                }
                if (matchedSub != null && !matchedInternalSubtitles) {
                    break;
                }
            }
        }
    }
    /**
     * Check for external subtitles that were skipped in the above code block
     * because they didn't match language preferences, if there wasn't already
     * a match and the user settings specify it.
     */
    if (matchedSub == null && configuration.isForceExternalSubtitles()) {
        for (DLNAMediaSubtitle present_sub : media.getSubtitleTracksList()) {
            if (present_sub.getExternalFile() != null) {
                matchedSub = present_sub;
                LOGGER.trace("Matched external subtitles track that did not match language preferences: " + matchedSub);
                break;
            }
        }
    }
    /**
     * Disable chosen subtitles if the user has disabled all subtitles or
     * if the language preferences have specified the "off" language.
     *
     * TODO: Can't we save a bunch of looping by checking for isDisableSubtitles
     * just after the Live Subtitles check above?
     */
    if (matchedSub != null && params.sid == null) {
        if (configuration.isDisableSubtitles() || (matchedSub.getLang() != null && matchedSub.getLang().equals("off"))) {
            LOGGER.trace("Disabled the subtitles: " + matchedSub);
        } else {
            params.sid = matchedSub;
        }
    }
    /**
     * Check for forced subtitles.
     */
    if (!configuration.isDisableSubtitles() && params.sid == null && media != null) {
        // Check for subtitles again
        File video = new File(fileName);
        FileUtil.isSubtitlesExists(video, media, false);
        if (configuration.isAutoloadExternalSubtitles()) {
            boolean forcedSubsFound = false;
            // Priority to external subtitles
            for (DLNAMediaSubtitle sub : media.getSubtitleTracksList()) {
                if (matchedSub != null && matchedSub.getLang() != null && matchedSub.getLang().equals("off")) {
                    st = new StringTokenizer(configuration.getForcedSubtitleTags(), ",");
                    while (sub.getSubtitlesTrackTitleFromMetadata() != null && st.hasMoreTokens()) {
                        String forcedTags = st.nextToken();
                        forcedTags = forcedTags.trim();
                        if (sub.getSubtitlesTrackTitleFromMetadata().toLowerCase().contains(forcedTags) && Iso639.isCodesMatching(sub.getLang(), configuration.getForcedSubtitleLanguage())) {
                            LOGGER.trace("Forcing preferred subtitles: " + sub.getLang() + "/" + sub.getSubtitlesTrackTitleFromMetadata());
                            LOGGER.trace("Forced subtitles track: " + sub);
                            if (sub.getExternalFile() != null) {
                                LOGGER.trace("Found external forced file: " + sub.getExternalFile().getAbsolutePath());
                            }
                            params.sid = sub;
                            forcedSubsFound = true;
                            break;
                        }
                    }
                    if (forcedSubsFound == true) {
                        break;
                    }
                } else {
                    LOGGER.trace("Found subtitles track: " + sub);
                    if (sub.getExternalFile() != null) {
                        LOGGER.trace("Found external file: " + sub.getExternalFile().getAbsolutePath());
                        params.sid = sub;
                        break;
                    }
                }
            }
        }
        if (matchedSub != null && matchedSub.getLang() != null && matchedSub.getLang().equals("off")) {
            return;
        }
        if (params.sid == null) {
            st = new StringTokenizer(UMSUtils.getLangList(params.mediaRenderer), ",");
            while (st.hasMoreTokens()) {
                String lang = st.nextToken();
                lang = lang.trim();
                LOGGER.trace("Looking for a subtitle track with lang: " + lang);
                for (DLNAMediaSubtitle sub : media.getSubtitleTracksList()) {
                    if (sub.matchCode(lang) && !(!configuration.isAutoloadExternalSubtitles() && sub.getExternalFile() != null)) {
                        params.sid = sub;
                        LOGGER.trace("Matched subtitles track: " + params.sid);
                        return;
                    }
                }
            }
        }
    }
}
Also used : DLNAMediaSubtitle(net.pms.dlna.DLNAMediaSubtitle) StringTokenizer(java.util.StringTokenizer) PmsConfiguration(net.pms.configuration.PmsConfiguration) IOException(java.io.IOException) File(java.io.File)

Example 4 with DLNAMediaSubtitle

use of net.pms.dlna.DLNAMediaSubtitle in project UniversalMediaServer by UniversalMediaServer.

the class AviSynthMEncoder method isCompatible.

/**
 * {@inheritDoc}
 */
@Override
public boolean isCompatible(DLNAResource resource) {
    Format format = resource.getFormat();
    if (format != null) {
        if (format.getIdentifier() == Format.Identifier.WEB) {
            return false;
        }
    }
    DLNAMediaSubtitle subtitle = resource.getMediaSubtitle();
    // Uninitialized DLNAMediaSubtitle objects have a null language.
    if (subtitle != null && subtitle.getLang() != null) {
        // This engine only supports external subtitles
        if (subtitle.getExternalFile() != null) {
            return true;
        }
        return false;
    }
    try {
        String audioTrackName = resource.getMediaAudio().toString();
        String defaultAudioTrackName = resource.getMedia().getAudioTracksList().get(0).toString();
        if (!audioTrackName.equals(defaultAudioTrackName)) {
            // This engine only supports playback of the default audio track
            return false;
        }
    } catch (NullPointerException e) {
        LOGGER.trace("AviSynth/MEncoder cannot determine compatibility based on audio track for " + resource.getSystemName());
    } catch (IndexOutOfBoundsException e) {
        LOGGER.trace("AviSynth/MEncoder cannot determine compatibility based on default audio track for " + resource.getSystemName());
    }
    if (PlayerUtil.isVideo(resource, Format.Identifier.MKV) || PlayerUtil.isVideo(resource, Format.Identifier.MPG) || PlayerUtil.isVideo(resource, Format.Identifier.OGG)) {
        return true;
    }
    return false;
}
Also used : DLNAMediaSubtitle(net.pms.dlna.DLNAMediaSubtitle) Format(net.pms.formats.Format)

Example 5 with DLNAMediaSubtitle

use of net.pms.dlna.DLNAMediaSubtitle in project UniversalMediaServer by UniversalMediaServer.

the class FFMpegVideo method getVideoFilterOptions.

/**
 * Returns a list of strings representing the rescale options for this transcode i.e. the ffmpeg -vf
 * options used to show subtitles in either SSA/ASS or picture-based format and resize a video that's too wide and/or high for the specified renderer.
 * If the renderer has no size limits, or there's no media metadata, or the video is within the renderer's
 * size limits, an empty list is returned.
 *
 * @param dlna
 * @param media metadata for the DLNA resource which is being transcoded
 * @param params
 * @return a {@link List} of <code>String</code>s representing the rescale options for this video,
 * or an empty list if the video doesn't need to be resized.
 * @throws java.io.IOException
 */
public List<String> getVideoFilterOptions(DLNAResource dlna, DLNAMediaInfo media, OutputParams params) throws IOException {
    List<String> videoFilterOptions = new ArrayList<>();
    ArrayList<String> filterChain = new ArrayList<>();
    ArrayList<String> scalePadFilterChain = new ArrayList<>();
    final RendererConfiguration renderer = params.mediaRenderer;
    boolean isMediaValid = media != null && media.isMediaparsed() && media.getHeight() != 0;
    boolean isResolutionTooHighForRenderer = isMediaValid && !params.mediaRenderer.isResolutionCompatibleWithRenderer(media.getWidth(), media.getHeight());
    int scaleWidth = 0;
    int scaleHeight = 0;
    if (media.getWidth() > 0 && media.getHeight() > 0) {
        scaleWidth = media.getWidth();
        scaleHeight = media.getHeight();
    }
    boolean is3D = media.is3d() && !media.stereoscopyIsAnaglyph();
    // Make sure the aspect ratio is 16/9 if the renderer needs it.
    boolean keepAR = (renderer.isKeepAspectRatio() || renderer.isKeepAspectRatioTranscoding()) && !media.is3dFullSbsOrOu() && !"16:9".equals(media.getAspectRatioContainer());
    // Scale and pad the video if necessary
    if (isResolutionTooHighForRenderer || (!renderer.isRescaleByRenderer() && renderer.isMaximumResolutionSpecified() && media.getWidth() < 720)) {
        // Do not rescale for SD video and higher
        if (media.is3dFullSbsOrOu()) {
            scalePadFilterChain.add(String.format("scale=%1$d:%2$d", renderer.getMaxVideoWidth(), renderer.getMaxVideoHeight()));
        } else {
            scalePadFilterChain.add(String.format("scale=iw*min(%1$d/iw\\,%2$d/ih):ih*min(%1$d/iw\\,%2$d/ih)", renderer.getMaxVideoWidth(), renderer.getMaxVideoHeight()));
            if (keepAR) {
                scalePadFilterChain.add(String.format("pad=%1$d:%2$d:(%1$d-iw)/2:(%2$d-ih)/2", renderer.getMaxVideoWidth(), renderer.getMaxVideoHeight()));
            }
        }
    } else if (keepAR && isMediaValid) {
        if ((media.getWidth() / (double) media.getHeight()) >= (16 / (double) 9)) {
            scalePadFilterChain.add("pad=iw:iw/(16/9):0:(oh-ih)/2");
            scaleHeight = (int) Math.round(scaleWidth / (16 / (double) 9));
        } else {
            scalePadFilterChain.add("pad=ih*(16/9):ih:(ow-iw)/2:0");
            scaleWidth = (int) Math.round(scaleHeight * (16 / (double) 9));
        }
        scaleWidth = convertToModX(scaleWidth, 4);
        scaleHeight = convertToModX(scaleHeight, 4);
        // Make sure we didn't exceed the renderer's maximum resolution.
        if (scaleHeight > renderer.getMaxVideoHeight() || scaleWidth > renderer.getMaxVideoWidth()) {
            scaleHeight = renderer.getMaxVideoHeight();
            scaleWidth = renderer.getMaxVideoWidth();
        }
        scalePadFilterChain.add("scale=" + scaleWidth + ":" + scaleHeight);
    }
    filterChain.addAll(scalePadFilterChain);
    boolean override = true;
    if (renderer instanceof RendererConfiguration.OutputOverride) {
        RendererConfiguration.OutputOverride or = (RendererConfiguration.OutputOverride) renderer;
        override = or.addSubtitles();
    }
    if (!isDisableSubtitles(params) && override) {
        boolean isSubsManualTiming = true;
        DLNAMediaSubtitle convertedSubs = dlna.getMediaSubtitle();
        StringBuilder subsFilter = new StringBuilder();
        if (params.sid != null && params.sid.getType().isText()) {
            boolean isSubsASS = params.sid.getType() == SubtitleType.ASS;
            String originalSubsFilename = null;
            if (is3D) {
                if (convertedSubs != null && convertedSubs.getConvertedFile() != null) {
                    // subs are already converted to 3D so use them
                    originalSubsFilename = convertedSubs.getConvertedFile().getAbsolutePath();
                } else if (!isSubsASS) {
                    // When subs are not converted and they are not in the ASS format and video is 3D then subs need conversion to 3D
                    originalSubsFilename = SubtitleUtils.getSubtitles(dlna, media, params, configuration, SubtitleType.ASS).getAbsolutePath();
                } else {
                    originalSubsFilename = params.sid.getExternalFile().getAbsolutePath();
                }
            } else if (params.sid.isExternal()) {
                if (params.sid.isStreamable() && renderer.streamSubsForTranscodedVideo()) {
                    // when subs are streamable do not transcode them
                    originalSubsFilename = null;
                } else {
                    originalSubsFilename = params.sid.getExternalFile().getAbsolutePath();
                }
            } else if (params.sid.isEmbedded()) {
                originalSubsFilename = dlna.getFileName();
            }
            if (originalSubsFilename != null) {
                subsFilter.append("subtitles=").append(StringUtil.ffmpegEscape(originalSubsFilename));
                if (params.sid.isEmbedded()) {
                    subsFilter.append(":si=").append(params.sid.getId());
                }
                // Set the input subtitles character encoding if not UTF-8
                if (!params.sid.isSubsUtf8()) {
                    if (isNotBlank(configuration.getSubtitlesCodepage())) {
                        subsFilter.append(":charenc=").append(configuration.getSubtitlesCodepage());
                    } else if (params.sid.getSubCharacterSet() != null) {
                        subsFilter.append(":charenc=").append(params.sid.getSubCharacterSet());
                    }
                }
                // If the FFmpeg font config is enabled than we need to add settings to the filter. TODO there could be also changed the font type. See http://ffmpeg.org/ffmpeg-filters.html#subtitles-1
                if (configuration.isFFmpegFontConfig() && !is3D && !isSubsASS) {
                    // Do not force style for 3D videos and ASS subtitles
                    subsFilter.append(":force_style=");
                    subsFilter.append("'");
                    String fontName = configuration.getFont();
                    if (isNotBlank(fontName)) {
                        String font = CodecUtil.isFontRegisteredInOS(fontName);
                        if (font != null) {
                            subsFilter.append("Fontname=").append(font);
                        }
                    }
                    // XXX (valib) If the font size is not acceptable it could be calculated better taking in to account the original video size. Unfortunately I don't know how to do that.
                    subsFilter.append(",Fontsize=").append((int) 15 * Double.parseDouble(configuration.getAssScale()));
                    subsFilter.append(",PrimaryColour=").append(configuration.getSubsColor().getASSv4StylesHexValue());
                    subsFilter.append(",Outline=").append(configuration.getAssOutline());
                    subsFilter.append(",Shadow=").append(configuration.getAssShadow());
                    subsFilter.append(",MarginV=").append(configuration.getAssMargin());
                    subsFilter.append("'");
                }
            }
        } else if (params.sid.getType().isPicture()) {
            if (params.sid.getId() < 100) {
                // Embedded
                subsFilter.append("[0:v][0:s:").append(media.getSubtitleTracksList().indexOf(params.sid)).append("]overlay");
                isSubsManualTiming = false;
            } else {
                // External
                videoFilterOptions.add("-i");
                videoFilterOptions.add(params.sid.getExternalFile().getAbsolutePath());
                // this assumes the sub file is single-language
                subsFilter.append("[0:v][1:s]overlay");
            }
        }
        if (isNotBlank(subsFilter)) {
            if (params.timeseek > 0 && isSubsManualTiming) {
                // based on https://trac.ffmpeg.org/ticket/2067
                filterChain.add("setpts=PTS+" + params.timeseek + "/TB");
            }
            filterChain.add(subsFilter.toString());
            if (params.timeseek > 0 && isSubsManualTiming) {
                // based on https://trac.ffmpeg.org/ticket/2067
                filterChain.add("setpts=PTS-STARTPTS");
            }
        }
    }
    String overrideVF = renderer.getFFmpegVideoFilterOverride();
    if (StringUtils.isNotEmpty(overrideVF)) {
        filterChain.add(overrideVF);
    }
    // Convert 3D video to the other output 3D format or to 2D using "Output3DFormat = ml" or "Output3DFormat = mr" in the renderer conf
    String stereoLayout = null;
    String renderer3DOutputFormat = null;
    if (media.get3DLayout() != null) {
        stereoLayout = media.get3DLayout().toString().toLowerCase(Locale.ROOT);
        renderer3DOutputFormat = params.mediaRenderer.getOutput3DFormat();
    }
    if (is3D && stereoLayout != null && isNotBlank(renderer3DOutputFormat) && !stereoLayout.equals(renderer3DOutputFormat)) {
        filterChain.add("stereo3d=" + stereoLayout + ":" + renderer3DOutputFormat);
    }
    if (filterChain.size() > 0) {
        videoFilterOptions.add("-filter_complex");
        videoFilterOptions.add(StringUtils.join(filterChain, ","));
    }
    return videoFilterOptions;
}
Also used : DLNAMediaSubtitle(net.pms.dlna.DLNAMediaSubtitle) ArrayList(java.util.ArrayList) RendererConfiguration(net.pms.configuration.RendererConfiguration)

Aggregations

DLNAMediaSubtitle (net.pms.dlna.DLNAMediaSubtitle)10 File (java.io.File)4 Test (org.junit.Test)4 ArrayList (java.util.ArrayList)2 Format (net.pms.formats.Format)2 IOException (java.io.IOException)1 MalformedURLException (java.net.MalformedURLException)1 Socket (java.net.Socket)1 URL (java.net.URL)1 StringTokenizer (java.util.StringTokenizer)1 Matcher (java.util.regex.Matcher)1 ParserConfigurationException (javax.xml.parsers.ParserConfigurationException)1 TransformerException (javax.xml.transform.TransformerException)1 XPathExpressionException (javax.xml.xpath.XPathExpressionException)1 PmsConfiguration (net.pms.configuration.PmsConfiguration)1 RendererConfiguration (net.pms.configuration.RendererConfiguration)1 DLNAImageInputStream (net.pms.dlna.DLNAImageInputStream)1 DLNAImageProfile (net.pms.dlna.DLNAImageProfile)1 DLNAResource (net.pms.dlna.DLNAResource)1 DLNAThumbnailInputStream (net.pms.dlna.DLNAThumbnailInputStream)1