initial commit: adx loop parser

This commit is contained in:
louis 2018-04-24 23:22:03 +09:00
commit 754c704d02
5 changed files with 216 additions and 0 deletions

0
adx/__init__.py Normal file
View file

44
adx/adx.py Normal file
View file

@ -0,0 +1,44 @@
import glob
import json
import os
import traceback
from .adx_parser import adx_file
def extract_loop_data_from_dir(dir_in, fout):
if fout is None:
fout = "adx_loop.json"
files = glob.glob(os.path.join(dir_in, "**/*.adx"), recursive=True)
extract_loop_data_from_files(files, fout)
def extract_loop_data_from_files(files_in, fout):
if not files_in:
raise FileNotFoundError("No input files found")
collect = {}
for x in files_in:
res = extract_loop_data_from_file(x)
if res is not None:
collect[os.path.splitext(os.path.basename(x))[0]] = res
with open(fout, 'w') as out:
json.dump(collect, out, ensure_ascii=False, indent="\t", sort_keys=True)
def extract_loop_data_from_file(file_in, fout=None):
if not file_in.endswith(".adx"):
raise ValueError("Input file not .adx")
try:
with open(file_in, 'rb') as x:
data = x.read(64)
ADX = adx_file(data)
if ADX.l_exists and ADX.is_valid:
res = ADX.parse_loop_data()
else:
res = None
except Exception as e:
traceback.print_exc()
print("Error processing file: " + file_in)
return None
if fout is not None and res is not None:
with open(fout, 'w') as out:
json.dump(res, out, ensure_ascii=False, indent="\t", sort_keys=True)
return res

119
adx/adx_parser.py Normal file
View file

@ -0,0 +1,119 @@
from math import modf #fuck me rounding is hard
# header map
# https://wiki.multimedia.cx/index.php/CRI_ADX_file
MAGIC_OFF = 0x00
MAGIC_LEN = 2
MAGIC = 0x8000
DATA_OFF_OFF = 0x02
DATA_OFF_LEN = 2
DATA_OFF_MIN = 0x38
FORMAT_OFF = 0x04
FORMAT_LEN = 1
FORMAT = 3 # always 3 for adx apparently
LOOP_TYPE_OFF = 0x12
LOOP_TYPE_LEN = 1
LOOP_TYPE_SUP = [4] # supported loop styles
SRATE_OFF = 0x08
SRATE_LEN = 4
SCOUNT_OFF = 0x0c
SCOUNT_LEN = 4
CRYPT_OFF = 0x13
CRYPT_LEN = 1
LOOP4_FLAG_OFF = 0x24
LOOP4_FLAG_LEN = 4
LOOP4_START_OFF = 0x28
LOOP4_START_LEN = 4
LOOP4_END_OFF = 0x30
LOOP4_END_LEN = 4
#class independent functions
def dumb_round(number):
dec = modf(number)[0]
if (dec <= 0.5):
return int(number)
else:
return int(number+1)
class adx_file(object):
magic = 0
form = 0
l_style = 0
encrypted = 0
sam_rate = 0
sam_count = 0
l_flag = 0
l_start = 0
l_end = 0
l_exists = 0
d_offset = 0
is_valid = 0
data = None
def __init__(self, data):
# first, validate
self.data = data
self.magic = self.get_val(MAGIC_OFF, MAGIC_LEN)
self.form = self.get_val(FORMAT_OFF, FORMAT_LEN)
self.l_style = self.get_val(LOOP_TYPE_OFF, LOOP_TYPE_LEN)
self.encrypted = self.get_val(CRYPT_OFF, CRYPT_LEN)
self.d_offset = self.get_val(DATA_OFF_OFF, DATA_OFF_LEN)
if self.d_offset < DATA_OFF_MIN:
return None
self.l_exists = 1
# now load things for math
self.sam_rate = self.get_val(SRATE_OFF, SRATE_LEN)
self.sam_count = self.get_val(SCOUNT_OFF, SCOUNT_LEN)
self.validate()
def get_val(self, offset, length):
return int.from_bytes(self.data[offset:offset+length], byteorder='big', signed=False)
def validate(self):
if self.magic != MAGIC:
raise ValueError("Invalid ADX File")
if self.form != FORMAT:
raise ValueError("Invalid ADX File")
if self.encrypted:
raise NotImplementedError("Encryption Not Supported")
self.is_valid = 1
def parse_loop_data(self):
if adxf.l_style == 4:
adxf.l_flag = adxf.get_val(LOOP4_FLAG_OFF, LOOP4_FLAG_LEN)
adxf.l_start = adxf.get_val(LOOP4_START_OFF, LOOP4_START_LEN)
adxf.l_end = adxf.get_val(LOOP4_END_OFF, LOOP4_END_LEN)
if not adxf.l_flag:
return None
if adxf.l_start == 0 and adxf.l_end == adxf.sam_count:
return None
if adxf.l_start > adxf.sam_count or adxf.l_end > adxf.sam_count:
raise ValueError("Invalid Loop Data")
return None
if not adxf.l_style in LOOP_TYPE_SUP:
raise NotImplementedError("Loop Style {} Not Implemented".format(str(adxf.l_style)))
return None
ret = {}
ret['duration'] = adxf.sam_count / adxf.sam_rate
ret['loop_start'] = {}
ret['loop_start']['seconds'] = adxf.l_start / adxf.sam_rate
ret['loop_start']['samples_native'] = adxf.l_start
ret['loop_start']['samples_48k'] = dumb_round(adxf.l_start / adxf.sam_rate * 48000)
ret['loop_end'] = {}
ret['loop_end']['seconds'] = adxf.l_end / adxf.sam_rate
ret['loop_end']['samples_native'] = adxf.l_end
ret['loop_end']['samples_48k'] = dumb_round(adxf.l_end / adxf.sam_rate * 48000)
return ret