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,130 @@
|
|
|
1
|
+
import stat
|
|
2
|
+
import subprocess
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@pytest.mark.e2e
|
|
11
|
+
class TestCLIFileAccessErrors:
|
|
12
|
+
def test_cli_read_nonexistent_file(self, tmp_path):
|
|
13
|
+
nonexistent_file = tmp_path / "nonexistent.mp3"
|
|
14
|
+
result = subprocess.run(
|
|
15
|
+
[sys.executable, "-m", "audiometa", "read", str(nonexistent_file)],
|
|
16
|
+
capture_output=True,
|
|
17
|
+
text=True,
|
|
18
|
+
check=False,
|
|
19
|
+
)
|
|
20
|
+
assert result.returncode == 1
|
|
21
|
+
assert "error" in result.stderr.lower()
|
|
22
|
+
|
|
23
|
+
def test_cli_read_with_continue_on_error(self, tmp_path):
|
|
24
|
+
nonexistent_file = tmp_path / "nonexistent.mp3"
|
|
25
|
+
result = subprocess.run(
|
|
26
|
+
[sys.executable, "-m", "audiometa", "read", str(nonexistent_file), "--continue-on-error"],
|
|
27
|
+
capture_output=True,
|
|
28
|
+
text=True,
|
|
29
|
+
check=False,
|
|
30
|
+
)
|
|
31
|
+
assert result.returncode == 0
|
|
32
|
+
|
|
33
|
+
def test_cli_output_file_permission_error(self, sample_mp3_file, tmp_path):
|
|
34
|
+
# Create read-only file (more reliable cross-platform than read-only directory)
|
|
35
|
+
output_file = tmp_path / "output.json"
|
|
36
|
+
output_file.write_text("existing content")
|
|
37
|
+
# Make file read-only
|
|
38
|
+
output_file.chmod(stat.S_IREAD)
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
# Try to write output to read-only file
|
|
42
|
+
result = subprocess.run(
|
|
43
|
+
[sys.executable, "-m", "audiometa", "read", str(sample_mp3_file), "--output", str(output_file)],
|
|
44
|
+
capture_output=True,
|
|
45
|
+
text=True,
|
|
46
|
+
check=False,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# Should fail due to permission error
|
|
50
|
+
assert result.returncode != 0
|
|
51
|
+
# Should contain error message about permission or writing
|
|
52
|
+
stderr_output = result.stderr.lower()
|
|
53
|
+
assert "error" in stderr_output or "permission" in stderr_output or "cannot" in stderr_output
|
|
54
|
+
finally:
|
|
55
|
+
# Restore write permissions for cleanup
|
|
56
|
+
output_file.chmod(stat.S_IWRITE | stat.S_IREAD)
|
|
57
|
+
|
|
58
|
+
def test_cli_output_file_permission_error_with_continue(self, sample_mp3_file, tmp_path):
|
|
59
|
+
# Create read-only file (more reliable cross-platform than read-only directory)
|
|
60
|
+
output_file = tmp_path / "output.json"
|
|
61
|
+
output_file.write_text("existing content")
|
|
62
|
+
# Make file read-only
|
|
63
|
+
output_file.chmod(stat.S_IREAD)
|
|
64
|
+
|
|
65
|
+
try:
|
|
66
|
+
# Try to write output with continue-on-error flag
|
|
67
|
+
result = subprocess.run(
|
|
68
|
+
[
|
|
69
|
+
sys.executable,
|
|
70
|
+
"-m",
|
|
71
|
+
"audiometa",
|
|
72
|
+
"read",
|
|
73
|
+
str(sample_mp3_file),
|
|
74
|
+
"--output",
|
|
75
|
+
str(output_file),
|
|
76
|
+
"--continue-on-error",
|
|
77
|
+
],
|
|
78
|
+
capture_output=True,
|
|
79
|
+
text=True,
|
|
80
|
+
check=False,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# Should succeed overall (exit code 0) because continue-on-error prevents exit
|
|
84
|
+
assert result.returncode == 0
|
|
85
|
+
stderr_output = result.stderr.lower()
|
|
86
|
+
assert "error" in stderr_output or "permission" in stderr_output or "denied" in stderr_output
|
|
87
|
+
finally:
|
|
88
|
+
# Restore write permissions for cleanup
|
|
89
|
+
output_file.chmod(stat.S_IWRITE | stat.S_IREAD)
|
|
90
|
+
|
|
91
|
+
def test_cli_output_file_nonexistent_directory(self, sample_mp3_file, tmp_path):
|
|
92
|
+
# Try to write to a file in a nonexistent directory
|
|
93
|
+
nonexistent_dir = tmp_path / "nonexistent" / "subdir"
|
|
94
|
+
output_file = nonexistent_dir / "output.json"
|
|
95
|
+
|
|
96
|
+
result = subprocess.run(
|
|
97
|
+
[sys.executable, "-m", "audiometa", "read", str(sample_mp3_file), "--output", str(output_file)],
|
|
98
|
+
capture_output=True,
|
|
99
|
+
text=True,
|
|
100
|
+
check=False,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# Should fail due to nonexistent directory
|
|
104
|
+
assert result.returncode != 0
|
|
105
|
+
stderr_output = result.stderr.lower()
|
|
106
|
+
assert "error" in stderr_output
|
|
107
|
+
|
|
108
|
+
def test_cli_output_file_unified_command(self, tmp_path):
|
|
109
|
+
with temp_file_with_metadata({}, "mp3") as temp_file_path:
|
|
110
|
+
# Create read-only file (more reliable cross-platform than read-only directory)
|
|
111
|
+
output_file = tmp_path / "output.json"
|
|
112
|
+
output_file.write_text("existing content")
|
|
113
|
+
# Make file read-only
|
|
114
|
+
output_file.chmod(stat.S_IREAD)
|
|
115
|
+
|
|
116
|
+
try:
|
|
117
|
+
result = subprocess.run(
|
|
118
|
+
[sys.executable, "-m", "audiometa", "unified", str(temp_file_path), "--output", str(output_file)],
|
|
119
|
+
capture_output=True,
|
|
120
|
+
text=True,
|
|
121
|
+
check=False,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
# Should fail due to permission error
|
|
125
|
+
assert result.returncode != 0
|
|
126
|
+
stderr_output = result.stderr.lower()
|
|
127
|
+
assert "error" in stderr_output or "permission" in stderr_output or "cannot" in stderr_output
|
|
128
|
+
finally:
|
|
129
|
+
# Restore write permissions for cleanup
|
|
130
|
+
output_file.chmod(stat.S_IWRITE | stat.S_IREAD)
|
|
@@ -0,0 +1,118 @@
|
|
|
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 TestCLIFormatOutputErrors:
|
|
11
|
+
def test_cli_yaml_format_with_pyyaml(self, capsys):
|
|
12
|
+
# Test the format_output function when PyYAML is installed
|
|
13
|
+
import json
|
|
14
|
+
|
|
15
|
+
from audiometa.cli import format_output
|
|
16
|
+
|
|
17
|
+
# Test that format_output uses YAML when yaml is available
|
|
18
|
+
test_data = {"test": "data", "number": 42}
|
|
19
|
+
result = format_output(test_data, "yaml")
|
|
20
|
+
|
|
21
|
+
# Should NOT be JSON (YAML format is different)
|
|
22
|
+
json_result = json.dumps(test_data, indent=2)
|
|
23
|
+
assert result != json_result
|
|
24
|
+
|
|
25
|
+
# Should NOT print warning to stderr
|
|
26
|
+
captured = capsys.readouterr()
|
|
27
|
+
assert "Warning: PyYAML not installed" not in captured.err
|
|
28
|
+
assert "falling back" not in captured.err
|
|
29
|
+
|
|
30
|
+
# Result should contain YAML formatting (includes colons for key-value pairs)
|
|
31
|
+
assert "test: data" in result or "test: 'data'" in result
|
|
32
|
+
assert "number: 42" in result
|
|
33
|
+
|
|
34
|
+
def test_cli_yaml_format_without_pyyaml(self, monkeypatch, capsys):
|
|
35
|
+
# Test the format_output function directly with mocked yaml import
|
|
36
|
+
import sys
|
|
37
|
+
|
|
38
|
+
from audiometa.cli import format_output
|
|
39
|
+
|
|
40
|
+
# Remove yaml from sys.modules if it exists
|
|
41
|
+
if "yaml" in sys.modules:
|
|
42
|
+
del sys.modules["yaml"]
|
|
43
|
+
|
|
44
|
+
# Mock the import to raise ImportError
|
|
45
|
+
original_import = __import__
|
|
46
|
+
|
|
47
|
+
def mock_import(name, *args, **kwargs):
|
|
48
|
+
if name == "yaml":
|
|
49
|
+
msg = "No module named 'yaml'"
|
|
50
|
+
raise ImportError(msg)
|
|
51
|
+
return original_import(name, *args, **kwargs)
|
|
52
|
+
|
|
53
|
+
monkeypatch.setattr("builtins.__import__", mock_import)
|
|
54
|
+
|
|
55
|
+
# Test that format_output falls back to JSON when yaml is not available
|
|
56
|
+
test_data = {"test": "data"}
|
|
57
|
+
result = format_output(test_data, "yaml")
|
|
58
|
+
|
|
59
|
+
# Should return JSON format
|
|
60
|
+
import json
|
|
61
|
+
|
|
62
|
+
expected_json = json.dumps(test_data, indent=2)
|
|
63
|
+
assert result == expected_json
|
|
64
|
+
|
|
65
|
+
# Should print warning to stderr
|
|
66
|
+
captured = capsys.readouterr()
|
|
67
|
+
assert "Warning: PyYAML not installed, falling back to JSON" in captured.err
|
|
68
|
+
|
|
69
|
+
def test_cli_invalid_format_argument(self):
|
|
70
|
+
result = subprocess.run(
|
|
71
|
+
[sys.executable, "-m", "audiometa", "read", "nonexistent.mp3", "--format", "invalid"],
|
|
72
|
+
capture_output=True,
|
|
73
|
+
text=True,
|
|
74
|
+
check=False,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# Should fail due to invalid format choice
|
|
78
|
+
assert result.returncode != 0
|
|
79
|
+
# argparse should show error about invalid choice
|
|
80
|
+
stderr_output = result.stderr.lower()
|
|
81
|
+
assert "invalid choice" in stderr_output or "error" in stderr_output
|
|
82
|
+
|
|
83
|
+
def test_cli_invalid_output_path_empty_string(self):
|
|
84
|
+
with temp_file_with_metadata({}, "mp3") as temp_file_path:
|
|
85
|
+
result = subprocess.run(
|
|
86
|
+
[sys.executable, "-m", "audiometa", "read", str(temp_file_path), "--output", ""],
|
|
87
|
+
capture_output=True,
|
|
88
|
+
text=True,
|
|
89
|
+
check=False,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# Should succeed - empty output path means stdout
|
|
93
|
+
assert result.returncode == 0
|
|
94
|
+
assert len(result.stdout.strip()) > 0
|
|
95
|
+
|
|
96
|
+
def test_cli_conflicting_format_options_read(self):
|
|
97
|
+
with temp_file_with_metadata({}, "mp3") as temp_file_path:
|
|
98
|
+
result = subprocess.run(
|
|
99
|
+
[
|
|
100
|
+
sys.executable,
|
|
101
|
+
"-m",
|
|
102
|
+
"audiometa",
|
|
103
|
+
"read",
|
|
104
|
+
str(temp_file_path),
|
|
105
|
+
"--format",
|
|
106
|
+
"table",
|
|
107
|
+
"--no-headers",
|
|
108
|
+
"--no-technical",
|
|
109
|
+
],
|
|
110
|
+
capture_output=True,
|
|
111
|
+
text=True,
|
|
112
|
+
check=False,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# Should succeed - these options are compatible
|
|
116
|
+
assert result.returncode == 0
|
|
117
|
+
# Table format with no headers/technical should still work
|
|
118
|
+
assert len(result.stdout.strip()) > 0
|
|
@@ -0,0 +1,172 @@
|
|
|
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 TestCLIInputValidationErrors:
|
|
11
|
+
def test_cli_invalid_rating_value_negative(self):
|
|
12
|
+
with temp_file_with_metadata({}, "mp3") as temp_file_path:
|
|
13
|
+
result = subprocess.run(
|
|
14
|
+
[sys.executable, "-m", "audiometa", "write", str(temp_file_path), "--rating", "-5"],
|
|
15
|
+
capture_output=True,
|
|
16
|
+
text=True,
|
|
17
|
+
check=False,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
# Should fail due to negative rating - CLI validates explicitly
|
|
21
|
+
assert result.returncode != 0
|
|
22
|
+
stderr_output = result.stderr.lower()
|
|
23
|
+
assert "error" in stderr_output
|
|
24
|
+
assert "rating" in stderr_output
|
|
25
|
+
|
|
26
|
+
def test_cli_rating_value_allowed_without_normalization(self):
|
|
27
|
+
with temp_file_with_metadata({}, "mp3") as temp_file_path:
|
|
28
|
+
# Any integer rating value should be allowed when normalized_rating_max_value is not provided
|
|
29
|
+
# Using a valid ID3v2 profile value (196 = 4 stars in BASE_255_NON_PROPORTIONAL profile)
|
|
30
|
+
result = subprocess.run(
|
|
31
|
+
[sys.executable, "-m", "audiometa", "write", str(temp_file_path), "--rating", "196"],
|
|
32
|
+
capture_output=True,
|
|
33
|
+
text=True,
|
|
34
|
+
check=False,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
# Should succeed - no write profile validation when normalized_rating_max_value is None
|
|
38
|
+
assert result.returncode == 0
|
|
39
|
+
|
|
40
|
+
def test_cli_rating_whole_number_float_allowed(self):
|
|
41
|
+
with temp_file_with_metadata({}, "mp3") as temp_file_path:
|
|
42
|
+
# Whole-number floats like 196.0 should be accepted and converted to integers
|
|
43
|
+
result = subprocess.run(
|
|
44
|
+
[sys.executable, "-m", "audiometa", "write", str(temp_file_path), "--rating", "196.0"],
|
|
45
|
+
capture_output=True,
|
|
46
|
+
text=True,
|
|
47
|
+
check=False,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# Should succeed - whole-number floats are converted to integers in raw mode
|
|
51
|
+
assert result.returncode == 0
|
|
52
|
+
|
|
53
|
+
def test_cli_rating_value_non_multiple_of_10_allowed(self):
|
|
54
|
+
with temp_file_with_metadata({}, "mp3") as temp_file_path:
|
|
55
|
+
result = subprocess.run(
|
|
56
|
+
[sys.executable, "-m", "audiometa", "write", str(temp_file_path), "--rating", "37"],
|
|
57
|
+
capture_output=True,
|
|
58
|
+
text=True,
|
|
59
|
+
check=False,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# Should succeed - no write profile validation when normalized_rating_max_value is None
|
|
63
|
+
assert result.returncode == 0
|
|
64
|
+
|
|
65
|
+
def test_cli_invalid_rating_value_non_numeric(self):
|
|
66
|
+
with temp_file_with_metadata({}, "mp3") as temp_file_path:
|
|
67
|
+
result = subprocess.run(
|
|
68
|
+
[sys.executable, "-m", "audiometa", "write", str(temp_file_path), "--rating", "not-a-number"],
|
|
69
|
+
capture_output=True,
|
|
70
|
+
text=True,
|
|
71
|
+
check=False,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# Should fail due to non-numeric rating
|
|
75
|
+
assert result.returncode != 0
|
|
76
|
+
stderr_output = result.stderr.lower()
|
|
77
|
+
assert "invalid" in stderr_output.lower() or "error" in stderr_output
|
|
78
|
+
|
|
79
|
+
def test_cli_valid_rating_multiple_of_10(self):
|
|
80
|
+
with temp_file_with_metadata({}, "mp3") as temp_file_path:
|
|
81
|
+
result = subprocess.run(
|
|
82
|
+
[sys.executable, "-m", "audiometa", "write", str(temp_file_path), "--rating", "128"],
|
|
83
|
+
capture_output=True,
|
|
84
|
+
text=True,
|
|
85
|
+
check=False,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
# Should succeed - any integer rating value is allowed
|
|
89
|
+
assert result.returncode == 0
|
|
90
|
+
assert "updated metadata" in result.stdout.lower()
|
|
91
|
+
|
|
92
|
+
def test_cli_invalid_year_value_non_numeric(self):
|
|
93
|
+
with temp_file_with_metadata({}, "mp3") as temp_file_path:
|
|
94
|
+
result = subprocess.run(
|
|
95
|
+
[sys.executable, "-m", "audiometa", "write", str(temp_file_path), "--year", "not-a-year"],
|
|
96
|
+
capture_output=True,
|
|
97
|
+
text=True,
|
|
98
|
+
check=False,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# Should fail due to non-numeric year
|
|
102
|
+
assert result.returncode != 0
|
|
103
|
+
stderr_output = result.stderr.lower()
|
|
104
|
+
assert "invalid" in stderr_output.lower() or "error" in stderr_output
|
|
105
|
+
|
|
106
|
+
def test_cli_invalid_year_value_negative(self):
|
|
107
|
+
with temp_file_with_metadata({}, "mp3") as temp_file_path:
|
|
108
|
+
result = subprocess.run(
|
|
109
|
+
[sys.executable, "-m", "audiometa", "write", str(temp_file_path), "--year", "-2023"],
|
|
110
|
+
capture_output=True,
|
|
111
|
+
text=True,
|
|
112
|
+
check=False,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# Should fail due to invalid date format (negative year doesn't match YYYY format)
|
|
116
|
+
assert result.returncode != 0
|
|
117
|
+
stderr_output = result.stderr.lower()
|
|
118
|
+
assert "error" in stderr_output or "invalid" in stderr_output
|
|
119
|
+
|
|
120
|
+
def test_cli_valid_year_value_future(self):
|
|
121
|
+
with temp_file_with_metadata({}, "mp3") as temp_file_path:
|
|
122
|
+
future_year = str(2030 + 1) # Future year is valid
|
|
123
|
+
result = subprocess.run(
|
|
124
|
+
[sys.executable, "-m", "audiometa", "write", str(temp_file_path), "--year", future_year],
|
|
125
|
+
capture_output=True,
|
|
126
|
+
text=True,
|
|
127
|
+
check=False,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# Should succeed - future years are allowed
|
|
131
|
+
assert result.returncode == 0
|
|
132
|
+
assert "updated metadata" in result.stdout.lower()
|
|
133
|
+
|
|
134
|
+
def test_cli_write_no_metadata_fields(self):
|
|
135
|
+
with temp_file_with_metadata({}, "mp3") as temp_file_path:
|
|
136
|
+
result = subprocess.run(
|
|
137
|
+
[sys.executable, "-m", "audiometa", "write", str(temp_file_path)],
|
|
138
|
+
capture_output=True,
|
|
139
|
+
text=True,
|
|
140
|
+
check=False,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
# Should fail due to no metadata fields
|
|
144
|
+
assert result.returncode != 0
|
|
145
|
+
stderr_output = result.stderr.lower()
|
|
146
|
+
assert "no metadata fields specified" in stderr_output
|
|
147
|
+
|
|
148
|
+
def test_cli_write_empty_title_artist_album(self):
|
|
149
|
+
with temp_file_with_metadata({}, "mp3") as temp_file_path:
|
|
150
|
+
result = subprocess.run(
|
|
151
|
+
[
|
|
152
|
+
sys.executable,
|
|
153
|
+
"-m",
|
|
154
|
+
"audiometa",
|
|
155
|
+
"write",
|
|
156
|
+
str(temp_file_path),
|
|
157
|
+
"--title",
|
|
158
|
+
"",
|
|
159
|
+
"--artist",
|
|
160
|
+
"",
|
|
161
|
+
"--album",
|
|
162
|
+
"",
|
|
163
|
+
],
|
|
164
|
+
capture_output=True,
|
|
165
|
+
text=True,
|
|
166
|
+
check=False,
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
# Should fail - empty strings are not considered valid metadata
|
|
170
|
+
assert result.returncode != 0
|
|
171
|
+
stderr_output = result.stderr.lower()
|
|
172
|
+
assert "no metadata fields specified" in stderr_output
|
|
@@ -0,0 +1,49 @@
|
|
|
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 TestCLIMissingFieldsValidation:
|
|
11
|
+
def test_cli_write_no_metadata_fields(self):
|
|
12
|
+
with temp_file_with_metadata({}, "mp3") as temp_file_path:
|
|
13
|
+
result = subprocess.run(
|
|
14
|
+
[sys.executable, "-m", "audiometa", "write", str(temp_file_path)],
|
|
15
|
+
capture_output=True,
|
|
16
|
+
text=True,
|
|
17
|
+
check=False,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
# Should fail due to no metadata fields
|
|
21
|
+
assert result.returncode != 0
|
|
22
|
+
stderr_output = result.stderr.lower()
|
|
23
|
+
assert "no metadata fields specified" in stderr_output
|
|
24
|
+
|
|
25
|
+
def test_cli_write_empty_title_artist_album(self):
|
|
26
|
+
with temp_file_with_metadata({}, "mp3") as temp_file_path:
|
|
27
|
+
result = subprocess.run(
|
|
28
|
+
[
|
|
29
|
+
sys.executable,
|
|
30
|
+
"-m",
|
|
31
|
+
"audiometa",
|
|
32
|
+
"write",
|
|
33
|
+
str(temp_file_path),
|
|
34
|
+
"--title",
|
|
35
|
+
"",
|
|
36
|
+
"--artist",
|
|
37
|
+
"",
|
|
38
|
+
"--album",
|
|
39
|
+
"",
|
|
40
|
+
],
|
|
41
|
+
capture_output=True,
|
|
42
|
+
text=True,
|
|
43
|
+
check=False,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# Should fail - empty strings are not considered valid metadata
|
|
47
|
+
assert result.returncode != 0
|
|
48
|
+
stderr_output = result.stderr.lower()
|
|
49
|
+
assert "no metadata fields specified" in stderr_output
|
|
@@ -0,0 +1,160 @@
|
|
|
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 TestCLIMultipleFilesErrors:
|
|
11
|
+
def test_cli_multiple_files_mixed_success_failure_continue_on_error(
|
|
12
|
+
self, sample_mp3_file, sample_wav_file, tmp_path
|
|
13
|
+
):
|
|
14
|
+
# Create unsupported file type
|
|
15
|
+
unsupported_file = tmp_path / "unsupported.txt"
|
|
16
|
+
unsupported_file.write_text("not audio")
|
|
17
|
+
|
|
18
|
+
# Run CLI with mixed files and continue_on_error=True
|
|
19
|
+
result = subprocess.run(
|
|
20
|
+
[
|
|
21
|
+
sys.executable,
|
|
22
|
+
"-m",
|
|
23
|
+
"audiometa",
|
|
24
|
+
"read",
|
|
25
|
+
str(sample_mp3_file),
|
|
26
|
+
str(sample_wav_file),
|
|
27
|
+
str(unsupported_file),
|
|
28
|
+
"--continue-on-error",
|
|
29
|
+
],
|
|
30
|
+
capture_output=True,
|
|
31
|
+
text=True,
|
|
32
|
+
check=False,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# Should succeed overall (exit code 0)
|
|
36
|
+
assert result.returncode == 0
|
|
37
|
+
|
|
38
|
+
# Should contain error messages for failed files
|
|
39
|
+
stderr_output = result.stderr.lower()
|
|
40
|
+
assert "error processing" in stderr_output or "error" in stderr_output
|
|
41
|
+
|
|
42
|
+
# Should contain output for successful files (at least some JSON output)
|
|
43
|
+
assert "{" in result.stdout or "}" in result.stdout
|
|
44
|
+
|
|
45
|
+
def test_cli_multiple_files_mixed_success_failure_no_continue(self, sample_mp3_file, tmp_path):
|
|
46
|
+
# Create unsupported file type
|
|
47
|
+
unsupported_file = tmp_path / "unsupported.txt"
|
|
48
|
+
unsupported_file.write_text("not audio")
|
|
49
|
+
|
|
50
|
+
# Run CLI with mixed files and continue_on_error=False (default)
|
|
51
|
+
result = subprocess.run(
|
|
52
|
+
[sys.executable, "-m", "audiometa", "read", str(sample_mp3_file), str(unsupported_file)],
|
|
53
|
+
capture_output=True,
|
|
54
|
+
text=True,
|
|
55
|
+
check=False,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# Should fail overall (exit code 1) due to the unsupported file
|
|
59
|
+
assert result.returncode == 1
|
|
60
|
+
|
|
61
|
+
# Should contain error message
|
|
62
|
+
stderr_output = result.stderr.lower()
|
|
63
|
+
assert "error" in stderr_output
|
|
64
|
+
|
|
65
|
+
def test_cli_multiple_files_all_fail_continue_on_error(self, tmp_path):
|
|
66
|
+
# Create unsupported files
|
|
67
|
+
unsupported1 = tmp_path / "unsupported1.txt"
|
|
68
|
+
unsupported1.write_text("not audio")
|
|
69
|
+
|
|
70
|
+
unsupported2 = tmp_path / "unsupported2.jpg"
|
|
71
|
+
unsupported2.write_text("not audio")
|
|
72
|
+
|
|
73
|
+
# Run CLI with all failing files and continue_on_error=True
|
|
74
|
+
result = subprocess.run(
|
|
75
|
+
[sys.executable, "-m", "audiometa", "read", str(unsupported1), str(unsupported2), "--continue-on-error"],
|
|
76
|
+
capture_output=True,
|
|
77
|
+
text=True,
|
|
78
|
+
check=False,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
# Should succeed overall (exit code 0) despite all files failing
|
|
82
|
+
assert result.returncode == 0
|
|
83
|
+
|
|
84
|
+
# Should contain error messages for all failed files
|
|
85
|
+
stderr_output = result.stderr.lower()
|
|
86
|
+
assert "error" in stderr_output
|
|
87
|
+
|
|
88
|
+
def test_cli_multiple_files_write_mixed_success_failure(self, tmp_path):
|
|
89
|
+
with temp_file_with_metadata({}, "mp3") as temp_mp3_path, temp_file_with_metadata({}, "flac") as temp_flac_path:
|
|
90
|
+
# Create unsupported file type
|
|
91
|
+
unsupported_file = tmp_path / "unsupported.txt"
|
|
92
|
+
unsupported_file.write_text("not audio")
|
|
93
|
+
|
|
94
|
+
# Run CLI write command with mixed files
|
|
95
|
+
result = subprocess.run(
|
|
96
|
+
[
|
|
97
|
+
sys.executable,
|
|
98
|
+
"-m",
|
|
99
|
+
"audiometa",
|
|
100
|
+
"write",
|
|
101
|
+
str(temp_mp3_path),
|
|
102
|
+
str(temp_flac_path),
|
|
103
|
+
str(unsupported_file),
|
|
104
|
+
"--title",
|
|
105
|
+
"Test Title",
|
|
106
|
+
"--continue-on-error",
|
|
107
|
+
],
|
|
108
|
+
capture_output=True,
|
|
109
|
+
text=True,
|
|
110
|
+
check=False,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
# Should succeed overall (exit code 0)
|
|
114
|
+
assert result.returncode == 0
|
|
115
|
+
|
|
116
|
+
# Should contain success messages for valid files
|
|
117
|
+
stdout_output = result.stdout.lower()
|
|
118
|
+
assert (
|
|
119
|
+
"updated metadata for" in stdout_output
|
|
120
|
+
), f"Expected success message in stdout but got:\nSTDOUT: {result.stdout}\nSTDERR: {result.stderr}"
|
|
121
|
+
|
|
122
|
+
# Should contain error message for unsupported file
|
|
123
|
+
stderr_output = result.stderr.lower()
|
|
124
|
+
assert "error" in stderr_output
|
|
125
|
+
|
|
126
|
+
def test_cli_multiple_files_delete_mixed_success_failure(self, tmp_path):
|
|
127
|
+
with temp_file_with_metadata({}, "mp3") as temp_mp3_path, temp_file_with_metadata({}, "wav") as temp_wav_path:
|
|
128
|
+
# Create unsupported file type
|
|
129
|
+
unsupported_file = tmp_path / "unsupported.txt"
|
|
130
|
+
unsupported_file.write_text("not audio")
|
|
131
|
+
|
|
132
|
+
# Run CLI delete command with mixed files
|
|
133
|
+
result = subprocess.run(
|
|
134
|
+
[
|
|
135
|
+
sys.executable,
|
|
136
|
+
"-m",
|
|
137
|
+
"audiometa",
|
|
138
|
+
"delete",
|
|
139
|
+
str(temp_mp3_path),
|
|
140
|
+
str(temp_wav_path),
|
|
141
|
+
str(unsupported_file),
|
|
142
|
+
"--continue-on-error",
|
|
143
|
+
],
|
|
144
|
+
capture_output=True,
|
|
145
|
+
text=True,
|
|
146
|
+
check=False,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
# Should succeed overall (exit code 0)
|
|
150
|
+
assert result.returncode == 0
|
|
151
|
+
|
|
152
|
+
# Should contain messages for processed files
|
|
153
|
+
stdout_output = result.stdout.lower()
|
|
154
|
+
assert (
|
|
155
|
+
"deleted" in stdout_output or "found" in stdout_output
|
|
156
|
+
), f"Expected success message in stdout but got:\nSTDOUT: {result.stdout}\nSTDERR: {result.stderr}"
|
|
157
|
+
|
|
158
|
+
# Should contain error message for unsupported file
|
|
159
|
+
stderr_output = result.stderr.lower()
|
|
160
|
+
assert "error" in stderr_output
|