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,83 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from audiometa import validate_metadata_for_update
|
|
4
|
+
from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@pytest.mark.unit
|
|
8
|
+
class TestValidateMetadata:
|
|
9
|
+
"""Test the integrate validate_metadata_for_update function.
|
|
10
|
+
|
|
11
|
+
This class tests the high-level validation function that integrates multiple validation layers. Specific field
|
|
12
|
+
validation (type, format) is tested separately in metadata_field/ test files.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def test_empty_dict_raises_error(self):
|
|
16
|
+
with pytest.raises(ValueError, match="no metadata fields specified"):
|
|
17
|
+
validate_metadata_for_update({})
|
|
18
|
+
|
|
19
|
+
def test_none_values_allowed(self):
|
|
20
|
+
validate_metadata_for_update({UnifiedMetadataKey.TITLE: None})
|
|
21
|
+
|
|
22
|
+
validate_metadata_for_update(
|
|
23
|
+
{
|
|
24
|
+
UnifiedMetadataKey.TITLE: None,
|
|
25
|
+
UnifiedMetadataKey.ARTISTS: None,
|
|
26
|
+
UnifiedMetadataKey.ALBUM: None,
|
|
27
|
+
}
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
def test_empty_string_allowed(self):
|
|
31
|
+
validate_metadata_for_update({UnifiedMetadataKey.TITLE: ""})
|
|
32
|
+
|
|
33
|
+
def test_empty_list_allowed(self):
|
|
34
|
+
validate_metadata_for_update({UnifiedMetadataKey.ARTISTS: []})
|
|
35
|
+
|
|
36
|
+
def test_list_with_none_values_allowed(self):
|
|
37
|
+
validate_metadata_for_update({UnifiedMetadataKey.ARTISTS: [None, None]})
|
|
38
|
+
validate_metadata_for_update({UnifiedMetadataKey.ARTISTS: [None]})
|
|
39
|
+
validate_metadata_for_update({UnifiedMetadataKey.GENRES_NAMES: [None, None, None]})
|
|
40
|
+
|
|
41
|
+
def test_empty_string_and_empty_list_allowed(self):
|
|
42
|
+
validate_metadata_for_update({UnifiedMetadataKey.TITLE: "", UnifiedMetadataKey.ARTISTS: []})
|
|
43
|
+
|
|
44
|
+
def test_valid_metadata_passes(self):
|
|
45
|
+
validate_metadata_for_update({UnifiedMetadataKey.TITLE: "Song Title"})
|
|
46
|
+
validate_metadata_for_update({UnifiedMetadataKey.ARTISTS: ["Artist 1", "Artist 2"]})
|
|
47
|
+
validate_metadata_for_update({UnifiedMetadataKey.ALBUM: "Album Name"})
|
|
48
|
+
|
|
49
|
+
def test_multiple_valid_fields_passes(self):
|
|
50
|
+
validate_metadata_for_update(
|
|
51
|
+
{
|
|
52
|
+
UnifiedMetadataKey.TITLE: "Song Title",
|
|
53
|
+
UnifiedMetadataKey.ARTISTS: ["Artist"],
|
|
54
|
+
UnifiedMetadataKey.ALBUM: "Album",
|
|
55
|
+
}
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
def test_mixed_none_and_valid_fields_passes(self):
|
|
59
|
+
validate_metadata_for_update(
|
|
60
|
+
{
|
|
61
|
+
UnifiedMetadataKey.TITLE: None,
|
|
62
|
+
UnifiedMetadataKey.ARTISTS: ["Artist"],
|
|
63
|
+
}
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
def test_combined_validation_rating_and_release_date(self):
|
|
67
|
+
validate_metadata_for_update(
|
|
68
|
+
{
|
|
69
|
+
UnifiedMetadataKey.RATING: 50,
|
|
70
|
+
UnifiedMetadataKey.RELEASE_DATE: "2024-01-01",
|
|
71
|
+
},
|
|
72
|
+
normalized_rating_max_value=100,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
def test_combined_validation_with_empty_fields(self):
|
|
76
|
+
validate_metadata_for_update(
|
|
77
|
+
{
|
|
78
|
+
UnifiedMetadataKey.TITLE: "",
|
|
79
|
+
UnifiedMetadataKey.ARTISTS: [],
|
|
80
|
+
UnifiedMetadataKey.RATING: 50,
|
|
81
|
+
},
|
|
82
|
+
normalized_rating_max_value=100,
|
|
83
|
+
)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from audiometa._audio_file import _AudioFile
|
|
4
|
+
from audiometa.manager._rating_supporting.id3v2._Id3v2Manager import _Id3v2Manager as Id3v2Manager
|
|
5
|
+
from audiometa.manager._rating_supporting.riff._RiffManager import _RiffManager as RiffManager
|
|
6
|
+
from audiometa.manager._rating_supporting.vorbis._VorbisManager import _VorbisManager as VorbisManager
|
|
7
|
+
from audiometa.manager.id3v1._Id3v1Manager import _Id3v1Manager as Id3v1Manager
|
|
8
|
+
from audiometa.manager.id3v1.id3v1_raw_metadata_key import Id3v1RawMetadataKey
|
|
9
|
+
from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
|
|
10
|
+
from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@pytest.mark.unit
|
|
14
|
+
class TestMetadataFormatManagersWriteAndRead:
|
|
15
|
+
def test_id3v1_manager_write_and_read(self):
|
|
16
|
+
with temp_file_with_metadata({}, "mp3") as test_file:
|
|
17
|
+
audio_file = _AudioFile(test_file)
|
|
18
|
+
manager = Id3v1Manager(audio_file)
|
|
19
|
+
|
|
20
|
+
manager.update_metadata(
|
|
21
|
+
{UnifiedMetadataKey.TITLE: "Test Title", UnifiedMetadataKey.ARTISTS: ["Test Artist"]}
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
raw_metadata = manager._extract_mutagen_metadata()
|
|
25
|
+
|
|
26
|
+
assert raw_metadata.tags.get(Id3v1RawMetadataKey.TITLE) == ["Test Title"]
|
|
27
|
+
assert raw_metadata.tags.get(Id3v1RawMetadataKey.ARTISTS_NAMES_STR) == ["Test Artist"]
|
|
28
|
+
|
|
29
|
+
def test_id3v2_manager_write_and_read(self):
|
|
30
|
+
with temp_file_with_metadata({}, "mp3") as test_file:
|
|
31
|
+
audio_file = _AudioFile(test_file)
|
|
32
|
+
manager = Id3v2Manager(audio_file)
|
|
33
|
+
|
|
34
|
+
manager.update_metadata(
|
|
35
|
+
{UnifiedMetadataKey.TITLE: "Test Title", UnifiedMetadataKey.ARTISTS: ["Test Artist"]}
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
raw_metadata = manager._extract_mutagen_metadata()
|
|
39
|
+
|
|
40
|
+
assert Id3v2Manager.Id3TextFrame.TITLE in raw_metadata
|
|
41
|
+
assert str(raw_metadata[Id3v2Manager.Id3TextFrame.TITLE][0]) == "Test Title"
|
|
42
|
+
assert Id3v2Manager.Id3TextFrame.ARTISTS in raw_metadata
|
|
43
|
+
artists_text = str(raw_metadata[Id3v2Manager.Id3TextFrame.ARTISTS][0])
|
|
44
|
+
assert "Test Artist" in artists_text
|
|
45
|
+
|
|
46
|
+
def test_riff_manager_write_and_read(self):
|
|
47
|
+
with temp_file_with_metadata({}, "wav") as test_file:
|
|
48
|
+
audio_file = _AudioFile(test_file)
|
|
49
|
+
manager = RiffManager(audio_file)
|
|
50
|
+
|
|
51
|
+
manager.update_metadata(
|
|
52
|
+
{UnifiedMetadataKey.TITLE: "Test Title", UnifiedMetadataKey.ARTISTS: ["Test Artist"]}
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
raw_metadata = manager._extract_mutagen_metadata()
|
|
56
|
+
|
|
57
|
+
assert hasattr(raw_metadata, "info")
|
|
58
|
+
info_tags = getattr(raw_metadata, "info", {})
|
|
59
|
+
assert info_tags.get(RiffManager.RiffTagKey.TITLE) == ["Test Title"]
|
|
60
|
+
assert info_tags.get(RiffManager.RiffTagKey.ARTIST) == ["Test Artist"]
|
|
61
|
+
|
|
62
|
+
def test_vorbis_manager_write_and_read(self):
|
|
63
|
+
with temp_file_with_metadata({}, "flac") as test_file:
|
|
64
|
+
audio_file = _AudioFile(test_file)
|
|
65
|
+
manager = VorbisManager(audio_file)
|
|
66
|
+
|
|
67
|
+
manager.update_metadata(
|
|
68
|
+
{UnifiedMetadataKey.TITLE: "Test Title", UnifiedMetadataKey.ARTISTS: ["Test Artist"]}
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
raw_metadata = manager._extract_mutagen_metadata()
|
|
72
|
+
|
|
73
|
+
assert raw_metadata.get(VorbisManager.VorbisKey.TITLE) == ["Test Title"]
|
|
74
|
+
assert raw_metadata.get(VorbisManager.VorbisKey.ARTIST) == ["Test Artist"]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from unittest.mock import MagicMock
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from audiometa.exceptions import ConfigurationError
|
|
6
|
+
from audiometa.manager._rating_supporting.riff._RiffManager import _RiffManager as RiffManager
|
|
7
|
+
from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@pytest.mark.unit
|
|
11
|
+
class TestRiffManagerConfigurationError:
|
|
12
|
+
def test_update_not_using_mutagen_metadata_raises_configuration_error_when_metadata_keys_direct_map_write_is_none(
|
|
13
|
+
self,
|
|
14
|
+
):
|
|
15
|
+
wave_audio_file = MagicMock()
|
|
16
|
+
wave_audio_file.file_extension = ".wav"
|
|
17
|
+
riff_manager = RiffManager(audio_file=wave_audio_file, normalized_rating_max_value=10)
|
|
18
|
+
|
|
19
|
+
# Manually set metadata_keys_direct_map_write to None to test the error condition
|
|
20
|
+
riff_manager.metadata_keys_direct_map_write = None
|
|
21
|
+
|
|
22
|
+
unified_metadata = {UnifiedMetadataKey.TITLE: "Test Title"}
|
|
23
|
+
|
|
24
|
+
with pytest.raises(ConfigurationError) as exc_info:
|
|
25
|
+
riff_manager._update_not_using_mutagen_metadata(unified_metadata)
|
|
26
|
+
assert "metadata_keys_direct_map_write must be set" in str(exc_info.value)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Utility classes and functions for audio metadata processing."""
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"""Standard ID3v1 genre codes mapping.
|
|
2
|
+
|
|
3
|
+
This is the complete standard genre map used by both ID3v1 and RIFF formats. Genres 0-79 are from the original ID3v1
|
|
4
|
+
spec. Genres 80-125 were added by Winamp. Genres 126-147 were added by other players. Genres 148-191 were added in
|
|
5
|
+
Winamp 5.6 (November 2010).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
ID3V1_GENRE_CODE_MAP = {
|
|
9
|
+
0: "Blues",
|
|
10
|
+
1: "Classic Rock",
|
|
11
|
+
2: "Country",
|
|
12
|
+
3: "Dance",
|
|
13
|
+
4: "Disco",
|
|
14
|
+
5: "Funk",
|
|
15
|
+
6: "Grunge",
|
|
16
|
+
7: "Hip-Hop",
|
|
17
|
+
8: "Jazz",
|
|
18
|
+
9: "Metal",
|
|
19
|
+
10: "New Age",
|
|
20
|
+
11: "Oldies",
|
|
21
|
+
12: "Other",
|
|
22
|
+
13: "Pop",
|
|
23
|
+
14: "R&B",
|
|
24
|
+
15: "Rap",
|
|
25
|
+
16: "Reggae",
|
|
26
|
+
17: "Rock",
|
|
27
|
+
18: "Techno",
|
|
28
|
+
19: "Industrial",
|
|
29
|
+
20: "Alternative",
|
|
30
|
+
21: "Ska",
|
|
31
|
+
22: "Death Metal",
|
|
32
|
+
23: "Pranks",
|
|
33
|
+
24: "Soundtrack",
|
|
34
|
+
25: "Euro-Techno",
|
|
35
|
+
26: "Ambient",
|
|
36
|
+
27: "Trip-Hop",
|
|
37
|
+
28: "Vocal",
|
|
38
|
+
29: "Jazz+Funk",
|
|
39
|
+
30: "Fusion",
|
|
40
|
+
31: "Trance",
|
|
41
|
+
32: "Classical",
|
|
42
|
+
33: "Instrumental",
|
|
43
|
+
34: "Acid",
|
|
44
|
+
35: "House",
|
|
45
|
+
36: "Game",
|
|
46
|
+
37: "Sound Clip",
|
|
47
|
+
38: "Gospel",
|
|
48
|
+
39: "Noise",
|
|
49
|
+
40: "Alternative Rock",
|
|
50
|
+
41: "Bass",
|
|
51
|
+
42: "Soul",
|
|
52
|
+
43: "Punk",
|
|
53
|
+
44: "Space",
|
|
54
|
+
45: "Meditative",
|
|
55
|
+
46: "Instrumental Pop",
|
|
56
|
+
47: "Instrumental Rock",
|
|
57
|
+
48: "Ethnic",
|
|
58
|
+
49: "Gothic",
|
|
59
|
+
50: "Darkwave",
|
|
60
|
+
51: "Techno-Industrial",
|
|
61
|
+
52: "Electronic",
|
|
62
|
+
53: "Pop-Folk",
|
|
63
|
+
54: "Eurodance",
|
|
64
|
+
55: "Dream",
|
|
65
|
+
56: "Southern Rock",
|
|
66
|
+
57: "Comedy",
|
|
67
|
+
58: "Cult",
|
|
68
|
+
59: "Gangsta",
|
|
69
|
+
60: "Top 40",
|
|
70
|
+
61: "Christian Rap",
|
|
71
|
+
62: "Pop/Funk",
|
|
72
|
+
63: "Jungle",
|
|
73
|
+
64: "Native US",
|
|
74
|
+
65: "Cabaret",
|
|
75
|
+
66: "New Wave",
|
|
76
|
+
67: "Psychedelic",
|
|
77
|
+
68: "Rave",
|
|
78
|
+
69: "Showtunes",
|
|
79
|
+
70: "Trailer",
|
|
80
|
+
71: "Lo-Fi",
|
|
81
|
+
72: "Tribal",
|
|
82
|
+
73: "Acid Punk",
|
|
83
|
+
74: "Acid Jazz",
|
|
84
|
+
75: "Polka",
|
|
85
|
+
76: "Retro",
|
|
86
|
+
77: "Musical",
|
|
87
|
+
78: "Rock & Roll",
|
|
88
|
+
79: "Hard Rock",
|
|
89
|
+
# Winamp extensions
|
|
90
|
+
80: "Folk",
|
|
91
|
+
81: "Folk-Rock",
|
|
92
|
+
82: "National Folk",
|
|
93
|
+
83: "Swing",
|
|
94
|
+
84: "Fast Fusion",
|
|
95
|
+
85: "Bebop",
|
|
96
|
+
86: "Latin",
|
|
97
|
+
87: "Revival",
|
|
98
|
+
88: "Celtic",
|
|
99
|
+
89: "Bluegrass",
|
|
100
|
+
90: "Avantgarde",
|
|
101
|
+
91: "Gothic Rock",
|
|
102
|
+
92: "Progressive Rock",
|
|
103
|
+
93: "Psychedelic Rock",
|
|
104
|
+
94: "Symphonic Rock",
|
|
105
|
+
95: "Slow Rock",
|
|
106
|
+
96: "Big Band",
|
|
107
|
+
97: "Chorus",
|
|
108
|
+
98: "Easy Listening",
|
|
109
|
+
99: "Acoustic",
|
|
110
|
+
100: "Humour",
|
|
111
|
+
101: "Speech",
|
|
112
|
+
102: "Chanson",
|
|
113
|
+
103: "Opera",
|
|
114
|
+
104: "Chamber Music",
|
|
115
|
+
105: "Sonata",
|
|
116
|
+
106: "Symphony",
|
|
117
|
+
107: "Booty Bass",
|
|
118
|
+
108: "Primus",
|
|
119
|
+
109: "Porn Groove",
|
|
120
|
+
110: "Satire",
|
|
121
|
+
111: "Slow Jam",
|
|
122
|
+
112: "Club",
|
|
123
|
+
113: "Tango",
|
|
124
|
+
114: "Samba",
|
|
125
|
+
115: "Folklore",
|
|
126
|
+
116: "Ballad",
|
|
127
|
+
117: "Power Ballad",
|
|
128
|
+
118: "Rhythmic Soul",
|
|
129
|
+
119: "Freestyle",
|
|
130
|
+
120: "Duet",
|
|
131
|
+
121: "Punk Rock",
|
|
132
|
+
122: "Drum Solo",
|
|
133
|
+
123: "A Cappella",
|
|
134
|
+
124: "Euro-House",
|
|
135
|
+
125: "Dance Hall",
|
|
136
|
+
# Other extensions
|
|
137
|
+
126: "Goa",
|
|
138
|
+
127: "Drum & Bass",
|
|
139
|
+
128: "Club-House",
|
|
140
|
+
129: "Hardcore",
|
|
141
|
+
130: "Terror",
|
|
142
|
+
131: "Indie",
|
|
143
|
+
132: "BritPop",
|
|
144
|
+
133: "Negerpunk",
|
|
145
|
+
134: "Polsk Punk",
|
|
146
|
+
135: "Beat",
|
|
147
|
+
136: "Christian Gangsta Rap",
|
|
148
|
+
137: "Heavy Metal",
|
|
149
|
+
138: "Black Metal",
|
|
150
|
+
139: "Crossover",
|
|
151
|
+
140: "Contemporary Christian",
|
|
152
|
+
141: "Christian Rock",
|
|
153
|
+
142: "Merengue",
|
|
154
|
+
143: "Salsa",
|
|
155
|
+
144: "Thrash Metal",
|
|
156
|
+
145: "Anime",
|
|
157
|
+
146: "JPop",
|
|
158
|
+
147: "Synthpop",
|
|
159
|
+
# Winamp 5.6 extensions (November 2010)
|
|
160
|
+
148: "Christmas",
|
|
161
|
+
149: "Art Rock",
|
|
162
|
+
150: "Baroque",
|
|
163
|
+
151: "Bhangra",
|
|
164
|
+
152: "Big Beat",
|
|
165
|
+
153: "Breakbeat",
|
|
166
|
+
154: "Chillout",
|
|
167
|
+
155: "Downtempo",
|
|
168
|
+
156: "Dub",
|
|
169
|
+
157: "EBM",
|
|
170
|
+
158: "Eclectic",
|
|
171
|
+
159: "Electro",
|
|
172
|
+
160: "Electroclash",
|
|
173
|
+
161: "Emo",
|
|
174
|
+
162: "Experimental",
|
|
175
|
+
163: "Garage",
|
|
176
|
+
164: "Global",
|
|
177
|
+
165: "IDM",
|
|
178
|
+
166: "Illbient",
|
|
179
|
+
167: "Industro-Goth",
|
|
180
|
+
168: "Jam Band",
|
|
181
|
+
169: "Krautrock",
|
|
182
|
+
170: "Leftfield",
|
|
183
|
+
171: "Lounge",
|
|
184
|
+
172: "Math Rock",
|
|
185
|
+
173: "New Romantic",
|
|
186
|
+
174: "Nu-Breakz",
|
|
187
|
+
175: "Post-Punk",
|
|
188
|
+
176: "Post-Rock",
|
|
189
|
+
177: "Psytrance",
|
|
190
|
+
178: "Shoegaze",
|
|
191
|
+
179: "Space Rock",
|
|
192
|
+
180: "Trop Rock",
|
|
193
|
+
181: "World Music",
|
|
194
|
+
182: "Neoclassical",
|
|
195
|
+
183: "Audiobook",
|
|
196
|
+
184: "Audio Theatre",
|
|
197
|
+
185: "Neue Deutsche Welle",
|
|
198
|
+
186: "Podcast",
|
|
199
|
+
187: "Indie Rock",
|
|
200
|
+
188: "G-Funk",
|
|
201
|
+
189: "Dubstep",
|
|
202
|
+
190: "Garage Rock",
|
|
203
|
+
191: "Psybient",
|
|
204
|
+
255: None,
|
|
205
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Tag type constants for audio metadata handling.
|
|
2
|
+
|
|
3
|
+
This module defines the supported metadata formats and their file extension priorities for reading and writing audio
|
|
4
|
+
metadata across different file types.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from enum import Enum
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MetadataFormat(str, Enum):
|
|
11
|
+
"""Enumeration of supported audio metadata formats."""
|
|
12
|
+
|
|
13
|
+
ID3V2 = "id3v2"
|
|
14
|
+
ID3V1 = "id3v1"
|
|
15
|
+
VORBIS = "vorbis"
|
|
16
|
+
RIFF = "riff"
|
|
17
|
+
|
|
18
|
+
@classmethod
|
|
19
|
+
def get_priorities(cls) -> dict[str, list["MetadataFormat"]]:
|
|
20
|
+
"""Get tag format priorities for different file formats.
|
|
21
|
+
|
|
22
|
+
First tag format in each list has highest priority.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
dictionary mapping file extensions to ordered list of tag types
|
|
26
|
+
"""
|
|
27
|
+
return {
|
|
28
|
+
".flac": [cls.VORBIS, cls.ID3V2, cls.ID3V1],
|
|
29
|
+
".mp3": [cls.ID3V2, cls.ID3V1],
|
|
30
|
+
".wav": [cls.RIFF, cls.ID3V2, cls.ID3V1],
|
|
31
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Metadata writing strategy constants for audio metadata handling."""
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class MetadataWritingStrategy(str, Enum):
|
|
7
|
+
"""Strategy for handling metadata when writing to files with existing metadata in other formats."""
|
|
8
|
+
|
|
9
|
+
SYNC = "sync"
|
|
10
|
+
"""Write to native format and synchronize other metadata formats that are already present (default)."""
|
|
11
|
+
|
|
12
|
+
PRESERVE = "preserve"
|
|
13
|
+
"""Write to native format only, preserve existing metadata in other formats."""
|
|
14
|
+
|
|
15
|
+
CLEANUP = "cleanup"
|
|
16
|
+
"""Write to native format and remove all non-native metadata formats."""
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Utility functions for handling mutagen exceptions."""
|
|
2
|
+
|
|
3
|
+
from audiometa.exceptions import FileCorruptedError
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def handle_mutagen_exception(operation: str, file_path: str, exception: Exception) -> None:
|
|
7
|
+
"""Handle exceptions from mutagen operations.
|
|
8
|
+
|
|
9
|
+
Re-raises standard I/O exceptions as-is and converts other exceptions
|
|
10
|
+
(including mutagen-specific ones) to FileCorruptedError.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
operation: Description of the operation being performed (e.g., "save metadata", "extract RIFF metadata from")
|
|
14
|
+
file_path: Path to the file being operated on
|
|
15
|
+
exception: The exception that was raised
|
|
16
|
+
|
|
17
|
+
Raises:
|
|
18
|
+
IOError, OSError, PermissionError: Re-raised as-is for standard I/O errors
|
|
19
|
+
FileCorruptedError: Raised for mutagen-specific or other unexpected exceptions
|
|
20
|
+
"""
|
|
21
|
+
if isinstance(exception, IOError | OSError | PermissionError):
|
|
22
|
+
raise exception
|
|
23
|
+
msg = f"Failed to {operation} {file_path}: {exception!s}"
|
|
24
|
+
raise FileCorruptedError(msg) from exception
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""OS-specific dependency checkers for verifying system dependencies."""
|
|
2
|
+
|
|
3
|
+
import platform
|
|
4
|
+
|
|
5
|
+
from audiometa.utils.os_dependencies_checker.base import OsDependenciesChecker
|
|
6
|
+
from audiometa.utils.os_dependencies_checker.macos import MacOSDependenciesChecker
|
|
7
|
+
from audiometa.utils.os_dependencies_checker.ubuntu import UbuntuDependenciesChecker
|
|
8
|
+
from audiometa.utils.os_dependencies_checker.windows import WindowsDependenciesChecker
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_dependencies_checker() -> OsDependenciesChecker | None:
|
|
12
|
+
"""Get the appropriate OS-specific dependencies checker.
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
OS-specific checker instance, or None if OS not supported
|
|
16
|
+
"""
|
|
17
|
+
system = platform.system().lower()
|
|
18
|
+
if system == "darwin":
|
|
19
|
+
return MacOSDependenciesChecker()
|
|
20
|
+
if system == "linux":
|
|
21
|
+
return UbuntuDependenciesChecker()
|
|
22
|
+
if system == "windows":
|
|
23
|
+
return WindowsDependenciesChecker()
|
|
24
|
+
return None
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Base class for OS-specific dependency checkers."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class OsDependenciesChecker(ABC):
|
|
7
|
+
"""Base class for OS-specific dependency checkers."""
|
|
8
|
+
|
|
9
|
+
@classmethod
|
|
10
|
+
@abstractmethod
|
|
11
|
+
def get_os_type(cls) -> str:
|
|
12
|
+
"""Get OS type identifier."""
|
|
13
|
+
|
|
14
|
+
@abstractmethod
|
|
15
|
+
def check_tool_available(self, tool_name: str) -> bool:
|
|
16
|
+
"""Check if a tool is available (in PATH or default locations).
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
tool_name: Name of the tool to check
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
True if tool is available, False otherwise
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
@abstractmethod
|
|
26
|
+
def get_installed_version(self, package: str, expected_version: str | None = None) -> str | None:
|
|
27
|
+
"""Get installed version of a package.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
package: Package name
|
|
31
|
+
expected_version: Optional expected/pinned version
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Installed version string, or None if not found
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
@staticmethod
|
|
38
|
+
def _normalize_version(version: str) -> str:
|
|
39
|
+
"""Normalize version string by removing revision suffix.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
version: Version string (e.g., "7.1_4" or "1.5.0")
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
Normalized version without revision suffix
|
|
46
|
+
"""
|
|
47
|
+
return version.split("_")[0]
|
|
48
|
+
|
|
49
|
+
@staticmethod
|
|
50
|
+
def _versions_match(version1: str, version2: str) -> bool:
|
|
51
|
+
"""Check if two version strings match (handles different precision).
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
version1: First version string
|
|
55
|
+
version2: Second version string
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
True if versions match, False otherwise
|
|
59
|
+
"""
|
|
60
|
+
v1_normalized = OsDependenciesChecker._normalize_version(version1)
|
|
61
|
+
v2_normalized = OsDependenciesChecker._normalize_version(version2)
|
|
62
|
+
return v1_normalized == v2_normalized or v2_normalized.startswith(v1_normalized + ".")
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Configuration loading for OS-specific dependency checkers."""
|
|
2
|
+
|
|
3
|
+
import tomllib
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _load_config_file(project_root: Path, filename: str) -> dict | None:
|
|
8
|
+
"""Load a TOML configuration file."""
|
|
9
|
+
config_path = project_root / filename
|
|
10
|
+
if not config_path.exists():
|
|
11
|
+
return None
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
with config_path.open("rb") as f:
|
|
15
|
+
return tomllib.load(f)
|
|
16
|
+
except Exception:
|
|
17
|
+
return None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def load_dependencies_pinned_versions() -> dict[str, dict[str, str]] | None:
|
|
21
|
+
"""Load pinned versions from system-dependencies-prod.toml and system-dependencies-test-only.toml.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
Dictionary mapping tool names to OS-specific versions, or None if config not found
|
|
25
|
+
"""
|
|
26
|
+
# Try to find config files relative to this file
|
|
27
|
+
# This file is in audiometa/utils/os_dependencies_checker/, so go up to project root
|
|
28
|
+
project_root = Path(__file__).parent.parent.parent.parent
|
|
29
|
+
|
|
30
|
+
# Load prod and test configs
|
|
31
|
+
prod_config = _load_config_file(project_root, "system-dependencies-prod.toml")
|
|
32
|
+
test_config = _load_config_file(project_root, "system-dependencies-test-only.toml")
|
|
33
|
+
|
|
34
|
+
if not prod_config and not test_config:
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
# Merge configs (test can override prod if needed, though they shouldn't overlap)
|
|
39
|
+
config = {}
|
|
40
|
+
if prod_config:
|
|
41
|
+
config.update(prod_config)
|
|
42
|
+
if test_config:
|
|
43
|
+
# Merge OS sections
|
|
44
|
+
for os_type in ["ubuntu", "macos", "windows"]:
|
|
45
|
+
if os_type in test_config:
|
|
46
|
+
if os_type not in config:
|
|
47
|
+
config[os_type] = {}
|
|
48
|
+
config[os_type].update(test_config[os_type])
|
|
49
|
+
|
|
50
|
+
pinned_versions: dict[str, dict[str, str]] = {}
|
|
51
|
+
|
|
52
|
+
# Extract versions for each OS
|
|
53
|
+
for os_type in ["ubuntu", "macos", "windows"]:
|
|
54
|
+
if os_type not in config:
|
|
55
|
+
continue
|
|
56
|
+
|
|
57
|
+
os_config = config[os_type]
|
|
58
|
+
for tool in ["ffmpeg", "flac", "mediainfo", "id3v2", "bwfmetaedit", "exiftool"]:
|
|
59
|
+
if tool not in os_config:
|
|
60
|
+
continue
|
|
61
|
+
|
|
62
|
+
version_value = os_config[tool]
|
|
63
|
+
# Handle both string values and dict values (for bwfmetaedit, exiftool on Windows)
|
|
64
|
+
if isinstance(version_value, str):
|
|
65
|
+
version = version_value
|
|
66
|
+
elif isinstance(version_value, dict) and "pinned_version" in version_value:
|
|
67
|
+
version = version_value["pinned_version"]
|
|
68
|
+
else:
|
|
69
|
+
continue
|
|
70
|
+
|
|
71
|
+
if tool not in pinned_versions:
|
|
72
|
+
pinned_versions[tool] = {}
|
|
73
|
+
pinned_versions[tool][os_type] = version
|
|
74
|
+
except Exception:
|
|
75
|
+
return None
|
|
76
|
+
else:
|
|
77
|
+
return pinned_versions
|