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 # shouldn't print if verbose is 0:
144 if opts['verbosity']:
145 tryp = conf.try_print
146 else:
147 tryp = lambda x: None
148
149 tryp("mp3togo %s\n\n" % mp3togo.version)
150 tryp("<space> or 'p' to pause, <esc> or 'q' to quit\n\n")
151 c = ''
152 start_time = time.time()
153 good_ones = 0
154 bad_ones = 0
155 playlist.run()
156
157 try:
158 # Terminal sushi
159 raw = False
160 if opts['verbosity']:
161 try:
162 fd = sys.stdin.fileno()
163 oldterm = termios.tcgetattr(fd)
164 newattr = termios.tcgetattr(fd)
165 newattr[3] = newattr[3] & ~termios.ICANON & ~termios.ECHO
166 termios.tcsetattr(fd, termios.TCSANOW, newattr)
167 oldflags = fcntl.fcntl(fd, fcntl.F_GETFL)
168 fcntl.fcntl(fd, fcntl.F_SETFL, oldflags | os.O_NONBLOCK)
169 raw = True
170 except:
171 fail('Error setting the terminal to raw mode.\nIs this a tty?')
172
173
174 pl = pool.Pool(opts) # make a new one for every track? Huh?
175 for name in playlist:
176 track_start = time.time()
177 tryp("(%d/%d) %s: \n" % (playlist.cur_file() + 1, len(playlist), name))
178 trk = track.Track(name, opts, cooked)
179 #pl = pool.Pool(opts) # make a new one for every track? Huh?
180 if pl.add_track(trk):
181 tsk = trk.getfirst()
182 try:
183 while tsk:
184 tsk.run()
185 if tsk.name in ('Start', 'Done', 'Cleaning'):
186 tsk.wait()
187 tsk = tsk.next()
188 continue
189 while tsk.status() == task.RUNNING:
190 #parse output
191 tryp("\r")
192 pcent = tsk.output()
193 if 0 < pcent < 101:
194 bar = '#' * int(pcent/5.0) + ('_' * (20 - int(pcent/5.0)))
195 else:
196 bar = ""
197 pcent = 0
198 tryp("%s: %3.d%% %s" % (tsk.name, int(pcent), bar))
199 # Get keyboard input
200 time.sleep(0.01)
201 for busy in range(5):
202 try:
203 c = sys.stdin.read(1)
204 break
205 except IOError: pass
206 if c in ('q', 'Q', chr(27)):
207 raise KeyboardInterrupt
208 elif c in (' ', 'p', 'P'):
209 if tsk.pause():
210 tryp("\r [PAUSED] hit <enter> to resume ")
211 while 1:
212 try:
213 c = sys.stdin.read(1)
214 tsk.run()
215 break
216 except IOError: pass
217 tm = tsk.elapsed_time()
218 ts = format_time(tm)
219 tsk.wait()
220 tryp("\r ")
221 if tsk.status() == task.DONE:
222 if ts:
223 tryp("\r%s: Done. %s elapsed.\n" % (tsk.name, ts))
224 else:
225 tryp("\r%s: Done.\n" % tsk.name)
226 tsk = tsk.next()
227 else:
228 tryp("\r%s: Failed. Cleaning up.\n" % tsk.name)
229 while tsk:
230 tryp("Undo: %s\n" % tsk.name)
231 tsk.undo()
232 tsk = tsk.prev()
233 except KeyboardInterrupt:
234 tsk.stop()
235 tryp("Stopping...\n")
236 while tsk:
237 tryp("Undo: %s\n" % tsk.name)
238 tsk.undo()
239 tsk = tsk.prev()
240 break
241 else:
242 opts.log(1, "Out of space. Exiting.")
243 break
244
245 pl.rm_track(trk)
246 if trk() == track.DONE:
247 good_ones += 1
248 else:
249 bad_ones += 1
250 trk.close()
251 del trk
252
253 tt = time.time() - track_start
254 ts = format_time(tt)
255 if ts:
256 tryp("Total time this track: %s\n\n" % ts)
257 else:
258 tryp('\n')
259
260 tm = time.time() - start_time
261 ts = format_time(tm)
262 if bad_ones:
263 if bad_ones == 1:
264 bad_str = ", 1 track failed."
265 else:
266 bad_str = ", %d tracks failed." % bad_ones
267 else:
268 bad_str = "."
269 bytes = format_bytes(pl.produced_bytes)
270 tryp("\n%s written for %d tracks%s %s total time elapsed.\n" %
271 (bytes, good_ones, bad_str, ts))
272
273 finally:
274 # Fry some fish:
275 if raw:
276 termios.tcsetattr(fd, termios.TCSAFLUSH, oldterm)
277 fcntl.fcntl(fd, fcntl.F_SETFL, oldflags)
278
279
280 def format_time(tm):
281 tm = int(tm)
282 if tm:
283 hr, min, sec = (tm/3600, (tm%3600)/60, (tm%3600)%60)
284 ts = "%ds " % sec
285 if min > 0:
286 ts = ("%dm " % min) + ts
287 if hr > 0:
288 ts = ("%dh " % hr) + ts
289 return ts[:-1]
290 else:
291 return ''
292
293 def format_bytes(bytes):
294 k = 1024.0
295 m = 1024 * k
296 g = 1024 * m
297 if k <= bytes < m:
298 return "%.2f KB" % (bytes/k)
299 elif m <= bytes: # < g:
300 return "%.2f MB" % (bytes/m)
301 else:
302 return str(bytes) + " Bytes"
303
304
305 if __name__ == "__main__": main(sys.argv)
306