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,58 @@
1
+ from pathlib import Path
2
+
3
+ import pytest
4
+
5
+ from audiometa import get_unified_metadata
6
+ from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
7
+
8
+
9
+ @pytest.mark.integration
10
+ class TestBase255Proportional:
11
+ @pytest.mark.parametrize(
12
+ ("star_rating", "expected_normalized_rating"),
13
+ [
14
+ (1, 20),
15
+ (2, 40),
16
+ (3, 60),
17
+ (4, 80),
18
+ (5, 100),
19
+ ],
20
+ )
21
+ def test_vorbis_flac_traktor(self, assets_dir: Path, star_rating, expected_normalized_rating):
22
+ file_path = assets_dir / f"rating_vorbis_traktor={star_rating} star.flac"
23
+ metadata = get_unified_metadata(file_path, normalized_rating_max_value=100)
24
+ rating = metadata.get(UnifiedMetadataKey.RATING)
25
+ assert rating is not None
26
+ assert isinstance(rating, int | float)
27
+ assert rating == expected_normalized_rating
28
+
29
+ def test_none_rating_vorbis_flac_traktor(self, assets_dir: Path):
30
+ file_path = assets_dir / "rating_vorbis_traktor=none.flac"
31
+ metadata = get_unified_metadata(file_path, normalized_rating_max_value=100)
32
+ rating = metadata.get(UnifiedMetadataKey.RATING)
33
+ assert rating is None
34
+
35
+ @pytest.mark.parametrize(
36
+ ("star_rating", "expected_normalized_rating"),
37
+ [
38
+ (1, 20),
39
+ (2, 40),
40
+ (3, 60),
41
+ (4, 80),
42
+ (5, 100),
43
+ ],
44
+ )
45
+ def test_id3v2_mp3_traktor(self, assets_dir: Path, star_rating, expected_normalized_rating):
46
+ file_path = assets_dir / f"rating_id3v2_tracktor={star_rating} star.mp3"
47
+ metadata = get_unified_metadata(file_path, normalized_rating_max_value=100)
48
+ rating = metadata.get(UnifiedMetadataKey.RATING)
49
+ assert rating is not None
50
+ assert isinstance(rating, int | float)
51
+ assert rating == expected_normalized_rating
52
+
53
+ def test_none_rating_id3v2_mp3_traktor(self, assets_dir: Path):
54
+ file_path = assets_dir / "rating_id3v2_tracktor=none.mp3"
55
+ metadata = get_unified_metadata(file_path, normalized_rating_max_value=100)
56
+ rating = metadata.get(UnifiedMetadataKey.RATING)
57
+ # Traktor "none" may actually be 0, not None
58
+ assert rating is None or rating == 0
@@ -0,0 +1,117 @@
1
+ import pytest
2
+
3
+ from audiometa import get_unified_metadata_field, update_metadata
4
+ from audiometa.exceptions import MetadataFieldNotSupportedByMetadataFormatError
5
+ from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
6
+ from audiometa.utils.metadata_format import MetadataFormat
7
+ from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
8
+
9
+
10
+ @pytest.mark.integration
11
+ class TestRatingDeleting:
12
+ def test_delete_rating_id3v2(self):
13
+ with temp_file_with_metadata({}, "mp3") as test_file:
14
+ update_metadata(
15
+ test_file,
16
+ {UnifiedMetadataKey.RATING: 50},
17
+ metadata_format=MetadataFormat.ID3V2,
18
+ normalized_rating_max_value=100,
19
+ )
20
+ assert (
21
+ get_unified_metadata_field(test_file, UnifiedMetadataKey.RATING, normalized_rating_max_value=100) == 50
22
+ )
23
+
24
+ update_metadata(test_file, {UnifiedMetadataKey.RATING: None}, metadata_format=MetadataFormat.ID3V2)
25
+ assert (
26
+ get_unified_metadata_field(test_file, UnifiedMetadataKey.RATING, normalized_rating_max_value=100)
27
+ is None
28
+ )
29
+
30
+ def test_delete_rating_id3v1(self):
31
+ with (
32
+ temp_file_with_metadata({}, "id3v1") as test_file,
33
+ pytest.raises(MetadataFieldNotSupportedByMetadataFormatError),
34
+ ):
35
+ update_metadata(
36
+ test_file,
37
+ {UnifiedMetadataKey.RATING: 50},
38
+ metadata_format=MetadataFormat.ID3V1,
39
+ normalized_rating_max_value=100,
40
+ )
41
+
42
+ def test_delete_rating_riff(self):
43
+ with temp_file_with_metadata({}, "wav") as test_file:
44
+ update_metadata(
45
+ test_file,
46
+ {UnifiedMetadataKey.RATING: 50},
47
+ metadata_format=MetadataFormat.RIFF,
48
+ normalized_rating_max_value=100,
49
+ )
50
+ assert (
51
+ get_unified_metadata_field(test_file, UnifiedMetadataKey.RATING, normalized_rating_max_value=100) == 50
52
+ )
53
+
54
+ update_metadata(test_file, {UnifiedMetadataKey.RATING: None}, metadata_format=MetadataFormat.RIFF)
55
+ assert (
56
+ get_unified_metadata_field(test_file, UnifiedMetadataKey.RATING, normalized_rating_max_value=100)
57
+ is None
58
+ )
59
+
60
+ def test_delete_rating_vorbis(self):
61
+ with temp_file_with_metadata({}, "flac") as test_file:
62
+ update_metadata(
63
+ test_file,
64
+ {UnifiedMetadataKey.RATING: 50},
65
+ metadata_format=MetadataFormat.VORBIS,
66
+ normalized_rating_max_value=100,
67
+ )
68
+ assert (
69
+ get_unified_metadata_field(test_file, UnifiedMetadataKey.RATING, normalized_rating_max_value=100) == 50
70
+ )
71
+
72
+ update_metadata(test_file, {UnifiedMetadataKey.RATING: None}, metadata_format=MetadataFormat.VORBIS)
73
+ assert (
74
+ get_unified_metadata_field(test_file, UnifiedMetadataKey.RATING, normalized_rating_max_value=100)
75
+ is None
76
+ )
77
+
78
+ def test_delete_rating_preserves_other_fields(self):
79
+ with temp_file_with_metadata({}, "mp3") as test_file:
80
+ update_metadata(
81
+ test_file,
82
+ {
83
+ UnifiedMetadataKey.RATING: 70,
84
+ UnifiedMetadataKey.TITLE: "Test Title",
85
+ UnifiedMetadataKey.ARTISTS: ["Test Artist"],
86
+ },
87
+ normalized_rating_max_value=100,
88
+ )
89
+
90
+ update_metadata(test_file, {UnifiedMetadataKey.RATING: None})
91
+
92
+ assert (
93
+ get_unified_metadata_field(test_file, UnifiedMetadataKey.RATING, normalized_rating_max_value=100)
94
+ is None
95
+ )
96
+ assert get_unified_metadata_field(test_file, UnifiedMetadataKey.TITLE) == "Test Title"
97
+ assert get_unified_metadata_field(test_file, UnifiedMetadataKey.ARTISTS) == ["Test Artist"]
98
+
99
+ def test_delete_rating_already_none(self):
100
+ with temp_file_with_metadata({}, "mp3") as test_file:
101
+ update_metadata(test_file, {UnifiedMetadataKey.RATING: None})
102
+ assert (
103
+ get_unified_metadata_field(test_file, UnifiedMetadataKey.RATING, normalized_rating_max_value=100)
104
+ is None
105
+ )
106
+
107
+ def test_delete_rating_zero(self):
108
+ with temp_file_with_metadata({}, "mp3") as test_file:
109
+ update_metadata(test_file, {UnifiedMetadataKey.RATING: 0}, normalized_rating_max_value=100)
110
+ assert (
111
+ get_unified_metadata_field(test_file, UnifiedMetadataKey.RATING, normalized_rating_max_value=100) == 0
112
+ )
113
+ update_metadata(test_file, {UnifiedMetadataKey.RATING: None})
114
+ assert (
115
+ get_unified_metadata_field(test_file, UnifiedMetadataKey.RATING, normalized_rating_max_value=100)
116
+ is None
117
+ )
@@ -0,0 +1,137 @@
1
+ import pytest
2
+
3
+ from audiometa import get_unified_metadata_field, update_metadata
4
+ from audiometa.exceptions import FileTypeNotSupportedError, InvalidMetadataFieldTypeError, InvalidRatingValueError
5
+ from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
6
+ from audiometa.utils.metadata_format import MetadataFormat
7
+ from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
8
+
9
+
10
+ @pytest.mark.integration
11
+ class TestRatingErrorHandling:
12
+ def test_rating_unsupported_file_type(self):
13
+ with temp_file_with_metadata({}, "mp3") as temp_file_path:
14
+ temp_file_path.write_bytes(b"fake audio content")
15
+ temp_txt = temp_file_path.with_suffix(".txt")
16
+ temp_txt.write_bytes(b"fake audio content")
17
+
18
+ with pytest.raises(FileTypeNotSupportedError):
19
+ get_unified_metadata_field(str(temp_txt), UnifiedMetadataKey.RATING)
20
+
21
+ with pytest.raises(FileTypeNotSupportedError):
22
+ update_metadata(str(temp_txt), {UnifiedMetadataKey.RATING: 85})
23
+
24
+ def test_rating_nonexistent_file(self):
25
+ nonexistent_file = "nonexistent_file.mp3"
26
+
27
+ with pytest.raises(FileNotFoundError):
28
+ get_unified_metadata_field(nonexistent_file, UnifiedMetadataKey.RATING)
29
+
30
+ with pytest.raises(FileNotFoundError):
31
+ update_metadata(nonexistent_file, {UnifiedMetadataKey.RATING: 85})
32
+
33
+ def test_write_fractional_values(self):
34
+ # Fractional values are now supported for half-star ratings (consistent with classic star rating systems)
35
+ basic_metadata = {"title": "Test Title", "artist": "Test Artist"}
36
+ with temp_file_with_metadata(basic_metadata, "mp3") as test_file:
37
+ # 25.5/100 maps to index 3 (1.5 stars), which is valid
38
+ update_metadata(
39
+ test_file,
40
+ {UnifiedMetadataKey.RATING: 25.5},
41
+ normalized_rating_max_value=100,
42
+ metadata_format=MetadataFormat.ID3V2,
43
+ )
44
+ # Verify it was written correctly
45
+ rating = get_unified_metadata_field(test_file, UnifiedMetadataKey.RATING, normalized_rating_max_value=100)
46
+ assert rating == 30 # 1.5 stars = 30 on 100-scale
47
+
48
+ def test_rating_invalid_string_value(self):
49
+ with (
50
+ temp_file_with_metadata({"title": "Test Title", "artist": "Test Artist"}, "mp3") as test_file,
51
+ pytest.raises(
52
+ InvalidMetadataFieldTypeError,
53
+ match="Invalid type for metadata field 'rating': expected Union\\[int, float\\], got str",
54
+ ),
55
+ ):
56
+ update_metadata(test_file, {UnifiedMetadataKey.RATING: "invalid"}, normalized_rating_max_value=100)
57
+
58
+ def test_rating_negative_value_rejected_in_normalized_mode(self):
59
+ with (
60
+ temp_file_with_metadata({"title": "Test Title", "artist": "Test Artist"}, "mp3") as test_file,
61
+ pytest.raises(InvalidRatingValueError) as exc_info,
62
+ ):
63
+ update_metadata(test_file, {UnifiedMetadataKey.RATING: -1}, normalized_rating_max_value=100)
64
+ assert "must be non-negative" in str(exc_info.value)
65
+
66
+ def test_rating_over_max_value_rejected_in_normalized_mode(self):
67
+ with temp_file_with_metadata({"title": "Test Title", "artist": "Test Artist"}, "mp3") as test_file:
68
+ with pytest.raises(InvalidRatingValueError) as exc_info:
69
+ update_metadata(test_file, {UnifiedMetadataKey.RATING: 101}, normalized_rating_max_value=100)
70
+ assert "out of range" in str(exc_info.value)
71
+ assert "must be between 0 and 100" in str(exc_info.value)
72
+
73
+ def test_rating_none_value(self):
74
+ with temp_file_with_metadata({"title": "Test Title", "artist": "Test Artist"}, "mp3") as test_file:
75
+ update_metadata(test_file, {UnifiedMetadataKey.RATING: None}, normalized_rating_max_value=100)
76
+ metadata = get_unified_metadata_field(test_file, UnifiedMetadataKey.RATING, normalized_rating_max_value=100)
77
+ assert metadata is None
78
+
79
+ def test_rating_without_max_value_allows_any_non_negative_integer(self):
80
+ with temp_file_with_metadata({"title": "Test Title", "artist": "Test Artist"}, "mp3") as test_file:
81
+ # Any non-negative integer value should be allowed when normalized_rating_max_value is None
82
+ update_metadata(test_file, {UnifiedMetadataKey.RATING: 128}, metadata_format=MetadataFormat.ID3V2)
83
+ update_metadata(test_file, {UnifiedMetadataKey.RATING: 75}, metadata_format=MetadataFormat.ID3V2)
84
+ update_metadata(test_file, {UnifiedMetadataKey.RATING: 0}, metadata_format=MetadataFormat.ID3V2)
85
+
86
+ # Negative values should be rejected
87
+ with pytest.raises(InvalidRatingValueError) as exc_info:
88
+ update_metadata(test_file, {UnifiedMetadataKey.RATING: -1}, metadata_format=MetadataFormat.ID3V2)
89
+ assert "must be non-negative" in str(exc_info.value)
90
+
91
+ # Valid value in BASE_100_PROPORTIONAL profile (Vorbis uses this)
92
+ with temp_file_with_metadata(
93
+ {"title": "Test Title", "artist": "Test Artist"}, "flac"
94
+ ) as test_file_flac_path:
95
+ update_metadata(
96
+ test_file_flac_path, {UnifiedMetadataKey.RATING: 50}, metadata_format=MetadataFormat.VORBIS
97
+ )
98
+ update_metadata(
99
+ test_file_flac_path, {UnifiedMetadataKey.RATING: 128}, metadata_format=MetadataFormat.VORBIS
100
+ )
101
+
102
+ def test_rating_with_normalized_max_validates_profile_values(self):
103
+ with temp_file_with_metadata({"title": "Test Title", "artist": "Test Artist"}, "mp3") as test_file:
104
+ # Valid: 50/100 maps to star rating index 5 (2.5 stars), which is valid
105
+ update_metadata(
106
+ test_file,
107
+ {UnifiedMetadataKey.RATING: 50},
108
+ normalized_rating_max_value=100,
109
+ metadata_format=MetadataFormat.ID3V2,
110
+ )
111
+
112
+ # Invalid: 37/100 maps to star rating index 4 (2 stars), which is valid
113
+ # But 110/100 would map to index 11, which is > 10 (invalid)
114
+ with pytest.raises(InvalidRatingValueError) as exc_info:
115
+ update_metadata(
116
+ test_file,
117
+ {UnifiedMetadataKey.RATING: 110},
118
+ normalized_rating_max_value=100,
119
+ metadata_format=MetadataFormat.ID3V2,
120
+ )
121
+ assert "out of range" in str(exc_info.value) or "do not exist in any supported writing profile" in str(
122
+ exc_info.value
123
+ )
124
+
125
+ def test_invalid_rating_value_error_non_numeric_string(self):
126
+ with temp_file_with_metadata({}, "mp3") as test_file:
127
+ with pytest.raises(InvalidMetadataFieldTypeError) as exc_info:
128
+ update_metadata(test_file, {UnifiedMetadataKey.RATING: "invalid"}, normalized_rating_max_value=100)
129
+ assert "invalid type for metadata field 'rating'" in str(exc_info.value).lower()
130
+
131
+ def test_invalid_rating_value_error_non_numeric_type(self):
132
+ with temp_file_with_metadata({}, "mp3") as test_file:
133
+ with pytest.raises(InvalidMetadataFieldTypeError) as exc_info:
134
+ update_metadata(
135
+ test_file, {UnifiedMetadataKey.RATING: {"not": "valid"}}, normalized_rating_max_value=100
136
+ )
137
+ assert "invalid type for metadata field 'rating'" in str(exc_info.value).lower()
@@ -0,0 +1,77 @@
1
+ import pytest
2
+
3
+ from audiometa import get_unified_metadata_field, update_metadata
4
+ from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
5
+ from audiometa.utils.metadata_format import MetadataFormat
6
+ from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
7
+
8
+
9
+ @pytest.mark.integration
10
+ class TestId3v2RatingWriting:
11
+ @pytest.mark.parametrize(
12
+ ("value", "expected"),
13
+ [
14
+ (0, 0),
15
+ (0.5, 13),
16
+ (1, 1),
17
+ (1.5, 54),
18
+ (2, 64),
19
+ (2.5, 118),
20
+ (3, 128),
21
+ (3.5, 186),
22
+ (4, 196),
23
+ (4.5, 242),
24
+ (5, 255),
25
+ ],
26
+ )
27
+ def test_write_base_255_non_proportional_values(self, value, expected):
28
+ basic_metadata = {"title": "Test Title", "artist": "Test Artist"}
29
+
30
+ with temp_file_with_metadata(basic_metadata, "mp3") as test_file:
31
+ test_metadata = {UnifiedMetadataKey.RATING: value}
32
+ update_metadata(
33
+ test_file, test_metadata, normalized_rating_max_value=5, metadata_format=MetadataFormat.ID3V2
34
+ )
35
+ rating = get_unified_metadata_field(test_file, UnifiedMetadataKey.RATING)
36
+ assert rating == expected
37
+
38
+ def test_write_none_removes_rating(self):
39
+ basic_metadata = {"title": "Test Title", "artist": "Test Artist"}
40
+
41
+ with temp_file_with_metadata(basic_metadata, "mp3") as test_file:
42
+ # First write a rating
43
+ test_metadata = {UnifiedMetadataKey.RATING: 80}
44
+ update_metadata(
45
+ test_file, test_metadata, normalized_rating_max_value=100, metadata_format=MetadataFormat.ID3V2
46
+ )
47
+ rating = get_unified_metadata_field(test_file, UnifiedMetadataKey.RATING, normalized_rating_max_value=100)
48
+ assert rating == 80
49
+
50
+ # Then remove it with None - this may not work as expected in all cases
51
+ test_metadata = {UnifiedMetadataKey.RATING: None}
52
+ update_metadata(
53
+ test_file, test_metadata, normalized_rating_max_value=100, metadata_format=MetadataFormat.ID3V2
54
+ )
55
+ rating = get_unified_metadata_field(test_file, UnifiedMetadataKey.RATING, normalized_rating_max_value=100)
56
+ # Rating removal behavior may vary - check if it's None or 0
57
+ assert rating is None or rating == 0
58
+
59
+ def test_write_edge_values(self):
60
+ basic_metadata = {"title": "Test Title", "artist": "Test Artist"}
61
+
62
+ with temp_file_with_metadata(basic_metadata, "mp3") as test_file:
63
+ # Test minimum value
64
+ test_metadata = {UnifiedMetadataKey.RATING: 0}
65
+ update_metadata(
66
+ test_file, test_metadata, normalized_rating_max_value=100, metadata_format=MetadataFormat.ID3V2
67
+ )
68
+ rating = get_unified_metadata_field(test_file, UnifiedMetadataKey.RATING, normalized_rating_max_value=100)
69
+ assert rating == 0
70
+
71
+ # Test maximum value
72
+ test_metadata = {UnifiedMetadataKey.RATING: 100}
73
+ update_metadata(
74
+ test_file, test_metadata, normalized_rating_max_value=100, metadata_format=MetadataFormat.ID3V2
75
+ )
76
+ rating = get_unified_metadata_field(test_file, UnifiedMetadataKey.RATING, normalized_rating_max_value=100)
77
+ assert rating == 100
@@ -0,0 +1,55 @@
1
+ import pytest
2
+
3
+ from audiometa import get_unified_metadata_field, update_metadata
4
+ from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
5
+ from audiometa.utils.metadata_format import MetadataFormat
6
+ from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
7
+
8
+
9
+ @pytest.mark.integration
10
+ class TestRiffRatingWriting:
11
+ @pytest.mark.parametrize(
12
+ ("star_rating", "expected_normalized_rating"),
13
+ [
14
+ (0, 0),
15
+ (0.5, 10),
16
+ (1, 20),
17
+ (1.5, 30),
18
+ (2, 40),
19
+ (2.5, 50),
20
+ (3, 60),
21
+ (3.5, 70),
22
+ (4, 80),
23
+ (4.5, 90),
24
+ (5, 100),
25
+ ],
26
+ )
27
+ def test_write_star_rating(self, star_rating, expected_normalized_rating):
28
+ basic_metadata = {"title": "Test Title", "artist": "Test Artist"}
29
+
30
+ with temp_file_with_metadata(basic_metadata, "wav") as test_file:
31
+ test_metadata = {UnifiedMetadataKey.RATING: star_rating}
32
+ update_metadata(
33
+ test_file, test_metadata, normalized_rating_max_value=5, metadata_format=MetadataFormat.RIFF
34
+ )
35
+ rating = get_unified_metadata_field(test_file, UnifiedMetadataKey.RATING, normalized_rating_max_value=100)
36
+ assert rating == expected_normalized_rating
37
+
38
+ def test_write_none_removes_rating(self):
39
+ basic_metadata = {"title": "Test Title", "artist": "Test Artist"}
40
+
41
+ with temp_file_with_metadata(basic_metadata, "wav") as test_file:
42
+ # First write a rating
43
+ test_metadata = {UnifiedMetadataKey.RATING: 80}
44
+ update_metadata(
45
+ test_file, test_metadata, normalized_rating_max_value=100, metadata_format=MetadataFormat.RIFF
46
+ )
47
+ rating = get_unified_metadata_field(test_file, UnifiedMetadataKey.RATING, normalized_rating_max_value=100)
48
+ assert rating == 80
49
+
50
+ # Then remove it with None
51
+ test_metadata = {UnifiedMetadataKey.RATING: None}
52
+ update_metadata(
53
+ test_file, test_metadata, normalized_rating_max_value=100, metadata_format=MetadataFormat.RIFF
54
+ )
55
+ rating = get_unified_metadata_field(test_file, UnifiedMetadataKey.RATING, normalized_rating_max_value=100)
@@ -0,0 +1,57 @@
1
+ import pytest
2
+
3
+ from audiometa import get_unified_metadata, get_unified_metadata_field, update_metadata
4
+ from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
5
+ from audiometa.utils.metadata_format import MetadataFormat
6
+ from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
7
+
8
+
9
+ @pytest.mark.integration
10
+ class TestVorbisRatingWriting:
11
+ @pytest.mark.parametrize(
12
+ ("star_rating", "expected_normalized_rating"),
13
+ [
14
+ (0, 0),
15
+ (0.5, 10),
16
+ (1, 20),
17
+ (1.5, 30),
18
+ (2, 40),
19
+ (2.5, 50),
20
+ (3, 60),
21
+ (3.5, 70),
22
+ (4, 80),
23
+ (4.5, 90),
24
+ (5, 100),
25
+ ],
26
+ )
27
+ def test_write_star_rating(self, star_rating, expected_normalized_rating):
28
+ basic_metadata = {"title": "Test Title", "artist": "Test Artist"}
29
+
30
+ with temp_file_with_metadata(basic_metadata, "flac") as test_file:
31
+ test_metadata = {UnifiedMetadataKey.RATING: star_rating}
32
+ update_metadata(
33
+ test_file, test_metadata, normalized_rating_max_value=5, metadata_format=MetadataFormat.VORBIS
34
+ )
35
+ metadata = get_unified_metadata(test_file)
36
+ rating = metadata.get(UnifiedMetadataKey.RATING)
37
+ assert rating == expected_normalized_rating
38
+
39
+ def test_write_none_removes_rating(self):
40
+ basic_metadata = {"title": "Test Title", "artist": "Test Artist"}
41
+
42
+ with temp_file_with_metadata(basic_metadata, "flac") as test_file:
43
+ # First write a rating
44
+ test_metadata = {UnifiedMetadataKey.RATING: 80}
45
+ update_metadata(
46
+ test_file, test_metadata, normalized_rating_max_value=100, metadata_format=MetadataFormat.VORBIS
47
+ )
48
+ rating = get_unified_metadata_field(test_file, UnifiedMetadataKey.RATING, normalized_rating_max_value=100)
49
+ assert rating == 80
50
+
51
+ # Then remove it with None
52
+ test_metadata = {UnifiedMetadataKey.RATING: None}
53
+ update_metadata(
54
+ test_file, test_metadata, normalized_rating_max_value=100, metadata_format=MetadataFormat.VORBIS
55
+ )
56
+ rating = get_unified_metadata_field(test_file, UnifiedMetadataKey.RATING, normalized_rating_max_value=100)
57
+ assert rating is None