Search in sources :

Example 6 with OutputParams

use of net.pms.io.OutputParams in project UniversalMediaServer by UniversalMediaServer.

the class DCRaw method parse.

/**
 * Parses {@code file} and stores the result in {@code media}.
 *
 * @param media the {@link DLNAMediaInfo} instance to store the parse
 *            results in.
 * @param file the {@link File} to parse.
 */
@Override
public void parse(DLNAMediaInfo media, File file) {
    if (media == null) {
        throw new NullPointerException("media cannot be null");
    }
    if (file == null) {
        throw new NullPointerException("file cannot be null");
    }
    OutputParams params = new OutputParams(configuration);
    params.log = true;
    String[] cmdArray = new String[4];
    cmdArray[0] = configuration.getDCRawPath();
    cmdArray[1] = "-i";
    cmdArray[2] = "-v";
    cmdArray[3] = file.getAbsolutePath();
    ProcessWrapperImpl pw = new ProcessWrapperImpl(cmdArray, params, true, false);
    pw.runInSameThread();
    List<String> list = pw.getOtherResults();
    Pattern pattern = Pattern.compile("^Output size:\\s*(\\d+)\\s*x\\s*(\\d+)");
    Matcher matcher;
    for (String s : list) {
        matcher = pattern.matcher(s);
        if (matcher.find()) {
            media.setWidth(Integer.parseInt(matcher.group(1)));
            media.setHeight(Integer.parseInt(matcher.group(2)));
            if (LOGGER.isTraceEnabled()) {
                LOGGER.trace("Parsed resolution {} x {} for image \"{}\" from DCRaw output", Integer.parseInt(matcher.group(1)), Integer.parseInt(matcher.group(2)), file.getPath());
            }
            break;
        }
    }
}
Also used : Pattern(java.util.regex.Pattern) Matcher(java.util.regex.Matcher) OutputParams(net.pms.io.OutputParams) ProcessWrapperImpl(net.pms.io.ProcessWrapperImpl)

Example 7 with OutputParams

use of net.pms.io.OutputParams 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 8 with OutputParams

use of net.pms.io.OutputParams in project UniversalMediaServer by UniversalMediaServer.

the class DLNAResource method resolvePlayer.

/**
 * Determine whether we are a candidate for streaming or transcoding to the
 * given renderer, and return the relevant player or null as appropriate.
 *
 * @param renderer The target renderer
 * @return A player if transcoding or null if streaming
 */
public Player resolvePlayer(RendererConfiguration renderer) {
    // Use device-specific pms conf, if any
    PmsConfiguration configurationSpecificToRenderer = PMS.getConfiguration(renderer);
    boolean parserV2 = media != null && renderer != null && renderer.isUseMediaInfo();
    Player resolvedPlayer = null;
    if (media == null) {
        media = new DLNAMediaInfo();
    }
    if (format == null) {
        // Shouldn't happen, this is just a desperate measure
        Format f = FormatFactory.getAssociatedFormat(getSystemName());
        setFormat(f != null ? f : FormatFactory.getAssociatedFormat(".mpg"));
    }
    // Check if we're a transcode folder item
    if (isNoName() && (getParent() instanceof FileTranscodeVirtualFolder)) {
        // Yes, leave everything as-is
        resolvedPlayer = getPlayer();
        LOGGER.trace("Selecting player {} based on transcode item settings", resolvedPlayer);
        return resolvedPlayer;
    }
    boolean hasSubsToTranscode = false;
    boolean hasEmbeddedSubs = false;
    for (DLNAMediaSubtitle s : media.getSubtitleTracksList()) {
        hasEmbeddedSubs = (hasEmbeddedSubs || s.isEmbedded());
    }
    /**
     * At this stage, we know the media is compatible with the renderer based on its
     * "Supported" lines, and can therefore be streamed to the renderer without a
     * player. However, other details about the media can change this, such as
     * whether it has subtitles that match this user's language settings, so here we
     * perform those checks.
     */
    if (format.isVideo() && !configurationSpecificToRenderer.isDisableSubtitles()) {
        if (hasEmbeddedSubs || hasExternalSubtitles()) {
            OutputParams params = new OutputParams(configurationSpecificToRenderer);
            // set proper subtitles in accordance with user setting
            Player.setAudioAndSubs(getSystemName(), media, params);
            if (params.sid != null) {
                if (params.sid.isExternal()) {
                    if (renderer != null && renderer.isExternalSubtitlesFormatSupported(params.sid, media)) {
                        media_subtitle = params.sid;
                        media_subtitle.setSubsStreamable(true);
                        LOGGER.trace("This video has external subtitles that could be streamed");
                    } else {
                        hasSubsToTranscode = true;
                        LOGGER.trace("This video has external subtitles that should be transcoded");
                    }
                } else if (params.sid.isEmbedded()) {
                    if (renderer != null && renderer.isEmbeddedSubtitlesFormatSupported(params.sid)) {
                        LOGGER.trace("This video has embedded subtitles that could be streamed");
                    } else {
                        hasSubsToTranscode = true;
                        LOGGER.trace("This video has embedded subtitles that should be transcoded");
                    }
                }
            }
        } else {
            LOGGER.trace("This video does not have subtitles");
        }
    }
    if (configurationSpecificToRenderer.isDisableTranscoding()) {
        LOGGER.trace("Final verdict: \"{}\" will be streamed since transcoding is disabled", getName());
        return null;
    }
    String configurationSkipExtensions = configurationSpecificToRenderer.getDisableTranscodeForExtensions();
    String rendererSkipExtensions = renderer == null ? null : renderer.getStreamedExtensions();
    // Should transcoding be skipped for this format?
    skipTranscode = format.skip(configurationSkipExtensions, rendererSkipExtensions);
    if (skipTranscode) {
        LOGGER.trace("Final verdict: \"{}\" will be streamed since it is forced by configuration", getName());
        return null;
    }
    // Try to match a player based on media information and format.
    resolvedPlayer = PlayerFactory.getPlayer(this);
    if (resolvedPlayer != null) {
        String configurationForceExtensions = configurationSpecificToRenderer.getForceTranscodeForExtensions();
        String rendererForceExtensions = null;
        if (renderer != null) {
            rendererForceExtensions = renderer.getTranscodedExtensions();
        }
        // Should transcoding be forced for this format?
        boolean forceTranscode = format.skip(configurationForceExtensions, rendererForceExtensions);
        boolean isIncompatible = false;
        String prependTraceReason = "File \"{}\" will not be streamed because ";
        if (forceTranscode) {
            LOGGER.trace(prependTraceReason + "transcoding is forced by configuration", getName());
        } else if (this instanceof DVDISOTitle) {
            forceTranscode = true;
            LOGGER.trace("DVD video track \"{}\" will be transcoded because streaming isn't supported", getName());
        } else if (!format.isCompatible(media, renderer)) {
            isIncompatible = true;
            LOGGER.trace(prependTraceReason + "it is not supported by the renderer", getName());
        } else if (configurationSpecificToRenderer.isEncodedAudioPassthrough()) {
            if (getMediaAudio() != null && (FormatConfiguration.AC3.equals(getMediaAudio().getAudioCodec()) || FormatConfiguration.DTS.equals(getMediaAudio().getAudioCodec()))) {
                isIncompatible = true;
                LOGGER.trace(prependTraceReason + "the audio will use the encoded audio passthrough feature", getName());
            } else {
                for (DLNAMediaAudio audioTrack : media.getAudioTracksList()) {
                    if (audioTrack != null && (FormatConfiguration.AC3.equals(audioTrack.getAudioCodec()) || FormatConfiguration.DTS.equals(audioTrack.getAudioCodec()))) {
                        isIncompatible = true;
                        LOGGER.trace(prependTraceReason + "the audio will use the encoded audio passthrough feature", getName());
                        break;
                    }
                }
            }
        }
        if (!isIncompatible && format.isVideo() && parserV2 && renderer != null) {
            int maxBandwidth = renderer.getMaxBandwidth();
            if (renderer.isKeepAspectRatio() && !"16:9".equals(media.getAspectRatioContainer())) {
                isIncompatible = true;
                LOGGER.trace(prependTraceReason + "the renderer needs us to add borders to change the aspect ratio from {} to 16/9.", getName(), media.getAspectRatioContainer());
            } else if (!renderer.isResolutionCompatibleWithRenderer(media.getWidth(), media.getHeight())) {
                isIncompatible = true;
                LOGGER.trace(prependTraceReason + "the resolution is incompatible with the renderer.", getName());
            } else if (media.getBitrate() > maxBandwidth) {
                isIncompatible = true;
                LOGGER.trace(prependTraceReason + "the bitrate ({} b/s) is too high ({} b/s).", getName(), media.getBitrate(), maxBandwidth);
            } else if (!renderer.isVideoBitDepthSupported(media.getVideoBitDepth())) {
                isIncompatible = true;
                LOGGER.trace(prependTraceReason + "the video bit depth ({}) is not supported.", getName(), media.getVideoBitDepth());
            } else if (renderer.isH264Level41Limited() && media.isH264()) {
                if (media.getAvcLevel() != null) {
                    double h264Level = 4.1;
                    try {
                        h264Level = Double.parseDouble(media.getAvcLevel());
                    } catch (NumberFormatException e) {
                        LOGGER.trace("Could not convert {} to double: {}", media.getAvcLevel(), e.getMessage());
                    }
                    if (h264Level > 4.1) {
                        isIncompatible = true;
                        LOGGER.trace(prependTraceReason + "the H.264 level ({}) is not supported.", getName(), h264Level);
                    }
                } else {
                    isIncompatible = true;
                    LOGGER.trace(prependTraceReason + "the H.264 level is unknown.", getName());
                }
            } else if (media.is3d() && StringUtils.isNotBlank(renderer.getOutput3DFormat()) && (!media.get3DLayout().toString().toLowerCase(Locale.ROOT).equals(renderer.getOutput3DFormat()))) {
                forceTranscode = true;
                LOGGER.trace("Video \"{}\" is 3D and is forced to transcode to the format \"{}\"", getName(), renderer.getOutput3DFormat());
            }
        }
        // Prefer transcoding over streaming if:
        // 1) the media is unsupported by the renderer, or
        // 2) there are subs to transcode
        boolean preferTranscode = isIncompatible || hasSubsToTranscode;
        // 2) transcoding is preferred and not prevented by configuration
        if (forceTranscode || (preferTranscode && !isSkipTranscode())) {
            if (parserV2) {
                LOGGER.trace("Final verdict: \"{}\" will be transcoded with player \"{}\" with mime type \"{}\"", getName(), resolvedPlayer.toString(), renderer != null ? renderer.getMimeType(mimeType(resolvedPlayer), media) : media.getMimeType());
            } else {
                LOGGER.trace("Final verdict: \"{}\" will be transcoded with player \"{}\"", getName(), resolvedPlayer.toString());
            }
        } else {
            resolvedPlayer = null;
            LOGGER.trace("Final verdict: \"{}\" will be streamed", getName());
        }
    } else {
        LOGGER.trace("Final verdict: \"{}\" will be streamed because no compatible player was found", getName());
    }
    return resolvedPlayer;
}
Also used : ImageFormat(net.pms.image.ImageFormat) SimpleDateFormat(java.text.SimpleDateFormat) Format(net.pms.formats.Format) PmsConfiguration(net.pms.configuration.PmsConfiguration) OutputParams(net.pms.io.OutputParams)

Example 9 with OutputParams

use of net.pms.io.OutputParams in project UniversalMediaServer by UniversalMediaServer.

the class DLNAResource method getInputStream.

/**
 * Returns an InputStream of this DLNAResource that starts at a given time, if possible. Very useful if video chapters are being used.
 *
 * @param range
 * @param mediarenderer
 * @return The inputstream
 * @throws IOException
 */
public synchronized InputStream getInputStream(Range range, RendererConfiguration mediarenderer) throws IOException {
    // Use device-specific pms conf, if any
    PmsConfiguration configurationSpecificToRenderer = PMS.getConfiguration(mediarenderer);
    LOGGER.trace("Asked stream chunk : " + range + " of " + getName() + " and player " + player);
    // shagrath: small fix, regression on chapters
    boolean timeseek_auto = false;
    // Ditlew - WDTV Live
    // Ditlew - We convert byteoffset to timeoffset here. This needs the stream to be CBR!
    int cbr_video_bitrate = mediarenderer.getCBRVideoBitrate();
    long low = range.isByteRange() && range.isStartOffsetAvailable() ? range.asByteRange().getStart() : 0;
    long high = range.isByteRange() && range.isEndLimitAvailable() ? range.asByteRange().getEnd() : -1;
    Range.Time timeRange = range.createTimeRange();
    if (player != null && low > 0 && cbr_video_bitrate > 0) {
        // 1.04 = container overhead
        int used_bit_rated = (int) ((cbr_video_bitrate + 256) * 1024 / (double) 8 * 1.04);
        if (low > used_bit_rated) {
            timeRange.setStart(low / (double) (used_bit_rated));
            low = 0;
            // asking for an invalid offset which kills MEncoder
            if (timeRange.getStartOrZero() > media.getDurationInSeconds()) {
                return null;
            }
            // Should we rewind a little (in case our overhead isn't accurate enough)
            int rewind_secs = mediarenderer.getByteToTimeseekRewindSeconds();
            timeRange.rewindStart(rewind_secs);
            // shagrath:
            timeseek_auto = true;
        }
    }
    if (low > 0 && media.getBitrate() > 0) {
        lastStartPosition = (low * 8) / media.getBitrate();
        LOGGER.trace("Estimating seek position from byte range:");
        LOGGER.trace("   media.getBitrate: " + media.getBitrate());
        LOGGER.trace("   low: " + low);
        LOGGER.trace("   lastStartPosition: " + lastStartPosition);
    } else {
        lastStartPosition = timeRange.getStartOrZero();
        LOGGER.trace("Setting lastStartPosition from time-seeking: " + lastStartPosition);
    }
    // Determine source of the stream
    if (player == null && !isResume()) {
        // No transcoding
        if (this instanceof IPushOutput) {
            PipedOutputStream out = new PipedOutputStream();
            InputStream fis = new PipedInputStream(out);
            ((IPushOutput) this).push(out);
            if (low > 0) {
                fis.skip(low);
            }
            // http://www.ps3mediaserver.org/forum/viewtopic.php?f=11&t=12035
            lastStartSystemTime = System.currentTimeMillis();
            return wrap(fis, high, low);
        }
        InputStream fis = getInputStream();
        if (fis != null) {
            if (low > 0) {
                fis.skip(low);
            }
            // http://www.ps3mediaserver.org/forum/viewtopic.php?f=11&t=12035
            fis = wrap(fis, high, low);
            if (timeRange.getStartOrZero() > 0 && this instanceof RealFile) {
                fis.skip(MpegUtil.getPositionForTimeInMpeg(((RealFile) this).getFile(), (int) timeRange.getStartOrZero()));
            }
        }
        lastStartSystemTime = System.currentTimeMillis();
        return fis;
    } else {
        // Pipe transcoding result
        OutputParams params = new OutputParams(configurationSpecificToRenderer);
        params.aid = getMediaAudio();
        params.sid = media_subtitle;
        params.header = getHeaders();
        params.mediaRenderer = mediarenderer;
        timeRange.limit(getSplitRange());
        params.timeseek = timeRange.getStartOrZero();
        params.timeend = timeRange.getEndOrZero();
        params.shift_scr = timeseek_auto;
        if (this instanceof IPushOutput) {
            params.stdin = (IPushOutput) this;
        }
        if (resume != null) {
            if (range.isTimeRange()) {
                resume.update((Range.Time) range, this);
            }
            params.timeseek = resume.getTimeOffset() / 1000;
            if (player == null) {
                player = new FFMpegVideo();
            }
        }
        if (System.currentTimeMillis() - lastStartSystemTime < 500) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                LOGGER.error(null, e);
            }
        }
        // (Re)start transcoding process if necessary
        if (externalProcess == null || externalProcess.isDestroyed()) {
            // First playback attempt => start new transcoding process
            LOGGER.debug("Starting transcode/remux of " + getName() + " with media info: " + media);
            lastStartSystemTime = System.currentTimeMillis();
            externalProcess = player.launchTranscode(this, media, params);
            if (params.waitbeforestart > 0) {
                LOGGER.trace("Sleeping for {} milliseconds", params.waitbeforestart);
                try {
                    Thread.sleep(params.waitbeforestart);
                } catch (InterruptedException e) {
                    LOGGER.error(null, e);
                }
                LOGGER.trace("Finished sleeping for " + params.waitbeforestart + " milliseconds");
            }
        } else if (params.timeseek > 0 && media != null && media.isMediaparsed() && media.getDurationInSeconds() > 0) {
            // Time seek request => stop running transcode process and start a new one
            LOGGER.debug("Requesting time seek: " + params.timeseek + " seconds");
            params.minBufferSize = 1;
            Runnable r = new Runnable() {

                @Override
                public void run() {
                    externalProcess.stopProcess();
                }
            };
            new Thread(r, "External Process Stopper").start();
            lastStartSystemTime = System.currentTimeMillis();
            ProcessWrapper newExternalProcess = player.launchTranscode(this, media, params);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                LOGGER.error(null, e);
            }
            if (newExternalProcess == null) {
                LOGGER.trace("External process instance is null... sounds not good");
            }
            externalProcess = newExternalProcess;
        }
        if (externalProcess == null) {
            return null;
        }
        InputStream is = null;
        int timer = 0;
        while (is == null && timer < 10) {
            is = externalProcess.getInputStream(low);
            timer++;
            if (is == null) {
                LOGGER.debug("External input stream instance is null... sounds not good, waiting 500ms");
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                }
            }
        }
        // instead of exiting
        if (is == null && !externalProcess.isDestroyed()) {
            Runnable r = new Runnable() {

                @Override
                public void run() {
                    LOGGER.error("External input stream instance is null... stopping process");
                    externalProcess.stopProcess();
                }
            };
            new Thread(r, "Hanging External Process Stopper").start();
        }
        return is;
    }
}
Also used : SizeLimitInputStream(net.pms.io.SizeLimitInputStream) ProcessWrapper(net.pms.io.ProcessWrapper) PmsConfiguration(net.pms.configuration.PmsConfiguration) OutputParams(net.pms.io.OutputParams)

Example 10 with OutputParams

use of net.pms.io.OutputParams in project UniversalMediaServer by UniversalMediaServer.

the class DVDISOTitle method resolveOnce.

@Override
protected void resolveOnce() {
    if (getMedia() == null) {
        setMedia(new DLNAMediaInfo());
    }
    OutputParams params = new OutputParams(configuration);
    params.maxBufferSize = 1;
    params.log = true;
    boolean generateThumbnails = false;
    if (configuration.isDvdIsoThumbnails()) {
        try {
            params.workDir = configuration.getTempFolder();
            generateThumbnails = true;
        } catch (IOException e1) {
            LOGGER.error("Could not create temporary folder, DVD thumbnails won't be generated: {}", e1.getMessage());
            LOGGER.trace("", e1);
        }
    }
    String[] cmd;
    if (generateThumbnails) {
        String outFolder = "jpeg:outdir=mplayer_thumbs:subdirs=\"" + this.hashCode() + "\"";
        cmd = new String[] { configuration.getMplayerPath(), "-identify", "-ss", Integer.toString(configuration.getThumbnailSeekPos()), "-frames", "1", "-v", "-ao", "null", "-vo", outFolder, "-dvd-device", ProcessUtil.getShortFileNameIfWideChars(file.getAbsolutePath()), "dvd://" + title };
    } else {
        cmd = new String[] { configuration.getMplayerPath(), "-identify", "-endpos", "0", "-v", "-ao", "null", "-vc", "null", "-vo", "null", "-dvd-device", ProcessUtil.getShortFileNameIfWideChars(file.getAbsolutePath()), "dvd://" + title };
    }
    final ProcessWrapperImpl pw = new ProcessWrapperImpl(cmd, params, true, false);
    Runnable r = new Runnable() {

        @Override
        public void run() {
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
            }
            pw.stopProcess();
        }
    };
    Thread failsafe = new Thread(r, "DVD ISO Title Failsafe");
    failsafe.start();
    pw.runInSameThread();
    List<String> lines = pw.getOtherResults();
    String duration = null;
    int nbsectors = 0;
    String fps = null;
    String aspect = null;
    String width = null;
    String height = null;
    String codecV = null;
    ArrayList<DLNAMediaAudio> audioTracks = new ArrayList<>();
    ArrayList<DLNAMediaSubtitle> subtitles = new ArrayList<>();
    if (lines != null) {
        for (String line : lines) {
            if (line.startsWith("DVD start=")) {
                nbsectors = Integer.parseInt(line.substring(line.lastIndexOf('=') + 1).trim());
            } else if (line.startsWith("audio stream:")) {
                DLNAMediaAudio audio = parseMEncoderAudioStream(line);
                if (audio != null) {
                    audioTracks.add(audio);
                }
            } else if (line.startsWith("subtitle")) {
                DLNAMediaSubtitle subtitle = parseMEncoderSubtitleStream(line);
                if (subtitle != null) {
                    subtitles.add(subtitle);
                }
            } else if (line.startsWith("ID_VIDEO_WIDTH=")) {
                width = line.substring(line.indexOf("ID_VIDEO_WIDTH=") + 15).trim();
            } else if (line.startsWith("ID_VIDEO_HEIGHT=")) {
                height = line.substring(line.indexOf("ID_VIDEO_HEIGHT=") + 16).trim();
            } else if (line.startsWith("ID_VIDEO_FPS=")) {
                fps = line.substring(line.indexOf("ID_VIDEO_FPS=") + 13).trim();
            } else if (line.startsWith("ID_LENGTH=")) {
                duration = line.substring(line.indexOf("ID_LENGTH=") + 10).trim();
            } else if (line.startsWith("ID_VIDEO_ASPECT=")) {
                aspect = line.substring(line.indexOf("ID_VIDEO_ASPECT=") + 16).trim();
            } else if (line.startsWith("ID_VIDEO_FORMAT=")) {
                String formatStr = line.substring(line.lastIndexOf("=") + 1).trim();
                if ("0x31435657".equals(formatStr)) {
                    codecV = FormatConfiguration.VC1;
                } else if ("0x10000001".equals(formatStr)) {
                    codecV = FormatConfiguration.MPEG1;
                } else if ("0x10000002".equals(formatStr)) {
                    codecV = FormatConfiguration.MPEG2;
                } else {
                    LOGGER.warn("Unknown video format value \"{}\"", formatStr);
                }
            }
        }
    }
    if (generateThumbnails) {
        try {
            String frameName = "" + this.hashCode();
            frameName = configuration.getTempFolder() + "/mplayer_thumbs/" + frameName + "00000001/00000001.jpg";
            frameName = frameName.replace(',', '_');
            File jpg = new File(frameName);
            if (jpg.exists()) {
                try (InputStream inputStream = new FileInputStream(jpg)) {
                    getMedia().setThumb(DLNAThumbnail.toThumbnail(inputStream, 640, 480, ScaleType.MAX, ImageFormat.SOURCE, false));
                }
                if (!jpg.delete()) {
                    jpg.deleteOnExit();
                }
                // Try and retry
                if (!jpg.getParentFile().delete() && !jpg.getParentFile().delete()) {
                    LOGGER.debug("Failed to delete \"" + jpg.getParentFile().getAbsolutePath() + "\"");
                }
            }
            jpg = new File(frameName + "1.jpg");
            if (jpg.exists()) {
                if (!jpg.delete()) {
                    jpg.deleteOnExit();
                }
                if (!jpg.getParentFile().delete()) {
                    if (!jpg.getParentFile().delete()) {
                        jpg.getParentFile().deleteOnExit();
                    }
                }
            }
        } catch (IOException e) {
            LOGGER.error("Error during DVD ISO thumbnail retrieval: {}", e.getMessage());
            LOGGER.trace("", e);
        }
    }
    // No point in trying to re-parse the thumbnail later
    getMedia().setThumbready(true);
    length = nbsectors * 2048;
    double d = 0;
    if (duration != null) {
        d = Double.parseDouble(duration);
    }
    getMedia().setAudioTracksList(audioTracks);
    getMedia().setSubtitleTracksList(subtitles);
    if (duration != null) {
        getMedia().setDuration(d);
    }
    getMedia().setFrameRate(fps);
    getMedia().setAspectRatioDvdIso(aspect);
    getMedia().setDvdtrack(title);
    getMedia().setContainer(FormatConfiguration.ISO);
    getMedia().setCodecV(codecV != null ? codecV : FormatConfiguration.MPEG2);
    getMedia().setVideoTrackCount(1);
    try {
        getMedia().setWidth(Integer.parseInt(width));
    } catch (NumberFormatException nfe) {
        LOGGER.debug("Could not parse DVD video width \"{}\"", width);
    }
    try {
        getMedia().setHeight(Integer.parseInt(height));
    } catch (NumberFormatException nfe) {
        LOGGER.debug("Could not parse DVD video height \"{}\"", height);
    }
    getMedia().setMediaparsed(true);
}
Also used : FileInputStream(java.io.FileInputStream) InputStream(java.io.InputStream) ArrayList(java.util.ArrayList) IOException(java.io.IOException) FileInputStream(java.io.FileInputStream) OutputParams(net.pms.io.OutputParams) File(java.io.File) ProcessWrapperImpl(net.pms.io.ProcessWrapperImpl)

Aggregations

OutputParams (net.pms.io.OutputParams)17 ProcessWrapperImpl (net.pms.io.ProcessWrapperImpl)10 IOException (java.io.IOException)5 PmsConfiguration (net.pms.configuration.PmsConfiguration)5 ProcessWrapper (net.pms.io.ProcessWrapper)4 InputStream (java.io.InputStream)3 Matcher (java.util.regex.Matcher)3 DLNAResource (net.pms.dlna.DLNAResource)3 ImagePlayer (net.pms.encoders.ImagePlayer)3 Format (net.pms.formats.Format)3 ByteArrayInputStream (java.io.ByteArrayInputStream)2 File (java.io.File)2 FileInputStream (java.io.FileInputStream)2 OutputStream (java.io.OutputStream)2 MalformedURLException (java.net.MalformedURLException)2 Socket (java.net.Socket)2 URL (java.net.URL)2 SimpleDateFormat (java.text.SimpleDateFormat)2 ArrayList (java.util.ArrayList)2 ParserConfigurationException (javax.xml.parsers.ParserConfigurationException)2