Function bodies 28 total
getDummyReports function · typescript · L405-L412 (8 LOC)apps/Repolog/src/AppMockupVideo.tsx
function getDummyReports(locale: 'ja' | 'en', dummyDay: number): DummyReport[] {
const allReports = locale === 'ja' ? jaReports : enReports;
const target = allReports.find((r) => r.day === dummyDay);
if (!target) return allReports.slice(0, 3);
// Return the target + 2 surrounding reports for list views
const idx = allReports.indexOf(target);
return [target, ...allReports.filter((_, i) => i !== idx).slice(0, 2)];
}withVariantB function · typescript · L5-L7 (3 LOC)apps/Repolog/src/i18n/variants.ts
export function withVariantB<
T extends { hookSubtitle: string; variant?: string },
>(props: T, hookSubtitle: string): T & { variant: 'B' } {ratioToY function · typescript · L75-L77 (3 LOC)packages/core/src/constants/design-tokens.ts
export function ratioToY(ratio: number): number {
return Math.round(ratio * VIDEO_HEIGHT);
}useLocale function · typescript · L3-L11 (9 LOC)packages/core/src/hooks/useLocale.ts
export function useLocale(locale: Locale) {
const isJapanese = locale === 'ja';
return {
locale,
isJapanese,
isEnglish: !isJapanese,
fontWeight: isJapanese ? 700 : 800,
};
}useSceneTiming function · typescript · L18-L39 (22 LOC)packages/core/src/hooks/useSceneTiming.ts
export function useSceneTiming(timing: SceneTiming): SceneFrames {
return useMemo(() => {
const fps = VIDEO_FPS;
const hookDuration = secToFrames(timing.hookDurationSec, fps);
const demoDuration = secToFrames(timing.demoDurationSec, fps);
const resultsDuration = secToFrames(timing.resultsDurationSec, fps);
const ctaDuration = secToFrames(timing.ctaDurationSec, fps);
return {
hookStart: 0,
hookDuration,
demoStart: hookDuration,
demoDuration,
resultsStart: hookDuration + demoDuration,
resultsDuration,
ctaStart: hookDuration + demoDuration + resultsDuration,
ctaDuration,
totalDuration:
hookDuration + demoDuration + resultsDuration + ctaDuration,
};
}, [timing]);
}useTransitionTiming function · typescript · L27-L40 (14 LOC)packages/core/src/hooks/useTransitionTiming.ts
export function useTransitionTiming(timing: SceneTiming): TransitionFrames {
const base = useSceneTiming(timing);
return useMemo(() => {
const tf = TRANSITIONS.durationFrames;
return {
...base,
transitionFrames: tf,
hookVisualDuration: base.hookDuration + tf,
demoVisualDuration: base.demoDuration + tf,
resultsVisualDuration: base.resultsDuration + tf,
ctaVisualDuration: base.ctaDuration,
};
}, [base]);
}t function · typescript · L11-L13 (3 LOC)packages/core/src/i18n/strings.ts
export function t(key: keyof typeof strings, locale: Locale): string {
return strings[key][locale];
}All rows scored by the Repobility analyzer (https://repobility.com)
useSlideIn function · typescript · L4-L24 (21 LOC)packages/core/src/utils/animations.ts
export function useSlideIn(
direction: 'left' | 'right' | 'up' | 'down' = 'up',
delay: number = 0,
) {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const progress = spring({ frame: frame - delay, fps, durationInFrames: 20 });
const distance = 100;
const offsets = {
left: { x: interpolate(progress, [0, 1], [-distance, 0]), y: 0 },
right: { x: interpolate(progress, [0, 1], [distance, 0]), y: 0 },
up: { x: 0, y: interpolate(progress, [0, 1], [distance, 0]) },
down: { x: 0, y: interpolate(progress, [0, 1], [-distance, 0]) },
};
return {
transform: `translate(${offsets[direction].x}px, ${offsets[direction].y}px)`,
opacity: progress,
};
}useFadeIn function · typescript · L26-L31 (6 LOC)packages/core/src/utils/animations.ts
export function useFadeIn(delay: number = 0) {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const opacity = spring({ frame: frame - delay, fps, durationInFrames: 15 });
return { opacity };
}useScale function · typescript · L33-L42 (10 LOC)packages/core/src/utils/animations.ts
export function useScale(delay: number = 0) {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const scale = spring({
frame: frame - delay,
fps,
config: { damping: 12 },
});
return { transform: `scale(${scale})` };
}useAnticipateScale function · typescript · L49-L82 (34 LOC)packages/core/src/utils/animations.ts
export function useAnticipateScale(delay: number = 0) {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const relFrame = frame - delay;
const { scaleDip, durationFrames: dipDur } = ANIMATION.anticipation;
// Phase 1: dip (0 → scaleDip over dipDur frames)
const dipProgress = interpolate(relFrame, [0, dipDur], [0, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
// Phase 2: spring from scaleDip to overshoot → 1.0
const springProgress = spring({
frame: relFrame - dipDur,
fps,
config: ANIMATION.spring.bouncy,
});
const scale =
relFrame <= 0
? 0
: relFrame <= dipDur
? interpolate(dipProgress, [0, 1], [0, scaleDip])
: interpolate(springProgress, [0, 1], [scaleDip, 1]);
const opacity = interpolate(relFrame, [0, 3], [0, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
return { transform: `scale(${scale})`, opacity };
}useStaggeredEntrance function · typescript · L87-L105 (19 LOC)packages/core/src/utils/animations.ts
export function useStaggeredEntrance(
index: number,
staggerFrames: number = ANIMATION.stagger.default,
) {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const delay = index * staggerFrames;
const progress = spring({
frame: frame - delay,
fps,
config: ANIMATION.spring.default,
});
const y = interpolate(progress, [0, 1], [ANIMATION.slideUp.distance, 0]);
return {
transform: `translateY(${y}px)`,
opacity: progress,
};
}usePopIn function · typescript · L111-L125 (15 LOC)packages/core/src/utils/animations.ts
export function usePopIn(delay: number = 0) {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const progress = spring({
frame: frame - delay,
fps,
config: ANIMATION.spring.snappy,
});
const scale = interpolate(progress, [0, 1], [0, 1]);
return {
transform: `scale(${scale})`,
opacity: progress,
};
}secToFrames function · typescript · L3-L5 (3 LOC)packages/core/src/utils/timing.ts
export function secToFrames(seconds: number, fps: number = VIDEO_FPS): number {
return Math.round(seconds * fps);
}framesToSec function · typescript · L7-L9 (3 LOC)packages/core/src/utils/timing.ts
export function framesToSec(frames: number, fps: number = VIDEO_FPS): number {
return frames / fps;
}Repobility · MCP-ready · https://repobility.com
pickText function · typescript · L73-L79 (7 LOC)scripts/convert-brief.ts
function pickText(
ja: string,
en: string | undefined,
locale: 'ja' | 'en',
): string {
return locale === 'en' && en ? en : ja;
}buildProps function · typescript · L81-L155 (75 LOC)scripts/convert-brief.ts
function buildProps(locale: 'ja' | 'en'): Record<string, unknown> {
const sceneTiming = {
hookDurationSec: brief.scenes.hook.duration_sec ?? 5,
demoDurationSec: brief.scenes.demo.duration_sec ?? 13,
resultsDurationSec: brief.scenes.results.duration_sec ?? 5,
ctaDurationSec: brief.scenes.cta.duration_sec ?? 5,
};
return {
appName: brief.app.name,
appTagline: brief.app.tagline,
locale,
accentColor: brief.style?.accent_color ?? '#6366F1',
backgroundColor: brief.style?.background_color ?? '#0F172A',
appIconPath: brief.app.icon || undefined,
screenRecordingPath: brief.assets.screen_recording ?? undefined,
screenshotPaths: brief.assets.screenshots,
hookSubtitle:
variant === 'B'
? pickText(
brief.scenes.hook.subtitle_variant_b ?? brief.scenes.hook.subtitle,
brief.scenes.hook.subtitle_en_variant_b ??
brief.scenes.hook.subtitle_en,
locale,
)
: pickText(
withComment function · typescript · L192-L197 (6 LOC)scripts/convert-brief.ts
function withComment(props: Record<string, unknown>): Record<string, unknown> {
return {
_comment: `DO NOT EDIT — auto-generated from video-brief.yaml. Run: npx tsx scripts/convert-brief.ts ${appName}`,
...props,
};
}generateProps function · typescript · L104-L195 (92 LOC)scripts/generate-day-props.ts
function generateProps(locale: 'ja' | 'en'): string {
const isJa = locale === 'ja';
const varName = `day${dayPadded}${isJa ? 'Ja' : 'En'}Props`;
const pick = (ja: string, en?: string) => (isJa ? ja : en || ja);
const hookSubtitle = pick(
brief.scenes.hook.subtitle,
brief.scenes.hook.subtitle_en,
);
const demoSubtitles = brief.scenes.demo.steps.map((s) =>
pick(s.subtitle, s.subtitle_en),
);
const resultsSubtitle = pick(
brief.scenes.results.subtitle,
brief.scenes.results.subtitle_en,
);
const ctaSubtitle = pick(
brief.scenes.cta.subtitle,
brief.scenes.cta.subtitle_en,
);
const demoIcons = brief.scenes.demo.steps.map((s) => s.icon || 'document');
const hookIcon = brief.scenes.hook.icon || 'document';
const hookVariantB = pick(
brief.scenes.hook.subtitle_variant_b || '',
brief.scenes.hook.subtitle_en_variant_b || '',
);
const accentColor = brief.style?.accent_color || '#39FF14';
const bgColor = brief.style?.backgrouescTs function · typescript · L197-L199 (3 LOC)scripts/generate-day-props.ts
function escTs(s: string): string {
return s.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
}regenerateDayConfigs function · typescript · L218-L302 (85 LOC)scripts/generate-day-props.ts
function regenerateDayConfigs() {
const files = fs
.readdirSync(i18nDir)
.filter((f) => /^day\d+-ja\.ts$/.test(f))
.sort();
const days = files
.map((f) => {
const match = f.match(/^day(\d+)-ja\.ts$/);
return match ? parseInt(match[1], 10) : 0;
})
.filter((d) => d > 0);
const imports: string[] = [];
const variantA: string[] = [];
const variantB: string[] = [];
for (const d of days) {
const dp = String(d).padStart(2, '0');
const jaVar = `day${dp}JaProps`;
const enVar = `day${dp}EnProps`;
imports.push(`import { ${jaVar} } from './day${dp}-ja';`);
imports.push(`import { ${enVar} } from './day${dp}-en';`);
variantA.push(` { id: 'Day${dp}-JA', props: ${jaVar} },`);
variantA.push(` { id: 'Day${dp}-EN', props: ${enVar} },`);
// Read variant B subtitle from YAML brief (source of truth)
const dayBriefPath = path.join(appDir, 'briefs', `day${dp}.yaml`);
let jaVariantB = '';
let enVariantB = '';
load_model function · python · L96-L121 (26 LOC)scripts/generate-narration.py
def load_model():
"""Qwen3-TTS モデルをロードする。"""
from qwen_tts import Qwen3TTSModel
print(f"[TTS] モデルをロード中: {MODEL_NAME}")
try:
import flash_attn # noqa: F401
attn_impl = "flash_attention_2"
print("[TTS] FlashAttention 2 を使用します(高速モード)")
except ImportError:
attn_impl = "eager"
print("[TTS] FlashAttention 2 未検出。eager モードで実行(やや低速)")
model = Qwen3TTSModel.from_pretrained(
MODEL_NAME,
device_map="cuda:0",
dtype=torch.bfloat16,
attn_implementation=attn_impl,
)
print("[TTS] モデルロード完了")
print(f"[TTS] 生成パラメータ: temperature={STABLE_GEN_KWARGS['temperature']}, "
f"top_p={STABLE_GEN_KWARGS['top_p']}, top_k={STABLE_GEN_KWARGS['top_k']}, "
f"repetition_penalty={STABLE_GEN_KWARGS['repetition_penalty']}")
return modelgenerate_audio function · python · L124-L149 (26 LOC)scripts/generate-narration.py
def generate_audio(model, text: str, locale: str, seed: int) -> tuple:
"""テキストから音声を生成する。
引数:
model: Qwen3TTSModel インスタンス
text: 読み上げるテキスト
locale: "ja" または "en"
seed: 乱数シード
戻り値:
(waveform, sample_rate): NumPy配列とサンプルレート
"""
config = VOICE_CONFIG[locale]
torch.manual_seed(seed)
torch.cuda.manual_seed(seed)
wavs, sr = model.generate_custom_voice(
text=text,
language=config["language"],
speaker=config["speaker"],
instruct=config["instruct"],
**STABLE_GEN_KWARGS,
)
return wavs[0], srRepobility · code-quality intelligence · https://repobility.com
generate_audio_with_retry function · python · L152-L200 (49 LOC)scripts/generate-narration.py
def generate_audio_with_retry(
model, text: str, locale: str, max_duration_sec: float
) -> tuple:
"""リトライ付きで音声を生成する。
音声がシーン尺の MAX_DURATION_MULTIPLIER 倍を超えた場合、
異なるシードで再生成を試みる(最大 MAX_RETRIES 回)。
引数:
model: Qwen3TTSModel インスタンス
text: 読み上げるテキスト
locale: "ja" または "en"
max_duration_sec: シーンの最大秒数
戻り値:
(waveform, sample_rate): 最も適切な音声
"""
duration_limit = max_duration_sec * MAX_DURATION_MULTIPLIER
best_waveform = None
best_sr = None
best_duration = float("inf")
for attempt in range(MAX_RETRIES):
seed = BASE_SEED + attempt
waveform, sr = generate_audio(model, text, locale, seed)
duration = len(waveform) / sr
if duration <= duration_limit:
# 許容範囲内ならそのまま返す
if attempt > 0:
print(f" リトライ {attempt + 1}/{MAX_RETRIES}: {duration:.1f}秒 → OK")
return waveform, sr
# 範囲外でも、これまでで最短なら記録
get_audio_duration function · python · L203-L205 (3 LOC)scripts/generate-narration.py
def get_audio_duration(waveform, sample_rate: int) -> float:
"""音声の長さ(秒)を計算する。"""
return len(waveform) / sample_ratesave_audio function · python · L208-L214 (7 LOC)scripts/generate-narration.py
def save_audio(waveform, sample_rate: int, output_path: str):
"""音声を WAV ファイルとして保存する。"""
os.makedirs(os.path.dirname(output_path), exist_ok=True)
sf.write(output_path, waveform, sample_rate)
duration = get_audio_duration(waveform, sample_rate)
print(f" 保存: {output_path} ({duration:.1f}秒)")
return durationextract_scenes_from_brief function · python · L217-L287 (71 LOC)scripts/generate-narration.py
def extract_scenes_from_brief(brief: dict, locale: str, narration_dir: Path) -> list:
"""YAML ブリーフファイルからシーン情報を抽出する。
引数:
brief: YAML をパースした辞書
locale: "ja" または "en"
narration_dir: WAV ファイルの出力ディレクトリ
戻り値:
シーンのリスト(main() の scenes と同じ形式)
"""
scenes_data = brief.get("scenes", {})
scenes = []
# ロケールに応じてナレーション/字幕フィールド名を決定
narration_key = "narration" if locale == "ja" else "narration_en"
subtitle_key = "subtitle" if locale == "ja" else "subtitle_en"
# シーン1: Hook
hook = scenes_data.get("hook", {})
hook_text = hook.get(narration_key) or hook.get(subtitle_key)
if hook_text:
scenes.append({
"name": "hook",
"text": hook_text,
"output": str(narration_dir / "hook.wav"),
"props_key": "hookNarrationPath",
"duration_sec": hook.get("duration_sec", 5),
})
# シーン2: Demo (複数ステップ)
demo = scenes_data.get("demo", {})
steps =main function · python · L290-L501 (212 LOC)scripts/generate-narration.py
def main():
parser = argparse.ArgumentParser(
description="Qwen3-TTS でナレーション音声を生成する"
)
parser.add_argument("app_name", help="アプリ名 (例: Repolog)")
parser.add_argument(
"--locale",
choices=["ja", "en"],
required=True,
help="言語 (ja=日本語, en=英語)",
)
parser.add_argument(
"--validate-timing",
action="store_true",
help="音声の長さとシーン尺のバリデーションを行う",
)
parser.add_argument(
"--root-dir",
default=None,
help="プロジェクトルートディレクトリ (デフォルト: スクリプトの親ディレクトリ)",
)
parser.add_argument(
"--brief",
default=None,
help=(
"YAML ブリーフファイルのパス (例: briefs/day01.yaml)。"
"指定時は JSON props の代わりに YAML からテキストを読み込む。"
"相対パスはアプリディレクトリからの相対パスとして解決される。"
),
)
args = parser.parse_args()
# パスの解決
script_dir = Path(__file__).resolve().parent
root_dir = Path(args.root_dir) if args.root_dir else script_dir.parent
app_dir = root_