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 print "mp3togo %s\n" % mp3togo.version
152 print "<space> or 'p' to pause, <esc> or 'q' to quit\n"
153 c = ''
154 start_time = time.time()
155 good_ones = 0
156 bad_ones = 0
157 playlist.run()
158
159 try:
160 # Terminal sushi
161 fd = sys.stdin.fileno()
162 oldterm = termios.tcgetattr(fd)
163 newattr = termios.tcgetattr(fd)
164 newattr[3] = newattr[3] & ~termios.ICANON & ~termios.ECHO
165 termios.tcsetattr(fd, termios.TCSANOW, newattr)
166
167 oldflags = fcntl.fcntl(fd, fcntl.F_GETFL)
168 fcntl.fcntl(fd, fcntl.F_SETFL, oldflags | os.O_NONBLOCK)
169
170 if opts['verbosity']:
171 tryp = conf.try_print
172 else:
173 tryp = lambda x: None
174
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 = util.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 sys.exit(1)
241 pass
242 else:
243 opts.log(1, "Out of space. Exiting.")
244 break
245
246 pl.rm_track(trk)
247 if trk() == track.DONE:
248 good_ones += 1
249 else:
250 bad_ones += 1
251 trk.close()
252 del trk
253
254 tt = time.time() - track_start
255 ts = util.format_time(tt)
256 if ts:
257 tryp("Total time this track: %s\n\n" % ts)
258 else:
259 tryp('\n')
260
261 tm = time.time() - start_time
262 ts = util.format_time(tm)
263 if bad_ones:
264 if bad_ones == 1:
265 bad_str = ", 1 track failed."
266 else:
267 bad_str = ", %d tracks failed." % bad_ones
268 else:
269 bad_str = "."
270 #bytes = format_bytes(pl.produced_bytes) # you are using a new pool each time!
271 tryp("\n%d tracks done%s %s total time elapsed.\n" %
272 (good_ones, bad_str, ts))
273
274 finally:
275 # Fry some fish:
276 termios.tcsetattr(fd, termios.TCSAFLUSH, oldterm)
277 fcntl.fcntl(fd, fcntl.F_SETFL, oldflags)
278
279
280 if __name__ == "__main__": main(sys.argv)
281