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,207 @@
|
|
|
1
|
+
"""Tests for get_full_metadata function options (include_headers, include_technical)."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from audiometa import get_full_metadata
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@pytest.mark.integration
|
|
11
|
+
class TestGetFullMetadataOptions:
|
|
12
|
+
def test_get_full_metadata_exclude_headers(self, sample_mp3_file: Path):
|
|
13
|
+
result = get_full_metadata(sample_mp3_file, include_headers=False)
|
|
14
|
+
|
|
15
|
+
# Should include basic structure
|
|
16
|
+
assert "unified_metadata" in result
|
|
17
|
+
assert "technical_info" in result
|
|
18
|
+
assert "metadata_format" in result
|
|
19
|
+
assert "format_priorities" in result
|
|
20
|
+
|
|
21
|
+
# Headers should be empty dict when excluded
|
|
22
|
+
assert "headers" in result
|
|
23
|
+
assert result["headers"] == {}
|
|
24
|
+
|
|
25
|
+
# Raw metadata should be empty dict when excluded
|
|
26
|
+
assert "raw_metadata" in result
|
|
27
|
+
assert result["raw_metadata"] == {}
|
|
28
|
+
|
|
29
|
+
# Verify technical info is still included
|
|
30
|
+
assert result["technical_info"] != {}
|
|
31
|
+
assert "duration_seconds" in result["technical_info"]
|
|
32
|
+
assert "bitrate_bps" in result["technical_info"]
|
|
33
|
+
|
|
34
|
+
# Verify unified metadata is still included
|
|
35
|
+
assert isinstance(result["unified_metadata"], dict)
|
|
36
|
+
|
|
37
|
+
# Verify format metadata is still included
|
|
38
|
+
assert isinstance(result["metadata_format"], dict)
|
|
39
|
+
assert "id3v2" in result["metadata_format"]
|
|
40
|
+
assert "id3v1" in result["metadata_format"]
|
|
41
|
+
|
|
42
|
+
def test_get_full_metadata_exclude_technical(self, sample_mp3_file: Path):
|
|
43
|
+
result = get_full_metadata(sample_mp3_file, include_technical=False)
|
|
44
|
+
|
|
45
|
+
# Should include basic structure
|
|
46
|
+
assert "unified_metadata" in result
|
|
47
|
+
assert "metadata_format" in result
|
|
48
|
+
assert "headers" in result
|
|
49
|
+
assert "raw_metadata" in result
|
|
50
|
+
assert "format_priorities" in result
|
|
51
|
+
|
|
52
|
+
# Technical info should be empty dict when excluded
|
|
53
|
+
assert "technical_info" in result
|
|
54
|
+
assert result["technical_info"] == {}
|
|
55
|
+
|
|
56
|
+
# Verify headers are still included
|
|
57
|
+
assert result["headers"] != {}
|
|
58
|
+
assert "id3v2" in result["headers"]
|
|
59
|
+
assert "id3v1" in result["headers"]
|
|
60
|
+
|
|
61
|
+
# Verify raw metadata is still included
|
|
62
|
+
assert result["raw_metadata"] != {}
|
|
63
|
+
assert "id3v2" in result["raw_metadata"]
|
|
64
|
+
assert "id3v1" in result["raw_metadata"]
|
|
65
|
+
|
|
66
|
+
# Verify unified metadata is still included
|
|
67
|
+
assert isinstance(result["unified_metadata"], dict)
|
|
68
|
+
|
|
69
|
+
# Verify format metadata is still included
|
|
70
|
+
assert isinstance(result["metadata_format"], dict)
|
|
71
|
+
|
|
72
|
+
def test_get_full_metadata_exclude_both_headers_and_technical(self, sample_mp3_file: Path):
|
|
73
|
+
result = get_full_metadata(sample_mp3_file, include_headers=False, include_technical=False)
|
|
74
|
+
|
|
75
|
+
# Should include basic structure
|
|
76
|
+
assert "unified_metadata" in result
|
|
77
|
+
assert "metadata_format" in result
|
|
78
|
+
assert "format_priorities" in result
|
|
79
|
+
|
|
80
|
+
# Headers should be empty dict when excluded
|
|
81
|
+
assert "headers" in result
|
|
82
|
+
assert result["headers"] == {}
|
|
83
|
+
|
|
84
|
+
# Raw metadata should be empty dict when excluded
|
|
85
|
+
assert "raw_metadata" in result
|
|
86
|
+
assert result["raw_metadata"] == {}
|
|
87
|
+
|
|
88
|
+
# Technical info should be empty dict when excluded
|
|
89
|
+
assert "technical_info" in result
|
|
90
|
+
assert result["technical_info"] == {}
|
|
91
|
+
|
|
92
|
+
# Verify unified metadata is still included
|
|
93
|
+
assert isinstance(result["unified_metadata"], dict)
|
|
94
|
+
|
|
95
|
+
# Verify format metadata is still included
|
|
96
|
+
assert isinstance(result["metadata_format"], dict)
|
|
97
|
+
|
|
98
|
+
def test_get_full_metadata_exclude_headers_flac(self, sample_flac_file: Path):
|
|
99
|
+
result = get_full_metadata(sample_flac_file, include_headers=False)
|
|
100
|
+
|
|
101
|
+
# Should include basic structure
|
|
102
|
+
assert "unified_metadata" in result
|
|
103
|
+
assert "technical_info" in result
|
|
104
|
+
assert "metadata_format" in result
|
|
105
|
+
assert "format_priorities" in result
|
|
106
|
+
|
|
107
|
+
# Headers should be empty dict when excluded
|
|
108
|
+
assert "headers" in result
|
|
109
|
+
assert result["headers"] == {}
|
|
110
|
+
|
|
111
|
+
# Raw metadata should be empty dict when excluded
|
|
112
|
+
assert "raw_metadata" in result
|
|
113
|
+
assert result["raw_metadata"] == {}
|
|
114
|
+
|
|
115
|
+
# Verify technical info is still included
|
|
116
|
+
assert result["technical_info"] != {}
|
|
117
|
+
assert "is_flac_md5_valid" in result["technical_info"]
|
|
118
|
+
|
|
119
|
+
# Verify format metadata is still included
|
|
120
|
+
assert isinstance(result["metadata_format"], dict)
|
|
121
|
+
assert "vorbis" in result["metadata_format"]
|
|
122
|
+
|
|
123
|
+
def test_get_full_metadata_exclude_technical_flac(self, sample_flac_file: Path):
|
|
124
|
+
result = get_full_metadata(sample_flac_file, include_technical=False)
|
|
125
|
+
|
|
126
|
+
# Should include basic structure
|
|
127
|
+
assert "unified_metadata" in result
|
|
128
|
+
assert "metadata_format" in result
|
|
129
|
+
assert "headers" in result
|
|
130
|
+
assert "raw_metadata" in result
|
|
131
|
+
assert "format_priorities" in result
|
|
132
|
+
|
|
133
|
+
# Technical info should be empty dict when excluded
|
|
134
|
+
assert "technical_info" in result
|
|
135
|
+
assert result["technical_info"] == {}
|
|
136
|
+
|
|
137
|
+
# Verify headers are still included
|
|
138
|
+
assert result["headers"] != {}
|
|
139
|
+
assert "vorbis" in result["headers"]
|
|
140
|
+
|
|
141
|
+
# Verify raw metadata is still included
|
|
142
|
+
assert result["raw_metadata"] != {}
|
|
143
|
+
assert "vorbis" in result["raw_metadata"]
|
|
144
|
+
|
|
145
|
+
def test_get_full_metadata_exclude_headers_wav(self, sample_wav_file: Path):
|
|
146
|
+
result = get_full_metadata(sample_wav_file, include_headers=False)
|
|
147
|
+
|
|
148
|
+
# Should include basic structure
|
|
149
|
+
assert "unified_metadata" in result
|
|
150
|
+
assert "technical_info" in result
|
|
151
|
+
assert "metadata_format" in result
|
|
152
|
+
assert "format_priorities" in result
|
|
153
|
+
|
|
154
|
+
# Headers should be empty dict when excluded
|
|
155
|
+
assert "headers" in result
|
|
156
|
+
assert result["headers"] == {}
|
|
157
|
+
|
|
158
|
+
# Raw metadata should be empty dict when excluded
|
|
159
|
+
assert "raw_metadata" in result
|
|
160
|
+
assert result["raw_metadata"] == {}
|
|
161
|
+
|
|
162
|
+
# Verify technical info is still included
|
|
163
|
+
assert result["technical_info"] != {}
|
|
164
|
+
assert result["technical_info"]["file_extension"] == ".wav"
|
|
165
|
+
|
|
166
|
+
# Verify format metadata is still included
|
|
167
|
+
assert isinstance(result["metadata_format"], dict)
|
|
168
|
+
assert "riff" in result["metadata_format"]
|
|
169
|
+
|
|
170
|
+
def test_get_full_metadata_exclude_technical_wav(self, sample_wav_file: Path):
|
|
171
|
+
result = get_full_metadata(sample_wav_file, include_technical=False)
|
|
172
|
+
|
|
173
|
+
# Should include basic structure
|
|
174
|
+
assert "unified_metadata" in result
|
|
175
|
+
assert "metadata_format" in result
|
|
176
|
+
assert "headers" in result
|
|
177
|
+
assert "raw_metadata" in result
|
|
178
|
+
assert "format_priorities" in result
|
|
179
|
+
|
|
180
|
+
# Technical info should be empty dict when excluded
|
|
181
|
+
assert "technical_info" in result
|
|
182
|
+
assert result["technical_info"] == {}
|
|
183
|
+
|
|
184
|
+
# Verify headers are still included
|
|
185
|
+
assert result["headers"] != {}
|
|
186
|
+
assert "riff" in result["headers"]
|
|
187
|
+
|
|
188
|
+
# Verify raw metadata is still included
|
|
189
|
+
assert result["raw_metadata"] != {}
|
|
190
|
+
assert "riff" in result["raw_metadata"]
|
|
191
|
+
|
|
192
|
+
def test_get_full_metadata_exclude_options(self, sample_mp3_file: Path):
|
|
193
|
+
result = get_full_metadata(sample_mp3_file, include_headers=False, include_technical=False)
|
|
194
|
+
|
|
195
|
+
# Should work the same as with path
|
|
196
|
+
assert "unified_metadata" in result
|
|
197
|
+
assert "metadata_format" in result
|
|
198
|
+
assert "format_priorities" in result
|
|
199
|
+
|
|
200
|
+
# Excluded sections should be empty
|
|
201
|
+
assert result["headers"] == {}
|
|
202
|
+
assert result["raw_metadata"] == {}
|
|
203
|
+
assert result["technical_info"] == {}
|
|
204
|
+
|
|
205
|
+
# Included sections should have data
|
|
206
|
+
assert isinstance(result["unified_metadata"], dict)
|
|
207
|
+
assert isinstance(result["metadata_format"], dict)
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""Performance tests for get_full_metadata function."""
|
|
2
|
+
|
|
3
|
+
import threading
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
from audiometa import get_full_metadata
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@pytest.mark.integration
|
|
12
|
+
class TestGetFullMetadataPerformance:
|
|
13
|
+
def test_get_full_metadata_performance_with_headers_disabled(self, sample_mp3_file: Path):
|
|
14
|
+
result = get_full_metadata(sample_mp3_file, include_headers=False)
|
|
15
|
+
|
|
16
|
+
# Should still work correctly
|
|
17
|
+
assert "unified_metadata" in result
|
|
18
|
+
assert "technical_info" in result
|
|
19
|
+
assert "metadata_format" in result
|
|
20
|
+
|
|
21
|
+
# Headers should be minimal
|
|
22
|
+
headers = result["headers"]
|
|
23
|
+
for _metadata_format_name, header_info in headers.items():
|
|
24
|
+
# Should have basic structure but minimal data
|
|
25
|
+
assert "present" in header_info
|
|
26
|
+
|
|
27
|
+
def test_get_full_metadata_performance_with_technical_disabled(self, sample_mp3_file: Path):
|
|
28
|
+
result = get_full_metadata(sample_mp3_file, include_technical=False)
|
|
29
|
+
|
|
30
|
+
# Should still work correctly
|
|
31
|
+
assert "unified_metadata" in result
|
|
32
|
+
assert "metadata_format" in result
|
|
33
|
+
assert "headers" in result
|
|
34
|
+
|
|
35
|
+
# Technical info should be minimal
|
|
36
|
+
tech_info = result["technical_info"]
|
|
37
|
+
assert isinstance(tech_info, dict)
|
|
38
|
+
|
|
39
|
+
def test_get_full_metadata_memory_usage(self, sample_mp3_file: Path):
|
|
40
|
+
# This is more of a smoke test to ensure no obvious memory leaks
|
|
41
|
+
for _ in range(10):
|
|
42
|
+
result = get_full_metadata(sample_mp3_file)
|
|
43
|
+
|
|
44
|
+
# Should complete successfully each time
|
|
45
|
+
assert "unified_metadata" in result
|
|
46
|
+
assert "technical_info" in result
|
|
47
|
+
|
|
48
|
+
# Clear result to help with memory management
|
|
49
|
+
del result
|
|
50
|
+
|
|
51
|
+
def test_get_full_metadata_concurrent_access(self, sample_mp3_file: Path):
|
|
52
|
+
results = []
|
|
53
|
+
errors = []
|
|
54
|
+
|
|
55
|
+
def get_metadata():
|
|
56
|
+
try:
|
|
57
|
+
result = get_full_metadata(sample_mp3_file)
|
|
58
|
+
results.append(result)
|
|
59
|
+
except Exception as e:
|
|
60
|
+
errors.append(e)
|
|
61
|
+
|
|
62
|
+
# Create multiple threads accessing the same file
|
|
63
|
+
threads = []
|
|
64
|
+
for _ in range(5):
|
|
65
|
+
thread = threading.Thread(target=get_metadata)
|
|
66
|
+
threads.append(thread)
|
|
67
|
+
thread.start()
|
|
68
|
+
|
|
69
|
+
# Wait for all threads to complete
|
|
70
|
+
for thread in threads:
|
|
71
|
+
thread.join()
|
|
72
|
+
|
|
73
|
+
# Should have 5 successful results
|
|
74
|
+
assert len(results) == 5
|
|
75
|
+
assert len(errors) == 0
|
|
76
|
+
|
|
77
|
+
# All results should be identical
|
|
78
|
+
first_result = results[0]
|
|
79
|
+
for result in results[1:]:
|
|
80
|
+
assert result["format_priorities"] == first_result["format_priorities"]
|
|
81
|
+
assert result["technical_info"]["file_size_bytes"] == first_result["technical_info"]["file_size_bytes"]
|
|
82
|
+
|
|
83
|
+
def test_get_full_metadata_performance_optimization(self, sample_mp3_file: Path):
|
|
84
|
+
"""Test that performance optimization flags work correctly."""
|
|
85
|
+
# Test with minimal data
|
|
86
|
+
result_minimal = get_full_metadata(sample_mp3_file, include_headers=False, include_technical=False)
|
|
87
|
+
|
|
88
|
+
# Should still have basic structure
|
|
89
|
+
assert "unified_metadata" in result_minimal
|
|
90
|
+
assert "metadata_format" in result_minimal
|
|
91
|
+
assert "format_priorities" in result_minimal
|
|
92
|
+
|
|
93
|
+
# Headers and technical info should be minimal
|
|
94
|
+
assert "headers" in result_minimal
|
|
95
|
+
assert "technical_info" in result_minimal
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""Integration tests for RIFF bext chunk extraction via get_full_metadata.
|
|
2
|
+
|
|
3
|
+
This file is kept separate from other format tests because bext is a sub-feature
|
|
4
|
+
(BWF extension) of RIFF, not a separate metadata format. It tests:
|
|
5
|
+
|
|
6
|
+
- bext-specific chunk extraction via get_full_metadata
|
|
7
|
+
- bext chunk structure in chunk_structure
|
|
8
|
+
- Consistency between manager and get_full_metadata for bext
|
|
9
|
+
- Edge cases specific to bext (e.g., extraction without INFO metadata)
|
|
10
|
+
|
|
11
|
+
Unlike other metadata formats (ID3v2, ID3v1, Vorbis) which are tested in general
|
|
12
|
+
structure/format files, bext warrants dedicated tests due to its unique integration
|
|
13
|
+
behavior and specific edge cases.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
import pytest
|
|
19
|
+
|
|
20
|
+
from audiometa import get_full_metadata
|
|
21
|
+
from audiometa.test.helpers.riff.riff_metadata_setter import RIFFMetadataSetter
|
|
22
|
+
from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@pytest.mark.integration
|
|
26
|
+
class TestRiffBextChunkStructure:
|
|
27
|
+
"""Test cases for RIFF bext chunk extraction via get_full_metadata."""
|
|
28
|
+
|
|
29
|
+
def test_bext_chunk_structure_present_in_get_full_metadata(self):
|
|
30
|
+
"""Test that bext chunk appears in chunk_structure when present."""
|
|
31
|
+
with temp_file_with_metadata({}, "wav") as test_file:
|
|
32
|
+
RIFFMetadataSetter.set_bext_description(test_file, "Test Description")
|
|
33
|
+
RIFFMetadataSetter.set_bext_originator(test_file, "Test Originator")
|
|
34
|
+
|
|
35
|
+
result = get_full_metadata(test_file)
|
|
36
|
+
|
|
37
|
+
riff_raw = result.get("raw_metadata", {}).get("riff", {})
|
|
38
|
+
assert "chunk_structure" in riff_raw
|
|
39
|
+
assert "bext" in riff_raw["chunk_structure"]
|
|
40
|
+
|
|
41
|
+
bext_data = riff_raw["chunk_structure"]["bext"]
|
|
42
|
+
assert bext_data["Description"] == "Test Description"
|
|
43
|
+
assert bext_data["Originator"] == "Test Originator"
|
|
44
|
+
|
|
45
|
+
def test_bext_chunk_structure_with_all_fields(self):
|
|
46
|
+
"""Test bext chunk extraction with all fields via get_full_metadata."""
|
|
47
|
+
with temp_file_with_metadata({}, "wav") as test_file:
|
|
48
|
+
RIFFMetadataSetter.set_bext_metadata(
|
|
49
|
+
test_file,
|
|
50
|
+
{
|
|
51
|
+
"Description": "Test Description",
|
|
52
|
+
"Originator": "Test Originator",
|
|
53
|
+
"OriginatorReference": "REF-12345",
|
|
54
|
+
"OriginationDate": "2024-01-15",
|
|
55
|
+
"OriginationTime": "14:30:00",
|
|
56
|
+
"TimeReference": 44100,
|
|
57
|
+
"CodingHistory": "A=PCM,F=44100,W=16,M=mono,T=PCM",
|
|
58
|
+
},
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
result = get_full_metadata(test_file)
|
|
62
|
+
|
|
63
|
+
riff_raw = result.get("raw_metadata", {}).get("riff", {})
|
|
64
|
+
assert "chunk_structure" in riff_raw
|
|
65
|
+
assert "bext" in riff_raw["chunk_structure"]
|
|
66
|
+
|
|
67
|
+
bext_data = riff_raw["chunk_structure"]["bext"]
|
|
68
|
+
assert bext_data["Description"] == "Test Description"
|
|
69
|
+
assert bext_data["Originator"] == "Test Originator"
|
|
70
|
+
assert bext_data["OriginatorReference"] == "REF-12345"
|
|
71
|
+
assert bext_data["OriginationDate"] == "2024-01-15"
|
|
72
|
+
assert bext_data["OriginationTime"] == "14:30:00"
|
|
73
|
+
assert bext_data["TimeReference"] == 44100
|
|
74
|
+
assert bext_data["CodingHistory"] == "A=PCM,F=44100,W=16,M=mono,T=PCM"
|
|
75
|
+
|
|
76
|
+
def test_bext_chunk_structure_absent_when_not_present(self, sample_wav_file: Path):
|
|
77
|
+
"""Test that bext chunk is not present in chunk_structure for regular WAV files."""
|
|
78
|
+
result = get_full_metadata(sample_wav_file)
|
|
79
|
+
|
|
80
|
+
riff_raw = result.get("raw_metadata", {}).get("riff", {})
|
|
81
|
+
assert "chunk_structure" in riff_raw
|
|
82
|
+
# Regular WAV files without bext chunk should not have bext in chunk_structure
|
|
83
|
+
assert "bext" not in riff_raw["chunk_structure"]
|
|
84
|
+
|
|
85
|
+
def test_bext_chunk_structure_consistency_with_manager(self):
|
|
86
|
+
"""Test that get_full_metadata returns same bext data as manager.get_raw_metadata_info."""
|
|
87
|
+
from audiometa._audio_file import _AudioFile
|
|
88
|
+
from audiometa.manager._rating_supporting.riff._RiffManager import _RiffManager as RiffManager
|
|
89
|
+
|
|
90
|
+
with temp_file_with_metadata({}, "wav") as test_file:
|
|
91
|
+
RIFFMetadataSetter.set_bext_description(test_file, "Test Description")
|
|
92
|
+
RIFFMetadataSetter.set_bext_originator(test_file, "Test Originator")
|
|
93
|
+
RIFFMetadataSetter.set_bext_time_reference(test_file, 44100)
|
|
94
|
+
|
|
95
|
+
# Get via get_full_metadata
|
|
96
|
+
result = get_full_metadata(test_file)
|
|
97
|
+
bext_from_full = result.get("raw_metadata", {}).get("riff", {}).get("chunk_structure", {}).get("bext")
|
|
98
|
+
|
|
99
|
+
# Get via manager directly
|
|
100
|
+
audio_file = _AudioFile(test_file)
|
|
101
|
+
manager = RiffManager(audio_file)
|
|
102
|
+
raw_info = manager.get_raw_metadata_info()
|
|
103
|
+
bext_from_manager = raw_info.get("chunk_structure", {}).get("bext")
|
|
104
|
+
|
|
105
|
+
# Should be identical
|
|
106
|
+
assert bext_from_full == bext_from_manager
|
|
107
|
+
|
|
108
|
+
def test_bext_chunk_extraction_via_get_full_metadata_without_info_metadata(self):
|
|
109
|
+
"""Test that bext chunk is extracted via get_full_metadata even when no user-defined INFO metadata is present.
|
|
110
|
+
|
|
111
|
+
This tests the code path at lines 899-901 in _RiffManager.py that ensures bext chunk
|
|
112
|
+
extraction happens even when raw_clean_metadata is empty (no INFO metadata).
|
|
113
|
+
"""
|
|
114
|
+
with temp_file_with_metadata({}, "wav") as test_file:
|
|
115
|
+
# Add bext metadata but no user-defined INFO metadata
|
|
116
|
+
RIFFMetadataSetter.set_bext_description(test_file, "Test Description")
|
|
117
|
+
RIFFMetadataSetter.set_bext_originator(test_file, "Test Originator")
|
|
118
|
+
|
|
119
|
+
result = get_full_metadata(test_file)
|
|
120
|
+
|
|
121
|
+
riff_raw = result.get("raw_metadata", {}).get("riff", {})
|
|
122
|
+
# Should still have bext chunk in chunk_structure regardless of INFO metadata
|
|
123
|
+
assert "chunk_structure" in riff_raw
|
|
124
|
+
assert "bext" in riff_raw["chunk_structure"]
|
|
125
|
+
|
|
126
|
+
bext_data = riff_raw["chunk_structure"]["bext"]
|
|
127
|
+
assert bext_data["Description"] == "Test Description"
|
|
128
|
+
assert bext_data["Originator"] == "Test Originator"
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""Tests for get_full_metadata function structure validation."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from audiometa import get_full_metadata
|
|
8
|
+
from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@pytest.mark.integration
|
|
12
|
+
class TestGetFullMetadataStructure:
|
|
13
|
+
def test_get_full_metadata_headers_present_flags(self, sample_mp3_file: Path):
|
|
14
|
+
result = get_full_metadata(sample_mp3_file)
|
|
15
|
+
|
|
16
|
+
# Check ID3v2 headers
|
|
17
|
+
id3v2_headers = result["headers"]["id3v2"]
|
|
18
|
+
assert "present" in id3v2_headers
|
|
19
|
+
assert "version" in id3v2_headers
|
|
20
|
+
assert "header_size_bytes" in id3v2_headers
|
|
21
|
+
assert "flags" in id3v2_headers
|
|
22
|
+
assert "extended_header" in id3v2_headers
|
|
23
|
+
|
|
24
|
+
# Check ID3v1 headers
|
|
25
|
+
id3v1_headers = result["headers"]["id3v1"]
|
|
26
|
+
assert "present" in id3v1_headers
|
|
27
|
+
assert "position" in id3v1_headers
|
|
28
|
+
assert "size_bytes" in id3v1_headers
|
|
29
|
+
assert "version" in id3v1_headers
|
|
30
|
+
assert "has_track_number" in id3v1_headers
|
|
31
|
+
|
|
32
|
+
def test_get_full_metadata_raw_metadata_structure(self, sample_mp3_file: Path):
|
|
33
|
+
result = get_full_metadata(sample_mp3_file)
|
|
34
|
+
|
|
35
|
+
# Check ID3v2 raw metadata
|
|
36
|
+
id3v2_raw = result["raw_metadata"]["id3v2"]
|
|
37
|
+
assert "raw_data" in id3v2_raw
|
|
38
|
+
assert "parsed_fields" in id3v2_raw
|
|
39
|
+
assert "frames" in id3v2_raw
|
|
40
|
+
assert "comments" in id3v2_raw
|
|
41
|
+
assert "chunk_structure" in id3v2_raw
|
|
42
|
+
|
|
43
|
+
# Check ID3v1 raw metadata
|
|
44
|
+
id3v1_raw = result["raw_metadata"]["id3v1"]
|
|
45
|
+
assert "raw_data" in id3v1_raw
|
|
46
|
+
assert "parsed_fields" in id3v1_raw
|
|
47
|
+
assert "frames" in id3v1_raw
|
|
48
|
+
assert "comments" in id3v1_raw
|
|
49
|
+
assert "chunk_structure" in id3v1_raw
|
|
50
|
+
|
|
51
|
+
def test_get_full_metadata_riff_raw_metadata_structure(self, sample_wav_file: Path):
|
|
52
|
+
"""Test RIFF raw metadata structure in get_full_metadata."""
|
|
53
|
+
result = get_full_metadata(sample_wav_file)
|
|
54
|
+
|
|
55
|
+
# Check RIFF raw metadata
|
|
56
|
+
riff_raw = result["raw_metadata"]["riff"]
|
|
57
|
+
assert "raw_data" in riff_raw
|
|
58
|
+
assert "parsed_fields" in riff_raw
|
|
59
|
+
assert "frames" in riff_raw
|
|
60
|
+
assert "comments" in riff_raw
|
|
61
|
+
assert "chunk_structure" in riff_raw
|
|
62
|
+
|
|
63
|
+
def test_get_full_metadata_header_detection_accuracy(self, sample_mp3_file: Path):
|
|
64
|
+
result = get_full_metadata(sample_mp3_file)
|
|
65
|
+
|
|
66
|
+
# Check that headers are detected correctly
|
|
67
|
+
headers = result["headers"]
|
|
68
|
+
|
|
69
|
+
for metadata_format_name, header_info in headers.items():
|
|
70
|
+
assert "present" in header_info
|
|
71
|
+
assert isinstance(header_info["present"], bool)
|
|
72
|
+
|
|
73
|
+
if header_info["present"]:
|
|
74
|
+
# If header is present, should have additional info
|
|
75
|
+
if metadata_format_name == "id3v2":
|
|
76
|
+
assert "version" in header_info
|
|
77
|
+
assert "header_size_bytes" in header_info
|
|
78
|
+
elif metadata_format_name == "id3v1":
|
|
79
|
+
assert "position" in header_info
|
|
80
|
+
assert "size_bytes" in header_info
|
|
81
|
+
elif metadata_format_name == "vorbis":
|
|
82
|
+
assert "vendor_string" in header_info
|
|
83
|
+
assert "comment_count" in header_info
|
|
84
|
+
elif metadata_format_name == "riff":
|
|
85
|
+
assert "chunk_info" in header_info
|
|
86
|
+
|
|
87
|
+
def test_id3v1_parsed_fields_use_unified_keys(self, sample_mp3_file: Path):
|
|
88
|
+
result = get_full_metadata(sample_mp3_file)
|
|
89
|
+
|
|
90
|
+
id3v1_raw = result.get("raw_metadata", {}).get("id3v1", {})
|
|
91
|
+
parsed_fields = id3v1_raw.get("parsed_fields", {})
|
|
92
|
+
|
|
93
|
+
# If there are parsed fields, they should use UnifiedMetadataKey enum values as keys
|
|
94
|
+
for key in parsed_fields:
|
|
95
|
+
assert isinstance(
|
|
96
|
+
key, UnifiedMetadataKey
|
|
97
|
+
), f"ID3v1 parsed_fields key {key} should be UnifiedMetadataKey enum, got {type(key)}"
|
|
98
|
+
# Verify it's a valid UnifiedMetadataKey value
|
|
99
|
+
assert key in UnifiedMetadataKey, f"ID3v1 parsed_fields key {key} is not a valid UnifiedMetadataKey"
|
|
100
|
+
|
|
101
|
+
def test_riff_parsed_fields_use_raw_keys(self, sample_wav_file: Path):
|
|
102
|
+
result = get_full_metadata(sample_wav_file)
|
|
103
|
+
|
|
104
|
+
riff_raw = result.get("raw_metadata", {}).get("riff", {})
|
|
105
|
+
parsed_fields = riff_raw.get("parsed_fields", {})
|
|
106
|
+
|
|
107
|
+
# RIFF should use raw RIFF tag keys (like 'INAM', 'IART', etc.)
|
|
108
|
+
# These are NOT UnifiedMetadataKey enum values, which is correct for RIFF
|
|
109
|
+
for key in parsed_fields:
|
|
110
|
+
assert isinstance(key, str), f"RIFF parsed_fields key {key} should be string, got {type(key)}"
|
|
111
|
+
# RIFF keys should be 4-character codes like 'INAM', 'IART', etc.
|
|
112
|
+
assert len(key) == 4, f"RIFF parsed_fields key {key} should be 4 characters (FourCC), got {len(key)}"
|
|
113
|
+
|
|
114
|
+
def test_cli_output_parsed_fields_keys(self, sample_mp3_file: Path):
|
|
115
|
+
import json
|
|
116
|
+
import subprocess
|
|
117
|
+
import sys
|
|
118
|
+
|
|
119
|
+
result = subprocess.run(
|
|
120
|
+
[sys.executable, "-m", "audiometa", "read", str(sample_mp3_file), "--format", "json"],
|
|
121
|
+
capture_output=True,
|
|
122
|
+
text=True,
|
|
123
|
+
check=False,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
assert result.returncode == 0, f"CLI failed: {result.stderr}"
|
|
127
|
+
|
|
128
|
+
data = json.loads(result.stdout)
|
|
129
|
+
raw_metadata = data.get("raw_metadata", {})
|
|
130
|
+
|
|
131
|
+
# Check ID3v1 parsed_fields keys in CLI output
|
|
132
|
+
id3v1_raw = raw_metadata.get("id3v1", {})
|
|
133
|
+
parsed_fields = id3v1_raw.get("parsed_fields", {})
|
|
134
|
+
|
|
135
|
+
for key in parsed_fields:
|
|
136
|
+
# In JSON output, UnifiedMetadataKey enum values are serialized as their string values
|
|
137
|
+
# (e.g., "title" instead of "UnifiedMetadataKey.TITLE") because UnifiedMetadataKey inherits from str
|
|
138
|
+
assert isinstance(key, str), f"ID3v1 parsed_fields key in CLI output should be string, got: {type(key)}"
|
|
139
|
+
|
|
140
|
+
# Verify it's a valid UnifiedMetadataKey value
|
|
141
|
+
assert key in [
|
|
142
|
+
e.value for e in UnifiedMetadataKey
|
|
143
|
+
], f"ID3v1 parsed_fields key {key} is not a valid UnifiedMetadataKey value"
|
|
144
|
+
|
|
145
|
+
def test_parsed_fields_consistency_across_formats(self, sample_mp3_file: Path):
|
|
146
|
+
result = get_full_metadata(sample_mp3_file)
|
|
147
|
+
|
|
148
|
+
raw_metadata = result.get("raw_metadata", {})
|
|
149
|
+
|
|
150
|
+
# Check that all formats have the expected structure
|
|
151
|
+
for metadata_format_name, format_data in raw_metadata.items():
|
|
152
|
+
assert "parsed_fields" in format_data, f"Format {metadata_format_name} should have parsed_fields"
|
|
153
|
+
assert isinstance(
|
|
154
|
+
format_data["parsed_fields"], dict
|
|
155
|
+
), f"Format {metadata_format_name} parsed_fields should be a dictionary"
|
|
156
|
+
|
|
157
|
+
# Check that parsed_fields values are strings (no binary data)
|
|
158
|
+
for key, value in format_data["parsed_fields"].items():
|
|
159
|
+
assert isinstance(
|
|
160
|
+
value, str
|
|
161
|
+
), f"Format {metadata_format_name} parsed_fields value for {key} should be string, got {type(value)}"
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from audiometa import get_unified_metadata_field, update_metadata
|
|
4
|
+
from audiometa.test.helpers.id3v1 import ID3v1MetadataSetter
|
|
5
|
+
from audiometa.test.helpers.id3v2 import ID3v2MetadataSetter
|
|
6
|
+
from audiometa.test.helpers.riff import RIFFMetadataSetter
|
|
7
|
+
from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
|
|
8
|
+
from audiometa.test.helpers.vorbis import VorbisMetadataSetter
|
|
9
|
+
from audiometa.utils.metadata_format import MetadataFormat
|
|
10
|
+
from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@pytest.mark.integration
|
|
14
|
+
class TestAlbumDeleting:
|
|
15
|
+
def test_delete_album_id3v2(self):
|
|
16
|
+
with temp_file_with_metadata({}, "mp3") as test_file:
|
|
17
|
+
ID3v2MetadataSetter.set_album(test_file, "Test Album")
|
|
18
|
+
assert get_unified_metadata_field(test_file, UnifiedMetadataKey.ALBUM) == "Test Album"
|
|
19
|
+
|
|
20
|
+
# Delete metadata using library API
|
|
21
|
+
update_metadata(test_file, {UnifiedMetadataKey.ALBUM: None}, metadata_format=MetadataFormat.ID3V2)
|
|
22
|
+
assert get_unified_metadata_field(test_file, UnifiedMetadataKey.ALBUM) is None
|
|
23
|
+
|
|
24
|
+
def test_delete_album_id3v1(self):
|
|
25
|
+
with temp_file_with_metadata({}, "id3v1") as test_file:
|
|
26
|
+
ID3v1MetadataSetter.set_album(test_file, "Test Album")
|
|
27
|
+
assert get_unified_metadata_field(test_file, UnifiedMetadataKey.ALBUM) == "Test Album"
|
|
28
|
+
|
|
29
|
+
# Delete metadata using library API
|
|
30
|
+
update_metadata(test_file, {UnifiedMetadataKey.ALBUM: None}, metadata_format=MetadataFormat.ID3V1)
|
|
31
|
+
assert get_unified_metadata_field(test_file, UnifiedMetadataKey.ALBUM) is None
|
|
32
|
+
|
|
33
|
+
def test_delete_album_riff(self):
|
|
34
|
+
with temp_file_with_metadata({}, "wav") as test_file:
|
|
35
|
+
RIFFMetadataSetter.set_album(test_file, "Test Album")
|
|
36
|
+
assert get_unified_metadata_field(test_file, UnifiedMetadataKey.ALBUM) == "Test Album"
|
|
37
|
+
|
|
38
|
+
# Delete metadata using library API
|
|
39
|
+
update_metadata(test_file, {UnifiedMetadataKey.ALBUM: None}, metadata_format=MetadataFormat.RIFF)
|
|
40
|
+
assert get_unified_metadata_field(test_file, UnifiedMetadataKey.ALBUM) is None
|
|
41
|
+
|
|
42
|
+
def test_delete_album_vorbis(self):
|
|
43
|
+
with temp_file_with_metadata({}, "flac") as test_file:
|
|
44
|
+
VorbisMetadataSetter.set_album(test_file, "Test Album")
|
|
45
|
+
assert get_unified_metadata_field(test_file, UnifiedMetadataKey.ALBUM) == "Test Album"
|
|
46
|
+
|
|
47
|
+
# Delete metadata using library API
|
|
48
|
+
update_metadata(test_file, {UnifiedMetadataKey.ALBUM: None}, metadata_format=MetadataFormat.VORBIS)
|
|
49
|
+
assert get_unified_metadata_field(test_file, UnifiedMetadataKey.ALBUM) is None
|
|
50
|
+
|
|
51
|
+
def test_delete_album_preserves_other_fields(self):
|
|
52
|
+
with temp_file_with_metadata({}, "mp3") as test_file:
|
|
53
|
+
ID3v2MetadataSetter.set_album(test_file, "Test Album")
|
|
54
|
+
ID3v2MetadataSetter.set_title(test_file, "Test Title")
|
|
55
|
+
ID3v2MetadataSetter.set_artists(test_file, "Test Artist")
|
|
56
|
+
|
|
57
|
+
# Delete only album using library API
|
|
58
|
+
update_metadata(test_file, {UnifiedMetadataKey.ALBUM: None}, metadata_format=MetadataFormat.ID3V2)
|
|
59
|
+
|
|
60
|
+
assert get_unified_metadata_field(test_file, UnifiedMetadataKey.ALBUM) is None
|
|
61
|
+
assert get_unified_metadata_field(test_file, UnifiedMetadataKey.TITLE) == "Test Title"
|
|
62
|
+
assert get_unified_metadata_field(test_file, UnifiedMetadataKey.ARTISTS) == ["Test Artist"]
|
|
63
|
+
|
|
64
|
+
def test_delete_album_already_none(self):
|
|
65
|
+
with temp_file_with_metadata({}, "mp3") as test_file:
|
|
66
|
+
# Try to delete album that doesn't exist
|
|
67
|
+
update_metadata(test_file, {UnifiedMetadataKey.ALBUM: None}, metadata_format=MetadataFormat.ID3V2)
|
|
68
|
+
assert get_unified_metadata_field(test_file, UnifiedMetadataKey.ALBUM) is None
|
|
69
|
+
|
|
70
|
+
def test_delete_album_empty_string(self):
|
|
71
|
+
with temp_file_with_metadata({}, "mp3") as test_file:
|
|
72
|
+
update_metadata(test_file, {UnifiedMetadataKey.ALBUM: ""}, metadata_format=MetadataFormat.ID3V2)
|
|
73
|
+
assert get_unified_metadata_field(test_file, UnifiedMetadataKey.ALBUM) is None
|