Package ewa :: Module audio
[hide private]
[frames] | no frames]

Source Code for Module ewa.audio

  1  """ 
  2   
  3  This module is responsible for managing audio files: 
  4   
  5    * managing the main (content) audio files, located in 
  6      <audioroot>/main/path/to/audiofile 
  7    * managing extra audio files, located in 
  8      <audioroot/extra, in two subdirectories, 
  9      <audioroot/extra/masters and <audioroot/extra/transcoded. 
 10    * resolving audiofile symbolic names to files within 
 11      the managed hierarchy. 
 12    * creating combined (spliced) files according to rules passed 
 13      in. 
 14   
 15  """ 
 16  import os 
 17  import thread 
 18   
 19  from ewa.mp3 import get_vbr_bitrate_samplerate_mode, splice 
 20  from ewa.transcode import transcode 
 21  from ewa.logutil import warn 
 22  from ewa.rules import DefaultRule 
 23   
 24  path_join = os.path.join 
 25  path_exists = os.path.exists 
 26  getmtime = os.path.getmtime 
 27   
 28   
29 -def is_original(s):
30 return getattr(s, 'is_original', False)
31 32
33 -class AudioProviderException(RuntimeError):
34 pass
35 36
37 -class FileNotFound(AudioProviderException):
38 pass
39 40
41 -class BaseAudioProvider(object):
42 - def __init__(self, 43 basedir, 44 tolerate_vbr=True, 45 tolerate_broken=True):
46 self.basedir = os.path.normpath(os.path.abspath(basedir)) 47 self.tolerate_vbr = tolerate_vbr 48 self.tolerate_broken = tolerate_broken
49
50 - def get_main_path(self, audioname):
51 return path_join(self.basedir, 52 'main', 53 audioname)
54
55 - def get_extra_master_path(self, 56 audioname):
57 p = path_join(self.basedir, 58 'extra', 59 'master', 60 audioname) 61 if path_exists(p): 62 return p 63 raise FileNotFound(p)
64
65 - def get_extra_transcoded_path(self, 66 audioname, 67 mode, 68 bitrate, 69 samplerate, 70 create=False):
71 path = path_join(self.basedir, 72 'extra', 73 'transcoded', 74 str(bitrate), 75 str(samplerate), 76 mode, 77 audioname) 78 if not path.endswith('.mp3') or path.endswith('.MP3'): 79 path += '.mp3' 80 if create: 81 orig_path = self.get_extra_master_path(audioname) 82 if path_exists(path): 83 trans_mtime = getmtime(path) 84 orig_mtime = getmtime(orig_path) 85 if orig_mtime <= trans_mtime: 86 # no transcoding necessary, return 87 return path 88 # either transcoded file is out of date 89 # or it doesn't exist, transcode 90 transcode(orig_path, path, bitrate, samplerate, mode) 91 return path
92
93 - def get_playlist(self, audioname, rule, create=True):
94 symbols = list(rule(audioname)) 95 if not symbols: 96 return symbols 97 audiopath = self.get_main_path(audioname) 98 try: 99 (isvbr, bitrate, 100 samplerate, mode) = get_vbr_bitrate_samplerate_mode(audiopath) 101 except (InvalidAudioFormatException, TagException), exc: 102 if self.tolerate_broken: 103 warn('got %s for %s', exc, audiopath) 104 # no splicing, return default rule 105 symbols = list(DefaultRule()(audioname)) 106 isvbr = bitrate = samplerate = mode = None 107 else: 108 raise exc 109 else: 110 if isvbr: 111 warn("file is vbr: %s", audiopath) 112 if not self.tolerate_vbr: 113 raise AudioProviderException("vbr not supported: %s" \ 114 % audiopath) 115 else: 116 # just return the default rule for vbr 117 symbols = list(DefaultRule()(audioname)) 118 119 def resolve(x): 120 if is_original(x): 121 path = self.get_main_path(x) 122 if create and not path_exists(path): 123 raise FileNotFound(path) 124 return path 125 else: 126 try: 127 return self.get_extra_transcoded_path(x, 128 mode, 129 bitrate, 130 samplerate, 131 create) 132 except FileNotFound: 133 warn("extra audio not found: %s", x) 134 return None
135 136 return [y for y in (resolve(x) for x in symbols) if y]
137 138
139 -class FSAudioProvider(BaseAudioProvider):
140
141 - def __init__(self, basedir, tolerate_vbr=True, 142 tolerate_broken=True, targetdir=None):
143 super(FSAudioProvider, self).__init__(basedir, 144 tolerate_vbr, 145 tolerate_broken) 146 if targetdir is None: 147 targetdir = path_join(basedir, 'combined') 148 self.targetdir = os.path.abspath(targetdir)
149
150 - def get_combined_path(self, audioname):
151 return path_join(self.targetdir, audioname)
152
153 - def create_combined(self, audioname, rule, **spliceKwargs):
154 if audioname.startswith('/'): 155 audioname = audioname[1:] 156 mainpath = self.get_main_path(audioname) 157 playlist = self.get_playlist(audioname, rule) 158 target = self.get_combined_path(audioname) 159 parent = os.path.dirname(target) 160 if not path_exists(parent): 161 os.makedirs(parent) 162 renamed = '%s%d~%d~' % (target, 163 os.getpid(), 164 thread.get_ident()) 165 fp = open(renamed, 'wb') 166 for chunk in splice(playlist, mainpath, **spliceKwargs): 167 fp.write(chunk) 168 fp.close() 169 os.rename(renamed, target) 170 return target
171 172
173 -class StreamAudioProvider(BaseAudioProvider):
174
175 - def create_combined(self, audioname, rule, **spliceKwargs):
176 if audioname.startswith('/'): 177 audioname = audioname[1:] 178 mainpath = self.get_main_path(audioname) 179 playlist = self.get_playlist(audioname, rule) 180 return splice(playlist, mainpath, **spliceKwargs)
181 182 183 __all__ = ['AudioProviderException', 184 'FileNotFound', 185 'FSAudioProvider', 186 'StreamAudioProvider'] 187