@renoise/video-maker 0.1.3 → 0.2.1
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/.claude-plugin/marketplace.json +15 -0
- package/.claude-plugin/plugin.json +21 -3
- package/README.md +25 -30
- package/hooks/check-api-key.sh +28 -0
- package/hooks/hooks.json +12 -0
- package/hooks/session-start.sh +30 -7
- package/openclaw.plugin.json +5 -3
- package/package.json +4 -9
- package/skills/director/SKILL.md +4 -7
- package/skills/file-upload/SKILL.md +79 -0
- package/skills/file-upload/scripts/upload.mjs +103 -0
- package/skills/gemini-gen/SKILL.md +236 -0
- package/skills/gemini-gen/scripts/gemini.mjs +220 -0
- package/skills/renoise-gen/SKILL.md +3 -1
- package/skills/renoise-gen/references/api-endpoints.md +37 -33
- package/skills/short-film-editor/SKILL.md +23 -24
- package/skills/short-film-editor/references/continuity-guide.md +2 -2
- package/skills/tiktok-content-maker/SKILL.md +78 -81
- package/skills/tiktok-content-maker/examples/dress-demo.md +42 -42
- package/skills/tiktok-content-maker/references/ecom-prompt-guide.md +157 -152
- package/skills/video-download/SKILL.md +1 -1
- package/lib/gemini.ts +0 -49
- package/skills/short-film-editor/scripts/generate-storyboard-html.ts +0 -714
- package/skills/tiktok-content-maker/scripts/analyze-images.ts +0 -122
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "renoise-plugins-official",
|
|
3
|
+
"owner": {
|
|
4
|
+
"name": "Renoise"
|
|
5
|
+
},
|
|
6
|
+
"description": "Official Claude Code plugins by Renoise",
|
|
7
|
+
"plugins": [
|
|
8
|
+
{
|
|
9
|
+
"name": "video-maker",
|
|
10
|
+
"version": "0.2.0",
|
|
11
|
+
"source": "./",
|
|
12
|
+
"description": "AI video production skills — creative direction, generation, editing, and e-commerce content"
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
}
|
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "video-maker",
|
|
3
|
-
"description": "
|
|
4
|
-
"version": "0.
|
|
5
|
-
|
|
3
|
+
"description": "AI video production skills — creative direction, generation, editing, and e-commerce content",
|
|
4
|
+
"version": "0.2.0",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Renoise"
|
|
7
|
+
},
|
|
8
|
+
"homepage": "https://github.com/ArcoCodes/renoise-plugins-official",
|
|
9
|
+
"repository": "https://github.com/ArcoCodes/renoise-plugins-official",
|
|
10
|
+
"keywords": [
|
|
11
|
+
"skills",
|
|
12
|
+
"video",
|
|
13
|
+
"ai-video",
|
|
14
|
+
"creative-direction",
|
|
15
|
+
"ecommerce",
|
|
16
|
+
"tiktok",
|
|
17
|
+
"renoise"
|
|
18
|
+
],
|
|
19
|
+
"commands": [
|
|
20
|
+
"./commands/setup.md",
|
|
21
|
+
"./commands/add-credits.md"
|
|
22
|
+
]
|
|
23
|
+
}
|
package/README.md
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
|
-
#
|
|
1
|
+
# renoise-plugins-official
|
|
2
2
|
|
|
3
|
-
AI video production
|
|
3
|
+
AI video production skills by Renoise — creative direction, generation, editing, and e-commerce content.
|
|
4
|
+
|
|
5
|
+
## Skills
|
|
6
|
+
|
|
7
|
+
| Skill | Description |
|
|
8
|
+
|-------|-------------|
|
|
9
|
+
| **director** | Creative director — main entry point for all video requests |
|
|
10
|
+
| **gemini-gen** | Visual understanding & multimodal analysis via Gemini 3.1 Pro |
|
|
11
|
+
| **renoise-gen** | AI video & image generation engine (renoise-cli) |
|
|
12
|
+
| **tiktok-content-maker** | TikTok & e-commerce short video specialist |
|
|
13
|
+
| **scene-generate** | Background/environment image generation |
|
|
14
|
+
| **product-sheet-generate** | Multi-angle product design sheet |
|
|
15
|
+
| **short-film-editor** | Short film & drama editing |
|
|
16
|
+
| **video-download** | Video downloader (yt-dlp) |
|
|
17
|
+
| **file-upload** | Upload files to Renoise for use with gemini-gen |
|
|
4
18
|
|
|
5
19
|
## Installation
|
|
6
20
|
|
|
@@ -24,35 +38,16 @@ claude plugin install video-maker@renoise-plugins-official
|
|
|
24
38
|
openclaw plugins install @renoise/video-maker
|
|
25
39
|
```
|
|
26
40
|
|
|
27
|
-
|
|
41
|
+
3. Launch Claude Code and run the setup command to connect your Renoise account:
|
|
28
42
|
|
|
29
|
-
|
|
43
|
+
```
|
|
44
|
+
/video-maker:setup
|
|
45
|
+
```
|
|
30
46
|
|
|
31
|
-
|
|
32
|
-
2. **Suggests** 2-3 style directions tailored to your project
|
|
33
|
-
3. **Generates** a complete video prompt, dialogue, and BGM plan
|
|
34
|
-
4. **Submits** to Renoise for AI video generation
|
|
35
|
-
5. **Learns** your preferences over time for better suggestions
|
|
47
|
+
This will guide you through connecting your API key and enabling the real-time credit balance display in the status bar.
|
|
36
48
|
|
|
37
|
-
##
|
|
49
|
+
## Environment Variables
|
|
38
50
|
|
|
39
|
-
|
|
|
40
|
-
|
|
41
|
-
|
|
|
42
|
-
| renoise-gen | AI video & image generation engine (CLI) |
|
|
43
|
-
| content-maker | TikTok e-commerce short video specialist |
|
|
44
|
-
| scene-generate | Background/environment image generation (Gemini) |
|
|
45
|
-
| product-sheet-generate | Multi-angle product design sheet (Gemini) |
|
|
46
|
-
| video-download | Video downloader (yt-dlp) |
|
|
47
|
-
|
|
48
|
-
## Adding New Verticals
|
|
49
|
-
|
|
50
|
-
Create a new skill directory with a `SKILL.md` that includes a `categories` field in its frontmatter. The Director automatically discovers and routes to it — no other changes needed.
|
|
51
|
-
|
|
52
|
-
```yaml
|
|
53
|
-
---
|
|
54
|
-
name: my-vertical
|
|
55
|
-
description: What this vertical handles
|
|
56
|
-
categories: [drama, storytelling]
|
|
57
|
-
---
|
|
58
|
-
```
|
|
51
|
+
| Variable | Required By | Description |
|
|
52
|
+
|----------|------------|-------------|
|
|
53
|
+
| `RENOISE_API_KEY` | All skills | Renoise API credential. Get one at https://www.renoise.ai |
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
# PreToolUse hook: block when neither RENOISE_API_KEY nor RENOISE_AUTH_TOKEN is set
|
|
4
|
+
# and the tool invocation looks like a renoise-cli call.
|
|
5
|
+
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
# Read the tool input from stdin
|
|
9
|
+
INPUT=$(cat)
|
|
10
|
+
|
|
11
|
+
# Extract the command being executed
|
|
12
|
+
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
|
|
13
|
+
|
|
14
|
+
# Only check if the command involves renoise-cli
|
|
15
|
+
if [[ "$COMMAND" != *renoise-cli* ]]; then
|
|
16
|
+
exit 0
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
# If either credential is configured, allow
|
|
20
|
+
if [ -n "${RENOISE_API_KEY:-}" ] || [ -n "${RENOISE_AUTH_TOKEN:-}" ]; then
|
|
21
|
+
exit 0
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
# Block and guide user
|
|
25
|
+
jq -n '{
|
|
26
|
+
decision: "block",
|
|
27
|
+
reason: "RENOISE_API_KEY or RENOISE_AUTH_TOKEN is not set. Add one to the env block in .claude/settings.local.json. Get your key at https://www.renoise.ai"
|
|
28
|
+
}'
|
package/hooks/hooks.json
CHANGED
package/hooks/session-start.sh
CHANGED
|
@@ -1,17 +1,40 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
|
|
3
|
-
# SessionStart hook:
|
|
3
|
+
# SessionStart hook: guide users based on their setup state.
|
|
4
|
+
# - No API key → prompt to login
|
|
5
|
+
# - Has API key but no statusLine → prompt to enable credits display
|
|
4
6
|
|
|
5
7
|
set -euo pipefail
|
|
6
8
|
|
|
7
|
-
#
|
|
8
|
-
|
|
9
|
+
# Consume stdin (SessionStart sends context JSON, we don't need it)
|
|
10
|
+
cat > /dev/null
|
|
11
|
+
|
|
12
|
+
HAS_KEY=false
|
|
13
|
+
HAS_STATUSLINE=false
|
|
14
|
+
|
|
15
|
+
# Check if API key is configured
|
|
16
|
+
if [ -n "${RENOISE_API_KEY:-}" ] || [ -n "${RENOISE_AUTH_TOKEN:-}" ]; then
|
|
17
|
+
HAS_KEY=true
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
# Check if statusLine is pointing to our script
|
|
21
|
+
SETTINGS_FILE="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/settings.json"
|
|
22
|
+
if [ -f "$SETTINGS_FILE" ] && grep -q "video-maker" "$SETTINGS_FILE" 2>/dev/null && grep -q "statusLine" "$SETTINGS_FILE" 2>/dev/null; then
|
|
23
|
+
HAS_STATUSLINE=true
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
# Case 1: New user — no API key
|
|
27
|
+
if [ "$HAS_KEY" = false ]; then
|
|
28
|
+
# stdout goes to Claude as context — Claude will relay the message to the user
|
|
29
|
+
echo '[Renoise Video Maker] Plugin installed successfully, but Renoise account is not connected yet. Tell the user: type /video-maker:setup to connect your account. One sentence only.'
|
|
9
30
|
exit 0
|
|
10
31
|
fi
|
|
11
32
|
|
|
12
|
-
#
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
33
|
+
# Case 2: Existing user updated plugin — has key but no statusLine
|
|
34
|
+
if [ "$HAS_STATUSLINE" = false ]; then
|
|
35
|
+
echo '[Renoise Video Maker] Plugin updated — new feature: real-time credit balance in status bar. Tell the user: type /video-maker:setup to activate. One sentence only.'
|
|
36
|
+
exit 0
|
|
37
|
+
fi
|
|
16
38
|
|
|
39
|
+
# Case 3: Everything configured — silent
|
|
17
40
|
exit 0
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "video-maker",
|
|
3
3
|
"name": "Video Maker",
|
|
4
|
-
"description": "
|
|
5
|
-
"version": "0.
|
|
4
|
+
"description": "AI video production skills — creative direction, generation, editing, and e-commerce content",
|
|
5
|
+
"version": "0.2.0",
|
|
6
6
|
"configSchema": {
|
|
7
7
|
"type": "object",
|
|
8
8
|
"additionalProperties": false,
|
|
@@ -10,11 +10,13 @@
|
|
|
10
10
|
},
|
|
11
11
|
"skills": [
|
|
12
12
|
"skills/director",
|
|
13
|
+
"skills/gemini-gen",
|
|
13
14
|
"skills/renoise-gen",
|
|
14
15
|
"skills/tiktok-content-maker",
|
|
15
16
|
"skills/scene-generate",
|
|
16
17
|
"skills/product-sheet-generate",
|
|
18
|
+
"skills/short-film-editor",
|
|
17
19
|
"skills/video-download",
|
|
18
|
-
"skills/
|
|
20
|
+
"skills/file-upload"
|
|
19
21
|
]
|
|
20
22
|
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@renoise/video-maker",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"description": "
|
|
6
|
+
"description": "AI video production skills — creative direction, generation, editing, and e-commerce content",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
|
-
"url": "https://github.com/ArcoCodes/renoise-plugins-official.git"
|
|
10
|
-
"directory": "video-maker"
|
|
9
|
+
"url": "https://github.com/ArcoCodes/renoise-plugins-official.git"
|
|
11
10
|
},
|
|
12
11
|
"openclaw": {
|
|
13
12
|
"extensions": ["./index.mjs"]
|
|
@@ -17,11 +16,7 @@
|
|
|
17
16
|
"openclaw.plugin.json",
|
|
18
17
|
"index.mjs",
|
|
19
18
|
"hooks/",
|
|
20
|
-
"lib/",
|
|
21
19
|
"skills/",
|
|
22
20
|
"README.md"
|
|
23
|
-
]
|
|
24
|
-
"dependencies": {
|
|
25
|
-
"@google/generative-ai": "^0.24.1"
|
|
26
|
-
}
|
|
21
|
+
]
|
|
27
22
|
}
|
package/skills/director/SKILL.md
CHANGED
|
@@ -6,8 +6,7 @@ description: >
|
|
|
6
6
|
directions, generates video prompts, and submits video tasks. Use when
|
|
7
7
|
user says "make a video", "video idea", "creative direction", "help me
|
|
8
8
|
shoot", "I want a video", "video script", "storyboard", "generate video",
|
|
9
|
-
"action sequence", "
|
|
10
|
-
"生成视频". Do NOT use for downloading videos or editing existing footage.
|
|
9
|
+
"action sequence", "generate video". Do NOT use for downloading videos or editing existing footage.
|
|
11
10
|
This skill is the ONLY entry point for video creation in the Visiono project.
|
|
12
11
|
allowed-tools: Bash, Read
|
|
13
12
|
metadata:
|
|
@@ -23,6 +22,7 @@ You are a creative director for AI video production. You guide users from raw id
|
|
|
23
22
|
|
|
24
23
|
## Critical Rules
|
|
25
24
|
|
|
25
|
+
- **The Renoise platform URL is https://www.renoise.ai** — NEVER say or link to "renoise.com". The correct domain is `renoise.ai`.
|
|
26
26
|
- **You are the default entry point** for ALL video creation requests. Only route to specialized skills when `metadata.tags` clearly match.
|
|
27
27
|
- **Video prompts must be in English** — the model understands English best.
|
|
28
28
|
- **Dialogue must feel natural** — conversational American English, never salesy or translated.
|
|
@@ -53,10 +53,7 @@ You are a creative director for AI video production. You guide users from raw id
|
|
|
53
53
|
- What materials does the user have? (product photos, character refs, scripts, nothing)
|
|
54
54
|
- What's the intended platform/audience? (TikTok, Instagram, YouTube, general)
|
|
55
55
|
|
|
56
|
-
5. **If user provided product images**, analyze them
|
|
57
|
-
```bash
|
|
58
|
-
cd ${CLAUDE_PLUGIN_ROOT} && npm install --silent && npx tsx ${CLAUDE_PLUGIN_ROOT}/skills/tiktok-content-maker/scripts/analyze-images.ts <product-image> [model-image]
|
|
59
|
-
```
|
|
56
|
+
5. **If user provided product images**, analyze them using the `gemini-gen` skill — send product image(s) with a prompt to extract product type, colors, material, selling points, brand tone, and scene suggestions.
|
|
60
57
|
|
|
61
58
|
6. **Present a brief summary**: "Here's what I understand: [product/story/concept]. I'll use [capabilities]. Let me suggest some creative directions."
|
|
62
59
|
|
|
@@ -226,7 +223,7 @@ Read that skill's SKILL.md and follow its workflow from the appropriate phase (s
|
|
|
226
223
|
|
|
227
224
|
### Example 1: Product video (common)
|
|
228
225
|
User: "I have photos of my new sneakers, help me make a video"
|
|
229
|
-
1. Phase 1: Analyze sneaker images via
|
|
226
|
+
1. Phase 1: Analyze sneaker images via `gemini-gen` skill → extract product type, colors, selling points
|
|
230
227
|
2. Phase 2: Suggest Minimal Showcase / Dynamic Sports / Lifestyle Vlog with adapted descriptions
|
|
231
228
|
3. User picks "Dynamic Sports"
|
|
232
229
|
4. Phase 3: Generate 15s video prompt with fast tracking, high-energy BGM, beat-synced cuts
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: file-upload
|
|
3
|
+
description: >
|
|
4
|
+
Upload files (images, videos) to Renoise and get a file URI for use with gemini-gen.
|
|
5
|
+
Use when a file exceeds 20MB inline base64 limit, or when you need to reuse
|
|
6
|
+
the same file across multiple gemini-gen calls without re-encoding.
|
|
7
|
+
allowed-tools: Bash, Read
|
|
8
|
+
metadata:
|
|
9
|
+
author: renoise
|
|
10
|
+
version: 0.1.0
|
|
11
|
+
category: utility
|
|
12
|
+
tags: [upload, file, gemini]
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# File Upload
|
|
16
|
+
|
|
17
|
+
Upload files via the Renoise gateway and get back a file URI for use with `gemini-gen`.
|
|
18
|
+
|
|
19
|
+
## When to Use
|
|
20
|
+
|
|
21
|
+
- File exceeds 20MB (inline base64 limit for `gemini-gen`)
|
|
22
|
+
- Same file needs to be referenced in multiple `gemini-gen` calls (upload once, reuse URI)
|
|
23
|
+
|
|
24
|
+
## Prerequisites
|
|
25
|
+
|
|
26
|
+
- `RENOISE_API_KEY` environment variable set
|
|
27
|
+
|
|
28
|
+
## API
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
POST https://renoise.ai/api/public/v1/llm/files/upload
|
|
32
|
+
Header: X-API-Key: <RENOISE_API_KEY>
|
|
33
|
+
Body: multipart/form-data with field "file"
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## CLI Script
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# Upload a file, get back a file URI
|
|
40
|
+
node ${CLAUDE_SKILL_DIR}/scripts/upload.mjs <file-path>
|
|
41
|
+
|
|
42
|
+
# Capture URI for use with gemini-gen
|
|
43
|
+
FILE_URI=$(node ${CLAUDE_SKILL_DIR}/scripts/upload.mjs large-video.mp4)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Progress messages go to stderr, the file URL goes to stdout.
|
|
47
|
+
|
|
48
|
+
## Response Format
|
|
49
|
+
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"previewUrl": "https://...r2.cloudflarestorage.com/.../filename?X-Amz-...",
|
|
53
|
+
"mimeType": "image/jpeg",
|
|
54
|
+
"size": 111198,
|
|
55
|
+
"expiresAt": "2026-03-26T11:49:07.328Z"
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
- `previewUrl` — Signed URL, valid for **1 hour**
|
|
60
|
+
- Upload once, use the URL immediately in downstream skills
|
|
61
|
+
|
|
62
|
+
## Usage with gemini-gen
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
# Step 1: Upload
|
|
66
|
+
FILE_URL=$(node ${CLAUDE_PLUGIN_ROOT}/skills/file-upload/scripts/upload.mjs large-video.mp4)
|
|
67
|
+
|
|
68
|
+
# Step 2: Use with gemini-gen
|
|
69
|
+
node ${CLAUDE_PLUGIN_ROOT}/skills/gemini-gen/scripts/gemini.mjs \
|
|
70
|
+
--file-uri "$FILE_URL" --file-mime video/mp4 \
|
|
71
|
+
"Analyze this video"
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Supported File Types
|
|
75
|
+
|
|
76
|
+
Images: `.jpg`, `.jpeg`, `.png`, `.webp`, `.gif`
|
|
77
|
+
Videos: `.mp4`, `.mov`, `.webm`
|
|
78
|
+
Audio: `.mp3`, `.wav`
|
|
79
|
+
Documents: `.pdf`
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Upload a file via Renoise gateway for use with gemini-gen.
|
|
5
|
+
* Outputs the file URI to stdout.
|
|
6
|
+
*
|
|
7
|
+
* Usage: node upload.mjs <file-path>
|
|
8
|
+
*
|
|
9
|
+
* Environment:
|
|
10
|
+
* RENOISE_API_KEY Required. Get one at https://www.renoise.ai
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import fs from "fs/promises";
|
|
14
|
+
import path from "path";
|
|
15
|
+
|
|
16
|
+
const RENOISE_API_KEY = process.env.RENOISE_API_KEY;
|
|
17
|
+
if (!RENOISE_API_KEY) {
|
|
18
|
+
console.error("RENOISE_API_KEY not set. Get one at: https://www.renoise.ai");
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const UPLOAD_ENDPOINT =
|
|
23
|
+
"https://renoise.ai/api/public/v1/llm/files/upload";
|
|
24
|
+
|
|
25
|
+
const MIME_MAP = {
|
|
26
|
+
".jpg": "image/jpeg",
|
|
27
|
+
".jpeg": "image/jpeg",
|
|
28
|
+
".png": "image/png",
|
|
29
|
+
".webp": "image/webp",
|
|
30
|
+
".gif": "image/gif",
|
|
31
|
+
".mp4": "video/mp4",
|
|
32
|
+
".mov": "video/quicktime",
|
|
33
|
+
".webm": "video/webm",
|
|
34
|
+
".mp3": "audio/mpeg",
|
|
35
|
+
".wav": "audio/wav",
|
|
36
|
+
".pdf": "application/pdf",
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
function getMimeType(filePath) {
|
|
40
|
+
return (
|
|
41
|
+
MIME_MAP[path.extname(filePath).toLowerCase()] ?? "application/octet-stream"
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function main() {
|
|
46
|
+
const filePath = process.argv[2];
|
|
47
|
+
if (!filePath) {
|
|
48
|
+
console.error("Usage: node upload.mjs <file-path>");
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const stat = await fs.stat(filePath).catch(() => {
|
|
53
|
+
console.error(`File not found: ${filePath}`);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const mimeType = getMimeType(filePath);
|
|
58
|
+
const fileData = await fs.readFile(filePath);
|
|
59
|
+
const fileName = path.basename(filePath);
|
|
60
|
+
|
|
61
|
+
console.error(
|
|
62
|
+
`Uploading ${fileName} (${(stat.size / 1024 / 1024).toFixed(1)}MB, ${mimeType})...`
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
// Build multipart form-data with native FormData + Blob
|
|
66
|
+
const blob = new Blob([fileData], { type: mimeType });
|
|
67
|
+
const form = new FormData();
|
|
68
|
+
form.append("file", blob, fileName);
|
|
69
|
+
|
|
70
|
+
const res = await fetch(UPLOAD_ENDPOINT, {
|
|
71
|
+
method: "POST",
|
|
72
|
+
headers: {
|
|
73
|
+
"X-API-Key": RENOISE_API_KEY,
|
|
74
|
+
},
|
|
75
|
+
body: form,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
if (!res.ok) {
|
|
79
|
+
const errText = await res.text();
|
|
80
|
+
console.error(`Upload error ${res.status}: ${errText}`);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const data = await res.json();
|
|
85
|
+
|
|
86
|
+
// Response format: { previewUrl, mimeType, size, expiresAt }
|
|
87
|
+
const fileUrl = data?.previewUrl;
|
|
88
|
+
|
|
89
|
+
if (!fileUrl) {
|
|
90
|
+
console.error("No previewUrl in response:", JSON.stringify(data, null, 2));
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const expires = data.expiresAt ? new Date(data.expiresAt).toLocaleString() : "unknown";
|
|
95
|
+
console.error(`Uploaded: ${(data.size / 1024).toFixed(0)}KB, expires ${expires}`);
|
|
96
|
+
// Print URL to stdout (stderr used for progress messages)
|
|
97
|
+
console.log(fileUrl);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
main().catch((err) => {
|
|
101
|
+
console.error("ERROR:", err.message);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
});
|