audiometa-python 0.9.0__tar.gz → 0.11.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 (396) hide show
  1. {audiometa_python-0.9.0/audiometa_python.egg-info → audiometa_python-0.11.0}/PKG-INFO +1 -1
  2. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/__init__.py +10 -0
  3. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/cli.py +13 -0
  4. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/manager/_MetadataManager.py +42 -0
  5. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/manager/_rating_supporting/id3v2/_Id3v2Manager.py +81 -0
  6. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/manager/_rating_supporting/riff/_RiffManager.py +26 -1
  7. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/manager/_rating_supporting/riff/_riff_bext_chunk.py +28 -8
  8. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/manager/_rating_supporting/vorbis/_VorbisManager.py +4 -0
  9. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/helpers/id3v2/id3v2_frame_manual_creator.py +54 -0
  10. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/helpers/id3v2/id3v2_metadata_setter.py +73 -1
  11. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/helpers/riff/riff_manual_metadata_creator.py +12 -0
  12. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/helpers/riff/riff_metadata_setter.py +20 -1
  13. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/helpers/vorbis/vorbis_metadata_setter.py +1 -0
  14. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/e2e/cli/read/test_comprehensive.py +11 -0
  15. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/e2e/cli/write/test_comprehensive.py +12 -0
  16. audiometa_python-0.11.0/audiometa/test/tests/integration/metadata_field/musicbrainz_trackid/test_deleting.py +37 -0
  17. audiometa_python-0.11.0/audiometa/test/tests/integration/metadata_field/musicbrainz_trackid/test_reading.py +91 -0
  18. audiometa_python-0.11.0/audiometa/test/tests/integration/metadata_field/musicbrainz_trackid/test_writing.py +161 -0
  19. audiometa_python-0.11.0/audiometa/test/tests/integration/metadata_field/originator/test_deleting.py +50 -0
  20. audiometa_python-0.11.0/audiometa/test/tests/integration/metadata_field/originator/test_reading.py +37 -0
  21. audiometa_python-0.11.0/audiometa/test/tests/integration/metadata_field/originator/test_writing.py +60 -0
  22. audiometa_python-0.11.0/audiometa/test/tests/unit/metadata_managers/metadata_field/multiple_values/writing/__init__.py +0 -0
  23. audiometa_python-0.11.0/audiometa/test/tests/unit/metadata_managers/metadata_field/musicbrainz_trackid/__init__.py +0 -0
  24. audiometa_python-0.11.0/audiometa/test/tests/unit/metadata_managers/metadata_field/musicbrainz_trackid/test_musicbrainz_trackid_format_validation.py +111 -0
  25. audiometa_python-0.11.0/audiometa/test/tests/unit/metadata_managers/metadata_field/musicbrainz_trackid/test_musicbrainz_trackid_type_validation.py +32 -0
  26. audiometa_python-0.11.0/audiometa/test/tests/unit/metadata_managers/metadata_field/rating/__init__.py +0 -0
  27. audiometa_python-0.11.0/audiometa/test/tests/unit/metadata_managers/metadata_field/rating/reading/__init__.py +0 -0
  28. audiometa_python-0.11.0/audiometa/test/tests/unit/metadata_managers/metadata_field/rating/writing/__init__.py +0 -0
  29. audiometa_python-0.11.0/audiometa/test/tests/unit/metadata_managers/metadata_field/release_date/__init__.py +0 -0
  30. audiometa_python-0.11.0/audiometa/test/tests/unit/metadata_managers/metadata_field/track_number/__init__.py +0 -0
  31. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/utils/unified_metadata_key.py +4 -0
  32. {audiometa_python-0.9.0 → audiometa_python-0.11.0/audiometa_python.egg-info}/PKG-INFO +1 -1
  33. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa_python.egg-info/SOURCES.txt +20 -5
  34. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/pyproject.toml +1 -1
  35. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/LICENSE +0 -0
  36. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/README.md +0 -0
  37. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/__main__.py +0 -0
  38. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/_audio_file.py +0 -0
  39. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/exceptions.py +0 -0
  40. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/manager/__init__.py +0 -0
  41. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/manager/_rating_supporting/_RatingSupportingMetadataManager.py +0 -0
  42. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/manager/_rating_supporting/__init__.py +0 -0
  43. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/manager/_rating_supporting/id3v2/__init__.py +0 -0
  44. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/manager/_rating_supporting/id3v2/_id3v2_constants.py +0 -0
  45. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/manager/_rating_supporting/riff/__init__.py +0 -0
  46. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/manager/_rating_supporting/riff/_riff_constants.py +0 -0
  47. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/manager/_rating_supporting/riff/_riff_file_structure.py +0 -0
  48. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/manager/_rating_supporting/riff/_riff_info_chunk.py +0 -0
  49. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/manager/_rating_supporting/vorbis/__init__.py +0 -0
  50. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/manager/_rating_supporting/vorbis/_vorbis_constants.py +0 -0
  51. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/manager/id3v1/_Id3v1Manager.py +0 -0
  52. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/manager/id3v1/__init__.py +0 -0
  53. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/manager/id3v1/_constants.py +0 -0
  54. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/manager/id3v1/id3v1_raw_metadata.py +0 -0
  55. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/manager/id3v1/id3v1_raw_metadata_key.py +0 -0
  56. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/__init__.py +0 -0
  57. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/assets/create_test_files.py +0 -0
  58. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/helpers/__init__.py +0 -0
  59. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/helpers/common/__init__.py +0 -0
  60. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/helpers/common/audio_file_creator.py +0 -0
  61. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/helpers/common/external_tool_runner.py +0 -0
  62. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/helpers/id3v1/__init__.py +0 -0
  63. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/helpers/id3v1/id3v1_header_verifier.py +0 -0
  64. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/helpers/id3v1/id3v1_metadata_deleter.py +0 -0
  65. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/helpers/id3v1/id3v1_metadata_getter.py +0 -0
  66. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/helpers/id3v1/id3v1_metadata_setter.py +0 -0
  67. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/helpers/id3v2/__init__.py +0 -0
  68. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/helpers/id3v2/id3v2_header_verifier.py +0 -0
  69. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/helpers/id3v2/id3v2_metadata_deleter.py +0 -0
  70. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/helpers/id3v2/id3v2_metadata_getter.py +0 -0
  71. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/helpers/riff/__init__.py +0 -0
  72. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/helpers/riff/riff_header_verifier.py +0 -0
  73. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/helpers/riff/riff_metadata_deleter.py +0 -0
  74. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/helpers/riff/riff_metadata_getter.py +0 -0
  75. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/helpers/scripts/__init__.py +0 -0
  76. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/helpers/technical_info_inspector.py +0 -0
  77. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/helpers/temp_file_with_metadata.py +0 -0
  78. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/helpers/vorbis/__init__.py +0 -0
  79. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/helpers/vorbis/vorbis_header_verifier.py +0 -0
  80. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/helpers/vorbis/vorbis_metadata_deleter.py +0 -0
  81. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/helpers/vorbis/vorbis_metadata_getter.py +0 -0
  82. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/__init__.py +0 -0
  83. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/conftest.py +0 -0
  84. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/e2e/__init__.py +0 -0
  85. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/e2e/cli/__init__.py +0 -0
  86. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/e2e/cli/error_handling/__init__.py +0 -0
  87. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/e2e/cli/error_handling/test_command_structure_errors.py +0 -0
  88. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/e2e/cli/error_handling/test_file_access_errors.py +0 -0
  89. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/e2e/cli/error_handling/test_format_output_errors.py +0 -0
  90. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/e2e/cli/error_handling/test_input_validation_errors.py +0 -0
  91. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/e2e/cli/error_handling/test_missing_fields_validation.py +0 -0
  92. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/e2e/cli/error_handling/test_multiple_files_errors.py +0 -0
  93. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/e2e/cli/error_handling/test_rating_validation.py +0 -0
  94. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/e2e/cli/error_handling/test_year_validation.py +0 -0
  95. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/e2e/cli/read/__init__.py +0 -0
  96. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/e2e/cli/read/test_basic.py +0 -0
  97. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/e2e/cli/read/test_formats.py +0 -0
  98. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/e2e/cli/read/test_metadata_content.py +0 -0
  99. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/e2e/cli/read/test_multiple_files.py +0 -0
  100. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/e2e/cli/read/test_options.py +0 -0
  101. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/e2e/cli/read/test_unified.py +0 -0
  102. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/e2e/cli/test_delete.py +0 -0
  103. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/e2e/cli/test_formatting.py +0 -0
  104. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/e2e/cli/test_help.py +0 -0
  105. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/e2e/cli/write/__init__.py +0 -0
  106. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/e2e/cli/write/test_basic.py +0 -0
  107. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/e2e/cli/write/test_force_format.py +0 -0
  108. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/e2e/cli/write/test_integer_fields.py +0 -0
  109. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/e2e/cli/write/test_list_fields.py +0 -0
  110. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/e2e/cli/write/test_rating.py +0 -0
  111. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/e2e/cli/write/test_string_fields.py +0 -0
  112. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/e2e/cli/write/test_validation.py +0 -0
  113. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/e2e/scenarios/__init__.py +0 -0
  114. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/e2e/scenarios/test_user_scenarios.py +0 -0
  115. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/e2e/workflows/__init__.py +0 -0
  116. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/e2e/workflows/test_core_workflows.py +0 -0
  117. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/e2e/workflows/test_deletion_workflows.py +0 -0
  118. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/e2e/workflows/test_error_handling_workflows.py +0 -0
  119. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/e2e/workflows/test_format_specific_workflows.py +0 -0
  120. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/e2e/workflows/test_rating_workflows.py +0 -0
  121. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/__init__.py +0 -0
  122. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/audio_format/__init__.py +0 -0
  123. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/audio_format/flac/__init__.py +0 -0
  124. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/audio_format/flac/test_flac_delete_all.py +0 -0
  125. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/audio_format/flac/test_flac_reading_all.py +0 -0
  126. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/audio_format/flac/test_flac_reading_field.py +0 -0
  127. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/audio_format/flac/test_flac_writing.py +0 -0
  128. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/audio_format/mp3/__init__.py +0 -0
  129. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/audio_format/mp3/test_mp3_delete_all.py +0 -0
  130. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/audio_format/mp3/test_mp3_reading_all.py +0 -0
  131. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/audio_format/mp3/test_mp3_reading_field.py +0 -0
  132. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/audio_format/mp3/test_mp3_writing.py +0 -0
  133. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/audio_format/wav/__init__.py +0 -0
  134. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/audio_format/wav/test_wav_delete_all.py +0 -0
  135. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/audio_format/wav/test_wav_reading_all.py +0 -0
  136. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/audio_format/wav/test_wav_reading_field.py +0 -0
  137. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/audio_format/wav/test_wav_writing.py +0 -0
  138. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/conftest.py +0 -0
  139. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/delete_all_metadata/__init__.py +0 -0
  140. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/delete_all_metadata/test_audio_format_all.py +0 -0
  141. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/delete_all_metadata/test_audio_format_header_deletion.py +0 -0
  142. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/delete_all_metadata/test_basic_functionality.py +0 -0
  143. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/delete_all_metadata/test_error_handling.py +0 -0
  144. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/encoding/__init__.py +0 -0
  145. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/encoding/test_encoding.py +0 -0
  146. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/encoding/test_special_characters_edge_cases.py +0 -0
  147. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/get_full_metadata/__init__.py +0 -0
  148. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/get_full_metadata/test_audio_formats.py +0 -0
  149. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/get_full_metadata/test_binary_data_filtering.py +0 -0
  150. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/get_full_metadata/test_consistency.py +0 -0
  151. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/get_full_metadata/test_edge_cases.py +0 -0
  152. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/get_full_metadata/test_error_handling.py +0 -0
  153. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/get_full_metadata/test_get_full_metadata.py +0 -0
  154. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/get_full_metadata/test_options.py +0 -0
  155. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/get_full_metadata/test_performance.py +0 -0
  156. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/get_full_metadata/test_riff_bext.py +0 -0
  157. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/get_full_metadata/test_structure.py +0 -0
  158. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/__init__.py +0 -0
  159. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/album/__init__.py +0 -0
  160. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/album/test_deleting.py +0 -0
  161. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/album/test_reading.py +0 -0
  162. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/album/test_writing.py +0 -0
  163. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/album_artists/__init__.py +0 -0
  164. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/album_artists/test_deleting.py +0 -0
  165. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/album_artists/test_reading.py +0 -0
  166. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/album_artists/test_writing.py +0 -0
  167. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/artists/__init__.py +0 -0
  168. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/artists/test_deleting.py +0 -0
  169. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/artists/test_reading.py +0 -0
  170. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/artists/test_writing.py +0 -0
  171. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/bpm/__init__.py +0 -0
  172. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/bpm/test_deleting.py +0 -0
  173. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/bpm/test_reading.py +0 -0
  174. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/bpm/test_writing.py +0 -0
  175. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/comment/__init__.py +0 -0
  176. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/comment/test_deleting.py +0 -0
  177. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/comment/test_reading.py +0 -0
  178. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/comment/test_writing.py +0 -0
  179. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/composer/__init__.py +0 -0
  180. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/composer/test_deleting.py +0 -0
  181. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/composer/test_reading.py +0 -0
  182. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/composer/test_writing.py +0 -0
  183. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/copyright/__init__.py +0 -0
  184. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/copyright/test_deleting.py +0 -0
  185. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/copyright/test_reading.py +0 -0
  186. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/copyright/test_writing.py +0 -0
  187. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/description/__init__.py +0 -0
  188. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/description/test_deleting.py +0 -0
  189. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/description/test_reading.py +0 -0
  190. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/description/test_writing.py +0 -0
  191. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/disc_number/__init__.py +0 -0
  192. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/disc_number/test_deleting.py +0 -0
  193. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/disc_number/test_reading.py +0 -0
  194. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/disc_number/test_writing.py +0 -0
  195. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/field_not_supported/__init__.py +0 -0
  196. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/field_not_supported/test_deleting.py +0 -0
  197. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/field_not_supported/test_reading.py +0 -0
  198. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/field_not_supported/test_writing.py +0 -0
  199. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/genre/__init__.py +0 -0
  200. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/genre/reading/__init__.py +0 -0
  201. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/genre/reading/metadata_format/__init__.py +0 -0
  202. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/genre/reading/metadata_format/test_id3v1_reading.py +0 -0
  203. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/genre/reading/metadata_format/test_id3v2_reading.py +0 -0
  204. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/genre/reading/metadata_format/test_riff_reading.py +0 -0
  205. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/genre/reading/metadata_format/test_vorbis_reading.py +0 -0
  206. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/genre/reading/test_smart_reading.py +0 -0
  207. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/genre/test_deleting.py +0 -0
  208. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/genre/test_writing.py +0 -0
  209. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/isrc/__init__.py +0 -0
  210. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/isrc/test_deleting.py +0 -0
  211. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/isrc/test_reading.py +0 -0
  212. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/isrc/test_writing.py +0 -0
  213. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/language/__init__.py +0 -0
  214. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/language/test_deleting.py +0 -0
  215. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/language/test_reading.py +0 -0
  216. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/language/test_writing.py +0 -0
  217. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/lyrics/__init__.py +0 -0
  218. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/lyrics/test_deleting.py +0 -0
  219. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/lyrics/test_reading.py +0 -0
  220. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/lyrics/test_writing.py +0 -0
  221. {audiometa_python-0.9.0/audiometa/test/tests/integration/metadata_field/publisher → audiometa_python-0.11.0/audiometa/test/tests/integration/metadata_field/musicbrainz_trackid}/__init__.py +0 -0
  222. {audiometa_python-0.9.0/audiometa/test/tests/integration/metadata_field/rating → audiometa_python-0.11.0/audiometa/test/tests/integration/metadata_field/originator}/__init__.py +0 -0
  223. {audiometa_python-0.9.0/audiometa/test/tests/integration/metadata_field/rating/reading → audiometa_python-0.11.0/audiometa/test/tests/integration/metadata_field/publisher}/__init__.py +0 -0
  224. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/publisher/test_deleting.py +0 -0
  225. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/publisher/test_reading.py +0 -0
  226. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/publisher/test_writing.py +0 -0
  227. {audiometa_python-0.9.0/audiometa/test/tests/integration/metadata_field/rating/writing → audiometa_python-0.11.0/audiometa/test/tests/integration/metadata_field/rating}/__init__.py +0 -0
  228. {audiometa_python-0.9.0/audiometa/test/tests/integration/metadata_field/rating/writing/metadata_format → audiometa_python-0.11.0/audiometa/test/tests/integration/metadata_field/rating/reading}/__init__.py +0 -0
  229. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/rating/reading/test_base_100_proportional.py +0 -0
  230. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/rating/reading/test_base_255_non_proportional.py +0 -0
  231. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/rating/reading/test_base_255_proportional.py +0 -0
  232. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/rating/test_deleting.py +0 -0
  233. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/rating/test_error_handling.py +0 -0
  234. {audiometa_python-0.9.0/audiometa/test/tests/integration/metadata_field/release_date → audiometa_python-0.11.0/audiometa/test/tests/integration/metadata_field/rating/writing}/__init__.py +0 -0
  235. {audiometa_python-0.9.0/audiometa/test/tests/integration/metadata_field/title → audiometa_python-0.11.0/audiometa/test/tests/integration/metadata_field/rating/writing/metadata_format}/__init__.py +0 -0
  236. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/rating/writing/metadata_format/test_id3v2.py +0 -0
  237. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/rating/writing/metadata_format/test_riff.py +0 -0
  238. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/rating/writing/metadata_format/test_vorbis.py +0 -0
  239. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/rating/writing/test_comprehensive.py +0 -0
  240. {audiometa_python-0.9.0/audiometa/test/tests/integration/metadata_field/track_number → audiometa_python-0.11.0/audiometa/test/tests/integration/metadata_field/release_date}/__init__.py +0 -0
  241. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/release_date/test_deleting.py +0 -0
  242. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/release_date/test_error_handling.py +0 -0
  243. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/release_date/test_reading.py +0 -0
  244. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/release_date/test_writing.py +0 -0
  245. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/test_metadata_field_validation.py +0 -0
  246. {audiometa_python-0.9.0/audiometa/test/tests/integration/metadata_field/track_number/reading → audiometa_python-0.11.0/audiometa/test/tests/integration/metadata_field/title}/__init__.py +0 -0
  247. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/title/test_deleting.py +0 -0
  248. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/title/test_error_handling.py +0 -0
  249. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/title/test_reading.py +0 -0
  250. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/title/test_writing.py +0 -0
  251. {audiometa_python-0.9.0/audiometa/test/tests/integration/real_audio_files → audiometa_python-0.11.0/audiometa/test/tests/integration/metadata_field/track_number}/__init__.py +0 -0
  252. {audiometa_python-0.9.0/audiometa/test/tests/integration/technical_info → audiometa_python-0.11.0/audiometa/test/tests/integration/metadata_field/track_number/reading}/__init__.py +0 -0
  253. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/track_number/reading/test_edge_cases.py +0 -0
  254. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/track_number/reading/test_metadata_format.py +0 -0
  255. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/track_number/test_deleting.py +0 -0
  256. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/metadata_field/track_number/test_writing.py +0 -0
  257. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/multiple_values/__init__.py +0 -0
  258. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/multiple_values/reading/__init__.py +0 -0
  259. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/multiple_values/reading/metadata_format/__init__.py +0 -0
  260. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/multiple_values/reading/metadata_format/test_id3v1.py +0 -0
  261. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/multiple_values/reading/metadata_format/test_id3v2_3.py +0 -0
  262. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/multiple_values/reading/metadata_format/test_id3v2_4.py +0 -0
  263. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/multiple_values/reading/metadata_format/test_riff.py +0 -0
  264. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/multiple_values/reading/metadata_format/test_vorbis.py +0 -0
  265. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/multiple_values/reading/test_performance_large_data.py +0 -0
  266. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/multiple_values/reading/test_smart_parsing_scenarios.py +0 -0
  267. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/multiple_values/reading/test_unicode_handling.py +0 -0
  268. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/multiple_values/writing/__init__.py +0 -0
  269. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/multiple_values/writing/metadata_format/__init__.py +0 -0
  270. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/multiple_values/writing/metadata_format/test_id3v1.py +0 -0
  271. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/multiple_values/writing/metadata_format/test_id3v2_3.py +0 -0
  272. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/multiple_values/writing/metadata_format/test_id3v2_4.py +0 -0
  273. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/multiple_values/writing/metadata_format/test_riff.py +0 -0
  274. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/multiple_values/writing/metadata_format/test_vorbis.py +0 -0
  275. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/multiple_values/writing/test_error_handling.py +0 -0
  276. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/multiple_values/writing/test_large_values.py +0 -0
  277. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/reading/__init__.py +0 -0
  278. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/reading/test_read_multiple_metadata.py +0 -0
  279. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/reading/test_reading_error_handling.py +0 -0
  280. {audiometa_python-0.9.0/audiometa/test/tests/integration/technical_info/flac_md5 → audiometa_python-0.11.0/audiometa/test/tests/integration/real_audio_files}/__init__.py +0 -0
  281. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/real_audio_files/test_reading.py +0 -0
  282. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/real_audio_files/test_writing.py +0 -0
  283. {audiometa_python-0.9.0/audiometa/test/tests/integration/technical_info/flac_md5/md5_checking → audiometa_python-0.11.0/audiometa/test/tests/integration/technical_info}/__init__.py +0 -0
  284. {audiometa_python-0.9.0/audiometa/test/tests/integration/technical_info/flac_md5/md5_checking/test_metadata_combinations → audiometa_python-0.11.0/audiometa/test/tests/integration/technical_info/flac_md5}/__init__.py +0 -0
  285. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/technical_info/flac_md5/conftest.py +0 -0
  286. {audiometa_python-0.9.0/audiometa/test/tests/integration/technical_info/flac_md5/md5_repair → audiometa_python-0.11.0/audiometa/test/tests/integration/technical_info/flac_md5/md5_checking}/__init__.py +0 -0
  287. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/technical_info/flac_md5/md5_checking/test_audio_data_corruption.py +0 -0
  288. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/technical_info/flac_md5/md5_checking/test_file.py +0 -0
  289. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/technical_info/flac_md5/md5_checking/test_invalid_md5.py +0 -0
  290. {audiometa_python-0.9.0/audiometa/test/tests/integration/technical_info/flac_md5/md5_repair/test_invalid_md5 → audiometa_python-0.11.0/audiometa/test/tests/integration/technical_info/flac_md5/md5_checking/test_metadata_combinations}/__init__.py +0 -0
  291. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/technical_info/flac_md5/md5_checking/test_metadata_combinations/test_md5_invalid_with_metadata_combinations.py +0 -0
  292. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/technical_info/flac_md5/md5_checking/test_metadata_combinations/test_md5_state_precedence.py +0 -0
  293. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/technical_info/flac_md5/md5_checking/test_metadata_combinations/test_md5_unset_with_metadata_combinations.py +0 -0
  294. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/technical_info/flac_md5/md5_checking/test_metadata_combinations/test_md5_validation_fails_with_id3v1.py +0 -0
  295. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/technical_info/flac_md5/md5_checking/test_metadata_combinations/test_md5_validation_with_id3v2_only.py +0 -0
  296. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/technical_info/flac_md5/md5_checking/test_metadata_combinations/test_md5_validation_works_without_id3v1.py +0 -0
  297. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/technical_info/flac_md5/md5_checking/test_metadata_combinations/test_md5_with_metadata_combinations.py +0 -0
  298. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/technical_info/flac_md5/md5_checking/test_unset_md5.py +0 -0
  299. {audiometa_python-0.9.0/audiometa/test/tests/integration/writing → audiometa_python-0.11.0/audiometa/test/tests/integration/technical_info/flac_md5/md5_repair}/__init__.py +0 -0
  300. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/technical_info/flac_md5/md5_repair/test_delete_original.py +0 -0
  301. {audiometa_python-0.9.0/audiometa/test/tests/integration/writing/writing_strategies → audiometa_python-0.11.0/audiometa/test/tests/integration/technical_info/flac_md5/md5_repair/test_invalid_md5}/__init__.py +0 -0
  302. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/technical_info/flac_md5/md5_repair/test_invalid_md5/test_flipped_md5.py +0 -0
  303. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/technical_info/flac_md5/md5_repair/test_invalid_md5/test_partial_md5.py +0 -0
  304. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/technical_info/flac_md5/md5_repair/test_invalid_md5/test_random_md5.py +0 -0
  305. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/technical_info/flac_md5/md5_repair/test_md5_repair_with_metadata.py +0 -0
  306. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/technical_info/flac_md5/md5_repair/test_non_flac_error.py +0 -0
  307. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/technical_info/flac_md5/md5_repair/test_unset_md5.py +0 -0
  308. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/technical_info/test_bitrate.py +0 -0
  309. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/technical_info/test_channels.py +0 -0
  310. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/technical_info/test_duration_in_sec.py +0 -0
  311. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/technical_info/test_sample_rate.py +0 -0
  312. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/test_audio_file.py +0 -0
  313. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/test_audio_format_readable_after_update_all_metadata_formats.py +0 -0
  314. {audiometa_python-0.9.0/audiometa/test/tests/integration/writing/writing_strategies/unsupported_fields → audiometa_python-0.11.0/audiometa/test/tests/integration/writing}/__init__.py +0 -0
  315. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/writing/test_error_handling.py +0 -0
  316. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/writing/test_forced_format.py +0 -0
  317. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/writing/test_multiple_format_preservation.py +0 -0
  318. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/writing/test_partial_update.py +0 -0
  319. {audiometa_python-0.9.0/audiometa/test/tests/unit → audiometa_python-0.11.0/audiometa/test/tests/integration/writing/writing_strategies}/__init__.py +0 -0
  320. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/writing/writing_strategies/sync_strategy/__init__.py +0 -0
  321. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/writing/writing_strategies/sync_strategy/test_flac_sync.py +0 -0
  322. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/writing/writing_strategies/sync_strategy/test_mp3_sync.py +0 -0
  323. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/writing/writing_strategies/sync_strategy/test_wav_sync.py +0 -0
  324. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/writing/writing_strategies/test_cleanup_strategy.py +0 -0
  325. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/writing/writing_strategies/test_preserve_strategy.py +0 -0
  326. {audiometa_python-0.9.0/audiometa/test/tests/unit/audio_file → audiometa_python-0.11.0/audiometa/test/tests/integration/writing/writing_strategies/unsupported_fields}/__init__.py +0 -0
  327. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/writing/writing_strategies/unsupported_fields/test_fail_behavior.py +0 -0
  328. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/writing/writing_strategies/unsupported_fields/test_no_writing_on_failure.py +0 -0
  329. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/integration/writing/writing_strategies/unsupported_fields/test_strategy_specific.py +0 -0
  330. {audiometa_python-0.9.0/audiometa/test/tests/unit/audio_file/technical_info → audiometa_python-0.11.0/audiometa/test/tests/unit}/__init__.py +0 -0
  331. {audiometa_python-0.9.0/audiometa/test/tests/unit/cli → audiometa_python-0.11.0/audiometa/test/tests/unit/audio_file}/__init__.py +0 -0
  332. {audiometa_python-0.9.0/audiometa/test/tests/unit/metadata_managers → audiometa_python-0.11.0/audiometa/test/tests/unit/audio_file/technical_info}/__init__.py +0 -0
  333. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/unit/audio_file/technical_info/test_bitrate.py +0 -0
  334. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/unit/audio_file/technical_info/test_channels.py +0 -0
  335. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/unit/audio_file/technical_info/test_duration_in_sec.py +0 -0
  336. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/unit/audio_file/technical_info/test_error_handling.py +0 -0
  337. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/unit/audio_file/technical_info/test_file_size.py +0 -0
  338. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/unit/audio_file/technical_info/test_format_name.py +0 -0
  339. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/unit/audio_file/technical_info/test_sample_rate.py +0 -0
  340. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/unit/audio_file/test_context_manager.py +0 -0
  341. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/unit/audio_file/test_file_validation.py +0 -0
  342. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/unit/audio_file/test_is_audio_file.py +0 -0
  343. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/unit/audio_file/test_operations.py +0 -0
  344. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/unit/audio_file/test_path_handling.py +0 -0
  345. {audiometa_python-0.9.0/audiometa/test/tests/unit/metadata_managers/header_info → audiometa_python-0.11.0/audiometa/test/tests/unit/cli}/__init__.py +0 -0
  346. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/unit/cli/test_expand_file_patterns.py +0 -0
  347. {audiometa_python-0.9.0/audiometa/test/tests/unit/metadata_managers/metadata_field → audiometa_python-0.11.0/audiometa/test/tests/unit/metadata_managers}/__init__.py +0 -0
  348. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/unit/metadata_managers/conftest.py +0 -0
  349. {audiometa_python-0.9.0/audiometa/test/tests/unit/metadata_managers/metadata_field/multiple_values → audiometa_python-0.11.0/audiometa/test/tests/unit/metadata_managers/header_info}/__init__.py +0 -0
  350. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/unit/metadata_managers/header_info/test_id3v1.py +0 -0
  351. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/unit/metadata_managers/header_info/test_id3v2.py +0 -0
  352. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/unit/metadata_managers/header_info/test_riff.py +0 -0
  353. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/unit/metadata_managers/header_info/test_vorbis.py +0 -0
  354. {audiometa_python-0.9.0/audiometa/test/tests/unit/metadata_managers/metadata_field/multiple_values/reading → audiometa_python-0.11.0/audiometa/test/tests/unit/metadata_managers/metadata_field}/__init__.py +0 -0
  355. {audiometa_python-0.9.0/audiometa/test/tests/unit/metadata_managers/metadata_field/multiple_values/writing → audiometa_python-0.11.0/audiometa/test/tests/unit/metadata_managers/metadata_field/disc_number}/__init__.py +0 -0
  356. {audiometa_python-0.9.0/audiometa/test/tests/unit/metadata_managers/metadata_field → audiometa_python-0.11.0/audiometa/test/tests/unit/metadata_managers/metadata_field/disc_number}/test_disc_number_validation.py +0 -0
  357. {audiometa_python-0.9.0/audiometa/test/tests/unit/metadata_managers/metadata_field/rating → audiometa_python-0.11.0/audiometa/test/tests/unit/metadata_managers/metadata_field/isrc}/__init__.py +0 -0
  358. {audiometa_python-0.9.0/audiometa/test/tests/unit/metadata_managers/metadata_field → audiometa_python-0.11.0/audiometa/test/tests/unit/metadata_managers/metadata_field/isrc}/test_isrc_format_validation.py +0 -0
  359. {audiometa_python-0.9.0/audiometa/test/tests/unit/metadata_managers/metadata_field → audiometa_python-0.11.0/audiometa/test/tests/unit/metadata_managers/metadata_field/isrc}/test_isrc_type_validation.py +0 -0
  360. {audiometa_python-0.9.0/audiometa/test/tests/unit/metadata_managers/metadata_field/rating/reading → audiometa_python-0.11.0/audiometa/test/tests/unit/metadata_managers/metadata_field/multiple_values}/__init__.py +0 -0
  361. {audiometa_python-0.9.0/audiometa/test/tests/unit/metadata_managers/metadata_field/rating/writing → audiometa_python-0.11.0/audiometa/test/tests/unit/metadata_managers/metadata_field/multiple_values/reading}/__init__.py +0 -0
  362. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/unit/metadata_managers/metadata_field/multiple_values/reading/test_smart_parsing.py +0 -0
  363. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/unit/metadata_managers/metadata_field/multiple_values/writing/test_separator_selection.py +0 -0
  364. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/unit/metadata_managers/metadata_field/multiple_values/writing/test_value_filtering.py +0 -0
  365. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/unit/metadata_managers/metadata_field/rating/reading/test_normalization.py +0 -0
  366. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/unit/metadata_managers/metadata_field/rating/reading/test_profiles_values.py +0 -0
  367. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/unit/metadata_managers/metadata_field/rating/test_rating_validation.py +0 -0
  368. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/unit/metadata_managers/metadata_field/rating/writing/test_configuration_error.py +0 -0
  369. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/unit/metadata_managers/metadata_field/rating/writing/test_validation.py +0 -0
  370. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/unit/metadata_managers/metadata_field/rating/writing/test_writing_profiles.py +0 -0
  371. {audiometa_python-0.9.0/audiometa/test/tests/unit/metadata_managers/metadata_field → audiometa_python-0.11.0/audiometa/test/tests/unit/metadata_managers/metadata_field/release_date}/test_date_format_validation.py +0 -0
  372. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/unit/metadata_managers/metadata_field/test_type_validation_exception.py +0 -0
  373. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/unit/metadata_managers/metadata_field/test_validation.py +0 -0
  374. {audiometa_python-0.9.0/audiometa/test/tests/unit/metadata_managers/metadata_field → audiometa_python-0.11.0/audiometa/test/tests/unit/metadata_managers/metadata_field/track_number}/test_track_number_validation.py +0 -0
  375. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/unit/metadata_managers/test_metadata_format_managers_write_and_read.py +0 -0
  376. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/test/tests/unit/metadata_managers/test_riff_configuration_error.py +0 -0
  377. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/utils/__init__.py +0 -0
  378. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/utils/flac_md5_state.py +0 -0
  379. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/utils/id3v1_genre_code_map.py +0 -0
  380. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/utils/metadata_format.py +0 -0
  381. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/utils/metadata_writing_strategy.py +0 -0
  382. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/utils/mutagen_exception_handler.py +0 -0
  383. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/utils/os_dependencies_checker/__init__.py +0 -0
  384. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/utils/os_dependencies_checker/base.py +0 -0
  385. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/utils/os_dependencies_checker/config.py +0 -0
  386. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/utils/os_dependencies_checker/macos.py +0 -0
  387. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/utils/os_dependencies_checker/ubuntu.py +0 -0
  388. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/utils/os_dependencies_checker/windows.py +0 -0
  389. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/utils/rating_profiles.py +0 -0
  390. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/utils/tool_path_resolver.py +0 -0
  391. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa/utils/types.py +0 -0
  392. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa_python.egg-info/dependency_links.txt +0 -0
  393. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa_python.egg-info/entry_points.txt +0 -0
  394. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa_python.egg-info/requires.txt +0 -0
  395. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/audiometa_python.egg-info/top_level.txt +0 -0
  396. {audiometa_python-0.9.0 → audiometa_python-0.11.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: audiometa-python
3
- Version: 0.9.0
3
+ Version: 0.11.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>
@@ -468,6 +468,16 @@ def _validate_metadata_field_formats(unified_metadata: UnifiedMetadata) -> None:
468
468
  if isrc_value is not None and isinstance(isrc_value, str) and isrc_value:
469
469
  _MetadataManager.validate_isrc(isrc_value)
470
470
 
471
+ # Validate MusicBrainz Track ID format if present and non-empty
472
+ if UnifiedMetadataKey.MUSICBRAINZ_TRACKID in unified_metadata:
473
+ musicbrainz_trackid_value = unified_metadata[UnifiedMetadataKey.MUSICBRAINZ_TRACKID]
474
+ if (
475
+ musicbrainz_trackid_value is not None
476
+ and isinstance(musicbrainz_trackid_value, str)
477
+ and musicbrainz_trackid_value
478
+ ):
479
+ _MetadataManager.validate_musicbrainz_trackid(musicbrainz_trackid_value)
480
+
471
481
 
472
482
  def validate_metadata_for_update(
473
483
  unified_metadata: dict[UnifiedMetadataKey, Any] | UnifiedMetadata,
@@ -94,6 +94,9 @@ def format_as_table(data: dict[str, Any]) -> str:
94
94
  "replaygain",
95
95
  "archival_location",
96
96
  "isrc",
97
+ "musicbrainz_trackid",
98
+ "description",
99
+ "originator",
97
100
  }
98
101
  if unified_keys.intersection(set(data.keys())):
99
102
  # This is a unified metadata dict, wrap it
@@ -188,12 +191,16 @@ def _write_metadata(args: argparse.Namespace) -> None:
188
191
  metadata[UnifiedMetadataKey.COMMENT] = args.comment
189
192
  if args.description and args.description.strip():
190
193
  metadata[UnifiedMetadataKey.DESCRIPTION] = args.description
194
+ if args.originator and args.originator.strip():
195
+ metadata[UnifiedMetadataKey.ORIGINATOR] = args.originator
191
196
  if args.replaygain and args.replaygain.strip():
192
197
  metadata[UnifiedMetadataKey.REPLAYGAIN] = args.replaygain
193
198
  if args.archival_location and args.archival_location.strip():
194
199
  metadata[UnifiedMetadataKey.ARCHIVAL_LOCATION] = args.archival_location
195
200
  if args.isrc and args.isrc.strip():
196
201
  metadata[UnifiedMetadataKey.ISRC] = args.isrc
202
+ if args.musicbrainz_track_id and args.musicbrainz_track_id.strip():
203
+ metadata[UnifiedMetadataKey.MUSICBRAINZ_TRACKID] = args.musicbrainz_track_id
197
204
 
198
205
  # List fields (can be specified multiple times)
199
206
  if args.artist:
@@ -432,9 +439,15 @@ Examples:
432
439
  write_parser.add_argument("--lyrics", help="Unsynchronized lyrics text")
433
440
  write_parser.add_argument("--comment", help="Comment")
434
441
  write_parser.add_argument("--description", help="Description")
442
+ write_parser.add_argument("--originator", help="Originator")
435
443
  write_parser.add_argument("--replaygain", help="ReplayGain information")
436
444
  write_parser.add_argument("--archival-location", help="Archival location")
437
445
  write_parser.add_argument("--isrc", help="International Standard Recording Code (12 characters)")
446
+ write_parser.add_argument(
447
+ "--musicbrainz-track-id",
448
+ dest="musicbrainz_track_id",
449
+ help="MusicBrainz Track ID (Recording ID) - UUID format (e.g., '9d6f6f7c-9d52-4c76-8f9e-01d18d8f8ec6')",
450
+ )
438
451
  write_parser.add_argument(
439
452
  "--force-format",
440
453
  choices=["id3v2", "id3v1", "vorbis", "riff"],
@@ -255,6 +255,48 @@ class _MetadataManager:
255
255
  isrc,
256
256
  )
257
257
 
258
+ @staticmethod
259
+ def validate_musicbrainz_trackid(track_id: str) -> None:
260
+ """Validate MusicBrainz Track ID (UUID) format.
261
+
262
+ MusicBrainz Track ID must be a valid UUID string in one of the following formats:
263
+ - 36-character hyphenated UUID (preferred): "9d6f6f7c-9d52-4c76-8f9e-01d18d8f8ec6"
264
+ - 32-character hex string without hyphens: "9d6f6f7c9d524c768f9e01d18d8f8ec6"
265
+ - Empty string is allowed (represents no Track ID)
266
+
267
+ Args:
268
+ track_id: The MusicBrainz Track ID string to validate
269
+
270
+ Raises:
271
+ InvalidMetadataFieldFormatError: If the Track ID format is invalid
272
+
273
+ Examples:
274
+ >>> _MetadataManager.validate_musicbrainz_trackid("9d6f6f7c-9d52-4c76-8f9e-01d18d8f8ec6")
275
+ # Valid (36 chars)
276
+ >>> _MetadataManager.validate_musicbrainz_trackid("9d6f6f7c9d524c768f9e01d18d8f8ec6")
277
+ # Valid (32 chars)
278
+ >>> _MetadataManager.validate_musicbrainz_trackid("") # Valid (empty string)
279
+ >>> _MetadataManager.validate_musicbrainz_trackid("not-a-uuid")
280
+ # Raises InvalidMetadataFieldFormatError
281
+ """
282
+ if not track_id:
283
+ return
284
+
285
+ # 36-character hyphenated UUID format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
286
+ if re.match(r"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$", track_id):
287
+ return
288
+
289
+ # 32-character hex string without hyphens
290
+ if re.match(r"^[0-9a-fA-F]{32}$", track_id):
291
+ return
292
+
293
+ raise InvalidMetadataFieldFormatError(
294
+ UnifiedMetadataKey.MUSICBRAINZ_TRACKID.value,
295
+ "36-character hyphenated UUID (e.g., '9d6f6f7c-9d52-4c76-8f9e-01d18d8f8ec6') or "
296
+ "32-character hex string (e.g., '9d6f6f7c9d524c768f9e01d18d8f8ec6')",
297
+ track_id,
298
+ )
299
+
258
300
  @abstractmethod
259
301
  def _extract_mutagen_metadata(self) -> MutagenMetadata:
260
302
  raise NotImplementedError
@@ -31,6 +31,7 @@ from mutagen.id3._frames import (
31
31
  TSRC,
32
32
  TXXX,
33
33
  TYER,
34
+ UFID,
34
35
  USLT,
35
36
  WOAR,
36
37
  )
@@ -284,6 +285,7 @@ class _Id3v2Manager(_RatingSupportingMetadataManager):
284
285
  UnifiedMetadataKey.COMMENT: self.Id3TextFrame.COMMENT,
285
286
  UnifiedMetadataKey.REPLAYGAIN: None,
286
287
  UnifiedMetadataKey.ISRC: self.Id3TextFrame.ISRC,
288
+ UnifiedMetadataKey.MUSICBRAINZ_TRACKID: None,
287
289
  }
288
290
  metadata_keys_direct_map_write: dict[UnifiedMetadataKey, RawMetadataKey | None] = {
289
291
  UnifiedMetadataKey.TITLE: self.Id3TextFrame.TITLE,
@@ -305,6 +307,7 @@ class _Id3v2Manager(_RatingSupportingMetadataManager):
305
307
  UnifiedMetadataKey.COMMENT: self.Id3TextFrame.COMMENT,
306
308
  UnifiedMetadataKey.REPLAYGAIN: None,
307
309
  UnifiedMetadataKey.ISRC: self.Id3TextFrame.ISRC,
310
+ UnifiedMetadataKey.MUSICBRAINZ_TRACKID: None,
308
311
  }
309
312
 
310
313
  super().__init__(
@@ -396,6 +399,56 @@ class _Id3v2Manager(_RatingSupportingMetadataManager):
396
399
  result[self.Id3TextFrame.REPLAYGAIN] = txxx_frame.text
397
400
  break
398
401
 
402
+ # Handle UFID frames for MusicBrainz Track ID (preferred)
403
+ musicbrainz_trackid = None
404
+ for raw_mutagen_frame in raw_mutagen_metadata.items():
405
+ if raw_mutagen_frame[0].startswith("UFID"):
406
+ ufid_frame = raw_mutagen_frame[1]
407
+ if (
408
+ hasattr(ufid_frame, "owner")
409
+ and ufid_frame.owner == "http://musicbrainz.org"
410
+ and hasattr(ufid_frame, "data")
411
+ ):
412
+ # UFID data is bytes, decode to string
413
+ try:
414
+ musicbrainz_trackid = ufid_frame.data.decode("utf-8", errors="replace").strip("\x00")
415
+ # Normalize to hyphenated UUID format if it's 32 hex chars
416
+ uuid_hex_length = 32
417
+ if len(musicbrainz_trackid) == uuid_hex_length and all(
418
+ c in "0123456789abcdefABCDEF" for c in musicbrainz_trackid
419
+ ):
420
+ musicbrainz_trackid = (
421
+ f"{musicbrainz_trackid[:8]}-{musicbrainz_trackid[8:12]}-"
422
+ f"{musicbrainz_trackid[12:16]}-{musicbrainz_trackid[16:20]}-{musicbrainz_trackid[20:32]}"
423
+ )
424
+ break
425
+ except (UnicodeDecodeError, AttributeError):
426
+ pass
427
+
428
+ # Handle TXXX frames for MusicBrainz Track ID (fallback)
429
+ if not musicbrainz_trackid:
430
+ for raw_mutagen_frame in raw_mutagen_metadata.items():
431
+ if raw_mutagen_frame[0].startswith("TXXX"):
432
+ txxx_frame = raw_mutagen_frame[1]
433
+ if hasattr(txxx_frame, "desc") and txxx_frame.desc == "MusicBrainz Track Id" and txxx_frame.text:
434
+ musicbrainz_trackid = (
435
+ txxx_frame.text[0] if isinstance(txxx_frame.text, list) else str(txxx_frame.text)
436
+ )
437
+ # Normalize to hyphenated UUID format if it's 32 hex chars
438
+ uuid_hex_length = 32
439
+ if len(musicbrainz_trackid) == uuid_hex_length and all(
440
+ c in "0123456789abcdefABCDEF" for c in musicbrainz_trackid
441
+ ):
442
+ musicbrainz_trackid = (
443
+ f"{musicbrainz_trackid[:8]}-{musicbrainz_trackid[8:12]}-"
444
+ f"{musicbrainz_trackid[12:16]}-{musicbrainz_trackid[16:20]}-{musicbrainz_trackid[20:32]}"
445
+ )
446
+ break
447
+
448
+ if musicbrainz_trackid:
449
+ # Use a special key for MusicBrainz Track ID (not a text frame, so use string key)
450
+ result[cast(RawMetadataKey, "MUSICBRAINZ_TRACKID")] = [musicbrainz_trackid]
451
+
399
452
  # Special handling for release date: if TDRC is not present, try to construct from TYER + TDAT
400
453
  # Only do this for ID3v2 files (not ID3v1) and only when both TYER and TDAT are present
401
454
  if self.Id3TextFrame.RECORDING_TIME not in result:
@@ -443,6 +496,23 @@ class _Id3v2Manager(_RatingSupportingMetadataManager):
443
496
  if app_metadata_value is not None:
444
497
  # Add new TXXX frame with desc 'REPLAYGAIN'
445
498
  raw_mutagen_metadata.add(TXXX(encoding=3, desc="REPLAYGAIN", text=str(app_metadata_value)))
499
+ elif unified_metadata_key == UnifiedMetadataKey.MUSICBRAINZ_TRACKID:
500
+ # Remove existing UFID frames with MusicBrainz owner
501
+ raw_mutagen_metadata.delall("UFID:http://musicbrainz.org")
502
+ # Remove existing TXXX frames with MusicBrainz Track Id description
503
+ raw_mutagen_metadata.delall("TXXX:MusicBrainz Track Id")
504
+
505
+ if app_metadata_value is not None and app_metadata_value != "":
506
+ # Normalize UUID: convert 32-char hex to 36-char hyphenated format if needed
507
+ track_id = str(app_metadata_value).strip()
508
+ uuid_hex_length = 32
509
+ if len(track_id) == uuid_hex_length and all(c in "0123456789abcdefABCDEF" for c in track_id):
510
+ track_id = f"{track_id[:8]}-{track_id[8:12]}-{track_id[12:16]}-{track_id[16:20]}-{track_id[20:32]}"
511
+
512
+ # Write as UFID frame with owner "http://musicbrainz.org"
513
+ # UFID data should be the UUID as bytes (without null terminator)
514
+ track_id_bytes = track_id.encode("utf-8")
515
+ raw_mutagen_metadata.add(UFID(owner="http://musicbrainz.org", data=track_id_bytes))
446
516
  elif unified_metadata_key in (UnifiedMetadataKey.DISC_NUMBER, UnifiedMetadataKey.DISC_TOTAL):
447
517
  tpos_key = self.Id3TextFrame.DISC_NUMBER
448
518
  tpos_frame_class = TPOS
@@ -512,6 +582,17 @@ class _Id3v2Manager(_RatingSupportingMetadataManager):
512
582
  return None
513
583
  first_value = replaygain_value[0]
514
584
  return cast(UnifiedMetadataValue, first_value)
585
+ if unified_metadata_key == UnifiedMetadataKey.MUSICBRAINZ_TRACKID:
586
+ musicbrainz_trackid_key = cast(RawMetadataKey, "MUSICBRAINZ_TRACKID")
587
+ if musicbrainz_trackid_key not in raw_clean_metadata:
588
+ return None
589
+ trackid_value = raw_clean_metadata[musicbrainz_trackid_key]
590
+ if trackid_value is None:
591
+ return None
592
+ if len(trackid_value) == 0:
593
+ return None
594
+ first_value = trackid_value[0]
595
+ return cast(UnifiedMetadataValue, first_value)
515
596
  if unified_metadata_key == UnifiedMetadataKey.DISC_NUMBER:
516
597
  tpos_key = self.Id3TextFrame.DISC_NUMBER
517
598
  if tpos_key not in raw_clean_metadata:
@@ -15,7 +15,13 @@ from ....utils.rating_profiles import RatingWriteProfile
15
15
  from ....utils.types import RawMetadataDict, RawMetadataKey, UnifiedMetadata, UnifiedMetadataValue
16
16
  from ....utils.unified_metadata_key import UnifiedMetadataKey
17
17
  from .._RatingSupportingMetadataManager import _RatingSupportingMetadataManager
18
- from ._riff_bext_chunk import extract_bext_chunk, find_bext_chunk, find_fmt_chunk, update_bext_description_in_riff_data
18
+ from ._riff_bext_chunk import (
19
+ extract_bext_chunk,
20
+ find_bext_chunk,
21
+ find_fmt_chunk,
22
+ update_bext_description_in_riff_data,
23
+ update_bext_originator_in_riff_data,
24
+ )
19
25
  from ._riff_constants import (
20
26
  RIFF_AUDIO_FORMAT_IEEE_FLOAT,
21
27
  RIFF_FORMAT_CHUNK_MIN_SIZE,
@@ -115,6 +121,7 @@ class _RiffManager(_RatingSupportingMetadataManager):
115
121
 
116
122
  # BWF
117
123
  ISRC = "ISRC" # International Standard Recording Code
124
+ MBID = "MBID" # MusicBrainz Track ID (Recording ID)
118
125
 
119
126
  def __init__(self, audio_file: "_AudioFile", normalized_rating_max_value: None | int = None):
120
127
  # Validate that the file is a WAV file
@@ -138,7 +145,9 @@ class _RiffManager(_RatingSupportingMetadataManager):
138
145
  UnifiedMetadataKey.UNSYNCHRONIZED_LYRICS: self.RiffTagKey.UNSYNCHRONIZED_LYRICS,
139
146
  UnifiedMetadataKey.TRACK_NUMBER: self.RiffTagKey.TRACK_NUMBER,
140
147
  UnifiedMetadataKey.ISRC: self.RiffTagKey.ISRC,
148
+ UnifiedMetadataKey.MUSICBRAINZ_TRACKID: self.RiffTagKey.MBID,
141
149
  UnifiedMetadataKey.DESCRIPTION: None,
150
+ UnifiedMetadataKey.ORIGINATOR: None,
142
151
  }
143
152
  metadata_keys_direct_map_write: dict[UnifiedMetadataKey, RawMetadataKey | None] = {
144
153
  UnifiedMetadataKey.TITLE: self.RiffTagKey.TITLE,
@@ -156,7 +165,9 @@ class _RiffManager(_RatingSupportingMetadataManager):
156
165
  UnifiedMetadataKey.UNSYNCHRONIZED_LYRICS: self.RiffTagKey.UNSYNCHRONIZED_LYRICS,
157
166
  UnifiedMetadataKey.TRACK_NUMBER: self.RiffTagKey.TRACK_NUMBER,
158
167
  UnifiedMetadataKey.ISRC: self.RiffTagKey.ISRC,
168
+ UnifiedMetadataKey.MUSICBRAINZ_TRACKID: self.RiffTagKey.MBID,
159
169
  UnifiedMetadataKey.DESCRIPTION: None,
170
+ UnifiedMetadataKey.ORIGINATOR: None,
160
171
  }
161
172
  super().__init__(
162
173
  audio_file=audio_file,
@@ -280,6 +291,17 @@ class _RiffManager(_RatingSupportingMetadataManager):
280
291
  except Exception:
281
292
  pass
282
293
  return None
294
+ if unified_metadata_key == UnifiedMetadataKey.ORIGINATOR:
295
+ # Read from bext chunk
296
+ try:
297
+ self.audio_file.seek(0)
298
+ file_data = self.audio_file.read()
299
+ bext_data = self._extract_bext_chunk(file_data)
300
+ if bext_data and "Originator" in bext_data:
301
+ return cast(str, bext_data["Originator"])
302
+ except Exception:
303
+ pass
304
+ return None
283
305
  msg = f"Metadata key not handled: {unified_metadata_key}"
284
306
  raise MetadataFieldNotSupportedByMetadataFormatError(msg)
285
307
 
@@ -459,6 +481,9 @@ class _RiffManager(_RatingSupportingMetadataManager):
459
481
  if UnifiedMetadataKey.DESCRIPTION in merged_metadata:
460
482
  description_value = merged_metadata[UnifiedMetadataKey.DESCRIPTION]
461
483
  update_bext_description_in_riff_data(riff_data, cast(str | None, description_value))
484
+ if UnifiedMetadataKey.ORIGINATOR in merged_metadata:
485
+ originator_value = merged_metadata[UnifiedMetadataKey.ORIGINATOR]
486
+ update_bext_originator_in_riff_data(riff_data, cast(str | None, originator_value))
462
487
 
463
488
  def _update_riff_chunk_size(self, riff_data: bytearray) -> None:
464
489
  """Update RIFF chunk size in RIFF data.
@@ -210,25 +210,33 @@ def extract_bext_chunk(file_data: bytes, skip_id3v2_tags_func: Callable[[bytes],
210
210
  return None
211
211
 
212
212
 
213
- def update_bext_description_in_riff_data(riff_data: bytearray, value: str | None) -> None:
214
- """Update the Description field in the bext chunk within RIFF data.
213
+ def _update_bext_field_in_riff_data(
214
+ riff_data: bytearray, value: str | None, field_size: int, field_offset: int
215
+ ) -> None:
216
+ """Update a field in the bext chunk within RIFF data.
215
217
 
216
218
  This function modifies the riff_data bytearray in-place to update or create
217
- the bext chunk with the Description field. This is integrated into the main
219
+ the bext chunk with the specified field. This is integrated into the main
218
220
  update flow to avoid multiple file writes.
219
221
 
220
222
  Args:
221
223
  riff_data: RIFF data bytearray (modified in-place)
222
- value: Description value to write, or None/empty string to clear
224
+ value: Field value to write, or None/empty string to clear
225
+ field_size: Size of the field in bytes
226
+ field_offset: Offset of the field within the bext chunk
223
227
  """
224
- encoded = b"\x00" * 256 if value is None or value == "" else value.encode("utf-8")[:255].ljust(256, b"\x00")
228
+ encoded = (
229
+ b"\x00" * field_size
230
+ if value is None or value == ""
231
+ else value.encode("utf-8")[: field_size - 1].ljust(field_size, b"\x00")
232
+ )
225
233
 
226
234
  # Find bext chunk in RIFF data (no ID3v2 tags in riff_data at this point)
227
235
  pos = find_bext_chunk_in_riff_data(riff_data)
228
236
  if pos != -1:
229
- # Update existing bext chunk Description field
237
+ # Update existing bext chunk field
230
238
  bext_start = pos + 8
231
- riff_data[bext_start : bext_start + 256] = encoded
239
+ riff_data[bext_start + field_offset : bext_start + field_offset + field_size] = encoded
232
240
  else:
233
241
  # No bext chunk, create one
234
242
  fmt_pos = find_fmt_chunk_in_riff_data(riff_data)
@@ -237,12 +245,24 @@ def update_bext_description_in_riff_data(riff_data: bytearray, value: str | None
237
245
  fmt_size = int.from_bytes(bytes(riff_data[fmt_pos + 4 : fmt_pos + 8]), "little")
238
246
  insert_pos = fmt_pos + 8 + fmt_size
239
247
  # Create bext chunk data: 602 bytes v1
240
- bext_data = b"bext" + (602).to_bytes(4, "little") + encoded + b"\x00" * 346
248
+ # Fill with zeros, then set the specific field
249
+ bext_data = bytearray(b"bext" + (602).to_bytes(4, "little") + b"\x00" * 602)
250
+ bext_data[8 + field_offset : 8 + field_offset + field_size] = encoded
241
251
  # Insert bext chunk after fmt chunk
242
252
  riff_data[insert_pos:insert_pos] = bext_data
243
253
  # Note: RIFF chunk size will be updated by caller after this method
244
254
 
245
255
 
256
+ def update_bext_description_in_riff_data(riff_data: bytearray, value: str | None) -> None:
257
+ """Update the Description field in the bext chunk within RIFF data."""
258
+ _update_bext_field_in_riff_data(riff_data, value, 256, 0)
259
+
260
+
261
+ def update_bext_originator_in_riff_data(riff_data: bytearray, value: str | None) -> None:
262
+ """Update the Originator field in the bext chunk within RIFF data."""
263
+ _update_bext_field_in_riff_data(riff_data, value, 32, 256)
264
+
265
+
246
266
  def find_bext_chunk_in_riff_data(riff_data: bytearray) -> int:
247
267
  """Find the position of the bext chunk in RIFF data.
248
268
 
@@ -59,6 +59,7 @@ class _VorbisManager(_RatingSupportingMetadataManager):
59
59
  LOCATION = "LOCATION" # Recording location
60
60
  CONTACT = "CONTACT" # Contact information
61
61
  ISRC = "ISRC" # International Standard Recording Code
62
+ MUSICBRAINZ_TRACKID = "MUSICBRAINZ_TRACKID" # MusicBrainz Track ID (Recording ID)
62
63
 
63
64
  # Non-standard
64
65
  LANGUAGE = "LANGUAGE"
@@ -92,6 +93,7 @@ class _VorbisManager(_RatingSupportingMetadataManager):
92
93
  UnifiedMetadataKey.REPLAYGAIN: self.VorbisKey.REPLAYGAIN,
93
94
  UnifiedMetadataKey.PUBLISHER: self.VorbisKey.PUBLISHER,
94
95
  UnifiedMetadataKey.ISRC: self.VorbisKey.ISRC,
96
+ UnifiedMetadataKey.MUSICBRAINZ_TRACKID: self.VorbisKey.MUSICBRAINZ_TRACKID,
95
97
  UnifiedMetadataKey.DESCRIPTION: self.VorbisKey.DESCRIPTION,
96
98
  }
97
99
  metadata_keys_direct_map_write = {
@@ -114,6 +116,7 @@ class _VorbisManager(_RatingSupportingMetadataManager):
114
116
  UnifiedMetadataKey.REPLAYGAIN: self.VorbisKey.REPLAYGAIN,
115
117
  UnifiedMetadataKey.PUBLISHER: self.VorbisKey.PUBLISHER,
116
118
  UnifiedMetadataKey.ISRC: self.VorbisKey.ISRC,
119
+ UnifiedMetadataKey.MUSICBRAINZ_TRACKID: self.VorbisKey.MUSICBRAINZ_TRACKID,
117
120
  UnifiedMetadataKey.DESCRIPTION: self.VorbisKey.DESCRIPTION,
118
121
  }
119
122
  super().__init__(
@@ -398,6 +401,7 @@ class _VorbisManager(_RatingSupportingMetadataManager):
398
401
  "ENCODER": "ENCODER",
399
402
  "URL": "URL",
400
403
  "ISRC": "ISRC",
404
+ "MUSICBRAINZ_TRACKID": "MUSICBRAINZ_TRACKID",
401
405
  "PUBLISHER": "PUBLISHER",
402
406
  }
403
407
 
@@ -85,6 +85,60 @@ class ManualID3v2FrameCreator:
85
85
 
86
86
  ManualID3v2FrameCreator._write_id3v2_tag(file_path, frames, version)
87
87
 
88
+ @staticmethod
89
+ def _create_ufid_frame(owner: str, data: bytes, version: str = "2.4") -> bytes:
90
+ """Create a UFID (Unique File Identifier) frame.
91
+
92
+ Args:
93
+ owner: UFID owner identifier (null-terminated string)
94
+ data: UFID data (binary data, no null terminator)
95
+ version: ID3v2 version ("2.3" or "2.4")
96
+
97
+ Returns:
98
+ Complete UFID frame as bytes (header + frame data)
99
+ """
100
+ # UFID frame format:
101
+ # - Owner: null-terminated string (ISO-8859-1)
102
+ # - Data: binary data (no null terminator)
103
+ owner_bytes = owner.encode("latin1", errors="ignore") + b"\x00"
104
+ frame_data = owner_bytes + data
105
+
106
+ # Frame header: ID (4 bytes) + size (4 bytes) + flags (2 bytes)
107
+ frame_id_bytes = b"UFID"
108
+ frame_size = len(frame_data)
109
+ frame_flags = 0x0000 # No flags
110
+
111
+ if version == "2.3":
112
+ frame_header = (
113
+ frame_id_bytes
114
+ + struct.pack(">I", frame_size) # Big-endian 32-bit size
115
+ + struct.pack(">H", frame_flags) # Big-endian 16-bit flags
116
+ )
117
+ else: # ID3v2.4
118
+ frame_header = (
119
+ frame_id_bytes
120
+ + ManualID3v2FrameCreator._synchsafe_int(frame_size) # Synchsafe size
121
+ + struct.pack(">H", frame_flags) # Big-endian 16-bit flags
122
+ )
123
+
124
+ return frame_header + frame_data
125
+
126
+ @staticmethod
127
+ def create_ufid_frame(file_path: Path, owner: str, data: bytes, version: str = "2.4") -> None:
128
+ """Create a UFID frame in an ID3v2 tag.
129
+
130
+ Args:
131
+ file_path: Path to the audio file
132
+ owner: UFID owner identifier (e.g., "http://musicbrainz.org")
133
+ data: UFID data (binary data)
134
+ version: ID3v2 version ("2.3" or "2.4")
135
+ """
136
+ if version not in ["2.3", "2.4"]:
137
+ msg = "Version must be '2.3' or '2.4'"
138
+ raise ValueError(msg)
139
+ frame = ManualID3v2FrameCreator._create_ufid_frame(owner, data, version)
140
+ ManualID3v2FrameCreator._write_id3v2_tag(file_path, [frame], version)
141
+
88
142
  @staticmethod
89
143
  def _create_text_frame(frame_id: str, text: str, version: str, encoding: int | None = None) -> bytes:
90
144
  """Create a single ID3v2 text frame with the given ID and text."""
@@ -118,6 +118,29 @@ class ID3v2MetadataSetter:
118
118
  cmd.extend(["--TDRC", release_date])
119
119
  metadata_added = True
120
120
 
121
+ # Handle musicbrainz_trackid (UFID frame) - needs special handling
122
+ musicbrainz_trackid = None
123
+ for key, value in metadata.items():
124
+ if key.lower() == "musicbrainz_trackid" and not isinstance(value, list) and value:
125
+ musicbrainz_trackid = str(value).strip()
126
+ break
127
+
128
+ if musicbrainz_trackid:
129
+ # Normalize UUID: convert 32-char hex to 36-char hyphenated format if needed
130
+ if len(musicbrainz_trackid) == 32 and all(c in "0123456789abcdefABCDEF" for c in musicbrainz_trackid):
131
+ musicbrainz_trackid = (
132
+ f"{musicbrainz_trackid[:8]}-{musicbrainz_trackid[8:12]}-"
133
+ f"{musicbrainz_trackid[12:16]}-{musicbrainz_trackid[16:20]}-{musicbrainz_trackid[20:32]}"
134
+ )
135
+ # Use manual frame creator for UFID frames to maintain test isolation
136
+ # (mid3v2 doesn't handle URLs with colons in UFID owner field correctly)
137
+ from .id3v2_frame_manual_creator import ManualID3v2FrameCreator
138
+
139
+ track_id_bytes = musicbrainz_trackid.encode("utf-8")
140
+ ManualID3v2FrameCreator.create_ufid_frame(
141
+ file_path, "http://musicbrainz.org", track_id_bytes, version=version
142
+ )
143
+
121
144
  # Handle non-list values (excluding already handled fields and list fields)
122
145
  # Only exclude list fields if they're actually lists (single strings should be processed)
123
146
  list_field_keys = set()
@@ -129,7 +152,7 @@ class ID3v2MetadataSetter:
129
152
  if (
130
153
  key.lower() in key_mapping
131
154
  and not isinstance(value, list)
132
- and key.lower() not in ["disc_number", "disc_total", "release_date", "year"]
155
+ and key.lower() not in ["disc_number", "disc_total", "release_date", "year", "musicbrainz_trackid"]
133
156
  and key.lower() not in list_field_keys # Only exclude if it's actually a list
134
157
  ):
135
158
  # Special handling for rating (POPM frame requires app name prefix)
@@ -506,3 +529,52 @@ class ID3v2MetadataSetter:
506
529
  creator = ManualID3v2FrameCreator()
507
530
  frame_data = creator._create_text_frame("TPE1", text, "2.4", encoding=encoding)
508
531
  creator._write_id3v2_tag(file_path, [frame_data], "2.4")
532
+
533
+ @staticmethod
534
+ def set_musicbrainz_trackid_ufid(file_path: Path, track_id: str) -> None:
535
+ """Set MusicBrainz Track ID using UFID frame (preferred format).
536
+
537
+ Uses manual binary construction to maintain test isolation (avoids using mutagen
538
+ which is also used in the implementation).
539
+
540
+ Args:
541
+ file_path: Path to the MP3 file
542
+ track_id: MusicBrainz Track ID (UUID string, hyphenated or 32-char hex)
543
+ """
544
+ from .id3v2_frame_manual_creator import ManualID3v2FrameCreator
545
+
546
+ track_id_bytes = track_id.encode("utf-8")
547
+ ManualID3v2FrameCreator.create_ufid_frame(file_path, "http://musicbrainz.org", track_id_bytes, version="2.4")
548
+
549
+ @staticmethod
550
+ def set_musicbrainz_trackid_txxx(file_path: Path, track_id: str) -> None:
551
+ """Set MusicBrainz Track ID using TXXX frame (fallback format).
552
+
553
+ Args:
554
+ file_path: Path to the MP3 file
555
+ track_id: MusicBrainz Track ID (UUID string, hyphenated or 32-char hex)
556
+ """
557
+ command = [
558
+ get_tool_path("mid3v2"),
559
+ "--TXXX",
560
+ f"MusicBrainz Track Id:{track_id}",
561
+ str(file_path),
562
+ ]
563
+ run_external_tool(command, "mid3v2")
564
+
565
+ @staticmethod
566
+ def set_ufid_with_owner(file_path: Path, owner: str, data: str) -> None:
567
+ """Set UFID frame with a specific owner (for testing purposes).
568
+
569
+ Uses manual binary construction to maintain test isolation (avoids using mutagen
570
+ which is also used in the implementation).
571
+
572
+ Args:
573
+ file_path: Path to the MP3 file
574
+ owner: UFID owner identifier (e.g., "http://musicbrainz.org", "http://example.com")
575
+ data: UFID data (typically a UUID or identifier string)
576
+ """
577
+ from .id3v2_frame_manual_creator import ManualID3v2FrameCreator
578
+
579
+ data_bytes = data.encode("utf-8")
580
+ ManualID3v2FrameCreator.create_ufid_frame(file_path, owner, data_bytes, version="2.4")
@@ -143,6 +143,18 @@ class ManualRIFFMetadataCreator:
143
143
  all_fields = [*existing_fields, rating_field]
144
144
  ManualRIFFMetadataCreator._write_riff_info_chunk(file_path, all_fields)
145
145
 
146
+ @staticmethod
147
+ def create_mbid_field(file_path: Path, mbid: str) -> None:
148
+ """Create MBID field in the RIFF INFO chunk, preserving existing fields."""
149
+ # Read existing fields and add MBID
150
+ existing_fields = ManualRIFFMetadataCreator._read_existing_info_fields(file_path)
151
+ # Remove existing MBID if present (we'll replace it)
152
+ existing_fields = [f for f in existing_fields if f[:4] != b"MBID"]
153
+ # Add new MBID field
154
+ mbid_field = ManualRIFFMetadataCreator._create_info_field("MBID", mbid)
155
+ all_fields = [*existing_fields, mbid_field]
156
+ ManualRIFFMetadataCreator._write_riff_info_chunk(file_path, all_fields)
157
+
146
158
  @staticmethod
147
159
  def _create_info_field(field_id: str, text: str) -> bytes:
148
160
  """Create a single RIFF INFO field with the given FourCC and text."""
@@ -34,7 +34,9 @@ class RIFFMetadataSetter:
34
34
  "rating": "--IRTD",
35
35
  "copyright": "--ICOP",
36
36
  "isrc": "--ISRC",
37
+ "musicbrainz_trackid": "--MBID",
37
38
  "description": "--Description",
39
+ "originator": "--Originator",
38
40
  }
39
41
 
40
42
  # Handle list values - include first value in main command to avoid overwriting
@@ -50,8 +52,16 @@ class RIFFMetadataSetter:
50
52
  elif key.lower() == "composer":
51
53
  composer_value = value[0] # Store first composer for main command
52
54
 
55
+ # Handle musicbrainz_trackid (MBID FourCC) - needs special handling with manual creator
56
+ musicbrainz_trackid = None
57
+ for key, value in metadata.items():
58
+ if key.lower() == "musicbrainz_trackid" and not isinstance(value, list) and value:
59
+ musicbrainz_trackid = str(value).strip()
60
+ break
61
+
53
62
  # Handle non-list values and include list values in main command
54
- # Note: Rating, BPM, Language, and Composer need to be set AFTER bwfmetaedit to avoid being overwritten
63
+ # Note: Rating, BPM, Language, Composer, and MusicBrainz Track ID need to be set
64
+ # AFTER bwfmetaedit to avoid being overwritten
55
65
  rating_value = None
56
66
  bpm_value = None
57
67
  language_value = None
@@ -67,6 +77,9 @@ class RIFFMetadataSetter:
67
77
  language_value = str(value) # Store for later
68
78
  elif key.lower() == "composer":
69
79
  composer_single_value = str(value) # Store for later
80
+ elif key.lower() == "musicbrainz_trackid":
81
+ # Handled separately after bwfmetaedit
82
+ pass
70
83
  else:
71
84
  cmd.extend([f"{key_mapping[key.lower()]}={value}"])
72
85
  metadata_added = True
@@ -112,6 +125,12 @@ class RIFFMetadataSetter:
112
125
 
113
126
  ManualRIFFMetadataCreator.create_composer_field(file_path, composer_single_value)
114
127
 
128
+ # Set MusicBrainz Track ID (MBID FourCC) AFTER bwfmetaedit
129
+ if musicbrainz_trackid is not None:
130
+ from .riff_manual_metadata_creator import ManualRIFFMetadataCreator
131
+
132
+ ManualRIFFMetadataCreator.create_mbid_field(file_path, musicbrainz_trackid)
133
+
115
134
  @staticmethod
116
135
  def set_comment(file_path: Path, comment: str) -> None:
117
136
  command = ["bwfmetaedit", f"--ICMT={comment}", str(file_path)]
@@ -59,6 +59,7 @@ class VorbisMetadataSetter:
59
59
  "encoder": "ENCODER",
60
60
  "url": "URL",
61
61
  "isrc": "ISRC",
62
+ "musicbrainz_trackid": "MUSICBRAINZ_TRACKID",
62
63
  "publisher": "PUBLISHER",
63
64
  "disc_number": "DISCNUMBER",
64
65
  "disc_total": "DISCTOTAL",