adyou 0.6.8 → 0.6.10

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.
@@ -16,6 +16,19 @@ import { isMockGen } from './images.js';
16
16
  import { checkArtifacts, checkFrames, checkProductMatch, checkSpeech } from './qa.js';
17
17
  /** 말하는 포맷의 음성 방식 — 영어만 Veo 네이티브 발화(실측 안정) · 그 외 언어는 TTS 내레이션(Veo 한국어·일본어 발화는 엉뚱한 대사 · 2026-09-16 사장님 검수) */
18
18
  export function speechModeFor(lang) { return lang.split('-')[0] === 'en' || hasSeedance() || !ttsAvailable() || isMockGen() ? 'native' : 'tts'; }
19
+ /** 영상 참고로 쓸 수 있는 사진만 — 비율 0.4~2.5 밖(가로 배너·세로 띠)은 영상 모델이 거절(Seedance 400)하거나 첫 프레임을 망친다 */
20
+ async function usableRefs(refs) {
21
+ const out = [];
22
+ for (const r of refs) {
23
+ try {
24
+ const p = await probe(r);
25
+ if (p.w && p.h && p.w / p.h >= 0.4 && p.w / p.h <= 2.5)
26
+ out.push(r);
27
+ }
28
+ catch { /* 못 읽으면 제외 */ }
29
+ }
30
+ return out;
31
+ }
19
32
  /** 영상용 엔드카드 PNG — 해당 비율의 포스터를 영상 크기로 렌더(언어별 문구) */
20
33
  export async function renderEndCard(dir, brief, concept, ratio, lang, backgrounds = {}) {
21
34
  try {
@@ -122,8 +135,9 @@ export async function produceConceptVideos(o) {
122
135
  log(` 화면 데모 컷 ${clip.sec}초 ✓ (생성 비용 0)`);
123
136
  }
124
137
  else {
125
- const person = format === 'ugc_selfie';
126
138
  const line = clip.speech ? (rl === board.lang ? clip.speech : lb0.speech || lb0.voice || clip.speech) : undefined;
139
+ // 말하는 클립(UGC · 대사 있는 라이프스타일)엔 참고 사진을 넣지 않는다 — Seedance 는 참고 이미지 비율 0.39~2.5 밖(사이트 배너 3.23)이면 400 (에어서울 라이프스타일 실사고 · 2026-09-17)
140
+ const person = format === 'ugc_selfie' || !!line;
127
141
  const mode = line ? speechModeFor(rl) : 'none';
128
142
  // 대사는 클립 안에서 여유 있게 끝나야 한다 — 뚝 끊김 방지: 짧은 한 문장 · 마지막 1~1.5초는 말 없이 미소/끄덕임
129
143
  // 2026-09-17 사장님 「말이 너무 느리다 · 빨리 치면 대사 더 들어가도 된다」 → 신난 친구 톤으로 빠르게 · 마지막 1초만 무언
@@ -163,7 +177,7 @@ export async function produceConceptVideos(o) {
163
177
  // 참고 사진은 실사 장르에만 — 애니·3D·일러스트·모션그래픽 컷에 사진(특히 로고)을 참고로 넣으면 세계관 대신 로고가 흐른다(ISEKAI'D 실사고 · 2026-09-17)
164
178
  const photoGenre = !['anime', '3d', 'illustration', 'motion_graphics'].includes(board.genre);
165
179
  try {
166
- r = await generateVideo({ file: cf, prompt, ratio: job.ratio, durationSec: clip.sec, model: clipModel, resolution: job.resolution, audio: job.audio, firstFrame, refs: !firstFrame && !person && photoGenre ? o.refs?.slice(0, 3) : undefined, negative: 'text, subtitles, captions, letters, watermark, logo, blurry, distorted face, extra fingers', log });
180
+ r = await generateVideo({ file: cf, prompt, ratio: job.ratio, durationSec: clip.sec, model: clipModel, resolution: job.resolution, audio: job.audio, firstFrame, refs: !firstFrame && !person && photoGenre ? (await usableRefs(o.refs || [])).slice(0, 3) : undefined, negative: 'text, subtitles, captions, letters, watermark, logo, blurry, distorted face, extra fingers', log });
167
181
  }
168
182
  catch (e) {
169
183
  if (!e.retryable)
@@ -6,7 +6,7 @@ import path from 'node:path';
6
6
  import satori from 'satori';
7
7
  import { Resvg } from '@resvg/resvg-js';
8
8
  import { HOME } from '../state.js';
9
- import { SIZES, wlen } from './specs.js';
9
+ import { SIZES, wlen, policyIssues } from './specs.js';
10
10
  const FONT_CDN = 'https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/packages/pretendard/dist/public/static';
11
11
  const WEIGHTS = [['Pretendard-ExtraBold', 800], ['Pretendard-Bold', 700], ['Pretendard-Medium', 500]];
12
12
  /** 언어별 글꼴 폴백 — Pretendard 는 한글·라틴만. 일본어=Pretendard JP(가나·한자) · 중국어=Noto Sans TC/SC · 태국어=Noto Sans Thai. 같은 family 이름으로 넣어 satori 가 빠진 글리프를 다음 글꼴에서 찾게 한다. */
@@ -109,6 +109,9 @@ export function cutWords(text, limit) {
109
109
  }
110
110
  export function wordsEl(text, style, align = 'left') {
111
111
  const words = String(text || '').split(/\s+/).filter(Boolean);
112
+ // 🔴 일본어·중국어는 띄어쓰기가 없어 한 어절이 줄 전체보다 길다 → nowrap 조각이 오른쪽으로 잘림(ISEKAI'D 일본어 엔드카드 · 2026-09-17) → 글자 단위로 줄바꿈
113
+ if (/[\u3040-\u30ff\u3400-\u9fff]/.test(text) && words.some((w) => [...w].length > 8))
114
+ return h('div', { display: 'flex', ...style, whiteSpace: 'normal', wordBreak: 'break-all', textAlign: align, justifyContent: align === 'center' ? 'center' : 'flex-start' }, String(text || '').trim());
112
115
  const gap = Math.round(Number(style.fontSize || 16) * 0.26);
113
116
  return h('div', { display: 'flex', flexWrap: 'wrap', justifyContent: align === 'center' ? 'center' : 'flex-start', columnGap: gap, rowGap: 0, ...style, whiteSpace: 'nowrap' }, words.map((w) => h('span', { whiteSpace: 'nowrap' }, w)));
114
117
  }
@@ -152,7 +155,8 @@ export function tree(size, concept, brief, bg, logo, ctaText) {
152
155
  ctaFs = Math.round(hh * 0.058);
153
156
  }
154
157
  const chipFs = Math.round(hh * 0.044);
155
- let proofs = widePoster ? [...(brief.proofPoints || []), ...(brief.usp || [])].map((x) => { const t = String(x || '').replace(/\s+/g, ' ').trim(); return cutWords(t, /[\u3000-\u9fff\uac00-\ud7af]/.test(t) ? 18 : 34); }).filter((x) => x && wlen(x) <= 36).filter((x, i, a) => a.indexOf(x) === i).slice(0, 3) : [];
158
+ // 알약은 통째로 들어가는 짧은 증거만(자르면 「영업보증보험 1.5억원 /」 처럼 어색 · 정책 위험 표현 제외)
159
+ let proofs = widePoster ? [...(brief.proofPoints || []), ...(brief.usp || [])].map((x) => String(x || '').replace(/\s+/g, ' ').trim()).filter((x) => x && wlen(x) <= 36 && !policyIssues(x).length).filter((x, i, a) => a.indexOf(x) === i).slice(0, 3) : [];
156
160
  const cjk = /[\u3000-\u9fff\uac00-\ud7af]/.test(body);
157
161
  let bodyText = banner ? cutWords(body, 70) : widePoster ? cutWords(body, cjk ? 60 : 110) : googleLight ? cutWords(body, 48) : body;
158
162
  if (widePoster) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adyou",
3
- "version": "0.6.8",
3
+ "version": "0.6.10",
4
4
  "description": "ADYou — 대행사 없이, 당신이 직접. 사이트 주소 하나로 광고 소재·매체 등록·자동 운영·보고까지: AI 광고 자율주행 CLI + MCP 서버(Meta·Google · 생성은 항상 PAUSED · 승인 뒤 시작)",
5
5
  "type": "module",
6
6
  "license": "MIT",