Added --no-tags option and an except clause to tagging code.
mp3togo/track.py
1 # - track.py -
2 # This file is part of mp3togo
3
4 # Convert audio files to play on a mp3 player
5 # Manage the transform of a single file
6 #
7 # (c) Simeon Veldstra 2006 <reallifesim@gmail.com>
8 #
9 # This software is free.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You may redistribute this program under the terms of the
17 # GNU General Public Licence version 2
18 # Available in this package or at http://www.fsf.org
19
20
21 import os
22 import sys
23 import threading
24
25 import mp3togo.conf as setup
26 import mp3togo.tags as tags
27 import mp3togo.task as task
28 import mp3togo.cache as cache
29 import mp3togo.helpers
30
31 helpers = mp3togo.helpers.helpers
32
33
34 READY = "ready"
35 RUNNING = "running"
36 DONE = 0
37 FAILED = None
38
39 DOGGFACTOR = 10
40 DMP3FACTOR = 10
41 DFLACFACTOR = 3
42
43
44 class Track:
45 """Encapsulate the transformation of a file."""
46
47 def __init__(self, filename, opts, cooked=None):
48
49 self.bytes = 0
50 self._filename = filename
51 self._queue = ()
52 self._state = FAILED
53 self._cache = cooked
54 self._hash = False
55
56 def undo_decode():
57 if os.path.exists(self._wavname):
58 os.unlink(self._wavname)
59 return True
60
61 def undo_encode():
62 if os.path.exists(self._outname):
63 os.unlink(self._outname)
64 return True
65
66 # Sentinel tasks
67 def finish():
68 if os.path.exists(self._outname):
69 self.bytes = os.stat(self._outname).st_size
70 self._state = DONE
71 return DONE
72 def start():
73 self.bytes = 0
74 self._state = RUNNING
75 return RUNNING
76 def abort():
77 self._state = FAILED
78 return FAILED
79 job = task.SimpleTask(self, start, None, abort, name="Start")
80 tasks = [job]
81 del job
82
83 # Read the tags
84 # Do this early so that 'treestructure' can access them
85 self.tags = tags.Tags(filename)
86 self.tags.read()
87
88 # Names
89 filetype = setup.getfiletype(filename)
90 dir, base = os.path.split(filename)
91 base = os.path.splitext(base)[0]
92 base = base + opts['brwarning']
93 base = opts.cleanfilename(base)
94 if opts['treestructure'] != '':
95 # Standard format string
96 # See tags.Tags.format.__doc__
97 fmt = opts['treestructure']
98 dest = self.tags.format(fmt)
99
100 self._outdir = os.path.dirname(dest)
101 self._outdir = os.path.join(opts['playerdir'], self._outdir)
102 self._outname = os.path.join(opts['playerdir'], dest)
103 else:
104 if opts['treedepth']:
105 head, dir = os.path.split(dir)
106 for i in range(opts['treedepth'] -1):
107 head, tail = os.path.split(head)
108 dir = os.path.join(tail, dir)
109 dir = opts.cleanfilename(dir)
110 else:
111 dir = ''
112 self._outdir = os.path.join(opts['playerdir'], dir)
113 self._outname = os.path.join(self._outdir, base)
114 self._outname += '.' + helpers[opts['encoder']]['type']
115 self._wavname = os.path.join(opts['tempdir'], base) + '.wav'
116
117 def make_dirs():
118 if not os.path.isdir(self._outdir):
119 os.makedirs(self._outdir)
120 return True
121
122 def rm_dirs():
123 if opts['treedepth']:
124 tail = self._outdir
125 for i in range(opts['treedepth']):
126 try:
127 os.rmdir(tail)
128 except OSError:
129 return True
130 tail, head = os.path.split(tail)
131 return True
132
133 job = task.SimpleTask(self, make_dirs, None,
134 rm_dirs, name="Creating dirs")
135 tasks.append(job)
136 del job
137
138 # Check the cache - if there is one
139 if self._cache:
140 self._hash = self._cache.search(self._filename)
141 if self._hash:
142 def recover_cache():
143 return self._cache.recover(self._hash, self._outname)
144 def undo_recover_cache():
145 if os.path.exists(self._outname):
146 os.unlink(self._outname)
147 return True
148 outreq = os.stat(self._cache.file(self._hash)).st_size
149 job = task.SimpleTask(self, recover_cache, None,
150 undo_recover_cache,
151 outsize=outreq, name="Hitting Cache")
152 tasks.append(job)
153 del job
154
155 # Decode
156 if not self._hash:
157 prog = mp3togo.helpers.find_helper(filetype, 'decode')
158 tmpreq = mp3togo.helpers.est_decoded_size(self._filename)
159 jobname = "Decoding %s" % filetype
160 if callable(helpers[prog]['cmd']):
161 # SimpleTask
162 func = helpers[prog]['cmd'](self._filename, self._wavname)
163 job = task.SimpleTask(self, func, None, undo_decode,
164 tmpsize=tmpreq, name=jobname)
165 else:
166 # Task
167 args = mp3togo.helpers.make_args(prog, self._filename,
168 self._wavname)
169 job = task.Task(self, args, helpers[prog]['parser'],
170 undo_decode, tmpsize=tmpreq, name=jobname)
171 tasks.append(job)
172 del job
173
174 # Normalize
175 if not self._hash:
176 if opts.bin['normalize-audio']:
177 ncmd = [opts.bin['normalize-audio'], self._wavname]
178 job = task.Task(self, ncmd, lambda: '',
179 lambda: True, name="Normalizing")
180 tasks.append(job)
181 del job
182 else:
183 if not opts['nonormal']:
184 opts.log(2, "'normalize-audio' binary not found, skipping.")
185
186 # Encode
187 if not self._hash:
188 encoder = opts['encoder']
189 prog = helpers[encoder]
190 outreq = tmpreq / opts['compfactor']
191 jobname = "Encoding %s" % prog['type']
192 filter = prog['parser']
193 if callable(prog['cmd']):
194 func = prog['cmd'](self._wavname, self._outname)
195 job = task.SimpleTask(self, func, None, undo_encode,
196 outsize=outreq, name=jobname)
197 else:
198 # Task
199 args = mp3togo.helpers.make_args(encoder, self._wavname,
200 self._outname, opts['encopts'])
201 job = task.Task(self, args, filter,
202 undo_encode, outsize=outreq, name=jobname)
203 tasks.append(job)
204 del job
205
206 # Clean up wav
207 if not self._hash:
208 job = task.SimpleTask(self, undo_decode, None, undo_decode,
209 tmpsize=-tmpreq,
210 name='Cleaning')
211 tasks.append(job)
212 del job
213
214 # Write index file
215 if opts['index']:
216 indexname = os.path.join(self._outdir, '2go.index')
217 def undo_index():
218 tags.remove_from_index(self._outname, indexname)
219 return True
220 def write_index():
221 self.tags.writeindex(self._outname, indexname)
222 return True
223 job = task.SimpleTask(self, write_index, None,
224 undo_index, name='Writing index')
225 tasks.append(job)
226 del job
227
228 # Tag
229 # tag files from the cache as well, brwarning may have changed
230 if not opts['notags']:
231 def tag_output():
232 self.tags.write(self._outname)
233 return True
234 job = task.SimpleTask(self, tag_output, None,
235 lambda: True, name="Tagging")
236 tasks.append(job)
237 del job
238
239 # Cache the output if we had to go to the trouble of producing it
240 if not self._hash and self._cache:
241 self._hash = cache.checksum(self._filename)
242 def cache_final():
243 self._cache.stash(self._outname, self._hash)
244 return True
245 job = task.SimpleTask(self, cache_final, name="Caching result")
246 tasks.append(job)
247 del job
248
249 # Completion sentinel
250 job = task.SimpleTask(self, finish, None, start, name="Done")
251 tasks.append(job)
252 del job
253
254 # Consider the track done if the output file exists:
255 if os.path.exists(self._outname) and not opts['force']:
256 opts.log(1, "Skipping existing file: %s\n" % self._outname)
257 self._queue = tuple(tasks)
258 if self._hash and self._cache:
259 self._cache.release(self._hash)
260 for tsk in self._queue:
261 tsk._status = task.DONE
262 self._state = DONE
263 return
264
265 # Ready to go
266 self._queue = tuple(tasks)
267 self._state = READY
268 return
269
270 def tasks(self):
271 return self._queue
272
273 def getfirst(self):
274 return self._queue[0]
275
276 def __call__(self):
277 return self._state
278
279 def __del__(self):
280 self.close()
281
282 def close(self):
283 for child in self._queue:
284 child._parent = None
285 self._queue = ()
286
287