16 Commits

Author SHA1 Message Date
Jamie Hardt
465fe64d93 Merge pull request #4 from iluvcapra/feat-rename
Feature: Rename
2025-05-26 12:12:40 -07:00
Jamie Hardt
4241d3a4e8 autopep 2025-05-26 12:11:24 -07:00
Jamie Hardt
33fd0597cd Rename tested and addressed bugs 2025-05-26 12:10:22 -07:00
Jamie Hardt
cb10eec36a implemented rename 2025-05-25 12:18:15 -07:00
Jamie Hardt
180adcbbe4 Nudge version number 2025-05-25 11:29:34 -07:00
Jamie Hardt
b315680bdd Merge pull request #2 from iluvcapra/iluvcapra-patch-1
Feature: Get file list from file
2025-05-25 11:28:32 -07:00
Jamie Hardt
49055ef881 lint 2025-05-25 11:25:54 -07:00
Jamie Hardt
ecc93640c2 Completed the feature. 2025-05-25 11:23:55 -07:00
Jamie Hardt
538da34a0c lint 2025-05-25 10:55:09 -07:00
Jamie Hardt
2d4ea5a8d8 /John/George 2025-05-25 10:52:59 -07:00
Jamie Hardt
532f67e3a8 Refactored some code 2025-05-25 09:32:59 -07:00
Jamie Hardt
4989832247 autopep (added explicit encoding for from-file) 2025-05-25 08:29:57 -07:00
Jamie Hardt
549f49da31 autopep 2025-05-25 08:28:32 -07:00
Jamie Hardt
ba3b0dbf96 Added implementation 2025-05-25 08:27:11 -07:00
Jamie Hardt
2c135d413e twiddle text 2025-05-24 21:30:14 -07:00
Jamie Hardt
334fa56a2c Update __main__.py 2025-05-24 20:59:53 -07:00
3 changed files with 123 additions and 58 deletions

View File

@@ -8,8 +8,9 @@ from subprocess import CalledProcessError, run
import sys
from argparse import ArgumentParser
import shlex
from typing import Callable
from typing import Callable, List, Tuple
import inspect
from io import StringIO
from tqdm import tqdm
@@ -29,70 +30,83 @@ def execute_batch_list(batch_list_path: str, dry_run: bool, interactive: bool):
parser.eval(line, line_no, interactive)
def create_batch_list(command_file: str, recursive=True, sort_mode='path'):
def sort_flac_files(file_list, mode):
"Sort flac files"
if mode == 'path':
return sorted(file_list)
if mode == 'mtime':
return sorted(file_list, key=os.path.getmtime)
if mode == 'ctime':
return sorted(file_list, key=os.path.getctime)
if mode == 'name':
return sorted(file_list, key=os.path.basename)
return file_list
def write_batchfile_entries_for_file(path, metadatums) -> Tuple[dict, str]:
"Create batchfile entries for `path`"
buffer = StringIO()
try:
this_file_metadata = metadata_funcs.read_metadata(path)
except CalledProcessError as e:
buffer.write(f"# !!! METAFLAC ERROR ({e.returncode}) while reading "
f"metadata from the file {path}\n\n")
return metadatums, buffer.getvalue()
for this_key, this_value in this_file_metadata.items():
if this_key not in metadatums:
buffer.write(f":set {this_key} "
f"{shlex.quote(this_value)}\n")
metadatums[this_key] = this_value
else:
if this_value != metadatums[this_key]:
buffer.write(f":set {this_key} "
f"{shlex.quote(this_value)}"
"\n")
metadatums[this_key] = this_value
keys = list(metadatums.keys())
for key in keys:
if key not in this_file_metadata:
buffer.write(f":unset {key}\n")
del metadatums[key]
buffer.write(path + "\n\n")
return metadatums, buffer.getvalue()
def create_batch_list(flac_files: List[str], command_file: str,
sort_mode='path'):
"""
Read all FLAC files in the cwd and create a batchfile that re-creates all
of their metadata.
:param recursive: Recursively enter directories
:param flac_files: Paths of files to create batchfile from
:param command_file: Name of new batchfile
:param sort_mode: Order of paths in the batch list. Either 'path',
'mtime', 'ctime', 'name'
:param input_files: FLAC files to scan
"""
flac_files = sort_flac_files(flac_files, sort_mode)
with open(command_file, mode='w', encoding='utf-8') as f:
f.write("# mfbatch\n\n")
# f.write("""
# # :set DESCRIPTION ""
# # :set TITLE ""
# # :set VERSION ""
# # :set ALBUM ""
# # :set ARTIST ""
# # :set TRACKNUMBER ""
# # :set COPYRIGHT ""
# # :set LICENSE ""
# # :set CONTACT ""
# # :set ORGAIZATION ""
# # :set LOCATION ""
# # :set MICROPHONE ""
# """)
metadatums = {}
flac_files = glob('./**/*.flac', recursive=recursive)
if sort_mode == 'path':
flac_files = sorted(flac_files)
elif sort_mode == 'mtime':
flac_files = sorted(flac_files, key=os.path.getmtime)
elif sort_mode == 'ctime':
flac_files = sorted(flac_files, key=os.path.getctime)
elif sort_mode == 'name':
flac_files = sorted(flac_files, key=os.path.basename)
f.write("# mfbatch\n\n")
for path in tqdm(flac_files, unit='File', desc='Scanning FLAC files'):
try:
this_file_metadata = metadata_funcs.read_metadata(path)
except CalledProcessError as e:
f.write(f"# !!! METAFLAC ERROR ({e.returncode}) while reading "
f"metadata from the file {path}\n\n")
continue
for path in tqdm(flac_files, unit='File',
desc='Scanning with metaflac...'):
for this_key, this_value in this_file_metadata.items():
if this_key not in metadatums:
f.write(f":set {this_key} "
f"{shlex.quote(this_value)}\n")
metadatums[this_key] = this_value
else:
if this_value != metadatums[this_key]:
f.write(f":set {this_key} "
f"{shlex.quote(this_value)}"
"\n")
metadatums[this_key] = this_value
metadatums, buffer = write_batchfile_entries_for_file(path,
metadatums)
f.write(buffer)
keys = list(metadatums.keys())
for key in keys:
if key not in this_file_metadata:
f.write(f":unset {key}\n")
del metadatums[key]
f.write(path + "\n\n")
f.write("# mfbatch: create batchlist operation complete\n")
def main():
@@ -105,6 +119,10 @@ def main():
op.add_argument('-c', '--create', default=False,
action='store_true',
help='create a new list')
op.add_argument('-F', '--from-file', metavar='FILE_LIST', action='store',
default=None, help="get file paths from FILE_LIST when "
"creating, instead of scanning directory"
"a new list")
op.add_argument('-e', '--edit', action='store_true',
help="open batch file in the default editor",
default=False)
@@ -152,7 +170,18 @@ def main():
if options.create:
mode_given = True
create_batch_list(options.batchfile, sort_mode=options.sort)
flac_files: List[str] = []
if options.from_file:
with open(options.from_file, mode='r',
encoding='utf-8') as from_file:
flac_files = [line.strip() for line in from_file.readlines()]
else:
flac_files = glob('./**/*.flac', recursive=True)
# print(flac_files)
create_batch_list(flac_files, options.batchfile,
sort_mode=options.sort)
if options.edit:
mode_given = True

View File

@@ -115,6 +115,9 @@ class CommandEnv:
del self.metadatums['_FILENAME']
del self.metadatums['_FOLDER']
if '_NEW_BASENAME' in self.metadatums:
del self.metadatums['_NEW_BASENAME']
def revert_onces(self):
"""
Revert all set-once keys.
@@ -192,13 +195,29 @@ they appear in the batchfile.
def _handle_comment(self, _):
pass
def _write_metadata_impl(self, line):
def _write_metadata_and_rename_impl(self, line):
if self.dry_run:
print("DRY RUN would write metadata here.", file=self.outstream)
if '_NEW_BASENAME' in self.env.metadatums:
self.outstream.write('DRY RUN would rename file here.\n')
else:
self.outstream.write("Writing metadata... ")
self.write_metadata_f(line, self.env.metadatums)
self.outstream.write("Complete!")
self.outstream.write("Complete!\n")
if '_NEW_BASENAME' in self.env.metadatums:
self.outstream.write("Attempting to rename... ")
full_old_path = os.path.abspath(line)
new_name = os.path.join(os.path.dirname(full_old_path),
self.env.metadatums['_NEW_BASENAME'])
if not os.path.exists(new_name):
os.rename(line, new_name)
self.outstream.write('File renamed!\n')
else:
self.outstream.write('File by new name already exists, '
'rename was not performed.\n')
self.env.increment_all()
self.env.revert_onces()
@@ -234,10 +253,16 @@ they appear in the batchfile.
self._print_kv_columnar(key, value)
if '_NEW_BASENAME' in self.env.metadatums:
print("")
msg = "File will be renamed:"
print(
f"{msg:.<30} \033[4m{self.env.metadatums['_NEW_BASENAME']}\033[0m\n")
if interactive:
val = input('Write? [Y/n/a/:] > ')
if val == '' or val[0].upper() == 'Y':
self._write_metadata_impl(line)
self._write_metadata_and_rename_impl(line)
break
if val.startswith(self.COMMAND_LEADER):
self._handle_command(val.lstrip(self.COMMAND_LEADER),
@@ -246,7 +271,7 @@ they appear in the batchfile.
print("Aborting write session...", file=sys.stdout)
break
else:
self._write_metadata_impl(line)
self._write_metadata_and_rename_impl(line)
break
def set(self, args):
@@ -322,6 +347,17 @@ they appear in the batchfile.
repl = args[3]
self.env.set_pattern(key, inp, pattern, repl)
def rename(self, args):
"""
rename NEW-BASENAME
Renames the next file to NEW-BASENAME. The existing file is renamed
while keeping it in the same directory by appending the dirname and
NEW-BASENAME and performing an mv(1). Renaming occurs after metadata
writing. If a file with NEW-BASENAME already exists in the directory,
the action will not be performed.
"""
self.env.set_once('_NEW_BASENAME', args[0])
def d(self, args):
"""
d VALUE

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "mfbatch"
version = "0.5.2"
version = "0.6.0"
description = "MetaFlac batch editor"
authors = ["Jamie Hardt <jamiehardt@me.com>"]
readme = "README.md"