private boolean isSeekable(DLNAResource dlna) { Player player = dlna.getPlayer(); if ((player == null) || player.isTimeSeekable()) { return true; } else { return false; } }
/** * Create a copy of the provided original resource and optionally set the copy's audio track, * subtitle track and player. * * @param original The original {@link DLNAResource} to create a copy of. * @param audio The audio track to use. * @param subtitle The subtitle track to use. * @param player The player to use. * @return The copy. */ private DLNAResource createResourceWithAudioSubtitlePlayer( DLNAResource original, DLNAMediaAudio audio, DLNAMediaSubtitle subtitle, Player player) { // FIXME clone is broken. should be e.g. original.newInstance() DLNAResource copy = original.clone(); copy.setMedia(original.getMedia()); copy.setNoName(true); copy.setMediaAudio(audio); copy.setMediaSubtitle(subtitle); copy.setPlayer(player); return copy; }
@Override public int compare(DLNAResource dlna1, DLNAResource dlna2) { Integer playerIndex1 = players.indexOf(dlna1.getPlayer()); Integer playerIndex2 = players.indexOf(dlna2.getPlayer()); if (!playerIndex1.equals(playerIndex2)) { return playerIndex1.compareTo(playerIndex2); } int cmpAudioLang = compareLanguage(getMediaAudioLanguage(dlna1), getMediaAudioLanguage(dlna2)); if (cmpAudioLang != 0) { return cmpAudioLang; } int cmpSubtitleLang = compareLanguage(getMediaSubtitleLanguage(dlna1), getMediaSubtitleLanguage(dlna2)); return cmpSubtitleLang; }
private void addChapterFolder(DLNAResource dlna) { if (!dlna.getFormat().isVideo()) { return; } int chapterInterval = configuration.isChapterSupport() ? configuration.getChapterInterval() : -1; if ((chapterInterval > 0) && isSeekable(dlna)) { // don't add a chapter folder if the duration of the video // is less than the chapter length. double duration = dlna.getMedia().getDurationInSeconds(); // 0 if the duration is unknown if (duration != 0 && duration <= (chapterInterval * 60)) { return; } ChapterFileTranscodeVirtualFolder chapterFolder = new ChapterFileTranscodeVirtualFolder( String.format( Messages.getString("FileTranscodeVirtualFolder.1"), dlna.getDisplayName()), null, chapterInterval); DLNAResource copy = dlna.clone(); copy.setNoName(true); chapterFolder.addChildInternal(copy); addChildInternal(chapterFolder); } }
@Override public void resolve() { if (getFormat() == null || !getFormat().isVideo()) { return; } boolean found = false; if (!found) { if (getMedia() == null) { setMedia(new DLNAMediaInfo()); } found = !getMedia().isMediaparsed() && !getMedia().isParsing(); if (getFormat() != null) { InputFile input = new InputFile(); input.setPush(this); input.setSize(length()); getFormat().parse(getMedia(), input, getType()); } } super.resolve(); }
private String getMediaSubtitleLanguage(DLNAResource dlna) { return dlna.getMediaSubtitle() == null ? null : dlna.getMediaSubtitle().getLang(); }
private String getMediaAudioLanguage(DLNAResource dlna) { return dlna.getMediaAudio() == null ? null : dlna.getMediaAudio().getLang(); }
/** * 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()); } } } } /* we add (or may add) a null entry to the audio list and/or subtitle list to ensure the inner loop is always entered: for audio in audioTracks: for subtitle in subtitleTracks: for player in players: newResource(audio, subtitle, player) there are 4 different scenarios: 1) a file with audio tracks and no subtitles (subtitle == null): in that case we want to assign a player for each audio track 2) a file with subtitles and no audio tracks (audio == null): in that case we want to assign a player for each subtitle track 3) a file with no audio tracks (audio == null) and no subtitles (subtitle == null) e.g. an audio file, a video with no sound and no subtitles or a web audio/video file: in that case we still want to provide a selection of players e.g. FFmpeg Web Video and VLC Web Video for a web video or FFmpeg Audio and MPlayer Audio for an audio file 4) one or more audio tracks AND one or more subtitle tracks: this is the case this code used to handle when it solely dealt with (local) video files: assign a player for each combination of audio track and subtitle track If a null audio or subtitle track is passed to createResourceWithAudioSubtitlePlayer, it sets the copy's corresponding mediaAudio (AKA params.aid) or mediaSubtitle (AKA params.sid) value to null. Note: this is the only place in the codebase where mediaAudio and mediaSubtitle are assigned (ignoring the trivial clone operation in ChapterFileTranscodeVirtualFolder), so setting one or both of them to null is a no-op as they're already null. */ 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); } } }
@Override public void handle(HttpExchange t) throws IOException { if (RemoteUtil.deny(t)) { throw new IOException("Access denied"); } RootFolder root = parent.getRoot(RemoteUtil.userName(t), t); if (root == null) { throw new IOException("Unknown root"); } Headers h = t.getRequestHeaders(); for (String h1 : h.keySet()) { LOGGER.debug("key " + h1 + "=" + h.get(h1)); } String id = RemoteUtil.getId(path, t); id = RemoteUtil.strip(id); RendererConfiguration r = render; if (render == null) { r = root.getDefaultRenderer(); } DLNAResource dlna = root.getDLNAResource(id, r); if (dlna == null) { // another error LOGGER.debug("media unkonwn"); throw new IOException("Bad id"); } if (!dlna.isCodeValid(dlna)) { LOGGER.debug("coded object with invalid code"); throw new IOException("Bad code"); } DLNAMediaSubtitle sid = null; long len = dlna.length(); Range range = RemoteUtil.parseRange(t.getRequestHeaders(), len); String mime = root.getDefaultRenderer().getMimeType(dlna.mimeType()); // DLNAResource dlna = res.get(0); WebRender render = (WebRender) r; DLNAMediaInfo m = dlna.getMedia(); if (m == null) { m = new DLNAMediaInfo(); dlna.setMedia(m); } if (mime.equals(FormatConfiguration.MIMETYPE_AUTO) && m.getMimeType() != null) { mime = m.getMimeType(); } int code = 200; dlna.setDefaultRenderer(r); if (dlna.getFormat().isVideo()) { if (flash) { mime = "video/flash"; } else if (!RemoteUtil.directmime(mime) || RemoteUtil.transMp4(mime, m)) { mime = render != null ? render.getVideoMimeType() : RemoteUtil.transMime(); if (FileUtil.isUrl(dlna.getSystemName())) { dlna.setPlayer(new FFmpegWebVideo()); } else { dlna.setPlayer(new FFMpegVideo()); } // code = 206; } if (PMS.getConfiguration().getWebSubs() && dlna.getMediaSubtitle() != null && dlna.getMediaSubtitle().isExternal()) { // fetched on the side sid = dlna.getMediaSubtitle(); dlna.setMediaSubtitle(null); } } if (!RemoteUtil.directmime(mime) && dlna.getFormat().isAudio()) { dlna.setPlayer(new FFmpegAudio()); code = 206; } m.setMimeType(mime); LOGGER.debug("dumping media " + mime + " " + dlna); InputStream in = dlna.getInputStream(range, root.getDefaultRenderer()); Headers hdr = t.getResponseHeaders(); hdr.add("Content-Type", mime); hdr.add("Accept-Ranges", "bytes"); if (range != null) { long end = range.asByteRange().getEnd(); long start = range.asByteRange().getStart(); String rStr = start + "-" + end + "/*"; hdr.add("Content-Range", "bytes " + rStr); if (start != 0) { code = 206; } } hdr.add("Server", PMS.get().getServerName()); hdr.add("Connection", "keep-alive"); t.sendResponseHeaders(code, 0); OutputStream os = t.getResponseBody(); render.start(dlna); if (sid != null) { dlna.setMediaSubtitle(sid); } RemoteUtil.dump(in, os, render); }