Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,10 @@ next-env.d.ts
# email
/.react-email/

# media studio (npm workspace)
/media-studio/node_modules
/media-studio/data
/media-studio/platform/.next

.vscode
.contentlayer
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,25 @@ cp .env.example .env.local
pnpm dev
```

## UGC Media Studio

This repo includes **Ultimate Multimodal** — a MuAPI-powered UGC media studio for generating product ads, lifestyle try-ons, and social video content. It lives in [`media-studio/`](media-studio/).

```bash
cd media-studio
npm install
cp .env.example .env.local # add MUAPI_API_KEY
npm run dev # http://localhost:3000
```

Features:
- UGC Video Factory, Ads Workflow, Lifestyle Try-On
- Image & Video studios (100+ MuAPI models)
- Admin dashboard at `/dashboard`
- Workflow recipes from [Generative-Media-Skills](https://github.com/SamurAIGPT/Generative-Media-Skills)

See [media-studio/README.md](media-studio/README.md) for full docs.

> [!NOTE]
> I use [npm-check-updates](https://www.npmjs.com/package/npm-check-updates) package for update this project.
>
Expand Down
2 changes: 2 additions & 0 deletions media-studio/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
MUAPI_API_KEY=
NEXT_PUBLIC_APP_URL=http://localhost:3000
9 changes: 9 additions & 0 deletions media-studio/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
node_modules/
.next/
dist/
.env
.env.local
*.log
.DS_Store
media_outputs/
.turbo/
405 changes: 405 additions & 0 deletions media-studio/DESIGN1.md

Large diffs are not rendered by default.

391 changes: 391 additions & 0 deletions media-studio/DESIGN2.md

Large diffs are not rendered by default.

52 changes: 52 additions & 0 deletions media-studio/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Ultimate Multimodal — UGC Media Studio Platform

Part of [ReplyGuy-clone](https://github.com/cameronking4/ReplyGuy-clone) — use alongside BuzzDaddy to scrape social posts and generate UGC video/image ads for your product.

A self-hosted generative media studio powered by [MuAPI](https://muapi.ai) and [Generative-Media-Skills](https://github.com/SamurAIGPT/Generative-Media-Skills).

## Features

- **UGC Studio** — Video Factory, Ads Workflow, Lifestyle Try-On pipelines
- **Image & Video Studios** — Generate with 100+ MuAPI models
- **Workflow Browser** — 41+ recipes from Generative-Media-Skills
- **Admin Dashboard** — Job queue, projects, API settings, credit balance
- **Dual design system** — Skillshare-style creator UI (DESIGN1) + Officevibe-style dashboard (DESIGN2)

## Quick Start

```bash
# Install dependencies
npm install

# Configure API key
cp .env.example .env.local
# Add MUAPI_API_KEY=your_key_here

# Start dev server
npm run dev
```

Open [http://localhost:3000](http://localhost:3000) for the creator studio.
Open [http://localhost:3000/dashboard](http://localhost:3000/dashboard) for the admin dashboard.

## Project Structure

```
├── skills/ # Generative-Media-Skills (cloned)
├── platform/ # Next.js 15 app
├── packages/
│ ├── muapi-client/ # MuAPI REST wrapper
│ ├── workflow-engine/ # SKILL.md parser + UGC pipelines
│ └── shared/ # Shared types
├── DESIGN1.md # Creator frontend design spec
└── DESIGN2.md # Dashboard design spec
```

## Prerequisites

- Node.js 20+
- MuAPI API key from [muapi.ai/dashboard](https://muapi.ai/dashboard)

## License

MIT
15 changes: 15 additions & 0 deletions media-studio/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "ultimate-multimodal",
"version": "0.1.0",
"private": true,
"workspaces": [
"platform",
"packages/*"
],
"scripts": {
"dev": "npm run dev -w platform",
"build": "npm run build -w platform",
"start": "npm run start -w platform",
"lint": "npm run lint -w platform"
}
}
16 changes: 16 additions & 0 deletions media-studio/packages/muapi-client/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "@ultimate-multimodal/muapi-client",
"version": "0.1.0",
"private": true,
"main": "./src/index.ts",
"types": "./src/index.ts",
"exports": {
".": "./src/index.ts"
},
"scripts": {
"test": "node --experimental-strip-types src/schema.test.ts"
},
"dependencies": {
"@ultimate-multimodal/shared": "*"
}
}
181 changes: 181 additions & 0 deletions media-studio/packages/muapi-client/src/cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import { spawn } from "child_process";
import type { CliRunOptions } from "./types";

export class MuapiCliError extends Error {
constructor(
message: string,
readonly exitCode: number,
readonly stderr: string
) {
super(message);
this.name = "MuapiCliError";
}
}

function resolveMuapiCommand(): string {
return process.platform === "win32" ? "muapi.cmd" : "muapi";
}

export async function runMuapiCli(
options: CliRunOptions
): Promise<Record<string, unknown>> {
const cmd = resolveMuapiCommand();
const args = [...options.args, "--output-json"];

return new Promise((resolve, reject) => {
const env = { ...process.env };
if (options.apiKey) env.MUAPI_API_KEY = options.apiKey;

const child = spawn(cmd, args, {
cwd: options.cwd,
env,
shell: process.platform === "win32",
});

let stdout = "";
let stderr = "";

child.stdout.on("data", (d) => {
stdout += d.toString();
});
child.stderr.on("data", (d) => {
stderr += d.toString();
});

child.on("error", (err) => {
reject(
new MuapiCliError(
`Failed to spawn muapi CLI: ${err.message}. Install with: npm install -g muapi-cli`,
127,
stderr
)
);
});

child.on("close", (code) => {
if (code !== 0) {
reject(new MuapiCliError(`muapi CLI exited with code ${code}`, code ?? 1, stderr));
return;
}
try {
const trimmed = stdout.trim();
const jsonStart = trimmed.indexOf("{");
const jsonArrStart = trimmed.indexOf("[");
const start =
jsonStart >= 0 && (jsonArrStart < 0 || jsonStart < jsonArrStart)
? jsonStart
: jsonArrStart;
if (start < 0) {
resolve({ raw: trimmed });
return;
}
resolve(JSON.parse(trimmed.slice(start)) as Record<string, unknown>);
} catch {
reject(new MuapiCliError("Failed to parse muapi CLI JSON output", 1, stdout));
}
});
});
}

export async function cliUpload(
filePath: string,
apiKey?: string
): Promise<string> {
const result = await runMuapiCli({
args: ["upload", "file", filePath, "--jq", ".url"],
apiKey,
});
const url = (result as { url?: string }).url ?? (result.raw as string | undefined);
if (!url || typeof url !== "string") throw new Error("CLI upload did not return url");
return url.replace(/^"|"$/g, "");
}

export async function cliPredictWait(
requestId: string,
apiKey?: string
): Promise<Record<string, unknown>> {
return runMuapiCli({
args: ["predict", "wait", requestId],
apiKey,
});
}

export async function cliPredictStatus(
requestId: string,
apiKey?: string
): Promise<Record<string, unknown>> {
return runMuapiCli({
args: ["predict", "result", requestId],
apiKey,
});
}

export async function cliAccountBalance(apiKey?: string): Promise<number | null> {
try {
const result = await runMuapiCli({
args: ["account", "balance"],
apiKey,
});
const balance = (result as { balance?: number; credits?: number }).balance ??
(result as { balance?: number; credits?: number }).credits;
return typeof balance === "number" ? balance : null;
} catch {
return null;
}
}

export async function cliImageGenerate(
prompt: string,
model: string,
extra: Record<string, string | number> = {},
apiKey?: string
): Promise<Record<string, unknown>> {
const args = ["image", "generate", prompt, "--model", model, "--no-wait"];
for (const [k, v] of Object.entries(extra)) {
args.push(`--${k.replace(/_/g, "-")}`, String(v));
}
return runMuapiCli({ args, apiKey });
}

export async function cliImageEdit(
prompt: string,
model: string,
imageUrl: string,
extra: Record<string, string | number> = {},
apiKey?: string
): Promise<Record<string, unknown>> {
const args = ["image", "edit", prompt, "--model", model, "--image", imageUrl, "--no-wait"];
for (const [k, v] of Object.entries(extra)) {
args.push(`--${k.replace(/_/g, "-")}`, String(v));
}
return runMuapiCli({ args, apiKey });
}

export async function cliVideoFromImage(
prompt: string,
model: string,
imageUrl: string,
extra: Record<string, string | number | boolean> = {},
apiKey?: string
): Promise<Record<string, unknown>> {
const args = ["video", "from-image"];
if (prompt) args.push(prompt);
args.push("--model", model, "--image", imageUrl, "--no-wait");
for (const [k, v] of Object.entries(extra)) {
args.push(`--${k.replace(/_/g, "-")}`, String(v));
}
return runMuapiCli({ args, apiKey });
}

export async function cliVideoGenerate(
prompt: string,
model: string,
extra: Record<string, string | number> = {},
apiKey?: string
): Promise<Record<string, unknown>> {
const args = ["video", "generate", prompt, "--model", model, "--no-wait"];
for (const [k, v] of Object.entries(extra)) {
args.push(`--${k.replace(/_/g, "-")}`, String(v));
}
return runMuapiCli({ args, apiKey });
}
Loading