mirror of
https://github.com/l1ving/youtube-dl
synced 2025-02-15 07:32:58 +08:00
commit
73fd1ab30a
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,5 +1,6 @@
|
|||||||
*.pyc
|
*.pyc
|
||||||
*.pyo
|
*.pyo
|
||||||
|
*.class
|
||||||
*~
|
*~
|
||||||
*.DS_Store
|
*.DS_Store
|
||||||
wine-py2exe/
|
wine-py2exe/
|
||||||
|
17
AUTHORS
17
AUTHORS
@ -146,3 +146,20 @@ Lukáš Lalinský
|
|||||||
Qijiang Fan
|
Qijiang Fan
|
||||||
Rémy Léone
|
Rémy Léone
|
||||||
Marco Ferragina
|
Marco Ferragina
|
||||||
|
reiv
|
||||||
|
Muratcan Simsek
|
||||||
|
Evan Lu
|
||||||
|
flatgreen
|
||||||
|
Brian Foley
|
||||||
|
Vignesh Venkat
|
||||||
|
Tom Gijselinck
|
||||||
|
Founder Fang
|
||||||
|
Andrew Alexeyew
|
||||||
|
Saso Bezlaj
|
||||||
|
Erwin de Haan
|
||||||
|
Jens Wille
|
||||||
|
Robin Houtevelts
|
||||||
|
Patrick Griffis
|
||||||
|
Aidan Rowe
|
||||||
|
mutantmonkey
|
||||||
|
Ben Congdon
|
||||||
|
@ -1,4 +1,18 @@
|
|||||||
**Please include the full output of youtube-dl when run with `-v`**.
|
**Please include the full output of youtube-dl when run with `-v`**, i.e. **add** `-v` flag to **your command line**, copy the **whole** output and post it in the issue body wrapped in \`\`\` for better formatting. It should look similar to this:
|
||||||
|
```
|
||||||
|
$ youtube-dl -v <your command line>
|
||||||
|
[debug] System config: []
|
||||||
|
[debug] User config: []
|
||||||
|
[debug] Command-line args: [u'-v', u'http://www.youtube.com/watch?v=BaW_jenozKcj']
|
||||||
|
[debug] Encodings: locale cp1251, fs mbcs, out cp866, pref cp1251
|
||||||
|
[debug] youtube-dl version 2015.12.06
|
||||||
|
[debug] Git HEAD: 135392e
|
||||||
|
[debug] Python version 2.6.6 - Windows-2003Server-5.2.3790-SP2
|
||||||
|
[debug] exe versions: ffmpeg N-75573-g1d0487f, ffprobe N-75573-g1d0487f, rtmpdump 2.4
|
||||||
|
[debug] Proxy map: {}
|
||||||
|
...
|
||||||
|
```
|
||||||
|
**Do not post screenshots of verbose log only plain text is acceptable.**
|
||||||
|
|
||||||
The output (including the first lines) contains important debugging information. Issues without the full output are often not reproducible and therefore do not get solved in short order, if ever.
|
The output (including the first lines) contains important debugging information. Issues without the full output are often not reproducible and therefore do not get solved in short order, if ever.
|
||||||
|
|
||||||
@ -14,13 +28,13 @@ So please elaborate on what feature you are requesting, or what bug you want to
|
|||||||
- How it could be fixed
|
- How it could be fixed
|
||||||
- How your proposed solution would look like
|
- How your proposed solution would look like
|
||||||
|
|
||||||
If your report is shorter than two lines, it is almost certainly missing some of these, which makes it hard for us to respond to it. We're often too polite to close the issue outright, but the missing info makes misinterpretation likely. As a commiter myself, I often get frustrated by these issues, since the only possible way for me to move forward on them is to ask for clarification over and over.
|
If your report is shorter than two lines, it is almost certainly missing some of these, which makes it hard for us to respond to it. We're often too polite to close the issue outright, but the missing info makes misinterpretation likely. As a committer myself, I often get frustrated by these issues, since the only possible way for me to move forward on them is to ask for clarification over and over.
|
||||||
|
|
||||||
For bug reports, this means that your report should contain the *complete* output of youtube-dl when called with the `-v` flag. The error message you get for (most) bugs even says so, but you would not believe how many of our bug reports do not contain this information.
|
For bug reports, this means that your report should contain the *complete* output of youtube-dl when called with the `-v` flag. The error message you get for (most) bugs even says so, but you would not believe how many of our bug reports do not contain this information.
|
||||||
|
|
||||||
If your server has multiple IPs or you suspect censorship, adding `--call-home` may be a good idea to get more diagnostics. If the error is `ERROR: Unable to extract ...` and you cannot reproduce it from multiple countries, add `--dump-pages` (warning: this will yield a rather large output, redirect it to the file `log.txt` by adding `>log.txt 2>&1` to your command-line) or upload the `.dump` files you get when you add `--write-pages` [somewhere](https://gist.github.com/).
|
If your server has multiple IPs or you suspect censorship, adding `--call-home` may be a good idea to get more diagnostics. If the error is `ERROR: Unable to extract ...` and you cannot reproduce it from multiple countries, add `--dump-pages` (warning: this will yield a rather large output, redirect it to the file `log.txt` by adding `>log.txt 2>&1` to your command-line) or upload the `.dump` files you get when you add `--write-pages` [somewhere](https://gist.github.com/).
|
||||||
|
|
||||||
**Site support requests must contain an example URL**. An example URL is a URL you might want to download, like http://www.youtube.com/watch?v=BaW_jenozKc . There should be an obvious video present. Except under very special circumstances, the main page of a video service (e.g. http://www.youtube.com/ ) is *not* an example URL.
|
**Site support requests must contain an example URL**. An example URL is a URL you might want to download, like `http://www.youtube.com/watch?v=BaW_jenozKc`. There should be an obvious video present. Except under very special circumstances, the main page of a video service (e.g. `http://www.youtube.com/`) is *not* an example URL.
|
||||||
|
|
||||||
### Are you using the latest version?
|
### Are you using the latest version?
|
||||||
|
|
||||||
@ -28,7 +42,7 @@ Before reporting any issue, type `youtube-dl -U`. This should report that you're
|
|||||||
|
|
||||||
### Is the issue already documented?
|
### Is the issue already documented?
|
||||||
|
|
||||||
Make sure that someone has not already opened the issue you're trying to open. Search at the top of the window or at https://github.com/rg3/youtube-dl/search?type=Issues . If there is an issue, feel free to write something along the lines of "This affects me as well, with version 2015.01.01. Here is some more information on the issue: ...". While some issues may be old, a new post into them often spurs rapid activity.
|
Make sure that someone has not already opened the issue you're trying to open. Search at the top of the window or browse the [GitHub Issues](https://github.com/rg3/youtube-dl/search?type=Issues) of this repository. If there is an issue, feel free to write something along the lines of "This affects me as well, with version 2015.01.01. Here is some more information on the issue: ...". While some issues may be old, a new post into them often spurs rapid activity.
|
||||||
|
|
||||||
### Why are existing options not enough?
|
### Why are existing options not enough?
|
||||||
|
|
||||||
@ -78,7 +92,9 @@ If you want to create a build of youtube-dl yourself, you'll need
|
|||||||
|
|
||||||
### Adding support for a new site
|
### Adding support for a new site
|
||||||
|
|
||||||
If you want to add support for a new site, you can follow this quick list (assuming your service is called `yourextractor`):
|
If you want to add support for a new site, first of all **make sure** this site is **not dedicated to [copyright infringement](#can-you-add-support-for-this-anime-video-site-or-site-which-shows-current-movies-for-free)**. youtube-dl does **not support** such sites thus pull requests adding support for them **will be rejected**.
|
||||||
|
|
||||||
|
After you have ensured this site is distributing it's content legally, you can follow this quick list (assuming your service is called `yourextractor`):
|
||||||
|
|
||||||
1. [Fork this repository](https://github.com/rg3/youtube-dl/fork)
|
1. [Fork this repository](https://github.com/rg3/youtube-dl/fork)
|
||||||
2. Check out the source code with `git clone git@github.com:YOUR_GITHUB_USERNAME/youtube-dl.git`
|
2. Check out the source code with `git clone git@github.com:YOUR_GITHUB_USERNAME/youtube-dl.git`
|
||||||
@ -126,16 +142,17 @@ If you want to add support for a new site, you can follow this quick list (assum
|
|||||||
```
|
```
|
||||||
5. Add an import in [`youtube_dl/extractor/__init__.py`](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/__init__.py).
|
5. Add an import in [`youtube_dl/extractor/__init__.py`](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/__init__.py).
|
||||||
6. Run `python test/test_download.py TestDownload.test_YourExtractor`. This *should fail* at first, but you can continually re-run it until you're done. If you decide to add more than one test, then rename ``_TEST`` to ``_TESTS`` and make it into a list of dictionaries. The tests will then be named `TestDownload.test_YourExtractor`, `TestDownload.test_YourExtractor_1`, `TestDownload.test_YourExtractor_2`, etc.
|
6. Run `python test/test_download.py TestDownload.test_YourExtractor`. This *should fail* at first, but you can continually re-run it until you're done. If you decide to add more than one test, then rename ``_TEST`` to ``_TESTS`` and make it into a list of dictionaries. The tests will then be named `TestDownload.test_YourExtractor`, `TestDownload.test_YourExtractor_1`, `TestDownload.test_YourExtractor_2`, etc.
|
||||||
7. Have a look at [`youtube_dl/extractor/common.py`](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/common.py) for possible helper methods and a [detailed description of what your extractor should and may return](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/common.py#L62-L200). Add tests and code for as many as you want.
|
7. Have a look at [`youtube_dl/extractor/common.py`](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/common.py) for possible helper methods and a [detailed description of what your extractor should and may return](https://github.com/rg3/youtube-dl/blob/58525c94d547be1c8167d16c298bdd75506db328/youtube_dl/extractor/common.py#L68-L226). Add tests and code for as many as you want.
|
||||||
8. If you can, check the code with [flake8](https://pypi.python.org/pypi/flake8).
|
8. Keep in mind that the only mandatory fields in info dict for successful extraction process are `id`, `title` and either `url` or `formats`, i.e. these are the critical data the extraction does not make any sense without. This means that [any field](https://github.com/rg3/youtube-dl/blob/58525c94d547be1c8167d16c298bdd75506db328/youtube_dl/extractor/common.py#L138-L226) apart from aforementioned mandatory ones should be treated **as optional** and extraction should be **tolerate** to situations when sources for these fields can potentially be unavailable (even if they always available at the moment) and **future-proof** in order not to break the extraction of general purpose mandatory fields. For example, if you have some intermediate dict `meta` that is a source of metadata and it has a key `summary` that you want to extract and put into resulting info dict as `description`, you should be ready that this key may be missing from the `meta` dict, i.e. you should extract it as `meta.get('summary')` and not `meta['summary']`. Similarly, you should pass `fatal=False` when extracting data from a webpage with `_search_regex/_html_search_regex`.
|
||||||
9. When the tests pass, [add](http://git-scm.com/docs/git-add) the new files and [commit](http://git-scm.com/docs/git-commit) them and [push](http://git-scm.com/docs/git-push) the result, like this:
|
9. Check the code with [flake8](https://pypi.python.org/pypi/flake8).
|
||||||
|
10. When the tests pass, [add](http://git-scm.com/docs/git-add) the new files and [commit](http://git-scm.com/docs/git-commit) them and [push](http://git-scm.com/docs/git-push) the result, like this:
|
||||||
|
|
||||||
$ git add youtube_dl/extractor/__init__.py
|
$ git add youtube_dl/extractor/__init__.py
|
||||||
$ git add youtube_dl/extractor/yourextractor.py
|
$ git add youtube_dl/extractor/yourextractor.py
|
||||||
$ git commit -m '[yourextractor] Add new extractor'
|
$ git commit -m '[yourextractor] Add new extractor'
|
||||||
$ git push origin yourextractor
|
$ git push origin yourextractor
|
||||||
|
|
||||||
10. Finally, [create a pull request](https://help.github.com/articles/creating-a-pull-request). We'll then review and merge it.
|
11. Finally, [create a pull request](https://help.github.com/articles/creating-a-pull-request). We'll then review and merge it.
|
||||||
|
|
||||||
In any case, thank you very much for your contributions!
|
In any case, thank you very much for your contributions!
|
||||||
|
|
||||||
|
17
Makefile
17
Makefile
@ -3,6 +3,7 @@ all: youtube-dl README.md CONTRIBUTING.md README.txt youtube-dl.1 youtube-dl.bas
|
|||||||
clean:
|
clean:
|
||||||
rm -rf youtube-dl.1.temp.md youtube-dl.1 youtube-dl.bash-completion README.txt MANIFEST build/ dist/ .coverage cover/ youtube-dl.tar.gz youtube-dl.zsh youtube-dl.fish *.dump *.part *.info.json *.mp4 *.flv *.mp3 *.avi CONTRIBUTING.md.tmp youtube-dl youtube-dl.exe
|
rm -rf youtube-dl.1.temp.md youtube-dl.1 youtube-dl.bash-completion README.txt MANIFEST build/ dist/ .coverage cover/ youtube-dl.tar.gz youtube-dl.zsh youtube-dl.fish *.dump *.part *.info.json *.mp4 *.flv *.mp3 *.avi CONTRIBUTING.md.tmp youtube-dl youtube-dl.exe
|
||||||
find . -name "*.pyc" -delete
|
find . -name "*.pyc" -delete
|
||||||
|
find . -name "*.class" -delete
|
||||||
|
|
||||||
PREFIX ?= /usr/local
|
PREFIX ?= /usr/local
|
||||||
BINDIR ?= $(PREFIX)/bin
|
BINDIR ?= $(PREFIX)/bin
|
||||||
@ -44,7 +45,7 @@ test:
|
|||||||
ot: offlinetest
|
ot: offlinetest
|
||||||
|
|
||||||
offlinetest: codetest
|
offlinetest: codetest
|
||||||
nosetests --verbose test --exclude test_download.py --exclude test_age_restriction.py --exclude test_subtitles.py --exclude test_write_annotations.py --exclude test_youtube_lists.py
|
$(PYTHON) -m nose --verbose test --exclude test_download.py --exclude test_age_restriction.py --exclude test_subtitles.py --exclude test_write_annotations.py --exclude test_youtube_lists.py --exclude test_iqiyi_sdk_interpreter.py
|
||||||
|
|
||||||
tar: youtube-dl.tar.gz
|
tar: youtube-dl.tar.gz
|
||||||
|
|
||||||
@ -61,34 +62,34 @@ youtube-dl: youtube_dl/*.py youtube_dl/*/*.py
|
|||||||
chmod a+x youtube-dl
|
chmod a+x youtube-dl
|
||||||
|
|
||||||
README.md: youtube_dl/*.py youtube_dl/*/*.py
|
README.md: youtube_dl/*.py youtube_dl/*/*.py
|
||||||
COLUMNS=80 python youtube_dl/__main__.py --help | python devscripts/make_readme.py
|
COLUMNS=80 $(PYTHON) youtube_dl/__main__.py --help | $(PYTHON) devscripts/make_readme.py
|
||||||
|
|
||||||
CONTRIBUTING.md: README.md
|
CONTRIBUTING.md: README.md
|
||||||
python devscripts/make_contributing.py README.md CONTRIBUTING.md
|
$(PYTHON) devscripts/make_contributing.py README.md CONTRIBUTING.md
|
||||||
|
|
||||||
supportedsites:
|
supportedsites:
|
||||||
python devscripts/make_supportedsites.py docs/supportedsites.md
|
$(PYTHON) devscripts/make_supportedsites.py docs/supportedsites.md
|
||||||
|
|
||||||
README.txt: README.md
|
README.txt: README.md
|
||||||
pandoc -f markdown -t plain README.md -o README.txt
|
pandoc -f markdown -t plain README.md -o README.txt
|
||||||
|
|
||||||
youtube-dl.1: README.md
|
youtube-dl.1: README.md
|
||||||
python devscripts/prepare_manpage.py >youtube-dl.1.temp.md
|
$(PYTHON) devscripts/prepare_manpage.py >youtube-dl.1.temp.md
|
||||||
pandoc -s -f markdown -t man youtube-dl.1.temp.md -o youtube-dl.1
|
pandoc -s -f markdown -t man youtube-dl.1.temp.md -o youtube-dl.1
|
||||||
rm -f youtube-dl.1.temp.md
|
rm -f youtube-dl.1.temp.md
|
||||||
|
|
||||||
youtube-dl.bash-completion: youtube_dl/*.py youtube_dl/*/*.py devscripts/bash-completion.in
|
youtube-dl.bash-completion: youtube_dl/*.py youtube_dl/*/*.py devscripts/bash-completion.in
|
||||||
python devscripts/bash-completion.py
|
$(PYTHON) devscripts/bash-completion.py
|
||||||
|
|
||||||
bash-completion: youtube-dl.bash-completion
|
bash-completion: youtube-dl.bash-completion
|
||||||
|
|
||||||
youtube-dl.zsh: youtube_dl/*.py youtube_dl/*/*.py devscripts/zsh-completion.in
|
youtube-dl.zsh: youtube_dl/*.py youtube_dl/*/*.py devscripts/zsh-completion.in
|
||||||
python devscripts/zsh-completion.py
|
$(PYTHON) devscripts/zsh-completion.py
|
||||||
|
|
||||||
zsh-completion: youtube-dl.zsh
|
zsh-completion: youtube-dl.zsh
|
||||||
|
|
||||||
youtube-dl.fish: youtube_dl/*.py youtube_dl/*/*.py devscripts/fish-completion.in
|
youtube-dl.fish: youtube_dl/*.py youtube_dl/*/*.py devscripts/fish-completion.in
|
||||||
python devscripts/fish-completion.py
|
$(PYTHON) devscripts/fish-completion.py
|
||||||
|
|
||||||
fish-completion: youtube-dl.fish
|
fish-completion: youtube-dl.fish
|
||||||
|
|
||||||
|
275
README.md
275
README.md
@ -35,7 +35,7 @@ You can also use pip:
|
|||||||
|
|
||||||
sudo pip install youtube-dl
|
sudo pip install youtube-dl
|
||||||
|
|
||||||
Alternatively, refer to the [developer instructions](#developer-instructions) for how to check out and work with the git repository. For further options, including PGP signatures, see https://rg3.github.io/youtube-dl/download.html .
|
Alternatively, refer to the [developer instructions](#developer-instructions) for how to check out and work with the git repository. For further options, including PGP signatures, see the [youtube-dl Download Page](https://rg3.github.io/youtube-dl/download.html).
|
||||||
|
|
||||||
# DESCRIPTION
|
# DESCRIPTION
|
||||||
**youtube-dl** is a small command-line program to download videos from
|
**youtube-dl** is a small command-line program to download videos from
|
||||||
@ -80,6 +80,8 @@ which means you can modify it, redistribute it or use it however you like.
|
|||||||
on Windows)
|
on Windows)
|
||||||
--flat-playlist Do not extract the videos of a playlist,
|
--flat-playlist Do not extract the videos of a playlist,
|
||||||
only list them.
|
only list them.
|
||||||
|
--mark-watched Mark videos watched (YouTube only)
|
||||||
|
--no-mark-watched Do not mark videos watched (YouTube only)
|
||||||
--no-color Do not emit color codes in output
|
--no-color Do not emit color codes in output
|
||||||
|
|
||||||
## Network Options:
|
## Network Options:
|
||||||
@ -173,9 +175,13 @@ 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,avconv,axel,curl,ffmpeg,httpie,wget
|
||||||
--external-downloader-args ARGS Give these arguments to the external
|
--external-downloader-args ARGS Give these arguments to the external
|
||||||
downloader
|
downloader
|
||||||
|
|
||||||
@ -319,7 +325,8 @@ which means you can modify it, redistribute it or use it however you like.
|
|||||||
--all-formats Download all available video formats
|
--all-formats Download all available video formats
|
||||||
--prefer-free-formats Prefer free video formats unless a specific
|
--prefer-free-formats Prefer free video formats unless a specific
|
||||||
one is requested
|
one is requested
|
||||||
-F, --list-formats List all available formats
|
-F, --list-formats List all available formats of requested
|
||||||
|
videos
|
||||||
--youtube-skip-dash-manifest Do not download the DASH manifests and
|
--youtube-skip-dash-manifest Do not download the DASH manifests and
|
||||||
related data on YouTube videos
|
related data on YouTube videos
|
||||||
--merge-output-format FORMAT If a merge is required (e.g.
|
--merge-output-format FORMAT If a merge is required (e.g.
|
||||||
@ -329,8 +336,8 @@ which means you can modify it, redistribute it or use it however you like.
|
|||||||
|
|
||||||
## Subtitle Options:
|
## Subtitle Options:
|
||||||
--write-sub Write subtitle file
|
--write-sub Write subtitle file
|
||||||
--write-auto-sub Write automatic subtitle file (YouTube
|
--write-auto-sub Write automatically generated subtitle file
|
||||||
only)
|
(YouTube only)
|
||||||
--all-subs Download all the available subtitles of the
|
--all-subs Download all the available subtitles of the
|
||||||
video
|
video
|
||||||
--list-subs List all available subtitles for the video
|
--list-subs List all available subtitles for the video
|
||||||
@ -338,8 +345,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
|
||||||
@ -399,21 +406,26 @@ which means you can modify it, redistribute it or use it however you like.
|
|||||||
downloading, similar to find's -exec
|
downloading, similar to find's -exec
|
||||||
syntax. Example: --exec 'adb push {}
|
syntax. Example: --exec 'adb push {}
|
||||||
/sdcard/Music/ && rm {}'
|
/sdcard/Music/ && rm {}'
|
||||||
--convert-subtitles FORMAT Convert the subtitles to other format
|
--convert-subs FORMAT Convert the subtitles to other format
|
||||||
(currently supported: srt|ass|vtt)
|
(currently supported: srt|ass|vtt)
|
||||||
|
|
||||||
# CONFIGURATION
|
# CONFIGURATION
|
||||||
|
|
||||||
You can configure youtube-dl by placing any supported command line option to a configuration file. On Linux, the system wide configuration file is located at `/etc/youtube-dl.conf` and the user wide configuration file at `~/.config/youtube-dl/config`. On Windows, the user wide configuration file locations are `%APPDATA%\youtube-dl\config.txt` or `C:\Users\<user name>\youtube-dl.conf`. For example, with the following configuration file youtube-dl will always extract the audio, not copy the mtime and use a proxy:
|
You can configure youtube-dl by placing any supported command line option to a configuration file. On Linux, the system wide configuration file is located at `/etc/youtube-dl.conf` and the user wide configuration file at `~/.config/youtube-dl/config`. On Windows, the user wide configuration file locations are `%APPDATA%\youtube-dl\config.txt` or `C:\Users\<user name>\youtube-dl.conf`.
|
||||||
|
|
||||||
|
For example, with the following configuration file youtube-dl will always extract the audio, not copy the mtime, use a proxy and save all videos under `Movies` directory in your home directory:
|
||||||
```
|
```
|
||||||
--extract-audio
|
-x
|
||||||
--no-mtime
|
--no-mtime
|
||||||
--proxy 127.0.0.1:3128
|
--proxy 127.0.0.1:3128
|
||||||
|
-o ~/Movies/%(title)s.%(ext)s
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Note that options in configuration file are just the same options aka switches used in regular command line calls thus there **must be no whitespace** after `-` or `--`, e.g. `-o` or `--proxy` but not `- o` or `-- proxy`.
|
||||||
|
|
||||||
You can use `--ignore-config` if you want to disable the configuration file for a particular youtube-dl run.
|
You can use `--ignore-config` if you want to disable the configuration file for a particular youtube-dl run.
|
||||||
|
|
||||||
### Authentication with `.netrc` file ###
|
### Authentication with `.netrc` file
|
||||||
|
|
||||||
You may also want to configure automatic credentials storage for extractors that support authentication (by providing login and password with `--username` and `--password`) in order not to pass credentials as command line arguments on every youtube-dl execution and prevent tracking plain text passwords in the shell command history. You can achieve this using a [`.netrc` file](http://stackoverflow.com/tags/.netrc/info) on per extractor basis. For that you will need to create a`.netrc` file in your `$HOME` and restrict permissions to read/write by you only:
|
You may also want to configure automatic credentials storage for extractors that support authentication (by providing login and password with `--username` and `--password`) in order not to pass credentials as command line arguments on every youtube-dl execution and prevent tracking plain text passwords in the shell command history. You can achieve this using a [`.netrc` file](http://stackoverflow.com/tags/.netrc/info) on per extractor basis. For that you will need to create a`.netrc` file in your `$HOME` and restrict permissions to read/write by you only:
|
||||||
```
|
```
|
||||||
@ -435,43 +447,189 @@ On Windows you may also need to setup the `%HOME%` environment variable manually
|
|||||||
|
|
||||||
# OUTPUT TEMPLATE
|
# OUTPUT TEMPLATE
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
- `id`: The sequence will be replaced by the video identifier.
|
**tl;dr:** [navigate me to examples](#output-template-examples).
|
||||||
- `url`: The sequence will be replaced by the video URL.
|
|
||||||
- `uploader`: The sequence will be replaced by the nickname of the person who uploaded the video.
|
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:
|
||||||
- `upload_date`: The sequence will be replaced by the upload date in YYYYMMDD format.
|
|
||||||
- `title`: The sequence will be replaced by the video title.
|
- `id`: Video identifier
|
||||||
- `ext`: The sequence will be replaced by the appropriate extension (like flv or mp4).
|
- `title`: Video title
|
||||||
- `epoch`: The sequence will be replaced by the Unix epoch when creating the file.
|
- `url`: Video URL
|
||||||
- `autonumber`: The sequence will be replaced by a five-digit number that will be increased with each download, starting at zero.
|
- `ext`: Video filename extension
|
||||||
- `playlist`: The sequence will be replaced by the name or the id of the playlist that contains the video.
|
- `alt_title`: A secondary title of the video
|
||||||
- `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.
|
- `display_id`: An alternative identifier for the video
|
||||||
- `format_id`: The sequence will be replaced by the format code specified by `--format`.
|
- `uploader`: Full name of the video uploader
|
||||||
- `duration`: The sequence will be replaced by the length of the video in seconds.
|
- `license`: License name the video is licensed under
|
||||||
|
- `creator`: The main artist who created the video
|
||||||
|
- `release_date`: The date (YYYYMMDD) when the video was released
|
||||||
|
- `timestamp`: UNIX timestamp of the moment the video became available
|
||||||
|
- `upload_date`: Video upload date (YYYYMMDD)
|
||||||
|
- `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:
|
||||||
|
|
||||||
|
#### Output template 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 all playlists of YouTube channel/user keeping each playlist in separate directory:
|
||||||
|
$ youtube-dl -o '%(uploader)s/%(playlist)s/%(playlist_index)s - %(title)s.%(ext)s' https://www.youtube.com/user/TheLinuxFoundation/playlists
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
**tl;dr:** [navigate me to examples](#format-selection-examples).
|
||||||
|
|
||||||
|
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](https://github.com/rg3/youtube-dl/issues/5447), [#5456](https://github.com/rg3/youtube-dl/issues/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.
|
||||||
|
|
||||||
|
#### Format selection 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:
|
||||||
@ -534,6 +692,12 @@ Most people asking this question are not aware that youtube-dl now defaults to d
|
|||||||
|
|
||||||
Apparently YouTube requires you to pass a CAPTCHA test if you download too much. We're [considering to provide a way to let you solve the CAPTCHA](https://github.com/rg3/youtube-dl/issues/154), but at the moment, your best course of action is pointing a webbrowser to the youtube URL, solving the CAPTCHA, and restart youtube-dl.
|
Apparently YouTube requires you to pass a CAPTCHA test if you download too much. We're [considering to provide a way to let you solve the CAPTCHA](https://github.com/rg3/youtube-dl/issues/154), but at the moment, your best course of action is pointing a webbrowser to the youtube URL, solving the CAPTCHA, and restart youtube-dl.
|
||||||
|
|
||||||
|
### Do I need any other programs?
|
||||||
|
|
||||||
|
youtube-dl works fine on its own on most sites. However, if you want to convert video/audio, you'll need [avconv](https://libav.org/) or [ffmpeg](https://www.ffmpeg.org/). On some sites - most notably YouTube - videos can be retrieved in a higher quality format without sound. youtube-dl will detect whether avconv/ffmpeg is present and automatically pick the best option.
|
||||||
|
|
||||||
|
Videos or video formats streamed via RTMP protocol can only be downloaded when [rtmpdump](https://rtmpdump.mplayerhq.hu/) is installed. Downloading MMS and RTSP videos requires either [mplayer](http://mplayerhq.hu/) or [mpv](https://mpv.io/) to be installed.
|
||||||
|
|
||||||
### I have downloaded a video but how can I play it?
|
### I have downloaded a video but how can I play it?
|
||||||
|
|
||||||
Once the video is fully downloaded, use any video player, such as [vlc](http://www.videolan.org) or [mplayer](http://www.mplayerhq.hu/).
|
Once the video is fully downloaded, use any video player, such as [vlc](http://www.videolan.org) or [mplayer](http://www.mplayerhq.hu/).
|
||||||
@ -552,11 +716,11 @@ If you want to play the video on a machine that is not running youtube-dl, you c
|
|||||||
|
|
||||||
YouTube has switched to a new video info format in July 2011 which is not supported by old versions of youtube-dl. See [above](#how-do-i-update-youtube-dl) for how to update youtube-dl.
|
YouTube has switched to a new video info format in July 2011 which is not supported by old versions of youtube-dl. See [above](#how-do-i-update-youtube-dl) for how to update youtube-dl.
|
||||||
|
|
||||||
### ERROR: unable to download video ###
|
### ERROR: unable to download video
|
||||||
|
|
||||||
YouTube requires an additional signature since September 2012 which is not supported by old versions of youtube-dl. See [above](#how-do-i-update-youtube-dl) for how to update youtube-dl.
|
YouTube requires an additional signature since September 2012 which is not supported by old versions of youtube-dl. See [above](#how-do-i-update-youtube-dl) for how to update youtube-dl.
|
||||||
|
|
||||||
### Video URL contains an ampersand and I'm getting some strange output `[1] 2839` or `'v' is not recognized as an internal or external command` ###
|
### Video URL contains an ampersand and I'm getting some strange output `[1] 2839` or `'v' is not recognized as an internal or external command`
|
||||||
|
|
||||||
That's actually the output from your shell. Since ampersand is one of the special shell characters it's interpreted by the shell preventing you from passing the whole URL to youtube-dl. To disable your shell from interpreting the ampersands (or any other special characters) you have to either put the whole URL in quotes or escape them with a backslash (which approach will work depends on your shell).
|
That's actually the output from your shell. Since ampersand is one of the special shell characters it's interpreted by the shell preventing you from passing the whole URL to youtube-dl. To disable your shell from interpreting the ampersands (or any other special characters) you have to either put the whole URL in quotes or escape them with a backslash (which approach will work depends on your shell).
|
||||||
|
|
||||||
@ -580,7 +744,7 @@ In February 2015, the new YouTube player contained a character sequence in a str
|
|||||||
|
|
||||||
These two error codes indicate that the service is blocking your IP address because of overuse. Contact the service and ask them to unblock your IP address, or - if you have acquired a whitelisted IP address already - use the [`--proxy` or `--source-address` options](#network-options) to select another IP address.
|
These two error codes indicate that the service is blocking your IP address because of overuse. Contact the service and ask them to unblock your IP address, or - if you have acquired a whitelisted IP address already - use the [`--proxy` or `--source-address` options](#network-options) to select another IP address.
|
||||||
|
|
||||||
### SyntaxError: Non-ASCII character ###
|
### SyntaxError: Non-ASCII character
|
||||||
|
|
||||||
The error
|
The error
|
||||||
|
|
||||||
@ -591,7 +755,7 @@ means you're using an outdated version of Python. Please update to Python 2.6 or
|
|||||||
|
|
||||||
### What is this binary file? Where has the code gone?
|
### What is this binary file? Where has the code gone?
|
||||||
|
|
||||||
Since June 2012 (#342) youtube-dl is packed as an executable zipfile, simply unzip it (might need renaming to `youtube-dl.zip` first on some systems) or clone the git repository, as laid out above. If you modify the code, you can run it by executing the `__main__.py` file. To recompile the executable, run `make youtube-dl`.
|
Since June 2012 ([#342](https://github.com/rg3/youtube-dl/issues/342)) youtube-dl is packed as an executable zipfile, simply unzip it (might need renaming to `youtube-dl.zip` first on some systems) or clone the git repository, as laid out above. If you modify the code, you can run it by executing the `__main__.py` file. To recompile the executable, run `make youtube-dl`.
|
||||||
|
|
||||||
### The exe throws a *Runtime error from Visual C++*
|
### The exe throws a *Runtime error from Visual C++*
|
||||||
|
|
||||||
@ -620,7 +784,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?
|
||||||
|
|
||||||
@ -674,7 +838,9 @@ If you want to create a build of youtube-dl yourself, you'll need
|
|||||||
|
|
||||||
### Adding support for a new site
|
### Adding support for a new site
|
||||||
|
|
||||||
If you want to add support for a new site, you can follow this quick list (assuming your service is called `yourextractor`):
|
If you want to add support for a new site, first of all **make sure** this site is **not dedicated to [copyright infringement](#can-you-add-support-for-this-anime-video-site-or-site-which-shows-current-movies-for-free)**. youtube-dl does **not support** such sites thus pull requests adding support for them **will be rejected**.
|
||||||
|
|
||||||
|
After you have ensured this site is distributing it's content legally, you can follow this quick list (assuming your service is called `yourextractor`):
|
||||||
|
|
||||||
1. [Fork this repository](https://github.com/rg3/youtube-dl/fork)
|
1. [Fork this repository](https://github.com/rg3/youtube-dl/fork)
|
||||||
2. Check out the source code with `git clone git@github.com:YOUR_GITHUB_USERNAME/youtube-dl.git`
|
2. Check out the source code with `git clone git@github.com:YOUR_GITHUB_USERNAME/youtube-dl.git`
|
||||||
@ -722,16 +888,17 @@ If you want to add support for a new site, you can follow this quick list (assum
|
|||||||
```
|
```
|
||||||
5. Add an import in [`youtube_dl/extractor/__init__.py`](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/__init__.py).
|
5. Add an import in [`youtube_dl/extractor/__init__.py`](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/__init__.py).
|
||||||
6. Run `python test/test_download.py TestDownload.test_YourExtractor`. This *should fail* at first, but you can continually re-run it until you're done. If you decide to add more than one test, then rename ``_TEST`` to ``_TESTS`` and make it into a list of dictionaries. The tests will then be named `TestDownload.test_YourExtractor`, `TestDownload.test_YourExtractor_1`, `TestDownload.test_YourExtractor_2`, etc.
|
6. Run `python test/test_download.py TestDownload.test_YourExtractor`. This *should fail* at first, but you can continually re-run it until you're done. If you decide to add more than one test, then rename ``_TEST`` to ``_TESTS`` and make it into a list of dictionaries. The tests will then be named `TestDownload.test_YourExtractor`, `TestDownload.test_YourExtractor_1`, `TestDownload.test_YourExtractor_2`, etc.
|
||||||
7. Have a look at [`youtube_dl/extractor/common.py`](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/common.py) for possible helper methods and a [detailed description of what your extractor should and may return](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/common.py#L62-L200). Add tests and code for as many as you want.
|
7. Have a look at [`youtube_dl/extractor/common.py`](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/extractor/common.py) for possible helper methods and a [detailed description of what your extractor should and may return](https://github.com/rg3/youtube-dl/blob/58525c94d547be1c8167d16c298bdd75506db328/youtube_dl/extractor/common.py#L68-L226). Add tests and code for as many as you want.
|
||||||
8. If you can, check the code with [flake8](https://pypi.python.org/pypi/flake8).
|
8. Keep in mind that the only mandatory fields in info dict for successful extraction process are `id`, `title` and either `url` or `formats`, i.e. these are the critical data the extraction does not make any sense without. This means that [any field](https://github.com/rg3/youtube-dl/blob/58525c94d547be1c8167d16c298bdd75506db328/youtube_dl/extractor/common.py#L138-L226) apart from aforementioned mandatory ones should be treated **as optional** and extraction should be **tolerate** to situations when sources for these fields can potentially be unavailable (even if they always available at the moment) and **future-proof** in order not to break the extraction of general purpose mandatory fields. For example, if you have some intermediate dict `meta` that is a source of metadata and it has a key `summary` that you want to extract and put into resulting info dict as `description`, you should be ready that this key may be missing from the `meta` dict, i.e. you should extract it as `meta.get('summary')` and not `meta['summary']`. Similarly, you should pass `fatal=False` when extracting data from a webpage with `_search_regex/_html_search_regex`.
|
||||||
9. When the tests pass, [add](http://git-scm.com/docs/git-add) the new files and [commit](http://git-scm.com/docs/git-commit) them and [push](http://git-scm.com/docs/git-push) the result, like this:
|
9. Check the code with [flake8](https://pypi.python.org/pypi/flake8).
|
||||||
|
10. When the tests pass, [add](http://git-scm.com/docs/git-add) the new files and [commit](http://git-scm.com/docs/git-commit) them and [push](http://git-scm.com/docs/git-push) the result, like this:
|
||||||
|
|
||||||
$ git add youtube_dl/extractor/__init__.py
|
$ git add youtube_dl/extractor/__init__.py
|
||||||
$ git add youtube_dl/extractor/yourextractor.py
|
$ git add youtube_dl/extractor/yourextractor.py
|
||||||
$ git commit -m '[yourextractor] Add new extractor'
|
$ git commit -m '[yourextractor] Add new extractor'
|
||||||
$ git push origin yourextractor
|
$ git push origin yourextractor
|
||||||
|
|
||||||
10. Finally, [create a pull request](https://help.github.com/articles/creating-a-pull-request). We'll then review and merge it.
|
11. Finally, [create a pull request](https://help.github.com/articles/creating-a-pull-request). We'll then review and merge it.
|
||||||
|
|
||||||
In any case, thank you very much for your contributions!
|
In any case, thank you very much for your contributions!
|
||||||
|
|
||||||
@ -750,7 +917,7 @@ with youtube_dl.YoutubeDL(ydl_opts) as ydl:
|
|||||||
ydl.download(['http://www.youtube.com/watch?v=BaW_jenozKc'])
|
ydl.download(['http://www.youtube.com/watch?v=BaW_jenozKc'])
|
||||||
```
|
```
|
||||||
|
|
||||||
Most likely, you'll want to use various options. For a list of what can be done, have a look at [youtube_dl/YoutubeDL.py](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/YoutubeDL.py#L117-L265). For a start, if you want to intercept youtube-dl's output, set a `logger` object.
|
Most likely, you'll want to use various options. For a list of what can be done, have a look at [`youtube_dl/YoutubeDL.py`](https://github.com/rg3/youtube-dl/blob/master/youtube_dl/YoutubeDL.py#L121-L269). For a start, if you want to intercept youtube-dl's output, set a `logger` object.
|
||||||
|
|
||||||
Here's a more complete example of a program that outputs only errors (and a short message after the download is finished), and downloads/converts the video to an mp3 file:
|
Here's a more complete example of a program that outputs only errors (and a short message after the download is finished), and downloads/converts the video to an mp3 file:
|
||||||
|
|
||||||
@ -791,9 +958,23 @@ with youtube_dl.YoutubeDL(ydl_opts) as ydl:
|
|||||||
|
|
||||||
# BUGS
|
# BUGS
|
||||||
|
|
||||||
Bugs and suggestions should be reported at: <https://github.com/rg3/youtube-dl/issues> . Unless you were prompted so or there is another pertinent reason (e.g. GitHub fails to accept the bug report), please do not send bug reports via personal email. For discussions, join us in the irc channel #youtube-dl on freenode.
|
Bugs and suggestions should be reported at: <https://github.com/rg3/youtube-dl/issues>. Unless you were prompted so or there is another pertinent reason (e.g. GitHub fails to accept the bug report), please do not send bug reports via personal email. For discussions, join us in the IRC channel [#youtube-dl](irc://chat.freenode.net/#youtube-dl) on freenode ([webchat](http://webchat.freenode.net/?randomnick=1&channels=youtube-dl)).
|
||||||
|
|
||||||
**Please include the full output of youtube-dl when run with `-v`**.
|
**Please include the full output of youtube-dl when run with `-v`**, i.e. **add** `-v` flag to **your command line**, copy the **whole** output and post it in the issue body wrapped in \`\`\` for better formatting. It should look similar to this:
|
||||||
|
```
|
||||||
|
$ youtube-dl -v <your command line>
|
||||||
|
[debug] System config: []
|
||||||
|
[debug] User config: []
|
||||||
|
[debug] Command-line args: [u'-v', u'http://www.youtube.com/watch?v=BaW_jenozKcj']
|
||||||
|
[debug] Encodings: locale cp1251, fs mbcs, out cp866, pref cp1251
|
||||||
|
[debug] youtube-dl version 2015.12.06
|
||||||
|
[debug] Git HEAD: 135392e
|
||||||
|
[debug] Python version 2.6.6 - Windows-2003Server-5.2.3790-SP2
|
||||||
|
[debug] exe versions: ffmpeg N-75573-g1d0487f, ffprobe N-75573-g1d0487f, rtmpdump 2.4
|
||||||
|
[debug] Proxy map: {}
|
||||||
|
...
|
||||||
|
```
|
||||||
|
**Do not post screenshots of verbose log only plain text is acceptable.**
|
||||||
|
|
||||||
The output (including the first lines) contains important debugging information. Issues without the full output are often not reproducible and therefore do not get solved in short order, if ever.
|
The output (including the first lines) contains important debugging information. Issues without the full output are often not reproducible and therefore do not get solved in short order, if ever.
|
||||||
|
|
||||||
@ -809,13 +990,13 @@ So please elaborate on what feature you are requesting, or what bug you want to
|
|||||||
- How it could be fixed
|
- How it could be fixed
|
||||||
- How your proposed solution would look like
|
- How your proposed solution would look like
|
||||||
|
|
||||||
If your report is shorter than two lines, it is almost certainly missing some of these, which makes it hard for us to respond to it. We're often too polite to close the issue outright, but the missing info makes misinterpretation likely. As a commiter myself, I often get frustrated by these issues, since the only possible way for me to move forward on them is to ask for clarification over and over.
|
If your report is shorter than two lines, it is almost certainly missing some of these, which makes it hard for us to respond to it. We're often too polite to close the issue outright, but the missing info makes misinterpretation likely. As a committer myself, I often get frustrated by these issues, since the only possible way for me to move forward on them is to ask for clarification over and over.
|
||||||
|
|
||||||
For bug reports, this means that your report should contain the *complete* output of youtube-dl when called with the `-v` flag. The error message you get for (most) bugs even says so, but you would not believe how many of our bug reports do not contain this information.
|
For bug reports, this means that your report should contain the *complete* output of youtube-dl when called with the `-v` flag. The error message you get for (most) bugs even says so, but you would not believe how many of our bug reports do not contain this information.
|
||||||
|
|
||||||
If your server has multiple IPs or you suspect censorship, adding `--call-home` may be a good idea to get more diagnostics. If the error is `ERROR: Unable to extract ...` and you cannot reproduce it from multiple countries, add `--dump-pages` (warning: this will yield a rather large output, redirect it to the file `log.txt` by adding `>log.txt 2>&1` to your command-line) or upload the `.dump` files you get when you add `--write-pages` [somewhere](https://gist.github.com/).
|
If your server has multiple IPs or you suspect censorship, adding `--call-home` may be a good idea to get more diagnostics. If the error is `ERROR: Unable to extract ...` and you cannot reproduce it from multiple countries, add `--dump-pages` (warning: this will yield a rather large output, redirect it to the file `log.txt` by adding `>log.txt 2>&1` to your command-line) or upload the `.dump` files you get when you add `--write-pages` [somewhere](https://gist.github.com/).
|
||||||
|
|
||||||
**Site support requests must contain an example URL**. An example URL is a URL you might want to download, like http://www.youtube.com/watch?v=BaW_jenozKc . There should be an obvious video present. Except under very special circumstances, the main page of a video service (e.g. http://www.youtube.com/ ) is *not* an example URL.
|
**Site support requests must contain an example URL**. An example URL is a URL you might want to download, like `http://www.youtube.com/watch?v=BaW_jenozKc`. There should be an obvious video present. Except under very special circumstances, the main page of a video service (e.g. `http://www.youtube.com/`) is *not* an example URL.
|
||||||
|
|
||||||
### Are you using the latest version?
|
### Are you using the latest version?
|
||||||
|
|
||||||
@ -823,7 +1004,7 @@ Before reporting any issue, type `youtube-dl -U`. This should report that you're
|
|||||||
|
|
||||||
### Is the issue already documented?
|
### Is the issue already documented?
|
||||||
|
|
||||||
Make sure that someone has not already opened the issue you're trying to open. Search at the top of the window or at https://github.com/rg3/youtube-dl/search?type=Issues . If there is an issue, feel free to write something along the lines of "This affects me as well, with version 2015.01.01. Here is some more information on the issue: ...". While some issues may be old, a new post into them often spurs rapid activity.
|
Make sure that someone has not already opened the issue you're trying to open. Search at the top of the window or browse the [GitHub Issues](https://github.com/rg3/youtube-dl/search?type=Issues) of this repository. If there is an issue, feel free to write something along the lines of "This affects me as well, with version 2015.01.01. Here is some more information on the issue: ...". While some issues may be old, a new post into them often spurs rapid activity.
|
||||||
|
|
||||||
### Why are existing options not enough?
|
### Why are existing options not enough?
|
||||||
|
|
||||||
@ -853,4 +1034,4 @@ It may sound strange, but some bug reports we receive are completely unrelated t
|
|||||||
|
|
||||||
youtube-dl is released into the public domain by the copyright holders.
|
youtube-dl is released into the public domain by the copyright holders.
|
||||||
|
|
||||||
This README file was originally written by Daniel Bolton (<https://github.com/dbbolton>) and is likewise released into the public domain.
|
This README file was originally written by [Daniel Bolton](https://github.com/dbbolton) and is likewise released into the public domain.
|
||||||
|
@ -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**
|
||||||
@ -15,37 +16,49 @@
|
|||||||
- **abc.net.au**
|
- **abc.net.au**
|
||||||
- **Abc7News**
|
- **Abc7News**
|
||||||
- **AcademicEarth:Course**
|
- **AcademicEarth:Course**
|
||||||
|
- **acast**
|
||||||
|
- **acast:channel**
|
||||||
- **AddAnime**
|
- **AddAnime**
|
||||||
- **AdobeTV**
|
- **AdobeTV**
|
||||||
|
- **AdobeTVChannel**
|
||||||
|
- **AdobeTVShow**
|
||||||
- **AdobeTVVideo**
|
- **AdobeTVVideo**
|
||||||
- **AdultSwim**
|
- **AdultSwim**
|
||||||
- **Aftenposten**
|
- **aenetworks**: A+E Networks: A&E, Lifetime, History.com, FYI Network
|
||||||
- **Aftonbladet**
|
- **Aftonbladet**
|
||||||
- **AirMozilla**
|
- **AirMozilla**
|
||||||
- **AlJazeera**
|
- **AlJazeera**
|
||||||
- **Allocine**
|
- **Allocine**
|
||||||
- **AlphaPorno**
|
- **AlphaPorno**
|
||||||
|
- **AnimeOnDemand**
|
||||||
- **anitube.se**
|
- **anitube.se**
|
||||||
- **AnySex**
|
- **AnySex**
|
||||||
- **Aparat**
|
- **Aparat**
|
||||||
- **AppleConnect**
|
- **AppleConnect**
|
||||||
- **AppleDaily**: 臺灣蘋果日報
|
- **AppleDaily**: 臺灣蘋果日報
|
||||||
- **AppleTrailers**
|
- **appletrailers**
|
||||||
|
- **appletrailers:section**
|
||||||
- **archive.org**: archive.org videos
|
- **archive.org**: archive.org videos
|
||||||
- **ARD**
|
- **ARD**
|
||||||
|
- **ARD:mediathek**: Saarländischer Rundfunk
|
||||||
- **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**
|
||||||
- **arte.tv:embed**
|
- **arte.tv:embed**
|
||||||
- **arte.tv:future**
|
- **arte.tv:future**
|
||||||
|
- **arte.tv:magazine**
|
||||||
- **AtresPlayer**
|
- **AtresPlayer**
|
||||||
- **ATTTechChannel**
|
- **ATTTechChannel**
|
||||||
|
- **AudiMedia**
|
||||||
|
- **AudioBoom**
|
||||||
- **audiomack**
|
- **audiomack**
|
||||||
- **audiomack:album**
|
- **audiomack:album**
|
||||||
- **Azubu**
|
- **Azubu**
|
||||||
|
- **AzubuLive**
|
||||||
- **BaiduVideo**: 百度视频
|
- **BaiduVideo**: 百度视频
|
||||||
- **bambuser**
|
- **bambuser**
|
||||||
- **bambuser:channel**
|
- **bambuser:channel**
|
||||||
@ -58,12 +71,14 @@
|
|||||||
- **Beeg**
|
- **Beeg**
|
||||||
- **BehindKink**
|
- **BehindKink**
|
||||||
- **Bet**
|
- **Bet**
|
||||||
|
- **Bigflix**
|
||||||
- **Bild**: Bild.de
|
- **Bild**: Bild.de
|
||||||
- **BiliBili**
|
- **BiliBili**
|
||||||
|
- **BleacherReport**
|
||||||
|
- **BleacherReportCMS**
|
||||||
- **blinkx**
|
- **blinkx**
|
||||||
- **blip.tv:user**
|
|
||||||
- **BlipTV**
|
|
||||||
- **Bloomberg**
|
- **Bloomberg**
|
||||||
|
- **BokeCC**
|
||||||
- **Bpb**: Bundeszentrale für politische Bildung
|
- **Bpb**: Bundeszentrale für politische Bildung
|
||||||
- **BR**: Bayerischer Rundfunk Mediathek
|
- **BR**: Bayerischer Rundfunk Mediathek
|
||||||
- **Break**
|
- **Break**
|
||||||
@ -75,11 +90,14 @@
|
|||||||
- **BYUtv**
|
- **BYUtv**
|
||||||
- **Camdemy**
|
- **Camdemy**
|
||||||
- **CamdemyFolder**
|
- **CamdemyFolder**
|
||||||
- **Canal13cl**
|
|
||||||
- **canalc2.tv**
|
- **canalc2.tv**
|
||||||
- **Canalplus**: canalplus.fr, piwiplus.fr and d8.tv
|
- **Canalplus**: canalplus.fr, piwiplus.fr and d8.tv
|
||||||
|
- **Canvas**
|
||||||
|
- **CBC**
|
||||||
|
- **CBCPlayer**
|
||||||
- **CBS**
|
- **CBS**
|
||||||
- **CBSNews**: CBS News
|
- **CBSNews**: CBS News
|
||||||
|
- **CBSNewsLiveVideo**: CBS News Live Videos
|
||||||
- **CBSSports**
|
- **CBSSports**
|
||||||
- **CeskaTelevize**
|
- **CeskaTelevize**
|
||||||
- **channel9**: Channel 9
|
- **channel9**: Channel 9
|
||||||
@ -92,6 +110,7 @@
|
|||||||
- **Clipfish**
|
- **Clipfish**
|
||||||
- **cliphunter**
|
- **cliphunter**
|
||||||
- **Clipsyndicate**
|
- **Clipsyndicate**
|
||||||
|
- **cloudtime**: CloudTime
|
||||||
- **Cloudy**
|
- **Cloudy**
|
||||||
- **Clubic**
|
- **Clubic**
|
||||||
- **Clyp**
|
- **Clyp**
|
||||||
@ -107,6 +126,7 @@
|
|||||||
- **ComedyCentralShows**: The Daily Show / The Colbert Report
|
- **ComedyCentralShows**: The Daily Show / The Colbert Report
|
||||||
- **CondeNast**: Condé Nast media group: Allure, Architectural Digest, Ars Technica, Bon Appétit, Brides, Condé Nast, Condé Nast Traveler, Details, Epicurious, GQ, Glamour, Golf Digest, SELF, Teen Vogue, The New Yorker, Vanity Fair, Vogue, W Magazine, WIRED
|
- **CondeNast**: Condé Nast media group: Allure, Architectural Digest, Ars Technica, Bon Appétit, Brides, Condé Nast, Condé Nast Traveler, Details, Epicurious, GQ, Glamour, Golf Digest, SELF, Teen Vogue, The New Yorker, Vanity Fair, Vogue, W Magazine, WIRED
|
||||||
- **Cracked**
|
- **Cracked**
|
||||||
|
- **Crackle**
|
||||||
- **Criterion**
|
- **Criterion**
|
||||||
- **CrooksAndLiars**
|
- **CrooksAndLiars**
|
||||||
- **Crunchyroll**
|
- **Crunchyroll**
|
||||||
@ -114,21 +134,31 @@
|
|||||||
- **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:playlist**
|
||||||
|
- **daum.net:user**
|
||||||
- **DBTV**
|
- **DBTV**
|
||||||
- **DCN**
|
- **DCN**
|
||||||
|
- **dcn:live**
|
||||||
|
- **dcn:season**
|
||||||
|
- **dcn:video**
|
||||||
- **DctpTv**
|
- **DctpTv**
|
||||||
- **DeezerPlaylist**
|
- **DeezerPlaylist**
|
||||||
- **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**: 斗鱼
|
||||||
|
- **DPlay**
|
||||||
- **dramafever**
|
- **dramafever**
|
||||||
- **dramafever:series**
|
- **dramafever:series**
|
||||||
- **DRBonanza**
|
- **DRBonanza**
|
||||||
@ -138,6 +168,8 @@
|
|||||||
- **Dump**
|
- **Dump**
|
||||||
- **Dumpert**
|
- **Dumpert**
|
||||||
- **dvtv**: http://video.aktualne.cz/
|
- **dvtv**: http://video.aktualne.cz/
|
||||||
|
- **dw**
|
||||||
|
- **dw:article**
|
||||||
- **EaglePlatform**
|
- **EaglePlatform**
|
||||||
- **EbaumsWorld**
|
- **EbaumsWorld**
|
||||||
- **EchoMsk**
|
- **EchoMsk**
|
||||||
@ -153,7 +185,7 @@
|
|||||||
- **Eporner**
|
- **Eporner**
|
||||||
- **EroProfile**
|
- **EroProfile**
|
||||||
- **Escapist**
|
- **Escapist**
|
||||||
- **ESPN** (Currently broken)
|
- **ESPN**
|
||||||
- **EsriVideo**
|
- **EsriVideo**
|
||||||
- **Europa**
|
- **Europa**
|
||||||
- **EveryonesMixtape**
|
- **EveryonesMixtape**
|
||||||
@ -164,24 +196,29 @@
|
|||||||
- **faz.net**
|
- **faz.net**
|
||||||
- **fc2**
|
- **fc2**
|
||||||
- **Fczenit**
|
- **Fczenit**
|
||||||
|
- **features.aol.com**
|
||||||
- **fernsehkritik.tv**
|
- **fernsehkritik.tv**
|
||||||
- **Firstpost**
|
- **Firstpost**
|
||||||
- **FiveTV**
|
- **FiveTV**
|
||||||
- **Flickr**
|
- **Flickr**
|
||||||
- **Folketinget**: Folketinget (ft.dk; Danish parliament)
|
- **Folketinget**: Folketinget (ft.dk; Danish parliament)
|
||||||
- **FootyRoom**
|
- **FootyRoom**
|
||||||
|
- **FOX**
|
||||||
- **Foxgay**
|
- **Foxgay**
|
||||||
- **FoxNews**: Fox News and Fox Business Video
|
- **FoxNews**: Fox News and Fox Business Video
|
||||||
- **FoxSports**
|
- **FoxSports**
|
||||||
- **france2.fr:generation-quoi**
|
- **france2.fr:generation-quoi**
|
||||||
- **FranceCulture**
|
- **FranceCulture**
|
||||||
|
- **FranceCultureEmission**
|
||||||
- **FranceInter**
|
- **FranceInter**
|
||||||
- **francetv**: France 2, 3, 4, 5 and Ô
|
- **francetv**: France 2, 3, 4, 5 and Ô
|
||||||
- **francetvinfo.fr**
|
- **francetvinfo.fr**
|
||||||
- **Freesound**
|
- **Freesound**
|
||||||
- **freespeech.org**
|
- **freespeech.org**
|
||||||
- **FreeVideo**
|
- **FreeVideo**
|
||||||
|
- **Funimation**
|
||||||
- **FunnyOrDie**
|
- **FunnyOrDie**
|
||||||
|
- **GameInformer**
|
||||||
- **Gamekings**
|
- **Gamekings**
|
||||||
- **GameOne**
|
- **GameOne**
|
||||||
- **gameone:playlist**
|
- **gameone:playlist**
|
||||||
@ -201,7 +238,9 @@
|
|||||||
- **GodTube**
|
- **GodTube**
|
||||||
- **GoldenMoustache**
|
- **GoldenMoustache**
|
||||||
- **Golem**
|
- **Golem**
|
||||||
|
- **GoogleDrive**
|
||||||
- **Goshgay**
|
- **Goshgay**
|
||||||
|
- **GPUTechConf**
|
||||||
- **Groupon**
|
- **Groupon**
|
||||||
- **Hark**
|
- **Hark**
|
||||||
- **HearThisAt**
|
- **HearThisAt**
|
||||||
@ -210,11 +249,11 @@
|
|||||||
- **Helsinki**: helsinki.fi
|
- **Helsinki**: helsinki.fi
|
||||||
- **HentaiStigma**
|
- **HentaiStigma**
|
||||||
- **HistoricFilms**
|
- **HistoricFilms**
|
||||||
- **History**
|
|
||||||
- **hitbox**
|
- **hitbox**
|
||||||
- **hitbox:live**
|
- **hitbox:live**
|
||||||
- **HornBunny**
|
- **HornBunny**
|
||||||
- **HotNewHipHop**
|
- **HotNewHipHop**
|
||||||
|
- **HotStar**
|
||||||
- **Howcast**
|
- **Howcast**
|
||||||
- **HowStuffWorks**
|
- **HowStuffWorks**
|
||||||
- **HuffPost**: Huffington Post
|
- **HuffPost**: Huffington Post
|
||||||
@ -237,12 +276,13 @@
|
|||||||
- **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**
|
||||||
- **Jove**
|
- **Jove**
|
||||||
- **jpopsuki.tv**
|
- **jpopsuki.tv**
|
||||||
- **Jukebox**
|
- **JWPlatform**
|
||||||
- **Kaltura**
|
- **Kaltura**
|
||||||
- **KanalPlay**: Kanal 5/9/11 Play
|
- **KanalPlay**: Kanal 5/9/11 Play
|
||||||
- **Kankan**
|
- **Kankan**
|
||||||
@ -252,9 +292,11 @@
|
|||||||
- **KeezMovies**
|
- **KeezMovies**
|
||||||
- **KhanAcademy**
|
- **KhanAcademy**
|
||||||
- **KickStarter**
|
- **KickStarter**
|
||||||
|
- **KonserthusetPlay**
|
||||||
- **kontrtube**: KontrTube.ru - Труба зовёт
|
- **kontrtube**: KontrTube.ru - Труба зовёт
|
||||||
- **KrasView**: Красвью
|
- **KrasView**: Красвью
|
||||||
- **Ku6**
|
- **Ku6**
|
||||||
|
- **KUSI**
|
||||||
- **kuwo:album**: 酷我音乐 - 专辑
|
- **kuwo:album**: 酷我音乐 - 专辑
|
||||||
- **kuwo:category**: 酷我音乐 - 分类
|
- **kuwo:category**: 酷我音乐 - 分类
|
||||||
- **kuwo:chart**: 酷我音乐 - 排行榜
|
- **kuwo:chart**: 酷我音乐 - 排行榜
|
||||||
@ -263,10 +305,11 @@
|
|||||||
- **kuwo:song**: 酷我音乐
|
- **kuwo:song**: 酷我音乐
|
||||||
- **la7.tv**
|
- **la7.tv**
|
||||||
- **Laola1Tv**
|
- **Laola1Tv**
|
||||||
|
- **Le**: 乐视网
|
||||||
- **Lecture2Go**
|
- **Lecture2Go**
|
||||||
- **Letv**: 乐视网
|
- **Lemonde**
|
||||||
- **LetvPlaylist**
|
- **LePlaylist**
|
||||||
- **LetvTv**
|
- **LetvCloud**: 乐视云
|
||||||
- **Libsyn**
|
- **Libsyn**
|
||||||
- **life:embed**
|
- **life:embed**
|
||||||
- **lifenews**: LIFE | NEWS
|
- **lifenews**: LIFE | NEWS
|
||||||
@ -277,13 +320,17 @@
|
|||||||
- **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
|
||||||
- **m6**
|
- **m6**
|
||||||
- **macgamestore**: MacGameStore trailers
|
- **macgamestore**: MacGameStore trailers
|
||||||
- **mailru**: Видео@Mail.Ru
|
- **mailru**: Видео@Mail.Ru
|
||||||
|
- **MakersChannel**
|
||||||
|
- **MakerTV**
|
||||||
- **Malemotion**
|
- **Malemotion**
|
||||||
|
- **MatchTV**
|
||||||
- **MDR**: MDR.DE and KiKA
|
- **MDR**: MDR.DE and KiKA
|
||||||
- **media.ccc.de**
|
- **media.ccc.de**
|
||||||
- **metacafe**
|
- **metacafe**
|
||||||
@ -291,6 +338,7 @@
|
|||||||
- **Mgoon**
|
- **Mgoon**
|
||||||
- **Minhateca**
|
- **Minhateca**
|
||||||
- **MinistryGrid**
|
- **MinistryGrid**
|
||||||
|
- **Minoto**
|
||||||
- **miomio.tv**
|
- **miomio.tv**
|
||||||
- **MiTele**: mitele.es
|
- **MiTele**: mitele.es
|
||||||
- **mixcloud**
|
- **mixcloud**
|
||||||
@ -306,7 +354,6 @@
|
|||||||
- **MovieClips**
|
- **MovieClips**
|
||||||
- **MovieFap**
|
- **MovieFap**
|
||||||
- **Moviezine**
|
- **Moviezine**
|
||||||
- **movshare**: MovShare
|
|
||||||
- **MPORA**
|
- **MPORA**
|
||||||
- **MSNBC**
|
- **MSNBC**
|
||||||
- **MTV**
|
- **MTV**
|
||||||
@ -321,7 +368,7 @@
|
|||||||
- **MySpace:album**
|
- **MySpace:album**
|
||||||
- **MySpass**
|
- **MySpass**
|
||||||
- **Myvi**
|
- **Myvi**
|
||||||
- **myvideo**
|
- **myvideo** (Currently broken)
|
||||||
- **MyVidster**
|
- **MyVidster**
|
||||||
- **n-tv.de**
|
- **n-tv.de**
|
||||||
- **NationalGeographic**
|
- **NationalGeographic**
|
||||||
@ -349,11 +396,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
|
||||||
@ -366,15 +415,18 @@
|
|||||||
- **nowness**
|
- **nowness**
|
||||||
- **nowness:playlist**
|
- **nowness:playlist**
|
||||||
- **nowness:series**
|
- **nowness:series**
|
||||||
- **NowTV**
|
- **NowTV** (Currently broken)
|
||||||
- **NowTVList**
|
- **NowTVList**
|
||||||
- **nowvideo**: NowVideo
|
- **nowvideo**: NowVideo
|
||||||
|
- **Noz**
|
||||||
- **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**
|
||||||
|
- **NRKSkole**: NRK Skole
|
||||||
- **NRKTV**: NRK TV and NRK Radio
|
- **NRKTV**: NRK TV and NRK Radio
|
||||||
- **ntv.ru**
|
- **ntv.ru**
|
||||||
- **Nuvid**
|
- **Nuvid**
|
||||||
@ -387,22 +439,26 @@
|
|||||||
- **OnionStudios**
|
- **OnionStudios**
|
||||||
- **Ooyala**
|
- **Ooyala**
|
||||||
- **OoyalaExternal**
|
- **OoyalaExternal**
|
||||||
|
- **OraTV**
|
||||||
- **orf:fm4**: radio FM4
|
- **orf:fm4**: radio FM4
|
||||||
- **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**
|
- **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)
|
||||||
|
- **pcmag**
|
||||||
- **Periscope**: Periscope
|
- **Periscope**: Periscope
|
||||||
- **PhilharmonieDeParis**: Philharmonie de Paris
|
- **PhilharmonieDeParis**: Philharmonie de Paris
|
||||||
- **Phoenix**
|
- **phoenix.de**
|
||||||
- **Photobucket**
|
- **Photobucket**
|
||||||
- **Pinkbike**
|
- **Pinkbike**
|
||||||
- **Pladform**
|
- **Pladform**
|
||||||
- **PlanetaPlay**
|
- **PlanetaPlay**
|
||||||
- **play.fm**
|
- **play.fm**
|
||||||
- **played.to**
|
- **played.to**
|
||||||
|
- **PlaysTV**
|
||||||
- **Playtvak**: Playtvak.cz, iDNES.cz and Lidovky.cz
|
- **Playtvak**: Playtvak.cz, iDNES.cz and Lidovky.cz
|
||||||
- **Playvid**
|
- **Playvid**
|
||||||
- **Playwire**
|
- **Playwire**
|
||||||
@ -414,6 +470,7 @@
|
|||||||
- **PornHd**
|
- **PornHd**
|
||||||
- **PornHub**
|
- **PornHub**
|
||||||
- **PornHubPlaylist**
|
- **PornHubPlaylist**
|
||||||
|
- **PornHubUserVideos**
|
||||||
- **Pornotube**
|
- **Pornotube**
|
||||||
- **PornoVoisines**
|
- **PornoVoisines**
|
||||||
- **PornoXO**
|
- **PornoXO**
|
||||||
@ -434,16 +491,20 @@
|
|||||||
- **radiofrance**
|
- **radiofrance**
|
||||||
- **RadioJavan**
|
- **RadioJavan**
|
||||||
- **Rai**
|
- **Rai**
|
||||||
|
- **RaiTV**
|
||||||
- **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**
|
||||||
@ -453,6 +514,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
|
||||||
@ -466,10 +528,12 @@
|
|||||||
- **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**
|
||||||
- **ScreencastOMatic**
|
- **ScreencastOMatic**
|
||||||
|
- **ScreenJunkies**
|
||||||
- **ScreenwaveMedia**
|
- **ScreenwaveMedia**
|
||||||
- **SenateISVP**
|
- **SenateISVP**
|
||||||
- **ServingSys**
|
- **ServingSys**
|
||||||
@ -479,6 +543,8 @@
|
|||||||
- **Shared**: shared.sx and vivo.sx
|
- **Shared**: shared.sx and vivo.sx
|
||||||
- **ShareSix**
|
- **ShareSix**
|
||||||
- **Sina**
|
- **Sina**
|
||||||
|
- **skynewsarabia:video**
|
||||||
|
- **skynewsarabia:video**
|
||||||
- **Slideshare**
|
- **Slideshare**
|
||||||
- **Slutload**
|
- **Slutload**
|
||||||
- **smotri**: Smotri.com
|
- **smotri**: Smotri.com
|
||||||
@ -489,10 +555,9 @@
|
|||||||
- **SnagFilmsEmbed**
|
- **SnagFilmsEmbed**
|
||||||
- **Snotr**
|
- **Snotr**
|
||||||
- **Sohu**
|
- **Sohu**
|
||||||
- **soompi**
|
|
||||||
- **soompi:show**
|
|
||||||
- **soundcloud**
|
- **soundcloud**
|
||||||
- **soundcloud:playlist**
|
- **soundcloud:playlist**
|
||||||
|
- **soundcloud:search**: Soundcloud search
|
||||||
- **soundcloud:set**
|
- **soundcloud:set**
|
||||||
- **soundcloud:user**
|
- **soundcloud:user**
|
||||||
- **soundgasm**
|
- **soundgasm**
|
||||||
@ -502,7 +567,6 @@
|
|||||||
- **southpark.de**
|
- **southpark.de**
|
||||||
- **southpark.nl**
|
- **southpark.nl**
|
||||||
- **southparkstudios.dk**
|
- **southparkstudios.dk**
|
||||||
- **Space**
|
|
||||||
- **SpankBang**
|
- **SpankBang**
|
||||||
- **Spankwire**
|
- **Spankwire**
|
||||||
- **Spiegel**
|
- **Spiegel**
|
||||||
@ -514,8 +578,8 @@
|
|||||||
- **SportBoxEmbed**
|
- **SportBoxEmbed**
|
||||||
- **SportDeutschland**
|
- **SportDeutschland**
|
||||||
- **Sportschau**
|
- **Sportschau**
|
||||||
- **Srf**
|
- **SRGSSR**
|
||||||
- **SRMediathek**: Saarländischer Rundfunk
|
- **SRGSSRPlay**: srf.ch, rts.ch, rsi.ch, rtr.ch and swissinfo.ch play sites
|
||||||
- **SSA**
|
- **SSA**
|
||||||
- **stanfordoc**: Stanford Open ClassRoom
|
- **stanfordoc**: Stanford Open ClassRoom
|
||||||
- **Steam**
|
- **Steam**
|
||||||
@ -540,14 +604,15 @@
|
|||||||
- **TechTalks**
|
- **TechTalks**
|
||||||
- **techtv.mit.edu**
|
- **techtv.mit.edu**
|
||||||
- **ted**
|
- **ted**
|
||||||
|
- **Tele13**
|
||||||
- **TeleBruxelles**
|
- **TeleBruxelles**
|
||||||
- **Telecinco**: telecinco.es, cuatro.com and mediaset.es
|
- **Telecinco**: telecinco.es, cuatro.com and mediaset.es
|
||||||
- **Telegraaf**
|
- **Telegraaf**
|
||||||
- **TeleMB**
|
- **TeleMB**
|
||||||
- **TeleTask**
|
- **TeleTask**
|
||||||
- **TenPlay**
|
- **TenPlay**
|
||||||
- **TestTube**
|
|
||||||
- **TF1**
|
- **TF1**
|
||||||
|
- **TheIntercept**
|
||||||
- **TheOnion**
|
- **TheOnion**
|
||||||
- **ThePlatform**
|
- **ThePlatform**
|
||||||
- **ThePlatformFeed**
|
- **ThePlatformFeed**
|
||||||
@ -557,22 +622,29 @@
|
|||||||
- **THVideo**
|
- **THVideo**
|
||||||
- **THVideoPlaylist**
|
- **THVideoPlaylist**
|
||||||
- **tinypic**: tinypic.com videos
|
- **tinypic**: tinypic.com videos
|
||||||
- **tlc.com**
|
|
||||||
- **tlc.de**
|
- **tlc.de**
|
||||||
- **TMZ**
|
- **TMZ**
|
||||||
- **TMZArticle**
|
- **TMZArticle**
|
||||||
- **TNAFlix**
|
- **TNAFlix**
|
||||||
|
- **TNAFlixNetworkEmbed**
|
||||||
|
- **toggle**
|
||||||
- **tou.tv**
|
- **tou.tv**
|
||||||
- **Toypics**: Toypics user profile
|
- **Toypics**: Toypics user profile
|
||||||
- **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**
|
- **tunein:clip**
|
||||||
|
- **tunein:program**
|
||||||
|
- **tunein:station**
|
||||||
|
- **tunein:topic**
|
||||||
- **Turbo**
|
- **Turbo**
|
||||||
- **Tutv**
|
- **Tutv**
|
||||||
- **tv.dfb.de**
|
- **tv.dfb.de**
|
||||||
@ -582,6 +654,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
|
||||||
@ -594,16 +667,18 @@
|
|||||||
- **twitch:video**
|
- **twitch:video**
|
||||||
- **twitch:vod**
|
- **twitch:vod**
|
||||||
- **twitter**
|
- **twitter**
|
||||||
|
- **twitter:amplify**
|
||||||
- **twitter:card**
|
- **twitter:card**
|
||||||
- **Ubu**
|
- **Ubu**
|
||||||
- **udemy**
|
- **udemy**
|
||||||
- **udemy:course**
|
- **udemy:course**
|
||||||
- **UDNEmbed**: 聯合影音
|
- **UDNEmbed**: 聯合影音
|
||||||
- **Ultimedia**
|
|
||||||
- **Unistra**
|
- **Unistra**
|
||||||
- **Urort**: NRK P3 Urørt
|
- **Urort**: NRK P3 Urørt
|
||||||
|
- **USAToday**
|
||||||
- **ustream**
|
- **ustream**
|
||||||
- **ustream:channel**
|
- **ustream:channel**
|
||||||
|
- **Ustudio**
|
||||||
- **Varzesh3**
|
- **Varzesh3**
|
||||||
- **Vbox7**
|
- **Vbox7**
|
||||||
- **VeeHD**
|
- **VeeHD**
|
||||||
@ -611,19 +686,25 @@
|
|||||||
- **Vessel**
|
- **Vessel**
|
||||||
- **Vesti**: Вести.Ru
|
- **Vesti**: Вести.Ru
|
||||||
- **Vevo**
|
- **Vevo**
|
||||||
- **VGTV**: VGTV and BTTV
|
- **VGTV**: VGTV, BTTV, FTV, Aftenposten and Aftonbladet
|
||||||
- **vh1.com**
|
- **vh1.com**
|
||||||
- **Vice**
|
- **Vice**
|
||||||
|
- **ViceShow**
|
||||||
- **Viddler**
|
- **Viddler**
|
||||||
- **video.google:search**: Google Video search
|
- **video.google:search**: Google Video search
|
||||||
- **video.mit.edu**
|
- **video.mit.edu**
|
||||||
- **VideoDetective**
|
- **VideoDetective**
|
||||||
- **videofy.me**
|
- **videofy.me**
|
||||||
- **VideoMega**
|
- **VideoMega**
|
||||||
|
- **videomore**
|
||||||
|
- **videomore:season**
|
||||||
|
- **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**
|
||||||
@ -636,6 +717,7 @@
|
|||||||
- **vimeo:channel**
|
- **vimeo:channel**
|
||||||
- **vimeo:group**
|
- **vimeo:group**
|
||||||
- **vimeo:likes**: Vimeo user likes
|
- **vimeo:likes**: Vimeo user likes
|
||||||
|
- **vimeo:ondemand**
|
||||||
- **vimeo:review**: Review pages on vimeo
|
- **vimeo:review**: Review pages on vimeo
|
||||||
- **vimeo:user**
|
- **vimeo:user**
|
||||||
- **vimeo:watchlater**: Vimeo watch later list, "vimeowatchlater" keyword (requires authentication)
|
- **vimeo:watchlater**: Vimeo watch later list, "vimeowatchlater" keyword (requires authentication)
|
||||||
@ -663,6 +745,8 @@
|
|||||||
- **WebOfStories**
|
- **WebOfStories**
|
||||||
- **WebOfStoriesPlaylist**
|
- **WebOfStoriesPlaylist**
|
||||||
- **Weibo**
|
- **Weibo**
|
||||||
|
- **WeiqiTV**: WQTV
|
||||||
|
- **wholecloud**: WholeCloud
|
||||||
- **Wimp**
|
- **Wimp**
|
||||||
- **Wistia**
|
- **Wistia**
|
||||||
- **WNL**
|
- **WNL**
|
||||||
@ -699,6 +783,7 @@
|
|||||||
- **youtube:favorites**: YouTube.com favourite videos, ":ytfav" for short (requires authentication)
|
- **youtube:favorites**: YouTube.com favourite videos, ":ytfav" for short (requires authentication)
|
||||||
- **youtube:history**: Youtube watch history, ":ythistory" for short (requires authentication)
|
- **youtube:history**: Youtube watch history, ":ythistory" for short (requires authentication)
|
||||||
- **youtube:playlist**: YouTube.com playlists
|
- **youtube:playlist**: YouTube.com playlists
|
||||||
|
- **youtube:playlists**: YouTube.com user/channel playlists
|
||||||
- **youtube:recommended**: YouTube.com recommended videos, ":ytrec" for short (requires authentication)
|
- **youtube:recommended**: YouTube.com recommended videos, ":ytrec" for short (requires authentication)
|
||||||
- **youtube:search**: YouTube.com searches
|
- **youtube:search**: YouTube.com searches
|
||||||
- **youtube:search:date**: YouTube.com searches, newest videos first
|
- **youtube:search:date**: YouTube.com searches, newest videos first
|
||||||
@ -712,3 +797,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**
|
||||||
|
@ -11,8 +11,11 @@ import sys
|
|||||||
|
|
||||||
import youtube_dl.extractor
|
import youtube_dl.extractor
|
||||||
from youtube_dl import YoutubeDL
|
from youtube_dl import YoutubeDL
|
||||||
from youtube_dl.utils import (
|
from youtube_dl.compat import (
|
||||||
|
compat_os_name,
|
||||||
compat_str,
|
compat_str,
|
||||||
|
)
|
||||||
|
from youtube_dl.utils import (
|
||||||
preferredencoding,
|
preferredencoding,
|
||||||
write_string,
|
write_string,
|
||||||
)
|
)
|
||||||
@ -42,7 +45,7 @@ def report_warning(message):
|
|||||||
Print the message to stderr, it will be prefixed with 'WARNING:'
|
Print the message to stderr, it will be prefixed with 'WARNING:'
|
||||||
If stderr is a tty file the 'WARNING:' will be colored
|
If stderr is a tty file the 'WARNING:' will be colored
|
||||||
'''
|
'''
|
||||||
if sys.stderr.isatty() and os.name != 'nt':
|
if sys.stderr.isatty() and compat_os_name != 'nt':
|
||||||
_msg_header = '\033[0;33mWARNING:\033[0m'
|
_msg_header = '\033[0;33mWARNING:\033[0m'
|
||||||
else:
|
else:
|
||||||
_msg_header = 'WARNING:'
|
_msg_header = 'WARNING:'
|
||||||
|
@ -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,9 +222,19 @@ 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', '17', '36', '13',
|
||||||
# Apple HTTP Live Streaming
|
# Apple HTTP Live Streaming
|
||||||
'96', '95', '94', '93', '92', '132', '151',
|
'96', '95', '94', '93', '92', '132', '151',
|
||||||
# 3D
|
# 3D
|
||||||
@ -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
|
||||||
@ -480,6 +502,9 @@ class TestYoutubeDL(unittest.TestCase):
|
|||||||
assertRegexpMatches(self, ydl._format_note({
|
assertRegexpMatches(self, ydl._format_note({
|
||||||
'vbr': 10,
|
'vbr': 10,
|
||||||
}), '^\s*10k$')
|
}), '^\s*10k$')
|
||||||
|
assertRegexpMatches(self, ydl._format_note({
|
||||||
|
'fps': 30,
|
||||||
|
}), '^30fps$')
|
||||||
|
|
||||||
def test_postprocessors(self):
|
def test_postprocessors(self):
|
||||||
filename = 'post-processor-testfile.mp4'
|
filename = 'post-processor-testfile.mp4'
|
||||||
@ -631,6 +656,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'])
|
||||||
@ -121,8 +121,8 @@ class TestAllURLsMatching(unittest.TestCase):
|
|||||||
|
|
||||||
def test_pbs(self):
|
def test_pbs(self):
|
||||||
# https://github.com/rg3/youtube-dl/issues/2350
|
# https://github.com/rg3/youtube-dl/issues/2350
|
||||||
self.assertMatch('http://video.pbs.org/viralplayer/2365173446/', ['PBS'])
|
self.assertMatch('http://video.pbs.org/viralplayer/2365173446/', ['pbs'])
|
||||||
self.assertMatch('http://video.pbs.org/widget/partnerplayer/980042464/', ['PBS'])
|
self.assertMatch('http://video.pbs.org/widget/partnerplayer/980042464/', ['pbs'])
|
||||||
|
|
||||||
def test_yahoo_https(self):
|
def test_yahoo_https(self):
|
||||||
# https://github.com/rg3/youtube-dl/issues/2701
|
# https://github.com/rg3/youtube-dl/issues/2701
|
||||||
|
@ -52,7 +52,12 @@ class TestHTTP(unittest.TestCase):
|
|||||||
('localhost', 0), HTTPTestRequestHandler)
|
('localhost', 0), HTTPTestRequestHandler)
|
||||||
self.httpd.socket = ssl.wrap_socket(
|
self.httpd.socket = ssl.wrap_socket(
|
||||||
self.httpd.socket, certfile=certfn, server_side=True)
|
self.httpd.socket, certfile=certfn, server_side=True)
|
||||||
self.port = self.httpd.socket.getsockname()[1]
|
if os.name == 'java':
|
||||||
|
# In Jython SSLSocket is not a subclass of socket.socket
|
||||||
|
sock = self.httpd.socket.sock
|
||||||
|
else:
|
||||||
|
sock = self.httpd.socket
|
||||||
|
self.port = sock.getsockname()[1]
|
||||||
self.server_thread = threading.Thread(target=self.httpd.serve_forever)
|
self.server_thread = threading.Thread(target=self.httpd.serve_forever)
|
||||||
self.server_thread.daemon = True
|
self.server_thread.daemon = True
|
||||||
self.server_thread.start()
|
self.server_thread.start()
|
||||||
|
47
test/test_iqiyi_sdk_interpreter.py
Normal file
47
test/test_iqiyi_sdk_interpreter.py
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
#!/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__))))
|
||||||
|
|
||||||
|
from test.helper import FakeYDL
|
||||||
|
from youtube_dl.extractor import IqiyiIE
|
||||||
|
|
||||||
|
|
||||||
|
class IqiyiIEWithCredentials(IqiyiIE):
|
||||||
|
def _get_login_info(self):
|
||||||
|
return 'foo', 'bar'
|
||||||
|
|
||||||
|
|
||||||
|
class WarningLogger(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.messages = []
|
||||||
|
|
||||||
|
def warning(self, msg):
|
||||||
|
self.messages.append(msg)
|
||||||
|
|
||||||
|
def debug(self, msg):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def error(self, msg):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TestIqiyiSDKInterpreter(unittest.TestCase):
|
||||||
|
def test_iqiyi_sdk_interpreter(self):
|
||||||
|
'''
|
||||||
|
Test the functionality of IqiyiSDKInterpreter by trying to log in
|
||||||
|
|
||||||
|
If `sign` is incorrect, /validate call throws an HTTP 556 error
|
||||||
|
'''
|
||||||
|
logger = WarningLogger()
|
||||||
|
ie = IqiyiIEWithCredentials(FakeYDL({'logger': logger}))
|
||||||
|
ie._login()
|
||||||
|
self.assertTrue('unable to log in:' in logger.messages[0])
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
@ -11,7 +11,6 @@ from test.helper import FakeYDL, md5
|
|||||||
|
|
||||||
|
|
||||||
from youtube_dl.extractor import (
|
from youtube_dl.extractor import (
|
||||||
BlipTVIE,
|
|
||||||
YoutubeIE,
|
YoutubeIE,
|
||||||
DailymotionIE,
|
DailymotionIE,
|
||||||
TEDIE,
|
TEDIE,
|
||||||
@ -22,7 +21,7 @@ from youtube_dl.extractor import (
|
|||||||
NPOIE,
|
NPOIE,
|
||||||
ComedyCentralIE,
|
ComedyCentralIE,
|
||||||
NRKTVIE,
|
NRKTVIE,
|
||||||
RaiIE,
|
RaiTVIE,
|
||||||
VikiIE,
|
VikiIE,
|
||||||
ThePlatformIE,
|
ThePlatformIE,
|
||||||
ThePlatformFeedIE,
|
ThePlatformFeedIE,
|
||||||
@ -66,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
|
||||||
@ -145,18 +144,6 @@ class TestTedSubtitles(BaseTestSubtitles):
|
|||||||
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)
|
||||||
|
|
||||||
|
|
||||||
class TestBlipTVSubtitles(BaseTestSubtitles):
|
|
||||||
url = 'http://blip.tv/a/a-6603250'
|
|
||||||
IE = BlipTVIE
|
|
||||||
|
|
||||||
def test_allsubtitles(self):
|
|
||||||
self.DL.params['writesubtitles'] = True
|
|
||||||
self.DL.params['allsubtitles'] = True
|
|
||||||
subtitles = self.getSubtitles()
|
|
||||||
self.assertEqual(set(subtitles.keys()), set(['en']))
|
|
||||||
self.assertEqual(md5(subtitles['en']), '5b75c300af65fe4476dff79478bb93e4')
|
|
||||||
|
|
||||||
|
|
||||||
class TestVimeoSubtitles(BaseTestSubtitles):
|
class TestVimeoSubtitles(BaseTestSubtitles):
|
||||||
url = 'http://vimeo.com/76979871'
|
url = 'http://vimeo.com/76979871'
|
||||||
IE = VimeoIE
|
IE = VimeoIE
|
||||||
@ -273,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()
|
@ -18,9 +18,13 @@ import xml.etree.ElementTree
|
|||||||
from youtube_dl.utils import (
|
from youtube_dl.utils import (
|
||||||
age_restricted,
|
age_restricted,
|
||||||
args_to_str,
|
args_to_str,
|
||||||
|
encode_base_n,
|
||||||
clean_html,
|
clean_html,
|
||||||
DateRange,
|
DateRange,
|
||||||
detect_exe_version,
|
detect_exe_version,
|
||||||
|
determine_ext,
|
||||||
|
dict_get,
|
||||||
|
encode_compat_str,
|
||||||
encodeFilename,
|
encodeFilename,
|
||||||
escape_rfc3986,
|
escape_rfc3986,
|
||||||
escape_url,
|
escape_url,
|
||||||
@ -32,16 +36,19 @@ from youtube_dl.utils import (
|
|||||||
is_html,
|
is_html,
|
||||||
js_to_json,
|
js_to_json,
|
||||||
limit_length,
|
limit_length,
|
||||||
|
ohdave_rsa_encrypt,
|
||||||
OnDemandPagedList,
|
OnDemandPagedList,
|
||||||
orderedSet,
|
orderedSet,
|
||||||
parse_duration,
|
parse_duration,
|
||||||
parse_filesize,
|
parse_filesize,
|
||||||
|
parse_count,
|
||||||
parse_iso8601,
|
parse_iso8601,
|
||||||
read_batch_urls,
|
read_batch_urls,
|
||||||
sanitize_filename,
|
sanitize_filename,
|
||||||
sanitize_path,
|
sanitize_path,
|
||||||
prepend_extension,
|
prepend_extension,
|
||||||
replace_extension,
|
replace_extension,
|
||||||
|
remove_quotes,
|
||||||
shell_quote,
|
shell_quote,
|
||||||
smuggle_url,
|
smuggle_url,
|
||||||
str_to_int,
|
str_to_int,
|
||||||
@ -55,6 +62,7 @@ from youtube_dl.utils import (
|
|||||||
lowercase_escape,
|
lowercase_escape,
|
||||||
url_basename,
|
url_basename,
|
||||||
urlencode_postdata,
|
urlencode_postdata,
|
||||||
|
update_url_query,
|
||||||
version_tuple,
|
version_tuple,
|
||||||
xpath_with_ns,
|
xpath_with_ns,
|
||||||
xpath_element,
|
xpath_element,
|
||||||
@ -70,6 +78,8 @@ from youtube_dl.utils import (
|
|||||||
)
|
)
|
||||||
from youtube_dl.compat import (
|
from youtube_dl.compat import (
|
||||||
compat_etree_fromstring,
|
compat_etree_fromstring,
|
||||||
|
compat_urlparse,
|
||||||
|
compat_parse_qs,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -199,6 +209,15 @@ class TestUtil(unittest.TestCase):
|
|||||||
self.assertEqual(replace_extension('.abc', 'temp'), '.abc.temp')
|
self.assertEqual(replace_extension('.abc', 'temp'), '.abc.temp')
|
||||||
self.assertEqual(replace_extension('.abc.ext', 'temp'), '.abc.temp')
|
self.assertEqual(replace_extension('.abc.ext', 'temp'), '.abc.temp')
|
||||||
|
|
||||||
|
def test_remove_quotes(self):
|
||||||
|
self.assertEqual(remove_quotes(None), None)
|
||||||
|
self.assertEqual(remove_quotes('"'), '"')
|
||||||
|
self.assertEqual(remove_quotes("'"), "'")
|
||||||
|
self.assertEqual(remove_quotes(';'), ';')
|
||||||
|
self.assertEqual(remove_quotes('";'), '";')
|
||||||
|
self.assertEqual(remove_quotes('""'), '')
|
||||||
|
self.assertEqual(remove_quotes('";"'), ';')
|
||||||
|
|
||||||
def test_ordered_set(self):
|
def test_ordered_set(self):
|
||||||
self.assertEqual(orderedSet([1, 1, 2, 3, 4, 4, 5, 6, 7, 3, 5]), [1, 2, 3, 4, 5, 6, 7])
|
self.assertEqual(orderedSet([1, 1, 2, 3, 4, 4, 5, 6, 7, 3, 5]), [1, 2, 3, 4, 5, 6, 7])
|
||||||
self.assertEqual(orderedSet([]), [])
|
self.assertEqual(orderedSet([]), [])
|
||||||
@ -235,9 +254,17 @@ class TestUtil(unittest.TestCase):
|
|||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
unified_strdate('2/2/2015 6:47:40 PM', day_first=False),
|
unified_strdate('2/2/2015 6:47:40 PM', day_first=False),
|
||||||
'20150202')
|
'20150202')
|
||||||
|
self.assertEqual(unified_strdate('Feb 14th 2016 5:45PM'), '20160214')
|
||||||
self.assertEqual(unified_strdate('25-09-2014'), '20140925')
|
self.assertEqual(unified_strdate('25-09-2014'), '20140925')
|
||||||
self.assertEqual(unified_strdate('UNKNOWN DATE FORMAT'), None)
|
self.assertEqual(unified_strdate('UNKNOWN DATE FORMAT'), None)
|
||||||
|
|
||||||
|
def test_determine_ext(self):
|
||||||
|
self.assertEqual(determine_ext('http://example.com/foo/bar.mp4/?download'), 'mp4')
|
||||||
|
self.assertEqual(determine_ext('http://example.com/foo/bar/?download', None), None)
|
||||||
|
self.assertEqual(determine_ext('http://example.com/foo/bar.nonext/?download', None), None)
|
||||||
|
self.assertEqual(determine_ext('http://example.com/foo/bar/mp4?download', None), None)
|
||||||
|
self.assertEqual(determine_ext('http://example.com/foo/bar.m3u8//?download'), 'm3u8')
|
||||||
|
|
||||||
def test_find_xpath_attr(self):
|
def test_find_xpath_attr(self):
|
||||||
testxml = '''<root>
|
testxml = '''<root>
|
||||||
<node/>
|
<node/>
|
||||||
@ -431,6 +458,66 @@ 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_update_url_query(self):
|
||||||
|
def query_dict(url):
|
||||||
|
return compat_parse_qs(compat_urlparse.urlparse(url).query)
|
||||||
|
self.assertEqual(query_dict(update_url_query(
|
||||||
|
'http://example.com/path', {'quality': ['HD'], 'format': ['mp4']})),
|
||||||
|
query_dict('http://example.com/path?quality=HD&format=mp4'))
|
||||||
|
self.assertEqual(query_dict(update_url_query(
|
||||||
|
'http://example.com/path', {'system': ['LINUX', 'WINDOWS']})),
|
||||||
|
query_dict('http://example.com/path?system=LINUX&system=WINDOWS'))
|
||||||
|
self.assertEqual(query_dict(update_url_query(
|
||||||
|
'http://example.com/path', {'fields': 'id,formats,subtitles'})),
|
||||||
|
query_dict('http://example.com/path?fields=id,formats,subtitles'))
|
||||||
|
self.assertEqual(query_dict(update_url_query(
|
||||||
|
'http://example.com/path', {'fields': ('id,formats,subtitles', 'thumbnails')})),
|
||||||
|
query_dict('http://example.com/path?fields=id,formats,subtitles&fields=thumbnails'))
|
||||||
|
self.assertEqual(query_dict(update_url_query(
|
||||||
|
'http://example.com/path?manifest=f4m', {'manifest': []})),
|
||||||
|
query_dict('http://example.com/path'))
|
||||||
|
self.assertEqual(query_dict(update_url_query(
|
||||||
|
'http://example.com/path?system=LINUX&system=WINDOWS', {'system': 'LINUX'})),
|
||||||
|
query_dict('http://example.com/path?system=LINUX'))
|
||||||
|
self.assertEqual(query_dict(update_url_query(
|
||||||
|
'http://example.com/path', {'fields': b'id,formats,subtitles'})),
|
||||||
|
query_dict('http://example.com/path?fields=id,formats,subtitles'))
|
||||||
|
self.assertEqual(query_dict(update_url_query(
|
||||||
|
'http://example.com/path', {'width': 1080, 'height': 720})),
|
||||||
|
query_dict('http://example.com/path?width=1080&height=720'))
|
||||||
|
self.assertEqual(query_dict(update_url_query(
|
||||||
|
'http://example.com/path', {'bitrate': 5020.43})),
|
||||||
|
query_dict('http://example.com/path?bitrate=5020.43'))
|
||||||
|
self.assertEqual(query_dict(update_url_query(
|
||||||
|
'http://example.com/path', {'test': '第二行тест'})),
|
||||||
|
query_dict('http://example.com/path?test=%E7%AC%AC%E4%BA%8C%E8%A1%8C%D1%82%D0%B5%D1%81%D1%82'))
|
||||||
|
|
||||||
|
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):
|
||||||
|
self.assertEqual(encode_compat_str(b'\xd1\x82\xd0\xb5\xd1\x81\xd1\x82', 'utf-8'), 'тест')
|
||||||
|
self.assertEqual(encode_compat_str('тест', 'utf-8'), 'тест')
|
||||||
|
|
||||||
def test_parse_iso8601(self):
|
def test_parse_iso8601(self):
|
||||||
self.assertEqual(parse_iso8601('2014-03-23T23:04:26+0100'), 1395612266)
|
self.assertEqual(parse_iso8601('2014-03-23T23:04:26+0100'), 1395612266)
|
||||||
self.assertEqual(parse_iso8601('2014-03-23T22:04:26+0000'), 1395612266)
|
self.assertEqual(parse_iso8601('2014-03-23T22:04:26+0000'), 1395612266)
|
||||||
@ -448,6 +535,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'), '𝕐')
|
||||||
@ -563,6 +654,15 @@ class TestUtil(unittest.TestCase):
|
|||||||
self.assertEqual(parse_filesize('1.2Tb'), 1200000000000)
|
self.assertEqual(parse_filesize('1.2Tb'), 1200000000000)
|
||||||
self.assertEqual(parse_filesize('1,24 KB'), 1240)
|
self.assertEqual(parse_filesize('1,24 KB'), 1240)
|
||||||
|
|
||||||
|
def test_parse_count(self):
|
||||||
|
self.assertEqual(parse_count(None), None)
|
||||||
|
self.assertEqual(parse_count(''), None)
|
||||||
|
self.assertEqual(parse_count('0'), 0)
|
||||||
|
self.assertEqual(parse_count('1000'), 1000)
|
||||||
|
self.assertEqual(parse_count('1.000'), 1000)
|
||||||
|
self.assertEqual(parse_count('1.1k'), 1100)
|
||||||
|
self.assertEqual(parse_count('1.1kk'), 1100000)
|
||||||
|
|
||||||
def test_version_tuple(self):
|
def test_version_tuple(self):
|
||||||
self.assertEqual(version_tuple('1'), (1,))
|
self.assertEqual(version_tuple('1'), (1,))
|
||||||
self.assertEqual(version_tuple('10.23.344'), (10, 23, 344))
|
self.assertEqual(version_tuple('10.23.344'), (10, 23, 344))
|
||||||
@ -643,12 +743,13 @@ ffmpeg version 2.4.4 Copyright (c) 2000-2014 the FFmpeg ...'''), '2.4.4')
|
|||||||
{'like_count': 190, 'dislike_count': 10}))
|
{'like_count': 190, 'dislike_count': 10}))
|
||||||
|
|
||||||
def test_parse_dfxp_time_expr(self):
|
def test_parse_dfxp_time_expr(self):
|
||||||
self.assertEqual(parse_dfxp_time_expr(None), 0.0)
|
self.assertEqual(parse_dfxp_time_expr(None), None)
|
||||||
self.assertEqual(parse_dfxp_time_expr(''), 0.0)
|
self.assertEqual(parse_dfxp_time_expr(''), None)
|
||||||
self.assertEqual(parse_dfxp_time_expr('0.1'), 0.1)
|
self.assertEqual(parse_dfxp_time_expr('0.1'), 0.1)
|
||||||
self.assertEqual(parse_dfxp_time_expr('0.1s'), 0.1)
|
self.assertEqual(parse_dfxp_time_expr('0.1s'), 0.1)
|
||||||
self.assertEqual(parse_dfxp_time_expr('00:00:01'), 1.0)
|
self.assertEqual(parse_dfxp_time_expr('00:00:01'), 1.0)
|
||||||
self.assertEqual(parse_dfxp_time_expr('00:00:01.100'), 1.1)
|
self.assertEqual(parse_dfxp_time_expr('00:00:01.100'), 1.1)
|
||||||
|
self.assertEqual(parse_dfxp_time_expr('00:00:01:100'), 1.1)
|
||||||
|
|
||||||
def test_dfxp2srt(self):
|
def test_dfxp2srt(self):
|
||||||
dfxp_data = '''<?xml version="1.0" encoding="UTF-8"?>
|
dfxp_data = '''<?xml version="1.0" encoding="UTF-8"?>
|
||||||
@ -658,6 +759,9 @@ ffmpeg version 2.4.4 Copyright (c) 2000-2014 the FFmpeg ...'''), '2.4.4')
|
|||||||
<p begin="0" end="1">The following line contains Chinese characters and special symbols</p>
|
<p begin="0" end="1">The following line contains Chinese characters and special symbols</p>
|
||||||
<p begin="1" end="2">第二行<br/>♪♪</p>
|
<p begin="1" end="2">第二行<br/>♪♪</p>
|
||||||
<p begin="2" dur="1"><span>Third<br/>Line</span></p>
|
<p begin="2" dur="1"><span>Third<br/>Line</span></p>
|
||||||
|
<p begin="3" end="-1">Lines with invalid timestamps are ignored</p>
|
||||||
|
<p begin="-1" end="-1">Ignore, two</p>
|
||||||
|
<p begin="3" dur="-1">Ignored, three</p>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</tt>'''
|
</tt>'''
|
||||||
@ -738,6 +842,24 @@ The first line
|
|||||||
{'nocheckcertificate': False}, '--check-certificate', 'nocheckcertificate', 'false', 'true', '='),
|
{'nocheckcertificate': False}, '--check-certificate', 'nocheckcertificate', 'false', 'true', '='),
|
||||||
['--check-certificate=true'])
|
['--check-certificate=true'])
|
||||||
|
|
||||||
|
def test_ohdave_rsa_encrypt(self):
|
||||||
|
N = 0xab86b6371b5318aaa1d3c9e612a9f1264f372323c8c0f19875b5fc3b3fd3afcc1e5bec527aa94bfa85bffc157e4245aebda05389a5357b75115ac94f074aefcd
|
||||||
|
e = 65537
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
ohdave_rsa_encrypt(b'aa111222', e, N),
|
||||||
|
'726664bd9a23fd0c70f9f1b84aab5e3905ce1e45a584e9cbcf9bcc7510338fc1986d6c599ff990d923aa43c51c0d9013cd572e13bc58f4ae48f2ed8c0b0ba881')
|
||||||
|
|
||||||
|
def test_encode_base_n(self):
|
||||||
|
self.assertEqual(encode_base_n(0, 30), '0')
|
||||||
|
self.assertEqual(encode_base_n(80, 30), '2k')
|
||||||
|
|
||||||
|
custom_table = '9876543210ZYXWVUTSRQPONMLKJIHGFEDCBA'
|
||||||
|
self.assertEqual(encode_base_n(0, 30, custom_table), '9')
|
||||||
|
self.assertEqual(encode_base_n(80, 30, custom_table), '7P')
|
||||||
|
|
||||||
|
self.assertRaises(ValueError, encode_base_n, 0, 70)
|
||||||
|
self.assertRaises(ValueError, encode_base_n, 0, 60, custom_table)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -24,15 +24,14 @@ import time
|
|||||||
import tokenize
|
import tokenize
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
if os.name == 'nt':
|
|
||||||
import ctypes
|
|
||||||
|
|
||||||
from .compat import (
|
from .compat import (
|
||||||
|
compat_basestring,
|
||||||
compat_cookiejar,
|
compat_cookiejar,
|
||||||
compat_expanduser,
|
compat_expanduser,
|
||||||
compat_get_terminal_size,
|
compat_get_terminal_size,
|
||||||
compat_http_client,
|
compat_http_client,
|
||||||
compat_kwargs,
|
compat_kwargs,
|
||||||
|
compat_os_name,
|
||||||
compat_str,
|
compat_str,
|
||||||
compat_tokenize_tokenize,
|
compat_tokenize_tokenize,
|
||||||
compat_urllib_error,
|
compat_urllib_error,
|
||||||
@ -45,8 +44,11 @@ from .utils import (
|
|||||||
DateRange,
|
DateRange,
|
||||||
DEFAULT_OUTTMPL,
|
DEFAULT_OUTTMPL,
|
||||||
determine_ext,
|
determine_ext,
|
||||||
|
determine_protocol,
|
||||||
DownloadError,
|
DownloadError,
|
||||||
|
encode_compat_str,
|
||||||
encodeFilename,
|
encodeFilename,
|
||||||
|
error_to_compat_str,
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
format_bytes,
|
format_bytes,
|
||||||
formatSeconds,
|
formatSeconds,
|
||||||
@ -63,6 +65,7 @@ from .utils import (
|
|||||||
SameFileError,
|
SameFileError,
|
||||||
sanitize_filename,
|
sanitize_filename,
|
||||||
sanitize_path,
|
sanitize_path,
|
||||||
|
sanitized_Request,
|
||||||
std_headers,
|
std_headers,
|
||||||
subtitles_filename,
|
subtitles_filename,
|
||||||
UnavailableVideoError,
|
UnavailableVideoError,
|
||||||
@ -82,6 +85,7 @@ from .extractor import get_info_extractor, gen_extractors
|
|||||||
from .downloader import get_suitable_downloader
|
from .downloader import get_suitable_downloader
|
||||||
from .downloader.rtmp import rtmpdump_version
|
from .downloader.rtmp import rtmpdump_version
|
||||||
from .postprocessor import (
|
from .postprocessor import (
|
||||||
|
FFmpegFixupM3u8PP,
|
||||||
FFmpegFixupM4aPP,
|
FFmpegFixupM4aPP,
|
||||||
FFmpegFixupStretchedPP,
|
FFmpegFixupStretchedPP,
|
||||||
FFmpegMergerPP,
|
FFmpegMergerPP,
|
||||||
@ -90,6 +94,9 @@ from .postprocessor import (
|
|||||||
)
|
)
|
||||||
from .version import __version__
|
from .version import __version__
|
||||||
|
|
||||||
|
if compat_os_name == 'nt':
|
||||||
|
import ctypes
|
||||||
|
|
||||||
|
|
||||||
class YoutubeDL(object):
|
class YoutubeDL(object):
|
||||||
"""YoutubeDL class.
|
"""YoutubeDL class.
|
||||||
@ -156,7 +163,7 @@ class YoutubeDL(object):
|
|||||||
writethumbnail: Write the thumbnail image to a file
|
writethumbnail: Write the thumbnail image to a file
|
||||||
write_all_thumbnails: Write all thumbnail formats to files
|
write_all_thumbnails: Write all thumbnail formats to files
|
||||||
writesubtitles: Write the video subtitles to a file
|
writesubtitles: Write the video subtitles to a file
|
||||||
writeautomaticsub: Write the automatic subtitles to a file
|
writeautomaticsub: Write the automatically generated subtitles to a file
|
||||||
allsubtitles: Downloads all the subtitles of the video
|
allsubtitles: Downloads all the subtitles of the video
|
||||||
(requires writesubtitles or writeautomaticsub)
|
(requires writesubtitles or writeautomaticsub)
|
||||||
listsubtitles: Lists all available subtitles for the video
|
listsubtitles: Lists all available subtitles for the video
|
||||||
@ -258,7 +265,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,
|
||||||
@ -445,7 +452,7 @@ class YoutubeDL(object):
|
|||||||
def to_console_title(self, message):
|
def to_console_title(self, message):
|
||||||
if not self.params.get('consoletitle', False):
|
if not self.params.get('consoletitle', False):
|
||||||
return
|
return
|
||||||
if os.name == 'nt' and ctypes.windll.kernel32.GetConsoleWindow():
|
if compat_os_name == 'nt' and ctypes.windll.kernel32.GetConsoleWindow():
|
||||||
# c_wchar_p() might not be necessary if `message` is
|
# c_wchar_p() might not be necessary if `message` is
|
||||||
# already of type unicode()
|
# already of type unicode()
|
||||||
ctypes.windll.kernel32.SetConsoleTitleW(ctypes.c_wchar_p(message))
|
ctypes.windll.kernel32.SetConsoleTitleW(ctypes.c_wchar_p(message))
|
||||||
@ -493,7 +500,7 @@ class YoutubeDL(object):
|
|||||||
tb = ''
|
tb = ''
|
||||||
if hasattr(sys.exc_info()[1], 'exc_info') and sys.exc_info()[1].exc_info[0]:
|
if hasattr(sys.exc_info()[1], 'exc_info') and sys.exc_info()[1].exc_info[0]:
|
||||||
tb += ''.join(traceback.format_exception(*sys.exc_info()[1].exc_info))
|
tb += ''.join(traceback.format_exception(*sys.exc_info()[1].exc_info))
|
||||||
tb += compat_str(traceback.format_exc())
|
tb += encode_compat_str(traceback.format_exc())
|
||||||
else:
|
else:
|
||||||
tb_data = traceback.format_list(traceback.extract_stack())
|
tb_data = traceback.format_list(traceback.extract_stack())
|
||||||
tb = ''.join(tb_data)
|
tb = ''.join(tb_data)
|
||||||
@ -516,7 +523,7 @@ class YoutubeDL(object):
|
|||||||
else:
|
else:
|
||||||
if self.params.get('no_warnings'):
|
if self.params.get('no_warnings'):
|
||||||
return
|
return
|
||||||
if not self.params.get('no_color') and self._err_file.isatty() and os.name != 'nt':
|
if not self.params.get('no_color') and self._err_file.isatty() and compat_os_name != 'nt':
|
||||||
_msg_header = '\033[0;33mWARNING:\033[0m'
|
_msg_header = '\033[0;33mWARNING:\033[0m'
|
||||||
else:
|
else:
|
||||||
_msg_header = 'WARNING:'
|
_msg_header = 'WARNING:'
|
||||||
@ -528,7 +535,7 @@ class YoutubeDL(object):
|
|||||||
Do the same as trouble, but prefixes the message with 'ERROR:', colored
|
Do the same as trouble, but prefixes the message with 'ERROR:', colored
|
||||||
in red if stderr is a tty file.
|
in red if stderr is a tty file.
|
||||||
'''
|
'''
|
||||||
if not self.params.get('no_color') and self._err_file.isatty() and os.name != 'nt':
|
if not self.params.get('no_color') and self._err_file.isatty() and compat_os_name != 'nt':
|
||||||
_msg_header = '\033[0;31mERROR:\033[0m'
|
_msg_header = '\033[0;31mERROR:\033[0m'
|
||||||
else:
|
else:
|
||||||
_msg_header = 'ERROR:'
|
_msg_header = 'ERROR:'
|
||||||
@ -561,7 +568,7 @@ class YoutubeDL(object):
|
|||||||
elif template_dict.get('height'):
|
elif template_dict.get('height'):
|
||||||
template_dict['resolution'] = '%sp' % template_dict['height']
|
template_dict['resolution'] = '%sp' % template_dict['height']
|
||||||
elif template_dict.get('width'):
|
elif template_dict.get('width'):
|
||||||
template_dict['resolution'] = '?x%d' % template_dict['width']
|
template_dict['resolution'] = '%dx?' % template_dict['width']
|
||||||
|
|
||||||
sanitize = lambda k, v: sanitize_filename(
|
sanitize = lambda k, v: sanitize_filename(
|
||||||
compat_str(v),
|
compat_str(v),
|
||||||
@ -600,12 +607,12 @@ class YoutubeDL(object):
|
|||||||
if rejecttitle:
|
if rejecttitle:
|
||||||
if re.search(rejecttitle, title, re.IGNORECASE):
|
if re.search(rejecttitle, title, re.IGNORECASE):
|
||||||
return '"' + title + '" title matched reject pattern "' + rejecttitle + '"'
|
return '"' + title + '" title matched reject pattern "' + rejecttitle + '"'
|
||||||
date = info_dict.get('upload_date', None)
|
date = info_dict.get('upload_date')
|
||||||
if date is not None:
|
if date is not None:
|
||||||
dateRange = self.params.get('daterange', DateRange())
|
dateRange = self.params.get('daterange', DateRange())
|
||||||
if date not in dateRange:
|
if date not in dateRange:
|
||||||
return '%s upload date is not in range %s' % (date_from_str(date).isoformat(), dateRange)
|
return '%s upload date is not in range %s' % (date_from_str(date).isoformat(), dateRange)
|
||||||
view_count = info_dict.get('view_count', None)
|
view_count = info_dict.get('view_count')
|
||||||
if view_count is not None:
|
if view_count is not None:
|
||||||
min_views = self.params.get('min_views')
|
min_views = self.params.get('min_views')
|
||||||
if min_views is not None and view_count < min_views:
|
if min_views is not None and view_count < min_views:
|
||||||
@ -672,14 +679,14 @@ class YoutubeDL(object):
|
|||||||
return self.process_ie_result(ie_result, download, extra_info)
|
return self.process_ie_result(ie_result, download, extra_info)
|
||||||
else:
|
else:
|
||||||
return ie_result
|
return ie_result
|
||||||
except ExtractorError as de: # An error we somewhat expected
|
except ExtractorError as e: # An error we somewhat expected
|
||||||
self.report_error(compat_str(de), de.format_traceback())
|
self.report_error(compat_str(e), e.format_traceback())
|
||||||
break
|
break
|
||||||
except MaxDownloadsReached:
|
except MaxDownloadsReached:
|
||||||
raise
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if self.params.get('ignoreerrors', False):
|
if self.params.get('ignoreerrors', False):
|
||||||
self.report_error(compat_str(e), tb=compat_str(traceback.format_exc()))
|
self.report_error(error_to_compat_str(e), tb=encode_compat_str(traceback.format_exc()))
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
@ -702,7 +709,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'):
|
||||||
@ -731,7 +737,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()
|
||||||
@ -743,18 +749,18 @@ class YoutubeDL(object):
|
|||||||
new_result, download=download, extra_info=extra_info)
|
new_result, download=download, extra_info=extra_info)
|
||||||
elif result_type == 'playlist' or result_type == 'multi_video':
|
elif result_type == 'playlist' or result_type == 'multi_video':
|
||||||
# We process each entry in the playlist
|
# We process each entry in the playlist
|
||||||
playlist = ie_result.get('title', None) or ie_result.get('id', None)
|
playlist = ie_result.get('title') or ie_result.get('id')
|
||||||
self.to_screen('[download] Downloading playlist: %s' % playlist)
|
self.to_screen('[download] Downloading playlist: %s' % playlist)
|
||||||
|
|
||||||
playlist_results = []
|
playlist_results = []
|
||||||
|
|
||||||
playliststart = self.params.get('playliststart', 1) - 1
|
playliststart = self.params.get('playliststart', 1) - 1
|
||||||
playlistend = self.params.get('playlistend', None)
|
playlistend = self.params.get('playlistend')
|
||||||
# For backwards compatibility, interpret -1 as whole list
|
# For backwards compatibility, interpret -1 as whole list
|
||||||
if playlistend == -1:
|
if playlistend == -1:
|
||||||
playlistend = None
|
playlistend = None
|
||||||
|
|
||||||
playlistitems_str = self.params.get('playlist_items', None)
|
playlistitems_str = self.params.get('playlist_items')
|
||||||
playlistitems = None
|
playlistitems = None
|
||||||
if playlistitems_str is not None:
|
if playlistitems_str is not None:
|
||||||
def iter_playlistitems(format):
|
def iter_playlistitems(format):
|
||||||
@ -778,7 +784,7 @@ class YoutubeDL(object):
|
|||||||
entries = ie_entries[playliststart:playlistend]
|
entries = ie_entries[playliststart:playlistend]
|
||||||
n_entries = len(entries)
|
n_entries = len(entries)
|
||||||
self.to_screen(
|
self.to_screen(
|
||||||
"[%s] playlist %s: Collected %d video ids (downloading %d of them)" %
|
'[%s] playlist %s: Collected %d video ids (downloading %d of them)' %
|
||||||
(ie_result['extractor'], playlist, n_all_entries, n_entries))
|
(ie_result['extractor'], playlist, n_all_entries, n_entries))
|
||||||
elif isinstance(ie_entries, PagedList):
|
elif isinstance(ie_entries, PagedList):
|
||||||
if playlistitems:
|
if playlistitems:
|
||||||
@ -792,7 +798,7 @@ class YoutubeDL(object):
|
|||||||
playliststart, playlistend)
|
playliststart, playlistend)
|
||||||
n_entries = len(entries)
|
n_entries = len(entries)
|
||||||
self.to_screen(
|
self.to_screen(
|
||||||
"[%s] playlist %s: Downloading %d videos" %
|
'[%s] playlist %s: Downloading %d videos' %
|
||||||
(ie_result['extractor'], playlist, n_entries))
|
(ie_result['extractor'], playlist, n_entries))
|
||||||
else: # iterable
|
else: # iterable
|
||||||
if playlistitems:
|
if playlistitems:
|
||||||
@ -803,7 +809,7 @@ class YoutubeDL(object):
|
|||||||
ie_entries, playliststart, playlistend))
|
ie_entries, playliststart, playlistend))
|
||||||
n_entries = len(entries)
|
n_entries = len(entries)
|
||||||
self.to_screen(
|
self.to_screen(
|
||||||
"[%s] playlist %s: Downloading %d videos" %
|
'[%s] playlist %s: Downloading %d videos' %
|
||||||
(ie_result['extractor'], playlist, n_entries))
|
(ie_result['extractor'], playlist, n_entries))
|
||||||
|
|
||||||
if self.params.get('playlistreverse', False):
|
if self.params.get('playlistreverse', False):
|
||||||
@ -833,6 +839,7 @@ class YoutubeDL(object):
|
|||||||
extra_info=extra)
|
extra_info=extra)
|
||||||
playlist_results.append(entry_result)
|
playlist_results.append(entry_result)
|
||||||
ie_result['entries'] = playlist_results
|
ie_result['entries'] = playlist_results
|
||||||
|
self.to_screen('[download] Finished downloading playlist: %s' % playlist)
|
||||||
return ie_result
|
return ie_result
|
||||||
elif result_type == 'compat_list':
|
elif result_type == 'compat_list':
|
||||||
self.report_warning(
|
self.report_warning(
|
||||||
@ -893,11 +900,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)
|
||||||
@ -937,7 +947,7 @@ class YoutubeDL(object):
|
|||||||
filter_parts.append(string)
|
filter_parts.append(string)
|
||||||
|
|
||||||
def _remove_unused_ops(tokens):
|
def _remove_unused_ops(tokens):
|
||||||
# Remove operators that we don't use and join them with the sourrounding strings
|
# Remove operators that we don't use and join them with the surrounding strings
|
||||||
# for example: 'mp4' '-' 'baseline' '-' '16x9' is converted to 'mp4-baseline-16x9'
|
# for example: 'mp4' '-' 'baseline' '-' '16x9' is converted to 'mp4-baseline-16x9'
|
||||||
ALLOWED_OPS = ('/', '+', ',', '(', ')')
|
ALLOWED_OPS = ('/', '+', ',', '(', ')')
|
||||||
last_string, last_start, last_end, last_line = None, None, None, None
|
last_string, last_start, last_end, last_line = None, None, None, None
|
||||||
@ -1107,6 +1117,12 @@ class YoutubeDL(object):
|
|||||||
'contain the video, try using '
|
'contain the video, try using '
|
||||||
'"-f %s+%s"' % (format_2, format_1))
|
'"-f %s+%s"' % (format_2, format_1))
|
||||||
return
|
return
|
||||||
|
# Formats must be opposite (video+audio)
|
||||||
|
if formats_info[0].get('acodec') == 'none' and formats_info[1].get('acodec') == 'none':
|
||||||
|
self.report_error(
|
||||||
|
'Both formats %s and %s are video-only, you must specify "-f video+audio"'
|
||||||
|
% (format_1, format_2))
|
||||||
|
return
|
||||||
output_ext = (
|
output_ext = (
|
||||||
formats_info[0]['ext']
|
formats_info[0]['ext']
|
||||||
if self.params.get('merge_output_format') is None
|
if self.params.get('merge_output_format') is None
|
||||||
@ -1186,7 +1202,7 @@ class YoutubeDL(object):
|
|||||||
return res
|
return res
|
||||||
|
|
||||||
def _calc_cookies(self, info_dict):
|
def _calc_cookies(self, info_dict):
|
||||||
pr = compat_urllib_request.Request(info_dict['url'])
|
pr = sanitized_Request(info_dict['url'])
|
||||||
self.cookiejar.add_cookie_header(pr)
|
self.cookiejar.add_cookie_header(pr)
|
||||||
return pr.get_header('Cookie')
|
return pr.get_header('Cookie')
|
||||||
|
|
||||||
@ -1218,6 +1234,10 @@ class YoutubeDL(object):
|
|||||||
if t.get('id') is None:
|
if t.get('id') is None:
|
||||||
t['id'] = '%d' % i
|
t['id'] = '%d' % i
|
||||||
|
|
||||||
|
if self.params.get('list_thumbnails'):
|
||||||
|
self.list_thumbnails(info_dict)
|
||||||
|
return
|
||||||
|
|
||||||
if thumbnails and 'thumbnail' not in info_dict:
|
if thumbnails and 'thumbnail' not in info_dict:
|
||||||
info_dict['thumbnail'] = thumbnails[-1]['url']
|
info_dict['thumbnail'] = thumbnails[-1]['url']
|
||||||
|
|
||||||
@ -1233,6 +1253,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():
|
||||||
@ -1268,6 +1294,9 @@ class YoutubeDL(object):
|
|||||||
|
|
||||||
if format.get('format_id') is None:
|
if format.get('format_id') is None:
|
||||||
format['format_id'] = compat_str(i)
|
format['format_id'] = compat_str(i)
|
||||||
|
else:
|
||||||
|
# Sanitize format_id from characters used in format selector expression
|
||||||
|
format['format_id'] = re.sub('[\s,/+\[\]()]', '_', format['format_id'])
|
||||||
format_id = format['format_id']
|
format_id = format['format_id']
|
||||||
if format_id not in formats_dict:
|
if format_id not in formats_dict:
|
||||||
formats_dict[format_id] = []
|
formats_dict[format_id] = []
|
||||||
@ -1289,6 +1318,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()
|
||||||
@ -1301,20 +1334,16 @@ 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)
|
||||||
return
|
return
|
||||||
if self.params.get('list_thumbnails'):
|
|
||||||
self.list_thumbnails(info_dict)
|
|
||||||
return
|
|
||||||
|
|
||||||
req_format = self.params.get('format')
|
req_format = self.params.get('format')
|
||||||
if req_format is None:
|
if req_format is None:
|
||||||
req_format_list = []
|
req_format_list = []
|
||||||
if (self.params.get('outtmpl', DEFAULT_OUTTMPL) != '-' and
|
if (self.params.get('outtmpl', DEFAULT_OUTTMPL) != '-' and
|
||||||
info_dict['extractor'] in ['youtube', 'ted'] and
|
|
||||||
not info_dict.get('is_live')):
|
not info_dict.get('is_live')):
|
||||||
merger = FFmpegMergerPP(self)
|
merger = FFmpegMergerPP(self)
|
||||||
if merger.available and merger.can_merge():
|
if merger.available and merger.can_merge():
|
||||||
@ -1450,7 +1479,7 @@ class YoutubeDL(object):
|
|||||||
if dn and not os.path.exists(dn):
|
if dn and not os.path.exists(dn):
|
||||||
os.makedirs(dn)
|
os.makedirs(dn)
|
||||||
except (OSError, IOError) as err:
|
except (OSError, IOError) as err:
|
||||||
self.report_error('unable to create directory ' + compat_str(err))
|
self.report_error('unable to create directory ' + error_to_compat_str(err))
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.params.get('writedescription', False):
|
if self.params.get('writedescription', False):
|
||||||
@ -1501,7 +1530,7 @@ class YoutubeDL(object):
|
|||||||
sub_info['url'], info_dict['id'], note=False)
|
sub_info['url'], info_dict['id'], note=False)
|
||||||
except ExtractorError as err:
|
except ExtractorError as err:
|
||||||
self.report_warning('Unable to download subtitle for "%s": %s' %
|
self.report_warning('Unable to download subtitle for "%s": %s' %
|
||||||
(sub_lang, compat_str(err.cause)))
|
(sub_lang, error_to_compat_str(err.cause)))
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
sub_filename = subtitles_filename(filename, sub_lang, sub_format)
|
sub_filename = subtitles_filename(filename, sub_lang, sub_format)
|
||||||
@ -1605,12 +1634,14 @@ class YoutubeDL(object):
|
|||||||
self.report_error('content too short (expected %s bytes and served %s)' % (err.expected, err.downloaded))
|
self.report_error('content too short (expected %s bytes and served %s)' % (err.expected, err.downloaded))
|
||||||
return
|
return
|
||||||
|
|
||||||
if success:
|
if success and filename != '-':
|
||||||
# Fixup content
|
# Fixup content
|
||||||
fixup_policy = self.params.get('fixup')
|
fixup_policy = self.params.get('fixup')
|
||||||
if fixup_policy is None:
|
if fixup_policy is None:
|
||||||
fixup_policy = 'detect_or_warn'
|
fixup_policy = 'detect_or_warn'
|
||||||
|
|
||||||
|
INSTALL_FFMPEG_MESSAGE = 'Install ffmpeg or avconv to fix this automatically.'
|
||||||
|
|
||||||
stretched_ratio = info_dict.get('stretched_ratio')
|
stretched_ratio = info_dict.get('stretched_ratio')
|
||||||
if stretched_ratio is not None and stretched_ratio != 1:
|
if stretched_ratio is not None and stretched_ratio != 1:
|
||||||
if fixup_policy == 'warn':
|
if fixup_policy == 'warn':
|
||||||
@ -1623,15 +1654,18 @@ class YoutubeDL(object):
|
|||||||
info_dict['__postprocessors'].append(stretched_pp)
|
info_dict['__postprocessors'].append(stretched_pp)
|
||||||
else:
|
else:
|
||||||
self.report_warning(
|
self.report_warning(
|
||||||
'%s: Non-uniform pixel ratio (%s). Install ffmpeg or avconv to fix this automatically.' % (
|
'%s: Non-uniform pixel ratio (%s). %s'
|
||||||
info_dict['id'], stretched_ratio))
|
% (info_dict['id'], stretched_ratio, INSTALL_FFMPEG_MESSAGE))
|
||||||
else:
|
else:
|
||||||
assert fixup_policy in ('ignore', 'never')
|
assert fixup_policy in ('ignore', 'never')
|
||||||
|
|
||||||
if info_dict.get('requested_formats') is None and info_dict.get('container') == 'm4a_dash':
|
if (info_dict.get('requested_formats') is None and
|
||||||
|
info_dict.get('container') == 'm4a_dash'):
|
||||||
if fixup_policy == 'warn':
|
if fixup_policy == 'warn':
|
||||||
self.report_warning('%s: writing DASH m4a. Only some players support this container.' % (
|
self.report_warning(
|
||||||
info_dict['id']))
|
'%s: writing DASH m4a. '
|
||||||
|
'Only some players support this container.'
|
||||||
|
% info_dict['id'])
|
||||||
elif fixup_policy == 'detect_or_warn':
|
elif fixup_policy == 'detect_or_warn':
|
||||||
fixup_pp = FFmpegFixupM4aPP(self)
|
fixup_pp = FFmpegFixupM4aPP(self)
|
||||||
if fixup_pp.available:
|
if fixup_pp.available:
|
||||||
@ -1639,8 +1673,27 @@ class YoutubeDL(object):
|
|||||||
info_dict['__postprocessors'].append(fixup_pp)
|
info_dict['__postprocessors'].append(fixup_pp)
|
||||||
else:
|
else:
|
||||||
self.report_warning(
|
self.report_warning(
|
||||||
'%s: writing DASH m4a. Only some players support this container. Install ffmpeg or avconv to fix this automatically.' % (
|
'%s: writing DASH m4a. '
|
||||||
|
'Only some players support this container. %s'
|
||||||
|
% (info_dict['id'], INSTALL_FFMPEG_MESSAGE))
|
||||||
|
else:
|
||||||
|
assert fixup_policy in ('ignore', 'never')
|
||||||
|
|
||||||
|
if (info_dict.get('protocol') == 'm3u8_native' or
|
||||||
|
info_dict.get('protocol') == 'm3u8' and
|
||||||
|
self.params.get('hls_prefer_native')):
|
||||||
|
if fixup_policy == 'warn':
|
||||||
|
self.report_warning('%s: malformated aac bitstream.' % (
|
||||||
info_dict['id']))
|
info_dict['id']))
|
||||||
|
elif fixup_policy == 'detect_or_warn':
|
||||||
|
fixup_pp = FFmpegFixupM3u8PP(self)
|
||||||
|
if fixup_pp.available:
|
||||||
|
info_dict.setdefault('__postprocessors', [])
|
||||||
|
info_dict['__postprocessors'].append(fixup_pp)
|
||||||
|
else:
|
||||||
|
self.report_warning(
|
||||||
|
'%s: malformated aac bitstream. %s'
|
||||||
|
% (info_dict['id'], INSTALL_FFMPEG_MESSAGE))
|
||||||
else:
|
else:
|
||||||
assert fixup_policy in ('ignore', 'never')
|
assert fixup_policy in ('ignore', 'never')
|
||||||
|
|
||||||
@ -1771,7 +1824,7 @@ class YoutubeDL(object):
|
|||||||
else:
|
else:
|
||||||
res = '%sp' % format['height']
|
res = '%sp' % format['height']
|
||||||
elif format.get('width') is not None:
|
elif format.get('width') is not None:
|
||||||
res = '?x%d' % format['width']
|
res = '%dx?' % format['width']
|
||||||
else:
|
else:
|
||||||
res = default
|
res = default
|
||||||
return res
|
return res
|
||||||
@ -1780,6 +1833,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:
|
||||||
@ -1800,7 +1857,9 @@ class YoutubeDL(object):
|
|||||||
if fdict.get('vbr') is not None:
|
if fdict.get('vbr') is not None:
|
||||||
res += '%4dk' % fdict['vbr']
|
res += '%4dk' % fdict['vbr']
|
||||||
if fdict.get('fps') is not None:
|
if fdict.get('fps') is not None:
|
||||||
res += ', %sfps' % fdict['fps']
|
if res:
|
||||||
|
res += ', '
|
||||||
|
res += '%sfps' % fdict['fps']
|
||||||
if fdict.get('acodec') is not None:
|
if fdict.get('acodec') is not None:
|
||||||
if res:
|
if res:
|
||||||
res += ', '
|
res += ', '
|
||||||
@ -1843,12 +1902,7 @@ class YoutubeDL(object):
|
|||||||
def list_thumbnails(self, info_dict):
|
def list_thumbnails(self, info_dict):
|
||||||
thumbnails = info_dict.get('thumbnails')
|
thumbnails = info_dict.get('thumbnails')
|
||||||
if not thumbnails:
|
if not thumbnails:
|
||||||
tn_url = info_dict.get('thumbnail')
|
self.to_screen('[info] No thumbnails present for %s' % info_dict['id'])
|
||||||
if tn_url:
|
|
||||||
thumbnails = [{'id': '0', 'url': tn_url}]
|
|
||||||
else:
|
|
||||||
self.to_screen(
|
|
||||||
'[info] No thumbnails present for %s' % info_dict['id'])
|
|
||||||
return
|
return
|
||||||
|
|
||||||
self.to_screen(
|
self.to_screen(
|
||||||
@ -1870,6 +1924,8 @@ class YoutubeDL(object):
|
|||||||
|
|
||||||
def urlopen(self, req):
|
def urlopen(self, req):
|
||||||
""" Start an HTTP download """
|
""" Start an HTTP download """
|
||||||
|
if isinstance(req, compat_basestring):
|
||||||
|
req = sanitized_Request(req)
|
||||||
return self._opener.open(req, timeout=self._socket_timeout)
|
return self._opener.open(req, timeout=self._socket_timeout)
|
||||||
|
|
||||||
def print_debug_header(self):
|
def print_debug_header(self):
|
||||||
@ -1969,8 +2025,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
|
||||||
@ -2028,4 +2095,4 @@ class YoutubeDL(object):
|
|||||||
(info_dict['extractor'], info_dict['id'], thumb_display_id, thumb_filename))
|
(info_dict['extractor'], info_dict['id'], thumb_display_id, thumb_filename))
|
||||||
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.report_warning('Unable to download thumbnail "%s": %s' %
|
self.report_warning('Unable to download thumbnail "%s": %s' %
|
||||||
(t['url'], compat_str(err)))
|
(t['url'], error_to_compat_str(err)))
|
||||||
|
@ -355,6 +355,7 @@ def _real_main(argv=None):
|
|||||||
'youtube_include_dash_manifest': opts.youtube_include_dash_manifest,
|
'youtube_include_dash_manifest': opts.youtube_include_dash_manifest,
|
||||||
'encoding': opts.encoding,
|
'encoding': opts.encoding,
|
||||||
'extract_flat': opts.extract_flat,
|
'extract_flat': opts.extract_flat,
|
||||||
|
'mark_watched': opts.mark_watched,
|
||||||
'merge_output_format': opts.merge_output_format,
|
'merge_output_format': opts.merge_output_format,
|
||||||
'postprocessors': postprocessors,
|
'postprocessors': postprocessors,
|
||||||
'fixup': opts.fixup,
|
'fixup': opts.fixup,
|
||||||
@ -369,6 +370,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,
|
||||||
|
@ -7,7 +7,7 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
if __package__ is None and not hasattr(sys, "frozen"):
|
if __package__ is None and not hasattr(sys, 'frozen'):
|
||||||
# direct call of __main__.py
|
# direct call of __main__.py
|
||||||
import os.path
|
import os.path
|
||||||
path = os.path.realpath(os.path.abspath(__file__))
|
path = os.path.realpath(os.path.abspath(__file__))
|
||||||
|
@ -161,7 +161,7 @@ def aes_decrypt_text(data, password, key_size_bytes):
|
|||||||
nonce = data[:NONCE_LENGTH_BYTES]
|
nonce = data[:NONCE_LENGTH_BYTES]
|
||||||
cipher = data[NONCE_LENGTH_BYTES:]
|
cipher = data[NONCE_LENGTH_BYTES:]
|
||||||
|
|
||||||
class Counter:
|
class Counter(object):
|
||||||
__value = nonce + [0] * (BLOCK_SIZE_BYTES - NONCE_LENGTH_BYTES)
|
__value = nonce + [0] * (BLOCK_SIZE_BYTES - NONCE_LENGTH_BYTES)
|
||||||
|
|
||||||
def next_value(self):
|
def next_value(self):
|
||||||
|
@ -181,20 +181,20 @@ except ImportError: # Python < 3.4
|
|||||||
# parameter := attribute "=" value
|
# parameter := attribute "=" value
|
||||||
url = req.get_full_url()
|
url = req.get_full_url()
|
||||||
|
|
||||||
scheme, data = url.split(":", 1)
|
scheme, data = url.split(':', 1)
|
||||||
mediatype, data = data.split(",", 1)
|
mediatype, data = data.split(',', 1)
|
||||||
|
|
||||||
# even base64 encoded data URLs might be quoted so unquote in any case:
|
# even base64 encoded data URLs might be quoted so unquote in any case:
|
||||||
data = compat_urllib_parse_unquote_to_bytes(data)
|
data = compat_urllib_parse_unquote_to_bytes(data)
|
||||||
if mediatype.endswith(";base64"):
|
if mediatype.endswith(';base64'):
|
||||||
data = binascii.a2b_base64(data)
|
data = binascii.a2b_base64(data)
|
||||||
mediatype = mediatype[:-7]
|
mediatype = mediatype[:-7]
|
||||||
|
|
||||||
if not mediatype:
|
if not mediatype:
|
||||||
mediatype = "text/plain;charset=US-ASCII"
|
mediatype = 'text/plain;charset=US-ASCII'
|
||||||
|
|
||||||
headers = email.message_from_string(
|
headers = email.message_from_string(
|
||||||
"Content-type: %s\nContent-length: %d\n" % (mediatype, len(data)))
|
'Content-type: %s\nContent-length: %d\n' % (mediatype, len(data)))
|
||||||
|
|
||||||
return compat_urllib_response.addinfourl(io.BytesIO(data), headers, url)
|
return compat_urllib_response.addinfourl(io.BytesIO(data), headers, url)
|
||||||
|
|
||||||
@ -268,7 +268,7 @@ except ImportError: # Python 2
|
|||||||
nv = name_value.split('=', 1)
|
nv = name_value.split('=', 1)
|
||||||
if len(nv) != 2:
|
if len(nv) != 2:
|
||||||
if strict_parsing:
|
if strict_parsing:
|
||||||
raise ValueError("bad query field: %r" % (name_value,))
|
raise ValueError('bad query field: %r' % (name_value,))
|
||||||
# Handle case of a control-name with no equal sign
|
# Handle case of a control-name with no equal sign
|
||||||
if keep_blank_values:
|
if keep_blank_values:
|
||||||
nv.append('')
|
nv.append('')
|
||||||
@ -326,6 +326,9 @@ def compat_ord(c):
|
|||||||
return ord(c)
|
return ord(c)
|
||||||
|
|
||||||
|
|
||||||
|
compat_os_name = os._name if os.name == 'java' else os.name
|
||||||
|
|
||||||
|
|
||||||
if sys.version_info >= (3, 0):
|
if sys.version_info >= (3, 0):
|
||||||
compat_getenv = os.getenv
|
compat_getenv = os.getenv
|
||||||
compat_expanduser = os.path.expanduser
|
compat_expanduser = os.path.expanduser
|
||||||
@ -346,7 +349,7 @@ else:
|
|||||||
# The following are os.path.expanduser implementations from cpython 2.7.8 stdlib
|
# The following are os.path.expanduser implementations from cpython 2.7.8 stdlib
|
||||||
# for different platforms with correct environment variables decoding.
|
# for different platforms with correct environment variables decoding.
|
||||||
|
|
||||||
if os.name == 'posix':
|
if compat_os_name == 'posix':
|
||||||
def compat_expanduser(path):
|
def compat_expanduser(path):
|
||||||
"""Expand ~ and ~user constructions. If user or $HOME is unknown,
|
"""Expand ~ and ~user constructions. If user or $HOME is unknown,
|
||||||
do nothing."""
|
do nothing."""
|
||||||
@ -370,7 +373,7 @@ else:
|
|||||||
userhome = pwent.pw_dir
|
userhome = pwent.pw_dir
|
||||||
userhome = userhome.rstrip('/')
|
userhome = userhome.rstrip('/')
|
||||||
return (userhome + path[i:]) or '/'
|
return (userhome + path[i:]) or '/'
|
||||||
elif os.name == 'nt' or os.name == 'ce':
|
elif compat_os_name == 'nt' or compat_os_name == 'ce':
|
||||||
def compat_expanduser(path):
|
def compat_expanduser(path):
|
||||||
"""Expand ~ and ~user constructs.
|
"""Expand ~ and ~user constructs.
|
||||||
|
|
||||||
@ -433,7 +436,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
|
||||||
@ -466,7 +469,7 @@ if sys.version_info < (2, 7):
|
|||||||
if err is not None:
|
if err is not None:
|
||||||
raise err
|
raise err
|
||||||
else:
|
else:
|
||||||
raise socket.error("getaddrinfo returns an empty list")
|
raise socket.error('getaddrinfo returns an empty list')
|
||||||
else:
|
else:
|
||||||
compat_socket_create_connection = socket.create_connection
|
compat_socket_create_connection = socket.create_connection
|
||||||
|
|
||||||
@ -556,6 +559,7 @@ __all__ = [
|
|||||||
'compat_itertools_count',
|
'compat_itertools_count',
|
||||||
'compat_kwargs',
|
'compat_kwargs',
|
||||||
'compat_ord',
|
'compat_ord',
|
||||||
|
'compat_os_name',
|
||||||
'compat_parse_qs',
|
'compat_parse_qs',
|
||||||
'compat_print',
|
'compat_print',
|
||||||
'compat_shlex_split',
|
'compat_shlex_split',
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from .common import FileDownloader
|
from .common import FileDownloader
|
||||||
from .external import get_external_downloader
|
|
||||||
from .f4m import F4mFD
|
from .f4m import F4mFD
|
||||||
from .hls import HlsFD
|
from .hls import HlsFD
|
||||||
from .hls import NativeHlsFD
|
|
||||||
from .http import HttpFD
|
from .http import HttpFD
|
||||||
from .rtsp import RtspFD
|
|
||||||
from .rtmp import RtmpFD
|
from .rtmp import RtmpFD
|
||||||
from .dash import DashSegmentsFD
|
from .dash import DashSegmentsFD
|
||||||
|
from .rtsp import RtspFD
|
||||||
|
from .external import (
|
||||||
|
get_external_downloader,
|
||||||
|
FFmpegFD,
|
||||||
|
)
|
||||||
|
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
determine_protocol,
|
determine_protocol,
|
||||||
@ -16,8 +18,8 @@ from ..utils import (
|
|||||||
|
|
||||||
PROTOCOL_MAP = {
|
PROTOCOL_MAP = {
|
||||||
'rtmp': RtmpFD,
|
'rtmp': RtmpFD,
|
||||||
'm3u8_native': NativeHlsFD,
|
'm3u8_native': HlsFD,
|
||||||
'm3u8': HlsFD,
|
'm3u8': FFmpegFD,
|
||||||
'mms': RtspFD,
|
'mms': RtspFD,
|
||||||
'rtsp': RtspFD,
|
'rtsp': RtspFD,
|
||||||
'f4m': F4mFD,
|
'f4m': F4mFD,
|
||||||
@ -30,14 +32,17 @@ def get_suitable_downloader(info_dict, params={}):
|
|||||||
protocol = determine_protocol(info_dict)
|
protocol = determine_protocol(info_dict)
|
||||||
info_dict['protocol'] = protocol
|
info_dict['protocol'] = protocol
|
||||||
|
|
||||||
|
# if (info_dict.get('start_time') or info_dict.get('end_time')) and not info_dict.get('requested_formats') and FFmpegFD.can_download(info_dict):
|
||||||
|
# return FFmpegFD
|
||||||
|
|
||||||
external_downloader = params.get('external_downloader')
|
external_downloader = params.get('external_downloader')
|
||||||
if external_downloader is not None:
|
if external_downloader is not None:
|
||||||
ed = get_external_downloader(external_downloader)
|
ed = get_external_downloader(external_downloader)
|
||||||
if ed.supports(info_dict):
|
if ed.can_download(info_dict):
|
||||||
return ed
|
return ed
|
||||||
|
|
||||||
if protocol == 'm3u8' and params.get('hls_prefer_native'):
|
if protocol == 'm3u8' and params.get('hls_prefer_native'):
|
||||||
return NativeHlsFD
|
return HlsFD
|
||||||
|
|
||||||
return PROTOCOL_MAP.get(protocol, HttpFD)
|
return PROTOCOL_MAP.get(protocol, HttpFD)
|
||||||
|
|
||||||
|
@ -5,9 +5,10 @@ import re
|
|||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from ..compat import compat_str
|
from ..compat import compat_os_name
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
encodeFilename,
|
encodeFilename,
|
||||||
|
error_to_compat_str,
|
||||||
decodeArgument,
|
decodeArgument,
|
||||||
format_bytes,
|
format_bytes,
|
||||||
timeconvert,
|
timeconvert,
|
||||||
@ -42,9 +43,10 @@ class FileDownloader(object):
|
|||||||
min_filesize: Skip files smaller than this size
|
min_filesize: Skip files smaller than this size
|
||||||
max_filesize: Skip files larger than this size
|
max_filesize: Skip files larger than this size
|
||||||
xattr_set_filesize: Set ytdl.filesize user xattribute with expected size.
|
xattr_set_filesize: Set ytdl.filesize user xattribute with expected size.
|
||||||
(experimenatal)
|
(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.
|
||||||
"""
|
"""
|
||||||
@ -156,7 +158,7 @@ class FileDownloader(object):
|
|||||||
|
|
||||||
def slow_down(self, start_time, now, byte_counter):
|
def slow_down(self, start_time, now, byte_counter):
|
||||||
"""Sleep if the download speed is over the rate limit."""
|
"""Sleep if the download speed is over the rate limit."""
|
||||||
rate_limit = self.params.get('ratelimit', None)
|
rate_limit = self.params.get('ratelimit')
|
||||||
if rate_limit is None or byte_counter == 0:
|
if rate_limit is None or byte_counter == 0:
|
||||||
return
|
return
|
||||||
if now is None:
|
if now is None:
|
||||||
@ -186,7 +188,7 @@ class FileDownloader(object):
|
|||||||
return
|
return
|
||||||
os.rename(encodeFilename(old_filename), encodeFilename(new_filename))
|
os.rename(encodeFilename(old_filename), encodeFilename(new_filename))
|
||||||
except (IOError, OSError) as err:
|
except (IOError, OSError) as err:
|
||||||
self.report_error('unable to rename file: %s' % compat_str(err))
|
self.report_error('unable to rename file: %s' % error_to_compat_str(err))
|
||||||
|
|
||||||
def try_utime(self, filename, last_modified_hdr):
|
def try_utime(self, filename, last_modified_hdr):
|
||||||
"""Try to set the last-modified time of the given file."""
|
"""Try to set the last-modified time of the given file."""
|
||||||
@ -218,7 +220,7 @@ class FileDownloader(object):
|
|||||||
if self.params.get('progress_with_newline', False):
|
if self.params.get('progress_with_newline', False):
|
||||||
self.to_screen(fullmsg)
|
self.to_screen(fullmsg)
|
||||||
else:
|
else:
|
||||||
if os.name == 'nt':
|
if compat_os_name == 'nt':
|
||||||
prev_len = getattr(self, '_report_progress_prev_line_length',
|
prev_len = getattr(self, '_report_progress_prev_line_length',
|
||||||
0)
|
0)
|
||||||
if prev_len > len(fullmsg):
|
if prev_len > len(fullmsg):
|
||||||
@ -295,7 +297,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."""
|
||||||
|
@ -1,66 +1,59 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import os
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import FileDownloader
|
from .fragment import FragmentFD
|
||||||
from ..compat import compat_urllib_request
|
from ..utils import (
|
||||||
|
sanitize_open,
|
||||||
|
encodeFilename,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class DashSegmentsFD(FileDownloader):
|
class DashSegmentsFD(FragmentFD):
|
||||||
"""
|
"""
|
||||||
Download segments in a DASH manifest
|
Download segments in a DASH manifest
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
FD_NAME = 'dashsegments'
|
||||||
|
|
||||||
def real_download(self, filename, info_dict):
|
def real_download(self, filename, info_dict):
|
||||||
self.report_destination(filename)
|
|
||||||
tmpfilename = self.temp_name(filename)
|
|
||||||
base_url = info_dict['url']
|
base_url = info_dict['url']
|
||||||
segment_urls = info_dict['segment_urls']
|
segment_urls = [info_dict['segment_urls'][0]] if self.params.get('test', False) else info_dict['segment_urls']
|
||||||
|
initialization_url = info_dict.get('initialization_url')
|
||||||
|
|
||||||
is_test = self.params.get('test', False)
|
ctx = {
|
||||||
remaining_bytes = self._TEST_FILE_SIZE if is_test else None
|
'filename': filename,
|
||||||
byte_counter = 0
|
'total_frags': len(segment_urls) + (1 if initialization_url else 0),
|
||||||
|
}
|
||||||
|
|
||||||
def append_url_to_file(outf, target_url, target_name, remaining_bytes=None):
|
self._prepare_and_start_frag_download(ctx)
|
||||||
self.to_screen('[DashSegments] %s: Downloading %s' % (info_dict['id'], target_name))
|
|
||||||
req = compat_urllib_request.Request(target_url)
|
|
||||||
if remaining_bytes is not None:
|
|
||||||
req.add_header('Range', 'bytes=0-%d' % (remaining_bytes - 1))
|
|
||||||
|
|
||||||
data = self.ydl.urlopen(req).read()
|
|
||||||
|
|
||||||
if remaining_bytes is not None:
|
|
||||||
data = data[:remaining_bytes]
|
|
||||||
|
|
||||||
outf.write(data)
|
|
||||||
return len(data)
|
|
||||||
|
|
||||||
def combine_url(base_url, target_url):
|
def combine_url(base_url, target_url):
|
||||||
if re.match(r'^https?://', target_url):
|
if re.match(r'^https?://', target_url):
|
||||||
return target_url
|
return target_url
|
||||||
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:
|
segments_filenames = []
|
||||||
append_url_to_file(
|
|
||||||
outf, combine_url(base_url, info_dict['initialization_url']),
|
def append_url_to_file(target_url, target_filename):
|
||||||
'initialization segment')
|
success = ctx['dl'].download(target_filename, {'url': combine_url(base_url, target_url)})
|
||||||
|
if not success:
|
||||||
|
return False
|
||||||
|
down, target_sanitized = sanitize_open(target_filename, 'rb')
|
||||||
|
ctx['dest_stream'].write(down.read())
|
||||||
|
down.close()
|
||||||
|
segments_filenames.append(target_sanitized)
|
||||||
|
|
||||||
|
if initialization_url:
|
||||||
|
append_url_to_file(initialization_url, ctx['tmpfilename'] + '-Init')
|
||||||
for i, segment_url in enumerate(segment_urls):
|
for i, segment_url in enumerate(segment_urls):
|
||||||
segment_len = append_url_to_file(
|
segment_filename = '%s-Seg%d' % (ctx['tmpfilename'], i)
|
||||||
outf, combine_url(base_url, segment_url),
|
append_url_to_file(segment_url, segment_filename)
|
||||||
'segment %d / %d' % (i + 1, len(segment_urls)),
|
|
||||||
remaining_bytes)
|
|
||||||
byte_counter += segment_len
|
|
||||||
if remaining_bytes is not None:
|
|
||||||
remaining_bytes -= segment_len
|
|
||||||
if remaining_bytes <= 0:
|
|
||||||
break
|
|
||||||
|
|
||||||
self.try_rename(tmpfilename, filename)
|
self._finish_frag_download(ctx)
|
||||||
|
|
||||||
self._hook_progress({
|
for segment_file in segments_filenames:
|
||||||
'downloaded_bytes': byte_counter,
|
os.remove(encodeFilename(segment_file))
|
||||||
'total_bytes': byte_counter,
|
|
||||||
'filename': filename,
|
|
||||||
'status': 'finished',
|
|
||||||
})
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
@ -2,8 +2,11 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
import os.path
|
import os.path
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import re
|
||||||
|
|
||||||
from .common import FileDownloader
|
from .common import FileDownloader
|
||||||
|
from ..postprocessor.ffmpeg import FFmpegPostProcessor, EXT_TO_OUT_FORMATS
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
cli_option,
|
cli_option,
|
||||||
cli_valueless_option,
|
cli_valueless_option,
|
||||||
@ -11,6 +14,8 @@ from ..utils import (
|
|||||||
cli_configuration_args,
|
cli_configuration_args,
|
||||||
encodeFilename,
|
encodeFilename,
|
||||||
encodeArgument,
|
encodeArgument,
|
||||||
|
handle_youtubedl_headers,
|
||||||
|
check_executable,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -45,10 +50,18 @@ class ExternalFD(FileDownloader):
|
|||||||
def exe(self):
|
def exe(self):
|
||||||
return self.params.get('external_downloader')
|
return self.params.get('external_downloader')
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def available(cls):
|
||||||
|
return check_executable(cls.get_basename(), [cls.AVAILABLE_OPT])
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def supports(cls, info_dict):
|
def supports(cls, info_dict):
|
||||||
return info_dict['protocol'] in ('http', 'https', 'ftp', 'ftps')
|
return info_dict['protocol'] in ('http', 'https', 'ftp', 'ftps')
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def can_download(cls, info_dict):
|
||||||
|
return cls.available() and cls.supports(info_dict)
|
||||||
|
|
||||||
def _option(self, command_option, param):
|
def _option(self, command_option, param):
|
||||||
return cli_option(self.params, command_option, param)
|
return cli_option(self.params, command_option, param)
|
||||||
|
|
||||||
@ -76,6 +89,8 @@ class ExternalFD(FileDownloader):
|
|||||||
|
|
||||||
|
|
||||||
class CurlFD(ExternalFD):
|
class CurlFD(ExternalFD):
|
||||||
|
AVAILABLE_OPT = '-V'
|
||||||
|
|
||||||
def _make_cmd(self, tmpfilename, info_dict):
|
def _make_cmd(self, tmpfilename, info_dict):
|
||||||
cmd = [self.exe, '--location', '-o', tmpfilename]
|
cmd = [self.exe, '--location', '-o', tmpfilename]
|
||||||
for key, val in info_dict['http_headers'].items():
|
for key, val in info_dict['http_headers'].items():
|
||||||
@ -89,6 +104,8 @@ class CurlFD(ExternalFD):
|
|||||||
|
|
||||||
|
|
||||||
class AxelFD(ExternalFD):
|
class AxelFD(ExternalFD):
|
||||||
|
AVAILABLE_OPT = '-V'
|
||||||
|
|
||||||
def _make_cmd(self, tmpfilename, info_dict):
|
def _make_cmd(self, tmpfilename, info_dict):
|
||||||
cmd = [self.exe, '-o', tmpfilename]
|
cmd = [self.exe, '-o', tmpfilename]
|
||||||
for key, val in info_dict['http_headers'].items():
|
for key, val in info_dict['http_headers'].items():
|
||||||
@ -99,6 +116,8 @@ class AxelFD(ExternalFD):
|
|||||||
|
|
||||||
|
|
||||||
class WgetFD(ExternalFD):
|
class WgetFD(ExternalFD):
|
||||||
|
AVAILABLE_OPT = '--version'
|
||||||
|
|
||||||
def _make_cmd(self, tmpfilename, info_dict):
|
def _make_cmd(self, tmpfilename, info_dict):
|
||||||
cmd = [self.exe, '-O', tmpfilename, '-nv', '--no-cookies']
|
cmd = [self.exe, '-O', tmpfilename, '-nv', '--no-cookies']
|
||||||
for key, val in info_dict['http_headers'].items():
|
for key, val in info_dict['http_headers'].items():
|
||||||
@ -112,6 +131,8 @@ class WgetFD(ExternalFD):
|
|||||||
|
|
||||||
|
|
||||||
class Aria2cFD(ExternalFD):
|
class Aria2cFD(ExternalFD):
|
||||||
|
AVAILABLE_OPT = '-v'
|
||||||
|
|
||||||
def _make_cmd(self, tmpfilename, info_dict):
|
def _make_cmd(self, tmpfilename, info_dict):
|
||||||
cmd = [self.exe, '-c']
|
cmd = [self.exe, '-c']
|
||||||
cmd += self._configuration_args([
|
cmd += self._configuration_args([
|
||||||
@ -130,12 +151,112 @@ class Aria2cFD(ExternalFD):
|
|||||||
|
|
||||||
|
|
||||||
class HttpieFD(ExternalFD):
|
class HttpieFD(ExternalFD):
|
||||||
|
@classmethod
|
||||||
|
def available(cls):
|
||||||
|
return check_executable('http', ['--version'])
|
||||||
|
|
||||||
def _make_cmd(self, tmpfilename, info_dict):
|
def _make_cmd(self, tmpfilename, info_dict):
|
||||||
cmd = ['http', '--download', '--output', tmpfilename, info_dict['url']]
|
cmd = ['http', '--download', '--output', tmpfilename, info_dict['url']]
|
||||||
for key, val in info_dict['http_headers'].items():
|
for key, val in info_dict['http_headers'].items():
|
||||||
cmd += ['%s:%s' % (key, val)]
|
cmd += ['%s:%s' % (key, val)]
|
||||||
return cmd
|
return cmd
|
||||||
|
|
||||||
|
|
||||||
|
class FFmpegFD(ExternalFD):
|
||||||
|
@classmethod
|
||||||
|
def supports(cls, info_dict):
|
||||||
|
return info_dict['protocol'] in ('http', 'https', 'ftp', 'ftps', 'm3u8', 'rtsp', 'rtmp', 'mms')
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def available(cls):
|
||||||
|
return FFmpegPostProcessor().available
|
||||||
|
|
||||||
|
def _call_downloader(self, tmpfilename, info_dict):
|
||||||
|
url = info_dict['url']
|
||||||
|
ffpp = FFmpegPostProcessor(downloader=self)
|
||||||
|
if not ffpp.available:
|
||||||
|
self.report_error('m3u8 download detected but ffmpeg or avconv could not be found. Please install one.')
|
||||||
|
return False
|
||||||
|
ffpp.check_version()
|
||||||
|
|
||||||
|
args = [ffpp.executable, '-y']
|
||||||
|
|
||||||
|
args += self._configuration_args()
|
||||||
|
|
||||||
|
# start_time = info_dict.get('start_time') or 0
|
||||||
|
# if start_time:
|
||||||
|
# args += ['-ss', compat_str(start_time)]
|
||||||
|
# end_time = info_dict.get('end_time')
|
||||||
|
# if end_time:
|
||||||
|
# args += ['-t', compat_str(end_time - start_time)]
|
||||||
|
|
||||||
|
if info_dict['http_headers'] and re.match(r'^https?://', url):
|
||||||
|
# Trailing \r\n after each HTTP header is important to prevent warning from ffmpeg/avconv:
|
||||||
|
# [http @ 00000000003d2fa0] No trailing CRLF found in HTTP header.
|
||||||
|
headers = handle_youtubedl_headers(info_dict['http_headers'])
|
||||||
|
args += [
|
||||||
|
'-headers',
|
||||||
|
''.join('%s: %s\r\n' % (key, val) for key, val in headers.items())]
|
||||||
|
|
||||||
|
protocol = info_dict.get('protocol')
|
||||||
|
|
||||||
|
if protocol == 'rtmp':
|
||||||
|
player_url = info_dict.get('player_url')
|
||||||
|
page_url = info_dict.get('page_url')
|
||||||
|
app = info_dict.get('app')
|
||||||
|
play_path = info_dict.get('play_path')
|
||||||
|
tc_url = info_dict.get('tc_url')
|
||||||
|
flash_version = info_dict.get('flash_version')
|
||||||
|
live = info_dict.get('rtmp_live', False)
|
||||||
|
if player_url is not None:
|
||||||
|
args += ['-rtmp_swfverify', player_url]
|
||||||
|
if page_url is not None:
|
||||||
|
args += ['-rtmp_pageurl', page_url]
|
||||||
|
if app is not None:
|
||||||
|
args += ['-rtmp_app', app]
|
||||||
|
if play_path is not None:
|
||||||
|
args += ['-rtmp_playpath', play_path]
|
||||||
|
if tc_url is not None:
|
||||||
|
args += ['-rtmp_tcurl', tc_url]
|
||||||
|
if flash_version is not None:
|
||||||
|
args += ['-rtmp_flashver', flash_version]
|
||||||
|
if live:
|
||||||
|
args += ['-rtmp_live', 'live']
|
||||||
|
|
||||||
|
args += ['-i', url, '-c', 'copy']
|
||||||
|
if protocol == 'm3u8':
|
||||||
|
if self.params.get('hls_use_mpegts', False):
|
||||||
|
args += ['-f', 'mpegts']
|
||||||
|
else:
|
||||||
|
args += ['-f', 'mp4', '-bsf:a', 'aac_adtstoasc']
|
||||||
|
elif protocol == 'rtmp':
|
||||||
|
args += ['-f', 'flv']
|
||||||
|
else:
|
||||||
|
args += ['-f', EXT_TO_OUT_FORMATS.get(info_dict['ext'], info_dict['ext'])]
|
||||||
|
|
||||||
|
args = [encodeArgument(opt) for opt in args]
|
||||||
|
args.append(encodeFilename(ffpp._ffmpeg_filename_argument(tmpfilename), True))
|
||||||
|
|
||||||
|
self._debug_cmd(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
|
||||||
|
return retval
|
||||||
|
|
||||||
|
|
||||||
|
class AVconvFD(FFmpegFD):
|
||||||
|
pass
|
||||||
|
|
||||||
_BY_NAME = dict(
|
_BY_NAME = dict(
|
||||||
(klass.get_basename(), klass)
|
(klass.get_basename(), klass)
|
||||||
for name, klass in globals().items()
|
for name, klass in globals().items()
|
||||||
|
@ -15,6 +15,7 @@ from ..compat import (
|
|||||||
)
|
)
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
encodeFilename,
|
encodeFilename,
|
||||||
|
fix_xml_ampersands,
|
||||||
sanitize_open,
|
sanitize_open,
|
||||||
struct_pack,
|
struct_pack,
|
||||||
struct_unpack,
|
struct_unpack,
|
||||||
@ -272,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']
|
||||||
@ -288,7 +295,10 @@ class F4mFD(FragmentFD):
|
|||||||
self.to_screen('[%s] Downloading f4m manifest' % self.FD_NAME)
|
self.to_screen('[%s] Downloading f4m manifest' % self.FD_NAME)
|
||||||
urlh = self.ydl.urlopen(man_url)
|
urlh = self.ydl.urlopen(man_url)
|
||||||
man_url = urlh.geturl()
|
man_url = urlh.geturl()
|
||||||
manifest = urlh.read()
|
# Some manifests may be malformed, e.g. prosiebensat1 generated manifests
|
||||||
|
# (see https://github.com/rg3/youtube-dl/issues/6215#issuecomment-121704244
|
||||||
|
# and https://github.com/rg3/youtube-dl/issues/7823)
|
||||||
|
manifest = fix_xml_ampersands(urlh.read().decode('utf-8', 'ignore')).strip()
|
||||||
|
|
||||||
doc = compat_etree_fromstring(manifest)
|
doc = compat_etree_fromstring(manifest)
|
||||||
formats = [(int(f.attrib.get('bitrate', -1)), f)
|
formats = [(int(f.attrib.get('bitrate', -1)), f)
|
||||||
@ -312,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)
|
||||||
@ -322,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)
|
||||||
@ -376,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,
|
||||||
@ -34,7 +38,7 @@ class FragmentFD(FileDownloader):
|
|||||||
'continuedl': True,
|
'continuedl': True,
|
||||||
'quiet': True,
|
'quiet': True,
|
||||||
'noprogress': True,
|
'noprogress': True,
|
||||||
'ratelimit': self.params.get('ratelimit', None),
|
'ratelimit': self.params.get('ratelimit'),
|
||||||
'retries': self.params.get('retries', 0),
|
'retries': self.params.get('retries', 0),
|
||||||
'test': self.params.get('test', False),
|
'test': self.params.get('test', False),
|
||||||
}
|
}
|
||||||
@ -59,37 +63,45 @@ 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)
|
|
||||||
progress += frag_progress / float(total_frags)
|
|
||||||
|
|
||||||
state['eta'] = self.calc_eta(
|
state['eta'] = self.calc_eta(
|
||||||
start, time_now, estimated_size, state['downloaded_bytes'] + frag_downloaded_bytes)
|
start, time_now, estimated_size,
|
||||||
state['speed'] = s.get('speed')
|
state['downloaded_bytes'])
|
||||||
|
state['speed'] = s.get('speed') or ctx.get('speed')
|
||||||
|
ctx['speed'] = state['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)
|
||||||
|
@ -1,69 +1,19 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import os
|
import os.path
|
||||||
import re
|
import re
|
||||||
import subprocess
|
|
||||||
|
|
||||||
from .common import FileDownloader
|
|
||||||
from .fragment import FragmentFD
|
from .fragment import FragmentFD
|
||||||
|
|
||||||
from ..compat import compat_urlparse
|
from ..compat import compat_urlparse
|
||||||
from ..postprocessor.ffmpeg import FFmpegPostProcessor
|
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
encodeArgument,
|
|
||||||
encodeFilename,
|
encodeFilename,
|
||||||
sanitize_open,
|
sanitize_open,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class HlsFD(FileDownloader):
|
class HlsFD(FragmentFD):
|
||||||
def real_download(self, filename, info_dict):
|
""" A limited implementation that does not require ffmpeg """
|
||||||
url = info_dict['url']
|
|
||||||
self.report_destination(filename)
|
|
||||||
tmpfilename = self.temp_name(filename)
|
|
||||||
|
|
||||||
ffpp = FFmpegPostProcessor(downloader=self)
|
|
||||||
if not ffpp.available:
|
|
||||||
self.report_error('m3u8 download detected but ffmpeg or avconv could not be found. Please install one.')
|
|
||||||
return False
|
|
||||||
ffpp.check_version()
|
|
||||||
|
|
||||||
args = [ffpp.executable, '-y']
|
|
||||||
|
|
||||||
if info_dict['http_headers'] and re.match(r'^https?://', url):
|
|
||||||
# Trailing \r\n after each HTTP header is important to prevent warning from ffmpeg/avconv:
|
|
||||||
# [http @ 00000000003d2fa0] No trailing CRLF found in HTTP header.
|
|
||||||
args += [
|
|
||||||
'-headers',
|
|
||||||
''.join('%s: %s\r\n' % (key, val) for key, val in info_dict['http_headers'].items())]
|
|
||||||
|
|
||||||
args += ['-i', url, '-f', 'mp4', '-c', 'copy', '-bsf:a', 'aac_adtstoasc']
|
|
||||||
|
|
||||||
args = [encodeArgument(opt) for opt in args]
|
|
||||||
args.append(encodeFilename(ffpp._ffmpeg_filename_argument(tmpfilename), True))
|
|
||||||
|
|
||||||
self._debug_cmd(args)
|
|
||||||
|
|
||||||
retval = subprocess.call(args)
|
|
||||||
if retval == 0:
|
|
||||||
fsize = os.path.getsize(encodeFilename(tmpfilename))
|
|
||||||
self.to_screen('\r[%s] %s bytes' % (args[0], fsize))
|
|
||||||
self.try_rename(tmpfilename, filename)
|
|
||||||
self._hook_progress({
|
|
||||||
'downloaded_bytes': fsize,
|
|
||||||
'total_bytes': fsize,
|
|
||||||
'filename': filename,
|
|
||||||
'status': 'finished',
|
|
||||||
})
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
self.to_stderr('\n')
|
|
||||||
self.report_error('%s exited with code %d' % (ffpp.basename, retval))
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class NativeHlsFD(FragmentFD):
|
|
||||||
""" A more limited implementation that does not require ffmpeg """
|
|
||||||
|
|
||||||
FD_NAME = 'hlsnative'
|
FD_NAME = 'hlsnative'
|
||||||
|
|
||||||
|
@ -7,14 +7,12 @@ import time
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import FileDownloader
|
from .common import FileDownloader
|
||||||
from ..compat import (
|
from ..compat import compat_urllib_error
|
||||||
compat_urllib_request,
|
|
||||||
compat_urllib_error,
|
|
||||||
)
|
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
ContentTooShortError,
|
ContentTooShortError,
|
||||||
encodeFilename,
|
encodeFilename,
|
||||||
sanitize_open,
|
sanitize_open,
|
||||||
|
sanitized_Request,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -29,8 +27,8 @@ class HttpFD(FileDownloader):
|
|||||||
add_headers = info_dict.get('http_headers')
|
add_headers = info_dict.get('http_headers')
|
||||||
if add_headers:
|
if add_headers:
|
||||||
headers.update(add_headers)
|
headers.update(add_headers)
|
||||||
basic_request = compat_urllib_request.Request(url, None, headers)
|
basic_request = sanitized_Request(url, None, headers)
|
||||||
request = compat_urllib_request.Request(url, None, headers)
|
request = sanitized_Request(url, None, headers)
|
||||||
|
|
||||||
is_test = self.params.get('test', False)
|
is_test = self.params.get('test', False)
|
||||||
|
|
||||||
@ -142,8 +140,8 @@ class HttpFD(FileDownloader):
|
|||||||
|
|
||||||
if data_len is not None:
|
if data_len is not None:
|
||||||
data_len = int(data_len) + resume_len
|
data_len = int(data_len) + resume_len
|
||||||
min_data_len = self.params.get("min_filesize", None)
|
min_data_len = self.params.get('min_filesize')
|
||||||
max_data_len = self.params.get("max_filesize", None)
|
max_data_len = self.params.get('max_filesize')
|
||||||
if min_data_len is not None and data_len < min_data_len:
|
if min_data_len is not None and data_len < min_data_len:
|
||||||
self.to_screen('\r[download] File is smaller than min-filesize (%s bytes < %s bytes). Aborting.' % (data_len, min_data_len))
|
self.to_screen('\r[download] File is smaller than min-filesize (%s bytes < %s bytes). Aborting.' % (data_len, min_data_len))
|
||||||
return False
|
return False
|
||||||
|
@ -94,15 +94,15 @@ class RtmpFD(FileDownloader):
|
|||||||
return proc.returncode
|
return proc.returncode
|
||||||
|
|
||||||
url = info_dict['url']
|
url = info_dict['url']
|
||||||
player_url = info_dict.get('player_url', None)
|
player_url = info_dict.get('player_url')
|
||||||
page_url = info_dict.get('page_url', None)
|
page_url = info_dict.get('page_url')
|
||||||
app = info_dict.get('app', None)
|
app = info_dict.get('app')
|
||||||
play_path = info_dict.get('play_path', None)
|
play_path = info_dict.get('play_path')
|
||||||
tc_url = info_dict.get('tc_url', None)
|
tc_url = info_dict.get('tc_url')
|
||||||
flash_version = info_dict.get('flash_version', None)
|
flash_version = info_dict.get('flash_version')
|
||||||
live = info_dict.get('rtmp_live', False)
|
live = info_dict.get('rtmp_live', False)
|
||||||
conn = info_dict.get('rtmp_conn', None)
|
conn = info_dict.get('rtmp_conn')
|
||||||
protocol = info_dict.get('rtmp_protocol', None)
|
protocol = info_dict.get('rtmp_protocol')
|
||||||
real_time = info_dict.get('rtmp_real_time', False)
|
real_time = info_dict.get('rtmp_real_time', False)
|
||||||
no_resume = info_dict.get('no_resume', False)
|
no_resume = info_dict.get('no_resume', False)
|
||||||
continue_dl = self.params.get('continuedl', True)
|
continue_dl = self.params.get('continuedl', True)
|
||||||
@ -117,7 +117,7 @@ class RtmpFD(FileDownloader):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# Download using rtmpdump. rtmpdump returns exit code 2 when
|
# Download using rtmpdump. rtmpdump returns exit code 2 when
|
||||||
# the connection was interrumpted and resuming appears to be
|
# the connection was interrupted and resuming appears to be
|
||||||
# possible. This is part of rtmpdump's normal usage, AFAIK.
|
# possible. This is part of rtmpdump's normal usage, AFAIK.
|
||||||
basic_args = [
|
basic_args = [
|
||||||
'rtmpdump', '--verbose', '-r', url,
|
'rtmpdump', '--verbose', '-r', url,
|
||||||
|
@ -3,24 +3,37 @@ from __future__ import unicode_literals
|
|||||||
from .abc import ABCIE
|
from .abc import ABCIE
|
||||||
from .abc7news import Abc7NewsIE
|
from .abc7news import Abc7NewsIE
|
||||||
from .academicearth import AcademicEarthCourseIE
|
from .academicearth import AcademicEarthCourseIE
|
||||||
|
from .acast import (
|
||||||
|
ACastIE,
|
||||||
|
ACastChannelIE,
|
||||||
|
)
|
||||||
from .addanime import AddAnimeIE
|
from .addanime import AddAnimeIE
|
||||||
from .adobetv import (
|
from .adobetv import (
|
||||||
AdobeTVIE,
|
AdobeTVIE,
|
||||||
|
AdobeTVShowIE,
|
||||||
|
AdobeTVChannelIE,
|
||||||
AdobeTVVideoIE,
|
AdobeTVVideoIE,
|
||||||
)
|
)
|
||||||
from .adultswim import AdultSwimIE
|
from .adultswim import AdultSwimIE
|
||||||
from .aftenposten import AftenpostenIE
|
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 .animeondemand import AnimeOnDemandIE
|
||||||
from .anitube import AnitubeIE
|
from .anitube import AnitubeIE
|
||||||
from .anysex import AnySexIE
|
from .anysex import AnySexIE
|
||||||
from .aol import AolIE
|
from .aol import (
|
||||||
|
AolIE,
|
||||||
|
AolFeaturesIE,
|
||||||
|
)
|
||||||
from .allocine import AllocineIE
|
from .allocine import AllocineIE
|
||||||
from .aparat import AparatIE
|
from .aparat import AparatIE
|
||||||
from .appleconnect import AppleConnectIE
|
from .appleconnect import AppleConnectIE
|
||||||
from .appletrailers import AppleTrailersIE
|
from .appletrailers import (
|
||||||
|
AppleTrailersIE,
|
||||||
|
AppleTrailersSectionIE,
|
||||||
|
)
|
||||||
from .archiveorg import ArchiveOrgIE
|
from .archiveorg import ArchiveOrgIE
|
||||||
from .ard import (
|
from .ard import (
|
||||||
ARDIE,
|
ARDIE,
|
||||||
@ -33,13 +46,17 @@ from .arte import (
|
|||||||
ArteTVCreativeIE,
|
ArteTVCreativeIE,
|
||||||
ArteTVConcertIE,
|
ArteTVConcertIE,
|
||||||
ArteTVFutureIE,
|
ArteTVFutureIE,
|
||||||
|
ArteTVCinemaIE,
|
||||||
ArteTVDDCIE,
|
ArteTVDDCIE,
|
||||||
|
ArteTVMagazineIE,
|
||||||
ArteTVEmbedIE,
|
ArteTVEmbedIE,
|
||||||
)
|
)
|
||||||
from .atresplayer import AtresPlayerIE
|
from .atresplayer import AtresPlayerIE
|
||||||
from .atttechchannel import ATTTechChannelIE
|
from .atttechchannel import ATTTechChannelIE
|
||||||
|
from .audimedia import AudiMediaIE
|
||||||
|
from .audioboom import AudioBoomIE
|
||||||
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
|
||||||
@ -52,11 +69,16 @@ 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 (
|
||||||
|
BleacherReportIE,
|
||||||
|
BleacherReportCMSIE,
|
||||||
|
)
|
||||||
from .blinkx import BlinkxIE
|
from .blinkx import BlinkxIE
|
||||||
from .bliptv import BlipTVIE, BlipTVUserIE
|
|
||||||
from .bloomberg import BloombergIE
|
from .bloomberg import BloombergIE
|
||||||
|
from .bokecc import BokeCCIE
|
||||||
from .bpb import BpbIE
|
from .bpb import BpbIE
|
||||||
from .br import BRIE
|
from .br import BRIE
|
||||||
from .breakcom import BreakIE
|
from .breakcom import BreakIE
|
||||||
@ -71,11 +93,18 @@ from .camdemy import (
|
|||||||
CamdemyIE,
|
CamdemyIE,
|
||||||
CamdemyFolderIE
|
CamdemyFolderIE
|
||||||
)
|
)
|
||||||
from .canal13cl import Canal13clIE
|
|
||||||
from .canalplus import CanalplusIE
|
from .canalplus import CanalplusIE
|
||||||
from .canalc2 import Canalc2IE
|
from .canalc2 import Canalc2IE
|
||||||
|
from .canvas import CanvasIE
|
||||||
|
from .cbc import (
|
||||||
|
CBCIE,
|
||||||
|
CBCPlayerIE,
|
||||||
|
)
|
||||||
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
|
||||||
@ -108,6 +137,7 @@ from .comcarcoff import ComCarCoffIE
|
|||||||
from .commonmistakes import CommonMistakesIE, UnicodeBOMIE
|
from .commonmistakes import CommonMistakesIE, UnicodeBOMIE
|
||||||
from .condenast import CondeNastIE
|
from .condenast import CondeNastIE
|
||||||
from .cracked import CrackedIE
|
from .cracked import CrackedIE
|
||||||
|
from .crackle import CrackleIE
|
||||||
from .criterion import CriterionIE
|
from .criterion import CriterionIE
|
||||||
from .crooksandliars import CrooksAndLiarsIE
|
from .crooksandliars import CrooksAndLiarsIE
|
||||||
from .crunchyroll import (
|
from .crunchyroll import (
|
||||||
@ -116,15 +146,27 @@ 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,
|
||||||
DailymotionUserIE,
|
DailymotionUserIE,
|
||||||
DailymotionCloudIE,
|
DailymotionCloudIE,
|
||||||
)
|
)
|
||||||
from .daum import DaumIE
|
from .daum import (
|
||||||
|
DaumIE,
|
||||||
|
DaumClipIE,
|
||||||
|
DaumPlaylistIE,
|
||||||
|
DaumUserIE,
|
||||||
|
)
|
||||||
from .dbtv import DBTVIE
|
from .dbtv import DBTVIE
|
||||||
from .dcn import DCNIE
|
from .dcn import (
|
||||||
|
DCNIE,
|
||||||
|
DCNVideoIE,
|
||||||
|
DCNLiveIE,
|
||||||
|
DCNSeasonIE,
|
||||||
|
)
|
||||||
from .dctp import DctpTvIE
|
from .dctp import DctpTvIE
|
||||||
from .deezer import DeezerPlaylistIE
|
from .deezer import DeezerPlaylistIE
|
||||||
from .democracynow import DemocracynowIE
|
from .democracynow import DemocracynowIE
|
||||||
@ -147,6 +189,10 @@ from .dumpert import DumpertIE
|
|||||||
from .defense import DefenseGouvFrIE
|
from .defense import DefenseGouvFrIE
|
||||||
from .discovery import DiscoveryIE
|
from .discovery import DiscoveryIE
|
||||||
from .dropbox import DropboxIE
|
from .dropbox import DropboxIE
|
||||||
|
from .dw import (
|
||||||
|
DWIE,
|
||||||
|
DWArticleIE,
|
||||||
|
)
|
||||||
from .eagleplatform import EaglePlatformIE
|
from .eagleplatform import EaglePlatformIE
|
||||||
from .ebaumsworld import EbaumsWorldIE
|
from .ebaumsworld import EbaumsWorldIE
|
||||||
from .echomsk import EchoMskIE
|
from .echomsk import EchoMskIE
|
||||||
@ -184,10 +230,14 @@ from .flickr import FlickrIE
|
|||||||
from .folketinget import FolketingetIE
|
from .folketinget import FolketingetIE
|
||||||
from .footyroom import FootyRoomIE
|
from .footyroom import FootyRoomIE
|
||||||
from .fourtube import FourTubeIE
|
from .fourtube import FourTubeIE
|
||||||
|
from .fox import FOXIE
|
||||||
from .foxgay import FoxgayIE
|
from .foxgay import FoxgayIE
|
||||||
from .foxnews import FoxNewsIE
|
from .foxnews import FoxNewsIE
|
||||||
from .foxsports import FoxSportsIE
|
from .foxsports import FoxSportsIE
|
||||||
from .franceculture import FranceCultureIE
|
from .franceculture import (
|
||||||
|
FranceCultureIE,
|
||||||
|
FranceCultureEmissionIE,
|
||||||
|
)
|
||||||
from .franceinter import FranceInterIE
|
from .franceinter import FranceInterIE
|
||||||
from .francetv import (
|
from .francetv import (
|
||||||
PluzzIE,
|
PluzzIE,
|
||||||
@ -199,7 +249,9 @@ from .francetv import (
|
|||||||
from .freesound import FreesoundIE
|
from .freesound import FreesoundIE
|
||||||
from .freespeech import FreespeechIE
|
from .freespeech import FreespeechIE
|
||||||
from .freevideo import FreeVideoIE
|
from .freevideo import FreeVideoIE
|
||||||
|
from .funimation import FunimationIE
|
||||||
from .funnyordie import FunnyOrDieIE
|
from .funnyordie import FunnyOrDieIE
|
||||||
|
from .gameinformer import GameInformerIE
|
||||||
from .gamekings import GamekingsIE
|
from .gamekings import GamekingsIE
|
||||||
from .gameone import (
|
from .gameone import (
|
||||||
GameOneIE,
|
GameOneIE,
|
||||||
@ -223,9 +275,11 @@ from .globo import (
|
|||||||
from .godtube import GodTubeIE
|
from .godtube import GodTubeIE
|
||||||
from .goldenmoustache import GoldenMoustacheIE
|
from .goldenmoustache import GoldenMoustacheIE
|
||||||
from .golem import GolemIE
|
from .golem import GolemIE
|
||||||
|
from .googledrive import GoogleDriveIE
|
||||||
from .googleplus import GooglePlusIE
|
from .googleplus import GooglePlusIE
|
||||||
from .googlesearch import GoogleSearchIE
|
from .googlesearch import GoogleSearchIE
|
||||||
from .goshgay import GoshgayIE
|
from .goshgay import GoshgayIE
|
||||||
|
from .gputechconf import GPUTechConfIE
|
||||||
from .groupon import GrouponIE
|
from .groupon import GrouponIE
|
||||||
from .hark import HarkIE
|
from .hark import HarkIE
|
||||||
from .hearthisat import HearThisAtIE
|
from .hearthisat import HearThisAtIE
|
||||||
@ -234,16 +288,20 @@ 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
|
||||||
|
from .hotstar import HotStarIE
|
||||||
from .howcast import HowcastIE
|
from .howcast import HowcastIE
|
||||||
from .howstuffworks import HowStuffWorksIE
|
from .howstuffworks import HowStuffWorksIE
|
||||||
from .huffpost import HuffPostIE
|
from .huffpost import HuffPostIE
|
||||||
from .hypem import HypemIE
|
from .hypem import HypemIE
|
||||||
from .iconosquare import IconosquareIE
|
from .iconosquare import IconosquareIE
|
||||||
from .ign import IGNIE, OneUPIE
|
from .ign import (
|
||||||
|
IGNIE,
|
||||||
|
OneUPIE,
|
||||||
|
PCMagIE,
|
||||||
|
)
|
||||||
from .imdb import (
|
from .imdb import (
|
||||||
ImdbIE,
|
ImdbIE,
|
||||||
ImdbListIE
|
ImdbListIE
|
||||||
@ -267,11 +325,12 @@ 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
|
||||||
from .jove import JoveIE
|
from .jove import JoveIE
|
||||||
from .jukebox import JukeboxIE
|
from .jwplatform import JWPlatformIE
|
||||||
from .jpopsukitv import JpopsukiIE
|
from .jpopsukitv import JpopsukiIE
|
||||||
from .kaltura import KalturaIE
|
from .kaltura import KalturaIE
|
||||||
from .kanalplay import KanalPlayIE
|
from .kanalplay import KanalPlayIE
|
||||||
@ -282,9 +341,11 @@ 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
|
||||||
|
from .kusi import KUSIIE
|
||||||
from .kuwo import (
|
from .kuwo import (
|
||||||
KuwoIE,
|
KuwoIE,
|
||||||
KuwoAlbumIE,
|
KuwoAlbumIE,
|
||||||
@ -296,10 +357,11 @@ 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 .letv import (
|
from .lemonde import LemondeIE
|
||||||
LetvIE,
|
from .leeco import (
|
||||||
LetvTvIE,
|
LeIE,
|
||||||
LetvPlaylistIE
|
LePlaylistIE,
|
||||||
|
LetvCloudIE,
|
||||||
)
|
)
|
||||||
from .libsyn import LibsynIE
|
from .libsyn import LibsynIE
|
||||||
from .lifenews import (
|
from .lifenews import (
|
||||||
@ -318,6 +380,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,
|
||||||
@ -326,13 +389,17 @@ from .lynda import (
|
|||||||
from .m6 import M6IE
|
from .m6 import M6IE
|
||||||
from .macgamestore import MacGameStoreIE
|
from .macgamestore import MacGameStoreIE
|
||||||
from .mailru import MailRuIE
|
from .mailru import MailRuIE
|
||||||
|
from .makerschannel import MakersChannelIE
|
||||||
|
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
|
||||||
from .mgoon import MgoonIE
|
from .mgoon import MgoonIE
|
||||||
from .minhateca import MinhatecaIE
|
from .minhateca import MinhatecaIE
|
||||||
from .ministrygrid import MinistryGridIE
|
from .ministrygrid import MinistryGridIE
|
||||||
|
from .minoto import MinotoIE
|
||||||
from .miomio import MioMioIE
|
from .miomio import MioMioIE
|
||||||
from .mit import TechTVMITIE, MITIE, OCWMITIE
|
from .mit import TechTVMITIE, MITIE, OCWMITIE
|
||||||
from .mitele import MiTeleIE
|
from .mitele import MiTeleIE
|
||||||
@ -349,7 +416,6 @@ from .motherless import MotherlessIE
|
|||||||
from .motorsport import MotorsportIE
|
from .motorsport import MotorsportIE
|
||||||
from .movieclips import MovieClipsIE
|
from .movieclips import MovieClipsIE
|
||||||
from .moviezine import MoviezineIE
|
from .moviezine import MoviezineIE
|
||||||
from .movshare import MovShareIE
|
|
||||||
from .mtv import (
|
from .mtv import (
|
||||||
MTVIE,
|
MTVIE,
|
||||||
MTVServicesEmbeddedIE,
|
MTVServicesEmbeddedIE,
|
||||||
@ -402,6 +468,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 (
|
||||||
@ -409,13 +476,20 @@ 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
|
||||||
from .normalboots import NormalbootsIE
|
from .normalboots import NormalbootsIE
|
||||||
from .nosvideo import NosVideoIE
|
from .nosvideo import NosVideoIE
|
||||||
from .nova import NovaIE
|
from .nova import NovaIE
|
||||||
from .novamov import NovaMovIE
|
from .novamov import (
|
||||||
|
NovaMovIE,
|
||||||
|
WholeCloudIE,
|
||||||
|
NowVideoIE,
|
||||||
|
VideoWeedIE,
|
||||||
|
CloudTimeIE,
|
||||||
|
)
|
||||||
from .nowness import (
|
from .nowness import (
|
||||||
NownessIE,
|
NownessIE,
|
||||||
NownessPlaylistIE,
|
NownessPlaylistIE,
|
||||||
@ -425,18 +499,21 @@ from .nowtv import (
|
|||||||
NowTVIE,
|
NowTVIE,
|
||||||
NowTVListIE,
|
NowTVListIE,
|
||||||
)
|
)
|
||||||
from .nowvideo import NowVideoIE
|
from .noz import NozIE
|
||||||
from .npo import (
|
from .npo import (
|
||||||
NPOIE,
|
NPOIE,
|
||||||
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,
|
||||||
|
NRKSkoleIE,
|
||||||
NRKTVIE,
|
NRKTVIE,
|
||||||
)
|
)
|
||||||
from .ntvde import NTVDeIE
|
from .ntvde import NTVDeIE
|
||||||
@ -453,12 +530,14 @@ from .ooyala import (
|
|||||||
OoyalaIE,
|
OoyalaIE,
|
||||||
OoyalaExternalIE,
|
OoyalaExternalIE,
|
||||||
)
|
)
|
||||||
|
from .ora import OraTVIE
|
||||||
from .orf import (
|
from .orf import (
|
||||||
ORFTVthekIE,
|
ORFTVthekIE,
|
||||||
ORFOE1IE,
|
ORFOE1IE,
|
||||||
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
|
||||||
@ -471,6 +550,7 @@ from .planetaplay import PlanetaPlayIE
|
|||||||
from .pladform import PladformIE
|
from .pladform import PladformIE
|
||||||
from .played import PlayedIE
|
from .played import PlayedIE
|
||||||
from .playfm import PlayFMIE
|
from .playfm import PlayFMIE
|
||||||
|
from .plays import PlaysTVIE
|
||||||
from .playtvak import PlaytvakIE
|
from .playtvak import PlaytvakIE
|
||||||
from .playvid import PlayvidIE
|
from .playvid import PlayvidIE
|
||||||
from .playwire import PlaywireIE
|
from .playwire import PlaywireIE
|
||||||
@ -484,6 +564,7 @@ from .pornhd import PornHdIE
|
|||||||
from .pornhub import (
|
from .pornhub import (
|
||||||
PornHubIE,
|
PornHubIE,
|
||||||
PornHubPlaylistIE,
|
PornHubPlaylistIE,
|
||||||
|
PornHubUserVideosIE,
|
||||||
)
|
)
|
||||||
from .pornotube import PornotubeIE
|
from .pornotube import PornotubeIE
|
||||||
from .pornovoisines import PornoVoisinesIE
|
from .pornovoisines import PornoVoisinesIE
|
||||||
@ -506,18 +587,24 @@ from .radiode import RadioDeIE
|
|||||||
from .radiojavan import RadioJavanIE
|
from .radiojavan import RadioJavanIE
|
||||||
from .radiobremen import RadioBremenIE
|
from .radiobremen import RadioBremenIE
|
||||||
from .radiofrance import RadioFranceIE
|
from .radiofrance import RadioFranceIE
|
||||||
from .rai import RaiIE
|
from .rai import (
|
||||||
|
RaiTVIE,
|
||||||
|
RaiIE,
|
||||||
|
)
|
||||||
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 .rice import RICEIE
|
||||||
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
|
||||||
@ -525,6 +612,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,
|
||||||
@ -545,6 +633,7 @@ from .sbs import SBSIE
|
|||||||
from .scivee import SciVeeIE
|
from .scivee import SciVeeIE
|
||||||
from .screencast import ScreencastIE
|
from .screencast import ScreencastIE
|
||||||
from .screencastomatic import ScreencastOMaticIE
|
from .screencastomatic import ScreencastOMaticIE
|
||||||
|
from .screenjunkies import ScreenJunkiesIE
|
||||||
from .screenwavemedia import ScreenwaveMediaIE, TeamFourIE
|
from .screenwavemedia import ScreenwaveMediaIE, TeamFourIE
|
||||||
from .senateisvp import SenateISVPIE
|
from .senateisvp import SenateISVPIE
|
||||||
from .servingsys import ServingSysIE
|
from .servingsys import ServingSysIE
|
||||||
@ -554,6 +643,10 @@ from .shahid import ShahidIE
|
|||||||
from .shared import SharedIE
|
from .shared import SharedIE
|
||||||
from .sharesix import ShareSixIE
|
from .sharesix import ShareSixIE
|
||||||
from .sina import SinaIE
|
from .sina import SinaIE
|
||||||
|
from .skynewsarabia import (
|
||||||
|
SkyNewsArabiaIE,
|
||||||
|
SkyNewsArabiaArticleIE,
|
||||||
|
)
|
||||||
from .slideshare import SlideshareIE
|
from .slideshare import SlideshareIE
|
||||||
from .slutload import SlutloadIE
|
from .slutload import SlutloadIE
|
||||||
from .smotri import (
|
from .smotri import (
|
||||||
@ -568,15 +661,12 @@ from .snagfilms import (
|
|||||||
)
|
)
|
||||||
from .snotr import SnotrIE
|
from .snotr import SnotrIE
|
||||||
from .sohu import SohuIE
|
from .sohu import SohuIE
|
||||||
from .soompi import (
|
|
||||||
SoompiIE,
|
|
||||||
SoompiShowIE,
|
|
||||||
)
|
|
||||||
from .soundcloud import (
|
from .soundcloud import (
|
||||||
SoundcloudIE,
|
SoundcloudIE,
|
||||||
SoundcloudSetIE,
|
SoundcloudSetIE,
|
||||||
SoundcloudUserIE,
|
SoundcloudUserIE,
|
||||||
SoundcloudPlaylistIE
|
SoundcloudPlaylistIE,
|
||||||
|
SoundcloudSearchIE
|
||||||
)
|
)
|
||||||
from .soundgasm import (
|
from .soundgasm import (
|
||||||
SoundgasmIE,
|
SoundgasmIE,
|
||||||
@ -589,7 +679,6 @@ from .southpark import (
|
|||||||
SouthParkEsIE,
|
SouthParkEsIE,
|
||||||
SouthParkNlIE
|
SouthParkNlIE
|
||||||
)
|
)
|
||||||
from .space import SpaceIE
|
|
||||||
from .spankbang import SpankBangIE
|
from .spankbang import SpankBangIE
|
||||||
from .spankwire import SpankwireIE
|
from .spankwire import SpankwireIE
|
||||||
from .spiegel import SpiegelIE, SpiegelArticleIE
|
from .spiegel import SpiegelIE, SpiegelArticleIE
|
||||||
@ -602,7 +691,10 @@ from .sportbox import (
|
|||||||
SportBoxEmbedIE,
|
SportBoxEmbedIE,
|
||||||
)
|
)
|
||||||
from .sportdeutschland import SportDeutschlandIE
|
from .sportdeutschland import SportDeutschlandIE
|
||||||
from .srf import SrfIE
|
from .srgssr import (
|
||||||
|
SRGSSRIE,
|
||||||
|
SRGSSRPlayIE,
|
||||||
|
)
|
||||||
from .srmediathek import SRMediathekIE
|
from .srmediathek import SRMediathekIE
|
||||||
from .ssa import SSAIE
|
from .ssa import SSAIE
|
||||||
from .stanfordoc import StanfordOpenClassroomIE
|
from .stanfordoc import StanfordOpenClassroomIE
|
||||||
@ -629,6 +721,7 @@ from .teachingchannel import TeachingChannelIE
|
|||||||
from .teamcoco import TeamcocoIE
|
from .teamcoco import TeamcocoIE
|
||||||
from .techtalks import TechTalksIE
|
from .techtalks import TechTalksIE
|
||||||
from .ted import TEDIE
|
from .ted import TEDIE
|
||||||
|
from .tele13 import Tele13IE
|
||||||
from .telebruxelles import TeleBruxellesIE
|
from .telebruxelles import TeleBruxellesIE
|
||||||
from .telecinco import TelecincoIE
|
from .telecinco import TelecincoIE
|
||||||
from .telegraaf import TelegraafIE
|
from .telegraaf import TelegraafIE
|
||||||
@ -636,27 +729,30 @@ 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 .theonion import TheOnionIE
|
from .theonion import TheOnionIE
|
||||||
from .theplatform import (
|
from .theplatform import (
|
||||||
ThePlatformIE,
|
ThePlatformIE,
|
||||||
ThePlatformFeedIE,
|
ThePlatformFeedIE,
|
||||||
)
|
)
|
||||||
from .thesixtyone import TheSixtyOneIE
|
from .thesixtyone import TheSixtyOneIE
|
||||||
|
from .thestar import TheStarIE
|
||||||
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,
|
||||||
)
|
)
|
||||||
from .tnaflix import (
|
from .tnaflix import (
|
||||||
|
TNAFlixNetworkEmbedIE,
|
||||||
TNAFlixIE,
|
TNAFlixIE,
|
||||||
EMPFlixIE,
|
EMPFlixIE,
|
||||||
MovieFapIE,
|
MovieFapIE,
|
||||||
)
|
)
|
||||||
|
from .toggle import ToggleIE
|
||||||
from .thvideo import (
|
from .thvideo import (
|
||||||
THVideoIE,
|
THVideoIE,
|
||||||
THVideoPlaylistIE
|
THVideoPlaylistIE
|
||||||
@ -665,12 +761,23 @@ 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 TuneInIE
|
from .tunein import (
|
||||||
|
TuneInClipIE,
|
||||||
|
TuneInStationIE,
|
||||||
|
TuneInProgramIE,
|
||||||
|
TuneInTopicIE,
|
||||||
|
TuneInShortenerIE,
|
||||||
|
)
|
||||||
from .turbo import TurboIE
|
from .turbo import TurboIE
|
||||||
from .tutv import TutvIE
|
from .tutv import TutvIE
|
||||||
from .tv2 import (
|
from .tv2 import (
|
||||||
@ -683,10 +790,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
|
||||||
@ -700,17 +809,23 @@ from .twitch import (
|
|||||||
TwitchBookmarksIE,
|
TwitchBookmarksIE,
|
||||||
TwitchStreamIE,
|
TwitchStreamIE,
|
||||||
)
|
)
|
||||||
from .twitter import TwitterCardIE, TwitterIE
|
from .twitter import (
|
||||||
|
TwitterCardIE,
|
||||||
|
TwitterIE,
|
||||||
|
TwitterAmplifyIE,
|
||||||
|
)
|
||||||
from .ubu import UbuIE
|
from .ubu import UbuIE
|
||||||
from .udemy import (
|
from .udemy import (
|
||||||
UdemyIE,
|
UdemyIE,
|
||||||
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 .usatoday import USATodayIE
|
||||||
from .ustream import UstreamIE, UstreamChannelIE
|
from .ustream import UstreamIE, UstreamChannelIE
|
||||||
|
from .ustudio import UstudioIE
|
||||||
from .varzesh3 import Varzesh3IE
|
from .varzesh3 import Varzesh3IE
|
||||||
from .vbox7 import Vbox7IE
|
from .vbox7 import Vbox7IE
|
||||||
from .veehd import VeeHDIE
|
from .veehd import VeeHDIE
|
||||||
@ -724,15 +839,26 @@ from .vgtv import (
|
|||||||
VGTVIE,
|
VGTVIE,
|
||||||
)
|
)
|
||||||
from .vh1 import VH1IE
|
from .vh1 import VH1IE
|
||||||
from .vice import ViceIE
|
from .vice import (
|
||||||
|
ViceIE,
|
||||||
|
ViceShowIE,
|
||||||
|
)
|
||||||
from .viddler import ViddlerIE
|
from .viddler import ViddlerIE
|
||||||
from .videodetective import VideoDetectiveIE
|
from .videodetective import VideoDetectiveIE
|
||||||
from .videofyme import VideofyMeIE
|
from .videofyme import VideofyMeIE
|
||||||
from .videomega import VideoMegaIE
|
from .videomega import VideoMegaIE
|
||||||
|
from .videomore import (
|
||||||
|
VideomoreIE,
|
||||||
|
VideomoreVideoIE,
|
||||||
|
VideomoreSeasonIE,
|
||||||
|
)
|
||||||
from .videopremium import VideoPremiumIE
|
from .videopremium import VideoPremiumIE
|
||||||
from .videott import VideoTtIE
|
from .videott import VideoTtIE
|
||||||
from .videoweed import VideoWeedIE
|
from .vidme import (
|
||||||
from .vidme import VidmeIE
|
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
|
||||||
@ -743,6 +869,7 @@ from .vimeo import (
|
|||||||
VimeoChannelIE,
|
VimeoChannelIE,
|
||||||
VimeoGroupsIE,
|
VimeoGroupsIE,
|
||||||
VimeoLikesIE,
|
VimeoLikesIE,
|
||||||
|
VimeoOndemandIE,
|
||||||
VimeoReviewIE,
|
VimeoReviewIE,
|
||||||
VimeoUserIE,
|
VimeoUserIE,
|
||||||
VimeoWatchLaterIE,
|
VimeoWatchLaterIE,
|
||||||
@ -782,6 +909,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
|
||||||
@ -833,6 +961,7 @@ from .youtube import (
|
|||||||
YoutubeTruncatedIDIE,
|
YoutubeTruncatedIDIE,
|
||||||
YoutubeTruncatedURLIE,
|
YoutubeTruncatedURLIE,
|
||||||
YoutubeUserIE,
|
YoutubeUserIE,
|
||||||
|
YoutubePlaylistsIE,
|
||||||
YoutubeWatchLaterIE,
|
YoutubeWatchLaterIE,
|
||||||
)
|
)
|
||||||
from .zapiks import ZapiksIE
|
from .zapiks import ZapiksIE
|
||||||
@ -841,6 +970,7 @@ from .zingmp3 import (
|
|||||||
ZingMp3SongIE,
|
ZingMp3SongIE,
|
||||||
ZingMp3AlbumIE,
|
ZingMp3AlbumIE,
|
||||||
)
|
)
|
||||||
|
from .zippcast import ZippCastIE
|
||||||
|
|
||||||
_ALL_CLASSES = [
|
_ALL_CLASSES = [
|
||||||
klass
|
klass
|
||||||
|
@ -23,6 +23,7 @@ class ABCIE(InfoExtractor):
|
|||||||
'title': 'Australia to help staff Ebola treatment centre in Sierra Leone',
|
'title': 'Australia to help staff Ebola treatment centre in Sierra Leone',
|
||||||
'description': 'md5:809ad29c67a05f54eb41f2a105693a67',
|
'description': 'md5:809ad29c67a05f54eb41f2a105693a67',
|
||||||
},
|
},
|
||||||
|
'skip': 'this video has expired',
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://www.abc.net.au/news/2015-08-17/warren-entsch-introduces-same-sex-marriage-bill/6702326',
|
'url': 'http://www.abc.net.au/news/2015-08-17/warren-entsch-introduces-same-sex-marriage-bill/6702326',
|
||||||
'md5': 'db2a5369238b51f9811ad815b69dc086',
|
'md5': 'db2a5369238b51f9811ad815b69dc086',
|
||||||
@ -36,6 +37,7 @@ class ABCIE(InfoExtractor):
|
|||||||
'title': 'Marriage Equality: Warren Entsch introduces same sex marriage bill',
|
'title': 'Marriage Equality: Warren Entsch introduces same sex marriage bill',
|
||||||
},
|
},
|
||||||
'add_ie': ['Youtube'],
|
'add_ie': ['Youtube'],
|
||||||
|
'skip': 'Not accessible from Travis CI server',
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://www.abc.net.au/news/2015-10-23/nab-lifts-interest-rates-following-westpac-and-cba/6880080',
|
'url': 'http://www.abc.net.au/news/2015-10-23/nab-lifts-interest-rates-following-westpac-and-cba/6880080',
|
||||||
'md5': 'b96eee7c9edf4fc5a358a0252881cc1f',
|
'md5': 'b96eee7c9edf4fc5a358a0252881cc1f',
|
||||||
@ -58,6 +60,9 @@ class ABCIE(InfoExtractor):
|
|||||||
r'inline(?P<type>Video|Audio|YouTube)Data\.push\((?P<json_data>[^)]+)\);',
|
r'inline(?P<type>Video|Audio|YouTube)Data\.push\((?P<json_data>[^)]+)\);',
|
||||||
webpage)
|
webpage)
|
||||||
if mobj is None:
|
if mobj is None:
|
||||||
|
expired = self._html_search_regex(r'(?s)class="expired-(?:video|audio)".+?<span>(.+?)</span>', webpage, 'expired', None)
|
||||||
|
if expired:
|
||||||
|
raise ExtractorError('%s said: %s' % (self.IE_NAME, expired), expected=True)
|
||||||
raise ExtractorError('Unable to extract video urls')
|
raise ExtractorError('Unable to extract video urls')
|
||||||
|
|
||||||
urls_info = self._parse_json(
|
urls_info = self._parse_json(
|
||||||
|
@ -44,7 +44,6 @@ class Abc7NewsIE(InfoExtractor):
|
|||||||
'contentURL', webpage, 'm3u8 url', fatal=True)
|
'contentURL', webpage, 'm3u8 url', fatal=True)
|
||||||
|
|
||||||
formats = self._extract_m3u8_formats(m3u8, display_id, 'mp4')
|
formats = self._extract_m3u8_formats(m3u8, display_id, 'mp4')
|
||||||
self._sort_formats(formats)
|
|
||||||
|
|
||||||
title = self._og_search_title(webpage).strip()
|
title = self._og_search_title(webpage).strip()
|
||||||
description = self._og_search_description(webpage).strip()
|
description = self._og_search_description(webpage).strip()
|
||||||
|
72
youtube_dl/extractor/acast.py
Normal file
72
youtube_dl/extractor/acast.py
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
# 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
|
||||||
|
|
||||||
|
|
||||||
|
class ACastIE(InfoExtractor):
|
||||||
|
IE_NAME = 'acast'
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?acast\.com/(?P<channel>[^/]+)/(?P<id>[^/#?]+)'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'https://www.acast.com/condenasttraveler/-where-are-you-taipei-101-taiwan',
|
||||||
|
'md5': 'ada3de5a1e3a2a381327d749854788bb',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '57de3baa-4bb0-487e-9418-2692c1277a34',
|
||||||
|
'ext': 'mp3',
|
||||||
|
'title': '"Where Are You?": Taipei 101, Taiwan',
|
||||||
|
'timestamp': 1196172000000,
|
||||||
|
'description': 'md5:a0b4ef3634e63866b542e5b1199a1a0e',
|
||||||
|
'duration': 211,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
channel, display_id = re.match(self._VALID_URL, url).groups()
|
||||||
|
|
||||||
|
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 {
|
||||||
|
'id': compat_str(cast_data['id']),
|
||||||
|
'display_id': display_id,
|
||||||
|
'url': cast_data['blings'][0]['audio'],
|
||||||
|
'title': cast_data['name'],
|
||||||
|
'description': cast_data.get('description'),
|
||||||
|
'thumbnail': cast_data.get('image'),
|
||||||
|
'timestamp': int_or_none(cast_data.get('publishingDate')),
|
||||||
|
'duration': int_or_none(cast_data.get('duration')),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ACastChannelIE(InfoExtractor):
|
||||||
|
IE_NAME = 'acast:channel'
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?acast\.com/(?P<id>[^/#?]+)'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'https://www.acast.com/condenasttraveler',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '50544219-29bb-499e-a083-6087f4cb7797',
|
||||||
|
'title': 'Condé Nast Traveler Podcast',
|
||||||
|
'description': 'md5:98646dee22a5b386626ae31866638fbd',
|
||||||
|
},
|
||||||
|
'playlist_mincount': 20,
|
||||||
|
}
|
||||||
|
_API_BASE_URL = 'https://www.acast.com/api/'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def suitable(cls, url):
|
||||||
|
return False if ACastIE.suitable(url) else super(ACastChannelIE, cls).suitable(url)
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
display_id = self._match_id(url)
|
||||||
|
channel_data = self._download_json(self._API_BASE_URL + 'channels/%s' % display_id, display_id)
|
||||||
|
casts = self._download_json(self._API_BASE_URL + 'channels/%s/acasts' % display_id, display_id)
|
||||||
|
entries = [self.url_result('https://www.acast.com/%s/%s' % (display_id, cast['url']), 'ACast') for cast in casts]
|
||||||
|
|
||||||
|
return self.playlist_result(entries, compat_str(channel_data['id']), channel_data['name'], channel_data.get('description'))
|
@ -1,23 +1,32 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
from ..compat import compat_str
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
parse_duration,
|
parse_duration,
|
||||||
unified_strdate,
|
unified_strdate,
|
||||||
str_to_int,
|
str_to_int,
|
||||||
|
int_or_none,
|
||||||
float_or_none,
|
float_or_none,
|
||||||
ISO639Utils,
|
ISO639Utils,
|
||||||
|
determine_ext,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class AdobeTVIE(InfoExtractor):
|
class AdobeTVBaseIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://tv\.adobe\.com/watch/[^/]+/(?P<id>[^/]+)'
|
_API_BASE_URL = 'http://tv.adobe.com/api/v4/'
|
||||||
|
|
||||||
|
|
||||||
|
class AdobeTVIE(AdobeTVBaseIE):
|
||||||
|
_VALID_URL = r'https?://tv\.adobe\.com/(?:(?P<language>fr|de|es|jp)/)?watch/(?P<show_urlname>[^/]+)/(?P<id>[^/]+)'
|
||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://tv.adobe.com/watch/the-complete-picture-with-julieanne-kost/quick-tip-how-to-draw-a-circle-around-an-object-in-photoshop/',
|
'url': 'http://tv.adobe.com/watch/the-complete-picture-with-julieanne-kost/quick-tip-how-to-draw-a-circle-around-an-object-in-photoshop/',
|
||||||
'md5': '9bc5727bcdd55251f35ad311ca74fa1e',
|
'md5': '9bc5727bcdd55251f35ad311ca74fa1e',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'quick-tip-how-to-draw-a-circle-around-an-object-in-photoshop',
|
'id': '10981',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Quick Tip - How to Draw a Circle Around an Object in Photoshop',
|
'title': 'Quick Tip - How to Draw a Circle Around an Object in Photoshop',
|
||||||
'description': 'md5:99ec318dc909d7ba2a1f2b038f7d2311',
|
'description': 'md5:99ec318dc909d7ba2a1f2b038f7d2311',
|
||||||
@ -29,50 +38,106 @@ class AdobeTVIE(InfoExtractor):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
language, show_urlname, urlname = re.match(self._VALID_URL, url).groups()
|
||||||
webpage = self._download_webpage(url, video_id)
|
if not language:
|
||||||
|
language = 'en'
|
||||||
|
|
||||||
player = self._parse_json(
|
video_data = self._download_json(
|
||||||
self._search_regex(r'html5player:\s*({.+?})\s*\n', webpage, 'player'),
|
self._API_BASE_URL + 'episode/get/?language=%s&show_urlname=%s&urlname=%s&disclosure=standard' % (language, show_urlname, urlname),
|
||||||
video_id)
|
urlname)['data'][0]
|
||||||
|
|
||||||
title = player.get('title') or self._search_regex(
|
|
||||||
r'data-title="([^"]+)"', webpage, 'title')
|
|
||||||
description = self._og_search_description(webpage)
|
|
||||||
thumbnail = self._og_search_thumbnail(webpage)
|
|
||||||
|
|
||||||
upload_date = unified_strdate(
|
|
||||||
self._html_search_meta('datepublished', webpage, 'upload date'))
|
|
||||||
|
|
||||||
duration = parse_duration(
|
|
||||||
self._html_search_meta('duration', webpage, 'duration') or
|
|
||||||
self._search_regex(
|
|
||||||
r'Runtime:\s*(\d{2}:\d{2}:\d{2})',
|
|
||||||
webpage, 'duration', fatal=False))
|
|
||||||
|
|
||||||
view_count = str_to_int(self._search_regex(
|
|
||||||
r'<div class="views">\s*Views?:\s*([\d,.]+)\s*</div>',
|
|
||||||
webpage, 'view count'))
|
|
||||||
|
|
||||||
formats = [{
|
formats = [{
|
||||||
'url': source['src'],
|
'url': source['url'],
|
||||||
'format_id': source.get('quality') or source['src'].split('-')[-1].split('.')[0] or None,
|
'format_id': source.get('quality_level') or source['url'].split('-')[-1].split('.')[0] or None,
|
||||||
'tbr': source.get('bitrate'),
|
'width': int_or_none(source.get('width')),
|
||||||
} for source in player['sources']]
|
'height': int_or_none(source.get('height')),
|
||||||
|
'tbr': int_or_none(source.get('video_data_rate')),
|
||||||
|
} for source in video_data['videos']]
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': compat_str(video_data['id']),
|
||||||
'title': title,
|
'title': video_data['title'],
|
||||||
'description': description,
|
'description': video_data.get('description'),
|
||||||
'thumbnail': thumbnail,
|
'thumbnail': video_data.get('thumbnail'),
|
||||||
'upload_date': upload_date,
|
'upload_date': unified_strdate(video_data.get('start_date')),
|
||||||
'duration': duration,
|
'duration': parse_duration(video_data.get('duration')),
|
||||||
'view_count': view_count,
|
'view_count': str_to_int(video_data.get('playcount')),
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class AdobeTVPlaylistBaseIE(AdobeTVBaseIE):
|
||||||
|
def _parse_page_data(self, page_data):
|
||||||
|
return [self.url_result(self._get_element_url(element_data)) for element_data in page_data]
|
||||||
|
|
||||||
|
def _extract_playlist_entries(self, url, display_id):
|
||||||
|
page = self._download_json(url, display_id)
|
||||||
|
entries = self._parse_page_data(page['data'])
|
||||||
|
for page_num in range(2, page['paging']['pages'] + 1):
|
||||||
|
entries.extend(self._parse_page_data(
|
||||||
|
self._download_json(url + '&page=%d' % page_num, display_id)['data']))
|
||||||
|
return entries
|
||||||
|
|
||||||
|
|
||||||
|
class AdobeTVShowIE(AdobeTVPlaylistBaseIE):
|
||||||
|
_VALID_URL = r'https?://tv\.adobe\.com/(?:(?P<language>fr|de|es|jp)/)?show/(?P<id>[^/]+)'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://tv.adobe.com/show/the-complete-picture-with-julieanne-kost',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '36',
|
||||||
|
'title': 'The Complete Picture with Julieanne Kost',
|
||||||
|
'description': 'md5:fa50867102dcd1aa0ddf2ab039311b27',
|
||||||
|
},
|
||||||
|
'playlist_mincount': 136,
|
||||||
|
}
|
||||||
|
|
||||||
|
def _get_element_url(self, element_data):
|
||||||
|
return element_data['urls'][0]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
language, show_urlname = re.match(self._VALID_URL, url).groups()
|
||||||
|
if not language:
|
||||||
|
language = 'en'
|
||||||
|
query = 'language=%s&show_urlname=%s' % (language, show_urlname)
|
||||||
|
|
||||||
|
show_data = self._download_json(self._API_BASE_URL + 'show/get/?%s' % query, show_urlname)['data'][0]
|
||||||
|
|
||||||
|
return self.playlist_result(
|
||||||
|
self._extract_playlist_entries(self._API_BASE_URL + 'episode/?%s' % query, show_urlname),
|
||||||
|
compat_str(show_data['id']),
|
||||||
|
show_data['show_name'],
|
||||||
|
show_data['show_description'])
|
||||||
|
|
||||||
|
|
||||||
|
class AdobeTVChannelIE(AdobeTVPlaylistBaseIE):
|
||||||
|
_VALID_URL = r'https?://tv\.adobe\.com/(?:(?P<language>fr|de|es|jp)/)?channel/(?P<id>[^/]+)(?:/(?P<category_urlname>[^/]+))?'
|
||||||
|
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://tv.adobe.com/channel/development',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'development',
|
||||||
|
},
|
||||||
|
'playlist_mincount': 96,
|
||||||
|
}
|
||||||
|
|
||||||
|
def _get_element_url(self, element_data):
|
||||||
|
return element_data['url']
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
language, channel_urlname, category_urlname = re.match(self._VALID_URL, url).groups()
|
||||||
|
if not language:
|
||||||
|
language = 'en'
|
||||||
|
query = 'language=%s&channel_urlname=%s' % (language, channel_urlname)
|
||||||
|
if category_urlname:
|
||||||
|
query += '&category_urlname=%s' % category_urlname
|
||||||
|
|
||||||
|
return self.playlist_result(
|
||||||
|
self._extract_playlist_entries(self._API_BASE_URL + 'show/?%s' % query, channel_urlname),
|
||||||
|
channel_urlname)
|
||||||
|
|
||||||
|
|
||||||
class AdobeTVVideoIE(InfoExtractor):
|
class AdobeTVVideoIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://video\.tv\.adobe\.com/v/(?P<id>\d+)'
|
_VALID_URL = r'https?://video\.tv\.adobe\.com/v/(?P<id>\d+)'
|
||||||
|
|
||||||
@ -91,28 +156,25 @@ class AdobeTVVideoIE(InfoExtractor):
|
|||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
|
video_data = self._download_json(url + '?format=json', video_id)
|
||||||
webpage = self._download_webpage(url, video_id)
|
|
||||||
|
|
||||||
player_params = self._parse_json(self._search_regex(
|
|
||||||
r'var\s+bridge\s*=\s*([^;]+);', webpage, 'player parameters'),
|
|
||||||
video_id)
|
|
||||||
|
|
||||||
formats = [{
|
formats = [{
|
||||||
|
'format_id': '%s-%s' % (determine_ext(source['src']), source.get('height')),
|
||||||
'url': source['src'],
|
'url': source['src'],
|
||||||
'width': source.get('width'),
|
'width': int_or_none(source.get('width')),
|
||||||
'height': source.get('height'),
|
'height': int_or_none(source.get('height')),
|
||||||
'tbr': source.get('bitrate'),
|
'tbr': int_or_none(source.get('bitrate')),
|
||||||
} for source in player_params['sources']]
|
} for source in video_data['sources']]
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
# For both metadata and downloaded files the duration varies among
|
# For both metadata and downloaded files the duration varies among
|
||||||
# formats. I just pick the max one
|
# formats. I just pick the max one
|
||||||
duration = max(filter(None, [
|
duration = max(filter(None, [
|
||||||
float_or_none(source.get('duration'), scale=1000)
|
float_or_none(source.get('duration'), scale=1000)
|
||||||
for source in player_params['sources']]))
|
for source in video_data['sources']]))
|
||||||
|
|
||||||
subtitles = {}
|
subtitles = {}
|
||||||
for translation in player_params.get('translations', []):
|
for translation in video_data.get('translations', []):
|
||||||
lang_id = translation.get('language_w3c') or ISO639Utils.long2short(translation['language_medium'])
|
lang_id = translation.get('language_w3c') or ISO639Utils.long2short(translation['language_medium'])
|
||||||
if lang_id not in subtitles:
|
if lang_id not in subtitles:
|
||||||
subtitles[lang_id] = []
|
subtitles[lang_id] = []
|
||||||
@ -124,8 +186,9 @@ class AdobeTVVideoIE(InfoExtractor):
|
|||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
'title': player_params['title'],
|
'title': video_data['title'],
|
||||||
'description': self._og_search_description(webpage),
|
'description': video_data.get('description'),
|
||||||
|
'thumbnail': video_data['video'].get('poster'),
|
||||||
'duration': duration,
|
'duration': duration,
|
||||||
'subtitles': subtitles,
|
'subtitles': subtitles,
|
||||||
}
|
}
|
||||||
|
@ -68,7 +68,7 @@ class AdultSwimIE(InfoExtractor):
|
|||||||
'md5': '3e346a2ab0087d687a05e1e7f3b3e529',
|
'md5': '3e346a2ab0087d687a05e1e7f3b3e529',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'sY3cMUR_TbuE4YmdjzbIcQ-0',
|
'id': 'sY3cMUR_TbuE4YmdjzbIcQ-0',
|
||||||
'ext': 'flv',
|
'ext': 'mp4',
|
||||||
'title': 'Tim and Eric Awesome Show Great Job! - Dr. Steve Brule, For Your Wine',
|
'title': 'Tim and Eric Awesome Show Great Job! - Dr. Steve Brule, For Your Wine',
|
||||||
'description': 'Dr. Brule reports live from Wine Country with a special report on wines. \r\nWatch Tim and Eric Awesome Show Great Job! episode #20, "Embarrassed" on Adult Swim.\r\n\r\n',
|
'description': 'Dr. Brule reports live from Wine Country with a special report on wines. \r\nWatch Tim and Eric Awesome Show Great Job! episode #20, "Embarrassed" on Adult Swim.\r\n\r\n',
|
||||||
},
|
},
|
||||||
@ -79,6 +79,10 @@ class AdultSwimIE(InfoExtractor):
|
|||||||
'title': 'Tim and Eric Awesome Show Great Job! - Dr. Steve Brule, For Your Wine',
|
'title': 'Tim and Eric Awesome Show Great Job! - Dr. Steve Brule, For Your Wine',
|
||||||
'description': 'Dr. Brule reports live from Wine Country with a special report on wines. \r\nWatch Tim and Eric Awesome Show Great Job! episode #20, "Embarrassed" on Adult Swim.\r\n\r\n',
|
'description': 'Dr. Brule reports live from Wine Country with a special report on wines. \r\nWatch Tim and Eric Awesome Show Great Job! episode #20, "Embarrassed" on Adult Swim.\r\n\r\n',
|
||||||
},
|
},
|
||||||
|
'params': {
|
||||||
|
# m3u8 download
|
||||||
|
'skip_download': True,
|
||||||
|
}
|
||||||
}]
|
}]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -183,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
|
@ -1,23 +0,0 @@
|
|||||||
# coding: utf-8
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
|
||||||
|
|
||||||
|
|
||||||
class AftenpostenIE(InfoExtractor):
|
|
||||||
_VALID_URL = r'https?://(?:www\.)?aftenposten\.no/webtv/(?:#!/)?video/(?P<id>\d+)'
|
|
||||||
_TEST = {
|
|
||||||
'url': 'http://www.aftenposten.no/webtv/#!/video/21039/trailer-sweatshop-i-can-t-take-any-more',
|
|
||||||
'md5': 'fd828cd29774a729bf4d4425fe192972',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '21039',
|
|
||||||
'ext': 'mov',
|
|
||||||
'title': 'TRAILER: "Sweatshop" - I can´t take any more',
|
|
||||||
'description': 'md5:21891f2b0dd7ec2f78d84a50e54f8238',
|
|
||||||
'timestamp': 1416927969,
|
|
||||||
'upload_date': '20141125',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
|
||||||
return self.url_result('xstream:ap:%s' % self._match_id(url), 'Xstream')
|
|
@ -13,24 +13,18 @@ class AlJazeeraIE(InfoExtractor):
|
|||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'The Slum - Episode 1: Deliverance',
|
'title': 'The Slum - Episode 1: Deliverance',
|
||||||
'description': 'As a birth attendant advocating for family planning, Remy is on the frontline of Tondo\'s battle with overcrowding.',
|
'description': 'As a birth attendant advocating for family planning, Remy is on the frontline of Tondo\'s battle with overcrowding.',
|
||||||
'uploader': 'Al Jazeera English',
|
'uploader_id': '665003303001',
|
||||||
|
'timestamp': 1411116829,
|
||||||
|
'upload_date': '20140919',
|
||||||
},
|
},
|
||||||
'add_ie': ['BrightcoveLegacy'],
|
'add_ie': ['BrightcoveNew'],
|
||||||
'skip': 'Not accessible from Travis CI server',
|
'skip': 'Not accessible from Travis CI server',
|
||||||
}
|
}
|
||||||
|
BRIGHTCOVE_URL_TEMPLATE = 'http://players.brightcove.net/665003303001/default_default/index.html?videoId=%s'
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
program_name = self._match_id(url)
|
program_name = self._match_id(url)
|
||||||
webpage = self._download_webpage(url, program_name)
|
webpage = self._download_webpage(url, program_name)
|
||||||
brightcove_id = self._search_regex(
|
brightcove_id = self._search_regex(
|
||||||
r'RenderPagesVideo\(\'(.+?)\'', webpage, 'brightcove id')
|
r'RenderPagesVideo\(\'(.+?)\'', webpage, 'brightcove id')
|
||||||
|
return self.url_result(self.BRIGHTCOVE_URL_TEMPLATE % brightcove_id, 'BrightcoveNew', brightcove_id)
|
||||||
return {
|
|
||||||
'_type': 'url',
|
|
||||||
'url': (
|
|
||||||
'brightcove:'
|
|
||||||
'playerKey=AQ~~%2CAAAAmtVJIFk~%2CTVGOQ5ZTwJbeMWnq5d_H4MOM57xfzApc'
|
|
||||||
'&%40videoPlayer={0}'.format(brightcove_id)
|
|
||||||
),
|
|
||||||
'ie_key': 'BrightcoveLegacy',
|
|
||||||
}
|
|
||||||
|
@ -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 = []
|
||||||
|
81
youtube_dl/extractor/amp.py
Normal file
81
youtube_dl/extractor/amp.py
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
int_or_none,
|
||||||
|
parse_iso8601,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AMPIE(InfoExtractor):
|
||||||
|
# parse Akamai Adaptive Media Player feed
|
||||||
|
def _extract_feed_info(self, url):
|
||||||
|
item = self._download_json(
|
||||||
|
url, None, 'Downloading Akamai AMP feed',
|
||||||
|
'Unable to download Akamai AMP feed')['channel']['item']
|
||||||
|
|
||||||
|
video_id = item['guid']
|
||||||
|
|
||||||
|
def get_media_node(name, default=None):
|
||||||
|
media_name = 'media-%s' % name
|
||||||
|
media_group = item.get('media-group') or item
|
||||||
|
return media_group.get(media_name) or item.get(media_name) or item.get(name, default)
|
||||||
|
|
||||||
|
thumbnails = []
|
||||||
|
media_thumbnail = get_media_node('thumbnail')
|
||||||
|
if media_thumbnail:
|
||||||
|
if isinstance(media_thumbnail, dict):
|
||||||
|
media_thumbnail = [media_thumbnail]
|
||||||
|
for thumbnail_data in media_thumbnail:
|
||||||
|
thumbnail = thumbnail_data['@attributes']
|
||||||
|
thumbnails.append({
|
||||||
|
'url': self._proto_relative_url(thumbnail['url'], 'http:'),
|
||||||
|
'width': int_or_none(thumbnail.get('width')),
|
||||||
|
'height': int_or_none(thumbnail.get('height')),
|
||||||
|
})
|
||||||
|
|
||||||
|
subtitles = {}
|
||||||
|
media_subtitle = get_media_node('subTitle')
|
||||||
|
if media_subtitle:
|
||||||
|
if isinstance(media_subtitle, dict):
|
||||||
|
media_subtitle = [media_subtitle]
|
||||||
|
for subtitle_data in media_subtitle:
|
||||||
|
subtitle = subtitle_data['@attributes']
|
||||||
|
lang = subtitle.get('lang') or 'en'
|
||||||
|
subtitles[lang] = [{'url': subtitle['href']}]
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
media_content = get_media_node('content')
|
||||||
|
if isinstance(media_content, dict):
|
||||||
|
media_content = [media_content]
|
||||||
|
for media_data in media_content:
|
||||||
|
media = media_data['@attributes']
|
||||||
|
media_type = media['type']
|
||||||
|
if media_type == 'video/f4m':
|
||||||
|
formats.extend(self._extract_f4m_formats(
|
||||||
|
media['url'] + '?hdcore=3.4.0&plugin=aasp-3.4.0.132.124',
|
||||||
|
video_id, f4m_id='hds', fatal=False))
|
||||||
|
elif media_type == 'application/x-mpegURL':
|
||||||
|
formats.extend(self._extract_m3u8_formats(
|
||||||
|
media['url'], video_id, 'mp4', m3u8_id='hls', fatal=False))
|
||||||
|
else:
|
||||||
|
formats.append({
|
||||||
|
'format_id': media_data['media-category']['@attributes']['label'],
|
||||||
|
'url': media['url'],
|
||||||
|
'tbr': int_or_none(media.get('bitrate')),
|
||||||
|
'filesize': int_or_none(media.get('fileSize')),
|
||||||
|
})
|
||||||
|
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': get_media_node('title'),
|
||||||
|
'description': get_media_node('description'),
|
||||||
|
'thumbnails': thumbnails,
|
||||||
|
'timestamp': parse_iso8601(item.get('pubDate'), ' '),
|
||||||
|
'duration': int_or_none(media_content[0].get('@attributes', {}).get('duration')),
|
||||||
|
'subtitles': subtitles,
|
||||||
|
'formats': formats,
|
||||||
|
}
|
160
youtube_dl/extractor/animeondemand.py
Normal file
160
youtube_dl/extractor/animeondemand.py
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..compat import compat_urlparse
|
||||||
|
from ..utils import (
|
||||||
|
determine_ext,
|
||||||
|
encode_dict,
|
||||||
|
ExtractorError,
|
||||||
|
sanitized_Request,
|
||||||
|
urlencode_postdata,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AnimeOnDemandIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?anime-on-demand\.de/anime/(?P<id>\d+)'
|
||||||
|
_LOGIN_URL = 'https://www.anime-on-demand.de/users/sign_in'
|
||||||
|
_APPLY_HTML5_URL = 'https://www.anime-on-demand.de/html5apply'
|
||||||
|
_NETRC_MACHINE = 'animeondemand'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'https://www.anime-on-demand.de/anime/161',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '161',
|
||||||
|
'title': 'Grimgar, Ashes and Illusions (OmU)',
|
||||||
|
'description': 'md5:6681ce3c07c7189d255ac6ab23812d31',
|
||||||
|
},
|
||||||
|
'playlist_mincount': 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
def _login(self):
|
||||||
|
(username, password) = self._get_login_info()
|
||||||
|
if username is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
login_page = self._download_webpage(
|
||||||
|
self._LOGIN_URL, None, 'Downloading login page')
|
||||||
|
|
||||||
|
login_form = self._form_hidden_inputs('new_user', login_page)
|
||||||
|
|
||||||
|
login_form.update({
|
||||||
|
'user[login]': username,
|
||||||
|
'user[password]': password,
|
||||||
|
})
|
||||||
|
|
||||||
|
post_url = self._search_regex(
|
||||||
|
r'<form[^>]+action=(["\'])(?P<url>.+?)\1', login_page,
|
||||||
|
'post url', default=self._LOGIN_URL, group='url')
|
||||||
|
|
||||||
|
if not post_url.startswith('http'):
|
||||||
|
post_url = compat_urlparse.urljoin(self._LOGIN_URL, post_url)
|
||||||
|
|
||||||
|
request = sanitized_Request(
|
||||||
|
post_url, urlencode_postdata(encode_dict(login_form)))
|
||||||
|
request.add_header('Referer', self._LOGIN_URL)
|
||||||
|
|
||||||
|
response = self._download_webpage(
|
||||||
|
request, None, 'Logging in as %s' % username)
|
||||||
|
|
||||||
|
if all(p not in response for p in ('>Logout<', 'href="/users/sign_out"')):
|
||||||
|
error = self._search_regex(
|
||||||
|
r'<p class="alert alert-danger">(.+?)</p>',
|
||||||
|
response, 'error', default=None)
|
||||||
|
if error:
|
||||||
|
raise ExtractorError('Unable to login: %s' % error, expected=True)
|
||||||
|
raise ExtractorError('Unable to log in')
|
||||||
|
|
||||||
|
def _real_initialize(self):
|
||||||
|
self._login()
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
anime_id = self._match_id(url)
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, anime_id)
|
||||||
|
|
||||||
|
if 'data-playlist=' not in webpage:
|
||||||
|
self._download_webpage(
|
||||||
|
self._APPLY_HTML5_URL, anime_id,
|
||||||
|
'Activating HTML5 beta', 'Unable to apply HTML5 beta')
|
||||||
|
webpage = self._download_webpage(url, anime_id)
|
||||||
|
|
||||||
|
csrf_token = self._html_search_meta(
|
||||||
|
'csrf-token', webpage, 'csrf token', fatal=True)
|
||||||
|
|
||||||
|
anime_title = self._html_search_regex(
|
||||||
|
r'(?s)<h1[^>]+itemprop="name"[^>]*>(.+?)</h1>',
|
||||||
|
webpage, 'anime name')
|
||||||
|
anime_description = self._html_search_regex(
|
||||||
|
r'(?s)<div[^>]+itemprop="description"[^>]*>(.+?)</div>',
|
||||||
|
webpage, 'anime description', default=None)
|
||||||
|
|
||||||
|
entries = []
|
||||||
|
|
||||||
|
for episode_html in re.findall(r'(?s)<h3[^>]+class="episodebox-title".+?>Episodeninhalt<', webpage):
|
||||||
|
m = re.search(
|
||||||
|
r'class="episodebox-title"[^>]+title="Episode (?P<number>\d+) - (?P<title>.+?)"', episode_html)
|
||||||
|
if not m:
|
||||||
|
continue
|
||||||
|
|
||||||
|
episode_number = int(m.group('number'))
|
||||||
|
episode_title = m.group('title')
|
||||||
|
video_id = 'episode-%d' % episode_number
|
||||||
|
|
||||||
|
common_info = {
|
||||||
|
'id': video_id,
|
||||||
|
'series': anime_title,
|
||||||
|
'episode': episode_title,
|
||||||
|
'episode_number': episode_number,
|
||||||
|
}
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
|
||||||
|
playlist_url = self._search_regex(
|
||||||
|
r'data-playlist=(["\'])(?P<url>.+?)\1',
|
||||||
|
episode_html, 'data playlist', default=None, group='url')
|
||||||
|
if playlist_url:
|
||||||
|
request = sanitized_Request(
|
||||||
|
compat_urlparse.urljoin(url, playlist_url),
|
||||||
|
headers={
|
||||||
|
'X-Requested-With': 'XMLHttpRequest',
|
||||||
|
'X-CSRF-Token': csrf_token,
|
||||||
|
'Referer': url,
|
||||||
|
'Accept': 'application/json, text/javascript, */*; q=0.01',
|
||||||
|
})
|
||||||
|
|
||||||
|
playlist = self._download_json(
|
||||||
|
request, video_id, 'Downloading playlist JSON', fatal=False)
|
||||||
|
if playlist:
|
||||||
|
playlist = playlist['playlist'][0]
|
||||||
|
title = playlist['title']
|
||||||
|
description = playlist.get('description')
|
||||||
|
for source in playlist.get('sources', []):
|
||||||
|
file_ = source.get('file')
|
||||||
|
if file_ and determine_ext(file_) == 'm3u8':
|
||||||
|
formats = self._extract_m3u8_formats(
|
||||||
|
file_, video_id, 'mp4',
|
||||||
|
entry_protocol='m3u8_native', m3u8_id='hls')
|
||||||
|
|
||||||
|
if formats:
|
||||||
|
f = common_info.copy()
|
||||||
|
f.update({
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'formats': formats,
|
||||||
|
})
|
||||||
|
entries.append(f)
|
||||||
|
|
||||||
|
m = re.search(
|
||||||
|
r'data-dialog-header=(["\'])(?P<title>.+?)\1[^>]+href=(["\'])(?P<href>.+?)\3[^>]*>Teaser<',
|
||||||
|
episode_html)
|
||||||
|
if m:
|
||||||
|
f = common_info.copy()
|
||||||
|
f.update({
|
||||||
|
'id': '%s-teaser' % f['id'],
|
||||||
|
'title': m.group('title'),
|
||||||
|
'url': compat_urlparse.urljoin(url, m.group('href')),
|
||||||
|
})
|
||||||
|
entries.append(f)
|
||||||
|
|
||||||
|
return self.playlist_result(entries, anime_id, anime_title, anime_description)
|
@ -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
|
|
||||||
}
|
|
||||||
|
@ -1,24 +1,11 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
|
||||||
|
|
||||||
class AolIE(InfoExtractor):
|
class AolIE(InfoExtractor):
|
||||||
IE_NAME = 'on.aol.com'
|
IE_NAME = 'on.aol.com'
|
||||||
_VALID_URL = r'''(?x)
|
_VALID_URL = r'(?:aol-video:|http://on\.aol\.com/video/.*-)(?P<id>[0-9]+)(?:$|\?)'
|
||||||
(?:
|
|
||||||
aol-video:|
|
|
||||||
http://on\.aol\.com/
|
|
||||||
(?:
|
|
||||||
video/.*-|
|
|
||||||
playlist/(?P<playlist_display_id>[^/?#]+?)-(?P<playlist_id>[0-9]+)[?#].*_videoid=
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(?P<id>[0-9]+)
|
|
||||||
(?:$|\?)
|
|
||||||
'''
|
|
||||||
|
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://on.aol.com/video/u-s--official-warns-of-largest-ever-irs-phone-scam-518167793?icid=OnHomepageC2Wide_MustSee_Img',
|
'url': 'http://on.aol.com/video/u-s--official-warns-of-largest-ever-irs-phone-scam-518167793?icid=OnHomepageC2Wide_MustSee_Img',
|
||||||
@ -29,42 +16,31 @@ class AolIE(InfoExtractor):
|
|||||||
'title': 'U.S. Official Warns Of \'Largest Ever\' IRS Phone Scam',
|
'title': 'U.S. Official Warns Of \'Largest Ever\' IRS Phone Scam',
|
||||||
},
|
},
|
||||||
'add_ie': ['FiveMin'],
|
'add_ie': ['FiveMin'],
|
||||||
}, {
|
|
||||||
'url': 'http://on.aol.com/playlist/brace-yourself---todays-weirdest-news-152147?icid=OnHomepageC4_Omg_Img#_videoid=518184316',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '152147',
|
|
||||||
'title': 'Brace Yourself - Today\'s Weirdest News',
|
|
||||||
},
|
|
||||||
'playlist_mincount': 10,
|
|
||||||
}]
|
}]
|
||||||
|
|
||||||
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')
|
|
||||||
playlist_id = mobj.group('playlist_id')
|
|
||||||
if not playlist_id or self._downloader.params.get('noplaylist'):
|
|
||||||
return self.url_result('5min:%s' % video_id)
|
return self.url_result('5min:%s' % video_id)
|
||||||
|
|
||||||
self.to_screen('Downloading playlist %s - add --no-playlist to just download video %s' % (playlist_id, video_id))
|
|
||||||
|
|
||||||
webpage = self._download_webpage(url, playlist_id)
|
class AolFeaturesIE(InfoExtractor):
|
||||||
title = self._html_search_regex(
|
IE_NAME = 'features.aol.com'
|
||||||
r'<h1 class="video-title[^"]*">(.+?)</h1>', webpage, 'title')
|
_VALID_URL = r'http://features\.aol\.com/video/(?P<id>[^/?#]+)'
|
||||||
playlist_html = self._search_regex(
|
|
||||||
r"(?s)<ul\s+class='video-related[^']*'>(.*?)</ul>", webpage,
|
|
||||||
'playlist HTML')
|
|
||||||
entries = [{
|
|
||||||
'_type': 'url',
|
|
||||||
'url': 'aol-video:%s' % m.group('id'),
|
|
||||||
'ie_key': 'Aol',
|
|
||||||
} for m in re.finditer(
|
|
||||||
r"<a\s+href='.*videoid=(?P<id>[0-9]+)'\s+class='video-thumb'>",
|
|
||||||
playlist_html)]
|
|
||||||
|
|
||||||
return {
|
_TESTS = [{
|
||||||
'_type': 'playlist',
|
'url': 'http://features.aol.com/video/behind-secret-second-careers-late-night-talk-show-hosts',
|
||||||
'id': playlist_id,
|
'md5': '7db483bb0c09c85e241f84a34238cc75',
|
||||||
'display_id': mobj.group('playlist_display_id'),
|
'info_dict': {
|
||||||
'title': title,
|
'id': '519507715',
|
||||||
'entries': entries,
|
'ext': 'mp4',
|
||||||
}
|
'title': 'What To Watch - February 17, 2016',
|
||||||
|
},
|
||||||
|
'add_ie': ['FiveMin'],
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
display_id = self._match_id(url)
|
||||||
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
return self.url_result(self._search_regex(
|
||||||
|
r'<script type="text/javascript" src="(https?://[^/]*?5min\.com/Scripts/PlayerSeed\.js[^"]+)"',
|
||||||
|
webpage, '5min embed url'), 'FiveMin')
|
||||||
|
@ -11,7 +11,8 @@ from ..utils import (
|
|||||||
|
|
||||||
|
|
||||||
class AppleTrailersIE(InfoExtractor):
|
class AppleTrailersIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?trailers\.apple\.com/(?:trailers|ca)/(?P<company>[^/]+)/(?P<movie>[^/]+)'
|
IE_NAME = 'appletrailers'
|
||||||
|
_VALID_URL = r'https?://(?:www\.|movie)?trailers\.apple\.com/(?:trailers|ca)/(?P<company>[^/]+)/(?P<movie>[^/]+)'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://trailers.apple.com/trailers/wb/manofsteel/',
|
'url': 'http://trailers.apple.com/trailers/wb/manofsteel/',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
@ -63,9 +64,18 @@ class AppleTrailersIE(InfoExtractor):
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
}, {
|
||||||
|
'url': 'http://trailers.apple.com/trailers/magnolia/blackthorn/',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'blackthorn',
|
||||||
|
},
|
||||||
|
'playlist_mincount': 2,
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://trailers.apple.com/ca/metropole/autrui/',
|
'url': 'http://trailers.apple.com/ca/metropole/autrui/',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://movietrailers.apple.com/trailers/focus_features/kuboandthetwostrings/',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
_JSON_RE = r'iTunes.playURL\((.*?)\);'
|
_JSON_RE = r'iTunes.playURL\((.*?)\);'
|
||||||
@ -79,7 +89,7 @@ class AppleTrailersIE(InfoExtractor):
|
|||||||
|
|
||||||
def fix_html(s):
|
def fix_html(s):
|
||||||
s = re.sub(r'(?s)<script[^<]*?>.*?</script>', '', s)
|
s = re.sub(r'(?s)<script[^<]*?>.*?</script>', '', s)
|
||||||
s = re.sub(r'<img ([^<]*?)>', r'<img \1/>', s)
|
s = re.sub(r'<img ([^<]*?)/?>', r'<img \1/>', s)
|
||||||
# The ' in the onClick attributes are not escaped, it couldn't be parsed
|
# The ' in the onClick attributes are not escaped, it couldn't be parsed
|
||||||
# like: http://trailers.apple.com/trailers/wb/gravity/
|
# like: http://trailers.apple.com/trailers/wb/gravity/
|
||||||
|
|
||||||
@ -96,6 +106,9 @@ class AppleTrailersIE(InfoExtractor):
|
|||||||
trailer_info_json = self._search_regex(self._JSON_RE,
|
trailer_info_json = self._search_regex(self._JSON_RE,
|
||||||
on_click, 'trailer info')
|
on_click, 'trailer info')
|
||||||
trailer_info = json.loads(trailer_info_json)
|
trailer_info = json.loads(trailer_info_json)
|
||||||
|
first_url = trailer_info.get('url')
|
||||||
|
if not first_url:
|
||||||
|
continue
|
||||||
title = trailer_info['title']
|
title = trailer_info['title']
|
||||||
video_id = movie + '-' + re.sub(r'[^a-zA-Z0-9]', '', title).lower()
|
video_id = movie + '-' + re.sub(r'[^a-zA-Z0-9]', '', title).lower()
|
||||||
thumbnail = li.find('.//img').attrib['src']
|
thumbnail = li.find('.//img').attrib['src']
|
||||||
@ -107,7 +120,6 @@ class AppleTrailersIE(InfoExtractor):
|
|||||||
if m:
|
if m:
|
||||||
duration = 60 * int(m.group('minutes')) + int(m.group('seconds'))
|
duration = 60 * int(m.group('minutes')) + int(m.group('seconds'))
|
||||||
|
|
||||||
first_url = trailer_info['url']
|
|
||||||
trailer_id = first_url.split('/')[-1].rpartition('_')[0].lower()
|
trailer_id = first_url.split('/')[-1].rpartition('_')[0].lower()
|
||||||
settings_json_url = compat_urlparse.urljoin(url, 'includes/settings/%s.json' % trailer_id)
|
settings_json_url = compat_urlparse.urljoin(url, 'includes/settings/%s.json' % trailer_id)
|
||||||
settings = self._download_json(settings_json_url, trailer_id, 'Downloading settings json')
|
settings = self._download_json(settings_json_url, trailer_id, 'Downloading settings json')
|
||||||
@ -144,3 +156,76 @@ class AppleTrailersIE(InfoExtractor):
|
|||||||
'id': movie,
|
'id': movie,
|
||||||
'entries': playlist,
|
'entries': playlist,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class AppleTrailersSectionIE(InfoExtractor):
|
||||||
|
IE_NAME = 'appletrailers:section'
|
||||||
|
_SECTIONS = {
|
||||||
|
'justadded': {
|
||||||
|
'feed_path': 'just_added',
|
||||||
|
'title': 'Just Added',
|
||||||
|
},
|
||||||
|
'exclusive': {
|
||||||
|
'feed_path': 'exclusive',
|
||||||
|
'title': 'Exclusive',
|
||||||
|
},
|
||||||
|
'justhd': {
|
||||||
|
'feed_path': 'just_hd',
|
||||||
|
'title': 'Just HD',
|
||||||
|
},
|
||||||
|
'mostpopular': {
|
||||||
|
'feed_path': 'most_pop',
|
||||||
|
'title': 'Most Popular',
|
||||||
|
},
|
||||||
|
'moviestudios': {
|
||||||
|
'feed_path': 'studios',
|
||||||
|
'title': 'Movie Studios',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?trailers\.apple\.com/#section=(?P<id>%s)' % '|'.join(_SECTIONS)
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://trailers.apple.com/#section=justadded',
|
||||||
|
'info_dict': {
|
||||||
|
'title': 'Just Added',
|
||||||
|
'id': 'justadded',
|
||||||
|
},
|
||||||
|
'playlist_mincount': 80,
|
||||||
|
}, {
|
||||||
|
'url': 'http://trailers.apple.com/#section=exclusive',
|
||||||
|
'info_dict': {
|
||||||
|
'title': 'Exclusive',
|
||||||
|
'id': 'exclusive',
|
||||||
|
},
|
||||||
|
'playlist_mincount': 80,
|
||||||
|
}, {
|
||||||
|
'url': 'http://trailers.apple.com/#section=justhd',
|
||||||
|
'info_dict': {
|
||||||
|
'title': 'Just HD',
|
||||||
|
'id': 'justhd',
|
||||||
|
},
|
||||||
|
'playlist_mincount': 80,
|
||||||
|
}, {
|
||||||
|
'url': 'http://trailers.apple.com/#section=mostpopular',
|
||||||
|
'info_dict': {
|
||||||
|
'title': 'Most Popular',
|
||||||
|
'id': 'mostpopular',
|
||||||
|
},
|
||||||
|
'playlist_mincount': 80,
|
||||||
|
}, {
|
||||||
|
'url': 'http://trailers.apple.com/#section=moviestudios',
|
||||||
|
'info_dict': {
|
||||||
|
'title': 'Movie Studios',
|
||||||
|
'id': 'moviestudios',
|
||||||
|
},
|
||||||
|
'playlist_mincount': 80,
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
section = self._match_id(url)
|
||||||
|
section_data = self._download_json(
|
||||||
|
'http://trailers.apple.com/trailers/home/feeds/%s.json' % self._SECTIONS[section]['feed_path'],
|
||||||
|
section)
|
||||||
|
entries = [
|
||||||
|
self.url_result('http://trailers.apple.com' + e['location'])
|
||||||
|
for e in section_data]
|
||||||
|
return self.playlist_result(entries, section, self._SECTIONS[section]['title'])
|
||||||
|
@ -110,13 +110,15 @@ class ARDMediathekIE(InfoExtractor):
|
|||||||
server = stream.get('_server')
|
server = stream.get('_server')
|
||||||
for stream_url in stream_urls:
|
for stream_url in stream_urls:
|
||||||
ext = determine_ext(stream_url)
|
ext = determine_ext(stream_url)
|
||||||
|
if quality != 'auto' and ext in ('f4m', 'm3u8'):
|
||||||
|
continue
|
||||||
if ext == 'f4m':
|
if ext == 'f4m':
|
||||||
formats.extend(self._extract_f4m_formats(
|
formats.extend(self._extract_f4m_formats(
|
||||||
stream_url + '?hdcore=3.1.1&plugin=aasp-3.1.1.69.124',
|
stream_url + '?hdcore=3.1.1&plugin=aasp-3.1.1.69.124',
|
||||||
video_id, preference=-1, f4m_id='hds'))
|
video_id, preference=-1, f4m_id='hds', fatal=False))
|
||||||
elif ext == 'm3u8':
|
elif ext == 'm3u8':
|
||||||
formats.extend(self._extract_m3u8_formats(
|
formats.extend(self._extract_m3u8_formats(
|
||||||
stream_url, video_id, 'mp4', preference=1, m3u8_id='hls'))
|
stream_url, video_id, 'mp4', preference=1, m3u8_id='hls', fatal=False))
|
||||||
else:
|
else:
|
||||||
if server and server.startswith('rtmp'):
|
if server and server.startswith('rtmp'):
|
||||||
f = {
|
f = {
|
||||||
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -22,7 +23,7 @@ from ..utils import (
|
|||||||
|
|
||||||
|
|
||||||
class ArteTvIE(InfoExtractor):
|
class ArteTvIE(InfoExtractor):
|
||||||
_VALID_URL = r'http://videos\.arte\.tv/(?P<lang>fr|de)/.*-(?P<id>.*?)\.html'
|
_VALID_URL = r'http://videos\.arte\.tv/(?P<lang>fr|de|en|es)/.*-(?P<id>.*?)\.html'
|
||||||
IE_NAME = 'arte.tv'
|
IE_NAME = 'arte.tv'
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
@ -62,12 +63,16 @@ class ArteTvIE(InfoExtractor):
|
|||||||
|
|
||||||
class ArteTVPlus7IE(InfoExtractor):
|
class ArteTVPlus7IE(InfoExtractor):
|
||||||
IE_NAME = 'arte.tv:+7'
|
IE_NAME = 'arte.tv:+7'
|
||||||
_VALID_URL = r'https?://(?:www\.)?arte\.tv/guide/(?P<lang>fr|de)/(?:(?:sendungen|emissions)/)?(?P<id>.*?)/(?P<name>.*?)(\?.*)?'
|
_VALID_URL = r'https?://(?:www\.)?arte\.tv/guide/(?P<lang>fr|de|en|es)/(?:(?:sendungen|emissions|embed)/)?(?P<id>[^/]+)/(?P<name>[^/?#&+])'
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _extract_url_info(cls, url):
|
def _extract_url_info(cls, url):
|
||||||
mobj = re.match(cls._VALID_URL, url)
|
mobj = re.match(cls._VALID_URL, url)
|
||||||
lang = mobj.group('lang')
|
lang = mobj.group('lang')
|
||||||
|
query = compat_parse_qs(compat_urllib_parse_urlparse(url).query)
|
||||||
|
if 'vid' in query:
|
||||||
|
video_id = query['vid'][0]
|
||||||
|
else:
|
||||||
# This is not a real id, it can be for example AJT for the news
|
# This is not a real id, it can be for example AJT for the news
|
||||||
# http://www.arte.tv/guide/fr/emissions/AJT/arte-journal
|
# http://www.arte.tv/guide/fr/emissions/AJT/arte-journal
|
||||||
video_id = mobj.group('id')
|
video_id = mobj.group('id')
|
||||||
@ -79,26 +84,63 @@ class ArteTVPlus7IE(InfoExtractor):
|
|||||||
return self._extract_from_webpage(webpage, video_id, lang)
|
return self._extract_from_webpage(webpage, video_id, lang)
|
||||||
|
|
||||||
def _extract_from_webpage(self, webpage, video_id, lang):
|
def _extract_from_webpage(self, webpage, video_id, lang):
|
||||||
|
patterns_templates = (r'arte_vp_url=["\'](.*?%s.*?)["\']', r'data-url=["\']([^"]+%s[^"]+)["\']')
|
||||||
|
ids = (video_id, '')
|
||||||
|
# some pages contain multiple videos (like
|
||||||
|
# http://www.arte.tv/guide/de/sendungen/XEN/xenius/?vid=055918-015_PLUS7-D),
|
||||||
|
# so we first try to look for json URLs that contain the video id from
|
||||||
|
# the 'vid' parameter.
|
||||||
|
patterns = [t % re.escape(_id) for _id in ids for t in patterns_templates]
|
||||||
json_url = self._html_search_regex(
|
json_url = self._html_search_regex(
|
||||||
[r'arte_vp_url=["\'](.*?)["\']', r'data-url=["\']([^"]+)["\']'],
|
patterns, webpage, 'json vp url', default=None)
|
||||||
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):
|
||||||
|
return self._html_search_regex(
|
||||||
r'<iframe[^>]+src=(["\'])(?P<url>.+\bjson_url=.+?)\1',
|
r'<iframe[^>]+src=(["\'])(?P<url>.+\bjson_url=.+?)\1',
|
||||||
webpage, 'iframe url', group='url')
|
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', default=None)
|
||||||
|
if embed_url:
|
||||||
|
player = self._download_json(
|
||||||
|
embed_url, video_id, 'Downloading player page')
|
||||||
|
iframe_url = find_iframe_url(player['html'])
|
||||||
|
# en and es URLs produce react-based pages with different layout (e.g.
|
||||||
|
# http://www.arte.tv/guide/en/053330-002-A/carnival-italy?zone=world)
|
||||||
|
if not iframe_url:
|
||||||
|
program = self._search_regex(
|
||||||
|
r'program\s*:\s*({.+?["\']embed_html["\'].+?}),?\s*\n',
|
||||||
|
webpage, 'program', default=None)
|
||||||
|
if program:
|
||||||
|
embed_html = self._parse_json(program, video_id)
|
||||||
|
if embed_html:
|
||||||
|
iframe_url = find_iframe_url(embed_html['embed_html'])
|
||||||
|
if iframe_url:
|
||||||
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)
|
if json_url:
|
||||||
|
title = self._search_regex(
|
||||||
|
r'<h3[^>]+title=(["\'])(?P<title>.+?)\1',
|
||||||
|
webpage, 'title', default=None, group='title')
|
||||||
|
return self._extract_from_json_url(json_url, video_id, lang, title=title)
|
||||||
|
# Different kind of embed URL (e.g.
|
||||||
|
# http://www.arte.tv/magazine/trepalium/fr/episode-0406-replay-trepalium)
|
||||||
|
embed_url = self._search_regex(
|
||||||
|
r'<iframe[^>]+src=(["\'])(?P<url>.+?)\1',
|
||||||
|
webpage, 'embed url', group='url')
|
||||||
|
return self.url_result(embed_url)
|
||||||
|
|
||||||
def _extract_from_json_url(self, json_url, video_id, lang):
|
def _extract_from_json_url(self, json_url, video_id, lang, title=None):
|
||||||
info = self._download_json(json_url, video_id)
|
info = self._download_json(json_url, video_id)
|
||||||
player_info = info['videoJsonPlayer']
|
player_info = info['videoJsonPlayer']
|
||||||
|
|
||||||
upload_date_str = player_info.get('shootingDate')
|
upload_date_str = player_info.get('shootingDate')
|
||||||
if not upload_date_str:
|
if not upload_date_str:
|
||||||
upload_date_str = player_info.get('VDA', '').split(' ')[0]
|
upload_date_str = (player_info.get('VRA') or player_info.get('VDA') or '').split(' ')[0]
|
||||||
|
|
||||||
title = player_info['VTI'].strip()
|
title = (player_info.get('VTI') or title or player_info['VID']).strip()
|
||||||
subtitle = player_info.get('VSU', '').strip()
|
subtitle = player_info.get('VSU', '').strip()
|
||||||
if subtitle:
|
if subtitle:
|
||||||
title += ' - %s' % subtitle
|
title += ' - %s' % subtitle
|
||||||
@ -112,27 +154,30 @@ class ArteTVPlus7IE(InfoExtractor):
|
|||||||
}
|
}
|
||||||
qfunc = qualities(['HQ', 'MQ', 'EQ', 'SQ'])
|
qfunc = qualities(['HQ', 'MQ', 'EQ', 'SQ'])
|
||||||
|
|
||||||
|
LANGS = {
|
||||||
|
'fr': 'F',
|
||||||
|
'de': 'A',
|
||||||
|
'en': 'E[ANG]',
|
||||||
|
'es': 'E[ESP]',
|
||||||
|
}
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
for format_id, format_dict in player_info['VSR'].items():
|
for format_id, format_dict in player_info['VSR'].items():
|
||||||
f = dict(format_dict)
|
f = dict(format_dict)
|
||||||
versionCode = f.get('versionCode')
|
versionCode = f.get('versionCode')
|
||||||
|
langcode = LANGS.get(lang, lang)
|
||||||
langcode = {
|
lang_rexs = [r'VO?%s-' % re.escape(langcode), r'VO?.-ST%s$' % re.escape(langcode)]
|
||||||
'fr': 'F',
|
lang_pref = None
|
||||||
'de': 'A',
|
if versionCode:
|
||||||
}.get(lang, lang)
|
matched_lang_rexs = [r for r in lang_rexs if re.match(r, versionCode)]
|
||||||
lang_rexs = [r'VO?%s' % langcode, r'VO?.-ST%s' % langcode]
|
lang_pref = -10 if not matched_lang_rexs else 10 * len(matched_lang_rexs)
|
||||||
lang_pref = (
|
|
||||||
None if versionCode is None else (
|
|
||||||
10 if any(re.match(r, versionCode) for r in lang_rexs)
|
|
||||||
else -10))
|
|
||||||
source_pref = 0
|
source_pref = 0
|
||||||
if versionCode is not None:
|
if versionCode is not None:
|
||||||
# The original version with subtitles has lower relevance
|
# The original version with subtitles has lower relevance
|
||||||
if re.match(r'VO-ST(F|A)', versionCode):
|
if re.match(r'VO-ST(F|A|E)', versionCode):
|
||||||
source_pref -= 10
|
source_pref -= 10
|
||||||
# The version with sourds/mal subtitles has also lower relevance
|
# The version with sourds/mal subtitles has also lower relevance
|
||||||
elif re.match(r'VO?(F|A)-STM\1', versionCode):
|
elif re.match(r'VO?(F|A|E)-STM\1', versionCode):
|
||||||
source_pref -= 9
|
source_pref -= 9
|
||||||
format = {
|
format = {
|
||||||
'format_id': format_id,
|
'format_id': format_id,
|
||||||
@ -165,7 +210,7 @@ class ArteTVPlus7IE(InfoExtractor):
|
|||||||
# It also uses the arte_vp_url url from the webpage to extract the information
|
# It also uses the arte_vp_url url from the webpage to extract the information
|
||||||
class ArteTVCreativeIE(ArteTVPlus7IE):
|
class ArteTVCreativeIE(ArteTVPlus7IE):
|
||||||
IE_NAME = 'arte.tv:creative'
|
IE_NAME = 'arte.tv:creative'
|
||||||
_VALID_URL = r'https?://creative\.arte\.tv/(?P<lang>fr|de)/(?:magazine?/)?(?P<id>[^?#]+)'
|
_VALID_URL = r'https?://creative\.arte\.tv/(?P<lang>fr|de|en|es)/(?:magazine?/)?(?P<id>[^/?#&]+)'
|
||||||
|
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://creative.arte.tv/de/magazin/agentur-amateur-corporate-design',
|
'url': 'http://creative.arte.tv/de/magazin/agentur-amateur-corporate-design',
|
||||||
@ -189,30 +234,25 @@ 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|en|es)/(?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',
|
'upload_date': '20140902',
|
||||||
},
|
},
|
||||||
}
|
}, {
|
||||||
|
'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):
|
||||||
IE_NAME = 'arte.tv:ddc'
|
IE_NAME = 'arte.tv:ddc'
|
||||||
_VALID_URL = r'https?://ddc\.arte\.tv/(?P<lang>emission|folge)/(?P<id>.+)'
|
_VALID_URL = r'https?://ddc\.arte\.tv/(?P<lang>emission|folge)/(?P<id>[^/?#&]+)'
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id, lang = self._extract_url_info(url)
|
video_id, lang = self._extract_url_info(url)
|
||||||
@ -230,7 +270,7 @@ class ArteTVDDCIE(ArteTVPlus7IE):
|
|||||||
|
|
||||||
class ArteTVConcertIE(ArteTVPlus7IE):
|
class ArteTVConcertIE(ArteTVPlus7IE):
|
||||||
IE_NAME = 'arte.tv:concert'
|
IE_NAME = 'arte.tv:concert'
|
||||||
_VALID_URL = r'https?://concert\.arte\.tv/(?P<lang>de|fr)/(?P<id>.+)'
|
_VALID_URL = r'https?://concert\.arte\.tv/(?P<lang>fr|de|en|es)/(?P<id>[^/?#&]+)'
|
||||||
|
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://concert.arte.tv/de/notwist-im-pariser-konzertclub-divan-du-monde',
|
'url': 'http://concert.arte.tv/de/notwist-im-pariser-konzertclub-divan-du-monde',
|
||||||
@ -245,6 +285,54 @@ class ArteTVConcertIE(ArteTVPlus7IE):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ArteTVCinemaIE(ArteTVPlus7IE):
|
||||||
|
IE_NAME = 'arte.tv:cinema'
|
||||||
|
_VALID_URL = r'https?://cinema\.arte\.tv/(?P<lang>fr|de|en|es)/(?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 ArteTVMagazineIE(ArteTVPlus7IE):
|
||||||
|
IE_NAME = 'arte.tv:magazine'
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?arte\.tv/magazine/[^/]+/(?P<lang>fr|de|en|es)/(?P<id>[^/?#&]+)'
|
||||||
|
|
||||||
|
_TESTS = [{
|
||||||
|
# Embedded via <iframe src="http://www.arte.tv/arte_vp/index.php?json_url=..."
|
||||||
|
'url': 'http://www.arte.tv/magazine/trepalium/fr/entretien-avec-le-realisateur-vincent-lannoo-trepalium',
|
||||||
|
'md5': '2a9369bcccf847d1c741e51416299f25',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '065965-000-A',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Trepalium - Extrait Ep.01',
|
||||||
|
'upload_date': '20160121',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
# Embedded via <iframe src="http://www.arte.tv/guide/fr/embed/054813-004-A/medium"
|
||||||
|
'url': 'http://www.arte.tv/magazine/trepalium/fr/episode-0406-replay-trepalium',
|
||||||
|
'md5': 'fedc64fc7a946110fe311634e79782ca',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '054813-004_PLUS7-F',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Trepalium (4/6)',
|
||||||
|
'description': 'md5:10057003c34d54e95350be4f9b05cb40',
|
||||||
|
'upload_date': '20160218',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.arte.tv/magazine/metropolis/de/frank-woeste-german-paris-metropolis',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
|
|
||||||
class ArteTVEmbedIE(ArteTVPlus7IE):
|
class ArteTVEmbedIE(ArteTVPlus7IE):
|
||||||
IE_NAME = 'arte.tv:embed'
|
IE_NAME = 'arte.tv:embed'
|
||||||
_VALID_URL = r'''(?x)
|
_VALID_URL = r'''(?x)
|
||||||
|
@ -2,16 +2,18 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
import time
|
import time
|
||||||
import hmac
|
import hmac
|
||||||
|
import hashlib
|
||||||
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
from ..compat import (
|
||||||
compat_str,
|
compat_str,
|
||||||
compat_urllib_parse,
|
compat_urllib_parse,
|
||||||
compat_urllib_request,
|
|
||||||
)
|
)
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
int_or_none,
|
int_or_none,
|
||||||
float_or_none,
|
float_or_none,
|
||||||
|
sanitized_Request,
|
||||||
xpath_text,
|
xpath_text,
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
)
|
)
|
||||||
@ -32,6 +34,19 @@ class AtresPlayerIE(InfoExtractor):
|
|||||||
'duration': 5527.6,
|
'duration': 5527.6,
|
||||||
'thumbnail': 're:^https?://.*\.jpg$',
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
},
|
},
|
||||||
|
'skip': 'This video is only available for registered users'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://www.atresplayer.com/television/especial/videoencuentros/temporada-1/capitulo-112-david-bustamante_2014121600375.html',
|
||||||
|
'md5': '0d0e918533bbd4b263f2de4d197d4aac',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'capitulo-112-david-bustamante',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'David Bustamante',
|
||||||
|
'description': 'md5:f33f1c0a05be57f6708d4dd83a3b81c6',
|
||||||
|
'duration': 1439.0,
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'url': 'http://www.atresplayer.com/television/series/el-secreto-de-puente-viejo/el-chico-de-los-tres-lunares/capitulo-977-29-12-14_2014122400174.html',
|
'url': 'http://www.atresplayer.com/television/series/el-secreto-de-puente-viejo/el-chico-de-los-tres-lunares/capitulo-977-29-12-14_2014122400174.html',
|
||||||
@ -50,6 +65,13 @@ class AtresPlayerIE(InfoExtractor):
|
|||||||
|
|
||||||
_LOGIN_URL = 'https://servicios.atresplayer.com/j_spring_security_check'
|
_LOGIN_URL = 'https://servicios.atresplayer.com/j_spring_security_check'
|
||||||
|
|
||||||
|
_ERRORS = {
|
||||||
|
'UNPUBLISHED': 'We\'re sorry, but this video is not yet available.',
|
||||||
|
'DELETED': 'This video has expired and is no longer available for online streaming.',
|
||||||
|
'GEOUNPUBLISHED': 'We\'re sorry, but this video is not available in your region due to right restrictions.',
|
||||||
|
# 'PREMIUM': 'PREMIUM',
|
||||||
|
}
|
||||||
|
|
||||||
def _real_initialize(self):
|
def _real_initialize(self):
|
||||||
self._login()
|
self._login()
|
||||||
|
|
||||||
@ -63,7 +85,7 @@ class AtresPlayerIE(InfoExtractor):
|
|||||||
'j_password': password,
|
'j_password': password,
|
||||||
}
|
}
|
||||||
|
|
||||||
request = compat_urllib_request.Request(
|
request = sanitized_Request(
|
||||||
self._LOGIN_URL, compat_urllib_parse.urlencode(login_form).encode('utf-8'))
|
self._LOGIN_URL, compat_urllib_parse.urlencode(login_form).encode('utf-8'))
|
||||||
request.add_header('Content-Type', 'application/x-www-form-urlencoded')
|
request.add_header('Content-Type', 'application/x-www-form-urlencoded')
|
||||||
response = self._download_webpage(
|
response = self._download_webpage(
|
||||||
@ -83,23 +105,48 @@ class AtresPlayerIE(InfoExtractor):
|
|||||||
episode_id = self._search_regex(
|
episode_id = self._search_regex(
|
||||||
r'episode="([^"]+)"', webpage, 'episode id')
|
r'episode="([^"]+)"', webpage, 'episode id')
|
||||||
|
|
||||||
|
request = sanitized_Request(
|
||||||
|
self._PLAYER_URL_TEMPLATE % episode_id,
|
||||||
|
headers={'User-Agent': self._USER_AGENT})
|
||||||
|
player = self._download_json(request, episode_id, 'Downloading player JSON')
|
||||||
|
|
||||||
|
episode_type = player.get('typeOfEpisode')
|
||||||
|
error_message = self._ERRORS.get(episode_type)
|
||||||
|
if error_message:
|
||||||
|
raise ExtractorError(
|
||||||
|
'%s returned error: %s' % (self.IE_NAME, error_message), expected=True)
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
video_url = player.get('urlVideo')
|
||||||
|
if video_url:
|
||||||
|
format_info = {
|
||||||
|
'url': video_url,
|
||||||
|
'format_id': 'http',
|
||||||
|
}
|
||||||
|
mobj = re.search(r'(?P<bitrate>\d+)K_(?P<width>\d+)x(?P<height>\d+)', video_url)
|
||||||
|
if mobj:
|
||||||
|
format_info.update({
|
||||||
|
'width': int_or_none(mobj.group('width')),
|
||||||
|
'height': int_or_none(mobj.group('height')),
|
||||||
|
'tbr': int_or_none(mobj.group('bitrate')),
|
||||||
|
})
|
||||||
|
formats.append(format_info)
|
||||||
|
|
||||||
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())
|
||||||
timestamp_shifted = compat_str(timestamp + self._TIMESTAMP_SHIFT)
|
timestamp_shifted = compat_str(timestamp + self._TIMESTAMP_SHIFT)
|
||||||
token = hmac.new(
|
token = hmac.new(
|
||||||
self._MAGIC.encode('ascii'),
|
self._MAGIC.encode('ascii'),
|
||||||
(episode_id + timestamp_shifted).encode('utf-8')
|
(episode_id + timestamp_shifted).encode('utf-8'), hashlib.md5
|
||||||
).hexdigest()
|
).hexdigest()
|
||||||
|
|
||||||
formats = []
|
request = sanitized_Request(
|
||||||
for fmt in ['windows', 'android_tablet']:
|
self._URL_VIDEO_TEMPLATE.format('windows', episode_id, timestamp_shifted, token),
|
||||||
request = compat_urllib_request.Request(
|
headers={'User-Agent': self._USER_AGENT})
|
||||||
self._URL_VIDEO_TEMPLATE.format(fmt, episode_id, timestamp_shifted, token))
|
|
||||||
request.add_header('User-Agent', self._USER_AGENT)
|
|
||||||
|
|
||||||
fmt_json = self._download_json(
|
fmt_json = self._download_json(
|
||||||
request, video_id, 'Downloading %s video JSON' % fmt)
|
request, video_id, 'Downloading windows video JSON')
|
||||||
|
|
||||||
result = fmt_json.get('resultDes')
|
result = fmt_json.get('resultDes')
|
||||||
if result.lower() != 'ok':
|
if result.lower() != 'ok':
|
||||||
@ -109,7 +156,6 @@ class AtresPlayerIE(InfoExtractor):
|
|||||||
for format_id, video_url in fmt_json['resultObject'].items():
|
for format_id, video_url in fmt_json['resultObject'].items():
|
||||||
if format_id == 'token' or not video_url.startswith('http'):
|
if format_id == 'token' or not video_url.startswith('http'):
|
||||||
continue
|
continue
|
||||||
if video_url.endswith('/Manifest'):
|
|
||||||
if 'geodeswowsmpra3player' in video_url:
|
if 'geodeswowsmpra3player' in video_url:
|
||||||
f4m_path = video_url.split('smil:', 1)[-1].split('free_', 1)[0]
|
f4m_path = video_url.split('smil:', 1)[-1].split('free_', 1)[0]
|
||||||
f4m_url = 'http://drg.antena3.com/{0}hds/es/sd.f4m'.format(f4m_path)
|
f4m_url = 'http://drg.antena3.com/{0}hds/es/sd.f4m'.format(f4m_path)
|
||||||
@ -117,24 +163,14 @@ class AtresPlayerIE(InfoExtractor):
|
|||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
f4m_url = video_url[:-9] + '/manifest.f4m'
|
f4m_url = video_url[:-9] + '/manifest.f4m'
|
||||||
formats.extend(self._extract_f4m_formats(f4m_url, video_id))
|
formats.extend(self._extract_f4m_formats(f4m_url, video_id, f4m_id='hds', fatal=False))
|
||||||
else:
|
|
||||||
formats.append({
|
|
||||||
'url': video_url,
|
|
||||||
'format_id': 'android-%s' % format_id,
|
|
||||||
'preference': 1,
|
|
||||||
})
|
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
player = self._download_json(
|
|
||||||
self._PLAYER_URL_TEMPLATE % episode_id,
|
|
||||||
episode_id)
|
|
||||||
|
|
||||||
path_data = player.get('pathData')
|
path_data = player.get('pathData')
|
||||||
|
|
||||||
episode = self._download_xml(
|
episode = self._download_xml(
|
||||||
self._EPISODE_URL_TEMPLATE % path_data,
|
self._EPISODE_URL_TEMPLATE % path_data, video_id,
|
||||||
video_id, 'Downloading episode XML')
|
'Downloading episode XML')
|
||||||
|
|
||||||
duration = float_or_none(xpath_text(
|
duration = float_or_none(xpath_text(
|
||||||
episode, './media/asset/info/technical/contentDuration', 'duration'))
|
episode, './media/asset/info/technical/contentDuration', 'duration'))
|
||||||
|
89
youtube_dl/extractor/audimedia.py
Normal file
89
youtube_dl/extractor/audimedia.py
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import (
|
||||||
|
int_or_none,
|
||||||
|
parse_iso8601,
|
||||||
|
sanitized_Request,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AudiMediaIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?audi-mediacenter\.com/(?:en|de)/audimediatv/(?P<id>[^/?#]+)'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'https://www.audi-mediacenter.com/en/audimediatv/60-seconds-of-audi-sport-104-2015-wec-bahrain-rookie-test-1467',
|
||||||
|
'md5': '79a8b71c46d49042609795ab59779b66',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '1565',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': '60 Seconds of Audi Sport 104/2015 - WEC Bahrain, Rookie Test',
|
||||||
|
'description': 'md5:60e5d30a78ced725f7b8d34370762941',
|
||||||
|
'upload_date': '20151124',
|
||||||
|
'timestamp': 1448354940,
|
||||||
|
'duration': 74022,
|
||||||
|
'view_count': int,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# extracted from https://audimedia.tv/assets/embed/embedded-player.js (dataSourceAuthToken)
|
||||||
|
_AUTH_TOKEN = 'e25b42847dba18c6c8816d5d8ce94c326e06823ebf0859ed164b3ba169be97f2'
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
display_id = self._match_id(url)
|
||||||
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
|
||||||
|
raw_payload = self._search_regex([
|
||||||
|
r'class="amtv-embed"[^>]+id="([^"]+)"',
|
||||||
|
r'class=\\"amtv-embed\\"[^>]+id=\\"([^"]+)\\"',
|
||||||
|
], webpage, 'raw payload')
|
||||||
|
_, stage_mode, video_id, lang = raw_payload.split('-')
|
||||||
|
|
||||||
|
# TODO: handle s and e stage_mode (live streams and ended live streams)
|
||||||
|
if stage_mode not in ('s', 'e'):
|
||||||
|
request = sanitized_Request(
|
||||||
|
'https://audimedia.tv/api/video/v1/videos/%s?embed[]=video_versions&embed[]=thumbnail_image&where[content_language_iso]=%s' % (video_id, lang),
|
||||||
|
headers={'X-Auth-Token': self._AUTH_TOKEN})
|
||||||
|
json_data = self._download_json(request, video_id)['results']
|
||||||
|
formats = []
|
||||||
|
|
||||||
|
stream_url_hls = json_data.get('stream_url_hls')
|
||||||
|
if stream_url_hls:
|
||||||
|
formats.extend(self._extract_m3u8_formats(
|
||||||
|
stream_url_hls, video_id, 'mp4',
|
||||||
|
entry_protocol='m3u8_native', m3u8_id='hls', fatal=False))
|
||||||
|
|
||||||
|
stream_url_hds = json_data.get('stream_url_hds')
|
||||||
|
if stream_url_hds:
|
||||||
|
formats.extend(self._extract_f4m_formats(
|
||||||
|
stream_url_hds + '?hdcore=3.4.0',
|
||||||
|
video_id, f4m_id='hds', fatal=False))
|
||||||
|
|
||||||
|
for video_version in json_data.get('video_versions'):
|
||||||
|
video_version_url = video_version.get('download_url') or video_version.get('stream_url')
|
||||||
|
if not video_version_url:
|
||||||
|
continue
|
||||||
|
f = {
|
||||||
|
'url': video_version_url,
|
||||||
|
'width': int_or_none(video_version.get('width')),
|
||||||
|
'height': int_or_none(video_version.get('height')),
|
||||||
|
'abr': int_or_none(video_version.get('audio_bitrate')),
|
||||||
|
'vbr': int_or_none(video_version.get('video_bitrate')),
|
||||||
|
}
|
||||||
|
bitrate = self._search_regex(r'(\d+)k', video_version_url, 'bitrate', default=None)
|
||||||
|
if bitrate:
|
||||||
|
f.update({
|
||||||
|
'format_id': 'http-%s' % bitrate,
|
||||||
|
})
|
||||||
|
formats.append(f)
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': json_data['title'],
|
||||||
|
'description': json_data.get('subtitle'),
|
||||||
|
'thumbnail': json_data.get('thumbnail_image', {}).get('file'),
|
||||||
|
'timestamp': parse_iso8601(json_data.get('publication_date')),
|
||||||
|
'duration': int_or_none(json_data.get('duration')),
|
||||||
|
'view_count': int_or_none(json_data.get('view_count')),
|
||||||
|
'formats': formats,
|
||||||
|
}
|
66
youtube_dl/extractor/audioboom.py
Normal file
66
youtube_dl/extractor/audioboom.py
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import float_or_none
|
||||||
|
|
||||||
|
|
||||||
|
class AudioBoomIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?audioboom\.com/boos/(?P<id>[0-9]+)'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'https://audioboom.com/boos/4279833-3-09-2016-czaban-hour-3?t=0',
|
||||||
|
'md5': '63a8d73a055c6ed0f1e51921a10a5a76',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '4279833',
|
||||||
|
'ext': 'mp3',
|
||||||
|
'title': '3/09/2016 Czaban Hour 3',
|
||||||
|
'description': 'Guest: Nate Davis - NFL free agency, Guest: Stan Gans',
|
||||||
|
'duration': 2245.72,
|
||||||
|
'uploader': 'Steve Czaban',
|
||||||
|
'uploader_url': 're:https?://(?:www\.)?audioboom\.com/channel/steveczabanyahoosportsradio',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
video_id = self._match_id(url)
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
clip = None
|
||||||
|
|
||||||
|
clip_store = self._parse_json(
|
||||||
|
self._search_regex(
|
||||||
|
r'data-new-clip-store=(["\'])(?P<json>{.*?"clipId"\s*:\s*%s.*?})\1' % video_id,
|
||||||
|
webpage, 'clip store', default='{}', group='json'),
|
||||||
|
video_id, fatal=False)
|
||||||
|
if clip_store:
|
||||||
|
clips = clip_store.get('clips')
|
||||||
|
if clips and isinstance(clips, list) and isinstance(clips[0], dict):
|
||||||
|
clip = clips[0]
|
||||||
|
|
||||||
|
def from_clip(field):
|
||||||
|
if clip:
|
||||||
|
clip.get(field)
|
||||||
|
|
||||||
|
audio_url = from_clip('clipURLPriorToLoading') or self._og_search_property(
|
||||||
|
'audio', webpage, 'audio url')
|
||||||
|
title = from_clip('title') or self._og_search_title(webpage)
|
||||||
|
description = from_clip('description') or self._og_search_description(webpage)
|
||||||
|
|
||||||
|
duration = float_or_none(from_clip('duration') or self._html_search_meta(
|
||||||
|
'weibo:audio:duration', webpage))
|
||||||
|
|
||||||
|
uploader = from_clip('author') or self._og_search_property(
|
||||||
|
'audio:artist', webpage, 'uploader', fatal=False)
|
||||||
|
uploader_url = from_clip('author_url') or self._html_search_meta(
|
||||||
|
'audioboo:channel', webpage, 'uploader url')
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'url': audio_url,
|
||||||
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'duration': duration,
|
||||||
|
'uploader': uploader,
|
||||||
|
'uploader_url': uploader_url,
|
||||||
|
}
|
@ -56,7 +56,7 @@ class AudiomackIE(InfoExtractor):
|
|||||||
|
|
||||||
# API is inconsistent with errors
|
# API is inconsistent with errors
|
||||||
if 'url' not in api_response or not api_response['url'] or 'error' in api_response:
|
if 'url' not in api_response or not api_response['url'] or 'error' in api_response:
|
||||||
raise ExtractorError('Invalid url %s', url)
|
raise ExtractorError('Invalid url %s' % url)
|
||||||
|
|
||||||
# Audiomack wraps a lot of soundcloud tracks in their branded wrapper
|
# Audiomack wraps a lot of soundcloud tracks in their branded wrapper
|
||||||
# if so, pass the work off to the soundcloud extractor
|
# if so, pass the work off to the soundcloud extractor
|
||||||
|
@ -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)
|
||||||
|
@ -6,13 +6,13 @@ import itertools
|
|||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
from ..compat import (
|
||||||
compat_urllib_parse,
|
compat_urllib_parse,
|
||||||
compat_urllib_request,
|
|
||||||
compat_str,
|
compat_str,
|
||||||
)
|
)
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
float_or_none,
|
float_or_none,
|
||||||
|
sanitized_Request,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -57,7 +57,7 @@ class BambuserIE(InfoExtractor):
|
|||||||
'pass': password,
|
'pass': password,
|
||||||
}
|
}
|
||||||
|
|
||||||
request = compat_urllib_request.Request(
|
request = sanitized_Request(
|
||||||
self._LOGIN_URL, compat_urllib_parse.urlencode(login_form).encode('utf-8'))
|
self._LOGIN_URL, compat_urllib_parse.urlencode(login_form).encode('utf-8'))
|
||||||
request.add_header('Referer', self._LOGIN_URL)
|
request.add_header('Referer', self._LOGIN_URL)
|
||||||
response = self._download_webpage(
|
response = self._download_webpage(
|
||||||
@ -126,7 +126,7 @@ class BambuserChannelIE(InfoExtractor):
|
|||||||
'&sort=created&access_mode=0%2C1%2C2&limit={count}'
|
'&sort=created&access_mode=0%2C1%2C2&limit={count}'
|
||||||
'&method=broadcast&format=json&vid_older_than={last}'
|
'&method=broadcast&format=json&vid_older_than={last}'
|
||||||
).format(user=user, count=self._STEP, last=last_id)
|
).format(user=user, count=self._STEP, last=last_id)
|
||||||
req = compat_urllib_request.Request(req_url)
|
req = sanitized_Request(req_url)
|
||||||
# Without setting this header, we wouldn't get any result
|
# Without setting this header, we wouldn't get any result
|
||||||
req.add_header('Referer', 'http://bambuser.com/channel/%s' % user)
|
req.add_header('Referer', 'http://bambuser.com/channel/%s' % user)
|
||||||
data = self._download_json(
|
data = self._download_json(
|
||||||
|
@ -10,7 +10,6 @@ from ..utils import (
|
|||||||
int_or_none,
|
int_or_none,
|
||||||
parse_duration,
|
parse_duration,
|
||||||
parse_iso8601,
|
parse_iso8601,
|
||||||
remove_end,
|
|
||||||
unescapeHTML,
|
unescapeHTML,
|
||||||
)
|
)
|
||||||
from ..compat import (
|
from ..compat import (
|
||||||
@ -22,7 +21,18 @@ from ..compat import (
|
|||||||
class BBCCoUkIE(InfoExtractor):
|
class BBCCoUkIE(InfoExtractor):
|
||||||
IE_NAME = 'bbc.co.uk'
|
IE_NAME = 'bbc.co.uk'
|
||||||
IE_DESC = 'BBC iPlayer'
|
IE_DESC = 'BBC iPlayer'
|
||||||
_VALID_URL = r'https?://(?:www\.)?bbc\.co\.uk/(?:(?:programmes/(?!articles/)|iplayer(?:/[^/]+)?/(?:episode/|playlist/))|music/clips[/#])(?P<id>[\da-z]{8})'
|
_ID_REGEX = r'[pb][\da-z]{7}'
|
||||||
|
_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
|
||||||
@ -46,9 +56,8 @@ class BBCCoUkIE(InfoExtractor):
|
|||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'b039d07m',
|
'id': 'b039d07m',
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
'title': 'Kaleidoscope, Leonard Cohen',
|
'title': 'Leonard Cohen, Kaleidoscope - BBC Radio 4',
|
||||||
'description': 'The Canadian poet and songwriter reflects on his musical career.',
|
'description': 'The Canadian poet and songwriter reflects on his musical career.',
|
||||||
'duration': 1740,
|
|
||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
# rtmp download
|
# rtmp download
|
||||||
@ -76,7 +85,7 @@ class BBCCoUkIE(InfoExtractor):
|
|||||||
'id': 'b00yng1d',
|
'id': 'b00yng1d',
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
'title': 'The Voice UK: Series 3: Blind Auditions 5',
|
'title': 'The Voice UK: Series 3: Blind Auditions 5',
|
||||||
'description': "Emma Willis and Marvin Humes present the fifth set of blind auditions in the singing competition, as the coaches continue to build their teams based on voice alone.",
|
'description': 'Emma Willis and Marvin Humes present the fifth set of blind auditions in the singing competition, as the coaches continue to build their teams based on voice alone.',
|
||||||
'duration': 5100,
|
'duration': 5100,
|
||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
@ -111,16 +120,17 @@ class BBCCoUkIE(InfoExtractor):
|
|||||||
'params': {
|
'params': {
|
||||||
# rtmp download
|
# rtmp download
|
||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
}
|
},
|
||||||
|
'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
|
||||||
@ -171,13 +181,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
|
||||||
@ -192,6 +214,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,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -222,11 +247,9 @@ class BBCCoUkIE(InfoExtractor):
|
|||||||
elif transfer_format == 'dash':
|
elif transfer_format == 'dash':
|
||||||
pass
|
pass
|
||||||
elif transfer_format == 'hls':
|
elif transfer_format == 'hls':
|
||||||
m3u8_formats = self._extract_m3u8_formats(
|
formats.extend(self._extract_m3u8_formats(
|
||||||
href, programme_id, ext='mp4', entry_protocol='m3u8_native',
|
href, programme_id, ext='mp4', entry_protocol='m3u8_native',
|
||||||
m3u8_id=supplier, fatal=False)
|
m3u8_id=supplier, fatal=False))
|
||||||
if m3u8_formats:
|
|
||||||
formats.extend(m3u8_formats)
|
|
||||||
# Direct link
|
# Direct link
|
||||||
else:
|
else:
|
||||||
formats.append({
|
formats.append({
|
||||||
@ -453,6 +476,7 @@ class BBCCoUkIE(InfoExtractor):
|
|||||||
webpage = self._download_webpage(url, group_id, 'Downloading video page')
|
webpage = self._download_webpage(url, group_id, 'Downloading video page')
|
||||||
|
|
||||||
programme_id = None
|
programme_id = None
|
||||||
|
duration = None
|
||||||
|
|
||||||
tviplayer = self._search_regex(
|
tviplayer = self._search_regex(
|
||||||
r'mediator\.bind\(({.+?})\s*,\s*document\.getElementById',
|
r'mediator\.bind\(({.+?})\s*,\s*document\.getElementById',
|
||||||
@ -465,14 +489,19 @@ class BBCCoUkIE(InfoExtractor):
|
|||||||
|
|
||||||
if not programme_id:
|
if not programme_id:
|
||||||
programme_id = self._search_regex(
|
programme_id = self._search_regex(
|
||||||
r'"vpid"\s*:\s*"([\da-z]{8})"', webpage, 'vpid', fatal=False, default=None)
|
r'"vpid"\s*:\s*"(%s)"' % self._ID_REGEX, webpage, 'vpid', fatal=False, default=None)
|
||||||
|
|
||||||
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>',
|
||||||
webpage, 'description', fatal=False)
|
r'<div[^>]+class="info_+synopsis"[^>]*>([^<]+)</div>'),
|
||||||
|
webpage, 'description', default=None)
|
||||||
|
if not description:
|
||||||
|
description = self._html_search_meta('description', webpage)
|
||||||
else:
|
else:
|
||||||
programme_id, title, description, duration, formats, subtitles = self._download_playlist(group_id)
|
programme_id, title, description, duration, formats, subtitles = self._download_playlist(group_id)
|
||||||
|
|
||||||
@ -531,7 +560,7 @@ class BBCIE(BBCCoUkIE):
|
|||||||
'url': 'http://www.bbc.co.uk/blogs/adamcurtis/entries/3662a707-0af9-3149-963f-47bea720b460',
|
'url': 'http://www.bbc.co.uk/blogs/adamcurtis/entries/3662a707-0af9-3149-963f-47bea720b460',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '3662a707-0af9-3149-963f-47bea720b460',
|
'id': '3662a707-0af9-3149-963f-47bea720b460',
|
||||||
'title': 'BBC Blogs - Adam Curtis - BUGGER',
|
'title': 'BUGGER',
|
||||||
},
|
},
|
||||||
'playlist_count': 18,
|
'playlist_count': 18,
|
||||||
}, {
|
}, {
|
||||||
@ -586,6 +615,7 @@ class BBCIE(BBCCoUkIE):
|
|||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': '''Judge Mindy Glazer: "I'm sorry to see you here... I always wondered what happened to you"''',
|
'title': '''Judge Mindy Glazer: "I'm sorry to see you here... I always wondered what happened to you"''',
|
||||||
'duration': 56,
|
'duration': 56,
|
||||||
|
'description': '''Judge Mindy Glazer: "I'm sorry to see you here... I always wondered what happened to you"''',
|
||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
@ -639,9 +669,17 @@ class BBCIE(BBCCoUkIE):
|
|||||||
'url': 'http://www.bbc.com/sport/0/football/34475836',
|
'url': 'http://www.bbc.com/sport/0/football/34475836',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '34475836',
|
'id': '34475836',
|
||||||
'title': 'What Liverpool can expect from Klopp',
|
'title': 'Jurgen Klopp: Furious football from a witty and winning coach',
|
||||||
},
|
},
|
||||||
'playlist_count': 3,
|
'playlist_count': 3,
|
||||||
|
}, {
|
||||||
|
# school report article with single video
|
||||||
|
'url': 'http://www.bbc.co.uk/schoolreport/35744779',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '35744779',
|
||||||
|
'title': 'School which breaks down barriers in Jerusalem',
|
||||||
|
},
|
||||||
|
'playlist_count': 1,
|
||||||
}, {
|
}, {
|
||||||
# single video with playlist URL from weather section
|
# single video with playlist URL from weather section
|
||||||
'url': 'http://www.bbc.com/weather/features/33601775',
|
'url': 'http://www.bbc.com/weather/features/33601775',
|
||||||
@ -702,19 +740,19 @@ 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
|
|
||||||
|
|
||||||
ld = self._parse_json(
|
playlist_title = json_ld_info.get('title')
|
||||||
self._search_regex(
|
if not playlist_title:
|
||||||
r'(?s)<script type="application/ld\+json">(.+?)</script>',
|
playlist_title = self._og_search_title(
|
||||||
webpage, 'ld json', default='{}'),
|
webpage, default=None) or self._html_search_regex(
|
||||||
playlist_id, fatal=False)
|
r'<title>(.+?)</title>', webpage, 'playlist title', default=None)
|
||||||
if ld:
|
if playlist_title:
|
||||||
timestamp = parse_iso8601(ld.get('datePublished'))
|
playlist_title = re.sub(r'(.+)\s*-\s*BBC.*?$', r'\1', playlist_title).strip()
|
||||||
playlist_title = ld.get('headline')
|
|
||||||
playlist_description = ld.get('articleBody')
|
playlist_description = json_ld_info.get(
|
||||||
|
'description') or self._og_search_description(webpage, default=None)
|
||||||
|
|
||||||
if not timestamp:
|
if not timestamp:
|
||||||
timestamp = parse_iso8601(self._search_regex(
|
timestamp = parse_iso8601(self._search_regex(
|
||||||
@ -728,6 +766,7 @@ class BBCIE(BBCCoUkIE):
|
|||||||
# article with multiple videos embedded with playlist.sxml (e.g.
|
# article with multiple videos embedded with playlist.sxml (e.g.
|
||||||
# http://www.bbc.com/sport/0/football/34475836)
|
# http://www.bbc.com/sport/0/football/34475836)
|
||||||
playlists = re.findall(r'<param[^>]+name="playlist"[^>]+value="([^"]+)"', webpage)
|
playlists = re.findall(r'<param[^>]+name="playlist"[^>]+value="([^"]+)"', webpage)
|
||||||
|
playlists.extend(re.findall(r'data-media-id="([^"]+/playlist\.sxml)"', webpage))
|
||||||
if playlists:
|
if playlists:
|
||||||
entries = [
|
entries = [
|
||||||
self._extract_from_playlist_sxml(playlist_url, playlist_id, timestamp)
|
self._extract_from_playlist_sxml(playlist_url, playlist_id, timestamp)
|
||||||
@ -774,14 +813,13 @@ class BBCIE(BBCCoUkIE):
|
|||||||
playlist.get('progressiveDownloadUrl'), playlist_id, timestamp))
|
playlist.get('progressiveDownloadUrl'), playlist_id, timestamp))
|
||||||
|
|
||||||
if entries:
|
if entries:
|
||||||
playlist_title = playlist_title or remove_end(self._og_search_title(webpage), ' - BBC News')
|
|
||||||
playlist_description = playlist_description or self._og_search_description(webpage, default=None)
|
|
||||||
return self.playlist_result(entries, playlist_id, playlist_title, playlist_description)
|
return self.playlist_result(entries, playlist_id, playlist_title, playlist_description)
|
||||||
|
|
||||||
# single video story (e.g. http://www.bbc.com/travel/story/20150625-sri-lankas-spicy-secret)
|
# single video story (e.g. http://www.bbc.com/travel/story/20150625-sri-lankas-spicy-secret)
|
||||||
programme_id = self._search_regex(
|
programme_id = self._search_regex(
|
||||||
[r'data-video-player-vpid="([\da-z]{8})"',
|
[r'data-video-player-vpid="(%s)"' % self._ID_REGEX,
|
||||||
r'<param[^>]+name="externalIdentifier"[^>]+value="([\da-z]{8})"'],
|
r'<param[^>]+name="externalIdentifier"[^>]+value="(%s)"' % self._ID_REGEX,
|
||||||
|
r'videoId\s*:\s*["\'](%s)["\']' % self._ID_REGEX],
|
||||||
webpage, 'vpid', default=None)
|
webpage, 'vpid', default=None)
|
||||||
|
|
||||||
if programme_id:
|
if programme_id:
|
||||||
@ -805,10 +843,6 @@ class BBCIE(BBCCoUkIE):
|
|||||||
'subtitles': subtitles,
|
'subtitles': subtitles,
|
||||||
}
|
}
|
||||||
|
|
||||||
playlist_title = self._html_search_regex(
|
|
||||||
r'<title>(.*?)(?:\s*-\s*BBC [^ ]+)?</title>', webpage, 'playlist title')
|
|
||||||
playlist_description = self._og_search_description(webpage, default=None)
|
|
||||||
|
|
||||||
def extract_all(pattern):
|
def extract_all(pattern):
|
||||||
return list(filter(None, map(
|
return list(filter(None, map(
|
||||||
lambda s: self._parse_json(s, playlist_id, fatal=False),
|
lambda s: self._parse_json(s, playlist_id, fatal=False),
|
||||||
@ -816,7 +850,7 @@ class BBCIE(BBCCoUkIE):
|
|||||||
|
|
||||||
# Multiple video article (e.g.
|
# Multiple video article (e.g.
|
||||||
# http://www.bbc.co.uk/blogs/adamcurtis/entries/3662a707-0af9-3149-963f-47bea720b460)
|
# http://www.bbc.co.uk/blogs/adamcurtis/entries/3662a707-0af9-3149-963f-47bea720b460)
|
||||||
EMBED_URL = r'https?://(?:www\.)?bbc\.co\.uk/(?:[^/]+/)+[\da-z]{8}(?:\b[^"]+)?'
|
EMBED_URL = r'https?://(?:www\.)?bbc\.co\.uk/(?:[^/]+/)+%s(?:\b[^"]+)?' % self._ID_REGEX
|
||||||
entries = []
|
entries = []
|
||||||
for match in extract_all(r'new\s+SMP\(({.+?})\)'):
|
for match in extract_all(r'new\s+SMP\(({.+?})\)'):
|
||||||
embed_url = match.get('playerSettings', {}).get('externalEmbedUrl')
|
embed_url = match.get('playerSettings', {}).get('externalEmbedUrl')
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
from ..compat import (
|
||||||
|
compat_chr,
|
||||||
|
compat_ord,
|
||||||
|
compat_urllib_parse_unquote,
|
||||||
|
)
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
int_or_none,
|
int_or_none,
|
||||||
parse_iso8601,
|
parse_iso8601,
|
||||||
@ -29,7 +34,38 @@ 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/v1/video/%s' % video_id, video_id)
|
'https://api.beeg.com/api/v5/video/%s' % video_id, video_id)
|
||||||
|
|
||||||
|
def split(o, e):
|
||||||
|
def cut(s, x):
|
||||||
|
n.append(s[:x])
|
||||||
|
return s[x:]
|
||||||
|
n = []
|
||||||
|
r = len(o) % e
|
||||||
|
if r > 0:
|
||||||
|
o = cut(o, r)
|
||||||
|
while len(o) > e:
|
||||||
|
o = cut(o, e)
|
||||||
|
n.append(o)
|
||||||
|
return n
|
||||||
|
|
||||||
|
def decrypt_key(key):
|
||||||
|
# Reverse engineered from http://static.beeg.com/cpl/1105.js
|
||||||
|
a = '5ShMcIQlssOd7zChAIOlmeTZDaUxULbJRnywYaiB'
|
||||||
|
e = compat_urllib_parse_unquote(key)
|
||||||
|
o = ''.join([
|
||||||
|
compat_chr(compat_ord(e[n]) - compat_ord(a[n % len(a)]) % 21)
|
||||||
|
for n in range(len(e))])
|
||||||
|
return ''.join(split(o, 3)[::-1])
|
||||||
|
|
||||||
|
def decrypt_url(encrypted_url):
|
||||||
|
encrypted_url = self._proto_relative_url(
|
||||||
|
encrypted_url.replace('{DATA_MARKERS}', ''), 'https:')
|
||||||
|
key = self._search_regex(
|
||||||
|
r'/key=(.*?)%2Cend=', encrypted_url, 'key', default=None)
|
||||||
|
if not key:
|
||||||
|
return encrypted_url
|
||||||
|
return encrypted_url.replace(key, decrypt_key(key))
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
for format_id, video_url in video.items():
|
for format_id, video_url in video.items():
|
||||||
@ -40,7 +76,7 @@ class BeegIE(InfoExtractor):
|
|||||||
if not height:
|
if not height:
|
||||||
continue
|
continue
|
||||||
formats.append({
|
formats.append({
|
||||||
'url': self._proto_relative_url(video_url.replace('{DATA_MARKERS}', ''), 'http:'),
|
'url': decrypt_url(video_url),
|
||||||
'format_id': format_id,
|
'format_id': format_id,
|
||||||
'height': int(height),
|
'height': int(height),
|
||||||
})
|
})
|
||||||
|
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
|
||||||
|
}
|
@ -2,143 +2,109 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import itertools
|
|
||||||
import json
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
from ..compat import compat_str
|
||||||
compat_etree_fromstring,
|
|
||||||
)
|
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
int_or_none,
|
int_or_none,
|
||||||
unified_strdate,
|
unescapeHTML,
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
|
xpath_text,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class BiliBiliIE(InfoExtractor):
|
class BiliBiliIE(InfoExtractor):
|
||||||
_VALID_URL = r'http://www\.bilibili\.(?:tv|com)/video/av(?P<id>[0-9]+)/'
|
_VALID_URL = r'http://www\.bilibili\.(?:tv|com)/video/av(?P<id>\d+)(?:/index_(?P<page_num>\d+).html)?'
|
||||||
|
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://www.bilibili.tv/video/av1074402/',
|
'url': 'http://www.bilibili.tv/video/av1074402/',
|
||||||
'md5': '2c301e4dab317596e837c3e7633e7d86',
|
'md5': '2c301e4dab317596e837c3e7633e7d86',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '1074402_part1',
|
'id': '1554319',
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
'title': '【金坷垃】金泡沫',
|
'title': '【金坷垃】金泡沫',
|
||||||
'duration': 308,
|
'duration': 308313,
|
||||||
'upload_date': '20140420',
|
'upload_date': '20140420',
|
||||||
'thumbnail': 're:^https?://.+\.jpg',
|
'thumbnail': 're:^https?://.+\.jpg',
|
||||||
|
'description': 'md5:ce18c2a2d2193f0df2917d270f2e5923',
|
||||||
|
'timestamp': 1397983878,
|
||||||
|
'uploader': '菊子桑',
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://www.bilibili.com/video/av1041170/',
|
'url': 'http://www.bilibili.com/video/av1041170/',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '1041170',
|
'id': '1041170',
|
||||||
'title': '【BD1080P】刀语【诸神&异域】',
|
'title': '【BD1080P】刀语【诸神&异域】',
|
||||||
|
'description': '这是个神奇的故事~每个人不留弹幕不给走哦~切利哦!~',
|
||||||
|
'uploader': '枫叶逝去',
|
||||||
|
'timestamp': 1396501299,
|
||||||
},
|
},
|
||||||
'playlist_count': 9,
|
'playlist_count': 9,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
webpage = self._download_webpage(url, video_id)
|
video_id = mobj.group('id')
|
||||||
|
page_num = mobj.group('page_num') or '1'
|
||||||
|
|
||||||
if '(此视频不存在或被删除)' in webpage:
|
view_data = self._download_json(
|
||||||
raise ExtractorError(
|
'http://api.bilibili.com/view?type=json&appkey=8e9fc618fbd41e28&id=%s&page=%s' % (video_id, page_num),
|
||||||
'The video does not exist or was deleted', expected=True)
|
video_id)
|
||||||
|
if 'error' in view_data:
|
||||||
|
raise ExtractorError('%s said: %s' % (self.IE_NAME, view_data['error']), expected=True)
|
||||||
|
|
||||||
if '>你没有权限浏览! 由于版权相关问题 我们不对您所在的地区提供服务<' in webpage:
|
cid = view_data['cid']
|
||||||
raise ExtractorError(
|
title = unescapeHTML(view_data['title'])
|
||||||
'The video is not available in your region due to copyright reasons',
|
|
||||||
expected=True)
|
|
||||||
|
|
||||||
video_code = self._search_regex(
|
doc = self._download_xml(
|
||||||
r'(?s)<div itemprop="video".*?>(.*?)</div>', webpage, 'video code')
|
'http://interface.bilibili.com/v_cdn_play?appkey=8e9fc618fbd41e28&cid=%s' % cid,
|
||||||
|
cid,
|
||||||
|
'Downloading page %s/%s' % (page_num, view_data['pages'])
|
||||||
|
)
|
||||||
|
|
||||||
title = self._html_search_meta(
|
if xpath_text(doc, './result') == 'error':
|
||||||
'media:title', video_code, 'title', fatal=True)
|
raise ExtractorError('%s said: %s' % (self.IE_NAME, xpath_text(doc, './message')), expected=True)
|
||||||
duration_str = self._html_search_meta(
|
|
||||||
'duration', video_code, 'duration')
|
|
||||||
if duration_str is None:
|
|
||||||
duration = None
|
|
||||||
else:
|
|
||||||
duration_mobj = re.match(
|
|
||||||
r'^T(?:(?P<hours>[0-9]+)H)?(?P<minutes>[0-9]+)M(?P<seconds>[0-9]+)S$',
|
|
||||||
duration_str)
|
|
||||||
duration = (
|
|
||||||
int_or_none(duration_mobj.group('hours'), default=0) * 3600 +
|
|
||||||
int(duration_mobj.group('minutes')) * 60 +
|
|
||||||
int(duration_mobj.group('seconds')))
|
|
||||||
upload_date = unified_strdate(self._html_search_meta(
|
|
||||||
'uploadDate', video_code, fatal=False))
|
|
||||||
thumbnail = self._html_search_meta(
|
|
||||||
'thumbnailUrl', video_code, 'thumbnail', fatal=False)
|
|
||||||
|
|
||||||
cid = self._search_regex(r'cid=(\d+)', webpage, 'cid')
|
|
||||||
|
|
||||||
entries = []
|
entries = []
|
||||||
|
|
||||||
lq_page = self._download_webpage(
|
for durl in doc.findall('./durl'):
|
||||||
'http://interface.bilibili.com/v_cdn_play?appkey=1&cid=%s' % cid,
|
size = xpath_text(durl, ['./filesize', './size'])
|
||||||
video_id,
|
|
||||||
note='Downloading LQ video info'
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
err_info = json.loads(lq_page)
|
|
||||||
raise ExtractorError(
|
|
||||||
'BiliBili said: ' + err_info['error_text'], expected=True)
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
lq_doc = compat_etree_fromstring(lq_page)
|
|
||||||
lq_durls = lq_doc.findall('./durl')
|
|
||||||
|
|
||||||
hq_doc = self._download_xml(
|
|
||||||
'http://interface.bilibili.com/playurl?appkey=1&cid=%s' % cid,
|
|
||||||
video_id,
|
|
||||||
note='Downloading HQ video info',
|
|
||||||
fatal=False,
|
|
||||||
)
|
|
||||||
if hq_doc is not False:
|
|
||||||
hq_durls = hq_doc.findall('./durl')
|
|
||||||
assert len(lq_durls) == len(hq_durls)
|
|
||||||
else:
|
|
||||||
hq_durls = itertools.repeat(None)
|
|
||||||
|
|
||||||
i = 1
|
|
||||||
for lq_durl, hq_durl in zip(lq_durls, hq_durls):
|
|
||||||
formats = [{
|
formats = [{
|
||||||
'format_id': 'lq',
|
'url': durl.find('./url').text,
|
||||||
'quality': 1,
|
'filesize': int_or_none(size),
|
||||||
'url': lq_durl.find('./url').text,
|
|
||||||
'filesize': int_or_none(
|
|
||||||
lq_durl.find('./size'), get_attr='text'),
|
|
||||||
}]
|
|
||||||
if hq_durl is not None:
|
|
||||||
formats.append({
|
|
||||||
'format_id': 'hq',
|
|
||||||
'quality': 2,
|
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
'url': hq_durl.find('./url').text,
|
}]
|
||||||
'filesize': int_or_none(
|
backup_urls = durl.find('./backup_url')
|
||||||
hq_durl.find('./size'), get_attr='text'),
|
if backup_urls is not None:
|
||||||
})
|
for backup_url in backup_urls.findall('./url'):
|
||||||
self._sort_formats(formats)
|
formats.append({'url': backup_url.text})
|
||||||
|
formats.reverse()
|
||||||
|
|
||||||
entries.append({
|
entries.append({
|
||||||
'id': '%s_part%d' % (video_id, i),
|
'id': '%s_part%s' % (cid, xpath_text(durl, './order')),
|
||||||
'title': title,
|
'title': title,
|
||||||
|
'duration': int_or_none(xpath_text(durl, './length'), 1000),
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
'duration': duration,
|
|
||||||
'upload_date': upload_date,
|
|
||||||
'thumbnail': thumbnail,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
i += 1
|
info = {
|
||||||
|
'id': compat_str(cid),
|
||||||
return {
|
'title': title,
|
||||||
'_type': 'multi_video',
|
'description': view_data.get('description'),
|
||||||
'entries': entries,
|
'thumbnail': view_data.get('pic'),
|
||||||
'id': video_id,
|
'uploader': view_data.get('author'),
|
||||||
'title': title
|
'timestamp': int_or_none(view_data.get('created')),
|
||||||
|
'view_count': int_or_none(view_data.get('play')),
|
||||||
|
'duration': int_or_none(xpath_text(doc, './timelength')),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(entries) == 1:
|
||||||
|
entries[0].update(info)
|
||||||
|
return entries[0]
|
||||||
|
else:
|
||||||
|
info.update({
|
||||||
|
'_type': 'multi_video',
|
||||||
|
'id': video_id,
|
||||||
|
'entries': entries,
|
||||||
|
})
|
||||||
|
return info
|
||||||
|
110
youtube_dl/extractor/bleacherreport.py
Normal file
110
youtube_dl/extractor/bleacherreport.py
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from .amp import AMPIE
|
||||||
|
from ..utils import (
|
||||||
|
ExtractorError,
|
||||||
|
int_or_none,
|
||||||
|
parse_iso8601,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class BleacherReportIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?bleacherreport\.com/articles/(?P<id>\d+)'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://bleacherreport.com/articles/2496438-fsu-stat-projections-is-jalen-ramsey-best-defensive-player-in-college-football',
|
||||||
|
'md5': 'a3ffc3dc73afdbc2010f02d98f990f20',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '2496438',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'FSU Stat Projections: Is Jalen Ramsey Best Defensive Player in College Football?',
|
||||||
|
'uploader_id': 3992341,
|
||||||
|
'description': 'CFB, ACC, Florida State',
|
||||||
|
'timestamp': 1434380212,
|
||||||
|
'upload_date': '20150615',
|
||||||
|
'uploader': 'Team Stream Now ',
|
||||||
|
},
|
||||||
|
'add_ie': ['Ooyala'],
|
||||||
|
}, {
|
||||||
|
'url': 'http://bleacherreport.com/articles/2586817-aussie-golfers-get-fright-of-their-lives-after-being-chased-by-angry-kangaroo',
|
||||||
|
'md5': '6a5cd403418c7b01719248ca97fb0692',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '2586817',
|
||||||
|
'ext': 'webm',
|
||||||
|
'title': 'Aussie Golfers Get Fright of Their Lives After Being Chased by Angry Kangaroo',
|
||||||
|
'timestamp': 1446839961,
|
||||||
|
'uploader': 'Sean Fay',
|
||||||
|
'description': 'md5:825e94e0f3521df52fa83b2ed198fa20',
|
||||||
|
'uploader_id': 6466954,
|
||||||
|
'upload_date': '20151011',
|
||||||
|
},
|
||||||
|
'add_ie': ['Youtube'],
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
article_id = self._match_id(url)
|
||||||
|
|
||||||
|
article_data = self._download_json('http://api.bleacherreport.com/api/v1/articles/%s' % article_id, article_id)['article']
|
||||||
|
|
||||||
|
thumbnails = []
|
||||||
|
primary_photo = article_data.get('primaryPhoto')
|
||||||
|
if primary_photo:
|
||||||
|
thumbnails = [{
|
||||||
|
'url': primary_photo['url'],
|
||||||
|
'width': primary_photo.get('width'),
|
||||||
|
'height': primary_photo.get('height'),
|
||||||
|
}]
|
||||||
|
|
||||||
|
info = {
|
||||||
|
'_type': 'url_transparent',
|
||||||
|
'id': article_id,
|
||||||
|
'title': article_data['title'],
|
||||||
|
'uploader': article_data.get('author', {}).get('name'),
|
||||||
|
'uploader_id': article_data.get('authorId'),
|
||||||
|
'timestamp': parse_iso8601(article_data.get('createdAt')),
|
||||||
|
'thumbnails': thumbnails,
|
||||||
|
'comment_count': int_or_none(article_data.get('commentsCount')),
|
||||||
|
'view_count': int_or_none(article_data.get('hitCount')),
|
||||||
|
}
|
||||||
|
|
||||||
|
video = article_data.get('video')
|
||||||
|
if video:
|
||||||
|
video_type = video['type']
|
||||||
|
if video_type == 'cms.bleacherreport.com':
|
||||||
|
info['url'] = 'http://bleacherreport.com/video_embed?id=%s' % video['id']
|
||||||
|
elif video_type == 'ooyala.com':
|
||||||
|
info['url'] = 'ooyala:%s' % video['id']
|
||||||
|
elif video_type == 'youtube.com':
|
||||||
|
info['url'] = video['id']
|
||||||
|
elif video_type == 'vine.co':
|
||||||
|
info['url'] = 'https://vine.co/v/%s' % video['id']
|
||||||
|
else:
|
||||||
|
info['url'] = video_type + video['id']
|
||||||
|
return info
|
||||||
|
else:
|
||||||
|
raise ExtractorError('no video in the article', expected=True)
|
||||||
|
|
||||||
|
|
||||||
|
class BleacherReportCMSIE(AMPIE):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?bleacherreport\.com/video_embed\?id=(?P<id>[0-9a-f-]{36})'
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://bleacherreport.com/video_embed?id=8fd44c2f-3dc5-4821-9118-2c825a98c0e1',
|
||||||
|
'md5': '8c2c12e3af7805152675446c905d159b',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '8fd44c2f-3dc5-4821-9118-2c825a98c0e1',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Cena vs. Rollins Would Expose the Heavyweight Division',
|
||||||
|
'description': 'md5:984afb4ade2f9c0db35f3267ed88b36e',
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
# m3u8 download
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
video_id = self._match_id(url)
|
||||||
|
info = self._extract_feed_info('http://cms.bleacherreport.com/media/items/%s/akamai.json' % video_id)
|
||||||
|
info['id'] = video_id
|
||||||
|
return info
|
@ -1,292 +0,0 @@
|
|||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
|
||||||
|
|
||||||
from ..compat import (
|
|
||||||
compat_urllib_request,
|
|
||||||
compat_urlparse,
|
|
||||||
)
|
|
||||||
from ..utils import (
|
|
||||||
clean_html,
|
|
||||||
int_or_none,
|
|
||||||
parse_iso8601,
|
|
||||||
unescapeHTML,
|
|
||||||
xpath_text,
|
|
||||||
xpath_with_ns,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class BlipTVIE(InfoExtractor):
|
|
||||||
_VALID_URL = r'https?://(?:\w+\.)?blip\.tv/(?:(?:.+-|rss/flash/)(?P<id>\d+)|((?:play/|api\.swf#)(?P<lookup_id>[\da-zA-Z+_]+)))'
|
|
||||||
|
|
||||||
_TESTS = [
|
|
||||||
{
|
|
||||||
'url': 'http://blip.tv/cbr/cbr-exclusive-gotham-city-imposters-bats-vs-jokerz-short-3-5796352',
|
|
||||||
'md5': '80baf1ec5c3d2019037c1c707d676b9f',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '5779306',
|
|
||||||
'ext': 'm4v',
|
|
||||||
'title': 'CBR EXCLUSIVE: "Gotham City Imposters" Bats VS Jokerz Short 3',
|
|
||||||
'description': 'md5:9bc31f227219cde65e47eeec8d2dc596',
|
|
||||||
'timestamp': 1323138843,
|
|
||||||
'upload_date': '20111206',
|
|
||||||
'uploader': 'cbr',
|
|
||||||
'uploader_id': '679425',
|
|
||||||
'duration': 81,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
# https://github.com/rg3/youtube-dl/pull/2274
|
|
||||||
'note': 'Video with subtitles',
|
|
||||||
'url': 'http://blip.tv/play/h6Uag5OEVgI.html',
|
|
||||||
'md5': '309f9d25b820b086ca163ffac8031806',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '6586561',
|
|
||||||
'ext': 'mp4',
|
|
||||||
'title': 'Red vs. Blue Season 11 Episode 1',
|
|
||||||
'description': 'One-Zero-One',
|
|
||||||
'timestamp': 1371261608,
|
|
||||||
'upload_date': '20130615',
|
|
||||||
'uploader': 'redvsblue',
|
|
||||||
'uploader_id': '792887',
|
|
||||||
'duration': 279,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
# https://bugzilla.redhat.com/show_bug.cgi?id=967465
|
|
||||||
'url': 'http://a.blip.tv/api.swf#h6Uag5KbVwI',
|
|
||||||
'md5': '314e87b1ebe7a48fcbfdd51b791ce5a6',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '6573122',
|
|
||||||
'ext': 'mov',
|
|
||||||
'upload_date': '20130520',
|
|
||||||
'description': 'Two hapless space marines argue over what to do when they realize they have an astronomically huge problem on their hands.',
|
|
||||||
'title': 'Red vs. Blue Season 11 Trailer',
|
|
||||||
'timestamp': 1369029609,
|
|
||||||
'uploader': 'redvsblue',
|
|
||||||
'uploader_id': '792887',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'url': 'http://blip.tv/play/gbk766dkj4Yn',
|
|
||||||
'md5': 'fe0a33f022d49399a241e84a8ea8b8e3',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '1749452',
|
|
||||||
'ext': 'mp4',
|
|
||||||
'upload_date': '20090208',
|
|
||||||
'description': 'Witness the first appearance of the Nostalgia Critic character, as Doug reviews the movie Transformers.',
|
|
||||||
'title': 'Nostalgia Critic: Transformers',
|
|
||||||
'timestamp': 1234068723,
|
|
||||||
'uploader': 'NostalgiaCritic',
|
|
||||||
'uploader_id': '246467',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
# https://github.com/rg3/youtube-dl/pull/4404
|
|
||||||
'note': 'Audio only',
|
|
||||||
'url': 'http://blip.tv/hilarios-productions/weekly-manga-recap-kingdom-7119982',
|
|
||||||
'md5': '76c0a56f24e769ceaab21fbb6416a351',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '7103299',
|
|
||||||
'ext': 'flv',
|
|
||||||
'title': 'Weekly Manga Recap: Kingdom',
|
|
||||||
'description': 'And then Shin breaks the enemy line, and he's all like HWAH! And then he slices a guy and it's all like FWASHING! And... it's really hard to describe the best parts of this series without breaking down into sound effects, okay?',
|
|
||||||
'timestamp': 1417660321,
|
|
||||||
'upload_date': '20141204',
|
|
||||||
'uploader': 'The Rollo T',
|
|
||||||
'uploader_id': '407429',
|
|
||||||
'duration': 7251,
|
|
||||||
'vcodec': 'none',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
# missing duration
|
|
||||||
'url': 'http://blip.tv/rss/flash/6700880',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '6684191',
|
|
||||||
'ext': 'm4v',
|
|
||||||
'title': 'Cowboy Bebop: Gateway Shuffle Review',
|
|
||||||
'description': 'md5:3acc480c0f9ae157f5fe88547ecaf3f8',
|
|
||||||
'timestamp': 1386639757,
|
|
||||||
'upload_date': '20131210',
|
|
||||||
'uploader': 'sfdebris',
|
|
||||||
'uploader_id': '706520',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _extract_url(webpage):
|
|
||||||
mobj = re.search(r'<meta\s[^>]*https?://api\.blip\.tv/\w+/redirect/\w+/(\d+)', webpage)
|
|
||||||
if mobj:
|
|
||||||
return 'http://blip.tv/a/a-' + mobj.group(1)
|
|
||||||
mobj = re.search(r'<(?:iframe|embed|object)\s[^>]*(https?://(?:\w+\.)?blip\.tv/(?:play/|api\.swf#)[a-zA-Z0-9_]+)', webpage)
|
|
||||||
if mobj:
|
|
||||||
return mobj.group(1)
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
|
||||||
mobj = re.match(self._VALID_URL, url)
|
|
||||||
lookup_id = mobj.group('lookup_id')
|
|
||||||
|
|
||||||
# See https://github.com/rg3/youtube-dl/issues/857 and
|
|
||||||
# https://github.com/rg3/youtube-dl/issues/4197
|
|
||||||
if lookup_id:
|
|
||||||
urlh = self._request_webpage(
|
|
||||||
'http://blip.tv/play/%s' % lookup_id, lookup_id, 'Resolving lookup id')
|
|
||||||
url = compat_urlparse.urlparse(urlh.geturl())
|
|
||||||
qs = compat_urlparse.parse_qs(url.query)
|
|
||||||
mobj = re.match(self._VALID_URL, qs['file'][0])
|
|
||||||
|
|
||||||
video_id = mobj.group('id')
|
|
||||||
|
|
||||||
rss = self._download_xml('http://blip.tv/rss/flash/%s' % video_id, video_id, 'Downloading video RSS')
|
|
||||||
|
|
||||||
def _x(p):
|
|
||||||
return xpath_with_ns(p, {
|
|
||||||
'blip': 'http://blip.tv/dtd/blip/1.0',
|
|
||||||
'media': 'http://search.yahoo.com/mrss/',
|
|
||||||
'itunes': 'http://www.itunes.com/dtds/podcast-1.0.dtd',
|
|
||||||
})
|
|
||||||
|
|
||||||
item = rss.find('channel/item')
|
|
||||||
|
|
||||||
video_id = xpath_text(item, _x('blip:item_id'), 'video id') or lookup_id
|
|
||||||
title = xpath_text(item, 'title', 'title', fatal=True)
|
|
||||||
description = clean_html(xpath_text(item, _x('blip:puredescription'), 'description'))
|
|
||||||
timestamp = parse_iso8601(xpath_text(item, _x('blip:datestamp'), 'timestamp'))
|
|
||||||
uploader = xpath_text(item, _x('blip:user'), 'uploader')
|
|
||||||
uploader_id = xpath_text(item, _x('blip:userid'), 'uploader id')
|
|
||||||
duration = int_or_none(xpath_text(item, _x('blip:runtime'), 'duration'))
|
|
||||||
media_thumbnail = item.find(_x('media:thumbnail'))
|
|
||||||
thumbnail = (media_thumbnail.get('url') if media_thumbnail is not None
|
|
||||||
else xpath_text(item, 'image', 'thumbnail'))
|
|
||||||
categories = [category.text for category in item.findall('category') if category is not None]
|
|
||||||
|
|
||||||
formats = []
|
|
||||||
subtitles_urls = {}
|
|
||||||
|
|
||||||
media_group = item.find(_x('media:group'))
|
|
||||||
for media_content in media_group.findall(_x('media:content')):
|
|
||||||
url = media_content.get('url')
|
|
||||||
role = media_content.get(_x('blip:role'))
|
|
||||||
msg = self._download_webpage(
|
|
||||||
url + '?showplayer=20140425131715&referrer=http://blip.tv&mask=7&skin=flashvars&view=url',
|
|
||||||
video_id, 'Resolving URL for %s' % role)
|
|
||||||
real_url = compat_urlparse.parse_qs(msg.strip())['message'][0]
|
|
||||||
|
|
||||||
media_type = media_content.get('type')
|
|
||||||
if media_type == 'text/srt' or url.endswith('.srt'):
|
|
||||||
LANGS = {
|
|
||||||
'english': 'en',
|
|
||||||
}
|
|
||||||
lang = role.rpartition('-')[-1].strip().lower()
|
|
||||||
langcode = LANGS.get(lang, lang)
|
|
||||||
subtitles_urls[langcode] = url
|
|
||||||
elif media_type.startswith('video/'):
|
|
||||||
formats.append({
|
|
||||||
'url': real_url,
|
|
||||||
'format_id': role,
|
|
||||||
'format_note': media_type,
|
|
||||||
'vcodec': media_content.get(_x('blip:vcodec')) or 'none',
|
|
||||||
'acodec': media_content.get(_x('blip:acodec')),
|
|
||||||
'filesize': media_content.get('filesize'),
|
|
||||||
'width': int_or_none(media_content.get('width')),
|
|
||||||
'height': int_or_none(media_content.get('height')),
|
|
||||||
})
|
|
||||||
self._check_formats(formats, video_id)
|
|
||||||
self._sort_formats(formats)
|
|
||||||
|
|
||||||
subtitles = self.extract_subtitles(video_id, subtitles_urls)
|
|
||||||
|
|
||||||
return {
|
|
||||||
'id': video_id,
|
|
||||||
'title': title,
|
|
||||||
'description': description,
|
|
||||||
'timestamp': timestamp,
|
|
||||||
'uploader': uploader,
|
|
||||||
'uploader_id': uploader_id,
|
|
||||||
'duration': duration,
|
|
||||||
'thumbnail': thumbnail,
|
|
||||||
'categories': categories,
|
|
||||||
'formats': formats,
|
|
||||||
'subtitles': subtitles,
|
|
||||||
}
|
|
||||||
|
|
||||||
def _get_subtitles(self, video_id, subtitles_urls):
|
|
||||||
subtitles = {}
|
|
||||||
for lang, url in subtitles_urls.items():
|
|
||||||
# For some weird reason, blip.tv serves a video instead of subtitles
|
|
||||||
# when we request with a common UA
|
|
||||||
req = compat_urllib_request.Request(url)
|
|
||||||
req.add_header('User-Agent', 'youtube-dl')
|
|
||||||
subtitles[lang] = [{
|
|
||||||
# The extension is 'srt' but it's actually an 'ass' file
|
|
||||||
'ext': 'ass',
|
|
||||||
'data': self._download_webpage(req, None, note=False),
|
|
||||||
}]
|
|
||||||
return subtitles
|
|
||||||
|
|
||||||
|
|
||||||
class BlipTVUserIE(InfoExtractor):
|
|
||||||
_VALID_URL = r'(?:(?:https?://(?:\w+\.)?blip\.tv/)|bliptvuser:)(?!api\.swf)([^/]+)/*$'
|
|
||||||
_PAGE_SIZE = 12
|
|
||||||
IE_NAME = 'blip.tv:user'
|
|
||||||
_TEST = {
|
|
||||||
'url': 'http://blip.tv/actone',
|
|
||||||
'info_dict': {
|
|
||||||
'id': 'actone',
|
|
||||||
'title': 'Act One: The Series',
|
|
||||||
},
|
|
||||||
'playlist_count': 5,
|
|
||||||
}
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
|
||||||
mobj = re.match(self._VALID_URL, url)
|
|
||||||
username = mobj.group(1)
|
|
||||||
|
|
||||||
page_base = 'http://m.blip.tv/pr/show_get_full_episode_list?users_id=%s&lite=0&esi=1'
|
|
||||||
|
|
||||||
page = self._download_webpage(url, username, 'Downloading user page')
|
|
||||||
mobj = re.search(r'data-users-id="([^"]+)"', page)
|
|
||||||
page_base = page_base % mobj.group(1)
|
|
||||||
title = self._og_search_title(page)
|
|
||||||
|
|
||||||
# Download video ids using BlipTV Ajax calls. Result size per
|
|
||||||
# query is limited (currently to 12 videos) so we need to query
|
|
||||||
# page by page until there are no video ids - it means we got
|
|
||||||
# all of them.
|
|
||||||
|
|
||||||
video_ids = []
|
|
||||||
pagenum = 1
|
|
||||||
|
|
||||||
while True:
|
|
||||||
url = page_base + "&page=" + str(pagenum)
|
|
||||||
page = self._download_webpage(
|
|
||||||
url, username, 'Downloading video ids from page %d' % pagenum)
|
|
||||||
|
|
||||||
# Extract video identifiers
|
|
||||||
ids_in_page = []
|
|
||||||
|
|
||||||
for mobj in re.finditer(r'href="/([^"]+)"', page):
|
|
||||||
if mobj.group(1) not in ids_in_page:
|
|
||||||
ids_in_page.append(unescapeHTML(mobj.group(1)))
|
|
||||||
|
|
||||||
video_ids.extend(ids_in_page)
|
|
||||||
|
|
||||||
# A little optimization - if current page is not
|
|
||||||
# "full", ie. does not contain PAGE_SIZE video ids then
|
|
||||||
# we can assume that this page is the last one - there
|
|
||||||
# are no more ids on further pages - no need to query
|
|
||||||
# again.
|
|
||||||
|
|
||||||
if len(ids_in_page) < self._PAGE_SIZE:
|
|
||||||
break
|
|
||||||
|
|
||||||
pagenum += 1
|
|
||||||
|
|
||||||
urls = ['http://blip.tv/%s' % video_id for video_id in video_ids]
|
|
||||||
url_entries = [self.url_result(vurl, 'BlipTV') for vurl in urls]
|
|
||||||
return self.playlist_result(
|
|
||||||
url_entries, playlist_title=title, playlist_id=username)
|
|
@ -6,9 +6,9 @@ from .common import InfoExtractor
|
|||||||
|
|
||||||
|
|
||||||
class BloombergIE(InfoExtractor):
|
class BloombergIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://www\.bloomberg\.com/news/videos/[^/]+/(?P<id>[^/?#]+)'
|
_VALID_URL = r'https?://(?:www\.)?bloomberg\.com/(?:[^/]+/)*(?P<id>[^/?#]+)'
|
||||||
|
|
||||||
_TEST = {
|
_TESTS = [{
|
||||||
'url': 'http://www.bloomberg.com/news/videos/b/aaeae121-5949-481e-a1ce-4562db6f5df2',
|
'url': 'http://www.bloomberg.com/news/videos/b/aaeae121-5949-481e-a1ce-4562db6f5df2',
|
||||||
# The md5 checksum changes
|
# The md5 checksum changes
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
@ -17,22 +17,35 @@ class BloombergIE(InfoExtractor):
|
|||||||
'title': 'Shah\'s Presentation on Foreign-Exchange Strategies',
|
'title': 'Shah\'s Presentation on Foreign-Exchange Strategies',
|
||||||
'description': 'md5:a8ba0302912d03d246979735c17d2761',
|
'description': 'md5:a8ba0302912d03d246979735c17d2761',
|
||||||
},
|
},
|
||||||
}
|
}, {
|
||||||
|
'url': 'http://www.bloomberg.com/news/articles/2015-11-12/five-strange-things-that-have-been-happening-in-financial-markets',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.bloomberg.com/politics/videos/2015-11-25/karl-rove-on-jeb-bush-s-struggles-stopping-trump',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
name = self._match_id(url)
|
name = self._match_id(url)
|
||||||
webpage = self._download_webpage(url, name)
|
webpage = self._download_webpage(url, name)
|
||||||
video_id = self._search_regex(r'"bmmrId":"(.+?)"', webpage, 'id')
|
video_id = self._search_regex(
|
||||||
|
r'["\']bmmrId["\']\s*:\s*(["\'])(?P<url>.+?)\1',
|
||||||
|
webpage, 'id', group='url')
|
||||||
title = re.sub(': Video$', '', self._og_search_title(webpage))
|
title = re.sub(': Video$', '', self._og_search_title(webpage))
|
||||||
|
|
||||||
embed_info = self._download_json(
|
embed_info = self._download_json(
|
||||||
'http://www.bloomberg.com/api/embed?id=%s' % video_id, video_id)
|
'http://www.bloomberg.com/api/embed?id=%s' % video_id, video_id)
|
||||||
formats = []
|
formats = []
|
||||||
for stream in embed_info['streams']:
|
for stream in embed_info['streams']:
|
||||||
if stream["muxing_format"] == "TS":
|
stream_url = stream.get('url')
|
||||||
formats.extend(self._extract_m3u8_formats(stream['url'], video_id))
|
if not stream_url:
|
||||||
|
continue
|
||||||
|
if stream['muxing_format'] == 'TS':
|
||||||
|
formats.extend(self._extract_m3u8_formats(
|
||||||
|
stream_url, video_id, 'mp4', m3u8_id='hls', fatal=False))
|
||||||
else:
|
else:
|
||||||
formats.extend(self._extract_f4m_formats(stream['url'], video_id))
|
formats.extend(self._extract_f4m_formats(
|
||||||
|
stream_url, video_id, f4m_id='hds', fatal=False))
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
60
youtube_dl/extractor/bokecc.py
Normal file
60
youtube_dl/extractor/bokecc.py
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..compat import compat_parse_qs
|
||||||
|
from ..utils import ExtractorError
|
||||||
|
|
||||||
|
|
||||||
|
class BokeCCBaseIE(InfoExtractor):
|
||||||
|
def _extract_bokecc_formats(self, webpage, video_id, format_id=None):
|
||||||
|
player_params_str = self._html_search_regex(
|
||||||
|
r'<(?:script|embed)[^>]+src="http://p\.bokecc\.com/player\?([^"]+)',
|
||||||
|
webpage, 'player params')
|
||||||
|
|
||||||
|
player_params = compat_parse_qs(player_params_str)
|
||||||
|
|
||||||
|
info_xml = self._download_xml(
|
||||||
|
'http://p.bokecc.com/servlet/playinfo?uid=%s&vid=%s&m=1' % (
|
||||||
|
player_params['siteid'][0], player_params['vid'][0]), video_id)
|
||||||
|
|
||||||
|
formats = [{
|
||||||
|
'format_id': format_id,
|
||||||
|
'url': quality.find('./copy').attrib['playurl'],
|
||||||
|
'preference': int(quality.attrib['value']),
|
||||||
|
} for quality in info_xml.findall('./video/quality')]
|
||||||
|
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
return formats
|
||||||
|
|
||||||
|
|
||||||
|
class BokeCCIE(BokeCCBaseIE):
|
||||||
|
_IE_DESC = 'CC视频'
|
||||||
|
_VALID_URL = r'http://union\.bokecc\.com/playvideo\.bo\?(?P<query>.*)'
|
||||||
|
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://union.bokecc.com/playvideo.bo?vid=E44D40C15E65EA30&uid=CD0C5D3C8614B28B',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'CD0C5D3C8614B28B_E44D40C15E65EA30',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'BokeCC Video',
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
qs = compat_parse_qs(re.match(self._VALID_URL, url).group('query'))
|
||||||
|
if not qs.get('vid') or not qs.get('uid'):
|
||||||
|
raise ExtractorError('Invalid URL', expected=True)
|
||||||
|
|
||||||
|
video_id = '%s_%s' % (qs['uid'][0], qs['vid'][0])
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, video_id)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': 'BokeCC Video', # no title provided in the webpage
|
||||||
|
'formats': self._extract_bokecc_formats(webpage, video_id),
|
||||||
|
}
|
@ -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),
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,21 @@
|
|||||||
# 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 (
|
from ..utils import (
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
parse_duration,
|
parse_duration,
|
||||||
|
xpath_element,
|
||||||
|
xpath_text,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class BRIE(InfoExtractor):
|
class BRIE(InfoExtractor):
|
||||||
IE_DESC = 'Bayerischer Rundfunk Mediathek'
|
IE_DESC = 'Bayerischer Rundfunk Mediathek'
|
||||||
_VALID_URL = r'https?://(?:www\.)?br\.de/(?:[a-z0-9\-_]+/)+(?P<id>[a-z0-9\-_]+)\.html'
|
_VALID_URL = r'(?P<base_url>https?://(?:www\.)?br(?:-klassik)?\.de)/(?:[a-z0-9\-_]+/)+(?P<id>[a-z0-9\-_]+)\.html'
|
||||||
_BASE_URL = 'http://www.br.de'
|
|
||||||
|
|
||||||
_TESTS = [
|
_TESTS = [
|
||||||
{
|
{
|
||||||
@ -22,7 +25,7 @@ class BRIE(InfoExtractor):
|
|||||||
'id': '48f656ef-287e-486f-be86-459122db22cc',
|
'id': '48f656ef-287e-486f-be86-459122db22cc',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Die böse Überraschung',
|
'title': 'Die böse Überraschung',
|
||||||
'description': 'Betriebliche Altersvorsorge: Die böse Überraschung',
|
'description': 'md5:ce9ac81b466ce775b8018f6801b48ac9',
|
||||||
'duration': 180,
|
'duration': 180,
|
||||||
'uploader': 'Reinhard Weber',
|
'uploader': 'Reinhard Weber',
|
||||||
'upload_date': '20150422',
|
'upload_date': '20150422',
|
||||||
@ -30,23 +33,23 @@ class BRIE(InfoExtractor):
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
'url': 'http://www.br.de/nachrichten/oberbayern/inhalt/muenchner-polizeipraesident-schreiber-gestorben-100.html',
|
'url': 'http://www.br.de/nachrichten/oberbayern/inhalt/muenchner-polizeipraesident-schreiber-gestorben-100.html',
|
||||||
'md5': 'a44396d73ab6a68a69a568fae10705bb',
|
'md5': 'af3a3a4aa43ff0ce6a89504c67f427ef',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'a4b83e34-123d-4b81-9f4e-c0d3121a4e05',
|
'id': 'a4b83e34-123d-4b81-9f4e-c0d3121a4e05',
|
||||||
'ext': 'mp4',
|
'ext': 'flv',
|
||||||
'title': 'Manfred Schreiber ist tot',
|
'title': 'Manfred Schreiber ist tot',
|
||||||
'description': 'Abendschau kompakt: Manfred Schreiber ist tot',
|
'description': 'md5:b454d867f2a9fc524ebe88c3f5092d97',
|
||||||
'duration': 26,
|
'duration': 26,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'url': 'http://www.br.de/radio/br-klassik/sendungen/allegro/premiere-urauffuehrung-the-land-2015-dance-festival-muenchen-100.html',
|
'url': 'https://www.br-klassik.de/audio/peeping-tom-premierenkritik-dance-festival-muenchen-100.html',
|
||||||
'md5': '8b5b27c0b090f3b35eac4ab3f7a73d3d',
|
'md5': '8b5b27c0b090f3b35eac4ab3f7a73d3d',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '74c603c9-26d3-48bb-b85b-079aeed66e0b',
|
'id': '74c603c9-26d3-48bb-b85b-079aeed66e0b',
|
||||||
'ext': 'aac',
|
'ext': 'aac',
|
||||||
'title': 'Kurzweilig und sehr bewegend',
|
'title': 'Kurzweilig und sehr bewegend',
|
||||||
'description': '"The Land" von Peeping Tom: Kurzweilig und sehr bewegend',
|
'description': 'md5:0351996e3283d64adeb38ede91fac54e',
|
||||||
'duration': 296,
|
'duration': 296,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -57,7 +60,7 @@ class BRIE(InfoExtractor):
|
|||||||
'id': '6ba73750-d405-45d3-861d-1ce8c524e059',
|
'id': '6ba73750-d405-45d3-861d-1ce8c524e059',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Umweltbewusster Häuslebauer',
|
'title': 'Umweltbewusster Häuslebauer',
|
||||||
'description': 'Uwe Erdelt: Umweltbewusster Häuslebauer',
|
'description': 'md5:d52dae9792d00226348c1dbb13c9bae2',
|
||||||
'duration': 116,
|
'duration': 116,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -68,7 +71,7 @@ class BRIE(InfoExtractor):
|
|||||||
'id': 'd982c9ce-8648-4753-b358-98abb8aec43d',
|
'id': 'd982c9ce-8648-4753-b358-98abb8aec43d',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Folge 1 - Metaphysik',
|
'title': 'Folge 1 - Metaphysik',
|
||||||
'description': 'Kant für Anfänger: Folge 1 - Metaphysik',
|
'description': 'md5:bb659990e9e59905c3d41e369db1fbe3',
|
||||||
'duration': 893,
|
'duration': 893,
|
||||||
'uploader': 'Eva Maria Steimle',
|
'uploader': 'Eva Maria Steimle',
|
||||||
'upload_date': '20140117',
|
'upload_date': '20140117',
|
||||||
@ -77,28 +80,31 @@ class BRIE(InfoExtractor):
|
|||||||
]
|
]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
display_id = self._match_id(url)
|
base_url, display_id = re.search(self._VALID_URL, url).groups()
|
||||||
page = self._download_webpage(url, display_id)
|
page = self._download_webpage(url, display_id)
|
||||||
xml_url = self._search_regex(
|
xml_url = self._search_regex(
|
||||||
r"return BRavFramework\.register\(BRavFramework\('avPlayer_(?:[a-f0-9-]{36})'\)\.setup\({dataURL:'(/(?:[a-z0-9\-]+/)+[a-z0-9/~_.-]+)'}\)\);", page, 'XMLURL')
|
r"return BRavFramework\.register\(BRavFramework\('avPlayer_(?:[a-f0-9-]{36})'\)\.setup\({dataURL:'(/(?:[a-z0-9\-]+/)+[a-z0-9/~_.-]+)'}\)\);", page, 'XMLURL')
|
||||||
xml = self._download_xml(self._BASE_URL + xml_url, None)
|
xml = self._download_xml(base_url + xml_url, display_id)
|
||||||
|
|
||||||
medias = []
|
medias = []
|
||||||
|
|
||||||
for xml_media in xml.findall('video') + xml.findall('audio'):
|
for xml_media in xml.findall('video') + xml.findall('audio'):
|
||||||
|
media_id = xml_media.get('externalId')
|
||||||
media = {
|
media = {
|
||||||
'id': xml_media.get('externalId'),
|
'id': media_id,
|
||||||
'title': xml_media.find('title').text,
|
'title': xpath_text(xml_media, 'title', 'title', True),
|
||||||
'duration': parse_duration(xml_media.find('duration').text),
|
'duration': parse_duration(xpath_text(xml_media, 'duration')),
|
||||||
'formats': self._extract_formats(xml_media.find('assets')),
|
'formats': self._extract_formats(xpath_element(
|
||||||
'thumbnails': self._extract_thumbnails(xml_media.find('teaserImage/variants')),
|
xml_media, 'assets'), media_id),
|
||||||
'description': ' '.join(xml_media.find('shareTitle').text.splitlines()),
|
'thumbnails': self._extract_thumbnails(xpath_element(
|
||||||
'webpage_url': xml_media.find('permalink').text
|
xml_media, 'teaserImage/variants'), base_url),
|
||||||
|
'description': xpath_text(xml_media, 'desc'),
|
||||||
|
'webpage_url': xpath_text(xml_media, 'permalink'),
|
||||||
|
'uploader': xpath_text(xml_media, 'author'),
|
||||||
}
|
}
|
||||||
if xml_media.find('author').text:
|
broadcast_date = xpath_text(xml_media, 'broadcastDate')
|
||||||
media['uploader'] = xml_media.find('author').text
|
if broadcast_date:
|
||||||
if xml_media.find('broadcastDate').text:
|
media['upload_date'] = ''.join(reversed(broadcast_date.split('.')))
|
||||||
media['upload_date'] = ''.join(reversed(xml_media.find('broadcastDate').text.split('.')))
|
|
||||||
medias.append(media)
|
medias.append(media)
|
||||||
|
|
||||||
if len(medias) > 1:
|
if len(medias) > 1:
|
||||||
@ -109,35 +115,54 @@ class BRIE(InfoExtractor):
|
|||||||
raise ExtractorError('No media entries found')
|
raise ExtractorError('No media entries found')
|
||||||
return medias[0]
|
return medias[0]
|
||||||
|
|
||||||
def _extract_formats(self, assets):
|
def _extract_formats(self, assets, media_id):
|
||||||
|
formats = []
|
||||||
def text_or_none(asset, tag):
|
for asset in assets.findall('asset'):
|
||||||
elem = asset.find(tag)
|
format_url = xpath_text(asset, ['downloadUrl', 'url'])
|
||||||
return None if elem is None else elem.text
|
asset_type = asset.get('type')
|
||||||
|
if asset_type == 'HDS':
|
||||||
formats = [{
|
formats.extend(self._extract_f4m_formats(
|
||||||
'url': text_or_none(asset, 'downloadUrl'),
|
format_url + '?hdcore=3.2.0', media_id, f4m_id='hds', fatal=False))
|
||||||
'ext': text_or_none(asset, 'mediaType'),
|
elif asset_type == 'HLS':
|
||||||
'format_id': asset.get('type'),
|
formats.extend(self._extract_m3u8_formats(
|
||||||
'width': int_or_none(text_or_none(asset, 'frameWidth')),
|
format_url, media_id, 'mp4', 'm3u8_native', m3u8_id='hds', fatal=False))
|
||||||
'height': int_or_none(text_or_none(asset, 'frameHeight')),
|
else:
|
||||||
'tbr': int_or_none(text_or_none(asset, 'bitrateVideo')),
|
format_info = {
|
||||||
'abr': int_or_none(text_or_none(asset, 'bitrateAudio')),
|
'ext': xpath_text(asset, 'mediaType'),
|
||||||
'vcodec': text_or_none(asset, 'codecVideo'),
|
'width': int_or_none(xpath_text(asset, 'frameWidth')),
|
||||||
'acodec': text_or_none(asset, 'codecAudio'),
|
'height': int_or_none(xpath_text(asset, 'frameHeight')),
|
||||||
'container': text_or_none(asset, 'mediaType'),
|
'tbr': int_or_none(xpath_text(asset, 'bitrateVideo')),
|
||||||
'filesize': int_or_none(text_or_none(asset, 'size')),
|
'abr': int_or_none(xpath_text(asset, 'bitrateAudio')),
|
||||||
} for asset in assets.findall('asset')
|
'vcodec': xpath_text(asset, 'codecVideo'),
|
||||||
if asset.find('downloadUrl') is not None]
|
'acodec': xpath_text(asset, 'codecAudio'),
|
||||||
|
'container': xpath_text(asset, 'mediaType'),
|
||||||
|
'filesize': int_or_none(xpath_text(asset, 'size')),
|
||||||
|
}
|
||||||
|
format_url = self._proto_relative_url(format_url)
|
||||||
|
if format_url:
|
||||||
|
http_format_info = format_info.copy()
|
||||||
|
http_format_info.update({
|
||||||
|
'url': format_url,
|
||||||
|
'format_id': 'http-%s' % asset_type,
|
||||||
|
})
|
||||||
|
formats.append(http_format_info)
|
||||||
|
server_prefix = xpath_text(asset, 'serverPrefix')
|
||||||
|
if server_prefix:
|
||||||
|
rtmp_format_info = format_info.copy()
|
||||||
|
rtmp_format_info.update({
|
||||||
|
'url': server_prefix,
|
||||||
|
'play_path': xpath_text(asset, 'fileName'),
|
||||||
|
'format_id': 'rtmp-%s' % asset_type,
|
||||||
|
})
|
||||||
|
formats.append(rtmp_format_info)
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
return formats
|
return formats
|
||||||
|
|
||||||
def _extract_thumbnails(self, variants):
|
def _extract_thumbnails(self, variants, base_url):
|
||||||
thumbnails = [{
|
thumbnails = [{
|
||||||
'url': self._BASE_URL + variant.find('url').text,
|
'url': base_url + xpath_text(variant, 'url'),
|
||||||
'width': int_or_none(variant.find('width').text),
|
'width': int_or_none(xpath_text(variant, 'width')),
|
||||||
'height': int_or_none(variant.find('height').text),
|
'height': int_or_none(xpath_text(variant, 'height')),
|
||||||
} for variant in variants.findall('variant')]
|
} for variant in variants.findall('variant') if xpath_text(variant, 'url')]
|
||||||
thumbnails.sort(key=lambda x: x['width'] * x['height'], reverse=True)
|
thumbnails.sort(key=lambda x: x['width'] * x['height'], reverse=True)
|
||||||
return thumbnails
|
return thumbnails
|
||||||
|
@ -11,9 +11,9 @@ from ..compat import (
|
|||||||
compat_str,
|
compat_str,
|
||||||
compat_urllib_parse,
|
compat_urllib_parse,
|
||||||
compat_urllib_parse_urlparse,
|
compat_urllib_parse_urlparse,
|
||||||
compat_urllib_request,
|
|
||||||
compat_urlparse,
|
compat_urlparse,
|
||||||
compat_xml_parse_error,
|
compat_xml_parse_error,
|
||||||
|
compat_HTTPError,
|
||||||
)
|
)
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
determine_ext,
|
determine_ext,
|
||||||
@ -24,6 +24,7 @@ from ..utils import (
|
|||||||
js_to_json,
|
js_to_json,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
parse_iso8601,
|
parse_iso8601,
|
||||||
|
sanitized_Request,
|
||||||
unescapeHTML,
|
unescapeHTML,
|
||||||
unsmuggle_url,
|
unsmuggle_url,
|
||||||
)
|
)
|
||||||
@ -156,7 +157,7 @@ class BrightcoveLegacyIE(InfoExtractor):
|
|||||||
if playerKey is not None:
|
if playerKey is not None:
|
||||||
params['playerKey'] = playerKey
|
params['playerKey'] = playerKey
|
||||||
# The three fields hold the id of the video
|
# The three fields hold the id of the video
|
||||||
videoPlayer = find_param('@videoPlayer') or find_param('videoId') or find_param('videoID')
|
videoPlayer = find_param('@videoPlayer') or find_param('videoId') or find_param('videoID') or find_param('@videoList')
|
||||||
if videoPlayer is not None:
|
if videoPlayer is not None:
|
||||||
params['@videoPlayer'] = videoPlayer
|
params['@videoPlayer'] = videoPlayer
|
||||||
linkBase = find_param('linkBaseURL')
|
linkBase = find_param('linkBaseURL')
|
||||||
@ -250,7 +251,7 @@ class BrightcoveLegacyIE(InfoExtractor):
|
|||||||
|
|
||||||
def _get_video_info(self, video_id, query_str, query, referer=None):
|
def _get_video_info(self, video_id, query_str, query, referer=None):
|
||||||
request_url = self._FEDERATED_URL_TEMPLATE % query_str
|
request_url = self._FEDERATED_URL_TEMPLATE % query_str
|
||||||
req = compat_urllib_request.Request(request_url)
|
req = sanitized_Request(request_url)
|
||||||
linkBase = query.get('linkBaseURL')
|
linkBase = query.get('linkBaseURL')
|
||||||
if linkBase is not None:
|
if linkBase is not None:
|
||||||
referer = linkBase[0]
|
referer = linkBase[0]
|
||||||
@ -355,7 +356,7 @@ class BrightcoveLegacyIE(InfoExtractor):
|
|||||||
|
|
||||||
class BrightcoveNewIE(InfoExtractor):
|
class BrightcoveNewIE(InfoExtractor):
|
||||||
IE_NAME = 'brightcove:new'
|
IE_NAME = 'brightcove:new'
|
||||||
_VALID_URL = r'https?://players\.brightcove\.net/(?P<account_id>\d+)/(?P<player_id>[^/]+)_(?P<embed>[^/]+)/index\.html\?.*videoId=(?P<video_id>\d+)'
|
_VALID_URL = r'https?://players\.brightcove\.net/(?P<account_id>\d+)/(?P<player_id>[^/]+)_(?P<embed>[^/]+)/index\.html\?.*videoId=(?P<video_id>\d+|ref:[^&]+)'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://players.brightcove.net/929656772001/e41d32dc-ec74-459e-a845-6c69f7b724ea_default/index.html?videoId=4463358922001',
|
'url': 'http://players.brightcove.net/929656772001/e41d32dc-ec74-459e-a845-6c69f7b724ea_default/index.html?videoId=4463358922001',
|
||||||
'md5': 'c8100925723840d4b0d243f7025703be',
|
'md5': 'c8100925723840d4b0d243f7025703be',
|
||||||
@ -387,14 +388,28 @@ class BrightcoveNewIE(InfoExtractor):
|
|||||||
'params': {
|
'params': {
|
||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
# ref: prefixed video id
|
||||||
|
'url': 'http://players.brightcove.net/3910869709001/21519b5c-4b3b-4363-accb-bdc8f358f823_default/index.html?videoId=ref:7069442',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
# non numeric ref: prefixed video id
|
||||||
|
'url': 'http://players.brightcove.net/710858724001/default_default/index.html?videoId=ref:event-stream-356',
|
||||||
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _extract_url(webpage):
|
||||||
|
urls = BrightcoveNewIE._extract_urls(webpage)
|
||||||
|
return urls[0] if urls else None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _extract_urls(webpage):
|
def _extract_urls(webpage):
|
||||||
# Reference:
|
# Reference:
|
||||||
# 1. http://docs.brightcove.com/en/video-cloud/brightcove-player/guides/publish-video.html#setvideoiniframe
|
# 1. http://docs.brightcove.com/en/video-cloud/brightcove-player/guides/publish-video.html#setvideoiniframe
|
||||||
# 2. http://docs.brightcove.com/en/video-cloud/brightcove-player/guides/publish-video.html#setvideousingjavascript)
|
# 2. http://docs.brightcove.com/en/video-cloud/brightcove-player/guides/publish-video.html#setvideousingjavascript
|
||||||
# 3. http://docs.brightcove.com/en/video-cloud/brightcove-player/guides/embed-in-page.html
|
# 3. http://docs.brightcove.com/en/video-cloud/brightcove-player/guides/embed-in-page.html
|
||||||
|
# 4. https://support.brightcove.com/en/video-cloud/docs/dynamically-assigning-videos-player
|
||||||
|
|
||||||
entries = []
|
entries = []
|
||||||
|
|
||||||
@ -407,13 +422,14 @@ class BrightcoveNewIE(InfoExtractor):
|
|||||||
for video_id, account_id, player_id, embed in re.findall(
|
for video_id, account_id, player_id, embed in re.findall(
|
||||||
# According to examples from [3] it's unclear whether video id
|
# According to examples from [3] it's unclear whether video id
|
||||||
# may be optional and what to do when it is
|
# may be optional and what to do when it is
|
||||||
|
# According to [4] data-video-id may be prefixed with ref:
|
||||||
r'''(?sx)
|
r'''(?sx)
|
||||||
<video[^>]+
|
<video[^>]+
|
||||||
data-video-id=["\'](\d+)["\'][^>]*>.*?
|
data-video-id=["\'](\d+|ref:[^"\']+)["\'][^>]*>.*?
|
||||||
</video>.*?
|
</video>.*?
|
||||||
<script[^>]+
|
<script[^>]+
|
||||||
src=["\'](?:https?:)?//players\.brightcove\.net/
|
src=["\'](?:https?:)?//players\.brightcove\.net/
|
||||||
(\d+)/([\da-f-]+)_([^/]+)/index\.min\.js
|
(\d+)/([\da-f-]+)_([^/]+)/index(?:\.min)?\.js
|
||||||
''', webpage):
|
''', webpage):
|
||||||
entries.append(
|
entries.append(
|
||||||
'http://players.brightcove.net/%s/%s_%s/index.html?videoId=%s'
|
'http://players.brightcove.net/%s/%s_%s/index.html?videoId=%s'
|
||||||
@ -443,26 +459,35 @@ class BrightcoveNewIE(InfoExtractor):
|
|||||||
r'policyKey\s*:\s*(["\'])(?P<pk>.+?)\1',
|
r'policyKey\s*:\s*(["\'])(?P<pk>.+?)\1',
|
||||||
webpage, 'policy key', group='pk')
|
webpage, 'policy key', group='pk')
|
||||||
|
|
||||||
req = compat_urllib_request.Request(
|
req = sanitized_Request(
|
||||||
'https://edge.api.brightcove.com/playback/v1/accounts/%s/videos/%s'
|
'https://edge.api.brightcove.com/playback/v1/accounts/%s/videos/%s'
|
||||||
% (account_id, video_id),
|
% (account_id, video_id),
|
||||||
headers={'Accept': 'application/json;pk=%s' % policy_key})
|
headers={'Accept': 'application/json;pk=%s' % policy_key})
|
||||||
|
try:
|
||||||
json_data = self._download_json(req, video_id)
|
json_data = self._download_json(req, video_id)
|
||||||
|
except ExtractorError as e:
|
||||||
|
if isinstance(e.cause, compat_HTTPError) and e.cause.code == 403:
|
||||||
|
json_data = self._parse_json(e.cause.read().decode(), video_id)
|
||||||
|
raise ExtractorError(json_data[0]['message'], expected=True)
|
||||||
|
raise
|
||||||
|
|
||||||
title = json_data['name']
|
title = json_data['name']
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
for source in json_data.get('sources', []):
|
for source in json_data.get('sources', []):
|
||||||
|
container = source.get('container')
|
||||||
source_type = source.get('type')
|
source_type = source.get('type')
|
||||||
src = source.get('src')
|
src = source.get('src')
|
||||||
if source_type == 'application/x-mpegURL':
|
if source_type == 'application/x-mpegURL' or container == 'M2TS':
|
||||||
if not src:
|
if not src:
|
||||||
continue
|
continue
|
||||||
m3u8_formats = self._extract_m3u8_formats(
|
formats.extend(self._extract_m3u8_formats(
|
||||||
src, video_id, 'mp4', entry_protocol='m3u8_native',
|
src, video_id, 'mp4', entry_protocol='m3u8_native',
|
||||||
m3u8_id='hls', fatal=False)
|
m3u8_id='hls', fatal=False))
|
||||||
if m3u8_formats:
|
elif source_type == 'application/dash+xml':
|
||||||
formats.extend(m3u8_formats)
|
if not src:
|
||||||
|
continue
|
||||||
|
formats.extend(self._extract_mpd_formats(src, video_id, 'dash', fatal=False))
|
||||||
else:
|
else:
|
||||||
streaming_src = source.get('streaming_src')
|
streaming_src = source.get('streaming_src')
|
||||||
stream_name, app_name = source.get('stream_name'), source.get('app_name')
|
stream_name, app_name = source.get('stream_name'), source.get('app_name')
|
||||||
@ -470,15 +495,23 @@ class BrightcoveNewIE(InfoExtractor):
|
|||||||
continue
|
continue
|
||||||
tbr = float_or_none(source.get('avg_bitrate'), 1000)
|
tbr = float_or_none(source.get('avg_bitrate'), 1000)
|
||||||
height = int_or_none(source.get('height'))
|
height = int_or_none(source.get('height'))
|
||||||
|
width = int_or_none(source.get('width'))
|
||||||
f = {
|
f = {
|
||||||
'tbr': tbr,
|
'tbr': tbr,
|
||||||
'width': int_or_none(source.get('width')),
|
|
||||||
'height': height,
|
|
||||||
'filesize': int_or_none(source.get('size')),
|
'filesize': int_or_none(source.get('size')),
|
||||||
'container': source.get('container'),
|
'container': container,
|
||||||
'vcodec': source.get('codec'),
|
'ext': container.lower(),
|
||||||
'ext': source.get('container').lower(),
|
|
||||||
}
|
}
|
||||||
|
if width == 0 and height == 0:
|
||||||
|
f.update({
|
||||||
|
'vcodec': 'none',
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
f.update({
|
||||||
|
'width': width,
|
||||||
|
'height': height,
|
||||||
|
'vcodec': source.get('codec'),
|
||||||
|
})
|
||||||
|
|
||||||
def build_format_id(kind):
|
def build_format_id(kind):
|
||||||
format_id = kind
|
format_id = kind
|
||||||
|
@ -14,9 +14,10 @@ class BYUtvIE(InfoExtractor):
|
|||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'studio-c-season-5-episode-5',
|
'id': 'studio-c-season-5-episode-5',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'description': 'md5:5438d33774b6bdc662f9485a340401cc',
|
'description': 'md5:e07269172baff037f8e8bf9956bc9747',
|
||||||
'title': 'Season 5 Episode 5',
|
'title': 'Season 5 Episode 5',
|
||||||
'thumbnail': 're:^https?://.*\.jpg$'
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
'duration': 1486.486,
|
||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
|
@ -4,12 +4,13 @@ from __future__ import unicode_literals
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
from ..utils import js_to_json
|
||||||
|
|
||||||
|
|
||||||
class C56IE(InfoExtractor):
|
class C56IE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:(?:www|player)\.)?56\.com/(?:.+?/)?(?:v_|(?:play_album.+-))(?P<textid>.+?)\.(?:html|swf)'
|
_VALID_URL = r'https?://(?:(?:www|player)\.)?56\.com/(?:.+?/)?(?:v_|(?:play_album.+-))(?P<textid>.+?)\.(?:html|swf)'
|
||||||
IE_NAME = '56.com'
|
IE_NAME = '56.com'
|
||||||
_TEST = {
|
_TESTS = [{
|
||||||
'url': 'http://www.56.com/u39/v_OTM0NDA3MTY.html',
|
'url': 'http://www.56.com/u39/v_OTM0NDA3MTY.html',
|
||||||
'md5': 'e59995ac63d0457783ea05f93f12a866',
|
'md5': 'e59995ac63d0457783ea05f93f12a866',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
@ -18,12 +19,29 @@ class C56IE(InfoExtractor):
|
|||||||
'title': '网事知多少 第32期:车怒',
|
'title': '网事知多少 第32期:车怒',
|
||||||
'duration': 283.813,
|
'duration': 283.813,
|
||||||
},
|
},
|
||||||
}
|
}, {
|
||||||
|
'url': 'http://www.56.com/u47/v_MTM5NjQ5ODc2.html',
|
||||||
|
'md5': '',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '82247482',
|
||||||
|
'title': '爱的诅咒之杜鹃花开',
|
||||||
|
},
|
||||||
|
'playlist_count': 7,
|
||||||
|
'add_ie': ['Sohu'],
|
||||||
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url, flags=re.VERBOSE)
|
mobj = re.match(self._VALID_URL, url, flags=re.VERBOSE)
|
||||||
text_id = mobj.group('textid')
|
text_id = mobj.group('textid')
|
||||||
|
|
||||||
|
webpage = self._download_webpage(url, text_id)
|
||||||
|
sohu_video_info_str = self._search_regex(
|
||||||
|
r'var\s+sohuVideoInfo\s*=\s*({[^}]+});', webpage, 'Sohu video info', default=None)
|
||||||
|
if sohu_video_info_str:
|
||||||
|
sohu_video_info = self._parse_json(
|
||||||
|
sohu_video_info_str, text_id, transform_source=js_to_json)
|
||||||
|
return self.url_result(sohu_video_info['url'], 'Sohu')
|
||||||
|
|
||||||
page = self._download_json(
|
page = self._download_json(
|
||||||
'http://vxml.56.com/json/%s/' % text_id, text_id, 'Downloading video info')
|
'http://vxml.56.com/json/%s/' % text_id, text_id, 'Downloading video info')
|
||||||
|
|
||||||
|
@ -1,48 +0,0 @@
|
|||||||
# coding: utf-8
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
|
||||||
|
|
||||||
|
|
||||||
class Canal13clIE(InfoExtractor):
|
|
||||||
_VALID_URL = r'^http://(?:www\.)?13\.cl/(?:[^/?#]+/)*(?P<id>[^/?#]+)'
|
|
||||||
_TEST = {
|
|
||||||
'url': 'http://www.13.cl/t13/nacional/el-circulo-de-hierro-de-michelle-bachelet-en-su-regreso-a-la-moneda',
|
|
||||||
'md5': '4cb1fa38adcad8fea88487a078831755',
|
|
||||||
'info_dict': {
|
|
||||||
'id': '1403022125',
|
|
||||||
'display_id': 'el-circulo-de-hierro-de-michelle-bachelet-en-su-regreso-a-la-moneda',
|
|
||||||
'ext': 'mp4',
|
|
||||||
'title': 'El "círculo de hierro" de Michelle Bachelet en su regreso a La Moneda',
|
|
||||||
'description': '(Foto: Agencia Uno) En nueve días más, Michelle Bachelet va a asumir por segunda vez como presidenta de la República. Entre aquellos que la acompañarán hay caras que se repiten y otras que se consolidan en su entorno de colaboradores más cercanos.',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def _real_extract(self, url):
|
|
||||||
mobj = re.match(self._VALID_URL, url)
|
|
||||||
display_id = mobj.group('id')
|
|
||||||
|
|
||||||
webpage = self._download_webpage(url, display_id)
|
|
||||||
|
|
||||||
title = self._html_search_meta(
|
|
||||||
'twitter:title', webpage, 'title', fatal=True)
|
|
||||||
description = self._html_search_meta(
|
|
||||||
'twitter:description', webpage, 'description')
|
|
||||||
url = self._html_search_regex(
|
|
||||||
r'articuloVideo = \"(.*?)\"', webpage, 'url')
|
|
||||||
real_id = self._search_regex(
|
|
||||||
r'[^0-9]([0-9]{7,})[^0-9]', url, 'id', default=display_id)
|
|
||||||
thumbnail = self._html_search_regex(
|
|
||||||
r'articuloImagen = \"(.*?)\"', webpage, 'thumbnail')
|
|
||||||
|
|
||||||
return {
|
|
||||||
'id': real_id,
|
|
||||||
'display_id': display_id,
|
|
||||||
'url': url,
|
|
||||||
'title': title,
|
|
||||||
'description': description,
|
|
||||||
'ext': 'mp4',
|
|
||||||
'thumbnail': thumbnail,
|
|
||||||
}
|
|
@ -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 = []
|
||||||
|
for _, video_url in re.findall(r'file\s*=\s*(["\'])(.+?)\1', webpage):
|
||||||
if video_url.startswith('rtmp://'):
|
if video_url.startswith('rtmp://'):
|
||||||
rtmp = re.search(r'^(?P<url>rtmp://[^/]+/(?P<app>.+/))(?P<play_path>mp4:.+)$', video_url)
|
rtmp = re.search(
|
||||||
formats[0].update({
|
r'^(?P<url>rtmp://[^/]+/(?P<app>.+/))(?P<play_path>mp4:.+)$', video_url)
|
||||||
|
formats.append({
|
||||||
'url': rtmp.group('url'),
|
'url': rtmp.group('url'),
|
||||||
|
'format_id': 'rtmp',
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
'app': rtmp.group('app'),
|
'app': rtmp.group('app'),
|
||||||
'play_path': rtmp.group('play_path'),
|
'play_path': rtmp.group('play_path'),
|
||||||
'page_url': url,
|
'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')
|
||||||
|
@ -10,13 +10,14 @@ from ..utils import (
|
|||||||
unified_strdate,
|
unified_strdate,
|
||||||
url_basename,
|
url_basename,
|
||||||
qualities,
|
qualities,
|
||||||
|
int_or_none,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class CanalplusIE(InfoExtractor):
|
class CanalplusIE(InfoExtractor):
|
||||||
IE_DESC = 'canalplus.fr, piwiplus.fr and d8.tv'
|
IE_DESC = 'canalplus.fr, piwiplus.fr and d8.tv'
|
||||||
_VALID_URL = r'https?://(?:www\.(?P<site>canalplus\.fr|piwiplus\.fr|d8\.tv|itele\.fr)/.*?/(?P<path>.*)|player\.canalplus\.fr/#/(?P<id>[0-9]+))'
|
_VALID_URL = r'https?://(?:www\.(?P<site>canalplus\.fr|piwiplus\.fr|d8\.tv|itele\.fr)/.*?/(?P<path>.*)|player\.canalplus\.fr/#/(?P<id>[0-9]+))'
|
||||||
_VIDEO_INFO_TEMPLATE = 'http://service.canal-plus.com/video/rest/getVideosLiees/%s/%s'
|
_VIDEO_INFO_TEMPLATE = 'http://service.canal-plus.com/video/rest/getVideosLiees/%s/%s?format=json'
|
||||||
_SITE_ID_MAP = {
|
_SITE_ID_MAP = {
|
||||||
'canalplus.fr': 'cplus',
|
'canalplus.fr': 'cplus',
|
||||||
'piwiplus.fr': 'teletoon',
|
'piwiplus.fr': 'teletoon',
|
||||||
@ -26,10 +27,10 @@ class CanalplusIE(InfoExtractor):
|
|||||||
|
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://www.canalplus.fr/c-emissions/pid1830-c-zapping.html?vid=1263092',
|
'url': 'http://www.canalplus.fr/c-emissions/pid1830-c-zapping.html?vid=1263092',
|
||||||
'md5': 'b3481d7ca972f61e37420798d0a9d934',
|
'md5': '12164a6f14ff6df8bd628e8ba9b10b78',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '1263092',
|
'id': '1263092',
|
||||||
'ext': 'flv',
|
'ext': 'mp4',
|
||||||
'title': 'Le Zapping - 13/05/15',
|
'title': 'Le Zapping - 13/05/15',
|
||||||
'description': 'md5:09738c0d06be4b5d06a0940edb0da73f',
|
'description': 'md5:09738c0d06be4b5d06a0940edb0da73f',
|
||||||
'upload_date': '20150513',
|
'upload_date': '20150513',
|
||||||
@ -56,10 +57,10 @@ class CanalplusIE(InfoExtractor):
|
|||||||
'skip': 'videos get deleted after a while',
|
'skip': 'videos get deleted after a while',
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://www.itele.fr/france/video/aubervilliers-un-lycee-en-colere-111559',
|
'url': 'http://www.itele.fr/france/video/aubervilliers-un-lycee-en-colere-111559',
|
||||||
'md5': 'f3a46edcdf28006598ffaf5b30e6a2d4',
|
'md5': '38b8f7934def74f0d6f3ba6c036a5f82',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '1213714',
|
'id': '1213714',
|
||||||
'ext': 'flv',
|
'ext': 'mp4',
|
||||||
'title': 'Aubervilliers : un lycée en colère - Le 11/02/2015 à 06h45',
|
'title': 'Aubervilliers : un lycée en colère - Le 11/02/2015 à 06h45',
|
||||||
'description': 'md5:8216206ec53426ea6321321f3b3c16db',
|
'description': 'md5:8216206ec53426ea6321321f3b3c16db',
|
||||||
'upload_date': '20150211',
|
'upload_date': '20150211',
|
||||||
@ -82,15 +83,16 @@ class CanalplusIE(InfoExtractor):
|
|||||||
webpage, 'video id', group='id')
|
webpage, 'video id', group='id')
|
||||||
|
|
||||||
info_url = self._VIDEO_INFO_TEMPLATE % (site_id, video_id)
|
info_url = self._VIDEO_INFO_TEMPLATE % (site_id, video_id)
|
||||||
doc = self._download_xml(info_url, video_id, 'Downloading video XML')
|
video_data = self._download_json(info_url, video_id, 'Downloading video JSON')
|
||||||
|
|
||||||
video_info = [video for video in doc if video.find('ID').text == video_id][0]
|
if isinstance(video_data, list):
|
||||||
media = video_info.find('MEDIA')
|
video_data = [video for video in video_data if video.get('ID') == video_id][0]
|
||||||
infos = video_info.find('INFOS')
|
media = video_data['MEDIA']
|
||||||
|
infos = video_data['INFOS']
|
||||||
|
|
||||||
preference = qualities(['MOBILE', 'BAS_DEBIT', 'HAUT_DEBIT', 'HD', 'HLS', 'HDS'])
|
preference = qualities(['MOBILE', 'BAS_DEBIT', 'HAUT_DEBIT', 'HD'])
|
||||||
|
|
||||||
fmt_url = next(iter(media.find('VIDEOS'))).text
|
fmt_url = next(iter(media.get('VIDEOS')))
|
||||||
if '/geo' in fmt_url.lower():
|
if '/geo' in fmt_url.lower():
|
||||||
response = self._request_webpage(
|
response = self._request_webpage(
|
||||||
HEADRequest(fmt_url), video_id,
|
HEADRequest(fmt_url), video_id,
|
||||||
@ -101,35 +103,42 @@ class CanalplusIE(InfoExtractor):
|
|||||||
expected=True)
|
expected=True)
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
for fmt in media.find('VIDEOS'):
|
for format_id, format_url in media['VIDEOS'].items():
|
||||||
format_url = fmt.text
|
|
||||||
if not format_url:
|
if not format_url:
|
||||||
continue
|
continue
|
||||||
format_id = fmt.tag
|
|
||||||
if format_id == 'HLS':
|
if format_id == 'HLS':
|
||||||
formats.extend(self._extract_m3u8_formats(
|
formats.extend(self._extract_m3u8_formats(
|
||||||
format_url, video_id, 'mp4', preference=preference(format_id)))
|
format_url, video_id, 'mp4', 'm3u8_native', m3u8_id=format_id, fatal=False))
|
||||||
elif format_id == 'HDS':
|
elif format_id == 'HDS':
|
||||||
formats.extend(self._extract_f4m_formats(
|
formats.extend(self._extract_f4m_formats(
|
||||||
format_url + '?hdcore=2.11.3', video_id, preference=preference(format_id)))
|
format_url + '?hdcore=2.11.3', video_id, f4m_id=format_id, fatal=False))
|
||||||
else:
|
else:
|
||||||
formats.append({
|
formats.append({
|
||||||
'url': format_url,
|
# the secret extracted ya function in http://player.canalplus.fr/common/js/canalPlayer.js
|
||||||
|
'url': format_url + '?secret=pqzerjlsmdkjfoiuerhsdlfknaes',
|
||||||
'format_id': format_id,
|
'format_id': format_id,
|
||||||
'preference': preference(format_id),
|
'preference': preference(format_id),
|
||||||
})
|
})
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
thumbnails = [{
|
||||||
|
'id': image_id,
|
||||||
|
'url': image_url,
|
||||||
|
} for image_id, image_url in media.get('images', {}).items()]
|
||||||
|
|
||||||
|
titrage = infos['TITRAGE']
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'display_id': display_id,
|
'display_id': display_id,
|
||||||
'title': '%s - %s' % (infos.find('TITRAGE/TITRE').text,
|
'title': '%s - %s' % (titrage['TITRE'],
|
||||||
infos.find('TITRAGE/SOUS_TITRE').text),
|
titrage['SOUS_TITRE']),
|
||||||
'upload_date': unified_strdate(infos.find('PUBLICATION/DATE').text),
|
'upload_date': unified_strdate(infos.get('PUBLICATION', {}).get('DATE')),
|
||||||
'thumbnail': media.find('IMAGES/GRAND').text,
|
'thumbnails': thumbnails,
|
||||||
'description': infos.find('DESCRIPTION').text,
|
'description': infos.get('DESCRIPTION'),
|
||||||
'view_count': int(infos.find('NB_VUES').text),
|
'duration': int_or_none(infos.get('DURATION')),
|
||||||
'like_count': int(infos.find('NB_LIKES').text),
|
'view_count': int_or_none(infos.get('NB_VUES')),
|
||||||
'comment_count': int(infos.find('NB_COMMENTS').text),
|
'like_count': int_or_none(infos.get('NB_LIKES')),
|
||||||
|
'comment_count': int_or_none(infos.get('NB_COMMENTS')),
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
}
|
}
|
||||||
|
94
youtube_dl/extractor/canvas.py
Normal file
94
youtube_dl/extractor/canvas.py
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
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>[^/?#&]+)'
|
||||||
|
_TESTS = [{
|
||||||
|
'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,
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
# with subtitles
|
||||||
|
'url': 'http://www.canvas.be/video/panorama/2016/pieter-0167',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'mz-ast-5240ff21-2d30-4101-bba6-92b5ec67c625',
|
||||||
|
'display_id': 'pieter-0167',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Pieter 0167',
|
||||||
|
'description': 'md5:943cd30f48a5d29ba02c3a104dc4ec4e',
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
'duration': 2553.08,
|
||||||
|
'subtitles': {
|
||||||
|
'nl': [{
|
||||||
|
'ext': 'vtt',
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
'skip_download': True,
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
subtitles = {}
|
||||||
|
subtitle_urls = data.get('subtitleUrls')
|
||||||
|
if isinstance(subtitle_urls, list):
|
||||||
|
for subtitle in subtitle_urls:
|
||||||
|
subtitle_url = subtitle.get('url')
|
||||||
|
if subtitle_url and subtitle.get('type') == 'CLOSED':
|
||||||
|
subtitles.setdefault('nl', []).append({'url': subtitle_url})
|
||||||
|
|
||||||
|
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'),
|
||||||
|
'subtitles': subtitles,
|
||||||
|
}
|
113
youtube_dl/extractor/cbc.py
Normal file
113
youtube_dl/extractor/cbc.py
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import js_to_json
|
||||||
|
|
||||||
|
|
||||||
|
class CBCIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?cbc\.ca/(?:[^/]+/)+(?P<id>[^/?#]+)'
|
||||||
|
_TESTS = [{
|
||||||
|
# with mediaId
|
||||||
|
'url': 'http://www.cbc.ca/22minutes/videos/clips-season-23/don-cherry-play-offs',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '2682904050',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'Don Cherry – All-Stars',
|
||||||
|
'description': 'Don Cherry has a bee in his bonnet about AHL player John Scott because that guy’s got heart.',
|
||||||
|
'timestamp': 1454475540,
|
||||||
|
'upload_date': '20160203',
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
# rtmp download
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
# with clipId
|
||||||
|
'url': 'http://www.cbc.ca/archives/entry/1978-robin-williams-freestyles-on-90-minutes-live',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '2487345465',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'Robin Williams freestyles on 90 Minutes Live',
|
||||||
|
'description': 'Wacky American comedian Robin Williams shows off his infamous "freestyle" comedic talents while being interviewed on CBC\'s 90 Minutes Live.',
|
||||||
|
'upload_date': '19700101',
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
# rtmp download
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
# multiple iframes
|
||||||
|
'url': 'http://www.cbc.ca/natureofthings/blog/birds-eye-view-from-vancouvers-burrard-street-bridge-how-we-got-the-shot',
|
||||||
|
'playlist': [{
|
||||||
|
'info_dict': {
|
||||||
|
'id': '2680832926',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'An Eagle\'s-Eye View Off Burrard Bridge',
|
||||||
|
'description': 'Hercules the eagle flies from Vancouver\'s Burrard Bridge down to a nearby park with a mini-camera strapped to his back.',
|
||||||
|
'upload_date': '19700101',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
'info_dict': {
|
||||||
|
'id': '2658915080',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'Fly like an eagle!',
|
||||||
|
'description': 'Eagle equipped with a mini camera flies from the world\'s tallest tower',
|
||||||
|
'upload_date': '19700101',
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
'params': {
|
||||||
|
# rtmp download
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def suitable(cls, url):
|
||||||
|
return False if CBCPlayerIE.suitable(url) else super(CBCIE, cls).suitable(url)
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
display_id = self._match_id(url)
|
||||||
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
player_init = self._search_regex(
|
||||||
|
r'CBC\.APP\.Caffeine\.initInstance\(({.+?})\);', webpage, 'player init',
|
||||||
|
default=None)
|
||||||
|
if player_init:
|
||||||
|
player_info = self._parse_json(player_init, display_id, js_to_json)
|
||||||
|
media_id = player_info.get('mediaId')
|
||||||
|
if not media_id:
|
||||||
|
clip_id = player_info['clipId']
|
||||||
|
media_id = self._download_json(
|
||||||
|
'http://feed.theplatform.com/f/h9dtGB/punlNGjMlc1F?fields=id&byContent=byReleases%3DbyId%253D' + clip_id,
|
||||||
|
clip_id)['entries'][0]['id'].split('/')[-1]
|
||||||
|
return self.url_result('cbcplayer:%s' % media_id, 'CBCPlayer', media_id)
|
||||||
|
else:
|
||||||
|
entries = [self.url_result('cbcplayer:%s' % media_id, 'CBCPlayer', media_id) for media_id in re.findall(r'<iframe[^>]+src="[^"]+?mediaId=(\d+)"', webpage)]
|
||||||
|
return self.playlist_result(entries)
|
||||||
|
|
||||||
|
|
||||||
|
class CBCPlayerIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'(?:cbcplayer:|https?://(?:www\.)?cbc\.ca/(?:player/play/|i/caffeine/syndicate/\?mediaId=))(?P<id>\d+)'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.cbc.ca/player/play/2683190193',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '2683190193',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 'Gerry Runs a Sweat Shop',
|
||||||
|
'description': 'md5:b457e1c01e8ff408d9d801c1c2cd29b0',
|
||||||
|
'timestamp': 1455067800,
|
||||||
|
'upload_date': '20160210',
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
# rtmp download
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
video_id = self._match_id(url)
|
||||||
|
return self.url_result(
|
||||||
|
'http://feed.theplatform.com/f/ExhSPC/vms_5akSXx4Ng_Zn?byGuid=%s' % video_id,
|
||||||
|
'ThePlatformFeed', video_id)
|
@ -1,8 +1,10 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import compat_urllib_request
|
from ..utils import (
|
||||||
from ..utils import smuggle_url
|
sanitized_Request,
|
||||||
|
smuggle_url,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class CBSIE(InfoExtractor):
|
class CBSIE(InfoExtractor):
|
||||||
@ -48,7 +50,7 @@ class CBSIE(InfoExtractor):
|
|||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
display_id = self._match_id(url)
|
display_id = self._match_id(url)
|
||||||
request = compat_urllib_request.Request(url)
|
request = sanitized_Request(url)
|
||||||
# Android UA is served with higher quality (720p) streams (see
|
# Android UA is served with higher quality (720p) streams (see
|
||||||
# https://github.com/rg3/youtube-dl/issues/7490)
|
# https://github.com/rg3/youtube-dl/issues/7490)
|
||||||
request.add_header('User-Agent', 'Mozilla/5.0 (Linux; Android 4.4; Nexus 5)')
|
request.add_header('User-Agent', 'Mozilla/5.0 (Linux; Android 4.4; Nexus 5)')
|
||||||
|
@ -1,15 +1,17 @@
|
|||||||
# 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 .theplatform import ThePlatformIE
|
||||||
|
from ..utils import (
|
||||||
|
parse_duration,
|
||||||
|
find_xpath_attr,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
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 = [
|
||||||
{
|
{
|
||||||
@ -30,56 +32,57 @@ 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 _parse_smil_subtitles(self, smil, namespace=None, subtitles_lang='en'):
|
||||||
|
closed_caption_e = find_xpath_attr(smil, self._xpath_ns('.//param', namespace), 'name', 'ClosedCaptionURL')
|
||||||
|
return {
|
||||||
|
'en': [{
|
||||||
|
'ext': 'ttml',
|
||||||
|
'url': closed_caption_e.attrib['value'],
|
||||||
|
}]
|
||||||
|
} if closed_caption_e is not None and closed_caption_e.attrib.get('value') else []
|
||||||
|
|
||||||
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 = {}
|
||||||
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
|
||||||
fmt = {
|
release_url = 'http://link.theplatform.com/s/dJ5BDC/%s?format=SMIL&mbr=true' % pid
|
||||||
'url': uri,
|
tp_formats, tp_subtitles = self._extract_theplatform_smil(release_url, video_id, 'Downloading %s SMIL data' % pid)
|
||||||
'format_id': format_id,
|
formats.extend(tp_formats)
|
||||||
}
|
subtitles = self._merge_subtitles(subtitles, tp_subtitles)
|
||||||
if uri.startswith('rtmp'):
|
self._sort_formats(formats)
|
||||||
play_path = re.sub(
|
|
||||||
r'{slistFilePath}', '',
|
|
||||||
uri.split('<break>')[-1].split('{break}')[-1])
|
|
||||||
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,
|
||||||
@ -87,4 +90,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,
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ import re
|
|||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
from ..compat import (
|
||||||
compat_urllib_request,
|
|
||||||
compat_urllib_parse,
|
compat_urllib_parse,
|
||||||
compat_urllib_parse_unquote,
|
compat_urllib_parse_unquote,
|
||||||
compat_urllib_parse_urlparse,
|
compat_urllib_parse_urlparse,
|
||||||
@ -13,6 +12,7 @@ from ..compat import (
|
|||||||
from ..utils import (
|
from ..utils import (
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
float_or_none,
|
float_or_none,
|
||||||
|
sanitized_Request,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -100,7 +100,7 @@ class CeskaTelevizeIE(InfoExtractor):
|
|||||||
'requestSource': 'iVysilani',
|
'requestSource': 'iVysilani',
|
||||||
}
|
}
|
||||||
|
|
||||||
req = compat_urllib_request.Request(
|
req = sanitized_Request(
|
||||||
'http://www.ceskatelevize.cz/ivysilani/ajax/get-client-playlist',
|
'http://www.ceskatelevize.cz/ivysilani/ajax/get-client-playlist',
|
||||||
data=compat_urllib_parse.urlencode(data))
|
data=compat_urllib_parse.urlencode(data))
|
||||||
|
|
||||||
@ -115,7 +115,7 @@ class CeskaTelevizeIE(InfoExtractor):
|
|||||||
if playlist_url == 'error_region':
|
if playlist_url == 'error_region':
|
||||||
raise ExtractorError(NOT_AVAILABLE_STRING, expected=True)
|
raise ExtractorError(NOT_AVAILABLE_STRING, expected=True)
|
||||||
|
|
||||||
req = compat_urllib_request.Request(compat_urllib_parse_unquote(playlist_url))
|
req = sanitized_Request(compat_urllib_parse_unquote(playlist_url))
|
||||||
req.add_header('Referer', url)
|
req.add_header('Referer', url)
|
||||||
|
|
||||||
playlist_title = self._og_search_title(webpage)
|
playlist_title = self._og_search_title(webpage)
|
||||||
@ -177,16 +177,16 @@ class CeskaTelevizeIE(InfoExtractor):
|
|||||||
for divider in [1000, 60, 60, 100]:
|
for divider in [1000, 60, 60, 100]:
|
||||||
components.append(msec % divider)
|
components.append(msec % divider)
|
||||||
msec //= divider
|
msec //= divider
|
||||||
return "{3:02}:{2:02}:{1:02},{0:03}".format(*components)
|
return '{3:02}:{2:02}:{1:02},{0:03}'.format(*components)
|
||||||
|
|
||||||
def _fix_subtitle(subtitle):
|
def _fix_subtitle(subtitle):
|
||||||
for line in subtitle.splitlines():
|
for line in subtitle.splitlines():
|
||||||
m = re.match(r"^\s*([0-9]+);\s*([0-9]+)\s+([0-9]+)\s*$", line)
|
m = re.match(r'^\s*([0-9]+);\s*([0-9]+)\s+([0-9]+)\s*$', line)
|
||||||
if m:
|
if m:
|
||||||
yield m.group(1)
|
yield m.group(1)
|
||||||
start, stop = (_msectotimecode(int(t)) for t in m.groups()[1:])
|
start, stop = (_msectotimecode(int(t)) for t in m.groups()[1:])
|
||||||
yield "{0} --> {1}".format(start, stop)
|
yield '{0} --> {1}'.format(start, stop)
|
||||||
else:
|
else:
|
||||||
yield line
|
yield line
|
||||||
|
|
||||||
return "\r\n".join(_fix_subtitle(subtitles))
|
return '\r\n'.join(_fix_subtitle(subtitles))
|
||||||
|
@ -23,6 +23,8 @@ class ChaturbateIE(InfoExtractor):
|
|||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
}]
|
}]
|
||||||
|
|
||||||
|
_ROOM_OFFLINE = 'Room is currently offline'
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
|
|
||||||
@ -34,9 +36,16 @@ class ChaturbateIE(InfoExtractor):
|
|||||||
|
|
||||||
if not m3u8_url:
|
if not m3u8_url:
|
||||||
error = self._search_regex(
|
error = self._search_regex(
|
||||||
r'<span[^>]+class=(["\'])desc_span\1[^>]*>(?P<error>[^<]+)</span>',
|
[r'<span[^>]+class=(["\'])desc_span\1[^>]*>(?P<error>[^<]+)</span>',
|
||||||
webpage, 'error', group='error')
|
r'<div[^>]+id=(["\'])defchat\1[^>]*>\s*<p><strong>(?P<error>[^<]+)<'],
|
||||||
|
webpage, 'error', group='error', default=None)
|
||||||
|
if not error:
|
||||||
|
if any(p not in webpage for p in (
|
||||||
|
self._ROOM_OFFLINE, 'offline_tipping', 'tip_offline')):
|
||||||
|
error = self._ROOM_OFFLINE
|
||||||
|
if error:
|
||||||
raise ExtractorError(error, expected=True)
|
raise ExtractorError(error, expected=True)
|
||||||
|
raise ExtractorError('Unable to find stream URL')
|
||||||
|
|
||||||
formats = self._extract_m3u8_formats(m3u8_url, video_id, ext='mp4')
|
formats = self._extract_m3u8_formats(m3u8_url, video_id, ext='mp4')
|
||||||
|
|
||||||
|
@ -5,7 +5,6 @@ import re
|
|||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import ExtractorError
|
from ..utils import ExtractorError
|
||||||
from .bliptv import BlipTVIE
|
|
||||||
from .screenwavemedia import ScreenwaveMediaIE
|
from .screenwavemedia import ScreenwaveMediaIE
|
||||||
|
|
||||||
|
|
||||||
@ -22,6 +21,10 @@ class CinemassacreIE(InfoExtractor):
|
|||||||
'title': '“Angry Video Game Nerd: The Movie” – Trailer',
|
'title': '“Angry Video Game Nerd: The Movie” – Trailer',
|
||||||
'description': 'md5:fb87405fcb42a331742a0dce2708560b',
|
'description': 'md5:fb87405fcb42a331742a0dce2708560b',
|
||||||
},
|
},
|
||||||
|
'params': {
|
||||||
|
# m3u8 download
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'url': 'http://cinemassacre.com/2013/10/02/the-mummys-hand-1940',
|
'url': 'http://cinemassacre.com/2013/10/02/the-mummys-hand-1940',
|
||||||
@ -32,31 +35,34 @@ class CinemassacreIE(InfoExtractor):
|
|||||||
'upload_date': '20131002',
|
'upload_date': '20131002',
|
||||||
'title': 'The Mummy’s Hand (1940)',
|
'title': 'The Mummy’s Hand (1940)',
|
||||||
},
|
},
|
||||||
|
'params': {
|
||||||
|
# m3u8 download
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
# blip.tv embedded video
|
# Youtube embedded video
|
||||||
'url': 'http://cinemassacre.com/2006/12/07/chronologically-confused-about-bad-movie-and-video-game-sequel-titles/',
|
'url': 'http://cinemassacre.com/2006/12/07/chronologically-confused-about-bad-movie-and-video-game-sequel-titles/',
|
||||||
'md5': 'ca9b3c8dd5a66f9375daeb5135f5a3de',
|
'md5': 'ec9838a5520ef5409b3e4e42fcb0a3b9',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '4065369',
|
'id': 'OEVzPCY2T-g',
|
||||||
'ext': 'flv',
|
'ext': 'webm',
|
||||||
'title': 'AVGN: Chronologically Confused about Bad Movie and Video Game Sequel Titles',
|
'title': 'AVGN: Chronologically Confused about Bad Movie and Video Game Sequel Titles',
|
||||||
'upload_date': '20061207',
|
'upload_date': '20061207',
|
||||||
'uploader': 'cinemassacre',
|
'uploader': 'Cinemassacre',
|
||||||
'uploader_id': '250778',
|
'uploader_id': 'JamesNintendoNerd',
|
||||||
'timestamp': 1283233867,
|
'description': 'md5:784734696c2b8b7f4b8625cc799e07f6',
|
||||||
'description': 'md5:0a108c78d130676b207d0f6d029ecffd',
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
# Youtube embedded video
|
# Youtube embedded video
|
||||||
'url': 'http://cinemassacre.com/2006/09/01/mckids/',
|
'url': 'http://cinemassacre.com/2006/09/01/mckids/',
|
||||||
'md5': '6eb30961fa795fedc750eac4881ad2e1',
|
'md5': '7393c4e0f54602ad110c793eb7a6513a',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'FnxsNhuikpo',
|
'id': 'FnxsNhuikpo',
|
||||||
'ext': 'mp4',
|
'ext': 'webm',
|
||||||
'upload_date': '20060901',
|
'upload_date': '20060901',
|
||||||
'uploader': 'Cinemassacre Extras',
|
'uploader': 'Cinemassacre Extra',
|
||||||
'description': 'md5:de9b751efa9e45fbaafd9c8a1123ed53',
|
'description': 'md5:de9b751efa9e45fbaafd9c8a1123ed53',
|
||||||
'uploader_id': 'Cinemassacre',
|
'uploader_id': 'Cinemassacre',
|
||||||
'title': 'AVGN: McKids',
|
'title': 'AVGN: McKids',
|
||||||
@ -71,7 +77,11 @@ class CinemassacreIE(InfoExtractor):
|
|||||||
'description': 'Let’s Play Mario Kart 64 !! Mario Kart 64 is a classic go-kart racing game released for the Nintendo 64 (N64). Today James & Mike do 4 player Battle Mode with Kyle and Bootsy!',
|
'description': 'Let’s Play Mario Kart 64 !! Mario Kart 64 is a classic go-kart racing game released for the Nintendo 64 (N64). Today James & Mike do 4 player Battle Mode with Kyle and Bootsy!',
|
||||||
'title': 'Mario Kart 64 (Nintendo 64) James & Mike Mondays',
|
'title': 'Mario Kart 64 (Nintendo 64) James & Mike Mondays',
|
||||||
'upload_date': '20150525',
|
'upload_date': '20150525',
|
||||||
}
|
},
|
||||||
|
'params': {
|
||||||
|
# m3u8 download
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -88,8 +98,6 @@ class CinemassacreIE(InfoExtractor):
|
|||||||
r'<iframe[^>]+src="(?P<url>(?:https?:)?//(?:[^.]+\.)?youtube\.com/.+?)"',
|
r'<iframe[^>]+src="(?P<url>(?:https?:)?//(?:[^.]+\.)?youtube\.com/.+?)"',
|
||||||
],
|
],
|
||||||
webpage, 'player data URL', default=None, group='url')
|
webpage, 'player data URL', default=None, group='url')
|
||||||
if not playerdata_url:
|
|
||||||
playerdata_url = BlipTVIE._extract_url(webpage)
|
|
||||||
if not playerdata_url:
|
if not playerdata_url:
|
||||||
raise ExtractorError('Unable to find player data')
|
raise ExtractorError('Unable to find player data')
|
||||||
|
|
||||||
|
@ -1,14 +1,9 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
determine_ext,
|
|
||||||
int_or_none,
|
int_or_none,
|
||||||
js_to_json,
|
unified_strdate,
|
||||||
parse_iso8601,
|
|
||||||
remove_end,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -21,48 +16,47 @@ class ClipfishIE(InfoExtractor):
|
|||||||
'id': '3966754',
|
'id': '3966754',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'FIFA 14 - E3 2013 Trailer',
|
'title': 'FIFA 14 - E3 2013 Trailer',
|
||||||
'timestamp': 1370938118,
|
'description': 'Video zu FIFA 14: E3 2013 Trailer',
|
||||||
'upload_date': '20130611',
|
'upload_date': '20130611',
|
||||||
'duration': 82,
|
'duration': 82,
|
||||||
|
'view_count': int,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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_info = self._download_json(
|
||||||
|
'http://www.clipfish.de/devapi/id/%s?format=json&apikey=hbbtv' % video_id,
|
||||||
video_info = self._parse_json(
|
video_id)['items'][0]
|
||||||
js_to_json(self._html_search_regex(
|
|
||||||
'(?s)videoObject\s*=\s*({.+?});', webpage, 'video object')),
|
|
||||||
video_id)
|
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
for video_url in re.findall(r'var\s+videourl\s*=\s*"([^"]+)"', webpage):
|
|
||||||
ext = determine_ext(video_url)
|
m3u8_url = video_info.get('media_videourl_hls')
|
||||||
if ext == 'm3u8':
|
if m3u8_url:
|
||||||
formats.append({
|
formats.append({
|
||||||
'url': video_url.replace('de.hls.fra.clipfish.de', 'hls.fra.clipfish.de'),
|
'url': m3u8_url.replace('de.hls.fra.clipfish.de', 'hls.fra.clipfish.de'),
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'format_id': 'hls',
|
'format_id': 'hls',
|
||||||
})
|
})
|
||||||
else:
|
|
||||||
formats.append({
|
|
||||||
'url': video_url,
|
|
||||||
'format_id': ext,
|
|
||||||
})
|
|
||||||
self._sort_formats(formats)
|
|
||||||
|
|
||||||
title = remove_end(self._og_search_title(webpage), ' - Video')
|
mp4_url = video_info.get('media_videourl')
|
||||||
thumbnail = self._og_search_thumbnail(webpage)
|
if mp4_url:
|
||||||
duration = int_or_none(video_info.get('length'))
|
formats.append({
|
||||||
timestamp = parse_iso8601(self._html_search_meta('uploadDate', webpage, 'upload date'))
|
'url': mp4_url,
|
||||||
|
'format_id': 'mp4',
|
||||||
|
'width': int_or_none(video_info.get('width')),
|
||||||
|
'height': int_or_none(video_info.get('height')),
|
||||||
|
'tbr': int_or_none(video_info.get('bitrate')),
|
||||||
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'title': title,
|
'title': video_info['title'],
|
||||||
|
'description': video_info.get('descr'),
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
'thumbnail': thumbnail,
|
'thumbnail': video_info.get('media_content_thumbnail_large') or video_info.get('media_thumbnail'),
|
||||||
'duration': duration,
|
'duration': int_or_none(video_info.get('media_length')),
|
||||||
'timestamp': timestamp,
|
'upload_date': unified_strdate(video_info.get('pubDate')),
|
||||||
|
'view_count': int_or_none(video_info.get('media_views'))
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import determine_ext
|
from ..utils import int_or_none
|
||||||
|
|
||||||
|
|
||||||
_translation_table = {
|
_translation_table = {
|
||||||
@ -42,31 +42,26 @@ class CliphunterIE(InfoExtractor):
|
|||||||
video_title = self._search_regex(
|
video_title = self._search_regex(
|
||||||
r'mediaTitle = "([^"]+)"', webpage, 'title')
|
r'mediaTitle = "([^"]+)"', webpage, 'title')
|
||||||
|
|
||||||
fmts = {}
|
gexo_files = self._parse_json(
|
||||||
for fmt in ('mp4', 'flv'):
|
self._search_regex(
|
||||||
fmt_list = self._parse_json(self._search_regex(
|
r'var\s+gexoFiles\s*=\s*({.+?});', webpage, 'gexo files'),
|
||||||
r'var %sjson\s*=\s*(\[.*?\]);' % fmt, webpage, '%s formats' % fmt), video_id)
|
video_id)
|
||||||
for f in fmt_list:
|
|
||||||
fmts[f['fname']] = _decode(f['sUrl'])
|
|
||||||
|
|
||||||
qualities = self._parse_json(self._search_regex(
|
|
||||||
r'var player_btns\s*=\s*(.*?);\n', webpage, 'quality info'), video_id)
|
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
for fname, url in fmts.items():
|
for format_id, f in gexo_files.items():
|
||||||
f = {
|
video_url = f.get('url')
|
||||||
'url': url,
|
if not video_url:
|
||||||
}
|
continue
|
||||||
if fname in qualities:
|
fmt = f.get('fmt')
|
||||||
qual = qualities[fname]
|
height = f.get('h')
|
||||||
f.update({
|
format_id = '%s_%sp' % (fmt, height) if fmt and height else format_id
|
||||||
'format_id': '%s_%sp' % (determine_ext(url), qual['h']),
|
formats.append({
|
||||||
'width': qual['w'],
|
'url': _decode(video_url),
|
||||||
'height': qual['h'],
|
'format_id': format_id,
|
||||||
'tbr': qual['br'],
|
'width': int_or_none(f.get('w')),
|
||||||
|
'height': int_or_none(height),
|
||||||
|
'tbr': int_or_none(f.get('br')),
|
||||||
})
|
})
|
||||||
formats.append(f)
|
|
||||||
|
|
||||||
self._sort_formats(formats)
|
self._sort_formats(formats)
|
||||||
|
|
||||||
thumbnail = self._search_regex(
|
thumbnail = self._search_regex(
|
||||||
|
@ -1,15 +1,11 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import json
|
from .theplatform import ThePlatformIE
|
||||||
|
from ..utils import int_or_none
|
||||||
from .common import InfoExtractor
|
|
||||||
from ..utils import (
|
|
||||||
ExtractorError,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class CNETIE(InfoExtractor):
|
class CNETIE(ThePlatformIE):
|
||||||
_VALID_URL = r'https?://(?:www\.)?cnet\.com/videos/(?P<id>[^/]+)/'
|
_VALID_URL = r'https?://(?:www\.)?cnet\.com/videos/(?P<id>[^/]+)/'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://www.cnet.com/videos/hands-on-with-microsofts-windows-8-1-update/',
|
'url': 'http://www.cnet.com/videos/hands-on-with-microsofts-windows-8-1-update/',
|
||||||
@ -18,25 +14,20 @@ class CNETIE(InfoExtractor):
|
|||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
'title': 'Hands-on with Microsoft Windows 8.1 Update',
|
'title': 'Hands-on with Microsoft Windows 8.1 Update',
|
||||||
'description': 'The new update to the Windows 8 OS brings improved performance for mouse and keyboard users.',
|
'description': 'The new update to the Windows 8 OS brings improved performance for mouse and keyboard users.',
|
||||||
'thumbnail': 're:^http://.*/flmswindows8.jpg$',
|
|
||||||
'uploader_id': '6085384d-619e-11e3-b231-14feb5ca9861',
|
'uploader_id': '6085384d-619e-11e3-b231-14feb5ca9861',
|
||||||
'uploader': 'Sarah Mitroff',
|
'uploader': 'Sarah Mitroff',
|
||||||
|
'duration': 70,
|
||||||
},
|
},
|
||||||
'params': {
|
|
||||||
'skip_download': 'requires rtmpdump',
|
|
||||||
}
|
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://www.cnet.com/videos/whiny-pothole-tweets-at-local-government-when-hit-by-cars-tomorrow-daily-187/',
|
'url': 'http://www.cnet.com/videos/whiny-pothole-tweets-at-local-government-when-hit-by-cars-tomorrow-daily-187/',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '56527b93-d25d-44e3-b738-f989ce2e49ba',
|
'id': '56527b93-d25d-44e3-b738-f989ce2e49ba',
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
|
'title': 'Whiny potholes tweet at local government when hit by cars (Tomorrow Daily 187)',
|
||||||
'description': 'Khail and Ashley wonder what other civic woes can be solved by self-tweeting objects, investigate a new kind of VR camera and watch an origami robot self-assemble, walk, climb, dig and dissolve. #TDPothole',
|
'description': 'Khail and Ashley wonder what other civic woes can be solved by self-tweeting objects, investigate a new kind of VR camera and watch an origami robot self-assemble, walk, climb, dig and dissolve. #TDPothole',
|
||||||
'uploader_id': 'b163284d-6b73-44fc-b3e6-3da66c392d40',
|
'uploader_id': 'b163284d-6b73-44fc-b3e6-3da66c392d40',
|
||||||
'uploader': 'Ashley Esqueda',
|
'uploader': 'Ashley Esqueda',
|
||||||
'title': 'Whiny potholes tweet at local government when hit by cars (Tomorrow Daily 187)',
|
'duration': 1482,
|
||||||
},
|
|
||||||
'params': {
|
|
||||||
'skip_download': True, # requires rtmpdump
|
|
||||||
},
|
},
|
||||||
}]
|
}]
|
||||||
|
|
||||||
@ -45,26 +36,13 @@ class CNETIE(InfoExtractor):
|
|||||||
webpage = self._download_webpage(url, display_id)
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
|
||||||
data_json = self._html_search_regex(
|
data_json = self._html_search_regex(
|
||||||
r"<div class=\"cnetVideoPlayer\"\s+.*?data-cnet-video-options='([^']+)'",
|
r"data-cnet-video(?:-uvp)?-options='([^']+)'",
|
||||||
webpage, 'data json')
|
webpage, 'data json')
|
||||||
data = json.loads(data_json)
|
data = self._parse_json(data_json, display_id)
|
||||||
vdata = data['video']
|
vdata = data.get('video') or data['videos'][0]
|
||||||
if not vdata:
|
|
||||||
vdata = data['videos'][0]
|
|
||||||
if not vdata:
|
|
||||||
raise ExtractorError('Cannot find video data')
|
|
||||||
|
|
||||||
mpx_account = data['config']['players']['default']['mpx_account']
|
|
||||||
vid = vdata['files'].get('rtmp', vdata['files']['hds'])
|
|
||||||
tp_link = 'http://link.theplatform.com/s/%s/%s' % (mpx_account, vid)
|
|
||||||
|
|
||||||
video_id = vdata['id']
|
video_id = vdata['id']
|
||||||
title = vdata.get('headline')
|
title = vdata['title']
|
||||||
if title is None:
|
|
||||||
title = vdata.get('title')
|
|
||||||
if title is None:
|
|
||||||
raise ExtractorError('Cannot find title!')
|
|
||||||
thumbnail = vdata.get('image', {}).get('path')
|
|
||||||
author = vdata.get('author')
|
author = vdata.get('author')
|
||||||
if author:
|
if author:
|
||||||
uploader = '%s %s' % (author['firstName'], author['lastName'])
|
uploader = '%s %s' % (author['firstName'], author['lastName'])
|
||||||
@ -73,13 +51,32 @@ class CNETIE(InfoExtractor):
|
|||||||
uploader = None
|
uploader = None
|
||||||
uploader_id = None
|
uploader_id = None
|
||||||
|
|
||||||
|
metadata = self.get_metadata('kYEXFC/%s' % list(vdata['files'].values())[0], video_id)
|
||||||
|
description = vdata.get('description') or metadata.get('description')
|
||||||
|
duration = int_or_none(vdata.get('duration')) or metadata.get('duration')
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
subtitles = {}
|
||||||
|
for (fkey, vid) in vdata['files'].items():
|
||||||
|
if fkey == 'hls_phone' and 'hls_tablet' in vdata['files']:
|
||||||
|
continue
|
||||||
|
release_url = 'http://link.theplatform.com/s/kYEXFC/%s?format=SMIL&mbr=true' % vid
|
||||||
|
if fkey == 'hds':
|
||||||
|
release_url += '&manifest=f4m'
|
||||||
|
tp_formats, tp_subtitles = self._extract_theplatform_smil(release_url, video_id, 'Downloading %s SMIL data' % fkey)
|
||||||
|
formats.extend(tp_formats)
|
||||||
|
subtitles = self._merge_subtitles(subtitles, tp_subtitles)
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'_type': 'url_transparent',
|
|
||||||
'url': tp_link,
|
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'display_id': display_id,
|
'display_id': display_id,
|
||||||
'title': title,
|
'title': title,
|
||||||
|
'description': description,
|
||||||
|
'thumbnail': metadata.get('thumbnail'),
|
||||||
|
'duration': duration,
|
||||||
'uploader': uploader,
|
'uploader': uploader,
|
||||||
'uploader_id': uploader_id,
|
'uploader_id': uploader_id,
|
||||||
'thumbnail': thumbnail,
|
'subtitles': subtitles,
|
||||||
|
'formats': formats,
|
||||||
}
|
}
|
||||||
|
@ -26,14 +26,14 @@ class CNNIE(InfoExtractor):
|
|||||||
'upload_date': '20130609',
|
'upload_date': '20130609',
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
"url": "http://edition.cnn.com/video/?/video/us/2013/08/21/sot-student-gives-epic-speech.georgia-institute-of-technology&utm_source=feedburner&utm_medium=feed&utm_campaign=Feed%3A+rss%2Fcnn_topstories+%28RSS%3A+Top+Stories%29",
|
'url': 'http://edition.cnn.com/video/?/video/us/2013/08/21/sot-student-gives-epic-speech.georgia-institute-of-technology&utm_source=feedburner&utm_medium=feed&utm_campaign=Feed%3A+rss%2Fcnn_topstories+%28RSS%3A+Top+Stories%29',
|
||||||
"md5": "b5cc60c60a3477d185af8f19a2a26f4e",
|
'md5': 'b5cc60c60a3477d185af8f19a2a26f4e',
|
||||||
"info_dict": {
|
'info_dict': {
|
||||||
'id': 'us/2013/08/21/sot-student-gives-epic-speech.georgia-institute-of-technology',
|
'id': 'us/2013/08/21/sot-student-gives-epic-speech.georgia-institute-of-technology',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
"title": "Student's epic speech stuns new freshmen",
|
'title': "Student's epic speech stuns new freshmen",
|
||||||
"description": "A Georgia Tech student welcomes the incoming freshmen with an epic speech backed by music from \"2001: A Space Odyssey.\"",
|
'description': "A Georgia Tech student welcomes the incoming freshmen with an epic speech backed by music from \"2001: A Space Odyssey.\"",
|
||||||
"upload_date": "20130821",
|
'upload_date': '20130821',
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://www.cnn.com/video/data/2.0/video/living/2014/12/22/growing-america-nashville-salemtown-board-episode-1.hln.html',
|
'url': 'http://www.cnn.com/video/data/2.0/video/living/2014/12/22/growing-america-nashville-salemtown-board-episode-1.hln.html',
|
||||||
|
@ -3,10 +3,10 @@ from __future__ import unicode_literals
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import compat_urllib_request
|
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
float_or_none,
|
float_or_none,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
|
sanitized_Request,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -46,13 +46,13 @@ class CollegeRamaIE(InfoExtractor):
|
|||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
|
|
||||||
player_options_request = {
|
player_options_request = {
|
||||||
"getPlayerOptionsRequest": {
|
'getPlayerOptionsRequest': {
|
||||||
"ResourceId": video_id,
|
'ResourceId': video_id,
|
||||||
"QueryString": "",
|
'QueryString': '',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
request = compat_urllib_request.Request(
|
request = sanitized_Request(
|
||||||
'http://collegerama.tudelft.nl/Mediasite/PlayerService/PlayerService.svc/json/GetPlayerOptions',
|
'http://collegerama.tudelft.nl/Mediasite/PlayerService/PlayerService.svc/json/GetPlayerOptions',
|
||||||
json.dumps(player_options_request))
|
json.dumps(player_options_request))
|
||||||
request.add_header('Content-Type', 'application/json')
|
request.add_header('Content-Type', 'application/json')
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
# encoding: utf-8
|
# encoding: utf-8
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import json
|
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..utils import parse_iso8601
|
from ..compat import compat_str
|
||||||
|
from ..utils import (
|
||||||
|
int_or_none,
|
||||||
|
parse_duration,
|
||||||
|
parse_iso8601,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ComCarCoffIE(InfoExtractor):
|
class ComCarCoffIE(InfoExtractor):
|
||||||
@ -12,13 +15,13 @@ class ComCarCoffIE(InfoExtractor):
|
|||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://comediansincarsgettingcoffee.com/miranda-sings-happy-thanksgiving-miranda/',
|
'url': 'http://comediansincarsgettingcoffee.com/miranda-sings-happy-thanksgiving-miranda/',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': 'miranda-sings-happy-thanksgiving-miranda',
|
'id': '2494164',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'upload_date': '20141127',
|
'upload_date': '20141127',
|
||||||
'timestamp': 1417107600,
|
'timestamp': 1417107600,
|
||||||
|
'duration': 1232,
|
||||||
'title': 'Happy Thanksgiving Miranda',
|
'title': 'Happy Thanksgiving Miranda',
|
||||||
'description': 'Jerry Seinfeld and his special guest Miranda Sings cruise around town in search of coffee, complaining and apologizing along the way.',
|
'description': 'Jerry Seinfeld and his special guest Miranda Sings cruise around town in search of coffee, complaining and apologizing along the way.',
|
||||||
'thumbnail': 'http://ccc.crackle.com/images/s5e4_thumb.jpg',
|
|
||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
'skip_download': 'requires ffmpeg',
|
'skip_download': 'requires ffmpeg',
|
||||||
@ -31,27 +34,36 @@ class ComCarCoffIE(InfoExtractor):
|
|||||||
display_id = 'comediansincarsgettingcoffee.com'
|
display_id = 'comediansincarsgettingcoffee.com'
|
||||||
webpage = self._download_webpage(url, display_id)
|
webpage = self._download_webpage(url, display_id)
|
||||||
|
|
||||||
full_data = json.loads(self._search_regex(
|
full_data = self._parse_json(
|
||||||
r'<script type="application/json" id="videoData">(?P<json>.+?)</script>',
|
self._search_regex(
|
||||||
webpage, 'full data json'))
|
r'window\.app\s*=\s*({.+?});\n', webpage, 'full data json'),
|
||||||
|
display_id)['videoData']
|
||||||
|
|
||||||
video_id = full_data['activeVideo']['video']
|
display_id = full_data['activeVideo']['video']
|
||||||
video_data = full_data.get('videos', {}).get(video_id) or full_data['singleshots'][video_id]
|
video_data = full_data.get('videos', {}).get(display_id) or full_data['singleshots'][display_id]
|
||||||
|
video_id = compat_str(video_data['mediaId'])
|
||||||
thumbnails = [{
|
thumbnails = [{
|
||||||
'url': video_data['images']['thumb'],
|
'url': video_data['images']['thumb'],
|
||||||
}, {
|
}, {
|
||||||
'url': video_data['images']['poster'],
|
'url': video_data['images']['poster'],
|
||||||
}]
|
}]
|
||||||
formats = self._extract_m3u8_formats(
|
|
||||||
video_data['mediaUrl'], video_id, ext='mp4')
|
timestamp = int_or_none(video_data.get('pubDateTime')) or parse_iso8601(
|
||||||
|
video_data.get('pubDate'))
|
||||||
|
duration = int_or_none(video_data.get('durationSeconds')) or parse_duration(
|
||||||
|
video_data.get('duration'))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
'_type': 'url_transparent',
|
||||||
|
'url': 'crackle:%s' % video_id,
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'display_id': display_id,
|
'display_id': display_id,
|
||||||
'title': video_data['title'],
|
'title': video_data['title'],
|
||||||
'description': video_data.get('description'),
|
'description': video_data.get('description'),
|
||||||
'timestamp': parse_iso8601(video_data.get('pubDate')),
|
'timestamp': timestamp,
|
||||||
|
'duration': duration,
|
||||||
'thumbnails': thumbnails,
|
'thumbnails': thumbnails,
|
||||||
'formats': formats,
|
'season_number': int_or_none(video_data.get('season')),
|
||||||
|
'episode_number': int_or_none(video_data.get('episode')),
|
||||||
'webpage_url': 'http://comediansincarsgettingcoffee.com/%s' % (video_data.get('urlSlug', video_data.get('slug'))),
|
'webpage_url': 'http://comediansincarsgettingcoffee.com/%s' % (video_data.get('urlSlug', video_data.get('slug'))),
|
||||||
}
|
}
|
||||||
|
@ -16,11 +16,11 @@ from ..utils import (
|
|||||||
|
|
||||||
class ComedyCentralIE(MTVServicesInfoExtractor):
|
class ComedyCentralIE(MTVServicesInfoExtractor):
|
||||||
_VALID_URL = r'''(?x)https?://(?:www\.)?cc\.com/
|
_VALID_URL = r'''(?x)https?://(?:www\.)?cc\.com/
|
||||||
(video-clips|episodes|cc-studios|video-collections|full-episodes)
|
(video-clips|episodes|cc-studios|video-collections|full-episodes|shows)
|
||||||
/(?P<title>.*)'''
|
/(?P<title>.*)'''
|
||||||
_FEED_URL = 'http://comedycentral.com/feeds/mrss/'
|
_FEED_URL = 'http://comedycentral.com/feeds/mrss/'
|
||||||
|
|
||||||
_TEST = {
|
_TESTS = [{
|
||||||
'url': 'http://www.cc.com/video-clips/kllhuv/stand-up-greg-fitzsimmons--uncensored---too-good-of-a-mother',
|
'url': 'http://www.cc.com/video-clips/kllhuv/stand-up-greg-fitzsimmons--uncensored---too-good-of-a-mother',
|
||||||
'md5': 'c4f48e9eda1b16dd10add0744344b6d8',
|
'md5': 'c4f48e9eda1b16dd10add0744344b6d8',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
@ -29,7 +29,10 @@ class ComedyCentralIE(MTVServicesInfoExtractor):
|
|||||||
'title': 'CC:Stand-Up|Greg Fitzsimmons: Life on Stage|Uncensored - Too Good of a Mother',
|
'title': 'CC:Stand-Up|Greg Fitzsimmons: Life on Stage|Uncensored - Too Good of a Mother',
|
||||||
'description': 'After a certain point, breastfeeding becomes c**kblocking.',
|
'description': 'After a certain point, breastfeeding becomes c**kblocking.',
|
||||||
},
|
},
|
||||||
}
|
}, {
|
||||||
|
'url': 'http://www.cc.com/shows/the-daily-show-with-trevor-noah/interviews/6yx39d/exclusive-rand-paul-extended-interview',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
|
|
||||||
class ComedyCentralShowsIE(MTVServicesInfoExtractor):
|
class ComedyCentralShowsIE(MTVServicesInfoExtractor):
|
||||||
@ -192,7 +195,7 @@ class ComedyCentralShowsIE(MTVServicesInfoExtractor):
|
|||||||
if len(altMovieParams) == 0:
|
if len(altMovieParams) == 0:
|
||||||
raise ExtractorError('unable to find Flash URL in webpage ' + url)
|
raise ExtractorError('unable to find Flash URL in webpage ' + url)
|
||||||
else:
|
else:
|
||||||
mMovieParams = [("http://media.mtvnservices.com/" + altMovieParams[0], altMovieParams[0])]
|
mMovieParams = [('http://media.mtvnservices.com/' + altMovieParams[0], altMovieParams[0])]
|
||||||
|
|
||||||
uri = mMovieParams[0][1]
|
uri = mMovieParams[0][1]
|
||||||
# Correct cc.com in uri
|
# Correct cc.com in uri
|
||||||
|
@ -10,19 +10,19 @@ 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,
|
||||||
compat_cookies,
|
compat_cookies,
|
||||||
|
compat_etree_fromstring,
|
||||||
compat_getpass,
|
compat_getpass,
|
||||||
compat_http_client,
|
compat_http_client,
|
||||||
|
compat_os_name,
|
||||||
|
compat_str,
|
||||||
compat_urllib_error,
|
compat_urllib_error,
|
||||||
compat_urllib_parse,
|
compat_urllib_parse,
|
||||||
compat_urllib_parse_urlparse,
|
|
||||||
compat_urllib_request,
|
|
||||||
compat_urlparse,
|
compat_urlparse,
|
||||||
compat_str,
|
|
||||||
compat_etree_fromstring,
|
|
||||||
)
|
)
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
NO_DEFAULT,
|
NO_DEFAULT,
|
||||||
@ -31,17 +31,24 @@ from ..utils import (
|
|||||||
clean_html,
|
clean_html,
|
||||||
compiled_regex_type,
|
compiled_regex_type,
|
||||||
determine_ext,
|
determine_ext,
|
||||||
|
error_to_compat_str,
|
||||||
ExtractorError,
|
ExtractorError,
|
||||||
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,
|
||||||
unescapeHTML,
|
unescapeHTML,
|
||||||
unified_strdate,
|
unified_strdate,
|
||||||
url_basename,
|
url_basename,
|
||||||
xpath_text,
|
xpath_text,
|
||||||
xpath_with_ns,
|
xpath_with_ns,
|
||||||
|
determine_protocol,
|
||||||
|
parse_duration,
|
||||||
|
mimetype2ext,
|
||||||
|
update_url_query,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -99,7 +106,7 @@ class InfoExtractor(object):
|
|||||||
* protocol The protocol that will be used for the actual
|
* protocol The protocol that will be used for the actual
|
||||||
download, lower-case.
|
download, lower-case.
|
||||||
"http", "https", "rtsp", "rtmp", "rtmpe",
|
"http", "https", "rtsp", "rtmp", "rtmpe",
|
||||||
"m3u8", or "m3u8_native".
|
"m3u8", "m3u8_native" or "http_dash_segments".
|
||||||
* preference Order number of this format. If this field is
|
* preference Order number of this format. If this field is
|
||||||
present and not None, the formats get sorted
|
present and not None, the formats get sorted
|
||||||
by this field, regardless of all other values.
|
by this field, regardless of all other values.
|
||||||
@ -107,8 +114,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.
|
||||||
@ -151,12 +159,14 @@ class InfoExtractor(object):
|
|||||||
thumbnail: Full URL to a video thumbnail image.
|
thumbnail: Full URL to a video thumbnail image.
|
||||||
description: Full video description.
|
description: Full video description.
|
||||||
uploader: Full name of the video uploader.
|
uploader: Full name of the video uploader.
|
||||||
|
license: License name the video is licensed under.
|
||||||
creator: The main artist who created the video.
|
creator: The main artist who created the video.
|
||||||
release_date: The date (YYYYMMDD) when the video was released.
|
release_date: The date (YYYYMMDD) when the video was released.
|
||||||
timestamp: UNIX timestamp of the moment the video became available.
|
timestamp: UNIX timestamp of the moment the video became available.
|
||||||
upload_date: Video upload date (YYYYMMDD).
|
upload_date: Video upload date (YYYYMMDD).
|
||||||
If not explicitly set, calculated from timestamp.
|
If not explicitly set, calculated from timestamp.
|
||||||
uploader_id: Nickname or id of the video uploader.
|
uploader_id: Nickname or id of the video uploader.
|
||||||
|
uploader_url: Full URL to a personal webpage of the video uploader.
|
||||||
location: Physical location where the video was filmed.
|
location: Physical location where the video was filmed.
|
||||||
subtitles: The available subtitles as a dictionary in the format
|
subtitles: The available subtitles as a dictionary in the format
|
||||||
{language: subformats}. "subformats" is a list sorted from
|
{language: subformats}. "subformats" is a list sorted from
|
||||||
@ -167,7 +177,7 @@ class InfoExtractor(object):
|
|||||||
"ext" will be calculated from URL if missing
|
"ext" will be calculated from URL if missing
|
||||||
automatic_captions: Like 'subtitles', used by the YoutubeIE for
|
automatic_captions: Like 'subtitles', used by the YoutubeIE for
|
||||||
automatically generated captions
|
automatically generated captions
|
||||||
duration: Length of the video in seconds, as an integer.
|
duration: Length of the video in seconds, as an integer or float.
|
||||||
view_count: How many users have watched the video on the platform.
|
view_count: How many users have watched the video on the platform.
|
||||||
like_count: Number of positive ratings of the video
|
like_count: Number of positive ratings of the video
|
||||||
dislike_count: Number of negative ratings of the video
|
dislike_count: Number of negative ratings of the video
|
||||||
@ -199,6 +209,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.
|
||||||
@ -291,9 +321,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."""
|
||||||
@ -316,7 +346,7 @@ class InfoExtractor(object):
|
|||||||
def IE_NAME(self):
|
def IE_NAME(self):
|
||||||
return compat_str(type(self).__name__[:-2])
|
return compat_str(type(self).__name__[:-2])
|
||||||
|
|
||||||
def _request_webpage(self, url_or_request, video_id, note=None, errnote=None, fatal=True):
|
def _request_webpage(self, url_or_request, video_id, note=None, errnote=None, fatal=True, data=None, headers=None, query=None):
|
||||||
""" Returns the response handle """
|
""" Returns the response handle """
|
||||||
if note is None:
|
if note is None:
|
||||||
self.report_download_webpage(video_id)
|
self.report_download_webpage(video_id)
|
||||||
@ -325,6 +355,12 @@ class InfoExtractor(object):
|
|||||||
self.to_screen('%s' % (note,))
|
self.to_screen('%s' % (note,))
|
||||||
else:
|
else:
|
||||||
self.to_screen('%s: %s' % (video_id, note))
|
self.to_screen('%s: %s' % (video_id, note))
|
||||||
|
# data, headers and query params will be ignored for `Request` objects
|
||||||
|
if isinstance(url_or_request, compat_str):
|
||||||
|
if query:
|
||||||
|
url_or_request = update_url_query(url_or_request, query)
|
||||||
|
if data or headers:
|
||||||
|
url_or_request = sanitized_Request(url_or_request, data, headers or {})
|
||||||
try:
|
try:
|
||||||
return self._downloader.urlopen(url_or_request)
|
return self._downloader.urlopen(url_or_request)
|
||||||
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:
|
||||||
@ -332,20 +368,21 @@ class InfoExtractor(object):
|
|||||||
return False
|
return False
|
||||||
if errnote is None:
|
if errnote is None:
|
||||||
errnote = 'Unable to download webpage'
|
errnote = 'Unable to download webpage'
|
||||||
errmsg = '%s: %s' % (errnote, compat_str(err))
|
|
||||||
|
errmsg = '%s: %s' % (errnote, error_to_compat_str(err))
|
||||||
if fatal:
|
if fatal:
|
||||||
raise ExtractorError(errmsg, sys.exc_info()[2], cause=err)
|
raise ExtractorError(errmsg, sys.exc_info()[2], cause=err)
|
||||||
else:
|
else:
|
||||||
self._downloader.report_warning(errmsg)
|
self._downloader.report_warning(errmsg)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _download_webpage_handle(self, url_or_request, video_id, note=None, errnote=None, fatal=True, encoding=None):
|
def _download_webpage_handle(self, url_or_request, video_id, note=None, errnote=None, fatal=True, encoding=None, data=None, headers=None, query=None):
|
||||||
""" Returns a tuple (page content as string, URL handle) """
|
""" Returns a tuple (page content as string, URL handle) """
|
||||||
# Strip hashes from the URL (#1038)
|
# Strip hashes from the URL (#1038)
|
||||||
if isinstance(url_or_request, (compat_str, str)):
|
if isinstance(url_or_request, (compat_str, str)):
|
||||||
url_or_request = url_or_request.partition('#')[0]
|
url_or_request = url_or_request.partition('#')[0]
|
||||||
|
|
||||||
urlh = self._request_webpage(url_or_request, video_id, note, errnote, fatal)
|
urlh = self._request_webpage(url_or_request, video_id, note, errnote, fatal, data=data, headers=headers, query=query)
|
||||||
if urlh is False:
|
if urlh is False:
|
||||||
assert not fatal
|
assert not fatal
|
||||||
return False
|
return False
|
||||||
@ -398,7 +435,7 @@ class InfoExtractor(object):
|
|||||||
self.to_screen('Saving request to ' + filename)
|
self.to_screen('Saving request to ' + filename)
|
||||||
# Working around MAX_PATH limitation on Windows (see
|
# Working around MAX_PATH limitation on Windows (see
|
||||||
# http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx)
|
# http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx)
|
||||||
if os.name == 'nt':
|
if compat_os_name == 'nt':
|
||||||
absfilepath = os.path.abspath(filename)
|
absfilepath = os.path.abspath(filename)
|
||||||
if len(absfilepath) > 259:
|
if len(absfilepath) > 259:
|
||||||
filename = '\\\\?\\' + absfilepath
|
filename = '\\\\?\\' + absfilepath
|
||||||
@ -432,13 +469,13 @@ class InfoExtractor(object):
|
|||||||
|
|
||||||
return content
|
return content
|
||||||
|
|
||||||
def _download_webpage(self, url_or_request, video_id, note=None, errnote=None, fatal=True, tries=1, timeout=5, encoding=None):
|
def _download_webpage(self, url_or_request, video_id, note=None, errnote=None, fatal=True, tries=1, timeout=5, encoding=None, data=None, headers=None, query=None):
|
||||||
""" Returns the data of the page as a string """
|
""" Returns the data of the page as a string """
|
||||||
success = False
|
success = False
|
||||||
try_count = 0
|
try_count = 0
|
||||||
while success is False:
|
while success is False:
|
||||||
try:
|
try:
|
||||||
res = self._download_webpage_handle(url_or_request, video_id, note, errnote, fatal, encoding=encoding)
|
res = self._download_webpage_handle(url_or_request, video_id, note, errnote, fatal, encoding=encoding, data=data, headers=headers, query=query)
|
||||||
success = True
|
success = True
|
||||||
except compat_http_client.IncompleteRead as e:
|
except compat_http_client.IncompleteRead as e:
|
||||||
try_count += 1
|
try_count += 1
|
||||||
@ -453,10 +490,10 @@ class InfoExtractor(object):
|
|||||||
|
|
||||||
def _download_xml(self, url_or_request, video_id,
|
def _download_xml(self, url_or_request, video_id,
|
||||||
note='Downloading XML', errnote='Unable to download XML',
|
note='Downloading XML', errnote='Unable to download XML',
|
||||||
transform_source=None, fatal=True, encoding=None):
|
transform_source=None, fatal=True, encoding=None, data=None, headers=None, query=None):
|
||||||
"""Return the xml as an xml.etree.ElementTree.Element"""
|
"""Return the xml as an xml.etree.ElementTree.Element"""
|
||||||
xml_string = self._download_webpage(
|
xml_string = self._download_webpage(
|
||||||
url_or_request, video_id, note, errnote, fatal=fatal, encoding=encoding)
|
url_or_request, video_id, note, errnote, fatal=fatal, encoding=encoding, data=data, headers=headers, query=query)
|
||||||
if xml_string is False:
|
if xml_string is False:
|
||||||
return xml_string
|
return xml_string
|
||||||
if transform_source:
|
if transform_source:
|
||||||
@ -467,10 +504,10 @@ class InfoExtractor(object):
|
|||||||
note='Downloading JSON metadata',
|
note='Downloading JSON metadata',
|
||||||
errnote='Unable to download JSON metadata',
|
errnote='Unable to download JSON metadata',
|
||||||
transform_source=None,
|
transform_source=None,
|
||||||
fatal=True, encoding=None):
|
fatal=True, encoding=None, data=None, headers=None, query=None):
|
||||||
json_string = self._download_webpage(
|
json_string = self._download_webpage(
|
||||||
url_or_request, video_id, note, errnote, fatal=fatal,
|
url_or_request, video_id, note, errnote, fatal=fatal,
|
||||||
encoding=encoding)
|
encoding=encoding, data=data, headers=headers, query=query)
|
||||||
if (not fatal) and json_string is False:
|
if (not fatal) and json_string is False:
|
||||||
return None
|
return None
|
||||||
return self._parse_json(
|
return self._parse_json(
|
||||||
@ -567,7 +604,7 @@ class InfoExtractor(object):
|
|||||||
if mobj:
|
if mobj:
|
||||||
break
|
break
|
||||||
|
|
||||||
if not self._downloader.params.get('no_color') and os.name != 'nt' and sys.stderr.isatty():
|
if not self._downloader.params.get('no_color') and compat_os_name != 'nt' and sys.stderr.isatty():
|
||||||
_name = '\033[0;34m%s\033[0m' % name
|
_name = '\033[0;34m%s\033[0m' % name
|
||||||
else:
|
else:
|
||||||
_name = name
|
_name = name
|
||||||
@ -610,7 +647,7 @@ class InfoExtractor(object):
|
|||||||
downloader_params = self._downloader.params
|
downloader_params = self._downloader.params
|
||||||
|
|
||||||
# Attempt to use provided username and password or .netrc data
|
# Attempt to use provided username and password or .netrc data
|
||||||
if downloader_params.get('username', None) is not None:
|
if downloader_params.get('username') is not None:
|
||||||
username = downloader_params['username']
|
username = downloader_params['username']
|
||||||
password = downloader_params['password']
|
password = downloader_params['password']
|
||||||
elif downloader_params.get('usenetrc', False):
|
elif downloader_params.get('usenetrc', False):
|
||||||
@ -622,7 +659,7 @@ class InfoExtractor(object):
|
|||||||
else:
|
else:
|
||||||
raise netrc.NetrcParseError('No authenticators for %s' % self._NETRC_MACHINE)
|
raise netrc.NetrcParseError('No authenticators for %s' % self._NETRC_MACHINE)
|
||||||
except (IOError, netrc.NetrcParseError) as err:
|
except (IOError, netrc.NetrcParseError) as err:
|
||||||
self._downloader.report_warning('parsing .netrc: %s' % compat_str(err))
|
self._downloader.report_warning('parsing .netrc: %s' % error_to_compat_str(err))
|
||||||
|
|
||||||
return (username, password)
|
return (username, password)
|
||||||
|
|
||||||
@ -637,7 +674,7 @@ class InfoExtractor(object):
|
|||||||
return None
|
return None
|
||||||
downloader_params = self._downloader.params
|
downloader_params = self._downloader.params
|
||||||
|
|
||||||
if downloader_params.get('twofactor', None) is not None:
|
if downloader_params.get('twofactor') is not None:
|
||||||
return downloader_params['twofactor']
|
return downloader_params['twofactor']
|
||||||
|
|
||||||
return compat_getpass('Type %s and press [Return]: ' % note)
|
return compat_getpass('Type %s and press [Return]: ' % note)
|
||||||
@ -718,7 +755,7 @@ class InfoExtractor(object):
|
|||||||
'mature': 17,
|
'mature': 17,
|
||||||
'restricted': 19,
|
'restricted': 19,
|
||||||
}
|
}
|
||||||
return RATING_TABLE.get(rating.lower(), None)
|
return RATING_TABLE.get(rating.lower())
|
||||||
|
|
||||||
def _family_friendly_search(self, html):
|
def _family_friendly_search(self, html):
|
||||||
# See http://schema.org/VideoObject
|
# See http://schema.org/VideoObject
|
||||||
@ -733,12 +770,48 @@ class InfoExtractor(object):
|
|||||||
'0': 18,
|
'0': 18,
|
||||||
'false': 18,
|
'false': 18,
|
||||||
}
|
}
|
||||||
return RATING_TABLE.get(family_friendly.lower(), None)
|
return RATING_TABLE.get(family_friendly.lower())
|
||||||
|
|
||||||
def _twitter_search_player(self, html):
|
def _twitter_search_player(self, html):
|
||||||
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)
|
||||||
@ -765,6 +838,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
|
||||||
@ -776,15 +855,14 @@ class InfoExtractor(object):
|
|||||||
|
|
||||||
preference = f.get('preference')
|
preference = f.get('preference')
|
||||||
if preference is None:
|
if preference is None:
|
||||||
proto = f.get('protocol')
|
preference = 0
|
||||||
if proto is None:
|
|
||||||
proto = compat_urllib_parse_urlparse(f.get('url', '')).scheme
|
|
||||||
|
|
||||||
preference = 0 if proto in ['http', 'https'] else -0.1
|
|
||||||
if f.get('ext') in ['f4f', 'f4m']: # Not yet supported
|
if f.get('ext') in ['f4f', 'f4m']: # Not yet supported
|
||||||
preference -= 0.5
|
preference -= 0.5
|
||||||
|
|
||||||
|
proto_preference = 0 if determine_protocol(f) in ['http', 'https'] else -0.1
|
||||||
|
|
||||||
if f.get('vcodec') == 'none': # audio only
|
if f.get('vcodec') == 'none': # audio only
|
||||||
|
preference -= 50
|
||||||
if self._downloader.params.get('prefer_free_formats'):
|
if self._downloader.params.get('prefer_free_formats'):
|
||||||
ORDER = ['aac', 'mp3', 'm4a', 'webm', 'ogg', 'opus']
|
ORDER = ['aac', 'mp3', 'm4a', 'webm', 'ogg', 'opus']
|
||||||
else:
|
else:
|
||||||
@ -795,6 +873,8 @@ class InfoExtractor(object):
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
audio_ext_preference = -1
|
audio_ext_preference = -1
|
||||||
else:
|
else:
|
||||||
|
if f.get('acodec') == 'none': # video only
|
||||||
|
preference -= 40
|
||||||
if self._downloader.params.get('prefer_free_formats'):
|
if self._downloader.params.get('prefer_free_formats'):
|
||||||
ORDER = ['flv', 'mp4', 'webm']
|
ORDER = ['flv', 'mp4', 'webm']
|
||||||
else:
|
else:
|
||||||
@ -814,6 +894,7 @@ class InfoExtractor(object):
|
|||||||
f.get('vbr') if f.get('vbr') is not None else -1,
|
f.get('vbr') if f.get('vbr') is not None else -1,
|
||||||
f.get('height') if f.get('height') is not None else -1,
|
f.get('height') if f.get('height') is not None else -1,
|
||||||
f.get('width') if f.get('width') is not None else -1,
|
f.get('width') if f.get('width') is not None else -1,
|
||||||
|
proto_preference,
|
||||||
ext_preference,
|
ext_preference,
|
||||||
f.get('abr') if f.get('abr') is not None else -1,
|
f.get('abr') if f.get('abr') is not None else -1,
|
||||||
audio_ext_preference,
|
audio_ext_preference,
|
||||||
@ -832,6 +913,16 @@ class InfoExtractor(object):
|
|||||||
item='%s video format' % f.get('format_id') if f.get('format_id') else 'video'),
|
item='%s video format' % f.get('format_id') if f.get('format_id') else 'video'),
|
||||||
formats)
|
formats)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _remove_duplicate_formats(formats):
|
||||||
|
format_urls = set()
|
||||||
|
unique_formats = []
|
||||||
|
for f in formats:
|
||||||
|
if f['url'] not in format_urls:
|
||||||
|
format_urls.add(f['url'])
|
||||||
|
unique_formats.append(f)
|
||||||
|
formats[:] = unique_formats
|
||||||
|
|
||||||
def _is_valid_url(self, url, video_id, item='video'):
|
def _is_valid_url(self, url, video_id, item='video'):
|
||||||
url = self._proto_relative_url(url, scheme='http:')
|
url = self._proto_relative_url(url, scheme='http:')
|
||||||
# For now assume non HTTP(S) URLs always valid
|
# For now assume non HTTP(S) URLs always valid
|
||||||
@ -883,14 +974,26 @@ class InfoExtractor(object):
|
|||||||
fatal=fatal)
|
fatal=fatal)
|
||||||
|
|
||||||
if manifest is False:
|
if manifest is False:
|
||||||
return manifest
|
return []
|
||||||
|
|
||||||
|
return self._parse_f4m_formats(
|
||||||
|
manifest, manifest_url, video_id, preference=preference, f4m_id=f4m_id,
|
||||||
|
transform_source=transform_source, fatal=fatal)
|
||||||
|
|
||||||
|
def _parse_f4m_formats(self, manifest, manifest_url, video_id, preference=None, f4m_id=None,
|
||||||
|
transform_source=lambda s: fix_xml_ampersands(s).strip(),
|
||||||
|
fatal=True):
|
||||||
formats = []
|
formats = []
|
||||||
manifest_version = '1.0'
|
manifest_version = '1.0'
|
||||||
media_nodes = manifest.findall('{http://ns.adobe.com/f4m/1.0}media')
|
media_nodes = manifest.findall('{http://ns.adobe.com/f4m/1.0}media')
|
||||||
if not media_nodes:
|
if not media_nodes:
|
||||||
manifest_version = '2.0'
|
manifest_version = '2.0'
|
||||||
media_nodes = manifest.findall('{http://ns.adobe.com/f4m/2.0}media')
|
media_nodes = manifest.findall('{http://ns.adobe.com/f4m/2.0}media')
|
||||||
|
base_url = xpath_text(
|
||||||
|
manifest, ['{http://ns.adobe.com/f4m/1.0}baseURL', '{http://ns.adobe.com/f4m/2.0}baseURL'],
|
||||||
|
'base URL', default=None)
|
||||||
|
if base_url:
|
||||||
|
base_url = base_url.strip()
|
||||||
for i, media_el in enumerate(media_nodes):
|
for i, media_el in enumerate(media_nodes):
|
||||||
if manifest_version == '2.0':
|
if manifest_version == '2.0':
|
||||||
media_url = media_el.attrib.get('href') or media_el.attrib.get('url')
|
media_url = media_el.attrib.get('href') or media_el.attrib.get('url')
|
||||||
@ -898,16 +1001,15 @@ class InfoExtractor(object):
|
|||||||
continue
|
continue
|
||||||
manifest_url = (
|
manifest_url = (
|
||||||
media_url if media_url.startswith('http://') or media_url.startswith('https://')
|
media_url if media_url.startswith('http://') or media_url.startswith('https://')
|
||||||
else ('/'.join(manifest_url.split('/')[:-1]) + '/' + media_url))
|
else ((base_url or '/'.join(manifest_url.split('/')[:-1])) + '/' + media_url))
|
||||||
# If media_url is itself a f4m manifest do the recursive extraction
|
# If media_url is itself a f4m manifest do the recursive extraction
|
||||||
# since bitrates in parent manifest (this one) and media_url manifest
|
# since bitrates in parent manifest (this one) and media_url manifest
|
||||||
# may differ leading to inability to resolve the format by requested
|
# may differ leading to inability to resolve the format by requested
|
||||||
# bitrate in f4m downloader
|
# bitrate in f4m downloader
|
||||||
if determine_ext(manifest_url) == 'f4m':
|
if determine_ext(manifest_url) == 'f4m':
|
||||||
f4m_formats = self._extract_f4m_formats(
|
formats.extend(self._extract_f4m_formats(
|
||||||
manifest_url, video_id, preference, f4m_id, fatal=fatal)
|
manifest_url, video_id, preference=preference, f4m_id=f4m_id,
|
||||||
if f4m_formats:
|
transform_source=transform_source, fatal=fatal))
|
||||||
formats.extend(f4m_formats)
|
|
||||||
continue
|
continue
|
||||||
tbr = int_or_none(media_el.attrib.get('bitrate'))
|
tbr = int_or_none(media_el.attrib.get('bitrate'))
|
||||||
formats.append({
|
formats.append({
|
||||||
@ -949,9 +1051,31 @@ class InfoExtractor(object):
|
|||||||
errnote=errnote or 'Failed to download m3u8 information',
|
errnote=errnote or 'Failed to download m3u8 information',
|
||||||
fatal=fatal)
|
fatal=fatal)
|
||||||
if res is False:
|
if res is False:
|
||||||
return res
|
return []
|
||||||
m3u8_doc, urlh = res
|
m3u8_doc, urlh = res
|
||||||
m3u8_url = urlh.geturl()
|
m3u8_url = urlh.geturl()
|
||||||
|
|
||||||
|
# We should try extracting formats only from master playlists [1], i.e.
|
||||||
|
# playlists that describe available qualities. On the other hand media
|
||||||
|
# playlists [2] should be returned as is since they contain just the media
|
||||||
|
# without qualities renditions.
|
||||||
|
# Fortunately, master playlist can be easily distinguished from media
|
||||||
|
# playlist based on particular tags availability. As of [1, 2] master
|
||||||
|
# playlist tags MUST NOT appear in a media playist and vice versa.
|
||||||
|
# As of [3] #EXT-X-TARGETDURATION tag is REQUIRED for every media playlist
|
||||||
|
# and MUST NOT appear in master playlist thus we can clearly detect media
|
||||||
|
# playlist with this criterion.
|
||||||
|
# 1. https://tools.ietf.org/html/draft-pantos-http-live-streaming-17#section-4.3.4
|
||||||
|
# 2. https://tools.ietf.org/html/draft-pantos-http-live-streaming-17#section-4.3.3
|
||||||
|
# 3. https://tools.ietf.org/html/draft-pantos-http-live-streaming-17#section-4.3.3.1
|
||||||
|
if '#EXT-X-TARGETDURATION' in m3u8_doc: # media playlist, return as is
|
||||||
|
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(
|
||||||
@ -991,19 +1115,29 @@ class InfoExtractor(object):
|
|||||||
'protocol': entry_protocol,
|
'protocol': entry_protocol,
|
||||||
'preference': preference,
|
'preference': preference,
|
||||||
}
|
}
|
||||||
codecs = last_info.get('CODECS')
|
|
||||||
if codecs:
|
|
||||||
# TODO: looks like video codec is not always necessarily goes first
|
|
||||||
va_codecs = codecs.split(',')
|
|
||||||
if va_codecs[0]:
|
|
||||||
f['vcodec'] = va_codecs[0].partition('.')[0]
|
|
||||||
if len(va_codecs) > 1 and va_codecs[1]:
|
|
||||||
f['acodec'] = va_codecs[1].partition('.')[0]
|
|
||||||
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')
|
||||||
f['width'] = int(width_str)
|
f['width'] = int(width_str)
|
||||||
f['height'] = int(height_str)
|
f['height'] = int(height_str)
|
||||||
|
codecs = last_info.get('CODECS')
|
||||||
|
if codecs:
|
||||||
|
vcodec, acodec = [None] * 2
|
||||||
|
va_codecs = codecs.split(',')
|
||||||
|
if len(va_codecs) == 1:
|
||||||
|
# Audio only entries usually come with single codec and
|
||||||
|
# no resolution. For more robustness we also check it to
|
||||||
|
# be mp4 audio.
|
||||||
|
if not resolution and va_codecs[0].startswith('mp4a'):
|
||||||
|
vcodec, acodec = 'none', va_codecs[0]
|
||||||
|
else:
|
||||||
|
vcodec = va_codecs[0]
|
||||||
|
else:
|
||||||
|
vcodec, acodec = va_codecs[:2]
|
||||||
|
f.update({
|
||||||
|
'acodec': acodec,
|
||||||
|
'vcodec': vcodec,
|
||||||
|
})
|
||||||
if last_media is not None:
|
if last_media is not None:
|
||||||
f['m3u8_media'] = last_media
|
f['m3u8_media'] = last_media
|
||||||
last_media = None
|
last_media = None
|
||||||
@ -1024,8 +1158,8 @@ class InfoExtractor(object):
|
|||||||
out.append('{%s}%s' % (namespace, c))
|
out.append('{%s}%s' % (namespace, c))
|
||||||
return '/'.join(out)
|
return '/'.join(out)
|
||||||
|
|
||||||
def _extract_smil_formats(self, smil_url, video_id, fatal=True, f4m_params=None):
|
def _extract_smil_formats(self, smil_url, video_id, fatal=True, f4m_params=None, transform_source=None):
|
||||||
smil = self._download_smil(smil_url, video_id, fatal=fatal)
|
smil = self._download_smil(smil_url, video_id, fatal=fatal, transform_source=transform_source)
|
||||||
|
|
||||||
if smil is False:
|
if smil is False:
|
||||||
assert not fatal
|
assert not fatal
|
||||||
@ -1042,10 +1176,10 @@ class InfoExtractor(object):
|
|||||||
return {}
|
return {}
|
||||||
return self._parse_smil(smil, smil_url, video_id, f4m_params=f4m_params)
|
return self._parse_smil(smil, smil_url, video_id, f4m_params=f4m_params)
|
||||||
|
|
||||||
def _download_smil(self, smil_url, video_id, fatal=True):
|
def _download_smil(self, smil_url, video_id, fatal=True, transform_source=None):
|
||||||
return self._download_xml(
|
return self._download_xml(
|
||||||
smil_url, video_id, 'Downloading SMIL file',
|
smil_url, video_id, 'Downloading SMIL file',
|
||||||
'Unable to download SMIL file', fatal=fatal)
|
'Unable to download SMIL file', fatal=fatal, transform_source=transform_source)
|
||||||
|
|
||||||
def _parse_smil(self, smil, smil_url, video_id, f4m_params=None):
|
def _parse_smil(self, smil, smil_url, video_id, f4m_params=None):
|
||||||
namespace = self._parse_smil_namespace(smil)
|
namespace = self._parse_smil_namespace(smil)
|
||||||
@ -1102,12 +1236,15 @@ class InfoExtractor(object):
|
|||||||
formats = []
|
formats = []
|
||||||
rtmp_count = 0
|
rtmp_count = 0
|
||||||
http_count = 0
|
http_count = 0
|
||||||
|
m3u8_count = 0
|
||||||
|
|
||||||
|
srcs = []
|
||||||
videos = smil.findall(self._xpath_ns('.//video', namespace))
|
videos = smil.findall(self._xpath_ns('.//video', namespace))
|
||||||
for video in videos:
|
for video in videos:
|
||||||
src = video.get('src')
|
src = video.get('src')
|
||||||
if not src:
|
if not src or src in srcs:
|
||||||
continue
|
continue
|
||||||
|
srcs.append(src)
|
||||||
|
|
||||||
bitrate = float_or_none(video.get('system-bitrate') or video.get('systemBitrate'), 1000)
|
bitrate = float_or_none(video.get('system-bitrate') or video.get('systemBitrate'), 1000)
|
||||||
filesize = int_or_none(video.get('size') or video.get('fileSize'))
|
filesize = int_or_none(video.get('size') or video.get('fileSize'))
|
||||||
@ -1139,11 +1276,19 @@ class InfoExtractor(object):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
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)
|
||||||
|
src_url = src_url.strip()
|
||||||
|
|
||||||
if proto == 'm3u8' or src_ext == 'm3u8':
|
if proto == 'm3u8' or src_ext == 'm3u8':
|
||||||
m3u8_formats = 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 m3u8_formats:
|
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)
|
formats.extend(m3u8_formats)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -1156,9 +1301,7 @@ class InfoExtractor(object):
|
|||||||
}
|
}
|
||||||
f4m_url += '&' if '?' in f4m_url else '?'
|
f4m_url += '&' if '?' in f4m_url else '?'
|
||||||
f4m_url += compat_urllib_parse.urlencode(f4m_params)
|
f4m_url += compat_urllib_parse.urlencode(f4m_params)
|
||||||
f4m_formats = self._extract_f4m_formats(f4m_url, video_id, f4m_id='hds', fatal=False)
|
formats.extend(self._extract_f4m_formats(f4m_url, video_id, f4m_id='hds', fatal=False))
|
||||||
if f4m_formats:
|
|
||||||
formats.extend(f4m_formats)
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if src_url.startswith('http') and self._is_valid_url(src, video_id):
|
if src_url.startswith('http') and self._is_valid_url(src, video_id):
|
||||||
@ -1179,21 +1322,14 @@ class InfoExtractor(object):
|
|||||||
return formats
|
return formats
|
||||||
|
|
||||||
def _parse_smil_subtitles(self, smil, namespace=None, subtitles_lang='en'):
|
def _parse_smil_subtitles(self, smil, namespace=None, subtitles_lang='en'):
|
||||||
|
urls = []
|
||||||
subtitles = {}
|
subtitles = {}
|
||||||
for num, textstream in enumerate(smil.findall(self._xpath_ns('.//textstream', namespace))):
|
for num, textstream in enumerate(smil.findall(self._xpath_ns('.//textstream', namespace))):
|
||||||
src = textstream.get('src')
|
src = textstream.get('src')
|
||||||
if not src:
|
if not src or src in urls:
|
||||||
continue
|
continue
|
||||||
ext = textstream.get('ext') or determine_ext(src)
|
urls.append(src)
|
||||||
if not ext:
|
ext = textstream.get('ext') or determine_ext(src) or mimetype2ext(textstream.get('type'))
|
||||||
type_ = textstream.get('type')
|
|
||||||
SUBTITLES_TYPES = {
|
|
||||||
'text/vtt': 'vtt',
|
|
||||||
'text/srt': 'srt',
|
|
||||||
'application/smptett+xml': 'tt',
|
|
||||||
}
|
|
||||||
if type_ in SUBTITLES_TYPES:
|
|
||||||
ext = SUBTITLES_TYPES[type_]
|
|
||||||
lang = textstream.get('systemLanguage') or textstream.get('systemLanguageName') or textstream.get('lang') or subtitles_lang
|
lang = textstream.get('systemLanguage') or textstream.get('systemLanguageName') or textstream.get('lang') or subtitles_lang
|
||||||
subtitles.setdefault(lang, []).append({
|
subtitles.setdefault(lang, []).append({
|
||||||
'url': src,
|
'url': src,
|
||||||
@ -1244,10 +1380,169 @@ 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)
|
||||||
|
# According to page 41 of ISO/IEC 29001-1:2014, @mimeType is mandatory
|
||||||
|
mime_type = representation_attrib['mimeType']
|
||||||
|
content_type = mime_type.split('/')[0]
|
||||||
|
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 mpd_base_url and not re.match(r'^https?://', base_url):
|
||||||
|
if not mpd_base_url.endswith('/') and not base_url.startswith('/'):
|
||||||
|
mpd_base_url += '/'
|
||||||
|
base_url = mpd_base_url + base_url
|
||||||
|
representation_id = representation_attrib.get('id')
|
||||||
|
lang = representation_attrib.get('lang')
|
||||||
|
url_el = representation.find(_add_ns('BaseURL'))
|
||||||
|
filesize = int_or_none(url_el.attrib.get('{http://youtube.com/yt/2012/10/10}contentLength') if url_el is not None else None)
|
||||||
|
f = {
|
||||||
|
'format_id': '%s-%s' % (mpd_id, representation_id) if mpd_id else representation_id,
|
||||||
|
'url': base_url,
|
||||||
|
'ext': mimetype2ext(mime_type),
|
||||||
|
'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,
|
||||||
|
'filesize': filesize,
|
||||||
|
}
|
||||||
|
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()
|
||||||
now_str = now.strftime("%Y-%m-%d %H:%M")
|
now_str = now.strftime('%Y-%m-%d %H:%M')
|
||||||
return name + ' ' + now_str
|
return name + ' ' + now_str
|
||||||
|
|
||||||
def _int(self, v, name, fatal=False, **kwargs):
|
def _int(self, v, name, fatal=False, **kwargs):
|
||||||
@ -1280,7 +1575,7 @@ class InfoExtractor(object):
|
|||||||
|
|
||||||
def _get_cookies(self, url):
|
def _get_cookies(self, url):
|
||||||
""" Return a compat_cookies.SimpleCookie with the cookies for the url """
|
""" Return a compat_cookies.SimpleCookie with the cookies for the url """
|
||||||
req = compat_urllib_request.Request(url)
|
req = sanitized_Request(url)
|
||||||
self._downloader.cookiejar.add_cookie_header(req)
|
self._downloader.cookiejar.add_cookie_header(req)
|
||||||
return compat_cookies.SimpleCookie(req.get_header('Cookie'))
|
return compat_cookies.SimpleCookie(req.get_header('Cookie'))
|
||||||
|
|
||||||
@ -1320,7 +1615,7 @@ class InfoExtractor(object):
|
|||||||
return {}
|
return {}
|
||||||
|
|
||||||
def _get_subtitles(self, *args, **kwargs):
|
def _get_subtitles(self, *args, **kwargs):
|
||||||
raise NotImplementedError("This method must be implemented by subclasses")
|
raise NotImplementedError('This method must be implemented by subclasses')
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _merge_subtitle_items(subtitle_list1, subtitle_list2):
|
def _merge_subtitle_items(subtitle_list1, subtitle_list2):
|
||||||
@ -1346,7 +1641,16 @@ class InfoExtractor(object):
|
|||||||
return {}
|
return {}
|
||||||
|
|
||||||
def _get_automatic_captions(self, *args, **kwargs):
|
def _get_automatic_captions(self, *args, **kwargs):
|
||||||
raise NotImplementedError("This method must be implemented by subclasses")
|
raise NotImplementedError('This method must be implemented by subclasses')
|
||||||
|
|
||||||
|
def mark_watched(self, *args, **kwargs):
|
||||||
|
if (self._downloader.params.get('mark_watched', False) and
|
||||||
|
(self._get_login_info()[0] is not None or
|
||||||
|
self._downloader.params.get('cookiefile') is not None)):
|
||||||
|
self._mark_watched(*args, **kwargs)
|
||||||
|
|
||||||
|
def _mark_watched(self, *args, **kwargs):
|
||||||
|
raise NotImplementedError('This method must be implemented by subclasses')
|
||||||
|
|
||||||
|
|
||||||
class SearchInfoExtractor(InfoExtractor):
|
class SearchInfoExtractor(InfoExtractor):
|
||||||
@ -1386,7 +1690,7 @@ class SearchInfoExtractor(InfoExtractor):
|
|||||||
|
|
||||||
def _get_n_results(self, query, n):
|
def _get_n_results(self, query, n):
|
||||||
"""Get a specified number of results for a query"""
|
"""Get a specified number of results for a query"""
|
||||||
raise NotImplementedError("This method must be implemented by subclasses")
|
raise NotImplementedError('This method must be implemented by subclasses')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def SEARCH_KEY(self):
|
def SEARCH_KEY(self):
|
||||||
|
95
youtube_dl/extractor/crackle.py
Normal file
95
youtube_dl/extractor/crackle.py
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import int_or_none
|
||||||
|
|
||||||
|
|
||||||
|
class CrackleIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'(?:crackle:|https?://(?:www\.)?crackle\.com/(?:playlist/\d+/|(?:[^/]+/)+))(?P<id>\d+)'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://www.crackle.com/the-art-of-more/2496419',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '2496419',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Heavy Lies the Head',
|
||||||
|
'description': 'md5:bb56aa0708fe7b9a4861535f15c3abca',
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
# m3u8 download
|
||||||
|
'skip_download': True,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# extracted from http://legacyweb-us.crackle.com/flash/QueryReferrer.ashx
|
||||||
|
_SUBTITLE_SERVER = 'http://web-us-az.crackle.com'
|
||||||
|
_UPLYNK_OWNER_ID = 'e8773f7770a44dbd886eee4fca16a66b'
|
||||||
|
_THUMBNAIL_TEMPLATE = 'http://images-us-am.crackle.com/%stnl_1920x1080.jpg?ts=20140107233116?c=635333335057637614'
|
||||||
|
|
||||||
|
# extracted from http://legacyweb-us.crackle.com/flash/ReferrerRedirect.ashx
|
||||||
|
_MEDIA_FILE_SLOTS = {
|
||||||
|
'c544.flv': {
|
||||||
|
'width': 544,
|
||||||
|
'height': 306,
|
||||||
|
},
|
||||||
|
'360p.mp4': {
|
||||||
|
'width': 640,
|
||||||
|
'height': 360,
|
||||||
|
},
|
||||||
|
'480p.mp4': {
|
||||||
|
'width': 852,
|
||||||
|
'height': 478,
|
||||||
|
},
|
||||||
|
'480p_1mbps.mp4': {
|
||||||
|
'width': 852,
|
||||||
|
'height': 478,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
video_id = self._match_id(url)
|
||||||
|
item = self._download_xml(
|
||||||
|
'http://legacyweb-us.crackle.com/app/revamp/vidwallcache.aspx?flags=-1&fm=%s' % video_id,
|
||||||
|
video_id).find('i')
|
||||||
|
title = item.attrib['t']
|
||||||
|
|
||||||
|
thumbnail = None
|
||||||
|
subtitles = {}
|
||||||
|
formats = self._extract_m3u8_formats(
|
||||||
|
'http://content.uplynk.com/ext/%s/%s.m3u8' % (self._UPLYNK_OWNER_ID, video_id),
|
||||||
|
video_id, 'mp4', m3u8_id='hls', fatal=None)
|
||||||
|
path = item.attrib.get('p')
|
||||||
|
if path:
|
||||||
|
thumbnail = self._THUMBNAIL_TEMPLATE % path
|
||||||
|
http_base_url = 'http://ahttp.crackle.com/' + path
|
||||||
|
for mfs_path, mfs_info in self._MEDIA_FILE_SLOTS.items():
|
||||||
|
formats.append({
|
||||||
|
'url': http_base_url + mfs_path,
|
||||||
|
'format_id': 'http-' + mfs_path.split('.')[0],
|
||||||
|
'width': mfs_info['width'],
|
||||||
|
'height': mfs_info['height'],
|
||||||
|
})
|
||||||
|
for cc in item.findall('cc'):
|
||||||
|
locale = cc.attrib.get('l')
|
||||||
|
v = cc.attrib.get('v')
|
||||||
|
if locale and v:
|
||||||
|
if locale not in subtitles:
|
||||||
|
subtitles[locale] = []
|
||||||
|
subtitles[locale] = [{
|
||||||
|
'url': '%s/%s%s_%s.xml' % (self._SUBTITLE_SERVER, path, locale, v),
|
||||||
|
'ext': 'ttml',
|
||||||
|
}]
|
||||||
|
self._sort_formats(formats, ('width', 'height', 'tbr', 'format_id'))
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'description': item.attrib.get('d'),
|
||||||
|
'duration': int(item.attrib.get('r'), 16) if item.attrib.get('r') else None,
|
||||||
|
'series': item.attrib.get('sn'),
|
||||||
|
'season_number': int_or_none(item.attrib.get('se')),
|
||||||
|
'episode_number': int_or_none(item.attrib.get('ep')),
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'subtitles': subtitles,
|
||||||
|
'formats': formats,
|
||||||
|
}
|
@ -23,6 +23,7 @@ from ..utils import (
|
|||||||
int_or_none,
|
int_or_none,
|
||||||
lowercase_escape,
|
lowercase_escape,
|
||||||
remove_end,
|
remove_end,
|
||||||
|
sanitized_Request,
|
||||||
unified_strdate,
|
unified_strdate,
|
||||||
urlencode_postdata,
|
urlencode_postdata,
|
||||||
xpath_text,
|
xpath_text,
|
||||||
@ -46,7 +47,7 @@ class CrunchyrollBaseIE(InfoExtractor):
|
|||||||
'name': username,
|
'name': username,
|
||||||
'password': password,
|
'password': password,
|
||||||
})
|
})
|
||||||
login_request = compat_urllib_request.Request(login_url, data)
|
login_request = sanitized_Request(login_url, data)
|
||||||
login_request.add_header('Content-Type', 'application/x-www-form-urlencoded')
|
login_request.add_header('Content-Type', 'application/x-www-form-urlencoded')
|
||||||
self._download_webpage(login_request, None, False, 'Wrong login info')
|
self._download_webpage(login_request, None, False, 'Wrong login info')
|
||||||
|
|
||||||
@ -55,7 +56,7 @@ class CrunchyrollBaseIE(InfoExtractor):
|
|||||||
|
|
||||||
def _download_webpage(self, url_or_request, video_id, note=None, errnote=None, fatal=True, tries=1, timeout=5, encoding=None):
|
def _download_webpage(self, url_or_request, video_id, note=None, errnote=None, fatal=True, tries=1, timeout=5, encoding=None):
|
||||||
request = (url_or_request if isinstance(url_or_request, compat_urllib_request.Request)
|
request = (url_or_request if isinstance(url_or_request, compat_urllib_request.Request)
|
||||||
else compat_urllib_request.Request(url_or_request))
|
else sanitized_Request(url_or_request))
|
||||||
# Accept-Language must be set explicitly to accept any language to avoid issues
|
# Accept-Language must be set explicitly to accept any language to avoid issues
|
||||||
# similar to https://github.com/rg3/youtube-dl/issues/6797.
|
# similar to https://github.com/rg3/youtube-dl/issues/6797.
|
||||||
# Along with IP address Crunchyroll uses Accept-Language to guess whether georestriction
|
# Along with IP address Crunchyroll uses Accept-Language to guess whether georestriction
|
||||||
@ -179,40 +180,40 @@ class CrunchyrollIE(CrunchyrollBaseIE):
|
|||||||
return assvalue
|
return assvalue
|
||||||
|
|
||||||
output = '[Script Info]\n'
|
output = '[Script Info]\n'
|
||||||
output += 'Title: %s\n' % sub_root.attrib["title"]
|
output += 'Title: %s\n' % sub_root.attrib['title']
|
||||||
output += 'ScriptType: v4.00+\n'
|
output += 'ScriptType: v4.00+\n'
|
||||||
output += 'WrapStyle: %s\n' % sub_root.attrib["wrap_style"]
|
output += 'WrapStyle: %s\n' % sub_root.attrib['wrap_style']
|
||||||
output += 'PlayResX: %s\n' % sub_root.attrib["play_res_x"]
|
output += 'PlayResX: %s\n' % sub_root.attrib['play_res_x']
|
||||||
output += 'PlayResY: %s\n' % sub_root.attrib["play_res_y"]
|
output += 'PlayResY: %s\n' % sub_root.attrib['play_res_y']
|
||||||
output += """ScaledBorderAndShadow: yes
|
output += """ScaledBorderAndShadow: yes
|
||||||
|
|
||||||
[V4+ Styles]
|
[V4+ Styles]
|
||||||
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
|
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
|
||||||
"""
|
"""
|
||||||
for style in sub_root.findall('./styles/style'):
|
for style in sub_root.findall('./styles/style'):
|
||||||
output += 'Style: ' + style.attrib["name"]
|
output += 'Style: ' + style.attrib['name']
|
||||||
output += ',' + style.attrib["font_name"]
|
output += ',' + style.attrib['font_name']
|
||||||
output += ',' + style.attrib["font_size"]
|
output += ',' + style.attrib['font_size']
|
||||||
output += ',' + style.attrib["primary_colour"]
|
output += ',' + style.attrib['primary_colour']
|
||||||
output += ',' + style.attrib["secondary_colour"]
|
output += ',' + style.attrib['secondary_colour']
|
||||||
output += ',' + style.attrib["outline_colour"]
|
output += ',' + style.attrib['outline_colour']
|
||||||
output += ',' + style.attrib["back_colour"]
|
output += ',' + style.attrib['back_colour']
|
||||||
output += ',' + ass_bool(style.attrib["bold"])
|
output += ',' + ass_bool(style.attrib['bold'])
|
||||||
output += ',' + ass_bool(style.attrib["italic"])
|
output += ',' + ass_bool(style.attrib['italic'])
|
||||||
output += ',' + ass_bool(style.attrib["underline"])
|
output += ',' + ass_bool(style.attrib['underline'])
|
||||||
output += ',' + ass_bool(style.attrib["strikeout"])
|
output += ',' + ass_bool(style.attrib['strikeout'])
|
||||||
output += ',' + style.attrib["scale_x"]
|
output += ',' + style.attrib['scale_x']
|
||||||
output += ',' + style.attrib["scale_y"]
|
output += ',' + style.attrib['scale_y']
|
||||||
output += ',' + style.attrib["spacing"]
|
output += ',' + style.attrib['spacing']
|
||||||
output += ',' + style.attrib["angle"]
|
output += ',' + style.attrib['angle']
|
||||||
output += ',' + style.attrib["border_style"]
|
output += ',' + style.attrib['border_style']
|
||||||
output += ',' + style.attrib["outline"]
|
output += ',' + style.attrib['outline']
|
||||||
output += ',' + style.attrib["shadow"]
|
output += ',' + style.attrib['shadow']
|
||||||
output += ',' + style.attrib["alignment"]
|
output += ',' + style.attrib['alignment']
|
||||||
output += ',' + style.attrib["margin_l"]
|
output += ',' + style.attrib['margin_l']
|
||||||
output += ',' + style.attrib["margin_r"]
|
output += ',' + style.attrib['margin_r']
|
||||||
output += ',' + style.attrib["margin_v"]
|
output += ',' + style.attrib['margin_v']
|
||||||
output += ',' + style.attrib["encoding"]
|
output += ',' + style.attrib['encoding']
|
||||||
output += '\n'
|
output += '\n'
|
||||||
|
|
||||||
output += """
|
output += """
|
||||||
@ -221,15 +222,15 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
|||||||
"""
|
"""
|
||||||
for event in sub_root.findall('./events/event'):
|
for event in sub_root.findall('./events/event'):
|
||||||
output += 'Dialogue: 0'
|
output += 'Dialogue: 0'
|
||||||
output += ',' + event.attrib["start"]
|
output += ',' + event.attrib['start']
|
||||||
output += ',' + event.attrib["end"]
|
output += ',' + event.attrib['end']
|
||||||
output += ',' + event.attrib["style"]
|
output += ',' + event.attrib['style']
|
||||||
output += ',' + event.attrib["name"]
|
output += ',' + event.attrib['name']
|
||||||
output += ',' + event.attrib["margin_l"]
|
output += ',' + event.attrib['margin_l']
|
||||||
output += ',' + event.attrib["margin_r"]
|
output += ',' + event.attrib['margin_r']
|
||||||
output += ',' + event.attrib["margin_v"]
|
output += ',' + event.attrib['margin_v']
|
||||||
output += ',' + event.attrib["effect"]
|
output += ',' + event.attrib['effect']
|
||||||
output += ',' + event.attrib["text"]
|
output += ',' + event.attrib['text']
|
||||||
output += '\n'
|
output += '\n'
|
||||||
|
|
||||||
return output
|
return output
|
||||||
@ -307,7 +308,7 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
|||||||
'video_uploader', fatal=False)
|
'video_uploader', fatal=False)
|
||||||
|
|
||||||
playerdata_url = compat_urllib_parse_unquote(self._html_search_regex(r'"config_url":"([^"]+)', webpage, 'playerdata_url'))
|
playerdata_url = compat_urllib_parse_unquote(self._html_search_regex(r'"config_url":"([^"]+)', webpage, 'playerdata_url'))
|
||||||
playerdata_req = compat_urllib_request.Request(playerdata_url)
|
playerdata_req = sanitized_Request(playerdata_url)
|
||||||
playerdata_req.data = compat_urllib_parse.urlencode({'current_page': webpage_url})
|
playerdata_req.data = compat_urllib_parse.urlencode({'current_page': webpage_url})
|
||||||
playerdata_req.add_header('Content-Type', 'application/x-www-form-urlencoded')
|
playerdata_req.add_header('Content-Type', 'application/x-www-form-urlencoded')
|
||||||
playerdata = self._download_webpage(playerdata_req, video_id, note='Downloading media info')
|
playerdata = self._download_webpage(playerdata_req, video_id, note='Downloading media info')
|
||||||
@ -319,7 +320,7 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
|||||||
for fmt in re.findall(r'showmedia\.([0-9]{3,4})p', webpage):
|
for fmt in re.findall(r'showmedia\.([0-9]{3,4})p', webpage):
|
||||||
stream_quality, stream_format = self._FORMAT_IDS[fmt]
|
stream_quality, stream_format = self._FORMAT_IDS[fmt]
|
||||||
video_format = fmt + 'p'
|
video_format = fmt + 'p'
|
||||||
streamdata_req = compat_urllib_request.Request(
|
streamdata_req = sanitized_Request(
|
||||||
'http://www.crunchyroll.com/xml/?req=RpcApiVideoPlayer_GetStandardConfig&media_id=%s&video_format=%s&video_quality=%s'
|
'http://www.crunchyroll.com/xml/?req=RpcApiVideoPlayer_GetStandardConfig&media_id=%s&video_format=%s&video_quality=%s'
|
||||||
% (stream_id, stream_format, stream_quality),
|
% (stream_id, stream_format, stream_quality),
|
||||||
compat_urllib_parse.urlencode({'current_page': url}).encode('utf-8'))
|
compat_urllib_parse.urlencode({'current_page': url}).encode('utf-8'))
|
||||||
@ -328,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,
|
||||||
@ -373,7 +376,7 @@ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
|
|||||||
|
|
||||||
|
|
||||||
class CrunchyrollShowPlaylistIE(CrunchyrollBaseIE):
|
class CrunchyrollShowPlaylistIE(CrunchyrollBaseIE):
|
||||||
IE_NAME = "crunchyroll:playlist"
|
IE_NAME = 'crunchyroll:playlist'
|
||||||
_VALID_URL = r'https?://(?:(?P<prefix>www|m)\.)?(?P<url>crunchyroll\.com/(?!(?:news|anime-news|library|forum|launchcalendar|lineup|store|comics|freetrial|login))(?P<id>[\w\-]+))/?(?:\?|$)'
|
_VALID_URL = r'https?://(?:(?P<prefix>www|m)\.)?(?P<url>crunchyroll\.com/(?!(?:news|anime-news|library|forum|launchcalendar|lineup|store|comics|freetrial|login))(?P<id>[\w\-]+))/?(?:\?|$)'
|
||||||
|
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
|
@ -9,6 +9,7 @@ from ..utils import (
|
|||||||
find_xpath_attr,
|
find_xpath_attr,
|
||||||
smuggle_url,
|
smuggle_url,
|
||||||
determine_ext,
|
determine_ext,
|
||||||
|
ExtractorError,
|
||||||
)
|
)
|
||||||
from .senateisvp import SenateISVPIE
|
from .senateisvp import SenateISVPIE
|
||||||
|
|
||||||
@ -18,33 +19,32 @@ class CSpanIE(InfoExtractor):
|
|||||||
IE_DESC = 'C-SPAN'
|
IE_DESC = 'C-SPAN'
|
||||||
_TESTS = [{
|
_TESTS = [{
|
||||||
'url': 'http://www.c-span.org/video/?313572-1/HolderonV',
|
'url': 'http://www.c-span.org/video/?313572-1/HolderonV',
|
||||||
'md5': '8e44ce11f0f725527daccc453f553eb0',
|
'md5': '94b29a4f131ff03d23471dd6f60b6a1d',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '315139',
|
'id': '315139',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'Attorney General Eric Holder on Voting Rights Act Decision',
|
'title': 'Attorney General Eric Holder on Voting Rights Act Decision',
|
||||||
'description': 'Attorney General Eric Holder spoke to reporters following the Supreme Court decision in Shelby County v. Holder in which the court ruled that the preclearance provisions of the Voting Rights Act could not be enforced until Congress established new guidelines for review.',
|
'description': 'Attorney General Eric Holder speaks to reporters following the Supreme Court decision in [Shelby County v. Holder], in which the court ruled that the preclearance provisions of the Voting Rights Act could not be enforced.',
|
||||||
},
|
},
|
||||||
'skip': 'Regularly fails on travis, for unknown reasons',
|
'skip': 'Regularly fails on travis, for unknown reasons',
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://www.c-span.org/video/?c4486943/cspan-international-health-care-models',
|
'url': 'http://www.c-span.org/video/?c4486943/cspan-international-health-care-models',
|
||||||
# For whatever reason, the served video alternates between
|
'md5': '8e5fbfabe6ad0f89f3012a7943c1287b',
|
||||||
# two different ones
|
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '340723',
|
'id': 'c4486943',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'International Health Care Models',
|
'title': 'CSPAN - International Health Care Models',
|
||||||
'description': 'md5:7a985a2d595dba00af3d9c9f0783c967',
|
'description': 'md5:7a985a2d595dba00af3d9c9f0783c967',
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://www.c-span.org/video/?318608-1/gm-ignition-switch-recall',
|
'url': 'http://www.c-span.org/video/?318608-1/gm-ignition-switch-recall',
|
||||||
'md5': '446562a736c6bf97118e389433ed88d4',
|
'md5': '2ae5051559169baadba13fc35345ae74',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '342759',
|
'id': '342759',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'General Motors Ignition Switch Recall',
|
'title': 'General Motors Ignition Switch Recall',
|
||||||
'duration': 14848,
|
'duration': 14848,
|
||||||
'description': 'md5:70c7c3b8fa63fa60d42772440596034c'
|
'description': 'md5:118081aedd24bf1d3b68b3803344e7f3'
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
# Video from senate.gov
|
# Video from senate.gov
|
||||||
@ -57,67 +57,94 @@ class CSpanIE(InfoExtractor):
|
|||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
video_id = self._match_id(url)
|
||||||
page_id = mobj.group('id')
|
video_type = None
|
||||||
webpage = self._download_webpage(url, page_id)
|
webpage = self._download_webpage(url, video_id)
|
||||||
video_id = self._search_regex(r'progid=\'?([0-9]+)\'?>', webpage, 'video id')
|
# We first look for clipid, because clipprog always appears before
|
||||||
|
patterns = [r'id=\'clip(%s)\'\s*value=\'([0-9]+)\'' % t for t in ('id', 'prog')]
|
||||||
|
results = list(filter(None, (re.search(p, webpage) for p in patterns)))
|
||||||
|
if results:
|
||||||
|
matches = results[0]
|
||||||
|
video_type, video_id = matches.groups()
|
||||||
|
video_type = 'clip' if video_type == 'id' else 'program'
|
||||||
|
else:
|
||||||
|
m = re.search(r'data-(?P<type>clip|prog)id=["\'](?P<id>\d+)', webpage)
|
||||||
|
if m:
|
||||||
|
video_id = m.group('id')
|
||||||
|
video_type = 'program' if m.group('type') == 'prog' else 'clip'
|
||||||
|
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:
|
||||||
|
raise ExtractorError('unable to find video id and type')
|
||||||
|
|
||||||
description = self._html_search_regex(
|
def get_text_attr(d, attr):
|
||||||
[
|
return d.get(attr, {}).get('#text')
|
||||||
# The full description
|
|
||||||
r'<div class=\'expandable\'>(.*?)<a href=\'#\'',
|
|
||||||
# If the description is small enough the other div is not
|
|
||||||
# present, otherwise this is a stripped version
|
|
||||||
r'<p class=\'initial\'>(.*?)</p>'
|
|
||||||
],
|
|
||||||
webpage, 'description', flags=re.DOTALL, default=None)
|
|
||||||
|
|
||||||
info_url = 'http://c-spanvideo.org/videoLibrary/assets/player/ajax-player.php?os=android&html5=program&id=' + video_id
|
data = self._download_json(
|
||||||
data = self._download_json(info_url, video_id)
|
'http://www.c-span.org/assets/player/ajax-player.php?os=android&html5=%s&id=%s' % (video_type, video_id),
|
||||||
|
video_id)['video']
|
||||||
|
if data['@status'] != 'Success':
|
||||||
|
raise ExtractorError('%s said: %s' % (self.IE_NAME, get_text_attr(data, 'error')), expected=True)
|
||||||
|
|
||||||
doc = self._download_xml(
|
doc = self._download_xml(
|
||||||
'http://www.c-span.org/common/services/flashXml.php?programid=' + video_id,
|
'http://www.c-span.org/common/services/flashXml.php?%sid=%s' % (video_type, video_id),
|
||||||
video_id)
|
video_id)
|
||||||
|
|
||||||
|
description = self._html_search_meta('description', webpage)
|
||||||
|
|
||||||
title = find_xpath_attr(doc, './/string', 'name', 'title').text
|
title = find_xpath_attr(doc, './/string', 'name', 'title').text
|
||||||
thumbnail = find_xpath_attr(doc, './/string', 'name', 'poster').text
|
thumbnail = find_xpath_attr(doc, './/string', 'name', 'poster').text
|
||||||
|
|
||||||
senate_isvp_url = SenateISVPIE._search_iframe_url(webpage)
|
files = data['files']
|
||||||
if senate_isvp_url:
|
capfile = get_text_attr(data, 'capfile')
|
||||||
surl = smuggle_url(senate_isvp_url, {'force_title': title})
|
|
||||||
return self.url_result(surl, 'SenateISVP', video_id, title)
|
|
||||||
|
|
||||||
files = data['video']['files']
|
entries = []
|
||||||
try:
|
for partnum, f in enumerate(files):
|
||||||
capfile = data['video']['capfile']['#text']
|
formats = []
|
||||||
except KeyError:
|
for quality in f['qualities']:
|
||||||
capfile = None
|
formats.append({
|
||||||
|
'format_id': '%s-%sp' % (get_text_attr(quality, 'bitrate'), get_text_attr(quality, 'height')),
|
||||||
entries = [{
|
'url': unescapeHTML(get_text_attr(quality, 'file')),
|
||||||
|
'height': int_or_none(get_text_attr(quality, 'height')),
|
||||||
|
'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)
|
||||||
|
entries.append({
|
||||||
'id': '%s_%d' % (video_id, partnum + 1),
|
'id': '%s_%d' % (video_id, partnum + 1),
|
||||||
'title': (
|
'title': (
|
||||||
title if len(files) == 1 else
|
title if len(files) == 1 else
|
||||||
'%s part %d' % (title, partnum + 1)),
|
'%s part %d' % (title, partnum + 1)),
|
||||||
'url': unescapeHTML(f['path']['#text']),
|
'formats': formats,
|
||||||
'description': description,
|
'description': description,
|
||||||
'thumbnail': thumbnail,
|
'thumbnail': thumbnail,
|
||||||
'duration': int_or_none(f.get('length', {}).get('#text')),
|
'duration': int_or_none(get_text_attr(f, 'length')),
|
||||||
'subtitles': {
|
'subtitles': {
|
||||||
'en': [{
|
'en': [{
|
||||||
'url': capfile,
|
'url': capfile,
|
||||||
'ext': determine_ext(capfile, 'dfxp')
|
'ext': determine_ext(capfile, 'dfxp')
|
||||||
}],
|
}],
|
||||||
} if capfile else None,
|
} if capfile else None,
|
||||||
} for partnum, f in enumerate(files)]
|
})
|
||||||
|
|
||||||
if len(entries) == 1:
|
if len(entries) == 1:
|
||||||
entry = dict(entries[0])
|
entry = dict(entries[0])
|
||||||
entry['id'] = video_id
|
entry['id'] = 'c' + video_id if video_type == 'clip' else video_id
|
||||||
return entry
|
return entry
|
||||||
else:
|
else:
|
||||||
return {
|
return {
|
||||||
'_type': 'playlist',
|
'_type': 'playlist',
|
||||||
'entries': entries,
|
'entries': entries,
|
||||||
'title': title,
|
'title': title,
|
||||||
'id': video_id,
|
'id': 'c' + video_id if video_type == 'clip' else video_id,
|
||||||
}
|
}
|
||||||
|
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,
|
||||||
|
}
|
@ -7,15 +7,13 @@ import itertools
|
|||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
|
|
||||||
from ..compat import (
|
|
||||||
compat_str,
|
|
||||||
compat_urllib_request,
|
|
||||||
)
|
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
ExtractorError,
|
|
||||||
determine_ext,
|
determine_ext,
|
||||||
|
error_to_compat_str,
|
||||||
|
ExtractorError,
|
||||||
int_or_none,
|
int_or_none,
|
||||||
parse_iso8601,
|
parse_iso8601,
|
||||||
|
sanitized_Request,
|
||||||
str_to_int,
|
str_to_int,
|
||||||
unescapeHTML,
|
unescapeHTML,
|
||||||
)
|
)
|
||||||
@ -25,7 +23,7 @@ class DailymotionBaseInfoExtractor(InfoExtractor):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def _build_request(url):
|
def _build_request(url):
|
||||||
"""Build a request with the family filter disabled"""
|
"""Build a request with the family filter disabled"""
|
||||||
request = compat_urllib_request.Request(url)
|
request = sanitized_Request(url)
|
||||||
request.add_header('Cookie', 'family_filter=off; ff=off')
|
request.add_header('Cookie', 'family_filter=off; ff=off')
|
||||||
return request
|
return request
|
||||||
|
|
||||||
@ -39,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 = [
|
||||||
@ -101,6 +99,15 @@ class DailymotionIE(DailymotionBaseInfoExtractor):
|
|||||||
{
|
{
|
||||||
'url': 'http://www.dailymotion.com/video/xhza0o',
|
'url': 'http://www.dailymotion.com/video/xhza0o',
|
||||||
'only_matching': True,
|
'only_matching': True,
|
||||||
|
},
|
||||||
|
# with subtitles
|
||||||
|
{
|
||||||
|
'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,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'url': 'http://www.dailymotion.com/swf/video/x3n92nf',
|
||||||
|
'only_matching': True,
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -115,16 +122,21 @@ class DailymotionIE(DailymotionBaseInfoExtractor):
|
|||||||
description = self._og_search_description(webpage) or self._html_search_meta(
|
description = self._og_search_description(webpage) or self._html_search_meta(
|
||||||
'description', webpage, 'description')
|
'description', webpage, 'description')
|
||||||
|
|
||||||
view_count = str_to_int(self._search_regex(
|
view_count_str = self._search_regex(
|
||||||
[r'<meta[^>]+itemprop="interactionCount"[^>]+content="UserPlays:(\d+)"',
|
(r'<meta[^>]+itemprop="interactionCount"[^>]+content="UserPlays:([\s\d,.]+)"',
|
||||||
r'video_views_count[^>]+>\s+([\d\.,]+)'],
|
r'video_views_count[^>]+>\s+([\s\d\,.]+)'),
|
||||||
webpage, 'view count', fatal=False))
|
webpage, 'view count', fatal=False)
|
||||||
|
if view_count_str:
|
||||||
|
view_count_str = re.sub(r'\s', '', view_count_str)
|
||||||
|
view_count = str_to_int(view_count_str)
|
||||||
comment_count = int_or_none(self._search_regex(
|
comment_count = int_or_none(self._search_regex(
|
||||||
r'<meta[^>]+itemprop="interactionCount"[^>]+content="UserComments:(\d+)"',
|
r'<meta[^>]+itemprop="interactionCount"[^>]+content="UserComments:(\d+)"',
|
||||||
webpage, 'comment count', fatal=False))
|
webpage, 'comment count', fatal=False))
|
||||||
|
|
||||||
player_v5 = self._search_regex(
|
player_v5 = self._search_regex(
|
||||||
[r'buildPlayer\(({.+?})\);', r'playerV5\s*=\s*dmp\.create\([^,]+?,\s*({.+?})\);'],
|
[r'buildPlayer\(({.+?})\);\n', # See https://github.com/rg3/youtube-dl/issues/7826
|
||||||
|
r'playerV5\s*=\s*dmp\.create\([^,]+?,\s*({.+?})\);',
|
||||||
|
r'buildPlayer\(({.+?})\);'],
|
||||||
webpage, 'player v5', default=None)
|
webpage, 'player v5', default=None)
|
||||||
if player_v5:
|
if player_v5:
|
||||||
player = self._parse_json(player_v5, video_id)
|
player = self._parse_json(player_v5, video_id)
|
||||||
@ -143,19 +155,16 @@ class DailymotionIE(DailymotionBaseInfoExtractor):
|
|||||||
continue
|
continue
|
||||||
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':
|
||||||
m3u8_formats = 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,
|
||||||
if m3u8_formats:
|
m3u8_id='hls', fatal=False))
|
||||||
formats.extend(m3u8_formats)
|
|
||||||
elif type_ == 'application/f4m' or ext == 'f4m':
|
elif type_ == 'application/f4m' or ext == 'f4m':
|
||||||
f4m_formats = 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))
|
||||||
if f4m_formats:
|
|
||||||
formats.extend(f4m_formats)
|
|
||||||
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:
|
||||||
@ -174,7 +183,9 @@ class DailymotionIE(DailymotionBaseInfoExtractor):
|
|||||||
uploader_id = metadata.get('owner', {}).get('id')
|
uploader_id = metadata.get('owner', {}).get('id')
|
||||||
|
|
||||||
subtitles = {}
|
subtitles = {}
|
||||||
for subtitle_lang, subtitle in metadata.get('subtitles', {}).get('data', {}).items():
|
subtitles_data = metadata.get('subtitles', {}).get('data', {})
|
||||||
|
if subtitles_data and isinstance(subtitles_data, dict):
|
||||||
|
for subtitle_lang, subtitle in subtitles_data.items():
|
||||||
subtitles[subtitle_lang] = [{
|
subtitles[subtitle_lang] = [{
|
||||||
'ext': determine_ext(subtitle_url),
|
'ext': determine_ext(subtitle_url),
|
||||||
'url': subtitle_url,
|
'url': subtitle_url,
|
||||||
@ -271,7 +282,7 @@ class DailymotionIE(DailymotionBaseInfoExtractor):
|
|||||||
'https://api.dailymotion.com/video/%s/subtitles?fields=id,language,url' % video_id,
|
'https://api.dailymotion.com/video/%s/subtitles?fields=id,language,url' % video_id,
|
||||||
video_id, note=False)
|
video_id, note=False)
|
||||||
except ExtractorError as err:
|
except ExtractorError as err:
|
||||||
self._downloader.report_warning('unable to download video subtitles: %s' % compat_str(err))
|
self._downloader.report_warning('unable to download video subtitles: %s' % error_to_compat_str(err))
|
||||||
return {}
|
return {}
|
||||||
info = json.loads(sub_list)
|
info = json.loads(sub_list)
|
||||||
if (info['total'] > 0):
|
if (info['total'] > 0):
|
||||||
@ -332,7 +343,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',
|
||||||
@ -388,13 +399,13 @@ class DailymotionCloudIE(DailymotionBaseInfoExtractor):
|
|||||||
}]
|
}]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _extract_dmcloud_url(self, webpage):
|
def _extract_dmcloud_url(cls, webpage):
|
||||||
mobj = re.search(r'<iframe[^>]+src=[\'"](%s)[\'"]' % self._VALID_EMBED_URL, webpage)
|
mobj = re.search(r'<iframe[^>]+src=[\'"](%s)[\'"]' % cls._VALID_EMBED_URL, webpage)
|
||||||
if mobj:
|
if mobj:
|
||||||
return mobj.group(1)
|
return mobj.group(1)
|
||||||
|
|
||||||
mobj = re.search(
|
mobj = re.search(
|
||||||
r'<input[^>]+id=[\'"]dmcloudUrlEmissionSelect[\'"][^>]+value=[\'"](%s)[\'"]' % self._VALID_EMBED_URL,
|
r'<input[^>]+id=[\'"]dmcloudUrlEmissionSelect[\'"][^>]+value=[\'"](%s)[\'"]' % cls._VALID_EMBED_URL,
|
||||||
webpage)
|
webpage)
|
||||||
if mobj:
|
if mobj:
|
||||||
return mobj.group(1)
|
return mobj.group(1)
|
||||||
|
@ -3,56 +3,91 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
import itertools
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
from ..compat import (
|
||||||
|
compat_parse_qs,
|
||||||
compat_urllib_parse,
|
compat_urllib_parse,
|
||||||
|
compat_urllib_parse_unquote,
|
||||||
|
compat_urlparse,
|
||||||
|
)
|
||||||
|
from ..utils import (
|
||||||
|
int_or_none,
|
||||||
|
str_to_int,
|
||||||
|
xpath_text,
|
||||||
|
unescapeHTML,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class DaumIE(InfoExtractor):
|
class DaumIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:m\.)?tvpot\.daum\.net/(?:v/|.*?clipid=)(?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 = [{
|
||||||
'url': 'http://tvpot.daum.net/clip/ClipView.do?clipid=52554690',
|
'url': 'http://tvpot.daum.net/v/vab4dyeDBysyBssyukBUjBz',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
'id': '52554690',
|
'id': 'vab4dyeDBysyBssyukBUjBz',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'DOTA 2GETHER 시즌2 6회 - 2부',
|
'title': '마크 헌트 vs 안토니오 실바',
|
||||||
'description': 'DOTA 2GETHER 시즌2 6회 - 2부',
|
'description': 'Mark Hunt vs Antonio Silva',
|
||||||
'upload_date': '20130831',
|
'upload_date': '20131217',
|
||||||
'duration': 3868,
|
'thumbnail': 're:^https?://.*\.(?:jpg|png)',
|
||||||
|
'duration': 2117,
|
||||||
|
'view_count': int,
|
||||||
|
'comment_count': int,
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://tvpot.daum.net/v/vab4dyeDBysyBssyukBUjBz',
|
'url': 'http://m.tvpot.daum.net/v/65139429',
|
||||||
'only_matching': True,
|
'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):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
video_id = compat_urllib_parse_unquote(self._match_id(url))
|
||||||
video_id = mobj.group('id')
|
query = compat_urllib_parse.urlencode({'vid': video_id})
|
||||||
canonical_url = 'http://tvpot.daum.net/v/%s' % video_id
|
movie_data = self._download_json(
|
||||||
webpage = self._download_webpage(canonical_url, video_id)
|
'http://videofarm.daum.net/controller/api/closed/v1_2/IntegratedMovieData.json?' + query,
|
||||||
full_id = self._search_regex(
|
video_id, 'Downloading video formats info')
|
||||||
r'src=["\']http://videofarm\.daum\.net/controller/video/viewer/Video\.html\?.*?vid=(.+?)[&"\']',
|
|
||||||
webpage, 'full id')
|
# For urls like http://m.tvpot.daum.net/v/65139429, where the video_id is really a clipid
|
||||||
query = compat_urllib_parse.urlencode({'vid': full_id})
|
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(
|
info = self._download_xml(
|
||||||
'http://tvpot.daum.net/clip/ClipInfoXml.do?' + query, video_id,
|
'http://tvpot.daum.net/clip/ClipInfoXml.do?' + query, video_id,
|
||||||
'Downloading video info')
|
'Downloading video info')
|
||||||
urls = self._download_xml(
|
|
||||||
'http://videofarm.daum.net/controller/api/open/v1_2/MovieData.apixml?' + query,
|
|
||||||
video_id, 'Downloading video formats info')
|
|
||||||
|
|
||||||
formats = []
|
formats = []
|
||||||
for format_el in urls.findall('result/output_list/output_list'):
|
for format_el in movie_data['output_list']['output_list']:
|
||||||
profile = format_el.attrib['profile']
|
profile = format_el['profile']
|
||||||
format_query = compat_urllib_parse.urlencode({
|
format_query = compat_urllib_parse.urlencode({
|
||||||
'vid': full_id,
|
'vid': video_id,
|
||||||
'profile': profile,
|
'profile': profile,
|
||||||
})
|
})
|
||||||
url_doc = self._download_xml(
|
url_doc = self._download_xml(
|
||||||
@ -62,14 +97,202 @@ class DaumIE(InfoExtractor):
|
|||||||
formats.append({
|
formats.append({
|
||||||
'url': format_url,
|
'url': format_url,
|
||||||
'format_id': profile,
|
'format_id': profile,
|
||||||
|
'width': int_or_none(format_el.get('width')),
|
||||||
|
'height': int_or_none(format_el.get('height')),
|
||||||
|
'filesize': int_or_none(format_el.get('filesize')),
|
||||||
})
|
})
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': video_id,
|
'id': video_id,
|
||||||
'title': info.find('TITLE').text,
|
'title': info.find('TITLE').text,
|
||||||
'formats': formats,
|
'formats': formats,
|
||||||
'thumbnail': self._og_search_thumbnail(webpage),
|
'thumbnail': xpath_text(info, 'THUMB_URL'),
|
||||||
'description': info.find('CONTENTS').text,
|
'description': xpath_text(info, 'CONTENTS'),
|
||||||
'duration': int(info.find('DURATION').text),
|
'duration': int_or_none(xpath_text(info, 'DURATION')),
|
||||||
'upload_date': info.find('REGDTTM').text[:8],
|
'upload_date': info.find('REGDTTM').text[:8],
|
||||||
|
'view_count': str_to_int(xpath_text(info, 'PLAY_CNT')),
|
||||||
|
'comment_count': str_to_int(xpath_text(info, 'COMMENT_CNT')),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class DaumClipIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'https?://(?:m\.)?tvpot\.daum\.net/(?:clip/ClipView.(?:do|tv)|mypot/View.do)\?.*?clipid=(?P<id>\d+)'
|
||||||
|
IE_NAME = 'daum.net:clip'
|
||||||
|
_URL_TEMPLATE = 'http://tvpot.daum.net/clip/ClipView.do?clipid=%s'
|
||||||
|
|
||||||
|
_TESTS = [{
|
||||||
|
'url': 'http://tvpot.daum.net/clip/ClipView.do?clipid=52554690',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '52554690',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'DOTA 2GETHER 시즌2 6회 - 2부',
|
||||||
|
'description': 'DOTA 2GETHER 시즌2 6회 - 2부',
|
||||||
|
'upload_date': '20130831',
|
||||||
|
'thumbnail': 're:^https?://.*\.(?:jpg|png)',
|
||||||
|
'duration': 3868,
|
||||||
|
'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):
|
||||||
|
video_id = self._match_id(url)
|
||||||
|
clip_info = self._download_json(
|
||||||
|
'http://tvpot.daum.net/mypot/json/GetClipInfo.do?clipid=%s' % video_id,
|
||||||
|
video_id, 'Downloading clip info')['clip_bean']
|
||||||
|
|
||||||
|
return {
|
||||||
|
'_type': 'url_transparent',
|
||||||
|
'id': video_id,
|
||||||
|
'url': 'http://tvpot.daum.net/v/%s' % clip_info['vid'],
|
||||||
|
'title': unescapeHTML(clip_info['title']),
|
||||||
|
'thumbnail': clip_info.get('thumb_url'),
|
||||||
|
'description': clip_info.get('contents'),
|
||||||
|
'duration': int_or_none(clip_info.get('duration')),
|
||||||
|
'upload_date': clip_info.get('up_date')[:8],
|
||||||
|
'view_count': int_or_none(clip_info.get('play_count')),
|
||||||
|
'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)
|
||||||
|
@ -13,8 +13,8 @@ from ..utils import (
|
|||||||
|
|
||||||
|
|
||||||
class DBTVIE(InfoExtractor):
|
class DBTVIE(InfoExtractor):
|
||||||
_VALID_URL = r'http://dbtv\.no/(?P<id>[0-9]+)#(?P<display_id>.+)'
|
_VALID_URL = r'https?://(?:www\.)?dbtv\.no/(?:(?:lazyplayer|player)/)?(?P<id>[0-9]+)(?:#(?P<display_id>.+))?'
|
||||||
_TEST = {
|
_TESTS = [{
|
||||||
'url': 'http://dbtv.no/3649835190001#Skulle_teste_ut_fornøyelsespark,_men_kollegaen_var_bare_opptatt_av_bikinikroppen',
|
'url': 'http://dbtv.no/3649835190001#Skulle_teste_ut_fornøyelsespark,_men_kollegaen_var_bare_opptatt_av_bikinikroppen',
|
||||||
'md5': 'b89953ed25dacb6edb3ef6c6f430f8bc',
|
'md5': 'b89953ed25dacb6edb3ef6c6f430f8bc',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
@ -30,12 +30,18 @@ class DBTVIE(InfoExtractor):
|
|||||||
'view_count': int,
|
'view_count': int,
|
||||||
'categories': list,
|
'categories': list,
|
||||||
}
|
}
|
||||||
}
|
}, {
|
||||||
|
'url': 'http://dbtv.no/3649835190001',
|
||||||
|
'only_matching': True,
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.dbtv.no/lazyplayer/4631135248001',
|
||||||
|
'only_matching': True,
|
||||||
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
mobj = re.match(self._VALID_URL, url)
|
mobj = re.match(self._VALID_URL, url)
|
||||||
video_id = mobj.group('id')
|
video_id = mobj.group('id')
|
||||||
display_id = mobj.group('display_id')
|
display_id = mobj.group('display_id') or video_id
|
||||||
|
|
||||||
data = self._download_json(
|
data = self._download_json(
|
||||||
'http://api.dbtv.no/discovery/%s' % video_id, display_id)
|
'http://api.dbtv.no/discovery/%s' % video_id, display_id)
|
||||||
|
@ -1,28 +1,90 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
import base64
|
||||||
|
|
||||||
from .common import InfoExtractor
|
from .common import InfoExtractor
|
||||||
from ..compat import (
|
from ..compat import (
|
||||||
compat_urllib_parse,
|
compat_urllib_parse,
|
||||||
compat_urllib_request,
|
compat_str,
|
||||||
)
|
)
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
int_or_none,
|
int_or_none,
|
||||||
parse_iso8601,
|
parse_iso8601,
|
||||||
|
sanitized_Request,
|
||||||
|
smuggle_url,
|
||||||
|
unsmuggle_url,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class DCNIE(InfoExtractor):
|
class DCNIE(InfoExtractor):
|
||||||
_VALID_URL = r'https?://(?:www\.)?dcndigital\.ae/(?:#/)?(?:video/.+|show/\d+/.+?)/(?P<id>\d+)'
|
_VALID_URL = r'https?://(?:www\.)?dcndigital\.ae/(?:#/)?show/(?P<show_id>\d+)/[^/]+(?:/(?P<video_id>\d+)/(?P<season_id>\d+))?'
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
show_id, video_id, season_id = re.match(self._VALID_URL, url).groups()
|
||||||
|
if video_id and int(video_id) > 0:
|
||||||
|
return self.url_result(
|
||||||
|
'http://www.dcndigital.ae/media/%s' % video_id, 'DCNVideo')
|
||||||
|
elif season_id and int(season_id) > 0:
|
||||||
|
return self.url_result(smuggle_url(
|
||||||
|
'http://www.dcndigital.ae/program/season/%s' % season_id,
|
||||||
|
{'show_id': show_id}), 'DCNSeason')
|
||||||
|
else:
|
||||||
|
return self.url_result(
|
||||||
|
'http://www.dcndigital.ae/program/%s' % show_id, 'DCNSeason')
|
||||||
|
|
||||||
|
|
||||||
|
class DCNBaseIE(InfoExtractor):
|
||||||
|
def _extract_video_info(self, video_data, video_id, is_live):
|
||||||
|
title = video_data.get('title_en') or video_data['title_ar']
|
||||||
|
img = video_data.get('img')
|
||||||
|
thumbnail = 'http://admin.mangomolo.com/analytics/%s' % img if img else None
|
||||||
|
duration = int_or_none(video_data.get('duration'))
|
||||||
|
description = video_data.get('description_en') or video_data.get('description_ar')
|
||||||
|
timestamp = parse_iso8601(video_data.get('create_time'), ' ')
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': self._live_title(title) if is_live else title,
|
||||||
|
'description': description,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'duration': duration,
|
||||||
|
'timestamp': timestamp,
|
||||||
|
'is_live': is_live,
|
||||||
|
}
|
||||||
|
|
||||||
|
def _extract_video_formats(self, webpage, video_id, entry_protocol):
|
||||||
|
formats = []
|
||||||
|
m3u8_url = self._html_search_regex(
|
||||||
|
r'file\s*:\s*"([^"]+)', webpage, 'm3u8 url', fatal=False)
|
||||||
|
if m3u8_url:
|
||||||
|
formats.extend(self._extract_m3u8_formats(
|
||||||
|
m3u8_url, video_id, 'mp4', entry_protocol, m3u8_id='hls', fatal=None))
|
||||||
|
|
||||||
|
rtsp_url = self._search_regex(
|
||||||
|
r'<a[^>]+href="(rtsp://[^"]+)"', webpage, 'rtsp url', fatal=False)
|
||||||
|
if rtsp_url:
|
||||||
|
formats.append({
|
||||||
|
'url': rtsp_url,
|
||||||
|
'format_id': 'rtsp',
|
||||||
|
})
|
||||||
|
|
||||||
|
self._sort_formats(formats)
|
||||||
|
return formats
|
||||||
|
|
||||||
|
|
||||||
|
class DCNVideoIE(DCNBaseIE):
|
||||||
|
IE_NAME = 'dcn:video'
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?dcndigital\.ae/(?:#/)?(?:video/[^/]+|media|catchup/[^/]+/[^/]+)/(?P<id>\d+)'
|
||||||
_TEST = {
|
_TEST = {
|
||||||
'url': 'http://www.dcndigital.ae/#/show/199074/%D8%B1%D8%AD%D9%84%D8%A9-%D8%A7%D9%84%D8%B9%D9%85%D8%B1-%D8%A7%D9%84%D8%AD%D9%84%D9%82%D8%A9-1/17375/6887',
|
'url': 'http://www.dcndigital.ae/#/video/%D8%B1%D8%AD%D9%84%D8%A9-%D8%A7%D9%84%D8%B9%D9%85%D8%B1-%D8%A7%D9%84%D8%AD%D9%84%D9%82%D8%A9-1/17375',
|
||||||
'info_dict':
|
'info_dict':
|
||||||
{
|
{
|
||||||
'id': '17375',
|
'id': '17375',
|
||||||
'ext': 'mp4',
|
'ext': 'mp4',
|
||||||
'title': 'رحلة العمر : الحلقة 1',
|
'title': 'رحلة العمر : الحلقة 1',
|
||||||
'description': 'md5:0156e935d870acb8ef0a66d24070c6d6',
|
'description': 'md5:0156e935d870acb8ef0a66d24070c6d6',
|
||||||
'thumbnail': 're:^https?://.*\.jpg$',
|
|
||||||
'duration': 2041,
|
'duration': 2041,
|
||||||
'timestamp': 1227504126,
|
'timestamp': 1227504126,
|
||||||
'upload_date': '20081124',
|
'upload_date': '20081124',
|
||||||
@ -36,49 +98,99 @@ class DCNIE(InfoExtractor):
|
|||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
video_id = self._match_id(url)
|
video_id = self._match_id(url)
|
||||||
|
|
||||||
request = compat_urllib_request.Request(
|
request = sanitized_Request(
|
||||||
'http://admin.mangomolo.com/analytics/index.php/plus/video?id=%s' % video_id,
|
'http://admin.mangomolo.com/analytics/index.php/plus/video?id=%s' % video_id,
|
||||||
headers={'Origin': 'http://www.dcndigital.ae'})
|
headers={'Origin': 'http://www.dcndigital.ae'})
|
||||||
|
video_data = self._download_json(request, video_id)
|
||||||
video = self._download_json(request, video_id)
|
info = self._extract_video_info(video_data, video_id, False)
|
||||||
title = video.get('title_en') or video['title_ar']
|
|
||||||
|
|
||||||
webpage = self._download_webpage(
|
webpage = self._download_webpage(
|
||||||
'http://admin.mangomolo.com/analytics/index.php/customers/embed/video?' +
|
'http://admin.mangomolo.com/analytics/index.php/customers/embed/video?' +
|
||||||
compat_urllib_parse.urlencode({
|
compat_urllib_parse.urlencode({
|
||||||
'id': video['id'],
|
'id': video_data['id'],
|
||||||
'user_id': video['user_id'],
|
'user_id': video_data['user_id'],
|
||||||
'signature': video['signature'],
|
'signature': video_data['signature'],
|
||||||
'countries': 'Q0M=',
|
'countries': 'Q0M=',
|
||||||
'filter': 'DENY',
|
'filter': 'DENY',
|
||||||
}), video_id)
|
}), video_id)
|
||||||
|
info['formats'] = self._extract_video_formats(webpage, video_id, 'm3u8_native')
|
||||||
|
return info
|
||||||
|
|
||||||
m3u8_url = self._html_search_regex(r'file:\s*"([^"]+)', webpage, 'm3u8 url')
|
|
||||||
formats = self._extract_m3u8_formats(
|
|
||||||
m3u8_url, video_id, 'mp4', entry_protocol='m3u8_native', m3u8_id='hls')
|
|
||||||
|
|
||||||
rtsp_url = self._search_regex(
|
class DCNLiveIE(DCNBaseIE):
|
||||||
r'<a[^>]+href="(rtsp://[^"]+)"', webpage, 'rtsp url', fatal=False)
|
IE_NAME = 'dcn:live'
|
||||||
if rtsp_url:
|
_VALID_URL = r'https?://(?:www\.)?dcndigital\.ae/(?:#/)?live/(?P<id>\d+)'
|
||||||
formats.append({
|
|
||||||
'url': rtsp_url,
|
def _real_extract(self, url):
|
||||||
'format_id': 'rtsp',
|
channel_id = self._match_id(url)
|
||||||
|
|
||||||
|
request = sanitized_Request(
|
||||||
|
'http://admin.mangomolo.com/analytics/index.php/plus/getchanneldetails?channel_id=%s' % channel_id,
|
||||||
|
headers={'Origin': 'http://www.dcndigital.ae'})
|
||||||
|
|
||||||
|
channel_data = self._download_json(request, channel_id)
|
||||||
|
info = self._extract_video_info(channel_data, channel_id, True)
|
||||||
|
|
||||||
|
webpage = self._download_webpage(
|
||||||
|
'http://admin.mangomolo.com/analytics/index.php/customers/embed/index?' +
|
||||||
|
compat_urllib_parse.urlencode({
|
||||||
|
'id': base64.b64encode(channel_data['user_id'].encode()).decode(),
|
||||||
|
'channelid': base64.b64encode(channel_data['id'].encode()).decode(),
|
||||||
|
'signature': channel_data['signature'],
|
||||||
|
'countries': 'Q0M=',
|
||||||
|
'filter': 'DENY',
|
||||||
|
}), channel_id)
|
||||||
|
info['formats'] = self._extract_video_formats(webpage, channel_id, 'm3u8')
|
||||||
|
return info
|
||||||
|
|
||||||
|
|
||||||
|
class DCNSeasonIE(InfoExtractor):
|
||||||
|
IE_NAME = 'dcn:season'
|
||||||
|
_VALID_URL = r'https?://(?:www\.)?dcndigital\.ae/(?:#/)?program/(?:(?P<show_id>\d+)|season/(?P<season_id>\d+))'
|
||||||
|
_TEST = {
|
||||||
|
'url': 'http://dcndigital.ae/#/program/205024/%D9%85%D8%AD%D8%A7%D8%B6%D8%B1%D8%A7%D8%AA-%D8%A7%D9%84%D8%B4%D9%8A%D8%AE-%D8%A7%D9%84%D8%B4%D8%B9%D8%B1%D8%A7%D9%88%D9%8A',
|
||||||
|
'info_dict':
|
||||||
|
{
|
||||||
|
'id': '7910',
|
||||||
|
'title': 'محاضرات الشيخ الشعراوي',
|
||||||
|
},
|
||||||
|
'playlist_mincount': 27,
|
||||||
|
}
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
url, smuggled_data = unsmuggle_url(url, {})
|
||||||
|
show_id, season_id = re.match(self._VALID_URL, url).groups()
|
||||||
|
|
||||||
|
data = {}
|
||||||
|
if season_id:
|
||||||
|
data['season'] = season_id
|
||||||
|
show_id = smuggled_data.get('show_id')
|
||||||
|
if show_id is None:
|
||||||
|
request = sanitized_Request(
|
||||||
|
'http://admin.mangomolo.com/analytics/index.php/plus/season_info?id=%s' % season_id,
|
||||||
|
headers={'Origin': 'http://www.dcndigital.ae'})
|
||||||
|
season = self._download_json(request, season_id)
|
||||||
|
show_id = season['id']
|
||||||
|
data['show_id'] = show_id
|
||||||
|
request = sanitized_Request(
|
||||||
|
'http://admin.mangomolo.com/analytics/index.php/plus/show',
|
||||||
|
compat_urllib_parse.urlencode(data),
|
||||||
|
{
|
||||||
|
'Origin': 'http://www.dcndigital.ae',
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
})
|
})
|
||||||
|
|
||||||
self._sort_formats(formats)
|
show = self._download_json(request, show_id)
|
||||||
|
if not season_id:
|
||||||
|
season_id = show['default_season']
|
||||||
|
for season in show['seasons']:
|
||||||
|
if season['id'] == season_id:
|
||||||
|
title = season.get('title_en') or season['title_ar']
|
||||||
|
|
||||||
img = video.get('img')
|
entries = []
|
||||||
thumbnail = 'http://admin.mangomolo.com/analytics/%s' % img if img else None
|
for video in show['videos']:
|
||||||
duration = int_or_none(video.get('duration'))
|
video_id = compat_str(video['id'])
|
||||||
description = video.get('description_en') or video.get('description_ar')
|
entries.append(self.url_result(
|
||||||
timestamp = parse_iso8601(video.get('create_time') or video.get('update_time'), ' ')
|
'http://www.dcndigital.ae/media/%s' % video_id, 'DCNVideo', video_id))
|
||||||
|
|
||||||
return {
|
return self.playlist_result(entries, season_id, title)
|
||||||
'id': video_id,
|
|
||||||
'title': title,
|
|
||||||
'description': description,
|
|
||||||
'thumbnail': thumbnail,
|
|
||||||
'duration': duration,
|
|
||||||
'timestamp': timestamp,
|
|
||||||
'formats': formats,
|
|
||||||
}
|
|
||||||
|
112
youtube_dl/extractor/digiteka.py
Normal file
112
youtube_dl/extractor/digiteka.py
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .common import InfoExtractor
|
||||||
|
from ..utils import int_or_none
|
||||||
|
|
||||||
|
|
||||||
|
class DigitekaIE(InfoExtractor):
|
||||||
|
_VALID_URL = r'''(?x)
|
||||||
|
https?://(?:www\.)?(?:digiteka\.net|ultimedia\.com)/
|
||||||
|
(?:
|
||||||
|
deliver/
|
||||||
|
(?P<embed_type>
|
||||||
|
generic|
|
||||||
|
musique
|
||||||
|
)
|
||||||
|
(?:/[^/]+)*/
|
||||||
|
(?:
|
||||||
|
src|
|
||||||
|
article
|
||||||
|
)|
|
||||||
|
default/index/video
|
||||||
|
(?P<site_type>
|
||||||
|
generic|
|
||||||
|
music
|
||||||
|
)
|
||||||
|
/id
|
||||||
|
)/(?P<id>[\d+a-z]+)'''
|
||||||
|
_TESTS = [{
|
||||||
|
# news
|
||||||
|
'url': 'https://www.ultimedia.com/default/index/videogeneric/id/s8uk0r',
|
||||||
|
'md5': '276a0e49de58c7e85d32b057837952a2',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 's8uk0r',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Loi sur la fin de vie: le texte prévoit un renforcement des directives anticipées',
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg',
|
||||||
|
'duration': 74,
|
||||||
|
'upload_date': '20150317',
|
||||||
|
'timestamp': 1426604939,
|
||||||
|
'uploader_id': '3fszv',
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
# music
|
||||||
|
'url': 'https://www.ultimedia.com/default/index/videomusic/id/xvpfp8',
|
||||||
|
'md5': '2ea3513813cf230605c7e2ffe7eca61c',
|
||||||
|
'info_dict': {
|
||||||
|
'id': 'xvpfp8',
|
||||||
|
'ext': 'mp4',
|
||||||
|
'title': 'Two - C\'est La Vie (clip)',
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg',
|
||||||
|
'duration': 233,
|
||||||
|
'upload_date': '20150224',
|
||||||
|
'timestamp': 1424760500,
|
||||||
|
'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
|
||||||
|
def _extract_url(webpage):
|
||||||
|
mobj = re.search(
|
||||||
|
r'<(?:iframe|script)[^>]+src=["\'](?P<url>(?:https?:)?//(?:www\.)?ultimedia\.com/deliver/(?:generic|musique)(?:/[^/]+)*/(?:src|article)/[\d+a-z]+)',
|
||||||
|
webpage)
|
||||||
|
if mobj:
|
||||||
|
return mobj.group('url')
|
||||||
|
|
||||||
|
def _real_extract(self, url):
|
||||||
|
mobj = re.match(self._VALID_URL, url)
|
||||||
|
video_id = mobj.group('id')
|
||||||
|
video_type = mobj.group('embed_type') or mobj.group('site_type')
|
||||||
|
if video_type == 'music':
|
||||||
|
video_type = 'musique'
|
||||||
|
|
||||||
|
deliver_info = self._download_json(
|
||||||
|
'http://www.ultimedia.com/deliver/video?video=%s&topic=%s' % (video_id, video_type),
|
||||||
|
video_id)
|
||||||
|
|
||||||
|
yt_id = deliver_info.get('yt_id')
|
||||||
|
if yt_id:
|
||||||
|
return self.url_result(yt_id, 'Youtube')
|
||||||
|
|
||||||
|
jwconf = deliver_info['jwconf']
|
||||||
|
|
||||||
|
formats = []
|
||||||
|
for source in jwconf['playlist'][0]['sources']:
|
||||||
|
formats.append({
|
||||||
|
'url': source['file'],
|
||||||
|
'format_id': source.get('label'),
|
||||||
|
})
|
||||||
|
|
||||||
|
self._sort_formats(formats)
|
||||||
|
|
||||||
|
title = deliver_info['title']
|
||||||
|
thumbnail = jwconf.get('image')
|
||||||
|
duration = int_or_none(deliver_info.get('duration'))
|
||||||
|
timestamp = int_or_none(deliver_info.get('release_time'))
|
||||||
|
uploader_id = deliver_info.get('owner_id')
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id': video_id,
|
||||||
|
'title': title,
|
||||||
|
'thumbnail': thumbnail,
|
||||||
|
'duration': duration,
|
||||||
|
'timestamp': timestamp,
|
||||||
|
'uploader_id': uploader_id,
|
||||||
|
'formats': formats,
|
||||||
|
}
|
@ -9,7 +9,17 @@ from ..compat import compat_str
|
|||||||
|
|
||||||
|
|
||||||
class DiscoveryIE(InfoExtractor):
|
class DiscoveryIE(InfoExtractor):
|
||||||
_VALID_URL = r'http://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)
|
||||||
|
@ -18,7 +18,7 @@ class DouyuTVIE(InfoExtractor):
|
|||||||
'display_id': 'iseven',
|
'display_id': 'iseven',
|
||||||
'ext': 'flv',
|
'ext': 'flv',
|
||||||
'title': 're:^清晨醒脑!T-ara根本停不下来! [0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}$',
|
'title': 're:^清晨醒脑!T-ara根本停不下来! [0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}$',
|
||||||
'description': 'md5:c93d6692dde6fe33809a46edcbecca44',
|
'description': 'md5:f34981259a03e980a3c6404190a3ed61',
|
||||||
'thumbnail': 're:^https?://.*\.jpg$',
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
'uploader': '7师傅',
|
'uploader': '7师傅',
|
||||||
'uploader_id': '431925',
|
'uploader_id': '431925',
|
||||||
@ -26,7 +26,7 @@ class DouyuTVIE(InfoExtractor):
|
|||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
}
|
},
|
||||||
}, {
|
}, {
|
||||||
'url': 'http://www.douyutv.com/85982',
|
'url': 'http://www.douyutv.com/85982',
|
||||||
'info_dict': {
|
'info_dict': {
|
||||||
@ -42,7 +42,24 @@ class DouyuTVIE(InfoExtractor):
|
|||||||
},
|
},
|
||||||
'params': {
|
'params': {
|
||||||
'skip_download': True,
|
'skip_download': True,
|
||||||
}
|
},
|
||||||
|
'skip': 'Romm not found',
|
||||||
|
}, {
|
||||||
|
'url': 'http://www.douyutv.com/17732',
|
||||||
|
'info_dict': {
|
||||||
|
'id': '17732',
|
||||||
|
'display_id': '17732',
|
||||||
|
'ext': 'flv',
|
||||||
|
'title': 're:^清晨醒脑!T-ara根本停不下来! [0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}$',
|
||||||
|
'description': 'md5:f34981259a03e980a3c6404190a3ed61',
|
||||||
|
'thumbnail': 're:^https?://.*\.jpg$',
|
||||||
|
'uploader': '7师傅',
|
||||||
|
'uploader_id': '431925',
|
||||||
|
'is_live': True,
|
||||||
|
},
|
||||||
|
'params': {
|
||||||
|
'skip_download': True,
|
||||||
|
},
|
||||||
}]
|
}]
|
||||||
|
|
||||||
def _real_extract(self, url):
|
def _real_extract(self, url):
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user