audiometa-python 0.2.2__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 (318) hide show
  1. audiometa/__init__.py +1240 -0
  2. audiometa/__main__.py +6 -0
  3. audiometa/_audio_file.py +602 -0
  4. audiometa/cli.py +347 -0
  5. audiometa/exceptions.py +167 -0
  6. audiometa/manager/_MetadataManager.py +665 -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 +945 -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 +778 -0
  14. audiometa/manager/_rating_supporting/riff/__init__.py +25 -0
  15. audiometa/manager/_rating_supporting/riff/_riff_constants.py +10 -0
  16. audiometa/manager/_rating_supporting/vorbis/_VorbisManager.py +525 -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 +305 -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 +188 -0
  40. audiometa/test/helpers/id3v2/id3v2_metadata_setter.py +428 -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 +216 -0
  44. audiometa/test/helpers/riff/riff_metadata_deleter.py +56 -0
  45. audiometa/test/helpers/riff/riff_metadata_getter.py +118 -0
  46. audiometa/test/helpers/riff/riff_metadata_setter.py +196 -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 +200 -0
  55. audiometa/test/tests/__init__.py +0 -0
  56. audiometa/test/tests/conftest.py +261 -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/test_cli_delete.py +20 -0
  69. audiometa/test/tests/e2e/cli/test_cli_formatting.py +31 -0
  70. audiometa/test/tests/e2e/cli/test_cli_help.py +40 -0
  71. audiometa/test/tests/e2e/cli/test_cli_read.py +79 -0
  72. audiometa/test/tests/e2e/cli/test_cli_unified.py +33 -0
  73. audiometa/test/tests/e2e/cli/test_cli_write.py +116 -0
  74. audiometa/test/tests/e2e/scenarios/__init__.py +0 -0
  75. audiometa/test/tests/e2e/scenarios/test_user_scenarios.py +166 -0
  76. audiometa/test/tests/e2e/workflows/__init__.py +0 -0
  77. audiometa/test/tests/e2e/workflows/test_core_workflows.py +166 -0
  78. audiometa/test/tests/e2e/workflows/test_deletion_workflows.py +318 -0
  79. audiometa/test/tests/e2e/workflows/test_error_handling_workflows.py +165 -0
  80. audiometa/test/tests/e2e/workflows/test_format_specific_workflows.py +129 -0
  81. audiometa/test/tests/e2e/workflows/test_rating_workflows.py +124 -0
  82. audiometa/test/tests/integration/__init__.py +0 -0
  83. audiometa/test/tests/integration/audio_format/__init__.py +0 -0
  84. audiometa/test/tests/integration/audio_format/flac/__init__.py +0 -0
  85. audiometa/test/tests/integration/audio_format/flac/test_flac_delete_all.py +108 -0
  86. audiometa/test/tests/integration/audio_format/flac/test_flac_reading_all.py +61 -0
  87. audiometa/test/tests/integration/audio_format/flac/test_flac_reading_field.py +65 -0
  88. audiometa/test/tests/integration/audio_format/flac/test_flac_writing.py +69 -0
  89. audiometa/test/tests/integration/audio_format/mp3/__init__.py +0 -0
  90. audiometa/test/tests/integration/audio_format/mp3/test_mp3_delete_all.py +79 -0
  91. audiometa/test/tests/integration/audio_format/mp3/test_mp3_reading_all.py +61 -0
  92. audiometa/test/tests/integration/audio_format/mp3/test_mp3_reading_field.py +67 -0
  93. audiometa/test/tests/integration/audio_format/mp3/test_mp3_writing.py +60 -0
  94. audiometa/test/tests/integration/audio_format/wav/__init__.py +0 -0
  95. audiometa/test/tests/integration/audio_format/wav/test_wav_delete_all.py +87 -0
  96. audiometa/test/tests/integration/audio_format/wav/test_wav_reading_all.py +62 -0
  97. audiometa/test/tests/integration/audio_format/wav/test_wav_reading_field.py +57 -0
  98. audiometa/test/tests/integration/audio_format/wav/test_wav_with_id3v2_tags.py +83 -0
  99. audiometa/test/tests/integration/audio_format/wav/test_wav_writing.py +62 -0
  100. audiometa/test/tests/integration/conftest.py +29 -0
  101. audiometa/test/tests/integration/delete_all_metadata/__init__.py +1 -0
  102. audiometa/test/tests/integration/delete_all_metadata/test_audio_format_all.py +102 -0
  103. audiometa/test/tests/integration/delete_all_metadata/test_audio_format_header_deletion.py +77 -0
  104. audiometa/test/tests/integration/delete_all_metadata/test_basic_functionality.py +47 -0
  105. audiometa/test/tests/integration/delete_all_metadata/test_error_handling.py +24 -0
  106. audiometa/test/tests/integration/encoding/__init__.py +1 -0
  107. audiometa/test/tests/integration/encoding/test_encoding.py +88 -0
  108. audiometa/test/tests/integration/encoding/test_special_characters_edge_cases.py +223 -0
  109. audiometa/test/tests/integration/get_full_metadata/__init__.py +0 -0
  110. audiometa/test/tests/integration/get_full_metadata/test_binary_data_filtering.py +250 -0
  111. audiometa/test/tests/integration/get_full_metadata/test_get_full_metadata.py +297 -0
  112. audiometa/test/tests/integration/get_full_metadata/test_get_full_metadata_edge_cases.py +231 -0
  113. audiometa/test/tests/integration/get_full_metadata/test_options.py +207 -0
  114. audiometa/test/tests/integration/get_full_metadata/test_parsed_fields_keys.py +85 -0
  115. audiometa/test/tests/integration/metadata_field/__init__.py +0 -0
  116. audiometa/test/tests/integration/metadata_field/album/__init__.py +0 -0
  117. audiometa/test/tests/integration/metadata_field/album/test_deleting.py +73 -0
  118. audiometa/test/tests/integration/metadata_field/album/test_reading.py +36 -0
  119. audiometa/test/tests/integration/metadata_field/album/test_writing.py +50 -0
  120. audiometa/test/tests/integration/metadata_field/album_artists/__init__.py +0 -0
  121. audiometa/test/tests/integration/metadata_field/album_artists/test_deleting.py +83 -0
  122. audiometa/test/tests/integration/metadata_field/album_artists/test_reading.py +38 -0
  123. audiometa/test/tests/integration/metadata_field/album_artists/test_writing.py +52 -0
  124. audiometa/test/tests/integration/metadata_field/artists/__init__.py +0 -0
  125. audiometa/test/tests/integration/metadata_field/artists/test_deleting.py +68 -0
  126. audiometa/test/tests/integration/metadata_field/artists/test_reading.py +36 -0
  127. audiometa/test/tests/integration/metadata_field/artists/test_writing.py +46 -0
  128. audiometa/test/tests/integration/metadata_field/bpm/__init__.py +0 -0
  129. audiometa/test/tests/integration/metadata_field/bpm/test_deleting.py +75 -0
  130. audiometa/test/tests/integration/metadata_field/bpm/test_reading.py +32 -0
  131. audiometa/test/tests/integration/metadata_field/bpm/test_writing.py +56 -0
  132. audiometa/test/tests/integration/metadata_field/comment/__init__.py +0 -0
  133. audiometa/test/tests/integration/metadata_field/comment/test_deleting.py +68 -0
  134. audiometa/test/tests/integration/metadata_field/comment/test_reading.py +36 -0
  135. audiometa/test/tests/integration/metadata_field/comment/test_writing.py +49 -0
  136. audiometa/test/tests/integration/metadata_field/composer/__init__.py +0 -0
  137. audiometa/test/tests/integration/metadata_field/composer/test_deleting.py +75 -0
  138. audiometa/test/tests/integration/metadata_field/composer/test_reading.py +34 -0
  139. audiometa/test/tests/integration/metadata_field/composer/test_writing.py +41 -0
  140. audiometa/test/tests/integration/metadata_field/copyright/__init__.py +0 -0
  141. audiometa/test/tests/integration/metadata_field/copyright/test_deleting.py +81 -0
  142. audiometa/test/tests/integration/metadata_field/copyright/test_reading.py +35 -0
  143. audiometa/test/tests/integration/metadata_field/copyright/test_writing.py +41 -0
  144. audiometa/test/tests/integration/metadata_field/field_not_supported/__init__.py +0 -0
  145. audiometa/test/tests/integration/metadata_field/field_not_supported/test_deleting.py +56 -0
  146. audiometa/test/tests/integration/metadata_field/field_not_supported/test_reading.py +54 -0
  147. audiometa/test/tests/integration/metadata_field/field_not_supported/test_writing.py +61 -0
  148. audiometa/test/tests/integration/metadata_field/genre/__init__.py +0 -0
  149. audiometa/test/tests/integration/metadata_field/genre/reading/__init__.py +0 -0
  150. audiometa/test/tests/integration/metadata_field/genre/reading/metadata_format/__init__.py +0 -0
  151. audiometa/test/tests/integration/metadata_field/genre/reading/metadata_format/test_id3v1_reading.py +65 -0
  152. audiometa/test/tests/integration/metadata_field/genre/reading/metadata_format/test_id3v2_reading.py +25 -0
  153. audiometa/test/tests/integration/metadata_field/genre/reading/metadata_format/test_riff_reading.py +58 -0
  154. audiometa/test/tests/integration/metadata_field/genre/reading/metadata_format/test_vorbis_reading.py +61 -0
  155. audiometa/test/tests/integration/metadata_field/genre/reading/test_smart_reading.py +191 -0
  156. audiometa/test/tests/integration/metadata_field/genre/test_deleting.py +62 -0
  157. audiometa/test/tests/integration/metadata_field/genre/test_writing.py +64 -0
  158. audiometa/test/tests/integration/metadata_field/language/__init__.py +0 -0
  159. audiometa/test/tests/integration/metadata_field/language/test_deleting.py +75 -0
  160. audiometa/test/tests/integration/metadata_field/language/test_reading.py +39 -0
  161. audiometa/test/tests/integration/metadata_field/language/test_writing.py +43 -0
  162. audiometa/test/tests/integration/metadata_field/lyrics/__init__.py +0 -0
  163. audiometa/test/tests/integration/metadata_field/lyrics/test_deleting.py +129 -0
  164. audiometa/test/tests/integration/metadata_field/lyrics/test_reading.py +57 -0
  165. audiometa/test/tests/integration/metadata_field/lyrics/test_writing.py +59 -0
  166. audiometa/test/tests/integration/metadata_field/publisher/__init__.py +0 -0
  167. audiometa/test/tests/integration/metadata_field/publisher/test_deleting.py +88 -0
  168. audiometa/test/tests/integration/metadata_field/publisher/test_reading.py +32 -0
  169. audiometa/test/tests/integration/metadata_field/publisher/test_writing.py +47 -0
  170. audiometa/test/tests/integration/metadata_field/rating/__init__.py +0 -0
  171. audiometa/test/tests/integration/metadata_field/rating/reading/__init__.py +0 -0
  172. audiometa/test/tests/integration/metadata_field/rating/reading/test_base_100_proportional.py +81 -0
  173. audiometa/test/tests/integration/metadata_field/rating/reading/test_base_255_non_proportional.py +33 -0
  174. audiometa/test/tests/integration/metadata_field/rating/reading/test_base_255_proportional.py +58 -0
  175. audiometa/test/tests/integration/metadata_field/rating/test_deleting.py +117 -0
  176. audiometa/test/tests/integration/metadata_field/rating/test_error_handling.py +137 -0
  177. audiometa/test/tests/integration/metadata_field/rating/writing/__init__.py +0 -0
  178. audiometa/test/tests/integration/metadata_field/rating/writing/metadata_format/__init__.py +0 -0
  179. audiometa/test/tests/integration/metadata_field/rating/writing/metadata_format/test_id3v2.py +77 -0
  180. audiometa/test/tests/integration/metadata_field/rating/writing/metadata_format/test_riff.py +55 -0
  181. audiometa/test/tests/integration/metadata_field/rating/writing/metadata_format/test_vorbis.py +57 -0
  182. audiometa/test/tests/integration/metadata_field/rating/writing/test_comprehensive.py +192 -0
  183. audiometa/test/tests/integration/metadata_field/release_date/__init__.py +0 -0
  184. audiometa/test/tests/integration/metadata_field/release_date/test_deleting.py +74 -0
  185. audiometa/test/tests/integration/metadata_field/release_date/test_error_handling.py +82 -0
  186. audiometa/test/tests/integration/metadata_field/release_date/test_reading.py +59 -0
  187. audiometa/test/tests/integration/metadata_field/release_date/test_writing.py +49 -0
  188. audiometa/test/tests/integration/metadata_field/test_metadata_field_validation.py +134 -0
  189. audiometa/test/tests/integration/metadata_field/title/__init__.py +0 -0
  190. audiometa/test/tests/integration/metadata_field/title/test_deleting.py +73 -0
  191. audiometa/test/tests/integration/metadata_field/title/test_error_handling.py +47 -0
  192. audiometa/test/tests/integration/metadata_field/title/test_reading.py +36 -0
  193. audiometa/test/tests/integration/metadata_field/title/test_writing.py +64 -0
  194. audiometa/test/tests/integration/metadata_field/track_number/__init__.py +0 -0
  195. audiometa/test/tests/integration/metadata_field/track_number/reading/__init__.py +0 -0
  196. audiometa/test/tests/integration/metadata_field/track_number/reading/test_edge_cases.py +43 -0
  197. audiometa/test/tests/integration/metadata_field/track_number/reading/test_metadata_format.py +32 -0
  198. audiometa/test/tests/integration/metadata_field/track_number/test_deleting.py +59 -0
  199. audiometa/test/tests/integration/metadata_field/track_number/test_writing.py +73 -0
  200. audiometa/test/tests/integration/multiple_values/__init__.py +1 -0
  201. audiometa/test/tests/integration/multiple_values/reading/__init__.py +1 -0
  202. audiometa/test/tests/integration/multiple_values/reading/metadata_format/__init__.py +1 -0
  203. audiometa/test/tests/integration/multiple_values/reading/metadata_format/test_id3v1.py +23 -0
  204. audiometa/test/tests/integration/multiple_values/reading/metadata_format/test_id3v2_3.py +92 -0
  205. audiometa/test/tests/integration/multiple_values/reading/metadata_format/test_id3v2_4.py +216 -0
  206. audiometa/test/tests/integration/multiple_values/reading/metadata_format/test_riff.py +84 -0
  207. audiometa/test/tests/integration/multiple_values/reading/metadata_format/test_vorbis.py +169 -0
  208. audiometa/test/tests/integration/multiple_values/reading/test_performance_large_data.py +209 -0
  209. audiometa/test/tests/integration/multiple_values/reading/test_smart_parsing_scenarios.py +198 -0
  210. audiometa/test/tests/integration/multiple_values/reading/test_unicode_handling.py +24 -0
  211. audiometa/test/tests/integration/multiple_values/writing/__init__.py +1 -0
  212. audiometa/test/tests/integration/multiple_values/writing/metadata_format/__init__.py +1 -0
  213. audiometa/test/tests/integration/multiple_values/writing/metadata_format/test_id3v1.py +62 -0
  214. audiometa/test/tests/integration/multiple_values/writing/metadata_format/test_id3v2_3.py +36 -0
  215. audiometa/test/tests/integration/multiple_values/writing/metadata_format/test_id3v2_4.py +34 -0
  216. audiometa/test/tests/integration/multiple_values/writing/metadata_format/test_riff.py +32 -0
  217. audiometa/test/tests/integration/multiple_values/writing/metadata_format/test_vorbis.py +54 -0
  218. audiometa/test/tests/integration/multiple_values/writing/test_error_handling.py +42 -0
  219. audiometa/test/tests/integration/multiple_values/writing/test_large_values.py +98 -0
  220. audiometa/test/tests/integration/reading/__init__.py +1 -0
  221. audiometa/test/tests/integration/reading/test_read_multiple_metadata.py +80 -0
  222. audiometa/test/tests/integration/reading/test_reading_error_handling.py +36 -0
  223. audiometa/test/tests/integration/real_audio_files/__init__.py +0 -0
  224. audiometa/test/tests/integration/real_audio_files/test_reading.py +146 -0
  225. audiometa/test/tests/integration/real_audio_files/test_writing.py +198 -0
  226. audiometa/test/tests/integration/technical_info/__init__.py +0 -0
  227. audiometa/test/tests/integration/technical_info/flac_md5/__init__.py +0 -0
  228. audiometa/test/tests/integration/technical_info/flac_md5/conftest.py +103 -0
  229. audiometa/test/tests/integration/technical_info/flac_md5/test_invalid_md5/__init__.py +0 -0
  230. audiometa/test/tests/integration/technical_info/flac_md5/test_invalid_md5/test_audio_data_corruption.py +21 -0
  231. audiometa/test/tests/integration/technical_info/flac_md5/test_invalid_md5/test_flipped_md5.py +29 -0
  232. audiometa/test/tests/integration/technical_info/flac_md5/test_invalid_md5/test_non_flac_error.py +13 -0
  233. audiometa/test/tests/integration/technical_info/flac_md5/test_invalid_md5/test_partial_md5.py +29 -0
  234. audiometa/test/tests/integration/technical_info/flac_md5/test_invalid_md5/test_random_md5.py +29 -0
  235. audiometa/test/tests/integration/technical_info/flac_md5/test_invalid_md5/test_unset_md5.py +56 -0
  236. audiometa/test/tests/integration/technical_info/flac_md5/test_valid_md5.py +21 -0
  237. audiometa/test/tests/integration/technical_info/test_bitrate.py +38 -0
  238. audiometa/test/tests/integration/technical_info/test_channels.py +38 -0
  239. audiometa/test/tests/integration/technical_info/test_duration_in_sec.py +38 -0
  240. audiometa/test/tests/integration/technical_info/test_sample_rate.py +40 -0
  241. audiometa/test/tests/integration/test_audio_file.py +23 -0
  242. audiometa/test/tests/integration/test_audio_format_readable_after_update_all_metadata_formats.py +95 -0
  243. audiometa/test/tests/integration/writing/__init__.py +0 -0
  244. audiometa/test/tests/integration/writing/test_error_handling.py +44 -0
  245. audiometa/test/tests/integration/writing/test_forced_format.py +224 -0
  246. audiometa/test/tests/integration/writing/test_multiple_format_preservation.py +223 -0
  247. audiometa/test/tests/integration/writing/test_partial_update.py +36 -0
  248. audiometa/test/tests/integration/writing/writing_strategies/__init__.py +0 -0
  249. audiometa/test/tests/integration/writing/writing_strategies/test_cleanup_strategy.py +79 -0
  250. audiometa/test/tests/integration/writing/writing_strategies/test_preserve_strategy.py +76 -0
  251. audiometa/test/tests/integration/writing/writing_strategies/test_sync_strategy.py +215 -0
  252. audiometa/test/tests/integration/writing/writing_strategies/unsupported_fields/__init__.py +0 -0
  253. audiometa/test/tests/integration/writing/writing_strategies/unsupported_fields/test_fail_behavior.py +42 -0
  254. audiometa/test/tests/integration/writing/writing_strategies/unsupported_fields/test_no_writing_on_failure.py +93 -0
  255. audiometa/test/tests/integration/writing/writing_strategies/unsupported_fields/test_strategy_specific.py +99 -0
  256. audiometa/test/tests/unit/__init__.py +0 -0
  257. audiometa/test/tests/unit/audio_file/__init__.py +0 -0
  258. audiometa/test/tests/unit/audio_file/technical_info/__init__.py +0 -0
  259. audiometa/test/tests/unit/audio_file/technical_info/test_bitrate.py +26 -0
  260. audiometa/test/tests/unit/audio_file/technical_info/test_channels.py +31 -0
  261. audiometa/test/tests/unit/audio_file/technical_info/test_duration_in_sec.py +38 -0
  262. audiometa/test/tests/unit/audio_file/technical_info/test_error_handling.py +130 -0
  263. audiometa/test/tests/unit/audio_file/technical_info/test_file_size.py +51 -0
  264. audiometa/test/tests/unit/audio_file/technical_info/test_format_name.py +28 -0
  265. audiometa/test/tests/unit/audio_file/technical_info/test_sample_rate.py +31 -0
  266. audiometa/test/tests/unit/audio_file/test_context_manager.py +30 -0
  267. audiometa/test/tests/unit/audio_file/test_file_validation.py +40 -0
  268. audiometa/test/tests/unit/audio_file/test_operations.py +20 -0
  269. audiometa/test/tests/unit/audio_file/test_path_handling.py +23 -0
  270. audiometa/test/tests/unit/cli/__init__.py +0 -0
  271. audiometa/test/tests/unit/cli/test_expand_file_patterns.py +234 -0
  272. audiometa/test/tests/unit/metadata_managers/__init__.py +0 -0
  273. audiometa/test/tests/unit/metadata_managers/conftest.py +142 -0
  274. audiometa/test/tests/unit/metadata_managers/header_info/__init__.py +0 -0
  275. audiometa/test/tests/unit/metadata_managers/header_info/test_id3v1.py +49 -0
  276. audiometa/test/tests/unit/metadata_managers/header_info/test_id3v2.py +66 -0
  277. audiometa/test/tests/unit/metadata_managers/header_info/test_riff.py +57 -0
  278. audiometa/test/tests/unit/metadata_managers/header_info/test_vorbis.py +53 -0
  279. audiometa/test/tests/unit/metadata_managers/metadata_field/__init__.py +0 -0
  280. audiometa/test/tests/unit/metadata_managers/metadata_field/multiple_values/__init__.py +0 -0
  281. audiometa/test/tests/unit/metadata_managers/metadata_field/multiple_values/reading/__init__.py +0 -0
  282. audiometa/test/tests/unit/metadata_managers/metadata_field/multiple_values/reading/test_smart_parsing.py +186 -0
  283. audiometa/test/tests/unit/metadata_managers/metadata_field/multiple_values/writing/__init__.py +0 -0
  284. audiometa/test/tests/unit/metadata_managers/metadata_field/multiple_values/writing/test_separator_selection.py +142 -0
  285. audiometa/test/tests/unit/metadata_managers/metadata_field/multiple_values/writing/test_value_filtering.py +76 -0
  286. audiometa/test/tests/unit/metadata_managers/metadata_field/rating/__init__.py +0 -0
  287. audiometa/test/tests/unit/metadata_managers/metadata_field/rating/reading/__init__.py +0 -0
  288. audiometa/test/tests/unit/metadata_managers/metadata_field/rating/reading/test_normalization.py +152 -0
  289. audiometa/test/tests/unit/metadata_managers/metadata_field/rating/reading/test_profiles_values.py +23 -0
  290. audiometa/test/tests/unit/metadata_managers/metadata_field/rating/test_rating_validation.py +77 -0
  291. audiometa/test/tests/unit/metadata_managers/metadata_field/rating/writing/__init__.py +0 -0
  292. audiometa/test/tests/unit/metadata_managers/metadata_field/rating/writing/test_validation.py +151 -0
  293. audiometa/test/tests/unit/metadata_managers/metadata_field/rating/writing/test_writing_profiles.py +61 -0
  294. audiometa/test/tests/unit/metadata_managers/metadata_field/test_date_format_validation.py +135 -0
  295. audiometa/test/tests/unit/metadata_managers/metadata_field/test_track_number_validation.py +46 -0
  296. audiometa/test/tests/unit/metadata_managers/metadata_field/test_type_validation_exception.py +22 -0
  297. audiometa/test/tests/unit/metadata_managers/metadata_field/test_validation.py +83 -0
  298. audiometa/test/tests/unit/metadata_managers/test_metadata_format_managers_write_and_read.py +74 -0
  299. audiometa/utils/__init__.py +1 -0
  300. audiometa/utils/id3v1_genre_code_map.py +205 -0
  301. audiometa/utils/metadata_format.py +31 -0
  302. audiometa/utils/metadata_writing_strategy.py +16 -0
  303. audiometa/utils/os_dependencies_checker/__init__.py +24 -0
  304. audiometa/utils/os_dependencies_checker/base.py +62 -0
  305. audiometa/utils/os_dependencies_checker/config.py +51 -0
  306. audiometa/utils/os_dependencies_checker/macos.py +236 -0
  307. audiometa/utils/os_dependencies_checker/ubuntu.py +95 -0
  308. audiometa/utils/os_dependencies_checker/windows.py +227 -0
  309. audiometa/utils/rating_profiles.py +110 -0
  310. audiometa/utils/tool_path_resolver.py +135 -0
  311. audiometa/utils/types.py +82 -0
  312. audiometa/utils/unified_metadata_key.py +81 -0
  313. audiometa_python-0.2.2.dist-info/METADATA +1464 -0
  314. audiometa_python-0.2.2.dist-info/RECORD +318 -0
  315. audiometa_python-0.2.2.dist-info/WHEEL +5 -0
  316. audiometa_python-0.2.2.dist-info/entry_points.txt +2 -0
  317. audiometa_python-0.2.2.dist-info/licenses/LICENSE +202 -0
  318. audiometa_python-0.2.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,26 @@
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 TestAudioFileBitrate:
10
+ def test_get_bitrate_mp3(self, sample_mp3_file: Path):
11
+ audio_file = _AudioFile(sample_mp3_file)
12
+ bitrate = audio_file.get_bitrate()
13
+ assert isinstance(bitrate, int)
14
+ assert bitrate > 0
15
+
16
+ def test_get_bitrate_flac(self, sample_flac_file: Path):
17
+ audio_file = _AudioFile(sample_flac_file)
18
+ bitrate = audio_file.get_bitrate()
19
+ assert isinstance(bitrate, int)
20
+ assert bitrate > 0
21
+
22
+ def test_get_bitrate_wav(self, sample_wav_file: Path):
23
+ audio_file = _AudioFile(sample_wav_file)
24
+ bitrate = audio_file.get_bitrate()
25
+ assert isinstance(bitrate, int)
26
+ assert bitrate > 0
@@ -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 TestAudioFileChannels:
10
+ def test_get_channels_mp3(self, sample_mp3_file: Path):
11
+ audio_file = _AudioFile(sample_mp3_file)
12
+ channels = audio_file.get_channels()
13
+ assert isinstance(channels, int)
14
+ assert channels > 0
15
+
16
+ def test_get_channels_flac(self, sample_flac_file: Path):
17
+ audio_file = _AudioFile(sample_flac_file)
18
+ channels = audio_file.get_channels()
19
+ assert isinstance(channels, int)
20
+ assert channels > 0
21
+
22
+ def test_get_channels_wav(self, sample_wav_file: Path):
23
+ audio_file = _AudioFile(sample_wav_file)
24
+ channels = audio_file.get_channels()
25
+ assert isinstance(channels, int)
26
+ assert channels > 0
27
+
28
+ def test_get_channels_returns_int(self, sample_mp3_file: Path):
29
+ audio_file = _AudioFile(sample_mp3_file)
30
+ channels = audio_file.get_channels()
31
+ assert isinstance(channels, int)
@@ -0,0 +1,38 @@
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 TestAudioFileDurationInSec:
10
+ def test_get_duration_in_sec_mp3(self, sample_mp3_file: Path):
11
+ audio_file = _AudioFile(sample_mp3_file)
12
+ duration = audio_file.get_duration_in_sec()
13
+ assert isinstance(duration, float)
14
+ assert duration > 0
15
+ assert duration < 1000 # Reasonable bounds check
16
+
17
+ def test_get_duration_in_sec_flac(self, sample_flac_file: Path):
18
+ audio_file = _AudioFile(sample_flac_file)
19
+ duration = audio_file.get_duration_in_sec()
20
+ assert isinstance(duration, float)
21
+ assert duration > 0
22
+
23
+ def test_get_duration_in_sec_wav(self, sample_wav_file: Path):
24
+ audio_file = _AudioFile(sample_wav_file)
25
+ duration = audio_file.get_duration_in_sec()
26
+ assert isinstance(duration, float)
27
+ assert duration > 0
28
+
29
+ def test_get_duration_in_sec_long_file(self, duration_182s_mp3: Path):
30
+ audio_file = _AudioFile(duration_182s_mp3)
31
+ duration = audio_file.get_duration_in_sec()
32
+ assert isinstance(duration, float)
33
+ assert duration > 100
34
+
35
+ def test_get_duration_in_sec_returns_float(self, sample_mp3_file: Path):
36
+ audio_file = _AudioFile(sample_mp3_file)
37
+ duration = audio_file.get_duration_in_sec()
38
+ assert isinstance(duration, float)
@@ -0,0 +1,130 @@
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
@@ -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,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