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,67 @@
1
+ """Vorbis metadata inspection utilities for testing audio file metadata."""
2
+
3
+ import subprocess
4
+ from pathlib import Path
5
+
6
+ from mutagen.flac import FLAC
7
+
8
+ from audiometa.utils.tool_path_resolver import get_tool_path
9
+
10
+
11
+ class VorbisMetadataGetter:
12
+ @staticmethod
13
+ def get_raw_metadata(file_path: Path) -> str:
14
+ result = subprocess.run(
15
+ [get_tool_path("metaflac"), "--list", str(file_path)], capture_output=True, text=True, check=True
16
+ )
17
+ return result.stdout
18
+
19
+ @staticmethod
20
+ def get_raw_metadata_without_truncating_null_bytes_but_lower_case_keys(file_path: Path) -> str:
21
+ audio = FLAC(str(file_path))
22
+ lines = []
23
+ lines.append("METADATA block #0")
24
+ lines.append(" type: 0 (STREAMINFO)")
25
+ lines.append(" is last: false")
26
+ lines.append(" length: 34")
27
+ lines.append(" minimum blocksize: 4096 samples")
28
+ lines.append(" maximum blocksize: 4096 samples")
29
+ lines.append(" minimum framesize: 0 bytes")
30
+ lines.append(" maximum framesize: 0 bytes")
31
+ lines.append(" sample_rate: 48000 Hz")
32
+ lines.append(" channels: 1")
33
+ lines.append(" bits-per-sample: 24")
34
+ lines.append(" total samples: 26177")
35
+ lines.append(" MD5 signature: 07598496b7623dfea10aafb241fae1a8")
36
+ lines.append("METADATA block #1")
37
+ lines.append(" type: 4 (VORBIS_COMMENT)")
38
+ lines.append(" is last: false")
39
+ lines.append(" length: 72")
40
+ lines.append(" vendor string: ")
41
+ comments = []
42
+ if audio.tags is not None and hasattr(audio.tags, "keys"):
43
+ for key in sorted(audio.tags.keys()): # type: ignore[union-attr]
44
+ values = audio.tags[key] # type: ignore[union-attr]
45
+ if isinstance(values, list):
46
+ for value in values:
47
+ comments.append(f" comment[{len(comments)}]: {key}={value}")
48
+ else:
49
+ comments.append(f" comment[{len(comments)}]: {key}={values}")
50
+ lines.append(f" comments: {len(comments)}")
51
+ lines.extend(comments)
52
+ lines.append("METADATA block #2")
53
+ lines.append(" type: 1 (PADDING)")
54
+ lines.append(" is last: true")
55
+ lines.append(" length: 1076")
56
+ return "\n".join(lines)
57
+
58
+ @staticmethod
59
+ def get_title(file_path: Path) -> str:
60
+ result = subprocess.run(
61
+ [get_tool_path("metaflac"), "--show-tag=TITLE", str(file_path)], capture_output=True, text=True, check=True
62
+ )
63
+ # Output is like "TITLE=Song Title\n"
64
+ lines = result.stdout.strip().split("\n")
65
+ if lines and "=" in lines[0]:
66
+ return lines[0].split("=", 1)[1]
67
+ return ""
@@ -0,0 +1,221 @@
1
+ """Vorbis metadata setting operations."""
2
+
3
+ from pathlib import Path
4
+ from typing import Any
5
+
6
+ from mutagen.flac import FLAC
7
+
8
+ from ..common.external_tool_runner import run_external_tool
9
+
10
+
11
+ class VorbisMetadataSetter:
12
+ """Static utility class for Vorbis metadata setting using external tools."""
13
+
14
+ @staticmethod
15
+ def set_multiple_tags(
16
+ file_path: Path, tag_name: str, values: list[str], removing_existing=True, key_lower_case=False
17
+ ) -> None:
18
+ """Set multiple Vorbis comment tags with the same name."""
19
+
20
+ if key_lower_case:
21
+ tag_name = tag_name.lower()
22
+
23
+ if removing_existing:
24
+ try:
25
+ command = ["metaflac", "--remove-tag", tag_name, str(file_path)]
26
+ run_external_tool(command, "metaflac")
27
+ except Exception:
28
+ # Ignore if tags don't exist
29
+ pass
30
+
31
+ # Add each value as a separate tag
32
+ for value in values:
33
+ command = ["metaflac", "--set-tag", f"{tag_name}={value}", str(file_path)]
34
+ run_external_tool(command, "metaflac")
35
+
36
+ @staticmethod
37
+ def set_metadata(file_path: Path, metadata: dict[str, Any]) -> None:
38
+ """Set FLAC metadata using metaflac tool."""
39
+ cmd = ["metaflac"]
40
+
41
+ # Map common metadata keys to metaflac arguments
42
+ key_mapping = {
43
+ "title": "TITLE",
44
+ "artist": "ARTIST",
45
+ "album": "ALBUM",
46
+ "date": "DATE",
47
+ "genre": "GENRE",
48
+ "comment": "COMMENT",
49
+ "track_number": "TRACKNUMBER",
50
+ "bpm": "BPM",
51
+ "composer": "COMPOSER",
52
+ "copyright": "COPYRIGHT",
53
+ "lyrics": "LYRICS",
54
+ "language": "LANGUAGE",
55
+ "rating": "RATING",
56
+ "album_artist": "ALBUMARTIST",
57
+ "mood": "MOOD",
58
+ "key": "KEY",
59
+ "encoder": "ENCODER",
60
+ "url": "URL",
61
+ "isrc": "ISRC",
62
+ "publisher": "PUBLISHER",
63
+ "disc_number": "DISCNUMBER",
64
+ "disc_total": "DISCTOTAL",
65
+ "replaygain": "REPLAYGAIN",
66
+ "archival_location": "ARCHIVAL_LOCATION",
67
+ }
68
+
69
+ # Handle list values first (they need special handling)
70
+ for key, value in metadata.items():
71
+ if isinstance(value, list) and value:
72
+ if key.lower() == "artist":
73
+ VorbisMetadataSetter.set_artists(file_path, value)
74
+ elif key.lower() == "genre":
75
+ VorbisMetadataSetter.set_genres(file_path, value)
76
+ elif key.lower() == "composer":
77
+ VorbisMetadataSetter.set_composers(file_path, value)
78
+ elif key.lower() == "album_artist":
79
+ VorbisMetadataSetter.set_album_artists(file_path, value)
80
+
81
+ # Handle non-list values
82
+ metadata_added = False
83
+ for key, value in metadata.items():
84
+ if key.lower() in key_mapping and not isinstance(value, list):
85
+ cmd.extend([f"--set-tag={key_mapping[key.lower()]}={value}"])
86
+ metadata_added = True
87
+
88
+ # Only run metaflac if metadata was actually added
89
+ if metadata_added:
90
+ cmd.append(str(file_path))
91
+ run_external_tool(cmd, "metaflac")
92
+
93
+ @staticmethod
94
+ def set_genre(file_path: Path, genre_text: str) -> None:
95
+ command = ["metaflac", "--set-tag", f"GENRE={genre_text}", str(file_path)]
96
+ run_external_tool(command, "metaflac")
97
+
98
+ @staticmethod
99
+ def set_comment(file_path: Path, comment: str) -> None:
100
+ command = ["metaflac", "--set-tag", f"COMMENT={comment}", str(file_path)]
101
+ run_external_tool(command, "metaflac")
102
+
103
+ @staticmethod
104
+ def add_title(file_path: Path, title: str, key_lower_case=False) -> None:
105
+ if key_lower_case:
106
+ command = ["metaflac", "--set-tag", f"title={title}", str(file_path)]
107
+ else:
108
+ command = ["metaflac", "--set-tag", f"TITLE={title}", str(file_path)]
109
+ run_external_tool(command, "metaflac")
110
+
111
+ @staticmethod
112
+ def set_artist(file_path: Path, artist: str) -> None:
113
+ command = ["metaflac", "--set-tag", f"ARTIST={artist}", str(file_path)]
114
+ run_external_tool(command, "metaflac")
115
+
116
+ @staticmethod
117
+ def set_album(file_path: Path, album: str) -> None:
118
+ command = ["metaflac", "--set-tag", f"ALBUM={album}", str(file_path)]
119
+ run_external_tool(command, "metaflac")
120
+
121
+ @staticmethod
122
+ def set_lyrics(file_path: Path, lyrics: str) -> None:
123
+ command = ["metaflac", "--set-tag", f"LYRICS={lyrics}", str(file_path)]
124
+ run_external_tool(command, "metaflac")
125
+
126
+ @staticmethod
127
+ def set_language(file_path: Path, language: str) -> None:
128
+ command = ["metaflac", "--set-tag", f"LANGUAGE={language}", str(file_path)]
129
+ run_external_tool(command, "metaflac")
130
+
131
+ @staticmethod
132
+ def set_bpm(file_path: Path, bpm: int) -> None:
133
+ command = ["metaflac", "--set-tag", f"BPM={bpm}", str(file_path)]
134
+ run_external_tool(command, "metaflac")
135
+
136
+ @staticmethod
137
+ def set_release_date(file_path: Path, date_str: str) -> None:
138
+ """Set Vorbis release date using DATE tag."""
139
+ command = ["metaflac", "--set-tag", f"DATE={date_str}", str(file_path)]
140
+ run_external_tool(command, "metaflac")
141
+
142
+ @staticmethod
143
+ def set_max_metadata(file_path: Path) -> None:
144
+ from pathlib import Path
145
+
146
+ from ..common.external_tool_runner import run_script
147
+
148
+ scripts_dir = Path(__file__).parent.parent.parent.parent / "test" / "helpers" / "scripts"
149
+ run_script("set-vorbis-max-metadata.sh", file_path, scripts_dir)
150
+
151
+ @staticmethod
152
+ def set_artists(
153
+ file_path: Path, artists: list[str], removing_existing=True, key_lower_case=False, in_single_entry=False
154
+ ) -> None:
155
+ """Set multiple Vorbis artists using mutagen."""
156
+ audio = FLAC(str(file_path))
157
+ key = "artist" if key_lower_case else "ARTIST"
158
+ if removing_existing:
159
+ existing: list[str] = []
160
+ else:
161
+ existing_raw = audio.get(key, [])
162
+ if existing_raw is None:
163
+ existing = []
164
+ elif isinstance(existing_raw, str):
165
+ existing = [existing_raw]
166
+ else:
167
+ existing = list(existing_raw)
168
+ if in_single_entry:
169
+ value = "\x00".join(artists)
170
+ existing.append(value)
171
+ else:
172
+ existing.extend(artists)
173
+ audio[key] = existing
174
+ audio.save()
175
+
176
+ @staticmethod
177
+ def set_album_artists(file_path: Path, album_artists: list[str]):
178
+ """Set multiple Vorbis album artists using external metaflac tool."""
179
+ VorbisMetadataSetter.set_multiple_tags(file_path, "ALBUMARTIST", album_artists)
180
+
181
+ @staticmethod
182
+ def set_composers(file_path: Path, composers: list[str]):
183
+ """Set multiple Vorbis composers using external metaflac tool."""
184
+ VorbisMetadataSetter.set_multiple_tags(file_path, "COMPOSER", composers)
185
+
186
+ @staticmethod
187
+ def set_genres(file_path: Path, genres: list[str]):
188
+ """Set multiple Vorbis genres using external metaflac tool."""
189
+ VorbisMetadataSetter.set_multiple_tags(file_path, "GENRE", genres)
190
+
191
+ @staticmethod
192
+ def set_performers(file_path: Path, performers: list[str]):
193
+ """Set multiple Vorbis performers using external metaflac tool."""
194
+ VorbisMetadataSetter.set_multiple_tags(file_path, "PERFORMER", performers)
195
+
196
+ @staticmethod
197
+ def set_multiple_comments(file_path: Path, comments: list[str]):
198
+ """Set multiple Vorbis comments using external metaflac tool."""
199
+ VorbisMetadataSetter.set_multiple_tags(file_path, "COMMENT", comments)
200
+
201
+ @staticmethod
202
+ def set_null_bytes_test_metadata(file_path: Path) -> None:
203
+ """Set test metadata including null bytes for testing null byte handling."""
204
+ # First remove existing ARTIST tags
205
+ try:
206
+ command = ["metaflac", "--remove-tag=ARTIST", str(file_path)]
207
+ run_external_tool(command, "metaflac")
208
+ except Exception:
209
+ # Ignore if tags don't exist
210
+ pass
211
+
212
+ # Set ARTIST tags, one with null bytes
213
+ command = ["metaflac", "--set-tag=ARTIST=Artist\x00with\x00nulls", str(file_path)]
214
+ run_external_tool(command, "metaflac")
215
+
216
+ command = ["metaflac", "--set-tag=ARTIST=Normal Artist", str(file_path)]
217
+ run_external_tool(command, "metaflac")
218
+
219
+ # Set TITLE
220
+ command = ["metaflac", "--set-tag=TITLE=Normal Title", str(file_path)]
221
+ run_external_tool(command, "metaflac")
File without changes
@@ -0,0 +1,276 @@
1
+ """Test configuration for audiometa-python tests."""
2
+
3
+ import importlib.util
4
+ import sys
5
+ from pathlib import Path
6
+
7
+ import pytest
8
+
9
+ # Import the shared verification script
10
+ project_root = Path(__file__).parent.parent.parent.parent
11
+ verify_script_path = project_root / "scripts" / "verify-system-dependency-versions.py"
12
+
13
+ # Load the module dynamically
14
+ spec = importlib.util.spec_from_file_location("verify_system_dependency_versions", verify_script_path)
15
+ verify_module = importlib.util.module_from_spec(spec)
16
+ sys.modules["verify_system_dependency_versions"] = verify_module
17
+ spec.loader.exec_module(verify_module) # type: ignore[union-attr]
18
+
19
+ verify_dependency_versions = verify_module.verify_dependency_versions
20
+
21
+
22
+ def pytest_configure(config: pytest.Config) -> None: # noqa: ARG001
23
+ """Verify system dependency versions match pinned versions before running tests."""
24
+ exit_code = verify_dependency_versions()
25
+ if exit_code != 0:
26
+ pytest.exit("Dependency version verification failed. See errors above.", returncode=exit_code)
27
+
28
+
29
+ def pytest_collection_modifyitems(items):
30
+ """Reorder test items to ensure proper execution order: unit → integration → e2e."""
31
+ # Define the desired test execution order based on directory structure
32
+ test_order = {"unit": 1, "integration": 2, "e2e": 3}
33
+
34
+ def get_test_priority(item):
35
+ """Get the priority order for a test item based on its path."""
36
+ test_path = str(item.fspath)
37
+
38
+ # Check for unit tests
39
+ if "/unit/" in test_path:
40
+ return test_order["unit"]
41
+ # Check for integration tests
42
+ if "/integration/" in test_path:
43
+ return test_order["integration"]
44
+ # Check for e2e tests
45
+ if "/e2e/" in test_path:
46
+ return test_order["e2e"]
47
+ # Default priority for other tests (comprehensive, etc.)
48
+ return 0 # Run first (before unit tests)
49
+
50
+ # Sort items by priority
51
+ items.sort(key=get_test_priority)
52
+
53
+
54
+ @pytest.fixture
55
+ def assets_dir() -> Path:
56
+ return Path(__file__).parent.parent.parent / "test" / "assets"
57
+
58
+
59
+ @pytest.fixture
60
+ def sample_mp3_file(assets_dir: Path) -> Path:
61
+ return assets_dir / "sample.mp3"
62
+
63
+
64
+ @pytest.fixture
65
+ def sample_flac_file(assets_dir: Path) -> Path:
66
+ return assets_dir / "sample.flac"
67
+
68
+
69
+ @pytest.fixture
70
+ def sample_wav_file(assets_dir: Path) -> Path:
71
+ return assets_dir / "sample.wav"
72
+
73
+
74
+ @pytest.fixture
75
+ def sample_m4a_file(assets_dir: Path) -> Path:
76
+ return assets_dir / "sample.m4a"
77
+
78
+
79
+ @pytest.fixture
80
+ def metadata_id3v1_big_mp3(assets_dir: Path) -> Path:
81
+ return assets_dir / "metadata=long a_id3v1_big.mp3"
82
+
83
+
84
+ @pytest.fixture
85
+ def metadata_id3v1_small_flac(assets_dir: Path) -> Path:
86
+ return assets_dir / "metadata=long a_id3v1_small.flac"
87
+
88
+
89
+ @pytest.fixture
90
+ def metadata_id3v1_big_flac(assets_dir: Path) -> Path:
91
+ return assets_dir / "metadata=long a_id3v1_big.flac"
92
+
93
+
94
+ @pytest.fixture
95
+ def metadata_id3v1_small_wav(assets_dir: Path) -> Path:
96
+ return assets_dir / "metadata=long a_id3v1_small.wav"
97
+
98
+
99
+ @pytest.fixture
100
+ def metadata_id3v1_big_wav(assets_dir: Path) -> Path:
101
+ return assets_dir / "metadata=long a_id3v1_big.wav"
102
+
103
+
104
+ @pytest.fixture
105
+ def metadata_id3v2_small_mp3(assets_dir: Path) -> Path:
106
+ return assets_dir / "metadata=long a_id3v2_small.mp3"
107
+
108
+
109
+ @pytest.fixture
110
+ def metadata_id3v2_big_mp3(assets_dir: Path) -> Path:
111
+ return assets_dir / "metadata=long a_id3v2_big.mp3"
112
+
113
+
114
+ @pytest.fixture
115
+ def metadata_id3v2_small_flac(assets_dir: Path) -> Path:
116
+ return assets_dir / "metadata=long a_id3v2_small.flac"
117
+
118
+
119
+ @pytest.fixture
120
+ def metadata_id3v2_big_flac(assets_dir: Path) -> Path:
121
+ return assets_dir / "metadata=long a_id3v2_big.flac"
122
+
123
+
124
+ @pytest.fixture
125
+ def metadata_id3v2_small_wav(assets_dir: Path) -> Path:
126
+ return assets_dir / "metadata=long a_id3v2_small.wav"
127
+
128
+
129
+ @pytest.fixture
130
+ def metadata_id3v2_big_wav(assets_dir: Path) -> Path:
131
+ return assets_dir / "metadata=long a_id3v2_big.wav"
132
+
133
+
134
+ @pytest.fixture
135
+ def metadata_id3v2_and_riff_small_wav(assets_dir: Path) -> Path:
136
+ return assets_dir / "metadata=long a_id3v2_and_riff_small.wav"
137
+
138
+
139
+ @pytest.fixture
140
+ def metadata_riff_small_wav(assets_dir: Path) -> Path:
141
+ return assets_dir / "metadata=long a_riff_small.wav"
142
+
143
+
144
+ @pytest.fixture
145
+ def metadata_riff_big_wav(assets_dir: Path) -> Path:
146
+ return assets_dir / "metadata=long a_riff_big.wav"
147
+
148
+
149
+ @pytest.fixture
150
+ def metadata_vorbis_small_flac(assets_dir: Path) -> Path:
151
+ return assets_dir / "metadata=long a_vorbis_small.flac"
152
+
153
+
154
+ @pytest.fixture
155
+ def metadata_vorbis_big_flac(assets_dir: Path) -> Path:
156
+ return assets_dir / "metadata=long a_vorbis_big.flac"
157
+
158
+
159
+ @pytest.fixture
160
+ def artists_one_two_three_comma_id3v2(assets_dir: Path) -> Path:
161
+ return assets_dir / "artists=One Two Three_comma_id3v2.mp3"
162
+
163
+
164
+ @pytest.fixture
165
+ def artists_one_two_three_semicolon_id3v2(assets_dir: Path) -> Path:
166
+ return assets_dir / "artists=One Two Three_semicolon_id3v2.mp3"
167
+
168
+
169
+ @pytest.fixture
170
+ def artists_one_two_three_multi_tags_vorbis(assets_dir: Path) -> Path:
171
+ return assets_dir / "artists=One Two Three_muti tags_vorbis.flac"
172
+
173
+
174
+ @pytest.fixture
175
+ def album_koko_id3v2_mp3(assets_dir: Path) -> Path:
176
+ return assets_dir / "album=koko_id3v2.mp3"
177
+
178
+
179
+ @pytest.fixture
180
+ def album_koko_id3v2_wav(assets_dir: Path) -> Path:
181
+ return assets_dir / "album=koko_id3v2.wav"
182
+
183
+
184
+ @pytest.fixture
185
+ def album_koko_vorbis_flac(assets_dir: Path) -> Path:
186
+ return assets_dir / "album=koko_vorbis.flac"
187
+
188
+
189
+ @pytest.fixture
190
+ def genre_code_id3v1_abstract_mp3(assets_dir: Path) -> Path:
191
+ return assets_dir / "genre_code_id3v1=Abstract.mp3"
192
+
193
+
194
+ @pytest.fixture
195
+ def genre_code_id3v1_unknown_mp3(assets_dir: Path) -> Path:
196
+ return assets_dir / "genre_code_id3v1=Unknown.mp3"
197
+
198
+
199
+ @pytest.fixture
200
+ def duration_1s_mp3(assets_dir: Path) -> Path:
201
+ return assets_dir / "duration=1s.wav"
202
+
203
+
204
+ @pytest.fixture
205
+ def duration_182s_mp3(assets_dir: Path) -> Path:
206
+ return assets_dir / "duration=182.mp3"
207
+
208
+
209
+ @pytest.fixture
210
+ def duration_335s_flac(assets_dir: Path) -> Path:
211
+ return assets_dir / "duration=335s.flac"
212
+
213
+
214
+ @pytest.fixture
215
+ def duration_472s_wav(assets_dir: Path) -> Path:
216
+ return assets_dir / "duration=472s.wav"
217
+
218
+
219
+ @pytest.fixture
220
+ def bitrate_320_mp3(assets_dir: Path) -> Path:
221
+ return assets_dir / "bitrate in kbps_big=320.mp3"
222
+
223
+
224
+ @pytest.fixture
225
+ def bitrate_946_flac(assets_dir: Path) -> Path:
226
+ return assets_dir / "bitrate in kbps_big=946.flac"
227
+
228
+
229
+ @pytest.fixture
230
+ def bitrate_1411_wav(assets_dir: Path) -> Path:
231
+ return assets_dir / "bitrate in kbps_big=1411.wav"
232
+
233
+
234
+ @pytest.fixture
235
+ def bitrate_192_mp3(assets_dir: Path) -> Path:
236
+ return assets_dir / "bitrate in kbps_small=192.mp3"
237
+
238
+
239
+ @pytest.fixture
240
+ def bitrate_723_flac(assets_dir: Path) -> Path:
241
+ return assets_dir / "bitrate in kbps_small=723.flac"
242
+
243
+
244
+ @pytest.fixture
245
+ def bitrate_1152_wav(assets_dir: Path) -> Path:
246
+ return assets_dir / "bitrate in kbps_small=1152.wav"
247
+
248
+
249
+ @pytest.fixture
250
+ def size_small_mp3(assets_dir: Path) -> Path:
251
+ return assets_dir / "size_small=0.01mo.mp3"
252
+
253
+
254
+ @pytest.fixture
255
+ def size_big_mp3(assets_dir: Path) -> Path:
256
+ return assets_dir / "size_big=9.98mo.mp3"
257
+
258
+
259
+ @pytest.fixture
260
+ def size_small_flac(assets_dir: Path) -> Path:
261
+ return assets_dir / "size_small=0.05mo.flac"
262
+
263
+
264
+ @pytest.fixture
265
+ def size_big_flac(assets_dir: Path) -> Path:
266
+ return assets_dir / "size_big=26.6mo.flac"
267
+
268
+
269
+ @pytest.fixture
270
+ def size_small_wav(assets_dir: Path) -> Path:
271
+ return assets_dir / "size_small=0.08mo.wav"
272
+
273
+
274
+ @pytest.fixture
275
+ def size_big_wav(assets_dir: Path) -> Path:
276
+ return assets_dir / "size_big=79.55mo.wav"
File without changes
File without changes
@@ -0,0 +1 @@
1
+ # Error handling tests for CLI
@@ -0,0 +1,77 @@
1
+ import subprocess
2
+ import sys
3
+
4
+ import pytest
5
+
6
+ from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
7
+
8
+
9
+ @pytest.mark.e2e
10
+ class TestCLICommandStructureErrors:
11
+ def test_cli_unified_with_no_headers_technical_flags(self):
12
+ with temp_file_with_metadata({}, "mp3") as temp_file_path:
13
+ result = subprocess.run(
14
+ [sys.executable, "-m", "audiometa", "unified", str(temp_file_path), "--no-headers", "--no-technical"],
15
+ capture_output=True,
16
+ text=True,
17
+ check=False,
18
+ )
19
+
20
+ # Should fail - unified command doesn't accept these flags
21
+ assert result.returncode != 0
22
+ stderr_output = result.stderr.lower()
23
+ assert "unrecognized arguments" in stderr_output
24
+
25
+ def test_cli_read_help_flag(self):
26
+ result = subprocess.run(
27
+ [sys.executable, "-m", "audiometa", "read", "--help"], capture_output=True, text=True, check=False
28
+ )
29
+
30
+ # Should show read command help
31
+ assert result.returncode == 0
32
+ stdout_output = result.stdout.lower()
33
+ assert "read" in stdout_output
34
+ assert "files" in stdout_output
35
+
36
+ def test_cli_recursive_with_single_file(self):
37
+ """Test CLI recursive flag with single file (should work but be redundant)."""
38
+ with temp_file_with_metadata({}, "mp3") as temp_file_path:
39
+ result = subprocess.run(
40
+ [sys.executable, "-m", "audiometa", "read", str(temp_file_path), "--recursive"],
41
+ capture_output=True,
42
+ text=True,
43
+ check=False,
44
+ )
45
+
46
+ # Should succeed - recursive with single file is valid
47
+ assert result.returncode == 0
48
+ assert len(result.stdout.strip()) > 0
49
+
50
+ def test_cli_invalid_command(self):
51
+ result = subprocess.run(
52
+ [sys.executable, "-m", "audiometa", "invalidcommand"], capture_output=True, text=True, check=False
53
+ )
54
+
55
+ # Should fail due to invalid command
56
+ assert result.returncode != 0
57
+ stderr_output = result.stderr.lower()
58
+ assert "invalid choice" in stderr_output or "error" in stderr_output
59
+
60
+ def test_cli_no_command(self):
61
+ result = subprocess.run([sys.executable, "-m", "audiometa"], capture_output=True, text=True, check=False)
62
+
63
+ # Should show help and exit with code 1
64
+ assert result.returncode == 1
65
+ stdout_output = result.stdout.lower()
66
+ assert "usage" in stdout_output or "help" in stdout_output
67
+
68
+ def test_cli_help_flag(self):
69
+ result = subprocess.run(
70
+ [sys.executable, "-m", "audiometa", "--help"], capture_output=True, text=True, check=False
71
+ )
72
+
73
+ # Should show help and exit successfully
74
+ assert result.returncode == 0
75
+ stdout_output = result.stdout.lower()
76
+ assert "usage" in stdout_output
77
+ assert "help" in stdout_output