#!/usr/bin/env python3 """Wie weit daneben landet ein Browser-Seek in dieser MP3? Ein Browser springt in einer VBR-Datei über die Xing-TOC: 100 Stützstellen, linear interpoliert -> Byte-Position. Er meldet danach die ANGEFRAGTE Zeit, dekodiert aber ab dem gefundenen Byte. Dieses Skript rechnet beides nach: Byte-Position wie der Browser, echte Zeit per Frame-Zählung. """ import sys, struct path = sys.argv[1] targets = [float(x) for x in sys.argv[2].split(',')] data = open(path, 'rb').read() BITRATES = [0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0] # MPEG1 Layer3 SRATES = [44100, 48000, 32000] def frame_len(hdr): if hdr[0] != 0xFF or (hdr[1] & 0xE0) != 0xE0: return None, None, None ver = (hdr[1] >> 3) & 3 # 3 = MPEG1 layer = (hdr[1] >> 1) & 3 # 1 = Layer III if ver != 3 or layer != 1: return None, None, None br = BITRATES[(hdr[2] >> 4) & 0xF] * 1000 sr = SRATES[(hdr[2] >> 2) & 3] pad = (hdr[2] >> 1) & 1 if br == 0 or sr == 0: return None, None, None return int(144 * br / sr) + pad, sr, 1152 # --- alle Frames einmal durchzählen: Byte -> exakte Zeit ------------------- pos, table = 0, [] # (byte_offset, start_time) t = 0.0 while pos < len(data) - 4: ln, sr, spf = frame_len(data[pos:pos + 4]) if not ln: pos += 1 continue table.append((pos, t)) t += spf / sr pos += ln print(f'{len(table)} frames, echte Dauer {t:.1f} s, Dateigroesse {len(data)} bytes') # --- Xing-TOC --------------------------------------------------------------- i = data.find(b'Xing') if i < 0: i = data.find(b'Info') flags = struct.unpack('>I', data[i + 4:i + 8])[0] p = i + 8 frames = struct.unpack('>I', data[p:p + 4])[0] if flags & 1 else None if flags & 1: p += 4 bytes_total = struct.unpack('>I', data[p:p + 4])[0] if flags & 2 else len(data) if flags & 2: p += 4 toc = list(data[p:p + 100]) if flags & 4 else None print(f'Xing: frames={frames}, bytes={bytes_total}, TOC={"ja" if toc else "nein"}') if not toc: sys.exit('kein TOC -> Browser extrapoliert rein linear') def real_time_at(byte): lo, best = 0, table[0][1] for off, tt in table: if off <= byte: best = tt else: break return best print(f'\n{"Ziel":>8} {"Byte(TOC)":>12} {"echte Zeit":>12} {"Fehler":>10}') for target in targets: pct = target / t * 100.0 a = int(pct) fa = toc[a] fb = toc[a + 1] if a < 99 else 256.0 frac = pct - a byte = int((fa + (fb - fa) * frac) / 256.0 * bytes_total) real = real_time_at(byte) lin_byte = int(target / t * len(data)) lin_real = real_time_at(lin_byte) print(f'{target:8.1f} {byte:12d} {real:12.1f} {real - target:+10.1f} s' f' | linear: {lin_real:8.1f} {lin_real - target:+8.1f} s')