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,234 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from audiometa.cli import expand_file_patterns
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@pytest.mark.unit
|
|
9
|
+
class TestExpandFilePatterns:
|
|
10
|
+
def test_single_file_exists(self, tmp_path):
|
|
11
|
+
test_file = tmp_path / "test.mp3"
|
|
12
|
+
test_file.write_text("fake mp3 content")
|
|
13
|
+
|
|
14
|
+
result = expand_file_patterns([str(test_file)])
|
|
15
|
+
assert len(result) == 1
|
|
16
|
+
assert result[0] == test_file
|
|
17
|
+
|
|
18
|
+
def test_single_file_nonexistent(self, tmp_path):
|
|
19
|
+
nonexistent_file = tmp_path / "nonexistent.mp3"
|
|
20
|
+
|
|
21
|
+
result = expand_file_patterns([str(nonexistent_file)], continue_on_error=True)
|
|
22
|
+
assert result == []
|
|
23
|
+
|
|
24
|
+
def test_glob_pattern_matching_files(self, tmp_path):
|
|
25
|
+
# Create test files
|
|
26
|
+
(tmp_path / "song1.mp3").write_text("content1")
|
|
27
|
+
(tmp_path / "song2.mp3").write_text("content2")
|
|
28
|
+
(tmp_path / "other.txt").write_text("content3")
|
|
29
|
+
|
|
30
|
+
pattern = str(tmp_path / "*.mp3")
|
|
31
|
+
result = expand_file_patterns([pattern])
|
|
32
|
+
|
|
33
|
+
assert len(result) == 2
|
|
34
|
+
assert all(path.suffix == ".mp3" for path in result)
|
|
35
|
+
assert all(path.parent == tmp_path for path in result)
|
|
36
|
+
|
|
37
|
+
def test_glob_pattern_no_matches(self, tmp_path):
|
|
38
|
+
pattern = str(tmp_path / "*.wav")
|
|
39
|
+
result = expand_file_patterns([pattern], continue_on_error=True)
|
|
40
|
+
assert result == []
|
|
41
|
+
|
|
42
|
+
def test_directory_non_recursive(self, tmp_path):
|
|
43
|
+
subdir = tmp_path / "subdir"
|
|
44
|
+
subdir.mkdir()
|
|
45
|
+
(subdir / "song.mp3").write_text("content")
|
|
46
|
+
|
|
47
|
+
result = expand_file_patterns([str(subdir)], continue_on_error=True)
|
|
48
|
+
assert result == []
|
|
49
|
+
|
|
50
|
+
def test_directory_recursive(self, tmp_path):
|
|
51
|
+
subdir = tmp_path / "music"
|
|
52
|
+
subdir.mkdir()
|
|
53
|
+
nested_dir = subdir / "album1"
|
|
54
|
+
nested_dir.mkdir()
|
|
55
|
+
|
|
56
|
+
# Create various audio files
|
|
57
|
+
(subdir / "song1.mp3").write_text("content1")
|
|
58
|
+
(nested_dir / "song2.flac").write_text("content2")
|
|
59
|
+
(nested_dir / "song3.wav").write_text("content3")
|
|
60
|
+
(nested_dir / "notes.txt").write_text("content4") # Non-audio file
|
|
61
|
+
|
|
62
|
+
result = expand_file_patterns([str(subdir)], recursive=True)
|
|
63
|
+
|
|
64
|
+
assert len(result) == 3
|
|
65
|
+
assert all(path.suffix in [".mp3", ".flac", ".wav"] for path in result)
|
|
66
|
+
assert all(subdir in path.parents for path in result)
|
|
67
|
+
|
|
68
|
+
def test_directory_recursive_nested_directories(self, tmp_path):
|
|
69
|
+
# Create nested structure: music/artist/album/songs
|
|
70
|
+
music_dir = tmp_path / "music"
|
|
71
|
+
artist_dir = music_dir / "artist"
|
|
72
|
+
album_dir = artist_dir / "album"
|
|
73
|
+
album_dir.mkdir(parents=True)
|
|
74
|
+
|
|
75
|
+
# Create files at different levels
|
|
76
|
+
(music_dir / "root.mp3").write_text("root")
|
|
77
|
+
(artist_dir / "artist.mp3").write_text("artist")
|
|
78
|
+
(album_dir / "song1.mp3").write_text("song1")
|
|
79
|
+
(album_dir / "song2.flac").write_text("song2")
|
|
80
|
+
|
|
81
|
+
result = expand_file_patterns([str(music_dir)], recursive=True)
|
|
82
|
+
|
|
83
|
+
assert len(result) == 4
|
|
84
|
+
assert all(path.suffix in [".mp3", ".flac"] for path in result)
|
|
85
|
+
|
|
86
|
+
def test_mixed_patterns(self, tmp_path):
|
|
87
|
+
# Create files and directories
|
|
88
|
+
single_file = tmp_path / "single.mp3"
|
|
89
|
+
single_file.write_text("single")
|
|
90
|
+
|
|
91
|
+
music_dir = tmp_path / "music"
|
|
92
|
+
music_dir.mkdir()
|
|
93
|
+
(music_dir / "album.mp3").write_text("album")
|
|
94
|
+
|
|
95
|
+
# Create glob-matching files
|
|
96
|
+
(tmp_path / "glob1.mp3").write_text("glob1")
|
|
97
|
+
(tmp_path / "glob2.mp3").write_text("glob2")
|
|
98
|
+
|
|
99
|
+
patterns = [
|
|
100
|
+
str(single_file), # Single file
|
|
101
|
+
str(music_dir), # Directory (recursive)
|
|
102
|
+
str(tmp_path / "glob*.mp3"), # Glob pattern
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
result = expand_file_patterns(patterns, recursive=True)
|
|
106
|
+
|
|
107
|
+
assert len(result) == 4 # single + album + glob1 + glob2
|
|
108
|
+
assert single_file in result
|
|
109
|
+
assert music_dir / "album.mp3" in result
|
|
110
|
+
|
|
111
|
+
def test_unsupported_audio_extensions_ignored(self, tmp_path):
|
|
112
|
+
music_dir = tmp_path / "music"
|
|
113
|
+
music_dir.mkdir()
|
|
114
|
+
|
|
115
|
+
# Create files with supported and unsupported extensions
|
|
116
|
+
(music_dir / "song.mp3").write_text("mp3")
|
|
117
|
+
(music_dir / "song.flac").write_text("flac")
|
|
118
|
+
(music_dir / "song.wav").write_text("wav")
|
|
119
|
+
(music_dir / "song.ogg").write_text("ogg") # Not supported
|
|
120
|
+
(music_dir / "song.m4a").write_text("m4a") # Not supported
|
|
121
|
+
|
|
122
|
+
result = expand_file_patterns([str(music_dir)], recursive=True)
|
|
123
|
+
|
|
124
|
+
assert len(result) == 3
|
|
125
|
+
assert all(path.suffix in [".mp3", ".flac", ".wav"] for path in result)
|
|
126
|
+
|
|
127
|
+
def test_no_files_found_continue_on_error_true(self, tmp_path, capsys):
|
|
128
|
+
nonexistent_pattern = str(tmp_path / "nonexistent.mp3")
|
|
129
|
+
|
|
130
|
+
result = expand_file_patterns([nonexistent_pattern], continue_on_error=True)
|
|
131
|
+
|
|
132
|
+
assert result == []
|
|
133
|
+
captured = capsys.readouterr()
|
|
134
|
+
assert "Warning: No valid audio files found" in captured.err
|
|
135
|
+
|
|
136
|
+
def test_no_files_found_continue_on_error_false(self, tmp_path, capsys):
|
|
137
|
+
nonexistent_pattern = str(tmp_path / "nonexistent.mp3")
|
|
138
|
+
|
|
139
|
+
with pytest.raises(SystemExit) as exc_info:
|
|
140
|
+
expand_file_patterns([nonexistent_pattern], continue_on_error=False)
|
|
141
|
+
|
|
142
|
+
assert exc_info.value.code == 1
|
|
143
|
+
captured = capsys.readouterr()
|
|
144
|
+
assert "Error: No valid audio files found" in captured.err
|
|
145
|
+
|
|
146
|
+
def test_complex_glob_patterns(self, tmp_path):
|
|
147
|
+
# Create test files with different patterns
|
|
148
|
+
(tmp_path / "track01.mp3").write_text("track01")
|
|
149
|
+
(tmp_path / "track02.mp3").write_text("track02")
|
|
150
|
+
(tmp_path / "song_a.mp3").write_text("song_a")
|
|
151
|
+
(tmp_path / "song_b.mp3").write_text("song_b")
|
|
152
|
+
(tmp_path / "other.wav").write_text("other")
|
|
153
|
+
|
|
154
|
+
# Test character class glob
|
|
155
|
+
pattern1 = str(tmp_path / "track[0-9][0-9].mp3")
|
|
156
|
+
result1 = expand_file_patterns([pattern1])
|
|
157
|
+
assert len(result1) == 2
|
|
158
|
+
|
|
159
|
+
# Test wildcard glob
|
|
160
|
+
pattern2 = str(tmp_path / "song_?.mp3")
|
|
161
|
+
result2 = expand_file_patterns([pattern2])
|
|
162
|
+
assert len(result2) == 2
|
|
163
|
+
|
|
164
|
+
def test_relative_paths(self, tmp_path, monkeypatch):
|
|
165
|
+
# Change to tmp_path directory
|
|
166
|
+
monkeypatch.chdir(tmp_path)
|
|
167
|
+
|
|
168
|
+
# Create relative file
|
|
169
|
+
rel_file = Path("relative.mp3")
|
|
170
|
+
rel_file.write_text("relative")
|
|
171
|
+
|
|
172
|
+
result = expand_file_patterns(["relative.mp3"])
|
|
173
|
+
assert len(result) == 1
|
|
174
|
+
assert result[0].name == "relative.mp3"
|
|
175
|
+
|
|
176
|
+
def test_hidden_files_ignored(self, tmp_path):
|
|
177
|
+
(tmp_path / "visible.mp3").write_text("visible")
|
|
178
|
+
(tmp_path / ".hidden.mp3").write_text("hidden")
|
|
179
|
+
|
|
180
|
+
pattern = str(tmp_path / "*.mp3")
|
|
181
|
+
result = expand_file_patterns([pattern])
|
|
182
|
+
|
|
183
|
+
assert len(result) == 1
|
|
184
|
+
assert result[0].name == "visible.mp3"
|
|
185
|
+
|
|
186
|
+
def test_case_sensitive_extensions(self, tmp_path):
|
|
187
|
+
# On case-insensitive filesystems, we can't create both .mp3 and .MP3 files
|
|
188
|
+
# So we'll test with different extensions instead
|
|
189
|
+
(tmp_path / "song.mp3").write_text("lowercase")
|
|
190
|
+
(tmp_path / "song.wav").write_text("wav")
|
|
191
|
+
|
|
192
|
+
pattern_mp3 = str(tmp_path / "*.mp3")
|
|
193
|
+
pattern_wav = str(tmp_path / "*.wav")
|
|
194
|
+
|
|
195
|
+
result_mp3 = expand_file_patterns([pattern_mp3], continue_on_error=True)
|
|
196
|
+
result_wav = expand_file_patterns([pattern_wav], continue_on_error=True)
|
|
197
|
+
|
|
198
|
+
# Each pattern should match exactly one file
|
|
199
|
+
assert len(result_mp3) == 1
|
|
200
|
+
assert result_mp3[0].name == "song.mp3"
|
|
201
|
+
assert len(result_wav) == 1
|
|
202
|
+
assert result_wav[0].name == "song.wav"
|
|
203
|
+
|
|
204
|
+
def test_empty_pattern_list(self):
|
|
205
|
+
result = expand_file_patterns([], continue_on_error=True)
|
|
206
|
+
assert result == []
|
|
207
|
+
|
|
208
|
+
def test_directory_with_no_audio_files_recursive(self, tmp_path):
|
|
209
|
+
empty_dir = tmp_path / "empty"
|
|
210
|
+
empty_dir.mkdir()
|
|
211
|
+
(empty_dir / "text.txt").write_text("text")
|
|
212
|
+
|
|
213
|
+
result = expand_file_patterns([str(empty_dir)], recursive=True, continue_on_error=True)
|
|
214
|
+
assert result == []
|
|
215
|
+
|
|
216
|
+
def test_file_vs_directory_same_name(self, tmp_path):
|
|
217
|
+
# Create a file named "music"
|
|
218
|
+
music_file = tmp_path / "music"
|
|
219
|
+
music_file.write_text("not audio")
|
|
220
|
+
|
|
221
|
+
# Create a directory named "music_dir" with audio files
|
|
222
|
+
music_dir = tmp_path / "music_dir"
|
|
223
|
+
music_dir.mkdir()
|
|
224
|
+
(music_dir / "song.mp3").write_text("audio")
|
|
225
|
+
|
|
226
|
+
# Test file takes precedence when it exists
|
|
227
|
+
result1 = expand_file_patterns([str(music_file)])
|
|
228
|
+
assert len(result1) == 1
|
|
229
|
+
assert result1[0] == music_file
|
|
230
|
+
|
|
231
|
+
# Test directory expansion
|
|
232
|
+
result2 = expand_file_patterns([str(music_dir)], recursive=True)
|
|
233
|
+
assert len(result2) == 1
|
|
234
|
+
assert result2[0] == music_dir / "song.mp3"
|
|
File without changes
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
from unittest.mock import MagicMock
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from audiometa._audio_file import _AudioFile
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@pytest.fixture
|
|
9
|
+
def mock_audio_file_mp3():
|
|
10
|
+
mock_audio_file = MagicMock(spec=_AudioFile)
|
|
11
|
+
mock_audio_file.file_path = "/path/to/test.mp3"
|
|
12
|
+
mock_audio_file.file_extension = ".mp3"
|
|
13
|
+
return mock_audio_file
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@pytest.fixture
|
|
17
|
+
def mock_audio_file_wav():
|
|
18
|
+
mock_audio_file = MagicMock(spec=_AudioFile)
|
|
19
|
+
mock_audio_file.file_path = "/path/to/test.wav"
|
|
20
|
+
mock_audio_file.file_extension = ".wav"
|
|
21
|
+
return mock_audio_file
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@pytest.fixture
|
|
25
|
+
def mock_audio_file_flac():
|
|
26
|
+
mock_audio_file = MagicMock(spec=_AudioFile)
|
|
27
|
+
mock_audio_file.file_path = "/path/to/test.flac"
|
|
28
|
+
mock_audio_file.file_extension = ".flac"
|
|
29
|
+
return mock_audio_file
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@pytest.fixture
|
|
33
|
+
def mock_id3_empty():
|
|
34
|
+
mock_id3 = MagicMock()
|
|
35
|
+
mock_id3.version = (2, 3, 0)
|
|
36
|
+
mock_id3.size = 0
|
|
37
|
+
mock_id3.flags = 0
|
|
38
|
+
mock_id3.__contains__ = lambda _self, _key: False
|
|
39
|
+
mock_id3.items.return_value = []
|
|
40
|
+
mock_id3.extended_header = None
|
|
41
|
+
return mock_id3
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@pytest.fixture
|
|
45
|
+
def mock_id3_with_metadata():
|
|
46
|
+
mock_id3 = MagicMock()
|
|
47
|
+
mock_id3.version = (2, 3, 0)
|
|
48
|
+
mock_id3.size = 2048
|
|
49
|
+
mock_id3.flags = 0x40
|
|
50
|
+
mock_id3.extended_header = None
|
|
51
|
+
|
|
52
|
+
mock_title = MagicMock()
|
|
53
|
+
mock_title.text = ["Test Title"]
|
|
54
|
+
mock_artists = MagicMock()
|
|
55
|
+
mock_artists.text = ["Test Artist"]
|
|
56
|
+
mock_album = MagicMock()
|
|
57
|
+
mock_album.text = ["Test Album"]
|
|
58
|
+
|
|
59
|
+
frame_dict = {
|
|
60
|
+
"TIT2": mock_title,
|
|
61
|
+
"TPE1": mock_artists,
|
|
62
|
+
"TALB": mock_album,
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
mock_id3.__contains__ = lambda _self, key: key in frame_dict
|
|
66
|
+
mock_id3.__getitem__ = lambda _self, key: frame_dict.get(key)
|
|
67
|
+
mock_id3.items.return_value = []
|
|
68
|
+
|
|
69
|
+
return mock_id3
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@pytest.fixture
|
|
73
|
+
def mock_id3_updatable():
|
|
74
|
+
"""Mock ID3 object that supports add/delall operations for update_metadata testing."""
|
|
75
|
+
mock_id3 = MagicMock()
|
|
76
|
+
mock_id3.version = (2, 3, 0)
|
|
77
|
+
mock_id3.size = 0
|
|
78
|
+
mock_id3.flags = 0
|
|
79
|
+
mock_id3.extended_header = None
|
|
80
|
+
|
|
81
|
+
# Storage for frames
|
|
82
|
+
frames = {}
|
|
83
|
+
|
|
84
|
+
def mock_delall(key):
|
|
85
|
+
"""Remove all frames with the given key."""
|
|
86
|
+
keys_to_remove = [k for k in frames if k.startswith(key)]
|
|
87
|
+
for k in keys_to_remove:
|
|
88
|
+
del frames[k]
|
|
89
|
+
|
|
90
|
+
def mock_add(frame):
|
|
91
|
+
"""Add a frame to the mock."""
|
|
92
|
+
frame_id = frame.FrameID
|
|
93
|
+
if hasattr(frame, "email"): # POPM frame
|
|
94
|
+
frame_id = f"{frame_id}:{frame.email}"
|
|
95
|
+
|
|
96
|
+
frames[frame_id] = frame
|
|
97
|
+
|
|
98
|
+
def mock_items():
|
|
99
|
+
"""Return list of (key, frame) tuples."""
|
|
100
|
+
return list(frames.items())
|
|
101
|
+
|
|
102
|
+
mock_id3.delall = mock_delall
|
|
103
|
+
mock_id3.add = mock_add
|
|
104
|
+
mock_id3.items = mock_items
|
|
105
|
+
mock_id3.__contains__ = lambda _self, key: key in frames
|
|
106
|
+
mock_id3.__getitem__ = lambda _self, key: frames[key]
|
|
107
|
+
|
|
108
|
+
return mock_id3
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@pytest.fixture
|
|
112
|
+
def mock_wave_empty():
|
|
113
|
+
mock_wave = MagicMock()
|
|
114
|
+
mock_wave.info = {}
|
|
115
|
+
return mock_wave
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@pytest.fixture
|
|
119
|
+
def mock_wave_with_metadata():
|
|
120
|
+
mock_wave = MagicMock()
|
|
121
|
+
mock_wave.info = {
|
|
122
|
+
"INAM": ["Test Title"],
|
|
123
|
+
"IART": ["Test Artist"],
|
|
124
|
+
"IPRD": ["Test Album"],
|
|
125
|
+
}
|
|
126
|
+
return mock_wave
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@pytest.fixture
|
|
130
|
+
def mock_vorbis_metadata_empty():
|
|
131
|
+
"""Mock Vorbis metadata dict with no metadata."""
|
|
132
|
+
return {}
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@pytest.fixture
|
|
136
|
+
def mock_vorbis_metadata_with_data():
|
|
137
|
+
"""Mock Vorbis metadata dict with sample metadata."""
|
|
138
|
+
return {
|
|
139
|
+
"TITLE": ["Test Title"],
|
|
140
|
+
"ARTIST": ["Test Artist"],
|
|
141
|
+
"ALBUM": ["Test Album"],
|
|
142
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from audiometa._audio_file import _AudioFile
|
|
6
|
+
from audiometa.manager.id3v1._Id3v1Manager import _Id3v1Manager as Id3v1Manager
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@pytest.mark.unit
|
|
10
|
+
class TestId3v1HeaderMethods:
|
|
11
|
+
def test_id3v1_manager_header_info(self, sample_mp3_file: Path):
|
|
12
|
+
audio_file = _AudioFile(sample_mp3_file)
|
|
13
|
+
manager = Id3v1Manager(audio_file)
|
|
14
|
+
|
|
15
|
+
header_info = manager.get_header_info()
|
|
16
|
+
|
|
17
|
+
# Should have ID3v1 specific structure
|
|
18
|
+
assert "present" in header_info
|
|
19
|
+
assert "position" in header_info
|
|
20
|
+
assert "size_bytes" in header_info
|
|
21
|
+
assert "version" in header_info
|
|
22
|
+
assert "has_track_number" in header_info
|
|
23
|
+
|
|
24
|
+
# Should be valid structure
|
|
25
|
+
assert isinstance(header_info["present"], bool)
|
|
26
|
+
assert header_info["position"] is None or isinstance(header_info["position"], str)
|
|
27
|
+
assert isinstance(header_info["size_bytes"], int)
|
|
28
|
+
assert header_info["version"] is None or isinstance(header_info["version"], str)
|
|
29
|
+
assert isinstance(header_info["has_track_number"], bool)
|
|
30
|
+
|
|
31
|
+
def test_id3v1_manager_raw_metadata_info(self, sample_mp3_file: Path):
|
|
32
|
+
audio_file = _AudioFile(sample_mp3_file)
|
|
33
|
+
manager = Id3v1Manager(audio_file)
|
|
34
|
+
|
|
35
|
+
raw_info = manager.get_raw_metadata_info()
|
|
36
|
+
|
|
37
|
+
# Should have ID3v1 specific structure
|
|
38
|
+
assert "raw_data" in raw_info
|
|
39
|
+
assert "parsed_fields" in raw_info
|
|
40
|
+
assert "frames" in raw_info
|
|
41
|
+
assert "comments" in raw_info
|
|
42
|
+
assert "chunk_structure" in raw_info
|
|
43
|
+
|
|
44
|
+
# Should be valid structure
|
|
45
|
+
assert raw_info["raw_data"] is None or isinstance(raw_info["raw_data"], bytes)
|
|
46
|
+
assert isinstance(raw_info["parsed_fields"], dict)
|
|
47
|
+
assert isinstance(raw_info["frames"], dict)
|
|
48
|
+
assert isinstance(raw_info["comments"], dict)
|
|
49
|
+
assert isinstance(raw_info["chunk_structure"], dict)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from unittest.mock import patch
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from audiometa.manager._rating_supporting.id3v2._Id3v2Manager import _Id3v2Manager as Id3v2Manager
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@pytest.mark.unit
|
|
9
|
+
class TestId3v2HeaderMethods:
|
|
10
|
+
@patch("audiometa.manager._rating_supporting.id3v2._Id3v2Manager.ID3")
|
|
11
|
+
def test_id3v2_manager_header_info(self, mock_id3_class, mock_audio_file_mp3, mock_id3_with_metadata):
|
|
12
|
+
mock_id3_class.return_value = mock_id3_with_metadata
|
|
13
|
+
|
|
14
|
+
manager = Id3v2Manager(mock_audio_file_mp3)
|
|
15
|
+
header_info = manager.get_header_info()
|
|
16
|
+
|
|
17
|
+
assert "present" in header_info
|
|
18
|
+
assert "version" in header_info
|
|
19
|
+
assert "header_size_bytes" in header_info
|
|
20
|
+
assert "flags" in header_info
|
|
21
|
+
assert "extended_header" in header_info
|
|
22
|
+
|
|
23
|
+
assert isinstance(header_info["present"], bool)
|
|
24
|
+
assert isinstance(header_info["version"], str)
|
|
25
|
+
assert header_info["version"] == "2.3.0"
|
|
26
|
+
assert isinstance(header_info["header_size_bytes"], int)
|
|
27
|
+
assert isinstance(header_info["flags"], dict)
|
|
28
|
+
assert isinstance(header_info["extended_header"], dict)
|
|
29
|
+
|
|
30
|
+
@patch("audiometa.manager._rating_supporting.id3v2._Id3v2Manager.ID3")
|
|
31
|
+
def test_id3v2_manager_header_info_empty(self, mock_id3_class, mock_audio_file_mp3, mock_id3_empty):
|
|
32
|
+
mock_id3_class.return_value = mock_id3_empty
|
|
33
|
+
|
|
34
|
+
manager = Id3v2Manager(mock_audio_file_mp3)
|
|
35
|
+
header_info = manager.get_header_info()
|
|
36
|
+
|
|
37
|
+
assert "present" in header_info
|
|
38
|
+
assert "version" in header_info
|
|
39
|
+
assert "header_size_bytes" in header_info
|
|
40
|
+
assert "flags" in header_info
|
|
41
|
+
assert "extended_header" in header_info
|
|
42
|
+
|
|
43
|
+
assert isinstance(header_info["present"], bool)
|
|
44
|
+
assert isinstance(header_info["version"], str)
|
|
45
|
+
assert isinstance(header_info["header_size_bytes"], int)
|
|
46
|
+
assert isinstance(header_info["flags"], dict)
|
|
47
|
+
assert isinstance(header_info["extended_header"], dict)
|
|
48
|
+
|
|
49
|
+
@patch("audiometa.manager._rating_supporting.id3v2._Id3v2Manager.ID3")
|
|
50
|
+
def test_id3v2_manager_raw_metadata_info(self, mock_id3_class, mock_audio_file_mp3, mock_id3_empty):
|
|
51
|
+
mock_id3_class.return_value = mock_id3_empty
|
|
52
|
+
|
|
53
|
+
manager = Id3v2Manager(mock_audio_file_mp3)
|
|
54
|
+
raw_info = manager.get_raw_metadata_info()
|
|
55
|
+
|
|
56
|
+
assert "raw_data" in raw_info
|
|
57
|
+
assert "parsed_fields" in raw_info
|
|
58
|
+
assert "frames" in raw_info
|
|
59
|
+
assert "comments" in raw_info
|
|
60
|
+
assert "chunk_structure" in raw_info
|
|
61
|
+
|
|
62
|
+
assert raw_info["raw_data"] is None or isinstance(raw_info["raw_data"], bytes)
|
|
63
|
+
assert isinstance(raw_info["parsed_fields"], dict)
|
|
64
|
+
assert isinstance(raw_info["frames"], dict)
|
|
65
|
+
assert isinstance(raw_info["comments"], dict)
|
|
66
|
+
assert isinstance(raw_info["chunk_structure"], dict)
|