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):
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
78 self.tags = tags.Tags(filename, opts)
82 filetype = opts.getfiletype(filename)
83 dir, base = os.path.split(filename)
84 base = os.path.splitext(base)[0]
85 base = base + opts['brwarning']
86 base = opts.cleanfilename(base)
87 if opts['treestructure'] != '':
88 # Standard format string
89 # See tags.Tags.format.__doc__
90 fmt = opts['treestructure']
91 dest = self.tags.format(fmt)
92 dest = opts.cleanfilename(dest)
94 self._outdir = os.path.dirname(dest)
95 self._outdir = os.path.join(opts['playerdir'], self._outdir)
96 self._outname = os.path.join(opts['playerdir'], dest)
99 head, dir = os.path.split(dir)
100 for i in range(opts['treedepth'] -1):
101 head, tail = os.path.split(head)
102 dir = os.path.join(tail, dir)
103 dir = opts.cleanfilename(dir)
106 self._outdir = os.path.join(opts['playerdir'], dir)
107 self._outname = os.path.join(self._outdir, base)
108 self._outname += '.' + helpers[opts['encoder']]['type']
109 self._wavname = os.path.join(opts['tempdir'], base) + '.wav'
112 if not os.path.isdir(self._outdir):
113 os.makedirs(self._outdir)
117 if opts['treedepth']:
119 for i in range(opts['treedepth']):
124 tail, head = os.path.split(tail)
127 job = task.SimpleTask(self, make_dirs, None,
128 rm_dirs, name="Creating dirs")
132 # Check the cache - if there is one
134 self._hash = self._cache.search(self._filename)
137 return self._cache.recover(self._hash, self._outname)
138 def undo_recover_cache():
139 if os.path.exists(self._outname):
140 os.unlink(self._outname)
142 outreq = os.stat(self._cache.file(self._hash)).st_size
143 job = task.SimpleTask(self, recover_cache, None,
145 outsize=outreq, name="Hitting Cache")
151 prog = conf.find_helper(filetype, 'decode')
152 tmpreq = opts.est_decoded_size(self._filename)
153 jobname = "Decoding %s" % filetype
154 if callable(helpers[prog]['cmd']):
156 func = helpers[prog]['cmd'](self._filename, self._wavname)
157 job = task.SimpleTask(self, func, None, undo_decode,
158 tmpsize=tmpreq, name=jobname)
161 args = conf.make_args(prog, self._filename,
163 job = task.Task(self, args, helpers[prog]['parser'],
164 undo_decode, tmpsize=tmpreq, name=jobname)
170 if opts.bin['normalize-audio']:
171 ncmd = [opts.bin['normalize-audio'], self._wavname]
172 job = task.Task(self, ncmd, lambda: '',
173 lambda: True, name="Normalizing")
177 if not opts['nonormal']:
178 opts.log(2, "'normalize-audio' binary not found, skipping.")
182 encoder = opts['encoder']
183 prog = helpers[encoder]
184 outreq = tmpreq / opts['compfactor']
185 jobname = "Encoding %s" % prog['type']
186 filter = prog['parser']
187 if callable(prog['cmd']):
188 func = prog['cmd'](self._wavname, self._outname)
189 job = task.SimpleTask(self, func, None, undo_encode,
190 outsize=outreq, name=jobname)
193 args = conf.make_args(encoder, self._wavname,
194 self._outname, opts['encopts'])
195 job = task.Task(self, args, filter,
196 undo_encode, outsize=outreq, name=jobname)
202 job = task.SimpleTask(self, undo_decode, None, undo_decode,
210 indexname = os.path.join(self._outdir, '2go.index')
212 tags.remove_from_index(self._outname, indexname)
215 self.tags.writeindex(self._outname, indexname)
217 job = task.SimpleTask(self, write_index, None,
218 undo_index, name='Writing index')
223 # tag files from the cache as well, brwarning may have changed
224 if not opts['notags']:
227 self.tags.write(self._outname)
229 if opts['verbosity'] > 3:
230 import sys, traceback
231 traceback.print_exc(file=sys.stdout)
234 job = task.SimpleTask(self, tag_output, None,
235 lambda: True, name="Tagging")
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)
243 self._cache.stash(self._outname, self._hash)
245 job = task.SimpleTask(self, cache_final, name="Caching result")
249 # Completion sentinel
250 job = task.SimpleTask(self, finish, None, start, name="Done")
254 if not opts['force']:
255 if os.path.exists(self._outname):
256 # In update mode, consider the track done if an existing file is older than the source
258 sourceinfo = os.stat(self._filename)
259 targetinfo = os.stat(self._outname)
260 if targetinfo.st_mtime >= sourceinfo.st_mtime:
261 opts.log(1, "Skipping up to date file: %s\n" % self._outname)
262 self._queue = tuple(tasks)
263 if self._hash and self._cache:
264 self._cache.release(self._hash)
265 for tsk in self._queue:
266 tsk._status = task.DONE
267 self.bytes = os.stat(self._outname).st_size
271 opts.log(1, "Replacing out of date file: %s\n" % self._outname)
272 else: # Consider the track done if the output file exists:
273 opts.log(1, "Skipping existing file: %s\n" % self._outname)
274 self._queue = tuple(tasks)
275 if self._hash and self._cache:
276 self._cache.release(self._hash)
277 for tsk in self._queue:
278 tsk._status = task.DONE
279 self.bytes = os.stat(self._outname).st_size
284 self._queue = tuple(tasks)
292 return self._queue[0]
301 for child in self._queue: