audiometa-python 0.2.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (318) hide show
  1. audiometa/__init__.py +1240 -0
  2. audiometa/__main__.py +6 -0
  3. audiometa/_audio_file.py +602 -0
  4. audiometa/cli.py +347 -0
  5. audiometa/exceptions.py +167 -0
  6. audiometa/manager/_MetadataManager.py +665 -0
  7. audiometa/manager/__init__.py +1 -0
  8. audiometa/manager/_rating_supporting/_RatingSupportingMetadataManager.py +250 -0
  9. audiometa/manager/_rating_supporting/__init__.py +1 -0
  10. audiometa/manager/_rating_supporting/id3v2/_Id3v2Manager.py +945 -0
  11. audiometa/manager/_rating_supporting/id3v2/__init__.py +25 -0
  12. audiometa/manager/_rating_supporting/id3v2/_id3v2_constants.py +11 -0
  13. audiometa/manager/_rating_supporting/riff/_RiffManager.py +778 -0
  14. audiometa/manager/_rating_supporting/riff/__init__.py +25 -0
  15. audiometa/manager/_rating_supporting/riff/_riff_constants.py +10 -0
  16. audiometa/manager/_rating_supporting/vorbis/_VorbisManager.py +525 -0
  17. audiometa/manager/_rating_supporting/vorbis/__init__.py +17 -0
  18. audiometa/manager/_rating_supporting/vorbis/_vorbis_constants.py +6 -0
  19. audiometa/manager/id3v1/_Id3v1Manager.py +512 -0
  20. audiometa/manager/id3v1/__init__.py +1 -0
  21. audiometa/manager/id3v1/_constants.py +8 -0
  22. audiometa/manager/id3v1/id3v1_raw_metadata.py +242 -0
  23. audiometa/manager/id3v1/id3v1_raw_metadata_key.py +13 -0
  24. audiometa/test/__init__.py +1 -0
  25. audiometa/test/assets/create_test_files.py +72 -0
  26. audiometa/test/helpers/__init__.py +51 -0
  27. audiometa/test/helpers/common/__init__.py +6 -0
  28. audiometa/test/helpers/common/audio_file_creator.py +68 -0
  29. audiometa/test/helpers/common/external_tool_runner.py +74 -0
  30. audiometa/test/helpers/id3v1/__init__.py +8 -0
  31. audiometa/test/helpers/id3v1/id3v1_header_verifier.py +18 -0
  32. audiometa/test/helpers/id3v1/id3v1_metadata_deleter.py +37 -0
  33. audiometa/test/helpers/id3v1/id3v1_metadata_getter.py +61 -0
  34. audiometa/test/helpers/id3v1/id3v1_metadata_setter.py +82 -0
  35. audiometa/test/helpers/id3v2/__init__.py +28 -0
  36. audiometa/test/helpers/id3v2/id3v2_frame_manual_creator.py +305 -0
  37. audiometa/test/helpers/id3v2/id3v2_header_verifier.py +38 -0
  38. audiometa/test/helpers/id3v2/id3v2_metadata_deleter.py +56 -0
  39. audiometa/test/helpers/id3v2/id3v2_metadata_getter.py +188 -0
  40. audiometa/test/helpers/id3v2/id3v2_metadata_setter.py +428 -0
  41. audiometa/test/helpers/riff/__init__.py +8 -0
  42. audiometa/test/helpers/riff/riff_header_verifier.py +85 -0
  43. audiometa/test/helpers/riff/riff_manual_metadata_creator.py +216 -0
  44. audiometa/test/helpers/riff/riff_metadata_deleter.py +56 -0
  45. audiometa/test/helpers/riff/riff_metadata_getter.py +118 -0
  46. audiometa/test/helpers/riff/riff_metadata_setter.py +196 -0
  47. audiometa/test/helpers/scripts/__init__.py +0 -0
  48. audiometa/test/helpers/technical_info_inspector.py +115 -0
  49. audiometa/test/helpers/temp_file_with_metadata.py +82 -0
  50. audiometa/test/helpers/vorbis/__init__.py +8 -0
  51. audiometa/test/helpers/vorbis/vorbis_header_verifier.py +31 -0
  52. audiometa/test/helpers/vorbis/vorbis_metadata_deleter.py +49 -0
  53. audiometa/test/helpers/vorbis/vorbis_metadata_getter.py +67 -0
  54. audiometa/test/helpers/vorbis/vorbis_metadata_setter.py +200 -0
  55. audiometa/test/tests/__init__.py +0 -0
  56. audiometa/test/tests/conftest.py +261 -0
  57. audiometa/test/tests/e2e/__init__.py +0 -0
  58. audiometa/test/tests/e2e/cli/__init__.py +0 -0
  59. audiometa/test/tests/e2e/cli/error_handling/__init__.py +1 -0
  60. audiometa/test/tests/e2e/cli/error_handling/test_command_structure_errors.py +77 -0
  61. audiometa/test/tests/e2e/cli/error_handling/test_file_access_errors.py +130 -0
  62. audiometa/test/tests/e2e/cli/error_handling/test_format_output_errors.py +118 -0
  63. audiometa/test/tests/e2e/cli/error_handling/test_input_validation_errors.py +172 -0
  64. audiometa/test/tests/e2e/cli/error_handling/test_missing_fields_validation.py +49 -0
  65. audiometa/test/tests/e2e/cli/error_handling/test_multiple_files_errors.py +160 -0
  66. audiometa/test/tests/e2e/cli/error_handling/test_rating_validation.py +90 -0
  67. audiometa/test/tests/e2e/cli/error_handling/test_year_validation.py +51 -0
  68. audiometa/test/tests/e2e/cli/test_cli_delete.py +20 -0
  69. audiometa/test/tests/e2e/cli/test_cli_formatting.py +31 -0
  70. audiometa/test/tests/e2e/cli/test_cli_help.py +40 -0
  71. audiometa/test/tests/e2e/cli/test_cli_read.py +79 -0
  72. audiometa/test/tests/e2e/cli/test_cli_unified.py +33 -0
  73. audiometa/test/tests/e2e/cli/test_cli_write.py +116 -0
  74. audiometa/test/tests/e2e/scenarios/__init__.py +0 -0
  75. audiometa/test/tests/e2e/scenarios/test_user_scenarios.py +166 -0
  76. audiometa/test/tests/e2e/workflows/__init__.py +0 -0
  77. audiometa/test/tests/e2e/workflows/test_core_workflows.py +166 -0
  78. audiometa/test/tests/e2e/workflows/test_deletion_workflows.py +318 -0
  79. audiometa/test/tests/e2e/workflows/test_error_handling_workflows.py +165 -0
  80. audiometa/test/tests/e2e/workflows/test_format_specific_workflows.py +129 -0
  81. audiometa/test/tests/e2e/workflows/test_rating_workflows.py +124 -0
  82. audiometa/test/tests/integration/__init__.py +0 -0
  83. audiometa/test/tests/integration/audio_format/__init__.py +0 -0
  84. audiometa/test/tests/integration/audio_format/flac/__init__.py +0 -0
  85. audiometa/test/tests/integration/audio_format/flac/test_flac_delete_all.py +108 -0
  86. audiometa/test/tests/integration/audio_format/flac/test_flac_reading_all.py +61 -0
  87. audiometa/test/tests/integration/audio_format/flac/test_flac_reading_field.py +65 -0
  88. audiometa/test/tests/integration/audio_format/flac/test_flac_writing.py +69 -0
  89. audiometa/test/tests/integration/audio_format/mp3/__init__.py +0 -0
  90. audiometa/test/tests/integration/audio_format/mp3/test_mp3_delete_all.py +79 -0
  91. audiometa/test/tests/integration/audio_format/mp3/test_mp3_reading_all.py +61 -0
  92. audiometa/test/tests/integration/audio_format/mp3/test_mp3_reading_field.py +67 -0
  93. audiometa/test/tests/integration/audio_format/mp3/test_mp3_writing.py +60 -0
  94. audiometa/test/tests/integration/audio_format/wav/__init__.py +0 -0
  95. audiometa/test/tests/integration/audio_format/wav/test_wav_delete_all.py +87 -0
  96. audiometa/test/tests/integration/audio_format/wav/test_wav_reading_all.py +62 -0
  97. audiometa/test/tests/integration/audio_format/wav/test_wav_reading_field.py +57 -0
  98. audiometa/test/tests/integration/audio_format/wav/test_wav_with_id3v2_tags.py +83 -0
  99. audiometa/test/tests/integration/audio_format/wav/test_wav_writing.py +62 -0
  100. audiometa/test/tests/integration/conftest.py +29 -0
  101. audiometa/test/tests/integration/delete_all_metadata/__init__.py +1 -0
  102. audiometa/test/tests/integration/delete_all_metadata/test_audio_format_all.py +102 -0
  103. audiometa/test/tests/integration/delete_all_metadata/test_audio_format_header_deletion.py +77 -0
  104. audiometa/test/tests/integration/delete_all_metadata/test_basic_functionality.py +47 -0
  105. audiometa/test/tests/integration/delete_all_metadata/test_error_handling.py +24 -0
  106. audiometa/test/tests/integration/encoding/__init__.py +1 -0
  107. audiometa/test/tests/integration/encoding/test_encoding.py +88 -0
  108. audiometa/test/tests/integration/encoding/test_special_characters_edge_cases.py +223 -0
  109. audiometa/test/tests/integration/get_full_metadata/__init__.py +0 -0
  110. audiometa/test/tests/integration/get_full_metadata/test_binary_data_filtering.py +250 -0
  111. audiometa/test/tests/integration/get_full_metadata/test_get_full_metadata.py +297 -0
  112. audiometa/test/tests/integration/get_full_metadata/test_get_full_metadata_edge_cases.py +231 -0
  113. audiometa/test/tests/integration/get_full_metadata/test_options.py +207 -0
  114. audiometa/test/tests/integration/get_full_metadata/test_parsed_fields_keys.py +85 -0
  115. audiometa/test/tests/integration/metadata_field/__init__.py +0 -0
  116. audiometa/test/tests/integration/metadata_field/album/__init__.py +0 -0
  117. audiometa/test/tests/integration/metadata_field/album/test_deleting.py +73 -0
  118. audiometa/test/tests/integration/metadata_field/album/test_reading.py +36 -0
  119. audiometa/test/tests/integration/metadata_field/album/test_writing.py +50 -0
  120. audiometa/test/tests/integration/metadata_field/album_artists/__init__.py +0 -0
  121. audiometa/test/tests/integration/metadata_field/album_artists/test_deleting.py +83 -0
  122. audiometa/test/tests/integration/metadata_field/album_artists/test_reading.py +38 -0
  123. audiometa/test/tests/integration/metadata_field/album_artists/test_writing.py +52 -0
  124. audiometa/test/tests/integration/metadata_field/artists/__init__.py +0 -0
  125. audiometa/test/tests/integration/metadata_field/artists/test_deleting.py +68 -0
  126. audiometa/test/tests/integration/metadata_field/artists/test_reading.py +36 -0
  127. audiometa/test/tests/integration/metadata_field/artists/test_writing.py +46 -0
  128. audiometa/test/tests/integration/metadata_field/bpm/__init__.py +0 -0
  129. audiometa/test/tests/integration/metadata_field/bpm/test_deleting.py +75 -0
  130. audiometa/test/tests/integration/metadata_field/bpm/test_reading.py +32 -0
  131. audiometa/test/tests/integration/metadata_field/bpm/test_writing.py +56 -0
  132. audiometa/test/tests/integration/metadata_field/comment/__init__.py +0 -0
  133. audiometa/test/tests/integration/metadata_field/comment/test_deleting.py +68 -0
  134. audiometa/test/tests/integration/metadata_field/comment/test_reading.py +36 -0
  135. audiometa/test/tests/integration/metadata_field/comment/test_writing.py +49 -0
  136. audiometa/test/tests/integration/metadata_field/composer/__init__.py +0 -0
  137. audiometa/test/tests/integration/metadata_field/composer/test_deleting.py +75 -0
  138. audiometa/test/tests/integration/metadata_field/composer/test_reading.py +34 -0
  139. audiometa/test/tests/integration/metadata_field/composer/test_writing.py +41 -0
  140. audiometa/test/tests/integration/metadata_field/copyright/__init__.py +0 -0
  141. audiometa/test/tests/integration/metadata_field/copyright/test_deleting.py +81 -0
  142. audiometa/test/tests/integration/metadata_field/copyright/test_reading.py +35 -0
  143. audiometa/test/tests/integration/metadata_field/copyright/test_writing.py +41 -0
  144. audiometa/test/tests/integration/metadata_field/field_not_supported/__init__.py +0 -0
  145. audiometa/test/tests/integration/metadata_field/field_not_supported/test_deleting.py +56 -0
  146. audiometa/test/tests/integration/metadata_field/field_not_supported/test_reading.py +54 -0
  147. audiometa/test/tests/integration/metadata_field/field_not_supported/test_writing.py +61 -0
  148. audiometa/test/tests/integration/metadata_field/genre/__init__.py +0 -0
  149. audiometa/test/tests/integration/metadata_field/genre/reading/__init__.py +0 -0
  150. audiometa/test/tests/integration/metadata_field/genre/reading/metadata_format/__init__.py +0 -0
  151. audiometa/test/tests/integration/metadata_field/genre/reading/metadata_format/test_id3v1_reading.py +65 -0
  152. audiometa/test/tests/integration/metadata_field/genre/reading/metadata_format/test_id3v2_reading.py +25 -0
  153. audiometa/test/tests/integration/metadata_field/genre/reading/metadata_format/test_riff_reading.py +58 -0
  154. audiometa/test/tests/integration/metadata_field/genre/reading/metadata_format/test_vorbis_reading.py +61 -0
  155. audiometa/test/tests/integration/metadata_field/genre/reading/test_smart_reading.py +191 -0
  156. audiometa/test/tests/integration/metadata_field/genre/test_deleting.py +62 -0
  157. audiometa/test/tests/integration/metadata_field/genre/test_writing.py +64 -0
  158. audiometa/test/tests/integration/metadata_field/language/__init__.py +0 -0
  159. audiometa/test/tests/integration/metadata_field/language/test_deleting.py +75 -0
  160. audiometa/test/tests/integration/metadata_field/language/test_reading.py +39 -0
  161. audiometa/test/tests/integration/metadata_field/language/test_writing.py +43 -0
  162. audiometa/test/tests/integration/metadata_field/lyrics/__init__.py +0 -0
  163. audiometa/test/tests/integration/metadata_field/lyrics/test_deleting.py +129 -0
  164. audiometa/test/tests/integration/metadata_field/lyrics/test_reading.py +57 -0
  165. audiometa/test/tests/integration/metadata_field/lyrics/test_writing.py +59 -0
  166. audiometa/test/tests/integration/metadata_field/publisher/__init__.py +0 -0
  167. audiometa/test/tests/integration/metadata_field/publisher/test_deleting.py +88 -0
  168. audiometa/test/tests/integration/metadata_field/publisher/test_reading.py +32 -0
  169. audiometa/test/tests/integration/metadata_field/publisher/test_writing.py +47 -0
  170. audiometa/test/tests/integration/metadata_field/rating/__init__.py +0 -0
  171. audiometa/test/tests/integration/metadata_field/rating/reading/__init__.py +0 -0
  172. audiometa/test/tests/integration/metadata_field/rating/reading/test_base_100_proportional.py +81 -0
  173. audiometa/test/tests/integration/metadata_field/rating/reading/test_base_255_non_proportional.py +33 -0
  174. audiometa/test/tests/integration/metadata_field/rating/reading/test_base_255_proportional.py +58 -0
  175. audiometa/test/tests/integration/metadata_field/rating/test_deleting.py +117 -0
  176. audiometa/test/tests/integration/metadata_field/rating/test_error_handling.py +137 -0
  177. audiometa/test/tests/integration/metadata_field/rating/writing/__init__.py +0 -0
  178. audiometa/test/tests/integration/metadata_field/rating/writing/metadata_format/__init__.py +0 -0
  179. audiometa/test/tests/integration/metadata_field/rating/writing/metadata_format/test_id3v2.py +77 -0
  180. audiometa/test/tests/integration/metadata_field/rating/writing/metadata_format/test_riff.py +55 -0
  181. audiometa/test/tests/integration/metadata_field/rating/writing/metadata_format/test_vorbis.py +57 -0
  182. audiometa/test/tests/integration/metadata_field/rating/writing/test_comprehensive.py +192 -0
  183. audiometa/test/tests/integration/metadata_field/release_date/__init__.py +0 -0
  184. audiometa/test/tests/integration/metadata_field/release_date/test_deleting.py +74 -0
  185. audiometa/test/tests/integration/metadata_field/release_date/test_error_handling.py +82 -0
  186. audiometa/test/tests/integration/metadata_field/release_date/test_reading.py +59 -0
  187. audiometa/test/tests/integration/metadata_field/release_date/test_writing.py +49 -0
  188. audiometa/test/tests/integration/metadata_field/test_metadata_field_validation.py +134 -0
  189. audiometa/test/tests/integration/metadata_field/title/__init__.py +0 -0
  190. audiometa/test/tests/integration/metadata_field/title/test_deleting.py +73 -0
  191. audiometa/test/tests/integration/metadata_field/title/test_error_handling.py +47 -0
  192. audiometa/test/tests/integration/metadata_field/title/test_reading.py +36 -0
  193. audiometa/test/tests/integration/metadata_field/title/test_writing.py +64 -0
  194. audiometa/test/tests/integration/metadata_field/track_number/__init__.py +0 -0
  195. audiometa/test/tests/integration/metadata_field/track_number/reading/__init__.py +0 -0
  196. audiometa/test/tests/integration/metadata_field/track_number/reading/test_edge_cases.py +43 -0
  197. audiometa/test/tests/integration/metadata_field/track_number/reading/test_metadata_format.py +32 -0
  198. audiometa/test/tests/integration/metadata_field/track_number/test_deleting.py +59 -0
  199. audiometa/test/tests/integration/metadata_field/track_number/test_writing.py +73 -0
  200. audiometa/test/tests/integration/multiple_values/__init__.py +1 -0
  201. audiometa/test/tests/integration/multiple_values/reading/__init__.py +1 -0
  202. audiometa/test/tests/integration/multiple_values/reading/metadata_format/__init__.py +1 -0
  203. audiometa/test/tests/integration/multiple_values/reading/metadata_format/test_id3v1.py +23 -0
  204. audiometa/test/tests/integration/multiple_values/reading/metadata_format/test_id3v2_3.py +92 -0
  205. audiometa/test/tests/integration/multiple_values/reading/metadata_format/test_id3v2_4.py +216 -0
  206. audiometa/test/tests/integration/multiple_values/reading/metadata_format/test_riff.py +84 -0
  207. audiometa/test/tests/integration/multiple_values/reading/metadata_format/test_vorbis.py +169 -0
  208. audiometa/test/tests/integration/multiple_values/reading/test_performance_large_data.py +209 -0
  209. audiometa/test/tests/integration/multiple_values/reading/test_smart_parsing_scenarios.py +198 -0
  210. audiometa/test/tests/integration/multiple_values/reading/test_unicode_handling.py +24 -0
  211. audiometa/test/tests/integration/multiple_values/writing/__init__.py +1 -0
  212. audiometa/test/tests/integration/multiple_values/writing/metadata_format/__init__.py +1 -0
  213. audiometa/test/tests/integration/multiple_values/writing/metadata_format/test_id3v1.py +62 -0
  214. audiometa/test/tests/integration/multiple_values/writing/metadata_format/test_id3v2_3.py +36 -0
  215. audiometa/test/tests/integration/multiple_values/writing/metadata_format/test_id3v2_4.py +34 -0
  216. audiometa/test/tests/integration/multiple_values/writing/metadata_format/test_riff.py +32 -0
  217. audiometa/test/tests/integration/multiple_values/writing/metadata_format/test_vorbis.py +54 -0
  218. audiometa/test/tests/integration/multiple_values/writing/test_error_handling.py +42 -0
  219. audiometa/test/tests/integration/multiple_values/writing/test_large_values.py +98 -0
  220. audiometa/test/tests/integration/reading/__init__.py +1 -0
  221. audiometa/test/tests/integration/reading/test_read_multiple_metadata.py +80 -0
  222. audiometa/test/tests/integration/reading/test_reading_error_handling.py +36 -0
  223. audiometa/test/tests/integration/real_audio_files/__init__.py +0 -0
  224. audiometa/test/tests/integration/real_audio_files/test_reading.py +146 -0
  225. audiometa/test/tests/integration/real_audio_files/test_writing.py +198 -0
  226. audiometa/test/tests/integration/technical_info/__init__.py +0 -0
  227. audiometa/test/tests/integration/technical_info/flac_md5/__init__.py +0 -0
  228. audiometa/test/tests/integration/technical_info/flac_md5/conftest.py +103 -0
  229. audiometa/test/tests/integration/technical_info/flac_md5/test_invalid_md5/__init__.py +0 -0
  230. audiometa/test/tests/integration/technical_info/flac_md5/test_invalid_md5/test_audio_data_corruption.py +21 -0
  231. audiometa/test/tests/integration/technical_info/flac_md5/test_invalid_md5/test_flipped_md5.py +29 -0
  232. audiometa/test/tests/integration/technical_info/flac_md5/test_invalid_md5/test_non_flac_error.py +13 -0
  233. audiometa/test/tests/integration/technical_info/flac_md5/test_invalid_md5/test_partial_md5.py +29 -0
  234. audiometa/test/tests/integration/technical_info/flac_md5/test_invalid_md5/test_random_md5.py +29 -0
  235. audiometa/test/tests/integration/technical_info/flac_md5/test_invalid_md5/test_unset_md5.py +56 -0
  236. audiometa/test/tests/integration/technical_info/flac_md5/test_valid_md5.py +21 -0
  237. audiometa/test/tests/integration/technical_info/test_bitrate.py +38 -0
  238. audiometa/test/tests/integration/technical_info/test_channels.py +38 -0
  239. audiometa/test/tests/integration/technical_info/test_duration_in_sec.py +38 -0
  240. audiometa/test/tests/integration/technical_info/test_sample_rate.py +40 -0
  241. audiometa/test/tests/integration/test_audio_file.py +23 -0
  242. audiometa/test/tests/integration/test_audio_format_readable_after_update_all_metadata_formats.py +95 -0
  243. audiometa/test/tests/integration/writing/__init__.py +0 -0
  244. audiometa/test/tests/integration/writing/test_error_handling.py +44 -0
  245. audiometa/test/tests/integration/writing/test_forced_format.py +224 -0
  246. audiometa/test/tests/integration/writing/test_multiple_format_preservation.py +223 -0
  247. audiometa/test/tests/integration/writing/test_partial_update.py +36 -0
  248. audiometa/test/tests/integration/writing/writing_strategies/__init__.py +0 -0
  249. audiometa/test/tests/integration/writing/writing_strategies/test_cleanup_strategy.py +79 -0
  250. audiometa/test/tests/integration/writing/writing_strategies/test_preserve_strategy.py +76 -0
  251. audiometa/test/tests/integration/writing/writing_strategies/test_sync_strategy.py +215 -0
  252. audiometa/test/tests/integration/writing/writing_strategies/unsupported_fields/__init__.py +0 -0
  253. audiometa/test/tests/integration/writing/writing_strategies/unsupported_fields/test_fail_behavior.py +42 -0
  254. audiometa/test/tests/integration/writing/writing_strategies/unsupported_fields/test_no_writing_on_failure.py +93 -0
  255. audiometa/test/tests/integration/writing/writing_strategies/unsupported_fields/test_strategy_specific.py +99 -0
  256. audiometa/test/tests/unit/__init__.py +0 -0
  257. audiometa/test/tests/unit/audio_file/__init__.py +0 -0
  258. audiometa/test/tests/unit/audio_file/technical_info/__init__.py +0 -0
  259. audiometa/test/tests/unit/audio_file/technical_info/test_bitrate.py +26 -0
  260. audiometa/test/tests/unit/audio_file/technical_info/test_channels.py +31 -0
  261. audiometa/test/tests/unit/audio_file/technical_info/test_duration_in_sec.py +38 -0
  262. audiometa/test/tests/unit/audio_file/technical_info/test_error_handling.py +130 -0
  263. audiometa/test/tests/unit/audio_file/technical_info/test_file_size.py +51 -0
  264. audiometa/test/tests/unit/audio_file/technical_info/test_format_name.py +28 -0
  265. audiometa/test/tests/unit/audio_file/technical_info/test_sample_rate.py +31 -0
  266. audiometa/test/tests/unit/audio_file/test_context_manager.py +30 -0
  267. audiometa/test/tests/unit/audio_file/test_file_validation.py +40 -0
  268. audiometa/test/tests/unit/audio_file/test_operations.py +20 -0
  269. audiometa/test/tests/unit/audio_file/test_path_handling.py +23 -0
  270. audiometa/test/tests/unit/cli/__init__.py +0 -0
  271. audiometa/test/tests/unit/cli/test_expand_file_patterns.py +234 -0
  272. audiometa/test/tests/unit/metadata_managers/__init__.py +0 -0
  273. audiometa/test/tests/unit/metadata_managers/conftest.py +142 -0
  274. audiometa/test/tests/unit/metadata_managers/header_info/__init__.py +0 -0
  275. audiometa/test/tests/unit/metadata_managers/header_info/test_id3v1.py +49 -0
  276. audiometa/test/tests/unit/metadata_managers/header_info/test_id3v2.py +66 -0
  277. audiometa/test/tests/unit/metadata_managers/header_info/test_riff.py +57 -0
  278. audiometa/test/tests/unit/metadata_managers/header_info/test_vorbis.py +53 -0
  279. audiometa/test/tests/unit/metadata_managers/metadata_field/__init__.py +0 -0
  280. audiometa/test/tests/unit/metadata_managers/metadata_field/multiple_values/__init__.py +0 -0
  281. audiometa/test/tests/unit/metadata_managers/metadata_field/multiple_values/reading/__init__.py +0 -0
  282. audiometa/test/tests/unit/metadata_managers/metadata_field/multiple_values/reading/test_smart_parsing.py +186 -0
  283. audiometa/test/tests/unit/metadata_managers/metadata_field/multiple_values/writing/__init__.py +0 -0
  284. audiometa/test/tests/unit/metadata_managers/metadata_field/multiple_values/writing/test_separator_selection.py +142 -0
  285. audiometa/test/tests/unit/metadata_managers/metadata_field/multiple_values/writing/test_value_filtering.py +76 -0
  286. audiometa/test/tests/unit/metadata_managers/metadata_field/rating/__init__.py +0 -0
  287. audiometa/test/tests/unit/metadata_managers/metadata_field/rating/reading/__init__.py +0 -0
  288. audiometa/test/tests/unit/metadata_managers/metadata_field/rating/reading/test_normalization.py +152 -0
  289. audiometa/test/tests/unit/metadata_managers/metadata_field/rating/reading/test_profiles_values.py +23 -0
  290. audiometa/test/tests/unit/metadata_managers/metadata_field/rating/test_rating_validation.py +77 -0
  291. audiometa/test/tests/unit/metadata_managers/metadata_field/rating/writing/__init__.py +0 -0
  292. audiometa/test/tests/unit/metadata_managers/metadata_field/rating/writing/test_validation.py +151 -0
  293. audiometa/test/tests/unit/metadata_managers/metadata_field/rating/writing/test_writing_profiles.py +61 -0
  294. audiometa/test/tests/unit/metadata_managers/metadata_field/test_date_format_validation.py +135 -0
  295. audiometa/test/tests/unit/metadata_managers/metadata_field/test_track_number_validation.py +46 -0
  296. audiometa/test/tests/unit/metadata_managers/metadata_field/test_type_validation_exception.py +22 -0
  297. audiometa/test/tests/unit/metadata_managers/metadata_field/test_validation.py +83 -0
  298. audiometa/test/tests/unit/metadata_managers/test_metadata_format_managers_write_and_read.py +74 -0
  299. audiometa/utils/__init__.py +1 -0
  300. audiometa/utils/id3v1_genre_code_map.py +205 -0
  301. audiometa/utils/metadata_format.py +31 -0
  302. audiometa/utils/metadata_writing_strategy.py +16 -0
  303. audiometa/utils/os_dependencies_checker/__init__.py +24 -0
  304. audiometa/utils/os_dependencies_checker/base.py +62 -0
  305. audiometa/utils/os_dependencies_checker/config.py +51 -0
  306. audiometa/utils/os_dependencies_checker/macos.py +236 -0
  307. audiometa/utils/os_dependencies_checker/ubuntu.py +95 -0
  308. audiometa/utils/os_dependencies_checker/windows.py +227 -0
  309. audiometa/utils/rating_profiles.py +110 -0
  310. audiometa/utils/tool_path_resolver.py +135 -0
  311. audiometa/utils/types.py +82 -0
  312. audiometa/utils/unified_metadata_key.py +81 -0
  313. audiometa_python-0.2.2.dist-info/METADATA +1464 -0
  314. audiometa_python-0.2.2.dist-info/RECORD +318 -0
  315. audiometa_python-0.2.2.dist-info/WHEEL +5 -0
  316. audiometa_python-0.2.2.dist-info/entry_points.txt +2 -0
  317. audiometa_python-0.2.2.dist-info/licenses/LICENSE +202 -0
  318. audiometa_python-0.2.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,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,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
@@ -0,0 +1,83 @@
1
+ import pytest
2
+
3
+ from audiometa import validate_metadata_for_update
4
+ from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
5
+
6
+
7
+ @pytest.mark.unit
8
+ class TestValidateMetadata:
9
+ """Test the integrate validate_metadata_for_update function.
10
+
11
+ This class tests the high-level validation function that integrates multiple validation layers. Specific field
12
+ validation (type, format) is tested separately in metadata_field/ test files.
13
+ """
14
+
15
+ def test_empty_dict_raises_error(self):
16
+ with pytest.raises(ValueError, match="no metadata fields specified"):
17
+ validate_metadata_for_update({})
18
+
19
+ def test_none_values_allowed(self):
20
+ validate_metadata_for_update({UnifiedMetadataKey.TITLE: None})
21
+
22
+ validate_metadata_for_update(
23
+ {
24
+ UnifiedMetadataKey.TITLE: None,
25
+ UnifiedMetadataKey.ARTISTS: None,
26
+ UnifiedMetadataKey.ALBUM: None,
27
+ }
28
+ )
29
+
30
+ def test_empty_string_allowed(self):
31
+ validate_metadata_for_update({UnifiedMetadataKey.TITLE: ""})
32
+
33
+ def test_empty_list_allowed(self):
34
+ validate_metadata_for_update({UnifiedMetadataKey.ARTISTS: []})
35
+
36
+ def test_list_with_none_values_allowed(self):
37
+ validate_metadata_for_update({UnifiedMetadataKey.ARTISTS: [None, None]})
38
+ validate_metadata_for_update({UnifiedMetadataKey.ARTISTS: [None]})
39
+ validate_metadata_for_update({UnifiedMetadataKey.GENRES_NAMES: [None, None, None]})
40
+
41
+ def test_empty_string_and_empty_list_allowed(self):
42
+ validate_metadata_for_update({UnifiedMetadataKey.TITLE: "", UnifiedMetadataKey.ARTISTS: []})
43
+
44
+ def test_valid_metadata_passes(self):
45
+ validate_metadata_for_update({UnifiedMetadataKey.TITLE: "Song Title"})
46
+ validate_metadata_for_update({UnifiedMetadataKey.ARTISTS: ["Artist 1", "Artist 2"]})
47
+ validate_metadata_for_update({UnifiedMetadataKey.ALBUM: "Album Name"})
48
+
49
+ def test_multiple_valid_fields_passes(self):
50
+ validate_metadata_for_update(
51
+ {
52
+ UnifiedMetadataKey.TITLE: "Song Title",
53
+ UnifiedMetadataKey.ARTISTS: ["Artist"],
54
+ UnifiedMetadataKey.ALBUM: "Album",
55
+ }
56
+ )
57
+
58
+ def test_mixed_none_and_valid_fields_passes(self):
59
+ validate_metadata_for_update(
60
+ {
61
+ UnifiedMetadataKey.TITLE: None,
62
+ UnifiedMetadataKey.ARTISTS: ["Artist"],
63
+ }
64
+ )
65
+
66
+ def test_combined_validation_rating_and_release_date(self):
67
+ validate_metadata_for_update(
68
+ {
69
+ UnifiedMetadataKey.RATING: 50,
70
+ UnifiedMetadataKey.RELEASE_DATE: "2024-01-01",
71
+ },
72
+ normalized_rating_max_value=100,
73
+ )
74
+
75
+ def test_combined_validation_with_empty_fields(self):
76
+ validate_metadata_for_update(
77
+ {
78
+ UnifiedMetadataKey.TITLE: "",
79
+ UnifiedMetadataKey.ARTISTS: [],
80
+ UnifiedMetadataKey.RATING: 50,
81
+ },
82
+ normalized_rating_max_value=100,
83
+ )
@@ -0,0 +1,74 @@
1
+ import pytest
2
+
3
+ from audiometa._audio_file import _AudioFile
4
+ from audiometa.manager._rating_supporting.id3v2._Id3v2Manager import _Id3v2Manager as Id3v2Manager
5
+ from audiometa.manager._rating_supporting.riff._RiffManager import _RiffManager as RiffManager
6
+ from audiometa.manager._rating_supporting.vorbis._VorbisManager import _VorbisManager as VorbisManager
7
+ from audiometa.manager.id3v1._Id3v1Manager import _Id3v1Manager as Id3v1Manager
8
+ from audiometa.manager.id3v1.id3v1_raw_metadata_key import Id3v1RawMetadataKey
9
+ from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
10
+ from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
11
+
12
+
13
+ @pytest.mark.unit
14
+ class TestMetadataFormatManagersWriteAndRead:
15
+ def test_id3v1_manager_write_and_read(self):
16
+ with temp_file_with_metadata({}, "mp3") as test_file:
17
+ audio_file = _AudioFile(test_file)
18
+ manager = Id3v1Manager(audio_file)
19
+
20
+ manager.update_metadata(
21
+ {UnifiedMetadataKey.TITLE: "Test Title", UnifiedMetadataKey.ARTISTS: ["Test Artist"]}
22
+ )
23
+
24
+ raw_metadata = manager._extract_mutagen_metadata()
25
+
26
+ assert raw_metadata.tags.get(Id3v1RawMetadataKey.TITLE) == ["Test Title"]
27
+ assert raw_metadata.tags.get(Id3v1RawMetadataKey.ARTISTS_NAMES_STR) == ["Test Artist"]
28
+
29
+ def test_id3v2_manager_write_and_read(self):
30
+ with temp_file_with_metadata({}, "mp3") as test_file:
31
+ audio_file = _AudioFile(test_file)
32
+ manager = Id3v2Manager(audio_file)
33
+
34
+ manager.update_metadata(
35
+ {UnifiedMetadataKey.TITLE: "Test Title", UnifiedMetadataKey.ARTISTS: ["Test Artist"]}
36
+ )
37
+
38
+ raw_metadata = manager._extract_mutagen_metadata()
39
+
40
+ assert Id3v2Manager.Id3TextFrame.TITLE in raw_metadata
41
+ assert str(raw_metadata[Id3v2Manager.Id3TextFrame.TITLE][0]) == "Test Title"
42
+ assert Id3v2Manager.Id3TextFrame.ARTISTS in raw_metadata
43
+ artists_text = str(raw_metadata[Id3v2Manager.Id3TextFrame.ARTISTS][0])
44
+ assert "Test Artist" in artists_text
45
+
46
+ def test_riff_manager_write_and_read(self):
47
+ with temp_file_with_metadata({}, "wav") as test_file:
48
+ audio_file = _AudioFile(test_file)
49
+ manager = RiffManager(audio_file)
50
+
51
+ manager.update_metadata(
52
+ {UnifiedMetadataKey.TITLE: "Test Title", UnifiedMetadataKey.ARTISTS: ["Test Artist"]}
53
+ )
54
+
55
+ raw_metadata = manager._extract_mutagen_metadata()
56
+
57
+ assert hasattr(raw_metadata, "info")
58
+ info_tags = getattr(raw_metadata, "info", {})
59
+ assert info_tags.get(RiffManager.RiffTagKey.TITLE) == ["Test Title"]
60
+ assert info_tags.get(RiffManager.RiffTagKey.ARTIST) == ["Test Artist"]
61
+
62
+ def test_vorbis_manager_write_and_read(self):
63
+ with temp_file_with_metadata({}, "flac") as test_file:
64
+ audio_file = _AudioFile(test_file)
65
+ manager = VorbisManager(audio_file)
66
+
67
+ manager.update_metadata(
68
+ {UnifiedMetadataKey.TITLE: "Test Title", UnifiedMetadataKey.ARTISTS: ["Test Artist"]}
69
+ )
70
+
71
+ raw_metadata = manager._extract_mutagen_metadata()
72
+
73
+ assert raw_metadata.get(VorbisManager.VorbisKey.TITLE) == ["Test Title"]
74
+ assert raw_metadata.get(VorbisManager.VorbisKey.ARTIST) == ["Test Artist"]
@@ -0,0 +1 @@
1
+ """Utility classes and functions for audio metadata processing."""