source: trunk/mp3tool.py @ 1252

Revision 1252, 7.2 KB checked in by jukka, 13 years ago (diff)

Fixed #1055, spent 1.5h.

  • Property svn:executable set to *
Line 
1#!/usr/bin/env python
2
3# based on http://www.ibiblio.org/mp3info/
4
5__all__ = ['get_mp3_info', 'get_length']
6
7from cStringIO import StringIO
8import os
9from stat import ST_SIZE
10
11class struct(object):
12    pass
13
14FRAME_HEADER_SIZE = 4
15MIN_FRAME_SIZE = 21
16MIN_CONSEC_GOOD_FRAMES = 4
17
18layer_tab= [0, 3, 2, 1]
19
20frequencies = [
21   [22050,24000,16000,50000],
22   [44100,48000,32000,50000],
23   [11025,12000,8000,50000]
24]
25
26bitrate = [
27  [ # /* MPEG 2.0 */
28    [32,48,56,64,80,96,112,128,144,160,176,192,224,256],
29    [8,16,24,32,40,48,56,64,80,96,112,128,144,160],
30    [8,16,24,32,40,48,56,64,80,96,112,128,144,160]
31  ],
32
33  [ # /* MPEG 1.0 */
34    [32,64,96,128,160,192,224,256,288,320,352,384,416,448],
35    [32,48,56,64,80,96,112,128,160,192,224,256,320,384],
36    [32,40,48,56,64,80,96,112,128,160,192,224,256,320]
37  ]
38]
39
40frame_size_index = [24000, 72000, 72000]
41
42
43mode_text = [
44   "stereo", "joint stereo", "dual channel", "mono"
45]
46
47emphasis_text = [
48  "none", "50/15 microsecs", "reserved", "CCITT J 17"
49]
50
51
52def get_mp3_info(stream):     #mp3, scantype, fullscan_vbr):
53    had_error = 0
54    frame_type=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
55    seconds=0
56    total_rate=0
57    frames=0
58    frame_types=0
59    frames_so_far=0
60    vbr_median=-1
61    counter=0
62    data_start=0
63
64    mp3 = struct()
65    mp3.file = stream
66    mp3.badframes = 0
67
68    mp3.file.seek(0, 2)
69    mp3.datasize = mp3.file.tell()
70    mp3.file.seek(0, 0)
71    mp3.seconds=0
72
73    if True:    #(scantype == SCAN_FULL):
74        if (get_first_header(mp3,0L)):
75            data_start=mp3.file.tell()
76            while True:
77                bitrate=get_next_header(mp3)
78                if not bitrate:
79                    break
80                frame_type[15-bitrate] += 1
81                frames += 1
82            header = mp3.header     # memcpy(&header,&(mp3->header),sizeof(mp3header)) FIXME ???
83            for counter in xrange(15):
84                if(frame_type[counter]):
85                    frame_types += 1
86                    header.bitrate=counter
87                    frames_so_far += frame_type[counter]
88                    seconds += (frame_length(header) * frame_type[counter])/(header_bitrate(header)*125)
89                    total_rate += ((header_bitrate(header))*frame_type[counter])
90                    if((vbr_median == -1) and (frames_so_far >= frames/2)):
91                        vbr_median=counter
92            mp3.seconds=seconds
93            mp3.header.bitrate=vbr_median
94            mp3.vbr_average=total_rate/frames
95            mp3.frames=frames;
96            if(frame_types > 1):
97                mp3.vbr=1
98    return mp3, had_error
99
100
101def get_first_header(mp3, startpos):
102    #k
103    l=0
104    #c
105    # mp3header h, h2;
106    h = struct()
107    h2 = struct()
108    valid_start=0
109   
110    mp3.file.seek(startpos, 0)
111    while True:
112        while True:
113            c = mp3.file.read(1)
114            if len(c) == 0:
115                break
116            c = ord(c)
117            if c == 255:
118                break
119        if(c == 255):
120            mp3.file.seek(-1, 1)
121            valid_start=mp3.file.tell()
122            l = get_header(mp3.file, h)
123            if l:
124                mp3.file.seek(l-FRAME_HEADER_SIZE, 1)
125                k=1
126                while (k < MIN_CONSEC_GOOD_FRAMES) and (mp3.datasize-mp3.file.tell() >= FRAME_HEADER_SIZE):
127                    l = get_header(mp3.file, h2)
128                    if not l:
129                        break
130                    if(not sameConstant(h,h2)):
131                        break
132                    mp3.file.seek(l-FRAME_HEADER_SIZE, 1)
133                    k += 1
134                if(k == MIN_CONSEC_GOOD_FRAMES):
135                    mp3.file.seek( valid_start, 0)
136                    mp3.header = h2     # memcpy(&(mp3->header),&h2,sizeof(mp3header)) FIXME ???
137                    mp3.header_isvalid=1
138                    return 1
139        else:
140            return 0
141 
142    return 0
143
144#/* get_next_header() - read header at current position or look for
145#   the next valid header if there isn't one at the current position
146#*/
147def get_next_header(mp3):
148    l=0
149    # c
150    skip_bytes=0
151    #mp3header h
152    h = struct()
153 
154    while True:
155        while True:
156            c =mp3.file.read(1)
157            if mp3.file.tell() >= mp3.datasize:
158                break
159            c = ord(c)
160            if c == 255:
161                break
162            skip_bytes += 1
163        if(c == 255):
164            mp3.file.seek(-1 , 1)
165            l = get_header(mp3.file, h)
166            if l:
167                if skip_bytes:
168                    mp3.badframes += 1
169                mp3.file.seek(l-FRAME_HEADER_SIZE, 1)
170                return 15-h.bitrate
171            else:
172                skip_bytes += FRAME_HEADER_SIZE
173        else:
174            if skip_bytes:
175                mp3.badframes += 1
176            return 0
177
178
179#/* Get next MP3 frame header.
180#   Return codes:
181#   positive value = Frame Length of this header
182#   0 = No, we did not retrieve a valid frame header
183#*/
184
185def get_header(file_, header):
186    #unsigned char buffer[FRAME_HEADER_SIZE];
187    #int fl;
188
189    buffer = [ord(x) for x in file_.read(FRAME_HEADER_SIZE)]
190    if len(buffer) < FRAME_HEADER_SIZE:
191        header.sync = 0
192        return 0
193    header.sync=((buffer[0]<<4) | ((buffer[1]&0xE0)>>4))
194    if(buffer[1] & 0x10):
195        header.version=(buffer[1] >> 3) & 1;
196    else:
197        header.version=2
198    header.layer=(buffer[1] >> 1) & 3;
199    if ((header.sync != 0xFFE) or (header.layer != 1)):
200        header.sync = 0
201        return 0
202    header.crc=buffer[1] & 1
203    header.bitrate=(buffer[2] >> 4) & 0x0F
204    header.freq=(buffer[2] >> 2) & 0x3
205    header.padding=(buffer[2] >>1) & 0x1
206    header.extension=(buffer[2]) & 0x1
207    header.mode=(buffer[3] >> 6) & 0x3
208    header.mode_extension=(buffer[3] >> 4) & 0x3
209    header.copyright=(buffer[3] >> 3) & 0x1
210    header.original=(buffer[3] >> 2) & 0x1
211    header.emphasis=(buffer[3]) & 0x3
212
213    fl = frame_length(header)
214
215    return fl >= MIN_FRAME_SIZE and fl or 0
216
217def frame_length(header):
218    if header.sync == 0xFFE:
219        return (frame_size_index[3-header.layer]*((header.version & 1) + 1) *
220               header_bitrate(header)/header_frequency(header)) + header.padding
221    else:
222        return 1
223
224def header_layer(h):
225    return layer_tab[h.layer]
226
227def header_bitrate(h):
228    try:
229        br = bitrate[h.version & 1][3-h.layer][h.bitrate-1]
230    except IndexError:
231        br = 128
232    return br
233
234def header_frequency(h):
235    return frequencies[h.version][h.freq]
236
237def header_emphasis(h):
238    return emphasis_text[h.emphasis]
239
240def header_mode(h):
241    return mode_text[h.mode]
242
243def sameConstant(h1, h2):
244#    if((*(uint*)h1) == (*(uint*)h2)):          FIXME
245#        return 1
246
247    if((h1.version       == h2.version         ) and
248       (h1.layer         == h2.layer           ) and
249       (h1.crc           == h2.crc             ) and
250       (h1.freq          == h2.freq            ) and
251       (h1.mode          == h2.mode            ) and
252       (h1.copyright     == h2.copyright       ) and
253       (h1.original      == h2.original        ) and
254       (h1.emphasis      == h2.emphasis        )):
255        return 1
256    else:
257        return 0
258
259
260def get_length(s):
261    if type(s)==str:
262        s=StringIO(s)
263    mp3, errors = get_mp3_info(s)
264    return mp3.seconds
265
266if __name__ == "__main__":
267    import sys
268
269    secs = get_length(open(sys.argv[1]).read())
270    print '%d:%02d' % (secs // 60, secs % 60)
Note: See TracBrowser for help on using the repository browser.