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,207 @@
1
+ """Tests for get_full_metadata function options (include_headers, include_technical)."""
2
+
3
+ from pathlib import Path
4
+
5
+ import pytest
6
+
7
+ from audiometa import get_full_metadata
8
+
9
+
10
+ @pytest.mark.integration
11
+ class TestGetFullMetadataOptions:
12
+ def test_get_full_metadata_exclude_headers(self, sample_mp3_file: Path):
13
+ result = get_full_metadata(sample_mp3_file, include_headers=False)
14
+
15
+ # Should include basic structure
16
+ assert "unified_metadata" in result
17
+ assert "technical_info" in result
18
+ assert "metadata_format" in result
19
+ assert "format_priorities" in result
20
+
21
+ # Headers should be empty dict when excluded
22
+ assert "headers" in result
23
+ assert result["headers"] == {}
24
+
25
+ # Raw metadata should be empty dict when excluded
26
+ assert "raw_metadata" in result
27
+ assert result["raw_metadata"] == {}
28
+
29
+ # Verify technical info is still included
30
+ assert result["technical_info"] != {}
31
+ assert "duration_seconds" in result["technical_info"]
32
+ assert "bitrate_bps" in result["technical_info"]
33
+
34
+ # Verify unified metadata is still included
35
+ assert isinstance(result["unified_metadata"], dict)
36
+
37
+ # Verify format metadata is still included
38
+ assert isinstance(result["metadata_format"], dict)
39
+ assert "id3v2" in result["metadata_format"]
40
+ assert "id3v1" in result["metadata_format"]
41
+
42
+ def test_get_full_metadata_exclude_technical(self, sample_mp3_file: Path):
43
+ result = get_full_metadata(sample_mp3_file, include_technical=False)
44
+
45
+ # Should include basic structure
46
+ assert "unified_metadata" in result
47
+ assert "metadata_format" in result
48
+ assert "headers" in result
49
+ assert "raw_metadata" in result
50
+ assert "format_priorities" in result
51
+
52
+ # Technical info should be empty dict when excluded
53
+ assert "technical_info" in result
54
+ assert result["technical_info"] == {}
55
+
56
+ # Verify headers are still included
57
+ assert result["headers"] != {}
58
+ assert "id3v2" in result["headers"]
59
+ assert "id3v1" in result["headers"]
60
+
61
+ # Verify raw metadata is still included
62
+ assert result["raw_metadata"] != {}
63
+ assert "id3v2" in result["raw_metadata"]
64
+ assert "id3v1" in result["raw_metadata"]
65
+
66
+ # Verify unified metadata is still included
67
+ assert isinstance(result["unified_metadata"], dict)
68
+
69
+ # Verify format metadata is still included
70
+ assert isinstance(result["metadata_format"], dict)
71
+
72
+ def test_get_full_metadata_exclude_both_headers_and_technical(self, sample_mp3_file: Path):
73
+ result = get_full_metadata(sample_mp3_file, include_headers=False, include_technical=False)
74
+
75
+ # Should include basic structure
76
+ assert "unified_metadata" in result
77
+ assert "metadata_format" in result
78
+ assert "format_priorities" in result
79
+
80
+ # Headers should be empty dict when excluded
81
+ assert "headers" in result
82
+ assert result["headers"] == {}
83
+
84
+ # Raw metadata should be empty dict when excluded
85
+ assert "raw_metadata" in result
86
+ assert result["raw_metadata"] == {}
87
+
88
+ # Technical info should be empty dict when excluded
89
+ assert "technical_info" in result
90
+ assert result["technical_info"] == {}
91
+
92
+ # Verify unified metadata is still included
93
+ assert isinstance(result["unified_metadata"], dict)
94
+
95
+ # Verify format metadata is still included
96
+ assert isinstance(result["metadata_format"], dict)
97
+
98
+ def test_get_full_metadata_exclude_headers_flac(self, sample_flac_file: Path):
99
+ result = get_full_metadata(sample_flac_file, include_headers=False)
100
+
101
+ # Should include basic structure
102
+ assert "unified_metadata" in result
103
+ assert "technical_info" in result
104
+ assert "metadata_format" in result
105
+ assert "format_priorities" in result
106
+
107
+ # Headers should be empty dict when excluded
108
+ assert "headers" in result
109
+ assert result["headers"] == {}
110
+
111
+ # Raw metadata should be empty dict when excluded
112
+ assert "raw_metadata" in result
113
+ assert result["raw_metadata"] == {}
114
+
115
+ # Verify technical info is still included
116
+ assert result["technical_info"] != {}
117
+ assert "is_flac_md5_valid" in result["technical_info"]
118
+
119
+ # Verify format metadata is still included
120
+ assert isinstance(result["metadata_format"], dict)
121
+ assert "vorbis" in result["metadata_format"]
122
+
123
+ def test_get_full_metadata_exclude_technical_flac(self, sample_flac_file: Path):
124
+ result = get_full_metadata(sample_flac_file, include_technical=False)
125
+
126
+ # Should include basic structure
127
+ assert "unified_metadata" in result
128
+ assert "metadata_format" in result
129
+ assert "headers" in result
130
+ assert "raw_metadata" in result
131
+ assert "format_priorities" in result
132
+
133
+ # Technical info should be empty dict when excluded
134
+ assert "technical_info" in result
135
+ assert result["technical_info"] == {}
136
+
137
+ # Verify headers are still included
138
+ assert result["headers"] != {}
139
+ assert "vorbis" in result["headers"]
140
+
141
+ # Verify raw metadata is still included
142
+ assert result["raw_metadata"] != {}
143
+ assert "vorbis" in result["raw_metadata"]
144
+
145
+ def test_get_full_metadata_exclude_headers_wav(self, sample_wav_file: Path):
146
+ result = get_full_metadata(sample_wav_file, include_headers=False)
147
+
148
+ # Should include basic structure
149
+ assert "unified_metadata" in result
150
+ assert "technical_info" in result
151
+ assert "metadata_format" in result
152
+ assert "format_priorities" in result
153
+
154
+ # Headers should be empty dict when excluded
155
+ assert "headers" in result
156
+ assert result["headers"] == {}
157
+
158
+ # Raw metadata should be empty dict when excluded
159
+ assert "raw_metadata" in result
160
+ assert result["raw_metadata"] == {}
161
+
162
+ # Verify technical info is still included
163
+ assert result["technical_info"] != {}
164
+ assert result["technical_info"]["file_extension"] == ".wav"
165
+
166
+ # Verify format metadata is still included
167
+ assert isinstance(result["metadata_format"], dict)
168
+ assert "riff" in result["metadata_format"]
169
+
170
+ def test_get_full_metadata_exclude_technical_wav(self, sample_wav_file: Path):
171
+ result = get_full_metadata(sample_wav_file, include_technical=False)
172
+
173
+ # Should include basic structure
174
+ assert "unified_metadata" in result
175
+ assert "metadata_format" in result
176
+ assert "headers" in result
177
+ assert "raw_metadata" in result
178
+ assert "format_priorities" in result
179
+
180
+ # Technical info should be empty dict when excluded
181
+ assert "technical_info" in result
182
+ assert result["technical_info"] == {}
183
+
184
+ # Verify headers are still included
185
+ assert result["headers"] != {}
186
+ assert "riff" in result["headers"]
187
+
188
+ # Verify raw metadata is still included
189
+ assert result["raw_metadata"] != {}
190
+ assert "riff" in result["raw_metadata"]
191
+
192
+ def test_get_full_metadata_exclude_options(self, sample_mp3_file: Path):
193
+ result = get_full_metadata(sample_mp3_file, include_headers=False, include_technical=False)
194
+
195
+ # Should work the same as with path
196
+ assert "unified_metadata" in result
197
+ assert "metadata_format" in result
198
+ assert "format_priorities" in result
199
+
200
+ # Excluded sections should be empty
201
+ assert result["headers"] == {}
202
+ assert result["raw_metadata"] == {}
203
+ assert result["technical_info"] == {}
204
+
205
+ # Included sections should have data
206
+ assert isinstance(result["unified_metadata"], dict)
207
+ assert isinstance(result["metadata_format"], dict)
@@ -0,0 +1,95 @@
1
+ """Performance tests for get_full_metadata function."""
2
+
3
+ import threading
4
+ from pathlib import Path
5
+
6
+ import pytest
7
+
8
+ from audiometa import get_full_metadata
9
+
10
+
11
+ @pytest.mark.integration
12
+ class TestGetFullMetadataPerformance:
13
+ def test_get_full_metadata_performance_with_headers_disabled(self, sample_mp3_file: Path):
14
+ result = get_full_metadata(sample_mp3_file, include_headers=False)
15
+
16
+ # Should still work correctly
17
+ assert "unified_metadata" in result
18
+ assert "technical_info" in result
19
+ assert "metadata_format" in result
20
+
21
+ # Headers should be minimal
22
+ headers = result["headers"]
23
+ for _metadata_format_name, header_info in headers.items():
24
+ # Should have basic structure but minimal data
25
+ assert "present" in header_info
26
+
27
+ def test_get_full_metadata_performance_with_technical_disabled(self, sample_mp3_file: Path):
28
+ result = get_full_metadata(sample_mp3_file, include_technical=False)
29
+
30
+ # Should still work correctly
31
+ assert "unified_metadata" in result
32
+ assert "metadata_format" in result
33
+ assert "headers" in result
34
+
35
+ # Technical info should be minimal
36
+ tech_info = result["technical_info"]
37
+ assert isinstance(tech_info, dict)
38
+
39
+ def test_get_full_metadata_memory_usage(self, sample_mp3_file: Path):
40
+ # This is more of a smoke test to ensure no obvious memory leaks
41
+ for _ in range(10):
42
+ result = get_full_metadata(sample_mp3_file)
43
+
44
+ # Should complete successfully each time
45
+ assert "unified_metadata" in result
46
+ assert "technical_info" in result
47
+
48
+ # Clear result to help with memory management
49
+ del result
50
+
51
+ def test_get_full_metadata_concurrent_access(self, sample_mp3_file: Path):
52
+ results = []
53
+ errors = []
54
+
55
+ def get_metadata():
56
+ try:
57
+ result = get_full_metadata(sample_mp3_file)
58
+ results.append(result)
59
+ except Exception as e:
60
+ errors.append(e)
61
+
62
+ # Create multiple threads accessing the same file
63
+ threads = []
64
+ for _ in range(5):
65
+ thread = threading.Thread(target=get_metadata)
66
+ threads.append(thread)
67
+ thread.start()
68
+
69
+ # Wait for all threads to complete
70
+ for thread in threads:
71
+ thread.join()
72
+
73
+ # Should have 5 successful results
74
+ assert len(results) == 5
75
+ assert len(errors) == 0
76
+
77
+ # All results should be identical
78
+ first_result = results[0]
79
+ for result in results[1:]:
80
+ assert result["format_priorities"] == first_result["format_priorities"]
81
+ assert result["technical_info"]["file_size_bytes"] == first_result["technical_info"]["file_size_bytes"]
82
+
83
+ def test_get_full_metadata_performance_optimization(self, sample_mp3_file: Path):
84
+ """Test that performance optimization flags work correctly."""
85
+ # Test with minimal data
86
+ result_minimal = get_full_metadata(sample_mp3_file, include_headers=False, include_technical=False)
87
+
88
+ # Should still have basic structure
89
+ assert "unified_metadata" in result_minimal
90
+ assert "metadata_format" in result_minimal
91
+ assert "format_priorities" in result_minimal
92
+
93
+ # Headers and technical info should be minimal
94
+ assert "headers" in result_minimal
95
+ assert "technical_info" in result_minimal
@@ -0,0 +1,128 @@
1
+ """Integration tests for RIFF bext chunk extraction via get_full_metadata.
2
+
3
+ This file is kept separate from other format tests because bext is a sub-feature
4
+ (BWF extension) of RIFF, not a separate metadata format. It tests:
5
+
6
+ - bext-specific chunk extraction via get_full_metadata
7
+ - bext chunk structure in chunk_structure
8
+ - Consistency between manager and get_full_metadata for bext
9
+ - Edge cases specific to bext (e.g., extraction without INFO metadata)
10
+
11
+ Unlike other metadata formats (ID3v2, ID3v1, Vorbis) which are tested in general
12
+ structure/format files, bext warrants dedicated tests due to its unique integration
13
+ behavior and specific edge cases.
14
+ """
15
+
16
+ from pathlib import Path
17
+
18
+ import pytest
19
+
20
+ from audiometa import get_full_metadata
21
+ from audiometa.test.helpers.riff.riff_metadata_setter import RIFFMetadataSetter
22
+ from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
23
+
24
+
25
+ @pytest.mark.integration
26
+ class TestRiffBextChunkStructure:
27
+ """Test cases for RIFF bext chunk extraction via get_full_metadata."""
28
+
29
+ def test_bext_chunk_structure_present_in_get_full_metadata(self):
30
+ """Test that bext chunk appears in chunk_structure when present."""
31
+ with temp_file_with_metadata({}, "wav") as test_file:
32
+ RIFFMetadataSetter.set_bext_description(test_file, "Test Description")
33
+ RIFFMetadataSetter.set_bext_originator(test_file, "Test Originator")
34
+
35
+ result = get_full_metadata(test_file)
36
+
37
+ riff_raw = result.get("raw_metadata", {}).get("riff", {})
38
+ assert "chunk_structure" in riff_raw
39
+ assert "bext" in riff_raw["chunk_structure"]
40
+
41
+ bext_data = riff_raw["chunk_structure"]["bext"]
42
+ assert bext_data["Description"] == "Test Description"
43
+ assert bext_data["Originator"] == "Test Originator"
44
+
45
+ def test_bext_chunk_structure_with_all_fields(self):
46
+ """Test bext chunk extraction with all fields via get_full_metadata."""
47
+ with temp_file_with_metadata({}, "wav") as test_file:
48
+ RIFFMetadataSetter.set_bext_metadata(
49
+ test_file,
50
+ {
51
+ "Description": "Test Description",
52
+ "Originator": "Test Originator",
53
+ "OriginatorReference": "REF-12345",
54
+ "OriginationDate": "2024-01-15",
55
+ "OriginationTime": "14:30:00",
56
+ "TimeReference": 44100,
57
+ "CodingHistory": "A=PCM,F=44100,W=16,M=mono,T=PCM",
58
+ },
59
+ )
60
+
61
+ result = get_full_metadata(test_file)
62
+
63
+ riff_raw = result.get("raw_metadata", {}).get("riff", {})
64
+ assert "chunk_structure" in riff_raw
65
+ assert "bext" in riff_raw["chunk_structure"]
66
+
67
+ bext_data = riff_raw["chunk_structure"]["bext"]
68
+ assert bext_data["Description"] == "Test Description"
69
+ assert bext_data["Originator"] == "Test Originator"
70
+ assert bext_data["OriginatorReference"] == "REF-12345"
71
+ assert bext_data["OriginationDate"] == "2024-01-15"
72
+ assert bext_data["OriginationTime"] == "14:30:00"
73
+ assert bext_data["TimeReference"] == 44100
74
+ assert bext_data["CodingHistory"] == "A=PCM,F=44100,W=16,M=mono,T=PCM"
75
+
76
+ def test_bext_chunk_structure_absent_when_not_present(self, sample_wav_file: Path):
77
+ """Test that bext chunk is not present in chunk_structure for regular WAV files."""
78
+ result = get_full_metadata(sample_wav_file)
79
+
80
+ riff_raw = result.get("raw_metadata", {}).get("riff", {})
81
+ assert "chunk_structure" in riff_raw
82
+ # Regular WAV files without bext chunk should not have bext in chunk_structure
83
+ assert "bext" not in riff_raw["chunk_structure"]
84
+
85
+ def test_bext_chunk_structure_consistency_with_manager(self):
86
+ """Test that get_full_metadata returns same bext data as manager.get_raw_metadata_info."""
87
+ from audiometa._audio_file import _AudioFile
88
+ from audiometa.manager._rating_supporting.riff._RiffManager import _RiffManager as RiffManager
89
+
90
+ with temp_file_with_metadata({}, "wav") as test_file:
91
+ RIFFMetadataSetter.set_bext_description(test_file, "Test Description")
92
+ RIFFMetadataSetter.set_bext_originator(test_file, "Test Originator")
93
+ RIFFMetadataSetter.set_bext_time_reference(test_file, 44100)
94
+
95
+ # Get via get_full_metadata
96
+ result = get_full_metadata(test_file)
97
+ bext_from_full = result.get("raw_metadata", {}).get("riff", {}).get("chunk_structure", {}).get("bext")
98
+
99
+ # Get via manager directly
100
+ audio_file = _AudioFile(test_file)
101
+ manager = RiffManager(audio_file)
102
+ raw_info = manager.get_raw_metadata_info()
103
+ bext_from_manager = raw_info.get("chunk_structure", {}).get("bext")
104
+
105
+ # Should be identical
106
+ assert bext_from_full == bext_from_manager
107
+
108
+ def test_bext_chunk_extraction_via_get_full_metadata_without_info_metadata(self):
109
+ """Test that bext chunk is extracted via get_full_metadata even when no user-defined INFO metadata is present.
110
+
111
+ This tests the code path at lines 899-901 in _RiffManager.py that ensures bext chunk
112
+ extraction happens even when raw_clean_metadata is empty (no INFO metadata).
113
+ """
114
+ with temp_file_with_metadata({}, "wav") as test_file:
115
+ # Add bext metadata but no user-defined INFO metadata
116
+ RIFFMetadataSetter.set_bext_description(test_file, "Test Description")
117
+ RIFFMetadataSetter.set_bext_originator(test_file, "Test Originator")
118
+
119
+ result = get_full_metadata(test_file)
120
+
121
+ riff_raw = result.get("raw_metadata", {}).get("riff", {})
122
+ # Should still have bext chunk in chunk_structure regardless of INFO metadata
123
+ assert "chunk_structure" in riff_raw
124
+ assert "bext" in riff_raw["chunk_structure"]
125
+
126
+ bext_data = riff_raw["chunk_structure"]["bext"]
127
+ assert bext_data["Description"] == "Test Description"
128
+ assert bext_data["Originator"] == "Test Originator"
@@ -0,0 +1,161 @@
1
+ """Tests for get_full_metadata function structure validation."""
2
+
3
+ from pathlib import Path
4
+
5
+ import pytest
6
+
7
+ from audiometa import get_full_metadata
8
+ from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
9
+
10
+
11
+ @pytest.mark.integration
12
+ class TestGetFullMetadataStructure:
13
+ def test_get_full_metadata_headers_present_flags(self, sample_mp3_file: Path):
14
+ result = get_full_metadata(sample_mp3_file)
15
+
16
+ # Check ID3v2 headers
17
+ id3v2_headers = result["headers"]["id3v2"]
18
+ assert "present" in id3v2_headers
19
+ assert "version" in id3v2_headers
20
+ assert "header_size_bytes" in id3v2_headers
21
+ assert "flags" in id3v2_headers
22
+ assert "extended_header" in id3v2_headers
23
+
24
+ # Check ID3v1 headers
25
+ id3v1_headers = result["headers"]["id3v1"]
26
+ assert "present" in id3v1_headers
27
+ assert "position" in id3v1_headers
28
+ assert "size_bytes" in id3v1_headers
29
+ assert "version" in id3v1_headers
30
+ assert "has_track_number" in id3v1_headers
31
+
32
+ def test_get_full_metadata_raw_metadata_structure(self, sample_mp3_file: Path):
33
+ result = get_full_metadata(sample_mp3_file)
34
+
35
+ # Check ID3v2 raw metadata
36
+ id3v2_raw = result["raw_metadata"]["id3v2"]
37
+ assert "raw_data" in id3v2_raw
38
+ assert "parsed_fields" in id3v2_raw
39
+ assert "frames" in id3v2_raw
40
+ assert "comments" in id3v2_raw
41
+ assert "chunk_structure" in id3v2_raw
42
+
43
+ # Check ID3v1 raw metadata
44
+ id3v1_raw = result["raw_metadata"]["id3v1"]
45
+ assert "raw_data" in id3v1_raw
46
+ assert "parsed_fields" in id3v1_raw
47
+ assert "frames" in id3v1_raw
48
+ assert "comments" in id3v1_raw
49
+ assert "chunk_structure" in id3v1_raw
50
+
51
+ def test_get_full_metadata_riff_raw_metadata_structure(self, sample_wav_file: Path):
52
+ """Test RIFF raw metadata structure in get_full_metadata."""
53
+ result = get_full_metadata(sample_wav_file)
54
+
55
+ # Check RIFF raw metadata
56
+ riff_raw = result["raw_metadata"]["riff"]
57
+ assert "raw_data" in riff_raw
58
+ assert "parsed_fields" in riff_raw
59
+ assert "frames" in riff_raw
60
+ assert "comments" in riff_raw
61
+ assert "chunk_structure" in riff_raw
62
+
63
+ def test_get_full_metadata_header_detection_accuracy(self, sample_mp3_file: Path):
64
+ result = get_full_metadata(sample_mp3_file)
65
+
66
+ # Check that headers are detected correctly
67
+ headers = result["headers"]
68
+
69
+ for metadata_format_name, header_info in headers.items():
70
+ assert "present" in header_info
71
+ assert isinstance(header_info["present"], bool)
72
+
73
+ if header_info["present"]:
74
+ # If header is present, should have additional info
75
+ if metadata_format_name == "id3v2":
76
+ assert "version" in header_info
77
+ assert "header_size_bytes" in header_info
78
+ elif metadata_format_name == "id3v1":
79
+ assert "position" in header_info
80
+ assert "size_bytes" in header_info
81
+ elif metadata_format_name == "vorbis":
82
+ assert "vendor_string" in header_info
83
+ assert "comment_count" in header_info
84
+ elif metadata_format_name == "riff":
85
+ assert "chunk_info" in header_info
86
+
87
+ def test_id3v1_parsed_fields_use_unified_keys(self, sample_mp3_file: Path):
88
+ result = get_full_metadata(sample_mp3_file)
89
+
90
+ id3v1_raw = result.get("raw_metadata", {}).get("id3v1", {})
91
+ parsed_fields = id3v1_raw.get("parsed_fields", {})
92
+
93
+ # If there are parsed fields, they should use UnifiedMetadataKey enum values as keys
94
+ for key in parsed_fields:
95
+ assert isinstance(
96
+ key, UnifiedMetadataKey
97
+ ), f"ID3v1 parsed_fields key {key} should be UnifiedMetadataKey enum, got {type(key)}"
98
+ # Verify it's a valid UnifiedMetadataKey value
99
+ assert key in UnifiedMetadataKey, f"ID3v1 parsed_fields key {key} is not a valid UnifiedMetadataKey"
100
+
101
+ def test_riff_parsed_fields_use_raw_keys(self, sample_wav_file: Path):
102
+ result = get_full_metadata(sample_wav_file)
103
+
104
+ riff_raw = result.get("raw_metadata", {}).get("riff", {})
105
+ parsed_fields = riff_raw.get("parsed_fields", {})
106
+
107
+ # RIFF should use raw RIFF tag keys (like 'INAM', 'IART', etc.)
108
+ # These are NOT UnifiedMetadataKey enum values, which is correct for RIFF
109
+ for key in parsed_fields:
110
+ assert isinstance(key, str), f"RIFF parsed_fields key {key} should be string, got {type(key)}"
111
+ # RIFF keys should be 4-character codes like 'INAM', 'IART', etc.
112
+ assert len(key) == 4, f"RIFF parsed_fields key {key} should be 4 characters (FourCC), got {len(key)}"
113
+
114
+ def test_cli_output_parsed_fields_keys(self, sample_mp3_file: Path):
115
+ import json
116
+ import subprocess
117
+ import sys
118
+
119
+ result = subprocess.run(
120
+ [sys.executable, "-m", "audiometa", "read", str(sample_mp3_file), "--format", "json"],
121
+ capture_output=True,
122
+ text=True,
123
+ check=False,
124
+ )
125
+
126
+ assert result.returncode == 0, f"CLI failed: {result.stderr}"
127
+
128
+ data = json.loads(result.stdout)
129
+ raw_metadata = data.get("raw_metadata", {})
130
+
131
+ # Check ID3v1 parsed_fields keys in CLI output
132
+ id3v1_raw = raw_metadata.get("id3v1", {})
133
+ parsed_fields = id3v1_raw.get("parsed_fields", {})
134
+
135
+ for key in parsed_fields:
136
+ # In JSON output, UnifiedMetadataKey enum values are serialized as their string values
137
+ # (e.g., "title" instead of "UnifiedMetadataKey.TITLE") because UnifiedMetadataKey inherits from str
138
+ assert isinstance(key, str), f"ID3v1 parsed_fields key in CLI output should be string, got: {type(key)}"
139
+
140
+ # Verify it's a valid UnifiedMetadataKey value
141
+ assert key in [
142
+ e.value for e in UnifiedMetadataKey
143
+ ], f"ID3v1 parsed_fields key {key} is not a valid UnifiedMetadataKey value"
144
+
145
+ def test_parsed_fields_consistency_across_formats(self, sample_mp3_file: Path):
146
+ result = get_full_metadata(sample_mp3_file)
147
+
148
+ raw_metadata = result.get("raw_metadata", {})
149
+
150
+ # Check that all formats have the expected structure
151
+ for metadata_format_name, format_data in raw_metadata.items():
152
+ assert "parsed_fields" in format_data, f"Format {metadata_format_name} should have parsed_fields"
153
+ assert isinstance(
154
+ format_data["parsed_fields"], dict
155
+ ), f"Format {metadata_format_name} parsed_fields should be a dictionary"
156
+
157
+ # Check that parsed_fields values are strings (no binary data)
158
+ for key, value in format_data["parsed_fields"].items():
159
+ assert isinstance(
160
+ value, str
161
+ ), f"Format {metadata_format_name} parsed_fields value for {key} should be string, got {type(value)}"
@@ -0,0 +1,73 @@
1
+ import pytest
2
+
3
+ from audiometa import get_unified_metadata_field, update_metadata
4
+ from audiometa.test.helpers.id3v1 import ID3v1MetadataSetter
5
+ from audiometa.test.helpers.id3v2 import ID3v2MetadataSetter
6
+ from audiometa.test.helpers.riff import RIFFMetadataSetter
7
+ from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
8
+ from audiometa.test.helpers.vorbis import VorbisMetadataSetter
9
+ from audiometa.utils.metadata_format import MetadataFormat
10
+ from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
11
+
12
+
13
+ @pytest.mark.integration
14
+ class TestAlbumDeleting:
15
+ def test_delete_album_id3v2(self):
16
+ with temp_file_with_metadata({}, "mp3") as test_file:
17
+ ID3v2MetadataSetter.set_album(test_file, "Test Album")
18
+ assert get_unified_metadata_field(test_file, UnifiedMetadataKey.ALBUM) == "Test Album"
19
+
20
+ # Delete metadata using library API
21
+ update_metadata(test_file, {UnifiedMetadataKey.ALBUM: None}, metadata_format=MetadataFormat.ID3V2)
22
+ assert get_unified_metadata_field(test_file, UnifiedMetadataKey.ALBUM) is None
23
+
24
+ def test_delete_album_id3v1(self):
25
+ with temp_file_with_metadata({}, "id3v1") as test_file:
26
+ ID3v1MetadataSetter.set_album(test_file, "Test Album")
27
+ assert get_unified_metadata_field(test_file, UnifiedMetadataKey.ALBUM) == "Test Album"
28
+
29
+ # Delete metadata using library API
30
+ update_metadata(test_file, {UnifiedMetadataKey.ALBUM: None}, metadata_format=MetadataFormat.ID3V1)
31
+ assert get_unified_metadata_field(test_file, UnifiedMetadataKey.ALBUM) is None
32
+
33
+ def test_delete_album_riff(self):
34
+ with temp_file_with_metadata({}, "wav") as test_file:
35
+ RIFFMetadataSetter.set_album(test_file, "Test Album")
36
+ assert get_unified_metadata_field(test_file, UnifiedMetadataKey.ALBUM) == "Test Album"
37
+
38
+ # Delete metadata using library API
39
+ update_metadata(test_file, {UnifiedMetadataKey.ALBUM: None}, metadata_format=MetadataFormat.RIFF)
40
+ assert get_unified_metadata_field(test_file, UnifiedMetadataKey.ALBUM) is None
41
+
42
+ def test_delete_album_vorbis(self):
43
+ with temp_file_with_metadata({}, "flac") as test_file:
44
+ VorbisMetadataSetter.set_album(test_file, "Test Album")
45
+ assert get_unified_metadata_field(test_file, UnifiedMetadataKey.ALBUM) == "Test Album"
46
+
47
+ # Delete metadata using library API
48
+ update_metadata(test_file, {UnifiedMetadataKey.ALBUM: None}, metadata_format=MetadataFormat.VORBIS)
49
+ assert get_unified_metadata_field(test_file, UnifiedMetadataKey.ALBUM) is None
50
+
51
+ def test_delete_album_preserves_other_fields(self):
52
+ with temp_file_with_metadata({}, "mp3") as test_file:
53
+ ID3v2MetadataSetter.set_album(test_file, "Test Album")
54
+ ID3v2MetadataSetter.set_title(test_file, "Test Title")
55
+ ID3v2MetadataSetter.set_artists(test_file, "Test Artist")
56
+
57
+ # Delete only album using library API
58
+ update_metadata(test_file, {UnifiedMetadataKey.ALBUM: None}, metadata_format=MetadataFormat.ID3V2)
59
+
60
+ assert get_unified_metadata_field(test_file, UnifiedMetadataKey.ALBUM) is None
61
+ assert get_unified_metadata_field(test_file, UnifiedMetadataKey.TITLE) == "Test Title"
62
+ assert get_unified_metadata_field(test_file, UnifiedMetadataKey.ARTISTS) == ["Test Artist"]
63
+
64
+ def test_delete_album_already_none(self):
65
+ with temp_file_with_metadata({}, "mp3") as test_file:
66
+ # Try to delete album that doesn't exist
67
+ update_metadata(test_file, {UnifiedMetadataKey.ALBUM: None}, metadata_format=MetadataFormat.ID3V2)
68
+ assert get_unified_metadata_field(test_file, UnifiedMetadataKey.ALBUM) is None
69
+
70
+ def test_delete_album_empty_string(self):
71
+ with temp_file_with_metadata({}, "mp3") as test_file:
72
+ update_metadata(test_file, {UnifiedMetadataKey.ALBUM: ""}, metadata_format=MetadataFormat.ID3V2)
73
+ assert get_unified_metadata_field(test_file, UnifiedMetadataKey.ALBUM) is None