Fix line-endings; Increase portability; Add speedtyper.py
This commit is contained in:
@@ -1,257 +1,257 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# overly complicated flac reencoder originally written for the big touhou music torrent on nyaa.si
|
||||
# contains example of process calling and multithreading
|
||||
# not recommended to use (probably destructive)
|
||||
|
||||
import os, re, time
|
||||
import subprocess
|
||||
import wave
|
||||
|
||||
from queue import Queue
|
||||
from threading import Thread
|
||||
|
||||
f = open("errlog", "w", encoding="UTF-8")
|
||||
|
||||
def remove_if_exists(filename):
|
||||
if os.path.exists(filename):
|
||||
os.remove(filename)
|
||||
|
||||
def opus_enc(queue, split_track_filename, track, quality=140.0):
|
||||
subprocess.call([
|
||||
'opusenc',
|
||||
'--vbr',
|
||||
'--bitrate', str(quality),
|
||||
#'--comp', 10, #default
|
||||
#'--framesize', '60', # default 20
|
||||
'--artist', track.performer,
|
||||
'--comment', 'tracknumber={}'.format(track.index),
|
||||
'--title', track.title,
|
||||
'--date', track.cd_date,
|
||||
'--genre', track.cd_genre,
|
||||
'--album', track.cd_title,
|
||||
split_track_filename,
|
||||
'{}.opus'.format(os.path.splitext(split_track_filename)[0]),
|
||||
])
|
||||
queue.get()
|
||||
|
||||
class Track():
|
||||
def __init__(self, track_index, filename, parent):
|
||||
for member in ('cd_performer', 'cd_title', 'cd_date', 'cd_genre'):
|
||||
setattr(self, member, getattr(parent, member))
|
||||
|
||||
self.filename = filename
|
||||
self.filepath = filename[:filename.rfind('\\')+1]
|
||||
self.title = ''
|
||||
self.index = track_index
|
||||
self.performer = self.cd_performer
|
||||
self.time = { 1:0.0 }
|
||||
|
||||
def __str__(self):
|
||||
return "{} - {} - {}".format(self.index, self.title, self.time)
|
||||
|
||||
class CueSheet():
|
||||
def __init__(self, filename):
|
||||
self.filename = filename
|
||||
self.filepath = filename[:filename.rfind('\\')+1]
|
||||
|
||||
self.cd_performer = ''
|
||||
self.cd_title = ''
|
||||
self.cd_genre = ''
|
||||
self.cd_date = ''
|
||||
|
||||
self.current_file = ''
|
||||
|
||||
self.tracks = []
|
||||
|
||||
self.regex_lst = (
|
||||
(re.compile(r'PERFORMER\s(.+)'), self.__performer),
|
||||
(re.compile(r'REM DATE\s(.+)'), self.__date),
|
||||
(re.compile(r'REM GENRE\s(.+)'), self.__genre),
|
||||
(re.compile(r'TITLE\s(.+)'), self.__title),
|
||||
(re.compile(r'FILE\s(.+)\sWAVE'), self.__file),
|
||||
(re.compile(r'TRACK\s(\d{2})\sAUDIO'), self.__track),
|
||||
(re.compile(r'INDEX\s(\d{2})\s(\d{1,3}:\d{2}:\d{2})'), self.__index),
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
value = "Title: {}\nPerformer: {}\nGenre: {}\nDate: {}\n".format(self.cd_title, self.cd_performer, self.cd_genre, self.cd_date)
|
||||
for track in self.tracks:
|
||||
value += ' ' + str(track) + '\n'
|
||||
return value
|
||||
|
||||
def read(self):
|
||||
with open(self.filename, 'r', encoding='utf-8-sig') as f:
|
||||
for line in f:
|
||||
for regex, handler in self.regex_lst:
|
||||
mobj = regex.match(line.strip())
|
||||
if mobj:
|
||||
handler(*self.unquote(mobj.groups()))
|
||||
|
||||
def split(self):
|
||||
encoding_queue = multiprocessing.Queue(multiprocessing.cpu_count())
|
||||
|
||||
cds = set()
|
||||
tracks = set()
|
||||
|
||||
for i, track in enumerate(self.tracks):
|
||||
# FATAL: sheet is not for .tta file
|
||||
if track.filename[-4:] != '.tta':
|
||||
f.write("\nFilename isn't .tta ({}):\n{}\n".format(track.filename, str(self)))
|
||||
return
|
||||
|
||||
track_path = track.filepath + ' - '.join((track.index, track.title)).replace('?', '').replace('\\', '').replace('\\', '').replace(':', '')
|
||||
|
||||
track_opus = track_path + '.opus'
|
||||
track_wav = track_path + '.wav'
|
||||
|
||||
if os.path.exists(track_opus):
|
||||
f.write("File already exists, continuing... ({})".format(track_opus))
|
||||
remove_if_exists(track_wav)
|
||||
continue
|
||||
|
||||
cd_wav = track.filename[:-4] + '.wav'
|
||||
|
||||
# decode .tta if needed
|
||||
if not os.path.exists(cd_wav):
|
||||
# FATAL: no file to decode
|
||||
if not os.path.exists(track.filename):
|
||||
f.write("\nFile doesn't exist ({}):\n{}\n".format(track.filename, str(self)))
|
||||
return
|
||||
|
||||
result = subprocess.call([
|
||||
'tta', #'ttaenc',
|
||||
'-d',
|
||||
track.filename,
|
||||
#'-o',
|
||||
cd_wav
|
||||
])
|
||||
|
||||
# FATAL: .tta decode failed
|
||||
if result != 0:
|
||||
f.write("Failed to decode .tta ({}):\n{}\n\n".format(track.index, str(self)))
|
||||
return
|
||||
|
||||
# remove .tta
|
||||
remove_if_exists(track.filename)
|
||||
|
||||
# split .wav into track
|
||||
if not os.path.exists(track_wav):
|
||||
wafi = wave.open(cd_wav, 'rb')
|
||||
param_names = ('nchannels', 'sampwidth', 'framerate', 'nframes', 'comptype', 'compname')
|
||||
params = wafi.getparams()
|
||||
param_dict = dict(zip(param_names, params))
|
||||
|
||||
start = int(param_dict['framerate'] * track.time[1])
|
||||
stop = param_dict['nframes']
|
||||
if len(sheet.tracks) > i+1 and sheet.tracks[i+1].filename == track.filename:
|
||||
stop = int(param_dict['framerate'] * sheet.tracks[i+1].time.get(0, sheet.tracks[i+1].time[1]))
|
||||
|
||||
wafi_write = wave.open(track_wav, 'wb')
|
||||
newparams = list(params)
|
||||
newparams[3] = 0
|
||||
wafi_write.setparams( tuple(newparams) )
|
||||
|
||||
wafi.setpos(start)
|
||||
wafi_write.writeframes(wafi.readframes(stop-start))
|
||||
wafi_write.close()
|
||||
|
||||
wafi.close()
|
||||
|
||||
encoding_queue.put(track_wav)
|
||||
p = multiprocessing.Process(
|
||||
target=opus_enc,
|
||||
args=(
|
||||
encoding_queue,
|
||||
track_wav,
|
||||
track
|
||||
)
|
||||
)
|
||||
|
||||
p.start()
|
||||
|
||||
if cd_wav not in cds:
|
||||
cds.add(cd_wav)
|
||||
|
||||
tracks.add(track_wav)
|
||||
|
||||
while not encoding_queue.empty():
|
||||
time.sleep(0.2)
|
||||
|
||||
for cd in cds:
|
||||
remove_if_exists(cd)
|
||||
|
||||
for track in tracks:
|
||||
remove_if_exists(track)
|
||||
|
||||
remove_if_exists(self.filename)
|
||||
|
||||
print(self.filename, "done!")
|
||||
|
||||
|
||||
def __performer(self, s):
|
||||
if not self.tracks:
|
||||
self.cd_performer = s
|
||||
else:
|
||||
self.tracks[-1].performer = s
|
||||
|
||||
def __title(self, s):
|
||||
if not self.tracks:
|
||||
self.cd_title = s
|
||||
else:
|
||||
self.tracks[-1].title = s
|
||||
|
||||
def __genre(self, s):
|
||||
self.cd_genre = s
|
||||
|
||||
def __date(self, s):
|
||||
self.cd_date = s
|
||||
|
||||
def __file(self, s):
|
||||
self.current_file = s
|
||||
|
||||
def __track(self, s):
|
||||
self.tracks.append( Track(s, self.filepath + self.current_file, self) )
|
||||
|
||||
def __index(self, idx, s):
|
||||
idx = int(idx)
|
||||
self.tracks[-1].time[idx] = self.index_split(s)
|
||||
|
||||
@staticmethod
|
||||
def index_split(s):
|
||||
t = s.split(':')
|
||||
return float(t[0])*60 + float(t[1]) + float(t[2]) / 75.0
|
||||
|
||||
@staticmethod
|
||||
def dqstrip(s):
|
||||
if s[0] == '"' and s[-1] == '"': return s[1:-1]
|
||||
return s
|
||||
|
||||
@staticmethod
|
||||
def unquote(t):
|
||||
return tuple([CueSheet.dqstrip(s.strip()) for s in t])
|
||||
|
||||
class SplitterWorker(Thread):
|
||||
def __init__(self, queue, filename):
|
||||
Thread.__init__(self)
|
||||
self.queue = queue
|
||||
self.filename = filename
|
||||
|
||||
def run(self):
|
||||
sheet = CueSheet(self.filename)
|
||||
sheet.read()
|
||||
sheet.split()
|
||||
|
||||
if __name__ == '__main__':
|
||||
queue = Queue()
|
||||
|
||||
for root, dirs, files in os.walk('.'):
|
||||
for name in files:
|
||||
if name[-4:].lower() == '.cue':
|
||||
worker = SplitterWorker(queue, root + '\\' + name)
|
||||
worker.daemon = True
|
||||
worker.start()
|
||||
|
||||
if os.path.exists('./stop'):
|
||||
exit(1)
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# overly complicated flac reencoder originally written for the big touhou music torrent on nyaa.si
|
||||
# contains example of process calling and multithreading
|
||||
# not recommended to use (probably destructive)
|
||||
|
||||
import os, re, time
|
||||
import subprocess
|
||||
import wave
|
||||
|
||||
from queue import Queue
|
||||
from threading import Thread
|
||||
|
||||
f = open("errlog", "w", encoding="UTF-8")
|
||||
|
||||
def remove_if_exists(filename):
|
||||
if os.path.exists(filename):
|
||||
os.remove(filename)
|
||||
|
||||
def opus_enc(queue, split_track_filename, track, quality=140.0):
|
||||
subprocess.call([
|
||||
'opusenc',
|
||||
'--vbr',
|
||||
'--bitrate', str(quality),
|
||||
#'--comp', 10, #default
|
||||
#'--framesize', '60', # default 20
|
||||
'--artist', track.performer,
|
||||
'--comment', 'tracknumber={}'.format(track.index),
|
||||
'--title', track.title,
|
||||
'--date', track.cd_date,
|
||||
'--genre', track.cd_genre,
|
||||
'--album', track.cd_title,
|
||||
split_track_filename,
|
||||
'{}.opus'.format(os.path.splitext(split_track_filename)[0]),
|
||||
])
|
||||
queue.get()
|
||||
|
||||
class Track():
|
||||
def __init__(self, track_index, filename, parent):
|
||||
for member in ('cd_performer', 'cd_title', 'cd_date', 'cd_genre'):
|
||||
setattr(self, member, getattr(parent, member))
|
||||
|
||||
self.filename = filename
|
||||
self.filepath = filename[:filename.rfind('\\')+1]
|
||||
self.title = ''
|
||||
self.index = track_index
|
||||
self.performer = self.cd_performer
|
||||
self.time = { 1:0.0 }
|
||||
|
||||
def __str__(self):
|
||||
return "{} - {} - {}".format(self.index, self.title, self.time)
|
||||
|
||||
class CueSheet():
|
||||
def __init__(self, filename):
|
||||
self.filename = filename
|
||||
self.filepath = filename[:filename.rfind('\\')+1]
|
||||
|
||||
self.cd_performer = ''
|
||||
self.cd_title = ''
|
||||
self.cd_genre = ''
|
||||
self.cd_date = ''
|
||||
|
||||
self.current_file = ''
|
||||
|
||||
self.tracks = []
|
||||
|
||||
self.regex_lst = (
|
||||
(re.compile(r'PERFORMER\s(.+)'), self.__performer),
|
||||
(re.compile(r'REM DATE\s(.+)'), self.__date),
|
||||
(re.compile(r'REM GENRE\s(.+)'), self.__genre),
|
||||
(re.compile(r'TITLE\s(.+)'), self.__title),
|
||||
(re.compile(r'FILE\s(.+)\sWAVE'), self.__file),
|
||||
(re.compile(r'TRACK\s(\d{2})\sAUDIO'), self.__track),
|
||||
(re.compile(r'INDEX\s(\d{2})\s(\d{1,3}:\d{2}:\d{2})'), self.__index),
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
value = "Title: {}\nPerformer: {}\nGenre: {}\nDate: {}\n".format(self.cd_title, self.cd_performer, self.cd_genre, self.cd_date)
|
||||
for track in self.tracks:
|
||||
value += ' ' + str(track) + '\n'
|
||||
return value
|
||||
|
||||
def read(self):
|
||||
with open(self.filename, 'r', encoding='utf-8-sig') as f:
|
||||
for line in f:
|
||||
for regex, handler in self.regex_lst:
|
||||
mobj = regex.match(line.strip())
|
||||
if mobj:
|
||||
handler(*self.unquote(mobj.groups()))
|
||||
|
||||
def split(self):
|
||||
encoding_queue = multiprocessing.Queue(multiprocessing.cpu_count())
|
||||
|
||||
cds = set()
|
||||
tracks = set()
|
||||
|
||||
for i, track in enumerate(self.tracks):
|
||||
# FATAL: sheet is not for .tta file
|
||||
if track.filename[-4:] != '.tta':
|
||||
f.write("\nFilename isn't .tta ({}):\n{}\n".format(track.filename, str(self)))
|
||||
return
|
||||
|
||||
track_path = track.filepath + ' - '.join((track.index, track.title)).replace('?', '').replace('\\', '').replace('\\', '').replace(':', '')
|
||||
|
||||
track_opus = track_path + '.opus'
|
||||
track_wav = track_path + '.wav'
|
||||
|
||||
if os.path.exists(track_opus):
|
||||
f.write("File already exists, continuing... ({})".format(track_opus))
|
||||
remove_if_exists(track_wav)
|
||||
continue
|
||||
|
||||
cd_wav = track.filename[:-4] + '.wav'
|
||||
|
||||
# decode .tta if needed
|
||||
if not os.path.exists(cd_wav):
|
||||
# FATAL: no file to decode
|
||||
if not os.path.exists(track.filename):
|
||||
f.write("\nFile doesn't exist ({}):\n{}\n".format(track.filename, str(self)))
|
||||
return
|
||||
|
||||
result = subprocess.call([
|
||||
'tta', #'ttaenc',
|
||||
'-d',
|
||||
track.filename,
|
||||
#'-o',
|
||||
cd_wav
|
||||
])
|
||||
|
||||
# FATAL: .tta decode failed
|
||||
if result != 0:
|
||||
f.write("Failed to decode .tta ({}):\n{}\n\n".format(track.index, str(self)))
|
||||
return
|
||||
|
||||
# remove .tta
|
||||
remove_if_exists(track.filename)
|
||||
|
||||
# split .wav into track
|
||||
if not os.path.exists(track_wav):
|
||||
wafi = wave.open(cd_wav, 'rb')
|
||||
param_names = ('nchannels', 'sampwidth', 'framerate', 'nframes', 'comptype', 'compname')
|
||||
params = wafi.getparams()
|
||||
param_dict = dict(zip(param_names, params))
|
||||
|
||||
start = int(param_dict['framerate'] * track.time[1])
|
||||
stop = param_dict['nframes']
|
||||
if len(sheet.tracks) > i+1 and sheet.tracks[i+1].filename == track.filename:
|
||||
stop = int(param_dict['framerate'] * sheet.tracks[i+1].time.get(0, sheet.tracks[i+1].time[1]))
|
||||
|
||||
wafi_write = wave.open(track_wav, 'wb')
|
||||
newparams = list(params)
|
||||
newparams[3] = 0
|
||||
wafi_write.setparams( tuple(newparams) )
|
||||
|
||||
wafi.setpos(start)
|
||||
wafi_write.writeframes(wafi.readframes(stop-start))
|
||||
wafi_write.close()
|
||||
|
||||
wafi.close()
|
||||
|
||||
encoding_queue.put(track_wav)
|
||||
p = multiprocessing.Process(
|
||||
target=opus_enc,
|
||||
args=(
|
||||
encoding_queue,
|
||||
track_wav,
|
||||
track
|
||||
)
|
||||
)
|
||||
|
||||
p.start()
|
||||
|
||||
if cd_wav not in cds:
|
||||
cds.add(cd_wav)
|
||||
|
||||
tracks.add(track_wav)
|
||||
|
||||
while not encoding_queue.empty():
|
||||
time.sleep(0.2)
|
||||
|
||||
for cd in cds:
|
||||
remove_if_exists(cd)
|
||||
|
||||
for track in tracks:
|
||||
remove_if_exists(track)
|
||||
|
||||
remove_if_exists(self.filename)
|
||||
|
||||
print(self.filename, "done!")
|
||||
|
||||
|
||||
def __performer(self, s):
|
||||
if not self.tracks:
|
||||
self.cd_performer = s
|
||||
else:
|
||||
self.tracks[-1].performer = s
|
||||
|
||||
def __title(self, s):
|
||||
if not self.tracks:
|
||||
self.cd_title = s
|
||||
else:
|
||||
self.tracks[-1].title = s
|
||||
|
||||
def __genre(self, s):
|
||||
self.cd_genre = s
|
||||
|
||||
def __date(self, s):
|
||||
self.cd_date = s
|
||||
|
||||
def __file(self, s):
|
||||
self.current_file = s
|
||||
|
||||
def __track(self, s):
|
||||
self.tracks.append( Track(s, self.filepath + self.current_file, self) )
|
||||
|
||||
def __index(self, idx, s):
|
||||
idx = int(idx)
|
||||
self.tracks[-1].time[idx] = self.index_split(s)
|
||||
|
||||
@staticmethod
|
||||
def index_split(s):
|
||||
t = s.split(':')
|
||||
return float(t[0])*60 + float(t[1]) + float(t[2]) / 75.0
|
||||
|
||||
@staticmethod
|
||||
def dqstrip(s):
|
||||
if s[0] == '"' and s[-1] == '"': return s[1:-1]
|
||||
return s
|
||||
|
||||
@staticmethod
|
||||
def unquote(t):
|
||||
return tuple([CueSheet.dqstrip(s.strip()) for s in t])
|
||||
|
||||
class SplitterWorker(Thread):
|
||||
def __init__(self, queue, filename):
|
||||
Thread.__init__(self)
|
||||
self.queue = queue
|
||||
self.filename = filename
|
||||
|
||||
def run(self):
|
||||
sheet = CueSheet(self.filename)
|
||||
sheet.read()
|
||||
sheet.split()
|
||||
|
||||
if __name__ == '__main__':
|
||||
queue = Queue()
|
||||
|
||||
for root, dirs, files in os.walk('.'):
|
||||
for name in files:
|
||||
if name[-4:].lower() == '.cue':
|
||||
worker = SplitterWorker(queue, root + '\\' + name)
|
||||
worker.daemon = True
|
||||
worker.start()
|
||||
|
||||
if os.path.exists('./stop'):
|
||||
exit(1)
|
||||
|
||||
Reference in New Issue
Block a user