SHARE
TWEET

txtp_maker.py

a guest Jan 23rd, 2019 16 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
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
 
Top