Bugfix release
mp3togo/main.py
1 ### #!/usr/bin/python
2
3 # - main.py -
4 # This file is part of mp3togo
5
6 # Convert audio files to play on a mp3 player
7 #
8 # (c) Simeon Veldstra 2004, 2006 <reallifesim@gmail.com>
9 #
10 # This software is free.
11 #
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
16 #
17 # You may redistribute this program under the terms of the
18 # GNU General Public Licence version 2
19 # Available in this package or at http://www.fsf.org
20
21
22 """Convert a list of files.
23
24 Find a list of audio files in various
25 formats and qualities and convert them
26 to a uniform format suitable for a
27 portable player."""
28
29 import sys
30 import os
31 import time
32 import termios
33 import fcntl
34
35 import mp3togo
36 import mp3togo.filelist as filelist
37 import mp3togo.conf as conf
38 import mp3togo.track as track
39 import mp3togo.task as task
40 import mp3togo.pool as pool
41 import mp3togo.cache as cache
42 import mp3togo.cluster as cluster
43
44
45 def fail(mesg='', code=1):
46 if mesg:
47 print >>sys.stderr, mesg
48 sys.exit(code)
49
50 def main(argv):
51
52 # Read in configuration
53 try:
54 opts = conf.Options(argv)
55 except mp3togo.options.Fail, msg:
56 print conf.Options().usage()
57 fail(str(msg))
58
59 # Cluster mode is handled in the cluster module
60 if opts['clusterslave']:
61 cluster.slave(opts)
62 return 0
63
64 # Are we creating a cache?
65 if opts['makecache']:
66 if not opts['cachesize']:
67 opts.log(1, "Must specify a --cache-size to create a cache")
68 fail()
69 if cache.create(opts['makecache'], opts):
70 opts.log(1, "Cache successfully created at %s" % opts['makecache'])
71 return 0
72 else:
73 opts.log(1, "Create cache at %s failed" % opts['makecache'])
74 fail()
75
76 # Are we using a cache?
77 if opts['usecache']:
78 try:
79 cooked = cache.Cache(opts['usecache'], opts)
80 except:
81 opts.log(1, "Error reading cache at %s" % opts['usecache'])
82 else:
83 cooked = None
84
85 # Check for sanity
86 if not opts.bin[opts['encoder']]:
87 opts.log(1, "Encoder binary '%s' not found! Quiting." % opts['encoder'])
88 fail('')
89 if not opts.bin['normalize-audio'] and not opts['nonormal']:
90 opts.log(1, "'normalize-audio' binary not found. Normalization disabled.")
91 opts['nonormal'] = True
92 if not os.path.isdir(opts['playerdir']):
93 opts.log(1, "target_dir does not exist")
94 fail('')
95
96 # Build a playlist
97 playlist = filelist.FileList(opts)
98
99 # We could move these into FileList,
100 # but, we may want interactive import buttons in a GUI later.
101 if opts['playlist']:
102 playlist.addplaylist(opts['playlist'])
103
104 if opts['arg_files']:
105 playlist.addfiles(opts['arg_files'])
106
107 if opts['readxmms']:
108 try:
109 try:
110 playlist.addXMMS()
111 except conf.ErrorNoXMMSControl:
112 opts.log(1, "python-xmms is not installed. Can't open XMMS.")
113 except conf.ErrorXMMSNotRunning:
114 opts.log(1, "XMMS Does not seem to be running.")
115
116 if opts['readamarok']:
117 if opts.bin['dcop']:
118 try:
119 playlist.addAmarok()
120 except conf.ErrorAmarokNotRunning:
121 opts.log(1, "Amarok Does not seem to be running.")
122 else:
123 opts.log(1, "dcop is not installed. Can't open Amarok.")
124
125 # Offer a hint
126 if len(playlist) == 0:
127 print opts.usage()
128 opts.log(1, "No files to convert.")
129 fail('')
130
131 # Start work
132 if opts['cluster']:
133 cluster.slavedriver(playlist, opts, cooked)
134 return
135
136 # Nothing fancy
137 execute_sequential(playlist, opts, cooked)
138 return
139
140
141 def execute_sequential(playlist, opts, cooked=None):
142 """Run the conversions one at a time"""
143 print "mp3togo %s\n" % mp3togo.version
144 print "<space> or 'p' to pause, <esc> or 'q' to quit\n"
145 c = ''
146 start_time = time.time()
147 good_ones = 0
148 bad_ones = 0
149 playlist.run()
150
151 try:
152 # Terminal sushi
153 fd = sys.stdin.fileno()
154 oldterm = termios.tcgetattr(fd)
155 newattr = termios.tcgetattr(fd)
156 newattr[3] = newattr[3] & ~termios.ICANON & ~termios.ECHO
157 termios.tcsetattr(fd, termios.TCSANOW, newattr)
158
159 oldflags = fcntl.fcntl(fd, fcntl.F_GETFL)
160 fcntl.fcntl(fd, fcntl.F_SETFL, oldflags | os.O_NONBLOCK)
161
162 if opts['verbosity']:
163 tryp = conf.try_print
164 else:
165 tryp = lambda x: None
166
167 for name in playlist:
168 track_start = time.time()
169 tryp("(%d/%d) %s: \n" % (playlist.cur_file() + 1, len(playlist), name))
170 trk = track.Track(name, opts, cooked)
171 pl = pool.Pool(opts) # make a new one for every track? Huh?
172 if pl.add_track(trk):
173 tsk = trk.getfirst()
174 try:
175 while tsk:
176 tsk.run()
177 if tsk.name in ('Start', 'Done', 'Cleaning'):
178 tsk.wait()
179 tsk = tsk.next()
180 continue
181 while tsk.status() == task.RUNNING:
182 #parse output
183 tryp("\r")
184 pcent = tsk.output()
185 if 0 < pcent < 101:
186 bar = '#' * int(pcent/5.0) + ('_' * (20 - int(pcent/5.0)))
187 else:
188 bar = ""
189 pcent = 0
190 tryp("%s: %3.d%% %s" % (tsk.name, int(pcent), bar))
191 # Get keyboard input
192 time.sleep(0.01)
193 for busy in range(5):
194 try:
195 c = sys.stdin.read(1)
196 break
197 except IOError: pass
198 if c in ('q', 'Q', chr(27)):
199 raise KeyboardInterrupt
200 elif c in (' ', 'p', 'P'):
201 if tsk.pause():
202 tryp("\r [PAUSED] hit <enter> to resume ")
203 while 1:
204 try:
205 c = sys.stdin.read(1)
206 tsk.run()
207 break
208 except IOError: pass
209 tm = tsk.elapsed_time()
210 ts = format_time(tm)
211 tsk.wait()
212 tryp("\r ")
213 if tsk.status() == task.DONE:
214 if ts:
215 tryp("\r%s: Done. %s elapsed.\n" % (tsk.name, ts))
216 else:
217 tryp("\r%s: Done.\n" % tsk.name)
218 tsk = tsk.next()
219 else:
220 tryp("\r%s: Failed. Cleaning up.\n" % tsk.name)
221 while tsk:
222 tryp("Undo: %s\n" % tsk.name)
223 tsk.undo()
224 tsk = tsk.prev()
225 except KeyboardInterrupt:
226 tsk.stop()
227 tryp("Stopping...\n")
228 while tsk:
229 tryp("Undo: %s\n" % tsk.name)
230 tsk.undo()
231 tsk = tsk.prev()
232 sys.exit(1)
233 pass
234 else:
235 opts.log(1, "Out of space. Exiting.")
236 break
237
238 pl.rm_track(trk)
239 if trk() == track.DONE:
240 good_ones += 1
241 else:
242 bad_ones += 1
243 trk.close()
244 del trk
245
246 tt = time.time() - track_start
247 ts = format_time(tt)
248 if ts:
249 tryp("Total time this track: %s\n\n" % ts)
250 else:
251 tryp('\n')
252
253 tm = time.time() - start_time
254 ts = format_time(tm)
255 if bad_ones:
256 if bad_ones == 1:
257 bad_str = ", 1 track failed."
258 else:
259 bad_str = ", %d tracks failed." % bad_ones
260 else:
261 bad_str = "."
262 #bytes = format_bytes(pl.produced_bytes) # you are using a new pool each time!
263 tryp("\n%d tracks done%s %s total time elapsed.\n" %
264 (good_ones, bad_str, ts))
265
266 finally:
267 # Fry some fish:
268 termios.tcsetattr(fd, termios.TCSAFLUSH, oldterm)
269 fcntl.fcntl(fd, fcntl.F_SETFL, oldflags)
270
271
272 def format_time(tm):
273 tm = int(tm)
274 if tm:
275 hr, min, sec = (tm/3600, (tm%3600)/60, (tm%3600)%60)
276 ts = "%ds " % sec
277 if min > 0:
278 ts = ("%dm " % min) + ts
279 if hr > 0:
280 ts = ("%dh " % hr) + ts
281 return ts[:-1]
282 else:
283 return ''
284
285 def format_bytes(bytes):
286 k = 1024.0
287 m = 1024 * k
288 g = 1024 * m
289 if 0 < bytes < k:
290 ret = str(int(bytes/k)) + " KB"
291 elif k <= bytes < m:
292 ret = str(int(bytes/k)) + " KB"
293 elif m <= bytes < g:
294 ret = str(int(bytes/m)) + " MB"
295 else:
296 ret = str(bytes) + " Bytes"
297
298
299 if __name__ == "__main__": main(sys.argv)
300