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