Guest User

txtp_maker.py

a guest
Jan 23rd, 2019
28
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. #!/usr/bin/env python3
  2.  
  3. # ########################################################################### #
  4. # TXTP MAKER by bnnm
  5. # v20190122: initial release
  6. # v20190123: * wildcard, ignore cmd errors
  7. # ########################################################################### #
  8.  
  9. from __future__ import division
  10. import subprocess
  11. import zlib
  12. import os.path
  13. import os
  14. import sys
  15.  
  16.  
  17. if (len(sys.argv) <= 1):
  18. print("Usage: {} (filename) [options]".format(os.path.basename(sys.argv[0])) + "\n"
  19. "\n"
  20. "Creates a (filename)_(subsong).txtp for every subsong in (filename).\n"
  21. " (filename) can be * to parse all files in dir (works with dupe filters too).\n"
  22. "Options:\n"
  23. " -c (name): set path to CLI (default: test.exe)\n"
  24. " -n (name): use (name)_(subsong).txtp format\n"
  25. " -z N: zero-fill subsong number (default: auto fill up to total subsongs)\n"
  26. " -d (dir): prepend dir to name (if the file will reside in a subdir)\n"
  27. " -m: create mini-txtp\n"
  28. " -o: overwrite existing .txtp (beware when using with internal names)\n"
  29. " -in: name TXTP using the subsong's internal name if found\n"
  30. " -ie: remove internal name's extension\n"
  31. " -ii: add subsong number when using internal name\n"
  32. " -l N: create multiple TXTP per subsong layers, every N channels\n"
  33. " -fd: filter duplicates (slower)\n"
  34. " -fcm N: filter min channels\n"
  35. " -fcM N: filter max channels\n"
  36. " -frm N: filter min sample rate\n"
  37. " -frM N: filter max sample rate\n"
  38. " -fsm N.N: filter min seconds\n"
  39. " -fsM N.N: filter max seconds\n"
  40. " -v N: verbose level (default: 1)\n"
  41. )
  42. exit()
  43.  
  44. # ########################################################################### #
  45.  
  46. def make_cmd(cfg, fname_in, fname_out, target_subsong):
  47. if (cfg.test_dupes):
  48. cmd = "{} -s {} -i -o {} {}".format(cfg.cli, target_subsong, fname_out, fname_in)
  49. else:
  50. cmd = "{} -s {} -m -i -o {} {}".format(cfg.cli, target_subsong, fname_out, fname_in)
  51. return cmd
  52.  
  53. class LogHelper(object):
  54.  
  55. def __init__(self, cfg):
  56. self.cfg = cfg
  57.  
  58. def debug(self, msg):
  59. if cfg.verbose >= 1:
  60. print(msg)
  61.  
  62. def info(self, msg):
  63. if cfg.verbose >= 2:
  64. print(msg)
  65.  
  66. class ConfigParser(object):
  67. cli = "test.exe"
  68.  
  69. base_name = ''
  70. zero_fill = -1
  71. subdir = ''
  72. mini_txtp = False
  73. overwrite = False
  74. layers = 0
  75.  
  76. use_internal_name = False
  77. use_internal_ext = False
  78. use_internal_index = False
  79.  
  80. test_dupes = False
  81. min_channels = 0
  82. max_channels = 0
  83. min_sample_rate = 0
  84. max_sample_rate = 0
  85. min_seconds = 0.0
  86. max_seconds = 0.0
  87.  
  88. verbose = 1
  89.  
  90. argv_len = 0
  91. index = 0
  92.  
  93.  
  94. def read_bool(self, command, default):
  95. if self.index > self.argv_len - 1:
  96. return default
  97. if self.argv[self.index] == command:
  98. val = True
  99. self.index += 1
  100. return val
  101. return default
  102.  
  103. def read_value(self, command, default):
  104. if self.index > self.argv_len - 2:
  105. return default
  106. if self.argv[self.index] == command:
  107. val = self.argv[self.index+1]
  108. self.index += 2
  109. return val
  110. return default
  111.  
  112. def read_string(self, command, default):
  113. return str(self.read_value(command, default))
  114.  
  115. def read_int(self, command, default):
  116. return int(self.read_value(command, default))
  117.  
  118. def read_float(self, command, default):
  119. return float(self.read_value(command, default))
  120.  
  121. #todo improve this poop
  122. def __init__(self, argv):
  123.  
  124. self.index = 2 #after file and
  125. self.argv = argv
  126. self.argv_len = len(argv)
  127.  
  128. prev_index = self.index
  129. while self.index < len(self.argv):
  130. self.cli = self.read_string('-c', self.cli)
  131. self.base_name = self.read_string('-n', self.base_name)
  132. self.zero_fill = self.read_int('-z', self.zero_fill)
  133. self.subdir = self.read_string('-d', self.subdir)
  134.  
  135. self.test_dupes = self.read_bool('-fd', self.test_dupes)
  136. self.min_channels = self.read_int('-fcm', self.min_channels)
  137. self.max_channels = self.read_int('-fcM', self.max_channels)
  138. self.min_sample_rate = self.read_int('-frm', self.min_sample_rate)
  139. self.max_sample_rate = self.read_int('-frM', self.max_sample_rate)
  140. self.min_seconds = self.read_float('-fsm', self.min_seconds)
  141. self.max_seconds = self.read_float('-fsM', self.max_seconds)
  142.  
  143. self.mini_txtp = self.read_bool('-m', self.mini_txtp)
  144. self.overwrite = self.read_bool('-o', self.overwrite)
  145. self.layers = self.read_int('-l', self.layers)
  146.  
  147. self.use_internal_name = self.read_bool('-in', self.use_internal_name)
  148. self.use_internal_ext = self.read_bool('-ie', self.use_internal_ext)
  149. self.use_internal_index = self.read_bool('-ii', self.use_internal_index)
  150.  
  151. self.verbose = self.read_int('-v', self.verbose)
  152.  
  153. if prev_index == self.index:
  154. self.index += 1
  155. prev_index = self.index
  156.  
  157. if (self.subdir != '') and not (self.subdir.endswith('/') or self.subdir.endswith('\\')):
  158. self.subdir += '/'
  159.  
  160. def __str__(self):
  161. return str(self.__dict__)
  162.  
  163.  
  164. class Cr32Helper(object):
  165. crc32_map = {}
  166. dupe = False
  167.  
  168.  
  169. def get_crc32(self, fname):
  170. buf_size = 0x8000
  171. with open(fname, 'rb') as file:
  172. buf = file.read(buf_size)
  173. crc32 = 0
  174. while len(buf) > 0:
  175. crc32 = zlib.crc32(buf, crc32)
  176. buf = file.read(buf_size)
  177. return crc32 & 0xFFFFFFFF
  178.  
  179. def update(self, fname):
  180. self.dupe = False
  181. if cfg.test_dupes == 0:
  182. return
  183. if not os.path.exists(fname):
  184. return
  185.  
  186. crc32_str = format(self.get_crc32(fname),'08x')
  187. if (crc32_str in self.crc32_map):
  188. self.dupe = True
  189. return
  190. self.crc32_map[crc32_str] = True
  191.  
  192. os.remove(fname)
  193. return
  194.  
  195. def is_dupe(self):
  196. return self.dupe
  197.  
  198. def __init__(self, cfg):
  199. self.cfg = cfg
  200.  
  201.  
  202. class TxtpMaker(object):
  203. channels = 0
  204. sample_rate = 0
  205. num_samples = 0
  206. stream_count = 0
  207. stream_index = 0
  208. stream_name = ''
  209. stream_seconds = 0
  210.  
  211. def get_string(self, str):
  212. find_pos = self.output.find(str)
  213. if (find_pos == -1):
  214. return ''
  215. cut_pos = find_pos + len(str)
  216. str_cut = self.output[cut_pos:]
  217. return str_cut.split()[0]
  218.  
  219. def get_value(self, str):
  220. res = self.get_string(str)
  221. if (res == ''):
  222. return 0;
  223. return int(res)
  224.  
  225. def is_ignorable(self):
  226. if (self.channels < cfg.min_channels):
  227. return True;
  228. if (cfg.max_channels > 0 and self.channels > cfg.max_channels):
  229. return True;
  230. if (self.sample_rate < cfg.min_sample_rate):
  231. return True;
  232. if (cfg.max_sample_rate > 0 and self.sample_rate > cfg.max_sample_rate):
  233. return True;
  234. if (self.stream_seconds < cfg.min_seconds):
  235. return True;
  236. if (cfg.max_seconds > 0 and self.stream_seconds > cfg.max_seconds):
  237. return True;
  238. return False
  239.  
  240. def get_stream_mask(self, layer):
  241. mask = '#c'
  242.  
  243. loops = cfg.layers
  244. if layer + cfg.layers > self.channels:
  245. loops = self.channels - cfg.layers
  246. for ch in range(0,loops):
  247. mask += str(layer+ch) + ','
  248.  
  249. mask = mask[:-1]
  250. return mask
  251.  
  252. def get_stream_name(self):
  253. if not cfg.use_internal_name:
  254. return ''
  255. txt = self.stream_name
  256.  
  257. # remove paths #todo maybe config/replace?
  258. pos = txt.rfind("\\")
  259. if (pos != -1):
  260. txt = txt[pos+1:]
  261. pos = txt.rfind("/")
  262. if (pos != -1):
  263. txt = txt[pos+1:]
  264. # remove bad chars
  265. txt = txt.replace("%", "_")
  266. txt = txt.replace("*", "_")
  267. txt = txt.replace("?", "_")
  268. txt = txt.replace(":", "_")
  269. txt = txt.replace("\"", "_")
  270. txt = txt.replace("|", "_")
  271. txt = txt.replace("<", "_")
  272. txt = txt.replace(">", "_")
  273.  
  274. if not cfg.use_internal_ext:
  275. pos = txt.rfind(".")
  276. if (pos != -1):
  277. txt = txt[:pos]
  278. return txt
  279.  
  280. def write(self, outname, line):
  281. outname += '.txtp'
  282. if not cfg.overwrite and os.path.exists(outname):
  283. raise ValueError('TXTP exists in path: ' + outname)
  284. ftxtp = open(outname,"w+")
  285. if line != '':
  286. ftxtp.write(line)
  287. ftxtp.close()
  288. print("created: " + outname)
  289. return
  290.  
  291. def make(self, fname):
  292. if self.is_ignorable():
  293. return
  294.  
  295. index = str(self.stream_index)
  296. if cfg.zero_fill < 0:
  297. index = index.zfill(len(str(self.stream_count)))
  298. else:
  299. index = index.zfill(cfg.zero_fill)
  300.  
  301. if cfg.mini_txtp:
  302. outname = "{}#{}".format(fname, index)
  303.  
  304. if cfg.layers > 0 and cfg.layers < self.channels:
  305. for layer in range(0, self.channels, cfg.layers):
  306. mask = self.get_stream_mask(layer)
  307. self.write(outname + mask, '')
  308. else:
  309. self.write(outname, '')
  310.  
  311. else:
  312. stream_name = self.get_stream_name()
  313. if stream_name != '':
  314. outname = stream_name
  315. if cfg.use_internal_index:
  316. outname += "_{}".format(index)
  317. else:
  318. if cfg.base_name != '':
  319. txt = cfg.base_name
  320. else:
  321. txt = fname
  322. pos = txt.rfind(".")
  323. if (pos != -1):
  324. txt = txt[:pos]
  325. outname = "{}_{}".format(txt, index)
  326.  
  327. line = ''
  328. if cfg.subdir != '':
  329. line += cfg.subdir
  330. line += "{}#{}".format(fname, self.stream_index)
  331.  
  332. if cfg.layers > 0 and cfg.layers < self.channels:
  333. done = 0
  334. for layer in range(0, self.channels, cfg.layers):
  335. sub = chr(ord('a') + done)
  336. done += 1
  337. mask = self.get_stream_mask(layer)
  338. self.write(outname + sub, line + mask)
  339. else:
  340. self.write(outname, line)
  341.  
  342. def has_more_subsongs(self, target_subsong):
  343. return target_subsong < self.stream_count
  344.  
  345. def __init__(self, cfg, output_b):
  346. self.cfg = cfg
  347.  
  348. self.output = str(output_b).replace("\\r","").replace("\\n","\n")
  349. self.channels = self.get_value("channels: ")
  350. self.sample_rate = self.get_value("sample rate: ")
  351. self.num_samples = self.get_value("stream total samples: ")
  352. self.stream_count = self.get_value("stream count: ")
  353. self.stream_index = self.get_value("stream index: ")
  354. self.stream_name = self.get_string("stream name: ")
  355.  
  356. if self.channels == 0:
  357. raise ValueError('Incorrect command result')
  358.  
  359. self.stream_seconds = self.num_samples / self.sample_rate
  360.  
  361. def __str__(self):
  362. return str(self.__dict__)
  363.  
  364. # ########################################################################### #
  365.  
  366. cfg = ConfigParser(sys.argv)
  367. crc32 = Cr32Helper(cfg)
  368. log = LogHelper(cfg)
  369.  
  370. fname = sys.argv[1]
  371. if fname == '*':
  372. fnames_in = os.listdir('.')
  373. else:
  374. fnames_in = []
  375. fnames_in.append(fname)
  376.  
  377.  
  378. for fname_in in fnames_in:
  379.  
  380. target_subsong = 1
  381. while 1:
  382. fname_out = fname_in + ".wav"
  383.  
  384. try:
  385. cmd = make_cmd(cfg, fname_in, fname_out, target_subsong)
  386. output_b = subprocess.check_output(cmd, shell=False) #stderr=subprocess.STDOUT
  387. except subprocess.CalledProcessError as e:
  388. #log.debug("ignoring CLI error in " + fname_in + ": " + e.output)
  389. break
  390.  
  391. if target_subsong == 1:
  392. log.debug("processing {}...".format(fname_out))
  393.  
  394. maker = TxtpMaker(cfg, output_b)
  395.  
  396. if not maker.is_ignorable():
  397. crc32.update(fname_out)
  398. if not crc32.is_dupe():
  399. maker.make(fname_in)
  400.  
  401. if not maker.has_more_subsongs(target_subsong):
  402. break
  403. target_subsong += 1
  404.  
  405. if target_subsong % 200 == 0:
  406. log.debug("{} subsongs...".format(target_subsong))
  407.  
  408.  
  409. print("done!")
RAW Paste Data