-
Notifications
You must be signed in to change notification settings - Fork 0
/
luckytubes.py
executable file
·239 lines (191 loc) · 6.65 KB
/
luckytubes.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
#!/usr/bin/python
# Copyright 2008 Scott Wolchok.
# This file is part of LuckyTubes.
# LuckyTubes is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# LuckyTubes is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with LuckyTubes. If not, see <http://www.gnu.org/licenses/>.
"""LuckyTubes -- play music off YouTube by feeling lucky!"""
import optparse
import os
import os.path
from cStringIO import StringIO
import sys
import urllib
import gdata.youtube.service
import youtubedl
VIDEO_SEARCH_URL = 'http://gdata.youtube.com/feeds/api/' + \
'videos?q=%s&max-results=10&v=2'
class Error(Exception):
"""LuckyTubes error."""
pass
class SearchFailedError(Error):
'''Search had no results.'''
pass
def daemonize():
"""Detach this process from the terminal. Only works on 'nix.
This function is
Copyright (C) 2005 Chad J. Schroeder
From http://code.activestate.com/recipes/278731/
In LuckyTubes, we don't bother closing file descriptors, we just
want to detach from terminal.
"""
def fork_once():
"""Fork the program, wrapping exceptions appropriately."""
try:
return os.fork()
except OSError, ex:
raise Error, "Couldn't daemonize: %s [%d]" % (ex.strerror, ex.errno)
pid = fork_once()
if pid == 0:
os.setsid()
pid = fork_once()
if pid == 0:
os.chdir('/')
else:
os._exit(0)
else:
os._exit(0)
# Drop stdin, stdout, stderr.
os.close(0)
os.close(1)
os.close(2)
os.open(os.devnull, os.O_RDWR)
os.dup2(0, 1)
os.dup2(0, 2)
class LuckyTubes(object):
"""Handle to LuckyTubes "service"."""
def __init__(self, quiet, cachedir, high_quality, override_ext):
"""Inits LuckyTubes with options.
Args:
nofork: True iff we should remain in the foreground instead of forking.
verbose: True iff we want chatty output indicating our progress.
cachedir: path to LuckyTubes cache directory.
"""
self.service = gdata.youtube.service.YouTubeService()
self.quiet = quiet
self.cachedir = cachedir
self.high_quality = high_quality
self.override_ext = override_ext
def vprint(self, out):
"""Print only if not quiet."""
if not self.quiet:
print out
def fetch_video(self, url, final_ext):
"""Fetch the video at the given (YouTube) URL. Includes caching,
extracting audio, etc.
Args:
url: A URL for an FLV to download.
video_filename: Name of the video file.
final_filename: Name of the resulting audio file.
Returns:
True if download and extract were successful, False otherwise.
"""
if self.quiet:
daemonize()
self.vprint('PID: %d' % os.getpid())
os.chdir(self.cachedir)
oldstdout = sys.stdout
sys.stdout = io = StringIO()
ytdl_opts = ['-t', url]
if self.high_quality:
ytdl_opts.append('-b')
youtubedl.main(ytdl_opts)
video_filename = None
DEST_PREFIX = '[download] Destination: '
for line in io.getvalue().split('\n'):
if line.startswith(DEST_PREFIX):
video_filename = line[len(DEST_PREFIX):].strip()
break
sys.stdout = oldstdout
print io.getvalue()
io.close()
final_filename = video_filename[:-3] + final_ext
if os.system('ffmpeg -i %s -vn -acodec copy %s' % (video_filename, final_filename)) != 0:
return None
else:
return final_filename
def get_watch_url(self, search_terms, racy=True):
"""Search for something on YouTube. Return a list of results.
Args:
search_terms: space-separated list to pass to YouTube search.
racy: whether to include restricted ("racy") videos.
Returns:
YouTube result URL for the video (suitable for browsing or input
to youtube-dl).
"""
query = gdata.youtube.service.YouTubeVideoQuery()
query.vq = search_terms
query.orderby = 'relevance'
if racy:
query.racy = 'include'
else:
query.racy = 'exclude'
feed = self.service.YouTubeQuery(query)
try:
return feed.entry[0].media.player.url
except (IndexError, e):
raise SearchFailedError(e)
def search_and_download(self, search_terms):
view_url = self.get_watch_url(search_terms)
self.download(view_url)
def download(self, url):
if self.override_ext is not None:
final_ext = self.override_ext
elif self.high_quality:
final_ext = 'm4a'
else:
final_ext = 'mp3'
self.vprint('Video URL: ' + url)
# System default in Windows (and pray it adds to playlist!)
if 'win' in sys.platform:
player = 'start '
# Show them the download.
os.system('start ' + self.cachedir)
else:
# TODO: configurable player...
player = 'amarok '
finalname = self.fetch_video(url, final_ext)
os.system(player + finalname)
def main(argv):
parser = optparse.OptionParser()
parser.add_option('-u', '--url-only', '--search-only',
dest='url_only',
action='store_true',
help='Only print the URL to watch the video')
parser.add_option('-q', '--quiet', dest='quiet', action='store_true',
help='Don\'t print any output')
parser.add_option('-b', '--best', '--high-quality', dest='high_quality',
action='store_true', help='Use high-quality video')
parser.add_option('-y', '--by-url', dest='by_url', action='store_true',
help='Don\'t search, just download by URL')
parser.add_option('-e', '--override-extension', dest='override_ext')
parser.add_option('--cache', dest='cachedir',
help='Directory to cache downloaded video/audio',
default=os.path.expanduser(os.path.join('~', '.luckytubes', '')))
(options, args) = parser.parse_args(argv)
if not os.path.exists(options.cachedir):
os.makedirs(options.cachedir)
lt = LuckyTubes(quiet=options.quiet,
cachedir=options.cachedir,
high_quality=options.high_quality,
override_ext=options.override_ext)
terms = ' '.join(args)
if options.url_only:
print lt.get_watch_url(terms)
elif options.by_url:
lt.download(terms)
else:
lt.search_and_download(terms)
if __name__ == '__main__':
if len(sys.argv) < 2:
print "Usage: luckytubes.py <search terms>"
print "Run with --help for options."
sys.exit(1)
main(sys.argv[1:])