@vargai/sdk 0.1.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/.env.example +24 -0
- package/CLAUDE.md +118 -0
- package/HIGGSFIELD_REWRITE_SUMMARY.md +300 -0
- package/README.md +231 -0
- package/SKILLS.md +157 -0
- package/STRUCTURE.md +92 -0
- package/TEST_RESULTS.md +122 -0
- package/action/captions/SKILL.md +170 -0
- package/action/captions/index.ts +169 -0
- package/action/edit/SKILL.md +235 -0
- package/action/edit/index.ts +437 -0
- package/action/image/SKILL.md +140 -0
- package/action/image/index.ts +105 -0
- package/action/sync/SKILL.md +136 -0
- package/action/sync/index.ts +145 -0
- package/action/transcribe/SKILL.md +179 -0
- package/action/transcribe/index.ts +210 -0
- package/action/video/SKILL.md +116 -0
- package/action/video/index.ts +125 -0
- package/action/voice/SKILL.md +125 -0
- package/action/voice/index.ts +136 -0
- package/biome.json +33 -0
- package/bun.lock +842 -0
- package/cli/commands/find.ts +58 -0
- package/cli/commands/help.ts +70 -0
- package/cli/commands/list.ts +49 -0
- package/cli/commands/run.ts +237 -0
- package/cli/commands/which.ts +66 -0
- package/cli/discover.ts +66 -0
- package/cli/index.ts +33 -0
- package/cli/runner.ts +65 -0
- package/cli/types.ts +49 -0
- package/cli/ui.ts +185 -0
- package/index.ts +75 -0
- package/lib/README.md +144 -0
- package/lib/ai-sdk/fal.ts +106 -0
- package/lib/ai-sdk/replicate.ts +107 -0
- package/lib/elevenlabs.ts +382 -0
- package/lib/fal.ts +467 -0
- package/lib/ffmpeg.ts +467 -0
- package/lib/fireworks.ts +235 -0
- package/lib/groq.ts +246 -0
- package/lib/higgsfield/MIGRATION.md +308 -0
- package/lib/higgsfield/README.md +273 -0
- package/lib/higgsfield/example.ts +228 -0
- package/lib/higgsfield/index.ts +241 -0
- package/lib/higgsfield/soul.ts +262 -0
- package/lib/higgsfield.ts +176 -0
- package/lib/remotion/SKILL.md +823 -0
- package/lib/remotion/cli.ts +115 -0
- package/lib/remotion/functions.ts +283 -0
- package/lib/remotion/index.ts +19 -0
- package/lib/remotion/templates.ts +73 -0
- package/lib/replicate.ts +304 -0
- package/output.txt +1 -0
- package/package.json +42 -0
- package/pipeline/cookbooks/SKILL.md +285 -0
- package/pipeline/cookbooks/remotion-video.md +585 -0
- package/pipeline/cookbooks/round-video-character.md +337 -0
- package/pipeline/cookbooks/talking-character.md +59 -0
- package/scripts/produce-menopause-campaign.sh +202 -0
- package/service/music/SKILL.md +229 -0
- package/service/music/index.ts +296 -0
- package/test-import.ts +7 -0
- package/test-services.ts +97 -0
- package/tsconfig.json +29 -0
- package/utilities/s3.ts +147 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: music-generation
|
|
3
|
+
description: generate full songs and instrumentals using sonauto v2 via fal.ai. create music in any style with customizable tags, lyrics, tempo, and output formats. use when user needs background music, theme songs, soundtracks, or custom audio tracks.
|
|
4
|
+
allowed-tools: Read, Bash
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# music generation
|
|
8
|
+
|
|
9
|
+
generate complete songs with vocals or instrumental tracks using sonauto v2 text-to-music model.
|
|
10
|
+
|
|
11
|
+
## features
|
|
12
|
+
|
|
13
|
+
- **text-to-music** - describe the song you want in natural language
|
|
14
|
+
- **style tags** - specify exact genres and musical styles
|
|
15
|
+
- **custom lyrics** - provide lyrics or generate instrumentals
|
|
16
|
+
- **tempo control** - set bpm or auto-detect from style
|
|
17
|
+
- **multiple formats** - output as mp3, wav, flac, ogg, or m4a
|
|
18
|
+
- **multi-song generation** - generate up to 2 variations at once
|
|
19
|
+
- **reproducible** - use seed parameter to regenerate same song
|
|
20
|
+
|
|
21
|
+
## usage
|
|
22
|
+
|
|
23
|
+
### generate from prompt
|
|
24
|
+
```bash
|
|
25
|
+
bun run service/music generate <prompt> [format] [numSongs] [upload]
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**parameters:**
|
|
29
|
+
- `prompt` (required): natural language description of the song
|
|
30
|
+
- `format` (optional): mp3 (default), wav, flac, ogg, m4a
|
|
31
|
+
- `numSongs` (optional): 1 (default) or 2
|
|
32
|
+
- `upload` (optional): "true" to upload to s3
|
|
33
|
+
|
|
34
|
+
**examples:**
|
|
35
|
+
```bash
|
|
36
|
+
# simple generation
|
|
37
|
+
bun run service/music generate "A pop song about turtles flying"
|
|
38
|
+
|
|
39
|
+
# generate 2 versions as mp3
|
|
40
|
+
bun run service/music generate "upbeat electronic dance music" mp3 2
|
|
41
|
+
|
|
42
|
+
# generate and upload to s3
|
|
43
|
+
bun run service/music generate "sad acoustic ballad" mp3 1 true
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### generate from style tags
|
|
47
|
+
```bash
|
|
48
|
+
bun run service/music tags <tag1> <tag2> ... [format] [upload]
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**examples:**
|
|
52
|
+
```bash
|
|
53
|
+
# rock song
|
|
54
|
+
bun run service/music tags "rock" "energetic" "electric guitar"
|
|
55
|
+
|
|
56
|
+
# ambient track
|
|
57
|
+
bun run service/music tags "ambient" "calm" "ethereal" wav
|
|
58
|
+
|
|
59
|
+
# upload to s3
|
|
60
|
+
bun run service/music tags "jazz" "smooth" "piano" mp3 true
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
explore all available tags at: https://sonauto.ai/tag-explorer
|
|
64
|
+
|
|
65
|
+
### generate instrumental
|
|
66
|
+
```bash
|
|
67
|
+
bun run service/music instrumental <tag1> <tag2> ... [format] [upload]
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**examples:**
|
|
71
|
+
```bash
|
|
72
|
+
# piano instrumental
|
|
73
|
+
bun run service/music instrumental "piano" "calm" "ambient"
|
|
74
|
+
|
|
75
|
+
# electronic instrumental
|
|
76
|
+
bun run service/music instrumental "electronic" "upbeat" "synth" wav true
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## as library
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
import { generateMusic } from "./service/music"
|
|
83
|
+
|
|
84
|
+
// generate from prompt
|
|
85
|
+
const result = await generateMusic({
|
|
86
|
+
prompt: "A pop song about summer",
|
|
87
|
+
format: "mp3",
|
|
88
|
+
upload: true,
|
|
89
|
+
outputPath: "media/summer-song.mp3"
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
// generate from tags with custom lyrics
|
|
93
|
+
const customSong = await generateMusic({
|
|
94
|
+
tags: ["rock", "energetic", "guitar"],
|
|
95
|
+
lyrics: "Verse 1\nI'm walking down the street\nFeeling the beat\n\nChorus\nThis is my song",
|
|
96
|
+
format: "mp3",
|
|
97
|
+
bpm: 120,
|
|
98
|
+
promptStrength: 2.5,
|
|
99
|
+
outputPath: "media/custom-rock.mp3"
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
// generate instrumental
|
|
103
|
+
const instrumental = await generateMusic({
|
|
104
|
+
tags: ["ambient", "calm", "piano"],
|
|
105
|
+
lyrics: "", // empty for instrumental
|
|
106
|
+
format: "wav",
|
|
107
|
+
outputPath: "media/ambient-instrumental.wav"
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
console.log(result.seed) // save seed to regenerate exact song
|
|
111
|
+
console.log(result.audio[0].url)
|
|
112
|
+
console.log(result.uploadUrls)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## output
|
|
116
|
+
|
|
117
|
+
returns `MusicResult`:
|
|
118
|
+
```typescript
|
|
119
|
+
{
|
|
120
|
+
seed: number, // seed used (save for reproducibility)
|
|
121
|
+
tags?: string[], // style tags used
|
|
122
|
+
lyrics?: string, // lyrics used (if any)
|
|
123
|
+
audio: Array<{
|
|
124
|
+
url: string, // download url
|
|
125
|
+
fileName: string, // original filename
|
|
126
|
+
contentType: string, // audio/mp3, audio/wav, etc
|
|
127
|
+
fileSize: number // size in bytes
|
|
128
|
+
}>,
|
|
129
|
+
uploadUrls?: string[] // s3 urls if upload requested
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
saves audio file(s) to `media/music-{timestamp}.{format}`
|
|
134
|
+
|
|
135
|
+
## advanced options
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
interface GenerateMusicOptions {
|
|
139
|
+
prompt?: string; // text description
|
|
140
|
+
tags?: string[]; // style tags
|
|
141
|
+
lyrics?: string; // custom lyrics (empty string = instrumental)
|
|
142
|
+
seed?: number; // for reproducibility
|
|
143
|
+
promptStrength?: number; // 1.4-3.1, default 2 (higher = more prompt adherence)
|
|
144
|
+
balanceStrength?: number; // 0-1, default 0.7 (higher = more natural vocals)
|
|
145
|
+
numSongs?: 1 | 2; // generate variations (2 costs 1.5x)
|
|
146
|
+
format?: string; // output format
|
|
147
|
+
bitRate?: 128 | 192 | 256 | 320; // for mp3/m4a only
|
|
148
|
+
bpm?: number | "auto"; // beats per minute
|
|
149
|
+
upload?: boolean; // upload to s3
|
|
150
|
+
outputPath?: string; // local save path
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## when to use
|
|
155
|
+
|
|
156
|
+
use this skill when:
|
|
157
|
+
- creating background music for videos
|
|
158
|
+
- generating theme songs or intros
|
|
159
|
+
- producing soundtracks for content
|
|
160
|
+
- testing different musical styles quickly
|
|
161
|
+
- need instrumental tracks for voiceovers
|
|
162
|
+
- creating audio for social media content
|
|
163
|
+
- prototyping music ideas
|
|
164
|
+
|
|
165
|
+
## tips
|
|
166
|
+
|
|
167
|
+
**for better results:**
|
|
168
|
+
- be specific with style descriptions
|
|
169
|
+
- combine multiple style tags for unique sounds
|
|
170
|
+
- use "auto" bpm to let the model choose appropriate tempo
|
|
171
|
+
- generating 2 songs gives you variations to choose from
|
|
172
|
+
- save the seed if you want to recreate the exact same song
|
|
173
|
+
- use instrumental mode (empty lyrics) for background music
|
|
174
|
+
|
|
175
|
+
**prompt strength:**
|
|
176
|
+
- `1.4-2.0` - more natural, flowing music
|
|
177
|
+
- `2.0-2.5` - balanced (recommended)
|
|
178
|
+
- `2.5-3.1` - strict adherence to prompt (may sound less natural)
|
|
179
|
+
|
|
180
|
+
**balance strength:**
|
|
181
|
+
- `0.5-0.6` - sharper instrumentals, less clear vocals
|
|
182
|
+
- `0.7` - balanced (recommended)
|
|
183
|
+
- `0.8-1.0` - more natural vocals
|
|
184
|
+
|
|
185
|
+
**format selection:**
|
|
186
|
+
- `mp3` - best for web/social media (small size, good quality)
|
|
187
|
+
- `wav` - uncompressed, best for further editing
|
|
188
|
+
- `flac` - lossless compression
|
|
189
|
+
- `ogg` - open format, good compression
|
|
190
|
+
- `m4a` - apple devices, good compression
|
|
191
|
+
|
|
192
|
+
## integration with other services
|
|
193
|
+
|
|
194
|
+
perfect companion for:
|
|
195
|
+
- **video service** - add background music to generated videos
|
|
196
|
+
- **edit service** - combine music with video content
|
|
197
|
+
- **voice service** - create instrumentals for voiceovers
|
|
198
|
+
- **captions service** - sync music with captioned videos
|
|
199
|
+
|
|
200
|
+
## environment variables
|
|
201
|
+
|
|
202
|
+
required:
|
|
203
|
+
- `FAL_API_KEY` - for music generation
|
|
204
|
+
|
|
205
|
+
optional (for s3 upload):
|
|
206
|
+
- `CLOUDFLARE_R2_API_URL`
|
|
207
|
+
- `CLOUDFLARE_ACCESS_KEY_ID`
|
|
208
|
+
- `CLOUDFLARE_ACCESS_SECRET`
|
|
209
|
+
- `CLOUDFLARE_R2_BUCKET`
|
|
210
|
+
|
|
211
|
+
## generation time
|
|
212
|
+
|
|
213
|
+
expect 30-120 seconds depending on song complexity and duration. generating 2 songs takes longer but costs only 1.5x.
|
|
214
|
+
|
|
215
|
+
## cost
|
|
216
|
+
|
|
217
|
+
- 1 song: 1x credit
|
|
218
|
+
- 2 songs: 1.5x credit
|
|
219
|
+
|
|
220
|
+
## reproducibility
|
|
221
|
+
|
|
222
|
+
to regenerate the exact same song:
|
|
223
|
+
1. use the same `tags` and `lyrics` (not `prompt`)
|
|
224
|
+
2. use the same `seed` from the previous result
|
|
225
|
+
3. keep all other parameters identical
|
|
226
|
+
4. keep `numSongs` the same
|
|
227
|
+
|
|
228
|
+
note: using `prompt` instead of `tags` + `lyrics` will not guarantee reproducibility even with the same seed.
|
|
229
|
+
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* music service - high-level music generation using sonauto via fal.ai
|
|
5
|
+
* supports text-to-music with customizable styles, lyrics, and parameters
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { textToMusic } from "../../lib/fal";
|
|
9
|
+
import { uploadFromUrl } from "../../utilities/s3";
|
|
10
|
+
import { writeFile } from "node:fs/promises";
|
|
11
|
+
|
|
12
|
+
// types
|
|
13
|
+
export interface GenerateMusicOptions {
|
|
14
|
+
prompt?: string;
|
|
15
|
+
tags?: string[];
|
|
16
|
+
lyrics?: string;
|
|
17
|
+
seed?: number;
|
|
18
|
+
promptStrength?: number;
|
|
19
|
+
balanceStrength?: number;
|
|
20
|
+
numSongs?: 1 | 2;
|
|
21
|
+
format?: "flac" | "mp3" | "wav" | "ogg" | "m4a";
|
|
22
|
+
bitRate?: 128 | 192 | 256 | 320;
|
|
23
|
+
bpm?: number | "auto";
|
|
24
|
+
upload?: boolean;
|
|
25
|
+
outputPath?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface MusicResult {
|
|
29
|
+
seed: number;
|
|
30
|
+
tags?: string[];
|
|
31
|
+
lyrics?: string;
|
|
32
|
+
audio: Array<{
|
|
33
|
+
url: string;
|
|
34
|
+
fileName: string;
|
|
35
|
+
contentType: string;
|
|
36
|
+
fileSize: number;
|
|
37
|
+
}>;
|
|
38
|
+
uploadUrls?: string[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// core functions
|
|
42
|
+
export async function generateMusic(
|
|
43
|
+
options: GenerateMusicOptions,
|
|
44
|
+
): Promise<MusicResult> {
|
|
45
|
+
const {
|
|
46
|
+
prompt,
|
|
47
|
+
tags,
|
|
48
|
+
lyrics,
|
|
49
|
+
seed,
|
|
50
|
+
promptStrength = 2,
|
|
51
|
+
balanceStrength = 0.7,
|
|
52
|
+
numSongs = 1,
|
|
53
|
+
format = "mp3",
|
|
54
|
+
bitRate,
|
|
55
|
+
bpm = "auto",
|
|
56
|
+
upload = false,
|
|
57
|
+
outputPath,
|
|
58
|
+
} = options;
|
|
59
|
+
|
|
60
|
+
if (!prompt && !tags) {
|
|
61
|
+
throw new Error("either prompt or tags is required");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
console.log(`[music] generating ${numSongs} song(s)...`);
|
|
65
|
+
if (prompt) console.log(`[music] prompt: ${prompt}`);
|
|
66
|
+
if (tags) console.log(`[music] tags: ${tags.join(", ")}`);
|
|
67
|
+
if (lyrics) console.log(`[music] lyrics: ${lyrics.substring(0, 50)}...`);
|
|
68
|
+
|
|
69
|
+
const result = await textToMusic({
|
|
70
|
+
prompt,
|
|
71
|
+
tags,
|
|
72
|
+
lyricsPrompt: lyrics,
|
|
73
|
+
seed,
|
|
74
|
+
promptStrength,
|
|
75
|
+
balanceStrength,
|
|
76
|
+
numSongs,
|
|
77
|
+
outputFormat: format,
|
|
78
|
+
outputBitRate: bitRate,
|
|
79
|
+
bpm,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const musicResult: MusicResult = {
|
|
83
|
+
seed: result.data.seed,
|
|
84
|
+
tags: result.data.tags,
|
|
85
|
+
lyrics: result.data.lyrics,
|
|
86
|
+
audio: Array.isArray(result.data.audio)
|
|
87
|
+
? result.data.audio.map((a: any) => ({
|
|
88
|
+
url: a.url,
|
|
89
|
+
fileName: a.file_name,
|
|
90
|
+
contentType: a.content_type,
|
|
91
|
+
fileSize: a.file_size,
|
|
92
|
+
}))
|
|
93
|
+
: [
|
|
94
|
+
{
|
|
95
|
+
url: result.data.audio.url,
|
|
96
|
+
fileName: result.data.audio.file_name,
|
|
97
|
+
contentType: result.data.audio.content_type,
|
|
98
|
+
fileSize: result.data.audio.file_size,
|
|
99
|
+
},
|
|
100
|
+
],
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// save files locally if requested
|
|
104
|
+
if (outputPath) {
|
|
105
|
+
for (let i = 0; i < musicResult.audio.length; i++) {
|
|
106
|
+
const audio = musicResult.audio[i];
|
|
107
|
+
const ext = format || "wav";
|
|
108
|
+
const filePath =
|
|
109
|
+
musicResult.audio.length === 1
|
|
110
|
+
? outputPath
|
|
111
|
+
: outputPath.replace(/\.[^.]+$/, `-${i + 1}.${ext}`);
|
|
112
|
+
|
|
113
|
+
// download the audio
|
|
114
|
+
const response = await fetch(audio.url);
|
|
115
|
+
const buffer = await response.arrayBuffer();
|
|
116
|
+
await writeFile(filePath, Buffer.from(buffer));
|
|
117
|
+
console.log(`[music] saved to ${filePath}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// upload to s3 if requested
|
|
122
|
+
if (upload) {
|
|
123
|
+
const uploadUrls: string[] = [];
|
|
124
|
+
for (let i = 0; i < musicResult.audio.length; i++) {
|
|
125
|
+
const audio = musicResult.audio[i];
|
|
126
|
+
const objectKey = `music/${Date.now()}-${i + 1}.${format || "wav"}`;
|
|
127
|
+
const uploadUrl = await uploadFromUrl(audio.url, objectKey);
|
|
128
|
+
uploadUrls.push(uploadUrl);
|
|
129
|
+
console.log(`[music] uploaded to ${uploadUrl}`);
|
|
130
|
+
}
|
|
131
|
+
musicResult.uploadUrls = uploadUrls;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return musicResult;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// cli
|
|
138
|
+
async function cli() {
|
|
139
|
+
const args = process.argv.slice(2);
|
|
140
|
+
const command = args[0];
|
|
141
|
+
|
|
142
|
+
if (!command || command === "help") {
|
|
143
|
+
console.log(`
|
|
144
|
+
usage:
|
|
145
|
+
bun run service/music <command> [args]
|
|
146
|
+
|
|
147
|
+
commands:
|
|
148
|
+
generate <prompt> [format] [numSongs] [upload] generate music from prompt
|
|
149
|
+
tags <tags...> [format] [upload] generate music from tags
|
|
150
|
+
instrumental <tags...> [format] [upload] generate instrumental track
|
|
151
|
+
help show this help
|
|
152
|
+
|
|
153
|
+
examples:
|
|
154
|
+
bun run service/music generate "A pop song about turtles flying"
|
|
155
|
+
bun run service/music generate "upbeat electronic" mp3 2 true
|
|
156
|
+
bun run service/music tags "rock" "energetic" "guitar" mp3
|
|
157
|
+
bun run service/music instrumental "ambient" "calm" "piano" wav true
|
|
158
|
+
|
|
159
|
+
formats:
|
|
160
|
+
mp3 (default), wav, flac, ogg, m4a
|
|
161
|
+
|
|
162
|
+
environment:
|
|
163
|
+
FAL_API_KEY - required for music generation
|
|
164
|
+
CLOUDFLARE_* - required for upload
|
|
165
|
+
`);
|
|
166
|
+
process.exit(0);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
switch (command) {
|
|
171
|
+
case "generate": {
|
|
172
|
+
const prompt = args[1];
|
|
173
|
+
const format = (args[2] || "mp3") as "flac" | "mp3" | "wav" | "ogg" | "m4a";
|
|
174
|
+
const numSongs = (parseInt(args[3]) || 1) as 1 | 2;
|
|
175
|
+
const upload = args[4] === "true";
|
|
176
|
+
|
|
177
|
+
if (!prompt) {
|
|
178
|
+
throw new Error("prompt is required");
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const outputPath = `media/music-${Date.now()}.${format}`;
|
|
182
|
+
|
|
183
|
+
const result = await generateMusic({
|
|
184
|
+
prompt,
|
|
185
|
+
format,
|
|
186
|
+
numSongs,
|
|
187
|
+
upload,
|
|
188
|
+
outputPath,
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
console.log(`[music] result:`, {
|
|
192
|
+
seed: result.seed,
|
|
193
|
+
tags: result.tags,
|
|
194
|
+
audioCount: result.audio.length,
|
|
195
|
+
outputPath,
|
|
196
|
+
uploadUrls: result.uploadUrls,
|
|
197
|
+
});
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
case "tags": {
|
|
202
|
+
// collect all tag arguments until we hit format/upload
|
|
203
|
+
const tags: string[] = [];
|
|
204
|
+
let format: "flac" | "mp3" | "wav" | "ogg" | "m4a" = "mp3";
|
|
205
|
+
let upload = false;
|
|
206
|
+
|
|
207
|
+
for (let i = 1; i < args.length; i++) {
|
|
208
|
+
const arg = args[i];
|
|
209
|
+
if (["mp3", "wav", "flac", "ogg", "m4a"].includes(arg)) {
|
|
210
|
+
format = arg as "flac" | "mp3" | "wav" | "ogg" | "m4a";
|
|
211
|
+
} else if (arg === "true") {
|
|
212
|
+
upload = true;
|
|
213
|
+
} else {
|
|
214
|
+
tags.push(arg);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (tags.length === 0) {
|
|
219
|
+
throw new Error("at least one tag is required");
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const outputPath = `media/music-${Date.now()}.${format}`;
|
|
223
|
+
|
|
224
|
+
const result = await generateMusic({
|
|
225
|
+
tags,
|
|
226
|
+
format,
|
|
227
|
+
upload,
|
|
228
|
+
outputPath,
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
console.log(`[music] result:`, {
|
|
232
|
+
seed: result.seed,
|
|
233
|
+
tags: result.tags,
|
|
234
|
+
audioCount: result.audio.length,
|
|
235
|
+
outputPath,
|
|
236
|
+
uploadUrls: result.uploadUrls,
|
|
237
|
+
});
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
case "instrumental": {
|
|
242
|
+
// same as tags but with empty lyrics
|
|
243
|
+
const tags: string[] = [];
|
|
244
|
+
let format: "flac" | "mp3" | "wav" | "ogg" | "m4a" = "mp3";
|
|
245
|
+
let upload = false;
|
|
246
|
+
|
|
247
|
+
for (let i = 1; i < args.length; i++) {
|
|
248
|
+
const arg = args[i];
|
|
249
|
+
if (["mp3", "wav", "flac", "ogg", "m4a"].includes(arg)) {
|
|
250
|
+
format = arg as "flac" | "mp3" | "wav" | "ogg" | "m4a";
|
|
251
|
+
} else if (arg === "true") {
|
|
252
|
+
upload = true;
|
|
253
|
+
} else {
|
|
254
|
+
tags.push(arg);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (tags.length === 0) {
|
|
259
|
+
throw new Error("at least one tag is required");
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const outputPath = `media/music-${Date.now()}.${format}`;
|
|
263
|
+
|
|
264
|
+
const result = await generateMusic({
|
|
265
|
+
tags,
|
|
266
|
+
lyrics: "", // empty string for instrumental
|
|
267
|
+
format,
|
|
268
|
+
upload,
|
|
269
|
+
outputPath,
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
console.log(`[music] result:`, {
|
|
273
|
+
seed: result.seed,
|
|
274
|
+
tags: result.tags,
|
|
275
|
+
audioCount: result.audio.length,
|
|
276
|
+
outputPath,
|
|
277
|
+
uploadUrls: result.uploadUrls,
|
|
278
|
+
});
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
default:
|
|
283
|
+
console.error(`unknown command: ${command}`);
|
|
284
|
+
console.log(`run 'bun run service/music help' for usage`);
|
|
285
|
+
process.exit(1);
|
|
286
|
+
}
|
|
287
|
+
} catch (error) {
|
|
288
|
+
console.error(`[music] error:`, error);
|
|
289
|
+
process.exit(1);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (import.meta.main) {
|
|
294
|
+
cli();
|
|
295
|
+
}
|
|
296
|
+
|
package/test-import.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { generateImage, imageToVideo, uploadFromUrl } from "./index";
|
|
2
|
+
|
|
3
|
+
console.log("✓ imports successful");
|
|
4
|
+
console.log("available functions:");
|
|
5
|
+
console.log("- generateImage:", typeof generateImage);
|
|
6
|
+
console.log("- imageToVideo:", typeof imageToVideo);
|
|
7
|
+
console.log("- uploadFromUrl:", typeof uploadFromUrl);
|
package/test-services.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* test script for all sdk services
|
|
5
|
+
* saves all media files to media/ directory
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { generateImage as generateFalImage } from "./lib/ai-sdk/fal";
|
|
9
|
+
import { generateImage as generateReplicateImage } from "./lib/ai-sdk/replicate";
|
|
10
|
+
import { textToSpeech } from "./lib/elevenlabs";
|
|
11
|
+
import { runImage } from "./lib/replicate";
|
|
12
|
+
|
|
13
|
+
async function testFalImage() {
|
|
14
|
+
console.log("\n=== testing fal image generation ===");
|
|
15
|
+
|
|
16
|
+
const result = await generateFalImage({
|
|
17
|
+
prompt: "a cozy coffee shop interior with warm lighting",
|
|
18
|
+
model: "fal-ai/flux/dev",
|
|
19
|
+
aspectRatio: "16:9",
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
if (result.image.uint8Array) {
|
|
23
|
+
const filename = "media/fal-coffee-shop.png";
|
|
24
|
+
await Bun.write(filename, result.image.uint8Array);
|
|
25
|
+
console.log(`✓ saved to ${filename}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function testReplicateImage() {
|
|
30
|
+
console.log("\n=== testing replicate image generation ===");
|
|
31
|
+
|
|
32
|
+
const result = await generateReplicateImage({
|
|
33
|
+
prompt: "a mystical forest with glowing mushrooms",
|
|
34
|
+
model: "black-forest-labs/flux-dev",
|
|
35
|
+
aspectRatio: "1:1",
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
if (result.image.uint8Array) {
|
|
39
|
+
const filename = "media/replicate-forest.png";
|
|
40
|
+
await Bun.write(filename, result.image.uint8Array);
|
|
41
|
+
console.log(`✓ saved to ${filename}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function testReplicateClient() {
|
|
46
|
+
console.log("\n=== testing replicate client (flux) ===");
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const output = await runImage({
|
|
50
|
+
model: "black-forest-labs/flux-schnell",
|
|
51
|
+
input: { prompt: "a serene zen garden with cherry blossoms" },
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// output is array of FileOutput objects
|
|
55
|
+
if (Array.isArray(output) && output[0]) {
|
|
56
|
+
const imageUrl = output[0].toString();
|
|
57
|
+
console.log(`✓ generated image: ${imageUrl}`);
|
|
58
|
+
|
|
59
|
+
// download and save
|
|
60
|
+
const response = await fetch(imageUrl);
|
|
61
|
+
const buffer = await response.arrayBuffer();
|
|
62
|
+
const filename = "media/replicate-zen-garden.png";
|
|
63
|
+
await Bun.write(filename, buffer);
|
|
64
|
+
console.log(`✓ saved to ${filename}`);
|
|
65
|
+
}
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.error("✗ replicate client error:", error);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function testElevenlabs() {
|
|
72
|
+
console.log("\n=== testing elevenlabs tts ===");
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
await textToSpeech({
|
|
76
|
+
text: "welcome to varg ai sdk, your complete video production toolkit",
|
|
77
|
+
voiceId: "21m00Tcm4TlvDq8ikWAM",
|
|
78
|
+
outputPath: "media/welcome.mp3",
|
|
79
|
+
});
|
|
80
|
+
console.log("✓ saved to media/welcome.mp3");
|
|
81
|
+
} catch (error) {
|
|
82
|
+
console.error("✗ elevenlabs error:", error);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function main() {
|
|
87
|
+
console.log("🚀 testing varg.ai sdk services...\n");
|
|
88
|
+
|
|
89
|
+
await testFalImage();
|
|
90
|
+
await testReplicateImage();
|
|
91
|
+
await testReplicateClient();
|
|
92
|
+
await testElevenlabs();
|
|
93
|
+
|
|
94
|
+
console.log("\n✨ all tests complete! check media/ directory");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
main().catch(console.error);
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
// Environment setup & latest features
|
|
4
|
+
"lib": ["ESNext"],
|
|
5
|
+
"target": "ESNext",
|
|
6
|
+
"module": "Preserve",
|
|
7
|
+
"moduleDetection": "force",
|
|
8
|
+
"jsx": "react-jsx",
|
|
9
|
+
"allowJs": true,
|
|
10
|
+
|
|
11
|
+
// Bundler mode
|
|
12
|
+
"moduleResolution": "bundler",
|
|
13
|
+
"allowImportingTsExtensions": true,
|
|
14
|
+
"verbatimModuleSyntax": true,
|
|
15
|
+
"noEmit": true,
|
|
16
|
+
|
|
17
|
+
// Best practices
|
|
18
|
+
"strict": true,
|
|
19
|
+
"skipLibCheck": true,
|
|
20
|
+
"noFallthroughCasesInSwitch": true,
|
|
21
|
+
"noUncheckedIndexedAccess": true,
|
|
22
|
+
"noImplicitOverride": true,
|
|
23
|
+
|
|
24
|
+
// Some stricter flags (disabled by default)
|
|
25
|
+
"noUnusedLocals": false,
|
|
26
|
+
"noUnusedParameters": false,
|
|
27
|
+
"noPropertyAccessFromIndexSignature": false
|
|
28
|
+
}
|
|
29
|
+
}
|