Source code for google_music_utils.misc
__all__ = [
'suggest_filename',
'template_to_filepath',
]
import re
from pathlib import Path
import more_itertools
from .constants import CHARACTER_REPLACEMENTS, TEMPLATE_PATTERNS
from .utils import list_to_single_value
def _replace_invalid_characters(filepath):
for char in CHARACTER_REPLACEMENTS:
filepath = filepath.replace(char, CHARACTER_REPLACEMENTS[char])
return filepath
def _split_number_field(field):
match = re.match(r'(\d+)(?:/\d+)?', field)
return match.group(1) if match else field
[docs]def suggest_filename(metadata):
"""Generate a filename like Google for a song based on metadata.
Parameters:
metadata (~collections.abc.Mapping): A metadata dict.
Returns:
str: A filename string without an extension.
"""
if 'title' in metadata and 'track_number' in metadata: # Music Manager.
suggested_filename = f"{metadata['track_number']:0>2} {metadata['title']}"
elif 'title' in metadata and 'trackNumber' in metadata: # Mobile.
suggested_filename = f"{metadata['trackNumber']:0>2} {metadata['title']}"
elif 'title' in metadata and 'tracknumber' in metadata: # audio-metadata/mutagen.
track_number = _split_number_field(
list_to_single_value(metadata['tracknumber'])
)
title = list_to_single_value(metadata['title'])
suggested_filename = f"{track_number:0>2} {title}"
else:
suggested_filename = f"00 {list_to_single_value(metadata.get('title', ['']))}"
return _replace_invalid_characters(suggested_filename)
[docs]def template_to_filepath(template, metadata, template_patterns=None):
"""Create directory structure and file name based on metadata template.
Note:
A template meant to be a base directory for suggested
names should have a trailing slash or backslash.
Parameters:
template (str or ~os.PathLike): A filepath which can include template patterns as defined by :param template_patterns:.
metadata (~collections.abc.Mapping): A metadata dict.
template_patterns (~collections.abc.Mapping): A dict of ``pattern: field`` pairs used to replace patterns with metadata field values.
Default: :const:`~google_music_utils.constants.TEMPLATE_PATTERNS`
Returns:
~pathlib.Path: A filepath.
"""
path = Path(template)
if template_patterns is None:
template_patterns = TEMPLATE_PATTERNS
suggested_filename = suggest_filename(metadata)
if (
path == Path.cwd()
or path == Path('%suggested%')
):
filepath = Path(suggested_filename)
elif any(
template_pattern in path.parts
for template_pattern in template_patterns
):
if template.endswith(('/', '\\')):
template += suggested_filename
path = Path(template.replace('%suggested%', suggested_filename))
parts = []
for part in path.parts:
if part == path.anchor:
parts.append(part)
else:
for key in template_patterns:
if ( # pragma: no branch
key in part
and any(
field in metadata
for field in template_patterns[key]
)
):
field = more_itertools.first_true(
template_patterns[key],
pred=lambda k: k in metadata
)
if key.startswith(('%disc', '%track')):
number = _split_number_field(
str(
list_to_single_value(
metadata[field]
)
)
)
if key.endswith('2%'):
metadata[field] = number.zfill(2)
else:
metadata[field] = number
part = part.replace(
key,
list_to_single_value(metadata[field])
)
parts.append(_replace_invalid_characters(part))
filepath = Path(*parts)
elif '%suggested%' in template:
filepath = Path(template.replace('%suggested%', suggested_filename))
elif template.endswith(('/', '\\')):
filepath = path / suggested_filename
else:
filepath = path
return filepath