]> Raphaƫl G. Git Repositories - youtubedl/blob - youtube_dl/extractor/tvnow.py
debian/{compat,control}: Relutanctly update compat to 11.
[youtubedl] / youtube_dl / extractor / tvnow.py
1 # coding: utf-8
2 from __future__ import unicode_literals
3
4 import re
5
6 from .common import InfoExtractor
7 from ..compat import compat_str
8 from ..utils import (
9 ExtractorError,
10 int_or_none,
11 parse_iso8601,
12 parse_duration,
13 update_url_query,
14 )
15
16
17 class TVNowBaseIE(InfoExtractor):
18 _VIDEO_FIELDS = (
19 'id', 'title', 'free', 'geoblocked', 'articleLong', 'articleShort',
20 'broadcastStartDate', 'isDrm', 'duration', 'season', 'episode',
21 'manifest.dashclear', 'format.title', 'format.defaultImage169Format',
22 'format.defaultImage169Logo')
23
24 def _call_api(self, path, video_id, query):
25 return self._download_json(
26 'https://api.tvnow.de/v3/' + path,
27 video_id, query=query)
28
29 def _extract_video(self, info, display_id):
30 video_id = compat_str(info['id'])
31 title = info['title']
32
33 mpd_url = info['manifest']['dashclear']
34 if not mpd_url:
35 if info.get('isDrm'):
36 raise ExtractorError(
37 'Video %s is DRM protected' % video_id, expected=True)
38 if info.get('geoblocked'):
39 raise ExtractorError(
40 'Video %s is not available from your location due to geo restriction' % video_id,
41 expected=True)
42 if not info.get('free', True):
43 raise ExtractorError(
44 'Video %s is not available for free' % video_id, expected=True)
45
46 mpd_url = update_url_query(mpd_url, {'filter': ''})
47 formats = self._extract_mpd_formats(mpd_url, video_id, mpd_id='dash', fatal=False)
48 formats.extend(self._extract_ism_formats(
49 mpd_url.replace('dash.', 'hss.').replace('/.mpd', '/Manifest'),
50 video_id, ism_id='mss', fatal=False))
51 formats.extend(self._extract_m3u8_formats(
52 mpd_url.replace('dash.', 'hls.').replace('/.mpd', '/.m3u8'),
53 video_id, 'mp4', 'm3u8_native', m3u8_id='hls', fatal=False))
54 self._sort_formats(formats)
55
56 description = info.get('articleLong') or info.get('articleShort')
57 timestamp = parse_iso8601(info.get('broadcastStartDate'), ' ')
58 duration = parse_duration(info.get('duration'))
59
60 f = info.get('format', {})
61 thumbnail = f.get('defaultImage169Format') or f.get('defaultImage169Logo')
62
63 return {
64 'id': video_id,
65 'display_id': display_id,
66 'title': title,
67 'description': description,
68 'thumbnail': thumbnail,
69 'timestamp': timestamp,
70 'duration': duration,
71 'series': f.get('title'),
72 'season_number': int_or_none(info.get('season')),
73 'episode_number': int_or_none(info.get('episode')),
74 'episode': title,
75 'formats': formats,
76 }
77
78
79 class TVNowIE(TVNowBaseIE):
80 _VALID_URL = r'https?://(?:www\.)?tvnow\.(?:de|at|ch)/(?:rtl(?:2|plus)?|nitro|superrtl|ntv|vox)/(?P<show_id>[^/]+)/(?:(?:list/[^/]+|jahr/\d{4}/\d{1,2})/)?(?P<id>[^/]+)/(?:player|preview)'
81
82 _TESTS = [{
83 'url': 'https://www.tvnow.de/rtl2/grip-das-motormagazin/der-neue-porsche-911-gt-3/player',
84 'info_dict': {
85 'id': '331082',
86 'display_id': 'grip-das-motormagazin/der-neue-porsche-911-gt-3',
87 'ext': 'mp4',
88 'title': 'Der neue Porsche 911 GT 3',
89 'description': 'md5:6143220c661f9b0aae73b245e5d898bb',
90 'thumbnail': r're:^https?://.*\.jpg$',
91 'timestamp': 1495994400,
92 'upload_date': '20170528',
93 'duration': 5283,
94 'series': 'GRIP - Das Motormagazin',
95 'season_number': 14,
96 'episode_number': 405,
97 'episode': 'Der neue Porsche 911 GT 3',
98 },
99 }, {
100 # rtl2
101 'url': 'https://www.tvnow.de/rtl2/armes-deutschland/episode-0008/player',
102 'only_matching': 'True',
103 }, {
104 # rtlnitro
105 'url': 'https://www.tvnow.de/nitro/alarm-fuer-cobra-11-die-autobahnpolizei/auf-eigene-faust-pilot/player',
106 'only_matching': 'True',
107 }, {
108 # superrtl
109 'url': 'https://www.tvnow.de/superrtl/die-lustigsten-schlamassel-der-welt/u-a-ketchup-effekt/player',
110 'only_matching': 'True',
111 }, {
112 # ntv
113 'url': 'https://www.tvnow.de/ntv/startup-news/goetter-in-weiss/player',
114 'only_matching': 'True',
115 }, {
116 # vox
117 'url': 'https://www.tvnow.de/vox/auto-mobil/neues-vom-automobilmarkt-2017-11-19-17-00-00/player',
118 'only_matching': 'True',
119 }, {
120 # rtlplus
121 'url': 'https://www.tvnow.de/rtlplus/op-ruft-dr-bruckner/die-vernaehte-frau/player',
122 'only_matching': 'True',
123 }]
124
125 def _real_extract(self, url):
126 display_id = '%s/%s' % re.match(self._VALID_URL, url).groups()
127
128 info = self._call_api(
129 'movies/' + display_id, display_id, query={
130 'fields': ','.join(self._VIDEO_FIELDS),
131 })
132
133 return self._extract_video(info, display_id)
134
135
136 class TVNowListIE(TVNowBaseIE):
137 _VALID_URL = r'(?P<base_url>https?://(?:www\.)?tvnow\.(?:de|at|ch)/(?:rtl(?:2|plus)?|nitro|superrtl|ntv|vox)/(?P<show_id>[^/]+)/)list/(?P<id>[^?/#&]+)$'
138
139 _SHOW_FIELDS = ('title', )
140 _SEASON_FIELDS = ('id', 'headline', 'seoheadline', )
141 _VIDEO_FIELDS = ('id', 'headline', 'seoUrl', )
142
143 _TESTS = [{
144 'url': 'https://www.tvnow.de/rtl/30-minuten-deutschland/list/aktuell',
145 'info_dict': {
146 'id': '28296',
147 'title': '30 Minuten Deutschland - Aktuell',
148 },
149 'playlist_mincount': 1,
150 }]
151
152 def _real_extract(self, url):
153 base_url, show_id, season_id = re.match(self._VALID_URL, url).groups()
154
155 fields = []
156 fields.extend(self._SHOW_FIELDS)
157 fields.extend('formatTabs.%s' % field for field in self._SEASON_FIELDS)
158 fields.extend(
159 'formatTabs.formatTabPages.container.movies.%s' % field
160 for field in self._VIDEO_FIELDS)
161
162 list_info = self._call_api(
163 'formats/seo', season_id, query={
164 'fields': ','.join(fields),
165 'name': show_id + '.php'
166 })
167
168 season = next(
169 season for season in list_info['formatTabs']['items']
170 if season.get('seoheadline') == season_id)
171
172 title = '%s - %s' % (list_info['title'], season['headline'])
173
174 entries = []
175 for container in season['formatTabPages']['items']:
176 for info in ((container.get('container') or {}).get('movies') or {}).get('items') or []:
177 seo_url = info.get('seoUrl')
178 if not seo_url:
179 continue
180 entries.append(self.url_result(
181 base_url + seo_url + '/player', 'TVNow', info.get('id')))
182
183 return self.playlist_result(
184 entries, compat_str(season.get('id') or season_id), title)