Skip to content

Instantly share code, notes, and snippets.

@dojoe
Created February 26, 2024 22:25
Show Gist options
  • Save dojoe/b73dabbe5746ec879dadf5694e707e24 to your computer and use it in GitHub Desktop.
Save dojoe/b73dabbe5746ec879dadf5694e707e24 to your computer and use it in GitHub Desktop.
How Traktor gets its Stripe/Transient filenames from the Audio ID
# How to find the name of a Traktor Stripe or Transients file
# if you have the AUDIO_ID from collection.nml.
#
# Unlicense, yo. (https://unlicense.org/)
import base64, sys
# md5Step() converted from @Zunawe's implementation on github - thanks!
A = 0x67452301
B = 0xefcdab89
C = 0x98badcfe
D = 0x10325476
S = [
7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21,
]
K = [
0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee,
0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa,
0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed,
0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05,
0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039,
0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391,
]
#
# Bit-manipulation functions defined by the MD5 algorithm
#
F = lambda X, Y, Z: ((X & Y) | (~X & Z))
G = lambda X, Y, Z: ((X & Z) | (Y & ~Z))
H = lambda X, Y, Z: (X ^ Y ^ Z)
I = lambda X, Y, Z: (Y ^ (X | ~Z))
#
# Rotates a 32-bit word left by n bits
#
def rotateLeft(x, n):
x &= 0xFFFFFFFF
return ((x << n) | (x >> (32 - n))) & 0xFFFFFFFF
def md5Step(ctx, data):
input = [int.from_bytes(data[i*4:i*4+4], "little") for i in range(16)]
AA = ctx[0]
BB = ctx[1]
CC = ctx[2]
DD = ctx[3]
for i in range(64):
if i < 16:
E = F(BB, CC, DD)
j = i
elif i < 32:
E = G(BB, CC, DD)
j = ((i * 5) + 1) % 16
elif i < 48:
E = H(BB, CC, DD)
j = ((i * 3) + 5) % 16
else:
E = I(BB, CC, DD)
j = (i * 7) % 16
temp = DD
DD = CC
CC = BB
BB = BB + rotateLeft(AA + E + K[i] + input[j], S[i])
AA = temp
ctx[0] = (ctx[0] + AA) & 0xFFFFFFFF
ctx[1] = (ctx[1] + BB) & 0xFFFFFFFF
ctx[2] = (ctx[2] + CC) & 0xFFFFFFFF
ctx[3] = (ctx[3] + DD) & 0xFFFFFFFF
# MD5 variant used by Traktor
def traktorhash(audio_id_bytes):
ctx = [A, B, C, D]
for i in range(4):
md5Step(ctx, audio_id_bytes[i*64 : i*64+64])
# this is what a true md5 would do (after exactly 256 bytes of input):
# md5Step(ctx, bytes.fromhex("80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000"))
# and this is what Traktor does:
md5Step(ctx, bytes(64))
#return b"".join(x.to_bytes(4, "little") for x in ctx) # for debugging
return ctx
# Determine base32ish stripe filename and directory number from hash
def stripe_fname(hash):
alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345"
fname = []
for i in range(4):
for j in range(7):
fname.append(alphabet[(hash[i] >> 5*j) & 0x1F])
return hash[0] & 127, "".join(fname)
# Putting it all together
def audioid2stripe(audio_id_from_nml):
audio_id_bytes = base64.b64decode(audio_id_from_nml)
hash = traktorhash(audio_id_bytes)
dirnum, fname = stripe_fname(hash)
return "%03d/%s" % (dirnum, fname)
# Example use
print(audioid2stripe(sys.argv[1]))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment