]> jfr.im git - yt-dlp.git/blob - yt_dlp/extractor/amcnetworks.py
Ensure `mergeall` selects best format when multistreams are disabled
[yt-dlp.git] / yt_dlp / extractor / amcnetworks.py
1 # coding: utf-8
2 from __future__ import unicode_literals
3
4 import re
5
6 from .theplatform import ThePlatformIE
7 from ..utils import (
8 int_or_none,
9 parse_age_limit,
10 try_get,
11 update_url_query,
12 )
13
14
15 class AMCNetworksIE(ThePlatformIE):
16 _VALID_URL = r'https?://(?:www\.)?(?P<site>amc|bbcamerica|ifc|(?:we|sundance)tv)\.com/(?P<id>(?:movies|shows(?:/[^/]+)+)/[^/?#&]+)'
17 _TESTS = [{
18 'url': 'https://www.bbcamerica.com/shows/the-graham-norton-show/videos/tina-feys-adorable-airline-themed-family-dinner--51631',
19 'info_dict': {
20 'id': '4Lq1dzOnZGt0',
21 'ext': 'mp4',
22 'title': "The Graham Norton Show - Season 28 - Tina Fey's Adorable Airline-Themed Family Dinner",
23 'description': "It turns out child stewardesses are very generous with the wine! All-new episodes of 'The Graham Norton Show' premiere Fridays at 11/10c on BBC America.",
24 'upload_date': '20201120',
25 'timestamp': 1605904350,
26 'uploader': 'AMCN',
27 },
28 'params': {
29 # m3u8 download
30 'skip_download': True,
31 },
32 }, {
33 'url': 'http://www.bbcamerica.com/shows/the-hunt/full-episodes/season-1/episode-01-the-hardest-challenge',
34 'only_matching': True,
35 }, {
36 'url': 'http://www.amc.com/shows/preacher/full-episodes/season-01/episode-00/pilot',
37 'only_matching': True,
38 }, {
39 'url': 'http://www.wetv.com/shows/million-dollar-matchmaker/season-01/episode-06-the-dumped-dj-and-shallow-hal',
40 'only_matching': True,
41 }, {
42 'url': 'http://www.ifc.com/movies/chaos',
43 'only_matching': True,
44 }, {
45 'url': 'http://www.bbcamerica.com/shows/doctor-who/full-episodes/the-power-of-the-daleks/episode-01-episode-1-color-version',
46 'only_matching': True,
47 }, {
48 'url': 'http://www.wetv.com/shows/mama-june-from-not-to-hot/full-episode/season-01/thin-tervention',
49 'only_matching': True,
50 }, {
51 'url': 'http://www.wetv.com/shows/la-hair/videos/season-05/episode-09-episode-9-2/episode-9-sneak-peek-3',
52 'only_matching': True,
53 }, {
54 'url': 'https://www.sundancetv.com/shows/riviera/full-episodes/season-1/episode-01-episode-1',
55 'only_matching': True,
56 }]
57 _REQUESTOR_ID_MAP = {
58 'amc': 'AMC',
59 'bbcamerica': 'BBCA',
60 'ifc': 'IFC',
61 'sundancetv': 'SUNDANCE',
62 'wetv': 'WETV',
63 }
64
65 def _real_extract(self, url):
66 site, display_id = re.match(self._VALID_URL, url).groups()
67 requestor_id = self._REQUESTOR_ID_MAP[site]
68 page_data = self._download_json(
69 'https://content-delivery-gw.svc.ds.amcn.com/api/v2/content/amcn/%s/url/%s'
70 % (requestor_id.lower(), display_id), display_id)['data']
71 properties = page_data.get('properties') or {}
72 query = {
73 'mbr': 'true',
74 'manifest': 'm3u',
75 }
76
77 video_player_count = 0
78 try:
79 for v in page_data['children']:
80 if v.get('type') == 'video-player':
81 releasePid = v['properties']['currentVideo']['meta']['releasePid']
82 tp_path = 'M_UwQC/' + releasePid
83 media_url = 'https://link.theplatform.com/s/' + tp_path
84 video_player_count += 1
85 except KeyError:
86 pass
87 if video_player_count > 1:
88 self.report_warning(
89 'The JSON data has %d video players. Only one will be extracted' % video_player_count)
90
91 # Fall back to videoPid if releasePid not found.
92 # TODO: Fall back to videoPid if releasePid manifest uses DRM.
93 if not video_player_count:
94 tp_path = 'M_UwQC/media/' + properties['videoPid']
95 media_url = 'https://link.theplatform.com/s/' + tp_path
96
97 theplatform_metadata = self._download_theplatform_metadata(tp_path, display_id)
98 info = self._parse_theplatform_metadata(theplatform_metadata)
99 video_id = theplatform_metadata['pid']
100 title = theplatform_metadata['title']
101 rating = try_get(
102 theplatform_metadata, lambda x: x['ratings'][0]['rating'])
103 video_category = properties.get('videoCategory')
104 if video_category and video_category.endswith('-Auth'):
105 resource = self._get_mvpd_resource(
106 requestor_id, title, video_id, rating)
107 query['auth'] = self._extract_mvpd_auth(
108 url, video_id, requestor_id, resource)
109 media_url = update_url_query(media_url, query)
110 formats, subtitles = self._extract_theplatform_smil(
111 media_url, video_id)
112 self._sort_formats(formats)
113
114 thumbnails = []
115 thumbnail_urls = [properties.get('imageDesktop')]
116 if 'thumbnail' in info:
117 thumbnail_urls.append(info.pop('thumbnail'))
118 for thumbnail_url in thumbnail_urls:
119 if not thumbnail_url:
120 continue
121 mobj = re.search(r'(\d+)x(\d+)', thumbnail_url)
122 thumbnails.append({
123 'url': thumbnail_url,
124 'width': int(mobj.group(1)) if mobj else None,
125 'height': int(mobj.group(2)) if mobj else None,
126 })
127
128 info.update({
129 'age_limit': parse_age_limit(rating),
130 'formats': formats,
131 'id': video_id,
132 'subtitles': subtitles,
133 'thumbnails': thumbnails,
134 })
135 ns_keys = theplatform_metadata.get('$xmlns', {}).keys()
136 if ns_keys:
137 ns = list(ns_keys)[0]
138 episode = theplatform_metadata.get(ns + '$episodeTitle') or None
139 episode_number = int_or_none(
140 theplatform_metadata.get(ns + '$episode'))
141 season_number = int_or_none(
142 theplatform_metadata.get(ns + '$season'))
143 series = theplatform_metadata.get(ns + '$show') or None
144 info.update({
145 'episode': episode,
146 'episode_number': episode_number,
147 'season_number': season_number,
148 'series': series,
149 })
150 return info