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,190 @@
1
+ from pathlib import Path
2
+
3
+ import pytest
4
+
5
+ from audiometa._audio_file import _AudioFile
6
+ from audiometa.exceptions import (
7
+ AudioFileMetadataParseError,
8
+ DurationNotFoundError,
9
+ FileByteMismatchError,
10
+ FileCorruptedError,
11
+ FlacMd5CheckFailedError,
12
+ InvalidChunkDecodeError,
13
+ )
14
+
15
+
16
+ @pytest.mark.unit
17
+ class TestAudioFileTechnicalInfoErrorHandling:
18
+ def test_file_byte_mismatch_error_corrupted_flac(self, tmp_path):
19
+ corrupted_flac = tmp_path / "corrupted.flac"
20
+ corrupted_flac.write_bytes(b"fake file")
21
+
22
+ try:
23
+ audio_file = _AudioFile(corrupted_flac)
24
+ audio_file.get_duration_in_sec()
25
+ pytest.fail("Should have raised FileByteMismatchError or FileCorruptedError")
26
+ except (FileByteMismatchError, FileCorruptedError):
27
+ pass
28
+
29
+ def test_flac_md5_check_failed_error_corrupted_flac(self, monkeypatch, sample_flac_file: Path):
30
+ def mock_subprocess_run(args, *_, **_kwargs):
31
+ class MockResult:
32
+ pass
33
+
34
+ result = MockResult()
35
+
36
+ # Handle brew --prefix call from get_tool_path
37
+ if isinstance(args, list) and len(args) > 0 and args[0] == "brew":
38
+ result.stdout = ""
39
+ result.stderr = ""
40
+ result.returncode = 0
41
+ return result
42
+
43
+ # Handle flac -t call - return code 0 but no "ok" message to trigger FlacMd5CheckFailedError
44
+ result.stdout = b""
45
+ result.stderr = b"Some unexpected FLAC output"
46
+ result.returncode = 0
47
+ return result
48
+
49
+ monkeypatch.setattr("subprocess.run", mock_subprocess_run)
50
+
51
+ audio_file = _AudioFile(sample_flac_file)
52
+ try:
53
+ audio_file.is_flac_file_md5_valid()
54
+ pytest.fail("Should have raised FlacMd5CheckFailedError")
55
+ except FlacMd5CheckFailedError:
56
+ pass
57
+
58
+ def test_invalid_chunk_decode_error_corrupted_flac_chunks(self, monkeypatch):
59
+ def mock_get_duration_in_sec(_self):
60
+ try:
61
+ msg = "FLAC chunk decoding failed"
62
+ raise RuntimeError(msg)
63
+ except Exception as exc:
64
+ error_str = str(exc)
65
+ if "file said" in error_str and "bytes, read" in error_str:
66
+ raise FileByteMismatchError(error_str.capitalize()) from exc
67
+ if "FLAC" in error_str or "chunk" in error_str.lower():
68
+ msg = f"Failed to decode FLAC chunks: {error_str}"
69
+ raise InvalidChunkDecodeError(msg) from exc
70
+ raise
71
+
72
+ monkeypatch.setattr("audiometa._audio_file._AudioFile.get_duration_in_sec", mock_get_duration_in_sec)
73
+
74
+ flac_file = "audiometa/test/assets/sample.flac"
75
+
76
+ try:
77
+ audio_file = _AudioFile(flac_file)
78
+ audio_file.get_duration_in_sec()
79
+ pytest.fail("Should have raised InvalidChunkDecodeError")
80
+ except InvalidChunkDecodeError:
81
+ pass
82
+
83
+ def test_duration_not_found_error_invalid_wav_duration(self, monkeypatch):
84
+ def mock_subprocess_run(*_args, **_kwargs):
85
+ class MockResult:
86
+ returncode = 0
87
+ stdout = '{"format": {"duration": "0.0"}, "streams": [{"duration": "0"}]}'
88
+
89
+ return MockResult()
90
+
91
+ monkeypatch.setattr("subprocess.run", mock_subprocess_run)
92
+
93
+ wav_file = "audiometa/test/assets/sample.wav"
94
+
95
+ try:
96
+ audio_file = _AudioFile(wav_file)
97
+ audio_file.get_duration_in_sec()
98
+ pytest.fail("Should have raised DurationNotFoundError")
99
+ except DurationNotFoundError:
100
+ pass
101
+
102
+ def test_audio_file_metadata_parse_error_invalid_json(self, monkeypatch):
103
+ def mock_subprocess_run(*_args, **_kwargs):
104
+ class MockResult:
105
+ returncode = 0
106
+ stdout = "invalid json response"
107
+
108
+ return MockResult()
109
+
110
+ monkeypatch.setattr("subprocess.run", mock_subprocess_run)
111
+
112
+ wav_file = "audiometa/test/assets/sample.wav"
113
+
114
+ try:
115
+ audio_file = _AudioFile(wav_file)
116
+ audio_file.get_duration_in_sec()
117
+ pytest.fail("Should have raised AudioFileMetadataParseError")
118
+ except AudioFileMetadataParseError:
119
+ pass
120
+
121
+ def test_file_corrupted_error_invalid_wav(self, tmp_path):
122
+ invalid_wav = tmp_path / "invalid.wav"
123
+ invalid_wav.write_bytes(b"not a valid wav file")
124
+
125
+ try:
126
+ audio_file = _AudioFile(invalid_wav)
127
+ audio_file.get_duration_in_sec()
128
+ pytest.fail("Should have raised FileCorruptedError")
129
+ except (FileCorruptedError, RuntimeError):
130
+ pass
131
+
132
+ def test_flac_duration_handles_generic_mutagen_exception(self, monkeypatch):
133
+ error_msg = "Some unexpected mutagen error"
134
+
135
+ class MockInfo:
136
+ @property
137
+ def length(self):
138
+ raise ValueError(error_msg)
139
+
140
+ class MockFLAC:
141
+ def __init__(self, *_args, **_kwargs):
142
+ self._info = MockInfo()
143
+
144
+ @property
145
+ def info(self):
146
+ return self._info
147
+
148
+ # Mock FLAC in get_duration_in_sec, not in __init__
149
+ original_get_duration = _AudioFile.get_duration_in_sec
150
+
151
+ def mock_get_duration(self):
152
+ if self.file_extension == ".flac":
153
+ try:
154
+ return float(MockFLAC(self.file_path).info.length)
155
+ except Exception as exc:
156
+ error_str = str(exc)
157
+ if "file said" in error_str and "bytes, read" in error_str:
158
+ raise FileByteMismatchError(error_str.capitalize()) from exc
159
+ if "FLAC" in error_str or "chunk" in error_str.lower():
160
+ msg = f"Failed to decode FLAC chunks: {error_str}"
161
+ raise InvalidChunkDecodeError(msg) from exc
162
+ from audiometa.utils.mutagen_exception_handler import handle_mutagen_exception
163
+
164
+ handle_mutagen_exception("read duration from FLAC file", self.file_path, exc)
165
+ return original_get_duration(self)
166
+
167
+ monkeypatch.setattr(_AudioFile, "get_duration_in_sec", mock_get_duration)
168
+
169
+ flac_file = "audiometa/test/assets/sample.flac"
170
+ audio_file = _AudioFile(flac_file)
171
+
172
+ with pytest.raises(FileCorruptedError) as exc_info:
173
+ audio_file.get_duration_in_sec()
174
+ assert "Failed to read duration from FLAC file" in str(exc_info.value)
175
+ assert error_msg in str(exc_info.value)
176
+
177
+ def test_flac_md5_fixing_handles_generic_exception(self, monkeypatch, sample_flac_file: Path):
178
+ error_msg = "Some unexpected error during MD5 fixing"
179
+
180
+ def mock_subprocess_run(*_args, **_kwargs):
181
+ raise ValueError(error_msg)
182
+
183
+ monkeypatch.setattr("subprocess.run", mock_subprocess_run)
184
+
185
+ audio_file = _AudioFile(sample_flac_file)
186
+
187
+ with pytest.raises(FileCorruptedError) as exc_info:
188
+ audio_file.get_file_with_corrected_md5()
189
+ assert "Failed to fix FLAC MD5 checksum" in str(exc_info.value)
190
+ assert error_msg in str(exc_info.value)
@@ -0,0 +1,51 @@
1
+ from pathlib import Path
2
+
3
+ import pytest
4
+
5
+ from audiometa import get_file_size
6
+ from audiometa.test.helpers.technical_info_inspector import TechnicalInfoInspector
7
+
8
+
9
+ @pytest.mark.unit
10
+ class TestFileSizeFunctions:
11
+ def test_file_size_mp3(self, sample_mp3_file: Path):
12
+ external_tool_size = TechnicalInfoInspector.get_file_size(sample_mp3_file)
13
+ assert external_tool_size == 17307
14
+
15
+ file_size = get_file_size(sample_mp3_file)
16
+ assert file_size == 17307
17
+
18
+ def test_file_size_mp3_big(self, size_big_mp3: Path):
19
+ external_tool_size = TechnicalInfoInspector.get_file_size(size_big_mp3)
20
+ assert external_tool_size == 10468959
21
+
22
+ file_size = get_file_size(size_big_mp3)
23
+ assert file_size == 10468959
24
+
25
+ def test_file_size_flac(self, sample_flac_file: Path):
26
+ external_tool_size = TechnicalInfoInspector.get_file_size(sample_flac_file)
27
+ assert external_tool_size == 18439
28
+
29
+ file_size = get_file_size(sample_flac_file)
30
+ assert file_size == 18439
31
+
32
+ def test_file_size_flac_big(self, size_big_flac: Path):
33
+ external_tool_size = TechnicalInfoInspector.get_file_size(size_big_flac)
34
+ assert external_tool_size == 27888561
35
+
36
+ file_size = get_file_size(size_big_flac)
37
+ assert file_size == 27888561
38
+
39
+ def test_file_size_wav(self, sample_wav_file: Path):
40
+ external_tool_size = TechnicalInfoInspector.get_file_size(sample_wav_file)
41
+ assert external_tool_size == 16822
42
+
43
+ file_size = get_file_size(sample_wav_file)
44
+ assert file_size == 16822
45
+
46
+ def test_file_size_wav_big(self, size_big_wav: Path):
47
+ external_tool_size = TechnicalInfoInspector.get_file_size(size_big_wav)
48
+ assert external_tool_size == 83414326
49
+
50
+ file_size = get_file_size(size_big_wav)
51
+ assert file_size == 83414326
@@ -0,0 +1,28 @@
1
+ """Unit tests for _AudioFile get_audio_format_name method."""
2
+
3
+ from pathlib import Path
4
+
5
+ import pytest
6
+
7
+ from audiometa._audio_file import _AudioFile
8
+
9
+
10
+ @pytest.mark.unit
11
+ class TestAudioFileFormatName:
12
+ def test_get_format_name_mp3(self, sample_mp3_file: Path):
13
+ audio_file = _AudioFile(sample_mp3_file)
14
+ audio_format_name = audio_file.get_audio_format_name()
15
+
16
+ assert audio_format_name == "MP3"
17
+
18
+ def test_get_format_name_wav(self, sample_wav_file: Path):
19
+ audio_file = _AudioFile(sample_wav_file)
20
+ audio_format_name = audio_file.get_audio_format_name()
21
+
22
+ assert audio_format_name == "WAV"
23
+
24
+ def test_get_format_name_flac(self, sample_flac_file: Path):
25
+ audio_file = _AudioFile(sample_flac_file)
26
+ audio_format_name = audio_file.get_audio_format_name()
27
+
28
+ assert audio_format_name == "FLAC"
@@ -0,0 +1,31 @@
1
+ from pathlib import Path
2
+
3
+ import pytest
4
+
5
+ from audiometa._audio_file import _AudioFile
6
+
7
+
8
+ @pytest.mark.unit
9
+ class TestAudioFileSampleRate:
10
+ def test_get_sample_rate_mp3(self, sample_mp3_file: Path):
11
+ audio_file = _AudioFile(sample_mp3_file)
12
+ sample_rate = audio_file.get_sample_rate()
13
+ assert isinstance(sample_rate, int)
14
+ assert sample_rate > 0
15
+
16
+ def test_get_sample_rate_wav(self, sample_wav_file: Path):
17
+ audio_file = _AudioFile(sample_wav_file)
18
+ sample_rate = audio_file.get_sample_rate()
19
+ assert isinstance(sample_rate, int)
20
+ assert sample_rate > 0
21
+
22
+ def test_get_sample_rate_flac(self, sample_flac_file: Path):
23
+ audio_file = _AudioFile(sample_flac_file)
24
+ sample_rate = audio_file.get_sample_rate()
25
+ assert isinstance(sample_rate, int)
26
+ assert sample_rate > 0
27
+
28
+ def test_get_sample_rate_returns_int(self, sample_mp3_file: Path):
29
+ audio_file = _AudioFile(sample_mp3_file)
30
+ sample_rate = audio_file.get_sample_rate()
31
+ assert isinstance(sample_rate, int)
@@ -0,0 +1,30 @@
1
+ from pathlib import Path
2
+
3
+ import pytest
4
+
5
+ from audiometa._audio_file import _AudioFile
6
+ from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
7
+
8
+
9
+ @pytest.mark.unit
10
+ class TestContextManager:
11
+ def test_context_manager(self, sample_mp3_file: Path):
12
+ with _AudioFile(sample_mp3_file) as audio_file:
13
+ assert isinstance(audio_file, _AudioFile)
14
+ assert audio_file.file_path == str(sample_mp3_file)
15
+
16
+ duration = audio_file.get_duration_in_sec()
17
+ assert duration > 0
18
+
19
+ def test_file_operations(self):
20
+ with temp_file_with_metadata({}, "mp3") as test_file:
21
+ audio_file = _AudioFile(test_file)
22
+
23
+ # Test write
24
+ test_data = b"test audio data"
25
+ bytes_written = audio_file.write(test_data)
26
+ assert bytes_written == len(test_data)
27
+
28
+ # Test read
29
+ read_data = audio_file.read()
30
+ assert read_data == test_data
@@ -0,0 +1,40 @@
1
+ from pathlib import Path
2
+
3
+ import pytest
4
+
5
+ from audiometa._audio_file import _AudioFile
6
+ from audiometa.exceptions import FileCorruptedError, FileTypeNotSupportedError
7
+ from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
8
+
9
+
10
+ @pytest.mark.unit
11
+ class TestAudioFileFileTypeValidation:
12
+ def test_extension_mp3_then_ok(self, sample_mp3_file: Path):
13
+ audio_file = _AudioFile(sample_mp3_file)
14
+ assert audio_file.file_extension == ".mp3"
15
+
16
+ def test_extension_flac_then_ok(self, sample_flac_file: Path):
17
+ audio_file = _AudioFile(sample_flac_file)
18
+ assert audio_file.file_extension == ".flac"
19
+
20
+ def test_extension_wav_then_ok(self, sample_wav_file: Path):
21
+ audio_file = _AudioFile(sample_wav_file)
22
+ assert audio_file.file_extension == ".wav"
23
+
24
+ def test_extension_m4a_then_not_ok(self, sample_m4a_file: Path):
25
+ with pytest.raises(FileTypeNotSupportedError):
26
+ _AudioFile(sample_m4a_file)
27
+
28
+ def test_bad_content_then_not_ok(self):
29
+ # Create a file with valid extension but bad content
30
+ with temp_file_with_metadata({}, "mp3") as temp_audio_file_path:
31
+ temp_audio_file_path.write_bytes(b"not a real audio file")
32
+ mp3_file_path = temp_audio_file_path.with_suffix(".mp3")
33
+ mp3_file_path.write_bytes(b"not a real audio file")
34
+
35
+ with pytest.raises(FileCorruptedError):
36
+ _AudioFile(str(mp3_file_path))
37
+
38
+ def test_nonexistent_file_raises_error(self):
39
+ with pytest.raises(FileNotFoundError):
40
+ _AudioFile("nonexistent.mp3")
@@ -0,0 +1,49 @@
1
+ from pathlib import Path
2
+
3
+ import pytest
4
+
5
+ from audiometa import is_audio_file
6
+
7
+
8
+ @pytest.mark.unit
9
+ class TestIsAudioFile:
10
+ def test_valid_mp3_file_returns_true(self, sample_mp3_file: Path):
11
+ assert is_audio_file(sample_mp3_file) is True
12
+ assert is_audio_file(str(sample_mp3_file)) is True
13
+
14
+ def test_valid_flac_file_returns_true(self, sample_flac_file: Path):
15
+ assert is_audio_file(sample_flac_file) is True
16
+ assert is_audio_file(str(sample_flac_file)) is True
17
+
18
+ def test_valid_wav_file_returns_true(self, sample_wav_file: Path):
19
+ assert is_audio_file(sample_wav_file) is True
20
+ assert is_audio_file(str(sample_wav_file)) is True
21
+
22
+ def test_nonexistent_file_returns_false(self):
23
+ assert is_audio_file("nonexistent.mp3") is False
24
+ assert is_audio_file(Path("nonexistent.flac")) is False
25
+
26
+ def test_unsupported_extension_returns_false(self, sample_m4a_file: Path):
27
+ assert is_audio_file(sample_m4a_file) is False
28
+ assert is_audio_file(str(sample_m4a_file)) is False
29
+
30
+ def test_invalid_mp3_content_returns_false(self, tmp_path: Path):
31
+ invalid_mp3 = tmp_path / "invalid.mp3"
32
+ invalid_mp3.write_bytes(b"not a real audio file")
33
+
34
+ assert is_audio_file(invalid_mp3) is False
35
+ assert is_audio_file(str(invalid_mp3)) is False
36
+
37
+ def test_invalid_flac_content_returns_false(self, tmp_path: Path):
38
+ invalid_flac = tmp_path / "invalid.flac"
39
+ invalid_flac.write_bytes(b"not a real audio file")
40
+
41
+ assert is_audio_file(invalid_flac) is False
42
+ assert is_audio_file(str(invalid_flac)) is False
43
+
44
+ def test_invalid_wav_content_returns_false(self, tmp_path: Path):
45
+ invalid_wav = tmp_path / "invalid.wav"
46
+ invalid_wav.write_bytes(b"not a real audio file")
47
+
48
+ assert is_audio_file(invalid_wav) is False
49
+ assert is_audio_file(str(invalid_wav)) is False
@@ -0,0 +1,20 @@
1
+ import pytest
2
+
3
+ from audiometa._audio_file import _AudioFile
4
+ from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
5
+
6
+
7
+ @pytest.mark.unit
8
+ class TestAudioFileOperations:
9
+ def test_file_operations(self):
10
+ with temp_file_with_metadata({}, "mp3") as test_file:
11
+ audio_file = _AudioFile(test_file)
12
+
13
+ # Test write
14
+ test_data = b"test audio data"
15
+ bytes_written = audio_file.write(test_data)
16
+ assert bytes_written == len(test_data)
17
+
18
+ # Test read
19
+ read_data = audio_file.read()
20
+ assert read_data == test_data
@@ -0,0 +1,23 @@
1
+ from pathlib import Path
2
+
3
+ import pytest
4
+
5
+ from audiometa._audio_file import _AudioFile
6
+
7
+
8
+ @pytest.mark.unit
9
+ class TestAudioFilePathHandling:
10
+ def test_audio_file_with_string_path(self, sample_mp3_file: Path):
11
+ sample_mp3_file_str = str(sample_mp3_file)
12
+ audio_file = _AudioFile(sample_mp3_file_str)
13
+ assert audio_file.file_path == sample_mp3_file_str
14
+ assert audio_file.file_extension == ".mp3"
15
+
16
+ def test_audio_file_with_path_object(self, sample_mp3_file: Path):
17
+ audio_file = _AudioFile(sample_mp3_file)
18
+ assert audio_file.file_path == str(sample_mp3_file)
19
+ assert audio_file.file_extension == ".mp3"
20
+
21
+ def test_audio_file_nonexistent_file(self):
22
+ with pytest.raises(FileNotFoundError):
23
+ _AudioFile("nonexistent_file.mp3")
File without changes