pymuleparser/ED2K.py

261 lines
9.2 KiB
Python

#!/usr/bin/env python3
"""
#define FT_FILENAME 0x01 // <string>
#define FT_FILESIZE 0x02 // <uint32> (or <uint64> when supported)
#define FT_FILESIZE_HI 0x3A // <uint32>
#define FT_FILETYPE 0x03 // <string>
#define FT_FILEFORMAT 0x04 // <string>
#define FT_LASTSEENCOMPLETE 0x05 // <uint32>
#define TAG_PART_PATH "\x06" // <string>
#define TAG_PART_HASH "\x07"
#define FT_TRANSFERRED 0x08 // <uint32>
#define TAG_TRANSFERRED "\x08" // <uint32>
#define FT_GAPSTART 0x09 // <uint32>
#define TAG_GAPSTART "\x09" // <uint32>
#define FT_GAPEND 0x0A // <uint32>
#define TAG_GAPEND "\x0A" // <uint32>
#define FT_DESCRIPTION 0x0B // <string>
#define TAG_DESCRIPTION "\x0B" // <string>
#define TAG_PING "\x0C"
#define TAG_FAIL "\x0D"
#define TAG_PREFERENCE "\x0E"
#define FT_PARTFILENAME 0x12 // <string>
//#define FT_PRIORITY 0x13 // Not used anymore
#define FT_STATUS 0x14 // <uint32>
#define FT_SOURCES 0x15 // <uint32>
#define FT_PERMISSIONS 0x16 // <uint32>
//#define FT_ULPRIORITY 0x17 // Not used anymore
#define FT_DLPRIORITY 0x18 // Was 13
#define FT_ULPRIORITY 0x19 // Was 17
#define FT_COMPRESSION 0x1A
#define FT_CORRUPTED 0x1B
#define FT_KADLASTPUBLISHKEY 0x20 // <uint32>
#define FT_KADLASTPUBLISHSRC 0x21 // <uint32>
#define FT_FLAGS 0x22 // <uint32>
#define FT_DL_ACTIVE_TIME 0x23 // <uint32>
#define FT_CORRUPTEDPARTS 0x24 // <string>
#define FT_DL_PREVIEW 0x25
#define FT_KADLASTPUBLISHNOTES 0x26 // <uint32>
#define FT_AICH_HASH 0x27
#define FT_FILEHASH 0x28
#define FT_COMPLETE_SOURCES 0x30 // nr. of sources which share a complete version of the associated file (supported by eserver 16.46+)
#define FT_COLLECTIONAUTHOR 0x31
#define FT_COLLECTIONAUTHORKEY 0x32
#define FT_PUBLISHINFO 0x33 // <uint32>
#define FT_LASTSHARED 0x34 // <uint32>
#define FT_AICHHASHSET 0x35 // <uint32>
#define TAG_KADAICHHASHPUB "\x36" // <AICH Hash>
// statistic
#define FT_ATTRANSFERRED 0x50 // <uint32>
#define FT_ATREQUESTED 0x51 // <uint32>
#define FT_ATACCEPTED 0x52 // <uint32>
#define FT_CATEGORY 0x53 // <uint32>
#define FT_ATTRANSFERREDHI 0x54 // <uint32>
#define FT_MAXSOURCES 0x55 // <uint32>
#define FT_MEDIA_ARTIST 0xD0 // <string>
#define TAG_MEDIA_ARTIST "\xD0" // <string>
#define FT_MEDIA_ALBUM 0xD1 // <string>
#define TAG_MEDIA_ALBUM "\xD1" // <string>
#define FT_MEDIA_TITLE 0xD2 // <string>
#define TAG_MEDIA_TITLE "\xD2" // <string>
#define FT_MEDIA_LENGTH 0xD3 // <uint32> !!!
#define TAG_MEDIA_LENGTH "\xD3" // <uint32> !!!
#define FT_MEDIA_BITRATE 0xD4 // <uint32>
#define TAG_MEDIA_BITRATE "\xD4" // <uint32>
#define FT_MEDIA_CODEC 0xD5 // <string>
#define TAG_MEDIA_CODEC "\xD5" // <string>
#define FT_FILECOMMENT 0xF6 // <string>
#define FT_FILERATING 0xF7 // <uint8>
"""
import sys
import os
import binascii
from collections import namedtuple
METENTRY_FIELDS = ('FT_FILENAME', 'ED2KHASH', 'NUMPARTS', 'LASTCHANGED', 'PARTHASHES', 'FT_FILESIZE',
'FT_ATTRANSFERRED', 'FT_ATREQUESTED', 'FT_ATACCEPTED', 'FT_ATTRANSFERREDHI', 'FT_PARTFILENAME',
'FT_AICHHASHSET', 'FT_AICH_HASH', 'FT_ULPRIORITY', 'FT_KADLASTPUBLISHSRC')
MetEntry = namedtuple('met_entry', METENTRY_FIELDS)
HEADER_KNOWN_MET_FILE = b'\x0e'
HEADER_PART_MET_FILE = b'\xe0'
FT_FILENAME = 0x01
FT_FILESIZE = 0x02
FT_PARTFILENAME = 0x12
FT_PERMISSIONS = 0x16
FT_ULPRIORITY = 0x19
FT_KADLASTPUBLISHKEY = 0x20
FT_KADLASTPUBLISHSRC = 0x21
FT_FLAGS = 0x22
FT_KADLASTPUBLISHNOTES = 0x26
FT_AICH_HASH = 0x27
FT_LASTSHARED = 0x34
FT_AICHHASHSET = 0x35
FT_ATTRANSFERRED = 0x50
FT_ATREQUESTED = 0x51
FT_ATACCEPTED = 0x52
FT_ATTRANSFERREDHI = 0x54
FT_MEDIA_ARTIST = 0xd0
FT_MEDIA_ALBUM = 0xd1
FT_MEDIA_TITLE = 0xd2
FT_MEDIA_LENGTH = 0xd3
FT_MEDIA_BITRATE = 0xd4
FT_MEDIA_CODEC = 0xd5
FT_STRING = (
0x03, 0x04, 0x06, 0x0b, 0x11, 0x24, 0x28, 0xf6
)
FT_INT = (
0x05, 0x08, 0x09, 0x0a, 0x14, 0x15, 0x18, 0x1a, 0x1b, 0x23, 0x25, 0x30, 0x33, 0x53, 0x55, 0xf7
)
def met_readstring(fd):
string_len = int.from_bytes(fd.read(2), byteorder='little')
return fd.read(string_len).decode('utf-8', errors='replace')
def met_readinteger(fd, size=4):
return int.from_bytes(fd.read(size), byteorder='little')
def record_last_changed(fd):
return met_readinteger(fd)
def record_read_hash16(fd):
aichash = fd.read(16)
return binascii.hexlify(aichash).decode('utf-8')
def record_tags(fd):
tags = {}
tags['TAGCOUNT'] = met_readinteger(fd)
# print('Tags: {}'.format(tags['TAGCOUNT']))
for index in range(tags['TAGCOUNT']):
filetag = fd.read(4)
if filetag[:3] not in [b'\x02\x01\x00', b'\x03\x01\x00']:
print('<<< Premature end of record at offset 0x{:08X}'.format(fd.tell()))
return tags
tagid = filetag[-1]
if tagid == FT_FILENAME:
tags['FT_FILENAME'] = met_readstring(fd)
elif tagid == FT_FILESIZE:
tags['FT_FILESIZE'] = met_readinteger(fd)
elif tagid == FT_ATTRANSFERRED:
tags['FT_ATTRANSFERRED'] = met_readinteger(fd)
elif tagid == FT_ATREQUESTED:
tags['FT_ATREQUESTED'] = met_readinteger(fd)
elif tagid == FT_ATACCEPTED:
tags['FT_ATACCEPTED'] = met_readinteger(fd)
elif tagid == FT_ATTRANSFERREDHI:
tags['FT_ATTRANSFERREDHI'] = met_readinteger(fd)
elif tagid == FT_ULPRIORITY:
tags['FT_ULPRIORITY'] = met_readinteger(fd)
elif tagid == FT_KADLASTPUBLISHSRC:
tags['FT_KADLASTPUBLISHSRC'] = met_readinteger(fd)
elif tagid == FT_KADLASTPUBLISHNOTES:
print('FT_KADLASTPUBLISHNOTES')
met_readinteger(fd)
elif tagid == FT_FLAGS:
print('FT_FLAGS')
met_readinteger(fd)
elif tagid == FT_PERMISSIONS:
print('FT_PERMISSIONS')
met_readinteger(fd)
elif tagid == FT_KADLASTPUBLISHKEY:
print('FT_KADLASTPUBLISHKEY')
met_readinteger(fd)
elif tagid == FT_AICH_HASH:
tags['FT_AICH_HASH'] = met_readstring(fd)
elif tagid == FT_LASTSHARED:
tags['FT_LASTSHARED'] = met_readinteger(fd)
elif tagid == FT_AICHHASHSET:
tags['FT_AICHHASHSET'] = met_readinteger(fd, 1)
elif tagid == FT_PARTFILENAME:
tags['FT_PARTFILENAME'] = met_readstring(fd)
elif tagid == FT_MEDIA_LENGTH:
print('Media Length: {}'.format(met_readinteger(fd)))
elif tagid == FT_MEDIA_CODEC:
print('Media Codec: {}'.format(met_readstring(fd)))
elif tagid == FT_MEDIA_BITRATE:
print('Media Bitrate: {}'.format(met_readinteger(fd)))
elif tagid == FT_MEDIA_TITLE:
print('Media Title: {}'.format(met_readstring(fd)))
elif tagid == FT_MEDIA_ARTIST:
print('Media Artist: {}'.format(met_readstring(fd)))
elif tagid == FT_MEDIA_ALBUM:
print('Media Album: {}'.format(met_readstring(fd)))
elif tagid in FT_STRING:
print('... Reading data with tag: {}'.format(tagid))
met_readstring(fd)
elif tagid in FT_INT:
print('... Reading data with tag: {}'.format(tagid))
met_readstring(fd)
return tags
def record_num_parts(fd):
return met_readinteger(fd, 2)
def load_record(fd):
met_tags = {}
met_tags['LASTCHANGED'] = record_last_changed(fd)
met_tags['ED2KHASH'] = record_read_hash16(fd)
met_tags['NUMPARTS'] = record_num_parts(fd)
partshashes = []
for i in range(met_tags['NUMPARTS']):
partshashes.append(record_read_hash16(fd))
met_tags['PARTHASHES'] = partshashes
met_tags.update(record_tags(fd))
for field in METENTRY_FIELDS:
if field not in met_tags:
met_tags[field] = None
# print(met_tags)
metentry = MetEntry(met_tags['FT_FILENAME'],
met_tags['ED2KHASH'],
met_tags['NUMPARTS'],
met_tags['LASTCHANGED'],
met_tags['PARTHASHES'],
met_tags['FT_FILESIZE'],
met_tags['FT_ATTRANSFERRED'],
met_tags['FT_ATREQUESTED'],
met_tags['FT_ATACCEPTED'],
met_tags['FT_ATTRANSFERREDHI'],
met_tags['FT_PARTFILENAME'],
met_tags['FT_AICHHASHSET'],
met_tags['FT_AICH_HASH'],
met_tags['FT_ULPRIORITY'],
met_tags['FT_KADLASTPUBLISHSRC']
)
# print(metentry)
return metentry
def load_knownfiles(filename):
with open(filename, 'rb') as fd:
magic = fd.read(1)
if os.path.basename(filename) == 'known.met':
count = int.from_bytes(fd.read(4), byteorder='little')
# print('Number of records: {}'.format(count))
if magic == HEADER_KNOWN_MET_FILE:
for index in range(count):
# print('>>> [{}] NEW RECORD ***'.format(index+1))
yield load_record(fd)
elif magic == HEADER_PART_MET_FILE:
# print('>>> [{}] NEW RECORD ***'.format(0))
yield load_record(fd)