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,29 @@
1
+ from pathlib import Path
2
+
3
+ import pytest
4
+
5
+ from audiometa import fix_md5_checking, is_flac_md5_valid
6
+ from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
7
+ from audiometa.test.tests.integration.technical_info.flac_md5.conftest import corrupt_md5, ensure_flac_has_md5
8
+
9
+
10
+ @pytest.mark.integration
11
+ class TestPartialMd5:
12
+ def test_is_flac_md5_valid_detects_partial_md5_corruption(self):
13
+ with temp_file_with_metadata({}, "flac") as test_file:
14
+ ensure_flac_has_md5(test_file)
15
+ corrupt_md5(test_file, "partial")
16
+
17
+ assert not is_flac_md5_valid(test_file), "Partially corrupted MD5 should be detected as invalid"
18
+
19
+ def test_fix_md5_checking_with_partial_corruption(self):
20
+ with temp_file_with_metadata({}, "flac") as test_file:
21
+ ensure_flac_has_md5(test_file)
22
+ corrupt_md5(test_file, "partial")
23
+
24
+ assert not is_flac_md5_valid(test_file), "Partially corrupted MD5 should be invalid"
25
+
26
+ fixed_file_path = fix_md5_checking(test_file)
27
+ assert is_flac_md5_valid(fixed_file_path), "Fixed file should have valid MD5"
28
+
29
+ Path(fixed_file_path).unlink()
@@ -0,0 +1,29 @@
1
+ from pathlib import Path
2
+
3
+ import pytest
4
+
5
+ from audiometa import fix_md5_checking, is_flac_md5_valid
6
+ from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
7
+ from audiometa.test.tests.integration.technical_info.flac_md5.conftest import corrupt_md5, ensure_flac_has_md5
8
+
9
+
10
+ @pytest.mark.integration
11
+ class TestRandomMd5:
12
+ def test_is_flac_md5_valid_detects_random_md5_corruption(self):
13
+ with temp_file_with_metadata({}, "flac") as test_file:
14
+ ensure_flac_has_md5(test_file)
15
+ corrupt_md5(test_file, "random")
16
+
17
+ assert not is_flac_md5_valid(test_file), "Random MD5 corruption should be detected as invalid"
18
+
19
+ def test_fix_md5_checking_with_random_corruption(self):
20
+ with temp_file_with_metadata({}, "flac") as test_file:
21
+ ensure_flac_has_md5(test_file)
22
+ corrupt_md5(test_file, "random")
23
+
24
+ assert not is_flac_md5_valid(test_file), "Random MD5 corruption should be invalid"
25
+
26
+ fixed_file_path = fix_md5_checking(test_file)
27
+ assert is_flac_md5_valid(fixed_file_path), "Fixed file should have valid MD5"
28
+
29
+ Path(fixed_file_path).unlink()
@@ -0,0 +1,56 @@
1
+ from pathlib import Path
2
+
3
+ import pytest
4
+
5
+ from audiometa import fix_md5_checking, is_flac_md5_valid
6
+ from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
7
+ from audiometa.test.tests.integration.technical_info.flac_md5.conftest import (
8
+ corrupt_md5,
9
+ create_flac_without_md5,
10
+ ensure_flac_has_md5,
11
+ )
12
+
13
+
14
+ @pytest.mark.integration
15
+ class TestUnsetMd5:
16
+ def test_is_flac_md5_valid_with_naturally_unset_md5(self):
17
+ with temp_file_with_metadata({}, "flac") as test_file:
18
+ # Create FLAC file without MD5 (naturally unset, not corrupted)
19
+ create_flac_without_md5(test_file)
20
+
21
+ # Unset MD5 (all zeros) should be consistently detected as invalid
22
+ result = is_flac_md5_valid(test_file)
23
+ assert result is False, "Unset MD5 should be detected as invalid"
24
+
25
+ def test_is_flac_md5_valid_with_corrupted_to_unset_md5(self):
26
+ with temp_file_with_metadata({}, "flac") as test_file:
27
+ ensure_flac_has_md5(test_file)
28
+ corrupt_md5(test_file, "zeros")
29
+
30
+ # Unset MD5 (all zeros) should be consistently detected as invalid
31
+ result = is_flac_md5_valid(test_file)
32
+ assert result is False, "Unset MD5 should be detected as invalid"
33
+
34
+ def test_fix_md5_checking_with_naturally_unset_md5(self):
35
+ with temp_file_with_metadata({}, "flac") as test_file:
36
+ # Create FLAC file without MD5 (naturally unset, not corrupted)
37
+ create_flac_without_md5(test_file)
38
+
39
+ assert not is_flac_md5_valid(test_file), "Unset MD5 should be detected as invalid"
40
+
41
+ fixed_file_path = fix_md5_checking(test_file)
42
+ assert is_flac_md5_valid(fixed_file_path), "Fixed file should have valid MD5"
43
+
44
+ Path(fixed_file_path).unlink()
45
+
46
+ def test_fix_md5_checking_with_corrupted_to_unset_md5(self):
47
+ with temp_file_with_metadata({}, "flac") as test_file:
48
+ ensure_flac_has_md5(test_file)
49
+ corrupt_md5(test_file, "zeros")
50
+
51
+ assert not is_flac_md5_valid(test_file), "Unset MD5 should be detected as invalid"
52
+
53
+ fixed_file_path = fix_md5_checking(test_file)
54
+ assert is_flac_md5_valid(fixed_file_path), "Fixed file should have valid MD5"
55
+
56
+ Path(fixed_file_path).unlink()
@@ -0,0 +1,21 @@
1
+ from pathlib import Path
2
+
3
+ import pytest
4
+
5
+ from audiometa import is_flac_md5_valid
6
+ from audiometa.exceptions import FileTypeNotSupportedError
7
+
8
+
9
+ @pytest.mark.integration
10
+ class TestValidFlacMd5:
11
+ def test_is_flac_md5_valid_works_with_path_string(self, sample_flac_file: Path):
12
+ is_valid = is_flac_md5_valid(str(sample_flac_file))
13
+ assert isinstance(is_valid, bool)
14
+
15
+ def test_is_flac_md5_valid_works_with_pathlib_path(self, sample_flac_file: Path):
16
+ is_valid = is_flac_md5_valid(sample_flac_file)
17
+ assert isinstance(is_valid, bool)
18
+
19
+ def test_is_flac_md5_valid_non_flac(self, sample_mp3_file: Path):
20
+ with pytest.raises(FileTypeNotSupportedError):
21
+ is_flac_md5_valid(sample_mp3_file)
@@ -0,0 +1,79 @@
1
+ from pathlib import Path
2
+
3
+ import pytest
4
+
5
+ from audiometa import get_bitrate
6
+ from audiometa.test.helpers.technical_info_inspector import TechnicalInfoInspector
7
+
8
+
9
+ @pytest.mark.integration
10
+ class TestGetBitrate:
11
+ def test_get_bitrate_works_with_path_string(self, sample_mp3_file: Path):
12
+ bitrate = get_bitrate(str(sample_mp3_file))
13
+ assert isinstance(bitrate, int)
14
+ assert bitrate > 0
15
+
16
+ def test_get_bitrate_works_with_pathlib_path(self, sample_mp3_file: Path):
17
+ bitrate = get_bitrate(sample_mp3_file)
18
+ assert isinstance(bitrate, int)
19
+ assert bitrate > 0
20
+
21
+ def test_get_bitrate_matches_external_tool(self, sample_mp3_file: Path):
22
+ external_tool_bitrate = TechnicalInfoInspector.get_bitrate(sample_mp3_file)
23
+ assert external_tool_bitrate == 128
24
+
25
+ bitrate = get_bitrate(sample_mp3_file)
26
+ assert bitrate == 127997
27
+
28
+ def test_get_bitrate_supports_all_formats(
29
+ self, sample_mp3_file: Path, sample_flac_file: Path, sample_wav_file: Path
30
+ ):
31
+ mp3_bitrate = get_bitrate(sample_mp3_file)
32
+ flac_bitrate = get_bitrate(sample_flac_file)
33
+ wav_bitrate = get_bitrate(sample_wav_file)
34
+
35
+ assert isinstance(mp3_bitrate, int)
36
+ assert isinstance(flac_bitrate, int)
37
+ assert isinstance(wav_bitrate, int)
38
+ assert all(b > 0 for b in [mp3_bitrate, flac_bitrate, wav_bitrate])
39
+
40
+ def test_get_bitrate_for_320_kbps_mp3(self, bitrate_320_mp3: Path):
41
+ """Test bitrate for 320 kbps MP3 file."""
42
+ bitrate = get_bitrate(bitrate_320_mp3)
43
+ assert isinstance(bitrate, int)
44
+ # Expected: 320000 bps (320 kbps)
45
+ assert bitrate == 320000, f"Expected 320000 bps, got {bitrate} bps"
46
+
47
+ def test_get_bitrate_for_946_kbps_flac(self, bitrate_946_flac: Path):
48
+ """Test bitrate for 946 kbps FLAC file."""
49
+ bitrate = get_bitrate(bitrate_946_flac)
50
+ assert isinstance(bitrate, int)
51
+ # Expected: 946677 bps (≈946 kbps variable bitrate)
52
+ assert bitrate == 946677, f"Expected 946677 bps, got {bitrate} bps"
53
+
54
+ def test_get_bitrate_for_1411_kbps_wav(self, bitrate_1411_wav: Path):
55
+ """Test bitrate for 1411 kbps WAV file."""
56
+ bitrate = get_bitrate(bitrate_1411_wav)
57
+ assert isinstance(bitrate, int)
58
+ # Expected: 1411200 bps (≈1411 kbps)
59
+ assert bitrate == 1411200, f"Expected 1411200 bps, got {bitrate} bps"
60
+
61
+ def test_get_bitrate_validates_across_all_test_assets(self, assets_dir: Path):
62
+ """Test bitrate for all dedicated bitrate test assets."""
63
+ bitrate_test_files = [
64
+ ("bitrate in kbps_big=320.mp3", 320000),
65
+ ("bitrate in kbps_big=946.flac", 946677), # Variable bitrate, actual measured value
66
+ ("bitrate in kbps_big=1411.wav", 1411200),
67
+ ("bitrate in kbps_small=192.mp3", 192000),
68
+ ("bitrate in kbps_small=723.flac", 777623), # Variable bitrate, actual measured value
69
+ ("bitrate in kbps_small=1152.wav", 1152000),
70
+ ]
71
+
72
+ for filename, expected_bitrate_bps in bitrate_test_files:
73
+ file_path = assets_dir / filename
74
+ if file_path.exists():
75
+ bitrate = get_bitrate(file_path)
76
+ assert isinstance(bitrate, int), f"Bitrate for {filename} should be int"
77
+ assert (
78
+ bitrate == expected_bitrate_bps
79
+ ), f"Expected {expected_bitrate_bps} bps for {filename}, got {bitrate} bps"
@@ -0,0 +1,38 @@
1
+ from pathlib import Path
2
+
3
+ import pytest
4
+
5
+ from audiometa import get_channels
6
+ from audiometa.test.helpers.technical_info_inspector import TechnicalInfoInspector
7
+
8
+
9
+ @pytest.mark.integration
10
+ class TestGetChannels:
11
+ def test_get_channels_works_with_path_string(self, sample_mp3_file: Path):
12
+ channels = get_channels(str(sample_mp3_file))
13
+ assert isinstance(channels, int)
14
+ assert channels > 0
15
+
16
+ def test_get_channels_works_with_pathlib_path(self, sample_mp3_file: Path):
17
+ channels = get_channels(sample_mp3_file)
18
+ assert isinstance(channels, int)
19
+ assert channels > 0
20
+
21
+ def test_get_channels_matches_external_tool(self, sample_mp3_file: Path):
22
+ external_tool_channels = TechnicalInfoInspector.get_channels(sample_mp3_file)
23
+ assert external_tool_channels == 2
24
+
25
+ channels = get_channels(sample_mp3_file)
26
+ assert channels == 2
27
+
28
+ def test_get_channels_supports_all_formats(
29
+ self, sample_mp3_file: Path, sample_flac_file: Path, sample_wav_file: Path
30
+ ):
31
+ mp3_channels = get_channels(sample_mp3_file)
32
+ flac_channels = get_channels(sample_flac_file)
33
+ wav_channels = get_channels(sample_wav_file)
34
+
35
+ assert isinstance(mp3_channels, int)
36
+ assert isinstance(flac_channels, int)
37
+ assert isinstance(wav_channels, int)
38
+ assert all(c > 0 for c in [mp3_channels, flac_channels, wav_channels])
@@ -0,0 +1,38 @@
1
+ from pathlib import Path
2
+
3
+ import pytest
4
+
5
+ from audiometa import get_duration_in_sec
6
+ from audiometa.test.helpers.technical_info_inspector import TechnicalInfoInspector
7
+
8
+
9
+ @pytest.mark.integration
10
+ class TestGetDurationInSec:
11
+ def test_get_duration_in_sec_works_with_path_string(self, sample_mp3_file: Path):
12
+ duration = get_duration_in_sec(str(sample_mp3_file))
13
+ assert isinstance(duration, float)
14
+ assert duration > 0
15
+
16
+ def test_get_duration_in_sec_works_with_pathlib_path(self, sample_mp3_file: Path):
17
+ duration = get_duration_in_sec(sample_mp3_file)
18
+ assert isinstance(duration, float)
19
+ assert duration > 0
20
+
21
+ def test_get_duration_in_sec_matches_external_tool(self, sample_mp3_file: Path):
22
+ external_tool_duration = TechnicalInfoInspector.get_duration(sample_mp3_file)
23
+ assert external_tool_duration == 1.045
24
+
25
+ duration = get_duration_in_sec(sample_mp3_file)
26
+ assert duration == pytest.approx(1.0448979591836736, rel=1e-6)
27
+
28
+ def test_get_duration_in_sec_supports_all_formats(
29
+ self, sample_mp3_file: Path, sample_flac_file: Path, sample_wav_file: Path
30
+ ):
31
+ mp3_duration = get_duration_in_sec(sample_mp3_file)
32
+ flac_duration = get_duration_in_sec(sample_flac_file)
33
+ wav_duration = get_duration_in_sec(sample_wav_file)
34
+
35
+ assert isinstance(mp3_duration, float)
36
+ assert isinstance(flac_duration, float)
37
+ assert isinstance(wav_duration, float)
38
+ assert all(d > 0 for d in [mp3_duration, flac_duration, wav_duration])
@@ -0,0 +1,40 @@
1
+ from pathlib import Path
2
+
3
+ import pytest
4
+
5
+ from audiometa import get_sample_rate
6
+ from audiometa.test.helpers.technical_info_inspector import TechnicalInfoInspector
7
+
8
+
9
+ @pytest.mark.integration
10
+ class TestGetSampleRate:
11
+ def test_get_sample_rate_works_with_path_string(self, sample_mp3_file: Path):
12
+ sample_rate = get_sample_rate(str(sample_mp3_file))
13
+ assert isinstance(sample_rate, int)
14
+ assert sample_rate > 0
15
+
16
+ def test_get_sample_rate_works_with_pathlib_path(self, sample_mp3_file: Path):
17
+ sample_rate = get_sample_rate(sample_mp3_file)
18
+ assert isinstance(sample_rate, int)
19
+ assert sample_rate > 0
20
+
21
+ def test_get_sample_rate_matches_external_tool(self, sample_mp3_file: Path):
22
+ external_sample_rate = TechnicalInfoInspector.get_sample_rate(sample_mp3_file)
23
+ assert external_sample_rate is not None
24
+ assert isinstance(external_sample_rate, int)
25
+ assert external_sample_rate == 44100
26
+
27
+ sample_rate = get_sample_rate(sample_mp3_file)
28
+ assert sample_rate == external_sample_rate
29
+
30
+ def test_get_sample_rate_supports_all_formats(
31
+ self, sample_mp3_file: Path, sample_flac_file: Path, sample_wav_file: Path
32
+ ):
33
+ mp3_sample_rate = get_sample_rate(sample_mp3_file)
34
+ flac_sample_rate = get_sample_rate(sample_flac_file)
35
+ wav_sample_rate = get_sample_rate(sample_wav_file)
36
+
37
+ assert isinstance(mp3_sample_rate, int)
38
+ assert isinstance(flac_sample_rate, int)
39
+ assert isinstance(wav_sample_rate, int)
40
+ assert all(r > 0 for r in [mp3_sample_rate, flac_sample_rate, wav_sample_rate])
@@ -0,0 +1,35 @@
1
+ from pathlib import Path
2
+
3
+ import pytest
4
+
5
+ from audiometa import get_unified_metadata, get_unified_metadata_field, is_audio_file
6
+ from audiometa.utils.metadata_format import MetadataFormat
7
+ from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
8
+
9
+
10
+ @pytest.mark.integration
11
+ class TestAudioFileIntegration:
12
+ def test_functional_api_with_file_path(self, sample_mp3_file: Path):
13
+ # Test that functional APIs work with file paths (string)
14
+ metadata = get_unified_metadata(str(sample_mp3_file))
15
+ assert isinstance(metadata, dict)
16
+
17
+ # Test single format with file path
18
+ id3v2_metadata = get_unified_metadata(str(sample_mp3_file), metadata_format=MetadataFormat.ID3V2)
19
+ assert isinstance(id3v2_metadata, dict)
20
+
21
+ # Test specific metadata with file path
22
+ title = get_unified_metadata_field(str(sample_mp3_file), UnifiedMetadataKey.TITLE)
23
+ assert title is None or isinstance(title, str)
24
+
25
+ def test_is_audio_file_with_valid_files(self, sample_mp3_file: Path, sample_flac_file: Path, sample_wav_file: Path):
26
+ assert is_audio_file(sample_mp3_file) is True
27
+ assert is_audio_file(sample_flac_file) is True
28
+ assert is_audio_file(sample_wav_file) is True
29
+
30
+ def test_is_audio_file_before_processing(self, sample_mp3_file: Path):
31
+ if is_audio_file(sample_mp3_file):
32
+ metadata = get_unified_metadata(sample_mp3_file)
33
+ assert isinstance(metadata, dict)
34
+ else:
35
+ pytest.fail("Valid audio file should return True")
@@ -0,0 +1,95 @@
1
+ import pytest
2
+
3
+ from audiometa import update_metadata
4
+ from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
5
+ from audiometa.utils.metadata_format import MetadataFormat
6
+ from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
7
+
8
+
9
+ @pytest.mark.integration
10
+ class TestAudioFormatReadableAfterUpdate:
11
+ def test_mp3_playable_after_id3v1_and_id3v2_updates(self):
12
+ """Test that MP3 file remains playable after updating with ID3v1 and ID3v2 tags."""
13
+ sf = pytest.importorskip("soundfile")
14
+
15
+ with temp_file_with_metadata({}, "mp3") as test_file:
16
+ # Update with ID3v2 metadata first
17
+ id3v2_metadata = {
18
+ UnifiedMetadataKey.TITLE: "ID3v2 Test Title",
19
+ UnifiedMetadataKey.ARTISTS: ["ID3v2 Test Artist"],
20
+ UnifiedMetadataKey.ALBUM: "ID3v2 Test Album",
21
+ UnifiedMetadataKey.RATING: 80,
22
+ }
23
+ update_metadata(
24
+ test_file, id3v2_metadata, metadata_format=MetadataFormat.ID3V2, normalized_rating_max_value=100
25
+ )
26
+
27
+ # Update with ID3v1 metadata
28
+ id3v1_metadata = {
29
+ UnifiedMetadataKey.TITLE: "ID3v1 Test Title",
30
+ UnifiedMetadataKey.ARTISTS: ["ID3v1 Test Artist"],
31
+ UnifiedMetadataKey.ALBUM: "ID3v1 Test Album",
32
+ }
33
+ update_metadata(test_file, id3v1_metadata, metadata_format=MetadataFormat.ID3V1)
34
+
35
+ # Verify the track is still playable by trying to read audio frames
36
+ try:
37
+ with sf.SoundFile(str(test_file)) as f:
38
+ # Just try reading a few frames
39
+ frames = f.read(frames=1024)
40
+ assert f.samplerate > 0
41
+ assert f.channels > 0
42
+ assert len(frames) > 0
43
+ except RuntimeError as e:
44
+ pytest.fail(f"Audio file {test_file} could not be opened or decoded: {e}")
45
+
46
+ def test_flac_playable_after_vorbis_updates(self):
47
+ """Test that FLAC file remains playable after updating with Vorbis tags."""
48
+ sf = pytest.importorskip("soundfile")
49
+
50
+ with temp_file_with_metadata({}, "flac") as test_file:
51
+ # Update with Vorbis metadata
52
+ vorbis_metadata = {
53
+ UnifiedMetadataKey.TITLE: "Vorbis Test Title",
54
+ UnifiedMetadataKey.ARTISTS: ["Vorbis Test Artist"],
55
+ UnifiedMetadataKey.ALBUM: "Vorbis Test Album",
56
+ UnifiedMetadataKey.RATING: 80,
57
+ }
58
+ update_metadata(
59
+ test_file, vorbis_metadata, metadata_format=MetadataFormat.VORBIS, normalized_rating_max_value=100
60
+ )
61
+
62
+ # Verify the track is still playable by trying to read audio frames
63
+ try:
64
+ with sf.SoundFile(str(test_file)) as f:
65
+ # Just try reading a few frames
66
+ frames = f.read(frames=1024)
67
+ assert f.samplerate > 0
68
+ assert f.channels > 0
69
+ assert len(frames) > 0
70
+ except RuntimeError as e:
71
+ pytest.fail(f"Audio file {test_file} could not be opened or decoded: {e}")
72
+
73
+ def test_wav_playable_after_riff_updates(self):
74
+ """Test that WAV file remains playable after updating with RIFF tags."""
75
+ sf = pytest.importorskip("soundfile")
76
+
77
+ with temp_file_with_metadata({}, "wav") as test_file:
78
+ # Update with RIFF metadata
79
+ riff_metadata = {
80
+ UnifiedMetadataKey.TITLE: "RIFF Test Title",
81
+ UnifiedMetadataKey.ARTISTS: ["RIFF Test Artist"],
82
+ UnifiedMetadataKey.ALBUM: "RIFF Test Album",
83
+ }
84
+ update_metadata(test_file, riff_metadata, metadata_format=MetadataFormat.RIFF)
85
+
86
+ # Verify the track is still playable by trying to read audio frames
87
+ try:
88
+ with sf.SoundFile(str(test_file)) as f:
89
+ # Just try reading a few frames
90
+ frames = f.read(frames=1024)
91
+ assert f.samplerate > 0
92
+ assert f.channels > 0
93
+ assert len(frames) > 0
94
+ except RuntimeError as e:
95
+ pytest.fail(f"Audio file {test_file} could not be opened or decoded: {e}")
File without changes
@@ -0,0 +1,44 @@
1
+ import pytest
2
+
3
+ from audiometa import delete_all_metadata, update_metadata
4
+ from audiometa.exceptions import FileTypeNotSupportedError, MetadataWritingConflictParametersError
5
+ from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
6
+ from audiometa.utils.metadata_format import MetadataFormat
7
+ from audiometa.utils.metadata_writing_strategy import MetadataWritingStrategy
8
+ from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
9
+
10
+
11
+ @pytest.mark.integration
12
+ class TestWritingErrorHandling:
13
+ def test_unsupported_file_type_raises_error(self):
14
+ # Create a file with unsupported extension using temp_file_with_metadata
15
+ with temp_file_with_metadata({}, "mp3") as temp_file_path:
16
+ temp_file_path.write_bytes(b"fake audio content")
17
+ temp_txt = temp_file_path.with_suffix(".txt")
18
+ temp_txt.write_bytes(b"fake audio content")
19
+
20
+ with pytest.raises(FileTypeNotSupportedError):
21
+ update_metadata(str(temp_txt), {UnifiedMetadataKey.TITLE: "Test"})
22
+
23
+ with pytest.raises(FileTypeNotSupportedError):
24
+ delete_all_metadata(str(temp_txt))
25
+
26
+ def test_nonexistent_file_raises_error(self):
27
+ nonexistent_file = "nonexistent_file.mp3"
28
+
29
+ with pytest.raises(FileNotFoundError):
30
+ update_metadata(nonexistent_file, {UnifiedMetadataKey.TITLE: "Test"})
31
+
32
+ # Note: delete_all_metadata error handling tests have been moved to test_delete_all_metadata.py
33
+
34
+ def test_metadata_writing_conflict_parameters_error_both_strategy_and_format(self):
35
+ with temp_file_with_metadata({}, "mp3") as test_file:
36
+ with pytest.raises(MetadataWritingConflictParametersError) as exc_info:
37
+ update_metadata(
38
+ test_file,
39
+ {UnifiedMetadataKey.TITLE: "Test"},
40
+ metadata_strategy=MetadataWritingStrategy.SYNC,
41
+ metadata_format=MetadataFormat.ID3V2,
42
+ )
43
+ assert "metadata_strategy" in str(exc_info.value).lower()
44
+ assert "metadata_format" in str(exc_info.value).lower()