Merge PR #422 from 'kevinamadeus/master'
authorFilippo Valsorda <filippo.valsorda@gmail.com>
Tue, 9 Oct 2012 08:48:49 +0000 (10:48 +0200)
committerFilippo Valsorda <filippo.valsorda@gmail.com>
Tue, 9 Oct 2012 08:48:49 +0000 (10:48 +0200)
Add InfoExtractor for Google Plus video
(with fixes)

17 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/FileDownloader.py
youtube_dl/InfoExtractors.py
youtube_dl/PostProcessor.py
youtube_dl/__init__.py
youtube_dl/utils.py

index 0c8dc502a85f6eb25152a8cea3b079a911389f24..88c5427e0ad9ce2824cc13d1e2c96f354fd8687a 100644 (file)
@@ -1 +1 @@
-2012.02.27
+2012.09.27
index e2da02e96ba304fec7e76c5fb604ae973cbf34fa..c960bfaebb2c2ee450f9398bb6e1e204dadc2147 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-phony 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:
+       @options=$$(COLUMNS=80 python -m 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:
+       python -m youtube_dl --version > LATEST_VERSION
index f04b96128de7effb5e2d19f06642398403425bcd..64a64c610df502cda227113640985bf17e6c2178 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 UA          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
@@ -38,9 +42,10 @@ which means you can modify it, redistribute it or use it however you like.
                              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.
+                             %(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.
     -a, --batch-file FILE    file containing URLs to download ('-' for stdin)
     -w, --no-overwrites      do not overwrite files
     -c, --continue           resume partially downloaded files
@@ -53,7 +58,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 +73,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,22 +85,23 @@ 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";
                              best by default
-    --audio-quality QUALITY  ffmpeg/avconv audio bitrate specification, 128k 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
 
-## FAQ
+# FAQ
 
 ### Can you please put the -b option back?
 
@@ -117,13 +123,42 @@ 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
+### ERROR: unable to download video ###
+
+youtube requires an additional signature since September 2012 which is not supported by old versions of youtube-dl. You can update youtube-dl with `sudo youtube-dl --update`.
+
+### SyntaxError: Non-ASCII character ###
+
+The error
+
+    File "youtube-dl", line 2
+    SyntaxError: Non-ASCII character '\x93' ...
+
+means you're using an outdated version of Python. Please update to Python 2.6 or 2.7.
+
+To run youtube-dl under Python 2.5, you'll have to manually check it out like this:
+
+       git clone git://github.com/rg3/youtube-dl.git
+       cd youtube-dl
+       python -m youtube_dl --help
+
+Please note that Python 2.5 is not supported anymore.
+
+### What is this binary file? Where has the code gone?
+
+Since June 2012 (#342) youtube-dl is packed as an executable zipfile, simply unzip it (might need renaming to `youtube-dl.zip` first on some systems) or clone the git repo to see the code. If you modify the code, you can run it by executing the `__main__.py` file. To recompile the executable, run `make compile`.
+
+### The exe throws a *Runtime error from Visual C++*
+
+To run the exe you need to install first the [Microsoft Visual C++ 2008 Redistributable Package](http://www.microsoft.com/en-us/download/details.aspx?id=29).
+
+# 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..963a6c2
--- /dev/null
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+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 789cb58f53739e4931ca05425c26f6f87b0fff8c..1055d246d3a9230e3d51f37bef20d0dbc2112716 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..a3100e9
--- /dev/null
@@ -0,0 +1,239 @@
+.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\ UA\ \ \ \ \ \ \ \ \ \ 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),
+\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ %(extractor)s\ for\ the\ provider\ (youtube,\ metacafe,
+\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ etc),\ %(id)s\ for\ the\ video\ id\ 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[].
+.SS ERROR: unable to download video
+.PP
+youtube requires an additional signature since September 2012 which is
+not supported by old versions of youtube-dl.
+You can update youtube-dl with \f[C]sudo\ youtube-dl\ --update\f[].
+.SS SyntaxError: Non-ASCII character
+.PP
+The error
+.IP
+.nf
+\f[C]
+File\ "youtube-dl",\ line\ 2
+SyntaxError:\ Non-ASCII\ character\ \[aq]\\x93\[aq]\ ...
+\f[]
+.fi
+.PP
+means you\[aq]re using an outdated version of Python.
+Please update to Python 2.6 or 2.7.
+.PP
+To run youtube-dl under Python 2.5, you\[aq]ll have to manually check it
+out like this:
+.IP
+.nf
+\f[C]
+git\ clone\ git://github.com/rg3/youtube-dl.git
+cd\ youtube-dl
+python\ -m\ youtube_dl\ --help
+\f[]
+.fi
+.PP
+Please note that Python 2.5 is not supported anymore.
+.SS What is this binary file? Where has the code gone?
+.PP
+Since June 2012 (#342) youtube-dl is packed as an executable zipfile,
+simply unzip it (might need renaming to \f[C]youtube-dl.zip\f[] first on
+some systems) or clone the git repo to see the code.
+If you modify the code, you can run it by executing the
+\f[C]__main__.py\f[] file.
+To recompile the executable, run \f[C]make\ compile\f[].
+.SS The exe throws a \f[I]Runtime error from Visual C++\f[]
+.PP
+To run the exe you need to install first the Microsoft Visual C++ 2008
+Redistributable
+Package (http://www.microsoft.com/en-us/download/details.aspx?id=29).
+.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 2aa052ccf569ae513f627910c18e55675effe844..2b82e43f2f88da48a77dd549d997c8f372b1da2f 100755 (executable)
Binary files a/youtube-dl.exe and b/youtube-dl.exe differ
index 14e872a98a922606b8a3f3ea15a9d3d61ef87274..38c6a519a0e5008c78d80cca2f83b135f98f9713 100644 (file)
@@ -474,6 +474,7 @@ class FileDownloader(object):
                                # Extract information from URL and process it
                                videos = ie.extract(url)
                                for video in videos or []:
+                                       video['extractor'] = ie.IE_NAME
                                        try:
                                                self.increment_downloads()
                                                self.process_info(video)
index ddb9fbca1fc9f64739d4e4e2e09094911cc3457f..f97611cb9b4ab681eb21f760db4c71ea1b2da98a 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,25 @@ 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?://)?                                       # http(s):// (optional)
+                            (?:youtu\.be/|(?:\w+\.)?youtube(?:-nocookie)?\.com/|
+                               tube\.majestyc\.net/)                             # the various hostnames, with wildcard subdomains
+                            (?!view_play_list|my_playlists|artist|playlist)      # ignore playlist URLs
+                            (?:                                                  # the various things that can precede the ID:
+                                (?:(?:v|embed|e)/)                               # v/ or embed/ or e/
+                                |(?:                                             # or the v= param in all its forms
+                                    (?:watch(?:_popup)?(?:\.php)?)?              # preceding watch(_popup|.php) or nothing (like /?v=xxxx)
+                                    (?:\?|\#!?)                                  # the params delimiter ? or # or #!
+                                    (?:.+&)?                                     # any other preceding param (like /?s=tuff&v=xxxx)
+                                    v=
+                                )
+                            )?                                                   # optional -> youtube.com/xxxx is OK
+                        )?                                                       # all until now is optional -> you can pass the naked ID
+                        ([0-9A-Za-z_-]+)                                         # here is it! the YouTube video ID
+                        (?(1).+)?                                                # if we found the ID, everything can follow
+                        $"""
        _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'
@@ -134,6 +154,10 @@ class YoutubeIE(InfoExtractor):
        }       
        IE_NAME = u'youtube'
 
+       def suitable(self, url):
+               """Receives a URL and returns True if suitable for this IE."""
+               return re.match(self._VALID_URL, url, re.VERBOSE) is not None
+
        def report_lang(self):
                """Report attempt to set language."""
                self._downloader.to_screen(u'[youtube] Setting language')
@@ -268,7 +292,7 @@ class YoutubeIE(InfoExtractor):
                        url = 'http://www.youtube.com/' + urllib.unquote(mobj.group(1)).lstrip('/')
 
                # Extract video id from URL
-               mobj = re.match(self._VALID_URL, url)
+               mobj = re.match(self._VALID_URL, url, re.VERBOSE)
                if mobj is None:
                        self._downloader.trouble(u'ERROR: invalid URL: %s' % url)
                        return
@@ -402,7 +426,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
@@ -592,7 +616,7 @@ class MetacafeIE(InfoExtractor):
 class DailymotionIE(InfoExtractor):
        """Information Extractor for Dailymotion"""
 
-       _VALID_URL = r'(?i)(?:https?://)?(?:www\.)?dailymotion\.[a-z]{2,3}/video/([^_/]+)_([^/]+)'
+       _VALID_URL = r'(?i)(?:https?://)?(?:www\.)?dailymotion\.[a-z]{2,3}/video/([^/]+)'
        IE_NAME = u'dailymotion'
 
        def __init__(self, downloader=None):
@@ -613,9 +637,9 @@ class DailymotionIE(InfoExtractor):
                        self._downloader.trouble(u'ERROR: invalid URL: %s' % url)
                        return
 
-               video_id = mobj.group(1)
+               video_id = mobj.group(1).split('_')[0].split('?')[0]
 
-               video_extension = 'flv'
+               video_extension = 'mp4'
 
                # Retrieve video webpage to extract further information
                request = urllib2.Request(url)
@@ -629,20 +653,23 @@ class DailymotionIE(InfoExtractor):
 
                # Extract URL, uploader and title from webpage
                self.report_extraction(video_id)
-               mobj = re.search(r'(?i)addVariable\(\"sequence\"\s*,\s*\"([^\"]+?)\"\)', webpage)
+               mobj = re.search(r'\s*var flashvars = (.*)', webpage)
                if mobj is None:
                        self._downloader.trouble(u'ERROR: unable to extract media URL')
                        return
-               sequence = urllib.unquote(mobj.group(1))
-               mobj = re.search(r',\"sdURL\"\:\"([^\"]+?)\",', sequence)
+               flashvars = urllib.unquote(mobj.group(1))
+               if 'hqURL' in flashvars: max_quality = 'hqURL'
+               elif 'sdURL' in flashvars: max_quality = 'sdURL'
+               else: max_quality = 'ldURL'
+               mobj = re.search(r'"' + max_quality + r'":"(.+?)"', flashvars)
+               if mobj is None:
+                       mobj = re.search(r'"video_url":"(.*?)",', flashvars)
                if mobj is None:
                        self._downloader.trouble(u'ERROR: unable to extract media URL')
                        return
-               mediaURL = urllib.unquote(mobj.group(1)).replace('\\', '')
-
-               # if needed add http://www.dailymotion.com/ if relative URL
+               video_url = urllib.unquote(mobj.group(1)).replace('\\/', '/')
 
-               video_url = mediaURL
+               # TODO: support choosing qualities
 
                mobj = re.search(r'<meta property="og:title" content="(?P<title>[^"]*)" />', webpage)
                if mobj is None:
@@ -656,11 +683,16 @@ class DailymotionIE(InfoExtractor):
                        return
                video_uploader = mobj.group(1)
 
+               video_upload_date = u'NA'
+               mobj = re.search(r'<div class="[^"]*uploaded_cont[^"]*" title="[^"]*">([0-9]{2})-([0-9]{2})-([0-9]{4})</div>', webpage)
+               if mobj is not None:
+                       video_upload_date = mobj.group(3) + mobj.group(2) + mobj.group(1)
+
                return [{
                        'id':           video_id.decode('utf-8'),
                        'url':          video_url.decode('utf-8'),
                        'uploader':     video_uploader.decode('utf-8'),
-                       'upload_date':  u'NA',
+                       'upload_date':  video_upload_date,
                        'title':        video_title,
                        'ext':          video_extension.decode('utf-8'),
                        'format':       u'NA',
@@ -1471,7 +1503,7 @@ class YoutubePlaylistIE(InfoExtractor):
 
        _VALID_URL = r'(?:https?://)?(?:\w+\.)?youtube\.com/(?:(?:course|view_play_list|my_playlists|artist|playlist)\?.*?(p|a|list)=|user/.*?/user/|p/|user/.*?#[pg]/c/)(?:PL)?([0-9A-Za-z-_]+)(?:/.*?/([0-9A-Za-z_-]+))?.*'
        _TEMPLATE_URL = 'http://www.youtube.com/%s?%s=%s&page=%s&gl=US&hl=en'
-       _VIDEO_INDICATOR_TEMPLATE = r'/watch\?v=(.+?)&amp;list=(PL)?%s&'
+       _VIDEO_INDICATOR_TEMPLATE = r'/watch\?v=(.+?)&amp;list=.*?%s'
        _MORE_PAGES_INDICATOR = r'yt-uix-pager-next'
        IE_NAME = u'youtube:playlist'
 
@@ -2956,10 +2988,198 @@ 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]
+
+
 class GooglePlusIE(InfoExtractor):
        """Information extractor for plus.google.com."""
 
-       _VALID_URL = r'(?:https://)?plus\.google\.com/(\d+)/posts/(\w+)'
+       _VALID_URL = r'(?:https://)?plus\.google\.com/(?:\w+/)*?(\d+)/posts/(\w+)'
        IE_NAME = u'plus.google'
 
        def __init__(self, downloader=None):
@@ -2998,9 +3218,9 @@ class GooglePlusIE(InfoExtractor):
                video_extension = 'flv'
 
                # Step 1, Retrieve post webpage to extract further information
+               self.report_extract_entry(post_url)
                request = urllib2.Request(post_url)
                try:
-                       self.report_extract_entry(post_url)
                        webpage = urllib2.urlopen(request).read()
                except (urllib2.URLError, httplib.HTTPException, socket.error), err:
                        self._downloader.trouble(u'ERROR: Unable to retrieve entry webpage: %s' % str(err))
@@ -3012,7 +3232,7 @@ class GooglePlusIE(InfoExtractor):
                mobj = re.search(pattern, webpage)
                if mobj:
                        upload_date = mobj.group(1)
-                       """Convert timestring to a format suitable for filename"""
+                       # Convert timestring to a format suitable for filename
                        upload_date = datetime.datetime.strptime(upload_date, "%Y-%m-%d")
                        upload_date = upload_date.strftime('%Y%m%d')
                self.report_date(upload_date)
@@ -3026,9 +3246,9 @@ class GooglePlusIE(InfoExtractor):
                self.report_uploader(uploader)
 
                # Extract title
-               """Get the first line for title"""
+               # Get the first line for title
                video_title = u'NA'
-               pattern = r'<meta name\=\"Description\" content\=\"(.*?)[\s<"]'
+               pattern = r'<meta name\=\"Description\" content\=\"(.*?)[\n<"]'
                mobj = re.search(pattern, webpage)
                if mobj:
                        video_title = mobj.group(1)
@@ -3054,7 +3274,7 @@ class GooglePlusIE(InfoExtractor):
                """Extract video links of all sizes"""
                pattern = '\d+,\d+,(\d+),"(http\://redirector\.googlevideo\.com.*?)"'
                mobj = re.findall(pattern, webpage)
-               if mobj is None:
+               if len(mobj) == 0:
                        self._downloader.trouble(u'ERROR: unable to extract video links')
 
                # Sort in resolution
@@ -3065,12 +3285,12 @@ class GooglePlusIE(InfoExtractor):
                # Only get the url. The resolution part in the tuple has no use anymore
                video_url = video_url[-1]
                # Treat escaped \u0026 style hex
-               video_url = unicode(video_url, "unicode_escape").encode("utf8")
+               video_url = unicode(video_url, "unicode_escape")
 
 
                return [{
                        'id':           video_id.decode('utf-8'),
-                       'url':          video_url.decode('utf-8'),
+                       'url':          video_url,
                        'uploader':     uploader.decode('utf-8'),
                        'upload_date':  upload_date.decode('utf-8'),
                        'title':        video_title.decode('utf-8'),
@@ -3078,4 +3298,3 @@ class GooglePlusIE(InfoExtractor):
                        'format':       u'NA',
                        'player_url':   None,
                }]
-
index 527dc3a3d73d4c671c922dca261046390117bdce..f2e2aa1fadf5e8c8c1962ef11851820802550ce0 100644 (file)
@@ -71,13 +71,14 @@ class FFmpegExtractAudioPP(PostProcessor):
 
        @staticmethod
        def detect_executables():
-               available = {'avprobe' : False, 'avconv' : False, 'ffmpeg' : False, 'ffprobe' : False}
-               for path in os.environ["PATH"].split(os.pathsep):
-                       for program in available.keys():
-                               exe_file = os.path.join(path, program)
-                               if os.path.isfile(exe_file) and os.access(exe_file, os.X_OK):
-                                       available[program] = exe_file
-               return available
+               def executable(exe):
+                       try:
+                               subprocess.check_output([exe, '-version'])
+                       except OSError:
+                               return False
+                       return exe
+               programs = ['avprobe', 'avconv', 'ffmpeg', 'ffprobe']
+               return dict((program, executable(program)) for program in programs)
 
        def get_audio_codec(self, path):
                if not self._exes['ffprobe'] and not self._exes['avprobe']: return None
@@ -142,14 +143,20 @@ class FFmpegExtractAudioPP(PostProcessor):
                                extension = 'mp3'
                                more_opts = []
                                if self._preferredquality is not None:
-                                       more_opts += [self._exes['avconv'] and '-b:a' or '-ab', self._preferredquality]
+                                       if int(self._preferredquality) < 10:
+                                               more_opts += [self._exes['avconv'] and '-q:a' or '-aq', self._preferredquality]
+                                       else:
+                                               more_opts += [self._exes['avconv'] and '-b:a' or '-ab', self._preferredquality]
                else:
                        # We convert the audio (lossy)
                        acodec = {'mp3': 'libmp3lame', 'aac': 'aac', 'm4a': 'aac', 'vorbis': 'libvorbis', 'wav': None}[self._preferredcodec]
                        extension = self._preferredcodec
                        more_opts = []
                        if self._preferredquality is not None:
-                               more_opts += [self._exes['avconv'] and '-b:a' or '-ab', self._preferredquality]
+                               if int(self._preferredquality) < 10:
+                                       more_opts += [self._exes['avconv'] and '-q:a' or '-aq', self._preferredquality]
+                               else:
+                                       more_opts += [self._exes['avconv'] and '-b:a' or '-ab', self._preferredquality]
                        if self._preferredcodec == 'aac':
                                more_opts += ['-f', 'adts']
                        if self._preferredcodec == 'm4a':
index fc8101f82600229b9d2671e7a7a01f98fa6624fd..15a3ec4cf8563f7d44b7c429d24e07ef7c92abbd 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'
@@ -186,16 +186,18 @@ def parseOpts():
        general.add_option('-r', '--rate-limit',
                        dest='ratelimit', metavar='LIMIT', help='download rate limit (e.g. 50k or 44.6m)')
        general.add_option('-R', '--retries',
-                       dest='retries', metavar='RETRIES', help='number of retries (default is 10)', default=10)
+                       dest='retries', metavar='RETRIES', help='number of retries (default is %default)', default=10)
        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',
+                       dest='user_agent', help='specify a custom user agent', metavar='UA')
        general.add_option('--list-extractors',
                        action='store_true', dest='list_extractors',
                        help='List all supported extractors and the URLs they would handle', default=False)
 
        selection.add_option('--playlist-start',
-                       dest='playliststart', metavar='NUMBER', help='playlist video to start at (default is 1)', default=1)
+                       dest='playliststart', metavar='NUMBER', help='playlist video to start at (default is %default)', default=1)
        selection.add_option('--playlist-end',
                        dest='playlistend', metavar='NUMBER', help='playlist video to end at (default is last)', default=-1)
        selection.add_option('--match-title', dest='matchtitle', metavar='REGEX',help='download only matching titles (regex or caseless sub-string)')
@@ -267,7 +269,7 @@ def parseOpts():
                        action='store_true', dest='autonumber',
                        help='number downloaded files starting from 00000', default=False)
        filesystem.add_option('-o', '--output',
-                       dest='outtmpl', metavar='TEMPLATE', help='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.')
+                       dest='outtmpl', metavar='TEMPLATE', help='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), %(extractor)s for the provider (youtube, metacafe, etc), %(id)s for the video id and %% for a literal percent. Use - to output to stdout.')
        filesystem.add_option('-a', '--batch-file',
                        dest='batchfile', metavar='FILE', help='file containing URLs to download (\'-\' for stdin)')
        filesystem.add_option('-w', '--no-overwrites',
@@ -296,8 +298,8 @@ def parseOpts():
                        help='convert video files to audio-only files (requires ffmpeg or avconv and ffprobe or avprobe)')
        postproc.add_option('--audio-format', metavar='FORMAT', dest='audioformat', default='best',
                        help='"best", "aac", "vorbis", "mp3", "m4a", or "wav"; best by default')
-       postproc.add_option('--audio-quality', metavar='QUALITY', dest='audioquality', default='128K',
-                       help='ffmpeg/avconv audio bitrate specification, 128k by default')
+       postproc.add_option('--audio-quality', metavar='QUALITY', dest='audioquality', default='5',
+                       help='ffmpeg/avconv audio quality specification, insert a value between 0 (better) and 9 (worse) for VBR or a specific bitrate like 128K (default 5)')
        postproc.add_option('-k', '--keep-video', action='store_true', dest='keepvideo', default=False,
                        help='keeps the video file on disk after the post-processing; the video is erased by default')
 
@@ -351,6 +353,8 @@ def gen_extractors():
                MixcloudIE(),
                StanfordOpenClassroomIE(),
                MTVIE(),
+               YoukuIE(),
+               XNXXIE(),
                GooglePlusIE(),
 
                GenericIE()
@@ -369,6 +373,9 @@ def _real_main():
                                jar.load()
                except (IOError, OSError), err:
                        sys.exit(u'ERROR: unable to open cookie file')
+       # Set user agent
+       if opts.user_agent is not None:
+               std_headers['User-Agent'] = opts.user_agent
 
        # Dump user agent
        if opts.dump_user_agent:
@@ -445,6 +452,10 @@ def _real_main():
        if opts.extractaudio:
                if opts.audioformat not in ['best', 'aac', 'mp3', 'vorbis', 'm4a', 'wav']:
                        parser.error(u'invalid audio format specified')
+       if opts.audioquality:
+               opts.audioquality = opts.audioquality.strip('k').strip('K')
+               if not opts.audioquality.isdigit():
+                       parser.error(u'invalid audio quality specified')
 
        # File downloader
        fd = FileDownloader({
index 922e17eccfac611a1d90bf83e913383c9afce30d..839da17d0763c6f41373856762d28f198ae94821 100644 (file)
@@ -19,7 +19,7 @@ except ImportError:
        import StringIO
 
 std_headers = {
-       'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:5.0.1) Gecko/20100101 Firefox/5.0.1',
+       'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:10.0) Gecko/20100101 Firefox/10.0',
        'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
        'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
        'Accept-Encoding': 'gzip, deflate',