plugins.funimationnow: deleted

This commit is contained in:
back-to 2023-01-29 11:17:47 +01:00 committed by Sebastian Meyer
parent 25c04a6b26
commit 39dd8f898e
4 changed files with 0 additions and 342 deletions

View File

@ -4,5 +4,4 @@ Plugin specific usage
.. toctree::
plugins/crunchyroll
plugins/funimationnow
plugins/twitch

View File

@ -1,21 +0,0 @@
FunimationNow
=============
Authentication
--------------
Like Crunchyroll, the FunimationNow plugin requires authenticating with a premium account to access some
content: :option:`--funimation-email`, :option:`--funimation-password`. In addition, this plugin requires
the ``incap_ses`` cookie to be sent with each HTTP request (see issue #2088). This unique session cookie
can be found in your browser and sent via the :option:`--http-cookie` option.
.. code-block:: console
$ streamlink --funimation-email='xxx' --funimation-password='xxx' --http-cookie 'incap_ses_xxx=xxxx=' https://funimation.com/shows/show/an-episode-link
.. note::
There are multiple ways to retrieve the required cookie. For more
information on browser cookies, please consult the following:
- `What are cookies? <https://en.wikipedia.org/wiki/HTTP_cookie>`_

View File

@ -1,280 +0,0 @@
"""
$description Anime video on-demand service.
$url funimation.com
$url funimationnow.uk
$type vod
$notes :ref:`Requires session cookies <cli/plugins/funimationnow:Authentication>`
"""
import logging
import random
import re
from streamlink.plugin import Plugin, pluginargument, pluginmatcher
from streamlink.plugin.api import useragents, validate
from streamlink.stream.ffmpegmux import MuxedStream
from streamlink.stream.hls import HLSStream
from streamlink.stream.http import HTTPStream
from streamlink.utils.l10n import Localization
log = logging.getLogger(__name__)
class Experience:
CSRF_NAME = "csrfmiddlewaretoken"
login_url = "https://www.funimation.com/log-in/"
api_base = "https://www.funimation.com/api"
login_api_url = "https://prod-api-funimationnow.dadcdigital.com/api/auth/login/"
show_api_url = api_base + "/experience/{experience_id}/"
sources_api_url = api_base + "/showexperience/{experience_id}/"
languages = ["english", "japanese"]
alphas = ["uncut", "simulcast"]
login_schema = validate.Schema(validate.any(
{"success": False,
"error": str},
{"token": str,
"user": {"id": int}}
))
def __init__(self, session, experience_id):
"""
:param session: streamlink session
:param experience_id: starting experience_id, may be changed later
"""
self.session = session
self.experience_id = experience_id
self._language = None
self.cache = {}
self.token = None
def request(self, method, url, *args, **kwargs):
headers = kwargs.pop("headers", {})
if self.token:
headers.update({"Authorization": "Token {0}".format(self.token)})
self.session.http.cookies.update({"src_token": self.token})
log.debug("Making {0}request to {1}".format("authorized " if self.token else "", url))
res = self.session.http.request(method, url, *args, headers=headers, **kwargs)
if "_Incapsula_Resource" in res.text:
log.error(
"This page is protected by Incapsula, please see "
"https://github.com/streamlink/streamlink/issues/2088"
" for a workaround."
)
return
return res
def get(self, *args, **kwargs):
return self.request("GET", *args, **kwargs)
def post(self, *args, **kwargs):
return self.request("POST", *args, **kwargs)
@property
def pinst_id(self):
return ''.join([
random.choice("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") for _ in range(8)
])
def _update(self):
api_url = self.show_api_url.format(experience_id=self.experience_id)
log.debug("Requesting experience data: {0}".format(api_url))
res = self.get(api_url)
if res:
data = self.session.http.json(res)
self.cache[self.experience_id] = data
@property
def show_info(self):
if self.experience_id not in self.cache:
self._update()
return self.cache.get(self.experience_id)
@property
def episode_info(self):
"""
Search for the episode with the requested experience Id
:return:
"""
if self.show_info:
for season in self.show_info["seasons"]:
for episode in season["episodes"]:
for lang in episode["languages"].values():
for alpha in lang["alpha"].values():
if alpha["experienceId"] == self.experience_id:
return episode
@property
def language(self):
for language, lang_data in self.episode_info["languages"].items():
for alpha in lang_data["alpha"].values():
if alpha["experienceId"] == self.experience_id:
return language
@property
def language_code(self):
return {"english": "eng", "japanese": "jpn"}[self.language]
def set_language(self, language):
if language in self.episode_info["languages"]:
for alpha in self.episode_info["languages"][language]["alpha"].values():
self.experience_id = alpha["experienceId"]
def _get_alpha(self):
for lang_data in self.episode_info["languages"].values():
for alpha in lang_data["alpha"].values():
if alpha["experienceId"] == self.experience_id:
return alpha
def subtitles(self):
alpha = self._get_alpha()
for src in alpha["sources"]:
return src["textTracks"]
def sources(self):
"""
Get the sources for a given experience_id, which is tied to a specific language
:param experience_id: int; video content id
:return: sources dict
"""
api_url = self.sources_api_url.format(experience_id=self.experience_id)
res = self.get(api_url, params={"pinst_id": self.pinst_id})
return self.session.http.json(res)
def login_csrf(self):
return self.session.http.get(self.login_url, schema=validate.Schema(
validate.parse_html(),
validate.xml_xpath_string(".//input[@name=$name][1]/@value", name=self.CSRF_NAME)
))
def login(self, email, password):
log.debug("Attempting to login as {0}".format(email))
r = self.post(self.login_api_url,
data={'username': email, 'password': password, self.CSRF_NAME: self.login_csrf()},
raise_for_status=False,
headers={"Referer": "https://www.funimation.com/log-in/"})
d = self.session.http.json(r, schema=self.login_schema)
self.token = d.get("token", None)
return self.token is not None
@pluginmatcher(re.compile(
r"https?://(?:www\.)?funimation(\.com|now\.uk)"
))
@pluginargument(
"email",
argument_name="funimation-email",
requires=["password"],
help="Email address for your Funimation account.",
)
@pluginargument(
"password",
argument_name="funimation-password",
sensitive=True,
help="Password for your Funimation account.",
)
@pluginargument(
"language",
argument_name="funimation-language",
choices=["en", "ja", "english", "japanese"],
default="english",
help="""
The audio language to use for the stream; japanese or english.
Default is "english".
""",
)
@pluginargument(
"mux-subtitles",
is_global=True,
)
class FunimationNow(Plugin):
experience_id_re = re.compile(r"/player/(\d+)")
mp4_quality = "480p"
def _get_streams(self):
self.session.http.headers = {"User-Agent": useragents.CHROME}
res = self.session.http.get(self.url)
# remap en to english, and ja to japanese
rlanguage = {"en": "english", "ja": "japanese"}.get(self.get_option("language").lower(),
self.get_option("language").lower())
if "_Incapsula_Resource" in res.text:
log.error(
"This page is protected by Incapsula, please see "
"https://github.com/streamlink/streamlink/issues/2088"
" for a workaround."
)
return
if "Out of Territory" in res.text:
log.error("The content requested is not available in your territory.")
return
id_m = self.experience_id_re.search(res.text)
experience_id = id_m and int(id_m.group(1))
if experience_id:
log.debug(f"Found experience ID: {experience_id}")
exp = Experience(self.session, experience_id)
if self.get_option("email") and self.get_option("password"):
if exp.login(self.get_option("email"), self.get_option("password")):
log.info(f"Logged in to Funimation as {self.get_option('email')}")
else:
log.warning("Failed to login")
if exp.episode_info:
log.debug(f"Found episode: {exp.episode_info['episodeTitle']}")
log.debug(f" has languages: {', '.join(exp.episode_info['languages'].keys())}")
log.debug(f" requested language: {rlanguage}")
log.debug(f" current language: {exp.language}")
if rlanguage != exp.language:
log.debug(f"switching language to: {rlanguage}")
exp.set_language(rlanguage)
if exp.language != rlanguage:
log.warning(f"Requested language {rlanguage} is not available, continuing with {exp.language}")
else:
log.debug(f"New experience ID: {exp.experience_id}")
subtitles = None
stream_metadata = {}
disposition = {}
for subtitle in exp.subtitles():
log.debug(f"Subtitles: {subtitle['src']}")
if subtitle["src"].endswith(".vtt") or subtitle["src"].endswith(".srt"):
sub_lang = Localization.get_language(subtitle["language"]).alpha3
# pick the first suitable subtitle stream
subtitles = subtitles or HTTPStream(self.session, subtitle["src"])
stream_metadata["s:s:0"] = ["language={0}".format(sub_lang)]
stream_metadata["s:a:0"] = ["language={0}".format(exp.language_code)]
sources = exp.sources()
if 'errors' in sources:
for error in sources['errors']:
log.error("{0} : {1}".format(error['title'], error['detail']))
return
for item in sources["items"]:
url = item["src"]
if ".m3u8" in url:
for q, s in HLSStream.parse_variant_playlist(self.session, url).items():
if self.get_option("mux_subtitles") and subtitles:
yield q, MuxedStream(self.session, s, subtitles, metadata=stream_metadata,
disposition=disposition)
else:
yield q, s
elif ".mp4" in url:
# TODO: fix quality
s = HTTPStream(self.session, url)
if self.get_option("mux_subtitles") and subtitles:
yield self.mp4_quality, MuxedStream(self.session, s, subtitles, metadata=stream_metadata,
disposition=disposition)
else:
yield self.mp4_quality, s
else:
log.error("Could not find experience ID?!")
__plugin__ = FunimationNow

View File

@ -1,40 +0,0 @@
import unittest
from unittest.mock import ANY, MagicMock, call
from streamlink import Streamlink
from streamlink.plugins.funimationnow import FunimationNow
from tests.plugins import PluginCanHandleUrl
class TestPluginCanHandleUrlFunimationNow(PluginCanHandleUrl):
__plugin__ = FunimationNow
should_match = [
"http://www.funimation.com/anything",
"http://www.funimation.com/anything123",
"http://www.funimationnow.uk/anything",
"http://www.funimationnow.uk/anything123",
]
class TestPluginFunimationNow(unittest.TestCase):
def test_arguments(self):
from streamlink_cli.main import setup_plugin_args
session = Streamlink()
parser = MagicMock()
plugins = parser.add_argument_group("Plugin Options")
group = parser.add_argument_group("FunimationNow", parent=plugins)
session.plugins = {
'funimationnow': FunimationNow
}
setup_plugin_args(session, parser)
self.assertSequenceEqual(
group.add_argument.mock_calls,
[
call('--funimation-email', help=ANY),
call('--funimation-password', help=ANY),
call('--funimation-language', choices=["en", "ja", "english", "japanese"], default="english", help=ANY)
]
)