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']:
226 self.tags.write(self._outname)
228 job = task.SimpleTask(self, tag_output, None,
229 lambda: True, name="Tagging")
233 # Cache the output if we had to go to the trouble of producing it
234 if not self._hash and self._cache:
235 self._hash = cache.checksum(self._filename)
237 self._cache.stash(self._outname, self._hash)
239 job = task.SimpleTask(self, cache_final, name="Caching result")
243 # Completion sentinel
244 job = task.SimpleTask(self, finish, None, start, name="Done")
248 if not opts['force']:
249 if os.path.exists(self._outname):
250 # In update mode, consider the track done if an existing file is older than the source
252 sourceinfo = os.stat(self._filename)
253 targetinfo = os.stat(self._outname)
254 if targetinfo.st_mtime >= sourceinfo.st_mtime:
255 opts.log(1, "Skipping up to date file: %s\n" % self._outname)
256 self._queue = tuple(tasks)
257 if self._hash and self._cache:
258 self._cache.release(self._hash)
259 for tsk in self._queue:
260 tsk._status = task.DONE
261 self.bytes = os.stat(self._outname).st_size
265 opts.log(1, "Replacing out of date file: %s\n" % self._outname)
266 else: # Consider the track done if the output file exists:
267 opts.log(1, "Skipping existing file: %s\n" % self._outname)
268 self._queue = tuple(tasks)
269 if self._hash and self._cache:
270 self._cache.release(self._hash)
271 for tsk in self._queue:
272 tsk._status = task.DONE
273 self.bytes = os.stat(self._outname).st_size
278 self._queue = tuple(tasks)
286 return self._queue[0]
295 for child in self._queue: