aria-code 0.167.0__py3-none-win_amd64.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 (601) hide show
  1. aria_bots/__init__.py +7 -0
  2. aria_bots/channels/README.md +97 -0
  3. aria_bots/channels/__init__.py +1 -0
  4. aria_bots/channels/approvals.py +263 -0
  5. aria_bots/channels/conversation.py +280 -0
  6. aria_bots/channels/digest.py +186 -0
  7. aria_bots/channels/registry.py +95 -0
  8. aria_bots/engine.py +144 -0
  9. aria_bots/feishu_bot.py +1385 -0
  10. aria_bots/logistics/__init__.py +1 -0
  11. aria_bots/logistics/carriers.py +253 -0
  12. aria_bots/logistics/freight.py +154 -0
  13. aria_bots/logistics/inventory.py +242 -0
  14. aria_bots/logistics/mcp_server.py +156 -0
  15. aria_bots/logistics/records.py +119 -0
  16. aria_bots/logistics/tenancy.py +152 -0
  17. aria_bots/market.py +91 -0
  18. aria_bots/relay_client.py +326 -0
  19. aria_bots/telegram_bot.py +299 -0
  20. aria_code/__init__.py +111 -0
  21. aria_code/_bin/aria-native.exe +0 -0
  22. aria_code/_bin/aria-runtime.exe +0 -0
  23. aria_code/_version.py +20 -0
  24. aria_code/account_memory.py +296 -0
  25. aria_code/account_session.py +166 -0
  26. aria_code/adk_apps/__init__.py +1 -0
  27. aria_code/adk_apps/aria_code_review/__init__.py +1 -0
  28. aria_code/adk_apps/aria_code_review/agent.py +32 -0
  29. aria_code/adk_apps/aria_research/__init__.py +1 -0
  30. aria_code/adk_apps/aria_research/agent.py +31 -0
  31. aria_code/agents/__init__.py +49 -0
  32. aria_code/agents/base.py +329 -0
  33. aria_code/agents/code_review.py +200 -0
  34. aria_code/agents/deep/__init__.py +37 -0
  35. aria_code/agents/deep/calibration_loop.py +144 -0
  36. aria_code/agents/deep/critic.py +125 -0
  37. aria_code/agents/deep/deepen.py +193 -0
  38. aria_code/agents/deep/models.py +149 -0
  39. aria_code/agents/deep/pipeline.py +164 -0
  40. aria_code/agents/deep/quant_fusion.py +192 -0
  41. aria_code/agents/deep/themes.py +95 -0
  42. aria_code/agents/deep/tiers.py +106 -0
  43. aria_code/agents/engineering/__init__.py +14 -0
  44. aria_code/agents/engineering/coder.py +222 -0
  45. aria_code/agents/engineering/tester.py +145 -0
  46. aria_code/agents/financial/__init__.py +21 -0
  47. aria_code/agents/financial/cashflow_burn.py +119 -0
  48. aria_code/agents/financial/catalyst.py +279 -0
  49. aria_code/agents/financial/corporate_finance.py +175 -0
  50. aria_code/agents/financial/debate.py +224 -0
  51. aria_code/agents/financial/earnings.py +315 -0
  52. aria_code/agents/financial/fundamental.py +178 -0
  53. aria_code/agents/financial/macro.py +111 -0
  54. aria_code/agents/financial/news.py +231 -0
  55. aria_code/agents/financial/risk.py +146 -0
  56. aria_code/agents/financial/sector.py +279 -0
  57. aria_code/agents/financial/strategist.py +121 -0
  58. aria_code/agents/financial/stripe_revenue.py +127 -0
  59. aria_code/agents/financial/synthesis.py +274 -0
  60. aria_code/agents/financial/technical.py +277 -0
  61. aria_code/agents/orchestrator.py +44 -0
  62. aria_code/agents/portfolio_agent.py +392 -0
  63. aria_code/agents/realty/__init__.py +62 -0
  64. aria_code/agents/realty/asset_diagnosis.py +152 -0
  65. aria_code/agents/realty/business_match.py +166 -0
  66. aria_code/agents/realty/cashflow_verify.py +209 -0
  67. aria_code/agents/realty/contract_rules.py +210 -0
  68. aria_code/agents/realty/energy_anomaly.py +189 -0
  69. aria_code/agents/realty/exit_settlement.py +208 -0
  70. aria_code/agents/realty/fulfillment_risk.py +206 -0
  71. aria_code/agents/realty/ops_optimize.py +160 -0
  72. aria_code/agents/realty/revenue_share.py +215 -0
  73. aria_code/agents/registry.py +168 -0
  74. aria_code/agents/signal_scheme.py +111 -0
  75. aria_code/agents/sports/__init__.py +0 -0
  76. aria_code/agents/sports/football_agent.py +197 -0
  77. aria_code/agents/supervisor.py +113 -0
  78. aria_code/agents/team.py +500 -0
  79. aria_code/agents/video/__init__.py +16 -0
  80. aria_code/agents/video/director.py +376 -0
  81. aria_code/agents/video/qa_agent.py +143 -0
  82. aria_code/agents/video/script_doctor.py +112 -0
  83. aria_code/agents/warehouse/__init__.py +25 -0
  84. aria_code/agents/warehouse/contracts.py +26 -0
  85. aria_code/agents/warehouse/dashboard.py +155 -0
  86. aria_code/agents/warehouse/fulfillment_leadtime.py +95 -0
  87. aria_code/agents/warehouse/inbound_exceptions.py +25 -0
  88. aria_code/agents/warehouse/inventory_health.py +24 -0
  89. aria_code/agents/warehouse/local_run.py +71 -0
  90. aria_code/agents/warehouse/local_snapshot.py +132 -0
  91. aria_code/agents/warehouse/logistics_cost.py +84 -0
  92. aria_code/agents/warehouse/logistics_sync.py +21 -0
  93. aria_code/agents/warehouse/navigation.schema.json +110 -0
  94. aria_code/agents/warehouse/task_agent.py +172 -0
  95. aria_code/agents/warehouse/workflow.py +31 -0
  96. aria_code/aliyun_data_client.py +4 -0
  97. aria_code/apps/README.md +12 -0
  98. aria_code/apps/__init__.py +2 -0
  99. aria_code/apps/channels/README.md +9 -0
  100. aria_code/apps/channels/__init__.py +17 -0
  101. aria_code/apps/channels/approvals.py +11 -0
  102. aria_code/apps/channels/conversation.py +11 -0
  103. aria_code/apps/channels/digest.py +11 -0
  104. aria_code/apps/channels/intake.py +92 -0
  105. aria_code/apps/channels/registry.py +11 -0
  106. aria_code/apps/channels/tradingview.py +94 -0
  107. aria_code/apps/cli/README.md +13 -0
  108. aria_code/apps/cli/__init__.py +2 -0
  109. aria_code/apps/cli/_optional.py +120 -0
  110. aria_code/apps/cli/app_server.py +656 -0
  111. aria_code/apps/cli/auto_update.py +125 -0
  112. aria_code/apps/cli/bootstrap.py +206 -0
  113. aria_code/apps/cli/broker_render.py +206 -0
  114. aria_code/apps/cli/chat_turn.py +1327 -0
  115. aria_code/apps/cli/cloud_models.py +231 -0
  116. aria_code/apps/cli/codegen_paths.py +29 -0
  117. aria_code/apps/cli/commands/__init__.py +34 -0
  118. aria_code/apps/cli/commands/_business_render.py +78 -0
  119. aria_code/apps/cli/commands/_ui.py +71 -0
  120. aria_code/apps/cli/commands/analysis_cmds.py +378 -0
  121. aria_code/apps/cli/commands/ashare_prediction_cmds.py +327 -0
  122. aria_code/apps/cli/commands/auth_cmds.py +215 -0
  123. aria_code/apps/cli/commands/backtest_cmds.py +2518 -0
  124. aria_code/apps/cli/commands/broker_cmds.py +1236 -0
  125. aria_code/apps/cli/commands/business_workflow_cmds.py +379 -0
  126. aria_code/apps/cli/commands/canvas_cmds.py +77 -0
  127. aria_code/apps/cli/commands/catalog.py +189 -0
  128. aria_code/apps/cli/commands/core_cmds.py +2748 -0
  129. aria_code/apps/cli/commands/data_cmds.py +521 -0
  130. aria_code/apps/cli/commands/diagnostic_cmds.py +285 -0
  131. aria_code/apps/cli/commands/diagnostic_ops_cmds.py +813 -0
  132. aria_code/apps/cli/commands/engineering_cmds.py +399 -0
  133. aria_code/apps/cli/commands/file_cmds.py +312 -0
  134. aria_code/apps/cli/commands/finance_service_cmds.py +60 -0
  135. aria_code/apps/cli/commands/fx_commodity_cmds.py +123 -0
  136. aria_code/apps/cli/commands/market.py +544 -0
  137. aria_code/apps/cli/commands/market_cmds.py +1419 -0
  138. aria_code/apps/cli/commands/market_context.py +432 -0
  139. aria_code/apps/cli/commands/market_render.py +7 -0
  140. aria_code/apps/cli/commands/model_cmds.py +2374 -0
  141. aria_code/apps/cli/commands/ops_cmds.py +721 -0
  142. aria_code/apps/cli/commands/orchestrator_cmds.py +181 -0
  143. aria_code/apps/cli/commands/pdf_export_cmds.py +125 -0
  144. aria_code/apps/cli/commands/portfolio_cmds.py +1316 -0
  145. aria_code/apps/cli/commands/report.py +476 -0
  146. aria_code/apps/cli/commands/scaffold_templates.py +617 -0
  147. aria_code/apps/cli/commands/session_cmds.py +236 -0
  148. aria_code/apps/cli/commands/session_ux_cmds.py +359 -0
  149. aria_code/apps/cli/commands/team.py +723 -0
  150. aria_code/apps/cli/commands/team_render.py +8 -0
  151. aria_code/apps/cli/commands/ui_cmds.py +401 -0
  152. aria_code/apps/cli/commands/warehouse_cmds.py +297 -0
  153. aria_code/apps/cli/commands/workflow_cmds.py +629 -0
  154. aria_code/apps/cli/commands/workspace_cmds.py +1543 -0
  155. aria_code/apps/cli/config_paths.py +73 -0
  156. aria_code/apps/cli/config_store.py +158 -0
  157. aria_code/apps/cli/constants.py +88 -0
  158. aria_code/apps/cli/context.py +34 -0
  159. aria_code/apps/cli/conversation_policy.py +53 -0
  160. aria_code/apps/cli/deterministic.py +200 -0
  161. aria_code/apps/cli/direct.py +48 -0
  162. aria_code/apps/cli/earnings_view.py +172 -0
  163. aria_code/apps/cli/endpoint_migration.py +64 -0
  164. aria_code/apps/cli/exec_events.py +255 -0
  165. aria_code/apps/cli/file_references.py +155 -0
  166. aria_code/apps/cli/finance_service_catalog.py +111 -0
  167. aria_code/apps/cli/football_reports.py +231 -0
  168. aria_code/apps/cli/github_app_auth.py +135 -0
  169. aria_code/apps/cli/google_login.py +239 -0
  170. aria_code/apps/cli/handlers/__init__.py +11 -0
  171. aria_code/apps/cli/handlers/broker_handlers.py +122 -0
  172. aria_code/apps/cli/handlers/chart_handlers.py +1314 -0
  173. aria_code/apps/cli/handlers/market_handlers.py +2570 -0
  174. aria_code/apps/cli/handlers/realty_handlers.py +114 -0
  175. aria_code/apps/cli/handlers/sports_handlers.py +22 -0
  176. aria_code/apps/cli/handlers/strategy_advice.py +82 -0
  177. aria_code/apps/cli/headless.py +352 -0
  178. aria_code/apps/cli/helpers.py +326 -0
  179. aria_code/apps/cli/hooks.py +180 -0
  180. aria_code/apps/cli/i18n.py +310 -0
  181. aria_code/apps/cli/intent.py +136 -0
  182. aria_code/apps/cli/intent_router.py +305 -0
  183. aria_code/apps/cli/intent_shadow.py +72 -0
  184. aria_code/apps/cli/intent_signals.py +347 -0
  185. aria_code/apps/cli/lifecycle_hooks.py +48 -0
  186. aria_code/apps/cli/local_tool_schemas.py +550 -0
  187. aria_code/apps/cli/main.py +39 -0
  188. aria_code/apps/cli/market_hours.py +100 -0
  189. aria_code/apps/cli/market_metadata.py +135 -0
  190. aria_code/apps/cli/market_universe.py +487 -0
  191. aria_code/apps/cli/message_processing.py +310 -0
  192. aria_code/apps/cli/mixin_binding.py +32 -0
  193. aria_code/apps/cli/model_catalog.py +652 -0
  194. aria_code/apps/cli/model_probe.py +169 -0
  195. aria_code/apps/cli/model_retirement.py +65 -0
  196. aria_code/apps/cli/native_branding.py +12 -0
  197. aria_code/apps/cli/native_bridge.py +118 -0
  198. aria_code/apps/cli/native_entry.py +58 -0
  199. aria_code/apps/cli/pdf_report.py +977 -0
  200. aria_code/apps/cli/plan_mode.py +139 -0
  201. aria_code/apps/cli/plotly_html.py +15 -0
  202. aria_code/apps/cli/prediction_feedback.py +202 -0
  203. aria_code/apps/cli/preflight.py +502 -0
  204. aria_code/apps/cli/project_aria.py +60 -0
  205. aria_code/apps/cli/prompt_assembly.py +78 -0
  206. aria_code/apps/cli/prompts/__init__.py +0 -0
  207. aria_code/apps/cli/prompts/coding.py +71 -0
  208. aria_code/apps/cli/prompts/select.py +193 -0
  209. aria_code/apps/cli/prompts/system_prompts.py +562 -0
  210. aria_code/apps/cli/prompts/ui.py +436 -0
  211. aria_code/apps/cli/provider_endpoints.py +237 -0
  212. aria_code/apps/cli/provider_keys.py +73 -0
  213. aria_code/apps/cli/providers/__init__.py +1 -0
  214. aria_code/apps/cli/providers/base.py +775 -0
  215. aria_code/apps/cli/providers/chat_routing.py +139 -0
  216. aria_code/apps/cli/providers/collaboration.py +98 -0
  217. aria_code/apps/cli/providers/llm/__init__.py +1 -0
  218. aria_code/apps/cli/providers/llm/offline_responses.py +68 -0
  219. aria_code/apps/cli/providers/llm/ollama_stream.py +1574 -0
  220. aria_code/apps/cli/providers/llm/response_cache.py +60 -0
  221. aria_code/apps/cli/providers/llm/sse_stream.py +298 -0
  222. aria_code/apps/cli/providers/runtime_bridge.py +586 -0
  223. aria_code/apps/cli/providers/vertexai_stream.py +592 -0
  224. aria_code/apps/cli/release_download.py +159 -0
  225. aria_code/apps/cli/review_runner.py +111 -0
  226. aria_code/apps/cli/runtime_consumer.py +769 -0
  227. aria_code/apps/cli/sandbox.py +32 -0
  228. aria_code/apps/cli/session_export.py +120 -0
  229. aria_code/apps/cli/session_jsonl.py +208 -0
  230. aria_code/apps/cli/session_refresh.py +123 -0
  231. aria_code/apps/cli/session_store.py +125 -0
  232. aria_code/apps/cli/skills_catalog.py +273 -0
  233. aria_code/apps/cli/task_isolation.py +211 -0
  234. aria_code/apps/cli/terminal_chrome.py +412 -0
  235. aria_code/apps/cli/todo_tracker.py +190 -0
  236. aria_code/apps/cli/tool_executor.py +932 -0
  237. aria_code/apps/cli/tool_registry.py +34 -0
  238. aria_code/apps/cli/tool_scope.py +113 -0
  239. aria_code/apps/cli/tools/__init__.py +40 -0
  240. aria_code/apps/cli/tools/context.py +46 -0
  241. aria_code/apps/cli/tools/file_tools.py +181 -0
  242. aria_code/apps/cli/tools/market_tools.py +626 -0
  243. aria_code/apps/cli/tools/notebook_tools.py +111 -0
  244. aria_code/apps/cli/tools/system_tools.py +656 -0
  245. aria_code/apps/cli/tools/write_tools.py +961 -0
  246. aria_code/apps/cli/tradingview_bridge.py +529 -0
  247. aria_code/apps/cli/transactions.py +269 -0
  248. aria_code/apps/cli/turn_planning.py +137 -0
  249. aria_code/apps/cli/update_check.py +257 -0
  250. aria_code/apps/cli/updater.py +373 -0
  251. aria_code/apps/cli/utils/__init__.py +0 -0
  252. aria_code/apps/cli/utils/market_detect.py +1663 -0
  253. aria_code/apps/cli/web_login.py +136 -0
  254. aria_code/apps/cli/workflow_runner.py +127 -0
  255. aria_code/apps/cli/workspace_route.py +121 -0
  256. aria_code/apps/daemon/README.md +14 -0
  257. aria_code/apps/vscode/README.md +115 -0
  258. aria_code/apps/vscode/package.json +70 -0
  259. aria_code/aria_cli.py +5244 -0
  260. aria_code/aria_daemon.py +1372 -0
  261. aria_code/aria_feishu_bot.py +16 -0
  262. aria_code/aria_mcp_server.py +61 -0
  263. aria_code/aria_relay_client.py +4 -0
  264. aria_code/aria_telegram_bot.py +11 -0
  265. aria_code/ariarc.py +385 -0
  266. aria_code/artifacts.py +581 -0
  267. aria_code/backtest_engine.py +4 -0
  268. aria_code/backtest_report.py +4 -0
  269. aria_code/brokers/__init__.py +78 -0
  270. aria_code/brokers/automation.py +193 -0
  271. aria_code/brokers/base.py +231 -0
  272. aria_code/brokers/capabilities.py +264 -0
  273. aria_code/brokers/cn/__init__.py +10 -0
  274. aria_code/brokers/cn/easytrader_broker.py +193 -0
  275. aria_code/brokers/cn/futu_broker.py +194 -0
  276. aria_code/brokers/cn/longbridge_broker.py +190 -0
  277. aria_code/brokers/cn/tiger_broker.py +196 -0
  278. aria_code/brokers/cn/xtquant_broker.py +225 -0
  279. aria_code/brokers/config.py +414 -0
  280. aria_code/brokers/intl/__init__.py +5 -0
  281. aria_code/brokers/intl/alpaca_broker.py +183 -0
  282. aria_code/brokers/intl/ibkr_broker.py +215 -0
  283. aria_code/brokers/intl/trading212_broker.py +183 -0
  284. aria_code/brokers/intl/webull_broker.py +156 -0
  285. aria_code/brokers/paper_broker.py +266 -0
  286. aria_code/brokers/planning.py +296 -0
  287. aria_code/brokers/registry.py +213 -0
  288. aria_code/brokers/trading.py +267 -0
  289. aria_code/canva_client.py +4 -0
  290. aria_code/change_store.py +127 -0
  291. aria_code/clients/__init__.py +0 -0
  292. aria_code/clients/aliyun_data_client.py +660 -0
  293. aria_code/clients/aria_relay_client.py +16 -0
  294. aria_code/clients/canva_client.py +284 -0
  295. aria_code/clients/figma_client.py +100 -0
  296. aria_code/clients/football_data_client.py +1671 -0
  297. aria_code/clients/kling_video_client.py +182 -0
  298. aria_code/clients/market_data_client.py +1985 -0
  299. aria_code/clients/openai_image_client.py +242 -0
  300. aria_code/clients/runway_video_client.py +157 -0
  301. aria_code/clients/warehouse_erp_client.py +105 -0
  302. aria_code/cloud_memory.py +190 -0
  303. aria_code/command_safety.py +19 -0
  304. aria_code/computer_use_server.py +276 -0
  305. aria_code/computer_use_tools.py +4 -0
  306. aria_code/dashboard_generator.py +579 -0
  307. aria_code/data_analysis_tools.py +4 -0
  308. aria_code/data_cleaner.py +483 -0
  309. aria_code/data_service.py +513 -0
  310. aria_code/datasources/__init__.py +23 -0
  311. aria_code/datasources/base.py +166 -0
  312. aria_code/datasources/router.py +253 -0
  313. aria_code/datasources/sec_statements.py +217 -0
  314. aria_code/datasources/sources/__init__.py +15 -0
  315. aria_code/datasources/sources/akshare_source.py +269 -0
  316. aria_code/datasources/sources/alpha_vantage_source.py +203 -0
  317. aria_code/datasources/sources/edgar_source.py +219 -0
  318. aria_code/datasources/sources/finnhub_source.py +198 -0
  319. aria_code/datasources/sources/fred_source.py +223 -0
  320. aria_code/datasources/sources/tushare_source.py +143 -0
  321. aria_code/datasources/sources/web_scraper_source.py +278 -0
  322. aria_code/datasources/sources/world_bank_source.py +206 -0
  323. aria_code/datasources/sources/yfinance_source.py +152 -0
  324. aria_code/doctor.py +860 -0
  325. aria_code/domain/__init__.py +0 -0
  326. aria_code/domain/_finance_core/__init__.py +5 -0
  327. aria_code/domain/_finance_core/backtest_engine.py +325 -0
  328. aria_code/domain/_finance_core/performance_stats.py +101 -0
  329. aria_code/domain/backtest_engine.py +75 -0
  330. aria_code/domain/backtest_report.py +488 -0
  331. aria_code/domain/finance_formulas.py +389 -0
  332. aria_code/domain/performance_stats.py +6 -0
  333. aria_code/domain/portfolio_ledger.py +307 -0
  334. aria_code/domain/strategy_vault.py +421 -0
  335. aria_code/evals/__init__.py +37 -0
  336. aria_code/evals/behavior.py +113 -0
  337. aria_code/evals/gap.py +93 -0
  338. aria_code/evals/harness.py +949 -0
  339. aria_code/evals/review_probe.py +132 -0
  340. aria_code/evals/runner.py +425 -0
  341. aria_code/evals/scorecard.py +322 -0
  342. aria_code/evals/summary.py +125 -0
  343. aria_code/external_agent_runner.py +115 -0
  344. aria_code/figma_client.py +4 -0
  345. aria_code/file_analysis_tools.py +4 -0
  346. aria_code/finance_formulas.py +4 -0
  347. aria_code/football_data_client.py +4 -0
  348. aria_code/image_gen_tools.py +4 -0
  349. aria_code/intent_classifier.py +459 -0
  350. aria_code/kling_video_client.py +4 -0
  351. aria_code/licensing.py +151 -0
  352. aria_code/local_finance_tools.py +4 -0
  353. aria_code/local_image_provider.py +4 -0
  354. aria_code/local_llm_provider.py +4 -0
  355. aria_code/macro_tools.py +4 -0
  356. aria_code/markdown_pdf.py +289 -0
  357. aria_code/market_data_client.py +4 -0
  358. aria_code/mcp_client.py +744 -0
  359. aria_code/memory_manager.py +273 -0
  360. aria_code/model_capability.py +512 -0
  361. aria_code/notification_tools.py +4 -0
  362. aria_code/openai_image_client.py +4 -0
  363. aria_code/packages/__init__.py +26 -0
  364. aria_code/packages/adk_bridge/__init__.py +11 -0
  365. aria_code/packages/adk_bridge/code_review_tools.py +49 -0
  366. aria_code/packages/adk_bridge/market_tools.py +125 -0
  367. aria_code/packages/aria_agents/__init__.py +5 -0
  368. aria_code/packages/aria_agents/manifest.py +78 -0
  369. aria_code/packages/aria_core/__init__.py +34 -0
  370. aria_code/packages/aria_core/architecture.py +269 -0
  371. aria_code/packages/aria_core/export.py +147 -0
  372. aria_code/packages/aria_core/manifest.py +65 -0
  373. aria_code/packages/aria_core/paths.py +57 -0
  374. aria_code/packages/aria_core/secure_file.py +68 -0
  375. aria_code/packages/aria_infra/__init__.py +15 -0
  376. aria_code/packages/aria_infra/arthera.py +58 -0
  377. aria_code/packages/aria_infra/doctor.py +246 -0
  378. aria_code/packages/aria_infra/product.py +40 -0
  379. aria_code/packages/aria_mcp/__init__.py +25 -0
  380. aria_code/packages/aria_mcp/bridge.py +232 -0
  381. aria_code/packages/aria_mcp/circuit.py +82 -0
  382. aria_code/packages/aria_mcp/config.py +162 -0
  383. aria_code/packages/aria_mcp/installer.py +95 -0
  384. aria_code/packages/aria_mcp/server.py +1705 -0
  385. aria_code/packages/aria_mcp/tools.py +81 -0
  386. aria_code/packages/aria_sdk/__init__.py +19 -0
  387. aria_code/packages/aria_sdk/client.py +393 -0
  388. aria_code/packages/aria_sdk/providers.py +70 -0
  389. aria_code/packages/aria_sdk/streaming.py +75 -0
  390. aria_code/packages/aria_sdk/types.py +86 -0
  391. aria_code/packages/aria_services/__init__.py +92 -0
  392. aria_code/packages/aria_services/cache.py +60 -0
  393. aria_code/packages/aria_services/context.py +356 -0
  394. aria_code/packages/aria_services/context_checkpoints.py +157 -0
  395. aria_code/packages/aria_services/data.py +11 -0
  396. aria_code/packages/aria_services/provider_health.py +304 -0
  397. aria_code/packages/aria_services/references.py +247 -0
  398. aria_code/packages/aria_services/registry.py +249 -0
  399. aria_code/packages/aria_services/research_protocol.py +137 -0
  400. aria_code/packages/aria_services/research_quality.py +186 -0
  401. aria_code/packages/aria_services/settings.py +160 -0
  402. aria_code/packages/aria_services/usage.py +151 -0
  403. aria_code/packages/aria_skills/__init__.py +46 -0
  404. aria_code/packages/aria_skills/catalog.py +149 -0
  405. aria_code/packages/aria_skills/loader.py +455 -0
  406. aria_code/packages/aria_skills/registry.py +84 -0
  407. aria_code/packages/aria_tools/__init__.py +5 -0
  408. aria_code/packages/aria_tools/financial/__init__.py +5 -0
  409. aria_code/packages/aria_tools/financial/_paths.py +92 -0
  410. aria_code/packages/aria_tools/registry.py +129 -0
  411. aria_code/packages/quant_engine/__init__.py +33 -0
  412. aria_code/packages/quant_engine/_namespace.py +24 -0
  413. aria_code/packages/quant_engine/backtest/__init__.py +10 -0
  414. aria_code/packages/quant_engine/backtest/core.py +53 -0
  415. aria_code/packages/quant_engine/backtest/engine.py +102 -0
  416. aria_code/packages/quant_engine/portfolio/__init__.py +9 -0
  417. aria_code/packages/quant_engine/portfolio/optimizer.py +85 -0
  418. aria_code/packages/quant_engine/sports/__init__.py +76 -0
  419. aria_code/packages/quant_engine/sports/calibrator.py +354 -0
  420. aria_code/packages/quant_engine/sports/dixon_coles.py +234 -0
  421. aria_code/packages/quant_engine/sports/elo.py +300 -0
  422. aria_code/packages/quant_engine/sports/form.py +188 -0
  423. aria_code/packages/quant_engine/sports/h2h.py +195 -0
  424. aria_code/packages/quant_engine/sports/predictor.py +312 -0
  425. aria_code/packages/quant_engine/sports/tracker.py +665 -0
  426. aria_code/packages/quant_engine/stochastic/__init__.py +31 -0
  427. aria_code/packages/quant_engine/stochastic/gbm_enhanced.py +195 -0
  428. aria_code/packages/quant_engine/stochastic/ito_calculus.py +477 -0
  429. aria_code/packages/quant_engine/stochastic/kelly_criterion.py +181 -0
  430. aria_code/packages/quant_engine/stochastic/monte_carlo_advanced.py +95 -0
  431. aria_code/packages/quant_engine/stochastic/options_pricing.py +576 -0
  432. aria_code/packages/quant_engine/stochastic/stochastic_processes.py +90 -0
  433. aria_code/packs/__init__.py +85 -0
  434. aria_code/packs/base.py +168 -0
  435. aria_code/packs/finance/__init__.py +250 -0
  436. aria_code/packs/healthcare/__init__.py +40 -0
  437. aria_code/packs/logistics/__init__.py +171 -0
  438. aria_code/packs/payments/__init__.py +139 -0
  439. aria_code/packs/realty/__init__.py +190 -0
  440. aria_code/packs/registry.py +202 -0
  441. aria_code/packs/rules.py +90 -0
  442. aria_code/plan_utils.py +194 -0
  443. aria_code/plugin_loader.py +328 -0
  444. aria_code/portfolio_ledger.py +4 -0
  445. aria_code/preview_server.py +449 -0
  446. aria_code/privacy/__init__.py +5 -0
  447. aria_code/privacy/feedback.py +123 -0
  448. aria_code/project_review.py +278 -0
  449. aria_code/project_review_api.py +235 -0
  450. aria_code/project_review_server.py +18 -0
  451. aria_code/project_tools.py +4 -0
  452. aria_code/providers/__init__.py +30 -0
  453. aria_code/providers/google_video_provider.py +256 -0
  454. aria_code/providers/llm/__init__.py +19 -0
  455. aria_code/providers/llm/anthropic.py +184 -0
  456. aria_code/providers/llm/arthera_gateway.py +90 -0
  457. aria_code/providers/llm/autoconfig.py +213 -0
  458. aria_code/providers/llm/base.py +139 -0
  459. aria_code/providers/llm/custom.py +158 -0
  460. aria_code/providers/llm/ollama.py +137 -0
  461. aria_code/providers/llm/openai_compat.py +350 -0
  462. aria_code/providers/llm/registry.py +457 -0
  463. aria_code/providers/local_image_provider.py +202 -0
  464. aria_code/providers/local_llm_provider.py +549 -0
  465. aria_code/providers/ollama_models.py +79 -0
  466. aria_code/providers/openai_params.py +32 -0
  467. aria_code/providers/tool_messages.py +100 -0
  468. aria_code/realty_data_tools.py +4 -0
  469. aria_code/report_exporters.py +150 -0
  470. aria_code/report_generator.py +1764 -0
  471. aria_code/review_service.py +497 -0
  472. aria_code/runtime/__init__.py +176 -0
  473. aria_code/runtime/acceptance.py +632 -0
  474. aria_code/runtime/agent_loop.py +1851 -0
  475. aria_code/runtime/agent_profiles.py +75 -0
  476. aria_code/runtime/approval.py +71 -0
  477. aria_code/runtime/baseline.py +219 -0
  478. aria_code/runtime/budget.py +232 -0
  479. aria_code/runtime/checkpoints.py +481 -0
  480. aria_code/runtime/consult.py +65 -0
  481. aria_code/runtime/contract.py +282 -0
  482. aria_code/runtime/delivery.py +432 -0
  483. aria_code/runtime/events.py +113 -0
  484. aria_code/runtime/gateway.py +167 -0
  485. aria_code/runtime/lsp.py +484 -0
  486. aria_code/runtime/native_index.py +153 -0
  487. aria_code/runtime/orchestrator.py +55 -0
  488. aria_code/runtime/processes.py +343 -0
  489. aria_code/runtime/project_graph.py +614 -0
  490. aria_code/runtime/repo_map.py +712 -0
  491. aria_code/runtime/research_bundle.py +105 -0
  492. aria_code/runtime/review.py +269 -0
  493. aria_code/runtime/run_state.py +127 -0
  494. aria_code/runtime/run_store.py +522 -0
  495. aria_code/runtime/scoped_runs.py +113 -0
  496. aria_code/runtime/self_healing.py +422 -0
  497. aria_code/runtime/semantic_diff.py +171 -0
  498. aria_code/runtime/subagent.py +721 -0
  499. aria_code/runtime/symbol_edit.py +117 -0
  500. aria_code/runtime/task_brief.py +46 -0
  501. aria_code/runtime/task_context.py +145 -0
  502. aria_code/runtime/task_ledger.py +108 -0
  503. aria_code/runtime/task_orchestrator.py +90 -0
  504. aria_code/runtime/task_outcome.py +33 -0
  505. aria_code/runtime/task_verification.py +266 -0
  506. aria_code/runtime/task_worktree.py +448 -0
  507. aria_code/runtime/tool_executor.py +329 -0
  508. aria_code/runtime/tool_policy.py +107 -0
  509. aria_code/runtime/transactions.py +216 -0
  510. aria_code/runtime/windows_job.py +115 -0
  511. aria_code/runtime/workflows.py +222 -0
  512. aria_code/runtime/worktrees.py +215 -0
  513. aria_code/runway_video_client.py +4 -0
  514. aria_code/safety/__init__.py +27 -0
  515. aria_code/safety/client_isolation.py +178 -0
  516. aria_code/safety/permissions.py +339 -0
  517. aria_code/safety/risk.py +699 -0
  518. aria_code/safety/sandbox.py +148 -0
  519. aria_code/safety/service.py +102 -0
  520. aria_code/service_endpoints.py +14 -0
  521. aria_code/setup_wizard.py +684 -0
  522. aria_code/spreadsheet_tools.py +4 -0
  523. aria_code/strategy_vault.py +4 -0
  524. aria_code/tools/__init__.py +0 -0
  525. aria_code/tools/artifact_tools.py +252 -0
  526. aria_code/tools/broker_tools.py +92 -0
  527. aria_code/tools/build_quant_engine.py +97 -0
  528. aria_code/tools/code_audit_tools.py +192 -0
  529. aria_code/tools/computer_use_tools.py +509 -0
  530. aria_code/tools/data_analysis_tools.py +831 -0
  531. aria_code/tools/enterprise_finance_tools.py +202 -0
  532. aria_code/tools/extended_tools.py +240 -0
  533. aria_code/tools/file_analysis_tools.py +880 -0
  534. aria_code/tools/image_gen_tools.py +114 -0
  535. aria_code/tools/local_finance_tools.py +3488 -0
  536. aria_code/tools/logistics_carriers.py +11 -0
  537. aria_code/tools/logistics_inventory.py +11 -0
  538. aria_code/tools/logistics_io.py +11 -0
  539. aria_code/tools/logistics_tenancy.py +11 -0
  540. aria_code/tools/logistics_tools.py +11 -0
  541. aria_code/tools/macro_tools.py +412 -0
  542. aria_code/tools/notification_tools.py +249 -0
  543. aria_code/tools/project_tools.py +525 -0
  544. aria_code/tools/realty_data_tools.py +662 -0
  545. aria_code/tools/spreadsheet_tools.py +399 -0
  546. aria_code/tools/stripe_tools.py +115 -0
  547. aria_code/tools/video_tools.py +500 -0
  548. aria_code/training/__init__.py +1 -0
  549. aria_code/training/compare.py +117 -0
  550. aria_code/training/prelude_commands.py +111 -0
  551. aria_code/training/prelude_eval.py +390 -0
  552. aria_code/training/prelude_real.py +183 -0
  553. aria_code/training/prelude_train.py +248 -0
  554. aria_code/training/sft_dataset.py +230 -0
  555. aria_code/training/trajectory_dataset.py +263 -0
  556. aria_code/ui/__init__.py +105 -0
  557. aria_code/ui/banner.py +404 -0
  558. aria_code/ui/completer.py +571 -0
  559. aria_code/ui/console.py +285 -0
  560. aria_code/ui/input_box.py +505 -0
  561. aria_code/ui/mentions.py +206 -0
  562. aria_code/ui/picker.py +248 -0
  563. aria_code/ui/render/__init__.py +11 -0
  564. aria_code/ui/render/actions.py +297 -0
  565. aria_code/ui/render/finance.py +1732 -0
  566. aria_code/ui/render/market.py +253 -0
  567. aria_code/ui/render/output.py +954 -0
  568. aria_code/ui/render/responsive.py +75 -0
  569. aria_code/ui/render/team.py +367 -0
  570. aria_code/ui/robot.py +239 -0
  571. aria_code/ui/startup_dashboard.py +110 -0
  572. aria_code/video_analysis.py +147 -0
  573. aria_code/video_editor.py +518 -0
  574. aria_code/video_workflow/__init__.py +8 -0
  575. aria_code/video_workflow/__main__.py +61 -0
  576. aria_code/video_workflow/engines/__init__.py +18 -0
  577. aria_code/video_workflow/engines/base.py +69 -0
  578. aria_code/video_workflow/engines/custom_model.py +204 -0
  579. aria_code/video_workflow/engines/google_veo.py +94 -0
  580. aria_code/video_workflow/engines/local_engine.py +71 -0
  581. aria_code/video_workflow/engines/registry.py +98 -0
  582. aria_code/video_workflow/google_models.py +278 -0
  583. aria_code/video_workflow/music/__init__.py +5 -0
  584. aria_code/video_workflow/music/foley_service.py +132 -0
  585. aria_code/video_workflow/music/service.py +212 -0
  586. aria_code/video_workflow/pipeline.py +620 -0
  587. aria_code/video_workflow/plan.py +202 -0
  588. aria_code/video_workflow/session.py +297 -0
  589. aria_code/workspace/__init__.py +6 -0
  590. aria_code/workspace/files.py +219 -0
  591. aria_code/workspace/verify.py +167 -0
  592. aria_code/workspace_changes.py +199 -0
  593. aria_code/workspace_service.py +196 -0
  594. aria_code/workspace_tools.py +150 -0
  595. aria_code-0.167.0.dist-info/METADATA +330 -0
  596. aria_code-0.167.0.dist-info/RECORD +601 -0
  597. aria_code-0.167.0.dist-info/WHEEL +5 -0
  598. aria_code-0.167.0.dist-info/entry_points.txt +5 -0
  599. aria_code-0.167.0.dist-info/licenses/LICENSE +201 -0
  600. aria_code-0.167.0.dist-info/licenses/NOTICE +49 -0
  601. aria_code-0.167.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,620 @@
1
+ """Script + images → finished short video, as a resumable pipeline.
2
+
3
+ plan_video() 1 understand assets (Gemini, reads every image)
4
+ 2 write the edit plan (Gemini, JSON schema, validated)
5
+ → edit_plan.json, for the user to review and edit
6
+ render_video() 3 narrate (Gemini TTS; beats stretch to fit speech)
7
+ 4 render (ffmpeg; Veo only with confirmation)
8
+ 5 verify (ffprobe / blackdetect / silencedetect)
9
+ 6 manifest (sources, models, checks)
10
+
11
+ Only stages 1–2 use judgement. Everything after reads the plan and is
12
+ deterministic, so an unsatisfying video is fixed by editing edit_plan.json and
13
+ rendering again, not by rerunning the model. Each stage writes into the
14
+ project directory and a finished stage is reused on the next run.
15
+ """
16
+ from __future__ import annotations
17
+
18
+ import hashlib
19
+ import json
20
+ import re
21
+ import shutil
22
+ import subprocess
23
+ from copy import deepcopy
24
+ from dataclasses import asdict
25
+ from datetime import datetime, timezone
26
+ from pathlib import Path
27
+ from typing import Any, Callable, Dict, List, Optional
28
+
29
+ # The submodule directly, not a name off the package: the package's
30
+ # __init__ imports this module, so asking it for an attribute while it
31
+ # is still executing is a cycle — the kind that only breaks when the
32
+ # import order changes, which is why main guards against it.
33
+ import aria_code.video_workflow.google_models as gm
34
+ from aria_code.video_workflow.plan import (
35
+ EFFECTS, MAX_GENERATED_SECONDS, PAID_SOURCES, PURPOSES, SOURCE_KINDS, Asset, Beat, EditPlan, default_aspect, to_srt, validate_plan,
36
+ )
37
+
38
+ IMAGE_SUFFIXES = {".jpg", ".jpeg", ".png", ".webp", ".heic", ".bmp"}
39
+ VIDEO_SUFFIXES = {".mp4", ".mov", ".m4v", ".webm", ".mkv"}
40
+ FPS = 25
41
+ SPEECH_PADDING = 0.4 # seconds of air after each beat's narration
42
+ FRAME_SIZE = {"9:16": (1080, 1920), "16:9": (1920, 1080), "1:1": (1080, 1080)}
43
+
44
+ Progress = Callable[[str, str], None]
45
+
46
+
47
+ def _noop(stage: str, message: str) -> None: # pragma: no cover - default sink
48
+ pass
49
+
50
+
51
+ class VideoWorkflowError(RuntimeError):
52
+ pass
53
+
54
+
55
+ # ── ffmpeg helpers ───────────────────────────────────────────────────────────
56
+
57
+ def _ffmpeg(args: List[str], timeout: float = 600.0) -> None:
58
+ if shutil.which("ffmpeg") is None:
59
+ raise VideoWorkflowError("ffmpeg is not installed (brew install ffmpeg)")
60
+ result = subprocess.run(["ffmpeg", "-y", "-loglevel", "error", *args], capture_output=True, text=True, timeout=timeout)
61
+ if result.returncode != 0:
62
+ raise VideoWorkflowError(f"ffmpeg failed: {result.stderr[-400:]}")
63
+
64
+
65
+ def ffmpeg_has_filter(name: str) -> bool:
66
+ """Whether this ffmpeg build has a filter. Homebrew's default build lacks libass, so no `subtitles`."""
67
+ result = subprocess.run(["ffmpeg", "-hide_banner", "-filters"], capture_output=True, text=True, timeout=30)
68
+ return any(line.split()[1:2] == [name] for line in result.stdout.splitlines() if len(line.split()) > 1)
69
+
70
+
71
+ def media_duration(path: str) -> float:
72
+ result = subprocess.run(
73
+ ["ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "default=nw=1:nk=1", path],
74
+ capture_output=True, text=True, timeout=30,
75
+ )
76
+ try:
77
+ return float(result.stdout.strip())
78
+ except ValueError as exc:
79
+ raise VideoWorkflowError(f"Could not read the duration of {Path(path).name}") from exc
80
+
81
+
82
+ def _sha256(path: Path) -> str:
83
+ digest = hashlib.sha256()
84
+ with path.open("rb") as handle:
85
+ for block in iter(lambda: handle.read(1 << 20), b""):
86
+ digest.update(block)
87
+ return digest.hexdigest()
88
+
89
+
90
+ def _write_json(path: Path, data: Any) -> None:
91
+ path.parent.mkdir(parents=True, exist_ok=True)
92
+ path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
93
+
94
+
95
+ # ── 1. Understand assets ─────────────────────────────────────────────────────
96
+
97
+ _ASSET_SCHEMA = {
98
+ "type": "object",
99
+ "properties": {
100
+ "assets": {
101
+ "type": "array",
102
+ "items": {
103
+ "type": "object",
104
+ "properties": {
105
+ "id": {"type": "string"},
106
+ "description": {"type": "string"},
107
+ "subjects": {"type": "array", "items": {"type": "string"}},
108
+ "text_in_frame": {"type": "string"},
109
+ "orientation": {"type": "string", "enum": ["portrait", "landscape", "square"]},
110
+ "suggested_use": {"type": "string"},
111
+ "rights_note": {"type": "string"},
112
+ },
113
+ "required": ["id", "description", "orientation", "suggested_use"],
114
+ },
115
+ }
116
+ },
117
+ "required": ["assets"],
118
+ }
119
+
120
+
121
+ def _frame_for_understanding(video: Path, workdir: Path) -> Path:
122
+ frame = workdir / f"{video.stem}_frame.jpg"
123
+ if not frame.exists():
124
+ middle = max(0.0, media_duration(str(video)) / 2)
125
+ _ffmpeg(["-ss", f"{middle:.2f}", "-i", str(video), "-frames:v", "1", "-q:v", "3", str(frame)], timeout=60)
126
+ return frame
127
+
128
+
129
+ def understand_assets(paths: List[str], script: str, workdir: Path, *, language: str = "zh") -> List[Asset]:
130
+ """Describe each provided image or clip so the plan can place it deliberately."""
131
+ files = [Path(p).expanduser().resolve() for p in paths]
132
+ for file in files:
133
+ if not file.exists():
134
+ raise VideoWorkflowError(f"Asset not found: {file}")
135
+ if file.suffix.lower() not in IMAGE_SUFFIXES | VIDEO_SUFFIXES:
136
+ raise VideoWorkflowError(f"{file.name}: only images and video clips can be used as assets")
137
+ assets = [
138
+ Asset(id=f"A{index + 1}", path=str(file), kind="video" if file.suffix.lower() in VIDEO_SUFFIXES else "image")
139
+ for index, file in enumerate(files)
140
+ ]
141
+ if not assets:
142
+ return []
143
+
144
+ parts: List[Dict[str, Any]] = [{"text": (
145
+ "下面是一个短视频项目的素材。逐个描述每个素材,供之后写分镜使用。\n"
146
+ f"脚本:\n{script.strip()}\n\n"
147
+ "每个素材写:画面内容、主体、画面里出现的文字、横竖构图、在这个脚本里最适合放在哪里;"
148
+ "如果画面里有人脸、商标、他人作品或看起来来自第三方的内容,在 rights_note 里写明需要确认的授权。"
149
+ f"用{'中文' if language.startswith('zh') else 'English'}回答。id 必须与标注的一致。"
150
+ )}]
151
+ workdir.mkdir(parents=True, exist_ok=True)
152
+ for asset in assets:
153
+ source = Path(asset.path)
154
+ view = _frame_for_understanding(source, workdir) if asset.kind == "video" else source
155
+ parts.append({"text": f"素材 {asset.id}({asset.kind},文件名 {source.name}):"})
156
+ parts.append(gm.image_part(str(view)))
157
+
158
+ described = {item.get("id"): item for item in gm.generate_json(parts, _ASSET_SCHEMA).get("assets", [])}
159
+ for asset in assets:
160
+ item = described.get(asset.id, {})
161
+ asset.description = str(item.get("description") or "")
162
+ asset.subjects = [str(s) for s in item.get("subjects") or []]
163
+ asset.text_in_frame = str(item.get("text_in_frame") or "")
164
+ asset.orientation = str(item.get("orientation") or "")
165
+ asset.suggested_use = str(item.get("suggested_use") or "")
166
+ asset.rights_note = str(item.get("rights_note") or "")
167
+ return assets
168
+
169
+
170
+ # ── 2. Write the edit plan ───────────────────────────────────────────────────
171
+
172
+ _PLAN_SCHEMA = {
173
+ "type": "object",
174
+ "properties": {
175
+ "title": {"type": "string"},
176
+ "beats": {
177
+ "type": "array",
178
+ "items": {
179
+ "type": "object",
180
+ "properties": {
181
+ "id": {"type": "string"},
182
+ "duration": {"type": "number"},
183
+ "purpose": {"type": "string", "enum": list(PURPOSES)},
184
+ "visual": {"type": "string"},
185
+ "source": {"type": "string", "enum": list(SOURCE_KINDS)},
186
+ "asset_id": {"type": "string"},
187
+ "generate_prompt": {"type": "string"},
188
+ "effect": {"type": "string", "enum": list(EFFECTS)},
189
+ "copy": {"type": "string"},
190
+ "caption": {"type": "string"},
191
+ "pending": {"type": "boolean"},
192
+ },
193
+ "required": ["id", "duration", "purpose", "visual", "source", "effect", "copy"],
194
+ },
195
+ },
196
+ "notes": {"type": "array", "items": {"type": "string"}},
197
+ },
198
+ "required": ["title", "beats"],
199
+ }
200
+
201
+
202
+ def _plan_schema(assets: List[Asset], allow_generation: bool) -> Dict[str, Any]:
203
+ """The plan schema narrowed to what is actually legal for this request.
204
+
205
+ asset_id was optional and unconstrained, and the rule that a shot taken
206
+ from the material must name it lived only in the prose instructions.
207
+ Structured output drops optional fields, so the model answered
208
+ source="asset" for every beat and omitted asset_id on all of them — the
209
+ plan then failed validation twice over ("uses asset ''"), after two full
210
+ rounds of paid calls, with an attached image sitting unused.
211
+
212
+ A rule the schema can state is stated here instead: the ids that exist are
213
+ the only ones it can choose from, and with generation switched off
214
+ "asset" is the only source there is, so naming one is not optional.
215
+ """
216
+ schema = deepcopy(_PLAN_SCHEMA)
217
+ beat = schema["properties"]["beats"]["items"]
218
+ ids = [asset.id for asset in assets]
219
+ if ids:
220
+ beat["properties"]["asset_id"] = {"type": "string", "enum": ids}
221
+ if not allow_generation:
222
+ # Every other source is paid, so there is nothing else to choose.
223
+ beat["properties"]["source"] = {"type": "string", "enum": ["asset"]}
224
+ if ids:
225
+ beat["required"] = [*beat["required"], "asset_id"]
226
+ return schema
227
+
228
+
229
+ def _plan_instructions(target_seconds: int, allow_generation: bool, language: str) -> str:
230
+ return (
231
+ "你是短视频剪辑师,按 short-form-video-editing 技能的规则把脚本和素材写成分镜。规则:\n"
232
+ "- 第一个镜头是 hook,3 秒内抓住注意力;最后是 conclusion 或 cta。\n"
233
+ f"- 总时长约 {target_seconds} 秒;每个镜头 2–6 秒,一个镜头只讲一件事。\n"
234
+ "- copy 是这个镜头的旁白,口语、短句,朗读时长要放得进这个镜头(中文约每秒 4 个字)。\n"
235
+ "- caption 是屏幕字幕,比旁白更短;不写就沿用旁白。\n"
236
+ "- source=asset 时 asset_id 必须是给出的素材 id;同一素材可复用,但换一个镜头运动。\n"
237
+ + (
238
+ f"- 想让用户给的某张图片真正动起来(人物动作、镜头推进、水流烟雾),用 source=animate,asset_id 写那张图片,generate_prompt 只描述动作和镜头运动,不要改画面内容;画面会从这张图开始,保持原图。\n"
239
+ f"- 素材不够时才用 source=generate,并写出具体的 generate_prompt(画面、镜头、光线、风格,英文更稳)。\n"
240
+ f"- animate 和 generate 镜头都不超过 {MAX_GENERATED_SECONDS} 秒、按秒计费,尽量少用;能用素材的普通运镜解决就用 source=asset。\n"
241
+ if allow_generation else
242
+ "- 不允许生成或让图片动起来:每个镜头都必须使用给出的素材(source=asset)。\n"
243
+ )
244
+ + "- 画面要有变化:同一素材最多连续用 2 个镜头,给出的素材尽量都用上。\n"
245
+ "- 需要用户补充的内容(缺素材、缺授权、事实待核实)把 pending 设为 true,并写进 notes。\n"
246
+ "- 不编造数据、价格、功效或承诺。\n"
247
+ f"- 用{'中文' if language.startswith('zh') else 'English'}写旁白与字幕。"
248
+ )
249
+
250
+
251
+ def _plan_from_model(data: Dict[str, Any], *, project_id: str, platform: str, aspect: str, language: str) -> EditPlan:
252
+ beats: List[Beat] = []
253
+ cursor = 0.0
254
+ for index, raw in enumerate(data.get("beats") or []):
255
+ duration = max(1.5, float(raw.get("duration") or 3.0))
256
+ beats.append(Beat(
257
+ id=str(raw.get("id") or f"B{index + 1}"),
258
+ start=round(cursor, 3),
259
+ end=round(cursor + duration, 3),
260
+ purpose=str(raw.get("purpose") or "evidence"),
261
+ visual=str(raw.get("visual") or ""),
262
+ source=str(raw.get("source") or "asset"),
263
+ asset_id=str(raw.get("asset_id") or ""),
264
+ generate_prompt=str(raw.get("generate_prompt") or ""),
265
+ effect=str(raw.get("effect") or "zoom_in"),
266
+ copy=str(raw.get("copy") or ""),
267
+ caption=str(raw.get("caption") or ""),
268
+ pending=bool(raw.get("pending")),
269
+ ))
270
+ cursor += duration
271
+ return EditPlan(
272
+ project_id=project_id, title=str(data.get("title") or project_id), platform=platform, aspect_ratio=aspect,
273
+ language=language, beats=beats, notes=[str(n) for n in data.get("notes") or []],
274
+ )
275
+
276
+
277
+ def write_edit_plan(
278
+ script: str,
279
+ assets: List[Asset],
280
+ *,
281
+ project_id: str,
282
+ platform: str,
283
+ aspect: str,
284
+ language: str,
285
+ target_seconds: int,
286
+ allow_generation: bool,
287
+ ) -> EditPlan:
288
+ """Ask Gemini for the plan; if it breaks a rule, show it the problems once and ask again."""
289
+ catalogue = [
290
+ {key: value for key, value in asdict(asset).items() if key != "path"} | {"file": Path(asset.path).name}
291
+ for asset in assets
292
+ ]
293
+ base = (
294
+ f"平台:{platform},画幅 {aspect}\n\n脚本:\n{script.strip()}\n\n"
295
+ f"素材:\n{json.dumps(catalogue, ensure_ascii=False, indent=2)}"
296
+ )
297
+ system = _plan_instructions(target_seconds, allow_generation, language)
298
+ by_id = {asset.id: asset for asset in assets}
299
+ schema = _plan_schema(assets, allow_generation)
300
+ plan = _plan_from_model(gm.generate_json([{"text": base}], schema, system=system),
301
+ project_id=project_id, platform=platform, aspect=aspect, language=language)
302
+ problems = validate_plan(plan, by_id) + _generation_problems(plan, allow_generation)
303
+ if problems:
304
+ retry = base + "\n\n上一版分镜有这些问题,请修正后重写完整分镜:\n- " + "\n- ".join(problems)
305
+ plan = _plan_from_model(gm.generate_json([{"text": retry}], schema, system=system),
306
+ project_id=project_id, platform=platform, aspect=aspect, language=language)
307
+ problems = validate_plan(plan, by_id) + _generation_problems(plan, allow_generation)
308
+ if problems:
309
+ raise VideoWorkflowError("The edit plan still breaks the rules after one revision:\n- " + "\n- ".join(problems))
310
+ return plan
311
+
312
+
313
+ def _generation_problems(plan: EditPlan, allow_generation: bool) -> List[str]:
314
+ if allow_generation:
315
+ return []
316
+ return [f"Beat {beat.id} uses Veo ({beat.source}), but generation is not allowed for this video." for beat in plan.beats if beat.source in PAID_SOURCES]
317
+
318
+
319
+ def plan_video(
320
+ script: str,
321
+ asset_paths: List[str],
322
+ project_dir: str,
323
+ *,
324
+ platform: str = "douyin",
325
+ aspect_ratio: Optional[str] = None,
326
+ language: str = "zh",
327
+ target_seconds: int = 30,
328
+ allow_generation: bool = False,
329
+ force: bool = False,
330
+ progress: Progress = _noop,
331
+ ) -> Dict[str, Any]:
332
+ """Stages 1–2. Writes assets.json and edit_plan.json into project_dir and returns them."""
333
+ root = Path(project_dir).expanduser().resolve()
334
+ root.mkdir(parents=True, exist_ok=True)
335
+ (root / "script.txt").write_text(script, encoding="utf-8")
336
+ project_id = re.sub(r"[^a-zA-Z0-9_-]+", "-", root.name).strip("-") or "video"
337
+ aspect = default_aspect(platform, aspect_ratio)
338
+
339
+ # Nothing to cut together, and not allowed to generate anything: the
340
+ # planner would be asked to pick shots from an empty list, write beats
341
+ # with an empty asset, and fail validation a revision later — a minute of
342
+ # paid model calls to reach a conclusion that is available here.
343
+ if not asset_paths and not allow_generation:
344
+ raise VideoWorkflowError(
345
+ "No material to make a video from: attach images or clips, "
346
+ "or allow shots to be generated."
347
+ )
348
+
349
+ assets_file = root / "assets.json"
350
+ if assets_file.exists() and not force:
351
+ assets = [Asset(**item) for item in json.loads(assets_file.read_text(encoding="utf-8"))]
352
+ progress("understand", "沿用已有的素材理解")
353
+ else:
354
+ progress("understand", f"正在理解 {len(asset_paths)} 个素材")
355
+ assets = understand_assets(asset_paths, script, root / "work", language=language)
356
+ _write_json(assets_file, [asdict(asset) for asset in assets])
357
+
358
+ progress("plan", "正在写分镜")
359
+ plan = write_edit_plan(script, assets, project_id=project_id, platform=platform, aspect=aspect, language=language,
360
+ target_seconds=target_seconds, allow_generation=allow_generation)
361
+ _write_json(root / "edit_plan.json", plan.to_dict())
362
+ return {
363
+ "project_dir": str(root),
364
+ "plan_path": str(root / "edit_plan.json"),
365
+ "plan": plan.to_dict(),
366
+ "assets": [asdict(asset) for asset in assets],
367
+ "generated_seconds": plan.generated_seconds(),
368
+ "pending": [beat.id for beat in plan.beats if beat.pending],
369
+ }
370
+
371
+
372
+ # ── 3. Narrate ───────────────────────────────────────────────────────────────
373
+
374
+ def narrate(plan: EditPlan, root: Path, progress: Progress = _noop) -> Path:
375
+ """Speak each beat, stretch beats that are shorter than their speech, and build one narration track."""
376
+ voice_dir = root / "narration"
377
+ voice_dir.mkdir(parents=True, exist_ok=True)
378
+ for beat in plan.beats:
379
+ if not beat.copy.strip():
380
+ continue
381
+ key = hashlib.sha1(f"{plan.voice}|{beat.copy}".encode("utf-8")).hexdigest()[:12]
382
+ speech = voice_dir / f"{beat.id}-{key}.wav"
383
+ if not speech.exists():
384
+ progress("narrate", f"正在为镜头 {beat.id} 配音")
385
+ gm.synthesize_speech(beat.copy, str(speech), voice=plan.voice or None)
386
+ needed = media_duration(str(speech)) + SPEECH_PADDING
387
+ if needed > beat.duration:
388
+ beat.end = round(beat.start + needed, 3)
389
+ setattr(beat, "_speech", str(speech))
390
+ plan.retime()
391
+
392
+ padded: List[str] = []
393
+ for beat in plan.beats:
394
+ out = voice_dir / f"{beat.id}-padded.wav"
395
+ speech = getattr(beat, "_speech", None)
396
+ if speech:
397
+ _ffmpeg(["-i", speech, "-af", f"apad,atrim=0:{beat.duration:.3f}", "-ar", "44100", "-ac", "2", str(out)], timeout=60)
398
+ else:
399
+ _ffmpeg(["-f", "lavfi", "-i", "anullsrc=channel_layout=stereo:sample_rate=44100", "-t", f"{beat.duration:.3f}", str(out)], timeout=60)
400
+ padded.append(str(out))
401
+
402
+ track = root / "narration.wav"
403
+ listing = voice_dir / "concat.txt"
404
+ listing.write_text("".join(f"file '{p}'\n" for p in padded), encoding="utf-8")
405
+ _ffmpeg(["-f", "concat", "-safe", "0", "-i", str(listing), "-c", "pcm_s16le", str(track)], timeout=120)
406
+ return track
407
+
408
+
409
+ # ── 4. Render ────────────────────────────────────────────────────────────────
410
+
411
+ def _normalise_clip(source: str, out: Path, duration: float, aspect: str) -> None:
412
+ width, height = FRAME_SIZE[aspect]
413
+ _ffmpeg([
414
+ "-i", source, "-f", "lavfi", "-i", "anullsrc=channel_layout=stereo:sample_rate=44100",
415
+ "-vf", (f"scale={width}:{height}:force_original_aspect_ratio=increase,crop={width}:{height},"
416
+ f"fps={FPS},tpad=stop_mode=clone:stop_duration={duration:.3f},format=yuv420p"),
417
+ "-map", "0:v:0", "-map", "1:a:0", "-t", f"{duration:.3f}",
418
+ "-c:v", "libx264", "-c:a", "aac", "-shortest", str(out),
419
+ ])
420
+
421
+
422
+ def _compose_still(image: str, out: Path, aspect: str) -> None:
423
+ """Fit an image into the frame over a blurred fill of itself.
424
+
425
+ Rendered directly, a square logo in a 9:16 frame was cropped by the camera
426
+ move and a transparent PNG came out on solid black. Composing first keeps
427
+ the whole subject in frame, and the camera move then works on an image of
428
+ the frame's own shape.
429
+ """
430
+ width, height = FRAME_SIZE[aspect]
431
+ fg_width = int(width * 0.86)
432
+ _ffmpeg([
433
+ "-i", image,
434
+ "-filter_complex", (
435
+ f"[0:v]format=rgba,split=2[bg][fg];"
436
+ f"[bg]scale={width}:{height}:force_original_aspect_ratio=increase,crop={width}:{height},"
437
+ f"boxblur=40:2,colorchannelmixer=rr=0.55:gg=0.55:bb=0.55[bgb];"
438
+ f"color=c=0x1c1f1e:s={width}x{height}[base];"
439
+ f"[base][bgb]overlay=0:0[filled];"
440
+ f"[fg]scale='min({fg_width},iw*{height}*0.7/ih)':-2[fgs];"
441
+ f"[filled][fgs]overlay=(W-w)/2:(H-h)/2,format=yuv420p"
442
+ ),
443
+ "-frames:v", "1", "-q:v", "2", str(out),
444
+ ], timeout=120)
445
+
446
+
447
+ def _image_clip(image: str, out: Path, duration: float, aspect: str, effect: str) -> None:
448
+ from aria_code.video_editor import create_ken_burns_clip
449
+
450
+ composed = out.with_name(out.stem + "-frame.jpg")
451
+ _compose_still(image, composed, aspect)
452
+ result = create_ken_burns_clip(str(composed), duration=duration, effect=effect, aspect_ratio=aspect, output_path=str(out))
453
+ if not result.get("success"):
454
+ raise VideoWorkflowError(f"Could not animate {Path(image).name}: {result.get('error')}")
455
+
456
+
457
+ def render_video(
458
+ project_dir: str,
459
+ *,
460
+ confirm_generation: bool = False,
461
+ progress: Progress = _noop,
462
+ ) -> Dict[str, Any]:
463
+ """Stages 3–6 from project_dir/edit_plan.json. Returns the video path, checks and manifest."""
464
+ root = Path(project_dir).expanduser().resolve()
465
+ plan_file, assets_file = root / "edit_plan.json", root / "assets.json"
466
+ if not plan_file.exists() or not assets_file.exists():
467
+ raise VideoWorkflowError("Run plan_video first: edit_plan.json and assets.json are missing")
468
+ plan = EditPlan.from_dict(json.loads(plan_file.read_text(encoding="utf-8")))
469
+ assets = {item["id"]: Asset(**item) for item in json.loads(assets_file.read_text(encoding="utf-8"))}
470
+ plan.retime()
471
+
472
+ problems = validate_plan(plan, assets)
473
+ if problems:
474
+ raise VideoWorkflowError("The edit plan cannot be rendered:\n- " + "\n- ".join(problems))
475
+ generated = plan.generated_seconds()
476
+ if generated and not confirm_generation:
477
+ return {
478
+ "status": "needs_confirmation",
479
+ "generated_seconds": generated,
480
+ "generated_beats": [beat.id for beat in plan.beats if beat.source in PAID_SOURCES],
481
+ "message": f"这个分镜有 {generated} 秒需要用 Veo 生成,按秒计费。确认后再渲染。",
482
+ }
483
+
484
+ narration = narrate(plan, root, progress)
485
+ _write_json(plan_file, plan.to_dict()) # durations now fit the narration
486
+
487
+ clips_dir = root / "clips"
488
+ clips_dir.mkdir(parents=True, exist_ok=True)
489
+ clips: List[str] = []
490
+ models_used = {"planner": gm.DEFAULT_PLANNER_MODEL, "tts": gm.DEFAULT_TTS_MODEL}
491
+ for beat in plan.beats:
492
+ out = clips_dir / f"{beat.id}.mp4"
493
+ progress("render", f"正在渲染镜头 {beat.id}")
494
+ if beat.source in PAID_SOURCES:
495
+ raw = clips_dir / f"{beat.id}-veo.mp4"
496
+ if not raw.exists():
497
+ # animate: image-to-video, the user's image is the first frame.
498
+ image = assets[beat.asset_id].path if beat.source == "animate" else None
499
+ prompt = beat.generate_prompt
500
+ if image:
501
+ prompt = f"Animate this exact image. Keep the subject, faces, colours and composition unchanged. Motion: {prompt}"
502
+ gm.generate_shot(prompt, str(raw), duration_seconds=int(round(beat.duration)) or 4,
503
+ aspect_ratio=plan.aspect_ratio, image_path=image, confirmed=True)
504
+ models_used["video"] = gm.DEFAULT_VEO_MODEL
505
+ _normalise_clip(str(raw), out, beat.duration, plan.aspect_ratio)
506
+ else:
507
+ asset = assets[beat.asset_id]
508
+ if asset.kind == "video":
509
+ _normalise_clip(asset.path, out, beat.duration, plan.aspect_ratio)
510
+ else:
511
+ _image_clip(asset.path, out, beat.duration, plan.aspect_ratio, beat.effect)
512
+ clips.append(str(out))
513
+
514
+ silent = root / "work" / "picture.mp4"
515
+ silent.parent.mkdir(parents=True, exist_ok=True)
516
+ listing = root / "work" / "clips.txt"
517
+ listing.write_text("".join(f"file '{c}'\n" for c in clips), encoding="utf-8")
518
+ _ffmpeg(["-f", "concat", "-safe", "0", "-i", str(listing), "-c:v", "libx264", "-r", str(FPS), "-an", "-pix_fmt", "yuv420p", str(silent)])
519
+
520
+ srt = root / "subtitles.srt"
521
+ srt.write_text(to_srt(plan), encoding="utf-8")
522
+ final = root / f"{plan.project_id}.mp4"
523
+ progress("render", "正在合成字幕与旁白")
524
+ burned = ffmpeg_has_filter("subtitles")
525
+ if burned:
526
+ subtitle_path = str(srt.resolve()).replace("\\", "/").replace(":", "\\:").replace("'", "\\'")
527
+ width, _ = FRAME_SIZE[plan.aspect_ratio]
528
+ font_size = 11 if width >= 1920 else 9
529
+ _ffmpeg([
530
+ "-i", str(silent), "-i", str(narration),
531
+ "-vf", f"subtitles='{subtitle_path}':force_style='FontSize={font_size},Outline=1,Shadow=0,MarginV=36'",
532
+ "-map", "0:v:0", "-map", "1:a:0", "-c:v", "libx264", "-c:a", "aac", "-b:a", "192k", "-shortest", str(final),
533
+ ])
534
+ else:
535
+ # No libass: ship the captions as a selectable subtitle track and say so
536
+ # in the checks, rather than silently delivering a video without text.
537
+ _ffmpeg([
538
+ "-i", str(silent), "-i", str(narration), "-i", str(srt),
539
+ "-map", "0:v:0", "-map", "1:a:0", "-map", "2:s:0",
540
+ "-c:v", "copy", "-c:a", "aac", "-b:a", "192k", "-c:s", "mov_text", "-shortest", str(final),
541
+ ])
542
+
543
+ progress("verify", "正在检查成片")
544
+ checks = verify_video(str(final), plan)
545
+ checks.append({
546
+ "check": "captions_burned_in",
547
+ "ok": burned,
548
+ "detail": "字幕已烧录进画面" if burned else "当前 ffmpeg 不支持字幕烧录(缺 libass),字幕以可开关字幕轨嵌入;发抖音等平台前请安装带 libass 的 ffmpeg",
549
+ })
550
+ manifest = build_manifest(root, plan, assets, final, checks, models_used)
551
+ _write_json(root / "manifest.json", manifest)
552
+ return {
553
+ "status": "rendered" if all(check["ok"] for check in checks) else "rendered_with_issues",
554
+ "video_path": str(final),
555
+ "subtitles_path": str(srt),
556
+ "manifest_path": str(root / "manifest.json"),
557
+ "duration": plan.duration,
558
+ "checks": checks,
559
+ }
560
+
561
+
562
+ # ── 5. Verify ────────────────────────────────────────────────────────────────
563
+
564
+ def _ffprobe_streams(path: str) -> Dict[str, Any]:
565
+ result = subprocess.run(
566
+ ["ffprobe", "-v", "error", "-show_entries", "stream=codec_type,width,height:format=duration", "-of", "json", path],
567
+ capture_output=True, text=True, timeout=30,
568
+ )
569
+ return json.loads(result.stdout or "{}")
570
+
571
+
572
+ def _detect(path: str, filter_args: List[str], marker: str) -> int:
573
+ result = subprocess.run(["ffmpeg", "-hide_banner", "-i", path, *filter_args, "-f", "null", "-"],
574
+ capture_output=True, text=True, timeout=300)
575
+ return result.stderr.count(marker)
576
+
577
+
578
+ def verify_video(path: str, plan: EditPlan) -> List[Dict[str, Any]]:
579
+ """What a person would check before posting, measured on the file itself."""
580
+ info = _ffprobe_streams(path)
581
+ streams = info.get("streams", [])
582
+ video = next((s for s in streams if s.get("codec_type") == "video"), {})
583
+ has_audio = any(s.get("codec_type") == "audio" for s in streams)
584
+ duration = float((info.get("format") or {}).get("duration") or 0)
585
+ width, height = FRAME_SIZE[plan.aspect_ratio]
586
+ black = _detect(path, ["-vf", "blackdetect=d=0.5:pix_th=0.10", "-an"], "black_start")
587
+ silence = _detect(path, ["-af", "silencedetect=n=-45dB:d=1.5", "-vn"], "silence_start") if has_audio else 0
588
+ return [
589
+ {"check": "duration", "ok": abs(duration - plan.duration) <= 0.6, "detail": f"成片 {duration:.1f}s,分镜 {plan.duration:.1f}s"},
590
+ {"check": "frame_size", "ok": (video.get("width"), video.get("height")) == (width, height), "detail": f"{video.get('width')}x{video.get('height')},应为 {width}x{height}"},
591
+ {"check": "audio", "ok": has_audio, "detail": "有音轨" if has_audio else "没有音轨"},
592
+ {"check": "black_frames", "ok": black == 0, "detail": f"{black} 段超过 0.5 秒的黑帧"},
593
+ {"check": "long_silence", "ok": silence == 0, "detail": f"{silence} 段超过 1.5 秒的静音"},
594
+ {"check": "pending_beats", "ok": not any(b.pending for b in plan.beats),
595
+ "detail": "无待确认镜头" if not any(b.pending for b in plan.beats) else "待确认:" + "、".join(b.id for b in plan.beats if b.pending)},
596
+ ]
597
+
598
+
599
+ # ── 6. Manifest ──────────────────────────────────────────────────────────────
600
+
601
+ def build_manifest(root: Path, plan: EditPlan, assets: Dict[str, Asset], final: Path, checks: List[Dict[str, Any]], models: Dict[str, str]) -> Dict[str, Any]:
602
+ used = {beat.asset_id for beat in plan.beats if beat.source in ("asset", "animate")}
603
+ return {
604
+ "project_id": plan.project_id,
605
+ "title": plan.title,
606
+ "platform": plan.platform,
607
+ "aspect_ratio": plan.aspect_ratio,
608
+ "duration": plan.duration,
609
+ "created_at": datetime.now(timezone.utc).isoformat(timespec="seconds"),
610
+ "video": {"path": str(final), "sha256": _sha256(final)},
611
+ "assets": [
612
+ {"id": asset.id, "file": Path(asset.path).name, "sha256": _sha256(Path(asset.path)), "rights_note": asset.rights_note}
613
+ for asset in assets.values() if asset.id in used
614
+ ],
615
+ "generated_seconds": plan.generated_seconds(),
616
+ "models": models,
617
+ "checks": checks,
618
+ "pending": [beat.id for beat in plan.beats if beat.pending],
619
+ "notes": plan.notes,
620
+ }