]>
Commit | Line | Data |
---|---|---|
1 | import re | |
2 | ||
3 | from .common import InfoExtractor | |
4 | from ..utils import ( | |
5 | int_or_none, | |
6 | parse_duration, | |
7 | url_or_none, | |
8 | ) | |
9 | from ..utils.traversal import traverse_obj | |
10 | ||
11 | ||
12 | class JTBCIE(InfoExtractor): | |
13 | IE_DESC = 'jtbc.co.kr' | |
14 | _VALID_URL = r'''(?x) | |
15 | https?://(?: | |
16 | vod\.jtbc\.co\.kr/player/(?:program|clip) | |
17 | |tv\.jtbc\.co\.kr/(?:replay|trailer|clip)/pr\d+/pm\d+ | |
18 | )/(?P<id>(?:ep|vo)\d+)''' | |
19 | _GEO_COUNTRIES = ['KR'] | |
20 | ||
21 | _TESTS = [{ | |
22 | 'url': 'https://tv.jtbc.co.kr/replay/pr10011629/pm10067930/ep20216321/view', | |
23 | 'md5': 'e6ade71d8c8685bbfd6e6ce4167c6a6c', | |
24 | 'info_dict': { | |
25 | 'id': 'VO10721192', | |
26 | 'display_id': 'ep20216321', | |
27 | 'ext': 'mp4', | |
28 | 'title': '힘쎈여자 강남순 2회 다시보기', | |
29 | 'description': 'md5:043c1d9019100ce271dba09995dbd1e2', | |
30 | 'duration': 3770.0, | |
31 | 'release_date': '20231008', | |
32 | 'age_limit': 15, | |
33 | 'thumbnail': 'https://fs.jtbc.co.kr//joydata/CP00000001/prog/drama/stronggirlnamsoon/img/20231008_163541_522_1.jpg', | |
34 | 'series': '힘쎈여자 강남순', | |
35 | }, | |
36 | }, { | |
37 | 'url': 'https://vod.jtbc.co.kr/player/program/ep20216733', | |
38 | 'md5': '217a6d190f115a75e4bda0ceaa4cd7f4', | |
39 | 'info_dict': { | |
40 | 'id': 'VO10721429', | |
41 | 'display_id': 'ep20216733', | |
42 | 'ext': 'mp4', | |
43 | 'title': '헬로 마이 닥터 친절한 진료실 149회 다시보기', | |
44 | 'description': 'md5:1d70788a982dd5de26874a92fcffddb8', | |
45 | 'duration': 2720.0, | |
46 | 'release_date': '20231009', | |
47 | 'age_limit': 15, | |
48 | 'thumbnail': 'https://fs.jtbc.co.kr//joydata/CP00000001/prog/culture/hellomydoctor/img/20231009_095002_528_1.jpg', | |
49 | 'series': '헬로 마이 닥터 친절한 진료실', | |
50 | }, | |
51 | }, { | |
52 | 'url': 'https://vod.jtbc.co.kr/player/clip/vo10721270', | |
53 | 'md5': '05782e2dc22a9c548aebefe62ae4328a', | |
54 | 'info_dict': { | |
55 | 'id': 'VO10721270', | |
56 | 'display_id': 'vo10721270', | |
57 | 'ext': 'mp4', | |
58 | 'title': '뭉쳐야 찬다3 2회 예고편 - A매치로 향하는 마지막 관문💥', | |
59 | 'description': 'md5:d48b51a8655c84843b4ed8d0c39aae68', | |
60 | 'duration': 46.0, | |
61 | 'release_date': '20231015', | |
62 | 'age_limit': 15, | |
63 | 'thumbnail': 'https://fs.jtbc.co.kr//joydata/CP00000001/prog/enter/soccer3/img/20231008_210957_775_1.jpg', | |
64 | 'series': '뭉쳐야 찬다3', | |
65 | }, | |
66 | }, { | |
67 | 'url': 'https://tv.jtbc.co.kr/trailer/pr10010392/pm10032526/vo10720912/view', | |
68 | 'md5': '367d480eb3ef54a9cd7a4b4d69c4b32d', | |
69 | 'info_dict': { | |
70 | 'id': 'VO10720912', | |
71 | 'display_id': 'vo10720912', | |
72 | 'ext': 'mp4', | |
73 | 'title': '아는 형님 404회 예고편 | 10월 14일(토) 저녁 8시 50분 방송!', | |
74 | 'description': 'md5:2743bb1079ceb85bb00060f2ad8f0280', | |
75 | 'duration': 148.0, | |
76 | 'release_date': '20231014', | |
77 | 'age_limit': 15, | |
78 | 'thumbnail': 'https://fs.jtbc.co.kr//joydata/CP00000001/prog/enter/jtbcbros/img/20231006_230023_802_1.jpg', | |
79 | 'series': '아는 형님', | |
80 | }, | |
81 | }] | |
82 | ||
83 | def _real_extract(self, url): | |
84 | display_id = self._match_id(url) | |
85 | ||
86 | if display_id.startswith('vo'): | |
87 | video_id = display_id.upper() | |
88 | else: | |
89 | webpage = self._download_webpage(url, display_id) | |
90 | video_id = self._search_regex(r'data-vod="(VO\d+)"', webpage, 'vod id') | |
91 | ||
92 | playback_data = self._download_json( | |
93 | f'https://api.jtbc.co.kr/vod/{video_id}', video_id, note='Downloading VOD playback data') | |
94 | ||
95 | subtitles = {} | |
96 | for sub in traverse_obj(playback_data, ('tracks', lambda _, v: v['file'])): | |
97 | subtitles.setdefault(sub.get('label', 'und'), []).append({'url': sub['file']}) | |
98 | ||
99 | formats = [] | |
100 | for stream_url in traverse_obj(playback_data, ('sources', 'HLS', ..., 'file', {url_or_none})): | |
101 | stream_url = re.sub(r'/playlist(?:_pd\d+)?\.m3u8', '/index.m3u8', stream_url) | |
102 | formats.extend(self._extract_m3u8_formats(stream_url, video_id, fatal=False)) | |
103 | ||
104 | metadata = self._download_json( | |
105 | 'https://now-api.jtbc.co.kr/v1/vod/detail', video_id, | |
106 | note='Downloading mobile details', fatal=False, query={'vodFileId': video_id}) | |
107 | return { | |
108 | 'id': video_id, | |
109 | 'display_id': display_id, | |
110 | **traverse_obj(metadata, ('vodDetail', { | |
111 | 'title': 'vodTitleView', | |
112 | 'series': 'programTitle', | |
113 | 'age_limit': ('watchAge', {int_or_none}), | |
114 | 'release_date': ('broadcastDate', {lambda x: re.match(r'\d{8}', x.replace('.', ''))}, 0), | |
115 | 'description': 'episodeContents', | |
116 | 'thumbnail': ('imgFileUrl', {url_or_none}), | |
117 | })), | |
118 | 'duration': parse_duration(playback_data.get('playTime')), | |
119 | 'formats': formats, | |
120 | 'subtitles': subtitles, | |
121 | } | |
122 | ||
123 | ||
124 | class JTBCProgramIE(InfoExtractor): | |
125 | IE_NAME = 'JTBC:program' | |
126 | _VALID_URL = r'https?://(?:vod\.jtbc\.co\.kr/program|tv\.jtbc\.co\.kr/replay)/(?P<id>pr\d+)/(?:replay|pm\d+)/?(?:$|[?#])' | |
127 | ||
128 | _TESTS = [{ | |
129 | 'url': 'https://tv.jtbc.co.kr/replay/pr10010392/pm10032710', | |
130 | 'info_dict': { | |
131 | '_type': 'playlist', | |
132 | 'id': 'pr10010392', | |
133 | }, | |
134 | 'playlist_count': 398, | |
135 | }, { | |
136 | 'url': 'https://vod.jtbc.co.kr/program/pr10011491/replay', | |
137 | 'info_dict': { | |
138 | '_type': 'playlist', | |
139 | 'id': 'pr10011491', | |
140 | }, | |
141 | 'playlist_count': 59, | |
142 | }] | |
143 | ||
144 | def _real_extract(self, url): | |
145 | program_id = self._match_id(url) | |
146 | ||
147 | vod_list = self._download_json( | |
148 | 'https://now-api.jtbc.co.kr/v1/vodClip/programHome/programReplayVodList', program_id, | |
149 | note='Downloading program replay list', query={ | |
150 | 'programId': program_id, | |
151 | 'rowCount': '10000', | |
152 | }) | |
153 | ||
154 | entries = [self.url_result(f'https://vod.jtbc.co.kr/player/program/{video_id}', JTBCIE, video_id) | |
155 | for video_id in traverse_obj(vod_list, ('programReplayVodList', ..., 'episodeId'))] | |
156 | return self.playlist_result(entries, program_id) |