ScaleValue Logo
ScaleValue

📦 Kahani Magic Assets

Complete asset lifecycle documentation
Last Updated: January 21, 2026


Asset Capability Matrix

| Asset | Create | Generate | Regenerate | Edit | Style | Clear | |-------|--------|----------|------------|------|-------|-------| | IMG | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | | TXT | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | AUD | ❌ | ✅ | ✅ | ❌ | ✅ | ✅ | | HIGH | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | | CAP | ❌ | ✅ | ✅ | ❌ | ✅ | ✅ | | KIN | ❌ | ✅ | ✅ | ❌ | ✅ | ✅ | | BGM | ❌ | ✅ | ❌ | ❌ | ✅ | ✅ | | SFX | ❌ | ✅ | ❌ | ❌ | ✅ | ✅ | | VID | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ |


1. IMAGE (IMG)

Description

Visual illustration per page generated from story context.

Fields in StoryPage

imagePrompt: string;           // AI-generated prompt
imageUrl?: string;             // Alias for generatedImageUrl
generatedImageUrl?: string;    // Hostinger CDN URL
isGeneratingImage?: boolean;   // Loading state
imageGenFailed?: boolean;      // Failure flag

Lifecycle

| Stage | Method | Details | |-------|--------|---------| | Generate | POST /generate-image | Uses imagePrompt + style modifiers | | Regenerate | Same API | Triggered manually | | Clear | UI action | Sets URL to null |

API Request

{
  text: string;           // Page text for context
  imagePrompt: string;    // Visual description
  imageStyleId?: string;  // "3d-animated", "watercolor", etc.
  characterAppearance?: string;  // Character consistency
}

Issues

| Severity | Issue | Fix | |----------|-------|-----| | 🔴 High | No inpainting/editing | Add edit API | | 🟡 Medium | No custom upload | Add upload option | | 🟢 Low | No filters | Add post-processing |


2. TEXT (TXT)

Description

Story narration text per page.

Fields in StoryPage

text: string;              // Main narration
pageNumber: number;        // 1-indexed
textStyle?: TextStyle;     // Typography options

Lifecycle

| Stage | Method | Details | |-------|--------|---------| | Generate | POST /generate-story | Initial story creation | | Regenerate | POST /regenerate-text | AI rewrites page | | Edit | useEditMode.ts | Inline user editing | | Style | StyleToolbar | Font, size, effects |

Issues

| Severity | Issue | Fix | |----------|-------|-----| | 🔴 High | Edit clears animations | Auto-regenerate KIN/CAP | | 🟡 Medium | No undo for text | Add text history |


3. AUDIO (AUD)

Description

TTS narration audio per page.

Fields in StoryPage

audioUrl?: string;            // Hostinger CDN URL
audioBase64?: string;         // Temp before upload
audioGenerationStatus?: 'pending' | 'generating' | 'ready' | 'failed';
audioDuration?: number;       // Duration in seconds

Lifecycle

| Stage | Method | Details | |-------|--------|---------| | Generate | POST /generate-audio | Gemini TTS → ElevenLabs fallback | | Regenerate | Same API | Manual trigger | | Style | Voice presets | voiceStyleId, emotion tags | | Clear | UI action | Clears audio + dependent assets |

Data Flow

text + languageCode + voiceStyleId
    → Emotion analysis
    → Gemini TTS (primary)
    → ElevenLabs TTS (fallback)
    → audioBase64
    → Hostinger upload
    → audioUrl

Issues

| Severity | Issue | Fix | |----------|-------|-----| | 🔴 Critical | No word timestamps | ElevenLabs with_timestamps | | 🔴 Critical | languageCode missing | Fix flow from UI | | 🟡 Medium | No waveform UI | Add visualization |


4. WORD TIMINGS / HIGHLIGHTS (HIGH)

Description

Word-level timestamps for karaoke-style highlighting.

Fields in StoryPage

wordTimings?: WordTiming[];   // Primary field
timings?: WordTiming[];       // Alias

interface WordTiming {
  word: string;
  start: number;  // seconds
  end: number;    // seconds
}

Lifecycle

| Stage | Method | Details | |-------|--------|---------| | Generate | POST /align-text or heuristic | From audio analysis | | Regenerate | Triggered by audio regen | Automatic | | Clear | Triggered by audio clear | Automatic |

Current Implementation

// HEURISTIC (inaccurate) - src/app/api/kahani-magic/align-text/route.ts
const avgWordDuration = audioDuration / words.length;
words.forEach((word, i) => {
  timings.push({
    word,
    start: i * avgWordDuration,
    end: (i + 1) * avgWordDuration
  });
});

Issues

| Severity | Issue | Fix | |----------|-------|-----| | 🔴 Critical | Heuristic-only | Use TTS timestamps | | 🔴 Critical | Drift accumulates | Real word boundaries | | 🟡 Medium | Manual adjustment | Add timing editor |


5. CAPTIONS (CAP)

Description

Phrase-based typography with emotion and layout analysis.

Fields in StoryPage

phraseAnalysis?: PhraseAnalysis[];
typographyPreset?: TypographyPreset;

interface PhraseAnalysis {
  words: string[];
  powerWordIndices: number[];
  powerWordTypes: PowerWordType[];
  layout: 'stacked' | 'diagonal' | 'centered' | 'split';
  emotion: 'happy' | 'sad' | 'exciting' | 'dramatic' | 'calm';
  sfx: SFXTrigger | null;
  start: number;
  end: number;
}

Lifecycle

| Stage | Method | Details | |-------|--------|---------| | Generate | POST /analyze-phrases | AI phrase analysis | | Style | Animation presets | Caption mode options |

Issues

| Severity | Issue | Fix | |----------|-------|-----| | 🔴 High | languageCode not passed | Add to API call | | 🟡 Medium | Timing sync | Use audio timestamps | | 🟡 Medium | Layout collisions | Add collision detection |


6. KINETIC ANIMATIONS (KIN)

Description

Word-level animation effects for dynamic text display.

Fields in StoryPage

kineticAnimations?: WordAnimation[];
animationMode?: TextAnimationMode;

interface WordAnimation {
  word: string;
  effect: KineticEffect;
  direction?: 'left' | 'right' | 'up' | 'down';
  isPowerWord?: boolean;
}

type KineticEffect = 'zoom' | 'slide' | 'bounce' | 'fade' | 'pop' | 
                     'wave' | 'shake' | 'flip' | 'typewriter' | 'morph';

Lifecycle

| Stage | Method | Details | |-------|--------|---------| | Generate | POST /generate-kinetic | AI assigns effects | | Regenerate | Same API | Manual trigger | | Style | StyleToolbar | Mode selection |

Issues

| Severity | Issue | Fix | |----------|-------|-----| | 🔴 Critical | Array mismatch | Sync with word count | | 🔴 Critical | Cleared on edit | Preserve + auto-regen | | 🟢 Low | No per-word edit | Add animation editor |


7. BACKGROUND MUSIC (BGM)

Description

Style-aware ambient music with narration ducking.

Fields in StoryPage

audioCues?: AudioCues;

interface AudioCues {
  bgmMood: BGMMood;  // 'peaceful' | 'tense' | 'joyful' | etc.
  ambientScene: AmbientScene;
  sfxTriggers: SFXTriggerCue[];
}

Service: bgmService.ts

// Key methods
initialize(storyStyle: string): Promise<void>
play(): void
pause(): void
duckForNarration(): void
restoreAfterNarration(): void
setVolume(volume: number): void

Features

  • ✅ Mood-based selection (8 moods)
  • ✅ Auto-ducking during narration
  • ✅ Volume control
  • ✅ Toggle on/off

Issues

| Severity | Issue | Fix | |----------|-------|-----| | 🟡 Medium | No preloading | Preload on story load | | 🟢 Low | Limited moods | Expand library |


8. SOUND EFFECTS (SFX)

Description

Word-triggered contextual sound effects.

Fields in StoryPage

interface SFXTriggerCue {
  word: string;
  wordIndex: number;
  sound: string;        // "thunder", "laughter"
  soundPrompt?: string; // AI generation prompt
  volume?: number;
}

Service: sfxService.ts

// Key methods
playSFX(trigger: SFXTrigger, volume: number): void
playEmotionalSFX(emotion: EmotionalSFX): void
onWordHighlight(wordIndex: number): void

Features

  • ✅ AI-generated triggers
  • ✅ ElevenLabs sound generation
  • ✅ Word-synced playback
  • ✅ Volume control

Issues

| Severity | Issue | Fix | |----------|-------|-----| | 🟡 Medium | Basic caching | Improve cache | | 🟡 Medium | Timing imprecise | Link to timestamps |


9. VIDEO (VID)

Description

AI-generated video segments per page.

Fields in StoryPage

videoSegments?: VideoSegment[];
videoUrl?: string;
videoStatus?: 'pending' | 'planning' | 'generating' | 'ready' | 'partial' | 'failed';

interface VideoSegment {
  order: number;
  duration: number;
  prompt: string;
  videoBase64?: string;
  videoUrl?: string;
  status: 'pending' | 'generating' | 'ready' | 'failed';
  loopEffect?: 'loop' | 'pingpong' | 'once' | 'slowmo' | 'reverse';
}

Lifecycle

| Stage | Method | Details | |-------|--------|---------| | Plan | POST /plan-segments | AI plans video structure | | Generate | POST /generate-segment | Veo generates video | | Edit | Timeline editor | Trim, effects | | Style | Loop effects | pingpong, slowmo, etc. |

Features

  • ✅ Multi-segment planning
  • ✅ Individual segment generation
  • ✅ Loop effects
  • ✅ Timeline editing

Export Behavior (storyToCombo)

When exporting to Combo editor, video is preferred over image if available:

  1. Check page.videoUrl (direct video URL)
  2. Check page.videoSegments[].videoUrl where status === 'ready'
  3. Falls back to image (page.imageUrl or page.generatedImageUrl) if no video
// Logic in src/lib/storyToCombo.ts
const videoUrl = page.videoUrl || 
    (page.videoSegments?.find(s => s.status === 'ready')?.videoUrl);
if (videoUrl) { /* export as Video clip */ }
else if (imageUrl) { /* export as Image clip (fallback) */ }

Asset Dependencies

graph TD
    TXT[TEXT] --> AUD[AUDIO]
    AUD --> HIGH[WORD TIMINGS]
    TXT --> CAP[CAPTIONS]
    TXT --> KIN[KINETIC]
    HIGH --> CAP
    HIGH --> SFX[SFX]
    TXT --> IMG[IMAGE]
    TXT --> VID[VIDEO]
    style AUD fill:#ff6b6b
    style HIGH fill:#ff6b6b
    style CAP fill:#ffd93d
    style KIN fill:#ff6b6b

Legend: 🔴 Critical issues | 🟡 Medium issues | ✅ Working


Language Data Architecture (V3)

Unified Content Format

As of January 2026, all language-specific data is stored in unified page.content[lang]:

// V3 Format - Single source of truth
interface LanguageContent {
  text?: string;                      // Translated text
  audio?: {
    base64?: string;                  // Temp before upload
    url?: string;                     // Hostinger CDN URL
    duration?: number;                // Duration in seconds
    status: 'pending' | 'generating' | 'ready' | 'failed';
  };
  wordTimings?: WordTiming[];         // Karaoke highlights
  phrases?: PhraseAnalysis[];         // Caption mode
  kinetic?: WordAnimation[];          // Animation effects
}

// In StoryPage
content?: { [langCode: string]: LanguageContent };

Access via pageUtils

All consumers use unified access layer:

import { getPageText, getPageAudio, getWordTimings } from './pageUtils';

// Reads V3 format with fallback to root fields for original language
const text = getPageText(page, langCode, story.originalLanguage);
const audio = getPageAudio(page, langCode, story.originalLanguage);

Migration from Legacy Formats

Old data is migrated on-the-fly:

  • story.translations[lang]page.content[lang].text
  • page.localizedAssets[lang]page.content[lang] (deprecated)

Storage Flow

page.content[lang].audio.base64
    → storageService.ts
    → Hostinger upload
    → page.content[lang].audio.url
    → Firebase (base64 deleted, URL persisted)

Legacy Format (Deprecated)

⚠️ DEPRECATED: Use page.content[lang] instead

// OLD - Do not use for new code
interface LocalizedPageAssets {
  text?: string;
  audioBase64?: string;
  audioUrl?: string;
  audioDuration?: number;
  wordTimings?: WordTiming[];
  phraseAnalysis?: PhraseAnalysis[];
  kineticAnimations?: WordAnimation[];
  audioGenerationStatus?: 'pending' | 'generating' | 'ready' | 'failed';
}

localizedAssets?: { [langCode: string]: LocalizedPageAssets };

See individual asset docs in ./assets/ for more details