project-graph-mcp 2.3.2 → 2.4.0
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.
- package/package.json +3 -2
- package/src/analysis/analysis-cache.ctx +9 -0
- package/src/analysis/analysis-cache.js +1 -1
- package/src/analysis/complexity.ctx +6 -0
- package/src/analysis/complexity.js +1 -1
- package/src/analysis/custom-rules.ctx +14 -0
- package/src/analysis/custom-rules.js +1 -1
- package/src/analysis/db-analysis.ctx +7 -0
- package/src/analysis/db-analysis.js +1 -1
- package/src/analysis/dead-code.ctx +6 -0
- package/src/analysis/dead-code.js +1 -1
- package/src/analysis/full-analysis.ctx +9 -0
- package/src/analysis/full-analysis.js +1 -1
- package/src/analysis/jsdoc-checker.ctx +10 -0
- package/src/analysis/jsdoc-checker.js +1 -1
- package/src/analysis/jsdoc-generator.ctx +9 -0
- package/src/analysis/jsdoc-generator.js +1 -1
- package/src/analysis/large-files.ctx +6 -0
- package/src/analysis/large-files.js +1 -1
- package/src/analysis/outdated-patterns.ctx +7 -0
- package/src/analysis/outdated-patterns.js +1 -1
- package/src/analysis/similar-functions.ctx +6 -0
- package/src/analysis/similar-functions.js +1 -1
- package/src/analysis/test-annotations.ctx +11 -0
- package/src/analysis/test-annotations.js +1 -1
- package/src/analysis/type-checker.ctx +6 -0
- package/src/analysis/type-checker.js +1 -1
- package/src/analysis/undocumented.ctx +8 -0
- package/src/analysis/undocumented.js +1 -1
- package/src/cli/cli-handlers.ctx +7 -0
- package/src/cli/cli-handlers.js +1 -1
- package/src/cli/cli.ctx +6 -0
- package/src/cli/cli.js +1 -1
- package/src/compact/ai-context.ctx +6 -0
- package/src/compact/ai-context.js +1 -1
- package/src/compact/compact-migrate.ctx +8 -0
- package/src/compact/compact-migrate.js +1 -1
- package/src/compact/compact.ctx +11 -0
- package/src/compact/compact.js +1 -1
- package/src/compact/compress.ctx +7 -0
- package/src/compact/compress.js +1 -1
- package/src/compact/ctx-resolver.ctx +2 -0
- package/src/compact/ctx-resolver.js +1 -1
- package/src/compact/ctx-to-jsdoc.ctx +11 -0
- package/src/compact/ctx-to-jsdoc.js +1 -1
- package/src/compact/doc-dialect.ctx +11 -0
- package/src/compact/doc-dialect.js +2 -2
- package/src/compact/expand.ctx +14 -0
- package/src/compact/expand.js +1 -1
- package/src/compact/framework-references.ctx +7 -0
- package/src/compact/framework-references.js +1 -1
- package/src/compact/instructions.ctx +6 -0
- package/src/compact/instructions.js +1 -1
- package/src/compact/jsdoc-builder.ctx +4 -0
- package/src/compact/jsdoc-builder.js +1 -1
- package/src/compact/mode-config.ctx +8 -0
- package/src/compact/mode-config.js +1 -1
- package/src/compact/split-declarations.ctx +6 -0
- package/src/compact/split-declarations.js +1 -1
- package/src/compact/validate-pipeline.ctx +12 -0
- package/src/compact/validate-pipeline.js +1 -1
- package/src/core/event-bus.ctx +9 -0
- package/src/core/event-bus.js +1 -1
- package/src/core/file-walker.ctx +1 -0
- package/src/core/file-walker.js +1 -1
- package/src/core/filters.ctx +12 -0
- package/src/core/filters.js +1 -1
- package/src/core/graph-builder.ctx +7 -0
- package/src/core/graph-builder.js +1 -1
- package/src/core/parser.ctx +12 -0
- package/src/core/parser.js +1 -1
- package/src/core/utils.ctx +1 -0
- package/src/core/utils.js +1 -1
- package/src/core/workspace.ctx +7 -0
- package/src/core/workspace.js +1 -1
- package/src/lang/lang-go.ctx +8 -0
- package/src/lang/lang-go.js +1 -1
- package/src/lang/lang-python.ctx +5 -0
- package/src/lang/lang-python.js +1 -1
- package/src/lang/lang-sql.ctx +10 -0
- package/src/lang/lang-sql.js +1 -1
- package/src/lang/lang-typescript.ctx +6 -0
- package/src/lang/lang-typescript.js +1 -1
- package/src/lang/lang-utils.ctx +5 -0
- package/src/lang/lang-utils.js +1 -1
- package/src/mcp/mcp-server.ctx +6 -0
- package/src/mcp/mcp-server.js +1 -1
- package/src/mcp/tool-defs.ctx +2 -0
- package/src/mcp/tool-defs.js +1 -1
- package/src/mcp/tools.ctx +13 -0
- package/src/mcp/tools.js +1 -1
- package/src/network/backend-lifecycle.ctx +10 -0
- package/src/network/backend-lifecycle.js +1 -1
- package/src/network/backend.ctx +5 -0
- package/src/network/backend.js +1 -1
- package/src/network/local-gateway.ctx +9 -0
- package/src/network/local-gateway.js +1 -1
- package/src/network/mdns.ctx +6 -0
- package/src/network/mdns.js +1 -1
- package/src/network/server.ctx +2 -0
- package/src/network/server.js +2 -2
- package/src/network/web-server.ctx +17 -0
- package/src/network/web-server.js +2 -2
- package/web/follow-controller.js +94 -25
- package/web/panels/dep-graph.js +207 -21
- package/project-graph-mcp-2.3.0.tgz +0 -0
- package/vendor/symbiote-node/CHANGELOG.md +0 -31
- package/vendor/symbiote-node/LICENSE +0 -21
- package/vendor/symbiote-node/README.md +0 -206
- package/vendor/symbiote-node/canvas/AutoLayout.js +0 -725
- package/vendor/symbiote-node/canvas/Breadcrumb/Breadcrumb.css.js +0 -73
- package/vendor/symbiote-node/canvas/Breadcrumb/Breadcrumb.js +0 -93
- package/vendor/symbiote-node/canvas/Breadcrumb/Breadcrumb.tpl.js +0 -9
- package/vendor/symbiote-node/canvas/CanvasConnectionRenderer.js +0 -962
- package/vendor/symbiote-node/canvas/ConnectionRenderer.js +0 -1468
- package/vendor/symbiote-node/canvas/FlowSimulator.js +0 -323
- package/vendor/symbiote-node/canvas/ForceLayout.js +0 -189
- package/vendor/symbiote-node/canvas/ForceWorker.js +0 -1325
- package/vendor/symbiote-node/canvas/GraphTabs/GraphTabs.css.js +0 -97
- package/vendor/symbiote-node/canvas/GraphTabs/GraphTabs.js +0 -176
- package/vendor/symbiote-node/canvas/GraphTabs/GraphTabs.tpl.js +0 -12
- package/vendor/symbiote-node/canvas/LODManager.js +0 -88
- package/vendor/symbiote-node/canvas/Minimap/Minimap.css.js +0 -71
- package/vendor/symbiote-node/canvas/Minimap/Minimap.js +0 -207
- package/vendor/symbiote-node/canvas/Minimap/Minimap.tpl.js +0 -9
- package/vendor/symbiote-node/canvas/NodeCanvas/NodeCanvas.css.js +0 -261
- package/vendor/symbiote-node/canvas/NodeCanvas/NodeCanvas.js +0 -1840
- package/vendor/symbiote-node/canvas/NodeCanvas/NodeCanvas.tpl.js +0 -22
- package/vendor/symbiote-node/canvas/NodeSearch/NodeSearch.css.js +0 -97
- package/vendor/symbiote-node/canvas/NodeSearch/NodeSearch.js +0 -132
- package/vendor/symbiote-node/canvas/NodeSearch/NodeSearch.tpl.js +0 -21
- package/vendor/symbiote-node/canvas/NodeViewManager.js +0 -584
- package/vendor/symbiote-node/canvas/PinExpansion.js +0 -131
- package/vendor/symbiote-node/canvas/PseudoConnection.js +0 -80
- package/vendor/symbiote-node/canvas/SubgraphManager.js +0 -201
- package/vendor/symbiote-node/canvas/SubgraphRouter.js +0 -443
- package/vendor/symbiote-node/canvas/ViewportActions.js +0 -446
- package/vendor/symbiote-node/core/Connection.js +0 -45
- package/vendor/symbiote-node/core/Editor.js +0 -451
- package/vendor/symbiote-node/core/Frame.js +0 -31
- package/vendor/symbiote-node/core/GraphMermaid.js +0 -348
- package/vendor/symbiote-node/core/GraphText.js +0 -210
- package/vendor/symbiote-node/core/Node.js +0 -143
- package/vendor/symbiote-node/core/Portal.js +0 -104
- package/vendor/symbiote-node/core/Socket.js +0 -185
- package/vendor/symbiote-node/core/SubgraphNode.js +0 -125
- package/vendor/symbiote-node/engine/AgentUICommands.js +0 -100
- package/vendor/symbiote-node/engine/Executor.js +0 -371
- package/vendor/symbiote-node/engine/Graph.js +0 -314
- package/vendor/symbiote-node/engine/GraphServer.js +0 -353
- package/vendor/symbiote-node/engine/HandlerLoader.js +0 -145
- package/vendor/symbiote-node/engine/History.js +0 -83
- package/vendor/symbiote-node/engine/Lifecycle.js +0 -118
- package/vendor/symbiote-node/engine/Persistence.js +0 -84
- package/vendor/symbiote-node/engine/Registry.js +0 -264
- package/vendor/symbiote-node/engine/SocketTypes.js +0 -79
- package/vendor/symbiote-node/engine/cli.js +0 -404
- package/vendor/symbiote-node/engine/index.js +0 -56
- package/vendor/symbiote-node/engine/nanoid.js +0 -28
- package/vendor/symbiote-node/engine/package.json +0 -26
- package/vendor/symbiote-node/engine/packs/ai/beat-detect.handler.js +0 -215
- package/vendor/symbiote-node/engine/packs/ai/content-adapt.handler.js +0 -238
- package/vendor/symbiote-node/engine/packs/ai/face-detect.handler.js +0 -287
- package/vendor/symbiote-node/engine/packs/ai/grok-generate.handler.js +0 -565
- package/vendor/symbiote-node/engine/packs/ai/kling-lipsync.handler.js +0 -414
- package/vendor/symbiote-node/engine/packs/ai/lesson-generate.handler.js +0 -343
- package/vendor/symbiote-node/engine/packs/ai/opencode.handler.js +0 -164
- package/vendor/symbiote-node/engine/packs/ai/replicate-lipsync.handler.js +0 -341
- package/vendor/symbiote-node/engine/packs/ai/tts.handler.js +0 -241
- package/vendor/symbiote-node/engine/packs/ai/whisper.handler.js +0 -191
- package/vendor/symbiote-node/engine/packs/data/db-query.handler.js +0 -67
- package/vendor/symbiote-node/engine/packs/data/news-accumulate.handler.js +0 -281
- package/vendor/symbiote-node/engine/packs/data/personas.handler.js +0 -160
- package/vendor/symbiote-node/engine/packs/data/prompt-loader.handler.js +0 -193
- package/vendor/symbiote-node/engine/packs/data/roles.handler.js +0 -216
- package/vendor/symbiote-node/engine/packs/data/rss-feed.handler.js +0 -244
- package/vendor/symbiote-node/engine/packs/debug/inject.handler.js +0 -52
- package/vendor/symbiote-node/engine/packs/flow/agent.handler.js +0 -73
- package/vendor/symbiote-node/engine/packs/flow/if.handler.js +0 -107
- package/vendor/symbiote-node/engine/packs/flow/loop.handler.js +0 -58
- package/vendor/symbiote-node/engine/packs/flow/merge.handler.js +0 -60
- package/vendor/symbiote-node/engine/packs/flow/retry.handler.js +0 -65
- package/vendor/symbiote-node/engine/packs/flow/switch.handler.js +0 -64
- package/vendor/symbiote-node/engine/packs/flow/wait-all.handler.js +0 -39
- package/vendor/symbiote-node/engine/packs/io/http-request.handler.js +0 -82
- package/vendor/symbiote-node/engine/packs/io/read-file.handler.js +0 -60
- package/vendor/symbiote-node/engine/packs/io/write-file.handler.js +0 -63
- package/vendor/symbiote-node/engine/packs/transform/anchor-match.handler.js +0 -494
- package/vendor/symbiote-node/engine/packs/transform/effects-skeleton.handler.js +0 -417
- package/vendor/symbiote-node/engine/packs/transform/json-parse.handler.js +0 -43
- package/vendor/symbiote-node/engine/packs/transform/lipsync-select.handler.js +0 -339
- package/vendor/symbiote-node/engine/packs/transform/riopla-adapt.handler.js +0 -432
- package/vendor/symbiote-node/engine/packs/transform/set.handler.js +0 -57
- package/vendor/symbiote-node/engine/packs/transform/template-builder.handler.js +0 -134
- package/vendor/symbiote-node/engine/packs/transform/template.handler.js +0 -79
- package/vendor/symbiote-node/engine/packs/transform/timeline-build.handler.js +0 -399
- package/vendor/symbiote-node/engine/packs/util/delay.handler.js +0 -39
- package/vendor/symbiote-node/engine/packs/util/log.handler.js +0 -44
- package/vendor/symbiote-node/engine/packs/video-pack.js +0 -323
- package/vendor/symbiote-node/index.js +0 -103
- package/vendor/symbiote-node/inspector/InspectorPanel/InspectorPanel.css.js +0 -361
- package/vendor/symbiote-node/inspector/InspectorPanel/InspectorPanel.js +0 -332
- package/vendor/symbiote-node/inspector/InspectorPanel/InspectorPanel.tpl.js +0 -96
- package/vendor/symbiote-node/inspector/TemplatePreview/TemplatePreview.css.js +0 -104
- package/vendor/symbiote-node/inspector/TemplatePreview/TemplatePreview.js +0 -133
- package/vendor/symbiote-node/inspector/TemplatePreview/TemplatePreview.tpl.js +0 -33
- package/vendor/symbiote-node/interactions/ConnectFlow.js +0 -307
- package/vendor/symbiote-node/interactions/Drag.js +0 -102
- package/vendor/symbiote-node/interactions/Selector.js +0 -132
- package/vendor/symbiote-node/interactions/SnapGrid.js +0 -65
- package/vendor/symbiote-node/interactions/Zoom.js +0 -140
- package/vendor/symbiote-node/layout/ActionZone/ActionZone.css.js +0 -88
- package/vendor/symbiote-node/layout/ActionZone/ActionZone.js +0 -254
- package/vendor/symbiote-node/layout/ActionZone/ActionZone.tpl.js +0 -11
- package/vendor/symbiote-node/layout/Layout/Layout.css.js +0 -88
- package/vendor/symbiote-node/layout/Layout/Layout.js +0 -622
- package/vendor/symbiote-node/layout/Layout/Layout.tpl.js +0 -25
- package/vendor/symbiote-node/layout/LayoutNode/LayoutNode.css.js +0 -293
- package/vendor/symbiote-node/layout/LayoutNode/LayoutNode.js +0 -467
- package/vendor/symbiote-node/layout/LayoutNode/LayoutNode.tpl.js +0 -33
- package/vendor/symbiote-node/layout/LayoutPreview/LayoutPreview.css.js +0 -46
- package/vendor/symbiote-node/layout/LayoutPreview/LayoutPreview.js +0 -102
- package/vendor/symbiote-node/layout/LayoutPreview/LayoutPreview.tpl.js +0 -6
- package/vendor/symbiote-node/layout/LayoutRouter/LayoutRouter.js +0 -156
- package/vendor/symbiote-node/layout/LayoutRouter/routerSync.js +0 -250
- package/vendor/symbiote-node/layout/LayoutSidebar/LayoutSidebar.css.js +0 -379
- package/vendor/symbiote-node/layout/LayoutSidebar/LayoutSidebar.js +0 -263
- package/vendor/symbiote-node/layout/LayoutSidebar/LayoutSidebar.tpl.js +0 -20
- package/vendor/symbiote-node/layout/LayoutSidebar/SidebarSection.js +0 -183
- package/vendor/symbiote-node/layout/LayoutTree.js +0 -246
- package/vendor/symbiote-node/layout/PanelMenu/PanelMenu.css.js +0 -43
- package/vendor/symbiote-node/layout/PanelMenu/PanelMenu.js +0 -89
- package/vendor/symbiote-node/layout/PanelMenu/PanelMenu.tpl.js +0 -14
- package/vendor/symbiote-node/layout/index.js +0 -16
- package/vendor/symbiote-node/menu/ContextMenu/ContextMenu.css.js +0 -61
- package/vendor/symbiote-node/menu/ContextMenu/ContextMenu.js +0 -79
- package/vendor/symbiote-node/menu/ContextMenu/ContextMenu.tpl.js +0 -19
- package/vendor/symbiote-node/node/CtrlItem/CtrlItem.css.js +0 -41
- package/vendor/symbiote-node/node/CtrlItem/CtrlItem.js +0 -24
- package/vendor/symbiote-node/node/CtrlItem/CtrlItem.tpl.js +0 -16
- package/vendor/symbiote-node/node/GraphFrame/GraphFrame.css.js +0 -65
- package/vendor/symbiote-node/node/GraphFrame/GraphFrame.js +0 -29
- package/vendor/symbiote-node/node/GraphFrame/GraphFrame.tpl.js +0 -13
- package/vendor/symbiote-node/node/GraphNode/GraphNode.css.js +0 -683
- package/vendor/symbiote-node/node/GraphNode/GraphNode.js +0 -92
- package/vendor/symbiote-node/node/GraphNode/GraphNode.tpl.js +0 -17
- package/vendor/symbiote-node/node/NodeSocket/NodeSocket.js +0 -25
- package/vendor/symbiote-node/node/NodeSocket/NodeSocket.tpl.js +0 -7
- package/vendor/symbiote-node/node/PortItem/PortItem.css.js +0 -90
- package/vendor/symbiote-node/node/PortItem/PortItem.js +0 -87
- package/vendor/symbiote-node/node/PortItem/PortItem.tpl.js +0 -10
- package/vendor/symbiote-node/package.json +0 -59
- package/vendor/symbiote-node/palette/PaletteBrowser/PaletteBrowser.css.js +0 -143
- package/vendor/symbiote-node/palette/PaletteBrowser/PaletteBrowser.js +0 -131
- package/vendor/symbiote-node/palette/PaletteBrowser/PaletteBrowser.tpl.js +0 -16
- package/vendor/symbiote-node/plugins/History.js +0 -384
- package/vendor/symbiote-node/plugins/Readonly.js +0 -59
- package/vendor/symbiote-node/shapes/CircleShape.js +0 -80
- package/vendor/symbiote-node/shapes/CommentShape.js +0 -35
- package/vendor/symbiote-node/shapes/DiamondShape.js +0 -115
- package/vendor/symbiote-node/shapes/NodeShape.js +0 -80
- package/vendor/symbiote-node/shapes/PillShape.js +0 -91
- package/vendor/symbiote-node/shapes/RectShape.js +0 -72
- package/vendor/symbiote-node/shapes/SVGShape.js +0 -494
- package/vendor/symbiote-node/shapes/index.js +0 -53
- package/vendor/symbiote-node/themes/Palette.js +0 -32
- package/vendor/symbiote-node/themes/Skin.js +0 -113
- package/vendor/symbiote-node/themes/Theme.js +0 -84
- package/vendor/symbiote-node/themes/carbon.js +0 -137
- package/vendor/symbiote-node/themes/dark.js +0 -137
- package/vendor/symbiote-node/themes/ebook.js +0 -138
- package/vendor/symbiote-node/themes/grey.js +0 -137
- package/vendor/symbiote-node/themes/light.js +0 -137
- package/vendor/symbiote-node/themes/neon.js +0 -138
- package/vendor/symbiote-node/themes/pcb.js +0 -273
- package/vendor/symbiote-node/themes/synthwave.js +0 -137
- package/vendor/symbiote-node/toolbar/QuickToolbar/QuickToolbar.css.js +0 -86
- package/vendor/symbiote-node/toolbar/QuickToolbar/QuickToolbar.js +0 -128
- package/vendor/symbiote-node/toolbar/QuickToolbar/QuickToolbar.tpl.js +0 -29
|
@@ -1,341 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ai/replicate-lipsync — Replicate API lip-sync via Kling model
|
|
3
|
-
*
|
|
4
|
-
* Alternative lipsync provider using Replicate API.
|
|
5
|
-
* Model: kwaivgi/kling-lip-sync (~$0.014/second of output)
|
|
6
|
-
* Simpler than direct Kling API — just video URL + audio URL → result.
|
|
7
|
-
*
|
|
8
|
-
* Supports:
|
|
9
|
-
* - Single segment processing
|
|
10
|
-
* - Batch processing with worker pool
|
|
11
|
-
* - Tunnel validation (cloudflared/ngrok)
|
|
12
|
-
*
|
|
13
|
-
* Ported from Mr-Computer/modules/ai-music-video/src/services/replicate-lipsync.js
|
|
14
|
-
*
|
|
15
|
-
* @module agi-graph/packs/ai/replicate-lipsync
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
import { readFile, writeFile, mkdir } from 'fs/promises';
|
|
19
|
-
import { existsSync } from 'fs';
|
|
20
|
-
import { execSync } from 'child_process';
|
|
21
|
-
import path from 'path';
|
|
22
|
-
|
|
23
|
-
export default {
|
|
24
|
-
type: 'ai/replicate-lipsync',
|
|
25
|
-
category: 'ai',
|
|
26
|
-
icon: 'mic',
|
|
27
|
-
|
|
28
|
-
driver: {
|
|
29
|
-
description: 'Replicate API lipsync via kwaivgi/kling-lip-sync model',
|
|
30
|
-
inputs: [
|
|
31
|
-
{ name: 'videoUrl', type: 'string' },
|
|
32
|
-
{ name: 'audioPath', type: 'string' },
|
|
33
|
-
],
|
|
34
|
-
outputs: [
|
|
35
|
-
{ name: 'result', type: 'any' },
|
|
36
|
-
{ name: 'error', type: 'string' },
|
|
37
|
-
],
|
|
38
|
-
params: {
|
|
39
|
-
operation: { type: 'string', default: 'process', description: 'Operation: process | batch | validate-tunnel' },
|
|
40
|
-
replicateToken: { type: 'string', default: '', description: 'Replicate API token' },
|
|
41
|
-
publicBaseUrl: { type: 'string', default: '', description: 'Public URL for file server' },
|
|
42
|
-
outputDir: { type: 'string', default: '/tmp/replicate-lipsync', description: 'Output directory' },
|
|
43
|
-
segmentId: { type: 'string', default: '', description: 'Segment identifier' },
|
|
44
|
-
startTime: { type: 'number', default: 0, description: 'Audio start time (seconds)' },
|
|
45
|
-
endTime: { type: 'number', default: 0, description: 'Audio end time (seconds)' },
|
|
46
|
-
maxWaitMs: { type: 'int', default: 300000, description: 'Max poll wait time (ms)' },
|
|
47
|
-
// Batch params
|
|
48
|
-
segments: { type: 'any', default: null, description: 'Segments array' },
|
|
49
|
-
videoMap: { type: 'any', default: null, description: 'Map of promptId → videoUrl' },
|
|
50
|
-
concurrency: { type: 'int', default: 3, description: 'Max concurrent tasks' },
|
|
51
|
-
},
|
|
52
|
-
},
|
|
53
|
-
|
|
54
|
-
lifecycle: {
|
|
55
|
-
validate: (inputs, params) => {
|
|
56
|
-
if (params.operation === 'validate-tunnel') return !!params.publicBaseUrl;
|
|
57
|
-
if (!params.replicateToken) return false;
|
|
58
|
-
if (params.operation === 'process' && (!inputs.videoUrl || !inputs.audioPath)) return false;
|
|
59
|
-
if (params.operation === 'batch' && (!params.segments || !inputs.audioPath)) return false;
|
|
60
|
-
return true;
|
|
61
|
-
},
|
|
62
|
-
|
|
63
|
-
cacheKey: (inputs, params) => {
|
|
64
|
-
return `replicate:${params.operation}:${params.segmentId || ''}`;
|
|
65
|
-
},
|
|
66
|
-
|
|
67
|
-
execute: async (inputs, params) => {
|
|
68
|
-
try {
|
|
69
|
-
const op = params.operation;
|
|
70
|
-
|
|
71
|
-
if (op === 'validate-tunnel') {
|
|
72
|
-
const valid = await validateTunnel(params.publicBaseUrl);
|
|
73
|
-
return { result: { valid }, error: null };
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (op === 'process') {
|
|
77
|
-
const result = await processSegment(inputs, params);
|
|
78
|
-
return { result, error: null };
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (op === 'batch') {
|
|
82
|
-
const results = await processBatch(inputs, params);
|
|
83
|
-
return { result: { processed: results.size, results: Object.fromEntries(results) }, error: null };
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return { result: null, error: `Unknown operation: ${op}` };
|
|
87
|
-
} catch (err) {
|
|
88
|
-
return { result: null, error: err.message };
|
|
89
|
-
}
|
|
90
|
-
},
|
|
91
|
-
},
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
// --- Replicate API ---
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Create prediction on Replicate
|
|
98
|
-
* @param {string} videoUrl
|
|
99
|
-
* @param {string} audioUrl - Public URL or data URI
|
|
100
|
-
* @param {string} token
|
|
101
|
-
* @returns {Promise<Object>}
|
|
102
|
-
*/
|
|
103
|
-
async function createPrediction(videoUrl, audioUrl, token) {
|
|
104
|
-
const response = await fetch('https://api.replicate.com/v1/predictions', {
|
|
105
|
-
method: 'POST',
|
|
106
|
-
headers: {
|
|
107
|
-
'Authorization': `Bearer ${token}`,
|
|
108
|
-
'Content-Type': 'application/json',
|
|
109
|
-
'Prefer': 'wait',
|
|
110
|
-
},
|
|
111
|
-
body: JSON.stringify({
|
|
112
|
-
version: 'kwaivgi/kling-lip-sync',
|
|
113
|
-
input: {
|
|
114
|
-
video: videoUrl,
|
|
115
|
-
audio: audioUrl,
|
|
116
|
-
},
|
|
117
|
-
}),
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
if (!response.ok) {
|
|
121
|
-
const error = await response.text();
|
|
122
|
-
throw new Error(`Replicate API error ${response.status}: ${error}`);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
return await response.json();
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Poll for prediction completion
|
|
130
|
-
* @param {string} predictionId
|
|
131
|
-
* @param {string} token
|
|
132
|
-
* @param {number} maxWaitMs
|
|
133
|
-
* @returns {Promise<Object>}
|
|
134
|
-
*/
|
|
135
|
-
async function pollPrediction(predictionId, token, maxWaitMs = 300000) {
|
|
136
|
-
const startTime = Date.now();
|
|
137
|
-
const pollInterval = 5000;
|
|
138
|
-
|
|
139
|
-
while (Date.now() - startTime < maxWaitMs) {
|
|
140
|
-
const response = await fetch(`https://api.replicate.com/v1/predictions/${predictionId}`, {
|
|
141
|
-
headers: { 'Authorization': `Bearer ${token}` },
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
if (!response.ok) {
|
|
145
|
-
throw new Error(`Replicate poll error: ${response.status}`);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const prediction = await response.json();
|
|
149
|
-
|
|
150
|
-
if (prediction.status === 'succeeded') {
|
|
151
|
-
return prediction;
|
|
152
|
-
}
|
|
153
|
-
if (prediction.status === 'failed' || prediction.status === 'canceled') {
|
|
154
|
-
throw new Error(`Replicate prediction ${prediction.status}: ${prediction.error || 'Unknown'}`);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
throw new Error(`Replicate prediction timed out after ${maxWaitMs / 1000}s`);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Download result video
|
|
165
|
-
* @param {string} videoUrl
|
|
166
|
-
* @param {string} outputPath
|
|
167
|
-
* @returns {Promise<string>}
|
|
168
|
-
*/
|
|
169
|
-
async function downloadResult(videoUrl, outputPath) {
|
|
170
|
-
const response = await fetch(videoUrl);
|
|
171
|
-
if (!response.ok) {
|
|
172
|
-
throw new Error(`Failed to download: ${response.status}`);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
const buffer = await response.arrayBuffer();
|
|
176
|
-
await writeFile(outputPath, Buffer.from(buffer));
|
|
177
|
-
return outputPath;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// --- Utilities ---
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Convert file to data URI
|
|
184
|
-
* @param {string} filePath
|
|
185
|
-
* @returns {Promise<string>}
|
|
186
|
-
*/
|
|
187
|
-
async function fileToDataUri(filePath) {
|
|
188
|
-
const buffer = await readFile(path.resolve(filePath));
|
|
189
|
-
const ext = path.extname(filePath).toLowerCase();
|
|
190
|
-
const mimeTypes = {
|
|
191
|
-
'.mp3': 'audio/mpeg',
|
|
192
|
-
'.wav': 'audio/wav',
|
|
193
|
-
'.mp4': 'video/mp4',
|
|
194
|
-
'.webm': 'video/webm',
|
|
195
|
-
};
|
|
196
|
-
const mime = mimeTypes[ext] || 'application/octet-stream';
|
|
197
|
-
return `data:${mime};base64,${buffer.toString('base64')}`;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Extract audio clip using FFmpeg
|
|
202
|
-
* @param {string} audioPath
|
|
203
|
-
* @param {number} startTime
|
|
204
|
-
* @param {number} endTime
|
|
205
|
-
* @param {string} outputPath
|
|
206
|
-
* @returns {string}
|
|
207
|
-
*/
|
|
208
|
-
function extractAudioClip(audioPath, startTime, endTime, outputPath) {
|
|
209
|
-
if (existsSync(outputPath)) return outputPath;
|
|
210
|
-
|
|
211
|
-
const duration = endTime - startTime;
|
|
212
|
-
const cmd = `ffmpeg -y -i "${path.resolve(audioPath)}" -ss ${startTime.toFixed(3)} -t ${duration.toFixed(3)} ` +
|
|
213
|
-
`-c:a libmp3lame -q:a 2 "${outputPath}" 2>/dev/null`;
|
|
214
|
-
|
|
215
|
-
execSync(cmd, { stdio: 'pipe' });
|
|
216
|
-
return outputPath;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Get public URL for local file
|
|
221
|
-
* @param {string} filePath
|
|
222
|
-
* @param {string} publicBaseUrl
|
|
223
|
-
* @returns {string}
|
|
224
|
-
*/
|
|
225
|
-
function getPublicUrl(filePath, publicBaseUrl) {
|
|
226
|
-
const absPath = path.resolve(filePath);
|
|
227
|
-
return `${publicBaseUrl}/${encodeURIComponent(absPath)}`;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* Validate tunnel accessibility
|
|
232
|
-
* @param {string} publicBaseUrl
|
|
233
|
-
* @returns {Promise<boolean>}
|
|
234
|
-
*/
|
|
235
|
-
async function validateTunnel(publicBaseUrl) {
|
|
236
|
-
try {
|
|
237
|
-
const controller = new AbortController();
|
|
238
|
-
const timeout = setTimeout(() => controller.abort(), 10000);
|
|
239
|
-
const response = await fetch(publicBaseUrl, { signal: controller.signal });
|
|
240
|
-
clearTimeout(timeout);
|
|
241
|
-
return response.status < 500;
|
|
242
|
-
} catch {
|
|
243
|
-
return false;
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// --- Processing pipeline ---
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* Process single segment
|
|
251
|
-
* @param {Object} inputs
|
|
252
|
-
* @param {Object} params
|
|
253
|
-
* @returns {Promise<Object>}
|
|
254
|
-
*/
|
|
255
|
-
async function processSegment(inputs, params) {
|
|
256
|
-
const { segmentId, startTime, endTime, outputDir, replicateToken, publicBaseUrl } = params;
|
|
257
|
-
|
|
258
|
-
const lipsyncDir = path.join(outputDir, 'lipsync-videos');
|
|
259
|
-
const clipsDir = path.join(outputDir, 'audio-clips');
|
|
260
|
-
await mkdir(lipsyncDir, { recursive: true });
|
|
261
|
-
await mkdir(clipsDir, { recursive: true });
|
|
262
|
-
|
|
263
|
-
const outputPath = path.join(lipsyncDir, `${segmentId}.mp4`);
|
|
264
|
-
if (existsSync(outputPath)) {
|
|
265
|
-
return { videoPath: outputPath, cached: true };
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// Extract audio clip
|
|
269
|
-
const clipPath = path.join(clipsDir, `${segmentId}.mp3`);
|
|
270
|
-
extractAudioClip(inputs.audioPath, startTime, endTime, clipPath);
|
|
271
|
-
|
|
272
|
-
// Get audio data URI or public URL
|
|
273
|
-
let audioUrl;
|
|
274
|
-
if (publicBaseUrl) {
|
|
275
|
-
audioUrl = getPublicUrl(clipPath, publicBaseUrl);
|
|
276
|
-
} else {
|
|
277
|
-
audioUrl = await fileToDataUri(clipPath);
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// Create prediction
|
|
281
|
-
const prediction = await createPrediction(inputs.videoUrl, audioUrl, replicateToken);
|
|
282
|
-
|
|
283
|
-
// Poll if not already complete
|
|
284
|
-
let result = prediction;
|
|
285
|
-
if (prediction.status !== 'succeeded') {
|
|
286
|
-
result = await pollPrediction(prediction.id, replicateToken, params.maxWaitMs);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
// Download result
|
|
290
|
-
const resultUrl = result.output;
|
|
291
|
-
if (!resultUrl) {
|
|
292
|
-
throw new Error('No output URL in prediction result');
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
await downloadResult(resultUrl, outputPath);
|
|
296
|
-
return { videoPath: outputPath, predictionId: prediction.id, cached: false };
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
/**
|
|
300
|
-
* Process batch with worker pool
|
|
301
|
-
* @param {Object} inputs
|
|
302
|
-
* @param {Object} params
|
|
303
|
-
* @returns {Promise<Map>}
|
|
304
|
-
*/
|
|
305
|
-
async function processBatch(inputs, params) {
|
|
306
|
-
const segments = params.segments;
|
|
307
|
-
const videoMap = params.videoMap || {};
|
|
308
|
-
const concurrency = params.concurrency;
|
|
309
|
-
const results = new Map();
|
|
310
|
-
const queue = [...segments];
|
|
311
|
-
|
|
312
|
-
const workers = Array(Math.min(concurrency, queue.length)).fill(null).map(async () => {
|
|
313
|
-
while (queue.length > 0) {
|
|
314
|
-
const segment = queue.shift();
|
|
315
|
-
if (!segment) break;
|
|
316
|
-
|
|
317
|
-
const videoUrl = videoMap[segment.promptId];
|
|
318
|
-
if (!videoUrl) continue;
|
|
319
|
-
|
|
320
|
-
try {
|
|
321
|
-
const segParams = {
|
|
322
|
-
...params,
|
|
323
|
-
segmentId: segment.promptId,
|
|
324
|
-
startTime: segment.start,
|
|
325
|
-
endTime: segment.end,
|
|
326
|
-
};
|
|
327
|
-
|
|
328
|
-
const result = await processSegment(
|
|
329
|
-
{ videoUrl, audioPath: inputs.audioPath },
|
|
330
|
-
segParams,
|
|
331
|
-
);
|
|
332
|
-
results.set(segment.promptId, result.videoPath);
|
|
333
|
-
} catch (error) {
|
|
334
|
-
console.error(`[Replicate] Failed: ${segment.promptId} - ${error.message}`);
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
await Promise.all(workers);
|
|
340
|
-
return results;
|
|
341
|
-
}
|
|
@@ -1,241 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ai/tts — Text-to-Speech via Qwen3-TTS
|
|
3
|
-
*
|
|
4
|
-
* Two modes:
|
|
5
|
-
* - SSH: batch script on remote server (mr-agent.rnd-pro.com)
|
|
6
|
-
* - HTTP: POST to Qwen3 TTS HTTP endpoint
|
|
7
|
-
*
|
|
8
|
-
* Supports:
|
|
9
|
-
* - Built-in speakers: ryan, vivian, aiden, dylan, eric, serena, sohee, chelsie, etc.
|
|
10
|
-
* - Voice cloning via ref_audio (reference audio sample)
|
|
11
|
-
* - Language: es (Spanish/Rioplatense), ru (Russian), en (English)
|
|
12
|
-
*
|
|
13
|
-
* Config from Mr-Computer/automations/argentine-spanish-bot:
|
|
14
|
-
* TTS_SERVER_URL: http://localhost:5008
|
|
15
|
-
* TTS_VENV_PATH: /home/mr-agent/automations/argentine-spanish-bot/venv
|
|
16
|
-
* Batch script: utils/generate_qwen3tts_batch.py
|
|
17
|
-
*
|
|
18
|
-
* @module agi-graph/packs/ai/tts
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
import { execSync } from 'child_process';
|
|
22
|
-
import { promises as fs } from 'fs';
|
|
23
|
-
import path from 'path';
|
|
24
|
-
import os from 'os';
|
|
25
|
-
|
|
26
|
-
export default {
|
|
27
|
-
type: 'ai/tts',
|
|
28
|
-
category: 'ai',
|
|
29
|
-
icon: 'record_voice_over',
|
|
30
|
-
|
|
31
|
-
driver: {
|
|
32
|
-
description: 'Text-to-Speech via Qwen3-TTS (SSH batch or HTTP)',
|
|
33
|
-
inputs: [
|
|
34
|
-
{ name: 'text', type: 'string' },
|
|
35
|
-
],
|
|
36
|
-
outputs: [
|
|
37
|
-
{ name: 'audioPath', type: 'string' },
|
|
38
|
-
{ name: 'error', type: 'string' },
|
|
39
|
-
],
|
|
40
|
-
params: {
|
|
41
|
-
mode: { type: 'string', default: 'http', description: 'ssh | http' },
|
|
42
|
-
language: { type: 'string', default: 'es', description: 'Language: es, ru, en' },
|
|
43
|
-
speaker: { type: 'string', default: 'vivian', description: 'Built-in Qwen3 speaker ID' },
|
|
44
|
-
refAudio: { type: 'string', default: '', description: 'Path to voice reference audio (clone mode)' },
|
|
45
|
-
outputDir: { type: 'string', default: '', description: 'Output directory for generated audio' },
|
|
46
|
-
outputFormat: { type: 'string', default: 'wav', description: 'wav | mp3' },
|
|
47
|
-
exaggeration: { type: 'number', default: 0, description: 'Voice exaggeration (0-1)' },
|
|
48
|
-
cfg: { type: 'number', default: 0.1, description: 'Classifier-free guidance (0-1)' },
|
|
49
|
-
// SSH params
|
|
50
|
-
remoteHost: { type: 'string', default: 'mr-agent@mr-agent.rnd-pro.com', description: 'SSH host' },
|
|
51
|
-
remotePath: { type: 'string', default: '/home/mr-agent/automations/argentine-spanish-bot', description: 'Remote project path' },
|
|
52
|
-
remoteVenv: { type: 'string', default: '/home/mr-agent/automations/argentine-spanish-bot/venv', description: 'Remote Python venv path' },
|
|
53
|
-
device: { type: 'string', default: 'cuda', description: 'cuda | cpu' },
|
|
54
|
-
// HTTP params
|
|
55
|
-
endpoint: { type: 'string', default: 'http://localhost:5008', description: 'TTS HTTP endpoint' },
|
|
56
|
-
timeout: { type: 'int', default: 120000, description: 'Max wait time (ms)' },
|
|
57
|
-
},
|
|
58
|
-
},
|
|
59
|
-
|
|
60
|
-
lifecycle: {
|
|
61
|
-
validate: (inputs) => {
|
|
62
|
-
if (!inputs.text) return false;
|
|
63
|
-
return true;
|
|
64
|
-
},
|
|
65
|
-
|
|
66
|
-
cacheKey: (inputs, params) =>
|
|
67
|
-
`tts:${params.mode}:${params.speaker}:${params.language}:${inputs.text}`,
|
|
68
|
-
|
|
69
|
-
execute: async (inputs, params) => {
|
|
70
|
-
const { text } = inputs;
|
|
71
|
-
const mode = params.mode || 'http';
|
|
72
|
-
|
|
73
|
-
if (mode === 'ssh') {
|
|
74
|
-
return executeSSH(text, params);
|
|
75
|
-
}
|
|
76
|
-
return executeHTTP(text, params);
|
|
77
|
-
},
|
|
78
|
-
},
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Qwen3-TTS built-in speaker IDs
|
|
83
|
-
* @type {Set<string>}
|
|
84
|
-
*/
|
|
85
|
-
const SPEAKERS = new Set([
|
|
86
|
-
'aiden', 'dylan', 'eric', 'ono_anna', 'ryan',
|
|
87
|
-
'serena', 'sohee', 'uncle_fu', 'vivian', 'chelsie',
|
|
88
|
-
]);
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* SSH mode: write batch JSON → scp → remote python exec → scp result back
|
|
92
|
-
* @param {string} text - Text to synthesize
|
|
93
|
-
* @param {Object} params - Node params
|
|
94
|
-
* @returns {Promise<Object>}
|
|
95
|
-
*/
|
|
96
|
-
async function executeSSH(text, params) {
|
|
97
|
-
const host = params.remoteHost || process.env.WHISPER_REMOTE_HOST || 'mr-agent@mr-agent.rnd-pro.com';
|
|
98
|
-
const remotePath = params.remotePath || process.env.WHISPER_REMOTE_PATH || '/home/mr-agent/automations/argentine-spanish-bot';
|
|
99
|
-
const venv = params.remoteVenv || process.env.TTS_VENV_PATH || `${remotePath}/venv`;
|
|
100
|
-
const device = params.device || process.env.PODCAST_TTS_DEVICE || 'cuda';
|
|
101
|
-
|
|
102
|
-
const outDir = params.outputDir || path.join(os.tmpdir(), 'agi-graph-tts');
|
|
103
|
-
const taskId = `tts_${Date.now()}`;
|
|
104
|
-
const localWav = path.join(outDir, `${taskId}.wav`);
|
|
105
|
-
const remoteTmpDir = '/tmp/agi-graph-tts';
|
|
106
|
-
|
|
107
|
-
try {
|
|
108
|
-
await fs.mkdir(outDir, { recursive: true });
|
|
109
|
-
|
|
110
|
-
// Build batch task
|
|
111
|
-
const batchTask = [{
|
|
112
|
-
id: taskId,
|
|
113
|
-
text,
|
|
114
|
-
lang: params.language || 'es',
|
|
115
|
-
prompt: params.refAudio || null,
|
|
116
|
-
out: `${remoteTmpDir}/${taskId}.wav`,
|
|
117
|
-
exaggeration: params.exaggeration ?? 0,
|
|
118
|
-
cfg: params.cfg ?? 0.1,
|
|
119
|
-
}];
|
|
120
|
-
|
|
121
|
-
// If using built-in speaker (no refAudio), add speaker param
|
|
122
|
-
if (!params.refAudio && SPEAKERS.has(params.speaker)) {
|
|
123
|
-
batchTask[0].speaker = params.speaker;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Write local batch file
|
|
127
|
-
const batchFile = path.join(outDir, `${taskId}_batch.json`);
|
|
128
|
-
await fs.writeFile(batchFile, JSON.stringify(batchTask, null, 2));
|
|
129
|
-
|
|
130
|
-
// Ensure remote dir + upload batch
|
|
131
|
-
execSync(`ssh ${host} "mkdir -p ${remoteTmpDir}"`, {
|
|
132
|
-
encoding: 'utf-8', stdio: 'pipe', timeout: 10000,
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
const remoteBatch = `${remoteTmpDir}/${taskId}_batch.json`;
|
|
136
|
-
execSync(`scp "${batchFile}" "${host}:${remoteBatch}"`, {
|
|
137
|
-
encoding: 'utf-8', stdio: 'pipe', timeout: 30000,
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
try {
|
|
141
|
-
// Run batch script
|
|
142
|
-
const pythonCmd = `${venv}/bin/python`;
|
|
143
|
-
const scriptPath = `${remotePath}/utils/generate_qwen3tts_batch.py`;
|
|
144
|
-
const cmd = `source "${venv}/bin/activate" && "${pythonCmd}" "${scriptPath}" --batch "${remoteBatch}" --device "${device}"`;
|
|
145
|
-
|
|
146
|
-
execSync(`ssh ${host} '${cmd}'`, {
|
|
147
|
-
encoding: 'utf-8',
|
|
148
|
-
maxBuffer: 50 * 1024 * 1024,
|
|
149
|
-
timeout: params.timeout || 120000,
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
// Download result
|
|
153
|
-
const remoteOut = `${remoteTmpDir}/${taskId}.wav`;
|
|
154
|
-
execSync(`scp "${host}:${remoteOut}" "${localWav}"`, {
|
|
155
|
-
encoding: 'utf-8', stdio: 'pipe', timeout: 30000,
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
// Cleanup batch + remote output
|
|
159
|
-
await fs.unlink(batchFile).catch(() => { });
|
|
160
|
-
execSync(`ssh ${host} "rm -f ${remoteBatch} ${remoteOut}"`, {
|
|
161
|
-
encoding: 'utf-8', stdio: 'pipe', timeout: 5000,
|
|
162
|
-
}).toString();
|
|
163
|
-
|
|
164
|
-
return { audioPath: localWav, error: null };
|
|
165
|
-
|
|
166
|
-
} catch (err) {
|
|
167
|
-
await fs.unlink(batchFile).catch(() => { });
|
|
168
|
-
return { audioPath: null, error: err.message };
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
} catch (err) {
|
|
172
|
-
return { audioPath: null, error: err.message };
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* HTTP mode: POST to Qwen3 TTS endpoint
|
|
178
|
-
* @param {string} text - Text to synthesize
|
|
179
|
-
* @param {Object} params - Node params
|
|
180
|
-
* @returns {Promise<Object>}
|
|
181
|
-
*/
|
|
182
|
-
async function executeHTTP(text, params) {
|
|
183
|
-
const endpoint = params.endpoint || process.env.TTS_SERVER_URL || 'http://localhost:5008';
|
|
184
|
-
const outDir = params.outputDir || path.join(os.tmpdir(), 'agi-graph-tts');
|
|
185
|
-
const taskId = `tts_${Date.now()}`;
|
|
186
|
-
const outputPath = path.join(outDir, `${taskId}.wav`);
|
|
187
|
-
|
|
188
|
-
try {
|
|
189
|
-
await fs.mkdir(outDir, { recursive: true });
|
|
190
|
-
|
|
191
|
-
const body = {
|
|
192
|
-
text,
|
|
193
|
-
language: params.language || 'es',
|
|
194
|
-
speaker: params.speaker || 'vivian',
|
|
195
|
-
exaggeration: params.exaggeration ?? 0,
|
|
196
|
-
cfg: params.cfg ?? 0.1,
|
|
197
|
-
};
|
|
198
|
-
|
|
199
|
-
// Add ref_audio for voice cloning
|
|
200
|
-
if (params.refAudio) {
|
|
201
|
-
const refBuffer = await fs.readFile(params.refAudio);
|
|
202
|
-
body.ref_audio = refBuffer.toString('base64');
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
const response = await fetch(`${endpoint}/synthesize`, {
|
|
206
|
-
method: 'POST',
|
|
207
|
-
headers: { 'Content-Type': 'application/json' },
|
|
208
|
-
body: JSON.stringify(body),
|
|
209
|
-
signal: AbortSignal.timeout(params.timeout || 120000),
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
if (!response.ok) {
|
|
213
|
-
return { audioPath: null, error: `TTS API error: ${response.status}` };
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Response is audio binary
|
|
217
|
-
const contentType = response.headers.get('content-type') || '';
|
|
218
|
-
|
|
219
|
-
if (contentType.includes('audio') || contentType.includes('octet-stream')) {
|
|
220
|
-
const buffer = Buffer.from(await response.arrayBuffer());
|
|
221
|
-
await fs.writeFile(outputPath, buffer);
|
|
222
|
-
return { audioPath: outputPath, error: null };
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// JSON response with file path
|
|
226
|
-
const result = await response.json();
|
|
227
|
-
if (result.audio_path) {
|
|
228
|
-
return { audioPath: result.audio_path, error: null };
|
|
229
|
-
}
|
|
230
|
-
if (result.audio) {
|
|
231
|
-
const buffer = Buffer.from(result.audio, 'base64');
|
|
232
|
-
await fs.writeFile(outputPath, buffer);
|
|
233
|
-
return { audioPath: outputPath, error: null };
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
return { audioPath: null, error: 'Unexpected TTS response format' };
|
|
237
|
-
|
|
238
|
-
} catch (err) {
|
|
239
|
-
return { audioPath: null, error: err.message };
|
|
240
|
-
}
|
|
241
|
-
}
|