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,67 @@
|
|
|
1
|
+
"""Vorbis metadata inspection utilities for testing audio file metadata."""
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from mutagen.flac import FLAC
|
|
7
|
+
|
|
8
|
+
from audiometa.utils.tool_path_resolver import get_tool_path
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class VorbisMetadataGetter:
|
|
12
|
+
@staticmethod
|
|
13
|
+
def get_raw_metadata(file_path: Path) -> str:
|
|
14
|
+
result = subprocess.run(
|
|
15
|
+
[get_tool_path("metaflac"), "--list", str(file_path)], capture_output=True, text=True, check=True
|
|
16
|
+
)
|
|
17
|
+
return result.stdout
|
|
18
|
+
|
|
19
|
+
@staticmethod
|
|
20
|
+
def get_raw_metadata_without_truncating_null_bytes_but_lower_case_keys(file_path: Path) -> str:
|
|
21
|
+
audio = FLAC(str(file_path))
|
|
22
|
+
lines = []
|
|
23
|
+
lines.append("METADATA block #0")
|
|
24
|
+
lines.append(" type: 0 (STREAMINFO)")
|
|
25
|
+
lines.append(" is last: false")
|
|
26
|
+
lines.append(" length: 34")
|
|
27
|
+
lines.append(" minimum blocksize: 4096 samples")
|
|
28
|
+
lines.append(" maximum blocksize: 4096 samples")
|
|
29
|
+
lines.append(" minimum framesize: 0 bytes")
|
|
30
|
+
lines.append(" maximum framesize: 0 bytes")
|
|
31
|
+
lines.append(" sample_rate: 48000 Hz")
|
|
32
|
+
lines.append(" channels: 1")
|
|
33
|
+
lines.append(" bits-per-sample: 24")
|
|
34
|
+
lines.append(" total samples: 26177")
|
|
35
|
+
lines.append(" MD5 signature: 07598496b7623dfea10aafb241fae1a8")
|
|
36
|
+
lines.append("METADATA block #1")
|
|
37
|
+
lines.append(" type: 4 (VORBIS_COMMENT)")
|
|
38
|
+
lines.append(" is last: false")
|
|
39
|
+
lines.append(" length: 72")
|
|
40
|
+
lines.append(" vendor string: ")
|
|
41
|
+
comments = []
|
|
42
|
+
if audio.tags is not None and hasattr(audio.tags, "keys"):
|
|
43
|
+
for key in sorted(audio.tags.keys()): # type: ignore[union-attr]
|
|
44
|
+
values = audio.tags[key] # type: ignore[union-attr]
|
|
45
|
+
if isinstance(values, list):
|
|
46
|
+
for value in values:
|
|
47
|
+
comments.append(f" comment[{len(comments)}]: {key}={value}")
|
|
48
|
+
else:
|
|
49
|
+
comments.append(f" comment[{len(comments)}]: {key}={values}")
|
|
50
|
+
lines.append(f" comments: {len(comments)}")
|
|
51
|
+
lines.extend(comments)
|
|
52
|
+
lines.append("METADATA block #2")
|
|
53
|
+
lines.append(" type: 1 (PADDING)")
|
|
54
|
+
lines.append(" is last: true")
|
|
55
|
+
lines.append(" length: 1076")
|
|
56
|
+
return "\n".join(lines)
|
|
57
|
+
|
|
58
|
+
@staticmethod
|
|
59
|
+
def get_title(file_path: Path) -> str:
|
|
60
|
+
result = subprocess.run(
|
|
61
|
+
[get_tool_path("metaflac"), "--show-tag=TITLE", str(file_path)], capture_output=True, text=True, check=True
|
|
62
|
+
)
|
|
63
|
+
# Output is like "TITLE=Song Title\n"
|
|
64
|
+
lines = result.stdout.strip().split("\n")
|
|
65
|
+
if lines and "=" in lines[0]:
|
|
66
|
+
return lines[0].split("=", 1)[1]
|
|
67
|
+
return ""
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"""Vorbis metadata setting operations."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from mutagen.flac import FLAC
|
|
7
|
+
|
|
8
|
+
from ..common.external_tool_runner import run_external_tool
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class VorbisMetadataSetter:
|
|
12
|
+
"""Static utility class for Vorbis metadata setting using external tools."""
|
|
13
|
+
|
|
14
|
+
@staticmethod
|
|
15
|
+
def set_multiple_tags(
|
|
16
|
+
file_path: Path, tag_name: str, values: list[str], removing_existing=True, key_lower_case=False
|
|
17
|
+
) -> None:
|
|
18
|
+
"""Set multiple Vorbis comment tags with the same name."""
|
|
19
|
+
|
|
20
|
+
if key_lower_case:
|
|
21
|
+
tag_name = tag_name.lower()
|
|
22
|
+
|
|
23
|
+
if removing_existing:
|
|
24
|
+
try:
|
|
25
|
+
command = ["metaflac", "--remove-tag", tag_name, str(file_path)]
|
|
26
|
+
run_external_tool(command, "metaflac")
|
|
27
|
+
except Exception:
|
|
28
|
+
# Ignore if tags don't exist
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
# Add each value as a separate tag
|
|
32
|
+
for value in values:
|
|
33
|
+
command = ["metaflac", "--set-tag", f"{tag_name}={value}", str(file_path)]
|
|
34
|
+
run_external_tool(command, "metaflac")
|
|
35
|
+
|
|
36
|
+
@staticmethod
|
|
37
|
+
def set_metadata(file_path: Path, metadata: dict[str, Any]) -> None:
|
|
38
|
+
"""Set FLAC metadata using metaflac tool."""
|
|
39
|
+
cmd = ["metaflac"]
|
|
40
|
+
|
|
41
|
+
# Map common metadata keys to metaflac arguments
|
|
42
|
+
key_mapping = {
|
|
43
|
+
"title": "TITLE",
|
|
44
|
+
"artist": "ARTIST",
|
|
45
|
+
"album": "ALBUM",
|
|
46
|
+
"date": "DATE",
|
|
47
|
+
"genre": "GENRE",
|
|
48
|
+
"comment": "COMMENT",
|
|
49
|
+
"track_number": "TRACKNUMBER",
|
|
50
|
+
"bpm": "BPM",
|
|
51
|
+
"composer": "COMPOSER",
|
|
52
|
+
"copyright": "COPYRIGHT",
|
|
53
|
+
"lyrics": "LYRICS",
|
|
54
|
+
"language": "LANGUAGE",
|
|
55
|
+
"rating": "RATING",
|
|
56
|
+
"album_artist": "ALBUMARTIST",
|
|
57
|
+
"mood": "MOOD",
|
|
58
|
+
"key": "KEY",
|
|
59
|
+
"encoder": "ENCODER",
|
|
60
|
+
"url": "URL",
|
|
61
|
+
"isrc": "ISRC",
|
|
62
|
+
"publisher": "PUBLISHER",
|
|
63
|
+
"disc_number": "DISCNUMBER",
|
|
64
|
+
"disc_total": "DISCTOTAL",
|
|
65
|
+
"replaygain": "REPLAYGAIN",
|
|
66
|
+
"archival_location": "ARCHIVAL_LOCATION",
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
# Handle list values first (they need special handling)
|
|
70
|
+
for key, value in metadata.items():
|
|
71
|
+
if isinstance(value, list) and value:
|
|
72
|
+
if key.lower() == "artist":
|
|
73
|
+
VorbisMetadataSetter.set_artists(file_path, value)
|
|
74
|
+
elif key.lower() == "genre":
|
|
75
|
+
VorbisMetadataSetter.set_genres(file_path, value)
|
|
76
|
+
elif key.lower() == "composer":
|
|
77
|
+
VorbisMetadataSetter.set_composers(file_path, value)
|
|
78
|
+
elif key.lower() == "album_artist":
|
|
79
|
+
VorbisMetadataSetter.set_album_artists(file_path, value)
|
|
80
|
+
|
|
81
|
+
# Handle non-list values
|
|
82
|
+
metadata_added = False
|
|
83
|
+
for key, value in metadata.items():
|
|
84
|
+
if key.lower() in key_mapping and not isinstance(value, list):
|
|
85
|
+
cmd.extend([f"--set-tag={key_mapping[key.lower()]}={value}"])
|
|
86
|
+
metadata_added = True
|
|
87
|
+
|
|
88
|
+
# Only run metaflac if metadata was actually added
|
|
89
|
+
if metadata_added:
|
|
90
|
+
cmd.append(str(file_path))
|
|
91
|
+
run_external_tool(cmd, "metaflac")
|
|
92
|
+
|
|
93
|
+
@staticmethod
|
|
94
|
+
def set_genre(file_path: Path, genre_text: str) -> None:
|
|
95
|
+
command = ["metaflac", "--set-tag", f"GENRE={genre_text}", str(file_path)]
|
|
96
|
+
run_external_tool(command, "metaflac")
|
|
97
|
+
|
|
98
|
+
@staticmethod
|
|
99
|
+
def set_comment(file_path: Path, comment: str) -> None:
|
|
100
|
+
command = ["metaflac", "--set-tag", f"COMMENT={comment}", str(file_path)]
|
|
101
|
+
run_external_tool(command, "metaflac")
|
|
102
|
+
|
|
103
|
+
@staticmethod
|
|
104
|
+
def add_title(file_path: Path, title: str, key_lower_case=False) -> None:
|
|
105
|
+
if key_lower_case:
|
|
106
|
+
command = ["metaflac", "--set-tag", f"title={title}", str(file_path)]
|
|
107
|
+
else:
|
|
108
|
+
command = ["metaflac", "--set-tag", f"TITLE={title}", str(file_path)]
|
|
109
|
+
run_external_tool(command, "metaflac")
|
|
110
|
+
|
|
111
|
+
@staticmethod
|
|
112
|
+
def set_artist(file_path: Path, artist: str) -> None:
|
|
113
|
+
command = ["metaflac", "--set-tag", f"ARTIST={artist}", str(file_path)]
|
|
114
|
+
run_external_tool(command, "metaflac")
|
|
115
|
+
|
|
116
|
+
@staticmethod
|
|
117
|
+
def set_album(file_path: Path, album: str) -> None:
|
|
118
|
+
command = ["metaflac", "--set-tag", f"ALBUM={album}", str(file_path)]
|
|
119
|
+
run_external_tool(command, "metaflac")
|
|
120
|
+
|
|
121
|
+
@staticmethod
|
|
122
|
+
def set_lyrics(file_path: Path, lyrics: str) -> None:
|
|
123
|
+
command = ["metaflac", "--set-tag", f"LYRICS={lyrics}", str(file_path)]
|
|
124
|
+
run_external_tool(command, "metaflac")
|
|
125
|
+
|
|
126
|
+
@staticmethod
|
|
127
|
+
def set_language(file_path: Path, language: str) -> None:
|
|
128
|
+
command = ["metaflac", "--set-tag", f"LANGUAGE={language}", str(file_path)]
|
|
129
|
+
run_external_tool(command, "metaflac")
|
|
130
|
+
|
|
131
|
+
@staticmethod
|
|
132
|
+
def set_bpm(file_path: Path, bpm: int) -> None:
|
|
133
|
+
command = ["metaflac", "--set-tag", f"BPM={bpm}", str(file_path)]
|
|
134
|
+
run_external_tool(command, "metaflac")
|
|
135
|
+
|
|
136
|
+
@staticmethod
|
|
137
|
+
def set_release_date(file_path: Path, date_str: str) -> None:
|
|
138
|
+
"""Set Vorbis release date using DATE tag."""
|
|
139
|
+
command = ["metaflac", "--set-tag", f"DATE={date_str}", str(file_path)]
|
|
140
|
+
run_external_tool(command, "metaflac")
|
|
141
|
+
|
|
142
|
+
@staticmethod
|
|
143
|
+
def set_max_metadata(file_path: Path) -> None:
|
|
144
|
+
from pathlib import Path
|
|
145
|
+
|
|
146
|
+
from ..common.external_tool_runner import run_script
|
|
147
|
+
|
|
148
|
+
scripts_dir = Path(__file__).parent.parent.parent.parent / "test" / "helpers" / "scripts"
|
|
149
|
+
run_script("set-vorbis-max-metadata.sh", file_path, scripts_dir)
|
|
150
|
+
|
|
151
|
+
@staticmethod
|
|
152
|
+
def set_artists(
|
|
153
|
+
file_path: Path, artists: list[str], removing_existing=True, key_lower_case=False, in_single_entry=False
|
|
154
|
+
) -> None:
|
|
155
|
+
"""Set multiple Vorbis artists using mutagen."""
|
|
156
|
+
audio = FLAC(str(file_path))
|
|
157
|
+
key = "artist" if key_lower_case else "ARTIST"
|
|
158
|
+
if removing_existing:
|
|
159
|
+
existing: list[str] = []
|
|
160
|
+
else:
|
|
161
|
+
existing_raw = audio.get(key, [])
|
|
162
|
+
if existing_raw is None:
|
|
163
|
+
existing = []
|
|
164
|
+
elif isinstance(existing_raw, str):
|
|
165
|
+
existing = [existing_raw]
|
|
166
|
+
else:
|
|
167
|
+
existing = list(existing_raw)
|
|
168
|
+
if in_single_entry:
|
|
169
|
+
value = "\x00".join(artists)
|
|
170
|
+
existing.append(value)
|
|
171
|
+
else:
|
|
172
|
+
existing.extend(artists)
|
|
173
|
+
audio[key] = existing
|
|
174
|
+
audio.save()
|
|
175
|
+
|
|
176
|
+
@staticmethod
|
|
177
|
+
def set_album_artists(file_path: Path, album_artists: list[str]):
|
|
178
|
+
"""Set multiple Vorbis album artists using external metaflac tool."""
|
|
179
|
+
VorbisMetadataSetter.set_multiple_tags(file_path, "ALBUMARTIST", album_artists)
|
|
180
|
+
|
|
181
|
+
@staticmethod
|
|
182
|
+
def set_composers(file_path: Path, composers: list[str]):
|
|
183
|
+
"""Set multiple Vorbis composers using external metaflac tool."""
|
|
184
|
+
VorbisMetadataSetter.set_multiple_tags(file_path, "COMPOSER", composers)
|
|
185
|
+
|
|
186
|
+
@staticmethod
|
|
187
|
+
def set_genres(file_path: Path, genres: list[str]):
|
|
188
|
+
"""Set multiple Vorbis genres using external metaflac tool."""
|
|
189
|
+
VorbisMetadataSetter.set_multiple_tags(file_path, "GENRE", genres)
|
|
190
|
+
|
|
191
|
+
@staticmethod
|
|
192
|
+
def set_performers(file_path: Path, performers: list[str]):
|
|
193
|
+
"""Set multiple Vorbis performers using external metaflac tool."""
|
|
194
|
+
VorbisMetadataSetter.set_multiple_tags(file_path, "PERFORMER", performers)
|
|
195
|
+
|
|
196
|
+
@staticmethod
|
|
197
|
+
def set_multiple_comments(file_path: Path, comments: list[str]):
|
|
198
|
+
"""Set multiple Vorbis comments using external metaflac tool."""
|
|
199
|
+
VorbisMetadataSetter.set_multiple_tags(file_path, "COMMENT", comments)
|
|
200
|
+
|
|
201
|
+
@staticmethod
|
|
202
|
+
def set_null_bytes_test_metadata(file_path: Path) -> None:
|
|
203
|
+
"""Set test metadata including null bytes for testing null byte handling."""
|
|
204
|
+
# First remove existing ARTIST tags
|
|
205
|
+
try:
|
|
206
|
+
command = ["metaflac", "--remove-tag=ARTIST", str(file_path)]
|
|
207
|
+
run_external_tool(command, "metaflac")
|
|
208
|
+
except Exception:
|
|
209
|
+
# Ignore if tags don't exist
|
|
210
|
+
pass
|
|
211
|
+
|
|
212
|
+
# Set ARTIST tags, one with null bytes
|
|
213
|
+
command = ["metaflac", "--set-tag=ARTIST=Artist\x00with\x00nulls", str(file_path)]
|
|
214
|
+
run_external_tool(command, "metaflac")
|
|
215
|
+
|
|
216
|
+
command = ["metaflac", "--set-tag=ARTIST=Normal Artist", str(file_path)]
|
|
217
|
+
run_external_tool(command, "metaflac")
|
|
218
|
+
|
|
219
|
+
# Set TITLE
|
|
220
|
+
command = ["metaflac", "--set-tag=TITLE=Normal Title", str(file_path)]
|
|
221
|
+
run_external_tool(command, "metaflac")
|
|
File without changes
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
"""Test configuration for audiometa-python tests."""
|
|
2
|
+
|
|
3
|
+
import importlib.util
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
# Import the shared verification script
|
|
10
|
+
project_root = Path(__file__).parent.parent.parent.parent
|
|
11
|
+
verify_script_path = project_root / "scripts" / "verify-system-dependency-versions.py"
|
|
12
|
+
|
|
13
|
+
# Load the module dynamically
|
|
14
|
+
spec = importlib.util.spec_from_file_location("verify_system_dependency_versions", verify_script_path)
|
|
15
|
+
verify_module = importlib.util.module_from_spec(spec)
|
|
16
|
+
sys.modules["verify_system_dependency_versions"] = verify_module
|
|
17
|
+
spec.loader.exec_module(verify_module) # type: ignore[union-attr]
|
|
18
|
+
|
|
19
|
+
verify_dependency_versions = verify_module.verify_dependency_versions
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def pytest_configure(config: pytest.Config) -> None: # noqa: ARG001
|
|
23
|
+
"""Verify system dependency versions match pinned versions before running tests."""
|
|
24
|
+
exit_code = verify_dependency_versions()
|
|
25
|
+
if exit_code != 0:
|
|
26
|
+
pytest.exit("Dependency version verification failed. See errors above.", returncode=exit_code)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def pytest_collection_modifyitems(items):
|
|
30
|
+
"""Reorder test items to ensure proper execution order: unit → integration → e2e."""
|
|
31
|
+
# Define the desired test execution order based on directory structure
|
|
32
|
+
test_order = {"unit": 1, "integration": 2, "e2e": 3}
|
|
33
|
+
|
|
34
|
+
def get_test_priority(item):
|
|
35
|
+
"""Get the priority order for a test item based on its path."""
|
|
36
|
+
test_path = str(item.fspath)
|
|
37
|
+
|
|
38
|
+
# Check for unit tests
|
|
39
|
+
if "/unit/" in test_path:
|
|
40
|
+
return test_order["unit"]
|
|
41
|
+
# Check for integration tests
|
|
42
|
+
if "/integration/" in test_path:
|
|
43
|
+
return test_order["integration"]
|
|
44
|
+
# Check for e2e tests
|
|
45
|
+
if "/e2e/" in test_path:
|
|
46
|
+
return test_order["e2e"]
|
|
47
|
+
# Default priority for other tests (comprehensive, etc.)
|
|
48
|
+
return 0 # Run first (before unit tests)
|
|
49
|
+
|
|
50
|
+
# Sort items by priority
|
|
51
|
+
items.sort(key=get_test_priority)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@pytest.fixture
|
|
55
|
+
def assets_dir() -> Path:
|
|
56
|
+
return Path(__file__).parent.parent.parent / "test" / "assets"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@pytest.fixture
|
|
60
|
+
def sample_mp3_file(assets_dir: Path) -> Path:
|
|
61
|
+
return assets_dir / "sample.mp3"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@pytest.fixture
|
|
65
|
+
def sample_flac_file(assets_dir: Path) -> Path:
|
|
66
|
+
return assets_dir / "sample.flac"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@pytest.fixture
|
|
70
|
+
def sample_wav_file(assets_dir: Path) -> Path:
|
|
71
|
+
return assets_dir / "sample.wav"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@pytest.fixture
|
|
75
|
+
def sample_m4a_file(assets_dir: Path) -> Path:
|
|
76
|
+
return assets_dir / "sample.m4a"
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@pytest.fixture
|
|
80
|
+
def metadata_id3v1_big_mp3(assets_dir: Path) -> Path:
|
|
81
|
+
return assets_dir / "metadata=long a_id3v1_big.mp3"
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@pytest.fixture
|
|
85
|
+
def metadata_id3v1_small_flac(assets_dir: Path) -> Path:
|
|
86
|
+
return assets_dir / "metadata=long a_id3v1_small.flac"
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@pytest.fixture
|
|
90
|
+
def metadata_id3v1_big_flac(assets_dir: Path) -> Path:
|
|
91
|
+
return assets_dir / "metadata=long a_id3v1_big.flac"
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@pytest.fixture
|
|
95
|
+
def metadata_id3v1_small_wav(assets_dir: Path) -> Path:
|
|
96
|
+
return assets_dir / "metadata=long a_id3v1_small.wav"
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@pytest.fixture
|
|
100
|
+
def metadata_id3v1_big_wav(assets_dir: Path) -> Path:
|
|
101
|
+
return assets_dir / "metadata=long a_id3v1_big.wav"
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@pytest.fixture
|
|
105
|
+
def metadata_id3v2_small_mp3(assets_dir: Path) -> Path:
|
|
106
|
+
return assets_dir / "metadata=long a_id3v2_small.mp3"
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@pytest.fixture
|
|
110
|
+
def metadata_id3v2_big_mp3(assets_dir: Path) -> Path:
|
|
111
|
+
return assets_dir / "metadata=long a_id3v2_big.mp3"
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@pytest.fixture
|
|
115
|
+
def metadata_id3v2_small_flac(assets_dir: Path) -> Path:
|
|
116
|
+
return assets_dir / "metadata=long a_id3v2_small.flac"
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@pytest.fixture
|
|
120
|
+
def metadata_id3v2_big_flac(assets_dir: Path) -> Path:
|
|
121
|
+
return assets_dir / "metadata=long a_id3v2_big.flac"
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@pytest.fixture
|
|
125
|
+
def metadata_id3v2_small_wav(assets_dir: Path) -> Path:
|
|
126
|
+
return assets_dir / "metadata=long a_id3v2_small.wav"
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@pytest.fixture
|
|
130
|
+
def metadata_id3v2_big_wav(assets_dir: Path) -> Path:
|
|
131
|
+
return assets_dir / "metadata=long a_id3v2_big.wav"
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
@pytest.fixture
|
|
135
|
+
def metadata_id3v2_and_riff_small_wav(assets_dir: Path) -> Path:
|
|
136
|
+
return assets_dir / "metadata=long a_id3v2_and_riff_small.wav"
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@pytest.fixture
|
|
140
|
+
def metadata_riff_small_wav(assets_dir: Path) -> Path:
|
|
141
|
+
return assets_dir / "metadata=long a_riff_small.wav"
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@pytest.fixture
|
|
145
|
+
def metadata_riff_big_wav(assets_dir: Path) -> Path:
|
|
146
|
+
return assets_dir / "metadata=long a_riff_big.wav"
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
@pytest.fixture
|
|
150
|
+
def metadata_vorbis_small_flac(assets_dir: Path) -> Path:
|
|
151
|
+
return assets_dir / "metadata=long a_vorbis_small.flac"
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@pytest.fixture
|
|
155
|
+
def metadata_vorbis_big_flac(assets_dir: Path) -> Path:
|
|
156
|
+
return assets_dir / "metadata=long a_vorbis_big.flac"
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@pytest.fixture
|
|
160
|
+
def artists_one_two_three_comma_id3v2(assets_dir: Path) -> Path:
|
|
161
|
+
return assets_dir / "artists=One Two Three_comma_id3v2.mp3"
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
@pytest.fixture
|
|
165
|
+
def artists_one_two_three_semicolon_id3v2(assets_dir: Path) -> Path:
|
|
166
|
+
return assets_dir / "artists=One Two Three_semicolon_id3v2.mp3"
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
@pytest.fixture
|
|
170
|
+
def artists_one_two_three_multi_tags_vorbis(assets_dir: Path) -> Path:
|
|
171
|
+
return assets_dir / "artists=One Two Three_muti tags_vorbis.flac"
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@pytest.fixture
|
|
175
|
+
def album_koko_id3v2_mp3(assets_dir: Path) -> Path:
|
|
176
|
+
return assets_dir / "album=koko_id3v2.mp3"
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@pytest.fixture
|
|
180
|
+
def album_koko_id3v2_wav(assets_dir: Path) -> Path:
|
|
181
|
+
return assets_dir / "album=koko_id3v2.wav"
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
@pytest.fixture
|
|
185
|
+
def album_koko_vorbis_flac(assets_dir: Path) -> Path:
|
|
186
|
+
return assets_dir / "album=koko_vorbis.flac"
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
@pytest.fixture
|
|
190
|
+
def genre_code_id3v1_abstract_mp3(assets_dir: Path) -> Path:
|
|
191
|
+
return assets_dir / "genre_code_id3v1=Abstract.mp3"
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
@pytest.fixture
|
|
195
|
+
def genre_code_id3v1_unknown_mp3(assets_dir: Path) -> Path:
|
|
196
|
+
return assets_dir / "genre_code_id3v1=Unknown.mp3"
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
@pytest.fixture
|
|
200
|
+
def duration_1s_mp3(assets_dir: Path) -> Path:
|
|
201
|
+
return assets_dir / "duration=1s.wav"
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
@pytest.fixture
|
|
205
|
+
def duration_182s_mp3(assets_dir: Path) -> Path:
|
|
206
|
+
return assets_dir / "duration=182.mp3"
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
@pytest.fixture
|
|
210
|
+
def duration_335s_flac(assets_dir: Path) -> Path:
|
|
211
|
+
return assets_dir / "duration=335s.flac"
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
@pytest.fixture
|
|
215
|
+
def duration_472s_wav(assets_dir: Path) -> Path:
|
|
216
|
+
return assets_dir / "duration=472s.wav"
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
@pytest.fixture
|
|
220
|
+
def bitrate_320_mp3(assets_dir: Path) -> Path:
|
|
221
|
+
return assets_dir / "bitrate in kbps_big=320.mp3"
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
@pytest.fixture
|
|
225
|
+
def bitrate_946_flac(assets_dir: Path) -> Path:
|
|
226
|
+
return assets_dir / "bitrate in kbps_big=946.flac"
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
@pytest.fixture
|
|
230
|
+
def bitrate_1411_wav(assets_dir: Path) -> Path:
|
|
231
|
+
return assets_dir / "bitrate in kbps_big=1411.wav"
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
@pytest.fixture
|
|
235
|
+
def bitrate_192_mp3(assets_dir: Path) -> Path:
|
|
236
|
+
return assets_dir / "bitrate in kbps_small=192.mp3"
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
@pytest.fixture
|
|
240
|
+
def bitrate_723_flac(assets_dir: Path) -> Path:
|
|
241
|
+
return assets_dir / "bitrate in kbps_small=723.flac"
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
@pytest.fixture
|
|
245
|
+
def bitrate_1152_wav(assets_dir: Path) -> Path:
|
|
246
|
+
return assets_dir / "bitrate in kbps_small=1152.wav"
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
@pytest.fixture
|
|
250
|
+
def size_small_mp3(assets_dir: Path) -> Path:
|
|
251
|
+
return assets_dir / "size_small=0.01mo.mp3"
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
@pytest.fixture
|
|
255
|
+
def size_big_mp3(assets_dir: Path) -> Path:
|
|
256
|
+
return assets_dir / "size_big=9.98mo.mp3"
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
@pytest.fixture
|
|
260
|
+
def size_small_flac(assets_dir: Path) -> Path:
|
|
261
|
+
return assets_dir / "size_small=0.05mo.flac"
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
@pytest.fixture
|
|
265
|
+
def size_big_flac(assets_dir: Path) -> Path:
|
|
266
|
+
return assets_dir / "size_big=26.6mo.flac"
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
@pytest.fixture
|
|
270
|
+
def size_small_wav(assets_dir: Path) -> Path:
|
|
271
|
+
return assets_dir / "size_small=0.08mo.wav"
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
@pytest.fixture
|
|
275
|
+
def size_big_wav(assets_dir: Path) -> Path:
|
|
276
|
+
return assets_dir / "size_big=79.55mo.wav"
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Error handling tests for CLI
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@pytest.mark.e2e
|
|
10
|
+
class TestCLICommandStructureErrors:
|
|
11
|
+
def test_cli_unified_with_no_headers_technical_flags(self):
|
|
12
|
+
with temp_file_with_metadata({}, "mp3") as temp_file_path:
|
|
13
|
+
result = subprocess.run(
|
|
14
|
+
[sys.executable, "-m", "audiometa", "unified", str(temp_file_path), "--no-headers", "--no-technical"],
|
|
15
|
+
capture_output=True,
|
|
16
|
+
text=True,
|
|
17
|
+
check=False,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
# Should fail - unified command doesn't accept these flags
|
|
21
|
+
assert result.returncode != 0
|
|
22
|
+
stderr_output = result.stderr.lower()
|
|
23
|
+
assert "unrecognized arguments" in stderr_output
|
|
24
|
+
|
|
25
|
+
def test_cli_read_help_flag(self):
|
|
26
|
+
result = subprocess.run(
|
|
27
|
+
[sys.executable, "-m", "audiometa", "read", "--help"], capture_output=True, text=True, check=False
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
# Should show read command help
|
|
31
|
+
assert result.returncode == 0
|
|
32
|
+
stdout_output = result.stdout.lower()
|
|
33
|
+
assert "read" in stdout_output
|
|
34
|
+
assert "files" in stdout_output
|
|
35
|
+
|
|
36
|
+
def test_cli_recursive_with_single_file(self):
|
|
37
|
+
"""Test CLI recursive flag with single file (should work but be redundant)."""
|
|
38
|
+
with temp_file_with_metadata({}, "mp3") as temp_file_path:
|
|
39
|
+
result = subprocess.run(
|
|
40
|
+
[sys.executable, "-m", "audiometa", "read", str(temp_file_path), "--recursive"],
|
|
41
|
+
capture_output=True,
|
|
42
|
+
text=True,
|
|
43
|
+
check=False,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# Should succeed - recursive with single file is valid
|
|
47
|
+
assert result.returncode == 0
|
|
48
|
+
assert len(result.stdout.strip()) > 0
|
|
49
|
+
|
|
50
|
+
def test_cli_invalid_command(self):
|
|
51
|
+
result = subprocess.run(
|
|
52
|
+
[sys.executable, "-m", "audiometa", "invalidcommand"], capture_output=True, text=True, check=False
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# Should fail due to invalid command
|
|
56
|
+
assert result.returncode != 0
|
|
57
|
+
stderr_output = result.stderr.lower()
|
|
58
|
+
assert "invalid choice" in stderr_output or "error" in stderr_output
|
|
59
|
+
|
|
60
|
+
def test_cli_no_command(self):
|
|
61
|
+
result = subprocess.run([sys.executable, "-m", "audiometa"], capture_output=True, text=True, check=False)
|
|
62
|
+
|
|
63
|
+
# Should show help and exit with code 1
|
|
64
|
+
assert result.returncode == 1
|
|
65
|
+
stdout_output = result.stdout.lower()
|
|
66
|
+
assert "usage" in stdout_output or "help" in stdout_output
|
|
67
|
+
|
|
68
|
+
def test_cli_help_flag(self):
|
|
69
|
+
result = subprocess.run(
|
|
70
|
+
[sys.executable, "-m", "audiometa", "--help"], capture_output=True, text=True, check=False
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
# Should show help and exit successfully
|
|
74
|
+
assert result.returncode == 0
|
|
75
|
+
stdout_output = result.stdout.lower()
|
|
76
|
+
assert "usage" in stdout_output
|
|
77
|
+
assert "help" in stdout_output
|