2 # This file is part of mp3togo
4 # Convert audio files to play on a mp3 player
5 # Manage the transform of a single file
7 # (c) Simeon Veldstra 2006 <reallifesim@gmail.com>
9 # This software is free.
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.
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
25 import mp3togo.conf as conf
26 import mp3togo.tags as tags
27 import mp3togo.task as task
28 import mp3togo.cache as cache
29 from mp3togo.helpers import helpers
38 """Encapsulate the transformation of a file."""
40 def __init__(self, filename, opts, cooked=None, intags=None):
43 self._filename = filename
50 if os.path.exists(self._wavname):
51 os.unlink(self._wavname)
55 if os.path.exists(self._outname):
56 os.unlink(self._outname)
61 if os.path.exists(self._outname):
62 self.bytes = os.stat(self._outname).st_size
72 job = task.SimpleTask(self, start, None, abort, name="Start")
77 # Do this early so that 'treestructure' can access them
81 self.tags = tags.Tags(filename, opts)
85 filetype = opts.getfiletype(filename)
86 dir, base = os.path.split(filename)
87 base = os.path.splitext(base)[0]
88 base = base + opts['brwarning']
89 base = opts.cleanfilename(base)
90 if opts['treestructure'] != '':
91 # Standard format string
92 # See tags.Tags.format.__doc__
93 fmt = opts['treestructure']
94 dest = self.tags.format(fmt)
95 dest = opts.cleanfilename(dest)
97 self._outdir = os.path.dirname(dest)
98 self._outdir = os.path.join(opts['playerdir'], self._outdir)
99 self._outname = os.path.join(opts['playerdir'], dest)
101 if opts['treedepth']:
102 head, dir = os.path.split(dir)
103 for i in range(opts['treedepth'] -1):
104 head, tail = os.path.split(head)
105 dir = os.path.join(tail, dir)
106 dir = opts.cleanfilename(dir)
109 self._outdir = os.path.join(opts['playerdir'], dir)
110 self._outname = os.path.join(self._outdir, base)
111 self._outname += '.' + helpers[opts['encoder']]['type']
112 self._wavname = os.path.join(opts['tempdir'], base) + '.wav'
115 if not os.path.isdir(self._outdir):
116 os.makedirs(self._outdir)
120 if opts['treedepth']:
122 for i in range(opts['treedepth']):
127 tail, head = os.path.split(tail)
130 job = task.SimpleTask(self, make_dirs, None,
131 rm_dirs, name="Creating dirs")
135 # Check the cache - if there is one
137 self._hash = self._cache.search(self._filename)
140 return self._cache.recover(self._hash, self._outname)
141 def undo_recover_cache():
142 if os.path.exists(self._outname):
143 os.unlink(self._outname)
145 outreq = os.stat(self._cache.file(self._hash)).st_size
146 job = task.SimpleTask(self, recover_cache, None,
148 outsize=outreq, name="Hitting Cache")
154 prog = conf.find_helper(filetype, 'decode')
155 tmpreq = opts.est_decoded_size(self._filename)
156 jobname = "Decoding %s" % filetype
157 if callable(helpers[prog]['cmd']):
159 func = helpers[prog]['cmd'](self._filename, self._wavname)
160 job = task.SimpleTask(self, func, None, undo_decode,
161 tmpsize=tmpreq, name=jobname)
164 args = conf.make_args(prog, self._filename,
166 job = task.Task(self, args, helpers[prog]['parser'],
167 undo_decode, tmpsize=tmpreq, name=jobname)
173 if opts.bin['normalize-audio']:
174 ncmd = [opts.bin['normalize-audio'], self._wavname]
175 job = task.Task(self, ncmd, lambda: '',
176 lambda: True, name="Normalizing")
180 if not opts['nonormal']:
181 opts.log(2, "'normalize-audio' binary not found, skipping.")
185 encoder = opts['encoder']
186 prog = helpers[encoder]
187 outreq = tmpreq / opts['compfactor']
188 jobname = "Encoding %s" % prog['type']
189 filter = prog['parser']
190 if callable(prog['cmd']):
191 func = prog['cmd'](self._wavname, self._outname)
192 job = task.SimpleTask(self, func, None, undo_encode,
193 outsize=outreq, name=jobname)
196 args = conf.make_args(encoder, self._wavname,
197 self._outname, opts['encopts'])
198 job = task.Task(self, args, filter,
199 undo_encode, outsize=outreq, name=jobname)
205 job = task.SimpleTask(self, undo_decode, None, undo_decode,
213 indexname = os.path.join(self._outdir, '2go.index')
215 tags.remove_from_index(self._outname, indexname)
218 self.tags.writeindex(self._outname, indexname)
220 job = task.SimpleTask(self, write_index, None,
221 undo_index, name='Writing index')
226 # tag files from the cache as well, brwarning may have changed
227 if not opts['notags']:
230 self.tags.write(self._outname)
232 if opts['verbosity'] > 3:
233 import sys, traceback
234 traceback.print_exc(file=sys.stdout)
237 job = task.SimpleTask(self, tag_output, None,
238 lambda: True, name="Tagging")
242 # Cache the output if we had to go to the trouble of producing it
243 if not self._hash and self._cache:
244 self._hash = cache.checksum(self._filename)
246 self._cache.stash(self._outname, self._hash)
248 job = task.SimpleTask(self, cache_final, name="Caching result")
252 # Completion sentinel
253 job = task.SimpleTask(self, finish, None, start, name="Done")
257 if not opts['force']:
258 if os.path.exists(self._outname):
259 # In update mode, consider the track done if an existing file is older than the source
261 sourceinfo = os.stat(self._filename)
262 targetinfo = os.stat(self._outname)
263 if targetinfo.st_mtime >= sourceinfo.st_mtime:
264 opts.log(1, "Skipping up to date file: %s\n" % self._outname)
265 self._queue = tuple(tasks)
266 if self._hash and self._cache:
267 self._cache.release(self._hash)
268 for tsk in self._queue:
269 tsk._status = task.DONE
270 self.bytes = os.stat(self._outname).st_size
274 opts.log(1, "Replacing out of date file: %s\n" % self._outname)
275 else: # Consider the track done if the output file exists:
276 opts.log(1, "Skipping existing file: %s\n" % self._outname)
277 self._queue = tuple(tasks)
278 if self._hash and self._cache:
279 self._cache.release(self._hash)
280 for tsk in self._queue:
281 tsk._status = task.DONE
282 self.bytes = os.stat(self._outname).st_size
287 self._queue = tuple(tasks)
295 return self._queue[0]
304 for child in self._queue: