summaryrefslogtreecommitdiff
path: root/youtube_dl/extractor/funimation.py
blob: 107f658baf2c393036dd4d2c770c01258e29e1a9 (plain)
    1 # coding: utf-8
    2 from __future__ import unicode_literals
    3 
    4 from .common import InfoExtractor
    5 from ..compat import compat_HTTPError
    6 from ..utils import (
    7     determine_ext,
    8     int_or_none,
    9     js_to_json,
   10     ExtractorError,
   11     urlencode_postdata
   12 )
   13 
   14 
   15 class FunimationIE(InfoExtractor):
   16     _VALID_URL = r'https?://(?:www\.)?funimation(?:\.com|now\.uk)/shows/[^/]+/(?P<id>[^/?#&]+)'
   17 
   18     _NETRC_MACHINE = 'funimation'
   19     _TOKEN = None
   20 
   21     _TESTS = [{
   22         'url': 'https://www.funimation.com/shows/hacksign/role-play/',
   23         'info_dict': {
   24             'id': '91144',
   25             'display_id': 'role-play',
   26             'ext': 'mp4',
   27             'title': '.hack//SIGN - Role Play',
   28             'description': 'md5:b602bdc15eef4c9bbb201bb6e6a4a2dd',
   29             'thumbnail': r're:https?://.*\.jpg',
   30         },
   31         'params': {
   32             # m3u8 download
   33             'skip_download': True,
   34         },
   35     }, {
   36         'url': 'https://www.funimation.com/shows/attack-on-titan-junior-high/broadcast-dub-preview/',
   37         'info_dict': {
   38             'id': '210051',
   39             'display_id': 'broadcast-dub-preview',
   40             'ext': 'mp4',
   41             'title': 'Attack on Titan: Junior High - Broadcast Dub Preview',
   42             'thumbnail': r're:https?://.*\.(?:jpg|png)',
   43         },
   44         'params': {
   45             # m3u8 download
   46             'skip_download': True,
   47         },
   48     }, {
   49         'url': 'https://www.funimationnow.uk/shows/puzzle-dragons-x/drop-impact/simulcast/',
   50         'only_matching': True,
   51     }]
   52 
   53     def _login(self):
   54         (username, password) = self._get_login_info()
   55         if username is None:
   56             return
   57         try:
   58             data = self._download_json(
   59                 'https://prod-api-funimationnow.dadcdigital.com/api/auth/login/',
   60                 None, 'Logging in', data=urlencode_postdata({
   61                     'username': username,
   62                     'password': password,
   63                 }))
   64             self._TOKEN = data['token']
   65         except ExtractorError as e:
   66             if isinstance(e.cause, compat_HTTPError) and e.cause.code == 401:
   67                 error = self._parse_json(e.cause.read().decode(), None)['error']
   68                 raise ExtractorError(error, expected=True)
   69             raise
   70 
   71     def _real_initialize(self):
   72         self._login()
   73 
   74     def _real_extract(self, url):
   75         display_id = self._match_id(url)
   76         webpage = self._download_webpage(url, display_id)
   77 
   78         def _search_kane(name):
   79             return self._search_regex(
   80                 r"KANE_customdimensions\.%s\s*=\s*'([^']+)';" % name,
   81                 webpage, name, default=None)
   82 
   83         title_data = self._parse_json(self._search_regex(
   84             r'TITLE_DATA\s*=\s*({[^}]+})',
   85             webpage, 'title data', default=''),
   86             display_id, js_to_json, fatal=False) or {}
   87 
   88         video_id = title_data.get('id') or self._search_regex([
   89             r"KANE_customdimensions.videoID\s*=\s*'(\d+)';",
   90             r'<iframe[^>]+src="/player/(\d+)"',
   91         ], webpage, 'video_id', default=None)
   92         if not video_id:
   93             player_url = self._html_search_meta([
   94                 'al:web:url',
   95                 'og:video:url',
   96                 'og:video:secure_url',
   97             ], webpage, fatal=True)
   98             video_id = self._search_regex(r'/player/(\d+)', player_url, 'video id')
   99 
  100         title = episode = title_data.get('title') or _search_kane('videoTitle') or self._og_search_title(webpage)
  101         series = _search_kane('showName')
  102         if series:
  103             title = '%s - %s' % (series, title)
  104         description = self._html_search_meta(['description', 'og:description'], webpage, fatal=True)
  105 
  106         try:
  107             headers = {}
  108             if self._TOKEN:
  109                 headers['Authorization'] = 'Token %s' % self._TOKEN
  110             sources = self._download_json(
  111                 'https://prod-api-funimationnow.dadcdigital.com/api/source/catalog/video/%s/signed/' % video_id,
  112                 video_id, headers=headers)['items']
  113         except ExtractorError as e:
  114             if isinstance(e.cause, compat_HTTPError) and e.cause.code == 403:
  115                 error = self._parse_json(e.cause.read(), video_id)['errors'][0]
  116                 raise ExtractorError('%s said: %s' % (
  117                     self.IE_NAME, error.get('detail') or error.get('title')), expected=True)
  118             raise
  119 
  120         formats = []
  121         for source in sources:
  122             source_url = source.get('src')
  123             if not source_url:
  124                 continue
  125             source_type = source.get('videoType') or determine_ext(source_url)
  126             if source_type == 'm3u8':
  127                 formats.extend(self._extract_m3u8_formats(
  128                     source_url, video_id, 'mp4',
  129                     m3u8_id='hls', fatal=False))
  130             else:
  131                 formats.append({
  132                     'format_id': source_type,
  133                     'url': source_url,
  134                 })
  135         self._sort_formats(formats)
  136 
  137         return {
  138             'id': video_id,
  139             'display_id': display_id,
  140             'title': title,
  141             'description': description,
  142             'thumbnail': self._og_search_thumbnail(webpage),
  143             'series': series,
  144             'season_number': int_or_none(title_data.get('seasonNum') or _search_kane('season')),
  145             'episode_number': int_or_none(title_data.get('episodeNum')),
  146             'episode': episode,
  147             'season_id': title_data.get('seriesId'),
  148             'formats': formats,
  149         }

Generated by cgit