mp3togo version 0.5.2
mp3togo/helpers.py
1 # helpers.py: functions to interpret coder program output.
2 # mp3togo Portable Music Manager
3 #
4 # Copyright 2005: Simeon Veldstra <reallifesim@gmail.com>
5 #
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
10 #
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.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc.
19 # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 #
21 # ***********************************************************
22 # The code to parse output from external programs is based on
23 # the jack CD ripping program by Arne Zellentin.
24 #
25 # 'jack' is responsible for my full hard disks
26 # and has inspired me to produce this program.
27 # Thanks for everything Arne.
28
29
30 import os
31 import shutil
32
33 import mp3togo.conf as conf
34
35
36 class frames:
37 def __init__(self):
38 self.frames = 0
39 self.tframes = 0
40
41 def parse_ogg123(buf):
42 s = buf.split('\r')
43 def sumtime(t):
44 sum = float(t.split(':')[0]) * 60
45 sum += float(t.split(':')[1])
46 return sum
47 for l in s:
48 if l.startswith('Time') and l.endswith('% '):
49 line = l.split(' ')
50 return sumtime(line[1])/sumtime(line[4]) * 100.0
51 return 0
52
53 def parse_mpg321(buf, f=frames()):
54 s = buf.split('\r')
55 if len(s) >= 2:
56 s = s[-2]
57 else:
58 s = s[-1]
59 if not f.tframes:
60 y0 = s.find('[')
61 y1 = s.find(']')
62 if y0 != -1 and y1 != -1:
63 try:
64 f.tframes = float(s[y0+1:y1])
65 except ValueError:
66 pass
67 if f.tframes:
68 try:
69 r = float(s.split()[1])
70 except ValueError:
71 r = f.tframes
72 if r < f.frames:
73 f.tframes = f.frames = 0
74 return 0
75 f.frames = r
76 return f.frames/f.tframes * 100.0
77 return 0
78
79 def parse_oggenc(buf):
80 # From "jack"
81 s = buf.split('\r')
82 if len(s) >= 2:
83 s = s[-2]
84 if len(s) == 1:
85 s = s[0]
86 y0 = s.find("[")
87 y1 = s.find("%]")
88 if y0 != -1 and y1 != -1:
89 percent = float(s[y0 + 1:y1])
90 else:
91 percent = 0
92 return percent
93
94 def parse_lame(buf):
95 # originaly from "jack"
96 s = buf.split('\r')
97 if len(s) >= 2:
98 s = s[-1] or s[-2]
99 if len(s) == 1: s=s[0]
100 if s.find("%") >= 0: # status reporting starts here
101 y = s.split("/")
102 y1 = y[1].split("(")[0]
103 percent = float(y[0]) / float(y1) * 100.0
104 elif s.find("Frame:") >= 0: # older versions, like 3.13
105 y = s.split("/")
106 y0 = y[0].split("[")[-1]
107 y1 = y[1].split("]")[0]
108 percent = float(y0) / float(y1) * 100.0
109 else:
110 percent = 0
111 return percent
112
113 def parse_flac_enc(buf):
114 # From "jack"
115 s = buf.split('\r')
116 if len (s) >= 2: s = s[-2]
117 if len (s) == 1: s = s[0]
118 y0 = s.find(": ")
119 y1 = s.find("%")
120 if y0 != -1 and y1 != -1:
121 return float(s[y0 + 1:y1])
122 else:
123 return 0
124
125 def parse_flac_dec(buf):
126 # I wrote this one myself
127 buf = buf.split('\r')
128 buf = buf[-1]
129 buf = buf.replace('%', '')
130 try:
131 return int(buf.split()[1])
132 except IndexError:
133 return 0
134
135 ### wav file "encoder/decoder"
136 def recode_wav(input, output, args=None):
137 """Factory returns function to copy wav file
138
139 Returned function takes no args and copies
140 input to output. Throws OSError on error."""
141 return lambda: shutil.copyfile(input, output)
142
143 def parse_m4a_dec(buf):
144 n = buf.find('% decoding')
145 # if n==1 then we are at single digit progress (i.e. 5%) and
146 # at the beginning of the buffer
147 if n == 1:
148 s = buf[0]
149 # else we are somewhere deeper in the buffer...check if we are at
150 # single or double digit progress by looking two characters back
151 elif buf[n-2] == '\n':
152 s = buf[n-1:n]
153 else:
154 s = buf[n-2:n]
155 return int(s)
156
157
158 ## Command line escape sequences:
159 #
160 # escape: for:
161 # ---------------------
162 # %% %
163 # %o output filename
164 # %i input filename
165 # %a args
166
167 helpers = {
168 'oggenc' : {'parser': parse_oggenc,
169 'cmd': 'oggenc %a -o %o %i',
170 'type': 'ogg',
171 'action': 'encode'},
172 'lame' : {'parser': parse_lame,
173 'cmd': 'lame --nohist %a %i %o',
174 'type': 'mp3',
175 'action': 'encode'},
176 'flac_enc':{'parser': parse_flac_enc,
177 'cmd': 'flac ???????',
178 'type': 'flac',
179 'action': 'encode'},
180 'wav_enc': {'parser': None,
181 'cmd': recode_wav,
182 'type': 'wav',
183 'action': 'encode'},
184 'ogg123' : {'parser': parse_ogg123,
185 'cmd': 'ogg123 -d wav -f %o %i',
186 'type': 'ogg',
187 'action': 'decode'},
188 'mpg321' : {'parser': parse_mpg321,
189 'cmd': 'mpg321 -v -w %o %i',
190 'type': 'mp3',
191 'action': 'decode'},
192 'flac_dec':{'parser': parse_flac_dec,
193 'cmd': 'flac --decode -F -o %o %i',
194 'type': 'flac',
195 'action': 'decode'},
196 'm4a_dec':{'parser': parse_m4a_dec,
197 'cmd': 'faad -o %o %i',
198 'type': 'm4a',
199 'action': 'decode'},
200 'wav_dec': {'parser': None,
201 'cmd': recode_wav,
202 'type': 'wav',
203 'action': 'decode'},
204 }
205
206
207 def find_helper(type, action):
208 """Return the name of the helper that performs action on type
209
210 Raises KeyError if type or action is unknown."""
211 return dict([(helpers[x]['type'], x)
212 for x in helpers.keys()
213 if helpers[x]['action'] == action]
214 )[type]
215
216 def make_args(helper, input, output, args=''):
217 """Substitute for command line args"""
218 esc = {'z': '%', 'i': "###input###", 'o': "###output###", 'a': args}
219 out = ""
220 fmt = helpers[helper]['cmd']
221 fmt = fmt.replace('%%', '%z')
222 fmt = fmt.split('%')
223 while fmt:
224 out += fmt[0]
225 if len(fmt) <= 1:
226 break
227 fmt = fmt[1:]
228 code = fmt[0] and fmt[0][0]
229 if code in esc.keys():
230 fmt[0] = esc[code] + fmt[0][1:]
231 else:
232 raise conf.ErrorBadFormat
233 out = out.split()
234 if "###input###" in out:
235 out[out.index("###input###")] = input
236 if "###output###" in out:
237 out[out.index("###output###")] = output
238 return out
239
240 def est_decoded_size(filename):
241 # This could be much better
242 tp = conf.getfiletype(filename)
243 factors = {'mp3': 16.5, 'ogg': 16.5, 'flac': 2.8, 'wav': 1, 'm4a': 16.5}
244 return os.stat(filename).st_size * factors[tp]