audiometa-python 0.2.2__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 (318) hide show
  1. audiometa/__init__.py +1240 -0
  2. audiometa/__main__.py +6 -0
  3. audiometa/_audio_file.py +602 -0
  4. audiometa/cli.py +347 -0
  5. audiometa/exceptions.py +167 -0
  6. audiometa/manager/_MetadataManager.py +665 -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 +945 -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 +778 -0
  14. audiometa/manager/_rating_supporting/riff/__init__.py +25 -0
  15. audiometa/manager/_rating_supporting/riff/_riff_constants.py +10 -0
  16. audiometa/manager/_rating_supporting/vorbis/_VorbisManager.py +525 -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 +305 -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 +188 -0
  40. audiometa/test/helpers/id3v2/id3v2_metadata_setter.py +428 -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 +216 -0
  44. audiometa/test/helpers/riff/riff_metadata_deleter.py +56 -0
  45. audiometa/test/helpers/riff/riff_metadata_getter.py +118 -0
  46. audiometa/test/helpers/riff/riff_metadata_setter.py +196 -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 +200 -0
  55. audiometa/test/tests/__init__.py +0 -0
  56. audiometa/test/tests/conftest.py +261 -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/test_cli_delete.py +20 -0
  69. audiometa/test/tests/e2e/cli/test_cli_formatting.py +31 -0
  70. audiometa/test/tests/e2e/cli/test_cli_help.py +40 -0
  71. audiometa/test/tests/e2e/cli/test_cli_read.py +79 -0
  72. audiometa/test/tests/e2e/cli/test_cli_unified.py +33 -0
  73. audiometa/test/tests/e2e/cli/test_cli_write.py +116 -0
  74. audiometa/test/tests/e2e/scenarios/__init__.py +0 -0
  75. audiometa/test/tests/e2e/scenarios/test_user_scenarios.py +166 -0
  76. audiometa/test/tests/e2e/workflows/__init__.py +0 -0
  77. audiometa/test/tests/e2e/workflows/test_core_workflows.py +166 -0
  78. audiometa/test/tests/e2e/workflows/test_deletion_workflows.py +318 -0
  79. audiometa/test/tests/e2e/workflows/test_error_handling_workflows.py +165 -0
  80. audiometa/test/tests/e2e/workflows/test_format_specific_workflows.py +129 -0
  81. audiometa/test/tests/e2e/workflows/test_rating_workflows.py +124 -0
  82. audiometa/test/tests/integration/__init__.py +0 -0
  83. audiometa/test/tests/integration/audio_format/__init__.py +0 -0
  84. audiometa/test/tests/integration/audio_format/flac/__init__.py +0 -0
  85. audiometa/test/tests/integration/audio_format/flac/test_flac_delete_all.py +108 -0
  86. audiometa/test/tests/integration/audio_format/flac/test_flac_reading_all.py +61 -0
  87. audiometa/test/tests/integration/audio_format/flac/test_flac_reading_field.py +65 -0
  88. audiometa/test/tests/integration/audio_format/flac/test_flac_writing.py +69 -0
  89. audiometa/test/tests/integration/audio_format/mp3/__init__.py +0 -0
  90. audiometa/test/tests/integration/audio_format/mp3/test_mp3_delete_all.py +79 -0
  91. audiometa/test/tests/integration/audio_format/mp3/test_mp3_reading_all.py +61 -0
  92. audiometa/test/tests/integration/audio_format/mp3/test_mp3_reading_field.py +67 -0
  93. audiometa/test/tests/integration/audio_format/mp3/test_mp3_writing.py +60 -0
  94. audiometa/test/tests/integration/audio_format/wav/__init__.py +0 -0
  95. audiometa/test/tests/integration/audio_format/wav/test_wav_delete_all.py +87 -0
  96. audiometa/test/tests/integration/audio_format/wav/test_wav_reading_all.py +62 -0
  97. audiometa/test/tests/integration/audio_format/wav/test_wav_reading_field.py +57 -0
  98. audiometa/test/tests/integration/audio_format/wav/test_wav_with_id3v2_tags.py +83 -0
  99. audiometa/test/tests/integration/audio_format/wav/test_wav_writing.py +62 -0
  100. audiometa/test/tests/integration/conftest.py +29 -0
  101. audiometa/test/tests/integration/delete_all_metadata/__init__.py +1 -0
  102. audiometa/test/tests/integration/delete_all_metadata/test_audio_format_all.py +102 -0
  103. audiometa/test/tests/integration/delete_all_metadata/test_audio_format_header_deletion.py +77 -0
  104. audiometa/test/tests/integration/delete_all_metadata/test_basic_functionality.py +47 -0
  105. audiometa/test/tests/integration/delete_all_metadata/test_error_handling.py +24 -0
  106. audiometa/test/tests/integration/encoding/__init__.py +1 -0
  107. audiometa/test/tests/integration/encoding/test_encoding.py +88 -0
  108. audiometa/test/tests/integration/encoding/test_special_characters_edge_cases.py +223 -0
  109. audiometa/test/tests/integration/get_full_metadata/__init__.py +0 -0
  110. audiometa/test/tests/integration/get_full_metadata/test_binary_data_filtering.py +250 -0
  111. audiometa/test/tests/integration/get_full_metadata/test_get_full_metadata.py +297 -0
  112. audiometa/test/tests/integration/get_full_metadata/test_get_full_metadata_edge_cases.py +231 -0
  113. audiometa/test/tests/integration/get_full_metadata/test_options.py +207 -0
  114. audiometa/test/tests/integration/get_full_metadata/test_parsed_fields_keys.py +85 -0
  115. audiometa/test/tests/integration/metadata_field/__init__.py +0 -0
  116. audiometa/test/tests/integration/metadata_field/album/__init__.py +0 -0
  117. audiometa/test/tests/integration/metadata_field/album/test_deleting.py +73 -0
  118. audiometa/test/tests/integration/metadata_field/album/test_reading.py +36 -0
  119. audiometa/test/tests/integration/metadata_field/album/test_writing.py +50 -0
  120. audiometa/test/tests/integration/metadata_field/album_artists/__init__.py +0 -0
  121. audiometa/test/tests/integration/metadata_field/album_artists/test_deleting.py +83 -0
  122. audiometa/test/tests/integration/metadata_field/album_artists/test_reading.py +38 -0
  123. audiometa/test/tests/integration/metadata_field/album_artists/test_writing.py +52 -0
  124. audiometa/test/tests/integration/metadata_field/artists/__init__.py +0 -0
  125. audiometa/test/tests/integration/metadata_field/artists/test_deleting.py +68 -0
  126. audiometa/test/tests/integration/metadata_field/artists/test_reading.py +36 -0
  127. audiometa/test/tests/integration/metadata_field/artists/test_writing.py +46 -0
  128. audiometa/test/tests/integration/metadata_field/bpm/__init__.py +0 -0
  129. audiometa/test/tests/integration/metadata_field/bpm/test_deleting.py +75 -0
  130. audiometa/test/tests/integration/metadata_field/bpm/test_reading.py +32 -0
  131. audiometa/test/tests/integration/metadata_field/bpm/test_writing.py +56 -0
  132. audiometa/test/tests/integration/metadata_field/comment/__init__.py +0 -0
  133. audiometa/test/tests/integration/metadata_field/comment/test_deleting.py +68 -0
  134. audiometa/test/tests/integration/metadata_field/comment/test_reading.py +36 -0
  135. audiometa/test/tests/integration/metadata_field/comment/test_writing.py +49 -0
  136. audiometa/test/tests/integration/metadata_field/composer/__init__.py +0 -0
  137. audiometa/test/tests/integration/metadata_field/composer/test_deleting.py +75 -0
  138. audiometa/test/tests/integration/metadata_field/composer/test_reading.py +34 -0
  139. audiometa/test/tests/integration/metadata_field/composer/test_writing.py +41 -0
  140. audiometa/test/tests/integration/metadata_field/copyright/__init__.py +0 -0
  141. audiometa/test/tests/integration/metadata_field/copyright/test_deleting.py +81 -0
  142. audiometa/test/tests/integration/metadata_field/copyright/test_reading.py +35 -0
  143. audiometa/test/tests/integration/metadata_field/copyright/test_writing.py +41 -0
  144. audiometa/test/tests/integration/metadata_field/field_not_supported/__init__.py +0 -0
  145. audiometa/test/tests/integration/metadata_field/field_not_supported/test_deleting.py +56 -0
  146. audiometa/test/tests/integration/metadata_field/field_not_supported/test_reading.py +54 -0
  147. audiometa/test/tests/integration/metadata_field/field_not_supported/test_writing.py +61 -0
  148. audiometa/test/tests/integration/metadata_field/genre/__init__.py +0 -0
  149. audiometa/test/tests/integration/metadata_field/genre/reading/__init__.py +0 -0
  150. audiometa/test/tests/integration/metadata_field/genre/reading/metadata_format/__init__.py +0 -0
  151. audiometa/test/tests/integration/metadata_field/genre/reading/metadata_format/test_id3v1_reading.py +65 -0
  152. audiometa/test/tests/integration/metadata_field/genre/reading/metadata_format/test_id3v2_reading.py +25 -0
  153. audiometa/test/tests/integration/metadata_field/genre/reading/metadata_format/test_riff_reading.py +58 -0
  154. audiometa/test/tests/integration/metadata_field/genre/reading/metadata_format/test_vorbis_reading.py +61 -0
  155. audiometa/test/tests/integration/metadata_field/genre/reading/test_smart_reading.py +191 -0
  156. audiometa/test/tests/integration/metadata_field/genre/test_deleting.py +62 -0
  157. audiometa/test/tests/integration/metadata_field/genre/test_writing.py +64 -0
  158. audiometa/test/tests/integration/metadata_field/language/__init__.py +0 -0
  159. audiometa/test/tests/integration/metadata_field/language/test_deleting.py +75 -0
  160. audiometa/test/tests/integration/metadata_field/language/test_reading.py +39 -0
  161. audiometa/test/tests/integration/metadata_field/language/test_writing.py +43 -0
  162. audiometa/test/tests/integration/metadata_field/lyrics/__init__.py +0 -0
  163. audiometa/test/tests/integration/metadata_field/lyrics/test_deleting.py +129 -0
  164. audiometa/test/tests/integration/metadata_field/lyrics/test_reading.py +57 -0
  165. audiometa/test/tests/integration/metadata_field/lyrics/test_writing.py +59 -0
  166. audiometa/test/tests/integration/metadata_field/publisher/__init__.py +0 -0
  167. audiometa/test/tests/integration/metadata_field/publisher/test_deleting.py +88 -0
  168. audiometa/test/tests/integration/metadata_field/publisher/test_reading.py +32 -0
  169. audiometa/test/tests/integration/metadata_field/publisher/test_writing.py +47 -0
  170. audiometa/test/tests/integration/metadata_field/rating/__init__.py +0 -0
  171. audiometa/test/tests/integration/metadata_field/rating/reading/__init__.py +0 -0
  172. audiometa/test/tests/integration/metadata_field/rating/reading/test_base_100_proportional.py +81 -0
  173. audiometa/test/tests/integration/metadata_field/rating/reading/test_base_255_non_proportional.py +33 -0
  174. audiometa/test/tests/integration/metadata_field/rating/reading/test_base_255_proportional.py +58 -0
  175. audiometa/test/tests/integration/metadata_field/rating/test_deleting.py +117 -0
  176. audiometa/test/tests/integration/metadata_field/rating/test_error_handling.py +137 -0
  177. audiometa/test/tests/integration/metadata_field/rating/writing/__init__.py +0 -0
  178. audiometa/test/tests/integration/metadata_field/rating/writing/metadata_format/__init__.py +0 -0
  179. audiometa/test/tests/integration/metadata_field/rating/writing/metadata_format/test_id3v2.py +77 -0
  180. audiometa/test/tests/integration/metadata_field/rating/writing/metadata_format/test_riff.py +55 -0
  181. audiometa/test/tests/integration/metadata_field/rating/writing/metadata_format/test_vorbis.py +57 -0
  182. audiometa/test/tests/integration/metadata_field/rating/writing/test_comprehensive.py +192 -0
  183. audiometa/test/tests/integration/metadata_field/release_date/__init__.py +0 -0
  184. audiometa/test/tests/integration/metadata_field/release_date/test_deleting.py +74 -0
  185. audiometa/test/tests/integration/metadata_field/release_date/test_error_handling.py +82 -0
  186. audiometa/test/tests/integration/metadata_field/release_date/test_reading.py +59 -0
  187. audiometa/test/tests/integration/metadata_field/release_date/test_writing.py +49 -0
  188. audiometa/test/tests/integration/metadata_field/test_metadata_field_validation.py +134 -0
  189. audiometa/test/tests/integration/metadata_field/title/__init__.py +0 -0
  190. audiometa/test/tests/integration/metadata_field/title/test_deleting.py +73 -0
  191. audiometa/test/tests/integration/metadata_field/title/test_error_handling.py +47 -0
  192. audiometa/test/tests/integration/metadata_field/title/test_reading.py +36 -0
  193. audiometa/test/tests/integration/metadata_field/title/test_writing.py +64 -0
  194. audiometa/test/tests/integration/metadata_field/track_number/__init__.py +0 -0
  195. audiometa/test/tests/integration/metadata_field/track_number/reading/__init__.py +0 -0
  196. audiometa/test/tests/integration/metadata_field/track_number/reading/test_edge_cases.py +43 -0
  197. audiometa/test/tests/integration/metadata_field/track_number/reading/test_metadata_format.py +32 -0
  198. audiometa/test/tests/integration/metadata_field/track_number/test_deleting.py +59 -0
  199. audiometa/test/tests/integration/metadata_field/track_number/test_writing.py +73 -0
  200. audiometa/test/tests/integration/multiple_values/__init__.py +1 -0
  201. audiometa/test/tests/integration/multiple_values/reading/__init__.py +1 -0
  202. audiometa/test/tests/integration/multiple_values/reading/metadata_format/__init__.py +1 -0
  203. audiometa/test/tests/integration/multiple_values/reading/metadata_format/test_id3v1.py +23 -0
  204. audiometa/test/tests/integration/multiple_values/reading/metadata_format/test_id3v2_3.py +92 -0
  205. audiometa/test/tests/integration/multiple_values/reading/metadata_format/test_id3v2_4.py +216 -0
  206. audiometa/test/tests/integration/multiple_values/reading/metadata_format/test_riff.py +84 -0
  207. audiometa/test/tests/integration/multiple_values/reading/metadata_format/test_vorbis.py +169 -0
  208. audiometa/test/tests/integration/multiple_values/reading/test_performance_large_data.py +209 -0
  209. audiometa/test/tests/integration/multiple_values/reading/test_smart_parsing_scenarios.py +198 -0
  210. audiometa/test/tests/integration/multiple_values/reading/test_unicode_handling.py +24 -0
  211. audiometa/test/tests/integration/multiple_values/writing/__init__.py +1 -0
  212. audiometa/test/tests/integration/multiple_values/writing/metadata_format/__init__.py +1 -0
  213. audiometa/test/tests/integration/multiple_values/writing/metadata_format/test_id3v1.py +62 -0
  214. audiometa/test/tests/integration/multiple_values/writing/metadata_format/test_id3v2_3.py +36 -0
  215. audiometa/test/tests/integration/multiple_values/writing/metadata_format/test_id3v2_4.py +34 -0
  216. audiometa/test/tests/integration/multiple_values/writing/metadata_format/test_riff.py +32 -0
  217. audiometa/test/tests/integration/multiple_values/writing/metadata_format/test_vorbis.py +54 -0
  218. audiometa/test/tests/integration/multiple_values/writing/test_error_handling.py +42 -0
  219. audiometa/test/tests/integration/multiple_values/writing/test_large_values.py +98 -0
  220. audiometa/test/tests/integration/reading/__init__.py +1 -0
  221. audiometa/test/tests/integration/reading/test_read_multiple_metadata.py +80 -0
  222. audiometa/test/tests/integration/reading/test_reading_error_handling.py +36 -0
  223. audiometa/test/tests/integration/real_audio_files/__init__.py +0 -0
  224. audiometa/test/tests/integration/real_audio_files/test_reading.py +146 -0
  225. audiometa/test/tests/integration/real_audio_files/test_writing.py +198 -0
  226. audiometa/test/tests/integration/technical_info/__init__.py +0 -0
  227. audiometa/test/tests/integration/technical_info/flac_md5/__init__.py +0 -0
  228. audiometa/test/tests/integration/technical_info/flac_md5/conftest.py +103 -0
  229. audiometa/test/tests/integration/technical_info/flac_md5/test_invalid_md5/__init__.py +0 -0
  230. audiometa/test/tests/integration/technical_info/flac_md5/test_invalid_md5/test_audio_data_corruption.py +21 -0
  231. audiometa/test/tests/integration/technical_info/flac_md5/test_invalid_md5/test_flipped_md5.py +29 -0
  232. audiometa/test/tests/integration/technical_info/flac_md5/test_invalid_md5/test_non_flac_error.py +13 -0
  233. audiometa/test/tests/integration/technical_info/flac_md5/test_invalid_md5/test_partial_md5.py +29 -0
  234. audiometa/test/tests/integration/technical_info/flac_md5/test_invalid_md5/test_random_md5.py +29 -0
  235. audiometa/test/tests/integration/technical_info/flac_md5/test_invalid_md5/test_unset_md5.py +56 -0
  236. audiometa/test/tests/integration/technical_info/flac_md5/test_valid_md5.py +21 -0
  237. audiometa/test/tests/integration/technical_info/test_bitrate.py +38 -0
  238. audiometa/test/tests/integration/technical_info/test_channels.py +38 -0
  239. audiometa/test/tests/integration/technical_info/test_duration_in_sec.py +38 -0
  240. audiometa/test/tests/integration/technical_info/test_sample_rate.py +40 -0
  241. audiometa/test/tests/integration/test_audio_file.py +23 -0
  242. audiometa/test/tests/integration/test_audio_format_readable_after_update_all_metadata_formats.py +95 -0
  243. audiometa/test/tests/integration/writing/__init__.py +0 -0
  244. audiometa/test/tests/integration/writing/test_error_handling.py +44 -0
  245. audiometa/test/tests/integration/writing/test_forced_format.py +224 -0
  246. audiometa/test/tests/integration/writing/test_multiple_format_preservation.py +223 -0
  247. audiometa/test/tests/integration/writing/test_partial_update.py +36 -0
  248. audiometa/test/tests/integration/writing/writing_strategies/__init__.py +0 -0
  249. audiometa/test/tests/integration/writing/writing_strategies/test_cleanup_strategy.py +79 -0
  250. audiometa/test/tests/integration/writing/writing_strategies/test_preserve_strategy.py +76 -0
  251. audiometa/test/tests/integration/writing/writing_strategies/test_sync_strategy.py +215 -0
  252. audiometa/test/tests/integration/writing/writing_strategies/unsupported_fields/__init__.py +0 -0
  253. audiometa/test/tests/integration/writing/writing_strategies/unsupported_fields/test_fail_behavior.py +42 -0
  254. audiometa/test/tests/integration/writing/writing_strategies/unsupported_fields/test_no_writing_on_failure.py +93 -0
  255. audiometa/test/tests/integration/writing/writing_strategies/unsupported_fields/test_strategy_specific.py +99 -0
  256. audiometa/test/tests/unit/__init__.py +0 -0
  257. audiometa/test/tests/unit/audio_file/__init__.py +0 -0
  258. audiometa/test/tests/unit/audio_file/technical_info/__init__.py +0 -0
  259. audiometa/test/tests/unit/audio_file/technical_info/test_bitrate.py +26 -0
  260. audiometa/test/tests/unit/audio_file/technical_info/test_channels.py +31 -0
  261. audiometa/test/tests/unit/audio_file/technical_info/test_duration_in_sec.py +38 -0
  262. audiometa/test/tests/unit/audio_file/technical_info/test_error_handling.py +130 -0
  263. audiometa/test/tests/unit/audio_file/technical_info/test_file_size.py +51 -0
  264. audiometa/test/tests/unit/audio_file/technical_info/test_format_name.py +28 -0
  265. audiometa/test/tests/unit/audio_file/technical_info/test_sample_rate.py +31 -0
  266. audiometa/test/tests/unit/audio_file/test_context_manager.py +30 -0
  267. audiometa/test/tests/unit/audio_file/test_file_validation.py +40 -0
  268. audiometa/test/tests/unit/audio_file/test_operations.py +20 -0
  269. audiometa/test/tests/unit/audio_file/test_path_handling.py +23 -0
  270. audiometa/test/tests/unit/cli/__init__.py +0 -0
  271. audiometa/test/tests/unit/cli/test_expand_file_patterns.py +234 -0
  272. audiometa/test/tests/unit/metadata_managers/__init__.py +0 -0
  273. audiometa/test/tests/unit/metadata_managers/conftest.py +142 -0
  274. audiometa/test/tests/unit/metadata_managers/header_info/__init__.py +0 -0
  275. audiometa/test/tests/unit/metadata_managers/header_info/test_id3v1.py +49 -0
  276. audiometa/test/tests/unit/metadata_managers/header_info/test_id3v2.py +66 -0
  277. audiometa/test/tests/unit/metadata_managers/header_info/test_riff.py +57 -0
  278. audiometa/test/tests/unit/metadata_managers/header_info/test_vorbis.py +53 -0
  279. audiometa/test/tests/unit/metadata_managers/metadata_field/__init__.py +0 -0
  280. audiometa/test/tests/unit/metadata_managers/metadata_field/multiple_values/__init__.py +0 -0
  281. audiometa/test/tests/unit/metadata_managers/metadata_field/multiple_values/reading/__init__.py +0 -0
  282. audiometa/test/tests/unit/metadata_managers/metadata_field/multiple_values/reading/test_smart_parsing.py +186 -0
  283. audiometa/test/tests/unit/metadata_managers/metadata_field/multiple_values/writing/__init__.py +0 -0
  284. audiometa/test/tests/unit/metadata_managers/metadata_field/multiple_values/writing/test_separator_selection.py +142 -0
  285. audiometa/test/tests/unit/metadata_managers/metadata_field/multiple_values/writing/test_value_filtering.py +76 -0
  286. audiometa/test/tests/unit/metadata_managers/metadata_field/rating/__init__.py +0 -0
  287. audiometa/test/tests/unit/metadata_managers/metadata_field/rating/reading/__init__.py +0 -0
  288. audiometa/test/tests/unit/metadata_managers/metadata_field/rating/reading/test_normalization.py +152 -0
  289. audiometa/test/tests/unit/metadata_managers/metadata_field/rating/reading/test_profiles_values.py +23 -0
  290. audiometa/test/tests/unit/metadata_managers/metadata_field/rating/test_rating_validation.py +77 -0
  291. audiometa/test/tests/unit/metadata_managers/metadata_field/rating/writing/__init__.py +0 -0
  292. audiometa/test/tests/unit/metadata_managers/metadata_field/rating/writing/test_validation.py +151 -0
  293. audiometa/test/tests/unit/metadata_managers/metadata_field/rating/writing/test_writing_profiles.py +61 -0
  294. audiometa/test/tests/unit/metadata_managers/metadata_field/test_date_format_validation.py +135 -0
  295. audiometa/test/tests/unit/metadata_managers/metadata_field/test_track_number_validation.py +46 -0
  296. audiometa/test/tests/unit/metadata_managers/metadata_field/test_type_validation_exception.py +22 -0
  297. audiometa/test/tests/unit/metadata_managers/metadata_field/test_validation.py +83 -0
  298. audiometa/test/tests/unit/metadata_managers/test_metadata_format_managers_write_and_read.py +74 -0
  299. audiometa/utils/__init__.py +1 -0
  300. audiometa/utils/id3v1_genre_code_map.py +205 -0
  301. audiometa/utils/metadata_format.py +31 -0
  302. audiometa/utils/metadata_writing_strategy.py +16 -0
  303. audiometa/utils/os_dependencies_checker/__init__.py +24 -0
  304. audiometa/utils/os_dependencies_checker/base.py +62 -0
  305. audiometa/utils/os_dependencies_checker/config.py +51 -0
  306. audiometa/utils/os_dependencies_checker/macos.py +236 -0
  307. audiometa/utils/os_dependencies_checker/ubuntu.py +95 -0
  308. audiometa/utils/os_dependencies_checker/windows.py +227 -0
  309. audiometa/utils/rating_profiles.py +110 -0
  310. audiometa/utils/tool_path_resolver.py +135 -0
  311. audiometa/utils/types.py +82 -0
  312. audiometa/utils/unified_metadata_key.py +81 -0
  313. audiometa_python-0.2.2.dist-info/METADATA +1464 -0
  314. audiometa_python-0.2.2.dist-info/RECORD +318 -0
  315. audiometa_python-0.2.2.dist-info/WHEEL +5 -0
  316. audiometa_python-0.2.2.dist-info/entry_points.txt +2 -0
  317. audiometa_python-0.2.2.dist-info/licenses/LICENSE +202 -0
  318. audiometa_python-0.2.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,318 @@
1
+ """End-to-end tests for metadata deletion workflows.
2
+
3
+ These tests verify complete deletion workflows that real users would perform, including full metadata deletion, partial
4
+ deletion, and cross-format deletion.
5
+ """
6
+
7
+ import pytest
8
+
9
+ from audiometa import delete_all_metadata, get_unified_metadata, update_metadata
10
+ from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
11
+ from audiometa.utils.metadata_format import MetadataFormat
12
+ from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
13
+
14
+
15
+ @pytest.mark.e2e
16
+ class TestDeletionWorkflows:
17
+ def test_complete_metadata_deletion_workflow_mp3(self):
18
+ # Complete e2e deletion workflow for MP3
19
+ initial_metadata = {
20
+ "title": "Original Title",
21
+ "artist": "Original Artist",
22
+ "album": "Original Album",
23
+ "year": "2023",
24
+ "genre": "Rock",
25
+ "comment": "Original comment",
26
+ }
27
+
28
+ with temp_file_with_metadata(initial_metadata, "mp3") as test_file:
29
+ # 1. Verify initial metadata exists
30
+ initial_metadata_result = get_unified_metadata(test_file)
31
+ assert initial_metadata_result.get(UnifiedMetadataKey.TITLE) == "Original Title"
32
+ assert initial_metadata_result.get(UnifiedMetadataKey.ARTISTS) == ["Original Artist"]
33
+ assert initial_metadata_result.get(UnifiedMetadataKey.ALBUM) == "Original Album"
34
+
35
+ # 2. Add more metadata using app's function
36
+ additional_metadata = {
37
+ UnifiedMetadataKey.RATING: 80,
38
+ UnifiedMetadataKey.BPM: 120,
39
+ UnifiedMetadataKey.COMMENT: "Updated comment",
40
+ }
41
+ update_metadata(test_file, additional_metadata, normalized_rating_max_value=100)
42
+
43
+ # 3. Verify metadata was added
44
+ updated_metadata = get_unified_metadata(test_file, normalized_rating_max_value=100)
45
+ assert updated_metadata.get(UnifiedMetadataKey.RATING) == 80
46
+ assert updated_metadata.get(UnifiedMetadataKey.BPM) == 120
47
+
48
+ # 4. Delete all metadata
49
+ delete_result = delete_all_metadata(test_file)
50
+ assert delete_result is True
51
+
52
+ # 5. Verify all metadata was deleted
53
+ deleted_metadata = get_unified_metadata(test_file)
54
+ assert (
55
+ deleted_metadata.get(UnifiedMetadataKey.TITLE) is None
56
+ or deleted_metadata.get(UnifiedMetadataKey.TITLE) != "Original Title"
57
+ )
58
+ assert deleted_metadata.get(UnifiedMetadataKey.ARTISTS) is None or deleted_metadata.get(
59
+ UnifiedMetadataKey.ARTISTS
60
+ ) != ["Original Artist"]
61
+ assert (
62
+ deleted_metadata.get(UnifiedMetadataKey.ALBUM) is None
63
+ or deleted_metadata.get(UnifiedMetadataKey.ALBUM) != "Original Album"
64
+ )
65
+ assert deleted_metadata.get(UnifiedMetadataKey.RATING) is None
66
+ assert deleted_metadata.get(UnifiedMetadataKey.BPM) is None
67
+
68
+ def test_complete_metadata_deletion_workflow_flac(self):
69
+ # Complete e2e deletion workflow for FLAC
70
+ initial_metadata = {
71
+ "title": "FLAC Original Title",
72
+ "artist": "FLAC Original Artist",
73
+ "album": "FLAC Original Album",
74
+ }
75
+
76
+ with temp_file_with_metadata(initial_metadata, "flac") as test_file:
77
+ # 1. Verify initial metadata exists
78
+ initial_metadata_result = get_unified_metadata(test_file)
79
+ assert initial_metadata_result.get(UnifiedMetadataKey.TITLE) == "FLAC Original Title"
80
+
81
+ # 2. Add more metadata
82
+ additional_metadata = {
83
+ UnifiedMetadataKey.RATING: 90,
84
+ UnifiedMetadataKey.BPM: 140,
85
+ UnifiedMetadataKey.COMMENT: "FLAC comment",
86
+ }
87
+ update_metadata(test_file, additional_metadata, normalized_rating_max_value=100)
88
+
89
+ # 3. Delete all metadata
90
+ delete_result = delete_all_metadata(test_file)
91
+ assert delete_result is True
92
+
93
+ # 4. Verify all metadata was deleted
94
+ deleted_metadata = get_unified_metadata(test_file)
95
+ assert (
96
+ deleted_metadata.get(UnifiedMetadataKey.TITLE) is None
97
+ or deleted_metadata.get(UnifiedMetadataKey.TITLE) != "FLAC Original Title"
98
+ )
99
+ assert deleted_metadata.get(UnifiedMetadataKey.RATING) is None
100
+
101
+ def test_complete_metadata_deletion_workflow_wav(self):
102
+ # Complete e2e deletion workflow for WAV
103
+ initial_metadata = {
104
+ "title": "WAV Original Title",
105
+ "artist": "WAV Original Artist",
106
+ "album": "WAV Original Album",
107
+ }
108
+
109
+ with temp_file_with_metadata(initial_metadata, "wav") as test_file:
110
+ # 1. Verify initial metadata exists
111
+ initial_metadata_result = get_unified_metadata(test_file)
112
+ assert initial_metadata_result.get(UnifiedMetadataKey.TITLE) == "WAV Original Title"
113
+
114
+ # 2. Add more metadata (WAV doesn't support rating/BPM)
115
+ additional_metadata = {UnifiedMetadataKey.COMMENT: "WAV comment"}
116
+ update_metadata(test_file, additional_metadata)
117
+
118
+ # 3. Delete all metadata
119
+ delete_result = delete_all_metadata(test_file)
120
+ assert delete_result is True
121
+
122
+ # 4. Verify all metadata was deleted
123
+ deleted_metadata = get_unified_metadata(test_file)
124
+ assert (
125
+ deleted_metadata.get(UnifiedMetadataKey.TITLE) is None
126
+ or deleted_metadata.get(UnifiedMetadataKey.TITLE) != "WAV Original Title"
127
+ )
128
+
129
+ def test_partial_metadata_deletion_workflow(self):
130
+ # E2e test for deleting specific metadata fields
131
+ initial_metadata = {
132
+ "title": "Partial Deletion Title",
133
+ "artist": "Partial Deletion Artist",
134
+ "album": "Partial Deletion Album",
135
+ "year": "2023",
136
+ "genre": "Jazz",
137
+ }
138
+
139
+ with temp_file_with_metadata(initial_metadata, "mp3") as test_file:
140
+ # 1. Verify initial metadata
141
+ initial_metadata_result = get_unified_metadata(test_file)
142
+ assert initial_metadata_result.get(UnifiedMetadataKey.TITLE) == "Partial Deletion Title"
143
+ assert initial_metadata_result.get(UnifiedMetadataKey.ARTISTS) == ["Partial Deletion Artist"]
144
+ assert initial_metadata_result.get(UnifiedMetadataKey.ALBUM) == "Partial Deletion Album"
145
+
146
+ # 2. Delete specific fields by setting them to None
147
+ deletion_metadata = {UnifiedMetadataKey.TITLE: None, UnifiedMetadataKey.ARTISTS: None}
148
+ update_metadata(test_file, deletion_metadata)
149
+
150
+ # 3. Verify specific fields were deleted while others remain
151
+ updated_metadata = get_unified_metadata(test_file)
152
+ assert updated_metadata.get(UnifiedMetadataKey.TITLE) is None
153
+ assert updated_metadata.get(UnifiedMetadataKey.ARTISTS) is None
154
+ assert updated_metadata.get(UnifiedMetadataKey.ALBUM) == "Partial Deletion Album" # Should remain
155
+
156
+ # 4. Delete remaining metadata
157
+ remaining_deletion = {
158
+ UnifiedMetadataKey.ALBUM: None,
159
+ UnifiedMetadataKey.RELEASE_DATE: None,
160
+ UnifiedMetadataKey.GENRES_NAMES: None,
161
+ }
162
+ update_metadata(test_file, remaining_deletion)
163
+
164
+ # 5. Verify all metadata is now deleted
165
+ final_metadata = get_unified_metadata(test_file)
166
+ assert final_metadata.get(UnifiedMetadataKey.ALBUM) is None
167
+ assert final_metadata.get(UnifiedMetadataKey.RELEASE_DATE) is None
168
+ assert final_metadata.get(UnifiedMetadataKey.GENRES_NAMES) is None
169
+
170
+ def test_cross_format_deletion_consistency(self, sample_mp3_file, sample_flac_file, sample_wav_file):
171
+ # E2e test for deletion consistency across formats
172
+ test_metadata = {
173
+ UnifiedMetadataKey.TITLE: "Cross Format Deletion Test",
174
+ UnifiedMetadataKey.ARTISTS: ["Test Artist"],
175
+ UnifiedMetadataKey.ALBUM: "Test Album",
176
+ }
177
+
178
+ sample_files = [(sample_mp3_file, "mp3"), (sample_flac_file, "flac"), (sample_wav_file, "wav")]
179
+
180
+ for _file_path, format_type in sample_files:
181
+ # Set up metadata using external script
182
+ initial_metadata = {"title": "Original Title", "artist": "Original Artist"}
183
+ with temp_file_with_metadata(initial_metadata, format_type) as test_file:
184
+ # Add metadata using app's function
185
+ update_metadata(test_file, test_metadata)
186
+
187
+ # Verify metadata was added
188
+ added_metadata = get_unified_metadata(test_file)
189
+ assert added_metadata.get(UnifiedMetadataKey.TITLE) == "Cross Format Deletion Test"
190
+
191
+ # Delete all metadata
192
+ delete_result = delete_all_metadata(test_file)
193
+ assert delete_result is True
194
+
195
+ # Verify metadata was deleted consistently across formats
196
+ deleted_metadata = get_unified_metadata(test_file)
197
+ assert (
198
+ deleted_metadata.get(UnifiedMetadataKey.TITLE) is None
199
+ or deleted_metadata.get(UnifiedMetadataKey.TITLE) != "Cross Format Deletion Test"
200
+ )
201
+
202
+ def test_format_specific_deletion_workflow(self):
203
+ # E2e test for deleting specific metadata formats
204
+ initial_metadata = {"title": "Format Specific Deletion", "artist": "Format Specific Artist"}
205
+
206
+ with temp_file_with_metadata(initial_metadata, "mp3") as test_file:
207
+ # 1. Add metadata in different formats
208
+ id3v2_metadata = {UnifiedMetadataKey.TITLE: "ID3v2 Title", UnifiedMetadataKey.ARTISTS: ["ID3v2 Artist"]}
209
+ update_metadata(test_file, id3v2_metadata, metadata_format=MetadataFormat.ID3V2)
210
+
211
+ id3v1_metadata = {UnifiedMetadataKey.ALBUM: "ID3v1 Album"}
212
+ update_metadata(test_file, id3v1_metadata, metadata_format=MetadataFormat.ID3V1)
213
+
214
+ # 2. Verify both formats have metadata
215
+ combined_metadata = get_unified_metadata(test_file)
216
+ assert combined_metadata.get(UnifiedMetadataKey.TITLE) == "ID3v2 Title"
217
+ assert combined_metadata.get(UnifiedMetadataKey.ALBUM) == "ID3v1 Album"
218
+
219
+ # 3. Delete only ID3v2 metadata
220
+ id3v2_deletion = {UnifiedMetadataKey.TITLE: None, UnifiedMetadataKey.ARTISTS: None}
221
+ update_metadata(test_file, id3v2_deletion, metadata_format=MetadataFormat.ID3V2)
222
+
223
+ # 4. Verify ID3v2 metadata is deleted but ID3v1 remains
224
+ after_id3v2_deletion = get_unified_metadata(test_file)
225
+ assert (
226
+ after_id3v2_deletion.get(UnifiedMetadataKey.TITLE) is None
227
+ or after_id3v2_deletion.get(UnifiedMetadataKey.TITLE) != "ID3v2 Title"
228
+ )
229
+ assert after_id3v2_deletion.get(UnifiedMetadataKey.ALBUM) == "ID3v1 Album" # Should remain
230
+
231
+ # 5. Delete ID3v1 metadata
232
+ id3v1_deletion = {UnifiedMetadataKey.ALBUM: None}
233
+ update_metadata(test_file, id3v1_deletion, metadata_format=MetadataFormat.ID3V1)
234
+
235
+ # 6. Verify all metadata is now deleted
236
+ final_metadata = get_unified_metadata(test_file)
237
+ assert final_metadata.get(UnifiedMetadataKey.ALBUM) is None
238
+
239
+ def test_deletion_error_handling_workflow(self):
240
+ # E2e test for deletion error scenarios
241
+ # Create a file with unsupported extension
242
+ with temp_file_with_metadata({}, "mp3") as temp_audio_file_path:
243
+ test_file = temp_audio_file_path.with_suffix(".txt")
244
+ test_file.write_bytes(b"fake audio content")
245
+
246
+ # All deletion operations should raise appropriate errors
247
+ from audiometa.exceptions import FileTypeNotSupportedError
248
+
249
+ with pytest.raises(FileTypeNotSupportedError):
250
+ delete_all_metadata(str(test_file))
251
+
252
+ with pytest.raises(FileTypeNotSupportedError):
253
+ update_metadata(str(test_file), {UnifiedMetadataKey.TITLE: None})
254
+
255
+ def test_deletion_with_rating_normalization_workflow(self):
256
+ # E2e test for deletion with rating normalization
257
+ initial_metadata = {"title": "Rating Deletion Test", "artist": "Rating Artist"}
258
+
259
+ with temp_file_with_metadata(initial_metadata, "mp3") as test_file:
260
+ # 1. Add rating with normalization
261
+ rating_metadata = {UnifiedMetadataKey.RATING: 80}
262
+ update_metadata(test_file, rating_metadata, normalized_rating_max_value=100)
263
+
264
+ # 2. Verify rating was added
265
+ metadata_with_rating = get_unified_metadata(test_file, normalized_rating_max_value=100)
266
+ assert metadata_with_rating.get(UnifiedMetadataKey.RATING) == 80
267
+
268
+ # 3. Delete rating
269
+ rating_deletion = {UnifiedMetadataKey.RATING: None}
270
+ update_metadata(test_file, rating_deletion, normalized_rating_max_value=100)
271
+
272
+ # 4. Verify rating was deleted
273
+ metadata_after_deletion = get_unified_metadata(test_file, normalized_rating_max_value=100)
274
+ assert metadata_after_deletion.get(UnifiedMetadataKey.RATING) is None
275
+
276
+ def test_batch_deletion_workflow(self, sample_mp3_file, sample_flac_file, sample_wav_file):
277
+ # E2e test for batch deletion operations
278
+ sample_files = [(sample_mp3_file, "mp3"), (sample_flac_file, "flac"), (sample_wav_file, "wav")]
279
+
280
+ results = []
281
+
282
+ for _file_path, format_type in sample_files:
283
+ try:
284
+ # Set up metadata using external script
285
+ initial_metadata = {"title": f"Batch Deletion Test {format_type.upper()}", "artist": "Batch Artist"}
286
+ with temp_file_with_metadata(initial_metadata, format_type) as test_file:
287
+ # Add more metadata using app's function
288
+ additional_metadata = {
289
+ UnifiedMetadataKey.ALBUM: f"Batch Album {format_type.upper()}",
290
+ UnifiedMetadataKey.COMMENT: f"Batch comment for {format_type}",
291
+ }
292
+ update_metadata(test_file, additional_metadata)
293
+
294
+ # Verify metadata was added
295
+ added_metadata = get_unified_metadata(test_file)
296
+ assert added_metadata.get(UnifiedMetadataKey.TITLE) == f"Batch Deletion Test {format_type.upper()}"
297
+
298
+ # Delete all metadata
299
+ delete_result = delete_all_metadata(test_file)
300
+ assert delete_result is True
301
+
302
+ # Verify deletion worked
303
+ deleted_metadata = get_unified_metadata(test_file)
304
+ assert (
305
+ deleted_metadata.get(UnifiedMetadataKey.TITLE) is None
306
+ or deleted_metadata.get(UnifiedMetadataKey.TITLE)
307
+ != f"Batch Deletion Test {format_type.upper()}"
308
+ )
309
+
310
+ results.append(("success", format_type))
311
+
312
+ except Exception as e:
313
+ results.append(("error", format_type, str(e)))
314
+
315
+ # Verify all files were processed successfully
316
+ assert len(results) == len(sample_files)
317
+ success_count = sum(1 for result in results if result[0] == "success")
318
+ assert success_count == len(sample_files) # All should succeed
@@ -0,0 +1,165 @@
1
+ """End-to-end tests for error handling and recovery workflows.
2
+
3
+ These tests verify that the system handles errors gracefully and recovers properly from various error conditions in
4
+ real-world scenarios.
5
+ """
6
+
7
+ import pytest
8
+
9
+ from audiometa import delete_all_metadata, get_bitrate, get_duration_in_sec, get_unified_metadata, update_metadata
10
+ from audiometa.exceptions import InvalidMetadataFieldFormatError, InvalidRatingValueError
11
+ from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
12
+ from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
13
+
14
+
15
+ @pytest.mark.e2e
16
+ class TestErrorHandlingWorkflows:
17
+ def test_error_recovery_workflow(self):
18
+ # E2E test for error scenarios and recovery
19
+ initial_metadata = {"title": "Original Title", "artist": "Original Artist"}
20
+ with temp_file_with_metadata(initial_metadata, "mp3") as test_file:
21
+ # 1. Verify initial metadata exists
22
+ initial_metadata_result = get_unified_metadata(test_file)
23
+ assert initial_metadata_result.get(UnifiedMetadataKey.TITLE) == "Original Title"
24
+
25
+ # 2. Test invalid rating operation - should raise InvalidRatingValueError
26
+ # Using normalized mode with invalid value (110 is over the max value and results in invalid star rating
27
+ # index > 10)
28
+ with pytest.raises(InvalidRatingValueError) as exc_info:
29
+ update_metadata(test_file, {UnifiedMetadataKey.RATING: 110}, normalized_rating_max_value=100)
30
+ assert "out of range" in str(exc_info.value) or "invalid star rating index" in str(exc_info.value)
31
+
32
+ # 3. Test recovery after error - verify the file is still usable
33
+ # The file should still have its original metadata intact
34
+ metadata_after_error = get_unified_metadata(test_file)
35
+ assert metadata_after_error.get(UnifiedMetadataKey.TITLE) == "Original Title"
36
+
37
+ # 4. Test successful operation after error - should work normally
38
+ recovery_metadata = {UnifiedMetadataKey.TITLE: "Recovery Test"}
39
+ update_metadata(test_file, recovery_metadata)
40
+
41
+ # 5. Verify recovery worked - file is fully functional
42
+ final_metadata = get_unified_metadata(test_file)
43
+ assert final_metadata.get(UnifiedMetadataKey.TITLE) == "Recovery Test"
44
+
45
+ def test_error_handling_workflow(self):
46
+ # Create a file with unsupported extension
47
+ with temp_file_with_metadata({}, "mp3") as temp_audio_file_path:
48
+ temp_audio_file_path.write_bytes(b"fake audio content")
49
+ test_file = temp_audio_file_path.with_suffix(".txt")
50
+ test_file.write_bytes(b"fake audio content")
51
+
52
+ # All operations should raise FileTypeNotSupportedError
53
+ from audiometa.exceptions import FileTypeNotSupportedError
54
+
55
+ with pytest.raises(FileTypeNotSupportedError):
56
+ get_unified_metadata(str(test_file))
57
+
58
+ with pytest.raises(FileTypeNotSupportedError):
59
+ update_metadata(str(test_file), {UnifiedMetadataKey.TITLE: "Test"})
60
+
61
+ with pytest.raises(FileTypeNotSupportedError):
62
+ delete_all_metadata(str(test_file))
63
+
64
+ with pytest.raises(FileTypeNotSupportedError):
65
+ get_bitrate(str(test_file))
66
+
67
+ with pytest.raises(FileTypeNotSupportedError):
68
+ get_duration_in_sec(str(test_file))
69
+
70
+ def test_deletion_error_recovery_workflow(self):
71
+ # E2E test for deletion error scenarios and recovery
72
+ initial_metadata = {"title": "Deletion Error Test", "artist": "Deletion Error Artist"}
73
+
74
+ with temp_file_with_metadata(initial_metadata, "mp3") as test_file:
75
+ # 1. Verify initial metadata exists
76
+ initial_metadata_result = get_unified_metadata(test_file)
77
+ assert initial_metadata_result.get(UnifiedMetadataKey.TITLE) == "Deletion Error Test"
78
+
79
+ # 2. Test deletion on file that doesn't exist
80
+ non_existent_file = test_file.parent / "non_existent.mp3"
81
+ with pytest.raises(FileNotFoundError):
82
+ delete_all_metadata(str(non_existent_file))
83
+
84
+ # 3. Test deletion on directory instead of file
85
+ from audiometa.exceptions import FileTypeNotSupportedError
86
+
87
+ with pytest.raises(FileTypeNotSupportedError):
88
+ delete_all_metadata(str(test_file.parent))
89
+
90
+ # 4. Verify original file is still usable after errors
91
+ metadata_after_errors = get_unified_metadata(test_file)
92
+ assert metadata_after_errors.get(UnifiedMetadataKey.TITLE) == "Deletion Error Test"
93
+
94
+ # 5. Successfully delete metadata from original file
95
+ delete_result = delete_all_metadata(test_file)
96
+ assert delete_result is True
97
+
98
+ # 6. Verify deletion worked
99
+ deleted_metadata = get_unified_metadata(test_file)
100
+ assert (
101
+ deleted_metadata.get(UnifiedMetadataKey.TITLE) is None
102
+ or deleted_metadata.get(UnifiedMetadataKey.TITLE) != "Deletion Error Test"
103
+ )
104
+
105
+ def test_deletion_with_corrupted_metadata_workflow(self):
106
+ # E2E test for deletion when metadata might be corrupted
107
+ initial_metadata = {"title": "Corrupted Metadata Test", "artist": "Corrupted Artist"}
108
+
109
+ with temp_file_with_metadata(initial_metadata, "mp3") as test_file:
110
+ # 1. Verify initial metadata exists
111
+ initial_metadata_result = get_unified_metadata(test_file)
112
+ assert initial_metadata_result.get(UnifiedMetadataKey.TITLE) == "Corrupted Metadata Test"
113
+
114
+ # 2. Try to delete metadata - should work even if some metadata is corrupted
115
+ delete_result = delete_all_metadata(test_file)
116
+ assert delete_result is True
117
+
118
+ # 3. Verify deletion worked
119
+ deleted_metadata = get_unified_metadata(test_file)
120
+ assert (
121
+ deleted_metadata.get(UnifiedMetadataKey.TITLE) is None
122
+ or deleted_metadata.get(UnifiedMetadataKey.TITLE) != "Corrupted Metadata Test"
123
+ )
124
+
125
+ # 4. Verify file is still usable after deletion
126
+ # Try to add new metadata
127
+ new_metadata = {UnifiedMetadataKey.TITLE: "New Title After Deletion"}
128
+ update_metadata(test_file, new_metadata)
129
+
130
+ # 5. Verify new metadata was added successfully
131
+ new_metadata_result = get_unified_metadata(test_file)
132
+ assert new_metadata_result.get(UnifiedMetadataKey.TITLE) == "New Title After Deletion"
133
+
134
+ def test_date_format_validation_workflow(self):
135
+ initial_metadata = {"title": "Date Validation Test", "artist": "Date Test Artist"}
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) == "Date Validation Test"
141
+
142
+ # 2. Test invalid date formats - should raise InvalidMetadataFieldFormatError
143
+ invalid_dates = [
144
+ "2024/01/01",
145
+ "2024-1-1",
146
+ "not-a-date",
147
+ "24",
148
+ ]
149
+
150
+ for invalid_date in invalid_dates:
151
+ with pytest.raises(InvalidMetadataFieldFormatError) as exc_info:
152
+ update_metadata(test_file, {UnifiedMetadataKey.RELEASE_DATE: invalid_date})
153
+ error = exc_info.value
154
+ assert error.field == UnifiedMetadataKey.RELEASE_DATE.value
155
+ assert error.value == invalid_date
156
+
157
+ # 3. Verify file is still usable after validation errors (validation happens before file write)
158
+ metadata_after_errors = get_unified_metadata(test_file)
159
+ assert metadata_after_errors.get(UnifiedMetadataKey.TITLE) == "Date Validation Test"
160
+
161
+ # 4. Test valid date format - should succeed
162
+ # Update with valid YYYY-MM-DD format
163
+ update_metadata(test_file, {UnifiedMetadataKey.RELEASE_DATE: "2024-01-01"})
164
+ updated_metadata = get_unified_metadata(test_file)
165
+ assert updated_metadata.get(UnifiedMetadataKey.RELEASE_DATE) == "2024-01-01"
@@ -0,0 +1,129 @@
1
+ """End-to-end tests for format-specific metadata workflows.
2
+
3
+ These tests verify that the system works correctly with different audio formats (MP3, FLAC, WAV) and their specific
4
+ capabilities and limitations.
5
+ """
6
+
7
+ import pytest
8
+
9
+ from audiometa import delete_all_metadata, get_bitrate, get_duration_in_sec, 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.e2e
15
+ class TestFormatSpecificWorkflows:
16
+ def test_complete_metadata_workflow_mp3(self):
17
+ # Use external script to set initial metadata
18
+ initial_metadata = {"title": "Initial MP3 Title", "artist": "Initial MP3 Artist", "album": "Initial MP3 Album"}
19
+ with temp_file_with_metadata(initial_metadata, "mp3") as test_file:
20
+ # 1. Read initial metadata
21
+ initial_metadata_result = get_unified_metadata(test_file)
22
+ assert isinstance(initial_metadata_result, dict)
23
+
24
+ # 2. Update metadata using app's function (this is what we're testing)
25
+ test_metadata = {
26
+ UnifiedMetadataKey.TITLE: "Integration Test Title",
27
+ UnifiedMetadataKey.ARTISTS: ["Integration Test Artist"],
28
+ UnifiedMetadataKey.ALBUM: "Integration Test Album",
29
+ UnifiedMetadataKey.RATING: 90,
30
+ UnifiedMetadataKey.BPM: 130,
31
+ }
32
+ update_metadata(test_file, test_metadata, normalized_rating_max_value=100)
33
+
34
+ # 3. Verify metadata was updated
35
+ updated_metadata = get_unified_metadata(test_file, normalized_rating_max_value=100)
36
+ assert updated_metadata.get(UnifiedMetadataKey.TITLE) == "Integration Test Title"
37
+ assert updated_metadata.get(UnifiedMetadataKey.ARTISTS) == ["Integration Test Artist"]
38
+ assert updated_metadata.get(UnifiedMetadataKey.ALBUM) == "Integration Test Album"
39
+ assert updated_metadata.get(UnifiedMetadataKey.RATING) == 90
40
+ assert updated_metadata.get(UnifiedMetadataKey.BPM) == 130
41
+
42
+ # 4. Test technical information
43
+ bitrate = get_bitrate(test_file)
44
+ duration = get_duration_in_sec(test_file)
45
+ assert isinstance(bitrate, int)
46
+ assert isinstance(duration, float)
47
+ assert bitrate > 0
48
+ assert duration > 0
49
+
50
+ # 5. Delete metadata
51
+ delete_result = delete_all_metadata(test_file)
52
+ assert delete_result is True
53
+
54
+ # 6. Verify metadata was deleted
55
+ deleted_metadata = get_unified_metadata(test_file)
56
+ # After deletion, metadata should be empty or minimal
57
+ assert (
58
+ UnifiedMetadataKey.TITLE not in deleted_metadata
59
+ or deleted_metadata.get(UnifiedMetadataKey.TITLE) != "Integration Test Title"
60
+ )
61
+
62
+ def test_complete_metadata_workflow_flac(self):
63
+ # Use external script to set initial metadata
64
+ initial_metadata = {
65
+ "title": "Initial FLAC Title",
66
+ "artist": "Initial FLAC Artist",
67
+ "album": "Initial FLAC Album",
68
+ }
69
+ with temp_file_with_metadata(initial_metadata, "flac") as test_file:
70
+ # 1. Read initial metadata
71
+ initial_metadata_result = get_unified_metadata(test_file)
72
+ assert isinstance(initial_metadata_result, dict)
73
+
74
+ # 2. Update metadata using app's function (this is what we're testing)
75
+ test_metadata = {
76
+ UnifiedMetadataKey.TITLE: "FLAC Integration Test Title",
77
+ UnifiedMetadataKey.ARTISTS: ["FLAC Integration Test Artist"],
78
+ UnifiedMetadataKey.ALBUM: "FLAC Integration Test Album",
79
+ UnifiedMetadataKey.RATING: 80,
80
+ UnifiedMetadataKey.BPM: 140,
81
+ }
82
+ update_metadata(test_file, test_metadata, normalized_rating_max_value=100)
83
+
84
+ # 3. Verify metadata was updated
85
+ updated_metadata = get_unified_metadata(test_file, normalized_rating_max_value=100)
86
+ assert updated_metadata.get(UnifiedMetadataKey.TITLE) == "FLAC Integration Test Title"
87
+ assert updated_metadata.get(UnifiedMetadataKey.ARTISTS) == ["FLAC Integration Test Artist"]
88
+ assert updated_metadata.get(UnifiedMetadataKey.ALBUM) == "FLAC Integration Test Album"
89
+ assert updated_metadata.get(UnifiedMetadataKey.RATING) == 80
90
+ assert updated_metadata.get(UnifiedMetadataKey.BPM) == 140
91
+
92
+ # 4. Test technical information
93
+ bitrate = get_bitrate(test_file)
94
+ duration = get_duration_in_sec(test_file)
95
+ assert isinstance(bitrate, int)
96
+ assert isinstance(duration, float)
97
+ assert bitrate > 0
98
+ assert duration > 0
99
+
100
+ def test_complete_metadata_workflow_wav(self):
101
+ # Use external script to set initial metadata
102
+ initial_metadata = {"title": "Initial WAV Title", "artist": "Initial WAV Artist", "album": "Initial WAV Album"}
103
+ with temp_file_with_metadata(initial_metadata, "wav") as test_file:
104
+ # 1. Read initial metadata
105
+ initial_metadata_result = get_unified_metadata(test_file)
106
+ assert isinstance(initial_metadata_result, dict)
107
+
108
+ # 2. Update metadata using app's function (this is what we're testing)
109
+ # WAV doesn't support rating or BPM
110
+ test_metadata = {
111
+ UnifiedMetadataKey.TITLE: "WAV Integration Test Title",
112
+ UnifiedMetadataKey.ARTISTS: ["WAV Integration Test Artist"],
113
+ UnifiedMetadataKey.ALBUM: "WAV Integration Test Album",
114
+ }
115
+ update_metadata(test_file, test_metadata)
116
+
117
+ # 3. Verify metadata was updated
118
+ updated_metadata = get_unified_metadata(test_file)
119
+ assert updated_metadata.get(UnifiedMetadataKey.TITLE) == "WAV Integration Test Title"
120
+ assert updated_metadata.get(UnifiedMetadataKey.ARTISTS) == ["WAV Integration Test Artist"]
121
+ assert updated_metadata.get(UnifiedMetadataKey.ALBUM) == "WAV Integration Test Album"
122
+
123
+ # 4. Test technical information
124
+ bitrate = get_bitrate(test_file)
125
+ duration = get_duration_in_sec(test_file)
126
+ assert isinstance(bitrate, int)
127
+ assert isinstance(duration, float)
128
+ assert bitrate > 0
129
+ assert duration > 0