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
31 #helpers = mp3togo.helpers.helpers
45 """Encapsulate the transformation of a file."""
47 def __init__(self, filename, opts, cooked=None):
50 self._filename = filename
57 if os.path.exists(self._wavname):
58 os.unlink(self._wavname)
62 if os.path.exists(self._outname):
63 os.unlink(self._outname)
68 if os.path.exists(self._outname):
69 self.bytes = os.stat(self._outname).st_size
79 job = task.SimpleTask(self, start, None, abort, name="Start")
84 # Do this early so that 'treestructure' can access them
85 self.tags = tags.Tags(filename, opts)
89 filetype = opts.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 dest = opts.cleanfilename(dest)
101 self._outdir = os.path.dirname(dest)
102 self._outdir = os.path.join(opts['playerdir'], self._outdir)
103 self._outname = os.path.join(opts['playerdir'], dest)
105 if opts['treedepth']:
106 head, dir = os.path.split(dir)
107 for i in range(opts['treedepth'] -1):
108 head, tail = os.path.split(head)
109 dir = os.path.join(tail, dir)
110 dir = opts.cleanfilename(dir)
113 self._outdir = os.path.join(opts['playerdir'], dir)
114 self._outname = os.path.join(self._outdir, base)
115 self._outname += '.' + helpers[opts['encoder']]['type']
116 self._wavname = os.path.join(opts['tempdir'], base) + '.wav'
119 if not os.path.isdir(self._outdir):
120 os.makedirs(self._outdir)
124 if opts['treedepth']:
126 for i in range(opts['treedepth']):
131 tail, head = os.path.split(tail)
134 job = task.SimpleTask(self, make_dirs, None,
135 rm_dirs, name="Creating dirs")
139 # Check the cache - if there is one
141 self._hash = self._cache.search(self._filename)
144 return self._cache.recover(self._hash, self._outname)
145 def undo_recover_cache():
146 if os.path.exists(self._outname):
147 os.unlink(self._outname)
149 outreq = os.stat(self._cache.file(self._hash)).st_size
150 job = task.SimpleTask(self, recover_cache, None,
152 outsize=outreq, name="Hitting Cache")
158 prog = conf.find_helper(filetype, 'decode')
159 tmpreq = opts.est_decoded_size(self._filename)
160 jobname = "Decoding %s" % filetype
161 if callable(helpers[prog]['cmd']):
163 func = helpers[prog]['cmd'](self._filename, self._wavname)
164 job = task.SimpleTask(self, func, None, undo_decode,
165 tmpsize=tmpreq, name=jobname)
168 args = conf.make_args(prog, self._filename,
170 job = task.Task(self, args, helpers[prog]['parser'],
171 undo_decode, tmpsize=tmpreq, name=jobname)
177 if opts.bin['normalize-audio']:
178 ncmd = [opts.bin['normalize-audio'], self._wavname]
179 job = task.Task(self, ncmd, lambda: '',
180 lambda: True, name="Normalizing")
184 if not opts['nonormal']:
185 opts.log(2, "'normalize-audio' binary not found, skipping.")
189 encoder = opts['encoder']
190 prog = helpers[encoder]
191 outreq = tmpreq / opts['compfactor']
192 jobname = "Encoding %s" % prog['type']
193 filter = prog['parser']
194 if callable(prog['cmd']):
195 func = prog['cmd'](self._wavname, self._outname)
196 job = task.SimpleTask(self, func, None, undo_encode,
197 outsize=outreq, name=jobname)
200 args = conf.make_args(encoder, self._wavname,
201 self._outname, opts['encopts'])
202 job = task.Task(self, args, filter,
203 undo_encode, outsize=outreq, name=jobname)
209 job = task.SimpleTask(self, undo_decode, None, undo_decode,
217 indexname = os.path.join(self._outdir, '2go.index')
219 tags.remove_from_index(self._outname, indexname)
222 self.tags.writeindex(self._outname, indexname)
224 job = task.SimpleTask(self, write_index, None,
225 undo_index, name='Writing index')
230 # tag files from the cache as well, brwarning may have changed
231 if not opts['notags']:
233 self.tags.write(self._outname)
235 job = task.SimpleTask(self, tag_output, None,
236 lambda: True, name="Tagging")
240 # Cache the output if we had to go to the trouble of producing it
241 if not self._hash and self._cache:
242 self._hash = cache.checksum(self._filename)
244 self._cache.stash(self._outname, self._hash)
246 job = task.SimpleTask(self, cache_final, name="Caching result")
250 # Completion sentinel
251 job = task.SimpleTask(self, finish, None, start, name="Done")
255 ## Consider the track done if the output file exists:
256 #if os.path.exists(self._outname) and not opts['force']:
257 # opts.log(1, "Skipping existing file: %s\n" % self._outname)
258 # self._queue = tuple(tasks)
259 # if self._hash and self._cache:
260 # self._cache.release(self._hash)
261 # for tsk in self._queue:
262 # tsk._status = task.DONE
265 if not opts['force']:
266 if os.path.exists(self._outname):
267 # In update mode, consider the track done if an existing file is older than the source
269 sourceinfo = os.stat(self._filename)
270 targetinfo = os.stat(self._outname)
271 if targetinfo.st_mtime >= sourceinfo.st_mtime:
272 opts.log(1, "Skipping up to date file: %s\n" % self._outname)
273 self._queue = tuple(tasks)
274 if self._hash and self._cache:
275 self._cache.release(self._hash)
276 for tsk in self._queue:
277 tsk._status = task.DONE
281 opts.log(1, "Replacing out of date file: %s\n" % self._outname)
282 else: # Consider the track done if the output file exists:
283 opts.log(1, "Skipping existing file: %s\n" % self._outname)
284 self._queue = tuple(tasks)
285 if self._hash and self._cache:
286 self._cache.release(self._hash)
287 for tsk in self._queue:
288 tsk._status = task.DONE
293 self._queue = tuple(tasks)
301 return self._queue[0]
310 for child in self._queue: