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,151 @@
1
+ from unittest.mock import MagicMock
2
+
3
+ import pytest
4
+
5
+ from audiometa.exceptions import InvalidRatingValueError
6
+ from audiometa.manager._rating_supporting._RatingSupportingMetadataManager import _RatingSupportingMetadataManager
7
+ from audiometa.manager._rating_supporting.id3v2._Id3v2Manager import _Id3v2Manager as Id3v2Manager
8
+ from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
9
+
10
+
11
+ @pytest.mark.unit
12
+ class TestRatingValidation:
13
+ def test_validate_rating_value_raw_mode_non_negative_allowed(self):
14
+ # These should not raise any exceptions
15
+ _RatingSupportingMetadataManager.validate_rating_value(0, None)
16
+ _RatingSupportingMetadataManager.validate_rating_value(1, None)
17
+ _RatingSupportingMetadataManager.validate_rating_value(128, None)
18
+ _RatingSupportingMetadataManager.validate_rating_value(255, None)
19
+ _RatingSupportingMetadataManager.validate_rating_value(1000, None)
20
+ _RatingSupportingMetadataManager.validate_rating_value(1.5, None)
21
+ _RatingSupportingMetadataManager.validate_rating_value(75.7, None)
22
+ _RatingSupportingMetadataManager.validate_rating_value(0.0, None)
23
+
24
+ def test_validate_rating_value_raw_mode_negative_rejected(self):
25
+ with pytest.raises(InvalidRatingValueError) as exc_info:
26
+ _RatingSupportingMetadataManager.validate_rating_value(-1, None)
27
+ assert "must be non-negative" in str(exc_info.value)
28
+
29
+ with pytest.raises(InvalidRatingValueError) as exc_info:
30
+ _RatingSupportingMetadataManager.validate_rating_value(-100, None)
31
+ assert "must be non-negative" in str(exc_info.value)
32
+
33
+ with pytest.raises(InvalidRatingValueError) as exc_info:
34
+ _RatingSupportingMetadataManager.validate_rating_value(-0.5, None)
35
+ assert "must be non-negative" in str(exc_info.value)
36
+
37
+ def test_validate_rating_value_normalized_mode_valid_values(self):
38
+ # Valid values: those that map to BASE_100_PROPORTIONAL profile
39
+ # (value/100 * 100) must be in [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
40
+ valid_values = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
41
+ for value in valid_values:
42
+ _RatingSupportingMetadataManager.validate_rating_value(value, 100)
43
+
44
+ def test_validate_rating_value_normalized_mode_valid_float_values(self):
45
+ # Valid float values: 1.5/10 * 100 = 15 (in BASE_100_PROPORTIONAL)
46
+ _RatingSupportingMetadataManager.validate_rating_value(1.5, 10)
47
+ # 2.5/10 * 100 = 25 (in BASE_100_PROPORTIONAL)
48
+ _RatingSupportingMetadataManager.validate_rating_value(2.5, 10)
49
+ # 7.5/10 * 100 = 75 (in BASE_100_PROPORTIONAL)
50
+ _RatingSupportingMetadataManager.validate_rating_value(7.5, 10)
51
+ # 50.0/100 * 100 = 50 (in BASE_100_PROPORTIONAL)
52
+ _RatingSupportingMetadataManager.validate_rating_value(50.0, 100)
53
+
54
+ def test_validate_rating_value_normalized_mode_negative_rejected(self):
55
+ with pytest.raises(InvalidRatingValueError) as exc_info:
56
+ _RatingSupportingMetadataManager.validate_rating_value(-1, 100)
57
+ assert "must be non-negative" in str(exc_info.value)
58
+
59
+ @pytest.mark.parametrize(
60
+ "rating",
61
+ [101, 100.1, 101.5],
62
+ )
63
+ def test_validate_rating_value_normalized_mode_over_max_rejected(self, rating):
64
+ with pytest.raises(InvalidRatingValueError) as exc_info:
65
+ _RatingSupportingMetadataManager.validate_rating_value(rating, 100)
66
+ assert "out of range" in str(exc_info.value)
67
+ assert "must be between 0 and 100" in str(exc_info.value)
68
+
69
+ def test_validate_rating_value_normalized_mode_invalid_profile_value(self):
70
+ # 33/100 maps to star rating index 3 (1.5 stars), which is valid
71
+ # This test verifies that values mapping to valid star rating indices are accepted
72
+ # Note: The old validation checked if output values exist, but the conversion uses star rating indices
73
+ _RatingSupportingMetadataManager.validate_rating_value(33, 100)
74
+
75
+ # Test a value that would map to an invalid star rating index (> 10)
76
+ with pytest.raises(InvalidRatingValueError) as exc_info:
77
+ _RatingSupportingMetadataManager.validate_rating_value(110, 100)
78
+ assert "out of range" in str(exc_info.value)
79
+
80
+ def test_validate_rating_value_normalized_mode_different_max_values(self):
81
+ """Test profile-based validation with different max values."""
82
+ # Test with max_value = 10 (0-10 scale)
83
+ # All values 0-10 map to valid profile values
84
+ valid_values_10 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
85
+ for value in valid_values_10:
86
+ _RatingSupportingMetadataManager.validate_rating_value(value, 10)
87
+
88
+ # Test with max_value = 255
89
+ # Valid values: those that map to BASE_255_NON_PROPORTIONAL profile values
90
+ # Profile values: 0, 13, 1, 54, 64, 118, 128, 186, 196, 242, 255
91
+ # So valid normalized values are: 0, 13, 1, 54, 64, 118, 128, 186, 196, 242, 255
92
+ valid_values_255 = [0, 1, 13, 54, 64, 118, 128, 186, 196, 242, 255]
93
+ for value in valid_values_255:
94
+ _RatingSupportingMetadataManager.validate_rating_value(value, 255)
95
+
96
+ # Also valid: values that map to BASE_100_PROPORTIONAL when scaled
97
+ # For example: 50/255 * 100 = 20 (round), which is in BASE_100_PROPORTIONAL
98
+ valid_values_255_also_100 = [25, 50, 76, 102, 127, 153, 178, 204, 229]
99
+ for value in valid_values_255_also_100:
100
+ _RatingSupportingMetadataManager.validate_rating_value(value, 255)
101
+
102
+ # Values that map to valid star rating indices are accepted
103
+ # The conversion uses star rating indices (0-10), not direct output values
104
+ # 37/255 -> index 1 (0.5 stars) -> valid
105
+ # 99/255 -> index 4 (2 stars) -> valid
106
+ # 200/255 -> index 8 (4 stars) -> valid
107
+ # All these values map to valid star rating indices, so they are accepted
108
+
109
+ # Test a value that would map to an invalid star rating index (> 10)
110
+ with pytest.raises(InvalidRatingValueError) as exc_info:
111
+ _RatingSupportingMetadataManager.validate_rating_value(256, 255)
112
+ assert "out of range" in str(exc_info.value)
113
+
114
+ def test_validate_rating_in_unified_metadata_valid(self):
115
+ manager = Id3v2Manager(audio_file=MagicMock(), normalized_rating_max_value=100)
116
+
117
+ # Valid metadata should not raise
118
+ manager._validate_rating_in_unified_metadata({UnifiedMetadataKey.RATING: 50})
119
+
120
+ def test_validate_rating_in_unified_metadata_invalid_type(self):
121
+ manager = Id3v2Manager(audio_file=MagicMock(), normalized_rating_max_value=100)
122
+
123
+ # String should raise InvalidRatingValueError
124
+ with pytest.raises(InvalidRatingValueError) as exc_info:
125
+ manager._validate_rating_in_unified_metadata({UnifiedMetadataKey.RATING: "invalid"})
126
+ assert "Rating value must be numeric" in str(exc_info.value)
127
+
128
+ # Dict should raise InvalidRatingValueError
129
+ with pytest.raises(InvalidRatingValueError) as exc_info:
130
+ manager._validate_rating_in_unified_metadata({UnifiedMetadataKey.RATING: {"not": "valid"}})
131
+ assert "Rating value must be numeric" in str(exc_info.value)
132
+
133
+ def test_validate_rating_in_unified_metadata_float_accepted(self):
134
+ manager = Id3v2Manager(audio_file=MagicMock(), normalized_rating_max_value=100)
135
+
136
+ # Float should be accepted
137
+ manager._validate_rating_in_unified_metadata({UnifiedMetadataKey.RATING: 50.0})
138
+ manager._validate_rating_in_unified_metadata({UnifiedMetadataKey.RATING: 1.5})
139
+ manager._validate_rating_in_unified_metadata({UnifiedMetadataKey.RATING: 7.5})
140
+
141
+ def test_validate_rating_in_unified_metadata_none_ignored(self):
142
+ manager = Id3v2Manager(audio_file=MagicMock(), normalized_rating_max_value=100)
143
+
144
+ # None should be ignored (no exception)
145
+ manager._validate_rating_in_unified_metadata({UnifiedMetadataKey.RATING: None})
146
+
147
+ def test_validate_rating_in_unified_metadata_missing_key(self):
148
+ manager = Id3v2Manager(audio_file=MagicMock(), normalized_rating_max_value=100)
149
+
150
+ # Missing key should be ignored
151
+ manager._validate_rating_in_unified_metadata({})
@@ -0,0 +1,61 @@
1
+ from unittest.mock import MagicMock
2
+
3
+ import pytest
4
+
5
+ from audiometa.manager._rating_supporting.id3v2._Id3v2Manager import _Id3v2Manager as Id3v2Manager
6
+ from audiometa.manager._rating_supporting.riff._RiffManager import _RiffManager as RiffManager
7
+ from audiometa.manager._rating_supporting.vorbis._VorbisManager import _VorbisManager as VorbisManager
8
+
9
+
10
+ @pytest.mark.unit
11
+ class TestWritingProfiles:
12
+ @pytest.mark.parametrize(
13
+ ("normalized_rating", "expected_raw_rating"),
14
+ [
15
+ (0, 0),
16
+ (1, 13),
17
+ (2, 1),
18
+ (3, 54),
19
+ (4, 64),
20
+ (5, 118),
21
+ (6, 128),
22
+ (7, 186),
23
+ (8, 196),
24
+ (9, 242),
25
+ (10, 255),
26
+ ],
27
+ )
28
+ def test_base_255_non_proportional(self, normalized_rating, expected_raw_rating):
29
+ id3v2_audio_file = MagicMock()
30
+ id3v2_manager = Id3v2Manager(audio_file=id3v2_audio_file, normalized_rating_max_value=10)
31
+ raw_rating = id3v2_manager._convert_normalized_rating_to_file_rating(normalized_rating=normalized_rating)
32
+ assert raw_rating == expected_raw_rating
33
+
34
+ wave_audio_file = MagicMock()
35
+ wave_audio_file.file_extension = ".wav"
36
+ riff_manager = RiffManager(audio_file=wave_audio_file, normalized_rating_max_value=10)
37
+ raw_rating = riff_manager._convert_normalized_rating_to_file_rating(normalized_rating=normalized_rating)
38
+ assert raw_rating == expected_raw_rating
39
+
40
+ @pytest.mark.parametrize(
41
+ ("normalized_rating", "expected_raw_rating"),
42
+ [
43
+ (0, 0),
44
+ (1, 10),
45
+ (2, 20),
46
+ (3, 30),
47
+ (4, 40),
48
+ (5, 50),
49
+ (6, 60),
50
+ (7, 70),
51
+ (8, 80),
52
+ (9, 90),
53
+ (10, 100),
54
+ ],
55
+ )
56
+ def test_base_100_proportional(self, normalized_rating, expected_raw_rating):
57
+ flac_audio_file = MagicMock()
58
+ flac_audio_file.file_extension = ".flac"
59
+ vorbis_manager = VorbisManager(audio_file=flac_audio_file, normalized_rating_max_value=10)
60
+ raw_rating = vorbis_manager._convert_normalized_rating_to_file_rating(normalized_rating=normalized_rating)
61
+ assert raw_rating == expected_raw_rating
@@ -0,0 +1,135 @@
1
+ import pytest
2
+
3
+ from audiometa import validate_metadata_for_update
4
+ from audiometa.exceptions import InvalidMetadataFieldFormatError, InvalidMetadataFieldTypeError
5
+ from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
6
+
7
+
8
+ @pytest.mark.unit
9
+ class TestReleaseDateFormatValidation:
10
+ @pytest.mark.parametrize(
11
+ "year",
12
+ ["2024", "1900", "0000", "9999", "1970"],
13
+ )
14
+ def test_valid_yyyy_format(self, year):
15
+ validate_metadata_for_update({UnifiedMetadataKey.RELEASE_DATE: year})
16
+
17
+ @pytest.mark.parametrize(
18
+ "date",
19
+ [
20
+ "2024-01-01",
21
+ "2024-12-31",
22
+ "1900-01-01",
23
+ "0000-00-00",
24
+ "9999-12-31",
25
+ "1970-06-15",
26
+ ],
27
+ )
28
+ def test_valid_yyyy_mm_dd_format(self, date):
29
+ validate_metadata_for_update({UnifiedMetadataKey.RELEASE_DATE: date})
30
+
31
+ @pytest.mark.parametrize(
32
+ "date",
33
+ [
34
+ "2024/01/01",
35
+ "2024.01.01",
36
+ "2024_01_01",
37
+ "2024 01 01",
38
+ ],
39
+ )
40
+ def test_invalid_format_wrong_separator(self, date):
41
+ with pytest.raises(InvalidMetadataFieldFormatError) as exc_info:
42
+ validate_metadata_for_update({UnifiedMetadataKey.RELEASE_DATE: date})
43
+ error = exc_info.value
44
+ assert error.field == UnifiedMetadataKey.RELEASE_DATE.value
45
+ assert error.value == date
46
+ assert "YYYY" in error.expected_format
47
+
48
+ @pytest.mark.parametrize(
49
+ "date",
50
+ [
51
+ "2024-1-1",
52
+ "2024-1-01",
53
+ "2024-01-1",
54
+ ],
55
+ )
56
+ def test_invalid_format_single_digit_month_day(self, date):
57
+ with pytest.raises(InvalidMetadataFieldFormatError) as exc_info:
58
+ validate_metadata_for_update({UnifiedMetadataKey.RELEASE_DATE: date})
59
+ error = exc_info.value
60
+ assert error.field == UnifiedMetadataKey.RELEASE_DATE.value
61
+ assert error.value == date
62
+
63
+ @pytest.mark.parametrize(
64
+ "date",
65
+ [
66
+ "24",
67
+ "024",
68
+ "999",
69
+ ],
70
+ )
71
+ def test_invalid_format_short_year(self, date):
72
+ with pytest.raises(InvalidMetadataFieldFormatError) as exc_info:
73
+ validate_metadata_for_update({UnifiedMetadataKey.RELEASE_DATE: date})
74
+ error = exc_info.value
75
+ assert error.field == UnifiedMetadataKey.RELEASE_DATE.value
76
+ assert error.value == date
77
+
78
+ @pytest.mark.parametrize(
79
+ "date",
80
+ [
81
+ "20241",
82
+ "20241-01-01",
83
+ ],
84
+ )
85
+ def test_invalid_format_long_year(self, date):
86
+ with pytest.raises(InvalidMetadataFieldFormatError) as exc_info:
87
+ validate_metadata_for_update({UnifiedMetadataKey.RELEASE_DATE: date})
88
+ error = exc_info.value
89
+ assert error.field == UnifiedMetadataKey.RELEASE_DATE.value
90
+ assert error.value == date
91
+
92
+ @pytest.mark.parametrize(
93
+ "date",
94
+ [
95
+ "not-a-date",
96
+ "2024-abc-01",
97
+ "abcd-01-01",
98
+ "2024-01-abc",
99
+ "2024a",
100
+ ],
101
+ )
102
+ def test_invalid_format_non_numeric(self, date):
103
+ with pytest.raises(InvalidMetadataFieldFormatError) as exc_info:
104
+ validate_metadata_for_update({UnifiedMetadataKey.RELEASE_DATE: date})
105
+ error = exc_info.value
106
+ assert error.field == UnifiedMetadataKey.RELEASE_DATE.value
107
+ assert error.value == date
108
+
109
+ @pytest.mark.parametrize(
110
+ "date",
111
+ [
112
+ "2024-",
113
+ "2024-01",
114
+ "2024-01-",
115
+ "-01-01",
116
+ ],
117
+ )
118
+ def test_invalid_format_incomplete_date(self, date):
119
+ with pytest.raises(InvalidMetadataFieldFormatError) as exc_info:
120
+ validate_metadata_for_update({UnifiedMetadataKey.RELEASE_DATE: date})
121
+ error = exc_info.value
122
+ assert error.field == UnifiedMetadataKey.RELEASE_DATE.value
123
+ assert error.value == date
124
+
125
+ def test_none_value_allowed(self):
126
+ validate_metadata_for_update({UnifiedMetadataKey.RELEASE_DATE: None})
127
+
128
+ def test_empty_string_allowed(self):
129
+ validate_metadata_for_update({UnifiedMetadataKey.RELEASE_DATE: ""})
130
+
131
+ def test_format_validation_after_type_validation(self):
132
+ invalid_type = {UnifiedMetadataKey.RELEASE_DATE: 2024}
133
+ with pytest.raises(InvalidMetadataFieldTypeError) as exc_info:
134
+ validate_metadata_for_update(invalid_type)
135
+ assert not isinstance(exc_info.value, InvalidMetadataFieldFormatError)
@@ -0,0 +1,75 @@
1
+ import pytest
2
+
3
+ from audiometa import validate_metadata_for_update
4
+ from audiometa.exceptions import InvalidMetadataFieldFormatError, InvalidMetadataFieldTypeError
5
+ from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
6
+
7
+
8
+ @pytest.mark.unit
9
+ class TestDiscNumberValidation:
10
+ """Test disc number field validation in validate_metadata_for_update."""
11
+
12
+ @pytest.mark.parametrize(
13
+ "disc_number",
14
+ [1, 5, 99, 255],
15
+ )
16
+ def test_valid_disc_number_as_int(self, disc_number):
17
+ validate_metadata_for_update({UnifiedMetadataKey.DISC_NUMBER: disc_number})
18
+
19
+ @pytest.mark.parametrize(
20
+ "disc_number",
21
+ ["1", "5", "99"],
22
+ )
23
+ def test_invalid_disc_number_as_string(self, disc_number):
24
+ with pytest.raises(InvalidMetadataFieldTypeError):
25
+ validate_metadata_for_update({UnifiedMetadataKey.DISC_NUMBER: disc_number})
26
+
27
+ def test_disc_number_none_is_allowed(self):
28
+ validate_metadata_for_update({UnifiedMetadataKey.DISC_NUMBER: None})
29
+
30
+ @pytest.mark.parametrize(
31
+ "invalid_value",
32
+ [3.14, [], "1"],
33
+ )
34
+ def test_invalid_disc_number_type(self, invalid_value):
35
+ with pytest.raises(InvalidMetadataFieldTypeError):
36
+ validate_metadata_for_update({UnifiedMetadataKey.DISC_NUMBER: invalid_value})
37
+
38
+ @pytest.mark.parametrize(
39
+ "disc_number",
40
+ [-1, -5],
41
+ )
42
+ def test_invalid_disc_number_negative(self, disc_number):
43
+ with pytest.raises(InvalidMetadataFieldFormatError):
44
+ validate_metadata_for_update({UnifiedMetadataKey.DISC_NUMBER: disc_number})
45
+
46
+
47
+ @pytest.mark.unit
48
+ class TestDiscTotalValidation:
49
+ """Test disc total field validation in validate_metadata_for_update."""
50
+
51
+ @pytest.mark.parametrize(
52
+ "disc_total",
53
+ [1, 5, 99, 255],
54
+ )
55
+ def test_valid_disc_total_as_int(self, disc_total):
56
+ validate_metadata_for_update({UnifiedMetadataKey.DISC_TOTAL: disc_total})
57
+
58
+ def test_disc_total_none_is_allowed(self):
59
+ validate_metadata_for_update({UnifiedMetadataKey.DISC_TOTAL: None})
60
+
61
+ @pytest.mark.parametrize(
62
+ "invalid_value",
63
+ [3.14, [], "1"],
64
+ )
65
+ def test_invalid_disc_total_type(self, invalid_value):
66
+ with pytest.raises(InvalidMetadataFieldTypeError):
67
+ validate_metadata_for_update({UnifiedMetadataKey.DISC_TOTAL: invalid_value})
68
+
69
+ @pytest.mark.parametrize(
70
+ "disc_total",
71
+ [-1, -5],
72
+ )
73
+ def test_invalid_disc_total_negative(self, disc_total):
74
+ with pytest.raises(InvalidMetadataFieldFormatError):
75
+ validate_metadata_for_update({UnifiedMetadataKey.DISC_TOTAL: disc_total})
@@ -0,0 +1,121 @@
1
+ """Unit tests for ISRC metadata field format validation."""
2
+
3
+ import pytest
4
+
5
+ from audiometa import validate_metadata_for_update
6
+ from audiometa.exceptions import InvalidMetadataFieldFormatError, InvalidMetadataFieldTypeError
7
+ from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
8
+
9
+
10
+ @pytest.mark.unit
11
+ class TestISRCFormatValidation:
12
+ @pytest.mark.parametrize(
13
+ "isrc",
14
+ [
15
+ "USRC17607839", # Standard 12-char format
16
+ "GBAYE0000001", # UK code
17
+ "JPAB01234567", # Japan code
18
+ "DEAB01234567", # Germany code
19
+ "usrc17607839", # Lowercase allowed
20
+ "UsRc17607839", # Mixed case allowed
21
+ ],
22
+ )
23
+ def test_valid_12_char_format(self, isrc):
24
+ validate_metadata_for_update({UnifiedMetadataKey.ISRC: isrc})
25
+
26
+ @pytest.mark.parametrize(
27
+ "isrc",
28
+ [
29
+ "US-RC1-76-07839", # Standard hyphenated format
30
+ "GB-AYE-00-00001", # UK code with hyphens
31
+ "JP-AB0-12-34567", # Japan code with hyphens
32
+ ],
33
+ )
34
+ def test_valid_hyphenated_format(self, isrc):
35
+ validate_metadata_for_update({UnifiedMetadataKey.ISRC: isrc})
36
+
37
+ @pytest.mark.parametrize(
38
+ "isrc",
39
+ [
40
+ "USRC1760783", # Too short (11 chars)
41
+ "USRC176078", # Too short (10 chars)
42
+ "USRC17607", # Too short (9 chars)
43
+ "ABC", # Way too short
44
+ "U", # Single char
45
+ ],
46
+ )
47
+ def test_invalid_format_too_short(self, isrc):
48
+ with pytest.raises(InvalidMetadataFieldFormatError) as exc_info:
49
+ validate_metadata_for_update({UnifiedMetadataKey.ISRC: isrc})
50
+ error = exc_info.value
51
+ assert error.field == UnifiedMetadataKey.ISRC.value
52
+ assert error.value == isrc
53
+ assert "12 alphanumeric characters" in error.expected_format
54
+
55
+ @pytest.mark.parametrize(
56
+ "isrc",
57
+ [
58
+ "USRC176078390", # Too long (13 chars)
59
+ "USRC1760783901", # Too long (14 chars)
60
+ "USRC17607839012", # Too long (15 chars non-hyphenated)
61
+ "USRC176078390123", # Too long (16 chars)
62
+ ],
63
+ )
64
+ def test_invalid_format_too_long(self, isrc):
65
+ with pytest.raises(InvalidMetadataFieldFormatError) as exc_info:
66
+ validate_metadata_for_update({UnifiedMetadataKey.ISRC: isrc})
67
+ error = exc_info.value
68
+ assert error.field == UnifiedMetadataKey.ISRC.value
69
+ assert error.value == isrc
70
+ assert "12 alphanumeric characters" in error.expected_format
71
+
72
+ @pytest.mark.parametrize(
73
+ "isrc",
74
+ [
75
+ "USRC-17607839", # Wrong hyphen position
76
+ "US-RC17607839", # Missing hyphens
77
+ "US-RC1-7607839", # Wrong segment lengths
78
+ "US-RC1-76-0783", # Last segment too short (4 chars)
79
+ "US-RC1-76-078390", # Last segment too long (6 chars)
80
+ "US-R-76-07839", # Second segment too short
81
+ "US-RC12-76-07839", # Second segment too long
82
+ "U-RC1-76-07839", # First segment too short
83
+ "USA-RC1-76-07839", # First segment too long
84
+ "US-RC1-7-07839", # Third segment too short
85
+ "US-RC1-761-07839", # Third segment too long
86
+ ],
87
+ )
88
+ def test_invalid_hyphenated_format(self, isrc):
89
+ with pytest.raises(InvalidMetadataFieldFormatError) as exc_info:
90
+ validate_metadata_for_update({UnifiedMetadataKey.ISRC: isrc})
91
+ error = exc_info.value
92
+ assert error.field == UnifiedMetadataKey.ISRC.value
93
+ assert error.value == isrc
94
+
95
+ @pytest.mark.parametrize(
96
+ "isrc",
97
+ [
98
+ "USRC1760783!", # Special character
99
+ "USRC@7607839", # Special character
100
+ "USRC 7607839", # Space
101
+ "USRC_7607839", # Underscore
102
+ ],
103
+ )
104
+ def test_invalid_format_special_characters(self, isrc):
105
+ with pytest.raises(InvalidMetadataFieldFormatError) as exc_info:
106
+ validate_metadata_for_update({UnifiedMetadataKey.ISRC: isrc})
107
+ error = exc_info.value
108
+ assert error.field == UnifiedMetadataKey.ISRC.value
109
+ assert error.value == isrc
110
+
111
+ def test_none_value_allowed(self):
112
+ validate_metadata_for_update({UnifiedMetadataKey.ISRC: None})
113
+
114
+ def test_empty_string_allowed(self):
115
+ validate_metadata_for_update({UnifiedMetadataKey.ISRC: ""})
116
+
117
+ def test_format_validation_after_type_validation(self):
118
+ invalid_type = {UnifiedMetadataKey.ISRC: 12345}
119
+ with pytest.raises(InvalidMetadataFieldTypeError) as exc_info:
120
+ validate_metadata_for_update(invalid_type)
121
+ assert not isinstance(exc_info.value, InvalidMetadataFieldFormatError)
@@ -0,0 +1,30 @@
1
+ """Unit tests for ISRC metadata field type validation."""
2
+
3
+ import pytest
4
+
5
+ from audiometa import validate_metadata_for_update
6
+ from audiometa.exceptions import InvalidMetadataFieldTypeError
7
+ from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
8
+
9
+
10
+ @pytest.mark.unit
11
+ class TestISRCTypeValidation:
12
+ def test_valid_isrc_string(self):
13
+ validate_metadata_for_update({UnifiedMetadataKey.ISRC: "USRC17607839"})
14
+
15
+ def test_valid_isrc_with_hyphens(self):
16
+ validate_metadata_for_update({UnifiedMetadataKey.ISRC: "US-RC1-76-07839"})
17
+
18
+ def test_invalid_isrc_type_integer_raises(self):
19
+ with pytest.raises(InvalidMetadataFieldTypeError):
20
+ validate_metadata_for_update({UnifiedMetadataKey.ISRC: 12345})
21
+
22
+ def test_invalid_isrc_type_list_raises(self):
23
+ with pytest.raises(InvalidMetadataFieldTypeError):
24
+ validate_metadata_for_update({UnifiedMetadataKey.ISRC: ["USRC17607839"]})
25
+
26
+ def test_isrc_none_is_allowed(self):
27
+ validate_metadata_for_update({UnifiedMetadataKey.ISRC: None})
28
+
29
+ def test_isrc_empty_string_is_allowed(self):
30
+ validate_metadata_for_update({UnifiedMetadataKey.ISRC: ""})
@@ -0,0 +1,46 @@
1
+ import pytest
2
+
3
+ from audiometa import validate_metadata_for_update
4
+ from audiometa.exceptions import InvalidMetadataFieldFormatError, InvalidMetadataFieldTypeError
5
+ from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
6
+
7
+
8
+ @pytest.mark.unit
9
+ class TestTrackNumberValidation:
10
+ """Test track number field validation in validate_metadata_for_update."""
11
+
12
+ @pytest.mark.parametrize(
13
+ "track_number",
14
+ [1, 5, 99],
15
+ )
16
+ def test_valid_track_number_as_int(self, track_number):
17
+ validate_metadata_for_update({UnifiedMetadataKey.TRACK_NUMBER: track_number})
18
+
19
+ @pytest.mark.parametrize(
20
+ "track_number",
21
+ ["1", "5", "99"],
22
+ )
23
+ def test_valid_track_number_as_string(self, track_number):
24
+ validate_metadata_for_update({UnifiedMetadataKey.TRACK_NUMBER: track_number})
25
+
26
+ @pytest.mark.parametrize(
27
+ "track_number",
28
+ ["5/12", "1/10", "99/100"],
29
+ )
30
+ def test_valid_track_number_with_total(self, track_number):
31
+ validate_metadata_for_update({UnifiedMetadataKey.TRACK_NUMBER: track_number})
32
+
33
+ def test_invalid_track_number_format(self):
34
+ with pytest.raises(InvalidMetadataFieldFormatError):
35
+ validate_metadata_for_update({UnifiedMetadataKey.TRACK_NUMBER: "/12"})
36
+
37
+ def test_track_number_none_is_allowed(self):
38
+ validate_metadata_for_update({UnifiedMetadataKey.TRACK_NUMBER: None})
39
+
40
+ @pytest.mark.parametrize(
41
+ "invalid_value",
42
+ [3.14, []],
43
+ )
44
+ def test_invalid_track_number_type(self, invalid_value):
45
+ with pytest.raises(InvalidMetadataFieldTypeError):
46
+ validate_metadata_for_update({UnifiedMetadataKey.TRACK_NUMBER: invalid_value})
@@ -0,0 +1,22 @@
1
+ import pytest
2
+
3
+ from audiometa.exceptions import InvalidMetadataFieldTypeError
4
+
5
+
6
+ @pytest.mark.unit
7
+ class TestExceptionClasses:
8
+ def test_invalid_metadata_field_type_error_attributes(self):
9
+ error = InvalidMetadataFieldTypeError("title", "str", 123)
10
+ assert error.field == "title"
11
+ assert error.expected_type == "str"
12
+ assert error.actual_type == "int"
13
+ assert error.value == 123
14
+ assert isinstance(error, TypeError)
15
+
16
+ def test_invalid_metadata_field_type_error_message_format(self):
17
+ error = InvalidMetadataFieldTypeError("artists", "list[str]", {"key": "value"})
18
+ message = str(error)
19
+ assert "artists" in message
20
+ assert "list[str]" in message
21
+ assert "dict" in message or "actual_type" in message
22
+ assert "'artists'" in message or "artists" in message