diff --git a/README.md b/README.md index d72b742..56987da 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,6 @@ An audio tagger based on Discogs metadata. [![PyPI Version](https://img.shields.io/pypi/v/discogs-tag.svg)](https://pypi.org/project/discogs-tag/) -# Development -- Install [`poetry`](https://python-poetry.org/docs/#installation) -- `poetry install && poetry build && pip install .` - # Usage ```shell NAME @@ -46,6 +42,8 @@ DESCRIPTION The SKIP flag can take one or more of the following values, comma-separated: artist, composer, title, position, date, subtrack, album, genre, albumartist + If subtracks are skipped, subtrack titles get appended to their parent track. + POSITIONAL ARGUMENTS RELEASE @@ -75,6 +73,8 @@ DESCRIPTION The SKIP flag can take one or more of the following values, comma-separated: artist, composer, title, position, date, subtrack, album, genre, albumartist + If subtracks are skipped, subtrack titles get appended to their parent track. + POSITIONAL ARGUMENTS SRC @@ -128,3 +128,7 @@ FLAGS NOTES You can also use flags syntax for POSITIONAL ARGUMENTS ``` +# Development +- Install [`poetry`](https://python-poetry.org/docs/#installation) +- `poetry install && poetry build && pip install .` + diff --git a/poetry.lock b/poetry.lock index 762b380..0cf6847 100644 --- a/poetry.lock +++ b/poetry.lock @@ -13,13 +13,13 @@ files = [ [[package]] name = "exceptiongroup" -version = "1.2.1" +version = "1.2.2" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"}, - {file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"}, + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, ] [package.extras] diff --git a/pyproject.toml b/pyproject.toml index df9740c..f32a1f9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "discogs-tag" -version = "1.1.1" +version = "1.2.0" description = "An audio tagger based on Discogs metadata." authors = ["infojunkie "] repository = "https://github.com/infojunkie/discogs-tag.py" diff --git a/src/discogs_tag/cli.py b/src/discogs_tag/cli.py index 5687f47..707b76d 100644 --- a/src/discogs_tag/cli.py +++ b/src/discogs_tag/cli.py @@ -19,7 +19,7 @@ 'title', 'position', 'date', - 'subtrack', + 'subtracks', 'album', 'genre', 'albumartist' @@ -32,6 +32,10 @@ AUDIO_EXTENSIONS = ['flac', 'mp3'] +VARIOUS_ARTISTS = [ + 'Various Artists' +] + def tag( release, dir='./', @@ -47,7 +51,9 @@ def tag( - A local file URI pointing to a release JSON file The SKIP flag can take one or more of the following values, comma-separated: - artist, composer, title, position, date, subtrack, album, genre, albumartist + artist, composer, title, position, date, subtracks, album, genre, albumartist + + If subtracks are skipped, subtrack titles get appended to their parent track. """ options = parse_options(locals()) @@ -66,7 +72,9 @@ def copy( """Copy the audio tags from source to destination folders. The SKIP flag can take one or more of the following values, comma-separated: - artist, composer, title, position, date, subtrack, album, genre, albumartist + artist, composer, title, position, date, subtracks, album, genre, albumartist + + If subtracks are skipped, subtrack titles get appended to their parent track. """ options = parse_options(locals()) @@ -210,7 +218,9 @@ def get_tracks(tracklist): def reduce_track(tracks, track): if track['type_'] == 'track': tracks.append(track) - if not options['skip_subtrack'] and 'sub_tracks' in track: + elif options['skip_subtracks'] and 'sub_tracks' in track: + tracks.append(track) + if not options['skip_subtracks'] and 'sub_tracks' in track: tracks = tracks + get_tracks(track['sub_tracks']) return tracks return reduce(reduce_track, tracklist, []) @@ -225,7 +235,7 @@ def reduce_track(tracks, track): for n, track in enumerate(tracks): try: audio = mutagen.File(files[n], easy=True) - audio = merge_metadata(release, track, audio, options) + audio = apply_metadata_track(release, track, audio, n+1, options) if options['dry']: pprint(audio) else: @@ -332,11 +342,13 @@ def parse_options(options): for skip in SKIP_KEYS: options['skip_' + skip.lower()] = False if 'skip' in options and options['skip'] is not None: + if isinstance(options['skip'], str): + options['skip'] = [options['skip']] for skip in options['skip']: options['skip_' + skip.lower()] = True return options -def merge_metadata(release, track, audio, options): +def apply_metadata_track(release, track, audio, n, options): def artist_name(artist): name = None if 'anv' in artist and artist['anv']: @@ -346,7 +358,11 @@ def artist_name(artist): return re.sub(r"\s+\(\d+\)$", '', name) if name else None if not options['skip_title']: - audio['title'] = track['title'] + title = track['title'] + if options['skip_subtracks'] and 'sub_tracks' in track: + title += ': ' + ' / '.join([subtrack['title'] for subtrack in track['sub_tracks'] if subtrack['type_'] == 'track']) + if title: + audio['title'] = title if not options['skip_artist']: artists = [] @@ -377,20 +393,19 @@ def artist_name(artist): if 'title' in release: audio['album'] = release['title'] - if not options['skip_composer']: - composers = [artist_name(composer) for composer in filter(lambda a: a['role'].casefold() in [(lambda c: c.casefold())(c) for c in COMPOSER_TAGS], track['extraartists'])] if 'extraartists' in track else None + if not options['skip_composer'] and 'extraartists' in track: + composers = [artist_name(composer) for composer in track['extraartists'] if composer['role'].casefold() in [(lambda c: c.casefold())(c) for c in COMPOSER_TAGS]] if composers: audio['composer'] = ', '.join(composers) if not options['skip_position']: positions = track['position'].split('-') - audio['tracknumber'] = positions[-1] + audio['tracknumber'] = positions[-1] or str(n) if len(positions) > 1: audio['discnumber'] = positions[0] - if not options['skip_date']: - if 'year' in release and release['year']: - audio['date'] = str(release['year']) + if not options['skip_date'] and 'year' in release: + audio['date'] = str(release['year']) return audio diff --git a/tests/8582788.json b/tests/8582788.json new file mode 100644 index 0000000..93a16a8 --- /dev/null +++ b/tests/8582788.json @@ -0,0 +1,501 @@ +{ + "id": 8582788, + "status": "Accepted", + "year": 1993, + "resource_url": "https://api.discogs.com/releases/8582788", + "uri": "https://www.discogs.com/release/8582788-Vinicius-De-Moraes-Minha-Hist%C3%B3ria", + "artists": [ + { + "name": "Vinicius De Moraes", + "anv": "", + "join": "", + "role": "", + "tracks": "", + "id": 315789, + "resource_url": "https://api.discogs.com/artists/315789" + } + ], + "artists_sort": "Vinicius De Moraes", + "labels": [ + { + "name": "Philips", + "catno": "510457-2", + "entity_type": "1", + "entity_type_name": "Label", + "id": 7704, + "resource_url": "https://api.discogs.com/labels/7704" + }, + { + "name": "PolyGram", + "catno": "510457-2", + "entity_type": "1", + "entity_type_name": "Label", + "id": 8742, + "resource_url": "https://api.discogs.com/labels/8742" + } + ], + "series": [ + { + "name": "Minha História", + "catno": "", + "entity_type": "2", + "entity_type_name": "Series", + "id": 545144, + "resource_url": "https://api.discogs.com/labels/545144" + } + ], + "companies": [ + { + "name": "PolyGram do Brasil Ltda.", + "catno": "", + "entity_type": "6", + "entity_type_name": "Licensed From", + "id": 290833, + "resource_url": "https://api.discogs.com/labels/290833" + }, + { + "name": "Microservice - Microfilmagens e Reproduções Técnicas da Amazônia Ltda.", + "catno": "", + "entity_type": "10", + "entity_type_name": "Manufactured By", + "id": 360144, + "resource_url": "https://api.discogs.com/labels/360144" + }, + { + "name": "Fonobrás, Distribuidora Fonográfica Ltda.", + "catno": "", + "entity_type": "9", + "entity_type_name": "Distributed By", + "id": 491539, + "resource_url": "https://api.discogs.com/labels/491539" + } + ], + "formats": [ + { + "name": "CD", + "qty": "1", + "descriptions": [ + "Compilation", + "Limited Edition", + "Remastered" + ] + } + ], + "data_quality": "Needs Vote", + "community": { + "have": 78, + "want": 2, + "rating": { + "count": 5, + "average": 4.2 + }, + "submitter": { + "username": "Blinge", + "resource_url": "https://api.discogs.com/users/Blinge" + }, + "contributors": [ + { + "username": "Blinge", + "resource_url": "https://api.discogs.com/users/Blinge" + }, + { + "username": "molitorbr", + "resource_url": "https://api.discogs.com/users/molitorbr" + }, + { + "username": "lbamaral", + "resource_url": "https://api.discogs.com/users/lbamaral" + }, + { + "username": "_Ivo_", + "resource_url": "https://api.discogs.com/users/_Ivo_" + }, + { + "username": "Picaba49", + "resource_url": "https://api.discogs.com/users/Picaba49" + } + ], + "data_quality": "Needs Vote", + "status": "Accepted" + }, + "format_quantity": 1, + "date_added": "2016-05-29T02:05:38-07:00", + "date_changed": "2020-10-26T10:48:33-07:00", + "num_for_sale": 12, + "lowest_price": 3.15, + "master_id": 1533477, + "master_url": "https://api.discogs.com/masters/1533477", + "title": "Minha História", + "country": "Brazil", + "released": "1993", + "released_formatted": "1993", + "identifiers": [ + { + "type": "Barcode", + "value": "731451045721" + }, + { + "type": "SPARS Code", + "value": "AAD" + } + ], + "genres": [ + "Jazz", + "Latin" + ], + "styles": [ + "Bossa Nova", + "Samba", + "MPB" + ], + "tracklist": [ + { + "position": "", + "type_": "index", + "artists": [ + { + "name": "Toquinho & Vinicius", + "anv": "Toquinho/Vinícius De Moraes", + "join": "", + "role": "", + "tracks": "", + "id": 961600, + "resource_url": "https://api.discogs.com/artists/961600" + } + ], + "title": "Pot-Pourri No. 4", + "duration": "6:38", + "sub_tracks": [ + { + "position": "1.1", + "type_": "track", + "title": "Como Dizia O Poeta", + "duration": "" + }, + { + "position": "1.2", + "type_": "track", + "title": "Testamento", + "duration": "" + }, + { + "position": "1.3", + "type_": "track", + "title": "Para Viver Um Grande Amor", + "duration": "" + }, + { + "position": "1.4", + "type_": "track", + "title": "Morena Flor", + "duration": "" + }, + { + "position": "1.5", + "type_": "track", + "title": "Samba Da Volta", + "duration": "" + }, + { + "position": "1.6", + "type_": "track", + "title": "Regra Três", + "duration": "" + } + ] + }, + { + "position": "2", + "type_": "track", + "artists": [ + { + "name": "Elis Regina", + "anv": "", + "join": "", + "role": "", + "tracks": "", + "id": 30703, + "resource_url": "https://api.discogs.com/artists/30703" + } + ], + "title": "Canto De Ossanha", + "duration": "2:38" + }, + { + "position": "3", + "type_": "track", + "artists": [ + { + "name": "Vinicius De Moraes", + "anv": "", + "join": "", + "role": "", + "tracks": "", + "id": 315789, + "resource_url": "https://api.discogs.com/artists/315789" + } + ], + "title": "Pela Luz Dos Olhos Teus", + "duration": "1:38" + }, + { + "position": "4", + "type_": "track", + "artists": [ + { + "name": "Toquinho & Vinicius", + "anv": "Toquinho/Vinícius De Moraes", + "join": "", + "role": "", + "tracks": "", + "id": 961600, + "resource_url": "https://api.discogs.com/artists/961600" + } + ], + "title": "Às Cores De Abril", + "duration": "4:00" + }, + { + "position": "5", + "type_": "track", + "artists": [ + { + "name": "Caetano Veloso", + "anv": "", + "join": "", + "role": "", + "tracks": "", + "id": 83763, + "resource_url": "https://api.discogs.com/artists/83763" + } + ], + "title": "Eu Sei Que Vou Te Amar", + "duration": "3:52" + }, + { + "position": "", + "type_": "index", + "artists": [ + { + "name": "Toquinho & Vinicius", + "anv": "Toquinho/Vinícius De Moraes", + "join": "", + "role": "", + "tracks": "", + "id": 961600, + "resource_url": "https://api.discogs.com/artists/961600" + } + ], + "title": "Pot-Pourri No. 3", + "duration": "4:34", + "sub_tracks": [ + { + "position": "6.1", + "type_": "track", + "title": "O Velho E A Flor", + "duration": "" + }, + { + "position": "6.2", + "type_": "track", + "title": "Veja Você", + "duration": "" + }, + { + "position": "6.3", + "type_": "track", + "title": "Mais Um Adeus", + "duration": "" + } + ] + }, + { + "position": "7", + "type_": "track", + "artists": [ + { + "name": "Vinicius De Moraes", + "anv": "", + "join": ",", + "role": "", + "tracks": "", + "id": 315789, + "resource_url": "https://api.discogs.com/artists/315789" + }, + { + "name": "Quarteto Em Cy", + "anv": "", + "join": "", + "role": "", + "tracks": "", + "id": 156051, + "resource_url": "https://api.discogs.com/artists/156051" + } + ], + "title": "Minha Namorada", + "duration": "4:03" + }, + { + "position": "8", + "type_": "track", + "artists": [ + { + "name": "Toquinho & Vinicius", + "anv": "Vinícius De Moraes/Toquinho", + "join": "", + "role": "", + "tracks": "", + "id": 961600, + "resource_url": "https://api.discogs.com/artists/961600" + } + ], + "title": "Onde Anda Você", + "duration": "2:47" + }, + { + "position": "9", + "type_": "track", + "artists": [ + { + "name": "Agostinho Dos Santos", + "anv": "", + "join": "", + "role": "", + "tracks": "", + "id": 447685, + "resource_url": "https://api.discogs.com/artists/447685" + } + ], + "title": "Se Todos Fossem Iguais A Você", + "duration": "3:34" + }, + { + "position": "", + "type_": "index", + "artists": [ + { + "name": "Toquinho & Vinicius", + "anv": "Toquinho/Vinícius De Moraes", + "join": "", + "role": "", + "tracks": "", + "id": 961600, + "resource_url": "https://api.discogs.com/artists/961600" + } + ], + "title": "Pot-Pourri No. 5", + "duration": "3:42", + "sub_tracks": [ + { + "position": "10.1", + "type_": "track", + "title": "São Demais Os Perigos Desta Vida", + "duration": "" + }, + { + "position": "10.2", + "type_": "track", + "title": "Às Cores De Abril", + "duration": "" + }, + { + "position": "10.3", + "type_": "track", + "title": "O Filho Que Eu Quero Ter", + "duration": "" + } + ] + }, + { + "position": "11", + "type_": "track", + "artists": [ + { + "name": "Toquinho & Vinicius", + "anv": "Vinícius De Moraes/Toquinho", + "join": ",", + "role": "", + "tracks": "", + "id": 961600, + "resource_url": "https://api.discogs.com/artists/961600" + }, + { + "name": "Quarteto Em Cy", + "anv": "", + "join": "", + "role": "", + "tracks": "", + "id": 156051, + "resource_url": "https://api.discogs.com/artists/156051" + } + ], + "title": "Carta Ao Tom 74", + "duration": "2:38" + }, + { + "position": "12", + "type_": "track", + "artists": [ + { + "name": "Toquinho & Vinicius", + "anv": "Toquinho/Vinícius De Moraes", + "join": "", + "role": "", + "tracks": "", + "id": 961600, + "resource_url": "https://api.discogs.com/artists/961600" + } + ], + "title": "A Tonga Da Mironga Do Kabuletê", + "duration": "1:02" + }, + { + "position": "13", + "type_": "track", + "artists": [ + { + "name": "Toquinho & Vinicius", + "anv": "Vinícius De Moraes/Toquinho", + "join": "", + "role": "", + "tracks": "", + "id": 961600, + "resource_url": "https://api.discogs.com/artists/961600" + } + ], + "title": "Samba Da Bênção", + "duration": "6:55" + }, + { + "position": "14", + "type_": "track", + "artists": [ + { + "name": "Nara Leão", + "anv": "", + "join": "", + "role": "", + "tracks": "", + "id": 83772, + "resource_url": "https://api.discogs.com/artists/83772" + } + ], + "title": "Marcha Da Quarta-feira De Cinzas", + "duration": "2:58" + } + ], + "extraartists": [], + "images": [ + { + "type": "secondary", + "uri": "", + "resource_url": "", + "uri150": "", + "width": 296, + "height": 300 + } + ], + "thumb": "", + "estimated_weight": 85, + "blocked_from_sale": false +} \ No newline at end of file diff --git a/tests/test_discogs_tag.py b/tests/test_discogs_tag.py index 2bc6467..261cac8 100644 --- a/tests/test_discogs_tag.py +++ b/tests/test_discogs_tag.py @@ -1,7 +1,7 @@ from discogs_tag.cli import ( list_files, read_metadata, - merge_metadata, + apply_metadata_track, apply_metadata, parse_options, rename_component, @@ -39,6 +39,7 @@ def test_read_metadata(): assert release['artists'] == [{ 'anv': 'Album Artist' }] assert release['tracklist'][0]['position'] == '1-2' assert release['tracklist'][0]['title'] == 'Title' + release = read_metadata([{ 'artist': ['Artist'], 'albumartist': ['Album Artist'], @@ -50,6 +51,7 @@ def test_read_metadata(): 'date': ['2024'] }], {}) assert release['tracklist'][0]['position'] == '2' + release = read_metadata([{ 'artist': ['Artist'], 'albumartist': ['Album Artist'], @@ -62,8 +64,8 @@ def test_read_metadata(): }], {}) assert release['tracklist'][0]['position'] == '2' -def test_merge_metadata(): - audio = merge_metadata({ +def test_apply_metadata_track(): + audio = apply_metadata_track({ 'year': 2002, }, { 'title': 'Title', @@ -86,7 +88,7 @@ def test_merge_metadata(): 'role': 'Composed By', 'name': 'Composer 2' }] - }, { 'title': 'Some other title' }, parse_options({ 'skip': None })) + }, { 'title': 'Some other title' }, 9999, parse_options({ 'skip': None })) assert audio['title'] == 'Title' assert audio['artist'] == 'Artist 1, Artist 2, Artist 3' assert audio['discnumber'] == '1' @@ -94,7 +96,7 @@ def test_merge_metadata(): assert audio['composer'] == 'Composer 1, Composer 2' assert audio['date'] == '2002' - audio = merge_metadata({ + audio = apply_metadata_track({ 'year': 2002, 'artists': [{ 'anv': 'Artist 1' @@ -117,7 +119,7 @@ def test_merge_metadata(): 'role': 'Composed By', 'name': 'Composer 2' }] - }, { 'title': 'Some other title' }, parse_options({ 'skip': None })) + }, { 'title': 'Some other title' }, 9999, parse_options({ 'skip': None })) assert audio['title'] == 'Title' assert audio['artist'] == 'Artist 1, Artist 2, Artist 3' assert audio['discnumber'] == '1' @@ -125,6 +127,43 @@ def test_merge_metadata(): assert audio['composer'] == 'Composer 1, Composer 2' assert audio['date'] == '2002' + audio = apply_metadata_track({ + 'year': 2002, + 'artists': [{ + 'anv': 'Artist 1' + }, { + 'name': 'Artist 2' + }, { + 'anv': '', + 'name': 'Artist 3 (56)' + }], + }, { + 'title': 'Title', + 'position': '', + 'sub_tracks': [{ + 'type_': 'track', + "title": "Sub track 1", + }, { + 'type_': 'track', + 'title': 'Sub track 2', + }, { + 'type_': 'data', + 'title': "Not a track", + }], + 'extraartists': [{ + 'role': 'Guitar', + 'name': 'Guitarist' + }, { + 'role': 'Written-By', + 'name': 'Composer 1' + }, { + 'role': 'Composed By', + 'name': 'Composer 2' + }] + }, { 'title': 'Some other title' }, 2, parse_options({ 'skip': ['subtracks'] })) + assert audio['title'] == 'Title: Sub track 1 / Sub track 2' + assert audio['tracknumber'] == '2' + def test_apply_metadata(): with open('tests/18051880.json') as release: data = json.load(release) @@ -138,11 +177,19 @@ def test_count_subtracks(): with open('tests/21343819.json') as release: data = json.load(release) - # Test that files must match API results. + # Test that files must match API results with subtracks. with pytest.raises(Exception) as error: apply_metadata(data, [], parse_options({ 'dry': False, 'ignore': False, 'skip': None })) assert "Expecting 18 files" in str(error.value) + with open('tests/8582788.json') as release: + data = json.load(release) + + # Test that files must match API results without subtracks. + with pytest.raises(Exception) as error: + apply_metadata(data, [], parse_options({ 'dry': False, 'ignore': False, 'skip': 'subtracks' })) + assert "Expecting 14 files" in str(error.value) + def test_rename_component(): assert rename_component({ 'artist': ['Artist'], @@ -155,6 +202,7 @@ def test_rename_component(): 'title': ['Title'], 'date': ['2024'] }, '%d-%n %t', parse_options({ 'dry': True, 'ignore': False })) == '1-02 Title' + assert rename_component({ 'artist': ['Artist'], 'albumartist': ['Album Artist'], @@ -165,6 +213,7 @@ def test_rename_component(): 'title': ['Title'], 'date': ['2024'] }, '%d-%n %t', parse_options({ 'dry': True, 'ignore': False })) == '02 Title' + with pytest.raises(IndexError) as error: rename_component({ 'artist': ['Artist'], @@ -191,6 +240,7 @@ def test_rename_path(): 'title': ['Title'], 'date': ['2024'] }, '%z - (%y) %b/%d-%n %t', parse_options({ 'dry': True, 'ignore': False })) == ('/src/path/Album Artist - (2024) Album', '/src/path/Album Artist - (2024) Album') + assert rename_path('/src/path/from', { 'artist': ['Artist'], 'albumartist': ['Album Artist'], @@ -202,6 +252,7 @@ def test_rename_path(): 'title': ['Title'], 'date': ['2024'] }, '%z - (%y) %b/%d-%n %t', parse_options({ 'dry': True, 'ignore': False })) == ('/src/path/Album Artist - (2024) Album1 - Album2', '/src/path/Album Artist - (2024) Album1 - Album2') + assert rename_path('/src/path/from', { 'artist': ['Artist'], 'albumartist': ['Album Artist'], @@ -212,6 +263,7 @@ def test_rename_path(): 'tracknumber': [2], 'title': ['Title'] }, '%z - (%y) %b/%d-%n %t', parse_options({ 'dry': True, 'ignore': False })) == ('/src/path/Album Artist - Album1 - Album2', '/src/path/Album Artist - Album1 - Album2') + assert rename_path('/src/path/from', { 'artist': ['Artist'], 'albumartist': ['Album Artist'], @@ -223,6 +275,7 @@ def test_rename_path(): 'title': ['Title'], 'date': ['2024'] }, '%d-%n %t', parse_options({ 'dry': True, 'ignore': False })) == ('/src/path/from', '/src/path/from') + assert rename_path('/src/path/from', { 'artist': ['Artist'], 'albumartist': ['Album Artist'], @@ -247,6 +300,7 @@ def test_rename_file(): 'title': ['Title'], 'date': ['2024'] }, '%z - (%y) %b/%d-%n %t', parse_options({ 'dry': True, 'ignore': False })) == '/dest/path/to/1-02 Title.flac' + assert rename_file('/src/path/from/test.flac', '/dest/path/to', { 'artist': ['Artist1 / Artist2'], 'albumartist': ['Album Artist'], @@ -258,6 +312,7 @@ def test_rename_file(): 'title': ['Title1 / Title2'], 'date': ['2024'] }, '%z - (%y) %b/%d-%n %a - %t', parse_options({ 'dry': True, 'ignore': False })) == '/dest/path/to/1-02 Artist1 - Artist2 - Title1 - Title2.flac' + assert rename_file('/src/path/from/test.flac', '/dest/path/to', { 'artist': ['Artist'], 'albumartist': ['Album Artist'],