60 Commits
v0.4.0 ... 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
Jamie Hardt
66ac136270 autopep 2025-05-20 15:23:14 -07:00
Jamie Hardt
aa64d5e183 Fixed a bug in the increment code
Not sure how that happened I thought this used to work!
2025-05-20 15:19:46 -07:00
Jamie Hardt
6766e81b23 Commenting this out for now 2024-10-17 21:32:34 -07:00
Jamie Hardt
a2ce03a259 Added default lines to the top of a new list 2024-10-17 12:19:08 -07:00
Jamie Hardt
0ba40893df Adding some blank picture methods...
...to be implemented later if ever a need.
2024-10-17 09:50:27 -07:00
Jamie Hardt
e2b93f5183 Fixed obvious bug in --help-commands 2024-10-16 14:39:41 -07:00
Jamie Hardt
7015e80cf9 Merge pull request #1 from iluvcapra/iluvcapra-patch-py313
Add support for Python 3.13
2024-10-16 14:33:43 -07:00
Jamie Hardt
042f3116dd Update pyproject.toml 2024-10-16 14:33:02 -07:00
Jamie Hardt
20518fa31c Update pyproject.toml 2024-10-16 14:32:31 -07:00
Jamie Hardt
c4a2e380de Update pylint.yml
Adding 3.13 to test matrix
2024-10-16 14:30:09 -07:00
Jamie Hardt
4ec028b51b Pylints 2024-10-16 14:19:15 -07:00
Jamie Hardt
ac00f93a8d Update pyproject.toml
Nudged version to 0.5.0
2024-10-16 14:10:49 -07:00
Jamie Hardt
8bcfd2ee54 Added .venv to gitignore 2024-10-16 14:09:57 -07:00
Jamie Hardt
63ec226be1 Implemented sorting on creation 2024-10-16 14:09:18 -07:00
Jamie Hardt
69cdb6dec1 Amended error line 2024-10-16 13:37:59 -07:00
Jamie Hardt
720eacc2a4 Added possible sort modes for --create 2024-10-15 22:09:55 -07:00
Jamie Hardt
afa02e4c96 Twiddle 2024-10-15 20:59:13 -07:00
Jamie Hardt
b986a36281 lint 2024-10-15 11:34:32 -07:00
Jamie Hardt
fb90b5db3c Online doc changes 2024-10-15 11:33:21 -07:00
Jamie Hardt
e69573b2a4 Made some online doc tweaks 2024-10-15 11:09:31 -07:00
Jamie Hardt
55cf591690 Added some commentary 2024-10-15 11:04:13 -07:00
Jamie Hardt
f0e05a9609 Paramaterized output stream
for reporting messages from BatchfileParser.
2024-07-07 22:44:52 -07:00
Jamie Hardt
88add2da85 Update pyproject.toml
Set explicit Trove descriptors for python version
2024-07-07 22:25:29 -07:00
Jamie Hardt
fd228493c6 Update README.md
Oops extra wheel badge
2024-07-07 22:22:55 -07:00
Jamie Hardt
11405ef06c Update pylint.yml
Added 3.12 to the test matrix
2024-07-07 22:22:00 -07:00
Jamie Hardt
229467c408 Update README.md
Added badges
2024-07-07 22:21:27 -07:00
Jamie Hardt
dc1c6cc742 Update pylint.yml
Tweaking
2024-07-07 22:16:46 -07:00
Jamie Hardt
d7a30275d1 Update pylint.yml
Added testing to the workflow
2024-07-07 22:15:07 -07:00
Jamie Hardt
61458660c9 pylint 2024-07-07 22:12:35 -07:00
Jamie Hardt
d286c4e6c7 Implemented some more tests 2024-07-07 22:11:40 -07:00
Jamie Hardt
8d299e2335 pylint 2024-07-07 15:21:24 -07:00
Jamie Hardt
e0431da6df Merge branch 'main' of https://github.com/iluvcapra/mfbatch 2024-07-07 15:15:14 -07:00
Jamie Hardt
d334181dc8 Test implementation 2024-07-07 15:15:01 -07:00
Jamie Hardt
0d765f9d84 Adding some tests 2024-07-07 14:40:52 -07:00
Jamie Hardt
ce6542b64d Updated version, gitignore 2024-07-07 13:50:23 -07:00
Jamie Hardt
5a47985154 Improved docs 2024-07-07 13:44:53 -07:00
Jamie Hardt
649427dd33 Made a fix that should work better 2024-07-07 13:44:10 -07:00
Jamie Hardt
21277aff15 Improved reset/key unset logic
Patterns and incrs are now unset as well
2024-07-07 13:42:24 -07:00
Jamie Hardt
1b044025ea Tweaked documentation 2024-07-07 13:35:55 -07:00
Jamie Hardt
2078c1559a Fixed bug in argparser help generation 2024-07-07 13:28:23 -07:00
8 changed files with 267 additions and 53 deletions

View File

@@ -1,4 +1,4 @@
name: Pylint name: Lint and Test
on: [push] on: [push]
@@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
python-version: ["3.8", "3.9", "3.10"] python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }} - name: Set up Python ${{ matrix.python-version }}
@@ -22,3 +22,7 @@ jobs:
- name: Analysing the code with pylint - name: Analysing the code with pylint
run: | run: |
pylint $(git ls-files '*.py') pylint $(git ls-files '*.py')
- name: Testing with unittest
run: |
python -m unittest tests

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

2
.gitignore vendored
View File

@@ -1,3 +1,5 @@
.DS_Store .DS_Store
dist/ dist/
*.pyc *.pyc
poetry.lock
.venv/

View File

@@ -1,3 +1,8 @@
![GitHub last commit](https://img.shields.io/github/last-commit/iluvcapra/mfbatch)
![](https://img.shields.io/github/license/iluvcapra/mfbatch.svg) ![](https://img.shields.io/pypi/pyversions/mfbatch.svg) [![](https://img.shields.io/pypi/v/mfbatch.svg)](https://pypi.org/project/mfbatch/) ![](https://img.shields.io/pypi/wheel/mfbatch.svg)
[![Lint and Test](https://github.com/iluvcapra/mfbatch/actions/workflows/pylint.yml/badge.svg)](https://github.com/iluvcapra/mfbatch/actions/workflows/pylint.yml)
# mfbatch # mfbatch
`mfbatch` is a command-line tool for batch-editing FLAC audio file metadata. `mfbatch` is a command-line tool for batch-editing FLAC audio file metadata.

View File

@@ -4,17 +4,18 @@ mfbatch main - Command entrypoint for mfbatch
import os import os
from glob import glob from glob import glob
from subprocess import run 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
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 metadata_funcs
from mfbatch.commands import BatchfileParser from mfbatch.commands import BatchfileParser
@@ -29,58 +30,112 @@ 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): 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 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: with open(command_file, mode='w', encoding='utf-8') as f:
f.write("# mfbatch\n\n")
metadatums = {} metadatums = {}
flac_files = glob('./**/*.flac', recursive=recursive)
flac_files = sorted(flac_files)
for path in tqdm(flac_files, unit='File', desc='Scanning FLAC files'):
this_file_metadata = flac.read_metadata(path)
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
keys = list(metadatums.keys()) f.write("# mfbatch\n\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") for path in tqdm(flac_files, unit='File',
desc='Scanning with metaflac...'):
metadatums, buffer = write_batchfile_entries_for_file(path,
metadatums)
f.write(buffer)
f.write("# mfbatch: create batchlist operation complete\n")
def main(): def main():
""" """
Entry point implementation Entry point implementation
""" """
op = ArgumentParser(usage="%prog (-c | -e | -W) [options]") op = ArgumentParser(
prog='mfbatch', usage='%(prog)s (-c | -e | -W) [options]')
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)
op.add_argument('-W', '--write', default=False, op.add_argument('-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_argument('-p', '--path', metavar='DIR', op.add_argument('-p', '--path', metavar='DIR',
help='chdir to DIR before running', help='chdir to DIR before running',
default=None) default=None)
op.add_argument('-s', '--sort', metavar='MODE', action='store',
default='path', help="when creating, Set mode to sort "
"files by. Default is 'path'. 'ctime, 'mtime' and 'name' "
"are also options.")
op.add_argument('-n', '--dry-run', action='store_true', op.add_argument('-n', '--dry-run', action='store_true',
help="dry-run -W.") help="dry-run -W.")
op.add_argument('-f', '--batchfile', metavar='FILE', op.add_argument('-f', '--batchfile', metavar='FILE',
@@ -100,12 +155,12 @@ def main():
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('_') and command != "eval"]
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")
sys.exit(0) sys.exit(0)
@@ -115,7 +170,18 @@ def main():
if options.create: if options.create:
mode_given = True mode_given = True
create_batch_list(options.batchfile) 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

@@ -9,9 +9,9 @@ import shutil
import re import re
import os.path import os.path
from typing import Dict, Tuple, Optional from typing import Callable, Dict, Tuple, Optional
import mfbatch.metaflac as flac from mfbatch.metaflac import write_metadata as flac
class UnrecognizedCommandError(Exception): class UnrecognizedCommandError(Exception):
@@ -64,6 +64,18 @@ class CommandEnv:
self.incr.pop(k, None) self.incr.pop(k, None)
self.patterns.pop(k, None) self.patterns.pop(k, None)
def reset_keys(self):
"""
Reset all keys in the environment
"""
all_keys = list(self.metadatums.keys())
for key in all_keys:
self.unset_key(key)
self.patterns = {}
self.incr = {}
def set_pattern(self, to: str, frm: str, pattern: str, repl: str): def set_pattern(self, to: str, frm: str, pattern: str, repl: str):
""" """
Establish a pattern replacement in the environment Establish a pattern replacement in the environment
@@ -103,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.
@@ -119,16 +134,17 @@ class CommandEnv:
""" """
Increment all increment keys. Increment all increment keys.
""" """
for k, v in self.incr.items(): for k, _ in self.incr.items():
v = int(v) val = int(self.metadatums[k])
self.metadatums[k] = self.incr[k] % (v + 1) self.metadatums[k] = self.incr[k] % (val + 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
comment, a ':' to denote a Command, and if neither of these are present, the comment, a ':' to denote a Command, and if neither of these are present, the
are interpreted as a file path to act upon. Empty lines are ignored. line is interpreted as a file path to act upon. Empty lines are ignored. Lines
are split into arguments using `shlex.split`.
If a line ends with a backslash '\\', the backslash is deleted and the contents If a line ends with a backslash '\\', the backslash is deleted and the contents
of the following line are appended to the end of the present line. of the following line are appended to the end of the present line.
@@ -140,6 +156,7 @@ they appear in the batchfile.
dry_run: bool dry_run: bool
env: CommandEnv env: CommandEnv
write_metadata_f: Callable
COMMAND_LEADER = ':' COMMAND_LEADER = ':'
COMMENT_LEADER = '#' COMMENT_LEADER = '#'
@@ -147,6 +164,8 @@ they appear in the batchfile.
def __init__(self): def __init__(self):
self.dry_run = True self.dry_run = True
self.env = CommandEnv() self.env = CommandEnv()
self.write_metadata_f = flac
self.outstream = sys.stdout
def eval(self, line: str, lineno: int, interactive: bool): def eval(self, line: str, lineno: int, interactive: bool):
""" """
@@ -176,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.") 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:
sys.stdout.write("Writing metadata... ") self.outstream.write("Writing metadata... ")
flac.write_metadata(line, self.env.metadatums) self.write_metadata_f(line, self.env.metadatums)
sys.stdout.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()
@@ -195,10 +230,10 @@ they appear in the batchfile.
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") self.outstream.write(f"{key:.<30} \033[4m{l}\033[0m\n")
key = None key = None
else: else:
sys.stdout.write(f"{' ' * 30} \033[4m{l}\033[0m\n") self.outstream.write(f"{' ' * 30} \033[4m{l}\033[0m\n")
def _handle_file(self, line, interactive): def _handle_file(self, line, interactive):
while True: while True:
@@ -207,9 +242,9 @@ they appear in the batchfile.
self.env.evaluate_patterns() self.env.evaluate_patterns()
if self.dry_run: if self.dry_run:
sys.stdout.write(f"\nDRY RUN File: \033[1m{line}\033[0m\n") self.outstream.write(f"\nDRY RUN File: \033[1m{line}\033[0m\n")
else: else:
sys.stdout.write(f"\nFile: \033[1m{line}\033[0m\n") self.outstream.write(f"\nFile: \033[1m{line}\033[0m\n")
for key, value in self.env.metadatums.items(): for key, value in self.env.metadatums.items():
@@ -218,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),
@@ -230,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):
@@ -267,7 +308,7 @@ they appear in the batchfile.
""" """
reset reset
All keys in the environment will be reset, subsequent files will have All keys in the environment will be reset, subsequent files will have
no keys set. no keys set, including keys set by the `setinc` and `setp` commands.
""" """
all_keys = list(self.env.metadatums.keys()) all_keys = list(self.env.metadatums.keys())
for k in all_keys: for k in all_keys:
@@ -306,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
@@ -313,3 +365,18 @@ they appear in the batchfile.
""" """
val = args[0] val = args[0]
self.env.set_once('DESCRIPTION', val) self.env.set_once('DESCRIPTION', val)
# def picture(self, args):
# """
# picture PATH
# Add PATH as a picture (flac picture type 0) to this and every
# subsequent file.
# """
# pass
#
# def nopicture(self, args):
# """
# unpicture
# Remove all p
# """
# pass

View File

@@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "mfbatch" name = "mfbatch"
version = "0.4.0" 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"
@@ -13,6 +13,11 @@ classifiers = [
'Environment :: Console', 'Environment :: Console',
'License :: OSI Approved :: MIT License', 'License :: OSI Approved :: MIT License',
'Topic :: Multimedia :: Sound/Audio :: Editors', 'Topic :: Multimedia :: Sound/Audio :: Editors',
'Programming Language :: Python :: 3.13',
'Programming Language :: Python :: 3.12',
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.9',
] ]
[tool.poetry.dependencies] [tool.poetry.dependencies]

65
tests/__init__.py Normal file
View File

@@ -0,0 +1,65 @@
"mfbatch tests"
import unittest
from unittest.mock import MagicMock
from typing import cast
from mfbatch.commands import BatchfileParser
class BatchfileParserTests(unittest.TestCase):
"""
Tests the BatchfileParser class
"""
def setUp(self):
self.command_parser = BatchfileParser()
self.command_parser.dry_run = False
self.command_parser.write_metadata_f = MagicMock()
def tearDown(self):
pass
def test_set_without_write(self):
"Test setting a key without writing"
self.command_parser.set(['TYPE', 'Everything'])
self.assertFalse(cast(MagicMock,
self.command_parser.write_metadata_f).called)
self.assertEqual(self.command_parser.env.metadatums['TYPE'],
'Everything')
def test_set_command(self):
"Test set command"
self.command_parser.set(['X', 'Y'])
self.command_parser.eval("./testfile.flac", lineno=1,
interactive=False)
self.assertTrue(cast(MagicMock,
self.command_parser.write_metadata_f).called)
self.assertEqual(cast(MagicMock,
self.command_parser.write_metadata_f).call_args.args,
('./testfile.flac', {'X': 'Y'}))
def test_unset_command(self):
"Test unset command"
self.command_parser.set(['A', '1'])
self.assertEqual(self.command_parser.env.metadatums['A'], '1')
self.command_parser.unset(['A'])
self.assertNotIn('A', self.command_parser.env.metadatums.keys())
def test_setp(self):
"Test setp command"
self.command_parser.set(['VAL', 'ABC123'])
self.command_parser.setp(['DONE', 'VAL', r"([A-Z]+)123", r"X\1"])
self.command_parser.eval("./testfile.flac", lineno=1,
interactive=False)
self.assertTrue(cast(MagicMock,
self.command_parser.write_metadata_f).called)
self.assertEqual(cast(MagicMock,
self.command_parser.write_metadata_f).call_args.args,
("./testfile.flac", {'VAL': 'ABC123', 'DONE': 'XABC'}))
def test_eval(self):
"Test eval"
self.command_parser.eval(":set A 1", 1, False)
self.assertEqual(self.command_parser.env.metadatums['A'], '1')