yesilcam-studios 0.1.0 → 0.3.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/README.md +30 -11
- package/package.json +20 -6
- package/src/cli.js +187 -95
- package/src/client.js +95 -25
- package/src/config.js +32 -10
- package/src/constants.js +4 -1
- package/src/files.js +87 -0
- package/src/mcp.js +88 -50
- package/src/ops/_util.js +43 -0
- package/src/ops/account.js +62 -0
- package/src/ops/animation.js +126 -0
- package/src/ops/cinema.js +58 -0
- package/src/ops/design.js +75 -0
- package/src/ops/fashion.js +51 -0
- package/src/ops/film.js +197 -0
- package/src/ops/influencer.js +140 -0
- package/src/ops/library.js +56 -0
- package/src/ops/longvideo.js +106 -0
- package/src/ops/maket.js +65 -0
- package/src/ops/social.js +26 -0
- package/src/ops/website.js +92 -0
- package/src/registry.js +38 -0
- package/src/run.js +36 -0
- package/src/wait.js +167 -0
- package/src/jobs.js +0 -45
package/src/wait.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
// Uzun işler için ortak yoklama. Her "tür" bir durum ucunu okuyup ortak biçime çevirir:
|
|
2
|
+
// { kind, id, status, done, ok, needs_action?, progress, stage, url, poster, asset_id, error, tokens_charged, refunded }
|
|
3
|
+
const J = (v) => { if (v && typeof v === 'object') return v; try { return JSON.parse(v || '{}') } catch { return {} } }
|
|
4
|
+
const num = (v) => (v == null || v === '' ? null : Number(v))
|
|
5
|
+
const pick = (o, ...keys) => { for (const k of keys) { const v = k.split('.').reduce((a, p) => (a == null ? a : a[p]), o); if (v != null && v !== '') return v } return null }
|
|
6
|
+
const split2 = (id) => { const [a, b] = String(id).split(':'); return [a, b] }
|
|
7
|
+
|
|
8
|
+
const KINDS = {
|
|
9
|
+
job: {
|
|
10
|
+
label: 'Film Studio / Image Studio / Fashion job',
|
|
11
|
+
fetch: (c, id) => c.call('aistudio-job-status', { job_id: Number(id) }).then((r) => r.job),
|
|
12
|
+
norm: (j) => {
|
|
13
|
+
const ip = J(j?.input_params)
|
|
14
|
+
const awaiting = j?.status === 'waiting' && (ip.awaiting_approval == 1 || ip.awaiting_approval === true) && Boolean(ip.source_image_url)
|
|
15
|
+
return { status: awaiting ? 'awaiting_approval' : j?.status, done: awaiting || ['succeeded', 'failed', 'cancelled', 'canceled', 'refunded', 'done'].includes(j?.status),
|
|
16
|
+
ok: j?.status === 'succeeded' || j?.status === 'done', needs_action: awaiting ? 'Approve or reject the first frame: yesilcam_first_frame (approve|reject|regen)' : null,
|
|
17
|
+
progress: num(j?.render_progress), url: pick(j, 'result_asset.spaces_url'), poster: pick(j, 'result_asset.thumb_url'), asset_id: pick(j, 'result_asset.id', 'result_asset_id'),
|
|
18
|
+
error: j?.error_code || null, tokens_charged: num(j?.tokens_charged), refunded: Boolean(j?.refunded_at) }
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
influencer_job: {
|
|
22
|
+
label: 'AI Influencer video or create job',
|
|
23
|
+
fetch: (c, id) => c.call('ys-influencer-job', { job_id: Number(id) }).then((r) => r.job),
|
|
24
|
+
norm: (j) => ({ status: j?.status, done: ['ready', 'failed'].includes(j?.status), ok: j?.status === 'ready', progress: num(j?.progress), stage: j?.stage || null,
|
|
25
|
+
url: pick(j, 'result.url'), poster: pick(j, 'result.poster'), asset_id: pick(j, 'result.asset_id'), error: j?.error_code || null, tokens_charged: num(j?.tokens_charged), refunded: Boolean(j?.refunded) }),
|
|
26
|
+
},
|
|
27
|
+
influencer: {
|
|
28
|
+
label: 'AI Influencer character set',
|
|
29
|
+
fetch: (c, id) => c.call('ys-influencer-get', { influencer_id: Number(id) }).then((r) => r.influencer),
|
|
30
|
+
norm: (i) => ({ status: i?.status, done: ['ready', 'failed'].includes(i?.status), ok: i?.status === 'ready', progress: num(pick(i, 'job.progress')), stage: pick(i, 'job.stage'),
|
|
31
|
+
url: i?.cover_url || null, error: pick(i, 'job.error_code'), refunded: Boolean(pick(i, 'job.refunded')) }),
|
|
32
|
+
},
|
|
33
|
+
edit: {
|
|
34
|
+
label: 'Long video (edit studio)',
|
|
35
|
+
fetch: (c, id) => c.call('ys-influencer-edit', { op: 'get', edit_id: Number(id) }).then((r) => r.edit),
|
|
36
|
+
norm: (e) => ({ status: e?.status, done: !['planning', 'framing', 'producing'].includes(e?.status), ok: ['draft', 'ready', 'partial'].includes(e?.status),
|
|
37
|
+
needs_action: e?.status === 'paused' ? 'Paused (balance). Top up, then: yesilcam_longvideo_resume' : (e?.status === 'draft' ? 'Plan ready. Review/edit the storyboard, then quote and produce.' : null),
|
|
38
|
+
progress: num(pick(e, 'job.progress', 'plan_job.progress', 'frames_job.progress')), url: pick(e, 'result.url'), poster: e?.cover || null, error: pick(e, 'job.error_code', 'plan_job.error_code') }),
|
|
39
|
+
},
|
|
40
|
+
anim: {
|
|
41
|
+
label: 'Animation Studio project',
|
|
42
|
+
fetch: (c, id) => c.call('aistudio-anim-get', { project_id: Number(id) }),
|
|
43
|
+
norm: (p) => ({ status: p?.status, done: ['ready', 'failed'].includes(p?.status), ok: p?.status === 'ready', progress: num(p?.progress), stage: p?.stage || null, poster: p?.cover || null, error: p?.error_code || null, tokens_charged: num(p?.tokens_charged) }),
|
|
44
|
+
},
|
|
45
|
+
anim_render: {
|
|
46
|
+
label: 'Animation render (id = projectId:renderId)',
|
|
47
|
+
fetch: async (c, id) => { const [p, r] = split2(id); const x = await c.call('aistudio-anim-renders', { project_id: Number(p) }); return (x.renders || []).find((k) => String(k.id) === String(r)) || null },
|
|
48
|
+
norm: (r) => ({ status: r?.status, done: ['done', 'failed'].includes(r?.status), ok: r?.status === 'done', progress: num(r?.progress), url: r?.url || null, error: r?.error || null, tokens_charged: num(r?.tokens_charged) }),
|
|
49
|
+
},
|
|
50
|
+
motion: {
|
|
51
|
+
label: 'Animation › Custom (image + camera path)',
|
|
52
|
+
fetch: (c, id) => c.call('aistudio-motion-get', { job_id: Number(id) }).then((r) => r.job),
|
|
53
|
+
norm: (j) => ({ status: j?.status, done: ['ready', 'failed'].includes(j?.status), ok: j?.status === 'ready', progress: num(j?.progress), stage: j?.stage || null,
|
|
54
|
+
url: pick(j, 'result.url'), poster: pick(j, 'result.poster'), asset_id: pick(j, 'result.asset_id'), error: j?.error_code || null, refunded: Boolean(j?.refunded) }),
|
|
55
|
+
},
|
|
56
|
+
inkmation: {
|
|
57
|
+
label: 'Inkmation project (script + voice)',
|
|
58
|
+
fetch: (c, id) => c.call('aistudio-inkmation-get', { project_id: Number(id) }),
|
|
59
|
+
norm: (p) => ({ status: p?.status, done: p?.status === 'failed' || (p?.status === 'ready' && p?.audio_status !== 'voicing'), ok: p?.status === 'ready', stage: p?.audio_status ? `audio: ${p.audio_status}` : null, error: p?.error_code || p?.audio_error || null }),
|
|
60
|
+
},
|
|
61
|
+
inkmation_render: {
|
|
62
|
+
label: 'Inkmation render (id = projectId:renderId)',
|
|
63
|
+
fetch: async (c, id) => { const [p, r] = split2(id); const x = await c.call('aistudio-inkmation-renders', { project_id: Number(p) }); return (x.renders || []).find((k) => String(k.id) === String(r)) || null },
|
|
64
|
+
norm: (r) => ({ status: r?.status, done: ['done', 'failed'].includes(r?.status), ok: r?.status === 'done', progress: num(r?.progress), url: r?.url || null, error: r?.error || null }),
|
|
65
|
+
},
|
|
66
|
+
design: {
|
|
67
|
+
label: 'Design Studio design (generate / add detail)',
|
|
68
|
+
fetch: (c, id) => c.call('ys-design-get', { design_id: Number(id) }).then((r) => r.design),
|
|
69
|
+
norm: (d) => ({ status: d?.status, done: d?.status !== 'working', ok: d?.status === 'ready', progress: num(pick(d, 'job.progress')), stage: pick(d, 'job.stage'), poster: d?.thumb_url || null, error: pick(d, 'job.error_code'), tokens_charged: num(pick(d, 'job.tokens_charged')) }),
|
|
70
|
+
},
|
|
71
|
+
design_export: {
|
|
72
|
+
label: 'Design Studio export',
|
|
73
|
+
fetch: (c, id) => c.call('ys-design-export-get', { export_id: Number(id) }).then((r) => r.export),
|
|
74
|
+
norm: (x) => ({ status: x?.status, done: ['ready', 'failed'].includes(x?.status), ok: x?.status === 'ready', url: x?.url || null, asset_id: x?.asset_id || null, error: x?.error_code || null, tokens_charged: num(x?.tokens_charged) }),
|
|
75
|
+
},
|
|
76
|
+
website: {
|
|
77
|
+
label: 'Website Studio site',
|
|
78
|
+
fetch: (c, id) => c.call('ys-website-get', { site_id: Number(id) }).then((r) => r.site),
|
|
79
|
+
norm: (s) => ({ status: s?.status, done: ['ready', 'failed'].includes(s?.status), ok: s?.status === 'ready', progress: num(s?.progress), url: s?.public_url || null, poster: s?.cover_url || null, zip_url: s?.zip_url || null, error: s?.error_code || null }),
|
|
80
|
+
},
|
|
81
|
+
promo: {
|
|
82
|
+
label: 'Website Promo video',
|
|
83
|
+
fetch: (c, id) => c.call('sitefilm-get', { project_id: Number(id) }).then((r) => r.project || r),
|
|
84
|
+
norm: (p) => ({ status: p?.status, done: ['review', 'ready', 'failed'].includes(p?.status), ok: ['review', 'ready'].includes(p?.status),
|
|
85
|
+
needs_action: p?.status === 'review' ? 'Script ready for review. Edit (promo save-script) or produce (yesilcam_promo_produce).' : null,
|
|
86
|
+
url: pick(p, 'result.video_url'), poster: pick(p, 'result.poster_url'), error: p?.error_code || null }),
|
|
87
|
+
},
|
|
88
|
+
assemble: {
|
|
89
|
+
label: 'Assemble / Disassemble video',
|
|
90
|
+
fetch: (c, id) => c.call('aistudio-assemble-get', { project_id: Number(id) }).then((r) => r.project),
|
|
91
|
+
norm: (p) => ({ status: p?.status, done: ['ready', 'failed'].includes(p?.status), ok: p?.status === 'ready', progress: num(p?.progress), stage: p?.stage || null,
|
|
92
|
+
url: pick(p, 'result.video_url'), poster: pick(p, 'result.poster_url'), asset_id: pick(p, 'result.asset_id'), error: p?.error_code || null }),
|
|
93
|
+
},
|
|
94
|
+
enhance: {
|
|
95
|
+
label: 'Product photo enhance',
|
|
96
|
+
fetch: (c, id) => c.call('aistudio-assemble-enhance-get', { job_id: Number(id) }),
|
|
97
|
+
norm: (j) => ({ status: j?.status, done: ['ready', 'failed'].includes(j?.status), ok: j?.status === 'ready', progress: num(j?.progress), url: pick(j, 'result.url'), asset_id: pick(j, 'result.asset_id'), error: j?.error_code || null }),
|
|
98
|
+
},
|
|
99
|
+
maket_design: {
|
|
100
|
+
label: 'Maket furnishing / landscape design',
|
|
101
|
+
fetch: (c, id) => c.call('maket-design-get', { jobId: String(id) }),
|
|
102
|
+
norm: (j) => ({ status: j?.status, done: ['ready', 'failed'].includes(j?.status), ok: j?.status === 'ready', progress: num(j?.progress), error: j?.error || null, tokens_charged: num(j?.charged) }),
|
|
103
|
+
},
|
|
104
|
+
maket_render: {
|
|
105
|
+
label: 'Maket photoreal render',
|
|
106
|
+
fetch: (c, id) => c.call('maket-render-get', { jobId: String(id) }),
|
|
107
|
+
norm: (j) => ({ status: j?.status, done: ['ready', 'failed'].includes(j?.status), ok: j?.status === 'ready', progress: num(j?.progress), url: pick(j, 'result.url'), error: j?.error || null, tokens_charged: num(j?.charged) }),
|
|
108
|
+
},
|
|
109
|
+
flow: {
|
|
110
|
+
label: 'Film Studio flow / Story to Film',
|
|
111
|
+
fetch: (c, id) => c.call('aistudio-flow-get', { flow_id: Number(id) }),
|
|
112
|
+
norm: (r) => { const f = r?.flow || {}; return { status: f.status, done: !['directing', 'casting', 'storyboarding', 'producing', 'assembling'].includes(f.status), ok: ['ready', 'completed'].includes(f.status),
|
|
113
|
+
needs_action: f.status === 'ready' ? 'Storyboard ready. Estimate and produce: yesilcam_flow_produce' : null,
|
|
114
|
+
url: pick(r, 'final_asset.spaces_url'), poster: pick(r, 'final_asset.thumb_url'), asset_id: f.final_asset_id || null, error: f.error_code || null } },
|
|
115
|
+
},
|
|
116
|
+
bible: {
|
|
117
|
+
label: 'Character Bible',
|
|
118
|
+
fetch: (c, id) => c.call('aistudio-character-bible-get', { character_id: Number(id) }),
|
|
119
|
+
norm: (b) => ({ status: b?.status, done: b?.status !== 'generating', ok: b?.status === 'ready', poster: pick(b, 'character.thumb_url'), error: b?.bible_error || null }),
|
|
120
|
+
},
|
|
121
|
+
auto_run: {
|
|
122
|
+
label: 'Automation run',
|
|
123
|
+
fetch: (c, id) => c.call('ys-influencer-auto', { op: 'run', run_id: Number(id) }).then((r) => r.run),
|
|
124
|
+
norm: (r) => ({ status: r?.status, done: ['delivered', 'failed', 'skipped', 'canceled', 'review'].includes(r?.status), ok: ['delivered', 'review'].includes(r?.status),
|
|
125
|
+
needs_action: r?.status === 'review' ? 'Waiting for your approval: yesilcam_automation_review action=approve|skip' : null, url: pick(r, 'result.url'), poster: r?.cover || null, error: r?.error_code || null }),
|
|
126
|
+
},
|
|
127
|
+
social_post: {
|
|
128
|
+
label: 'Social media post',
|
|
129
|
+
fetch: (c, id) => c.call('ys-social', { op: 'post', post_id: Number(id) }).then((r) => r.post || r),
|
|
130
|
+
norm: (p) => ({ status: p?.status, done: ['published', 'failed', 'canceled'].includes(p?.status), ok: p?.status === 'published', url: p?.permalink || p?.url || null, error: p?.error_code || null }),
|
|
131
|
+
},
|
|
132
|
+
cinema_ai: {
|
|
133
|
+
label: 'Cinema Studio presenter / subtitles',
|
|
134
|
+
fetch: (c, id) => c.call('aistudio-cinema-ai-job', { job_id: String(id) }),
|
|
135
|
+
norm: (j) => ({ status: j?.status, done: ['done', 'failed'].includes(j?.status), ok: j?.status === 'done', url: pick(j, 'item.url'), asset_id: pick(j, 'item.asset_id'), error: j?.error_code || null, tokens_charged: num(j?.tokens_charged), refunded: Boolean(j?.refunded) }),
|
|
136
|
+
},
|
|
137
|
+
}
|
|
138
|
+
export const WAIT_KINDS = Object.keys(KINDS)
|
|
139
|
+
export const waitKindHelp = () => Object.entries(KINDS).map(([k, v]) => `${k}: ${v.label}`).join('; ')
|
|
140
|
+
|
|
141
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
|
|
142
|
+
|
|
143
|
+
/** Tek okuma. */
|
|
144
|
+
export async function checkOnce(c, kind, id) {
|
|
145
|
+
const K = KINDS[kind]; if (!K) throw new Error(`unknown kind: ${kind}`)
|
|
146
|
+
const raw = await K.fetch(c, id)
|
|
147
|
+
if (raw == null) return { kind, id: String(id), status: 'not_found', done: true, ok: false }
|
|
148
|
+
return { kind, id: String(id), ...K.norm(raw) }
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Bitene (ya da işlem gerekene / süre dolana) kadar bekler. onTick(state) ilerleme için. */
|
|
152
|
+
export async function waitFor(c, kind, id, { intervalMs = 4000, timeoutMs = 20 * 60 * 1000, onTick, sleepImpl = sleep } = {}) {
|
|
153
|
+
const started = Date.now()
|
|
154
|
+
let errors = 0
|
|
155
|
+
for (;;) {
|
|
156
|
+
let st
|
|
157
|
+
try { st = await checkOnce(c, kind, id); errors = 0 }
|
|
158
|
+
catch (e) { if (++errors >= 4) throw e; await sleepImpl(intervalMs * errors); continue }
|
|
159
|
+
onTick?.(st)
|
|
160
|
+
if (st.done) return st
|
|
161
|
+
if (Date.now() - started > timeoutMs) return { ...st, timed_out: true, hint: `Still running. Check later: yesilcam wait ${kind} ${id}` }
|
|
162
|
+
await sleepImpl(intervalMs)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Geriye uyum (0.2): jobs.js yerine
|
|
167
|
+
export async function waitForJob(c, jobId, opts = {}) { const s = await waitFor(c, 'job', jobId, opts); return s }
|
package/src/jobs.js
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
// İş oluşturma ve bekleme. Sözleşme: aistudio_jobs.status ∈ queued|processing|succeeded|failed.
|
|
2
|
-
export const TIERS = ['draft', 'standard', 'cinema', 'cinemapro']
|
|
3
|
-
export const ASPECTS = ['1:1', '16:9', '9:16', '3:4', '4:3']
|
|
4
|
-
|
|
5
|
-
export function imageJob({ prompt, tier = 'draft', aspect = '16:9', framing }) {
|
|
6
|
-
const p = { job_type: 'text2image', prompt, quality_tier: tier, aspect_ratio: aspect }
|
|
7
|
-
if (framing) p.framing = framing
|
|
8
|
-
return p
|
|
9
|
-
}
|
|
10
|
-
export function videoJob({ prompt = '', tier = 'draft', aspect = '16:9', sourceAssetId, duration, resolution, generateAudio }) {
|
|
11
|
-
const p = { job_type: sourceAssetId ? 'image2video' : 'text2video', prompt, quality_tier: tier, aspect_ratio: aspect }
|
|
12
|
-
if (sourceAssetId) p.source_asset_id = Number(sourceAssetId)
|
|
13
|
-
if (duration) p.duration = Number(duration)
|
|
14
|
-
if (resolution) p.resolution = resolution
|
|
15
|
-
if (generateAudio) p.generate_audio = true
|
|
16
|
-
return p
|
|
17
|
-
}
|
|
18
|
-
export function tryonJob({ garmentAssetId, personAssetId, characterId, prompt = '' }) {
|
|
19
|
-
const p = { job_type: 'tryon', garment_asset_id: Number(garmentAssetId), prompt, quality_tier: 'standard' }
|
|
20
|
-
if (characterId) p.character_id = Number(characterId)
|
|
21
|
-
else if (personAssetId) p.source_asset_id = Number(personAssetId)
|
|
22
|
-
return p
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
|
|
26
|
-
const isDone = (s) => ['succeeded', 'failed', 'cancelled', 'refunded'].includes(String(s))
|
|
27
|
-
|
|
28
|
-
/** İş bitene kadar bekler (varsayılan 4 sn aralık, 20 dk). onTick(job) ilerleme için. */
|
|
29
|
-
export async function waitForJob(client, jobId, { intervalMs = 4000, timeoutMs = 20 * 60 * 1000, onTick, sleepImpl = sleep } = {}) {
|
|
30
|
-
const started = Date.now()
|
|
31
|
-
for (;;) {
|
|
32
|
-
const r = await client.jobStatus(Number(jobId))
|
|
33
|
-
const job = r?.job
|
|
34
|
-
onTick?.(job)
|
|
35
|
-
if (job && isDone(job.status)) return job
|
|
36
|
-
if (Date.now() - started > timeoutMs) throw new Error('job_wait_timeout')
|
|
37
|
-
await sleepImpl(intervalMs)
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
export function describeJob(job) {
|
|
41
|
-
if (!job) return null
|
|
42
|
-
return { id: String(job.id), type: job.job_type, status: job.status, tier: job.quality_tier, progress: job.render_progress != null ? Number(job.render_progress) : null,
|
|
43
|
-
tokens_charged: job.tokens_charged != null ? Number(job.tokens_charged) : null, error: job.error_code || null,
|
|
44
|
-
result: job.result_asset ? { asset_id: String(job.result_asset.id), kind: job.result_asset.kind, url: job.result_asset.spaces_url, thumb: job.result_asset.thumb_url } : null }
|
|
45
|
-
}
|