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,54 @@
1
+ import subprocess
2
+ import sys
3
+
4
+ import pytest
5
+
6
+ from audiometa import get_unified_metadata
7
+ from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
8
+ from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
9
+
10
+
11
+ @pytest.mark.e2e
12
+ class TestCLIWriteStringFields:
13
+ def test_cli_write_all_string_fields(self):
14
+ with temp_file_with_metadata({}, "mp3") as test_file:
15
+ result = subprocess.run(
16
+ [
17
+ sys.executable,
18
+ "-m",
19
+ "audiometa",
20
+ "write",
21
+ str(test_file),
22
+ "--title",
23
+ "Test Title",
24
+ "--language",
25
+ "eng",
26
+ "--publisher",
27
+ "Test Publisher",
28
+ "--copyright",
29
+ "© 2024 Test",
30
+ "--lyrics",
31
+ "Test lyrics text",
32
+ "--comment",
33
+ "Test comment",
34
+ "--replaygain",
35
+ "+2.5 dB",
36
+ "--archival-location",
37
+ "Archive Location",
38
+ ],
39
+ capture_output=True,
40
+ text=True,
41
+ check=False,
42
+ )
43
+ assert result.returncode == 0
44
+
45
+ metadata = get_unified_metadata(test_file)
46
+ assert metadata.get(UnifiedMetadataKey.TITLE) == "Test Title"
47
+ assert metadata.get(UnifiedMetadataKey.LANGUAGE) == "eng"
48
+ assert metadata.get(UnifiedMetadataKey.PUBLISHER) == "Test Publisher"
49
+ assert metadata.get(UnifiedMetadataKey.COPYRIGHT) == "© 2024 Test"
50
+ assert metadata.get(UnifiedMetadataKey.UNSYNCHRONIZED_LYRICS) == "Test lyrics text"
51
+ assert metadata.get(UnifiedMetadataKey.COMMENT) == "Test comment"
52
+ assert metadata.get(UnifiedMetadataKey.REPLAYGAIN) == "+2.5 dB"
53
+ # ARCHIVAL_LOCATION is not supported by ID3v2 format (MP3)
54
+ # It's only supported by Vorbis (FLAC)
@@ -0,0 +1,85 @@
1
+ import subprocess
2
+ import sys
3
+
4
+ import pytest
5
+
6
+ from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
7
+
8
+
9
+ @pytest.mark.e2e
10
+ class TestCLIWriteValidation:
11
+ def test_cli_write_negative_disc_number_error(self):
12
+ with temp_file_with_metadata({}, "mp3") as test_file:
13
+ result = subprocess.run(
14
+ [
15
+ sys.executable,
16
+ "-m",
17
+ "audiometa",
18
+ "write",
19
+ str(test_file),
20
+ "--disc-number",
21
+ "-1",
22
+ ],
23
+ capture_output=True,
24
+ text=True,
25
+ check=False,
26
+ )
27
+ assert result.returncode != 0
28
+ assert "cannot be negative" in result.stderr.lower()
29
+
30
+ def test_cli_write_negative_disc_total_error(self):
31
+ with temp_file_with_metadata({}, "mp3") as test_file:
32
+ result = subprocess.run(
33
+ [
34
+ sys.executable,
35
+ "-m",
36
+ "audiometa",
37
+ "write",
38
+ str(test_file),
39
+ "--disc-total",
40
+ "-1",
41
+ ],
42
+ capture_output=True,
43
+ text=True,
44
+ check=False,
45
+ )
46
+ assert result.returncode != 0
47
+ assert "cannot be negative" in result.stderr.lower()
48
+
49
+ def test_cli_write_negative_bpm_error(self):
50
+ with temp_file_with_metadata({}, "mp3") as test_file:
51
+ result = subprocess.run(
52
+ [
53
+ sys.executable,
54
+ "-m",
55
+ "audiometa",
56
+ "write",
57
+ str(test_file),
58
+ "--bpm",
59
+ "-1",
60
+ ],
61
+ capture_output=True,
62
+ text=True,
63
+ check=False,
64
+ )
65
+ assert result.returncode != 0
66
+ assert "cannot be negative" in result.stderr.lower()
67
+
68
+ def test_cli_write_negative_year_error(self):
69
+ with temp_file_with_metadata({}, "mp3") as test_file:
70
+ result = subprocess.run(
71
+ [
72
+ sys.executable,
73
+ "-m",
74
+ "audiometa",
75
+ "write",
76
+ str(test_file),
77
+ "--year",
78
+ "-1",
79
+ ],
80
+ capture_output=True,
81
+ text=True,
82
+ check=False,
83
+ )
84
+ assert result.returncode != 0
85
+ assert "cannot be negative" in result.stderr.lower()
File without changes
@@ -0,0 +1,166 @@
1
+ """End-to-end tests for real user scenarios 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
+ These tests simulate how actual users would interact with the library in real-world applications.
7
+ """
8
+
9
+ import pytest
10
+
11
+ from audiometa import delete_all_metadata, get_unified_metadata, update_metadata
12
+ from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
13
+ from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
14
+
15
+
16
+ @pytest.mark.e2e
17
+ class TestUserScenarios:
18
+ def test_music_library_organization(self, sample_mp3_file, sample_flac_file, sample_wav_file):
19
+ # Simulate a user organizing their music library
20
+
21
+ sample_files = [(sample_mp3_file, "mp3"), (sample_flac_file, "flac"), (sample_wav_file, "wav")]
22
+
23
+ for i, (_file_path, format_type) in enumerate(sample_files[:3]): # Test with first 3 files
24
+ # Set basic metadata using external script
25
+ basic_metadata = {"title": f"Original Track {i + 1}", "artist": "Original Artist"}
26
+ with temp_file_with_metadata(basic_metadata, format_type) as test_file:
27
+ # Set consistent metadata for organization using app's function (this is what we're testing)
28
+ test_metadata = {
29
+ UnifiedMetadataKey.ALBUM: "My Music Library",
30
+ UnifiedMetadataKey.TITLE: f"Track {i + 1}",
31
+ }
32
+ update_metadata(test_file, test_metadata)
33
+
34
+ # Verify the organization worked
35
+ metadata = get_unified_metadata(test_file)
36
+ assert metadata.get(UnifiedMetadataKey.ALBUM) == "My Music Library"
37
+ assert metadata.get(UnifiedMetadataKey.TITLE) == f"Track {i + 1}"
38
+
39
+ def test_metadata_import_export_workflow(self):
40
+ # Simulate a user importing metadata from external source
41
+ # Use external script to set initial metadata
42
+ initial_metadata = {"title": "Original Title", "artist": "Original Artist", "album": "Original Album"}
43
+ with temp_file_with_metadata(initial_metadata, "mp3") as test_file:
44
+ # Export current metadata
45
+ current_metadata = get_unified_metadata(test_file)
46
+ metadata = {
47
+ "title": current_metadata.get(UnifiedMetadataKey.TITLE),
48
+ "artist": current_metadata.get(UnifiedMetadataKey.ARTISTS),
49
+ "album": current_metadata.get(UnifiedMetadataKey.ALBUM),
50
+ }
51
+
52
+ # Simulate external metadata update
53
+ metadata["title"] = "Updated Title"
54
+ metadata["artist"] = ["Updated Artist"]
55
+
56
+ # Apply updated metadata using app's function (this is what we're testing)
57
+ test_metadata = {
58
+ UnifiedMetadataKey.TITLE: metadata["title"],
59
+ UnifiedMetadataKey.ARTISTS: metadata["artist"],
60
+ UnifiedMetadataKey.ALBUM: metadata["album"],
61
+ }
62
+ update_metadata(test_file, test_metadata)
63
+
64
+ # Verify the import worked
65
+ updated_metadata = get_unified_metadata(test_file)
66
+ assert updated_metadata.get(UnifiedMetadataKey.TITLE) == "Updated Title"
67
+ assert updated_metadata.get(UnifiedMetadataKey.ARTISTS) == ["Updated Artist"]
68
+
69
+ def test_metadata_privacy_cleanup_workflow(self):
70
+ # Simulate a user cleaning up metadata for privacy before sharing files
71
+ initial_metadata = {
72
+ "title": "Personal Music Track",
73
+ "artist": "Personal Artist",
74
+ "album": "Personal Album",
75
+ "comment": "Personal comment with sensitive info",
76
+ }
77
+
78
+ with temp_file_with_metadata(initial_metadata, "mp3") as test_file:
79
+ # 1. Verify initial metadata exists
80
+ initial_metadata_result = get_unified_metadata(test_file)
81
+ assert initial_metadata_result.get(UnifiedMetadataKey.TITLE) == "Personal Music Track"
82
+ assert initial_metadata_result.get(UnifiedMetadataKey.COMMENT) == "Personal comment with sensitive info"
83
+
84
+ # 2. User decides to clean up sensitive metadata
85
+ cleanup_metadata = {
86
+ UnifiedMetadataKey.COMMENT: None, # Remove personal comment
87
+ UnifiedMetadataKey.ALBUM: "Generic Album", # Replace personal album
88
+ }
89
+ update_metadata(test_file, cleanup_metadata)
90
+
91
+ # 3. Verify sensitive data was removed
92
+ cleaned_metadata = get_unified_metadata(test_file)
93
+ assert cleaned_metadata.get(UnifiedMetadataKey.COMMENT) is None
94
+ assert cleaned_metadata.get(UnifiedMetadataKey.ALBUM) == "Generic Album"
95
+ assert cleaned_metadata.get(UnifiedMetadataKey.TITLE) == "Personal Music Track" # Title remains
96
+
97
+ # 4. User decides to remove all metadata for complete privacy
98
+ delete_result = delete_all_metadata(test_file)
99
+ assert delete_result is True
100
+
101
+ # 5. Verify all metadata is removed
102
+ final_metadata = get_unified_metadata(test_file)
103
+ assert (
104
+ final_metadata.get(UnifiedMetadataKey.TITLE) is None
105
+ or final_metadata.get(UnifiedMetadataKey.TITLE) != "Personal Music Track"
106
+ )
107
+ assert final_metadata.get(UnifiedMetadataKey.COMMENT) is None
108
+
109
+ def test_metadata_correction_workflow(self):
110
+ # Simulate a user correcting incorrect metadata
111
+ initial_metadata = {"title": "Wrong Title", "artist": "Wrong Artist", "album": "Wrong Album"}
112
+
113
+ with temp_file_with_metadata(initial_metadata, "mp3") as test_file:
114
+ # 1. Verify initial incorrect metadata
115
+ initial_metadata_result = get_unified_metadata(test_file)
116
+ assert initial_metadata_result.get(UnifiedMetadataKey.TITLE) == "Wrong Title"
117
+
118
+ # 2. User realizes the metadata is wrong and wants to start fresh
119
+ # Delete all metadata first
120
+ delete_result = delete_all_metadata(test_file)
121
+ assert delete_result is True
122
+
123
+ # 3. Verify metadata was deleted
124
+ deleted_metadata = get_unified_metadata(test_file)
125
+ assert (
126
+ deleted_metadata.get(UnifiedMetadataKey.TITLE) is None
127
+ or deleted_metadata.get(UnifiedMetadataKey.TITLE) != "Wrong Title"
128
+ )
129
+
130
+ # 4. Add correct metadata
131
+ correct_metadata = {
132
+ UnifiedMetadataKey.TITLE: "Correct Title",
133
+ UnifiedMetadataKey.ARTISTS: ["Correct Artist"],
134
+ UnifiedMetadataKey.ALBUM: "Correct Album",
135
+ }
136
+ update_metadata(test_file, correct_metadata)
137
+
138
+ # 5. Verify correct metadata was added
139
+ final_metadata = get_unified_metadata(test_file)
140
+ assert final_metadata.get(UnifiedMetadataKey.TITLE) == "Correct Title"
141
+ assert final_metadata.get(UnifiedMetadataKey.ARTISTS) == ["Correct Artist"]
142
+ assert final_metadata.get(UnifiedMetadataKey.ALBUM) == "Correct Album"
143
+
144
+ def test_cross_format_compatibility(self, sample_mp3_file, sample_flac_file, sample_wav_file):
145
+ # Test that metadata works consistently across MP3, FLAC, etc.
146
+
147
+ test_metadata = {
148
+ UnifiedMetadataKey.TITLE: "Cross Format Test",
149
+ UnifiedMetadataKey.ARTISTS: ["Test Artist"],
150
+ UnifiedMetadataKey.ALBUM: "Test Album",
151
+ }
152
+
153
+ sample_files = [(sample_mp3_file, "mp3"), (sample_flac_file, "flac"), (sample_wav_file, "wav")]
154
+
155
+ for _file_path, format_type in sample_files:
156
+ # Set basic metadata using external script
157
+ basic_metadata = {"title": "Original Title", "artist": "Original Artist"}
158
+ with temp_file_with_metadata(basic_metadata, format_type) as test_file:
159
+ # Set metadata using app's function (this is what we're testing)
160
+ update_metadata(test_file, test_metadata)
161
+
162
+ # Verify metadata was set correctly
163
+ metadata = get_unified_metadata(test_file)
164
+ assert metadata.get(UnifiedMetadataKey.TITLE) == test_metadata[UnifiedMetadataKey.TITLE]
165
+ assert metadata.get(UnifiedMetadataKey.ARTISTS) == test_metadata[UnifiedMetadataKey.ARTISTS]
166
+ assert metadata.get(UnifiedMetadataKey.ALBUM) == test_metadata[UnifiedMetadataKey.ALBUM]
File without changes
@@ -0,0 +1,166 @@
1
+ """End-to-end tests for core metadata editing workflows.
2
+
3
+ These tests verify the basic functionality of the entire system for real users, including file I/O and complete metadata
4
+ editing workflows.
5
+ """
6
+
7
+ from pathlib import Path
8
+
9
+ import pytest
10
+
11
+ from audiometa import (
12
+ _AudioFile,
13
+ delete_all_metadata,
14
+ get_bitrate,
15
+ get_duration_in_sec,
16
+ get_unified_metadata,
17
+ update_metadata,
18
+ )
19
+ from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
20
+ from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
21
+
22
+
23
+ @pytest.mark.e2e
24
+ class TestCoreWorkflows:
25
+ def test_complete_metadata_editing_workflow(self):
26
+ # This is an e2e test - it tests the entire user journey
27
+ # 1. Load a file
28
+ # 2. Read existing metadata
29
+ # 3. Edit multiple fields
30
+ # 4. Save changes
31
+ # 5. Verify persistence
32
+
33
+ # Use external script to set initial metadata
34
+ initial_metadata = {"title": "Original Title", "artist": "Original Artist", "album": "Original Album"}
35
+
36
+ with temp_file_with_metadata(initial_metadata, "mp3") as test_file:
37
+ # Edit metadata using app's function (this is what we're testing)
38
+ test_metadata = {
39
+ UnifiedMetadataKey.TITLE: "New Title",
40
+ UnifiedMetadataKey.ARTISTS: ["New Artist"],
41
+ UnifiedMetadataKey.ALBUM: "New Album",
42
+ UnifiedMetadataKey.GENRES_NAMES: ["Rock"],
43
+ UnifiedMetadataKey.COMMENT: "Test comment",
44
+ }
45
+
46
+ # Save changes
47
+ update_metadata(test_file, test_metadata)
48
+
49
+ # Verify persistence by reloading
50
+ metadata = get_unified_metadata(test_file)
51
+ assert metadata.get(UnifiedMetadataKey.TITLE) == "New Title"
52
+ assert metadata.get(UnifiedMetadataKey.ARTISTS) == ["New Artist"]
53
+ assert metadata.get(UnifiedMetadataKey.ALBUM) == "New Album"
54
+ assert metadata.get(UnifiedMetadataKey.GENRES_NAMES) == ["Rock"]
55
+ assert metadata.get(UnifiedMetadataKey.COMMENT) == "Test comment"
56
+
57
+ def test_batch_metadata_processing(self, sample_mp3_file, sample_flac_file, sample_wav_file):
58
+ # E2E test for batch operations
59
+ results = []
60
+
61
+ sample_files = [(sample_mp3_file, "mp3"), (sample_flac_file, "flac"), (sample_wav_file, "wav")]
62
+
63
+ for file_path, format_type in sample_files:
64
+ try:
65
+ # Set initial metadata using external script
66
+ initial_metadata = {"title": "Batch Test Title", "artist": "Batch Test Artist"}
67
+ with temp_file_with_metadata(initial_metadata, format_type) as test_file:
68
+ # Update metadata using functional API (this is what we're testing)
69
+ test_metadata = {
70
+ UnifiedMetadataKey.ALBUM: "Batch Album",
71
+ UnifiedMetadataKey.COMMENT: "Batch processing test",
72
+ }
73
+ update_metadata(test_file, test_metadata)
74
+ results.append(("success", file_path))
75
+ except Exception as e:
76
+ results.append(("error", file_path, str(e)))
77
+
78
+ # Verify all files were processed
79
+ assert len(results) == len(sample_files)
80
+ success_count = sum(1 for result in results if result[0] == "success")
81
+ assert success_count > 0
82
+
83
+ def test_audio_file_context_manager(self, sample_mp3_file: Path):
84
+ with _AudioFile(sample_mp3_file) as audio_file:
85
+ # Test that we can read metadata within context using file path
86
+ metadata = get_unified_metadata(audio_file.file_path)
87
+ assert isinstance(metadata, dict)
88
+
89
+ # Test that we can get technical info within context using file path
90
+ bitrate = get_bitrate(audio_file.file_path)
91
+ duration = get_duration_in_sec(audio_file.file_path)
92
+ assert isinstance(bitrate, int)
93
+ assert isinstance(duration, float)
94
+
95
+ def test_cross_format_deletion_workflow(self, sample_mp3_file, sample_flac_file, sample_wav_file):
96
+ # E2E test for deletion across multiple formats
97
+ test_metadata = {
98
+ UnifiedMetadataKey.TITLE: "Cross Format Deletion",
99
+ UnifiedMetadataKey.ARTISTS: ["Cross Format Artist"],
100
+ UnifiedMetadataKey.ALBUM: "Cross Format Album",
101
+ }
102
+
103
+ sample_files = [(sample_mp3_file, "mp3"), (sample_flac_file, "flac"), (sample_wav_file, "wav")]
104
+
105
+ for _file_path, format_type in sample_files:
106
+ # Set up metadata using external script
107
+ initial_metadata = {"title": "Original Title", "artist": "Original Artist"}
108
+ with temp_file_with_metadata(initial_metadata, format_type) as test_file:
109
+ # Add metadata using app's function
110
+ update_metadata(test_file, test_metadata)
111
+
112
+ # Verify metadata was added
113
+ added_metadata = get_unified_metadata(test_file)
114
+ assert added_metadata.get(UnifiedMetadataKey.TITLE) == "Cross Format Deletion"
115
+
116
+ # Delete all metadata
117
+ delete_result = delete_all_metadata(test_file)
118
+ assert delete_result is True
119
+
120
+ # Verify metadata was deleted
121
+ deleted_metadata = get_unified_metadata(test_file)
122
+ assert (
123
+ deleted_metadata.get(UnifiedMetadataKey.TITLE) is None
124
+ or deleted_metadata.get(UnifiedMetadataKey.TITLE) != "Cross Format Deletion"
125
+ )
126
+
127
+ def test_metadata_cleanup_workflow(self):
128
+ # E2E test for complete metadata cleanup workflow
129
+ initial_metadata = {
130
+ "title": "Cleanup Test Title",
131
+ "artist": "Cleanup Test Artist",
132
+ "album": "Cleanup Test Album",
133
+ "year": "2023",
134
+ "genre": "Electronic",
135
+ }
136
+
137
+ with temp_file_with_metadata(initial_metadata, "mp3") as test_file:
138
+ # 1. Verify initial metadata exists
139
+ initial_metadata_result = get_unified_metadata(test_file)
140
+ assert initial_metadata_result.get(UnifiedMetadataKey.TITLE) == "Cleanup Test Title"
141
+
142
+ # 2. Add more metadata
143
+ additional_metadata = {
144
+ UnifiedMetadataKey.RATING: 80,
145
+ UnifiedMetadataKey.BPM: 128,
146
+ UnifiedMetadataKey.COMMENT: "Cleanup test comment",
147
+ }
148
+ update_metadata(test_file, additional_metadata, normalized_rating_max_value=100)
149
+
150
+ # 3. Verify all metadata exists
151
+ full_metadata = get_unified_metadata(test_file, normalized_rating_max_value=100)
152
+ assert full_metadata.get(UnifiedMetadataKey.RATING) == 80
153
+ assert full_metadata.get(UnifiedMetadataKey.BPM) == 128
154
+
155
+ # 4. Complete cleanup - delete all metadata
156
+ delete_result = delete_all_metadata(test_file)
157
+ assert delete_result is True
158
+
159
+ # 5. Verify complete cleanup
160
+ cleaned_metadata = get_unified_metadata(test_file)
161
+ assert (
162
+ cleaned_metadata.get(UnifiedMetadataKey.TITLE) is None
163
+ or cleaned_metadata.get(UnifiedMetadataKey.TITLE) != "Cleanup Test Title"
164
+ )
165
+ assert cleaned_metadata.get(UnifiedMetadataKey.RATING) is None
166
+ assert cleaned_metadata.get(UnifiedMetadataKey.BPM) is None