audiometa-python 1.3.3__tar.gz → 1.4.0__tar.gz

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.
Files changed (422) hide show
  1. {audiometa_python-1.3.3/audiometa_python.egg-info → audiometa_python-1.4.0}/PKG-INFO +44 -4
  2. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/README.md +42 -2
  3. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/__init__.py +53 -1
  4. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/get_full_metadata/test_get_full_metadata.py +13 -1
  5. audiometa_python-1.4.0/audiometa/test/tests/unit/test_get_supported_unified_metadata_field_ids.py +48 -0
  6. audiometa_python-1.4.0/audiometa/test/tests/unit/utils/test_unified_metadata_field_schema.py +88 -0
  7. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/utils/types.py +12 -0
  8. audiometa_python-1.4.0/audiometa/utils/unified_metadata_field_schema.py +80 -0
  9. {audiometa_python-1.3.3 → audiometa_python-1.4.0/audiometa_python.egg-info}/PKG-INFO +44 -4
  10. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa_python.egg-info/SOURCES.txt +3 -0
  11. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/pyproject.toml +2 -2
  12. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/LICENSE +0 -0
  13. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/__main__.py +0 -0
  14. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/_audio_file.py +0 -0
  15. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/cli.py +0 -0
  16. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/exceptions.py +0 -0
  17. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/manager/_MetadataManager.py +0 -0
  18. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/manager/__init__.py +0 -0
  19. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/manager/_rating_supporting/_RatingSupportingMetadataManager.py +0 -0
  20. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/manager/_rating_supporting/__init__.py +0 -0
  21. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/manager/_rating_supporting/id3v2/_Id3v2Manager.py +0 -0
  22. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/manager/_rating_supporting/id3v2/__init__.py +0 -0
  23. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/manager/_rating_supporting/id3v2/_id3v1_preserver.py +0 -0
  24. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/manager/_rating_supporting/id3v2/_id3v2_constants.py +0 -0
  25. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/manager/_rating_supporting/id3v2/_id3v2_flac_handler.py +0 -0
  26. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/manager/_rating_supporting/id3v2/_id3v2_reader.py +0 -0
  27. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/manager/_rating_supporting/id3v2/_id3v2_writer.py +0 -0
  28. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/manager/_rating_supporting/riff/_RiffManager.py +0 -0
  29. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/manager/_rating_supporting/riff/__init__.py +0 -0
  30. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/manager/_rating_supporting/riff/_riff_bext_chunk.py +0 -0
  31. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/manager/_rating_supporting/riff/_riff_constants.py +0 -0
  32. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/manager/_rating_supporting/riff/_riff_file_structure.py +0 -0
  33. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/manager/_rating_supporting/riff/_riff_info_chunk.py +0 -0
  34. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/manager/_rating_supporting/vorbis/_VorbisManager.py +0 -0
  35. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/manager/_rating_supporting/vorbis/__init__.py +0 -0
  36. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/manager/_rating_supporting/vorbis/_vorbis_constants.py +0 -0
  37. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/manager/id3v1/_Id3v1Manager.py +0 -0
  38. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/manager/id3v1/__init__.py +0 -0
  39. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/manager/id3v1/_constants.py +0 -0
  40. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/manager/id3v1/id3v1_raw_metadata.py +0 -0
  41. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/manager/id3v1/id3v1_raw_metadata_key.py +0 -0
  42. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/__init__.py +0 -0
  43. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/assets/create_test_files.py +0 -0
  44. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/helpers/__init__.py +0 -0
  45. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/helpers/common/__init__.py +0 -0
  46. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/helpers/common/audio_file_creator.py +0 -0
  47. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/helpers/common/external_tool_runner.py +0 -0
  48. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/helpers/id3v1/__init__.py +0 -0
  49. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/helpers/id3v1/id3v1_header_verifier.py +0 -0
  50. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/helpers/id3v1/id3v1_metadata_deleter.py +0 -0
  51. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/helpers/id3v1/id3v1_metadata_getter.py +0 -0
  52. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/helpers/id3v1/id3v1_metadata_setter.py +0 -0
  53. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/helpers/id3v2/__init__.py +0 -0
  54. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/helpers/id3v2/id3v2_frame_manual_creator.py +0 -0
  55. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/helpers/id3v2/id3v2_header_verifier.py +0 -0
  56. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/helpers/id3v2/id3v2_metadata_deleter.py +0 -0
  57. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/helpers/id3v2/id3v2_metadata_getter.py +0 -0
  58. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/helpers/id3v2/id3v2_metadata_setter.py +0 -0
  59. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/helpers/riff/__init__.py +0 -0
  60. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/helpers/riff/riff_header_verifier.py +0 -0
  61. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/helpers/riff/riff_manual_metadata_creator.py +0 -0
  62. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/helpers/riff/riff_metadata_deleter.py +0 -0
  63. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/helpers/riff/riff_metadata_getter.py +0 -0
  64. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/helpers/riff/riff_metadata_setter.py +0 -0
  65. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/helpers/scripts/__init__.py +0 -0
  66. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/helpers/technical_info_inspector.py +0 -0
  67. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/helpers/temp_file_with_metadata.py +0 -0
  68. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/helpers/vorbis/__init__.py +0 -0
  69. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/helpers/vorbis/vorbis_header_verifier.py +0 -0
  70. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/helpers/vorbis/vorbis_metadata_deleter.py +0 -0
  71. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/helpers/vorbis/vorbis_metadata_getter.py +0 -0
  72. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/helpers/vorbis/vorbis_metadata_setter.py +0 -0
  73. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/__init__.py +0 -0
  74. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/conftest.py +0 -0
  75. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/e2e/__init__.py +0 -0
  76. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/e2e/cli/__init__.py +0 -0
  77. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/e2e/cli/error_handling/__init__.py +0 -0
  78. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/e2e/cli/error_handling/test_command_structure_errors.py +0 -0
  79. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/e2e/cli/error_handling/test_file_access_errors.py +0 -0
  80. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/e2e/cli/error_handling/test_format_output_errors.py +0 -0
  81. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/e2e/cli/error_handling/test_input_validation_errors.py +0 -0
  82. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/e2e/cli/error_handling/test_missing_fields_validation.py +0 -0
  83. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/e2e/cli/error_handling/test_multiple_files_errors.py +0 -0
  84. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/e2e/cli/error_handling/test_rating_validation.py +0 -0
  85. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/e2e/cli/error_handling/test_year_validation.py +0 -0
  86. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/e2e/cli/read/__init__.py +0 -0
  87. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/e2e/cli/read/test_basic.py +0 -0
  88. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/e2e/cli/read/test_comprehensive.py +0 -0
  89. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/e2e/cli/read/test_formats.py +0 -0
  90. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/e2e/cli/read/test_metadata_content.py +0 -0
  91. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/e2e/cli/read/test_multiple_files.py +0 -0
  92. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/e2e/cli/read/test_options.py +0 -0
  93. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/e2e/cli/read/test_unified.py +0 -0
  94. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/e2e/cli/test_delete.py +0 -0
  95. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/e2e/cli/test_formatting.py +0 -0
  96. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/e2e/cli/test_help.py +0 -0
  97. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/e2e/cli/write/__init__.py +0 -0
  98. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/e2e/cli/write/test_basic.py +0 -0
  99. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/e2e/cli/write/test_comprehensive.py +0 -0
  100. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/e2e/cli/write/test_force_format.py +0 -0
  101. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/e2e/cli/write/test_integer_fields.py +0 -0
  102. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/e2e/cli/write/test_list_fields.py +0 -0
  103. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/e2e/cli/write/test_rating.py +0 -0
  104. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/e2e/cli/write/test_string_fields.py +0 -0
  105. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/e2e/cli/write/test_validation.py +0 -0
  106. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/e2e/scenarios/__init__.py +0 -0
  107. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/e2e/scenarios/test_user_scenarios.py +0 -0
  108. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/e2e/workflows/__init__.py +0 -0
  109. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/e2e/workflows/test_core_workflows.py +0 -0
  110. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/e2e/workflows/test_deletion_workflows.py +0 -0
  111. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/e2e/workflows/test_error_handling_workflows.py +0 -0
  112. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/e2e/workflows/test_format_specific_workflows.py +0 -0
  113. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/e2e/workflows/test_rating_workflows.py +0 -0
  114. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/__init__.py +0 -0
  115. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/audio_format/__init__.py +0 -0
  116. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/audio_format/flac/__init__.py +0 -0
  117. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/audio_format/flac/test_flac_delete_all.py +0 -0
  118. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/audio_format/flac/test_flac_reading_all.py +0 -0
  119. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/audio_format/flac/test_flac_reading_field.py +0 -0
  120. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/audio_format/flac/test_flac_writing.py +0 -0
  121. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/audio_format/mp3/__init__.py +0 -0
  122. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/audio_format/mp3/test_mp3_delete_all.py +0 -0
  123. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/audio_format/mp3/test_mp3_reading_all.py +0 -0
  124. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/audio_format/mp3/test_mp3_reading_field.py +0 -0
  125. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/audio_format/mp3/test_mp3_writing.py +0 -0
  126. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/audio_format/wav/__init__.py +0 -0
  127. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/audio_format/wav/test_wav_delete_all.py +0 -0
  128. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/audio_format/wav/test_wav_reading_all.py +0 -0
  129. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/audio_format/wav/test_wav_reading_field.py +0 -0
  130. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/audio_format/wav/test_wav_writing.py +0 -0
  131. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/conftest.py +0 -0
  132. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/delete_all_metadata/__init__.py +0 -0
  133. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/delete_all_metadata/test_audio_format_all.py +0 -0
  134. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/delete_all_metadata/test_audio_format_header_deletion.py +0 -0
  135. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/delete_all_metadata/test_basic_functionality.py +0 -0
  136. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/delete_all_metadata/test_error_handling.py +0 -0
  137. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/encoding/__init__.py +0 -0
  138. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/encoding/test_encoding.py +0 -0
  139. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/encoding/test_special_characters_edge_cases.py +0 -0
  140. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/get_full_metadata/__init__.py +0 -0
  141. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/get_full_metadata/options/__init__.py +0 -0
  142. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/get_full_metadata/options/test_include_headers.py +0 -0
  143. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/get_full_metadata/options/test_include_technical.py +0 -0
  144. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/get_full_metadata/test_audio_formats.py +0 -0
  145. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/get_full_metadata/test_binary_data_filtering.py +0 -0
  146. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/get_full_metadata/test_consistency.py +0 -0
  147. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/get_full_metadata/test_edge_cases.py +0 -0
  148. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/get_full_metadata/test_error_handling.py +0 -0
  149. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/get_full_metadata/test_performance.py +0 -0
  150. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/get_full_metadata/test_raw_metadata_includes_unsupported_tags.py +0 -0
  151. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/get_full_metadata/test_riff_bext.py +0 -0
  152. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/get_full_metadata/test_structure.py +0 -0
  153. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/__init__.py +0 -0
  154. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/album/__init__.py +0 -0
  155. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/album/test_deleting.py +0 -0
  156. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/album/test_reading.py +0 -0
  157. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/album/test_writing.py +0 -0
  158. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/album_artists/__init__.py +0 -0
  159. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/album_artists/test_deleting.py +0 -0
  160. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/album_artists/test_reading.py +0 -0
  161. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/album_artists/test_writing.py +0 -0
  162. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/artists/__init__.py +0 -0
  163. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/artists/test_deleting.py +0 -0
  164. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/artists/test_reading.py +0 -0
  165. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/artists/test_writing.py +0 -0
  166. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/bpm/__init__.py +0 -0
  167. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/bpm/test_deleting.py +0 -0
  168. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/bpm/test_reading.py +0 -0
  169. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/bpm/test_writing.py +0 -0
  170. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/comment/__init__.py +0 -0
  171. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/comment/test_deleting.py +0 -0
  172. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/comment/test_reading.py +0 -0
  173. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/comment/test_writing.py +0 -0
  174. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/composer/__init__.py +0 -0
  175. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/composer/test_deleting.py +0 -0
  176. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/composer/test_reading.py +0 -0
  177. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/composer/test_writing.py +0 -0
  178. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/copyright/__init__.py +0 -0
  179. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/copyright/test_deleting.py +0 -0
  180. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/copyright/test_reading.py +0 -0
  181. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/copyright/test_writing.py +0 -0
  182. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/description/__init__.py +0 -0
  183. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/description/test_deleting.py +0 -0
  184. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/description/test_reading.py +0 -0
  185. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/description/test_writing.py +0 -0
  186. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/disc_number/__init__.py +0 -0
  187. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/disc_number/test_deleting.py +0 -0
  188. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/disc_number/test_reading.py +0 -0
  189. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/disc_number/test_writing.py +0 -0
  190. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/field_not_supported/__init__.py +0 -0
  191. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/field_not_supported/test_deleting.py +0 -0
  192. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/field_not_supported/test_reading.py +0 -0
  193. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/field_not_supported/test_writing.py +0 -0
  194. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/genre/__init__.py +0 -0
  195. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/genre/reading/__init__.py +0 -0
  196. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/genre/reading/metadata_format/__init__.py +0 -0
  197. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/genre/reading/metadata_format/test_id3v1_reading.py +0 -0
  198. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/genre/reading/metadata_format/test_id3v2_reading.py +0 -0
  199. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/genre/reading/metadata_format/test_riff_reading.py +0 -0
  200. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/genre/reading/metadata_format/test_vorbis_reading.py +0 -0
  201. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/genre/reading/test_smart_reading.py +0 -0
  202. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/genre/test_deleting.py +0 -0
  203. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/genre/test_writing.py +0 -0
  204. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/isrc/__init__.py +0 -0
  205. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/isrc/test_deleting.py +0 -0
  206. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/isrc/test_reading.py +0 -0
  207. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/isrc/test_writing.py +0 -0
  208. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/language/__init__.py +0 -0
  209. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/language/test_deleting.py +0 -0
  210. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/language/test_reading.py +0 -0
  211. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/language/test_writing.py +0 -0
  212. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/lyrics/__init__.py +0 -0
  213. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/lyrics/test_deleting.py +0 -0
  214. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/lyrics/test_reading.py +0 -0
  215. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/lyrics/test_writing.py +0 -0
  216. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/musicbrainz_artistid/__init__.py +0 -0
  217. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/musicbrainz_artistid/test_deleting.py +0 -0
  218. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/musicbrainz_artistid/test_reading.py +0 -0
  219. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/musicbrainz_artistid/test_writing.py +0 -0
  220. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/musicbrainz_trackid/__init__.py +0 -0
  221. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/musicbrainz_trackid/test_deleting.py +0 -0
  222. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/musicbrainz_trackid/test_reading.py +0 -0
  223. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/musicbrainz_trackid/test_writing.py +0 -0
  224. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/originator/__init__.py +0 -0
  225. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/originator/test_deleting.py +0 -0
  226. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/originator/test_reading.py +0 -0
  227. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/originator/test_writing.py +0 -0
  228. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/publisher/__init__.py +0 -0
  229. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/publisher/test_deleting.py +0 -0
  230. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/publisher/test_reading.py +0 -0
  231. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/publisher/test_writing.py +0 -0
  232. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/rating/__init__.py +0 -0
  233. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/rating/reading/__init__.py +0 -0
  234. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/rating/reading/test_base_100_proportional.py +0 -0
  235. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/rating/reading/test_base_255_non_proportional.py +0 -0
  236. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/rating/reading/test_base_255_proportional.py +0 -0
  237. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/rating/test_deleting.py +0 -0
  238. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/rating/test_error_handling.py +0 -0
  239. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/rating/writing/__init__.py +0 -0
  240. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/rating/writing/metadata_format/__init__.py +0 -0
  241. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/rating/writing/metadata_format/test_id3v2.py +0 -0
  242. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/rating/writing/metadata_format/test_riff.py +0 -0
  243. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/rating/writing/metadata_format/test_vorbis.py +0 -0
  244. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/rating/writing/test_comprehensive.py +0 -0
  245. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/release_date/__init__.py +0 -0
  246. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/release_date/test_deleting.py +0 -0
  247. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/release_date/test_error_handling.py +0 -0
  248. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/release_date/test_reading.py +0 -0
  249. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/release_date/test_writing.py +0 -0
  250. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/test_metadata_field_validation.py +0 -0
  251. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/title/__init__.py +0 -0
  252. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/title/test_deleting.py +0 -0
  253. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/title/test_error_handling.py +0 -0
  254. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/title/test_reading.py +0 -0
  255. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/title/test_writing.py +0 -0
  256. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/track_number/__init__.py +0 -0
  257. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/track_number/reading/__init__.py +0 -0
  258. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/track_number/reading/test_edge_cases.py +0 -0
  259. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/track_number/reading/test_metadata_format.py +0 -0
  260. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/track_number/test_deleting.py +0 -0
  261. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/metadata_field/track_number/test_writing.py +0 -0
  262. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/multiple_values/__init__.py +0 -0
  263. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/multiple_values/reading/__init__.py +0 -0
  264. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/multiple_values/reading/metadata_format/__init__.py +0 -0
  265. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/multiple_values/reading/metadata_format/test_id3v1.py +0 -0
  266. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/multiple_values/reading/metadata_format/test_id3v2_3.py +0 -0
  267. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/multiple_values/reading/metadata_format/test_id3v2_4.py +0 -0
  268. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/multiple_values/reading/metadata_format/test_riff.py +0 -0
  269. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/multiple_values/reading/metadata_format/test_vorbis.py +0 -0
  270. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/multiple_values/reading/test_performance_large_data.py +0 -0
  271. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/multiple_values/reading/test_smart_parsing_scenarios.py +0 -0
  272. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/multiple_values/reading/test_unicode_handling.py +0 -0
  273. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/multiple_values/writing/__init__.py +0 -0
  274. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/multiple_values/writing/metadata_format/__init__.py +0 -0
  275. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/multiple_values/writing/metadata_format/test_id3v1.py +0 -0
  276. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/multiple_values/writing/metadata_format/test_id3v2_3.py +0 -0
  277. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/multiple_values/writing/metadata_format/test_id3v2_4.py +0 -0
  278. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/multiple_values/writing/metadata_format/test_riff.py +0 -0
  279. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/multiple_values/writing/metadata_format/test_vorbis.py +0 -0
  280. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/multiple_values/writing/test_error_handling.py +0 -0
  281. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/multiple_values/writing/test_large_values.py +0 -0
  282. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/reading/__init__.py +0 -0
  283. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/reading/test_read_multiple_metadata.py +0 -0
  284. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/reading/test_reading_error_handling.py +0 -0
  285. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/real_audio_files/__init__.py +0 -0
  286. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/real_audio_files/test_reading.py +0 -0
  287. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/real_audio_files/test_writing.py +0 -0
  288. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/technical_info/__init__.py +0 -0
  289. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/technical_info/flac_md5/__init__.py +0 -0
  290. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/technical_info/flac_md5/conftest.py +0 -0
  291. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/technical_info/flac_md5/md5_checking/__init__.py +0 -0
  292. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/technical_info/flac_md5/md5_checking/test_audio_data_corruption.py +0 -0
  293. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/technical_info/flac_md5/md5_checking/test_file.py +0 -0
  294. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/technical_info/flac_md5/md5_checking/test_invalid_md5.py +0 -0
  295. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/technical_info/flac_md5/md5_checking/test_metadata_combinations/__init__.py +0 -0
  296. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/technical_info/flac_md5/md5_checking/test_metadata_combinations/test_md5_invalid_with_metadata_combinations.py +0 -0
  297. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/technical_info/flac_md5/md5_checking/test_metadata_combinations/test_md5_state_precedence.py +0 -0
  298. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/technical_info/flac_md5/md5_checking/test_metadata_combinations/test_md5_unset_with_metadata_combinations.py +0 -0
  299. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/technical_info/flac_md5/md5_checking/test_metadata_combinations/test_md5_validation_fails_with_id3v1.py +0 -0
  300. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/technical_info/flac_md5/md5_checking/test_metadata_combinations/test_md5_validation_with_id3v2_only.py +0 -0
  301. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/technical_info/flac_md5/md5_checking/test_metadata_combinations/test_md5_validation_works_without_id3v1.py +0 -0
  302. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/technical_info/flac_md5/md5_checking/test_metadata_combinations/test_md5_with_metadata_combinations.py +0 -0
  303. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/technical_info/flac_md5/md5_checking/test_unset_md5.py +0 -0
  304. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/technical_info/flac_md5/md5_repair/__init__.py +0 -0
  305. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/technical_info/flac_md5/md5_repair/test_delete_original.py +0 -0
  306. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/technical_info/flac_md5/md5_repair/test_invalid_md5/__init__.py +0 -0
  307. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/technical_info/flac_md5/md5_repair/test_invalid_md5/test_flipped_md5.py +0 -0
  308. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/technical_info/flac_md5/md5_repair/test_invalid_md5/test_partial_md5.py +0 -0
  309. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/technical_info/flac_md5/md5_repair/test_invalid_md5/test_random_md5.py +0 -0
  310. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/technical_info/flac_md5/md5_repair/test_md5_repair_with_metadata.py +0 -0
  311. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/technical_info/flac_md5/md5_repair/test_non_flac_error.py +0 -0
  312. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/technical_info/flac_md5/md5_repair/test_unset_md5.py +0 -0
  313. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/technical_info/test_bitrate.py +0 -0
  314. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/technical_info/test_channels.py +0 -0
  315. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/technical_info/test_duration_in_sec.py +0 -0
  316. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/technical_info/test_sample_rate.py +0 -0
  317. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/test_audio_file.py +0 -0
  318. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/test_audio_format_readable_after_update_all_metadata_formats.py +0 -0
  319. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/writing/__init__.py +0 -0
  320. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/writing/test_error_handling.py +0 -0
  321. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/writing/test_forced_format.py +0 -0
  322. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/writing/test_multiple_format_preservation.py +0 -0
  323. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/writing/test_partial_update.py +0 -0
  324. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/writing/writing_strategies/__init__.py +0 -0
  325. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/writing/writing_strategies/sync_strategy/__init__.py +0 -0
  326. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/writing/writing_strategies/sync_strategy/test_flac_sync.py +0 -0
  327. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/writing/writing_strategies/sync_strategy/test_mp3_sync.py +0 -0
  328. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/writing/writing_strategies/sync_strategy/test_wav_sync.py +0 -0
  329. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/writing/writing_strategies/test_cleanup_strategy.py +0 -0
  330. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/writing/writing_strategies/test_preserve_strategy.py +0 -0
  331. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/writing/writing_strategies/unsupported_fields/__init__.py +0 -0
  332. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/writing/writing_strategies/unsupported_fields/test_fail_behavior.py +0 -0
  333. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/writing/writing_strategies/unsupported_fields/test_no_writing_on_failure.py +0 -0
  334. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/integration/writing/writing_strategies/unsupported_fields/test_strategy_specific.py +0 -0
  335. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/__init__.py +0 -0
  336. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/audio_file/__init__.py +0 -0
  337. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/audio_file/technical_info/__init__.py +0 -0
  338. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/audio_file/technical_info/test_bitrate.py +0 -0
  339. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/audio_file/technical_info/test_channels.py +0 -0
  340. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/audio_file/technical_info/test_duration_in_sec.py +0 -0
  341. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/audio_file/technical_info/test_error_handling.py +0 -0
  342. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/audio_file/technical_info/test_file_size.py +0 -0
  343. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/audio_file/technical_info/test_format_name.py +0 -0
  344. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/audio_file/technical_info/test_sample_rate.py +0 -0
  345. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/audio_file/test_context_manager.py +0 -0
  346. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/audio_file/test_file_validation.py +0 -0
  347. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/audio_file/test_is_audio_file.py +0 -0
  348. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/audio_file/test_operations.py +0 -0
  349. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/audio_file/test_path_handling.py +0 -0
  350. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/cli/__init__.py +0 -0
  351. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/cli/test_cli_colorize.py +0 -0
  352. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/cli/test_expand_file_patterns.py +0 -0
  353. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/metadata_managers/__init__.py +0 -0
  354. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/metadata_managers/conftest.py +0 -0
  355. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/metadata_managers/header_info/__init__.py +0 -0
  356. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/metadata_managers/header_info/test_id3v1.py +0 -0
  357. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/metadata_managers/header_info/test_id3v2.py +0 -0
  358. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/metadata_managers/header_info/test_riff.py +0 -0
  359. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/metadata_managers/header_info/test_riff_info_chunk_fourcc.py +0 -0
  360. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/metadata_managers/header_info/test_vorbis.py +0 -0
  361. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/metadata_managers/metadata_field/__init__.py +0 -0
  362. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/metadata_managers/metadata_field/disc_number/__init__.py +0 -0
  363. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/metadata_managers/metadata_field/disc_number/test_disc_number_validation.py +0 -0
  364. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/metadata_managers/metadata_field/isrc/__init__.py +0 -0
  365. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/metadata_managers/metadata_field/isrc/test_isrc_format_validation.py +0 -0
  366. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/metadata_managers/metadata_field/isrc/test_isrc_type_validation.py +0 -0
  367. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/metadata_managers/metadata_field/multiple_values/__init__.py +0 -0
  368. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/metadata_managers/metadata_field/multiple_values/reading/__init__.py +0 -0
  369. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/metadata_managers/metadata_field/multiple_values/reading/test_smart_parsing.py +0 -0
  370. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/metadata_managers/metadata_field/multiple_values/writing/__init__.py +0 -0
  371. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/metadata_managers/metadata_field/multiple_values/writing/test_separator_selection.py +0 -0
  372. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/metadata_managers/metadata_field/multiple_values/writing/test_value_filtering.py +0 -0
  373. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/metadata_managers/metadata_field/musicbrainz_artistid/__init__.py +0 -0
  374. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/metadata_managers/metadata_field/musicbrainz_artistid/test_musicbrainz_artistid_format_validation.py +0 -0
  375. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/metadata_managers/metadata_field/musicbrainz_artistid/test_musicbrainz_artistid_type_validation.py +0 -0
  376. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/metadata_managers/metadata_field/musicbrainz_trackid/__init__.py +0 -0
  377. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/metadata_managers/metadata_field/musicbrainz_trackid/test_musicbrainz_trackid_format_validation.py +0 -0
  378. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/metadata_managers/metadata_field/musicbrainz_trackid/test_musicbrainz_trackid_type_validation.py +0 -0
  379. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/metadata_managers/metadata_field/musicbrainz_uuid/__init__.py +0 -0
  380. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/metadata_managers/metadata_field/musicbrainz_uuid/test_uuid_format_validation.py +0 -0
  381. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/metadata_managers/metadata_field/rating/__init__.py +0 -0
  382. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/metadata_managers/metadata_field/rating/reading/__init__.py +0 -0
  383. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/metadata_managers/metadata_field/rating/reading/test_normalization.py +0 -0
  384. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/metadata_managers/metadata_field/rating/reading/test_profiles_values.py +0 -0
  385. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/metadata_managers/metadata_field/rating/test_rating_validation.py +0 -0
  386. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/metadata_managers/metadata_field/rating/writing/__init__.py +0 -0
  387. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/metadata_managers/metadata_field/rating/writing/test_configuration_error.py +0 -0
  388. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/metadata_managers/metadata_field/rating/writing/test_validation.py +0 -0
  389. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/metadata_managers/metadata_field/rating/writing/test_writing_profiles.py +0 -0
  390. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/metadata_managers/metadata_field/release_date/__init__.py +0 -0
  391. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/metadata_managers/metadata_field/release_date/test_date_format_validation.py +0 -0
  392. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/metadata_managers/metadata_field/test_type_validation_exception.py +0 -0
  393. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/metadata_managers/metadata_field/test_validation.py +0 -0
  394. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/metadata_managers/metadata_field/track_number/__init__.py +0 -0
  395. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/metadata_managers/metadata_field/track_number/test_track_number_validation.py +0 -0
  396. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/metadata_managers/test_metadata_format_managers_write_and_read.py +0 -0
  397. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/metadata_managers/test_riff_configuration_error.py +0 -0
  398. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/utils/__init__.py +0 -0
  399. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/utils/test_disc_number_read.py +0 -0
  400. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/test/tests/unit/utils/test_raw_metadata_sanitizer.py +0 -0
  401. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/utils/__init__.py +0 -0
  402. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/utils/disc_number_read.py +0 -0
  403. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/utils/flac_md5_state.py +0 -0
  404. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/utils/id3v1_genre_code_map.py +0 -0
  405. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/utils/metadata_format.py +0 -0
  406. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/utils/metadata_writing_strategy.py +0 -0
  407. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/utils/mutagen_exception_handler.py +0 -0
  408. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/utils/os_dependencies_checker/__init__.py +0 -0
  409. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/utils/os_dependencies_checker/base.py +0 -0
  410. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/utils/os_dependencies_checker/config.py +0 -0
  411. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/utils/os_dependencies_checker/macos.py +0 -0
  412. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/utils/os_dependencies_checker/ubuntu.py +0 -0
  413. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/utils/os_dependencies_checker/windows.py +0 -0
  414. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/utils/rating_profiles.py +0 -0
  415. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/utils/raw_metadata_sanitizer.py +0 -0
  416. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/utils/tool_path_resolver.py +0 -0
  417. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa/utils/unified_metadata_key.py +0 -0
  418. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa_python.egg-info/dependency_links.txt +0 -0
  419. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa_python.egg-info/entry_points.txt +0 -0
  420. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa_python.egg-info/requires.txt +0 -0
  421. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/audiometa_python.egg-info/top_level.txt +0 -0
  422. {audiometa_python-1.3.3 → audiometa_python-1.4.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: audiometa-python
3
- Version: 1.3.3
3
+ Version: 1.4.0
4
4
  Summary: A comprehensive Python library for reading and writing audio metadata across multiple formats
5
5
  Author: AudioMeta Python Contributors
6
6
  Author-email: Andreas Garcia <garcia.andreas.1991@gmail.com>
@@ -9,7 +9,7 @@ Project-URL: Homepage, https://github.com/BehindTheMusicTree/audiometa
9
9
  Project-URL: Repository, https://github.com/BehindTheMusicTree/audiometa
10
10
  Project-URL: Issues, https://github.com/BehindTheMusicTree/audiometa/issues
11
11
  Keywords: audio,metadata,mp3,flac,wav,id3,vorbis,riff
12
- Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Development Status :: 4 - Beta
13
13
  Classifier: Intended Audience :: Developers
14
14
  Classifier: License :: OSI Approved :: Apache Software License
15
15
  Classifier: Operating System :: OS Independent
@@ -321,11 +321,13 @@ AudioMeta uses a combination of Python libraries and external command-line tools
321
321
 
322
322
  ### Reading Metadata
323
323
 
324
- When reading metadata, there are three functions to use: `get_unified_metadata` and `get_unified_metadata_field`, and `get_full_metadata`.
324
+ When reading metadata, the main entry points are `get_unified_metadata`, `get_unified_metadata_field`, and `get_full_metadata`. For tooling and UIs, `get_unified_metadata_field_schema` and `get_supported_unified_metadata_field_ids` describe the full unified vocabulary and which fields are writable for a given file’s primary format.
325
325
 
326
326
  - `get_unified_metadata`: Reads all metadata from a file and returns a unified dictionary.
327
327
  - `get_unified_metadata_field`: Reads a specific metadata field from a file.
328
- - `get_full_metadata`: Reads all metadata from a file and returns a dictionary including headers and technical info.
328
+ - `get_full_metadata`: Reads all metadata from a file and returns a dictionary including headers, technical info, raw tags, **unified field schema**, and **per-file supported unified field ids**.
329
+ - `get_unified_metadata_field_schema`: Returns wire-oriented descriptors (`id`, `label`, `multiple`, `value_type`, `optional_value`) for every `UnifiedMetadataKey` (no file required).
330
+ - `get_supported_unified_metadata_field_ids`: Returns sorted unified field ids that have a write mapping for the file’s primary metadata format.
329
331
 
330
332
  #### Reading from a specific metadata format
331
333
 
@@ -413,6 +415,21 @@ except MetadataFieldNotSupportedByMetadataFormatError as e:
413
415
  print(f"Error: {e}")
414
416
  ```
415
417
 
418
+ #### Unified field schema and writable field ids
419
+
420
+ **`get_unified_metadata_field_schema()`** returns one descriptor per `UnifiedMetadataKey`: stable string **`id`** (the enum value), English **`label`**, **`multiple`**, JSON-oriented **`value_type`**, and **`optional_value`**. Use it for forms, validation, or API docs without hard-coding the enum.
421
+
422
+ **`get_supported_unified_metadata_field_ids(file)`** returns sorted **`UnifiedMetadataKey.value`** strings for fields that have a non-`None` write mapping in the file’s **primary** (native) metadata format.
423
+
424
+ ```python
425
+ from audiometa import get_unified_metadata_field_schema, get_supported_unified_metadata_field_ids
426
+
427
+ schema = get_unified_metadata_field_schema()
428
+ writable_on_this_file = get_supported_unified_metadata_field_ids("song.mp3")
429
+ ```
430
+
431
+ `get_full_metadata` always includes **`unified_metadata_field_schema`** (same list as above) and **`supported_unified_metadata_field_ids`** for that path. The **`audiometa read`** command surfaces these keys in **JSON** and **YAML** output; **table** output only prints unified, technical, and format metadata sections.
432
+
416
433
  #### Reading Full Metadata From All Formats Including Headers and Technical Info
417
434
 
418
435
  **`get_full_metadata(file_path, include_headers=True, include_technical=True, include_raw_binary_data=False)`**
@@ -425,6 +442,8 @@ Gets comprehensive metadata including all available information from a file, inc
425
442
  from audiometa import get_full_metadata
426
443
 
427
444
  full_metadata = get_full_metadata("song.mp3")
445
+ # full_metadata["unified_metadata_field_schema"] — all unified keys
446
+ # full_metadata["supported_unified_metadata_field_ids"] — writable via primary format
428
447
  ```
429
448
 
430
449
  ### Validate Metadata Before Update
@@ -702,6 +721,7 @@ This function provides the most complete view of an audio file by combining:
702
721
  - Technical information (duration, bitrate, sample rate, channels, file size)
703
722
  - Format-specific headers and structure information
704
723
  - Raw metadata details from each format (when include_raw_binary_data is False, binary/opaque content such as APIC, PRIV, TRAKTOR4 is summarized as size placeholders)
724
+ - **Unified field schema** (`unified_metadata_field_schema`) and **per-file writable unified ids** (`supported_unified_metadata_field_ids`); see `get_unified_metadata_field_schema` and `get_supported_unified_metadata_field_ids`
705
725
 
706
726
  ```python
707
727
  from audiometa import get_full_metadata, UnifiedMetadataKey
@@ -713,6 +733,10 @@ full_metadata = get_full_metadata("song.mp3")
713
733
  print(f"Title: {full_metadata['unified_metadata'][UnifiedMetadataKey.TITLE]}")
714
734
  print(f"Artists: {full_metadata['unified_metadata'][UnifiedMetadataKey.ARTISTS]}")
715
735
 
736
+ # Field vocabulary and ids writable for this file's primary format
737
+ print(full_metadata["unified_metadata_field_schema"][0])
738
+ print(full_metadata["supported_unified_metadata_field_ids"])
739
+
716
740
  # Access technical information
717
741
  print(f"Duration: {full_metadata['technical_info']['duration_seconds']} seconds")
718
742
  print(f"Bitrate: {full_metadata['technical_info']['bitrate_bps']} bps ({full_metadata['technical_info']['bitrate_bps'] // 1000} kbps)")
@@ -839,6 +863,22 @@ A comprehensive dictionary containing:
839
863
  'chunk_structure': {...}
840
864
  }
841
865
  },
866
+ 'unified_metadata_field_schema': [
867
+ {
868
+ 'id': 'title',
869
+ 'label': 'Title',
870
+ 'multiple': False,
871
+ 'value_type': 'string',
872
+ 'optional_value': False,
873
+ },
874
+ # ... one entry per UnifiedMetadataKey
875
+ ],
876
+ 'supported_unified_metadata_field_ids': [
877
+ 'album',
878
+ 'artists',
879
+ 'title',
880
+ # ... ids with a write mapping for this file's primary format
881
+ ],
842
882
  'format_priorities': {
843
883
  'file_extension': '.mp3',
844
884
  'reading_order': ['id3v2', 'id3v1'],
@@ -279,11 +279,13 @@ AudioMeta uses a combination of Python libraries and external command-line tools
279
279
 
280
280
  ### Reading Metadata
281
281
 
282
- When reading metadata, there are three functions to use: `get_unified_metadata` and `get_unified_metadata_field`, and `get_full_metadata`.
282
+ When reading metadata, the main entry points are `get_unified_metadata`, `get_unified_metadata_field`, and `get_full_metadata`. For tooling and UIs, `get_unified_metadata_field_schema` and `get_supported_unified_metadata_field_ids` describe the full unified vocabulary and which fields are writable for a given file’s primary format.
283
283
 
284
284
  - `get_unified_metadata`: Reads all metadata from a file and returns a unified dictionary.
285
285
  - `get_unified_metadata_field`: Reads a specific metadata field from a file.
286
- - `get_full_metadata`: Reads all metadata from a file and returns a dictionary including headers and technical info.
286
+ - `get_full_metadata`: Reads all metadata from a file and returns a dictionary including headers, technical info, raw tags, **unified field schema**, and **per-file supported unified field ids**.
287
+ - `get_unified_metadata_field_schema`: Returns wire-oriented descriptors (`id`, `label`, `multiple`, `value_type`, `optional_value`) for every `UnifiedMetadataKey` (no file required).
288
+ - `get_supported_unified_metadata_field_ids`: Returns sorted unified field ids that have a write mapping for the file’s primary metadata format.
287
289
 
288
290
  #### Reading from a specific metadata format
289
291
 
@@ -371,6 +373,21 @@ except MetadataFieldNotSupportedByMetadataFormatError as e:
371
373
  print(f"Error: {e}")
372
374
  ```
373
375
 
376
+ #### Unified field schema and writable field ids
377
+
378
+ **`get_unified_metadata_field_schema()`** returns one descriptor per `UnifiedMetadataKey`: stable string **`id`** (the enum value), English **`label`**, **`multiple`**, JSON-oriented **`value_type`**, and **`optional_value`**. Use it for forms, validation, or API docs without hard-coding the enum.
379
+
380
+ **`get_supported_unified_metadata_field_ids(file)`** returns sorted **`UnifiedMetadataKey.value`** strings for fields that have a non-`None` write mapping in the file’s **primary** (native) metadata format.
381
+
382
+ ```python
383
+ from audiometa import get_unified_metadata_field_schema, get_supported_unified_metadata_field_ids
384
+
385
+ schema = get_unified_metadata_field_schema()
386
+ writable_on_this_file = get_supported_unified_metadata_field_ids("song.mp3")
387
+ ```
388
+
389
+ `get_full_metadata` always includes **`unified_metadata_field_schema`** (same list as above) and **`supported_unified_metadata_field_ids`** for that path. The **`audiometa read`** command surfaces these keys in **JSON** and **YAML** output; **table** output only prints unified, technical, and format metadata sections.
390
+
374
391
  #### Reading Full Metadata From All Formats Including Headers and Technical Info
375
392
 
376
393
  **`get_full_metadata(file_path, include_headers=True, include_technical=True, include_raw_binary_data=False)`**
@@ -383,6 +400,8 @@ Gets comprehensive metadata including all available information from a file, inc
383
400
  from audiometa import get_full_metadata
384
401
 
385
402
  full_metadata = get_full_metadata("song.mp3")
403
+ # full_metadata["unified_metadata_field_schema"] — all unified keys
404
+ # full_metadata["supported_unified_metadata_field_ids"] — writable via primary format
386
405
  ```
387
406
 
388
407
  ### Validate Metadata Before Update
@@ -660,6 +679,7 @@ This function provides the most complete view of an audio file by combining:
660
679
  - Technical information (duration, bitrate, sample rate, channels, file size)
661
680
  - Format-specific headers and structure information
662
681
  - Raw metadata details from each format (when include_raw_binary_data is False, binary/opaque content such as APIC, PRIV, TRAKTOR4 is summarized as size placeholders)
682
+ - **Unified field schema** (`unified_metadata_field_schema`) and **per-file writable unified ids** (`supported_unified_metadata_field_ids`); see `get_unified_metadata_field_schema` and `get_supported_unified_metadata_field_ids`
663
683
 
664
684
  ```python
665
685
  from audiometa import get_full_metadata, UnifiedMetadataKey
@@ -671,6 +691,10 @@ full_metadata = get_full_metadata("song.mp3")
671
691
  print(f"Title: {full_metadata['unified_metadata'][UnifiedMetadataKey.TITLE]}")
672
692
  print(f"Artists: {full_metadata['unified_metadata'][UnifiedMetadataKey.ARTISTS]}")
673
693
 
694
+ # Field vocabulary and ids writable for this file's primary format
695
+ print(full_metadata["unified_metadata_field_schema"][0])
696
+ print(full_metadata["supported_unified_metadata_field_ids"])
697
+
674
698
  # Access technical information
675
699
  print(f"Duration: {full_metadata['technical_info']['duration_seconds']} seconds")
676
700
  print(f"Bitrate: {full_metadata['technical_info']['bitrate_bps']} bps ({full_metadata['technical_info']['bitrate_bps'] // 1000} kbps)")
@@ -797,6 +821,22 @@ A comprehensive dictionary containing:
797
821
  'chunk_structure': {...}
798
822
  }
799
823
  },
824
+ 'unified_metadata_field_schema': [
825
+ {
826
+ 'id': 'title',
827
+ 'label': 'Title',
828
+ 'multiple': False,
829
+ 'value_type': 'string',
830
+ 'optional_value': False,
831
+ },
832
+ # ... one entry per UnifiedMetadataKey
833
+ ],
834
+ 'supported_unified_metadata_field_ids': [
835
+ 'album',
836
+ 'artists',
837
+ 'title',
838
+ # ... ids with a write mapping for this file's primary format
839
+ ],
800
840
  'format_priorities': {
801
841
  'file_extension': '.mp3',
802
842
  'reading_order': ['id3v2', 'id3v1'],
@@ -34,10 +34,13 @@ from .utils.flac_md5_state import FlacMd5State
34
34
  from .utils.metadata_format import MetadataFormat
35
35
  from .utils.metadata_writing_strategy import MetadataWritingStrategy
36
36
  from .utils.types import UnifiedMetadata, UnifiedMetadataValue
37
+ from .utils.unified_metadata_field_schema import get_unified_metadata_field_schema
37
38
  from .utils.unified_metadata_key import UnifiedMetadataKey
38
39
 
39
40
  __all__ = [
40
41
  "UnifiedMetadataKey",
42
+ "get_unified_metadata_field_schema",
43
+ "get_supported_unified_metadata_field_ids",
41
44
  "FlacMd5State",
42
45
  "MetadataFormat",
43
46
  "MetadataWritingStrategy",
@@ -79,6 +82,35 @@ METADATA_FORMAT_MANAGER_CLASS_MAP: dict[MetadataFormat, type] = {
79
82
  type PublicFileType = str | Path
80
83
 
81
84
 
85
+ def _supported_unified_metadata_field_ids_from_manager(manager: _MetadataManager | None) -> list[str]:
86
+ if manager is None:
87
+ return []
88
+ wmap = getattr(manager, "metadata_keys_direct_map_write", None)
89
+ if not wmap:
90
+ return []
91
+ return sorted(ukey.value for ukey, raw_key in wmap.items() if raw_key is not None)
92
+
93
+
94
+ def get_supported_unified_metadata_field_ids(file: PublicFileType) -> list[str]:
95
+ """Return unified field ids writable to the file's primary (native) metadata format.
96
+
97
+ Keys are :attr:`UnifiedMetadataKey.value` strings. Fields with no write mapping
98
+ for that format (``None`` in the manager's write map) are omitted.
99
+ """
100
+ audio_file = _AudioFile(file)
101
+ available_formats = MetadataFormat.get_priorities().get(audio_file.file_extension, [])
102
+ if not available_formats:
103
+ return []
104
+ target_format = available_formats[0]
105
+ manager = _get_metadata_manager(
106
+ audio_file=audio_file,
107
+ metadata_format=target_format,
108
+ normalized_rating_max_value=None,
109
+ id3v2_version=None,
110
+ )
111
+ return _supported_unified_metadata_field_ids_from_manager(manager)
112
+
113
+
82
114
  def _get_metadata_manager(
83
115
  audio_file: _AudioFile,
84
116
  metadata_format: MetadataFormat | None = None,
@@ -1290,7 +1322,18 @@ def get_full_metadata(
1290
1322
  TRAKTOR4). If False (default), such content is replaced by size placeholders.
1291
1323
 
1292
1324
  Returns:
1293
- Comprehensive dictionary containing all available metadata and technical information
1325
+ Comprehensive dictionary. Top-level keys always include:
1326
+
1327
+ - ``unified_metadata``: Same merged view as :func:`get_unified_metadata`
1328
+ - ``technical_info``, ``metadata_format``, ``headers``, ``raw_metadata``: As documented below;
1329
+ ``technical_info`` / ``headers`` may be empty dicts when disabled via parameters
1330
+ - ``unified_metadata_field_schema``: List of field descriptors (``id``, ``label``, ``multiple``,
1331
+ ``value_type``, ``optional_value``) for every
1332
+ :class:`~audiometa.utils.unified_metadata_key.UnifiedMetadataKey`; same data as
1333
+ :func:`get_unified_metadata_field_schema`
1334
+ - ``supported_unified_metadata_field_ids``: Sorted :attr:`UnifiedMetadataKey.value` strings writable
1335
+ for this file's primary metadata format (see :func:`get_supported_unified_metadata_field_ids`)
1336
+ - ``format_priorities``: Extension, reading order, and primary writing format
1294
1337
 
1295
1338
  Raises:
1296
1339
  FileTypeNotSupportedError: If the file format is not supported
@@ -1315,6 +1358,10 @@ def get_full_metadata(
1315
1358
  # Access header information
1316
1359
  print(f"ID3v2 Version: {full_metadata['headers']['id3v2']['version']}")
1317
1360
  print(f"Has ID3v1 Header: {full_metadata['headers']['id3v1']['present']}")
1361
+
1362
+ # Full unified key schema; writable field ids for this file's primary format
1363
+ print(len(full_metadata['unified_metadata_field_schema']))
1364
+ print(full_metadata['supported_unified_metadata_field_ids'])
1318
1365
  """
1319
1366
  audio_file = _AudioFile(file)
1320
1367
 
@@ -1324,6 +1371,9 @@ def get_full_metadata(
1324
1371
  # Get file-specific format priorities
1325
1372
  available_formats = MetadataFormat.get_priorities().get(audio_file.file_extension, [])
1326
1373
 
1374
+ primary_format = available_formats[0] if available_formats else None
1375
+ primary_manager = all_managers.get(primary_format) if primary_format is not None else None
1376
+
1327
1377
  # Initialize result structure
1328
1378
  result: dict[str, Any] = {
1329
1379
  "unified_metadata": {},
@@ -1331,6 +1381,8 @@ def get_full_metadata(
1331
1381
  "metadata_format": {},
1332
1382
  "headers": {},
1333
1383
  "raw_metadata": {},
1384
+ "unified_metadata_field_schema": get_unified_metadata_field_schema(),
1385
+ "supported_unified_metadata_field_ids": _supported_unified_metadata_field_ids_from_manager(primary_manager),
1334
1386
  "format_priorities": {
1335
1387
  "file_extension": audio_file.file_extension,
1336
1388
  "reading_order": [fmt.value for fmt in available_formats],
@@ -4,7 +4,7 @@ from pathlib import Path
4
4
 
5
5
  import pytest
6
6
 
7
- from audiometa import get_full_metadata
7
+ from audiometa import UnifiedMetadataKey, get_full_metadata, get_unified_metadata_field_schema
8
8
 
9
9
 
10
10
  @pytest.mark.integration
@@ -20,6 +20,8 @@ class TestGetFullMetadata:
20
20
  assert "headers" in result
21
21
  assert "raw_metadata" in result
22
22
  assert "format_priorities" in result
23
+ assert "unified_metadata_field_schema" in result
24
+ assert "supported_unified_metadata_field_ids" in result
23
25
 
24
26
  # Check that each section is a dictionary
25
27
  assert isinstance(result["unified_metadata"], dict)
@@ -28,6 +30,16 @@ class TestGetFullMetadata:
28
30
  assert isinstance(result["headers"], dict)
29
31
  assert isinstance(result["raw_metadata"], dict)
30
32
  assert isinstance(result["format_priorities"], dict)
33
+ schema = result["unified_metadata_field_schema"]
34
+ assert isinstance(schema, list)
35
+ assert len(schema) == len(UnifiedMetadataKey)
36
+ assert {item["id"] for item in schema} == {k.value for k in UnifiedMetadataKey}
37
+ supported = result["supported_unified_metadata_field_ids"]
38
+ assert isinstance(supported, list)
39
+ assert all(isinstance(x, str) for x in supported)
40
+ assert supported == sorted(supported)
41
+ assert set(supported) <= {k.value for k in UnifiedMetadataKey}
42
+ assert schema == get_unified_metadata_field_schema()
31
43
 
32
44
  def test_get_full_metadata_format_priorities_structure(self, sample_mp3_file: Path):
33
45
  """Test that format_priorities has the expected structure."""
@@ -0,0 +1,48 @@
1
+ """Tests for get_supported_unified_metadata_field_ids."""
2
+
3
+ from pathlib import Path
4
+
5
+ import pytest
6
+
7
+ from audiometa import FileTypeNotSupportedError, get_full_metadata, get_supported_unified_metadata_field_ids
8
+
9
+
10
+ @pytest.mark.unit
11
+ class TestGetSupportedUnifiedMetadataFieldIds:
12
+ @pytest.mark.parametrize(
13
+ "fixture_name",
14
+ [
15
+ pytest.param("sample_mp3_file", id="mp3"),
16
+ pytest.param("sample_flac_file", id="flac"),
17
+ pytest.param("sample_wav_file", id="wav"),
18
+ ],
19
+ )
20
+ def test_returns_sorted_ids(self, fixture_name: str, request: pytest.FixtureRequest) -> None:
21
+ path: Path = request.getfixturevalue(fixture_name)
22
+ supported = get_supported_unified_metadata_field_ids(path)
23
+ assert supported == sorted(supported)
24
+
25
+ @pytest.mark.parametrize(
26
+ "fixture_name",
27
+ [
28
+ pytest.param("sample_mp3_file", id="mp3"),
29
+ pytest.param("sample_flac_file", id="flac"),
30
+ pytest.param("sample_wav_file", id="wav"),
31
+ ],
32
+ )
33
+ def test_matches_get_full_metadata(self, fixture_name: str, request: pytest.FixtureRequest) -> None:
34
+ path: Path = request.getfixturevalue(fixture_name)
35
+ direct = get_supported_unified_metadata_field_ids(path)
36
+ from_full = get_full_metadata(path)["supported_unified_metadata_field_ids"]
37
+ assert direct == from_full
38
+
39
+ def test_accepts_str_path(self, sample_mp3_file: Path) -> None:
40
+ a = get_supported_unified_metadata_field_ids(str(sample_mp3_file))
41
+ b = get_supported_unified_metadata_field_ids(sample_mp3_file)
42
+ assert a == b
43
+
44
+ def test_unsupported_extension_raises(self, tmp_path: Path) -> None:
45
+ p = tmp_path / "x.txt"
46
+ p.write_bytes(b"not audio")
47
+ with pytest.raises(FileTypeNotSupportedError):
48
+ get_supported_unified_metadata_field_ids(p)
@@ -0,0 +1,88 @@
1
+ """Tests for unified metadata field schema helpers."""
2
+
3
+ import pytest
4
+
5
+ from audiometa import UnifiedMetadataKey, get_unified_metadata_field_schema
6
+ from audiometa.utils.unified_metadata_field_schema import describe_unified_metadata_field
7
+
8
+
9
+ def _expected_value_shape(key: UnifiedMetadataKey) -> tuple[str, bool, bool]:
10
+ if key.can_semantically_have_multiple_values():
11
+ return ("strings", True, False)
12
+ if key == UnifiedMetadataKey.TRACK_NUMBER:
13
+ return ("string_or_integer", False, False)
14
+ if key == UnifiedMetadataKey.DISC_TOTAL:
15
+ return ("integer", False, True)
16
+ if key == UnifiedMetadataKey.RATING:
17
+ return ("number", False, False)
18
+ opt = key.get_optional_type()
19
+ if opt is str:
20
+ return ("string", False, False)
21
+ if opt is int:
22
+ return ("integer", False, False)
23
+ return ("string", False, False)
24
+
25
+
26
+ @pytest.mark.unit
27
+ class TestUnifiedMetadataFieldSchema:
28
+ def test_schema_covers_all_enum_members(self) -> None:
29
+ schema = get_unified_metadata_field_schema()
30
+ ids = {entry["id"] for entry in schema}
31
+ assert ids == {k.value for k in UnifiedMetadataKey}
32
+
33
+ def test_each_descriptor_keys_and_id_match_enum(self) -> None:
34
+ required = {"id", "label", "multiple", "value_type", "optional_value"}
35
+ for key in UnifiedMetadataKey:
36
+ d = describe_unified_metadata_field(key)
37
+ assert set(d.keys()) == required
38
+ assert d["id"] == key.value
39
+ assert isinstance(d["label"], str)
40
+ assert d["label"]
41
+ assert isinstance(d["multiple"], bool)
42
+ assert isinstance(d["value_type"], str)
43
+ assert d["value_type"]
44
+ assert isinstance(d["optional_value"], bool)
45
+
46
+ def test_each_descriptor_value_shape_matches_contract(self) -> None:
47
+ for key in UnifiedMetadataKey:
48
+ d = describe_unified_metadata_field(key)
49
+ vt, mult, optv = _expected_value_shape(key)
50
+ assert d["value_type"] == vt
51
+ assert d["multiple"] is mult
52
+ assert d["optional_value"] is optv
53
+
54
+ def test_describe_title_scalar_string(self) -> None:
55
+ d = describe_unified_metadata_field(UnifiedMetadataKey.TITLE)
56
+ assert d["id"] == "title"
57
+ assert d["label"] == "Title"
58
+ assert d["multiple"] is False
59
+ assert d["value_type"] == "string"
60
+ assert d["optional_value"] is False
61
+
62
+ def test_describe_artists_multi_strings(self) -> None:
63
+ d = describe_unified_metadata_field(UnifiedMetadataKey.ARTISTS)
64
+ assert d["multiple"] is True
65
+ assert d["value_type"] == "strings"
66
+
67
+ def test_describe_track_number_string_or_integer(self) -> None:
68
+ d = describe_unified_metadata_field(UnifiedMetadataKey.TRACK_NUMBER)
69
+ assert d["value_type"] == "string_or_integer"
70
+ assert d["multiple"] is False
71
+ assert d["optional_value"] is False
72
+
73
+ def test_describe_disc_total_integer_optional_value(self) -> None:
74
+ d = describe_unified_metadata_field(UnifiedMetadataKey.DISC_TOTAL)
75
+ assert d["value_type"] == "integer"
76
+ assert d["optional_value"] is True
77
+ assert d["multiple"] is False
78
+
79
+ def test_describe_rating_number(self) -> None:
80
+ d = describe_unified_metadata_field(UnifiedMetadataKey.RATING)
81
+ assert d["value_type"] == "number"
82
+ assert d["multiple"] is False
83
+ assert d["optional_value"] is False
84
+
85
+ def test_describe_bpm_integer_scalar(self) -> None:
86
+ d = describe_unified_metadata_field(UnifiedMetadataKey.BPM)
87
+ assert d["value_type"] == "integer"
88
+ assert d["optional_value"] is False
@@ -71,6 +71,16 @@ class RawMetadataInfo(TypedDict):
71
71
  chunk_structure: dict[str, Any]
72
72
 
73
73
 
74
+ class UnifiedMetadataFieldDescriptor(TypedDict):
75
+ """Single entry from get_unified_metadata_field_schema()."""
76
+
77
+ id: str
78
+ label: str
79
+ multiple: bool
80
+ value_type: str
81
+ optional_value: bool
82
+
83
+
74
84
  class FullMetadata(TypedDict):
75
85
  """Type for comprehensive metadata returned by get_full_metadata."""
76
86
 
@@ -79,4 +89,6 @@ class FullMetadata(TypedDict):
79
89
  metadata_format: dict[str, UnifiedMetadata]
80
90
  headers: dict[str, HeaderInfo]
81
91
  raw_metadata: dict[str, RawMetadataInfo]
92
+ unified_metadata_field_schema: list[UnifiedMetadataFieldDescriptor]
93
+ supported_unified_metadata_field_ids: list[str]
82
94
  format_priorities: FormatPriorities
@@ -0,0 +1,80 @@
1
+ """Wire-oriented schema for unified metadata fields (API / UI).
2
+
3
+ Describes every :class:`UnifiedMetadataKey` with stable ids (enum values), human labels,
4
+ and value shape hints. Format-specific mapping stays in metadata managers.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Any
10
+
11
+ from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
12
+
13
+ # Stable English labels for clients that do not localize.
14
+ _UNIFIED_METADATA_LABELS: dict[UnifiedMetadataKey, str] = {
15
+ UnifiedMetadataKey.TITLE: "Title",
16
+ UnifiedMetadataKey.ARTISTS: "Artists",
17
+ UnifiedMetadataKey.ALBUM: "Album",
18
+ UnifiedMetadataKey.ALBUM_ARTISTS: "Album artists",
19
+ UnifiedMetadataKey.GENRES_NAMES: "Genres",
20
+ UnifiedMetadataKey.RATING: "Rating",
21
+ UnifiedMetadataKey.LANGUAGE: "Language",
22
+ UnifiedMetadataKey.RELEASE_DATE: "Release date",
23
+ UnifiedMetadataKey.TRACK_NUMBER: "Track number",
24
+ UnifiedMetadataKey.DISC_NUMBER: "Disc number",
25
+ UnifiedMetadataKey.DISC_TOTAL: "Total discs",
26
+ UnifiedMetadataKey.BPM: "BPM",
27
+ UnifiedMetadataKey.COMPOSERS: "Composers",
28
+ UnifiedMetadataKey.PUBLISHER: "Publisher",
29
+ UnifiedMetadataKey.COPYRIGHT: "Copyright",
30
+ UnifiedMetadataKey.UNSYNCHRONIZED_LYRICS: "Lyrics (unsynchronized)",
31
+ UnifiedMetadataKey.COMMENT: "Comment",
32
+ UnifiedMetadataKey.REPLAYGAIN: "ReplayGain",
33
+ UnifiedMetadataKey.ARCHIVAL_LOCATION: "Archival location",
34
+ UnifiedMetadataKey.ISRC: "ISRC",
35
+ UnifiedMetadataKey.MUSICBRAINZ_TRACKID: "MusicBrainz recording ID",
36
+ UnifiedMetadataKey.MUSICBRAINZ_ARTISTIDS: "MusicBrainz artist IDs",
37
+ UnifiedMetadataKey.DESCRIPTION: "Description",
38
+ UnifiedMetadataKey.ORIGINATOR: "Originator",
39
+ }
40
+
41
+ # value_type is a coarse JSON-oriented hint for forms.
42
+ # - strings: list of strings
43
+ # - string, integer, number: scalar
44
+ # - string_or_integer: track number (library accepts int or str on write)
45
+ # integer with optional_value=True: may be null (e.g. disc total)
46
+ _VALUE_SHAPE: dict[UnifiedMetadataKey, tuple[str, bool, bool]] = {}
47
+ for _k in UnifiedMetadataKey:
48
+ if _k.can_semantically_have_multiple_values():
49
+ _VALUE_SHAPE[_k] = ("strings", True, False)
50
+ elif _k == UnifiedMetadataKey.TRACK_NUMBER:
51
+ _VALUE_SHAPE[_k] = ("string_or_integer", False, False)
52
+ elif _k == UnifiedMetadataKey.DISC_TOTAL:
53
+ _VALUE_SHAPE[_k] = ("integer", False, True)
54
+ elif _k == UnifiedMetadataKey.RATING:
55
+ _VALUE_SHAPE[_k] = ("number", False, False)
56
+ else:
57
+ _opt = _k.get_optional_type()
58
+ if _opt is str:
59
+ _VALUE_SHAPE[_k] = ("string", False, False)
60
+ elif _opt is int:
61
+ _VALUE_SHAPE[_k] = ("integer", False, False)
62
+ else:
63
+ _VALUE_SHAPE[_k] = ("string", False, False)
64
+
65
+
66
+ def describe_unified_metadata_field(key: UnifiedMetadataKey) -> dict[str, Any]:
67
+ """Single field descriptor for APIs."""
68
+ value_type, multiple, optional_value = _VALUE_SHAPE[key]
69
+ return {
70
+ "id": key.value,
71
+ "label": _UNIFIED_METADATA_LABELS[key],
72
+ "multiple": multiple,
73
+ "value_type": value_type,
74
+ "optional_value": optional_value,
75
+ }
76
+
77
+
78
+ def get_unified_metadata_field_schema() -> list[dict[str, Any]]:
79
+ """Return descriptors for all unified metadata keys (library vocabulary)."""
80
+ return [describe_unified_metadata_field(k) for k in UnifiedMetadataKey]