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,297 @@
1
+ """Tests for get_full_metadata function."""
2
+
3
+ from pathlib import Path
4
+
5
+ import pytest
6
+
7
+ from audiometa import get_full_metadata
8
+ from audiometa._audio_file import _AudioFile
9
+ from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
10
+
11
+
12
+ @pytest.mark.integration
13
+ class TestGetFullMetadata:
14
+ def test_get_full_metadata_mp3_with_metadata(self, sample_mp3_file: Path):
15
+ result = get_full_metadata(sample_mp3_file)
16
+
17
+ # Check structure
18
+ assert "unified_metadata" in result
19
+ assert "technical_info" in result
20
+ assert "metadata_format" in result
21
+ assert "headers" in result
22
+ assert "raw_metadata" in result
23
+ assert "format_priorities" in result
24
+
25
+ # Check format priorities
26
+ assert result["format_priorities"]["file_extension"] == ".mp3"
27
+ assert "id3v2" in result["format_priorities"]["reading_order"]
28
+ assert "id3v1" in result["format_priorities"]["reading_order"]
29
+ assert result["format_priorities"]["writing_format"] == "id3v2"
30
+
31
+ # Check technical info
32
+ tech_info = result["technical_info"]
33
+ assert "duration_seconds" in tech_info
34
+ assert "bitrate_kbps" in tech_info
35
+ assert "sample_rate_hz" in tech_info
36
+ assert "channels" in tech_info
37
+ assert "file_size_bytes" in tech_info
38
+ assert "file_extension" in tech_info
39
+ assert "audio_format_name" in tech_info
40
+ assert tech_info["file_extension"] == ".mp3"
41
+ assert tech_info["audio_format_name"] == "MP3"
42
+ assert tech_info["is_flac_md5_valid"] is None # Not a FLAC file
43
+
44
+ # Check format metadata
45
+ assert "id3v2" in result["metadata_format"]
46
+ assert "id3v1" in result["metadata_format"]
47
+
48
+ # Check headers
49
+ assert "id3v2" in result["headers"]
50
+ assert "id3v1" in result["headers"]
51
+
52
+ # Check raw metadata
53
+ assert "id3v2" in result["raw_metadata"]
54
+ assert "id3v1" in result["raw_metadata"]
55
+
56
+ def test_get_full_metadata_flac_with_metadata(self, sample_flac_file: Path):
57
+ result = get_full_metadata(sample_flac_file)
58
+
59
+ # Check format priorities
60
+ assert result["format_priorities"]["file_extension"] == ".flac"
61
+ assert "vorbis" in result["format_priorities"]["reading_order"]
62
+ assert "id3v2" in result["format_priorities"]["reading_order"]
63
+ assert "id3v1" in result["format_priorities"]["reading_order"]
64
+ assert result["format_priorities"]["writing_format"] == "vorbis"
65
+
66
+ # Check technical info
67
+ tech_info = result["technical_info"]
68
+ assert tech_info["file_extension"] == ".flac"
69
+ assert tech_info["audio_format_name"] == "FLAC"
70
+ assert "is_flac_md5_valid" in tech_info # Should be present for FLAC
71
+
72
+ # Check format metadata
73
+ assert "vorbis" in result["metadata_format"]
74
+ assert "id3v2" in result["metadata_format"]
75
+ assert "id3v1" in result["metadata_format"]
76
+
77
+ # Check headers
78
+ assert "vorbis" in result["headers"]
79
+ assert "id3v2" in result["headers"]
80
+ assert "id3v1" in result["headers"]
81
+
82
+ def test_get_full_metadata_wav_with_metadata(self, sample_wav_file: Path):
83
+ result = get_full_metadata(sample_wav_file)
84
+
85
+ # Check format priorities
86
+ assert result["format_priorities"]["file_extension"] == ".wav"
87
+ assert "riff" in result["format_priorities"]["reading_order"]
88
+ assert "id3v2" in result["format_priorities"]["reading_order"]
89
+ assert "id3v1" in result["format_priorities"]["reading_order"]
90
+ assert result["format_priorities"]["writing_format"] == "riff"
91
+
92
+ # Check technical info
93
+ tech_info = result["technical_info"]
94
+ assert tech_info["file_extension"] == ".wav"
95
+ assert tech_info["audio_format_name"] == "WAV"
96
+ assert tech_info["is_flac_md5_valid"] is None # Not a FLAC file
97
+
98
+ # Check format metadata
99
+ assert "riff" in result["metadata_format"]
100
+ assert "id3v2" in result["metadata_format"]
101
+ assert "id3v1" in result["metadata_format"]
102
+
103
+ # Check headers
104
+ assert "riff" in result["headers"]
105
+ assert "id3v2" in result["headers"]
106
+ assert "id3v1" in result["headers"]
107
+
108
+ def test_get_full_metadata_without_headers(self, sample_mp3_file: Path):
109
+ result = get_full_metadata(sample_mp3_file, include_headers=False)
110
+
111
+ # Should still have basic structure
112
+ assert "unified_metadata" in result
113
+ assert "technical_info" in result
114
+ assert "metadata_format" in result
115
+ assert "format_priorities" in result
116
+
117
+ # Headers and raw metadata should be empty or minimal
118
+ assert "headers" in result
119
+ assert "raw_metadata" in result
120
+
121
+ def test_get_full_metadata_without_technical(self, sample_mp3_file: Path):
122
+ result = get_full_metadata(sample_mp3_file, include_technical=False)
123
+
124
+ # Should still have basic structure
125
+ assert "unified_metadata" in result
126
+ assert "metadata_format" in result
127
+ assert "headers" in result
128
+ assert "raw_metadata" in result
129
+ assert "format_priorities" in result
130
+
131
+ # Technical info should be empty or minimal
132
+ assert "technical_info" in result
133
+
134
+ def test_get_full_metadata_file_with_no_metadata(self):
135
+ with temp_file_with_metadata({}, "mp3") as temp_file_path:
136
+ result = get_full_metadata(temp_file_path)
137
+
138
+ # Should still return complete structure
139
+ assert "unified_metadata" in result
140
+ assert "technical_info" in result
141
+ assert "metadata_format" in result
142
+ assert "headers" in result
143
+ assert "raw_metadata" in result
144
+ assert "format_priorities" in result
145
+
146
+ # Unified metadata should be empty or minimal
147
+ assert isinstance(result["unified_metadata"], dict)
148
+
149
+ # Technical info should still be present
150
+ tech_info = result["technical_info"]
151
+ assert "duration_seconds" in tech_info
152
+ assert "bitrate_kbps" in tech_info
153
+ assert "file_size_bytes" in tech_info
154
+
155
+ def test_get_full_metadata_headers_present_flags(self, sample_mp3_file: Path):
156
+ result = get_full_metadata(sample_mp3_file)
157
+
158
+ # Check ID3v2 headers
159
+ id3v2_headers = result["headers"]["id3v2"]
160
+ assert "present" in id3v2_headers
161
+ assert "version" in id3v2_headers
162
+ assert "header_size_bytes" in id3v2_headers
163
+ assert "flags" in id3v2_headers
164
+ assert "extended_header" in id3v2_headers
165
+
166
+ # Check ID3v1 headers
167
+ id3v1_headers = result["headers"]["id3v1"]
168
+ assert "present" in id3v1_headers
169
+ assert "position" in id3v1_headers
170
+ assert "size_bytes" in id3v1_headers
171
+ assert "version" in id3v1_headers
172
+ assert "has_track_number" in id3v1_headers
173
+
174
+ def test_get_full_metadata_raw_metadata_structure(self, sample_mp3_file: Path):
175
+ result = get_full_metadata(sample_mp3_file)
176
+
177
+ # Check ID3v2 raw metadata
178
+ id3v2_raw = result["raw_metadata"]["id3v2"]
179
+ assert "raw_data" in id3v2_raw
180
+ assert "parsed_fields" in id3v2_raw
181
+ assert "frames" in id3v2_raw
182
+ assert "comments" in id3v2_raw
183
+ assert "chunk_structure" in id3v2_raw
184
+
185
+ # Check ID3v1 raw metadata
186
+ id3v1_raw = result["raw_metadata"]["id3v1"]
187
+ assert "raw_data" in id3v1_raw
188
+ assert "parsed_fields" in id3v1_raw
189
+ assert "frames" in id3v1_raw
190
+ assert "comments" in id3v1_raw
191
+ assert "chunk_structure" in id3v1_raw
192
+
193
+ def test_get_full_metadata_consistency_with_merged_metadata(self, sample_mp3_file: Path):
194
+ from audiometa import get_unified_metadata
195
+
196
+ full_result = get_full_metadata(sample_mp3_file)
197
+ merged_result = get_unified_metadata(sample_mp3_file)
198
+
199
+ # Should be identical
200
+ assert full_result["unified_metadata"] == merged_result
201
+
202
+ def test_get_full_metadata_technical_info_accuracy(self, sample_mp3_file: Path):
203
+ audio_file = _AudioFile(sample_mp3_file)
204
+ result = get_full_metadata(sample_mp3_file)
205
+
206
+ tech_info = result["technical_info"]
207
+
208
+ # Compare with direct _AudioFile methods
209
+ assert tech_info["duration_seconds"] == audio_file.get_duration_in_sec()
210
+ assert tech_info["bitrate_kbps"] == audio_file.get_bitrate()
211
+ assert tech_info["sample_rate_hz"] == audio_file.get_sample_rate()
212
+ assert tech_info["channels"] == audio_file.get_channels()
213
+ assert tech_info["file_size_bytes"] == audio_file.get_file_size()
214
+ assert tech_info["file_extension"] == audio_file.file_extension
215
+ assert tech_info["audio_format_name"] == audio_file.get_audio_format_name()
216
+
217
+ def test_get_full_metadata_flac_md5_validation(self, sample_flac_file: Path):
218
+ result = get_full_metadata(sample_flac_file)
219
+
220
+ tech_info = result["technical_info"]
221
+ assert "is_flac_md5_valid" in tech_info
222
+ assert isinstance(tech_info["is_flac_md5_valid"], bool)
223
+
224
+ def test_get_full_metadata_error_handling(self):
225
+ # Test with non-existent file
226
+ with pytest.raises(FileNotFoundError):
227
+ get_full_metadata("non_existent_file.mp3")
228
+
229
+ # Test with unsupported file type
230
+ import tempfile
231
+
232
+ from audiometa.exceptions import FileTypeNotSupportedError
233
+
234
+ with tempfile.NamedTemporaryFile(suffix=".txt", delete=False) as tmp_file:
235
+ tmp_path = tmp_file.name
236
+
237
+ try:
238
+ with pytest.raises(FileTypeNotSupportedError):
239
+ get_full_metadata(tmp_path)
240
+ finally:
241
+ Path(tmp_path).unlink()
242
+
243
+ def test_get_full_metadata_performance_optimization(self, sample_mp3_file: Path):
244
+ """Test that performance optimization flags work correctly."""
245
+ # Test with minimal data
246
+ result_minimal = get_full_metadata(sample_mp3_file, include_headers=False, include_technical=False)
247
+
248
+ # Should still have basic structure
249
+ assert "unified_metadata" in result_minimal
250
+ assert "metadata_format" in result_minimal
251
+ assert "format_priorities" in result_minimal
252
+
253
+ # Headers and technical info should be minimal
254
+ assert "headers" in result_minimal
255
+ assert "technical_info" in result_minimal
256
+
257
+ def test_get_full_metadata_format_specific_metadata_isolation(self, sample_mp3_file: Path):
258
+ result = get_full_metadata(sample_mp3_file)
259
+
260
+ # Each format should have its own metadata section
261
+ metadata_format = result["metadata_format"]
262
+
263
+ # ID3v2 metadata should be separate from ID3v1
264
+ if "id3v2" in metadata_format and "id3v1" in metadata_format:
265
+ id3v2_metadata = metadata_format["id3v2"]
266
+ id3v1_metadata = metadata_format["id3v1"]
267
+
268
+ # They should be separate dictionaries
269
+ assert isinstance(id3v2_metadata, dict)
270
+ assert isinstance(id3v1_metadata, dict)
271
+
272
+ # They might have different content or structure
273
+ # This is expected and correct behavior
274
+
275
+ def test_get_full_metadata_header_detection_accuracy(self, sample_mp3_file: Path):
276
+ result = get_full_metadata(sample_mp3_file)
277
+
278
+ # Check that headers are detected correctly
279
+ headers = result["headers"]
280
+
281
+ for metadata_format_name, header_info in headers.items():
282
+ assert "present" in header_info
283
+ assert isinstance(header_info["present"], bool)
284
+
285
+ if header_info["present"]:
286
+ # If header is present, should have additional info
287
+ if metadata_format_name == "id3v2":
288
+ assert "version" in header_info
289
+ assert "header_size_bytes" in header_info
290
+ elif metadata_format_name == "id3v1":
291
+ assert "position" in header_info
292
+ assert "size_bytes" in header_info
293
+ elif metadata_format_name == "vorbis":
294
+ assert "vendor_string" in header_info
295
+ assert "comment_count" in header_info
296
+ elif metadata_format_name == "riff":
297
+ assert "chunk_info" in header_info
@@ -0,0 +1,231 @@
1
+ """Edge case tests for get_full_metadata function."""
2
+
3
+ import tempfile
4
+ from pathlib import Path
5
+
6
+ import pytest
7
+
8
+ from audiometa import get_full_metadata
9
+ from audiometa.exceptions import FileCorruptedError, FileTypeNotSupportedError
10
+ from audiometa.test.helpers.temp_file_with_metadata import temp_file_with_metadata
11
+
12
+
13
+ @pytest.mark.integration
14
+ class TestGetFullMetadataEdgeCases:
15
+ def test_get_full_metadata_empty_file(self):
16
+ with temp_file_with_metadata({}, "mp3") as temp_file_path:
17
+ # Should handle gracefully and return structure with minimal data
18
+ result = get_full_metadata(temp_file_path)
19
+
20
+ # Should still return complete structure
21
+ assert "unified_metadata" in result
22
+ assert "technical_info" in result
23
+ assert "metadata_format" in result
24
+ assert "headers" in result
25
+ assert "raw_metadata" in result
26
+ assert "format_priorities" in result
27
+
28
+ def test_get_full_metadata_corrupted_file(self):
29
+ with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as temp_file:
30
+ temp_path = temp_file.name
31
+ # Write some garbage data
32
+ temp_file.write(b"This is not a valid audio file")
33
+
34
+ try:
35
+ with pytest.raises(FileCorruptedError):
36
+ get_full_metadata(temp_path)
37
+ finally:
38
+ Path(temp_path).unlink()
39
+
40
+ def test_get_full_metadata_file_with_only_headers_no_metadata(self):
41
+ with temp_file_with_metadata({}, "mp3") as temp_file_path:
42
+ result = get_full_metadata(temp_file_path)
43
+
44
+ # Should detect headers even if no metadata content
45
+ headers = result["headers"]
46
+
47
+ for _metadata_format_name, header_info in headers.items():
48
+ # Headers might be present even without metadata content
49
+ assert "present" in header_info
50
+ assert isinstance(header_info["present"], bool)
51
+
52
+ def test_get_full_metadata_large_file(self, sample_mp3_file: Path):
53
+ # This test ensures the function can handle larger files
54
+ result = get_full_metadata(sample_mp3_file)
55
+
56
+ # Should complete successfully
57
+ assert "unified_metadata" in result
58
+ assert "technical_info" in result
59
+
60
+ # File size should be reasonable
61
+ tech_info = result["technical_info"]
62
+ assert tech_info["file_size_bytes"] > 0
63
+
64
+ def test_get_full_metadata_file_with_mixed_formats(self, sample_mp3_file: Path):
65
+ result = get_full_metadata(sample_mp3_file)
66
+
67
+ # Should handle multiple formats gracefully
68
+ metadata_format = result["metadata_format"]
69
+ headers = result["headers"]
70
+
71
+ # Each format should have its own section
72
+ for metadata_format_name in ["id3v2", "id3v1"]:
73
+ assert metadata_format_name in metadata_format
74
+ assert metadata_format_name in headers
75
+
76
+ # Each should be a dictionary
77
+ assert isinstance(metadata_format[metadata_format_name], dict)
78
+ assert isinstance(headers[metadata_format_name], dict)
79
+
80
+ def test_get_full_metadata_with_unicode_metadata(self, sample_mp3_file: Path):
81
+ # This test ensures unicode handling works correctly
82
+ result = get_full_metadata(sample_mp3_file)
83
+
84
+ # Should handle unicode in metadata
85
+ unified_metadata = result["unified_metadata"]
86
+
87
+ # Check that string values are properly handled
88
+ for _key, value in unified_metadata.items():
89
+ if isinstance(value, str):
90
+ # Should be able to handle unicode
91
+ assert isinstance(value, str)
92
+ elif isinstance(value, list):
93
+ for item in value:
94
+ if isinstance(item, str):
95
+ assert isinstance(item, str)
96
+
97
+ def test_get_full_metadata_performance_with_headers_disabled(self, sample_mp3_file: Path):
98
+ result = get_full_metadata(sample_mp3_file, include_headers=False)
99
+
100
+ # Should still work correctly
101
+ assert "unified_metadata" in result
102
+ assert "technical_info" in result
103
+ assert "metadata_format" in result
104
+
105
+ # Headers should be minimal
106
+ headers = result["headers"]
107
+ for _metadata_format_name, header_info in headers.items():
108
+ # Should have basic structure but minimal data
109
+ assert "present" in header_info
110
+
111
+ def test_get_full_metadata_performance_with_technical_disabled(self, sample_mp3_file: Path):
112
+ result = get_full_metadata(sample_mp3_file, include_technical=False)
113
+
114
+ # Should still work correctly
115
+ assert "unified_metadata" in result
116
+ assert "metadata_format" in result
117
+ assert "headers" in result
118
+
119
+ # Technical info should be minimal
120
+ tech_info = result["technical_info"]
121
+ assert isinstance(tech_info, dict)
122
+
123
+ def test_get_full_metadata_memory_usage(self, sample_mp3_file: Path):
124
+ # This is more of a smoke test to ensure no obvious memory leaks
125
+ for _ in range(10):
126
+ result = get_full_metadata(sample_mp3_file)
127
+
128
+ # Should complete successfully each time
129
+ assert "unified_metadata" in result
130
+ assert "technical_info" in result
131
+
132
+ # Clear result to help with memory management
133
+ del result
134
+
135
+ def test_get_full_metadata_concurrent_access(self, sample_mp3_file: Path):
136
+ import threading
137
+
138
+ results = []
139
+ errors = []
140
+
141
+ def get_metadata():
142
+ try:
143
+ result = get_full_metadata(sample_mp3_file)
144
+ results.append(result)
145
+ except Exception as e:
146
+ errors.append(e)
147
+
148
+ # Create multiple threads accessing the same file
149
+ threads = []
150
+ for _ in range(5):
151
+ thread = threading.Thread(target=get_metadata)
152
+ threads.append(thread)
153
+ thread.start()
154
+
155
+ # Wait for all threads to complete
156
+ for thread in threads:
157
+ thread.join()
158
+
159
+ # Should have 5 successful results
160
+ assert len(results) == 5
161
+ assert len(errors) == 0
162
+
163
+ # All results should be identical
164
+ first_result = results[0]
165
+ for result in results[1:]:
166
+ assert result["format_priorities"] == first_result["format_priorities"]
167
+ assert result["technical_info"]["file_size_bytes"] == first_result["technical_info"]["file_size_bytes"]
168
+
169
+ def test_get_full_metadata_error_recovery(self):
170
+ # Test with non-existent file
171
+ with pytest.raises(FileNotFoundError):
172
+ get_full_metadata("non_existent_file.mp3")
173
+
174
+ # Test with unsupported file type (create the file first)
175
+ with tempfile.NamedTemporaryFile(suffix=".txt", delete=False) as temp_file:
176
+ temp_path = temp_file.name
177
+ temp_file.write(b"This is not an audio file")
178
+
179
+ try:
180
+ with pytest.raises(FileTypeNotSupportedError):
181
+ get_full_metadata(temp_path)
182
+ finally:
183
+ Path(temp_path).unlink()
184
+
185
+ def test_get_full_metadata_structure_consistency(self, sample_mp3_file: Path):
186
+ result1 = get_full_metadata(sample_mp3_file)
187
+ result2 = get_full_metadata(sample_mp3_file)
188
+
189
+ # Structure should be identical
190
+ assert set(result1.keys()) == set(result2.keys())
191
+
192
+ # Each top-level section should have same keys
193
+ for key in result1:
194
+ if key in ["unified_metadata", "technical_info", "metadata_format", "headers", "raw_metadata"]:
195
+ assert set(result1[key].keys()) == set(result2[key].keys())
196
+
197
+ def test_get_full_metadata_with_minimal_metadata(self):
198
+ with temp_file_with_metadata({}, "mp3") as temp_file_path:
199
+ result = get_full_metadata(temp_file_path)
200
+
201
+ # Should still return complete structure
202
+ assert "unified_metadata" in result
203
+ assert "technical_info" in result
204
+ assert "metadata_format" in result
205
+ assert "headers" in result
206
+ assert "raw_metadata" in result
207
+ assert "format_priorities" in result
208
+
209
+ # Unified metadata might be empty or minimal
210
+ unified_metadata = result["unified_metadata"]
211
+ assert isinstance(unified_metadata, dict)
212
+
213
+ # Technical info should still be present
214
+ tech_info = result["technical_info"]
215
+ assert "file_size_bytes" in tech_info
216
+ assert tech_info["file_size_bytes"] >= 0 # Can be 0 for empty files
217
+
218
+ def test_get_full_metadata_format_detection_accuracy(self, sample_mp3_file: Path):
219
+ result = get_full_metadata(sample_mp3_file)
220
+
221
+ # Format priorities should be correct for MP3
222
+ priorities = result["format_priorities"]
223
+ assert priorities["file_extension"] == ".mp3"
224
+ assert "id3v2" in priorities["reading_order"]
225
+ assert "id3v1" in priorities["reading_order"]
226
+ assert priorities["writing_format"] == "id3v2"
227
+
228
+ # Technical info should reflect MP3 format
229
+ tech_info = result["technical_info"]
230
+ assert tech_info["file_extension"] == ".mp3"
231
+ assert tech_info["audio_format_name"] == "MP3"