Esempio n. 1
0
def test_ffmpeg_open_vcodec_acodec(session):
    with patch('streamlink.stream.ffmpegmux.which', return_value="ffmpeg"):
        f = FFMPEGMuxer(session, acodec="mp3", vcodec="avc")
        with patch('subprocess.Popen') as popen:
            f.open()
            popen.assert_called_with(['ffmpeg', '-nostats', '-y', '-c:v', 'avc', '-c:a', 'mp3', '-f', 'matroska', 'pipe:1'],
                                     stderr=ANY,
                                     stdout=ANY,
                                     stdin=ANY)
Esempio n. 2
0
def test_ffmpeg_open_copyts(session):
    with patch('streamlink.stream.ffmpegmux.which', return_value="ffmpeg"):
        f = FFMPEGMuxer(session, copyts=True)
        with patch('subprocess.Popen') as popen:
            f.open()
            popen.assert_called_with(['ffmpeg', '-nostats', '-y', '-c:v', FFMPEGMuxer.DEFAULT_VIDEO_CODEC, '-c:a',
                                      FFMPEGMuxer.DEFAULT_AUDIO_CODEC, '-copyts', '-f', 'matroska', 'pipe:1'],
                                     stderr=ANY,
                                     stdout=ANY,
                                     stdin=ANY)
Esempio n. 3
0
def test_ffmpeg_open_metadata_title(session):
    with patch('streamlink.stream.ffmpegmux.which', return_value="ffmpeg"):
        f = FFMPEGMuxer(session, metadata={None: ["title=test"]})
        with patch('subprocess.Popen') as popen:
            f.open()
            popen.assert_called_with(['ffmpeg', '-nostats', '-y', '-c:v', FFMPEGMuxer.DEFAULT_VIDEO_CODEC, '-c:a',
                                      FFMPEGMuxer.DEFAULT_AUDIO_CODEC, '-metadata', 'title=test', '-f', 'matroska', 'pipe:1'],
                                     stderr=ANY,
                                     stdout=ANY,
                                     stdin=ANY)
Esempio n. 4
0
def test_ffmpeg_open_format(session):
    with patch('streamlink.stream.ffmpegmux.which', return_value="ffmpeg"):
        session.options.set("ffmpeg-fout", "avi")
        f = FFMPEGMuxer(session, format="mpegts")
        with patch('subprocess.Popen') as popen:
            f.open()
            popen.assert_called_with(['ffmpeg', '-nostats', '-y', '-c:v', FFMPEGMuxer.DEFAULT_VIDEO_CODEC, '-c:a',
                                      FFMPEGMuxer.DEFAULT_AUDIO_CODEC, '-f', 'avi', 'pipe:1'],
                                     stderr=ANY,
                                     stdout=ANY,
                                     stdin=ANY)
Esempio n. 5
0
def test_ffmpeg_open_vcodec_acodec_user_override(session):
    with patch('streamlink.stream.ffmpegmux.which', return_value="ffmpeg"):
        session.options.set("ffmpeg-video-transcode", "divx")
        session.options.set("ffmpeg-audio-transcode", "ogg")
        f = FFMPEGMuxer(session, acodec="mp3", vcodec="avc")
        with patch('subprocess.Popen') as popen:
            f.open()
            popen.assert_called_with(['ffmpeg', '-nostats', '-y', '-c:v', 'divx', '-c:a', 'ogg', '-f', 'matroska', 'pipe:1'],
                                     stderr=ANY,
                                     stdout=ANY,
                                     stdin=ANY)
Esempio n. 6
0
def test_ffmpeg_open_copyts_enable_start_at_zero_user_override(session):
    with patch('streamlink.stream.ffmpegmux.which', return_value="ffmpeg"):
        session.options.set("ffmpeg-copyts", True)
        f = FFMPEGMuxer(session, copyts=False, start_at_zero=True)
        with patch('subprocess.Popen') as popen:
            f.open()
            popen.assert_called_with(['ffmpeg', '-nostats', '-y', '-c:v', FFMPEGMuxer.DEFAULT_VIDEO_CODEC, '-c:a',
                                      FFMPEGMuxer.DEFAULT_AUDIO_CODEC, '-copyts', '-start_at_zero', '-f', 'matroska', 'pipe:1'],
                                     stderr=ANY,
                                     stdout=ANY,
                                     stdin=ANY)
Esempio n. 7
0
    def open(self):
        if self.video_representation:
            video = DASHStreamReader(self, self.video_representation.id)
            video.open()

        if self.audio_representation:
            audio = DASHStreamReader(self, self.audio_representation.id)
            audio.open()

        if self.video_representation and self.audio_representation:
            return FFMPEGMuxer(self.session, video, audio, copyts=True).open()
        elif self.video_representation:
            return video
        elif self.audio_representation:
            return audio
Esempio n. 8
0
    def parse_variant_playlist(cls, session_, url, name_key="name",
                               name_prefix="", check_streams=False,
                               force_restart=False, name_fmt=None,
                               start_offset=0, duration=None,
                               **request_params):
        """Attempts to parse a variant playlist and return its streams.

        :param url: The URL of the variant playlist.
        :param name_key: Prefer to use this key as stream name, valid keys are:
                         name, pixels, bitrate.
        :param name_prefix: Add this prefix to the stream names.
        :param check_streams: Only allow streams that are accessible.
        :param force_restart: Start at the first segment even for a live stream
        :param name_fmt: A format string for the name, allowed format keys are
                         name, pixels, bitrate.
        """
        locale = session_.localization
        # Backwards compatibility with "namekey" and "nameprefix" params.
        name_key = request_params.pop("namekey", name_key)
        name_prefix = request_params.pop("nameprefix", name_prefix)
        audio_select = session_.options.get("hls-audio-select") or []

        res = session_.http.get(url, exception=IOError, **request_params)

        try:
            parser = cls._get_variant_playlist(res)
        except ValueError as err:
            raise IOError("Failed to parse playlist: {0}".format(err))

        streams = OrderedDict()
        for playlist in filter(lambda p: not p.is_iframe, parser.playlists):
            names = dict(name=None, pixels=None, bitrate=None)
            audio_streams = []
            fallback_audio = []
            default_audio = []
            preferred_audio = []
            for media in playlist.media:
                if media.type == "VIDEO" and media.name:
                    names["name"] = media.name
                elif media.type == "AUDIO":
                    audio_streams.append(media)
            for media in audio_streams:
                # Media without a uri is not relevant as external audio
                if not media.uri:
                    continue

                if not fallback_audio and media.default:
                    fallback_audio = [media]

                # if the media is "audoselect" and it better matches the users preferences, use that
                # instead of default
                if not default_audio and (media.autoselect and locale.equivalent(language=media.language)):
                    default_audio = [media]

                # select the first audio stream that matches the users explict language selection
                if (('*' in audio_select or media.language in audio_select or media.name in audio_select)
                        or ((not preferred_audio or media.default) and locale.explicit and locale.equivalent(
                            language=media.language))):
                    preferred_audio.append(media)

            # final fallback on the first audio stream listed
            fallback_audio = fallback_audio or (len(audio_streams) and audio_streams[0].uri and [audio_streams[0]])

            if playlist.stream_info.resolution:
                width, height = playlist.stream_info.resolution
                names["pixels"] = "{0}p".format(height)

            if playlist.stream_info.bandwidth:
                bw = playlist.stream_info.bandwidth

                if bw >= 1000:
                    names["bitrate"] = "{0}k".format(int(bw / 1000.0))
                else:
                    names["bitrate"] = "{0}k".format(bw / 1000.0)

            if name_fmt:
                stream_name = name_fmt.format(**names)
            else:
                stream_name = (
                    names.get(name_key)
                    or names.get("name")
                    or names.get("pixels")
                    or names.get("bitrate")
                )

            if not stream_name:
                continue
            if name_prefix:
                stream_name = "{0}{1}".format(name_prefix, stream_name)

            if stream_name in streams:  # rename duplicate streams
                stream_name = "{0}_alt".format(stream_name)
                num_alts = len(list(filter(lambda n: n.startswith(stream_name), streams.keys())))

                # We shouldn't need more than 2 alt streams
                if num_alts >= 2:
                    continue
                elif num_alts > 0:
                    stream_name = "{0}{1}".format(stream_name, num_alts + 1)

            if check_streams:
                try:
                    session_.http.get(playlist.uri, **request_params)
                except KeyboardInterrupt:
                    raise
                except Exception:
                    continue

            external_audio = preferred_audio or default_audio or fallback_audio

            if external_audio and FFMPEGMuxer.is_usable(session_):
                external_audio_msg = u", ".join([
                    u"(language={0}, name={1})".format(x.language, (x.name or "N/A"))
                    for x in external_audio
                ])
                log.debug(u"Using external audio tracks for stream {0} {1}".format(
                          stream_name, external_audio_msg))

                stream = MuxedHLSStream(session_,
                                        video=playlist.uri,
                                        audio=[x.uri for x in external_audio if x.uri],
                                        force_restart=force_restart,
                                        start_offset=start_offset,
                                        duration=duration,
                                        **request_params)
            else:
                stream = cls(session_,
                             playlist.uri,
                             force_restart=force_restart,
                             start_offset=start_offset,
                             duration=duration,
                             **request_params)
            streams[stream_name] = stream

        return streams
Esempio n. 9
0
    def parse_variant_playlist(
            cls,
            session_,
            url: str,
            name_key: str = "name",
            name_prefix: str = "",
            check_streams: bool = False,
            force_restart: bool = False,
            name_fmt: Optional[str] = None,
            start_offset: float = 0,
            duration: Optional[float] = None,
            **request_params
    ) -> Dict[str, Union["HLSStream", "MuxedHLSStream"]]:
        """
        Parse a variant playlist and return its streams.

        :param streamlink.Streamlink session_: Streamlink session instance
        :param url: The URL of the variant playlist
        :param name_key: Prefer to use this key as stream name, valid keys are: name, pixels, bitrate
        :param name_prefix: Add this prefix to the stream names
        :param check_streams: Only allow streams that are accessible
        :param force_restart: Start at the first segment even for a live stream
        :param name_fmt: A format string for the name, allowed format keys are: name, pixels, bitrate
        :param start_offset: Number of seconds to be skipped from the beginning
        :param duration: Number of second until ending the stream
        :param request_params: Additional keyword arguments passed to :class:`HLSStream`, :class:`MuxedHLSStream`,
                               or :py:meth:`requests.Session.request`
        """

        locale = session_.localization
        audio_select = session_.options.get("hls-audio-select") or []

        res = session_.http.get(url, exception=IOError, **request_params)

        try:
            parser = cls._get_variant_playlist(res)
        except ValueError as err:
            raise OSError("Failed to parse playlist: {0}".format(err))

        streams = OrderedDict()
        for playlist in filter(lambda p: not p.is_iframe, parser.playlists):
            names = dict(name=None, pixels=None, bitrate=None)
            audio_streams = []
            fallback_audio = []
            default_audio = []
            preferred_audio = []
            for media in playlist.media:
                if media.type == "VIDEO" and media.name:
                    names["name"] = media.name
                elif media.type == "AUDIO":
                    audio_streams.append(media)
            for media in audio_streams:
                # Media without a uri is not relevant as external audio
                if not media.uri:
                    continue

                if not fallback_audio and media.default:
                    fallback_audio = [media]

                # if the media is "audoselect" and it better matches the users preferences, use that
                # instead of default
                if not default_audio and (
                        media.autoselect
                        and locale.equivalent(language=media.language)):
                    default_audio = [media]

                # select the first audio stream that matches the users explict language selection
                if (('*' in audio_select or media.language in audio_select
                     or media.name in audio_select) or
                    ((not preferred_audio or media.default) and locale.explicit
                     and locale.equivalent(language=media.language))):
                    preferred_audio.append(media)

            # final fallback on the first audio stream listed
            fallback_audio = fallback_audio or (len(audio_streams)
                                                and audio_streams[0].uri
                                                and [audio_streams[0]])

            if playlist.stream_info.resolution:
                width, height = playlist.stream_info.resolution
                names["pixels"] = "{0}p".format(height)

            if playlist.stream_info.bandwidth:
                bw = playlist.stream_info.bandwidth

                if bw >= 1000:
                    names["bitrate"] = "{0}k".format(int(bw / 1000.0))
                else:
                    names["bitrate"] = "{0}k".format(bw / 1000.0)

            if name_fmt:
                stream_name = name_fmt.format(**names)
            else:
                stream_name = (names.get(name_key) or names.get("name")
                               or names.get("pixels") or names.get("bitrate"))

            if not stream_name:
                continue
            if name_prefix:
                stream_name = "{0}{1}".format(name_prefix, stream_name)

            if stream_name in streams:  # rename duplicate streams
                stream_name = "{0}_alt".format(stream_name)
                num_alts = len(
                    list(
                        filter(lambda n: n.startswith(stream_name),
                               streams.keys())))

                # We shouldn't need more than 2 alt streams
                if num_alts >= 2:
                    continue
                elif num_alts > 0:
                    stream_name = "{0}{1}".format(stream_name, num_alts + 1)

            if check_streams:
                try:
                    session_.http.get(playlist.uri, **request_params)
                except KeyboardInterrupt:
                    raise
                except Exception:
                    continue

            external_audio = preferred_audio or default_audio or fallback_audio

            if external_audio and FFMPEGMuxer.is_usable(session_):
                external_audio_msg = ", ".join([
                    f"(language={x.language}, name={x.name or 'N/A'})"
                    for x in external_audio
                ])
                log.debug(
                    f"Using external audio tracks for stream {stream_name} {external_audio_msg}"
                )

                stream = MuxedHLSStream(
                    session_,
                    video=playlist.uri,
                    audio=[x.uri for x in external_audio if x.uri],
                    url_master=url,
                    force_restart=force_restart,
                    start_offset=start_offset,
                    duration=duration,
                    **request_params)
            else:
                stream = cls(session_,
                             playlist.uri,
                             url_master=url,
                             force_restart=force_restart,
                             start_offset=start_offset,
                             duration=duration,
                             **request_params)
            streams[stream_name] = stream

        return streams
Esempio n. 10
0
def test_ffmpeg_command(session):
    with patch('streamlink.stream.ffmpegmux.which', return_value="ffmpeg"):
        assert FFMPEGMuxer.command(session) == "ffmpeg"
Esempio n. 11
0
    def _get_streams(self):
        page = http.get(self.url, schema=_schema)
        if not page:
            return

        pubkey_pem = get_public_key(self.cache, urljoin(self.url, page["clientlibs"]))
        if not pubkey_pem:
            raise PluginError("Unable to get public key")

        flashvars = page["flashvars"]

        params = {
            "cashPath": int(time.time() * 1000)
        }
        res = http.get(urljoin(self.url, flashvars["country"]), params=params)
        if not res:
            return
        language = http.xml(res, schema=_language_schema)

        api_params = {}
        for key in ("ss_id", "mv_id", "device_cd", "ss1_prm", "ss2_prm", "ss3_prm"):
            if flashvars.get(key, ""):
                api_params[key] = flashvars[key]

        aeskey = number.long_to_bytes(random.getrandbits(8 * 32), 32)

        params = {
            "s": flashvars["s"],
            "c": language,
            "e": self.url,
            "d": aes_encrypt(aeskey, json.dumps(api_params)),
            "a": rsa_encrypt(pubkey_pem, aeskey)
        }
        res = http.get(urljoin(self.url, flashvars["init"]), params=params)
        if not res:
            return
        rtn = http.json(res, schema=_init_schema)
        if not rtn:
            return

        init_data = parse_json(aes_decrypt(aeskey, rtn))

        parsed = urlparse(init_data["play_url"])
        if parsed.scheme != "https" or not parsed.path.startswith("/i/") or not parsed.path.endswith("/master.m3u8"):
            return
        hlsstream_url = init_data["play_url"]

        streams = HLSStream.parse_variant_playlist(self.session, hlsstream_url)

        if "caption_url" in init_data:
            if self.get_option("mux_subtitles") and FFMPEGMuxer.is_usable(self.session):
                res = http.get(init_data["caption_url"])
                srt = http.xml(res, ignore_ns=True, schema=_xml_to_srt_schema)
                subfiles = []
                metadata = {}
                for i, lang, srt in ((i, s[0], s[1]) for i, s in enumerate(srt)):
                    subfile = tempfile.TemporaryFile()
                    subfile.write(srt.encode("utf8"))
                    subfile.seek(0)
                    subfiles.append(FileStream(self.session, fileobj=subfile))
                    metadata["s:s:{0}".format(i)] = ["language={0}".format(lang)]

                for n, s in streams.items():
                    yield n, MuxedStream(self.session, s, *subfiles,
                                         maps=list(range(0, len(metadata) + 1)),
                                         metadata=metadata)
                return
            else:
                self.logger.info("Subtitles: {0}".format(init_data["caption_url"]))

        for s in streams.items():
            yield s
Esempio n. 12
0
    def parse_variant_playlist(cls,
                               session_,
                               url,
                               name_key="name",
                               name_prefix="",
                               check_streams=False,
                               force_restart=False,
                               **request_params):
        """Attempts to parse a variant playlist and return its streams.

        :param url: The URL of the variant playlist.
        :param name_key: Prefer to use this key as stream name, valid keys are:
                         name, pixels, bitrate.
        :param name_prefix: Add this prefix to the stream names.
        :param force_restart: Start at the first segment even for a live stream
        :param check_streams: Only allow streams that are accesible.
        """
        logger = session_.logger.new_module("hls.parse_variant_playlist")
        locale = session_.localization
        # Backwards compatibility with "namekey" and "nameprefix" params.
        name_key = request_params.pop("namekey", name_key)
        name_prefix = request_params.pop("nameprefix", name_prefix)

        res = session_.http.get(url, exception=IOError, **request_params)

        try:
            parser = hls_playlist.load(res.text, base_uri=res.url)
        except ValueError as err:
            raise IOError("Failed to parse playlist: {0}".format(err))

        streams = {}
        for playlist in filter(lambda p: not p.is_iframe, parser.playlists):
            names = dict(name=None, pixels=None, bitrate=None)
            fallback_audio = None
            default_audio = None
            preferred_audio = None

            for media in playlist.media:
                if media.type == "VIDEO" and media.name:
                    names["name"] = media.name
                elif media.type == "AUDIO":
                    if not fallback_audio and media.default:
                        fallback_audio = media

                    # if the media is "audoselect" and it better matches the users preferences, use that
                    # instead of default
                    if not default_audio and (
                            media.autoselect
                            and locale.equivalent(language=media.language)):
                        default_audio = media

                    # select the first audio stream that matches the users explict language selection
                    if (not preferred_audio or media.default
                        ) and locale.explicit and locale.equivalent(
                            language=media.language):
                        preferred_audio = media

            if playlist.stream_info.resolution:
                width, height = playlist.stream_info.resolution
                names["pixels"] = "{0}p".format(height)

            if playlist.stream_info.bandwidth:
                bw = playlist.stream_info.bandwidth

                if bw >= 1000:
                    names["bitrate"] = "{0}k".format(int(bw / 1000.0))
                else:
                    names["bitrate"] = "{0}k".format(bw / 1000.0)

            stream_name = (names.get(name_key) or names.get("name")
                           or names.get("pixels") or names.get("bitrate"))

            if not stream_name:
                continue
            if stream_name in streams:  # rename duplicate streams
                stream_name = "{0}_alt".format(stream_name)
                num_alts = len(
                    list(
                        filter(lambda n: n.startswith(stream_name),
                               streams.keys())))

                # We shouldn't need more than 2 alt streams
                if num_alts >= 2:
                    continue
                elif num_alts > 0:
                    stream_name = "{0}{1}".format(stream_name, num_alts + 1)

            if check_streams:
                try:
                    session_.http.get(playlist.uri, **request_params)
                except KeyboardInterrupt:
                    raise
                except Exception:
                    continue

            external_audio = preferred_audio or default_audio or fallback_audio
            if external_audio and external_audio.uri and FFMPEGMuxer.is_usable(
                    session_):
                logger.debug(
                    "Using external audio track for stream {0} (language={1}, name={2})"
                    .format(name_prefix + stream_name, external_audio.language,
                            external_audio.name or "N/A"))

                stream = MuxedHLSStream(session_,
                                        video=playlist.uri,
                                        audio=external_audio
                                        and external_audio.uri,
                                        force_restart=force_restart,
                                        **request_params)
            else:
                stream = HLSStream(session_,
                                   playlist.uri,
                                   force_restart=force_restart,
                                   **request_params)
            streams[name_prefix + stream_name] = stream

        return streams
Esempio n. 13
0
    def parse_variant_playlist(cls, session_, url, name_key="name",
                               name_prefix="", check_streams=False,
                               force_restart=False, name_fmt=None,
                               start_offset=0, duration=None,
                               **request_params):
        """Attempts to parse a variant playlist and return its streams.

        :param url: The URL of the variant playlist.
        :param name_key: Prefer to use this key as stream name, valid keys are:
                         name, pixels, bitrate.
        :param name_prefix: Add this prefix to the stream names.
        :param check_streams: Only allow streams that are accessible.
        :param force_restart: Start at the first segment even for a live stream
        :param name_fmt: A format string for the name, allowed format keys are
                         name, pixels, bitrate.
        """
        locale = session_.localization
        # Backwards compatibility with "namekey" and "nameprefix" params.
        name_key = request_params.pop("namekey", name_key)
        name_prefix = request_params.pop("nameprefix", name_prefix)
        audio_select = session_.options.get("hls-audio-select") or []

        res = session_.http.get(url, exception=IOError, **request_params)

        try:
            parser = hls_playlist.load(res.text, base_uri=res.url)
        except ValueError as err:
            raise IOError("Failed to parse playlist: {0}".format(err))

        streams = {}
        for playlist in filter(lambda p: not p.is_iframe, parser.playlists):
            names = dict(name=None, pixels=None, bitrate=None)
            audio_streams = []
            fallback_audio = []
            default_audio = []
            preferred_audio = []
            for media in playlist.media:
                if media.type == "VIDEO" and media.name:
                    names["name"] = media.name
                elif media.type == "AUDIO":
                    audio_streams.append(media)
            for media in audio_streams:
                # Media without a uri is not relevant as external audio
                if not media.uri:
                    continue

                if not fallback_audio and media.default:
                    fallback_audio = [media]

                # if the media is "audoselect" and it better matches the users preferences, use that
                # instead of default
                if not default_audio and (media.autoselect and locale.equivalent(language=media.language)):
                    default_audio = [media]

                # select the first audio stream that matches the users explict language selection
                if (('*' in audio_select or media.language in audio_select or media.name in audio_select) or
                        ((not preferred_audio or media.default) and locale.explicit and locale.equivalent(
                            language=media.language))):
                    preferred_audio.append(media)

            # final fallback on the first audio stream listed
            fallback_audio = fallback_audio or (len(audio_streams) and
                                                audio_streams[0].uri and [audio_streams[0]])

            if playlist.stream_info.resolution:
                width, height = playlist.stream_info.resolution
                names["pixels"] = "{0}p".format(height)

            if playlist.stream_info.bandwidth:
                bw = playlist.stream_info.bandwidth

                if bw >= 1000:
                    names["bitrate"] = "{0}k".format(int(bw / 1000.0))
                else:
                    names["bitrate"] = "{0}k".format(bw / 1000.0)

            if name_fmt:
                stream_name = name_fmt.format(**names)
            else:
                stream_name = (names.get(name_key) or names.get("name") or
                               names.get("pixels") or names.get("bitrate"))

            if not stream_name:
                continue
            if stream_name in streams:  # rename duplicate streams
                stream_name = "{0}_alt".format(stream_name)
                num_alts = len(list(filter(lambda n: n.startswith(stream_name), streams.keys())))

                # We shouldn't need more than 2 alt streams
                if num_alts >= 2:
                    continue
                elif num_alts > 0:
                    stream_name = "{0}{1}".format(stream_name, num_alts + 1)

            if check_streams:
                try:
                    session_.http.get(playlist.uri, **request_params)
                except KeyboardInterrupt:
                    raise
                except Exception:
                    continue

            external_audio = preferred_audio or default_audio or fallback_audio

            if external_audio and FFMPEGMuxer.is_usable(session_):
                external_audio_msg = ", ".join([
                    "(language={0}, name={1})".format(x.language, (x.name or "N/A"))
                    for x in external_audio
                ])
                log.debug("Using external audio tracks for stream {0} {1}", name_prefix + stream_name,
                          external_audio_msg)

                stream = MuxedHLSStream(session_,
                                        video=playlist.uri,
                                        audio=[x.uri for x in external_audio if x.uri],
                                        force_restart=force_restart,
                                        start_offset=start_offset,
                                        duration=duration,
                                        **request_params)
            else:
                stream = HLSStream(session_, playlist.uri, force_restart=force_restart,
                                   start_offset=start_offset, duration=duration, **request_params)
            streams[name_prefix + stream_name] = stream

        return streams