Skip to content

Commit

Permalink
Added better exception handling, fixed a ton of bugs, added setup.py.
Browse files Browse the repository at this point in the history
  • Loading branch information
nficano committed Sep 15, 2012
1 parent 193f9f8 commit 4eb9602
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 91 deletions.
81 changes: 43 additions & 38 deletions README.md
Expand Up @@ -29,18 +29,13 @@ The only features I see implementing in the near future are:
- Allow it to run as a command-line utility.
- Making it compatible with Python 3.

### Known bugs
- "Multiple videos returned" gets raised to frequently due to the lack
of codec/quality information I've mapped to the fmt code in the TT_ENCODING
dict. For more info see: [Wikipedia - YouTube Quality and codecs](http://en.wikipedia.org/wiki/YouTube#Quality_and_codecs)

## Usage Example

``` python
from youtube import YouTube

# not necessary, just for demo purposes
from pprint import pprint as pp
from pprint import pprint

yt = YouTube()

Expand All @@ -50,26 +45,17 @@ yt.url = "http://www.youtube.com/watch?v=Ik-RsDGPI5Y"
# Once set, you can see all the codec and quality options YouTube has made
# available for the perticular video by printing videos.

pp(yt.videos)

#[<Video: 3gp - 144p>,
# <Video: 3gp - 144p>,
# <Video: 3gp - 240p>,
# <Video: 3gp - 240p>,
# <Video: flv - 224p>,
# <Video: flv - 224p>,
# <Video: flv - 360p>,
# <Video: flv - 360p>,
# <Video: flv - 480p>,
# <Video: flv - 480p>,
# <Video: mp4 - 360p>,
# <Video: mp4 - 360p>,
# <Video: mp4 - 720p>,
# <Video: mp4 - 720p>,
# <Video: webm - 360p>,
# <Video: webm - 360p>,
# <Video: webm - 480p>,
# <Video: webm - 480p>]
pprint(yt.videos)

#[<Video: MPEG-4 Visual (.3gp) - 144p>,
# <Video: MPEG-4 Visual (.3gp) - 240p>,
# <Video: Sorenson H.263 (.flv) - 240p>,
# <Video: H.264 (.flv) - 360p>,
# <Video: H.264 (.flv) - 480p>,
# <Video: H.264 (.mp4) - 360p>,
# <Video: H.264 (.mp4) - 720p>,
# <Video: VP8 (.webm) - 360p>,
# <Video: VP8 (.webm) - 480p>]

# The filename is automatically generated based on the video title.
# You can override this by manually setting the filename.
Expand All @@ -84,28 +70,47 @@ yt.filename = 'Dancing Scene from Pulp Fiction'

# You can also filter the criteria by filetype.

pp(yt.filter('flv'))
pprint(yt.filter('flv'))

# [<Video: flv - 224p>,
# <Video: flv - 224p>,
# <Video: flv - 360p>,
# <Video: flv - 360p>,
# <Video: flv - 480p>,
# <Video: flv - 480p>]
#[<Video: Sorenson H.263 (.flv) - 240p>,
# <Video: H.264 (.flv) - 360p>,
# <Video: H.264 (.flv) - 480p>]

# and by resolution
pp(yt.filter(res='480p'))
pprint(yt.filter(res='480p'))

# [<Video: flv - 480p>,
# <Video: flv - 480p>,
# <Video: webm - 480p>,
# <Video: webm - 480p>]
#[<Video: H.264 (.flv) - 480p>,
#<Video: VP8 (.webm) - 480p>]

# to select a video by a specific resolution and filetype you can use the get
# method.

video = yt.get('mp4', '720p')

# NOTE: get() can only be used if and only if one object matches your criteria.
# for example:

pprint(yt.videos)

#[<Video: MPEG-4 Visual (.3gp) - 144p>,
# <Video: MPEG-4 Visual (.3gp) - 240p>,
# <Video: Sorenson H.263 (.flv) - 240p>,
# <Video: H.264 (.flv) - 360p>,
# <Video: H.264 (.flv) - 480p>,
# <Video: H.264 (.mp4) - 360p>,
# <Video: H.264 (.mp4) - 720p>,
# <Video: VP8 (.webm) - 360p>,
# <Video: VP8 (.webm) - 480p>]

# Notice we have two H.264 (.mp4) available to us.. now if we try to call get()
# on mp4..

video = yt.get('mp4')
# MultipleObjectsReturned: get() returned more than one object -- it returned 2!

# In this case, we'll need to specify both the codec (mp4) and resolution
# (either 360p or 720p).

# Okay, let's download it!
video.download()

Expand Down
12 changes: 0 additions & 12 deletions TODO

This file was deleted.

112 changes: 71 additions & 41 deletions youtube/youtube.py → pytube/__init__.py
Expand Up @@ -9,36 +9,54 @@

# YouTube media encoding options.
YT_ENCODING = {
5: (5, "flv", "224p"),
6: (6, "flv", "270p"),
13: (13, "3gp", "N/A"),
17: (17, "3gp", "144p"),
18: (18, "mp4", "360p"),
22: (22, "mp4", "720p"),
34: (34, "flv", "360p"),
35: (35, "flv", "480p"),
36: (36, "3gp", "240p"),
37: (37, "mp4", "1080p"),
38: (38, "mp4", "3072p"),
43: (43, "webm", "360p"),
44: (44, "webm", "480p"),
45: (45, "webm", "720p"),
46: (46, "webm", "1080p"),
82: (82, "mp4", "360p"),
83: (83, "mp4", "240p"),
84: (84, "mp4", "720p"),
85: (85, "mp4", "520p"),
100: (100, "webm", "360p"),
101: (101, "webm", "360p"),
102: (102, "webm", "720p")
#Flash Video
5: ["flv", "240p", "Sorenson H.263", "N/A", "0.25", "MP3", "64"],
6: ["flv", "270p", "Sorenson H.263", "N/A", "0.8", "MP3", "64"],
34: ["flv", "360p", "H.264", "Main", "0.5", "AAC", "128"],
35: ["flv", "480p", "H.264", "Main", "0.8-1", "AAC", "128"],

#3GP
36: ["3gp", "240p", "MPEG-4 Visual", "Simple", "0.17", "AAC", "38"],
13: ["3gp", "N/A", "MPEG-4 Visual", "N/A", "0.5", "AAC", "N/A"],
17: ["3gp", "144p", "MPEG-4 Visual", "Simple", "0.05", "AAC", "24"],

#MPEG-4
18: ["mp4", "360p", "H.264", "Baseline", "0.5", "AAC", "96"],
22: ["mp4", "720p", "H.264", "High", "2-2.9", "AAC", "192"],
37: ["mp4", "1080p", "H.264", "High", "3-4.3", "AAC", "192"],
38: ["mp4", "3072p", "H.264", "High", "3.5-5", "AAC", "192"],
82: ["mp4", "360p", "H.264", "3D", "0.5", "AAC", "96"],
83: ["mp4", "240p", "H.264", "3D", "0.5", "AAC", "96"],
84: ["mp4", "720p", "H.264", "3D", "2-2.9", "AAC", "152"],
85: ["mp4", "520p", "H.264", "3D", "2-2.9", "AAC", "152"],

#WebM
43: ["webm", "360p", "VP8", "N/A", "0.5", "Vorbis", "128"],
44: ["webm", "480p", "VP8", "N/A", "1", "Vorbis", "128"],
45: ["webm", "720p", "VP8", "N/A", "2", "Vorbis", "192"],
46: ["webm", "1080p", "VP8", "N/A", "N/A", "Vorbis", "192"],
100: ["webm", "360p", "VP8", "3D", "N/A", "Vorbis", "128"],
101: ["webm", "360p", "VP8", "3D", "N/A", "Vorbis", "192"],
102: ["webm", "720p", "VP8", "3D", "N/A", "Vorbis", "192"]
}

YT_ENCODING_KEYS = (
'extension', 'resolution', 'video_codec', 'profile', 'video_bitrate',
'audio_codec', 'audio_bitrate'
)


class MultipleObjectsReturned(Exception):
pass

class YouTubeError(Exception):
pass

class Video(object):
"""
Class representation of a single instance of a YouTube video.
"""
def __init__(self, extension, resolution, url, filename):
def __init__(self, url, filename, **attributes):
"""
Define the variables required to declare a new video.
Expand All @@ -48,10 +66,10 @@ def __init__(self, extension, resolution, url, filename):
url -- The url of the video. (e.g.: youtube.com/watch?v=..)
filename -- The filename (minus the extention) to save the video.
"""
self.extension = extension
self.resolution = resolution

self.url = url
self.filename = filename
self.__dict__.update(**attributes)

def download(self, path=None):
"""
Expand Down Expand Up @@ -85,14 +103,16 @@ def download(self, path=None):

def __repr__(self):
"""A cleaner representation of the class instance."""
return "<Video: %s - %s>" % (self.extension, self.resolution)
return "<Video: %s (.%s) - %s>" % (self.video_codec, self.extension,
self.resolution)

def __cmp__(self, other):
if type(other) == Video:
v1 = "%s %s" % (self.extension, self.resolution)
v2 = "%s %s" % (other.extension, other.resolution)
return cmp(v1, v2)


class YouTube(object):
_filename = None
_fmt_values = []
Expand Down Expand Up @@ -157,10 +177,13 @@ def get(self, extension=None, res=None):
continue
else:
result.append(v)
if len(result) is 1:
if not len(result):
return
elif len(result) is 1:
return result[0]
else:
raise Exception("Multiple videos returned")
raise MultipleObjectsReturned("get() returned more than one " \
"object -- it returned %d!" % len(result))

def filter(self, extension=None, res=None):
"""
Expand Down Expand Up @@ -219,10 +242,10 @@ def _get_video_info(self):
resolutions and formats into a list.
"""
querystring = urlencode({
'asv': 3,
'el': 'detailpage',
'hl': 'en_US',
'video_id': self.video_id
'asv': 3,
'el': 'detailpage',
'hl': 'en_US',
'video_id': self.video_id
})

self.title = None
Expand All @@ -233,10 +256,11 @@ def _get_video_info(self):
if response:
content = response.read()
data = parse_qs(content)
if data.get('status', [''])[0] == 'failure':
print('Error downloading video: %s' %
data.get('reason', ['Unknown reason'])[0])
return
if 'errorcode' in data:
error = data.get('reason', 'An unknown error has occurred')
if isinstance(error, list):
error = error.pop()
raise YouTubeError(error)

#Use my cool traversing method to extract the specific
#attribute from the response body.
Expand All @@ -253,12 +277,13 @@ def _get_video_info(self):
#a single empty element, so we'll skip those here.
continue
try:
fmt, ext, res = self._extract_fmt(video)
filename = "%s.%s" % (self.filename, ext)
except TypeError:
fmt, data = self._extract_fmt(video)
filename = "%s.%s" % (self.filename, data['extension'])
except TypeError, KeyError:
pass
else:
self.videos.append(Video(ext, res, url, filename))
v = Video(url, filename, **data)
self.videos.append(v)
self._fmt_values.append(fmt)
self.videos.sort()

Expand All @@ -274,7 +299,12 @@ def _extract_fmt(self, text):
itag = re.findall('itag=(\d+)', text)
if itag and len(itag) is 1:
itag = int(itag[0])
return YT_ENCODING.get(itag, None)
attr = YT_ENCODING.get(itag, None)
if not attr:
return itag, None
data = {}
map(lambda k, v: data.update({k: v}), YT_ENCODING_KEYS, attr)
return itag, data

def _extract_url(self, text):
"""
Expand Down
34 changes: 34 additions & 0 deletions setup.py
@@ -0,0 +1,34 @@
import os
from setuptools import setup

"""
PyTube
-------
"""

def read(fname):
return open(os.path.join(os.path.dirname(__file__), fname)).read()

setup(
name = "pytube",
version = "0.0.4",
author = "Nick Ficano",
author_email = "nficano@gmail.com",
description = "A lightwight, dependency-free YouTube Video downloading library",
license='The MIT License: http://www.opensource.org/licenses/mit-license.php',
keywords = "youtube downloader",
url = "https://github.com/NFicano/python-youtube-download",
download_url="https://github.com/NFicano/python-youtube-download/tarball/master",
packages=['pytube'],
long_description=read('README.md'),
classifiers=[
"Development Status :: - Beta",
"Environment :: Console"
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 2.6",
"Programming Language :: Python :: 2.7",
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: Utilities",
],
)
Empty file removed youtube/__init__.py
Empty file.

0 comments on commit 4eb9602

Please sign in to comment.