Merge remote-tracking branch 'FiloSottille/vbr'
authorPhilipp Hagemeister <phihag@phihag.de>
Thu, 27 Sep 2012 18:18:29 +0000 (20:18 +0200)
committerPhilipp Hagemeister <phihag@phihag.de>
Thu, 27 Sep 2012 18:18:29 +0000 (20:18 +0200)
Conflicts:
youtube-dl
youtube-dl.exe

15 files changed:
LATEST_VERSION
Makefile
README.md
devscripts/posix-locale.sh [changed mode: 0644->0755]
devscripts/release.sh [new file with mode: 0755]
devscripts/wine-py2exe.sh [changed mode: 0644->0755]
test/testvideo-original.mp4 [new file with mode: 0644]
youtube-dl
youtube-dl.1 [new file with mode: 0644]
youtube-dl.bash-completion [new file with mode: 0644]
youtube-dl.dev [deleted file]
youtube-dl.exe
youtube_dl/InfoExtractors.py
youtube_dl/__init__.py
youtube_dl/utils.py

index 0c8dc502a85f6eb25152a8cea3b079a911389f24..88c5427e0ad9ce2824cc13d1e2c96f354fd8687a 100644 (file)
@@ -1 +1 @@
-2012.02.27
+2012.09.27
index e2da02e96ba304fec7e76c5fb604ae973cbf34fa..625948211d1b12700c0d88a84dae630fd7c20326 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,26 +1,48 @@
-default: update
+all: youtube-dl README.md youtube-dl.1 youtube-dl.bash-completion LATEST_VERSION
 
-update: compile update-readme update-latest
+# TODO: re-add youtube-dl.exe, and make sure it's 1. safe and 2. doesn't need sudo
 
-update-latest:
-       ./youtube-dl.dev --version > LATEST_VERSION
+clean:
+       rm -f youtube-dl youtube-dl.exe youtube-dl.1 LATEST_VERSION
 
-update-readme:
-       @options=$$(COLUMNS=80 ./youtube-dl.dev --help | sed -e '1,/.*General Options.*/ d' -e 's/^\W\{2\}\(\w\)/### \1/') && \
-               header=$$(sed -e '/.*## OPTIONS/,$$ d' README.md) && \
-               footer=$$(sed -e '1,/.*## FAQ/ d' README.md) && \
+PREFIX=/usr/local
+install: youtube-dl youtube-dl.1 youtube-dl.bash-completion
+       install -m 755 --owner root --group root youtube-dl $(PREFIX)/bin/
+       install -m 644 --owner root --group root youtube-dl.1 $(PREFIX)/man/man1
+       install -m 644 --owner root --group root youtube-dl.bash-completion /etc/bash_completion.d/youtube-dl
+
+.PHONY: all clean install README.md youtube-dl.bash-completion
+# TODO un-phone README.md and youtube-dl.bash_completion by reading from .in files and generating from them
+
+youtube-dl: youtube_dl/*.py
+       zip --quiet --junk-paths youtube-dl youtube_dl/*.py
+       echo '#!/usr/bin/env python' > youtube-dl
+       cat youtube-dl.zip >> youtube-dl
+       rm youtube-dl.zip
+       chmod a+x youtube-dl
+
+youtube-dl.exe: youtube_dl/*.py
+       bash devscripts/wine-py2exe.sh build_exe.py
+
+README.md: youtube-dl
+       @options=$$(COLUMNS=80 ./youtube-dl --help | sed -e '1,/.*General Options.*/ d' -e 's/^\W\{2\}\(\w\)/## \1/') && \
+               header=$$(sed -e '/.*# OPTIONS/,$$ d' README.md) && \
+               footer=$$(sed -e '1,/.*# FAQ/ d' README.md) && \
                echo "$${header}" > README.md && \
                echo >> README.md && \
-               echo '## OPTIONS' >> README.md && \
+               echo '# OPTIONS' >> README.md && \
                echo "$${options}" >> README.md&& \
                echo >> README.md && \
-               echo '## FAQ' >> README.md && \
+               echo '# FAQ' >> README.md && \
                echo "$${footer}" >> README.md
 
-compile:
-       zip --quiet --junk-paths youtube-dl youtube_dl/*.py
-       echo '#!/usr/bin/env python' > youtube-dl
-       cat youtube-dl.zip >> youtube-dl
-       rm youtube-dl.zip
+youtube-dl.1: README.md
+       pandoc -s -w man README.md -o youtube-dl.1
+
+youtube-dl.bash-completion: README.md
+       @options=`egrep -o '(--[a-z-]+) ' README.md | sort -u | xargs echo` && \
+               content=`sed "s/opts=\"[^\"]*\"/opts=\"$${options}\"/g" youtube-dl.bash-completion` && \
+               echo "$${content}" > youtube-dl.bash-completion
 
-.PHONY: default compile update update-latest update-readme
+LATEST_VERSION: youtube-dl
+       ./youtube-dl --version > LATEST_VERSION
index 8a6742d941ec5b547c1c3084a403a6bd2853d26d..51501684b3607ac12f312bb7337177358fc1993a 100644 (file)
--- a/README.md
+++ b/README.md
@@ -1,16 +1,19 @@
-# youtube-dl
+% youtube-dl(1)
 
-## USAGE
-youtube-dl [options] url [url...]
+# NAME
+youtube-dl
 
-## DESCRIPTION
+# SYNOPSIS
+**youtube-dl** [OPTIONS] URL [URL...]
+
+# DESCRIPTION
 **youtube-dl** is a small command-line program to download videos from
 YouTube.com and a few more sites. It requires the Python interpreter, version
 2.x (x being at least 6), and it is not platform specific. It should work in
 your Unix box, in Windows or in Mac OS X. It is released to the public domain,
 which means you can modify it, redistribute it or use it however you like.
 
-## OPTIONS
+# OPTIONS
     -h, --help               print this help text and exit
     --version                print program version and exit
     -U, --update             update this program to latest version
@@ -18,10 +21,11 @@ which means you can modify it, redistribute it or use it however you like.
     -r, --rate-limit LIMIT   download rate limit (e.g. 50k or 44.6m)
     -R, --retries RETRIES    number of retries (default is 10)
     --dump-user-agent        display the current browser identification
+    --user-agent None        specify a custom user agent
     --list-extractors        List all supported extractors and the URLs they
                              would handle
 
-### Video Selection:
+## Video Selection:
     --playlist-start NUMBER  playlist video to start at (default is 1)
     --playlist-end NUMBER    playlist video to end at (default is last)
     --match-title REGEX      download only matching titles (regex or caseless
@@ -30,7 +34,7 @@ which means you can modify it, redistribute it or use it however you like.
                              caseless sub-string)
     --max-downloads NUMBER   Abort after downloading NUMBER files
 
-### Filesystem Options:
+## Filesystem Options:
     -t, --title              use title in file name
     -l, --literal            use literal title in file name
     -A, --auto-number        number downloaded files starting from 00000
@@ -53,7 +57,7 @@ which means you can modify it, redistribute it or use it however you like.
     --write-description      write video description to a .description file
     --write-info-json        write video metadata to a .info.json file
 
-### Verbosity / Simulation Options:
+## Verbosity / Simulation Options:
     -q, --quiet              activates quiet mode
     -s, --simulate           do not download the video and do not write anything
                              to disk
@@ -68,7 +72,7 @@ which means you can modify it, redistribute it or use it however you like.
     --console-title          display progress in console titlebar
     -v, --verbose            print various debugging information
 
-### Video Format Options:
+## Video Format Options:
     -f, --format FORMAT      video format code
     --all-formats            download all available video formats
     --prefer-free-formats    prefer free video formats unless a specific one is
@@ -80,12 +84,12 @@ which means you can modify it, redistribute it or use it however you like.
     --srt-lang LANG          language of the closed captions to download
                              (optional) use IETF language tags like 'en'
 
-### Authentication Options:
+## Authentication Options:
     -u, --username USERNAME  account username
     -p, --password PASSWORD  account password
     -n, --netrc              use .netrc authentication data
 
-### Post-processing Options:
+## Post-processing Options:
     --extract-audio          convert video files to audio-only files (requires
                              ffmpeg or avconv and ffprobe or avprobe)
     --audio-format FORMAT    "best", "aac", "vorbis", "mp3", "m4a", or "wav";
@@ -96,7 +100,7 @@ which means you can modify it, redistribute it or use it however you like.
     -k, --keep-video         keeps the video file on disk after the post-
                              processing; the video is erased by default
 
-## FAQ
+# FAQ
 
 ### Can you please put the -b option back?
 
@@ -118,13 +122,13 @@ The URLs youtube-dl outputs require the downloader to have the correct cookies.
 
 youtube has switched to a new video info format in July 2011 which is not supported by old versions of youtube-dl. You can update youtube-dl with `sudo youtube-dl --update`.
 
-## COPYRIGHT
+# COPYRIGHT
 
 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.
 
-## BUGS
+# BUGS
 
 Bugs and suggestions should be reported at: <https://github.com/rg3/youtube-dl/issues>
 
old mode 100644 (file)
new mode 100755 (executable)
diff --git a/devscripts/release.sh b/devscripts/release.sh
new file mode 100755 (executable)
index 0000000..75f8ec8
--- /dev/null
@@ -0,0 +1,11 @@
+#! /bin/bash
+
+if [ -z "$1" ]; then echo "ERROR: specify version number like this: $0 1994.09.06"; exit 1; fi
+version="$1"
+if [ ! -z "`git tag | grep "$version"`" ]; then echo 'ERROR: version already present'; exit 1; fi
+if [ ! -z "`git status --porcelain`" ]; then echo 'ERROR: the working directory is not clean; commit or stash changes'; exit 1; fi
+sed -i "s/__version__ = '.*'/__version__ = '$version'/" youtube_dl/__init__.py
+make all
+git add -A
+git commit -m "release $version"
+git tag -m "Release $version" "$version"
\ No newline at end of file
old mode 100644 (file)
new mode 100755 (executable)
diff --git a/test/testvideo-original.mp4 b/test/testvideo-original.mp4
new file mode 100644 (file)
index 0000000..2d25af7
Binary files /dev/null and b/test/testvideo-original.mp4 differ
index 913619d6d342876bfe185242d7e91c8e08618447..9c78766ffe7b7a9bb44a187192b17c6c678911eb 100755 (executable)
Binary files a/youtube-dl and b/youtube-dl differ
diff --git a/youtube-dl.1 b/youtube-dl.1
new file mode 100644 (file)
index 0000000..1220a3a
--- /dev/null
@@ -0,0 +1,194 @@
+.TH youtube-dl 1 "" 
+.SH NAME
+.PP
+youtube-dl
+.SH SYNOPSIS
+.PP
+\f[B]youtube-dl\f[] [OPTIONS] URL [URL...]
+.SH DESCRIPTION
+.PP
+\f[B]youtube-dl\f[] is a small command-line program to download videos
+from YouTube.com and a few more sites.
+It requires the Python interpreter, version 2.x (x being at least 6),
+and it is not platform specific.
+It should work in your Unix box, in Windows or in Mac OS X.
+It is released to the public domain, which means you can modify it,
+redistribute it or use it however you like.
+.SH OPTIONS
+.IP
+.nf
+\f[C]
+-h,\ --help\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ print\ this\ help\ text\ and\ exit
+--version\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ print\ program\ version\ and\ exit
+-U,\ --update\ \ \ \ \ \ \ \ \ \ \ \ \ update\ this\ program\ to\ latest\ version
+-i,\ --ignore-errors\ \ \ \ \ \ continue\ on\ download\ errors
+-r,\ --rate-limit\ LIMIT\ \ \ download\ rate\ limit\ (e.g.\ 50k\ or\ 44.6m)
+-R,\ --retries\ RETRIES\ \ \ \ number\ of\ retries\ (default\ is\ 10)
+--dump-user-agent\ \ \ \ \ \ \ \ display\ the\ current\ browser\ identification
+--user-agent\ None\ \ \ \ \ \ \ \ specify\ a\ custom\ user\ agent
+--list-extractors\ \ \ \ \ \ \ \ List\ all\ supported\ extractors\ and\ the\ URLs\ they
+\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ would\ handle
+\f[]
+.fi
+.SS Video Selection:
+.IP
+.nf
+\f[C]
+--playlist-start\ NUMBER\ \ playlist\ video\ to\ start\ at\ (default\ is\ 1)
+--playlist-end\ NUMBER\ \ \ \ playlist\ video\ to\ end\ at\ (default\ is\ last)
+--match-title\ REGEX\ \ \ \ \ \ download\ only\ matching\ titles\ (regex\ or\ caseless
+\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ sub-string)
+--reject-title\ REGEX\ \ \ \ \ skip\ download\ for\ matching\ titles\ (regex\ or
+\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ caseless\ sub-string)
+--max-downloads\ NUMBER\ \ \ Abort\ after\ downloading\ NUMBER\ files
+\f[]
+.fi
+.SS Filesystem Options:
+.IP
+.nf
+\f[C]
+-t,\ --title\ \ \ \ \ \ \ \ \ \ \ \ \ \ use\ title\ in\ file\ name
+-l,\ --literal\ \ \ \ \ \ \ \ \ \ \ \ use\ literal\ title\ in\ file\ name
+-A,\ --auto-number\ \ \ \ \ \ \ \ number\ downloaded\ files\ starting\ from\ 00000
+-o,\ --output\ TEMPLATE\ \ \ \ output\ filename\ template.\ Use\ %(stitle)s\ to\ get\ the
+\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ title,\ %(uploader)s\ for\ the\ uploader\ name,
+\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ %(autonumber)s\ to\ get\ an\ automatically\ incremented
+\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ number,\ %(ext)s\ for\ the\ filename\ extension,
+\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ %(upload_date)s\ for\ the\ upload\ date\ (YYYYMMDD),\ and
+\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ %%\ for\ a\ literal\ percent.\ Use\ -\ to\ output\ to
+\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ stdout.
+-a,\ --batch-file\ FILE\ \ \ \ file\ containing\ URLs\ to\ download\ (\[aq]-\[aq]\ for\ stdin)
+-w,\ --no-overwrites\ \ \ \ \ \ do\ not\ overwrite\ files
+-c,\ --continue\ \ \ \ \ \ \ \ \ \ \ resume\ partially\ downloaded\ files
+--no-continue\ \ \ \ \ \ \ \ \ \ \ \ do\ not\ resume\ partially\ downloaded\ files\ (restart
+\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ from\ beginning)
+--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
+\f[]
+.fi
+.SS Verbosity / Simulation Options:
+.IP
+.nf
+\f[C]
+-q,\ --quiet\ \ \ \ \ \ \ \ \ \ \ \ \ \ activates\ quiet\ mode
+-s,\ --simulate\ \ \ \ \ \ \ \ \ \ \ do\ not\ download\ the\ video\ and\ do\ not\ write\ anything
+\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ to\ disk
+--skip-download\ \ \ \ \ \ \ \ \ \ do\ not\ download\ the\ video
+-g,\ --get-url\ \ \ \ \ \ \ \ \ \ \ \ simulate,\ quiet\ but\ print\ URL
+-e,\ --get-title\ \ \ \ \ \ \ \ \ \ simulate,\ quiet\ but\ print\ title
+--get-thumbnail\ \ \ \ \ \ \ \ \ \ simulate,\ quiet\ but\ print\ thumbnail\ URL
+--get-description\ \ \ \ \ \ \ \ simulate,\ quiet\ but\ print\ video\ description
+--get-filename\ \ \ \ \ \ \ \ \ \ \ simulate,\ quiet\ but\ print\ output\ filename
+--get-format\ \ \ \ \ \ \ \ \ \ \ \ \ simulate,\ quiet\ but\ print\ output\ format
+--no-progress\ \ \ \ \ \ \ \ \ \ \ \ do\ not\ print\ progress\ bar
+--console-title\ \ \ \ \ \ \ \ \ \ display\ progress\ in\ console\ titlebar
+-v,\ --verbose\ \ \ \ \ \ \ \ \ \ \ \ print\ various\ debugging\ information
+\f[]
+.fi
+.SS Video Format Options:
+.IP
+.nf
+\f[C]
+-f,\ --format\ FORMAT\ \ \ \ \ \ video\ format\ code
+--all-formats\ \ \ \ \ \ \ \ \ \ \ \ download\ all\ available\ video\ formats
+--prefer-free-formats\ \ \ \ prefer\ free\ video\ formats\ unless\ a\ specific\ one\ is
+\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ requested
+--max-quality\ FORMAT\ \ \ \ \ highest\ quality\ format\ to\ download
+-F,\ --list-formats\ \ \ \ \ \ \ list\ all\ available\ formats\ (currently\ youtube\ only)
+--write-srt\ \ \ \ \ \ \ \ \ \ \ \ \ \ write\ video\ closed\ captions\ to\ a\ .srt\ file
+\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ (currently\ youtube\ only)
+--srt-lang\ LANG\ \ \ \ \ \ \ \ \ \ language\ of\ the\ closed\ captions\ to\ download
+\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ (optional)\ use\ IETF\ language\ tags\ like\ \[aq]en\[aq]
+\f[]
+.fi
+.SS Authentication Options:
+.IP
+.nf
+\f[C]
+-u,\ --username\ USERNAME\ \ account\ username
+-p,\ --password\ PASSWORD\ \ account\ password
+-n,\ --netrc\ \ \ \ \ \ \ \ \ \ \ \ \ \ use\ .netrc\ authentication\ data
+\f[]
+.fi
+.SS Post-processing Options:
+.IP
+.nf
+\f[C]
+--extract-audio\ \ \ \ \ \ \ \ \ \ convert\ video\ files\ to\ audio-only\ files\ (requires
+\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ ffmpeg\ or\ avconv\ and\ ffprobe\ or\ avprobe)
+--audio-format\ FORMAT\ \ \ \ "best",\ "aac",\ "vorbis",\ "mp3",\ "m4a",\ or\ "wav";
+\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ best\ by\ default
+--audio-quality\ QUALITY\ \ ffmpeg/avconv\ audio\ quality\ specification,\ insert\ a
+\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ value\ between\ 0\ (better)\ and\ 9\ (worse)\ for\ VBR\ or\ a
+\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ specific\ bitrate\ like\ 128K\ (default\ 5)
+-k,\ --keep-video\ \ \ \ \ \ \ \ \ keeps\ the\ video\ file\ on\ disk\ after\ the\ post-
+\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ processing;\ the\ video\ is\ erased\ by\ default
+\f[]
+.fi
+.SH FAQ
+.SS Can you please put the -b option back?
+.PP
+Most people asking this question are not aware that youtube-dl now
+defaults to downloading the highest available quality as reported by
+YouTube, which will be 1080p or 720p in some cases, so you no longer
+need the -b option.
+For some specific videos, maybe YouTube does not report them to be
+available in a specific high quality format you\[aq]\[aq]re interested
+in.
+In that case, simply request it with the -f option and youtube-dl will
+try to download it.
+.SS I get HTTP error 402 when trying to download a video. What\[aq]s
+this?
+.PP
+Apparently YouTube requires you to pass a CAPTCHA test if you download
+too much.
+We\[aq]\[aq]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.
+.SS I have downloaded a video but how can I play it?
+.PP
+Once the video is fully downloaded, use any video player, such as
+vlc (http://www.videolan.org) or mplayer (http://www.mplayerhq.hu/).
+.SS The links provided by youtube-dl -g are not working anymore
+.PP
+The URLs youtube-dl outputs require the downloader to have the correct
+cookies.
+Use the \f[C]--cookies\f[] option to write the required cookies into a
+file, and advise your downloader to read cookies from that file.
+Some sites also require a common user agent to be used, use
+\f[C]--dump-user-agent\f[] to see the one in use by youtube-dl.
+.SS ERROR: no fmt_url_map or conn information found in video info
+.PP
+youtube has switched to a new video info format in July 2011 which is
+not supported by old versions of youtube-dl.
+You can update youtube-dl with \f[C]sudo\ youtube-dl\ --update\f[].
+.SH COPYRIGHT
+.PP
+youtube-dl is released into the public domain by the copyright holders.
+.PP
+This README file was originally written by Daniel Bolton
+(<https://github.com/dbbolton>) and is likewise released into the public
+domain.
+.SH BUGS
+.PP
+Bugs and suggestions should be reported at:
+<https://github.com/rg3/youtube-dl/issues>
+.PP
+Please include:
+.IP \[bu] 2
+Your exact command line, like
+\f[C]youtube-dl\ -t\ "http://www.youtube.com/watch?v=uHlDtZ6Oc3s&feature=channel_video_title"\f[].
+A common mistake is not to escape the \f[C]&\f[].
+Putting URLs in quotes should solve this problem.
+.IP \[bu] 2
+The output of \f[C]youtube-dl\ --version\f[]
+.IP \[bu] 2
+The output of \f[C]python\ --version\f[]
+.IP \[bu] 2
+The name and version of your Operating System ("Ubuntu 11.04 x64" or
+"Windows 7 x64" is usually enough).
diff --git a/youtube-dl.bash-completion b/youtube-dl.bash-completion
new file mode 100644 (file)
index 0000000..1eca2ad
--- /dev/null
@@ -0,0 +1,14 @@
+__youtube-dl()
+{
+    local cur prev opts
+    COMPREPLY=()
+    cur="${COMP_WORDS[COMP_CWORD]}"
+    opts="--all-formats --audio-format --audio-quality --auto-number --batch-file --console-title --continue --cookies --dump-user-agent --extract-audio --format --get-description --get-filename --get-format --get-thumbnail --get-title --get-url --help --ignore-errors --keep-video --list-extractors --list-formats --literal --match-title --max-downloads --max-quality --netrc --no-continue --no-mtime --no-overwrites --no-part --no-progress --output --password --playlist-end --playlist-start --prefer-free-formats --quiet --rate-limit --reject-title --retries --simulate --skip-download --srt-lang --title --update --user-agent --username --verbose --version --write-description --write-info-json --write-srt"
+
+    if [[ ${cur} == * ]] ; then
+        COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+        return 0
+    fi
+}
+
+complete -F __youtube-dl youtube-dl
diff --git a/youtube-dl.dev b/youtube-dl.dev
deleted file mode 100755 (executable)
index 473b7f4..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-import youtube_dl
-
-youtube_dl.main()
index bf8ed478bec3af8f910d5974fd6e0c611a39cd94..2aa052ccf569ae513f627910c18e55675effe844 100755 (executable)
Binary files a/youtube-dl.exe and b/youtube-dl.exe differ
index baf859ea8beac248c7cc9caf0a525a71ae4368fb..ff5c47726db5c1f4d1bfb3492c747b2bb2481382 100644 (file)
@@ -13,6 +13,8 @@ import urllib
 import urllib2
 import email.utils
 import xml.etree.ElementTree
+import random
+import math
 from urlparse import parse_qs
 
 try:
@@ -95,7 +97,7 @@ class InfoExtractor(object):
 class YoutubeIE(InfoExtractor):
        """Information extractor for youtube.com."""
 
-       _VALID_URL = r'^((?:https?://)?(?:youtu\.be/|(?:\w+\.)?youtube(?:-nocookie)?\.com/)(?!view_play_list|my_playlists|artist|playlist)(?:(?:(?:v|embed|e)/)|(?:(?:watch(?:_popup)?(?:\.php)?)?(?:\?|#!?)(?:.+&)?v=))?)?([0-9A-Za-z_-]+)(?(1).+)?$'
+       _VALID_URL = r'^((?:https?://)?(?:youtu\.be/|(?:\w+\.)?youtube(?:-nocookie)?\.com/|tube\.majestyc\.net/)(?!view_play_list|my_playlists|artist|playlist)(?:(?:(?:v|embed|e)/)|(?:(?:watch(?:_popup)?(?:\.php)?)?(?:\?|#!?)(?:.+&)?v=))?)?([0-9A-Za-z_-]+)(?(1).+)?$'
        _LANG_URL = r'http://www.youtube.com/?hl=en&persist_hl=1&gl=US&persist_gl=1&opt_out_ackd=1'
        _LOGIN_URL = 'https://www.youtube.com/signup?next=/&gl=US&hl=en'
        _AGE_URL = 'http://www.youtube.com/verify_age?next_url=/&gl=US&hl=en'
@@ -366,7 +368,8 @@ class YoutubeIE(InfoExtractor):
                                        srt_list = urllib2.urlopen(request).read()
                                except (urllib2.URLError, httplib.HTTPException, socket.error), err:
                                        raise Trouble(u'WARNING: unable to download video subtitles: %s' % str(err))
-                               srt_lang_list = re.findall(r'lang_code="([\w\-]+)"', srt_list)
+                               srt_lang_list = re.findall(r'name="([^"]*)"[^>]+lang_code="([\w\-]+)"', srt_list)
+                               srt_lang_list = dict((l[1], l[0]) for l in srt_lang_list)
                                if not srt_lang_list:
                                        raise Trouble(u'WARNING: video has no closed captions')
                                if self._downloader.params.get('subtitleslang', False):
@@ -374,14 +377,16 @@ class YoutubeIE(InfoExtractor):
                                elif 'en' in srt_lang_list:
                                        srt_lang = 'en'
                                else:
-                                       srt_lang = srt_lang_list[0]
+                                       srt_lang = srt_lang_list.keys()[0]
                                if not srt_lang in srt_lang_list:
                                        raise Trouble(u'WARNING: no closed captions found in the specified language')
-                               request = urllib2.Request('http://video.google.com/timedtext?hl=en&lang=%s&v=%s' % (srt_lang, video_id))
+                               request = urllib2.Request('http://www.youtube.com/api/timedtext?lang=%s&name=%s&v=%s' % (srt_lang, srt_lang_list[srt_lang], video_id))
                                try:
                                        srt_xml = urllib2.urlopen(request).read()
                                except (urllib2.URLError, httplib.HTTPException, socket.error), err:
                                        raise Trouble(u'WARNING: unable to download video subtitles: %s' % str(err))
+                               if not srt_xml:
+                                       raise Trouble(u'WARNING: unable to download video subtitles')
                                video_subtitles = self._closed_captions_xml_to_srt(srt_xml.decode('utf-8'))
                        except Trouble as trouble:
                                self._downloader.trouble(trouble[0])
@@ -399,7 +404,7 @@ class YoutubeIE(InfoExtractor):
                        url_data_strs = video_info['url_encoded_fmt_stream_map'][0].split(',')
                        url_data = [parse_qs(uds) for uds in url_data_strs]
                        url_data = filter(lambda ud: 'itag' in ud and 'url' in ud, url_data)
-                       url_map = dict((ud['itag'][0], ud['url'][0]) for ud in url_data)
+                       url_map = dict((ud['itag'][0], ud['url'][0] + '&signature=' + ud['sig'][0]) for ud in url_data)
 
                        format_limit = self._downloader.params.get('format_limit', None)
                        available_formats = self._available_formats_prefer_free if self._downloader.params.get('prefer_free_formats', False) else self._available_formats
@@ -1619,6 +1624,98 @@ class YoutubeUserIE(InfoExtractor):
                        self._downloader.download(['http://www.youtube.com/watch?v=%s' % video_id])
 
 
+class BlipTVUserIE(InfoExtractor):
+       """Information Extractor for blip.tv users."""
+
+       _VALID_URL = r'(?:(?:(?:https?://)?(?:\w+\.)?blip\.tv/)|bliptvuser:)([^/]+)/*$'
+       _PAGE_SIZE = 12
+       IE_NAME = u'blip.tv:user'
+
+       def __init__(self, downloader=None):
+               InfoExtractor.__init__(self, downloader)
+
+       def report_download_page(self, username, pagenum):
+               """Report attempt to download user page."""
+               self._downloader.to_screen(u'[%s] user %s: Downloading video ids from page %d' %
+                               (self.IE_NAME, username, pagenum))
+
+       def _real_extract(self, url):
+               # Extract username
+               mobj = re.match(self._VALID_URL, url)
+               if mobj is None:
+                       self._downloader.trouble(u'ERROR: invalid url: %s' % url)
+                       return
+
+               username = mobj.group(1)
+
+               page_base = 'http://m.blip.tv/pr/show_get_full_episode_list?users_id=%s&lite=0&esi=1'
+
+               request = urllib2.Request(url)
+
+               try:
+                       page = urllib2.urlopen(request).read().decode('utf-8')
+                       mobj = re.search(r'data-users-id="([^"]+)"', page)
+                       page_base = page_base % mobj.group(1)
+               except (urllib2.URLError, httplib.HTTPException, socket.error), err:
+                       self._downloader.trouble(u'ERROR: unable to download webpage: %s' % str(err))
+                       return
+
+
+               # 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:
+                       self.report_download_page(username, pagenum)
+
+                       request = urllib2.Request( page_base + "&page=" + str(pagenum) )
+
+                       try:
+                               page = urllib2.urlopen(request).read().decode('utf-8')
+                       except (urllib2.URLError, httplib.HTTPException, socket.error), err:
+                               self._downloader.trouble(u'ERROR: unable to download webpage: %s' % str(err))
+                               return
+
+                       # 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
+
+               all_ids_count = len(video_ids)
+               playliststart = self._downloader.params.get('playliststart', 1) - 1
+               playlistend = self._downloader.params.get('playlistend', -1)
+
+               if playlistend == -1:
+                       video_ids = video_ids[playliststart:]
+               else:
+                       video_ids = video_ids[playliststart:playlistend]
+
+               self._downloader.to_screen(u"[%s] user %s: Collected %d video ids (downloading %d of them)" %
+                               (self.IE_NAME, username, all_ids_count, len(video_ids)))
+
+               for video_id in video_ids:
+                       self._downloader.download([u'http://blip.tv/'+video_id])
+
+
 class DepositFilesIE(InfoExtractor):
        """Information extractor for depositfiles.com"""
 
@@ -1917,7 +2014,7 @@ class BlipTVIE(InfoExtractor):
                else:
                        cchar = '?'
                json_url = url + cchar + 'skin=json&version=2&no_wrap=1'
-               request = urllib2.Request(json_url)
+               request = urllib2.Request(json_url.encode('utf-8'))
                self.report_extraction(mobj.group(1))
                info = None
                try:
@@ -1975,6 +2072,7 @@ class BlipTVIE(InfoExtractor):
                                self._downloader.trouble(u'ERROR: unable to parse video information: %s' % repr(err))
                                return
 
+               std_headers['User-Agent'] = 'iTunes/10.6.1'
                return [info]
 
 
@@ -2193,12 +2291,14 @@ class EscapistIE(InfoExtractor):
 
                self.report_extraction(showName)
                try:
-                       webPageBytes = urllib2.urlopen(url).read()
+                       webPage = urllib2.urlopen(url)
+                       webPageBytes = webPage.read()
+                       m = re.match(r'text/html; charset="?([^"]+)"?', webPage.headers['Content-Type'])
+                       webPage = webPageBytes.decode(m.group(1) if m else 'utf-8')
                except (urllib2.URLError, httplib.HTTPException, socket.error), err:
                        self._downloader.trouble(u'ERROR: unable to download webpage: ' + unicode(err))
                        return
 
-               webPage = webPageBytes.decode('utf-8')
                descMatch = re.search('<meta name="description" content="([^"]*)"', webPage)
                description = unescapeHTML(descMatch.group(1))
                imgMatch = re.search('<meta property="og:image" content="([^"]*)"', webPage)
@@ -2357,11 +2457,11 @@ class XVideosIE(InfoExtractor):
 
 
                # Extract video thumbnail
-               mobj = re.search(r'http://(?:img.*?\.)xvideos.com/videos/thumbs/[a-fA-F0-9]/[a-fA-F0-9]/[a-fA-F0-9]/([a-fA-F0-9.]+jpg)', webpage)
+               mobj = re.search(r'http://(?:img.*?\.)xvideos.com/videos/thumbs/[a-fA-F0-9]+/[a-fA-F0-9]+/[a-fA-F0-9]+/[a-fA-F0-9]+/([a-fA-F0-9.]+jpg)', webpage)
                if mobj is None:
                        self._downloader.trouble(u'ERROR: unable to extract video thumbnail')
                        return
-               video_thumbnail = mobj.group(1).decode('utf-8')
+               video_thumbnail = mobj.group(0).decode('utf-8')
 
                info = {
                        'id': video_id,
@@ -2857,3 +2957,190 @@ class MTVIE(InfoExtractor):
                }
 
                return [info]
+
+
+class YoukuIE(InfoExtractor):
+
+       _VALID_URL =  r'(?:http://)?v\.youku\.com/v_show/id_(?P<ID>[A-Za-z0-9]+)\.html'
+       IE_NAME = u'Youku'
+
+       def __init__(self, downloader=None):
+               InfoExtractor.__init__(self, downloader)
+
+       def report_download_webpage(self, file_id):
+               """Report webpage download."""
+               self._downloader.to_screen(u'[Youku] %s: Downloading webpage' % file_id)
+
+       def report_extraction(self, file_id):
+               """Report information extraction."""
+               self._downloader.to_screen(u'[Youku] %s: Extracting information' % file_id)
+
+       def _gen_sid(self):
+               nowTime = int(time.time() * 1000)
+               random1 = random.randint(1000,1998)
+               random2 = random.randint(1000,9999)
+
+               return "%d%d%d" %(nowTime,random1,random2)
+
+       def _get_file_ID_mix_string(self, seed):
+               mixed = []
+               source = list("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ/\:._-1234567890")
+               seed = float(seed)
+               for i in range(len(source)):
+                       seed  =  (seed * 211 + 30031 ) % 65536
+                       index  =  math.floor(seed / 65536 * len(source) )
+                       mixed.append(source[int(index)])
+                       source.remove(source[int(index)])
+               #return ''.join(mixed)
+               return mixed
+
+       def _get_file_id(self, fileId, seed):
+               mixed = self._get_file_ID_mix_string(seed)
+               ids = fileId.split('*')
+               realId = []
+               for ch in ids:
+                       if ch:
+                               realId.append(mixed[int(ch)])
+               return ''.join(realId)
+
+       def _real_extract(self, url):
+               mobj = re.match(self._VALID_URL, url)
+               if mobj is None:
+                       self._downloader.trouble(u'ERROR: invalid URL: %s' % url)
+                       return
+               video_id = mobj.group('ID')
+
+               info_url = 'http://v.youku.com/player/getPlayList/VideoIDS/' + video_id
+
+               request = urllib2.Request(info_url, None, std_headers)
+               try:
+                       self.report_download_webpage(video_id)
+                       jsondata = urllib2.urlopen(request).read()
+               except (urllib2.URLError, httplib.HTTPException, socket.error) as err:
+                       self._downloader.trouble(u'ERROR: Unable to retrieve video webpage: %s' % str(err))
+                       return
+
+               self.report_extraction(video_id)
+               try:
+                       config = json.loads(jsondata)
+
+                       video_title =  config['data'][0]['title']
+                       seed = config['data'][0]['seed']
+
+                       format = self._downloader.params.get('format', None)
+                       supported_format = config['data'][0]['streamfileids'].keys()
+
+                       if format is None or format == 'best':
+                               if 'hd2' in supported_format:
+                                       format = 'hd2'
+                               else:
+                                       format = 'flv'
+                               ext = u'flv'
+                       elif format == 'worst':
+                               format = 'mp4'
+                               ext = u'mp4'
+                       else:
+                               format = 'flv'
+                               ext = u'flv'
+
+
+                       fileid = config['data'][0]['streamfileids'][format]
+                       seg_number = len(config['data'][0]['segs'][format])
+
+                       keys=[]
+                       for i in xrange(seg_number):
+                               keys.append(config['data'][0]['segs'][format][i]['k'])
+
+                       #TODO check error
+                       #youku only could be viewed from mainland china
+               except:
+                       self._downloader.trouble(u'ERROR: unable to extract info section')
+                       return
+
+               files_info=[]
+               sid = self._gen_sid()
+               fileid = self._get_file_id(fileid, seed)
+
+               #column 8,9 of fileid represent the segment number
+               #fileid[7:9] should be changed
+               for index, key in enumerate(keys):
+
+                       temp_fileid = '%s%02X%s' % (fileid[0:8], index, fileid[10:])
+                       download_url = 'http://f.youku.com/player/getFlvPath/sid/%s_%02X/st/flv/fileid/%s?k=%s' % (sid, index, temp_fileid, key)
+
+                       info = {
+                               'id': '%s_part%02d' % (video_id, index),
+                               'url': download_url,
+                               'uploader': None,
+                               'title': video_title,
+                               'ext': ext,
+                               'format': u'NA'
+                       }
+                       files_info.append(info)
+
+               return files_info
+
+
+class XNXXIE(InfoExtractor):
+       """Information extractor for xnxx.com"""
+
+       _VALID_URL = r'^http://video\.xnxx\.com/video([0-9]+)/(.*)'
+       IE_NAME = u'xnxx'
+       VIDEO_URL_RE = r'flv_url=(.*?)&amp;'
+       VIDEO_TITLE_RE = r'<title>(.*?)\s+-\s+XNXX.COM'
+       VIDEO_THUMB_RE = r'url_bigthumb=(.*?)&amp;'
+
+       def report_webpage(self, video_id):
+               """Report information extraction"""
+               self._downloader.to_screen(u'[%s] %s: Downloading webpage' % (self.IE_NAME, video_id))
+
+       def report_extraction(self, video_id):
+               """Report information extraction"""
+               self._downloader.to_screen(u'[%s] %s: Extracting information' % (self.IE_NAME, video_id))
+
+       def _real_extract(self, url):
+               mobj = re.match(self._VALID_URL, url)
+               if mobj is None:
+                       self._downloader.trouble(u'ERROR: invalid URL: %s' % url)
+                       return
+               video_id = mobj.group(1).decode('utf-8')
+
+               self.report_webpage(video_id)
+
+               # Get webpage content
+               try:
+                       webpage = urllib2.urlopen(url).read()
+               except (urllib2.URLError, httplib.HTTPException, socket.error), err:
+                       self._downloader.trouble(u'ERROR: unable to download video webpage: %s' % err)
+                       return
+
+               result = re.search(self.VIDEO_URL_RE, webpage)
+               if result is None:
+                       self._downloader.trouble(u'ERROR: unable to extract video url')
+                       return
+               video_url = urllib.unquote(result.group(1).decode('utf-8'))
+
+               result = re.search(self.VIDEO_TITLE_RE, webpage)
+               if result is None:
+                       self._downloader.trouble(u'ERROR: unable to extract video title')
+                       return
+               video_title = result.group(1).decode('utf-8')
+
+               result = re.search(self.VIDEO_THUMB_RE, webpage)
+               if result is None:
+                       self._downloader.trouble(u'ERROR: unable to extract video thumbnail')
+                       return
+               video_thumbnail = result.group(1).decode('utf-8')
+
+               info = {'id': video_id,
+                               'url': video_url,
+                               'uploader': None,
+                               'upload_date': None,
+                               'title': video_title,
+                               'ext': 'flv',
+                               'format': 'flv',
+                               'thumbnail': video_thumbnail,
+                               'description': None,
+                               'player_url': None}
+
+               return [info]
index 8817228f31ea9bd9073189a5980d2f56bbf30d44..3b3a9a4f494d099c1a00d00900f83549d379e65c 100644 (file)
@@ -19,7 +19,7 @@ __authors__  = (
        )
 
 __license__ = 'Public Domain'
-__version__ = '2012.02.27'
+__version__ = '2012.09.27'
 
 UPDATE_URL = 'https://raw.github.com/rg3/youtube-dl/master/youtube-dl'
 UPDATE_URL_VERSION = 'https://raw.github.com/rg3/youtube-dl/master/LATEST_VERSION'
@@ -190,6 +190,8 @@ def parseOpts():
        general.add_option('--dump-user-agent',
                        action='store_true', dest='dump_user_agent',
                        help='display the current browser identification', default=False)
+       general.add_option('--user-agent',
+                       action='store', dest='useragent', help='specify a custom user agent')
        general.add_option('--list-extractors',
                        action='store_true', dest='list_extractors',
                        help='List all supported extractors and the URLs they would handle', default=False)
@@ -338,6 +340,7 @@ def gen_extractors():
                YahooSearchIE(),
                DepositFilesIE(),
                FacebookIE(),
+               BlipTVUserIE(),
                BlipTVIE(),
                VimeoIE(),
                MyVideoIE(),
@@ -350,6 +353,8 @@ def gen_extractors():
                MixcloudIE(),
                StanfordOpenClassroomIE(),
                MTVIE(),
+               YoukuIE(),
+               XNXXIE(),
 
                GenericIE()
        ]
@@ -367,7 +372,10 @@ def _real_main():
                                jar.load()
                except (IOError, OSError), err:
                        sys.exit(u'ERROR: unable to open cookie file')
-
+       # Set user agent
+       if opts.useragent is not None:
+               std_headers['User-Agent'] = opts.useragent
+               
        # Dump user agent
        if opts.dump_user_agent:
                print std_headers['User-Agent']
index 2853ba50f228bb1f87900aa78a256b1637fcee2b..922e17eccfac611a1d90bf83e913383c9afce30d 100644 (file)
@@ -223,7 +223,7 @@ def encodeFilename(s):
 
        assert type(s) == type(u'')
 
-       if sys.platform == 'win32' and sys.getwindowsversion().major >= 5:
+       if sys.platform == 'win32' and sys.getwindowsversion()[0] >= 5:
                # Pass u'' directly to use Unicode APIs on Windows 2000 and up
                # (Detecting Windows NT 4 is tricky because 'major >= 4' would
                # match Windows 9x series as well. Besides, NT 4 is obsolete.)