1 ###########################################################################
2 # Copyright (C) 2008 by Andrew Mahone
3 # <andrew.mahone@gmail.com>
5 # Copyright: See COPYING file that comes with this distribution
7 ###########################################################################
12 from cStringIO
import StringIO
14 from StringIO
import StringIO
15 from tempfile
import mkdtemp
16 from threading
import BoundedSemaphore
, RLock
17 from mutagen
import FileType
18 from audiomangler
import Config
, from_config
, FuncTask
, CLITask
, TaskSet
, Expr
, File
, util
20 class CodecMeta(type):
21 def __new__(cls
, name
, bases
, cls_dict
):
22 class_init
= cls_dict
.get('__classinit__',None)
24 cls_dict
['__classinit__'] = staticmethod(class_init
)
25 return super(CodecMeta
,cls
).__new
__(cls
, name
, bases
, cls_dict
)
27 def __init__(self
, name
, bases
, cls_dict
):
28 if callable(getattr(self
,'__classinit__',None)):
29 self
.__classinit
__(self
, name
, bases
, cls_dict
)
34 __metaclass__
= CodecMeta
36 def __classinit__(cls
, name
, bases
, cls_dict
):
37 if 'type_' in cls_dict
:
38 codec_map
[cls_dict
['type_']] = cls
41 def _conv_out_filename(cls
, filename
):
42 return ''.join((filename
.rsplit('.',1)[0],'.',cls
.ext
))
45 def from_wav_multi(cls
,indir
,infiles
,outfiles
):
46 if not getattr(cls
,'_from_wav_multi_cmd',None):
48 encopts
= Config
['encopts']
52 encopts
= tuple(encopts
.split())
53 args
= cls
._from
_wav
_multi
_cmd
.evaluate({
55 'infiles':tuple(infiles
),
56 'outfiles':tuple(outfiles
),
62 return (CLITask(args
=args
,stdin
='/dev/null',stdout
='/dev/null',stderr
=sys
.stderr
,background
=True))
65 def from_wav_pipe(cls
, infile
, outfile
):
66 if not getattr(cls
,'_from_wav_pipe_cmd',None):
68 encopts
= Config
['encopts']
72 encopts
= tuple(encopts
.split())
73 outfile
= cls
._conv
_out
_filename
(infile
)
80 'encoder': cls
.encoder
82 args
= cls
._from
_wav
_pipe
_cmd
.evaluate(env
)
84 if hasattr(cls
,'_from_wav_pipe_stdin'):
85 stdin
= cls
._from
_wav
_pipe
_stdin
.evaluate(env
)
86 return CLITask(args
=args
,stdin
=stdin
,stdout
='/dev/null',stderr
=sys
.stderr
,background
=True)
89 def to_wav_pipe(cls
, infile
, outfile
):
90 if not getattr(cls
,'_to_wav_pipe_cmd',None):
99 args
= cls
._to
_wav
_pipe
_cmd
.evaluate(env
)
101 if hasattr(cls
,'_to_wav_pipe_stdout'):
102 stdout
= cls
._to
_wav
_pipe
_stdout
.evaluate(env
)
103 return CLITask(args
=args
,stdin
='/dev/null',stdout
=stdout
,stderr
=sys
.stderr
,background
=False)
106 def add_replaygain(cls
,files
):
108 'replaygain':cls
.replaygain
,
111 args
= cls
._replaygain
_cmd
.evaluate(env
)
112 return CLITask(args
=args
,stdin
='/dev/null',stdout
='/dev/null',stderr
=sys
.stderr
,background
=False)
114 class MP3Codec(Codec
):
118 replaygain
= 'mp3gain'
119 _from_wav_multi_cmd
= Expr("(encoder,'--quiet')+encopts+('--noreplaygain','--nogapout',indir,'--nogaptags','--nogap')+infiles")
121 class WavPackCodec(Codec
):
126 replaygain
= 'wvgain'
127 _to_wav_pipe_cmd
= Expr("(decoder,'-q','-w',infile,'-o','-')")
128 _to_wav_pipe_stdout
= Expr("outfile")
129 _from_wav_pipe_cmd
= Expr("(encoder,'-q')+encopts+(infile,'-o',outfile)")
131 class FLACCodec(Codec
):
136 replaygain
= 'metaflac'
137 _to_wav_pipe_cmd
= Expr("(decoder,'-s','-c','-d',infile)")
138 _to_wav_pipe_stdout
= Expr("outfile")
139 _from_wav_pipe_cmd
= Expr("(encoder,'-s')+encopts+(infile,)")
141 class OggVorbisCodec(Codec
):
146 replaygain
= 'vorbisgain'
147 _to_wav_pipe_cmd
= Expr("(decoder,'-Q','-o','-',infile)")
148 _to_wav_pipe_stdout
= Expr("outfile")
149 _from_wav_pipe_cmd
= Expr("(encoder,'-Q')+encopts+('-o',outfile,infile)")
150 _replaygain_cmd
= Expr("(replaygain,'-q','-a')+files)")
152 def transcode_track(dtask
, etask
, sem
):
159 def check_and_copy_cover(fileset
,targetfiles
):
160 cover_sizes
= Config
['cover_sizes']
163 cover_out_filename
= Config
['cover_out_filename']
164 if not cover_out_filename
:
166 cover_out_filename
= Expr(cover_out_filename
)
167 cover_sizes
= cover_sizes
.split(',')
171 cover_filenames
= Config
['cover_filenames']
173 cover_filenames
= cover_filenames
.split(',')
176 for (infile
,targetfile
) in zip(fileset
,targetfiles
):
177 if infile
.meta
['dir'] in outdirs
: continue
179 for filename
in (os
.path
.join(infile
.meta
['dir'],f
) for f
in cover_filenames
):
181 d
= open(filename
).read()
182 i
= Image
.open(StringIO(d
))
187 tags
= [(value
.type,value
) for key
,value
in infile
.tags
.items() if key
.startswith('APIC') and \
188 hasattr(value
,'type') and value
.type in (0,3)]
189 tags
.sort(None,None,True)
194 i
= Image
.open(StringIO(d
))
200 for s
in cover_sizes
:
209 iw
= i
.resize((w
,h
),Image
.ADAPTIVE
)
210 filename
= os
.path
.join(os
.path
.split(targetfile
)[0],cover_out_filename
.evaluate({'size':s
}).encode(Config
['fs_encoding'],Config
['fs_encoding_err'] or 'replace'))
212 outdirs
.add(infile
.meta
['dir'])
214 def transcode_set(targetcodec
,fileset
,targetfiles
,alsem
,trsem
,workdirs
,workdirs_l
):
220 workdir
, pipefiles
= workdirs
.pop()
222 outfiles
= map(targetcodec
._conv
_out
_filename
,pipefiles
[:len(fileset
)])
223 if hasattr(targetcodec
,'_from_wav_pipe_cmd'):
224 for i
,p
,o
in zip(fileset
,pipefiles
,outfiles
):
226 dtask
= get_codec(i
).to_wav_pipe(i
.meta
['path'],p
)
227 etask
= targetcodec
.from_wav_pipe(p
,o
)
228 ttask
= FuncTask(background
=True,target
=transcode_track
,args
=(dtask
,etask
,trsem
))
231 bgprocs
.add(ttask
.run())
236 elif hasattr(targetcodec
,'_from_wav_multi_cmd'):
237 etask
= targetcodec
.from_wav_multi(workdir
,pipefiles
[:len(fileset
),outfiles
])
239 for i
,o
in zip(fileset
,pipefiles
):
240 task
= get_codec(i
).to_wav_pipe(i
.meta
['path'])
244 if hasattr(targetcodec
,'_replaygain_cmd'):
245 targetcodec
.add_replaygain(outfiles
).run()
246 for i
,o
,t
in zip(fileset
,outfiles
,targetfiles
):
250 targetdir
= os
.path
.split(t
)[0]
251 if targetdir
not in dirs
:
253 if not os
.path
.isdir(targetdir
):
254 os
.makedirs(targetdir
)
255 print "%s -> %s" %(i
.filename
,t
)
256 util
.move(o
.filename
,t
)
257 check_and_copy_cover(fileset
,targetfiles
)
261 workdirs
.add((workdir
,pipefiles
))
266 def sync_sets(sets
=[]):
268 semct
= int(Config
['jobs'])
269 except (ValueError,TypeError):
272 targetcodec
= Config
['type']
273 if ',' in targetcodec
:
274 allowedcodecs
= targetcodec
.split(',')
275 targetcodec
= allowedcodecs
[0]
276 allowedcodecs
= set(allowedcodecs
)
278 allowedcodecs
= set((targetcodec
,))
279 targetcodec
= get_codec(targetcodec
)
280 workdir
= Config
['workdir'] or Config
['base']
281 workdir
= mkdtemp(dir=workdir
,prefix
='audiomangler_work_')
282 if hasattr(targetcodec
,'_from_wav_pipe_cmd'):
283 if len(sets
) > semct
* 2:
284 alsem
= BoundedSemaphore(semct
)
287 trsem
= BoundedSemaphore(semct
)
289 elif hasattr(targetcodec
,'_from_wav_multi_cmd'):
291 alsem
= BoundedSemaphore(semct
)
292 numpipes
= max(len(s
) for s
in sets
)
295 for n
in range(semct
):
296 w
= os
.path
.join(workdir
,"%02d" % n
)
299 for m
in range(numpipes
):
300 pipes
.append(os
.path
.join(w
,"%02d.wav"%m
))
303 workdirs
.add((w
,pipes
))
305 targetfiles
= [f
.format(postadd
={'type':targetcodec
.type_
,'ext':targetcodec
.ext
}) for f
in fileset
]
306 if reduce(lambda x
,y
: x
and os
.path
.isfile(y
), targetfiles
, True):
307 check_and_copy_cover(fileset
,targetfiles
)
309 if reduce(lambda x
,y
: x
and y
.type_
in allowedcodecs
, fileset
, True):
310 targetfiles
= [f
.format() for f
in fileset
]
311 if not reduce(lambda x
,y
: x
and os
.path
.isfile(y
), targetfiles
, True):
315 targetdir
= os
.path
.split(t
)[0]
316 if targetdir
not in dirs
:
318 if not os
.path
.isdir(targetdir
):
319 os
.makedirs(targetdir
)
320 print "%s -> %s" % (i
.filename
,t
)
321 util
.copy(i
.filename
,t
)
322 check_and_copy_cover(fileset
,targetfiles
)
326 for task
in list(bgtasks
):
329 task
= FuncTask(background
=True,target
=transcode_set
,args
=(targetcodec
,fileset
,targetfiles
,alsem
,trsem
,workdirs
,workdirs_l
))
331 bgtasks
.add(task
.run())
336 for w
,ps
in workdirs
:
343 if isinstance(item
, FileType
):
344 item
= getattr(item
,'type_')
345 return codec_map
[item
]
347 __all__
= ['sync_sets']