20 Commits
v0.5.1 ... main

Author SHA1 Message Date
Jamie Hardt
5e4db46328 Update python-publish.yml
Nudged pypa/gh-action-pypi-publish@v1.13.0
2025-09-08 12:53:49 -07:00
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
ef1377a616 Update pyproject.toml
Nudge version to 0.5.2
2025-05-24 21:46:43 -07:00
Jamie Hardt
b7535618e6 Merge pull request #3 from iluvcapra/iluvcapra-patch-2
Update python-publish.yml
2025-05-24 21:46:06 -07:00
Jamie Hardt
dfa2a2a5ad Update python-publish.yml
Updated publish action
2025-05-24 21:41:21 -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
4 changed files with 124 additions and 59 deletions

View File

@@ -35,4 +35,4 @@ jobs:
- name: Build package - name: Build package
run: python -m build run: python -m build
- name: Publish package - name: Publish package
uses: pypa/gh-action-pypi-publish@v1.8.6 uses: pypa/gh-action-pypi-publish@v1.13.0

View File

@@ -8,8 +8,9 @@ from subprocess import CalledProcessError, run
import sys import sys
from argparse import ArgumentParser from argparse import ArgumentParser
import shlex import shlex
from typing import Callable from typing import Callable, List, Tuple
import inspect import inspect
from io import StringIO
from tqdm import tqdm 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) 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 Read all FLAC files in the cwd and create a batchfile that re-creates all
of their metadata. 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', :param sort_mode: Order of paths in the batch list. Either 'path',
'mtime', 'ctime', 'name' '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: 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 = {} metadatums = {}
flac_files = glob('./**/*.flac', recursive=recursive)
if sort_mode == 'path': f.write("# mfbatch\n\n")
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)
for path in tqdm(flac_files, unit='File', desc='Scanning FLAC files'): for path in tqdm(flac_files, unit='File',
try: desc='Scanning with metaflac...'):
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 this_key, this_value in this_file_metadata.items(): metadatums, buffer = write_batchfile_entries_for_file(path,
if this_key not in metadatums: metadatums)
f.write(f":set {this_key} " f.write(buffer)
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
keys = list(metadatums.keys()) f.write("# mfbatch: create batchlist operation complete\n")
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")
def main(): def main():
@@ -105,6 +119,10 @@ def main():
op.add_argument('-c', '--create', default=False, op.add_argument('-c', '--create', default=False,
action='store_true', action='store_true',
help='create a new list') 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', op.add_argument('-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)
@@ -152,7 +170,18 @@ def main():
if options.create: if options.create:
mode_given = True 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: if options.edit:
mode_given = True mode_given = True

View File

@@ -115,6 +115,9 @@ class CommandEnv:
del self.metadatums['_FILENAME'] del self.metadatums['_FILENAME']
del self.metadatums['_FOLDER'] del self.metadatums['_FOLDER']
if '_NEW_BASENAME' in self.metadatums:
del self.metadatums['_NEW_BASENAME']
def revert_onces(self): def revert_onces(self):
""" """
Revert all set-once keys. Revert all set-once keys.
@@ -192,13 +195,29 @@ they appear in the batchfile.
def _handle_comment(self, _): def _handle_comment(self, _):
pass pass
def _write_metadata_impl(self, line): def _write_metadata_and_rename_impl(self, line):
if self.dry_run: if self.dry_run:
print("DRY RUN would write metadata here.", file=self.outstream) 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: else:
self.outstream.write("Writing metadata... ") self.outstream.write("Writing metadata... ")
self.write_metadata_f(line, self.env.metadatums) 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.increment_all()
self.env.revert_onces() self.env.revert_onces()
@@ -234,10 +253,16 @@ they appear in the batchfile.
self._print_kv_columnar(key, value) 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: if interactive:
val = input('Write? [Y/n/a/:] > ') val = input('Write? [Y/n/a/:] > ')
if val == '' or val[0].upper() == 'Y': if val == '' or val[0].upper() == 'Y':
self._write_metadata_impl(line) self._write_metadata_and_rename_impl(line)
break break
if val.startswith(self.COMMAND_LEADER): if val.startswith(self.COMMAND_LEADER):
self._handle_command(val.lstrip(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) print("Aborting write session...", file=sys.stdout)
break break
else: else:
self._write_metadata_impl(line) self._write_metadata_and_rename_impl(line)
break break
def set(self, args): def set(self, args):
@@ -322,6 +347,17 @@ they appear in the batchfile.
repl = args[3] repl = args[3]
self.env.set_pattern(key, inp, pattern, repl) 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): def d(self, args):
""" """
d VALUE d VALUE

View File

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