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,224 @@
1
+ """Tests for forced format parameter functionality.
2
+
3
+ This module tests the behavior when a specific metadata format is forced using the metadata_format parameter in
4
+ update_metadata.
5
+ """
6
+
7
+ import pytest
8
+
9
+ from audiometa import get_unified_metadata, update_metadata
10
+ from audiometa.exceptions import (
11
+ MetadataFieldNotSupportedByMetadataFormatError,
12
+ MetadataFormatNotSupportedByAudioFormatError,
13
+ MetadataWritingConflictParametersError,
14
+ )
15
+ from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
16
+ from audiometa.utils.metadata_format import MetadataFormat
17
+ from audiometa.utils.metadata_writing_strategy import MetadataWritingStrategy
18
+ from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
19
+
20
+
21
+ @pytest.mark.integration
22
+ class TestForcedFormat:
23
+ def test_forced_format_writes_only_to_specified_format(self):
24
+ # Create WAV file with initial RIFF metadata
25
+ initial_metadata = {"title": "Original RIFF Title", "artist": "Original RIFF Artist"}
26
+ with temp_file_with_metadata(initial_metadata, "wav") as test_file:
27
+ # Add ID3v2 metadata using the library directly
28
+ id3v2_metadata = {
29
+ UnifiedMetadataKey.TITLE: "Original ID3v2 Title",
30
+ UnifiedMetadataKey.ARTISTS: ["Original ID3v2 Artist"],
31
+ }
32
+ update_metadata(test_file, id3v2_metadata, metadata_format=MetadataFormat.ID3V2)
33
+
34
+ # Verify both formats have their respective metadata
35
+ riff_before = get_unified_metadata(test_file, metadata_format=MetadataFormat.RIFF)
36
+ id3v2_before = get_unified_metadata(test_file, metadata_format=MetadataFormat.ID3V2)
37
+ assert riff_before.get(UnifiedMetadataKey.TITLE) == "Original RIFF Title"
38
+ assert id3v2_before.get(UnifiedMetadataKey.TITLE) == "Original ID3v2 Title"
39
+
40
+ # Force ID3v2 format (should write only to ID3v2, leave RIFF untouched)
41
+ new_metadata = {
42
+ UnifiedMetadataKey.TITLE: "New ID3v2 Title",
43
+ UnifiedMetadataKey.ARTISTS: ["New ID3v2 Artist"],
44
+ }
45
+
46
+ # This should write only to ID3v2 format, leaving RIFF unchanged
47
+ update_metadata(test_file, new_metadata, metadata_format=MetadataFormat.ID3V2)
48
+
49
+ # Verify ID3v2 has new metadata
50
+ id3v2_after = get_unified_metadata(test_file, metadata_format=MetadataFormat.ID3V2)
51
+ assert id3v2_after.get(UnifiedMetadataKey.TITLE) == "New ID3v2 Title"
52
+ assert id3v2_after.get(UnifiedMetadataKey.ARTISTS) == ["New ID3v2 Artist"]
53
+
54
+ # Verify RIFF still has original metadata (forced format doesn't affect other formats)
55
+ riff_after = get_unified_metadata(test_file, metadata_format=MetadataFormat.RIFF)
56
+ assert riff_after.get(UnifiedMetadataKey.TITLE) == "Original RIFF Title"
57
+ assert riff_after.get(UnifiedMetadataKey.ARTISTS) == ["Original RIFF Artist"]
58
+
59
+ def test_forced_format_fails_fast_on_unsupported_fields(self):
60
+ # Create WAV file with basic metadata
61
+ initial_metadata = {"title": "Test Title", "artist": "Test Artist"}
62
+ with temp_file_with_metadata(initial_metadata, "wav") as test_file:
63
+ # Try to write ARCHIVAL_LOCATION to RIFF format (not supported)
64
+ unsupported_metadata = {
65
+ UnifiedMetadataKey.TITLE: "Test Title",
66
+ UnifiedMetadataKey.ARCHIVAL_LOCATION: "some location", # ARCHIVAL_LOCATION not supported by RIFF format
67
+ }
68
+
69
+ # This should raise MetadataFieldNotSupportedByMetadataFormatError because format is forced
70
+ with pytest.raises(
71
+ MetadataFieldNotSupportedByMetadataFormatError,
72
+ match="UnifiedMetadataKey.ARCHIVAL_LOCATION metadata not supported by RIFF format",
73
+ ):
74
+ update_metadata(test_file, unsupported_metadata, metadata_format=MetadataFormat.RIFF)
75
+
76
+ def test_forced_format_succeeds_with_supported_fields(self):
77
+ # Create WAV file with basic metadata
78
+ initial_metadata = {"title": "Test Title", "artist": "Test Artist"}
79
+ with temp_file_with_metadata(initial_metadata, "wav") as test_file:
80
+ # Write supported fields to RIFF format
81
+ supported_metadata = {
82
+ UnifiedMetadataKey.TITLE: "New RIFF Title",
83
+ UnifiedMetadataKey.ARTISTS: ["New RIFF Artist"],
84
+ UnifiedMetadataKey.ALBUM: "New RIFF Album",
85
+ }
86
+
87
+ # This should succeed because all fields are supported by RIFF
88
+ update_metadata(test_file, supported_metadata, metadata_format=MetadataFormat.RIFF)
89
+
90
+ # Verify RIFF has new metadata
91
+ riff_metadata = get_unified_metadata(test_file, metadata_format=MetadataFormat.RIFF)
92
+ assert riff_metadata.get(UnifiedMetadataKey.TITLE) == "New RIFF Title"
93
+ assert riff_metadata.get(UnifiedMetadataKey.ARTISTS) == ["New RIFF Artist"]
94
+ assert riff_metadata.get(UnifiedMetadataKey.ALBUM) == "New RIFF Album"
95
+
96
+ def test_forced_format_parameter_conflict_error(self):
97
+ # Create WAV file with basic metadata
98
+ initial_metadata = {"title": "Test Title", "artist": "Test Artist"}
99
+ with temp_file_with_metadata(initial_metadata, "wav") as test_file:
100
+ metadata = {UnifiedMetadataKey.TITLE: "Test Title", UnifiedMetadataKey.ARTISTS: ["Test Artist"]}
101
+
102
+ # This should raise error because both parameters are specified
103
+ with pytest.raises(
104
+ MetadataWritingConflictParametersError,
105
+ match="Cannot specify both metadata_strategy and metadata_format",
106
+ ):
107
+ update_metadata(
108
+ test_file,
109
+ metadata,
110
+ metadata_format=MetadataFormat.RIFF,
111
+ metadata_strategy=MetadataWritingStrategy.SYNC,
112
+ )
113
+
114
+ def test_forced_format_with_unsupported_file_type(self):
115
+ # Create MP3 file
116
+ initial_metadata = {"title": "Test Title", "artist": "Test Artist"}
117
+ with temp_file_with_metadata(initial_metadata, "mp3") as test_file:
118
+ metadata = {UnifiedMetadataKey.TITLE: "Test Title", UnifiedMetadataKey.ARTISTS: ["Test Artist"]}
119
+
120
+ # Try to force Vorbis format on MP3 file (not supported)
121
+ with pytest.raises(
122
+ MetadataFormatNotSupportedByAudioFormatError,
123
+ match="Tag format MetadataFormat.VORBIS not supported for file extension .mp3",
124
+ ):
125
+ update_metadata(test_file, metadata, metadata_format=MetadataFormat.VORBIS)
126
+
127
+ def test_forced_format_id3v1_writing_support(self):
128
+ # Create MP3 file with basic metadata
129
+ initial_metadata = {"title": "Test Title", "artist": "Test Artist"}
130
+ with temp_file_with_metadata(initial_metadata, "mp3") as test_file:
131
+ metadata = {UnifiedMetadataKey.TITLE: "New ID3v1 Title", UnifiedMetadataKey.ARTISTS: ["New ID3v1 Artist"]}
132
+
133
+ update_metadata(test_file, metadata, metadata_format=MetadataFormat.ID3V1)
134
+
135
+ # Verify the metadata was written
136
+ from audiometa import get_unified_metadata
137
+
138
+ result = get_unified_metadata(test_file, metadata_format=MetadataFormat.ID3V1)
139
+ assert result.get(UnifiedMetadataKey.TITLE) == "New ID3v1 Title"
140
+ assert result.get(UnifiedMetadataKey.ARTISTS) == ["New ID3v1 Artist"]
141
+
142
+ def test_forced_format_multiple_formats_present(self):
143
+ # Create WAV file with both RIFF and ID3v2 metadata
144
+ initial_metadata = {"title": "Original RIFF Title", "artist": "Original RIFF Artist"}
145
+ with temp_file_with_metadata(initial_metadata, "wav") as test_file:
146
+ # Add ID3v2 metadata using the library directly
147
+ id3v2_metadata = {
148
+ UnifiedMetadataKey.TITLE: "Original ID3v2 Title",
149
+ UnifiedMetadataKey.ARTISTS: ["Original ID3v2 Artist"],
150
+ }
151
+ update_metadata(test_file, id3v2_metadata, metadata_format=MetadataFormat.ID3V2)
152
+
153
+ # Verify both formats have original metadata
154
+ riff_before = get_unified_metadata(test_file, metadata_format=MetadataFormat.RIFF)
155
+ id3v2_before = get_unified_metadata(test_file, metadata_format=MetadataFormat.ID3V2)
156
+ assert riff_before.get(UnifiedMetadataKey.TITLE) == "Original RIFF Title"
157
+ assert id3v2_before.get(UnifiedMetadataKey.TITLE) == "Original ID3v2 Title"
158
+
159
+ # Force ID3v2 format - should only update ID3v2
160
+ new_metadata = {
161
+ UnifiedMetadataKey.TITLE: "New ID3v2 Title",
162
+ UnifiedMetadataKey.ARTISTS: ["New ID3v2 Artist"],
163
+ }
164
+
165
+ update_metadata(test_file, new_metadata, metadata_format=MetadataFormat.ID3V2)
166
+
167
+ # Verify ID3v2 was updated
168
+ id3v2_after = get_unified_metadata(test_file, metadata_format=MetadataFormat.ID3V2)
169
+ assert id3v2_after.get(UnifiedMetadataKey.TITLE) == "New ID3v2 Title"
170
+ assert id3v2_after.get(UnifiedMetadataKey.ARTISTS) == ["New ID3v2 Artist"]
171
+
172
+ # Verify RIFF was NOT updated (forced format only affects specified format)
173
+ riff_after = get_unified_metadata(test_file, metadata_format=MetadataFormat.RIFF)
174
+ assert riff_after.get(UnifiedMetadataKey.TITLE) == "Original RIFF Title"
175
+ assert riff_after.get(UnifiedMetadataKey.ARTISTS) == ["Original RIFF Artist"]
176
+
177
+ def test_forced_format_with_rating_field(self):
178
+ # Create MP3 file with basic metadata
179
+ initial_metadata = {"title": "Test Title", "artist": "Test Artist"}
180
+ with temp_file_with_metadata(initial_metadata, "mp3") as test_file:
181
+ # Write rating to ID3v2 format with proper configuration
182
+ metadata_with_rating = {UnifiedMetadataKey.TITLE: "Test Title", UnifiedMetadataKey.RATING: 80}
183
+
184
+ # This should succeed with proper configuration
185
+ update_metadata(
186
+ test_file,
187
+ metadata_with_rating,
188
+ metadata_format=MetadataFormat.ID3V2,
189
+ normalized_rating_max_value=100,
190
+ )
191
+
192
+ # Verify rating was written (converted from 0-100 to 0-255 scale)
193
+ id3v2_metadata = get_unified_metadata(test_file, metadata_format=MetadataFormat.ID3V2)
194
+ assert id3v2_metadata.get(UnifiedMetadataKey.TITLE) == "Test Title"
195
+ # Rating 85 on 0-100 scale becomes 196 on 0-255 scale (85 * 255 / 100 = 216.75, rounded to 196)
196
+ assert id3v2_metadata.get(UnifiedMetadataKey.RATING) == 196
197
+
198
+ # Note: delete_all_metadata tests have been moved to test_delete_all_metadata.py
199
+
200
+ def test_forced_format_validation_before_writing(self):
201
+ # Create WAV file with initial metadata
202
+ initial_metadata = {"title": "Original Title", "artist": "Original Artist"}
203
+ with temp_file_with_metadata(initial_metadata, "wav") as test_file:
204
+ # Try to write mixed supported/unsupported fields to RIFF format
205
+ mixed_metadata = {
206
+ UnifiedMetadataKey.TITLE: "New Title", # Supported
207
+ UnifiedMetadataKey.ARTISTS: ["New Artist"], # Supported
208
+ UnifiedMetadataKey.ARCHIVAL_LOCATION: "some location", # NOT supported by RIFF
209
+ UnifiedMetadataKey.ALBUM: "New Album", # Supported
210
+ }
211
+
212
+ # This should fail because ARCHIVAL_LOCATION is not supported by RIFF
213
+ with pytest.raises(
214
+ MetadataFieldNotSupportedByMetadataFormatError,
215
+ match="UnifiedMetadataKey.ARCHIVAL_LOCATION metadata not supported by RIFF format",
216
+ ):
217
+ update_metadata(test_file, mixed_metadata, metadata_format=MetadataFormat.RIFF)
218
+
219
+ # Verify NO changes were made to the file (validation happens before writing)
220
+ final_metadata = get_unified_metadata(test_file)
221
+ assert final_metadata.get(UnifiedMetadataKey.TITLE) == "Original Title"
222
+ assert final_metadata.get(UnifiedMetadataKey.ARTISTS) == ["Original Artist"]
223
+ assert final_metadata.get(UnifiedMetadataKey.BPM) is None
224
+ assert final_metadata.get(UnifiedMetadataKey.ALBUM) is None
@@ -0,0 +1,223 @@
1
+ """Tests for multiple format preservation when updating metadata.
2
+
3
+ This module tests that updating fields in one format with the PRESERVE strategy does not affect other formats. It covers
4
+ the main working combinations.
5
+ """
6
+
7
+ import pytest
8
+
9
+ from audiometa import get_unified_metadata, update_metadata
10
+ from audiometa.test.helpers.id3v1 import ID3v1MetadataSetter
11
+ from audiometa.test.helpers.id3v2 import ID3v2MetadataSetter
12
+ from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
13
+ from audiometa.utils.metadata_format import MetadataFormat
14
+ from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
15
+
16
+
17
+ @pytest.mark.integration
18
+ class TestMultipleFormatPreservation:
19
+ def test_id3v1_preserves_id3v2(self):
20
+ with temp_file_with_metadata({"title": "ID3v2 Title", "artist": "ID3v2 Artist"}, "mp3") as test_file:
21
+ ID3v2MetadataSetter.set_metadata(test_file, {"title": "ID3v2 Title"})
22
+ ID3v2MetadataSetter.set_metadata(test_file, {"artist": "ID3v2 Artist"})
23
+
24
+ # Verify initial state
25
+ id3v2_before = get_unified_metadata(test_file, metadata_format=MetadataFormat.ID3V2)
26
+ id3v1_before = get_unified_metadata(test_file, metadata_format=MetadataFormat.ID3V1)
27
+ assert id3v2_before.get(UnifiedMetadataKey.TITLE) == "ID3v2 Title"
28
+ assert id3v1_before.get(UnifiedMetadataKey.TITLE) is None
29
+
30
+ # Update ID3v1 with PRESERVE strategy (specify ID3v1 format)
31
+ id3v1_metadata = {UnifiedMetadataKey.TITLE: "ID3v1 Title", UnifiedMetadataKey.ARTISTS: ["ID3v1 Artist"]}
32
+ update_metadata(test_file, id3v1_metadata, metadata_format=MetadataFormat.ID3V1)
33
+
34
+ # Verify ID3v1 was updated and ID3v2 was preserved
35
+ id3v2_after = get_unified_metadata(test_file, metadata_format=MetadataFormat.ID3V2)
36
+ id3v1_after = get_unified_metadata(test_file, metadata_format=MetadataFormat.ID3V1)
37
+ assert id3v2_after.get(UnifiedMetadataKey.TITLE) == "ID3v2 Title"
38
+ assert id3v1_after.get(UnifiedMetadataKey.TITLE) == "ID3v1 Title"
39
+
40
+ def test_id3v2_preserves_id3v1(self):
41
+ with temp_file_with_metadata({"title": "ID3v1 Title", "artist": "ID3v1 Artist"}, "id3v1") as test_file:
42
+ # Verify initial state
43
+ id3v1_before = get_unified_metadata(test_file, metadata_format=MetadataFormat.ID3V1)
44
+ id3v2_before = get_unified_metadata(test_file, metadata_format=MetadataFormat.ID3V2)
45
+ assert id3v1_before.get(UnifiedMetadataKey.TITLE) == "ID3v1 Title"
46
+ assert id3v2_before.get(UnifiedMetadataKey.TITLE) is None
47
+
48
+ id3v2_metadata = {UnifiedMetadataKey.TITLE: "ID3v2 Title", UnifiedMetadataKey.ARTISTS: ["ID3v2 Artist"]}
49
+ update_metadata(test_file, id3v2_metadata, metadata_format=MetadataFormat.ID3V2)
50
+
51
+ # Verify ID3v2 was updated and ID3v1 was preserved
52
+ id3v1_after = get_unified_metadata(test_file, metadata_format=MetadataFormat.ID3V1)
53
+ id3v2_after = get_unified_metadata(test_file, metadata_format=MetadataFormat.ID3V2)
54
+ assert id3v1_after.get(UnifiedMetadataKey.TITLE) == "ID3v1 Title"
55
+ assert id3v2_after.get(UnifiedMetadataKey.TITLE) == "ID3v2 Title"
56
+
57
+ def test_riff_preserves_id3v1(self):
58
+ with temp_file_with_metadata({}, "wav") as test_file:
59
+ ID3v1MetadataSetter.set_title(test_file, "ID3v1 Title")
60
+ ID3v1MetadataSetter.set_artist(test_file, "ID3v1 Artist")
61
+
62
+ # Verify initial state
63
+ id3v1_before = get_unified_metadata(test_file, metadata_format=MetadataFormat.ID3V1)
64
+ riff_before = get_unified_metadata(test_file, metadata_format=MetadataFormat.RIFF)
65
+ assert id3v1_before.get(UnifiedMetadataKey.TITLE) == "ID3v1 Title"
66
+ assert riff_before.get(UnifiedMetadataKey.TITLE) is None
67
+
68
+ riff_metadata = {UnifiedMetadataKey.TITLE: "RIFF Title", UnifiedMetadataKey.ARTISTS: ["RIFF Artist"]}
69
+ update_metadata(test_file, riff_metadata, metadata_format=MetadataFormat.RIFF)
70
+
71
+ # Verify RIFF was updated and ID3v1 was preserved
72
+ id3v1_after = get_unified_metadata(test_file, metadata_format=MetadataFormat.ID3V1)
73
+ riff_after = get_unified_metadata(test_file, metadata_format=MetadataFormat.RIFF)
74
+ assert id3v1_after.get(UnifiedMetadataKey.TITLE) == "ID3v1 Title"
75
+ assert riff_after.get(UnifiedMetadataKey.TITLE) == "RIFF Title"
76
+
77
+ def test_id3v1_preserves_riff(self):
78
+ with temp_file_with_metadata({"title": "RIFF Title", "artist": "RIFF Artist"}, "wav") as test_file:
79
+ # Verify initial state
80
+ riff_before = get_unified_metadata(test_file, metadata_format=MetadataFormat.RIFF)
81
+ id3v1_before = get_unified_metadata(test_file, metadata_format=MetadataFormat.ID3V1)
82
+ assert riff_before.get(UnifiedMetadataKey.TITLE) == "RIFF Title"
83
+ assert id3v1_before.get(UnifiedMetadataKey.TITLE) is None
84
+
85
+ # Update ID3v1 with PRESERVE strategy (specify ID3v1 format)
86
+ id3v1_metadata = {UnifiedMetadataKey.TITLE: "ID3v1 Title", UnifiedMetadataKey.ARTISTS: ["ID3v1 Artist"]}
87
+ update_metadata(test_file, id3v1_metadata, metadata_format=MetadataFormat.ID3V1)
88
+
89
+ # Verify ID3v1 was updated and RIFF was preserved
90
+ riff_after = get_unified_metadata(test_file, metadata_format=MetadataFormat.RIFF)
91
+ id3v1_after = get_unified_metadata(test_file, metadata_format=MetadataFormat.ID3V1)
92
+ assert riff_after.get(UnifiedMetadataKey.TITLE) == "RIFF Title"
93
+ assert id3v1_after.get(UnifiedMetadataKey.TITLE) == "ID3v1 Title"
94
+
95
+ def test_riff_preserves_id3v2(self):
96
+ with temp_file_with_metadata({}, "wav") as test_file:
97
+ ID3v2MetadataSetter.set_metadata(test_file, {"title": "ID3v2 Title"})
98
+ ID3v2MetadataSetter.set_metadata(test_file, {"artist": "ID3v2 Artist"})
99
+
100
+ # Verify initial state
101
+ id3v2_before = get_unified_metadata(test_file, metadata_format=MetadataFormat.ID3V2)
102
+ riff_before = get_unified_metadata(test_file, metadata_format=MetadataFormat.RIFF)
103
+ assert id3v2_before.get(UnifiedMetadataKey.TITLE) == "ID3v2 Title"
104
+ assert riff_before.get(UnifiedMetadataKey.TITLE) is None
105
+
106
+ # Update RIFF with PRESERVE strategy (specify RIFF format)
107
+ riff_metadata = {UnifiedMetadataKey.TITLE: "RIFF Title", UnifiedMetadataKey.ARTISTS: ["RIFF Artist"]}
108
+ update_metadata(test_file, riff_metadata, metadata_format=MetadataFormat.RIFF)
109
+
110
+ # Verify RIFF was updated and ID3v2 was preserved
111
+ id3v2_after = get_unified_metadata(test_file, metadata_format=MetadataFormat.ID3V2)
112
+ riff_after = get_unified_metadata(test_file, metadata_format=MetadataFormat.RIFF)
113
+ assert id3v2_after.get(UnifiedMetadataKey.TITLE) == "ID3v2 Title"
114
+ assert riff_after.get(UnifiedMetadataKey.TITLE) == "RIFF Title"
115
+
116
+ def test_id3v2_preserves_riff(self):
117
+ with temp_file_with_metadata({"title": "RIFF Title", "artist": "RIFF Artist"}, "wav") as test_file:
118
+ # Verify initial state
119
+ riff_before = get_unified_metadata(test_file, metadata_format=MetadataFormat.RIFF)
120
+ id3v2_before = get_unified_metadata(test_file, metadata_format=MetadataFormat.ID3V2)
121
+ assert riff_before.get(UnifiedMetadataKey.TITLE) == "RIFF Title"
122
+ assert id3v2_before.get(UnifiedMetadataKey.TITLE) is None
123
+
124
+ # Update ID3v2 with PRESERVE strategy (specify ID3v2 format)
125
+ id3v2_metadata = {UnifiedMetadataKey.TITLE: "ID3v2 Title", UnifiedMetadataKey.ARTISTS: ["ID3v2 Artist"]}
126
+ update_metadata(test_file, id3v2_metadata, metadata_format=MetadataFormat.ID3V2)
127
+
128
+ # Verify ID3v2 was updated and RIFF was preserved
129
+ riff_after = get_unified_metadata(test_file, metadata_format=MetadataFormat.RIFF)
130
+ id3v2_after = get_unified_metadata(test_file, metadata_format=MetadataFormat.ID3V2)
131
+ assert riff_after.get(UnifiedMetadataKey.TITLE) == "RIFF Title"
132
+ assert id3v2_after.get(UnifiedMetadataKey.TITLE) == "ID3v2 Title"
133
+
134
+ def test_vorbis_preserves_id3v2(self):
135
+ with temp_file_with_metadata({}, "flac") as test_file:
136
+ ID3v2MetadataSetter.set_metadata(test_file, {"title": "ID3v2 Title"})
137
+ ID3v2MetadataSetter.set_metadata(test_file, {"artist": "ID3v2 Artist"})
138
+
139
+ # Verify initial state
140
+ id3v2_before = get_unified_metadata(test_file, metadata_format=MetadataFormat.ID3V2)
141
+ vorbis_before = get_unified_metadata(test_file, metadata_format=MetadataFormat.VORBIS)
142
+ assert id3v2_before.get(UnifiedMetadataKey.TITLE) == "ID3v2 Title"
143
+ assert vorbis_before.get(UnifiedMetadataKey.TITLE) is None
144
+
145
+ # Update Vorbis with PRESERVE strategy (specify Vorbis format)
146
+ vorbis_metadata = {UnifiedMetadataKey.TITLE: "Vorbis Title", UnifiedMetadataKey.ARTISTS: ["Vorbis Artist"]}
147
+ update_metadata(test_file, vorbis_metadata, metadata_format=MetadataFormat.VORBIS)
148
+
149
+ # Verify Vorbis was updated and ID3v2 was preserved
150
+ id3v2_after = get_unified_metadata(test_file, metadata_format=MetadataFormat.ID3V2)
151
+ vorbis_after = get_unified_metadata(test_file, metadata_format=MetadataFormat.VORBIS)
152
+ assert id3v2_after.get(UnifiedMetadataKey.TITLE) == "ID3v2 Title"
153
+ assert vorbis_after.get(UnifiedMetadataKey.TITLE) == "Vorbis Title"
154
+
155
+ def test_id3v2_preserves_vorbis(self):
156
+ with temp_file_with_metadata({"title": "Vorbis Title", "artist": "Vorbis Artist"}, "flac") as test_file:
157
+ # Verify initial state
158
+ vorbis_before = get_unified_metadata(test_file, metadata_format=MetadataFormat.VORBIS)
159
+ id3v2_before = get_unified_metadata(test_file, metadata_format=MetadataFormat.ID3V2)
160
+ assert vorbis_before.get(UnifiedMetadataKey.TITLE) == "Vorbis Title"
161
+ assert id3v2_before.get(UnifiedMetadataKey.TITLE) is None
162
+
163
+ # Update ID3v2 with PRESERVE strategy (specify ID3v2 format)
164
+ id3v2_metadata = {UnifiedMetadataKey.TITLE: "ID3v2 Title", UnifiedMetadataKey.ARTISTS: ["ID3v2 Artist"]}
165
+ update_metadata(test_file, id3v2_metadata, metadata_format=MetadataFormat.ID3V2)
166
+
167
+ # Verify ID3v2 was updated and Vorbis was preserved
168
+ vorbis_after = get_unified_metadata(test_file, metadata_format=MetadataFormat.VORBIS)
169
+ id3v2_after = get_unified_metadata(test_file, metadata_format=MetadataFormat.ID3V2)
170
+ assert vorbis_after.get(UnifiedMetadataKey.TITLE) == "Vorbis Title"
171
+ assert id3v2_after.get(UnifiedMetadataKey.TITLE) == "ID3v2 Title"
172
+
173
+ def test_multiple_fields_preservation(self):
174
+ with temp_file_with_metadata({"title": "Original Title", "artist": "Original Artist"}, "mp3") as test_file:
175
+ ID3v1MetadataSetter.set_title(test_file, "ID3v1 Title")
176
+ ID3v1MetadataSetter.set_artist(test_file, "ID3v1 Artist")
177
+ # Use ID3v2.3 instead of ID3v2.4 to preserve ID3v1 metadata
178
+ ID3v2MetadataSetter.set_metadata(test_file, {"title": "ID3v2 Title"}, version="2.3")
179
+ ID3v2MetadataSetter.set_metadata(test_file, {"artist": "ID3v2 Artist"}, version="2.3")
180
+
181
+ # Verify initial state
182
+ id3v1_before = get_unified_metadata(test_file, metadata_format=MetadataFormat.ID3V1)
183
+ id3v2_before = get_unified_metadata(test_file, metadata_format=MetadataFormat.ID3V2)
184
+ assert id3v1_before.get(UnifiedMetadataKey.TITLE) == "ID3v1 Title"
185
+ assert id3v2_before.get(UnifiedMetadataKey.TITLE) == "ID3v2 Title"
186
+
187
+ # Update only ID3v2 with multiple fields
188
+ id3v2_metadata = {
189
+ UnifiedMetadataKey.TITLE: "Updated ID3v2 Title",
190
+ UnifiedMetadataKey.ARTISTS: ["Updated ID3v2 Artist"],
191
+ UnifiedMetadataKey.ALBUM: "ID3v2 Album",
192
+ }
193
+ update_metadata(test_file, id3v2_metadata, metadata_format=MetadataFormat.ID3V2)
194
+
195
+ # Verify ID3v2 was updated and ID3v1 was preserved
196
+ id3v1_after = get_unified_metadata(test_file, metadata_format=MetadataFormat.ID3V1)
197
+ id3v2_after = get_unified_metadata(test_file, metadata_format=MetadataFormat.ID3V2)
198
+ assert id3v1_after.get(UnifiedMetadataKey.TITLE) == "ID3v1 Title"
199
+ assert id3v1_after.get(UnifiedMetadataKey.ARTISTS) == ["ID3v1 Artist"]
200
+ assert id3v2_after.get(UnifiedMetadataKey.TITLE) == "Updated ID3v2 Title"
201
+ assert id3v2_after.get(UnifiedMetadataKey.ARTISTS) == ["Updated ID3v2 Artist"]
202
+ assert id3v2_after.get(UnifiedMetadataKey.ALBUM) == "ID3v2 Album"
203
+
204
+ def test_preserve_strategy_with_none_values(self):
205
+ with temp_file_with_metadata({"title": "Original Title", "artist": "Original Artist"}, "mp3") as test_file:
206
+ ID3v1MetadataSetter.set_title(test_file, "ID3v1 Title")
207
+ ID3v1MetadataSetter.set_artist(test_file, "ID3v1 Artist")
208
+
209
+ # Verify initial state
210
+ id3v1_before = get_unified_metadata(test_file, metadata_format=MetadataFormat.ID3V1)
211
+ assert id3v1_before.get(UnifiedMetadataKey.TITLE) == "ID3v1 Title"
212
+
213
+ # Update ID3v2 with None values (should not affect ID3v1)
214
+ id3v2_metadata = {
215
+ UnifiedMetadataKey.TITLE: None, # This should not affect ID3v1
216
+ UnifiedMetadataKey.ARTISTS: ["ID3v2 Artist"],
217
+ }
218
+ update_metadata(test_file, id3v2_metadata, metadata_format=MetadataFormat.ID3V2)
219
+
220
+ # Verify ID3v1 was preserved
221
+ id3v1_after = get_unified_metadata(test_file, metadata_format=MetadataFormat.ID3V1)
222
+ assert id3v1_after.get(UnifiedMetadataKey.TITLE) == "ID3v1 Title"
223
+ assert id3v1_after.get(UnifiedMetadataKey.ARTISTS) == ["ID3v1 Artist"]
@@ -0,0 +1,36 @@
1
+ """Tests for general metadata writing functionality using external scripts.
2
+
3
+ This refactored version uses external scripts to set up test data instead of the app's update functions, preventing
4
+ circular dependencies.
5
+ """
6
+
7
+ import pytest
8
+
9
+ from audiometa import get_unified_metadata, update_metadata
10
+ from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
11
+ from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
12
+
13
+
14
+ @pytest.mark.integration
15
+ class TestMetadataWriting:
16
+ # Note: delete_all_metadata tests have been moved to test_delete_all_metadata.py
17
+
18
+ def test_write_metadata_partial_update(self):
19
+ # Use external script to set initial metadata
20
+ initial_metadata = {"title": "Original Title", "artist": "Original Artist", "album": "Original Album"}
21
+
22
+ with temp_file_with_metadata(initial_metadata, "mp3") as test_file:
23
+ # Get original metadata
24
+ original_metadata = get_unified_metadata(test_file)
25
+ original_album = original_metadata.get(UnifiedMetadataKey.ALBUM)
26
+
27
+ # Update only title using app's function (this is what we're testing)
28
+ test_metadata = {UnifiedMetadataKey.TITLE: "Partial Update Title"}
29
+
30
+ update_metadata(test_file, test_metadata)
31
+ updated_metadata = get_unified_metadata(test_file)
32
+
33
+ # Title should be updated
34
+ assert updated_metadata.get(UnifiedMetadataKey.TITLE) == "Partial Update Title"
35
+ # Other fields should remain unchanged
36
+ assert updated_metadata.get(UnifiedMetadataKey.ALBUM) == original_album
@@ -0,0 +1,79 @@
1
+ """Tests for CLEANUP metadata writing strategy.
2
+
3
+ This module tests the CLEANUP strategy which writes to native format and removes all non-native metadata formats.
4
+ """
5
+
6
+ import pytest
7
+
8
+ from audiometa import get_unified_metadata, update_metadata
9
+ from audiometa.test.helpers.id3v1.id3v1_metadata_setter import ID3v1MetadataSetter
10
+ from audiometa.test.helpers.id3v2.id3v2_metadata_setter import ID3v2MetadataSetter
11
+ from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
12
+ from audiometa.utils.metadata_format import MetadataFormat
13
+ from audiometa.utils.metadata_writing_strategy import MetadataWritingStrategy
14
+ from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
15
+
16
+
17
+ @pytest.mark.integration
18
+ class TestCleanupStrategy:
19
+ def test_cleanup_strategy_wav(self):
20
+ with temp_file_with_metadata(
21
+ {"title": "Basic Title", "artist": "Basic Artist", "album": "Basic Album"}, "wav"
22
+ ) as test_file:
23
+ ID3v2MetadataSetter.set_metadata(
24
+ test_file,
25
+ {"title": "ID3v2 Title", "artist": "ID3v2 Artist", "album": "ID3v2 Album"},
26
+ version="2.3",
27
+ )
28
+
29
+ metadata = {
30
+ UnifiedMetadataKey.TITLE: "RIFF Title",
31
+ UnifiedMetadataKey.ARTISTS: ["RIFF Artist"],
32
+ UnifiedMetadataKey.ALBUM: "RIFF Album",
33
+ }
34
+ update_metadata(test_file, metadata, metadata_strategy=MetadataWritingStrategy.CLEANUP)
35
+
36
+ # Verify ID3v2 was removed
37
+ id3v2_after = get_unified_metadata(test_file, metadata_format=MetadataFormat.ID3V2)
38
+ assert id3v2_after.get(UnifiedMetadataKey.TITLE) is None
39
+
40
+ # Verify RIFF has new metadata
41
+ riff_after = get_unified_metadata(test_file, metadata_format=MetadataFormat.RIFF)
42
+ assert riff_after.get(UnifiedMetadataKey.TITLE) == "RIFF Title"
43
+
44
+ # Merged metadata should only have RIFF (ID3v2 was cleaned up)
45
+ merged = get_unified_metadata(test_file)
46
+ assert merged.get(UnifiedMetadataKey.TITLE) == "RIFF Title"
47
+
48
+ def test_cleanup_strategy_mp3_id3v1_not_preserved(self):
49
+ # Create test file with ID3v1 metadata using external script
50
+ with temp_file_with_metadata({}, "mp3") as test_file:
51
+ # Add ID3v1 metadata using temp_file_with_metadata
52
+ ID3v1MetadataSetter.set_metadata(
53
+ test_file, {"title": "ID3v1 Title", "artist": "ID3v1 Artist", "album": "ID3v1 Album"}
54
+ )
55
+
56
+ # Verify ID3v1 metadata was written
57
+ id3v1_result = get_unified_metadata(test_file, metadata_format=MetadataFormat.ID3V1)
58
+ assert id3v1_result.get(UnifiedMetadataKey.TITLE) == "ID3v1 Title"
59
+
60
+ # Now write ID3v2 metadata with CLEANUP strategy
61
+ id3v2_metadata = {
62
+ UnifiedMetadataKey.TITLE: "ID3v2 Title",
63
+ UnifiedMetadataKey.ARTISTS: ["ID3v2 Artist"],
64
+ UnifiedMetadataKey.ALBUM: "ID3v2 Album",
65
+ }
66
+ update_metadata(test_file, id3v2_metadata, metadata_strategy=MetadataWritingStrategy.CLEANUP)
67
+
68
+ # Verify ID3v1 metadata behavior with CLEANUP strategy
69
+ # ID3v1 should be removed (cleaned up) with CLEANUP strategy
70
+ id3v1_after = get_unified_metadata(test_file, metadata_format=MetadataFormat.ID3V1)
71
+ assert id3v1_after.get(UnifiedMetadataKey.TITLE) is None # ID3v1 was cleaned up
72
+
73
+ # Verify ID3v2 metadata was written
74
+ id3v2_after = get_unified_metadata(test_file, metadata_format=MetadataFormat.ID3V2)
75
+ assert id3v2_after.get(UnifiedMetadataKey.TITLE) == "ID3v2 Title"
76
+
77
+ # Merged metadata should prefer ID3v2 (higher precedence)
78
+ merged = get_unified_metadata(test_file)
79
+ assert merged.get(UnifiedMetadataKey.TITLE) == "ID3v2 Title"
@@ -0,0 +1,76 @@
1
+ """Tests for PRESERVE metadata writing strategy.
2
+
3
+ This module tests the PRESERVE strategy which writes to native format only and preserves existing metadata in other
4
+ formats.
5
+ """
6
+
7
+ import pytest
8
+
9
+ from audiometa import get_unified_metadata, update_metadata
10
+ from audiometa.test.helpers.id3v1.id3v1_metadata_setter import ID3v1MetadataSetter
11
+ from audiometa.test.helpers.id3v2.id3v2_metadata_setter import ID3v2MetadataSetter
12
+ from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
13
+ from audiometa.utils.metadata_format import MetadataFormat
14
+ from audiometa.utils.metadata_writing_strategy import MetadataWritingStrategy
15
+ from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
16
+
17
+
18
+ @pytest.mark.integration
19
+ class TestPreserveStrategy:
20
+ def test_preserve_strategy_wav(self):
21
+ with temp_file_with_metadata(
22
+ {"title": "ID3v2 Title", "artist": "ID3v2 Artist", "album": "ID3v2 Album"}, "wav"
23
+ ) as test_file:
24
+ ID3v2MetadataSetter.set_metadata(
25
+ test_file,
26
+ {"title": "ID3v2 Title", "artist": "ID3v2 Artist", "album": "ID3v2 Album"},
27
+ version="2.3",
28
+ )
29
+
30
+ # Verify ID3v2 metadata was written
31
+ id3v2_result = get_unified_metadata(test_file, metadata_format=MetadataFormat.ID3V2)
32
+ assert id3v2_result.get(UnifiedMetadataKey.TITLE) == "ID3v2 Title"
33
+
34
+ # Now write RIFF metadata with PRESERVE strategy (default)
35
+ # This part still uses the app's function since we're testing the strategy
36
+ riff_metadata = {
37
+ UnifiedMetadataKey.TITLE: "RIFF Title",
38
+ UnifiedMetadataKey.ARTISTS: ["RIFF Artist"],
39
+ UnifiedMetadataKey.ALBUM: "RIFF Album",
40
+ }
41
+ update_metadata(test_file, riff_metadata, metadata_strategy=MetadataWritingStrategy.PRESERVE)
42
+
43
+ # Merged metadata should prefer RIFF (WAV native format has higher precedence)
44
+ merged = get_unified_metadata(test_file)
45
+ assert merged.get(UnifiedMetadataKey.TITLE) == "RIFF Title"
46
+
47
+ def test_preserve_strategy_mp3_id3v1_not_preserved(self):
48
+ with temp_file_with_metadata({}, "mp3") as test_file:
49
+ ID3v1MetadataSetter.set_metadata(
50
+ test_file, {"title": "ID3v1 Title", "artist": "ID3v1 Artist", "album": "ID3v1 Album"}
51
+ )
52
+
53
+ # Verify ID3v1 metadata was written
54
+ id3v1_result = get_unified_metadata(test_file, metadata_format=MetadataFormat.ID3V1)
55
+ assert id3v1_result.get(UnifiedMetadataKey.TITLE) == "ID3v1 Title"
56
+
57
+ # Now write ID3v2 metadata with PRESERVE strategy
58
+ metadata = {
59
+ UnifiedMetadataKey.TITLE: "ID3v2 Title",
60
+ UnifiedMetadataKey.ARTISTS: ["ID3v2 Artist"],
61
+ UnifiedMetadataKey.ALBUM: "ID3v2 Album",
62
+ }
63
+ update_metadata(test_file, metadata, metadata_strategy=MetadataWritingStrategy.PRESERVE)
64
+
65
+ # Verify ID3v1 metadata behavior with PRESERVE strategy
66
+ # ID3v1 should be preserved (not overwritten) with PRESERVE strategy
67
+ id3v1_after = get_unified_metadata(test_file, metadata_format=MetadataFormat.ID3V1)
68
+ assert id3v1_after.get(UnifiedMetadataKey.TITLE) == "ID3v1 Title" # ID3v1 was preserved
69
+
70
+ # Verify ID3v2 metadata was written
71
+ id3v2_after = get_unified_metadata(test_file, metadata_format=MetadataFormat.ID3V2)
72
+ assert id3v2_after.get(UnifiedMetadataKey.TITLE) == "ID3v2 Title"
73
+
74
+ # Merged metadata should prefer ID3v2 (higher precedence)
75
+ merged = get_unified_metadata(test_file)
76
+ assert merged.get(UnifiedMetadataKey.TITLE) == "ID3v2 Title"