mirror of
https://source.netsyms.com/Mirrors/youtube-dl
synced 2026-04-24 02:45:17 +00:00
Compare commits
128 Commits
2013.04.03
...
2013.05.04
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f99511210 | ||
|
|
0d94f2474c | ||
|
|
480b6c1e8b | ||
|
|
95464f14d1 | ||
|
|
c34407d16c | ||
|
|
5e34d2ebbf | ||
|
|
815dd2ffa8 | ||
|
|
ecd5fb49c5 | ||
|
|
b86174e7a3 | ||
|
|
2e2038dc35 | ||
|
|
46bfb42258 | ||
|
|
feecf22511 | ||
|
|
4c4f15eb78 | ||
|
|
104ccdb8b4 | ||
|
|
6ccff79594 | ||
|
|
aed523ecc1 | ||
|
|
d496a75d0a | ||
|
|
5c01dd1e73 | ||
|
|
11d9224e3b | ||
|
|
34c29ba1d7 | ||
|
|
6cd657f9f2 | ||
|
|
4ae9e55822 | ||
|
|
8749b71273 | ||
|
|
dbc50fdf82 | ||
|
|
b1d2ef9255 | ||
|
|
5fb16555af | ||
|
|
ba7c775a04 | ||
|
|
fe348844d9 | ||
|
|
767e00277f | ||
|
|
6ce533a220 | ||
|
|
08b2ac745a | ||
|
|
46a127eecb | ||
|
|
fc63faf070 | ||
|
|
9665577802 | ||
|
|
434aca5b14 | ||
|
|
e31852aba9 | ||
|
|
37254abc36 | ||
|
|
a11ea50319 | ||
|
|
81df121dd3 | ||
|
|
50f6412eb8 | ||
|
|
bf50b0383e | ||
|
|
bd55852517 | ||
|
|
4c9f7a9988 | ||
|
|
aba8df23ed | ||
|
|
3820df0106 | ||
|
|
fa70605db2 | ||
|
|
0d173446ff | ||
|
|
320e26a0af | ||
|
|
a3d689cfb3 | ||
|
|
59cc5d9380 | ||
|
|
28535652ab | ||
|
|
7b670a4483 | ||
|
|
69fc019f26 | ||
|
|
613bf66939 | ||
|
|
9edb0916f4 | ||
|
|
f4b659f782 | ||
|
|
c70446c7df | ||
|
|
c76cb6d548 | ||
|
|
71f37e90ef | ||
|
|
75b5c590a8 | ||
|
|
4469666780 | ||
|
|
c15e024141 | ||
|
|
8cb94542f4 | ||
|
|
c681a03918 | ||
|
|
30f2999962 | ||
|
|
74e3452b9e | ||
|
|
9e1cf0c200 | ||
|
|
e11eb11906 | ||
|
|
c04bca6f60 | ||
|
|
b0936ef423 | ||
|
|
41a6eb949a | ||
|
|
f17ce13a92 | ||
|
|
8c416ad29a | ||
|
|
c72938240e | ||
|
|
e905b6f80e | ||
|
|
6de8f1afb7 | ||
|
|
9341212642 | ||
|
|
f7a9721e16 | ||
|
|
089e843b0f | ||
|
|
c8056d866a | ||
|
|
49da66e459 | ||
|
|
fb6c319904 | ||
|
|
5a8d13199c | ||
|
|
dce9027045 | ||
|
|
feba604e92 | ||
|
|
d22f65413a | ||
|
|
0599ef8c08 | ||
|
|
bfdf469295 | ||
|
|
32c96387c1 | ||
|
|
c8c5443bb5 | ||
|
|
a60b854d90 | ||
|
|
b8ad4f02a2 | ||
|
|
d281274bf2 | ||
|
|
b625bc2c31 | ||
|
|
f4381ab88a | ||
|
|
744435f2a4 | ||
|
|
855703e55e | ||
|
|
927c8c4924 | ||
|
|
0ba994e9e3 | ||
|
|
af9ad45cd4 | ||
|
|
e0fee250c3 | ||
|
|
72ca05016d | ||
|
|
844d1f9fa1 | ||
|
|
213c31ae16 | ||
|
|
04f3d551a0 | ||
|
|
e8600d69fd | ||
|
|
b03d65c237 | ||
|
|
8743974189 | ||
|
|
dc36bc9434 | ||
|
|
bce878a7c1 | ||
|
|
532d797824 | ||
|
|
146c12a2da | ||
|
|
d39919c03e | ||
|
|
df2dedeefb | ||
|
|
adb029ed81 | ||
|
|
43ff1a347d | ||
|
|
14294236bf | ||
|
|
7eab8dc750 | ||
|
|
d2c690828a | ||
|
|
cfa90f4adc | ||
|
|
a0d6fe7b92 | ||
|
|
8a38a194fb | ||
|
|
6ac7f082c4 | ||
|
|
f6e6da9525 | ||
|
|
597cc8a455 | ||
|
|
3370abd509 | ||
|
|
631f73978c | ||
|
|
df8db1aa21 |
@@ -8,6 +8,7 @@ notifications:
|
|||||||
email:
|
email:
|
||||||
- filippo.valsorda@gmail.com
|
- filippo.valsorda@gmail.com
|
||||||
- phihag@phihag.de
|
- phihag@phihag.de
|
||||||
|
- jaime.marquinez.ferrandiz+travis@gmail.com
|
||||||
# irc:
|
# irc:
|
||||||
# channels:
|
# channels:
|
||||||
# - "irc.freenode.org#youtube-dl"
|
# - "irc.freenode.org#youtube-dl"
|
||||||
|
|||||||
233
README.md
233
README.md
@@ -14,119 +14,135 @@ your Unix box, on Windows or on Mac OS X. It is released to the public domain,
|
|||||||
which means you can modify it, redistribute it or use it however you like.
|
which means you can modify it, redistribute it or use it however you like.
|
||||||
|
|
||||||
# OPTIONS
|
# OPTIONS
|
||||||
-h, --help print this help text and exit
|
-h, --help print this help text and exit
|
||||||
--version print program version and exit
|
--version print program version and exit
|
||||||
-U, --update update this program to latest version
|
-U, --update update this program to latest version
|
||||||
-i, --ignore-errors continue on download errors
|
-i, --ignore-errors continue on download errors
|
||||||
-r, --rate-limit LIMIT maximum download rate (e.g. 50k or 44.6m)
|
-r, --rate-limit LIMIT maximum download rate (e.g. 50k or 44.6m)
|
||||||
-R, --retries RETRIES number of retries (default is 10)
|
-R, --retries RETRIES number of retries (default is 10)
|
||||||
--buffer-size SIZE size of download buffer (e.g. 1024 or 16k) (default
|
--buffer-size SIZE size of download buffer (e.g. 1024 or 16k)
|
||||||
is 1024)
|
(default is 1024)
|
||||||
--no-resize-buffer do not automatically adjust the buffer size. By
|
--no-resize-buffer do not automatically adjust the buffer size. By
|
||||||
default, the buffer size is automatically resized
|
default, the buffer size is automatically resized
|
||||||
from an initial value of SIZE.
|
from an initial value of SIZE.
|
||||||
--dump-user-agent display the current browser identification
|
--dump-user-agent display the current browser identification
|
||||||
--user-agent UA specify a custom user agent
|
--user-agent UA specify a custom user agent
|
||||||
--list-extractors List all supported extractors and the URLs they
|
--referer REF specify a custom referer, use if the video access
|
||||||
would handle
|
is restricted to one domain
|
||||||
|
--list-extractors List all supported extractors and the URLs they
|
||||||
|
would handle
|
||||||
|
--proxy URL Use the specified HTTP/HTTPS proxy
|
||||||
|
|
||||||
## Video Selection:
|
## Video Selection:
|
||||||
--playlist-start NUMBER playlist video to start at (default is 1)
|
--playlist-start NUMBER playlist video to start at (default is 1)
|
||||||
--playlist-end NUMBER playlist video to end at (default is last)
|
--playlist-end NUMBER playlist video to end at (default is last)
|
||||||
--match-title REGEX download only matching titles (regex or caseless
|
--match-title REGEX download only matching titles (regex or caseless
|
||||||
sub-string)
|
sub-string)
|
||||||
--reject-title REGEX skip download for matching titles (regex or
|
--reject-title REGEX skip download for matching titles (regex or
|
||||||
caseless sub-string)
|
caseless sub-string)
|
||||||
--max-downloads NUMBER Abort after downloading NUMBER files
|
--max-downloads NUMBER Abort after downloading NUMBER files
|
||||||
--min-filesize SIZE Do not download any videos smaller than SIZE (e.g.
|
--min-filesize SIZE Do not download any videos smaller than SIZE
|
||||||
50k or 44.6m)
|
(e.g. 50k or 44.6m)
|
||||||
--max-filesize SIZE Do not download any videos larger than SIZE (e.g.
|
--max-filesize SIZE Do not download any videos larger than SIZE (e.g.
|
||||||
50k or 44.6m)
|
50k or 44.6m)
|
||||||
|
--date DATE download only videos uploaded in this date
|
||||||
|
--datebefore DATE download only videos uploaded before this date
|
||||||
|
--dateafter DATE download only videos uploaded after this date
|
||||||
|
|
||||||
## Filesystem Options:
|
## Filesystem Options:
|
||||||
-t, --title use title in file name
|
-t, --title use title in file name (default)
|
||||||
--id use video ID in file name
|
--id use only video ID in file name
|
||||||
-l, --literal [deprecated] alias of --title
|
-l, --literal [deprecated] alias of --title
|
||||||
-A, --auto-number number downloaded files starting from 00000
|
-A, --auto-number number downloaded files starting from 00000
|
||||||
-o, --output TEMPLATE output filename template. Use %(title)s to get the
|
-o, --output TEMPLATE output filename template. Use %(title)s to get
|
||||||
title, %(uploader)s for the uploader name,
|
the title, %(uploader)s for the uploader name,
|
||||||
%(uploader_id)s for the uploader nickname if
|
%(uploader_id)s for the uploader nickname if
|
||||||
different, %(autonumber)s to get an automatically
|
different, %(autonumber)s to get an automatically
|
||||||
incremented number, %(ext)s for the filename
|
incremented number, %(ext)s for the filename
|
||||||
extension, %(upload_date)s for the upload date
|
extension, %(upload_date)s for the upload date
|
||||||
(YYYYMMDD), %(extractor)s for the provider
|
(YYYYMMDD), %(extractor)s for the provider
|
||||||
(youtube, metacafe, etc), %(id)s for the video id
|
(youtube, metacafe, etc), %(id)s for the video id
|
||||||
and %% for a literal percent. Use - to output to
|
, %(playlist)s for the playlist the video is in,
|
||||||
stdout. Can also be used to download to a different
|
%(playlist_index)s for the position in the
|
||||||
directory, for example with -o '/my/downloads/%(upl
|
playlist and %% for a literal percent. Use - to
|
||||||
oader)s/%(title)s-%(id)s.%(ext)s' .
|
output to stdout. Can also be used to download to
|
||||||
--restrict-filenames Restrict filenames to only ASCII characters, and
|
a different directory, for example with -o '/my/d
|
||||||
avoid "&" and spaces in filenames
|
ownloads/%(uploader)s/%(title)s-%(id)s.%(ext)s' .
|
||||||
-a, --batch-file FILE file containing URLs to download ('-' for stdin)
|
--autonumber-size NUMBER Specifies the number of digits in %(autonumber)s
|
||||||
-w, --no-overwrites do not overwrite files
|
when it is present in output filename template or
|
||||||
-c, --continue resume partially downloaded files
|
--autonumber option is given
|
||||||
--no-continue do not resume partially downloaded files (restart
|
--restrict-filenames Restrict filenames to only ASCII characters, and
|
||||||
from beginning)
|
avoid "&" and spaces in filenames
|
||||||
--cookies FILE file to read cookies from and dump cookie jar in
|
-a, --batch-file FILE file containing URLs to download ('-' for stdin)
|
||||||
--no-part do not use .part files
|
-w, --no-overwrites do not overwrite files
|
||||||
--no-mtime do not use the Last-modified header to set the file
|
-c, --continue resume partially downloaded files
|
||||||
modification time
|
--no-continue do not resume partially downloaded files (restart
|
||||||
--write-description write video description to a .description file
|
from beginning)
|
||||||
--write-info-json write video metadata to a .info.json file
|
--cookies FILE file to read cookies from and dump cookie jar in
|
||||||
|
--no-part do not use .part files
|
||||||
|
--no-mtime do not use the Last-modified header to set the
|
||||||
|
file modification time
|
||||||
|
--write-description write video description to a .description file
|
||||||
|
--write-info-json write video metadata to a .info.json file
|
||||||
|
--write-thumbnail write thumbnail image to disk
|
||||||
|
|
||||||
## Verbosity / Simulation Options:
|
## Verbosity / Simulation Options:
|
||||||
-q, --quiet activates quiet mode
|
-q, --quiet activates quiet mode
|
||||||
-s, --simulate do not download the video and do not write anything
|
-s, --simulate do not download the video and do not write
|
||||||
to disk
|
anything to disk
|
||||||
--skip-download do not download the video
|
--skip-download do not download the video
|
||||||
-g, --get-url simulate, quiet but print URL
|
-g, --get-url simulate, quiet but print URL
|
||||||
-e, --get-title simulate, quiet but print title
|
-e, --get-title simulate, quiet but print title
|
||||||
--get-thumbnail simulate, quiet but print thumbnail URL
|
--get-thumbnail simulate, quiet but print thumbnail URL
|
||||||
--get-description simulate, quiet but print video description
|
--get-description simulate, quiet but print video description
|
||||||
--get-filename simulate, quiet but print output filename
|
--get-filename simulate, quiet but print output filename
|
||||||
--get-format simulate, quiet but print output format
|
--get-format simulate, quiet but print output format
|
||||||
--newline output progress bar as new lines
|
--newline output progress bar as new lines
|
||||||
--no-progress do not print progress bar
|
--no-progress do not print progress bar
|
||||||
--console-title display progress in console titlebar
|
--console-title display progress in console titlebar
|
||||||
-v, --verbose print various debugging information
|
-v, --verbose print various debugging information
|
||||||
|
--dump-intermediate-pages print downloaded pages to debug problems(very
|
||||||
|
verbose)
|
||||||
|
|
||||||
## Video Format Options:
|
## Video Format Options:
|
||||||
-f, --format FORMAT video format code
|
-f, --format FORMAT video format code, specifiy the order of
|
||||||
--all-formats download all available video formats
|
preference using slashes: "-f 22/17/18"
|
||||||
--prefer-free-formats prefer free video formats unless a specific one is
|
--all-formats download all available video formats
|
||||||
requested
|
--prefer-free-formats prefer free video formats unless a specific one
|
||||||
--max-quality FORMAT highest quality format to download
|
is requested
|
||||||
-F, --list-formats list all available formats (currently youtube only)
|
--max-quality FORMAT highest quality format to download
|
||||||
--write-sub write subtitle file (currently youtube only)
|
-F, --list-formats list all available formats (currently youtube
|
||||||
--only-sub downloads only the subtitles (no video)
|
only)
|
||||||
--all-subs downloads all the available subtitles of the video
|
--write-sub write subtitle file (currently youtube only)
|
||||||
(currently youtube only)
|
--only-sub downloads only the subtitles (no video)
|
||||||
--list-subs lists all available subtitles for the video
|
--all-subs downloads all the available subtitles of the
|
||||||
(currently youtube only)
|
video (currently youtube only)
|
||||||
--sub-format LANG subtitle format [srt/sbv] (default=srt) (currently
|
--list-subs lists all available subtitles for the video
|
||||||
youtube only)
|
(currently youtube only)
|
||||||
--sub-lang LANG language of the subtitles to download (optional)
|
--sub-format LANG subtitle format [srt/sbv] (default=srt)
|
||||||
use IETF language tags like 'en'
|
(currently youtube only)
|
||||||
|
--sub-lang LANG language of the subtitles to download (optional)
|
||||||
|
use IETF language tags like 'en'
|
||||||
|
|
||||||
## Authentication Options:
|
## Authentication Options:
|
||||||
-u, --username USERNAME account username
|
-u, --username USERNAME account username
|
||||||
-p, --password PASSWORD account password
|
-p, --password PASSWORD account password
|
||||||
-n, --netrc use .netrc authentication data
|
-n, --netrc use .netrc authentication data
|
||||||
|
|
||||||
## Post-processing Options:
|
## Post-processing Options:
|
||||||
-x, --extract-audio convert video files to audio-only files (requires
|
-x, --extract-audio convert video files to audio-only files (requires
|
||||||
ffmpeg or avconv and ffprobe or avprobe)
|
ffmpeg or avconv and ffprobe or avprobe)
|
||||||
--audio-format FORMAT "best", "aac", "vorbis", "mp3", "m4a", "opus", or
|
--audio-format FORMAT "best", "aac", "vorbis", "mp3", "m4a", "opus", or
|
||||||
"wav"; best by default
|
"wav"; best by default
|
||||||
--audio-quality QUALITY ffmpeg/avconv audio quality specification, insert a
|
--audio-quality QUALITY ffmpeg/avconv audio quality specification, insert
|
||||||
value between 0 (better) and 9 (worse) for VBR or a
|
a value between 0 (better) and 9 (worse) for VBR
|
||||||
specific bitrate like 128K (default 5)
|
or a specific bitrate like 128K (default 5)
|
||||||
--recode-video FORMAT Encode the video to another format if necessary
|
--recode-video FORMAT Encode the video to another format if necessary
|
||||||
(currently supported: mp4|flv|ogg|webm)
|
(currently supported: mp4|flv|ogg|webm)
|
||||||
-k, --keep-video keeps the video file on disk after the post-
|
-k, --keep-video keeps the video file on disk after the post-
|
||||||
processing; the video is erased by default
|
processing; the video is erased by default
|
||||||
--no-post-overwrites do not overwrite post-processed files; the post-
|
--no-post-overwrites do not overwrite post-processed files; the post-
|
||||||
processed files are overwritten by default
|
processed files are overwritten by default
|
||||||
|
|
||||||
# CONFIGURATION
|
# CONFIGURATION
|
||||||
|
|
||||||
@@ -144,6 +160,8 @@ The `-o` option allows users to indicate a template for the output file names. T
|
|||||||
- `ext`: The sequence will be replaced by the appropriate extension (like flv or mp4).
|
- `ext`: The sequence will be replaced by the appropriate extension (like flv or mp4).
|
||||||
- `epoch`: The sequence will be replaced by the Unix epoch when creating the file.
|
- `epoch`: The sequence will be replaced by the Unix epoch when creating the file.
|
||||||
- `autonumber`: The sequence will be replaced by a five-digit number that will be increased with each download, starting at zero.
|
- `autonumber`: The sequence will be replaced by a five-digit number that will be increased with each download, starting at zero.
|
||||||
|
- `playlist`: The name or the id of the playlist that contains the video.
|
||||||
|
- `playlist_index`: The index of the video in the playlist, a five-digit number.
|
||||||
|
|
||||||
The current default template is `%(id)s.%(ext)s`, but that will be switchted to `%(title)s-%(id)s.%(ext)s` (which can be requested with `-t` at the moment).
|
The current default template is `%(id)s.%(ext)s`, but that will be switchted to `%(title)s-%(id)s.%(ext)s` (which can be requested with `-t` at the moment).
|
||||||
|
|
||||||
@@ -154,6 +172,19 @@ In some cases, you don't want special characters such as 中, spaces, or &, such
|
|||||||
$ 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
|
||||||
|
|
||||||
|
# VIDEO SELECTION
|
||||||
|
|
||||||
|
Videos can be filtered by their upload date using the options `--date`, `--datebefore` or `--dateafter`, they accept dates in two formats:
|
||||||
|
|
||||||
|
- Absolute dates: Dates in the format `YYYYMMDD`.
|
||||||
|
- Relative dates: Dates in the format `(now|today)[+-][0-9](day|week|month|year)(s)?`
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
$ youtube-dl --dateafter now-6months #will only download the videos uploaded in the last 6 months
|
||||||
|
$ youtube-dl --date 19700101 #will only download the videos uploaded in January 1, 1970
|
||||||
|
$ youtube-dl --dateafter 20000101 --datebefore 20100101 #will only download the videos uploaded between 2000 and 2010
|
||||||
|
|
||||||
# FAQ
|
# FAQ
|
||||||
|
|
||||||
### Can you please put the -b option back?
|
### Can you please put the -b option back?
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ entry_template=textwrap.dedent("""
|
|||||||
<atom:entry>
|
<atom:entry>
|
||||||
<atom:id>youtube-dl-@VERSION@</atom:id>
|
<atom:id>youtube-dl-@VERSION@</atom:id>
|
||||||
<atom:title>New version @VERSION@</atom:title>
|
<atom:title>New version @VERSION@</atom:title>
|
||||||
<atom:link href="http://rg3.github.com/youtube-dl" />
|
<atom:link href="http://rg3.github.io/youtube-dl" />
|
||||||
<atom:content type="xhtml">
|
<atom:content type="xhtml">
|
||||||
<div xmlns="http://www.w3.org/1999/xhtml">
|
<div xmlns="http://www.w3.org/1999/xhtml">
|
||||||
Downloads available at <a href="http://youtube-dl.org/downloads/@VERSION@/">http://youtube-dl.org/downloads/@VERSION@/</a>
|
Downloads available at <a href="http://youtube-dl.org/downloads/@VERSION@/">http://youtube-dl.org/downloads/@VERSION@/</a>
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ raw_input()
|
|||||||
|
|
||||||
filename = sys.argv[0]
|
filename = sys.argv[0]
|
||||||
|
|
||||||
UPDATE_URL = "http://rg3.github.com/youtube-dl/update/"
|
UPDATE_URL = "http://rg3.github.io/youtube-dl/update/"
|
||||||
VERSION_URL = UPDATE_URL + 'LATEST_VERSION'
|
VERSION_URL = UPDATE_URL + 'LATEST_VERSION'
|
||||||
JSON_URL = UPDATE_URL + 'versions.json'
|
JSON_URL = UPDATE_URL + 'versions.json'
|
||||||
UPDATES_RSA_KEY = (0x9d60ee4d8f805312fdb15a62f87b95bd66177b91df176765d13514a0f1754bcd2057295c5b6f1d35daa6742c3ffc9a82d3e118861c207995a8031e151d863c9927e304576bc80692bc8e094896fcf11b66f3e29e04e3a71e9a11558558acea1840aec37fc396fb6b65dc81a1c4144e03bd1c011de62e3f1357b327d08426fe93, 65537)
|
UPDATES_RSA_KEY = (0x9d60ee4d8f805312fdb15a62f87b95bd66177b91df176765d13514a0f1754bcd2057295c5b6f1d35daa6742c3ffc9a82d3e118861c207995a8031e151d863c9927e304576bc80692bc8e094896fcf11b66f3e29e04e3a71e9a11558558acea1840aec37fc396fb6b65dc81a1c4144e03bd1c011de62e3f1357b327d08426fe93, 65537)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import unittest
|
|||||||
import os
|
import os
|
||||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
from youtube_dl.InfoExtractors import YoutubeIE, YoutubePlaylistIE
|
from youtube_dl.InfoExtractors import YoutubeIE, YoutubePlaylistIE, YoutubeChannelIE
|
||||||
|
|
||||||
class TestAllURLsMatching(unittest.TestCase):
|
class TestAllURLsMatching(unittest.TestCase):
|
||||||
def test_youtube_playlist_matching(self):
|
def test_youtube_playlist_matching(self):
|
||||||
@@ -24,6 +24,11 @@ class TestAllURLsMatching(unittest.TestCase):
|
|||||||
self.assertTrue(YoutubeIE.suitable(u'PLtS2H6bU1M'))
|
self.assertTrue(YoutubeIE.suitable(u'PLtS2H6bU1M'))
|
||||||
self.assertFalse(YoutubeIE.suitable(u'https://www.youtube.com/watch?v=AV6J6_AeFEQ&playnext=1&list=PL4023E734DA416012')) #668
|
self.assertFalse(YoutubeIE.suitable(u'https://www.youtube.com/watch?v=AV6J6_AeFEQ&playnext=1&list=PL4023E734DA416012')) #668
|
||||||
|
|
||||||
|
def test_youtube_channel_matching(self):
|
||||||
|
self.assertTrue(YoutubeChannelIE.suitable('https://www.youtube.com/channel/HCtnHdj3df7iM'))
|
||||||
|
self.assertTrue(YoutubeChannelIE.suitable('https://www.youtube.com/channel/HCtnHdj3df7iM?feature=gb_ch_rec'))
|
||||||
|
self.assertTrue(YoutubeChannelIE.suitable('https://www.youtube.com/channel/HCtnHdj3df7iM/videos'))
|
||||||
|
|
||||||
def test_youtube_extract(self):
|
def test_youtube_extract(self):
|
||||||
self.assertEqual(YoutubeIE()._extract_id('http://www.youtube.com/watch?&v=BaW_jenozKc'), 'BaW_jenozKc')
|
self.assertEqual(YoutubeIE()._extract_id('http://www.youtube.com/watch?&v=BaW_jenozKc'), 'BaW_jenozKc')
|
||||||
self.assertEqual(YoutubeIE()._extract_id('https://www.youtube.com/watch?&v=BaW_jenozKc'), 'BaW_jenozKc')
|
self.assertEqual(YoutubeIE()._extract_id('https://www.youtube.com/watch?&v=BaW_jenozKc'), 'BaW_jenozKc')
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ with io.open(PARAMETERS_FILE, encoding='utf-8') as pf:
|
|||||||
|
|
||||||
|
|
||||||
class TestDownload(unittest.TestCase):
|
class TestDownload(unittest.TestCase):
|
||||||
|
maxDiff = None
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.parameters = parameters
|
self.parameters = parameters
|
||||||
self.defs = defs
|
self.defs = defs
|
||||||
@@ -66,7 +67,7 @@ class TestDownload(unittest.TestCase):
|
|||||||
def generator(test_case):
|
def generator(test_case):
|
||||||
|
|
||||||
def test_template(self):
|
def test_template(self):
|
||||||
ie = getattr(youtube_dl.InfoExtractors, test_case['name'] + 'IE')
|
ie = youtube_dl.InfoExtractors.get_info_extractor(test_case['name'])
|
||||||
if not ie._WORKING:
|
if not ie._WORKING:
|
||||||
print('Skipping: IE marked as not _WORKING')
|
print('Skipping: IE marked as not _WORKING')
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ from youtube_dl.utils import timeconvert
|
|||||||
from youtube_dl.utils import sanitize_filename
|
from youtube_dl.utils import sanitize_filename
|
||||||
from youtube_dl.utils import unescapeHTML
|
from youtube_dl.utils import unescapeHTML
|
||||||
from youtube_dl.utils import orderedSet
|
from youtube_dl.utils import orderedSet
|
||||||
|
from youtube_dl.utils import DateRange
|
||||||
|
from youtube_dl.utils import unified_strdate
|
||||||
|
|
||||||
if sys.version_info < (3, 0):
|
if sys.version_info < (3, 0):
|
||||||
_compat_str = lambda b: b.decode('unicode-escape')
|
_compat_str = lambda b: b.decode('unicode-escape')
|
||||||
@@ -95,6 +97,20 @@ class TestUtil(unittest.TestCase):
|
|||||||
|
|
||||||
def test_unescape_html(self):
|
def test_unescape_html(self):
|
||||||
self.assertEqual(unescapeHTML(_compat_str('%20;')), _compat_str('%20;'))
|
self.assertEqual(unescapeHTML(_compat_str('%20;')), _compat_str('%20;'))
|
||||||
|
|
||||||
|
def test_daterange(self):
|
||||||
|
_20century = DateRange("19000101","20000101")
|
||||||
|
self.assertFalse("17890714" in _20century)
|
||||||
|
_ac = DateRange("00010101")
|
||||||
|
self.assertTrue("19690721" in _ac)
|
||||||
|
_firstmilenium = DateRange(end="10000101")
|
||||||
|
self.assertTrue("07110427" in _firstmilenium)
|
||||||
|
|
||||||
|
def test_unified_dates(self):
|
||||||
|
self.assertEqual(unified_strdate('December 21, 2010'), '20101221')
|
||||||
|
self.assertEqual(unified_strdate('8/7/2009'), '20090708')
|
||||||
|
self.assertEqual(unified_strdate('Dec 14, 2012'), '20121214')
|
||||||
|
self.assertEqual(unified_strdate('2012/10/11 01:56:38 +0000'), '20121011')
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
@@ -8,8 +8,9 @@ import json
|
|||||||
import os
|
import os
|
||||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
from youtube_dl.InfoExtractors import YoutubeUserIE, YoutubePlaylistIE, YoutubeIE
|
from youtube_dl.InfoExtractors import YoutubeUserIE, YoutubePlaylistIE, YoutubeIE, YoutubeChannelIE
|
||||||
from youtube_dl.utils import *
|
from youtube_dl.utils import *
|
||||||
|
from youtube_dl.FileDownloader import FileDownloader
|
||||||
|
|
||||||
PARAMETERS_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "parameters.json")
|
PARAMETERS_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "parameters.json")
|
||||||
with io.open(PARAMETERS_FILE, encoding='utf-8') as pf:
|
with io.open(PARAMETERS_FILE, encoding='utf-8') as pf:
|
||||||
@@ -22,64 +23,87 @@ proxy_handler = compat_urllib_request.ProxyHandler()
|
|||||||
opener = compat_urllib_request.build_opener(proxy_handler, cookie_processor, YoutubeDLHandler())
|
opener = compat_urllib_request.build_opener(proxy_handler, cookie_processor, YoutubeDLHandler())
|
||||||
compat_urllib_request.install_opener(opener)
|
compat_urllib_request.install_opener(opener)
|
||||||
|
|
||||||
class FakeDownloader(object):
|
class FakeDownloader(FileDownloader):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.result = []
|
self.result = []
|
||||||
self.params = parameters
|
self.params = parameters
|
||||||
def to_screen(self, s):
|
def to_screen(self, s):
|
||||||
print(s)
|
print(s)
|
||||||
def trouble(self, s):
|
def trouble(self, s, tb=None):
|
||||||
raise Exception(s)
|
raise Exception(s)
|
||||||
def download(self, x):
|
def extract_info(self, url):
|
||||||
self.result.append(x)
|
self.result.append(url)
|
||||||
|
return url
|
||||||
|
|
||||||
class TestYoutubeLists(unittest.TestCase):
|
class TestYoutubeLists(unittest.TestCase):
|
||||||
|
def assertIsPlaylist(self,info):
|
||||||
|
"""Make sure the info has '_type' set to 'playlist'"""
|
||||||
|
self.assertEqual(info['_type'], 'playlist')
|
||||||
|
|
||||||
def test_youtube_playlist(self):
|
def test_youtube_playlist(self):
|
||||||
dl = FakeDownloader()
|
dl = FakeDownloader()
|
||||||
ie = YoutubePlaylistIE(dl)
|
ie = YoutubePlaylistIE(dl)
|
||||||
ie.extract('https://www.youtube.com/playlist?list=PLwiyx1dc3P2JR9N8gQaQN_BCvlSlap7re')
|
result = ie.extract('https://www.youtube.com/playlist?list=PLwiyx1dc3P2JR9N8gQaQN_BCvlSlap7re')[0]
|
||||||
ytie_results = [YoutubeIE()._extract_id(r[0]) for r in dl.result]
|
self.assertIsPlaylist(result)
|
||||||
|
self.assertEqual(result['title'], 'ytdl test PL')
|
||||||
|
ytie_results = [YoutubeIE()._extract_id(url['url']) for url in result['entries']]
|
||||||
self.assertEqual(ytie_results, [ 'bV9L5Ht9LgY', 'FXxLjLQi3Fg', 'tU3Bgo5qJZE'])
|
self.assertEqual(ytie_results, [ 'bV9L5Ht9LgY', 'FXxLjLQi3Fg', 'tU3Bgo5qJZE'])
|
||||||
|
|
||||||
def test_issue_673(self):
|
def test_issue_673(self):
|
||||||
dl = FakeDownloader()
|
dl = FakeDownloader()
|
||||||
ie = YoutubePlaylistIE(dl)
|
ie = YoutubePlaylistIE(dl)
|
||||||
ie.extract('PLBB231211A4F62143')
|
result = ie.extract('PLBB231211A4F62143')[0]
|
||||||
self.assertTrue(len(dl.result) > 40)
|
self.assertEqual(result['title'], 'Team Fortress 2')
|
||||||
|
self.assertTrue(len(result['entries']) > 40)
|
||||||
|
|
||||||
def test_youtube_playlist_long(self):
|
def test_youtube_playlist_long(self):
|
||||||
dl = FakeDownloader()
|
dl = FakeDownloader()
|
||||||
ie = YoutubePlaylistIE(dl)
|
ie = YoutubePlaylistIE(dl)
|
||||||
ie.extract('https://www.youtube.com/playlist?list=UUBABnxM4Ar9ten8Mdjj1j0Q')
|
result = ie.extract('https://www.youtube.com/playlist?list=UUBABnxM4Ar9ten8Mdjj1j0Q')[0]
|
||||||
self.assertTrue(len(dl.result) >= 799)
|
self.assertIsPlaylist(result)
|
||||||
|
self.assertTrue(len(result['entries']) >= 799)
|
||||||
|
|
||||||
def test_youtube_playlist_with_deleted(self):
|
def test_youtube_playlist_with_deleted(self):
|
||||||
#651
|
#651
|
||||||
dl = FakeDownloader()
|
dl = FakeDownloader()
|
||||||
ie = YoutubePlaylistIE(dl)
|
ie = YoutubePlaylistIE(dl)
|
||||||
ie.extract('https://www.youtube.com/playlist?list=PLwP_SiAcdui0KVebT0mU9Apz359a4ubsC')
|
result = ie.extract('https://www.youtube.com/playlist?list=PLwP_SiAcdui0KVebT0mU9Apz359a4ubsC')[0]
|
||||||
ytie_results = [YoutubeIE()._extract_id(r[0]) for r in dl.result]
|
ytie_results = [YoutubeIE()._extract_id(url['url']) for url in result['entries']]
|
||||||
self.assertFalse('pElCt5oNDuI' in ytie_results)
|
self.assertFalse('pElCt5oNDuI' in ytie_results)
|
||||||
self.assertFalse('KdPEApIVdWM' in ytie_results)
|
self.assertFalse('KdPEApIVdWM' in ytie_results)
|
||||||
|
|
||||||
|
def test_youtube_playlist_empty(self):
|
||||||
|
dl = FakeDownloader()
|
||||||
|
ie = YoutubePlaylistIE(dl)
|
||||||
|
result = ie.extract('https://www.youtube.com/playlist?list=PLtPgu7CB4gbZDA7i_euNxn75ISqxwZPYx')[0]
|
||||||
|
self.assertIsPlaylist(result)
|
||||||
|
self.assertEqual(len(result['entries']), 0)
|
||||||
|
|
||||||
def test_youtube_course(self):
|
def test_youtube_course(self):
|
||||||
dl = FakeDownloader()
|
dl = FakeDownloader()
|
||||||
ie = YoutubePlaylistIE(dl)
|
ie = YoutubePlaylistIE(dl)
|
||||||
# TODO find a > 100 (paginating?) videos course
|
# TODO find a > 100 (paginating?) videos course
|
||||||
ie.extract('https://www.youtube.com/course?list=ECUl4u3cNGP61MdtwGTqZA0MreSaDybji8')
|
result = ie.extract('https://www.youtube.com/course?list=ECUl4u3cNGP61MdtwGTqZA0MreSaDybji8')[0]
|
||||||
self.assertEqual(YoutubeIE()._extract_id(dl.result[0][0]), 'j9WZyLZCBzs')
|
entries = result['entries']
|
||||||
self.assertEqual(len(dl.result), 25)
|
self.assertEqual(YoutubeIE()._extract_id(entries[0]['url']), 'j9WZyLZCBzs')
|
||||||
self.assertEqual(YoutubeIE()._extract_id(dl.result[-1][0]), 'rYefUsYuEp0')
|
self.assertEqual(len(entries), 25)
|
||||||
|
self.assertEqual(YoutubeIE()._extract_id(entries[-1]['url']), 'rYefUsYuEp0')
|
||||||
|
|
||||||
def test_youtube_channel(self):
|
def test_youtube_channel(self):
|
||||||
# I give up, please find a channel that does paginate and test this like test_youtube_playlist_long
|
dl = FakeDownloader()
|
||||||
pass # TODO
|
ie = YoutubeChannelIE(dl)
|
||||||
|
#test paginated channel
|
||||||
|
result = ie.extract('https://www.youtube.com/channel/UCKfVa3S1e4PHvxWcwyMMg8w')[0]
|
||||||
|
self.assertTrue(len(result['entries']) > 90)
|
||||||
|
#test autogenerated channel
|
||||||
|
result = ie.extract('https://www.youtube.com/channel/HCtnHdj3df7iM/videos')[0]
|
||||||
|
self.assertTrue(len(result['entries']) >= 18)
|
||||||
|
|
||||||
def test_youtube_user(self):
|
def test_youtube_user(self):
|
||||||
dl = FakeDownloader()
|
dl = FakeDownloader()
|
||||||
ie = YoutubeUserIE(dl)
|
ie = YoutubeUserIE(dl)
|
||||||
ie.extract('https://www.youtube.com/user/TheLinuxFoundation')
|
result = ie.extract('https://www.youtube.com/user/TheLinuxFoundation')[0]
|
||||||
self.assertTrue(len(dl.result) >= 320)
|
self.assertTrue(len(result['entries']) >= 320)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class FakeDownloader(object):
|
|||||||
self.params = parameters
|
self.params = parameters
|
||||||
def to_screen(self, s):
|
def to_screen(self, s):
|
||||||
print(s)
|
print(s)
|
||||||
def trouble(self, s):
|
def trouble(self, s, tb=None):
|
||||||
raise Exception(s)
|
raise Exception(s)
|
||||||
def download(self, x):
|
def download(self, x):
|
||||||
self.result.append(x)
|
self.result.append(x)
|
||||||
@@ -80,7 +80,7 @@ class TestYoutubeSubtitles(unittest.TestCase):
|
|||||||
IE = YoutubeIE(DL)
|
IE = YoutubeIE(DL)
|
||||||
info_dict = IE.extract('QRS8MkLhQmM')
|
info_dict = IE.extract('QRS8MkLhQmM')
|
||||||
subtitles = info_dict[0]['subtitles']
|
subtitles = info_dict[0]['subtitles']
|
||||||
self.assertEqual(len(subtitles), 12)
|
self.assertEqual(len(subtitles), 13)
|
||||||
def test_youtube_subtitles_format(self):
|
def test_youtube_subtitles_format(self):
|
||||||
DL = FakeDownloader()
|
DL = FakeDownloader()
|
||||||
DL.params['writesubtitles'] = True
|
DL.params['writesubtitles'] = True
|
||||||
|
|||||||
@@ -76,8 +76,7 @@
|
|||||||
"name": "StanfordOpenClassroom",
|
"name": "StanfordOpenClassroom",
|
||||||
"md5": "544a9468546059d4e80d76265b0443b8",
|
"md5": "544a9468546059d4e80d76265b0443b8",
|
||||||
"url": "http://openclassroom.stanford.edu/MainFolder/VideoPage.php?course=PracticalUnix&video=intro-environment&speed=100",
|
"url": "http://openclassroom.stanford.edu/MainFolder/VideoPage.php?course=PracticalUnix&video=intro-environment&speed=100",
|
||||||
"file": "PracticalUnix_intro-environment.mp4",
|
"file": "PracticalUnix_intro-environment.mp4"
|
||||||
"skip": "Currently offline"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "XNXX",
|
"name": "XNXX",
|
||||||
@@ -113,7 +112,7 @@
|
|||||||
{
|
{
|
||||||
"name": "Escapist",
|
"name": "Escapist",
|
||||||
"url": "http://www.escapistmagazine.com/videos/view/the-escapist-presents/6618-Breaking-Down-Baldurs-Gate",
|
"url": "http://www.escapistmagazine.com/videos/view/the-escapist-presents/6618-Breaking-Down-Baldurs-Gate",
|
||||||
"file": "6618-Breaking-Down-Baldurs-Gate.flv",
|
"file": "6618-Breaking-Down-Baldurs-Gate.mp4",
|
||||||
"md5": "c6793dbda81388f4264c1ba18684a74d",
|
"md5": "c6793dbda81388f4264c1ba18684a74d",
|
||||||
"skip": "Fails with timeout on Travis"
|
"skip": "Fails with timeout on Travis"
|
||||||
},
|
},
|
||||||
@@ -328,5 +327,90 @@
|
|||||||
"info_dict": {
|
"info_dict": {
|
||||||
"title": "Video: KO Of The Week: MMA Fighter Gets Knocked Out By Swift Head Kick! "
|
"title": "Video: KO Of The Week: MMA Fighter Gets Knocked Out By Swift Head Kick! "
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ARD",
|
||||||
|
"url": "http://www.ardmediathek.de/das-erste/tagesschau-in-100-sek?documentId=14077640",
|
||||||
|
"file": "14077640.mp4",
|
||||||
|
"md5": "6ca8824255460c787376353f9e20bbd8",
|
||||||
|
"info_dict": {
|
||||||
|
"title": "11.04.2013 09:23 Uhr - Tagesschau in 100 Sekunden"
|
||||||
|
},
|
||||||
|
"skip": "Requires rtmpdump"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Tumblr",
|
||||||
|
"url": "http://birthdayproject2012.tumblr.com/post/17258355236/a-sample-video-from-leeann-if-you-need-an-idea",
|
||||||
|
"file": "17258355236.mp4",
|
||||||
|
"md5": "7c6a514d691b034ccf8567999e9e88a3",
|
||||||
|
"info_dict": {
|
||||||
|
"title": "Calling all Pris! - A sample video from LeeAnn. (If you need an idea..."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "SoundcloudSet",
|
||||||
|
"url":"https://soundcloud.com/the-concept-band/sets/the-royal-concept-ep",
|
||||||
|
"playlist":[
|
||||||
|
{
|
||||||
|
"file":"30510138.mp3",
|
||||||
|
"md5":"f9136bf103901728f29e419d2c70f55d",
|
||||||
|
"info_dict": {
|
||||||
|
"title":"D-D-Dance"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file":"47127625.mp3",
|
||||||
|
"md5":"09b6758a018470570f8fd423c9453dd8",
|
||||||
|
"info_dict": {
|
||||||
|
"title":"The Royal Concept - Gimme Twice"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file":"47127627.mp3",
|
||||||
|
"md5":"154abd4e418cea19c3b901f1e1306d9c",
|
||||||
|
"info_dict": {
|
||||||
|
"title":"Goldrushed"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file":"47127629.mp3",
|
||||||
|
"md5":"2f5471edc79ad3f33a683153e96a79c1",
|
||||||
|
"info_dict": {
|
||||||
|
"title":"In the End"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file":"47127631.mp3",
|
||||||
|
"md5":"f9ba87aa940af7213f98949254f1c6e2",
|
||||||
|
"info_dict": {
|
||||||
|
"title":"Knocked Up"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file":"75206121.mp3",
|
||||||
|
"md5":"f9d1fe9406717e302980c30de4af9353",
|
||||||
|
"info_dict": {
|
||||||
|
"title":"World On Fire"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"Bandcamp",
|
||||||
|
"url":"http://youtube-dl.bandcamp.com/track/youtube-dl-test-song",
|
||||||
|
"file":"1812978515.mp3",
|
||||||
|
"md5":"cdeb30cdae1921719a3cbcab696ef53c",
|
||||||
|
"info_dict": {
|
||||||
|
"title":"youtube-dl test song \"'/\\ä↭"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "RedTube",
|
||||||
|
"url": "http://www.redtube.com/66418",
|
||||||
|
"file": "66418.mp4",
|
||||||
|
"md5": "7b8c22b5e7098a3e1c09709df1126d2d",
|
||||||
|
"info_dict":{
|
||||||
|
"title":"Sucked on a toilet"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ def rsa_verify(message, signature, key):
|
|||||||
|
|
||||||
sys.stderr.write(u'Hi! We changed distribution method and now youtube-dl needs to update itself one more time.\n')
|
sys.stderr.write(u'Hi! We changed distribution method and now youtube-dl needs to update itself one more time.\n')
|
||||||
sys.stderr.write(u'This will only happen once. Simply press enter to go on. Sorry for the trouble!\n')
|
sys.stderr.write(u'This will only happen once. Simply press enter to go on. Sorry for the trouble!\n')
|
||||||
sys.stderr.write(u'From now on, get the binaries from http://rg3.github.com/youtube-dl/download.html, not from the git repository.\n\n')
|
sys.stderr.write(u'From now on, get the binaries from http://rg3.github.io/youtube-dl/download.html, not from the git repository.\n\n')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
raw_input()
|
raw_input()
|
||||||
@@ -47,7 +47,7 @@ except NameError: # Python 3
|
|||||||
|
|
||||||
filename = sys.argv[0]
|
filename = sys.argv[0]
|
||||||
|
|
||||||
UPDATE_URL = "http://rg3.github.com/youtube-dl/update/"
|
UPDATE_URL = "http://rg3.github.io/youtube-dl/update/"
|
||||||
VERSION_URL = UPDATE_URL + 'LATEST_VERSION'
|
VERSION_URL = UPDATE_URL + 'LATEST_VERSION'
|
||||||
JSON_URL = UPDATE_URL + 'versions.json'
|
JSON_URL = UPDATE_URL + 'versions.json'
|
||||||
UPDATES_RSA_KEY = (0x9d60ee4d8f805312fdb15a62f87b95bd66177b91df176765d13514a0f1754bcd2057295c5b6f1d35daa6742c3ffc9a82d3e118861c207995a8031e151d863c9927e304576bc80692bc8e094896fcf11b66f3e29e04e3a71e9a11558558acea1840aec37fc396fb6b65dc81a1c4144e03bd1c011de62e3f1357b327d08426fe93, 65537)
|
UPDATES_RSA_KEY = (0x9d60ee4d8f805312fdb15a62f87b95bd66177b91df176765d13514a0f1754bcd2057295c5b6f1d35daa6742c3ffc9a82d3e118861c207995a8031e151d863c9927e304576bc80692bc8e094896fcf11b66f3e29e04e3a71e9a11558558acea1840aec37fc396fb6b65dc81a1c4144e03bd1c011de62e3f1357b327d08426fe93, 65537)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import math
|
|||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import shutil
|
||||||
import socket
|
import socket
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
@@ -17,6 +18,7 @@ if os.name == 'nt':
|
|||||||
import ctypes
|
import ctypes
|
||||||
|
|
||||||
from .utils import *
|
from .utils import *
|
||||||
|
from .InfoExtractors import get_info_extractor
|
||||||
|
|
||||||
|
|
||||||
class FileDownloader(object):
|
class FileDownloader(object):
|
||||||
@@ -78,6 +80,7 @@ class FileDownloader(object):
|
|||||||
updatetime: Use the Last-modified header to set output file timestamps.
|
updatetime: Use the Last-modified header to set output file timestamps.
|
||||||
writedescription: Write the video description to a .description file
|
writedescription: Write the video description to a .description file
|
||||||
writeinfojson: Write the video description to a .info.json file
|
writeinfojson: Write the video description to a .info.json file
|
||||||
|
writethumbnail: Write the thumbnail image to a file
|
||||||
writesubtitles: Write the video subtitles to a file
|
writesubtitles: Write the video subtitles to a file
|
||||||
onlysubtitles: Downloads only the subtitles of the video
|
onlysubtitles: Downloads only the subtitles of the video
|
||||||
allsubtitles: Downloads all the subtitles of the video
|
allsubtitles: Downloads all the subtitles of the video
|
||||||
@@ -88,6 +91,7 @@ class FileDownloader(object):
|
|||||||
keepvideo: Keep the video file after post-processing
|
keepvideo: Keep the video file after post-processing
|
||||||
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
|
||||||
|
daterange: A DateRange object, download only if the upload_date is in the range.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
params = None
|
params = None
|
||||||
@@ -120,7 +124,7 @@ class FileDownloader(object):
|
|||||||
exponent = 0
|
exponent = 0
|
||||||
else:
|
else:
|
||||||
exponent = int(math.log(bytes, 1024.0))
|
exponent = int(math.log(bytes, 1024.0))
|
||||||
suffix = 'bkMGTPEZY'[exponent]
|
suffix = ['B','KiB','MiB','GiB','TiB','PiB','EiB','ZiB','YiB'][exponent]
|
||||||
converted = float(bytes) / float(1024 ** exponent)
|
converted = float(bytes) / float(1024 ** exponent)
|
||||||
return '%.2f%s' % (converted, suffix)
|
return '%.2f%s' % (converted, suffix)
|
||||||
|
|
||||||
@@ -253,7 +257,7 @@ class FileDownloader(object):
|
|||||||
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():
|
if sys.stderr.isatty() and os.name != 'nt':
|
||||||
_msg_header=u'\033[0;33mWARNING:\033[0m'
|
_msg_header=u'\033[0;33mWARNING:\033[0m'
|
||||||
else:
|
else:
|
||||||
_msg_header=u'WARNING:'
|
_msg_header=u'WARNING:'
|
||||||
@@ -265,7 +269,7 @@ class FileDownloader(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 sys.stderr.isatty():
|
if sys.stderr.isatty() and os.name != 'nt':
|
||||||
_msg_header = u'\033[0;31mERROR:\033[0m'
|
_msg_header = u'\033[0;31mERROR:\033[0m'
|
||||||
else:
|
else:
|
||||||
_msg_header = u'ERROR:'
|
_msg_header = u'ERROR:'
|
||||||
@@ -343,12 +347,13 @@ class FileDownloader(object):
|
|||||||
"""Report download progress."""
|
"""Report download progress."""
|
||||||
if self.params.get('noprogress', False):
|
if self.params.get('noprogress', False):
|
||||||
return
|
return
|
||||||
|
clear_line = (u'\x1b[K' if sys.stderr.isatty() and os.name != 'nt' else u'')
|
||||||
if self.params.get('progress_with_newline', False):
|
if self.params.get('progress_with_newline', False):
|
||||||
self.to_screen(u'[download] %s of %s at %s ETA %s' %
|
self.to_screen(u'[download] %s of %s at %s ETA %s' %
|
||||||
(percent_str, data_len_str, speed_str, eta_str))
|
(percent_str, data_len_str, speed_str, eta_str))
|
||||||
else:
|
else:
|
||||||
self.to_screen(u'\r[download] %s of %s at %s ETA %s' %
|
self.to_screen(u'\r%s[download] %s of %s at %s ETA %s' %
|
||||||
(percent_str, data_len_str, speed_str, eta_str), skip_eol=True)
|
(clear_line, percent_str, data_len_str, speed_str, eta_str), skip_eol=True)
|
||||||
self.to_cons_title(u'youtube-dl - %s of %s at %s ETA %s' %
|
self.to_cons_title(u'youtube-dl - %s of %s at %s ETA %s' %
|
||||||
(percent_str.strip(), data_len_str.strip(), speed_str.strip(), eta_str.strip()))
|
(percent_str.strip(), data_len_str.strip(), speed_str.strip(), eta_str.strip()))
|
||||||
|
|
||||||
@@ -388,7 +393,13 @@ class FileDownloader(object):
|
|||||||
template_dict = dict(info_dict)
|
template_dict = dict(info_dict)
|
||||||
|
|
||||||
template_dict['epoch'] = int(time.time())
|
template_dict['epoch'] = int(time.time())
|
||||||
template_dict['autonumber'] = u'%05d' % self._num_downloads
|
autonumber_size = self.params.get('autonumber_size')
|
||||||
|
if autonumber_size is None:
|
||||||
|
autonumber_size = 5
|
||||||
|
autonumber_templ = u'%0' + str(autonumber_size) + u'd'
|
||||||
|
template_dict['autonumber'] = autonumber_templ % self._num_downloads
|
||||||
|
if template_dict['playlist_index'] is not None:
|
||||||
|
template_dict['playlist_index'] = u'%05d' % template_dict['playlist_index']
|
||||||
|
|
||||||
sanitize = lambda k,v: sanitize_filename(
|
sanitize = lambda k,v: sanitize_filename(
|
||||||
u'NA' if v is None else compat_str(v),
|
u'NA' if v is None else compat_str(v),
|
||||||
@@ -399,10 +410,10 @@ class FileDownloader(object):
|
|||||||
filename = self.params['outtmpl'] % template_dict
|
filename = self.params['outtmpl'] % template_dict
|
||||||
return filename
|
return filename
|
||||||
except KeyError as err:
|
except KeyError as err:
|
||||||
self.trouble(u'ERROR: Erroneous output template')
|
self.report_error(u'Erroneous output template')
|
||||||
return None
|
return None
|
||||||
except ValueError as err:
|
except ValueError as err:
|
||||||
self.trouble(u'ERROR: Insufficient system charset ' + repr(preferredencoding()))
|
self.report_error(u'Insufficient system charset ' + repr(preferredencoding()))
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _match_entry(self, info_dict):
|
def _match_entry(self, info_dict):
|
||||||
@@ -417,11 +428,132 @@ class FileDownloader(object):
|
|||||||
if rejecttitle:
|
if rejecttitle:
|
||||||
if re.search(rejecttitle, title, re.IGNORECASE):
|
if re.search(rejecttitle, title, re.IGNORECASE):
|
||||||
return u'"' + title + '" title matched reject pattern "' + rejecttitle + '"'
|
return u'"' + title + '" title matched reject pattern "' + rejecttitle + '"'
|
||||||
|
date = info_dict.get('upload_date', None)
|
||||||
|
if date is not None:
|
||||||
|
dateRange = self.params.get('daterange', DateRange())
|
||||||
|
if date not in dateRange:
|
||||||
|
return u'[download] %s upload date is not in range %s' % (date_from_str(date).isoformat(), dateRange)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def extract_info(self, url, download = True, ie_name = None):
|
||||||
|
'''
|
||||||
|
Returns a list with a dictionary for each video we find.
|
||||||
|
If 'download', also downloads the videos.
|
||||||
|
'''
|
||||||
|
suitable_found = False
|
||||||
|
|
||||||
|
#We copy the original list
|
||||||
|
ies = list(self._ies)
|
||||||
|
|
||||||
|
if ie_name is not None:
|
||||||
|
#We put in the first place the given info extractor
|
||||||
|
first_ie = get_info_extractor(ie_name)()
|
||||||
|
first_ie.set_downloader(self)
|
||||||
|
ies.insert(0, first_ie)
|
||||||
|
|
||||||
|
for ie in ies:
|
||||||
|
# Go to next InfoExtractor if not suitable
|
||||||
|
if not ie.suitable(url):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Warn if the _WORKING attribute is False
|
||||||
|
if not ie.working():
|
||||||
|
self.report_warning(u'the program functionality for this site has been marked as broken, '
|
||||||
|
u'and will probably not work. If you want to go on, use the -i option.')
|
||||||
|
|
||||||
|
# Suitable InfoExtractor found
|
||||||
|
suitable_found = True
|
||||||
|
|
||||||
|
# Extract information from URL and process it
|
||||||
|
try:
|
||||||
|
ie_results = ie.extract(url)
|
||||||
|
if ie_results is None: # Finished already (backwards compatibility; listformats and friends should be moved here)
|
||||||
|
break
|
||||||
|
results = []
|
||||||
|
for ie_result in ie_results:
|
||||||
|
if not 'extractor' in ie_result:
|
||||||
|
#The extractor has already been set somewhere else
|
||||||
|
ie_result['extractor'] = ie.IE_NAME
|
||||||
|
results.append(self.process_ie_result(ie_result, download))
|
||||||
|
return results
|
||||||
|
except ExtractorError as de: # An error we somewhat expected
|
||||||
|
self.report_error(compat_str(de), de.format_traceback())
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
if self.params.get('ignoreerrors', False):
|
||||||
|
self.report_error(compat_str(e), tb=compat_str(traceback.format_exc()))
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
if not suitable_found:
|
||||||
|
self.report_error(u'no suitable InfoExtractor: %s' % url)
|
||||||
|
|
||||||
|
def process_ie_result(self, ie_result, download = True):
|
||||||
|
"""
|
||||||
|
Take the result of the ie and return a list of videos.
|
||||||
|
For url elements it will search the suitable ie and get the videos
|
||||||
|
For playlist elements it will process each of the elements of the 'entries' key
|
||||||
|
|
||||||
|
It will also download the videos if 'download'.
|
||||||
|
"""
|
||||||
|
result_type = ie_result.get('_type', 'video') #If not given we suppose it's a video, support the dafault old system
|
||||||
|
if result_type == 'video':
|
||||||
|
if 'playlist' not in ie_result:
|
||||||
|
#It isn't part of a playlist
|
||||||
|
ie_result['playlist'] = None
|
||||||
|
ie_result['playlist_index'] = None
|
||||||
|
if download:
|
||||||
|
#Do the download:
|
||||||
|
self.process_info(ie_result)
|
||||||
|
return ie_result
|
||||||
|
elif result_type == 'url':
|
||||||
|
#We get the video pointed by the url
|
||||||
|
result = self.extract_info(ie_result['url'], download, ie_name = ie_result['ie_key'])[0]
|
||||||
|
return result
|
||||||
|
elif result_type == 'playlist':
|
||||||
|
#We process each entry in the playlist
|
||||||
|
playlist = ie_result.get('title', None) or ie_result.get('id', None)
|
||||||
|
self.to_screen(u'[download] Downloading playlist: %s' % playlist)
|
||||||
|
|
||||||
|
playlist_results = []
|
||||||
|
|
||||||
|
n_all_entries = len(ie_result['entries'])
|
||||||
|
playliststart = self.params.get('playliststart', 1) - 1
|
||||||
|
playlistend = self.params.get('playlistend', -1)
|
||||||
|
|
||||||
|
if playlistend == -1:
|
||||||
|
entries = ie_result['entries'][playliststart:]
|
||||||
|
else:
|
||||||
|
entries = ie_result['entries'][playliststart:playlistend]
|
||||||
|
|
||||||
|
n_entries = len(entries)
|
||||||
|
|
||||||
|
self.to_screen(u"[%s] playlist '%s': Collected %d video ids (downloading %d of them)" %
|
||||||
|
(ie_result['extractor'], playlist, n_all_entries, n_entries))
|
||||||
|
|
||||||
|
for i,entry in enumerate(entries,1):
|
||||||
|
self.to_screen(u'[download] Downloading video #%s of %s' %(i, n_entries))
|
||||||
|
entry_result = self.process_ie_result(entry, False)
|
||||||
|
entry_result['playlist'] = playlist
|
||||||
|
entry_result['playlist_index'] = i + playliststart
|
||||||
|
#We must do the download here to correctly set the 'playlist' key
|
||||||
|
if download:
|
||||||
|
self.process_info(entry_result)
|
||||||
|
playlist_results.append(entry_result)
|
||||||
|
result = ie_result.copy()
|
||||||
|
result['entries'] = playlist_results
|
||||||
|
return result
|
||||||
|
|
||||||
def process_info(self, info_dict):
|
def process_info(self, info_dict):
|
||||||
"""Process a single dictionary returned by an InfoExtractor."""
|
"""Process a single dictionary returned by an InfoExtractor."""
|
||||||
|
|
||||||
|
#We increment the download the download count here to match the previous behaviour.
|
||||||
|
self.increment_downloads()
|
||||||
|
|
||||||
|
info_dict['fulltitle'] = info_dict['title']
|
||||||
|
if len(info_dict['title']) > 200:
|
||||||
|
info_dict['title'] = info_dict['title'][:197] + u'...'
|
||||||
|
|
||||||
# Keep for backwards compatibility
|
# Keep for backwards compatibility
|
||||||
info_dict['stitle'] = info_dict['title']
|
info_dict['stitle'] = info_dict['title']
|
||||||
|
|
||||||
@@ -513,7 +645,7 @@ class FileDownloader(object):
|
|||||||
with io.open(encodeFilename(sub_filename), 'w', encoding='utf-8') as subfile:
|
with io.open(encodeFilename(sub_filename), 'w', encoding='utf-8') as subfile:
|
||||||
subfile.write(sub)
|
subfile.write(sub)
|
||||||
except (OSError, IOError):
|
except (OSError, IOError):
|
||||||
self.trouble(u'ERROR: Cannot write subtitles file ' + descfn)
|
self.report_error(u'Cannot write subtitles file ' + descfn)
|
||||||
return
|
return
|
||||||
if self.params.get('onlysubtitles', False):
|
if self.params.get('onlysubtitles', False):
|
||||||
return
|
return
|
||||||
@@ -528,6 +660,20 @@ class FileDownloader(object):
|
|||||||
self.report_error(u'Cannot write metadata to JSON file ' + infofn)
|
self.report_error(u'Cannot write metadata to JSON file ' + infofn)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if self.params.get('writethumbnail', False):
|
||||||
|
if 'thumbnail' in info_dict:
|
||||||
|
thumb_format = info_dict['thumbnail'].rpartition(u'/')[2].rpartition(u'.')[2]
|
||||||
|
if not thumb_format:
|
||||||
|
thumb_format = 'jpg'
|
||||||
|
thumb_filename = filename.rpartition('.')[0] + u'.' + thumb_format
|
||||||
|
self.to_screen(u'[%s] %s: Downloading thumbnail ...' %
|
||||||
|
(info_dict['extractor'], info_dict['id']))
|
||||||
|
uf = compat_urllib_request.urlopen(info_dict['thumbnail'])
|
||||||
|
with open(thumb_filename, 'wb') as thumbf:
|
||||||
|
shutil.copyfileobj(uf, thumbf)
|
||||||
|
self.to_screen(u'[%s] %s: Writing thumbnail to: %s' %
|
||||||
|
(info_dict['extractor'], info_dict['id'], thumb_filename))
|
||||||
|
|
||||||
if not self.params.get('skip_download', False):
|
if not self.params.get('skip_download', False):
|
||||||
if self.params.get('nooverwrites', False) and os.path.exists(encodeFilename(filename)):
|
if self.params.get('nooverwrites', False) and os.path.exists(encodeFilename(filename)):
|
||||||
success = True
|
success = True
|
||||||
@@ -556,53 +702,14 @@ class FileDownloader(object):
|
|||||||
raise SameFileError(self.params['outtmpl'])
|
raise SameFileError(self.params['outtmpl'])
|
||||||
|
|
||||||
for url in url_list:
|
for url in url_list:
|
||||||
suitable_found = False
|
try:
|
||||||
for ie in self._ies:
|
#It also downloads the videos
|
||||||
# Go to next InfoExtractor if not suitable
|
videos = self.extract_info(url)
|
||||||
if not ie.suitable(url):
|
except UnavailableVideoError:
|
||||||
continue
|
self.report_error(u'unable to download video')
|
||||||
|
except MaxDownloadsReached:
|
||||||
# Warn if the _WORKING attribute is False
|
self.to_screen(u'[info] Maximum number of downloaded files reached.')
|
||||||
if not ie.working():
|
raise
|
||||||
self.report_warning(u'the program functionality for this site has been marked as broken, '
|
|
||||||
u'and will probably not work. If you want to go on, use the -i option.')
|
|
||||||
|
|
||||||
# Suitable InfoExtractor found
|
|
||||||
suitable_found = True
|
|
||||||
|
|
||||||
# Extract information from URL and process it
|
|
||||||
try:
|
|
||||||
videos = ie.extract(url)
|
|
||||||
except ExtractorError as de: # An error we somewhat expected
|
|
||||||
self.trouble(u'ERROR: ' + compat_str(de), de.format_traceback())
|
|
||||||
break
|
|
||||||
except MaxDownloadsReached:
|
|
||||||
self.to_screen(u'[info] Maximum number of downloaded files reached.')
|
|
||||||
raise
|
|
||||||
except Exception as e:
|
|
||||||
if self.params.get('ignoreerrors', False):
|
|
||||||
self.report_error(u'' + compat_str(e), tb=compat_str(traceback.format_exc()))
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
|
|
||||||
if len(videos or []) > 1 and self.fixed_template():
|
|
||||||
raise SameFileError(self.params['outtmpl'])
|
|
||||||
|
|
||||||
for video in videos or []:
|
|
||||||
video['extractor'] = ie.IE_NAME
|
|
||||||
try:
|
|
||||||
self.increment_downloads()
|
|
||||||
self.process_info(video)
|
|
||||||
except UnavailableVideoError:
|
|
||||||
self.to_stderr(u"\n")
|
|
||||||
self.report_error(u'unable to download video')
|
|
||||||
|
|
||||||
# Suitable InfoExtractor had been found; go to next URL
|
|
||||||
break
|
|
||||||
|
|
||||||
if not suitable_found:
|
|
||||||
self.report_error(u'no suitable InfoExtractor: %s' % url)
|
|
||||||
|
|
||||||
return self._download_retcode
|
return self._download_retcode
|
||||||
|
|
||||||
@@ -629,7 +736,7 @@ class FileDownloader(object):
|
|||||||
except (IOError, OSError):
|
except (IOError, OSError):
|
||||||
self.report_warning(u'Unable to remove downloaded video file')
|
self.report_warning(u'Unable to remove downloaded video file')
|
||||||
|
|
||||||
def _download_with_rtmpdump(self, filename, url, player_url, page_url):
|
def _download_with_rtmpdump(self, filename, url, player_url, page_url, play_path):
|
||||||
self.report_destination(filename)
|
self.report_destination(filename)
|
||||||
tmpfilename = self.temp_name(filename)
|
tmpfilename = self.temp_name(filename)
|
||||||
|
|
||||||
@@ -648,6 +755,8 @@ class FileDownloader(object):
|
|||||||
basic_args += ['-W', player_url]
|
basic_args += ['-W', player_url]
|
||||||
if page_url is not None:
|
if page_url is not None:
|
||||||
basic_args += ['--pageUrl', page_url]
|
basic_args += ['--pageUrl', page_url]
|
||||||
|
if play_path is not None:
|
||||||
|
basic_args += ['-y', play_path]
|
||||||
args = basic_args + [[], ['-e', '-k', '1']][self.params.get('continuedl', False)]
|
args = basic_args + [[], ['-e', '-k', '1']][self.params.get('continuedl', False)]
|
||||||
if self.params.get('verbose', False):
|
if self.params.get('verbose', False):
|
||||||
try:
|
try:
|
||||||
@@ -702,7 +811,8 @@ class FileDownloader(object):
|
|||||||
if url.startswith('rtmp'):
|
if url.startswith('rtmp'):
|
||||||
return self._download_with_rtmpdump(filename, url,
|
return self._download_with_rtmpdump(filename, url,
|
||||||
info_dict.get('player_url', None),
|
info_dict.get('player_url', None),
|
||||||
info_dict.get('page_url', None))
|
info_dict.get('page_url', None),
|
||||||
|
info_dict.get('play_path', None))
|
||||||
|
|
||||||
tmpfilename = self.temp_name(filename)
|
tmpfilename = self.temp_name(filename)
|
||||||
stream = None
|
stream = None
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -24,10 +24,13 @@ __authors__ = (
|
|||||||
'Jaime Marquínez Ferrándiz',
|
'Jaime Marquínez Ferrándiz',
|
||||||
'Jeff Crouse',
|
'Jeff Crouse',
|
||||||
'Osama Khalid',
|
'Osama Khalid',
|
||||||
|
'Michael Walter',
|
||||||
|
'M. Yasoob Ullah Khalid',
|
||||||
)
|
)
|
||||||
|
|
||||||
__license__ = 'Public Domain'
|
__license__ = 'Public Domain'
|
||||||
|
|
||||||
|
import codecs
|
||||||
import getpass
|
import getpass
|
||||||
import optparse
|
import optparse
|
||||||
import os
|
import os
|
||||||
@@ -46,7 +49,7 @@ from .FileDownloader import *
|
|||||||
from .InfoExtractors import gen_extractors
|
from .InfoExtractors import gen_extractors
|
||||||
from .PostProcessor import *
|
from .PostProcessor import *
|
||||||
|
|
||||||
def parseOpts():
|
def parseOpts(overrideArguments=None):
|
||||||
def _readOptions(filename_bytes):
|
def _readOptions(filename_bytes):
|
||||||
try:
|
try:
|
||||||
optionf = open(filename_bytes)
|
optionf = open(filename_bytes)
|
||||||
@@ -139,9 +142,13 @@ def parseOpts():
|
|||||||
help='display the current browser identification', default=False)
|
help='display the current browser identification', default=False)
|
||||||
general.add_option('--user-agent',
|
general.add_option('--user-agent',
|
||||||
dest='user_agent', help='specify a custom user agent', metavar='UA')
|
dest='user_agent', help='specify a custom user agent', metavar='UA')
|
||||||
|
general.add_option('--referer',
|
||||||
|
dest='referer', help='specify a custom referer, use if the video access is restricted to one domain',
|
||||||
|
metavar='REF', default=None)
|
||||||
general.add_option('--list-extractors',
|
general.add_option('--list-extractors',
|
||||||
action='store_true', dest='list_extractors',
|
action='store_true', dest='list_extractors',
|
||||||
help='List all supported extractors and the URLs they would handle', default=False)
|
help='List all supported extractors and the URLs they would handle', default=False)
|
||||||
|
general.add_option('--proxy', dest='proxy', default=None, help='Use the specified HTTP/HTTPS proxy', metavar='URL')
|
||||||
general.add_option('--test', action='store_true', dest='test', default=False, help=optparse.SUPPRESS_HELP)
|
general.add_option('--test', action='store_true', dest='test', default=False, help=optparse.SUPPRESS_HELP)
|
||||||
|
|
||||||
selection.add_option('--playlist-start',
|
selection.add_option('--playlist-start',
|
||||||
@@ -153,6 +160,9 @@ def parseOpts():
|
|||||||
selection.add_option('--max-downloads', metavar='NUMBER', dest='max_downloads', help='Abort after downloading NUMBER files', default=None)
|
selection.add_option('--max-downloads', metavar='NUMBER', dest='max_downloads', help='Abort after downloading NUMBER files', default=None)
|
||||||
selection.add_option('--min-filesize', metavar='SIZE', dest='min_filesize', help="Do not download any videos smaller than SIZE (e.g. 50k or 44.6m)", default=None)
|
selection.add_option('--min-filesize', metavar='SIZE', dest='min_filesize', help="Do not download any videos smaller than SIZE (e.g. 50k or 44.6m)", default=None)
|
||||||
selection.add_option('--max-filesize', metavar='SIZE', dest='max_filesize', help="Do not download any videos larger than SIZE (e.g. 50k or 44.6m)", default=None)
|
selection.add_option('--max-filesize', metavar='SIZE', dest='max_filesize', help="Do not download any videos larger than SIZE (e.g. 50k or 44.6m)", default=None)
|
||||||
|
selection.add_option('--date', metavar='DATE', dest='date', help='download only videos uploaded in this date', default=None)
|
||||||
|
selection.add_option('--datebefore', metavar='DATE', dest='datebefore', help='download only videos uploaded before this date', default=None)
|
||||||
|
selection.add_option('--dateafter', metavar='DATE', dest='dateafter', help='download only videos uploaded after this date', default=None)
|
||||||
|
|
||||||
|
|
||||||
authentication.add_option('-u', '--username',
|
authentication.add_option('-u', '--username',
|
||||||
@@ -164,7 +174,8 @@ def parseOpts():
|
|||||||
|
|
||||||
|
|
||||||
video_format.add_option('-f', '--format',
|
video_format.add_option('-f', '--format',
|
||||||
action='store', dest='format', metavar='FORMAT', help='video format code')
|
action='store', dest='format', metavar='FORMAT',
|
||||||
|
help='video format code, specifiy the order of preference using slashes: "-f 22/17/18"')
|
||||||
video_format.add_option('--all-formats',
|
video_format.add_option('--all-formats',
|
||||||
action='store_const', dest='format', help='download all available video formats', const='all')
|
action='store_const', dest='format', help='download all available video formats', const='all')
|
||||||
video_format.add_option('--prefer-free-formats',
|
video_format.add_option('--prefer-free-formats',
|
||||||
@@ -223,18 +234,33 @@ def parseOpts():
|
|||||||
help='display progress in console titlebar', default=False)
|
help='display progress in console titlebar', default=False)
|
||||||
verbosity.add_option('-v', '--verbose',
|
verbosity.add_option('-v', '--verbose',
|
||||||
action='store_true', dest='verbose', help='print various debugging information', default=False)
|
action='store_true', dest='verbose', help='print various debugging information', default=False)
|
||||||
|
verbosity.add_option('--dump-intermediate-pages',
|
||||||
|
action='store_true', dest='dump_intermediate_pages', default=False,
|
||||||
|
help='print downloaded pages to debug problems(very verbose)')
|
||||||
|
|
||||||
filesystem.add_option('-t', '--title',
|
filesystem.add_option('-t', '--title',
|
||||||
action='store_true', dest='usetitle', help='use title in file name', default=False)
|
action='store_true', dest='usetitle', help='use title in file name (default)', default=False)
|
||||||
filesystem.add_option('--id',
|
filesystem.add_option('--id',
|
||||||
action='store_true', dest='useid', help='use video ID in file name', default=False)
|
action='store_true', dest='useid', help='use only video ID in file name', default=False)
|
||||||
filesystem.add_option('-l', '--literal',
|
filesystem.add_option('-l', '--literal',
|
||||||
action='store_true', dest='usetitle', help='[deprecated] alias of --title', default=False)
|
action='store_true', dest='usetitle', help='[deprecated] alias of --title', default=False)
|
||||||
filesystem.add_option('-A', '--auto-number',
|
filesystem.add_option('-A', '--auto-number',
|
||||||
action='store_true', dest='autonumber',
|
action='store_true', dest='autonumber',
|
||||||
help='number downloaded files starting from 00000', default=False)
|
help='number downloaded files starting from 00000', default=False)
|
||||||
filesystem.add_option('-o', '--output',
|
filesystem.add_option('-o', '--output',
|
||||||
dest='outtmpl', metavar='TEMPLATE', help='output filename template. Use %(title)s to get the title, %(uploader)s for the uploader name, %(uploader_id)s for the uploader nickname if different, %(autonumber)s to get an automatically incremented number, %(ext)s for the filename extension, %(upload_date)s for the upload date (YYYYMMDD), %(extractor)s for the provider (youtube, metacafe, etc), %(id)s for the video id and %% for a literal percent. Use - to output to stdout. Can also be used to download to a different directory, for example with -o \'/my/downloads/%(uploader)s/%(title)s-%(id)s.%(ext)s\' .')
|
dest='outtmpl', metavar='TEMPLATE',
|
||||||
|
help=('output filename template. Use %(title)s to get the title, '
|
||||||
|
'%(uploader)s for the uploader name, %(uploader_id)s for the uploader nickname if different, '
|
||||||
|
'%(autonumber)s to get an automatically incremented number, '
|
||||||
|
'%(ext)s for the filename extension, %(upload_date)s for the upload date (YYYYMMDD), '
|
||||||
|
'%(extractor)s for the provider (youtube, metacafe, etc), '
|
||||||
|
'%(id)s for the video id , %(playlist)s for the playlist the video is in, '
|
||||||
|
'%(playlist_index)s for the position in the playlist and %% for a literal percent. '
|
||||||
|
'Use - to output to stdout. Can also be used to download to a different directory, '
|
||||||
|
'for example with -o \'/my/downloads/%(uploader)s/%(title)s-%(id)s.%(ext)s\' .'))
|
||||||
|
filesystem.add_option('--autonumber-size',
|
||||||
|
dest='autonumber_size', metavar='NUMBER',
|
||||||
|
help='Specifies the number of digits in %(autonumber)s when it is present in output filename template or --autonumber option is given')
|
||||||
filesystem.add_option('--restrict-filenames',
|
filesystem.add_option('--restrict-filenames',
|
||||||
action='store_true', dest='restrictfilenames',
|
action='store_true', dest='restrictfilenames',
|
||||||
help='Restrict filenames to only ASCII characters, and avoid "&" and spaces in filenames', default=False)
|
help='Restrict filenames to only ASCII characters, and avoid "&" and spaces in filenames', default=False)
|
||||||
@@ -260,6 +286,9 @@ def parseOpts():
|
|||||||
filesystem.add_option('--write-info-json',
|
filesystem.add_option('--write-info-json',
|
||||||
action='store_true', dest='writeinfojson',
|
action='store_true', dest='writeinfojson',
|
||||||
help='write video metadata to a .info.json file', default=False)
|
help='write video metadata to a .info.json file', default=False)
|
||||||
|
filesystem.add_option('--write-thumbnail',
|
||||||
|
action='store_true', dest='writethumbnail',
|
||||||
|
help='write thumbnail image to disk', default=False)
|
||||||
|
|
||||||
|
|
||||||
postproc.add_option('-x', '--extract-audio', action='store_true', dest='extractaudio', default=False,
|
postproc.add_option('-x', '--extract-audio', action='store_true', dest='extractaudio', default=False,
|
||||||
@@ -284,26 +313,35 @@ def parseOpts():
|
|||||||
parser.add_option_group(authentication)
|
parser.add_option_group(authentication)
|
||||||
parser.add_option_group(postproc)
|
parser.add_option_group(postproc)
|
||||||
|
|
||||||
xdg_config_home = os.environ.get('XDG_CONFIG_HOME')
|
if overrideArguments is not None:
|
||||||
if xdg_config_home:
|
opts, args = parser.parse_args(overrideArguments)
|
||||||
userConfFile = os.path.join(xdg_config_home, 'youtube-dl.conf')
|
if opts.verbose:
|
||||||
|
print(u'[debug] Override config: ' + repr(overrideArguments))
|
||||||
else:
|
else:
|
||||||
userConfFile = os.path.join(os.path.expanduser('~'), '.config', 'youtube-dl.conf')
|
xdg_config_home = os.environ.get('XDG_CONFIG_HOME')
|
||||||
systemConf = _readOptions('/etc/youtube-dl.conf')
|
if xdg_config_home:
|
||||||
userConf = _readOptions(userConfFile)
|
userConfFile = os.path.join(xdg_config_home, 'youtube-dl.conf')
|
||||||
commandLineConf = sys.argv[1:]
|
else:
|
||||||
argv = systemConf + userConf + commandLineConf
|
userConfFile = os.path.join(os.path.expanduser('~'), '.config', 'youtube-dl.conf')
|
||||||
opts, args = parser.parse_args(argv)
|
systemConf = _readOptions('/etc/youtube-dl.conf')
|
||||||
|
userConf = _readOptions(userConfFile)
|
||||||
if opts.verbose:
|
commandLineConf = sys.argv[1:]
|
||||||
print(u'[debug] System config: ' + repr(systemConf))
|
argv = systemConf + userConf + commandLineConf
|
||||||
print(u'[debug] User config: ' + repr(userConf))
|
opts, args = parser.parse_args(argv)
|
||||||
print(u'[debug] Command-line args: ' + repr(commandLineConf))
|
if opts.verbose:
|
||||||
|
print(u'[debug] System config: ' + repr(systemConf))
|
||||||
|
print(u'[debug] User config: ' + repr(userConf))
|
||||||
|
print(u'[debug] Command-line args: ' + repr(commandLineConf))
|
||||||
|
|
||||||
return parser, opts, args
|
return parser, opts, args
|
||||||
|
|
||||||
def _real_main():
|
def _real_main(argv=None):
|
||||||
parser, opts, args = parseOpts()
|
# Compatibility fixes for Windows
|
||||||
|
if sys.platform == 'win32':
|
||||||
|
# https://github.com/rg3/youtube-dl/issues/820
|
||||||
|
codecs.register(lambda name: codecs.lookup('utf-8') if name == 'cp65001' else None)
|
||||||
|
|
||||||
|
parser, opts, args = parseOpts(argv)
|
||||||
|
|
||||||
# Open appropriate CookieJar
|
# Open appropriate CookieJar
|
||||||
if opts.cookiefile is None:
|
if opts.cookiefile is None:
|
||||||
@@ -321,6 +359,10 @@ def _real_main():
|
|||||||
# Set user agent
|
# Set user agent
|
||||||
if opts.user_agent is not None:
|
if opts.user_agent is not None:
|
||||||
std_headers['User-Agent'] = opts.user_agent
|
std_headers['User-Agent'] = opts.user_agent
|
||||||
|
|
||||||
|
# Set referer
|
||||||
|
if opts.referer is not None:
|
||||||
|
std_headers['Referer'] = opts.referer
|
||||||
|
|
||||||
# Dump user agent
|
# Dump user agent
|
||||||
if opts.dump_user_agent:
|
if opts.dump_user_agent:
|
||||||
@@ -345,8 +387,16 @@ def _real_main():
|
|||||||
|
|
||||||
# General configuration
|
# General configuration
|
||||||
cookie_processor = compat_urllib_request.HTTPCookieProcessor(jar)
|
cookie_processor = compat_urllib_request.HTTPCookieProcessor(jar)
|
||||||
proxy_handler = compat_urllib_request.ProxyHandler()
|
if opts.proxy:
|
||||||
opener = compat_urllib_request.build_opener(proxy_handler, cookie_processor, YoutubeDLHandler())
|
proxies = {'http': opts.proxy, 'https': opts.proxy}
|
||||||
|
else:
|
||||||
|
proxies = compat_urllib_request.getproxies()
|
||||||
|
# Set HTTPS proxy to HTTP one if given (https://github.com/rg3/youtube-dl/issues/805)
|
||||||
|
if 'http' in proxies and 'https' not in proxies:
|
||||||
|
proxies['https'] = proxies['http']
|
||||||
|
proxy_handler = compat_urllib_request.ProxyHandler(proxies)
|
||||||
|
https_handler = compat_urllib_request.HTTPSHandler()
|
||||||
|
opener = compat_urllib_request.build_opener(https_handler, proxy_handler, cookie_processor, YoutubeDLHandler())
|
||||||
compat_urllib_request.install_opener(opener)
|
compat_urllib_request.install_opener(opener)
|
||||||
socket.setdefaulttimeout(300) # 5 minutes should be enough (famous last words)
|
socket.setdefaulttimeout(300) # 5 minutes should be enough (famous last words)
|
||||||
|
|
||||||
@@ -419,6 +469,10 @@ def _real_main():
|
|||||||
if opts.recodevideo is not None:
|
if opts.recodevideo is not None:
|
||||||
if opts.recodevideo not in ['mp4', 'flv', 'webm', 'ogg']:
|
if opts.recodevideo not in ['mp4', 'flv', 'webm', 'ogg']:
|
||||||
parser.error(u'invalid video recode format specified')
|
parser.error(u'invalid video recode format specified')
|
||||||
|
if opts.date is not None:
|
||||||
|
date = DateRange.day(opts.date)
|
||||||
|
else:
|
||||||
|
date = DateRange(opts.dateafter, opts.datebefore)
|
||||||
|
|
||||||
if sys.version_info < (3,):
|
if sys.version_info < (3,):
|
||||||
# In Python 2, sys.argv is a bytestring (also note http://bugs.python.org/issue2128 for Windows systems)
|
# In Python 2, sys.argv is a bytestring (also note http://bugs.python.org/issue2128 for Windows systems)
|
||||||
@@ -431,7 +485,7 @@ def _real_main():
|
|||||||
or (opts.usetitle and u'%(title)s-%(id)s.%(ext)s')
|
or (opts.usetitle and u'%(title)s-%(id)s.%(ext)s')
|
||||||
or (opts.useid and u'%(id)s.%(ext)s')
|
or (opts.useid and u'%(id)s.%(ext)s')
|
||||||
or (opts.autonumber and u'%(autonumber)s-%(id)s.%(ext)s')
|
or (opts.autonumber and u'%(autonumber)s-%(id)s.%(ext)s')
|
||||||
or u'%(id)s.%(ext)s')
|
or u'%(title)s-%(id)s.%(ext)s')
|
||||||
|
|
||||||
# File downloader
|
# File downloader
|
||||||
fd = FileDownloader({
|
fd = FileDownloader({
|
||||||
@@ -451,6 +505,7 @@ def _real_main():
|
|||||||
'format_limit': opts.format_limit,
|
'format_limit': opts.format_limit,
|
||||||
'listformats': opts.listformats,
|
'listformats': opts.listformats,
|
||||||
'outtmpl': outtmpl,
|
'outtmpl': outtmpl,
|
||||||
|
'autonumber_size': opts.autonumber_size,
|
||||||
'restrictfilenames': opts.restrictfilenames,
|
'restrictfilenames': opts.restrictfilenames,
|
||||||
'ignoreerrors': opts.ignoreerrors,
|
'ignoreerrors': opts.ignoreerrors,
|
||||||
'ratelimit': opts.ratelimit,
|
'ratelimit': opts.ratelimit,
|
||||||
@@ -469,6 +524,7 @@ def _real_main():
|
|||||||
'updatetime': opts.updatetime,
|
'updatetime': opts.updatetime,
|
||||||
'writedescription': opts.writedescription,
|
'writedescription': opts.writedescription,
|
||||||
'writeinfojson': opts.writeinfojson,
|
'writeinfojson': opts.writeinfojson,
|
||||||
|
'writethumbnail': opts.writethumbnail,
|
||||||
'writesubtitles': opts.writesubtitles,
|
'writesubtitles': opts.writesubtitles,
|
||||||
'onlysubtitles': opts.onlysubtitles,
|
'onlysubtitles': opts.onlysubtitles,
|
||||||
'allsubtitles': opts.allsubtitles,
|
'allsubtitles': opts.allsubtitles,
|
||||||
@@ -480,10 +536,12 @@ def _real_main():
|
|||||||
'max_downloads': opts.max_downloads,
|
'max_downloads': opts.max_downloads,
|
||||||
'prefer_free_formats': opts.prefer_free_formats,
|
'prefer_free_formats': opts.prefer_free_formats,
|
||||||
'verbose': opts.verbose,
|
'verbose': opts.verbose,
|
||||||
|
'dump_intermediate_pages': opts.dump_intermediate_pages,
|
||||||
'test': opts.test,
|
'test': opts.test,
|
||||||
'keepvideo': opts.keepvideo,
|
'keepvideo': opts.keepvideo,
|
||||||
'min_filesize': opts.min_filesize,
|
'min_filesize': opts.min_filesize,
|
||||||
'max_filesize': opts.max_filesize
|
'max_filesize': opts.max_filesize,
|
||||||
|
'daterange': date,
|
||||||
})
|
})
|
||||||
|
|
||||||
if opts.verbose:
|
if opts.verbose:
|
||||||
@@ -535,9 +593,9 @@ def _real_main():
|
|||||||
|
|
||||||
sys.exit(retcode)
|
sys.exit(retcode)
|
||||||
|
|
||||||
def main():
|
def main(argv=None):
|
||||||
try:
|
try:
|
||||||
_real_main()
|
_real_main(argv)
|
||||||
except DownloadError:
|
except DownloadError:
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
except SameFileError:
|
except SameFileError:
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ 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
|
||||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
path = os.path.realpath(os.path.abspath(__file__))
|
||||||
|
sys.path.append(os.path.dirname(os.path.dirname(path)))
|
||||||
|
|
||||||
import youtube_dl
|
import youtube_dl
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ def rsa_verify(message, signature, key):
|
|||||||
def update_self(to_screen, verbose, filename):
|
def update_self(to_screen, verbose, filename):
|
||||||
"""Update the program file with the latest version from the repository"""
|
"""Update the program file with the latest version from the repository"""
|
||||||
|
|
||||||
UPDATE_URL = "http://rg3.github.com/youtube-dl/update/"
|
UPDATE_URL = "http://rg3.github.io/youtube-dl/update/"
|
||||||
VERSION_URL = UPDATE_URL + 'LATEST_VERSION'
|
VERSION_URL = UPDATE_URL + 'LATEST_VERSION'
|
||||||
JSON_URL = UPDATE_URL + 'versions.json'
|
JSON_URL = UPDATE_URL + 'versions.json'
|
||||||
UPDATES_RSA_KEY = (0x9d60ee4d8f805312fdb15a62f87b95bd66177b91df176765d13514a0f1754bcd2057295c5b6f1d35daa6742c3ffc9a82d3e118861c207995a8031e151d863c9927e304576bc80692bc8e094896fcf11b66f3e29e04e3a71e9a11558558acea1840aec37fc396fb6b65dc81a1c4144e03bd1c011de62e3f1357b327d08426fe93, 65537)
|
UPDATES_RSA_KEY = (0x9d60ee4d8f805312fdb15a62f87b95bd66177b91df176765d13514a0f1754bcd2057295c5b6f1d35daa6742c3ffc9a82d3e118861c207995a8031e151d863c9927e304576bc80692bc8e094896fcf11b66f3e29e04e3a71e9a11558558acea1840aec37fc396fb6b65dc81a1c4144e03bd1c011de62e3f1357b327d08426fe93, 65537)
|
||||||
@@ -78,7 +78,7 @@ def update_self(to_screen, verbose, filename):
|
|||||||
to_screen(u'Updating to version ' + versions_info['latest'] + '...')
|
to_screen(u'Updating to version ' + versions_info['latest'] + '...')
|
||||||
version = versions_info['versions'][versions_info['latest']]
|
version = versions_info['versions'][versions_info['latest']]
|
||||||
|
|
||||||
print_notes(versions_info['versions'])
|
print_notes(to_screen, versions_info['versions'])
|
||||||
|
|
||||||
if not os.access(filename, os.W_OK):
|
if not os.access(filename, os.W_OK):
|
||||||
to_screen(u'ERROR: no write permissions on %s' % filename)
|
to_screen(u'ERROR: no write permissions on %s' % filename)
|
||||||
@@ -157,11 +157,15 @@ del "%s"
|
|||||||
|
|
||||||
to_screen(u'Updated youtube-dl. Restart youtube-dl to use the new version.')
|
to_screen(u'Updated youtube-dl. Restart youtube-dl to use the new version.')
|
||||||
|
|
||||||
def print_notes(versions, fromVersion=__version__):
|
def get_notes(versions, fromVersion):
|
||||||
notes = []
|
notes = []
|
||||||
for v,vdata in sorted(versions.items()):
|
for v,vdata in sorted(versions.items()):
|
||||||
if v > fromVersion:
|
if v > fromVersion:
|
||||||
notes.extend(vdata.get('notes', []))
|
notes.extend(vdata.get('notes', []))
|
||||||
|
return notes
|
||||||
|
|
||||||
|
def print_notes(to_screen, versions, fromVersion=__version__):
|
||||||
|
notes = get_notes(versions, fromVersion)
|
||||||
if notes:
|
if notes:
|
||||||
to_screen(u'PLEASE NOTE:')
|
to_screen(u'PLEASE NOTE:')
|
||||||
for note in notes:
|
for note in notes:
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import traceback
|
|||||||
import zlib
|
import zlib
|
||||||
import email.utils
|
import email.utils
|
||||||
import json
|
import json
|
||||||
|
import datetime
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import urllib.request as compat_urllib_request
|
import urllib.request as compat_urllib_request
|
||||||
@@ -568,3 +569,70 @@ class YoutubeDLHandler(compat_urllib_request.HTTPHandler):
|
|||||||
|
|
||||||
https_request = http_request
|
https_request = http_request
|
||||||
https_response = http_response
|
https_response = http_response
|
||||||
|
|
||||||
|
def unified_strdate(date_str):
|
||||||
|
"""Return a string with the date in the format YYYYMMDD"""
|
||||||
|
upload_date = None
|
||||||
|
#Replace commas
|
||||||
|
date_str = date_str.replace(',',' ')
|
||||||
|
# %z (UTC offset) is only supported in python>=3.2
|
||||||
|
date_str = re.sub(r' (\+|-)[\d]*$', '', date_str)
|
||||||
|
format_expressions = ['%d %B %Y', '%B %d %Y', '%b %d %Y', '%Y-%m-%d', '%d/%m/%Y', '%Y/%m/%d %H:%M:%S']
|
||||||
|
for expression in format_expressions:
|
||||||
|
try:
|
||||||
|
upload_date = datetime.datetime.strptime(date_str, expression).strftime('%Y%m%d')
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return upload_date
|
||||||
|
|
||||||
|
def date_from_str(date_str):
|
||||||
|
"""
|
||||||
|
Return a datetime object from a string in the format YYYYMMDD or
|
||||||
|
(now|today)[+-][0-9](day|week|month|year)(s)?"""
|
||||||
|
today = datetime.date.today()
|
||||||
|
if date_str == 'now'or date_str == 'today':
|
||||||
|
return today
|
||||||
|
match = re.match('(now|today)(?P<sign>[+-])(?P<time>\d+)(?P<unit>day|week|month|year)(s)?', date_str)
|
||||||
|
if match is not None:
|
||||||
|
sign = match.group('sign')
|
||||||
|
time = int(match.group('time'))
|
||||||
|
if sign == '-':
|
||||||
|
time = -time
|
||||||
|
unit = match.group('unit')
|
||||||
|
#A bad aproximation?
|
||||||
|
if unit == 'month':
|
||||||
|
unit = 'day'
|
||||||
|
time *= 30
|
||||||
|
elif unit == 'year':
|
||||||
|
unit = 'day'
|
||||||
|
time *= 365
|
||||||
|
unit += 's'
|
||||||
|
delta = datetime.timedelta(**{unit: time})
|
||||||
|
return today + delta
|
||||||
|
return datetime.datetime.strptime(date_str, "%Y%m%d").date()
|
||||||
|
|
||||||
|
class DateRange(object):
|
||||||
|
"""Represents a time interval between two dates"""
|
||||||
|
def __init__(self, start=None, end=None):
|
||||||
|
"""start and end must be strings in the format accepted by date"""
|
||||||
|
if start is not None:
|
||||||
|
self.start = date_from_str(start)
|
||||||
|
else:
|
||||||
|
self.start = datetime.datetime.min.date()
|
||||||
|
if end is not None:
|
||||||
|
self.end = date_from_str(end)
|
||||||
|
else:
|
||||||
|
self.end = datetime.datetime.max.date()
|
||||||
|
if self.start > self.end:
|
||||||
|
raise ValueError('Date range: "%s" , the start date must be before the end date' % self)
|
||||||
|
@classmethod
|
||||||
|
def day(cls, day):
|
||||||
|
"""Returns a range that only contains the given day"""
|
||||||
|
return cls(day,day)
|
||||||
|
def __contains__(self, date):
|
||||||
|
"""Check if the date is in the range"""
|
||||||
|
if not isinstance(date, datetime.date):
|
||||||
|
date = date_from_str(date)
|
||||||
|
return self.start <= date <= self.end
|
||||||
|
def __str__(self):
|
||||||
|
return '%s - %s' % ( self.start.isoformat(), self.end.isoformat())
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
|
|
||||||
__version__ = '2013.04.03'
|
__version__ = '2013.05.04'
|
||||||
|
|||||||
Reference in New Issue
Block a user