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,90 @@
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 TestCLIRatingValidation:
11
+ def test_cli_invalid_rating_value_negative(self):
12
+ with temp_file_with_metadata({}, "mp3") as temp_file_path:
13
+ result = subprocess.run(
14
+ [sys.executable, "-m", "audiometa", "write", str(temp_file_path), "--rating", "-5"],
15
+ capture_output=True,
16
+ text=True,
17
+ check=False,
18
+ )
19
+
20
+ # Should fail due to negative rating - CLI validates explicitly
21
+ assert result.returncode != 0
22
+ stderr_output = result.stderr.lower()
23
+ assert "error" in stderr_output
24
+ assert "rating" in stderr_output
25
+
26
+ def test_cli_rating_value_allowed_without_normalization(self):
27
+ with temp_file_with_metadata({}, "mp3") as temp_file_path:
28
+ # Any integer rating value should be allowed when normalized_rating_max_value is not provided
29
+ # Using a valid ID3v2 profile value (196 = 4 stars in BASE_255_NON_PROPORTIONAL profile)
30
+ result = subprocess.run(
31
+ [sys.executable, "-m", "audiometa", "write", str(temp_file_path), "--rating", "196"],
32
+ capture_output=True,
33
+ text=True,
34
+ check=False,
35
+ )
36
+
37
+ # Should succeed - no write profile validation when normalized_rating_max_value is None
38
+ assert result.returncode == 0
39
+
40
+ def test_cli_rating_whole_number_float_allowed(self):
41
+ with temp_file_with_metadata({}, "mp3") as temp_file_path:
42
+ # Whole-number floats like 196.0 should be accepted and converted to integers
43
+ result = subprocess.run(
44
+ [sys.executable, "-m", "audiometa", "write", str(temp_file_path), "--rating", "196.0"],
45
+ capture_output=True,
46
+ text=True,
47
+ check=False,
48
+ )
49
+
50
+ # Should succeed - whole-number floats are converted to integers in raw mode
51
+ assert result.returncode == 0
52
+
53
+ def test_cli_rating_value_non_multiple_of_10_allowed(self):
54
+ with temp_file_with_metadata({}, "mp3") as temp_file_path:
55
+ result = subprocess.run(
56
+ [sys.executable, "-m", "audiometa", "write", str(temp_file_path), "--rating", "37"],
57
+ capture_output=True,
58
+ text=True,
59
+ check=False,
60
+ )
61
+
62
+ # Should succeed - no write profile validation when normalized_rating_max_value is None
63
+ assert result.returncode == 0
64
+
65
+ def test_cli_invalid_rating_value_non_numeric(self):
66
+ with temp_file_with_metadata({}, "mp3") as temp_file_path:
67
+ result = subprocess.run(
68
+ [sys.executable, "-m", "audiometa", "write", str(temp_file_path), "--rating", "not-a-number"],
69
+ capture_output=True,
70
+ text=True,
71
+ check=False,
72
+ )
73
+
74
+ # Should fail due to non-numeric rating
75
+ assert result.returncode != 0
76
+ stderr_output = result.stderr.lower()
77
+ assert "invalid" in stderr_output.lower() or "error" in stderr_output
78
+
79
+ def test_cli_valid_rating_multiple_of_10(self):
80
+ with temp_file_with_metadata({}, "mp3") as temp_file_path:
81
+ result = subprocess.run(
82
+ [sys.executable, "-m", "audiometa", "write", str(temp_file_path), "--rating", "128"],
83
+ capture_output=True,
84
+ text=True,
85
+ check=False,
86
+ )
87
+
88
+ # Should succeed - any integer rating value is allowed
89
+ assert result.returncode == 0
90
+ assert "updated metadata" in result.stdout.lower()
@@ -0,0 +1,51 @@
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 TestCLIYearValidation:
11
+ def test_cli_invalid_year_value_non_numeric(self):
12
+ with temp_file_with_metadata({}, "mp3") as temp_file_path:
13
+ result = subprocess.run(
14
+ [sys.executable, "-m", "audiometa", "write", str(temp_file_path), "--year", "not-a-year"],
15
+ capture_output=True,
16
+ text=True,
17
+ check=False,
18
+ )
19
+
20
+ # Should fail due to non-numeric year
21
+ assert result.returncode != 0
22
+ stderr_output = result.stderr.lower()
23
+ assert "invalid" in stderr_output.lower() or "error" in stderr_output
24
+
25
+ def test_cli_invalid_year_value_negative(self):
26
+ with temp_file_with_metadata({}, "mp3") as temp_file_path:
27
+ result = subprocess.run(
28
+ [sys.executable, "-m", "audiometa", "write", str(temp_file_path), "--year", "-2023"],
29
+ capture_output=True,
30
+ text=True,
31
+ check=False,
32
+ )
33
+
34
+ # Should fail due to invalid date format (negative year doesn't match YYYY format)
35
+ assert result.returncode != 0
36
+ stderr_output = result.stderr.lower()
37
+ assert "error" in stderr_output or "invalid" in stderr_output
38
+
39
+ def test_cli_valid_year_value_future(self):
40
+ with temp_file_with_metadata({}, "mp3") as temp_file_path:
41
+ future_year = str(2030 + 1) # Future year is valid
42
+ result = subprocess.run(
43
+ [sys.executable, "-m", "audiometa", "write", str(temp_file_path), "--year", future_year],
44
+ capture_output=True,
45
+ text=True,
46
+ check=False,
47
+ )
48
+
49
+ # Should succeed - future years are allowed
50
+ assert result.returncode == 0
51
+ assert "updated metadata" in result.stdout.lower()
File without changes
@@ -0,0 +1,58 @@
1
+ import json
2
+ import subprocess
3
+ import sys
4
+
5
+ import pytest
6
+
7
+ from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
8
+
9
+
10
+ @pytest.mark.e2e
11
+ class TestCLIReadBasic:
12
+ def test_cli_read_nonexistent_file(self, tmp_path):
13
+ nonexistent_file = tmp_path / "nonexistent.mp3"
14
+ result = subprocess.run(
15
+ [sys.executable, "-m", "audiometa", "read", str(nonexistent_file)],
16
+ capture_output=True,
17
+ text=True,
18
+ check=False,
19
+ )
20
+ assert result.returncode == 1
21
+ assert "error" in result.stderr.lower()
22
+
23
+ def test_cli_read_with_continue_on_error(self, tmp_path):
24
+ nonexistent_file = tmp_path / "nonexistent.mp3"
25
+ result = subprocess.run(
26
+ [sys.executable, "-m", "audiometa", "read", str(nonexistent_file), "--continue-on-error"],
27
+ capture_output=True,
28
+ text=True,
29
+ check=False,
30
+ )
31
+ assert result.returncode == 0
32
+
33
+ def test_cli_with_spaces_in_filename(self):
34
+ with temp_file_with_metadata({}, "mp3") as test_file:
35
+ result = subprocess.run(
36
+ [sys.executable, "-m", "audiometa", "read", str(test_file), "--format", "json"],
37
+ capture_output=True,
38
+ text=True,
39
+ check=False,
40
+ )
41
+ assert result.returncode == 0
42
+ data = json.loads(result.stdout)
43
+ assert isinstance(data, dict)
44
+ assert "unified_metadata" in data
45
+
46
+ def test_cli_read_basic_metadata(self):
47
+ with temp_file_with_metadata({"title": "Test Title", "artist": "Test Artist"}, "mp3") as test_file:
48
+ result = subprocess.run(
49
+ [sys.executable, "-m", "audiometa", "read", str(test_file), "--format", "json"],
50
+ capture_output=True,
51
+ text=True,
52
+ check=False,
53
+ )
54
+ assert result.returncode == 0
55
+ data = json.loads(result.stdout)
56
+ assert "unified_metadata" in data
57
+ assert data["unified_metadata"].get("title") == "Test Title"
58
+ assert data["unified_metadata"].get("artists") == ["Test Artist"]
@@ -0,0 +1,240 @@
1
+ import json
2
+ import subprocess
3
+ import sys
4
+
5
+ import pytest
6
+
7
+ from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
8
+ from audiometa.utils.unified_metadata_key import UnifiedMetadataKey
9
+
10
+
11
+ @pytest.mark.e2e
12
+ class TestCLIReadComprehensive:
13
+ def test_cli_read_all_fields_comprehensive_mp3(self):
14
+ with temp_file_with_metadata(
15
+ {
16
+ "title": "Comprehensive Test Title",
17
+ "artist": ["Artist One", "Artist Two"],
18
+ "album": "Test Album",
19
+ "album_artist": ["Album Artist"],
20
+ "year": "2024",
21
+ "genre": ["Rock", "Blues"],
22
+ "track": "5/12",
23
+ "disc_number": 1,
24
+ "disc_total": 2,
25
+ "rating": 85,
26
+ "bpm": 120,
27
+ "language": "eng",
28
+ "composer": ["Composer One", "Composer Two"],
29
+ "publisher": "Test Publisher",
30
+ "copyright": "© 2024",
31
+ "lyrics": "Test lyrics",
32
+ "comment": "Test comment",
33
+ "isrc": "USRC17607839",
34
+ },
35
+ "mp3",
36
+ ) as test_file:
37
+ result = subprocess.run(
38
+ [sys.executable, "-m", "audiometa", "read", str(test_file), "--format", "json"],
39
+ capture_output=True,
40
+ text=True,
41
+ check=False,
42
+ )
43
+ assert result.returncode == 0
44
+ data = json.loads(result.stdout)
45
+ unified = data.get("unified_metadata", {})
46
+
47
+ assert unified.get(UnifiedMetadataKey.TITLE) == "Comprehensive Test Title"
48
+ assert unified.get(UnifiedMetadataKey.ARTISTS) == ["Artist One", "Artist Two"]
49
+ assert unified.get(UnifiedMetadataKey.ALBUM) == "Test Album"
50
+ assert unified.get(UnifiedMetadataKey.ALBUM_ARTISTS) == ["Album Artist"]
51
+ assert unified.get(UnifiedMetadataKey.RELEASE_DATE) == "2024"
52
+ assert unified.get(UnifiedMetadataKey.GENRES_NAMES) == ["Rock", "Blues"]
53
+ assert unified.get(UnifiedMetadataKey.TRACK_NUMBER) == "5/12"
54
+ assert unified.get(UnifiedMetadataKey.DISC_NUMBER) == 1
55
+ assert unified.get(UnifiedMetadataKey.DISC_TOTAL) == 2
56
+ assert unified.get(UnifiedMetadataKey.RATING) == 85
57
+ assert unified.get(UnifiedMetadataKey.BPM) == 120
58
+ assert unified.get(UnifiedMetadataKey.LANGUAGE) == "eng"
59
+ assert unified.get(UnifiedMetadataKey.COMPOSERS) == ["Composer One", "Composer Two"]
60
+ assert unified.get(UnifiedMetadataKey.PUBLISHER) == "Test Publisher"
61
+ assert unified.get(UnifiedMetadataKey.COPYRIGHT) == "© 2024"
62
+ assert unified.get(UnifiedMetadataKey.UNSYNCHRONIZED_LYRICS) == "Test lyrics"
63
+ assert unified.get(UnifiedMetadataKey.COMMENT) == "Test comment"
64
+ assert unified.get(UnifiedMetadataKey.ISRC) == "USRC17607839"
65
+ # REPLAYGAIN and ARCHIVAL_LOCATION are not supported by ID3v2 format (MP3)
66
+
67
+ def test_cli_read_all_fields_comprehensive_flac(self):
68
+ with temp_file_with_metadata(
69
+ {
70
+ "title": "FLAC Comprehensive Test",
71
+ "artist": ["FLAC Artist"],
72
+ "album": "FLAC Album",
73
+ "track_number": "3/10",
74
+ "disc_number": 1,
75
+ "disc_total": 2,
76
+ "bpm": 140,
77
+ "language": "eng",
78
+ "composer": ["FLAC Composer"],
79
+ "publisher": "FLAC Publisher",
80
+ "copyright": "© FLAC",
81
+ "lyrics": "FLAC lyrics",
82
+ "comment": "FLAC comment",
83
+ "replaygain": "+2.5 dB",
84
+ "isrc": "FRXXX1800001",
85
+ },
86
+ "flac",
87
+ ) as test_file:
88
+ result = subprocess.run(
89
+ [sys.executable, "-m", "audiometa", "read", str(test_file), "--format", "json"],
90
+ capture_output=True,
91
+ text=True,
92
+ check=False,
93
+ )
94
+ assert result.returncode == 0
95
+ data = json.loads(result.stdout)
96
+ unified = data.get("unified_metadata", {})
97
+
98
+ assert unified.get(UnifiedMetadataKey.TITLE) == "FLAC Comprehensive Test"
99
+ assert unified.get(UnifiedMetadataKey.ARTISTS) == ["FLAC Artist"]
100
+ assert unified.get(UnifiedMetadataKey.ALBUM) == "FLAC Album"
101
+ assert unified.get(UnifiedMetadataKey.TRACK_NUMBER) == "3/10"
102
+ assert unified.get(UnifiedMetadataKey.DISC_NUMBER) == 1
103
+ assert unified.get(UnifiedMetadataKey.DISC_TOTAL) == 2
104
+ assert unified.get(UnifiedMetadataKey.BPM) == 140
105
+ assert unified.get(UnifiedMetadataKey.LANGUAGE) == "eng"
106
+ assert unified.get(UnifiedMetadataKey.COMPOSERS) == ["FLAC Composer"]
107
+ assert unified.get(UnifiedMetadataKey.PUBLISHER) == "FLAC Publisher"
108
+ assert unified.get(UnifiedMetadataKey.COPYRIGHT) == "© FLAC"
109
+ assert unified.get(UnifiedMetadataKey.UNSYNCHRONIZED_LYRICS) == "FLAC lyrics"
110
+ assert unified.get(UnifiedMetadataKey.COMMENT) == "FLAC comment"
111
+ assert unified.get(UnifiedMetadataKey.REPLAYGAIN) == "+2.5 dB"
112
+ assert unified.get(UnifiedMetadataKey.ISRC) == "FRXXX1800001"
113
+ # ARCHIVAL_LOCATION is not supported by Vorbis format (FLAC)
114
+
115
+ def test_cli_read_all_fields_comprehensive_wav(self):
116
+ with temp_file_with_metadata(
117
+ {
118
+ "title": "WAV Comprehensive Test",
119
+ "artist": ["WAV Artist"],
120
+ "album": "WAV Album",
121
+ "year": "2024",
122
+ "genre": ["Rock"],
123
+ "rating": 100,
124
+ "bpm": 120,
125
+ "language": "eng",
126
+ "composer": ["WAV Composer"],
127
+ "copyright": "© WAV",
128
+ "comment": "WAV comment",
129
+ "isrc": "GBUM71505078",
130
+ },
131
+ "wav",
132
+ ) as test_file:
133
+ result = subprocess.run(
134
+ [sys.executable, "-m", "audiometa", "read", str(test_file), "--format", "json"],
135
+ capture_output=True,
136
+ text=True,
137
+ check=False,
138
+ )
139
+ assert result.returncode == 0
140
+ data = json.loads(result.stdout)
141
+ unified = data.get("unified_metadata", {})
142
+
143
+ assert unified.get(UnifiedMetadataKey.TITLE) == "WAV Comprehensive Test"
144
+ assert unified.get(UnifiedMetadataKey.ARTISTS) == ["WAV Artist"]
145
+ assert unified.get(UnifiedMetadataKey.ALBUM) == "WAV Album"
146
+ assert unified.get(UnifiedMetadataKey.RELEASE_DATE) == "2024"
147
+ assert unified.get(UnifiedMetadataKey.GENRES_NAMES) == ["Rock"]
148
+ assert unified.get(UnifiedMetadataKey.RATING) == 100
149
+ assert unified.get(UnifiedMetadataKey.BPM) == 120
150
+ assert unified.get(UnifiedMetadataKey.LANGUAGE) == "eng"
151
+ assert unified.get(UnifiedMetadataKey.COMPOSERS) == ["WAV Composer"]
152
+ assert unified.get(UnifiedMetadataKey.COPYRIGHT) == "© WAV"
153
+ assert unified.get(UnifiedMetadataKey.COMMENT) == "WAV comment"
154
+ assert unified.get(UnifiedMetadataKey.ISRC) == "GBUM71505078"
155
+
156
+ def test_cli_read_comprehensive_roundtrip(self):
157
+ """Test that we can write all fields via CLI and read them back correctly."""
158
+ with temp_file_with_metadata({}, "mp3") as test_file:
159
+ write_result = subprocess.run(
160
+ [
161
+ sys.executable,
162
+ "-m",
163
+ "audiometa",
164
+ "write",
165
+ str(test_file),
166
+ "--title",
167
+ "Roundtrip Test",
168
+ "--artist",
169
+ "Roundtrip Artist One",
170
+ "--artist",
171
+ "Roundtrip Artist Two",
172
+ "--album",
173
+ "Roundtrip Album",
174
+ "--album-artist",
175
+ "Roundtrip Album Artist",
176
+ "--year",
177
+ "2024",
178
+ "--genre",
179
+ "Rock",
180
+ "--genre",
181
+ "Blues",
182
+ "--track-number",
183
+ "5/12",
184
+ "--disc-number",
185
+ "1",
186
+ "--disc-total",
187
+ "2",
188
+ "--rating",
189
+ "85",
190
+ "--bpm",
191
+ "120",
192
+ "--language",
193
+ "eng",
194
+ "--composer",
195
+ "Roundtrip Composer",
196
+ "--publisher",
197
+ "Roundtrip Publisher",
198
+ "--copyright",
199
+ "© Roundtrip",
200
+ "--lyrics",
201
+ "Roundtrip lyrics",
202
+ "--comment",
203
+ "Roundtrip comment",
204
+ "--isrc",
205
+ "USRC17607839",
206
+ ],
207
+ capture_output=True,
208
+ text=True,
209
+ check=False,
210
+ )
211
+ assert write_result.returncode == 0
212
+
213
+ read_result = subprocess.run(
214
+ [sys.executable, "-m", "audiometa", "read", str(test_file), "--format", "json"],
215
+ capture_output=True,
216
+ text=True,
217
+ check=False,
218
+ )
219
+ assert read_result.returncode == 0
220
+ data = json.loads(read_result.stdout)
221
+ unified = data.get("unified_metadata", {})
222
+
223
+ assert unified.get(UnifiedMetadataKey.TITLE) == "Roundtrip Test"
224
+ assert unified.get(UnifiedMetadataKey.ARTISTS) == ["Roundtrip Artist One", "Roundtrip Artist Two"]
225
+ assert unified.get(UnifiedMetadataKey.ALBUM) == "Roundtrip Album"
226
+ assert unified.get(UnifiedMetadataKey.ALBUM_ARTISTS) == ["Roundtrip Album Artist"]
227
+ assert unified.get(UnifiedMetadataKey.RELEASE_DATE) == "2024"
228
+ assert unified.get(UnifiedMetadataKey.GENRES_NAMES) == ["Rock", "Blues"]
229
+ assert unified.get(UnifiedMetadataKey.TRACK_NUMBER) == "5/12"
230
+ assert unified.get(UnifiedMetadataKey.DISC_NUMBER) == 1
231
+ assert unified.get(UnifiedMetadataKey.DISC_TOTAL) == 2
232
+ assert unified.get(UnifiedMetadataKey.RATING) == 85
233
+ assert unified.get(UnifiedMetadataKey.BPM) == 120
234
+ assert unified.get(UnifiedMetadataKey.LANGUAGE) == "eng"
235
+ assert unified.get(UnifiedMetadataKey.COMPOSERS) == ["Roundtrip Composer"]
236
+ assert unified.get(UnifiedMetadataKey.PUBLISHER) == "Roundtrip Publisher"
237
+ assert unified.get(UnifiedMetadataKey.COPYRIGHT) == "© Roundtrip"
238
+ assert unified.get(UnifiedMetadataKey.UNSYNCHRONIZED_LYRICS) == "Roundtrip lyrics"
239
+ assert unified.get(UnifiedMetadataKey.COMMENT) == "Roundtrip comment"
240
+ assert unified.get(UnifiedMetadataKey.ISRC) == "USRC17607839"
@@ -0,0 +1,55 @@
1
+ import json
2
+ import subprocess
3
+ import sys
4
+
5
+ import pytest
6
+
7
+
8
+ @pytest.mark.e2e
9
+ class TestCLIReadFormats:
10
+ def test_cli_read_output_formats_json(self, sample_mp3_file):
11
+ result = subprocess.run(
12
+ [sys.executable, "-m", "audiometa", "read", str(sample_mp3_file), "--format", "json"],
13
+ capture_output=True,
14
+ text=True,
15
+ check=False,
16
+ )
17
+ assert result.returncode == 0
18
+ data = json.loads(result.stdout)
19
+ assert "unified_metadata" in data
20
+
21
+ def test_cli_read_output_formats_table(self, sample_mp3_file):
22
+ result = subprocess.run(
23
+ [sys.executable, "-m", "audiometa", "read", str(sample_mp3_file), "--format", "table"],
24
+ capture_output=True,
25
+ text=True,
26
+ check=False,
27
+ )
28
+ assert result.returncode == 0
29
+ assert "UNIFIED METADATA" in result.stdout or "TECHNICAL INFO" in result.stdout
30
+
31
+ def test_cli_read_output_formats_yaml(self, sample_mp3_file):
32
+ result = subprocess.run(
33
+ [sys.executable, "-m", "audiometa", "read", str(sample_mp3_file), "--format", "yaml"],
34
+ capture_output=True,
35
+ text=True,
36
+ check=False,
37
+ )
38
+ assert result.returncode == 0
39
+ assert len(result.stdout.strip()) > 0
40
+
41
+ def test_cli_output_to_file(self, sample_mp3_file, tmp_path):
42
+ output_file = tmp_path / "metadata.json"
43
+ result = subprocess.run(
44
+ [sys.executable, "-m", "audiometa", "read", str(sample_mp3_file), "--output", str(output_file)],
45
+ capture_output=True,
46
+ text=True,
47
+ check=False,
48
+ )
49
+ assert result.returncode == 0
50
+ assert output_file.exists()
51
+
52
+ with output_file.open() as f:
53
+ data = json.load(f)
54
+ assert isinstance(data, dict)
55
+ assert "unified_metadata" in data
@@ -0,0 +1,164 @@
1
+ import json
2
+ import subprocess
3
+ import sys
4
+
5
+ import pytest
6
+
7
+ from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
8
+
9
+
10
+ @pytest.mark.e2e
11
+ class TestCLIReadMetadataContent:
12
+ def test_cli_read_all_metadata_fields_mp3(self):
13
+ with temp_file_with_metadata(
14
+ {
15
+ "title": "Test Title",
16
+ "artist": "Test Artist",
17
+ "album": "Test Album",
18
+ "album_artist": "Test Album Artist",
19
+ "genre": "Rock",
20
+ "year": "2024",
21
+ "track": "5/12",
22
+ "disc_number": 1,
23
+ "disc_total": 2,
24
+ "rating": 85,
25
+ "bpm": 120,
26
+ "language": "eng",
27
+ "composer": "Test Composer",
28
+ "publisher": "Test Publisher",
29
+ "copyright": "© 2024",
30
+ "comment": "Test comment",
31
+ },
32
+ "mp3",
33
+ ) as test_file:
34
+ result = subprocess.run(
35
+ [sys.executable, "-m", "audiometa", "read", str(test_file), "--format", "json"],
36
+ capture_output=True,
37
+ text=True,
38
+ check=False,
39
+ )
40
+ assert result.returncode == 0
41
+ data = json.loads(result.stdout)
42
+ unified = data.get("unified_metadata", {})
43
+ assert unified.get("title") == "Test Title"
44
+ assert unified.get("artists") == ["Test Artist"]
45
+ assert unified.get("album") == "Test Album"
46
+ assert unified.get("album_artists") == ["Test Album Artist"]
47
+ assert unified.get("genres_names") == ["Rock"]
48
+ assert unified.get("release_date") == "2024"
49
+ assert unified.get("track_number") == "5/12"
50
+ assert unified.get("disc_number") == 1
51
+ assert unified.get("disc_total") == 2
52
+ assert unified.get("rating") == 85
53
+ assert unified.get("bpm") == 120
54
+ assert unified.get("language") == "eng"
55
+ assert unified.get("composer") == ["Test Composer"]
56
+ assert unified.get("publisher") == "Test Publisher"
57
+ assert unified.get("copyright") == "© 2024"
58
+ assert unified.get("comment") == "Test comment"
59
+
60
+ def test_cli_read_all_metadata_fields_flac(self):
61
+ with temp_file_with_metadata(
62
+ {
63
+ "title": "FLAC Title",
64
+ "artist": "FLAC Artist",
65
+ "album": "FLAC Album",
66
+ "track_number": "3/10",
67
+ "disc_number": 1,
68
+ "disc_total": 2,
69
+ "bpm": 140,
70
+ "language": "eng",
71
+ "composer": "FLAC Composer",
72
+ "publisher": "FLAC Publisher",
73
+ "copyright": "© FLAC",
74
+ "lyrics": "FLAC lyrics",
75
+ "comment": "FLAC comment",
76
+ },
77
+ "flac",
78
+ ) as test_file:
79
+ result = subprocess.run(
80
+ [sys.executable, "-m", "audiometa", "read", str(test_file), "--format", "json"],
81
+ capture_output=True,
82
+ text=True,
83
+ check=False,
84
+ )
85
+ assert result.returncode == 0
86
+ data = json.loads(result.stdout)
87
+ unified = data.get("unified_metadata", {})
88
+ assert unified.get("title") == "FLAC Title"
89
+ assert unified.get("artists") == ["FLAC Artist"]
90
+ assert unified.get("album") == "FLAC Album"
91
+ assert unified.get("track_number") == "3/10"
92
+ assert unified.get("disc_number") == 1
93
+ assert unified.get("disc_total") == 2
94
+ assert unified.get("bpm") == 140
95
+ assert unified.get("language") == "eng"
96
+ assert unified.get("composer") == ["FLAC Composer"]
97
+ assert unified.get("publisher") == "FLAC Publisher"
98
+ assert unified.get("copyright") == "© FLAC"
99
+ assert unified.get("unsynchronized_lyrics") == "FLAC lyrics"
100
+ assert unified.get("comment") == "FLAC comment"
101
+
102
+ def test_cli_read_all_metadata_fields_wav(self):
103
+ with temp_file_with_metadata(
104
+ {
105
+ "title": "WAV Title",
106
+ "artist": "WAV Artist",
107
+ "album": "WAV Album",
108
+ "year": "2024",
109
+ "genre": "Rock",
110
+ "rating": 100,
111
+ "bpm": 120,
112
+ "language": "eng",
113
+ "composer": "WAV Composer",
114
+ "copyright": "© WAV",
115
+ "comment": "WAV comment",
116
+ },
117
+ "wav",
118
+ ) as test_file:
119
+ result = subprocess.run(
120
+ [sys.executable, "-m", "audiometa", "read", str(test_file), "--format", "json"],
121
+ capture_output=True,
122
+ text=True,
123
+ check=False,
124
+ )
125
+ assert result.returncode == 0
126
+ data = json.loads(result.stdout)
127
+ unified = data.get("unified_metadata", {})
128
+ assert unified.get("title") == "WAV Title"
129
+ assert unified.get("artists") == ["WAV Artist"]
130
+ assert unified.get("album") == "WAV Album"
131
+ assert unified.get("release_date") == "2024"
132
+ assert unified.get("genres_names") == ["Rock"]
133
+ assert unified.get("rating") == 100
134
+ assert unified.get("bpm") == 120
135
+ assert unified.get("language") == "eng"
136
+ assert unified.get("composer") == ["WAV Composer"]
137
+ assert unified.get("copyright") == "© WAV"
138
+ assert unified.get("comment") == "WAV comment"
139
+
140
+ def test_cli_read_multiple_artists(self):
141
+ with temp_file_with_metadata({"artist": ["Artist One", "Artist Two", "Artist Three"]}, "mp3") as test_file:
142
+ result = subprocess.run(
143
+ [sys.executable, "-m", "audiometa", "read", str(test_file), "--format", "json"],
144
+ capture_output=True,
145
+ text=True,
146
+ check=False,
147
+ )
148
+ assert result.returncode == 0
149
+ data = json.loads(result.stdout)
150
+ unified = data.get("unified_metadata", {})
151
+ assert unified.get("artists") == ["Artist One", "Artist Two", "Artist Three"]
152
+
153
+ def test_cli_read_multiple_genres(self):
154
+ with temp_file_with_metadata({"genre": ["Rock", "Blues", "Jazz"]}, "mp3") as test_file:
155
+ result = subprocess.run(
156
+ [sys.executable, "-m", "audiometa", "read", str(test_file), "--format", "json"],
157
+ capture_output=True,
158
+ text=True,
159
+ check=False,
160
+ )
161
+ assert result.returncode == 0
162
+ data = json.loads(result.stdout)
163
+ unified = data.get("unified_metadata", {})
164
+ assert unified.get("genres_names") == ["Rock", "Blues", "Jazz"]