audiometa-python 0.6.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (352) hide show
  1. audiometa/__init__.py +1297 -0
  2. audiometa/__main__.py +6 -0
  3. audiometa/_audio_file.py +607 -0
  4. audiometa/cli.py +476 -0
  5. audiometa/exceptions.py +167 -0
  6. audiometa/manager/_MetadataManager.py +768 -0
  7. audiometa/manager/__init__.py +1 -0
  8. audiometa/manager/_rating_supporting/_RatingSupportingMetadataManager.py +250 -0
  9. audiometa/manager/_rating_supporting/__init__.py +1 -0
  10. audiometa/manager/_rating_supporting/id3v2/_Id3v2Manager.py +1032 -0
  11. audiometa/manager/_rating_supporting/id3v2/__init__.py +25 -0
  12. audiometa/manager/_rating_supporting/id3v2/_id3v2_constants.py +11 -0
  13. audiometa/manager/_rating_supporting/riff/_RiffManager.py +1002 -0
  14. audiometa/manager/_rating_supporting/riff/__init__.py +25 -0
  15. audiometa/manager/_rating_supporting/riff/_riff_constants.py +17 -0
  16. audiometa/manager/_rating_supporting/vorbis/_VorbisManager.py +542 -0
  17. audiometa/manager/_rating_supporting/vorbis/__init__.py +17 -0
  18. audiometa/manager/_rating_supporting/vorbis/_vorbis_constants.py +6 -0
  19. audiometa/manager/id3v1/_Id3v1Manager.py +512 -0
  20. audiometa/manager/id3v1/__init__.py +1 -0
  21. audiometa/manager/id3v1/_constants.py +8 -0
  22. audiometa/manager/id3v1/id3v1_raw_metadata.py +242 -0
  23. audiometa/manager/id3v1/id3v1_raw_metadata_key.py +13 -0
  24. audiometa/test/__init__.py +1 -0
  25. audiometa/test/assets/create_test_files.py +72 -0
  26. audiometa/test/helpers/__init__.py +51 -0
  27. audiometa/test/helpers/common/__init__.py +6 -0
  28. audiometa/test/helpers/common/audio_file_creator.py +68 -0
  29. audiometa/test/helpers/common/external_tool_runner.py +74 -0
  30. audiometa/test/helpers/id3v1/__init__.py +8 -0
  31. audiometa/test/helpers/id3v1/id3v1_header_verifier.py +18 -0
  32. audiometa/test/helpers/id3v1/id3v1_metadata_deleter.py +37 -0
  33. audiometa/test/helpers/id3v1/id3v1_metadata_getter.py +61 -0
  34. audiometa/test/helpers/id3v1/id3v1_metadata_setter.py +82 -0
  35. audiometa/test/helpers/id3v2/__init__.py +28 -0
  36. audiometa/test/helpers/id3v2/id3v2_frame_manual_creator.py +349 -0
  37. audiometa/test/helpers/id3v2/id3v2_header_verifier.py +38 -0
  38. audiometa/test/helpers/id3v2/id3v2_metadata_deleter.py +56 -0
  39. audiometa/test/helpers/id3v2/id3v2_metadata_getter.py +189 -0
  40. audiometa/test/helpers/id3v2/id3v2_metadata_setter.py +506 -0
  41. audiometa/test/helpers/riff/__init__.py +8 -0
  42. audiometa/test/helpers/riff/riff_header_verifier.py +85 -0
  43. audiometa/test/helpers/riff/riff_manual_metadata_creator.py +298 -0
  44. audiometa/test/helpers/riff/riff_metadata_deleter.py +56 -0
  45. audiometa/test/helpers/riff/riff_metadata_getter.py +219 -0
  46. audiometa/test/helpers/riff/riff_metadata_setter.py +374 -0
  47. audiometa/test/helpers/scripts/__init__.py +0 -0
  48. audiometa/test/helpers/technical_info_inspector.py +115 -0
  49. audiometa/test/helpers/temp_file_with_metadata.py +82 -0
  50. audiometa/test/helpers/vorbis/__init__.py +8 -0
  51. audiometa/test/helpers/vorbis/vorbis_header_verifier.py +31 -0
  52. audiometa/test/helpers/vorbis/vorbis_metadata_deleter.py +49 -0
  53. audiometa/test/helpers/vorbis/vorbis_metadata_getter.py +67 -0
  54. audiometa/test/helpers/vorbis/vorbis_metadata_setter.py +221 -0
  55. audiometa/test/tests/__init__.py +0 -0
  56. audiometa/test/tests/conftest.py +276 -0
  57. audiometa/test/tests/e2e/__init__.py +0 -0
  58. audiometa/test/tests/e2e/cli/__init__.py +0 -0
  59. audiometa/test/tests/e2e/cli/error_handling/__init__.py +1 -0
  60. audiometa/test/tests/e2e/cli/error_handling/test_command_structure_errors.py +77 -0
  61. audiometa/test/tests/e2e/cli/error_handling/test_file_access_errors.py +130 -0
  62. audiometa/test/tests/e2e/cli/error_handling/test_format_output_errors.py +118 -0
  63. audiometa/test/tests/e2e/cli/error_handling/test_input_validation_errors.py +172 -0
  64. audiometa/test/tests/e2e/cli/error_handling/test_missing_fields_validation.py +49 -0
  65. audiometa/test/tests/e2e/cli/error_handling/test_multiple_files_errors.py +160 -0
  66. audiometa/test/tests/e2e/cli/error_handling/test_rating_validation.py +90 -0
  67. audiometa/test/tests/e2e/cli/error_handling/test_year_validation.py +51 -0
  68. audiometa/test/tests/e2e/cli/read/__init__.py +0 -0
  69. audiometa/test/tests/e2e/cli/read/test_basic.py +58 -0
  70. audiometa/test/tests/e2e/cli/read/test_comprehensive.py +240 -0
  71. audiometa/test/tests/e2e/cli/read/test_formats.py +55 -0
  72. audiometa/test/tests/e2e/cli/read/test_metadata_content.py +164 -0
  73. audiometa/test/tests/e2e/cli/read/test_multiple_files.py +149 -0
  74. audiometa/test/tests/e2e/cli/read/test_options.py +88 -0
  75. audiometa/test/tests/e2e/cli/read/test_unified.py +84 -0
  76. audiometa/test/tests/e2e/cli/test_delete.py +20 -0
  77. audiometa/test/tests/e2e/cli/test_formatting.py +31 -0
  78. audiometa/test/tests/e2e/cli/test_help.py +41 -0
  79. audiometa/test/tests/e2e/cli/write/__init__.py +0 -0
  80. audiometa/test/tests/e2e/cli/write/test_basic.py +51 -0
  81. audiometa/test/tests/e2e/cli/write/test_comprehensive.py +210 -0
  82. audiometa/test/tests/e2e/cli/write/test_force_format.py +336 -0
  83. audiometa/test/tests/e2e/cli/write/test_integer_fields.py +145 -0
  84. audiometa/test/tests/e2e/cli/write/test_list_fields.py +107 -0
  85. audiometa/test/tests/e2e/cli/write/test_rating.py +74 -0
  86. audiometa/test/tests/e2e/cli/write/test_string_fields.py +54 -0
  87. audiometa/test/tests/e2e/cli/write/test_validation.py +85 -0
  88. audiometa/test/tests/e2e/scenarios/__init__.py +0 -0
  89. audiometa/test/tests/e2e/scenarios/test_user_scenarios.py +166 -0
  90. audiometa/test/tests/e2e/workflows/__init__.py +0 -0
  91. audiometa/test/tests/e2e/workflows/test_core_workflows.py +166 -0
  92. audiometa/test/tests/e2e/workflows/test_deletion_workflows.py +318 -0
  93. audiometa/test/tests/e2e/workflows/test_error_handling_workflows.py +165 -0
  94. audiometa/test/tests/e2e/workflows/test_format_specific_workflows.py +129 -0
  95. audiometa/test/tests/e2e/workflows/test_rating_workflows.py +124 -0
  96. audiometa/test/tests/integration/__init__.py +0 -0
  97. audiometa/test/tests/integration/audio_format/__init__.py +0 -0
  98. audiometa/test/tests/integration/audio_format/flac/__init__.py +0 -0
  99. audiometa/test/tests/integration/audio_format/flac/test_flac_delete_all.py +108 -0
  100. audiometa/test/tests/integration/audio_format/flac/test_flac_reading_all.py +61 -0
  101. audiometa/test/tests/integration/audio_format/flac/test_flac_reading_field.py +65 -0
  102. audiometa/test/tests/integration/audio_format/flac/test_flac_writing.py +69 -0
  103. audiometa/test/tests/integration/audio_format/mp3/__init__.py +0 -0
  104. audiometa/test/tests/integration/audio_format/mp3/test_mp3_delete_all.py +79 -0
  105. audiometa/test/tests/integration/audio_format/mp3/test_mp3_reading_all.py +61 -0
  106. audiometa/test/tests/integration/audio_format/mp3/test_mp3_reading_field.py +67 -0
  107. audiometa/test/tests/integration/audio_format/mp3/test_mp3_writing.py +60 -0
  108. audiometa/test/tests/integration/audio_format/wav/__init__.py +0 -0
  109. audiometa/test/tests/integration/audio_format/wav/test_wav_delete_all.py +87 -0
  110. audiometa/test/tests/integration/audio_format/wav/test_wav_reading_all.py +62 -0
  111. audiometa/test/tests/integration/audio_format/wav/test_wav_reading_field.py +57 -0
  112. audiometa/test/tests/integration/audio_format/wav/test_wav_with_id3v2_tags.py +83 -0
  113. audiometa/test/tests/integration/audio_format/wav/test_wav_writing.py +62 -0
  114. audiometa/test/tests/integration/conftest.py +29 -0
  115. audiometa/test/tests/integration/delete_all_metadata/__init__.py +1 -0
  116. audiometa/test/tests/integration/delete_all_metadata/test_audio_format_all.py +102 -0
  117. audiometa/test/tests/integration/delete_all_metadata/test_audio_format_header_deletion.py +77 -0
  118. audiometa/test/tests/integration/delete_all_metadata/test_basic_functionality.py +47 -0
  119. audiometa/test/tests/integration/delete_all_metadata/test_error_handling.py +24 -0
  120. audiometa/test/tests/integration/encoding/__init__.py +1 -0
  121. audiometa/test/tests/integration/encoding/test_encoding.py +88 -0
  122. audiometa/test/tests/integration/encoding/test_special_characters_edge_cases.py +223 -0
  123. audiometa/test/tests/integration/get_full_metadata/__init__.py +0 -0
  124. audiometa/test/tests/integration/get_full_metadata/test_audio_formats.py +122 -0
  125. audiometa/test/tests/integration/get_full_metadata/test_binary_data_filtering.py +250 -0
  126. audiometa/test/tests/integration/get_full_metadata/test_consistency.py +67 -0
  127. audiometa/test/tests/integration/get_full_metadata/test_edge_cases.py +123 -0
  128. audiometa/test/tests/integration/get_full_metadata/test_error_handling.py +40 -0
  129. audiometa/test/tests/integration/get_full_metadata/test_get_full_metadata.py +43 -0
  130. audiometa/test/tests/integration/get_full_metadata/test_options.py +207 -0
  131. audiometa/test/tests/integration/get_full_metadata/test_performance.py +95 -0
  132. audiometa/test/tests/integration/get_full_metadata/test_riff_bext.py +128 -0
  133. audiometa/test/tests/integration/get_full_metadata/test_structure.py +161 -0
  134. audiometa/test/tests/integration/metadata_field/__init__.py +0 -0
  135. audiometa/test/tests/integration/metadata_field/album/__init__.py +0 -0
  136. audiometa/test/tests/integration/metadata_field/album/test_deleting.py +73 -0
  137. audiometa/test/tests/integration/metadata_field/album/test_reading.py +36 -0
  138. audiometa/test/tests/integration/metadata_field/album/test_writing.py +50 -0
  139. audiometa/test/tests/integration/metadata_field/album_artists/__init__.py +0 -0
  140. audiometa/test/tests/integration/metadata_field/album_artists/test_deleting.py +83 -0
  141. audiometa/test/tests/integration/metadata_field/album_artists/test_reading.py +38 -0
  142. audiometa/test/tests/integration/metadata_field/album_artists/test_writing.py +52 -0
  143. audiometa/test/tests/integration/metadata_field/artists/__init__.py +0 -0
  144. audiometa/test/tests/integration/metadata_field/artists/test_deleting.py +68 -0
  145. audiometa/test/tests/integration/metadata_field/artists/test_reading.py +36 -0
  146. audiometa/test/tests/integration/metadata_field/artists/test_writing.py +46 -0
  147. audiometa/test/tests/integration/metadata_field/bpm/__init__.py +0 -0
  148. audiometa/test/tests/integration/metadata_field/bpm/test_deleting.py +75 -0
  149. audiometa/test/tests/integration/metadata_field/bpm/test_reading.py +32 -0
  150. audiometa/test/tests/integration/metadata_field/bpm/test_writing.py +56 -0
  151. audiometa/test/tests/integration/metadata_field/comment/__init__.py +0 -0
  152. audiometa/test/tests/integration/metadata_field/comment/test_deleting.py +68 -0
  153. audiometa/test/tests/integration/metadata_field/comment/test_reading.py +36 -0
  154. audiometa/test/tests/integration/metadata_field/comment/test_writing.py +49 -0
  155. audiometa/test/tests/integration/metadata_field/composer/__init__.py +0 -0
  156. audiometa/test/tests/integration/metadata_field/composer/test_deleting.py +75 -0
  157. audiometa/test/tests/integration/metadata_field/composer/test_reading.py +34 -0
  158. audiometa/test/tests/integration/metadata_field/composer/test_writing.py +41 -0
  159. audiometa/test/tests/integration/metadata_field/copyright/__init__.py +0 -0
  160. audiometa/test/tests/integration/metadata_field/copyright/test_deleting.py +81 -0
  161. audiometa/test/tests/integration/metadata_field/copyright/test_reading.py +35 -0
  162. audiometa/test/tests/integration/metadata_field/copyright/test_writing.py +41 -0
  163. audiometa/test/tests/integration/metadata_field/disc_number/__init__.py +0 -0
  164. audiometa/test/tests/integration/metadata_field/disc_number/test_deleting.py +97 -0
  165. audiometa/test/tests/integration/metadata_field/disc_number/test_reading.py +92 -0
  166. audiometa/test/tests/integration/metadata_field/disc_number/test_writing.py +153 -0
  167. audiometa/test/tests/integration/metadata_field/field_not_supported/__init__.py +0 -0
  168. audiometa/test/tests/integration/metadata_field/field_not_supported/test_deleting.py +56 -0
  169. audiometa/test/tests/integration/metadata_field/field_not_supported/test_reading.py +54 -0
  170. audiometa/test/tests/integration/metadata_field/field_not_supported/test_writing.py +61 -0
  171. audiometa/test/tests/integration/metadata_field/genre/__init__.py +0 -0
  172. audiometa/test/tests/integration/metadata_field/genre/reading/__init__.py +0 -0
  173. audiometa/test/tests/integration/metadata_field/genre/reading/metadata_format/__init__.py +0 -0
  174. audiometa/test/tests/integration/metadata_field/genre/reading/metadata_format/test_id3v1_reading.py +65 -0
  175. audiometa/test/tests/integration/metadata_field/genre/reading/metadata_format/test_id3v2_reading.py +25 -0
  176. audiometa/test/tests/integration/metadata_field/genre/reading/metadata_format/test_riff_reading.py +58 -0
  177. audiometa/test/tests/integration/metadata_field/genre/reading/metadata_format/test_vorbis_reading.py +61 -0
  178. audiometa/test/tests/integration/metadata_field/genre/reading/test_smart_reading.py +191 -0
  179. audiometa/test/tests/integration/metadata_field/genre/test_deleting.py +62 -0
  180. audiometa/test/tests/integration/metadata_field/genre/test_writing.py +64 -0
  181. audiometa/test/tests/integration/metadata_field/isrc/__init__.py +1 -0
  182. audiometa/test/tests/integration/metadata_field/isrc/test_deleting.py +31 -0
  183. audiometa/test/tests/integration/metadata_field/isrc/test_reading.py +35 -0
  184. audiometa/test/tests/integration/metadata_field/isrc/test_writing.py +165 -0
  185. audiometa/test/tests/integration/metadata_field/language/__init__.py +0 -0
  186. audiometa/test/tests/integration/metadata_field/language/test_deleting.py +75 -0
  187. audiometa/test/tests/integration/metadata_field/language/test_reading.py +39 -0
  188. audiometa/test/tests/integration/metadata_field/language/test_writing.py +43 -0
  189. audiometa/test/tests/integration/metadata_field/lyrics/__init__.py +0 -0
  190. audiometa/test/tests/integration/metadata_field/lyrics/test_deleting.py +129 -0
  191. audiometa/test/tests/integration/metadata_field/lyrics/test_reading.py +57 -0
  192. audiometa/test/tests/integration/metadata_field/lyrics/test_writing.py +59 -0
  193. audiometa/test/tests/integration/metadata_field/publisher/__init__.py +0 -0
  194. audiometa/test/tests/integration/metadata_field/publisher/test_deleting.py +88 -0
  195. audiometa/test/tests/integration/metadata_field/publisher/test_reading.py +32 -0
  196. audiometa/test/tests/integration/metadata_field/publisher/test_writing.py +47 -0
  197. audiometa/test/tests/integration/metadata_field/rating/__init__.py +0 -0
  198. audiometa/test/tests/integration/metadata_field/rating/reading/__init__.py +0 -0
  199. audiometa/test/tests/integration/metadata_field/rating/reading/test_base_100_proportional.py +81 -0
  200. audiometa/test/tests/integration/metadata_field/rating/reading/test_base_255_non_proportional.py +33 -0
  201. audiometa/test/tests/integration/metadata_field/rating/reading/test_base_255_proportional.py +58 -0
  202. audiometa/test/tests/integration/metadata_field/rating/test_deleting.py +117 -0
  203. audiometa/test/tests/integration/metadata_field/rating/test_error_handling.py +137 -0
  204. audiometa/test/tests/integration/metadata_field/rating/writing/__init__.py +0 -0
  205. audiometa/test/tests/integration/metadata_field/rating/writing/metadata_format/__init__.py +0 -0
  206. audiometa/test/tests/integration/metadata_field/rating/writing/metadata_format/test_id3v2.py +77 -0
  207. audiometa/test/tests/integration/metadata_field/rating/writing/metadata_format/test_riff.py +55 -0
  208. audiometa/test/tests/integration/metadata_field/rating/writing/metadata_format/test_vorbis.py +57 -0
  209. audiometa/test/tests/integration/metadata_field/rating/writing/test_comprehensive.py +192 -0
  210. audiometa/test/tests/integration/metadata_field/release_date/__init__.py +0 -0
  211. audiometa/test/tests/integration/metadata_field/release_date/test_deleting.py +74 -0
  212. audiometa/test/tests/integration/metadata_field/release_date/test_error_handling.py +82 -0
  213. audiometa/test/tests/integration/metadata_field/release_date/test_reading.py +59 -0
  214. audiometa/test/tests/integration/metadata_field/release_date/test_writing.py +49 -0
  215. audiometa/test/tests/integration/metadata_field/test_metadata_field_validation.py +135 -0
  216. audiometa/test/tests/integration/metadata_field/title/__init__.py +0 -0
  217. audiometa/test/tests/integration/metadata_field/title/test_deleting.py +73 -0
  218. audiometa/test/tests/integration/metadata_field/title/test_error_handling.py +47 -0
  219. audiometa/test/tests/integration/metadata_field/title/test_reading.py +36 -0
  220. audiometa/test/tests/integration/metadata_field/title/test_writing.py +64 -0
  221. audiometa/test/tests/integration/metadata_field/track_number/__init__.py +0 -0
  222. audiometa/test/tests/integration/metadata_field/track_number/reading/__init__.py +0 -0
  223. audiometa/test/tests/integration/metadata_field/track_number/reading/test_edge_cases.py +43 -0
  224. audiometa/test/tests/integration/metadata_field/track_number/reading/test_metadata_format.py +32 -0
  225. audiometa/test/tests/integration/metadata_field/track_number/test_deleting.py +59 -0
  226. audiometa/test/tests/integration/metadata_field/track_number/test_writing.py +73 -0
  227. audiometa/test/tests/integration/multiple_values/__init__.py +1 -0
  228. audiometa/test/tests/integration/multiple_values/reading/__init__.py +1 -0
  229. audiometa/test/tests/integration/multiple_values/reading/metadata_format/__init__.py +1 -0
  230. audiometa/test/tests/integration/multiple_values/reading/metadata_format/test_id3v1.py +23 -0
  231. audiometa/test/tests/integration/multiple_values/reading/metadata_format/test_id3v2_3.py +92 -0
  232. audiometa/test/tests/integration/multiple_values/reading/metadata_format/test_id3v2_4.py +216 -0
  233. audiometa/test/tests/integration/multiple_values/reading/metadata_format/test_riff.py +84 -0
  234. audiometa/test/tests/integration/multiple_values/reading/metadata_format/test_vorbis.py +169 -0
  235. audiometa/test/tests/integration/multiple_values/reading/test_performance_large_data.py +209 -0
  236. audiometa/test/tests/integration/multiple_values/reading/test_smart_parsing_scenarios.py +198 -0
  237. audiometa/test/tests/integration/multiple_values/reading/test_unicode_handling.py +24 -0
  238. audiometa/test/tests/integration/multiple_values/writing/__init__.py +1 -0
  239. audiometa/test/tests/integration/multiple_values/writing/metadata_format/__init__.py +1 -0
  240. audiometa/test/tests/integration/multiple_values/writing/metadata_format/test_id3v1.py +62 -0
  241. audiometa/test/tests/integration/multiple_values/writing/metadata_format/test_id3v2_3.py +36 -0
  242. audiometa/test/tests/integration/multiple_values/writing/metadata_format/test_id3v2_4.py +34 -0
  243. audiometa/test/tests/integration/multiple_values/writing/metadata_format/test_riff.py +32 -0
  244. audiometa/test/tests/integration/multiple_values/writing/metadata_format/test_vorbis.py +54 -0
  245. audiometa/test/tests/integration/multiple_values/writing/test_error_handling.py +42 -0
  246. audiometa/test/tests/integration/multiple_values/writing/test_large_values.py +98 -0
  247. audiometa/test/tests/integration/reading/__init__.py +1 -0
  248. audiometa/test/tests/integration/reading/test_read_multiple_metadata.py +80 -0
  249. audiometa/test/tests/integration/reading/test_reading_error_handling.py +36 -0
  250. audiometa/test/tests/integration/real_audio_files/__init__.py +0 -0
  251. audiometa/test/tests/integration/real_audio_files/test_reading.py +146 -0
  252. audiometa/test/tests/integration/real_audio_files/test_writing.py +198 -0
  253. audiometa/test/tests/integration/technical_info/__init__.py +0 -0
  254. audiometa/test/tests/integration/technical_info/flac_md5/__init__.py +0 -0
  255. audiometa/test/tests/integration/technical_info/flac_md5/conftest.py +103 -0
  256. audiometa/test/tests/integration/technical_info/flac_md5/test_invalid_md5/__init__.py +0 -0
  257. audiometa/test/tests/integration/technical_info/flac_md5/test_invalid_md5/test_audio_data_corruption.py +21 -0
  258. audiometa/test/tests/integration/technical_info/flac_md5/test_invalid_md5/test_flipped_md5.py +29 -0
  259. audiometa/test/tests/integration/technical_info/flac_md5/test_invalid_md5/test_non_flac_error.py +13 -0
  260. audiometa/test/tests/integration/technical_info/flac_md5/test_invalid_md5/test_partial_md5.py +29 -0
  261. audiometa/test/tests/integration/technical_info/flac_md5/test_invalid_md5/test_random_md5.py +29 -0
  262. audiometa/test/tests/integration/technical_info/flac_md5/test_invalid_md5/test_unset_md5.py +56 -0
  263. audiometa/test/tests/integration/technical_info/flac_md5/test_valid_md5.py +21 -0
  264. audiometa/test/tests/integration/technical_info/test_bitrate.py +79 -0
  265. audiometa/test/tests/integration/technical_info/test_channels.py +38 -0
  266. audiometa/test/tests/integration/technical_info/test_duration_in_sec.py +38 -0
  267. audiometa/test/tests/integration/technical_info/test_sample_rate.py +40 -0
  268. audiometa/test/tests/integration/test_audio_file.py +35 -0
  269. audiometa/test/tests/integration/test_audio_format_readable_after_update_all_metadata_formats.py +95 -0
  270. audiometa/test/tests/integration/writing/__init__.py +0 -0
  271. audiometa/test/tests/integration/writing/test_error_handling.py +44 -0
  272. audiometa/test/tests/integration/writing/test_forced_format.py +224 -0
  273. audiometa/test/tests/integration/writing/test_multiple_format_preservation.py +223 -0
  274. audiometa/test/tests/integration/writing/test_partial_update.py +36 -0
  275. audiometa/test/tests/integration/writing/writing_strategies/__init__.py +0 -0
  276. audiometa/test/tests/integration/writing/writing_strategies/test_cleanup_strategy.py +79 -0
  277. audiometa/test/tests/integration/writing/writing_strategies/test_preserve_strategy.py +76 -0
  278. audiometa/test/tests/integration/writing/writing_strategies/test_sync_strategy.py +215 -0
  279. audiometa/test/tests/integration/writing/writing_strategies/unsupported_fields/__init__.py +0 -0
  280. audiometa/test/tests/integration/writing/writing_strategies/unsupported_fields/test_fail_behavior.py +42 -0
  281. audiometa/test/tests/integration/writing/writing_strategies/unsupported_fields/test_no_writing_on_failure.py +93 -0
  282. audiometa/test/tests/integration/writing/writing_strategies/unsupported_fields/test_strategy_specific.py +99 -0
  283. audiometa/test/tests/unit/__init__.py +0 -0
  284. audiometa/test/tests/unit/audio_file/__init__.py +0 -0
  285. audiometa/test/tests/unit/audio_file/technical_info/__init__.py +0 -0
  286. audiometa/test/tests/unit/audio_file/technical_info/test_bitrate.py +26 -0
  287. audiometa/test/tests/unit/audio_file/technical_info/test_channels.py +31 -0
  288. audiometa/test/tests/unit/audio_file/technical_info/test_duration_in_sec.py +38 -0
  289. audiometa/test/tests/unit/audio_file/technical_info/test_error_handling.py +190 -0
  290. audiometa/test/tests/unit/audio_file/technical_info/test_file_size.py +51 -0
  291. audiometa/test/tests/unit/audio_file/technical_info/test_format_name.py +28 -0
  292. audiometa/test/tests/unit/audio_file/technical_info/test_sample_rate.py +31 -0
  293. audiometa/test/tests/unit/audio_file/test_context_manager.py +30 -0
  294. audiometa/test/tests/unit/audio_file/test_file_validation.py +40 -0
  295. audiometa/test/tests/unit/audio_file/test_is_audio_file.py +49 -0
  296. audiometa/test/tests/unit/audio_file/test_operations.py +20 -0
  297. audiometa/test/tests/unit/audio_file/test_path_handling.py +23 -0
  298. audiometa/test/tests/unit/cli/__init__.py +0 -0
  299. audiometa/test/tests/unit/cli/test_expand_file_patterns.py +234 -0
  300. audiometa/test/tests/unit/metadata_managers/__init__.py +0 -0
  301. audiometa/test/tests/unit/metadata_managers/conftest.py +142 -0
  302. audiometa/test/tests/unit/metadata_managers/header_info/__init__.py +0 -0
  303. audiometa/test/tests/unit/metadata_managers/header_info/test_id3v1.py +49 -0
  304. audiometa/test/tests/unit/metadata_managers/header_info/test_id3v2.py +66 -0
  305. audiometa/test/tests/unit/metadata_managers/header_info/test_riff.py +343 -0
  306. audiometa/test/tests/unit/metadata_managers/header_info/test_vorbis.py +53 -0
  307. audiometa/test/tests/unit/metadata_managers/metadata_field/__init__.py +0 -0
  308. audiometa/test/tests/unit/metadata_managers/metadata_field/multiple_values/__init__.py +0 -0
  309. audiometa/test/tests/unit/metadata_managers/metadata_field/multiple_values/reading/__init__.py +0 -0
  310. audiometa/test/tests/unit/metadata_managers/metadata_field/multiple_values/reading/test_smart_parsing.py +186 -0
  311. audiometa/test/tests/unit/metadata_managers/metadata_field/multiple_values/writing/__init__.py +0 -0
  312. audiometa/test/tests/unit/metadata_managers/metadata_field/multiple_values/writing/test_separator_selection.py +142 -0
  313. audiometa/test/tests/unit/metadata_managers/metadata_field/multiple_values/writing/test_value_filtering.py +76 -0
  314. audiometa/test/tests/unit/metadata_managers/metadata_field/rating/__init__.py +0 -0
  315. audiometa/test/tests/unit/metadata_managers/metadata_field/rating/reading/__init__.py +0 -0
  316. audiometa/test/tests/unit/metadata_managers/metadata_field/rating/reading/test_normalization.py +152 -0
  317. audiometa/test/tests/unit/metadata_managers/metadata_field/rating/reading/test_profiles_values.py +23 -0
  318. audiometa/test/tests/unit/metadata_managers/metadata_field/rating/test_rating_validation.py +77 -0
  319. audiometa/test/tests/unit/metadata_managers/metadata_field/rating/writing/__init__.py +0 -0
  320. audiometa/test/tests/unit/metadata_managers/metadata_field/rating/writing/test_configuration_error.py +43 -0
  321. audiometa/test/tests/unit/metadata_managers/metadata_field/rating/writing/test_validation.py +151 -0
  322. audiometa/test/tests/unit/metadata_managers/metadata_field/rating/writing/test_writing_profiles.py +61 -0
  323. audiometa/test/tests/unit/metadata_managers/metadata_field/test_date_format_validation.py +135 -0
  324. audiometa/test/tests/unit/metadata_managers/metadata_field/test_disc_number_validation.py +75 -0
  325. audiometa/test/tests/unit/metadata_managers/metadata_field/test_isrc_format_validation.py +121 -0
  326. audiometa/test/tests/unit/metadata_managers/metadata_field/test_isrc_type_validation.py +30 -0
  327. audiometa/test/tests/unit/metadata_managers/metadata_field/test_track_number_validation.py +46 -0
  328. audiometa/test/tests/unit/metadata_managers/metadata_field/test_type_validation_exception.py +22 -0
  329. audiometa/test/tests/unit/metadata_managers/metadata_field/test_validation.py +83 -0
  330. audiometa/test/tests/unit/metadata_managers/test_metadata_format_managers_write_and_read.py +74 -0
  331. audiometa/test/tests/unit/metadata_managers/test_riff_configuration_error.py +26 -0
  332. audiometa/utils/__init__.py +1 -0
  333. audiometa/utils/id3v1_genre_code_map.py +205 -0
  334. audiometa/utils/metadata_format.py +31 -0
  335. audiometa/utils/metadata_writing_strategy.py +16 -0
  336. audiometa/utils/mutagen_exception_handler.py +24 -0
  337. audiometa/utils/os_dependencies_checker/__init__.py +24 -0
  338. audiometa/utils/os_dependencies_checker/base.py +62 -0
  339. audiometa/utils/os_dependencies_checker/config.py +77 -0
  340. audiometa/utils/os_dependencies_checker/macos.py +236 -0
  341. audiometa/utils/os_dependencies_checker/ubuntu.py +95 -0
  342. audiometa/utils/os_dependencies_checker/windows.py +227 -0
  343. audiometa/utils/rating_profiles.py +110 -0
  344. audiometa/utils/tool_path_resolver.py +135 -0
  345. audiometa/utils/types.py +82 -0
  346. audiometa/utils/unified_metadata_key.py +87 -0
  347. audiometa_python-0.6.0.dist-info/METADATA +1593 -0
  348. audiometa_python-0.6.0.dist-info/RECORD +352 -0
  349. audiometa_python-0.6.0.dist-info/WHEEL +5 -0
  350. audiometa_python-0.6.0.dist-info/entry_points.txt +2 -0
  351. audiometa_python-0.6.0.dist-info/licenses/LICENSE +202 -0
  352. audiometa_python-0.6.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1593 @@
1
+ Metadata-Version: 2.4
2
+ Name: audiometa-python
3
+ Version: 0.6.0
4
+ Summary: A comprehensive Python library for reading and writing audio metadata across multiple formats
5
+ Author: AudioMeta Python Contributors
6
+ Author-email: Andreas Garcia <garcia.andreas.1991@gmail.com>
7
+ License: Apache-2.0
8
+ Project-URL: Homepage, https://github.com/your-username/audiometa-python
9
+ Project-URL: Repository, https://github.com/your-username/audiometa-python
10
+ Project-URL: Issues, https://github.com/your-username/audiometa-python/issues
11
+ Keywords: audio,metadata,mp3,flac,wav,id3,vorbis,riff
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: Apache Software License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Topic :: Multimedia :: Sound/Audio
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Requires-Python: >=3.12
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: mutagen==1.45.0
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest==9.0.1; extra == "dev"
28
+ Requires-Dist: pytest-cov==4.0.0; extra == "dev"
29
+ Requires-Dist: pytest-mock==3.15.1; extra == "dev"
30
+ Requires-Dist: ruff==0.6.9; extra == "dev"
31
+ Requires-Dist: isort==5.13.2; extra == "dev"
32
+ Requires-Dist: mypy==1.18.2; extra == "dev"
33
+ Requires-Dist: pre-commit==3.0.0; extra == "dev"
34
+ Requires-Dist: autoflake==2.0.0; extra == "dev"
35
+ Requires-Dist: pydocstringformatter==0.7.5; extra == "dev"
36
+ Requires-Dist: soundfile==0.10.1; extra == "dev"
37
+ Requires-Dist: numpy==2.3.4; extra == "dev"
38
+ Requires-Dist: bump2version==1.0.1; extra == "dev"
39
+ Provides-Extra: yaml
40
+ Requires-Dist: PyYAML==6.0; extra == "yaml"
41
+ Dynamic: license-file
42
+
43
+ <div align="center">
44
+ <img src="https://raw.githubusercontent.com/Andreas-Garcia/audiometa/main/assets/logo.png" alt="AudioMeta Logo" width="200"/>
45
+ </div>
46
+
47
+ # AudioMeta Python
48
+
49
+ [![CI](https://github.com/Andreas-Garcia/audiometa/actions/workflows/ci.yml/badge.svg)](https://github.com/Andreas-Garcia/audiometa/actions/workflows/ci.yml)
50
+ [![Python](https://img.shields.io/badge/python-3.12%20%7C%203.13%20%7C%203.14-blue)](https://www.python.org/)
51
+ [![License](https://img.shields.io/badge/license-Apache%202.0-green.svg)](LICENSE)
52
+ [![PyPI version](https://img.shields.io/pypi/v/audiometa-python)](https://pypi.org/project/audiometa-python/)
53
+ [![Downloads](https://img.shields.io/pepy/dt/audiometa-python)](https://pepy.tech/project/audiometa-python)
54
+ [![GitHub stars](https://img.shields.io/github/stars/Andreas-Garcia/audiometa?style=social)](https://github.com/Andreas-Garcia/audiometa/stargazers)
55
+
56
+ A powerful, unified Python library for reading and writing audio metadata across multiple formats. AudioMeta supports MP3, FLAC, and WAV audio files, working seamlessly with ID3v1, ID3v2, Vorbis, and RIFF metadata formats through a single, consistent API.
57
+
58
+ **Author**: [Andreas Garcia](https://github.com/Andreas-Garcia)
59
+
60
+ ## ⭐ Show Your Support
61
+
62
+ If you find AudioMeta Python useful, please consider:
63
+
64
+ - ⭐ **Starring this repository** - It helps others discover the project
65
+ - 🐛 **Reporting bugs** - Help improve the library by [opening an issue](https://github.com/Andreas-Garcia/audiometa/issues)
66
+ - 💡 **Suggesting features** - Share your ideas via [GitHub Discussions](https://github.com/Andreas-Garcia/audiometa/discussions) or [feature requests](https://github.com/Andreas-Garcia/audiometa/issues)
67
+ - 🤝 **Contributing** - See [CONTRIBUTING.md](CONTRIBUTING.md) for ways to help
68
+ - 📢 **Sharing** - Tell others about AudioMeta Python
69
+
70
+ Your support helps make this project better for everyone! 🎵
71
+
72
+ ## Table of Contents
73
+
74
+ - [⭐ Show Your Support](#-show-your-support)
75
+ - [✨ Features](#-features)
76
+ - [📁 Supported Formats](#-supported-formats)
77
+ - [Supported Audio Formats Per Metadata Format](#supported-audio-formats-per-metadata-format)
78
+ - [Supported Metadata Formats per Audio Format](#supported-metadata-formats-per-audio-format)
79
+ - [Format Capabilities](#format-capabilities)
80
+ - [📊 Supported Fields And Audio Technical Info](#-supported-fields-and-audio-technical-info)
81
+ - [📦 Installation](#-installation)
82
+ - [System Requirements](#system-requirements)
83
+ - [Installing Required Tools](#installing-required-tools)
84
+ - [Verifying Installation](#verifying-installation)
85
+ - [External Tools Usage](#external-tools-usage)
86
+ - [🚀 Getting Started](#-getting-started)
87
+ - [What You Need](#what-you-need)
88
+ - [Your First Steps](#your-first-steps)
89
+ - [Common Use Cases](#common-use-cases)
90
+ - [⚡ Quick Start](#-quick-start)
91
+ - [Reading Metadata](#reading-metadata)
92
+ - [Validate Metadata Before Update](#validate-metadata-before-update)
93
+ - [Writing Metadata](#writing-metadata)
94
+ - [Deleting Metadata](#deleting-metadata)
95
+ - [📚 Core API Reference](#-core-api-reference)
96
+ - [Reading Metadata (API Reference)](#reading-metadata-api-reference)
97
+ - [Pre-Update Validation (API Reference)](#pre-update-validation-api-reference)
98
+ - [Writing Metadata (API Reference)](#writing-metadata-api-reference)
99
+ - [Deleting Metadata (API Reference)](#deleting-metadata-api-reference)
100
+ - [Error Handling (API Reference)](#error-handling-api-reference)
101
+ - [📖 Metadata Guide](#-metadata-guide)
102
+ - [Metadata Field Guide: Support and Handling](#metadata-field-guide-support-and-handling)
103
+ - [Audio Technical Info Guide](#audio-technical-info-guide)
104
+ - [Unsupported Metadata Handling](#unsupported-metadata-handling)
105
+ - [💻 Command Line Interface](#-command-line-interface)
106
+ - [Installation](#cli-installation)
107
+ - [Basic Usage](#basic-usage)
108
+ - [Advanced Options](#advanced-options)
109
+ - [Output Formats](#output-formats)
110
+ - [Examples](#examples)
111
+ - [📝 Changelog](#-changelog)
112
+ - [🤝 Contributing](#-contributing)
113
+ - [Code of Conduct](CODE_OF_CONDUCT.md)
114
+ - [Security Policy](https://github.com/Andreas-Garcia/audiometa/security/policy)
115
+ - [License](#license)
116
+
117
+ ## ✨ Features
118
+
119
+ - **Unified API**: A single, consistent API for reading and writing metadata across all supported formats. Use the same functions (`get_unified_metadata()`, `update_metadata()`, etc.) regardless of whether you're working with MP3, FLAC, or WAV files. The library automatically handles format-specific differences, normalizes field names, and intelligently merges metadata from multiple formats when reading.
120
+
121
+ - **Multi-format Support**: ID3v1, ID3v2, Vorbis (FLAC), and RIFF (WAV) metadata formats. Many audio files can contain multiple metadata formats simultaneously (e.g., MP3 files with both ID3v1 and ID3v2 tags, FLAC files with ID3v1, ID3v2, and Vorbis comments). AudioMeta intelligently handles these scenarios with automatic format detection and priority-based reading.
122
+
123
+ - **Format Control**: Force specific metadata formats when reading or writing for precise control. Read only ID3v1 tags from an MP3 file that contains both ID3v1 and ID3v2, or write metadata exclusively to the Vorbis format in a FLAC file. Essential for format-specific operations, migration tasks, or working with legacy metadata formats.
124
+
125
+ - **Technical Information**: Access to technical information about audio files, including duration, bitrate, sample rate, channels, and file size. This technical data is extracted directly from audio file headers, so you can get comprehensive file analysis even when no metadata tags are present.
126
+
127
+ - **Core Metadata Fields**: Support for 15+ metadata fields including title, artist, album, rating, BPM, and more. More fields are planned to be supported soon.
128
+
129
+ - **Read/Write Operations**: Full read and write support for most formats
130
+
131
+ - **Rating Support**: Normalized rating handling across different formats
132
+
133
+ - **Complete File Analysis**: Get full metadata including headers and technical details even when no metadata is present
134
+
135
+ ## 📁 Supported Formats
136
+
137
+ ### Supported Audio Formats Per Metadata Format
138
+
139
+ | Format | Audio Format |
140
+ | ------ | -------------- |
141
+ | ID3v1 | MP3, FLAC, WAV |
142
+ | ID3v2 | MP3, FLAC, WAV |
143
+ | Vorbis | FLAC |
144
+ | RIFF | WAV |
145
+
146
+ ### Supported Metadata Formats per Audio Format
147
+
148
+ | Audio Format | Supported Metadata Formats |
149
+ | ------------ | -------------------------- |
150
+ | MP3 | ID3v1, ID3v2 |
151
+ | FLAC | ID3v1, ID3v2, Vorbis |
152
+ | WAV | ID3v1, ID3v2, RIFF |
153
+
154
+ ### Format Capabilities
155
+
156
+ For comprehensive information about each metadata format (history, structure, advantages, disadvantages, use cases), see the **[Metadata Formats Guide](docs/METADATA_FORMATS.md)**.
157
+
158
+ #### ID3v1 Metadata Format
159
+
160
+ - **Primary Support**: MP3 files (native format)
161
+ - **Extended Support**: FLAC and WAV files with ID3v1 tags
162
+ - **Limitations**: 30-character field limits, no album artist support
163
+ - **Operations**: Full read/write support with direct file manipulation
164
+ - **Note**: ID3v1.1 is supported (track number supported in comment field)
165
+
166
+ #### ID3v2 Metadata Format
167
+
168
+ - **Supported Formats**: MP3, WAV, FLAC
169
+ - **Features**: All metadata fields, multiple artists, cover art, extended metadata
170
+ - **Versions**: Supports ID3v2.3 and ID3v2.4
171
+ - **Note**: Most versatile format, works across multiple file types
172
+
173
+ #### Vorbis Metadata Format
174
+
175
+ - **Primary Support**: FLAC files (native Vorbis comments)
176
+ - **Features**: Most metadata fields, multiple artists, cover art
177
+ - **Limitations**: Some fields not supported (lyrics, etc.)
178
+ - **Note**: Standard metadata format for FLAC files
179
+
180
+ **Vorbis Comment Key Handling**
181
+ Vorbis comment field names are case-insensitive, as defined by the Xiph.org Vorbis Comment specification.
182
+ To ensure consistent and predictable behavior, this library normalizes all field names internally and follows modern interoperability conventions.
183
+
184
+ **_Reading_**
185
+ When reading Vorbis comments, the library treats field names in a case-insensitive manner. For example, "TITLE", "title", and "Title" are considered equivalent.
186
+
187
+ **_Writing_**
188
+ When writing Vorbis comments, the library standardizes field names to uppercase to maintain consistency and compatibility with common practices in audio metadata management. It thus writes "TITLE" removing eventual existing variations in casing.
189
+
190
+ #### RIFF Metadata Format
191
+
192
+ - **Strict Support**: WAV files only
193
+ - **Features**: Most metadata fields including album artist, language, comments
194
+ - **Limitations**: Some fields not supported (BPM, lyrics, etc.)
195
+ - **Note**: Native metadata format for WAV files
196
+
197
+ ## 📊 Supported Fields And Audio Technical Info
198
+
199
+ AudioMeta supports comprehensive audio information across all formats. For technical audio information (duration, bitrate, sample rate, channels, file size, format info, MD5 checksum validation and repair), see:
200
+
201
+ **[Audio Technical Info Guide](docs/AUDIO_TECHNICAL_INFO_GUIDE.md)**
202
+
203
+ For metadata fields (title, artist, album, genres, ratings, etc.), see:
204
+
205
+ **[Metadata Field Guide: Support and Handling](docs/METADATA_FIELD_GUIDE.md)**
206
+
207
+ ## 📦 Installation
208
+
209
+ ```bash
210
+ pip install audiometa-python
211
+ ```
212
+
213
+ ### System Requirements
214
+
215
+ - **Python**: 3.12, 3.13, or 3.14
216
+ - **Operating Systems**: Windows, macOS, Linux
217
+ - **Dependencies**: Automatically installed with the package
218
+ - **Required Tools**: ffprobe (for WAV file processing), flac (for FLAC MD5 validation)
219
+
220
+ ### Installing Required Tools
221
+
222
+ The library requires several external tools for full functionality. **Use the automated installation scripts** to ensure you have the correct pinned versions that match CI.
223
+
224
+ #### Automated Setup {#automated-setup-recommended}
225
+
226
+ To ensure your local environment matches CI exactly, use the automated installation scripts:
227
+
228
+ ```bash
229
+ # Ubuntu/Linux
230
+ ./scripts/install-system-dependencies-ubuntu.sh
231
+
232
+ # macOS
233
+ ./scripts/install-system-dependencies-macos.sh
234
+
235
+ # Windows
236
+ .\scripts\install-system-dependencies-windows.ps1
237
+ ```
238
+
239
+ These scripts install all required tools with pinned versions that match CI:
240
+
241
+ **Production tools (required for library functionality):**
242
+
243
+ - **ffmpeg** / **ffprobe** - For WAV file processing and technical info (all platforms)
244
+ - **flac** / **metaflac** - For FLAC MD5 validation and metadata writing (all platforms)
245
+ - **id3v2** - For ID3v2 tag writing on FLAC files (Ubuntu/macOS only; Windows requires WSL)
246
+ - **bwfmetaedit** - For BWF metadata (Ubuntu/macOS/Windows)
247
+ - **libsndfile** - For audio file I/O (Ubuntu/macOS only)
248
+
249
+ **Dev/testing tools (only needed for running tests locally):**
250
+
251
+ - **mediainfo** - Only for integration test verification
252
+ - **exiftool** - Only for integration test verification
253
+
254
+ **Pinned versions:** All tool versions are pinned in separate configuration files (the single source of truth):
255
+
256
+ - [`system-dependencies-prod.toml`](system-dependencies-prod.toml) - Production dependencies (ffmpeg, flac, id3v2)
257
+ - [`system-dependencies-test-only.toml`](system-dependencies-test-only.toml) - Test-only dependencies (mediainfo, exiftool, bwfmetaedit, libsndfile) - supplementary to prod dependencies
258
+ - [`system-dependencies-lint.toml`](system-dependencies-lint.toml) - Lint dependencies (PowerShell)
259
+
260
+ The scripts verify installed versions match these pinned versions. See the configuration files for complete details and OS-specific version information.
261
+
262
+ **Note for Windows users:**
263
+
264
+ - The `id3v2` tool is not available as a native Windows binary. The installation script attempts to use **WSL (Windows Subsystem for Linux)** to install `id3v2` via Ubuntu's package manager, but WSL installation complexity (requiring system restarts, DISM configuration, and Ubuntu distribution setup) has prevented successful full installation in practice. This is why Windows CI only runs e2e tests (which don't require `id3v2`). For local development, the script will attempt WSL installation, but manual WSL setup may be required.
265
+ - **Windows CI differences:** Windows CI only runs e2e tests (not unit/integration tests), so some tools are skipped in CI but still installed locally for full test coverage: `mediainfo` and `exiftool` are required for integration tests and are installed by the script for local development, but skipped in Windows CI since integration tests don't run there.
266
+
267
+ #### Verifying Installation
268
+
269
+ After installation, verify the tools are available:
270
+
271
+ ```bash
272
+ ffprobe -version
273
+ flac --version
274
+ ```
275
+
276
+ The installation scripts automatically verify installed versions match pinned versions from `system-dependencies-prod.toml`, `system-dependencies-test-only.toml`, and `system-dependencies-lint.toml`.
277
+
278
+ #### External Tools Usage
279
+
280
+ AudioMeta uses a combination of Python libraries and external command-line tools depending on the operation and audio format. This section provides a comprehensive overview of when external tools are required versus when pure Python libraries are used.
281
+
282
+ | Format | Read Metadata | Write Metadata | Technical Info (Duration/Bitrate/etc.) | Validation |
283
+ | ---------- | ---------------- | ------------------------------------------ | -------------------------------------- | -------------------- |
284
+ | **ID3v1** | Custom (Python) | Custom (Python) | mutagen (Python) | N/A |
285
+ | **ID3v2** | mutagen (Python) | mutagen (Python) / id3v2/mid3v2 (external) | mutagen (Python) | N/A |
286
+ | **Vorbis** | Custom (Python) | metaflac (external) | mutagen (Python) | flac (external tool) |
287
+ | **RIFF** | mutagen (Python) | Custom (Python) | ffprobe (external tool) | N/A |
288
+
289
+ **Notes:**
290
+
291
+ - **ID3v2**: Uses external tools (`id3v2` or `mid3v2`) for writing to FLAC files to prevent file corruption
292
+ - **Vorbis**: Uses `metaflac` external tool for writing to preserve proper uppercase key casing and avoid file corruption
293
+ - **External tools required**: `metaflac`, `id3v2`/`mid3v2` (for FLAC files), `ffprobe`, `flac`
294
+
295
+ ## 🚀 Getting Started
296
+
297
+ ### What You Need
298
+
299
+ - Python 3.12, 3.13, or 3.14
300
+ - Audio files (MP3, FLAC, WAV)
301
+ - Basic Python knowledge
302
+
303
+ ### Your First Steps
304
+
305
+ 1. **Install the library** using pip
306
+ 2. **Try reading metadata** from an existing audio file
307
+ 3. **Update some metadata** to see how writing works
308
+ 4. **Explore advanced features** like format-specific operations
309
+
310
+ ### Common Use Cases
311
+
312
+ - **Music library management**: Organize and clean up metadata
313
+ - **Metadata cleanup**: Remove unwanted or duplicate information
314
+ - **Format conversion**: Migrate metadata between formats
315
+ - **Batch processing**: Update multiple files at once
316
+ - **Privacy protection**: Remove personal information from files
317
+
318
+ ## ⚡ Quick Start
319
+
320
+ ### Reading Metadata
321
+
322
+ When reading metadata, there are three functions to use: `get_unified_metadata` and `get_unified_metadata_field`, and `get_full_metadata`.
323
+
324
+ - `get_unified_metadata`: Reads all metadata from a file and returns a unified dictionary.
325
+ - `get_unified_metadata_field`: Reads a specific metadata field from a file.
326
+ - `get_full_metadata`: Reads all metadata from a file and returns a dictionary including headers and technical info.
327
+
328
+ #### Reading from a specific metadata format
329
+
330
+ The library supports reading metadata from specific formats (ID3v1, ID3v2.3, ID3v2.4, Vorbis, RIFF). This is useful when you know the format of the file you are working with and you want to read only from that format.
331
+
332
+ ```python
333
+ from audiometa import get_unified_metadata, UnifiedMetadataKey
334
+ from audiometa.utils.MetadataFormat import MetadataFormat
335
+
336
+ metadata = get_unified_metadata("path/to/your/audio.mp3", metadata_format=MetadataFormat.ID3V2)
337
+ print(f"Title: {metadata.get(UnifiedMetadataKey.TITLE, 'Unknown')}")
338
+ ```
339
+
340
+ When specifying a metadata format not supported by the audio format of the file, raises a MetadataFormatNotSupportedByAudioFormatError.
341
+
342
+ ```python
343
+ from audiometa import get_unified_metadata, UnifiedMetadataKey
344
+ from audiometa.utils.MetadataFormat import MetadataFormat
345
+ from audiometa.exceptions import MetadataFormatNotSupportedByAudioFormatError
346
+
347
+ try:
348
+ metadata = get_unified_metadata("path/to/your/audio.mp3", metadata_format=MetadataFormat.RIFF)
349
+ except MetadataFormatNotSupportedByAudioFormatError as e:
350
+ print(f"Error: {e}")
351
+ ```
352
+
353
+ #### Reading All Metadata
354
+
355
+ **`get_unified_metadata(file_path, metadata_format=None)`**
356
+
357
+ Reads all metadata from a file and returns a unified dictionary.
358
+ If `metadata_format` is specified, reads only from that format.
359
+ If not specified, uses priority order across all formats.
360
+
361
+ **Note:** `file_path` can be a string or `pathlib.Path` object.
362
+
363
+ ```python
364
+ from audiometa import get_unified_metadata
365
+
366
+ metadata = get_unified_metadata("path/to/your/audio.mp3")
367
+ print(f"Title: {metadata.get(UnifiedMetadataKey.TITLE, 'Unknown')}")
368
+ print(f"Artist: {metadata.get(UnifiedMetadataKey.ARTISTS, ['Unknown'])}")
369
+ print(f"Album: {metadata.get(UnifiedMetadataKey.ALBUM, 'Unknown')}")
370
+ ```
371
+
372
+ #### Reading Specific Metadata Fields (Quick Start)
373
+
374
+ **`get_unified_metadata_field(file_path, field, metadata_format=None)`**
375
+
376
+ Reads a specific metadata field. If no metadata format is specified, uses priority order across all formats.
377
+
378
+ **Note:** `file_path` can be a string or `pathlib.Path` object.
379
+
380
+ **Note:** The `field` parameter can be a `UnifiedMetadataKey` enum instance or a string matching an enum value (e.g., `"title"`). Invalid values will raise `MetadataFieldNotSupportedByLibError`.
381
+
382
+ ```python
383
+ from audiometa import get_unified_metadata_field, UnifiedMetadataKey
384
+
385
+ # Get title using priority order (all formats)
386
+ title = get_unified_metadata_field("song.mp3", UnifiedMetadataKey.TITLE)
387
+ ```
388
+
389
+ If `metadata_format` is specified, reads only from that format.
390
+
391
+ ```python
392
+ from audiometa import get_unified_metadata_field, UnifiedMetadataKey
393
+ from audiometa.utils.MetadataFormat import MetadataFormat
394
+
395
+ # Get raw rating from specific format only
396
+ id3v2_rating = get_unified_metadata_field("song.mp3", UnifiedMetadataKey.RATING, metadata_format=MetadataFormat.ID3V2, id3v2_version=(2, 4, 0))
397
+ ```
398
+
399
+ If `metadata_format` is specified and the field is not supported by that format, raises a MetadataFieldNotSupportedError.
400
+
401
+ ```python
402
+ from audiometa import get_unified_metadata_field, UnifiedMetadataKey
403
+ from audiometa.utils.MetadataFormat import MetadataFormat
404
+ from audiometa.exceptions import MetadataFieldNotSupportedByMetadataFormatError
405
+
406
+ # Attempt to get unsupported field from specific format
407
+
408
+ try:
409
+ riff_bpm = get_unified_metadata_field("song.wav", UnifiedMetadataKey.BPM, metadata_format=MetadataFormat.RIFF)
410
+ except MetadataFieldNotSupportedByMetadataFormatError as e:
411
+ print(f"Error: {e}")
412
+ ```
413
+
414
+ #### Reading Full Metadata From All Formats Including Headers and Technical Info
415
+
416
+ **`get_full_metadata(file_path, include_headers=True, include_technical=True)`**
417
+
418
+ Gets comprehensive metadata including all available information from a file, including headers and technical details even when no metadata is present.
419
+
420
+ **Note:** `file_path` can be a string or `pathlib.Path` object.
421
+
422
+ ```python
423
+ from audiometa import get_full_metadata
424
+
425
+ full_metadata = get_full_metadata("song.mp3")
426
+ ```
427
+
428
+ ### Validate Metadata Before Update
429
+
430
+ Before updating metadata in a file, it's recommended to validate your metadata to catch errors early:
431
+
432
+ ```python
433
+ from audiometa import validate_metadata_for_update, UnifiedMetadataKey
434
+
435
+ # Validate metadata before updating
436
+ metadata = {
437
+ UnifiedMetadataKey.TITLE: 'New Song Title',
438
+ UnifiedMetadataKey.ARTISTS: ['Artist Name'],
439
+ UnifiedMetadataKey.ALBUM: 'Album Name',
440
+ UnifiedMetadataKey.RATING: 85,
441
+ }
442
+
443
+ try:
444
+ validate_metadata_for_update(metadata)
445
+ print("Metadata is valid!")
446
+ except Exception as e:
447
+ print(f"Metadata validation failed: {e}")
448
+ ```
449
+
450
+ This validation checks for:
451
+
452
+ - **Type correctness**: Ensures values match expected types (strings for title, lists for artists, etc.)
453
+ - **Format rules**: Validates field formats (e.g., release dates must be in ISO 8601 format)
454
+ - **Value ranges**: Checks ratings, track numbers, and other numeric values are within valid ranges
455
+ - **Empty values**: Verifies at least one field is provided
456
+
457
+ See [Pre-Update Validation Function](#pre-update-validation-function) for detailed validation rules and examples.
458
+
459
+ ### Writing Metadata
460
+
461
+ ```python
462
+ from audiometa import update_metadata
463
+
464
+ # Update metadata (use UnifiedMetadataKey for explicit typing)
465
+ from audiometa.utils.UnifiedMetadataKey import UnifiedMetadataKey
466
+
467
+ new_metadata = {
468
+ UnifiedMetadataKey.TITLE: 'New Song Title',
469
+ UnifiedMetadataKey.ARTISTS: ['Artist Name'],
470
+ UnifiedMetadataKey.ALBUM: 'Album Name',
471
+ UnifiedMetadataKey.RATING: 85,
472
+ }
473
+ update_metadata("path/to/your/audio.mp3", new_metadata)
474
+ ```
475
+
476
+ **Format-specific Writing**
477
+
478
+ ```python
479
+ from audiometa.utils.MetadataFormat import MetadataFormat
480
+ update_metadata("song.wav", new_metadata, metadata_format=MetadataFormat.RIFF)
481
+ ```
482
+
483
+ ### Deleting Metadata
484
+
485
+ There are two ways to remove metadata from audio files:
486
+
487
+ #### Delete All Metadata (Complete Removal)
488
+
489
+ ```python
490
+ from audiometa import delete_all_metadata
491
+
492
+ # Delete ALL metadata from ALL supported formats (removes metadata headers entirely)
493
+ success = delete_all_metadata("path/to/your/audio.mp3")
494
+ print(f"All metadata deleted: {success}")
495
+
496
+ # Delete metadata from specific format only
497
+ from audiometa.utils.MetadataFormat import MetadataFormat
498
+ success = delete_all_metadata("song.wav", metadata_format=MetadataFormat.ID3V2)
499
+ # This removes only ID3v2 tags, keeps RIFF metadata
500
+ ```
501
+
502
+ **Important**: This function removes the metadata headers/containers entirely from the file, not just the content. This means:
503
+
504
+ - ID3v2 tag structure is completely removed
505
+ - Vorbis comment blocks are completely removed
506
+ - RIFF INFO chunks are completely removed
507
+ - File size is significantly reduced
508
+
509
+ #### Remove Specific Fields (Selective Removal)
510
+
511
+ ```python
512
+ from audiometa import update_metadata, UnifiedMetadataKey
513
+
514
+ # Remove only specific fields by setting them to None
515
+ update_metadata("path/to/your/audio.mp3", {
516
+ UnifiedMetadataKey.TITLE: None, # Remove title field
517
+ UnifiedMetadataKey.ARTISTS: None # Remove artist field
518
+ # Other fields remain unchanged
519
+ })
520
+
521
+ # This removes only the specified fields while keeping:
522
+ # - Other metadata fields intact
523
+ # - Metadata headers/containers in place
524
+ # - File size mostly unchanged
525
+ ```
526
+
527
+ **When to use each approach:**
528
+
529
+ - **`delete_all_metadata()`**: When you want to completely strip all metadata from a file
530
+ - **Setting fields to `None`**: When you want to clean up specific fields while preserving others
531
+
532
+ #### Comparison Table
533
+
534
+ | Aspect | `delete_all_metadata()` | Setting fields to `None` |
535
+ | -------------------- | ------------------------- | ----------------------------- |
536
+ | **Scope** | Removes ALL metadata | Removes only specified fields |
537
+ | **Metadata headers** | **Completely removed** | **Preserved** |
538
+ | **File size** | Significantly reduced | Minimal change |
539
+ | **Other fields** | All removed | Unchanged |
540
+ | **Use case** | Complete cleanup | Selective cleanup |
541
+ | **Performance** | Faster (single operation) | Slower (field-by-field) |
542
+
543
+ #### Example Scenarios
544
+
545
+ **Scenario 1: Complete Privacy Cleanup**
546
+
547
+ ```python
548
+ # Remove ALL metadata for privacy
549
+ delete_all_metadata("personal_recording.mp3")
550
+ # Result: File has no metadata headers at all (ID3v2 tags completely removed)
551
+ ```
552
+
553
+ **Scenario 2: Clean Up Specific Information**
554
+
555
+ ```python
556
+ # Remove only personal info, keep technical metadata
557
+ update_metadata("song.mp3", {
558
+ UnifiedMetadataKey.TITLE: None, # Remove title
559
+ UnifiedMetadataKey.ARTISTS: None, # Remove artist
560
+ # Keep album, genre, year, etc.
561
+ })
562
+ # Result: File keeps metadata headers but removes specific fields
563
+ ```
564
+
565
+ ### Getting Technical Information
566
+
567
+ The library provides functional APIs for getting technical information about audio files:
568
+
569
+ ```python
570
+ from audiometa import get_duration_in_sec, get_bitrate, get_sample_rate, get_channels, get_file_size, is_audio_file
571
+
572
+ # Check if a file is a valid audio file before processing
573
+ if is_audio_file("path/to/your/audio.flac"):
574
+ # Get technical information using functional API (recommended)
575
+ duration = get_duration_in_sec("path/to/your/audio.flac")
576
+ bitrate = get_bitrate("path/to/your/audio.flac")
577
+ sample_rate = get_sample_rate("path/to/your/audio.flac")
578
+ channels = get_channels("path/to/your/audio.flac")
579
+ file_size = get_file_size("path/to/your/audio.flac")
580
+
581
+ print(f"Duration: {duration} seconds")
582
+ print(f"Bitrate: {bitrate} bps ({bitrate // 1000} kbps)")
583
+ print(f"Sample Rate: {sample_rate} Hz")
584
+ print(f"Channels: {channels}")
585
+ print(f"File Size: {file_size} bytes")
586
+ else:
587
+ print("File is not a valid audio file")
588
+ ```
589
+
590
+ ## 📚 Core API Reference
591
+
592
+ ### Reading Metadata (API Reference)
593
+
594
+ #### Reading Priorities (Tag Precedence)
595
+
596
+ When the same metadata tag exists in multiple formats within the same file, the library follows file-specific precedence orders for reading:
597
+
598
+ #### FLAC Files Reading Priorities
599
+
600
+ 1. **Vorbis** (highest precedence)
601
+ 2. **ID3v2**
602
+ 3. **ID3v1** (lowest precedence, legacy format)
603
+
604
+ #### MP3 Files Reading Priorities
605
+
606
+ 1. **ID3v2** (highest precedence)
607
+ 2. **ID3v1** (lowest precedence, legacy format)
608
+
609
+ #### WAV Files Reading Priorities
610
+
611
+ 1. **RIFF** (highest precedence)
612
+ 2. **ID3v2**
613
+ 3. **ID3v1** (lowest precedence, legacy format)
614
+
615
+ **Examples**:
616
+
617
+ - For MP3 files: If a title exists in both ID3v1 and ID3v2, the ID3v2 title will be returned.
618
+ - For WAV files: If a title exists in both RIFF and ID3v2, the RIFF title will be returned.
619
+ - For FLAC files: If a title exists in both Vorbis and ID3v2, the Vorbis title will be returned.
620
+
621
+ #### Reading All Metadata From All Metadata Formats Including Priority Logic
622
+
623
+ **`get_unified_metadata(file_path, metadata_format=None)`**
624
+
625
+ Reads all metadata from a file and returns a unified dictionary.
626
+ If `metadata_format` is specified, reads only from that format.
627
+ If not specified, uses priority order across all formats.
628
+
629
+ **Note:** `file_path` can be a string or `pathlib.Path` object.
630
+
631
+ ```python
632
+ from audiometa import get_unified_metadata
633
+
634
+ # Read all metadata (unified across all formats)
635
+ metadata = get_unified_metadata("song.mp3")
636
+ print(metadata[UnifiedMetadataKey.TITLE]) # Song title
637
+ print(metadata[UnifiedMetadataKey.ARTISTS]) # List of artists
638
+ ```
639
+
640
+ #### Reading All Metadata From A Specific Format
641
+
642
+ **`get_unified_metadata(file_path, metadata_format=MetadataFormat.ID3V2)`**
643
+
644
+ ```python
645
+
646
+ # Read only ID3v2 metadata
647
+ from audiometa.utils.MetadataFormat import MetadataFormat
648
+ id3v2_metadata = get_unified_metadata("song.mp3", metadata_format=MetadataFormat.ID3V2)
649
+
650
+ # Read only Vorbis metadata
651
+ vorbis_metadata = get_unified_metadata("song.flac", metadata_format=MetadataFormat.VORBIS)
652
+ ```
653
+
654
+ #### Reading All Metadata From A ID3v2 Format With Version
655
+
656
+ **`get_unified_metadata(file_path, metadata_format=MetadataFormat.ID3V2), id3v2_version=(2, 3, 0))`**
657
+
658
+ ```python
659
+
660
+ # Read only ID3v2.3 metadata
661
+ from audiometa.utils.MetadataFormat import MetadataFormat
662
+ id3v2_3_metadata = get_unified_metadata("song.mp3", metadata_format=MetadataFormat.ID3V2, id3v2_version=(2, 3, 0))
663
+
664
+ # Read only ID3v2.4 metadata
665
+ id3v2_4_metadata = get_unified_metadata("song.mp3", metadata_format=MetadataFormat.ID3V2, id3v2_version=(2, 4, 0))
666
+ ```
667
+
668
+ #### Reading Specific Metadata Fields
669
+
670
+ **`get_unified_metadata_field(file_path, field, metadata_format=None)`**
671
+
672
+ Reads a specific metadata field. If `metadata_format` is specified, reads only from that format; otherwise uses priority order across all formats.
673
+
674
+ **Note:** `file_path` can be a string or `pathlib.Path` object.
675
+
676
+ ```python
677
+ from audiometa import get_unified_metadata_field, UnifiedMetadataKey
678
+ from audiometa.utils.MetadataFormat import MetadataFormat
679
+
680
+ # Get title using priority order (all formats)
681
+ title = get_unified_metadata_field("song.mp3", UnifiedMetadataKey.TITLE)
682
+
683
+ # Get raw rating from specific format only
684
+ id3v2_rating = get_unified_metadata_field("song.mp3", UnifiedMetadataKey.RATING, metadata_format=MetadataFormat.ID3V2)
685
+ ```
686
+
687
+ #### Reading Full Metadata From All Formats Including Headers and Technical Info
688
+
689
+ **`get_full_metadata(file_path, include_headers=True, include_technical=True)`**
690
+
691
+ Gets comprehensive metadata including all available information from a file, including headers and technical details even when no metadata is present.
692
+
693
+ **Note:** `file_path` can be a string or `pathlib.Path` object.
694
+
695
+ This function provides the most complete view of an audio file by combining:
696
+
697
+ - All metadata from all supported formats (ID3v1, ID3v2, Vorbis, RIFF)
698
+ - Technical information (duration, bitrate, sample rate, channels, file size)
699
+ - Format-specific headers and structure information
700
+ - Raw metadata details from each format
701
+
702
+ ```python
703
+ from audiometa import get_full_metadata, UnifiedMetadataKey
704
+
705
+ # Get complete metadata including headers and technical info
706
+ full_metadata = get_full_metadata("song.mp3")
707
+
708
+ # Access unified metadata (same as get_unified_metadata)
709
+ print(f"Title: {full_metadata['unified_metadata'][UnifiedMetadataKey.TITLE]}")
710
+ print(f"Artists: {full_metadata['unified_metadata'][UnifiedMetadataKey.ARTISTS]}")
711
+
712
+ # Access technical information
713
+ print(f"Duration: {full_metadata['technical_info']['duration_seconds']} seconds")
714
+ print(f"Bitrate: {full_metadata['technical_info']['bitrate_bps']} bps ({full_metadata['technical_info']['bitrate_bps'] // 1000} kbps)")
715
+ print(f"Sample Rate: {full_metadata['technical_info']['sample_rate_hz']} Hz")
716
+ print(f"Channels: {full_metadata['technical_info']['channels']}")
717
+ print(f"File Size: {full_metadata['technical_info']['file_size_bytes']} bytes")
718
+
719
+ # Access format-specific metadata
720
+ print(f"ID3v2 Title: {full_metadata['metadata_format']['id3v2']['title']}")
721
+ print(f"Vorbis Title: {full_metadata['metadata_format']['vorbis']['title']}")
722
+
723
+ # Access header information
724
+ print(f"ID3v2 Version: {full_metadata['headers']['id3v2']['version']}")
725
+ print(f"ID3v2 Header Size: {full_metadata['headers']['id3v2']['header_size_bytes']}")
726
+ print(f"Has ID3v1 Header: {full_metadata['headers']['id3v1']['present']}")
727
+ print(f"RIFF Chunk Info: {full_metadata['headers']['riff']['chunk_info']}")
728
+
729
+ # Access raw metadata details
730
+ print(f"Raw ID3v2 Frames: {full_metadata['raw_metadata']['id3v2']['frames']}")
731
+ print(f"Raw Vorbis Comments: {full_metadata['raw_metadata']['vorbis']['comments']}")
732
+ ```
733
+
734
+ **Parameters:**
735
+
736
+ - `file_path`: Path to the audio file (str or Path)
737
+ - `include_headers`: Whether to include format-specific header information (default: True)
738
+ - `include_technical`: Whether to include technical audio information (default: True)
739
+
740
+ **Returns:**
741
+ A comprehensive dictionary containing:
742
+
743
+ ```python
744
+ {
745
+ 'unified_metadata': {
746
+ # Same as get_unified_metadata() result
747
+ 'title': 'Song Title',
748
+ 'artists': ['Artist 1', 'Artist 2'],
749
+ 'album_name': 'Album Name',
750
+ # ... all other metadata fields
751
+ },
752
+ 'technical_info': {
753
+ 'duration_seconds': 180.5,
754
+ 'bitrate_bps': 320000,
755
+ 'sample_rate_hz': 44100,
756
+ 'channels': 2,
757
+ 'file_size_bytes': 7234567,
758
+ 'file_extension': '.mp3',
759
+ 'audio_format_name': 'MP3',
760
+ 'is_flac_md5_valid': None, # Only for FLAC files
761
+ },
762
+ 'metadata_format': {
763
+ 'id3v1': {
764
+ # ID3v1 specific metadata (if present)
765
+ 'title': 'Song Title',
766
+ 'artist': 'Artist Name',
767
+ # ... other ID3v1 fields
768
+ },
769
+ 'id3v2': {
770
+ # ID3v2 specific metadata (if present)
771
+ 'title': 'Song Title',
772
+ 'artists': ['Artist 1', 'Artist 2'],
773
+ # ... other ID3v2 fields
774
+ },
775
+ 'vorbis': {
776
+ # Vorbis specific metadata (if present)
777
+ 'title': 'Song Title',
778
+ 'artists': ['Artist 1', 'Artist 2'],
779
+ # ... other Vorbis fields
780
+ },
781
+ 'riff': {
782
+ # RIFF specific metadata (if present)
783
+ 'title': 'Song Title',
784
+ 'artist': 'Artist Name',
785
+ # ... other RIFF fields
786
+ }
787
+ },
788
+ 'headers': {
789
+ 'id3v1': {
790
+ 'present': True,
791
+ 'position': 'end_of_file',
792
+ 'size_bytes': 128,
793
+ 'version': '1.1',
794
+ 'has_track_number': True
795
+ },
796
+ 'id3v2': {
797
+ 'present': True,
798
+ 'version': '2.3.0',
799
+ 'header_size_bytes': 2048,
800
+ 'flags': {...},
801
+ 'extended_header': {...}
802
+ },
803
+ 'vorbis': {
804
+ 'present': True,
805
+ 'vendor_string': 'reference libFLAC 1.3.2',
806
+ 'comment_count': 15,
807
+ 'block_size': 4096
808
+ },
809
+ 'riff': {
810
+ 'present': True,
811
+ 'chunk_info': {
812
+ 'riff_chunk_size': 7234000,
813
+ 'info_chunk_size': 1024,
814
+ 'audio_format': 'PCM',
815
+ 'subchunk_size': 7232000
816
+ }
817
+ }
818
+ },
819
+ 'raw_metadata': {
820
+ 'id3v1': {
821
+ 'raw_data': b'...', # Raw 128-byte ID3v1 tag
822
+ 'parsed_fields': {...}
823
+ },
824
+ 'id3v2': {
825
+ 'frames': {...}, # Raw ID3v2 frames
826
+ 'raw_header': b'...'
827
+ },
828
+ 'vorbis': {
829
+ 'comments': {...}, # Raw Vorbis comment blocks
830
+ 'vendor_string': '...'
831
+ },
832
+ 'riff': {
833
+ 'info_chunk': {...}, # Raw RIFF INFO chunk data
834
+ 'chunk_structure': {...}
835
+ }
836
+ },
837
+ 'format_priorities': {
838
+ 'file_extension': '.mp3',
839
+ 'reading_order': ['id3v2', 'id3v1'],
840
+ 'writing_format': 'id3v2'
841
+ }
842
+ }
843
+ ```
844
+
845
+ **Use Cases:**
846
+
847
+ - **Complete file analysis**: Get everything about an audio file in one call
848
+ - **Debugging metadata issues**: Inspect raw headers and format-specific data
849
+ - **Format migration**: Understand what metadata exists in each format before converting
850
+ - **File validation**: Check header integrity and format compliance
851
+ - **Metadata forensics**: Analyze metadata structure and detect anomalies
852
+ - **Batch processing**: Get comprehensive information for multiple files efficiently
853
+
854
+ **Examples:**
855
+
856
+ ```python
857
+ # Basic usage - get everything
858
+ full_info = get_full_metadata("song.mp3")
859
+
860
+ # Get only metadata without technical details
861
+ metadata_only = get_full_metadata("song.mp3", include_technical=False)
862
+
863
+ # Get only technical info without headers
864
+ tech_only = get_full_metadata("song.mp3", include_headers=False)
865
+
866
+ # Check if file has specific format headers
867
+ if full_info['headers']['id3v2']['present']:
868
+ print("File has ID3v2 tags")
869
+ print(f"ID3v2 version: {full_info['headers']['id3v2']['version']}")
870
+
871
+ # Compare metadata across formats
872
+ id3v2_title = full_info['metadata_format']['id3v2'].get('title')
873
+ vorbis_title = full_info['metadata_format']['vorbis'].get('title')
874
+ if id3v2_title != vorbis_title:
875
+ print("Title differs between ID3v2 and Vorbis")
876
+
877
+ # Analyze file structure
878
+ print(f"File size: {full_info['technical_info']['file_size_bytes']} bytes")
879
+ print(f"Metadata overhead: {full_info['headers']['id3v2']['header_size_bytes']} bytes")
880
+ print(f"Audio data ratio: {(full_info['technical_info']['file_size_bytes'] - full_info['headers']['id3v2']['header_size_bytes']) / full_info['technical_info']['file_size_bytes'] * 100:.1f}%")
881
+ ```
882
+
883
+ ### Pre-Update Validation (API Reference)
884
+
885
+ Before updating metadata, the library provides validation to ensure your data is correct:
886
+
887
+ **Validation Rules**
888
+
889
+ The library validates metadata value types and formats when keys are provided as `UnifiedMetadataKey` instances:
890
+
891
+ - `None` values are allowed and indicate field removal.
892
+ - For fields whose expected type is `list[...]` (for example `ARTISTS` or `GENRES_NAMES`) the validator accepts only lists. Each list element is checked against the expected inner type (e.g., `str` for `ARTISTS`).
893
+ - For plain types (`str`, `int`, etc.) the value must be an instance of that type.
894
+ - On type mismatch the library raises `InvalidMetadataFieldTypeError`.
895
+ - **Rating Validation**: See [Rating Validation Rules](#rating-validation-rules) for detailed rules on rating values.
896
+ - **Release Date Validation**: See [Release Date Validation Rules](#release-date-validation-rules) for detailed rules on release date formats.
897
+
898
+ Note: The validator uses the `UnifiedMetadataKey` enum to determine expected types. String keys that match `UnifiedMetadataKey` enum values (e.g., `"title"`, `"artists"`) are automatically converted to enum instances and validated. You can use either string keys or `UnifiedMetadataKey` enum instances - both are validated the same way. Using `UnifiedMetadataKey` enum instances provides better IDE support and type checking.
899
+
900
+ **`validate_metadata_for_update(unified_metadata, normalized_rating_max_value=None)`**
901
+
902
+ Validates unified metadata values before updating metadata in a file. Validates that a metadata dictionary contains at least one field and validates types, formats, and values (rating, release date, track number) if present. For detailed validation rules, see [Rating Validation Rules](#rating-validation-rules) and [Release Date Validation Rules](#release-date-validation-rules).
903
+
904
+ ```python
905
+ from audiometa import validate_metadata_for_update, UnifiedMetadataKey
906
+
907
+ # Valid metadata
908
+ validate_metadata_for_update({UnifiedMetadataKey.TITLE: "Song Title"})
909
+
910
+ # Valid: empty string is allowed (represents setting field to empty)
911
+ validate_metadata_for_update({UnifiedMetadataKey.TITLE: ""})
912
+
913
+ # Valid: None value is allowed (represents field removal)
914
+ validate_metadata_for_update({UnifiedMetadataKey.TITLE: None})
915
+
916
+ # Valid: empty list is allowed
917
+ validate_metadata_for_update({UnifiedMetadataKey.ARTISTS: []})
918
+
919
+ # Valid: list with None values is allowed (None values will be filtered during writing)
920
+ validate_metadata_for_update({UnifiedMetadataKey.ARTISTS: [None, None]})
921
+
922
+ # Valid: rating with normalization (see Rating Validation Rules for details)
923
+ validate_metadata_for_update({UnifiedMetadataKey.RATING: 50}, normalized_rating_max_value=100)
924
+
925
+ # Invalid: negative rating
926
+ validate_metadata_for_update({UnifiedMetadataKey.RATING: -1})
927
+ # Raises: InvalidRatingValueError
928
+
929
+ # Valid: release date
930
+ validate_metadata_for_update({UnifiedMetadataKey.RELEASE_DATE: "2024-01-01"})
931
+
932
+ # Invalid: invalid release date format
933
+ validate_metadata_for_update({UnifiedMetadataKey.RELEASE_DATE: "2024/01/01"})
934
+ # Raises: InvalidMetadataFieldFormatError
935
+ ```
936
+
937
+ ### Writing Metadata (API Reference)
938
+
939
+ For validation before writing, see [Pre-Update Validation (API Reference)](#pre-update-validation-api-reference).
940
+
941
+ #### Metadata Dictionary Structure
942
+
943
+ When writing, metadata should be provided as a dictionary with keys corresponding to unified metadata fields defined in `UnifiedMetadataKey`.
944
+
945
+ ```python
946
+ metadata = {
947
+ UnifiedMetadataKey.TITLE: 'Song Title',
948
+ UnifiedMetadataKey.ARTISTS: ['Artist 1', 'Artist 2'],
949
+ UnifiedMetadataKey.ALBUM: 'Album Name',
950
+ UnifiedMetadataKey.YEAR: 2024,
951
+ UnifiedMetadataKey.GENRES_NAMES: ['Rock'],
952
+ UnifiedMetadataKey.RATING: 85,
953
+ UnifiedMetadataKey.BPM: 120,
954
+ UnifiedMetadataKey.COMMENT: 'Some comments here',
955
+ }
956
+ ```
957
+
958
+ **`update_metadata(file_path, metadata, **options)`\*\*
959
+
960
+ Updates metadata in a file. The function automatically calls pre-update validation on the metadata before writing (see [Pre-Update Validation](#pre-update-validation) for validation rules).
961
+
962
+ **Note:** `file_path` can be a string or `pathlib.Path` object.
963
+
964
+ ```python
965
+ from audiometa import update_metadata
966
+
967
+ # Basic writing (recommended: use UnifiedMetadataKey constants)
968
+ from audiometa.utils.UnifiedMetadataKey import UnifiedMetadataKey
969
+
970
+ update_metadata("song.mp3", {
971
+ UnifiedMetadataKey.TITLE: 'New Title',
972
+ UnifiedMetadataKey.ARTISTS: ['Artist Name'],
973
+ UnifiedMetadataKey.RATING: 85
974
+ })
975
+
976
+ # Format-specific writing
977
+ from audiometa.utils.MetadataFormat import MetadataFormat
978
+ update_metadata("song.wav", metadata, metadata_format=MetadataFormat.RIFF)
979
+
980
+ # Advanced examples
981
+
982
+ # Write to a specific ID3v2 version (e.g., ID3v2.4)
983
+ from audiometa.utils.MetadataFormat import MetadataFormat
984
+ update_metadata(
985
+ "song.mp3",
986
+ metadata,
987
+ metadata_format=MetadataFormat.ID3V2,
988
+ id3v2_version=(2, 4, 0)
989
+ )
990
+
991
+ # Write to ID3v2.3 (default)
992
+ update_metadata(
993
+ "song.mp3",
994
+ metadata,
995
+ metadata_format=MetadataFormat.ID3V2
996
+ )
997
+
998
+ # Use writing strategy and specify ID3v2 version
999
+ from audiometa.utils.MetadataWritingStrategy import MetadataWritingStrategy
1000
+ update_metadata(
1001
+ "song.mp3",
1002
+ metadata,
1003
+ metadata_strategy=MetadataWritingStrategy.SYNC,
1004
+ id3v2_version=(2, 4, 0)
1005
+ )
1006
+
1007
+ """
1008
+ Note: The `id3v2_version` parameter lets you choose which ID3v2 version to target (e.g., (2, 3, 0) for ID3v2.3, (2, 4, 0) for ID3v2.4). This affects how multi-value fields and certain metadata are written.
1009
+ """
1010
+ # Strategy-based writing
1011
+ from audiometa.utils.MetadataWritingStrategy import MetadataWritingStrategy
1012
+ update_metadata("song.mp3", metadata, metadata_strategy=MetadataWritingStrategy.CLEANUP)
1013
+ ```
1014
+
1015
+ ##### Writing Defaults by Audio Format
1016
+
1017
+ The library automatically selects appropriate default metadata formats for different audio file types:
1018
+
1019
+ #### MP3 Files (ID3v2)
1020
+
1021
+ - **Default Format**: ID3v2.4
1022
+ - **Why ID3v2.4?**: Most compatible with modern software and supports Unicode
1023
+ - **Fallback**: If ID3v2.4 writing fails, automatically falls back to ID3v2.3
1024
+
1025
+ #### FLAC Files (Vorbis Comments)
1026
+
1027
+ - **Default Format**: Vorbis Comments
1028
+ - **Why Vorbis?**: Native format for FLAC files, full Unicode support
1029
+
1030
+ #### WAV Files (RIFF INFO)
1031
+
1032
+ - **Default Format**: RIFF INFO chunks
1033
+ - **Why RIFF?**: Native format for WAV files, widely supported
1034
+
1035
+ #### ID3v2 Version Selection
1036
+
1037
+ When writing to MP3 files, the library intelligently selects the best ID3v2 version:
1038
+
1039
+ ```python
1040
+ from audiometa import update_metadata
1041
+
1042
+ # The library automatically chooses ID3v2.3 for MP3 files for best compatibility
1043
+ update_metadata("song.mp3", {"title": "Song Title"})
1044
+
1045
+ # You can override the version if needed
1046
+ from audiometa.utils.MetadataFormat import MetadataFormat
1047
+ update_metadata("song.mp3", {"title": "Song Title"},
1048
+ metadata_format=MetadataFormat.ID3V2_4) # Force ID3v2.4
1049
+ ```
1050
+
1051
+ #### Writing Strategies
1052
+
1053
+ The library provides flexible control over how metadata is written to files that may already contain metadata in other formats.
1054
+
1055
+ ##### Available Strategies
1056
+
1057
+ 1. **`SYNC` (Default)**: Write to native format and synchronize other metadata formats that are already present
1058
+ 2. **`PRESERVE`**: Write to native format only, preserve existing metadata in other formats
1059
+ 3. **`CLEANUP`**: Write to native format and remove all non-native metadata formats
1060
+
1061
+ ##### Usage Examples
1062
+
1063
+ ```python
1064
+ from audiometa import update_metadata
1065
+ from audiometa.utils.MetadataWritingStrategy import MetadataWritingStrategy
1066
+
1067
+ # SYNC strategy (default) - synchronize all existing formats
1068
+ update_metadata("song.wav", {"title": "New Title"},
1069
+ metadata_strategy=MetadataWritingStrategy.SYNC)
1070
+
1071
+ # CLEANUP strategy - remove non-native formats
1072
+ update_metadata("song.wav", {"title": "New Title"},
1073
+ metadata_strategy=MetadataWritingStrategy.CLEANUP)
1074
+
1075
+ # PRESERVE strategy - keep other formats unchanged
1076
+ update_metadata("song.wav", {"title": "New Title"},
1077
+ metadata_strategy=MetadataWritingStrategy.PRESERVE)
1078
+ ```
1079
+
1080
+ ##### Default Behavior
1081
+
1082
+ By default, the library uses the **SYNC strategy** which writes metadata to the native format and synchronizes other metadata formats that are already present. This provides the best user experience by writing metadata where possible and handling unsupported fields gracefully.
1083
+
1084
+ - **MP3 files**: Writes to ID3v2 and syncs other formats
1085
+ - **FLAC files**: Writes to Vorbis comments and syncs other formats
1086
+ - **WAV files**: Writes to RIFF and syncs other formats
1087
+
1088
+ #### Forced Format Behavior
1089
+
1090
+ When you specify a `metadata_format` parameter, you **cannot** also specify a `metadata_strategy`:
1091
+
1092
+ - **Write only to the specified format**: Other formats are left completely untouched
1093
+ - **Fail fast on unsupported fields**: Raises `MetadataFieldNotSupportedByMetadataFormatError` for any unsupported metadata
1094
+ - **Predictable behavior**: No side effects on other metadata formats
1095
+
1096
+ ```python
1097
+ # Correct usage - specify only the format
1098
+ update_metadata("song.mp3", metadata,
1099
+ metadata_format=MetadataFormat.RIFF) # Writes only to RIFF, ignores ID3v2
1100
+
1101
+ # This will raise MetadataWritingConflictParametersError - cannot specify both parameters
1102
+ update_metadata("song.mp3", metadata,
1103
+ metadata_format=MetadataFormat.RIFF,
1104
+ metadata_strategy=MetadataWritingStrategy.CLEANUP) # Raises MetadataWritingConflictParametersError
1105
+ ```
1106
+
1107
+ #### Usage Examples
1108
+
1109
+ **Default Behavior (SYNC strategy)**
1110
+
1111
+ ```python
1112
+ from audiometa import update_metadata
1113
+
1114
+ # WAV file with existing ID3v1 tags (30-char limit)
1115
+ update_metadata("song.wav", {"title": "This is a Very Long Title That Exceeds ID3v1 Limits"})
1116
+
1117
+ # Result:
1118
+ # - RIFF tags: Updated with full title (native format)
1119
+ # - ID3v1 tags: Synchronized with truncated title (30 chars max)
1120
+ # - When reading: RIFF title is returned (higher precedence)
1121
+ # Note: ID3v1 title becomes "This is a Very Long Title Th" (truncated)
1122
+ ```
1123
+
1124
+ **CLEANUP Strategy - Remove Non-Native Formats**
1125
+
1126
+ ```python
1127
+ from audiometa import update_metadata
1128
+ from audiometa.utils.MetadataWritingStrategy import MetadataWritingStrategy
1129
+
1130
+ # Clean up WAV file - remove ID3v2, keep only RIFF
1131
+ update_metadata("song.wav", {"title": "New Title"},
1132
+ metadata_strategy=MetadataWritingStrategy.CLEANUP)
1133
+
1134
+ # Result:
1135
+ # - ID3v2 tags: Removed completely
1136
+ # - RIFF tags: Updated with new metadata
1137
+ # - When reading: Only RIFF metadata available
1138
+ ```
1139
+
1140
+ **SYNC Strategy - Synchronize All Existing Formats**
1141
+
1142
+ ```python
1143
+ # Synchronize all existing metadata formats with same values
1144
+ update_metadata("song.wav", {"title": "New Title"},
1145
+ metadata_strategy=MetadataWritingStrategy.SYNC)
1146
+
1147
+ # Result:
1148
+ # - RIFF tags: Synchronized with new metadata (native format)
1149
+ # - ID3v2 tags: Synchronized with new metadata (if present)
1150
+ # - ID3v1 tags: Synchronized with new metadata (if present)
1151
+ # - When reading: RIFF title is returned (highest precedence)
1152
+ # Note: SYNC preserves and updates ALL existing metadata formats
1153
+ ```
1154
+
1155
+ **Format-Specific Writing**
1156
+
1157
+ ```python
1158
+ from audiometa.utils.MetadataFormat import MetadataFormat
1159
+
1160
+ # Write specifically to ID3v2 format (even for WAV files)
1161
+ update_metadata("song.wav", {"title": "New Title"},
1162
+ metadata_format=MetadataFormat.ID3V2)
1163
+
1164
+ # Write specifically to RIFF format
1165
+ update_metadata("song.wav", {"title": "New Title"},
1166
+ metadata_format=MetadataFormat.RIFF)
1167
+ ```
1168
+
1169
+ ### Deleting Metadata (API Reference)
1170
+
1171
+ #### Delete All Metadata From All Formats
1172
+
1173
+ Deletes all metadata from all supported formats for the file type.
1174
+
1175
+ **`delete_all_metadata(file_path, metadata_format=None)`**
1176
+
1177
+ **Note:** `file_path` can be a string or `pathlib.Path` object.
1178
+
1179
+ ```python
1180
+ from audiometa import delete_all_metadata
1181
+
1182
+ # Delete all metadata from all supported formats for the file type
1183
+ delete_all_metadata("song.mp3")
1184
+ ```
1185
+
1186
+ #### Delete All Metadata From A Specific Format
1187
+
1188
+ Deletes all metadata from a specific format.
1189
+
1190
+ **`delete_all_metadata(file_path, metadata_format=MetadataFormat.ID3V2)`**
1191
+
1192
+ **Note:** `file_path` can be a string or `pathlib.Path` object.
1193
+
1194
+ ```python
1195
+ from audiometa import delete_all_metadata
1196
+
1197
+ # Delete all metadata from a specific format
1198
+ delete_all_metadata("song.mp3", metadata_format=MetadataFormat.ID3V2)
1199
+ ```
1200
+
1201
+ When specifying a metadata format not supported by the audio format of the file, raises a MetadataFormatNotSupportedByAudioFormatError.
1202
+
1203
+ ```python
1204
+ from audiometa import delete_all_metadata
1205
+ from audiometa.utils.MetadataFormat import MetadataFormat
1206
+ from audiometa.exceptions import MetadataFormatNotSupportedByAudioFormatError
1207
+
1208
+ try:
1209
+ delete_all_metadata("path/to/your/audio.mp3", metadata_format=MetadataFormat.RIFF)
1210
+ except MetadataFormatNotSupportedByAudioFormatError as e:
1211
+ print(f"Error: {e}")
1212
+ ```
1213
+
1214
+ The library provides specific exception types for different error conditions:
1215
+
1216
+ ```python
1217
+ from audiometa.exceptions import (
1218
+ FileCorruptedError,
1219
+ FileTypeNotSupportedError,
1220
+ MetadataFieldNotSupportedByMetadataFormatError,
1221
+ InvalidMetadataFieldTypeError,
1222
+ InvalidMetadataFieldFormatError,
1223
+ AudioFileMetadataParseError
1224
+ )
1225
+
1226
+ try:
1227
+ metadata = get_unified_metadata("invalid_file.txt")
1228
+ except FileTypeNotSupportedError:
1229
+ print("File format not supported")
1230
+ except FileCorruptedError:
1231
+ print("File is corrupted")
1232
+ except MetadataFieldNotSupportedByMetadataFormatError:
1233
+ print("Metadata field not supported for this format")
1234
+ except InvalidMetadataFieldTypeError:
1235
+ print("Invalid metadata field type")
1236
+ except InvalidMetadataFieldFormatError:
1237
+ print("Invalid metadata field format (e.g., date format)")
1238
+ except AudioFileMetadataParseError:
1239
+ print("Failed to parse audio file metadata")
1240
+ ```
1241
+
1242
+ ### Error Handling (API Reference)
1243
+
1244
+ The library provides comprehensive exception handling for all operations. All library functions can raise specific exception types that help you handle errors appropriately.
1245
+
1246
+ **For comprehensive exception documentation**, including detailed explanations, common causes, and examples for all exceptions, see the dedicated guide:
1247
+
1248
+ **[Error Handling Guide: Exceptions and Error Management](docs/ERROR_HANDLING_GUIDE.md)**
1249
+
1250
+ #### Quick Reference: Common Exceptions
1251
+
1252
+ The library defines custom exceptions organized into categories:
1253
+
1254
+ **File-Related Exceptions:**
1255
+
1256
+ - `FileCorruptedError` - Base exception for file corruption errors
1257
+ - `FlacMd5CheckFailedError` - FLAC MD5 checksum verification failed
1258
+ - `FileByteMismatchError` - File bytes don't match expected content
1259
+ - `InvalidChunkDecodeError` - Chunk cannot be decoded
1260
+ - `DurationNotFoundError` - Audio duration cannot be determined
1261
+ - `AudioFileMetadataParseError` - Metadata parsing from external tools failed
1262
+ - `FileTypeNotSupportedError` - File type not supported (only `.mp3`, `.flac`, `.wav`)
1263
+
1264
+ **Metadata Format Exceptions:**
1265
+
1266
+ - `MetadataFormatNotSupportedByAudioFormatError` - Format not supported for audio type
1267
+ - `MetadataFieldNotSupportedByMetadataFormatError` - Field not supported by format
1268
+ - `MetadataFieldNotSupportedByLibError` - Field not supported by library
1269
+ - `MetadataWritingConflictParametersError` - Conflicting parameters specified
1270
+
1271
+ **Validation Exceptions:**
1272
+
1273
+ - `InvalidMetadataFieldTypeError` - Invalid field type (e.g., string instead of list)
1274
+ - `InvalidMetadataFieldFormatError` - Invalid field format (e.g., date format)
1275
+ - `InvalidRatingValueError` - Invalid rating value
1276
+
1277
+ **Configuration Exceptions:**
1278
+
1279
+ - `ConfigurationError` - Configuration error in metadata manager
1280
+
1281
+ **Standard Python Exceptions:**
1282
+
1283
+ - `FileNotFoundError` - File does not exist
1284
+ - `IOError`, `OSError`, `PermissionError` - System-level I/O errors
1285
+
1286
+ #### Basic Exception Handling Example
1287
+
1288
+ ```python
1289
+ from audiometa import get_unified_metadata, update_metadata
1290
+ from audiometa.exceptions import (
1291
+ FileTypeNotSupportedError,
1292
+ FileCorruptedError,
1293
+ MetadataFieldNotSupportedByMetadataFormatError,
1294
+ InvalidMetadataFieldTypeError,
1295
+ )
1296
+
1297
+ try:
1298
+ metadata = get_unified_metadata("song.mp3")
1299
+ except FileTypeNotSupportedError:
1300
+ print("File format not supported")
1301
+ except FileCorruptedError:
1302
+ print("File is corrupted")
1303
+ except FileNotFoundError:
1304
+ print("File not found")
1305
+
1306
+ try:
1307
+ update_metadata("song.mp3", {"title": "New Title"})
1308
+ except InvalidMetadataFieldTypeError:
1309
+ print("Invalid metadata field type")
1310
+ except MetadataFieldNotSupportedByMetadataFormatError:
1311
+ print("Field not supported for this format")
1312
+ except PermissionError:
1313
+ print("Permission denied")
1314
+ ```
1315
+
1316
+ #### Exception Handling for Mutagen Operations
1317
+
1318
+ The library uses mutagen internally and wraps all mutagen operations with proper exception handling. Mutagen-specific exceptions are converted to `FileCorruptedError` with descriptive messages, while standard I/O exceptions (`IOError`, `OSError`, `PermissionError`) are re-raised as-is.
1319
+
1320
+ See the **[Error Handling Guide](docs/ERROR_HANDLING_GUIDE.md)** for detailed information about mutagen exception handling and all exception types.
1321
+
1322
+ ## 📖 Metadata Guide
1323
+
1324
+ ### Metadata Field Guide: Support and Handling
1325
+
1326
+ For a comprehensive reference on metadata field support and handling across all audio formats (ID3v1, ID3v2, Vorbis, RIFF), including multiple values, genres, ratings, track numbers, release dates, and lyrics support, see the dedicated guide:
1327
+
1328
+ **[Metadata Field Guide: Support and Handling](docs/METADATA_FIELD_GUIDE.md)**
1329
+
1330
+ ### Audio Technical Info Guide
1331
+
1332
+ For information about audio information (duration, bitrate, sample rate, channels, file size, format info, MD5 checksum validation and repair), see the dedicated guide:
1333
+
1334
+ **[Audio Technical Info Guide](docs/AUDIO_TECHNICAL_INFO_GUIDE.md)**
1335
+
1336
+ ### Error Handling Guide
1337
+
1338
+ For comprehensive documentation on all exceptions that can be raised by the library, including detailed explanations, common causes, usage examples, and mutagen exception handling, see the dedicated guide:
1339
+
1340
+ **[Error Handling Guide: Exceptions and Error Management](docs/ERROR_HANDLING_GUIDE.md)**
1341
+
1342
+ ### Unsupported Metadata Handling
1343
+
1344
+ The library handles unsupported metadata consistently across all strategies:
1345
+
1346
+ - **Forced format** (when `metadata_format` is specified): Always fails fast by raising `MetadataFieldNotSupportedByMetadataFormatError` for any unsupported field. **No writing is performed** - the file remains completely unchanged.
1347
+ - **All strategies (SYNC, PRESERVE, CLEANUP) with `fail_on_unsupported_field=False` (default)**: Handle unsupported fields gracefully by logging warnings and continuing with supported fields
1348
+ - **All strategies (SYNC, PRESERVE, CLEANUP) with `fail_on_unsupported_field=True`**: Fails fast if any field is not supported by the target format. **No writing is performed** - the file remains completely unchanged (atomic operation).
1349
+
1350
+ #### Format-Specific Limitations
1351
+
1352
+ | Format | Forced Format | All Strategies with `fail_on_unsupported_field=False` | All Strategies with `fail_on_unsupported_field=True` |
1353
+ | -------------- | --------------------------------- | ----------------------------------------------------------- | ---------------------------------------------------- |
1354
+ | **RIFF (WAV)** | Always fails fast, **no writing** | Logs warnings for unsupported fields, writes supported ones | Fails fast for unsupported fields, **no writing** |
1355
+ | **ID3v1** | Always fails fast, **no writing** | Logs warnings for unsupported fields, writes supported ones | Fails fast for unsupported fields, **no writing** |
1356
+ | **ID3v2** | Always fails fast, **no writing** | All fields supported | All fields supported |
1357
+ | **Vorbis** | Always fails fast, **no writing** | All fields supported | All fields supported |
1358
+
1359
+ #### Atomic Write Operations
1360
+
1361
+ When `fail_on_unsupported_field=True` is used, the library ensures **atomic write operations**:
1362
+
1363
+ - **All-or-nothing behavior**: Either all metadata is written successfully, or nothing is written at all
1364
+ - **File integrity**: If any field is unsupported, the file remains completely unchanged
1365
+ - **No partial updates**: Prevents inconsistent metadata states where only some fields are updated
1366
+ - **Error safety**: Ensures that failed operations don't leave files in a partially modified state
1367
+
1368
+ #### Example: Handling Unsupported Metadata
1369
+
1370
+ ```python
1371
+ from audiometa import update_metadata
1372
+ from audiometa.exceptions import MetadataFieldNotSupportedByMetadataFormatError
1373
+ from audiometa.utils.MetadataFormat import MetadataFormat
1374
+ from audiometa.utils.MetadataWritingStrategy import MetadataWritingStrategy
1375
+
1376
+ # All strategies - handle unsupported fields gracefully with warnings
1377
+ update_metadata("song.wav", {"title": "Song", "rating": 85, "bpm": 120})
1378
+ # Result: Writes title and rating to RIFF, logs warning about BPM, continues
1379
+
1380
+ update_metadata("song.wav", {"title": "Song", "rating": 85, "bpm": 120},
1381
+ metadata_strategy=MetadataWritingStrategy.PRESERVE)
1382
+ # Result: Writes title and rating to RIFF, logs warning about BPM, preserves other formats
1383
+
1384
+ update_metadata("song.wav", {"title": "Song", "rating": 85, "bpm": 120},
1385
+ metadata_strategy=MetadataWritingStrategy.CLEANUP)
1386
+ # Result: Writes title and rating to RIFF, logs warning about BPM, removes other formats
1387
+
1388
+ # Forced format - always fails fast for unsupported fields, no writing performed
1389
+ try:
1390
+ update_metadata("song.wav", {"title": "Song", "rating": 85, "bpm": 120},
1391
+ metadata_format=MetadataFormat.RIFF)
1392
+ except MetadataFieldNotSupportedByMetadataFormatError as e:
1393
+ print(f"BPM not supported in RIFF format: {e}")
1394
+ # File remains completely unchanged - no metadata was written
1395
+
1396
+ # Strategies with fail_on_unsupported_field=True - atomic operation, no writing on failure
1397
+ try:
1398
+ update_metadata("song.wav", {"title": "Song", "rating": 85, "bpm": 120},
1399
+ metadata_strategy=MetadataWritingStrategy.SYNC,
1400
+ fail_on_unsupported_field=True)
1401
+ except MetadataFieldNotSupportedByMetadataFormatError as e:
1402
+ print(f"BPM not supported: {e}")
1403
+ # File remains completely unchanged - no metadata was written (atomic operation)
1404
+
1405
+ # Practical example: Demonstrating atomic behavior
1406
+ from audiometa import get_unified_metadata
1407
+
1408
+ # File with existing metadata
1409
+ original_metadata = get_unified_metadata("song.wav")
1410
+ print(f"Original title: {original_metadata.get('title')}") # e.g., "Original Title"
1411
+
1412
+ # Attempt to write metadata with unsupported field
1413
+ try:
1414
+ update_metadata("song.wav", {
1415
+ "title": "New Title", # This would be supported
1416
+ "rating": 85, # This would be supported
1417
+ "bpm": 120 # This is NOT supported by RIFF format
1418
+ }, fail_on_unsupported_field=True)
1419
+ except MetadataFieldNotSupportedByMetadataFormatError:
1420
+ pass
1421
+
1422
+ # Verify file is unchanged (atomic behavior)
1423
+ final_metadata = get_unified_metadata("song.wav")
1424
+ print(f"Final title: {final_metadata.get('title')}") # Still "Original Title" - no changes made
1425
+ ```
1426
+
1427
+ ## 💻 Command Line Interface
1428
+
1429
+ AudioMeta provides a powerful command-line interface for quick metadata operations without writing Python code.
1430
+
1431
+ ### Installation {#cli-installation}
1432
+
1433
+ After installing the package, the `audiometa` command will be available:
1434
+
1435
+ ```bash
1436
+ pip install audiometa-python
1437
+ audiometa --help
1438
+ ```
1439
+
1440
+ ### Basic Usage
1441
+
1442
+ #### Reading Metadata {#cli-reading-metadata}
1443
+
1444
+ ```bash
1445
+ # Read full metadata from a file
1446
+ audiometa read song.mp3
1447
+
1448
+ # Read unified metadata only (simplified output)
1449
+ audiometa unified song.mp3
1450
+
1451
+ # Read multiple files
1452
+ audiometa read *.mp3
1453
+
1454
+ # Process directory recursively
1455
+ audiometa read music/ --recursive
1456
+
1457
+ # Output in different formats
1458
+ audiometa read song.mp3 --format table
1459
+ audiometa read song.mp3 --format yaml
1460
+ audiometa read song.mp3 --output metadata.json
1461
+ ```
1462
+
1463
+ #### Writing Metadata {#cli-writing-metadata}
1464
+
1465
+ ```bash
1466
+ # Write basic metadata
1467
+ audiometa write song.mp3 --title "New Title" --artist "Artist Name"
1468
+
1469
+ # Write multiple fields
1470
+ audiometa write song.mp3 --title "Song Title" --artist "Artist" --album "Album" --year "2024" --rating 85
1471
+
1472
+ # Update multiple files
1473
+ audiometa write *.mp3 --artist "New Artist"
1474
+
1475
+ # Force a specific metadata format
1476
+ audiometa write song.mp3 --title "New Title" --force-format id3v2
1477
+ audiometa write song.flac --title "New Title" --force-format vorbis
1478
+ audiometa write song.wav --title "New Title" --force-format riff
1479
+ ```
1480
+
1481
+ ##### Force Format {#cli-force-format}
1482
+
1483
+ The `--force-format` parameter allows you to write metadata to a specific format, regardless of the file's native format priority. Available formats: `id3v2`, `id3v1`, `vorbis`, `riff`.
1484
+
1485
+ ```bash
1486
+ # Force writing to a specific metadata format
1487
+ audiometa write song.mp3 --title "New Title" --force-format id3v2
1488
+ audiometa write song.flac --title "New Title" --force-format vorbis
1489
+ audiometa write song.wav --title "New Title" --force-format riff
1490
+ ```
1491
+
1492
+ **Note:** The format must be supported by the file type. For example, MP3 files support `id3v2` and `id3v1`, but not `vorbis` or `riff`.
1493
+
1494
+ #### Deleting Metadata {#cli-deleting-metadata}
1495
+
1496
+ ```bash
1497
+ # Delete all metadata from a file
1498
+ audiometa delete song.mp3
1499
+
1500
+ # Delete metadata from multiple files
1501
+ audiometa delete *.mp3
1502
+ ```
1503
+
1504
+ ### Advanced Options
1505
+
1506
+ #### Output Control
1507
+
1508
+ ```bash
1509
+ # Exclude technical information
1510
+ audiometa read song.mp3 --no-technical
1511
+
1512
+ # Exclude header information
1513
+ audiometa read song.mp3 --no-headers
1514
+
1515
+ # Save to file
1516
+ audiometa read song.mp3 --output metadata.json
1517
+ ```
1518
+
1519
+ #### Error Handling {#cli-error-handling}
1520
+
1521
+ ```bash
1522
+ # Continue processing other files on error
1523
+ audiometa read *.mp3 --continue-on-error
1524
+ ```
1525
+
1526
+ #### Batch Processing
1527
+
1528
+ ```bash
1529
+ # Process all audio files in a directory
1530
+ audiometa read music/ --recursive
1531
+
1532
+ # Process specific file patterns
1533
+ audiometa read "**/*.mp3" --recursive
1534
+ ```
1535
+
1536
+ ### Output Formats
1537
+
1538
+ - **JSON** (default): Structured data for programmatic use
1539
+ - **YAML**: Human-readable structured format (requires PyYAML)
1540
+ - **Table**: Simple text table format
1541
+
1542
+ ### Examples
1543
+
1544
+ ```bash
1545
+ # Quick metadata check
1546
+ audiometa unified song.mp3 --format table
1547
+
1548
+ # Batch metadata update
1549
+ audiometa write music/ --recursive --artist "Various Artists"
1550
+
1551
+ # Export metadata for analysis
1552
+ audiometa read music/ --recursive --format json --output all_metadata.json
1553
+
1554
+ # Clean up metadata
1555
+ audiometa delete music/ --recursive
1556
+ ```
1557
+
1558
+ ## 🤝 Contributing
1559
+
1560
+ Contributions are welcome and greatly appreciated! 🎉
1561
+
1562
+ Whether you're fixing bugs, adding features, improving documentation, or sharing feedback, your help makes AudioMeta Python better for everyone.
1563
+
1564
+ **Ways to contribute:**
1565
+
1566
+ - 🐛 **Report bugs** - Use the [bug report template](https://github.com/Andreas-Garcia/audiometa/issues/new?template=bug_report.yml)
1567
+ - 💡 **Suggest features** - Use the [feature request template](https://github.com/Andreas-Garcia/audiometa/issues/new?template=feature_request.yml)
1568
+ - 🔧 **Submit pull requests** - See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines
1569
+ - 📝 **Improve documentation** - Fix typos, clarify explanations, add examples
1570
+ - 💬 **Join discussions** - Share ideas and help others in [GitHub Discussions](https://github.com/Andreas-Garcia/audiometa/discussions)
1571
+ - ⭐ **Star the repo** - Help others discover the project
1572
+
1573
+ For detailed contribution guidelines, see [CONTRIBUTING.md](CONTRIBUTING.md).
1574
+
1575
+ **Quick start for code contributions:**
1576
+
1577
+ 1. Fork the repository
1578
+ 2. Create a `feature/` branch
1579
+ 3. Make your changes
1580
+ 4. Run tests: `pytest`
1581
+ 5. Submit a pull request
1582
+
1583
+ Thank you for contributing! 🙏
1584
+
1585
+ ## 📄 License
1586
+
1587
+ This project is licensed under the Apache License 2.0 - see the LICENSE file for details.
1588
+
1589
+ The Apache 2.0 license provides patent protection, which helps prevent contributors and users from facing patent litigation from other contributors. This makes it a safer choice for both individual contributors and organizations compared to licenses without explicit patent grants.
1590
+
1591
+ ## 📝 Changelog
1592
+
1593
+ See [CHANGELOG.md](CHANGELOG.md) for a detailed list of changes.