def chooseMedia(self, item, forceUpdate=False):
        # If we've already evaluated this item, use our previous choice.
        if not forceUpdate and item.mediaChoice is not None and item.mediaChoice.media is not None and not item.mediaChoice.media.isIndirect(
        ):
            return item.mediaChoice

        # See if we're missing media/stream details for this item.
        if item.isLibraryItem() and item.isVideoItem() and len(
                item.media) > 0 and not item.media[0].hasStreams():
            # TODO(schuyler): Fetch the details
            util.WARN_LOG("Can't make media choice, missing details")

        # Take a first pass through the media items to create an array of candidates
        # that we'll evaluate more completely. If we find a forced item, we use it.
        # If we find an indirect, we only keep a single candidate.
        indirect = False
        candidates = []
        maxResolution = item.settings.getMaxResolution(item.getQualityType())
        for mediaIndex in range(len(item.media)):
            media = item.media[mediaIndex]
            media.mediaIndex = mediaIndex
            if media.isSelected():
                candidates = []
                candidates.append(media)
                break
            if media.isIndirect():
                # Only add indirect media if the resolution fits. We cannot
                # exit early as the user may have selected media.

                indirect = True
                if media.getVideoResolution() <= maxResolution:
                    candidates.append(media)

            elif media.isAccessible():
                # Only consider testing available media
                candidates.append(media)

        # Only use the first indirect media item
        if indirect and candidates:
            candidates = candidates[0]

        # Make sure we have at least one valid item, regardless of availability
        if len(candidates) == 0:
            candidates.append(item.media[0])

        # Now that we have an array of candidates, evaluate them completely.
        choices = []
        for media in candidates:
            choice = None
            if media is not None:
                if item.isVideoItem():
                    choice = self.evaluateMediaVideo(item, media)
                elif item.isMusicItem():
                    choice = self.evaluateMediaMusic(item, media)
                else:
                    choice = mediachoice.MediaChoice(media)
                choices.append(choice)
        item.mediaChoice = self.sortChoices(choices)[-1]
        util.LOG("MDE: MediaChoice: {0}".format(item.mediaChoice))
        return item.mediaChoice
Beispiel #2
0
    def init(self):
        self.isSupported = self.response.server.supportsFeature("streamingBrain")
        self.item = self.response.items[0]

        if self.item and self.item.media:
            self.original.transcodeDecision = mediachoice.MediaChoice(self.item.media[0])

        # Decision codes and text
        self.decisionsCodes = {}
        self.decisionsTexts = {}
        for key in ["directPlayDecision", "generalDecision", "mdeDecision", "transcodeDecision", "termination"]:
            self.decisionsCodes[key] = self.response.container.get(key + "Code", "-1").asInt()
            self.decisionsTexts[key] = self.response.container.get(key + "Text")
    def evaluateMediaMusic(self, item, media):
        # Resolve indirects before doing anything else.
        if media.isIndirect():
            util.LOG("Resolve indirect media for {0}".format(item))
            media = media.resolveIndirect()

        choice = mediachoice.MediaChoice(media)
        if media is None:
            return choice

        # Verify the server supports audio transcoding, otherwise force direct play
        if not item.getServer().supportsAudioTranscoding:
            util.LOG(
                "MDE: force direct play because the server does not support audio transcoding"
            )
            choice.isDirectPlayable = True
            return choice

        # See if this part has a server decision to transcode and obey it
        if choice.part and choice.part.get(
                "decision", serverdecision.ServerDecision.DECISION_DIRECT_PLAY
        ) != serverdecision.ServerDecision.DECISION_DIRECT_PLAY:
            util.WARN_LOG("MDE: Server has decided this cannot direct play")
            return choice

        # Verify the codec and container are compatible
        codec = media.audioCodec
        container = media.get('container')
        canPlayCodec = item.settings.supportsAudioStream(
            codec, media.audioChannels.asInt())
        canPlayContainer = (
            codec == container) or True  # (container in ("mp4", "mka", "mkv"))

        choice.isDirectPlayable = (canPlayCodec and canPlayContainer)
        if choice.isDirectPlayable:
            # Inspect the audio stream attributes if the codec/container can direct
            # play. For now we only need to verify the sample rate.

            if choice.audioStream is not None and choice.audioStream.samplingRate.asInt(
            ) >= 192000:
                util.LOG("MDE: sampling rate is not compatible")
                choice.isDirectPlayable = False
        else:
            util.LOG("MDE: container or codec is incompatible")

        return choice
Beispiel #4
0
    def init(self):
        self.isSupported = self.response.server.supportsFeature(
            "streamingBrain")
        for item in self.response.items:
            util.TEST(item)
            if item and item.media:
                self.item = item
                self.original.transcodeDecision = mediachoice.MediaChoice(
                    self.item.media[0])

        # Decision codes and text
        self.decisionsCodes = {}
        self.decisionsTexts = {}
        for key in [
                "directPlayDecision", "generalDecision", "mdeDecision",
                "transcodeDecision", "termination"
        ]:
            self.decisionsCodes[key] = self.response.container.get(
                key + "Code", "-1").asInt()
            self.decisionsTexts[key] = self.response.container.get(key +
                                                                   "Text")

        util.DEBUG_LOG("Decision codes: {0}".format(self.decisionsCodes))
    def evaluateMediaVideo(self, item, media, partIndex=0):
        # Resolve indirects before doing anything else.
        if media.isIndirect():
            util.LOG("Resolve indirect media for {0}".format(item))
            media = media.resolveIndirect()

        choice = mediachoice.MediaChoice(media, partIndex)
        server = item.getServer()

        if not media:
            return choice

        choice.isSelected = media.isSelected()
        choice.protocol = media.protocol("http")

        maxResolution = item.settings.getMaxResolution(
            item.getQualityType(), self.isSupported4k(media,
                                                      choice.videoStream))
        maxBitrate = item.settings.getMaxBitrate(item.getQualityType())

        choice.resolution = media.getVideoResolution()
        if choice.resolution > maxResolution or media.bitrate.asInt(
        ) > maxBitrate:
            choice.forceTranscode = True

        if choice.subtitleStream:
            choice.subtitleDecision = self.evaluateSubtitles(
                choice.subtitleStream)
            choice.hasBurnedInSubtitles = (
                choice.subtitleDecision != choice.SUBTITLES_SOFT_DP
                and choice.subtitleDecision != choice.SUBTITLES_SOFT_ANY)
        else:
            choice.hasBurnedInSubtitles = False

        # For evaluation purposes, we only care about the first part
        part = media.parts[partIndex]
        if not part:
            return choice

        # Although PMS has already told us which streams are selected, we can't
        # necessarily tell the video player which streams we want. So we need to
        # iterate over the streams and see if there are any red flags that would
        # prevent direct play. If there are multiple video streams, we're hosed.
        # For audio streams, we have a fighting chance if the selected stream can
        # be selected by language, but we need to be careful about guessing which
        # audio stream the Roku will pick for a given language.

        numVideoStreams = 0
        problematicAudioStream = False

        if part.get('hasChapterVideoStream').asBool():
            numVideoStreams = 1

        for stream in part.streams:
            streamType = stream.streamType.asInt()
            if streamType == stream.TYPE_VIDEO:
                numVideoStreams = numVideoStreams + 1

                if stream.codec == "h264" or (
                        stream.codec == "hevc"
                        and item.settings.getPreference("allow_hevc", False)
                ) or (stream.codec == "vp9"
                      and item.settings.getGlobal("vp9Support")):
                    choice.sorts.videoDS = 1

        # Special cases to force direct play
        forceDirectPlay = False
        if choice.protocol == "hls":
            util.LOG("MDE: Assuming HLS is direct playable")
            forceDirectPlay = True
        elif not server.supportsVideoTranscoding:
            # See if we can use another server to transcode, otherwise force direct play
            transcodeServer = item.getTranscodeServer(True, "video")
            if not transcodeServer or not transcodeServer.supportsVideoTranscoding:
                util.LOG(
                    "MDE: force direct play because the server does not support video transcoding"
                )
                forceDirectPlay = True

        # See if we found any red flags based on the streams. Otherwise, go ahead
        # with our codec checks.

        if forceDirectPlay:
            # Consider the choice DP, but continue to allow the
            # choice to have the sorts set properly.
            choice.isDirectPlayable = True
        elif choice.hasBurnedInSubtitles:
            util.LOG("MDE: Need to burn in subtitles")
        elif choice.protocol != "http":
            util.LOG("MDE: " + choice.protocol + " not supported")
        elif numVideoStreams > 1:
            util.LOG("MDE: Multiple video streams, won't try to direct play")
        elif problematicAudioStream:
            util.LOG(
                "MDE: Problematic AAC stream with more than 2 channels prevents direct play"
            )
        elif self.canDirectPlay(item, choice):
            choice.isDirectPlayable = True
        elif item.isMediaSynthesized:
            util.LOG("MDE: assuming synthesized media can direct play")
            choice.isDirectPlayable = True

        # Check for a server decision. This is authority as it's the only playback type
        # the server will allow. This will also support forcing direct play, overriding
        # only our local MDE checks based on the user pref, and only if the server
        # agrees.
        decision = part.get("decision")
        if decision:
            if decision != serverdecision.ServerDecision.DECISION_DIRECT_PLAY:
                util.LOG("MDE: Server has decided this cannot direct play")
                choice.isDirectPlayable = False
            else:
                util.LOG("MDE: Server has allowed direct play")
                choice.isDirectPlayable = True

        # Setup sorts
        if choice.videoStream is not None:
            choice.sorts.bitrate = choice.videoStream.bitrate.asInt()
        elif choice.media is not None:
            choice.sorts.bitrate = choice.media.bitrate.asInt()
        else:
            choice.sorts.bitrate = 0

        if choice.audioStream is not None:
            choice.sorts.audioChannels = choice.audioStream.channels.asInt()
        elif choice.media is not None:
            choice.sorts.audioChannels = choice.media.audioChannels.asInt()
        else:
            choice.sorts.audioChannels = 0

        choice.sorts.videoDS = not (
            choice.sorts.videoDS is None
            or choice.forceTranscode is True) and choice.sorts.videoDS or 0
        choice.sorts.resolution = choice.resolution

        # Server properties probably don't need to be associated with each choice
        choice.sorts.canTranscode = server.supportsVideoTranscoding and 1 or 0
        choice.sorts.canRemuxOnly = server.supportsVideoRemuxOnly and 1 or 0
        choice.sorts.directPlay = (choice.isDirectPlayable is True
                                   and choice.forceTranscode
                                   is not True) and 1 or 0
        choice.sorts.proxyType = choice.media.proxyType and choice.media.proxyType or self.proxyTypes.NORMAL

        return choice