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,215 @@
1
+ """Tests for SYNC metadata writing strategy.
2
+
3
+ This module tests the SYNC strategy which writes to native format and synchronizes other metadata formats that are
4
+ already present.
5
+ """
6
+
7
+ import pytest
8
+
9
+ from audiometa import get_unified_metadata, update_metadata
10
+ from audiometa.test.helpers.id3v1.id3v1_metadata_setter import ID3v1MetadataSetter
11
+ from audiometa.test.helpers.id3v2.id3v2_metadata_setter import ID3v2MetadataSetter
12
+ from audiometa.test.helpers.riff.riff_metadata_setter import RIFFMetadataSetter
13
+ from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
14
+ from audiometa.utils.metadata_format import MetadataFormat
15
+ from audiometa.utils.metadata_writing_strategy import MetadataWritingStrategy
16
+ from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
17
+
18
+
19
+ @pytest.mark.integration
20
+ class TestSyncStrategy:
21
+ def test_sync_strategy_wav(self):
22
+ # Create WAV file with initial RIFF metadata
23
+ with temp_file_with_metadata(
24
+ {"title": "Initial Title", "artist": "Initial Artist", "album": "Initial Album"}, "wav"
25
+ ) as test_file:
26
+ # Verify initial RIFF metadata was written
27
+ riff_initial = get_unified_metadata(test_file, metadata_format=MetadataFormat.RIFF)
28
+ assert riff_initial.get(UnifiedMetadataKey.TITLE) == "Initial Title"
29
+
30
+ # Now write RIFF metadata with SYNC strategy
31
+ # For WAV files, SYNC strategy should only affect RIFF metadata
32
+ sync_metadata = {
33
+ UnifiedMetadataKey.TITLE: "Synced Title",
34
+ UnifiedMetadataKey.ARTISTS: ["Synced Artist"],
35
+ UnifiedMetadataKey.ALBUM: "Synced Album",
36
+ }
37
+ update_metadata(test_file, sync_metadata, metadata_strategy=MetadataWritingStrategy.SYNC)
38
+
39
+ # Verify RIFF metadata has the synced values
40
+ riff_after = get_unified_metadata(test_file, metadata_format=MetadataFormat.RIFF)
41
+ assert riff_after.get(UnifiedMetadataKey.TITLE) == "Synced Title"
42
+ assert riff_after.get(UnifiedMetadataKey.ARTISTS) == ["Synced Artist"]
43
+ assert riff_after.get(UnifiedMetadataKey.ALBUM) == "Synced Album"
44
+
45
+ # Verify merged metadata prefers RIFF (WAV native format)
46
+ merged = get_unified_metadata(test_file)
47
+ assert merged.get(UnifiedMetadataKey.TITLE) == "Synced Title"
48
+
49
+ def test_default_strategy_is_sync(self):
50
+ # Create WAV file with initial RIFF metadata
51
+ with temp_file_with_metadata({}, "wav") as test_file:
52
+ # First, add RIFF metadata using external tools
53
+ RIFFMetadataSetter.set_title(test_file, "Initial RIFF Title")
54
+ RIFFMetadataSetter.set_artists(test_file, ["Initial RIFF Artist"])
55
+
56
+ # Now write RIFF metadata without specifying strategy (should default to SYNC)
57
+ riff_metadata = {UnifiedMetadataKey.TITLE: "RIFF Title", UnifiedMetadataKey.ARTISTS: ["RIFF Artist"]}
58
+ update_metadata(test_file, riff_metadata)
59
+
60
+ # Verify RIFF metadata has the new values (SYNC strategy)
61
+ riff_after = get_unified_metadata(test_file, metadata_format=MetadataFormat.RIFF)
62
+ assert riff_after.get(UnifiedMetadataKey.TITLE) == "RIFF Title"
63
+
64
+ # Merged metadata should prefer RIFF (WAV native format)
65
+ merged = get_unified_metadata(test_file)
66
+ assert merged.get(UnifiedMetadataKey.TITLE) == "RIFF Title"
67
+
68
+ def test_id3v1_not_preserved_with_sync_strategy(self):
69
+ # Create test file with ID3v1 metadata using external tools
70
+ with temp_file_with_metadata({}, "mp3") as test_file:
71
+ # Add ID3v1 metadata using external tools
72
+ ID3v1MetadataSetter.set_metadata(
73
+ test_file, {"title": "ID3v1 Title", "artist": "ID3v1 Artist", "album": "ID3v1 Album"}
74
+ )
75
+
76
+ # Verify ID3v1 metadata was written
77
+ id3v1_result = get_unified_metadata(test_file, metadata_format=MetadataFormat.ID3V1)
78
+ assert id3v1_result.get(UnifiedMetadataKey.TITLE) == "ID3v1 Title"
79
+
80
+ # Now write ID3v2 metadata with SYNC strategy
81
+ id3v2_metadata = {
82
+ UnifiedMetadataKey.TITLE: "Synced Title",
83
+ UnifiedMetadataKey.ARTISTS: ["Synced Artist"],
84
+ UnifiedMetadataKey.ALBUM: "Synced Album",
85
+ }
86
+ update_metadata(test_file, id3v2_metadata, metadata_strategy=MetadataWritingStrategy.SYNC)
87
+
88
+ # Verify ID3v1 metadata behavior with different strategies
89
+ # When ID3v2 is written, it overwrites the ID3v1 tag
90
+ id3v1_after = get_unified_metadata(test_file, metadata_format=MetadataFormat.ID3V1)
91
+ assert id3v1_after.get(UnifiedMetadataKey.TITLE) == "Synced Title" # ID3v1 was overwritten
92
+
93
+ # Verify ID3v2 metadata was written with synced values
94
+ id3v2_after = get_unified_metadata(test_file, metadata_format=MetadataFormat.ID3V2)
95
+ assert id3v2_after.get(UnifiedMetadataKey.TITLE) == "Synced Title"
96
+
97
+ # Merged metadata should prefer ID3v2 (higher precedence)
98
+ merged = get_unified_metadata(test_file)
99
+ assert merged.get(UnifiedMetadataKey.TITLE) == "Synced Title"
100
+
101
+ def test_id3v1_modification_success(self):
102
+ # Create test file with ID3v1 metadata using external tools
103
+ with temp_file_with_metadata({}, "mp3") as test_file:
104
+ # Add ID3v1 metadata using external tools
105
+ ID3v1MetadataSetter.set_metadata(
106
+ test_file, {"title": "ID3v1 Title", "artist": "ID3v1 Artist", "album": "ID3v1 Album"}
107
+ )
108
+
109
+ # Verify ID3v1 metadata was written
110
+ id3v1_result = get_unified_metadata(test_file, metadata_format=MetadataFormat.ID3V1)
111
+ assert id3v1_result.get(UnifiedMetadataKey.TITLE) == "ID3v1 Title"
112
+
113
+ # Modify ID3v1 metadata directly should succeed
114
+ update_metadata(test_file, {UnifiedMetadataKey.TITLE: "New Title"}, metadata_format=MetadataFormat.ID3V1)
115
+
116
+ # Verify the modification was successful
117
+ updated_id3v1_result = get_unified_metadata(test_file, metadata_format=MetadataFormat.ID3V1)
118
+ assert updated_id3v1_result.get(UnifiedMetadataKey.TITLE) == "New Title"
119
+
120
+ def test_sync_strategy_wav_with_field_length_limits(self):
121
+ # Create WAV file with initial RIFF metadata
122
+ with temp_file_with_metadata({}, "wav") as test_file:
123
+ # Add initial RIFF metadata using external tools
124
+ RIFFMetadataSetter.set_title(test_file, "Short Title")
125
+ RIFFMetadataSetter.set_artists(test_file, ["Short Artist"])
126
+ RIFFMetadataSetter.set_album(test_file, "Short Album")
127
+
128
+ # Verify initial RIFF metadata was written
129
+ riff_initial = get_unified_metadata(test_file, metadata_format=MetadataFormat.RIFF)
130
+ assert riff_initial.get(UnifiedMetadataKey.TITLE) == "Short Title"
131
+
132
+ # Now test SYNC strategy with long title
133
+ long_title = "This is a Very Long Title That Tests RIFF Metadata Handling"
134
+ sync_metadata = {
135
+ UnifiedMetadataKey.TITLE: long_title,
136
+ UnifiedMetadataKey.ARTISTS: ["Long Artist Name That Tests RIFF Handling"],
137
+ UnifiedMetadataKey.ALBUM: "Long Album Name That Tests RIFF Handling",
138
+ }
139
+ update_metadata(test_file, sync_metadata, metadata_strategy=MetadataWritingStrategy.SYNC)
140
+
141
+ # Verify RIFF metadata has full values (RIFF supports longer fields than ID3v1)
142
+ riff_after = get_unified_metadata(test_file, metadata_format=MetadataFormat.RIFF)
143
+ assert riff_after.get(UnifiedMetadataKey.TITLE) == long_title
144
+ assert riff_after.get(UnifiedMetadataKey.ARTISTS) == ["Long Artist Name That Tests RIFF Handling"]
145
+ assert riff_after.get(UnifiedMetadataKey.ALBUM) == "Long Album Name That Tests RIFF Handling"
146
+
147
+ # Merged metadata should prefer RIFF (WAV native format has higher precedence)
148
+ merged = get_unified_metadata(test_file)
149
+ assert merged.get(UnifiedMetadataKey.TITLE) == long_title # Full title from RIFF
150
+ assert merged.get(UnifiedMetadataKey.ARTISTS) == ["Long Artist Name That Tests RIFF Handling"]
151
+
152
+ def test_sync_strategy_mp3(self):
153
+ with temp_file_with_metadata({}, "mp3") as test_file:
154
+ # Add ID3v1 metadata using external tools
155
+ ID3v1MetadataSetter.set_metadata(
156
+ test_file, {"title": "ID3v1 Title", "artist": "ID3v1 Artist", "album": "ID3v1 Album"}
157
+ )
158
+
159
+ # Verify ID3v1 metadata was written
160
+ id3v1_result = get_unified_metadata(test_file, metadata_format=MetadataFormat.ID3V1)
161
+ assert id3v1_result.get(UnifiedMetadataKey.TITLE) == "ID3v1 Title"
162
+
163
+ # Now write ID3v2 metadata with SYNC strategy
164
+ id3v2_metadata = {
165
+ UnifiedMetadataKey.TITLE: "Synced Title",
166
+ UnifiedMetadataKey.ARTISTS: ["Synced Artist"],
167
+ UnifiedMetadataKey.ALBUM: "Synced Album",
168
+ }
169
+ update_metadata(test_file, id3v2_metadata, metadata_strategy=MetadataWritingStrategy.SYNC)
170
+
171
+ # Verify ID3v1 metadata behavior with different strategies
172
+ # When ID3v2 is written, it overwrites the ID3v1 tag
173
+ id3v1_after = get_unified_metadata(test_file, metadata_format=MetadataFormat.ID3V1)
174
+ assert id3v1_after.get(UnifiedMetadataKey.TITLE) == "Synced Title" # ID3v1 was overwritten
175
+
176
+ # Verify ID3v2 metadata was written with synced values
177
+ id3v2_after = get_unified_metadata(test_file, metadata_format=MetadataFormat.ID3V2)
178
+ assert id3v2_after.get(UnifiedMetadataKey.TITLE) == "Synced Title"
179
+
180
+ def test_sync_strategy_flac(self):
181
+ with temp_file_with_metadata({}, "flac") as test_file:
182
+ ID3v1MetadataSetter.set_metadata(
183
+ test_file, {"title": "ID3v1 Title", "artist": "ID3v1 Artist", "album": "ID3v1 Album"}
184
+ )
185
+
186
+ # Verify ID3v1 metadata was written
187
+ id3v1_result = get_unified_metadata(test_file, metadata_format=MetadataFormat.ID3V1)
188
+ assert id3v1_result.get(UnifiedMetadataKey.TITLE) == "ID3v1 Title"
189
+
190
+ ID3v2MetadataSetter.set_metadata(
191
+ test_file,
192
+ {"title": "ID3v2 Title", "artist": "ID3v2 Artist", "album": "ID3v2 Album"},
193
+ version="2.3",
194
+ )
195
+ id3v2_result = get_unified_metadata(test_file, metadata_format=MetadataFormat.ID3V2)
196
+ assert id3v2_result.get(UnifiedMetadataKey.TITLE) == "ID3v2 Title"
197
+
198
+ # Now write Vorbis metadata with SYNC strategy
199
+ vorbis_metadata = {
200
+ UnifiedMetadataKey.TITLE: "Synced Title",
201
+ UnifiedMetadataKey.ARTISTS: ["Synced Artist"],
202
+ UnifiedMetadataKey.ALBUM: "Synced Album",
203
+ }
204
+ update_metadata(test_file, vorbis_metadata, metadata_strategy=MetadataWritingStrategy.SYNC)
205
+
206
+ # Verify ID3v1 metadata behavior with different strategies
207
+ # When Vorbis is written, it overwrites the ID3v1 tag
208
+ id3v1_after = get_unified_metadata(test_file, metadata_format=MetadataFormat.ID3V1)
209
+ assert id3v1_after.get(UnifiedMetadataKey.TITLE) == "Synced Title" # ID3v1 was overwritten
210
+
211
+ id3v2_after = get_unified_metadata(test_file, metadata_format=MetadataFormat.ID3V2)
212
+ assert id3v2_after.get(UnifiedMetadataKey.TITLE) == "Synced Title"
213
+
214
+ vorbis_after = get_unified_metadata(test_file, metadata_format=MetadataFormat.VORBIS)
215
+ assert vorbis_after.get(UnifiedMetadataKey.TITLE) == "Synced Title"
@@ -0,0 +1,42 @@
1
+ import warnings
2
+
3
+ import pytest
4
+
5
+ from audiometa import get_unified_metadata, update_metadata
6
+ from audiometa.exceptions import MetadataFieldNotSupportedByMetadataFormatError
7
+ from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
8
+ from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
9
+
10
+
11
+ @pytest.mark.integration
12
+ class TestFailBehavior:
13
+ def test_fail_on_unsupported_field_enabled(self):
14
+ with temp_file_with_metadata({"title": "Test"}, "wav") as test_file:
15
+ test_metadata = {
16
+ UnifiedMetadataKey.TITLE: "Test Title",
17
+ UnifiedMetadataKey.REPLAYGAIN: "89 dB", # REPLAYGAIN is not supported by RIFF format
18
+ }
19
+
20
+ with pytest.raises(MetadataFieldNotSupportedByMetadataFormatError) as exc_info:
21
+ update_metadata(test_file, test_metadata, fail_on_unsupported_field=True)
22
+
23
+ assert "Fields not supported by riff format" in str(exc_info.value)
24
+ assert "REPLAYGAIN" in str(exc_info.value)
25
+
26
+ def test_fail_on_unsupported_field_disabled_graceful_default(self):
27
+ with temp_file_with_metadata({"title": "Test"}, "wav") as test_file:
28
+ test_metadata = {
29
+ UnifiedMetadataKey.TITLE: "Test Title",
30
+ UnifiedMetadataKey.REPLAYGAIN: "89 dB", # REPLAYGAIN is not supported by RIFF format
31
+ }
32
+
33
+ with warnings.catch_warnings(record=True) as w:
34
+ warnings.simplefilter("always")
35
+ update_metadata(test_file, test_metadata) # fail_on_unsupported_field=False by default
36
+
37
+ assert len(w) > 0
38
+ warning_messages = [str(warning.message) for warning in w]
39
+ assert any("unsupported" in msg.lower() or "not supported" in msg.lower() for msg in warning_messages)
40
+
41
+ metadata = get_unified_metadata(test_file)
42
+ assert metadata.get(UnifiedMetadataKey.TITLE) == "Test Title"
@@ -0,0 +1,93 @@
1
+ import pytest
2
+
3
+ from audiometa import get_unified_metadata, update_metadata
4
+ from audiometa.exceptions import MetadataFieldNotSupportedByMetadataFormatError
5
+ from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
6
+ from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
7
+
8
+
9
+ @pytest.mark.integration
10
+ class TestNoWritingOnFailure:
11
+ def test_fail_on_unsupported_field_no_changes_wav_only(self):
12
+ initial_metadata = {"title": "Original Title", "artist": "Original Artist"}
13
+ with temp_file_with_metadata(initial_metadata, "wav") as test_file:
14
+ initial_read = get_unified_metadata(test_file)
15
+ assert initial_read.get(UnifiedMetadataKey.TITLE) == "Original Title"
16
+ assert initial_read.get(UnifiedMetadataKey.ARTISTS) == ["Original Artist"]
17
+
18
+ test_metadata = {
19
+ UnifiedMetadataKey.TITLE: "New Title", # This should NOT be written
20
+ UnifiedMetadataKey.ARTISTS: ["New Artist"], # This should NOT be written
21
+ UnifiedMetadataKey.REPLAYGAIN: "89 dB", # REPLAYGAIN is not supported by RIFF format
22
+ }
23
+
24
+ with pytest.raises(MetadataFieldNotSupportedByMetadataFormatError):
25
+ update_metadata(test_file, test_metadata, fail_on_unsupported_field=True)
26
+
27
+ final_read = get_unified_metadata(test_file)
28
+ assert final_read.get(UnifiedMetadataKey.TITLE) == "Original Title" # Should be unchanged
29
+ assert final_read.get(UnifiedMetadataKey.ARTISTS) == ["Original Artist"] # Should be unchanged
30
+ assert final_read.get(UnifiedMetadataKey.REPLAYGAIN) is None
31
+
32
+ def test_fail_on_unsupported_field_no_changes_id3v2_only(self):
33
+ initial_metadata = {"title": "Original MP3 Title", "artist": "Original MP3 Artist"}
34
+ with temp_file_with_metadata(initial_metadata, "mp3") as test_file:
35
+ initial_read = get_unified_metadata(test_file)
36
+ assert initial_read.get(UnifiedMetadataKey.TITLE) == "Original MP3 Title"
37
+ assert initial_read.get(UnifiedMetadataKey.ARTISTS) == ["Original MP3 Artist"]
38
+
39
+ test_metadata = {
40
+ UnifiedMetadataKey.TITLE: "New MP3 Title", # Should NOT be written
41
+ UnifiedMetadataKey.ARTISTS: ["New MP3 Artist"], # Should NOT be written
42
+ UnifiedMetadataKey.ARCHIVAL_LOCATION: "some location", # This field is not supported by any format
43
+ }
44
+
45
+ with pytest.raises(MetadataFieldNotSupportedByMetadataFormatError):
46
+ update_metadata(test_file, test_metadata, fail_on_unsupported_field=True)
47
+
48
+ final_read = get_unified_metadata(test_file)
49
+ assert final_read.get(UnifiedMetadataKey.TITLE) == "Original MP3 Title" # Should be unchanged
50
+ assert final_read.get(UnifiedMetadataKey.ARTISTS) == ["Original MP3 Artist"] # Should be unchanged
51
+ assert final_read.get(UnifiedMetadataKey.ARCHIVAL_LOCATION) is None # Should not exist
52
+
53
+ def test_fail_on_unsupported_field_no_changes_vorbis_only(self):
54
+ initial_metadata = {"title": "Original FLAC Title", "artist": "Original FLAC Artist"}
55
+ with temp_file_with_metadata(initial_metadata, "flac") as test_file:
56
+ initial_read = get_unified_metadata(test_file)
57
+ assert initial_read.get(UnifiedMetadataKey.TITLE) == "Original FLAC Title"
58
+ assert initial_read.get(UnifiedMetadataKey.ARTISTS) == ["Original FLAC Artist"]
59
+
60
+ test_metadata = {
61
+ UnifiedMetadataKey.TITLE: "New FLAC Title", # Should NOT be written
62
+ UnifiedMetadataKey.ARTISTS: ["New FLAC Artist"], # Should NOT be written
63
+ UnifiedMetadataKey.ARCHIVAL_LOCATION: "some location", # This field is not supported by any format
64
+ }
65
+
66
+ with pytest.raises(MetadataFieldNotSupportedByMetadataFormatError):
67
+ update_metadata(test_file, test_metadata, fail_on_unsupported_field=True)
68
+
69
+ final_read = get_unified_metadata(test_file)
70
+ assert final_read.get(UnifiedMetadataKey.TITLE) == "Original FLAC Title" # Should be unchanged
71
+ assert final_read.get(UnifiedMetadataKey.ARTISTS) == ["Original FLAC Artist"] # Should be unchanged
72
+ assert final_read.get(UnifiedMetadataKey.ARCHIVAL_LOCATION) is None # Should not exist
73
+
74
+ def test_fail_on_unsupported_field_no_changes_riff_only(self):
75
+ initial_metadata = {"title": "Original WAV Title", "artist": "Original WAV Artist"}
76
+ with temp_file_with_metadata(initial_metadata, "wav") as test_file:
77
+ initial_read = get_unified_metadata(test_file)
78
+ assert initial_read.get(UnifiedMetadataKey.TITLE) == "Original WAV Title"
79
+ assert initial_read.get(UnifiedMetadataKey.ARTISTS) == ["Original WAV Artist"]
80
+
81
+ test_metadata = {
82
+ UnifiedMetadataKey.TITLE: "New WAV Title", # TITLE is supported by RIFF format
83
+ UnifiedMetadataKey.ARTISTS: ["New WAV Artist"], # ARTISTS is supported by RIFF format
84
+ UnifiedMetadataKey.REPLAYGAIN: "89 dB", # REPLAYGAIN is not supported by any format
85
+ }
86
+
87
+ with pytest.raises(MetadataFieldNotSupportedByMetadataFormatError):
88
+ update_metadata(test_file, test_metadata, fail_on_unsupported_field=True)
89
+
90
+ final_read = get_unified_metadata(test_file)
91
+ assert final_read.get(UnifiedMetadataKey.TITLE) == "Original WAV Title" # Should be unchanged
92
+ assert final_read.get(UnifiedMetadataKey.ARTISTS) == ["Original WAV Artist"] # Should be unchanged
93
+ assert final_read.get(UnifiedMetadataKey.REPLAYGAIN) is None # Should not exist
@@ -0,0 +1,99 @@
1
+ import pytest
2
+
3
+ from audiometa import get_unified_metadata, update_metadata
4
+ from audiometa.exceptions import MetadataFieldNotSupportedByMetadataFormatError
5
+ from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
6
+ from audiometa.utils.metadata_writing_strategy import MetadataWritingStrategy
7
+ from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
8
+
9
+
10
+ @pytest.mark.integration
11
+ class TestStrategySpecific:
12
+ def test_fail_on_unsupported_field_preserve_strategy(self):
13
+ initial_metadata = {"title": "Original WAV Title", "artist": "Original WAV Artist"}
14
+ with temp_file_with_metadata(initial_metadata, "wav") as test_file:
15
+ initial_read = get_unified_metadata(test_file)
16
+ assert initial_read.get(UnifiedMetadataKey.TITLE) == "Original WAV Title"
17
+ assert initial_read.get(UnifiedMetadataKey.ARTISTS) == ["Original WAV Artist"]
18
+
19
+ test_metadata = {
20
+ UnifiedMetadataKey.TITLE: "New WAV Title", # Should NOT be written
21
+ UnifiedMetadataKey.ARTISTS: ["New WAV Artist"], # Should NOT be written
22
+ UnifiedMetadataKey.REPLAYGAIN: "89 dB", # Not supported by RIFF format
23
+ }
24
+
25
+ with pytest.raises(MetadataFieldNotSupportedByMetadataFormatError) as exc_info:
26
+ update_metadata(
27
+ test_file,
28
+ test_metadata,
29
+ metadata_strategy=MetadataWritingStrategy.PRESERVE,
30
+ fail_on_unsupported_field=True,
31
+ )
32
+
33
+ assert "Fields not supported by riff format" in str(exc_info.value)
34
+ assert "REPLAYGAIN" in str(exc_info.value)
35
+
36
+ final_read = get_unified_metadata(test_file)
37
+ assert final_read.get(UnifiedMetadataKey.TITLE) == "Original WAV Title" # Should be unchanged
38
+ assert final_read.get(UnifiedMetadataKey.ARTISTS) == ["Original WAV Artist"] # Should be unchanged
39
+ assert final_read.get(UnifiedMetadataKey.REPLAYGAIN) is None # Should not exist
40
+
41
+ def test_fail_on_unsupported_field_cleanup_strategy(self):
42
+ initial_metadata = {"title": "Original WAV Title", "artist": "Original WAV Artist"}
43
+ with temp_file_with_metadata(initial_metadata, "wav") as test_file:
44
+ initial_read = get_unified_metadata(test_file)
45
+ assert initial_read.get(UnifiedMetadataKey.TITLE) == "Original WAV Title"
46
+ assert initial_read.get(UnifiedMetadataKey.ARTISTS) == ["Original WAV Artist"]
47
+
48
+ test_metadata = {
49
+ UnifiedMetadataKey.TITLE: "New WAV Title", # Should NOT be written
50
+ UnifiedMetadataKey.ARTISTS: ["New WAV Artist"], # Should NOT be written
51
+ UnifiedMetadataKey.REPLAYGAIN: "89 dB", # Not supported by RIFF format
52
+ }
53
+
54
+ with pytest.raises(MetadataFieldNotSupportedByMetadataFormatError) as exc_info:
55
+ update_metadata(
56
+ test_file,
57
+ test_metadata,
58
+ metadata_strategy=MetadataWritingStrategy.CLEANUP,
59
+ fail_on_unsupported_field=True,
60
+ )
61
+
62
+ assert "Fields not supported by riff format" in str(exc_info.value)
63
+ assert "REPLAYGAIN" in str(exc_info.value)
64
+
65
+ final_read = get_unified_metadata(test_file)
66
+ assert final_read.get(UnifiedMetadataKey.TITLE) == "Original WAV Title" # Should be unchanged
67
+ assert final_read.get(UnifiedMetadataKey.ARTISTS) == ["Original WAV Artist"] # Should be unchanged
68
+ assert final_read.get(UnifiedMetadataKey.REPLAYGAIN) is None # Should not exist
69
+
70
+ def test_fail_on_unsupported_field_sync_strategy(self):
71
+ initial_metadata = {"title": "Original WAV Title", "artist": "Original WAV Artist"}
72
+ with temp_file_with_metadata(initial_metadata, "wav") as test_file:
73
+ initial_read = get_unified_metadata(test_file)
74
+ assert initial_read.get(UnifiedMetadataKey.TITLE) == "Original WAV Title"
75
+ assert initial_read.get(UnifiedMetadataKey.ARTISTS) == ["Original WAV Artist"]
76
+
77
+ test_metadata = {
78
+ UnifiedMetadataKey.TITLE: "New WAV Title", # Should be written to RIFF (supported)
79
+ UnifiedMetadataKey.ARTISTS: ["New WAV Artist"], # Should be written to RIFF (supported)
80
+ UnifiedMetadataKey.REPLAYGAIN: "89 dB", # Not supported by RIFF format - should cause failure
81
+ }
82
+
83
+ with pytest.raises(MetadataFieldNotSupportedByMetadataFormatError) as exc_info:
84
+ update_metadata(
85
+ test_file,
86
+ test_metadata,
87
+ metadata_strategy=MetadataWritingStrategy.SYNC,
88
+ fail_on_unsupported_field=True,
89
+ )
90
+
91
+ assert "Fields not supported by riff format" in str(exc_info.value)
92
+ assert "REPLAYGAIN" in str(exc_info.value)
93
+
94
+ # With fail_on_unsupported_field=True, the operation should be atomic
95
+ # No writing should occur, so file should remain unchanged
96
+ final_read = get_unified_metadata(test_file)
97
+ assert final_read.get(UnifiedMetadataKey.TITLE) == "Original WAV Title" # Should be unchanged
98
+ assert final_read.get(UnifiedMetadataKey.ARTISTS) == ["Original WAV Artist"] # Should be unchanged
99
+ assert final_read.get(UnifiedMetadataKey.REPLAYGAIN) is None # Should not exist
File without changes
File without changes
@@ -0,0 +1,26 @@
1
+ from pathlib import Path
2
+
3
+ import pytest
4
+
5
+ from audiometa._audio_file import _AudioFile
6
+
7
+
8
+ @pytest.mark.unit
9
+ class TestAudioFileBitrate:
10
+ def test_get_bitrate_mp3(self, sample_mp3_file: Path):
11
+ audio_file = _AudioFile(sample_mp3_file)
12
+ bitrate = audio_file.get_bitrate()
13
+ assert isinstance(bitrate, int)
14
+ assert bitrate > 0
15
+
16
+ def test_get_bitrate_flac(self, sample_flac_file: Path):
17
+ audio_file = _AudioFile(sample_flac_file)
18
+ bitrate = audio_file.get_bitrate()
19
+ assert isinstance(bitrate, int)
20
+ assert bitrate > 0
21
+
22
+ def test_get_bitrate_wav(self, sample_wav_file: Path):
23
+ audio_file = _AudioFile(sample_wav_file)
24
+ bitrate = audio_file.get_bitrate()
25
+ assert isinstance(bitrate, int)
26
+ assert bitrate > 0
@@ -0,0 +1,31 @@
1
+ from pathlib import Path
2
+
3
+ import pytest
4
+
5
+ from audiometa._audio_file import _AudioFile
6
+
7
+
8
+ @pytest.mark.unit
9
+ class TestAudioFileChannels:
10
+ def test_get_channels_mp3(self, sample_mp3_file: Path):
11
+ audio_file = _AudioFile(sample_mp3_file)
12
+ channels = audio_file.get_channels()
13
+ assert isinstance(channels, int)
14
+ assert channels > 0
15
+
16
+ def test_get_channels_flac(self, sample_flac_file: Path):
17
+ audio_file = _AudioFile(sample_flac_file)
18
+ channels = audio_file.get_channels()
19
+ assert isinstance(channels, int)
20
+ assert channels > 0
21
+
22
+ def test_get_channels_wav(self, sample_wav_file: Path):
23
+ audio_file = _AudioFile(sample_wav_file)
24
+ channels = audio_file.get_channels()
25
+ assert isinstance(channels, int)
26
+ assert channels > 0
27
+
28
+ def test_get_channels_returns_int(self, sample_mp3_file: Path):
29
+ audio_file = _AudioFile(sample_mp3_file)
30
+ channels = audio_file.get_channels()
31
+ assert isinstance(channels, int)
@@ -0,0 +1,38 @@
1
+ from pathlib import Path
2
+
3
+ import pytest
4
+
5
+ from audiometa._audio_file import _AudioFile
6
+
7
+
8
+ @pytest.mark.unit
9
+ class TestAudioFileDurationInSec:
10
+ def test_get_duration_in_sec_mp3(self, sample_mp3_file: Path):
11
+ audio_file = _AudioFile(sample_mp3_file)
12
+ duration = audio_file.get_duration_in_sec()
13
+ assert isinstance(duration, float)
14
+ assert duration > 0
15
+ assert duration < 1000 # Reasonable bounds check
16
+
17
+ def test_get_duration_in_sec_flac(self, sample_flac_file: Path):
18
+ audio_file = _AudioFile(sample_flac_file)
19
+ duration = audio_file.get_duration_in_sec()
20
+ assert isinstance(duration, float)
21
+ assert duration > 0
22
+
23
+ def test_get_duration_in_sec_wav(self, sample_wav_file: Path):
24
+ audio_file = _AudioFile(sample_wav_file)
25
+ duration = audio_file.get_duration_in_sec()
26
+ assert isinstance(duration, float)
27
+ assert duration > 0
28
+
29
+ def test_get_duration_in_sec_long_file(self, duration_182s_mp3: Path):
30
+ audio_file = _AudioFile(duration_182s_mp3)
31
+ duration = audio_file.get_duration_in_sec()
32
+ assert isinstance(duration, float)
33
+ assert duration > 100
34
+
35
+ def test_get_duration_in_sec_returns_float(self, sample_mp3_file: Path):
36
+ audio_file = _AudioFile(sample_mp3_file)
37
+ duration = audio_file.get_duration_in_sec()
38
+ assert isinstance(duration, float)