This commit is contained in:
Jamie Hardt
2024-07-01 21:04:50 -07:00
parent 12ad66e327
commit 35480978e4
4 changed files with 55 additions and 62 deletions

View File

@@ -1,4 +1,4 @@
# __main__.py # __main__.py
import os import os
from glob import glob from glob import glob
@@ -11,12 +11,13 @@ import inspect
from mfbatch.util import readline_with_escaped_newlines from mfbatch.util import readline_with_escaped_newlines
import mfbatch.metaflac as flac import mfbatch.metaflac as flac
from mfbatch.commands import BatchfileParser from mfbatch.commands import BatchfileParser
from tqdm import tqdm from tqdm import tqdm
# import readline # import readline
def execute_batch_list(batch_list_path: str, dry_run: bool, interactive: bool): def execute_batch_list(batch_list_path: str, dry_run: bool, interactive: bool):
with open(batch_list_path, mode='r') as f: with open(batch_list_path, mode='r') as f:
parser = BatchfileParser() parser = BatchfileParser()
@@ -35,7 +36,7 @@ def create_batch_list(command_file: str):
flac_files = glob('./**/*.flac', recursive=True) flac_files = glob('./**/*.flac', recursive=True)
flac_files = sorted(flac_files) flac_files = sorted(flac_files)
for path in tqdm(flac_files, unit='File', desc='Scanning FLAC files'): for path in tqdm(flac_files, unit='File', desc='Scanning FLAC files'):
this_file_metadata = flac.read_metadata(path) this_file_metadata = flac.read_metadata(path)
for this_key in this_file_metadata.keys(): for this_key in this_file_metadata.keys():
if this_key not in metadatums: if this_key not in metadatums:
f.write(f":set {this_key} " f.write(f":set {this_key} "
@@ -59,20 +60,20 @@ def create_batch_list(command_file: str):
def main(): def main():
op = OptionParser(usage="%prog (-c | -e | -W) [options]") op = OptionParser(usage="%prog (-c | -e | -W) [options]")
op.add_option('-c', '--create', default=False, op.add_option('-c', '--create', default=False,
action='store_true', action='store_true',
help='create a new list') help='create a new list')
op.add_option('-e', '--edit', action='store_true', op.add_option('-e', '--edit', action='store_true',
help="open batch file in the default editor", help="open batch file in the default editor",
default=False) default=False)
op.add_option('-W', '--write', default=False, op.add_option('-W', '--write', default=False,
action='store_true', action='store_true',
help="execute batch list, write to files") help="execute batch list, write to files")
op.add_option('-p', '--path', metavar='DIR', op.add_option('-p', '--path', metavar='DIR',
help='chdir to DIR before running', help='chdir to DIR before running',
default=None) default=None)
op.add_option('-n', '--dry-run', action='store_true', op.add_option('-n', '--dry-run', action='store_true',
help="dry-run -W.") help="dry-run -W.")
op.add_option('-f', '--batchfile', metavar='FILE', op.add_option('-f', '--batchfile', metavar='FILE',
@@ -84,24 +85,24 @@ def main():
"inhibits interactive editing in -W mode") "inhibits interactive editing in -W mode")
op.add_option('--help-commands', action='store_true', default=False, op.add_option('--help-commands', action='store_true', default=False,
dest='help_commands', dest='help_commands',
help='print a list of available commands for batch lists ' help='print a list of available commands for batch lists '
'and interactive writing.') 'and interactive writing.')
options, _ = op.parse_args() options, _ = op.parse_args()
if options.help_commands: if options.help_commands:
print("Command Help\n------------") print("Command Help\n------------")
commands = [command for command in dir(BatchfileParser) if commands = [command for command in dir(BatchfileParser) if
not command.startswith('_')] not command.startswith('_')]
print(f"{inspect.cleandoc(BatchfileParser.__doc__ or '')}\n\n") print(f"{inspect.cleandoc(BatchfileParser.__doc__ or '')}\n\n")
for command in commands: for command in commands:
meth = getattr(BatchfileParser, command) meth = getattr(BatchfileParser, command)
if isinstance(meth, Callable): if isinstance(meth, Callable):
print(f"{inspect.cleandoc(meth.__doc__ or '')}\n") print(f"{inspect.cleandoc(meth.__doc__ or '')}\n")
exit(0) exit(0)
mode_given = False mode_given = False
if options.path is not None: if options.path is not None:
os.chdir(options.path) os.chdir(options.path)
@@ -113,11 +114,11 @@ def main():
mode_given = True mode_given = True
editor_command = [os.getenv('EDITOR'), options.batchfile] editor_command = [os.getenv('EDITOR'), options.batchfile]
run(editor_command) run(editor_command)
if options.write: if options.write:
mode_given = True mode_given = True
execute_batch_list(options.batchfile, execute_batch_list(options.batchfile,
dry_run=options.dry_run, dry_run=options.dry_run,
interactive=not options.yes) interactive=not options.yes)
if mode_given == False: if mode_given == False:
@@ -127,5 +128,3 @@ def main():
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@@ -1,5 +1,5 @@
# commands.py # commands.py
# mfbatch # mfbatch
# from string import Template # from string import Template
import sys import sys
@@ -15,18 +15,19 @@ from typing import Dict, Tuple, Optional
class UnrecognizedCommandError(Exception): class UnrecognizedCommandError(Exception):
command: str command: str
line: int line: int
def __init__(self, command, line) -> None: def __init__(self, command, line) -> None:
self.command = command self.command = command
self.line = line self.line = line
class CommandArgumentError(Exception): class CommandArgumentError(Exception):
command: str command: str
line: int line: int
def __init__(self, command, line) -> None: def __init__(self, command, line) -> None:
self.command = command self.command = command
self.line = line self.line = line
@@ -35,7 +36,7 @@ class CommandEnv:
incr: Dict[str, str] incr: Dict[str, str]
patterns: Dict[str, Tuple[str, str, str]] patterns: Dict[str, Tuple[str, str, str]]
onces: Dict[str, Optional[str]] onces: Dict[str, Optional[str]]
artwork_file: Optional[str] artwork_file: Optional[str]
artwork_desc: Optional[str] artwork_desc: Optional[str]
@@ -80,7 +81,7 @@ class CommandEnv:
for key in keys: for key in keys:
del self.metadatums[key] del self.metadatums[key]
if self.onces[key] != None: if self.onces[key] != None:
self.metadatums[key] = self.onces[key] or '' self.metadatums[key] = self.onces[key] or ''
del self.onces[key] del self.onces[key]
@@ -88,8 +89,8 @@ class CommandEnv:
for k in self.incr.keys(): for k in self.incr.keys():
v = int(self.metadatums[k]) v = int(self.metadatums[k])
self.metadatums[k] = self.incr[k] % (v + 1) self.metadatums[k] = self.incr[k] % (v + 1)
class BatchfileParser: class BatchfileParser:
""" """
A batchfile is a text file of lines. Lines either begin with a '#' to denote a A batchfile is a text file of lines. Lines either begin with a '#' to denote a
@@ -106,16 +107,15 @@ they appear in the batchfile.
dry_run: bool dry_run: bool
env: CommandEnv env: CommandEnv
COMMAND_LEADER = ':' COMMAND_LEADER = ':'
COMMENT_LEADER = '#' COMMENT_LEADER = '#'
def __init__(self): def __init__(self):
self.dry_run = True self.dry_run = True
self.env = CommandEnv() self.env = CommandEnv()
def _handle_line(self, line:str, lineno: int, interactive: bool): def _handle_line(self, line: str, lineno: int, interactive: bool):
if line.startswith(self.COMMAND_LEADER): if line.startswith(self.COMMAND_LEADER):
self._handle_command(line.lstrip(self.COMMAND_LEADER), lineno) self._handle_command(line.lstrip(self.COMMAND_LEADER), lineno)
elif line.startswith(self.COMMENT_LEADER): elif line.startswith(self.COMMENT_LEADER):
@@ -125,8 +125,8 @@ they appear in the batchfile.
def _handle_command(self, line, lineno): def _handle_command(self, line, lineno):
args = shlex.split(line) args = shlex.split(line)
actions = [member for member in dir(self) \ actions = [member for member in dir(self)
if not member.startswith('_')] if not member.startswith('_')]
if args[0] in actions: if args[0] in actions:
try: try:
@@ -139,7 +139,7 @@ they appear in the batchfile.
def _handle_comment(self, _): def _handle_comment(self, _):
pass pass
def _handle_file(self, line, interactive): def _handle_file(self, line, interactive):
while True: while True:
self.env.set_file_keys(line) self.env.set_file_keys(line)
@@ -148,19 +148,19 @@ they appear in the batchfile.
if self.dry_run: if self.dry_run:
sys.stdout.write(f"\nDRY RUN File: \033[1m{line}\033[0m\n") sys.stdout.write(f"\nDRY RUN File: \033[1m{line}\033[0m\n")
else: else:
sys.stdout.write(f"\nFile: \033[1m{line}\033[0m\n") sys.stdout.write(f"\nFile: \033[1m{line}\033[0m\n")
for key in self.env.metadatums.keys(): for key in self.env.metadatums.keys():
if key.startswith('_'): if key.startswith('_'):
continue continue
value = self.env.metadatums[key] value = self.env.metadatums[key]
LINE_LEN = int(shutil.get_terminal_size()[0]) - 32 LINE_LEN = int(shutil.get_terminal_size()[0]) - 32
value_lines = [value[i:i+LINE_LEN] for i in \ value_lines = [value[i:i+LINE_LEN] for i in
range(0,len(value), LINE_LEN)] range(0, len(value), LINE_LEN)]
for l in value_lines: for l in value_lines:
if key: if key:
sys.stdout.write(f"{key:.<30} \033[4m{l}\033[0m\n") sys.stdout.write(f"{key:.<30} \033[4m{l}\033[0m\n")
@@ -183,7 +183,7 @@ they appear in the batchfile.
self.env.clear_file_keys() self.env.clear_file_keys()
elif val.startswith(self.COMMAND_LEADER): elif val.startswith(self.COMMAND_LEADER):
self._handle_command(val.lstrip(self.COMMAND_LEADER), self._handle_command(val.lstrip(self.COMMAND_LEADER),
lineno=-1) lineno=-1)
continue continue
elif val == 'a': elif val == 'a':
@@ -211,7 +211,7 @@ they appear in the batchfile.
""" """
key = args[0] key = args[0]
value = args[1] value = args[1]
self.env.metadatums[key] = value self.env.metadatums[key] = value
def set1(self, args): def set1(self, args):
""" """
@@ -273,8 +273,8 @@ they appear in the batchfile.
inp = args[1] inp = args[1]
pattern = args[2] pattern = args[2]
repl = args[3] repl = args[3]
self.env.set_pattern(key,inp,pattern, repl) self.env.set_pattern(key, inp, pattern, repl)
def d(self, args): def d(self, args):
""" """
d VALUE d VALUE
@@ -282,7 +282,3 @@ they appear in the batchfile.
""" """
val = args[0] val = args[0]
self.env.set_once('DESCRIPTION', val) self.env.set_once('DESCRIPTION', val)

View File

@@ -1,11 +1,11 @@
# metaflac.py # metaflac.py
# mfbatch # mfbatch
from subprocess import run from subprocess import run
from re import match from re import match
from io import StringIO from io import StringIO
from typing import Dict from typing import Dict
METAFLAC_PATH = '/opt/homebrew/bin/metaflac' METAFLAC_PATH = '/opt/homebrew/bin/metaflac'
@@ -28,13 +28,13 @@ def sanatize_key(k: str) -> str:
return rval return rval
def sanatize_value(v: str) -> str: def sanatize_value(v: str) -> str:
""" """
Enforces the VORBIS_COMMENT spec with regard to values. Also removes Enforces the VORBIS_COMMENT spec with regard to values. Also removes
newlines, which are not supported by this tool. newlines, which are not supported by this tool.
""" """
return v.translate(str.maketrans('\n',' ')) return v.translate(str.maketrans('\n', ' '))
def read_metadata(path: str, metaflac_path=METAFLAC_PATH) -> FlacMetadata: def read_metadata(path: str, metaflac_path=METAFLAC_PATH) -> FlacMetadata:
@@ -51,12 +51,12 @@ def read_metadata(path: str, metaflac_path=METAFLAC_PATH) -> FlacMetadata:
return file_metadata return file_metadata
def write_metadata(path: str, data: FlacMetadata, def write_metadata(path: str, data: FlacMetadata,
metaflac_path=METAFLAC_PATH): metaflac_path=METAFLAC_PATH):
remove_job = run([metaflac_path, '--remove-all-tags', path]) remove_job = run([metaflac_path, '--remove-all-tags', path])
remove_job.check_returncode() remove_job.check_returncode()
metadatum_f = "" metadatum_f = ""
for k in data.keys(): for k in data.keys():
key = sanatize_key(k) key = sanatize_key(k)
@@ -64,9 +64,8 @@ def write_metadata(path: str, data: FlacMetadata,
if key.startswith('_'): if key.startswith('_'):
continue continue
metadatum_f = metadatum_f + f"{key}={val}\n" metadatum_f = metadatum_f + f"{key}={val}\n"
insert_job = run([metaflac_path, "--import-tags-from=-", path], insert_job = run([metaflac_path, "--import-tags-from=-", path],
input=metadatum_f.encode('utf-8')) input=metadatum_f.encode('utf-8'))
insert_job.check_returncode() insert_job.check_returncode()

View File

@@ -1,4 +1,4 @@
# util.py # util.py
def readline_with_escaped_newlines(f): def readline_with_escaped_newlines(f):
line = '' line = ''
@@ -6,9 +6,9 @@ def readline_with_escaped_newlines(f):
while True: while True:
line += f.readline() line += f.readline()
line_no += 1 line_no += 1
if len(line) == 0: if len(line) == 0:
break break
line = line.rstrip("\n") line = line.rstrip("\n")
@@ -17,6 +17,5 @@ def readline_with_escaped_newlines(f):
continue continue
else: else:
yield line, line_no yield line, line_no
line = '' line = ''