Search in sources :

Example 16 with RendererConfiguration

use of net.pms.configuration.RendererConfiguration in project UniversalMediaServer by UniversalMediaServer.

the class RequestHandler method run.

@Override
public void run() {
    Request request = null;
    StartStopListenerDelegate startStopListenerDelegate = new StartStopListenerDelegate(socket.getInetAddress().getHostAddress());
    try {
        int receivedContentLength = -1;
        String userAgentString = null;
        ArrayList<String> identifiers = new ArrayList<>();
        RendererConfiguration renderer = null;
        InetSocketAddress remoteAddress = (InetSocketAddress) socket.getRemoteSocketAddress();
        InetAddress ia = remoteAddress.getAddress();
        boolean isSelf = ia.getHostAddress().equals(PMS.get().getServer().getHost());
        // Apply the IP filter
        if (filterIp(ia)) {
            throw new IOException("Access denied for address " + ia + " based on IP filter");
        }
        // The handler makes a couple of attempts to recognize a renderer from its requests.
        // IP address matches from previous requests are preferred, when that fails request
        // header matches are attempted and if those fail as well we're stuck with the
        // default renderer.
        // Attempt 1: try to recognize the renderer by its socket address from previous requests
        renderer = RendererConfiguration.getRendererConfigurationBySocketAddress(ia);
        // If the renderer exists but isn't marked as loaded it means it's unrecognized
        // by upnp and we still need to attempt http recognition here.
        boolean unrecognized = renderer == null || !renderer.loaded;
        RendererConfiguration.SortedHeaderMap sortedHeaders = unrecognized ? new RendererConfiguration.SortedHeaderMap() : null;
        // Gather all the headers
        ArrayList<String> headerLines = new ArrayList<>();
        String line = br.readLine();
        while (line != null && line.length() > 0) {
            headerLines.add(line);
            if (sortedHeaders != null) {
                sortedHeaders.put(line);
            }
            line = br.readLine();
        }
        if (unrecognized) {
            // Attempt 2: try to recognize the renderer by matching headers
            renderer = RendererConfiguration.getRendererConfigurationByHeaders(sortedHeaders, ia);
        }
        for (String headerLine : headerLines) {
            // The request object is created inside the while loop.
            if (request != null && request.getMediaRenderer() == null && renderer != null) {
                request.setMediaRenderer(renderer);
            }
            if (headerLine.toUpperCase().startsWith("USER-AGENT")) {
                // Is the request from our own Cling service, i.e. self-originating?
                if (isSelf && headerLine.contains("UMS/")) {
                    // LOGGER.trace("Ignoring self-originating request from {}:{}", ia, remoteAddress.getPort());
                    return;
                }
                userAgentString = headerLine.substring(headerLine.indexOf(':') + 1).trim();
            } else if (renderer != null && headerLine.startsWith("X-PANASONIC-DMP-Profile:")) {
                PanasonicDmpProfiles.parsePanasonicDmpProfiles(headerLine, renderer);
            }
            try {
                StringTokenizer s = new StringTokenizer(headerLine);
                String temp = s.nextToken();
                if (temp.equals("SUBSCRIBE") || temp.equals("GET") || temp.equals("POST") || temp.equals("HEAD")) {
                    request = new Request(temp, s.nextToken().substring(1));
                    if (s.hasMoreTokens() && s.nextToken().equals("HTTP/1.0")) {
                        request.setHttp10(true);
                    }
                } else if (request != null && temp.toUpperCase().equals("CALLBACK:")) {
                    request.setSoapaction(s.nextToken());
                } else if (request != null && temp.toUpperCase().equals("SOAPACTION:")) {
                    request.setSoapaction(s.nextToken());
                } else if (headerLine.toUpperCase().contains("CONTENT-LENGTH:")) {
                    receivedContentLength = Integer.parseInt(headerLine.substring(headerLine.toUpperCase().indexOf("CONTENT-LENGTH: ") + 16));
                } else if (headerLine.toUpperCase().contains("RANGE: BYTES=")) {
                    String nums = headerLine.substring(headerLine.toUpperCase().indexOf("RANGE: BYTES=") + 13).trim();
                    StringTokenizer st = new StringTokenizer(nums, "-");
                    if (!nums.startsWith("-")) {
                        request.setLowRange(Long.parseLong(st.nextToken()));
                    }
                    if (!nums.startsWith("-") && !nums.endsWith("-")) {
                        request.setHighRange(Long.parseLong(st.nextToken()));
                    } else {
                        request.setHighRange(-1);
                    }
                } else if (headerLine.toLowerCase().contains("transfermode.dlna.org:")) {
                    request.setTransferMode(headerLine.substring(headerLine.toLowerCase().indexOf("transfermode.dlna.org:") + 22).trim());
                } else if (headerLine.toLowerCase().contains("getcontentfeatures.dlna.org:")) {
                    request.setContentFeatures(headerLine.substring(headerLine.toLowerCase().indexOf("getcontentfeatures.dlna.org:") + 28).trim());
                } else if (headerLine.toUpperCase().contains("TIMESEEKRANGE.DLNA.ORG: NPT=")) {
                    // firmware 2.50+
                    String timeseek = headerLine.substring(headerLine.toUpperCase().indexOf("TIMESEEKRANGE.DLNA.ORG: NPT=") + 28);
                    if (timeseek.endsWith("-")) {
                        timeseek = timeseek.substring(0, timeseek.length() - 1);
                    } else if (timeseek.indexOf('-') > -1) {
                        timeseek = timeseek.substring(0, timeseek.indexOf('-'));
                    }
                    request.setTimeseek(convertStringToTime(timeseek));
                } else if (headerLine.toUpperCase().contains("TIMESEEKRANGE.DLNA.ORG : NPT=")) {
                    // firmware 2.40
                    String timeseek = headerLine.substring(headerLine.toUpperCase().indexOf("TIMESEEKRANGE.DLNA.ORG : NPT=") + 29);
                    if (timeseek.endsWith("-")) {
                        timeseek = timeseek.substring(0, timeseek.length() - 1);
                    } else if (timeseek.indexOf('-') > -1) {
                        timeseek = timeseek.substring(0, timeseek.indexOf('-'));
                    }
                    request.setTimeseek(convertStringToTime(timeseek));
                } else {
                    /*
						 * If we made it to here, none of the previous header checks matched.
						 * Unknown headers make interesting logging info when we cannot recognize
						 * the media renderer, so keep track of the truly unknown ones.
						 */
                    boolean isKnown = false;
                    // Try to match possible known headers.
                    String lowerCaseHeaderLine = headerLine.toLowerCase();
                    for (String knownHeaderString : KNOWN_HEADERS) {
                        if (lowerCaseHeaderLine.startsWith(knownHeaderString.toLowerCase())) {
                            isKnown = true;
                            break;
                        }
                    }
                    // It may be unusual but already known
                    if (renderer != null) {
                        String additionalHeader = renderer.getUserAgentAdditionalHttpHeader();
                        if (StringUtils.isNotBlank(additionalHeader) && lowerCaseHeaderLine.startsWith(additionalHeader)) {
                            isKnown = true;
                        }
                    }
                    if (!isKnown) {
                        // Truly unknown header, therefore interesting. Save for later use.
                        identifiers.add(headerLine);
                    }
                }
            } catch (IllegalArgumentException e) {
                LOGGER.error("Error parsing HTTP headers: {}", e.getMessage());
                LOGGER.trace("", e);
            }
        }
        if (request != null) {
            // Still no media renderer recognized?
            if (renderer == null) {
                // Attempt 3: Not really an attempt; all other attempts to recognize
                // the renderer have failed. The only option left is to assume the
                // default renderer.
                renderer = RendererConfiguration.resolve(ia, null);
                request.setMediaRenderer(renderer);
                if (renderer != null) {
                    LOGGER.debug("Using default media renderer \"{}\"", renderer.getConfName());
                    if (userAgentString != null && !userAgentString.equals("FDSSDP")) {
                        // We have found an unknown renderer
                        identifiers.add(0, "User-Agent: " + userAgentString);
                        renderer.setIdentifiers(identifiers);
                        LOGGER.info("Media renderer was not recognized. Possible identifying HTTP headers:\n{}", StringUtils.join(identifiers, "\n"));
                    }
                } else {
                    // it means we know via upnp that it's not really a renderer.
                    return;
                }
            } else if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Recognized media renderer \"{}\"", renderer.getRendererName());
            }
        }
        if (receivedContentLength > 0) {
            char[] buf = new char[receivedContentLength];
            br.read(buf);
            if (request != null) {
                String textContent = new String(buf);
                request.setTextContent(textContent);
                if (LOGGER.isTraceEnabled()) {
                    logMessageReceived(headerLines, textContent, socket.getRemoteSocketAddress(), renderer);
                }
            } else if (LOGGER.isTraceEnabled()) {
                logMessageReceived(headerLines, null, socket.getRemoteSocketAddress(), renderer);
            }
        }
        if (request != null) {
            request.answer(output, startStopListenerDelegate);
        }
        if (request != null && request.getInputStream() != null) {
            request.getInputStream().close();
        }
    } catch (IOException e) {
        LOGGER.error("Unexpected IO error in {}: {}", getClass().getName(), e.getMessage());
        // LOGGER.trace("", e);
        if (request != null && request.getInputStream() != null) {
            try {
                LOGGER.trace("Closing input stream: {}", request.getInputStream());
                request.getInputStream().close();
            } catch (IOException e1) {
                LOGGER.error("Error closing input stream: {}", e1);
                LOGGER.trace("", e1);
            }
        }
    } finally {
        try {
            output.close();
            br.close();
            socket.close();
        } catch (IOException e) {
            LOGGER.error("Error closing connection: {}", e.getMessage());
            LOGGER.trace("", e);
        }
        startStopListenerDelegate.stop();
    }
}
Also used : InetSocketAddress(java.net.InetSocketAddress) ArrayList(java.util.ArrayList) IOException(java.io.IOException) StartStopListenerDelegate(net.pms.external.StartStopListenerDelegate) StringTokenizer(java.util.StringTokenizer) RendererConfiguration(net.pms.configuration.RendererConfiguration) InetAddress(java.net.InetAddress)

Example 17 with RendererConfiguration

use of net.pms.configuration.RendererConfiguration in project UniversalMediaServer by UniversalMediaServer.

the class RequestHandlerV2 method messageReceived.

@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent event) throws Exception {
    RequestV2 request = null;
    RendererConfiguration renderer = null;
    String userAgentString = null;
    ArrayList<String> identifiers = new ArrayList<>();
    HttpRequest nettyRequest = this.nettyRequest = (HttpRequest) event.getMessage();
    InetSocketAddress remoteAddress = (InetSocketAddress) event.getChannel().getRemoteAddress();
    InetAddress ia = remoteAddress.getAddress();
    // Is the request from our own Cling service, i.e. self-originating?
    boolean isSelf = ia.getHostAddress().equals(PMS.get().getServer().getHost()) && nettyRequest.headers().get(HttpHeaders.Names.USER_AGENT) != null && nettyRequest.headers().get(HttpHeaders.Names.USER_AGENT).contains("UMS/");
    // Filter if required
    if (isSelf || filterIp(ia)) {
        event.getChannel().close();
        /*if (isSelf && LOGGER.isTraceEnabled()) {
				LOGGER.trace("Ignoring self-originating request from {}:{}", ia, remoteAddress.getPort());
			}*/
        return;
    }
    request = new RequestV2(nettyRequest.getMethod().getName(), nettyRequest.getUri().substring(1));
    if (nettyRequest.getProtocolVersion().getMinorVersion() == 0) {
        request.setHttp10(true);
    }
    HttpHeaders headers = nettyRequest.headers();
    // The handler makes a couple of attempts to recognize a renderer from its requests.
    // IP address matches from previous requests are preferred, when that fails request
    // header matches are attempted and if those fail as well we're stuck with the
    // default renderer.
    // Attempt 1: try to recognize the renderer by its socket address from previous requests
    renderer = RendererConfiguration.getRendererConfigurationBySocketAddress(ia);
    // by upnp and we still need to attempt http recognition here.
    if (renderer == null || !renderer.loaded) {
        // Attempt 2: try to recognize the renderer by matching headers
        renderer = RendererConfiguration.getRendererConfigurationByHeaders(headers.entries(), ia);
    }
    if (renderer != null) {
        request.setMediaRenderer(renderer);
    }
    Set<String> headerNames = headers.names();
    Iterator<String> iterator = headerNames.iterator();
    while (iterator.hasNext()) {
        String name = iterator.next();
        String headerLine = name + ": " + headers.get(name);
        if (headerLine.toUpperCase().startsWith("USER-AGENT")) {
            userAgentString = headerLine.substring(headerLine.indexOf(':') + 1).trim();
        } else if (renderer != null && name.equals("X-PANASONIC-DMP-Profile")) {
            PanasonicDmpProfiles.parsePanasonicDmpProfiles(headers.get(name), renderer);
        }
        try {
            StringTokenizer s = new StringTokenizer(headerLine);
            String temp = s.nextToken();
            if (temp.toUpperCase().equals("SOAPACTION:")) {
                request.setSoapaction(s.nextToken());
            } else if (temp.toUpperCase().equals("CALLBACK:")) {
                request.setSoapaction(s.nextToken());
            } else if (headerLine.toUpperCase().contains("RANGE: BYTES=")) {
                String nums = headerLine.substring(headerLine.toUpperCase().indexOf("RANGE: BYTES=") + 13).trim();
                StringTokenizer st = new StringTokenizer(nums, "-");
                if (!nums.startsWith("-")) {
                    request.setLowRange(Long.parseLong(st.nextToken()));
                }
                if (!nums.startsWith("-") && !nums.endsWith("-")) {
                    request.setHighRange(Long.parseLong(st.nextToken()));
                } else {
                    request.setHighRange(-1);
                }
            } else if (headerLine.toLowerCase().contains("transfermode.dlna.org:")) {
                request.setTransferMode(headerLine.substring(headerLine.toLowerCase().indexOf("transfermode.dlna.org:") + 22).trim());
            } else if (headerLine.toLowerCase().contains("getcontentfeatures.dlna.org:")) {
                request.setContentFeatures(headerLine.substring(headerLine.toLowerCase().indexOf("getcontentfeatures.dlna.org:") + 28).trim());
            } else {
                Matcher matcher = TIMERANGE_PATTERN.matcher(headerLine);
                if (matcher.find()) {
                    String first = matcher.group(1);
                    if (first != null) {
                        request.setTimeRangeStartString(first);
                    }
                    String end = matcher.group(2);
                    if (end != null) {
                        request.setTimeRangeEndString(end);
                    }
                } else {
                    /**
                     * If we made it to here, none of the previous header checks matched.
                     * Unknown headers make interesting logging info when we cannot recognize
                     * the media renderer, so keep track of the truly unknown ones.
                     */
                    boolean isKnown = false;
                    // Try to match known headers.
                    String lowerCaseHeaderLine = headerLine.toLowerCase();
                    for (String knownHeaderString : KNOWN_HEADERS) {
                        if (lowerCaseHeaderLine.startsWith(knownHeaderString)) {
                            isKnown = true;
                            break;
                        }
                    }
                    // It may be unusual but already known
                    if (!isKnown && renderer != null) {
                        String additionalHeader = renderer.getUserAgentAdditionalHttpHeader();
                        if (StringUtils.isNotBlank(additionalHeader) && lowerCaseHeaderLine.startsWith(additionalHeader)) {
                            isKnown = true;
                        }
                    }
                    if (!isKnown) {
                        // Truly unknown header, therefore interesting. Save for later use.
                        identifiers.add(headerLine);
                    }
                }
            }
        } catch (Exception ee) {
            LOGGER.error("Error parsing HTTP headers: {}", ee.getMessage());
            LOGGER.trace("", ee);
        }
    }
    // Still no media renderer recognized?
    if (renderer == null) {
        // Attempt 3: Not really an attempt; all other attempts to recognize
        // the renderer have failed. The only option left is to assume the
        // default renderer.
        renderer = RendererConfiguration.resolve(ia, null);
        request.setMediaRenderer(renderer);
        if (renderer != null) {
            LOGGER.debug("Using default media renderer \"{}\"", renderer.getConfName());
            if (userAgentString != null && !userAgentString.equals("FDSSDP")) {
                // We have found an unknown renderer
                identifiers.add(0, "User-Agent: " + userAgentString);
                renderer.setIdentifiers(identifiers);
                LOGGER.info("Media renderer was not recognized. Possible identifying HTTP headers:\n{}", StringUtils.join(identifiers, "\n"));
                PMS.get().setRendererFound(renderer);
            }
        } else {
            // it means we know via upnp that it's not really a renderer.
            return;
        }
    } else if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Recognized media renderer \"{}\"", renderer.getRendererName());
    }
    if (nettyRequest.headers().contains(HttpHeaders.Names.CONTENT_LENGTH)) {
        byte[] data = new byte[(int) HttpHeaders.getContentLength(nettyRequest)];
        ChannelBuffer content = nettyRequest.getContent();
        content.readBytes(data);
        String textContent = new String(data, "UTF-8");
        request.setTextContent(textContent);
        if (LOGGER.isTraceEnabled()) {
            logMessageReceived(event, textContent, renderer);
        }
    } else if (LOGGER.isTraceEnabled()) {
        logMessageReceived(event, null, renderer);
    }
    writeResponse(ctx, event, request, ia);
}
Also used : Matcher(java.util.regex.Matcher) InetSocketAddress(java.net.InetSocketAddress) ArrayList(java.util.ArrayList) XPathExpressionException(javax.xml.xpath.XPathExpressionException) TransformerException(javax.xml.transform.TransformerException) ClosedChannelException(java.nio.channels.ClosedChannelException) IOException(java.io.IOException) TooLongFrameException(org.jboss.netty.handler.codec.frame.TooLongFrameException) ParserConfigurationException(javax.xml.parsers.ParserConfigurationException) SAXException(org.xml.sax.SAXException) ChannelBuffer(org.jboss.netty.buffer.ChannelBuffer) StringTokenizer(java.util.StringTokenizer) RendererConfiguration(net.pms.configuration.RendererConfiguration) InetAddress(java.net.InetAddress)

Example 18 with RendererConfiguration

use of net.pms.configuration.RendererConfiguration in project UniversalMediaServer by UniversalMediaServer.

the class DLNAResource method stopPlaying.

/**
 * Plugin implementation. When this item is going to stop playing, it will notify all the StartStopListener
 * objects available.
 *
 * @see StartStopListener
 */
public void stopPlaying(final String rendererId, final RendererConfiguration incomingRenderer) {
    final DLNAResource self = this;
    final String requestId = getRequestId(rendererId);
    Runnable defer = new Runnable() {

        @Override
        public void run() {
            long start = startTime;
            try {
                Thread.sleep(STOP_PLAYING_DELAY);
            } catch (InterruptedException e) {
                LOGGER.error("stopPlaying sleep interrupted", e);
            }
            synchronized (requestIdToRefcount) {
                final Integer refCount = requestIdToRefcount.get(requestId);
                assert refCount != null;
                assert refCount > 0;
                requestIdToRefcount.put(requestId, refCount - 1);
                if (start != startTime) {
                    return;
                }
                Runnable r = new Runnable() {

                    @Override
                    public void run() {
                        if (refCount == 1) {
                            requestIdToRefcount.put(requestId, 0);
                            InetAddress rendererIp;
                            try {
                                rendererIp = InetAddress.getByName(rendererId);
                                RendererConfiguration renderer;
                                if (incomingRenderer == null) {
                                    renderer = RendererConfiguration.getRendererConfigurationBySocketAddress(rendererIp);
                                } else {
                                    renderer = incomingRenderer;
                                }
                                String rendererName = "unknown renderer";
                                try {
                                    // Reset only if another item hasn't already begun playing
                                    if (renderer.getPlayingRes() == self) {
                                        renderer.setPlayingRes(null);
                                    }
                                    rendererName = renderer.getRendererName();
                                } catch (NullPointerException e) {
                                }
                                if (!quietPlay()) {
                                    LOGGER.info("Stopped playing " + getName() + " on your " + rendererName);
                                    LOGGER.debug("The full filename of which is: " + getSystemName() + " and the address of the renderer is: " + rendererId);
                                }
                            } catch (UnknownHostException ex) {
                                LOGGER.debug("" + ex);
                            }
                            internalStop();
                            for (final ExternalListener listener : ExternalFactory.getExternalListeners()) {
                                if (listener instanceof StartStopListener) {
                                    // run these asynchronously for slow handlers (e.g. logging, scrobbling)
                                    Runnable fireStartStopEvent = new Runnable() {

                                        @Override
                                        public void run() {
                                            try {
                                                ((StartStopListener) listener).donePlaying(media, self);
                                            } catch (Throwable t) {
                                                LOGGER.error("Notification of donePlaying event failed for StartStopListener {}", listener.getClass(), t);
                                            }
                                        }
                                    };
                                    new Thread(fireStartStopEvent, "StopPlaying Event for " + listener.name()).start();
                                }
                            }
                        }
                    }
                };
                new Thread(r, "StopPlaying Event").start();
            }
        }
    };
    new Thread(defer, "StopPlaying Event Deferrer").start();
}
Also used : UnknownHostException(java.net.UnknownHostException) StartStopListener(net.pms.external.StartStopListener) RendererConfiguration(net.pms.configuration.RendererConfiguration) ExternalListener(net.pms.external.ExternalListener) InetAddress(java.net.InetAddress)

Example 19 with RendererConfiguration

use of net.pms.configuration.RendererConfiguration in project UniversalMediaServer by UniversalMediaServer.

the class FileTranscodeVirtualFolder method resolveOnce.

/**
 * This populates the file-specific transcode folder with all combinations of players,
 * audio tracks and subtitles.
 */
@Override
protected void resolveOnce() {
    if (getChildren().size() == 1) {
        // OK
        DLNAResource child = getChildren().get(0);
        child.syncResolve();
        RendererConfiguration renderer = null;
        if (this.getParent() != null) {
            renderer = this.getParent().getDefaultRenderer();
        }
        // create copies of the audio/subtitle track lists as we're making (local)
        // modifications to them
        List<DLNAMediaAudio> audioTracks = new ArrayList<>(child.getMedia().getAudioTracksList());
        List<DLNAMediaSubtitle> subtitleTracks = new ArrayList<>(child.getMedia().getSubtitleTracksList());
        // assemble copies for each combination of audio, subtitle and player
        ArrayList<DLNAResource> entries = new ArrayList<>();
        // First, add the option to simply stream the resource.
        if (renderer != null) {
            LOGGER.trace("Duplicating {} for direct streaming to renderer: {}", child.getName(), renderer.getRendererName());
        }
        DLNAResource noTranscode = createResourceWithAudioSubtitlePlayer(child, null, null, null);
        addChildInternal(noTranscode);
        addChapterFolder(noTranscode);
        // add options for renderer capable to handle streamed subtitles
        if (!configuration.isDisableSubtitles() && renderer != null && renderer.isSubtitlesStreamingSupported()) {
            for (DLNAMediaSubtitle subtitle : subtitleTracks) {
                // only add the option if the renderer supports the given format
                if (subtitle.isExternal()) {
                    // do not check for embedded subs
                    if (renderer.isExternalSubtitlesFormatSupported(subtitle, child.getMedia())) {
                        DLNAResource copy = createResourceWithAudioSubtitlePlayer(child, null, subtitle, null);
                        copy.getMediaSubtitle().setSubsStreamable(true);
                        entries.add(copy);
                        LOGGER.trace("Duplicating {} for direct streaming subtitles {}", child.getName(), subtitle.toString());
                    }
                }
            }
        }
        if (audioTracks.isEmpty()) {
            audioTracks.add(null);
        }
        if (subtitleTracks.isEmpty()) {
            subtitleTracks.add(null);
        } else {
            // if there are subtitles, make sure a no-subtitle option is added
            // for each player
            DLNAMediaSubtitle noSubtitle = new DLNAMediaSubtitle();
            noSubtitle.setId(-1);
            subtitleTracks.add(noSubtitle);
        }
        for (DLNAMediaAudio audio : audioTracks) {
            // Create combinations of all audio tracks, subtitles and players.
            for (DLNAMediaSubtitle subtitle : subtitleTracks) {
                // Create a temporary copy of the child with the audio and
                // subtitle modified in order to be able to match players to it.
                DLNAResource temp = createResourceWithAudioSubtitlePlayer(child, audio, subtitle, null);
                // Determine which players match this audio track and subtitle
                ArrayList<Player> players = PlayerFactory.getPlayers(temp);
                // create a copy for each compatible player
                for (Player player : players) {
                    DLNAResource copy = createResourceWithAudioSubtitlePlayer(child, audio, subtitle, player);
                    entries.add(copy);
                }
            }
        }
        // Sort the list of combinations
        Collections.sort(entries, new ResourceSort(PlayerFactory.getPlayers()));
        // Now add the sorted list of combinations to the folder
        for (DLNAResource dlna : entries) {
            LOGGER.trace("Adding {}: audio: {}, subtitle: {}, player: {}", new Object[] { dlna.getName(), dlna.getMediaAudio(), dlna.getMediaSubtitle(), (dlna.getPlayer() != null ? dlna.getPlayer().name() : null) });
            addChildInternal(dlna);
            addChapterFolder(dlna);
        }
    }
}
Also used : Player(net.pms.encoders.Player) RendererConfiguration(net.pms.configuration.RendererConfiguration) ArrayList(java.util.ArrayList)

Example 20 with RendererConfiguration

use of net.pms.configuration.RendererConfiguration 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

RendererConfiguration (net.pms.configuration.RendererConfiguration)24 ArrayList (java.util.ArrayList)10 IOException (java.io.IOException)5 Test (org.junit.Test)5 InetAddress (java.net.InetAddress)4 DLNAMediaInfo (net.pms.dlna.DLNAMediaInfo)4 Format (net.pms.formats.Format)4 UnknownHostException (java.net.UnknownHostException)3 PmsConfiguration (net.pms.configuration.PmsConfiguration)3 WebRender (net.pms.configuration.WebRender)3 DLNAMediaAudio (net.pms.dlna.DLNAMediaAudio)3 ExternalListener (net.pms.external.ExternalListener)3 MPG (net.pms.formats.MPG)3 InetSocketAddress (java.net.InetSocketAddress)2 StringTokenizer (java.util.StringTokenizer)2 DeviceConfiguration (net.pms.configuration.DeviceConfiguration)2 InputFile (net.pms.dlna.InputFile)2 StartStopListener (net.pms.external.StartStopListener)2 MP3 (net.pms.formats.audio.MP3)2 KeyedComboBoxModel (net.pms.util.KeyedComboBoxModel)2