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,142 @@
1
+ import pytest
2
+
3
+ from audiometa.manager._MetadataManager import _MetadataManager as MetadataManager
4
+
5
+
6
+ @pytest.mark.unit
7
+ class TestSeparatorSelection:
8
+ @pytest.mark.parametrize(
9
+ ("values", "expected_separator"),
10
+ [
11
+ (["Artist One", "Artist Two"], "//"),
12
+ (["Artist One", "Artist Two", "Artist Three"], "//"),
13
+ (["Artist;One", "Artist;Two"], "//"),
14
+ (["Artist,One", "Artist,Two"], "//"),
15
+ (["Artist/One", "Artist/Two"], "//"),
16
+ (["Artist\\One", "Artist\\Two"], "//"),
17
+ (["Artist\\One", "Artist;Two"], "//"),
18
+ # Cases where // is present, so pick \\\\
19
+ (["Artist//One", "Artist Two"], "\\\\"),
20
+ (["Artist//One", "Artist//Two"], "\\\\"),
21
+ # Cases where // and \\\\ are present, so pick ;
22
+ (["Artist//One", "Artist\\\\Two"], ";"),
23
+ (["Artist//One", "Artist\\\\Two", "Artist\\Three"], ";"),
24
+ # Cases where //, \\\\, \\, ; are present, so pick ,
25
+ (["Artist//One", "Artist\\\\Two", "Artist\\Three", "Artist;Four"], ","),
26
+ (["Artist//One", "Artist\\\\Two", "Artist\\Three", "Artist;Four", "Artist/Five"], ","),
27
+ # Cases with mixed separators
28
+ (["Artist;One", "Artist,Two", "Artist/Three"], "//"),
29
+ (["Artist\\One", "Artist;Two", "Artist,Three"], "//"),
30
+ (["Artist/One", "Artist,Two"], "//"),
31
+ # Edge cases
32
+ (["Artist//One;Two", "Artist Three"], "\\\\"),
33
+ (["Artist\\\\One\\Two", "Artist;Three"], "//"),
34
+ ],
35
+ )
36
+ def test_find_safe_separator(self, values, expected_separator):
37
+ separator = MetadataManager.find_safe_separator(values)
38
+ assert separator == expected_separator
39
+
40
+ for value in values:
41
+ assert separator not in value
42
+
43
+ def test_find_safe_separator_prioritizes_highest_available(self):
44
+ # When clean values, uses highest priority
45
+ values = ["Artist One", "Artist Two"]
46
+ separator = MetadataManager.find_safe_separator(values)
47
+ assert separator == "//"
48
+
49
+ # When // appears, uses next priority
50
+ values = ["Artist//One", "Artist Two"]
51
+ separator = MetadataManager.find_safe_separator(values)
52
+ assert separator == "\\\\" # Double backslash
53
+
54
+ # When both // and \\ appear, uses semicolon
55
+ values = ["Artist//One", "Artist\\\\Two"] # Note: \\\\ in string is \\
56
+ separator = MetadataManager.find_safe_separator(values)
57
+ assert separator not in ["//", "\\"] # Neither // nor single \
58
+ for value in values:
59
+ assert separator not in value
60
+
61
+ def test_find_safe_separator_no_safe_separator(self):
62
+ # Test case where all common separators appear
63
+ # Note: Single backslash is hard to test, so this tests the common separators
64
+ from audiometa.manager._MetadataManager import METADATA_MULTI_VALUE_SEPARATORS_PRIORITIZED
65
+
66
+ values = ["Artist//One", "Artist;Two", "Artist/Three", "Artist,Four"]
67
+ separator = MetadataManager.find_safe_separator(values)
68
+ # Should still find a safe separator (backslash-based ones don't appear)
69
+ assert separator in METADATA_MULTI_VALUE_SEPARATORS_PRIORITIZED
70
+ for value in values:
71
+ assert separator not in value
72
+
73
+ @pytest.mark.parametrize(
74
+ "values",
75
+ [
76
+ (["Artist//One", "Artist\\\\Two"]),
77
+ (["Artist;One", "Artist\\Two", "Artist,Three"]),
78
+ (["Band/Name", "Artist;Separated", "Composer,List"]),
79
+ (["Artist//With\\Slashes", "Another;With\\Semicolon"]),
80
+ ],
81
+ )
82
+ def test_real_world_artist_names(self, values):
83
+ separator = MetadataManager.find_safe_separator(values)
84
+ for value in values:
85
+ assert separator not in value
86
+
87
+ def test_values_with_multiple_separators(self):
88
+ values = ["Artist//One/Two", "Artist\\\\Three\\Four", "Artist;Five"]
89
+ separator = MetadataManager.find_safe_separator(values)
90
+ # Should find a separator that's safe (doesn't appear in values)
91
+ # Since all common separators appear, it will pick the last available one
92
+ for value in values:
93
+ assert separator not in value
94
+
95
+ def test_empty_list(self):
96
+ from audiometa.manager._MetadataManager import METADATA_MULTI_VALUE_SEPARATORS_PRIORITIZED
97
+
98
+ separator = MetadataManager.find_safe_separator([])
99
+ assert separator in METADATA_MULTI_VALUE_SEPARATORS_PRIORITIZED
100
+
101
+ def test_single_value(self):
102
+ separator = MetadataManager.find_safe_separator(["Artist One"])
103
+ assert separator == "//"
104
+
105
+ @pytest.mark.parametrize(
106
+ ("values", "expected_separator"),
107
+ [
108
+ (["DJ Snake", "The Chainsmokers"], "//"),
109
+ (["Miley Cyrus & Billy Ray Cyrus", "Florida Georgia Line"], "//"),
110
+ (["Hank Williams, Jr", "Hank Williams"], "//"),
111
+ # // and / appear, so skips to \\\\
112
+ (["7//Heaven", "Back/Street"], "\\\\"),
113
+ # ; and \ appear, so skips to //
114
+ (["Artist;With\\Backslash", "Another\\Artist"], "//"),
115
+ ],
116
+ )
117
+ def test_complex_artist_names_from_spec_examples(self, values, expected_separator):
118
+ separator = MetadataManager.find_safe_separator(values)
119
+ assert separator == expected_separator
120
+ for value in values:
121
+ assert separator not in value
122
+
123
+ def test_unicode_and_special_characters(self):
124
+ values = ["Artíst", "Artišt", "Arti§t"]
125
+ separator = MetadataManager.find_safe_separator(values)
126
+ assert separator == "//"
127
+
128
+ def test_spaces_and_punctuation(self):
129
+ values = ["Artist One (Feat. Collaborator)", "Artist Two & Friend"]
130
+ separator = MetadataManager.find_safe_separator(values)
131
+ assert separator == "//"
132
+
133
+ def test_find_safe_separator_empty_and_whitespace_values(self):
134
+ """Test behavior with empty strings and whitespace."""
135
+ values = ["", " ", "Artist One", "Artist Two"]
136
+ separator = MetadataManager.find_safe_separator(values)
137
+ assert separator == "//"
138
+
139
+ # Empty and whitespace shouldn't affect separator choice
140
+ for value in values:
141
+ if value.strip(): # Only check non-empty values
142
+ assert separator not in value
@@ -0,0 +1,76 @@
1
+ import pytest
2
+
3
+ from audiometa.manager._MetadataManager import _MetadataManager as MetadataManager
4
+
5
+
6
+ @pytest.mark.unit
7
+ class TestValueFiltering:
8
+ @pytest.mark.parametrize(
9
+ ("values", "expected_filtered"),
10
+ [
11
+ (["Artist One", "Artist Two"], ["Artist One", "Artist Two"]),
12
+ (["Artist One", ""], ["Artist One"]),
13
+ (["Artist One", " "], ["Artist One", " "]),
14
+ (["", "Artist Two"], ["Artist Two"]),
15
+ ([None, "Artist Two"], ["Artist Two"]),
16
+ (["Artist One", None, "Artist Two"], ["Artist One", "Artist Two"]),
17
+ ([], []),
18
+ ([""], []),
19
+ ([" "], [" "]),
20
+ ([None], []),
21
+ (["", None, " "], [" "]),
22
+ ],
23
+ )
24
+ def test_filter_valid_values(self, values, expected_filtered):
25
+ result = MetadataManager._filter_valid_values(values)
26
+ assert result == expected_filtered
27
+
28
+ def test_filter_valid_values_all_valid(self):
29
+ values = ["Artist One", "Artist Two", "Artist Three"]
30
+ result = MetadataManager._filter_valid_values(values)
31
+ assert result == values
32
+
33
+ def test_filter_valid_values_all_invalid(self):
34
+ values = [None, ""]
35
+ result = MetadataManager._filter_valid_values(values)
36
+ assert result == []
37
+
38
+ def test_mixed_empty_and_valid_entries_filtering(self):
39
+ values = ["Valid Artist 1", "", "Valid Artist 2", "", "Valid Artist 3"]
40
+ result = MetadataManager._filter_valid_values(values)
41
+ assert result == ["Valid Artist 1", "Valid Artist 2", "Valid Artist 3"]
42
+
43
+ def test_filter_valid_values_removes_empty_strings(self):
44
+ values = ["Valid Artist", "", "Another Valid Artist"]
45
+ result = MetadataManager._filter_valid_values(values)
46
+ assert result == ["Valid Artist", "Another Valid Artist"]
47
+
48
+ def test_filter_valid_values_only_whitespace_not_empty(self):
49
+ # filter_valid_values only removes None and empty strings, not whitespace
50
+ values = [" ", "\t", "\n"]
51
+ result = MetadataManager._filter_valid_values(values)
52
+ assert result == values # Whitespace strings are not empty
53
+
54
+ def test_single_empty_value(self):
55
+ values = [""]
56
+ result = MetadataManager._filter_valid_values(values)
57
+ assert result == []
58
+
59
+ def test_none_and_empty_mixed(self):
60
+ values = [None, "Artist", "", "Another Artist", None]
61
+ result = MetadataManager._filter_valid_values(values)
62
+ assert result == ["Artist", "Another Artist"]
63
+
64
+ def test_all_none_values_filtered_to_empty_list(self):
65
+ # Test that [None, None] filters to empty list
66
+ # This matches the integration test scenario where field is removed
67
+ values = [None, None]
68
+ result = MetadataManager._filter_valid_values(values)
69
+ assert result == []
70
+
71
+ def test_mixed_none_and_valid_values_filtering(self):
72
+ # Test that None values are filtered but valid values remain
73
+ # This matches the integration test scenario
74
+ values = ["Artist One", None, "Artist Two", None, "Artist Three"]
75
+ result = MetadataManager._filter_valid_values(values)
76
+ assert result == ["Artist One", "Artist Two", "Artist Three"]
@@ -0,0 +1,152 @@
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
+ from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
9
+
10
+
11
+ @pytest.mark.unit
12
+ class TestNormalization:
13
+ @pytest.mark.parametrize(
14
+ ("metadata_rating_read", "expected_normalized_value"),
15
+ [
16
+ (0, 0),
17
+ (13, 1),
18
+ (1, 2),
19
+ (54, 3),
20
+ (64, 4),
21
+ (118, 5),
22
+ (128, 6),
23
+ (186, 7),
24
+ (196, 8),
25
+ (242, 9),
26
+ (255, 10),
27
+ ],
28
+ )
29
+ def test_base_255_non_proportional(self, metadata_rating_read, expected_normalized_value):
30
+ id3v2_audio_file = MagicMock()
31
+ id3v2_manager = Id3v2Manager(audio_file=id3v2_audio_file, normalized_rating_max_value=10)
32
+ id3v2_normalized_rating = id3v2_manager._get_undirectly_mapped_metadata_value_from_raw_clean_metadata(
33
+ unified_metadata_key=UnifiedMetadataKey.RATING,
34
+ raw_clean_metadata_uppercase_keys={
35
+ Id3v2Manager.Id3TextFrame.RATING: [id3v2_manager.ID3_RATING_APP_EMAIL, metadata_rating_read]
36
+ },
37
+ )
38
+ assert id3v2_normalized_rating == expected_normalized_value
39
+
40
+ wave_audio_file = MagicMock()
41
+ wave_audio_file.file_extension = ".wav"
42
+ riff_manager = RiffManager(audio_file=wave_audio_file, normalized_rating_max_value=10)
43
+ riff_normalized_rating = riff_manager._get_undirectly_mapped_metadata_value_from_raw_clean_metadata(
44
+ unified_metadata_key=UnifiedMetadataKey.RATING,
45
+ raw_clean_metadata_uppercase_keys={RiffManager.RiffTagKey.RATING: [str(metadata_rating_read)]},
46
+ )
47
+ assert riff_normalized_rating == expected_normalized_value
48
+
49
+ flac_audio_file = MagicMock()
50
+ flac_audio_file.file_extension = ".flac"
51
+ vorbis_manager = VorbisManager(audio_file=flac_audio_file, normalized_rating_max_value=10)
52
+ vorbis_normalized_rating = vorbis_manager._get_undirectly_mapped_metadata_value_from_raw_clean_metadata(
53
+ unified_metadata_key=UnifiedMetadataKey.RATING,
54
+ raw_clean_metadata_uppercase_keys={VorbisManager.VorbisKey.RATING: [metadata_rating_read]},
55
+ )
56
+ assert vorbis_normalized_rating == expected_normalized_value
57
+
58
+ @pytest.mark.parametrize(
59
+ ("metadata_rating_read", "expected_normalized_value"),
60
+ [
61
+ (0, 0),
62
+ (10, 1),
63
+ (20, 2),
64
+ (30, 3),
65
+ (40, 4),
66
+ (50, 5),
67
+ (60, 6),
68
+ (70, 7),
69
+ (80, 8),
70
+ (90, 9),
71
+ (100, 10),
72
+ ],
73
+ )
74
+ def test_base_100_proportional(self, metadata_rating_read, expected_normalized_value):
75
+ # ID3v2
76
+ id3v2_manager = Id3v2Manager(audio_file=MagicMock(), normalized_rating_max_value=10)
77
+ id3v2_normalized_rating = id3v2_manager._get_undirectly_mapped_metadata_value_from_raw_clean_metadata(
78
+ unified_metadata_key=UnifiedMetadataKey.RATING,
79
+ raw_clean_metadata_uppercase_keys={
80
+ Id3v2Manager.Id3TextFrame.RATING: [id3v2_manager.ID3_RATING_APP_EMAIL, metadata_rating_read]
81
+ },
82
+ )
83
+ assert id3v2_normalized_rating == expected_normalized_value
84
+
85
+ # RIFF / WAV
86
+ wave_audio_file = MagicMock()
87
+ wave_audio_file.file_extension = ".wav"
88
+ riff_manager = RiffManager(audio_file=wave_audio_file, normalized_rating_max_value=10)
89
+ riff_normalized_rating = riff_manager._get_undirectly_mapped_metadata_value_from_raw_clean_metadata(
90
+ unified_metadata_key=UnifiedMetadataKey.RATING,
91
+ raw_clean_metadata_uppercase_keys={RiffManager.RiffTagKey.RATING: [str(metadata_rating_read)]},
92
+ )
93
+ assert riff_normalized_rating == expected_normalized_value
94
+
95
+ # VORBIS / FLAC
96
+ flac_audio_file = MagicMock()
97
+ flac_audio_file.file_extension = ".flac"
98
+ vorbis_manager = VorbisManager(audio_file=flac_audio_file, normalized_rating_max_value=10)
99
+ vorbis_normalized_rating = vorbis_manager._get_undirectly_mapped_metadata_value_from_raw_clean_metadata(
100
+ unified_metadata_key=UnifiedMetadataKey.RATING,
101
+ raw_clean_metadata_uppercase_keys={VorbisManager.VorbisKey.RATING: [metadata_rating_read]},
102
+ )
103
+ assert vorbis_normalized_rating == expected_normalized_value
104
+
105
+ @pytest.mark.parametrize(
106
+ ("metadata_rating_read", "expected_normalized_value"),
107
+ [
108
+ (51, 1),
109
+ (102, 2),
110
+ (153, 3),
111
+ (204, 4),
112
+ (255, 5),
113
+ ],
114
+ )
115
+ def test_base_255_proportional(self, metadata_rating_read, expected_normalized_value):
116
+ # ID3v2
117
+ id3v2_manager = Id3v2Manager(audio_file=MagicMock(), normalized_rating_max_value=5)
118
+ id3v2_normalized_rating = id3v2_manager._get_undirectly_mapped_metadata_value_from_raw_clean_metadata(
119
+ unified_metadata_key=UnifiedMetadataKey.RATING,
120
+ raw_clean_metadata_uppercase_keys={
121
+ Id3v2Manager.Id3TextFrame.RATING: [id3v2_manager.ID3_RATING_APP_EMAIL, metadata_rating_read]
122
+ },
123
+ )
124
+ assert id3v2_normalized_rating == expected_normalized_value
125
+
126
+ # RIFF / WAV
127
+ wave_audio_file = MagicMock()
128
+ wave_audio_file.file_extension = ".wav"
129
+ riff_manager = RiffManager(audio_file=wave_audio_file, normalized_rating_max_value=5)
130
+ riff_normalized_rating = riff_manager._get_undirectly_mapped_metadata_value_from_raw_clean_metadata(
131
+ unified_metadata_key=UnifiedMetadataKey.RATING,
132
+ raw_clean_metadata_uppercase_keys={RiffManager.RiffTagKey.RATING: [str(metadata_rating_read)]},
133
+ )
134
+ assert riff_normalized_rating == expected_normalized_value
135
+
136
+ # VORBIS / FLAC
137
+ flac_audio_file = MagicMock()
138
+ flac_audio_file.file_extension = ".flac"
139
+ vorbis_manager = VorbisManager(audio_file=flac_audio_file, normalized_rating_max_value=5)
140
+ vorbis_normalized_rating = vorbis_manager._get_undirectly_mapped_metadata_value_from_raw_clean_metadata(
141
+ unified_metadata_key=UnifiedMetadataKey.RATING,
142
+ raw_clean_metadata_uppercase_keys={VorbisManager.VorbisKey.RATING: [metadata_rating_read]},
143
+ )
144
+ assert vorbis_normalized_rating == expected_normalized_value
145
+
146
+ def test_invalid_value(self):
147
+ manager = Id3v2Manager(audio_file=MagicMock(), normalized_rating_max_value=10)
148
+ normalized_rating = manager._get_undirectly_mapped_metadata_value_from_raw_clean_metadata(
149
+ unified_metadata_key=UnifiedMetadataKey.RATING,
150
+ raw_clean_metadata_uppercase_keys={Id3v2Manager.Id3TextFrame.RATING: [manager.ID3_RATING_APP_EMAIL, 300]},
151
+ )
152
+ assert normalized_rating is None
@@ -0,0 +1,23 @@
1
+ import pytest
2
+
3
+ from audiometa.utils.rating_profiles import RatingReadProfile
4
+
5
+
6
+ @pytest.mark.unit
7
+ class TestRatingProfileValues:
8
+ @pytest.mark.parametrize(
9
+ ("profile_enum", "expected_values"),
10
+ [
11
+ (RatingReadProfile.BASE_255_NON_PROPORTIONAL, (0, 13, 1, 54, 64, 118, 128, 186, 196, 242, 255)),
12
+ (RatingReadProfile.BASE_100_PROPORTIONAL, (0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100)),
13
+ (
14
+ RatingReadProfile.BASE_255_PROPORTIONAL_TRAKTOR,
15
+ (None, None, 51, None, 102, None, 153, None, 204, None, 255),
16
+ ),
17
+ ],
18
+ )
19
+ def test_profile_values(self, profile_enum, expected_values):
20
+ profile = profile_enum.value
21
+
22
+ assert profile == expected_values
23
+ assert len(profile) == 11 # 0-5 stars (0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5)
@@ -0,0 +1,77 @@
1
+ import pytest
2
+
3
+ from audiometa import validate_metadata_for_update
4
+ from audiometa.exceptions import InvalidMetadataFieldTypeError, InvalidRatingValueError
5
+ from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
6
+
7
+
8
+ @pytest.mark.unit
9
+ class TestRatingValidation:
10
+ """Test rating field validation in validate_metadata_for_update."""
11
+
12
+ @pytest.mark.parametrize(
13
+ "rating",
14
+ [0, 128, 255],
15
+ )
16
+ def test_valid_rating_raw_mode(self, rating):
17
+ validate_metadata_for_update({UnifiedMetadataKey.RATING: rating})
18
+
19
+ @pytest.mark.parametrize(
20
+ "rating",
21
+ [1.5, 75.7],
22
+ )
23
+ def test_invalid_rating_float_in_raw_mode_raises_error(self, rating):
24
+ with pytest.raises(InvalidRatingValueError) as exc_info:
25
+ validate_metadata_for_update({UnifiedMetadataKey.RATING: rating})
26
+ assert "In raw mode, float values must be whole numbers" in str(exc_info.value)
27
+
28
+ @pytest.mark.parametrize(
29
+ "rating",
30
+ [0.0, 196.0, 128.0],
31
+ )
32
+ def test_valid_rating_whole_number_float_in_raw_mode(self, rating):
33
+ # Whole-number floats like 0.0, 196.0 are accepted and converted to int
34
+ validate_metadata_for_update({UnifiedMetadataKey.RATING: rating})
35
+
36
+ @pytest.mark.parametrize(
37
+ "rating",
38
+ [50, 0, 100, 1.5, 7.5, 50.0],
39
+ )
40
+ def test_valid_rating_normalized_mode(self, rating):
41
+ validate_metadata_for_update({UnifiedMetadataKey.RATING: rating}, normalized_rating_max_value=100)
42
+
43
+ @pytest.mark.parametrize(
44
+ "rating",
45
+ [-1, -100, -0.5, -1.5],
46
+ )
47
+ def test_invalid_rating_negative_raises_error(self, rating):
48
+ with pytest.raises(InvalidRatingValueError):
49
+ validate_metadata_for_update({UnifiedMetadataKey.RATING: rating})
50
+
51
+ @pytest.mark.parametrize(
52
+ "rating",
53
+ [101, 100.1, 101.5],
54
+ )
55
+ def test_invalid_rating_over_max_raises_error(self, rating):
56
+ with pytest.raises(InvalidRatingValueError):
57
+ validate_metadata_for_update({UnifiedMetadataKey.RATING: rating}, normalized_rating_max_value=100)
58
+
59
+ @pytest.mark.parametrize(
60
+ "rating",
61
+ [110, 110.5],
62
+ )
63
+ def test_invalid_rating_profile_value_raises_error(self, rating):
64
+ # Values > max that would map to invalid star rating indices (> 10)
65
+ with pytest.raises(InvalidRatingValueError):
66
+ validate_metadata_for_update({UnifiedMetadataKey.RATING: rating}, normalized_rating_max_value=100)
67
+
68
+ @pytest.mark.parametrize(
69
+ "invalid_value",
70
+ ["invalid", {}],
71
+ )
72
+ def test_invalid_rating_type_raises_error(self, invalid_value):
73
+ with pytest.raises(InvalidMetadataFieldTypeError):
74
+ validate_metadata_for_update({UnifiedMetadataKey.RATING: invalid_value})
75
+
76
+ def test_rating_none_is_allowed(self):
77
+ validate_metadata_for_update({UnifiedMetadataKey.RATING: None})
@@ -0,0 +1,43 @@
1
+ from unittest.mock import MagicMock
2
+
3
+ import pytest
4
+
5
+ from audiometa.exceptions import ConfigurationError
6
+ from audiometa.manager._rating_supporting.id3v2._Id3v2Manager import _Id3v2Manager as Id3v2Manager
7
+ from audiometa.manager._rating_supporting.riff._RiffManager import _RiffManager as RiffManager
8
+ from audiometa.manager._rating_supporting.vorbis._VorbisManager import _VorbisManager as VorbisManager
9
+
10
+
11
+ @pytest.mark.unit
12
+ class TestConfigurationError:
13
+ def test_convert_normalized_rating_raises_config_error_when_max_value_none(
14
+ self,
15
+ ):
16
+ id3v2_audio_file = MagicMock()
17
+ id3v2_manager = Id3v2Manager(audio_file=id3v2_audio_file, normalized_rating_max_value=None)
18
+
19
+ with pytest.raises(ConfigurationError) as exc_info:
20
+ id3v2_manager._convert_normalized_rating_to_file_rating(normalized_rating=5.0)
21
+ assert "normalized_rating_max_value must be set" in str(exc_info.value)
22
+
23
+ def test_convert_normalized_rating_to_file_rating_raises_configuration_error_for_riff_manager(
24
+ self,
25
+ ):
26
+ wave_audio_file = MagicMock()
27
+ wave_audio_file.file_extension = ".wav"
28
+ riff_manager = RiffManager(audio_file=wave_audio_file, normalized_rating_max_value=None)
29
+
30
+ with pytest.raises(ConfigurationError) as exc_info:
31
+ riff_manager._convert_normalized_rating_to_file_rating(normalized_rating=5.0)
32
+ assert "normalized_rating_max_value must be set" in str(exc_info.value)
33
+
34
+ def test_convert_normalized_rating_to_file_rating_raises_configuration_error_for_vorbis_manager(
35
+ self,
36
+ ):
37
+ flac_audio_file = MagicMock()
38
+ flac_audio_file.file_extension = ".flac"
39
+ vorbis_manager = VorbisManager(audio_file=flac_audio_file, normalized_rating_max_value=None)
40
+
41
+ with pytest.raises(ConfigurationError) as exc_info:
42
+ vorbis_manager._convert_normalized_rating_to_file_rating(normalized_rating=5.0)
43
+ assert "normalized_rating_max_value must be set" in str(exc_info.value)