audiometa-python 0.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- audiometa/__init__.py +1297 -0
- audiometa/__main__.py +6 -0
- audiometa/_audio_file.py +607 -0
- audiometa/cli.py +476 -0
- audiometa/exceptions.py +167 -0
- audiometa/manager/_MetadataManager.py +768 -0
- audiometa/manager/__init__.py +1 -0
- audiometa/manager/_rating_supporting/_RatingSupportingMetadataManager.py +250 -0
- audiometa/manager/_rating_supporting/__init__.py +1 -0
- audiometa/manager/_rating_supporting/id3v2/_Id3v2Manager.py +1032 -0
- audiometa/manager/_rating_supporting/id3v2/__init__.py +25 -0
- audiometa/manager/_rating_supporting/id3v2/_id3v2_constants.py +11 -0
- audiometa/manager/_rating_supporting/riff/_RiffManager.py +1002 -0
- audiometa/manager/_rating_supporting/riff/__init__.py +25 -0
- audiometa/manager/_rating_supporting/riff/_riff_constants.py +17 -0
- audiometa/manager/_rating_supporting/vorbis/_VorbisManager.py +542 -0
- audiometa/manager/_rating_supporting/vorbis/__init__.py +17 -0
- audiometa/manager/_rating_supporting/vorbis/_vorbis_constants.py +6 -0
- audiometa/manager/id3v1/_Id3v1Manager.py +512 -0
- audiometa/manager/id3v1/__init__.py +1 -0
- audiometa/manager/id3v1/_constants.py +8 -0
- audiometa/manager/id3v1/id3v1_raw_metadata.py +242 -0
- audiometa/manager/id3v1/id3v1_raw_metadata_key.py +13 -0
- audiometa/test/__init__.py +1 -0
- audiometa/test/assets/create_test_files.py +72 -0
- audiometa/test/helpers/__init__.py +51 -0
- audiometa/test/helpers/common/__init__.py +6 -0
- audiometa/test/helpers/common/audio_file_creator.py +68 -0
- audiometa/test/helpers/common/external_tool_runner.py +74 -0
- audiometa/test/helpers/id3v1/__init__.py +8 -0
- audiometa/test/helpers/id3v1/id3v1_header_verifier.py +18 -0
- audiometa/test/helpers/id3v1/id3v1_metadata_deleter.py +37 -0
- audiometa/test/helpers/id3v1/id3v1_metadata_getter.py +61 -0
- audiometa/test/helpers/id3v1/id3v1_metadata_setter.py +82 -0
- audiometa/test/helpers/id3v2/__init__.py +28 -0
- audiometa/test/helpers/id3v2/id3v2_frame_manual_creator.py +349 -0
- audiometa/test/helpers/id3v2/id3v2_header_verifier.py +38 -0
- audiometa/test/helpers/id3v2/id3v2_metadata_deleter.py +56 -0
- audiometa/test/helpers/id3v2/id3v2_metadata_getter.py +189 -0
- audiometa/test/helpers/id3v2/id3v2_metadata_setter.py +506 -0
- audiometa/test/helpers/riff/__init__.py +8 -0
- audiometa/test/helpers/riff/riff_header_verifier.py +85 -0
- audiometa/test/helpers/riff/riff_manual_metadata_creator.py +298 -0
- audiometa/test/helpers/riff/riff_metadata_deleter.py +56 -0
- audiometa/test/helpers/riff/riff_metadata_getter.py +219 -0
- audiometa/test/helpers/riff/riff_metadata_setter.py +374 -0
- audiometa/test/helpers/scripts/__init__.py +0 -0
- audiometa/test/helpers/technical_info_inspector.py +115 -0
- audiometa/test/helpers/temp_file_with_metadata.py +82 -0
- audiometa/test/helpers/vorbis/__init__.py +8 -0
- audiometa/test/helpers/vorbis/vorbis_header_verifier.py +31 -0
- audiometa/test/helpers/vorbis/vorbis_metadata_deleter.py +49 -0
- audiometa/test/helpers/vorbis/vorbis_metadata_getter.py +67 -0
- audiometa/test/helpers/vorbis/vorbis_metadata_setter.py +221 -0
- audiometa/test/tests/__init__.py +0 -0
- audiometa/test/tests/conftest.py +276 -0
- audiometa/test/tests/e2e/__init__.py +0 -0
- audiometa/test/tests/e2e/cli/__init__.py +0 -0
- audiometa/test/tests/e2e/cli/error_handling/__init__.py +1 -0
- audiometa/test/tests/e2e/cli/error_handling/test_command_structure_errors.py +77 -0
- audiometa/test/tests/e2e/cli/error_handling/test_file_access_errors.py +130 -0
- audiometa/test/tests/e2e/cli/error_handling/test_format_output_errors.py +118 -0
- audiometa/test/tests/e2e/cli/error_handling/test_input_validation_errors.py +172 -0
- audiometa/test/tests/e2e/cli/error_handling/test_missing_fields_validation.py +49 -0
- audiometa/test/tests/e2e/cli/error_handling/test_multiple_files_errors.py +160 -0
- audiometa/test/tests/e2e/cli/error_handling/test_rating_validation.py +90 -0
- audiometa/test/tests/e2e/cli/error_handling/test_year_validation.py +51 -0
- audiometa/test/tests/e2e/cli/read/__init__.py +0 -0
- audiometa/test/tests/e2e/cli/read/test_basic.py +58 -0
- audiometa/test/tests/e2e/cli/read/test_comprehensive.py +240 -0
- audiometa/test/tests/e2e/cli/read/test_formats.py +55 -0
- audiometa/test/tests/e2e/cli/read/test_metadata_content.py +164 -0
- audiometa/test/tests/e2e/cli/read/test_multiple_files.py +149 -0
- audiometa/test/tests/e2e/cli/read/test_options.py +88 -0
- audiometa/test/tests/e2e/cli/read/test_unified.py +84 -0
- audiometa/test/tests/e2e/cli/test_delete.py +20 -0
- audiometa/test/tests/e2e/cli/test_formatting.py +31 -0
- audiometa/test/tests/e2e/cli/test_help.py +41 -0
- audiometa/test/tests/e2e/cli/write/__init__.py +0 -0
- audiometa/test/tests/e2e/cli/write/test_basic.py +51 -0
- audiometa/test/tests/e2e/cli/write/test_comprehensive.py +210 -0
- audiometa/test/tests/e2e/cli/write/test_force_format.py +336 -0
- audiometa/test/tests/e2e/cli/write/test_integer_fields.py +145 -0
- audiometa/test/tests/e2e/cli/write/test_list_fields.py +107 -0
- audiometa/test/tests/e2e/cli/write/test_rating.py +74 -0
- audiometa/test/tests/e2e/cli/write/test_string_fields.py +54 -0
- audiometa/test/tests/e2e/cli/write/test_validation.py +85 -0
- audiometa/test/tests/e2e/scenarios/__init__.py +0 -0
- audiometa/test/tests/e2e/scenarios/test_user_scenarios.py +166 -0
- audiometa/test/tests/e2e/workflows/__init__.py +0 -0
- audiometa/test/tests/e2e/workflows/test_core_workflows.py +166 -0
- audiometa/test/tests/e2e/workflows/test_deletion_workflows.py +318 -0
- audiometa/test/tests/e2e/workflows/test_error_handling_workflows.py +165 -0
- audiometa/test/tests/e2e/workflows/test_format_specific_workflows.py +129 -0
- audiometa/test/tests/e2e/workflows/test_rating_workflows.py +124 -0
- audiometa/test/tests/integration/__init__.py +0 -0
- audiometa/test/tests/integration/audio_format/__init__.py +0 -0
- audiometa/test/tests/integration/audio_format/flac/__init__.py +0 -0
- audiometa/test/tests/integration/audio_format/flac/test_flac_delete_all.py +108 -0
- audiometa/test/tests/integration/audio_format/flac/test_flac_reading_all.py +61 -0
- audiometa/test/tests/integration/audio_format/flac/test_flac_reading_field.py +65 -0
- audiometa/test/tests/integration/audio_format/flac/test_flac_writing.py +69 -0
- audiometa/test/tests/integration/audio_format/mp3/__init__.py +0 -0
- audiometa/test/tests/integration/audio_format/mp3/test_mp3_delete_all.py +79 -0
- audiometa/test/tests/integration/audio_format/mp3/test_mp3_reading_all.py +61 -0
- audiometa/test/tests/integration/audio_format/mp3/test_mp3_reading_field.py +67 -0
- audiometa/test/tests/integration/audio_format/mp3/test_mp3_writing.py +60 -0
- audiometa/test/tests/integration/audio_format/wav/__init__.py +0 -0
- audiometa/test/tests/integration/audio_format/wav/test_wav_delete_all.py +87 -0
- audiometa/test/tests/integration/audio_format/wav/test_wav_reading_all.py +62 -0
- audiometa/test/tests/integration/audio_format/wav/test_wav_reading_field.py +57 -0
- audiometa/test/tests/integration/audio_format/wav/test_wav_with_id3v2_tags.py +83 -0
- audiometa/test/tests/integration/audio_format/wav/test_wav_writing.py +62 -0
- audiometa/test/tests/integration/conftest.py +29 -0
- audiometa/test/tests/integration/delete_all_metadata/__init__.py +1 -0
- audiometa/test/tests/integration/delete_all_metadata/test_audio_format_all.py +102 -0
- audiometa/test/tests/integration/delete_all_metadata/test_audio_format_header_deletion.py +77 -0
- audiometa/test/tests/integration/delete_all_metadata/test_basic_functionality.py +47 -0
- audiometa/test/tests/integration/delete_all_metadata/test_error_handling.py +24 -0
- audiometa/test/tests/integration/encoding/__init__.py +1 -0
- audiometa/test/tests/integration/encoding/test_encoding.py +88 -0
- audiometa/test/tests/integration/encoding/test_special_characters_edge_cases.py +223 -0
- audiometa/test/tests/integration/get_full_metadata/__init__.py +0 -0
- audiometa/test/tests/integration/get_full_metadata/test_audio_formats.py +122 -0
- audiometa/test/tests/integration/get_full_metadata/test_binary_data_filtering.py +250 -0
- audiometa/test/tests/integration/get_full_metadata/test_consistency.py +67 -0
- audiometa/test/tests/integration/get_full_metadata/test_edge_cases.py +123 -0
- audiometa/test/tests/integration/get_full_metadata/test_error_handling.py +40 -0
- audiometa/test/tests/integration/get_full_metadata/test_get_full_metadata.py +43 -0
- audiometa/test/tests/integration/get_full_metadata/test_options.py +207 -0
- audiometa/test/tests/integration/get_full_metadata/test_performance.py +95 -0
- audiometa/test/tests/integration/get_full_metadata/test_riff_bext.py +128 -0
- audiometa/test/tests/integration/get_full_metadata/test_structure.py +161 -0
- audiometa/test/tests/integration/metadata_field/__init__.py +0 -0
- audiometa/test/tests/integration/metadata_field/album/__init__.py +0 -0
- audiometa/test/tests/integration/metadata_field/album/test_deleting.py +73 -0
- audiometa/test/tests/integration/metadata_field/album/test_reading.py +36 -0
- audiometa/test/tests/integration/metadata_field/album/test_writing.py +50 -0
- audiometa/test/tests/integration/metadata_field/album_artists/__init__.py +0 -0
- audiometa/test/tests/integration/metadata_field/album_artists/test_deleting.py +83 -0
- audiometa/test/tests/integration/metadata_field/album_artists/test_reading.py +38 -0
- audiometa/test/tests/integration/metadata_field/album_artists/test_writing.py +52 -0
- audiometa/test/tests/integration/metadata_field/artists/__init__.py +0 -0
- audiometa/test/tests/integration/metadata_field/artists/test_deleting.py +68 -0
- audiometa/test/tests/integration/metadata_field/artists/test_reading.py +36 -0
- audiometa/test/tests/integration/metadata_field/artists/test_writing.py +46 -0
- audiometa/test/tests/integration/metadata_field/bpm/__init__.py +0 -0
- audiometa/test/tests/integration/metadata_field/bpm/test_deleting.py +75 -0
- audiometa/test/tests/integration/metadata_field/bpm/test_reading.py +32 -0
- audiometa/test/tests/integration/metadata_field/bpm/test_writing.py +56 -0
- audiometa/test/tests/integration/metadata_field/comment/__init__.py +0 -0
- audiometa/test/tests/integration/metadata_field/comment/test_deleting.py +68 -0
- audiometa/test/tests/integration/metadata_field/comment/test_reading.py +36 -0
- audiometa/test/tests/integration/metadata_field/comment/test_writing.py +49 -0
- audiometa/test/tests/integration/metadata_field/composer/__init__.py +0 -0
- audiometa/test/tests/integration/metadata_field/composer/test_deleting.py +75 -0
- audiometa/test/tests/integration/metadata_field/composer/test_reading.py +34 -0
- audiometa/test/tests/integration/metadata_field/composer/test_writing.py +41 -0
- audiometa/test/tests/integration/metadata_field/copyright/__init__.py +0 -0
- audiometa/test/tests/integration/metadata_field/copyright/test_deleting.py +81 -0
- audiometa/test/tests/integration/metadata_field/copyright/test_reading.py +35 -0
- audiometa/test/tests/integration/metadata_field/copyright/test_writing.py +41 -0
- audiometa/test/tests/integration/metadata_field/disc_number/__init__.py +0 -0
- audiometa/test/tests/integration/metadata_field/disc_number/test_deleting.py +97 -0
- audiometa/test/tests/integration/metadata_field/disc_number/test_reading.py +92 -0
- audiometa/test/tests/integration/metadata_field/disc_number/test_writing.py +153 -0
- audiometa/test/tests/integration/metadata_field/field_not_supported/__init__.py +0 -0
- audiometa/test/tests/integration/metadata_field/field_not_supported/test_deleting.py +56 -0
- audiometa/test/tests/integration/metadata_field/field_not_supported/test_reading.py +54 -0
- audiometa/test/tests/integration/metadata_field/field_not_supported/test_writing.py +61 -0
- audiometa/test/tests/integration/metadata_field/genre/__init__.py +0 -0
- audiometa/test/tests/integration/metadata_field/genre/reading/__init__.py +0 -0
- audiometa/test/tests/integration/metadata_field/genre/reading/metadata_format/__init__.py +0 -0
- audiometa/test/tests/integration/metadata_field/genre/reading/metadata_format/test_id3v1_reading.py +65 -0
- audiometa/test/tests/integration/metadata_field/genre/reading/metadata_format/test_id3v2_reading.py +25 -0
- audiometa/test/tests/integration/metadata_field/genre/reading/metadata_format/test_riff_reading.py +58 -0
- audiometa/test/tests/integration/metadata_field/genre/reading/metadata_format/test_vorbis_reading.py +61 -0
- audiometa/test/tests/integration/metadata_field/genre/reading/test_smart_reading.py +191 -0
- audiometa/test/tests/integration/metadata_field/genre/test_deleting.py +62 -0
- audiometa/test/tests/integration/metadata_field/genre/test_writing.py +64 -0
- audiometa/test/tests/integration/metadata_field/isrc/__init__.py +1 -0
- audiometa/test/tests/integration/metadata_field/isrc/test_deleting.py +31 -0
- audiometa/test/tests/integration/metadata_field/isrc/test_reading.py +35 -0
- audiometa/test/tests/integration/metadata_field/isrc/test_writing.py +165 -0
- audiometa/test/tests/integration/metadata_field/language/__init__.py +0 -0
- audiometa/test/tests/integration/metadata_field/language/test_deleting.py +75 -0
- audiometa/test/tests/integration/metadata_field/language/test_reading.py +39 -0
- audiometa/test/tests/integration/metadata_field/language/test_writing.py +43 -0
- audiometa/test/tests/integration/metadata_field/lyrics/__init__.py +0 -0
- audiometa/test/tests/integration/metadata_field/lyrics/test_deleting.py +129 -0
- audiometa/test/tests/integration/metadata_field/lyrics/test_reading.py +57 -0
- audiometa/test/tests/integration/metadata_field/lyrics/test_writing.py +59 -0
- audiometa/test/tests/integration/metadata_field/publisher/__init__.py +0 -0
- audiometa/test/tests/integration/metadata_field/publisher/test_deleting.py +88 -0
- audiometa/test/tests/integration/metadata_field/publisher/test_reading.py +32 -0
- audiometa/test/tests/integration/metadata_field/publisher/test_writing.py +47 -0
- audiometa/test/tests/integration/metadata_field/rating/__init__.py +0 -0
- audiometa/test/tests/integration/metadata_field/rating/reading/__init__.py +0 -0
- audiometa/test/tests/integration/metadata_field/rating/reading/test_base_100_proportional.py +81 -0
- audiometa/test/tests/integration/metadata_field/rating/reading/test_base_255_non_proportional.py +33 -0
- audiometa/test/tests/integration/metadata_field/rating/reading/test_base_255_proportional.py +58 -0
- audiometa/test/tests/integration/metadata_field/rating/test_deleting.py +117 -0
- audiometa/test/tests/integration/metadata_field/rating/test_error_handling.py +137 -0
- audiometa/test/tests/integration/metadata_field/rating/writing/__init__.py +0 -0
- audiometa/test/tests/integration/metadata_field/rating/writing/metadata_format/__init__.py +0 -0
- audiometa/test/tests/integration/metadata_field/rating/writing/metadata_format/test_id3v2.py +77 -0
- audiometa/test/tests/integration/metadata_field/rating/writing/metadata_format/test_riff.py +55 -0
- audiometa/test/tests/integration/metadata_field/rating/writing/metadata_format/test_vorbis.py +57 -0
- audiometa/test/tests/integration/metadata_field/rating/writing/test_comprehensive.py +192 -0
- audiometa/test/tests/integration/metadata_field/release_date/__init__.py +0 -0
- audiometa/test/tests/integration/metadata_field/release_date/test_deleting.py +74 -0
- audiometa/test/tests/integration/metadata_field/release_date/test_error_handling.py +82 -0
- audiometa/test/tests/integration/metadata_field/release_date/test_reading.py +59 -0
- audiometa/test/tests/integration/metadata_field/release_date/test_writing.py +49 -0
- audiometa/test/tests/integration/metadata_field/test_metadata_field_validation.py +135 -0
- audiometa/test/tests/integration/metadata_field/title/__init__.py +0 -0
- audiometa/test/tests/integration/metadata_field/title/test_deleting.py +73 -0
- audiometa/test/tests/integration/metadata_field/title/test_error_handling.py +47 -0
- audiometa/test/tests/integration/metadata_field/title/test_reading.py +36 -0
- audiometa/test/tests/integration/metadata_field/title/test_writing.py +64 -0
- audiometa/test/tests/integration/metadata_field/track_number/__init__.py +0 -0
- audiometa/test/tests/integration/metadata_field/track_number/reading/__init__.py +0 -0
- audiometa/test/tests/integration/metadata_field/track_number/reading/test_edge_cases.py +43 -0
- audiometa/test/tests/integration/metadata_field/track_number/reading/test_metadata_format.py +32 -0
- audiometa/test/tests/integration/metadata_field/track_number/test_deleting.py +59 -0
- audiometa/test/tests/integration/metadata_field/track_number/test_writing.py +73 -0
- audiometa/test/tests/integration/multiple_values/__init__.py +1 -0
- audiometa/test/tests/integration/multiple_values/reading/__init__.py +1 -0
- audiometa/test/tests/integration/multiple_values/reading/metadata_format/__init__.py +1 -0
- audiometa/test/tests/integration/multiple_values/reading/metadata_format/test_id3v1.py +23 -0
- audiometa/test/tests/integration/multiple_values/reading/metadata_format/test_id3v2_3.py +92 -0
- audiometa/test/tests/integration/multiple_values/reading/metadata_format/test_id3v2_4.py +216 -0
- audiometa/test/tests/integration/multiple_values/reading/metadata_format/test_riff.py +84 -0
- audiometa/test/tests/integration/multiple_values/reading/metadata_format/test_vorbis.py +169 -0
- audiometa/test/tests/integration/multiple_values/reading/test_performance_large_data.py +209 -0
- audiometa/test/tests/integration/multiple_values/reading/test_smart_parsing_scenarios.py +198 -0
- audiometa/test/tests/integration/multiple_values/reading/test_unicode_handling.py +24 -0
- audiometa/test/tests/integration/multiple_values/writing/__init__.py +1 -0
- audiometa/test/tests/integration/multiple_values/writing/metadata_format/__init__.py +1 -0
- audiometa/test/tests/integration/multiple_values/writing/metadata_format/test_id3v1.py +62 -0
- audiometa/test/tests/integration/multiple_values/writing/metadata_format/test_id3v2_3.py +36 -0
- audiometa/test/tests/integration/multiple_values/writing/metadata_format/test_id3v2_4.py +34 -0
- audiometa/test/tests/integration/multiple_values/writing/metadata_format/test_riff.py +32 -0
- audiometa/test/tests/integration/multiple_values/writing/metadata_format/test_vorbis.py +54 -0
- audiometa/test/tests/integration/multiple_values/writing/test_error_handling.py +42 -0
- audiometa/test/tests/integration/multiple_values/writing/test_large_values.py +98 -0
- audiometa/test/tests/integration/reading/__init__.py +1 -0
- audiometa/test/tests/integration/reading/test_read_multiple_metadata.py +80 -0
- audiometa/test/tests/integration/reading/test_reading_error_handling.py +36 -0
- audiometa/test/tests/integration/real_audio_files/__init__.py +0 -0
- audiometa/test/tests/integration/real_audio_files/test_reading.py +146 -0
- audiometa/test/tests/integration/real_audio_files/test_writing.py +198 -0
- audiometa/test/tests/integration/technical_info/__init__.py +0 -0
- audiometa/test/tests/integration/technical_info/flac_md5/__init__.py +0 -0
- audiometa/test/tests/integration/technical_info/flac_md5/conftest.py +103 -0
- audiometa/test/tests/integration/technical_info/flac_md5/test_invalid_md5/__init__.py +0 -0
- audiometa/test/tests/integration/technical_info/flac_md5/test_invalid_md5/test_audio_data_corruption.py +21 -0
- audiometa/test/tests/integration/technical_info/flac_md5/test_invalid_md5/test_flipped_md5.py +29 -0
- audiometa/test/tests/integration/technical_info/flac_md5/test_invalid_md5/test_non_flac_error.py +13 -0
- audiometa/test/tests/integration/technical_info/flac_md5/test_invalid_md5/test_partial_md5.py +29 -0
- audiometa/test/tests/integration/technical_info/flac_md5/test_invalid_md5/test_random_md5.py +29 -0
- audiometa/test/tests/integration/technical_info/flac_md5/test_invalid_md5/test_unset_md5.py +56 -0
- audiometa/test/tests/integration/technical_info/flac_md5/test_valid_md5.py +21 -0
- audiometa/test/tests/integration/technical_info/test_bitrate.py +79 -0
- audiometa/test/tests/integration/technical_info/test_channels.py +38 -0
- audiometa/test/tests/integration/technical_info/test_duration_in_sec.py +38 -0
- audiometa/test/tests/integration/technical_info/test_sample_rate.py +40 -0
- audiometa/test/tests/integration/test_audio_file.py +35 -0
- audiometa/test/tests/integration/test_audio_format_readable_after_update_all_metadata_formats.py +95 -0
- audiometa/test/tests/integration/writing/__init__.py +0 -0
- audiometa/test/tests/integration/writing/test_error_handling.py +44 -0
- audiometa/test/tests/integration/writing/test_forced_format.py +224 -0
- audiometa/test/tests/integration/writing/test_multiple_format_preservation.py +223 -0
- audiometa/test/tests/integration/writing/test_partial_update.py +36 -0
- audiometa/test/tests/integration/writing/writing_strategies/__init__.py +0 -0
- audiometa/test/tests/integration/writing/writing_strategies/test_cleanup_strategy.py +79 -0
- audiometa/test/tests/integration/writing/writing_strategies/test_preserve_strategy.py +76 -0
- audiometa/test/tests/integration/writing/writing_strategies/test_sync_strategy.py +215 -0
- audiometa/test/tests/integration/writing/writing_strategies/unsupported_fields/__init__.py +0 -0
- audiometa/test/tests/integration/writing/writing_strategies/unsupported_fields/test_fail_behavior.py +42 -0
- audiometa/test/tests/integration/writing/writing_strategies/unsupported_fields/test_no_writing_on_failure.py +93 -0
- audiometa/test/tests/integration/writing/writing_strategies/unsupported_fields/test_strategy_specific.py +99 -0
- audiometa/test/tests/unit/__init__.py +0 -0
- audiometa/test/tests/unit/audio_file/__init__.py +0 -0
- audiometa/test/tests/unit/audio_file/technical_info/__init__.py +0 -0
- audiometa/test/tests/unit/audio_file/technical_info/test_bitrate.py +26 -0
- audiometa/test/tests/unit/audio_file/technical_info/test_channels.py +31 -0
- audiometa/test/tests/unit/audio_file/technical_info/test_duration_in_sec.py +38 -0
- audiometa/test/tests/unit/audio_file/technical_info/test_error_handling.py +190 -0
- audiometa/test/tests/unit/audio_file/technical_info/test_file_size.py +51 -0
- audiometa/test/tests/unit/audio_file/technical_info/test_format_name.py +28 -0
- audiometa/test/tests/unit/audio_file/technical_info/test_sample_rate.py +31 -0
- audiometa/test/tests/unit/audio_file/test_context_manager.py +30 -0
- audiometa/test/tests/unit/audio_file/test_file_validation.py +40 -0
- audiometa/test/tests/unit/audio_file/test_is_audio_file.py +49 -0
- audiometa/test/tests/unit/audio_file/test_operations.py +20 -0
- audiometa/test/tests/unit/audio_file/test_path_handling.py +23 -0
- audiometa/test/tests/unit/cli/__init__.py +0 -0
- audiometa/test/tests/unit/cli/test_expand_file_patterns.py +234 -0
- audiometa/test/tests/unit/metadata_managers/__init__.py +0 -0
- audiometa/test/tests/unit/metadata_managers/conftest.py +142 -0
- audiometa/test/tests/unit/metadata_managers/header_info/__init__.py +0 -0
- audiometa/test/tests/unit/metadata_managers/header_info/test_id3v1.py +49 -0
- audiometa/test/tests/unit/metadata_managers/header_info/test_id3v2.py +66 -0
- audiometa/test/tests/unit/metadata_managers/header_info/test_riff.py +343 -0
- audiometa/test/tests/unit/metadata_managers/header_info/test_vorbis.py +53 -0
- audiometa/test/tests/unit/metadata_managers/metadata_field/__init__.py +0 -0
- audiometa/test/tests/unit/metadata_managers/metadata_field/multiple_values/__init__.py +0 -0
- audiometa/test/tests/unit/metadata_managers/metadata_field/multiple_values/reading/__init__.py +0 -0
- audiometa/test/tests/unit/metadata_managers/metadata_field/multiple_values/reading/test_smart_parsing.py +186 -0
- audiometa/test/tests/unit/metadata_managers/metadata_field/multiple_values/writing/__init__.py +0 -0
- audiometa/test/tests/unit/metadata_managers/metadata_field/multiple_values/writing/test_separator_selection.py +142 -0
- audiometa/test/tests/unit/metadata_managers/metadata_field/multiple_values/writing/test_value_filtering.py +76 -0
- audiometa/test/tests/unit/metadata_managers/metadata_field/rating/__init__.py +0 -0
- audiometa/test/tests/unit/metadata_managers/metadata_field/rating/reading/__init__.py +0 -0
- audiometa/test/tests/unit/metadata_managers/metadata_field/rating/reading/test_normalization.py +152 -0
- audiometa/test/tests/unit/metadata_managers/metadata_field/rating/reading/test_profiles_values.py +23 -0
- audiometa/test/tests/unit/metadata_managers/metadata_field/rating/test_rating_validation.py +77 -0
- audiometa/test/tests/unit/metadata_managers/metadata_field/rating/writing/__init__.py +0 -0
- audiometa/test/tests/unit/metadata_managers/metadata_field/rating/writing/test_configuration_error.py +43 -0
- audiometa/test/tests/unit/metadata_managers/metadata_field/rating/writing/test_validation.py +151 -0
- audiometa/test/tests/unit/metadata_managers/metadata_field/rating/writing/test_writing_profiles.py +61 -0
- audiometa/test/tests/unit/metadata_managers/metadata_field/test_date_format_validation.py +135 -0
- audiometa/test/tests/unit/metadata_managers/metadata_field/test_disc_number_validation.py +75 -0
- audiometa/test/tests/unit/metadata_managers/metadata_field/test_isrc_format_validation.py +121 -0
- audiometa/test/tests/unit/metadata_managers/metadata_field/test_isrc_type_validation.py +30 -0
- audiometa/test/tests/unit/metadata_managers/metadata_field/test_track_number_validation.py +46 -0
- audiometa/test/tests/unit/metadata_managers/metadata_field/test_type_validation_exception.py +22 -0
- audiometa/test/tests/unit/metadata_managers/metadata_field/test_validation.py +83 -0
- audiometa/test/tests/unit/metadata_managers/test_metadata_format_managers_write_and_read.py +74 -0
- audiometa/test/tests/unit/metadata_managers/test_riff_configuration_error.py +26 -0
- audiometa/utils/__init__.py +1 -0
- audiometa/utils/id3v1_genre_code_map.py +205 -0
- audiometa/utils/metadata_format.py +31 -0
- audiometa/utils/metadata_writing_strategy.py +16 -0
- audiometa/utils/mutagen_exception_handler.py +24 -0
- audiometa/utils/os_dependencies_checker/__init__.py +24 -0
- audiometa/utils/os_dependencies_checker/base.py +62 -0
- audiometa/utils/os_dependencies_checker/config.py +77 -0
- audiometa/utils/os_dependencies_checker/macos.py +236 -0
- audiometa/utils/os_dependencies_checker/ubuntu.py +95 -0
- audiometa/utils/os_dependencies_checker/windows.py +227 -0
- audiometa/utils/rating_profiles.py +110 -0
- audiometa/utils/tool_path_resolver.py +135 -0
- audiometa/utils/types.py +82 -0
- audiometa/utils/unified_metadata_key.py +87 -0
- audiometa_python-0.6.0.dist-info/METADATA +1593 -0
- audiometa_python-0.6.0.dist-info/RECORD +352 -0
- audiometa_python-0.6.0.dist-info/WHEEL +5 -0
- audiometa_python-0.6.0.dist-info/entry_points.txt +2 -0
- audiometa_python-0.6.0.dist-info/licenses/LICENSE +202 -0
- audiometa_python-0.6.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from audiometa import get_unified_metadata_field
|
|
4
|
+
from audiometa.test.helpers.id3v1.id3v1_metadata_getter import ID3v1MetadataGetter
|
|
5
|
+
from audiometa.test.helpers.id3v1.id3v1_metadata_setter import ID3v1MetadataSetter
|
|
6
|
+
from audiometa.test.helpers.id3v2.id3v2_metadata_getter import ID3v2MetadataGetter
|
|
7
|
+
from audiometa.test.helpers.id3v2.id3v2_metadata_setter import ID3v2MetadataSetter
|
|
8
|
+
from audiometa.test.helpers.riff.riff_metadata_getter import RIFFMetadataGetter
|
|
9
|
+
from audiometa.test.helpers.riff.riff_metadata_setter import RIFFMetadataSetter
|
|
10
|
+
from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
|
|
11
|
+
from audiometa.test.helpers.vorbis.vorbis_metadata_getter import VorbisMetadataGetter
|
|
12
|
+
from audiometa.test.helpers.vorbis.vorbis_metadata_setter import VorbisMetadataSetter
|
|
13
|
+
from audiometa.utils.metadata_format import MetadataFormat
|
|
14
|
+
from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@pytest.mark.integration
|
|
18
|
+
class TestSmartParsingScenarios:
|
|
19
|
+
"""Test the smart parsing scenarios described in the README:
|
|
20
|
+
|
|
21
|
+
- Modern formats (ID3v2, Vorbis) + Multiple entries: No separator parsing
|
|
22
|
+
- Modern formats (ID3v2, Vorbis) + Single entry: Applies separator parsing
|
|
23
|
+
- Legacy formats (RIFF, ID3v1): Always applies separator parsing
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def test_scenario_1_multiple_entries_no_parsing_id3v2_3(self):
|
|
27
|
+
"""Scenario 1: ID3v2.3 uses single frame with separators - gets parsed on read."""
|
|
28
|
+
with temp_file_with_metadata({"title": "Test Song"}, "id3v2.3") as test_file:
|
|
29
|
+
ID3v2MetadataSetter.set_artists(
|
|
30
|
+
test_file,
|
|
31
|
+
["Artist One", "Artist; with; semicolons", "Artist Three"],
|
|
32
|
+
version="2.3",
|
|
33
|
+
in_separate_frames=True,
|
|
34
|
+
)
|
|
35
|
+
raw_metadata = ID3v2MetadataGetter.get_raw_metadata(test_file, version="2.3")
|
|
36
|
+
assert raw_metadata["TPE1"] == ["Artist One", "Artist; with; semicolons", "Artist Three"]
|
|
37
|
+
|
|
38
|
+
# Read metadata
|
|
39
|
+
artists = get_unified_metadata_field(
|
|
40
|
+
test_file,
|
|
41
|
+
UnifiedMetadataKey.ARTISTS,
|
|
42
|
+
metadata_format=MetadataFormat.ID3V2,
|
|
43
|
+
id3v2_version=(2, 3, 0),
|
|
44
|
+
)
|
|
45
|
+
assert artists == ["Artist One", "Artist; with; semicolons", "Artist Three"]
|
|
46
|
+
|
|
47
|
+
def test_scenario_1_multiple_entries_no_parsing_id3v2_4(self):
|
|
48
|
+
"""Scenario 1: ID3v2.4 uses single frame with separators - gets parsed on read."""
|
|
49
|
+
with temp_file_with_metadata({"title": "Test Song"}, "id3v2.4") as test_file:
|
|
50
|
+
# Set multiple separate artist entries (ID3v2.4 will concatenate them)
|
|
51
|
+
ID3v2MetadataSetter.set_artists(
|
|
52
|
+
test_file,
|
|
53
|
+
["Artist One", "Artist; with; semicolons", "Artist Three"],
|
|
54
|
+
version="2.4",
|
|
55
|
+
in_separate_frames=True,
|
|
56
|
+
)
|
|
57
|
+
raw_metadata = ID3v2MetadataGetter.get_raw_metadata(test_file, version="2.4")
|
|
58
|
+
assert raw_metadata["TPE1"] == ["Artist One", "Artist; with; semicolons", "Artist Three"]
|
|
59
|
+
|
|
60
|
+
# Read metadata
|
|
61
|
+
artists = get_unified_metadata_field(
|
|
62
|
+
test_file,
|
|
63
|
+
UnifiedMetadataKey.ARTISTS,
|
|
64
|
+
metadata_format=MetadataFormat.ID3V2,
|
|
65
|
+
id3v2_version=(2, 4, 0),
|
|
66
|
+
)
|
|
67
|
+
assert artists == ["Artist One", "Artist; with; semicolons", "Artist Three"]
|
|
68
|
+
|
|
69
|
+
def test_scenario_1_multiple_entries_no_parsing_vorbis(self):
|
|
70
|
+
"""Scenario 1: Modern file with separate entries - separators preserved (Vorbis)."""
|
|
71
|
+
with temp_file_with_metadata({"title": "Test Song"}, "flac") as test_file:
|
|
72
|
+
# Set multiple separate artist entries (modern format)
|
|
73
|
+
VorbisMetadataSetter.set_artists(test_file, ["Artist One", "Artist; with; semicolons", "Artist Three"])
|
|
74
|
+
raw_metadata = VorbisMetadataGetter.get_raw_metadata(test_file)
|
|
75
|
+
assert "ARTIST=Artist One" in raw_metadata
|
|
76
|
+
assert "ARTIST=Artist; with; semicolons" in raw_metadata
|
|
77
|
+
assert "ARTIST=Artist Three" in raw_metadata
|
|
78
|
+
|
|
79
|
+
# Read metadata
|
|
80
|
+
artists = get_unified_metadata_field(
|
|
81
|
+
test_file, UnifiedMetadataKey.ARTISTS, metadata_format=MetadataFormat.VORBIS
|
|
82
|
+
)
|
|
83
|
+
assert artists == ["Artist One", "Artist; with; semicolons", "Artist Three"]
|
|
84
|
+
|
|
85
|
+
def test_scenario_1_multiple_entries_no_parsing_riff(self):
|
|
86
|
+
"""Scenario 1: Modern file with separate entries - separators preserved (RIFF)."""
|
|
87
|
+
with temp_file_with_metadata({"title": "Test Song"}, "wav") as test_file:
|
|
88
|
+
# Set multiple separate artist entries (modern format)
|
|
89
|
+
RIFFMetadataSetter.set_artists(
|
|
90
|
+
test_file, ["Artist One", "Artist; with; semicolons", "Artist Three"], in_separate_frames=True
|
|
91
|
+
)
|
|
92
|
+
raw_metadata = RIFFMetadataGetter.get_raw_metadata(test_file)
|
|
93
|
+
assert "TAG:artist=Artist One" in raw_metadata
|
|
94
|
+
assert "TAG:artist=Artist; with; semicolons" in raw_metadata
|
|
95
|
+
assert "TAG:artist=Artist Three" in raw_metadata
|
|
96
|
+
|
|
97
|
+
artists = get_unified_metadata_field(
|
|
98
|
+
test_file, UnifiedMetadataKey.ARTISTS, metadata_format=MetadataFormat.RIFF
|
|
99
|
+
)
|
|
100
|
+
assert artists == ["Artist One", "Artist; with; semicolons", "Artist Three"]
|
|
101
|
+
|
|
102
|
+
def test_scenario_2_single_entry_parsed_id3v2_3(self):
|
|
103
|
+
"""Scenario 2: Legacy data in modern format - single entry gets parsed (ID3v2.3)."""
|
|
104
|
+
with temp_file_with_metadata({"title": "Test Song"}, "id3v2.3") as test_file:
|
|
105
|
+
# Set single artist entry with semicolons (legacy data in modern format)
|
|
106
|
+
ID3v2MetadataSetter.set_artists(
|
|
107
|
+
test_file, ["Artist One;Artist Two;Artist Three"], version="2.3", in_separate_frames=False
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
raw_metadata = ID3v2MetadataGetter.get_raw_metadata(test_file, version="2.3")
|
|
111
|
+
assert raw_metadata["TPE1"] == ["Artist One;Artist Two;Artist Three"]
|
|
112
|
+
|
|
113
|
+
# Read metadata
|
|
114
|
+
artists = get_unified_metadata_field(
|
|
115
|
+
test_file, UnifiedMetadataKey.ARTISTS, metadata_format=MetadataFormat.ID3V2
|
|
116
|
+
)
|
|
117
|
+
assert artists == ["Artist One", "Artist Two", "Artist Three"]
|
|
118
|
+
|
|
119
|
+
def test_scenario_2_single_entry_parsed_id3v2_4(self):
|
|
120
|
+
"""Scenario 2: Legacy data in modern format - single entry gets parsed (ID3v2.4)."""
|
|
121
|
+
with temp_file_with_metadata({"title": "Test Song"}, "id3v2.4") as test_file:
|
|
122
|
+
# Set single artist entry with semicolons (legacy data in modern format)
|
|
123
|
+
ID3v2MetadataSetter.set_artists(
|
|
124
|
+
test_file, ["Artist One;Artist Two;Artist Three"], version="2.4", in_separate_frames=False
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
raw_metadata = ID3v2MetadataGetter.get_raw_metadata(test_file, version="2.4")
|
|
128
|
+
assert raw_metadata["TPE1"] == ["Artist One;Artist Two;Artist Three"]
|
|
129
|
+
|
|
130
|
+
# Read metadata
|
|
131
|
+
artists = get_unified_metadata_field(
|
|
132
|
+
test_file, UnifiedMetadataKey.ARTISTS, metadata_format=MetadataFormat.ID3V2
|
|
133
|
+
)
|
|
134
|
+
assert artists == ["Artist One", "Artist Two", "Artist Three"]
|
|
135
|
+
|
|
136
|
+
def test_scenario_2_single_entry_parsed_vorbis(self):
|
|
137
|
+
"""Scenario 2: Legacy data in modern format - single entry gets parsed (Vorbis)."""
|
|
138
|
+
with temp_file_with_metadata({"title": "Test Song"}, "flac") as test_file:
|
|
139
|
+
# Set single artist entry with semicolons (legacy data in modern format)
|
|
140
|
+
VorbisMetadataSetter.set_artists(test_file, ["Artist One;Artist Two;Artist Three"])
|
|
141
|
+
raw_metadata = VorbisMetadataGetter.get_raw_metadata(test_file)
|
|
142
|
+
assert "ARTIST=Artist One;Artist Two;Artist Three" in raw_metadata
|
|
143
|
+
|
|
144
|
+
# Read metadata
|
|
145
|
+
artists = get_unified_metadata_field(
|
|
146
|
+
test_file, UnifiedMetadataKey.ARTISTS, metadata_format=MetadataFormat.VORBIS
|
|
147
|
+
)
|
|
148
|
+
assert artists == ["Artist One", "Artist Two", "Artist Three"]
|
|
149
|
+
|
|
150
|
+
def test_scenario_2_single_entry_parsed_riff(self):
|
|
151
|
+
"""Scenario 3: Legacy format (RIFF) - always applies separator parsing."""
|
|
152
|
+
with temp_file_with_metadata({"title": "Test Song"}, "wav") as test_file:
|
|
153
|
+
# Set single artist entry with semicolons in RIFF format
|
|
154
|
+
RIFFMetadataSetter.set_artists(test_file, ["Artist One;Artist Two"], in_separate_frames=False)
|
|
155
|
+
raw_metadata = RIFFMetadataGetter.get_raw_metadata(test_file)
|
|
156
|
+
assert "TAG:artist=Artist One;Artist Two" in raw_metadata
|
|
157
|
+
|
|
158
|
+
# Read metadata
|
|
159
|
+
artists = get_unified_metadata_field(
|
|
160
|
+
test_file, UnifiedMetadataKey.ARTISTS, metadata_format=MetadataFormat.RIFF
|
|
161
|
+
)
|
|
162
|
+
assert artists == ["Artist One", "Artist Two"]
|
|
163
|
+
|
|
164
|
+
def test_scenario_3_legacy_format_always_parses_id3v1(self):
|
|
165
|
+
"""Scenario 3: Legacy format (ID3v1) - always applies separator parsing."""
|
|
166
|
+
with temp_file_with_metadata({"title": "Test Song"}, "mp3") as test_file:
|
|
167
|
+
# Set single artist entry with semicolons in ID3v1 format
|
|
168
|
+
ID3v1MetadataSetter.set_artist(test_file, "Artist One;Artist Two")
|
|
169
|
+
raw_metadata = ID3v1MetadataGetter.get_raw_metadata(test_file)
|
|
170
|
+
assert "Artist One;Artist Two" in raw_metadata.get("artist", "")
|
|
171
|
+
|
|
172
|
+
# Read metadata
|
|
173
|
+
artists = get_unified_metadata_field(
|
|
174
|
+
test_file, UnifiedMetadataKey.ARTISTS, metadata_format=MetadataFormat.ID3V1
|
|
175
|
+
)
|
|
176
|
+
assert artists == ["Artist One", "Artist Two"]
|
|
177
|
+
|
|
178
|
+
def test_mixed_scenario_modern_format_with_both_patterns(self):
|
|
179
|
+
"""Test mixed scenario: ID3v2 concatenates multiple entries, single entries get parsed."""
|
|
180
|
+
with temp_file_with_metadata({"title": "Test Song"}, "id3v2.3") as test_file:
|
|
181
|
+
# Set artists as multiple separate entries (ID3v2 will concatenate them)
|
|
182
|
+
ID3v2MetadataSetter.set_artists(
|
|
183
|
+
test_file,
|
|
184
|
+
["Artist One", "Artist; with; semicolons", "Artist Three"],
|
|
185
|
+
version="2.3",
|
|
186
|
+
in_separate_frames=True,
|
|
187
|
+
)
|
|
188
|
+
raw_metadata = ID3v2MetadataGetter.get_raw_metadata(test_file, version="2.3")
|
|
189
|
+
assert raw_metadata["TPE1"] == ["Artist One", "Artist; with; semicolons", "Artist Three"]
|
|
190
|
+
|
|
191
|
+
# Read metadata
|
|
192
|
+
artists = get_unified_metadata_field(
|
|
193
|
+
test_file,
|
|
194
|
+
UnifiedMetadataKey.ARTISTS,
|
|
195
|
+
metadata_format=MetadataFormat.ID3V2,
|
|
196
|
+
id3v2_version=(2, 3, 0),
|
|
197
|
+
)
|
|
198
|
+
assert artists == ["Artist One", "Artist; with; semicolons", "Artist Three"]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from audiometa import get_unified_metadata_field
|
|
4
|
+
from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
|
|
5
|
+
from audiometa.test.helpers.vorbis.vorbis_metadata_setter import VorbisMetadataSetter
|
|
6
|
+
from audiometa.utils.metadata_format import MetadataFormat
|
|
7
|
+
from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@pytest.mark.integration
|
|
11
|
+
class TestUnicodeHandling:
|
|
12
|
+
def test_unicode_characters(self):
|
|
13
|
+
with temp_file_with_metadata({"title": "Test Song"}, "flac") as test_file:
|
|
14
|
+
VorbisMetadataSetter.set_artists(test_file, ["Artist Café", "Artist 音乐", "Artist 🎵"])
|
|
15
|
+
|
|
16
|
+
artists = get_unified_metadata_field(
|
|
17
|
+
test_file, UnifiedMetadataKey.ARTISTS, metadata_format=MetadataFormat.VORBIS
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
assert isinstance(artists, list)
|
|
21
|
+
assert len(artists) == 3
|
|
22
|
+
assert "Artist Café" in artists
|
|
23
|
+
assert "Artist 音乐" in artists
|
|
24
|
+
assert "Artist 🎵" in artists
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Tests for writing multiple entries for the same tag."""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Tests for writing multiple entries for the same tag in different metadata formats."""
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from audiometa import update_metadata
|
|
4
|
+
from audiometa.test.helpers.id3v1.id3v1_metadata_getter import ID3v1MetadataGetter
|
|
5
|
+
from audiometa.test.helpers.id3v1.id3v1_metadata_setter import ID3v1MetadataSetter
|
|
6
|
+
from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
|
|
7
|
+
from audiometa.utils.metadata_format import MetadataFormat
|
|
8
|
+
from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@pytest.mark.integration
|
|
12
|
+
class TestMultipleValuesId3v1:
|
|
13
|
+
def test_id3v1_artists_concatenation_default_comma(self):
|
|
14
|
+
initial_metadata = {"title": "Test Song"}
|
|
15
|
+
with temp_file_with_metadata(initial_metadata, "mp3") as test_file:
|
|
16
|
+
metadata = {UnifiedMetadataKey.ARTISTS: ["Artist 1", "Artist 2"]}
|
|
17
|
+
|
|
18
|
+
update_metadata(test_file, metadata, metadata_format=MetadataFormat.ID3V1)
|
|
19
|
+
|
|
20
|
+
# Use helper to check the created ID3v1 artist field directly
|
|
21
|
+
raw_metadata = ID3v1MetadataGetter.get_raw_metadata(test_file)
|
|
22
|
+
artists = raw_metadata.get("artist", "")
|
|
23
|
+
assert "Artist 1,Artist 2" in artists or "Artist 2,Artist 1" in artists
|
|
24
|
+
|
|
25
|
+
def test_with_existing_artists_field(self):
|
|
26
|
+
with temp_file_with_metadata({}, "mp3") as test_file:
|
|
27
|
+
ID3v1MetadataSetter.set_artist(test_file, "Existing 1; Existing 2")
|
|
28
|
+
raw_metadata = ID3v1MetadataGetter.get_raw_metadata(test_file)
|
|
29
|
+
assert "Existing 1; Existing 2" in raw_metadata.get("artist", "")
|
|
30
|
+
|
|
31
|
+
# Now update with multiple artists
|
|
32
|
+
metadata = {UnifiedMetadataKey.ARTISTS: ["Existing 1", "New 2"]}
|
|
33
|
+
update_metadata(test_file, metadata, metadata_format=MetadataFormat.ID3V1)
|
|
34
|
+
raw_metadata = ID3v1MetadataGetter.get_raw_metadata(test_file)
|
|
35
|
+
artists = raw_metadata.get("artist", "")
|
|
36
|
+
assert "Existing 1" in artists
|
|
37
|
+
assert "New 2" in artists
|
|
38
|
+
assert "Existing 2" not in artists
|
|
39
|
+
|
|
40
|
+
def test_id3v1_separator_priority(self):
|
|
41
|
+
# Each test case: values, expected separator
|
|
42
|
+
test_cases = [
|
|
43
|
+
(["A1", "A2", "A3"], ","),
|
|
44
|
+
(["A,1", "A2", "A3"], ";"),
|
|
45
|
+
(["A,1", "A;2", "A3"], "|"),
|
|
46
|
+
(["A,1", "A;2", "A|3"], "·"),
|
|
47
|
+
(["A,1", "A;2", "A|3", "A·4"], "/"),
|
|
48
|
+
(["A,1", "A;2", "A|3", "A·4", "A/5"], ","),
|
|
49
|
+
]
|
|
50
|
+
for values, expected_sep in test_cases:
|
|
51
|
+
initial_metadata = {"title": "Test Song"}
|
|
52
|
+
with temp_file_with_metadata(initial_metadata, "mp3") as test_file:
|
|
53
|
+
metadata = {UnifiedMetadataKey.ARTISTS: values}
|
|
54
|
+
update_metadata(test_file, metadata, metadata_format=MetadataFormat.ID3V1)
|
|
55
|
+
raw_metadata = ID3v1MetadataGetter.get_raw_metadata(test_file)
|
|
56
|
+
artists = raw_metadata.get("artist", "")
|
|
57
|
+
|
|
58
|
+
# Check that the expected separator is used
|
|
59
|
+
assert expected_sep in artists
|
|
60
|
+
# Check that all values are present as substrings
|
|
61
|
+
for v in values:
|
|
62
|
+
assert v in artists
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from audiometa import update_metadata
|
|
4
|
+
from audiometa.test.helpers.id3v2.id3v2_metadata_getter import ID3v2MetadataGetter
|
|
5
|
+
from audiometa.test.helpers.id3v2.id3v2_metadata_setter import ID3v2MetadataSetter
|
|
6
|
+
from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
|
|
7
|
+
from audiometa.utils.metadata_format import MetadataFormat
|
|
8
|
+
from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@pytest.mark.integration
|
|
12
|
+
class TestMultipleValuesId3v23:
|
|
13
|
+
def test_artists_concatenation(self):
|
|
14
|
+
initial_metadata = {"title": "Test Song"}
|
|
15
|
+
with temp_file_with_metadata(initial_metadata, "id3v2.3") as test_file:
|
|
16
|
+
metadata = {UnifiedMetadataKey.ARTISTS: ["Artist 1", "Artist 2", "Artist 3"]}
|
|
17
|
+
|
|
18
|
+
update_metadata(test_file, metadata, metadata_format=MetadataFormat.ID3V2, id3v2_version=(2, 3, 0))
|
|
19
|
+
|
|
20
|
+
raw_metadata = ID3v2MetadataGetter.get_raw_metadata(test_file, version="2.3")
|
|
21
|
+
assert raw_metadata["TPE1"] == ["Artist 1//Artist 2//Artist 3"]
|
|
22
|
+
|
|
23
|
+
def test_with_existing_artists_field(self):
|
|
24
|
+
# Start with an existing artist field
|
|
25
|
+
initial_metadata = {"artist": "Existing Artist"}
|
|
26
|
+
with temp_file_with_metadata(initial_metadata, "id3v2.3") as test_file:
|
|
27
|
+
ID3v2MetadataSetter.set_artists(test_file, "Existing 1; Existing 2", version="2.3")
|
|
28
|
+
raw_metadata = ID3v2MetadataGetter.get_raw_metadata(test_file, version="2.3")
|
|
29
|
+
assert raw_metadata["TPE1"] == ["Existing 1; Existing 2"]
|
|
30
|
+
|
|
31
|
+
metadata = {UnifiedMetadataKey.ARTISTS: ["Existing 1", "New 2"]}
|
|
32
|
+
update_metadata(test_file, metadata, metadata_format=MetadataFormat.ID3V2, id3v2_version=(2, 3, 0))
|
|
33
|
+
|
|
34
|
+
raw_metadata = ID3v2MetadataGetter.get_raw_metadata(test_file, version="2.3")
|
|
35
|
+
|
|
36
|
+
assert raw_metadata["TPE1"] == ["Existing 1//New 2"]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from audiometa import update_metadata
|
|
4
|
+
from audiometa.test.helpers.id3v2.id3v2_metadata_getter import ID3v2MetadataGetter
|
|
5
|
+
from audiometa.test.helpers.id3v2.id3v2_metadata_setter import ID3v2MetadataSetter
|
|
6
|
+
from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
|
|
7
|
+
from audiometa.utils.metadata_format import MetadataFormat
|
|
8
|
+
from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@pytest.mark.integration
|
|
12
|
+
class TestMultipleValuesId3v24:
|
|
13
|
+
def test_write_multiple_artists(self):
|
|
14
|
+
with temp_file_with_metadata({"title": "Test Song"}, "id3v2.4") as test_file:
|
|
15
|
+
metadata = {UnifiedMetadataKey.ARTISTS: ["Artist One", "Artist Two"]}
|
|
16
|
+
|
|
17
|
+
update_metadata(test_file, metadata, metadata_format=MetadataFormat.ID3V2, id3v2_version=(2, 4, 0))
|
|
18
|
+
|
|
19
|
+
raw_metadata = ID3v2MetadataGetter.get_raw_metadata(test_file, version="2.4")
|
|
20
|
+
|
|
21
|
+
assert raw_metadata["TPE1"] == ["Artist One\x00Artist Two"]
|
|
22
|
+
|
|
23
|
+
def test_write_on_existing_artists_field(self):
|
|
24
|
+
with temp_file_with_metadata({}, "id3v2.4") as test_file:
|
|
25
|
+
ID3v2MetadataSetter.set_artists(test_file, ["Existing A\x00Existing B"], version="2.4")
|
|
26
|
+
raw_metadata = ID3v2MetadataGetter.get_raw_metadata(test_file, version="2.4")
|
|
27
|
+
|
|
28
|
+
assert raw_metadata["TPE1"] == ["Existing A\x00Existing B"]
|
|
29
|
+
|
|
30
|
+
metadata = {UnifiedMetadataKey.ARTISTS: ["Existing A", "New B"]}
|
|
31
|
+
update_metadata(test_file, metadata, metadata_format=MetadataFormat.ID3V2, id3v2_version=(2, 4, 0))
|
|
32
|
+
|
|
33
|
+
raw_metadata = ID3v2MetadataGetter.get_raw_metadata(test_file, version="2.4")
|
|
34
|
+
assert raw_metadata["TPE1"] == ["Existing A\x00New B"]
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from audiometa import update_metadata
|
|
4
|
+
from audiometa.test.helpers.riff.riff_metadata_getter import RIFFMetadataGetter
|
|
5
|
+
from audiometa.test.helpers.riff.riff_metadata_setter import RIFFMetadataSetter
|
|
6
|
+
from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
|
|
7
|
+
from audiometa.utils.metadata_format import MetadataFormat
|
|
8
|
+
from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@pytest.mark.integration
|
|
12
|
+
class TestMultipleValuesRiff:
|
|
13
|
+
def test_artists_concatenation(self):
|
|
14
|
+
initial_metadata = {"title": "Test Song"}
|
|
15
|
+
with temp_file_with_metadata(initial_metadata, "wav") as test_file:
|
|
16
|
+
metadata = {UnifiedMetadataKey.ARTISTS: ["Artist 1", "Artist 2", "Artist 3"]}
|
|
17
|
+
update_metadata(test_file, metadata, metadata_format=MetadataFormat.RIFF)
|
|
18
|
+
|
|
19
|
+
raw_metadata = RIFFMetadataGetter.get_raw_metadata(test_file)
|
|
20
|
+
assert "TAG:artist=Artist 1//Artist 2//Artist 3" in raw_metadata
|
|
21
|
+
|
|
22
|
+
def test_with_existing_artists_field(self):
|
|
23
|
+
with temp_file_with_metadata({}, "wav") as test_file:
|
|
24
|
+
RIFFMetadataSetter.set_artists(test_file, ["Existing 1;Existing 2"])
|
|
25
|
+
raw_metadata = RIFFMetadataGetter.get_raw_metadata(test_file)
|
|
26
|
+
assert "TAG:artist=Existing 1;Existing 2" in raw_metadata
|
|
27
|
+
|
|
28
|
+
metadata = {UnifiedMetadataKey.ARTISTS: ["Existing 1", "New 2"]}
|
|
29
|
+
update_metadata(test_file, metadata, metadata_format=MetadataFormat.RIFF)
|
|
30
|
+
|
|
31
|
+
raw_metadata = RIFFMetadataGetter.get_raw_metadata(test_file)
|
|
32
|
+
assert "TAG:artist=Existing 1//New 2" in raw_metadata
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from audiometa import update_metadata
|
|
4
|
+
from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
|
|
5
|
+
from audiometa.test.helpers.vorbis.vorbis_metadata_getter import VorbisMetadataGetter
|
|
6
|
+
from audiometa.test.helpers.vorbis.vorbis_metadata_setter import VorbisMetadataSetter
|
|
7
|
+
from audiometa.utils.metadata_format import MetadataFormat
|
|
8
|
+
from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@pytest.mark.integration
|
|
12
|
+
class TestMultipleValuesVorbis:
|
|
13
|
+
def test_write_multiple_artists(self):
|
|
14
|
+
with temp_file_with_metadata({}, "flac") as test_file:
|
|
15
|
+
# Write multiple artists using update_metadata
|
|
16
|
+
metadata = {UnifiedMetadataKey.ARTISTS: ["Artist One", "Artist Two", "Artist Three"]}
|
|
17
|
+
|
|
18
|
+
update_metadata(test_file, metadata, metadata_format=MetadataFormat.VORBIS)
|
|
19
|
+
VorbisMetadataSetter.add_title(test_file, "Test Song")
|
|
20
|
+
raw_metadata = VorbisMetadataGetter.get_raw_metadata(test_file)
|
|
21
|
+
assert "ARTIST=Artist One" in raw_metadata
|
|
22
|
+
assert "ARTIST=Artist Two" in raw_metadata
|
|
23
|
+
assert "ARTIST=Artist Three" in raw_metadata
|
|
24
|
+
|
|
25
|
+
def test_with_existing_artists_fields(self):
|
|
26
|
+
# Start with an existing artist field
|
|
27
|
+
with temp_file_with_metadata({}, "flac") as test_file:
|
|
28
|
+
# create an existing value using setter
|
|
29
|
+
VorbisMetadataSetter.set_artists(test_file, ["Existing 1; Existing 2"])
|
|
30
|
+
raw_metadata = VorbisMetadataGetter.get_raw_metadata(test_file)
|
|
31
|
+
assert "Existing 1; Existing 2" in raw_metadata
|
|
32
|
+
|
|
33
|
+
metadata = {UnifiedMetadataKey.ARTISTS: ["Existing 1", "New 2"]}
|
|
34
|
+
update_metadata(test_file, metadata, metadata_format=MetadataFormat.VORBIS)
|
|
35
|
+
|
|
36
|
+
raw_metadata = VorbisMetadataGetter.get_raw_metadata(test_file)
|
|
37
|
+
assert "Existing 1" in raw_metadata
|
|
38
|
+
assert "New 2" in raw_metadata
|
|
39
|
+
assert "Existing 2" not in raw_metadata
|
|
40
|
+
|
|
41
|
+
def test_with_existing_artists_fields_with_lower_case_key(self):
|
|
42
|
+
# Start with an existing artist field in lower case
|
|
43
|
+
with temp_file_with_metadata({}, "flac") as test_file:
|
|
44
|
+
# create an existing value using setter
|
|
45
|
+
VorbisMetadataSetter.set_artists(test_file, ["ExistingLower 1; ExistingLower 2"], key_lower_case=True)
|
|
46
|
+
raw_output = VorbisMetadataGetter.get_raw_metadata(test_file)
|
|
47
|
+
assert "artist=ExistingLower 1; ExistingLower 2" in raw_output
|
|
48
|
+
|
|
49
|
+
metadata = {UnifiedMetadataKey.ARTISTS: ["ExistingLower 1", "New 2"]}
|
|
50
|
+
update_metadata(test_file, metadata, metadata_format=MetadataFormat.VORBIS)
|
|
51
|
+
|
|
52
|
+
raw_output = VorbisMetadataGetter.get_raw_metadata(test_file)
|
|
53
|
+
assert "ARTIST=ExistingLower 1" in raw_output
|
|
54
|
+
assert "ARTIST=New 2" in raw_output
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from audiometa import get_unified_metadata_field, update_metadata
|
|
4
|
+
from audiometa.exceptions import InvalidMetadataFieldTypeError
|
|
5
|
+
from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
|
|
6
|
+
from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@pytest.mark.integration
|
|
10
|
+
class TestMultipleValuesErrorHandling:
|
|
11
|
+
def test_write_invalid_data_types_in_list(self):
|
|
12
|
+
# Test with invalid data types in multiple value lists
|
|
13
|
+
with temp_file_with_metadata({}, "mp3") as temp_audio_file_path:
|
|
14
|
+
metadata = {UnifiedMetadataKey.ARTISTS: [1, 2, 3]} # Numbers instead of strings
|
|
15
|
+
with pytest.raises(InvalidMetadataFieldTypeError):
|
|
16
|
+
update_metadata(temp_audio_file_path, metadata)
|
|
17
|
+
|
|
18
|
+
def test_write_mixed_data_types_in_list(self):
|
|
19
|
+
# Test with mixed data types in multiple value lists
|
|
20
|
+
with temp_file_with_metadata({}, "mp3") as temp_audio_file_path:
|
|
21
|
+
metadata = {UnifiedMetadataKey.ARTISTS: ["Artist One", 123, None, "Artist Two"]}
|
|
22
|
+
with pytest.raises(InvalidMetadataFieldTypeError):
|
|
23
|
+
update_metadata(temp_audio_file_path, metadata)
|
|
24
|
+
|
|
25
|
+
def test_write_list_with_none_values_are_filtered(self):
|
|
26
|
+
# Test that None values in lists are automatically filtered out
|
|
27
|
+
# If all values are None, the field should be removed entirely
|
|
28
|
+
with temp_file_with_metadata({}, "mp3") as temp_audio_file_path:
|
|
29
|
+
metadata = {UnifiedMetadataKey.ARTISTS: [None, None]}
|
|
30
|
+
update_metadata(temp_audio_file_path, metadata)
|
|
31
|
+
# Field should be removed (None) since all values were filtered out
|
|
32
|
+
artists = get_unified_metadata_field(temp_audio_file_path, UnifiedMetadataKey.ARTISTS)
|
|
33
|
+
assert artists is None
|
|
34
|
+
|
|
35
|
+
def test_write_list_with_mixed_none_and_valid_values(self):
|
|
36
|
+
# Test that None values are filtered but valid values remain
|
|
37
|
+
with temp_file_with_metadata({}, "mp3") as temp_audio_file_path:
|
|
38
|
+
metadata = {UnifiedMetadataKey.ARTISTS: ["Artist One", None, "Artist Two", None, "Artist Three"]}
|
|
39
|
+
update_metadata(temp_audio_file_path, metadata)
|
|
40
|
+
# None values should be filtered out, only valid artists remain
|
|
41
|
+
artists = get_unified_metadata_field(temp_audio_file_path, UnifiedMetadataKey.ARTISTS)
|
|
42
|
+
assert artists == ["Artist One", "Artist Two", "Artist Three"]
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import time
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from audiometa import get_unified_metadata_field, update_metadata
|
|
6
|
+
from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
|
|
7
|
+
from audiometa.utils.metadata_format import MetadataFormat
|
|
8
|
+
from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@pytest.mark.integration
|
|
12
|
+
class TestMultipleValuesBoundaryConditions:
|
|
13
|
+
def test_write_large_number_of_multiple_values_per_field(self):
|
|
14
|
+
with temp_file_with_metadata({"title": "Test Song"}, "flac") as test_file:
|
|
15
|
+
# Test with very large number of values per field
|
|
16
|
+
large_values_number = 1000
|
|
17
|
+
large_artist_list = [f"Artist {i:04d}" for i in range(large_values_number)]
|
|
18
|
+
|
|
19
|
+
metadata = {
|
|
20
|
+
UnifiedMetadataKey.ARTISTS: large_artist_list,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
start_time = time.time()
|
|
24
|
+
update_metadata(test_file, metadata, metadata_format=MetadataFormat.VORBIS)
|
|
25
|
+
write_time = time.time() - start_time
|
|
26
|
+
|
|
27
|
+
artists = get_unified_metadata_field(
|
|
28
|
+
test_file, UnifiedMetadataKey.ARTISTS, metadata_format=MetadataFormat.VORBIS
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
assert isinstance(artists, list)
|
|
32
|
+
assert len(artists) == large_values_number
|
|
33
|
+
|
|
34
|
+
# Performance should be reasonable
|
|
35
|
+
assert write_time < 10.0, f"Write took too long: {write_time:.2f}s"
|
|
36
|
+
|
|
37
|
+
def test_write_extremely_long_individual_values(self):
|
|
38
|
+
with temp_file_with_metadata({"title": "Test Song"}, "flac") as test_file:
|
|
39
|
+
# Test with extremely long individual values
|
|
40
|
+
very_long_string = "A" * 50000 # 50KB string
|
|
41
|
+
metadata = {
|
|
42
|
+
UnifiedMetadataKey.ARTISTS: [very_long_string, "Normal Artist"],
|
|
43
|
+
UnifiedMetadataKey.COMMENT: very_long_string,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
update_metadata(test_file, metadata, metadata_format=MetadataFormat.VORBIS)
|
|
47
|
+
|
|
48
|
+
artists = get_unified_metadata_field(
|
|
49
|
+
test_file, UnifiedMetadataKey.ARTISTS, metadata_format=MetadataFormat.VORBIS
|
|
50
|
+
)
|
|
51
|
+
assert isinstance(artists, list)
|
|
52
|
+
assert len(artists) == 2
|
|
53
|
+
assert very_long_string in artists
|
|
54
|
+
assert "Normal Artist" in artists
|
|
55
|
+
|
|
56
|
+
def test_write_mixed_length_values(self):
|
|
57
|
+
with temp_file_with_metadata({"title": "Test Song"}, "flac") as test_file:
|
|
58
|
+
# Test with mixed length values
|
|
59
|
+
mixed_lengths = [
|
|
60
|
+
"A", # 1 character
|
|
61
|
+
"AB", # 2 characters
|
|
62
|
+
"ABC", # 3 characters
|
|
63
|
+
"A" * 100, # 100 characters
|
|
64
|
+
"A" * 1000, # 1000 characters
|
|
65
|
+
"A" * 10000, # 10000 characters
|
|
66
|
+
]
|
|
67
|
+
metadata = {UnifiedMetadataKey.ARTISTS: mixed_lengths}
|
|
68
|
+
update_metadata(test_file, metadata, metadata_format=MetadataFormat.VORBIS)
|
|
69
|
+
|
|
70
|
+
artists = get_unified_metadata_field(test_file, UnifiedMetadataKey.ARTISTS)
|
|
71
|
+
|
|
72
|
+
assert isinstance(artists, list)
|
|
73
|
+
assert len(artists) == 6
|
|
74
|
+
for value in mixed_lengths:
|
|
75
|
+
assert value in artists
|
|
76
|
+
|
|
77
|
+
def test_write_very_large_metadata_dict(self):
|
|
78
|
+
with temp_file_with_metadata({"title": "Test Song"}, "flac") as test_file:
|
|
79
|
+
# Test with very large metadata dictionary
|
|
80
|
+
large_metadata = {}
|
|
81
|
+
|
|
82
|
+
# Add multiple values for each supported multiple-value field
|
|
83
|
+
# Actual implementation of Vorbis metadata cannot support more than 50 values per field
|
|
84
|
+
for i in range(50):
|
|
85
|
+
large_metadata[UnifiedMetadataKey.ARTISTS] = [f"Artist {i:04d}" for i in range(50)]
|
|
86
|
+
start_time = time.time()
|
|
87
|
+
update_metadata(test_file, large_metadata, metadata_format=MetadataFormat.VORBIS)
|
|
88
|
+
write_time = time.time() - start_time
|
|
89
|
+
|
|
90
|
+
artists = get_unified_metadata_field(
|
|
91
|
+
test_file, UnifiedMetadataKey.ARTISTS, metadata_format=MetadataFormat.VORBIS
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
assert isinstance(artists, list)
|
|
95
|
+
assert len(artists) == 50
|
|
96
|
+
|
|
97
|
+
# Performance should be reasonable
|
|
98
|
+
assert write_time < 15.0, f"Write took too long: {write_time:.2f}s"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Integration tests for reading metadata."""
|