libextractor

GNU libextractor
Log | Files | Refs | Submodules | README | LICENSE

gen_id3_testdata.sh (5384B)


      1 #!/bin/sh
      2 # Regenerate src/plugins/testdata/id3_test.mp3 for test_id3.c.
      3 #
      4 # Debian packages needed: python3, python3-mutagen, lame.
      5 #
      6 # The audio is a one second 440 Hz sine at 44100 Hz, mono, written as a
      7 # WAV by python and encoded by LAME so that the file carries a real
      8 # Xing/LAME tag -- that tag is the point of the exercise, and ffmpeg
      9 # writes its own ("Lavf lame") rather than LAME's, which would not
     10 # exercise the same fields.
     11 #
     12 # Everything is deterministic: no timestamps, no random data.  Note
     13 # that the LAME encoder version string ("LAME3.100") and the settings
     14 # summary derived from the LAME tag are asserted on literally in
     15 # test_id3.c; if you regenerate this with a different LAME release,
     16 # those two expectations have to be updated to match.
     17 #
     18 # The tag set is ID3v2.4 on purpose.  It exercises all four text
     19 # encodings in one file (TIT2 as UTF-8, TPE1 as UTF-16 with a byte
     20 # order mark, TALB as ISO-8859-1, the rest as written), and the USLT
     21 # and APIC frames are both longer than 127 bytes, which is where the
     22 # synchsafe frame size of 2.4 first differs from the plain integer of
     23 # 2.3.  A hand-written ID3v1 tag follows the audio; its comment and
     24 # year fields are the only ones the plugin should use, since the ID3v2
     25 # tag covers everything else.
     26 #
     27 # This file is part of libextractor; the generated data is CC0.
     28 set -e
     29 
     30 out="$(dirname "$0")/../src/plugins/testdata"
     31 tmp="$(mktemp -d)"
     32 trap 'rm -rf "$tmp"' 0
     33 
     34 python3 - "$tmp" <<'EOF'
     35 import math
     36 import os
     37 import struct
     38 import sys
     39 import wave
     40 
     41 tmp = sys.argv[1]
     42 w = wave.open(os.path.join(tmp, 'tone.wav'), 'wb')
     43 w.setnchannels(1)
     44 w.setsampwidth(2)
     45 w.setframerate(44100)
     46 w.writeframes(b''.join(struct.pack('<h', int(12000 * math.sin(2 * math.pi
     47                                                               * 440 * i
     48                                                               / 44100)))
     49                        for i in range(44100)))
     50 w.close()
     51 EOF
     52 
     53 lame -V 5 --vbr-new -m m -q 2 --quiet "$tmp/tone.wav" "$tmp/id3_test.mp3"
     54 
     55 python3 - "$tmp" <<'EOF'
     56 import os
     57 import struct
     58 import sys
     59 import zlib
     60 
     61 from mutagen.id3 import (ID3, APIC, COMM, PRIV, TALB, TBPM, TCOM, TCON, TCOP,
     62                          TDRC, TENC, TFLT, TIT2, TLAN, TMED, TOWN, TPE1, TPE3,
     63                          TPOS, TPUB, TRCK, TSSE, TXXX, UFID, USLT, WOAF, WOAR)
     64 
     65 tmp = sys.argv[1]
     66 path = os.path.join(tmp, 'id3_test.mp3')
     67 
     68 
     69 def png(side):
     70     """A deterministic side x side RGB PNG, roughly 300 bytes."""
     71     raw = b''.join(b'\x00' + bytes(((x * 7) % 256, (y * 11) % 256,
     72                                     ((x + y) * 3) % 256)[c]
     73                                    for x in range(side) for c in range(3))
     74                    for y in range(side))
     75 
     76     def chunk(tag, data):
     77         c = tag + data
     78         return (struct.pack('>I', len(data)) + c
     79                 + struct.pack('>I', zlib.crc32(c) & 0xFFFFFFFF))
     80 
     81     return (b'\x89PNG\r\n\x1a\n'
     82             + chunk(b'IHDR', struct.pack('>IIBBBBB', side, side, 8, 2, 0, 0, 0))
     83             + chunk(b'IDAT', zlib.compress(raw, 9))
     84             + chunk(b'IEND', b''))
     85 
     86 
     87 lyrics = ('One two three four five six seven eight nine ten. '
     88           'This lyric frame exists only to be longer than one hundred '
     89           'and twenty seven bytes, so that the synchsafe frame size of '
     90           'ID3v2.4 differs from a plain big endian integer here.')
     91 
     92 tags = ID3()
     93 tags.add(TIT2(encoding=3, text=['Grüße aus Köln']))
     94 tags.add(TPE1(encoding=1, text=['Bœuf Trio']))
     95 tags.add(TALB(encoding=0, text=['Café Sessions']))
     96 tags.add(TCON(encoding=0, text=['(52)']))
     97 tags.add(TRCK(encoding=0, text=['3/12']))
     98 tags.add(TPOS(encoding=0, text=['1/2']))
     99 tags.add(TDRC(encoding=0, text=['2024-03-14']))
    100 tags.add(TCOM(encoding=0, text=['Ada Lovelace']))
    101 tags.add(TPE3(encoding=0, text=['Grace Hopper']))
    102 tags.add(TPUB(encoding=0, text=['GNU Records']))
    103 tags.add(TCOP(encoding=0, text=['2026 GNU libextractor']))
    104 tags.add(TLAN(encoding=0, text=['deu']))
    105 tags.add(TBPM(encoding=0, text=['128']))
    106 tags.add(TENC(encoding=0, text=['GNU libextractor test rig']))
    107 tags.add(TSSE(encoding=0, text=['LAME 3.100 -V 5 --vbr-new']))
    108 tags.add(TOWN(encoding=0, text=['licensee@example.org']))
    109 tags.add(TFLT(encoding=0, text=['MPG/3']))
    110 tags.add(TMED(encoding=0, text=['DIG/A/T']))
    111 tags.add(USLT(encoding=3, lang='eng', desc='', text=lyrics))
    112 tags.add(UFID(owner='http://musicbrainz.org',
    113               data=b'9f1a3e6c-0000-4000-8000-0123456789ab'))
    114 tags.add(WOAF(url='https://www.gnu.org/software/libextractor/'))
    115 tags.add(WOAR(url='https://www.gnu.org/'))
    116 tags.add(PRIV(owner='com.apple.iTunes', data=b'\x00\x01\x02\x03'))
    117 tags.add(TXXX(encoding=0, desc='apID', text=['buyer@example.org']))
    118 tags.add(APIC(encoding=0, mime='image/png', type=3, desc='Cover',
    119               data=png(8)))
    120 tags.save(path, v2_version=4, v1=0)
    121 
    122 # An ID3v1 tag written by hand, so that its fields are visibly
    123 # different from the ID3v2 ones: only the comment and the year should
    124 # ever reach the caller, because the ID3v2 tag covers everything else.
    125 def field(s, n):
    126     return s.encode('latin-1')[:n].ljust(n, b'\x00')
    127 
    128 
    129 v1 = (b'TAG' + field('ID3v1 Title', 30) + field('ID3v1 Artist', 30)
    130       + field('ID3v1 Album', 30) + field('1999', 4)
    131       + field('ID3v1 comment field', 30) + bytes([52]))
    132 assert len(v1) == 128
    133 with open(path, 'ab') as f:
    134     f.write(v1)
    135 EOF
    136 
    137 cp "$tmp/id3_test.mp3" "$out/id3_test.mp3"
    138 ls -l "$out/id3_test.mp3"