from getopt import gnu_getopt as getopt, GetoptError from math import floor from os.path import basename, dirname, join, splitext from re import compile, Match, search from sys import argv, exit from typing import List, Optional try: from _rssbotlib import AVDict, version, VideoInfo have_rssbotlib = True except ImportError: AVDict = None have_rssbotlib = False RSSBOTLIB_NOTFOUND = '''rssbotlib not found. The source code is available at https://github.com/lifegpc/ffmpeg-study/tree/master/rssbotlib''' # noqa: E501 DUR_REG = compile(r'^(?P[\+-])?(((?P\d+):)?((?P\d+):))?(?P\d+)(\.(?P\d+))?$') # noqa: E501 LRCDUR_REG = compile(r'\[(((?P\d+):)?((?P\d+):))?(?P\d+)(\.(?P\d+))?\]') # noqa: E501 def convert_dur(r: Match) -> float: rd = r.groupdict() t = int(rd['sec']) if rd['ms']: t += int(rd['ms']) / (10 ** len(rd['ms'])) if rd['min']: t += int(rd['min']) * 60 if rd['h']: t += int(rd['h']) * 3600 if 'sign' in rd and rd['sign'] == '-': t = -t return t def prase_duration(s: str) -> float: r = search(DUR_REG, s) if r is None: raise ValueError(f'Can not parse duration "{s}"') return convert_dur(r) def generate_good_filename(meta: AVDict) -> Optional[str]: m = meta.to_dict() if 'title' in m and 'artist' in m: return f"{m['artist']} - {m['title']}.lrc" elif 'title' in m: return f"{m['title']}.lrc" class Lyric: def __init__(self) -> None: self._l = [] self._meta = [] self._bom = False self._has_dur = True def parse(self, fn: str): li = [] me = [] with open(fn, 'r', encoding='UTF-8') as f: t = f.read(1) if t == '\ufeff': self._bom = True else: f.seek(0, 0) ll = f.readlines(1) while len(ll) > 0: for i in ll: i = i.rstrip('\n') if self._has_dur: tre = LRCDUR_REG.finditer(i) re: List[Match] = [] for tmp in tre: re.append(tmp) if len(re) == 0: if i.startswith('[') and i.endswith(']'): me.append(i[1:-1]) else: if len(li) == 0: self._has_dur = False li.append(i) else: print(f'Ignored "{i}"') else: d = i[re[-1].end():] for r in re: li.append({'time': round(convert_dur(r), 2), 'data': d}) else: if i.startswith('[') and i.endswith(']'): me.append(i[1:-1]) else: li.append(i) ll = f.readlines(1) self._l = li self._meta = me def save(self, fn: str): with open(fn, 'w', encoding='UTF-8') as f: if self._bom: f.write('\ufeff') for m in self._meta: f.write(f"[{m}]\n") for i in self._l: if isinstance(i, str): f.write(f"{i}\n") else: t = round(i['time'] * 100) m = floor(t / 6000) s = floor((t % 6000) / 100) ms = t % 100 f.write(f"[{m:02}:{s:02}.{ms:02}]{i['data']}\n") class Cml: def __init__(self, arg: List[str]) -> None: self.output = None self.file = None self.verbose = False self.duration = None self.dir = None if len(arg) == 0: self.print_help() exit(0) try: r = getopt(arg, '-hVvo:f:t:d:', ['help', 'version', 'verbose', 'output=', 'file=', 'duration=', 'dir=']) for i in r[0]: if i[0] == '-h' or i[0] == '--help': self.print_help() exit(0) elif i[0] == '-V' or i[0] == '--version': self.print_version() exit(0) elif i[0] == '-v' or i[0] == '--verbose': self.verbose = True elif i[0] == '-o' or i[0] == '--output': self.output = i[1] elif i[0] == '-f' or i[0] == '--file': self.file = i[1] elif i[0] == '-t' or i[0] == '--duration': self.duration = prase_duration(i[1]) elif i[0] == '-d' or i[0] == '--dir': self.dir = i[1] if len(r[1]) == 0: raise GetoptError('Input lyric file is needed.') if len(r[1]) > 1: raise GetoptError('Too much input lyric file.') self.input = r[1][0] except GetoptError as e: print(e.msg) exit(1) def print_help(self): print('''convert_lrc.py [options] Convert translated lryics. Options: -h, --help Print this help message. -V, --version Print version. -v, --verbose Enable verbose logging. -o, --output Specify output path. -f, --file Specify music file, will read duration and other information from file. (rssbotlib is needed.) -t, --duration