Compare commits
211 Commits
2017.10.29
...
totalwebca
Author | SHA1 | Date | |
---|---|---|---|
97bc05116e | |||
0a5b1295b7 | |||
a133eb7764 | |||
f12628f934 | |||
45283afdec | |||
b7c74c0403 | |||
0b0870f9d0 | |||
c2f18e1c49 | |||
da35331c6c | |||
de329f64ab | |||
75ba0efb52 | |||
f0c6c2bce2 | |||
9650c3e91d | |||
b5e531f31a | |||
7a6c204fcb | |||
d7cd9a9e84 | |||
54009c246e | |||
b300cda476 | |||
04cf1a191a | |||
c95c08a856 | |||
126f225bcf | |||
4f5cf31977 | |||
77341dae14 | |||
2e65e7db9e | |||
538d4f8681 | |||
620ee8712e | |||
2ca7ed41fe | |||
8056c8542d | |||
2501d41ef4 | |||
d97cb84b31 | |||
2c8e11b4af | |||
d2c5b5a951 | |||
580f3c79d5 | |||
9d6ac71c27 | |||
84f085d4bd | |||
a491fd0c6f | |||
99277daaac | |||
640788f6f4 | |||
1ae0f0a21d | |||
616bb95b28 | |||
be069839b4 | |||
a14001a5a1 | |||
db145ee54a | |||
45d20488f1 | |||
0f897e0929 | |||
173558ce96 | |||
d3ca283235 | |||
d99a1000c7 | |||
a75419586b | |||
273c23d960 | |||
b954e72c87 | |||
116561697d | |||
0e25a1a278 | |||
307a7588b0 | |||
c2f2f8b120 | |||
f5a6321107 | |||
69d69da98a | |||
5c5e60cff8 | |||
2132edaa03 | |||
4b7dd1705a | |||
9e3682d555 | |||
3e191da6d9 | |||
963d237d26 | |||
d2d766bc6d | |||
17c3aced5d | |||
78466fcab5 | |||
3961c6cb9d | |||
07aeced68e | |||
c10c93238e | |||
4a109f81bc | |||
99081da90c | |||
7e81010987 | |||
549bb416f5 | |||
25475dfab3 | |||
3dfa9ec213 | |||
06dbcd7be4 | |||
b555ae9bf1 | |||
c402e7f3a0 | |||
498a8a4ca5 | |||
d05ba4b89e | |||
23f511f5c7 | |||
1c4804ef9b | |||
8ff2b16435 | |||
c6a5a811a1 | |||
3fae11ac00 | |||
7974e289a1 | |||
6bf9c28b0a | |||
bec49996c6 | |||
c8be7d5f74 | |||
15960255fe | |||
6b2d8c9182 | |||
e6b8803d59 | |||
cb0c2310fb | |||
23b6e23002 | |||
127e98d31d | |||
e4f201bc1b | |||
08d77a95c9 | |||
5868079e99 | |||
b6f78d76c1 | |||
1fa0dce2c0 | |||
fa1dd6d2cd | |||
c38970ca10 | |||
51f2863357 | |||
913b61eeee | |||
6f1ec339a0 | |||
a3de5e6c0e | |||
f4cc03d60b | |||
2a57b62b80 | |||
e2707a832c | |||
1115271ac6 | |||
d21d0ba6c1 | |||
a670b1ba26 | |||
1bd4fc96e6 | |||
684ae10236 | |||
3c4fbfeca2 | |||
b271e33526 | |||
d3f8b76b69 | |||
91328f26b0 | |||
61d18c8a4b | |||
c94427dd60 | |||
d4f05d4731 | |||
d7df308981 | |||
0d56eddc59 | |||
e25ee72657 | |||
78593e294c | |||
593f2f7989 | |||
603fc4e0ea | |||
41bf647e89 | |||
fea92aa65d | |||
0981585bef | |||
f5ac68d88f | |||
1663b32946 | |||
5ea765fb72 | |||
fb61b57d0f | |||
07cf18b9c5 | |||
5f699251e9 | |||
a3474aa59e | |||
115afb77ec | |||
53f024e7c5 | |||
ffe6979ef9 | |||
dafb4c6647 | |||
82a62de192 | |||
f58a506044 | |||
5ddeb7702a | |||
6c07f0b288 | |||
e94d1adc36 | |||
d08dcd2dbd | |||
7512aa986f | |||
93f3f10cdc | |||
87dac57cf6 | |||
b485d5d6bf | |||
a238a868ba | |||
c0f647a179 | |||
6ff27b8d5a | |||
9ef909f2b2 | |||
8cfbcfab9a | |||
b7785cf156 | |||
9105523818 | |||
dbb25af657 | |||
fe4bfe36e1 | |||
6f5c598a28 | |||
cd9ff4ec5b | |||
c6c6a64aa5 | |||
e0a8686f48 | |||
6049176471 | |||
805f5bf759 | |||
32ad4f3faf | |||
6899b1d9e8 | |||
939be9adfe | |||
2688664762 | |||
8f63941104 | |||
a9efdf3d4a | |||
f610dbb05f | |||
38db52adf3 | |||
3192d4bc7a | |||
9cbd4dda10 | |||
08e45b39e7 | |||
fae0eb42ec | |||
ea2295842f | |||
a2b6aba8de | |||
ff31f2d5c3 | |||
0987f2ddb2 | |||
5871ebac47 | |||
05dee6c520 | |||
27adc9ec65 | |||
388beb86e0 | |||
d4e31b72b9 | |||
5fc12b9549 | |||
af85ce29c6 | |||
e4d9586562 | |||
79d1f8ed68 | |||
a5203935d6 | |||
59d2e6d04f | |||
a9543e37c8 | |||
61fb07e156 | |||
4222346fb2 | |||
cc6a960e13 | |||
f34b841b51 | |||
e0998333fa | |||
909191de91 | |||
477c97f86b | |||
6e71bbf4ab | |||
181e381fda | |||
187ee66c94 | |||
48107c198b | |||
cd670befc4 | |||
44cca168cc | |||
b0f4331002 | |||
044eeb1455 | |||
8fe767e072 | |||
7608a91ee7 |
6
.github/ISSUE_TEMPLATE.md
vendored
6
.github/ISSUE_TEMPLATE.md
vendored
@ -6,8 +6,8 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Make sure you are using the *latest* version: run `youtube-dl --version` and ensure your version is *2017.10.29*. If it's not, read [this FAQ entry](https://github.com/rg3/youtube-dl/blob/master/README.md#how-do-i-update-youtube-dl) and update. Issues with outdated version will be rejected.
|
### Make sure you are using the *latest* version: run `youtube-dl --version` and ensure your version is *2017.12.31*. If it's not, read [this FAQ entry](https://github.com/rg3/youtube-dl/blob/master/README.md#how-do-i-update-youtube-dl) and update. Issues with outdated version will be rejected.
|
||||||
- [ ] I've **verified** and **I assure** that I'm running youtube-dl **2017.10.29**
|
- [ ] I've **verified** and **I assure** that I'm running youtube-dl **2017.12.31**
|
||||||
|
|
||||||
### Before submitting an *issue* make sure you have:
|
### Before submitting an *issue* make sure you have:
|
||||||
- [ ] At least skimmed through the [README](https://github.com/rg3/youtube-dl/blob/master/README.md), **most notably** the [FAQ](https://github.com/rg3/youtube-dl#faq) and [BUGS](https://github.com/rg3/youtube-dl#bugs) sections
|
- [ ] At least skimmed through the [README](https://github.com/rg3/youtube-dl/blob/master/README.md), **most notably** the [FAQ](https://github.com/rg3/youtube-dl#faq) and [BUGS](https://github.com/rg3/youtube-dl#bugs) sections
|
||||||
@ -35,7 +35,7 @@ Add the `-v` flag to **your command line** you run youtube-dl with (`youtube-dl
|
|||||||
[debug] User config: []
|
[debug] User config: []
|
||||||
[debug] Command-line args: [u'-v', u'http://www.youtube.com/watch?v=BaW_jenozKcj']
|
[debug] Command-line args: [u'-v', u'http://www.youtube.com/watch?v=BaW_jenozKcj']
|
||||||
[debug] Encodings: locale cp1251, fs mbcs, out cp866, pref cp1251
|
[debug] Encodings: locale cp1251, fs mbcs, out cp866, pref cp1251
|
||||||
[debug] youtube-dl version 2017.10.29
|
[debug] youtube-dl version 2017.12.31
|
||||||
[debug] Python version 2.7.11 - Windows-2003Server-5.2.3790-SP2
|
[debug] Python version 2.7.11 - Windows-2003Server-5.2.3790-SP2
|
||||||
[debug] exe versions: ffmpeg N-75573-g1d0487f, ffprobe N-75573-g1d0487f, rtmpdump 2.4
|
[debug] exe versions: ffmpeg N-75573-g1d0487f, ffprobe N-75573-g1d0487f, rtmpdump 2.4
|
||||||
[debug] Proxy map: {}
|
[debug] Proxy map: {}
|
||||||
|
13
.travis.yml
13
.travis.yml
@ -7,16 +7,21 @@ python:
|
|||||||
- "3.4"
|
- "3.4"
|
||||||
- "3.5"
|
- "3.5"
|
||||||
- "3.6"
|
- "3.6"
|
||||||
|
- "pypy"
|
||||||
|
- "pypy3"
|
||||||
sudo: false
|
sudo: false
|
||||||
env:
|
env:
|
||||||
- YTDL_TEST_SET=core
|
- YTDL_TEST_SET=core
|
||||||
- YTDL_TEST_SET=download
|
- YTDL_TEST_SET=download
|
||||||
matrix:
|
matrix:
|
||||||
|
include:
|
||||||
|
- env: JYTHON=true; YTDL_TEST_SET=core
|
||||||
|
- env: JYTHON=true; YTDL_TEST_SET=download
|
||||||
fast_finish: true
|
fast_finish: true
|
||||||
allow_failures:
|
allow_failures:
|
||||||
- env: YTDL_TEST_SET=download
|
- env: YTDL_TEST_SET=download
|
||||||
|
- env: JYTHON=true; YTDL_TEST_SET=core
|
||||||
|
- env: JYTHON=true; YTDL_TEST_SET=download
|
||||||
|
before_install:
|
||||||
|
- if [ "$JYTHON" == "true" ]; then ./devscripts/install_jython.sh; export PATH="$HOME/jython/bin:$PATH"; fi
|
||||||
script: ./devscripts/run_tests.sh
|
script: ./devscripts/run_tests.sh
|
||||||
notifications:
|
|
||||||
email:
|
|
||||||
- filippo.valsorda@gmail.com
|
|
||||||
- yasoob.khld@gmail.com
|
|
||||||
|
223
ChangeLog
223
ChangeLog
@ -1,3 +1,222 @@
|
|||||||
|
version <unreleased>
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
* [youku] Fix list extraction (#15135)
|
||||||
|
* [openload] Fix extraction (#15166)
|
||||||
|
* [rtve.es:alacarta] Fix extraction of some new URLs
|
||||||
|
|
||||||
|
|
||||||
|
version 2017.12.31
|
||||||
|
|
||||||
|
Core
|
||||||
|
+ [extractor/common] Add container meta field for formats extracted
|
||||||
|
in _parse_mpd_formats (#13616)
|
||||||
|
+ [downloader/hls] Use HTTP headers for key request
|
||||||
|
* [common] Use AACL as the default fourcc when AudioTag is 255
|
||||||
|
* [extractor/common] Fix extraction of DASH formats with the same
|
||||||
|
representation id (#15111)
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
+ [slutload] Add support for mobile URLs (#14806)
|
||||||
|
* [abc:iview] Bypass geo restriction
|
||||||
|
* [abc:iview] Fix extraction (#14711, #14782, #14838, #14917, #14963, #14985,
|
||||||
|
#15035, #15057, #15061, #15071, #15095, #15106)
|
||||||
|
* [openload] Fix extraction (#15118)
|
||||||
|
- [sandia] Remove extractor
|
||||||
|
- [collegerama] Remove extractor
|
||||||
|
+ [mediasite] Add support for sites based on Mediasite Video Platform (#5428,
|
||||||
|
#11185, #14343)
|
||||||
|
+ [ufctv] Add support for ufc.tv (#14520)
|
||||||
|
* [pluralsight] Fix missing first line of subtitles (#11118)
|
||||||
|
* [openload] Fallback on f-page extraction (#14665, #14879)
|
||||||
|
* [vimeo] Improve password protected videos extraction (#15114)
|
||||||
|
* [aws] Fix canonical/signed headers generation on python 2 (#15102)
|
||||||
|
|
||||||
|
|
||||||
|
version 2017.12.28
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
+ [internazionale] Add support for internazionale.it (#14973)
|
||||||
|
* [playtvak] Relax video regular expression and make description optional
|
||||||
|
(#15037)
|
||||||
|
+ [filmweb] Add support for filmweb.no (#8773, #10368)
|
||||||
|
+ [23video] Add support for 23video.com
|
||||||
|
+ [espn] Add support for fivethirtyeight.com (#6864)
|
||||||
|
+ [umg:de] Add support for universal-music.de (#11582, #11584)
|
||||||
|
+ [espn] Add support for espnfc and extract more formats (#8053)
|
||||||
|
* [youku] Update ccode (#14880)
|
||||||
|
+ [openload] Add support for oload.stream (#15070)
|
||||||
|
* [youku] Fix list extraction (#15065)
|
||||||
|
|
||||||
|
|
||||||
|
version 2017.12.23
|
||||||
|
|
||||||
|
Core
|
||||||
|
* [extractor/common] Move X-Forwarded-For setup code into _request_webpage
|
||||||
|
+ [YoutubeDL] Add support for playlist_uploader and playlist_uploader_id in
|
||||||
|
output template (#11427, #15018)
|
||||||
|
+ [extractor/common] Introduce uploader, uploader_id and uploader_url
|
||||||
|
meta fields for playlists (#11427, #15018)
|
||||||
|
* [downloader/fragment] Encode filename of fragment being removed (#15020)
|
||||||
|
+ [utils] Add another date format pattern (#14999)
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
+ [kaltura] Add another embed pattern for entry_id
|
||||||
|
+ [7plus] Add support for 7plus.com.au (#15043)
|
||||||
|
* [animeondemand] Relax login error regular expression
|
||||||
|
+ [shahid] Add support for show pages (#7401)
|
||||||
|
+ [youtube] Extract uploader, uploader_id and uploader_url for playlists
|
||||||
|
(#11427, #15018)
|
||||||
|
* [afreecatv] Improve format extraction (#15019)
|
||||||
|
+ [cspan] Add support for audio only pages and catch page errors (#14995)
|
||||||
|
+ [mailru] Add support for embed URLs (#14904)
|
||||||
|
* [crunchyroll] Future-proof XML element checks (#15013)
|
||||||
|
* [cbslocal] Fix timestamp extraction (#14999, #15000)
|
||||||
|
* [discoverygo] Correct TTML subtitle extension
|
||||||
|
* [vk] Make view count optional (#14979)
|
||||||
|
* [disney] Skip Apple FairPlay formats (#14982)
|
||||||
|
* [voot] Fix format extraction (#14758)
|
||||||
|
|
||||||
|
|
||||||
|
version 2017.12.14
|
||||||
|
|
||||||
|
Core
|
||||||
|
* [postprocessor/xattr] Clarify NO_SPACE message (#14970)
|
||||||
|
* [downloader/http] Return actual download result from real_download (#14971)
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
+ [itv] Extract more subtitles and duration
|
||||||
|
* [itv] Improve extraction (#14944)
|
||||||
|
+ [byutv] Add support for geo restricted videos
|
||||||
|
* [byutv] Fix extraction (#14966, #14967)
|
||||||
|
+ [bbccouk] Fix extraction for 320k HLS streams
|
||||||
|
+ [toutv] Add support for special video URLs (#14179)
|
||||||
|
* [discovery] Fix free videos extraction (#14157, #14954)
|
||||||
|
* [tvnow] Fix extraction (#7831)
|
||||||
|
+ [nickelodeon:br] Add support for nickelodeon brazil websites (#14893)
|
||||||
|
* [nick] Improve extraction (#14876)
|
||||||
|
* [tbs] Fix extraction (#13658)
|
||||||
|
|
||||||
|
|
||||||
|
version 2017.12.10
|
||||||
|
|
||||||
|
Core
|
||||||
|
+ [utils] Add sami mimetype to mimetype2ext
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
* [culturebox] Improve video id extraction (#14947)
|
||||||
|
* [twitter] Improve extraction (#14197)
|
||||||
|
+ [udemy] Extract more HLS formats
|
||||||
|
* [udemy] Improve course id extraction (#14938)
|
||||||
|
+ [stretchinternet] Add support for portal.stretchinternet.com (#14576)
|
||||||
|
* [ellentube] Fix extraction (#14407, #14570)
|
||||||
|
+ [raiplay:playlist] Add support for playlists (#14563)
|
||||||
|
* [sonyliv] Bypass geo restriction
|
||||||
|
* [sonyliv] Extract higher quality formats (#14922)
|
||||||
|
* [fox] Extract subtitles
|
||||||
|
+ [fox] Add support for Adobe Pass authentication (#14205, #14489)
|
||||||
|
- [dailymotion:cloud] Remove extractor (#6794)
|
||||||
|
* [xhamster] Fix thumbnail extraction (#14780)
|
||||||
|
+ [xhamster] Add support for mobile URLs (#14780)
|
||||||
|
* [generic] Don't pass video id as mpd id while extracting DASH (#14902)
|
||||||
|
* [ard] Skip invalid stream URLs (#14906)
|
||||||
|
* [porncom] Fix metadata extraction (#14911)
|
||||||
|
* [pluralsight] Detect agreement request (#14913)
|
||||||
|
* [toutv] Fix login (#14614)
|
||||||
|
|
||||||
|
|
||||||
|
version 2017.12.02
|
||||||
|
|
||||||
|
Core
|
||||||
|
+ [downloader/fragment] Commit part file after each fragment
|
||||||
|
+ [extractor/common] Add durations for DASH fragments with bare SegmentURLs
|
||||||
|
+ [extractor/common] Add support for DASH manifests with SegmentLists with
|
||||||
|
bare SegmentURLs (#14844)
|
||||||
|
+ [utils] Add hvc1 codec code to parse_codecs
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
* [xhamster] Fix extraction (#14884)
|
||||||
|
* [youku] Update ccode (#14872)
|
||||||
|
* [mnet] Fix format extraction (#14883)
|
||||||
|
+ [xiami] Add Referer header to API request
|
||||||
|
* [mtv] Correct scc extention in extracted subtitles (#13730)
|
||||||
|
* [vvvvid] Fix extraction for kenc videos (#13406)
|
||||||
|
+ [br] Add support for BR Mediathek videos (#14560, #14788)
|
||||||
|
+ [daisuki] Add support for motto.daisuki.com (#14681)
|
||||||
|
* [odnoklassniki] Fix API metadata request (#14862)
|
||||||
|
* [itv] Fix HLS formats extraction
|
||||||
|
+ [pbs] Add another media id regular expression
|
||||||
|
|
||||||
|
|
||||||
|
version 2017.11.26
|
||||||
|
|
||||||
|
Core
|
||||||
|
* [extractor/common] Use final URL when dumping request (#14769)
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
* [fczenit] Fix extraction
|
||||||
|
- [firstpost] Remove extractor
|
||||||
|
* [freespeech] Fix extraction
|
||||||
|
* [nexx] Extract more formats
|
||||||
|
+ [openload] Add support for openload.link (#14763)
|
||||||
|
* [empflix] Relax URL regular expression
|
||||||
|
* [empflix] Fix extractrion
|
||||||
|
* [tnaflix] Don't modify download URLs (#14811)
|
||||||
|
- [gamersyde] Remove extractor
|
||||||
|
* [francetv:generationwhat] Fix extraction
|
||||||
|
+ [massengeschmacktv] Add support for Massengeschmack TV
|
||||||
|
* [fox9] Fix extraction
|
||||||
|
* [faz] Fix extraction and add support for Perform Group embeds (#14714)
|
||||||
|
+ [performgroup] Add support for performgroup.com
|
||||||
|
+ [jwplatform] Add support for iframes (#14828)
|
||||||
|
* [culturebox] Fix extraction (#14827)
|
||||||
|
* [youku] Fix extraction; update ccode (#14815)
|
||||||
|
* [livestream] Make SMIL extraction non fatal (#14792)
|
||||||
|
+ [drtuber] Add support for mobile URLs (#14772)
|
||||||
|
+ [spankbang] Add support for mobile URLs (#14771)
|
||||||
|
* [instagram] Fix description, timestamp and counters extraction (#14755)
|
||||||
|
|
||||||
|
|
||||||
|
version 2017.11.15
|
||||||
|
|
||||||
|
Core
|
||||||
|
* [common] Skip Apple FairPlay m3u8 manifests (#14741)
|
||||||
|
* [YoutubeDL] Fix playlist range optimization for --playlist-items (#14740)
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
* [vshare] Capture and output error message
|
||||||
|
* [vshare] Fix extraction (#14473)
|
||||||
|
* [crunchyroll] Extract old RTMP formats
|
||||||
|
* [tva] Fix extraction (#14736)
|
||||||
|
* [gamespot] Lower preference of HTTP formats (#14652)
|
||||||
|
* [instagram:user] Fix extraction (#14699)
|
||||||
|
* [ccma] Fix typo (#14730)
|
||||||
|
- Remove sensitive data from logging in messages
|
||||||
|
* [instagram:user] Fix extraction (#14699)
|
||||||
|
+ [gamespot] Add support for article URLs (#14652)
|
||||||
|
* [gamespot] Skip Brightcove Once HTTP formats (#14652)
|
||||||
|
* [cartoonnetwork] Update tokenizer_src (#14666)
|
||||||
|
+ [wsj] Recognize another URL pattern (#14704)
|
||||||
|
* [pandatv] Update API URL and sign format URLs (#14693)
|
||||||
|
* [crunchyroll] Use old login method (#11572)
|
||||||
|
|
||||||
|
|
||||||
|
version 2017.11.06
|
||||||
|
|
||||||
|
Core
|
||||||
|
+ [extractor/common] Add protocol for f4m formats
|
||||||
|
* [f4m] Prefer baseURL for relative URLs (#14660)
|
||||||
|
* [extractor/common] Respect URL query in _extract_wowza_formats (14645)
|
||||||
|
|
||||||
|
Extractors
|
||||||
|
+ [hotstar:playlist] Add support for playlists (#12465)
|
||||||
|
* [hotstar] Bypass geo restriction (#14672)
|
||||||
|
- [22tracks] Remove extractor (#11024, #14628)
|
||||||
|
+ [skysport] Sdd support ooyala videos protected with embed_token (#14641)
|
||||||
|
* [gamespot] Extract formats referenced with new data fields (#14652)
|
||||||
|
* [spankbang] Detect unavailable videos (#14644)
|
||||||
|
|
||||||
|
|
||||||
version 2017.10.29
|
version 2017.10.29
|
||||||
|
|
||||||
Core
|
Core
|
||||||
@ -9,8 +228,8 @@ Extractors
|
|||||||
+ [fxnetworks] Extract series metadata (#14603)
|
+ [fxnetworks] Extract series metadata (#14603)
|
||||||
+ [younow] Add support for younow.com (#9255, #9432, #12436)
|
+ [younow] Add support for younow.com (#9255, #9432, #12436)
|
||||||
* [dctptv] Fix extraction (#14599)
|
* [dctptv] Fix extraction (#14599)
|
||||||
* [youtube] Restrict embed regex (#14600)
|
* [youtube] Restrict embed regular expression (#14600)
|
||||||
* [vimeo] Restrict iframe embed regex (#14600)
|
* [vimeo] Restrict iframe embed regular expression (#14600)
|
||||||
* [soundgasm] Improve extraction (#14588)
|
* [soundgasm] Improve extraction (#14588)
|
||||||
- [myvideo] Remove extractor (#8557)
|
- [myvideo] Remove extractor (#8557)
|
||||||
+ [nbc] Add support for classic-tv videos (#14575)
|
+ [nbc] Add support for classic-tv videos (#14575)
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
include README.md
|
include README.md
|
||||||
include test/*.py
|
include LICENSE
|
||||||
include test/*.json
|
include AUTHORS
|
||||||
|
include ChangeLog
|
||||||
include youtube-dl.bash-completion
|
include youtube-dl.bash-completion
|
||||||
include youtube-dl.fish
|
include youtube-dl.fish
|
||||||
include youtube-dl.1
|
include youtube-dl.1
|
||||||
recursive-include docs Makefile conf.py *.rst
|
recursive-include docs Makefile conf.py *.rst
|
||||||
|
recursive-include test *
|
||||||
|
18
Makefile
18
Makefile
@ -36,8 +36,17 @@ test:
|
|||||||
|
|
||||||
ot: offlinetest
|
ot: offlinetest
|
||||||
|
|
||||||
|
# Keep this list in sync with devscripts/run_tests.sh
|
||||||
offlinetest: codetest
|
offlinetest: codetest
|
||||||
$(PYTHON) -m nose --verbose test --exclude test_download.py --exclude test_age_restriction.py --exclude test_subtitles.py --exclude test_write_annotations.py --exclude test_youtube_lists.py --exclude test_iqiyi_sdk_interpreter.py --exclude test_socks.py
|
$(PYTHON) -m nose --verbose test \
|
||||||
|
--exclude test_age_restriction.py \
|
||||||
|
--exclude test_download.py \
|
||||||
|
--exclude test_iqiyi_sdk_interpreter.py \
|
||||||
|
--exclude test_socks.py \
|
||||||
|
--exclude test_subtitles.py \
|
||||||
|
--exclude test_write_annotations.py \
|
||||||
|
--exclude test_youtube_lists.py \
|
||||||
|
--exclude test_youtube_signature.py
|
||||||
|
|
||||||
tar: youtube-dl.tar.gz
|
tar: youtube-dl.tar.gz
|
||||||
|
|
||||||
@ -101,7 +110,7 @@ _EXTRACTOR_FILES = $(shell find youtube_dl/extractor -iname '*.py' -and -not -in
|
|||||||
youtube_dl/extractor/lazy_extractors.py: devscripts/make_lazy_extractors.py devscripts/lazy_load_template.py $(_EXTRACTOR_FILES)
|
youtube_dl/extractor/lazy_extractors.py: devscripts/make_lazy_extractors.py devscripts/lazy_load_template.py $(_EXTRACTOR_FILES)
|
||||||
$(PYTHON) devscripts/make_lazy_extractors.py $@
|
$(PYTHON) devscripts/make_lazy_extractors.py $@
|
||||||
|
|
||||||
youtube-dl.tar.gz: youtube-dl README.md README.txt youtube-dl.1 youtube-dl.bash-completion youtube-dl.zsh youtube-dl.fish ChangeLog
|
youtube-dl.tar.gz: youtube-dl README.md README.txt youtube-dl.1 youtube-dl.bash-completion youtube-dl.zsh youtube-dl.fish ChangeLog AUTHORS
|
||||||
@tar -czf youtube-dl.tar.gz --transform "s|^|youtube-dl/|" --owner 0 --group 0 \
|
@tar -czf youtube-dl.tar.gz --transform "s|^|youtube-dl/|" --owner 0 --group 0 \
|
||||||
--exclude '*.DS_Store' \
|
--exclude '*.DS_Store' \
|
||||||
--exclude '*.kate-swp' \
|
--exclude '*.kate-swp' \
|
||||||
@ -110,11 +119,10 @@ youtube-dl.tar.gz: youtube-dl README.md README.txt youtube-dl.1 youtube-dl.bash-
|
|||||||
--exclude '*~' \
|
--exclude '*~' \
|
||||||
--exclude '__pycache__' \
|
--exclude '__pycache__' \
|
||||||
--exclude '.git' \
|
--exclude '.git' \
|
||||||
--exclude 'testdata' \
|
|
||||||
--exclude 'docs/_build' \
|
--exclude 'docs/_build' \
|
||||||
-- \
|
-- \
|
||||||
bin devscripts test youtube_dl docs \
|
bin devscripts test youtube_dl docs \
|
||||||
ChangeLog LICENSE README.md README.txt \
|
ChangeLog AUTHORS LICENSE README.md README.txt \
|
||||||
Makefile MANIFEST.in youtube-dl.1 youtube-dl.bash-completion \
|
Makefile MANIFEST.in youtube-dl.1 youtube-dl.bash-completion \
|
||||||
youtube-dl.zsh youtube-dl.fish setup.py \
|
youtube-dl.zsh youtube-dl.fish setup.py setup.cfg \
|
||||||
youtube-dl
|
youtube-dl
|
||||||
|
@ -511,6 +511,9 @@ The basic usage is not to set any template arguments when downloading a single f
|
|||||||
- `average_rating` (numeric): Average rating give by users, the scale used depends on the webpage
|
- `average_rating` (numeric): Average rating give by users, the scale used depends on the webpage
|
||||||
- `comment_count` (numeric): Number of comments on the video
|
- `comment_count` (numeric): Number of comments on the video
|
||||||
- `age_limit` (numeric): Age restriction for the video (years)
|
- `age_limit` (numeric): Age restriction for the video (years)
|
||||||
|
- `is_live` (boolean): Whether this video is a live stream or a fixed-length video
|
||||||
|
- `start_time` (numeric): Time in seconds where the reproduction should start, as specified in the URL
|
||||||
|
- `end_time` (numeric): Time in seconds where the reproduction should end, as specified in the URL
|
||||||
- `format` (string): A human-readable description of the format
|
- `format` (string): A human-readable description of the format
|
||||||
- `format_id` (string): Format code specified by `--format`
|
- `format_id` (string): Format code specified by `--format`
|
||||||
- `format_note` (string): Additional info about the format
|
- `format_note` (string): Additional info about the format
|
||||||
@ -536,6 +539,8 @@ The basic usage is not to set any template arguments when downloading a single f
|
|||||||
- `playlist_index` (numeric): Index of the video in the playlist padded with leading zeros according to the total length of the playlist
|
- `playlist_index` (numeric): Index of the video in the playlist padded with leading zeros according to the total length of the playlist
|
||||||
- `playlist_id` (string): Playlist identifier
|
- `playlist_id` (string): Playlist identifier
|
||||||
- `playlist_title` (string): Playlist title
|
- `playlist_title` (string): Playlist title
|
||||||
|
- `playlist_uploader` (string): Full name of the playlist uploader
|
||||||
|
- `playlist_uploader_id` (string): Nickname or id of the playlist uploader
|
||||||
|
|
||||||
Available for the video that belongs to some logical chapter or section:
|
Available for the video that belongs to some logical chapter or section:
|
||||||
|
|
||||||
|
5
devscripts/install_jython.sh
Executable file
5
devscripts/install_jython.sh
Executable file
@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
wget http://central.maven.org/maven2/org/python/jython-installer/2.7.1/jython-installer-2.7.1.jar
|
||||||
|
java -jar jython-installer-2.7.1.jar -s -d "$HOME/jython"
|
||||||
|
$HOME/jython/bin/jython -m pip install nose
|
@ -1,6 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
DOWNLOAD_TESTS="age_restriction|download|subtitles|write_annotations|iqiyi_sdk_interpreter|youtube_lists"
|
# Keep this list in sync with the `offlinetest` target in Makefile
|
||||||
|
DOWNLOAD_TESTS="age_restriction|download|iqiyi_sdk_interpreter|socks|subtitles|write_annotations|youtube_lists|youtube_signature"
|
||||||
|
|
||||||
test_set=""
|
test_set=""
|
||||||
multiprocess_args=""
|
multiprocess_args=""
|
||||||
|
@ -3,8 +3,7 @@
|
|||||||
- **1up.com**
|
- **1up.com**
|
||||||
- **20min**
|
- **20min**
|
||||||
- **220.ro**
|
- **220.ro**
|
||||||
- **22tracks:genre**
|
- **23video**
|
||||||
- **22tracks:track**
|
|
||||||
- **24video**
|
- **24video**
|
||||||
- **3qsdn**: 3Q SDN
|
- **3qsdn**: 3Q SDN
|
||||||
- **3sat**
|
- **3sat**
|
||||||
@ -12,6 +11,7 @@
|
|||||||
- **56.com**
|
- **56.com**
|
||||||
- **5min**
|
- **5min**
|
||||||
- **6play**
|
- **6play**
|
||||||
|
- **7plus**
|
||||||
- **8tracks**
|
- **8tracks**
|
||||||
- **91porn**
|
- **91porn**
|
||||||
- **9c9media**
|
- **9c9media**
|
||||||
@ -114,16 +114,16 @@
|
|||||||
- **BokeCC**
|
- **BokeCC**
|
||||||
- **BostonGlobe**
|
- **BostonGlobe**
|
||||||
- **Bpb**: Bundeszentrale für politische Bildung
|
- **Bpb**: Bundeszentrale für politische Bildung
|
||||||
- **BR**: Bayerischer Rundfunk Mediathek
|
- **BR**: Bayerischer Rundfunk
|
||||||
- **BravoTV**
|
- **BravoTV**
|
||||||
- **Break**
|
- **Break**
|
||||||
- **brightcove:legacy**
|
- **brightcove:legacy**
|
||||||
- **brightcove:new**
|
- **brightcove:new**
|
||||||
|
- **BRMediathek**: Bayerischer Rundfunk Mediathek
|
||||||
- **bt:article**: Bergens Tidende Articles
|
- **bt:article**: Bergens Tidende Articles
|
||||||
- **bt:vestlendingen**: Bergens Tidende - Vestlendingen
|
- **bt:vestlendingen**: Bergens Tidende - Vestlendingen
|
||||||
- **BuzzFeed**
|
- **BuzzFeed**
|
||||||
- **BYUtv**
|
- **BYUtv**
|
||||||
- **BYUtvEvent**
|
|
||||||
- **Camdemy**
|
- **Camdemy**
|
||||||
- **CamdemyFolder**
|
- **CamdemyFolder**
|
||||||
- **CamWithHer**
|
- **CamWithHer**
|
||||||
@ -171,7 +171,6 @@
|
|||||||
- **CNN**
|
- **CNN**
|
||||||
- **CNNArticle**
|
- **CNNArticle**
|
||||||
- **CNNBlogs**
|
- **CNNBlogs**
|
||||||
- **CollegeRama**
|
|
||||||
- **ComCarCoff**
|
- **ComCarCoff**
|
||||||
- **ComedyCentral**
|
- **ComedyCentral**
|
||||||
- **ComedyCentralFullEpisodes**
|
- **ComedyCentralFullEpisodes**
|
||||||
@ -199,9 +198,8 @@
|
|||||||
- **dailymotion**
|
- **dailymotion**
|
||||||
- **dailymotion:playlist**
|
- **dailymotion:playlist**
|
||||||
- **dailymotion:user**
|
- **dailymotion:user**
|
||||||
- **DailymotionCloud**
|
- **DaisukiMotto**
|
||||||
- **Daisuki**
|
- **DaisukiMottoPlaylist**
|
||||||
- **DaisukiPlaylist**
|
|
||||||
- **daum.net**
|
- **daum.net**
|
||||||
- **daum.net:clip**
|
- **daum.net:clip**
|
||||||
- **daum.net:playlist**
|
- **daum.net:playlist**
|
||||||
@ -244,8 +242,9 @@
|
|||||||
- **eHow**
|
- **eHow**
|
||||||
- **Einthusan**
|
- **Einthusan**
|
||||||
- **eitb.tv**
|
- **eitb.tv**
|
||||||
- **EllenTV**
|
- **EllenTube**
|
||||||
- **EllenTV:clips**
|
- **EllenTubePlaylist**
|
||||||
|
- **EllenTubeVideo**
|
||||||
- **ElPais**: El País
|
- **ElPais**: El País
|
||||||
- **Embedly**
|
- **Embedly**
|
||||||
- **EMPFlix**
|
- **EMPFlix**
|
||||||
@ -268,10 +267,10 @@
|
|||||||
- **fc2**
|
- **fc2**
|
||||||
- **fc2:embed**
|
- **fc2:embed**
|
||||||
- **Fczenit**
|
- **Fczenit**
|
||||||
- **fernsehkritik.tv**
|
|
||||||
- **filmon**
|
- **filmon**
|
||||||
- **filmon:channel**
|
- **filmon:channel**
|
||||||
- **Firstpost**
|
- **Filmweb**
|
||||||
|
- **FiveThirtyEight**
|
||||||
- **FiveTV**
|
- **FiveTV**
|
||||||
- **Flickr**
|
- **Flickr**
|
||||||
- **Flipagram**
|
- **Flipagram**
|
||||||
@ -285,7 +284,7 @@
|
|||||||
- **foxnews:article**
|
- **foxnews:article**
|
||||||
- **foxnews:insider**
|
- **foxnews:insider**
|
||||||
- **FoxSports**
|
- **FoxSports**
|
||||||
- **france2.fr:generation-quoi**
|
- **france2.fr:generation-what**
|
||||||
- **FranceCulture**
|
- **FranceCulture**
|
||||||
- **FranceInter**
|
- **FranceInter**
|
||||||
- **FranceTV**
|
- **FranceTV**
|
||||||
@ -303,7 +302,6 @@
|
|||||||
- **GameInformer**
|
- **GameInformer**
|
||||||
- **GameOne**
|
- **GameOne**
|
||||||
- **gameone:playlist**
|
- **gameone:playlist**
|
||||||
- **Gamersyde**
|
|
||||||
- **GameSpot**
|
- **GameSpot**
|
||||||
- **GameStar**
|
- **GameStar**
|
||||||
- **Gaskrank**
|
- **Gaskrank**
|
||||||
@ -342,6 +340,7 @@
|
|||||||
- **HornBunny**
|
- **HornBunny**
|
||||||
- **HotNewHipHop**
|
- **HotNewHipHop**
|
||||||
- **HotStar**
|
- **HotStar**
|
||||||
|
- **hotstar:playlist**
|
||||||
- **Howcast**
|
- **Howcast**
|
||||||
- **HowStuffWorks**
|
- **HowStuffWorks**
|
||||||
- **HRTi**
|
- **HRTi**
|
||||||
@ -362,6 +361,7 @@
|
|||||||
- **InfoQ**
|
- **InfoQ**
|
||||||
- **Instagram**
|
- **Instagram**
|
||||||
- **instagram:user**: Instagram user profile
|
- **instagram:user**: Instagram user profile
|
||||||
|
- **Internazionale**
|
||||||
- **InternetVideoArchive**
|
- **InternetVideoArchive**
|
||||||
- **IPrima**
|
- **IPrima**
|
||||||
- **iqiyi**: 爱奇艺
|
- **iqiyi**: 爱奇艺
|
||||||
@ -442,11 +442,13 @@
|
|||||||
- **mangomolo:live**
|
- **mangomolo:live**
|
||||||
- **mangomolo:video**
|
- **mangomolo:video**
|
||||||
- **ManyVids**
|
- **ManyVids**
|
||||||
|
- **massengeschmack.tv**
|
||||||
- **MatchTV**
|
- **MatchTV**
|
||||||
- **MDR**: MDR.DE and KiKA
|
- **MDR**: MDR.DE and KiKA
|
||||||
- **media.ccc.de**
|
- **media.ccc.de**
|
||||||
- **Medialaan**
|
- **Medialaan**
|
||||||
- **Mediaset**
|
- **Mediaset**
|
||||||
|
- **Mediasite**
|
||||||
- **Medici**
|
- **Medici**
|
||||||
- **megaphone.fm**: megaphone.fm embedded players
|
- **megaphone.fm**: megaphone.fm embedded players
|
||||||
- **Meipai**: 美拍
|
- **Meipai**: 美拍
|
||||||
@ -540,6 +542,7 @@
|
|||||||
- **nhl.com:videocenter:category**: NHL videocenter category
|
- **nhl.com:videocenter:category**: NHL videocenter category
|
||||||
- **nick.com**
|
- **nick.com**
|
||||||
- **nick.de**
|
- **nick.de**
|
||||||
|
- **nickelodeon:br**
|
||||||
- **nickelodeonru**
|
- **nickelodeonru**
|
||||||
- **nicknight**
|
- **nicknight**
|
||||||
- **niconico**: ニコニコ動画
|
- **niconico**: ニコニコ動画
|
||||||
@ -558,8 +561,6 @@
|
|||||||
- **nowness**
|
- **nowness**
|
||||||
- **nowness:playlist**
|
- **nowness:playlist**
|
||||||
- **nowness:series**
|
- **nowness:series**
|
||||||
- **NowTV** (Currently broken)
|
|
||||||
- **NowTVList**
|
|
||||||
- **nowvideo**: NowVideo
|
- **nowvideo**: NowVideo
|
||||||
- **Noz**
|
- **Noz**
|
||||||
- **npo**: npo.nl, ntr.nl, omroepwnl.nl, zapp.nl and npo3.nl
|
- **npo**: npo.nl, ntr.nl, omroepwnl.nl, zapp.nl and npo3.nl
|
||||||
@ -609,6 +610,7 @@
|
|||||||
- **pcmag**
|
- **pcmag**
|
||||||
- **PearVideo**
|
- **PearVideo**
|
||||||
- **People**
|
- **People**
|
||||||
|
- **PerformGroup**
|
||||||
- **periscope**: Periscope
|
- **periscope**: Periscope
|
||||||
- **periscope:user**: Periscope user videos
|
- **periscope:user**: Periscope user videos
|
||||||
- **PhilharmonieDeParis**: Philharmonie de Paris
|
- **PhilharmonieDeParis**: Philharmonie de Paris
|
||||||
@ -663,6 +665,7 @@
|
|||||||
- **Rai**
|
- **Rai**
|
||||||
- **RaiPlay**
|
- **RaiPlay**
|
||||||
- **RaiPlayLive**
|
- **RaiPlayLive**
|
||||||
|
- **RaiPlayPlaylist**
|
||||||
- **RBMARadio**
|
- **RBMARadio**
|
||||||
- **RDS**: RDS.ca
|
- **RDS**: RDS.ca
|
||||||
- **RedBullTV**
|
- **RedBullTV**
|
||||||
@ -714,7 +717,6 @@
|
|||||||
- **safari**: safaribooksonline.com online video
|
- **safari**: safaribooksonline.com online video
|
||||||
- **safari:api**
|
- **safari:api**
|
||||||
- **safari:course**: safaribooksonline.com online courses
|
- **safari:course**: safaribooksonline.com online courses
|
||||||
- **Sandia**: Sandia National Laboratories
|
|
||||||
- **Sapo**: SAPO Vídeos
|
- **Sapo**: SAPO Vídeos
|
||||||
- **savefrom.net**
|
- **savefrom.net**
|
||||||
- **SBS**: sbs.com.au
|
- **SBS**: sbs.com.au
|
||||||
@ -730,6 +732,7 @@
|
|||||||
- **Servus**
|
- **Servus**
|
||||||
- **Sexu**
|
- **Sexu**
|
||||||
- **Shahid**
|
- **Shahid**
|
||||||
|
- **ShahidShow**
|
||||||
- **Shared**: shared.sx
|
- **Shared**: shared.sx
|
||||||
- **ShowRoomLive**
|
- **ShowRoomLive**
|
||||||
- **Sina**
|
- **Sina**
|
||||||
@ -782,6 +785,7 @@
|
|||||||
- **streamcloud.eu**
|
- **streamcloud.eu**
|
||||||
- **StreamCZ**
|
- **StreamCZ**
|
||||||
- **StreetVoice**
|
- **StreetVoice**
|
||||||
|
- **StretchInternet**
|
||||||
- **SunPorno**
|
- **SunPorno**
|
||||||
- **SVT**
|
- **SVT**
|
||||||
- **SVTPlay**: SVT Play and Öppet arkiv
|
- **SVTPlay**: SVT Play and Öppet arkiv
|
||||||
@ -793,7 +797,7 @@
|
|||||||
- **tagesschau:player**
|
- **tagesschau:player**
|
||||||
- **Tass**
|
- **Tass**
|
||||||
- **TastyTrade**
|
- **TastyTrade**
|
||||||
- **TBS** (Currently broken)
|
- **TBS**
|
||||||
- **TDSLifeway**
|
- **TDSLifeway**
|
||||||
- **teachertube**: teachertube.com videos
|
- **teachertube**: teachertube.com videos
|
||||||
- **teachertube:user:collection**: teachertube.com user and collection videos
|
- **teachertube:user:collection**: teachertube.com user and collection videos
|
||||||
@ -864,6 +868,8 @@
|
|||||||
- **tvland.com**
|
- **tvland.com**
|
||||||
- **TVN24**
|
- **TVN24**
|
||||||
- **TVNoe**
|
- **TVNoe**
|
||||||
|
- **TVNow**
|
||||||
|
- **TVNowList**
|
||||||
- **tvp**: Telewizja Polska
|
- **tvp**: Telewizja Polska
|
||||||
- **tvp:embed**: Telewizja Polska
|
- **tvp:embed**: Telewizja Polska
|
||||||
- **tvp:series**
|
- **tvp:series**
|
||||||
@ -885,7 +891,9 @@
|
|||||||
- **udemy**
|
- **udemy**
|
||||||
- **udemy:course**
|
- **udemy:course**
|
||||||
- **UDNEmbed**: 聯合影音
|
- **UDNEmbed**: 聯合影音
|
||||||
|
- **UFCTV**
|
||||||
- **UKTVPlay**
|
- **UKTVPlay**
|
||||||
|
- **umg:de**: Universal Music Deutschland
|
||||||
- **Unistra**
|
- **Unistra**
|
||||||
- **Unity**
|
- **Unity**
|
||||||
- **uol.com.br**
|
- **uol.com.br**
|
||||||
|
1
setup.py
1
setup.py
@ -109,6 +109,7 @@ setup(
|
|||||||
author_email='ytdl@yt-dl.org',
|
author_email='ytdl@yt-dl.org',
|
||||||
maintainer='Sergey M.',
|
maintainer='Sergey M.',
|
||||||
maintainer_email='dstftw@gmail.com',
|
maintainer_email='dstftw@gmail.com',
|
||||||
|
license='Unlicense',
|
||||||
packages=[
|
packages=[
|
||||||
'youtube_dl',
|
'youtube_dl',
|
||||||
'youtube_dl.extractor', 'youtube_dl.downloader',
|
'youtube_dl.extractor', 'youtube_dl.downloader',
|
||||||
|
@ -493,9 +493,20 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
|
|||||||
_TEST_CASES = [
|
_TEST_CASES = [
|
||||||
(
|
(
|
||||||
# https://github.com/rg3/youtube-dl/issues/13919
|
# https://github.com/rg3/youtube-dl/issues/13919
|
||||||
|
# Also tests duplicate representation ids, see
|
||||||
|
# https://github.com/rg3/youtube-dl/issues/15111
|
||||||
'float_duration',
|
'float_duration',
|
||||||
'http://unknown/manifest.mpd',
|
'http://unknown/manifest.mpd',
|
||||||
[{
|
[{
|
||||||
|
'manifest_url': 'http://unknown/manifest.mpd',
|
||||||
|
'ext': 'm4a',
|
||||||
|
'format_id': '318597',
|
||||||
|
'format_note': 'DASH audio',
|
||||||
|
'protocol': 'http_dash_segments',
|
||||||
|
'acodec': 'mp4a.40.2',
|
||||||
|
'vcodec': 'none',
|
||||||
|
'tbr': 61.587,
|
||||||
|
}, {
|
||||||
'manifest_url': 'http://unknown/manifest.mpd',
|
'manifest_url': 'http://unknown/manifest.mpd',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'format_id': '318597',
|
'format_id': '318597',
|
||||||
@ -562,7 +573,89 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
|
|||||||
'width': 1920,
|
'width': 1920,
|
||||||
'height': 1080,
|
'height': 1080,
|
||||||
}]
|
}]
|
||||||
),
|
), (
|
||||||
|
# https://github.com/rg3/youtube-dl/pull/14844
|
||||||
|
'urls_only',
|
||||||
|
'http://unknown/manifest.mpd',
|
||||||
|
[{
|
||||||
|
'manifest_url': 'http://unknown/manifest.mpd',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'format_id': 'h264_aac_144p_m4s',
|
||||||
|
'format_note': 'DASH video',
|
||||||
|
'protocol': 'http_dash_segments',
|
||||||
|
'acodec': 'mp4a.40.2',
|
||||||
|
'vcodec': 'avc3.42c01e',
|
||||||
|
'tbr': 200,
|
||||||
|
'width': 256,
|
||||||
|
'height': 144,
|
||||||
|
}, {
|
||||||
|
'manifest_url': 'http://unknown/manifest.mpd',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'format_id': 'h264_aac_240p_m4s',
|
||||||
|
'format_note': 'DASH video',
|
||||||
|
'protocol': 'http_dash_segments',
|
||||||
|
'acodec': 'mp4a.40.2',
|
||||||
|
'vcodec': 'avc3.42c01e',
|
||||||
|
'tbr': 400,
|
||||||
|
'width': 424,
|
||||||
|
'height': 240,
|
||||||
|
}, {
|
||||||
|
'manifest_url': 'http://unknown/manifest.mpd',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'format_id': 'h264_aac_360p_m4s',
|
||||||
|
'format_note': 'DASH video',
|
||||||
|
'protocol': 'http_dash_segments',
|
||||||
|
'acodec': 'mp4a.40.2',
|
||||||
|
'vcodec': 'avc3.42c01e',
|
||||||
|
'tbr': 800,
|
||||||
|
'width': 640,
|
||||||
|
'height': 360,
|
||||||
|
}, {
|
||||||
|
'manifest_url': 'http://unknown/manifest.mpd',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'format_id': 'h264_aac_480p_m4s',
|
||||||
|
'format_note': 'DASH video',
|
||||||
|
'protocol': 'http_dash_segments',
|
||||||
|
'acodec': 'mp4a.40.2',
|
||||||
|
'vcodec': 'avc3.42c01e',
|
||||||
|
'tbr': 1200,
|
||||||
|
'width': 856,
|
||||||
|
'height': 480,
|
||||||
|
}, {
|
||||||
|
'manifest_url': 'http://unknown/manifest.mpd',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'format_id': 'h264_aac_576p_m4s',
|
||||||
|
'format_note': 'DASH video',
|
||||||
|
'protocol': 'http_dash_segments',
|
||||||
|
'acodec': 'mp4a.40.2',
|
||||||
|
'vcodec': 'avc3.42c01e',
|
||||||
|
'tbr': 1600,
|
||||||
|
'width': 1024,
|
||||||
|
'height': 576,
|
||||||
|
}, {
|
||||||
|
'manifest_url': 'http://unknown/manifest.mpd',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'format_id': 'h264_aac_720p_m4s',
|
||||||
|
'format_note': 'DASH video',
|
||||||
|
'protocol': 'http_dash_segments',
|
||||||
|
'acodec': 'mp4a.40.2',
|
||||||
|
'vcodec': 'avc3.42c01e',
|
||||||
|
'tbr': 2400,
|
||||||
|
'width': 1280,
|
||||||
|
'height': 720,
|
||||||
|
}, {
|
||||||
|
'manifest_url': 'http://unknown/manifest.mpd',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'format_id': 'h264_aac_1080p_m4s',
|
||||||
|
'format_note': 'DASH video',
|
||||||
|
'protocol': 'http_dash_segments',
|
||||||
|
'acodec': 'mp4a.40.2',
|
||||||
|
'vcodec': 'avc3.42c01e',
|
||||||
|
'tbr': 4400,
|
||||||
|
'width': 1920,
|
||||||
|
'height': 1080,
|
||||||
|
}]
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
for mpd_file, mpd_url, expected_formats in _TEST_CASES:
|
for mpd_file, mpd_url, expected_formats in _TEST_CASES:
|
||||||
@ -574,6 +667,33 @@ jwplayer("mediaplayer").setup({"abouttext":"Visit Indie DB","aboutlink":"http:\/
|
|||||||
self.ie._sort_formats(formats)
|
self.ie._sort_formats(formats)
|
||||||
expect_value(self, formats, expected_formats, None)
|
expect_value(self, formats, expected_formats, None)
|
||||||
|
|
||||||
|
def test_parse_f4m_formats(self):
|
||||||
|
_TEST_CASES = [
|
||||||
|
(
|
||||||
|
# https://github.com/rg3/youtube-dl/issues/14660
|
||||||
|
'custom_base_url',
|
||||||
|
'http://api.new.livestream.com/accounts/6115179/events/6764928/videos/144884262.f4m',
|
||||||
|
[{
|
||||||
|
'manifest_url': 'http://api.new.livestream.com/accounts/6115179/events/6764928/videos/144884262.f4m',
|
||||||
|
'ext': 'flv',
|
||||||
|
'format_id': '2148',
|
||||||
|
'protocol': 'f4m',
|
||||||
|
'tbr': 2148,
|
||||||
|
'width': 1280,
|
||||||
|
'height': 720,
|
||||||
|
}]
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
for f4m_file, f4m_url, expected_formats in _TEST_CASES:
|
||||||
|
with io.open('./test/testdata/f4m/%s.f4m' % f4m_file,
|
||||||
|
mode='r', encoding='utf-8') as f:
|
||||||
|
formats = self.ie._parse_f4m_formats(
|
||||||
|
compat_etree_fromstring(f.read().encode('utf-8')),
|
||||||
|
f4m_url, None)
|
||||||
|
self.ie._sort_formats(formats)
|
||||||
|
expect_value(self, formats, expected_formats, None)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@ -466,11 +466,11 @@ class TestFormatSelection(unittest.TestCase):
|
|||||||
ydl = YDL({'simulate': True})
|
ydl = YDL({'simulate': True})
|
||||||
self.assertEqual(ydl._default_format_spec({}), 'bestvideo+bestaudio/best')
|
self.assertEqual(ydl._default_format_spec({}), 'bestvideo+bestaudio/best')
|
||||||
|
|
||||||
ydl = YDL({'is_live': True})
|
ydl = YDL({})
|
||||||
self.assertEqual(ydl._default_format_spec({}), 'best/bestvideo+bestaudio')
|
self.assertEqual(ydl._default_format_spec({'is_live': True}), 'best/bestvideo+bestaudio')
|
||||||
|
|
||||||
ydl = YDL({'simulate': True, 'is_live': True})
|
ydl = YDL({'simulate': True})
|
||||||
self.assertEqual(ydl._default_format_spec({}), 'bestvideo+bestaudio/best')
|
self.assertEqual(ydl._default_format_spec({'is_live': True}), 'bestvideo+bestaudio/best')
|
||||||
|
|
||||||
ydl = YDL({'outtmpl': '-'})
|
ydl = YDL({'outtmpl': '-'})
|
||||||
self.assertEqual(ydl._default_format_spec({}), 'best/bestvideo+bestaudio')
|
self.assertEqual(ydl._default_format_spec({}), 'best/bestvideo+bestaudio')
|
||||||
|
@ -343,6 +343,7 @@ class TestUtil(unittest.TestCase):
|
|||||||
self.assertEqual(unified_timestamp('Feb 7, 2016 at 6:35 pm'), 1454870100)
|
self.assertEqual(unified_timestamp('Feb 7, 2016 at 6:35 pm'), 1454870100)
|
||||||
self.assertEqual(unified_timestamp('2017-03-30T17:52:41Q'), 1490896361)
|
self.assertEqual(unified_timestamp('2017-03-30T17:52:41Q'), 1490896361)
|
||||||
self.assertEqual(unified_timestamp('Sep 11, 2013 | 5:49 AM'), 1378878540)
|
self.assertEqual(unified_timestamp('Sep 11, 2013 | 5:49 AM'), 1378878540)
|
||||||
|
self.assertEqual(unified_timestamp('December 15, 2017 at 7:49 am'), 1513324140)
|
||||||
|
|
||||||
def test_determine_ext(self):
|
def test_determine_ext(self):
|
||||||
self.assertEqual(determine_ext('http://example.com/foo/bar.mp4/?download'), 'mp4')
|
self.assertEqual(determine_ext('http://example.com/foo/bar.mp4/?download'), 'mp4')
|
||||||
|
10
test/testdata/f4m/custom_base_url.f4m
vendored
Normal file
10
test/testdata/f4m/custom_base_url.f4m
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<manifest xmlns="http://ns.adobe.com/f4m/1.0">
|
||||||
|
<streamType>recorded</streamType>
|
||||||
|
<baseURL>http://vod.livestream.com/events/0000000000673980/</baseURL>
|
||||||
|
<duration>269.293</duration>
|
||||||
|
<bootstrapInfo profile="named" id="bootstrap_1">AAAAm2Fic3QAAAAAAAAAAQAAAAPoAAAAAAAEG+0AAAAAAAAAAAAAAAAAAQAAABlhc3J0AAAAAAAAAAABAAAAAQAAAC4BAAAAVmFmcnQAAAAAAAAD6AAAAAAEAAAAAQAAAAAAAAAAAAAXcAAAAC0AAAAAAAQHQAAAE5UAAAAuAAAAAAAEGtUAAAEYAAAAAAAAAAAAAAAAAAAAAAA=</bootstrapInfo>
|
||||||
|
<media url="b90f532f-b0f6-4f4e-8289-706d490b2fd8_2292" bootstrapInfoId="bootstrap_1" bitrate="2148" width="1280" height="720" videoCodec="avc1.4d401f" audioCodec="mp4a.40.2">
|
||||||
|
<metadata>AgAKb25NZXRhRGF0YQgAAAAIAAhkdXJhdGlvbgBAcNSwIMSbpgAFd2lkdGgAQJQAAAAAAAAABmhlaWdodABAhoAAAAAAAAAJZnJhbWVyYXRlAEA4/7DoLwW3AA12aWRlb2RhdGFyYXRlAECe1DLgjcobAAx2aWRlb2NvZGVjaWQAQBwAAAAAAAAADWF1ZGlvZGF0YXJhdGUAQGSimlvaPKQADGF1ZGlvY29kZWNpZABAJAAAAAAAAAAACQ==</metadata>
|
||||||
|
</media>
|
||||||
|
</manifest>
|
218
test/testdata/mpd/urls_only.mpd
vendored
Normal file
218
test/testdata/mpd/urls_only.mpd
vendored
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
<?xml version="1.0" ?>
|
||||||
|
<MPD maxSegmentDuration="PT0H0M10.000S" mediaPresentationDuration="PT0H4M1.728S" minBufferTime="PT1.500S" profiles="urn:mpeg:dash:profile:isoff-main:2011" type="static" xmlns="urn:mpeg:dash:schema:mpd:2011">
|
||||||
|
<Period duration="PT0H4M1.728S">
|
||||||
|
<AdaptationSet bitstreamSwitching="true" lang="und" maxHeight="1080" maxWidth="1920" par="16:9" segmentAlignment="true">
|
||||||
|
<ContentComponent contentType="video" id="1"/>
|
||||||
|
<Representation audioSamplingRate="44100" bandwidth="200000" codecs="avc3.42c01e,mp4a.40.2" frameRate="25" height="144" id="h264_aac_144p_m4s" mimeType="video/mp4" sar="1:1" startWithSAP="1" width="256">
|
||||||
|
<SegmentList duration="10000" timescale="1000">
|
||||||
|
<Initialization sourceURL="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_144p_m4s/init/432f65a0.mp4"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_144p_m4s/0/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_144p_m4s/1/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_144p_m4s/2/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_144p_m4s/3/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_144p_m4s/4/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_144p_m4s/5/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_144p_m4s/6/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_144p_m4s/7/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_144p_m4s/8/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_144p_m4s/9/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_144p_m4s/10/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_144p_m4s/11/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_144p_m4s/12/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_144p_m4s/13/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_144p_m4s/14/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_144p_m4s/15/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_144p_m4s/16/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_144p_m4s/17/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_144p_m4s/18/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_144p_m4s/19/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_144p_m4s/20/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_144p_m4s/21/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_144p_m4s/22/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_144p_m4s/23/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_144p_m4s/24/432f65a0.m4s"/>
|
||||||
|
</SegmentList>
|
||||||
|
</Representation>
|
||||||
|
<Representation audioSamplingRate="44100" bandwidth="400000" codecs="avc3.42c01e,mp4a.40.2" frameRate="25" height="240" id="h264_aac_240p_m4s" mimeType="video/mp4" sar="160:159" startWithSAP="1" width="424">
|
||||||
|
<SegmentList duration="10000" timescale="1000">
|
||||||
|
<Initialization sourceURL="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_240p_m4s/init/432f65a0.mp4"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_240p_m4s/0/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_240p_m4s/1/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_240p_m4s/2/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_240p_m4s/3/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_240p_m4s/4/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_240p_m4s/5/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_240p_m4s/6/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_240p_m4s/7/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_240p_m4s/8/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_240p_m4s/9/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_240p_m4s/10/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_240p_m4s/11/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_240p_m4s/12/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_240p_m4s/13/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_240p_m4s/14/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_240p_m4s/15/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_240p_m4s/16/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_240p_m4s/17/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_240p_m4s/18/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_240p_m4s/19/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_240p_m4s/20/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_240p_m4s/21/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_240p_m4s/22/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_240p_m4s/23/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_240p_m4s/24/432f65a0.m4s"/>
|
||||||
|
</SegmentList>
|
||||||
|
</Representation>
|
||||||
|
<Representation audioSamplingRate="44100" bandwidth="800000" codecs="avc3.42c01e,mp4a.40.2" frameRate="25" height="360" id="h264_aac_360p_m4s" mimeType="video/mp4" sar="1:1" startWithSAP="1" width="640">
|
||||||
|
<SegmentList duration="10000" timescale="1000">
|
||||||
|
<Initialization sourceURL="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_360p_m4s/init/432f65a0.mp4"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_360p_m4s/0/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_360p_m4s/1/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_360p_m4s/2/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_360p_m4s/3/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_360p_m4s/4/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_360p_m4s/5/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_360p_m4s/6/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_360p_m4s/7/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_360p_m4s/8/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_360p_m4s/9/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_360p_m4s/10/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_360p_m4s/11/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_360p_m4s/12/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_360p_m4s/13/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_360p_m4s/14/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_360p_m4s/15/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_360p_m4s/16/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_360p_m4s/17/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_360p_m4s/18/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_360p_m4s/19/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_360p_m4s/20/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_360p_m4s/21/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_360p_m4s/22/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_360p_m4s/23/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_360p_m4s/24/432f65a0.m4s"/>
|
||||||
|
</SegmentList>
|
||||||
|
</Representation>
|
||||||
|
<Representation audioSamplingRate="44100" bandwidth="1200000" codecs="avc3.42c01e,mp4a.40.2" frameRate="25" height="480" id="h264_aac_480p_m4s" mimeType="video/mp4" sar="320:321" startWithSAP="1" width="856">
|
||||||
|
<SegmentList duration="10000" timescale="1000">
|
||||||
|
<Initialization sourceURL="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_480p_m4s/init/432f65a0.mp4"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_480p_m4s/0/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_480p_m4s/1/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_480p_m4s/2/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_480p_m4s/3/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_480p_m4s/4/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_480p_m4s/5/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_480p_m4s/6/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_480p_m4s/7/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_480p_m4s/8/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_480p_m4s/9/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_480p_m4s/10/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_480p_m4s/11/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_480p_m4s/12/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_480p_m4s/13/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_480p_m4s/14/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_480p_m4s/15/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_480p_m4s/16/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_480p_m4s/17/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_480p_m4s/18/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_480p_m4s/19/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_480p_m4s/20/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_480p_m4s/21/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_480p_m4s/22/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_480p_m4s/23/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_480p_m4s/24/432f65a0.m4s"/>
|
||||||
|
</SegmentList>
|
||||||
|
</Representation>
|
||||||
|
<Representation audioSamplingRate="44100" bandwidth="1600000" codecs="avc3.42c01e,mp4a.40.2" frameRate="25" height="576" id="h264_aac_576p_m4s" mimeType="video/mp4" sar="1:1" startWithSAP="1" width="1024">
|
||||||
|
<SegmentList duration="10000" timescale="1000">
|
||||||
|
<Initialization sourceURL="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_576p_m4s/init/432f65a0.mp4"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_576p_m4s/0/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_576p_m4s/1/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_576p_m4s/2/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_576p_m4s/3/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_576p_m4s/4/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_576p_m4s/5/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_576p_m4s/6/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_576p_m4s/7/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_576p_m4s/8/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_576p_m4s/9/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_576p_m4s/10/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_576p_m4s/11/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_576p_m4s/12/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_576p_m4s/13/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_576p_m4s/14/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_576p_m4s/15/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_576p_m4s/16/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_576p_m4s/17/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_576p_m4s/18/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_576p_m4s/19/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_576p_m4s/20/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_576p_m4s/21/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_576p_m4s/22/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_576p_m4s/23/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_576p_m4s/24/432f65a0.m4s"/>
|
||||||
|
</SegmentList>
|
||||||
|
</Representation>
|
||||||
|
<Representation audioSamplingRate="44100" bandwidth="2400000" codecs="avc3.42c01e,mp4a.40.2" frameRate="25" height="720" id="h264_aac_720p_m4s" mimeType="video/mp4" sar="1:1" startWithSAP="1" width="1280">
|
||||||
|
<SegmentList duration="10000" timescale="1000">
|
||||||
|
<Initialization sourceURL="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_720p_m4s/init/432f65a0.mp4"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_720p_m4s/0/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_720p_m4s/1/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_720p_m4s/2/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_720p_m4s/3/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_720p_m4s/4/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_720p_m4s/5/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_720p_m4s/6/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_720p_m4s/7/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_720p_m4s/8/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_720p_m4s/9/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_720p_m4s/10/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_720p_m4s/11/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_720p_m4s/12/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_720p_m4s/13/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_720p_m4s/14/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_720p_m4s/15/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_720p_m4s/16/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_720p_m4s/17/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_720p_m4s/18/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_720p_m4s/19/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_720p_m4s/20/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_720p_m4s/21/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_720p_m4s/22/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_720p_m4s/23/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_720p_m4s/24/432f65a0.m4s"/>
|
||||||
|
</SegmentList>
|
||||||
|
</Representation>
|
||||||
|
<Representation audioSamplingRate="44100" bandwidth="4400000" codecs="avc3.42c01e,mp4a.40.2" frameRate="25" height="1080" id="h264_aac_1080p_m4s" mimeType="video/mp4" sar="1:1" startWithSAP="1" width="1920">
|
||||||
|
<SegmentList duration="10000" timescale="1000">
|
||||||
|
<Initialization sourceURL="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_1080p_m4s/init/432f65a0.mp4"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_1080p_m4s/0/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_1080p_m4s/1/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_1080p_m4s/2/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_1080p_m4s/3/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_1080p_m4s/4/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_1080p_m4s/5/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_1080p_m4s/6/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_1080p_m4s/7/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_1080p_m4s/8/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_1080p_m4s/9/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_1080p_m4s/10/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_1080p_m4s/11/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_1080p_m4s/12/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_1080p_m4s/13/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_1080p_m4s/14/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_1080p_m4s/15/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_1080p_m4s/16/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_1080p_m4s/17/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_1080p_m4s/18/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_1080p_m4s/19/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_1080p_m4s/20/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_1080p_m4s/21/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_1080p_m4s/22/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_1080p_m4s/23/432f65a0.m4s"/>
|
||||||
|
<SegmentURL media="../vd_5999c902ea707c67d8e267a9_1503250723/h264_aac_1080p_m4s/24/432f65a0.m4s"/>
|
||||||
|
</SegmentList>
|
||||||
|
</Representation>
|
||||||
|
</AdaptationSet>
|
||||||
|
</Period>
|
||||||
|
</MPD>
|
@ -948,7 +948,8 @@ class YoutubeDL(object):
|
|||||||
report_download(n_entries)
|
report_download(n_entries)
|
||||||
else: # iterable
|
else: # iterable
|
||||||
if playlistitems:
|
if playlistitems:
|
||||||
entries = make_playlistitems_entries(list(ie_entries))
|
entries = make_playlistitems_entries(list(itertools.islice(
|
||||||
|
ie_entries, 0, max(playlistitems))))
|
||||||
else:
|
else:
|
||||||
entries = list(itertools.islice(
|
entries = list(itertools.islice(
|
||||||
ie_entries, playliststart, playlistend))
|
ie_entries, playliststart, playlistend))
|
||||||
@ -974,6 +975,8 @@ class YoutubeDL(object):
|
|||||||
'playlist': playlist,
|
'playlist': playlist,
|
||||||
'playlist_id': ie_result.get('id'),
|
'playlist_id': ie_result.get('id'),
|
||||||
'playlist_title': ie_result.get('title'),
|
'playlist_title': ie_result.get('title'),
|
||||||
|
'playlist_uploader': ie_result.get('uploader'),
|
||||||
|
'playlist_uploader_id': ie_result.get('uploader_id'),
|
||||||
'playlist_index': i + playliststart,
|
'playlist_index': i + playliststart,
|
||||||
'extractor': ie_result['extractor'],
|
'extractor': ie_result['extractor'],
|
||||||
'webpage_url': ie_result['webpage_url'],
|
'webpage_url': ie_result['webpage_url'],
|
||||||
@ -2230,8 +2233,16 @@ class YoutubeDL(object):
|
|||||||
sys.exc_clear()
|
sys.exc_clear()
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
self._write_string('[debug] Python version %s - %s\n' % (
|
|
||||||
platform.python_version(), platform_name()))
|
def python_implementation():
|
||||||
|
impl_name = platform.python_implementation()
|
||||||
|
if impl_name == 'PyPy' and hasattr(sys, 'pypy_version_info'):
|
||||||
|
return impl_name + ' version %d.%d.%d' % sys.pypy_version_info[:3]
|
||||||
|
return impl_name
|
||||||
|
|
||||||
|
self._write_string('[debug] Python version %s (%s) - %s\n' % (
|
||||||
|
platform.python_version(), python_implementation(),
|
||||||
|
platform_name()))
|
||||||
|
|
||||||
exe_versions = FFmpegPostProcessor.get_versions(self)
|
exe_versions = FFmpegPostProcessor.get_versions(self)
|
||||||
exe_versions['rtmpdump'] = rtmpdump_version()
|
exe_versions['rtmpdump'] = rtmpdump_version()
|
||||||
|
@ -3,12 +3,14 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
import binascii
|
import binascii
|
||||||
import collections
|
import collections
|
||||||
|
import ctypes
|
||||||
import email
|
import email
|
||||||
import getpass
|
import getpass
|
||||||
import io
|
import io
|
||||||
import itertools
|
import itertools
|
||||||
import optparse
|
import optparse
|
||||||
import os
|
import os
|
||||||
|
import platform
|
||||||
import re
|
import re
|
||||||
import shlex
|
import shlex
|
||||||
import shutil
|
import shutil
|
||||||
@ -2906,6 +2908,24 @@ except ImportError: # not 2.6+ or is 3.x
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
compat_zip = zip
|
compat_zip = zip
|
||||||
|
|
||||||
|
if platform.python_implementation() == 'PyPy' and sys.pypy_version_info < (5, 4, 0):
|
||||||
|
# PyPy2 prior to version 5.4.0 expects byte strings as Windows function
|
||||||
|
# names, see the original PyPy issue [1] and the youtube-dl one [2].
|
||||||
|
# 1. https://bitbucket.org/pypy/pypy/issues/2360/windows-ctypescdll-typeerror-function-name
|
||||||
|
# 2. https://github.com/rg3/youtube-dl/pull/4392
|
||||||
|
def compat_ctypes_WINFUNCTYPE(*args, **kwargs):
|
||||||
|
real = ctypes.WINFUNCTYPE(*args, **kwargs)
|
||||||
|
|
||||||
|
def resf(tpl, *args, **kwargs):
|
||||||
|
funcname, dll = tpl
|
||||||
|
return real((str(funcname), dll), *args, **kwargs)
|
||||||
|
|
||||||
|
return resf
|
||||||
|
else:
|
||||||
|
def compat_ctypes_WINFUNCTYPE(*args, **kwargs):
|
||||||
|
return ctypes.WINFUNCTYPE(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'compat_HTMLParseError',
|
'compat_HTMLParseError',
|
||||||
'compat_HTMLParser',
|
'compat_HTMLParser',
|
||||||
@ -2914,6 +2934,7 @@ __all__ = [
|
|||||||
'compat_chr',
|
'compat_chr',
|
||||||
'compat_cookiejar',
|
'compat_cookiejar',
|
||||||
'compat_cookies',
|
'compat_cookies',
|
||||||
|
'compat_ctypes_WINFUNCTYPE',
|
||||||
'compat_etree_fromstring',
|
'compat_etree_fromstring',
|
||||||
'compat_etree_register_namespace',
|
'compat_etree_register_namespace',
|
||||||
'compat_expanduser',
|
'compat_expanduser',
|
||||||
|
@ -243,8 +243,17 @@ def remove_encrypted_media(media):
|
|||||||
media))
|
media))
|
||||||
|
|
||||||
|
|
||||||
def _add_ns(prop):
|
def _add_ns(prop, ver=1):
|
||||||
return '{http://ns.adobe.com/f4m/1.0}%s' % prop
|
return '{http://ns.adobe.com/f4m/%d.0}%s' % (ver, prop)
|
||||||
|
|
||||||
|
|
||||||
|
def get_base_url(manifest):
|
||||||
|
base_url = xpath_text(
|
||||||
|
manifest, [_add_ns('baseURL'), _add_ns('baseURL', 2)],
|
||||||
|
'base URL', default=None)
|
||||||
|
if base_url:
|
||||||
|
base_url = base_url.strip()
|
||||||
|
return base_url
|
||||||
|
|
||||||
|
|
||||||
class F4mFD(FragmentFD):
|
class F4mFD(FragmentFD):
|
||||||
@ -330,13 +339,13 @@ class F4mFD(FragmentFD):
|
|||||||
rate, media = list(filter(
|
rate, media = list(filter(
|
||||||
lambda f: int(f[0]) == requested_bitrate, formats))[0]
|
lambda f: int(f[0]) == requested_bitrate, formats))[0]
|
||||||
|
|
||||||
base_url = compat_urlparse.urljoin(man_url, media.attrib['url'])
|
# Prefer baseURL for relative URLs as per 11.2 of F4M 3.0 spec.
|
||||||
|
man_base_url = get_base_url(doc) or man_url
|
||||||
|
|
||||||
|
base_url = compat_urlparse.urljoin(man_base_url, media.attrib['url'])
|
||||||
bootstrap_node = doc.find(_add_ns('bootstrapInfo'))
|
bootstrap_node = doc.find(_add_ns('bootstrapInfo'))
|
||||||
# From Adobe F4M 3.0 spec:
|
boot_info, bootstrap_url = self._parse_bootstrap_node(
|
||||||
# The <baseURL> element SHALL be the base URL for all relative
|
bootstrap_node, man_base_url)
|
||||||
# (HTTP-based) URLs in the manifest. If <baseURL> is not present, said
|
|
||||||
# URLs should be relative to the location of the containing document.
|
|
||||||
boot_info, bootstrap_url = self._parse_bootstrap_node(bootstrap_node, man_url)
|
|
||||||
live = boot_info['live']
|
live = boot_info['live']
|
||||||
metadata_node = media.find(_add_ns('metadata'))
|
metadata_node = media.find(_add_ns('metadata'))
|
||||||
if metadata_node is not None:
|
if metadata_node is not None:
|
||||||
|
@ -107,11 +107,12 @@ class FragmentFD(FileDownloader):
|
|||||||
def _append_fragment(self, ctx, frag_content):
|
def _append_fragment(self, ctx, frag_content):
|
||||||
try:
|
try:
|
||||||
ctx['dest_stream'].write(frag_content)
|
ctx['dest_stream'].write(frag_content)
|
||||||
|
ctx['dest_stream'].flush()
|
||||||
finally:
|
finally:
|
||||||
if self.__do_ytdl_file(ctx):
|
if self.__do_ytdl_file(ctx):
|
||||||
self._write_ytdl_file(ctx)
|
self._write_ytdl_file(ctx)
|
||||||
if not self.params.get('keep_fragments', False):
|
if not self.params.get('keep_fragments', False):
|
||||||
os.remove(ctx['fragment_filename_sanitized'])
|
os.remove(encodeFilename(ctx['fragment_filename_sanitized']))
|
||||||
del ctx['fragment_filename_sanitized']
|
del ctx['fragment_filename_sanitized']
|
||||||
|
|
||||||
def _prepare_frag_download(self, ctx):
|
def _prepare_frag_download(self, ctx):
|
||||||
|
@ -163,7 +163,8 @@ class HlsFD(FragmentFD):
|
|||||||
return False
|
return False
|
||||||
if decrypt_info['METHOD'] == 'AES-128':
|
if decrypt_info['METHOD'] == 'AES-128':
|
||||||
iv = decrypt_info.get('IV') or compat_struct_pack('>8xq', media_sequence)
|
iv = decrypt_info.get('IV') or compat_struct_pack('>8xq', media_sequence)
|
||||||
decrypt_info['KEY'] = decrypt_info.get('KEY') or self.ydl.urlopen(decrypt_info['URI']).read()
|
decrypt_info['KEY'] = decrypt_info.get('KEY') or self.ydl.urlopen(
|
||||||
|
self._prepare_url(info_dict, decrypt_info['URI'])).read()
|
||||||
frag_content = AES.new(
|
frag_content = AES.new(
|
||||||
decrypt_info['KEY'], AES.MODE_CBC, iv).decrypt(frag_content)
|
decrypt_info['KEY'], AES.MODE_CBC, iv).decrypt(frag_content)
|
||||||
self._append_fragment(ctx, frag_content)
|
self._append_fragment(ctx, frag_content)
|
||||||
|
@ -284,8 +284,7 @@ class HttpFD(FileDownloader):
|
|||||||
while count <= retries:
|
while count <= retries:
|
||||||
try:
|
try:
|
||||||
establish_connection()
|
establish_connection()
|
||||||
download()
|
return download()
|
||||||
return True
|
|
||||||
except RetryDownload as e:
|
except RetryDownload as e:
|
||||||
count += 1
|
count += 1
|
||||||
if count <= retries:
|
if count <= retries:
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
import hmac
|
||||||
import re
|
import re
|
||||||
|
import time
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import compat_str
|
from ..compat import compat_str
|
||||||
@ -10,6 +13,7 @@ from ..utils import (
|
|||||||
int_or_none,
|
int_or_none,
|
||||||
parse_iso8601,
|
parse_iso8601,
|
||||||
try_get,
|
try_get,
|
||||||
|
update_url_query,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -101,21 +105,24 @@ class ABCIE(InfoExtractor):
|
|||||||
class ABCIViewIE(InfoExtractor):
|
class ABCIViewIE(InfoExtractor):
|
||||||
IE_NAME = 'abc.net.au:iview'
|
IE_NAME = 'abc.net.au:iview'
|
||||||
_VALID_URL = r'https?://iview\.abc\.net\.au/programs/[^/]+/(?P<id>[^/?#]+)'
|
_VALID_URL = r'https?://iview\.abc\.net\.au/programs/[^/]+/(?P<id>[^/?#]+)'
|
||||||
|
_GEO_COUNTRIES = ['AU']
|
||||||
|
|
||||||
# ABC iview programs are normally available for 14 days only.
|
# ABC iview programs are normally available for 14 days only.
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://iview.abc.net.au/programs/diaries-of-a-broken-mind/ZX9735A001S00',
|
'url': 'http://iview.abc.net.au/programs/call-the-midwife/ZW0898A003S00',
|
||||||
'md5': 'cde42d728b3b7c2b32b1b94b4a548afc',
|
'md5': 'cde42d728b3b7c2b32b1b94b4a548afc',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'ZX9735A001S00',
|
'id': 'ZW0898A003S00',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Diaries Of A Broken Mind',
|
'title': 'Series 5 Ep 3',
|
||||||
'description': 'md5:7de3903874b7a1be279fe6b68718fc9e',
|
'description': 'md5:e0ef7d4f92055b86c4f33611f180ed79',
|
||||||
'upload_date': '20161010',
|
'upload_date': '20171228',
|
||||||
'uploader_id': 'abc2',
|
'uploader_id': 'abc1',
|
||||||
'timestamp': 1476064920,
|
'timestamp': 1514499187,
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
'skip_download': True,
|
||||||
},
|
},
|
||||||
'skip': 'Video gone',
|
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
@ -126,20 +133,30 @@ class ABCIViewIE(InfoExtractor):
|
|||||||
title = video_params.get('title') or video_params['seriesTitle']
|
title = video_params.get('title') or video_params['seriesTitle']
|
||||||
stream = next(s for s in video_params['playlist'] if s.get('type') == 'program')
|
stream = next(s for s in video_params['playlist'] if s.get('type') == 'program')
|
||||||
|
|
||||||
format_urls = [
|
house_number = video_params.get('episodeHouseNumber')
|
||||||
try_get(stream, lambda x: x['hds-unmetered'], compat_str)]
|
path = '/auth/hls/sign?ts={0}&hn={1}&d=android-mobile'.format(
|
||||||
|
int(time.time()), house_number)
|
||||||
|
sig = hmac.new(
|
||||||
|
'android.content.res.Resources'.encode('utf-8'),
|
||||||
|
path.encode('utf-8'), hashlib.sha256).hexdigest()
|
||||||
|
token = self._download_webpage(
|
||||||
|
'http://iview.abc.net.au{0}&sig={1}'.format(path, sig), video_id)
|
||||||
|
|
||||||
# May have higher quality video
|
def tokenize_url(url, token):
|
||||||
|
return update_url_query(url, {
|
||||||
|
'hdnea': token,
|
||||||
|
})
|
||||||
|
|
||||||
|
for sd in ('sd', 'sd-low'):
|
||||||
sd_url = try_get(
|
sd_url = try_get(
|
||||||
stream, lambda x: x['streams']['hds']['sd'], compat_str)
|
stream, lambda x: x['streams']['hls'][sd], compat_str)
|
||||||
if sd_url:
|
if not sd_url:
|
||||||
format_urls.append(sd_url.replace('metered', 'um'))
|
continue
|
||||||
|
formats = self._extract_m3u8_formats(
|
||||||
formats = []
|
tokenize_url(sd_url, token), video_id, 'mp4',
|
||||||
for format_url in format_urls:
|
entry_protocol='m3u8_native', m3u8_id='hls', fatal=False)
|
||||||
if format_url:
|
if formats:
|
||||||
formats.extend(
|
break
|
||||||
self._extract_akamai_formats(format_url, video_id))
|
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
subtitles = {}
|
subtitles = {}
|
||||||
|
@ -8,7 +8,7 @@ from .common import InfoExtractor
|
|||||||
from ..compat import compat_str
|
from ..compat import compat_str
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
int_or_none,
|
int_or_none,
|
||||||
parse_iso8601,
|
unified_timestamp,
|
||||||
OnDemandPagedList,
|
OnDemandPagedList,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -32,7 +32,7 @@ class ACastIE(InfoExtractor):
|
|||||||
}, {
|
}, {
|
||||||
# test with multiple blings
|
# test with multiple blings
|
||||||
'url': 'https://www.acast.com/sparpodcast/2.raggarmordet-rosterurdetforflutna',
|
'url': 'https://www.acast.com/sparpodcast/2.raggarmordet-rosterurdetforflutna',
|
||||||
'md5': '55c0097badd7095f494c99a172f86501',
|
'md5': 'e87d5b8516cd04c0d81b6ee1caca28d0',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '2a92b283-1a75-4ad8-8396-499c641de0d9',
|
'id': '2a92b283-1a75-4ad8-8396-499c641de0d9',
|
||||||
'ext': 'mp3',
|
'ext': 'mp3',
|
||||||
@ -40,23 +40,24 @@ class ACastIE(InfoExtractor):
|
|||||||
'timestamp': 1477346700,
|
'timestamp': 1477346700,
|
||||||
'upload_date': '20161024',
|
'upload_date': '20161024',
|
||||||
'description': 'md5:4f81f6d8cf2e12ee21a321d8bca32db4',
|
'description': 'md5:4f81f6d8cf2e12ee21a321d8bca32db4',
|
||||||
'duration': 2797,
|
'duration': 2766,
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
channel, display_id = re.match(self._VALID_URL, url).groups()
|
channel, display_id = re.match(self._VALID_URL, url).groups()
|
||||||
cast_data = self._download_json(
|
cast_data = self._download_json(
|
||||||
'https://embed.acast.com/api/acasts/%s/%s' % (channel, display_id), display_id)
|
'https://play-api.acast.com/splash/%s/%s' % (channel, display_id), display_id)
|
||||||
|
e = cast_data['result']['episode']
|
||||||
return {
|
return {
|
||||||
'id': compat_str(cast_data['id']),
|
'id': compat_str(e['id']),
|
||||||
'display_id': display_id,
|
'display_id': display_id,
|
||||||
'url': [b['audio'] for b in cast_data['blings'] if b['type'] == 'BlingAudio'][0],
|
'url': e['mediaUrl'],
|
||||||
'title': cast_data['name'],
|
'title': e['name'],
|
||||||
'description': cast_data.get('description'),
|
'description': e.get('description'),
|
||||||
'thumbnail': cast_data.get('image'),
|
'thumbnail': e.get('image'),
|
||||||
'timestamp': parse_iso8601(cast_data.get('publishingDate')),
|
'timestamp': unified_timestamp(e.get('publishingDate')),
|
||||||
'duration': int_or_none(cast_data.get('duration')),
|
'duration': int_or_none(e.get('duration')),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -228,10 +228,19 @@ class AfreecaTVIE(InfoExtractor):
|
|||||||
r'^(\d{8})_', key, 'upload date', default=None)
|
r'^(\d{8})_', key, 'upload date', default=None)
|
||||||
file_duration = int_or_none(file_element.get('duration'))
|
file_duration = int_or_none(file_element.get('duration'))
|
||||||
format_id = key if key else '%s_%s' % (video_id, file_num)
|
format_id = key if key else '%s_%s' % (video_id, file_num)
|
||||||
|
if determine_ext(file_url) == 'm3u8':
|
||||||
formats = self._extract_m3u8_formats(
|
formats = self._extract_m3u8_formats(
|
||||||
file_url, video_id, 'mp4', entry_protocol='m3u8_native',
|
file_url, video_id, 'mp4', entry_protocol='m3u8_native',
|
||||||
m3u8_id='hls',
|
m3u8_id='hls',
|
||||||
note='Downloading part %d m3u8 information' % file_num)
|
note='Downloading part %d m3u8 information' % file_num)
|
||||||
|
else:
|
||||||
|
formats = [{
|
||||||
|
'url': file_url,
|
||||||
|
'format_id': 'http',
|
||||||
|
}]
|
||||||
|
if not formats:
|
||||||
|
continue
|
||||||
|
self._sort_formats(formats)
|
||||||
file_info = common_entry.copy()
|
file_info = common_entry.copy()
|
||||||
file_info.update({
|
file_info.update({
|
||||||
'id': format_id,
|
'id': format_id,
|
||||||
|
@ -78,15 +78,15 @@ class AnimeOnDemandIE(InfoExtractor):
|
|||||||
post_url = urljoin(self._LOGIN_URL, post_url)
|
post_url = urljoin(self._LOGIN_URL, post_url)
|
||||||
|
|
||||||
response = self._download_webpage(
|
response = self._download_webpage(
|
||||||
post_url, None, 'Logging in as %s' % username,
|
post_url, None, 'Logging in',
|
||||||
data=urlencode_postdata(login_form), headers={
|
data=urlencode_postdata(login_form), headers={
|
||||||
'Referer': self._LOGIN_URL,
|
'Referer': self._LOGIN_URL,
|
||||||
})
|
})
|
||||||
|
|
||||||
if all(p not in response for p in ('>Logout<', 'href="/users/sign_out"')):
|
if all(p not in response for p in ('>Logout<', 'href="/users/sign_out"')):
|
||||||
error = self._search_regex(
|
error = self._search_regex(
|
||||||
r'<p class="alert alert-danger">(.+?)</p>',
|
r'<p[^>]+\bclass=(["\'])(?:(?!\1).)*\balert\b(?:(?!\1).)*\1[^>]*>(?P<error>.+?)</p>',
|
||||||
response, 'error', default=None)
|
response, 'error', default=None, group='error')
|
||||||
if error:
|
if error:
|
||||||
raise ExtractorError('Unable to login: %s' % error, expected=True)
|
raise ExtractorError('Unable to login: %s' % error, expected=True)
|
||||||
raise ExtractorError('Unable to log in')
|
raise ExtractorError('Unable to log in')
|
||||||
|
@ -5,6 +5,7 @@ import re
|
|||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from .generic import GenericIE
|
from .generic import GenericIE
|
||||||
|
from ..compat import compat_str
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
determine_ext,
|
determine_ext,
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
@ -126,6 +127,8 @@ class ARDMediathekIE(InfoExtractor):
|
|||||||
quality = stream.get('_quality')
|
quality = stream.get('_quality')
|
||||||
server = stream.get('_server')
|
server = stream.get('_server')
|
||||||
for stream_url in stream_urls:
|
for stream_url in stream_urls:
|
||||||
|
if not isinstance(stream_url, compat_str) or '//' not in stream_url:
|
||||||
|
continue
|
||||||
ext = determine_ext(stream_url)
|
ext = determine_ext(stream_url)
|
||||||
if quality != 'auto' and ext in ('f4m', 'm3u8'):
|
if quality != 'auto' and ext in ('f4m', 'm3u8'):
|
||||||
continue
|
continue
|
||||||
@ -146,13 +149,11 @@ class ARDMediathekIE(InfoExtractor):
|
|||||||
'play_path': stream_url,
|
'play_path': stream_url,
|
||||||
'format_id': 'a%s-rtmp-%s' % (num, quality),
|
'format_id': 'a%s-rtmp-%s' % (num, quality),
|
||||||
}
|
}
|
||||||
elif stream_url.startswith('http'):
|
else:
|
||||||
f = {
|
f = {
|
||||||
'url': stream_url,
|
'url': stream_url,
|
||||||
'format_id': 'a%s-%s-%s' % (num, ext, quality)
|
'format_id': 'a%s-%s-%s' % (num, ext, quality)
|
||||||
}
|
}
|
||||||
else:
|
|
||||||
continue
|
|
||||||
m = re.search(r'_(?P<width>\d+)x(?P<height>\d+)\.mp4$', stream_url)
|
m = re.search(r'_(?P<width>\d+)x(?P<height>\d+)\.mp4$', stream_url)
|
||||||
if m:
|
if m:
|
||||||
f.update({
|
f.update({
|
||||||
|
@ -87,7 +87,7 @@ class AtresPlayerIE(InfoExtractor):
|
|||||||
self._LOGIN_URL, urlencode_postdata(login_form))
|
self._LOGIN_URL, urlencode_postdata(login_form))
|
||||||
request.add_header('Content-Type', 'application/x-www-form-urlencoded')
|
request.add_header('Content-Type', 'application/x-www-form-urlencoded')
|
||||||
response = self._download_webpage(
|
response = self._download_webpage(
|
||||||
request, None, 'Logging in as %s' % username)
|
request, None, 'Logging in')
|
||||||
|
|
||||||
error = self._html_search_regex(
|
error = self._html_search_regex(
|
||||||
r'(?s)<ul[^>]+class="[^"]*\blist_error\b[^"]*">(.+?)</ul>',
|
r'(?s)<ul[^>]+class="[^"]*\blist_error\b[^"]*">(.+?)</ul>',
|
||||||
|
78
youtube_dl/extractor/aws.py
Normal file
78
youtube_dl/extractor/aws.py
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import datetime
|
||||||
|
import hashlib
|
||||||
|
import hmac
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..compat import compat_urllib_parse_urlencode
|
||||||
|
|
||||||
|
|
||||||
|
class AWSIE(InfoExtractor):
|
||||||
|
_AWS_ALGORITHM = 'AWS4-HMAC-SHA256'
|
||||||
|
_AWS_REGION = 'us-east-1'
|
||||||
|
|
||||||
|
def _aws_execute_api(self, aws_dict, video_id, query=None):
|
||||||
|
query = query or {}
|
||||||
|
amz_date = datetime.datetime.utcnow().strftime('%Y%m%dT%H%M%SZ')
|
||||||
|
date = amz_date[:8]
|
||||||
|
headers = {
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Host': self._AWS_PROXY_HOST,
|
||||||
|
'X-Amz-Date': amz_date,
|
||||||
|
'X-Api-Key': self._AWS_API_KEY
|
||||||
|
}
|
||||||
|
session_token = aws_dict.get('session_token')
|
||||||
|
if session_token:
|
||||||
|
headers['X-Amz-Security-Token'] = session_token
|
||||||
|
|
||||||
|
def aws_hash(s):
|
||||||
|
return hashlib.sha256(s.encode('utf-8')).hexdigest()
|
||||||
|
|
||||||
|
# Task 1: http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
|
||||||
|
canonical_querystring = compat_urllib_parse_urlencode(query)
|
||||||
|
canonical_headers = ''
|
||||||
|
for header_name, header_value in sorted(headers.items()):
|
||||||
|
canonical_headers += '%s:%s\n' % (header_name.lower(), header_value)
|
||||||
|
signed_headers = ';'.join([header.lower() for header in sorted(headers.keys())])
|
||||||
|
canonical_request = '\n'.join([
|
||||||
|
'GET',
|
||||||
|
aws_dict['uri'],
|
||||||
|
canonical_querystring,
|
||||||
|
canonical_headers,
|
||||||
|
signed_headers,
|
||||||
|
aws_hash('')
|
||||||
|
])
|
||||||
|
|
||||||
|
# Task 2: http://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html
|
||||||
|
credential_scope_list = [date, self._AWS_REGION, 'execute-api', 'aws4_request']
|
||||||
|
credential_scope = '/'.join(credential_scope_list)
|
||||||
|
string_to_sign = '\n'.join([self._AWS_ALGORITHM, amz_date, credential_scope, aws_hash(canonical_request)])
|
||||||
|
|
||||||
|
# Task 3: http://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html
|
||||||
|
def aws_hmac(key, msg):
|
||||||
|
return hmac.new(key, msg.encode('utf-8'), hashlib.sha256)
|
||||||
|
|
||||||
|
def aws_hmac_digest(key, msg):
|
||||||
|
return aws_hmac(key, msg).digest()
|
||||||
|
|
||||||
|
def aws_hmac_hexdigest(key, msg):
|
||||||
|
return aws_hmac(key, msg).hexdigest()
|
||||||
|
|
||||||
|
k_signing = ('AWS4' + aws_dict['secret_key']).encode('utf-8')
|
||||||
|
for value in credential_scope_list:
|
||||||
|
k_signing = aws_hmac_digest(k_signing, value)
|
||||||
|
|
||||||
|
signature = aws_hmac_hexdigest(k_signing, string_to_sign)
|
||||||
|
|
||||||
|
# Task 4: http://docs.aws.amazon.com/general/latest/gr/sigv4-add-signature-to-request.html
|
||||||
|
headers['Authorization'] = ', '.join([
|
||||||
|
'%s Credential=%s/%s' % (self._AWS_ALGORITHM, aws_dict['access_key'], credential_scope),
|
||||||
|
'SignedHeaders=%s' % signed_headers,
|
||||||
|
'Signature=%s' % signature,
|
||||||
|
])
|
||||||
|
|
||||||
|
return self._download_json(
|
||||||
|
'https://%s%s%s' % (self._AWS_PROXY_HOST, aws_dict['uri'], '?' + canonical_querystring if canonical_querystring else ''),
|
||||||
|
video_id, headers=headers)
|
@ -59,7 +59,7 @@ class BambuserIE(InfoExtractor):
|
|||||||
self._LOGIN_URL, urlencode_postdata(login_form))
|
self._LOGIN_URL, urlencode_postdata(login_form))
|
||||||
request.add_header('Referer', self._LOGIN_URL)
|
request.add_header('Referer', self._LOGIN_URL)
|
||||||
response = self._download_webpage(
|
response = self._download_webpage(
|
||||||
request, None, 'Logging in as %s' % username)
|
request, None, 'Logging in')
|
||||||
|
|
||||||
login_error = self._html_search_regex(
|
login_error = self._html_search_regex(
|
||||||
r'(?s)<div class="messages error">(.+?)</div>',
|
r'(?s)<div class="messages error">(.+?)</div>',
|
||||||
|
@ -386,7 +386,7 @@ class BBCCoUkIE(InfoExtractor):
|
|||||||
m3u8_id=format_id, fatal=False))
|
m3u8_id=format_id, fatal=False))
|
||||||
if re.search(self._USP_RE, href):
|
if re.search(self._USP_RE, href):
|
||||||
usp_formats = self._extract_m3u8_formats(
|
usp_formats = self._extract_m3u8_formats(
|
||||||
re.sub(self._USP_RE, r'/\1\.ism/\1\.m3u8', href),
|
re.sub(self._USP_RE, r'/\1.ism/\1.m3u8', href),
|
||||||
programme_id, ext='mp4', entry_protocol='m3u8_native',
|
programme_id, ext='mp4', entry_protocol='m3u8_native',
|
||||||
m3u8_id=format_id, fatal=False)
|
m3u8_id=format_id, fatal=False)
|
||||||
for f in usp_formats:
|
for f in usp_formats:
|
||||||
|
@ -1,20 +1,23 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import json
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
|
determine_ext,
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
parse_duration,
|
parse_duration,
|
||||||
|
parse_iso8601,
|
||||||
xpath_element,
|
xpath_element,
|
||||||
xpath_text,
|
xpath_text,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class BRIE(InfoExtractor):
|
class BRIE(InfoExtractor):
|
||||||
IE_DESC = 'Bayerischer Rundfunk Mediathek'
|
IE_DESC = 'Bayerischer Rundfunk'
|
||||||
_VALID_URL = r'(?P<base_url>https?://(?:www\.)?br(?:-klassik)?\.de)/(?:[a-z0-9\-_]+/)+(?P<id>[a-z0-9\-_]+)\.html'
|
_VALID_URL = r'(?P<base_url>https?://(?:www\.)?br(?:-klassik)?\.de)/(?:[a-z0-9\-_]+/)+(?P<id>[a-z0-9\-_]+)\.html'
|
||||||
|
|
||||||
_TESTS = [
|
_TESTS = [
|
||||||
@ -123,10 +126,10 @@ class BRIE(InfoExtractor):
|
|||||||
for asset in assets.findall('asset'):
|
for asset in assets.findall('asset'):
|
||||||
format_url = xpath_text(asset, ['downloadUrl', 'url'])
|
format_url = xpath_text(asset, ['downloadUrl', 'url'])
|
||||||
asset_type = asset.get('type')
|
asset_type = asset.get('type')
|
||||||
if asset_type == 'HDS':
|
if asset_type.startswith('HDS'):
|
||||||
formats.extend(self._extract_f4m_formats(
|
formats.extend(self._extract_f4m_formats(
|
||||||
format_url + '?hdcore=3.2.0', media_id, f4m_id='hds', fatal=False))
|
format_url + '?hdcore=3.2.0', media_id, f4m_id='hds', fatal=False))
|
||||||
elif asset_type == 'HLS':
|
elif asset_type.startswith('HLS'):
|
||||||
formats.extend(self._extract_m3u8_formats(
|
formats.extend(self._extract_m3u8_formats(
|
||||||
format_url, media_id, 'mp4', 'm3u8_native', m3u8_id='hds', fatal=False))
|
format_url, media_id, 'mp4', 'm3u8_native', m3u8_id='hds', fatal=False))
|
||||||
else:
|
else:
|
||||||
@ -169,3 +172,140 @@ class BRIE(InfoExtractor):
|
|||||||
} for variant in variants.findall('variant') if xpath_text(variant, 'url')]
|
} for variant in variants.findall('variant') if xpath_text(variant, 'url')]
|
||||||
thumbnails.sort(key=lambda x: x['width'] * x['height'], reverse=True)
|
thumbnails.sort(key=lambda x: x['width'] * x['height'], reverse=True)
|
||||||
return thumbnails
|
return thumbnails
|
||||||
|
|
||||||
|
|
||||||
|
class BRMediathekIE(InfoExtractor):
|
||||||
|
IE_DESC = 'Bayerischer Rundfunk Mediathek'
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?br\.de/mediathek/video/[^/?&#]*?-(?P<id>av:[0-9a-f]{24})'
|
||||||
|
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://www.br.de/mediathek/video/gesundheit-die-sendung-vom-28112017-av:5a1e6a6e8fce6d001871cc8e',
|
||||||
|
'md5': 'fdc3d485835966d1622587d08ba632ec',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'av:5a1e6a6e8fce6d001871cc8e',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Die Sendung vom 28.11.2017',
|
||||||
|
'description': 'md5:6000cdca5912ab2277e5b7339f201ccc',
|
||||||
|
'timestamp': 1511942766,
|
||||||
|
'upload_date': '20171129',
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
clip_id = self._match_id(url)
|
||||||
|
|
||||||
|
clip = self._download_json(
|
||||||
|
'https://proxy-base.master.mango.express/graphql',
|
||||||
|
clip_id, data=json.dumps({
|
||||||
|
"query": """{
|
||||||
|
viewer {
|
||||||
|
clip(id: "%s") {
|
||||||
|
title
|
||||||
|
description
|
||||||
|
duration
|
||||||
|
createdAt
|
||||||
|
ageRestriction
|
||||||
|
videoFiles {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
publicLocation
|
||||||
|
fileSize
|
||||||
|
videoProfile {
|
||||||
|
width
|
||||||
|
height
|
||||||
|
bitrate
|
||||||
|
encoding
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
captionFiles {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
publicLocation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
teaserImages {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
imageFiles {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
publicLocation
|
||||||
|
width
|
||||||
|
height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}""" % clip_id}).encode(), headers={
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
})['data']['viewer']['clip']
|
||||||
|
title = clip['title']
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
for edge in clip.get('videoFiles', {}).get('edges', []):
|
||||||
|
node = edge.get('node', {})
|
||||||
|
n_url = node.get('publicLocation')
|
||||||
|
if not n_url:
|
||||||
|
continue
|
||||||
|
ext = determine_ext(n_url)
|
||||||
|
if ext == 'm3u8':
|
||||||
|
formats.extend(self._extract_m3u8_formats(
|
||||||
|
n_url, clip_id, 'mp4', 'm3u8_native',
|
||||||
|
m3u8_id='hls', fatal=False))
|
||||||
|
else:
|
||||||
|
video_profile = node.get('videoProfile', {})
|
||||||
|
tbr = int_or_none(video_profile.get('bitrate'))
|
||||||
|
format_id = 'http'
|
||||||
|
if tbr:
|
||||||
|
format_id += '-%d' % tbr
|
||||||
|
formats.append({
|
||||||
|
'format_id': format_id,
|
||||||
|
'url': n_url,
|
||||||
|
'width': int_or_none(video_profile.get('width')),
|
||||||
|
'height': int_or_none(video_profile.get('height')),
|
||||||
|
'tbr': tbr,
|
||||||
|
'filesize': int_or_none(node.get('fileSize')),
|
||||||
|
})
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
subtitles = {}
|
||||||
|
for edge in clip.get('captionFiles', {}).get('edges', []):
|
||||||
|
node = edge.get('node', {})
|
||||||
|
n_url = node.get('publicLocation')
|
||||||
|
if not n_url:
|
||||||
|
continue
|
||||||
|
subtitles.setdefault('de', []).append({
|
||||||
|
'url': n_url,
|
||||||
|
})
|
||||||
|
|
||||||
|
thumbnails = []
|
||||||
|
for edge in clip.get('teaserImages', {}).get('edges', []):
|
||||||
|
for image_edge in edge.get('node', {}).get('imageFiles', {}).get('edges', []):
|
||||||
|
node = image_edge.get('node', {})
|
||||||
|
n_url = node.get('publicLocation')
|
||||||
|
if not n_url:
|
||||||
|
continue
|
||||||
|
thumbnails.append({
|
||||||
|
'url': n_url,
|
||||||
|
'width': int_or_none(node.get('width')),
|
||||||
|
'height': int_or_none(node.get('height')),
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': clip_id,
|
||||||
|
'title': title,
|
||||||
|
'description': clip.get('description'),
|
||||||
|
'duration': int_or_none(clip.get('duration')),
|
||||||
|
'timestamp': parse_iso8601(clip.get('createdAt')),
|
||||||
|
'age_limit': int_or_none(clip.get('ageRestriction')),
|
||||||
|
'formats': formats,
|
||||||
|
'subtitles': subtitles,
|
||||||
|
'thumbnails': thumbnails,
|
||||||
|
}
|
||||||
|
@ -464,7 +464,7 @@ class BrightcoveNewIE(AdobePassIE):
|
|||||||
'timestamp': 1441391203,
|
'timestamp': 1441391203,
|
||||||
'upload_date': '20150904',
|
'upload_date': '20150904',
|
||||||
'uploader_id': '929656772001',
|
'uploader_id': '929656772001',
|
||||||
'formats': 'mincount:22',
|
'formats': 'mincount:20',
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
# with rtmp streams
|
# with rtmp streams
|
||||||
@ -478,7 +478,7 @@ class BrightcoveNewIE(AdobePassIE):
|
|||||||
'timestamp': 1433556729,
|
'timestamp': 1433556729,
|
||||||
'upload_date': '20150606',
|
'upload_date': '20150606',
|
||||||
'uploader_id': '4036320279001',
|
'uploader_id': '4036320279001',
|
||||||
'formats': 'mincount:41',
|
'formats': 'mincount:39',
|
||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
# m3u8 download
|
# m3u8 download
|
||||||
@ -564,59 +564,7 @@ class BrightcoveNewIE(AdobePassIE):
|
|||||||
|
|
||||||
return entries
|
return entries
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _parse_brightcove_metadata(self, json_data, video_id):
|
||||||
url, smuggled_data = unsmuggle_url(url, {})
|
|
||||||
self._initialize_geo_bypass(smuggled_data.get('geo_countries'))
|
|
||||||
|
|
||||||
account_id, player_id, embed, video_id = re.match(self._VALID_URL, url).groups()
|
|
||||||
|
|
||||||
webpage = self._download_webpage(
|
|
||||||
'http://players.brightcove.net/%s/%s_%s/index.min.js'
|
|
||||||
% (account_id, player_id, embed), video_id)
|
|
||||||
|
|
||||||
policy_key = None
|
|
||||||
|
|
||||||
catalog = self._search_regex(
|
|
||||||
r'catalog\(({.+?})\);', webpage, 'catalog', default=None)
|
|
||||||
if catalog:
|
|
||||||
catalog = self._parse_json(
|
|
||||||
js_to_json(catalog), video_id, fatal=False)
|
|
||||||
if catalog:
|
|
||||||
policy_key = catalog.get('policyKey')
|
|
||||||
|
|
||||||
if not policy_key:
|
|
||||||
policy_key = self._search_regex(
|
|
||||||
r'policyKey\s*:\s*(["\'])(?P<pk>.+?)\1',
|
|
||||||
webpage, 'policy key', group='pk')
|
|
||||||
|
|
||||||
api_url = 'https://edge.api.brightcove.com/playback/v1/accounts/%s/videos/%s' % (account_id, video_id)
|
|
||||||
try:
|
|
||||||
json_data = self._download_json(api_url, video_id, headers={
|
|
||||||
'Accept': 'application/json;pk=%s' % policy_key
|
|
||||||
})
|
|
||||||
except ExtractorError as e:
|
|
||||||
if isinstance(e.cause, compat_HTTPError) and e.cause.code == 403:
|
|
||||||
json_data = self._parse_json(e.cause.read().decode(), video_id)[0]
|
|
||||||
message = json_data.get('message') or json_data['error_code']
|
|
||||||
if json_data.get('error_subcode') == 'CLIENT_GEO':
|
|
||||||
self.raise_geo_restricted(msg=message)
|
|
||||||
raise ExtractorError(message, expected=True)
|
|
||||||
raise
|
|
||||||
|
|
||||||
errors = json_data.get('errors')
|
|
||||||
if errors and errors[0].get('error_subcode') == 'TVE_AUTH':
|
|
||||||
custom_fields = json_data['custom_fields']
|
|
||||||
tve_token = self._extract_mvpd_auth(
|
|
||||||
smuggled_data['source_url'], video_id,
|
|
||||||
custom_fields['bcadobepassrequestorid'],
|
|
||||||
custom_fields['bcadobepassresourceid'])
|
|
||||||
json_data = self._download_json(
|
|
||||||
api_url, video_id, headers={
|
|
||||||
'Accept': 'application/json;pk=%s' % policy_key
|
|
||||||
}, query={
|
|
||||||
'tveToken': tve_token,
|
|
||||||
})
|
|
||||||
|
|
||||||
title = json_data['name'].strip()
|
title = json_data['name'].strip()
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
@ -682,6 +630,7 @@ class BrightcoveNewIE(AdobePassIE):
|
|||||||
})
|
})
|
||||||
formats.append(f)
|
formats.append(f)
|
||||||
|
|
||||||
|
errors = json_data.get('errors')
|
||||||
if not formats and errors:
|
if not formats and errors:
|
||||||
error = errors[0]
|
error = errors[0]
|
||||||
raise ExtractorError(
|
raise ExtractorError(
|
||||||
@ -708,9 +657,64 @@ class BrightcoveNewIE(AdobePassIE):
|
|||||||
'thumbnail': json_data.get('thumbnail') or json_data.get('poster'),
|
'thumbnail': json_data.get('thumbnail') or json_data.get('poster'),
|
||||||
'duration': duration,
|
'duration': duration,
|
||||||
'timestamp': parse_iso8601(json_data.get('published_at')),
|
'timestamp': parse_iso8601(json_data.get('published_at')),
|
||||||
'uploader_id': account_id,
|
'uploader_id': json_data.get('account_id'),
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
'subtitles': subtitles,
|
'subtitles': subtitles,
|
||||||
'tags': json_data.get('tags', []),
|
'tags': json_data.get('tags', []),
|
||||||
'is_live': is_live,
|
'is_live': is_live,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
url, smuggled_data = unsmuggle_url(url, {})
|
||||||
|
self._initialize_geo_bypass(smuggled_data.get('geo_countries'))
|
||||||
|
|
||||||
|
account_id, player_id, embed, video_id = re.match(self._VALID_URL, url).groups()
|
||||||
|
|
||||||
|
webpage = self._download_webpage(
|
||||||
|
'http://players.brightcove.net/%s/%s_%s/index.min.js'
|
||||||
|
% (account_id, player_id, embed), video_id)
|
||||||
|
|
||||||
|
policy_key = None
|
||||||
|
|
||||||
|
catalog = self._search_regex(
|
||||||
|
r'catalog\(({.+?})\);', webpage, 'catalog', default=None)
|
||||||
|
if catalog:
|
||||||
|
catalog = self._parse_json(
|
||||||
|
js_to_json(catalog), video_id, fatal=False)
|
||||||
|
if catalog:
|
||||||
|
policy_key = catalog.get('policyKey')
|
||||||
|
|
||||||
|
if not policy_key:
|
||||||
|
policy_key = self._search_regex(
|
||||||
|
r'policyKey\s*:\s*(["\'])(?P<pk>.+?)\1',
|
||||||
|
webpage, 'policy key', group='pk')
|
||||||
|
|
||||||
|
api_url = 'https://edge.api.brightcove.com/playback/v1/accounts/%s/videos/%s' % (account_id, video_id)
|
||||||
|
try:
|
||||||
|
json_data = self._download_json(api_url, video_id, headers={
|
||||||
|
'Accept': 'application/json;pk=%s' % policy_key
|
||||||
|
})
|
||||||
|
except ExtractorError as e:
|
||||||
|
if isinstance(e.cause, compat_HTTPError) and e.cause.code == 403:
|
||||||
|
json_data = self._parse_json(e.cause.read().decode(), video_id)[0]
|
||||||
|
message = json_data.get('message') or json_data['error_code']
|
||||||
|
if json_data.get('error_subcode') == 'CLIENT_GEO':
|
||||||
|
self.raise_geo_restricted(msg=message)
|
||||||
|
raise ExtractorError(message, expected=True)
|
||||||
|
raise
|
||||||
|
|
||||||
|
errors = json_data.get('errors')
|
||||||
|
if errors and errors[0].get('error_subcode') == 'TVE_AUTH':
|
||||||
|
custom_fields = json_data['custom_fields']
|
||||||
|
tve_token = self._extract_mvpd_auth(
|
||||||
|
smuggled_data['source_url'], video_id,
|
||||||
|
custom_fields['bcadobepassrequestorid'],
|
||||||
|
custom_fields['bcadobepassresourceid'])
|
||||||
|
json_data = self._download_json(
|
||||||
|
api_url, video_id, headers={
|
||||||
|
'Accept': 'application/json;pk=%s' % policy_key
|
||||||
|
}, query={
|
||||||
|
'tveToken': tve_token,
|
||||||
|
})
|
||||||
|
|
||||||
|
return self._parse_brightcove_metadata(json_data, video_id)
|
||||||
|
@ -3,20 +3,19 @@ from __future__ import unicode_literals
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import ExtractorError
|
|
||||||
|
|
||||||
|
|
||||||
class BYUtvIE(InfoExtractor):
|
class BYUtvIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?byutv\.org/watch/(?!event/)(?P<id>[0-9a-f-]+)(?:/(?P<display_id>[^/?#&]+))?'
|
_VALID_URL = r'https?://(?:www\.)?byutv\.org/(?:watch|player)/(?!event/)(?P<id>[0-9a-f-]+)(?:/(?P<display_id>[^/?#&]+))?'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://www.byutv.org/watch/6587b9a3-89d2-42a6-a7f7-fd2f81840a7d/studio-c-season-5-episode-5',
|
'url': 'http://www.byutv.org/watch/6587b9a3-89d2-42a6-a7f7-fd2f81840a7d/studio-c-season-5-episode-5',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '6587b9a3-89d2-42a6-a7f7-fd2f81840a7d',
|
'id': 'ZvanRocTpW-G5_yZFeltTAMv6jxOU9KH',
|
||||||
'display_id': 'studio-c-season-5-episode-5',
|
'display_id': 'studio-c-season-5-episode-5',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Season 5 Episode 5',
|
'title': 'Season 5 Episode 5',
|
||||||
'description': 'md5:e07269172baff037f8e8bf9956bc9747',
|
'description': 'md5:1d31dc18ef4f075b28f6a65937d22c65',
|
||||||
'thumbnail': r're:^https?://.*\.jpg$',
|
'thumbnail': r're:^https?://.*',
|
||||||
'duration': 1486.486,
|
'duration': 1486.486,
|
||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
@ -26,6 +25,9 @@ class BYUtvIE(InfoExtractor):
|
|||||||
}, {
|
}, {
|
||||||
'url': 'http://www.byutv.org/watch/6587b9a3-89d2-42a6-a7f7-fd2f81840a7d',
|
'url': 'http://www.byutv.org/watch/6587b9a3-89d2-42a6-a7f7-fd2f81840a7d',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.byutv.org/player/27741493-dc83-40b0-8420-e7ae38a2ae98/byu-football-toledo-vs-byu-93016?listid=4fe0fee5-0d3c-4a29-b725-e4948627f472&listindex=0&q=toledo',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
@ -33,16 +35,16 @@ class BYUtvIE(InfoExtractor):
|
|||||||
video_id = mobj.group('id')
|
video_id = mobj.group('id')
|
||||||
display_id = mobj.group('display_id') or video_id
|
display_id = mobj.group('display_id') or video_id
|
||||||
|
|
||||||
webpage = self._download_webpage(url, display_id)
|
ep = self._download_json(
|
||||||
episode_code = self._search_regex(
|
'https://api.byutv.org/api3/catalog/getvideosforcontent', video_id,
|
||||||
r'(?s)episode:(.*?\}),\s*\n', webpage, 'episode information')
|
query={
|
||||||
|
'contentid': video_id,
|
||||||
ep = self._parse_json(
|
'channel': 'byutv',
|
||||||
episode_code, display_id, transform_source=lambda s:
|
'x-byutv-context': 'web$US',
|
||||||
re.sub(r'(\n\s+)([a-zA-Z]+):\s+\'(.*?)\'', r'\1"\2": "\3"', s))
|
}, headers={
|
||||||
|
'x-byutv-context': 'web$US',
|
||||||
if ep['providerType'] != 'Ooyala':
|
'x-byutv-platformkey': 'xsaaw9c7y5',
|
||||||
raise ExtractorError('Unsupported provider %s' % ep['provider'])
|
})['ooyalaVOD']
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'_type': 'url_transparent',
|
'_type': 'url_transparent',
|
||||||
@ -50,44 +52,7 @@ class BYUtvIE(InfoExtractor):
|
|||||||
'url': 'ooyala:%s' % ep['providerId'],
|
'url': 'ooyala:%s' % ep['providerId'],
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'display_id': display_id,
|
'display_id': display_id,
|
||||||
'title': ep['title'],
|
'title': ep.get('title'),
|
||||||
'description': ep.get('description'),
|
'description': ep.get('description'),
|
||||||
'thumbnail': ep.get('imageThumbnail'),
|
'thumbnail': ep.get('imageThumbnail'),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class BYUtvEventIE(InfoExtractor):
|
|
||||||
_VALID_URL = r'https?://(?:www\.)?byutv\.org/watch/event/(?P<id>[0-9a-f-]+)'
|
|
||||||
_TEST = {
|
|
||||||
'url': 'http://www.byutv.org/watch/event/29941b9b-8bf6-48d2-aebf-7a87add9e34b',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '29941b9b-8bf6-48d2-aebf-7a87add9e34b',
|
|
||||||
'ext': 'mp4',
|
|
||||||
'title': 'Toledo vs. BYU (9/30/16)',
|
|
||||||
},
|
|
||||||
'params': {
|
|
||||||
'skip_download': True,
|
|
||||||
},
|
|
||||||
'add_ie': ['Ooyala'],
|
|
||||||
}
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
|
||||||
video_id = self._match_id(url)
|
|
||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
|
||||||
|
|
||||||
ooyala_id = self._search_regex(
|
|
||||||
r'providerId\s*:\s*(["\'])(?P<id>(?:(?!\1).)+)\1',
|
|
||||||
webpage, 'ooyala id', group='id')
|
|
||||||
|
|
||||||
title = self._search_regex(
|
|
||||||
r'class=["\']description["\'][^>]*>\s*<h1>([^<]+)</h1>', webpage,
|
|
||||||
'title').strip()
|
|
||||||
|
|
||||||
return {
|
|
||||||
'_type': 'url_transparent',
|
|
||||||
'ie_key': 'Ooyala',
|
|
||||||
'url': 'ooyala:%s' % ooyala_id,
|
|
||||||
'id': video_id,
|
|
||||||
'title': title,
|
|
||||||
}
|
|
||||||
|
@ -31,7 +31,7 @@ class CartoonNetworkIE(TurnerBaseIE):
|
|||||||
'http://www.cartoonnetwork.com/video-seo-svc/episodeservices/getCvpPlaylist?networkName=CN2&' + query, video_id, {
|
'http://www.cartoonnetwork.com/video-seo-svc/episodeservices/getCvpPlaylist?networkName=CN2&' + query, video_id, {
|
||||||
'secure': {
|
'secure': {
|
||||||
'media_src': 'http://androidhls-secure.cdn.turner.com/toon/big',
|
'media_src': 'http://androidhls-secure.cdn.turner.com/toon/big',
|
||||||
'tokenizer_src': 'http://www.cartoonnetwork.com/cntv/mvpd/processors/services/token_ipadAdobe.do',
|
'tokenizer_src': 'https://token.vgtf.net/token/token_mobile',
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
'url': url,
|
'url': url,
|
||||||
|
@ -91,12 +91,10 @@ class CBSLocalIE(AnvatoIE):
|
|||||||
|
|
||||||
info_dict = self._extract_anvato_videos(webpage, display_id)
|
info_dict = self._extract_anvato_videos(webpage, display_id)
|
||||||
|
|
||||||
time_str = self._html_search_regex(
|
timestamp = unified_timestamp(self._html_search_regex(
|
||||||
r'class="entry-date">([^<]+)<', webpage, 'released date', default=None)
|
r'class="(?:entry|post)-date"[^>]*>([^<]+)', webpage,
|
||||||
if time_str:
|
'released date', default=None)) or parse_iso8601(
|
||||||
timestamp = unified_timestamp(time_str)
|
self._html_search_meta('uploadDate', webpage))
|
||||||
else:
|
|
||||||
timestamp = parse_iso8601(self._html_search_meta('uploadDate', webpage))
|
|
||||||
|
|
||||||
info_dict.update({
|
info_dict.update({
|
||||||
'display_id': display_id,
|
'display_id': display_id,
|
||||||
|
@ -93,7 +93,7 @@ class CCMAIE(InfoExtractor):
|
|||||||
'description': clean_html(informacio.get('descripcio')),
|
'description': clean_html(informacio.get('descripcio')),
|
||||||
'duration': duration,
|
'duration': duration,
|
||||||
'timestamp': timestamp,
|
'timestamp': timestamp,
|
||||||
'thumnails': thumbnails,
|
'thumbnails': thumbnails,
|
||||||
'subtitles': subtitles,
|
'subtitles': subtitles,
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
}
|
}
|
||||||
|
@ -1,93 +0,0 @@
|
|||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import json
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
|
||||||
from ..utils import (
|
|
||||||
float_or_none,
|
|
||||||
int_or_none,
|
|
||||||
sanitized_Request,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class CollegeRamaIE(InfoExtractor):
|
|
||||||
_VALID_URL = r'https?://collegerama\.tudelft\.nl/Mediasite/Play/(?P<id>[\da-f]+)'
|
|
||||||
_TESTS = [
|
|
||||||
{
|
|
||||||
'url': 'https://collegerama.tudelft.nl/Mediasite/Play/585a43626e544bdd97aeb71a0ec907a01d',
|
|
||||||
'md5': '481fda1c11f67588c0d9d8fbdced4e39',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '585a43626e544bdd97aeb71a0ec907a01d',
|
|
||||||
'ext': 'mp4',
|
|
||||||
'title': 'Een nieuwe wereld: waarden, bewustzijn en techniek van de mensheid 2.0.',
|
|
||||||
'description': '',
|
|
||||||
'thumbnail': r're:^https?://.*\.jpg(?:\?.*?)?$',
|
|
||||||
'duration': 7713.088,
|
|
||||||
'timestamp': 1413309600,
|
|
||||||
'upload_date': '20141014',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'url': 'https://collegerama.tudelft.nl/Mediasite/Play/86a9ea9f53e149079fbdb4202b521ed21d?catalog=fd32fd35-6c99-466c-89d4-cd3c431bc8a4',
|
|
||||||
'md5': 'ef1fdded95bdf19b12c5999949419c92',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '86a9ea9f53e149079fbdb4202b521ed21d',
|
|
||||||
'ext': 'wmv',
|
|
||||||
'title': '64ste Vakantiecursus: Afvalwater',
|
|
||||||
'description': 'md5:7fd774865cc69d972f542b157c328305',
|
|
||||||
'thumbnail': r're:^https?://.*\.jpg(?:\?.*?)?$',
|
|
||||||
'duration': 10853,
|
|
||||||
'timestamp': 1326446400,
|
|
||||||
'upload_date': '20120113',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
|
||||||
video_id = self._match_id(url)
|
|
||||||
|
|
||||||
player_options_request = {
|
|
||||||
'getPlayerOptionsRequest': {
|
|
||||||
'ResourceId': video_id,
|
|
||||||
'QueryString': '',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
request = sanitized_Request(
|
|
||||||
'http://collegerama.tudelft.nl/Mediasite/PlayerService/PlayerService.svc/json/GetPlayerOptions',
|
|
||||||
json.dumps(player_options_request))
|
|
||||||
request.add_header('Content-Type', 'application/json')
|
|
||||||
|
|
||||||
player_options = self._download_json(request, video_id)
|
|
||||||
|
|
||||||
presentation = player_options['d']['Presentation']
|
|
||||||
title = presentation['Title']
|
|
||||||
description = presentation.get('Description')
|
|
||||||
thumbnail = None
|
|
||||||
duration = float_or_none(presentation.get('Duration'), 1000)
|
|
||||||
timestamp = int_or_none(presentation.get('UnixTime'), 1000)
|
|
||||||
|
|
||||||
formats = []
|
|
||||||
for stream in presentation['Streams']:
|
|
||||||
for video in stream['VideoUrls']:
|
|
||||||
thumbnail_url = stream.get('ThumbnailUrl')
|
|
||||||
if thumbnail_url:
|
|
||||||
thumbnail = 'http://collegerama.tudelft.nl' + thumbnail_url
|
|
||||||
format_id = video['MediaType']
|
|
||||||
if format_id == 'SS':
|
|
||||||
continue
|
|
||||||
formats.append({
|
|
||||||
'url': video['Location'],
|
|
||||||
'format_id': format_id,
|
|
||||||
})
|
|
||||||
self._sort_formats(formats)
|
|
||||||
|
|
||||||
return {
|
|
||||||
'id': video_id,
|
|
||||||
'title': title,
|
|
||||||
'description': description,
|
|
||||||
'thumbnail': thumbnail,
|
|
||||||
'duration': duration,
|
|
||||||
'timestamp': timestamp,
|
|
||||||
'formats': formats,
|
|
||||||
}
|
|
@ -29,7 +29,10 @@ from ..compat import (
|
|||||||
compat_urlparse,
|
compat_urlparse,
|
||||||
compat_xml_parse_error,
|
compat_xml_parse_error,
|
||||||
)
|
)
|
||||||
from ..downloader.f4m import remove_encrypted_media
|
from ..downloader.f4m import (
|
||||||
|
get_base_url,
|
||||||
|
remove_encrypted_media,
|
||||||
|
)
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
NO_DEFAULT,
|
NO_DEFAULT,
|
||||||
age_restricted,
|
age_restricted,
|
||||||
@ -298,8 +301,9 @@ class InfoExtractor(object):
|
|||||||
There must be a key "entries", which is a list, an iterable, or a PagedList
|
There must be a key "entries", which is a list, an iterable, or a PagedList
|
||||||
object, each element of which is a valid dictionary by this specification.
|
object, each element of which is a valid dictionary by this specification.
|
||||||
|
|
||||||
Additionally, playlists can have "title", "description" and "id" attributes
|
Additionally, playlists can have "id", "title", "description", "uploader",
|
||||||
with the same semantics as videos (see above).
|
"uploader_id", "uploader_url" attributes with the same semantics as videos
|
||||||
|
(see above).
|
||||||
|
|
||||||
|
|
||||||
_type "multi_video" indicates that there are multiple videos that
|
_type "multi_video" indicates that there are multiple videos that
|
||||||
@ -491,6 +495,16 @@ class InfoExtractor(object):
|
|||||||
self.to_screen('%s' % (note,))
|
self.to_screen('%s' % (note,))
|
||||||
else:
|
else:
|
||||||
self.to_screen('%s: %s' % (video_id, note))
|
self.to_screen('%s: %s' % (video_id, note))
|
||||||
|
|
||||||
|
# Some sites check X-Forwarded-For HTTP header in order to figure out
|
||||||
|
# the origin of the client behind proxy. This allows bypassing geo
|
||||||
|
# restriction by faking this header's value to IP that belongs to some
|
||||||
|
# geo unrestricted country. We will do so once we encounter any
|
||||||
|
# geo restriction error.
|
||||||
|
if self._x_forwarded_for_ip:
|
||||||
|
if 'X-Forwarded-For' not in headers:
|
||||||
|
headers['X-Forwarded-For'] = self._x_forwarded_for_ip
|
||||||
|
|
||||||
if isinstance(url_or_request, compat_urllib_request.Request):
|
if isinstance(url_or_request, compat_urllib_request.Request):
|
||||||
url_or_request = update_Request(
|
url_or_request = update_Request(
|
||||||
url_or_request, data=data, headers=headers, query=query)
|
url_or_request, data=data, headers=headers, query=query)
|
||||||
@ -520,15 +534,6 @@ class InfoExtractor(object):
|
|||||||
if isinstance(url_or_request, (compat_str, str)):
|
if isinstance(url_or_request, (compat_str, str)):
|
||||||
url_or_request = url_or_request.partition('#')[0]
|
url_or_request = url_or_request.partition('#')[0]
|
||||||
|
|
||||||
# Some sites check X-Forwarded-For HTTP header in order to figure out
|
|
||||||
# the origin of the client behind proxy. This allows bypassing geo
|
|
||||||
# restriction by faking this header's value to IP that belongs to some
|
|
||||||
# geo unrestricted country. We will do so once we encounter any
|
|
||||||
# geo restriction error.
|
|
||||||
if self._x_forwarded_for_ip:
|
|
||||||
if 'X-Forwarded-For' not in headers:
|
|
||||||
headers['X-Forwarded-For'] = self._x_forwarded_for_ip
|
|
||||||
|
|
||||||
urlh = self._request_webpage(url_or_request, video_id, note, errnote, fatal, data=data, headers=headers, query=query)
|
urlh = self._request_webpage(url_or_request, video_id, note, errnote, fatal, data=data, headers=headers, query=query)
|
||||||
if urlh is False:
|
if urlh is False:
|
||||||
assert not fatal
|
assert not fatal
|
||||||
@ -589,19 +594,11 @@ class InfoExtractor(object):
|
|||||||
if not encoding:
|
if not encoding:
|
||||||
encoding = self._guess_encoding_from_content(content_type, webpage_bytes)
|
encoding = self._guess_encoding_from_content(content_type, webpage_bytes)
|
||||||
if self._downloader.params.get('dump_intermediate_pages', False):
|
if self._downloader.params.get('dump_intermediate_pages', False):
|
||||||
try:
|
self.to_screen('Dumping request to ' + urlh.geturl())
|
||||||
url = url_or_request.get_full_url()
|
|
||||||
except AttributeError:
|
|
||||||
url = url_or_request
|
|
||||||
self.to_screen('Dumping request to ' + url)
|
|
||||||
dump = base64.b64encode(webpage_bytes).decode('ascii')
|
dump = base64.b64encode(webpage_bytes).decode('ascii')
|
||||||
self._downloader.to_screen(dump)
|
self._downloader.to_screen(dump)
|
||||||
if self._downloader.params.get('write_pages', False):
|
if self._downloader.params.get('write_pages', False):
|
||||||
try:
|
basen = '%s_%s' % (video_id, urlh.geturl())
|
||||||
url = url_or_request.get_full_url()
|
|
||||||
except AttributeError:
|
|
||||||
url = url_or_request
|
|
||||||
basen = '%s_%s' % (video_id, url)
|
|
||||||
if len(basen) > 240:
|
if len(basen) > 240:
|
||||||
h = '___' + hashlib.md5(basen.encode('utf-8')).hexdigest()
|
h = '___' + hashlib.md5(basen.encode('utf-8')).hexdigest()
|
||||||
basen = basen[:240 - len(h)] + h
|
basen = basen[:240 - len(h)] + h
|
||||||
@ -1239,11 +1236,8 @@ class InfoExtractor(object):
|
|||||||
media_nodes = remove_encrypted_media(media_nodes)
|
media_nodes = remove_encrypted_media(media_nodes)
|
||||||
if not media_nodes:
|
if not media_nodes:
|
||||||
return formats
|
return formats
|
||||||
base_url = xpath_text(
|
|
||||||
manifest, ['{http://ns.adobe.com/f4m/1.0}baseURL', '{http://ns.adobe.com/f4m/2.0}baseURL'],
|
manifest_base_url = get_base_url(manifest)
|
||||||
'base URL', default=None)
|
|
||||||
if base_url:
|
|
||||||
base_url = base_url.strip()
|
|
||||||
|
|
||||||
bootstrap_info = xpath_element(
|
bootstrap_info = xpath_element(
|
||||||
manifest, ['{http://ns.adobe.com/f4m/1.0}bootstrapInfo', '{http://ns.adobe.com/f4m/2.0}bootstrapInfo'],
|
manifest, ['{http://ns.adobe.com/f4m/1.0}bootstrapInfo', '{http://ns.adobe.com/f4m/2.0}bootstrapInfo'],
|
||||||
@ -1275,7 +1269,7 @@ class InfoExtractor(object):
|
|||||||
continue
|
continue
|
||||||
manifest_url = (
|
manifest_url = (
|
||||||
media_url if media_url.startswith('http://') or media_url.startswith('https://')
|
media_url if media_url.startswith('http://') or media_url.startswith('https://')
|
||||||
else ((base_url or '/'.join(manifest_url.split('/')[:-1])) + '/' + media_url))
|
else ((manifest_base_url or '/'.join(manifest_url.split('/')[:-1])) + '/' + media_url))
|
||||||
# If media_url is itself a f4m manifest do the recursive extraction
|
# If media_url is itself a f4m manifest do the recursive extraction
|
||||||
# since bitrates in parent manifest (this one) and media_url manifest
|
# since bitrates in parent manifest (this one) and media_url manifest
|
||||||
# may differ leading to inability to resolve the format by requested
|
# may differ leading to inability to resolve the format by requested
|
||||||
@ -1310,6 +1304,7 @@ class InfoExtractor(object):
|
|||||||
'url': manifest_url,
|
'url': manifest_url,
|
||||||
'manifest_url': manifest_url,
|
'manifest_url': manifest_url,
|
||||||
'ext': 'flv' if bootstrap_info is not None else None,
|
'ext': 'flv' if bootstrap_info is not None else None,
|
||||||
|
'protocol': 'f4m',
|
||||||
'tbr': tbr,
|
'tbr': tbr,
|
||||||
'width': width,
|
'width': width,
|
||||||
'height': height,
|
'height': height,
|
||||||
@ -1355,6 +1350,9 @@ class InfoExtractor(object):
|
|||||||
if '#EXT-X-FAXS-CM:' in m3u8_doc: # Adobe Flash Access
|
if '#EXT-X-FAXS-CM:' in m3u8_doc: # Adobe Flash Access
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
if re.search(r'#EXT-X-SESSION-KEY:.*?URI="skd://', m3u8_doc): # Apple FairPlay
|
||||||
|
return []
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
|
|
||||||
format_url = lambda u: (
|
format_url = lambda u: (
|
||||||
@ -1882,6 +1880,7 @@ class InfoExtractor(object):
|
|||||||
'language': lang if lang not in ('mul', 'und', 'zxx', 'mis') else None,
|
'language': lang if lang not in ('mul', 'und', 'zxx', 'mis') else None,
|
||||||
'format_note': 'DASH %s' % content_type,
|
'format_note': 'DASH %s' % content_type,
|
||||||
'filesize': filesize,
|
'filesize': filesize,
|
||||||
|
'container': mimetype2ext(mime_type) + '_dash',
|
||||||
}
|
}
|
||||||
f.update(parse_codecs(representation_attrib.get('codecs')))
|
f.update(parse_codecs(representation_attrib.get('codecs')))
|
||||||
representation_ms_info = extract_multisegment_info(representation, adaption_set_ms_info)
|
representation_ms_info = extract_multisegment_info(representation, adaption_set_ms_info)
|
||||||
@ -1979,6 +1978,22 @@ class InfoExtractor(object):
|
|||||||
})
|
})
|
||||||
segment_index += 1
|
segment_index += 1
|
||||||
representation_ms_info['fragments'] = fragments
|
representation_ms_info['fragments'] = fragments
|
||||||
|
elif 'segment_urls' in representation_ms_info:
|
||||||
|
# Segment URLs with no SegmentTimeline
|
||||||
|
# Example: https://www.seznam.cz/zpravy/clanek/cesko-zasahne-vitr-o-sile-vichrice-muze-byt-i-zivotu-nebezpecny-39091
|
||||||
|
# https://github.com/rg3/youtube-dl/pull/14844
|
||||||
|
fragments = []
|
||||||
|
segment_duration = float_or_none(
|
||||||
|
representation_ms_info['segment_duration'],
|
||||||
|
representation_ms_info['timescale']) if 'segment_duration' in representation_ms_info else None
|
||||||
|
for segment_url in representation_ms_info['segment_urls']:
|
||||||
|
fragment = {
|
||||||
|
location_key(segment_url): segment_url,
|
||||||
|
}
|
||||||
|
if segment_duration:
|
||||||
|
fragment['duration'] = segment_duration
|
||||||
|
fragments.append(fragment)
|
||||||
|
representation_ms_info['fragments'] = fragments
|
||||||
# NB: MPD manifest may contain direct URLs to unfragmented media.
|
# NB: MPD manifest may contain direct URLs to unfragmented media.
|
||||||
# No fragments key is present in this case.
|
# No fragments key is present in this case.
|
||||||
if 'fragments' in representation_ms_info:
|
if 'fragments' in representation_ms_info:
|
||||||
@ -1993,16 +2008,14 @@ class InfoExtractor(object):
|
|||||||
f['url'] = initialization_url
|
f['url'] = initialization_url
|
||||||
f['fragments'].append({location_key(initialization_url): initialization_url})
|
f['fragments'].append({location_key(initialization_url): initialization_url})
|
||||||
f['fragments'].extend(representation_ms_info['fragments'])
|
f['fragments'].extend(representation_ms_info['fragments'])
|
||||||
try:
|
# According to [1, 5.3.5.2, Table 7, page 35] @id of Representation
|
||||||
existing_format = next(
|
# is not necessarily unique within a Period thus formats with
|
||||||
fo for fo in formats
|
# the same `format_id` are quite possible. There are numerous examples
|
||||||
if fo['format_id'] == representation_id)
|
# of such manifests (see https://github.com/rg3/youtube-dl/issues/15111,
|
||||||
except StopIteration:
|
# https://github.com/rg3/youtube-dl/issues/13919)
|
||||||
full_info = formats_dict.get(representation_id, {}).copy()
|
full_info = formats_dict.get(representation_id, {}).copy()
|
||||||
full_info.update(f)
|
full_info.update(f)
|
||||||
formats.append(full_info)
|
formats.append(full_info)
|
||||||
else:
|
|
||||||
existing_format.update(f)
|
|
||||||
else:
|
else:
|
||||||
self.report_warning('Unknown MIME type %s in DASH manifest' % mime_type)
|
self.report_warning('Unknown MIME type %s in DASH manifest' % mime_type)
|
||||||
return formats
|
return formats
|
||||||
@ -2042,7 +2055,7 @@ class InfoExtractor(object):
|
|||||||
stream_timescale = int_or_none(stream.get('TimeScale')) or timescale
|
stream_timescale = int_or_none(stream.get('TimeScale')) or timescale
|
||||||
stream_name = stream.get('Name')
|
stream_name = stream.get('Name')
|
||||||
for track in stream.findall('QualityLevel'):
|
for track in stream.findall('QualityLevel'):
|
||||||
fourcc = track.get('FourCC')
|
fourcc = track.get('FourCC', 'AACL' if track.get('AudioTag') == '255' else None)
|
||||||
# TODO: add support for WVC1 and WMAP
|
# TODO: add support for WVC1 and WMAP
|
||||||
if fourcc not in ('H264', 'AVC1', 'AACL'):
|
if fourcc not in ('H264', 'AVC1', 'AACL'):
|
||||||
self.report_warning('%s is not a supported codec' % fourcc)
|
self.report_warning('%s is not a supported codec' % fourcc)
|
||||||
@ -2233,27 +2246,35 @@ class InfoExtractor(object):
|
|||||||
return formats
|
return formats
|
||||||
|
|
||||||
def _extract_wowza_formats(self, url, video_id, m3u8_entry_protocol='m3u8_native', skip_protocols=[]):
|
def _extract_wowza_formats(self, url, video_id, m3u8_entry_protocol='m3u8_native', skip_protocols=[]):
|
||||||
|
query = compat_urlparse.urlparse(url).query
|
||||||
url = re.sub(r'/(?:manifest|playlist|jwplayer)\.(?:m3u8|f4m|mpd|smil)', '', url)
|
url = re.sub(r'/(?:manifest|playlist|jwplayer)\.(?:m3u8|f4m|mpd|smil)', '', url)
|
||||||
url_base = self._search_regex(
|
url_base = self._search_regex(
|
||||||
r'(?:(?:https?|rtmp|rtsp):)?(//[^?]+)', url, 'format url')
|
r'(?:(?:https?|rtmp|rtsp):)?(//[^?]+)', url, 'format url')
|
||||||
http_base_url = '%s:%s' % ('http', url_base)
|
http_base_url = '%s:%s' % ('http', url_base)
|
||||||
formats = []
|
formats = []
|
||||||
|
|
||||||
|
def manifest_url(manifest):
|
||||||
|
m_url = '%s/%s' % (http_base_url, manifest)
|
||||||
|
if query:
|
||||||
|
m_url += '?%s' % query
|
||||||
|
return m_url
|
||||||
|
|
||||||
if 'm3u8' not in skip_protocols:
|
if 'm3u8' not in skip_protocols:
|
||||||
formats.extend(self._extract_m3u8_formats(
|
formats.extend(self._extract_m3u8_formats(
|
||||||
http_base_url + '/playlist.m3u8', video_id, 'mp4',
|
manifest_url('playlist.m3u8'), video_id, 'mp4',
|
||||||
m3u8_entry_protocol, m3u8_id='hls', fatal=False))
|
m3u8_entry_protocol, m3u8_id='hls', fatal=False))
|
||||||
if 'f4m' not in skip_protocols:
|
if 'f4m' not in skip_protocols:
|
||||||
formats.extend(self._extract_f4m_formats(
|
formats.extend(self._extract_f4m_formats(
|
||||||
http_base_url + '/manifest.f4m',
|
manifest_url('manifest.f4m'),
|
||||||
video_id, f4m_id='hds', fatal=False))
|
video_id, f4m_id='hds', fatal=False))
|
||||||
if 'dash' not in skip_protocols:
|
if 'dash' not in skip_protocols:
|
||||||
formats.extend(self._extract_mpd_formats(
|
formats.extend(self._extract_mpd_formats(
|
||||||
http_base_url + '/manifest.mpd',
|
manifest_url('manifest.mpd'),
|
||||||
video_id, mpd_id='dash', fatal=False))
|
video_id, mpd_id='dash', fatal=False))
|
||||||
if re.search(r'(?:/smil:|\.smil)', url_base):
|
if re.search(r'(?:/smil:|\.smil)', url_base):
|
||||||
if 'smil' not in skip_protocols:
|
if 'smil' not in skip_protocols:
|
||||||
rtmp_formats = self._extract_smil_formats(
|
rtmp_formats = self._extract_smil_formats(
|
||||||
http_base_url + '/jwplayer.smil',
|
manifest_url('jwplayer.smil'),
|
||||||
video_id, fatal=False)
|
video_id, fatal=False)
|
||||||
for rtmp_format in rtmp_formats:
|
for rtmp_format in rtmp_formats:
|
||||||
rtsp_format = rtmp_format.copy()
|
rtsp_format = rtmp_format.copy()
|
||||||
|
@ -38,11 +38,32 @@ class CrunchyrollBaseIE(InfoExtractor):
|
|||||||
_LOGIN_FORM = 'login_form'
|
_LOGIN_FORM = 'login_form'
|
||||||
_NETRC_MACHINE = 'crunchyroll'
|
_NETRC_MACHINE = 'crunchyroll'
|
||||||
|
|
||||||
|
def _call_rpc_api(self, method, video_id, note=None, data=None):
|
||||||
|
data = data or {}
|
||||||
|
data['req'] = 'RpcApi' + method
|
||||||
|
data = compat_urllib_parse_urlencode(data).encode('utf-8')
|
||||||
|
return self._download_xml(
|
||||||
|
'http://www.crunchyroll.com/xml/',
|
||||||
|
video_id, note, fatal=False, data=data, headers={
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
})
|
||||||
|
|
||||||
def _login(self):
|
def _login(self):
|
||||||
(username, password) = self._get_login_info()
|
(username, password) = self._get_login_info()
|
||||||
if username is None:
|
if username is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
self._download_webpage(
|
||||||
|
'https://www.crunchyroll.com/?a=formhandler',
|
||||||
|
None, 'Logging in', 'Wrong login info',
|
||||||
|
data=urlencode_postdata({
|
||||||
|
'formname': 'RpcApiUser_Login',
|
||||||
|
'next_url': 'https://www.crunchyroll.com/acct/membership',
|
||||||
|
'name': username,
|
||||||
|
'password': password,
|
||||||
|
}))
|
||||||
|
|
||||||
|
'''
|
||||||
login_page = self._download_webpage(
|
login_page = self._download_webpage(
|
||||||
self._LOGIN_URL, None, 'Downloading login page')
|
self._LOGIN_URL, None, 'Downloading login page')
|
||||||
|
|
||||||
@ -86,6 +107,7 @@ class CrunchyrollBaseIE(InfoExtractor):
|
|||||||
raise ExtractorError('Unable to login: %s' % error, expected=True)
|
raise ExtractorError('Unable to login: %s' % error, expected=True)
|
||||||
|
|
||||||
raise ExtractorError('Unable to log in')
|
raise ExtractorError('Unable to log in')
|
||||||
|
'''
|
||||||
|
|
||||||
def _real_initialize(self):
|
def _real_initialize(self):
|
||||||
self._login()
|
self._login()
|
||||||
@ -365,15 +387,19 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
|||||||
def _get_subtitles(self, video_id, webpage):
|
def _get_subtitles(self, video_id, webpage):
|
||||||
subtitles = {}
|
subtitles = {}
|
||||||
for sub_id, sub_name in re.findall(r'\bssid=([0-9]+)"[^>]+?\btitle="([^"]+)', webpage):
|
for sub_id, sub_name in re.findall(r'\bssid=([0-9]+)"[^>]+?\btitle="([^"]+)', webpage):
|
||||||
sub_page = self._download_webpage(
|
sub_doc = self._call_rpc_api(
|
||||||
'http://www.crunchyroll.com/xml/?req=RpcApiSubtitle_GetXml&subtitle_script_id=' + sub_id,
|
'Subtitle_GetXml', video_id,
|
||||||
video_id, note='Downloading subtitles for ' + sub_name)
|
'Downloading subtitles for ' + sub_name, data={
|
||||||
id = self._search_regex(r'id=\'([0-9]+)', sub_page, 'subtitle_id', fatal=False)
|
'subtitle_script_id': sub_id,
|
||||||
iv = self._search_regex(r'<iv>([^<]+)', sub_page, 'subtitle_iv', fatal=False)
|
})
|
||||||
data = self._search_regex(r'<data>([^<]+)', sub_page, 'subtitle_data', fatal=False)
|
if sub_doc is None:
|
||||||
if not id or not iv or not data:
|
|
||||||
continue
|
continue
|
||||||
subtitle = self._decrypt_subtitles(data, iv, id).decode('utf-8')
|
sid = sub_doc.get('id')
|
||||||
|
iv = xpath_text(sub_doc, 'iv', 'subtitle iv')
|
||||||
|
data = xpath_text(sub_doc, 'data', 'subtitle data')
|
||||||
|
if not sid or not iv or not data:
|
||||||
|
continue
|
||||||
|
subtitle = self._decrypt_subtitles(data, iv, sid).decode('utf-8')
|
||||||
lang_code = self._search_regex(r'lang_code=["\']([^"\']+)', subtitle, 'subtitle_lang_code', fatal=False)
|
lang_code = self._search_regex(r'lang_code=["\']([^"\']+)', subtitle, 'subtitle_lang_code', fatal=False)
|
||||||
if not lang_code:
|
if not lang_code:
|
||||||
continue
|
continue
|
||||||
@ -444,15 +470,29 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
|||||||
for fmt in available_fmts:
|
for fmt in available_fmts:
|
||||||
stream_quality, stream_format = self._FORMAT_IDS[fmt]
|
stream_quality, stream_format = self._FORMAT_IDS[fmt]
|
||||||
video_format = fmt + 'p'
|
video_format = fmt + 'p'
|
||||||
streamdata_req = sanitized_Request(
|
stream_infos = []
|
||||||
'http://www.crunchyroll.com/xml/?req=RpcApiVideoPlayer_GetStandardConfig&media_id=%s&video_format=%s&video_quality=%s'
|
streamdata = self._call_rpc_api(
|
||||||
% (video_id, stream_format, stream_quality),
|
'VideoPlayer_GetStandardConfig', video_id,
|
||||||
compat_urllib_parse_urlencode({'current_page': url}).encode('utf-8'))
|
'Downloading media info for %s' % video_format, data={
|
||||||
streamdata_req.add_header('Content-Type', 'application/x-www-form-urlencoded')
|
'media_id': video_id,
|
||||||
streamdata = self._download_xml(
|
'video_format': stream_format,
|
||||||
streamdata_req, video_id,
|
'video_quality': stream_quality,
|
||||||
note='Downloading media info for %s' % video_format)
|
'current_page': url,
|
||||||
|
})
|
||||||
|
if streamdata is not None:
|
||||||
stream_info = streamdata.find('./{default}preload/stream_info')
|
stream_info = streamdata.find('./{default}preload/stream_info')
|
||||||
|
if stream_info is not None:
|
||||||
|
stream_infos.append(stream_info)
|
||||||
|
stream_info = self._call_rpc_api(
|
||||||
|
'VideoEncode_GetStreamInfo', video_id,
|
||||||
|
'Downloading stream info for %s' % video_format, data={
|
||||||
|
'media_id': video_id,
|
||||||
|
'video_format': stream_format,
|
||||||
|
'video_encode_quality': stream_quality,
|
||||||
|
})
|
||||||
|
if stream_info is not None:
|
||||||
|
stream_infos.append(stream_info)
|
||||||
|
for stream_info in stream_infos:
|
||||||
video_encode_id = xpath_text(stream_info, './video_encode_id')
|
video_encode_id = xpath_text(stream_info, './video_encode_id')
|
||||||
if video_encode_id in video_encode_ids:
|
if video_encode_id in video_encode_ids:
|
||||||
continue
|
continue
|
||||||
@ -473,7 +513,6 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
|||||||
metadata = stream_info.find('./metadata')
|
metadata = stream_info.find('./metadata')
|
||||||
format_info = {
|
format_info = {
|
||||||
'format': video_format,
|
'format': video_format,
|
||||||
'format_id': video_format,
|
|
||||||
'height': int_or_none(xpath_text(metadata, './height')),
|
'height': int_or_none(xpath_text(metadata, './height')),
|
||||||
'width': int_or_none(xpath_text(metadata, './width')),
|
'width': int_or_none(xpath_text(metadata, './width')),
|
||||||
}
|
}
|
||||||
@ -486,23 +525,24 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
|||||||
path='%s/%s' % (remove_end(parsed_video_url.path, '/'), video_file.split(':')[-1])))
|
path='%s/%s' % (remove_end(parsed_video_url.path, '/'), video_file.split(':')[-1])))
|
||||||
if self._is_valid_url(direct_video_url, video_id, video_format):
|
if self._is_valid_url(direct_video_url, video_id, video_format):
|
||||||
format_info.update({
|
format_info.update({
|
||||||
|
'format_id': 'http-' + video_format,
|
||||||
'url': direct_video_url,
|
'url': direct_video_url,
|
||||||
})
|
})
|
||||||
formats.append(format_info)
|
formats.append(format_info)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
format_info.update({
|
format_info.update({
|
||||||
|
'format_id': 'rtmp-' + video_format,
|
||||||
'url': video_url,
|
'url': video_url,
|
||||||
'play_path': video_file,
|
'play_path': video_file,
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
})
|
})
|
||||||
formats.append(format_info)
|
formats.append(format_info)
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats, ('height', 'width', 'tbr', 'fps'))
|
||||||
|
|
||||||
metadata = self._download_xml(
|
metadata = self._call_rpc_api(
|
||||||
'http://www.crunchyroll.com/xml', video_id,
|
'VideoPlayer_GetMediaMetadata', video_id,
|
||||||
note='Downloading media info', query={
|
note='Downloading media info', data={
|
||||||
'req': 'RpcApiVideoPlayer_GetMediaMetadata',
|
|
||||||
'media_id': video_id,
|
'media_id': video_id,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -4,13 +4,14 @@ import re
|
|||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
int_or_none,
|
|
||||||
unescapeHTML,
|
|
||||||
find_xpath_attr,
|
|
||||||
smuggle_url,
|
|
||||||
determine_ext,
|
determine_ext,
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
extract_attributes,
|
extract_attributes,
|
||||||
|
find_xpath_attr,
|
||||||
|
get_element_by_class,
|
||||||
|
int_or_none,
|
||||||
|
smuggle_url,
|
||||||
|
unescapeHTML,
|
||||||
)
|
)
|
||||||
from .senateisvp import SenateISVPIE
|
from .senateisvp import SenateISVPIE
|
||||||
from .ustream import UstreamIE
|
from .ustream import UstreamIE
|
||||||
@ -68,6 +69,10 @@ class CSpanIE(InfoExtractor):
|
|||||||
'uploader': 'HouseCommittee',
|
'uploader': 'HouseCommittee',
|
||||||
'uploader_id': '12987475',
|
'uploader_id': '12987475',
|
||||||
},
|
},
|
||||||
|
}, {
|
||||||
|
# Audio Only
|
||||||
|
'url': 'https://www.c-span.org/video/?437336-1/judiciary-antitrust-competition-policy-consumer-rights',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
BRIGHTCOVE_URL_TEMPLATE = 'http://players.brightcove.net/%s/%s_%s/index.html?videoId=%s'
|
BRIGHTCOVE_URL_TEMPLATE = 'http://players.brightcove.net/%s/%s_%s/index.html?videoId=%s'
|
||||||
|
|
||||||
@ -111,7 +116,15 @@ class CSpanIE(InfoExtractor):
|
|||||||
title = self._og_search_title(webpage)
|
title = self._og_search_title(webpage)
|
||||||
surl = smuggle_url(senate_isvp_url, {'force_title': title})
|
surl = smuggle_url(senate_isvp_url, {'force_title': title})
|
||||||
return self.url_result(surl, 'SenateISVP', video_id, title)
|
return self.url_result(surl, 'SenateISVP', video_id, title)
|
||||||
|
video_id = self._search_regex(
|
||||||
|
r'jwsetup\.clipprog\s*=\s*(\d+);',
|
||||||
|
webpage, 'jwsetup program id', default=None)
|
||||||
|
if video_id:
|
||||||
|
video_type = 'program'
|
||||||
if video_type is None or video_id is None:
|
if video_type is None or video_id is None:
|
||||||
|
error_message = get_element_by_class('VLplayer-error-message', webpage)
|
||||||
|
if error_message:
|
||||||
|
raise ExtractorError(error_message)
|
||||||
raise ExtractorError('unable to find video id and type')
|
raise ExtractorError('unable to find video id and type')
|
||||||
|
|
||||||
def get_text_attr(d, attr):
|
def get_text_attr(d, attr):
|
||||||
@ -138,7 +151,7 @@ class CSpanIE(InfoExtractor):
|
|||||||
entries = []
|
entries = []
|
||||||
for partnum, f in enumerate(files):
|
for partnum, f in enumerate(files):
|
||||||
formats = []
|
formats = []
|
||||||
for quality in f['qualities']:
|
for quality in f.get('qualities', []):
|
||||||
formats.append({
|
formats.append({
|
||||||
'format_id': '%s-%sp' % (get_text_attr(quality, 'bitrate'), get_text_attr(quality, 'height')),
|
'format_id': '%s-%sp' % (get_text_attr(quality, 'bitrate'), get_text_attr(quality, 'height')),
|
||||||
'url': unescapeHTML(get_text_attr(quality, 'file')),
|
'url': unescapeHTML(get_text_attr(quality, 'file')),
|
||||||
|
@ -413,52 +413,3 @@ class DailymotionUserIE(DailymotionPlaylistIE):
|
|||||||
'title': full_user,
|
'title': full_user,
|
||||||
'entries': self._extract_entries(user),
|
'entries': self._extract_entries(user),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class DailymotionCloudIE(DailymotionBaseInfoExtractor):
|
|
||||||
_VALID_URL_PREFIX = r'https?://api\.dmcloud\.net/(?:player/)?embed/'
|
|
||||||
_VALID_URL = r'%s[^/]+/(?P<id>[^/?]+)' % _VALID_URL_PREFIX
|
|
||||||
_VALID_EMBED_URL = r'%s[^/]+/[^\'"]+' % _VALID_URL_PREFIX
|
|
||||||
|
|
||||||
_TESTS = [{
|
|
||||||
# From http://www.francetvinfo.fr/economie/entreprises/les-entreprises-familiales-le-secret-de-la-reussite_933271.html
|
|
||||||
# Tested at FranceTvInfo_2
|
|
||||||
'url': 'http://api.dmcloud.net/embed/4e7343f894a6f677b10006b4/556e03339473995ee145930c?auth=1464865870-0-jyhsm84b-ead4c701fb750cf9367bf4447167a3db&autoplay=1',
|
|
||||||
'only_matching': True,
|
|
||||||
}, {
|
|
||||||
# http://www.francetvinfo.fr/societe/larguez-les-amarres-le-cobaturage-se-developpe_980101.html
|
|
||||||
'url': 'http://api.dmcloud.net/player/embed/4e7343f894a6f677b10006b4/559545469473996d31429f06?auth=1467430263-0-90tglw2l-a3a4b64ed41efe48d7fccad85b8b8fda&autoplay=1',
|
|
||||||
'only_matching': True,
|
|
||||||
}]
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _extract_dmcloud_url(cls, webpage):
|
|
||||||
mobj = re.search(r'<iframe[^>]+src=[\'"](%s)[\'"]' % cls._VALID_EMBED_URL, webpage)
|
|
||||||
if mobj:
|
|
||||||
return mobj.group(1)
|
|
||||||
|
|
||||||
mobj = re.search(
|
|
||||||
r'<input[^>]+id=[\'"]dmcloudUrlEmissionSelect[\'"][^>]+value=[\'"](%s)[\'"]' % cls._VALID_EMBED_URL,
|
|
||||||
webpage)
|
|
||||||
if mobj:
|
|
||||||
return mobj.group(1)
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
|
||||||
video_id = self._match_id(url)
|
|
||||||
|
|
||||||
webpage = self._download_webpage_no_ff(url, video_id)
|
|
||||||
|
|
||||||
title = self._html_search_regex(r'<title>([^>]+)</title>', webpage, 'title')
|
|
||||||
|
|
||||||
video_info = self._parse_json(self._search_regex(
|
|
||||||
r'var\s+info\s*=\s*([^;]+);', webpage, 'video info'), video_id)
|
|
||||||
|
|
||||||
# TODO: parse ios_url, which is in fact a manifest
|
|
||||||
video_url = video_info['mp4_url']
|
|
||||||
|
|
||||||
return {
|
|
||||||
'id': video_id,
|
|
||||||
'url': video_url,
|
|
||||||
'title': title,
|
|
||||||
'thumbnail': video_info.get('thumbnail_url'),
|
|
||||||
}
|
|
||||||
|
@ -13,33 +13,30 @@ from ..aes import (
|
|||||||
from ..utils import (
|
from ..utils import (
|
||||||
bytes_to_intlist,
|
bytes_to_intlist,
|
||||||
bytes_to_long,
|
bytes_to_long,
|
||||||
clean_html,
|
extract_attributes,
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
intlist_to_bytes,
|
intlist_to_bytes,
|
||||||
get_element_by_id,
|
|
||||||
js_to_json,
|
js_to_json,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
long_to_bytes,
|
long_to_bytes,
|
||||||
pkcs1pad,
|
pkcs1pad,
|
||||||
remove_end,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class DaisukiIE(InfoExtractor):
|
class DaisukiMottoIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?daisuki\.net/[^/]+/[^/]+/[^/]+/watch\.[^.]+\.(?P<id>\d+)\.html'
|
_VALID_URL = r'https?://motto\.daisuki\.net/framewatch/embed/[^/]+/(?P<id>[0-9a-zA-Z]{3})'
|
||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.daisuki.net/tw/en/anime/watch.TheIdolMasterCG.11213.html',
|
'url': 'http://motto.daisuki.net/framewatch/embed/embedDRAGONBALLSUPERUniverseSurvivalsaga/V2e/760/428',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '11213',
|
'id': 'V2e',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': '#01 Who is in the pumpkin carriage? - THE IDOLM@STER CINDERELLA GIRLS',
|
'title': '#117 SHOWDOWN OF LOVE! ANDROIDS VS UNIVERSE 2!!',
|
||||||
'subtitles': {
|
'subtitles': {
|
||||||
'mul': [{
|
'mul': [{
|
||||||
'ext': 'ttml',
|
'ext': 'ttml',
|
||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
'creator': 'BANDAI NAMCO Entertainment',
|
|
||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
'skip_download': True, # AES-encrypted HLS stream
|
'skip_download': True, # AES-encrypted HLS stream
|
||||||
@ -73,7 +70,9 @@ class DaisukiIE(InfoExtractor):
|
|||||||
|
|
||||||
n, e = self._RSA_KEY
|
n, e = self._RSA_KEY
|
||||||
encrypted_aeskey = long_to_bytes(pow(bytes_to_long(padded_aeskey), e, n))
|
encrypted_aeskey = long_to_bytes(pow(bytes_to_long(padded_aeskey), e, n))
|
||||||
init_data = self._download_json('http://www.daisuki.net/bin/bgn/init', video_id, query={
|
init_data = self._download_json(
|
||||||
|
'http://motto.daisuki.net/fastAPI/bgn/init/',
|
||||||
|
video_id, query={
|
||||||
's': flashvars.get('s', ''),
|
's': flashvars.get('s', ''),
|
||||||
'c': flashvars.get('ss3_prm', ''),
|
'c': flashvars.get('ss3_prm', ''),
|
||||||
'e': url,
|
'e': url,
|
||||||
@ -98,14 +97,11 @@ class DaisukiIE(InfoExtractor):
|
|||||||
aes_key, iv)).decode('utf-8').rstrip('\0'),
|
aes_key, iv)).decode('utf-8').rstrip('\0'),
|
||||||
video_id)
|
video_id)
|
||||||
|
|
||||||
|
title = rtn['title_str']
|
||||||
|
|
||||||
formats = self._extract_m3u8_formats(
|
formats = self._extract_m3u8_formats(
|
||||||
rtn['play_url'], video_id, ext='mp4', entry_protocol='m3u8_native')
|
rtn['play_url'], video_id, ext='mp4', entry_protocol='m3u8_native')
|
||||||
|
|
||||||
title = remove_end(self._og_search_title(webpage), ' - DAISUKI')
|
|
||||||
|
|
||||||
creator = self._html_search_regex(
|
|
||||||
r'Creator\s*:\s*([^<]+)', webpage, 'creator', fatal=False)
|
|
||||||
|
|
||||||
subtitles = {}
|
subtitles = {}
|
||||||
caption_url = rtn.get('caption_url')
|
caption_url = rtn.get('caption_url')
|
||||||
if caption_url:
|
if caption_url:
|
||||||
@ -120,21 +116,18 @@ class DaisukiIE(InfoExtractor):
|
|||||||
'title': title,
|
'title': title,
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
'subtitles': subtitles,
|
'subtitles': subtitles,
|
||||||
'creator': creator,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class DaisukiPlaylistIE(InfoExtractor):
|
class DaisukiMottoPlaylistIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)daisuki\.net/[^/]+/[^/]+/[^/]+/detail\.(?P<id>[a-zA-Z0-9]+)\.html'
|
_VALID_URL = r'https?://motto\.daisuki\.net/(?P<id>information)/'
|
||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.daisuki.net/tw/en/anime/detail.TheIdolMasterCG.html',
|
'url': 'http://motto.daisuki.net/information/',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'TheIdolMasterCG',
|
'title': 'DRAGON BALL SUPER',
|
||||||
'title': 'THE IDOLM@STER CINDERELLA GIRLS',
|
|
||||||
'description': 'md5:0f2c028a9339f7a2c7fbf839edc5c5d8',
|
|
||||||
},
|
},
|
||||||
'playlist_count': 26,
|
'playlist_mincount': 117,
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
@ -142,18 +135,19 @@ class DaisukiPlaylistIE(InfoExtractor):
|
|||||||
|
|
||||||
webpage = self._download_webpage(url, playlist_id)
|
webpage = self._download_webpage(url, playlist_id)
|
||||||
|
|
||||||
episode_pattern = r'''(?sx)
|
entries = []
|
||||||
<img[^>]+delay="[^"]+/(\d+)/movie\.jpg".+?
|
for li in re.findall(r'(<li[^>]+?data-product_id="[a-zA-Z0-9]{3}"[^>]+>)', webpage):
|
||||||
<p[^>]+class=".*?\bepisodeNumber\b.*?">(?:<a[^>]+>)?([^<]+)'''
|
attr = extract_attributes(li)
|
||||||
entries = [{
|
ad_id = attr.get('data-ad_id')
|
||||||
|
product_id = attr.get('data-product_id')
|
||||||
|
if ad_id and product_id:
|
||||||
|
episode_id = attr.get('data-chapter')
|
||||||
|
entries.append({
|
||||||
'_type': 'url_transparent',
|
'_type': 'url_transparent',
|
||||||
'url': url.replace('detail', 'watch').replace('.html', '.' + movie_id + '.html'),
|
'url': 'http://motto.daisuki.net/framewatch/embed/%s/%s/760/428' % (ad_id, product_id),
|
||||||
'episode_id': episode_id,
|
'episode_id': episode_id,
|
||||||
'episode_number': int_or_none(episode_id),
|
'episode_number': int_or_none(episode_id),
|
||||||
} for movie_id, episode_id in re.findall(episode_pattern, webpage)]
|
'ie_key': 'DaisukiMotto',
|
||||||
|
})
|
||||||
|
|
||||||
playlist_title = remove_end(
|
return self.playlist_result(entries, playlist_title='DRAGON BALL SUPER')
|
||||||
self._og_search_title(webpage, fatal=False), ' - Anime - DAISUKI')
|
|
||||||
playlist_description = clean_html(get_element_by_id('synopsisTxt', webpage))
|
|
||||||
|
|
||||||
return self.playlist_result(entries, playlist_id, playlist_title, playlist_description)
|
|
||||||
|
@ -1,14 +1,18 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from .common import InfoExtractor
|
import random
|
||||||
|
import re
|
||||||
|
import string
|
||||||
|
|
||||||
|
from .discoverygo import DiscoveryGoBaseIE
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
parse_duration,
|
ExtractorError,
|
||||||
parse_iso8601,
|
update_url_query,
|
||||||
)
|
)
|
||||||
from ..compat import compat_str
|
from ..compat import compat_HTTPError
|
||||||
|
|
||||||
|
|
||||||
class DiscoveryIE(InfoExtractor):
|
class DiscoveryIE(DiscoveryGoBaseIE):
|
||||||
_VALID_URL = r'''(?x)https?://(?:www\.)?(?:
|
_VALID_URL = r'''(?x)https?://(?:www\.)?(?:
|
||||||
discovery|
|
discovery|
|
||||||
investigationdiscovery|
|
investigationdiscovery|
|
||||||
@ -19,79 +23,65 @@ class DiscoveryIE(InfoExtractor):
|
|||||||
sciencechannel|
|
sciencechannel|
|
||||||
tlc|
|
tlc|
|
||||||
velocity
|
velocity
|
||||||
)\.com/(?:[^/]+/)*(?P<id>[^./?#]+)'''
|
)\.com(?P<path>/tv-shows/[^/]+/(?:video|full-episode)s/(?P<id>[^./?#]+))'''
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://www.discovery.com/tv-shows/mythbusters/videos/mission-impossible-outtakes.htm',
|
'url': 'https://www.discovery.com/tv-shows/cash-cab/videos/dave-foley',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '20769',
|
'id': '5a2d9b4d6b66d17a5026e1fd',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Mission Impossible Outtakes',
|
'title': 'Dave Foley',
|
||||||
'description': ('Watch Jamie Hyneman and Adam Savage practice being'
|
'description': 'md5:4b39bcafccf9167ca42810eb5f28b01f',
|
||||||
' each other -- to the point of confusing Jamie\'s dog -- and '
|
'duration': 608,
|
||||||
'don\'t miss Adam moon-walking as Jamie ... behind Jamie\'s'
|
|
||||||
' back.'),
|
|
||||||
'duration': 156,
|
|
||||||
'timestamp': 1302032462,
|
|
||||||
'upload_date': '20110405',
|
|
||||||
'uploader_id': '103207',
|
|
||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
'skip_download': True, # requires ffmpeg
|
'skip_download': True, # requires ffmpeg
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://www.discovery.com/tv-shows/mythbusters/videos/mythbusters-the-simpsons',
|
'url': 'https://www.investigationdiscovery.com/tv-shows/final-vision/full-episodes/final-vision',
|
||||||
'info_dict': {
|
'only_matching': True,
|
||||||
'id': 'mythbusters-the-simpsons',
|
|
||||||
'title': 'MythBusters: The Simpsons',
|
|
||||||
},
|
|
||||||
'playlist_mincount': 10,
|
|
||||||
}, {
|
|
||||||
'url': 'http://www.animalplanet.com/longfin-eels-maneaters/',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '78326',
|
|
||||||
'ext': 'mp4',
|
|
||||||
'title': 'Longfin Eels: Maneaters?',
|
|
||||||
'description': 'Jeremy Wade tests whether or not New Zealand\'s longfin eels are man-eaters by covering himself in fish guts and getting in the water with them.',
|
|
||||||
'upload_date': '20140725',
|
|
||||||
'timestamp': 1406246400,
|
|
||||||
'duration': 116,
|
|
||||||
'uploader_id': '103207',
|
|
||||||
},
|
|
||||||
'params': {
|
|
||||||
'skip_download': True, # requires ffmpeg
|
|
||||||
}
|
|
||||||
}]
|
}]
|
||||||
|
_GEO_COUNTRIES = ['US']
|
||||||
|
_GEO_BYPASS = False
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
display_id = self._match_id(url)
|
path, display_id = re.match(self._VALID_URL, url).groups()
|
||||||
info = self._download_json(url + '?flat=1', display_id)
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
|
||||||
video_title = info.get('playlist_title') or info.get('video_title')
|
react_data = self._parse_json(self._search_regex(
|
||||||
|
r'window\.__reactTransmitPacket\s*=\s*({.+?});',
|
||||||
|
webpage, 'react data'), display_id)
|
||||||
|
content_blocks = react_data['layout'][path]['contentBlocks']
|
||||||
|
video = next(cb for cb in content_blocks if cb.get('type') == 'video')['content']['items'][0]
|
||||||
|
video_id = video['id']
|
||||||
|
|
||||||
entries = []
|
access_token = self._download_json(
|
||||||
|
'https://www.discovery.com/anonymous', display_id, query={
|
||||||
for idx, video_info in enumerate(info['playlist']):
|
'authLink': update_url_query(
|
||||||
subtitles = {}
|
'https://login.discovery.com/v1/oauth2/authorize', {
|
||||||
caption_url = video_info.get('captionsUrl')
|
'client_id': react_data['application']['apiClientId'],
|
||||||
if caption_url:
|
'redirect_uri': 'https://fusion.ddmcdn.com/app/mercury-sdk/180/redirectHandler.html',
|
||||||
subtitles = {
|
'response_type': 'anonymous',
|
||||||
'en': [{
|
'state': 'nonce,' + ''.join([random.choice(string.ascii_letters) for _ in range(32)]),
|
||||||
'url': caption_url,
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
|
|
||||||
entries.append({
|
|
||||||
'_type': 'url_transparent',
|
|
||||||
'url': 'http://players.brightcove.net/103207/default_default/index.html?videoId=ref:%s' % video_info['referenceId'],
|
|
||||||
'id': compat_str(video_info['id']),
|
|
||||||
'title': video_info['title'],
|
|
||||||
'description': video_info.get('description'),
|
|
||||||
'duration': parse_duration(video_info.get('video_length')),
|
|
||||||
'webpage_url': video_info.get('href') or video_info.get('url'),
|
|
||||||
'thumbnail': video_info.get('thumbnailURL'),
|
|
||||||
'alt_title': video_info.get('secondary_title'),
|
|
||||||
'timestamp': parse_iso8601(video_info.get('publishedDate')),
|
|
||||||
'subtitles': subtitles,
|
|
||||||
})
|
})
|
||||||
|
})['access_token']
|
||||||
|
|
||||||
return self.playlist_result(entries, display_id, video_title)
|
try:
|
||||||
|
stream = self._download_json(
|
||||||
|
'https://api.discovery.com/v1/streaming/video/' + video_id,
|
||||||
|
display_id, headers={
|
||||||
|
'Authorization': 'Bearer ' + access_token,
|
||||||
|
})
|
||||||
|
except ExtractorError as e:
|
||||||
|
if isinstance(e.cause, compat_HTTPError) and e.cause.code == 403:
|
||||||
|
e_description = self._parse_json(
|
||||||
|
e.cause.read().decode(), display_id)['description']
|
||||||
|
if 'resource not available for country' in e_description:
|
||||||
|
self.raise_geo_restricted(countries=self._GEO_COUNTRIES)
|
||||||
|
if 'Authorized Networks' in e_description:
|
||||||
|
raise ExtractorError(
|
||||||
|
'This video is only available via cable service provider subscription that'
|
||||||
|
' is not currently supported. You may want to use --cookies.', expected=True)
|
||||||
|
raise ExtractorError(e_description)
|
||||||
|
raise
|
||||||
|
|
||||||
|
return self._extract_video_info(video, stream, display_id)
|
||||||
|
@ -5,6 +5,7 @@ import re
|
|||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import compat_str
|
from ..compat import compat_str
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
|
determine_ext,
|
||||||
extract_attributes,
|
extract_attributes,
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
@ -27,42 +28,9 @@ class DiscoveryGoBaseIE(InfoExtractor):
|
|||||||
velocitychannel
|
velocitychannel
|
||||||
)go\.com/%s(?P<id>[^/?#&]+)'''
|
)go\.com/%s(?P<id>[^/?#&]+)'''
|
||||||
|
|
||||||
|
def _extract_video_info(self, video, stream, display_id):
|
||||||
class DiscoveryGoIE(DiscoveryGoBaseIE):
|
|
||||||
_VALID_URL = DiscoveryGoBaseIE._VALID_URL_TEMPLATE % r'(?:[^/]+/)+'
|
|
||||||
_GEO_COUNTRIES = ['US']
|
|
||||||
_TEST = {
|
|
||||||
'url': 'https://www.discoverygo.com/bering-sea-gold/reaper-madness/',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '58c167d86b66d12f2addeb01',
|
|
||||||
'ext': 'mp4',
|
|
||||||
'title': 'Reaper Madness',
|
|
||||||
'description': 'md5:09f2c625c99afb8946ed4fb7865f6e78',
|
|
||||||
'duration': 2519,
|
|
||||||
'series': 'Bering Sea Gold',
|
|
||||||
'season_number': 8,
|
|
||||||
'episode_number': 6,
|
|
||||||
'age_limit': 14,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
|
||||||
display_id = self._match_id(url)
|
|
||||||
|
|
||||||
webpage = self._download_webpage(url, display_id)
|
|
||||||
|
|
||||||
container = extract_attributes(
|
|
||||||
self._search_regex(
|
|
||||||
r'(<div[^>]+class=["\']video-player-container[^>]+>)',
|
|
||||||
webpage, 'video container'))
|
|
||||||
|
|
||||||
video = self._parse_json(
|
|
||||||
container.get('data-video') or container.get('data-json'),
|
|
||||||
display_id)
|
|
||||||
|
|
||||||
title = video['name']
|
title = video['name']
|
||||||
|
|
||||||
stream = video.get('stream')
|
|
||||||
if not stream:
|
if not stream:
|
||||||
if video.get('authenticated') is True:
|
if video.get('authenticated') is True:
|
||||||
raise ExtractorError(
|
raise ExtractorError(
|
||||||
@ -106,7 +74,11 @@ class DiscoveryGoIE(DiscoveryGoBaseIE):
|
|||||||
not subtitle_url.startswith('http')):
|
not subtitle_url.startswith('http')):
|
||||||
continue
|
continue
|
||||||
lang = caption.get('fileLang', 'en')
|
lang = caption.get('fileLang', 'en')
|
||||||
subtitles.setdefault(lang, []).append({'url': subtitle_url})
|
ext = determine_ext(subtitle_url)
|
||||||
|
subtitles.setdefault(lang, []).append({
|
||||||
|
'url': subtitle_url,
|
||||||
|
'ext': 'ttml' if ext == 'xml' else ext,
|
||||||
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
@ -124,6 +96,43 @@ class DiscoveryGoIE(DiscoveryGoBaseIE):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class DiscoveryGoIE(DiscoveryGoBaseIE):
|
||||||
|
_VALID_URL = DiscoveryGoBaseIE._VALID_URL_TEMPLATE % r'(?:[^/]+/)+'
|
||||||
|
_GEO_COUNTRIES = ['US']
|
||||||
|
_TEST = {
|
||||||
|
'url': 'https://www.discoverygo.com/bering-sea-gold/reaper-madness/',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '58c167d86b66d12f2addeb01',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Reaper Madness',
|
||||||
|
'description': 'md5:09f2c625c99afb8946ed4fb7865f6e78',
|
||||||
|
'duration': 2519,
|
||||||
|
'series': 'Bering Sea Gold',
|
||||||
|
'season_number': 8,
|
||||||
|
'episode_number': 6,
|
||||||
|
'age_limit': 14,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
display_id = self._match_id(url)
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
|
||||||
|
container = extract_attributes(
|
||||||
|
self._search_regex(
|
||||||
|
r'(<div[^>]+class=["\']video-player-container[^>]+>)',
|
||||||
|
webpage, 'video container'))
|
||||||
|
|
||||||
|
video = self._parse_json(
|
||||||
|
container.get('data-video') or container.get('data-json'),
|
||||||
|
display_id)
|
||||||
|
|
||||||
|
stream = video.get('stream')
|
||||||
|
|
||||||
|
return self._extract_video_info(video, stream, display_id)
|
||||||
|
|
||||||
|
|
||||||
class DiscoveryGoPlaylistIE(DiscoveryGoBaseIE):
|
class DiscoveryGoPlaylistIE(DiscoveryGoBaseIE):
|
||||||
_VALID_URL = DiscoveryGoBaseIE._VALID_URL_TEMPLATE % ''
|
_VALID_URL = DiscoveryGoBaseIE._VALID_URL_TEMPLATE % ''
|
||||||
_TEST = {
|
_TEST = {
|
||||||
|
@ -10,6 +10,7 @@ from ..utils import (
|
|||||||
compat_str,
|
compat_str,
|
||||||
determine_ext,
|
determine_ext,
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
|
update_url_query,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -108,9 +109,16 @@ class DisneyIE(InfoExtractor):
|
|||||||
continue
|
continue
|
||||||
tbr = int_or_none(flavor.get('bitrate'))
|
tbr = int_or_none(flavor.get('bitrate'))
|
||||||
if tbr == 99999:
|
if tbr == 99999:
|
||||||
formats.extend(self._extract_m3u8_formats(
|
# wrong ks(Kaltura Signature) causes 404 Error
|
||||||
|
flavor_url = update_url_query(flavor_url, {'ks': ''})
|
||||||
|
m3u8_formats = self._extract_m3u8_formats(
|
||||||
flavor_url, video_id, 'mp4',
|
flavor_url, video_id, 'mp4',
|
||||||
m3u8_id=flavor_format, fatal=False))
|
m3u8_id=flavor_format, fatal=False)
|
||||||
|
for f in m3u8_formats:
|
||||||
|
# Apple FairPlay
|
||||||
|
if '/fpshls/' in f['url']:
|
||||||
|
continue
|
||||||
|
formats.append(f)
|
||||||
continue
|
continue
|
||||||
format_id = []
|
format_id = []
|
||||||
if flavor_format:
|
if flavor_format:
|
||||||
|
@ -54,7 +54,7 @@ class DramaFeverBaseIE(AMPIE):
|
|||||||
request = sanitized_Request(
|
request = sanitized_Request(
|
||||||
self._LOGIN_URL, urlencode_postdata(login_form))
|
self._LOGIN_URL, urlencode_postdata(login_form))
|
||||||
response = self._download_webpage(
|
response = self._download_webpage(
|
||||||
request, None, 'Logging in as %s' % username)
|
request, None, 'Logging in')
|
||||||
|
|
||||||
if all(logout_pattern not in response
|
if all(logout_pattern not in response
|
||||||
for logout_pattern in ['href="/accounts/logout/"', '>Log out<']):
|
for logout_pattern in ['href="/accounts/logout/"', '>Log out<']):
|
||||||
|
@ -10,7 +10,7 @@ from ..utils import (
|
|||||||
|
|
||||||
|
|
||||||
class DrTuberIE(InfoExtractor):
|
class DrTuberIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?drtuber\.com/(?:video|embed)/(?P<id>\d+)(?:/(?P<display_id>[\w-]+))?'
|
_VALID_URL = r'https?://(?:(?:www|m)\.)?drtuber\.com/(?:video|embed)/(?P<id>\d+)(?:/(?P<display_id>[\w-]+))?'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://www.drtuber.com/video/1740434/hot-perky-blonde-naked-golf',
|
'url': 'http://www.drtuber.com/video/1740434/hot-perky-blonde-naked-golf',
|
||||||
'md5': '93e680cf2536ad0dfb7e74d94a89facd',
|
'md5': '93e680cf2536ad0dfb7e74d94a89facd',
|
||||||
@ -28,6 +28,9 @@ class DrTuberIE(InfoExtractor):
|
|||||||
}, {
|
}, {
|
||||||
'url': 'http://www.drtuber.com/embed/489939',
|
'url': 'http://www.drtuber.com/embed/489939',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://m.drtuber.com/video/3893529/lingerie-blowjob-from-beautiful-teen',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
133
youtube_dl/extractor/ellentube.py
Normal file
133
youtube_dl/extractor/ellentube.py
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
clean_html,
|
||||||
|
extract_attributes,
|
||||||
|
float_or_none,
|
||||||
|
int_or_none,
|
||||||
|
try_get,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class EllenTubeBaseIE(InfoExtractor):
|
||||||
|
def _extract_data_config(self, webpage, video_id):
|
||||||
|
details = self._search_regex(
|
||||||
|
r'(<[^>]+\bdata-component=(["\'])[Dd]etails.+?></div>)', webpage,
|
||||||
|
'details')
|
||||||
|
return self._parse_json(
|
||||||
|
extract_attributes(details)['data-config'], video_id)
|
||||||
|
|
||||||
|
def _extract_video(self, data, video_id):
|
||||||
|
title = data['title']
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
duration = None
|
||||||
|
for entry in data.get('media'):
|
||||||
|
if entry.get('id') == 'm3u8':
|
||||||
|
formats = self._extract_m3u8_formats(
|
||||||
|
entry['url'], video_id, 'mp4',
|
||||||
|
entry_protocol='m3u8_native', m3u8_id='hls')
|
||||||
|
duration = int_or_none(entry.get('duration'))
|
||||||
|
break
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
def get_insight(kind):
|
||||||
|
return int_or_none(try_get(
|
||||||
|
data, lambda x: x['insight']['%ss' % kind]))
|
||||||
|
|
||||||
|
return {
|
||||||
|
'extractor_key': EllenTubeIE.ie_key(),
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'description': data.get('description'),
|
||||||
|
'duration': duration,
|
||||||
|
'thumbnail': data.get('thumbnail'),
|
||||||
|
'timestamp': float_or_none(data.get('publishTime'), scale=1000),
|
||||||
|
'view_count': get_insight('view'),
|
||||||
|
'like_count': get_insight('like'),
|
||||||
|
'formats': formats,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class EllenTubeIE(EllenTubeBaseIE):
|
||||||
|
_VALID_URL = r'''(?x)
|
||||||
|
(?:
|
||||||
|
ellentube:|
|
||||||
|
https://api-prod\.ellentube\.com/ellenapi/api/item/
|
||||||
|
)
|
||||||
|
(?P<id>[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12})
|
||||||
|
'''
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://api-prod.ellentube.com/ellenapi/api/item/0822171c-3829-43bf-b99f-d77358ae75e3',
|
||||||
|
'md5': '2fabc277131bddafdd120e0fc0f974c9',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '0822171c-3829-43bf-b99f-d77358ae75e3',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Ellen Meets Las Vegas Survivors Jesus Campos and Stephen Schuck',
|
||||||
|
'description': 'md5:76e3355e2242a78ad9e3858e5616923f',
|
||||||
|
'thumbnail': r're:^https?://.+?',
|
||||||
|
'duration': 514,
|
||||||
|
'timestamp': 1508505120,
|
||||||
|
'upload_date': '20171020',
|
||||||
|
'view_count': int,
|
||||||
|
'like_count': int,
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
'url': 'ellentube:734a3353-f697-4e79-9ca9-bfc3002dc1e0',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
video_id = self._match_id(url)
|
||||||
|
data = self._download_json(
|
||||||
|
'https://api-prod.ellentube.com/ellenapi/api/item/%s' % video_id,
|
||||||
|
video_id)
|
||||||
|
return self._extract_video(data, video_id)
|
||||||
|
|
||||||
|
|
||||||
|
class EllenTubeVideoIE(EllenTubeBaseIE):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?ellentube\.com/video/(?P<id>.+?)\.html'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'https://www.ellentube.com/video/ellen-meets-las-vegas-survivors-jesus-campos-and-stephen-schuck.html',
|
||||||
|
'only_matching': True,
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
display_id = self._match_id(url)
|
||||||
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
video_id = self._extract_data_config(webpage, display_id)['id']
|
||||||
|
return self.url_result(
|
||||||
|
'ellentube:%s' % video_id, ie=EllenTubeIE.ie_key(),
|
||||||
|
video_id=video_id)
|
||||||
|
|
||||||
|
|
||||||
|
class EllenTubePlaylistIE(EllenTubeBaseIE):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?ellentube\.com/(?:episode|studios)/(?P<id>.+?)\.html'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://www.ellentube.com/episode/dax-shepard-jordan-fisher-haim.html',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'dax-shepard-jordan-fisher-haim',
|
||||||
|
'title': "Dax Shepard, 'DWTS' Team Jordan Fisher & Lindsay Arnold, HAIM",
|
||||||
|
'description': 'md5:bfc982194dabb3f4e325e43aa6b2e21c',
|
||||||
|
},
|
||||||
|
'playlist_count': 6,
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.ellentube.com/studios/macey-goes-rving0.html',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
display_id = self._match_id(url)
|
||||||
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
data = self._extract_data_config(webpage, display_id)['data']
|
||||||
|
feed = self._download_json(
|
||||||
|
'https://api-prod.ellentube.com/ellenapi/api/feed/?%s'
|
||||||
|
% data['filter'], display_id)
|
||||||
|
entries = [
|
||||||
|
self._extract_video(elem, elem['id'])
|
||||||
|
for elem in feed if elem.get('type') == 'VIDEO' and elem.get('id')]
|
||||||
|
return self.playlist_result(
|
||||||
|
entries, display_id, data.get('title'),
|
||||||
|
clean_html(data.get('description')))
|
@ -1,101 +0,0 @@
|
|||||||
# coding: utf-8
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
|
||||||
from .kaltura import KalturaIE
|
|
||||||
from ..utils import NO_DEFAULT
|
|
||||||
|
|
||||||
|
|
||||||
class EllenTVIE(InfoExtractor):
|
|
||||||
_VALID_URL = r'https?://(?:www\.)?(?:ellentv|ellentube)\.com/videos/(?P<id>[a-z0-9_-]+)'
|
|
||||||
_TESTS = [{
|
|
||||||
'url': 'http://www.ellentv.com/videos/0-ipq1gsai/',
|
|
||||||
'md5': '4294cf98bc165f218aaa0b89e0fd8042',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '0_ipq1gsai',
|
|
||||||
'ext': 'mov',
|
|
||||||
'title': 'Fast Fingers of Fate',
|
|
||||||
'description': 'md5:3539013ddcbfa64b2a6d1b38d910868a',
|
|
||||||
'timestamp': 1428035648,
|
|
||||||
'upload_date': '20150403',
|
|
||||||
'uploader_id': 'batchUser',
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
# not available via http://widgets.ellentube.com/
|
|
||||||
'url': 'http://www.ellentv.com/videos/1-szkgu2m2/',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '1_szkgu2m2',
|
|
||||||
'ext': 'flv',
|
|
||||||
'title': "Ellen's Amazingly Talented Audience",
|
|
||||||
'description': 'md5:86ff1e376ff0d717d7171590e273f0a5',
|
|
||||||
'timestamp': 1255140900,
|
|
||||||
'upload_date': '20091010',
|
|
||||||
'uploader_id': 'ellenkaltura@gmail.com',
|
|
||||||
},
|
|
||||||
'params': {
|
|
||||||
'skip_download': True,
|
|
||||||
},
|
|
||||||
}]
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
|
||||||
video_id = self._match_id(url)
|
|
||||||
|
|
||||||
URLS = ('http://widgets.ellentube.com/videos/%s' % video_id, url)
|
|
||||||
|
|
||||||
for num, url_ in enumerate(URLS, 1):
|
|
||||||
webpage = self._download_webpage(
|
|
||||||
url_, video_id, fatal=num == len(URLS))
|
|
||||||
|
|
||||||
default = NO_DEFAULT if num == len(URLS) else None
|
|
||||||
|
|
||||||
partner_id = self._search_regex(
|
|
||||||
r"var\s+partnerId\s*=\s*'([^']+)", webpage, 'partner id',
|
|
||||||
default=default)
|
|
||||||
|
|
||||||
kaltura_id = self._search_regex(
|
|
||||||
[r'id="kaltura_player_([^"]+)"',
|
|
||||||
r"_wb_entry_id\s*:\s*'([^']+)",
|
|
||||||
r'data-kaltura-entry-id="([^"]+)'],
|
|
||||||
webpage, 'kaltura id', default=default)
|
|
||||||
|
|
||||||
if partner_id and kaltura_id:
|
|
||||||
break
|
|
||||||
|
|
||||||
return self.url_result('kaltura:%s:%s' % (partner_id, kaltura_id), KalturaIE.ie_key())
|
|
||||||
|
|
||||||
|
|
||||||
class EllenTVClipsIE(InfoExtractor):
|
|
||||||
IE_NAME = 'EllenTV:clips'
|
|
||||||
_VALID_URL = r'https?://(?:www\.)?ellentv\.com/episodes/(?P<id>[a-z0-9_-]+)'
|
|
||||||
_TEST = {
|
|
||||||
'url': 'http://www.ellentv.com/episodes/meryl-streep-vanessa-hudgens/',
|
|
||||||
'info_dict': {
|
|
||||||
'id': 'meryl-streep-vanessa-hudgens',
|
|
||||||
'title': 'Meryl Streep, Vanessa Hudgens',
|
|
||||||
},
|
|
||||||
'playlist_mincount': 5,
|
|
||||||
}
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
|
||||||
playlist_id = self._match_id(url)
|
|
||||||
|
|
||||||
webpage = self._download_webpage(url, playlist_id)
|
|
||||||
playlist = self._extract_playlist(webpage, playlist_id)
|
|
||||||
|
|
||||||
return {
|
|
||||||
'_type': 'playlist',
|
|
||||||
'id': playlist_id,
|
|
||||||
'title': self._og_search_title(webpage),
|
|
||||||
'entries': self._extract_entries(playlist)
|
|
||||||
}
|
|
||||||
|
|
||||||
def _extract_playlist(self, webpage, playlist_id):
|
|
||||||
json_string = self._search_regex(r'playerView.addClips\(\[\{(.*?)\}\]\);', webpage, 'json')
|
|
||||||
return self._parse_json('[{' + json_string + '}]', playlist_id)
|
|
||||||
|
|
||||||
def _extract_entries(self, playlist):
|
|
||||||
return [
|
|
||||||
self.url_result(
|
|
||||||
'kaltura:%s:%s' % (item['kaltura_partner_id'], item['kaltura_entry_id']),
|
|
||||||
KalturaIE.ie_key(), video_id=item['kaltura_entry_id'])
|
|
||||||
for item in playlist]
|
|
@ -1,6 +1,9 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
from .once import OnceIE
|
||||||
from ..compat import compat_str
|
from ..compat import compat_str
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
determine_ext,
|
determine_ext,
|
||||||
@ -9,23 +12,28 @@ from ..utils import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ESPNIE(InfoExtractor):
|
class ESPNIE(OnceIE):
|
||||||
_VALID_URL = r'''(?x)
|
_VALID_URL = r'''(?x)
|
||||||
https?://
|
https?://
|
||||||
|
(?:
|
||||||
|
(?:
|
||||||
(?:
|
(?:
|
||||||
(?:(?:\w+\.)+)?espn\.go|
|
(?:(?:\w+\.)+)?espn\.go|
|
||||||
(?:www\.)?espn
|
(?:www\.)?espn
|
||||||
)\.com/
|
)\.com/
|
||||||
(?:
|
(?:
|
||||||
(?:
|
(?:
|
||||||
video/clip|
|
video/(?:clip|iframe/twitter)|
|
||||||
watch/player
|
watch/player
|
||||||
)
|
)
|
||||||
(?:
|
(?:
|
||||||
\?.*?\bid=|
|
.*?\?.*?\bid=|
|
||||||
/_/id/
|
/_/id/
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
)|
|
||||||
|
(?:www\.)espnfc\.(?:com|us)/(?:video/)?[^/]+/\d+/video/
|
||||||
|
)
|
||||||
(?P<id>\d+)
|
(?P<id>\d+)
|
||||||
'''
|
'''
|
||||||
|
|
||||||
@ -77,6 +85,15 @@ class ESPNIE(InfoExtractor):
|
|||||||
}, {
|
}, {
|
||||||
'url': 'http://www.espn.com/video/clip/_/id/17989860',
|
'url': 'http://www.espn.com/video/clip/_/id/17989860',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://espn.go.com/video/iframe/twitter/?cms=espn&id=10365079',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.espnfc.us/video/espn-fc-tv/86/video/3319154/nashville-unveiled-as-the-newest-club-in-mls',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.espnfc.com/english-premier-league/23/video/3324163/premier-league-in-90-seconds-golden-tweets',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
@ -93,7 +110,9 @@ class ESPNIE(InfoExtractor):
|
|||||||
|
|
||||||
def traverse_source(source, base_source_id=None):
|
def traverse_source(source, base_source_id=None):
|
||||||
for source_id, source in source.items():
|
for source_id, source in source.items():
|
||||||
if isinstance(source, compat_str):
|
if source_id == 'alert':
|
||||||
|
continue
|
||||||
|
elif isinstance(source, compat_str):
|
||||||
extract_source(source, base_source_id)
|
extract_source(source, base_source_id)
|
||||||
elif isinstance(source, dict):
|
elif isinstance(source, dict):
|
||||||
traverse_source(
|
traverse_source(
|
||||||
@ -106,7 +125,9 @@ class ESPNIE(InfoExtractor):
|
|||||||
return
|
return
|
||||||
format_urls.add(source_url)
|
format_urls.add(source_url)
|
||||||
ext = determine_ext(source_url)
|
ext = determine_ext(source_url)
|
||||||
if ext == 'smil':
|
if OnceIE.suitable(source_url):
|
||||||
|
formats.extend(self._extract_once_formats(source_url))
|
||||||
|
elif ext == 'smil':
|
||||||
formats.extend(self._extract_smil_formats(
|
formats.extend(self._extract_smil_formats(
|
||||||
source_url, video_id, fatal=False))
|
source_url, video_id, fatal=False))
|
||||||
elif ext == 'f4m':
|
elif ext == 'f4m':
|
||||||
@ -117,12 +138,24 @@ class ESPNIE(InfoExtractor):
|
|||||||
source_url, video_id, 'mp4', entry_protocol='m3u8_native',
|
source_url, video_id, 'mp4', entry_protocol='m3u8_native',
|
||||||
m3u8_id=source_id, fatal=False))
|
m3u8_id=source_id, fatal=False))
|
||||||
else:
|
else:
|
||||||
formats.append({
|
f = {
|
||||||
'url': source_url,
|
'url': source_url,
|
||||||
'format_id': source_id,
|
'format_id': source_id,
|
||||||
|
}
|
||||||
|
mobj = re.search(r'(\d+)p(\d+)_(\d+)k\.', source_url)
|
||||||
|
if mobj:
|
||||||
|
f.update({
|
||||||
|
'height': int(mobj.group(1)),
|
||||||
|
'fps': int(mobj.group(2)),
|
||||||
|
'tbr': int(mobj.group(3)),
|
||||||
})
|
})
|
||||||
|
if source_id == 'mezzanine':
|
||||||
|
f['preference'] = 1
|
||||||
|
formats.append(f)
|
||||||
|
|
||||||
traverse_source(clip['links']['source'])
|
links = clip.get('links', {})
|
||||||
|
traverse_source(links.get('source', {}))
|
||||||
|
traverse_source(links.get('mobile', {}))
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
description = clip.get('caption') or clip.get('description')
|
description = clip.get('caption') or clip.get('description')
|
||||||
@ -144,9 +177,6 @@ class ESPNIE(InfoExtractor):
|
|||||||
class ESPNArticleIE(InfoExtractor):
|
class ESPNArticleIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:espn\.go|(?:www\.)?espn)\.com/(?:[^/]+/)*(?P<id>[^/]+)'
|
_VALID_URL = r'https?://(?:espn\.go|(?:www\.)?espn)\.com/(?:[^/]+/)*(?P<id>[^/]+)'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'https://espn.go.com/video/iframe/twitter/?cms=espn&id=10365079',
|
|
||||||
'only_matching': True,
|
|
||||||
}, {
|
|
||||||
'url': 'http://espn.go.com/nba/recap?gameId=400793786',
|
'url': 'http://espn.go.com/nba/recap?gameId=400793786',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
}, {
|
}, {
|
||||||
@ -175,3 +205,34 @@ class ESPNArticleIE(InfoExtractor):
|
|||||||
|
|
||||||
return self.url_result(
|
return self.url_result(
|
||||||
'http://espn.go.com/video/clip?id=%s' % video_id, ESPNIE.ie_key())
|
'http://espn.go.com/video/clip?id=%s' % video_id, ESPNIE.ie_key())
|
||||||
|
|
||||||
|
|
||||||
|
class FiveThirtyEightIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?fivethirtyeight\.com/features/(?P<id>[^/?#]+)'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://fivethirtyeight.com/features/how-the-6-8-raiders-can-still-make-the-playoffs/',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '21846851',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'FiveThirtyEight: The Raiders can still make the playoffs',
|
||||||
|
'description': 'Neil Paine breaks down the simplest scenario that will put the Raiders into the playoffs at 8-8.',
|
||||||
|
'timestamp': 1513960621,
|
||||||
|
'upload_date': '20171222',
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
|
'expected_warnings': ['Unable to download f4m manifest'],
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
video_id = self._match_id(url)
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
video_id = self._search_regex(
|
||||||
|
r'data-video-id=["\'](?P<id>\d+)',
|
||||||
|
webpage, 'video id', group='id')
|
||||||
|
|
||||||
|
return self.url_result(
|
||||||
|
'http://espn.go.com/video/clip?id=%s' % video_id, ESPNIE.ie_key())
|
||||||
|
@ -127,7 +127,10 @@ from .bloomberg import BloombergIE
|
|||||||
from .bokecc import BokeCCIE
|
from .bokecc import BokeCCIE
|
||||||
from .bostonglobe import BostonGlobeIE
|
from .bostonglobe import BostonGlobeIE
|
||||||
from .bpb import BpbIE
|
from .bpb import BpbIE
|
||||||
from .br import BRIE
|
from .br import (
|
||||||
|
BRIE,
|
||||||
|
BRMediathekIE,
|
||||||
|
)
|
||||||
from .bravotv import BravoTVIE
|
from .bravotv import BravoTVIE
|
||||||
from .breakcom import BreakIE
|
from .breakcom import BreakIE
|
||||||
from .brightcove import (
|
from .brightcove import (
|
||||||
@ -135,10 +138,7 @@ from .brightcove import (
|
|||||||
BrightcoveNewIE,
|
BrightcoveNewIE,
|
||||||
)
|
)
|
||||||
from .buzzfeed import BuzzFeedIE
|
from .buzzfeed import BuzzFeedIE
|
||||||
from .byutv import (
|
from .byutv import BYUtvIE
|
||||||
BYUtvIE,
|
|
||||||
BYUtvEventIE,
|
|
||||||
)
|
|
||||||
from .c56 import C56IE
|
from .c56 import C56IE
|
||||||
from .camdemy import (
|
from .camdemy import (
|
||||||
CamdemyIE,
|
CamdemyIE,
|
||||||
@ -205,7 +205,6 @@ from .cnn import (
|
|||||||
CNNArticleIE,
|
CNNArticleIE,
|
||||||
)
|
)
|
||||||
from .coub import CoubIE
|
from .coub import CoubIE
|
||||||
from .collegerama import CollegeRamaIE
|
|
||||||
from .comedycentral import (
|
from .comedycentral import (
|
||||||
ComedyCentralFullEpisodesIE,
|
ComedyCentralFullEpisodesIE,
|
||||||
ComedyCentralIE,
|
ComedyCentralIE,
|
||||||
@ -243,11 +242,10 @@ from .dailymotion import (
|
|||||||
DailymotionIE,
|
DailymotionIE,
|
||||||
DailymotionPlaylistIE,
|
DailymotionPlaylistIE,
|
||||||
DailymotionUserIE,
|
DailymotionUserIE,
|
||||||
DailymotionCloudIE,
|
|
||||||
)
|
)
|
||||||
from .daisuki import (
|
from .daisuki import (
|
||||||
DaisukiIE,
|
DaisukiMottoIE,
|
||||||
DaisukiPlaylistIE,
|
DaisukiMottoPlaylistIE,
|
||||||
)
|
)
|
||||||
from .daum import (
|
from .daum import (
|
||||||
DaumIE,
|
DaumIE,
|
||||||
@ -309,9 +307,10 @@ from .ehow import EHowIE
|
|||||||
from .eighttracks import EightTracksIE
|
from .eighttracks import EightTracksIE
|
||||||
from .einthusan import EinthusanIE
|
from .einthusan import EinthusanIE
|
||||||
from .eitb import EitbIE
|
from .eitb import EitbIE
|
||||||
from .ellentv import (
|
from .ellentube import (
|
||||||
EllenTVIE,
|
EllenTubeIE,
|
||||||
EllenTVClipsIE,
|
EllenTubeVideoIE,
|
||||||
|
EllenTubePlaylistIE,
|
||||||
)
|
)
|
||||||
from .elpais import ElPaisIE
|
from .elpais import ElPaisIE
|
||||||
from .embedly import EmbedlyIE
|
from .embedly import EmbedlyIE
|
||||||
@ -322,6 +321,7 @@ from .escapist import EscapistIE
|
|||||||
from .espn import (
|
from .espn import (
|
||||||
ESPNIE,
|
ESPNIE,
|
||||||
ESPNArticleIE,
|
ESPNArticleIE,
|
||||||
|
FiveThirtyEightIE,
|
||||||
)
|
)
|
||||||
from .esri import EsriVideoIE
|
from .esri import EsriVideoIE
|
||||||
from .etonline import ETOnlineIE
|
from .etonline import ETOnlineIE
|
||||||
@ -344,11 +344,10 @@ from .filmon import (
|
|||||||
FilmOnIE,
|
FilmOnIE,
|
||||||
FilmOnChannelIE,
|
FilmOnChannelIE,
|
||||||
)
|
)
|
||||||
from .firstpost import FirstpostIE
|
from .filmweb import FilmwebIE
|
||||||
from .firsttv import FirstTVIE
|
from .firsttv import FirstTVIE
|
||||||
from .fivemin import FiveMinIE
|
from .fivemin import FiveMinIE
|
||||||
from .fivetv import FiveTVIE
|
from .fivetv import FiveTVIE
|
||||||
from .fktv import FKTVIE
|
|
||||||
from .flickr import FlickrIE
|
from .flickr import FlickrIE
|
||||||
from .flipagram import FlipagramIE
|
from .flipagram import FlipagramIE
|
||||||
from .folketinget import FolketingetIE
|
from .folketinget import FolketingetIE
|
||||||
@ -375,7 +374,7 @@ from .francetv import (
|
|||||||
FranceTVIE,
|
FranceTVIE,
|
||||||
FranceTVEmbedIE,
|
FranceTVEmbedIE,
|
||||||
FranceTVInfoIE,
|
FranceTVInfoIE,
|
||||||
GenerationQuoiIE,
|
GenerationWhatIE,
|
||||||
CultureboxIE,
|
CultureboxIE,
|
||||||
)
|
)
|
||||||
from .freesound import FreesoundIE
|
from .freesound import FreesoundIE
|
||||||
@ -391,7 +390,6 @@ from .gameone import (
|
|||||||
GameOneIE,
|
GameOneIE,
|
||||||
GameOnePlaylistIE,
|
GameOnePlaylistIE,
|
||||||
)
|
)
|
||||||
from .gamersyde import GamersydeIE
|
|
||||||
from .gamespot import GameSpotIE
|
from .gamespot import GameSpotIE
|
||||||
from .gamestar import GameStarIE
|
from .gamestar import GameStarIE
|
||||||
from .gaskrank import GaskrankIE
|
from .gaskrank import GaskrankIE
|
||||||
@ -432,7 +430,10 @@ from .hitbox import HitboxIE, HitboxLiveIE
|
|||||||
from .hitrecord import HitRecordIE
|
from .hitrecord import HitRecordIE
|
||||||
from .hornbunny import HornBunnyIE
|
from .hornbunny import HornBunnyIE
|
||||||
from .hotnewhiphop import HotNewHipHopIE
|
from .hotnewhiphop import HotNewHipHopIE
|
||||||
from .hotstar import HotStarIE
|
from .hotstar import (
|
||||||
|
HotStarIE,
|
||||||
|
HotStarPlaylistIE,
|
||||||
|
)
|
||||||
from .howcast import HowcastIE
|
from .howcast import HowcastIE
|
||||||
from .howstuffworks import HowStuffWorksIE
|
from .howstuffworks import HowStuffWorksIE
|
||||||
from .hrti import (
|
from .hrti import (
|
||||||
@ -464,6 +465,7 @@ from .indavideo import (
|
|||||||
)
|
)
|
||||||
from .infoq import InfoQIE
|
from .infoq import InfoQIE
|
||||||
from .instagram import InstagramIE, InstagramUserIE
|
from .instagram import InstagramIE, InstagramUserIE
|
||||||
|
from .internazionale import InternazionaleIE
|
||||||
from .internetvideoarchive import InternetVideoArchiveIE
|
from .internetvideoarchive import InternetVideoArchiveIE
|
||||||
from .iprima import IPrimaIE
|
from .iprima import IPrimaIE
|
||||||
from .iqiyi import IqiyiIE
|
from .iqiyi import IqiyiIE
|
||||||
@ -569,9 +571,11 @@ from .mangomolo import (
|
|||||||
MangomoloLiveIE,
|
MangomoloLiveIE,
|
||||||
)
|
)
|
||||||
from .manyvids import ManyVidsIE
|
from .manyvids import ManyVidsIE
|
||||||
|
from .massengeschmacktv import MassengeschmackTVIE
|
||||||
from .matchtv import MatchTVIE
|
from .matchtv import MatchTVIE
|
||||||
from .mdr import MDRIE
|
from .mdr import MDRIE
|
||||||
from .mediaset import MediasetIE
|
from .mediaset import MediasetIE
|
||||||
|
from .mediasite import MediasiteIE
|
||||||
from .medici import MediciIE
|
from .medici import MediciIE
|
||||||
from .megaphone import MegaphoneIE
|
from .megaphone import MegaphoneIE
|
||||||
from .meipai import MeipaiIE
|
from .meipai import MeipaiIE
|
||||||
@ -605,7 +609,10 @@ from .mofosex import MofosexIE
|
|||||||
from .mojvideo import MojvideoIE
|
from .mojvideo import MojvideoIE
|
||||||
from .moniker import MonikerIE
|
from .moniker import MonikerIE
|
||||||
from .morningstar import MorningstarIE
|
from .morningstar import MorningstarIE
|
||||||
from .motherless import MotherlessIE
|
from .motherless import (
|
||||||
|
MotherlessIE,
|
||||||
|
MotherlessGroupIE
|
||||||
|
)
|
||||||
from .motorsport import MotorsportIE
|
from .motorsport import MotorsportIE
|
||||||
from .movieclips import MovieClipsIE
|
from .movieclips import MovieClipsIE
|
||||||
from .moviezine import MoviezineIE
|
from .moviezine import MoviezineIE
|
||||||
@ -685,6 +692,7 @@ from .nhl import (
|
|||||||
)
|
)
|
||||||
from .nick import (
|
from .nick import (
|
||||||
NickIE,
|
NickIE,
|
||||||
|
NickBrIE,
|
||||||
NickDeIE,
|
NickDeIE,
|
||||||
NickNightIE,
|
NickNightIE,
|
||||||
NickRuIE,
|
NickRuIE,
|
||||||
@ -717,10 +725,6 @@ from .nowness import (
|
|||||||
NownessPlaylistIE,
|
NownessPlaylistIE,
|
||||||
NownessSeriesIE,
|
NownessSeriesIE,
|
||||||
)
|
)
|
||||||
from .nowtv import (
|
|
||||||
NowTVIE,
|
|
||||||
NowTVListIE,
|
|
||||||
)
|
|
||||||
from .noz import NozIE
|
from .noz import NozIE
|
||||||
from .npo import (
|
from .npo import (
|
||||||
AndereTijdenIE,
|
AndereTijdenIE,
|
||||||
@ -786,6 +790,7 @@ from .patreon import PatreonIE
|
|||||||
from .pbs import PBSIE
|
from .pbs import PBSIE
|
||||||
from .pearvideo import PearVideoIE
|
from .pearvideo import PearVideoIE
|
||||||
from .people import PeopleIE
|
from .people import PeopleIE
|
||||||
|
from .performgroup import PerformGroupIE
|
||||||
from .periscope import (
|
from .periscope import (
|
||||||
PeriscopeIE,
|
PeriscopeIE,
|
||||||
PeriscopeUserIE,
|
PeriscopeUserIE,
|
||||||
@ -852,6 +857,7 @@ from .radiofrance import RadioFranceIE
|
|||||||
from .rai import (
|
from .rai import (
|
||||||
RaiPlayIE,
|
RaiPlayIE,
|
||||||
RaiPlayLiveIE,
|
RaiPlayLiveIE,
|
||||||
|
RaiPlayPlaylistIE,
|
||||||
RaiIE,
|
RaiIE,
|
||||||
)
|
)
|
||||||
from .rbmaradio import RBMARadioIE
|
from .rbmaradio import RBMARadioIE
|
||||||
@ -909,7 +915,6 @@ from .rutube import (
|
|||||||
from .rutv import RUTVIE
|
from .rutv import RUTVIE
|
||||||
from .ruutu import RuutuIE
|
from .ruutu import RuutuIE
|
||||||
from .ruv import RuvIE
|
from .ruv import RuvIE
|
||||||
from .sandia import SandiaIE
|
|
||||||
from .safari import (
|
from .safari import (
|
||||||
SafariIE,
|
SafariIE,
|
||||||
SafariApiIE,
|
SafariApiIE,
|
||||||
@ -926,8 +931,12 @@ from .senateisvp import SenateISVPIE
|
|||||||
from .sendtonews import SendtoNewsIE
|
from .sendtonews import SendtoNewsIE
|
||||||
from .servingsys import ServingSysIE
|
from .servingsys import ServingSysIE
|
||||||
from .servus import ServusIE
|
from .servus import ServusIE
|
||||||
|
from .sevenplus import SevenPlusIE
|
||||||
from .sexu import SexuIE
|
from .sexu import SexuIE
|
||||||
from .shahid import ShahidIE
|
from .shahid import (
|
||||||
|
ShahidIE,
|
||||||
|
ShahidShowIE,
|
||||||
|
)
|
||||||
from .shared import (
|
from .shared import (
|
||||||
SharedIE,
|
SharedIE,
|
||||||
VivoIE,
|
VivoIE,
|
||||||
@ -995,6 +1004,7 @@ from .streamango import StreamangoIE
|
|||||||
from .streamcloud import StreamcloudIE
|
from .streamcloud import StreamcloudIE
|
||||||
from .streamcz import StreamCZIE
|
from .streamcz import StreamCZIE
|
||||||
from .streetvoice import StreetVoiceIE
|
from .streetvoice import StreetVoiceIE
|
||||||
|
from .stretchinternet import StretchInternetIE
|
||||||
from .sunporno import SunPornoIE
|
from .sunporno import SunPornoIE
|
||||||
from .svt import (
|
from .svt import (
|
||||||
SVTIE,
|
SVTIE,
|
||||||
@ -1058,6 +1068,7 @@ from .tnaflix import (
|
|||||||
from .toggle import ToggleIE
|
from .toggle import ToggleIE
|
||||||
from .tonline import TOnlineIE
|
from .tonline import TOnlineIE
|
||||||
from .toongoggles import ToonGogglesIE
|
from .toongoggles import ToonGogglesIE
|
||||||
|
from .totalwebcasting import TotalWebCastingIE
|
||||||
from .toutv import TouTvIE
|
from .toutv import TouTvIE
|
||||||
from .toypics import ToypicsUserIE, ToypicsIE
|
from .toypics import ToypicsUserIE, ToypicsIE
|
||||||
from .traileraddict import TrailerAddictIE
|
from .traileraddict import TrailerAddictIE
|
||||||
@ -1097,6 +1108,10 @@ from .tvigle import TvigleIE
|
|||||||
from .tvland import TVLandIE
|
from .tvland import TVLandIE
|
||||||
from .tvn24 import TVN24IE
|
from .tvn24 import TVN24IE
|
||||||
from .tvnoe import TVNoeIE
|
from .tvnoe import TVNoeIE
|
||||||
|
from .tvnow import (
|
||||||
|
TVNowIE,
|
||||||
|
TVNowListIE,
|
||||||
|
)
|
||||||
from .tvp import (
|
from .tvp import (
|
||||||
TVPEmbedIE,
|
TVPEmbedIE,
|
||||||
TVPIE,
|
TVPIE,
|
||||||
@ -1110,10 +1125,7 @@ from .tvplayer import TVPlayerIE
|
|||||||
from .tweakers import TweakersIE
|
from .tweakers import TweakersIE
|
||||||
from .twentyfourvideo import TwentyFourVideoIE
|
from .twentyfourvideo import TwentyFourVideoIE
|
||||||
from .twentymin import TwentyMinutenIE
|
from .twentymin import TwentyMinutenIE
|
||||||
from .twentytwotracks import (
|
from .twentythreevideo import TwentyThreeVideoIE
|
||||||
TwentyTwoTracksIE,
|
|
||||||
TwentyTwoTracksGenreIE
|
|
||||||
)
|
|
||||||
from .twitch import (
|
from .twitch import (
|
||||||
TwitchVideoIE,
|
TwitchVideoIE,
|
||||||
TwitchChapterIE,
|
TwitchChapterIE,
|
||||||
@ -1136,8 +1148,10 @@ from .udemy import (
|
|||||||
UdemyCourseIE
|
UdemyCourseIE
|
||||||
)
|
)
|
||||||
from .udn import UDNEmbedIE
|
from .udn import UDNEmbedIE
|
||||||
|
from .ufctv import UFCTVIE
|
||||||
from .uktvplay import UKTVPlayIE
|
from .uktvplay import UKTVPlayIE
|
||||||
from .digiteka import DigitekaIE
|
from .digiteka import DigitekaIE
|
||||||
|
from .umg import UMGDeIE
|
||||||
from .unistra import UnistraIE
|
from .unistra import UnistraIE
|
||||||
from .unity import UnityIE
|
from .unity import UnityIE
|
||||||
from .uol import UOLIE
|
from .uol import UOLIE
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
from ..compat import compat_etree_fromstring
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
xpath_element,
|
xpath_element,
|
||||||
xpath_text,
|
xpath_text,
|
||||||
@ -43,10 +46,15 @@ class FazIE(InfoExtractor):
|
|||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
description = self._og_search_description(webpage)
|
description = self._og_search_description(webpage)
|
||||||
config_xml_url = self._search_regex(
|
media = self._html_search_regex(
|
||||||
r'videoXMLURL\s*=\s*"([^"]+)', webpage, 'config xml url')
|
r"data-videojs-media='([^']+)",
|
||||||
config = self._download_xml(
|
webpage, 'media')
|
||||||
config_xml_url, video_id, 'Downloading config xml')
|
if media == 'extern':
|
||||||
|
perform_url = self._search_regex(
|
||||||
|
r"<iframe[^>]+?src='((?:http:)?//player\.performgroup\.com/eplayer/eplayer\.html#/?[0-9a-f]{26}\.[0-9a-z]{26})",
|
||||||
|
webpage, 'perform url')
|
||||||
|
return self.url_result(perform_url)
|
||||||
|
config = compat_etree_fromstring(media)
|
||||||
|
|
||||||
encodings = xpath_element(config, 'ENCODINGS', 'encodings', True)
|
encodings = xpath_element(config, 'ENCODINGS', 'encodings', True)
|
||||||
formats = []
|
formats = []
|
||||||
@ -55,12 +63,24 @@ class FazIE(InfoExtractor):
|
|||||||
if encoding is not None:
|
if encoding is not None:
|
||||||
encoding_url = xpath_text(encoding, 'FILENAME')
|
encoding_url = xpath_text(encoding, 'FILENAME')
|
||||||
if encoding_url:
|
if encoding_url:
|
||||||
formats.append({
|
tbr = xpath_text(encoding, 'AVERAGEBITRATE', 1000)
|
||||||
|
if tbr:
|
||||||
|
tbr = int_or_none(tbr.replace(',', '.'))
|
||||||
|
f = {
|
||||||
'url': encoding_url,
|
'url': encoding_url,
|
||||||
'format_id': code.lower(),
|
'format_id': code.lower(),
|
||||||
'quality': pref,
|
'quality': pref,
|
||||||
'tbr': int_or_none(xpath_text(encoding, 'AVERAGEBITRATE')),
|
'tbr': tbr,
|
||||||
|
'vcodec': xpath_text(encoding, 'CODEC'),
|
||||||
|
}
|
||||||
|
mobj = re.search(r'(\d+)x(\d+)_(\d+)\.mp4', encoding_url)
|
||||||
|
if mobj:
|
||||||
|
f.update({
|
||||||
|
'width': int(mobj.group(1)),
|
||||||
|
'height': int(mobj.group(2)),
|
||||||
|
'tbr': tbr or int(mobj.group(3)),
|
||||||
})
|
})
|
||||||
|
formats.append(f)
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -2,7 +2,10 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import compat_urlparse
|
from ..utils import (
|
||||||
|
int_or_none,
|
||||||
|
float_or_none,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class FczenitIE(InfoExtractor):
|
class FczenitIE(InfoExtractor):
|
||||||
@ -14,6 +17,8 @@ class FczenitIE(InfoExtractor):
|
|||||||
'id': '41044',
|
'id': '41044',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Так пишется история: казанский разгром ЦСКА на «Зенит-ТВ»',
|
'title': 'Так пишется история: казанский разгром ЦСКА на «Зенит-ТВ»',
|
||||||
|
'timestamp': 1462283735,
|
||||||
|
'upload_date': '20160503',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -21,28 +26,31 @@ class FczenitIE(InfoExtractor):
|
|||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
video_title = self._html_search_regex(
|
msi_id = self._search_regex(
|
||||||
r'<[^>]+class=\"photoalbum__title\">([^<]+)', webpage, 'title')
|
r"(?s)config\s*=\s*{.+?video_id\s*:\s*'([^']+)'", webpage, 'msi id')
|
||||||
|
|
||||||
video_items = self._parse_json(self._search_regex(
|
msi_data = self._download_json(
|
||||||
r'arrPath\s*=\s*JSON\.parse\(\'(.+)\'\)', webpage, 'video items'),
|
'http://player.fc-zenit.ru/msi/video', msi_id, query={
|
||||||
video_id)
|
'video': msi_id,
|
||||||
|
})['data']
|
||||||
def merge_dicts(*dicts):
|
title = msi_data['name']
|
||||||
ret = {}
|
|
||||||
for a_dict in dicts:
|
|
||||||
ret.update(a_dict)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
formats = [{
|
formats = [{
|
||||||
'url': compat_urlparse.urljoin(url, video_url),
|
'format_id': q.get('label'),
|
||||||
'tbr': int(tbr),
|
'url': q['url'],
|
||||||
} for tbr, video_url in merge_dicts(*video_items).items()]
|
'height': int_or_none(q.get('label')),
|
||||||
|
} for q in msi_data['qualities'] if q.get('url')]
|
||||||
|
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
tags = [tag['label'] for tag in msi_data.get('tags', []) if tag.get('label')]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'title': video_title,
|
'title': title,
|
||||||
|
'thumbnail': msi_data.get('preview'),
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
|
'duration': float_or_none(msi_data.get('duration')),
|
||||||
|
'timestamp': int_or_none(msi_data.get('date')),
|
||||||
|
'tags': tags,
|
||||||
}
|
}
|
||||||
|
42
youtube_dl/extractor/filmweb.py
Normal file
42
youtube_dl/extractor/filmweb.py
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
|
||||||
|
|
||||||
|
class FilmwebIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?filmweb\.no/(?P<type>trailere|filmnytt)/article(?P<id>\d+)\.ece'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.filmweb.no/trailere/article1264921.ece',
|
||||||
|
'md5': 'e353f47df98e557d67edaceda9dece89',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '13033574',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Det som en gang var',
|
||||||
|
'upload_date': '20160316',
|
||||||
|
'timestamp': 1458140101,
|
||||||
|
'uploader_id': '12639966',
|
||||||
|
'uploader': 'Live Roaldset',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
article_type, article_id = re.match(self._VALID_URL, url).groups()
|
||||||
|
if article_type == 'filmnytt':
|
||||||
|
webpage = self._download_webpage(url, article_id)
|
||||||
|
article_id = self._search_regex(r'data-videoid="(\d+)"', webpage, 'article id')
|
||||||
|
embed_code = self._download_json(
|
||||||
|
'https://www.filmweb.no/template_v2/ajax/json_trailerEmbed.jsp',
|
||||||
|
article_id, query={
|
||||||
|
'articleId': article_id,
|
||||||
|
})['embedCode']
|
||||||
|
iframe_url = self._proto_relative_url(self._search_regex(
|
||||||
|
r'<iframe[^>]+src="([^"]+)', embed_code, 'iframe url'))
|
||||||
|
|
||||||
|
return {
|
||||||
|
'_type': 'url_transparent',
|
||||||
|
'id': article_id,
|
||||||
|
'url': iframe_url,
|
||||||
|
'ie_key': 'TwentyThreeVideo',
|
||||||
|
}
|
@ -1,50 +0,0 @@
|
|||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
|
||||||
|
|
||||||
|
|
||||||
class FirstpostIE(InfoExtractor):
|
|
||||||
_VALID_URL = r'https?://(?:www\.)?firstpost\.com/[^/]+/.*-(?P<id>[0-9]+)\.html'
|
|
||||||
|
|
||||||
_TEST = {
|
|
||||||
'url': 'http://www.firstpost.com/india/india-to-launch-indigenous-aircraft-carrier-monday-1025403.html',
|
|
||||||
'md5': 'ee9114957692f01fb1263ed87039112a',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '1025403',
|
|
||||||
'ext': 'mp4',
|
|
||||||
'title': 'India to launch indigenous aircraft carrier INS Vikrant today',
|
|
||||||
'description': 'md5:feef3041cb09724e0bdc02843348f5f4',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
|
||||||
video_id = self._match_id(url)
|
|
||||||
page = self._download_webpage(url, video_id)
|
|
||||||
|
|
||||||
title = self._html_search_meta('twitter:title', page, 'title', fatal=True)
|
|
||||||
description = self._html_search_meta('twitter:description', page, 'title')
|
|
||||||
|
|
||||||
data = self._download_xml(
|
|
||||||
'http://www.firstpost.com/getvideoxml-%s.xml' % video_id, video_id,
|
|
||||||
'Downloading video XML')
|
|
||||||
|
|
||||||
item = data.find('./playlist/item')
|
|
||||||
thumbnail = item.find('./image').text
|
|
||||||
|
|
||||||
formats = [
|
|
||||||
{
|
|
||||||
'url': details.find('./file').text,
|
|
||||||
'format_id': details.find('./label').text.strip(),
|
|
||||||
'width': int(details.find('./width').text.strip()),
|
|
||||||
'height': int(details.find('./height').text.strip()),
|
|
||||||
} for details in item.findall('./source/file_details') if details.find('./file').text
|
|
||||||
]
|
|
||||||
self._sort_formats(formats)
|
|
||||||
|
|
||||||
return {
|
|
||||||
'id': video_id,
|
|
||||||
'title': title,
|
|
||||||
'description': description,
|
|
||||||
'thumbnail': thumbnail,
|
|
||||||
'formats': formats,
|
|
||||||
}
|
|
@ -1,51 +0,0 @@
|
|||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
|
||||||
from ..utils import (
|
|
||||||
clean_html,
|
|
||||||
determine_ext,
|
|
||||||
js_to_json,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class FKTVIE(InfoExtractor):
|
|
||||||
IE_NAME = 'fernsehkritik.tv'
|
|
||||||
_VALID_URL = r'https?://(?:www\.)?fernsehkritik\.tv/folge-(?P<id>[0-9]+)(?:/.*)?'
|
|
||||||
|
|
||||||
_TEST = {
|
|
||||||
'url': 'http://fernsehkritik.tv/folge-1',
|
|
||||||
'md5': '21f0b0c99bce7d5b524eb1b17b1c6d79',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '1',
|
|
||||||
'ext': 'mp4',
|
|
||||||
'title': 'Folge 1 vom 10. April 2007',
|
|
||||||
'thumbnail': r're:^https?://.*\.jpg$',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
|
||||||
episode = self._match_id(url)
|
|
||||||
|
|
||||||
webpage = self._download_webpage(
|
|
||||||
'http://fernsehkritik.tv/folge-%s/play' % episode, episode)
|
|
||||||
title = clean_html(self._html_search_regex(
|
|
||||||
'<h3>([^<]+)</h3>', webpage, 'title'))
|
|
||||||
thumbnail = self._search_regex(r'POSTER\s*=\s*"([^"]+)', webpage, 'thumbnail', fatal=False)
|
|
||||||
sources = self._parse_json(self._search_regex(r'(?s)MEDIA\s*=\s*(\[.+?\]);', webpage, 'media'), episode, js_to_json)
|
|
||||||
|
|
||||||
formats = []
|
|
||||||
for source in sources:
|
|
||||||
furl = source.get('src')
|
|
||||||
if furl:
|
|
||||||
formats.append({
|
|
||||||
'url': furl,
|
|
||||||
'format_id': determine_ext(furl),
|
|
||||||
})
|
|
||||||
self._sort_formats(formats)
|
|
||||||
|
|
||||||
return {
|
|
||||||
'id': episode,
|
|
||||||
'title': title,
|
|
||||||
'formats': formats,
|
|
||||||
'thumbnail': thumbnail,
|
|
||||||
}
|
|
@ -11,6 +11,7 @@ from ..utils import (
|
|||||||
parse_duration,
|
parse_duration,
|
||||||
try_get,
|
try_get,
|
||||||
unified_timestamp,
|
unified_timestamp,
|
||||||
|
update_url_query,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -62,7 +63,8 @@ class FOXIE(AdobePassIE):
|
|||||||
duration = int_or_none(video.get('durationInSeconds')) or int_or_none(
|
duration = int_or_none(video.get('durationInSeconds')) or int_or_none(
|
||||||
video.get('duration')) or parse_duration(video.get('duration'))
|
video.get('duration')) or parse_duration(video.get('duration'))
|
||||||
timestamp = unified_timestamp(video.get('datePublished'))
|
timestamp = unified_timestamp(video.get('datePublished'))
|
||||||
age_limit = parse_age_limit(video.get('contentRating'))
|
rating = video.get('contentRating')
|
||||||
|
age_limit = parse_age_limit(rating)
|
||||||
|
|
||||||
data = try_get(
|
data = try_get(
|
||||||
video, lambda x: x['trackingData']['properties'], dict) or {}
|
video, lambda x: x['trackingData']['properties'], dict) or {}
|
||||||
@ -77,8 +79,24 @@ class FOXIE(AdobePassIE):
|
|||||||
release_year = int_or_none(video.get('releaseYear'))
|
release_year = int_or_none(video.get('releaseYear'))
|
||||||
|
|
||||||
if data.get('authRequired'):
|
if data.get('authRequired'):
|
||||||
# TODO: AP
|
resource = self._get_mvpd_resource(
|
||||||
pass
|
'fbc-fox', title, video.get('guid'), rating)
|
||||||
|
release_url = update_url_query(
|
||||||
|
release_url, {
|
||||||
|
'auth': self._extract_mvpd_auth(
|
||||||
|
url, video_id, 'fbc-fox', resource)
|
||||||
|
})
|
||||||
|
|
||||||
|
subtitles = {}
|
||||||
|
for doc_rel in video.get('documentReleases', []):
|
||||||
|
rel_url = doc_rel.get('url')
|
||||||
|
if not url or doc_rel.get('format') != 'SCC':
|
||||||
|
continue
|
||||||
|
subtitles['en'] = [{
|
||||||
|
'url': rel_url,
|
||||||
|
'ext': 'scc',
|
||||||
|
}]
|
||||||
|
break
|
||||||
|
|
||||||
info = {
|
info = {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
@ -93,6 +111,7 @@ class FOXIE(AdobePassIE):
|
|||||||
'episode': episode,
|
'episode': episode,
|
||||||
'episode_number': episode_number,
|
'episode_number': episode_number,
|
||||||
'release_year': release_year,
|
'release_year': release_year,
|
||||||
|
'subtitles': subtitles,
|
||||||
}
|
}
|
||||||
|
|
||||||
urlh = self._request_webpage(HEADRequest(release_url), video_id)
|
urlh = self._request_webpage(HEADRequest(release_url), video_id)
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from .anvato import AnvatoIE
|
from .anvato import AnvatoIE
|
||||||
from ..utils import js_to_json
|
|
||||||
|
|
||||||
|
|
||||||
class FOX9IE(AnvatoIE):
|
class FOX9IE(AnvatoIE):
|
||||||
@ -34,9 +33,9 @@ class FOX9IE(AnvatoIE):
|
|||||||
|
|
||||||
video_id = self._parse_json(
|
video_id = self._parse_json(
|
||||||
self._search_regex(
|
self._search_regex(
|
||||||
r'AnvatoPlaylist\s*\(\s*(\[.+?\])\s*\)\s*;',
|
r"this\.videosJson\s*=\s*'(\[.+?\])';",
|
||||||
webpage, 'anvato playlist'),
|
webpage, 'anvato playlist'),
|
||||||
video_id, transform_source=js_to_json)[0]['video']
|
video_id)[0]['video']
|
||||||
|
|
||||||
return self._get_anvato_videos(
|
return self._get_anvato_videos(
|
||||||
'anvato_epfox_app_web_prod_b3373168e12f423f41504f207000188daf88251b',
|
'anvato_epfox_app_web_prod_b3373168e12f423f41504f207000188daf88251b',
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import json
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import compat_urlparse
|
from ..compat import compat_urlparse
|
||||||
@ -14,10 +13,7 @@ from ..utils import (
|
|||||||
parse_duration,
|
parse_duration,
|
||||||
determine_ext,
|
determine_ext,
|
||||||
)
|
)
|
||||||
from .dailymotion import (
|
from .dailymotion import DailymotionIE
|
||||||
DailymotionIE,
|
|
||||||
DailymotionCloudIE,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class FranceTVBaseInfoExtractor(InfoExtractor):
|
class FranceTVBaseInfoExtractor(InfoExtractor):
|
||||||
@ -291,10 +287,6 @@ class FranceTVInfoIE(FranceTVBaseInfoExtractor):
|
|||||||
page_title = mobj.group('title')
|
page_title = mobj.group('title')
|
||||||
webpage = self._download_webpage(url, page_title)
|
webpage = self._download_webpage(url, page_title)
|
||||||
|
|
||||||
dmcloud_url = DailymotionCloudIE._extract_dmcloud_url(webpage)
|
|
||||||
if dmcloud_url:
|
|
||||||
return self.url_result(dmcloud_url, DailymotionCloudIE.ie_key())
|
|
||||||
|
|
||||||
dailymotion_urls = DailymotionIE._extract_urls(webpage)
|
dailymotion_urls = DailymotionIE._extract_urls(webpage)
|
||||||
if dailymotion_urls:
|
if dailymotion_urls:
|
||||||
return self.playlist_result([
|
return self.playlist_result([
|
||||||
@ -308,31 +300,32 @@ class FranceTVInfoIE(FranceTVBaseInfoExtractor):
|
|||||||
return self._extract_video(video_id, catalogue)
|
return self._extract_video(video_id, catalogue)
|
||||||
|
|
||||||
|
|
||||||
class GenerationQuoiIE(InfoExtractor):
|
class GenerationWhatIE(InfoExtractor):
|
||||||
IE_NAME = 'france2.fr:generation-quoi'
|
IE_NAME = 'france2.fr:generation-what'
|
||||||
_VALID_URL = r'https?://generation-quoi\.france2\.fr/portrait/(?P<id>[^/?#]+)'
|
_VALID_URL = r'https?://generation-what\.francetv\.fr/[^/]+/video/(?P<id>[^/?#]+)'
|
||||||
|
|
||||||
_TEST = {
|
_TESTS = [{
|
||||||
'url': 'http://generation-quoi.france2.fr/portrait/garde-a-vous',
|
'url': 'http://generation-what.francetv.fr/portrait/video/present-arms',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'k7FJX8VBcvvLmX4wA5Q',
|
'id': 'wtvKYUG45iw',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Génération Quoi - Garde à Vous',
|
'title': 'Generation What - Garde à vous - FRA',
|
||||||
'uploader': 'Génération Quoi',
|
'uploader': 'Generation What',
|
||||||
|
'uploader_id': 'UCHH9p1eetWCgt4kXBYCb3_w',
|
||||||
|
'upload_date': '20160411',
|
||||||
},
|
},
|
||||||
'params': {
|
}, {
|
||||||
# It uses Dailymotion
|
'url': 'http://generation-what.francetv.fr/europe/video/present-arms',
|
||||||
'skip_download': True,
|
'only_matching': True,
|
||||||
},
|
}]
|
||||||
}
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
display_id = self._match_id(url)
|
display_id = self._match_id(url)
|
||||||
info_url = compat_urlparse.urljoin(url, '/medias/video/%s.json' % display_id)
|
webpage = self._download_webpage(url, display_id)
|
||||||
info_json = self._download_webpage(info_url, display_id)
|
youtube_id = self._search_regex(
|
||||||
info = json.loads(info_json)
|
r"window\.videoURL\s*=\s*'([0-9A-Za-z_-]{11})';",
|
||||||
return self.url_result('http://www.dailymotion.com/video/%s' % info['id'],
|
webpage, 'youtube id')
|
||||||
ie='Dailymotion')
|
return self.url_result(youtube_id, 'Youtube', youtube_id)
|
||||||
|
|
||||||
|
|
||||||
class CultureboxIE(FranceTVBaseInfoExtractor):
|
class CultureboxIE(FranceTVBaseInfoExtractor):
|
||||||
@ -363,6 +356,7 @@ class CultureboxIE(FranceTVBaseInfoExtractor):
|
|||||||
raise ExtractorError('Video %s is not available' % name, expected=True)
|
raise ExtractorError('Video %s is not available' % name, expected=True)
|
||||||
|
|
||||||
video_id, catalogue = self._search_regex(
|
video_id, catalogue = self._search_regex(
|
||||||
r'"http://videos\.francetv\.fr/video/([^@]+@[^"]+)"', webpage, 'video id').split('@')
|
r'["\'>]https?://videos\.francetv\.fr/video/([^@]+@.+?)["\'<]',
|
||||||
|
webpage, 'video id').split('@')
|
||||||
|
|
||||||
return self._extract_video(video_id, catalogue)
|
return self._extract_video(video_id, catalogue)
|
||||||
|
@ -1,37 +1,34 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
|
||||||
import json
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
|
||||||
|
|
||||||
class FreespeechIE(InfoExtractor):
|
class FreespeechIE(InfoExtractor):
|
||||||
IE_NAME = 'freespeech.org'
|
IE_NAME = 'freespeech.org'
|
||||||
_VALID_URL = r'https?://(?:www\.)?freespeech\.org/video/(?P<title>.+)'
|
_VALID_URL = r'https?://(?:www\.)?freespeech\.org/stories/(?P<id>.+)'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'add_ie': ['Youtube'],
|
'add_ie': ['Youtube'],
|
||||||
'url': 'https://www.freespeech.org/video/obama-romney-campaign-colorado-ahead-debate-0',
|
'url': 'http://www.freespeech.org/stories/fcc-announces-net-neutrality-rollback-whats-stake/',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'poKsVCZ64uU',
|
'id': 'waRk6IPqyWM',
|
||||||
'ext': 'webm',
|
'ext': 'mp4',
|
||||||
'title': 'Obama, Romney Campaign in Colorado Ahead of Debate',
|
'title': 'What\'s At Stake - Net Neutrality Special',
|
||||||
'description': 'Obama, Romney Campaign in Colorado Ahead of Debate',
|
'description': 'Presented by MNN and FSTV',
|
||||||
'uploader': 'freespeechtv',
|
'upload_date': '20170728',
|
||||||
'uploader_id': 'freespeechtv',
|
'uploader_id': 'freespeechtv',
|
||||||
'upload_date': '20121002',
|
'uploader': 'freespeechtv',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
display_id = self._match_id(url)
|
||||||
title = mobj.group('title')
|
webpage = self._download_webpage(url, display_id)
|
||||||
webpage = self._download_webpage(url, title)
|
youtube_url = self._search_regex(
|
||||||
info_json = self._search_regex(r'jQuery\.extend\(Drupal\.settings, ({.*?})\);', webpage, 'info')
|
r'data-video-url="([^"]+)"',
|
||||||
info = json.loads(info_json)
|
webpage, 'youtube url')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'_type': 'url',
|
'_type': 'url',
|
||||||
'url': info['jw_player']['basic_video_node_player']['file'],
|
'url': youtube_url,
|
||||||
'ie_key': 'Youtube',
|
'ie_key': 'Youtube',
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,7 @@ class FunimationIE(InfoExtractor):
|
|||||||
try:
|
try:
|
||||||
data = self._download_json(
|
data = self._download_json(
|
||||||
'https://prod-api-funimationnow.dadcdigital.com/api/auth/login/',
|
'https://prod-api-funimationnow.dadcdigital.com/api/auth/login/',
|
||||||
None, 'Logging in as %s' % username, data=urlencode_postdata({
|
None, 'Logging in', data=urlencode_postdata({
|
||||||
'username': username,
|
'username': username,
|
||||||
'password': password,
|
'password': password,
|
||||||
}))
|
}))
|
||||||
|
@ -1,70 +0,0 @@
|
|||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
|
||||||
from ..utils import (
|
|
||||||
js_to_json,
|
|
||||||
parse_duration,
|
|
||||||
remove_start,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class GamersydeIE(InfoExtractor):
|
|
||||||
_VALID_URL = r'https?://(?:www\.)?gamersyde\.com/hqstream_(?P<display_id>[\da-z_]+)-(?P<id>\d+)_[a-z]{2}\.html'
|
|
||||||
_TEST = {
|
|
||||||
'url': 'http://www.gamersyde.com/hqstream_bloodborne_birth_of_a_hero-34371_en.html',
|
|
||||||
'md5': 'f38d400d32f19724570040d5ce3a505f',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '34371',
|
|
||||||
'ext': 'mp4',
|
|
||||||
'duration': 372,
|
|
||||||
'title': 'Bloodborne - Birth of a hero',
|
|
||||||
'thumbnail': r're:^https?://.*\.jpg$',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
|
||||||
mobj = re.match(self._VALID_URL, url)
|
|
||||||
video_id = mobj.group('id')
|
|
||||||
display_id = mobj.group('display_id')
|
|
||||||
|
|
||||||
webpage = self._download_webpage(url, display_id)
|
|
||||||
|
|
||||||
playlist = self._parse_json(
|
|
||||||
self._search_regex(
|
|
||||||
r'(?s)playlist: \[({.+?})\]\s*}\);', webpage, 'files'),
|
|
||||||
display_id, transform_source=js_to_json)
|
|
||||||
|
|
||||||
formats = []
|
|
||||||
for source in playlist['sources']:
|
|
||||||
video_url = source.get('file')
|
|
||||||
if not video_url:
|
|
||||||
continue
|
|
||||||
format_id = source.get('label')
|
|
||||||
f = {
|
|
||||||
'url': video_url,
|
|
||||||
'format_id': format_id,
|
|
||||||
}
|
|
||||||
m = re.search(r'^(?P<height>\d+)[pP](?P<fps>\d+)fps', format_id)
|
|
||||||
if m:
|
|
||||||
f.update({
|
|
||||||
'height': int(m.group('height')),
|
|
||||||
'fps': int(m.group('fps')),
|
|
||||||
})
|
|
||||||
formats.append(f)
|
|
||||||
self._sort_formats(formats)
|
|
||||||
|
|
||||||
title = remove_start(playlist['title'], '%s - ' % video_id)
|
|
||||||
thumbnail = playlist.get('image')
|
|
||||||
duration = parse_duration(self._search_regex(
|
|
||||||
r'Length:</label>([^<]+)<', webpage, 'duration', fatal=False))
|
|
||||||
|
|
||||||
return {
|
|
||||||
'id': video_id,
|
|
||||||
'display_id': display_id,
|
|
||||||
'title': title,
|
|
||||||
'thumbnail': thumbnail,
|
|
||||||
'duration': duration,
|
|
||||||
'formats': formats,
|
|
||||||
}
|
|
@ -14,7 +14,7 @@ from ..utils import (
|
|||||||
|
|
||||||
|
|
||||||
class GameSpotIE(OnceIE):
|
class GameSpotIE(OnceIE):
|
||||||
_VALID_URL = r'https?://(?:www\.)?gamespot\.com/.*-(?P<id>\d+)/?'
|
_VALID_URL = r'https?://(?:www\.)?gamespot\.com/(?:video|article)s/(?:[^/]+/\d+-|embed/)(?P<id>\d+)'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://www.gamespot.com/videos/arma-3-community-guide-sitrep-i/2300-6410818/',
|
'url': 'http://www.gamespot.com/videos/arma-3-community-guide-sitrep-i/2300-6410818/',
|
||||||
'md5': 'b2a30deaa8654fcccd43713a6b6a4825',
|
'md5': 'b2a30deaa8654fcccd43713a6b6a4825',
|
||||||
@ -35,6 +35,12 @@ class GameSpotIE(OnceIE):
|
|||||||
'params': {
|
'params': {
|
||||||
'skip_download': True, # m3u8 downloads
|
'skip_download': True, # m3u8 downloads
|
||||||
},
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.gamespot.com/videos/embed/6439218/',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.gamespot.com/articles/the-last-of-us-2-receives-new-ps4-trailer/1100-6454469/',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
@ -52,7 +58,7 @@ class GameSpotIE(OnceIE):
|
|||||||
manifest_url = f4m_url
|
manifest_url = f4m_url
|
||||||
formats.extend(self._extract_f4m_formats(
|
formats.extend(self._extract_f4m_formats(
|
||||||
f4m_url + '?hdcore=3.7.0', page_id, f4m_id='hds', fatal=False))
|
f4m_url + '?hdcore=3.7.0', page_id, f4m_id='hds', fatal=False))
|
||||||
m3u8_url = streams.get('m3u8_stream')
|
m3u8_url = dict_get(streams, ('m3u8_stream', 'adaptive_stream'))
|
||||||
if m3u8_url:
|
if m3u8_url:
|
||||||
manifest_url = m3u8_url
|
manifest_url = m3u8_url
|
||||||
m3u8_formats = self._extract_m3u8_formats(
|
m3u8_formats = self._extract_m3u8_formats(
|
||||||
@ -60,7 +66,7 @@ class GameSpotIE(OnceIE):
|
|||||||
m3u8_id='hls', fatal=False)
|
m3u8_id='hls', fatal=False)
|
||||||
formats.extend(m3u8_formats)
|
formats.extend(m3u8_formats)
|
||||||
progressive_url = dict_get(
|
progressive_url = dict_get(
|
||||||
streams, ('progressive_hd', 'progressive_high', 'progressive_low'))
|
streams, ('progressive_hd', 'progressive_high', 'progressive_low', 'other_lr'))
|
||||||
if progressive_url and manifest_url:
|
if progressive_url and manifest_url:
|
||||||
qualities_basename = self._search_regex(
|
qualities_basename = self._search_regex(
|
||||||
r'/([^/]+)\.csmil/',
|
r'/([^/]+)\.csmil/',
|
||||||
@ -105,7 +111,8 @@ class GameSpotIE(OnceIE):
|
|||||||
onceux_url = self._parse_json(unescapeHTML(onceux_json), page_id).get('metadataUri')
|
onceux_url = self._parse_json(unescapeHTML(onceux_json), page_id).get('metadataUri')
|
||||||
if onceux_url:
|
if onceux_url:
|
||||||
formats.extend(self._extract_once_formats(re.sub(
|
formats.extend(self._extract_once_formats(re.sub(
|
||||||
r'https?://[^/]+', 'http://once.unicornmedia.com', onceux_url)))
|
r'https?://[^/]+', 'http://once.unicornmedia.com', onceux_url),
|
||||||
|
http_formats_preference=-1))
|
||||||
|
|
||||||
if not formats:
|
if not formats:
|
||||||
for quality in ['sd', 'hd']:
|
for quality in ['sd', 'hd']:
|
||||||
|
@ -59,10 +59,7 @@ from .tnaflix import TNAFlixNetworkEmbedIE
|
|||||||
from .drtuber import DrTuberIE
|
from .drtuber import DrTuberIE
|
||||||
from .redtube import RedTubeIE
|
from .redtube import RedTubeIE
|
||||||
from .vimeo import VimeoIE
|
from .vimeo import VimeoIE
|
||||||
from .dailymotion import (
|
from .dailymotion import DailymotionIE
|
||||||
DailymotionIE,
|
|
||||||
DailymotionCloudIE,
|
|
||||||
)
|
|
||||||
from .dailymail import DailyMailIE
|
from .dailymail import DailyMailIE
|
||||||
from .onionstudios import OnionStudiosIE
|
from .onionstudios import OnionStudiosIE
|
||||||
from .viewlift import ViewLiftEmbedIE
|
from .viewlift import ViewLiftEmbedIE
|
||||||
@ -102,6 +99,8 @@ from .joj import JojIE
|
|||||||
from .megaphone import MegaphoneIE
|
from .megaphone import MegaphoneIE
|
||||||
from .vzaar import VzaarIE
|
from .vzaar import VzaarIE
|
||||||
from .channel9 import Channel9IE
|
from .channel9 import Channel9IE
|
||||||
|
from .vshare import VShareIE
|
||||||
|
from .mediasite import MediasiteIE
|
||||||
|
|
||||||
|
|
||||||
class GenericIE(InfoExtractor):
|
class GenericIE(InfoExtractor):
|
||||||
@ -1098,9 +1097,9 @@ class GenericIE(InfoExtractor):
|
|||||||
},
|
},
|
||||||
# jwplayer rtmp
|
# jwplayer rtmp
|
||||||
{
|
{
|
||||||
'url': 'http://www.suffolk.edu/sjc/',
|
'url': 'http://www.suffolk.edu/sjc/live.php',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'sjclive',
|
'id': 'live',
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
'title': 'Massachusetts Supreme Judicial Court Oral Arguments',
|
'title': 'Massachusetts Supreme Judicial Court Oral Arguments',
|
||||||
'uploader': 'www.suffolk.edu',
|
'uploader': 'www.suffolk.edu',
|
||||||
@ -1108,7 +1107,7 @@ class GenericIE(InfoExtractor):
|
|||||||
'params': {
|
'params': {
|
||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
},
|
},
|
||||||
'skip': 'does not contain a video anymore',
|
'skip': 'Only has video a few mornings per month, see http://www.suffolk.edu/sjc/',
|
||||||
},
|
},
|
||||||
# Complex jwplayer
|
# Complex jwplayer
|
||||||
{
|
{
|
||||||
@ -1135,6 +1134,19 @@ class GenericIE(InfoExtractor):
|
|||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
# JWPlatform iframe
|
||||||
|
'url': 'https://www.mediaite.com/tv/dem-senator-claims-gary-cohn-faked-a-bad-connection-during-trump-call-to-get-him-off-the-phone/',
|
||||||
|
'md5': 'ca00a040364b5b439230e7ebfd02c4e9',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'O0c5JcKT',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'upload_date': '20171122',
|
||||||
|
'timestamp': 1511366290,
|
||||||
|
'title': 'Dem Senator Claims Gary Cohn Faked a Bad Connection During Trump Call to Get Him Off the Phone',
|
||||||
|
},
|
||||||
|
'add_ie': [JWPlatformIE.ie_key()],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
# Video.js embed, multiple formats
|
# Video.js embed, multiple formats
|
||||||
'url': 'http://ortcam.com/solidworks-урок-6-настройка-чертежа_33f9b7351.html',
|
'url': 'http://ortcam.com/solidworks-урок-6-настройка-чертежа_33f9b7351.html',
|
||||||
@ -1458,23 +1470,6 @@ class GenericIE(InfoExtractor):
|
|||||||
'timestamp': 1432570283,
|
'timestamp': 1432570283,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
# Dailymotion Cloud video
|
|
||||||
{
|
|
||||||
'url': 'http://replay.publicsenat.fr/vod/le-debat/florent-kolandjian,dominique-cena,axel-decourtye,laurence-abeille,bruno-parmentier/175910',
|
|
||||||
'md5': 'dcaf23ad0c67a256f4278bce6e0bae38',
|
|
||||||
'info_dict': {
|
|
||||||
'id': 'x2uy8t3',
|
|
||||||
'ext': 'mp4',
|
|
||||||
'title': 'Sauvons les abeilles ! - Le débat',
|
|
||||||
'description': 'md5:d9082128b1c5277987825d684939ca26',
|
|
||||||
'thumbnail': r're:^https?://.*\.jpe?g$',
|
|
||||||
'timestamp': 1434970506,
|
|
||||||
'upload_date': '20150622',
|
|
||||||
'uploader': 'Public Sénat',
|
|
||||||
'uploader_id': 'xa9gza',
|
|
||||||
},
|
|
||||||
'skip': 'File not found.',
|
|
||||||
},
|
|
||||||
# OnionStudios embed
|
# OnionStudios embed
|
||||||
{
|
{
|
||||||
'url': 'http://www.clickhole.com/video/dont-understand-bitcoin-man-will-mumble-explanatio-2537',
|
'url': 'http://www.clickhole.com/video/dont-understand-bitcoin-man-will-mumble-explanatio-2537',
|
||||||
@ -1921,6 +1916,28 @@ class GenericIE(InfoExtractor):
|
|||||||
'title': 'Rescue Kit 14 Free Edition - Getting started',
|
'title': 'Rescue Kit 14 Free Edition - Getting started',
|
||||||
},
|
},
|
||||||
'playlist_count': 4,
|
'playlist_count': 4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
# vshare embed
|
||||||
|
'url': 'https://youtube-dl-demo.neocities.org/vshare.html',
|
||||||
|
'md5': '17b39f55b5497ae8b59f5fbce8e35886',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '0f64ce6',
|
||||||
|
'title': 'vl14062007715967',
|
||||||
|
'ext': 'mp4',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://www.heidelberg-laureate-forum.org/blog/video/lecture-friday-september-23-2016-sir-c-antony-r-hoare/',
|
||||||
|
'md5': 'aecd089f55b1cb5a59032cb049d3a356',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '90227f51a80c4d8f86c345a7fa62bd9a1d',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Lecture: Friday, September 23, 2016 - Sir Tony Hoare',
|
||||||
|
'description': 'md5:5a51db84a62def7b7054df2ade403c6c',
|
||||||
|
'timestamp': 1474354800,
|
||||||
|
'upload_date': '20160920',
|
||||||
|
}
|
||||||
}
|
}
|
||||||
# {
|
# {
|
||||||
# # TODO: find another test
|
# # TODO: find another test
|
||||||
@ -2171,7 +2188,7 @@ class GenericIE(InfoExtractor):
|
|||||||
return self.playlist_result(self._parse_xspf(doc, video_id), video_id)
|
return self.playlist_result(self._parse_xspf(doc, video_id), video_id)
|
||||||
elif re.match(r'(?i)^(?:{[^}]+})?MPD$', doc.tag):
|
elif re.match(r'(?i)^(?:{[^}]+})?MPD$', doc.tag):
|
||||||
info_dict['formats'] = self._parse_mpd_formats(
|
info_dict['formats'] = self._parse_mpd_formats(
|
||||||
doc, video_id,
|
doc,
|
||||||
mpd_base_url=compat_str(full_response.geturl()).rpartition('/')[0],
|
mpd_base_url=compat_str(full_response.geturl()).rpartition('/')[0],
|
||||||
mpd_url=url)
|
mpd_url=url)
|
||||||
self._sort_formats(info_dict['formats'])
|
self._sort_formats(info_dict['formats'])
|
||||||
@ -2680,11 +2697,6 @@ class GenericIE(InfoExtractor):
|
|||||||
if senate_isvp_url:
|
if senate_isvp_url:
|
||||||
return self.url_result(senate_isvp_url, 'SenateISVP')
|
return self.url_result(senate_isvp_url, 'SenateISVP')
|
||||||
|
|
||||||
# Look for Dailymotion Cloud videos
|
|
||||||
dmcloud_url = DailymotionCloudIE._extract_dmcloud_url(webpage)
|
|
||||||
if dmcloud_url:
|
|
||||||
return self.url_result(dmcloud_url, 'DailymotionCloud')
|
|
||||||
|
|
||||||
# Look for OnionStudios embeds
|
# Look for OnionStudios embeds
|
||||||
onionstudios_url = OnionStudiosIE._extract_url(webpage)
|
onionstudios_url = OnionStudiosIE._extract_url(webpage)
|
||||||
if onionstudios_url:
|
if onionstudios_url:
|
||||||
@ -2879,6 +2891,21 @@ class GenericIE(InfoExtractor):
|
|||||||
return self.playlist_from_matches(
|
return self.playlist_from_matches(
|
||||||
channel9_urls, video_id, video_title, ie=Channel9IE.ie_key())
|
channel9_urls, video_id, video_title, ie=Channel9IE.ie_key())
|
||||||
|
|
||||||
|
vshare_urls = VShareIE._extract_urls(webpage)
|
||||||
|
if vshare_urls:
|
||||||
|
return self.playlist_from_matches(
|
||||||
|
vshare_urls, video_id, video_title, ie=VShareIE.ie_key())
|
||||||
|
|
||||||
|
# Look for Mediasite embeds
|
||||||
|
mediasite_urls = MediasiteIE._extract_urls(webpage)
|
||||||
|
if mediasite_urls:
|
||||||
|
entries = [
|
||||||
|
self.url_result(smuggle_url(
|
||||||
|
compat_urlparse.urljoin(url, mediasite_url),
|
||||||
|
{'UrlReferrer': url}), ie=MediasiteIE.ie_key())
|
||||||
|
for mediasite_url in mediasite_urls]
|
||||||
|
return self.playlist_result(entries, video_id, video_title)
|
||||||
|
|
||||||
def merge_dicts(dict1, dict2):
|
def merge_dicts(dict1, dict2):
|
||||||
merged = {}
|
merged = {}
|
||||||
for k, v in dict1.items():
|
for k, v in dict1.items():
|
||||||
|
@ -1,22 +1,47 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
from ..compat import compat_str
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
ExtractorError,
|
|
||||||
determine_ext,
|
determine_ext,
|
||||||
|
ExtractorError,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class HotStarIE(InfoExtractor):
|
class HotStarBaseIE(InfoExtractor):
|
||||||
|
_GEO_COUNTRIES = ['IN']
|
||||||
|
|
||||||
|
def _download_json(self, *args, **kwargs):
|
||||||
|
response = super(HotStarBaseIE, self)._download_json(*args, **kwargs)
|
||||||
|
if response['resultCode'] != 'OK':
|
||||||
|
if kwargs.get('fatal'):
|
||||||
|
raise ExtractorError(
|
||||||
|
response['errorDescription'], expected=True)
|
||||||
|
return None
|
||||||
|
return response['resultObj']
|
||||||
|
|
||||||
|
def _download_content_info(self, content_id):
|
||||||
|
return self._download_json(
|
||||||
|
'https://account.hotstar.com/AVS/besc', content_id, query={
|
||||||
|
'action': 'GetAggregatedContentDetails',
|
||||||
|
'appVersion': '5.0.40',
|
||||||
|
'channel': 'PCTV',
|
||||||
|
'contentId': content_id,
|
||||||
|
})['contentInfo'][0]
|
||||||
|
|
||||||
|
|
||||||
|
class HotStarIE(HotStarBaseIE):
|
||||||
_VALID_URL = r'https?://(?:www\.)?hotstar\.com/(?:.+?[/-])?(?P<id>\d{10})'
|
_VALID_URL = r'https?://(?:www\.)?hotstar\.com/(?:.+?[/-])?(?P<id>\d{10})'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://www.hotstar.com/on-air-with-aib--english-1000076273',
|
'url': 'http://www.hotstar.com/on-air-with-aib--english-1000076273',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '1000076273',
|
'id': '1000076273',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'On Air With AIB - English',
|
'title': 'On Air With AIB',
|
||||||
'description': 'md5:c957d8868e9bc793ccb813691cc4c434',
|
'description': 'md5:c957d8868e9bc793ccb813691cc4c434',
|
||||||
'timestamp': 1447227000,
|
'timestamp': 1447227000,
|
||||||
'upload_date': '20151111',
|
'upload_date': '20151111',
|
||||||
@ -34,23 +59,11 @@ class HotStarIE(InfoExtractor):
|
|||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _download_json(self, url_or_request, video_id, note='Downloading JSON metadata', fatal=True, query=None):
|
|
||||||
json_data = super(HotStarIE, self)._download_json(
|
|
||||||
url_or_request, video_id, note, fatal=fatal, query=query)
|
|
||||||
if json_data['resultCode'] != 'OK':
|
|
||||||
if fatal:
|
|
||||||
raise ExtractorError(json_data['errorDescription'])
|
|
||||||
return None
|
|
||||||
return json_data['resultObj']
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
video_data = self._download_json(
|
|
||||||
'http://account.hotstar.com/AVS/besc', video_id, query={
|
video_data = self._download_content_info(video_id)
|
||||||
'action': 'GetAggregatedContentDetails',
|
|
||||||
'channel': 'PCTV',
|
|
||||||
'contentId': video_id,
|
|
||||||
})['contentInfo'][0]
|
|
||||||
title = video_data['episodeTitle']
|
title = video_data['episodeTitle']
|
||||||
|
|
||||||
if video_data.get('encrypted') == 'Y':
|
if video_data.get('encrypted') == 'Y':
|
||||||
@ -99,3 +112,51 @@ class HotStarIE(InfoExtractor):
|
|||||||
'episode_number': int_or_none(video_data.get('episodeNumber')),
|
'episode_number': int_or_none(video_data.get('episodeNumber')),
|
||||||
'series': video_data.get('contentTitle'),
|
'series': video_data.get('contentTitle'),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class HotStarPlaylistIE(HotStarBaseIE):
|
||||||
|
IE_NAME = 'hotstar:playlist'
|
||||||
|
_VALID_URL = r'(?P<url>https?://(?:www\.)?hotstar\.com/tv/[^/]+/(?P<content_id>\d+))/(?P<type>[^/]+)/(?P<id>\d+)'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://www.hotstar.com/tv/pratidaan/14982/episodes/14812/9993',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '14812',
|
||||||
|
},
|
||||||
|
'playlist_mincount': 75,
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.hotstar.com/tv/pratidaan/14982/popular-clips/9998/9998',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
_ITEM_TYPES = {
|
||||||
|
'episodes': 'EPISODE',
|
||||||
|
'popular-clips': 'CLIPS',
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
base_url = mobj.group('url')
|
||||||
|
content_id = mobj.group('content_id')
|
||||||
|
playlist_type = mobj.group('type')
|
||||||
|
|
||||||
|
content_info = self._download_content_info(content_id)
|
||||||
|
playlist_id = compat_str(content_info['categoryId'])
|
||||||
|
|
||||||
|
collection = self._download_json(
|
||||||
|
'https://search.hotstar.com/AVS/besc', playlist_id, query={
|
||||||
|
'action': 'SearchContents',
|
||||||
|
'appVersion': '5.0.40',
|
||||||
|
'channel': 'PCTV',
|
||||||
|
'moreFilters': 'series:%s;' % playlist_id,
|
||||||
|
'query': '*',
|
||||||
|
'searchOrder': 'last_broadcast_date desc,year desc,title asc',
|
||||||
|
'type': self._ITEM_TYPES.get(playlist_type, 'EPISODE'),
|
||||||
|
})
|
||||||
|
|
||||||
|
entries = [
|
||||||
|
self.url_result(
|
||||||
|
'%s/_/%s' % (base_url, video['contentId']),
|
||||||
|
ie=HotStarIE.ie_key(), video_id=video['contentId'])
|
||||||
|
for video in collection['response']['docs']
|
||||||
|
if video.get('contentId')]
|
||||||
|
|
||||||
|
return self.playlist_result(entries, playlist_id)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import itertools
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
@ -7,7 +8,6 @@ from ..compat import compat_str
|
|||||||
from ..utils import (
|
from ..utils import (
|
||||||
get_element_by_attribute,
|
get_element_by_attribute,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
limit_length,
|
|
||||||
lowercase_escape,
|
lowercase_escape,
|
||||||
try_get,
|
try_get,
|
||||||
)
|
)
|
||||||
@ -130,13 +130,21 @@ class InstagramIE(InfoExtractor):
|
|||||||
video_url = media.get('video_url')
|
video_url = media.get('video_url')
|
||||||
height = int_or_none(media.get('dimensions', {}).get('height'))
|
height = int_or_none(media.get('dimensions', {}).get('height'))
|
||||||
width = int_or_none(media.get('dimensions', {}).get('width'))
|
width = int_or_none(media.get('dimensions', {}).get('width'))
|
||||||
description = media.get('caption')
|
description = try_get(
|
||||||
|
media, lambda x: x['edge_media_to_caption']['edges'][0]['node']['text'],
|
||||||
|
compat_str) or media.get('caption')
|
||||||
thumbnail = media.get('display_src')
|
thumbnail = media.get('display_src')
|
||||||
timestamp = int_or_none(media.get('date'))
|
timestamp = int_or_none(media.get('taken_at_timestamp') or media.get('date'))
|
||||||
uploader = media.get('owner', {}).get('full_name')
|
uploader = media.get('owner', {}).get('full_name')
|
||||||
uploader_id = media.get('owner', {}).get('username')
|
uploader_id = media.get('owner', {}).get('username')
|
||||||
like_count = int_or_none(media.get('likes', {}).get('count'))
|
|
||||||
comment_count = int_or_none(media.get('comments', {}).get('count'))
|
def get_count(key, kind):
|
||||||
|
return int_or_none(try_get(
|
||||||
|
media, (lambda x: x['edge_media_%s' % key]['count'],
|
||||||
|
lambda x: x['%ss' % kind]['count'])))
|
||||||
|
like_count = get_count('preview_like', 'like')
|
||||||
|
comment_count = get_count('to_comment', 'comment')
|
||||||
|
|
||||||
comments = [{
|
comments = [{
|
||||||
'author': comment.get('user', {}).get('username'),
|
'author': comment.get('user', {}).get('username'),
|
||||||
'author_id': comment.get('user', {}).get('id'),
|
'author_id': comment.get('user', {}).get('id'),
|
||||||
@ -212,7 +220,7 @@ class InstagramIE(InfoExtractor):
|
|||||||
|
|
||||||
|
|
||||||
class InstagramUserIE(InfoExtractor):
|
class InstagramUserIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?instagram\.com/(?P<username>[^/]{2,})/?(?:$|[?#])'
|
_VALID_URL = r'https?://(?:www\.)?instagram\.com/(?P<id>[^/]{2,})/?(?:$|[?#])'
|
||||||
IE_DESC = 'Instagram user profile'
|
IE_DESC = 'Instagram user profile'
|
||||||
IE_NAME = 'instagram:user'
|
IE_NAME = 'instagram:user'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
@ -221,82 +229,79 @@ class InstagramUserIE(InfoExtractor):
|
|||||||
'id': 'porsche',
|
'id': 'porsche',
|
||||||
'title': 'porsche',
|
'title': 'porsche',
|
||||||
},
|
},
|
||||||
'playlist_mincount': 2,
|
'playlist_count': 5,
|
||||||
'playlist': [{
|
|
||||||
'info_dict': {
|
|
||||||
'id': '614605558512799803_462752227',
|
|
||||||
'ext': 'mp4',
|
|
||||||
'title': '#Porsche Intelligent Performance.',
|
|
||||||
'thumbnail': r're:^https?://.*\.jpg',
|
|
||||||
'uploader': 'Porsche',
|
|
||||||
'uploader_id': 'porsche',
|
|
||||||
'timestamp': 1387486713,
|
|
||||||
'upload_date': '20131219',
|
|
||||||
},
|
|
||||||
}],
|
|
||||||
'params': {
|
'params': {
|
||||||
'extract_flat': True,
|
'extract_flat': True,
|
||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
|
'playlistend': 5,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _entries(self, uploader_id):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
query = {
|
||||||
uploader_id = mobj.group('username')
|
'__a': 1,
|
||||||
|
}
|
||||||
|
|
||||||
entries = []
|
def get_count(kind):
|
||||||
page_count = 0
|
return int_or_none(try_get(
|
||||||
media_url = 'http://instagram.com/%s/media' % uploader_id
|
node, lambda x: x['%ss' % kind]['count']))
|
||||||
while True:
|
|
||||||
|
for page_num in itertools.count(1):
|
||||||
page = self._download_json(
|
page = self._download_json(
|
||||||
media_url, uploader_id,
|
'https://instagram.com/%s/' % uploader_id, uploader_id,
|
||||||
note='Downloading page %d ' % (page_count + 1),
|
note='Downloading page %d' % page_num,
|
||||||
)
|
fatal=False, query=query)
|
||||||
page_count += 1
|
if not page:
|
||||||
|
break
|
||||||
|
|
||||||
for it in page['items']:
|
nodes = try_get(page, lambda x: x['user']['media']['nodes'], list)
|
||||||
if it.get('type') != 'video':
|
if not nodes:
|
||||||
|
break
|
||||||
|
|
||||||
|
max_id = None
|
||||||
|
|
||||||
|
for node in nodes:
|
||||||
|
node_id = node.get('id')
|
||||||
|
if node_id:
|
||||||
|
max_id = node_id
|
||||||
|
|
||||||
|
if node.get('__typename') != 'GraphVideo' and node.get('is_video') is not True:
|
||||||
|
continue
|
||||||
|
video_id = node.get('code')
|
||||||
|
if not video_id:
|
||||||
continue
|
continue
|
||||||
like_count = int_or_none(it.get('likes', {}).get('count'))
|
|
||||||
user = it.get('user', {})
|
|
||||||
|
|
||||||
formats = [{
|
info = self.url_result(
|
||||||
'format_id': k,
|
'https://instagram.com/p/%s/' % video_id,
|
||||||
'height': v.get('height'),
|
ie=InstagramIE.ie_key(), video_id=video_id)
|
||||||
'width': v.get('width'),
|
|
||||||
'url': v['url'],
|
|
||||||
} for k, v in it['videos'].items()]
|
|
||||||
self._sort_formats(formats)
|
|
||||||
|
|
||||||
thumbnails_el = it.get('images', {})
|
description = try_get(
|
||||||
thumbnail = thumbnails_el.get('thumbnail', {}).get('url')
|
node, [lambda x: x['caption'], lambda x: x['text']['id']],
|
||||||
|
compat_str)
|
||||||
|
thumbnail = node.get('thumbnail_src') or node.get('display_src')
|
||||||
|
timestamp = int_or_none(node.get('date'))
|
||||||
|
|
||||||
# In some cases caption is null, which corresponds to None
|
comment_count = get_count('comment')
|
||||||
# in python. As a result, it.get('caption', {}) gives None
|
like_count = get_count('like')
|
||||||
title = (it.get('caption') or {}).get('text', it['id'])
|
view_count = int_or_none(node.get('video_views'))
|
||||||
|
|
||||||
entries.append({
|
info.update({
|
||||||
'id': it['id'],
|
'description': description,
|
||||||
'title': limit_length(title, 80),
|
|
||||||
'formats': formats,
|
|
||||||
'thumbnail': thumbnail,
|
'thumbnail': thumbnail,
|
||||||
'webpage_url': it.get('link'),
|
'timestamp': timestamp,
|
||||||
'uploader': user.get('full_name'),
|
'comment_count': comment_count,
|
||||||
'uploader_id': user.get('username'),
|
|
||||||
'like_count': like_count,
|
'like_count': like_count,
|
||||||
'timestamp': int_or_none(it.get('created_time')),
|
'view_count': view_count,
|
||||||
})
|
})
|
||||||
|
|
||||||
if not page['items']:
|
yield info
|
||||||
break
|
|
||||||
max_id = page['items'][-1]['id'].split('_')[0]
|
|
||||||
media_url = (
|
|
||||||
'http://instagram.com/%s/media?max_id=%s' % (
|
|
||||||
uploader_id, max_id))
|
|
||||||
|
|
||||||
return {
|
if not max_id:
|
||||||
'_type': 'playlist',
|
break
|
||||||
'entries': entries,
|
|
||||||
'id': uploader_id,
|
query['max_id'] = max_id
|
||||||
'title': uploader_id,
|
|
||||||
}
|
def _real_extract(self, url):
|
||||||
|
uploader_id = self._match_id(url)
|
||||||
|
return self.playlist_result(
|
||||||
|
self._entries(uploader_id), uploader_id, uploader_id)
|
||||||
|
64
youtube_dl/extractor/internazionale.py
Normal file
64
youtube_dl/extractor/internazionale.py
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import unified_timestamp
|
||||||
|
|
||||||
|
|
||||||
|
class InternazionaleIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?internazionale\.it/video/(?:[^/]+/)*(?P<id>[^/?#&]+)'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'https://www.internazionale.it/video/2015/02/19/richard-linklater-racconta-una-scena-di-boyhood',
|
||||||
|
'md5': '3e39d32b66882c1218e305acbf8348ca',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '265968',
|
||||||
|
'display_id': 'richard-linklater-racconta-una-scena-di-boyhood',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Richard Linklater racconta una scena di Boyhood',
|
||||||
|
'description': 'md5:efb7e5bbfb1a54ae2ed5a4a015f0e665',
|
||||||
|
'timestamp': 1424354635,
|
||||||
|
'upload_date': '20150219',
|
||||||
|
'thumbnail': r're:^https?://.*\.jpg$',
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
'format': 'bestvideo',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
display_id = self._match_id(url)
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
|
||||||
|
DATA_RE = r'data-%s=(["\'])(?P<value>(?:(?!\1).)+)\1'
|
||||||
|
|
||||||
|
title = self._search_regex(
|
||||||
|
DATA_RE % 'video-title', webpage, 'title', default=None,
|
||||||
|
group='value') or self._og_search_title(webpage)
|
||||||
|
|
||||||
|
video_id = self._search_regex(
|
||||||
|
DATA_RE % 'job-id', webpage, 'video id', group='value')
|
||||||
|
video_path = self._search_regex(
|
||||||
|
DATA_RE % 'video-path', webpage, 'video path', group='value')
|
||||||
|
|
||||||
|
video_base = 'https://video.internazionale.it/%s/%s.' % (video_path, video_id)
|
||||||
|
|
||||||
|
formats = self._extract_m3u8_formats(
|
||||||
|
video_base + 'm3u8', display_id, 'mp4',
|
||||||
|
entry_protocol='m3u8_native', m3u8_id='hls', fatal=False)
|
||||||
|
formats.extend(self._extract_mpd_formats(
|
||||||
|
video_base + 'mpd', display_id, mpd_id='dash', fatal=False))
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
timestamp = unified_timestamp(self._html_search_meta(
|
||||||
|
'article:published_time', webpage, 'timestamp'))
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'display_id': display_id,
|
||||||
|
'title': title,
|
||||||
|
'thumbnail': self._og_search_thumbnail(webpage),
|
||||||
|
'description': self._og_search_description(webpage),
|
||||||
|
'timestamp': timestamp,
|
||||||
|
'formats': formats,
|
||||||
|
}
|
@ -4,6 +4,7 @@ from __future__ import unicode_literals
|
|||||||
import uuid
|
import uuid
|
||||||
import xml.etree.ElementTree as etree
|
import xml.etree.ElementTree as etree
|
||||||
import json
|
import json
|
||||||
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
from ..compat import (
|
||||||
@ -25,7 +26,7 @@ from ..utils import (
|
|||||||
class ITVIE(InfoExtractor):
|
class ITVIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?itv\.com/hub/[^/]+/(?P<id>[0-9a-zA-Z]+)'
|
_VALID_URL = r'https?://(?:www\.)?itv\.com/hub/[^/]+/(?P<id>[0-9a-zA-Z]+)'
|
||||||
_GEO_COUNTRIES = ['GB']
|
_GEO_COUNTRIES = ['GB']
|
||||||
_TEST = {
|
_TESTS = [{
|
||||||
'url': 'http://www.itv.com/hub/mr-bean-animated-series/2a2936a0053',
|
'url': 'http://www.itv.com/hub/mr-bean-animated-series/2a2936a0053',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '2a2936a0053',
|
'id': '2a2936a0053',
|
||||||
@ -36,7 +37,11 @@ class ITVIE(InfoExtractor):
|
|||||||
# rtmp download
|
# rtmp download
|
||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
},
|
},
|
||||||
}
|
}, {
|
||||||
|
# unavailable via data-playlist-url
|
||||||
|
'url': 'https://www.itv.com/hub/through-the-keyhole/2a2271a0033',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
@ -100,6 +105,18 @@ class ITVIE(InfoExtractor):
|
|||||||
'Content-Type': 'text/xml; charset=utf-8',
|
'Content-Type': 'text/xml; charset=utf-8',
|
||||||
'SOAPAction': 'http://tempuri.org/PlaylistService/GetPlaylist',
|
'SOAPAction': 'http://tempuri.org/PlaylistService/GetPlaylist',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
info = self._search_json_ld(webpage, video_id, default={})
|
||||||
|
formats = []
|
||||||
|
subtitles = {}
|
||||||
|
|
||||||
|
def extract_subtitle(sub_url):
|
||||||
|
ext = determine_ext(sub_url, 'ttml')
|
||||||
|
subtitles.setdefault('en', []).append({
|
||||||
|
'url': sub_url,
|
||||||
|
'ext': 'ttml' if ext == 'xml' else ext,
|
||||||
|
})
|
||||||
|
|
||||||
resp_env = self._download_xml(
|
resp_env = self._download_xml(
|
||||||
params['data-playlist-url'], video_id,
|
params['data-playlist-url'], video_id,
|
||||||
headers=headers, data=etree.tostring(req_env))
|
headers=headers, data=etree.tostring(req_env))
|
||||||
@ -110,13 +127,27 @@ class ITVIE(InfoExtractor):
|
|||||||
if fault_code == 'InvalidGeoRegion':
|
if fault_code == 'InvalidGeoRegion':
|
||||||
self.raise_geo_restricted(
|
self.raise_geo_restricted(
|
||||||
msg=fault_string, countries=self._GEO_COUNTRIES)
|
msg=fault_string, countries=self._GEO_COUNTRIES)
|
||||||
raise ExtractorError('%s said: %s' % (self.IE_NAME, fault_string))
|
elif fault_code != 'InvalidEntity':
|
||||||
title = xpath_text(playlist, 'EpisodeTitle', fatal=True)
|
raise ExtractorError(
|
||||||
|
'%s said: %s' % (self.IE_NAME, fault_string), expected=True)
|
||||||
|
info.update({
|
||||||
|
'title': self._og_search_title(webpage),
|
||||||
|
'episode_title': params.get('data-video-episode'),
|
||||||
|
'series': params.get('data-video-title'),
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
title = xpath_text(playlist, 'EpisodeTitle', default=None)
|
||||||
|
info.update({
|
||||||
|
'title': title,
|
||||||
|
'episode_title': title,
|
||||||
|
'episode_number': int_or_none(xpath_text(playlist, 'EpisodeNumber')),
|
||||||
|
'series': xpath_text(playlist, 'ProgrammeTitle'),
|
||||||
|
'duration': parse_duration(xpath_text(playlist, 'Duration')),
|
||||||
|
})
|
||||||
video_element = xpath_element(playlist, 'VideoEntries/Video', fatal=True)
|
video_element = xpath_element(playlist, 'VideoEntries/Video', fatal=True)
|
||||||
media_files = xpath_element(video_element, 'MediaFiles', fatal=True)
|
media_files = xpath_element(video_element, 'MediaFiles', fatal=True)
|
||||||
rtmp_url = media_files.attrib['base']
|
rtmp_url = media_files.attrib['base']
|
||||||
|
|
||||||
formats = []
|
|
||||||
for media_file in media_files.findall('MediaFile'):
|
for media_file in media_files.findall('MediaFile'):
|
||||||
play_path = xpath_text(media_file, 'URL')
|
play_path = xpath_text(media_file, 'URL')
|
||||||
if not play_path:
|
if not play_path:
|
||||||
@ -142,9 +173,13 @@ class ITVIE(InfoExtractor):
|
|||||||
f['url'] = rtmp_url
|
f['url'] = rtmp_url
|
||||||
formats.append(f)
|
formats.append(f)
|
||||||
|
|
||||||
ios_playlist_url = params.get('data-video-playlist')
|
for caption_url in video_element.findall('ClosedCaptioningURIs/URL'):
|
||||||
|
if caption_url.text:
|
||||||
|
extract_subtitle(caption_url.text)
|
||||||
|
|
||||||
|
ios_playlist_url = params.get('data-video-playlist') or params.get('data-video-id')
|
||||||
hmac = params.get('data-video-hmac')
|
hmac = params.get('data-video-hmac')
|
||||||
if ios_playlist_url and hmac:
|
if ios_playlist_url and hmac and re.match(r'https?://', ios_playlist_url):
|
||||||
headers = self.geo_verification_headers()
|
headers = self.geo_verification_headers()
|
||||||
headers.update({
|
headers.update({
|
||||||
'Accept': 'application/vnd.itv.vod.playlist.v2+json',
|
'Accept': 'application/vnd.itv.vod.playlist.v2+json',
|
||||||
@ -159,12 +194,12 @@ class ITVIE(InfoExtractor):
|
|||||||
'token': ''
|
'token': ''
|
||||||
},
|
},
|
||||||
'device': {
|
'device': {
|
||||||
'manufacturer': 'Apple',
|
'manufacturer': 'Safari',
|
||||||
'model': 'iPad',
|
'model': '5',
|
||||||
'os': {
|
'os': {
|
||||||
'name': 'iPhone OS',
|
'name': 'Windows NT',
|
||||||
'version': '9.3',
|
'version': '6.1',
|
||||||
'type': 'ios'
|
'type': 'desktop'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'client': {
|
'client': {
|
||||||
@ -173,10 +208,10 @@ class ITVIE(InfoExtractor):
|
|||||||
},
|
},
|
||||||
'variantAvailability': {
|
'variantAvailability': {
|
||||||
'featureset': {
|
'featureset': {
|
||||||
'min': ['hls', 'aes'],
|
'min': ['hls', 'aes', 'outband-webvtt'],
|
||||||
'max': ['hls', 'aes']
|
'max': ['hls', 'aes', 'outband-webvtt']
|
||||||
},
|
},
|
||||||
'platformTag': 'mobile'
|
'platformTag': 'dotcom'
|
||||||
}
|
}
|
||||||
}).encode(), headers=headers, fatal=False)
|
}).encode(), headers=headers, fatal=False)
|
||||||
if ios_playlist:
|
if ios_playlist:
|
||||||
@ -197,27 +232,22 @@ class ITVIE(InfoExtractor):
|
|||||||
formats.append({
|
formats.append({
|
||||||
'url': href,
|
'url': href,
|
||||||
})
|
})
|
||||||
|
subs = video_data.get('Subtitles')
|
||||||
|
if isinstance(subs, list):
|
||||||
|
for sub in subs:
|
||||||
|
if not isinstance(sub, dict):
|
||||||
|
continue
|
||||||
|
href = sub.get('Href')
|
||||||
|
if isinstance(href, compat_str):
|
||||||
|
extract_subtitle(href)
|
||||||
|
if not info.get('duration'):
|
||||||
|
info['duration'] = parse_duration(video_data.get('Duration'))
|
||||||
|
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
subtitles = {}
|
|
||||||
for caption_url in video_element.findall('ClosedCaptioningURIs/URL'):
|
|
||||||
if not caption_url.text:
|
|
||||||
continue
|
|
||||||
ext = determine_ext(caption_url.text, 'ttml')
|
|
||||||
subtitles.setdefault('en', []).append({
|
|
||||||
'url': caption_url.text,
|
|
||||||
'ext': 'ttml' if ext == 'xml' else ext,
|
|
||||||
})
|
|
||||||
|
|
||||||
info = self._search_json_ld(webpage, video_id, default={})
|
|
||||||
info.update({
|
info.update({
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'title': title,
|
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
'subtitles': subtitles,
|
'subtitles': subtitles,
|
||||||
'episode_title': title,
|
|
||||||
'episode_number': int_or_none(xpath_text(playlist, 'EpisodeNumber')),
|
|
||||||
'series': xpath_text(playlist, 'ProgrammeTitle'),
|
|
||||||
'duartion': parse_duration(xpath_text(playlist, 'Duration')),
|
|
||||||
})
|
})
|
||||||
return info
|
return info
|
||||||
|
@ -24,7 +24,7 @@ class JWPlatformIE(InfoExtractor):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def _extract_url(webpage):
|
def _extract_url(webpage):
|
||||||
mobj = re.search(
|
mobj = re.search(
|
||||||
r'<script[^>]+?src=["\'](?P<url>(?:https?:)?//content.jwplatform.com/players/[a-zA-Z0-9]{8})',
|
r'<(?:script|iframe)[^>]+?src=["\'](?P<url>(?:https?:)?//content.jwplatform.com/players/[a-zA-Z0-9]{8})',
|
||||||
webpage)
|
webpage)
|
||||||
if mobj:
|
if mobj:
|
||||||
return mobj.group('url')
|
return mobj.group('url')
|
||||||
|
@ -124,10 +124,13 @@ class KalturaIE(InfoExtractor):
|
|||||||
(?P<q1>["'])
|
(?P<q1>["'])
|
||||||
(?:https?:)?//cdnapi(?:sec)?\.kaltura\.com(?::\d+)?/(?:(?!(?P=q1)).)*\b(?:p|partner_id)/(?P<partner_id>\d+)(?:(?!(?P=q1)).)*
|
(?:https?:)?//cdnapi(?:sec)?\.kaltura\.com(?::\d+)?/(?:(?!(?P=q1)).)*\b(?:p|partner_id)/(?P<partner_id>\d+)(?:(?!(?P=q1)).)*
|
||||||
(?P=q1).*?
|
(?P=q1).*?
|
||||||
|
(?:
|
||||||
(?:
|
(?:
|
||||||
entry_?[Ii]d|
|
entry_?[Ii]d|
|
||||||
(?P<q2>["'])entry_?[Ii]d(?P=q2)
|
(?P<q2>["'])entry_?[Ii]d(?P=q2)
|
||||||
)\s*:\s*
|
)\s*:\s*|
|
||||||
|
\[\s*(?P<q2_1>["'])entry_?[Ii]d(?P=q2_1)\s*\]\s*=\s*
|
||||||
|
)
|
||||||
(?P<q3>["'])(?P<id>(?:(?!(?P=q3)).)+)(?P=q3)
|
(?P<q3>["'])(?P<id>(?:(?!(?P=q3)).)+)(?P=q3)
|
||||||
''', webpage) or
|
''', webpage) or
|
||||||
re.search(
|
re.search(
|
||||||
|
@ -114,7 +114,7 @@ class LivestreamIE(InfoExtractor):
|
|||||||
|
|
||||||
smil_url = video_data.get('smil_url')
|
smil_url = video_data.get('smil_url')
|
||||||
if smil_url:
|
if smil_url:
|
||||||
formats.extend(self._extract_smil_formats(smil_url, video_id))
|
formats.extend(self._extract_smil_formats(smil_url, video_id, fatal=False))
|
||||||
|
|
||||||
m3u8_url = video_data.get('m3u8_url')
|
m3u8_url = video_data.get('m3u8_url')
|
||||||
if m3u8_url:
|
if m3u8_url:
|
||||||
|
@ -94,7 +94,15 @@ class LyndaBaseIE(InfoExtractor):
|
|||||||
class LyndaIE(LyndaBaseIE):
|
class LyndaIE(LyndaBaseIE):
|
||||||
IE_NAME = 'lynda'
|
IE_NAME = 'lynda'
|
||||||
IE_DESC = 'lynda.com videos'
|
IE_DESC = 'lynda.com videos'
|
||||||
_VALID_URL = r'https?://(?:www\.)?(?:lynda\.com|educourse\.ga)/(?:[^/]+/[^/]+/(?P<course_id>\d+)|player/embed)/(?P<id>\d+)'
|
_VALID_URL = r'''(?x)
|
||||||
|
https?://
|
||||||
|
(?:www\.)?(?:lynda\.com|educourse\.ga)/
|
||||||
|
(?:
|
||||||
|
(?:[^/]+/){2,3}(?P<course_id>\d+)|
|
||||||
|
player/embed
|
||||||
|
)/
|
||||||
|
(?P<id>\d+)
|
||||||
|
'''
|
||||||
|
|
||||||
_TIMECODE_REGEX = r'\[(?P<timecode>\d+:\d+:\d+[\.,]\d+)\]'
|
_TIMECODE_REGEX = r'\[(?P<timecode>\d+:\d+:\d+[\.,]\d+)\]'
|
||||||
|
|
||||||
@ -113,6 +121,9 @@ class LyndaIE(LyndaBaseIE):
|
|||||||
}, {
|
}, {
|
||||||
'url': 'https://educourse.ga/Bootstrap-tutorials/Using-exercise-files/110885/114408-4.html',
|
'url': 'https://educourse.ga/Bootstrap-tutorials/Using-exercise-files/110885/114408-4.html',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.lynda.com/de/Graphic-Design-tutorials/Willkommen-Grundlagen-guten-Gestaltung/393570/393572-4.html',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _raise_unavailable(self, video_id):
|
def _raise_unavailable(self, video_id):
|
||||||
@ -244,8 +255,9 @@ class LyndaIE(LyndaBaseIE):
|
|||||||
def _get_subtitles(self, video_id):
|
def _get_subtitles(self, video_id):
|
||||||
url = 'https://www.lynda.com/ajax/player?videoId=%s&type=transcript' % video_id
|
url = 'https://www.lynda.com/ajax/player?videoId=%s&type=transcript' % video_id
|
||||||
subs = self._download_json(url, None, False)
|
subs = self._download_json(url, None, False)
|
||||||
if subs:
|
fixed_subs = self._fix_subtitles(subs)
|
||||||
return {'en': [{'ext': 'srt', 'data': self._fix_subtitles(subs)}]}
|
if fixed_subs:
|
||||||
|
return {'en': [{'ext': 'srt', 'data': fixed_subs}]}
|
||||||
else:
|
else:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
@ -256,7 +268,15 @@ class LyndaCourseIE(LyndaBaseIE):
|
|||||||
|
|
||||||
# Course link equals to welcome/introduction video link of same course
|
# Course link equals to welcome/introduction video link of same course
|
||||||
# We will recognize it as course link
|
# We will recognize it as course link
|
||||||
_VALID_URL = r'https?://(?:www|m)\.(?:lynda\.com|educourse\.ga)/(?P<coursepath>[^/]+/[^/]+/(?P<courseid>\d+))-\d\.html'
|
_VALID_URL = r'https?://(?:www|m)\.(?:lynda\.com|educourse\.ga)/(?P<coursepath>(?:[^/]+/){2,3}(?P<courseid>\d+))-2\.html'
|
||||||
|
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://www.lynda.com/Graphic-Design-tutorials/Grundlagen-guten-Gestaltung/393570-2.html',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.lynda.com/de/Graphic-Design-tutorials/Grundlagen-guten-Gestaltung/393570-2.html',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
@ -13,8 +13,15 @@ from ..utils import (
|
|||||||
class MailRuIE(InfoExtractor):
|
class MailRuIE(InfoExtractor):
|
||||||
IE_NAME = 'mailru'
|
IE_NAME = 'mailru'
|
||||||
IE_DESC = 'Видео@Mail.Ru'
|
IE_DESC = 'Видео@Mail.Ru'
|
||||||
_VALID_URL = r'https?://(?:(?:www|m)\.)?my\.mail\.ru/(?:video/.*#video=/?(?P<idv1>(?:[^/]+/){3}\d+)|(?:(?P<idv2prefix>(?:[^/]+/){2})video/(?P<idv2suffix>[^/]+/\d+))\.html)'
|
_VALID_URL = r'''(?x)
|
||||||
|
https?://
|
||||||
|
(?:(?:www|m)\.)?my\.mail\.ru/
|
||||||
|
(?:
|
||||||
|
video/.*\#video=/?(?P<idv1>(?:[^/]+/){3}\d+)|
|
||||||
|
(?:(?P<idv2prefix>(?:[^/]+/){2})video/(?P<idv2suffix>[^/]+/\d+))\.html|
|
||||||
|
(?:video/embed|\+/video/meta)/(?P<metaid>\d+)
|
||||||
|
)
|
||||||
|
'''
|
||||||
_TESTS = [
|
_TESTS = [
|
||||||
{
|
{
|
||||||
'url': 'http://my.mail.ru/video/top#video=/mail/sonypicturesrus/75/76',
|
'url': 'http://my.mail.ru/video/top#video=/mail/sonypicturesrus/75/76',
|
||||||
@ -23,7 +30,7 @@ class MailRuIE(InfoExtractor):
|
|||||||
'id': '46301138_76',
|
'id': '46301138_76',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Новый Человек-Паук. Высокое напряжение. Восстание Электро',
|
'title': 'Новый Человек-Паук. Высокое напряжение. Восстание Электро',
|
||||||
'timestamp': 1393232740,
|
'timestamp': 1393235077,
|
||||||
'upload_date': '20140224',
|
'upload_date': '20140224',
|
||||||
'uploader': 'sonypicturesrus',
|
'uploader': 'sonypicturesrus',
|
||||||
'uploader_id': 'sonypicturesrus@mail.ru',
|
'uploader_id': 'sonypicturesrus@mail.ru',
|
||||||
@ -40,7 +47,7 @@ class MailRuIE(InfoExtractor):
|
|||||||
'title': 'Samsung Galaxy S5 Hammer Smash Fail Battery Explosion',
|
'title': 'Samsung Galaxy S5 Hammer Smash Fail Battery Explosion',
|
||||||
'timestamp': 1397039888,
|
'timestamp': 1397039888,
|
||||||
'upload_date': '20140409',
|
'upload_date': '20140409',
|
||||||
'uploader': 'hitech@corp.mail.ru',
|
'uploader': 'hitech',
|
||||||
'uploader_id': 'hitech@corp.mail.ru',
|
'uploader_id': 'hitech@corp.mail.ru',
|
||||||
'duration': 245,
|
'duration': 245,
|
||||||
},
|
},
|
||||||
@ -65,28 +72,42 @@ class MailRuIE(InfoExtractor):
|
|||||||
{
|
{
|
||||||
'url': 'http://m.my.mail.ru/mail/3sktvtr/video/_myvideo/138.html',
|
'url': 'http://m.my.mail.ru/mail/3sktvtr/video/_myvideo/138.html',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': 'https://my.mail.ru/video/embed/7949340477499637815',
|
||||||
|
'only_matching': True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://my.mail.ru/+/video/meta/7949340477499637815',
|
||||||
|
'only_matching': True,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
video_id = mobj.group('idv1')
|
meta_id = mobj.group('metaid')
|
||||||
|
|
||||||
|
video_id = None
|
||||||
|
if meta_id:
|
||||||
|
meta_url = 'https://my.mail.ru/+/video/meta/%s' % meta_id
|
||||||
|
else:
|
||||||
|
video_id = mobj.group('idv1')
|
||||||
if not video_id:
|
if not video_id:
|
||||||
video_id = mobj.group('idv2prefix') + mobj.group('idv2suffix')
|
video_id = mobj.group('idv2prefix') + mobj.group('idv2suffix')
|
||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
video_data = None
|
|
||||||
|
|
||||||
page_config = self._parse_json(self._search_regex(
|
page_config = self._parse_json(self._search_regex(
|
||||||
r'(?s)<script[^>]+class="sp-video__page-config"[^>]*>(.+?)</script>',
|
r'(?s)<script[^>]+class="sp-video__page-config"[^>]*>(.+?)</script>',
|
||||||
webpage, 'page config', default='{}'), video_id, fatal=False)
|
webpage, 'page config', default='{}'), video_id, fatal=False)
|
||||||
if page_config:
|
if page_config:
|
||||||
meta_url = page_config.get('metaUrl') or page_config.get('video', {}).get('metaUrl')
|
meta_url = page_config.get('metaUrl') or page_config.get('video', {}).get('metaUrl')
|
||||||
|
else:
|
||||||
|
meta_url = None
|
||||||
|
|
||||||
|
video_data = None
|
||||||
if meta_url:
|
if meta_url:
|
||||||
video_data = self._download_json(
|
video_data = self._download_json(
|
||||||
meta_url, video_id, 'Downloading video meta JSON', fatal=False)
|
meta_url, video_id or meta_id, 'Downloading video meta JSON',
|
||||||
|
fatal=not video_id)
|
||||||
|
|
||||||
# Fallback old approach
|
# Fallback old approach
|
||||||
if not video_data:
|
if not video_data:
|
||||||
|
77
youtube_dl/extractor/massengeschmacktv.py
Normal file
77
youtube_dl/extractor/massengeschmacktv.py
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
clean_html,
|
||||||
|
determine_ext,
|
||||||
|
int_or_none,
|
||||||
|
js_to_json,
|
||||||
|
mimetype2ext,
|
||||||
|
parse_filesize,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class MassengeschmackTVIE(InfoExtractor):
|
||||||
|
IE_NAME = 'massengeschmack.tv'
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?massengeschmack\.tv/play/(?P<id>[^?&#]+)'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
|
'url': 'https://massengeschmack.tv/play/fktv202',
|
||||||
|
'md5': 'a9e054db9c2b5a08f0a0527cc201e8d3',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'fktv202',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Fernsehkritik-TV - Folge 202',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
episode = self._match_id(url)
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, episode)
|
||||||
|
title = clean_html(self._html_search_regex(
|
||||||
|
'<h3>([^<]+)</h3>', webpage, 'title'))
|
||||||
|
thumbnail = self._search_regex(r'POSTER\s*=\s*"([^"]+)', webpage, 'thumbnail', fatal=False)
|
||||||
|
sources = self._parse_json(self._search_regex(r'(?s)MEDIA\s*=\s*(\[.+?\]);', webpage, 'media'), episode, js_to_json)
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
for source in sources:
|
||||||
|
furl = source.get('src')
|
||||||
|
if not furl:
|
||||||
|
continue
|
||||||
|
furl = self._proto_relative_url(furl)
|
||||||
|
ext = determine_ext(furl) or mimetype2ext(source.get('type'))
|
||||||
|
if ext == 'm3u8':
|
||||||
|
formats.extend(self._extract_m3u8_formats(
|
||||||
|
furl, episode, 'mp4', 'm3u8_native',
|
||||||
|
m3u8_id='hls', fatal=False))
|
||||||
|
else:
|
||||||
|
formats.append({
|
||||||
|
'url': furl,
|
||||||
|
'format_id': determine_ext(furl),
|
||||||
|
})
|
||||||
|
|
||||||
|
for (durl, format_id, width, height, filesize) in re.findall(r'''(?x)
|
||||||
|
<a[^>]+?href="(?P<url>(?:https:)?//[^"]+)".*?
|
||||||
|
<strong>(?P<format_id>.+?)</strong>.*?
|
||||||
|
<small>(?:(?P<width>\d+)x(?P<height>\d+))?\s+?\((?P<filesize>[\d,]+\s*[GM]iB)\)</small>
|
||||||
|
''', webpage):
|
||||||
|
formats.append({
|
||||||
|
'url': durl,
|
||||||
|
'format_id': format_id,
|
||||||
|
'width': int_or_none(width),
|
||||||
|
'height': int_or_none(height),
|
||||||
|
'filesize': parse_filesize(filesize),
|
||||||
|
'vcodec': 'none' if format_id.startswith('Audio') else None,
|
||||||
|
})
|
||||||
|
|
||||||
|
self._sort_formats(formats, ('width', 'height', 'filesize', 'tbr'))
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': episode,
|
||||||
|
'title': title,
|
||||||
|
'formats': formats,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
}
|
214
youtube_dl/extractor/mediasite.py
Normal file
214
youtube_dl/extractor/mediasite.py
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
import json
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..compat import (
|
||||||
|
compat_str,
|
||||||
|
compat_urlparse,
|
||||||
|
)
|
||||||
|
from ..utils import (
|
||||||
|
ExtractorError,
|
||||||
|
float_or_none,
|
||||||
|
mimetype2ext,
|
||||||
|
unescapeHTML,
|
||||||
|
unsmuggle_url,
|
||||||
|
urljoin,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class MediasiteIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'(?xi)https?://[^/]+/Mediasite/Play/(?P<id>[0-9a-f]{32,34})(?P<query>\?[^#]+|)'
|
||||||
|
_TESTS = [
|
||||||
|
{
|
||||||
|
'url': 'https://hitsmediaweb.h-its.org/mediasite/Play/2db6c271681e4f199af3c60d1f82869b1d',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '2db6c271681e4f199af3c60d1f82869b1d',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Lecture: Tuesday, September 20, 2016 - Sir Andrew Wiles',
|
||||||
|
'description': 'Sir Andrew Wiles: “Equations in arithmetic”\\n\\nI will describe some of the interactions between modern number theory and the problem of solving equations in rational numbers or integers\\u0027.',
|
||||||
|
'timestamp': 1474268400.0,
|
||||||
|
'upload_date': '20160919',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://mediasite.uib.no/Mediasite/Play/90bb363295d945d6b548c867d01181361d?catalog=a452b7df-9ae1-46b7-a3ba-aceeb285f3eb',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '90bb363295d945d6b548c867d01181361d',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'upload_date': '20150429',
|
||||||
|
'title': '5) IT-forum 2015-Dag 1 - Dungbeetle - How and why Rain created a tiny bug tracker for Unity',
|
||||||
|
'timestamp': 1430311380.0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': 'https://collegerama.tudelft.nl/Mediasite/Play/585a43626e544bdd97aeb71a0ec907a01d',
|
||||||
|
'md5': '481fda1c11f67588c0d9d8fbdced4e39',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '585a43626e544bdd97aeb71a0ec907a01d',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Een nieuwe wereld: waarden, bewustzijn en techniek van de mensheid 2.0.',
|
||||||
|
'description': '',
|
||||||
|
'thumbnail': r're:^https?://.*\.jpg(?:\?.*)?$',
|
||||||
|
'duration': 7713.088,
|
||||||
|
'timestamp': 1413309600,
|
||||||
|
'upload_date': '20141014',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': 'https://collegerama.tudelft.nl/Mediasite/Play/86a9ea9f53e149079fbdb4202b521ed21d?catalog=fd32fd35-6c99-466c-89d4-cd3c431bc8a4',
|
||||||
|
'md5': 'ef1fdded95bdf19b12c5999949419c92',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '86a9ea9f53e149079fbdb4202b521ed21d',
|
||||||
|
'ext': 'wmv',
|
||||||
|
'title': '64ste Vakantiecursus: Afvalwater',
|
||||||
|
'description': 'md5:7fd774865cc69d972f542b157c328305',
|
||||||
|
'thumbnail': r're:^https?://.*\.jpg(?:\?.*?)?$',
|
||||||
|
'duration': 10853,
|
||||||
|
'timestamp': 1326446400,
|
||||||
|
'upload_date': '20120113',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://digitalops.sandia.gov/Mediasite/Play/24aace4429fc450fb5b38cdbf424a66e1d',
|
||||||
|
'md5': '9422edc9b9a60151727e4b6d8bef393d',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '24aace4429fc450fb5b38cdbf424a66e1d',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Xyce Software Training - Section 1',
|
||||||
|
'description': r're:(?s)SAND Number: SAND 2013-7800.{200,}',
|
||||||
|
'upload_date': '20120409',
|
||||||
|
'timestamp': 1333983600,
|
||||||
|
'duration': 7794,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
# look in Mediasite.Core.js (Mediasite.ContentStreamType[*])
|
||||||
|
_STREAM_TYPES = {
|
||||||
|
0: 'video1', # the main video
|
||||||
|
2: 'slide',
|
||||||
|
3: 'presentation',
|
||||||
|
4: 'video2', # screencast?
|
||||||
|
5: 'video3',
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _extract_urls(webpage):
|
||||||
|
return [
|
||||||
|
unescapeHTML(mobj.group('url'))
|
||||||
|
for mobj in re.finditer(
|
||||||
|
r'(?xi)<iframe\b[^>]+\bsrc=(["\'])(?P<url>(?:(?:https?:)?//[^/]+)?/Mediasite/Play/[0-9a-f]{32,34}(?:\?.*?)?)\1',
|
||||||
|
webpage)]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
url, data = unsmuggle_url(url, {})
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
resource_id = mobj.group('id')
|
||||||
|
query = mobj.group('query')
|
||||||
|
|
||||||
|
webpage, urlh = self._download_webpage_handle(url, resource_id) # XXX: add UrlReferrer?
|
||||||
|
redirect_url = compat_str(urlh.geturl())
|
||||||
|
|
||||||
|
# XXX: might have also extracted UrlReferrer and QueryString from the html
|
||||||
|
service_path = compat_urlparse.urljoin(redirect_url, self._html_search_regex(
|
||||||
|
r'<div[^>]+\bid=["\']ServicePath[^>]+>(.+?)</div>', webpage, resource_id,
|
||||||
|
default='/Mediasite/PlayerService/PlayerService.svc/json'))
|
||||||
|
|
||||||
|
player_options = self._download_json(
|
||||||
|
'%s/GetPlayerOptions' % service_path, resource_id,
|
||||||
|
headers={
|
||||||
|
'Content-type': 'application/json; charset=utf-8',
|
||||||
|
'X-Requested-With': 'XMLHttpRequest',
|
||||||
|
},
|
||||||
|
data=json.dumps({
|
||||||
|
'getPlayerOptionsRequest': {
|
||||||
|
'ResourceId': resource_id,
|
||||||
|
'QueryString': query,
|
||||||
|
'UrlReferrer': data.get('UrlReferrer', ''),
|
||||||
|
'UseScreenReader': False,
|
||||||
|
}
|
||||||
|
}).encode('utf-8'))['d']
|
||||||
|
|
||||||
|
presentation = player_options['Presentation']
|
||||||
|
title = presentation['Title']
|
||||||
|
|
||||||
|
if presentation is None:
|
||||||
|
raise ExtractorError(
|
||||||
|
'Mediasite says: %s' % player_options['PlayerPresentationStatusMessage'],
|
||||||
|
expected=True)
|
||||||
|
|
||||||
|
thumbnails = []
|
||||||
|
formats = []
|
||||||
|
for snum, Stream in enumerate(presentation['Streams']):
|
||||||
|
stream_type = Stream.get('StreamType')
|
||||||
|
if stream_type is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
video_urls = Stream.get('VideoUrls')
|
||||||
|
if not isinstance(video_urls, list):
|
||||||
|
video_urls = []
|
||||||
|
|
||||||
|
stream_id = self._STREAM_TYPES.get(
|
||||||
|
stream_type, 'type%u' % stream_type)
|
||||||
|
|
||||||
|
stream_formats = []
|
||||||
|
for unum, VideoUrl in enumerate(video_urls):
|
||||||
|
video_url = VideoUrl.get('Location')
|
||||||
|
if not video_url or not isinstance(video_url, compat_str):
|
||||||
|
continue
|
||||||
|
# XXX: if Stream.get('CanChangeScheme', False), switch scheme to HTTP/HTTPS
|
||||||
|
|
||||||
|
media_type = VideoUrl.get('MediaType')
|
||||||
|
if media_type == 'SS':
|
||||||
|
stream_formats.extend(self._extract_ism_formats(
|
||||||
|
video_url, resource_id,
|
||||||
|
ism_id='%s-%u.%u' % (stream_id, snum, unum),
|
||||||
|
fatal=False))
|
||||||
|
elif media_type == 'Dash':
|
||||||
|
stream_formats.extend(self._extract_mpd_formats(
|
||||||
|
video_url, resource_id,
|
||||||
|
mpd_id='%s-%u.%u' % (stream_id, snum, unum),
|
||||||
|
fatal=False))
|
||||||
|
else:
|
||||||
|
stream_formats.append({
|
||||||
|
'format_id': '%s-%u.%u' % (stream_id, snum, unum),
|
||||||
|
'url': video_url,
|
||||||
|
'ext': mimetype2ext(VideoUrl.get('MimeType')),
|
||||||
|
})
|
||||||
|
|
||||||
|
# TODO: if Stream['HasSlideContent']:
|
||||||
|
# synthesise an MJPEG video stream '%s-%u.slides' % (stream_type, snum)
|
||||||
|
# from Stream['Slides']
|
||||||
|
# this will require writing a custom downloader...
|
||||||
|
|
||||||
|
# disprefer 'secondary' streams
|
||||||
|
if stream_type != 0:
|
||||||
|
for fmt in stream_formats:
|
||||||
|
fmt['preference'] = -1
|
||||||
|
|
||||||
|
thumbnail_url = Stream.get('ThumbnailUrl')
|
||||||
|
if thumbnail_url:
|
||||||
|
thumbnails.append({
|
||||||
|
'id': '%s-%u' % (stream_id, snum),
|
||||||
|
'url': urljoin(redirect_url, thumbnail_url),
|
||||||
|
'preference': -1 if stream_type != 0 else 0,
|
||||||
|
})
|
||||||
|
formats.extend(stream_formats)
|
||||||
|
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
# XXX: Presentation['Presenters']
|
||||||
|
# XXX: Presentation['Transcript']
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': resource_id,
|
||||||
|
'title': title,
|
||||||
|
'description': presentation.get('Description'),
|
||||||
|
'duration': float_or_none(presentation.get('Duration'), 1000),
|
||||||
|
'timestamp': float_or_none(presentation.get('UnixTime'), 1000),
|
||||||
|
'formats': formats,
|
||||||
|
'thumbnails': thumbnails,
|
||||||
|
}
|
@ -1,13 +1,13 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import json
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from .ooyala import OoyalaIE
|
from .ooyala import OoyalaIE
|
||||||
from ..compat import (
|
from ..compat import (
|
||||||
compat_str,
|
compat_str,
|
||||||
compat_urllib_parse_urlencode,
|
|
||||||
compat_urlparse,
|
compat_urlparse,
|
||||||
)
|
)
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
@ -42,31 +42,33 @@ class MiTeleBaseIE(InfoExtractor):
|
|||||||
duration = int_or_none(mmc.get('duration'))
|
duration = int_or_none(mmc.get('duration'))
|
||||||
for location in mmc['locations']:
|
for location in mmc['locations']:
|
||||||
gat = self._proto_relative_url(location.get('gat'), 'http:')
|
gat = self._proto_relative_url(location.get('gat'), 'http:')
|
||||||
bas = location.get('bas')
|
gcp = location.get('gcp')
|
||||||
loc = location.get('loc')
|
|
||||||
ogn = location.get('ogn')
|
ogn = location.get('ogn')
|
||||||
if None in (gat, bas, loc, ogn):
|
if None in (gat, gcp, ogn):
|
||||||
continue
|
continue
|
||||||
token_data = {
|
token_data = {
|
||||||
'bas': bas,
|
'gcp': gcp,
|
||||||
'icd': loc,
|
|
||||||
'ogn': ogn,
|
'ogn': ogn,
|
||||||
'sta': '0',
|
'sta': 0,
|
||||||
}
|
}
|
||||||
media = self._download_json(
|
media = self._download_json(
|
||||||
'%s/?%s' % (gat, compat_urllib_parse_urlencode(token_data)),
|
gat, video_id, data=json.dumps(token_data).encode('utf-8'),
|
||||||
video_id, 'Downloading %s JSON' % location['loc'])
|
headers={
|
||||||
file_ = media.get('file')
|
'Content-Type': 'application/json;charset=utf-8',
|
||||||
if not file_:
|
'Referer': url,
|
||||||
|
})
|
||||||
|
stream = media.get('stream') or media.get('file')
|
||||||
|
if not stream:
|
||||||
continue
|
continue
|
||||||
ext = determine_ext(file_)
|
ext = determine_ext(stream)
|
||||||
if ext == 'f4m':
|
if ext == 'f4m':
|
||||||
formats.extend(self._extract_f4m_formats(
|
formats.extend(self._extract_f4m_formats(
|
||||||
file_ + '&hdcore=3.2.0&plugin=aasp-3.2.0.77.18',
|
stream + '&hdcore=3.2.0&plugin=aasp-3.2.0.77.18',
|
||||||
video_id, f4m_id='hds', fatal=False))
|
video_id, f4m_id='hds', fatal=False))
|
||||||
elif ext == 'm3u8':
|
elif ext == 'm3u8':
|
||||||
formats.extend(self._extract_m3u8_formats(
|
formats.extend(self._extract_m3u8_formats(
|
||||||
file_, video_id, 'mp4', 'm3u8_native', m3u8_id='hls', fatal=False))
|
stream, video_id, 'mp4', 'm3u8_native',
|
||||||
|
m3u8_id='hls', fatal=False))
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -40,21 +40,29 @@ class MnetIE(InfoExtractor):
|
|||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
|
|
||||||
|
# TODO: extract rtmp formats
|
||||||
|
# no stype -> rtmp url
|
||||||
|
# stype=H -> m3u8 url
|
||||||
|
# stype=M -> mpd url
|
||||||
info = self._download_json(
|
info = self._download_json(
|
||||||
'http://content.api.mnet.com/player/vodConfig?id=%s&ctype=CLIP' % video_id,
|
'http://content.api.mnet.com/player/vodConfig',
|
||||||
video_id, 'Downloading vod config JSON')['data']['info']
|
video_id, 'Downloading vod config JSON', query={
|
||||||
|
'id': video_id,
|
||||||
|
'ctype': 'CLIP',
|
||||||
|
'stype': 'H',
|
||||||
|
})['data']['info']
|
||||||
|
|
||||||
title = info['title']
|
title = info['title']
|
||||||
|
|
||||||
rtmp_info = self._download_json(
|
cdn_data = self._download_json(
|
||||||
info['cdn'], video_id, 'Downloading vod cdn JSON')
|
info['cdn'], video_id, 'Downloading vod cdn JSON')['data'][0]
|
||||||
|
m3u8_url = cdn_data['url']
|
||||||
formats = [{
|
token = cdn_data.get('token')
|
||||||
'url': rtmp_info['serverurl'] + rtmp_info['fileurl'],
|
if token and token != '-':
|
||||||
'ext': 'flv',
|
m3u8_url += '?' + token
|
||||||
'page_url': url,
|
formats = self._extract_wowza_formats(
|
||||||
'player_url': 'http://flvfile.mnet.com/service/player/201602/cjem_player_tv.swf?v=201602191318',
|
m3u8_url, video_id, skip_protocols=['rtmp', 'rtsp', 'f4m'])
|
||||||
}]
|
self._sort_formats(formats)
|
||||||
|
|
||||||
description = info.get('ment')
|
description = info.get('ment')
|
||||||
duration = parse_duration(info.get('time'))
|
duration = parse_duration(info.get('time'))
|
||||||
|
@ -4,8 +4,11 @@ import datetime
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
from ..compat import compat_urlparse
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
|
InAdvancePagedList,
|
||||||
|
orderedSet,
|
||||||
str_to_int,
|
str_to_int,
|
||||||
unified_strdate,
|
unified_strdate,
|
||||||
)
|
)
|
||||||
@ -114,3 +117,86 @@ class MotherlessIE(InfoExtractor):
|
|||||||
'age_limit': age_limit,
|
'age_limit': age_limit,
|
||||||
'url': video_url,
|
'url': video_url,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class MotherlessGroupIE(InfoExtractor):
|
||||||
|
_VALID_URL = 'https?://(?:www\.)?motherless\.com/gv?/(?P<id>[a-z0-9_]+)'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://motherless.com/g/movie_scenes',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'movie_scenes',
|
||||||
|
'title': 'Movie Scenes',
|
||||||
|
'description': 'Hot and sexy scenes from "regular" movies... '
|
||||||
|
'Beautiful actresses fully nude... A looot of '
|
||||||
|
'skin! :)Enjoy!',
|
||||||
|
},
|
||||||
|
'playlist_mincount': 662,
|
||||||
|
}, {
|
||||||
|
'url': 'http://motherless.com/gv/sex_must_be_funny',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'sex_must_be_funny',
|
||||||
|
'title': 'Sex must be funny',
|
||||||
|
'description': 'Sex can be funny. Wide smiles,laugh, games, fun of '
|
||||||
|
'any kind!'
|
||||||
|
},
|
||||||
|
'playlist_mincount': 9,
|
||||||
|
}]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def suitable(cls, url):
|
||||||
|
return (False if MotherlessIE.suitable(url)
|
||||||
|
else super(MotherlessGroupIE, cls).suitable(url))
|
||||||
|
|
||||||
|
def _extract_entries(self, webpage, base):
|
||||||
|
entries = []
|
||||||
|
for mobj in re.finditer(
|
||||||
|
r'href="(?P<href>/[^"]+)"[^>]*>(?:\s*<img[^>]+alt="[^-]+-\s(?P<title>[^"]+)")?',
|
||||||
|
webpage):
|
||||||
|
video_url = compat_urlparse.urljoin(base, mobj.group('href'))
|
||||||
|
if not MotherlessIE.suitable(video_url):
|
||||||
|
continue
|
||||||
|
video_id = MotherlessIE._match_id(video_url)
|
||||||
|
title = mobj.group('title')
|
||||||
|
entries.append(self.url_result(
|
||||||
|
video_url, ie=MotherlessIE.ie_key(), video_id=video_id,
|
||||||
|
video_title=title))
|
||||||
|
# Alternative fallback
|
||||||
|
if not entries:
|
||||||
|
entries = [
|
||||||
|
self.url_result(
|
||||||
|
compat_urlparse.urljoin(base, '/' + video_id),
|
||||||
|
ie=MotherlessIE.ie_key(), video_id=video_id)
|
||||||
|
for video_id in orderedSet(re.findall(
|
||||||
|
r'data-codename=["\']([A-Z0-9]+)', webpage))]
|
||||||
|
return entries
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
group_id = self._match_id(url)
|
||||||
|
page_url = compat_urlparse.urljoin(url, '/gv/%s' % group_id)
|
||||||
|
webpage = self._download_webpage(page_url, group_id)
|
||||||
|
title = self._search_regex(
|
||||||
|
r'<title>([\w\s]+\w)\s+-', webpage, 'title', fatal=False)
|
||||||
|
description = self._html_search_meta(
|
||||||
|
'description', webpage, fatal=False)
|
||||||
|
page_count = self._int(self._search_regex(
|
||||||
|
r'(\d+)</(?:a|span)><(?:a|span)[^>]+>\s*NEXT',
|
||||||
|
webpage, 'page_count'), 'page_count')
|
||||||
|
PAGE_SIZE = 80
|
||||||
|
|
||||||
|
def _get_page(idx):
|
||||||
|
webpage = self._download_webpage(
|
||||||
|
page_url, group_id, query={'page': idx + 1},
|
||||||
|
note='Downloading page %d/%d' % (idx + 1, page_count)
|
||||||
|
)
|
||||||
|
for entry in self._extract_entries(webpage, url):
|
||||||
|
yield entry
|
||||||
|
|
||||||
|
playlist = InAdvancePagedList(_get_page, page_count, PAGE_SIZE)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'_type': 'playlist',
|
||||||
|
'id': group_id,
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'entries': playlist
|
||||||
|
}
|
||||||
|
@ -115,10 +115,17 @@ class MTVServicesInfoExtractor(InfoExtractor):
|
|||||||
if transcript.get('kind') != 'captions':
|
if transcript.get('kind') != 'captions':
|
||||||
continue
|
continue
|
||||||
lang = transcript.get('srclang')
|
lang = transcript.get('srclang')
|
||||||
subtitles[lang] = [{
|
for typographic in transcript.findall('./typographic'):
|
||||||
'url': compat_str(typographic.get('src')),
|
sub_src = typographic.get('src')
|
||||||
'ext': typographic.get('format')
|
if not sub_src:
|
||||||
} for typographic in transcript.findall('./typographic')]
|
continue
|
||||||
|
ext = typographic.get('format')
|
||||||
|
if ext == 'cea-608':
|
||||||
|
ext = 'scc'
|
||||||
|
subtitles.setdefault(lang, []).append({
|
||||||
|
'url': compat_str(sub_src),
|
||||||
|
'ext': ext
|
||||||
|
})
|
||||||
return subtitles
|
return subtitles
|
||||||
|
|
||||||
def _get_video_info(self, itemdoc, use_hls=True):
|
def _get_video_info(self, itemdoc, use_hls=True):
|
||||||
|
@ -28,7 +28,7 @@ class NexxIE(InfoExtractor):
|
|||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
# movie
|
# movie
|
||||||
'url': 'https://api.nexx.cloud/v3/748/videos/byid/128907',
|
'url': 'https://api.nexx.cloud/v3/748/videos/byid/128907',
|
||||||
'md5': '16746bfc28c42049492385c989b26c4a',
|
'md5': '828cea195be04e66057b846288295ba1',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '128907',
|
'id': '128907',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
@ -42,9 +42,6 @@ class NexxIE(InfoExtractor):
|
|||||||
'timestamp': 1384264416,
|
'timestamp': 1384264416,
|
||||||
'upload_date': '20131112',
|
'upload_date': '20131112',
|
||||||
},
|
},
|
||||||
'params': {
|
|
||||||
'format': 'bestvideo',
|
|
||||||
},
|
|
||||||
}, {
|
}, {
|
||||||
# episode
|
# episode
|
||||||
'url': 'https://api.nexx.cloud/v3/741/videos/byid/247858',
|
'url': 'https://api.nexx.cloud/v3/741/videos/byid/247858',
|
||||||
@ -62,7 +59,6 @@ class NexxIE(InfoExtractor):
|
|||||||
'season_number': 2,
|
'season_number': 2,
|
||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
'format': 'bestvideo',
|
|
||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
@ -193,35 +189,67 @@ class NexxIE(InfoExtractor):
|
|||||||
stream_data = video['streamdata']
|
stream_data = video['streamdata']
|
||||||
language = general.get('language_raw') or ''
|
language = general.get('language_raw') or ''
|
||||||
|
|
||||||
# TODO: reverse more cdns and formats
|
# TODO: reverse more cdns
|
||||||
|
|
||||||
cdn = stream_data['cdnType']
|
cdn = stream_data['cdnType']
|
||||||
assert cdn == 'azure'
|
assert cdn == 'azure'
|
||||||
|
|
||||||
azure_locator = stream_data['azureLocator']
|
azure_locator = stream_data['azureLocator']
|
||||||
|
|
||||||
AZURE_URL = 'http://nx-p%02d.akamaized.net/'
|
AZURE_URL = 'http://nx%s%02d.akamaized.net/'
|
||||||
|
|
||||||
for secure in ('s', ''):
|
def get_cdn_shield_base(shield_type='', prefix='-p'):
|
||||||
cdn_shield = stream_data.get('cdnShieldHTTP%s' % secure.upper())
|
for secure in ('', 's'):
|
||||||
|
cdn_shield = stream_data.get('cdnShield%sHTTP%s' % (shield_type, secure.upper()))
|
||||||
if cdn_shield:
|
if cdn_shield:
|
||||||
azure_base = 'http%s://%s' % (secure, cdn_shield)
|
return 'http%s://%s' % (secure, cdn_shield)
|
||||||
break
|
|
||||||
else:
|
else:
|
||||||
azure_base = AZURE_URL % int(stream_data['azureAccount'].replace('nexxplayplus', ''))
|
return AZURE_URL % (prefix, int(stream_data['azureAccount'].replace('nexxplayplus', '')))
|
||||||
|
|
||||||
|
azure_stream_base = get_cdn_shield_base()
|
||||||
is_ml = ',' in language
|
is_ml = ',' in language
|
||||||
azure_m3u8_url = '%s%s/%s_src%s.ism/Manifest(format=m3u8-aapl)' % (
|
azure_manifest_url = '%s%s/%s_src%s.ism/Manifest' % (
|
||||||
azure_base, azure_locator, video_id, ('_manifest' if is_ml else ''))
|
azure_stream_base, azure_locator, video_id, ('_manifest' if is_ml else '')) + '%s'
|
||||||
|
|
||||||
protection_token = try_get(
|
protection_token = try_get(
|
||||||
video, lambda x: x['protectiondata']['token'], compat_str)
|
video, lambda x: x['protectiondata']['token'], compat_str)
|
||||||
if protection_token:
|
if protection_token:
|
||||||
azure_m3u8_url += '?hdnts=%s' % protection_token
|
azure_manifest_url += '?hdnts=%s' % protection_token
|
||||||
|
|
||||||
formats = self._extract_m3u8_formats(
|
formats = self._extract_m3u8_formats(
|
||||||
azure_m3u8_url, video_id, 'mp4', entry_protocol='m3u8_native',
|
azure_manifest_url % '(format=m3u8-aapl)',
|
||||||
m3u8_id='%s-hls' % cdn)
|
video_id, 'mp4', 'm3u8_native',
|
||||||
|
m3u8_id='%s-hls' % cdn, fatal=False)
|
||||||
|
formats.extend(self._extract_mpd_formats(
|
||||||
|
azure_manifest_url % '(format=mpd-time-csf)',
|
||||||
|
video_id, mpd_id='%s-dash' % cdn, fatal=False))
|
||||||
|
formats.extend(self._extract_ism_formats(
|
||||||
|
azure_manifest_url % '', video_id, ism_id='%s-mss' % cdn, fatal=False))
|
||||||
|
|
||||||
|
azure_progressive_base = get_cdn_shield_base('Prog', '-d')
|
||||||
|
azure_file_distribution = stream_data.get('azureFileDistribution')
|
||||||
|
if azure_file_distribution:
|
||||||
|
fds = azure_file_distribution.split(',')
|
||||||
|
if fds:
|
||||||
|
for fd in fds:
|
||||||
|
ss = fd.split(':')
|
||||||
|
if len(ss) == 2:
|
||||||
|
tbr = int_or_none(ss[0])
|
||||||
|
if tbr:
|
||||||
|
f = {
|
||||||
|
'url': '%s%s/%s_src_%s_%d.mp4' % (
|
||||||
|
azure_progressive_base, azure_locator, video_id, ss[1], tbr),
|
||||||
|
'format_id': '%s-http-%d' % (cdn, tbr),
|
||||||
|
'tbr': tbr,
|
||||||
|
}
|
||||||
|
width_height = ss[1].split('x')
|
||||||
|
if len(width_height) == 2:
|
||||||
|
f.update({
|
||||||
|
'width': int_or_none(width_height[0]),
|
||||||
|
'height': int_or_none(width_height[1]),
|
||||||
|
})
|
||||||
|
formats.append(f)
|
||||||
|
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -10,7 +10,7 @@ from ..utils import update_url_query
|
|||||||
class NickIE(MTVServicesInfoExtractor):
|
class NickIE(MTVServicesInfoExtractor):
|
||||||
# None of videos on the website are still alive?
|
# None of videos on the website are still alive?
|
||||||
IE_NAME = 'nick.com'
|
IE_NAME = 'nick.com'
|
||||||
_VALID_URL = r'https?://(?:(?:www|beta)\.)?nick(?:jr)?\.com/(?:[^/]+/)?(?:videos/clip|[^/]+/videos)/(?P<id>[^/?#.]+)'
|
_VALID_URL = r'https?://(?P<domain>(?:(?:www|beta)\.)?nick(?:jr)?\.com)/(?:[^/]+/)?(?:videos/clip|[^/]+/videos)/(?P<id>[^/?#.]+)'
|
||||||
_FEED_URL = 'http://udat.mtvnservices.com/service1/dispatch.htm'
|
_FEED_URL = 'http://udat.mtvnservices.com/service1/dispatch.htm'
|
||||||
_GEO_COUNTRIES = ['US']
|
_GEO_COUNTRIES = ['US']
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
@ -69,8 +69,59 @@ class NickIE(MTVServicesInfoExtractor):
|
|||||||
'mgid': uri,
|
'mgid': uri,
|
||||||
}
|
}
|
||||||
|
|
||||||
def _extract_mgid(self, webpage):
|
def _real_extract(self, url):
|
||||||
return self._search_regex(r'data-contenturi="([^"]+)', webpage, 'mgid')
|
domain, display_id = re.match(self._VALID_URL, url).groups()
|
||||||
|
video_data = self._download_json(
|
||||||
|
'http://%s/data/video.endLevel.json' % domain,
|
||||||
|
display_id, query={
|
||||||
|
'urlKey': display_id,
|
||||||
|
})
|
||||||
|
return self._get_videos_info(video_data['player'] + video_data['id'])
|
||||||
|
|
||||||
|
|
||||||
|
class NickBrIE(MTVServicesInfoExtractor):
|
||||||
|
IE_NAME = 'nickelodeon:br'
|
||||||
|
_VALID_URL = r'https?://(?P<domain>(?:www\.)?nickjr|mundonick\.uol)\.com\.br/(?:programas/)?[^/]+/videos/(?:episodios/)?(?P<id>[^/?#.]+)'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://www.nickjr.com.br/patrulha-canina/videos/210-labirinto-de-pipoca/',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://mundonick.uol.com.br/programas/the-loud-house/videos/muitas-irmas/7ljo9j',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
domain, display_id = re.match(self._VALID_URL, url).groups()
|
||||||
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
uri = self._search_regex(
|
||||||
|
r'data-(?:contenturi|mgid)="([^"]+)', webpage, 'mgid')
|
||||||
|
video_id = self._id_from_uri(uri)
|
||||||
|
config = self._download_json(
|
||||||
|
'http://media.mtvnservices.com/pmt/e1/access/index.html',
|
||||||
|
video_id, query={
|
||||||
|
'uri': uri,
|
||||||
|
'configtype': 'edge',
|
||||||
|
}, headers={
|
||||||
|
'Referer': url,
|
||||||
|
})
|
||||||
|
info_url = self._remove_template_parameter(config['feedWithQueryParams'])
|
||||||
|
if info_url == 'None':
|
||||||
|
if domain.startswith('www.'):
|
||||||
|
domain = domain[4:]
|
||||||
|
content_domain = {
|
||||||
|
'mundonick.uol': 'mundonick.com.br',
|
||||||
|
'nickjr': 'br.nickelodeonjunior.tv',
|
||||||
|
}[domain]
|
||||||
|
query = {
|
||||||
|
'mgid': uri,
|
||||||
|
'imageEp': content_domain,
|
||||||
|
'arcEp': content_domain,
|
||||||
|
}
|
||||||
|
if domain == 'nickjr.com.br':
|
||||||
|
query['ep'] = 'c4b16088'
|
||||||
|
info_url = update_url_query(
|
||||||
|
'http://feeds.mtvnservices.com/od/feed/intl-mrss-player-feed', query)
|
||||||
|
return self._get_videos_info_from_url(info_url, video_id)
|
||||||
|
|
||||||
|
|
||||||
class NickDeIE(MTVServicesInfoExtractor):
|
class NickDeIE(MTVServicesInfoExtractor):
|
||||||
|
@ -70,7 +70,7 @@ class NocoIE(InfoExtractor):
|
|||||||
return
|
return
|
||||||
|
|
||||||
login = self._download_json(
|
login = self._download_json(
|
||||||
self._LOGIN_URL, None, 'Logging in as %s' % username,
|
self._LOGIN_URL, None, 'Logging in',
|
||||||
data=urlencode_postdata({
|
data=urlencode_postdata({
|
||||||
'a': 'login',
|
'a': 'login',
|
||||||
'cookie': '1',
|
'cookie': '1',
|
||||||
|
@ -1,261 +0,0 @@
|
|||||||
# coding: utf-8
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
|
||||||
from ..compat import compat_str
|
|
||||||
from ..utils import (
|
|
||||||
ExtractorError,
|
|
||||||
determine_ext,
|
|
||||||
int_or_none,
|
|
||||||
parse_iso8601,
|
|
||||||
parse_duration,
|
|
||||||
remove_start,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class NowTVBaseIE(InfoExtractor):
|
|
||||||
_VIDEO_FIELDS = (
|
|
||||||
'id', 'title', 'free', 'geoblocked', 'articleLong', 'articleShort',
|
|
||||||
'broadcastStartDate', 'seoUrl', 'duration', 'files',
|
|
||||||
'format.defaultImage169Format', 'format.defaultImage169Logo')
|
|
||||||
|
|
||||||
def _extract_video(self, info, display_id=None):
|
|
||||||
video_id = compat_str(info['id'])
|
|
||||||
|
|
||||||
files = info['files']
|
|
||||||
if not files:
|
|
||||||
if info.get('geoblocked', False):
|
|
||||||
raise ExtractorError(
|
|
||||||
'Video %s is not available from your location due to geo restriction' % video_id,
|
|
||||||
expected=True)
|
|
||||||
if not info.get('free', True):
|
|
||||||
raise ExtractorError(
|
|
||||||
'Video %s is not available for free' % video_id, expected=True)
|
|
||||||
|
|
||||||
formats = []
|
|
||||||
for item in files['items']:
|
|
||||||
if determine_ext(item['path']) != 'f4v':
|
|
||||||
continue
|
|
||||||
app, play_path = remove_start(item['path'], '/').split('/', 1)
|
|
||||||
formats.append({
|
|
||||||
'url': 'rtmpe://fms.rtl.de',
|
|
||||||
'app': app,
|
|
||||||
'play_path': 'mp4:%s' % play_path,
|
|
||||||
'ext': 'flv',
|
|
||||||
'page_url': 'http://rtlnow.rtl.de',
|
|
||||||
'player_url': 'http://cdn.static-fra.de/now/vodplayer.swf',
|
|
||||||
'tbr': int_or_none(item.get('bitrate')),
|
|
||||||
})
|
|
||||||
self._sort_formats(formats)
|
|
||||||
|
|
||||||
title = info['title']
|
|
||||||
description = info.get('articleLong') or info.get('articleShort')
|
|
||||||
timestamp = parse_iso8601(info.get('broadcastStartDate'), ' ')
|
|
||||||
duration = parse_duration(info.get('duration'))
|
|
||||||
|
|
||||||
f = info.get('format', {})
|
|
||||||
thumbnail = f.get('defaultImage169Format') or f.get('defaultImage169Logo')
|
|
||||||
|
|
||||||
return {
|
|
||||||
'id': video_id,
|
|
||||||
'display_id': display_id or info.get('seoUrl'),
|
|
||||||
'title': title,
|
|
||||||
'description': description,
|
|
||||||
'thumbnail': thumbnail,
|
|
||||||
'timestamp': timestamp,
|
|
||||||
'duration': duration,
|
|
||||||
'formats': formats,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class NowTVIE(NowTVBaseIE):
|
|
||||||
_WORKING = False
|
|
||||||
_VALID_URL = r'https?://(?:www\.)?nowtv\.(?:de|at|ch)/(?:rtl|rtl2|rtlnitro|superrtl|ntv|vox)/(?P<show_id>[^/]+)/(?:(?:list/[^/]+|jahr/\d{4}/\d{1,2})/)?(?P<id>[^/]+)/(?:player|preview)'
|
|
||||||
|
|
||||||
_TESTS = [{
|
|
||||||
# rtl
|
|
||||||
'url': 'http://www.nowtv.de/rtl/bauer-sucht-frau/die-neuen-bauern-und-eine-hochzeit/player',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '203519',
|
|
||||||
'display_id': 'bauer-sucht-frau/die-neuen-bauern-und-eine-hochzeit',
|
|
||||||
'ext': 'flv',
|
|
||||||
'title': 'Inka Bause stellt die neuen Bauern vor',
|
|
||||||
'description': 'md5:e234e1ed6d63cf06be5c070442612e7e',
|
|
||||||
'thumbnail': r're:^https?://.*\.jpg$',
|
|
||||||
'timestamp': 1432580700,
|
|
||||||
'upload_date': '20150525',
|
|
||||||
'duration': 2786,
|
|
||||||
},
|
|
||||||
'params': {
|
|
||||||
# rtmp download
|
|
||||||
'skip_download': True,
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
# rtl2
|
|
||||||
'url': 'http://www.nowtv.de/rtl2/berlin-tag-nacht/berlin-tag-nacht-folge-934/player',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '203481',
|
|
||||||
'display_id': 'berlin-tag-nacht/berlin-tag-nacht-folge-934',
|
|
||||||
'ext': 'flv',
|
|
||||||
'title': 'Berlin - Tag & Nacht (Folge 934)',
|
|
||||||
'description': 'md5:c85e88c2e36c552dfe63433bc9506dd0',
|
|
||||||
'thumbnail': r're:^https?://.*\.jpg$',
|
|
||||||
'timestamp': 1432666800,
|
|
||||||
'upload_date': '20150526',
|
|
||||||
'duration': 2641,
|
|
||||||
},
|
|
||||||
'params': {
|
|
||||||
# rtmp download
|
|
||||||
'skip_download': True,
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
# rtlnitro
|
|
||||||
'url': 'http://www.nowtv.de/rtlnitro/alarm-fuer-cobra-11-die-autobahnpolizei/hals-und-beinbruch-2014-08-23-21-10-00/player',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '165780',
|
|
||||||
'display_id': 'alarm-fuer-cobra-11-die-autobahnpolizei/hals-und-beinbruch-2014-08-23-21-10-00',
|
|
||||||
'ext': 'flv',
|
|
||||||
'title': 'Hals- und Beinbruch',
|
|
||||||
'description': 'md5:b50d248efffe244e6f56737f0911ca57',
|
|
||||||
'thumbnail': r're:^https?://.*\.jpg$',
|
|
||||||
'timestamp': 1432415400,
|
|
||||||
'upload_date': '20150523',
|
|
||||||
'duration': 2742,
|
|
||||||
},
|
|
||||||
'params': {
|
|
||||||
# rtmp download
|
|
||||||
'skip_download': True,
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
# superrtl
|
|
||||||
'url': 'http://www.nowtv.de/superrtl/medicopter-117/angst/player',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '99205',
|
|
||||||
'display_id': 'medicopter-117/angst',
|
|
||||||
'ext': 'flv',
|
|
||||||
'title': 'Angst!',
|
|
||||||
'description': 'md5:30cbc4c0b73ec98bcd73c9f2a8c17c4e',
|
|
||||||
'thumbnail': r're:^https?://.*\.jpg$',
|
|
||||||
'timestamp': 1222632900,
|
|
||||||
'upload_date': '20080928',
|
|
||||||
'duration': 3025,
|
|
||||||
},
|
|
||||||
'params': {
|
|
||||||
# rtmp download
|
|
||||||
'skip_download': True,
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
# ntv
|
|
||||||
'url': 'http://www.nowtv.de/ntv/ratgeber-geld/thema-ua-der-erste-blick-die-apple-watch/player',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '203521',
|
|
||||||
'display_id': 'ratgeber-geld/thema-ua-der-erste-blick-die-apple-watch',
|
|
||||||
'ext': 'flv',
|
|
||||||
'title': 'Thema u.a.: Der erste Blick: Die Apple Watch',
|
|
||||||
'description': 'md5:4312b6c9d839ffe7d8caf03865a531af',
|
|
||||||
'thumbnail': r're:^https?://.*\.jpg$',
|
|
||||||
'timestamp': 1432751700,
|
|
||||||
'upload_date': '20150527',
|
|
||||||
'duration': 1083,
|
|
||||||
},
|
|
||||||
'params': {
|
|
||||||
# rtmp download
|
|
||||||
'skip_download': True,
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
# vox
|
|
||||||
'url': 'http://www.nowtv.de/vox/der-hundeprofi/buero-fall-chihuahua-joel/player',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '128953',
|
|
||||||
'display_id': 'der-hundeprofi/buero-fall-chihuahua-joel',
|
|
||||||
'ext': 'flv',
|
|
||||||
'title': "Büro-Fall / Chihuahua 'Joel'",
|
|
||||||
'description': 'md5:e62cb6bf7c3cc669179d4f1eb279ad8d',
|
|
||||||
'thumbnail': r're:^https?://.*\.jpg$',
|
|
||||||
'timestamp': 1432408200,
|
|
||||||
'upload_date': '20150523',
|
|
||||||
'duration': 3092,
|
|
||||||
},
|
|
||||||
'params': {
|
|
||||||
# rtmp download
|
|
||||||
'skip_download': True,
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
'url': 'http://www.nowtv.de/rtl/bauer-sucht-frau/die-neuen-bauern-und-eine-hochzeit/preview',
|
|
||||||
'only_matching': True,
|
|
||||||
}, {
|
|
||||||
'url': 'http://www.nowtv.at/rtl/bauer-sucht-frau/die-neuen-bauern-und-eine-hochzeit/preview?return=/rtl/bauer-sucht-frau/die-neuen-bauern-und-eine-hochzeit',
|
|
||||||
'only_matching': True,
|
|
||||||
}, {
|
|
||||||
'url': 'http://www.nowtv.de/rtl2/echtzeit/list/aktuell/schnelles-geld-am-ende-der-welt/player',
|
|
||||||
'only_matching': True,
|
|
||||||
}, {
|
|
||||||
'url': 'http://www.nowtv.de/rtl2/zuhause-im-glueck/jahr/2015/11/eine-erschuetternde-diagnose/player',
|
|
||||||
'only_matching': True,
|
|
||||||
}]
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
|
||||||
mobj = re.match(self._VALID_URL, url)
|
|
||||||
display_id = '%s/%s' % (mobj.group('show_id'), mobj.group('id'))
|
|
||||||
|
|
||||||
info = self._download_json(
|
|
||||||
'https://api.nowtv.de/v3/movies/%s?fields=%s'
|
|
||||||
% (display_id, ','.join(self._VIDEO_FIELDS)), display_id)
|
|
||||||
|
|
||||||
return self._extract_video(info, display_id)
|
|
||||||
|
|
||||||
|
|
||||||
class NowTVListIE(NowTVBaseIE):
|
|
||||||
_VALID_URL = r'https?://(?:www\.)?nowtv\.(?:de|at|ch)/(?:rtl|rtl2|rtlnitro|superrtl|ntv|vox)/(?P<show_id>[^/]+)/list/(?P<id>[^?/#&]+)$'
|
|
||||||
|
|
||||||
_SHOW_FIELDS = ('title', )
|
|
||||||
_SEASON_FIELDS = ('id', 'headline', 'seoheadline', )
|
|
||||||
|
|
||||||
_TESTS = [{
|
|
||||||
'url': 'http://www.nowtv.at/rtl/stern-tv/list/aktuell',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '17006',
|
|
||||||
'title': 'stern TV - Aktuell',
|
|
||||||
},
|
|
||||||
'playlist_count': 1,
|
|
||||||
}, {
|
|
||||||
'url': 'http://www.nowtv.at/rtl/das-supertalent/list/free-staffel-8',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '20716',
|
|
||||||
'title': 'Das Supertalent - FREE Staffel 8',
|
|
||||||
},
|
|
||||||
'playlist_count': 14,
|
|
||||||
}]
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
|
||||||
mobj = re.match(self._VALID_URL, url)
|
|
||||||
show_id = mobj.group('show_id')
|
|
||||||
season_id = mobj.group('id')
|
|
||||||
|
|
||||||
fields = []
|
|
||||||
fields.extend(self._SHOW_FIELDS)
|
|
||||||
fields.extend('formatTabs.%s' % field for field in self._SEASON_FIELDS)
|
|
||||||
fields.extend(
|
|
||||||
'formatTabs.formatTabPages.container.movies.%s' % field
|
|
||||||
for field in self._VIDEO_FIELDS)
|
|
||||||
|
|
||||||
list_info = self._download_json(
|
|
||||||
'https://api.nowtv.de/v3/formats/seo?fields=%s&name=%s.php'
|
|
||||||
% (','.join(fields), show_id),
|
|
||||||
season_id)
|
|
||||||
|
|
||||||
season = next(
|
|
||||||
season for season in list_info['formatTabs']['items']
|
|
||||||
if season.get('seoheadline') == season_id)
|
|
||||||
|
|
||||||
title = '%s - %s' % (list_info['title'], season['headline'])
|
|
||||||
|
|
||||||
entries = []
|
|
||||||
for container in season['formatTabPages']['items']:
|
|
||||||
for info in ((container.get('container') or {}).get('movies') or {}).get('items') or []:
|
|
||||||
entries.append(self._extract_video(info))
|
|
||||||
|
|
||||||
return self.playlist_result(
|
|
||||||
entries, compat_str(season.get('id') or season_id), title)
|
|
@ -14,6 +14,7 @@ from ..utils import (
|
|||||||
int_or_none,
|
int_or_none,
|
||||||
qualities,
|
qualities,
|
||||||
unescapeHTML,
|
unescapeHTML,
|
||||||
|
urlencode_postdata,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -56,7 +57,7 @@ class OdnoklassnikiIE(InfoExtractor):
|
|||||||
'url': 'http://ok.ru/video/64211978996595-1',
|
'url': 'http://ok.ru/video/64211978996595-1',
|
||||||
'md5': '2f206894ffb5dbfcce2c5a14b909eea5',
|
'md5': '2f206894ffb5dbfcce2c5a14b909eea5',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '64211978996595-1',
|
'id': 'V_VztHT5BzY',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Космическая среда от 26 августа 2015',
|
'title': 'Космическая среда от 26 августа 2015',
|
||||||
'description': 'md5:848eb8b85e5e3471a3a803dae1343ed0',
|
'description': 'md5:848eb8b85e5e3471a3a803dae1343ed0',
|
||||||
@ -127,9 +128,14 @@ class OdnoklassnikiIE(InfoExtractor):
|
|||||||
if metadata:
|
if metadata:
|
||||||
metadata = self._parse_json(metadata, video_id)
|
metadata = self._parse_json(metadata, video_id)
|
||||||
else:
|
else:
|
||||||
|
data = {}
|
||||||
|
st_location = flashvars.get('location')
|
||||||
|
if st_location:
|
||||||
|
data['st.location'] = st_location
|
||||||
metadata = self._download_json(
|
metadata = self._download_json(
|
||||||
compat_urllib_parse_unquote(flashvars['metadataUrl']),
|
compat_urllib_parse_unquote(flashvars['metadataUrl']),
|
||||||
video_id, 'Downloading metadata JSON')
|
video_id, 'Downloading metadata JSON',
|
||||||
|
data=urlencode_postdata(data))
|
||||||
|
|
||||||
movie = metadata['movie']
|
movie = metadata['movie']
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ class OnceIE(InfoExtractor):
|
|||||||
ADAPTIVE_URL_TEMPLATE = 'http://once.unicornmedia.com/now/master/playlist/%s/%s/%s/content.m3u8'
|
ADAPTIVE_URL_TEMPLATE = 'http://once.unicornmedia.com/now/master/playlist/%s/%s/%s/content.m3u8'
|
||||||
PROGRESSIVE_URL_TEMPLATE = 'http://once.unicornmedia.com/now/media/progressive/%s/%s/%s/%s/content.mp4'
|
PROGRESSIVE_URL_TEMPLATE = 'http://once.unicornmedia.com/now/media/progressive/%s/%s/%s/%s/content.mp4'
|
||||||
|
|
||||||
def _extract_once_formats(self, url):
|
def _extract_once_formats(self, url, http_formats_preference=None):
|
||||||
domain_id, application_id, media_item_id = re.match(
|
domain_id, application_id, media_item_id = re.match(
|
||||||
OnceIE._VALID_URL, url).groups()
|
OnceIE._VALID_URL, url).groups()
|
||||||
formats = self._extract_m3u8_formats(
|
formats = self._extract_m3u8_formats(
|
||||||
@ -35,6 +35,7 @@ class OnceIE(InfoExtractor):
|
|||||||
'format_id': adaptive_format['format_id'].replace(
|
'format_id': adaptive_format['format_id'].replace(
|
||||||
'hls', 'http'),
|
'hls', 'http'),
|
||||||
'protocol': 'http',
|
'protocol': 'http',
|
||||||
|
'preference': http_formats_preference,
|
||||||
})
|
})
|
||||||
progressive_formats.append(progressive_format)
|
progressive_formats.append(progressive_format)
|
||||||
self._check_formats(progressive_formats, media_item_id)
|
self._check_formats(progressive_formats, media_item_id)
|
||||||
|
@ -112,6 +112,8 @@ class PhantomJSwrapper(object):
|
|||||||
return get_exe_version('phantomjs', version_re=r'([0-9.]+)')
|
return get_exe_version('phantomjs', version_re=r'([0-9.]+)')
|
||||||
|
|
||||||
def __init__(self, extractor, required_version=None, timeout=10000):
|
def __init__(self, extractor, required_version=None, timeout=10000):
|
||||||
|
self._TMP_FILES = {}
|
||||||
|
|
||||||
self.exe = check_executable('phantomjs', ['-v'])
|
self.exe = check_executable('phantomjs', ['-v'])
|
||||||
if not self.exe:
|
if not self.exe:
|
||||||
raise ExtractorError('PhantomJS executable not found in PATH, '
|
raise ExtractorError('PhantomJS executable not found in PATH, '
|
||||||
@ -130,7 +132,6 @@ class PhantomJSwrapper(object):
|
|||||||
self.options = {
|
self.options = {
|
||||||
'timeout': timeout,
|
'timeout': timeout,
|
||||||
}
|
}
|
||||||
self._TMP_FILES = {}
|
|
||||||
for name in self._TMP_FILE_NAMES:
|
for name in self._TMP_FILE_NAMES:
|
||||||
tmp = tempfile.NamedTemporaryFile(delete=False)
|
tmp = tempfile.NamedTemporaryFile(delete=False)
|
||||||
tmp.close()
|
tmp.close()
|
||||||
@ -140,7 +141,7 @@ class PhantomJSwrapper(object):
|
|||||||
for name in self._TMP_FILE_NAMES:
|
for name in self._TMP_FILE_NAMES:
|
||||||
try:
|
try:
|
||||||
os.remove(self._TMP_FILES[name].name)
|
os.remove(self._TMP_FILES[name].name)
|
||||||
except:
|
except (IOError, OSError, KeyError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _save_cookies(self, url):
|
def _save_cookies(self, url):
|
||||||
@ -242,7 +243,7 @@ class PhantomJSwrapper(object):
|
|||||||
|
|
||||||
|
|
||||||
class OpenloadIE(InfoExtractor):
|
class OpenloadIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:openload\.(?:co|io)|oload\.tv)/(?:f|embed)/(?P<id>[a-zA-Z0-9-_]+)'
|
_VALID_URL = r'https?://(?:www\.)?(?:openload\.(?:co|io|link)|oload\.(?:tv|stream))/(?:f|embed)/(?P<id>[a-zA-Z0-9-_]+)'
|
||||||
|
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'https://openload.co/f/kUEfGclsU9o',
|
'url': 'https://openload.co/f/kUEfGclsU9o',
|
||||||
@ -283,9 +284,20 @@ class OpenloadIE(InfoExtractor):
|
|||||||
# for title and ext
|
# for title and ext
|
||||||
'url': 'https://openload.co/embed/Sxz5sADo82g/',
|
'url': 'https://openload.co/embed/Sxz5sADo82g/',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
# unavailable via https://openload.co/embed/e-Ixz9ZR5L0/ but available
|
||||||
|
# via https://openload.co/f/e-Ixz9ZR5L0/
|
||||||
|
'url': 'https://openload.co/f/e-Ixz9ZR5L0/',
|
||||||
|
'only_matching': True,
|
||||||
}, {
|
}, {
|
||||||
'url': 'https://oload.tv/embed/KnG-kKZdcfY/',
|
'url': 'https://oload.tv/embed/KnG-kKZdcfY/',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.openload.link/f/KnG-kKZdcfY',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://oload.stream/f/KnG-kKZdcfY',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
_USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36'
|
_USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36'
|
||||||
@ -298,20 +310,34 @@ class OpenloadIE(InfoExtractor):
|
|||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
url = 'https://openload.co/embed/%s/' % video_id
|
url_pattern = 'https://openload.co/%%s/%s/' % video_id
|
||||||
headers = {
|
headers = {
|
||||||
'User-Agent': self._USER_AGENT,
|
'User-Agent': self._USER_AGENT,
|
||||||
}
|
}
|
||||||
|
|
||||||
webpage = self._download_webpage(url, video_id, headers=headers)
|
for path in ('embed', 'f'):
|
||||||
|
page_url = url_pattern % path
|
||||||
|
last = path == 'f'
|
||||||
|
webpage = self._download_webpage(
|
||||||
|
page_url, video_id, 'Downloading %s webpage' % path,
|
||||||
|
headers=headers, fatal=last)
|
||||||
|
if not webpage:
|
||||||
|
continue
|
||||||
if 'File not found' in webpage or 'deleted by the owner' in webpage:
|
if 'File not found' in webpage or 'deleted by the owner' in webpage:
|
||||||
|
if not last:
|
||||||
|
continue
|
||||||
raise ExtractorError('File not found', expected=True, video_id=video_id)
|
raise ExtractorError('File not found', expected=True, video_id=video_id)
|
||||||
|
break
|
||||||
|
|
||||||
phantom = PhantomJSwrapper(self, required_version='2.0')
|
phantom = PhantomJSwrapper(self, required_version='2.0')
|
||||||
webpage, _ = phantom.get(url, html=webpage, video_id=video_id, headers=headers)
|
webpage, _ = phantom.get(page_url, html=webpage, video_id=video_id, headers=headers)
|
||||||
|
|
||||||
decoded_id = get_element_by_id('streamurl', webpage)
|
decoded_id = (get_element_by_id('streamurl', webpage) or
|
||||||
|
get_element_by_id('streamuri', webpage) or
|
||||||
|
get_element_by_id('streamurj', webpage))
|
||||||
|
|
||||||
|
if not decoded_id:
|
||||||
|
raise ExtractorError('Can\'t find stream URL', video_id=video_id)
|
||||||
|
|
||||||
video_url = 'https://openload.co/stream/%s?mime=true' % decoded_id
|
video_url = 'https://openload.co/stream/%s?mime=true' % decoded_id
|
||||||
|
|
||||||
@ -320,7 +346,7 @@ class OpenloadIE(InfoExtractor):
|
|||||||
'title', default=None) or self._html_search_meta(
|
'title', default=None) or self._html_search_meta(
|
||||||
'description', webpage, 'title', fatal=True)
|
'description', webpage, 'title', fatal=True)
|
||||||
|
|
||||||
entries = self._parse_html5_media_entries(url, webpage, video_id)
|
entries = self._parse_html5_media_entries(page_url, webpage, video_id)
|
||||||
entry = entries[0] if entries else {}
|
entry = entries[0] if entries else {}
|
||||||
subtitles = entry.get('subtitles')
|
subtitles = entry.get('subtitles')
|
||||||
|
|
||||||
|
@ -49,13 +49,13 @@ class ORFTVthekIE(InfoExtractor):
|
|||||||
'params': {
|
'params': {
|
||||||
'skip_download': True, # rtsp downloads
|
'skip_download': True, # rtsp downloads
|
||||||
},
|
},
|
||||||
'_skip': 'Blocked outside of Austria / Germany',
|
'skip': 'Blocked outside of Austria / Germany',
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://tvthek.orf.at/topic/Fluechtlingskrise/10463081/Heimat-Fremde-Heimat/13879132/Senioren-betreuen-Migrantenkinder/13879141',
|
'url': 'http://tvthek.orf.at/topic/Fluechtlingskrise/10463081/Heimat-Fremde-Heimat/13879132/Senioren-betreuen-Migrantenkinder/13879141',
|
||||||
'skip_download': True,
|
'only_matching': True,
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://tvthek.orf.at/profile/Universum/35429',
|
'url': 'http://tvthek.orf.at/profile/Universum/35429',
|
||||||
'skip_download': True,
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
|
@ -33,7 +33,7 @@ class PandaTVIE(InfoExtractor):
|
|||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
|
|
||||||
config = self._download_json(
|
config = self._download_json(
|
||||||
'https://www.panda.tv/api_room?roomid=%s' % video_id, video_id)
|
'https://www.panda.tv/api_room_v2?roomid=%s' % video_id, video_id)
|
||||||
|
|
||||||
error_code = config.get('errno', 0)
|
error_code = config.get('errno', 0)
|
||||||
if error_code is not 0:
|
if error_code is not 0:
|
||||||
@ -66,6 +66,11 @@ class PandaTVIE(InfoExtractor):
|
|||||||
plflag1 = '4'
|
plflag1 = '4'
|
||||||
live_panda = 'live_panda' if plflag0 < 1 else ''
|
live_panda = 'live_panda' if plflag0 < 1 else ''
|
||||||
|
|
||||||
|
plflag_auth = self._parse_json(video_info['plflag_list'], video_id)
|
||||||
|
sign = plflag_auth['auth']['sign']
|
||||||
|
ts = plflag_auth['auth']['time']
|
||||||
|
rid = plflag_auth['auth']['rid']
|
||||||
|
|
||||||
quality_key = qualities(['OD', 'HD', 'SD'])
|
quality_key = qualities(['OD', 'HD', 'SD'])
|
||||||
suffix = ['_small', '_mid', '']
|
suffix = ['_small', '_mid', '']
|
||||||
formats = []
|
formats = []
|
||||||
@ -77,8 +82,8 @@ class PandaTVIE(InfoExtractor):
|
|||||||
continue
|
continue
|
||||||
for pref, (ext, pl) in enumerate((('m3u8', '-hls'), ('flv', ''))):
|
for pref, (ext, pl) in enumerate((('m3u8', '-hls'), ('flv', ''))):
|
||||||
formats.append({
|
formats.append({
|
||||||
'url': 'https://pl%s%s.live.panda.tv/live_panda/%s%s%s.%s'
|
'url': 'https://pl%s%s.live.panda.tv/live_panda/%s%s%s.%s?sign=%s&ts=%s&rid=%s'
|
||||||
% (pl, plflag1, room_key, live_panda, suffix[quality], ext),
|
% (pl, plflag1, room_key, live_panda, suffix[quality], ext, sign, ts, rid),
|
||||||
'format_id': '%s-%s' % (k, ext),
|
'format_id': '%s-%s' % (k, ext),
|
||||||
'quality': quality,
|
'quality': quality,
|
||||||
'source_preference': pref,
|
'source_preference': pref,
|
||||||
|
@ -67,7 +67,7 @@ class PatreonIE(InfoExtractor):
|
|||||||
'https://www.patreon.com/processLogin',
|
'https://www.patreon.com/processLogin',
|
||||||
compat_urllib_parse_urlencode(login_form).encode('utf-8')
|
compat_urllib_parse_urlencode(login_form).encode('utf-8')
|
||||||
)
|
)
|
||||||
login_page = self._download_webpage(request, None, note='Logging in as %s' % username)
|
login_page = self._download_webpage(request, None, note='Logging in')
|
||||||
|
|
||||||
if re.search(r'onLoginFailed', login_page):
|
if re.search(r'onLoginFailed', login_page):
|
||||||
raise ExtractorError('Unable to login, incorrect username and/or password', expected=True)
|
raise ExtractorError('Unable to login, incorrect username and/or password', expected=True)
|
||||||
|
@ -421,6 +421,7 @@ class PBSIE(InfoExtractor):
|
|||||||
r'class="coveplayerid">([^<]+)<', # coveplayer
|
r'class="coveplayerid">([^<]+)<', # coveplayer
|
||||||
r'<section[^>]+data-coveid="(\d+)"', # coveplayer from http://www.pbs.org/wgbh/frontline/film/real-csi/
|
r'<section[^>]+data-coveid="(\d+)"', # coveplayer from http://www.pbs.org/wgbh/frontline/film/real-csi/
|
||||||
r'<input type="hidden" id="pbs_video_id_[0-9]+" value="([0-9]+)"/>', # jwplayer
|
r'<input type="hidden" id="pbs_video_id_[0-9]+" value="([0-9]+)"/>', # jwplayer
|
||||||
|
r"(?s)window\.PBS\.playerConfig\s*=\s*{.*?id\s*:\s*'([0-9]+)',",
|
||||||
]
|
]
|
||||||
|
|
||||||
media_id = self._search_regex(
|
media_id = self._search_regex(
|
||||||
|
83
youtube_dl/extractor/performgroup.py
Normal file
83
youtube_dl/extractor/performgroup.py
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import int_or_none
|
||||||
|
|
||||||
|
|
||||||
|
class PerformGroupIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://player\.performgroup\.com/eplayer(?:/eplayer\.html|\.js)#/?(?P<id>[0-9a-f]{26})\.(?P<auth_token>[0-9a-z]{26})'
|
||||||
|
_TESTS = [{
|
||||||
|
# http://www.faz.net/aktuell/sport/fussball/wm-2018-playoffs-schweiz-besiegt-nordirland-1-0-15286104.html
|
||||||
|
'url': 'http://player.performgroup.com/eplayer/eplayer.html#d478c41c5d192f56b9aa859de8.1w4crrej5w14e1ed4s1ce4ykab',
|
||||||
|
'md5': '259cb03d142e2e52471e8837ecacb29f',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'xgrwobuzumes1lwjxtcdpwgxd',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Liga MX: Keine Einsicht nach Horrorfoul',
|
||||||
|
'description': 'md5:7cd3b459c82725b021e046ab10bf1c5b',
|
||||||
|
'timestamp': 1511533477,
|
||||||
|
'upload_date': '20171124',
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _call_api(self, service, auth_token, content_id, referer_url):
|
||||||
|
return self._download_json(
|
||||||
|
'http://ep3.performfeeds.com/ep%s/%s/%s/' % (service, auth_token, content_id),
|
||||||
|
content_id, headers={
|
||||||
|
'Referer': referer_url,
|
||||||
|
'Origin': 'http://player.performgroup.com',
|
||||||
|
}, query={
|
||||||
|
'_fmt': 'json',
|
||||||
|
})
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
player_id, auth_token = re.search(self._VALID_URL, url).groups()
|
||||||
|
bootstrap = self._call_api('bootstrap', auth_token, player_id, url)
|
||||||
|
video = bootstrap['config']['dataSource']['sourceItems'][0]['videos'][0]
|
||||||
|
video_id = video['uuid']
|
||||||
|
vod = self._call_api('vod', auth_token, video_id, url)
|
||||||
|
media = vod['videos']['video'][0]['media']
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
hls_url = media.get('hls', {}).get('url')
|
||||||
|
if hls_url:
|
||||||
|
formats.extend(self._extract_m3u8_formats(hls_url, video_id, 'mp4', 'm3u8_native', m3u8_id='hls', fatal=False))
|
||||||
|
|
||||||
|
hds_url = media.get('hds', {}).get('url')
|
||||||
|
if hds_url:
|
||||||
|
formats.extend(self._extract_f4m_formats(hds_url + '?hdcore', video_id, f4m_id='hds', fatal=False))
|
||||||
|
|
||||||
|
for c in media.get('content', []):
|
||||||
|
c_url = c.get('url')
|
||||||
|
if not c_url:
|
||||||
|
continue
|
||||||
|
tbr = int_or_none(c.get('bitrate'), 1000)
|
||||||
|
format_id = 'http'
|
||||||
|
if tbr:
|
||||||
|
format_id += '-%d' % tbr
|
||||||
|
formats.append({
|
||||||
|
'format_id': format_id,
|
||||||
|
'url': c_url,
|
||||||
|
'tbr': tbr,
|
||||||
|
'width': int_or_none(c.get('width')),
|
||||||
|
'height': int_or_none(c.get('height')),
|
||||||
|
'filesize': int_or_none(c.get('fileSize')),
|
||||||
|
'vcodec': c.get('type'),
|
||||||
|
'fps': int_or_none(c.get('videoFrameRate')),
|
||||||
|
'vbr': int_or_none(c.get('videoRate'), 1000),
|
||||||
|
'abr': int_or_none(c.get('audioRate'), 1000),
|
||||||
|
})
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': video['title'],
|
||||||
|
'description': video.get('description'),
|
||||||
|
'thumbnail': video.get('poster'),
|
||||||
|
'duration': int_or_none(video.get('duration')),
|
||||||
|
'timestamp': int_or_none(video.get('publishedTime'), 1000),
|
||||||
|
'formats': formats,
|
||||||
|
}
|
@ -24,7 +24,7 @@ class PlaytvakIE(InfoExtractor):
|
|||||||
'id': 'A150730_150323_hodinovy-manzel_kuko',
|
'id': 'A150730_150323_hodinovy-manzel_kuko',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Vyžeňte vosy a sršně ze zahrady',
|
'title': 'Vyžeňte vosy a sršně ze zahrady',
|
||||||
'description': 'md5:f93d398691044d303bc4a3de62f3e976',
|
'description': 'md5:4436e61b7df227a093778efb7e373571',
|
||||||
'thumbnail': r're:(?i)^https?://.*\.(?:jpg|png)$',
|
'thumbnail': r're:(?i)^https?://.*\.(?:jpg|png)$',
|
||||||
'duration': 279,
|
'duration': 279,
|
||||||
'timestamp': 1438732860,
|
'timestamp': 1438732860,
|
||||||
@ -36,9 +36,19 @@ class PlaytvakIE(InfoExtractor):
|
|||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'A150624_164934_planespotting_cat',
|
'id': 'A150624_164934_planespotting_cat',
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
'title': 're:^Přímý přenos iDNES.cz [0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}$',
|
'title': 're:^Planespotting [0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}$',
|
||||||
'description': 'Sledujte provoz na ranveji Letiště Václava Havla v Praze',
|
'description': 'Sledujte provoz na ranveji Letiště Václava Havla v Praze',
|
||||||
'thumbnail': r're:(?i)^https?://.*\.(?:jpg|png)$',
|
'is_live': True,
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
'skip_download': True, # requires rtmpdump
|
||||||
|
},
|
||||||
|
}, { # another live stream, this one without Misc.videoFLV
|
||||||
|
'url': 'https://slowtv.playtvak.cz/zive-sledujte-vlaky-v-primem-prenosu-dwi-/hlavni-nadrazi.aspx?c=A151218_145728_hlavni-nadrazi_plap',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'A151218_145728_hlavni-nadrazi_plap',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 're:^Hlavní nádraží [0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}$',
|
||||||
'is_live': True,
|
'is_live': True,
|
||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
@ -95,7 +105,7 @@ class PlaytvakIE(InfoExtractor):
|
|||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
info_url = self._html_search_regex(
|
info_url = self._html_search_regex(
|
||||||
r'Misc\.videoFLV\(\s*{\s*data\s*:\s*"([^"]+)"', webpage, 'info url')
|
r'Misc\.video(?:FLV)?\(\s*{\s*data\s*:\s*"([^"]+)"', webpage, 'info url')
|
||||||
|
|
||||||
parsed_url = compat_urlparse.urlparse(info_url)
|
parsed_url = compat_urlparse.urlparse(info_url)
|
||||||
|
|
||||||
@ -160,7 +170,7 @@ class PlaytvakIE(InfoExtractor):
|
|||||||
if is_live:
|
if is_live:
|
||||||
title = self._live_title(title)
|
title = self._live_title(title)
|
||||||
description = self._og_search_description(webpage, default=None) or self._html_search_meta(
|
description = self._og_search_description(webpage, default=None) or self._html_search_meta(
|
||||||
'description', webpage, 'description')
|
'description', webpage, 'description', default=None)
|
||||||
timestamp = None
|
timestamp = None
|
||||||
duration = None
|
duration = None
|
||||||
if not is_live:
|
if not is_live:
|
||||||
|
@ -116,7 +116,7 @@ class PluralsightIE(PluralsightBaseIE):
|
|||||||
post_url = compat_urlparse.urljoin(self._LOGIN_URL, post_url)
|
post_url = compat_urlparse.urljoin(self._LOGIN_URL, post_url)
|
||||||
|
|
||||||
response = self._download_webpage(
|
response = self._download_webpage(
|
||||||
post_url, None, 'Logging in as %s' % username,
|
post_url, None, 'Logging in',
|
||||||
data=urlencode_postdata(login_form),
|
data=urlencode_postdata(login_form),
|
||||||
headers={'Content-Type': 'application/x-www-form-urlencoded'})
|
headers={'Content-Type': 'application/x-www-form-urlencoded'})
|
||||||
|
|
||||||
@ -131,6 +131,13 @@ class PluralsightIE(PluralsightBaseIE):
|
|||||||
if BLOCKED in response:
|
if BLOCKED in response:
|
||||||
raise ExtractorError(
|
raise ExtractorError(
|
||||||
'Unable to login: %s' % BLOCKED, expected=True)
|
'Unable to login: %s' % BLOCKED, expected=True)
|
||||||
|
MUST_AGREE = 'To continue using Pluralsight, you must agree to'
|
||||||
|
if any(p in response for p in (MUST_AGREE, '>Disagree<', '>Agree<')):
|
||||||
|
raise ExtractorError(
|
||||||
|
'Unable to login: %s some documents. Go to pluralsight.com, '
|
||||||
|
'log in and agree with what Pluralsight requires.'
|
||||||
|
% MUST_AGREE, expected=True)
|
||||||
|
|
||||||
raise ExtractorError('Unable to log in')
|
raise ExtractorError('Unable to log in')
|
||||||
|
|
||||||
def _get_subtitles(self, author, clip_id, lang, name, duration, video_id):
|
def _get_subtitles(self, author, clip_id, lang, name, duration, video_id):
|
||||||
@ -164,12 +171,12 @@ class PluralsightIE(PluralsightBaseIE):
|
|||||||
for num, current in enumerate(subs):
|
for num, current in enumerate(subs):
|
||||||
current = subs[num]
|
current = subs[num]
|
||||||
start, text = (
|
start, text = (
|
||||||
float_or_none(dict_get(current, TIME_OFFSET_KEYS)),
|
float_or_none(dict_get(current, TIME_OFFSET_KEYS, skip_false_values=False)),
|
||||||
dict_get(current, TEXT_KEYS))
|
dict_get(current, TEXT_KEYS))
|
||||||
if start is None or text is None:
|
if start is None or text is None:
|
||||||
continue
|
continue
|
||||||
end = duration if num == len(subs) - 1 else float_or_none(
|
end = duration if num == len(subs) - 1 else float_or_none(
|
||||||
dict_get(subs[num + 1], TIME_OFFSET_KEYS))
|
dict_get(subs[num + 1], TIME_OFFSET_KEYS, skip_false_values=False))
|
||||||
if end is None:
|
if end is None:
|
||||||
continue
|
continue
|
||||||
srt += os.linesep.join(
|
srt += os.linesep.join(
|
||||||
|
@ -77,12 +77,14 @@ class PornComIE(InfoExtractor):
|
|||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
view_count = str_to_int(self._search_regex(
|
view_count = str_to_int(self._search_regex(
|
||||||
r'class=["\']views["\'][^>]*><p>([\d,.]+)', webpage,
|
(r'Views:\s*</span>\s*<span>\s*([\d,.]+)',
|
||||||
|
r'class=["\']views["\'][^>]*><p>([\d,.]+)'), webpage,
|
||||||
'view count', fatal=False))
|
'view count', fatal=False))
|
||||||
|
|
||||||
def extract_list(kind):
|
def extract_list(kind):
|
||||||
s = self._search_regex(
|
s = self._search_regex(
|
||||||
r'(?s)<p[^>]*>%s:(.+?)</p>' % kind.capitalize(),
|
(r'(?s)%s:\s*</span>\s*<span>(.+?)</span>' % kind.capitalize(),
|
||||||
|
r'(?s)<p[^>]*>%s:(.+?)</p>' % kind.capitalize()),
|
||||||
webpage, kind, fatal=False)
|
webpage, kind, fatal=False)
|
||||||
return re.findall(r'<a[^>]+>([^<]+)</a>', s or '')
|
return re.findall(r'<a[^>]+>([^<]+)</a>', s or '')
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ from ..utils import (
|
|||||||
parse_duration,
|
parse_duration,
|
||||||
strip_or_none,
|
strip_or_none,
|
||||||
try_get,
|
try_get,
|
||||||
|
unescapeHTML,
|
||||||
unified_strdate,
|
unified_strdate,
|
||||||
unified_timestamp,
|
unified_timestamp,
|
||||||
update_url_query,
|
update_url_query,
|
||||||
@ -249,6 +250,41 @@ class RaiPlayLiveIE(RaiBaseIE):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class RaiPlayPlaylistIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?raiplay\.it/programmi/(?P<id>[^/?#&]+)'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://www.raiplay.it/programmi/nondirloalmiocapo/',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'nondirloalmiocapo',
|
||||||
|
'title': 'Non dirlo al mio capo',
|
||||||
|
'description': 'md5:9f3d603b2947c1c7abb098f3b14fac86',
|
||||||
|
},
|
||||||
|
'playlist_mincount': 12,
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
playlist_id = self._match_id(url)
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, playlist_id)
|
||||||
|
|
||||||
|
title = self._html_search_meta(
|
||||||
|
('programma', 'nomeProgramma'), webpage, 'title')
|
||||||
|
description = unescapeHTML(self._html_search_meta(
|
||||||
|
('description', 'og:description'), webpage, 'description'))
|
||||||
|
print(description)
|
||||||
|
|
||||||
|
entries = []
|
||||||
|
for mobj in re.finditer(
|
||||||
|
r'<a\b[^>]+\bhref=(["\'])(?P<path>/raiplay/video/.+?)\1',
|
||||||
|
webpage):
|
||||||
|
video_url = urljoin(url, mobj.group('path'))
|
||||||
|
entries.append(self.url_result(
|
||||||
|
video_url, ie=RaiPlayIE.ie_key(),
|
||||||
|
video_id=RaiPlayIE._match_id(video_url)))
|
||||||
|
|
||||||
|
return self.playlist_result(entries, playlist_id, title, description)
|
||||||
|
|
||||||
|
|
||||||
class RaiIE(RaiBaseIE):
|
class RaiIE(RaiBaseIE):
|
||||||
_VALID_URL = r'https?://[^/]+\.(?:rai\.(?:it|tv)|rainews\.it)/dl/.+?-(?P<id>%s)(?:-.+?)?\.html' % RaiBaseIE._UUID_RE
|
_VALID_URL = r'https?://[^/]+\.(?:rai\.(?:it|tv)|rainews\.it)/dl/.+?-(?P<id>%s)(?:-.+?)?\.html' % RaiBaseIE._UUID_RE
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
|
@ -68,7 +68,7 @@ class RoosterTeethIE(InfoExtractor):
|
|||||||
|
|
||||||
login_request = self._download_webpage(
|
login_request = self._download_webpage(
|
||||||
self._LOGIN_URL, None,
|
self._LOGIN_URL, None,
|
||||||
note='Logging in as %s' % username,
|
note='Logging in',
|
||||||
data=urlencode_postdata(login_form),
|
data=urlencode_postdata(login_form),
|
||||||
headers={
|
headers={
|
||||||
'Referer': self._LOGIN_URL,
|
'Referer': self._LOGIN_URL,
|
||||||
|
@ -21,7 +21,7 @@ class RozhlasIE(InfoExtractor):
|
|||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://prehravac.rozhlas.cz/audio/3421320/embed',
|
'url': 'http://prehravac.rozhlas.cz/audio/3421320/embed',
|
||||||
'skip_download': True,
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
|
@ -31,6 +31,9 @@ def _decrypt_url(png):
|
|||||||
hash_index = data.index('#')
|
hash_index = data.index('#')
|
||||||
alphabet_data = data[:hash_index]
|
alphabet_data = data[:hash_index]
|
||||||
url_data = data[hash_index + 1:]
|
url_data = data[hash_index + 1:]
|
||||||
|
if url_data[0] == 'H' and url_data[3] == '%':
|
||||||
|
# remove useless HQ%% at the start
|
||||||
|
url_data = url_data[4:]
|
||||||
|
|
||||||
alphabet = []
|
alphabet = []
|
||||||
e = 0
|
e = 0
|
||||||
|
@ -61,7 +61,7 @@ class SafariBaseIE(InfoExtractor):
|
|||||||
request = sanitized_Request(
|
request = sanitized_Request(
|
||||||
self._LOGIN_URL, urlencode_postdata(login_form), headers=headers)
|
self._LOGIN_URL, urlencode_postdata(login_form), headers=headers)
|
||||||
login_page = self._download_webpage(
|
login_page = self._download_webpage(
|
||||||
request, None, 'Logging in as %s' % username)
|
request, None, 'Logging in')
|
||||||
|
|
||||||
if not is_logged(login_page):
|
if not is_logged(login_page):
|
||||||
raise ExtractorError(
|
raise ExtractorError(
|
||||||
|
@ -1,65 +0,0 @@
|
|||||||
# coding: utf-8
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import json
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
|
||||||
from ..utils import (
|
|
||||||
int_or_none,
|
|
||||||
mimetype2ext,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class SandiaIE(InfoExtractor):
|
|
||||||
IE_DESC = 'Sandia National Laboratories'
|
|
||||||
_VALID_URL = r'https?://digitalops\.sandia\.gov/Mediasite/Play/(?P<id>[0-9a-f]+)'
|
|
||||||
_TEST = {
|
|
||||||
'url': 'http://digitalops.sandia.gov/Mediasite/Play/24aace4429fc450fb5b38cdbf424a66e1d',
|
|
||||||
'md5': '9422edc9b9a60151727e4b6d8bef393d',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '24aace4429fc450fb5b38cdbf424a66e1d',
|
|
||||||
'ext': 'mp4',
|
|
||||||
'title': 'Xyce Software Training - Section 1',
|
|
||||||
'description': 're:(?s)SAND Number: SAND 2013-7800.{200,}',
|
|
||||||
'upload_date': '20120409',
|
|
||||||
'timestamp': 1333983600,
|
|
||||||
'duration': 7794,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
|
||||||
video_id = self._match_id(url)
|
|
||||||
|
|
||||||
presentation_data = self._download_json(
|
|
||||||
'http://digitalops.sandia.gov/Mediasite/PlayerService/PlayerService.svc/json/GetPlayerOptions',
|
|
||||||
video_id, data=json.dumps({
|
|
||||||
'getPlayerOptionsRequest': {
|
|
||||||
'ResourceId': video_id,
|
|
||||||
'QueryString': '',
|
|
||||||
}
|
|
||||||
}), headers={
|
|
||||||
'Content-Type': 'application/json; charset=utf-8',
|
|
||||||
})['d']['Presentation']
|
|
||||||
|
|
||||||
title = presentation_data['Title']
|
|
||||||
|
|
||||||
formats = []
|
|
||||||
for stream in presentation_data.get('Streams', []):
|
|
||||||
for fd in stream.get('VideoUrls', []):
|
|
||||||
formats.append({
|
|
||||||
'format_id': fd['MediaType'],
|
|
||||||
'format_note': fd['MimeType'].partition('/')[2],
|
|
||||||
'ext': mimetype2ext(fd['MimeType']),
|
|
||||||
'url': fd['Location'],
|
|
||||||
'protocol': 'f4m' if fd['MimeType'] == 'video/x-mp4-fragmented' else None,
|
|
||||||
})
|
|
||||||
self._sort_formats(formats)
|
|
||||||
|
|
||||||
return {
|
|
||||||
'id': video_id,
|
|
||||||
'title': title,
|
|
||||||
'description': presentation_data.get('Description'),
|
|
||||||
'formats': formats,
|
|
||||||
'timestamp': int_or_none(presentation_data.get('UnixTime'), 1000),
|
|
||||||
'duration': int_or_none(presentation_data.get('Duration'), 1000),
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user