audiometa-python 0.2.2__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 +1240 -0
- audiometa/__main__.py +6 -0
- audiometa/_audio_file.py +602 -0
- audiometa/cli.py +347 -0
- audiometa/exceptions.py +167 -0
- audiometa/manager/_MetadataManager.py +665 -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 +945 -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 +778 -0
- audiometa/manager/_rating_supporting/riff/__init__.py +25 -0
- audiometa/manager/_rating_supporting/riff/_riff_constants.py +10 -0
- audiometa/manager/_rating_supporting/vorbis/_VorbisManager.py +525 -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 +305 -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 +188 -0
- audiometa/test/helpers/id3v2/id3v2_metadata_setter.py +428 -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 +216 -0
- audiometa/test/helpers/riff/riff_metadata_deleter.py +56 -0
- audiometa/test/helpers/riff/riff_metadata_getter.py +118 -0
- audiometa/test/helpers/riff/riff_metadata_setter.py +196 -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 +200 -0
- audiometa/test/tests/__init__.py +0 -0
- audiometa/test/tests/conftest.py +261 -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/test_cli_delete.py +20 -0
- audiometa/test/tests/e2e/cli/test_cli_formatting.py +31 -0
- audiometa/test/tests/e2e/cli/test_cli_help.py +40 -0
- audiometa/test/tests/e2e/cli/test_cli_read.py +79 -0
- audiometa/test/tests/e2e/cli/test_cli_unified.py +33 -0
- audiometa/test/tests/e2e/cli/test_cli_write.py +116 -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_binary_data_filtering.py +250 -0
- audiometa/test/tests/integration/get_full_metadata/test_get_full_metadata.py +297 -0
- audiometa/test/tests/integration/get_full_metadata/test_get_full_metadata_edge_cases.py +231 -0
- audiometa/test/tests/integration/get_full_metadata/test_options.py +207 -0
- audiometa/test/tests/integration/get_full_metadata/test_parsed_fields_keys.py +85 -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/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/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 +134 -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 +38 -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 +23 -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 +130 -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_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 +57 -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_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_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/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/os_dependencies_checker/__init__.py +24 -0
- audiometa/utils/os_dependencies_checker/base.py +62 -0
- audiometa/utils/os_dependencies_checker/config.py +51 -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 +81 -0
- audiometa_python-0.2.2.dist-info/METADATA +1464 -0
- audiometa_python-0.2.2.dist-info/RECORD +318 -0
- audiometa_python-0.2.2.dist-info/WHEEL +5 -0
- audiometa_python-0.2.2.dist-info/entry_points.txt +2 -0
- audiometa_python-0.2.2.dist-info/licenses/LICENSE +202 -0
- audiometa_python-0.2.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
from unittest.mock import MagicMock
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from audiometa.exceptions import InvalidRatingValueError
|
|
6
|
+
from audiometa.manager._rating_supporting._RatingSupportingMetadataManager import _RatingSupportingMetadataManager
|
|
7
|
+
from audiometa.manager._rating_supporting.id3v2._Id3v2Manager import _Id3v2Manager as Id3v2Manager
|
|
8
|
+
from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@pytest.mark.unit
|
|
12
|
+
class TestRatingValidation:
|
|
13
|
+
def test_validate_rating_value_raw_mode_non_negative_allowed(self):
|
|
14
|
+
# These should not raise any exceptions
|
|
15
|
+
_RatingSupportingMetadataManager.validate_rating_value(0, None)
|
|
16
|
+
_RatingSupportingMetadataManager.validate_rating_value(1, None)
|
|
17
|
+
_RatingSupportingMetadataManager.validate_rating_value(128, None)
|
|
18
|
+
_RatingSupportingMetadataManager.validate_rating_value(255, None)
|
|
19
|
+
_RatingSupportingMetadataManager.validate_rating_value(1000, None)
|
|
20
|
+
_RatingSupportingMetadataManager.validate_rating_value(1.5, None)
|
|
21
|
+
_RatingSupportingMetadataManager.validate_rating_value(75.7, None)
|
|
22
|
+
_RatingSupportingMetadataManager.validate_rating_value(0.0, None)
|
|
23
|
+
|
|
24
|
+
def test_validate_rating_value_raw_mode_negative_rejected(self):
|
|
25
|
+
with pytest.raises(InvalidRatingValueError) as exc_info:
|
|
26
|
+
_RatingSupportingMetadataManager.validate_rating_value(-1, None)
|
|
27
|
+
assert "must be non-negative" in str(exc_info.value)
|
|
28
|
+
|
|
29
|
+
with pytest.raises(InvalidRatingValueError) as exc_info:
|
|
30
|
+
_RatingSupportingMetadataManager.validate_rating_value(-100, None)
|
|
31
|
+
assert "must be non-negative" in str(exc_info.value)
|
|
32
|
+
|
|
33
|
+
with pytest.raises(InvalidRatingValueError) as exc_info:
|
|
34
|
+
_RatingSupportingMetadataManager.validate_rating_value(-0.5, None)
|
|
35
|
+
assert "must be non-negative" in str(exc_info.value)
|
|
36
|
+
|
|
37
|
+
def test_validate_rating_value_normalized_mode_valid_values(self):
|
|
38
|
+
# Valid values: those that map to BASE_100_PROPORTIONAL profile
|
|
39
|
+
# (value/100 * 100) must be in [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
|
|
40
|
+
valid_values = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
|
|
41
|
+
for value in valid_values:
|
|
42
|
+
_RatingSupportingMetadataManager.validate_rating_value(value, 100)
|
|
43
|
+
|
|
44
|
+
def test_validate_rating_value_normalized_mode_valid_float_values(self):
|
|
45
|
+
# Valid float values: 1.5/10 * 100 = 15 (in BASE_100_PROPORTIONAL)
|
|
46
|
+
_RatingSupportingMetadataManager.validate_rating_value(1.5, 10)
|
|
47
|
+
# 2.5/10 * 100 = 25 (in BASE_100_PROPORTIONAL)
|
|
48
|
+
_RatingSupportingMetadataManager.validate_rating_value(2.5, 10)
|
|
49
|
+
# 7.5/10 * 100 = 75 (in BASE_100_PROPORTIONAL)
|
|
50
|
+
_RatingSupportingMetadataManager.validate_rating_value(7.5, 10)
|
|
51
|
+
# 50.0/100 * 100 = 50 (in BASE_100_PROPORTIONAL)
|
|
52
|
+
_RatingSupportingMetadataManager.validate_rating_value(50.0, 100)
|
|
53
|
+
|
|
54
|
+
def test_validate_rating_value_normalized_mode_negative_rejected(self):
|
|
55
|
+
with pytest.raises(InvalidRatingValueError) as exc_info:
|
|
56
|
+
_RatingSupportingMetadataManager.validate_rating_value(-1, 100)
|
|
57
|
+
assert "must be non-negative" in str(exc_info.value)
|
|
58
|
+
|
|
59
|
+
@pytest.mark.parametrize(
|
|
60
|
+
"rating",
|
|
61
|
+
[101, 100.1, 101.5],
|
|
62
|
+
)
|
|
63
|
+
def test_validate_rating_value_normalized_mode_over_max_rejected(self, rating):
|
|
64
|
+
with pytest.raises(InvalidRatingValueError) as exc_info:
|
|
65
|
+
_RatingSupportingMetadataManager.validate_rating_value(rating, 100)
|
|
66
|
+
assert "out of range" in str(exc_info.value)
|
|
67
|
+
assert "must be between 0 and 100" in str(exc_info.value)
|
|
68
|
+
|
|
69
|
+
def test_validate_rating_value_normalized_mode_invalid_profile_value(self):
|
|
70
|
+
# 33/100 maps to star rating index 3 (1.5 stars), which is valid
|
|
71
|
+
# This test verifies that values mapping to valid star rating indices are accepted
|
|
72
|
+
# Note: The old validation checked if output values exist, but the conversion uses star rating indices
|
|
73
|
+
_RatingSupportingMetadataManager.validate_rating_value(33, 100)
|
|
74
|
+
|
|
75
|
+
# Test a value that would map to an invalid star rating index (> 10)
|
|
76
|
+
with pytest.raises(InvalidRatingValueError) as exc_info:
|
|
77
|
+
_RatingSupportingMetadataManager.validate_rating_value(110, 100)
|
|
78
|
+
assert "out of range" in str(exc_info.value)
|
|
79
|
+
|
|
80
|
+
def test_validate_rating_value_normalized_mode_different_max_values(self):
|
|
81
|
+
"""Test profile-based validation with different max values."""
|
|
82
|
+
# Test with max_value = 10 (0-10 scale)
|
|
83
|
+
# All values 0-10 map to valid profile values
|
|
84
|
+
valid_values_10 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
|
85
|
+
for value in valid_values_10:
|
|
86
|
+
_RatingSupportingMetadataManager.validate_rating_value(value, 10)
|
|
87
|
+
|
|
88
|
+
# Test with max_value = 255
|
|
89
|
+
# Valid values: those that map to BASE_255_NON_PROPORTIONAL profile values
|
|
90
|
+
# Profile values: 0, 13, 1, 54, 64, 118, 128, 186, 196, 242, 255
|
|
91
|
+
# So valid normalized values are: 0, 13, 1, 54, 64, 118, 128, 186, 196, 242, 255
|
|
92
|
+
valid_values_255 = [0, 1, 13, 54, 64, 118, 128, 186, 196, 242, 255]
|
|
93
|
+
for value in valid_values_255:
|
|
94
|
+
_RatingSupportingMetadataManager.validate_rating_value(value, 255)
|
|
95
|
+
|
|
96
|
+
# Also valid: values that map to BASE_100_PROPORTIONAL when scaled
|
|
97
|
+
# For example: 50/255 * 100 = 20 (round), which is in BASE_100_PROPORTIONAL
|
|
98
|
+
valid_values_255_also_100 = [25, 50, 76, 102, 127, 153, 178, 204, 229]
|
|
99
|
+
for value in valid_values_255_also_100:
|
|
100
|
+
_RatingSupportingMetadataManager.validate_rating_value(value, 255)
|
|
101
|
+
|
|
102
|
+
# Values that map to valid star rating indices are accepted
|
|
103
|
+
# The conversion uses star rating indices (0-10), not direct output values
|
|
104
|
+
# 37/255 -> index 1 (0.5 stars) -> valid
|
|
105
|
+
# 99/255 -> index 4 (2 stars) -> valid
|
|
106
|
+
# 200/255 -> index 8 (4 stars) -> valid
|
|
107
|
+
# All these values map to valid star rating indices, so they are accepted
|
|
108
|
+
|
|
109
|
+
# Test a value that would map to an invalid star rating index (> 10)
|
|
110
|
+
with pytest.raises(InvalidRatingValueError) as exc_info:
|
|
111
|
+
_RatingSupportingMetadataManager.validate_rating_value(256, 255)
|
|
112
|
+
assert "out of range" in str(exc_info.value)
|
|
113
|
+
|
|
114
|
+
def test_validate_rating_in_unified_metadata_valid(self):
|
|
115
|
+
manager = Id3v2Manager(audio_file=MagicMock(), normalized_rating_max_value=100)
|
|
116
|
+
|
|
117
|
+
# Valid metadata should not raise
|
|
118
|
+
manager._validate_rating_in_unified_metadata({UnifiedMetadataKey.RATING: 50})
|
|
119
|
+
|
|
120
|
+
def test_validate_rating_in_unified_metadata_invalid_type(self):
|
|
121
|
+
manager = Id3v2Manager(audio_file=MagicMock(), normalized_rating_max_value=100)
|
|
122
|
+
|
|
123
|
+
# String should raise InvalidRatingValueError
|
|
124
|
+
with pytest.raises(InvalidRatingValueError) as exc_info:
|
|
125
|
+
manager._validate_rating_in_unified_metadata({UnifiedMetadataKey.RATING: "invalid"})
|
|
126
|
+
assert "Rating value must be numeric" in str(exc_info.value)
|
|
127
|
+
|
|
128
|
+
# Dict should raise InvalidRatingValueError
|
|
129
|
+
with pytest.raises(InvalidRatingValueError) as exc_info:
|
|
130
|
+
manager._validate_rating_in_unified_metadata({UnifiedMetadataKey.RATING: {"not": "valid"}})
|
|
131
|
+
assert "Rating value must be numeric" in str(exc_info.value)
|
|
132
|
+
|
|
133
|
+
def test_validate_rating_in_unified_metadata_float_accepted(self):
|
|
134
|
+
manager = Id3v2Manager(audio_file=MagicMock(), normalized_rating_max_value=100)
|
|
135
|
+
|
|
136
|
+
# Float should be accepted
|
|
137
|
+
manager._validate_rating_in_unified_metadata({UnifiedMetadataKey.RATING: 50.0})
|
|
138
|
+
manager._validate_rating_in_unified_metadata({UnifiedMetadataKey.RATING: 1.5})
|
|
139
|
+
manager._validate_rating_in_unified_metadata({UnifiedMetadataKey.RATING: 7.5})
|
|
140
|
+
|
|
141
|
+
def test_validate_rating_in_unified_metadata_none_ignored(self):
|
|
142
|
+
manager = Id3v2Manager(audio_file=MagicMock(), normalized_rating_max_value=100)
|
|
143
|
+
|
|
144
|
+
# None should be ignored (no exception)
|
|
145
|
+
manager._validate_rating_in_unified_metadata({UnifiedMetadataKey.RATING: None})
|
|
146
|
+
|
|
147
|
+
def test_validate_rating_in_unified_metadata_missing_key(self):
|
|
148
|
+
manager = Id3v2Manager(audio_file=MagicMock(), normalized_rating_max_value=100)
|
|
149
|
+
|
|
150
|
+
# Missing key should be ignored
|
|
151
|
+
manager._validate_rating_in_unified_metadata({})
|
audiometa/test/tests/unit/metadata_managers/metadata_field/rating/writing/test_writing_profiles.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from unittest.mock import MagicMock
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from audiometa.manager._rating_supporting.id3v2._Id3v2Manager import _Id3v2Manager as Id3v2Manager
|
|
6
|
+
from audiometa.manager._rating_supporting.riff._RiffManager import _RiffManager as RiffManager
|
|
7
|
+
from audiometa.manager._rating_supporting.vorbis._VorbisManager import _VorbisManager as VorbisManager
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@pytest.mark.unit
|
|
11
|
+
class TestWritingProfiles:
|
|
12
|
+
@pytest.mark.parametrize(
|
|
13
|
+
("normalized_rating", "expected_raw_rating"),
|
|
14
|
+
[
|
|
15
|
+
(0, 0),
|
|
16
|
+
(1, 13),
|
|
17
|
+
(2, 1),
|
|
18
|
+
(3, 54),
|
|
19
|
+
(4, 64),
|
|
20
|
+
(5, 118),
|
|
21
|
+
(6, 128),
|
|
22
|
+
(7, 186),
|
|
23
|
+
(8, 196),
|
|
24
|
+
(9, 242),
|
|
25
|
+
(10, 255),
|
|
26
|
+
],
|
|
27
|
+
)
|
|
28
|
+
def test_base_255_non_proportional(self, normalized_rating, expected_raw_rating):
|
|
29
|
+
id3v2_audio_file = MagicMock()
|
|
30
|
+
id3v2_manager = Id3v2Manager(audio_file=id3v2_audio_file, normalized_rating_max_value=10)
|
|
31
|
+
raw_rating = id3v2_manager._convert_normalized_rating_to_file_rating(normalized_rating=normalized_rating)
|
|
32
|
+
assert raw_rating == expected_raw_rating
|
|
33
|
+
|
|
34
|
+
wave_audio_file = MagicMock()
|
|
35
|
+
wave_audio_file.file_extension = ".wav"
|
|
36
|
+
riff_manager = RiffManager(audio_file=wave_audio_file, normalized_rating_max_value=10)
|
|
37
|
+
raw_rating = riff_manager._convert_normalized_rating_to_file_rating(normalized_rating=normalized_rating)
|
|
38
|
+
assert raw_rating == expected_raw_rating
|
|
39
|
+
|
|
40
|
+
@pytest.mark.parametrize(
|
|
41
|
+
("normalized_rating", "expected_raw_rating"),
|
|
42
|
+
[
|
|
43
|
+
(0, 0),
|
|
44
|
+
(1, 10),
|
|
45
|
+
(2, 20),
|
|
46
|
+
(3, 30),
|
|
47
|
+
(4, 40),
|
|
48
|
+
(5, 50),
|
|
49
|
+
(6, 60),
|
|
50
|
+
(7, 70),
|
|
51
|
+
(8, 80),
|
|
52
|
+
(9, 90),
|
|
53
|
+
(10, 100),
|
|
54
|
+
],
|
|
55
|
+
)
|
|
56
|
+
def test_base_100_proportional(self, normalized_rating, expected_raw_rating):
|
|
57
|
+
flac_audio_file = MagicMock()
|
|
58
|
+
flac_audio_file.file_extension = ".flac"
|
|
59
|
+
vorbis_manager = VorbisManager(audio_file=flac_audio_file, normalized_rating_max_value=10)
|
|
60
|
+
raw_rating = vorbis_manager._convert_normalized_rating_to_file_rating(normalized_rating=normalized_rating)
|
|
61
|
+
assert raw_rating == expected_raw_rating
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from audiometa import validate_metadata_for_update
|
|
4
|
+
from audiometa.exceptions import InvalidMetadataFieldFormatError, InvalidMetadataFieldTypeError
|
|
5
|
+
from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@pytest.mark.unit
|
|
9
|
+
class TestReleaseDateFormatValidation:
|
|
10
|
+
@pytest.mark.parametrize(
|
|
11
|
+
"year",
|
|
12
|
+
["2024", "1900", "0000", "9999", "1970"],
|
|
13
|
+
)
|
|
14
|
+
def test_valid_yyyy_format(self, year):
|
|
15
|
+
validate_metadata_for_update({UnifiedMetadataKey.RELEASE_DATE: year})
|
|
16
|
+
|
|
17
|
+
@pytest.mark.parametrize(
|
|
18
|
+
"date",
|
|
19
|
+
[
|
|
20
|
+
"2024-01-01",
|
|
21
|
+
"2024-12-31",
|
|
22
|
+
"1900-01-01",
|
|
23
|
+
"0000-00-00",
|
|
24
|
+
"9999-12-31",
|
|
25
|
+
"1970-06-15",
|
|
26
|
+
],
|
|
27
|
+
)
|
|
28
|
+
def test_valid_yyyy_mm_dd_format(self, date):
|
|
29
|
+
validate_metadata_for_update({UnifiedMetadataKey.RELEASE_DATE: date})
|
|
30
|
+
|
|
31
|
+
@pytest.mark.parametrize(
|
|
32
|
+
"date",
|
|
33
|
+
[
|
|
34
|
+
"2024/01/01",
|
|
35
|
+
"2024.01.01",
|
|
36
|
+
"2024_01_01",
|
|
37
|
+
"2024 01 01",
|
|
38
|
+
],
|
|
39
|
+
)
|
|
40
|
+
def test_invalid_format_wrong_separator(self, date):
|
|
41
|
+
with pytest.raises(InvalidMetadataFieldFormatError) as exc_info:
|
|
42
|
+
validate_metadata_for_update({UnifiedMetadataKey.RELEASE_DATE: date})
|
|
43
|
+
error = exc_info.value
|
|
44
|
+
assert error.field == UnifiedMetadataKey.RELEASE_DATE.value
|
|
45
|
+
assert error.value == date
|
|
46
|
+
assert "YYYY" in error.expected_format
|
|
47
|
+
|
|
48
|
+
@pytest.mark.parametrize(
|
|
49
|
+
"date",
|
|
50
|
+
[
|
|
51
|
+
"2024-1-1",
|
|
52
|
+
"2024-1-01",
|
|
53
|
+
"2024-01-1",
|
|
54
|
+
],
|
|
55
|
+
)
|
|
56
|
+
def test_invalid_format_single_digit_month_day(self, date):
|
|
57
|
+
with pytest.raises(InvalidMetadataFieldFormatError) as exc_info:
|
|
58
|
+
validate_metadata_for_update({UnifiedMetadataKey.RELEASE_DATE: date})
|
|
59
|
+
error = exc_info.value
|
|
60
|
+
assert error.field == UnifiedMetadataKey.RELEASE_DATE.value
|
|
61
|
+
assert error.value == date
|
|
62
|
+
|
|
63
|
+
@pytest.mark.parametrize(
|
|
64
|
+
"date",
|
|
65
|
+
[
|
|
66
|
+
"24",
|
|
67
|
+
"024",
|
|
68
|
+
"999",
|
|
69
|
+
],
|
|
70
|
+
)
|
|
71
|
+
def test_invalid_format_short_year(self, date):
|
|
72
|
+
with pytest.raises(InvalidMetadataFieldFormatError) as exc_info:
|
|
73
|
+
validate_metadata_for_update({UnifiedMetadataKey.RELEASE_DATE: date})
|
|
74
|
+
error = exc_info.value
|
|
75
|
+
assert error.field == UnifiedMetadataKey.RELEASE_DATE.value
|
|
76
|
+
assert error.value == date
|
|
77
|
+
|
|
78
|
+
@pytest.mark.parametrize(
|
|
79
|
+
"date",
|
|
80
|
+
[
|
|
81
|
+
"20241",
|
|
82
|
+
"20241-01-01",
|
|
83
|
+
],
|
|
84
|
+
)
|
|
85
|
+
def test_invalid_format_long_year(self, date):
|
|
86
|
+
with pytest.raises(InvalidMetadataFieldFormatError) as exc_info:
|
|
87
|
+
validate_metadata_for_update({UnifiedMetadataKey.RELEASE_DATE: date})
|
|
88
|
+
error = exc_info.value
|
|
89
|
+
assert error.field == UnifiedMetadataKey.RELEASE_DATE.value
|
|
90
|
+
assert error.value == date
|
|
91
|
+
|
|
92
|
+
@pytest.mark.parametrize(
|
|
93
|
+
"date",
|
|
94
|
+
[
|
|
95
|
+
"not-a-date",
|
|
96
|
+
"2024-abc-01",
|
|
97
|
+
"abcd-01-01",
|
|
98
|
+
"2024-01-abc",
|
|
99
|
+
"2024a",
|
|
100
|
+
],
|
|
101
|
+
)
|
|
102
|
+
def test_invalid_format_non_numeric(self, date):
|
|
103
|
+
with pytest.raises(InvalidMetadataFieldFormatError) as exc_info:
|
|
104
|
+
validate_metadata_for_update({UnifiedMetadataKey.RELEASE_DATE: date})
|
|
105
|
+
error = exc_info.value
|
|
106
|
+
assert error.field == UnifiedMetadataKey.RELEASE_DATE.value
|
|
107
|
+
assert error.value == date
|
|
108
|
+
|
|
109
|
+
@pytest.mark.parametrize(
|
|
110
|
+
"date",
|
|
111
|
+
[
|
|
112
|
+
"2024-",
|
|
113
|
+
"2024-01",
|
|
114
|
+
"2024-01-",
|
|
115
|
+
"-01-01",
|
|
116
|
+
],
|
|
117
|
+
)
|
|
118
|
+
def test_invalid_format_incomplete_date(self, date):
|
|
119
|
+
with pytest.raises(InvalidMetadataFieldFormatError) as exc_info:
|
|
120
|
+
validate_metadata_for_update({UnifiedMetadataKey.RELEASE_DATE: date})
|
|
121
|
+
error = exc_info.value
|
|
122
|
+
assert error.field == UnifiedMetadataKey.RELEASE_DATE.value
|
|
123
|
+
assert error.value == date
|
|
124
|
+
|
|
125
|
+
def test_none_value_allowed(self):
|
|
126
|
+
validate_metadata_for_update({UnifiedMetadataKey.RELEASE_DATE: None})
|
|
127
|
+
|
|
128
|
+
def test_empty_string_allowed(self):
|
|
129
|
+
validate_metadata_for_update({UnifiedMetadataKey.RELEASE_DATE: ""})
|
|
130
|
+
|
|
131
|
+
def test_format_validation_after_type_validation(self):
|
|
132
|
+
invalid_type = {UnifiedMetadataKey.RELEASE_DATE: 2024}
|
|
133
|
+
with pytest.raises(InvalidMetadataFieldTypeError) as exc_info:
|
|
134
|
+
validate_metadata_for_update(invalid_type)
|
|
135
|
+
assert not isinstance(exc_info.value, InvalidMetadataFieldFormatError)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from audiometa import validate_metadata_for_update
|
|
4
|
+
from audiometa.exceptions import InvalidMetadataFieldFormatError, InvalidMetadataFieldTypeError
|
|
5
|
+
from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@pytest.mark.unit
|
|
9
|
+
class TestTrackNumberValidation:
|
|
10
|
+
"""Test track number field validation in validate_metadata_for_update."""
|
|
11
|
+
|
|
12
|
+
@pytest.mark.parametrize(
|
|
13
|
+
"track_number",
|
|
14
|
+
[1, 5, 99],
|
|
15
|
+
)
|
|
16
|
+
def test_valid_track_number_as_int(self, track_number):
|
|
17
|
+
validate_metadata_for_update({UnifiedMetadataKey.TRACK_NUMBER: track_number})
|
|
18
|
+
|
|
19
|
+
@pytest.mark.parametrize(
|
|
20
|
+
"track_number",
|
|
21
|
+
["1", "5", "99"],
|
|
22
|
+
)
|
|
23
|
+
def test_valid_track_number_as_string(self, track_number):
|
|
24
|
+
validate_metadata_for_update({UnifiedMetadataKey.TRACK_NUMBER: track_number})
|
|
25
|
+
|
|
26
|
+
@pytest.mark.parametrize(
|
|
27
|
+
"track_number",
|
|
28
|
+
["5/12", "1/10", "99/100"],
|
|
29
|
+
)
|
|
30
|
+
def test_valid_track_number_with_total(self, track_number):
|
|
31
|
+
validate_metadata_for_update({UnifiedMetadataKey.TRACK_NUMBER: track_number})
|
|
32
|
+
|
|
33
|
+
def test_invalid_track_number_format(self):
|
|
34
|
+
with pytest.raises(InvalidMetadataFieldFormatError):
|
|
35
|
+
validate_metadata_for_update({UnifiedMetadataKey.TRACK_NUMBER: "/12"})
|
|
36
|
+
|
|
37
|
+
def test_track_number_none_is_allowed(self):
|
|
38
|
+
validate_metadata_for_update({UnifiedMetadataKey.TRACK_NUMBER: None})
|
|
39
|
+
|
|
40
|
+
@pytest.mark.parametrize(
|
|
41
|
+
"invalid_value",
|
|
42
|
+
[3.14, []],
|
|
43
|
+
)
|
|
44
|
+
def test_invalid_track_number_type(self, invalid_value):
|
|
45
|
+
with pytest.raises(InvalidMetadataFieldTypeError):
|
|
46
|
+
validate_metadata_for_update({UnifiedMetadataKey.TRACK_NUMBER: invalid_value})
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from audiometa.exceptions import InvalidMetadataFieldTypeError
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@pytest.mark.unit
|
|
7
|
+
class TestExceptionClasses:
|
|
8
|
+
def test_invalid_metadata_field_type_error_attributes(self):
|
|
9
|
+
error = InvalidMetadataFieldTypeError("title", "str", 123)
|
|
10
|
+
assert error.field == "title"
|
|
11
|
+
assert error.expected_type == "str"
|
|
12
|
+
assert error.actual_type == "int"
|
|
13
|
+
assert error.value == 123
|
|
14
|
+
assert isinstance(error, TypeError)
|
|
15
|
+
|
|
16
|
+
def test_invalid_metadata_field_type_error_message_format(self):
|
|
17
|
+
error = InvalidMetadataFieldTypeError("artists", "list[str]", {"key": "value"})
|
|
18
|
+
message = str(error)
|
|
19
|
+
assert "artists" in message
|
|
20
|
+
assert "list[str]" in message
|
|
21
|
+
assert "dict" in message or "actual_type" in message
|
|
22
|
+
assert "'artists'" in message or "artists" in message
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from audiometa import validate_metadata_for_update
|
|
4
|
+
from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@pytest.mark.unit
|
|
8
|
+
class TestValidateMetadata:
|
|
9
|
+
"""Test the integrate validate_metadata_for_update function.
|
|
10
|
+
|
|
11
|
+
This class tests the high-level validation function that integrates multiple validation layers. Specific field
|
|
12
|
+
validation (type, format) is tested separately in metadata_field/ test files.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def test_empty_dict_raises_error(self):
|
|
16
|
+
with pytest.raises(ValueError, match="no metadata fields specified"):
|
|
17
|
+
validate_metadata_for_update({})
|
|
18
|
+
|
|
19
|
+
def test_none_values_allowed(self):
|
|
20
|
+
validate_metadata_for_update({UnifiedMetadataKey.TITLE: None})
|
|
21
|
+
|
|
22
|
+
validate_metadata_for_update(
|
|
23
|
+
{
|
|
24
|
+
UnifiedMetadataKey.TITLE: None,
|
|
25
|
+
UnifiedMetadataKey.ARTISTS: None,
|
|
26
|
+
UnifiedMetadataKey.ALBUM: None,
|
|
27
|
+
}
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
def test_empty_string_allowed(self):
|
|
31
|
+
validate_metadata_for_update({UnifiedMetadataKey.TITLE: ""})
|
|
32
|
+
|
|
33
|
+
def test_empty_list_allowed(self):
|
|
34
|
+
validate_metadata_for_update({UnifiedMetadataKey.ARTISTS: []})
|
|
35
|
+
|
|
36
|
+
def test_list_with_none_values_allowed(self):
|
|
37
|
+
validate_metadata_for_update({UnifiedMetadataKey.ARTISTS: [None, None]})
|
|
38
|
+
validate_metadata_for_update({UnifiedMetadataKey.ARTISTS: [None]})
|
|
39
|
+
validate_metadata_for_update({UnifiedMetadataKey.GENRES_NAMES: [None, None, None]})
|
|
40
|
+
|
|
41
|
+
def test_empty_string_and_empty_list_allowed(self):
|
|
42
|
+
validate_metadata_for_update({UnifiedMetadataKey.TITLE: "", UnifiedMetadataKey.ARTISTS: []})
|
|
43
|
+
|
|
44
|
+
def test_valid_metadata_passes(self):
|
|
45
|
+
validate_metadata_for_update({UnifiedMetadataKey.TITLE: "Song Title"})
|
|
46
|
+
validate_metadata_for_update({UnifiedMetadataKey.ARTISTS: ["Artist 1", "Artist 2"]})
|
|
47
|
+
validate_metadata_for_update({UnifiedMetadataKey.ALBUM: "Album Name"})
|
|
48
|
+
|
|
49
|
+
def test_multiple_valid_fields_passes(self):
|
|
50
|
+
validate_metadata_for_update(
|
|
51
|
+
{
|
|
52
|
+
UnifiedMetadataKey.TITLE: "Song Title",
|
|
53
|
+
UnifiedMetadataKey.ARTISTS: ["Artist"],
|
|
54
|
+
UnifiedMetadataKey.ALBUM: "Album",
|
|
55
|
+
}
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
def test_mixed_none_and_valid_fields_passes(self):
|
|
59
|
+
validate_metadata_for_update(
|
|
60
|
+
{
|
|
61
|
+
UnifiedMetadataKey.TITLE: None,
|
|
62
|
+
UnifiedMetadataKey.ARTISTS: ["Artist"],
|
|
63
|
+
}
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
def test_combined_validation_rating_and_release_date(self):
|
|
67
|
+
validate_metadata_for_update(
|
|
68
|
+
{
|
|
69
|
+
UnifiedMetadataKey.RATING: 50,
|
|
70
|
+
UnifiedMetadataKey.RELEASE_DATE: "2024-01-01",
|
|
71
|
+
},
|
|
72
|
+
normalized_rating_max_value=100,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
def test_combined_validation_with_empty_fields(self):
|
|
76
|
+
validate_metadata_for_update(
|
|
77
|
+
{
|
|
78
|
+
UnifiedMetadataKey.TITLE: "",
|
|
79
|
+
UnifiedMetadataKey.ARTISTS: [],
|
|
80
|
+
UnifiedMetadataKey.RATING: 50,
|
|
81
|
+
},
|
|
82
|
+
normalized_rating_max_value=100,
|
|
83
|
+
)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from audiometa._audio_file import _AudioFile
|
|
4
|
+
from audiometa.manager._rating_supporting.id3v2._Id3v2Manager import _Id3v2Manager as Id3v2Manager
|
|
5
|
+
from audiometa.manager._rating_supporting.riff._RiffManager import _RiffManager as RiffManager
|
|
6
|
+
from audiometa.manager._rating_supporting.vorbis._VorbisManager import _VorbisManager as VorbisManager
|
|
7
|
+
from audiometa.manager.id3v1._Id3v1Manager import _Id3v1Manager as Id3v1Manager
|
|
8
|
+
from audiometa.manager.id3v1.id3v1_raw_metadata_key import Id3v1RawMetadataKey
|
|
9
|
+
from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
|
|
10
|
+
from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@pytest.mark.unit
|
|
14
|
+
class TestMetadataFormatManagersWriteAndRead:
|
|
15
|
+
def test_id3v1_manager_write_and_read(self):
|
|
16
|
+
with temp_file_with_metadata({}, "mp3") as test_file:
|
|
17
|
+
audio_file = _AudioFile(test_file)
|
|
18
|
+
manager = Id3v1Manager(audio_file)
|
|
19
|
+
|
|
20
|
+
manager.update_metadata(
|
|
21
|
+
{UnifiedMetadataKey.TITLE: "Test Title", UnifiedMetadataKey.ARTISTS: ["Test Artist"]}
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
raw_metadata = manager._extract_mutagen_metadata()
|
|
25
|
+
|
|
26
|
+
assert raw_metadata.tags.get(Id3v1RawMetadataKey.TITLE) == ["Test Title"]
|
|
27
|
+
assert raw_metadata.tags.get(Id3v1RawMetadataKey.ARTISTS_NAMES_STR) == ["Test Artist"]
|
|
28
|
+
|
|
29
|
+
def test_id3v2_manager_write_and_read(self):
|
|
30
|
+
with temp_file_with_metadata({}, "mp3") as test_file:
|
|
31
|
+
audio_file = _AudioFile(test_file)
|
|
32
|
+
manager = Id3v2Manager(audio_file)
|
|
33
|
+
|
|
34
|
+
manager.update_metadata(
|
|
35
|
+
{UnifiedMetadataKey.TITLE: "Test Title", UnifiedMetadataKey.ARTISTS: ["Test Artist"]}
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
raw_metadata = manager._extract_mutagen_metadata()
|
|
39
|
+
|
|
40
|
+
assert Id3v2Manager.Id3TextFrame.TITLE in raw_metadata
|
|
41
|
+
assert str(raw_metadata[Id3v2Manager.Id3TextFrame.TITLE][0]) == "Test Title"
|
|
42
|
+
assert Id3v2Manager.Id3TextFrame.ARTISTS in raw_metadata
|
|
43
|
+
artists_text = str(raw_metadata[Id3v2Manager.Id3TextFrame.ARTISTS][0])
|
|
44
|
+
assert "Test Artist" in artists_text
|
|
45
|
+
|
|
46
|
+
def test_riff_manager_write_and_read(self):
|
|
47
|
+
with temp_file_with_metadata({}, "wav") as test_file:
|
|
48
|
+
audio_file = _AudioFile(test_file)
|
|
49
|
+
manager = RiffManager(audio_file)
|
|
50
|
+
|
|
51
|
+
manager.update_metadata(
|
|
52
|
+
{UnifiedMetadataKey.TITLE: "Test Title", UnifiedMetadataKey.ARTISTS: ["Test Artist"]}
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
raw_metadata = manager._extract_mutagen_metadata()
|
|
56
|
+
|
|
57
|
+
assert hasattr(raw_metadata, "info")
|
|
58
|
+
info_tags = getattr(raw_metadata, "info", {})
|
|
59
|
+
assert info_tags.get(RiffManager.RiffTagKey.TITLE) == ["Test Title"]
|
|
60
|
+
assert info_tags.get(RiffManager.RiffTagKey.ARTIST) == ["Test Artist"]
|
|
61
|
+
|
|
62
|
+
def test_vorbis_manager_write_and_read(self):
|
|
63
|
+
with temp_file_with_metadata({}, "flac") as test_file:
|
|
64
|
+
audio_file = _AudioFile(test_file)
|
|
65
|
+
manager = VorbisManager(audio_file)
|
|
66
|
+
|
|
67
|
+
manager.update_metadata(
|
|
68
|
+
{UnifiedMetadataKey.TITLE: "Test Title", UnifiedMetadataKey.ARTISTS: ["Test Artist"]}
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
raw_metadata = manager._extract_mutagen_metadata()
|
|
72
|
+
|
|
73
|
+
assert raw_metadata.get(VorbisManager.VorbisKey.TITLE) == ["Test Title"]
|
|
74
|
+
assert raw_metadata.get(VorbisManager.VorbisKey.ARTIST) == ["Test Artist"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Utility classes and functions for audio metadata processing."""
|