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,198 @@
1
+ import pytest
2
+
3
+ from audiometa import get_unified_metadata_field
4
+ from audiometa.test.helpers.id3v1.id3v1_metadata_getter import ID3v1MetadataGetter
5
+ from audiometa.test.helpers.id3v1.id3v1_metadata_setter import ID3v1MetadataSetter
6
+ from audiometa.test.helpers.id3v2.id3v2_metadata_getter import ID3v2MetadataGetter
7
+ from audiometa.test.helpers.id3v2.id3v2_metadata_setter import ID3v2MetadataSetter
8
+ from audiometa.test.helpers.riff.riff_metadata_getter import RIFFMetadataGetter
9
+ from audiometa.test.helpers.riff.riff_metadata_setter import RIFFMetadataSetter
10
+ from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
11
+ from audiometa.test.helpers.vorbis.vorbis_metadata_getter import VorbisMetadataGetter
12
+ from audiometa.test.helpers.vorbis.vorbis_metadata_setter import VorbisMetadataSetter
13
+ from audiometa.utils.metadata_format import MetadataFormat
14
+ from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
15
+
16
+
17
+ @pytest.mark.integration
18
+ class TestSmartParsingScenarios:
19
+ """Test the smart parsing scenarios described in the README:
20
+
21
+ - Modern formats (ID3v2, Vorbis) + Multiple entries: No separator parsing
22
+ - Modern formats (ID3v2, Vorbis) + Single entry: Applies separator parsing
23
+ - Legacy formats (RIFF, ID3v1): Always applies separator parsing
24
+ """
25
+
26
+ def test_scenario_1_multiple_entries_no_parsing_id3v2_3(self):
27
+ """Scenario 1: ID3v2.3 uses single frame with separators - gets parsed on read."""
28
+ with temp_file_with_metadata({"title": "Test Song"}, "id3v2.3") as test_file:
29
+ ID3v2MetadataSetter.set_artists(
30
+ test_file,
31
+ ["Artist One", "Artist; with; semicolons", "Artist Three"],
32
+ version="2.3",
33
+ in_separate_frames=True,
34
+ )
35
+ raw_metadata = ID3v2MetadataGetter.get_raw_metadata(test_file, version="2.3")
36
+ assert raw_metadata["TPE1"] == ["Artist One", "Artist; with; semicolons", "Artist Three"]
37
+
38
+ # Read metadata
39
+ artists = get_unified_metadata_field(
40
+ test_file,
41
+ UnifiedMetadataKey.ARTISTS,
42
+ metadata_format=MetadataFormat.ID3V2,
43
+ id3v2_version=(2, 3, 0),
44
+ )
45
+ assert artists == ["Artist One", "Artist; with; semicolons", "Artist Three"]
46
+
47
+ def test_scenario_1_multiple_entries_no_parsing_id3v2_4(self):
48
+ """Scenario 1: ID3v2.4 uses single frame with separators - gets parsed on read."""
49
+ with temp_file_with_metadata({"title": "Test Song"}, "id3v2.4") as test_file:
50
+ # Set multiple separate artist entries (ID3v2.4 will concatenate them)
51
+ ID3v2MetadataSetter.set_artists(
52
+ test_file,
53
+ ["Artist One", "Artist; with; semicolons", "Artist Three"],
54
+ version="2.4",
55
+ in_separate_frames=True,
56
+ )
57
+ raw_metadata = ID3v2MetadataGetter.get_raw_metadata(test_file, version="2.4")
58
+ assert raw_metadata["TPE1"] == ["Artist One", "Artist; with; semicolons", "Artist Three"]
59
+
60
+ # Read metadata
61
+ artists = get_unified_metadata_field(
62
+ test_file,
63
+ UnifiedMetadataKey.ARTISTS,
64
+ metadata_format=MetadataFormat.ID3V2,
65
+ id3v2_version=(2, 4, 0),
66
+ )
67
+ assert artists == ["Artist One", "Artist; with; semicolons", "Artist Three"]
68
+
69
+ def test_scenario_1_multiple_entries_no_parsing_vorbis(self):
70
+ """Scenario 1: Modern file with separate entries - separators preserved (Vorbis)."""
71
+ with temp_file_with_metadata({"title": "Test Song"}, "flac") as test_file:
72
+ # Set multiple separate artist entries (modern format)
73
+ VorbisMetadataSetter.set_artists(test_file, ["Artist One", "Artist; with; semicolons", "Artist Three"])
74
+ raw_metadata = VorbisMetadataGetter.get_raw_metadata(test_file)
75
+ assert "ARTIST=Artist One" in raw_metadata
76
+ assert "ARTIST=Artist; with; semicolons" in raw_metadata
77
+ assert "ARTIST=Artist Three" in raw_metadata
78
+
79
+ # Read metadata
80
+ artists = get_unified_metadata_field(
81
+ test_file, UnifiedMetadataKey.ARTISTS, metadata_format=MetadataFormat.VORBIS
82
+ )
83
+ assert artists == ["Artist One", "Artist; with; semicolons", "Artist Three"]
84
+
85
+ def test_scenario_1_multiple_entries_no_parsing_riff(self):
86
+ """Scenario 1: Modern file with separate entries - separators preserved (RIFF)."""
87
+ with temp_file_with_metadata({"title": "Test Song"}, "wav") as test_file:
88
+ # Set multiple separate artist entries (modern format)
89
+ RIFFMetadataSetter.set_artists(
90
+ test_file, ["Artist One", "Artist; with; semicolons", "Artist Three"], in_separate_frames=True
91
+ )
92
+ raw_metadata = RIFFMetadataGetter.get_raw_metadata(test_file)
93
+ assert "TAG:artist=Artist One" in raw_metadata
94
+ assert "TAG:artist=Artist; with; semicolons" in raw_metadata
95
+ assert "TAG:artist=Artist Three" in raw_metadata
96
+
97
+ artists = get_unified_metadata_field(
98
+ test_file, UnifiedMetadataKey.ARTISTS, metadata_format=MetadataFormat.RIFF
99
+ )
100
+ assert artists == ["Artist One", "Artist; with; semicolons", "Artist Three"]
101
+
102
+ def test_scenario_2_single_entry_parsed_id3v2_3(self):
103
+ """Scenario 2: Legacy data in modern format - single entry gets parsed (ID3v2.3)."""
104
+ with temp_file_with_metadata({"title": "Test Song"}, "id3v2.3") as test_file:
105
+ # Set single artist entry with semicolons (legacy data in modern format)
106
+ ID3v2MetadataSetter.set_artists(
107
+ test_file, ["Artist One;Artist Two;Artist Three"], version="2.3", in_separate_frames=False
108
+ )
109
+
110
+ raw_metadata = ID3v2MetadataGetter.get_raw_metadata(test_file, version="2.3")
111
+ assert raw_metadata["TPE1"] == ["Artist One;Artist Two;Artist Three"]
112
+
113
+ # Read metadata
114
+ artists = get_unified_metadata_field(
115
+ test_file, UnifiedMetadataKey.ARTISTS, metadata_format=MetadataFormat.ID3V2
116
+ )
117
+ assert artists == ["Artist One", "Artist Two", "Artist Three"]
118
+
119
+ def test_scenario_2_single_entry_parsed_id3v2_4(self):
120
+ """Scenario 2: Legacy data in modern format - single entry gets parsed (ID3v2.4)."""
121
+ with temp_file_with_metadata({"title": "Test Song"}, "id3v2.4") as test_file:
122
+ # Set single artist entry with semicolons (legacy data in modern format)
123
+ ID3v2MetadataSetter.set_artists(
124
+ test_file, ["Artist One;Artist Two;Artist Three"], version="2.4", in_separate_frames=False
125
+ )
126
+
127
+ raw_metadata = ID3v2MetadataGetter.get_raw_metadata(test_file, version="2.4")
128
+ assert raw_metadata["TPE1"] == ["Artist One;Artist Two;Artist Three"]
129
+
130
+ # Read metadata
131
+ artists = get_unified_metadata_field(
132
+ test_file, UnifiedMetadataKey.ARTISTS, metadata_format=MetadataFormat.ID3V2
133
+ )
134
+ assert artists == ["Artist One", "Artist Two", "Artist Three"]
135
+
136
+ def test_scenario_2_single_entry_parsed_vorbis(self):
137
+ """Scenario 2: Legacy data in modern format - single entry gets parsed (Vorbis)."""
138
+ with temp_file_with_metadata({"title": "Test Song"}, "flac") as test_file:
139
+ # Set single artist entry with semicolons (legacy data in modern format)
140
+ VorbisMetadataSetter.set_artists(test_file, ["Artist One;Artist Two;Artist Three"])
141
+ raw_metadata = VorbisMetadataGetter.get_raw_metadata(test_file)
142
+ assert "ARTIST=Artist One;Artist Two;Artist Three" in raw_metadata
143
+
144
+ # Read metadata
145
+ artists = get_unified_metadata_field(
146
+ test_file, UnifiedMetadataKey.ARTISTS, metadata_format=MetadataFormat.VORBIS
147
+ )
148
+ assert artists == ["Artist One", "Artist Two", "Artist Three"]
149
+
150
+ def test_scenario_2_single_entry_parsed_riff(self):
151
+ """Scenario 3: Legacy format (RIFF) - always applies separator parsing."""
152
+ with temp_file_with_metadata({"title": "Test Song"}, "wav") as test_file:
153
+ # Set single artist entry with semicolons in RIFF format
154
+ RIFFMetadataSetter.set_artists(test_file, ["Artist One;Artist Two"], in_separate_frames=False)
155
+ raw_metadata = RIFFMetadataGetter.get_raw_metadata(test_file)
156
+ assert "TAG:artist=Artist One;Artist Two" in raw_metadata
157
+
158
+ # Read metadata
159
+ artists = get_unified_metadata_field(
160
+ test_file, UnifiedMetadataKey.ARTISTS, metadata_format=MetadataFormat.RIFF
161
+ )
162
+ assert artists == ["Artist One", "Artist Two"]
163
+
164
+ def test_scenario_3_legacy_format_always_parses_id3v1(self):
165
+ """Scenario 3: Legacy format (ID3v1) - always applies separator parsing."""
166
+ with temp_file_with_metadata({"title": "Test Song"}, "mp3") as test_file:
167
+ # Set single artist entry with semicolons in ID3v1 format
168
+ ID3v1MetadataSetter.set_artist(test_file, "Artist One;Artist Two")
169
+ raw_metadata = ID3v1MetadataGetter.get_raw_metadata(test_file)
170
+ assert "Artist One;Artist Two" in raw_metadata.get("artist", "")
171
+
172
+ # Read metadata
173
+ artists = get_unified_metadata_field(
174
+ test_file, UnifiedMetadataKey.ARTISTS, metadata_format=MetadataFormat.ID3V1
175
+ )
176
+ assert artists == ["Artist One", "Artist Two"]
177
+
178
+ def test_mixed_scenario_modern_format_with_both_patterns(self):
179
+ """Test mixed scenario: ID3v2 concatenates multiple entries, single entries get parsed."""
180
+ with temp_file_with_metadata({"title": "Test Song"}, "id3v2.3") as test_file:
181
+ # Set artists as multiple separate entries (ID3v2 will concatenate them)
182
+ ID3v2MetadataSetter.set_artists(
183
+ test_file,
184
+ ["Artist One", "Artist; with; semicolons", "Artist Three"],
185
+ version="2.3",
186
+ in_separate_frames=True,
187
+ )
188
+ raw_metadata = ID3v2MetadataGetter.get_raw_metadata(test_file, version="2.3")
189
+ assert raw_metadata["TPE1"] == ["Artist One", "Artist; with; semicolons", "Artist Three"]
190
+
191
+ # Read metadata
192
+ artists = get_unified_metadata_field(
193
+ test_file,
194
+ UnifiedMetadataKey.ARTISTS,
195
+ metadata_format=MetadataFormat.ID3V2,
196
+ id3v2_version=(2, 3, 0),
197
+ )
198
+ assert artists == ["Artist One", "Artist; with; semicolons", "Artist Three"]
@@ -0,0 +1,24 @@
1
+ import pytest
2
+
3
+ from audiometa import get_unified_metadata_field
4
+ from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
5
+ from audiometa.test.helpers.vorbis.vorbis_metadata_setter import VorbisMetadataSetter
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 TestUnicodeHandling:
12
+ def test_unicode_characters(self):
13
+ with temp_file_with_metadata({"title": "Test Song"}, "flac") as test_file:
14
+ VorbisMetadataSetter.set_artists(test_file, ["Artist Café", "Artist 音乐", "Artist 🎵"])
15
+
16
+ artists = get_unified_metadata_field(
17
+ test_file, UnifiedMetadataKey.ARTISTS, metadata_format=MetadataFormat.VORBIS
18
+ )
19
+
20
+ assert isinstance(artists, list)
21
+ assert len(artists) == 3
22
+ assert "Artist Café" in artists
23
+ assert "Artist 音乐" in artists
24
+ assert "Artist 🎵" in artists
@@ -0,0 +1 @@
1
+ """Tests for writing multiple entries for the same tag."""
@@ -0,0 +1 @@
1
+ """Tests for writing multiple entries for the same tag in different metadata formats."""
@@ -0,0 +1,62 @@
1
+ import pytest
2
+
3
+ from audiometa import update_metadata
4
+ from audiometa.test.helpers.id3v1.id3v1_metadata_getter import ID3v1MetadataGetter
5
+ from audiometa.test.helpers.id3v1.id3v1_metadata_setter import ID3v1MetadataSetter
6
+ from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
7
+ from audiometa.utils.metadata_format import MetadataFormat
8
+ from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
9
+
10
+
11
+ @pytest.mark.integration
12
+ class TestMultipleValuesId3v1:
13
+ def test_id3v1_artists_concatenation_default_comma(self):
14
+ initial_metadata = {"title": "Test Song"}
15
+ with temp_file_with_metadata(initial_metadata, "mp3") as test_file:
16
+ metadata = {UnifiedMetadataKey.ARTISTS: ["Artist 1", "Artist 2"]}
17
+
18
+ update_metadata(test_file, metadata, metadata_format=MetadataFormat.ID3V1)
19
+
20
+ # Use helper to check the created ID3v1 artist field directly
21
+ raw_metadata = ID3v1MetadataGetter.get_raw_metadata(test_file)
22
+ artists = raw_metadata.get("artist", "")
23
+ assert "Artist 1,Artist 2" in artists or "Artist 2,Artist 1" in artists
24
+
25
+ def test_with_existing_artists_field(self):
26
+ with temp_file_with_metadata({}, "mp3") as test_file:
27
+ ID3v1MetadataSetter.set_artist(test_file, "Existing 1; Existing 2")
28
+ raw_metadata = ID3v1MetadataGetter.get_raw_metadata(test_file)
29
+ assert "Existing 1; Existing 2" in raw_metadata.get("artist", "")
30
+
31
+ # Now update with multiple artists
32
+ metadata = {UnifiedMetadataKey.ARTISTS: ["Existing 1", "New 2"]}
33
+ update_metadata(test_file, metadata, metadata_format=MetadataFormat.ID3V1)
34
+ raw_metadata = ID3v1MetadataGetter.get_raw_metadata(test_file)
35
+ artists = raw_metadata.get("artist", "")
36
+ assert "Existing 1" in artists
37
+ assert "New 2" in artists
38
+ assert "Existing 2" not in artists
39
+
40
+ def test_id3v1_separator_priority(self):
41
+ # Each test case: values, expected separator
42
+ test_cases = [
43
+ (["A1", "A2", "A3"], ","),
44
+ (["A,1", "A2", "A3"], ";"),
45
+ (["A,1", "A;2", "A3"], "|"),
46
+ (["A,1", "A;2", "A|3"], "·"),
47
+ (["A,1", "A;2", "A|3", "A·4"], "/"),
48
+ (["A,1", "A;2", "A|3", "A·4", "A/5"], ","),
49
+ ]
50
+ for values, expected_sep in test_cases:
51
+ initial_metadata = {"title": "Test Song"}
52
+ with temp_file_with_metadata(initial_metadata, "mp3") as test_file:
53
+ metadata = {UnifiedMetadataKey.ARTISTS: values}
54
+ update_metadata(test_file, metadata, metadata_format=MetadataFormat.ID3V1)
55
+ raw_metadata = ID3v1MetadataGetter.get_raw_metadata(test_file)
56
+ artists = raw_metadata.get("artist", "")
57
+
58
+ # Check that the expected separator is used
59
+ assert expected_sep in artists
60
+ # Check that all values are present as substrings
61
+ for v in values:
62
+ assert v in artists
@@ -0,0 +1,36 @@
1
+ import pytest
2
+
3
+ from audiometa import update_metadata
4
+ from audiometa.test.helpers.id3v2.id3v2_metadata_getter import ID3v2MetadataGetter
5
+ from audiometa.test.helpers.id3v2.id3v2_metadata_setter import ID3v2MetadataSetter
6
+ from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
7
+ from audiometa.utils.metadata_format import MetadataFormat
8
+ from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
9
+
10
+
11
+ @pytest.mark.integration
12
+ class TestMultipleValuesId3v23:
13
+ def test_artists_concatenation(self):
14
+ initial_metadata = {"title": "Test Song"}
15
+ with temp_file_with_metadata(initial_metadata, "id3v2.3") as test_file:
16
+ metadata = {UnifiedMetadataKey.ARTISTS: ["Artist 1", "Artist 2", "Artist 3"]}
17
+
18
+ update_metadata(test_file, metadata, metadata_format=MetadataFormat.ID3V2, id3v2_version=(2, 3, 0))
19
+
20
+ raw_metadata = ID3v2MetadataGetter.get_raw_metadata(test_file, version="2.3")
21
+ assert raw_metadata["TPE1"] == ["Artist 1//Artist 2//Artist 3"]
22
+
23
+ def test_with_existing_artists_field(self):
24
+ # Start with an existing artist field
25
+ initial_metadata = {"artist": "Existing Artist"}
26
+ with temp_file_with_metadata(initial_metadata, "id3v2.3") as test_file:
27
+ ID3v2MetadataSetter.set_artists(test_file, "Existing 1; Existing 2", version="2.3")
28
+ raw_metadata = ID3v2MetadataGetter.get_raw_metadata(test_file, version="2.3")
29
+ assert raw_metadata["TPE1"] == ["Existing 1; Existing 2"]
30
+
31
+ metadata = {UnifiedMetadataKey.ARTISTS: ["Existing 1", "New 2"]}
32
+ update_metadata(test_file, metadata, metadata_format=MetadataFormat.ID3V2, id3v2_version=(2, 3, 0))
33
+
34
+ raw_metadata = ID3v2MetadataGetter.get_raw_metadata(test_file, version="2.3")
35
+
36
+ assert raw_metadata["TPE1"] == ["Existing 1//New 2"]
@@ -0,0 +1,34 @@
1
+ import pytest
2
+
3
+ from audiometa import update_metadata
4
+ from audiometa.test.helpers.id3v2.id3v2_metadata_getter import ID3v2MetadataGetter
5
+ from audiometa.test.helpers.id3v2.id3v2_metadata_setter import ID3v2MetadataSetter
6
+ from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
7
+ from audiometa.utils.metadata_format import MetadataFormat
8
+ from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
9
+
10
+
11
+ @pytest.mark.integration
12
+ class TestMultipleValuesId3v24:
13
+ def test_write_multiple_artists(self):
14
+ with temp_file_with_metadata({"title": "Test Song"}, "id3v2.4") as test_file:
15
+ metadata = {UnifiedMetadataKey.ARTISTS: ["Artist One", "Artist Two"]}
16
+
17
+ update_metadata(test_file, metadata, metadata_format=MetadataFormat.ID3V2, id3v2_version=(2, 4, 0))
18
+
19
+ raw_metadata = ID3v2MetadataGetter.get_raw_metadata(test_file, version="2.4")
20
+
21
+ assert raw_metadata["TPE1"] == ["Artist One\x00Artist Two"]
22
+
23
+ def test_write_on_existing_artists_field(self):
24
+ with temp_file_with_metadata({}, "id3v2.4") as test_file:
25
+ ID3v2MetadataSetter.set_artists(test_file, ["Existing A\x00Existing B"], version="2.4")
26
+ raw_metadata = ID3v2MetadataGetter.get_raw_metadata(test_file, version="2.4")
27
+
28
+ assert raw_metadata["TPE1"] == ["Existing A\x00Existing B"]
29
+
30
+ metadata = {UnifiedMetadataKey.ARTISTS: ["Existing A", "New B"]}
31
+ update_metadata(test_file, metadata, metadata_format=MetadataFormat.ID3V2, id3v2_version=(2, 4, 0))
32
+
33
+ raw_metadata = ID3v2MetadataGetter.get_raw_metadata(test_file, version="2.4")
34
+ assert raw_metadata["TPE1"] == ["Existing A\x00New B"]
@@ -0,0 +1,32 @@
1
+ import pytest
2
+
3
+ from audiometa import update_metadata
4
+ from audiometa.test.helpers.riff.riff_metadata_getter import RIFFMetadataGetter
5
+ from audiometa.test.helpers.riff.riff_metadata_setter import RIFFMetadataSetter
6
+ from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
7
+ from audiometa.utils.metadata_format import MetadataFormat
8
+ from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
9
+
10
+
11
+ @pytest.mark.integration
12
+ class TestMultipleValuesRiff:
13
+ def test_artists_concatenation(self):
14
+ initial_metadata = {"title": "Test Song"}
15
+ with temp_file_with_metadata(initial_metadata, "wav") as test_file:
16
+ metadata = {UnifiedMetadataKey.ARTISTS: ["Artist 1", "Artist 2", "Artist 3"]}
17
+ update_metadata(test_file, metadata, metadata_format=MetadataFormat.RIFF)
18
+
19
+ raw_metadata = RIFFMetadataGetter.get_raw_metadata(test_file)
20
+ assert "TAG:artist=Artist 1//Artist 2//Artist 3" in raw_metadata
21
+
22
+ def test_with_existing_artists_field(self):
23
+ with temp_file_with_metadata({}, "wav") as test_file:
24
+ RIFFMetadataSetter.set_artists(test_file, ["Existing 1;Existing 2"])
25
+ raw_metadata = RIFFMetadataGetter.get_raw_metadata(test_file)
26
+ assert "TAG:artist=Existing 1;Existing 2" in raw_metadata
27
+
28
+ metadata = {UnifiedMetadataKey.ARTISTS: ["Existing 1", "New 2"]}
29
+ update_metadata(test_file, metadata, metadata_format=MetadataFormat.RIFF)
30
+
31
+ raw_metadata = RIFFMetadataGetter.get_raw_metadata(test_file)
32
+ assert "TAG:artist=Existing 1//New 2" in raw_metadata
@@ -0,0 +1,54 @@
1
+ import pytest
2
+
3
+ from audiometa import update_metadata
4
+ from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
5
+ from audiometa.test.helpers.vorbis.vorbis_metadata_getter import VorbisMetadataGetter
6
+ from audiometa.test.helpers.vorbis.vorbis_metadata_setter import VorbisMetadataSetter
7
+ from audiometa.utils.metadata_format import MetadataFormat
8
+ from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
9
+
10
+
11
+ @pytest.mark.integration
12
+ class TestMultipleValuesVorbis:
13
+ def test_write_multiple_artists(self):
14
+ with temp_file_with_metadata({}, "flac") as test_file:
15
+ # Write multiple artists using update_metadata
16
+ metadata = {UnifiedMetadataKey.ARTISTS: ["Artist One", "Artist Two", "Artist Three"]}
17
+
18
+ update_metadata(test_file, metadata, metadata_format=MetadataFormat.VORBIS)
19
+ VorbisMetadataSetter.add_title(test_file, "Test Song")
20
+ raw_metadata = VorbisMetadataGetter.get_raw_metadata(test_file)
21
+ assert "ARTIST=Artist One" in raw_metadata
22
+ assert "ARTIST=Artist Two" in raw_metadata
23
+ assert "ARTIST=Artist Three" in raw_metadata
24
+
25
+ def test_with_existing_artists_fields(self):
26
+ # Start with an existing artist field
27
+ with temp_file_with_metadata({}, "flac") as test_file:
28
+ # create an existing value using setter
29
+ VorbisMetadataSetter.set_artists(test_file, ["Existing 1; Existing 2"])
30
+ raw_metadata = VorbisMetadataGetter.get_raw_metadata(test_file)
31
+ assert "Existing 1; Existing 2" in raw_metadata
32
+
33
+ metadata = {UnifiedMetadataKey.ARTISTS: ["Existing 1", "New 2"]}
34
+ update_metadata(test_file, metadata, metadata_format=MetadataFormat.VORBIS)
35
+
36
+ raw_metadata = VorbisMetadataGetter.get_raw_metadata(test_file)
37
+ assert "Existing 1" in raw_metadata
38
+ assert "New 2" in raw_metadata
39
+ assert "Existing 2" not in raw_metadata
40
+
41
+ def test_with_existing_artists_fields_with_lower_case_key(self):
42
+ # Start with an existing artist field in lower case
43
+ with temp_file_with_metadata({}, "flac") as test_file:
44
+ # create an existing value using setter
45
+ VorbisMetadataSetter.set_artists(test_file, ["ExistingLower 1; ExistingLower 2"], key_lower_case=True)
46
+ raw_output = VorbisMetadataGetter.get_raw_metadata(test_file)
47
+ assert "artist=ExistingLower 1; ExistingLower 2" in raw_output
48
+
49
+ metadata = {UnifiedMetadataKey.ARTISTS: ["ExistingLower 1", "New 2"]}
50
+ update_metadata(test_file, metadata, metadata_format=MetadataFormat.VORBIS)
51
+
52
+ raw_output = VorbisMetadataGetter.get_raw_metadata(test_file)
53
+ assert "ARTIST=ExistingLower 1" in raw_output
54
+ assert "ARTIST=New 2" in raw_output
@@ -0,0 +1,42 @@
1
+ import pytest
2
+
3
+ from audiometa import get_unified_metadata_field, update_metadata
4
+ from audiometa.exceptions import InvalidMetadataFieldTypeError
5
+ from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
6
+ from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
7
+
8
+
9
+ @pytest.mark.integration
10
+ class TestMultipleValuesErrorHandling:
11
+ def test_write_invalid_data_types_in_list(self):
12
+ # Test with invalid data types in multiple value lists
13
+ with temp_file_with_metadata({}, "mp3") as temp_audio_file_path:
14
+ metadata = {UnifiedMetadataKey.ARTISTS: [1, 2, 3]} # Numbers instead of strings
15
+ with pytest.raises(InvalidMetadataFieldTypeError):
16
+ update_metadata(temp_audio_file_path, metadata)
17
+
18
+ def test_write_mixed_data_types_in_list(self):
19
+ # Test with mixed data types in multiple value lists
20
+ with temp_file_with_metadata({}, "mp3") as temp_audio_file_path:
21
+ metadata = {UnifiedMetadataKey.ARTISTS: ["Artist One", 123, None, "Artist Two"]}
22
+ with pytest.raises(InvalidMetadataFieldTypeError):
23
+ update_metadata(temp_audio_file_path, metadata)
24
+
25
+ def test_write_list_with_none_values_are_filtered(self):
26
+ # Test that None values in lists are automatically filtered out
27
+ # If all values are None, the field should be removed entirely
28
+ with temp_file_with_metadata({}, "mp3") as temp_audio_file_path:
29
+ metadata = {UnifiedMetadataKey.ARTISTS: [None, None]}
30
+ update_metadata(temp_audio_file_path, metadata)
31
+ # Field should be removed (None) since all values were filtered out
32
+ artists = get_unified_metadata_field(temp_audio_file_path, UnifiedMetadataKey.ARTISTS)
33
+ assert artists is None
34
+
35
+ def test_write_list_with_mixed_none_and_valid_values(self):
36
+ # Test that None values are filtered but valid values remain
37
+ with temp_file_with_metadata({}, "mp3") as temp_audio_file_path:
38
+ metadata = {UnifiedMetadataKey.ARTISTS: ["Artist One", None, "Artist Two", None, "Artist Three"]}
39
+ update_metadata(temp_audio_file_path, metadata)
40
+ # None values should be filtered out, only valid artists remain
41
+ artists = get_unified_metadata_field(temp_audio_file_path, UnifiedMetadataKey.ARTISTS)
42
+ assert artists == ["Artist One", "Artist Two", "Artist Three"]
@@ -0,0 +1,98 @@
1
+ import time
2
+
3
+ import pytest
4
+
5
+ from audiometa import get_unified_metadata_field, update_metadata
6
+ from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
7
+ from audiometa.utils.metadata_format import MetadataFormat
8
+ from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
9
+
10
+
11
+ @pytest.mark.integration
12
+ class TestMultipleValuesBoundaryConditions:
13
+ def test_write_large_number_of_multiple_values_per_field(self):
14
+ with temp_file_with_metadata({"title": "Test Song"}, "flac") as test_file:
15
+ # Test with very large number of values per field
16
+ large_values_number = 1000
17
+ large_artist_list = [f"Artist {i:04d}" for i in range(large_values_number)]
18
+
19
+ metadata = {
20
+ UnifiedMetadataKey.ARTISTS: large_artist_list,
21
+ }
22
+
23
+ start_time = time.time()
24
+ update_metadata(test_file, metadata, metadata_format=MetadataFormat.VORBIS)
25
+ write_time = time.time() - start_time
26
+
27
+ artists = get_unified_metadata_field(
28
+ test_file, UnifiedMetadataKey.ARTISTS, metadata_format=MetadataFormat.VORBIS
29
+ )
30
+
31
+ assert isinstance(artists, list)
32
+ assert len(artists) == large_values_number
33
+
34
+ # Performance should be reasonable
35
+ assert write_time < 10.0, f"Write took too long: {write_time:.2f}s"
36
+
37
+ def test_write_extremely_long_individual_values(self):
38
+ with temp_file_with_metadata({"title": "Test Song"}, "flac") as test_file:
39
+ # Test with extremely long individual values
40
+ very_long_string = "A" * 50000 # 50KB string
41
+ metadata = {
42
+ UnifiedMetadataKey.ARTISTS: [very_long_string, "Normal Artist"],
43
+ UnifiedMetadataKey.COMMENT: very_long_string,
44
+ }
45
+
46
+ update_metadata(test_file, metadata, metadata_format=MetadataFormat.VORBIS)
47
+
48
+ artists = get_unified_metadata_field(
49
+ test_file, UnifiedMetadataKey.ARTISTS, metadata_format=MetadataFormat.VORBIS
50
+ )
51
+ assert isinstance(artists, list)
52
+ assert len(artists) == 2
53
+ assert very_long_string in artists
54
+ assert "Normal Artist" in artists
55
+
56
+ def test_write_mixed_length_values(self):
57
+ with temp_file_with_metadata({"title": "Test Song"}, "flac") as test_file:
58
+ # Test with mixed length values
59
+ mixed_lengths = [
60
+ "A", # 1 character
61
+ "AB", # 2 characters
62
+ "ABC", # 3 characters
63
+ "A" * 100, # 100 characters
64
+ "A" * 1000, # 1000 characters
65
+ "A" * 10000, # 10000 characters
66
+ ]
67
+ metadata = {UnifiedMetadataKey.ARTISTS: mixed_lengths}
68
+ update_metadata(test_file, metadata, metadata_format=MetadataFormat.VORBIS)
69
+
70
+ artists = get_unified_metadata_field(test_file, UnifiedMetadataKey.ARTISTS)
71
+
72
+ assert isinstance(artists, list)
73
+ assert len(artists) == 6
74
+ for value in mixed_lengths:
75
+ assert value in artists
76
+
77
+ def test_write_very_large_metadata_dict(self):
78
+ with temp_file_with_metadata({"title": "Test Song"}, "flac") as test_file:
79
+ # Test with very large metadata dictionary
80
+ large_metadata = {}
81
+
82
+ # Add multiple values for each supported multiple-value field
83
+ # Actual implementation of Vorbis metadata cannot support more than 50 values per field
84
+ for i in range(50):
85
+ large_metadata[UnifiedMetadataKey.ARTISTS] = [f"Artist {i:04d}" for i in range(50)]
86
+ start_time = time.time()
87
+ update_metadata(test_file, large_metadata, metadata_format=MetadataFormat.VORBIS)
88
+ write_time = time.time() - start_time
89
+
90
+ artists = get_unified_metadata_field(
91
+ test_file, UnifiedMetadataKey.ARTISTS, metadata_format=MetadataFormat.VORBIS
92
+ )
93
+
94
+ assert isinstance(artists, list)
95
+ assert len(artists) == 50
96
+
97
+ # Performance should be reasonable
98
+ assert write_time < 15.0, f"Write took too long: {write_time:.2f}s"
@@ -0,0 +1 @@
1
+ """Integration tests for reading metadata."""