File size: 5,776 Bytes
ed9bf62
 
3d2098f
 
4021f36
3d2098f
ed9bf62
3d2098f
ed9bf62
 
 
 
 
 
 
 
 
 
 
 
d2d6ea9
 
 
3d2098f
d2d6ea9
3d2098f
 
 
 
 
 
 
 
 
 
 
d2d6ea9
3d2098f
 
 
 
 
 
 
 
 
 
d2d6ea9
 
 
 
 
 
 
 
 
 
 
 
 
 
3d2098f
 
d2d6ea9
 
 
 
 
 
 
 
 
3d2098f
d2d6ea9
 
 
 
 
3d2098f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ed9bf62
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3d2098f
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
import { useEffect, useState } from 'react'
import { api } from '../api'
import { useJob, ProgressBox, ErrorBox } from '../hooks'

const PRESETS = ['bold-center-karaoke', 'clean-lower-third', 'minimal', 'typewriter', 'punch']

export default function RenderStep({ reelData, refreshReel, setStep, projectId, reelId, selectReel, refreshProject }) {
  const job = useJob()
  // Translate this reel into another language → a new sibling reel.
  const tjob = useJob()
  const [langs, setLangs] = useState([])
  const [targetLang, setTargetLang] = useState('es')
  useEffect(() => { api.get('/api/translate/languages').then((d) => setLangs(d.languages || [])).catch(() => {}) }, [])
  const translate = async () => {
    try {
      const result = await tjob.start(`/api/projects/${projectId}/reels/${reelId}/translate`, { target_lang: targetLang })
      if (refreshProject) await refreshProject()
      if (selectReel && result?.reel) { selectReel(result.reel.id); setStep(4) }  // → Media: pick a voice in that language
    } catch { /* shown */ }
  }
  // Default to ONE variant that matches the editor (WYSIWYG); add more for A/B.
  const editPreset = reelData?.overlays?.caption_preset || PRESETS[0]
  const editCaptions = reelData?.overlays?.captions_on !== false
  const [specs, setSpecs] = useState([
    { name: 'As edited', caption_preset: editPreset, captions: editCaptions, voice: true, clip_offset: 0 },
  ])

  const lines = reelData?.voice?.lines?.length || 0
  // segments + captions + final per variant — rough progress out of total steps
  const totalSteps = specs.length * (lines + 2)
  const pct = job.running ? Math.min(95, Math.round((job.progress.length / Math.max(totalSteps, 1)) * 100)) : 0

  const start = async () => {
    try {
      await job.start(`/api/projects/${projectId}/reels/${reelId}/render`, { variants: specs })
      await refreshReel()
      setStep(7)
    } catch { /* shown */ }
  }

  return (
    <div>
      <h2>Render</h2>
      <p className="subtitle">
        Renders {specs.length} draft variants — different clip picks (from the alternates) and caption styles — to compare side by side.
      </p>

      {specs.map((s, i) => {
        const upd = (patch) => setSpecs(specs.map((x, j) => (j === i ? { ...x, ...patch } : x)))
        const captionsOn = s.captions !== false
        const voiceOn = s.voice !== false
        return (
          <div key={i} className="card">
            <div className="row">
              <strong style={{ width: 90 }}>{s.name}</strong>
              <label className="row" style={{ gap: 6 }}>
                <input type="checkbox" checked={captionsOn} onChange={(e) => upd({ captions: e.target.checked })} />
                <span className="muted">Captions</span>
              </label>
              <select value={s.caption_preset} disabled={!captionsOn}
                onChange={(e) => upd({ caption_preset: e.target.value })}>
                {PRESETS.map((p) => <option key={p} value={p}>{p}</option>)}
              </select>
              <label className="row" style={{ gap: 6 }}>
                <input type="checkbox" checked={voiceOn} onChange={(e) => upd({ voice: e.target.checked })} />
                <span className="muted">Voice</span>
              </label>
              <span className="tag">clips: alt #{s.clip_offset}</span>
              {specs.length > 1 && (
                <button className="btn small danger" onClick={() => setSpecs(specs.filter((_, j) => j !== i))}>Remove</button>
              )}
            </div>
          </div>
        )
      })}
      <p className="muted" style={{ marginTop: -4 }}>
        Turn off Captions for clips-only, or Voice for a music-only / silent cut. Overlays from the Effects step always render.
      </p>

      <ErrorBox error={job.error} onRetry={start} />
      {job.running && (
        <>
          <div className="bar"><div className="fill" style={{ width: `${pct}%` }} /></div>
          <ProgressBox progress={job.progress.length ? job.progress : ['Starting render…']} />
        </>
      )}

      <div className="row">
        {specs.length < 3 && (
          <button className="btn secondary"
            onClick={() => setSpecs([...specs, { name: `Variant ${'ABC'[specs.length]}`, caption_preset: PRESETS[specs.length % 3], clip_offset: specs.length }])}>
            + Add variant
          </button>
        )}
        <div className="grow" />
        <button className="btn" onClick={start} disabled={job.running}>
          {job.running ? `Rendering… ${pct}%` : `Render ${specs.length} variants`}
        </button>
      </div>

      {/* Translate → a new reel in another language (same visuals, native narration + captions). */}
      <div className="card" style={{ marginTop: 16 }}>
        <div className="row">
          <strong className="grow">🌍 Make this reel in another language</strong>
          <select value={targetLang} onChange={(e) => setTargetLang(e.target.value)} disabled={tjob.running}>
            {langs.map((l) => <option key={l.code} value={l.code}>{l.name}</option>)}
          </select>
          <button className="btn secondary" onClick={translate} disabled={tjob.running || !reelData?.script}>
            {tjob.running ? 'Translating…' : 'Translate'}
          </button>
        </div>
        <p className="muted" style={{ margin: '6px 0 0', fontSize: 13 }}>
          Creates a new reel with the script translated (visuals stay the same). Pick a matching voice on the Media step, then render — captions come out native.
        </p>
        {tjob.running && <div style={{ marginTop: 10 }}><ProgressBox progress={tjob.progress.length ? tjob.progress : ['Starting…']} /></div>}
        <ErrorBox error={tjob.error} />
      </div>
    </div>
  )
}