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 @@
|
|
|
1
|
+
"""Metadata manager classes for different audio formats."""
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
from abc import abstractmethod
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
|
+
|
|
4
|
+
from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from ..._audio_file import _AudioFile
|
|
8
|
+
from ...exceptions import ConfigurationError, InvalidRatingValueError, MetadataFieldNotSupportedByMetadataFormatError
|
|
9
|
+
from ...utils.rating_profiles import RatingReadProfile, RatingWriteProfile
|
|
10
|
+
from ...utils.types import RawMetadataDict, RawMetadataKey, UnifiedMetadata, UnifiedMetadataValue
|
|
11
|
+
from .._MetadataManager import _MetadataManager
|
|
12
|
+
|
|
13
|
+
# Maximum star rating index (0-10, where 0=0 stars, 1=0.5 stars, 2=1 star, ..., 10=5 stars)
|
|
14
|
+
MAX_STAR_RATING_INDEX = 10
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class _RatingSupportingMetadataManager(_MetadataManager):
|
|
18
|
+
TRAKTOR_RATING_TAG_MAIL = "traktor@native-instruments.de"
|
|
19
|
+
|
|
20
|
+
normalized_rating_max_value: int | None
|
|
21
|
+
rating_write_profile: RatingWriteProfile
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
audio_file: "_AudioFile",
|
|
26
|
+
metadata_keys_direct_map_read: dict[UnifiedMetadataKey, RawMetadataKey | None],
|
|
27
|
+
metadata_keys_direct_map_write: dict[UnifiedMetadataKey, RawMetadataKey | None],
|
|
28
|
+
rating_write_profile: RatingWriteProfile,
|
|
29
|
+
normalized_rating_max_value: int | None,
|
|
30
|
+
update_using_mutagen_metadata: bool = True,
|
|
31
|
+
):
|
|
32
|
+
self.rating_write_profile = rating_write_profile
|
|
33
|
+
self.normalized_rating_max_value = normalized_rating_max_value
|
|
34
|
+
super().__init__(
|
|
35
|
+
audio_file=audio_file,
|
|
36
|
+
update_using_mutagen_metadata=update_using_mutagen_metadata,
|
|
37
|
+
metadata_keys_direct_map_read=metadata_keys_direct_map_read,
|
|
38
|
+
metadata_keys_direct_map_write=metadata_keys_direct_map_write,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
def _get_formatted_metadata_format_name(self) -> str:
|
|
42
|
+
"""Get the formatted metadata format name from the class name.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
The formatted format name (e.g., 'RIFF', 'ID3v2', 'Vorbis')
|
|
46
|
+
"""
|
|
47
|
+
metadata_format_name = self.__class__.__name__.replace("Manager", "").lstrip("_").upper()
|
|
48
|
+
if metadata_format_name == "RIFF":
|
|
49
|
+
return "RIFF"
|
|
50
|
+
if metadata_format_name == "ID3V2":
|
|
51
|
+
return "ID3v2"
|
|
52
|
+
if metadata_format_name == "VORBIS":
|
|
53
|
+
return "Vorbis"
|
|
54
|
+
return metadata_format_name
|
|
55
|
+
|
|
56
|
+
@staticmethod
|
|
57
|
+
def validate_rating_value(rating_value: float, normalized_rating_max_value: int | None) -> None:
|
|
58
|
+
"""Validate rating value based on normalized_rating_max_value.
|
|
59
|
+
|
|
60
|
+
Rules:
|
|
61
|
+
- When normalized_rating_max_value is None: value must be >= 0 (any non-negative number is allowed)
|
|
62
|
+
- When normalized_rating_max_value is provided: value must be between 0 and normalized_rating_max_value
|
|
63
|
+
and when converted to output values (value/max * 100 or value/max * 255), at least one must exist
|
|
64
|
+
in a writing profile (BASE_100_PROPORTIONAL or BASE_255_NON_PROPORTIONAL)
|
|
65
|
+
|
|
66
|
+
Half-star ratings (e.g., 1.5, 2.5, 3.5) are supported to be consistent with classic star rating
|
|
67
|
+
systems that allow half-star increments.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
rating_value: The rating value to validate (int or float). Supports half-star ratings (e.g., 1.5, 2.5).
|
|
71
|
+
normalized_rating_max_value: Maximum value for rating normalization, or None for raw values
|
|
72
|
+
|
|
73
|
+
Raises InvalidRatingValueError if validation fails.
|
|
74
|
+
"""
|
|
75
|
+
if normalized_rating_max_value is None:
|
|
76
|
+
# Rating is written as-is - must be non-negative
|
|
77
|
+
if rating_value < 0:
|
|
78
|
+
msg = f"Rating value {rating_value} is invalid. Rating values must be non-negative (>= 0)."
|
|
79
|
+
raise InvalidRatingValueError(msg)
|
|
80
|
+
else:
|
|
81
|
+
# Value is normalized - must be non-negative and within max
|
|
82
|
+
if rating_value < 0:
|
|
83
|
+
msg = f"Rating value {rating_value} is invalid. Rating values must be non-negative (>= 0)."
|
|
84
|
+
raise InvalidRatingValueError(msg)
|
|
85
|
+
if rating_value > normalized_rating_max_value:
|
|
86
|
+
msg = (
|
|
87
|
+
f"Rating value {rating_value} is out of range. "
|
|
88
|
+
f"Value must be between 0 and {normalized_rating_max_value} (inclusive)."
|
|
89
|
+
)
|
|
90
|
+
raise InvalidRatingValueError(msg)
|
|
91
|
+
# Convert normalized rating to star rating index (0-10, where 0=0 stars, 1=0.5 stars, 2=1 star, etc.)
|
|
92
|
+
# Use round() to properly handle half-star ratings (consistent with classic star rating systems)
|
|
93
|
+
star_rating_index = round((rating_value * 10) / normalized_rating_max_value)
|
|
94
|
+
if star_rating_index < 0 or star_rating_index > MAX_STAR_RATING_INDEX:
|
|
95
|
+
msg = (
|
|
96
|
+
f"Rating value {rating_value} results in invalid star rating index {star_rating_index}. "
|
|
97
|
+
f"Index must be between 0 and 10."
|
|
98
|
+
)
|
|
99
|
+
raise InvalidRatingValueError(msg)
|
|
100
|
+
|
|
101
|
+
# Check if profile values at this star rating index exist in writing profiles
|
|
102
|
+
profile_value_100 = RatingWriteProfile.BASE_100_PROPORTIONAL[star_rating_index]
|
|
103
|
+
profile_value_255 = RatingWriteProfile.BASE_255_NON_PROPORTIONAL[star_rating_index]
|
|
104
|
+
|
|
105
|
+
# At least one profile must have a valid value at this index
|
|
106
|
+
if profile_value_100 is None and profile_value_255 is None:
|
|
107
|
+
msg = (
|
|
108
|
+
f"Rating value {rating_value} is not valid for max value {normalized_rating_max_value}. "
|
|
109
|
+
f"Star rating index {star_rating_index} does not exist in any supported writing profile."
|
|
110
|
+
)
|
|
111
|
+
raise InvalidRatingValueError(msg)
|
|
112
|
+
|
|
113
|
+
@abstractmethod
|
|
114
|
+
def _get_raw_rating_by_traktor_or_not(self, raw_clean_metadata: RawMetadataDict) -> tuple[int | None, bool]:
|
|
115
|
+
"""Return True if the rating is from Traktor, False otherwise."""
|
|
116
|
+
raise NotImplementedError
|
|
117
|
+
|
|
118
|
+
@abstractmethod
|
|
119
|
+
def _get_undirectly_mapped_metadata_value_other_than_rating_from_raw_clean_metadata(
|
|
120
|
+
self, raw_clean_metadata: RawMetadataDict, unified_metadata_key: UnifiedMetadataKey
|
|
121
|
+
) -> UnifiedMetadataValue:
|
|
122
|
+
raise NotImplementedError
|
|
123
|
+
|
|
124
|
+
def _get_undirectly_mapped_metadata_value_from_raw_clean_metadata(
|
|
125
|
+
self, raw_clean_metadata_uppercase_keys: RawMetadataDict, unified_metadata_key: UnifiedMetadataKey
|
|
126
|
+
) -> UnifiedMetadataValue | None:
|
|
127
|
+
if unified_metadata_key == UnifiedMetadataKey.RATING:
|
|
128
|
+
return self._get_potentially_normalized_rating_from_raw(raw_clean_metadata_uppercase_keys)
|
|
129
|
+
return self._get_undirectly_mapped_metadata_value_other_than_rating_from_raw_clean_metadata(
|
|
130
|
+
raw_clean_metadata=raw_clean_metadata_uppercase_keys, unified_metadata_key=unified_metadata_key
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
def _get_potentially_normalized_rating_from_raw(self, raw_clean_metadata: RawMetadataDict) -> int | None:
|
|
134
|
+
file_rating, is_rating_from_traktor = self._get_raw_rating_by_traktor_or_not(raw_clean_metadata)
|
|
135
|
+
if file_rating is None:
|
|
136
|
+
return None
|
|
137
|
+
if self.normalized_rating_max_value:
|
|
138
|
+
if file_rating == 0 and is_rating_from_traktor:
|
|
139
|
+
return None
|
|
140
|
+
for star_rating_base_10 in range(11):
|
|
141
|
+
if file_rating in [
|
|
142
|
+
RatingReadProfile.BASE_255_PROPORTIONAL_TRAKTOR[star_rating_base_10],
|
|
143
|
+
RatingReadProfile.BASE_255_NON_PROPORTIONAL[star_rating_base_10],
|
|
144
|
+
RatingReadProfile.BASE_100_PROPORTIONAL[star_rating_base_10],
|
|
145
|
+
]:
|
|
146
|
+
return int(star_rating_base_10 * self.normalized_rating_max_value / 10)
|
|
147
|
+
return None
|
|
148
|
+
return file_rating
|
|
149
|
+
|
|
150
|
+
def _convert_normalized_rating_to_file_rating(self, normalized_rating: float) -> int | None:
|
|
151
|
+
if not self.normalized_rating_max_value:
|
|
152
|
+
msg = "normalized_rating_max_value must be set."
|
|
153
|
+
raise ConfigurationError(msg)
|
|
154
|
+
|
|
155
|
+
# Convert normalized rating to star rating index (0-10, where 0=0 stars, 1=0.5 stars, 2=1 star, etc.)
|
|
156
|
+
# Use round() to properly handle half-star ratings (consistent with classic star rating systems)
|
|
157
|
+
star_rating_base_10 = round((normalized_rating * 10) / self.normalized_rating_max_value)
|
|
158
|
+
result = self.rating_write_profile[star_rating_base_10]
|
|
159
|
+
return int(result) if result is not None else 0
|
|
160
|
+
|
|
161
|
+
def _validate_rating_in_unified_metadata(self, unified_metadata: UnifiedMetadata) -> None:
|
|
162
|
+
"""Validate rating value in unified metadata if present.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
unified_metadata: The metadata dictionary to validate
|
|
166
|
+
|
|
167
|
+
Raises:
|
|
168
|
+
InvalidRatingValueError: If rating value is invalid
|
|
169
|
+
"""
|
|
170
|
+
if UnifiedMetadataKey.RATING in unified_metadata:
|
|
171
|
+
value = unified_metadata[UnifiedMetadataKey.RATING]
|
|
172
|
+
if value is not None:
|
|
173
|
+
if isinstance(value, int | float):
|
|
174
|
+
# In raw mode (no normalization), only accept floats that can be parsed to int
|
|
175
|
+
# This allows the library to accept values like 196.0 as 196
|
|
176
|
+
if self.normalized_rating_max_value is None and isinstance(value, float):
|
|
177
|
+
if value.is_integer():
|
|
178
|
+
value = int(value)
|
|
179
|
+
unified_metadata[UnifiedMetadataKey.RATING] = value
|
|
180
|
+
else:
|
|
181
|
+
msg = (
|
|
182
|
+
f"Rating value {value} is invalid. In raw mode, float values must be whole numbers "
|
|
183
|
+
f"(e.g., 196.0). Half-star values like {value} require normalization."
|
|
184
|
+
)
|
|
185
|
+
raise InvalidRatingValueError(msg)
|
|
186
|
+
self.validate_rating_value(value, self.normalized_rating_max_value)
|
|
187
|
+
else:
|
|
188
|
+
msg = f"Rating value must be numeric, got {type(value).__name__}"
|
|
189
|
+
raise InvalidRatingValueError(msg)
|
|
190
|
+
|
|
191
|
+
def _validate_and_process_rating(self, unified_metadata: UnifiedMetadata) -> None:
|
|
192
|
+
"""Validate and process rating in unified metadata if present.
|
|
193
|
+
|
|
194
|
+
This method handles:
|
|
195
|
+
- Checking if rating is supported by the format
|
|
196
|
+
- Validating the rating value
|
|
197
|
+
- Converting normalized ratings to file ratings (when applicable)
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
unified_metadata: The metadata dictionary to validate and process
|
|
201
|
+
|
|
202
|
+
Raises:
|
|
203
|
+
MetadataFieldNotSupportedByMetadataFormatError: If rating is not supported by the format
|
|
204
|
+
InvalidRatingValueError: If rating value is invalid
|
|
205
|
+
"""
|
|
206
|
+
if UnifiedMetadataKey.RATING not in unified_metadata:
|
|
207
|
+
return
|
|
208
|
+
|
|
209
|
+
# Check if rating is supported by this format first
|
|
210
|
+
if (
|
|
211
|
+
not self.metadata_keys_direct_map_write
|
|
212
|
+
or UnifiedMetadataKey.RATING not in self.metadata_keys_direct_map_write
|
|
213
|
+
):
|
|
214
|
+
metadata_format_name = self._get_formatted_metadata_format_name()
|
|
215
|
+
msg = f"{UnifiedMetadataKey.RATING} metadata not supported by {metadata_format_name} format"
|
|
216
|
+
raise MetadataFieldNotSupportedByMetadataFormatError(msg)
|
|
217
|
+
|
|
218
|
+
# Validate rating value before processing
|
|
219
|
+
self._validate_rating_in_unified_metadata(unified_metadata)
|
|
220
|
+
|
|
221
|
+
# If rating is mapped to None, it means it's handled indirectly by the manager
|
|
222
|
+
# We should let the manager handle it in its own way
|
|
223
|
+
if (
|
|
224
|
+
self.metadata_keys_direct_map_write[UnifiedMetadataKey.RATING] is not None
|
|
225
|
+
and self.update_using_mutagen_metadata
|
|
226
|
+
):
|
|
227
|
+
# Only process rating if it's handled directly by the base class
|
|
228
|
+
# (i.e., when using mutagen-based approach)
|
|
229
|
+
value: int | float | None = unified_metadata[UnifiedMetadataKey.RATING] # type: ignore[assignment]
|
|
230
|
+
if value is not None:
|
|
231
|
+
if self.normalized_rating_max_value is None:
|
|
232
|
+
# When no normalization, write value as-is (already validated above - must be int, floats rejected)
|
|
233
|
+
pass
|
|
234
|
+
else:
|
|
235
|
+
try:
|
|
236
|
+
# Preserve float values to support half-star ratings (consistent with classic star rating
|
|
237
|
+
# systems)
|
|
238
|
+
normalized_rating = float(value)
|
|
239
|
+
file_rating = self._convert_normalized_rating_to_file_rating(
|
|
240
|
+
normalized_rating=normalized_rating
|
|
241
|
+
)
|
|
242
|
+
unified_metadata[UnifiedMetadataKey.RATING] = file_rating
|
|
243
|
+
except (TypeError, ValueError) as e:
|
|
244
|
+
msg = f"Invalid rating value: {value}. Expected a numeric value."
|
|
245
|
+
raise InvalidRatingValueError(msg) from e
|
|
246
|
+
# If value is None, let the individual managers handle the removal
|
|
247
|
+
|
|
248
|
+
def update_metadata(self, unified_metadata: UnifiedMetadata) -> None:
|
|
249
|
+
self._validate_and_process_rating(unified_metadata)
|
|
250
|
+
super().update_metadata(unified_metadata)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Rating-supporting metadata managers."""
|