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.
- aria_bots/__init__.py +7 -0
- aria_bots/channels/README.md +97 -0
- aria_bots/channels/__init__.py +1 -0
- aria_bots/channels/approvals.py +263 -0
- aria_bots/channels/conversation.py +280 -0
- aria_bots/channels/digest.py +186 -0
- aria_bots/channels/registry.py +95 -0
- aria_bots/engine.py +144 -0
- aria_bots/feishu_bot.py +1385 -0
- aria_bots/logistics/__init__.py +1 -0
- aria_bots/logistics/carriers.py +253 -0
- aria_bots/logistics/freight.py +154 -0
- aria_bots/logistics/inventory.py +242 -0
- aria_bots/logistics/mcp_server.py +156 -0
- aria_bots/logistics/records.py +119 -0
- aria_bots/logistics/tenancy.py +152 -0
- aria_bots/market.py +91 -0
- aria_bots/relay_client.py +326 -0
- aria_bots/telegram_bot.py +299 -0
- aria_code/__init__.py +111 -0
- aria_code/_bin/aria-native.exe +0 -0
- aria_code/_bin/aria-runtime.exe +0 -0
- aria_code/_version.py +20 -0
- aria_code/account_memory.py +296 -0
- aria_code/account_session.py +166 -0
- aria_code/adk_apps/__init__.py +1 -0
- aria_code/adk_apps/aria_code_review/__init__.py +1 -0
- aria_code/adk_apps/aria_code_review/agent.py +32 -0
- aria_code/adk_apps/aria_research/__init__.py +1 -0
- aria_code/adk_apps/aria_research/agent.py +31 -0
- aria_code/agents/__init__.py +49 -0
- aria_code/agents/base.py +329 -0
- aria_code/agents/code_review.py +200 -0
- aria_code/agents/deep/__init__.py +37 -0
- aria_code/agents/deep/calibration_loop.py +144 -0
- aria_code/agents/deep/critic.py +125 -0
- aria_code/agents/deep/deepen.py +193 -0
- aria_code/agents/deep/models.py +149 -0
- aria_code/agents/deep/pipeline.py +164 -0
- aria_code/agents/deep/quant_fusion.py +192 -0
- aria_code/agents/deep/themes.py +95 -0
- aria_code/agents/deep/tiers.py +106 -0
- aria_code/agents/engineering/__init__.py +14 -0
- aria_code/agents/engineering/coder.py +222 -0
- aria_code/agents/engineering/tester.py +145 -0
- aria_code/agents/financial/__init__.py +21 -0
- aria_code/agents/financial/cashflow_burn.py +119 -0
- aria_code/agents/financial/catalyst.py +279 -0
- aria_code/agents/financial/corporate_finance.py +175 -0
- aria_code/agents/financial/debate.py +224 -0
- aria_code/agents/financial/earnings.py +315 -0
- aria_code/agents/financial/fundamental.py +178 -0
- aria_code/agents/financial/macro.py +111 -0
- aria_code/agents/financial/news.py +231 -0
- aria_code/agents/financial/risk.py +146 -0
- aria_code/agents/financial/sector.py +279 -0
- aria_code/agents/financial/strategist.py +121 -0
- aria_code/agents/financial/stripe_revenue.py +127 -0
- aria_code/agents/financial/synthesis.py +274 -0
- aria_code/agents/financial/technical.py +277 -0
- aria_code/agents/orchestrator.py +44 -0
- aria_code/agents/portfolio_agent.py +392 -0
- aria_code/agents/realty/__init__.py +62 -0
- aria_code/agents/realty/asset_diagnosis.py +152 -0
- aria_code/agents/realty/business_match.py +166 -0
- aria_code/agents/realty/cashflow_verify.py +209 -0
- aria_code/agents/realty/contract_rules.py +210 -0
- aria_code/agents/realty/energy_anomaly.py +189 -0
- aria_code/agents/realty/exit_settlement.py +208 -0
- aria_code/agents/realty/fulfillment_risk.py +206 -0
- aria_code/agents/realty/ops_optimize.py +160 -0
- aria_code/agents/realty/revenue_share.py +215 -0
- aria_code/agents/registry.py +168 -0
- aria_code/agents/signal_scheme.py +111 -0
- aria_code/agents/sports/__init__.py +0 -0
- aria_code/agents/sports/football_agent.py +197 -0
- aria_code/agents/supervisor.py +113 -0
- aria_code/agents/team.py +500 -0
- aria_code/agents/video/__init__.py +16 -0
- aria_code/agents/video/director.py +376 -0
- aria_code/agents/video/qa_agent.py +143 -0
- aria_code/agents/video/script_doctor.py +112 -0
- aria_code/agents/warehouse/__init__.py +25 -0
- aria_code/agents/warehouse/contracts.py +26 -0
- aria_code/agents/warehouse/dashboard.py +155 -0
- aria_code/agents/warehouse/fulfillment_leadtime.py +95 -0
- aria_code/agents/warehouse/inbound_exceptions.py +25 -0
- aria_code/agents/warehouse/inventory_health.py +24 -0
- aria_code/agents/warehouse/local_run.py +71 -0
- aria_code/agents/warehouse/local_snapshot.py +132 -0
- aria_code/agents/warehouse/logistics_cost.py +84 -0
- aria_code/agents/warehouse/logistics_sync.py +21 -0
- aria_code/agents/warehouse/navigation.schema.json +110 -0
- aria_code/agents/warehouse/task_agent.py +172 -0
- aria_code/agents/warehouse/workflow.py +31 -0
- aria_code/aliyun_data_client.py +4 -0
- aria_code/apps/README.md +12 -0
- aria_code/apps/__init__.py +2 -0
- aria_code/apps/channels/README.md +9 -0
- aria_code/apps/channels/__init__.py +17 -0
- aria_code/apps/channels/approvals.py +11 -0
- aria_code/apps/channels/conversation.py +11 -0
- aria_code/apps/channels/digest.py +11 -0
- aria_code/apps/channels/intake.py +92 -0
- aria_code/apps/channels/registry.py +11 -0
- aria_code/apps/channels/tradingview.py +94 -0
- aria_code/apps/cli/README.md +13 -0
- aria_code/apps/cli/__init__.py +2 -0
- aria_code/apps/cli/_optional.py +120 -0
- aria_code/apps/cli/app_server.py +656 -0
- aria_code/apps/cli/auto_update.py +125 -0
- aria_code/apps/cli/bootstrap.py +206 -0
- aria_code/apps/cli/broker_render.py +206 -0
- aria_code/apps/cli/chat_turn.py +1327 -0
- aria_code/apps/cli/cloud_models.py +231 -0
- aria_code/apps/cli/codegen_paths.py +29 -0
- aria_code/apps/cli/commands/__init__.py +34 -0
- aria_code/apps/cli/commands/_business_render.py +78 -0
- aria_code/apps/cli/commands/_ui.py +71 -0
- aria_code/apps/cli/commands/analysis_cmds.py +378 -0
- aria_code/apps/cli/commands/ashare_prediction_cmds.py +327 -0
- aria_code/apps/cli/commands/auth_cmds.py +215 -0
- aria_code/apps/cli/commands/backtest_cmds.py +2518 -0
- aria_code/apps/cli/commands/broker_cmds.py +1236 -0
- aria_code/apps/cli/commands/business_workflow_cmds.py +379 -0
- aria_code/apps/cli/commands/canvas_cmds.py +77 -0
- aria_code/apps/cli/commands/catalog.py +189 -0
- aria_code/apps/cli/commands/core_cmds.py +2748 -0
- aria_code/apps/cli/commands/data_cmds.py +521 -0
- aria_code/apps/cli/commands/diagnostic_cmds.py +285 -0
- aria_code/apps/cli/commands/diagnostic_ops_cmds.py +813 -0
- aria_code/apps/cli/commands/engineering_cmds.py +399 -0
- aria_code/apps/cli/commands/file_cmds.py +312 -0
- aria_code/apps/cli/commands/finance_service_cmds.py +60 -0
- aria_code/apps/cli/commands/fx_commodity_cmds.py +123 -0
- aria_code/apps/cli/commands/market.py +544 -0
- aria_code/apps/cli/commands/market_cmds.py +1419 -0
- aria_code/apps/cli/commands/market_context.py +432 -0
- aria_code/apps/cli/commands/market_render.py +7 -0
- aria_code/apps/cli/commands/model_cmds.py +2374 -0
- aria_code/apps/cli/commands/ops_cmds.py +721 -0
- aria_code/apps/cli/commands/orchestrator_cmds.py +181 -0
- aria_code/apps/cli/commands/pdf_export_cmds.py +125 -0
- aria_code/apps/cli/commands/portfolio_cmds.py +1316 -0
- aria_code/apps/cli/commands/report.py +476 -0
- aria_code/apps/cli/commands/scaffold_templates.py +617 -0
- aria_code/apps/cli/commands/session_cmds.py +236 -0
- aria_code/apps/cli/commands/session_ux_cmds.py +359 -0
- aria_code/apps/cli/commands/team.py +723 -0
- aria_code/apps/cli/commands/team_render.py +8 -0
- aria_code/apps/cli/commands/ui_cmds.py +401 -0
- aria_code/apps/cli/commands/warehouse_cmds.py +297 -0
- aria_code/apps/cli/commands/workflow_cmds.py +629 -0
- aria_code/apps/cli/commands/workspace_cmds.py +1543 -0
- aria_code/apps/cli/config_paths.py +73 -0
- aria_code/apps/cli/config_store.py +158 -0
- aria_code/apps/cli/constants.py +88 -0
- aria_code/apps/cli/context.py +34 -0
- aria_code/apps/cli/conversation_policy.py +53 -0
- aria_code/apps/cli/deterministic.py +200 -0
- aria_code/apps/cli/direct.py +48 -0
- aria_code/apps/cli/earnings_view.py +172 -0
- aria_code/apps/cli/endpoint_migration.py +64 -0
- aria_code/apps/cli/exec_events.py +255 -0
- aria_code/apps/cli/file_references.py +155 -0
- aria_code/apps/cli/finance_service_catalog.py +111 -0
- aria_code/apps/cli/football_reports.py +231 -0
- aria_code/apps/cli/github_app_auth.py +135 -0
- aria_code/apps/cli/google_login.py +239 -0
- aria_code/apps/cli/handlers/__init__.py +11 -0
- aria_code/apps/cli/handlers/broker_handlers.py +122 -0
- aria_code/apps/cli/handlers/chart_handlers.py +1314 -0
- aria_code/apps/cli/handlers/market_handlers.py +2570 -0
- aria_code/apps/cli/handlers/realty_handlers.py +114 -0
- aria_code/apps/cli/handlers/sports_handlers.py +22 -0
- aria_code/apps/cli/handlers/strategy_advice.py +82 -0
- aria_code/apps/cli/headless.py +352 -0
- aria_code/apps/cli/helpers.py +326 -0
- aria_code/apps/cli/hooks.py +180 -0
- aria_code/apps/cli/i18n.py +310 -0
- aria_code/apps/cli/intent.py +136 -0
- aria_code/apps/cli/intent_router.py +305 -0
- aria_code/apps/cli/intent_shadow.py +72 -0
- aria_code/apps/cli/intent_signals.py +347 -0
- aria_code/apps/cli/lifecycle_hooks.py +48 -0
- aria_code/apps/cli/local_tool_schemas.py +550 -0
- aria_code/apps/cli/main.py +39 -0
- aria_code/apps/cli/market_hours.py +100 -0
- aria_code/apps/cli/market_metadata.py +135 -0
- aria_code/apps/cli/market_universe.py +487 -0
- aria_code/apps/cli/message_processing.py +310 -0
- aria_code/apps/cli/mixin_binding.py +32 -0
- aria_code/apps/cli/model_catalog.py +652 -0
- aria_code/apps/cli/model_probe.py +169 -0
- aria_code/apps/cli/model_retirement.py +65 -0
- aria_code/apps/cli/native_branding.py +12 -0
- aria_code/apps/cli/native_bridge.py +118 -0
- aria_code/apps/cli/native_entry.py +58 -0
- aria_code/apps/cli/pdf_report.py +977 -0
- aria_code/apps/cli/plan_mode.py +139 -0
- aria_code/apps/cli/plotly_html.py +15 -0
- aria_code/apps/cli/prediction_feedback.py +202 -0
- aria_code/apps/cli/preflight.py +502 -0
- aria_code/apps/cli/project_aria.py +60 -0
- aria_code/apps/cli/prompt_assembly.py +78 -0
- aria_code/apps/cli/prompts/__init__.py +0 -0
- aria_code/apps/cli/prompts/coding.py +71 -0
- aria_code/apps/cli/prompts/select.py +193 -0
- aria_code/apps/cli/prompts/system_prompts.py +562 -0
- aria_code/apps/cli/prompts/ui.py +436 -0
- aria_code/apps/cli/provider_endpoints.py +237 -0
- aria_code/apps/cli/provider_keys.py +73 -0
- aria_code/apps/cli/providers/__init__.py +1 -0
- aria_code/apps/cli/providers/base.py +775 -0
- aria_code/apps/cli/providers/chat_routing.py +139 -0
- aria_code/apps/cli/providers/collaboration.py +98 -0
- aria_code/apps/cli/providers/llm/__init__.py +1 -0
- aria_code/apps/cli/providers/llm/offline_responses.py +68 -0
- aria_code/apps/cli/providers/llm/ollama_stream.py +1574 -0
- aria_code/apps/cli/providers/llm/response_cache.py +60 -0
- aria_code/apps/cli/providers/llm/sse_stream.py +298 -0
- aria_code/apps/cli/providers/runtime_bridge.py +586 -0
- aria_code/apps/cli/providers/vertexai_stream.py +592 -0
- aria_code/apps/cli/release_download.py +159 -0
- aria_code/apps/cli/review_runner.py +111 -0
- aria_code/apps/cli/runtime_consumer.py +769 -0
- aria_code/apps/cli/sandbox.py +32 -0
- aria_code/apps/cli/session_export.py +120 -0
- aria_code/apps/cli/session_jsonl.py +208 -0
- aria_code/apps/cli/session_refresh.py +123 -0
- aria_code/apps/cli/session_store.py +125 -0
- aria_code/apps/cli/skills_catalog.py +273 -0
- aria_code/apps/cli/task_isolation.py +211 -0
- aria_code/apps/cli/terminal_chrome.py +412 -0
- aria_code/apps/cli/todo_tracker.py +190 -0
- aria_code/apps/cli/tool_executor.py +932 -0
- aria_code/apps/cli/tool_registry.py +34 -0
- aria_code/apps/cli/tool_scope.py +113 -0
- aria_code/apps/cli/tools/__init__.py +40 -0
- aria_code/apps/cli/tools/context.py +46 -0
- aria_code/apps/cli/tools/file_tools.py +181 -0
- aria_code/apps/cli/tools/market_tools.py +626 -0
- aria_code/apps/cli/tools/notebook_tools.py +111 -0
- aria_code/apps/cli/tools/system_tools.py +656 -0
- aria_code/apps/cli/tools/write_tools.py +961 -0
- aria_code/apps/cli/tradingview_bridge.py +529 -0
- aria_code/apps/cli/transactions.py +269 -0
- aria_code/apps/cli/turn_planning.py +137 -0
- aria_code/apps/cli/update_check.py +257 -0
- aria_code/apps/cli/updater.py +373 -0
- aria_code/apps/cli/utils/__init__.py +0 -0
- aria_code/apps/cli/utils/market_detect.py +1663 -0
- aria_code/apps/cli/web_login.py +136 -0
- aria_code/apps/cli/workflow_runner.py +127 -0
- aria_code/apps/cli/workspace_route.py +121 -0
- aria_code/apps/daemon/README.md +14 -0
- aria_code/apps/vscode/README.md +115 -0
- aria_code/apps/vscode/package.json +70 -0
- aria_code/aria_cli.py +5244 -0
- aria_code/aria_daemon.py +1372 -0
- aria_code/aria_feishu_bot.py +16 -0
- aria_code/aria_mcp_server.py +61 -0
- aria_code/aria_relay_client.py +4 -0
- aria_code/aria_telegram_bot.py +11 -0
- aria_code/ariarc.py +385 -0
- aria_code/artifacts.py +581 -0
- aria_code/backtest_engine.py +4 -0
- aria_code/backtest_report.py +4 -0
- aria_code/brokers/__init__.py +78 -0
- aria_code/brokers/automation.py +193 -0
- aria_code/brokers/base.py +231 -0
- aria_code/brokers/capabilities.py +264 -0
- aria_code/brokers/cn/__init__.py +10 -0
- aria_code/brokers/cn/easytrader_broker.py +193 -0
- aria_code/brokers/cn/futu_broker.py +194 -0
- aria_code/brokers/cn/longbridge_broker.py +190 -0
- aria_code/brokers/cn/tiger_broker.py +196 -0
- aria_code/brokers/cn/xtquant_broker.py +225 -0
- aria_code/brokers/config.py +414 -0
- aria_code/brokers/intl/__init__.py +5 -0
- aria_code/brokers/intl/alpaca_broker.py +183 -0
- aria_code/brokers/intl/ibkr_broker.py +215 -0
- aria_code/brokers/intl/trading212_broker.py +183 -0
- aria_code/brokers/intl/webull_broker.py +156 -0
- aria_code/brokers/paper_broker.py +266 -0
- aria_code/brokers/planning.py +296 -0
- aria_code/brokers/registry.py +213 -0
- aria_code/brokers/trading.py +267 -0
- aria_code/canva_client.py +4 -0
- aria_code/change_store.py +127 -0
- aria_code/clients/__init__.py +0 -0
- aria_code/clients/aliyun_data_client.py +660 -0
- aria_code/clients/aria_relay_client.py +16 -0
- aria_code/clients/canva_client.py +284 -0
- aria_code/clients/figma_client.py +100 -0
- aria_code/clients/football_data_client.py +1671 -0
- aria_code/clients/kling_video_client.py +182 -0
- aria_code/clients/market_data_client.py +1985 -0
- aria_code/clients/openai_image_client.py +242 -0
- aria_code/clients/runway_video_client.py +157 -0
- aria_code/clients/warehouse_erp_client.py +105 -0
- aria_code/cloud_memory.py +190 -0
- aria_code/command_safety.py +19 -0
- aria_code/computer_use_server.py +276 -0
- aria_code/computer_use_tools.py +4 -0
- aria_code/dashboard_generator.py +579 -0
- aria_code/data_analysis_tools.py +4 -0
- aria_code/data_cleaner.py +483 -0
- aria_code/data_service.py +513 -0
- aria_code/datasources/__init__.py +23 -0
- aria_code/datasources/base.py +166 -0
- aria_code/datasources/router.py +253 -0
- aria_code/datasources/sec_statements.py +217 -0
- aria_code/datasources/sources/__init__.py +15 -0
- aria_code/datasources/sources/akshare_source.py +269 -0
- aria_code/datasources/sources/alpha_vantage_source.py +203 -0
- aria_code/datasources/sources/edgar_source.py +219 -0
- aria_code/datasources/sources/finnhub_source.py +198 -0
- aria_code/datasources/sources/fred_source.py +223 -0
- aria_code/datasources/sources/tushare_source.py +143 -0
- aria_code/datasources/sources/web_scraper_source.py +278 -0
- aria_code/datasources/sources/world_bank_source.py +206 -0
- aria_code/datasources/sources/yfinance_source.py +152 -0
- aria_code/doctor.py +860 -0
- aria_code/domain/__init__.py +0 -0
- aria_code/domain/_finance_core/__init__.py +5 -0
- aria_code/domain/_finance_core/backtest_engine.py +325 -0
- aria_code/domain/_finance_core/performance_stats.py +101 -0
- aria_code/domain/backtest_engine.py +75 -0
- aria_code/domain/backtest_report.py +488 -0
- aria_code/domain/finance_formulas.py +389 -0
- aria_code/domain/performance_stats.py +6 -0
- aria_code/domain/portfolio_ledger.py +307 -0
- aria_code/domain/strategy_vault.py +421 -0
- aria_code/evals/__init__.py +37 -0
- aria_code/evals/behavior.py +113 -0
- aria_code/evals/gap.py +93 -0
- aria_code/evals/harness.py +949 -0
- aria_code/evals/review_probe.py +132 -0
- aria_code/evals/runner.py +425 -0
- aria_code/evals/scorecard.py +322 -0
- aria_code/evals/summary.py +125 -0
- aria_code/external_agent_runner.py +115 -0
- aria_code/figma_client.py +4 -0
- aria_code/file_analysis_tools.py +4 -0
- aria_code/finance_formulas.py +4 -0
- aria_code/football_data_client.py +4 -0
- aria_code/image_gen_tools.py +4 -0
- aria_code/intent_classifier.py +459 -0
- aria_code/kling_video_client.py +4 -0
- aria_code/licensing.py +151 -0
- aria_code/local_finance_tools.py +4 -0
- aria_code/local_image_provider.py +4 -0
- aria_code/local_llm_provider.py +4 -0
- aria_code/macro_tools.py +4 -0
- aria_code/markdown_pdf.py +289 -0
- aria_code/market_data_client.py +4 -0
- aria_code/mcp_client.py +744 -0
- aria_code/memory_manager.py +273 -0
- aria_code/model_capability.py +512 -0
- aria_code/notification_tools.py +4 -0
- aria_code/openai_image_client.py +4 -0
- aria_code/packages/__init__.py +26 -0
- aria_code/packages/adk_bridge/__init__.py +11 -0
- aria_code/packages/adk_bridge/code_review_tools.py +49 -0
- aria_code/packages/adk_bridge/market_tools.py +125 -0
- aria_code/packages/aria_agents/__init__.py +5 -0
- aria_code/packages/aria_agents/manifest.py +78 -0
- aria_code/packages/aria_core/__init__.py +34 -0
- aria_code/packages/aria_core/architecture.py +269 -0
- aria_code/packages/aria_core/export.py +147 -0
- aria_code/packages/aria_core/manifest.py +65 -0
- aria_code/packages/aria_core/paths.py +57 -0
- aria_code/packages/aria_core/secure_file.py +68 -0
- aria_code/packages/aria_infra/__init__.py +15 -0
- aria_code/packages/aria_infra/arthera.py +58 -0
- aria_code/packages/aria_infra/doctor.py +246 -0
- aria_code/packages/aria_infra/product.py +40 -0
- aria_code/packages/aria_mcp/__init__.py +25 -0
- aria_code/packages/aria_mcp/bridge.py +232 -0
- aria_code/packages/aria_mcp/circuit.py +82 -0
- aria_code/packages/aria_mcp/config.py +162 -0
- aria_code/packages/aria_mcp/installer.py +95 -0
- aria_code/packages/aria_mcp/server.py +1705 -0
- aria_code/packages/aria_mcp/tools.py +81 -0
- aria_code/packages/aria_sdk/__init__.py +19 -0
- aria_code/packages/aria_sdk/client.py +393 -0
- aria_code/packages/aria_sdk/providers.py +70 -0
- aria_code/packages/aria_sdk/streaming.py +75 -0
- aria_code/packages/aria_sdk/types.py +86 -0
- aria_code/packages/aria_services/__init__.py +92 -0
- aria_code/packages/aria_services/cache.py +60 -0
- aria_code/packages/aria_services/context.py +356 -0
- aria_code/packages/aria_services/context_checkpoints.py +157 -0
- aria_code/packages/aria_services/data.py +11 -0
- aria_code/packages/aria_services/provider_health.py +304 -0
- aria_code/packages/aria_services/references.py +247 -0
- aria_code/packages/aria_services/registry.py +249 -0
- aria_code/packages/aria_services/research_protocol.py +137 -0
- aria_code/packages/aria_services/research_quality.py +186 -0
- aria_code/packages/aria_services/settings.py +160 -0
- aria_code/packages/aria_services/usage.py +151 -0
- aria_code/packages/aria_skills/__init__.py +46 -0
- aria_code/packages/aria_skills/catalog.py +149 -0
- aria_code/packages/aria_skills/loader.py +455 -0
- aria_code/packages/aria_skills/registry.py +84 -0
- aria_code/packages/aria_tools/__init__.py +5 -0
- aria_code/packages/aria_tools/financial/__init__.py +5 -0
- aria_code/packages/aria_tools/financial/_paths.py +92 -0
- aria_code/packages/aria_tools/registry.py +129 -0
- aria_code/packages/quant_engine/__init__.py +33 -0
- aria_code/packages/quant_engine/_namespace.py +24 -0
- aria_code/packages/quant_engine/backtest/__init__.py +10 -0
- aria_code/packages/quant_engine/backtest/core.py +53 -0
- aria_code/packages/quant_engine/backtest/engine.py +102 -0
- aria_code/packages/quant_engine/portfolio/__init__.py +9 -0
- aria_code/packages/quant_engine/portfolio/optimizer.py +85 -0
- aria_code/packages/quant_engine/sports/__init__.py +76 -0
- aria_code/packages/quant_engine/sports/calibrator.py +354 -0
- aria_code/packages/quant_engine/sports/dixon_coles.py +234 -0
- aria_code/packages/quant_engine/sports/elo.py +300 -0
- aria_code/packages/quant_engine/sports/form.py +188 -0
- aria_code/packages/quant_engine/sports/h2h.py +195 -0
- aria_code/packages/quant_engine/sports/predictor.py +312 -0
- aria_code/packages/quant_engine/sports/tracker.py +665 -0
- aria_code/packages/quant_engine/stochastic/__init__.py +31 -0
- aria_code/packages/quant_engine/stochastic/gbm_enhanced.py +195 -0
- aria_code/packages/quant_engine/stochastic/ito_calculus.py +477 -0
- aria_code/packages/quant_engine/stochastic/kelly_criterion.py +181 -0
- aria_code/packages/quant_engine/stochastic/monte_carlo_advanced.py +95 -0
- aria_code/packages/quant_engine/stochastic/options_pricing.py +576 -0
- aria_code/packages/quant_engine/stochastic/stochastic_processes.py +90 -0
- aria_code/packs/__init__.py +85 -0
- aria_code/packs/base.py +168 -0
- aria_code/packs/finance/__init__.py +250 -0
- aria_code/packs/healthcare/__init__.py +40 -0
- aria_code/packs/logistics/__init__.py +171 -0
- aria_code/packs/payments/__init__.py +139 -0
- aria_code/packs/realty/__init__.py +190 -0
- aria_code/packs/registry.py +202 -0
- aria_code/packs/rules.py +90 -0
- aria_code/plan_utils.py +194 -0
- aria_code/plugin_loader.py +328 -0
- aria_code/portfolio_ledger.py +4 -0
- aria_code/preview_server.py +449 -0
- aria_code/privacy/__init__.py +5 -0
- aria_code/privacy/feedback.py +123 -0
- aria_code/project_review.py +278 -0
- aria_code/project_review_api.py +235 -0
- aria_code/project_review_server.py +18 -0
- aria_code/project_tools.py +4 -0
- aria_code/providers/__init__.py +30 -0
- aria_code/providers/google_video_provider.py +256 -0
- aria_code/providers/llm/__init__.py +19 -0
- aria_code/providers/llm/anthropic.py +184 -0
- aria_code/providers/llm/arthera_gateway.py +90 -0
- aria_code/providers/llm/autoconfig.py +213 -0
- aria_code/providers/llm/base.py +139 -0
- aria_code/providers/llm/custom.py +158 -0
- aria_code/providers/llm/ollama.py +137 -0
- aria_code/providers/llm/openai_compat.py +350 -0
- aria_code/providers/llm/registry.py +457 -0
- aria_code/providers/local_image_provider.py +202 -0
- aria_code/providers/local_llm_provider.py +549 -0
- aria_code/providers/ollama_models.py +79 -0
- aria_code/providers/openai_params.py +32 -0
- aria_code/providers/tool_messages.py +100 -0
- aria_code/realty_data_tools.py +4 -0
- aria_code/report_exporters.py +150 -0
- aria_code/report_generator.py +1764 -0
- aria_code/review_service.py +497 -0
- aria_code/runtime/__init__.py +176 -0
- aria_code/runtime/acceptance.py +632 -0
- aria_code/runtime/agent_loop.py +1851 -0
- aria_code/runtime/agent_profiles.py +75 -0
- aria_code/runtime/approval.py +71 -0
- aria_code/runtime/baseline.py +219 -0
- aria_code/runtime/budget.py +232 -0
- aria_code/runtime/checkpoints.py +481 -0
- aria_code/runtime/consult.py +65 -0
- aria_code/runtime/contract.py +282 -0
- aria_code/runtime/delivery.py +432 -0
- aria_code/runtime/events.py +113 -0
- aria_code/runtime/gateway.py +167 -0
- aria_code/runtime/lsp.py +484 -0
- aria_code/runtime/native_index.py +153 -0
- aria_code/runtime/orchestrator.py +55 -0
- aria_code/runtime/processes.py +343 -0
- aria_code/runtime/project_graph.py +614 -0
- aria_code/runtime/repo_map.py +712 -0
- aria_code/runtime/research_bundle.py +105 -0
- aria_code/runtime/review.py +269 -0
- aria_code/runtime/run_state.py +127 -0
- aria_code/runtime/run_store.py +522 -0
- aria_code/runtime/scoped_runs.py +113 -0
- aria_code/runtime/self_healing.py +422 -0
- aria_code/runtime/semantic_diff.py +171 -0
- aria_code/runtime/subagent.py +721 -0
- aria_code/runtime/symbol_edit.py +117 -0
- aria_code/runtime/task_brief.py +46 -0
- aria_code/runtime/task_context.py +145 -0
- aria_code/runtime/task_ledger.py +108 -0
- aria_code/runtime/task_orchestrator.py +90 -0
- aria_code/runtime/task_outcome.py +33 -0
- aria_code/runtime/task_verification.py +266 -0
- aria_code/runtime/task_worktree.py +448 -0
- aria_code/runtime/tool_executor.py +329 -0
- aria_code/runtime/tool_policy.py +107 -0
- aria_code/runtime/transactions.py +216 -0
- aria_code/runtime/windows_job.py +115 -0
- aria_code/runtime/workflows.py +222 -0
- aria_code/runtime/worktrees.py +215 -0
- aria_code/runway_video_client.py +4 -0
- aria_code/safety/__init__.py +27 -0
- aria_code/safety/client_isolation.py +178 -0
- aria_code/safety/permissions.py +339 -0
- aria_code/safety/risk.py +699 -0
- aria_code/safety/sandbox.py +148 -0
- aria_code/safety/service.py +102 -0
- aria_code/service_endpoints.py +14 -0
- aria_code/setup_wizard.py +684 -0
- aria_code/spreadsheet_tools.py +4 -0
- aria_code/strategy_vault.py +4 -0
- aria_code/tools/__init__.py +0 -0
- aria_code/tools/artifact_tools.py +252 -0
- aria_code/tools/broker_tools.py +92 -0
- aria_code/tools/build_quant_engine.py +97 -0
- aria_code/tools/code_audit_tools.py +192 -0
- aria_code/tools/computer_use_tools.py +509 -0
- aria_code/tools/data_analysis_tools.py +831 -0
- aria_code/tools/enterprise_finance_tools.py +202 -0
- aria_code/tools/extended_tools.py +240 -0
- aria_code/tools/file_analysis_tools.py +880 -0
- aria_code/tools/image_gen_tools.py +114 -0
- aria_code/tools/local_finance_tools.py +3488 -0
- aria_code/tools/logistics_carriers.py +11 -0
- aria_code/tools/logistics_inventory.py +11 -0
- aria_code/tools/logistics_io.py +11 -0
- aria_code/tools/logistics_tenancy.py +11 -0
- aria_code/tools/logistics_tools.py +11 -0
- aria_code/tools/macro_tools.py +412 -0
- aria_code/tools/notification_tools.py +249 -0
- aria_code/tools/project_tools.py +525 -0
- aria_code/tools/realty_data_tools.py +662 -0
- aria_code/tools/spreadsheet_tools.py +399 -0
- aria_code/tools/stripe_tools.py +115 -0
- aria_code/tools/video_tools.py +500 -0
- aria_code/training/__init__.py +1 -0
- aria_code/training/compare.py +117 -0
- aria_code/training/prelude_commands.py +111 -0
- aria_code/training/prelude_eval.py +390 -0
- aria_code/training/prelude_real.py +183 -0
- aria_code/training/prelude_train.py +248 -0
- aria_code/training/sft_dataset.py +230 -0
- aria_code/training/trajectory_dataset.py +263 -0
- aria_code/ui/__init__.py +105 -0
- aria_code/ui/banner.py +404 -0
- aria_code/ui/completer.py +571 -0
- aria_code/ui/console.py +285 -0
- aria_code/ui/input_box.py +505 -0
- aria_code/ui/mentions.py +206 -0
- aria_code/ui/picker.py +248 -0
- aria_code/ui/render/__init__.py +11 -0
- aria_code/ui/render/actions.py +297 -0
- aria_code/ui/render/finance.py +1732 -0
- aria_code/ui/render/market.py +253 -0
- aria_code/ui/render/output.py +954 -0
- aria_code/ui/render/responsive.py +75 -0
- aria_code/ui/render/team.py +367 -0
- aria_code/ui/robot.py +239 -0
- aria_code/ui/startup_dashboard.py +110 -0
- aria_code/video_analysis.py +147 -0
- aria_code/video_editor.py +518 -0
- aria_code/video_workflow/__init__.py +8 -0
- aria_code/video_workflow/__main__.py +61 -0
- aria_code/video_workflow/engines/__init__.py +18 -0
- aria_code/video_workflow/engines/base.py +69 -0
- aria_code/video_workflow/engines/custom_model.py +204 -0
- aria_code/video_workflow/engines/google_veo.py +94 -0
- aria_code/video_workflow/engines/local_engine.py +71 -0
- aria_code/video_workflow/engines/registry.py +98 -0
- aria_code/video_workflow/google_models.py +278 -0
- aria_code/video_workflow/music/__init__.py +5 -0
- aria_code/video_workflow/music/foley_service.py +132 -0
- aria_code/video_workflow/music/service.py +212 -0
- aria_code/video_workflow/pipeline.py +620 -0
- aria_code/video_workflow/plan.py +202 -0
- aria_code/video_workflow/session.py +297 -0
- aria_code/workspace/__init__.py +6 -0
- aria_code/workspace/files.py +219 -0
- aria_code/workspace/verify.py +167 -0
- aria_code/workspace_changes.py +199 -0
- aria_code/workspace_service.py +196 -0
- aria_code/workspace_tools.py +150 -0
- aria_code-0.167.0.dist-info/METADATA +330 -0
- aria_code-0.167.0.dist-info/RECORD +601 -0
- aria_code-0.167.0.dist-info/WHEEL +5 -0
- aria_code-0.167.0.dist-info/entry_points.txt +5 -0
- aria_code-0.167.0.dist-info/licenses/LICENSE +201 -0
- aria_code-0.167.0.dist-info/licenses/NOTICE +49 -0
- aria_code-0.167.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
"""Google Cloud (Vertex AI) models used by the video workflow.
|
|
2
|
+
|
|
3
|
+
Every model call in the workflow goes through here, so the pipeline has one
|
|
4
|
+
place that knows endpoints, credentials and response shapes:
|
|
5
|
+
|
|
6
|
+
- Gemini (native :generateContent) — reads images and scripts, writes the
|
|
7
|
+
edit plan with a JSON response schema.
|
|
8
|
+
- Gemini TTS (gemini-2.5-flash-tts) — narration. Returns bare 24 kHz / 16-bit
|
|
9
|
+
mono PCM, wrapped here in a WAV header. The same request shape is already in
|
|
10
|
+
production in Arthera's voice routes.
|
|
11
|
+
- Veo — generated shots. Paid per second, so it is only reachable through
|
|
12
|
+
`generate_shot(..., confirmed=True)`; see pipeline.render_video.
|
|
13
|
+
|
|
14
|
+
Credentials: Application Default Credentials plus VERTEX_PROJECT (or
|
|
15
|
+
GOOGLE_CLOUD_PROJECT). No API keys are read here.
|
|
16
|
+
"""
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import base64
|
|
20
|
+
import json
|
|
21
|
+
import os
|
|
22
|
+
import shutil
|
|
23
|
+
import struct
|
|
24
|
+
import subprocess
|
|
25
|
+
import tempfile
|
|
26
|
+
import time
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
from typing import Any, Dict, List, Optional
|
|
29
|
+
|
|
30
|
+
DEFAULT_PLANNER_MODEL = "gemini-3.8-flash"
|
|
31
|
+
DEFAULT_TTS_MODEL = "gemini-2.5-flash-tts"
|
|
32
|
+
DEFAULT_TTS_VOICE = "Aoede"
|
|
33
|
+
# Listed in the Vertex publisher catalog (checked 2026-09-14). Generation itself
|
|
34
|
+
# has not been exercised from this workflow yet: it costs money per second.
|
|
35
|
+
# veo-3.0-* is not reachable from our production project (404, checked 2026-09-15); Veo 3.1 is.
|
|
36
|
+
DEFAULT_VEO_MODEL = "veo-3.1-fast-generate-001"
|
|
37
|
+
|
|
38
|
+
MAX_INLINE_IMAGE_BYTES = 4 * 1024 * 1024
|
|
39
|
+
IMAGE_SUFFIX_MIME = {".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".png": "image/png", ".webp": "image/webp"}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class GoogleModelError(RuntimeError):
|
|
43
|
+
"""A Google Cloud model call failed; the message says which and why."""
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _adc_config_project() -> str:
|
|
47
|
+
"""The project named on disk by `gcloud auth application-default login`.
|
|
48
|
+
|
|
49
|
+
`google.auth.default()` finds the project for a user credential by running
|
|
50
|
+
`gcloud`, so it returns nothing whenever gcloud is not on PATH — which is
|
|
51
|
+
every run launched from a desktop app rather than a shell. Both files it
|
|
52
|
+
would consult are plain text, so they are read directly instead.
|
|
53
|
+
"""
|
|
54
|
+
config = Path(os.getenv("CLOUDSDK_CONFIG") or Path.home() / ".config" / "gcloud")
|
|
55
|
+
try:
|
|
56
|
+
adc = json.loads((config / "application_default_credentials.json").read_text())
|
|
57
|
+
if adc.get("quota_project_id"):
|
|
58
|
+
return str(adc["quota_project_id"])
|
|
59
|
+
except Exception: # noqa: BLE001
|
|
60
|
+
pass
|
|
61
|
+
try:
|
|
62
|
+
for line in (config / "configurations" / "config_default").read_text().splitlines():
|
|
63
|
+
name, _, value = line.partition("=")
|
|
64
|
+
if name.strip() == "project" and value.strip():
|
|
65
|
+
return value.strip()
|
|
66
|
+
except Exception: # noqa: BLE001
|
|
67
|
+
pass
|
|
68
|
+
return ""
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def vertex_project() -> str:
|
|
72
|
+
"""The Google Cloud project: VERTEX_PROJECT, GOOGLE_CLOUD_PROJECT, then what ADC names.
|
|
73
|
+
|
|
74
|
+
A desktop app launches this workflow without the shell's environment, so
|
|
75
|
+
the project configured with `gcloud` is the useful last resort.
|
|
76
|
+
"""
|
|
77
|
+
configured = os.getenv("VERTEX_PROJECT") or os.getenv("GOOGLE_CLOUD_PROJECT", "")
|
|
78
|
+
if configured:
|
|
79
|
+
return configured
|
|
80
|
+
try:
|
|
81
|
+
import google.auth
|
|
82
|
+
|
|
83
|
+
_, project = google.auth.default(scopes=["https://www.googleapis.com/auth/cloud-platform"])
|
|
84
|
+
if project:
|
|
85
|
+
return str(project)
|
|
86
|
+
except Exception: # noqa: BLE001
|
|
87
|
+
pass
|
|
88
|
+
return _adc_config_project()
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _access_token() -> str:
|
|
92
|
+
try:
|
|
93
|
+
import google.auth
|
|
94
|
+
from google.auth.transport.requests import Request as GoogleAuthRequest
|
|
95
|
+
|
|
96
|
+
creds, _ = google.auth.default(scopes=["https://www.googleapis.com/auth/cloud-platform"])
|
|
97
|
+
creds.refresh(GoogleAuthRequest())
|
|
98
|
+
except Exception as exc: # noqa: BLE001
|
|
99
|
+
raise GoogleModelError(f"Google Cloud credentials unavailable (run `gcloud auth application-default login`): {exc}") from exc
|
|
100
|
+
if not creds.token:
|
|
101
|
+
raise GoogleModelError("Google Cloud credentials returned no access token")
|
|
102
|
+
return creds.token
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# Vertex's shared endpoints shed load: 429 and 503 mean "busy now", not
|
|
106
|
+
# "wrong request", and a plan that took a minute to set up should not be
|
|
107
|
+
# thrown away because of one of them.
|
|
108
|
+
_BUSY_STATUSES = frozenset({429, 503})
|
|
109
|
+
_BUSY_ATTEMPTS = 4
|
|
110
|
+
_BUSY_BACKOFF = (2.0, 6.0, 15.0)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _explain(status: int, model: str, body: str) -> str:
|
|
114
|
+
"""Say what a refusal means, when Vertex says it in its own vocabulary.
|
|
115
|
+
|
|
116
|
+
A project whose billing is suspended is refused with "Lightning dunning
|
|
117
|
+
decision is deny for project: projects/<number>" — internal wording that
|
|
118
|
+
reads like a fault in the request, and sends you looking through code and
|
|
119
|
+
credentials for something that is not there. It is the billing account.
|
|
120
|
+
"""
|
|
121
|
+
if status == 403 and "dunning" in body.lower():
|
|
122
|
+
return (
|
|
123
|
+
"Google Cloud is refusing this project because of its billing account "
|
|
124
|
+
"(check for an unpaid balance or a suspended billing account in the "
|
|
125
|
+
"Cloud console). Nothing is wrong with the request or the credentials."
|
|
126
|
+
)
|
|
127
|
+
if status == 403:
|
|
128
|
+
return f"Google Cloud refused the call to {model}: {body[:300]}"
|
|
129
|
+
if status == 404:
|
|
130
|
+
return f"{model} is not available to this project (HTTP 404)"
|
|
131
|
+
return f"HTTP {status} from {model}: {body[:300]}"
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _post(url: str, body: Dict[str, Any], timeout: float) -> Dict[str, Any]:
|
|
135
|
+
import requests
|
|
136
|
+
|
|
137
|
+
project = vertex_project()
|
|
138
|
+
if not project:
|
|
139
|
+
raise GoogleModelError(
|
|
140
|
+
"No Google Cloud project: run `gcloud auth application-default login` "
|
|
141
|
+
"or set VERTEX_PROJECT"
|
|
142
|
+
)
|
|
143
|
+
model = url.rsplit("/", 1)[-1]
|
|
144
|
+
for attempt in range(_BUSY_ATTEMPTS):
|
|
145
|
+
response = requests.post(
|
|
146
|
+
url,
|
|
147
|
+
headers={"Authorization": f"Bearer {_access_token()}", "Content-Type": "application/json", "x-goog-user-project": project},
|
|
148
|
+
json=body,
|
|
149
|
+
timeout=timeout,
|
|
150
|
+
)
|
|
151
|
+
if response.status_code == 200:
|
|
152
|
+
return response.json()
|
|
153
|
+
if response.status_code in _BUSY_STATUSES and attempt < _BUSY_ATTEMPTS - 1:
|
|
154
|
+
time.sleep(_BUSY_BACKOFF[attempt])
|
|
155
|
+
continue
|
|
156
|
+
if response.status_code in _BUSY_STATUSES:
|
|
157
|
+
raise GoogleModelError(f"{model} is busy right now (HTTP {response.status_code}); try again in a minute")
|
|
158
|
+
raise GoogleModelError(_explain(response.status_code, model, response.text))
|
|
159
|
+
raise GoogleModelError(f"{model} is busy right now; try again in a minute")
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _generate_content_url(model: str) -> str:
|
|
163
|
+
return (
|
|
164
|
+
f"https://aiplatform.googleapis.com/v1/projects/{vertex_project()}"
|
|
165
|
+
f"/locations/global/publishers/google/models/{model}:generateContent"
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def image_part(path: str) -> Dict[str, Any]:
|
|
170
|
+
"""An inline image part. Large images are downscaled with ffmpeg first."""
|
|
171
|
+
source = Path(path)
|
|
172
|
+
mime = IMAGE_SUFFIX_MIME.get(source.suffix.lower())
|
|
173
|
+
data = source.read_bytes()
|
|
174
|
+
if mime is None or len(data) > MAX_INLINE_IMAGE_BYTES:
|
|
175
|
+
if shutil.which("ffmpeg") is None:
|
|
176
|
+
raise GoogleModelError(f"{source.name}: unsupported or too large, and ffmpeg is not available to convert it")
|
|
177
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
178
|
+
out = Path(tmp) / "image.jpg"
|
|
179
|
+
result = subprocess.run(
|
|
180
|
+
["ffmpeg", "-y", "-loglevel", "error", "-i", str(source), "-vf", "scale='min(1600,iw)':-2", "-q:v", "3", str(out)],
|
|
181
|
+
capture_output=True, text=True, timeout=60,
|
|
182
|
+
)
|
|
183
|
+
if result.returncode != 0 or not out.exists():
|
|
184
|
+
raise GoogleModelError(f"{source.name}: could not convert image: {result.stderr[-200:]}")
|
|
185
|
+
data, mime = out.read_bytes(), "image/jpeg"
|
|
186
|
+
return {"inlineData": {"mimeType": mime, "data": base64.b64encode(data).decode("ascii")}}
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def generate_json(
|
|
190
|
+
parts: List[Dict[str, Any]],
|
|
191
|
+
response_schema: Dict[str, Any],
|
|
192
|
+
*,
|
|
193
|
+
system: str = "",
|
|
194
|
+
model: Optional[str] = None,
|
|
195
|
+
timeout: float = 120.0,
|
|
196
|
+
) -> Dict[str, Any]:
|
|
197
|
+
"""Ask Gemini for a JSON object matching `response_schema` (OpenAPI subset)."""
|
|
198
|
+
body: Dict[str, Any] = {
|
|
199
|
+
"contents": [{"role": "user", "parts": parts}],
|
|
200
|
+
"generationConfig": {
|
|
201
|
+
"responseMimeType": "application/json",
|
|
202
|
+
"responseSchema": response_schema,
|
|
203
|
+
"temperature": 0.4,
|
|
204
|
+
},
|
|
205
|
+
}
|
|
206
|
+
if system:
|
|
207
|
+
body["systemInstruction"] = {"parts": [{"text": system}]}
|
|
208
|
+
model_id = model or os.getenv("ARIA_VIDEO_PLANNER_MODEL", DEFAULT_PLANNER_MODEL)
|
|
209
|
+
payload = _post(_generate_content_url(model_id), body, timeout)
|
|
210
|
+
try:
|
|
211
|
+
text = "".join(part.get("text", "") for part in payload["candidates"][0]["content"]["parts"])
|
|
212
|
+
return json.loads(text)
|
|
213
|
+
except (KeyError, IndexError, json.JSONDecodeError) as exc:
|
|
214
|
+
raise GoogleModelError(f"{model_id} returned no usable JSON: {str(payload)[:300]}") from exc
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def _wav_from_pcm(pcm: bytes, sample_rate: int = 24000, channels: int = 1, bits: int = 16) -> bytes:
|
|
218
|
+
byte_rate = sample_rate * channels * bits // 8
|
|
219
|
+
block_align = channels * bits // 8
|
|
220
|
+
header = (
|
|
221
|
+
b"RIFF" + struct.pack("<I", 36 + len(pcm)) + b"WAVE"
|
|
222
|
+
+ b"fmt " + struct.pack("<IHHIIHH", 16, 1, channels, sample_rate, byte_rate, block_align, bits)
|
|
223
|
+
+ b"data" + struct.pack("<I", len(pcm))
|
|
224
|
+
)
|
|
225
|
+
return header + pcm
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def synthesize_speech(text: str, output_path: str, *, voice: Optional[str] = None, model: Optional[str] = None) -> str:
|
|
229
|
+
"""Narrate `text` with Gemini TTS into a WAV file; returns the path."""
|
|
230
|
+
text = (text or "").strip()
|
|
231
|
+
if not text:
|
|
232
|
+
raise GoogleModelError("Nothing to narrate")
|
|
233
|
+
model_id = model or os.getenv("ARIA_VIDEO_TTS_MODEL", DEFAULT_TTS_MODEL)
|
|
234
|
+
body = {
|
|
235
|
+
"contents": [{"role": "user", "parts": [{"text": text}]}],
|
|
236
|
+
"generationConfig": {
|
|
237
|
+
"responseModalities": ["AUDIO"],
|
|
238
|
+
"speechConfig": {"voiceConfig": {"prebuiltVoiceConfig": {"voiceName": voice or DEFAULT_TTS_VOICE}}},
|
|
239
|
+
},
|
|
240
|
+
}
|
|
241
|
+
payload = _post(_generate_content_url(model_id), body, timeout=90.0)
|
|
242
|
+
try:
|
|
243
|
+
inline = payload["candidates"][0]["content"]["parts"][0]["inlineData"]
|
|
244
|
+
pcm = base64.b64decode(inline["data"])
|
|
245
|
+
except (KeyError, IndexError) as exc:
|
|
246
|
+
raise GoogleModelError(f"{model_id} returned no audio: {str(payload)[:300]}") from exc
|
|
247
|
+
out = Path(output_path)
|
|
248
|
+
out.parent.mkdir(parents=True, exist_ok=True)
|
|
249
|
+
out.write_bytes(_wav_from_pcm(pcm))
|
|
250
|
+
return str(out)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def generate_shot(
|
|
254
|
+
prompt: str,
|
|
255
|
+
output_path: str,
|
|
256
|
+
*,
|
|
257
|
+
duration_seconds: int,
|
|
258
|
+
aspect_ratio: str,
|
|
259
|
+
image_path: Optional[str] = None,
|
|
260
|
+
model: Optional[str] = None,
|
|
261
|
+
confirmed: bool = False,
|
|
262
|
+
) -> str:
|
|
263
|
+
"""Generate one shot with Veo. Refuses unless `confirmed` — it is billed per second."""
|
|
264
|
+
if not confirmed:
|
|
265
|
+
raise GoogleModelError("Veo generation is paid per second; it needs explicit confirmation")
|
|
266
|
+
from aria_code.providers.google_video_provider import generate_video_google
|
|
267
|
+
|
|
268
|
+
model_id = model or os.getenv("ARIA_VIDEO_VEO_MODEL", DEFAULT_VEO_MODEL)
|
|
269
|
+
result = generate_video_google(
|
|
270
|
+
prompt, image_path=image_path, model=model_id, aspect_ratio=aspect_ratio, duration_seconds=duration_seconds,
|
|
271
|
+
max_wait_seconds=600.0,
|
|
272
|
+
)
|
|
273
|
+
if not result.get("success") or not result.get("path"):
|
|
274
|
+
raise GoogleModelError(f"Veo ({model_id}) failed: {result.get('error') or result}")
|
|
275
|
+
out = Path(output_path)
|
|
276
|
+
out.parent.mkdir(parents=True, exist_ok=True)
|
|
277
|
+
shutil.move(result["path"], out)
|
|
278
|
+
return str(out)
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""foley_service.py — 影视级拟音声效 (Foley SFX) 与转场音效服务。
|
|
2
|
+
|
|
3
|
+
为视频自动识别并在精确毫秒时间码插入音效:
|
|
4
|
+
- whoosh: 镜头快速推拉或转场切换时的空气扫风声;
|
|
5
|
+
- glint: 金属墨镜反光、高光扫掠或极光破晓时的清脆晶莹音效;
|
|
6
|
+
- impact: 标题出字或产品霸气定格时的低音共振冲击 (Sub-bass impact);
|
|
7
|
+
- click: 极简机械按键或聚光灯开启声。
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import logging
|
|
13
|
+
import math
|
|
14
|
+
import struct
|
|
15
|
+
import wave
|
|
16
|
+
from dataclasses import dataclass, field
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Any, Dict, List, Optional
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class FoleyCue:
|
|
25
|
+
sound_type: str # "whoosh" | "glint" | "impact" | "click"
|
|
26
|
+
timestamp_seconds: float
|
|
27
|
+
volume: float = 0.5
|
|
28
|
+
duration: float = 0.8
|
|
29
|
+
description: str = ""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class FoleySoundService:
|
|
33
|
+
"""拟音声效编排与程序化生成服务。"""
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def generate_cues_for_storyboard(cls, shots: List[Dict[str, Any]]) -> List[FoleyCue]:
|
|
37
|
+
"""依据分镜时序与动作特征自动打点生成音效提示清单。"""
|
|
38
|
+
cues: List[FoleyCue] = []
|
|
39
|
+
cursor = 0.0
|
|
40
|
+
|
|
41
|
+
for idx, shot in enumerate(shots):
|
|
42
|
+
dur = float(shot.get("duration", 4.0))
|
|
43
|
+
prompt = str(shot.get("prompt", "")).lower()
|
|
44
|
+
|
|
45
|
+
# 镜头转场处(除首镜头外)插入转场扫风
|
|
46
|
+
if idx > 0:
|
|
47
|
+
cues.append(FoleyCue(
|
|
48
|
+
sound_type="whoosh",
|
|
49
|
+
timestamp_seconds=round(max(0.0, cursor - 0.15), 2),
|
|
50
|
+
volume=0.35,
|
|
51
|
+
description="Shot transition whoosh",
|
|
52
|
+
))
|
|
53
|
+
|
|
54
|
+
# 包含光线反光或高光的镜头插入 Glint
|
|
55
|
+
if any(k in prompt for k in ["sunglasses", "reflection", "flare", "shimmer", "glint", "反光", "高光"]):
|
|
56
|
+
cues.append(FoleyCue(
|
|
57
|
+
sound_type="glint",
|
|
58
|
+
timestamp_seconds=round(cursor + dur * 0.4, 2),
|
|
59
|
+
volume=0.4,
|
|
60
|
+
description="Specular highlight glint shimmer",
|
|
61
|
+
))
|
|
62
|
+
|
|
63
|
+
# 压轴定格或 Hook 开头插入 Sub-impact
|
|
64
|
+
if idx == 0 or idx == len(shots) - 1:
|
|
65
|
+
cues.append(FoleyCue(
|
|
66
|
+
sound_type="impact",
|
|
67
|
+
timestamp_seconds=round(cursor + 0.2, 2),
|
|
68
|
+
volume=0.45,
|
|
69
|
+
description="Cinematic low-end keynote presence",
|
|
70
|
+
))
|
|
71
|
+
|
|
72
|
+
cursor += dur
|
|
73
|
+
|
|
74
|
+
return cues
|
|
75
|
+
|
|
76
|
+
@classmethod
|
|
77
|
+
def synthesize_cue_track(
|
|
78
|
+
cls,
|
|
79
|
+
cues: List[FoleyCue],
|
|
80
|
+
total_duration: float,
|
|
81
|
+
output_path: str,
|
|
82
|
+
sample_rate: int = 44100,
|
|
83
|
+
) -> str:
|
|
84
|
+
"""程序化合成包含所有 Foley 打点音效的独立音轨。"""
|
|
85
|
+
out_path = Path(output_path)
|
|
86
|
+
out_path.parent.mkdir(parents=True, exist_ok=True)
|
|
87
|
+
|
|
88
|
+
total_samples = int(total_duration * sample_rate)
|
|
89
|
+
samples = [0.0] * total_samples
|
|
90
|
+
|
|
91
|
+
for cue in cues:
|
|
92
|
+
start_sample = int(cue.timestamp_seconds * sample_rate)
|
|
93
|
+
if start_sample >= total_samples:
|
|
94
|
+
continue
|
|
95
|
+
|
|
96
|
+
cue_samples = int(cue.duration * sample_rate)
|
|
97
|
+
for s in range(cue_samples):
|
|
98
|
+
idx = start_sample + s
|
|
99
|
+
if idx >= total_samples:
|
|
100
|
+
break
|
|
101
|
+
|
|
102
|
+
t = s / sample_rate
|
|
103
|
+
# 根据不同音效类型生成对应的正弦频移波形
|
|
104
|
+
if cue.sound_type == "whoosh":
|
|
105
|
+
# 扫频白噪过滤带通效果
|
|
106
|
+
freq = 200.0 + 600.0 * math.sin(math.pi * (t / cue.duration))
|
|
107
|
+
val = 0.5 * math.sin(2 * math.pi * freq * t) * math.sin(math.pi * (t / cue.duration))
|
|
108
|
+
elif cue.sound_type == "glint":
|
|
109
|
+
# 高频清脆钟音 2400Hz 快速衰减
|
|
110
|
+
val = math.sin(2 * math.pi * 2400 * t) * math.exp(-6.0 * t)
|
|
111
|
+
elif cue.sound_type == "impact":
|
|
112
|
+
# 低频 60Hz -> 30Hz 重低音下潜
|
|
113
|
+
freq = 65.0 * math.exp(-2.5 * t)
|
|
114
|
+
val = math.sin(2 * math.pi * freq * t) * math.exp(-3.0 * t)
|
|
115
|
+
else:
|
|
116
|
+
val = 0.0
|
|
117
|
+
|
|
118
|
+
samples[idx] += val * cue.volume
|
|
119
|
+
|
|
120
|
+
# 封包为 16-bit 双声道 PCM WAV
|
|
121
|
+
frames = bytearray()
|
|
122
|
+
for sample in samples:
|
|
123
|
+
clamped = int(max(-32767, min(32767, sample * 16000)))
|
|
124
|
+
frames.extend(struct.pack("<hh", clamped, clamped))
|
|
125
|
+
|
|
126
|
+
with wave.open(str(out_path), "wb") as wf:
|
|
127
|
+
wf.setnchannels(2)
|
|
128
|
+
wf.setsampwidth(2)
|
|
129
|
+
wf.setframerate(sample_rate)
|
|
130
|
+
wf.writeframes(frames)
|
|
131
|
+
|
|
132
|
+
return str(out_path)
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"""service.py — 视频背景音乐 (BGM) 与音效智能管理服务。
|
|
2
|
+
|
|
3
|
+
核心功能:
|
|
4
|
+
1. 情绪与风格智能选曲:根据剧本文案与视觉风格(Keynote发布会、科技极简、萌宠轻快、史诗电影等)
|
|
5
|
+
自动分析匹配最契合的背景音乐风格。
|
|
6
|
+
2. 免版税与本地音乐库索引:统一管理本地存储 (~/.aria-code/music 或 assets/music) 的免版税音频资产。
|
|
7
|
+
3. 程序化高保真氛围音轨合成 (Procedural Ambient Synth):零依赖、零外链,可在本地瞬时合成指定时长的
|
|
8
|
+
苹果发布会质感 44.1kHz 立体声环境背景音轨。
|
|
9
|
+
4. AI 生成式配乐 (MusicGen / Suno) 提示词编排:为后续接入云端生成式音乐大模型提供专业的 Prompt 生成器。
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
import logging
|
|
16
|
+
import math
|
|
17
|
+
import os
|
|
18
|
+
import struct
|
|
19
|
+
import wave
|
|
20
|
+
from dataclasses import dataclass, field
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from typing import Any, Dict, List, Optional
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class MusicTrackMetadata:
|
|
29
|
+
id: str
|
|
30
|
+
title: str
|
|
31
|
+
genre: str # ambient, electronic, acoustic, orchestral, lofi
|
|
32
|
+
mood: str # professional, futuristic, cute, epic, chill
|
|
33
|
+
bpm: int = 90
|
|
34
|
+
duration_seconds: float = 30.0
|
|
35
|
+
file_path: Optional[str] = None
|
|
36
|
+
tags: List[str] = field(default_factory=list)
|
|
37
|
+
license: str = "Royalty-Free / CC0"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class MusicLibraryService:
|
|
41
|
+
"""背景音乐与音频资产管理服务单例。"""
|
|
42
|
+
|
|
43
|
+
_instance: Optional[MusicLibraryService] = None
|
|
44
|
+
|
|
45
|
+
STYLE_MOOD_MAP = {
|
|
46
|
+
"apple_keynote": {"genre": "ambient", "mood": "professional", "bpm": 80, "desc": "Apple keynote ambient synthesizer, C major 7th chords, minimalist, spacious, inspiring"},
|
|
47
|
+
"tech": {"genre": "electronic", "mood": "futuristic", "bpm": 110, "desc": "Modern tech, crisp digital synth plucks, clean bassline, innovation narrative"},
|
|
48
|
+
"cute": {"genre": "acoustic", "mood": "cute", "bpm": 105, "desc": "Playful acoustic, wooden marimba, ukulele, cheerful, warm, bouncy pet style"},
|
|
49
|
+
"cinematic": {"genre": "orchestral", "mood": "epic", "bpm": 75, "desc": "Cinematic orchestral swelling, deep cello, expansive cinematic atmosphere"},
|
|
50
|
+
"chill": {"genre": "lofi", "mood": "chill", "bpm": 85, "desc": "Warm lo-fi chill hip-hop, vinyl crackle, gentle Rhodes piano, relaxing vibe"},
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
def __init__(self, custom_library_dir: Optional[str] = None):
|
|
54
|
+
self.library_dir = Path(custom_library_dir or os.path.expanduser("~/.aria-code/music"))
|
|
55
|
+
self.library_dir.mkdir(parents=True, exist_ok=True)
|
|
56
|
+
self._tracks: Dict[str, MusicTrackMetadata] = {}
|
|
57
|
+
self._load_local_library()
|
|
58
|
+
|
|
59
|
+
@classmethod
|
|
60
|
+
def get_instance(cls) -> MusicLibraryService:
|
|
61
|
+
if cls._instance is None:
|
|
62
|
+
cls._instance = cls()
|
|
63
|
+
return cls._instance
|
|
64
|
+
|
|
65
|
+
def _load_local_library(self) -> None:
|
|
66
|
+
"""扫描本地音乐库目录下的音频文件与元数据。"""
|
|
67
|
+
meta_file = self.library_dir / "catalog.json"
|
|
68
|
+
if meta_file.exists():
|
|
69
|
+
try:
|
|
70
|
+
data = json.loads(meta_file.read_text(encoding="utf-8"))
|
|
71
|
+
for item in data.get("tracks", []):
|
|
72
|
+
self._tracks[item["id"]] = MusicTrackMetadata(**item)
|
|
73
|
+
except Exception as exc:
|
|
74
|
+
logger.warning(f"[MusicLibraryService] Failed to parse {meta_file}: {exc}")
|
|
75
|
+
|
|
76
|
+
# 扫描目录下的 mp3 / wav 文件
|
|
77
|
+
for ext in ("*.mp3", "*.wav", "*.m4a"):
|
|
78
|
+
for audio_file in self.library_dir.glob(ext):
|
|
79
|
+
tid = audio_file.stem
|
|
80
|
+
if tid not in self._tracks:
|
|
81
|
+
self._tracks[tid] = MusicTrackMetadata(
|
|
82
|
+
id=tid,
|
|
83
|
+
title=tid.replace("_", " ").title(),
|
|
84
|
+
genre="general",
|
|
85
|
+
mood="ambient",
|
|
86
|
+
file_path=str(audio_file),
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
def analyze_script_mood(self, script: str, style: str = "apple_keynote") -> Dict[str, Any]:
|
|
90
|
+
"""根据脚本词汇与视频风格推断配乐情绪与匹配标准。"""
|
|
91
|
+
script_lower = script.lower()
|
|
92
|
+
matched_style = style.lower()
|
|
93
|
+
|
|
94
|
+
# 关键字启发式推导
|
|
95
|
+
if any(w in script_lower for w in ["发布会", "keynote", "apple", "旗舰", "pro", "极简", "优雅"]):
|
|
96
|
+
matched_style = "apple_keynote"
|
|
97
|
+
elif any(w in script_lower for w in ["猫", "狗", "萌", "可爱", "taco", "cute", "pet", "bunny", "兔"]):
|
|
98
|
+
matched_style = "cute"
|
|
99
|
+
elif any(w in script_lower for w in ["芯片", "算法", "ai", "算力", "科技", "智能", "未来"]):
|
|
100
|
+
matched_style = "tech"
|
|
101
|
+
elif any(w in script_lower for w in ["震撼", "史诗", "宇宙", "宏大", "征程", "突破"]):
|
|
102
|
+
matched_style = "cinematic"
|
|
103
|
+
|
|
104
|
+
spec = self.STYLE_MOOD_MAP.get(matched_style, self.STYLE_MOOD_MAP["apple_keynote"])
|
|
105
|
+
return {
|
|
106
|
+
"style": matched_style,
|
|
107
|
+
"genre": spec["genre"],
|
|
108
|
+
"mood": spec["mood"],
|
|
109
|
+
"target_bpm": spec["bpm"],
|
|
110
|
+
"ai_prompt": spec["desc"],
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
def match_or_synthesize_bgm(
|
|
114
|
+
self,
|
|
115
|
+
script: str,
|
|
116
|
+
style: str = "apple_keynote",
|
|
117
|
+
duration_seconds: float = 20.0,
|
|
118
|
+
output_dir: Optional[str] = None,
|
|
119
|
+
) -> str:
|
|
120
|
+
"""获取或合成与脚本最匹配的背景音乐,返回音频文件绝对路径。"""
|
|
121
|
+
mood_spec = self.analyze_script_mood(script, style)
|
|
122
|
+
matched_genre = mood_spec["genre"]
|
|
123
|
+
|
|
124
|
+
# 1. 如果本地库有匹配的音频文件,优先使用
|
|
125
|
+
for track in self._tracks.values():
|
|
126
|
+
if track.genre == matched_genre and track.file_path and os.path.exists(track.file_path):
|
|
127
|
+
logger.info(f"[MusicLibraryService] Using matched local library track: {track.title}")
|
|
128
|
+
return track.file_path
|
|
129
|
+
|
|
130
|
+
# 2. 如果没有本地预录文件,运行程序化高保真合成 (零依赖快速产出)
|
|
131
|
+
target_dir = Path(output_dir) if output_dir else self.library_dir
|
|
132
|
+
target_dir.mkdir(parents=True, exist_ok=True)
|
|
133
|
+
filename = f"bgm_{mood_spec['style']}_{int(duration_seconds)}s.wav"
|
|
134
|
+
out_path = target_dir / filename
|
|
135
|
+
|
|
136
|
+
if not out_path.exists():
|
|
137
|
+
logger.info(f"[MusicLibraryService] Synthesizing procedural ambient keynote track ({duration_seconds}s)...")
|
|
138
|
+
self.synthesize_ambient_synth_bgm(str(out_path), duration_seconds=duration_seconds)
|
|
139
|
+
|
|
140
|
+
return str(out_path)
|
|
141
|
+
|
|
142
|
+
@classmethod
|
|
143
|
+
def synthesize_ambient_synth_bgm(
|
|
144
|
+
cls,
|
|
145
|
+
output_path: str,
|
|
146
|
+
duration_seconds: float = 25.0,
|
|
147
|
+
sample_rate: int = 44100,
|
|
148
|
+
) -> str:
|
|
149
|
+
"""纯 Python 程序化合成 44.1kHz 立体声苹果发布会风格环境氛围音轨 (Cmaj7 -> Am9)。"""
|
|
150
|
+
out_path = Path(output_path)
|
|
151
|
+
out_path.parent.mkdir(parents=True, exist_ok=True)
|
|
152
|
+
|
|
153
|
+
total_samples = int(duration_seconds * sample_rate)
|
|
154
|
+
# 苹果发布会经典和弦走带: Cmaj7 (C, E, G, B) -> Am9 (A, C, E, G, B) -> Fmaj7 -> Gsus4
|
|
155
|
+
chords = [
|
|
156
|
+
[261.63, 329.63, 392.00, 493.88], # Cmaj7
|
|
157
|
+
[220.00, 261.63, 329.63, 392.00], # Am7/Am9
|
|
158
|
+
[174.61, 220.00, 261.63, 329.63], # Fmaj7
|
|
159
|
+
[196.00, 261.63, 293.66, 392.00], # Gsus4
|
|
160
|
+
]
|
|
161
|
+
|
|
162
|
+
chord_dur = 6.0 # 每个和弦 6 秒展开
|
|
163
|
+
frames = bytearray()
|
|
164
|
+
|
|
165
|
+
for i in range(total_samples):
|
|
166
|
+
t = i / sample_rate
|
|
167
|
+
chord_idx = int((t / chord_dur) % len(chords))
|
|
168
|
+
chord = chords[chord_idx]
|
|
169
|
+
|
|
170
|
+
# 和弦内插淡入淡出包络
|
|
171
|
+
t_in_chord = t % chord_dur
|
|
172
|
+
env = math.sin(math.pi * min(1.0, t_in_chord / chord_dur)) ** 0.6
|
|
173
|
+
|
|
174
|
+
# 整体首尾淡入淡出
|
|
175
|
+
fade_in = min(1.0, t / 1.5)
|
|
176
|
+
fade_out = min(1.0, (duration_seconds - t) / 2.0)
|
|
177
|
+
master_vol = 0.28 * fade_in * fade_out * env
|
|
178
|
+
|
|
179
|
+
left_sample = 0.0
|
|
180
|
+
right_sample = 0.0
|
|
181
|
+
|
|
182
|
+
# 4个声音分音合唱 (Chorus & Stereo Detune)
|
|
183
|
+
for idx, freq in enumerate(chord):
|
|
184
|
+
# 微音分失谐 (Detuning) 营造空灵大空间感
|
|
185
|
+
freq_l = freq * (1.0 + 0.0012 * (idx - 1.5))
|
|
186
|
+
freq_r = freq * (1.0 - 0.0012 * (idx - 1.5))
|
|
187
|
+
|
|
188
|
+
phase_l = 2.0 * math.pi * freq_l * t
|
|
189
|
+
phase_r = 2.0 * math.pi * freq_r * t
|
|
190
|
+
|
|
191
|
+
# 正弦基波 + 微弱次泛音 (Warm Ambient)
|
|
192
|
+
tone_l = 0.7 * math.sin(phase_l) + 0.2 * math.sin(phase_l * 2) + 0.1 * math.sin(phase_l * 0.5)
|
|
193
|
+
tone_r = 0.7 * math.sin(phase_r) + 0.2 * math.sin(phase_r * 2) + 0.1 * math.sin(phase_r * 0.5)
|
|
194
|
+
|
|
195
|
+
# 立体声微向展开
|
|
196
|
+
pan_bias = (idx / 3.0) - 0.5 # -0.5 ~ +0.5
|
|
197
|
+
left_sample += tone_l * (0.6 - pan_bias * 0.3)
|
|
198
|
+
right_sample += tone_r * (0.6 + pan_bias * 0.3)
|
|
199
|
+
|
|
200
|
+
# 压限防止削波
|
|
201
|
+
left_val = int(max(-32767, min(32767, left_sample * master_vol * 12000)))
|
|
202
|
+
right_val = int(max(-32767, min(32767, right_sample * master_vol * 12000)))
|
|
203
|
+
|
|
204
|
+
frames.extend(struct.pack("<hh", left_val, right_val))
|
|
205
|
+
|
|
206
|
+
with wave.open(str(out_path), "wb") as wf:
|
|
207
|
+
wf.setnchannels(2)
|
|
208
|
+
wf.setsampwidth(2)
|
|
209
|
+
wf.setframerate(sample_rate)
|
|
210
|
+
wf.writeframes(frames)
|
|
211
|
+
|
|
212
|
+
return str(out_path)
|