Cleaning some music collection converters.
This commit is contained in:
parent
b39807db44
commit
c09179f5da
8 changed files with 94 additions and 80 deletions
14
src/guile/hello-test.scm
Normal file
14
src/guile/hello-test.scm
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
(use-modules (srfi srfi-64)
|
||||||
|
(hello))
|
||||||
|
|
||||||
|
(test-begin "harness")
|
||||||
|
|
||||||
|
(test-equal "test-hello"
|
||||||
|
"Hello world!\n"
|
||||||
|
(hi))
|
||||||
|
|
||||||
|
(test-equal "test-named-hello"
|
||||||
|
"Hello bob!\n"
|
||||||
|
(hi "bob"))
|
||||||
|
|
||||||
|
(test-end "harness")
|
16
src/guile/hello.scm
Normal file
16
src/guile/hello.scm
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
(define-module (hello))
|
||||||
|
|
||||||
|
(define GREETING_PREFIX "Hello ")
|
||||||
|
(define GREETING_SUFFIX "!\n")
|
||||||
|
(define DEFAULT_NAME "world")
|
||||||
|
|
||||||
|
(define-public hi
|
||||||
|
"Says hello to the named person"
|
||||||
|
(lambda* (#:optional name)
|
||||||
|
(string-append GREETING_PREFIX (addressee name) GREETING_SUFFIX)))
|
||||||
|
|
||||||
|
(define addressee
|
||||||
|
(lambda (name)
|
||||||
|
(if name
|
||||||
|
name
|
||||||
|
DEFAULT_NAME)))
|
|
@ -1,48 +1,37 @@
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from typing import NamedTuple, Callable
|
from typing import Callable
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from .tasks import Task
|
from jgutils.filesystem import get_files_recursive
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class ConversionConfig(NamedTuple):
|
def _get_converted_path(input_path: Path,
|
||||||
input_dir: Path
|
output_dir: Path,
|
||||||
output_dir: Path
|
output_ext: str):
|
||||||
input_ext: str
|
|
||||||
output_ext: str
|
|
||||||
|
|
||||||
def _get_converted_path(input_path: Path, config: ConversionConfig):
|
|
||||||
"""
|
"""
|
||||||
Return the path you would obtain if you moved the file in the input
|
Return the path you would obtain if you moved the file in the input
|
||||||
path to the output directory in the config and changed its extension.
|
path to the output directory in the config and changed its extension.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
relative_path = input_path.relative_to(config.input_dir)
|
output_filename = str(input_path.stem) + f".{output_ext}"
|
||||||
output_filename = str(input_path.stem) + f".{config.output_ext}"
|
return output_dir / input_path.parent / output_filename
|
||||||
return output_dir / relative_path.parent / output_filename
|
|
||||||
|
|
||||||
|
def convert(input_dir: Path,
|
||||||
|
output_dir: Path,
|
||||||
|
input_ext: str,
|
||||||
|
output_ext: str,
|
||||||
|
conversion_func: Callable):
|
||||||
|
|
||||||
def get_unconverted_files(
|
logger.info("Converting files in %s to %s", input_dir, output_dir)
|
||||||
input_files: list[Path], config: ConversionConfig
|
|
||||||
) -> list[Path]:
|
|
||||||
output_files = [_get_converted_path(f, config) for f in input_files]
|
|
||||||
|
|
||||||
conversion_pairs = []
|
input_files = get_files_recursive(input_dir.resolve(), [input_ext])
|
||||||
for input_file, output_file in zip(input_files, output_files):
|
output_files = [_get_converted_path(f.relative_to(input_dir),
|
||||||
if not output_file.exists():
|
output_dir,
|
||||||
conversion_pairs.append((input_file, output_file))
|
output_ext) for f in input_files]
|
||||||
return conversion_pairs
|
conversion_pairs = [io_paths for io_paths in
|
||||||
|
zip(input_files, output_files) if not io_paths[1].exists()]
|
||||||
|
|
||||||
|
run_tasks(conversion_func, conversion_pairs)
|
||||||
def convert(config: ConversionConfig, conversion_func: Callable):
|
|
||||||
|
|
||||||
logger.info("Converting files in %s", config.input_dir)
|
|
||||||
logger.info("Writing output to: %s", config.output_dir)
|
|
||||||
|
|
||||||
input_files = get_files_recursive(config.input_dir.resolve(), [config.input_ext])
|
|
||||||
output_files = get_uncoverted_files(input_files, config)
|
|
||||||
|
|
||||||
run_tasks(conversion_func, zip(input_files, output_files))
|
|
||||||
|
|
|
@ -7,25 +7,29 @@ def get_files_recursive(path: Path, extensions: list[str] | None) -> list[Path]:
|
||||||
if not extensions:
|
if not extensions:
|
||||||
return list(path.rglob("*.*"))
|
return list(path.rglob("*.*"))
|
||||||
else:
|
else:
|
||||||
ret = []
|
paths_list = [list(path.rglob(f"*.{ext}")) for ext in extensions]
|
||||||
for ext in extensions:
|
return [p for paths in paths_list for p in paths]
|
||||||
ret.append(list(search_path.rglob(f"*.{ext}")))
|
|
||||||
return ret
|
def get_empty_dirs(path: Path) -> list[Path]:
|
||||||
|
return [curdir
|
||||||
|
for curdir, subdirs, files in os.walk(path)
|
||||||
|
if len(subdirs)==0 and len(files) == 0]
|
||||||
|
|
||||||
def _delete_empty_dirs(path: Path) -> bool:
|
def _delete_empty_dirs(path: Path) -> bool:
|
||||||
found_empty = False
|
empty_dirs = get_empty_dirs(path)
|
||||||
for curdir, subdirs, files in os.walk(path):
|
map(os.rmdir, empty_dirs)
|
||||||
if len(subdirs) == 0 and len(files) == 0:
|
return bool(empty_dirs)
|
||||||
found_empty = True
|
|
||||||
os.rmdir(curdir)
|
|
||||||
return found_empty
|
|
||||||
|
|
||||||
def delete_empty_dirs_recursive(path: Path):
|
def delete_empty_dirs_recursive(path: Path):
|
||||||
found_empty = True
|
found_empty = True
|
||||||
while found_empty:
|
while found_empty:
|
||||||
found_empty = _delete_empty_dirs(path)
|
found_empty = _delete_empty_dirs(path)
|
||||||
|
|
||||||
|
|
||||||
def replace_filename(path: Path, replacement: str) -> Path:
|
def replace_filename(path: Path, replacement: str) -> Path:
|
||||||
return path.parent / (replacement + path.suffix)
|
return path.parent / (replacement + path.suffix)
|
||||||
|
|
||||||
|
def move_file_new_name(src: Path, dst: Path, name: str):
|
||||||
|
os.makedirs(dst, exist_ok=True)
|
||||||
|
shutil.move(src, dst / f"{name}{src.suffix}")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -12,8 +12,8 @@ def cli_music_read(args):
|
||||||
write_model(collection)
|
write_model(collection)
|
||||||
|
|
||||||
def cli_music_refresh(args):
|
def cli_music_refresh(args):
|
||||||
music.collection.refresh(args.collection.resolve(),
|
music.refresh(args.collection.resolve(),
|
||||||
args.work_dir.resolve())
|
args.work_dir.resolve())
|
||||||
|
|
||||||
def main_cli():
|
def main_cli():
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
from .collection import refresh # NOQA
|
|
@ -6,7 +6,7 @@ from pathlib import Path
|
||||||
from tinytag import TinyTag
|
from tinytag import TinyTag
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from jgutils.filesystem import get_files_recursive
|
from jgutils.filesystem import get_files_recursive, move_file_new_name
|
||||||
from jgutils import converters
|
from jgutils import converters
|
||||||
|
|
||||||
from .models import Song, Album, Artist
|
from .models import Song, Album, Artist
|
||||||
|
@ -21,12 +21,6 @@ class MusicCollection(BaseModel):
|
||||||
|
|
||||||
artists: list[Artist] = []
|
artists: list[Artist] = []
|
||||||
|
|
||||||
def get_artist(self, name:str) -> Artist | None:
|
|
||||||
for artist in self.artists:
|
|
||||||
if artist.name == name:
|
|
||||||
return artist
|
|
||||||
return None
|
|
||||||
|
|
||||||
_DEFAULT_EXTENSIONS = ["flac",
|
_DEFAULT_EXTENSIONS = ["flac",
|
||||||
"mp3"]
|
"mp3"]
|
||||||
|
|
||||||
|
@ -40,17 +34,18 @@ def read(input_dir: Path, extensions: list[str] = None) -> MusicCollection:
|
||||||
if not extensions:
|
if not extensions:
|
||||||
extensions = _DEFAULT_EXTENSIONS
|
extensions = _DEFAULT_EXTENSIONS
|
||||||
|
|
||||||
collection = MusicCollection()
|
tags = [TingTag.get(f) for f in get_files_recursive(input_dir, extensions)]
|
||||||
for eachFile in get_files_recursive(input_dir, extensions):
|
tags_with_titles = [t for t in tags if t.title]
|
||||||
tag = TinyTag.get(eachFile)
|
|
||||||
if not tag.title:
|
|
||||||
logger.warn("Found tag with no title, skipping: %s", tag)
|
|
||||||
continue
|
|
||||||
|
|
||||||
artist = collection.get_artist(tag.artist)
|
artists = []
|
||||||
if not artist:
|
for tag in tags_with_titles:
|
||||||
|
|
||||||
|
matching_artists = filter(lambda x: x.name == tag.artist, artists)
|
||||||
|
if not matching_artists:
|
||||||
artist = Artist(name=tag.artist)
|
artist = Artist(name=tag.artist)
|
||||||
collection.artists.append(artist)
|
artists.append(artist)
|
||||||
|
else:
|
||||||
|
artist = next(matching_artists)
|
||||||
|
|
||||||
if tag.album:
|
if tag.album:
|
||||||
album = artist.get_album(tag.album)
|
album = artist.get_album(tag.album)
|
||||||
|
@ -65,7 +60,15 @@ def read(input_dir: Path, extensions: list[str] = None) -> MusicCollection:
|
||||||
else:
|
else:
|
||||||
artist.songs.append(song)
|
artist.songs.append(song)
|
||||||
|
|
||||||
return collection
|
return MusicCollection(artists=artists)
|
||||||
|
|
||||||
|
def _refresh_collection_dirs(tags: list, collection_dir: Path):
|
||||||
|
for tag, song_file in tags:
|
||||||
|
artist_dir = collection_dir / tag.artist
|
||||||
|
if tag.album:
|
||||||
|
move_file_new_name(song_file, artist_dir / tag.album, tag.title)
|
||||||
|
else:
|
||||||
|
move_file_new_name(song_file, artist_dir, tag.title)
|
||||||
|
|
||||||
def refresh(collection_dir: Path, work_dir: Path):
|
def refresh(collection_dir: Path, work_dir: Path):
|
||||||
|
|
||||||
|
@ -76,29 +79,14 @@ def refresh(collection_dir: Path, work_dir: Path):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
ext = "flac"
|
ext = "flac"
|
||||||
files = get_files_recursive(collection_dir, [ext])
|
tags = [(TinyTag.get(f), f) for f in get_files_recursive(collection_dir, [ext])]
|
||||||
|
tags_with_titles = [t for t in tags if t[0].title]
|
||||||
|
|
||||||
for eachFile in files:
|
_refresh_collection_dirs(tags_with_titles, collection_dir)
|
||||||
tag = TinyTag.get(eachFile)
|
|
||||||
if not tag.title:
|
|
||||||
logger.warn("Found tag with no title, skipping: %s", tag)
|
|
||||||
continue
|
|
||||||
|
|
||||||
artist_dir = input_dir / tag.artist
|
|
||||||
os.makedirs(artist_dir, exist_ok=True)
|
|
||||||
|
|
||||||
if tag.album:
|
|
||||||
os.makedirs(artist_dir / tag.album, exist_ok=True)
|
|
||||||
shutil.move(eachFile, artist_dir / tag.album / f"{tag.title}.{ext}")
|
|
||||||
else:
|
|
||||||
shutil.move(eachFile, artist_dir / f"{tag.title}.{ext}")
|
|
||||||
|
|
||||||
# Get any files not existing on the sync target, convert them to a suitable format
|
# Get any files not existing on the sync target, convert them to a suitable format
|
||||||
# and push them to the target.
|
# and push them to the target.
|
||||||
config = converters.ConversionConfig(
|
converters.convert(collection_dir, work_dir, "flac", "mp3")
|
||||||
collection_dir, work_dir, "flac", "mp3"
|
|
||||||
)
|
|
||||||
converters.convert(config)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
class Song(BaseModel):
|
class Song(BaseModel):
|
||||||
|
|
Loading…
Reference in a new issue