Skip to content

Instantly share code, notes, and snippets.

@patrickwolf
Created June 21, 2024 10:59
Show Gist options
  • Save patrickwolf/ccb2ae7cf93999cb93c56b378706e2e6 to your computer and use it in GitHub Desktop.
Save patrickwolf/ccb2ae7cf93999cb93c56b378706e2e6 to your computer and use it in GitHub Desktop.
Post processing for AAX Audio Converter to keep JSON files
"""
AAX Audio Converter PostJob v0.1
Post processing for AAX Audio Converter to keep JSON files.
This script maps and copies JSON files based on AAX filenames to their respective destinations.
Authors: Patrick Wolf, ChatGPT
Year: 2024
License: Open source (all rights allowed)
# Setup:
Settings > General > Copy .aax to second folder with (flat) \ <book> - <author>
- Set Folder to AAXTemp
Settings > Folder Structure > Flat Folder Structure and Naming : <book> - <author>
This makes AAX create these files
AAXTemp/5 Ways to Improve Your Sleep - Alexandra Hayes_B088GNTWH5.aax
target/5 Ways to Improve Your Sleep - Alexandra Hayes/5 Ways to Improve Your Sleep - Alexandra Hayes.m4b"
And then this script will generate
target/5 Ways to Improve Your Sleep - Alexandra Hayes/5 Ways to Improve Your Sleep - Alexandra Hayes.json
target/5 Ways to Improve Your Sleep - Alexandra Hayes/5 Ways to Improve Your Sleep - Alexandra Hayes_content.json
# Command line:
python aax_audio_converter_keep_json.py /path/to/source /path/to/destination --dry_run
"""
import os
import shutil
from typing import List, Tuple
import argparse
import unittest
class FileMapper:
def __init__(self, srcDirList: List[str], dstDirList: List[str]):
# Normalize paths to use forward slashes
self.srcDirList = [s.replace('\\', '/') for s in srcDirList]
self.dstDirList = [d.replace('\\', '/') for d in dstDirList]
def determineActions(self, aax_filename: str) -> List[Tuple[str, str]]:
"""Determine the actions needed to copy JSON files based on the AAX filename."""
actions = []
# Normalize path to use forward slashes
aax_filename = aax_filename.replace('\\', '/')
base_name = os.path.basename(aax_filename)
new_name, asin = base_name.rsplit('_', 1)
asin = asin.replace('.aax', '')
json_file = f"{asin}.json"
metadata_file = f"content_metadata_{asin}.json"
for dst in self.dstDirList:
if new_name in dst:
folder_name = os.path.basename(dst)
json_dst = f"{dst}/{folder_name}.json"
metadata_dst = f"{dst}/{folder_name}_metadata.json"
json_src = json_file
metadata_src = metadata_file
if json_src in self.srcDirList:
actions.append((json_src, json_dst))
if metadata_src in self.srcDirList:
actions.append((metadata_src, metadata_dst))
break
return actions
def executeActions(self, actions: List[Tuple[str, str]], source_path: str,
destination_path: str, dry_run: bool):
"""Execute the determined actions or print them if in dry run mode."""
for src, dst in actions:
src_full = os.path.normpath(os.path.join(source_path, src))
dst_full = os.path.normpath(os.path.join(destination_path, dst))
try:
action = ""
if dry_run:
action = "Copy (dry)"
else:
if os.path.exists(dst_full):
action = "Skip"
else:
shutil.copy(src_full, dst_full)
action = "Copy"
print(f"\n{action}\nFrom {src_full}\nTo {dst_full}")
except FileNotFoundError as e:
print(f"Error: {e}\nFile not found: {src_full}")
except PermissionError as e:
print(f"Error: {e}\nPermission denied: {dst_full}")
except Exception as e:
print(
f"Error: {e}\nUnexpected error while copying from {src_full} to {dst_full}"
)
def main(source_path: str, destination_path: str, dry_run: bool):
srcDirList = os.listdir(source_path)
dstDirList = os.listdir(destination_path)
file_mapper = FileMapper(srcDirList, dstDirList)
for file in [f for f in srcDirList if f.endswith(".aax")]:
actions = file_mapper.determineActions(file)
file_mapper.executeActions(actions, source_path, destination_path,
dry_run)
class TestFileMapper(unittest.TestCase):
def setUp(self):
self.srcDirList = [
"B00HU7T81S.json", "content_metadata_B00HU7T81S.json",
"B0193MJLV4.json", "content_metadata_B0193MJLV4.json",
"B123456789.json", "content_metadata_B123456789.json"
]
self.dstDirList = [
"/Complete/12 Essential Scientific Concepts - The Great Courses; Indre Viskontas",
"/Complete/5 Ways to Improve Your Sleep - Alexandra Hayes",
"/Complete/Some Book Title_with_multiple_underscores"
]
def test_determineActions(self):
aax_filename = "/Data/12 Essential Scientific Concepts - The Great Courses; Indre Viskontas_B00HU7T81S.aax"
expected_actions = [
("B00HU7T81S.json",
"/Complete/12 Essential Scientific Concepts - The Great Courses; Indre Viskontas/12 Essential Scientific Concepts - The Great Courses; Indre Viskontas.json"
),
("content_metadata_B00HU7T81S.json",
"/Complete/12 Essential Scientific Concepts - The Great Courses; Indre Viskontas/12 Essential Scientific Concepts - The Great Courses; Indre Viskontas_metadata.json"
)
]
file_mapper = FileMapper(self.srcDirList, self.dstDirList)
actions = file_mapper.determineActions(aax_filename)
self.assertEqual(actions, expected_actions)
def test_determineActions_multiple_underscores(self):
aax_filename = "/Data/Some Book Title_with_multiple_underscores_B123456789.aax"
expected_actions = [
("B123456789.json",
"/Complete/Some Book Title_with_multiple_underscores/Some Book Title_with_multiple_underscores.json"
),
("content_metadata_B123456789.json",
"/Complete/Some Book Title_with_multiple_underscores/Some Book Title_with_multiple_underscores_metadata.json"
)
]
file_mapper = FileMapper(self.srcDirList, self.dstDirList)
actions = file_mapper.determineActions(aax_filename)
self.assertEqual(actions, expected_actions)
if __name__ == "__main__":
print("AAX Audio Converter PostJob v0.1")
default_source_path = r""
default_destination_path = r""
default_dry_run = False
# Define default values for testing
if False:
default_source_path = r"/path/to/aaxTemp"
default_destination_path = r"/path/to/completeFiles"
default_dry_run = False
parser = argparse.ArgumentParser(
description="Map and copy JSON files based on AAX file names.")
parser.add_argument("source_path",
type=str,
nargs='?',
default=default_source_path,
help="Path to the source directory")
parser.add_argument("destination_path",
type=str,
nargs='?',
default=default_destination_path,
help="Path to the destination directory")
parser.add_argument("--dry_run",
action="store_true",
default=default_dry_run,
help="Print actions without executing")
args = parser.parse_args()
if args.source_path and args.destination_path:
main(args.source_path, args.destination_path, args.dry_run)
else:
print("No parameters given. Running unit tests.")
unittest.main(argv=[''], exit=False)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment