iOpenPod 2.0.4__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 (681) hide show
  1. device_registry/__init__.py +68 -0
  2. device_registry/authority.py +671 -0
  3. device_registry/data/__init__.py +1 -0
  4. device_registry/data/catalog.py +1115 -0
  5. device_registry/identifiers.py +44 -0
  6. device_registry/metadata.py +352 -0
  7. device_registry/models.py +403 -0
  8. device_registry/py.typed +1 -0
  9. device_registry/registry.py +389 -0
  10. iOpenPod/GUI/PROTOTYPE.md +218 -0
  11. iOpenPod/GUI/delegates/album_card_delegate.py +155 -0
  12. iOpenPod/GUI/delegates/collection_card_delegate.py +124 -0
  13. iOpenPod/GUI/delegates/collection_list_delegate.py +172 -0
  14. iOpenPod/GUI/delegates/photo_album_membership_delegate.py +168 -0
  15. iOpenPod/GUI/delegates/photo_card_delegate.py +142 -0
  16. iOpenPod/GUI/delegates/podcast_delegates.py +726 -0
  17. iOpenPod/GUI/delegates/selection_group_delegate.py +326 -0
  18. iOpenPod/GUI/delegates/track_artwork_delegate.py +92 -0
  19. iOpenPod/GUI/dialogs/artwork_editor.py +816 -0
  20. iOpenPod/GUI/dialogs/device_picker.py +396 -0
  21. iOpenPod/GUI/dialogs/external_playlist_files.py +286 -0
  22. iOpenPod/GUI/dialogs/library_review.py +379 -0
  23. iOpenPod/GUI/dialogs/linux_identity_setup.py +182 -0
  24. iOpenPod/GUI/dialogs/media_folders.py +440 -0
  25. iOpenPod/GUI/dialogs/media_tools_setup.py +175 -0
  26. iOpenPod/GUI/dialogs/metadata_editor.py +819 -0
  27. iOpenPod/GUI/dialogs/photo_album_editor.py +122 -0
  28. iOpenPod/GUI/dialogs/photo_album_manager.py +143 -0
  29. iOpenPod/GUI/dialogs/photo_metadata_editor.py +365 -0
  30. iOpenPod/GUI/dialogs/playlist_editor.py +1064 -0
  31. iOpenPod/GUI/dialogs/playlist_export.py +148 -0
  32. iOpenPod/GUI/dialogs/podcast_search.py +326 -0
  33. iOpenPod/GUI/dialogs/podcast_sync_settings.py +224 -0
  34. iOpenPod/GUI/dialogs/scrobble_report.py +68 -0
  35. iOpenPod/GUI/dialogs/tag_normalizer.py +489 -0
  36. iOpenPod/GUI/host_library_browser.py +312 -0
  37. iOpenPod/GUI/main_window.py +2069 -0
  38. iOpenPod/GUI/navigation.py +113 -0
  39. iOpenPod/GUI/pages/backup_page.py +1254 -0
  40. iOpenPod/GUI/pages/collection_page.py +504 -0
  41. iOpenPod/GUI/pages/library_page.py +227 -0
  42. iOpenPod/GUI/pages/photo_page.py +753 -0
  43. iOpenPod/GUI/pages/placeholder_page.py +75 -0
  44. iOpenPod/GUI/pages/playlist_page.py +486 -0
  45. iOpenPod/GUI/pages/podcast_page.py +1049 -0
  46. iOpenPod/GUI/pages/settings_page.py +950 -0
  47. iOpenPod/GUI/pages/sync_execution_page.py +589 -0
  48. iOpenPod/GUI/pages/sync_plan_page.py +401 -0
  49. iOpenPod/GUI/pages/synesthesia_page.py +175 -0
  50. iOpenPod/GUI/pages/track_page.py +90 -0
  51. iOpenPod/GUI/presentation/application_icon.py +13 -0
  52. iOpenPod/GUI/presentation/artwork.py +128 -0
  53. iOpenPod/GUI/presentation/artwork_provider.py +317 -0
  54. iOpenPod/GUI/presentation/backup_messages.py +480 -0
  55. iOpenPod/GUI/presentation/collection_text.py +27 -0
  56. iOpenPod/GUI/presentation/device_images.py +81 -0
  57. iOpenPod/GUI/presentation/display.py +20 -0
  58. iOpenPod/GUI/presentation/eta.py +61 -0
  59. iOpenPod/GUI/presentation/i18n/manager.py +146 -0
  60. iOpenPod/GUI/presentation/i18n/text.py +155 -0
  61. iOpenPod/GUI/presentation/i18n/translations/iopenpod_de.qm +0 -0
  62. iOpenPod/GUI/presentation/i18n/translations/iopenpod_de.ts +10060 -0
  63. iOpenPod/GUI/presentation/i18n/translations/iopenpod_es.qm +0 -0
  64. iOpenPod/GUI/presentation/i18n/translations/iopenpod_es.ts +10060 -0
  65. iOpenPod/GUI/presentation/i18n/workflow.py +24 -0
  66. iOpenPod/GUI/presentation/icons.py +51 -0
  67. iOpenPod/GUI/presentation/image_color.py +136 -0
  68. iOpenPod/GUI/presentation/library_card.py +252 -0
  69. iOpenPod/GUI/presentation/middle_mouse_scrolling.py +208 -0
  70. iOpenPod/GUI/presentation/photo.py +47 -0
  71. iOpenPod/GUI/presentation/photo_provider.py +182 -0
  72. iOpenPod/GUI/presentation/podcast_artwork_provider.py +83 -0
  73. iOpenPod/GUI/presentation/podcast_collection_artwork.py +91 -0
  74. iOpenPod/GUI/presentation/selection_checkbox.py +148 -0
  75. iOpenPod/GUI/presentation/theme/backup_styles.py +84 -0
  76. iOpenPod/GUI/presentation/theme/checkbox_style.py +173 -0
  77. iOpenPod/GUI/presentation/theme/dynamic.py +187 -0
  78. iOpenPod/GUI/presentation/theme/manager.py +235 -0
  79. iOpenPod/GUI/presentation/theme/podcast_styles.py +163 -0
  80. iOpenPod/GUI/presentation/theme/stylesheet.py +2322 -0
  81. iOpenPod/GUI/presentation/theme/tokens.py +631 -0
  82. iOpenPod/GUI/presentation/track_drag_preview.py +229 -0
  83. iOpenPod/GUI/sync_workspace.py +795 -0
  84. iOpenPod/GUI/synesthesia/__init__.py +36 -0
  85. iOpenPod/GUI/synesthesia/conductor.py +677 -0
  86. iOpenPod/GUI/synesthesia/motion_phases.py +81 -0
  87. iOpenPod/GUI/synesthesia/persistent_world.py +751 -0
  88. iOpenPod/GUI/synesthesia/preview.py +76 -0
  89. iOpenPod/GUI/synesthesia/render_resolution.py +104 -0
  90. iOpenPod/GUI/synesthesia/renderer.py +1653 -0
  91. iOpenPod/GUI/synesthesia/scene_director.py +1632 -0
  92. iOpenPod/GUI/synesthesia/shader_sources/comets.frag +21 -0
  93. iOpenPod/GUI/synesthesia/shader_sources/comets.frag.qsb +0 -0
  94. iOpenPod/GUI/synesthesia/shader_sources/comets.vert +98 -0
  95. iOpenPod/GUI/synesthesia/shader_sources/comets.vert.qsb +0 -0
  96. iOpenPod/GUI/synesthesia/shader_sources/events.frag +166 -0
  97. iOpenPod/GUI/synesthesia/shader_sources/events.frag.qsb +0 -0
  98. iOpenPod/GUI/synesthesia/shader_sources/feedback.frag +144 -0
  99. iOpenPod/GUI/synesthesia/shader_sources/feedback.frag.qsb +0 -0
  100. iOpenPod/GUI/synesthesia/shader_sources/field_sim.comp +200 -0
  101. iOpenPod/GUI/synesthesia/shader_sources/field_sim.comp.qsb +0 -0
  102. iOpenPod/GUI/synesthesia/shader_sources/filaments.frag +21 -0
  103. iOpenPod/GUI/synesthesia/shader_sources/filaments.frag.qsb +0 -0
  104. iOpenPod/GUI/synesthesia/shader_sources/filaments.vert +95 -0
  105. iOpenPod/GUI/synesthesia/shader_sources/filaments.vert.qsb +0 -0
  106. iOpenPod/GUI/synesthesia/shader_sources/fullscreen.vert +13 -0
  107. iOpenPod/GUI/synesthesia/shader_sources/fullscreen.vert.qsb +0 -0
  108. iOpenPod/GUI/synesthesia/shader_sources/particles.frag +25 -0
  109. iOpenPod/GUI/synesthesia/shader_sources/particles.frag.qsb +0 -0
  110. iOpenPod/GUI/synesthesia/shader_sources/particles.vert +113 -0
  111. iOpenPod/GUI/synesthesia/shader_sources/particles.vert.qsb +0 -0
  112. iOpenPod/GUI/synesthesia/shader_sources/present.frag +95 -0
  113. iOpenPod/GUI/synesthesia/shader_sources/present.frag.qsb +0 -0
  114. iOpenPod/GUI/synesthesia/shader_sources/scenes.frag +758 -0
  115. iOpenPod/GUI/synesthesia/shader_sources/scenes.frag.qsb +0 -0
  116. iOpenPod/GUI/synesthesia/visual_style.py +129 -0
  117. iOpenPod/GUI/widgets/album_grid.py +86 -0
  118. iOpenPod/GUI/widgets/app_combo_box.py +158 -0
  119. iOpenPod/GUI/widgets/artwork_view.py +124 -0
  120. iOpenPod/GUI/widgets/browser_chrome.py +147 -0
  121. iOpenPod/GUI/widgets/collection_detail.py +321 -0
  122. iOpenPod/GUI/widgets/collection_grid.py +87 -0
  123. iOpenPod/GUI/widgets/elided_label.py +171 -0
  124. iOpenPod/GUI/widgets/equalized_grid.py +160 -0
  125. iOpenPod/GUI/widgets/eta_label.py +61 -0
  126. iOpenPod/GUI/widgets/host_media_drop.py +144 -0
  127. iOpenPod/GUI/widgets/ipod_preferences.py +96 -0
  128. iOpenPod/GUI/widgets/library_content.py +193 -0
  129. iOpenPod/GUI/widgets/library_drag_grid.py +102 -0
  130. iOpenPod/GUI/widgets/library_toolbar.py +257 -0
  131. iOpenPod/GUI/widgets/media_tools_settings.py +145 -0
  132. iOpenPod/GUI/widgets/metadata_field_editor.py +416 -0
  133. iOpenPod/GUI/widgets/photo_album_membership_grid.py +70 -0
  134. iOpenPod/GUI/widgets/photo_grid.py +131 -0
  135. iOpenPod/GUI/widgets/photo_inspector.py +612 -0
  136. iOpenPod/GUI/widgets/playback_pane.py +881 -0
  137. iOpenPod/GUI/widgets/player_bar.py +1087 -0
  138. iOpenPod/GUI/widgets/playlist_artwork_banner.py +203 -0
  139. iOpenPod/GUI/widgets/playlist_tree.py +790 -0
  140. iOpenPod/GUI/widgets/podcast_artwork.py +153 -0
  141. iOpenPod/GUI/widgets/scrobbling_settings.py +210 -0
  142. iOpenPod/GUI/widgets/search_field.py +99 -0
  143. iOpenPod/GUI/widgets/selection_grid.py +55 -0
  144. iOpenPod/GUI/widgets/selection_group_transition.py +298 -0
  145. iOpenPod/GUI/widgets/setting_group.py +75 -0
  146. iOpenPod/GUI/widgets/sidebar.py +695 -0
  147. iOpenPod/GUI/widgets/status_controls.py +94 -0
  148. iOpenPod/GUI/widgets/status_list.py +386 -0
  149. iOpenPod/GUI/widgets/sync_preparation_progress.py +130 -0
  150. iOpenPod/GUI/widgets/sync_review_group.py +285 -0
  151. iOpenPod/GUI/widgets/sync_selection_actions.py +101 -0
  152. iOpenPod/GUI/widgets/sync_settings.py +510 -0
  153. iOpenPod/GUI/widgets/sync_storage_bar.py +241 -0
  154. iOpenPod/GUI/widgets/themed_buttons.py +351 -0
  155. iOpenPod/GUI/widgets/track_actions.py +861 -0
  156. iOpenPod/GUI/widgets/track_list_header.py +214 -0
  157. iOpenPod/GUI/widgets/track_table.py +483 -0
  158. iOpenPod/__main__.py +6 -0
  159. iOpenPod/app/app.py +195 -0
  160. iOpenPod/app/artwork_controller.py +254 -0
  161. iOpenPod/app/artwork_import.py +195 -0
  162. iOpenPod/app/backup_controller.py +591 -0
  163. iOpenPod/app/backups/__init__.py +77 -0
  164. iOpenPod/app/backups/_json.py +60 -0
  165. iOpenPod/app/backups/archive.py +500 -0
  166. iOpenPod/app/backups/models.py +628 -0
  167. iOpenPod/app/backups/original_archive.py +249 -0
  168. iOpenPod/app/backups/outcomes.py +419 -0
  169. iOpenPod/app/backups/recovery.py +183 -0
  170. iOpenPod/app/backups/repository.py +2139 -0
  171. iOpenPod/app/backups/scope.py +57 -0
  172. iOpenPod/app/backups/service.py +1281 -0
  173. iOpenPod/app/chaptered_conversion.py +258 -0
  174. iOpenPod/app/chaptered_conversion_controller.py +228 -0
  175. iOpenPod/app/context.py +236 -0
  176. iOpenPod/app/core/certifi_ssl.py +9 -0
  177. iOpenPod/app/core/eta.py +340 -0
  178. iOpenPod/app/core/logging.py +63 -0
  179. iOpenPod/app/core/runtime.py +52 -0
  180. iOpenPod/app/core/settings/definitions.py +299 -0
  181. iOpenPod/app/core/settings/paths.py +43 -0
  182. iOpenPod/app/core/settings/service.py +134 -0
  183. iOpenPod/app/core/settings/stores.py +250 -0
  184. iOpenPod/app/core/settings/transcoding.py +152 -0
  185. iOpenPod/app/core/status.py +205 -0
  186. iOpenPod/app/core/version.py +14 -0
  187. iOpenPod/app/device_controller.py +562 -0
  188. iOpenPod/app/display_text.py +58 -0
  189. iOpenPod/app/export_tagging.py +301 -0
  190. iOpenPod/app/host_media_controller.py +250 -0
  191. iOpenPod/app/host_media_fingerprint.py +206 -0
  192. iOpenPod/app/host_media_folders.py +162 -0
  193. iOpenPod/app/host_media_library.py +2396 -0
  194. iOpenPod/app/host_playlists.py +232 -0
  195. iOpenPod/app/ipod_media_controller.py +146 -0
  196. iOpenPod/app/library_export.py +714 -0
  197. iOpenPod/app/library_export_controller.py +271 -0
  198. iOpenPod/app/library_sync_helper.py +1108 -0
  199. iOpenPod/app/library_workspace.py +1294 -0
  200. iOpenPod/app/library_write.py +130 -0
  201. iOpenPod/app/library_write_controller.py +608 -0
  202. iOpenPod/app/library_write_inspection.py +424 -0
  203. iOpenPod/app/lyrics_controller.py +161 -0
  204. iOpenPod/app/media/__init__.py +20 -0
  205. iOpenPod/app/media/content_type.py +42 -0
  206. iOpenPod/app/media/importing.py +252 -0
  207. iOpenPod/app/media/inspection.py +368 -0
  208. iOpenPod/app/media/lyrics.py +143 -0
  209. iOpenPod/app/media/lyrics_reader.py +96 -0
  210. iOpenPod/app/media/models.py +95 -0
  211. iOpenPod/app/media/music_paths.py +95 -0
  212. iOpenPod/app/media/photo_sync.py +266 -0
  213. iOpenPod/app/media/progress.py +23 -0
  214. iOpenPod/app/media/sync_artwork.py +58 -0
  215. iOpenPod/app/media/tags.py +429 -0
  216. iOpenPod/app/media/transcoding.py +1031 -0
  217. iOpenPod/app/media_tools_controller.py +235 -0
  218. iOpenPod/app/metadata_fields.py +212 -0
  219. iOpenPod/app/models/album_list_model.py +285 -0
  220. iOpenPod/app/models/artwork.py +49 -0
  221. iOpenPod/app/models/artwork_seed.py +13 -0
  222. iOpenPod/app/models/collection_list_model.py +332 -0
  223. iOpenPod/app/models/device.py +138 -0
  224. iOpenPod/app/models/filter_compat.py +22 -0
  225. iOpenPod/app/models/ipod_preferences.py +27 -0
  226. iOpenPod/app/models/keyed_projection.py +101 -0
  227. iOpenPod/app/models/library_drag.py +141 -0
  228. iOpenPod/app/models/library_filter_models.py +425 -0
  229. iOpenPod/app/models/photo_album_membership_model.py +189 -0
  230. iOpenPod/app/models/photo_list_model.py +554 -0
  231. iOpenPod/app/models/photos.py +62 -0
  232. iOpenPod/app/models/playback_models.py +525 -0
  233. iOpenPod/app/models/playlist_tree_model.py +295 -0
  234. iOpenPod/app/models/podcast_list_models.py +355 -0
  235. iOpenPod/app/models/selection_grouping.py +323 -0
  236. iOpenPod/app/models/sync_plan_table_model.py +383 -0
  237. iOpenPod/app/models/sync_selection.py +274 -0
  238. iOpenPod/app/models/track_columns.py +982 -0
  239. iOpenPod/app/models/track_order.py +19 -0
  240. iOpenPod/app/models/track_table_model.py +330 -0
  241. iOpenPod/app/music_import_controller.py +205 -0
  242. iOpenPod/app/photo_controller.py +350 -0
  243. iOpenPod/app/playback/__init__.py +21 -0
  244. iOpenPod/app/playback/_qt_io.py +54 -0
  245. iOpenPod/app/playback/_qt_logging.py +101 -0
  246. iOpenPod/app/playback/backend.py +92 -0
  247. iOpenPod/app/playback/qt_backend.py +224 -0
  248. iOpenPod/app/playback/runtime_check.py +82 -0
  249. iOpenPod/app/playback/system_media/__init__.py +25 -0
  250. iOpenPod/app/playback/system_media/bridge.py +284 -0
  251. iOpenPod/app/playback/system_media/factory.py +66 -0
  252. iOpenPod/app/playback/system_media/linux.py +699 -0
  253. iOpenPod/app/playback/system_media/macos.py +430 -0
  254. iOpenPod/app/playback/system_media/session.py +145 -0
  255. iOpenPod/app/playback/system_media/windows.py +554 -0
  256. iOpenPod/app/playback_controller.py +352 -0
  257. iOpenPod/app/podcasts/__init__.py +59 -0
  258. iOpenPod/app/podcasts/artwork_controller.py +215 -0
  259. iOpenPod/app/podcasts/catalog.py +630 -0
  260. iOpenPod/app/podcasts/controller.py +610 -0
  261. iOpenPod/app/podcasts/documents.py +292 -0
  262. iOpenPod/app/podcasts/feed_client.py +460 -0
  263. iOpenPod/app/podcasts/identity.py +115 -0
  264. iOpenPod/app/podcasts/media.py +151 -0
  265. iOpenPod/app/podcasts/models.py +294 -0
  266. iOpenPod/app/podcasts/store.py +236 -0
  267. iOpenPod/app/podcasts/sync.py +459 -0
  268. iOpenPod/app/podcasts/sync_artwork.py +98 -0
  269. iOpenPod/app/podcasts/sync_preparation.py +172 -0
  270. iOpenPod/app/scrobbling/__init__.py +1 -0
  271. iOpenPod/app/scrobbling/_json.py +25 -0
  272. iOpenPod/app/scrobbling/clients.py +373 -0
  273. iOpenPod/app/scrobbling/controller.py +304 -0
  274. iOpenPod/app/scrobbling/credentials.py +98 -0
  275. iOpenPod/app/scrobbling/models.py +80 -0
  276. iOpenPod/app/scrobbling/queue.py +219 -0
  277. iOpenPod/app/scrobbling/reporting.py +132 -0
  278. iOpenPod/app/scrobbling/service.py +191 -0
  279. iOpenPod/app/scrobbling/settings.py +22 -0
  280. iOpenPod/app/scrobbling/timestamps.py +56 -0
  281. iOpenPod/app/services/device_coordinator.py +4175 -0
  282. iOpenPod/app/services/device_time.py +42 -0
  283. iOpenPod/app/services/ipod_preferences.py +359 -0
  284. iOpenPod/app/services/library_resources.py +677 -0
  285. iOpenPod/app/services/linux_identity.py +259 -0
  286. iOpenPod/app/services/media_tools.py +273 -0
  287. iOpenPod/app/services/volume_presentation.py +206 -0
  288. iOpenPod/app/smart_playlist_preview.py +264 -0
  289. iOpenPod/app/sync_controller.py +677 -0
  290. iOpenPod/app/sync_execution.py +2319 -0
  291. iOpenPod/app/sync_plan.py +680 -0
  292. iOpenPod/app/sync_storage.py +131 -0
  293. iOpenPod/app/synesthesia/__init__.py +143 -0
  294. iOpenPod/app/synesthesia/_rhythm.py +167 -0
  295. iOpenPod/app/synesthesia/_signals.py +226 -0
  296. iOpenPod/app/synesthesia/_structure.py +229 -0
  297. iOpenPod/app/synesthesia/audio.py +227 -0
  298. iOpenPod/app/synesthesia/backend.py +202 -0
  299. iOpenPod/app/synesthesia/controller.py +356 -0
  300. iOpenPod/app/synesthesia/dsp.py +882 -0
  301. iOpenPod/app/synesthesia/enrichment.py +407 -0
  302. iOpenPod/app/synesthesia/models.py +796 -0
  303. iOpenPod/app/tag_normalization_controller.py +197 -0
  304. iOpenPod/app/tag_normalizer.py +314 -0
  305. iOpenPod/app/track_conversion.py +110 -0
  306. iOpenPod/app/track_playback_policy.py +60 -0
  307. iOpenPod/app/updates/__init__.py +1 -0
  308. iOpenPod/app/updates/backend.py +97 -0
  309. iOpenPod/app/updates/bootstrap.py +116 -0
  310. iOpenPod/app/updates/controller.py +386 -0
  311. iOpenPod/app/updates/github.py +222 -0
  312. iOpenPod/app/updates/helper.py +228 -0
  313. iOpenPod/app/updates/installation.py +94 -0
  314. iOpenPod/app/updates/platform.py +156 -0
  315. iOpenPod/app/updates/portable.py +430 -0
  316. iOpenPod/app/updates/processes.py +127 -0
  317. iOpenPod/app/updates/processes_windows.py +229 -0
  318. iOpenPod/app/updates/pypi.py +288 -0
  319. iOpenPod/app/updates/python_helper.py +352 -0
  320. iOpenPod/app/updates/python_installation.py +420 -0
  321. iOpenPod/app/updates/releases.py +276 -0
  322. iOpenPod/app/updates/sparkle.py +111 -0
  323. iOpenPod/app/updates/transport.py +183 -0
  324. iOpenPod/app/updates/windows.py +252 -0
  325. iOpenPod/assets/glyphs/README.md +6 -0
  326. iOpenPod/assets/glyphs/album.svg +19 -0
  327. iOpenPod/assets/glyphs/alert.svg +3 -0
  328. iOpenPod/assets/glyphs/bell.svg +4 -0
  329. iOpenPod/assets/glyphs/book.svg +4 -0
  330. iOpenPod/assets/glyphs/box.svg +3 -0
  331. iOpenPod/assets/glyphs/broadcast.svg +8 -0
  332. iOpenPod/assets/glyphs/check-circle.svg +4 -0
  333. iOpenPod/assets/glyphs/chevron-left.svg +1 -0
  334. iOpenPod/assets/glyphs/eject.svg +3 -0
  335. iOpenPod/assets/glyphs/film.svg +4 -0
  336. iOpenPod/assets/glyphs/folder.svg +3 -0
  337. iOpenPod/assets/glyphs/grid.svg +6 -0
  338. iOpenPod/assets/glyphs/list.svg +4 -0
  339. iOpenPod/assets/glyphs/minus.svg +3 -0
  340. iOpenPod/assets/glyphs/monitor.svg +4 -0
  341. iOpenPod/assets/glyphs/music.svg +5 -0
  342. iOpenPod/assets/glyphs/pause.svg +4 -0
  343. iOpenPod/assets/glyphs/photo.svg +3 -0
  344. iOpenPod/assets/glyphs/play-last.svg +6 -0
  345. iOpenPod/assets/glyphs/play-next.svg +6 -0
  346. iOpenPod/assets/glyphs/play.svg +3 -0
  347. iOpenPod/assets/glyphs/playlist.svg +7 -0
  348. iOpenPod/assets/glyphs/plus.svg +3 -0
  349. iOpenPod/assets/glyphs/refresh.svg +3 -0
  350. iOpenPod/assets/glyphs/search.svg +3 -0
  351. iOpenPod/assets/glyphs/settings-sliders.svg +5 -0
  352. iOpenPod/assets/glyphs/skip-back.svg +4 -0
  353. iOpenPod/assets/glyphs/skip-forward.svg +4 -0
  354. iOpenPod/assets/glyphs/smart-playlist.svg +5 -0
  355. iOpenPod/assets/glyphs/synesthesia.svg +8 -0
  356. iOpenPod/assets/glyphs/user.svg +4 -0
  357. iOpenPod/assets/glyphs/video.svg +4 -0
  358. iOpenPod/assets/glyphs/volume.svg +3 -0
  359. iOpenPod/assets/glyphs/x.svg +3 -0
  360. iOpenPod/assets/icons/DJShott-icon.txt +21 -0
  361. iOpenPod/assets/icons/README.md +13 -0
  362. iOpenPod/assets/icons/icon-128.png +0 -0
  363. iOpenPod/assets/icons/icon-16.png +0 -0
  364. iOpenPod/assets/icons/icon-24.png +0 -0
  365. iOpenPod/assets/icons/icon-256.png +0 -0
  366. iOpenPod/assets/icons/icon-32.png +0 -0
  367. iOpenPod/assets/icons/icon-48.png +0 -0
  368. iOpenPod/assets/icons/icon-64.png +0 -0
  369. iOpenPod/assets/icons/icon.ico +0 -0
  370. iOpenPod/assets/ipod_images/iPod1.png +0 -0
  371. iOpenPod/assets/ipod_images/iPod11-Black.png +0 -0
  372. iOpenPod/assets/ipod_images/iPod11-Silver.png +0 -0
  373. iOpenPod/assets/ipod_images/iPod11B-Black.png +0 -0
  374. iOpenPod/assets/ipod_images/iPod12-Black.png +0 -0
  375. iOpenPod/assets/ipod_images/iPod12-Blue.png +0 -0
  376. iOpenPod/assets/ipod_images/iPod12-Green.png +0 -0
  377. iOpenPod/assets/ipod_images/iPod12-Pink.png +0 -0
  378. iOpenPod/assets/ipod_images/iPod12-Red.png +0 -0
  379. iOpenPod/assets/ipod_images/iPod12-Silver.png +0 -0
  380. iOpenPod/assets/ipod_images/iPod15-Black.png +0 -0
  381. iOpenPod/assets/ipod_images/iPod15-Blue.png +0 -0
  382. iOpenPod/assets/ipod_images/iPod15-Green.png +0 -0
  383. iOpenPod/assets/ipod_images/iPod15-Orange.png +0 -0
  384. iOpenPod/assets/ipod_images/iPod15-Pink.png +0 -0
  385. iOpenPod/assets/ipod_images/iPod15-Purple.png +0 -0
  386. iOpenPod/assets/ipod_images/iPod15-Red.png +0 -0
  387. iOpenPod/assets/ipod_images/iPod15-Silver.png +0 -0
  388. iOpenPod/assets/ipod_images/iPod15-Yellow.png +0 -0
  389. iOpenPod/assets/ipod_images/iPod16-Black.png +0 -0
  390. iOpenPod/assets/ipod_images/iPod16-Blue.png +0 -0
  391. iOpenPod/assets/ipod_images/iPod16-Green.png +0 -0
  392. iOpenPod/assets/ipod_images/iPod16-Orange.png +0 -0
  393. iOpenPod/assets/ipod_images/iPod16-Pink.png +0 -0
  394. iOpenPod/assets/ipod_images/iPod16-Purple.png +0 -0
  395. iOpenPod/assets/ipod_images/iPod16-Red.png +0 -0
  396. iOpenPod/assets/ipod_images/iPod16-Silver.png +0 -0
  397. iOpenPod/assets/ipod_images/iPod16-Yellow.png +0 -0
  398. iOpenPod/assets/ipod_images/iPod17-Blue.png +0 -0
  399. iOpenPod/assets/ipod_images/iPod17-DarkGray.png +0 -0
  400. iOpenPod/assets/ipod_images/iPod17-Green.png +0 -0
  401. iOpenPod/assets/ipod_images/iPod17-Orange.png +0 -0
  402. iOpenPod/assets/ipod_images/iPod17-Pink.png +0 -0
  403. iOpenPod/assets/ipod_images/iPod17-Red.png +0 -0
  404. iOpenPod/assets/ipod_images/iPod17-Silver.png +0 -0
  405. iOpenPod/assets/ipod_images/iPod18-Blue.png +0 -0
  406. iOpenPod/assets/ipod_images/iPod18-DarkGray.png +0 -0
  407. iOpenPod/assets/ipod_images/iPod18-Green.png +0 -0
  408. iOpenPod/assets/ipod_images/iPod18-Pink.png +0 -0
  409. iOpenPod/assets/ipod_images/iPod18-Purple.png +0 -0
  410. iOpenPod/assets/ipod_images/iPod18-Red.png +0 -0
  411. iOpenPod/assets/ipod_images/iPod18-Silver.png +0 -0
  412. iOpenPod/assets/ipod_images/iPod18-SpaceGray.png +0 -0
  413. iOpenPod/assets/ipod_images/iPod18-Yellow.png +0 -0
  414. iOpenPod/assets/ipod_images/iPod18A-Blue.png +0 -0
  415. iOpenPod/assets/ipod_images/iPod18A-Gold.png +0 -0
  416. iOpenPod/assets/ipod_images/iPod18A-Pink.png +0 -0
  417. iOpenPod/assets/ipod_images/iPod18A-Red.png +0 -0
  418. iOpenPod/assets/ipod_images/iPod18A-Silver.png +0 -0
  419. iOpenPod/assets/ipod_images/iPod18A-SpaceGray.png +0 -0
  420. iOpenPod/assets/ipod_images/iPod2.png +0 -0
  421. iOpenPod/assets/ipod_images/iPod3-Blue.png +0 -0
  422. iOpenPod/assets/ipod_images/iPod3-Gold.png +0 -0
  423. iOpenPod/assets/ipod_images/iPod3-Green.png +0 -0
  424. iOpenPod/assets/ipod_images/iPod3-Pink.png +0 -0
  425. iOpenPod/assets/ipod_images/iPod3-Silver.png +0 -0
  426. iOpenPod/assets/ipod_images/iPod3B-Blue.png +0 -0
  427. iOpenPod/assets/ipod_images/iPod3B-Green.png +0 -0
  428. iOpenPod/assets/ipod_images/iPod3B-Pink.png +0 -0
  429. iOpenPod/assets/ipod_images/iPod4-BlackRed.png +0 -0
  430. iOpenPod/assets/ipod_images/iPod4-White.png +0 -0
  431. iOpenPod/assets/ipod_images/iPod5-BlackRed.png +0 -0
  432. iOpenPod/assets/ipod_images/iPod5-White.png +0 -0
  433. iOpenPod/assets/ipod_images/iPod6-Black.png +0 -0
  434. iOpenPod/assets/ipod_images/iPod6-BlackRed.png +0 -0
  435. iOpenPod/assets/ipod_images/iPod6-White.png +0 -0
  436. iOpenPod/assets/ipod_images/iPod7-Black.png +0 -0
  437. iOpenPod/assets/ipod_images/iPod7-White.png +0 -0
  438. iOpenPod/assets/ipod_images/iPod9-Black.png +0 -0
  439. iOpenPod/assets/ipod_images/iPod9-Blue.png +0 -0
  440. iOpenPod/assets/ipod_images/iPod9-Green.png +0 -0
  441. iOpenPod/assets/ipod_images/iPod9-Pink.png +0 -0
  442. iOpenPod/assets/ipod_images/iPod9-Red.png +0 -0
  443. iOpenPod/assets/ipod_images/iPod9-Silver.png +0 -0
  444. iOpenPod/assets/ipod_images/iPodGeneric.png +0 -0
  445. iOpenPod/assets/linux/61-iopenpod.rules +24 -0
  446. iPodDB/ArtworkDB/builder/build_ArtworkDB.py +180 -0
  447. iPodDB/ArtworkDB/ithmb.py +453 -0
  448. iPodDB/ArtworkDB/ithmb_writer.py +170 -0
  449. iPodDB/ArtworkDB/parser/mhod_parsers/container_mhod.py +38 -0
  450. iPodDB/ArtworkDB/parser/mhod_parsers/opaque_mhod.py +12 -0
  451. iPodDB/ArtworkDB/parser/mhod_parsers/string_mhod.py +33 -0
  452. iPodDB/ArtworkDB/parser/mhod_payload_spec_registry.py +75 -0
  453. iPodDB/ArtworkDB/parser/parse_ArtworkDB.py +14 -0
  454. iPodDB/ArtworkDB/parser/parser_definition.py +31 -0
  455. iPodDB/ArtworkDB/shared/artwork_index.py +163 -0
  456. iPodDB/ArtworkDB/shared/chunk_defs/mhaf.py +23 -0
  457. iPodDB/ArtworkDB/shared/chunk_defs/mhba.py +44 -0
  458. iPodDB/ArtworkDB/shared/chunk_defs/mhfd.py +36 -0
  459. iPodDB/ArtworkDB/shared/chunk_defs/mhia.py +24 -0
  460. iPodDB/ArtworkDB/shared/chunk_defs/mhif.py +27 -0
  461. iPodDB/ArtworkDB/shared/chunk_defs/mhii.py +34 -0
  462. iPodDB/ArtworkDB/shared/chunk_defs/mhla.py +16 -0
  463. iPodDB/ArtworkDB/shared/chunk_defs/mhlf.py +16 -0
  464. iPodDB/ArtworkDB/shared/chunk_defs/mhli.py +16 -0
  465. iPodDB/ArtworkDB/shared/chunk_defs/mhni.py +34 -0
  466. iPodDB/ArtworkDB/shared/chunk_defs/mhod.py +27 -0
  467. iPodDB/ArtworkDB/shared/chunk_defs/mhod_payloads/container_mhod.py +12 -0
  468. iPodDB/ArtworkDB/shared/chunk_defs/mhod_payloads/opaque_mhod.py +14 -0
  469. iPodDB/ArtworkDB/shared/chunk_defs/mhod_payloads/string_mhod.py +43 -0
  470. iPodDB/ArtworkDB/shared/chunk_defs/mhsd.py +51 -0
  471. iPodDB/ArtworkDB/shared/constants.py +24 -0
  472. iPodDB/ArtworkDB/shared/database_definition.py +36 -0
  473. iPodDB/ArtworkDB/shared/mhod_payload_spec_registry.py +88 -0
  474. iPodDB/ArtworkDB/writer/mhod_encoder.py +172 -0
  475. iPodDB/ArtworkDB/writer/mhod_serializer.py +72 -0
  476. iPodDB/ArtworkDB/writer/write_ArtworkDB.py +24 -0
  477. iPodDB/PhotosDB/builder/build_PhotosDB.py +181 -0
  478. iPodDB/PhotosDB/parser/mhod_parsers/container_mhod.py +38 -0
  479. iPodDB/PhotosDB/parser/mhod_parsers/opaque_mhod.py +12 -0
  480. iPodDB/PhotosDB/parser/mhod_parsers/string_mhod.py +33 -0
  481. iPodDB/PhotosDB/parser/mhod_payload_spec_registry.py +75 -0
  482. iPodDB/PhotosDB/parser/parse_PhotosDB.py +14 -0
  483. iPodDB/PhotosDB/parser/parser_definition.py +29 -0
  484. iPodDB/PhotosDB/shared/chunk_defs/mhaf.py +17 -0
  485. iPodDB/PhotosDB/shared/chunk_defs/mhba.py +22 -0
  486. iPodDB/PhotosDB/shared/chunk_defs/mhfd.py +38 -0
  487. iPodDB/PhotosDB/shared/chunk_defs/mhia.py +17 -0
  488. iPodDB/PhotosDB/shared/chunk_defs/mhif.py +18 -0
  489. iPodDB/PhotosDB/shared/chunk_defs/mhii.py +19 -0
  490. iPodDB/PhotosDB/shared/chunk_defs/mhla.py +16 -0
  491. iPodDB/PhotosDB/shared/chunk_defs/mhlf.py +16 -0
  492. iPodDB/PhotosDB/shared/chunk_defs/mhli.py +16 -0
  493. iPodDB/PhotosDB/shared/chunk_defs/mhni.py +18 -0
  494. iPodDB/PhotosDB/shared/chunk_defs/mhod.py +17 -0
  495. iPodDB/PhotosDB/shared/chunk_defs/mhod_payloads/container_mhod.py +7 -0
  496. iPodDB/PhotosDB/shared/chunk_defs/mhod_payloads/opaque_mhod.py +8 -0
  497. iPodDB/PhotosDB/shared/chunk_defs/mhod_payloads/string_mhod.py +36 -0
  498. iPodDB/PhotosDB/shared/chunk_defs/mhsd.py +43 -0
  499. iPodDB/PhotosDB/shared/constants.py +31 -0
  500. iPodDB/PhotosDB/shared/database_definition.py +36 -0
  501. iPodDB/PhotosDB/shared/mhod_payload_spec_registry.py +21 -0
  502. iPodDB/PhotosDB/writer/write_PhotosDB.py +22 -0
  503. iPodDB/SQLiteDB/artifacts.py +27 -0
  504. iPodDB/SQLiteDB/checksum.py +68 -0
  505. iPodDB/SQLiteDB/database.py +1089 -0
  506. iPodDB/device_time.py +143 -0
  507. iPodDB/iTunesDB/builder/build_iTunesDB.py +94 -0
  508. iPodDB/iTunesDB/cdb.py +136 -0
  509. iPodDB/iTunesDB/parser/mhod_parsers/chapter_data_mhod.py +327 -0
  510. iPodDB/iTunesDB/parser/mhod_parsers/contextual_100_mhod.py +17 -0
  511. iPodDB/iTunesDB/parser/mhod_parsers/library_index_mhod.py +31 -0
  512. iPodDB/iTunesDB/parser/mhod_parsers/library_jump_table_mhod.py +36 -0
  513. iPodDB/iTunesDB/parser/mhod_parsers/opaque_mhod.py +14 -0
  514. iPodDB/iTunesDB/parser/mhod_parsers/plist_mhod.py +52 -0
  515. iPodDB/iTunesDB/parser/mhod_parsers/settings_mhod.py +28 -0
  516. iPodDB/iTunesDB/parser/mhod_parsers/smart_prefs_mhod.py +17 -0
  517. iPodDB/iTunesDB/parser/mhod_parsers/smart_rules_mhod.py +180 -0
  518. iPodDB/iTunesDB/parser/mhod_parsers/string_mhod.py +40 -0
  519. iPodDB/iTunesDB/parser/mhod_parsers/url_mhod.py +18 -0
  520. iPodDB/iTunesDB/parser/mhod_parsers/video_details_mhod.py +16 -0
  521. iPodDB/iTunesDB/parser/mhod_payload_spec_registry.py +128 -0
  522. iPodDB/iTunesDB/parser/parse_iTunesDB.py +23 -0
  523. iPodDB/iTunesDB/parser/parser_definition.py +29 -0
  524. iPodDB/iTunesDB/shared/chunk_defs/mhbd.py +47 -0
  525. iPodDB/iTunesDB/shared/chunk_defs/mhia.py +33 -0
  526. iPodDB/iTunesDB/shared/chunk_defs/mhii.py +28 -0
  527. iPodDB/iTunesDB/shared/chunk_defs/mhip.py +34 -0
  528. iPodDB/iTunesDB/shared/chunk_defs/mhit.py +131 -0
  529. iPodDB/iTunesDB/shared/chunk_defs/mhla.py +16 -0
  530. iPodDB/iTunesDB/shared/chunk_defs/mhli.py +16 -0
  531. iPodDB/iTunesDB/shared/chunk_defs/mhlp.py +16 -0
  532. iPodDB/iTunesDB/shared/chunk_defs/mhlt.py +16 -0
  533. iPodDB/iTunesDB/shared/chunk_defs/mhod.py +26 -0
  534. iPodDB/iTunesDB/shared/chunk_defs/mhod_payloads/chapter_data_mhod.py +93 -0
  535. iPodDB/iTunesDB/shared/chunk_defs/mhod_payloads/contextual_100_mhod.py +28 -0
  536. iPodDB/iTunesDB/shared/chunk_defs/mhod_payloads/library_index_mhod.py +19 -0
  537. iPodDB/iTunesDB/shared/chunk_defs/mhod_payloads/library_jump_table_mhod.py +27 -0
  538. iPodDB/iTunesDB/shared/chunk_defs/mhod_payloads/opaque_mhod.py +10 -0
  539. iPodDB/iTunesDB/shared/chunk_defs/mhod_payloads/plist_mhod.py +52 -0
  540. iPodDB/iTunesDB/shared/chunk_defs/mhod_payloads/settings_mhod.py +18 -0
  541. iPodDB/iTunesDB/shared/chunk_defs/mhod_payloads/smart_prefs_mhod.py +28 -0
  542. iPodDB/iTunesDB/shared/chunk_defs/mhod_payloads/smart_rules_mhod.py +173 -0
  543. iPodDB/iTunesDB/shared/chunk_defs/mhod_payloads/string_mhod.py +19 -0
  544. iPodDB/iTunesDB/shared/chunk_defs/mhod_payloads/url_mhod.py +10 -0
  545. iPodDB/iTunesDB/shared/chunk_defs/mhod_payloads/video_details_mhod.py +13 -0
  546. iPodDB/iTunesDB/shared/chunk_defs/mhsd.py +44 -0
  547. iPodDB/iTunesDB/shared/chunk_defs/mhyp.py +50 -0
  548. iPodDB/iTunesDB/shared/constants.py +205 -0
  549. iPodDB/iTunesDB/shared/database_definition.py +36 -0
  550. iPodDB/iTunesDB/shared/device_time.py +39 -0
  551. iPodDB/iTunesDB/shared/field_converters.py +22 -0
  552. iPodDB/iTunesDB/shared/mhod_payload_spec_registry.py +165 -0
  553. iPodDB/iTunesDB/writer/calcHashAB.NOTICE +20 -0
  554. iPodDB/iTunesDB/writer/calcHashAB.wasm.b64 +150 -0
  555. iPodDB/iTunesDB/writer/mhod_encoder.py +607 -0
  556. iPodDB/iTunesDB/writer/signature.py +291 -0
  557. iPodDB/iTunesDB/writer/write_iTunesDB.py +67 -0
  558. iPodDB/library/__init__.py +218 -0
  559. iPodDB/library/_album_index.py +375 -0
  560. iPodDB/library/_artwork_verification.py +171 -0
  561. iPodDB/library/_artwork_writing.py +652 -0
  562. iPodDB/library/_browse_groups.py +68 -0
  563. iPodDB/library/_browse_index.py +103 -0
  564. iPodDB/library/_document_edit.py +23 -0
  565. iPodDB/library/_field_policy.py +387 -0
  566. iPodDB/library/_folder_validation.py +78 -0
  567. iPodDB/library/_folder_writing.py +163 -0
  568. iPodDB/library/_group_verification.py +305 -0
  569. iPodDB/library/_hidden_writing.py +461 -0
  570. iPodDB/library/_hierarchy.py +36 -0
  571. iPodDB/library/_identities.py +40 -0
  572. iPodDB/library/_index_verification.py +126 -0
  573. iPodDB/library/_lyrics_resources.py +71 -0
  574. iPodDB/library/_native_values.py +72 -0
  575. iPodDB/library/_photo_analysis.py +575 -0
  576. iPodDB/library/_photo_projection.py +178 -0
  577. iPodDB/library/_photo_resources.py +238 -0
  578. iPodDB/library/_photo_writing.py +376 -0
  579. iPodDB/library/_playlist_datasets.py +263 -0
  580. iPodDB/library/_playlist_projection.py +207 -0
  581. iPodDB/library/_playlist_verification.py +622 -0
  582. iPodDB/library/_playlist_writing.py +244 -0
  583. iPodDB/library/_podcast_writing.py +272 -0
  584. iPodDB/library/_projection.py +422 -0
  585. iPodDB/library/_reconcile.py +597 -0
  586. iPodDB/library/_resolve_write.py +360 -0
  587. iPodDB/library/_resolved_write.py +36 -0
  588. iPodDB/library/_sidecar_projection.py +168 -0
  589. iPodDB/library/_smart_projection.py +332 -0
  590. iPodDB/library/_smart_writing.py +321 -0
  591. iPodDB/library/_source_warnings.py +87 -0
  592. iPodDB/library/_time_warnings.py +83 -0
  593. iPodDB/library/_track_header_verification.py +168 -0
  594. iPodDB/library/_track_verification.py +206 -0
  595. iPodDB/library/_track_writing.py +467 -0
  596. iPodDB/library/_write_analysis.py +415 -0
  597. iPodDB/library/_write_effects.py +363 -0
  598. iPodDB/library/_write_logging.py +201 -0
  599. iPodDB/library/_write_preparation.py +1052 -0
  600. iPodDB/library/_write_verification.py +147 -0
  601. iPodDB/library/artwork.py +112 -0
  602. iPodDB/library/database.py +414 -0
  603. iPodDB/library/editing.py +114 -0
  604. iPodDB/library/media.py +48 -0
  605. iPodDB/library/models.py +254 -0
  606. iPodDB/library/photos.py +285 -0
  607. iPodDB/library/playlists.py +437 -0
  608. iPodDB/library/signing.py +64 -0
  609. iPodDB/library/smart_rules.py +236 -0
  610. iPodDB/library/sqlite.py +5 -0
  611. iPodDB/library/writing.py +305 -0
  612. iPodDB/preferences/__init__.py +58 -0
  613. iPodDB/preferences/_source.py +9 -0
  614. iPodDB/preferences/codec.py +175 -0
  615. iPodDB/preferences/definitions.py +104 -0
  616. iPodDB/preferences/itunes.py +145 -0
  617. iPodDB/preferences/itunes_definitions.py +93 -0
  618. iPodDB/preferences/models.py +81 -0
  619. iPodDB/preferences/timezones.py +220 -0
  620. iPodDB/py.typed +0 -0
  621. iPodDB/shared/binary_struct.py +271 -0
  622. iPodDB/shared/chunk.py +408 -0
  623. iPodDB/shared/chunk_builder.py +46 -0
  624. iPodDB/shared/chunk_field.py +178 -0
  625. iPodDB/shared/chunk_reader.py +433 -0
  626. iPodDB/shared/chunk_writer.py +325 -0
  627. iPodDB/shared/errors.py +42 -0
  628. iPodDB/shared/measure_memory.py +107 -0
  629. iPodDB/shared/to_json.py +91 -0
  630. iPodDB/shared/types.py +377 -0
  631. iPodDB/sidecars/__init__.py +41 -0
  632. iPodDB/sidecars/codec.py +225 -0
  633. iPodDB/sidecars/models.py +63 -0
  634. iPodDB/sidecars/remapping.py +64 -0
  635. iPodDB/tools/chunk_browser.py +1051 -0
  636. iopenpod-2.0.4.dist-info/METADATA +327 -0
  637. iopenpod-2.0.4.dist-info/RECORD +681 -0
  638. iopenpod-2.0.4.dist-info/WHEEL +4 -0
  639. iopenpod-2.0.4.dist-info/entry_points.txt +2 -0
  640. iopenpod-2.0.4.dist-info/licenses/ACKNOWLEDGEMENTS.md +44 -0
  641. iopenpod-2.0.4.dist-info/licenses/COPYING.md +47 -0
  642. iopenpod-2.0.4.dist-info/licenses/LICENSE +675 -0
  643. iopenpod_launcher.py +283 -0
  644. storage/__init__.py +177 -0
  645. storage/_filesystem.py +238 -0
  646. storage/_transaction_journal.py +242 -0
  647. storage/_transactions.py +1426 -0
  648. storage/_writer_lock.py +189 -0
  649. storage/device_capture.py +39 -0
  650. storage/errors.py +111 -0
  651. storage/host_capture.py +139 -0
  652. storage/host_directory.py +242 -0
  653. storage/host_files.py +221 -0
  654. storage/host_input.py +417 -0
  655. storage/host_installation.py +147 -0
  656. storage/host_tools.py +124 -0
  657. storage/host_usage.py +119 -0
  658. storage/media_probe.py +161 -0
  659. storage/media_processing.py +527 -0
  660. storage/models.py +364 -0
  661. storage/paths.py +97 -0
  662. storage/platform/__init__.py +1 -0
  663. storage/platform/base.py +58 -0
  664. storage/platform/common.py +88 -0
  665. storage/platform/host_install_windows.py +201 -0
  666. storage/platform/linux.py +619 -0
  667. storage/platform/linux_eject.py +392 -0
  668. storage/platform/macos.py +974 -0
  669. storage/platform/macos_eject.py +90 -0
  670. storage/platform/macos_volume_metadata.py +116 -0
  671. storage/platform/macos_vpd.py +546 -0
  672. storage/platform/native.py +29 -0
  673. storage/platform/windows.py +667 -0
  674. storage/platform/windows_eject.py +386 -0
  675. storage/portal.py +403 -0
  676. storage/py.typed +1 -0
  677. storage/scsi_vpd.py +127 -0
  678. storage/session.py +1626 -0
  679. storage/testing.py +282 -0
  680. storage/transactions.py +209 -0
  681. storage/volume_metadata.py +40 -0
@@ -0,0 +1,47 @@
1
+ """Aspect-preserving Photo painting shared by cards and the inspector."""
2
+
3
+ from PySide6.QtCore import QRectF, Qt
4
+ from PySide6.QtGui import QColor, QPainter, QPainterPath, QPen, QPixmap
5
+
6
+ from iOpenPod.GUI.presentation.theme.tokens import ThemeTokens
7
+
8
+
9
+ def paint_photo_pixmap(
10
+ painter: QPainter,
11
+ rect: QRectF,
12
+ pixmap: QPixmap,
13
+ tokens: ThemeTokens,
14
+ radius: float,
15
+ ) -> None:
16
+ """Letterbox one Photo within ``rect`` while retaining its aspect ratio."""
17
+
18
+ clip = QPainterPath()
19
+ clip.addRoundedRect(rect, radius, radius)
20
+ source = QRectF(pixmap.rect())
21
+ if source.isEmpty():
22
+ return
23
+ scale = min(rect.width() / source.width(), rect.height() / source.height())
24
+ width = source.width() * scale
25
+ height = source.height() * scale
26
+ target = QRectF(
27
+ rect.left() + (rect.width() - width) / 2.0,
28
+ rect.top() + (rect.height() - height) / 2.0,
29
+ width,
30
+ height,
31
+ )
32
+
33
+ painter.save()
34
+ painter.setClipPath(clip)
35
+ painter.fillRect(rect, QColor(tokens.surface_alt))
36
+ painter.setRenderHint(QPainter.RenderHint.SmoothPixmapTransform, True)
37
+ painter.drawPixmap(target, pixmap, source)
38
+ painter.restore()
39
+
40
+ border = QPen(QColor(tokens.border))
41
+ border.setWidthF(1.0)
42
+ painter.setPen(border)
43
+ painter.setBrush(Qt.BrushStyle.NoBrush)
44
+ painter.drawRoundedRect(rect, radius, radius)
45
+
46
+
47
+ __all__ = ["paint_photo_pixmap"]
@@ -0,0 +1,182 @@
1
+ """GUI-thread conversion of lazily decoded Photo images into display pixmaps."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from enum import StrEnum
6
+ from math import ceil
7
+ from typing import TYPE_CHECKING
8
+
9
+ from PySide6.QtCore import QObject, Signal, Slot
10
+ from PySide6.QtGui import QImage, QPixmap, QPixmapCache
11
+
12
+ from iOpenPod.app.models.photos import PhotoImage
13
+ from iOpenPod.app.photo_controller import PhotoLoadFailure, PhotoUnavailable
14
+
15
+ if TYPE_CHECKING:
16
+ from iOpenPod.app.photo_controller import PhotoController
17
+
18
+ _CACHE_PREFIX = "iOpenPod/photo/"
19
+
20
+
21
+ class PhotoPixmapState(StrEnum):
22
+ """Presentation state for one automatic or exact Photo representation."""
23
+
24
+ IDLE = "idle"
25
+ LOADING = "loading"
26
+ READY = "ready"
27
+ UNAVAILABLE = "unavailable"
28
+ FAILED = "failed"
29
+
30
+
31
+ class PhotoPixmapProvider(QObject):
32
+ """Bridge owned Photo RGB pixels to Qt's bounded display-pixmap cache."""
33
+
34
+ photoChanged = Signal(object)
35
+ cleared = Signal()
36
+ capacityAvailable = Signal()
37
+
38
+ def __init__(
39
+ self,
40
+ controller: PhotoController,
41
+ parent: QObject | None = None,
42
+ ) -> None:
43
+ super().__init__(parent)
44
+ self._controller = controller
45
+ self._qt_cache_keys: set[str] = set()
46
+ self._states: dict[tuple[int, int | None], PhotoPixmapState] = {}
47
+ self._closed = False
48
+ controller.photoReady.connect(self._photo_ready)
49
+ controller.photoUnavailable.connect(self._photo_unavailable)
50
+ controller.photoFailed.connect(self._photo_failed)
51
+ controller.generationChanged.connect(self._generation_changed)
52
+ controller.capacityAvailable.connect(self._capacity_available)
53
+
54
+ def pixmap(
55
+ self,
56
+ photo_id: int,
57
+ logical_size: int,
58
+ device_pixel_ratio: float,
59
+ *,
60
+ format_id: int | None = None,
61
+ ) -> QPixmap | None:
62
+ """Return a pixmap now, queuing automatic or exact-format decode on miss."""
63
+
64
+ if (
65
+ self._closed
66
+ or photo_id <= 0
67
+ or logical_size <= 0
68
+ or (format_id is not None and format_id < 0)
69
+ ):
70
+ return None
71
+ target_px = max(1, ceil(logical_size * max(1.0, device_pixel_ratio)))
72
+ image = self._controller.request(
73
+ photo_id,
74
+ target_px,
75
+ format_id=format_id,
76
+ )
77
+ if image is None:
78
+ key = (photo_id, format_id)
79
+ if self._states.get(key) not in (
80
+ PhotoPixmapState.UNAVAILABLE,
81
+ PhotoPixmapState.FAILED,
82
+ ):
83
+ self._states[key] = PhotoPixmapState.LOADING
84
+ return None
85
+ self._states[(photo_id, format_id)] = PhotoPixmapState.READY
86
+ return self._pixmap_for(image)
87
+
88
+ def state(
89
+ self,
90
+ photo_id: int,
91
+ *,
92
+ format_id: int | None = None,
93
+ ) -> PhotoPixmapState:
94
+ """Return the last observed presentation state without starting I/O."""
95
+
96
+ return self._states.get((photo_id, format_id), PhotoPixmapState.IDLE)
97
+
98
+ def retry(self, photo_id: int, *, format_id: int | None = None) -> None:
99
+ """Clear one terminal state so the next paint retries the request."""
100
+
101
+ if self._closed or photo_id <= 0 or (format_id is not None and format_id < 0):
102
+ return
103
+ self._controller.retry(photo_id, format_id=format_id)
104
+ self._states.pop((photo_id, format_id), None)
105
+ self.photoChanged.emit(photo_id)
106
+
107
+ def shutdown(self) -> None:
108
+ """Release presentation cache entries before the owning window closes."""
109
+
110
+ if self._closed:
111
+ return
112
+ self._closed = True
113
+ self._clear_pixmaps()
114
+ self._states.clear()
115
+
116
+ @Slot(object)
117
+ def _photo_ready(self, value: object) -> None:
118
+ if self._closed or not isinstance(value, PhotoImage):
119
+ return
120
+ self._pixmap_for(value)
121
+ for key in tuple(self._states):
122
+ if key[0] == value.photo_id and (
123
+ key[1] is None or key[1] == value.format_id
124
+ ):
125
+ self._states[key] = PhotoPixmapState.READY
126
+ self.photoChanged.emit(value.photo_id)
127
+
128
+ @Slot(object)
129
+ def _photo_unavailable(self, value: object) -> None:
130
+ if self._closed or not isinstance(value, PhotoUnavailable):
131
+ return
132
+ self._states[(value.photo_id, value.format_id)] = PhotoPixmapState.UNAVAILABLE
133
+ self.photoChanged.emit(value.photo_id)
134
+
135
+ @Slot(object)
136
+ def _photo_failed(self, value: object) -> None:
137
+ if self._closed or not isinstance(value, PhotoLoadFailure):
138
+ return
139
+ self._states[(value.photo_id, value.format_id)] = PhotoPixmapState.FAILED
140
+ self.photoChanged.emit(value.photo_id)
141
+
142
+ @Slot(int)
143
+ def _generation_changed(self, _generation: int) -> None:
144
+ if self._closed:
145
+ return
146
+ self._clear_pixmaps()
147
+ self._states.clear()
148
+ self.cleared.emit()
149
+
150
+ @Slot()
151
+ def _capacity_available(self) -> None:
152
+ if not self._closed:
153
+ self.capacityAvailable.emit()
154
+
155
+ def _clear_pixmaps(self) -> None:
156
+ for cache_key in self._qt_cache_keys:
157
+ QPixmapCache.remove(cache_key)
158
+ self._qt_cache_keys.clear()
159
+
160
+ def _pixmap_for(self, image: PhotoImage) -> QPixmap:
161
+ cache_key = f"{_CACHE_PREFIX}{image.cache_key}"
162
+ cached = QPixmap()
163
+ if QPixmapCache.find(cache_key, cached) and not cached.isNull():
164
+ return cached
165
+
166
+ wrapped = QImage(
167
+ image.rgb888,
168
+ image.width,
169
+ image.height,
170
+ image.width * 3,
171
+ QImage.Format.Format_RGB888,
172
+ )
173
+ # QImage's raw-buffer constructor borrows Python memory. Detach from that
174
+ # buffer before crossing into Qt's independently retained pixmap cache.
175
+ owned = wrapped.copy()
176
+ pixmap = QPixmap.fromImage(owned)
177
+ QPixmapCache.insert(cache_key, pixmap)
178
+ self._qt_cache_keys.add(cache_key)
179
+ return pixmap
180
+
181
+
182
+ __all__ = ["PhotoPixmapProvider", "PhotoPixmapState"]
@@ -0,0 +1,83 @@
1
+ """DPR-aware GUI pixmaps for asynchronously fetched Podcast artwork."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from math import ceil
6
+ from typing import TYPE_CHECKING
7
+
8
+ from PySide6.QtCore import QObject, Signal, Slot
9
+ from PySide6.QtGui import QImage, QPixmap, QPixmapCache
10
+
11
+ from iOpenPod.app.podcasts.models import PodcastArtworkImage
12
+
13
+ if TYPE_CHECKING:
14
+ from iOpenPod.app.podcasts.artwork_controller import PodcastArtworkController
15
+
16
+ _CACHE_PREFIX = "iOpenPod/podcast-artwork/"
17
+
18
+
19
+ class PodcastArtworkPixmapProvider(QObject):
20
+ """Bridge remote Podcast RGB pixels to the GUI-owned pixmap cache."""
21
+
22
+ artworkChanged = Signal(str)
23
+
24
+ def __init__(
25
+ self,
26
+ controller: PodcastArtworkController,
27
+ parent: QObject | None = None,
28
+ ) -> None:
29
+ super().__init__(parent)
30
+ self._controller = controller
31
+ self._qt_cache_keys: set[str] = set()
32
+ self._closed = False
33
+ controller.artworkReady.connect(self._artwork_ready)
34
+ controller.artworkUnavailable.connect(self.artworkChanged)
35
+
36
+ def pixmap(
37
+ self,
38
+ source_url: str,
39
+ logical_size: int,
40
+ device_pixel_ratio: float,
41
+ ) -> QPixmap | None:
42
+ if self._closed or not source_url.strip() or logical_size <= 0:
43
+ return None
44
+ target_px = max(1, ceil(logical_size * max(1.0, device_pixel_ratio)))
45
+ image = self._controller.request(source_url, target_px)
46
+ return self._pixmap_for(image) if image is not None else None
47
+
48
+ def shutdown(self) -> None:
49
+ if self._closed:
50
+ return
51
+ self._closed = True
52
+ self._controller.artworkReady.disconnect(self._artwork_ready)
53
+ self._controller.artworkUnavailable.disconnect(self.artworkChanged)
54
+ for cache_key in self._qt_cache_keys:
55
+ QPixmapCache.remove(cache_key)
56
+ self._qt_cache_keys.clear()
57
+
58
+ @Slot(object)
59
+ def _artwork_ready(self, value: object) -> None:
60
+ if self._closed or not isinstance(value, PodcastArtworkImage):
61
+ return
62
+ self._pixmap_for(value)
63
+ self.artworkChanged.emit(value.source_url)
64
+
65
+ def _pixmap_for(self, image: PodcastArtworkImage) -> QPixmap:
66
+ cache_key = f"{_CACHE_PREFIX}{image.cache_key}"
67
+ cached = QPixmap()
68
+ if QPixmapCache.find(cache_key, cached) and not cached.isNull():
69
+ return cached
70
+ wrapped = QImage(
71
+ image.rgb888,
72
+ image.width,
73
+ image.height,
74
+ image.width * 3,
75
+ QImage.Format.Format_RGB888,
76
+ )
77
+ pixmap = QPixmap.fromImage(wrapped.copy())
78
+ QPixmapCache.insert(cache_key, pixmap)
79
+ self._qt_cache_keys.add(cache_key)
80
+ return pixmap
81
+
82
+
83
+ __all__ = ["PodcastArtworkPixmapProvider"]
@@ -0,0 +1,91 @@
1
+ """Shared four-cover artwork treatment for the All Podcasts projection."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ from PySide6.QtCore import QRectF
8
+ from PySide6.QtGui import QColor, QPainter, QPen
9
+
10
+ from iOpenPod.app.models.artwork_seed import stable_artwork_seed
11
+ from iOpenPod.GUI.presentation.artwork import (
12
+ paint_artwork_pixmap,
13
+ paint_artwork_placeholder,
14
+ )
15
+ from iOpenPod.GUI.presentation.theme.tokens import LAYOUT
16
+
17
+ if TYPE_CHECKING:
18
+ from collections.abc import Sequence
19
+
20
+ from iOpenPod.app.podcasts.models import PodcastSubscription
21
+ from iOpenPod.GUI.presentation.artwork_provider import ArtworkPixmapProvider
22
+ from iOpenPod.GUI.presentation.podcast_artwork_provider import (
23
+ PodcastArtworkPixmapProvider,
24
+ )
25
+ from iOpenPod.GUI.presentation.theme.tokens import ThemeTokens
26
+
27
+
28
+ def paint_podcast_collection_artwork(
29
+ painter: QPainter,
30
+ rect: QRectF,
31
+ subscriptions: Sequence[PodcastSubscription],
32
+ provider: PodcastArtworkPixmapProvider,
33
+ tokens: ThemeTokens,
34
+ device_pixel_ratio: float,
35
+ device_provider: ArtworkPixmapProvider | None = None,
36
+ ) -> None:
37
+ """Paint the first four Podcast covers as a fixed 2-by-2 collection grid."""
38
+
39
+ gap = float(LAYOUT.space_2xs)
40
+ tile_size = (min(rect.width(), rect.height()) - gap) / 2.0
41
+ tile_radius = float(LAYOUT.space_2xs)
42
+ for position in range(4):
43
+ column = position % 2
44
+ row = position // 2
45
+ tile_rect = QRectF(
46
+ rect.left() + column * (tile_size + gap),
47
+ rect.top() + row * (tile_size + gap),
48
+ tile_size,
49
+ tile_size,
50
+ )
51
+ if position >= len(subscriptions):
52
+ painter.setPen(QPen(QColor(tokens.border)))
53
+ painter.setBrush(QColor(tokens.surface_alt))
54
+ painter.drawRoundedRect(tile_rect, tile_radius, tile_radius)
55
+ continue
56
+
57
+ subscription = subscriptions[position]
58
+ pixmap = provider.pixmap(
59
+ subscription.artwork_url,
60
+ round(tile_size),
61
+ device_pixel_ratio,
62
+ )
63
+ if (
64
+ pixmap is None
65
+ and device_provider is not None
66
+ and subscription.artwork_id > 0
67
+ ):
68
+ pixmap = device_provider.pixmap(
69
+ subscription.artwork_id,
70
+ round(tile_size),
71
+ device_pixel_ratio,
72
+ )
73
+ if pixmap is None:
74
+ paint_artwork_placeholder(
75
+ painter,
76
+ tile_rect,
77
+ stable_artwork_seed(subscription.subscription_id),
78
+ tokens,
79
+ tile_radius,
80
+ )
81
+ else:
82
+ paint_artwork_pixmap(
83
+ painter,
84
+ tile_rect,
85
+ pixmap,
86
+ tokens,
87
+ tile_radius,
88
+ )
89
+
90
+
91
+ __all__ = ["paint_podcast_collection_artwork"]
@@ -0,0 +1,148 @@
1
+ # Hallmark · component: Library selection checkbox · genre: modern-minimal
2
+ # theme: iOpenPod · states: unchecked · checked · mixed · hover · focus · disabled
3
+ # contrast: pass · pre-emit critique: P5 H5 E5 S5 R5 V4
4
+ """Shared circular selection control for virtualized Library cards."""
5
+
6
+ from PySide6.QtCore import (
7
+ QAbstractItemModel,
8
+ QEvent,
9
+ QLineF,
10
+ QModelIndex,
11
+ QPersistentModelIndex,
12
+ QRectF,
13
+ Qt,
14
+ )
15
+ from PySide6.QtGui import QColor, QKeyEvent, QMouseEvent, QPainter, QPainterPath, QPen
16
+ from PySide6.QtWidgets import QStyle, QStyleOptionViewItem
17
+
18
+ from iOpenPod.GUI.presentation.library_card import (
19
+ library_card_artwork_rect,
20
+ library_card_rect,
21
+ )
22
+ from iOpenPod.GUI.presentation.theme.tokens import ThemeTokens, TypographyTokens
23
+
24
+ _CHECKBOX_EXTENT = 36.0
25
+ _CHECKBOX_MARGIN = 8.0
26
+ _CHECKBOX_BORDER_WIDTH = 2.0
27
+
28
+
29
+ def library_card_checkbox_rect(
30
+ option: QStyleOptionViewItem,
31
+ typography: TypographyTokens,
32
+ ) -> QRectF:
33
+ artwork = library_card_artwork_rect(option, typography)
34
+ return QRectF(
35
+ artwork.right() - _CHECKBOX_MARGIN - _CHECKBOX_EXTENT,
36
+ artwork.top() + _CHECKBOX_MARGIN,
37
+ _CHECKBOX_EXTENT,
38
+ _CHECKBOX_EXTENT,
39
+ )
40
+
41
+
42
+ def paint_library_card_checkbox(
43
+ painter: QPainter,
44
+ option: QStyleOptionViewItem,
45
+ state: Qt.CheckState,
46
+ tokens: ThemeTokens,
47
+ typography: TypographyTokens,
48
+ ) -> None:
49
+ checked = state is Qt.CheckState.Checked
50
+ partial = state is Qt.CheckState.PartiallyChecked
51
+ active = checked or partial
52
+ enabled = bool(option.state & QStyle.StateFlag.State_Enabled)
53
+ hovered = bool(option.state & QStyle.StateFlag.State_MouseOver)
54
+ focused = bool(option.state & QStyle.StateFlag.State_HasFocus)
55
+ rect = library_card_checkbox_rect(option, typography)
56
+ fill = (
57
+ tokens.accent_hover
58
+ if active and hovered
59
+ else tokens.accent
60
+ if active
61
+ else tokens.surface_hover
62
+ if hovered
63
+ else tokens.surface
64
+ )
65
+ if not enabled:
66
+ fill = tokens.surface_alt
67
+ border = tokens.text_disabled if not enabled else tokens.border_strong
68
+ mark = tokens.text_disabled if not enabled else tokens.accent_ink
69
+
70
+ painter.save()
71
+ painter.setRenderHint(QPainter.RenderHint.Antialiasing, True)
72
+ if focused:
73
+ painter.setPen(QPen(QColor(tokens.focus), _CHECKBOX_BORDER_WIDTH))
74
+ painter.setBrush(Qt.BrushStyle.NoBrush)
75
+ painter.drawEllipse(rect.adjusted(-3.0, -3.0, 3.0, 3.0))
76
+ painter.setPen(QPen(QColor(border), _CHECKBOX_BORDER_WIDTH))
77
+ painter.setBrush(QColor(fill))
78
+ painter.drawEllipse(rect)
79
+ if checked:
80
+ path = QPainterPath()
81
+ path.moveTo(rect.left() + 9.0, rect.center().y())
82
+ path.lineTo(rect.left() + 15.0, rect.bottom() - 10.0)
83
+ path.lineTo(rect.right() - 8.0, rect.top() + 10.0)
84
+ pen = QPen(QColor(mark), 3.0)
85
+ pen.setCapStyle(Qt.PenCapStyle.RoundCap)
86
+ pen.setJoinStyle(Qt.PenJoinStyle.RoundJoin)
87
+ painter.setPen(pen)
88
+ painter.setBrush(Qt.BrushStyle.NoBrush)
89
+ painter.drawPath(path)
90
+ elif partial:
91
+ pen = QPen(QColor(mark), 3.0)
92
+ pen.setCapStyle(Qt.PenCapStyle.RoundCap)
93
+ painter.setPen(pen)
94
+ painter.drawLine(
95
+ QLineF(
96
+ rect.left() + 9.0,
97
+ rect.center().y(),
98
+ rect.right() - 9.0,
99
+ rect.center().y(),
100
+ )
101
+ )
102
+ painter.restore()
103
+
104
+
105
+ def handle_library_card_check_event(
106
+ event: QEvent,
107
+ model: QAbstractItemModel,
108
+ option: QStyleOptionViewItem,
109
+ index: QModelIndex | QPersistentModelIndex,
110
+ typography: TypographyTokens,
111
+ *,
112
+ checkbox_only: bool = True,
113
+ ) -> bool:
114
+ if not index.flags() & Qt.ItemFlag.ItemIsUserCheckable:
115
+ return False
116
+ activate = False
117
+ if isinstance(event, QMouseEvent):
118
+ hit_rect = (
119
+ library_card_checkbox_rect(option, typography)
120
+ if checkbox_only
121
+ else library_card_rect(option, typography)
122
+ )
123
+ activate = (
124
+ event.type() is QEvent.Type.MouseButtonRelease
125
+ and event.button() is Qt.MouseButton.LeftButton
126
+ and hit_rect.contains(event.position())
127
+ )
128
+ elif isinstance(event, QKeyEvent):
129
+ activate = event.type() is QEvent.Type.KeyPress and event.key() in {
130
+ Qt.Key.Key_Space,
131
+ Qt.Key.Key_Select,
132
+ }
133
+ if not activate:
134
+ return False
135
+ state = index.data(Qt.ItemDataRole.CheckStateRole)
136
+ next_state = (
137
+ Qt.CheckState.Unchecked
138
+ if state == Qt.CheckState.Checked
139
+ else Qt.CheckState.Checked
140
+ )
141
+ return model.setData(index, next_state, Qt.ItemDataRole.CheckStateRole)
142
+
143
+
144
+ __all__ = [
145
+ "handle_library_card_check_event",
146
+ "library_card_checkbox_rect",
147
+ "paint_library_card_checkbox",
148
+ ]
@@ -0,0 +1,84 @@
1
+ """Semantic Qt styles for the Backup Snapshot browser."""
2
+
3
+ from iOpenPod.GUI.presentation.theme.tokens import (
4
+ LAYOUT,
5
+ ThemeTokens,
6
+ TypographyTokens,
7
+ )
8
+
9
+
10
+ def render_backup_page_style(
11
+ tokens: ThemeTokens,
12
+ typography: TypographyTokens,
13
+ ) -> str:
14
+ """Render the archive browser using the application theme contract."""
15
+
16
+ return f"""/* Hallmark · macrostructure: Saved backup list · genre: quiet desktop utility
17
+ * pre-emit critique: P4 H5 E4 S5 R5 V4 · theme: iOpenPod semantic
18
+ * design-system: docs/gui-design-language.md
19
+ * structure: iPod source list · device context · compact cards with optional details
20
+ */
21
+ QWidget#backupsPage,
22
+ QWidget#backupBrowserPage,
23
+ QWidget#backupEmptyPage,
24
+ QWidget#backupDetail,
25
+ QWidget#backupSnapshotHost {{
26
+ color: {tokens.text};
27
+ background-color: {tokens.window};
28
+ }}
29
+ QFrame#backupRecoveryBanner {{
30
+ color: {tokens.text};
31
+ background-color: {tokens.surface_selected};
32
+ border: none;
33
+ border-bottom: 1px solid {tokens.warning};
34
+ }}
35
+ QLabel#backupRecoveryText {{
36
+ color: {tokens.text};
37
+ font-weight: 600;
38
+ }}
39
+ QLabel#backupDeviceName,
40
+ QLabel#backupEmptyTitle {{
41
+ color: {tokens.text};
42
+ font-family: "{typography.display}";
43
+ font-size: {typography.title_pt:g}pt;
44
+ font-weight: 700;
45
+ }}
46
+ QLabel#backupDeviceModel,
47
+ QLabel#backupDeviceSummary,
48
+ QLabel#backupSnapshotsCount,
49
+ QLabel#backupSnapshotSummary,
50
+ QLabel#backupProgressFile,
51
+ QLabel#backupEmptyDetail,
52
+ QLabel#backupArchiveEmpty {{
53
+ color: {tokens.text_secondary};
54
+ }}
55
+ QFrame#backupHero {{
56
+ color: {tokens.text};
57
+ background-color: {tokens.surface};
58
+ border-bottom: 1px solid {tokens.border};
59
+ }}
60
+ QLabel#backupSnapshotsLabel,
61
+ QLabel#backupSnapshotDate,
62
+ QLabel#backupProgressTitle {{
63
+ color: {tokens.text};
64
+ font-family: "{typography.display}";
65
+ font-size: {typography.heading_pt:g}pt;
66
+ font-weight: 700;
67
+ }}
68
+ QFrame#backupSnapshotCard,
69
+ QFrame#backupProgressPanel {{
70
+ color: {tokens.text};
71
+ background-color: {tokens.surface};
72
+ border: 1px solid {tokens.border};
73
+ border-radius: {LAYOUT.radius_panel}px;
74
+ }}
75
+ QLabel#backupSnapshotNote {{
76
+ color: {tokens.text};
77
+ }}
78
+ QLabel#backupSnapshotInvalid {{
79
+ color: {tokens.danger};
80
+ }}
81
+ """
82
+
83
+
84
+ __all__ = ["render_backup_page_style"]