Compare commits
444 Commits
2015.12.31
...
2016.02.09
Author | SHA1 | Date | |
---|---|---|---|
|
085ad71157 | ||
|
35972ba172 | ||
|
3834d3e35c | ||
|
8d0a2a2a4e | ||
|
11c0339bec | ||
|
915dd77783 | ||
|
b6bfa6fb79 | ||
|
f070197bd7 | ||
|
5a7699bb2e | ||
|
8628d26f38 | ||
|
8411229bd5 | ||
|
72b9ebc65d | ||
|
3b799ca14c | ||
|
0474512e30 | ||
|
f0905c6ec3 | ||
|
86296ad2cd | ||
|
52f5889f77 | ||
|
81e0b4f2d1 | ||
|
cbecc9b903 | ||
|
b8b465af3e | ||
|
59b35c6745 | ||
|
7032833011 | ||
|
f406c78785 | ||
|
f326b5837a | ||
|
5dd4b3468f | ||
|
d4f8e83404 | ||
|
7b8b007cd9 | ||
|
3547d26587 | ||
|
7e62c2eb6d | ||
|
56401e1e5f | ||
|
860db2d508 | ||
|
4b8874975c | ||
|
bd6b6f6622 | ||
|
4340727e6c | ||
|
3ceccade87 | ||
|
28ad7df65d | ||
|
79a3508579 | ||
|
1b840245bd | ||
|
6a3828fddd | ||
|
91cb6b5065 | ||
|
0826a0b555 | ||
|
bcbbb98bfe | ||
|
66159b38aa | ||
|
23d17e4beb | ||
|
d97b0e3241 | ||
|
eb2533ec4c | ||
|
b7b365067f | ||
|
86e284e028 | ||
|
d9e543b680 | ||
|
c773c232d8 | ||
|
58ae24336a | ||
|
7d3a035ee0 | ||
|
e06e75c7e7 | ||
|
593e0f43b4 | ||
|
008ab0f814 | ||
|
3f7e8750d4 | ||
|
f1ed3acae5 | ||
|
920d21b9d3 | ||
|
2fb35d1c28 | ||
|
09be85b8dd | ||
|
eadc3ccd50 | ||
|
255732f0d3 | ||
|
53c269c6fd | ||
|
675d001633 | ||
|
58be922079 | ||
|
c84d3a557d | ||
|
d577c79632 | ||
|
6ad2b01e14 | ||
|
fd3a1f3d60 | ||
|
87de7069b9 | ||
|
6fba62c87a | ||
|
f14be22816 | ||
|
1df4141196 | ||
|
fae45ede08 | ||
|
4e0cff2a50 | ||
|
9c74423510 | ||
|
5976e7ab57 | ||
|
a1a22572fb | ||
|
c11875b328 | ||
|
8ff648e4f9 | ||
|
1bac34556f | ||
|
0436157b95 | ||
|
ae0db349c1 | ||
|
08411970d5 | ||
|
dc724e0c8b | ||
|
0a5d1ec706 | ||
|
58250eff2b | ||
|
11a4efc505 | ||
|
7537b35fb8 | ||
|
33cc74eeeb | ||
|
f021acee49 | ||
|
abe694ca95 | ||
|
b286f201a8 | ||
|
bd93a12e85 | ||
|
92769650fa | ||
|
dc4fe5c6d7 | ||
|
566bda51f2 | ||
|
f63757ec35 | ||
|
7a0ed06909 | ||
|
9934fe76be | ||
|
a8aad21001 | ||
|
d055bf91cc | ||
|
0e1b1a011d | ||
|
eab3c2895c | ||
|
163da6a484 | ||
|
324916d11a | ||
|
3ccb0655c1 | ||
|
e04398e397 | ||
|
231ea2a3bb | ||
|
b99d88c6a1 | ||
|
189d72d5fd | ||
|
a7aab0c23e | ||
|
a69bee4762 | ||
|
9acd33094d | ||
|
8e7aad2075 | ||
|
ce5879fa14 | ||
|
7b7507d6e1 | ||
|
14823decf3 | ||
|
673fb82e65 | ||
|
181cf24bc0 | ||
|
89f2602880 | ||
|
db9b1dbcd9 | ||
|
e881c4bcab | ||
|
670ad51ade | ||
|
eb6fc7d32a | ||
|
ed1a390583 | ||
|
809e1857c5 | ||
|
7c38af48b9 | ||
|
60ad3eb970 | ||
|
a7685b3a6b | ||
|
8f1fddc816 | ||
|
1bf996fa5c | ||
|
248ae880b6 | ||
|
2d2fa82d17 | ||
|
c94678957f | ||
|
16f38a699f | ||
|
a6c2c24479 | ||
|
b8c9926c0a | ||
|
df374b5222 | ||
|
5ea1eb78f5 | ||
|
5d2c0fd9ba | ||
|
0803753fea | ||
|
2c2f1efdcd | ||
|
b323e1707d | ||
|
09104e9930 | ||
|
5fa1702ca6 | ||
|
17b598d30c | ||
|
53be8894e4 | ||
|
c3deacd562 | ||
|
8ab3fe81d8 | ||
|
2f0a33d8a3 | ||
|
05d0d131a7 | ||
|
c140629995 | ||
|
7d106a65ca | ||
|
0179f6a830 | ||
|
830afe85dc | ||
|
8bf39420b4 | ||
|
71d08b3e29 | ||
|
06ffa33485 | ||
|
874e05975b | ||
|
f5d30d521c | ||
|
e047922be0 | ||
|
83ab8a79cc | ||
|
350cf045d8 | ||
|
68a0ea15b4 | ||
|
2b4f5e68d1 | ||
|
055f417278 | ||
|
70029bc348 | ||
|
cf57433bbd | ||
|
1ac6e794cb | ||
|
a853427427 | ||
|
50e989e263 | ||
|
10e6ed9341 | ||
|
38c84acae5 | ||
|
29f46c2bee | ||
|
39c10a2b6e | ||
|
b913348d5f | ||
|
2b14cb566f | ||
|
b0df5223be | ||
|
ed7cd1e859 | ||
|
f125d9115b | ||
|
a9d5f12fec | ||
|
7f32e5dc35 | ||
|
c3111ab34f | ||
|
9339774af2 | ||
|
b0d21deda9 | ||
|
fab6f0e65b | ||
|
b6c33fd544 | ||
|
fb4b345800 | ||
|
af9c2a07ae | ||
|
ab180fc648 | ||
|
682f8c43b5 | ||
|
f693213567 | ||
|
9165d6bab9 | ||
|
2975fe1a7b | ||
|
de691a498d | ||
|
2e6e742c3c | ||
|
e9bd0f772b | ||
|
77f785076f | ||
|
94278f7202 | ||
|
a0d8d704df | ||
|
f6861ec96f | ||
|
f733b05302 | ||
|
6fa73386cb | ||
|
5ca01bb9e4 | ||
|
1ca59daca9 | ||
|
594c4d79a5 | ||
|
1f16b958b1 | ||
|
4c0d13df9b | ||
|
b2c6528baf | ||
|
ea17820432 | ||
|
1257b049bc | ||
|
b969813548 | ||
|
10677ece81 | ||
|
d570746e45 | ||
|
4fcd9d147d | ||
|
9c54ae3387 | ||
|
24114fee74 | ||
|
220ee33f2b | ||
|
4118cc02c1 | ||
|
32d77eeb04 | ||
|
032f232626 | ||
|
4d318be195 | ||
|
6b45f9aba2 | ||
|
1e10d02fec | ||
|
51290d8457 | ||
|
582f4f834e | ||
|
e87d98b0dd | ||
|
383496e65e | ||
|
4519c1f43c | ||
|
a616f65471 | ||
|
1f78ed189a | ||
|
7dde358adc | ||
|
27b83249c9 | ||
|
56aa074538 | ||
|
9d90e7de03 | ||
|
7d4d9c526a | ||
|
fe6856b059 | ||
|
a54fbf2ca6 | ||
|
d8024aebe5 | ||
|
8652bd22f1 | ||
|
f15a9ca301 | ||
|
65ced034b8 | ||
|
bec30224ff | ||
|
0428106da3 | ||
|
73e7442456 | ||
|
26de1bba83 | ||
|
e0690782b8 | ||
|
8fff4f61e5 | ||
|
10defdd06a | ||
|
485139c15c | ||
|
b605ebb609 | ||
|
aecfcd4e59 | ||
|
942d46196f | ||
|
78be2eca7c | ||
|
1fa2b9841d | ||
|
9fbd0822aa | ||
|
e323cf3ff3 | ||
|
8ceabd4df3 | ||
|
a8776b107b | ||
|
096b533982 | ||
|
dae503afaa | ||
|
b39eab7f94 | ||
|
e5a66240c0 | ||
|
e0ef13ddeb | ||
|
855f90fa6f | ||
|
614db89ae3 | ||
|
1358b94163 | ||
|
350e02d40d | ||
|
0b26ba3fc8 | ||
|
3a0a78731b | ||
|
6be16ed24b | ||
|
b555942428 | ||
|
b2dca40d81 | ||
|
15870bbd01 | ||
|
10d33b3473 | ||
|
ac25992bc7 | ||
|
30783c442d | ||
|
a50a8003a0 | ||
|
315bdae00a | ||
|
2ddfd26f1b | ||
|
f3ed5df611 | ||
|
b4e44234bc | ||
|
4ca2a3cf3c | ||
|
33d2fc2f64 | ||
|
27a95f51aa | ||
|
a78d6a9bb1 | ||
|
567f9a5809 | ||
|
3a421c724f | ||
|
34dd81c03a | ||
|
b3f502cdb9 | ||
|
587dfd44a4 | ||
|
52767c1ba0 | ||
|
014b5c59d8 | ||
|
fad7a336a1 | ||
|
ffbc0baf72 | ||
|
345f12196c | ||
|
5769b68bc0 | ||
|
4e2743abd9 | ||
|
be2d40a58a | ||
|
81549898c0 | ||
|
0baedd1851 | ||
|
6b559c2fbc | ||
|
986986064e | ||
|
4654c1d016 | ||
|
163e8369b0 | ||
|
5cc9c5dfa8 | ||
|
fbd90643cb | ||
|
30e2f2d76f | ||
|
11c60089a8 | ||
|
abb893e6e4 | ||
|
4511c1976d | ||
|
4240d50496 | ||
|
6240b0a278 | ||
|
e37afbe0b8 | ||
|
40cf7fcbd2 | ||
|
cc28492d31 | ||
|
bc0550c262 | ||
|
b83b782dc4 | ||
|
16a348475c | ||
|
709185a264 | ||
|
9cb1a06b6c | ||
|
be27283ef6 | ||
|
b924bfad68 | ||
|
192b9a571c | ||
|
6ec6cb4e95 | ||
|
36a0e46c39 | ||
|
dfb1b1468c | ||
|
3c91e41614 | ||
|
7e8a800f29 | ||
|
2334762b03 | ||
|
3fc088f8c7 | ||
|
a9bbd26f1d | ||
|
6e99d5762a | ||
|
15b1c6656f | ||
|
d412794205 | ||
|
0a899a1448 | ||
|
7a34302e95 | ||
|
27783821af | ||
|
b374af6ebd | ||
|
16f1131a4d | ||
|
d5f071afb5 | ||
|
14b4f038c0 | ||
|
bcac2a0710 | ||
|
1a6d92847f | ||
|
6a16fd4a1a | ||
|
44731e308c | ||
|
4763b624a6 | ||
|
6609b3ce37 | ||
|
7e182627d9 | ||
|
5777f5d386 | ||
|
5dbe81a1d3 | ||
|
4cf096a4a9 | ||
|
18e6c97c48 | ||
|
97afd99a18 | ||
|
23f13e9754 | ||
|
2e02ecbccc | ||
|
e4f49a8753 | ||
|
51d3045de2 | ||
|
76048b23e8 | ||
|
f20756fb10 | ||
|
17b2d7ca77 | ||
|
40f796288a | ||
|
2f546d0a3c | ||
|
18c782ab26 | ||
|
33cee6c7f6 | ||
|
a2e51e7b49 | ||
|
bd19aa0ed3 | ||
|
8f4c56f334 | ||
|
1dcc38b233 | ||
|
fff79f1867 | ||
|
3f17c357d9 | ||
|
9938a17f92 | ||
|
9746f4314a | ||
|
0238451fc0 | ||
|
2098aee7d6 | ||
|
fb588f6a56 | ||
|
896c7a23cd | ||
|
1463c5b9ac | ||
|
c6270b2ed5 | ||
|
ab3176af34 | ||
|
5aa535c329 | ||
|
133b1886fc | ||
|
66295fa4a6 | ||
|
e54c44eeab | ||
|
a7aaa39863 | ||
|
ea6abd740f | ||
|
e1a0bfdffe | ||
|
3f3343cd3e | ||
|
4059eabd58 | ||
|
6b46102661 | ||
|
141a273a8b | ||
|
2fffb1dcd0 | ||
|
e698e4e533 | ||
|
b7546397f0 | ||
|
0311677258 | ||
|
88fb59d91b | ||
|
a1d9f6c5dc | ||
|
c579c5e967 | ||
|
c9c194053d | ||
|
f20a11ed25 | ||
|
76a353c9e5 | ||
|
392f04d586 | ||
|
94de6cf59c | ||
|
8af2804a5d | ||
|
054479754c | ||
|
5bafcf6525 | ||
|
306c51c669 | ||
|
27bfd4e526 | ||
|
ca227c8698 | ||
|
32f9036447 | ||
|
190ef07981 | ||
|
82597f0ec0 | ||
|
8499d21158 | ||
|
c9154514c4 | ||
|
0d5095fc65 | ||
|
034caf70b2 | ||
|
e565cf6048 | ||
|
59f197aec1 | ||
|
a0e5beb0fb | ||
|
c1e90619bd | ||
|
fec09bf15d | ||
|
a0d7ede350 | ||
|
b26afec81f | ||
|
8f7c4f7d2e | ||
|
0416006a30 | ||
|
7f9134fb2d | ||
|
91e274546c | ||
|
69f8595256 | ||
|
930087f2f6 | ||
|
9f9f7664b5 | ||
|
72528252e3 | ||
|
e4bd63f9c0 | ||
|
9accfed4e7 | ||
|
f1e21efe63 | ||
|
b05641ce40 | ||
|
fec040e754 | ||
|
34a9da136f | ||
|
c43fda4c1a | ||
|
7a0b07c719 | ||
|
4d402db521 | ||
|
7109903e61 | ||
|
5f432ac8f5 | ||
|
957e0db1d2 |
7
AUTHORS
7
AUTHORS
@@ -150,3 +150,10 @@ reiv
|
|||||||
Muratcan Simsek
|
Muratcan Simsek
|
||||||
Evan Lu
|
Evan Lu
|
||||||
flatgreen
|
flatgreen
|
||||||
|
Brian Foley
|
||||||
|
Vignesh Venkat
|
||||||
|
Tom Gijselinck
|
||||||
|
Founder Fang
|
||||||
|
Andrew Alexeyew
|
||||||
|
Saso Bezlaj
|
||||||
|
Erwin de Haan
|
||||||
|
177
README.md
177
README.md
@@ -173,6 +173,10 @@ which means you can modify it, redistribute it or use it however you like.
|
|||||||
expected filesize (experimental)
|
expected filesize (experimental)
|
||||||
--hls-prefer-native Use the native HLS downloader instead of
|
--hls-prefer-native Use the native HLS downloader instead of
|
||||||
ffmpeg (experimental)
|
ffmpeg (experimental)
|
||||||
|
--hls-use-mpegts Use the mpegts container for HLS videos,
|
||||||
|
allowing to play the video while
|
||||||
|
downloading (some players may not be able
|
||||||
|
to play it)
|
||||||
--external-downloader COMMAND Use the specified external downloader.
|
--external-downloader COMMAND Use the specified external downloader.
|
||||||
Currently supports
|
Currently supports
|
||||||
aria2c,axel,curl,httpie,wget
|
aria2c,axel,curl,httpie,wget
|
||||||
@@ -339,8 +343,8 @@ which means you can modify it, redistribute it or use it however you like.
|
|||||||
preference, for example: "srt" or
|
preference, for example: "srt" or
|
||||||
"ass/srt/best"
|
"ass/srt/best"
|
||||||
--sub-lang LANGS Languages of the subtitles to download
|
--sub-lang LANGS Languages of the subtitles to download
|
||||||
(optional) separated by commas, use IETF
|
(optional) separated by commas, use --list-
|
||||||
language tags like 'en,pt'
|
subs for available language tags
|
||||||
|
|
||||||
## Authentication Options:
|
## Authentication Options:
|
||||||
-u, --username USERNAME Login with this account ID
|
-u, --username USERNAME Login with this account ID
|
||||||
@@ -438,41 +442,172 @@ On Windows you may also need to setup the `%HOME%` environment variable manually
|
|||||||
|
|
||||||
The `-o` option allows users to indicate a template for the output file names. The basic usage is not to set any template arguments when downloading a single file, like in `youtube-dl -o funny_video.flv "http://some/video"`. However, it may contain special sequences that will be replaced when downloading each video. The special sequences have the format `%(NAME)s`. To clarify, that is a percent symbol followed by a name in parentheses, followed by a lowercase S. Allowed names are:
|
The `-o` option allows users to indicate a template for the output file names. The basic usage is not to set any template arguments when downloading a single file, like in `youtube-dl -o funny_video.flv "http://some/video"`. However, it may contain special sequences that will be replaced when downloading each video. The special sequences have the format `%(NAME)s`. To clarify, that is a percent symbol followed by a name in parentheses, followed by a lowercase S. Allowed names are:
|
||||||
|
|
||||||
- `id`: The sequence will be replaced by the video identifier.
|
- `id`: Video identifier
|
||||||
- `url`: The sequence will be replaced by the video URL.
|
- `title`: Video title
|
||||||
- `uploader`: The sequence will be replaced by the nickname of the person who uploaded the video.
|
- `url`: Video URL
|
||||||
- `upload_date`: The sequence will be replaced by the upload date in YYYYMMDD format.
|
- `ext`: Video filename extension
|
||||||
- `title`: The sequence will be replaced by the video title.
|
- `alt_title`: A secondary title of the video
|
||||||
- `ext`: The sequence will be replaced by the appropriate extension (like flv or mp4).
|
- `display_id`: An alternative identifier for the video
|
||||||
- `epoch`: The sequence will be replaced by the Unix epoch when creating the file.
|
- `uploader`: Full name of the video uploader
|
||||||
- `autonumber`: The sequence will be replaced by a five-digit number that will be increased with each download, starting at zero.
|
- `creator`: The main artist who created the video
|
||||||
- `playlist`: The sequence will be replaced by the name or the id of the playlist that contains the video.
|
- `release_date`: The date (YYYYMMDD) when the video was released
|
||||||
- `playlist_index`: The sequence will be replaced by the index of the video in the playlist padded with leading zeros according to the total length of the playlist.
|
- `timestamp`: UNIX timestamp of the moment the video became available
|
||||||
- `format_id`: The sequence will be replaced by the format code specified by `--format`.
|
- `upload_date`: Video upload date (YYYYMMDD)
|
||||||
- `duration`: The sequence will be replaced by the length of the video in seconds.
|
- `uploader_id`: Nickname or id of the video uploader
|
||||||
|
- `location`: Physical location where the video was filmed
|
||||||
|
- `duration`: Length of the video in seconds
|
||||||
|
- `view_count`: How many users have watched the video on the platform
|
||||||
|
- `like_count`: Number of positive ratings of the video
|
||||||
|
- `dislike_count`: Number of negative ratings of the video
|
||||||
|
- `repost_count`: Number of reposts of the video
|
||||||
|
- `average_rating`: Average rating give by users, the scale used depends on the webpage
|
||||||
|
- `comment_count`: Number of comments on the video
|
||||||
|
- `age_limit`: Age restriction for the video (years)
|
||||||
|
- `format`: A human-readable description of the format
|
||||||
|
- `format_id`: Format code specified by `--format`
|
||||||
|
- `format_note`: Additional info about the format
|
||||||
|
- `width`: Width of the video
|
||||||
|
- `height`: Height of the video
|
||||||
|
- `resolution`: Textual description of width and height
|
||||||
|
- `tbr`: Average bitrate of audio and video in KBit/s
|
||||||
|
- `abr`: Average audio bitrate in KBit/s
|
||||||
|
- `acodec`: Name of the audio codec in use
|
||||||
|
- `asr`: Audio sampling rate in Hertz
|
||||||
|
- `vbr`: Average video bitrate in KBit/s
|
||||||
|
- `fps`: Frame rate
|
||||||
|
- `vcodec`: Name of the video codec in use
|
||||||
|
- `container`: Name of the container format
|
||||||
|
- `filesize`: The number of bytes, if known in advance
|
||||||
|
- `filesize_approx`: An estimate for the number of bytes
|
||||||
|
- `protocol`: The protocol that will be used for the actual download
|
||||||
|
- `extractor`: Name of the extractor
|
||||||
|
- `extractor_key`: Key name of the extractor
|
||||||
|
- `epoch`: Unix epoch when creating the file
|
||||||
|
- `autonumber`: Five-digit number that will be increased with each download, starting at zero
|
||||||
|
- `playlist`: Name or id of the playlist that contains the video
|
||||||
|
- `playlist_index`: Index of the video in the playlist padded with leading zeros according to the total length of the playlist
|
||||||
|
|
||||||
|
Available for the video that belongs to some logical chapter or section:
|
||||||
|
- `chapter`: Name or title of the chapter the video belongs to
|
||||||
|
- `chapter_number`: Number of the chapter the video belongs to
|
||||||
|
- `chapter_id`: Id of the chapter the video belongs to
|
||||||
|
|
||||||
|
Available for the video that is an episode of some series or programme:
|
||||||
|
- `series`: Title of the series or programme the video episode belongs to
|
||||||
|
- `season`: Title of the season the video episode belongs to
|
||||||
|
- `season_number`: Number of the season the video episode belongs to
|
||||||
|
- `season_id`: Id of the season the video episode belongs to
|
||||||
|
- `episode`: Title of the video episode
|
||||||
|
- `episode_number`: Number of the video episode within a season
|
||||||
|
- `episode_id`: Id of the video episode
|
||||||
|
|
||||||
|
Each aforementioned sequence when referenced in output template will be replaced by the actual value corresponding to the sequence name. Note that some of the sequences are not guaranteed to be present since they depend on the metadata obtained by particular extractor, such sequences will be replaced with `NA`.
|
||||||
|
|
||||||
|
For example for `-o %(title)s-%(id)s.%(ext)s` and mp4 video with title `youtube-dl test video` and id `BaW_jenozKcj` this will result in a `youtube-dl test video-BaW_jenozKcj.mp4` file created in the current directory.
|
||||||
|
|
||||||
|
Output template can also contain arbitrary hierarchical path, e.g. `-o '%(playlist)s/%(playlist_index)s - %(title)s.%(ext)s'` that will result in downloading each video in a directory corresponding to this path template. Any missing directory will be automatically created for you.
|
||||||
|
|
||||||
|
To specify percent literal in output template use `%%`. To output to stdout use `-o -`.
|
||||||
|
|
||||||
The current default template is `%(title)s-%(id)s.%(ext)s`.
|
The current default template is `%(title)s-%(id)s.%(ext)s`.
|
||||||
|
|
||||||
In some cases, you don't want special characters such as 中, spaces, or &, such as when transferring the downloaded filename to a Windows system or the filename through an 8bit-unsafe channel. In these cases, add the `--restrict-filenames` flag to get a shorter title:
|
In some cases, you don't want special characters such as 中, spaces, or &, such as when transferring the downloaded filename to a Windows system or the filename through an 8bit-unsafe channel. In these cases, add the `--restrict-filenames` flag to get a shorter title:
|
||||||
|
|
||||||
|
Examples (note on Windows you may need to use double quotes instead of single):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ youtube-dl --get-filename -o "%(title)s.%(ext)s" BaW_jenozKc
|
$ youtube-dl --get-filename -o '%(title)s.%(ext)s' BaW_jenozKc
|
||||||
youtube-dl test video ''_ä↭𝕐.mp4 # All kinds of weird characters
|
youtube-dl test video ''_ä↭𝕐.mp4 # All kinds of weird characters
|
||||||
$ youtube-dl --get-filename -o "%(title)s.%(ext)s" BaW_jenozKc --restrict-filenames
|
|
||||||
|
$ youtube-dl --get-filename -o '%(title)s.%(ext)s' BaW_jenozKc --restrict-filenames
|
||||||
youtube-dl_test_video_.mp4 # A simple file name
|
youtube-dl_test_video_.mp4 # A simple file name
|
||||||
|
|
||||||
|
# Download YouTube playlist videos in separate directory indexed by video order in a playlist
|
||||||
|
$ youtube-dl -o '%(playlist)s/%(playlist_index)s - %(title)s.%(ext)s' https://www.youtube.com/playlist?list=PLwiyx1dc3P2JR9N8gQaQN_BCvlSlap7re
|
||||||
|
|
||||||
|
# Download Udemy course keeping each chapter in separate directory under MyVideos directory in your home
|
||||||
|
$ youtube-dl -u user -p password -o '~/MyVideos/%(playlist)s/%(chapter_number)s - %(chapter)s/%(title)s.%(ext)s' https://www.udemy.com/java-tutorial/
|
||||||
|
|
||||||
|
# Download entire series season keeping each series and each season in separate directory under C:/MyVideos
|
||||||
|
$ youtube-dl -o "C:/MyVideos/%(series)s/%(season_number)s - %(season)s/%(episode_number)s - %(episode)s.%(ext)s" http://videomore.ru/kino_v_detalayah/5_sezon/367617
|
||||||
|
|
||||||
|
# Stream the video being downloaded to stdout
|
||||||
|
$ youtube-dl -o - BaW_jenozKc
|
||||||
```
|
```
|
||||||
|
|
||||||
# FORMAT SELECTION
|
# FORMAT SELECTION
|
||||||
|
|
||||||
By default youtube-dl tries to download the best quality, but sometimes you may want to download in a different format.
|
By default youtube-dl tries to download the best available quality, i.e. if you want the best quality you **don't need** to pass any special options, youtube-dl will guess it for you by **default**.
|
||||||
The simplest case is requesting a specific format, for example `-f 22`. You can get the list of available formats using `--list-formats`, you can also use a file extension (currently it supports aac, m4a, mp3, mp4, ogg, wav, webm) or the special names `best`, `bestvideo`, `bestaudio` and `worst`.
|
|
||||||
|
|
||||||
If you want to download multiple videos and they don't have the same formats available, you can specify the order of preference using slashes, as in `-f 22/17/18`. You can also filter the video results by putting a condition in brackets, as in `-f "best[height=720]"` (or `-f "[filesize>10M]"`). This works for filesize, height, width, tbr, abr, vbr, asr, and fps and the comparisons <, <=, >, >=, =, != and for ext, acodec, vcodec, container, and protocol and the comparisons =, != . Formats for which the value is not known are excluded unless you put a question mark (?) after the operator. You can combine format filters, so `-f "[height <=? 720][tbr>500]"` selects up to 720p videos (or videos where the height is not known) with a bitrate of at least 500 KBit/s. Use commas to download multiple formats, such as `-f 136/137/mp4/bestvideo,140/m4a/bestaudio`. You can merge the video and audio of two formats into a single file using `-f <video-format>+<audio-format>` (requires ffmpeg or avconv), for example `-f bestvideo+bestaudio`. Format selectors can also be grouped using parentheses, for example if you want to download the best mp4 and webm formats with a height lower than 480 you can use `-f '(mp4,webm)[height<480]'`.
|
But sometimes you may want to download in a different format, for example when you are on a slow or intermittent connection. The key mechanism for achieving this is so called *format selection* based on which you can explicitly specify desired format, select formats based on some criterion or criteria, setup precedence and much more.
|
||||||
|
|
||||||
Since the end of April 2015 and version 2015.04.26 youtube-dl uses `-f bestvideo+bestaudio/best` as default format selection (see #5447, #5456). If ffmpeg or avconv are installed this results in downloading `bestvideo` and `bestaudio` separately and muxing them together into a single file giving the best overall quality available. Otherwise it falls back to `best` and results in downloading the best available quality served as a single file. `best` is also needed for videos that don't come from YouTube because they don't provide the audio and video in two different files. If you want to only download some dash formats (for example if you are not interested in getting videos with a resolution higher than 1080p), you can add `-f bestvideo[height<=?1080]+bestaudio/best` to your configuration file. Note that if you use youtube-dl to stream to `stdout` (and most likely to pipe it to your media player then), i.e. you explicitly specify output template as `-o -`, youtube-dl still uses `-f best` format selection in order to start content delivery immediately to your player and not to wait until `bestvideo` and `bestaudio` are downloaded and muxed.
|
The general syntax for format selection is `--format FORMAT` or shorter `-f FORMAT` where `FORMAT` is a *selector expression*, i.e. an expression that describes format or formats you would like to download.
|
||||||
|
|
||||||
|
The simplest case is requesting a specific format, for example with `-f 22` you can download the format with format code equal to 22. You can get the list of available format codes for particular video using `--list-formats` or `-F`. Note that these format codes are extractor specific.
|
||||||
|
|
||||||
|
You can also use a file extension (currently `3gp`, `aac`, `flv`, `m4a`, `mp3`, `mp4`, `ogg`, `wav`, `webm` are supported) to download best quality format of particular file extension served as a single file, e.g. `-f webm` will download best quality format with `webm` extension served as a single file.
|
||||||
|
|
||||||
|
You can also use special names to select particular edge case format:
|
||||||
|
- `best`: Select best quality format represented by single file with video and audio
|
||||||
|
- `worst`: Select worst quality format represented by single file with video and audio
|
||||||
|
- `bestvideo`: Select best quality video only format (e.g. DASH video), may not be available
|
||||||
|
- `worstvideo`: Select worst quality video only format, may not be available
|
||||||
|
- `bestaudio`: Select best quality audio only format, may not be available
|
||||||
|
- `worstaudio`: Select worst quality audio only format, may not be available
|
||||||
|
|
||||||
|
For example, to download worst quality video only format you can use `-f worstvideo`.
|
||||||
|
|
||||||
|
If you want to download multiple videos and they don't have the same formats available, you can specify the order of preference using slashes. Note that slash is left-associative, i.e. formats on the left hand side are preferred, for example `-f 22/17/18` will download format 22 if it's available, otherwise it will download format 17 if it's available, otherwise it will download format 18 if it's available, otherwise it will complain that no suitable formats are available for download.
|
||||||
|
|
||||||
|
If you want to download several formats of the same video use comma as a separator, e.g. `-f 22,17,18` will download all these three formats, of course if they are available. Or more sophisticated example combined with precedence feature `-f 136/137/mp4/bestvideo,140/m4a/bestaudio`.
|
||||||
|
|
||||||
|
You can also filter the video formats by putting a condition in brackets, as in `-f "best[height=720]"` (or `-f "[filesize>10M]"`).
|
||||||
|
|
||||||
|
The following numeric meta fields can be used with comparisons `<`, `<=`, `>`, `>=`, `=` (equals), `!=` (not equals):
|
||||||
|
- `filesize`: The number of bytes, if known in advance
|
||||||
|
- `width`: Width of the video, if known
|
||||||
|
- `height`: Height of the video, if known
|
||||||
|
- `tbr`: Average bitrate of audio and video in KBit/s
|
||||||
|
- `abr`: Average audio bitrate in KBit/s
|
||||||
|
- `vbr`: Average video bitrate in KBit/s
|
||||||
|
- `asr`: Audio sampling rate in Hertz
|
||||||
|
- `fps`: Frame rate
|
||||||
|
|
||||||
|
Also filtering work for comparisons `=` (equals), `!=` (not equals), `^=` (begins with), `$=` (ends with), `*=` (contains) and following string meta fields:
|
||||||
|
- `ext`: File extension
|
||||||
|
- `acodec`: Name of the audio codec in use
|
||||||
|
- `vcodec`: Name of the video codec in use
|
||||||
|
- `container`: Name of the container format
|
||||||
|
- `protocol`: The protocol that will be used for the actual download, lower-case. `http`, `https`, `rtsp`, `rtmp`, `rtmpe`, `m3u8`, or `m3u8_native`
|
||||||
|
|
||||||
|
Note that none of the aforementioned meta fields are guaranteed to be present since this solely depends on the metadata obtained by particular extractor, i.e. the metadata offered by video hoster.
|
||||||
|
|
||||||
|
Formats for which the value is not known are excluded unless you put a question mark (`?`) after the operator. You can combine format filters, so `-f "[height <=? 720][tbr>500]"` selects up to 720p videos (or videos where the height is not known) with a bitrate of at least 500 KBit/s.
|
||||||
|
|
||||||
|
You can merge the video and audio of two formats into a single file using `-f <video-format>+<audio-format>` (requires ffmpeg or avconv installed), for example `-f bestvideo+bestaudio` will download best video only format, best audio only format and mux them together with ffmpeg/avconv.
|
||||||
|
|
||||||
|
Format selectors can also be grouped using parentheses, for example if you want to download the best mp4 and webm formats with a height lower than 480 you can use `-f '(mp4,webm)[height<480]'`.
|
||||||
|
|
||||||
|
Since the end of April 2015 and version 2015.04.26 youtube-dl uses `-f bestvideo+bestaudio/best` as default format selection (see #5447, #5456). If ffmpeg or avconv are installed this results in downloading `bestvideo` and `bestaudio` separately and muxing them together into a single file giving the best overall quality available. Otherwise it falls back to `best` and results in downloading the best available quality served as a single file. `best` is also needed for videos that don't come from YouTube because they don't provide the audio and video in two different files. If you want to only download some DASH formats (for example if you are not interested in getting videos with a resolution higher than 1080p), you can add `-f bestvideo[height<=?1080]+bestaudio/best` to your configuration file. Note that if you use youtube-dl to stream to `stdout` (and most likely to pipe it to your media player then), i.e. you explicitly specify output template as `-o -`, youtube-dl still uses `-f best` format selection in order to start content delivery immediately to your player and not to wait until `bestvideo` and `bestaudio` are downloaded and muxed.
|
||||||
|
|
||||||
If you want to preserve the old format selection behavior (prior to youtube-dl 2015.04.26), i.e. you want to download the best available quality media served as a single file, you should explicitly specify your choice with `-f best`. You may want to add it to the [configuration file](#configuration) in order not to type it every time you run youtube-dl.
|
If you want to preserve the old format selection behavior (prior to youtube-dl 2015.04.26), i.e. you want to download the best available quality media served as a single file, you should explicitly specify your choice with `-f best`. You may want to add it to the [configuration file](#configuration) in order not to type it every time you run youtube-dl.
|
||||||
|
|
||||||
|
Examples (note on Windows you may need to use double quotes instead of single):
|
||||||
|
```bash
|
||||||
|
# Download best mp4 format available or any other best if no mp4 available
|
||||||
|
$ youtube-dl -f 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best'
|
||||||
|
|
||||||
|
# Download best format available but not better that 480p
|
||||||
|
$ youtube-dl -f 'bestvideo[height<=480]+bestaudio/best[height<=480]'
|
||||||
|
|
||||||
|
# Download best video only format but no bigger that 50 MB
|
||||||
|
$ youtube-dl -f 'best[filesize<50M]'
|
||||||
|
|
||||||
|
# Download best format available via direct link over HTTP/HTTPS protocol
|
||||||
|
$ youtube-dl -f '(bestvideo+bestaudio/best)[protocol^=http]'
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
# VIDEO SELECTION
|
# VIDEO SELECTION
|
||||||
|
|
||||||
Videos can be filtered by their upload date using the options `--date`, `--datebefore` or `--dateafter`. They accept dates in two formats:
|
Videos can be filtered by their upload date using the options `--date`, `--datebefore` or `--dateafter`. They accept dates in two formats:
|
||||||
@@ -627,7 +762,7 @@ Either prepend `http://www.youtube.com/watch?v=` or separate the ID from the opt
|
|||||||
|
|
||||||
Use the `--cookies` option, for example `--cookies /path/to/cookies/file.txt`. Note that the cookies file must be in Mozilla/Netscape format and the first line of the cookies file must be either `# HTTP Cookie File` or `# Netscape HTTP Cookie File`. Make sure you have correct [newline format](https://en.wikipedia.org/wiki/Newline) in the cookies file and convert newlines if necessary to correspond with your OS, namely `CRLF` (`\r\n`) for Windows, `LF` (`\n`) for Linux and `CR` (`\r`) for Mac OS. `HTTP Error 400: Bad Request` when using `--cookies` is a good sign of invalid newline format.
|
Use the `--cookies` option, for example `--cookies /path/to/cookies/file.txt`. Note that the cookies file must be in Mozilla/Netscape format and the first line of the cookies file must be either `# HTTP Cookie File` or `# Netscape HTTP Cookie File`. Make sure you have correct [newline format](https://en.wikipedia.org/wiki/Newline) in the cookies file and convert newlines if necessary to correspond with your OS, namely `CRLF` (`\r\n`) for Windows, `LF` (`\n`) for Linux and `CR` (`\r`) for Mac OS. `HTTP Error 400: Bad Request` when using `--cookies` is a good sign of invalid newline format.
|
||||||
|
|
||||||
Passing cookies to youtube-dl is a good way to workaround login when a particular extractor does not implement it explicitly.
|
Passing cookies to youtube-dl is a good way to workaround login when a particular extractor does not implement it explicitly. Another use case is working around [CAPTCHA](https://en.wikipedia.org/wiki/CAPTCHA) some websites require you to solve in particular cases in order to get access (e.g. YouTube, CloudFlare).
|
||||||
|
|
||||||
### Can you add support for this anime video site, or site which shows current movies for free?
|
### Can you add support for this anime video site, or site which shows current movies for free?
|
||||||
|
|
||||||
|
@@ -5,7 +5,7 @@ from __future__ import with_statement, unicode_literals
|
|||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import glob
|
import glob
|
||||||
import io # For Python 2 compatibilty
|
import io # For Python 2 compatibility
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
# Supported sites
|
# Supported sites
|
||||||
- **1tv**: Первый канал
|
- **1tv**: Первый канал
|
||||||
- **1up.com**
|
- **1up.com**
|
||||||
|
- **20min**
|
||||||
- **220.ro**
|
- **220.ro**
|
||||||
- **22tracks:genre**
|
- **22tracks:genre**
|
||||||
- **22tracks:track**
|
- **22tracks:track**
|
||||||
@@ -23,12 +24,12 @@
|
|||||||
- **AdobeTVShow**
|
- **AdobeTVShow**
|
||||||
- **AdobeTVVideo**
|
- **AdobeTVVideo**
|
||||||
- **AdultSwim**
|
- **AdultSwim**
|
||||||
|
- **aenetworks**: A+E Networks: A&E, Lifetime, History.com, FYI Network
|
||||||
- **Aftonbladet**
|
- **Aftonbladet**
|
||||||
- **AirMozilla**
|
- **AirMozilla**
|
||||||
- **AlJazeera**
|
- **AlJazeera**
|
||||||
- **Allocine**
|
- **Allocine**
|
||||||
- **AlphaPorno**
|
- **AlphaPorno**
|
||||||
- **AnimalPlanet**
|
|
||||||
- **anitube.se**
|
- **anitube.se**
|
||||||
- **AnySex**
|
- **AnySex**
|
||||||
- **Aparat**
|
- **Aparat**
|
||||||
@@ -42,6 +43,7 @@
|
|||||||
- **ARD:mediathek**
|
- **ARD:mediathek**
|
||||||
- **arte.tv**
|
- **arte.tv**
|
||||||
- **arte.tv:+7**
|
- **arte.tv:+7**
|
||||||
|
- **arte.tv:cinema**
|
||||||
- **arte.tv:concert**
|
- **arte.tv:concert**
|
||||||
- **arte.tv:creative**
|
- **arte.tv:creative**
|
||||||
- **arte.tv:ddc**
|
- **arte.tv:ddc**
|
||||||
@@ -53,6 +55,7 @@
|
|||||||
- **audiomack**
|
- **audiomack**
|
||||||
- **audiomack:album**
|
- **audiomack:album**
|
||||||
- **Azubu**
|
- **Azubu**
|
||||||
|
- **AzubuLive**
|
||||||
- **BaiduVideo**: 百度视频
|
- **BaiduVideo**: 百度视频
|
||||||
- **bambuser**
|
- **bambuser**
|
||||||
- **bambuser:channel**
|
- **bambuser:channel**
|
||||||
@@ -65,6 +68,7 @@
|
|||||||
- **Beeg**
|
- **Beeg**
|
||||||
- **BehindKink**
|
- **BehindKink**
|
||||||
- **Bet**
|
- **Bet**
|
||||||
|
- **Bigflix**
|
||||||
- **Bild**: Bild.de
|
- **Bild**: Bild.de
|
||||||
- **BiliBili**
|
- **BiliBili**
|
||||||
- **BleacherReport**
|
- **BleacherReport**
|
||||||
@@ -84,8 +88,10 @@
|
|||||||
- **CamdemyFolder**
|
- **CamdemyFolder**
|
||||||
- **canalc2.tv**
|
- **canalc2.tv**
|
||||||
- **Canalplus**: canalplus.fr, piwiplus.fr and d8.tv
|
- **Canalplus**: canalplus.fr, piwiplus.fr and d8.tv
|
||||||
|
- **Canvas**
|
||||||
- **CBS**
|
- **CBS**
|
||||||
- **CBSNews**: CBS News
|
- **CBSNews**: CBS News
|
||||||
|
- **CBSNewsLiveVideo**: CBS News Live Videos
|
||||||
- **CBSSports**
|
- **CBSSports**
|
||||||
- **CeskaTelevize**
|
- **CeskaTelevize**
|
||||||
- **channel9**: Channel 9
|
- **channel9**: Channel 9
|
||||||
@@ -121,12 +127,16 @@
|
|||||||
- **CSpan**: C-SPAN
|
- **CSpan**: C-SPAN
|
||||||
- **CtsNews**: 華視新聞
|
- **CtsNews**: 華視新聞
|
||||||
- **culturebox.francetvinfo.fr**
|
- **culturebox.francetvinfo.fr**
|
||||||
|
- **CultureUnplugged**
|
||||||
|
- **CWTV**
|
||||||
- **dailymotion**
|
- **dailymotion**
|
||||||
- **dailymotion:playlist**
|
- **dailymotion:playlist**
|
||||||
- **dailymotion:user**
|
- **dailymotion:user**
|
||||||
- **DailymotionCloud**
|
- **DailymotionCloud**
|
||||||
- **daum.net**
|
- **daum.net**
|
||||||
- **daum.net:clip**
|
- **daum.net:clip**
|
||||||
|
- **daum.net:playlist**
|
||||||
|
- **daum.net:user**
|
||||||
- **DBTV**
|
- **DBTV**
|
||||||
- **DCN**
|
- **DCN**
|
||||||
- **dcn:live**
|
- **dcn:live**
|
||||||
@@ -137,6 +147,7 @@
|
|||||||
- **defense.gouv.fr**
|
- **defense.gouv.fr**
|
||||||
- **democracynow**
|
- **democracynow**
|
||||||
- **DHM**: Filmarchiv - Deutsches Historisches Museum
|
- **DHM**: Filmarchiv - Deutsches Historisches Museum
|
||||||
|
- **Digiteka**
|
||||||
- **Discovery**
|
- **Discovery**
|
||||||
- **Dotsub**
|
- **Dotsub**
|
||||||
- **DouyuTV**: 斗鱼
|
- **DouyuTV**: 斗鱼
|
||||||
@@ -173,6 +184,7 @@
|
|||||||
- **ExpoTV**
|
- **ExpoTV**
|
||||||
- **ExtremeTube**
|
- **ExtremeTube**
|
||||||
- **facebook**
|
- **facebook**
|
||||||
|
- **facebook:post**
|
||||||
- **faz.net**
|
- **faz.net**
|
||||||
- **fc2**
|
- **fc2**
|
||||||
- **Fczenit**
|
- **Fczenit**
|
||||||
@@ -228,7 +240,6 @@
|
|||||||
- **Helsinki**: helsinki.fi
|
- **Helsinki**: helsinki.fi
|
||||||
- **HentaiStigma**
|
- **HentaiStigma**
|
||||||
- **HistoricFilms**
|
- **HistoricFilms**
|
||||||
- **History**
|
|
||||||
- **hitbox**
|
- **hitbox**
|
||||||
- **hitbox:live**
|
- **hitbox:live**
|
||||||
- **HornBunny**
|
- **HornBunny**
|
||||||
@@ -256,6 +267,7 @@
|
|||||||
- **Ir90Tv**
|
- **Ir90Tv**
|
||||||
- **ivi**: ivi.ru
|
- **ivi**: ivi.ru
|
||||||
- **ivi:compilation**: ivi.ru compilations
|
- **ivi:compilation**: ivi.ru compilations
|
||||||
|
- **ivideon**: Ivideon TV
|
||||||
- **Izlesene**
|
- **Izlesene**
|
||||||
- **JadoreCettePub**
|
- **JadoreCettePub**
|
||||||
- **JeuxVideo**
|
- **JeuxVideo**
|
||||||
@@ -271,6 +283,7 @@
|
|||||||
- **KeezMovies**
|
- **KeezMovies**
|
||||||
- **KhanAcademy**
|
- **KhanAcademy**
|
||||||
- **KickStarter**
|
- **KickStarter**
|
||||||
|
- **KonserthusetPlay**
|
||||||
- **kontrtube**: KontrTube.ru - Труба зовёт
|
- **kontrtube**: KontrTube.ru - Труба зовёт
|
||||||
- **KrasView**: Красвью
|
- **KrasView**: Красвью
|
||||||
- **Ku6**
|
- **Ku6**
|
||||||
@@ -283,7 +296,9 @@
|
|||||||
- **la7.tv**
|
- **la7.tv**
|
||||||
- **Laola1Tv**
|
- **Laola1Tv**
|
||||||
- **Lecture2Go**
|
- **Lecture2Go**
|
||||||
|
- **Lemonde**
|
||||||
- **Letv**: 乐视网
|
- **Letv**: 乐视网
|
||||||
|
- **LetvCloud**: 乐视云
|
||||||
- **LetvPlaylist**
|
- **LetvPlaylist**
|
||||||
- **LetvTv**
|
- **LetvTv**
|
||||||
- **Libsyn**
|
- **Libsyn**
|
||||||
@@ -296,6 +311,7 @@
|
|||||||
- **livestream**
|
- **livestream**
|
||||||
- **livestream:original**
|
- **livestream:original**
|
||||||
- **LnkGo**
|
- **LnkGo**
|
||||||
|
- **LoveHomePorn**
|
||||||
- **lrt.lt**
|
- **lrt.lt**
|
||||||
- **lynda**: lynda.com videos
|
- **lynda**: lynda.com videos
|
||||||
- **lynda:course**: lynda.com online courses
|
- **lynda:course**: lynda.com online courses
|
||||||
@@ -304,6 +320,7 @@
|
|||||||
- **mailru**: Видео@Mail.Ru
|
- **mailru**: Видео@Mail.Ru
|
||||||
- **MakerTV**
|
- **MakerTV**
|
||||||
- **Malemotion**
|
- **Malemotion**
|
||||||
|
- **MatchTV**
|
||||||
- **MDR**: MDR.DE and KiKA
|
- **MDR**: MDR.DE and KiKA
|
||||||
- **media.ccc.de**
|
- **media.ccc.de**
|
||||||
- **metacafe**
|
- **metacafe**
|
||||||
@@ -368,11 +385,13 @@
|
|||||||
- **Newstube**
|
- **Newstube**
|
||||||
- **NextMedia**: 蘋果日報
|
- **NextMedia**: 蘋果日報
|
||||||
- **NextMediaActionNews**: 蘋果日報 - 動新聞
|
- **NextMediaActionNews**: 蘋果日報 - 動新聞
|
||||||
|
- **nextmovie.com**
|
||||||
- **nfb**: National Film Board of Canada
|
- **nfb**: National Film Board of Canada
|
||||||
- **nfl.com**
|
- **nfl.com**
|
||||||
- **nhl.com**
|
- **nhl.com**
|
||||||
- **nhl.com:news**: NHL news
|
- **nhl.com:news**: NHL news
|
||||||
- **nhl.com:videocenter**: NHL videocenter category
|
- **nhl.com:videocenter**: NHL videocenter category
|
||||||
|
- **nick.com**
|
||||||
- **niconico**: ニコニコ動画
|
- **niconico**: ニコニコ動画
|
||||||
- **NiconicoPlaylist**
|
- **NiconicoPlaylist**
|
||||||
- **njoy**: N-JOY
|
- **njoy**: N-JOY
|
||||||
@@ -385,13 +404,14 @@
|
|||||||
- **nowness**
|
- **nowness**
|
||||||
- **nowness:playlist**
|
- **nowness:playlist**
|
||||||
- **nowness:series**
|
- **nowness:series**
|
||||||
- **NowTV**
|
- **NowTV** (Currently broken)
|
||||||
- **NowTVList**
|
- **NowTVList**
|
||||||
- **nowvideo**: NowVideo
|
- **nowvideo**: NowVideo
|
||||||
- **npo**: npo.nl and ntr.nl
|
- **npo**: npo.nl and ntr.nl
|
||||||
- **npo.nl:live**
|
- **npo.nl:live**
|
||||||
- **npo.nl:radio**
|
- **npo.nl:radio**
|
||||||
- **npo.nl:radio:fragment**
|
- **npo.nl:radio:fragment**
|
||||||
|
- **Npr**
|
||||||
- **NRK**
|
- **NRK**
|
||||||
- **NRKPlaylist**
|
- **NRKPlaylist**
|
||||||
- **NRKTV**: NRK TV and NRK Radio
|
- **NRKTV**: NRK TV and NRK Radio
|
||||||
@@ -411,6 +431,7 @@
|
|||||||
- **orf:iptv**: iptv.ORF.at
|
- **orf:iptv**: iptv.ORF.at
|
||||||
- **orf:oe1**: Radio Österreich 1
|
- **orf:oe1**: Radio Österreich 1
|
||||||
- **orf:tvthek**: ORF TVthek
|
- **orf:tvthek**: ORF TVthek
|
||||||
|
- **pandora.tv**: 판도라TV
|
||||||
- **parliamentlive.tv**: UK parliament videos
|
- **parliamentlive.tv**: UK parliament videos
|
||||||
- **Patreon**
|
- **Patreon**
|
||||||
- **pbs**: Public Broadcasting Service (PBS) and member stations: PBS: Public Broadcasting Service, APT - Alabama Public Television (WBIQ), GPB/Georgia Public Broadcasting (WGTV), Mississippi Public Broadcasting (WMPN), Nashville Public Television (WNPT), WFSU-TV (WFSU), WSRE (WSRE), WTCI (WTCI), WPBA/Channel 30 (WPBA), Alaska Public Media (KAKM), Arizona PBS (KAET), KNME-TV/Channel 5 (KNME), Vegas PBS (KLVX), AETN/ARKANSAS ETV NETWORK (KETS), KET (WKLE), WKNO/Channel 10 (WKNO), LPB/LOUISIANA PUBLIC BROADCASTING (WLPB), OETA (KETA), Ozarks Public Television (KOZK), WSIU Public Broadcasting (WSIU), KEET TV (KEET), KIXE/Channel 9 (KIXE), KPBS San Diego (KPBS), KQED (KQED), KVIE Public Television (KVIE), PBS SoCal/KOCE (KOCE), ValleyPBS (KVPT), CONNECTICUT PUBLIC TELEVISION (WEDH), KNPB Channel 5 (KNPB), SOPTV (KSYS), Rocky Mountain PBS (KRMA), KENW-TV3 (KENW), KUED Channel 7 (KUED), Wyoming PBS (KCWC), Colorado Public Television / KBDI 12 (KBDI), KBYU-TV (KBYU), Thirteen/WNET New York (WNET), WGBH/Channel 2 (WGBH), WGBY (WGBY), NJTV Public Media NJ (WNJT), WLIW21 (WLIW), mpt/Maryland Public Television (WMPB), WETA Television and Radio (WETA), WHYY (WHYY), PBS 39 (WLVT), WVPT - Your Source for PBS and More! (WVPT), Howard University Television (WHUT), WEDU PBS (WEDU), WGCU Public Media (WGCU), WPBT2 (WPBT), WUCF TV (WUCF), WUFT/Channel 5 (WUFT), WXEL/Channel 42 (WXEL), WLRN/Channel 17 (WLRN), WUSF Public Broadcasting (WUSF), ETV (WRLK), UNC-TV (WUNC), PBS Hawaii - Oceanic Cable Channel 10 (KHET), Idaho Public Television (KAID), KSPS (KSPS), OPB (KOPB), KWSU/Channel 10 & KTNW/Channel 31 (KWSU), WILL-TV (WILL), Network Knowledge - WSEC/Springfield (WSEC), WTTW11 (WTTW), Iowa Public Television/IPTV (KDIN), Nine Network (KETC), PBS39 Fort Wayne (WFWA), WFYI Indianapolis (WFYI), Milwaukee Public Television (WMVS), WNIN (WNIN), WNIT Public Television (WNIT), WPT (WPNE), WVUT/Channel 22 (WVUT), WEIU/Channel 51 (WEIU), WQPT-TV (WQPT), WYCC PBS Chicago (WYCC), WIPB-TV (WIPB), WTIU (WTIU), CET (WCET), ThinkTVNetwork (WPTD), WBGU-TV (WBGU), WGVU TV (WGVU), NET1 (KUON), Pioneer Public Television (KWCM), SDPB Television (KUSD), TPT (KTCA), KSMQ (KSMQ), KPTS/Channel 8 (KPTS), KTWU/Channel 11 (KTWU), East Tennessee PBS (WSJK), WCTE-TV (WCTE), WLJT, Channel 11 (WLJT), WOSU TV (WOSU), WOUB/WOUC (WOUB), WVPB (WVPB), WKYU-PBS (WKYU), KERA 13 (KERA), MPBN (WCBB), Mountain Lake PBS (WCFE), NHPTV (WENH), Vermont PBS (WETK), witf (WITF), WQED Multimedia (WQED), WMHT Educational Telecommunications (WMHT), Q-TV (WDCQ), WTVS Detroit Public TV (WTVS), CMU Public Television (WCMU), WKAR-TV (WKAR), WNMU-TV Public TV 13 (WNMU), WDSE - WRPT (WDSE), WGTE TV (WGTE), Lakeland Public Television (KAWE), KMOS-TV - Channels 6.1, 6.2 and 6.3 (KMOS), MontanaPBS (KUSM), KRWG/Channel 22 (KRWG), KACV (KACV), KCOS/Channel 13 (KCOS), WCNY/Channel 24 (WCNY), WNED (WNED), WPBS (WPBS), WSKG Public TV (WSKG), WXXI (WXXI), WPSU (WPSU), WVIA Public Media Studios (WVIA), WTVI (WTVI), Western Reserve PBS (WNEO), WVIZ/PBS ideastream (WVIZ), KCTS 9 (KCTS), Basin PBS (KPBT), KUHT / Channel 8 (KUHT), KLRN (KLRN), KLRU (KLRU), WTJX Channel 12 (WTJX), WCVE PBS (WCVE), KBTC Public Television (KBTC)
|
- **pbs**: Public Broadcasting Service (PBS) and member stations: PBS: Public Broadcasting Service, APT - Alabama Public Television (WBIQ), GPB/Georgia Public Broadcasting (WGTV), Mississippi Public Broadcasting (WMPN), Nashville Public Television (WNPT), WFSU-TV (WFSU), WSRE (WSRE), WTCI (WTCI), WPBA/Channel 30 (WPBA), Alaska Public Media (KAKM), Arizona PBS (KAET), KNME-TV/Channel 5 (KNME), Vegas PBS (KLVX), AETN/ARKANSAS ETV NETWORK (KETS), KET (WKLE), WKNO/Channel 10 (WKNO), LPB/LOUISIANA PUBLIC BROADCASTING (WLPB), OETA (KETA), Ozarks Public Television (KOZK), WSIU Public Broadcasting (WSIU), KEET TV (KEET), KIXE/Channel 9 (KIXE), KPBS San Diego (KPBS), KQED (KQED), KVIE Public Television (KVIE), PBS SoCal/KOCE (KOCE), ValleyPBS (KVPT), CONNECTICUT PUBLIC TELEVISION (WEDH), KNPB Channel 5 (KNPB), SOPTV (KSYS), Rocky Mountain PBS (KRMA), KENW-TV3 (KENW), KUED Channel 7 (KUED), Wyoming PBS (KCWC), Colorado Public Television / KBDI 12 (KBDI), KBYU-TV (KBYU), Thirteen/WNET New York (WNET), WGBH/Channel 2 (WGBH), WGBY (WGBY), NJTV Public Media NJ (WNJT), WLIW21 (WLIW), mpt/Maryland Public Television (WMPB), WETA Television and Radio (WETA), WHYY (WHYY), PBS 39 (WLVT), WVPT - Your Source for PBS and More! (WVPT), Howard University Television (WHUT), WEDU PBS (WEDU), WGCU Public Media (WGCU), WPBT2 (WPBT), WUCF TV (WUCF), WUFT/Channel 5 (WUFT), WXEL/Channel 42 (WXEL), WLRN/Channel 17 (WLRN), WUSF Public Broadcasting (WUSF), ETV (WRLK), UNC-TV (WUNC), PBS Hawaii - Oceanic Cable Channel 10 (KHET), Idaho Public Television (KAID), KSPS (KSPS), OPB (KOPB), KWSU/Channel 10 & KTNW/Channel 31 (KWSU), WILL-TV (WILL), Network Knowledge - WSEC/Springfield (WSEC), WTTW11 (WTTW), Iowa Public Television/IPTV (KDIN), Nine Network (KETC), PBS39 Fort Wayne (WFWA), WFYI Indianapolis (WFYI), Milwaukee Public Television (WMVS), WNIN (WNIN), WNIT Public Television (WNIT), WPT (WPNE), WVUT/Channel 22 (WVUT), WEIU/Channel 51 (WEIU), WQPT-TV (WQPT), WYCC PBS Chicago (WYCC), WIPB-TV (WIPB), WTIU (WTIU), CET (WCET), ThinkTVNetwork (WPTD), WBGU-TV (WBGU), WGVU TV (WGVU), NET1 (KUON), Pioneer Public Television (KWCM), SDPB Television (KUSD), TPT (KTCA), KSMQ (KSMQ), KPTS/Channel 8 (KPTS), KTWU/Channel 11 (KTWU), East Tennessee PBS (WSJK), WCTE-TV (WCTE), WLJT, Channel 11 (WLJT), WOSU TV (WOSU), WOUB/WOUC (WOUB), WVPB (WVPB), WKYU-PBS (WKYU), KERA 13 (KERA), MPBN (WCBB), Mountain Lake PBS (WCFE), NHPTV (WENH), Vermont PBS (WETK), witf (WITF), WQED Multimedia (WQED), WMHT Educational Telecommunications (WMHT), Q-TV (WDCQ), WTVS Detroit Public TV (WTVS), CMU Public Television (WCMU), WKAR-TV (WKAR), WNMU-TV Public TV 13 (WNMU), WDSE - WRPT (WDSE), WGTE TV (WGTE), Lakeland Public Television (KAWE), KMOS-TV - Channels 6.1, 6.2 and 6.3 (KMOS), MontanaPBS (KUSM), KRWG/Channel 22 (KRWG), KACV (KACV), KCOS/Channel 13 (KCOS), WCNY/Channel 24 (WCNY), WNED (WNED), WPBS (WPBS), WSKG Public TV (WSKG), WXXI (WXXI), WPSU (WPSU), WVIA Public Media Studios (WVIA), WTVI (WTVI), Western Reserve PBS (WNEO), WVIZ/PBS ideastream (WVIZ), KCTS 9 (KCTS), Basin PBS (KPBT), KUHT / Channel 8 (KUHT), KLRN (KLRN), KLRU (KLRU), WTJX Channel 12 (WTJX), WCVE PBS (WCVE), KBTC Public Television (KBTC)
|
||||||
@@ -459,13 +480,16 @@
|
|||||||
- **RBMARadio**
|
- **RBMARadio**
|
||||||
- **RDS**: RDS.ca
|
- **RDS**: RDS.ca
|
||||||
- **RedTube**
|
- **RedTube**
|
||||||
|
- **RegioTV**
|
||||||
- **Restudy**
|
- **Restudy**
|
||||||
- **ReverbNation**
|
- **ReverbNation**
|
||||||
|
- **Revision3**
|
||||||
- **RingTV**
|
- **RingTV**
|
||||||
- **RottenTomatoes**
|
- **RottenTomatoes**
|
||||||
- **Roxwel**
|
- **Roxwel**
|
||||||
- **RTBF**
|
- **RTBF**
|
||||||
- **Rte**
|
- **rte**: Raidió Teilifís Éireann TV
|
||||||
|
- **rte:radio**: Raidió Teilifís Éireann radio
|
||||||
- **rtl.nl**: rtl.nl and rtlxl.nl
|
- **rtl.nl**: rtl.nl and rtlxl.nl
|
||||||
- **RTL2**
|
- **RTL2**
|
||||||
- **RTP**
|
- **RTP**
|
||||||
@@ -475,6 +499,7 @@
|
|||||||
- **rtve.es:live**: RTVE.es live streams
|
- **rtve.es:live**: RTVE.es live streams
|
||||||
- **RTVNH**
|
- **RTVNH**
|
||||||
- **RUHD**
|
- **RUHD**
|
||||||
|
- **RulePorn**
|
||||||
- **rutube**: Rutube videos
|
- **rutube**: Rutube videos
|
||||||
- **rutube:channel**: Rutube channels
|
- **rutube:channel**: Rutube channels
|
||||||
- **rutube:embed**: Rutube embedded videos
|
- **rutube:embed**: Rutube embedded videos
|
||||||
@@ -488,6 +513,7 @@
|
|||||||
- **Sapo**: SAPO Vídeos
|
- **Sapo**: SAPO Vídeos
|
||||||
- **savefrom.net**
|
- **savefrom.net**
|
||||||
- **SBS**: sbs.com.au
|
- **SBS**: sbs.com.au
|
||||||
|
- **schooltv**
|
||||||
- **SciVee**
|
- **SciVee**
|
||||||
- **screen.yahoo:search**: Yahoo screen search
|
- **screen.yahoo:search**: Yahoo screen search
|
||||||
- **Screencast**
|
- **Screencast**
|
||||||
@@ -570,7 +596,6 @@
|
|||||||
- **TeleMB**
|
- **TeleMB**
|
||||||
- **TeleTask**
|
- **TeleTask**
|
||||||
- **TenPlay**
|
- **TenPlay**
|
||||||
- **TestTube**
|
|
||||||
- **TF1**
|
- **TF1**
|
||||||
- **TheIntercept**
|
- **TheIntercept**
|
||||||
- **TheOnion**
|
- **TheOnion**
|
||||||
@@ -582,7 +607,6 @@
|
|||||||
- **THVideo**
|
- **THVideo**
|
||||||
- **THVideoPlaylist**
|
- **THVideoPlaylist**
|
||||||
- **tinypic**: tinypic.com videos
|
- **tinypic**: tinypic.com videos
|
||||||
- **tlc.com**
|
|
||||||
- **tlc.de**
|
- **tlc.de**
|
||||||
- **TMZ**
|
- **TMZ**
|
||||||
- **TMZArticle**
|
- **TMZArticle**
|
||||||
@@ -593,10 +617,13 @@
|
|||||||
- **ToypicsUser**: Toypics user profile
|
- **ToypicsUser**: Toypics user profile
|
||||||
- **TrailerAddict** (Currently broken)
|
- **TrailerAddict** (Currently broken)
|
||||||
- **Trilulilu**
|
- **Trilulilu**
|
||||||
|
- **trollvids**
|
||||||
- **TruTube**
|
- **TruTube**
|
||||||
- **Tube8**
|
- **Tube8**
|
||||||
- **TubiTv**
|
- **TubiTv**
|
||||||
- **Tudou**
|
- **tudou**
|
||||||
|
- **tudou:album**
|
||||||
|
- **tudou:playlist**
|
||||||
- **Tumblr**
|
- **Tumblr**
|
||||||
- **tunein:clip**
|
- **tunein:clip**
|
||||||
- **tunein:program**
|
- **tunein:program**
|
||||||
@@ -611,6 +638,7 @@
|
|||||||
- **TVC**
|
- **TVC**
|
||||||
- **TVCArticle**
|
- **TVCArticle**
|
||||||
- **tvigle**: Интернет-телевидение Tvigle.ru
|
- **tvigle**: Интернет-телевидение Tvigle.ru
|
||||||
|
- **tvland.com**
|
||||||
- **tvp.pl**
|
- **tvp.pl**
|
||||||
- **tvp.pl:Series**
|
- **tvp.pl:Series**
|
||||||
- **TVPlay**: TV3Play and related services
|
- **TVPlay**: TV3Play and related services
|
||||||
@@ -628,7 +656,6 @@
|
|||||||
- **udemy**
|
- **udemy**
|
||||||
- **udemy:course**
|
- **udemy:course**
|
||||||
- **UDNEmbed**: 聯合影音
|
- **UDNEmbed**: 聯合影音
|
||||||
- **Ultimedia**
|
|
||||||
- **Unistra**
|
- **Unistra**
|
||||||
- **Urort**: NRK P3 Urørt
|
- **Urort**: NRK P3 Urørt
|
||||||
- **ustream**
|
- **ustream**
|
||||||
@@ -648,14 +675,16 @@
|
|||||||
- **video.mit.edu**
|
- **video.mit.edu**
|
||||||
- **VideoDetective**
|
- **VideoDetective**
|
||||||
- **videofy.me**
|
- **videofy.me**
|
||||||
- **VideoMega**
|
- **VideoMega** (Currently broken)
|
||||||
- **videomore**
|
- **videomore**
|
||||||
- **videomore:season**
|
- **videomore:season**
|
||||||
- **videomore:video**
|
- **videomore:video**
|
||||||
- **VideoPremium**
|
- **VideoPremium**
|
||||||
- **VideoTt**: video.tt - Your True Tube
|
- **VideoTt**: video.tt - Your True Tube (Currently broken)
|
||||||
- **videoweed**: VideoWeed
|
- **videoweed**: VideoWeed
|
||||||
- **Vidme**
|
- **vidme**
|
||||||
|
- **vidme:user**
|
||||||
|
- **vidme:user:likes**
|
||||||
- **Vidzi**
|
- **Vidzi**
|
||||||
- **vier**
|
- **vier**
|
||||||
- **vier:videos**
|
- **vier:videos**
|
||||||
@@ -695,6 +724,7 @@
|
|||||||
- **WebOfStories**
|
- **WebOfStories**
|
||||||
- **WebOfStoriesPlaylist**
|
- **WebOfStoriesPlaylist**
|
||||||
- **Weibo**
|
- **Weibo**
|
||||||
|
- **WeiqiTV**: WQTV
|
||||||
- **wholecloud**: WholeCloud
|
- **wholecloud**: WholeCloud
|
||||||
- **Wimp**
|
- **Wimp**
|
||||||
- **Wistia**
|
- **Wistia**
|
||||||
@@ -746,3 +776,4 @@
|
|||||||
- **ZDFChannel**
|
- **ZDFChannel**
|
||||||
- **zingmp3:album**: mp3.zing.vn albums
|
- **zingmp3:album**: mp3.zing.vn albums
|
||||||
- **zingmp3:song**: mp3.zing.vn songs
|
- **zingmp3:song**: mp3.zing.vn songs
|
||||||
|
- **ZippCast**
|
||||||
|
@@ -12,8 +12,9 @@ import copy
|
|||||||
|
|
||||||
from test.helper import FakeYDL, assertRegexpMatches
|
from test.helper import FakeYDL, assertRegexpMatches
|
||||||
from youtube_dl import YoutubeDL
|
from youtube_dl import YoutubeDL
|
||||||
from youtube_dl.compat import compat_str
|
from youtube_dl.compat import compat_str, compat_urllib_error
|
||||||
from youtube_dl.extractor import YoutubeIE
|
from youtube_dl.extractor import YoutubeIE
|
||||||
|
from youtube_dl.extractor.common import InfoExtractor
|
||||||
from youtube_dl.postprocessor.common import PostProcessor
|
from youtube_dl.postprocessor.common import PostProcessor
|
||||||
from youtube_dl.utils import ExtractorError, match_filter_func
|
from youtube_dl.utils import ExtractorError, match_filter_func
|
||||||
|
|
||||||
@@ -221,6 +222,16 @@ class TestFormatSelection(unittest.TestCase):
|
|||||||
downloaded = ydl.downloaded_info_dicts[0]
|
downloaded = ydl.downloaded_info_dicts[0]
|
||||||
self.assertEqual(downloaded['format_id'], 'dash-video-low')
|
self.assertEqual(downloaded['format_id'], 'dash-video-low')
|
||||||
|
|
||||||
|
formats = [
|
||||||
|
{'format_id': 'vid-vcodec-dot', 'ext': 'mp4', 'preference': 1, 'vcodec': 'avc1.123456', 'acodec': 'none', 'url': TEST_URL},
|
||||||
|
]
|
||||||
|
info_dict = _make_result(formats)
|
||||||
|
|
||||||
|
ydl = YDL({'format': 'bestvideo[vcodec=avc1.123456]'})
|
||||||
|
ydl.process_ie_result(info_dict.copy())
|
||||||
|
downloaded = ydl.downloaded_info_dicts[0]
|
||||||
|
self.assertEqual(downloaded['format_id'], 'vid-vcodec-dot')
|
||||||
|
|
||||||
def test_youtube_format_selection(self):
|
def test_youtube_format_selection(self):
|
||||||
order = [
|
order = [
|
||||||
'38', '37', '46', '22', '45', '35', '44', '18', '34', '43', '6', '5', '36', '17', '13',
|
'38', '37', '46', '22', '45', '35', '44', '18', '34', '43', '6', '5', '36', '17', '13',
|
||||||
@@ -237,6 +248,17 @@ class TestFormatSelection(unittest.TestCase):
|
|||||||
|
|
||||||
def format_info(f_id):
|
def format_info(f_id):
|
||||||
info = YoutubeIE._formats[f_id].copy()
|
info = YoutubeIE._formats[f_id].copy()
|
||||||
|
|
||||||
|
# XXX: In real cases InfoExtractor._parse_mpd_formats() fills up 'acodec'
|
||||||
|
# and 'vcodec', while in tests such information is incomplete since
|
||||||
|
# commit a6c2c24479e5f4827ceb06f64d855329c0a6f593
|
||||||
|
# test_YoutubeDL.test_youtube_format_selection is broken without
|
||||||
|
# this fix
|
||||||
|
if 'acodec' in info and 'vcodec' not in info:
|
||||||
|
info['vcodec'] = 'none'
|
||||||
|
elif 'vcodec' in info and 'acodec' not in info:
|
||||||
|
info['acodec'] = 'none'
|
||||||
|
|
||||||
info['format_id'] = f_id
|
info['format_id'] = f_id
|
||||||
info['url'] = 'url:' + f_id
|
info['url'] = 'url:' + f_id
|
||||||
return info
|
return info
|
||||||
@@ -631,6 +653,47 @@ class TestYoutubeDL(unittest.TestCase):
|
|||||||
result = get_ids({'playlist_items': '10'})
|
result = get_ids({'playlist_items': '10'})
|
||||||
self.assertEqual(result, [])
|
self.assertEqual(result, [])
|
||||||
|
|
||||||
|
def test_urlopen_no_file_protocol(self):
|
||||||
|
# see https://github.com/rg3/youtube-dl/issues/8227
|
||||||
|
ydl = YDL()
|
||||||
|
self.assertRaises(compat_urllib_error.URLError, ydl.urlopen, 'file:///etc/passwd')
|
||||||
|
|
||||||
|
def test_do_not_override_ie_key_in_url_transparent(self):
|
||||||
|
ydl = YDL()
|
||||||
|
|
||||||
|
class Foo1IE(InfoExtractor):
|
||||||
|
_VALID_URL = r'foo1:'
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
return {
|
||||||
|
'_type': 'url_transparent',
|
||||||
|
'url': 'foo2:',
|
||||||
|
'ie_key': 'Foo2',
|
||||||
|
}
|
||||||
|
|
||||||
|
class Foo2IE(InfoExtractor):
|
||||||
|
_VALID_URL = r'foo2:'
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
return {
|
||||||
|
'_type': 'url',
|
||||||
|
'url': 'foo3:',
|
||||||
|
'ie_key': 'Foo3',
|
||||||
|
}
|
||||||
|
|
||||||
|
class Foo3IE(InfoExtractor):
|
||||||
|
_VALID_URL = r'foo3:'
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
return _make_result([{'url': TEST_URL}])
|
||||||
|
|
||||||
|
ydl.add_info_extractor(Foo1IE(ydl))
|
||||||
|
ydl.add_info_extractor(Foo2IE(ydl))
|
||||||
|
ydl.add_info_extractor(Foo3IE(ydl))
|
||||||
|
ydl.extract_info('foo1:')
|
||||||
|
downloaded = ydl.downloaded_info_dicts[0]
|
||||||
|
self.assertEqual(downloaded['url'], TEST_URL)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@@ -56,7 +56,7 @@ class TestAllURLsMatching(unittest.TestCase):
|
|||||||
assertChannel('https://www.youtube.com/channel/HCtnHdj3df7iM/videos')
|
assertChannel('https://www.youtube.com/channel/HCtnHdj3df7iM/videos')
|
||||||
|
|
||||||
def test_youtube_user_matching(self):
|
def test_youtube_user_matching(self):
|
||||||
self.assertMatch('www.youtube.com/NASAgovVideo/videos', ['youtube:user'])
|
self.assertMatch('http://www.youtube.com/NASAgovVideo/videos', ['youtube:user'])
|
||||||
|
|
||||||
def test_youtube_feeds(self):
|
def test_youtube_feeds(self):
|
||||||
self.assertMatch('https://www.youtube.com/feed/watch_later', ['youtube:watchlater'])
|
self.assertMatch('https://www.youtube.com/feed/watch_later', ['youtube:watchlater'])
|
||||||
|
@@ -21,7 +21,7 @@ from youtube_dl.extractor import (
|
|||||||
NPOIE,
|
NPOIE,
|
||||||
ComedyCentralIE,
|
ComedyCentralIE,
|
||||||
NRKTVIE,
|
NRKTVIE,
|
||||||
RaiIE,
|
RaiTVIE,
|
||||||
VikiIE,
|
VikiIE,
|
||||||
ThePlatformIE,
|
ThePlatformIE,
|
||||||
ThePlatformFeedIE,
|
ThePlatformFeedIE,
|
||||||
@@ -65,16 +65,16 @@ class TestYoutubeSubtitles(BaseTestSubtitles):
|
|||||||
self.DL.params['allsubtitles'] = True
|
self.DL.params['allsubtitles'] = True
|
||||||
subtitles = self.getSubtitles()
|
subtitles = self.getSubtitles()
|
||||||
self.assertEqual(len(subtitles.keys()), 13)
|
self.assertEqual(len(subtitles.keys()), 13)
|
||||||
self.assertEqual(md5(subtitles['en']), '4cd9278a35ba2305f47354ee13472260')
|
self.assertEqual(md5(subtitles['en']), '3cb210999d3e021bd6c7f0ea751eab06')
|
||||||
self.assertEqual(md5(subtitles['it']), '164a51f16f260476a05b50fe4c2f161d')
|
self.assertEqual(md5(subtitles['it']), '6d752b98c31f1cf8d597050c7a2cb4b5')
|
||||||
for lang in ['it', 'fr', 'de']:
|
for lang in ['fr', 'de']:
|
||||||
self.assertTrue(subtitles.get(lang) is not None, 'Subtitles for \'%s\' not extracted' % lang)
|
self.assertTrue(subtitles.get(lang) is not None, 'Subtitles for \'%s\' not extracted' % lang)
|
||||||
|
|
||||||
def test_youtube_subtitles_sbv_format(self):
|
def test_youtube_subtitles_ttml_format(self):
|
||||||
self.DL.params['writesubtitles'] = True
|
self.DL.params['writesubtitles'] = True
|
||||||
self.DL.params['subtitlesformat'] = 'sbv'
|
self.DL.params['subtitlesformat'] = 'ttml'
|
||||||
subtitles = self.getSubtitles()
|
subtitles = self.getSubtitles()
|
||||||
self.assertEqual(md5(subtitles['en']), '13aeaa0c245a8bed9a451cb643e3ad8b')
|
self.assertEqual(md5(subtitles['en']), 'e306f8c42842f723447d9f63ad65df54')
|
||||||
|
|
||||||
def test_youtube_subtitles_vtt_format(self):
|
def test_youtube_subtitles_vtt_format(self):
|
||||||
self.DL.params['writesubtitles'] = True
|
self.DL.params['writesubtitles'] = True
|
||||||
@@ -260,7 +260,7 @@ class TestNRKSubtitles(BaseTestSubtitles):
|
|||||||
|
|
||||||
class TestRaiSubtitles(BaseTestSubtitles):
|
class TestRaiSubtitles(BaseTestSubtitles):
|
||||||
url = 'http://www.rai.tv/dl/RaiTV/programmi/media/ContentItem-cb27157f-9dd0-4aee-b788-b1f67643a391.html'
|
url = 'http://www.rai.tv/dl/RaiTV/programmi/media/ContentItem-cb27157f-9dd0-4aee-b788-b1f67643a391.html'
|
||||||
IE = RaiIE
|
IE = RaiTVIE
|
||||||
|
|
||||||
def test_allsubtitles(self):
|
def test_allsubtitles(self):
|
||||||
self.DL.params['writesubtitles'] = True
|
self.DL.params['writesubtitles'] = True
|
||||||
|
30
test/test_update.py
Normal file
30
test/test_update.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
# Allow direct execution
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
|
||||||
|
import json
|
||||||
|
from youtube_dl.update import rsa_verify
|
||||||
|
|
||||||
|
|
||||||
|
class TestUpdate(unittest.TestCase):
|
||||||
|
def test_rsa_verify(self):
|
||||||
|
UPDATES_RSA_KEY = (0x9d60ee4d8f805312fdb15a62f87b95bd66177b91df176765d13514a0f1754bcd2057295c5b6f1d35daa6742c3ffc9a82d3e118861c207995a8031e151d863c9927e304576bc80692bc8e094896fcf11b66f3e29e04e3a71e9a11558558acea1840aec37fc396fb6b65dc81a1c4144e03bd1c011de62e3f1357b327d08426fe93, 65537)
|
||||||
|
with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'versions.json'), 'rb') as f:
|
||||||
|
versions_info = f.read().decode()
|
||||||
|
versions_info = json.loads(versions_info)
|
||||||
|
signature = versions_info['signature']
|
||||||
|
del versions_info['signature']
|
||||||
|
self.assertTrue(rsa_verify(
|
||||||
|
json.dumps(versions_info, sort_keys=True).encode('utf-8'),
|
||||||
|
signature, UPDATES_RSA_KEY))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
@@ -22,6 +22,7 @@ from youtube_dl.utils import (
|
|||||||
DateRange,
|
DateRange,
|
||||||
detect_exe_version,
|
detect_exe_version,
|
||||||
determine_ext,
|
determine_ext,
|
||||||
|
dict_get,
|
||||||
encode_compat_str,
|
encode_compat_str,
|
||||||
encodeFilename,
|
encodeFilename,
|
||||||
escape_rfc3986,
|
escape_rfc3986,
|
||||||
@@ -450,6 +451,28 @@ class TestUtil(unittest.TestCase):
|
|||||||
data = urlencode_postdata({'username': 'foo@bar.com', 'password': '1234'})
|
data = urlencode_postdata({'username': 'foo@bar.com', 'password': '1234'})
|
||||||
self.assertTrue(isinstance(data, bytes))
|
self.assertTrue(isinstance(data, bytes))
|
||||||
|
|
||||||
|
def test_dict_get(self):
|
||||||
|
FALSE_VALUES = {
|
||||||
|
'none': None,
|
||||||
|
'false': False,
|
||||||
|
'zero': 0,
|
||||||
|
'empty_string': '',
|
||||||
|
'empty_list': [],
|
||||||
|
}
|
||||||
|
d = FALSE_VALUES.copy()
|
||||||
|
d['a'] = 42
|
||||||
|
self.assertEqual(dict_get(d, 'a'), 42)
|
||||||
|
self.assertEqual(dict_get(d, 'b'), None)
|
||||||
|
self.assertEqual(dict_get(d, 'b', 42), 42)
|
||||||
|
self.assertEqual(dict_get(d, ('a', )), 42)
|
||||||
|
self.assertEqual(dict_get(d, ('b', 'a', )), 42)
|
||||||
|
self.assertEqual(dict_get(d, ('b', 'c', 'a', 'd', )), 42)
|
||||||
|
self.assertEqual(dict_get(d, ('b', 'c', )), None)
|
||||||
|
self.assertEqual(dict_get(d, ('b', 'c', ), 42), 42)
|
||||||
|
for key, false_value in FALSE_VALUES.items():
|
||||||
|
self.assertEqual(dict_get(d, ('b', 'c', key, )), None)
|
||||||
|
self.assertEqual(dict_get(d, ('b', 'c', key, ), skip_false_values=False), false_value)
|
||||||
|
|
||||||
def test_encode_compat_str(self):
|
def test_encode_compat_str(self):
|
||||||
self.assertEqual(encode_compat_str(b'\xd1\x82\xd0\xb5\xd1\x81\xd1\x82', 'utf-8'), 'тест')
|
self.assertEqual(encode_compat_str(b'\xd1\x82\xd0\xb5\xd1\x81\xd1\x82', 'utf-8'), 'тест')
|
||||||
self.assertEqual(encode_compat_str('тест', 'utf-8'), 'тест')
|
self.assertEqual(encode_compat_str('тест', 'utf-8'), 'тест')
|
||||||
@@ -471,6 +494,10 @@ class TestUtil(unittest.TestCase):
|
|||||||
d = json.loads(stripped)
|
d = json.loads(stripped)
|
||||||
self.assertEqual(d, {'STATUS': 'OK'})
|
self.assertEqual(d, {'STATUS': 'OK'})
|
||||||
|
|
||||||
|
stripped = strip_jsonp('ps.embedHandler({"status": "success"});')
|
||||||
|
d = json.loads(stripped)
|
||||||
|
self.assertEqual(d, {'status': 'success'})
|
||||||
|
|
||||||
def test_uppercase_escape(self):
|
def test_uppercase_escape(self):
|
||||||
self.assertEqual(uppercase_escape('aä'), 'aä')
|
self.assertEqual(uppercase_escape('aä'), 'aä')
|
||||||
self.assertEqual(uppercase_escape('\\U0001d550'), '𝕐')
|
self.assertEqual(uppercase_escape('\\U0001d550'), '𝕐')
|
||||||
|
@@ -66,7 +66,7 @@ class TestAnnotations(unittest.TestCase):
|
|||||||
textTag = a.find('TEXT')
|
textTag = a.find('TEXT')
|
||||||
text = textTag.text
|
text = textTag.text
|
||||||
self.assertTrue(text in expected) # assertIn only added in python 2.7
|
self.assertTrue(text in expected) # assertIn only added in python 2.7
|
||||||
# remove the first occurance, there could be more than one annotation with the same text
|
# remove the first occurrence, there could be more than one annotation with the same text
|
||||||
expected.remove(text)
|
expected.remove(text)
|
||||||
# We should have seen (and removed) all the expected annotation texts.
|
# We should have seen (and removed) all the expected annotation texts.
|
||||||
self.assertEqual(len(expected), 0, 'Not all expected annotations were found.')
|
self.assertEqual(len(expected), 0, 'Not all expected annotations were found.')
|
||||||
|
@@ -34,7 +34,7 @@ class TestYoutubeLists(unittest.TestCase):
|
|||||||
ie = YoutubePlaylistIE(dl)
|
ie = YoutubePlaylistIE(dl)
|
||||||
# TODO find a > 100 (paginating?) videos course
|
# TODO find a > 100 (paginating?) videos course
|
||||||
result = ie.extract('https://www.youtube.com/course?list=ECUl4u3cNGP61MdtwGTqZA0MreSaDybji8')
|
result = ie.extract('https://www.youtube.com/course?list=ECUl4u3cNGP61MdtwGTqZA0MreSaDybji8')
|
||||||
entries = result['entries']
|
entries = list(result['entries'])
|
||||||
self.assertEqual(YoutubeIE().extract_id(entries[0]['url']), 'j9WZyLZCBzs')
|
self.assertEqual(YoutubeIE().extract_id(entries[0]['url']), 'j9WZyLZCBzs')
|
||||||
self.assertEqual(len(entries), 25)
|
self.assertEqual(len(entries), 25)
|
||||||
self.assertEqual(YoutubeIE().extract_id(entries[-1]['url']), 'rYefUsYuEp0')
|
self.assertEqual(YoutubeIE().extract_id(entries[-1]['url']), 'rYefUsYuEp0')
|
||||||
|
34
test/versions.json
Normal file
34
test/versions.json
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"latest": "2013.01.06",
|
||||||
|
"signature": "72158cdba391628569ffdbea259afbcf279bbe3d8aeb7492690735dc1cfa6afa754f55c61196f3871d429599ab22f2667f1fec98865527b32632e7f4b3675a7ef0f0fbe084d359256ae4bba68f0d33854e531a70754712f244be71d4b92e664302aa99653ee4df19800d955b6c4149cd2b3f24288d6e4b40b16126e01f4c8ce6",
|
||||||
|
"versions": {
|
||||||
|
"2013.01.02": {
|
||||||
|
"bin": [
|
||||||
|
"http://youtube-dl.org/downloads/2013.01.02/youtube-dl",
|
||||||
|
"f5b502f8aaa77675c4884938b1e4871ebca2611813a0c0e74f60c0fbd6dcca6b"
|
||||||
|
],
|
||||||
|
"exe": [
|
||||||
|
"http://youtube-dl.org/downloads/2013.01.02/youtube-dl.exe",
|
||||||
|
"75fa89d2ce297d102ff27675aa9d92545bbc91013f52ec52868c069f4f9f0422"
|
||||||
|
],
|
||||||
|
"tar": [
|
||||||
|
"http://youtube-dl.org/downloads/2013.01.02/youtube-dl-2013.01.02.tar.gz",
|
||||||
|
"6a66d022ac8e1c13da284036288a133ec8dba003b7bd3a5179d0c0daca8c8196"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"2013.01.06": {
|
||||||
|
"bin": [
|
||||||
|
"http://youtube-dl.org/downloads/2013.01.06/youtube-dl",
|
||||||
|
"64b6ed8865735c6302e836d4d832577321b4519aa02640dc508580c1ee824049"
|
||||||
|
],
|
||||||
|
"exe": [
|
||||||
|
"http://youtube-dl.org/downloads/2013.01.06/youtube-dl.exe",
|
||||||
|
"58609baf91e4389d36e3ba586e21dab882daaaee537e4448b1265392ae86ff84"
|
||||||
|
],
|
||||||
|
"tar": [
|
||||||
|
"http://youtube-dl.org/downloads/2013.01.06/youtube-dl-2013.01.06.tar.gz",
|
||||||
|
"fe77ab20a95d980ed17a659aa67e371fdd4d656d19c4c7950e7b720b0c2f1a86"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -46,6 +46,7 @@ from .utils import (
|
|||||||
DateRange,
|
DateRange,
|
||||||
DEFAULT_OUTTMPL,
|
DEFAULT_OUTTMPL,
|
||||||
determine_ext,
|
determine_ext,
|
||||||
|
determine_protocol,
|
||||||
DownloadError,
|
DownloadError,
|
||||||
encode_compat_str,
|
encode_compat_str,
|
||||||
encodeFilename,
|
encodeFilename,
|
||||||
@@ -262,7 +263,7 @@ class YoutubeDL(object):
|
|||||||
the downloader (see youtube_dl/downloader/common.py):
|
the downloader (see youtube_dl/downloader/common.py):
|
||||||
nopart, updatetime, buffersize, ratelimit, min_filesize, max_filesize, test,
|
nopart, updatetime, buffersize, ratelimit, min_filesize, max_filesize, test,
|
||||||
noresizebuffer, retries, continuedl, noprogress, consoletitle,
|
noresizebuffer, retries, continuedl, noprogress, consoletitle,
|
||||||
xattr_set_filesize, external_downloader_args.
|
xattr_set_filesize, external_downloader_args, hls_use_mpegts.
|
||||||
|
|
||||||
The following options are used by the post processors:
|
The following options are used by the post processors:
|
||||||
prefer_ffmpeg: If True, use ffmpeg instead of avconv if both are available,
|
prefer_ffmpeg: If True, use ffmpeg instead of avconv if both are available,
|
||||||
@@ -706,7 +707,6 @@ class YoutubeDL(object):
|
|||||||
It will also download the videos if 'download'.
|
It will also download the videos if 'download'.
|
||||||
Returns the resolved ie_result.
|
Returns the resolved ie_result.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
result_type = ie_result.get('_type', 'video')
|
result_type = ie_result.get('_type', 'video')
|
||||||
|
|
||||||
if result_type in ('url', 'url_transparent'):
|
if result_type in ('url', 'url_transparent'):
|
||||||
@@ -735,7 +735,7 @@ class YoutubeDL(object):
|
|||||||
|
|
||||||
force_properties = dict(
|
force_properties = dict(
|
||||||
(k, v) for k, v in ie_result.items() if v is not None)
|
(k, v) for k, v in ie_result.items() if v is not None)
|
||||||
for f in ('_type', 'url'):
|
for f in ('_type', 'url', 'ie_key'):
|
||||||
if f in force_properties:
|
if f in force_properties:
|
||||||
del force_properties[f]
|
del force_properties[f]
|
||||||
new_result = info.copy()
|
new_result = info.copy()
|
||||||
@@ -898,11 +898,14 @@ class YoutubeDL(object):
|
|||||||
STR_OPERATORS = {
|
STR_OPERATORS = {
|
||||||
'=': operator.eq,
|
'=': operator.eq,
|
||||||
'!=': operator.ne,
|
'!=': operator.ne,
|
||||||
|
'^=': lambda attr, value: attr.startswith(value),
|
||||||
|
'$=': lambda attr, value: attr.endswith(value),
|
||||||
|
'*=': lambda attr, value: value in attr,
|
||||||
}
|
}
|
||||||
str_operator_rex = re.compile(r'''(?x)
|
str_operator_rex = re.compile(r'''(?x)
|
||||||
\s*(?P<key>ext|acodec|vcodec|container|protocol)
|
\s*(?P<key>ext|acodec|vcodec|container|protocol)
|
||||||
\s*(?P<op>%s)(?P<none_inclusive>\s*\?)?
|
\s*(?P<op>%s)(?P<none_inclusive>\s*\?)?
|
||||||
\s*(?P<value>[a-zA-Z0-9_-]+)
|
\s*(?P<value>[a-zA-Z0-9._-]+)
|
||||||
\s*$
|
\s*$
|
||||||
''' % '|'.join(map(re.escape, STR_OPERATORS.keys())))
|
''' % '|'.join(map(re.escape, STR_OPERATORS.keys())))
|
||||||
m = str_operator_rex.search(filter_spec)
|
m = str_operator_rex.search(filter_spec)
|
||||||
@@ -1244,6 +1247,12 @@ class YoutubeDL(object):
|
|||||||
except (ValueError, OverflowError, OSError):
|
except (ValueError, OverflowError, OSError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# Auto generate title fields corresponding to the *_number fields when missing
|
||||||
|
# in order to always have clean titles. This is very common for TV series.
|
||||||
|
for field in ('chapter', 'season', 'episode'):
|
||||||
|
if info_dict.get('%s_number' % field) is not None and not info_dict.get(field):
|
||||||
|
info_dict[field] = '%s %d' % (field.capitalize(), info_dict['%s_number' % field])
|
||||||
|
|
||||||
subtitles = info_dict.get('subtitles')
|
subtitles = info_dict.get('subtitles')
|
||||||
if subtitles:
|
if subtitles:
|
||||||
for _, subtitle in subtitles.items():
|
for _, subtitle in subtitles.items():
|
||||||
@@ -1300,6 +1309,10 @@ class YoutubeDL(object):
|
|||||||
# Automatically determine file extension if missing
|
# Automatically determine file extension if missing
|
||||||
if 'ext' not in format:
|
if 'ext' not in format:
|
||||||
format['ext'] = determine_ext(format['url']).lower()
|
format['ext'] = determine_ext(format['url']).lower()
|
||||||
|
# Automatically determine protocol if missing (useful for format
|
||||||
|
# selection purposes)
|
||||||
|
if 'protocol' not in format:
|
||||||
|
format['protocol'] = determine_protocol(format)
|
||||||
# Add HTTP headers, so that external programs can use them from the
|
# Add HTTP headers, so that external programs can use them from the
|
||||||
# json output
|
# json output
|
||||||
full_format_info = info_dict.copy()
|
full_format_info = info_dict.copy()
|
||||||
@@ -1312,7 +1325,7 @@ class YoutubeDL(object):
|
|||||||
# only set the 'formats' fields if the original info_dict list them
|
# only set the 'formats' fields if the original info_dict list them
|
||||||
# otherwise we end up with a circular reference, the first (and unique)
|
# otherwise we end up with a circular reference, the first (and unique)
|
||||||
# element in the 'formats' field in info_dict is info_dict itself,
|
# element in the 'formats' field in info_dict is info_dict itself,
|
||||||
# wich can't be exported to json
|
# which can't be exported to json
|
||||||
info_dict['formats'] = formats
|
info_dict['formats'] = formats
|
||||||
if self.params.get('listformats'):
|
if self.params.get('listformats'):
|
||||||
self.list_formats(info_dict)
|
self.list_formats(info_dict)
|
||||||
@@ -1791,6 +1804,10 @@ class YoutubeDL(object):
|
|||||||
res = ''
|
res = ''
|
||||||
if fdict.get('ext') in ['f4f', 'f4m']:
|
if fdict.get('ext') in ['f4f', 'f4m']:
|
||||||
res += '(unsupported) '
|
res += '(unsupported) '
|
||||||
|
if fdict.get('language'):
|
||||||
|
if res:
|
||||||
|
res += ' '
|
||||||
|
res += '[%s]' % fdict['language']
|
||||||
if fdict.get('format_note') is not None:
|
if fdict.get('format_note') is not None:
|
||||||
res += fdict['format_note'] + ' '
|
res += fdict['format_note'] + ' '
|
||||||
if fdict.get('tbr') is not None:
|
if fdict.get('tbr') is not None:
|
||||||
@@ -1982,8 +1999,19 @@ class YoutubeDL(object):
|
|||||||
https_handler = make_HTTPS_handler(self.params, debuglevel=debuglevel)
|
https_handler = make_HTTPS_handler(self.params, debuglevel=debuglevel)
|
||||||
ydlh = YoutubeDLHandler(self.params, debuglevel=debuglevel)
|
ydlh = YoutubeDLHandler(self.params, debuglevel=debuglevel)
|
||||||
data_handler = compat_urllib_request_DataHandler()
|
data_handler = compat_urllib_request_DataHandler()
|
||||||
|
|
||||||
|
# When passing our own FileHandler instance, build_opener won't add the
|
||||||
|
# default FileHandler and allows us to disable the file protocol, which
|
||||||
|
# can be used for malicious purposes (see
|
||||||
|
# https://github.com/rg3/youtube-dl/issues/8227)
|
||||||
|
file_handler = compat_urllib_request.FileHandler()
|
||||||
|
|
||||||
|
def file_open(*args, **kwargs):
|
||||||
|
raise compat_urllib_error.URLError('file:// scheme is explicitly disabled in youtube-dl for security reasons')
|
||||||
|
file_handler.file_open = file_open
|
||||||
|
|
||||||
opener = compat_urllib_request.build_opener(
|
opener = compat_urllib_request.build_opener(
|
||||||
proxy_handler, https_handler, cookie_processor, ydlh, data_handler)
|
proxy_handler, https_handler, cookie_processor, ydlh, data_handler, file_handler)
|
||||||
|
|
||||||
# Delete the default user-agent header, which would otherwise apply in
|
# Delete the default user-agent header, which would otherwise apply in
|
||||||
# cases where our custom HTTP handler doesn't come into play
|
# cases where our custom HTTP handler doesn't come into play
|
||||||
|
@@ -369,6 +369,7 @@ def _real_main(argv=None):
|
|||||||
'no_color': opts.no_color,
|
'no_color': opts.no_color,
|
||||||
'ffmpeg_location': opts.ffmpeg_location,
|
'ffmpeg_location': opts.ffmpeg_location,
|
||||||
'hls_prefer_native': opts.hls_prefer_native,
|
'hls_prefer_native': opts.hls_prefer_native,
|
||||||
|
'hls_use_mpegts': opts.hls_use_mpegts,
|
||||||
'external_downloader_args': external_downloader_args,
|
'external_downloader_args': external_downloader_args,
|
||||||
'postprocessor_args': postprocessor_args,
|
'postprocessor_args': postprocessor_args,
|
||||||
'cn_verification_proxy': opts.cn_verification_proxy,
|
'cn_verification_proxy': opts.cn_verification_proxy,
|
||||||
|
@@ -433,7 +433,7 @@ if sys.version_info < (3, 0) and sys.platform == 'win32':
|
|||||||
else:
|
else:
|
||||||
compat_getpass = getpass.getpass
|
compat_getpass = getpass.getpass
|
||||||
|
|
||||||
# Old 2.6 and 2.7 releases require kwargs to be bytes
|
# Python < 2.6.5 require kwargs to be bytes
|
||||||
try:
|
try:
|
||||||
def _testfunc(x):
|
def _testfunc(x):
|
||||||
pass
|
pass
|
||||||
|
@@ -45,6 +45,7 @@ class FileDownloader(object):
|
|||||||
(experimental)
|
(experimental)
|
||||||
external_downloader_args: A list of additional command-line arguments for the
|
external_downloader_args: A list of additional command-line arguments for the
|
||||||
external downloader.
|
external downloader.
|
||||||
|
hls_use_mpegts: Use the mpegts container for HLS videos.
|
||||||
|
|
||||||
Subclasses of this one must re-define the real_download method.
|
Subclasses of this one must re-define the real_download method.
|
||||||
"""
|
"""
|
||||||
@@ -295,7 +296,7 @@ class FileDownloader(object):
|
|||||||
|
|
||||||
def report_retry(self, count, retries):
|
def report_retry(self, count, retries):
|
||||||
"""Report retry in case of HTTP error 5xx"""
|
"""Report retry in case of HTTP error 5xx"""
|
||||||
self.to_screen('[download] Got server HTTP error. Retrying (attempt %d of %d)...' % (count, retries))
|
self.to_screen('[download] Got server HTTP error. Retrying (attempt %d of %.0f)...' % (count, retries))
|
||||||
|
|
||||||
def report_file_already_downloaded(self, file_name):
|
def report_file_already_downloaded(self, file_name):
|
||||||
"""Report file has already been fully downloaded."""
|
"""Report file has already been fully downloaded."""
|
||||||
|
@@ -40,9 +40,10 @@ class DashSegmentsFD(FileDownloader):
|
|||||||
return '%s%s%s' % (base_url, '' if base_url.endswith('/') else '/', target_url)
|
return '%s%s%s' % (base_url, '' if base_url.endswith('/') else '/', target_url)
|
||||||
|
|
||||||
with open(tmpfilename, 'wb') as outf:
|
with open(tmpfilename, 'wb') as outf:
|
||||||
append_url_to_file(
|
if info_dict.get('initialization_url'):
|
||||||
outf, combine_url(base_url, info_dict['initialization_url']),
|
append_url_to_file(
|
||||||
'initialization segment')
|
outf, combine_url(base_url, info_dict['initialization_url']),
|
||||||
|
'initialization segment')
|
||||||
for i, segment_url in enumerate(segment_urls):
|
for i, segment_url in enumerate(segment_urls):
|
||||||
segment_len = append_url_to_file(
|
segment_len = append_url_to_file(
|
||||||
outf, combine_url(base_url, segment_url),
|
outf, combine_url(base_url, segment_url),
|
||||||
|
@@ -273,15 +273,21 @@ class F4mFD(FragmentFD):
|
|||||||
return fragments_list
|
return fragments_list
|
||||||
|
|
||||||
def _parse_bootstrap_node(self, node, base_url):
|
def _parse_bootstrap_node(self, node, base_url):
|
||||||
if node.text is None:
|
# Sometimes non empty inline bootstrap info can be specified along
|
||||||
|
# with bootstrap url attribute (e.g. dummy inline bootstrap info
|
||||||
|
# contains whitespace characters in [1]). We will prefer bootstrap
|
||||||
|
# url over inline bootstrap info when present.
|
||||||
|
# 1. http://live-1-1.rutube.ru/stream/1024/HDS/SD/C2NKsS85HQNckgn5HdEmOQ/1454167650/S-s604419906/move/four/dirs/upper/1024-576p.f4m
|
||||||
|
bootstrap_url = node.get('url')
|
||||||
|
if bootstrap_url:
|
||||||
bootstrap_url = compat_urlparse.urljoin(
|
bootstrap_url = compat_urlparse.urljoin(
|
||||||
base_url, node.attrib['url'])
|
base_url, bootstrap_url)
|
||||||
boot_info = self._get_bootstrap_from_url(bootstrap_url)
|
boot_info = self._get_bootstrap_from_url(bootstrap_url)
|
||||||
else:
|
else:
|
||||||
bootstrap_url = None
|
bootstrap_url = None
|
||||||
bootstrap = base64.b64decode(node.text.encode('ascii'))
|
bootstrap = base64.b64decode(node.text.encode('ascii'))
|
||||||
boot_info = read_bootstrap_info(bootstrap)
|
boot_info = read_bootstrap_info(bootstrap)
|
||||||
return (boot_info, bootstrap_url)
|
return boot_info, bootstrap_url
|
||||||
|
|
||||||
def real_download(self, filename, info_dict):
|
def real_download(self, filename, info_dict):
|
||||||
man_url = info_dict['url']
|
man_url = info_dict['url']
|
||||||
@@ -316,7 +322,8 @@ class F4mFD(FragmentFD):
|
|||||||
metadata = None
|
metadata = None
|
||||||
|
|
||||||
fragments_list = build_fragments_list(boot_info)
|
fragments_list = build_fragments_list(boot_info)
|
||||||
if self.params.get('test', False):
|
test = self.params.get('test', False)
|
||||||
|
if test:
|
||||||
# We only download the first fragment
|
# We only download the first fragment
|
||||||
fragments_list = fragments_list[:1]
|
fragments_list = fragments_list[:1]
|
||||||
total_frags = len(fragments_list)
|
total_frags = len(fragments_list)
|
||||||
@@ -326,6 +333,7 @@ class F4mFD(FragmentFD):
|
|||||||
ctx = {
|
ctx = {
|
||||||
'filename': filename,
|
'filename': filename,
|
||||||
'total_frags': total_frags,
|
'total_frags': total_frags,
|
||||||
|
'live': live,
|
||||||
}
|
}
|
||||||
|
|
||||||
self._prepare_frag_download(ctx)
|
self._prepare_frag_download(ctx)
|
||||||
@@ -380,7 +388,7 @@ class F4mFD(FragmentFD):
|
|||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
if not fragments_list and live and bootstrap_url:
|
if not fragments_list and not test and live and bootstrap_url:
|
||||||
fragments_list = self._update_live_fragments(bootstrap_url, frag_i)
|
fragments_list = self._update_live_fragments(bootstrap_url, frag_i)
|
||||||
total_frags += len(fragments_list)
|
total_frags += len(fragments_list)
|
||||||
if fragments_list and (fragments_list[0][1] > frag_i + 1):
|
if fragments_list and (fragments_list[0][1] > frag_i + 1):
|
||||||
|
@@ -26,7 +26,11 @@ class FragmentFD(FileDownloader):
|
|||||||
self._start_frag_download(ctx)
|
self._start_frag_download(ctx)
|
||||||
|
|
||||||
def _prepare_frag_download(self, ctx):
|
def _prepare_frag_download(self, ctx):
|
||||||
self.to_screen('[%s] Total fragments: %d' % (self.FD_NAME, ctx['total_frags']))
|
if 'live' not in ctx:
|
||||||
|
ctx['live'] = False
|
||||||
|
self.to_screen(
|
||||||
|
'[%s] Total fragments: %s'
|
||||||
|
% (self.FD_NAME, ctx['total_frags'] if not ctx['live'] else 'unknown (live)'))
|
||||||
self.report_destination(ctx['filename'])
|
self.report_destination(ctx['filename'])
|
||||||
dl = HttpQuietDownloader(
|
dl = HttpQuietDownloader(
|
||||||
self.ydl,
|
self.ydl,
|
||||||
@@ -59,37 +63,44 @@ class FragmentFD(FileDownloader):
|
|||||||
'filename': ctx['filename'],
|
'filename': ctx['filename'],
|
||||||
'tmpfilename': ctx['tmpfilename'],
|
'tmpfilename': ctx['tmpfilename'],
|
||||||
}
|
}
|
||||||
|
|
||||||
start = time.time()
|
start = time.time()
|
||||||
ctx['started'] = start
|
ctx.update({
|
||||||
|
'started': start,
|
||||||
|
# Total complete fragments downloaded so far in bytes
|
||||||
|
'complete_frags_downloaded_bytes': 0,
|
||||||
|
# Amount of fragment's bytes downloaded by the time of the previous
|
||||||
|
# frag progress hook invocation
|
||||||
|
'prev_frag_downloaded_bytes': 0,
|
||||||
|
})
|
||||||
|
|
||||||
def frag_progress_hook(s):
|
def frag_progress_hook(s):
|
||||||
if s['status'] not in ('downloading', 'finished'):
|
if s['status'] not in ('downloading', 'finished'):
|
||||||
return
|
return
|
||||||
|
|
||||||
frag_total_bytes = s.get('total_bytes', 0)
|
|
||||||
if s['status'] == 'finished':
|
|
||||||
state['downloaded_bytes'] += frag_total_bytes
|
|
||||||
state['frag_index'] += 1
|
|
||||||
|
|
||||||
estimated_size = (
|
|
||||||
(state['downloaded_bytes'] + frag_total_bytes) /
|
|
||||||
(state['frag_index'] + 1) * total_frags)
|
|
||||||
time_now = time.time()
|
time_now = time.time()
|
||||||
state['total_bytes_estimate'] = estimated_size
|
|
||||||
state['elapsed'] = time_now - start
|
state['elapsed'] = time_now - start
|
||||||
|
frag_total_bytes = s.get('total_bytes') or 0
|
||||||
|
if not ctx['live']:
|
||||||
|
estimated_size = (
|
||||||
|
(ctx['complete_frags_downloaded_bytes'] + frag_total_bytes) /
|
||||||
|
(state['frag_index'] + 1) * total_frags)
|
||||||
|
state['total_bytes_estimate'] = estimated_size
|
||||||
|
|
||||||
if s['status'] == 'finished':
|
if s['status'] == 'finished':
|
||||||
progress = self.calc_percent(state['frag_index'], total_frags)
|
state['frag_index'] += 1
|
||||||
|
state['downloaded_bytes'] += frag_total_bytes - ctx['prev_frag_downloaded_bytes']
|
||||||
|
ctx['complete_frags_downloaded_bytes'] = state['downloaded_bytes']
|
||||||
|
ctx['prev_frag_downloaded_bytes'] = 0
|
||||||
else:
|
else:
|
||||||
frag_downloaded_bytes = s['downloaded_bytes']
|
frag_downloaded_bytes = s['downloaded_bytes']
|
||||||
frag_progress = self.calc_percent(frag_downloaded_bytes,
|
state['downloaded_bytes'] += frag_downloaded_bytes - ctx['prev_frag_downloaded_bytes']
|
||||||
frag_total_bytes)
|
if not ctx['live']:
|
||||||
progress = self.calc_percent(state['frag_index'], total_frags)
|
state['eta'] = self.calc_eta(
|
||||||
progress += frag_progress / float(total_frags)
|
start, time_now, estimated_size,
|
||||||
|
state['downloaded_bytes'])
|
||||||
state['eta'] = self.calc_eta(
|
|
||||||
start, time_now, estimated_size, state['downloaded_bytes'] + frag_downloaded_bytes)
|
|
||||||
state['speed'] = s.get('speed')
|
state['speed'] = s.get('speed')
|
||||||
|
ctx['prev_frag_downloaded_bytes'] = frag_downloaded_bytes
|
||||||
self._hook_progress(state)
|
self._hook_progress(state)
|
||||||
|
|
||||||
ctx['dl'].add_progress_hook(frag_progress_hook)
|
ctx['dl'].add_progress_hook(frag_progress_hook)
|
||||||
|
@@ -3,6 +3,7 @@ from __future__ import unicode_literals
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
from .common import FileDownloader
|
from .common import FileDownloader
|
||||||
from .fragment import FragmentFD
|
from .fragment import FragmentFD
|
||||||
@@ -39,14 +40,29 @@ class HlsFD(FileDownloader):
|
|||||||
'-headers',
|
'-headers',
|
||||||
''.join('%s: %s\r\n' % (key, val) for key, val in headers.items())]
|
''.join('%s: %s\r\n' % (key, val) for key, val in headers.items())]
|
||||||
|
|
||||||
args += ['-i', url, '-f', 'mp4', '-c', 'copy', '-bsf:a', 'aac_adtstoasc']
|
args += ['-i', url, '-c', 'copy']
|
||||||
|
if self.params.get('hls_use_mpegts', False):
|
||||||
|
args += ['-f', 'mpegts']
|
||||||
|
else:
|
||||||
|
args += ['-f', 'mp4', '-bsf:a', 'aac_adtstoasc']
|
||||||
|
|
||||||
args = [encodeArgument(opt) for opt in args]
|
args = [encodeArgument(opt) for opt in args]
|
||||||
args.append(encodeFilename(ffpp._ffmpeg_filename_argument(tmpfilename), True))
|
args.append(encodeFilename(ffpp._ffmpeg_filename_argument(tmpfilename), True))
|
||||||
|
|
||||||
self._debug_cmd(args)
|
self._debug_cmd(args)
|
||||||
|
|
||||||
retval = subprocess.call(args)
|
proc = subprocess.Popen(args, stdin=subprocess.PIPE)
|
||||||
|
try:
|
||||||
|
retval = proc.wait()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
# subprocces.run would send the SIGKILL signal to ffmpeg and the
|
||||||
|
# mp4 file couldn't be played, but if we ask ffmpeg to quit it
|
||||||
|
# produces a file that is playable (this is mostly useful for live
|
||||||
|
# streams). Note that Windows is not affected and produces playable
|
||||||
|
# files (see https://github.com/rg3/youtube-dl/issues/8300).
|
||||||
|
if sys.platform != 'win32':
|
||||||
|
proc.communicate(b'q')
|
||||||
|
raise
|
||||||
if retval == 0:
|
if retval == 0:
|
||||||
fsize = os.path.getsize(encodeFilename(tmpfilename))
|
fsize = os.path.getsize(encodeFilename(tmpfilename))
|
||||||
self.to_screen('\r[%s] %s bytes' % (args[0], fsize))
|
self.to_screen('\r[%s] %s bytes' % (args[0], fsize))
|
||||||
|
@@ -15,11 +15,11 @@ from .adobetv import (
|
|||||||
AdobeTVVideoIE,
|
AdobeTVVideoIE,
|
||||||
)
|
)
|
||||||
from .adultswim import AdultSwimIE
|
from .adultswim import AdultSwimIE
|
||||||
|
from .aenetworks import AENetworksIE
|
||||||
from .aftonbladet import AftonbladetIE
|
from .aftonbladet import AftonbladetIE
|
||||||
from .airmozilla import AirMozillaIE
|
from .airmozilla import AirMozillaIE
|
||||||
from .aljazeera import AlJazeeraIE
|
from .aljazeera import AlJazeeraIE
|
||||||
from .alphaporno import AlphaPornoIE
|
from .alphaporno import AlphaPornoIE
|
||||||
from .animalplanet import AnimalPlanetIE
|
|
||||||
from .anitube import AnitubeIE
|
from .anitube import AnitubeIE
|
||||||
from .anysex import AnySexIE
|
from .anysex import AnySexIE
|
||||||
from .aol import AolIE
|
from .aol import AolIE
|
||||||
@@ -42,6 +42,7 @@ from .arte import (
|
|||||||
ArteTVCreativeIE,
|
ArteTVCreativeIE,
|
||||||
ArteTVConcertIE,
|
ArteTVConcertIE,
|
||||||
ArteTVFutureIE,
|
ArteTVFutureIE,
|
||||||
|
ArteTVCinemaIE,
|
||||||
ArteTVDDCIE,
|
ArteTVDDCIE,
|
||||||
ArteTVEmbedIE,
|
ArteTVEmbedIE,
|
||||||
)
|
)
|
||||||
@@ -49,7 +50,7 @@ from .atresplayer import AtresPlayerIE
|
|||||||
from .atttechchannel import ATTTechChannelIE
|
from .atttechchannel import ATTTechChannelIE
|
||||||
from .audimedia import AudiMediaIE
|
from .audimedia import AudiMediaIE
|
||||||
from .audiomack import AudiomackIE, AudiomackAlbumIE
|
from .audiomack import AudiomackIE, AudiomackAlbumIE
|
||||||
from .azubu import AzubuIE
|
from .azubu import AzubuIE, AzubuLiveIE
|
||||||
from .baidu import BaiduVideoIE
|
from .baidu import BaiduVideoIE
|
||||||
from .bambuser import BambuserIE, BambuserChannelIE
|
from .bambuser import BambuserIE, BambuserChannelIE
|
||||||
from .bandcamp import BandcampIE, BandcampAlbumIE
|
from .bandcamp import BandcampIE, BandcampAlbumIE
|
||||||
@@ -62,6 +63,7 @@ from .beeg import BeegIE
|
|||||||
from .behindkink import BehindKinkIE
|
from .behindkink import BehindKinkIE
|
||||||
from .beatportpro import BeatportProIE
|
from .beatportpro import BeatportProIE
|
||||||
from .bet import BetIE
|
from .bet import BetIE
|
||||||
|
from .bigflix import BigflixIE
|
||||||
from .bild import BildIE
|
from .bild import BildIE
|
||||||
from .bilibili import BiliBiliIE
|
from .bilibili import BiliBiliIE
|
||||||
from .bleacherreport import (
|
from .bleacherreport import (
|
||||||
@@ -86,8 +88,12 @@ from .camdemy import (
|
|||||||
)
|
)
|
||||||
from .canalplus import CanalplusIE
|
from .canalplus import CanalplusIE
|
||||||
from .canalc2 import Canalc2IE
|
from .canalc2 import Canalc2IE
|
||||||
|
from .canvas import CanvasIE
|
||||||
from .cbs import CBSIE
|
from .cbs import CBSIE
|
||||||
from .cbsnews import CBSNewsIE
|
from .cbsnews import (
|
||||||
|
CBSNewsIE,
|
||||||
|
CBSNewsLiveVideoIE,
|
||||||
|
)
|
||||||
from .cbssports import CBSSportsIE
|
from .cbssports import CBSSportsIE
|
||||||
from .ccc import CCCIE
|
from .ccc import CCCIE
|
||||||
from .ceskatelevize import CeskaTelevizeIE
|
from .ceskatelevize import CeskaTelevizeIE
|
||||||
@@ -128,6 +134,8 @@ from .crunchyroll import (
|
|||||||
)
|
)
|
||||||
from .cspan import CSpanIE
|
from .cspan import CSpanIE
|
||||||
from .ctsnews import CtsNewsIE
|
from .ctsnews import CtsNewsIE
|
||||||
|
from .cultureunplugged import CultureUnpluggedIE
|
||||||
|
from .cwtv import CWTVIE
|
||||||
from .dailymotion import (
|
from .dailymotion import (
|
||||||
DailymotionIE,
|
DailymotionIE,
|
||||||
DailymotionPlaylistIE,
|
DailymotionPlaylistIE,
|
||||||
@@ -137,6 +145,8 @@ from .dailymotion import (
|
|||||||
from .daum import (
|
from .daum import (
|
||||||
DaumIE,
|
DaumIE,
|
||||||
DaumClipIE,
|
DaumClipIE,
|
||||||
|
DaumPlaylistIE,
|
||||||
|
DaumUserIE,
|
||||||
)
|
)
|
||||||
from .dbtv import DBTVIE
|
from .dbtv import DBTVIE
|
||||||
from .dcn import (
|
from .dcn import (
|
||||||
@@ -191,7 +201,10 @@ from .everyonesmixtape import EveryonesMixtapeIE
|
|||||||
from .exfm import ExfmIE
|
from .exfm import ExfmIE
|
||||||
from .expotv import ExpoTVIE
|
from .expotv import ExpoTVIE
|
||||||
from .extremetube import ExtremeTubeIE
|
from .extremetube import ExtremeTubeIE
|
||||||
from .facebook import FacebookIE
|
from .facebook import (
|
||||||
|
FacebookIE,
|
||||||
|
FacebookPostIE,
|
||||||
|
)
|
||||||
from .faz import FazIE
|
from .faz import FazIE
|
||||||
from .fc2 import FC2IE
|
from .fc2 import FC2IE
|
||||||
from .fczenit import FczenitIE
|
from .fczenit import FczenitIE
|
||||||
@@ -262,7 +275,6 @@ from .hellporno import HellPornoIE
|
|||||||
from .helsinki import HelsinkiIE
|
from .helsinki import HelsinkiIE
|
||||||
from .hentaistigma import HentaiStigmaIE
|
from .hentaistigma import HentaiStigmaIE
|
||||||
from .historicfilms import HistoricFilmsIE
|
from .historicfilms import HistoricFilmsIE
|
||||||
from .history import HistoryIE
|
|
||||||
from .hitbox import HitboxIE, HitboxLiveIE
|
from .hitbox import HitboxIE, HitboxLiveIE
|
||||||
from .hornbunny import HornBunnyIE
|
from .hornbunny import HornBunnyIE
|
||||||
from .hotnewhiphop import HotNewHipHopIE
|
from .hotnewhiphop import HotNewHipHopIE
|
||||||
@@ -300,6 +312,7 @@ from .ivi import (
|
|||||||
IviIE,
|
IviIE,
|
||||||
IviCompilationIE
|
IviCompilationIE
|
||||||
)
|
)
|
||||||
|
from .ivideon import IvideonIE
|
||||||
from .izlesene import IzleseneIE
|
from .izlesene import IzleseneIE
|
||||||
from .jadorecettepub import JadoreCettePubIE
|
from .jadorecettepub import JadoreCettePubIE
|
||||||
from .jeuxvideo import JeuxVideoIE
|
from .jeuxvideo import JeuxVideoIE
|
||||||
@@ -315,6 +328,7 @@ from .keezmovies import KeezMoviesIE
|
|||||||
from .khanacademy import KhanAcademyIE
|
from .khanacademy import KhanAcademyIE
|
||||||
from .kickstarter import KickStarterIE
|
from .kickstarter import KickStarterIE
|
||||||
from .keek import KeekIE
|
from .keek import KeekIE
|
||||||
|
from .konserthusetplay import KonserthusetPlayIE
|
||||||
from .kontrtube import KontrTubeIE
|
from .kontrtube import KontrTubeIE
|
||||||
from .krasview import KrasViewIE
|
from .krasview import KrasViewIE
|
||||||
from .ku6 import Ku6IE
|
from .ku6 import Ku6IE
|
||||||
@@ -329,10 +343,12 @@ from .kuwo import (
|
|||||||
from .la7 import LA7IE
|
from .la7 import LA7IE
|
||||||
from .laola1tv import Laola1TvIE
|
from .laola1tv import Laola1TvIE
|
||||||
from .lecture2go import Lecture2GoIE
|
from .lecture2go import Lecture2GoIE
|
||||||
|
from .lemonde import LemondeIE
|
||||||
from .letv import (
|
from .letv import (
|
||||||
LetvIE,
|
LetvIE,
|
||||||
LetvTvIE,
|
LetvTvIE,
|
||||||
LetvPlaylistIE
|
LetvPlaylistIE,
|
||||||
|
LetvCloudIE,
|
||||||
)
|
)
|
||||||
from .libsyn import LibsynIE
|
from .libsyn import LibsynIE
|
||||||
from .lifenews import (
|
from .lifenews import (
|
||||||
@@ -351,6 +367,7 @@ from .livestream import (
|
|||||||
LivestreamShortenerIE,
|
LivestreamShortenerIE,
|
||||||
)
|
)
|
||||||
from .lnkgo import LnkGoIE
|
from .lnkgo import LnkGoIE
|
||||||
|
from .lovehomeporn import LoveHomePornIE
|
||||||
from .lrt import LRTIE
|
from .lrt import LRTIE
|
||||||
from .lynda import (
|
from .lynda import (
|
||||||
LyndaIE,
|
LyndaIE,
|
||||||
@@ -361,6 +378,7 @@ from .macgamestore import MacGameStoreIE
|
|||||||
from .mailru import MailRuIE
|
from .mailru import MailRuIE
|
||||||
from .makertv import MakerTVIE
|
from .makertv import MakerTVIE
|
||||||
from .malemotion import MalemotionIE
|
from .malemotion import MalemotionIE
|
||||||
|
from .matchtv import MatchTVIE
|
||||||
from .mdr import MDRIE
|
from .mdr import MDRIE
|
||||||
from .metacafe import MetacafeIE
|
from .metacafe import MetacafeIE
|
||||||
from .metacritic import MetacriticIE
|
from .metacritic import MetacriticIE
|
||||||
@@ -435,6 +453,7 @@ from .nextmedia import (
|
|||||||
NextMediaActionNewsIE,
|
NextMediaActionNewsIE,
|
||||||
AppleDailyIE,
|
AppleDailyIE,
|
||||||
)
|
)
|
||||||
|
from .nextmovie import NextMovieIE
|
||||||
from .nfb import NFBIE
|
from .nfb import NFBIE
|
||||||
from .nfl import NFLIE
|
from .nfl import NFLIE
|
||||||
from .nhl import (
|
from .nhl import (
|
||||||
@@ -442,6 +461,7 @@ from .nhl import (
|
|||||||
NHLNewsIE,
|
NHLNewsIE,
|
||||||
NHLVideocenterIE,
|
NHLVideocenterIE,
|
||||||
)
|
)
|
||||||
|
from .nick import NickIE
|
||||||
from .niconico import NiconicoIE, NiconicoPlaylistIE
|
from .niconico import NiconicoIE, NiconicoPlaylistIE
|
||||||
from .ninegag import NineGagIE
|
from .ninegag import NineGagIE
|
||||||
from .noco import NocoIE
|
from .noco import NocoIE
|
||||||
@@ -469,9 +489,11 @@ from .npo import (
|
|||||||
NPOLiveIE,
|
NPOLiveIE,
|
||||||
NPORadioIE,
|
NPORadioIE,
|
||||||
NPORadioFragmentIE,
|
NPORadioFragmentIE,
|
||||||
|
SchoolTVIE,
|
||||||
VPROIE,
|
VPROIE,
|
||||||
WNLIE
|
WNLIE
|
||||||
)
|
)
|
||||||
|
from .npr import NprIE
|
||||||
from .nrk import (
|
from .nrk import (
|
||||||
NRKIE,
|
NRKIE,
|
||||||
NRKPlaylistIE,
|
NRKPlaylistIE,
|
||||||
@@ -498,6 +520,7 @@ from .orf import (
|
|||||||
ORFFM4IE,
|
ORFFM4IE,
|
||||||
ORFIPTVIE,
|
ORFIPTVIE,
|
||||||
)
|
)
|
||||||
|
from .pandoratv import PandoraTVIE
|
||||||
from .parliamentliveuk import ParliamentLiveUKIE
|
from .parliamentliveuk import ParliamentLiveUKIE
|
||||||
from .patreon import PatreonIE
|
from .patreon import PatreonIE
|
||||||
from .pbs import PBSIE
|
from .pbs import PBSIE
|
||||||
@@ -552,14 +575,16 @@ from .rai import (
|
|||||||
from .rbmaradio import RBMARadioIE
|
from .rbmaradio import RBMARadioIE
|
||||||
from .rds import RDSIE
|
from .rds import RDSIE
|
||||||
from .redtube import RedTubeIE
|
from .redtube import RedTubeIE
|
||||||
|
from .regiotv import RegioTVIE
|
||||||
from .restudy import RestudyIE
|
from .restudy import RestudyIE
|
||||||
from .reverbnation import ReverbNationIE
|
from .reverbnation import ReverbNationIE
|
||||||
|
from .revision3 import Revision3IE
|
||||||
from .ringtv import RingTVIE
|
from .ringtv import RingTVIE
|
||||||
from .ro220 import Ro220IE
|
from .ro220 import Ro220IE
|
||||||
from .rottentomatoes import RottenTomatoesIE
|
from .rottentomatoes import RottenTomatoesIE
|
||||||
from .roxwel import RoxwelIE
|
from .roxwel import RoxwelIE
|
||||||
from .rtbf import RTBFIE
|
from .rtbf import RTBFIE
|
||||||
from .rte import RteIE
|
from .rte import RteIE, RteRadioIE
|
||||||
from .rtlnl import RtlNlIE
|
from .rtlnl import RtlNlIE
|
||||||
from .rtl2 import RTL2IE
|
from .rtl2 import RTL2IE
|
||||||
from .rtp import RTPIE
|
from .rtp import RTPIE
|
||||||
@@ -567,6 +592,7 @@ from .rts import RTSIE
|
|||||||
from .rtve import RTVEALaCartaIE, RTVELiveIE, RTVEInfantilIE
|
from .rtve import RTVEALaCartaIE, RTVELiveIE, RTVEInfantilIE
|
||||||
from .rtvnh import RTVNHIE
|
from .rtvnh import RTVNHIE
|
||||||
from .ruhd import RUHDIE
|
from .ruhd import RUHDIE
|
||||||
|
from .ruleporn import RulePornIE
|
||||||
from .rutube import (
|
from .rutube import (
|
||||||
RutubeIE,
|
RutubeIE,
|
||||||
RutubeChannelIE,
|
RutubeChannelIE,
|
||||||
@@ -683,7 +709,6 @@ from .telemb import TeleMBIE
|
|||||||
from .teletask import TeleTaskIE
|
from .teletask import TeleTaskIE
|
||||||
from .tenplay import TenPlayIE
|
from .tenplay import TenPlayIE
|
||||||
from .testurl import TestURLIE
|
from .testurl import TestURLIE
|
||||||
from .testtube import TestTubeIE
|
|
||||||
from .tf1 import TF1IE
|
from .tf1 import TF1IE
|
||||||
from .theintercept import TheInterceptIE
|
from .theintercept import TheInterceptIE
|
||||||
from .theonion import TheOnionIE
|
from .theonion import TheOnionIE
|
||||||
@@ -695,7 +720,7 @@ from .thesixtyone import TheSixtyOneIE
|
|||||||
from .thisamericanlife import ThisAmericanLifeIE
|
from .thisamericanlife import ThisAmericanLifeIE
|
||||||
from .thisav import ThisAVIE
|
from .thisav import ThisAVIE
|
||||||
from .tinypic import TinyPicIE
|
from .tinypic import TinyPicIE
|
||||||
from .tlc import TlcIE, TlcDeIE
|
from .tlc import TlcDeIE
|
||||||
from .tmz import (
|
from .tmz import (
|
||||||
TMZIE,
|
TMZIE,
|
||||||
TMZArticleIE,
|
TMZArticleIE,
|
||||||
@@ -714,10 +739,15 @@ from .toutv import TouTvIE
|
|||||||
from .toypics import ToypicsUserIE, ToypicsIE
|
from .toypics import ToypicsUserIE, ToypicsIE
|
||||||
from .traileraddict import TrailerAddictIE
|
from .traileraddict import TrailerAddictIE
|
||||||
from .trilulilu import TriluliluIE
|
from .trilulilu import TriluliluIE
|
||||||
|
from .trollvids import TrollvidsIE
|
||||||
from .trutube import TruTubeIE
|
from .trutube import TruTubeIE
|
||||||
from .tube8 import Tube8IE
|
from .tube8 import Tube8IE
|
||||||
from .tubitv import TubiTvIE
|
from .tubitv import TubiTvIE
|
||||||
from .tudou import TudouIE
|
from .tudou import (
|
||||||
|
TudouIE,
|
||||||
|
TudouPlaylistIE,
|
||||||
|
TudouAlbumIE,
|
||||||
|
)
|
||||||
from .tumblr import TumblrIE
|
from .tumblr import TumblrIE
|
||||||
from .tunein import (
|
from .tunein import (
|
||||||
TuneInClipIE,
|
TuneInClipIE,
|
||||||
@@ -738,10 +768,12 @@ from .tvc import (
|
|||||||
TVCArticleIE,
|
TVCArticleIE,
|
||||||
)
|
)
|
||||||
from .tvigle import TvigleIE
|
from .tvigle import TvigleIE
|
||||||
|
from .tvland import TVLandIE
|
||||||
from .tvp import TvpIE, TvpSeriesIE
|
from .tvp import TvpIE, TvpSeriesIE
|
||||||
from .tvplay import TVPlayIE
|
from .tvplay import TVPlayIE
|
||||||
from .tweakers import TweakersIE
|
from .tweakers import TweakersIE
|
||||||
from .twentyfourvideo import TwentyFourVideoIE
|
from .twentyfourvideo import TwentyFourVideoIE
|
||||||
|
from .twentymin import TwentyMinutenIE
|
||||||
from .twentytwotracks import (
|
from .twentytwotracks import (
|
||||||
TwentyTwoTracksIE,
|
TwentyTwoTracksIE,
|
||||||
TwentyTwoTracksGenreIE
|
TwentyTwoTracksGenreIE
|
||||||
@@ -762,7 +794,7 @@ from .udemy import (
|
|||||||
UdemyCourseIE
|
UdemyCourseIE
|
||||||
)
|
)
|
||||||
from .udn import UDNEmbedIE
|
from .udn import UDNEmbedIE
|
||||||
from .ultimedia import UltimediaIE
|
from .digiteka import DigitekaIE
|
||||||
from .unistra import UnistraIE
|
from .unistra import UnistraIE
|
||||||
from .urort import UrortIE
|
from .urort import UrortIE
|
||||||
from .ustream import UstreamIE, UstreamChannelIE
|
from .ustream import UstreamIE, UstreamChannelIE
|
||||||
@@ -791,7 +823,11 @@ from .videomore import (
|
|||||||
)
|
)
|
||||||
from .videopremium import VideoPremiumIE
|
from .videopremium import VideoPremiumIE
|
||||||
from .videott import VideoTtIE
|
from .videott import VideoTtIE
|
||||||
from .vidme import VidmeIE
|
from .vidme import (
|
||||||
|
VidmeIE,
|
||||||
|
VidmeUserIE,
|
||||||
|
VidmeUserLikesIE,
|
||||||
|
)
|
||||||
from .vidzi import VidziIE
|
from .vidzi import VidziIE
|
||||||
from .vier import VierIE, VierVideosIE
|
from .vier import VierIE, VierVideosIE
|
||||||
from .viewster import ViewsterIE
|
from .viewster import ViewsterIE
|
||||||
@@ -841,6 +877,7 @@ from .webofstories import (
|
|||||||
WebOfStoriesPlaylistIE,
|
WebOfStoriesPlaylistIE,
|
||||||
)
|
)
|
||||||
from .weibo import WeiboIE
|
from .weibo import WeiboIE
|
||||||
|
from .weiqitv import WeiqiTVIE
|
||||||
from .wimp import WimpIE
|
from .wimp import WimpIE
|
||||||
from .wistia import WistiaIE
|
from .wistia import WistiaIE
|
||||||
from .worldstarhiphop import WorldStarHipHopIE
|
from .worldstarhiphop import WorldStarHipHopIE
|
||||||
@@ -901,6 +938,7 @@ from .zingmp3 import (
|
|||||||
ZingMp3SongIE,
|
ZingMp3SongIE,
|
||||||
ZingMp3AlbumIE,
|
ZingMp3AlbumIE,
|
||||||
)
|
)
|
||||||
|
from .zippcast import ZippCastIE
|
||||||
|
|
||||||
_ALL_CLASSES = [
|
_ALL_CLASSES = [
|
||||||
klass
|
klass
|
||||||
|
@@ -8,11 +8,7 @@ from ..compat import compat_str
|
|||||||
from ..utils import int_or_none
|
from ..utils import int_or_none
|
||||||
|
|
||||||
|
|
||||||
class ACastBaseIE(InfoExtractor):
|
class ACastIE(InfoExtractor):
|
||||||
_API_BASE_URL = 'https://www.acast.com/api/'
|
|
||||||
|
|
||||||
|
|
||||||
class ACastIE(ACastBaseIE):
|
|
||||||
IE_NAME = 'acast'
|
IE_NAME = 'acast'
|
||||||
_VALID_URL = r'https?://(?:www\.)?acast\.com/(?P<channel>[^/]+)/(?P<id>[^/#?]+)'
|
_VALID_URL = r'https?://(?:www\.)?acast\.com/(?P<channel>[^/]+)/(?P<id>[^/#?]+)'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
@@ -23,14 +19,19 @@ class ACastIE(ACastBaseIE):
|
|||||||
'ext': 'mp3',
|
'ext': 'mp3',
|
||||||
'title': '"Where Are You?": Taipei 101, Taiwan',
|
'title': '"Where Are You?": Taipei 101, Taiwan',
|
||||||
'timestamp': 1196172000000,
|
'timestamp': 1196172000000,
|
||||||
'description': 'md5:0c5d8201dfea2b93218ea986c91eee6e',
|
'description': 'md5:a0b4ef3634e63866b542e5b1199a1a0e',
|
||||||
'duration': 211,
|
'duration': 211,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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(self._API_BASE_URL + 'channels/%s/acasts/%s/playback' % (channel, display_id), display_id)
|
|
||||||
|
embed_page = self._download_webpage(
|
||||||
|
re.sub('(?:www\.)?acast\.com', 'embedcdn.acast.com', url), display_id)
|
||||||
|
cast_data = self._parse_json(self._search_regex(
|
||||||
|
r'window\[\'acast/queries\'\]\s*=\s*([^;]+);', embed_page, 'acast data'),
|
||||||
|
display_id)['GetAcast/%s/%s' % (channel, display_id)]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': compat_str(cast_data['id']),
|
'id': compat_str(cast_data['id']),
|
||||||
@@ -44,7 +45,7 @@ class ACastIE(ACastBaseIE):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class ACastChannelIE(ACastBaseIE):
|
class ACastChannelIE(InfoExtractor):
|
||||||
IE_NAME = 'acast:channel'
|
IE_NAME = 'acast:channel'
|
||||||
_VALID_URL = r'https?://(?:www\.)?acast\.com/(?P<id>[^/#?]+)'
|
_VALID_URL = r'https?://(?:www\.)?acast\.com/(?P<id>[^/#?]+)'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
@@ -56,6 +57,7 @@ class ACastChannelIE(ACastBaseIE):
|
|||||||
},
|
},
|
||||||
'playlist_mincount': 20,
|
'playlist_mincount': 20,
|
||||||
}
|
}
|
||||||
|
_API_BASE_URL = 'https://www.acast.com/api/'
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def suitable(cls, url):
|
def suitable(cls, url):
|
||||||
|
@@ -187,7 +187,8 @@ class AdultSwimIE(InfoExtractor):
|
|||||||
media_url = file_el.text
|
media_url = file_el.text
|
||||||
if determine_ext(media_url) == 'm3u8':
|
if determine_ext(media_url) == 'm3u8':
|
||||||
formats.extend(self._extract_m3u8_formats(
|
formats.extend(self._extract_m3u8_formats(
|
||||||
media_url, segment_title, 'mp4', preference=0, m3u8_id='hls'))
|
media_url, segment_title, 'mp4', preference=0,
|
||||||
|
m3u8_id='hls', fatal=False))
|
||||||
else:
|
else:
|
||||||
formats.append({
|
formats.append({
|
||||||
'format_id': '%s_%s' % (bitrate, ftype),
|
'format_id': '%s_%s' % (bitrate, ftype),
|
||||||
|
66
youtube_dl/extractor/aenetworks.py
Normal file
66
youtube_dl/extractor/aenetworks.py
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import smuggle_url
|
||||||
|
|
||||||
|
|
||||||
|
class AENetworksIE(InfoExtractor):
|
||||||
|
IE_NAME = 'aenetworks'
|
||||||
|
IE_DESC = 'A+E Networks: A&E, Lifetime, History.com, FYI Network'
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?(?:(?:history|aetv|mylifetime)\.com|fyi\.tv)/(?:[^/]+/)+(?P<id>[^/]+?)(?:$|[?#])'
|
||||||
|
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://www.history.com/topics/valentines-day/history-of-valentines-day/videos/bet-you-didnt-know-valentines-day?m=528e394da93ae&s=undefined&f=1&free=false',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'g12m5Gyt3fdR',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': "Bet You Didn't Know: Valentine's Day",
|
||||||
|
'description': 'md5:7b57ea4829b391995b405fa60bd7b5f7',
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
# m3u8 download
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
|
'add_ie': ['ThePlatform'],
|
||||||
|
'expected_warnings': ['JSON-LD'],
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.history.com/shows/mountain-men/season-1/episode-1',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'eg47EERs_JsZ',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': "Winter Is Coming",
|
||||||
|
'description': 'md5:641f424b7a19d8e24f26dea22cf59d74',
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
# m3u8 download
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
|
'add_ie': ['ThePlatform'],
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.aetv.com/shows/duck-dynasty/video/inlawful-entry',
|
||||||
|
'only_matching': True
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.fyi.tv/shows/tiny-house-nation/videos/207-sq-ft-minnesota-prairie-cottage',
|
||||||
|
'only_matching': True
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.mylifetime.com/shows/project-runway-junior/video/season-1/episode-6/superstar-clients',
|
||||||
|
'only_matching': True
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
video_id = self._match_id(url)
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
video_url_re = [
|
||||||
|
r'data-href="[^"]*/%s"[^>]+data-release-url="([^"]+)"' % video_id,
|
||||||
|
r"media_url\s*=\s*'([^']+)'"
|
||||||
|
]
|
||||||
|
video_url = self._search_regex(video_url_re, webpage, 'video url')
|
||||||
|
|
||||||
|
info = self._search_json_ld(webpage, video_id, fatal=False)
|
||||||
|
info.update({
|
||||||
|
'_type': 'url_transparent',
|
||||||
|
'url': smuggle_url(video_url, {'sig': {'key': 'crazyjava', 'secret': 's3cr3t'}}),
|
||||||
|
})
|
||||||
|
return info
|
@@ -8,6 +8,8 @@ from .common import InfoExtractor
|
|||||||
from ..compat import compat_str
|
from ..compat import compat_str
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
qualities,
|
qualities,
|
||||||
|
unescapeHTML,
|
||||||
|
xpath_element,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -31,7 +33,7 @@ class AllocineIE(InfoExtractor):
|
|||||||
'id': '19540403',
|
'id': '19540403',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Planes 2 Bande-annonce VF',
|
'title': 'Planes 2 Bande-annonce VF',
|
||||||
'description': 'md5:eeaffe7c2d634525e21159b93acf3b1e',
|
'description': 'Regardez la bande annonce du film Planes 2 (Planes 2 Bande-annonce VF). Planes 2, un film de Roberts Gannaway',
|
||||||
'thumbnail': 're:http://.*\.jpg',
|
'thumbnail': 're:http://.*\.jpg',
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
@@ -41,7 +43,7 @@ class AllocineIE(InfoExtractor):
|
|||||||
'id': '19544709',
|
'id': '19544709',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Dragons 2 - Bande annonce finale VF',
|
'title': 'Dragons 2 - Bande annonce finale VF',
|
||||||
'description': 'md5:71742e3a74b0d692c7fce0dd2017a4ac',
|
'description': 'md5:601d15393ac40f249648ef000720e7e3',
|
||||||
'thumbnail': 're:http://.*\.jpg',
|
'thumbnail': 're:http://.*\.jpg',
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
@@ -59,14 +61,18 @@ class AllocineIE(InfoExtractor):
|
|||||||
if typ == 'film':
|
if typ == 'film':
|
||||||
video_id = self._search_regex(r'href="/video/player_gen_cmedia=([0-9]+).+"', webpage, 'video id')
|
video_id = self._search_regex(r'href="/video/player_gen_cmedia=([0-9]+).+"', webpage, 'video id')
|
||||||
else:
|
else:
|
||||||
player = self._search_regex(r'data-player=\'([^\']+)\'>', webpage, 'data player')
|
player = self._search_regex(r'data-player=\'([^\']+)\'>', webpage, 'data player', default=None)
|
||||||
|
if player:
|
||||||
player_data = json.loads(player)
|
player_data = json.loads(player)
|
||||||
video_id = compat_str(player_data['refMedia'])
|
video_id = compat_str(player_data['refMedia'])
|
||||||
|
else:
|
||||||
|
model = self._search_regex(r'data-model="([^"]+)">', webpage, 'data model')
|
||||||
|
model_data = self._parse_json(unescapeHTML(model), display_id)
|
||||||
|
video_id = compat_str(model_data['id'])
|
||||||
|
|
||||||
xml = self._download_xml('http://www.allocine.fr/ws/AcVisiondataV4.ashx?media=%s' % video_id, display_id)
|
xml = self._download_xml('http://www.allocine.fr/ws/AcVisiondataV4.ashx?media=%s' % video_id, display_id)
|
||||||
|
|
||||||
video = xml.find('.//AcVisionVideo').attrib
|
video = xpath_element(xml, './/AcVisionVideo').attrib
|
||||||
quality = qualities(['ld', 'md', 'hd'])
|
quality = qualities(['ld', 'md', 'hd'])
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
|
@@ -76,5 +76,6 @@ class AMPIE(InfoExtractor):
|
|||||||
'thumbnails': thumbnails,
|
'thumbnails': thumbnails,
|
||||||
'timestamp': parse_iso8601(item.get('pubDate'), ' '),
|
'timestamp': parse_iso8601(item.get('pubDate'), ' '),
|
||||||
'duration': int_or_none(media_content[0].get('@attributes', {}).get('duration')),
|
'duration': int_or_none(media_content[0].get('@attributes', {}).get('duration')),
|
||||||
|
'subtitles': subtitles,
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
}
|
}
|
||||||
|
@@ -1,53 +0,0 @@
|
|||||||
# coding: utf-8
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
|
||||||
from ..compat import compat_str
|
|
||||||
from ..utils import (
|
|
||||||
parse_duration,
|
|
||||||
parse_iso8601,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class AnimalPlanetIE(InfoExtractor):
|
|
||||||
_VALID_URL = r'https?://(?:www\.)?animalplanet\.com/([^/]+/)*(?P<id>[^/\?#]+)'
|
|
||||||
_TESTS = [{
|
|
||||||
'url': 'http://www.animalplanet.com/tv-shows/i-shouldnt-be-alive/videos/dog-saves-injured-owner/',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '10608',
|
|
||||||
'ext': 'mp4',
|
|
||||||
'title': 'Dog Saves Injured Owner',
|
|
||||||
'description': 'A world class athlete is put to the test when she falls into a canyon and breaks her hip. Her only companion is her dog, Taz, who is on a mission to save her!',
|
|
||||||
'upload_date': '20100410',
|
|
||||||
'timestamp': 1270857727,
|
|
||||||
'duration': 220,
|
|
||||||
},
|
|
||||||
'params': {
|
|
||||||
# m3u8 download
|
|
||||||
'skip_download': True,
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
'url': 'http://www.animalplanet.com/longfin-eels-maneaters/',
|
|
||||||
'only_matching': True,
|
|
||||||
}]
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
|
||||||
display_id = self._match_id(url)
|
|
||||||
webpage = self._download_webpage(url, display_id)
|
|
||||||
|
|
||||||
video_data = self._parse_json(self._search_regex(
|
|
||||||
r'initialVideoData\s*=\s*({.+?});',
|
|
||||||
webpage, 'initialVideoData'), display_id)['playlist'][0]
|
|
||||||
|
|
||||||
return {
|
|
||||||
'id': compat_str(video_data['id']),
|
|
||||||
'display_id': display_id,
|
|
||||||
'title': video_data['title'],
|
|
||||||
'description': video_data.get('description'),
|
|
||||||
'thumbnail': video_data.get('thumbnailURL'),
|
|
||||||
'duration': parse_duration(video_data.get('video_length')),
|
|
||||||
'timestamp': parse_iso8601(video_data.get('publishedDate')),
|
|
||||||
'formats': self._extract_m3u8_formats(
|
|
||||||
video_data['src'], display_id, 'mp4',
|
|
||||||
'm3u8_native', m3u8_id='hls')
|
|
||||||
}
|
|
@@ -1,11 +1,9 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
from .nuevo import NuevoBaseIE
|
||||||
|
|
||||||
from .common import InfoExtractor
|
|
||||||
|
|
||||||
|
|
||||||
class AnitubeIE(InfoExtractor):
|
class AnitubeIE(NuevoBaseIE):
|
||||||
IE_NAME = 'anitube.se'
|
IE_NAME = 'anitube.se'
|
||||||
_VALID_URL = r'https?://(?:www\.)?anitube\.se/video/(?P<id>\d+)'
|
_VALID_URL = r'https?://(?:www\.)?anitube\.se/video/(?P<id>\d+)'
|
||||||
|
|
||||||
@@ -22,38 +20,11 @@ class AnitubeIE(InfoExtractor):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
video_id = self._match_id(url)
|
||||||
video_id = mobj.group('id')
|
|
||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
key = self._search_regex(
|
key = self._search_regex(
|
||||||
r'src=["\']https?://[^/]+/embed/([A-Za-z0-9_-]+)', webpage, 'key')
|
r'src=["\']https?://[^/]+/embed/([A-Za-z0-9_-]+)', webpage, 'key')
|
||||||
|
|
||||||
config_xml = self._download_xml(
|
return self._extract_nuevo(
|
||||||
'http://www.anitube.se/nuevo/econfig.php?key=%s' % key, key)
|
'http://www.anitube.se/nuevo/econfig.php?key=%s' % key, video_id)
|
||||||
|
|
||||||
video_title = config_xml.find('title').text
|
|
||||||
thumbnail = config_xml.find('image').text
|
|
||||||
duration = float(config_xml.find('duration').text)
|
|
||||||
|
|
||||||
formats = []
|
|
||||||
video_url = config_xml.find('file')
|
|
||||||
if video_url is not None:
|
|
||||||
formats.append({
|
|
||||||
'format_id': 'sd',
|
|
||||||
'url': video_url.text,
|
|
||||||
})
|
|
||||||
video_url = config_xml.find('filehd')
|
|
||||||
if video_url is not None:
|
|
||||||
formats.append({
|
|
||||||
'format_id': 'hd',
|
|
||||||
'url': video_url.text,
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
'id': video_id,
|
|
||||||
'title': video_title,
|
|
||||||
'thumbnail': thumbnail,
|
|
||||||
'duration': duration,
|
|
||||||
'formats': formats
|
|
||||||
}
|
|
||||||
|
@@ -13,6 +13,7 @@ from ..utils import (
|
|||||||
unified_strdate,
|
unified_strdate,
|
||||||
get_element_by_attribute,
|
get_element_by_attribute,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
|
NO_DEFAULT,
|
||||||
qualities,
|
qualities,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -93,9 +94,18 @@ class ArteTVPlus7IE(InfoExtractor):
|
|||||||
json_url = self._html_search_regex(
|
json_url = self._html_search_regex(
|
||||||
patterns, webpage, 'json vp url', default=None)
|
patterns, webpage, 'json vp url', default=None)
|
||||||
if not json_url:
|
if not json_url:
|
||||||
iframe_url = self._html_search_regex(
|
def find_iframe_url(webpage, default=NO_DEFAULT):
|
||||||
r'<iframe[^>]+src=(["\'])(?P<url>.+\bjson_url=.+?)\1',
|
return self._html_search_regex(
|
||||||
webpage, 'iframe url', group='url')
|
r'<iframe[^>]+src=(["\'])(?P<url>.+\bjson_url=.+?)\1',
|
||||||
|
webpage, 'iframe url', group='url', default=default)
|
||||||
|
|
||||||
|
iframe_url = find_iframe_url(webpage, None)
|
||||||
|
if not iframe_url:
|
||||||
|
embed_url = self._html_search_regex(
|
||||||
|
r'arte_vp_url_oembed=\'([^\']+?)\'', webpage, 'embed url')
|
||||||
|
player = self._download_json(
|
||||||
|
embed_url, video_id, 'Downloading player page')
|
||||||
|
iframe_url = find_iframe_url(player['html'])
|
||||||
json_url = compat_parse_qs(
|
json_url = compat_parse_qs(
|
||||||
compat_urllib_parse_urlparse(iframe_url).query)['json_url'][0]
|
compat_urllib_parse_urlparse(iframe_url).query)['json_url'][0]
|
||||||
return self._extract_from_json_url(json_url, video_id, lang)
|
return self._extract_from_json_url(json_url, video_id, lang)
|
||||||
@@ -199,25 +209,19 @@ class ArteTVCreativeIE(ArteTVPlus7IE):
|
|||||||
|
|
||||||
class ArteTVFutureIE(ArteTVPlus7IE):
|
class ArteTVFutureIE(ArteTVPlus7IE):
|
||||||
IE_NAME = 'arte.tv:future'
|
IE_NAME = 'arte.tv:future'
|
||||||
_VALID_URL = r'https?://future\.arte\.tv/(?P<lang>fr|de)/(thema|sujet)/.*?#article-anchor-(?P<id>\d+)'
|
_VALID_URL = r'https?://future\.arte\.tv/(?P<lang>fr|de)/(?P<id>.+)'
|
||||||
|
|
||||||
_TEST = {
|
_TESTS = [{
|
||||||
'url': 'http://future.arte.tv/fr/sujet/info-sciences#article-anchor-7081',
|
'url': 'http://future.arte.tv/fr/info-sciences/les-ecrevisses-aussi-sont-anxieuses',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '5201',
|
'id': '050940-028-A',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Les champignons au secours de la planète',
|
'title': 'Les écrevisses aussi peuvent être anxieuses',
|
||||||
'upload_date': '20131101',
|
|
||||||
},
|
},
|
||||||
}
|
}, {
|
||||||
|
'url': 'http://future.arte.tv/fr/la-science-est-elle-responsable',
|
||||||
def _real_extract(self, url):
|
'only_matching': True,
|
||||||
anchor_id, lang = self._extract_url_info(url)
|
}]
|
||||||
webpage = self._download_webpage(url, anchor_id)
|
|
||||||
row = self._search_regex(
|
|
||||||
r'(?s)id="%s"[^>]*>.+?(<div[^>]*arte_vp_url[^>]*>)' % anchor_id,
|
|
||||||
webpage, 'row')
|
|
||||||
return self._extract_from_webpage(row, anchor_id, lang)
|
|
||||||
|
|
||||||
|
|
||||||
class ArteTVDDCIE(ArteTVPlus7IE):
|
class ArteTVDDCIE(ArteTVPlus7IE):
|
||||||
@@ -255,6 +259,23 @@ class ArteTVConcertIE(ArteTVPlus7IE):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ArteTVCinemaIE(ArteTVPlus7IE):
|
||||||
|
IE_NAME = 'arte.tv:cinema'
|
||||||
|
_VALID_URL = r'https?://cinema\.arte\.tv/(?P<lang>de|fr)/(?P<id>.+)'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://cinema.arte.tv/de/node/38291',
|
||||||
|
'md5': '6b275511a5107c60bacbeeda368c3aa1',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '055876-000_PWA12025-D',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Tod auf dem Nil',
|
||||||
|
'upload_date': '20160122',
|
||||||
|
'description': 'md5:7f749bbb77d800ef2be11d54529b96bc',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class ArteTVEmbedIE(ArteTVPlus7IE):
|
class ArteTVEmbedIE(ArteTVPlus7IE):
|
||||||
IE_NAME = 'arte.tv:embed'
|
IE_NAME = 'arte.tv:embed'
|
||||||
_VALID_URL = r'''(?x)
|
_VALID_URL = r'''(?x)
|
||||||
|
@@ -132,11 +132,6 @@ class AtresPlayerIE(InfoExtractor):
|
|||||||
})
|
})
|
||||||
formats.append(format_info)
|
formats.append(format_info)
|
||||||
|
|
||||||
m3u8_url = player.get('urlVideoHls')
|
|
||||||
if m3u8_url:
|
|
||||||
formats.extend(self._extract_m3u8_formats(
|
|
||||||
m3u8_url, episode_id, 'mp4', 'm3u8_native', m3u8_id='hls', fatal=False))
|
|
||||||
|
|
||||||
timestamp = int_or_none(self._download_webpage(
|
timestamp = int_or_none(self._download_webpage(
|
||||||
self._TIME_API_URL,
|
self._TIME_API_URL,
|
||||||
video_id, 'Downloading timestamp', fatal=False), 1000, time.time())
|
video_id, 'Downloading timestamp', fatal=False), 1000, time.time())
|
||||||
|
@@ -3,7 +3,11 @@ from __future__ import unicode_literals
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import float_or_none
|
from ..utils import (
|
||||||
|
ExtractorError,
|
||||||
|
float_or_none,
|
||||||
|
sanitized_Request,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class AzubuIE(InfoExtractor):
|
class AzubuIE(InfoExtractor):
|
||||||
@@ -91,3 +95,37 @@ class AzubuIE(InfoExtractor):
|
|||||||
'view_count': view_count,
|
'view_count': view_count,
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class AzubuLiveIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'http://www.azubu.tv/(?P<id>[^/]+)$'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.azubu.tv/MarsTVMDLen',
|
||||||
|
'only_matching': True,
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
user = self._match_id(url)
|
||||||
|
|
||||||
|
info = self._download_json(
|
||||||
|
'http://api.azubu.tv/public/modules/last-video/{0}/info'.format(user),
|
||||||
|
user)['data']
|
||||||
|
if info['type'] != 'STREAM':
|
||||||
|
raise ExtractorError('{0} is not streaming live'.format(user), expected=True)
|
||||||
|
|
||||||
|
req = sanitized_Request(
|
||||||
|
'https://edge-elb.api.brightcove.com/playback/v1/accounts/3361910549001/videos/ref:' + info['reference_id'])
|
||||||
|
req.add_header('Accept', 'application/json;pk=BCpkADawqM1gvI0oGWg8dxQHlgT8HkdE2LnAlWAZkOlznO39bSZX726u4JqnDsK3MDXcO01JxXK2tZtJbgQChxgaFzEVdHRjaDoxaOu8hHOO8NYhwdxw9BzvgkvLUlpbDNUuDoc4E4wxDToV')
|
||||||
|
bc_info = self._download_json(req, user)
|
||||||
|
m3u8_url = next(source['src'] for source in bc_info['sources'] if source['container'] == 'M2TS')
|
||||||
|
formats = self._extract_m3u8_formats(m3u8_url, user, ext='mp4')
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': info['id'],
|
||||||
|
'title': self._live_title(info['title']),
|
||||||
|
'uploader_id': user,
|
||||||
|
'formats': formats,
|
||||||
|
'is_live': True,
|
||||||
|
'thumbnail': bc_info['poster'],
|
||||||
|
}
|
||||||
|
@@ -4,7 +4,7 @@ from __future__ import unicode_literals
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import compat_urlparse
|
from ..utils import unescapeHTML
|
||||||
|
|
||||||
|
|
||||||
class BaiduVideoIE(InfoExtractor):
|
class BaiduVideoIE(InfoExtractor):
|
||||||
@@ -14,8 +14,8 @@ class BaiduVideoIE(InfoExtractor):
|
|||||||
'url': 'http://v.baidu.com/comic/1069.htm?frp=bdbrand&q=%E4%B8%AD%E5%8D%8E%E5%B0%8F%E5%BD%93%E5%AE%B6',
|
'url': 'http://v.baidu.com/comic/1069.htm?frp=bdbrand&q=%E4%B8%AD%E5%8D%8E%E5%B0%8F%E5%BD%93%E5%AE%B6',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '1069',
|
'id': '1069',
|
||||||
'title': '中华小当家 TV版 (全52集)',
|
'title': '中华小当家 TV版国语',
|
||||||
'description': 'md5:395a419e41215e531c857bb037bbaf80',
|
'description': 'md5:51be07afe461cf99fa61231421b5397c',
|
||||||
},
|
},
|
||||||
'playlist_count': 52,
|
'playlist_count': 52,
|
||||||
}, {
|
}, {
|
||||||
@@ -25,45 +25,32 @@ class BaiduVideoIE(InfoExtractor):
|
|||||||
'title': 're:^奔跑吧兄弟',
|
'title': 're:^奔跑吧兄弟',
|
||||||
'description': 'md5:1bf88bad6d850930f542d51547c089b8',
|
'description': 'md5:1bf88bad6d850930f542d51547c089b8',
|
||||||
},
|
},
|
||||||
'playlist_mincount': 3,
|
'playlist_mincount': 12,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
|
def _call_api(self, path, category, playlist_id, note):
|
||||||
|
return self._download_json('http://app.video.baidu.com/%s/?worktype=adnative%s&id=%s' % (
|
||||||
|
path, category, playlist_id), playlist_id, note)
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
category, playlist_id = re.match(self._VALID_URL, url).groups()
|
||||||
playlist_id = mobj.group('id')
|
|
||||||
category = category2 = mobj.group('type')
|
|
||||||
if category == 'show':
|
if category == 'show':
|
||||||
category2 = 'tvshow'
|
category = 'tvshow'
|
||||||
|
if category == 'tv':
|
||||||
|
category = 'tvplay'
|
||||||
|
|
||||||
webpage = self._download_webpage(url, playlist_id)
|
playlist_detail = self._call_api(
|
||||||
|
'xqinfo', category, playlist_id, 'Download playlist JSON metadata')
|
||||||
|
|
||||||
playlist_title = self._html_search_regex(
|
playlist_title = playlist_detail['title']
|
||||||
r'title\s*:\s*(["\'])(?P<title>[^\']+)\1', webpage,
|
playlist_description = unescapeHTML(playlist_detail.get('intro'))
|
||||||
'playlist title', group='title')
|
|
||||||
playlist_description = self._html_search_regex(
|
|
||||||
r'<input[^>]+class="j-data-intro"[^>]+value="([^"]+)"/>', webpage,
|
|
||||||
playlist_id, 'playlist description')
|
|
||||||
|
|
||||||
site = self._html_search_regex(
|
episodes_detail = self._call_api(
|
||||||
r'filterSite\s*:\s*["\']([^"]*)["\']', webpage,
|
'xqsingle', category, playlist_id, 'Download episodes JSON metadata')
|
||||||
'primary provider site')
|
|
||||||
api_result = self._download_json(
|
|
||||||
'http://v.baidu.com/%s_intro/?dtype=%sPlayUrl&id=%s&site=%s' % (
|
|
||||||
category, category2, playlist_id, site),
|
|
||||||
playlist_id, 'Get playlist links')
|
|
||||||
|
|
||||||
entries = []
|
entries = [self.url_result(
|
||||||
for episode in api_result[0]['episodes']:
|
episode['url'], video_title=episode['title']
|
||||||
episode_id = '%s_%s' % (playlist_id, episode['episode'])
|
) for episode in episodes_detail['videos']]
|
||||||
|
|
||||||
redirect_page = self._download_webpage(
|
|
||||||
compat_urlparse.urljoin(url, episode['url']), episode_id,
|
|
||||||
note='Download Baidu redirect page')
|
|
||||||
real_url = self._html_search_regex(
|
|
||||||
r'location\.replace\("([^"]+)"\)', redirect_page, 'real URL')
|
|
||||||
|
|
||||||
entries.append(self.url_result(
|
|
||||||
real_url, video_title=episode['single_title']))
|
|
||||||
|
|
||||||
return self.playlist_result(
|
return self.playlist_result(
|
||||||
entries, playlist_id, playlist_title, playlist_description)
|
entries, playlist_id, playlist_title, playlist_description)
|
||||||
|
@@ -23,7 +23,17 @@ class BBCCoUkIE(InfoExtractor):
|
|||||||
IE_NAME = 'bbc.co.uk'
|
IE_NAME = 'bbc.co.uk'
|
||||||
IE_DESC = 'BBC iPlayer'
|
IE_DESC = 'BBC iPlayer'
|
||||||
_ID_REGEX = r'[pb][\da-z]{7}'
|
_ID_REGEX = r'[pb][\da-z]{7}'
|
||||||
_VALID_URL = r'https?://(?:www\.)?bbc\.co\.uk/(?:(?:programmes/(?!articles/)|iplayer(?:/[^/]+)?/(?:episode/|playlist/))|music/clips[/#])(?P<id>%s)' % _ID_REGEX
|
_VALID_URL = r'''(?x)
|
||||||
|
https?://
|
||||||
|
(?:www\.)?bbc\.co\.uk/
|
||||||
|
(?:
|
||||||
|
programmes/(?!articles/)|
|
||||||
|
iplayer(?:/[^/]+)?/(?:episode/|playlist/)|
|
||||||
|
music/clips[/#]|
|
||||||
|
radio/player/
|
||||||
|
)
|
||||||
|
(?P<id>%s)
|
||||||
|
''' % _ID_REGEX
|
||||||
|
|
||||||
_MEDIASELECTOR_URLS = [
|
_MEDIASELECTOR_URLS = [
|
||||||
# Provides HQ HLS streams with even better quality that pc mediaset but fails
|
# Provides HQ HLS streams with even better quality that pc mediaset but fails
|
||||||
@@ -114,14 +124,14 @@ class BBCCoUkIE(InfoExtractor):
|
|||||||
},
|
},
|
||||||
'skip': 'Episode is no longer available on BBC iPlayer Radio',
|
'skip': 'Episode is no longer available on BBC iPlayer Radio',
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://www.bbc.co.uk/music/clips/p02frcc3',
|
'url': 'http://www.bbc.co.uk/music/clips/p022h44b',
|
||||||
'note': 'Audio',
|
'note': 'Audio',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'p02frcch',
|
'id': 'p022h44j',
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
'title': 'Pete Tong, Past, Present and Future Special, Madeon - After Hours mix',
|
'title': 'BBC Proms Music Guides, Rachmaninov: Symphonic Dances',
|
||||||
'description': 'French house superstar Madeon takes us out of the club and onto the after party.',
|
'description': "In this Proms Music Guide, Andrew McGregor looks at Rachmaninov's Symphonic Dances.",
|
||||||
'duration': 3507,
|
'duration': 227,
|
||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
# rtmp download
|
# rtmp download
|
||||||
@@ -172,13 +182,25 @@ class BBCCoUkIE(InfoExtractor):
|
|||||||
}, {
|
}, {
|
||||||
# iptv-all mediaset fails with geolocation however there is no geo restriction
|
# iptv-all mediaset fails with geolocation however there is no geo restriction
|
||||||
# for this programme at all
|
# for this programme at all
|
||||||
'url': 'http://www.bbc.co.uk/programmes/b06bp7lf',
|
'url': 'http://www.bbc.co.uk/programmes/b06rkn85',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'b06bp7kf',
|
'id': 'b06rkms3',
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
'title': "Annie Mac's Friday Night, B.Traits sits in for Annie",
|
'title': "Best of the Mini-Mixes 2015: Part 3, Annie Mac's Friday Night - BBC Radio 1",
|
||||||
'description': 'B.Traits sits in for Annie Mac with a Mini-Mix from Disclosure.',
|
'description': "Annie has part three in the Best of the Mini-Mixes 2015, plus the year's Most Played!",
|
||||||
'duration': 10800,
|
},
|
||||||
|
'params': {
|
||||||
|
# rtmp download
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
# compact player (https://github.com/rg3/youtube-dl/issues/8147)
|
||||||
|
'url': 'http://www.bbc.co.uk/programmes/p028bfkf/player',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'p028bfkj',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'Extract from BBC documentary Look Stranger - Giant Leeks and Magic Brews',
|
||||||
|
'description': 'Extract from BBC documentary Look Stranger - Giant Leeks and Magic Brews',
|
||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
# rtmp download
|
# rtmp download
|
||||||
@@ -193,6 +215,9 @@ class BBCCoUkIE(InfoExtractor):
|
|||||||
}, {
|
}, {
|
||||||
'url': 'http://www.bbc.co.uk/iplayer/cbeebies/episode/b0480276/bing-14-atchoo',
|
'url': 'http://www.bbc.co.uk/iplayer/cbeebies/episode/b0480276/bing-14-atchoo',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.bbc.co.uk/radio/player/p03cchwf',
|
||||||
|
'only_matching': True,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -469,9 +494,12 @@ class BBCCoUkIE(InfoExtractor):
|
|||||||
|
|
||||||
if programme_id:
|
if programme_id:
|
||||||
formats, subtitles = self._download_media_selector(programme_id)
|
formats, subtitles = self._download_media_selector(programme_id)
|
||||||
title = self._og_search_title(webpage)
|
title = self._og_search_title(webpage, default=None) or self._html_search_regex(
|
||||||
|
(r'<h2[^>]+id="parent-title"[^>]*>(.+?)</h2>',
|
||||||
|
r'<div[^>]+class="info"[^>]*>\s*<h1>(.+?)</h1>'), webpage, 'title')
|
||||||
description = self._search_regex(
|
description = self._search_regex(
|
||||||
r'<p class="[^"]*medium-description[^"]*">([^<]+)</p>',
|
(r'<p class="[^"]*medium-description[^"]*">([^<]+)</p>',
|
||||||
|
r'<div[^>]+class="info_+synopsis"[^>]*>([^<]+)</div>'),
|
||||||
webpage, 'description', default=None)
|
webpage, 'description', default=None)
|
||||||
if not description:
|
if not description:
|
||||||
description = self._html_search_meta('description', webpage)
|
description = self._html_search_meta('description', webpage)
|
||||||
@@ -705,19 +733,10 @@ class BBCIE(BBCCoUkIE):
|
|||||||
|
|
||||||
webpage = self._download_webpage(url, playlist_id)
|
webpage = self._download_webpage(url, playlist_id)
|
||||||
|
|
||||||
timestamp = None
|
json_ld_info = self._search_json_ld(webpage, playlist_id, default=None)
|
||||||
playlist_title = None
|
timestamp = json_ld_info.get('timestamp')
|
||||||
playlist_description = None
|
playlist_title = json_ld_info.get('title')
|
||||||
|
playlist_description = json_ld_info.get('description')
|
||||||
ld = self._parse_json(
|
|
||||||
self._search_regex(
|
|
||||||
r'(?s)<script type="application/ld\+json">(.+?)</script>',
|
|
||||||
webpage, 'ld json', default='{}'),
|
|
||||||
playlist_id, fatal=False)
|
|
||||||
if ld:
|
|
||||||
timestamp = parse_iso8601(ld.get('datePublished'))
|
|
||||||
playlist_title = ld.get('headline')
|
|
||||||
playlist_description = ld.get('articleBody')
|
|
||||||
|
|
||||||
if not timestamp:
|
if not timestamp:
|
||||||
timestamp = parse_iso8601(self._search_regex(
|
timestamp = parse_iso8601(self._search_regex(
|
||||||
|
@@ -34,7 +34,7 @@ class BeegIE(InfoExtractor):
|
|||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
|
|
||||||
video = self._download_json(
|
video = self._download_json(
|
||||||
'http://beeg.com/api/v5/video/%s' % video_id, video_id)
|
'https://api.beeg.com/api/v5/video/%s' % video_id, video_id)
|
||||||
|
|
||||||
def split(o, e):
|
def split(o, e):
|
||||||
def cut(s, x):
|
def cut(s, x):
|
||||||
@@ -60,7 +60,7 @@ class BeegIE(InfoExtractor):
|
|||||||
|
|
||||||
def decrypt_url(encrypted_url):
|
def decrypt_url(encrypted_url):
|
||||||
encrypted_url = self._proto_relative_url(
|
encrypted_url = self._proto_relative_url(
|
||||||
encrypted_url.replace('{DATA_MARKERS}', ''), 'http:')
|
encrypted_url.replace('{DATA_MARKERS}', ''), 'https:')
|
||||||
key = self._search_regex(
|
key = self._search_regex(
|
||||||
r'/key=(.*?)%2Cend=', encrypted_url, 'key', default=None)
|
r'/key=(.*?)%2Cend=', encrypted_url, 'key', default=None)
|
||||||
if not key:
|
if not key:
|
||||||
|
85
youtube_dl/extractor/bigflix.py
Normal file
85
youtube_dl/extractor/bigflix.py
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import base64
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..compat import compat_urllib_parse_unquote
|
||||||
|
|
||||||
|
|
||||||
|
class BigflixIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?bigflix\.com/.+/(?P<id>[0-9]+)'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://www.bigflix.com/Hindi-movies/Action-movies/Singham-Returns/16537',
|
||||||
|
'md5': 'ec76aa9b1129e2e5b301a474e54fab74',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '16537',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Singham Returns',
|
||||||
|
'description': 'md5:3d2ba5815f14911d5cc6a501ae0cf65d',
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
# 2 formats
|
||||||
|
'url': 'http://www.bigflix.com/Tamil-movies/Drama-movies/Madarasapatinam/16070',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '16070',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Madarasapatinam',
|
||||||
|
'description': 'md5:63b9b8ed79189c6f0418c26d9a3452ca',
|
||||||
|
'formats': 'mincount:2',
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
'skip_download': True,
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
# multiple formats
|
||||||
|
'url': 'http://www.bigflix.com/Malayalam-movies/Drama-movies/Indian-Rupee/15967',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
video_id = self._match_id(url)
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
title = self._html_search_regex(
|
||||||
|
r'<div[^>]+class=["\']pagetitle["\'][^>]*>(.+?)</div>',
|
||||||
|
webpage, 'title')
|
||||||
|
|
||||||
|
def decode_url(quoted_b64_url):
|
||||||
|
return base64.b64decode(compat_urllib_parse_unquote(
|
||||||
|
quoted_b64_url).encode('ascii')).decode('utf-8')
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
for height, encoded_url in re.findall(
|
||||||
|
r'ContentURL_(\d{3,4})[pP][^=]+=([^&]+)', webpage):
|
||||||
|
video_url = decode_url(encoded_url)
|
||||||
|
f = {
|
||||||
|
'url': video_url,
|
||||||
|
'format_id': '%sp' % height,
|
||||||
|
'height': int(height),
|
||||||
|
}
|
||||||
|
if video_url.startswith('rtmp'):
|
||||||
|
f['ext'] = 'flv'
|
||||||
|
formats.append(f)
|
||||||
|
|
||||||
|
file_url = self._search_regex(
|
||||||
|
r'file=([^&]+)', webpage, 'video url', default=None)
|
||||||
|
if file_url:
|
||||||
|
video_url = decode_url(file_url)
|
||||||
|
if all(f['url'] != video_url for f in formats):
|
||||||
|
formats.append({
|
||||||
|
'url': decode_url(file_url),
|
||||||
|
})
|
||||||
|
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
description = self._html_search_meta('description', webpage)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'formats': formats
|
||||||
|
}
|
@@ -1,7 +1,13 @@
|
|||||||
# 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 ..utils import (
|
||||||
|
js_to_json,
|
||||||
|
determine_ext,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class BpbIE(InfoExtractor):
|
class BpbIE(InfoExtractor):
|
||||||
@@ -10,7 +16,8 @@ class BpbIE(InfoExtractor):
|
|||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.bpb.de/mediathek/297/joachim-gauck-zu-1989-und-die-erinnerung-an-die-ddr',
|
'url': 'http://www.bpb.de/mediathek/297/joachim-gauck-zu-1989-und-die-erinnerung-an-die-ddr',
|
||||||
'md5': '0792086e8e2bfbac9cdf27835d5f2093',
|
# md5 fails in Python 2.6 due to buggy server response and wrong handling of urllib2
|
||||||
|
'md5': 'c4f84c8a8044ca9ff68bb8441d300b3f',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '297',
|
'id': '297',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
@@ -25,13 +32,26 @@ class BpbIE(InfoExtractor):
|
|||||||
|
|
||||||
title = self._html_search_regex(
|
title = self._html_search_regex(
|
||||||
r'<h2 class="white">(.*?)</h2>', webpage, 'title')
|
r'<h2 class="white">(.*?)</h2>', webpage, 'title')
|
||||||
video_url = self._html_search_regex(
|
video_info_dicts = re.findall(
|
||||||
r'(http://film\.bpb\.de/player/dokument_[0-9]+\.mp4)',
|
r"({\s*src:\s*'http://film\.bpb\.de/[^}]+})", webpage)
|
||||||
webpage, 'video URL')
|
|
||||||
|
formats = []
|
||||||
|
for video_info in video_info_dicts:
|
||||||
|
video_info = self._parse_json(video_info, video_id, transform_source=js_to_json)
|
||||||
|
quality = video_info['quality']
|
||||||
|
video_url = video_info['src']
|
||||||
|
formats.append({
|
||||||
|
'url': video_url,
|
||||||
|
'preference': 10 if quality == 'high' else 0,
|
||||||
|
'format_note': quality,
|
||||||
|
'format_id': '%s-%s' % (quality, determine_ext(video_url)),
|
||||||
|
})
|
||||||
|
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'url': video_url,
|
'formats': formats,
|
||||||
'title': title,
|
'title': title,
|
||||||
'description': self._og_search_description(webpage),
|
'description': self._og_search_description(webpage),
|
||||||
}
|
}
|
||||||
|
@@ -9,9 +9,9 @@ from ..utils import parse_duration
|
|||||||
|
|
||||||
class Canalc2IE(InfoExtractor):
|
class Canalc2IE(InfoExtractor):
|
||||||
IE_NAME = 'canalc2.tv'
|
IE_NAME = 'canalc2.tv'
|
||||||
_VALID_URL = r'https?://(?:www\.)?canalc2\.tv/video/(?P<id>\d+)'
|
_VALID_URL = r'https?://(?:(?:www\.)?canalc2\.tv/video/|archives-canalc2\.u-strasbg\.fr/video\.asp\?.*\bidVideo=)(?P<id>\d+)'
|
||||||
|
|
||||||
_TEST = {
|
_TESTS = [{
|
||||||
'url': 'http://www.canalc2.tv/video/12163',
|
'url': 'http://www.canalc2.tv/video/12163',
|
||||||
'md5': '060158428b650f896c542dfbb3d6487f',
|
'md5': '060158428b650f896c542dfbb3d6487f',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
@@ -23,24 +23,36 @@ class Canalc2IE(InfoExtractor):
|
|||||||
'params': {
|
'params': {
|
||||||
'skip_download': True, # Requires rtmpdump
|
'skip_download': True, # Requires rtmpdump
|
||||||
}
|
}
|
||||||
}
|
}, {
|
||||||
|
'url': 'http://archives-canalc2.u-strasbg.fr/video.asp?idVideo=11427&voir=oui',
|
||||||
|
'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)
|
||||||
webpage = self._download_webpage(url, video_id)
|
|
||||||
video_url = self._search_regex(
|
webpage = self._download_webpage(
|
||||||
r'jwplayer\((["\'])Player\1\)\.setup\({[^}]*file\s*:\s*(["\'])(?P<file>.+?)\2',
|
'http://www.canalc2.tv/video/%s' % video_id, video_id)
|
||||||
webpage, 'video_url', group='file')
|
|
||||||
formats = [{'url': video_url}]
|
formats = []
|
||||||
if video_url.startswith('rtmp://'):
|
for _, video_url in re.findall(r'file\s*=\s*(["\'])(.+?)\1', webpage):
|
||||||
rtmp = re.search(r'^(?P<url>rtmp://[^/]+/(?P<app>.+/))(?P<play_path>mp4:.+)$', video_url)
|
if video_url.startswith('rtmp://'):
|
||||||
formats[0].update({
|
rtmp = re.search(
|
||||||
'url': rtmp.group('url'),
|
r'^(?P<url>rtmp://[^/]+/(?P<app>.+/))(?P<play_path>mp4:.+)$', video_url)
|
||||||
'ext': 'flv',
|
formats.append({
|
||||||
'app': rtmp.group('app'),
|
'url': rtmp.group('url'),
|
||||||
'play_path': rtmp.group('play_path'),
|
'format_id': 'rtmp',
|
||||||
'page_url': url,
|
'ext': 'flv',
|
||||||
})
|
'app': rtmp.group('app'),
|
||||||
|
'play_path': rtmp.group('play_path'),
|
||||||
|
'page_url': url,
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
formats.append({
|
||||||
|
'url': video_url,
|
||||||
|
'format_id': 'http',
|
||||||
|
})
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
title = self._html_search_regex(
|
title = self._html_search_regex(
|
||||||
r'(?s)class="[^"]*col_description[^"]*">.*?<h3>(.*?)</h3>', webpage, 'title')
|
r'(?s)class="[^"]*col_description[^"]*">.*?<h3>(.*?)</h3>', webpage, 'title')
|
||||||
|
65
youtube_dl/extractor/canvas.py
Normal file
65
youtube_dl/extractor/canvas.py
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import float_or_none
|
||||||
|
|
||||||
|
|
||||||
|
class CanvasIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?canvas\.be/video/(?:[^/]+/)*(?P<id>[^/?#&]+)'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.canvas.be/video/de-afspraak/najaar-2015/de-afspraak-veilt-voor-de-warmste-week',
|
||||||
|
'md5': 'ea838375a547ac787d4064d8c7860a6c',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'mz-ast-5e5f90b6-2d72-4c40-82c2-e134f884e93e',
|
||||||
|
'display_id': 'de-afspraak-veilt-voor-de-warmste-week',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'De afspraak veilt voor de Warmste Week',
|
||||||
|
'description': 'md5:24cb860c320dc2be7358e0e5aa317ba6',
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
'duration': 49.02,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
display_id = self._match_id(url)
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
|
||||||
|
title = self._search_regex(
|
||||||
|
r'<h1[^>]+class="video__body__header__title"[^>]*>(.+?)</h1>',
|
||||||
|
webpage, 'title', default=None) or self._og_search_title(webpage)
|
||||||
|
|
||||||
|
video_id = self._html_search_regex(
|
||||||
|
r'data-video=(["\'])(?P<id>.+?)\1', webpage, 'video id', group='id')
|
||||||
|
|
||||||
|
data = self._download_json(
|
||||||
|
'https://mediazone.vrt.be/api/v1/canvas/assets/%s' % video_id, display_id)
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
for target in data['targetUrls']:
|
||||||
|
format_url, format_type = target.get('url'), target.get('type')
|
||||||
|
if not format_url or not format_type:
|
||||||
|
continue
|
||||||
|
if format_type == 'HLS':
|
||||||
|
formats.extend(self._extract_m3u8_formats(
|
||||||
|
format_url, display_id, entry_protocol='m3u8_native',
|
||||||
|
ext='mp4', preference=0, fatal=False, m3u8_id=format_type))
|
||||||
|
elif format_type == 'HDS':
|
||||||
|
formats.extend(self._extract_f4m_formats(
|
||||||
|
format_url, display_id, f4m_id=format_type, fatal=False))
|
||||||
|
else:
|
||||||
|
formats.append({
|
||||||
|
'format_id': format_type,
|
||||||
|
'url': format_url,
|
||||||
|
})
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'display_id': display_id,
|
||||||
|
'title': title,
|
||||||
|
'description': self._og_search_description(webpage),
|
||||||
|
'formats': formats,
|
||||||
|
'duration': float_or_none(data.get('duration'), 1000),
|
||||||
|
'thumbnail': data.get('posterImageUrl'),
|
||||||
|
}
|
@@ -1,16 +1,14 @@
|
|||||||
# encoding: utf-8
|
# encoding: utf-8
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
|
||||||
import json
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import remove_start
|
from .theplatform import ThePlatformIE
|
||||||
|
from ..utils import parse_duration
|
||||||
|
|
||||||
|
|
||||||
class CBSNewsIE(InfoExtractor):
|
class CBSNewsIE(ThePlatformIE):
|
||||||
IE_DESC = 'CBS News'
|
IE_DESC = 'CBS News'
|
||||||
_VALID_URL = r'http://(?:www\.)?cbsnews\.com/(?:[^/]+/)+(?P<id>[\da-z_-]+)'
|
_VALID_URL = r'http://(?:www\.)?cbsnews\.com/(?:news|videos)/(?P<id>[\da-z_-]+)'
|
||||||
|
|
||||||
_TESTS = [
|
_TESTS = [
|
||||||
{
|
{
|
||||||
@@ -31,59 +29,54 @@ class CBSNewsIE(InfoExtractor):
|
|||||||
'url': 'http://www.cbsnews.com/videos/fort-hood-shooting-army-downplays-mental-illness-as-cause-of-attack/',
|
'url': 'http://www.cbsnews.com/videos/fort-hood-shooting-army-downplays-mental-illness-as-cause-of-attack/',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'fort-hood-shooting-army-downplays-mental-illness-as-cause-of-attack',
|
'id': 'fort-hood-shooting-army-downplays-mental-illness-as-cause-of-attack',
|
||||||
'ext': 'flv',
|
'ext': 'mp4',
|
||||||
'title': 'Fort Hood shooting: Army downplays mental illness as cause of attack',
|
'title': 'Fort Hood shooting: Army downplays mental illness as cause of attack',
|
||||||
'thumbnail': 're:^https?://.*\.jpg$',
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
'duration': 205,
|
'duration': 205,
|
||||||
|
'subtitles': {
|
||||||
|
'en': [{
|
||||||
|
'ext': 'ttml',
|
||||||
|
}],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
# rtmp download
|
# m3u8 download
|
||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
video_id = self._match_id(url)
|
||||||
video_id = mobj.group('id')
|
|
||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
video_info = json.loads(self._html_search_regex(
|
video_info = self._parse_json(self._html_search_regex(
|
||||||
r'(?:<ul class="media-list items" id="media-related-items"><li data-video-info|<div id="cbsNewsVideoPlayer" data-video-player-options)=\'({.+?})\'',
|
r'(?:<ul class="media-list items" id="media-related-items"><li data-video-info|<div id="cbsNewsVideoPlayer" data-video-player-options)=\'({.+?})\'',
|
||||||
webpage, 'video JSON info'))
|
webpage, 'video JSON info'), video_id)
|
||||||
|
|
||||||
item = video_info['item'] if 'item' in video_info else video_info
|
item = video_info['item'] if 'item' in video_info else video_info
|
||||||
title = item.get('articleTitle') or item.get('hed')
|
title = item.get('articleTitle') or item.get('hed')
|
||||||
duration = item.get('duration')
|
duration = item.get('duration')
|
||||||
thumbnail = item.get('mediaImage') or item.get('thumbnail')
|
thumbnail = item.get('mediaImage') or item.get('thumbnail')
|
||||||
|
|
||||||
|
subtitles = {}
|
||||||
|
if 'mpxRefId' in video_info:
|
||||||
|
subtitles['en'] = [{
|
||||||
|
'ext': 'ttml',
|
||||||
|
'url': 'http://www.cbsnews.com/videos/captions/%s.adb_xml' % video_info['mpxRefId'],
|
||||||
|
}]
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
for format_id in ['RtmpMobileLow', 'RtmpMobileHigh', 'Hls', 'RtmpDesktop']:
|
for format_id in ['RtmpMobileLow', 'RtmpMobileHigh', 'Hls', 'RtmpDesktop']:
|
||||||
uri = item.get('media' + format_id + 'URI')
|
pid = item.get('media' + format_id)
|
||||||
if not uri:
|
if not pid:
|
||||||
continue
|
continue
|
||||||
uri = remove_start(uri, '{manifest:none}')
|
release_url = 'http://link.theplatform.com/s/dJ5BDC/%s?format=SMIL&mbr=true' % pid
|
||||||
fmt = {
|
tp_formats, tp_subtitles = self._extract_theplatform_smil(release_url, video_id, 'Downloading %s SMIL data' % pid)
|
||||||
'url': uri,
|
formats.extend(tp_formats)
|
||||||
'format_id': format_id,
|
subtitles = self._merge_subtitles(subtitles, tp_subtitles)
|
||||||
}
|
self._sort_formats(formats)
|
||||||
if uri.startswith('rtmp'):
|
|
||||||
play_path = re.sub(
|
|
||||||
r'{slistFilePath}', '',
|
|
||||||
uri.split('<break>')[-1].split('{break}')[-1])
|
|
||||||
play_path = re.sub(
|
|
||||||
r'{manifest:.+}.*$', '', play_path)
|
|
||||||
fmt.update({
|
|
||||||
'app': 'ondemand?auth=cbs',
|
|
||||||
'play_path': 'mp4:' + play_path,
|
|
||||||
'player_url': 'http://www.cbsnews.com/[[IMPORT]]/vidtech.cbsinteractive.com/player/3_3_0/CBSI_PLAYER_HD.swf',
|
|
||||||
'page_url': 'http://www.cbsnews.com',
|
|
||||||
'ext': 'flv',
|
|
||||||
})
|
|
||||||
elif uri.endswith('.m3u8'):
|
|
||||||
fmt['ext'] = 'mp4'
|
|
||||||
formats.append(fmt)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
@@ -91,4 +84,43 @@ class CBSNewsIE(InfoExtractor):
|
|||||||
'thumbnail': thumbnail,
|
'thumbnail': thumbnail,
|
||||||
'duration': duration,
|
'duration': duration,
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
|
'subtitles': subtitles,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class CBSNewsLiveVideoIE(InfoExtractor):
|
||||||
|
IE_DESC = 'CBS News Live Videos'
|
||||||
|
_VALID_URL = r'http://(?:www\.)?cbsnews\.com/live/video/(?P<id>[\da-z_-]+)'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.cbsnews.com/live/video/clinton-sanders-prepare-to-face-off-in-nh/',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'clinton-sanders-prepare-to-face-off-in-nh',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'Clinton, Sanders Prepare To Face Off In NH',
|
||||||
|
'duration': 334,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
video_id = self._match_id(url)
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
video_info = self._parse_json(self._html_search_regex(
|
||||||
|
r'data-story-obj=\'({.+?})\'', webpage, 'video JSON info'), video_id)['story']
|
||||||
|
|
||||||
|
hdcore_sign = 'hdcore=3.3.1'
|
||||||
|
f4m_formats = self._extract_f4m_formats(video_info['url'] + '&' + hdcore_sign, video_id)
|
||||||
|
if f4m_formats:
|
||||||
|
for entry in f4m_formats:
|
||||||
|
# URLs without the extra param induce an 404 error
|
||||||
|
entry.update({'extra_param_to_segment_url': hdcore_sign})
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': video_info['headline'],
|
||||||
|
'thumbnail': video_info.get('thumbnail_url_hd') or video_info.get('thumbnail_url_sd'),
|
||||||
|
'duration': parse_duration(video_info.get('segmentDur')),
|
||||||
|
'formats': f4m_formats,
|
||||||
}
|
}
|
||||||
|
@@ -5,6 +5,7 @@ import re
|
|||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
int_or_none,
|
int_or_none,
|
||||||
|
parse_duration,
|
||||||
qualities,
|
qualities,
|
||||||
unified_strdate,
|
unified_strdate,
|
||||||
)
|
)
|
||||||
@@ -12,21 +13,25 @@ from ..utils import (
|
|||||||
|
|
||||||
class CCCIE(InfoExtractor):
|
class CCCIE(InfoExtractor):
|
||||||
IE_NAME = 'media.ccc.de'
|
IE_NAME = 'media.ccc.de'
|
||||||
_VALID_URL = r'https?://(?:www\.)?media\.ccc\.de/[^?#]+/[^?#/]*?_(?P<id>[0-9]{8,})._[^?#/]*\.html'
|
_VALID_URL = r'https?://(?:www\.)?media\.ccc\.de/v/(?P<id>[^/?#&]+)'
|
||||||
|
|
||||||
_TEST = {
|
_TESTS = [{
|
||||||
'url': 'http://media.ccc.de/browse/congress/2013/30C3_-_5443_-_en_-_saal_g_-_201312281830_-_introduction_to_processor_design_-_byterazor.html#video',
|
'url': 'https://media.ccc.de/v/30C3_-_5443_-_en_-_saal_g_-_201312281830_-_introduction_to_processor_design_-_byterazor#video',
|
||||||
'md5': '3a1eda8f3a29515d27f5adb967d7e740',
|
'md5': '3a1eda8f3a29515d27f5adb967d7e740',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '20131228183',
|
'id': '30C3_-_5443_-_en_-_saal_g_-_201312281830_-_introduction_to_processor_design_-_byterazor',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Introduction to Processor Design',
|
'title': 'Introduction to Processor Design',
|
||||||
'description': 'md5:5ddbf8c734800267f2cee4eab187bc1b',
|
'description': 'md5:80be298773966f66d56cb11260b879af',
|
||||||
'thumbnail': 're:^https?://.*\.jpg$',
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
'view_count': int,
|
'view_count': int,
|
||||||
'upload_date': '20131229',
|
'upload_date': '20131228',
|
||||||
|
'duration': 3660,
|
||||||
}
|
}
|
||||||
}
|
}, {
|
||||||
|
'url': 'https://media.ccc.de/v/32c3-7368-shopshifting#download',
|
||||||
|
'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)
|
||||||
@@ -40,21 +45,25 @@ class CCCIE(InfoExtractor):
|
|||||||
title = self._html_search_regex(
|
title = self._html_search_regex(
|
||||||
r'(?s)<h1>(.*?)</h1>', webpage, 'title')
|
r'(?s)<h1>(.*?)</h1>', webpage, 'title')
|
||||||
description = self._html_search_regex(
|
description = self._html_search_regex(
|
||||||
r"(?s)<p class='description'>(.*?)</p>",
|
r"(?s)<h3>About</h3>(.+?)<h3>",
|
||||||
webpage, 'description', fatal=False)
|
webpage, 'description', fatal=False)
|
||||||
upload_date = unified_strdate(self._html_search_regex(
|
upload_date = unified_strdate(self._html_search_regex(
|
||||||
r"(?s)<span class='[^']*fa-calendar-o'></span>(.*?)</li>",
|
r"(?s)<span[^>]+class='[^']*fa-calendar-o'[^>]*>(.+?)</span>",
|
||||||
webpage, 'upload date', fatal=False))
|
webpage, 'upload date', fatal=False))
|
||||||
view_count = int_or_none(self._html_search_regex(
|
view_count = int_or_none(self._html_search_regex(
|
||||||
r"(?s)<span class='[^']*fa-eye'></span>(.*?)</li>",
|
r"(?s)<span class='[^']*fa-eye'></span>(.*?)</li>",
|
||||||
webpage, 'view count', fatal=False))
|
webpage, 'view count', fatal=False))
|
||||||
|
duration = parse_duration(self._html_search_regex(
|
||||||
|
r'(?s)<span[^>]+class=(["\']).*?fa-clock-o.*?\1[^>]*></span>(?P<duration>.+?)</li',
|
||||||
|
webpage, 'duration', fatal=False, group='duration'))
|
||||||
|
|
||||||
matches = re.finditer(r'''(?xs)
|
matches = re.finditer(r'''(?xs)
|
||||||
<(?:span|div)\s+class='label\s+filetype'>(?P<format>.*?)</(?:span|div)>\s*
|
<(?:span|div)\s+class='label\s+filetype'>(?P<format>[^<]*)</(?:span|div)>\s*
|
||||||
|
<(?:span|div)\s+class='label\s+filetype'>(?P<lang>[^<]*)</(?:span|div)>\s*
|
||||||
<a\s+download\s+href='(?P<http_url>[^']+)'>\s*
|
<a\s+download\s+href='(?P<http_url>[^']+)'>\s*
|
||||||
(?:
|
(?:
|
||||||
.*?
|
.*?
|
||||||
<a\s+href='(?P<torrent_url>[^']+\.torrent)'
|
<a\s+(?:download\s+)?href='(?P<torrent_url>[^']+\.torrent)'
|
||||||
)?''', webpage)
|
)?''', webpage)
|
||||||
formats = []
|
formats = []
|
||||||
for m in matches:
|
for m in matches:
|
||||||
@@ -62,12 +71,15 @@ class CCCIE(InfoExtractor):
|
|||||||
format_id = self._search_regex(
|
format_id = self._search_regex(
|
||||||
r'.*/([a-z0-9_-]+)/[^/]*$',
|
r'.*/([a-z0-9_-]+)/[^/]*$',
|
||||||
m.group('http_url'), 'format id', default=None)
|
m.group('http_url'), 'format id', default=None)
|
||||||
|
if format_id:
|
||||||
|
format_id = m.group('lang') + '-' + format_id
|
||||||
vcodec = 'h264' if 'h264' in format_id else (
|
vcodec = 'h264' if 'h264' in format_id else (
|
||||||
'none' if format_id in ('mp3', 'opus') else None
|
'none' if format_id in ('mp3', 'opus') else None
|
||||||
)
|
)
|
||||||
formats.append({
|
formats.append({
|
||||||
'format_id': format_id,
|
'format_id': format_id,
|
||||||
'format': format,
|
'format': format,
|
||||||
|
'language': m.group('lang'),
|
||||||
'url': m.group('http_url'),
|
'url': m.group('http_url'),
|
||||||
'vcodec': vcodec,
|
'vcodec': vcodec,
|
||||||
'preference': preference(format_id),
|
'preference': preference(format_id),
|
||||||
@@ -95,5 +107,6 @@ class CCCIE(InfoExtractor):
|
|||||||
'thumbnail': thumbnail,
|
'thumbnail': thumbnail,
|
||||||
'view_count': view_count,
|
'view_count': view_count,
|
||||||
'upload_date': upload_date,
|
'upload_date': upload_date,
|
||||||
|
'duration': duration,
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
}
|
}
|
||||||
|
@@ -10,6 +10,7 @@ import re
|
|||||||
import socket
|
import socket
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
import math
|
||||||
|
|
||||||
from ..compat import (
|
from ..compat import (
|
||||||
compat_cookiejar,
|
compat_cookiejar,
|
||||||
@@ -34,6 +35,7 @@ from ..utils import (
|
|||||||
fix_xml_ampersands,
|
fix_xml_ampersands,
|
||||||
float_or_none,
|
float_or_none,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
|
parse_iso8601,
|
||||||
RegexNotFoundError,
|
RegexNotFoundError,
|
||||||
sanitize_filename,
|
sanitize_filename,
|
||||||
sanitized_Request,
|
sanitized_Request,
|
||||||
@@ -43,6 +45,7 @@ from ..utils import (
|
|||||||
xpath_text,
|
xpath_text,
|
||||||
xpath_with_ns,
|
xpath_with_ns,
|
||||||
determine_protocol,
|
determine_protocol,
|
||||||
|
parse_duration,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -108,8 +111,9 @@ class InfoExtractor(object):
|
|||||||
-2 or smaller for less than default.
|
-2 or smaller for less than default.
|
||||||
< -1000 to hide the format (if there is
|
< -1000 to hide the format (if there is
|
||||||
another one which is strictly better)
|
another one which is strictly better)
|
||||||
* language_preference Is this in the correct requested
|
* language Language code, e.g. "de" or "en-US".
|
||||||
language?
|
* language_preference Is this in the language mentioned in
|
||||||
|
the URL?
|
||||||
10 if it's what the URL is about,
|
10 if it's what the URL is about,
|
||||||
-1 for default (don't know),
|
-1 for default (don't know),
|
||||||
-10 otherwise, other values reserved for now.
|
-10 otherwise, other values reserved for now.
|
||||||
@@ -200,6 +204,26 @@ class InfoExtractor(object):
|
|||||||
end_time: Time in seconds where the reproduction should end, as
|
end_time: Time in seconds where the reproduction should end, as
|
||||||
specified in the URL.
|
specified in the URL.
|
||||||
|
|
||||||
|
The following fields should only be used when the video belongs to some logical
|
||||||
|
chapter or section:
|
||||||
|
|
||||||
|
chapter: Name or title of the chapter the video belongs to.
|
||||||
|
chapter_number: Number of the chapter the video belongs to, as an integer.
|
||||||
|
chapter_id: Id of the chapter the video belongs to, as a unicode string.
|
||||||
|
|
||||||
|
The following fields should only be used when the video is an episode of some
|
||||||
|
series or programme:
|
||||||
|
|
||||||
|
series: Title of the series or programme the video episode belongs to.
|
||||||
|
season: Title of the season the video episode belongs to.
|
||||||
|
season_number: Number of the season the video episode belongs to, as an integer.
|
||||||
|
season_id: Id of the season the video episode belongs to, as a unicode string.
|
||||||
|
episode: Title of the video episode. Unlike mandatory video title field,
|
||||||
|
this field should denote the exact title of the video episode
|
||||||
|
without any kind of decoration.
|
||||||
|
episode_number: Number of the video episode within a season, as an integer.
|
||||||
|
episode_id: Id of the video episode, as a unicode string.
|
||||||
|
|
||||||
Unless mentioned otherwise, the fields should be Unicode strings.
|
Unless mentioned otherwise, the fields should be Unicode strings.
|
||||||
|
|
||||||
Unless mentioned otherwise, None is equivalent to absence of information.
|
Unless mentioned otherwise, None is equivalent to absence of information.
|
||||||
@@ -292,9 +316,9 @@ class InfoExtractor(object):
|
|||||||
except ExtractorError:
|
except ExtractorError:
|
||||||
raise
|
raise
|
||||||
except compat_http_client.IncompleteRead as e:
|
except compat_http_client.IncompleteRead as e:
|
||||||
raise ExtractorError('A network error has occured.', cause=e, expected=True)
|
raise ExtractorError('A network error has occurred.', cause=e, expected=True)
|
||||||
except (KeyError, StopIteration) as e:
|
except (KeyError, StopIteration) as e:
|
||||||
raise ExtractorError('An extractor error has occured.', cause=e)
|
raise ExtractorError('An extractor error has occurred.', cause=e)
|
||||||
|
|
||||||
def set_downloader(self, downloader):
|
def set_downloader(self, downloader):
|
||||||
"""Sets the downloader for this IE."""
|
"""Sets the downloader for this IE."""
|
||||||
@@ -741,6 +765,42 @@ class InfoExtractor(object):
|
|||||||
return self._html_search_meta('twitter:player', html,
|
return self._html_search_meta('twitter:player', html,
|
||||||
'twitter card player')
|
'twitter card player')
|
||||||
|
|
||||||
|
def _search_json_ld(self, html, video_id, **kwargs):
|
||||||
|
json_ld = self._search_regex(
|
||||||
|
r'(?s)<script[^>]+type=(["\'])application/ld\+json\1[^>]*>(?P<json_ld>.+?)</script>',
|
||||||
|
html, 'JSON-LD', group='json_ld', **kwargs)
|
||||||
|
if not json_ld:
|
||||||
|
return {}
|
||||||
|
return self._json_ld(json_ld, video_id, fatal=kwargs.get('fatal', True))
|
||||||
|
|
||||||
|
def _json_ld(self, json_ld, video_id, fatal=True):
|
||||||
|
if isinstance(json_ld, compat_str):
|
||||||
|
json_ld = self._parse_json(json_ld, video_id, fatal=fatal)
|
||||||
|
if not json_ld:
|
||||||
|
return {}
|
||||||
|
info = {}
|
||||||
|
if json_ld.get('@context') == 'http://schema.org':
|
||||||
|
item_type = json_ld.get('@type')
|
||||||
|
if item_type == 'TVEpisode':
|
||||||
|
info.update({
|
||||||
|
'episode': unescapeHTML(json_ld.get('name')),
|
||||||
|
'episode_number': int_or_none(json_ld.get('episodeNumber')),
|
||||||
|
'description': unescapeHTML(json_ld.get('description')),
|
||||||
|
})
|
||||||
|
part_of_season = json_ld.get('partOfSeason')
|
||||||
|
if isinstance(part_of_season, dict) and part_of_season.get('@type') == 'TVSeason':
|
||||||
|
info['season_number'] = int_or_none(part_of_season.get('seasonNumber'))
|
||||||
|
part_of_series = json_ld.get('partOfSeries')
|
||||||
|
if isinstance(part_of_series, dict) and part_of_series.get('@type') == 'TVSeries':
|
||||||
|
info['series'] = unescapeHTML(part_of_series.get('name'))
|
||||||
|
elif item_type == 'Article':
|
||||||
|
info.update({
|
||||||
|
'timestamp': parse_iso8601(json_ld.get('datePublished')),
|
||||||
|
'title': unescapeHTML(json_ld.get('headline')),
|
||||||
|
'description': unescapeHTML(json_ld.get('articleBody')),
|
||||||
|
})
|
||||||
|
return dict((k, v) for k, v in info.items() if v is not None)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _hidden_inputs(html):
|
def _hidden_inputs(html):
|
||||||
html = re.sub(r'<!--(?:(?!<!--).)*-->', '', html)
|
html = re.sub(r'<!--(?:(?!<!--).)*-->', '', html)
|
||||||
@@ -767,6 +827,12 @@ class InfoExtractor(object):
|
|||||||
if not formats:
|
if not formats:
|
||||||
raise ExtractorError('No video formats found')
|
raise ExtractorError('No video formats found')
|
||||||
|
|
||||||
|
for f in formats:
|
||||||
|
# Automatically determine tbr when missing based on abr and vbr (improves
|
||||||
|
# formats sorting in some cases)
|
||||||
|
if 'tbr' not in f and f.get('abr') is not None and f.get('vbr') is not None:
|
||||||
|
f['tbr'] = f['abr'] + f['vbr']
|
||||||
|
|
||||||
def _formats_key(f):
|
def _formats_key(f):
|
||||||
# TODO remove the following workaround
|
# TODO remove the following workaround
|
||||||
from ..utils import determine_ext
|
from ..utils import determine_ext
|
||||||
@@ -956,6 +1022,18 @@ class InfoExtractor(object):
|
|||||||
return []
|
return []
|
||||||
m3u8_doc, urlh = res
|
m3u8_doc, urlh = res
|
||||||
m3u8_url = urlh.geturl()
|
m3u8_url = urlh.geturl()
|
||||||
|
# A Media Playlist Tag MUST NOT appear in a Master Playlist
|
||||||
|
# https://tools.ietf.org/html/draft-pantos-http-live-streaming-17#section-4.3.3
|
||||||
|
# The EXT-X-TARGETDURATION tag is REQUIRED for every M3U8 Media Playlists
|
||||||
|
# https://tools.ietf.org/html/draft-pantos-http-live-streaming-17#section-4.3.3.1
|
||||||
|
if '#EXT-X-TARGETDURATION' in m3u8_doc:
|
||||||
|
return [{
|
||||||
|
'url': m3u8_url,
|
||||||
|
'format_id': m3u8_id,
|
||||||
|
'ext': ext,
|
||||||
|
'protocol': entry_protocol,
|
||||||
|
'preference': preference,
|
||||||
|
}]
|
||||||
last_info = None
|
last_info = None
|
||||||
last_media = None
|
last_media = None
|
||||||
kv_rex = re.compile(
|
kv_rex = re.compile(
|
||||||
@@ -1000,9 +1078,9 @@ class InfoExtractor(object):
|
|||||||
# TODO: looks like video codec is not always necessarily goes first
|
# TODO: looks like video codec is not always necessarily goes first
|
||||||
va_codecs = codecs.split(',')
|
va_codecs = codecs.split(',')
|
||||||
if va_codecs[0]:
|
if va_codecs[0]:
|
||||||
f['vcodec'] = va_codecs[0].partition('.')[0]
|
f['vcodec'] = va_codecs[0]
|
||||||
if len(va_codecs) > 1 and va_codecs[1]:
|
if len(va_codecs) > 1 and va_codecs[1]:
|
||||||
f['acodec'] = va_codecs[1].partition('.')[0]
|
f['acodec'] = va_codecs[1]
|
||||||
resolution = last_info.get('RESOLUTION')
|
resolution = last_info.get('RESOLUTION')
|
||||||
if resolution:
|
if resolution:
|
||||||
width_str, height_str = resolution.split('x')
|
width_str, height_str = resolution.split('x')
|
||||||
@@ -1106,6 +1184,7 @@ class InfoExtractor(object):
|
|||||||
formats = []
|
formats = []
|
||||||
rtmp_count = 0
|
rtmp_count = 0
|
||||||
http_count = 0
|
http_count = 0
|
||||||
|
m3u8_count = 0
|
||||||
|
|
||||||
videos = smil.findall(self._xpath_ns('.//video', namespace))
|
videos = smil.findall(self._xpath_ns('.//video', namespace))
|
||||||
for video in videos:
|
for video in videos:
|
||||||
@@ -1145,8 +1224,17 @@ class InfoExtractor(object):
|
|||||||
src_url = src if src.startswith('http') else compat_urlparse.urljoin(base, src)
|
src_url = src if src.startswith('http') else compat_urlparse.urljoin(base, src)
|
||||||
|
|
||||||
if proto == 'm3u8' or src_ext == 'm3u8':
|
if proto == 'm3u8' or src_ext == 'm3u8':
|
||||||
formats.extend(self._extract_m3u8_formats(
|
m3u8_formats = self._extract_m3u8_formats(
|
||||||
src_url, video_id, ext or 'mp4', m3u8_id='hls', fatal=False))
|
src_url, video_id, ext or 'mp4', m3u8_id='hls', fatal=False)
|
||||||
|
if len(m3u8_formats) == 1:
|
||||||
|
m3u8_count += 1
|
||||||
|
m3u8_formats[0].update({
|
||||||
|
'format_id': 'hls-%d' % (m3u8_count if bitrate is None else bitrate),
|
||||||
|
'tbr': bitrate,
|
||||||
|
'width': width,
|
||||||
|
'height': height,
|
||||||
|
})
|
||||||
|
formats.extend(m3u8_formats)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if src_ext == 'f4m':
|
if src_ext == 'f4m':
|
||||||
@@ -1244,6 +1332,158 @@ class InfoExtractor(object):
|
|||||||
})
|
})
|
||||||
return entries
|
return entries
|
||||||
|
|
||||||
|
def _extract_mpd_formats(self, mpd_url, video_id, mpd_id=None, note=None, errnote=None, fatal=True, formats_dict={}):
|
||||||
|
res = self._download_webpage_handle(
|
||||||
|
mpd_url, video_id,
|
||||||
|
note=note or 'Downloading MPD manifest',
|
||||||
|
errnote=errnote or 'Failed to download MPD manifest',
|
||||||
|
fatal=fatal)
|
||||||
|
if res is False:
|
||||||
|
return []
|
||||||
|
mpd, urlh = res
|
||||||
|
mpd_base_url = re.match(r'https?://.+/', urlh.geturl()).group()
|
||||||
|
|
||||||
|
return self._parse_mpd_formats(
|
||||||
|
compat_etree_fromstring(mpd.encode('utf-8')), mpd_id, mpd_base_url, formats_dict=formats_dict)
|
||||||
|
|
||||||
|
def _parse_mpd_formats(self, mpd_doc, mpd_id=None, mpd_base_url='', formats_dict={}):
|
||||||
|
if mpd_doc.get('type') == 'dynamic':
|
||||||
|
return []
|
||||||
|
|
||||||
|
namespace = self._search_regex(r'(?i)^{([^}]+)?}MPD$', mpd_doc.tag, 'namespace', default=None)
|
||||||
|
|
||||||
|
def _add_ns(path):
|
||||||
|
return self._xpath_ns(path, namespace)
|
||||||
|
|
||||||
|
def is_drm_protected(element):
|
||||||
|
return element.find(_add_ns('ContentProtection')) is not None
|
||||||
|
|
||||||
|
def extract_multisegment_info(element, ms_parent_info):
|
||||||
|
ms_info = ms_parent_info.copy()
|
||||||
|
segment_list = element.find(_add_ns('SegmentList'))
|
||||||
|
if segment_list is not None:
|
||||||
|
segment_urls_e = segment_list.findall(_add_ns('SegmentURL'))
|
||||||
|
if segment_urls_e:
|
||||||
|
ms_info['segment_urls'] = [segment.attrib['media'] for segment in segment_urls_e]
|
||||||
|
initialization = segment_list.find(_add_ns('Initialization'))
|
||||||
|
if initialization is not None:
|
||||||
|
ms_info['initialization_url'] = initialization.attrib['sourceURL']
|
||||||
|
else:
|
||||||
|
segment_template = element.find(_add_ns('SegmentTemplate'))
|
||||||
|
if segment_template is not None:
|
||||||
|
start_number = segment_template.get('startNumber')
|
||||||
|
if start_number:
|
||||||
|
ms_info['start_number'] = int(start_number)
|
||||||
|
segment_timeline = segment_template.find(_add_ns('SegmentTimeline'))
|
||||||
|
if segment_timeline is not None:
|
||||||
|
s_e = segment_timeline.findall(_add_ns('S'))
|
||||||
|
if s_e:
|
||||||
|
ms_info['total_number'] = 0
|
||||||
|
for s in s_e:
|
||||||
|
ms_info['total_number'] += 1 + int(s.get('r', '0'))
|
||||||
|
else:
|
||||||
|
timescale = segment_template.get('timescale')
|
||||||
|
if timescale:
|
||||||
|
ms_info['timescale'] = int(timescale)
|
||||||
|
segment_duration = segment_template.get('duration')
|
||||||
|
if segment_duration:
|
||||||
|
ms_info['segment_duration'] = int(segment_duration)
|
||||||
|
media_template = segment_template.get('media')
|
||||||
|
if media_template:
|
||||||
|
ms_info['media_template'] = media_template
|
||||||
|
initialization = segment_template.get('initialization')
|
||||||
|
if initialization:
|
||||||
|
ms_info['initialization_url'] = initialization
|
||||||
|
else:
|
||||||
|
initialization = segment_template.find(_add_ns('Initialization'))
|
||||||
|
if initialization is not None:
|
||||||
|
ms_info['initialization_url'] = initialization.attrib['sourceURL']
|
||||||
|
return ms_info
|
||||||
|
|
||||||
|
mpd_duration = parse_duration(mpd_doc.get('mediaPresentationDuration'))
|
||||||
|
formats = []
|
||||||
|
for period in mpd_doc.findall(_add_ns('Period')):
|
||||||
|
period_duration = parse_duration(period.get('duration')) or mpd_duration
|
||||||
|
period_ms_info = extract_multisegment_info(period, {
|
||||||
|
'start_number': 1,
|
||||||
|
'timescale': 1,
|
||||||
|
})
|
||||||
|
for adaptation_set in period.findall(_add_ns('AdaptationSet')):
|
||||||
|
if is_drm_protected(adaptation_set):
|
||||||
|
continue
|
||||||
|
adaption_set_ms_info = extract_multisegment_info(adaptation_set, period_ms_info)
|
||||||
|
for representation in adaptation_set.findall(_add_ns('Representation')):
|
||||||
|
if is_drm_protected(representation):
|
||||||
|
continue
|
||||||
|
representation_attrib = adaptation_set.attrib.copy()
|
||||||
|
representation_attrib.update(representation.attrib)
|
||||||
|
mime_type = representation_attrib.get('mimeType')
|
||||||
|
content_type = mime_type.split('/')[0] if mime_type else representation_attrib.get('contentType')
|
||||||
|
if content_type == 'text':
|
||||||
|
# TODO implement WebVTT downloading
|
||||||
|
pass
|
||||||
|
elif content_type == 'video' or content_type == 'audio':
|
||||||
|
base_url = ''
|
||||||
|
for element in (representation, adaptation_set, period, mpd_doc):
|
||||||
|
base_url_e = element.find(_add_ns('BaseURL'))
|
||||||
|
if base_url_e is not None:
|
||||||
|
base_url = base_url_e.text + base_url
|
||||||
|
if re.match(r'^https?://', base_url):
|
||||||
|
break
|
||||||
|
if not re.match(r'^https?://', base_url):
|
||||||
|
base_url = mpd_base_url + base_url
|
||||||
|
representation_id = representation_attrib.get('id')
|
||||||
|
lang = representation_attrib.get('lang')
|
||||||
|
f = {
|
||||||
|
'format_id': mpd_id or representation_id,
|
||||||
|
'url': base_url,
|
||||||
|
'width': int_or_none(representation_attrib.get('width')),
|
||||||
|
'height': int_or_none(representation_attrib.get('height')),
|
||||||
|
'tbr': int_or_none(representation_attrib.get('bandwidth'), 1000),
|
||||||
|
'asr': int_or_none(representation_attrib.get('audioSamplingRate')),
|
||||||
|
'fps': int_or_none(representation_attrib.get('frameRate')),
|
||||||
|
'vcodec': 'none' if content_type == 'audio' else representation_attrib.get('codecs'),
|
||||||
|
'acodec': 'none' if content_type == 'video' else representation_attrib.get('codecs'),
|
||||||
|
'language': lang if lang not in ('mul', 'und', 'zxx', 'mis') else None,
|
||||||
|
'format_note': 'DASH %s' % content_type,
|
||||||
|
}
|
||||||
|
representation_ms_info = extract_multisegment_info(representation, adaption_set_ms_info)
|
||||||
|
if 'segment_urls' not in representation_ms_info and 'media_template' in representation_ms_info:
|
||||||
|
if 'total_number' not in representation_ms_info and 'segment_duration':
|
||||||
|
segment_duration = float(representation_ms_info['segment_duration']) / float(representation_ms_info['timescale'])
|
||||||
|
representation_ms_info['total_number'] = int(math.ceil(float(period_duration) / segment_duration))
|
||||||
|
media_template = representation_ms_info['media_template']
|
||||||
|
media_template = media_template.replace('$RepresentationID$', representation_id)
|
||||||
|
media_template = re.sub(r'\$(Number|Bandwidth)(?:%(0\d+)d)?\$', r'%(\1)\2d', media_template)
|
||||||
|
media_template.replace('$$', '$')
|
||||||
|
representation_ms_info['segment_urls'] = [media_template % {'Number': segment_number, 'Bandwidth': representation_attrib.get('bandwidth')} for segment_number in range(representation_ms_info['start_number'], representation_ms_info['total_number'] + representation_ms_info['start_number'])]
|
||||||
|
if 'segment_urls' in representation_ms_info:
|
||||||
|
f.update({
|
||||||
|
'segment_urls': representation_ms_info['segment_urls'],
|
||||||
|
'protocol': 'http_dash_segments',
|
||||||
|
})
|
||||||
|
if 'initialization_url' in representation_ms_info:
|
||||||
|
initialization_url = representation_ms_info['initialization_url'].replace('$RepresentationID$', representation_id)
|
||||||
|
f.update({
|
||||||
|
'initialization_url': initialization_url,
|
||||||
|
})
|
||||||
|
if not f.get('url'):
|
||||||
|
f['url'] = initialization_url
|
||||||
|
try:
|
||||||
|
existing_format = next(
|
||||||
|
fo for fo in formats
|
||||||
|
if fo['format_id'] == representation_id)
|
||||||
|
except StopIteration:
|
||||||
|
full_info = formats_dict.get(representation_id, {}).copy()
|
||||||
|
full_info.update(f)
|
||||||
|
formats.append(full_info)
|
||||||
|
else:
|
||||||
|
existing_format.update(f)
|
||||||
|
else:
|
||||||
|
self.report_warning('Unknown MIME type %s in DASH manifest' % mime_type)
|
||||||
|
self._sort_formats(formats)
|
||||||
|
return formats
|
||||||
|
|
||||||
def _live_title(self, name):
|
def _live_title(self, name):
|
||||||
""" Generate the title for a live video """
|
""" Generate the title for a live video """
|
||||||
now = datetime.datetime.now()
|
now = datetime.datetime.now()
|
||||||
|
@@ -329,8 +329,10 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
|||||||
streamdata_req, video_id,
|
streamdata_req, video_id,
|
||||||
note='Downloading media info for %s' % video_format)
|
note='Downloading media info for %s' % video_format)
|
||||||
stream_info = streamdata.find('./{default}preload/stream_info')
|
stream_info = streamdata.find('./{default}preload/stream_info')
|
||||||
video_url = stream_info.find('./host').text
|
video_url = xpath_text(stream_info, './host')
|
||||||
video_play_path = stream_info.find('./file').text
|
video_play_path = xpath_text(stream_info, './file')
|
||||||
|
if not video_url or not video_play_path:
|
||||||
|
continue
|
||||||
metadata = stream_info.find('./metadata')
|
metadata = stream_info.find('./metadata')
|
||||||
format_info = {
|
format_info = {
|
||||||
'format': video_format,
|
'format': video_format,
|
||||||
|
@@ -68,11 +68,16 @@ class CSpanIE(InfoExtractor):
|
|||||||
video_type, video_id = matches.groups()
|
video_type, video_id = matches.groups()
|
||||||
video_type = 'clip' if video_type == 'id' else 'program'
|
video_type = 'clip' if video_type == 'id' else 'program'
|
||||||
else:
|
else:
|
||||||
senate_isvp_url = SenateISVPIE._search_iframe_url(webpage)
|
m = re.search(r'data-(?P<type>clip|prog)id=["\'](?P<id>\d+)', webpage)
|
||||||
if senate_isvp_url:
|
if m:
|
||||||
title = self._og_search_title(webpage)
|
video_id = m.group('id')
|
||||||
surl = smuggle_url(senate_isvp_url, {'force_title': title})
|
video_type = 'program' if m.group('type') == 'prog' else 'clip'
|
||||||
return self.url_result(surl, 'SenateISVP', video_id, title)
|
else:
|
||||||
|
senate_isvp_url = SenateISVPIE._search_iframe_url(webpage)
|
||||||
|
if senate_isvp_url:
|
||||||
|
title = self._og_search_title(webpage)
|
||||||
|
surl = smuggle_url(senate_isvp_url, {'force_title': title})
|
||||||
|
return self.url_result(surl, 'SenateISVP', video_id, title)
|
||||||
if video_type is None or video_id is None:
|
if video_type is None or video_id is None:
|
||||||
raise ExtractorError('unable to find video id and type')
|
raise ExtractorError('unable to find video id and type')
|
||||||
|
|
||||||
@@ -107,6 +112,13 @@ class CSpanIE(InfoExtractor):
|
|||||||
'height': int_or_none(get_text_attr(quality, 'height')),
|
'height': int_or_none(get_text_attr(quality, 'height')),
|
||||||
'tbr': int_or_none(get_text_attr(quality, 'bitrate')),
|
'tbr': int_or_none(get_text_attr(quality, 'bitrate')),
|
||||||
})
|
})
|
||||||
|
if not formats:
|
||||||
|
path = unescapeHTML(get_text_attr(f, 'path'))
|
||||||
|
if not path:
|
||||||
|
continue
|
||||||
|
formats = self._extract_m3u8_formats(
|
||||||
|
path, video_id, 'mp4', entry_protocol='m3u8_native',
|
||||||
|
m3u8_id='hls') if determine_ext(path) == 'm3u8' else [{'url': path, }]
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
entries.append({
|
entries.append({
|
||||||
'id': '%s_%d' % (video_id, partnum + 1),
|
'id': '%s_%d' % (video_id, partnum + 1),
|
||||||
|
63
youtube_dl/extractor/cultureunplugged.py
Normal file
63
youtube_dl/extractor/cultureunplugged.py
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import int_or_none
|
||||||
|
|
||||||
|
|
||||||
|
class CultureUnpluggedIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?cultureunplugged\.com/documentary/watch-online/play/(?P<id>\d+)(?:/(?P<display_id>[^/]+))?'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://www.cultureunplugged.com/documentary/watch-online/play/53662/The-Next--Best-West',
|
||||||
|
'md5': 'ac6c093b089f7d05e79934dcb3d228fc',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '53662',
|
||||||
|
'display_id': 'The-Next--Best-West',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'The Next, Best West',
|
||||||
|
'description': 'md5:0423cd00833dea1519cf014e9d0903b1',
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
'creator': 'Coldstream Creative',
|
||||||
|
'duration': 2203,
|
||||||
|
'view_count': int,
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.cultureunplugged.com/documentary/watch-online/play/53662',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
display_id = mobj.group('display_id') or video_id
|
||||||
|
|
||||||
|
movie_data = self._download_json(
|
||||||
|
'http://www.cultureunplugged.com/movie-data/cu-%s.json' % video_id, display_id)
|
||||||
|
|
||||||
|
video_url = movie_data['url']
|
||||||
|
title = movie_data['title']
|
||||||
|
|
||||||
|
description = movie_data.get('synopsis')
|
||||||
|
creator = movie_data.get('producer')
|
||||||
|
duration = int_or_none(movie_data.get('duration'))
|
||||||
|
view_count = int_or_none(movie_data.get('views'))
|
||||||
|
|
||||||
|
thumbnails = [{
|
||||||
|
'url': movie_data['%s_thumb' % size],
|
||||||
|
'id': size,
|
||||||
|
'preference': preference,
|
||||||
|
} for preference, size in enumerate((
|
||||||
|
'small', 'large')) if movie_data.get('%s_thumb' % size)]
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'display_id': display_id,
|
||||||
|
'url': video_url,
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'creator': creator,
|
||||||
|
'duration': duration,
|
||||||
|
'view_count': view_count,
|
||||||
|
'thumbnails': thumbnails,
|
||||||
|
}
|
88
youtube_dl/extractor/cwtv.py
Normal file
88
youtube_dl/extractor/cwtv.py
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
int_or_none,
|
||||||
|
parse_iso8601,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CWTVIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?cw(?:tv|seed)\.com/shows/(?:[^/]+/){2}\?play=(?P<id>[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12})'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://cwtv.com/shows/arrow/legends-of-yesterday/?play=6b15e985-9345-4f60-baf8-56e96be57c63',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '6b15e985-9345-4f60-baf8-56e96be57c63',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Legends of Yesterday',
|
||||||
|
'description': 'Oliver and Barry Allen take Kendra Saunders and Carter Hall to a remote location to keep them hidden from Vandal Savage while they figure out how to defeat him.',
|
||||||
|
'duration': 2665,
|
||||||
|
'series': 'Arrow',
|
||||||
|
'season_number': 4,
|
||||||
|
'season': '4',
|
||||||
|
'episode_number': 8,
|
||||||
|
'upload_date': '20151203',
|
||||||
|
'timestamp': 1449122100,
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
# m3u8 download
|
||||||
|
'skip_download': True,
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.cwseed.com/shows/whose-line-is-it-anyway/jeff-davis-4/?play=24282b12-ead2-42f2-95ad-26770c2c6088',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '24282b12-ead2-42f2-95ad-26770c2c6088',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Jeff Davis 4',
|
||||||
|
'description': 'Jeff Davis is back to make you laugh.',
|
||||||
|
'duration': 1263,
|
||||||
|
'series': 'Whose Line Is It Anyway?',
|
||||||
|
'season_number': 11,
|
||||||
|
'season': '11',
|
||||||
|
'episode_number': 20,
|
||||||
|
'upload_date': '20151006',
|
||||||
|
'timestamp': 1444107300,
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
# m3u8 download
|
||||||
|
'skip_download': True,
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
video_id = self._match_id(url)
|
||||||
|
video_data = self._download_json(
|
||||||
|
'http://metaframe.digitalsmiths.tv/v2/CWtv/assets/%s/partner/132?format=json' % video_id, video_id)
|
||||||
|
|
||||||
|
formats = self._extract_m3u8_formats(
|
||||||
|
video_data['videos']['variantplaylist']['uri'], video_id, 'mp4')
|
||||||
|
|
||||||
|
thumbnails = [{
|
||||||
|
'url': image['uri'],
|
||||||
|
'width': image.get('width'),
|
||||||
|
'height': image.get('height'),
|
||||||
|
} for image_id, image in video_data['images'].items() if image.get('uri')] if video_data.get('images') else None
|
||||||
|
|
||||||
|
video_metadata = video_data['assetFields']
|
||||||
|
|
||||||
|
subtitles = {
|
||||||
|
'en': [{
|
||||||
|
'url': video_metadata['UnicornCcUrl'],
|
||||||
|
}],
|
||||||
|
} if video_metadata.get('UnicornCcUrl') else None
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': video_metadata['title'],
|
||||||
|
'description': video_metadata.get('description'),
|
||||||
|
'duration': int_or_none(video_metadata.get('duration')),
|
||||||
|
'series': video_metadata.get('seriesName'),
|
||||||
|
'season_number': int_or_none(video_metadata.get('seasonNumber')),
|
||||||
|
'season': video_metadata.get('seasonName'),
|
||||||
|
'episode_number': int_or_none(video_metadata.get('episodeNumber')),
|
||||||
|
'timestamp': parse_iso8601(video_data.get('startTime')),
|
||||||
|
'thumbnails': thumbnails,
|
||||||
|
'formats': formats,
|
||||||
|
'subtitles': subtitles,
|
||||||
|
}
|
@@ -37,7 +37,7 @@ class DailymotionBaseInfoExtractor(InfoExtractor):
|
|||||||
|
|
||||||
|
|
||||||
class DailymotionIE(DailymotionBaseInfoExtractor):
|
class DailymotionIE(DailymotionBaseInfoExtractor):
|
||||||
_VALID_URL = r'(?i)(?:https?://)?(?:(www|touch)\.)?dailymotion\.[a-z]{2,3}/(?:(embed|#)/)?video/(?P<id>[^/?_]+)'
|
_VALID_URL = r'(?i)(?:https?://)?(?:(www|touch)\.)?dailymotion\.[a-z]{2,3}/(?:(?:embed|swf|#)/)?video/(?P<id>[^/?_]+)'
|
||||||
IE_NAME = 'dailymotion'
|
IE_NAME = 'dailymotion'
|
||||||
|
|
||||||
_FORMATS = [
|
_FORMATS = [
|
||||||
@@ -104,6 +104,10 @@ class DailymotionIE(DailymotionBaseInfoExtractor):
|
|||||||
{
|
{
|
||||||
'url': 'http://www.dailymotion.com/video/x20su5f_the-power-of-nightmares-1-the-rise-of-the-politics-of-fear-bbc-2004_news',
|
'url': 'http://www.dailymotion.com/video/x20su5f_the-power-of-nightmares-1-the-rise-of-the-politics-of-fear-bbc-2004_news',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://www.dailymotion.com/swf/video/x3n92nf',
|
||||||
|
'only_matching': True,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -149,14 +153,15 @@ class DailymotionIE(DailymotionBaseInfoExtractor):
|
|||||||
ext = determine_ext(media_url)
|
ext = determine_ext(media_url)
|
||||||
if type_ == 'application/x-mpegURL' or ext == 'm3u8':
|
if type_ == 'application/x-mpegURL' or ext == 'm3u8':
|
||||||
formats.extend(self._extract_m3u8_formats(
|
formats.extend(self._extract_m3u8_formats(
|
||||||
media_url, video_id, 'mp4', m3u8_id='hls', fatal=False))
|
media_url, video_id, 'mp4', preference=-1,
|
||||||
|
m3u8_id='hls', fatal=False))
|
||||||
elif type_ == 'application/f4m' or ext == 'f4m':
|
elif type_ == 'application/f4m' or ext == 'f4m':
|
||||||
formats.extend(self._extract_f4m_formats(
|
formats.extend(self._extract_f4m_formats(
|
||||||
media_url, video_id, preference=-1, f4m_id='hds', fatal=False))
|
media_url, video_id, preference=-1, f4m_id='hds', fatal=False))
|
||||||
else:
|
else:
|
||||||
f = {
|
f = {
|
||||||
'url': media_url,
|
'url': media_url,
|
||||||
'format_id': quality,
|
'format_id': 'http-%s' % quality,
|
||||||
}
|
}
|
||||||
m = re.search(r'H264-(?P<width>\d+)x(?P<height>\d+)', media_url)
|
m = re.search(r'H264-(?P<width>\d+)x(?P<height>\d+)', media_url)
|
||||||
if m:
|
if m:
|
||||||
@@ -335,7 +340,7 @@ class DailymotionPlaylistIE(DailymotionBaseInfoExtractor):
|
|||||||
|
|
||||||
class DailymotionUserIE(DailymotionPlaylistIE):
|
class DailymotionUserIE(DailymotionPlaylistIE):
|
||||||
IE_NAME = 'dailymotion:user'
|
IE_NAME = 'dailymotion:user'
|
||||||
_VALID_URL = r'https?://(?:www\.)?dailymotion\.[a-z]{2,3}/(?!(?:embed|#|video|playlist)/)(?:(?:old/)?user/)?(?P<user>[^/]+)'
|
_VALID_URL = r'https?://(?:www\.)?dailymotion\.[a-z]{2,3}/(?!(?:embed|swf|#|video|playlist)/)(?:(?:old/)?user/)?(?P<user>[^/]+)'
|
||||||
_PAGE_TEMPLATE = 'http://www.dailymotion.com/user/%s/%s'
|
_PAGE_TEMPLATE = 'http://www.dailymotion.com/user/%s/%s'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'https://www.dailymotion.com/user/nqtv',
|
'url': 'https://www.dailymotion.com/user/nqtv',
|
||||||
|
@@ -2,17 +2,26 @@
|
|||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
import itertools
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import compat_urllib_parse
|
from ..compat import (
|
||||||
|
compat_parse_qs,
|
||||||
|
compat_urllib_parse,
|
||||||
|
compat_urllib_parse_unquote,
|
||||||
|
compat_urlparse,
|
||||||
|
)
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
int_or_none,
|
int_or_none,
|
||||||
str_to_int,
|
str_to_int,
|
||||||
xpath_text,
|
xpath_text,
|
||||||
|
unescapeHTML,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class DaumIE(InfoExtractor):
|
class DaumIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:m\.)?tvpot\.daum\.net/v/(?P<id>[^?#&]+)'
|
_VALID_URL = r'https?://(?:(?:m\.)?tvpot\.daum\.net/v/|videofarm\.daum\.net/controller/player/VodPlayer\.swf\?vid=)(?P<id>[^?#&]+)'
|
||||||
IE_NAME = 'daum.net'
|
IE_NAME = 'daum.net'
|
||||||
|
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
@@ -23,25 +32,57 @@ class DaumIE(InfoExtractor):
|
|||||||
'title': '마크 헌트 vs 안토니오 실바',
|
'title': '마크 헌트 vs 안토니오 실바',
|
||||||
'description': 'Mark Hunt vs Antonio Silva',
|
'description': 'Mark Hunt vs Antonio Silva',
|
||||||
'upload_date': '20131217',
|
'upload_date': '20131217',
|
||||||
|
'thumbnail': 're:^https?://.*\.(?:jpg|png)',
|
||||||
'duration': 2117,
|
'duration': 2117,
|
||||||
'view_count': int,
|
'view_count': int,
|
||||||
'comment_count': int,
|
'comment_count': int,
|
||||||
},
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'http://m.tvpot.daum.net/v/65139429',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '65139429',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': '1297회, \'아빠 아들로 태어나길 잘 했어\' 민수, 감동의 눈물[아빠 어디가] 20150118',
|
||||||
|
'description': 'md5:79794514261164ff27e36a21ad229fc5',
|
||||||
|
'upload_date': '20150604',
|
||||||
|
'thumbnail': 're:^https?://.*\.(?:jpg|png)',
|
||||||
|
'duration': 154,
|
||||||
|
'view_count': int,
|
||||||
|
'comment_count': int,
|
||||||
|
},
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://tvpot.daum.net/v/07dXWRka62Y%24',
|
'url': 'http://tvpot.daum.net/v/07dXWRka62Y%24',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://videofarm.daum.net/controller/player/VodPlayer.swf?vid=vwIpVpCQsT8%24&ref=',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'vwIpVpCQsT8$',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': '01-Korean War ( Trouble on the horizon )',
|
||||||
|
'description': '\nKorean War 01\nTrouble on the horizon\n전쟁의 먹구름',
|
||||||
|
'upload_date': '20080223',
|
||||||
|
'thumbnail': 're:^https?://.*\.(?:jpg|png)',
|
||||||
|
'duration': 249,
|
||||||
|
'view_count': int,
|
||||||
|
'comment_count': int,
|
||||||
|
},
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
video_id = compat_urllib_parse_unquote(self._match_id(url))
|
||||||
query = compat_urllib_parse.urlencode({'vid': video_id})
|
query = compat_urllib_parse.urlencode({'vid': video_id})
|
||||||
info = self._download_xml(
|
|
||||||
'http://tvpot.daum.net/clip/ClipInfoXml.do?' + query, video_id,
|
|
||||||
'Downloading video info')
|
|
||||||
movie_data = self._download_json(
|
movie_data = self._download_json(
|
||||||
'http://videofarm.daum.net/controller/api/closed/v1_2/IntegratedMovieData.json?' + query,
|
'http://videofarm.daum.net/controller/api/closed/v1_2/IntegratedMovieData.json?' + query,
|
||||||
video_id, 'Downloading video formats info')
|
video_id, 'Downloading video formats info')
|
||||||
|
|
||||||
|
# For urls like http://m.tvpot.daum.net/v/65139429, where the video_id is really a clipid
|
||||||
|
if not movie_data.get('output_list', {}).get('output_list') and re.match(r'^\d+$', video_id):
|
||||||
|
return self.url_result('http://tvpot.daum.net/clip/ClipView.do?clipid=%s' % video_id)
|
||||||
|
|
||||||
|
info = self._download_xml(
|
||||||
|
'http://tvpot.daum.net/clip/ClipInfoXml.do?' + query, video_id,
|
||||||
|
'Downloading video info')
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
for format_el in movie_data['output_list']['output_list']:
|
for format_el in movie_data['output_list']['output_list']:
|
||||||
profile = format_el['profile']
|
profile = format_el['profile']
|
||||||
@@ -76,8 +117,9 @@ class DaumIE(InfoExtractor):
|
|||||||
|
|
||||||
|
|
||||||
class DaumClipIE(InfoExtractor):
|
class DaumClipIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:m\.)?tvpot\.daum\.net/(?:clip/ClipView.do|mypot/View.do)\?.*?clipid=(?P<id>\d+)'
|
_VALID_URL = r'https?://(?:m\.)?tvpot\.daum\.net/(?:clip/ClipView.(?:do|tv)|mypot/View.do)\?.*?clipid=(?P<id>\d+)'
|
||||||
IE_NAME = 'daum.net:clip'
|
IE_NAME = 'daum.net:clip'
|
||||||
|
_URL_TEMPLATE = 'http://tvpot.daum.net/clip/ClipView.do?clipid=%s'
|
||||||
|
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://tvpot.daum.net/clip/ClipView.do?clipid=52554690',
|
'url': 'http://tvpot.daum.net/clip/ClipView.do?clipid=52554690',
|
||||||
@@ -87,11 +129,19 @@ class DaumClipIE(InfoExtractor):
|
|||||||
'title': 'DOTA 2GETHER 시즌2 6회 - 2부',
|
'title': 'DOTA 2GETHER 시즌2 6회 - 2부',
|
||||||
'description': 'DOTA 2GETHER 시즌2 6회 - 2부',
|
'description': 'DOTA 2GETHER 시즌2 6회 - 2부',
|
||||||
'upload_date': '20130831',
|
'upload_date': '20130831',
|
||||||
|
'thumbnail': 're:^https?://.*\.(?:jpg|png)',
|
||||||
'duration': 3868,
|
'duration': 3868,
|
||||||
'view_count': int,
|
'view_count': int,
|
||||||
},
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'http://m.tvpot.daum.net/clip/ClipView.tv?clipid=54999425',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def suitable(cls, url):
|
||||||
|
return False if DaumPlaylistIE.suitable(url) or DaumUserIE.suitable(url) else super(DaumClipIE, cls).suitable(url)
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
clip_info = self._download_json(
|
clip_info = self._download_json(
|
||||||
@@ -102,7 +152,7 @@ class DaumClipIE(InfoExtractor):
|
|||||||
'_type': 'url_transparent',
|
'_type': 'url_transparent',
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'url': 'http://tvpot.daum.net/v/%s' % clip_info['vid'],
|
'url': 'http://tvpot.daum.net/v/%s' % clip_info['vid'],
|
||||||
'title': clip_info['title'],
|
'title': unescapeHTML(clip_info['title']),
|
||||||
'thumbnail': clip_info.get('thumb_url'),
|
'thumbnail': clip_info.get('thumb_url'),
|
||||||
'description': clip_info.get('contents'),
|
'description': clip_info.get('contents'),
|
||||||
'duration': int_or_none(clip_info.get('duration')),
|
'duration': int_or_none(clip_info.get('duration')),
|
||||||
@@ -110,3 +160,139 @@ class DaumClipIE(InfoExtractor):
|
|||||||
'view_count': int_or_none(clip_info.get('play_count')),
|
'view_count': int_or_none(clip_info.get('play_count')),
|
||||||
'ie_key': 'Daum',
|
'ie_key': 'Daum',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class DaumListIE(InfoExtractor):
|
||||||
|
def _get_entries(self, list_id, list_id_type):
|
||||||
|
name = None
|
||||||
|
entries = []
|
||||||
|
for pagenum in itertools.count(1):
|
||||||
|
list_info = self._download_json(
|
||||||
|
'http://tvpot.daum.net/mypot/json/GetClipInfo.do?size=48&init=true&order=date&page=%d&%s=%s' % (
|
||||||
|
pagenum, list_id_type, list_id), list_id, 'Downloading list info - %s' % pagenum)
|
||||||
|
|
||||||
|
entries.extend([
|
||||||
|
self.url_result(
|
||||||
|
'http://tvpot.daum.net/v/%s' % clip['vid'])
|
||||||
|
for clip in list_info['clip_list']
|
||||||
|
])
|
||||||
|
|
||||||
|
if not name:
|
||||||
|
name = list_info.get('playlist_bean', {}).get('name') or \
|
||||||
|
list_info.get('potInfo', {}).get('name')
|
||||||
|
|
||||||
|
if not list_info.get('has_more'):
|
||||||
|
break
|
||||||
|
|
||||||
|
return name, entries
|
||||||
|
|
||||||
|
def _check_clip(self, url, list_id):
|
||||||
|
query_dict = compat_parse_qs(compat_urlparse.urlparse(url).query)
|
||||||
|
if 'clipid' in query_dict:
|
||||||
|
clip_id = query_dict['clipid'][0]
|
||||||
|
if self._downloader.params.get('noplaylist'):
|
||||||
|
self.to_screen('Downloading just video %s because of --no-playlist' % clip_id)
|
||||||
|
return self.url_result(DaumClipIE._URL_TEMPLATE % clip_id, 'DaumClip')
|
||||||
|
else:
|
||||||
|
self.to_screen('Downloading playlist %s - add --no-playlist to just download video' % list_id)
|
||||||
|
|
||||||
|
|
||||||
|
class DaumPlaylistIE(DaumListIE):
|
||||||
|
_VALID_URL = r'https?://(?:m\.)?tvpot\.daum\.net/mypot/(?:View\.do|Top\.tv)\?.*?playlistid=(?P<id>[0-9]+)'
|
||||||
|
IE_NAME = 'daum.net:playlist'
|
||||||
|
_URL_TEMPLATE = 'http://tvpot.daum.net/mypot/View.do?playlistid=%s'
|
||||||
|
|
||||||
|
_TESTS = [{
|
||||||
|
'note': 'Playlist url with clipid',
|
||||||
|
'url': 'http://tvpot.daum.net/mypot/View.do?playlistid=6213966&clipid=73806844',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '6213966',
|
||||||
|
'title': 'Woorissica Official',
|
||||||
|
},
|
||||||
|
'playlist_mincount': 181
|
||||||
|
}, {
|
||||||
|
'note': 'Playlist url with clipid - noplaylist',
|
||||||
|
'url': 'http://tvpot.daum.net/mypot/View.do?playlistid=6213966&clipid=73806844',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '73806844',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': '151017 Airport',
|
||||||
|
'upload_date': '20160117',
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
'noplaylist': True,
|
||||||
|
'skip_download': True,
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def suitable(cls, url):
|
||||||
|
return False if DaumUserIE.suitable(url) else super(DaumPlaylistIE, cls).suitable(url)
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
list_id = self._match_id(url)
|
||||||
|
|
||||||
|
clip_result = self._check_clip(url, list_id)
|
||||||
|
if clip_result:
|
||||||
|
return clip_result
|
||||||
|
|
||||||
|
name, entries = self._get_entries(list_id, 'playlistid')
|
||||||
|
|
||||||
|
return self.playlist_result(entries, list_id, name)
|
||||||
|
|
||||||
|
|
||||||
|
class DaumUserIE(DaumListIE):
|
||||||
|
_VALID_URL = r'https?://(?:m\.)?tvpot\.daum\.net/mypot/(?:View|Top)\.(?:do|tv)\?.*?ownerid=(?P<id>[0-9a-zA-Z]+)'
|
||||||
|
IE_NAME = 'daum.net:user'
|
||||||
|
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://tvpot.daum.net/mypot/View.do?ownerid=o2scDLIVbHc0',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'o2scDLIVbHc0',
|
||||||
|
'title': '마이 리틀 텔레비전',
|
||||||
|
},
|
||||||
|
'playlist_mincount': 213
|
||||||
|
}, {
|
||||||
|
'url': 'http://tvpot.daum.net/mypot/View.do?ownerid=o2scDLIVbHc0&clipid=73801156',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '73801156',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': '[미공개] 김구라, 오만석이 부릅니다 \'오케피\' - 마이 리틀 텔레비전 20160116',
|
||||||
|
'upload_date': '20160117',
|
||||||
|
'description': 'md5:5e91d2d6747f53575badd24bd62b9f36'
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
'noplaylist': True,
|
||||||
|
'skip_download': True,
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
'note': 'Playlist url has ownerid and playlistid, playlistid takes precedence',
|
||||||
|
'url': 'http://tvpot.daum.net/mypot/View.do?ownerid=o2scDLIVbHc0&playlistid=6196631',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '6196631',
|
||||||
|
'title': '마이 리틀 텔레비전 - 20160109',
|
||||||
|
},
|
||||||
|
'playlist_count': 11
|
||||||
|
}, {
|
||||||
|
'url': 'http://tvpot.daum.net/mypot/Top.do?ownerid=o2scDLIVbHc0',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://m.tvpot.daum.net/mypot/Top.tv?ownerid=45x1okb1If50&playlistid=3569733',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
list_id = self._match_id(url)
|
||||||
|
|
||||||
|
clip_result = self._check_clip(url, list_id)
|
||||||
|
if clip_result:
|
||||||
|
return clip_result
|
||||||
|
|
||||||
|
query_dict = compat_parse_qs(compat_urlparse.urlparse(url).query)
|
||||||
|
if 'playlistid' in query_dict:
|
||||||
|
playlist_id = query_dict['playlistid'][0]
|
||||||
|
return self.url_result(DaumPlaylistIE._URL_TEMPLATE % playlist_id, 'DaumPlaylist')
|
||||||
|
|
||||||
|
name, entries = self._get_entries(list_id, 'ownerid')
|
||||||
|
|
||||||
|
return self.playlist_result(entries, list_id, name)
|
||||||
|
@@ -5,7 +5,10 @@ import re
|
|||||||
import base64
|
import base64
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import compat_urllib_parse
|
from ..compat import (
|
||||||
|
compat_urllib_parse,
|
||||||
|
compat_str,
|
||||||
|
)
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
int_or_none,
|
int_or_none,
|
||||||
parse_iso8601,
|
parse_iso8601,
|
||||||
@@ -186,7 +189,8 @@ class DCNSeasonIE(InfoExtractor):
|
|||||||
|
|
||||||
entries = []
|
entries = []
|
||||||
for video in show['videos']:
|
for video in show['videos']:
|
||||||
|
video_id = compat_str(video['id'])
|
||||||
entries.append(self.url_result(
|
entries.append(self.url_result(
|
||||||
'http://www.dcndigital.ae/media/%s' % video['id'], 'DCNVideo'))
|
'http://www.dcndigital.ae/media/%s' % video_id, 'DCNVideo', video_id))
|
||||||
|
|
||||||
return self.playlist_result(entries, season_id, title)
|
return self.playlist_result(entries, season_id, title)
|
||||||
|
@@ -7,9 +7,9 @@ from .common import InfoExtractor
|
|||||||
from ..utils import int_or_none
|
from ..utils import int_or_none
|
||||||
|
|
||||||
|
|
||||||
class UltimediaIE(InfoExtractor):
|
class DigitekaIE(InfoExtractor):
|
||||||
_VALID_URL = r'''(?x)
|
_VALID_URL = r'''(?x)
|
||||||
https?://(?:www\.)?ultimedia\.com/
|
https?://(?:www\.)?(?:digiteka\.net|ultimedia\.com)/
|
||||||
(?:
|
(?:
|
||||||
deliver/
|
deliver/
|
||||||
(?P<embed_type>
|
(?P<embed_type>
|
||||||
@@ -56,6 +56,9 @@ class UltimediaIE(InfoExtractor):
|
|||||||
'timestamp': 1424760500,
|
'timestamp': 1424760500,
|
||||||
'uploader_id': '3rfzk',
|
'uploader_id': '3rfzk',
|
||||||
},
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.digiteka.net/deliver/generic/iframe/mdtk/01637594/src/lqm3kl/zone/1/showtitle/1/autoplay/yes',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
@@ -9,7 +9,17 @@ from ..compat import compat_str
|
|||||||
|
|
||||||
|
|
||||||
class DiscoveryIE(InfoExtractor):
|
class DiscoveryIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://www\.discovery\.com\/[a-zA-Z0-9\-]*/[a-zA-Z0-9\-]*/videos/(?P<id>[a-zA-Z0-9_\-]*)(?:\.htm)?'
|
_VALID_URL = r'''(?x)http://(?:www\.)?(?:
|
||||||
|
discovery|
|
||||||
|
investigationdiscovery|
|
||||||
|
discoverylife|
|
||||||
|
animalplanet|
|
||||||
|
ahctv|
|
||||||
|
destinationamerica|
|
||||||
|
sciencechannel|
|
||||||
|
tlc|
|
||||||
|
velocity
|
||||||
|
)\.com/(?:[^/]+/)*(?P<id>[^./?#]+)'''
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://www.discovery.com/tv-shows/mythbusters/videos/mission-impossible-outtakes.htm',
|
'url': 'http://www.discovery.com/tv-shows/mythbusters/videos/mission-impossible-outtakes.htm',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
@@ -21,8 +31,8 @@ class DiscoveryIE(InfoExtractor):
|
|||||||
'don\'t miss Adam moon-walking as Jamie ... behind Jamie\'s'
|
'don\'t miss Adam moon-walking as Jamie ... behind Jamie\'s'
|
||||||
' back.'),
|
' back.'),
|
||||||
'duration': 156,
|
'duration': 156,
|
||||||
'timestamp': 1303099200,
|
'timestamp': 1302032462,
|
||||||
'upload_date': '20110418',
|
'upload_date': '20110405',
|
||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
'skip_download': True, # requires ffmpeg
|
'skip_download': True, # requires ffmpeg
|
||||||
@@ -33,27 +43,38 @@ class DiscoveryIE(InfoExtractor):
|
|||||||
'id': 'mythbusters-the-simpsons',
|
'id': 'mythbusters-the-simpsons',
|
||||||
'title': 'MythBusters: The Simpsons',
|
'title': 'MythBusters: The Simpsons',
|
||||||
},
|
},
|
||||||
'playlist_count': 9,
|
'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,
|
||||||
|
},
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
display_id = self._match_id(url)
|
||||||
info = self._download_json(url + '?flat=1', video_id)
|
info = self._download_json(url + '?flat=1', display_id)
|
||||||
|
|
||||||
video_title = info.get('playlist_title') or info.get('video_title')
|
video_title = info.get('playlist_title') or info.get('video_title')
|
||||||
|
|
||||||
entries = [{
|
entries = [{
|
||||||
'id': compat_str(video_info['id']),
|
'id': compat_str(video_info['id']),
|
||||||
'formats': self._extract_m3u8_formats(
|
'formats': self._extract_m3u8_formats(
|
||||||
video_info['src'], video_id, ext='mp4',
|
video_info['src'], display_id, 'mp4', 'm3u8_native', m3u8_id='hls',
|
||||||
note='Download m3u8 information for video %d' % (idx + 1)),
|
note='Download m3u8 information for video %d' % (idx + 1)),
|
||||||
'title': video_info['title'],
|
'title': video_info['title'],
|
||||||
'description': video_info.get('description'),
|
'description': video_info.get('description'),
|
||||||
'duration': parse_duration(video_info.get('video_length')),
|
'duration': parse_duration(video_info.get('video_length')),
|
||||||
'webpage_url': video_info.get('href'),
|
'webpage_url': video_info.get('href') or video_info.get('url'),
|
||||||
'thumbnail': video_info.get('thumbnailURL'),
|
'thumbnail': video_info.get('thumbnailURL'),
|
||||||
'alt_title': video_info.get('secondary_title'),
|
'alt_title': video_info.get('secondary_title'),
|
||||||
'timestamp': parse_iso8601(video_info.get('publishedDate')),
|
'timestamp': parse_iso8601(video_info.get('publishedDate')),
|
||||||
} for idx, video_info in enumerate(info['playlist'])]
|
} for idx, video_info in enumerate(info['playlist'])]
|
||||||
|
|
||||||
return self.playlist_result(entries, video_id, video_title)
|
return self.playlist_result(entries, display_id, video_title)
|
||||||
|
@@ -12,6 +12,7 @@ from ..compat import (
|
|||||||
from ..utils import (
|
from ..utils import (
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
clean_html,
|
clean_html,
|
||||||
|
int_or_none,
|
||||||
sanitized_Request,
|
sanitized_Request,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -66,13 +67,15 @@ class DramaFeverBaseIE(AMPIE):
|
|||||||
class DramaFeverIE(DramaFeverBaseIE):
|
class DramaFeverIE(DramaFeverBaseIE):
|
||||||
IE_NAME = 'dramafever'
|
IE_NAME = 'dramafever'
|
||||||
_VALID_URL = r'https?://(?:www\.)?dramafever\.com/drama/(?P<id>[0-9]+/[0-9]+)(?:/|$)'
|
_VALID_URL = r'https?://(?:www\.)?dramafever\.com/drama/(?P<id>[0-9]+/[0-9]+)(?:/|$)'
|
||||||
_TEST = {
|
_TESTS = [{
|
||||||
'url': 'http://www.dramafever.com/drama/4512/1/Cooking_with_Shin/',
|
'url': 'http://www.dramafever.com/drama/4512/1/Cooking_with_Shin/',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '4512.1',
|
'id': '4512.1',
|
||||||
'ext': 'flv',
|
'ext': 'mp4',
|
||||||
'title': 'Cooking with Shin 4512.1',
|
'title': 'Cooking with Shin 4512.1',
|
||||||
'description': 'md5:a8eec7942e1664a6896fcd5e1287bfd0',
|
'description': 'md5:a8eec7942e1664a6896fcd5e1287bfd0',
|
||||||
|
'episode': 'Episode 1',
|
||||||
|
'episode_number': 1,
|
||||||
'thumbnail': 're:^https?://.*\.jpg',
|
'thumbnail': 're:^https?://.*\.jpg',
|
||||||
'timestamp': 1404336058,
|
'timestamp': 1404336058,
|
||||||
'upload_date': '20140702',
|
'upload_date': '20140702',
|
||||||
@@ -82,7 +85,25 @@ class DramaFeverIE(DramaFeverBaseIE):
|
|||||||
# m3u8 download
|
# m3u8 download
|
||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
},
|
},
|
||||||
}
|
}, {
|
||||||
|
'url': 'http://www.dramafever.com/drama/4826/4/Mnet_Asian_Music_Awards_2015/?ap=1',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '4826.4',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Mnet Asian Music Awards 2015 4826.4',
|
||||||
|
'description': 'md5:3ff2ee8fedaef86e076791c909cf2e91',
|
||||||
|
'episode': 'Mnet Asian Music Awards 2015 - Part 3',
|
||||||
|
'episode_number': 4,
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg',
|
||||||
|
'timestamp': 1450213200,
|
||||||
|
'upload_date': '20151215',
|
||||||
|
'duration': 5602,
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
# m3u8 download
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url).replace('/', '.')
|
video_id = self._match_id(url).replace('/', '.')
|
||||||
@@ -105,13 +126,22 @@ class DramaFeverIE(DramaFeverBaseIE):
|
|||||||
video_id, 'Downloading episode info JSON', fatal=False)
|
video_id, 'Downloading episode info JSON', fatal=False)
|
||||||
if episode_info:
|
if episode_info:
|
||||||
value = episode_info.get('value')
|
value = episode_info.get('value')
|
||||||
if value:
|
if isinstance(value, list):
|
||||||
subfile = value[0].get('subfile') or value[0].get('new_subfile')
|
for v in value:
|
||||||
if subfile and subfile != 'http://www.dramafever.com/st/':
|
if v.get('type') == 'Episode':
|
||||||
info.setdefault('subtitles', {}).setdefault('English', []).append({
|
subfile = v.get('subfile') or v.get('new_subfile')
|
||||||
'ext': 'srt',
|
if subfile and subfile != 'http://www.dramafever.com/st/':
|
||||||
'url': subfile,
|
info.setdefault('subtitles', {}).setdefault('English', []).append({
|
||||||
})
|
'ext': 'srt',
|
||||||
|
'url': subfile,
|
||||||
|
})
|
||||||
|
episode_number = int_or_none(v.get('number'))
|
||||||
|
episode_fallback = 'Episode'
|
||||||
|
if episode_number:
|
||||||
|
episode_fallback += ' %d' % episode_number
|
||||||
|
info['episode'] = v.get('title') or episode_fallback
|
||||||
|
info['episode_number'] = episode_number
|
||||||
|
break
|
||||||
|
|
||||||
return info
|
return info
|
||||||
|
|
||||||
|
@@ -2,14 +2,10 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .zdf import ZDFIE
|
||||||
from ..utils import (
|
|
||||||
ExtractorError,
|
|
||||||
unified_strdate,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class DreiSatIE(InfoExtractor):
|
class DreiSatIE(ZDFIE):
|
||||||
IE_NAME = '3sat'
|
IE_NAME = '3sat'
|
||||||
_VALID_URL = r'(?:http://)?(?:www\.)?3sat\.de/mediathek/(?:index\.php|mediathek\.php)?\?(?:(?:mode|display)=[^&]+&)*obj=(?P<id>[0-9]+)$'
|
_VALID_URL = r'(?:http://)?(?:www\.)?3sat\.de/mediathek/(?:index\.php|mediathek\.php)?\?(?:(?:mode|display)=[^&]+&)*obj=(?P<id>[0-9]+)$'
|
||||||
_TESTS = [
|
_TESTS = [
|
||||||
@@ -35,53 +31,4 @@ class DreiSatIE(InfoExtractor):
|
|||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
video_id = mobj.group('id')
|
video_id = mobj.group('id')
|
||||||
details_url = 'http://www.3sat.de/mediathek/xmlservice/web/beitragsDetails?ak=web&id=%s' % video_id
|
details_url = 'http://www.3sat.de/mediathek/xmlservice/web/beitragsDetails?ak=web&id=%s' % video_id
|
||||||
details_doc = self._download_xml(details_url, video_id, 'Downloading video details')
|
return self.extract_from_xml_url(video_id, details_url)
|
||||||
|
|
||||||
status_code = details_doc.find('./status/statuscode')
|
|
||||||
if status_code is not None and status_code.text != 'ok':
|
|
||||||
code = status_code.text
|
|
||||||
if code == 'notVisibleAnymore':
|
|
||||||
message = 'Video %s is not available' % video_id
|
|
||||||
else:
|
|
||||||
message = '%s returned error: %s' % (self.IE_NAME, code)
|
|
||||||
raise ExtractorError(message, expected=True)
|
|
||||||
|
|
||||||
thumbnail_els = details_doc.findall('.//teaserimage')
|
|
||||||
thumbnails = [{
|
|
||||||
'width': int(te.attrib['key'].partition('x')[0]),
|
|
||||||
'height': int(te.attrib['key'].partition('x')[2]),
|
|
||||||
'url': te.text,
|
|
||||||
} for te in thumbnail_els]
|
|
||||||
|
|
||||||
information_el = details_doc.find('.//information')
|
|
||||||
video_title = information_el.find('./title').text
|
|
||||||
video_description = information_el.find('./detail').text
|
|
||||||
|
|
||||||
details_el = details_doc.find('.//details')
|
|
||||||
video_uploader = details_el.find('./channel').text
|
|
||||||
upload_date = unified_strdate(details_el.find('./airtime').text)
|
|
||||||
|
|
||||||
format_els = details_doc.findall('.//formitaet')
|
|
||||||
formats = [{
|
|
||||||
'format_id': fe.attrib['basetype'],
|
|
||||||
'width': int(fe.find('./width').text),
|
|
||||||
'height': int(fe.find('./height').text),
|
|
||||||
'url': fe.find('./url').text,
|
|
||||||
'filesize': int(fe.find('./filesize').text),
|
|
||||||
'video_bitrate': int(fe.find('./videoBitrate').text),
|
|
||||||
} for fe in format_els
|
|
||||||
if not fe.find('./url').text.startswith('http://www.metafilegenerator.de/')]
|
|
||||||
|
|
||||||
self._sort_formats(formats)
|
|
||||||
|
|
||||||
return {
|
|
||||||
'_type': 'video',
|
|
||||||
'id': video_id,
|
|
||||||
'title': video_title,
|
|
||||||
'formats': formats,
|
|
||||||
'description': video_description,
|
|
||||||
'thumbnails': thumbnails,
|
|
||||||
'thumbnail': thumbnails[-1]['url'],
|
|
||||||
'uploader': video_uploader,
|
|
||||||
'upload_date': upload_date,
|
|
||||||
}
|
|
||||||
|
@@ -91,7 +91,7 @@ class DRTVIE(InfoExtractor):
|
|||||||
subtitles_list = asset.get('SubtitlesList')
|
subtitles_list = asset.get('SubtitlesList')
|
||||||
if isinstance(subtitles_list, list):
|
if isinstance(subtitles_list, list):
|
||||||
LANGS = {
|
LANGS = {
|
||||||
'Danish': 'dk',
|
'Danish': 'da',
|
||||||
}
|
}
|
||||||
for subs in subtitles_list:
|
for subs in subtitles_list:
|
||||||
lang = subs['Language']
|
lang = subs['Language']
|
||||||
|
@@ -1,9 +1,12 @@
|
|||||||
# 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_urlparse
|
||||||
|
from ..utils import (
|
||||||
|
remove_start,
|
||||||
|
sanitized_Request,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class EinthusanIE(InfoExtractor):
|
class EinthusanIE(InfoExtractor):
|
||||||
@@ -34,27 +37,33 @@ class EinthusanIE(InfoExtractor):
|
|||||||
]
|
]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
video_id = self._match_id(url)
|
||||||
video_id = mobj.group('id')
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
|
||||||
|
|
||||||
video_title = self._html_search_regex(
|
request = sanitized_Request(url)
|
||||||
r'<h1><a class="movie-title".*?>(.*?)</a></h1>', webpage, 'title')
|
request.add_header('User-Agent', 'Mozilla/5.0 (Windows NT 5.2; WOW64; rv:43.0) Gecko/20100101 Firefox/43.0')
|
||||||
|
webpage = self._download_webpage(request, video_id)
|
||||||
|
|
||||||
video_url = self._html_search_regex(
|
title = self._html_search_regex(
|
||||||
r'''(?s)jwplayer\("mediaplayer"\)\.setup\({.*?'file': '([^']+)'.*?}\);''',
|
r'<h1><a[^>]+class=["\']movie-title["\'][^>]*>(.+?)</a></h1>',
|
||||||
webpage, 'video url')
|
webpage, 'title')
|
||||||
|
|
||||||
|
video_id = self._search_regex(
|
||||||
|
r'data-movieid=["\'](\d+)', webpage, 'video id', default=video_id)
|
||||||
|
|
||||||
|
video_url = self._download_webpage(
|
||||||
|
'http://cdn.einthusan.com/geturl/%s/hd/London,Washington,Toronto,Dallas,San,Sydney/'
|
||||||
|
% video_id, video_id)
|
||||||
|
|
||||||
description = self._html_search_meta('description', webpage)
|
description = self._html_search_meta('description', webpage)
|
||||||
thumbnail = self._html_search_regex(
|
thumbnail = self._html_search_regex(
|
||||||
r'''<a class="movie-cover-wrapper".*?><img src=["'](.*?)["'].*?/></a>''',
|
r'''<a class="movie-cover-wrapper".*?><img src=["'](.*?)["'].*?/></a>''',
|
||||||
webpage, "thumbnail url", fatal=False)
|
webpage, "thumbnail url", fatal=False)
|
||||||
if thumbnail is not None:
|
if thumbnail is not None:
|
||||||
thumbnail = thumbnail.replace('..', 'http://www.einthusan.com')
|
thumbnail = compat_urlparse.urljoin(url, remove_start(thumbnail, '..'))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'title': video_title,
|
'title': title,
|
||||||
'url': video_url,
|
'url': video_url,
|
||||||
'thumbnail': thumbnail,
|
'thumbnail': thumbnail,
|
||||||
'description': description,
|
'description': description,
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
from ..utils import remove_end
|
||||||
|
|
||||||
|
|
||||||
class ESPNIE(InfoExtractor):
|
class ESPNIE(InfoExtractor):
|
||||||
@@ -10,8 +11,20 @@ class ESPNIE(InfoExtractor):
|
|||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'FkYWtmazr6Ed8xmvILvKLWjd4QvYZpzG',
|
'id': 'FkYWtmazr6Ed8xmvILvKLWjd4QvYZpzG',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'dm_140128_30for30Shorts___JudgingJewellv2',
|
'title': '30 for 30 Shorts: Judging Jewell',
|
||||||
'description': '',
|
'description': None,
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
# m3u8 download
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
# intl video, from http://www.espnfc.us/video/mls-highlights/150/video/2743663/must-see-moments-best-of-the-mls-season
|
||||||
|
'url': 'http://espn.go.com/video/clip?id=2743663',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '50NDFkeTqRHB0nXBOK-RGdSG5YQPuxHg',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Must-See Moments: Best of the MLS season',
|
||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
# m3u8 download
|
# m3u8 download
|
||||||
@@ -40,15 +53,26 @@ class ESPNIE(InfoExtractor):
|
|||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
video_id = self._search_regex(
|
video_id = self._search_regex(
|
||||||
r'class="video-play-button"[^>]+data-id="(\d+)',
|
r'class=(["\']).*?video-play-button.*?\1[^>]+data-id=["\'](?P<id>\d+)',
|
||||||
webpage, 'video id')
|
webpage, 'video id', group='id')
|
||||||
|
|
||||||
|
cms = 'espn'
|
||||||
|
if 'data-source="intl"' in webpage:
|
||||||
|
cms = 'intl'
|
||||||
|
player_url = 'https://espn.go.com/video/iframe/twitter/?id=%s&cms=%s' % (video_id, cms)
|
||||||
player = self._download_webpage(
|
player = self._download_webpage(
|
||||||
'https://espn.go.com/video/iframe/twitter/?id=%s' % video_id, video_id)
|
player_url, video_id)
|
||||||
|
|
||||||
pcode = self._search_regex(
|
pcode = self._search_regex(
|
||||||
r'["\']pcode=([^"\']+)["\']', player, 'pcode')
|
r'["\']pcode=([^"\']+)["\']', player, 'pcode')
|
||||||
|
|
||||||
return self.url_result(
|
title = remove_end(
|
||||||
'ooyalaexternal:espn:%s:%s' % (video_id, pcode),
|
self._og_search_title(webpage),
|
||||||
'OoyalaExternal')
|
'- ESPN Video').strip()
|
||||||
|
|
||||||
|
return {
|
||||||
|
'_type': 'url_transparent',
|
||||||
|
'url': 'ooyalaexternal:%s:%s:%s' % (cms, video_id, pcode),
|
||||||
|
'ie_key': 'OoyalaExternal',
|
||||||
|
'title': title,
|
||||||
|
}
|
||||||
|
@@ -6,9 +6,11 @@ import socket
|
|||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
from ..compat import (
|
||||||
|
compat_etree_fromstring,
|
||||||
compat_http_client,
|
compat_http_client,
|
||||||
compat_urllib_error,
|
compat_urllib_error,
|
||||||
compat_urllib_parse_unquote,
|
compat_urllib_parse_unquote,
|
||||||
|
compat_urllib_parse_unquote_plus,
|
||||||
)
|
)
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
error_to_compat_str,
|
error_to_compat_str,
|
||||||
@@ -23,19 +25,30 @@ from ..utils import (
|
|||||||
|
|
||||||
class FacebookIE(InfoExtractor):
|
class FacebookIE(InfoExtractor):
|
||||||
_VALID_URL = r'''(?x)
|
_VALID_URL = r'''(?x)
|
||||||
https?://(?:\w+\.)?facebook\.com/
|
(?:
|
||||||
(?:[^#]*?\#!/)?
|
https?://
|
||||||
(?:
|
(?:\w+\.)?facebook\.com/
|
||||||
(?:video/video\.php|photo\.php|video\.php|video/embed)\?(?:.*?)
|
(?:[^#]*?\#!/)?
|
||||||
(?:v|video_id)=|
|
(?:
|
||||||
[^/]+/videos/(?:[^/]+/)?
|
(?:
|
||||||
)
|
video/video\.php|
|
||||||
(?P<id>[0-9]+)
|
photo\.php|
|
||||||
(?:.*)'''
|
video\.php|
|
||||||
|
video/embed
|
||||||
|
)\?(?:.*?)(?:v|video_id)=|
|
||||||
|
[^/]+/videos/(?:[^/]+/)?
|
||||||
|
)|
|
||||||
|
facebook:
|
||||||
|
)
|
||||||
|
(?P<id>[0-9]+)
|
||||||
|
'''
|
||||||
_LOGIN_URL = 'https://www.facebook.com/login.php?next=http%3A%2F%2Ffacebook.com%2Fhome.php&login_attempt=1'
|
_LOGIN_URL = 'https://www.facebook.com/login.php?next=http%3A%2F%2Ffacebook.com%2Fhome.php&login_attempt=1'
|
||||||
_CHECKPOINT_URL = 'https://www.facebook.com/checkpoint/?next=http%3A%2F%2Ffacebook.com%2Fhome.php&_fb_noscript=1'
|
_CHECKPOINT_URL = 'https://www.facebook.com/checkpoint/?next=http%3A%2F%2Ffacebook.com%2Fhome.php&_fb_noscript=1'
|
||||||
_NETRC_MACHINE = 'facebook'
|
_NETRC_MACHINE = 'facebook'
|
||||||
IE_NAME = 'facebook'
|
IE_NAME = 'facebook'
|
||||||
|
|
||||||
|
_CHROME_USER_AGENT = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.97 Safari/537.36'
|
||||||
|
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'https://www.facebook.com/video.php?v=637842556329505&fref=nf',
|
'url': 'https://www.facebook.com/video.php?v=637842556329505&fref=nf',
|
||||||
'md5': '6a40d33c0eccbb1af76cf0485a052659',
|
'md5': '6a40d33c0eccbb1af76cf0485a052659',
|
||||||
@@ -57,6 +70,16 @@ class FacebookIE(InfoExtractor):
|
|||||||
'expected_warnings': [
|
'expected_warnings': [
|
||||||
'title'
|
'title'
|
||||||
]
|
]
|
||||||
|
}, {
|
||||||
|
'note': 'Video with DASH manifest',
|
||||||
|
'url': 'https://www.facebook.com/video.php?v=957955867617029',
|
||||||
|
'md5': '54706e4db4f5ad58fbad82dde1f1213f',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '957955867617029',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'When you post epic content on instagram.com/433 8 million followers, this is ...',
|
||||||
|
'uploader': 'Demy de Zeeuw',
|
||||||
|
},
|
||||||
}, {
|
}, {
|
||||||
'url': 'https://www.facebook.com/video.php?v=10204634152394104',
|
'url': 'https://www.facebook.com/video.php?v=10204634152394104',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
@@ -66,6 +89,9 @@ class FacebookIE(InfoExtractor):
|
|||||||
}, {
|
}, {
|
||||||
'url': 'https://www.facebook.com/ChristyClarkForBC/videos/vb.22819070941/10153870694020942/?type=2&theater',
|
'url': 'https://www.facebook.com/ChristyClarkForBC/videos/vb.22819070941/10153870694020942/?type=2&theater',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'facebook:544765982287235',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _login(self):
|
def _login(self):
|
||||||
@@ -105,7 +131,7 @@ class FacebookIE(InfoExtractor):
|
|||||||
login_results, 'login error', default=None, group='error')
|
login_results, 'login 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)
|
||||||
self._downloader.report_warning('unable to log in: bad username/password, or exceded login rate limit (~3/min). Check credentials or wait.')
|
self._downloader.report_warning('unable to log in: bad username/password, or exceeded login rate limit (~3/min). Check credentials or wait.')
|
||||||
return
|
return
|
||||||
|
|
||||||
fb_dtsg = self._search_regex(
|
fb_dtsg = self._search_regex(
|
||||||
@@ -126,7 +152,7 @@ class FacebookIE(InfoExtractor):
|
|||||||
check_response = self._download_webpage(check_req, None,
|
check_response = self._download_webpage(check_req, None,
|
||||||
note='Confirming login')
|
note='Confirming login')
|
||||||
if re.search(r'id="checkpointSubmitButton"', check_response) is not None:
|
if re.search(r'id="checkpointSubmitButton"', check_response) is not None:
|
||||||
self._downloader.report_warning('Unable to confirm login, you have to login in your brower and authorize the login.')
|
self._downloader.report_warning('Unable to confirm login, you have to login in your browser and authorize the login.')
|
||||||
except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err:
|
except (compat_urllib_error.URLError, compat_http_client.HTTPException, socket.error) as err:
|
||||||
self._downloader.report_warning('unable to log in: %s' % error_to_compat_str(err))
|
self._downloader.report_warning('unable to log in: %s' % error_to_compat_str(err))
|
||||||
return
|
return
|
||||||
@@ -136,13 +162,36 @@ class FacebookIE(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://www.facebook.com/video/video.php?v=%s' % video_id
|
req = sanitized_Request('https://www.facebook.com/video/video.php?v=%s' % video_id)
|
||||||
webpage = self._download_webpage(url, video_id)
|
req.add_header('User-Agent', self._CHROME_USER_AGENT)
|
||||||
|
webpage = self._download_webpage(req, video_id)
|
||||||
|
|
||||||
|
video_data = None
|
||||||
|
|
||||||
BEFORE = '{swf.addParam(param[0], param[1]);});\n'
|
BEFORE = '{swf.addParam(param[0], param[1]);});\n'
|
||||||
AFTER = '.forEach(function(variable) {swf.addVariable(variable[0], variable[1]);});'
|
AFTER = '.forEach(function(variable) {swf.addVariable(variable[0], variable[1]);});'
|
||||||
m = re.search(re.escape(BEFORE) + '(.*?)' + re.escape(AFTER), webpage)
|
m = re.search(re.escape(BEFORE) + '(.*?)' + re.escape(AFTER), webpage)
|
||||||
if not m:
|
if m:
|
||||||
|
data = dict(json.loads(m.group(1)))
|
||||||
|
params_raw = compat_urllib_parse_unquote(data['params'])
|
||||||
|
video_data = json.loads(params_raw)['video_data']
|
||||||
|
|
||||||
|
def video_data_list2dict(video_data):
|
||||||
|
ret = {}
|
||||||
|
for item in video_data:
|
||||||
|
format_id = item['stream_type']
|
||||||
|
ret.setdefault(format_id, []).append(item)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
if not video_data:
|
||||||
|
server_js_data = self._parse_json(self._search_regex(
|
||||||
|
r'handleServerJS\(({.+})\);', webpage, 'server js data'), video_id)
|
||||||
|
for item in server_js_data['instances']:
|
||||||
|
if item[1][0] == 'VideoConfig':
|
||||||
|
video_data = video_data_list2dict(item[2][0]['videoData'])
|
||||||
|
break
|
||||||
|
|
||||||
|
if not video_data:
|
||||||
m_msg = re.search(r'class="[^"]*uiInterstitialContent[^"]*"><div>(.*?)</div>', webpage)
|
m_msg = re.search(r'class="[^"]*uiInterstitialContent[^"]*"><div>(.*?)</div>', webpage)
|
||||||
if m_msg is not None:
|
if m_msg is not None:
|
||||||
raise ExtractorError(
|
raise ExtractorError(
|
||||||
@@ -150,12 +199,9 @@ class FacebookIE(InfoExtractor):
|
|||||||
expected=True)
|
expected=True)
|
||||||
else:
|
else:
|
||||||
raise ExtractorError('Cannot parse data')
|
raise ExtractorError('Cannot parse data')
|
||||||
data = dict(json.loads(m.group(1)))
|
|
||||||
params_raw = compat_urllib_parse_unquote(data['params'])
|
|
||||||
params = json.loads(params_raw)
|
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
for format_id, f in params['video_data'].items():
|
for format_id, f in video_data.items():
|
||||||
if not f or not isinstance(f, list):
|
if not f or not isinstance(f, list):
|
||||||
continue
|
continue
|
||||||
for quality in ('sd', 'hd'):
|
for quality in ('sd', 'hd'):
|
||||||
@@ -167,9 +213,15 @@ class FacebookIE(InfoExtractor):
|
|||||||
'url': src,
|
'url': src,
|
||||||
'preference': -10 if format_id == 'progressive' else 0,
|
'preference': -10 if format_id == 'progressive' else 0,
|
||||||
})
|
})
|
||||||
|
dash_manifest = f[0].get('dash_manifest')
|
||||||
|
if dash_manifest:
|
||||||
|
formats.extend(self._parse_mpd_formats(
|
||||||
|
compat_etree_fromstring(compat_urllib_parse_unquote_plus(dash_manifest))))
|
||||||
if not formats:
|
if not formats:
|
||||||
raise ExtractorError('Cannot find video formats')
|
raise ExtractorError('Cannot find video formats')
|
||||||
|
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
video_title = self._html_search_regex(
|
video_title = self._html_search_regex(
|
||||||
r'<h2\s+[^>]*class="uiHeaderTitle"[^>]*>([^<]*)</h2>', webpage, 'title',
|
r'<h2\s+[^>]*class="uiHeaderTitle"[^>]*>([^<]*)</h2>', webpage, 'title',
|
||||||
default=None)
|
default=None)
|
||||||
@@ -188,3 +240,33 @@ class FacebookIE(InfoExtractor):
|
|||||||
'formats': formats,
|
'formats': formats,
|
||||||
'uploader': uploader,
|
'uploader': uploader,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class FacebookPostIE(InfoExtractor):
|
||||||
|
IE_NAME = 'facebook:post'
|
||||||
|
_VALID_URL = r'https?://(?:\w+\.)?facebook\.com/[^/]+/posts/(?P<id>\d+)'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'https://www.facebook.com/maxlayn/posts/10153807558977570',
|
||||||
|
'md5': '037b1fa7f3c2d02b7a0d7bc16031ecc6',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '544765982287235',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': '"What are you doing running in the snow?"',
|
||||||
|
'uploader': 'FailArmy',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
post_id = self._match_id(url)
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, post_id)
|
||||||
|
|
||||||
|
entries = [
|
||||||
|
self.url_result('facebook:%s' % video_id, FacebookIE.ie_key())
|
||||||
|
for video_id in self._parse_json(
|
||||||
|
self._search_regex(
|
||||||
|
r'(["\'])video_ids\1\s*:\s*(?P<ids>\[.+?\])',
|
||||||
|
webpage, 'video ids', group='ids'),
|
||||||
|
post_id)]
|
||||||
|
|
||||||
|
return self.playlist_result(entries, post_id)
|
||||||
|
@@ -6,24 +6,29 @@ from ..utils import (
|
|||||||
xpath_text,
|
xpath_text,
|
||||||
xpath_with_ns,
|
xpath_with_ns,
|
||||||
)
|
)
|
||||||
|
from .youtube import YoutubeIE
|
||||||
|
|
||||||
|
|
||||||
class GamekingsIE(InfoExtractor):
|
class GamekingsIE(InfoExtractor):
|
||||||
_VALID_URL = r'http://www\.gamekings\.tv/(?:videos|nieuws)/(?P<id>[^/]+)'
|
_VALID_URL = r'http://www\.gamekings\.nl/(?:videos|nieuws)/(?P<id>[^/]+)'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://www.gamekings.tv/videos/phoenix-wright-ace-attorney-dual-destinies-review/',
|
# YouTube embed video
|
||||||
# MD5 is flaky, seems to change regularly
|
'url': 'http://www.gamekings.nl/videos/phoenix-wright-ace-attorney-dual-destinies-review/',
|
||||||
# 'md5': '2f32b1f7b80fdc5cb616efb4f387f8a3',
|
'md5': '5208d3a17adeaef829a7861887cb9029',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'phoenix-wright-ace-attorney-dual-destinies-review',
|
'id': 'HkSQKetlGOU',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Phoenix Wright: Ace Attorney \u2013 Dual Destinies Review',
|
'title': 'Phoenix Wright: Ace Attorney - Dual Destinies Review',
|
||||||
'description': 'md5:36fd701e57e8c15ac8682a2374c99731',
|
'description': 'md5:db88c0e7f47e9ea50df3271b9dc72e1d',
|
||||||
'thumbnail': 're:^https?://.*\.jpg$',
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
'uploader_id': 'UCJugRGo4STYMeFr5RoOShtQ',
|
||||||
|
'uploader': 'Gamekings Vault',
|
||||||
|
'upload_date': '20151123',
|
||||||
},
|
},
|
||||||
|
'add_ie': ['Youtube'],
|
||||||
}, {
|
}, {
|
||||||
# vimeo video
|
# vimeo video
|
||||||
'url': 'http://www.gamekings.tv/videos/the-legend-of-zelda-majoras-mask/',
|
'url': 'http://www.gamekings.nl/videos/the-legend-of-zelda-majoras-mask/',
|
||||||
'md5': '12bf04dfd238e70058046937657ea68d',
|
'md5': '12bf04dfd238e70058046937657ea68d',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'the-legend-of-zelda-majoras-mask',
|
'id': 'the-legend-of-zelda-majoras-mask',
|
||||||
@@ -33,7 +38,7 @@ class GamekingsIE(InfoExtractor):
|
|||||||
'thumbnail': 're:^https?://.*\.jpg$',
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://www.gamekings.tv/nieuws/gamekings-extra-shelly-en-david-bereiden-zich-voor-op-de-livestream/',
|
'url': 'http://www.gamekings.nl/nieuws/gamekings-extra-shelly-en-david-bereiden-zich-voor-op-de-livestream/',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
@@ -43,7 +48,11 @@ class GamekingsIE(InfoExtractor):
|
|||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
playlist_id = self._search_regex(
|
playlist_id = self._search_regex(
|
||||||
r'gogoVideo\(\s*\d+\s*,\s*"([^"]+)', webpage, 'playlist id')
|
r'gogoVideo\([^,]+,\s*"([^"]+)', webpage, 'playlist id')
|
||||||
|
|
||||||
|
# Check if a YouTube embed is used
|
||||||
|
if YoutubeIE.suitable(playlist_id):
|
||||||
|
return self.url_result(playlist_id, ie='Youtube')
|
||||||
|
|
||||||
playlist = self._download_xml(
|
playlist = self._download_xml(
|
||||||
'http://www.gamekings.tv/wp-content/themes/gk2010/rss_playlist.php?id=%s' % playlist_id,
|
'http://www.gamekings.tv/wp-content/themes/gk2010/rss_playlist.php?id=%s' % playlist_id,
|
||||||
|
@@ -57,7 +57,7 @@ from .pladform import PladformIE
|
|||||||
from .videomore import VideomoreIE
|
from .videomore import VideomoreIE
|
||||||
from .googledrive import GoogleDriveIE
|
from .googledrive import GoogleDriveIE
|
||||||
from .jwplatform import JWPlatformIE
|
from .jwplatform import JWPlatformIE
|
||||||
from .ultimedia import UltimediaIE
|
from .digiteka import DigitekaIE
|
||||||
|
|
||||||
|
|
||||||
class GenericIE(InfoExtractor):
|
class GenericIE(InfoExtractor):
|
||||||
@@ -487,7 +487,7 @@ class GenericIE(InfoExtractor):
|
|||||||
'description': 'md5:8145d19d320ff3e52f28401f4c4283b9',
|
'description': 'md5:8145d19d320ff3e52f28401f4c4283b9',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
# Embeded Ustream video
|
# Embedded Ustream video
|
||||||
{
|
{
|
||||||
'url': 'http://www.american.edu/spa/pti/nsa-privacy-janus-2014.cfm',
|
'url': 'http://www.american.edu/spa/pti/nsa-privacy-janus-2014.cfm',
|
||||||
'md5': '27b99cdb639c9b12a79bca876a073417',
|
'md5': '27b99cdb639c9b12a79bca876a073417',
|
||||||
@@ -1229,19 +1229,24 @@ class GenericIE(InfoExtractor):
|
|||||||
|
|
||||||
# Check for direct link to a video
|
# Check for direct link to a video
|
||||||
content_type = head_response.headers.get('Content-Type', '')
|
content_type = head_response.headers.get('Content-Type', '')
|
||||||
m = re.match(r'^(?P<type>audio|video|application(?=/ogg$))/(?P<format_id>.+)$', content_type)
|
m = re.match(r'^(?P<type>audio|video|application(?=/(?:ogg$|(?:vnd\.apple\.|x-)?mpegurl)))/(?P<format_id>.+)$', content_type)
|
||||||
if m:
|
if m:
|
||||||
upload_date = unified_strdate(
|
upload_date = unified_strdate(
|
||||||
head_response.headers.get('Last-Modified'))
|
head_response.headers.get('Last-Modified'))
|
||||||
|
formats = []
|
||||||
|
if m.group('format_id').endswith('mpegurl'):
|
||||||
|
formats = self._extract_m3u8_formats(url, video_id, 'mp4')
|
||||||
|
else:
|
||||||
|
formats = [{
|
||||||
|
'format_id': m.group('format_id'),
|
||||||
|
'url': url,
|
||||||
|
'vcodec': 'none' if m.group('type') == 'audio' else None
|
||||||
|
}]
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'title': compat_urllib_parse_unquote(os.path.splitext(url_basename(url))[0]),
|
'title': compat_urllib_parse_unquote(os.path.splitext(url_basename(url))[0]),
|
||||||
'direct': True,
|
'direct': True,
|
||||||
'formats': [{
|
'formats': formats,
|
||||||
'format_id': m.group('format_id'),
|
|
||||||
'url': url,
|
|
||||||
'vcodec': 'none' if m.group('type') == 'audio' else None
|
|
||||||
}],
|
|
||||||
'upload_date': upload_date,
|
'upload_date': upload_date,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1284,7 +1289,7 @@ class GenericIE(InfoExtractor):
|
|||||||
|
|
||||||
self.report_extraction(video_id)
|
self.report_extraction(video_id)
|
||||||
|
|
||||||
# Is it an RSS feed, a SMIL file or a XSPF playlist?
|
# Is it an RSS feed, a SMIL file, an XSPF playlist or a MPD manifest?
|
||||||
try:
|
try:
|
||||||
doc = compat_etree_fromstring(webpage.encode('utf-8'))
|
doc = compat_etree_fromstring(webpage.encode('utf-8'))
|
||||||
if doc.tag == 'rss':
|
if doc.tag == 'rss':
|
||||||
@@ -1293,6 +1298,12 @@ class GenericIE(InfoExtractor):
|
|||||||
return self._parse_smil(doc, url, video_id)
|
return self._parse_smil(doc, url, video_id)
|
||||||
elif doc.tag == '{http://xspf.org/ns/0/}playlist':
|
elif doc.tag == '{http://xspf.org/ns/0/}playlist':
|
||||||
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):
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': compat_urllib_parse_unquote(os.path.splitext(url_basename(url))[0]),
|
||||||
|
'formats': self._parse_mpd_formats(doc, video_id),
|
||||||
|
}
|
||||||
except compat_xml_parse_error:
|
except compat_xml_parse_error:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -1402,7 +1413,7 @@ class GenericIE(InfoExtractor):
|
|||||||
|
|
||||||
# Look for embedded Dailymotion player
|
# Look for embedded Dailymotion player
|
||||||
matches = re.findall(
|
matches = re.findall(
|
||||||
r'<iframe[^>]+?src=(["\'])(?P<url>(?:https?:)?//(?:www\.)?dailymotion\.com/embed/video/.+?)\1', webpage)
|
r'<(?:embed|iframe)[^>]+?src=(["\'])(?P<url>(?:https?:)?//(?:www\.)?dailymotion\.com/(?:embed|swf)/video/.+?)\1', webpage)
|
||||||
if matches:
|
if matches:
|
||||||
return _playlist_from_matches(
|
return _playlist_from_matches(
|
||||||
matches, lambda m: unescapeHTML(m[1]))
|
matches, lambda m: unescapeHTML(m[1]))
|
||||||
@@ -1644,7 +1655,7 @@ class GenericIE(InfoExtractor):
|
|||||||
if myvi_url:
|
if myvi_url:
|
||||||
return self.url_result(myvi_url)
|
return self.url_result(myvi_url)
|
||||||
|
|
||||||
# Look for embeded soundcloud player
|
# Look for embedded soundcloud player
|
||||||
mobj = re.search(
|
mobj = re.search(
|
||||||
r'<iframe\s+(?:[a-zA-Z0-9_-]+="[^"]+"\s+)*src="(?P<url>https?://(?:w\.)?soundcloud\.com/player[^"]+)"',
|
r'<iframe\s+(?:[a-zA-Z0-9_-]+="[^"]+"\s+)*src="(?P<url>https?://(?:w\.)?soundcloud\.com/player[^"]+)"',
|
||||||
webpage)
|
webpage)
|
||||||
@@ -1814,10 +1825,21 @@ class GenericIE(InfoExtractor):
|
|||||||
if mobj is not None:
|
if mobj is not None:
|
||||||
return self.url_result(unescapeHTML(mobj.group('url')), 'ScreenwaveMedia')
|
return self.url_result(unescapeHTML(mobj.group('url')), 'ScreenwaveMedia')
|
||||||
|
|
||||||
# Look for Ulltimedia embeds
|
# Look for Digiteka embeds
|
||||||
ultimedia_url = UltimediaIE._extract_url(webpage)
|
digiteka_url = DigitekaIE._extract_url(webpage)
|
||||||
if ultimedia_url:
|
if digiteka_url:
|
||||||
return self.url_result(self._proto_relative_url(ultimedia_url), 'Ultimedia')
|
return self.url_result(self._proto_relative_url(digiteka_url), DigitekaIE.ie_key())
|
||||||
|
|
||||||
|
# Look for Limelight embeds
|
||||||
|
mobj = re.search(r'LimelightPlayer\.doLoad(Media|Channel|ChannelList)\(["\'](?P<id>[a-z0-9]{32})', webpage)
|
||||||
|
if mobj:
|
||||||
|
lm = {
|
||||||
|
'Media': 'media',
|
||||||
|
'Channel': 'channel',
|
||||||
|
'ChannelList': 'channel_list',
|
||||||
|
}
|
||||||
|
return self.url_result('limelight:%s:%s' % (
|
||||||
|
lm[mobj.group(1)], mobj.group(2)), 'Limelight%s' % mobj.group(1), mobj.group(2))
|
||||||
|
|
||||||
# Look for AdobeTVVideo embeds
|
# Look for AdobeTVVideo embeds
|
||||||
mobj = re.search(
|
mobj = re.search(
|
||||||
@@ -1935,6 +1957,8 @@ class GenericIE(InfoExtractor):
|
|||||||
return self.playlist_result(self._extract_xspf_playlist(video_url, video_id), video_id)
|
return self.playlist_result(self._extract_xspf_playlist(video_url, video_id), video_id)
|
||||||
elif ext == 'm3u8':
|
elif ext == 'm3u8':
|
||||||
entry_info_dict['formats'] = self._extract_m3u8_formats(video_url, video_id, ext='mp4')
|
entry_info_dict['formats'] = self._extract_m3u8_formats(video_url, video_id, ext='mp4')
|
||||||
|
elif ext == 'mpd':
|
||||||
|
entry_info_dict['formats'] = self._extract_mpd_formats(video_url, video_id)
|
||||||
else:
|
else:
|
||||||
entry_info_dict['url'] = video_url
|
entry_info_dict['url'] = video_url
|
||||||
|
|
||||||
|
@@ -1,31 +0,0 @@
|
|||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
|
||||||
from ..utils import smuggle_url
|
|
||||||
|
|
||||||
|
|
||||||
class HistoryIE(InfoExtractor):
|
|
||||||
_VALID_URL = r'https?://(?:www\.)?history\.com/(?:[^/]+/)+(?P<id>[^/]+?)(?:$|[?#])'
|
|
||||||
|
|
||||||
_TESTS = [{
|
|
||||||
'url': 'http://www.history.com/topics/valentines-day/history-of-valentines-day/videos/bet-you-didnt-know-valentines-day?m=528e394da93ae&s=undefined&f=1&free=false',
|
|
||||||
'md5': '6fe632d033c92aa10b8d4a9be047a7c5',
|
|
||||||
'info_dict': {
|
|
||||||
'id': 'bLx5Dv5Aka1G',
|
|
||||||
'ext': 'mp4',
|
|
||||||
'title': "Bet You Didn't Know: Valentine's Day",
|
|
||||||
'description': 'md5:7b57ea4829b391995b405fa60bd7b5f7',
|
|
||||||
},
|
|
||||||
'add_ie': ['ThePlatform'],
|
|
||||||
}]
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
|
||||||
video_id = self._match_id(url)
|
|
||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
|
||||||
|
|
||||||
video_url = self._search_regex(
|
|
||||||
r'data-href="[^"]*/%s"[^>]+data-release-url="([^"]+)"' % video_id,
|
|
||||||
webpage, 'video url')
|
|
||||||
|
|
||||||
return self.url_result(smuggle_url(video_url, {'sig': {'key': 'crazyjava', 'secret': 's3cr3t'}}))
|
|
@@ -159,6 +159,9 @@ class HitboxLiveIE(HitboxIE):
|
|||||||
cdns = player_config.get('cdns')
|
cdns = player_config.get('cdns')
|
||||||
servers = []
|
servers = []
|
||||||
for cdn in cdns:
|
for cdn in cdns:
|
||||||
|
# Subscribe URLs are not playable
|
||||||
|
if cdn.get('rtmpSubscribe') is True:
|
||||||
|
continue
|
||||||
base_url = cdn.get('netConnectionUrl')
|
base_url = cdn.get('netConnectionUrl')
|
||||||
host = re.search('.+\.([^\.]+\.[^\./]+)/.+', base_url).group(1)
|
host = re.search('.+\.([^\.]+\.[^\./]+)/.+', base_url).group(1)
|
||||||
if base_url not in servers:
|
if base_url not in servers:
|
||||||
|
@@ -21,6 +21,18 @@ class InstagramIE(InfoExtractor):
|
|||||||
'title': 'Video by naomipq',
|
'title': 'Video by naomipq',
|
||||||
'description': 'md5:1f17f0ab29bd6fe2bfad705f58de3cb8',
|
'description': 'md5:1f17f0ab29bd6fe2bfad705f58de3cb8',
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
# missing description
|
||||||
|
'url': 'https://www.instagram.com/p/BA-pQFBG8HZ/?taken-by=britneyspears',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'BA-pQFBG8HZ',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'uploader_id': 'britneyspears',
|
||||||
|
'title': 'Video by britneyspears',
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
}, {
|
}, {
|
||||||
'url': 'https://instagram.com/p/-Cmh1cukG2/',
|
'url': 'https://instagram.com/p/-Cmh1cukG2/',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
@@ -32,8 +44,8 @@ class InstagramIE(InfoExtractor):
|
|||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
uploader_id = self._search_regex(r'"owner":{"username":"(.+?)"',
|
uploader_id = self._search_regex(r'"owner":{"username":"(.+?)"',
|
||||||
webpage, 'uploader id', fatal=False)
|
webpage, 'uploader id', fatal=False)
|
||||||
desc = self._search_regex(r'"caption":"(.*?)"', webpage, 'description',
|
desc = self._search_regex(
|
||||||
fatal=False)
|
r'"caption":"(.+?)"', webpage, 'description', default=None)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
|
@@ -2,45 +2,30 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
import re
|
||||||
from random import random
|
import time
|
||||||
from math import floor
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
ExtractorError,
|
|
||||||
remove_end,
|
|
||||||
sanitized_Request,
|
sanitized_Request,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class IPrimaIE(InfoExtractor):
|
class IPrimaIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://play\.iprima\.cz/(?:[^/]+/)*(?P<id>[^?#]+)'
|
_VALID_URL = r'https?://play\.iprima\.cz/(?:.+/)?(?P<id>[^?#]+)'
|
||||||
|
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
|
'url': 'http://play.iprima.cz/gondici-s-r-o-33',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'p136534',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Gondíci s. r. o. (34)',
|
||||||
|
'description': 'md5:16577c629d006aa91f59ca8d8e7f99bd',
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
'skip_download': True, # m3u8 download
|
||||||
|
},
|
||||||
|
}, {
|
||||||
'url': 'http://play.iprima.cz/particka/particka-92',
|
'url': 'http://play.iprima.cz/particka/particka-92',
|
||||||
'info_dict': {
|
|
||||||
'id': '39152',
|
|
||||||
'ext': 'flv',
|
|
||||||
'title': 'Partička (92)',
|
|
||||||
'description': 'md5:74e9617e51bca67c3ecfb2c6f9766f45',
|
|
||||||
'thumbnail': 'http://play.iprima.cz/sites/default/files/image_crops/image_620x349/3/491483_particka-92_image_620x349.jpg',
|
|
||||||
},
|
|
||||||
'params': {
|
|
||||||
'skip_download': True, # requires rtmpdump
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
'url': 'http://play.iprima.cz/particka/tchibo-particka-jarni-moda',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '9718337',
|
|
||||||
'ext': 'flv',
|
|
||||||
'title': 'Tchibo Partička - Jarní móda',
|
|
||||||
'thumbnail': 're:^http:.*\.jpg$',
|
|
||||||
},
|
|
||||||
'params': {
|
|
||||||
'skip_download': True, # requires rtmpdump
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
'url': 'http://play.iprima.cz/zpravy-ftv-prima-2752015',
|
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
@@ -50,62 +35,24 @@ class IPrimaIE(InfoExtractor):
|
|||||||
|
|
||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
if re.search(r'Nemáte oprávnění přistupovat na tuto stránku\.\s*</div>', webpage):
|
video_id = self._search_regex(r'data-product="([^"]+)">', webpage, 'real id')
|
||||||
raise ExtractorError(
|
|
||||||
'%s said: You do not have permission to access this page' % self.IE_NAME, expected=True)
|
|
||||||
|
|
||||||
player_url = (
|
req = sanitized_Request(
|
||||||
'http://embed.livebox.cz/iprimaplay/player-embed-v2.js?__tok%s__=%s' %
|
'http://play.iprima.cz/prehravac/init?_infuse=1'
|
||||||
(floor(random() * 1073741824), floor(random() * 1073741824))
|
'&_ts=%s&productId=%s' % (round(time.time()), video_id))
|
||||||
)
|
|
||||||
|
|
||||||
req = sanitized_Request(player_url)
|
|
||||||
req.add_header('Referer', url)
|
req.add_header('Referer', url)
|
||||||
playerpage = self._download_webpage(req, video_id)
|
playerpage = self._download_webpage(req, video_id, note='Downloading player')
|
||||||
|
|
||||||
base_url = ''.join(re.findall(r"embed\['stream'\] = '(.+?)'.+'(\?auth=)'.+'(.+?)';", playerpage)[1])
|
m3u8_url = self._search_regex(r"'src': '([^']+\.m3u8)'", playerpage, 'm3u8 url')
|
||||||
|
|
||||||
zoneGEO = self._html_search_regex(r'"zoneGEO":(.+?),', webpage, 'zoneGEO')
|
formats = self._extract_m3u8_formats(m3u8_url, video_id, ext='mp4')
|
||||||
if zoneGEO != '0':
|
|
||||||
base_url = base_url.replace('token', 'token_' + zoneGEO)
|
|
||||||
|
|
||||||
formats = []
|
|
||||||
for format_id in ['lq', 'hq', 'hd']:
|
|
||||||
filename = self._html_search_regex(
|
|
||||||
r'"%s_id":(.+?),' % format_id, webpage, 'filename')
|
|
||||||
|
|
||||||
if filename == 'null':
|
|
||||||
continue
|
|
||||||
|
|
||||||
real_id = self._search_regex(
|
|
||||||
r'Prima-(?:[0-9]{10}|WEB)-([0-9]+)[-_]',
|
|
||||||
filename, 'real video id')
|
|
||||||
|
|
||||||
if format_id == 'lq':
|
|
||||||
quality = 0
|
|
||||||
elif format_id == 'hq':
|
|
||||||
quality = 1
|
|
||||||
elif format_id == 'hd':
|
|
||||||
quality = 2
|
|
||||||
filename = 'hq/' + filename
|
|
||||||
|
|
||||||
formats.append({
|
|
||||||
'format_id': format_id,
|
|
||||||
'url': base_url,
|
|
||||||
'quality': quality,
|
|
||||||
'play_path': 'mp4:' + filename.replace('"', '')[:-4],
|
|
||||||
'rtmp_live': True,
|
|
||||||
'ext': 'flv',
|
|
||||||
})
|
|
||||||
|
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': real_id,
|
'id': video_id,
|
||||||
'title': remove_end(self._og_search_title(webpage), ' | Prima PLAY'),
|
'title': self._og_search_title(webpage),
|
||||||
'thumbnail': self._og_search_thumbnail(webpage),
|
'thumbnail': self._og_search_thumbnail(webpage),
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
'description': self._search_regex(
|
'description': self._og_search_description(webpage),
|
||||||
r'<p[^>]+itemprop="description"[^>]*>([^<]+)',
|
|
||||||
webpage, 'description', default=None),
|
|
||||||
}
|
}
|
||||||
|
@@ -214,8 +214,8 @@ class IqiyiIE(InfoExtractor):
|
|||||||
|
|
||||||
def get_enc_key(self, swf_url, video_id):
|
def get_enc_key(self, swf_url, video_id):
|
||||||
# TODO: automatic key extraction
|
# TODO: automatic key extraction
|
||||||
# last update at 2015-12-18 for Zombie::bite
|
# last update at 2016-01-22 for Zombie::bite
|
||||||
enc_key = '8b6b683780897eb8d9a48a02ccc4817d'[::-1]
|
enc_key = '6ab6d0280511493ba85594779759d4ed'
|
||||||
return enc_key
|
return enc_key
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
|
@@ -7,6 +7,7 @@ import json
|
|||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
|
int_or_none,
|
||||||
sanitized_Request,
|
sanitized_Request,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -27,44 +28,36 @@ class IviIE(InfoExtractor):
|
|||||||
'title': 'Иван Васильевич меняет профессию',
|
'title': 'Иван Васильевич меняет профессию',
|
||||||
'description': 'md5:b924063ea1677c8fe343d8a72ac2195f',
|
'description': 'md5:b924063ea1677c8fe343d8a72ac2195f',
|
||||||
'duration': 5498,
|
'duration': 5498,
|
||||||
'thumbnail': 'http://thumbs.ivi.ru/f20.vcp.digitalaccess.ru/contents/d/1/c3c885163a082c29bceeb7b5a267a6.jpg',
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
},
|
},
|
||||||
'skip': 'Only works from Russia',
|
'skip': 'Only works from Russia',
|
||||||
},
|
},
|
||||||
# Serial's serie
|
# Serial's series
|
||||||
{
|
{
|
||||||
'url': 'http://www.ivi.ru/watch/dvoe_iz_lartsa/9549',
|
'url': 'http://www.ivi.ru/watch/dvoe_iz_lartsa/9549',
|
||||||
'md5': '221f56b35e3ed815fde2df71032f4b3e',
|
'md5': '221f56b35e3ed815fde2df71032f4b3e',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '9549',
|
'id': '9549',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Двое из ларца - Серия 1',
|
'title': 'Двое из ларца - Дело Гольдберга (1 часть)',
|
||||||
|
'series': 'Двое из ларца',
|
||||||
|
'season': 'Сезон 1',
|
||||||
|
'season_number': 1,
|
||||||
|
'episode': 'Дело Гольдберга (1 часть)',
|
||||||
|
'episode_number': 1,
|
||||||
'duration': 2655,
|
'duration': 2655,
|
||||||
'thumbnail': 'http://thumbs.ivi.ru/f15.vcp.digitalaccess.ru/contents/8/4/0068dc0677041f3336b7c2baad8fc0.jpg',
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
},
|
},
|
||||||
'skip': 'Only works from Russia',
|
'skip': 'Only works from Russia',
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
# Sorted by quality
|
# Sorted by quality
|
||||||
_known_formats = ['MP4-low-mobile', 'MP4-mobile', 'FLV-lo', 'MP4-lo', 'FLV-hi', 'MP4-hi', 'MP4-SHQ']
|
_KNOWN_FORMATS = ['MP4-low-mobile', 'MP4-mobile', 'FLV-lo', 'MP4-lo', 'FLV-hi', 'MP4-hi', 'MP4-SHQ']
|
||||||
|
|
||||||
# Sorted by size
|
|
||||||
_known_thumbnails = ['Thumb-120x90', 'Thumb-160', 'Thumb-640x480']
|
|
||||||
|
|
||||||
def _extract_description(self, html):
|
|
||||||
m = re.search(r'<meta name="description" content="(?P<description>[^"]+)"/>', html)
|
|
||||||
return m.group('description') if m is not None else None
|
|
||||||
|
|
||||||
def _extract_comment_count(self, html):
|
|
||||||
m = re.search('(?s)<a href="#" id="view-comments" class="action-button dim gradient">\s*Комментарии:\s*(?P<commentcount>\d+)\s*</a>', html)
|
|
||||||
return int(m.group('commentcount')) if m is not None else 0
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
|
|
||||||
api_url = 'http://api.digitalaccess.ru/api/json/'
|
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'method': 'da.content.get',
|
'method': 'da.content.get',
|
||||||
'params': [
|
'params': [
|
||||||
@@ -76,11 +69,10 @@ class IviIE(InfoExtractor):
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
request = sanitized_Request(api_url, json.dumps(data))
|
request = sanitized_Request(
|
||||||
|
'http://api.digitalaccess.ru/api/json/', json.dumps(data))
|
||||||
video_json_page = self._download_webpage(
|
video_json = self._download_json(
|
||||||
request, video_id, 'Downloading video JSON')
|
request, video_id, 'Downloading video JSON')
|
||||||
video_json = json.loads(video_json_page)
|
|
||||||
|
|
||||||
if 'error' in video_json:
|
if 'error' in video_json:
|
||||||
error = video_json['error']
|
error = video_json['error']
|
||||||
@@ -95,35 +87,51 @@ class IviIE(InfoExtractor):
|
|||||||
formats = [{
|
formats = [{
|
||||||
'url': x['url'],
|
'url': x['url'],
|
||||||
'format_id': x['content_format'],
|
'format_id': x['content_format'],
|
||||||
'preference': self._known_formats.index(x['content_format']),
|
'preference': self._KNOWN_FORMATS.index(x['content_format']),
|
||||||
} for x in result['files'] if x['content_format'] in self._known_formats]
|
} for x in result['files'] if x['content_format'] in self._KNOWN_FORMATS]
|
||||||
|
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
if not formats:
|
|
||||||
raise ExtractorError('No media links available for %s' % video_id)
|
|
||||||
|
|
||||||
duration = result['duration']
|
|
||||||
compilation = result['compilation']
|
|
||||||
title = result['title']
|
title = result['title']
|
||||||
|
|
||||||
|
duration = int_or_none(result.get('duration'))
|
||||||
|
compilation = result.get('compilation')
|
||||||
|
episode = title if compilation else None
|
||||||
|
|
||||||
title = '%s - %s' % (compilation, title) if compilation is not None else title
|
title = '%s - %s' % (compilation, title) if compilation is not None else title
|
||||||
|
|
||||||
previews = result['preview']
|
thumbnails = [{
|
||||||
previews.sort(key=lambda fmt: self._known_thumbnails.index(fmt['content_format']))
|
'url': preview['url'],
|
||||||
thumbnail = previews[-1]['url'] if len(previews) > 0 else None
|
'id': preview.get('content_format'),
|
||||||
|
} for preview in result.get('preview', []) if preview.get('url')]
|
||||||
|
|
||||||
video_page = self._download_webpage(url, video_id, 'Downloading video page')
|
webpage = self._download_webpage(url, video_id)
|
||||||
description = self._extract_description(video_page)
|
|
||||||
comment_count = self._extract_comment_count(video_page)
|
season = self._search_regex(
|
||||||
|
r'<li[^>]+class="season active"[^>]*><a[^>]+>([^<]+)',
|
||||||
|
webpage, 'season', default=None)
|
||||||
|
season_number = int_or_none(self._search_regex(
|
||||||
|
r'<li[^>]+class="season active"[^>]*><a[^>]+data-season(?:-index)?="(\d+)"',
|
||||||
|
webpage, 'season number', default=None))
|
||||||
|
|
||||||
|
episode_number = int_or_none(self._search_regex(
|
||||||
|
r'<meta[^>]+itemprop="episode"[^>]*>\s*<meta[^>]+itemprop="episodeNumber"[^>]+content="(\d+)',
|
||||||
|
webpage, 'episode number', default=None))
|
||||||
|
|
||||||
|
description = self._og_search_description(webpage, default=None) or self._html_search_meta(
|
||||||
|
'description', webpage, 'description', default=None)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'title': title,
|
'title': title,
|
||||||
'thumbnail': thumbnail,
|
'series': compilation,
|
||||||
|
'season': season,
|
||||||
|
'season_number': season_number,
|
||||||
|
'episode': episode,
|
||||||
|
'episode_number': episode_number,
|
||||||
|
'thumbnails': thumbnails,
|
||||||
'description': description,
|
'description': description,
|
||||||
'duration': duration,
|
'duration': duration,
|
||||||
'comment_count': comment_count,
|
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,8 +157,11 @@ class IviCompilationIE(InfoExtractor):
|
|||||||
}]
|
}]
|
||||||
|
|
||||||
def _extract_entries(self, html, compilation_id):
|
def _extract_entries(self, html, compilation_id):
|
||||||
return [self.url_result('http://www.ivi.ru/watch/%s/%s' % (compilation_id, serie), 'Ivi')
|
return [
|
||||||
for serie in re.findall(r'<strong><a href="/watch/%s/(\d+)">(?:[^<]+)</a></strong>' % compilation_id, html)]
|
self.url_result(
|
||||||
|
'http://www.ivi.ru/watch/%s/%s' % (compilation_id, serie), IviIE.ie_key())
|
||||||
|
for serie in re.findall(
|
||||||
|
r'<a href="/watch/%s/(\d+)"[^>]+data-id="\1"' % compilation_id, html)]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
@@ -158,7 +169,8 @@ class IviCompilationIE(InfoExtractor):
|
|||||||
season_id = mobj.group('seasonid')
|
season_id = mobj.group('seasonid')
|
||||||
|
|
||||||
if season_id is not None: # Season link
|
if season_id is not None: # Season link
|
||||||
season_page = self._download_webpage(url, compilation_id, 'Downloading season %s web page' % season_id)
|
season_page = self._download_webpage(
|
||||||
|
url, compilation_id, 'Downloading season %s web page' % season_id)
|
||||||
playlist_id = '%s/season%s' % (compilation_id, season_id)
|
playlist_id = '%s/season%s' % (compilation_id, season_id)
|
||||||
playlist_title = self._html_search_meta('title', season_page, 'title')
|
playlist_title = self._html_search_meta('title', season_page, 'title')
|
||||||
entries = self._extract_entries(season_page, compilation_id)
|
entries = self._extract_entries(season_page, compilation_id)
|
||||||
@@ -166,8 +178,9 @@ class IviCompilationIE(InfoExtractor):
|
|||||||
compilation_page = self._download_webpage(url, compilation_id, 'Downloading compilation web page')
|
compilation_page = self._download_webpage(url, compilation_id, 'Downloading compilation web page')
|
||||||
playlist_id = compilation_id
|
playlist_id = compilation_id
|
||||||
playlist_title = self._html_search_meta('title', compilation_page, 'title')
|
playlist_title = self._html_search_meta('title', compilation_page, 'title')
|
||||||
seasons = re.findall(r'<a href="/watch/%s/season(\d+)">[^<]+</a>' % compilation_id, compilation_page)
|
seasons = re.findall(
|
||||||
if len(seasons) == 0: # No seasons in this compilation
|
r'<a href="/watch/%s/season(\d+)' % compilation_id, compilation_page)
|
||||||
|
if not seasons: # No seasons in this compilation
|
||||||
entries = self._extract_entries(compilation_page, compilation_id)
|
entries = self._extract_entries(compilation_page, compilation_id)
|
||||||
else:
|
else:
|
||||||
entries = []
|
entries = []
|
||||||
|
83
youtube_dl/extractor/ivideon.py
Normal file
83
youtube_dl/extractor/ivideon.py
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..compat import (
|
||||||
|
compat_urllib_parse,
|
||||||
|
compat_urlparse,
|
||||||
|
)
|
||||||
|
from ..utils import qualities
|
||||||
|
|
||||||
|
|
||||||
|
class IvideonIE(InfoExtractor):
|
||||||
|
IE_NAME = 'ivideon'
|
||||||
|
IE_DESC = 'Ivideon TV'
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?ivideon\.com/tv/(?:[^/]+/)*camera/(?P<id>\d+-[\da-f]+)/(?P<camera_id>\d+)'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'https://www.ivideon.com/tv/camera/100-916ca13b5c4ad9f564266424a026386d/0/',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '100-916ca13b5c4ad9f564266424a026386d',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 're:^Касса [0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}$',
|
||||||
|
'description': 'Основное предназначение - запись действий кассиров. Плюс общий вид.',
|
||||||
|
'is_live': True,
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
'skip_download': True,
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.ivideon.com/tv/camera/100-c4ee4cb9ede885cf62dfbe93d7b53783/589824/?lang=ru',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'https://www.ivideon.com/tv/map/22.917923/-31.816406/16/camera/100-e7bc16c7d4b5bbd633fd5350b66dfa9a/0',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
|
_QUALITIES = ('low', 'mid', 'hi')
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
server_id, camera_id = mobj.group('id'), mobj.group('camera_id')
|
||||||
|
camera_name, description = None, None
|
||||||
|
camera_url = compat_urlparse.urljoin(
|
||||||
|
url, '/tv/camera/%s/%s/' % (server_id, camera_id))
|
||||||
|
|
||||||
|
webpage = self._download_webpage(camera_url, server_id, fatal=False)
|
||||||
|
if webpage:
|
||||||
|
config_string = self._search_regex(
|
||||||
|
r'var\s+config\s*=\s*({.+?});', webpage, 'config', default=None)
|
||||||
|
if config_string:
|
||||||
|
config = self._parse_json(config_string, server_id, fatal=False)
|
||||||
|
camera_info = config.get('ivTvAppOptions', {}).get('currentCameraInfo')
|
||||||
|
if camera_info:
|
||||||
|
camera_name = camera_info.get('camera_name')
|
||||||
|
description = camera_info.get('misc', {}).get('description')
|
||||||
|
if not camera_name:
|
||||||
|
camera_name = self._html_search_meta(
|
||||||
|
'name', webpage, 'camera name', default=None) or self._search_regex(
|
||||||
|
r'<h1[^>]+class="b-video-title"[^>]*>([^<]+)', webpage, 'camera name', default=None)
|
||||||
|
|
||||||
|
quality = qualities(self._QUALITIES)
|
||||||
|
|
||||||
|
formats = [{
|
||||||
|
'url': 'https://streaming.ivideon.com/flv/live?%s' % compat_urllib_parse.urlencode({
|
||||||
|
'server': server_id,
|
||||||
|
'camera': camera_id,
|
||||||
|
'sessionId': 'demo',
|
||||||
|
'q': quality(format_id),
|
||||||
|
}),
|
||||||
|
'format_id': format_id,
|
||||||
|
'ext': 'flv',
|
||||||
|
'quality': quality(format_id),
|
||||||
|
} for format_id in self._QUALITIES]
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': server_id,
|
||||||
|
'title': self._live_title(camera_name or server_id),
|
||||||
|
'description': description,
|
||||||
|
'is_live': True,
|
||||||
|
'formats': formats,
|
||||||
|
}
|
@@ -49,7 +49,7 @@ class KanalPlayIE(InfoExtractor):
|
|||||||
subs = self._download_json(
|
subs = self._download_json(
|
||||||
'http://www.kanal%splay.se/api/subtitles/%s' % (channel_id, video_id),
|
'http://www.kanal%splay.se/api/subtitles/%s' % (channel_id, video_id),
|
||||||
video_id, 'Downloading subtitles JSON', fatal=False)
|
video_id, 'Downloading subtitles JSON', fatal=False)
|
||||||
return {'se': [{'ext': 'srt', 'data': self._fix_subtitles(subs)}]} if subs else {}
|
return {'sv': [{'ext': 'srt', 'data': self._fix_subtitles(subs)}]} if subs else {}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
@@ -2,12 +2,13 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
from ..utils import smuggle_url
|
||||||
|
|
||||||
|
|
||||||
class KickStarterIE(InfoExtractor):
|
class KickStarterIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://www\.kickstarter\.com/projects/(?P<id>[^/]*)/.*'
|
_VALID_URL = r'https?://www\.kickstarter\.com/projects/(?P<id>[^/]*)/.*'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'https://www.kickstarter.com/projects/1404461844/intersection-the-story-of-josh-grant?ref=home_location',
|
'url': 'https://www.kickstarter.com/projects/1404461844/intersection-the-story-of-josh-grant/description',
|
||||||
'md5': 'c81addca81327ffa66c642b5d8b08cab',
|
'md5': 'c81addca81327ffa66c642b5d8b08cab',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '1404461844',
|
'id': '1404461844',
|
||||||
@@ -27,7 +28,8 @@ class KickStarterIE(InfoExtractor):
|
|||||||
'uploader_id': 'pebble',
|
'uploader_id': 'pebble',
|
||||||
'uploader': 'Pebble Technology',
|
'uploader': 'Pebble Technology',
|
||||||
'title': 'Pebble iOS Notifications',
|
'title': 'Pebble iOS Notifications',
|
||||||
}
|
},
|
||||||
|
'add_ie': ['Vimeo'],
|
||||||
}, {
|
}, {
|
||||||
'url': 'https://www.kickstarter.com/projects/1420158244/power-drive-2000/widget/video.html',
|
'url': 'https://www.kickstarter.com/projects/1420158244/power-drive-2000/widget/video.html',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
@@ -43,7 +45,7 @@ class KickStarterIE(InfoExtractor):
|
|||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
title = self._html_search_regex(
|
title = self._html_search_regex(
|
||||||
r'<title>\s*(.*?)(?:\s*— Kickstarter)?\s*</title>',
|
r'<title>\s*(.*?)(?:\s*—\s*Kickstarter)?\s*</title>',
|
||||||
webpage, 'title')
|
webpage, 'title')
|
||||||
video_url = self._search_regex(
|
video_url = self._search_regex(
|
||||||
r'data-video-url="(.*?)"',
|
r'data-video-url="(.*?)"',
|
||||||
@@ -52,7 +54,7 @@ class KickStarterIE(InfoExtractor):
|
|||||||
return {
|
return {
|
||||||
'_type': 'url_transparent',
|
'_type': 'url_transparent',
|
||||||
'ie_key': 'Generic',
|
'ie_key': 'Generic',
|
||||||
'url': url,
|
'url': smuggle_url(url, {'to_generic': True}),
|
||||||
'title': title,
|
'title': title,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
107
youtube_dl/extractor/konserthusetplay.py
Normal file
107
youtube_dl/extractor/konserthusetplay.py
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
float_or_none,
|
||||||
|
int_or_none,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class KonserthusetPlayIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?konserthusetplay\.se/\?.*\bm=(?P<id>[^&]+)'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.konserthusetplay.se/?m=CKDDnlCY-dhWAAqiMERd-A',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'CKDDnlCY-dhWAAqiMERd-A',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'Orkesterns instrument: Valthornen',
|
||||||
|
'description': 'md5:f10e1f0030202020396a4d712d2fa827',
|
||||||
|
'thumbnail': 're:^https?://.*$',
|
||||||
|
'duration': 398.8,
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
# rtmp download
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
video_id = self._match_id(url)
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
e = self._search_regex(
|
||||||
|
r'https?://csp\.picsearch\.com/rest\?.*\be=(.+?)[&"\']', webpage, 'e')
|
||||||
|
|
||||||
|
rest = self._download_json(
|
||||||
|
'http://csp.picsearch.com/rest?e=%s&containerId=mediaplayer&i=object' % e,
|
||||||
|
video_id, transform_source=lambda s: s[s.index('{'):s.rindex('}') + 1])
|
||||||
|
|
||||||
|
media = rest['media']
|
||||||
|
player_config = media['playerconfig']
|
||||||
|
playlist = player_config['playlist']
|
||||||
|
|
||||||
|
source = next(f for f in playlist if f.get('bitrates'))
|
||||||
|
|
||||||
|
FORMAT_ID_REGEX = r'_([^_]+)_h264m\.mp4'
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
|
||||||
|
fallback_url = source.get('fallbackUrl')
|
||||||
|
fallback_format_id = None
|
||||||
|
if fallback_url:
|
||||||
|
fallback_format_id = self._search_regex(
|
||||||
|
FORMAT_ID_REGEX, fallback_url, 'format id', default=None)
|
||||||
|
|
||||||
|
connection_url = (player_config.get('rtmp', {}).get(
|
||||||
|
'netConnectionUrl') or player_config.get(
|
||||||
|
'plugins', {}).get('bwcheck', {}).get('netConnectionUrl'))
|
||||||
|
if connection_url:
|
||||||
|
for f in source['bitrates']:
|
||||||
|
video_url = f.get('url')
|
||||||
|
if not video_url:
|
||||||
|
continue
|
||||||
|
format_id = self._search_regex(
|
||||||
|
FORMAT_ID_REGEX, video_url, 'format id', default=None)
|
||||||
|
f_common = {
|
||||||
|
'vbr': int_or_none(f.get('bitrate')),
|
||||||
|
'width': int_or_none(f.get('width')),
|
||||||
|
'height': int_or_none(f.get('height')),
|
||||||
|
}
|
||||||
|
f = f_common.copy()
|
||||||
|
f.update({
|
||||||
|
'url': connection_url,
|
||||||
|
'play_path': video_url,
|
||||||
|
'format_id': 'rtmp-%s' % format_id if format_id else 'rtmp',
|
||||||
|
'ext': 'flv',
|
||||||
|
})
|
||||||
|
formats.append(f)
|
||||||
|
if format_id and format_id == fallback_format_id:
|
||||||
|
f = f_common.copy()
|
||||||
|
f.update({
|
||||||
|
'url': fallback_url,
|
||||||
|
'format_id': 'http-%s' % format_id if format_id else 'http',
|
||||||
|
})
|
||||||
|
formats.append(f)
|
||||||
|
|
||||||
|
if not formats and fallback_url:
|
||||||
|
formats.append({
|
||||||
|
'url': fallback_url,
|
||||||
|
})
|
||||||
|
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
title = player_config.get('title') or media['title']
|
||||||
|
description = player_config.get('mediaInfo', {}).get('description')
|
||||||
|
thumbnail = media.get('image')
|
||||||
|
duration = float_or_none(media.get('duration'), 1000)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'duration': duration,
|
||||||
|
'formats': formats,
|
||||||
|
}
|
@@ -31,6 +31,10 @@ class KuwoBaseIE(InfoExtractor):
|
|||||||
(file_format['ext'], file_format.get('br', ''), song_id),
|
(file_format['ext'], file_format.get('br', ''), song_id),
|
||||||
song_id, note='Download %s url info' % file_format['format'],
|
song_id, note='Download %s url info' % file_format['format'],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if song_url == 'IPDeny':
|
||||||
|
raise ExtractorError('This song is blocked in this region', expected=True)
|
||||||
|
|
||||||
if song_url.startswith('http://') or song_url.startswith('https://'):
|
if song_url.startswith('http://') or song_url.startswith('https://'):
|
||||||
formats.append({
|
formats.append({
|
||||||
'url': song_url,
|
'url': song_url,
|
||||||
|
34
youtube_dl/extractor/lemonde.py
Normal file
34
youtube_dl/extractor/lemonde.py
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
|
||||||
|
|
||||||
|
class LemondeIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:.+?\.)?lemonde\.fr/(?:[^/]+/)*(?P<id>[^/]+)\.html'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://www.lemonde.fr/police-justice/video/2016/01/19/comprendre-l-affaire-bygmalion-en-cinq-minutes_4849702_1653578.html',
|
||||||
|
'md5': '01fb3c92de4c12c573343d63e163d302',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'lqm3kl',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': "Comprendre l'affaire Bygmalion en 5 minutes",
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg',
|
||||||
|
'duration': 320,
|
||||||
|
'upload_date': '20160119',
|
||||||
|
'timestamp': 1453194778,
|
||||||
|
'uploader_id': '3pmkp',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'http://redaction.actu.lemonde.fr/societe/video/2016/01/18/calais-debut-des-travaux-de-defrichement-dans-la-jungle_4849233_3224.html',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
display_id = self._match_id(url)
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
|
||||||
|
digiteka_url = self._proto_relative_url(self._search_regex(
|
||||||
|
r'url\s*:\s*(["\'])(?P<url>(?:https?://)?//(?:www\.)?(?:digiteka\.net|ultimedia\.com)/deliver/.+?)\1',
|
||||||
|
webpage, 'digiteka url', group='url'))
|
||||||
|
return self.url_result(digiteka_url, 'Digiteka')
|
@@ -4,11 +4,14 @@ from __future__ import unicode_literals
|
|||||||
import datetime
|
import datetime
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
|
import base64
|
||||||
|
import hashlib
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
from ..compat import (
|
||||||
compat_urllib_parse,
|
compat_urllib_parse,
|
||||||
compat_ord,
|
compat_ord,
|
||||||
|
compat_str,
|
||||||
)
|
)
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
determine_ext,
|
determine_ext,
|
||||||
@@ -16,7 +19,9 @@ from ..utils import (
|
|||||||
parse_iso8601,
|
parse_iso8601,
|
||||||
sanitized_Request,
|
sanitized_Request,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
|
str_or_none,
|
||||||
encode_data_uri,
|
encode_data_uri,
|
||||||
|
url_basename,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -239,3 +244,113 @@ class LetvPlaylistIE(LetvTvIE):
|
|||||||
},
|
},
|
||||||
'playlist_mincount': 7
|
'playlist_mincount': 7
|
||||||
}]
|
}]
|
||||||
|
|
||||||
|
|
||||||
|
class LetvCloudIE(InfoExtractor):
|
||||||
|
IE_DESC = '乐视云'
|
||||||
|
_VALID_URL = r'https?://yuntv\.letv\.com/bcloud.html\?.+'
|
||||||
|
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://yuntv.letv.com/bcloud.html?uu=p7jnfw5hw9&vu=467623dedf',
|
||||||
|
'md5': '26450599afd64c513bc77030ad15db44',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'p7jnfw5hw9_467623dedf',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Video p7jnfw5hw9_467623dedf',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'http://yuntv.letv.com/bcloud.html?uu=p7jnfw5hw9&vu=ec93197892&pu=2c7cd40209&auto_play=1&gpcflag=1&width=640&height=360',
|
||||||
|
'md5': 'e03d9cc8d9c13191e1caf277e42dbd31',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'p7jnfw5hw9_ec93197892',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Video p7jnfw5hw9_ec93197892',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'http://yuntv.letv.com/bcloud.html?uu=p7jnfw5hw9&vu=187060b6fd',
|
||||||
|
'md5': 'cb988699a776b22d4a41b9d43acfb3ac',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'p7jnfw5hw9_187060b6fd',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Video p7jnfw5hw9_187060b6fd',
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def sign_data(obj):
|
||||||
|
if obj['cf'] == 'flash':
|
||||||
|
salt = '2f9d6924b33a165a6d8b5d3d42f4f987'
|
||||||
|
items = ['cf', 'format', 'ran', 'uu', 'ver', 'vu']
|
||||||
|
elif obj['cf'] == 'html5':
|
||||||
|
salt = 'fbeh5player12c43eccf2bec3300344'
|
||||||
|
items = ['cf', 'ran', 'uu', 'bver', 'vu']
|
||||||
|
input_data = ''.join([item + obj[item] for item in items]) + salt
|
||||||
|
obj['sign'] = hashlib.md5(input_data.encode('utf-8')).hexdigest()
|
||||||
|
|
||||||
|
def _get_formats(self, cf, uu, vu, media_id):
|
||||||
|
def get_play_json(cf, timestamp):
|
||||||
|
data = {
|
||||||
|
'cf': cf,
|
||||||
|
'ver': '2.2',
|
||||||
|
'bver': 'firefox44.0',
|
||||||
|
'format': 'json',
|
||||||
|
'uu': uu,
|
||||||
|
'vu': vu,
|
||||||
|
'ran': compat_str(timestamp),
|
||||||
|
}
|
||||||
|
self.sign_data(data)
|
||||||
|
return self._download_json(
|
||||||
|
'http://api.letvcloud.com/gpc.php?' + compat_urllib_parse.urlencode(data),
|
||||||
|
media_id, 'Downloading playJson data for type %s' % cf)
|
||||||
|
|
||||||
|
play_json = get_play_json(cf, time.time())
|
||||||
|
# The server time may be different from local time
|
||||||
|
if play_json.get('code') == 10071:
|
||||||
|
play_json = get_play_json(cf, play_json['timestamp'])
|
||||||
|
|
||||||
|
if not play_json.get('data'):
|
||||||
|
if play_json.get('message'):
|
||||||
|
raise ExtractorError('Letv cloud said: %s' % play_json['message'], expected=True)
|
||||||
|
elif play_json.get('code'):
|
||||||
|
raise ExtractorError('Letv cloud returned error %d' % play_json['code'], expected=True)
|
||||||
|
else:
|
||||||
|
raise ExtractorError('Letv cloud returned an unknwon error')
|
||||||
|
|
||||||
|
def b64decode(s):
|
||||||
|
return base64.b64decode(s.encode('utf-8')).decode('utf-8')
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
for media in play_json['data']['video_info']['media'].values():
|
||||||
|
play_url = media['play_url']
|
||||||
|
url = b64decode(play_url['main_url'])
|
||||||
|
decoded_url = b64decode(url_basename(url))
|
||||||
|
formats.append({
|
||||||
|
'url': url,
|
||||||
|
'ext': determine_ext(decoded_url),
|
||||||
|
'format_id': int_or_none(play_url.get('vtype')),
|
||||||
|
'format_note': str_or_none(play_url.get('definition')),
|
||||||
|
'width': int_or_none(play_url.get('vwidth')),
|
||||||
|
'height': int_or_none(play_url.get('vheight')),
|
||||||
|
})
|
||||||
|
|
||||||
|
return formats
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
uu_mobj = re.search('uu=([\w]+)', url)
|
||||||
|
vu_mobj = re.search('vu=([\w]+)', url)
|
||||||
|
|
||||||
|
if not uu_mobj or not vu_mobj:
|
||||||
|
raise ExtractorError('Invalid URL: %s' % url, expected=True)
|
||||||
|
|
||||||
|
uu = uu_mobj.group(1)
|
||||||
|
vu = vu_mobj.group(1)
|
||||||
|
media_id = uu + '_' + vu
|
||||||
|
|
||||||
|
formats = self._get_formats('flash', uu, vu, media_id) + self._get_formats('html5', uu, vu, media_id)
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': media_id,
|
||||||
|
'title': 'Video %s' % media_id,
|
||||||
|
'formats': formats,
|
||||||
|
}
|
||||||
|
@@ -40,7 +40,8 @@ class LimelightBaseIE(InfoExtractor):
|
|||||||
if not stream_url:
|
if not stream_url:
|
||||||
continue
|
continue
|
||||||
if '.f4m' in stream_url:
|
if '.f4m' in stream_url:
|
||||||
formats.extend(self._extract_f4m_formats(stream_url, video_id))
|
formats.extend(self._extract_f4m_formats(
|
||||||
|
stream_url, video_id, fatal=False))
|
||||||
else:
|
else:
|
||||||
fmt = {
|
fmt = {
|
||||||
'url': stream_url,
|
'url': stream_url,
|
||||||
@@ -72,8 +73,8 @@ class LimelightBaseIE(InfoExtractor):
|
|||||||
format_id = mobile_url.get('targetMediaPlatform')
|
format_id = mobile_url.get('targetMediaPlatform')
|
||||||
if determine_ext(media_url) == 'm3u8':
|
if determine_ext(media_url) == 'm3u8':
|
||||||
formats.extend(self._extract_m3u8_formats(
|
formats.extend(self._extract_m3u8_formats(
|
||||||
media_url, video_id, 'mp4', entry_protocol='m3u8_native',
|
media_url, video_id, 'mp4', 'm3u8_native',
|
||||||
preference=-1, m3u8_id=format_id))
|
m3u8_id=format_id, fatal=False))
|
||||||
else:
|
else:
|
||||||
formats.append({
|
formats.append({
|
||||||
'url': media_url,
|
'url': media_url,
|
||||||
|
37
youtube_dl/extractor/lovehomeporn.py
Normal file
37
youtube_dl/extractor/lovehomeporn.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .nuevo import NuevoBaseIE
|
||||||
|
|
||||||
|
|
||||||
|
class LoveHomePornIE(NuevoBaseIE):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?lovehomeporn\.com/video/(?P<id>\d+)(?:/(?P<display_id>[^/?#&]+))?'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://lovehomeporn.com/video/48483/stunning-busty-brunette-girlfriend-sucking-and-riding-a-big-dick#menu',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '48483',
|
||||||
|
'display_id': 'stunning-busty-brunette-girlfriend-sucking-and-riding-a-big-dick',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Stunning busty brunette girlfriend sucking and riding a big dick',
|
||||||
|
'age_limit': 18,
|
||||||
|
'duration': 238.47,
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
'skip_download': True,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
display_id = mobj.group('display_id')
|
||||||
|
|
||||||
|
info = self._extract_nuevo(
|
||||||
|
'http://lovehomeporn.com/media/nuevo/config.php?key=%s' % video_id,
|
||||||
|
video_id)
|
||||||
|
info.update({
|
||||||
|
'display_id': display_id,
|
||||||
|
'age_limit': 18
|
||||||
|
})
|
||||||
|
return info
|
55
youtube_dl/extractor/matchtv.py
Normal file
55
youtube_dl/extractor/matchtv.py
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import random
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..compat import compat_urllib_parse
|
||||||
|
from ..utils import (
|
||||||
|
sanitized_Request,
|
||||||
|
xpath_text,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class MatchTVIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://matchtv\.ru/?#live-player'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://matchtv.ru/#live-player',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'matchtv-live',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 're:^Матч ТВ - Прямой эфир \d{4}-\d{2}-\d{2} \d{2}:\d{2}$',
|
||||||
|
'is_live': True,
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
video_id = 'matchtv-live'
|
||||||
|
request = sanitized_Request(
|
||||||
|
'http://player.matchtv.ntvplus.tv/player/smil?%s' % compat_urllib_parse.urlencode({
|
||||||
|
'ts': '',
|
||||||
|
'quality': 'SD',
|
||||||
|
'contentId': '561d2c0df7159b37178b4567',
|
||||||
|
'sign': '',
|
||||||
|
'includeHighlights': '0',
|
||||||
|
'userId': '',
|
||||||
|
'sessionId': random.randint(1, 1000000000),
|
||||||
|
'contentType': 'channel',
|
||||||
|
'timeShift': '0',
|
||||||
|
'platform': 'portal',
|
||||||
|
}),
|
||||||
|
headers={
|
||||||
|
'Referer': 'http://player.matchtv.ntvplus.tv/embed-player/NTVEmbedPlayer.swf',
|
||||||
|
})
|
||||||
|
video_url = self._download_json(request, video_id)['data']['videoUrl']
|
||||||
|
f4m_url = xpath_text(self._download_xml(video_url, video_id), './to')
|
||||||
|
formats = self._extract_f4m_formats(f4m_url, video_id)
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': self._live_title('Матч ТВ - Прямой эфир'),
|
||||||
|
'is_live': True,
|
||||||
|
'formats': formats,
|
||||||
|
}
|
@@ -17,7 +17,7 @@ class MDRIE(InfoExtractor):
|
|||||||
_VALID_URL = r'https?://(?:www\.)?(?:mdr|kika)\.de/(?:.*)/[a-z]+(?P<id>\d+)(?:_.+?)?\.html'
|
_VALID_URL = r'https?://(?:www\.)?(?:mdr|kika)\.de/(?:.*)/[a-z]+(?P<id>\d+)(?:_.+?)?\.html'
|
||||||
|
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
# MDR regularily deletes its videos
|
# MDR regularly deletes its videos
|
||||||
'url': 'http://www.mdr.de/fakt/video189002.html',
|
'url': 'http://www.mdr.de/fakt/video189002.html',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
}, {
|
}, {
|
||||||
|
@@ -167,14 +167,16 @@ class MTVServicesInfoExtractor(InfoExtractor):
|
|||||||
'description': description,
|
'description': description,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def _get_feed_query(self, uri):
|
||||||
|
data = {'uri': uri}
|
||||||
|
if self._LANG:
|
||||||
|
data['lang'] = self._LANG
|
||||||
|
return compat_urllib_parse.urlencode(data)
|
||||||
|
|
||||||
def _get_videos_info(self, uri):
|
def _get_videos_info(self, uri):
|
||||||
video_id = self._id_from_uri(uri)
|
video_id = self._id_from_uri(uri)
|
||||||
feed_url = self._get_feed_url(uri)
|
feed_url = self._get_feed_url(uri)
|
||||||
data = compat_urllib_parse.urlencode({'uri': uri})
|
info_url = feed_url + '?' + self._get_feed_query(uri)
|
||||||
info_url = feed_url + '?'
|
|
||||||
if self._LANG:
|
|
||||||
info_url += 'lang=%s&' % self._LANG
|
|
||||||
info_url += data
|
|
||||||
return self._get_videos_info_from_url(info_url, video_id)
|
return self._get_videos_info_from_url(info_url, video_id)
|
||||||
|
|
||||||
def _get_videos_info_from_url(self, url, video_id):
|
def _get_videos_info_from_url(self, url, video_id):
|
||||||
@@ -184,9 +186,7 @@ class MTVServicesInfoExtractor(InfoExtractor):
|
|||||||
return self.playlist_result(
|
return self.playlist_result(
|
||||||
[self._get_video_info(item) for item in idoc.findall('.//item')])
|
[self._get_video_info(item) for item in idoc.findall('.//item')])
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _extract_mgid(self, webpage):
|
||||||
title = url_basename(url)
|
|
||||||
webpage = self._download_webpage(url, title)
|
|
||||||
try:
|
try:
|
||||||
# the url can be http://media.mtvnservices.com/fb/{mgid}.swf
|
# the url can be http://media.mtvnservices.com/fb/{mgid}.swf
|
||||||
# or http://media.mtvnservices.com/{mgid}
|
# or http://media.mtvnservices.com/{mgid}
|
||||||
@@ -207,7 +207,12 @@ class MTVServicesInfoExtractor(InfoExtractor):
|
|||||||
'sm4:video:embed', webpage, 'sm4 embed', default='')
|
'sm4:video:embed', webpage, 'sm4 embed', default='')
|
||||||
mgid = self._search_regex(
|
mgid = self._search_regex(
|
||||||
r'embed/(mgid:.+?)["\'&?/]', sm4_embed, 'mgid')
|
r'embed/(mgid:.+?)["\'&?/]', sm4_embed, 'mgid')
|
||||||
|
return mgid
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
title = url_basename(url)
|
||||||
|
webpage = self._download_webpage(url, title)
|
||||||
|
mgid = self._extract_mgid(webpage)
|
||||||
videos_info = self._get_videos_info(mgid)
|
videos_info = self._get_videos_info(mgid)
|
||||||
return videos_info
|
return videos_info
|
||||||
|
|
||||||
|
@@ -18,13 +18,17 @@ class NBAIE(InfoExtractor):
|
|||||||
'md5': '9e7729d3010a9c71506fd1248f74e4f4',
|
'md5': '9e7729d3010a9c71506fd1248f74e4f4',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '0021200253-okc-bkn-recap',
|
'id': '0021200253-okc-bkn-recap',
|
||||||
'ext': 'flv',
|
'ext': 'mp4',
|
||||||
'title': 'Thunder vs. Nets',
|
'title': 'Thunder vs. Nets',
|
||||||
'description': 'Kevin Durant scores 32 points and dishes out six assists as the Thunder beat the Nets in Brooklyn.',
|
'description': 'Kevin Durant scores 32 points and dishes out six assists as the Thunder beat the Nets in Brooklyn.',
|
||||||
'duration': 181,
|
'duration': 181,
|
||||||
'timestamp': 1354638466,
|
'timestamp': 1354638466,
|
||||||
'upload_date': '20121204',
|
'upload_date': '20121204',
|
||||||
},
|
},
|
||||||
|
'params': {
|
||||||
|
# m3u8 download
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://www.nba.com/video/games/hornets/2014/12/05/0021400276-nyk-cha-play5.nba/',
|
'url': 'http://www.nba.com/video/games/hornets/2014/12/05/0021400276-nyk-cha-play5.nba/',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
@@ -68,7 +72,7 @@ class NBAIE(InfoExtractor):
|
|||||||
if video_url.startswith('/'):
|
if video_url.startswith('/'):
|
||||||
continue
|
continue
|
||||||
if video_url.endswith('.m3u8'):
|
if video_url.endswith('.m3u8'):
|
||||||
formats.extend(self._extract_m3u8_formats(video_url, video_id, m3u8_id='hls', fatal=False))
|
formats.extend(self._extract_m3u8_formats(video_url, video_id, ext='mp4', m3u8_id='hls', fatal=False))
|
||||||
elif video_url.endswith('.f4m'):
|
elif video_url.endswith('.f4m'):
|
||||||
formats.extend(self._extract_f4m_formats(video_url + '?hdcore=3.4.1.1', video_id, f4m_id='hds', fatal=False))
|
formats.extend(self._extract_f4m_formats(video_url + '?hdcore=3.4.1.1', video_id, f4m_id='hds', fatal=False))
|
||||||
else:
|
else:
|
||||||
|
@@ -19,32 +19,39 @@ class NBCIE(InfoExtractor):
|
|||||||
_TESTS = [
|
_TESTS = [
|
||||||
{
|
{
|
||||||
'url': 'http://www.nbc.com/the-tonight-show/segments/112966',
|
'url': 'http://www.nbc.com/the-tonight-show/segments/112966',
|
||||||
# md5 checksum is not stable
|
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'c9xnCo0YPOPH',
|
'id': '112966',
|
||||||
'ext': 'flv',
|
'ext': 'mp4',
|
||||||
'title': 'Jimmy Fallon Surprises Fans at Ben & Jerry\'s',
|
'title': 'Jimmy Fallon Surprises Fans at Ben & Jerry\'s',
|
||||||
'description': 'Jimmy gives out free scoops of his new "Tonight Dough" ice cream flavor by surprising customers at the Ben & Jerry\'s scoop shop.',
|
'description': 'Jimmy gives out free scoops of his new "Tonight Dough" ice cream flavor by surprising customers at the Ben & Jerry\'s scoop shop.',
|
||||||
},
|
},
|
||||||
|
'params': {
|
||||||
|
# m3u8 download
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'url': 'http://www.nbc.com/the-tonight-show/episodes/176',
|
'url': 'http://www.nbc.com/the-tonight-show/episodes/176',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'XwU9KZkp98TH',
|
'id': '176',
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
'title': 'Ricky Gervais, Steven Van Zandt, ILoveMakonnen',
|
'title': 'Ricky Gervais, Steven Van Zandt, ILoveMakonnen',
|
||||||
'description': 'A brand new episode of The Tonight Show welcomes Ricky Gervais, Steven Van Zandt and ILoveMakonnen.',
|
'description': 'A brand new episode of The Tonight Show welcomes Ricky Gervais, Steven Van Zandt and ILoveMakonnen.',
|
||||||
},
|
},
|
||||||
'skip': 'Only works from US',
|
'skip': '404 Not Found',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'url': 'http://www.nbc.com/saturday-night-live/video/star-wars-teaser/2832821',
|
'url': 'http://www.nbc.com/saturday-night-live/video/star-wars-teaser/2832821',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '8iUuyzWDdYUZ',
|
'id': '2832821',
|
||||||
'ext': 'flv',
|
'ext': 'mp4',
|
||||||
'title': 'Star Wars Teaser',
|
'title': 'Star Wars Teaser',
|
||||||
'description': 'md5:0b40f9cbde5b671a7ff62fceccc4f442',
|
'description': 'md5:0b40f9cbde5b671a7ff62fceccc4f442',
|
||||||
},
|
},
|
||||||
|
'params': {
|
||||||
|
# m3u8 download
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
'skip': 'Only works from US',
|
'skip': 'Only works from US',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -66,7 +73,11 @@ class NBCIE(InfoExtractor):
|
|||||||
webpage, 'theplatform url').replace('_no_endcard', '').replace('\\/', '/')))
|
webpage, 'theplatform url').replace('_no_endcard', '').replace('\\/', '/')))
|
||||||
if theplatform_url.startswith('//'):
|
if theplatform_url.startswith('//'):
|
||||||
theplatform_url = 'http:' + theplatform_url
|
theplatform_url = 'http:' + theplatform_url
|
||||||
return self.url_result(smuggle_url(theplatform_url, {'source_url': url}))
|
return {
|
||||||
|
'_type': 'url_transparent',
|
||||||
|
'url': smuggle_url(theplatform_url, {'source_url': url}),
|
||||||
|
'id': video_id,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class NBCSportsVPlayerIE(InfoExtractor):
|
class NBCSportsVPlayerIE(InfoExtractor):
|
||||||
@@ -100,7 +111,7 @@ class NBCSportsVPlayerIE(InfoExtractor):
|
|||||||
|
|
||||||
|
|
||||||
class NBCSportsIE(InfoExtractor):
|
class NBCSportsIE(InfoExtractor):
|
||||||
# Does not include https becuase its certificate is invalid
|
# Does not include https because its certificate is invalid
|
||||||
_VALID_URL = r'http://www\.nbcsports\.com//?(?:[^/]+/)+(?P<id>[0-9a-z-]+)'
|
_VALID_URL = r'http://www\.nbcsports\.com//?(?:[^/]+/)+(?P<id>[0-9a-z-]+)'
|
||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
|
@@ -193,7 +193,7 @@ class NDREmbedBaseIE(InfoExtractor):
|
|||||||
src + '?hdcore=3.7.0&plugin=aasp-3.7.0.39.44', video_id, f4m_id='hds'))
|
src + '?hdcore=3.7.0&plugin=aasp-3.7.0.39.44', video_id, f4m_id='hds'))
|
||||||
elif ext == 'm3u8':
|
elif ext == 'm3u8':
|
||||||
formats.extend(self._extract_m3u8_formats(
|
formats.extend(self._extract_m3u8_formats(
|
||||||
src, video_id, m3u8_id='hls', entry_protocol='m3u8_native'))
|
src, video_id, 'mp4', m3u8_id='hls', entry_protocol='m3u8_native'))
|
||||||
else:
|
else:
|
||||||
quality = f.get('quality')
|
quality = f.get('quality')
|
||||||
ff = {
|
ff = {
|
||||||
|
@@ -12,7 +12,10 @@ from ..compat import (
|
|||||||
compat_str,
|
compat_str,
|
||||||
compat_itertools_count,
|
compat_itertools_count,
|
||||||
)
|
)
|
||||||
from ..utils import sanitized_Request
|
from ..utils import (
|
||||||
|
sanitized_Request,
|
||||||
|
float_or_none,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class NetEaseMusicBaseIE(InfoExtractor):
|
class NetEaseMusicBaseIE(InfoExtractor):
|
||||||
@@ -32,23 +35,32 @@ class NetEaseMusicBaseIE(InfoExtractor):
|
|||||||
result = b64encode(m.digest()).decode('ascii')
|
result = b64encode(m.digest()).decode('ascii')
|
||||||
return result.replace('/', '_').replace('+', '-')
|
return result.replace('/', '_').replace('+', '-')
|
||||||
|
|
||||||
@classmethod
|
def extract_formats(self, info):
|
||||||
def extract_formats(cls, info):
|
|
||||||
formats = []
|
formats = []
|
||||||
for song_format in cls._FORMATS:
|
for song_format in self._FORMATS:
|
||||||
details = info.get(song_format)
|
details = info.get(song_format)
|
||||||
if not details:
|
if not details:
|
||||||
continue
|
continue
|
||||||
formats.append({
|
song_file_path = '/%s/%s.%s' % (
|
||||||
'url': 'http://m5.music.126.net/%s/%s.%s' %
|
self._encrypt(details['dfsId']), details['dfsId'], details['extension'])
|
||||||
(cls._encrypt(details['dfsId']), details['dfsId'],
|
|
||||||
details['extension']),
|
# 203.130.59.9, 124.40.233.182, 115.231.74.139, etc is a reverse proxy-like feature
|
||||||
'ext': details.get('extension'),
|
# from NetEase's CDN provider that can be used if m5.music.126.net does not
|
||||||
'abr': details.get('bitrate', 0) / 1000,
|
# work, especially for users outside of Mainland China
|
||||||
'format_id': song_format,
|
# via: https://github.com/JixunMoe/unblock-163/issues/3#issuecomment-163115880
|
||||||
'filesize': details.get('size'),
|
for host in ('http://m5.music.126.net', 'http://115.231.74.139/m1.music.126.net',
|
||||||
'asr': details.get('sr')
|
'http://124.40.233.182/m1.music.126.net', 'http://203.130.59.9/m1.music.126.net'):
|
||||||
})
|
song_url = host + song_file_path
|
||||||
|
if self._is_valid_url(song_url, info['id'], 'song'):
|
||||||
|
formats.append({
|
||||||
|
'url': song_url,
|
||||||
|
'ext': details.get('extension'),
|
||||||
|
'abr': float_or_none(details.get('bitrate'), scale=1000),
|
||||||
|
'format_id': song_format,
|
||||||
|
'filesize': details.get('size'),
|
||||||
|
'asr': details.get('sr')
|
||||||
|
})
|
||||||
|
break
|
||||||
return formats
|
return formats
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
30
youtube_dl/extractor/nextmovie.py
Normal file
30
youtube_dl/extractor/nextmovie.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .mtv import MTVServicesInfoExtractor
|
||||||
|
from ..compat import compat_urllib_parse
|
||||||
|
|
||||||
|
|
||||||
|
class NextMovieIE(MTVServicesInfoExtractor):
|
||||||
|
IE_NAME = 'nextmovie.com'
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?nextmovie\.com/shows/[^/]+/\d{4}-\d{2}-\d{2}/(?P<id>[^/?#]+)'
|
||||||
|
_FEED_URL = 'http://lite.dextr.mtvi.com/service1/dispatch.htm'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://www.nextmovie.com/shows/exclusives/2013-03-10/mgid:uma:videolist:nextmovie.com:1715019/',
|
||||||
|
'md5': '09a9199f2f11f10107d04fcb153218aa',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '961726',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'The Muppets\' Gravity',
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _get_feed_query(self, uri):
|
||||||
|
return compat_urllib_parse.urlencode({
|
||||||
|
'feed': '1505',
|
||||||
|
'mgid': uri,
|
||||||
|
})
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mgid = self._match_id(url)
|
||||||
|
return self._get_videos_info(mgid)
|
@@ -223,7 +223,7 @@ class NHLVideocenterIE(NHLBaseInfoExtractor):
|
|||||||
response = self._download_webpage(request_url, playlist_title)
|
response = self._download_webpage(request_url, playlist_title)
|
||||||
response = self._fix_json(response)
|
response = self._fix_json(response)
|
||||||
if not response.strip():
|
if not response.strip():
|
||||||
self._downloader.report_warning('Got an empty reponse, trying '
|
self._downloader.report_warning('Got an empty response, trying '
|
||||||
'adding the "newvideos" parameter')
|
'adding the "newvideos" parameter')
|
||||||
response = self._download_webpage(request_url + '&newvideos=true',
|
response = self._download_webpage(request_url + '&newvideos=true',
|
||||||
playlist_title)
|
playlist_title)
|
||||||
|
63
youtube_dl/extractor/nick.py
Normal file
63
youtube_dl/extractor/nick.py
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .mtv import MTVServicesInfoExtractor
|
||||||
|
from ..compat import compat_urllib_parse
|
||||||
|
|
||||||
|
|
||||||
|
class NickIE(MTVServicesInfoExtractor):
|
||||||
|
IE_NAME = 'nick.com'
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?nick\.com/videos/clip/(?P<id>[^/?#.]+)'
|
||||||
|
_FEED_URL = 'http://udat.mtvnservices.com/service1/dispatch.htm'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://www.nick.com/videos/clip/alvinnn-and-the-chipmunks-112-full-episode.html',
|
||||||
|
'playlist': [
|
||||||
|
{
|
||||||
|
'md5': '6e5adc1e28253bbb1b28ab05403dd4d4',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'be6a17b0-412d-11e5-8ff7-0026b9414f30',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'ALVINNN!!! and The Chipmunks: "Mojo Missing/Who\'s The Animal" S1',
|
||||||
|
'description': 'Alvin is convinced his mojo was in a cap he gave to a fan, and must find a way to get his hat back before the Chipmunks’ big concert.\nDuring a costume visit to the zoo, Alvin finds himself mistaken for the real Tasmanian devil.',
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'md5': 'd7be441fc53a1d4882fa9508a1e5b3ce',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'be6b8f96-412d-11e5-8ff7-0026b9414f30',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'ALVINNN!!! and The Chipmunks: "Mojo Missing/Who\'s The Animal" S2',
|
||||||
|
'description': 'Alvin is convinced his mojo was in a cap he gave to a fan, and must find a way to get his hat back before the Chipmunks’ big concert.\nDuring a costume visit to the zoo, Alvin finds himself mistaken for the real Tasmanian devil.',
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'md5': 'efffe1728a234b2b0d2f2b343dd1946f',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'be6cf7e6-412d-11e5-8ff7-0026b9414f30',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'ALVINNN!!! and The Chipmunks: "Mojo Missing/Who\'s The Animal" S3',
|
||||||
|
'description': 'Alvin is convinced his mojo was in a cap he gave to a fan, and must find a way to get his hat back before the Chipmunks’ big concert.\nDuring a costume visit to the zoo, Alvin finds himself mistaken for the real Tasmanian devil.',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'md5': '1ec6690733ab9f41709e274a1d5c7556',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'be6e3354-412d-11e5-8ff7-0026b9414f30',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'ALVINNN!!! and The Chipmunks: "Mojo Missing/Who\'s The Animal" S4',
|
||||||
|
'description': 'Alvin is convinced his mojo was in a cap he gave to a fan, and must find a way to get his hat back before the Chipmunks’ big concert.\nDuring a costume visit to the zoo, Alvin finds himself mistaken for the real Tasmanian devil.',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _get_feed_query(self, uri):
|
||||||
|
return compat_urllib_parse.urlencode({
|
||||||
|
'feed': 'nick_arc_player_prime',
|
||||||
|
'mgid': uri,
|
||||||
|
})
|
||||||
|
|
||||||
|
def _extract_mgid(self, webpage):
|
||||||
|
return self._search_regex(r'data-contenturi="([^"]+)', webpage, 'mgid')
|
@@ -71,6 +71,7 @@ class NowTVBaseIE(InfoExtractor):
|
|||||||
|
|
||||||
|
|
||||||
class NowTVIE(NowTVBaseIE):
|
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)'
|
_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 = [{
|
_TESTS = [{
|
||||||
|
@@ -189,7 +189,7 @@ class NPOIE(NPOBaseIE):
|
|||||||
if not video_url:
|
if not video_url:
|
||||||
continue
|
continue
|
||||||
if format_id == 'adaptive':
|
if format_id == 'adaptive':
|
||||||
formats.extend(self._extract_m3u8_formats(video_url, video_id))
|
formats.extend(self._extract_m3u8_formats(video_url, video_id, 'mp4'))
|
||||||
else:
|
else:
|
||||||
formats.append({
|
formats.append({
|
||||||
'url': video_url,
|
'url': video_url,
|
||||||
@@ -406,6 +406,38 @@ class NPORadioFragmentIE(InfoExtractor):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class SchoolTVIE(InfoExtractor):
|
||||||
|
IE_NAME = 'schooltv'
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?schooltv\.nl/video/(?P<id>[^/?#&]+)'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.schooltv.nl/video/ademhaling-de-hele-dag-haal-je-adem-maar-wat-gebeurt-er-dan-eigenlijk-in-je-lichaam/',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'WO_NTR_429477',
|
||||||
|
'display_id': 'ademhaling-de-hele-dag-haal-je-adem-maar-wat-gebeurt-er-dan-eigenlijk-in-je-lichaam',
|
||||||
|
'title': 'Ademhaling: De hele dag haal je adem. Maar wat gebeurt er dan eigenlijk in je lichaam?',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'description': 'md5:abfa0ff690adb73fd0297fd033aaa631'
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
# Skip because of m3u8 download
|
||||||
|
'skip_download': True
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
display_id = self._match_id(url)
|
||||||
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
video_id = self._search_regex(
|
||||||
|
r'data-mid=(["\'])(?P<id>.+?)\1', webpage, 'video_id', group='id')
|
||||||
|
return {
|
||||||
|
'_type': 'url_transparent',
|
||||||
|
'ie_key': 'NPO',
|
||||||
|
'url': 'npo:%s' % video_id,
|
||||||
|
'display_id': display_id
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class VPROIE(NPOIE):
|
class VPROIE(NPOIE):
|
||||||
IE_NAME = 'vpro'
|
IE_NAME = 'vpro'
|
||||||
_VALID_URL = r'https?://(?:www\.)?(?:tegenlicht\.)?vpro\.nl/(?:[^/]+/){2,}(?P<id>[^/]+)\.html'
|
_VALID_URL = r'https?://(?:www\.)?(?:tegenlicht\.)?vpro\.nl/(?:[^/]+/){2,}(?P<id>[^/]+)\.html'
|
||||||
|
82
youtube_dl/extractor/npr.py
Normal file
82
youtube_dl/extractor/npr.py
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..compat import compat_urllib_parse
|
||||||
|
from ..utils import (
|
||||||
|
int_or_none,
|
||||||
|
qualities,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class NprIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'http://(?:www\.)?npr\.org/player/v2/mediaPlayer\.html\?.*\bid=(?P<id>\d+)'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://www.npr.org/player/v2/mediaPlayer.html?id=449974205',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '449974205',
|
||||||
|
'title': 'New Music From Beach House, Chairlift, CMJ Discoveries And More'
|
||||||
|
},
|
||||||
|
'playlist_count': 7,
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.npr.org/player/v2/mediaPlayer.html?action=1&t=1&islist=false&id=446928052&m=446929930&live=1',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '446928052',
|
||||||
|
'title': "Songs We Love: Tigran Hamasyan, 'Your Mercy is Boundless'"
|
||||||
|
},
|
||||||
|
'playlist': [{
|
||||||
|
'md5': '12fa60cb2d3ed932f53609d4aeceabf1',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '446929930',
|
||||||
|
'ext': 'mp3',
|
||||||
|
'title': 'Your Mercy is Boundless (Bazum en Qo gtutyunqd)',
|
||||||
|
'duration': 402,
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
playlist_id = self._match_id(url)
|
||||||
|
|
||||||
|
config = self._download_json(
|
||||||
|
'http://api.npr.org/query?%s' % compat_urllib_parse.urlencode({
|
||||||
|
'id': playlist_id,
|
||||||
|
'fields': 'titles,audio,show',
|
||||||
|
'format': 'json',
|
||||||
|
'apiKey': 'MDAzMzQ2MjAyMDEyMzk4MTU1MDg3ZmM3MQ010',
|
||||||
|
}), playlist_id)
|
||||||
|
|
||||||
|
story = config['list']['story'][0]
|
||||||
|
|
||||||
|
KNOWN_FORMATS = ('threegp', 'mp4', 'mp3')
|
||||||
|
quality = qualities(KNOWN_FORMATS)
|
||||||
|
|
||||||
|
entries = []
|
||||||
|
for audio in story.get('audio', []):
|
||||||
|
title = audio.get('title', {}).get('$text')
|
||||||
|
duration = int_or_none(audio.get('duration', {}).get('$text'))
|
||||||
|
formats = []
|
||||||
|
for format_id, formats_entry in audio.get('format', {}).items():
|
||||||
|
if not formats_entry:
|
||||||
|
continue
|
||||||
|
if isinstance(formats_entry, list):
|
||||||
|
formats_entry = formats_entry[0]
|
||||||
|
format_url = formats_entry.get('$text')
|
||||||
|
if not format_url:
|
||||||
|
continue
|
||||||
|
if format_id in KNOWN_FORMATS:
|
||||||
|
formats.append({
|
||||||
|
'url': format_url,
|
||||||
|
'format_id': format_id,
|
||||||
|
'ext': formats_entry.get('type'),
|
||||||
|
'quality': quality(format_id),
|
||||||
|
})
|
||||||
|
self._sort_formats(formats)
|
||||||
|
entries.append({
|
||||||
|
'id': audio['id'],
|
||||||
|
'title': title,
|
||||||
|
'duration': duration,
|
||||||
|
'formats': formats,
|
||||||
|
})
|
||||||
|
|
||||||
|
playlist_title = story.get('title', {}).get('$text')
|
||||||
|
return self.playlist_result(entries, playlist_id, playlist_title)
|
@@ -133,26 +133,32 @@ class NRKTVIE(InfoExtractor):
|
|||||||
_TESTS = [
|
_TESTS = [
|
||||||
{
|
{
|
||||||
'url': 'https://tv.nrk.no/serie/20-spoersmaal-tv/MUHH48000314/23-05-2014',
|
'url': 'https://tv.nrk.no/serie/20-spoersmaal-tv/MUHH48000314/23-05-2014',
|
||||||
'md5': 'adf2c5454fa2bf032f47a9f8fb351342',
|
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'MUHH48000314',
|
'id': 'MUHH48000314',
|
||||||
'ext': 'flv',
|
'ext': 'mp4',
|
||||||
'title': '20 spørsmål',
|
'title': '20 spørsmål',
|
||||||
'description': 'md5:bdea103bc35494c143c6a9acdd84887a',
|
'description': 'md5:bdea103bc35494c143c6a9acdd84887a',
|
||||||
'upload_date': '20140523',
|
'upload_date': '20140523',
|
||||||
'duration': 1741.52,
|
'duration': 1741.52,
|
||||||
},
|
},
|
||||||
|
'params': {
|
||||||
|
# m3u8 download
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'url': 'https://tv.nrk.no/program/mdfp15000514',
|
'url': 'https://tv.nrk.no/program/mdfp15000514',
|
||||||
'md5': '383650ece2b25ecec996ad7b5bb2a384',
|
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'mdfp15000514',
|
'id': 'mdfp15000514',
|
||||||
'ext': 'flv',
|
'ext': 'mp4',
|
||||||
'title': 'Kunnskapskanalen: Grunnlovsjubiléet - Stor ståhei for ingenting',
|
'title': 'Grunnlovsjubiléet - Stor ståhei for ingenting',
|
||||||
'description': 'md5:654c12511f035aed1e42bdf5db3b206a',
|
'description': 'md5:654c12511f035aed1e42bdf5db3b206a',
|
||||||
'upload_date': '20140524',
|
'upload_date': '20140524',
|
||||||
'duration': 4605.0,
|
'duration': 4605.08,
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
# m3u8 download
|
||||||
|
'skip_download': True,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@@ -2,6 +2,7 @@
|
|||||||
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 (
|
from ..utils import (
|
||||||
int_or_none,
|
int_or_none,
|
||||||
js_to_json,
|
js_to_json,
|
||||||
@@ -34,7 +35,7 @@ class NTVDeIE(InfoExtractor):
|
|||||||
webpage = self._download_webpage(url, video_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
info = self._parse_json(self._search_regex(
|
info = self._parse_json(self._search_regex(
|
||||||
r'(?s)ntv.pageInfo.article =\s(\{.*?\});', webpage, 'info'),
|
r'(?s)ntv\.pageInfo\.article\s*=\s*(\{.*?\});', webpage, 'info'),
|
||||||
video_id, transform_source=js_to_json)
|
video_id, transform_source=js_to_json)
|
||||||
timestamp = int_or_none(info.get('publishedDateAsUnixTimeStamp'))
|
timestamp = int_or_none(info.get('publishedDateAsUnixTimeStamp'))
|
||||||
vdata = self._parse_json(self._search_regex(
|
vdata = self._parse_json(self._search_regex(
|
||||||
@@ -42,18 +43,24 @@ class NTVDeIE(InfoExtractor):
|
|||||||
webpage, 'player data'),
|
webpage, 'player data'),
|
||||||
video_id, transform_source=js_to_json)
|
video_id, transform_source=js_to_json)
|
||||||
duration = parse_duration(vdata.get('duration'))
|
duration = parse_duration(vdata.get('duration'))
|
||||||
formats = [{
|
|
||||||
'format_id': 'flash',
|
formats = []
|
||||||
'url': 'rtmp://fms.n-tv.de/' + vdata['video'],
|
if vdata.get('video'):
|
||||||
}, {
|
formats.append({
|
||||||
'format_id': 'mobile',
|
'format_id': 'flash',
|
||||||
'url': 'http://video.n-tv.de' + vdata['videoMp4'],
|
'url': 'rtmp://fms.n-tv.de/%s' % vdata['video'],
|
||||||
'tbr': 400, # estimation
|
})
|
||||||
}]
|
if vdata.get('videoMp4'):
|
||||||
m3u8_url = 'http://video.n-tv.de' + vdata['videoM3u8']
|
formats.append({
|
||||||
formats.extend(self._extract_m3u8_formats(
|
'format_id': 'mobile',
|
||||||
m3u8_url, video_id, ext='mp4',
|
'url': compat_urlparse.urljoin('http://video.n-tv.de', vdata['videoMp4']),
|
||||||
entry_protocol='m3u8_native', preference=0))
|
'tbr': 400, # estimation
|
||||||
|
})
|
||||||
|
if vdata.get('videoM3u8'):
|
||||||
|
m3u8_url = compat_urlparse.urljoin('http://video.n-tv.de', vdata['videoM3u8'])
|
||||||
|
formats.extend(self._extract_m3u8_formats(
|
||||||
|
m3u8_url, video_id, ext='mp4', entry_protocol='m3u8_native',
|
||||||
|
preference=0, m3u8_id='hls', fatal=False))
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
38
youtube_dl/extractor/nuevo.py
Normal file
38
youtube_dl/extractor/nuevo.py
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# encoding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
|
||||||
|
from ..utils import (
|
||||||
|
float_or_none,
|
||||||
|
xpath_text
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class NuevoBaseIE(InfoExtractor):
|
||||||
|
def _extract_nuevo(self, config_url, video_id):
|
||||||
|
config = self._download_xml(
|
||||||
|
config_url, video_id, transform_source=lambda s: s.strip())
|
||||||
|
|
||||||
|
title = xpath_text(config, './title', 'title', fatal=True).strip()
|
||||||
|
video_id = xpath_text(config, './mediaid', default=video_id)
|
||||||
|
thumbnail = xpath_text(config, ['./image', './thumb'])
|
||||||
|
duration = float_or_none(xpath_text(config, './duration'))
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
for element_name, format_id in (('file', 'sd'), ('filehd', 'hd')):
|
||||||
|
video_url = xpath_text(config, element_name)
|
||||||
|
if video_url:
|
||||||
|
formats.append({
|
||||||
|
'url': video_url,
|
||||||
|
'format_id': format_id,
|
||||||
|
})
|
||||||
|
self._check_formats(formats, video_id)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'duration': duration,
|
||||||
|
'formats': formats
|
||||||
|
}
|
@@ -13,7 +13,7 @@ from ..utils import (
|
|||||||
|
|
||||||
|
|
||||||
class OdnoklassnikiIE(InfoExtractor):
|
class OdnoklassnikiIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?(?:odnoklassniki|ok)\.ru/(?:video(?:embed)?|web-api/video/moviePlayer)/(?P<id>[\d-]+)'
|
_VALID_URL = r'https?://(?:(?:www|m|mobile)\.)?(?:odnoklassniki|ok)\.ru/(?:video(?:embed)?|web-api/video/moviePlayer)/(?P<id>[\d-]+)'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
# metadata in JSON
|
# metadata in JSON
|
||||||
'url': 'http://ok.ru/video/20079905452',
|
'url': 'http://ok.ru/video/20079905452',
|
||||||
@@ -69,6 +69,12 @@ class OdnoklassnikiIE(InfoExtractor):
|
|||||||
}, {
|
}, {
|
||||||
'url': 'http://www.ok.ru/videoembed/20648036891',
|
'url': 'http://www.ok.ru/videoembed/20648036891',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://m.ok.ru/video/20079905452',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://mobile.ok.ru/video/20079905452',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
|
@@ -21,7 +21,6 @@ class OraTVIE(InfoExtractor):
|
|||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Vine & YouTube Stars Zach King & King Bach On Their Viral Videos!',
|
'title': 'Vine & YouTube Stars Zach King & King Bach On Their Viral Videos!',
|
||||||
'description': 'md5:ebbc5b1424dd5dba7be7538148287ac1',
|
'description': 'md5:ebbc5b1424dd5dba7be7538148287ac1',
|
||||||
'duration': 1477,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,14 +29,14 @@ class OraTVIE(InfoExtractor):
|
|||||||
webpage = self._download_webpage(url, display_id)
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
|
||||||
video_data = self._search_regex(
|
video_data = self._search_regex(
|
||||||
r'"current"\s*:\s*({[^}]+?})', webpage, 'current video')
|
r'"(?:video|current)"\s*:\s*({[^}]+?})', webpage, 'current video')
|
||||||
m3u8_url = self._search_regex(
|
m3u8_url = self._search_regex(
|
||||||
r'"hls_stream"\s*:\s*"([^"]+)', video_data, 'm3u8 url', None)
|
r'hls_stream"?\s*:\s*"([^"]+)', video_data, 'm3u8 url', None)
|
||||||
if m3u8_url:
|
if m3u8_url:
|
||||||
formats = self._extract_m3u8_formats(
|
formats = self._extract_m3u8_formats(
|
||||||
m3u8_url, display_id, 'mp4', 'm3u8_native',
|
m3u8_url, display_id, 'mp4', 'm3u8_native',
|
||||||
m3u8_id='hls', fatal=False)
|
m3u8_id='hls', fatal=False)
|
||||||
# simular to GameSpotIE
|
# similar to GameSpotIE
|
||||||
m3u8_path = compat_urlparse.urlparse(m3u8_url).path
|
m3u8_path = compat_urlparse.urlparse(m3u8_url).path
|
||||||
QUALITIES_RE = r'((,[a-z]+\d+)+,?)'
|
QUALITIES_RE = r'((,[a-z]+\d+)+,?)'
|
||||||
available_qualities = self._search_regex(
|
available_qualities = self._search_regex(
|
||||||
@@ -62,14 +61,12 @@ class OraTVIE(InfoExtractor):
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
'id': self._search_regex(
|
'id': self._search_regex(
|
||||||
r'"video_id"\s*:\s*(\d+)', video_data, 'video id'),
|
r'"id"\s*:\s*(\d+)', video_data, 'video id', default=display_id),
|
||||||
'display_id': display_id,
|
'display_id': display_id,
|
||||||
'title': unescapeHTML(self._og_search_title(webpage)),
|
'title': unescapeHTML(self._og_search_title(webpage)),
|
||||||
'description': get_element_by_attribute(
|
'description': get_element_by_attribute(
|
||||||
'class', 'video_txt_decription', webpage),
|
'class', 'video_txt_decription', webpage),
|
||||||
'thumbnail': self._proto_relative_url(self._search_regex(
|
'thumbnail': self._proto_relative_url(self._search_regex(
|
||||||
r'"thumb"\s*:\s*"([^"]+)', video_data, 'thumbnail', None)),
|
r'"thumb"\s*:\s*"([^"]+)', video_data, 'thumbnail', None)),
|
||||||
'duration': int(self._search_regex(
|
|
||||||
r'"duration"\s*:\s*(\d+)', video_data, 'duration')),
|
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
}
|
}
|
||||||
|
@@ -170,7 +170,21 @@ class ORFOE1IE(InfoExtractor):
|
|||||||
class ORFFM4IE(InfoExtractor):
|
class ORFFM4IE(InfoExtractor):
|
||||||
IE_NAME = 'orf:fm4'
|
IE_NAME = 'orf:fm4'
|
||||||
IE_DESC = 'radio FM4'
|
IE_DESC = 'radio FM4'
|
||||||
_VALID_URL = r'http://fm4\.orf\.at/7tage/?#(?P<date>[0-9]+)/(?P<show>\w+)'
|
_VALID_URL = r'http://fm4\.orf\.at/(?:7tage/?#|player/)(?P<date>[0-9]+)/(?P<show>\w+)'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://fm4.orf.at/player/20160110/IS/',
|
||||||
|
'md5': '01e736e8f1cef7e13246e880a59ad298',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '2016-01-10_2100_tl_54_7DaysSun13_11244',
|
||||||
|
'ext': 'mp3',
|
||||||
|
'title': 'Im Sumpf',
|
||||||
|
'description': 'md5:384c543f866c4e422a55f66a62d669cd',
|
||||||
|
'duration': 7173,
|
||||||
|
'timestamp': 1452456073,
|
||||||
|
'upload_date': '20160110',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
78
youtube_dl/extractor/pandoratv.py
Normal file
78
youtube_dl/extractor/pandoratv.py
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
# encoding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..compat import (
|
||||||
|
compat_str,
|
||||||
|
compat_urlparse,
|
||||||
|
)
|
||||||
|
from ..utils import (
|
||||||
|
ExtractorError,
|
||||||
|
float_or_none,
|
||||||
|
parse_duration,
|
||||||
|
str_to_int,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PandoraTVIE(InfoExtractor):
|
||||||
|
IE_NAME = 'pandora.tv'
|
||||||
|
IE_DESC = '판도라TV'
|
||||||
|
_VALID_URL = r'https?://(?:.+?\.)?channel\.pandora\.tv/channel/video\.ptv\?'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://jp.channel.pandora.tv/channel/video.ptv?c1=&prgid=53294230&ch_userid=mikakim&ref=main&lot=cate_01_2',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '53294230',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': '頭を撫でてくれる?',
|
||||||
|
'description': '頭を撫でてくれる?',
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
'duration': 39,
|
||||||
|
'upload_date': '20151218',
|
||||||
|
'uploader': 'カワイイ動物まとめ',
|
||||||
|
'uploader_id': 'mikakim',
|
||||||
|
'view_count': int,
|
||||||
|
'like_count': int,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
qs = compat_urlparse.parse_qs(compat_urlparse.urlparse(url).query)
|
||||||
|
video_id = qs.get('prgid', [None])[0]
|
||||||
|
user_id = qs.get('ch_userid', [None])[0]
|
||||||
|
if any(not f for f in (video_id, user_id,)):
|
||||||
|
raise ExtractorError('Invalid URL', expected=True)
|
||||||
|
|
||||||
|
data = self._download_json(
|
||||||
|
'http://m.pandora.tv/?c=view&m=viewJsonApi&ch_userid=%s&prgid=%s'
|
||||||
|
% (user_id, video_id), video_id)
|
||||||
|
|
||||||
|
info = data['data']['rows']['vod_play_info']['result']
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
for format_id, format_url in info.items():
|
||||||
|
if not format_url:
|
||||||
|
continue
|
||||||
|
height = self._search_regex(
|
||||||
|
r'^v(\d+)[Uu]rl$', format_id, 'height', default=None)
|
||||||
|
if not height:
|
||||||
|
continue
|
||||||
|
formats.append({
|
||||||
|
'format_id': '%sp' % height,
|
||||||
|
'url': format_url,
|
||||||
|
'height': int(height),
|
||||||
|
})
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': info['subject'],
|
||||||
|
'description': info.get('body'),
|
||||||
|
'thumbnail': info.get('thumbnail') or info.get('poster'),
|
||||||
|
'duration': float_or_none(info.get('runtime'), 1000) or parse_duration(info.get('time')),
|
||||||
|
'upload_date': info['fid'][:8] if isinstance(info.get('fid'), compat_str) else None,
|
||||||
|
'uploader': info.get('nickname'),
|
||||||
|
'uploader_id': info.get('upload_userid'),
|
||||||
|
'view_count': str_to_int(info.get('hit')),
|
||||||
|
'like_count': str_to_int(info.get('likecnt')),
|
||||||
|
'formats': formats,
|
||||||
|
}
|
@@ -232,7 +232,7 @@ class PluralsightIE(PluralsightBaseIE):
|
|||||||
# { a = author, cn = clip_id, lc = end, m = name }
|
# { a = author, cn = clip_id, lc = end, m = name }
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': clip['clipName'],
|
'id': clip.get('clipName') or clip['name'],
|
||||||
'title': '%s - %s' % (module['title'], clip['title']),
|
'title': '%s - %s' % (module['title'], clip['title']),
|
||||||
'duration': int_or_none(clip.get('duration')) or parse_duration(clip.get('formattedDuration')),
|
'duration': int_or_none(clip.get('duration')) or parse_duration(clip.get('formattedDuration')),
|
||||||
'creator': author,
|
'creator': author,
|
||||||
|
@@ -20,7 +20,7 @@ from ..utils import (
|
|||||||
class ProSiebenSat1IE(InfoExtractor):
|
class ProSiebenSat1IE(InfoExtractor):
|
||||||
IE_NAME = 'prosiebensat1'
|
IE_NAME = 'prosiebensat1'
|
||||||
IE_DESC = 'ProSiebenSat.1 Digital'
|
IE_DESC = 'ProSiebenSat.1 Digital'
|
||||||
_VALID_URL = r'https?://(?:www\.)?(?:(?:prosieben|prosiebenmaxx|sixx|sat1|kabeleins|the-voice-of-germany)\.(?:de|at|ch)|ran\.de|fem\.com)/(?P<id>.+)'
|
_VALID_URL = r'https?://(?:www\.)?(?:(?:prosieben|prosiebenmaxx|sixx|sat1|kabeleins|the-voice-of-germany|7tv)\.(?:de|at|ch)|ran\.de|fem\.com)/(?P<id>.+)'
|
||||||
|
|
||||||
_TESTS = [
|
_TESTS = [
|
||||||
{
|
{
|
||||||
@@ -32,7 +32,7 @@ class ProSiebenSat1IE(InfoExtractor):
|
|||||||
'url': 'http://www.prosieben.de/tv/circus-halligalli/videos/218-staffel-2-episode-18-jahresrueckblick-ganze-folge',
|
'url': 'http://www.prosieben.de/tv/circus-halligalli/videos/218-staffel-2-episode-18-jahresrueckblick-ganze-folge',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '2104602',
|
'id': '2104602',
|
||||||
'ext': 'mp4',
|
'ext': 'flv',
|
||||||
'title': 'Episode 18 - Staffel 2',
|
'title': 'Episode 18 - Staffel 2',
|
||||||
'description': 'md5:8733c81b702ea472e069bc48bb658fc1',
|
'description': 'md5:8733c81b702ea472e069bc48bb658fc1',
|
||||||
'upload_date': '20131231',
|
'upload_date': '20131231',
|
||||||
@@ -138,14 +138,13 @@ class ProSiebenSat1IE(InfoExtractor):
|
|||||||
'url': 'http://www.the-voice-of-germany.de/video/31-andreas-kuemmert-rocket-man-clip',
|
'url': 'http://www.the-voice-of-germany.de/video/31-andreas-kuemmert-rocket-man-clip',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '2572814',
|
'id': '2572814',
|
||||||
'ext': 'mp4',
|
'ext': 'flv',
|
||||||
'title': 'Andreas Kümmert: Rocket Man',
|
'title': 'Andreas Kümmert: Rocket Man',
|
||||||
'description': 'md5:6ddb02b0781c6adf778afea606652e38',
|
'description': 'md5:6ddb02b0781c6adf778afea606652e38',
|
||||||
'upload_date': '20131017',
|
'upload_date': '20131017',
|
||||||
'duration': 469.88,
|
'duration': 469.88,
|
||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
# rtmp download
|
|
||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -153,13 +152,12 @@ class ProSiebenSat1IE(InfoExtractor):
|
|||||||
'url': 'http://www.fem.com/wellness/videos/wellness-video-clip-kurztripps-zum-valentinstag.html',
|
'url': 'http://www.fem.com/wellness/videos/wellness-video-clip-kurztripps-zum-valentinstag.html',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '2156342',
|
'id': '2156342',
|
||||||
'ext': 'mp4',
|
'ext': 'flv',
|
||||||
'title': 'Kurztrips zum Valentinstag',
|
'title': 'Kurztrips zum Valentinstag',
|
||||||
'description': 'Romantischer Kurztrip zum Valentinstag? Wir verraten, was sich hier wirklich lohnt.',
|
'description': 'Romantischer Kurztrip zum Valentinstag? Nina Heinemann verrät, was sich hier wirklich lohnt.',
|
||||||
'duration': 307.24,
|
'duration': 307.24,
|
||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
# rtmp download
|
|
||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -172,12 +170,26 @@ class ProSiebenSat1IE(InfoExtractor):
|
|||||||
},
|
},
|
||||||
'playlist_count': 2,
|
'playlist_count': 2,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://www.7tv.de/circus-halligalli/615-best-of-circus-halligalli-ganze-folge',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '4187506',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'Best of Circus HalliGalli',
|
||||||
|
'description': 'md5:8849752efd90b9772c9db6fdf87fb9e9',
|
||||||
|
'upload_date': '20151229',
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
_CLIPID_REGEXES = [
|
_CLIPID_REGEXES = [
|
||||||
r'"clip_id"\s*:\s+"(\d+)"',
|
r'"clip_id"\s*:\s+"(\d+)"',
|
||||||
r'clipid: "(\d+)"',
|
r'clipid: "(\d+)"',
|
||||||
r'clip[iI]d=(\d+)',
|
r'clip[iI]d=(\d+)',
|
||||||
|
r'clip[iI]d\s*=\s*["\'](\d+)',
|
||||||
r"'itemImageUrl'\s*:\s*'/dynamic/thumbnails/full/\d+/(\d+)",
|
r"'itemImageUrl'\s*:\s*'/dynamic/thumbnails/full/\d+/(\d+)",
|
||||||
]
|
]
|
||||||
_TITLE_REGEXES = [
|
_TITLE_REGEXES = [
|
||||||
@@ -186,12 +198,16 @@ class ProSiebenSat1IE(InfoExtractor):
|
|||||||
r'<!-- start video -->\s*<h1>(.+?)</h1>',
|
r'<!-- start video -->\s*<h1>(.+?)</h1>',
|
||||||
r'<h1 class="att-name">\s*(.+?)</h1>',
|
r'<h1 class="att-name">\s*(.+?)</h1>',
|
||||||
r'<header class="module_header">\s*<h2>([^<]+)</h2>\s*</header>',
|
r'<header class="module_header">\s*<h2>([^<]+)</h2>\s*</header>',
|
||||||
|
r'<h2 class="video-title" itemprop="name">\s*(.+?)</h2>',
|
||||||
|
r'<div[^>]+id="veeseoTitle"[^>]*>(.+?)</div>',
|
||||||
]
|
]
|
||||||
_DESCRIPTION_REGEXES = [
|
_DESCRIPTION_REGEXES = [
|
||||||
r'<p itemprop="description">\s*(.+?)</p>',
|
r'<p itemprop="description">\s*(.+?)</p>',
|
||||||
r'<div class="videoDecription">\s*<p><strong>Beschreibung</strong>: (.+?)</p>',
|
r'<div class="videoDecription">\s*<p><strong>Beschreibung</strong>: (.+?)</p>',
|
||||||
r'<div class="g-plusone" data-size="medium"></div>\s*</div>\s*</header>\s*(.+?)\s*<footer>',
|
r'<div class="g-plusone" data-size="medium"></div>\s*</div>\s*</header>\s*(.+?)\s*<footer>',
|
||||||
r'<p class="att-description">\s*(.+?)\s*</p>',
|
r'<p class="att-description">\s*(.+?)\s*</p>',
|
||||||
|
r'<p class="video-description" itemprop="description">\s*(.+?)</p>',
|
||||||
|
r'<div[^>]+id="veeseoDescription"[^>]*>(.+?)</div>',
|
||||||
]
|
]
|
||||||
_UPLOAD_DATE_REGEXES = [
|
_UPLOAD_DATE_REGEXES = [
|
||||||
r'<meta property="og:published_time" content="(.+?)">',
|
r'<meta property="og:published_time" content="(.+?)">',
|
||||||
|
@@ -11,6 +11,7 @@ from ..utils import (
|
|||||||
strip_jsonp,
|
strip_jsonp,
|
||||||
unescapeHTML,
|
unescapeHTML,
|
||||||
clean_html,
|
clean_html,
|
||||||
|
ExtractorError,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -177,7 +178,7 @@ class QQMusicSingerIE(QQPlaylistBaseIE):
|
|||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '001BLpXF2DyJe2',
|
'id': '001BLpXF2DyJe2',
|
||||||
'title': '林俊杰',
|
'title': '林俊杰',
|
||||||
'description': 'md5:2a222d89ba4455a3af19940c0481bb78',
|
'description': 'md5:870ec08f7d8547c29c93010899103751',
|
||||||
},
|
},
|
||||||
'playlist_count': 12,
|
'playlist_count': 12,
|
||||||
}
|
}
|
||||||
@@ -272,7 +273,7 @@ class QQMusicToplistIE(QQPlaylistBaseIE):
|
|||||||
'url': 'http://y.qq.com/#type=toplist&p=top_3',
|
'url': 'http://y.qq.com/#type=toplist&p=top_3',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'top_3',
|
'id': 'top_3',
|
||||||
'title': 'QQ音乐巅峰榜·欧美',
|
'title': '巅峰榜·欧美',
|
||||||
'description': 'QQ音乐巅峰榜·欧美根据用户收听行为自动生成,集结当下最流行的欧美新歌!:更新时间:每周四22点|统'
|
'description': 'QQ音乐巅峰榜·欧美根据用户收听行为自动生成,集结当下最流行的欧美新歌!:更新时间:每周四22点|统'
|
||||||
'计周期:一周(上周四至本周三)|统计对象:三个月内发行的欧美歌曲|统计数量:100首|统计算法:根据'
|
'计周期:一周(上周四至本周三)|统计对象:三个月内发行的欧美歌曲|统计数量:100首|统计算法:根据'
|
||||||
'歌曲在一周内的有效播放次数,由高到低取前100名(同一歌手最多允许5首歌曲同时上榜)|有效播放次数:'
|
'歌曲在一周内的有效播放次数,由高到低取前100名(同一歌手最多允许5首歌曲同时上榜)|有效播放次数:'
|
||||||
@@ -315,7 +316,7 @@ class QQMusicPlaylistIE(QQPlaylistBaseIE):
|
|||||||
IE_DESC = 'QQ音乐 - 歌单'
|
IE_DESC = 'QQ音乐 - 歌单'
|
||||||
_VALID_URL = r'http://y\.qq\.com/#type=taoge&id=(?P<id>[0-9]+)'
|
_VALID_URL = r'http://y\.qq\.com/#type=taoge&id=(?P<id>[0-9]+)'
|
||||||
|
|
||||||
_TEST = {
|
_TESTS = [{
|
||||||
'url': 'http://y.qq.com/#type=taoge&id=3462654915',
|
'url': 'http://y.qq.com/#type=taoge&id=3462654915',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '3462654915',
|
'id': '3462654915',
|
||||||
@@ -323,7 +324,16 @@ class QQMusicPlaylistIE(QQPlaylistBaseIE):
|
|||||||
'description': 'md5:d2c9d758a96b9888cf4fe82f603121d4',
|
'description': 'md5:d2c9d758a96b9888cf4fe82f603121d4',
|
||||||
},
|
},
|
||||||
'playlist_count': 40,
|
'playlist_count': 40,
|
||||||
}
|
'skip': 'playlist gone',
|
||||||
|
}, {
|
||||||
|
'url': 'http://y.qq.com/#type=taoge&id=1374105607',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '1374105607',
|
||||||
|
'title': '易入人心的华语民谣',
|
||||||
|
'description': '民谣的歌曲易于传唱、、歌词朗朗伤口、旋律简单温馨。属于那种才入耳孔。却上心头的感觉。没有太多的复杂情绪。简单而直接地表达乐者的情绪,就是这样的简单才易入人心。',
|
||||||
|
},
|
||||||
|
'playlist_count': 20,
|
||||||
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
list_id = self._match_id(url)
|
list_id = self._match_id(url)
|
||||||
@@ -331,14 +341,21 @@ class QQMusicPlaylistIE(QQPlaylistBaseIE):
|
|||||||
list_json = self._download_json(
|
list_json = self._download_json(
|
||||||
'http://i.y.qq.com/qzone-music/fcg-bin/fcg_ucc_getcdinfo_byids_cp.fcg?type=1&json=1&utf8=1&onlysong=0&disstid=%s'
|
'http://i.y.qq.com/qzone-music/fcg-bin/fcg_ucc_getcdinfo_byids_cp.fcg?type=1&json=1&utf8=1&onlysong=0&disstid=%s'
|
||||||
% list_id, list_id, 'Download list page',
|
% list_id, list_id, 'Download list page',
|
||||||
transform_source=strip_jsonp)['cdlist'][0]
|
transform_source=strip_jsonp)
|
||||||
|
if not len(list_json.get('cdlist', [])):
|
||||||
|
if list_json.get('code'):
|
||||||
|
raise ExtractorError(
|
||||||
|
'QQ Music said: error %d in fetching playlist info' % list_json['code'],
|
||||||
|
expected=True)
|
||||||
|
raise ExtractorError('Unable to get playlist info')
|
||||||
|
|
||||||
|
cdlist = list_json['cdlist'][0]
|
||||||
entries = [
|
entries = [
|
||||||
self.url_result(
|
self.url_result(
|
||||||
'http://y.qq.com/#type=song&mid=' + song['songmid'], 'QQMusic', song['songmid']
|
'http://y.qq.com/#type=song&mid=' + song['songmid'], 'QQMusic', song['songmid']
|
||||||
) for song in list_json['songlist']
|
) for song in cdlist['songlist']
|
||||||
]
|
]
|
||||||
|
|
||||||
list_name = list_json.get('dissname')
|
list_name = cdlist.get('dissname')
|
||||||
list_description = clean_html(unescapeHTML(list_json.get('desc')))
|
list_description = clean_html(unescapeHTML(cdlist.get('desc')))
|
||||||
return self.playlist_result(entries, list_id, list_name, list_description)
|
return self.playlist_result(entries, list_id, list_name, list_description)
|
||||||
|
62
youtube_dl/extractor/regiotv.py
Normal file
62
youtube_dl/extractor/regiotv.py
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
|
||||||
|
from ..utils import (
|
||||||
|
sanitized_Request,
|
||||||
|
xpath_text,
|
||||||
|
xpath_with_ns,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class RegioTVIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?regio-tv\.de/video/(?P<id>[0-9]+)'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://www.regio-tv.de/video/395808.html',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '395808',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Wir in Ludwigsburg',
|
||||||
|
'description': 'Mit unseren zuckersüßen Adventskindern, außerdem besuchen wir die Abendsterne!',
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.regio-tv.de/video/395808',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
video_id = self._match_id(url)
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
key = self._search_regex(
|
||||||
|
r'key\s*:\s*(["\'])(?P<key>.+?)\1', webpage, 'key', group='key')
|
||||||
|
title = self._og_search_title(webpage)
|
||||||
|
|
||||||
|
SOAP_TEMPLATE = '<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><{0} xmlns="http://v.telvi.de/"><key xsi:type="xsd:string">{1}</key></{0}></soap:Body></soap:Envelope>'
|
||||||
|
|
||||||
|
request = sanitized_Request(
|
||||||
|
'http://v.telvi.de/',
|
||||||
|
SOAP_TEMPLATE.format('GetHTML5VideoData', key).encode('utf-8'))
|
||||||
|
video_data = self._download_xml(request, video_id, 'Downloading video XML')
|
||||||
|
|
||||||
|
NS_MAP = {
|
||||||
|
'xsi': 'http://www.w3.org/2001/XMLSchema-instance',
|
||||||
|
'soap': 'http://schemas.xmlsoap.org/soap/envelope/',
|
||||||
|
}
|
||||||
|
|
||||||
|
video_url = xpath_text(
|
||||||
|
video_data, xpath_with_ns('.//video', NS_MAP), 'video url', fatal=True)
|
||||||
|
thumbnail = xpath_text(
|
||||||
|
video_data, xpath_with_ns('.//image', NS_MAP), 'thumbnail')
|
||||||
|
description = self._og_search_description(
|
||||||
|
webpage) or self._html_search_meta('description', webpage)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'url': video_url,
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
}
|
127
youtube_dl/extractor/revision3.py
Normal file
127
youtube_dl/extractor/revision3.py
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..compat import compat_str
|
||||||
|
from ..utils import (
|
||||||
|
int_or_none,
|
||||||
|
parse_iso8601,
|
||||||
|
unescapeHTML,
|
||||||
|
qualities,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Revision3IE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?(?P<domain>(?:revision3|testtube|animalist)\.com)/(?P<id>[^/]+(?:/[^/?#]+)?)'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://www.revision3.com/technobuffalo/5-google-predictions-for-2016',
|
||||||
|
'md5': 'd94a72d85d0a829766de4deb8daaf7df',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '73034',
|
||||||
|
'display_id': 'technobuffalo/5-google-predictions-for-2016',
|
||||||
|
'ext': 'webm',
|
||||||
|
'title': '5 Google Predictions for 2016',
|
||||||
|
'description': 'Google had a great 2015, but it\'s already time to look ahead. Here are our five predictions for 2016.',
|
||||||
|
'upload_date': '20151228',
|
||||||
|
'timestamp': 1451325600,
|
||||||
|
'duration': 187,
|
||||||
|
'uploader': 'TechnoBuffalo',
|
||||||
|
'uploader_id': 'technobuffalo',
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
'url': 'http://testtube.com/brainstuff',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '251',
|
||||||
|
'title': 'BrainStuff',
|
||||||
|
'description': 'Whether the topic is popcorn or particle physics, you can count on the HowStuffWorks team to explore-and explain-the everyday science in the world around us on BrainStuff.',
|
||||||
|
},
|
||||||
|
'playlist_mincount': 93,
|
||||||
|
}, {
|
||||||
|
'url': 'https://testtube.com/dnews/5-weird-ways-plants-can-eat-animals?utm_source=FB&utm_medium=DNews&utm_campaign=DNewsSocial',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '60163',
|
||||||
|
'display_id': 'dnews/5-weird-ways-plants-can-eat-animals',
|
||||||
|
'duration': 275,
|
||||||
|
'ext': 'webm',
|
||||||
|
'title': '5 Weird Ways Plants Can Eat Animals',
|
||||||
|
'description': 'Why have some plants evolved to eat meat?',
|
||||||
|
'upload_date': '20150120',
|
||||||
|
'timestamp': 1421763300,
|
||||||
|
'uploader': 'DNews',
|
||||||
|
'uploader_id': 'dnews',
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
_PAGE_DATA_TEMPLATE = 'http://www.%s/apiProxy/ddn/%s?domain=%s'
|
||||||
|
_API_KEY = 'ba9c741bce1b9d8e3defcc22193f3651b8867e62'
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
domain, display_id = re.match(self._VALID_URL, url).groups()
|
||||||
|
page_info = self._download_json(
|
||||||
|
self._PAGE_DATA_TEMPLATE % (domain, display_id, domain), display_id)
|
||||||
|
|
||||||
|
if page_info['data']['type'] == 'episode':
|
||||||
|
episode_data = page_info['data']
|
||||||
|
video_id = compat_str(episode_data['video']['data']['id'])
|
||||||
|
video_data = self._download_json(
|
||||||
|
'http://revision3.com/api/getPlaylist.json?api_key=%s&codecs=h264,vp8,theora&video_id=%s' % (self._API_KEY, video_id),
|
||||||
|
video_id)['items'][0]
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
for vcodec, media in video_data['media'].items():
|
||||||
|
for quality_id, quality in media.items():
|
||||||
|
if quality_id == 'hls':
|
||||||
|
formats.extend(self._extract_m3u8_formats(
|
||||||
|
quality['url'], video_id, 'mp4',
|
||||||
|
'm3u8_native', m3u8_id='hls', fatal=False))
|
||||||
|
else:
|
||||||
|
formats.append({
|
||||||
|
'url': quality['url'],
|
||||||
|
'format_id': '%s-%s' % (vcodec, quality_id),
|
||||||
|
'tbr': int_or_none(quality.get('bitrate')),
|
||||||
|
'vcodec': vcodec,
|
||||||
|
})
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
preference = qualities(['mini', 'small', 'medium', 'large'])
|
||||||
|
thumbnails = [{
|
||||||
|
'url': image_url,
|
||||||
|
'id': image_id,
|
||||||
|
'preference': preference(image_id)
|
||||||
|
} for image_id, image_url in video_data.get('images', {}).items()]
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'display_id': display_id,
|
||||||
|
'title': unescapeHTML(video_data['title']),
|
||||||
|
'description': unescapeHTML(video_data.get('summary')),
|
||||||
|
'timestamp': parse_iso8601(episode_data.get('publishTime'), ' '),
|
||||||
|
'author': episode_data.get('author'),
|
||||||
|
'uploader': video_data.get('show', {}).get('name'),
|
||||||
|
'uploader_id': video_data.get('show', {}).get('slug'),
|
||||||
|
'duration': int_or_none(video_data.get('duration')),
|
||||||
|
'thumbnails': thumbnails,
|
||||||
|
'formats': formats,
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
show_data = page_info['show']['data']
|
||||||
|
episodes_data = page_info['episodes']['data']
|
||||||
|
num_episodes = page_info['meta']['totalEpisodes']
|
||||||
|
processed_episodes = 0
|
||||||
|
entries = []
|
||||||
|
page_num = 1
|
||||||
|
while True:
|
||||||
|
entries.extend([self.url_result(
|
||||||
|
'http://%s/%s/%s' % (domain, display_id, episode['slug'])) for episode in episodes_data])
|
||||||
|
processed_episodes += len(episodes_data)
|
||||||
|
if processed_episodes == num_episodes:
|
||||||
|
break
|
||||||
|
page_num += 1
|
||||||
|
episodes_data = self._download_json(self._PAGE_DATA_TEMPLATE % (
|
||||||
|
domain, display_id + '/' + compat_str(page_num), domain),
|
||||||
|
display_id)['episodes']['data']
|
||||||
|
|
||||||
|
return self.playlist_result(
|
||||||
|
entries, compat_str(show_data['id']),
|
||||||
|
show_data.get('name'), show_data.get('summary'))
|
@@ -1,20 +1,25 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from .common import InfoExtractor
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
float_or_none,
|
float_or_none,
|
||||||
|
parse_iso8601,
|
||||||
|
unescapeHTML,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class RteIE(InfoExtractor):
|
class RteIE(InfoExtractor):
|
||||||
|
IE_NAME = 'rte'
|
||||||
|
IE_DESC = 'Raidió Teilifís Éireann TV'
|
||||||
_VALID_URL = r'https?://(?:www\.)?rte\.ie/player/[^/]{2,3}/show/[^/]+/(?P<id>[0-9]+)'
|
_VALID_URL = r'https?://(?:www\.)?rte\.ie/player/[^/]{2,3}/show/[^/]+/(?P<id>[0-9]+)'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.rte.ie/player/ie/show/iwitness-862/10478715/',
|
'url': 'http://www.rte.ie/player/ie/show/iwitness-862/10478715/',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '10478715',
|
'id': '10478715',
|
||||||
'ext': 'mp4',
|
'ext': 'flv',
|
||||||
'title': 'Watch iWitness online',
|
'title': 'Watch iWitness online',
|
||||||
'thumbnail': 're:^https?://.*\.jpg$',
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
'description': 'iWitness : The spirit of Ireland, one voice and one minute at a time.',
|
'description': 'iWitness : The spirit of Ireland, one voice and one minute at a time.',
|
||||||
@@ -44,13 +49,6 @@ class RteIE(InfoExtractor):
|
|||||||
# f4m_url = server + relative_url
|
# f4m_url = server + relative_url
|
||||||
f4m_url = json_string['shows'][0]['media:group'][0]['rte:server'] + json_string['shows'][0]['media:group'][0]['url']
|
f4m_url = json_string['shows'][0]['media:group'][0]['rte:server'] + json_string['shows'][0]['media:group'][0]['url']
|
||||||
f4m_formats = self._extract_f4m_formats(f4m_url, video_id)
|
f4m_formats = self._extract_f4m_formats(f4m_url, video_id)
|
||||||
f4m_formats = [{
|
|
||||||
'format_id': f['format_id'],
|
|
||||||
'url': f['url'],
|
|
||||||
'ext': 'mp4',
|
|
||||||
'width': f['width'],
|
|
||||||
'height': f['height'],
|
|
||||||
} for f in f4m_formats]
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
@@ -60,3 +58,102 @@ class RteIE(InfoExtractor):
|
|||||||
'thumbnail': thumbnail,
|
'thumbnail': thumbnail,
|
||||||
'duration': duration,
|
'duration': duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class RteRadioIE(InfoExtractor):
|
||||||
|
IE_NAME = 'rte:radio'
|
||||||
|
IE_DESC = 'Raidió Teilifís Éireann radio'
|
||||||
|
# Radioplayer URLs have two distinct specifier formats,
|
||||||
|
# the old format #!rii=<channel_id>:<id>:<playable_item_id>:<date>:
|
||||||
|
# the new format #!rii=b<channel_id>_<id>_<playable_item_id>_<date>_
|
||||||
|
# where the IDs are int/empty, the date is DD-MM-YYYY, and the specifier may be truncated.
|
||||||
|
# An <id> uniquely defines an individual recording, and is the only part we require.
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?rte\.ie/radio/utils/radioplayer/rteradioweb\.html#!rii=(?:b?[0-9]*)(?:%3A|:|%5F|_)(?P<id>[0-9]+)'
|
||||||
|
|
||||||
|
_TESTS = [{
|
||||||
|
# Old-style player URL; HLS and RTMPE formats
|
||||||
|
'url': 'http://www.rte.ie/radio/utils/radioplayer/rteradioweb.html#!rii=16:10507902:2414:27-12-2015:',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '10507902',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Gloria',
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
'description': 'md5:9ce124a7fb41559ec68f06387cabddf0',
|
||||||
|
'timestamp': 1451203200,
|
||||||
|
'upload_date': '20151227',
|
||||||
|
'duration': 7230.0,
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
'skip_download': 'f4m fails with --test atm'
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
# New-style player URL; RTMPE formats only
|
||||||
|
'url': 'http://rte.ie/radio/utils/radioplayer/rteradioweb.html#!rii=b16_3250678_8861_06-04-2012_',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '3250678',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'The Lyric Concert with Paul Herriott',
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
'description': '',
|
||||||
|
'timestamp': 1333742400,
|
||||||
|
'upload_date': '20120406',
|
||||||
|
'duration': 7199.016,
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
'skip_download': 'f4m fails with --test atm'
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
item_id = self._match_id(url)
|
||||||
|
|
||||||
|
json_string = self._download_json(
|
||||||
|
'http://www.rte.ie/rteavgen/getplaylist/?type=web&format=json&id=' + item_id,
|
||||||
|
item_id)
|
||||||
|
|
||||||
|
# NB the string values in the JSON are stored using XML escaping(!)
|
||||||
|
show = json_string['shows'][0]
|
||||||
|
title = unescapeHTML(show['title'])
|
||||||
|
description = unescapeHTML(show.get('description'))
|
||||||
|
thumbnail = show.get('thumbnail')
|
||||||
|
duration = float_or_none(show.get('duration'), 1000)
|
||||||
|
timestamp = parse_iso8601(show.get('published'))
|
||||||
|
|
||||||
|
mg = show['media:group'][0]
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
|
||||||
|
if mg.get('url'):
|
||||||
|
m = re.match(r'(?P<url>rtmpe?://[^/]+)/(?P<app>.+)/(?P<playpath>mp4:.*)', mg['url'])
|
||||||
|
if m:
|
||||||
|
m = m.groupdict()
|
||||||
|
formats.append({
|
||||||
|
'url': m['url'] + '/' + m['app'],
|
||||||
|
'app': m['app'],
|
||||||
|
'play_path': m['playpath'],
|
||||||
|
'player_url': url,
|
||||||
|
'ext': 'flv',
|
||||||
|
'format_id': 'rtmp',
|
||||||
|
})
|
||||||
|
|
||||||
|
if mg.get('hls_server') and mg.get('hls_url'):
|
||||||
|
formats.extend(self._extract_m3u8_formats(
|
||||||
|
mg['hls_server'] + mg['hls_url'], item_id, 'mp4',
|
||||||
|
entry_protocol='m3u8_native', m3u8_id='hls', fatal=False))
|
||||||
|
|
||||||
|
if mg.get('hds_server') and mg.get('hds_url'):
|
||||||
|
formats.extend(self._extract_f4m_formats(
|
||||||
|
mg['hds_server'] + mg['hds_url'], item_id,
|
||||||
|
f4m_id='hds', fatal=False))
|
||||||
|
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': item_id,
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'timestamp': timestamp,
|
||||||
|
'duration': duration,
|
||||||
|
'formats': formats,
|
||||||
|
}
|
||||||
|
44
youtube_dl/extractor/ruleporn.py
Normal file
44
youtube_dl/extractor/ruleporn.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .nuevo import NuevoBaseIE
|
||||||
|
|
||||||
|
|
||||||
|
class RulePornIE(NuevoBaseIE):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?ruleporn\.com/(?:[^/?#&]+/)*(?P<id>[^/?#&]+)'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://ruleporn.com/brunette-nympho-chick-takes-her-boyfriend-in-every-angle/',
|
||||||
|
'md5': '86861ebc624a1097c7c10eaf06d7d505',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '48212',
|
||||||
|
'display_id': 'brunette-nympho-chick-takes-her-boyfriend-in-every-angle',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Brunette Nympho Chick Takes Her Boyfriend In Every Angle',
|
||||||
|
'description': 'md5:6d28be231b981fff1981deaaa03a04d5',
|
||||||
|
'age_limit': 18,
|
||||||
|
'duration': 635.1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
display_id = self._match_id(url)
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
|
||||||
|
video_id = self._search_regex(
|
||||||
|
r'lovehomeporn\.com/embed/(\d+)', webpage, 'video id')
|
||||||
|
|
||||||
|
title = self._search_regex(
|
||||||
|
r'<h2[^>]+title=(["\'])(?P<url>.+?)\1',
|
||||||
|
webpage, 'title', group='url')
|
||||||
|
description = self._html_search_meta('description', webpage)
|
||||||
|
|
||||||
|
info = self._extract_nuevo(
|
||||||
|
'http://lovehomeporn.com/media/nuevo/econfig.php?key=%s&rp=true' % video_id,
|
||||||
|
video_id)
|
||||||
|
info.update({
|
||||||
|
'display_id': display_id,
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'age_limit': 18
|
||||||
|
})
|
||||||
|
return info
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user