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,343 @@
1
+ """Unit tests for RIFF metadata manager header information methods."""
2
+
3
+ from pathlib import Path
4
+
5
+ import pytest
6
+
7
+ from audiometa._audio_file import _AudioFile
8
+ from audiometa.manager._rating_supporting.riff._RiffManager import _RiffManager as RiffManager
9
+ from audiometa.test.helpers.riff.riff_metadata_getter import RIFFMetadataGetter
10
+ from audiometa.test.helpers.riff.riff_metadata_setter import RIFFMetadataSetter
11
+ from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
12
+
13
+
14
+ @pytest.mark.unit
15
+ class TestRiffHeaderMethods:
16
+ """Test cases for RIFF metadata manager header information methods."""
17
+
18
+ def test_riff_manager_header_info(self, sample_wav_file: Path):
19
+ """Test RiffManager header info method."""
20
+ audio_file = _AudioFile(sample_wav_file)
21
+ manager = RiffManager(audio_file)
22
+
23
+ header_info = manager.get_header_info()
24
+
25
+ # Should have RIFF specific structure
26
+ assert "present" in header_info
27
+ assert "chunk_info" in header_info
28
+
29
+ # Should be valid structure
30
+ assert isinstance(header_info["present"], bool)
31
+ assert isinstance(header_info["chunk_info"], dict)
32
+
33
+ # Chunk info should have expected keys
34
+ chunk_info = header_info["chunk_info"]
35
+ if header_info["present"]:
36
+ assert "riff_chunk_size" in chunk_info
37
+ assert "info_chunk_size" in chunk_info
38
+ assert "audio_format" in chunk_info
39
+ assert "subchunk_size" in chunk_info
40
+
41
+ def test_riff_manager_raw_metadata_info(self, sample_wav_file: Path):
42
+ """Test RiffManager raw metadata info method."""
43
+ audio_file = _AudioFile(sample_wav_file)
44
+ manager = RiffManager(audio_file)
45
+
46
+ raw_info = manager.get_raw_metadata_info()
47
+
48
+ # Should have RIFF specific structure
49
+ assert "raw_data" in raw_info
50
+ assert "parsed_fields" in raw_info
51
+ assert "frames" in raw_info
52
+ assert "comments" in raw_info
53
+ assert "chunk_structure" in raw_info
54
+
55
+ # Should be valid structure
56
+ assert raw_info["raw_data"] is None or isinstance(raw_info["raw_data"], bytes)
57
+ assert isinstance(raw_info["parsed_fields"], dict)
58
+ assert isinstance(raw_info["frames"], dict)
59
+ assert isinstance(raw_info["comments"], dict)
60
+ assert isinstance(raw_info["chunk_structure"], dict)
61
+
62
+ def test_riff_manager_bext_chunk_extraction_with_description(self):
63
+ """Test RIFF manager bext chunk extraction with Description field."""
64
+ with temp_file_with_metadata({}, "wav") as test_file:
65
+ RIFFMetadataSetter.set_bext_description(test_file, "Test Description")
66
+
67
+ # Verify using external tool
68
+ external_bext = RIFFMetadataGetter.get_bext_metadata(test_file)
69
+ assert external_bext.get("Description") == "Test Description"
70
+
71
+ # Verify using our extraction
72
+ audio_file = _AudioFile(test_file)
73
+ manager = RiffManager(audio_file)
74
+ raw_info = manager.get_raw_metadata_info()
75
+
76
+ assert "chunk_structure" in raw_info
77
+ assert "bext" in raw_info["chunk_structure"]
78
+ bext_data = raw_info["chunk_structure"]["bext"]
79
+ assert bext_data["Description"] == "Test Description"
80
+ # Verify our extraction matches external tool
81
+ assert bext_data["Description"] == external_bext.get("Description")
82
+
83
+ def test_riff_manager_bext_chunk_extraction_with_originator(self):
84
+ """Test RIFF manager bext chunk extraction with Originator field."""
85
+ with temp_file_with_metadata({}, "wav") as test_file:
86
+ RIFFMetadataSetter.set_bext_originator(test_file, "Test Originator")
87
+
88
+ # Verify using external tool
89
+ external_bext = RIFFMetadataGetter.get_bext_metadata(test_file)
90
+ assert external_bext.get("Originator") == "Test Originator"
91
+
92
+ # Verify using our extraction
93
+ audio_file = _AudioFile(test_file)
94
+ manager = RiffManager(audio_file)
95
+ raw_info = manager.get_raw_metadata_info()
96
+
97
+ assert "chunk_structure" in raw_info
98
+ assert "bext" in raw_info["chunk_structure"]
99
+ bext_data = raw_info["chunk_structure"]["bext"]
100
+ assert bext_data["Originator"] == "Test Originator"
101
+ # Verify our extraction matches external tool
102
+ assert bext_data["Originator"] == external_bext.get("Originator")
103
+
104
+ def test_riff_manager_bext_chunk_extraction_with_originator_reference(self):
105
+ """Test RIFF manager bext chunk extraction with OriginatorReference field."""
106
+ with temp_file_with_metadata({}, "wav") as test_file:
107
+ RIFFMetadataSetter.set_bext_originator_reference(test_file, "REF-12345")
108
+
109
+ # Verify using external tool
110
+ external_bext = RIFFMetadataGetter.get_bext_metadata(test_file)
111
+ assert external_bext.get("OriginatorReference") == "REF-12345"
112
+
113
+ # Verify using our extraction
114
+ audio_file = _AudioFile(test_file)
115
+ manager = RiffManager(audio_file)
116
+ raw_info = manager.get_raw_metadata_info()
117
+
118
+ assert "chunk_structure" in raw_info
119
+ assert "bext" in raw_info["chunk_structure"]
120
+ bext_data = raw_info["chunk_structure"]["bext"]
121
+ assert bext_data["OriginatorReference"] == "REF-12345"
122
+ assert bext_data["OriginatorReference"] == external_bext.get("OriginatorReference")
123
+
124
+ def test_riff_manager_bext_chunk_extraction_with_origination_date(self):
125
+ """Test RIFF manager bext chunk extraction with OriginationDate field."""
126
+ with temp_file_with_metadata({}, "wav") as test_file:
127
+ RIFFMetadataSetter.set_bext_origination_date(test_file, "2024-01-15")
128
+
129
+ # Verify using external tool
130
+ external_bext = RIFFMetadataGetter.get_bext_metadata(test_file)
131
+ assert external_bext.get("OriginationDate") == "2024-01-15"
132
+
133
+ # Verify using our extraction
134
+ audio_file = _AudioFile(test_file)
135
+ manager = RiffManager(audio_file)
136
+ raw_info = manager.get_raw_metadata_info()
137
+
138
+ assert "chunk_structure" in raw_info
139
+ assert "bext" in raw_info["chunk_structure"]
140
+ bext_data = raw_info["chunk_structure"]["bext"]
141
+ assert bext_data["OriginationDate"] == "2024-01-15"
142
+ assert bext_data["OriginationDate"] == external_bext.get("OriginationDate")
143
+
144
+ def test_riff_manager_bext_chunk_extraction_with_origination_time(self):
145
+ """Test RIFF manager bext chunk extraction with OriginationTime field."""
146
+ with temp_file_with_metadata({}, "wav") as test_file:
147
+ RIFFMetadataSetter.set_bext_origination_time(test_file, "14:30:00")
148
+
149
+ # Verify using external tool
150
+ external_bext = RIFFMetadataGetter.get_bext_metadata(test_file)
151
+ assert external_bext.get("OriginationTime") == "14:30:00"
152
+
153
+ # Verify using our extraction
154
+ audio_file = _AudioFile(test_file)
155
+ manager = RiffManager(audio_file)
156
+ raw_info = manager.get_raw_metadata_info()
157
+
158
+ assert "chunk_structure" in raw_info
159
+ assert "bext" in raw_info["chunk_structure"]
160
+ bext_data = raw_info["chunk_structure"]["bext"]
161
+ assert bext_data["OriginationTime"] == "14:30:00"
162
+ assert bext_data["OriginationTime"] == external_bext.get("OriginationTime")
163
+
164
+ def test_riff_manager_bext_chunk_extraction_with_time_reference(self):
165
+ """Test RIFF manager bext chunk extraction with TimeReference field."""
166
+ with temp_file_with_metadata({}, "wav") as test_file:
167
+ RIFFMetadataSetter.set_bext_time_reference(test_file, 44100)
168
+
169
+ # Verify using external tool
170
+ external_bext = RIFFMetadataGetter.get_bext_metadata(test_file)
171
+ assert external_bext.get("TimeReference") == 44100
172
+
173
+ # Verify using our extraction
174
+ audio_file = _AudioFile(test_file)
175
+ manager = RiffManager(audio_file)
176
+ raw_info = manager.get_raw_metadata_info()
177
+
178
+ assert "chunk_structure" in raw_info
179
+ assert "bext" in raw_info["chunk_structure"]
180
+ bext_data = raw_info["chunk_structure"]["bext"]
181
+ assert bext_data["TimeReference"] == 44100
182
+ assert bext_data["TimeReference"] == external_bext.get("TimeReference")
183
+
184
+ def test_riff_manager_bext_chunk_extraction_with_coding_history(self):
185
+ """Test RIFF manager bext chunk extraction with CodingHistory field."""
186
+ with temp_file_with_metadata({}, "wav") as test_file:
187
+ RIFFMetadataSetter.set_bext_coding_history(test_file, "A=PCM,F=44100,W=16,M=mono,T=PCM")
188
+
189
+ # Verify using external tool
190
+ external_bext = RIFFMetadataGetter.get_bext_metadata(test_file)
191
+ assert external_bext.get("CodingHistory") == "A=PCM,F=44100,W=16,M=mono,T=PCM"
192
+
193
+ # Verify using our extraction
194
+ audio_file = _AudioFile(test_file)
195
+ manager = RiffManager(audio_file)
196
+ raw_info = manager.get_raw_metadata_info()
197
+
198
+ assert "chunk_structure" in raw_info
199
+ assert "bext" in raw_info["chunk_structure"]
200
+ bext_data = raw_info["chunk_structure"]["bext"]
201
+ assert bext_data["CodingHistory"] == "A=PCM,F=44100,W=16,M=mono,T=PCM"
202
+ assert bext_data["CodingHistory"] == external_bext.get("CodingHistory")
203
+
204
+ def test_riff_manager_bext_chunk_extraction_with_multiple_fields(self):
205
+ """Test RIFF manager bext chunk extraction with multiple fields."""
206
+ with temp_file_with_metadata({}, "wav") as test_file:
207
+ RIFFMetadataSetter.set_bext_metadata(
208
+ test_file,
209
+ {
210
+ "Description": "Test Description",
211
+ "Originator": "Test Originator",
212
+ "OriginatorReference": "REF-12345",
213
+ "OriginationDate": "2024-01-15",
214
+ "OriginationTime": "14:30:00",
215
+ "TimeReference": 44100,
216
+ "CodingHistory": "A=PCM,F=44100,W=16,M=mono,T=PCM",
217
+ },
218
+ )
219
+
220
+ # Verify using external tool
221
+ external_bext = RIFFMetadataGetter.get_bext_metadata(test_file)
222
+
223
+ # Verify using our extraction
224
+ audio_file = _AudioFile(test_file)
225
+ manager = RiffManager(audio_file)
226
+ raw_info = manager.get_raw_metadata_info()
227
+
228
+ assert "chunk_structure" in raw_info
229
+ assert "bext" in raw_info["chunk_structure"]
230
+ bext_data = raw_info["chunk_structure"]["bext"]
231
+
232
+ # Verify each field matches external tool
233
+ assert bext_data["Description"] == "Test Description"
234
+ assert bext_data["Description"] == external_bext.get("Description")
235
+ assert bext_data["Originator"] == "Test Originator"
236
+ assert bext_data["Originator"] == external_bext.get("Originator")
237
+ assert bext_data["OriginatorReference"] == "REF-12345"
238
+ assert bext_data["OriginatorReference"] == external_bext.get("OriginatorReference")
239
+ assert bext_data["OriginationDate"] == "2024-01-15"
240
+ assert bext_data["OriginationDate"] == external_bext.get("OriginationDate")
241
+ assert bext_data["OriginationTime"] == "14:30:00"
242
+ assert bext_data["OriginationTime"] == external_bext.get("OriginationTime")
243
+ assert bext_data["TimeReference"] == 44100
244
+ assert bext_data["TimeReference"] == external_bext.get("TimeReference")
245
+ assert bext_data["CodingHistory"] == "A=PCM,F=44100,W=16,M=mono,T=PCM"
246
+ assert bext_data["CodingHistory"] == external_bext.get("CodingHistory")
247
+
248
+ def test_riff_manager_bext_chunk_extraction_without_bext_chunk(self, sample_wav_file: Path):
249
+ """Test that regular WAV files without bext chunk return empty chunk_structure."""
250
+ audio_file = _AudioFile(sample_wav_file)
251
+ manager = RiffManager(audio_file)
252
+ raw_info = manager.get_raw_metadata_info()
253
+
254
+ assert "chunk_structure" in raw_info
255
+ # Regular WAV files without bext chunk should not have bext in chunk_structure
256
+ assert "bext" not in raw_info["chunk_structure"]
257
+
258
+ def test_riff_manager_bext_chunk_extraction_without_info_metadata(self):
259
+ """Test that bext chunk is extracted even when no user-defined RIFF INFO metadata is present.
260
+
261
+ This tests the code path at lines 899-901 in _RiffManager.py that ensures bext chunk
262
+ extraction happens even when raw_clean_metadata is empty (no INFO metadata).
263
+ """
264
+ with temp_file_with_metadata({}, "wav") as test_file:
265
+ # Add bext metadata but no user-defined INFO metadata
266
+ RIFFMetadataSetter.set_bext_description(test_file, "Test Description")
267
+ RIFFMetadataSetter.set_bext_originator(test_file, "Test Originator")
268
+
269
+ # Verify using external tool
270
+ external_bext = RIFFMetadataGetter.get_bext_metadata(test_file)
271
+ assert external_bext.get("Description") == "Test Description"
272
+ assert external_bext.get("Originator") == "Test Originator"
273
+
274
+ # Verify using our extraction
275
+ audio_file = _AudioFile(test_file)
276
+ manager = RiffManager(audio_file)
277
+ raw_info = manager.get_raw_metadata_info()
278
+
279
+ # Should still have bext chunk in chunk_structure regardless of INFO metadata
280
+ assert "chunk_structure" in raw_info
281
+ assert "bext" in raw_info["chunk_structure"]
282
+ bext_data = raw_info["chunk_structure"]["bext"]
283
+ assert bext_data["Description"] == "Test Description"
284
+ assert bext_data["Originator"] == "Test Originator"
285
+ # Verify our extraction matches external tool
286
+ assert bext_data["Description"] == external_bext.get("Description")
287
+ assert bext_data["Originator"] == external_bext.get("Originator")
288
+
289
+ def test_riff_manager_bext_chunk_extraction_with_loudness_metadata(self):
290
+ """Test RIFF manager bext chunk extraction with BWF v2 loudness metadata fields."""
291
+ with temp_file_with_metadata({}, "wav") as test_file:
292
+ # Set bext metadata including loudness fields (requires BWF v2)
293
+ RIFFMetadataSetter.set_bext_metadata(
294
+ test_file,
295
+ {
296
+ "Description": "Test Description",
297
+ "LoudnessValue": -23.0,
298
+ "LoudnessRange": 7.0,
299
+ "MaxTruePeakLevel": -1.5,
300
+ "MaxMomentaryLoudness": -22.0,
301
+ "MaxShortTermLoudness": -22.5,
302
+ },
303
+ )
304
+
305
+ # Verify using external tool
306
+ external_bext = RIFFMetadataGetter.get_bext_metadata(test_file)
307
+ assert external_bext.get("Description") == "Test Description"
308
+ assert external_bext.get("LoudnessValue") == pytest.approx(-23.0, abs=0.1)
309
+ assert external_bext.get("LoudnessRange") == pytest.approx(7.0, abs=0.1)
310
+ assert external_bext.get("MaxTruePeakLevel") == pytest.approx(-1.5, abs=0.1)
311
+ assert external_bext.get("MaxMomentaryLoudness") == pytest.approx(-22.0, abs=0.1)
312
+ assert external_bext.get("MaxShortTermLoudness") == pytest.approx(-22.5, abs=0.1)
313
+
314
+ # Verify using our extraction
315
+ audio_file = _AudioFile(test_file)
316
+ manager = RiffManager(audio_file)
317
+ raw_info = manager.get_raw_metadata_info()
318
+
319
+ assert "chunk_structure" in raw_info
320
+ assert "bext" in raw_info["chunk_structure"]
321
+ bext_data = raw_info["chunk_structure"]["bext"]
322
+
323
+ # Verify version is 2 (BWF v2)
324
+ assert bext_data.get("Version") == 2
325
+
326
+ # Verify loudness fields match external tool
327
+ assert bext_data["Description"] == "Test Description"
328
+ assert bext_data.get("LoudnessValue") == pytest.approx(-23.0, abs=0.1)
329
+ assert bext_data.get("LoudnessRange") == pytest.approx(7.0, abs=0.1)
330
+ assert bext_data.get("MaxTruePeakLevel") == pytest.approx(-1.5, abs=0.1)
331
+ assert bext_data.get("MaxMomentaryLoudness") == pytest.approx(-22.0, abs=0.1)
332
+ assert bext_data.get("MaxShortTermLoudness") == pytest.approx(-22.5, abs=0.1)
333
+
334
+ # Verify our extraction matches external tool
335
+ assert bext_data.get("LoudnessValue") == pytest.approx(external_bext.get("LoudnessValue", 0), abs=0.1)
336
+ assert bext_data.get("LoudnessRange") == pytest.approx(external_bext.get("LoudnessRange", 0), abs=0.1)
337
+ assert bext_data.get("MaxTruePeakLevel") == pytest.approx(external_bext.get("MaxTruePeakLevel", 0), abs=0.1)
338
+ assert bext_data.get("MaxMomentaryLoudness") == pytest.approx(
339
+ external_bext.get("MaxMomentaryLoudness", 0), abs=0.1
340
+ )
341
+ assert bext_data.get("MaxShortTermLoudness") == pytest.approx(
342
+ external_bext.get("MaxShortTermLoudness", 0), abs=0.1
343
+ )
@@ -0,0 +1,53 @@
1
+ """Unit tests for Vorbis metadata manager header information methods."""
2
+
3
+ from pathlib import Path
4
+
5
+ import pytest
6
+
7
+ from audiometa._audio_file import _AudioFile
8
+ from audiometa.manager._rating_supporting.vorbis._VorbisManager import _VorbisManager as VorbisManager
9
+
10
+
11
+ @pytest.mark.unit
12
+ class TestVorbisHeaderMethods:
13
+ """Test cases for Vorbis metadata manager header information methods."""
14
+
15
+ def test_vorbis_manager_header_info(self, sample_flac_file: Path):
16
+ """Test VorbisManager header info method."""
17
+ audio_file = _AudioFile(sample_flac_file)
18
+ manager = VorbisManager(audio_file)
19
+
20
+ header_info = manager.get_header_info()
21
+
22
+ # Should have Vorbis specific structure
23
+ assert "present" in header_info
24
+ assert "vendor_string" in header_info
25
+ assert "comment_count" in header_info
26
+ assert "block_size" in header_info
27
+
28
+ # Should be valid structure
29
+ assert isinstance(header_info["present"], bool)
30
+ assert header_info["vendor_string"] is None or isinstance(header_info["vendor_string"], str)
31
+ assert isinstance(header_info["comment_count"], int)
32
+ assert isinstance(header_info["block_size"], int)
33
+
34
+ def test_vorbis_manager_raw_metadata_info(self, sample_flac_file: Path):
35
+ """Test VorbisManager raw metadata info method."""
36
+ audio_file = _AudioFile(sample_flac_file)
37
+ manager = VorbisManager(audio_file)
38
+
39
+ raw_info = manager.get_raw_metadata_info()
40
+
41
+ # Should have Vorbis specific structure
42
+ assert "raw_data" in raw_info
43
+ assert "parsed_fields" in raw_info
44
+ assert "frames" in raw_info
45
+ assert "comments" in raw_info
46
+ assert "chunk_structure" in raw_info
47
+
48
+ # Should be valid structure
49
+ assert raw_info["raw_data"] is None or isinstance(raw_info["raw_data"], bytes)
50
+ assert isinstance(raw_info["parsed_fields"], dict)
51
+ assert isinstance(raw_info["frames"], dict)
52
+ assert isinstance(raw_info["comments"], dict)
53
+ assert isinstance(raw_info["chunk_structure"], dict)
@@ -0,0 +1,186 @@
1
+ from unittest.mock import MagicMock
2
+
3
+ import pytest
4
+
5
+ from audiometa.manager._MetadataManager import _MetadataManager as MetadataManager
6
+
7
+
8
+ @pytest.mark.unit
9
+ class TestSmartParsing:
10
+ @pytest.mark.parametrize(
11
+ ("values", "expected_should_parse"),
12
+ [
13
+ ([], False),
14
+ ([""], False),
15
+ ([" "], False),
16
+ (["Artist One;Artist Two"], True),
17
+ (["Artist One", "Artist Two"], False),
18
+ (["Artist One//Artist Two"], True),
19
+ ],
20
+ )
21
+ def test_should_apply_smart_parsing(self, values, expected_should_parse):
22
+ audio_file = MagicMock()
23
+ manager = MetadataManager(audio_file, {}, {})
24
+
25
+ result = manager._should_apply_smart_parsing(values)
26
+ assert result == expected_should_parse
27
+
28
+ def test_should_apply_smart_parsing_with_null_separators(self):
29
+ audio_file = MagicMock()
30
+ manager = MetadataManager(audio_file, {}, {})
31
+
32
+ result = manager._should_apply_smart_parsing(["Artist\x00One"])
33
+ assert result is True
34
+
35
+ result = manager._should_apply_smart_parsing(["Artist One", "Artist\x00Two"])
36
+ assert result is True
37
+
38
+ @pytest.mark.parametrize(
39
+ ("values", "expected_parsed"),
40
+ [
41
+ (["Artist One;Artist Two"], ["Artist One", "Artist Two"]),
42
+ (["Artist One//Artist Two"], ["Artist One", "Artist Two"]),
43
+ (["Artist One,Artist Two"], ["Artist One", "Artist Two"]),
44
+ (["Artist One\\Artist Two"], ["Artist One", "Artist Two"]),
45
+ (["Artist One/Artist Two"], ["Artist One", "Artist Two"]),
46
+ (["Artist One;Artist;Three"], ["Artist One", "Artist", "Three"]),
47
+ (["Artist One;Artist Two;Artist Three"], ["Artist One", "Artist Two", "Artist Three"]),
48
+ ([" Artist One "], ["Artist One"]),
49
+ ([""], []),
50
+ ([" "], []),
51
+ (["Artist One"], ["Artist One"]),
52
+ ],
53
+ )
54
+ def test_apply_smart_parsing(self, values, expected_parsed):
55
+ audio_file = MagicMock()
56
+ manager = MetadataManager(audio_file, {}, {})
57
+
58
+ result = manager._apply_smart_parsing(values)
59
+ assert result == expected_parsed
60
+
61
+ def test_apply_smart_parsing_with_null_separators(self):
62
+ audio_file = MagicMock()
63
+ manager = MetadataManager(audio_file, {}, {})
64
+
65
+ result = manager._apply_smart_parsing(["Artist\x00One\x00Two"])
66
+ assert result == ["Artist", "One", "Two"]
67
+
68
+ def test_apply_smart_parsing_mixed_null_and_regular_separators(self):
69
+ audio_file = MagicMock()
70
+ manager = MetadataManager(audio_file, {}, {})
71
+
72
+ # Null separators take priority - split on null first
73
+ result = manager._apply_smart_parsing(["Artist\x00One;Two"])
74
+ assert result == ["Artist", "One;Two"]
75
+
76
+ def test_apply_smart_parsing_multiple_entries_no_separators(self):
77
+ audio_file = MagicMock()
78
+ manager = MetadataManager(audio_file, {}, {})
79
+
80
+ # Multiple entries without separators - only first non-empty entry is parsed
81
+ # This is because _apply_smart_parsing is designed for single-entry legacy data
82
+ result = manager._apply_smart_parsing(["Artist One", "Artist Two"])
83
+ assert result == ["Artist One"]
84
+
85
+ def test_apply_smart_parsing_separator_priority_semicolon_over_comma(self):
86
+ audio_file = MagicMock()
87
+ manager = MetadataManager(audio_file, {}, {})
88
+
89
+ result = manager._apply_smart_parsing(["Artist One;Artist Two,Artist Three"])
90
+ assert result == ["Artist One", "Artist Two,Artist Three"]
91
+
92
+ def test_apply_smart_parsing_separator_priority_double_slash_over_slash(self):
93
+ audio_file = MagicMock()
94
+ manager = MetadataManager(audio_file, {}, {})
95
+
96
+ result = manager._apply_smart_parsing(["Artist One//Artist Two/Artist Three"])
97
+ assert result == ["Artist One", "Artist Two/Artist Three"]
98
+
99
+ def test_unicode_characters(self):
100
+ audio_file = MagicMock()
101
+ manager = MetadataManager(audio_file, {}, {})
102
+
103
+ # Unicode characters in parsed values
104
+ values = ["Artist Café"]
105
+ result = manager._apply_smart_parsing(values)
106
+ assert "Artist Café" in result
107
+
108
+ def test_empty_values_after_separation(self):
109
+ audio_file = MagicMock()
110
+ manager = MetadataManager(audio_file, {}, {})
111
+
112
+ result = manager._apply_smart_parsing(["Artist One;;Artist Two;"])
113
+ assert result == ["Artist One", "Artist Two"]
114
+
115
+ def test_whitespace_around_separators(self):
116
+ audio_file = MagicMock()
117
+ manager = MetadataManager(audio_file, {}, {})
118
+
119
+ result = manager._apply_smart_parsing(["Artist One ; Artist Two ; Artist Three"])
120
+ assert result == ["Artist One", "Artist Two", "Artist Three"]
121
+
122
+ def test_numeric_entries_in_parsed_value(self):
123
+ audio_file = MagicMock()
124
+ manager = MetadataManager(audio_file, {}, {})
125
+
126
+ values = ["Artist 1;Artist 2;123"]
127
+ result = manager._apply_smart_parsing(values)
128
+ assert "Artist 1" in result
129
+ assert "Artist 2" in result
130
+ assert "123" in result
131
+
132
+ def test_case_sensitivity_in_parsed_value(self):
133
+ audio_file = MagicMock()
134
+ manager = MetadataManager(audio_file, {}, {})
135
+
136
+ values = ["Artist One;ARTIST TWO;artist three;ArTiSt FoUr"]
137
+ result = manager._apply_smart_parsing(values)
138
+ assert "Artist One" in result
139
+ assert "ARTIST TWO" in result
140
+ assert "artist three" in result
141
+ assert "ArTiSt FoUr" in result
142
+
143
+ def test_duplicate_entries_in_parsed_value(self):
144
+ audio_file = MagicMock()
145
+ manager = MetadataManager(audio_file, {}, {})
146
+
147
+ values = ["Artist One;Artist Two;Artist One;Artist Three;Artist Two"]
148
+ result = manager._apply_smart_parsing(values)
149
+ assert result.count("Artist One") == 2
150
+ assert result.count("Artist Two") == 2
151
+ assert result.count("Artist Three") == 1
152
+
153
+ def test_order_preservation_in_parsed_value(self):
154
+ audio_file = MagicMock()
155
+ manager = MetadataManager(audio_file, {}, {})
156
+
157
+ values = ["First Artist;Second Artist;Third Artist;Fourth Artist"]
158
+ result = manager._apply_smart_parsing(values)
159
+ assert result[0] == "First Artist"
160
+ assert result[1] == "Second Artist"
161
+ assert result[2] == "Third Artist"
162
+ assert result[3] == "Fourth Artist"
163
+
164
+ def test_very_long_single_entry(self):
165
+ audio_file = MagicMock()
166
+ manager = MetadataManager(audio_file, {}, {})
167
+
168
+ long_artist = "A" * 10000
169
+ result = manager._apply_smart_parsing([long_artist])
170
+ assert result == [long_artist]
171
+ assert len(result[0]) == 10000
172
+
173
+ def test_mixed_empty_values_with_separators(self):
174
+ audio_file = MagicMock()
175
+ manager = MetadataManager(audio_file, {}, {})
176
+
177
+ result = manager._apply_smart_parsing(["Artist One;;;Artist Two;Artist;"])
178
+ assert result == ["Artist One", "Artist Two", "Artist"]
179
+
180
+ def test_multiple_spaces_within_values_preserved(self):
181
+ audio_file = MagicMock()
182
+ manager = MetadataManager(audio_file, {}, {})
183
+
184
+ values = ["Artist One"]
185
+ result = manager._apply_smart_parsing(values)
186
+ assert "Artist One" in result