format code

This commit is contained in:
ChaotiCryptidz 2022-02-04 13:54:10 +00:00
parent 5272a3d356
commit 1c57673dc4
11 changed files with 303 additions and 250 deletions

27
main.py
View file

@ -1,27 +0,0 @@
#!/usr/bin/env python3
import argparse
from musicutil.commands.process_command import ProcessCommand
from musicutil.commands.copy_command import CopyCommand
parser = argparse.ArgumentParser(description='Highly Opinionated Music ')
subparsers = parser.add_subparsers(dest="subparser_name")
process_parser = subparsers.add_parser('process')
process_parser.add_argument('src', type=str, help='src base music directory')
process_parser.add_argument('--dry-run', action='store_true')
copy_parser = subparsers.add_parser('copy')
copy_parser.add_argument('src', type=str, help='src base music directory')
copy_parser.add_argument('dest', type=str, help='dest music directory')
copy_parser.add_argument('--transcode-level', type=str, help='transcode level', default="copy")
copy_parser.add_argument('--skip-existing', action='store_true')
copy_parser.add_argument('--single-directory', action='store_true')
args = parser.parse_args()
if args.subparser_name == "process":
ProcessCommand(args.src, args.dry_run).run()
elif args.subparser_name == "copy":
CopyCommand(args.src, args.dest, args.transcode_level, args.single_directory, args.skip_existing).run()

52
musicutil/__main__.py Normal file
View file

@ -0,0 +1,52 @@
#!/usr/bin/env python3
import argparse
from .commands.process_command import ProcessCommand
from .commands.copy_command import CopyCommand
parser = argparse.ArgumentParser(
description="chaos's musicutil")
subparsers = parser.add_subparsers(dest="subparser_name")
process_parser = subparsers.add_parser('process')
process_parser.add_argument(
'src',
type=str,
help='src base music directory')
process_parser.add_argument(
'--dry-run', action='store_true')
copy_parser = subparsers.add_parser('copy')
copy_parser.add_argument(
'src',
type=str,
help='src base music directory')
copy_parser.add_argument(
'dest',
type=str,
help='dest music directory')
copy_parser.add_argument(
'--transcode-level',
type=str,
help='transcode level',
default="copy")
copy_parser.add_argument(
'--skip-existing',
action='store_true')
copy_parser.add_argument(
'--single-directory',
action='store_true')
args = parser.parse_args()
if args.subparser_name == "process":
ProcessCommand(args.src, args.dry_run).run()
elif args.subparser_name == "copy":
CopyCommand(
args.src,
args.dest,
args.transcode_level,
args.single_directory,
args.skip_existing
).run()

View file

@ -8,140 +8,149 @@ from shutil import copy as copy_file
from copy import deepcopy as deep_copy from copy import deepcopy as deep_copy
from subprocess import run as run_command from subprocess import run as run_command
class CopyCommandState: class CopyCommandState:
files: list[File] = [] files: list[File] = []
transcoded_files: list[File] = [] transcoded_files: list[File] = []
class CopyCommand():
def __init__(self,
src: str,
dest: str,
transcode_level: str,
single_directory: bool,
skip_existing: bool
):
self.src = src
self.dest = dest
self.transcode_level = transcode_level
self.single_directory = single_directory
self.skip_existing = skip_existing
self.state = CopyCommandState()
def run(self): class CopyCommand():
print("Copying") def __init__(self,
self.scan_for_music() src: str,
self.load_tag_information() dest: str,
if self.single_directory: transcode_level: str,
self.check_for_collisions() single_directory: bool,
self.transcode_files() skip_existing: bool
):
self.src = src
self.dest = dest
self.transcode_level = transcode_level
self.single_directory = single_directory
self.skip_existing = skip_existing
self.state = CopyCommandState()
def scan_for_music(self): def run(self):
print("Scanning For Music") print("Copying")
self.state.files = scan_for_music(self.src) self.scan_for_music()
self.load_tag_information()
if self.single_directory:
self.check_for_collisions()
self.transcode_files()
def load_tag_information(self): def scan_for_music(self):
print("Loading Tag Information") print("Scanning For Music")
self.state.files = scan_for_music(self.src)
for file in self.state.files: def load_tag_information(self):
file.tags = load_tag_information(file) print("Loading Tag Information")
def check_for_collisions(self):
print("Checking For Colisions")
seen = set()
dupes = []
for file in self.state.files: for file in self.state.files:
filename = file.filename file.tags = load_tag_information(file)
if filename in seen:
dupes.append(filename)
else:
seen.add(filename)
if len(dupes) > 0: def check_for_collisions(self):
print("Dupes Found:", dupes) print("Checking For Colisions")
print("Cannot continue using --single-directory") seen = set()
print("Please rename or remove duplicates") dupes = []
exit()
def _transcode_copy(self, file: File): for file in self.state.files:
src = file.join_path_to() filename = file.filename
dest = file.join_filename() if self.single_directory else file.join_path_from_src() if filename in seen:
dest = self.dest + "/" + dest dupes.append(filename)
else:
seen.add(filename)
exists = path_exists(dest) if len(dupes) > 0:
print("Dupes Found:", dupes)
print("Cannot continue using --single-directory")
print("Please rename or remove duplicates")
exit()
if (self.skip_existing and not exists) or not self.skip_existing: def _transcode_copy(self, file: File):
print("Copying", src, "to", dest) src = file.join_path_to()
copy_file( dest = file.join_filename(
src, ) if self.single_directory else file.join_path_from_src()
dest, dest = self.dest + "/" + dest
)
else:
print("Skipping", src, "as already is copied at", dest)
self.state.transcoded_files.append(file)
def _transcode_with_level(self, file: File, level: str): exists = path_exists(dest)
transcoded_file_extension = "opus"
src = file.join_path_to() if (self.skip_existing and not exists) or not self.skip_existing:
print("Copying", src, "to", dest)
copy_file(
src,
dest,
)
else:
print(
"Skipping",
src,
"as already is copied at",
dest)
new_file = deep_copy(file) self.state.transcoded_files.append(file)
new_file.extension = transcoded_file_extension
dest_filepath = new_file.join_filename() if self.single_directory else new_file.join_path_from_src()
dest_filepath = self.dest + "/" + dest_filepath
if (self.skip_existing and path_exists(dest_filepath)): def _transcode_with_level(self, file: File, level: str):
print("Skipping transcoding", dest_filepath) transcoded_file_extension = "opus"
self.state.transcoded_files.append(new_file)
return
bitrate = ""
if self.transcode_level == "high": src = file.join_path_to()
bitrate = "128K"
elif self.transcode_level == "medium":
bitrate = "96K"
elif self.transcode_level == "low":
bitrate = "64K"
print("Transcoding", src, "to", dest_filepath) new_file = deep_copy(file)
new_file.extension = transcoded_file_extension
title = file.tags.title dest_filepath = new_file.join_filename(
artist = file.tags.artist ) if self.single_directory else new_file.join_path_from_src()
dest_filepath = self.dest + "/" + dest_filepath
# TODO: check for errors if (self.skip_existing and path_exists(dest_filepath)):
run_command([ print("Skipping transcoding", dest_filepath)
"ffmpeg", self.state.transcoded_files.append(new_file)
"-y", return
"-hide_banner",
"-loglevel", "warning",
"-i", src,
"-c:a", "libopus",
"-b:a", bitrate,
"-metadata", f"title=\"{title}\"",
"-metadata", f"artist=\"{artist}\"",
dest_filepath
])
self.state.transcoded_files.append(new_file) bitrate = ""
def transcode_files(self):
print("Transcoding Files")
if not self.single_directory:
directories = set()
for file in self.state.files:
directories.add(file.path_from_src)
for dir in directories:
make_directories(self.dest + "/" + dir, exist_ok=True)
if self.transcode_level == "copy": if self.transcode_level == "high":
for file in self.state.files: bitrate = "128K"
self._transcode_copy(file) elif self.transcode_level == "medium":
return bitrate = "96K"
elif self.transcode_level in ["high", "medium", "low"]: elif self.transcode_level == "low":
for file in self.state.files: bitrate = "64K"
self._transcode_with_level(file, self.transcode_level)
print("Transcoding", src, "to", dest_filepath)
title = file.tags.title
artist = file.tags.artist
# TODO: check for errors
run_command([
"ffmpeg",
"-y",
"-hide_banner",
"-loglevel", "warning",
"-i", src,
"-c:a", "libopus",
"-b:a", bitrate,
"-metadata", f"title=\"{title}\"",
"-metadata", f"artist=\"{artist}\"",
dest_filepath
])
self.state.transcoded_files.append(new_file)
def transcode_files(self):
print("Transcoding Files")
if not self.single_directory:
directories = set()
for file in self.state.files:
directories.add(file.path_from_src)
for dir in directories:
make_directories(
self.dest + "/" + dir, exist_ok=True)
if self.transcode_level == "copy":
for file in self.state.files:
self._transcode_copy(file)
return
elif self.transcode_level in ["high", "medium", "low"]:
for file in self.state.files:
self._transcode_with_level(
file, self.transcode_level)

View file

@ -6,57 +6,63 @@ from ..utils.substitutions import reduce_to_ascii_and_substitute
from copy import deepcopy as deep_copy from copy import deepcopy as deep_copy
from os import rename as rename_file from os import rename as rename_file
class ProcessCommandState: class ProcessCommandState:
files: list[File] = [] files: list[File] = []
class ProcessCommand():
def __init__(self, src: str, dry_run: bool):
self.src = src
self.dry_run = dry_run
self.state = ProcessCommandState()
def run(self): class ProcessCommand():
print("Renaming") def __init__(self, src: str, dry_run: bool):
self.scan_for_music() self.src = src
self.load_tag_information() self.dry_run = dry_run
self.rename_files() self.state = ProcessCommandState()
def scan_for_music(self): def run(self):
print("Scanning For Music") print("Renaming")
self.state.files = scan_for_music(self.src) self.scan_for_music()
self.load_tag_information()
self.rename_files()
def load_tag_information(self): def scan_for_music(self):
print("Loading Tag Information") print("Scanning For Music")
self.state.files = scan_for_music(self.src)
for file in self.state.files: def load_tag_information(self):
tags = load_tag_information(file) print("Loading Tag Information")
file.tags = tags
def _rename_file(self, file: File) -> File: for file in self.state.files:
filename = file.filename tags = load_tag_information(file)
artist = file.tags.artist.replace("\n", "") file.tags = tags
title = file.tags.title.replace("\n", "")
proper_filename = reduce_to_ascii_and_substitute(f"{artist} - {title}") def _rename_file(self, file: File) -> File:
filename = file.filename
artist = file.tags.artist.replace("\n", "")
title = file.tags.title.replace("\n", "")
if filename != proper_filename: proper_filename = reduce_to_ascii_and_substitute(
print(f"Renaming \"{filename}\"", "to" + f"\"{proper_filename}\"" + "\n") f"{artist} - {title}")
new_file = deep_copy(file) if filename != proper_filename:
new_file.filename = proper_filename print(f"Renaming \"{filename}\"", "to" +
f"\"{proper_filename}\"" + "\n")
if not self.dry_run: new_file = deep_copy(file)
rename_file(file.join_path_to(), new_file.join_path_to()) new_file.filename = proper_filename
# so that other steps after read the new file and not
# the orig pre-rename file when not dry run
return new_file
else:
return file
def rename_files(self): if not self.dry_run:
print("Renaming files") rename_file(
file.join_path_to(),
new_file.join_path_to())
# so that other steps after read the new file and not
# the orig pre-rename file when not dry run
return new_file
else:
return file
for file in self.state.files: def rename_files(self):
index = self.state.files.index(file) print("Renaming files")
self.state.files[index] = self._rename_file(file)
for file in self.state.files:
index = self.state.files.index(file)
self.state.files[index] = self._rename_file(
file)

View file

@ -1,7 +1,9 @@
# All file extensions that are supported and have tag
# extraction
supported_formats = ["mp3", "flac"] supported_formats = ["mp3", "flac"]
sub_char = "_" sub_char = "_"
substitutions = { substitutions = {
"α": "a", "α": "a",
} }

View file

@ -1,43 +1,44 @@
class Tags: class Tags:
title = "" title = ""
artist = "" artist = ""
def to_dict(self): def to_dict(self):
return { return {
"title": self.title, "title": self.title,
"artist": self.artist "artist": self.artist
} }
def __repr__(self):
return repr(self.to_dict())
def __repr__(self):
return repr(self.to_dict())
class File: class File:
filename = "" filename = ""
extension = "" extension = ""
# The path to the actual file # The path to the actual file
path_to = "" path_to = ""
# The path relative to the source directory # The path relative to the source directory
path_from_src = "" path_from_src = ""
tags = Tags() tags = Tags()
def join_filename(self): def join_filename(self):
return f"{self.filename}.{self.extension}" return f"{self.filename}.{self.extension}"
def join_path_to(self): def join_path_to(self):
return f"{self.path_to}/{self.filename}.{self.extension}" return f"{self.path_to}/{self.filename}.{self.extension}"
def join_path_from_src(self): def join_path_from_src(self):
return f"{self.path_from_src}/{self.filename}.{self.extension}" return f"{self.path_from_src}/{self.filename}.{self.extension}"
def to_dict(self): def to_dict(self):
return { return {
"filename": self.filename, "filename": self.filename,
"extension": self.extension, "extension": self.extension,
"path_to": self.path_to, "path_to": self.path_to,
"path_from_src": self.path_from_src, "path_from_src": self.path_from_src,
"tags": self.tags.to_dict(), "tags": self.tags.to_dict(),
} }
def __repr__(self): def __repr__(self):
return repr(self.to_dict()) return repr(self.to_dict())

View file

@ -3,18 +3,19 @@ from ..types import File, Tags
from mutagen.mp3 import EasyMP3 as MP3 from mutagen.mp3 import EasyMP3 as MP3
from mutagen.flac import FLAC from mutagen.flac import FLAC
def load_tag_information(file: File) -> Tags: def load_tag_information(file: File) -> Tags:
path = file.join_path_to() path = file.join_path_to()
tags = Tags() tags = Tags()
if file.extension == "mp3": if file.extension == "mp3":
mp3 = MP3(path) mp3 = MP3(path)
tags.title = mp3["title"][0] tags.title = mp3["title"][0]
tags.artist = mp3["artist"][0] tags.artist = mp3["artist"][0]
elif file.extension == "flac": elif file.extension == "flac":
flac = FLAC(path) flac = FLAC(path)
tags.title = flac["title"][0] tags.title = flac["title"][0]
tags.artist = flac["artist"][0] tags.artist = flac["artist"][0]
else: else:
print("Invalid / Unsupported Container") print("Invalid / Unsupported Container")
exit() exit()
return tags return tags

View file

@ -4,14 +4,16 @@ from os.path import relpath
from ..types import File from ..types import File
from ..meta import supported_formats from ..meta import supported_formats
def scan_for_music(src: str) -> list[File]: def scan_for_music(src: str) -> list[File]:
files: list[File] = [] files: list[File] = []
for format in supported_formats: for format in supported_formats:
for path in Path(src).rglob("*." + format): for path in Path(src).rglob("*." + format):
file = File() file = File()
file.path_to = str(path.parent) file.path_to = str(path.parent)
file.path_from_src = relpath(str(path.parent), src) file.path_from_src = relpath(
file.filename = path.stem str(path.parent), src)
file.extension = path.suffix.replace(".", "") file.filename = path.stem
files.append(file) file.extension = path.suffix.replace(".", "")
return files files.append(file)
return files

View file

@ -1,11 +1,13 @@
from ..meta import sub_char, substitutions from ..meta import sub_char, substitutions
from fold_to_ascii import fold from fold_to_ascii import fold
def reduce_to_ascii_and_substitute(filename: str):
filename = filename.replace("/", sub_char) def reduce_to_ascii_and_substitute(filename: str):
filename = filename.replace("\\", sub_char) filename = filename.replace("/", sub_char)
filename = filename.replace("\n", "") filename = filename.replace("\\", sub_char)
for sub_before in substitutions.keys(): filename = filename.replace("\n", "")
filename = filename.replace(sub_before, substitutions[sub_before]) for sub_before in substitutions.keys():
filename = fold(filename) filename = filename.replace(
return filename sub_before, substitutions[sub_before])
filename = fold(filename)
return filename

5
pyproject.toml Normal file
View file

@ -0,0 +1,5 @@
[tool.autopep8]
max_line_length = 60
in-place = true
recursive = true
aggressive = 3

View file

@ -2,6 +2,6 @@
let let
fold-to-ascii = (py: py.callPackage ./nix-extra-deps/fold-to-ascii.nix { }); fold-to-ascii = (py: py.callPackage ./nix-extra-deps/fold-to-ascii.nix { });
my_python = pkgs.python39.withPackages my_python = pkgs.python39.withPackages
(py: with py; [ py.mutagen (fold-to-ascii py) ]); (py: with py; [ py.mutagen (fold-to-ascii py) py.autopep8 ]);
in pkgs.mkShell { packages = with pkgs; [ my_python ffmpeg ]; } in pkgs.mkShell { packages = with pkgs; [ my_python ffmpeg ]; }