@praeko/studio-core 0.1.0 → 0.2.0
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/CHANGELOG.md +11 -2
- package/LICENSES.md +28 -30
- package/README.md +4 -6
- package/dist/engine.d.ts +56 -0
- package/dist/index.d.ts +6 -5
- package/dist/index.js +155 -150
- package/dist/long.d.ts +39 -0
- package/dist/theme-context.d.ts +6 -0
- package/dist/theme.d.ts +37 -0
- package/dist/util.d.ts +7 -6
- package/dist/word.d.ts +6 -0
- package/package.json +4 -3
- package/dist/longo.d.ts +0 -39
- package/dist/motor.d.ts +0 -56
- package/dist/palavra.d.ts +0 -5
- package/dist/tema.d.ts +0 -37
package/CHANGELOG.md
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and [SemVer](https://semver.org/).
|
|
4
|
+
|
|
5
|
+
## [0.2.0] - unreleased
|
|
6
|
+
|
|
7
|
+
Breaking: the whole public API is English. There are no deprecated aliases; move to the new names. Published types are generated by `tsc`, and their relative specifiers end in `.js`.
|
|
8
|
+
|
|
9
|
+
- Types and data: `Palavra` -> `Word`, `Tema` -> `Theme`, `TEMA_PADRAO` -> `DEFAULT_THEME`, `EpData` -> `EpisodeData` (`voz` -> `voice`, `palavras` -> `words`, `cenas` -> `scenes`, `fechamento` -> `outro`, `fundoLinhas` -> `backgroundLines`, `legendaPx` -> `captionPx`, `golpes` -> `hits`), `EpCena` -> `EpisodeScene` (`rotulo` -> `label`, `sons` -> `sounds`), `Som` -> `Sound` (`f` -> `sound`), `Golpe` -> `Hit` (`tipo` -> `type`, `tudo` -> `all`), `Grupo` -> `Group`, `LongoData` -> `LongData`, `CenaLongo` -> `LongScene`, `FechoLongo` -> `LongEndcard`.
|
|
10
|
+
- Functions and components: `Episodio` -> `Episode`, `Longo` -> `Long`, `Rotulo` -> `Label`, `Fundo` -> `Background`, `agrupar` -> `groupWords`, `TemaProvider` -> `ThemeProvider` (prop `tema` -> `theme`), `useTema` -> `useTheme`, `temaDaMarca` -> `themeFromBrand`, `surge` -> `appear`, `mkAt` -> `makeAt`, `mkAtG` -> `makeAtGlobal`, `janela` -> `fadeWindow`.
|
|
11
|
+
- Theme fields: `fundo` -> `background`, `fundoCapa` -> `coverBackground`, `destaque` -> `accent`, `texto` -> `text`, `suave` -> `muted`, `erro` -> `error`, `caixa` -> `box`, `caixaLongo` -> `longBox`, `codigo` -> `code`, `fonte` -> `font`. New `mascotPath` (default `mascots/{name}.png`). `themeFromBrand` reads the English `brand.json` shape (`colors`, `fonts.title`, `fonts.mono`).
|
|
12
|
+
- Error messages are English (`phrase not found: ...`).
|
|
4
13
|
|
|
5
14
|
## [0.1.0] - 2026-11-07
|
|
6
15
|
|
|
7
|
-
|
|
16
|
+
First published version, extracted from the project where it was born. Publishes compiled `dist/` (ESM + `.d.ts`); `react` and `remotion` are peers.
|
package/LICENSES.md
CHANGED
|
@@ -1,38 +1,36 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Praeko Studio licenses
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Verified on 2026-11-07. This file is not legal advice; each vendor's own text prevails.
|
|
4
4
|
|
|
5
|
-
##
|
|
6
|
-
|
|
5
|
+
## Praeko Studio's code
|
|
6
|
+
The packages `@praeko/studio`, `@praeko/studio-core` and `@praeko/studio-kit` are **MIT** (see `LICENSE`, Copyright (c) 2026 Marcone Santos), like `@praeko/sdk` and `@praeko/cli`. The sound effects (`pop`, `ding`, `thump`, `whoosh`) and the `avatar.png` of the project template were created for Studio (sounds synthesized by `scripts/generate-sfx.mjs`, with no third-party samples) and follow the same MIT license. The package ships no fonts, music, stock images or third-party art.
|
|
7
7
|
|
|
8
|
-
##
|
|
9
|
-
> **Remotion.**
|
|
10
|
-
>
|
|
11
|
-
> **Remotion** is source-available, not open source. Free for individuals, non-profits, evaluation and companies of up to 3 employees (commercial use included); companies of 4+ need the Company License (remotion.pro/license). You install and run Remotion, so the license is yours. They also sell plans for programmatic/agent-driven rendering; their FAQ does not say whether a local agent running `remotion render` counts: ask Remotion before using at scale. Praeko Studio does not copy or modify Remotion code; it uses it as a dependency you install.
|
|
8
|
+
## Notice you need to see (Remotion is NOT MIT)
|
|
9
|
+
> **Remotion.** Remotion is *source-available*, not open source. It is **free** for individuals, non-profits, evaluation and companies with **up to 3 employees** (commercial use included). Companies with **4 or more people** need the **Company License** (remotion.pro/license). You install and run Remotion, so the license is **yours**, not Praeko's. Remotion also sells plans for those who **automate renders by program or agent** (Creators, Automators); their FAQ does not expressly say whether a local agent running `remotion render` counts: **ask Remotion** before using at scale. Praeko Studio does not copy or modify Remotion code: it uses it as a dependency (peer) that you install.
|
|
12
10
|
|
|
13
|
-
|
|
11
|
+
This notice appears in the `README`, at install time (`praeko studio init`), in `praeko studio doctor` and in `praeko studio licenses` (`--accept` records that you read it in `~/.config/praeko/studio/licenses.json`).
|
|
14
12
|
|
|
15
|
-
**
|
|
13
|
+
**Engines.** Remotion is the default engine for videos **and** covers. With `--engine hyperframes` (or `"engine": "hyperframes"` in `brand.json`) the video **and** the cover are rendered by HyperFrames and nothing from Remotion is used, so a project that only uses HyperFrames does not need a Remotion license. Scenes or covers written as TSX (`component:`) only exist in Remotion.
|
|
16
14
|
|
|
17
|
-
**
|
|
15
|
+
**We do not promise "free forever":** Remotion's conditions may change (their own page warns that the license changes in Remotion 5.0). Review the license when you upgrade.
|
|
18
16
|
|
|
19
|
-
##
|
|
20
|
-
|
|
|
17
|
+
## Third-party dependencies
|
|
18
|
+
| Component | Where | License | Source |
|
|
21
19
|
|---|---|---|---|
|
|
22
|
-
| `remotion`, `@remotion/cli` (4.0.539) |
|
|
23
|
-
| `react`, `react-dom` (19.2.3) |
|
|
24
|
-
| `@praeko/studio-core`, `-kit` |
|
|
25
|
-
| `yaml` (2.9.1) |
|
|
26
|
-
| `@elevenlabs/elevenlabs-js` (2.x) |
|
|
27
|
-
| `hyperframes` (0.9.13,
|
|
28
|
-
| `typescript`, `esbuild`, `@types/*` |
|
|
29
|
-
| ffmpeg / ffprobe |
|
|
30
|
-
| Chrome Headless Shell / Chromium |
|
|
31
|
-
|
|
32
|
-
##
|
|
33
|
-
- **ElevenLabs**:
|
|
34
|
-
- **VoiceStudio
|
|
35
|
-
- **YouTube, Instagram, TikTok**:
|
|
36
|
-
|
|
37
|
-
##
|
|
38
|
-
|
|
20
|
+
| `remotion`, `@remotion/cli` (4.0.539) | installed in the user's **project** (peer of core/kit) | Remotion License (see above), not MIT | remotion.pro/license; github.com/remotion-dev/remotion (LICENSE.md) |
|
|
21
|
+
| `react`, `react-dom` (19.2.3) | user's project (peer) | MIT | each package's package.json |
|
|
22
|
+
| `@praeko/studio-core`, `-kit` | user's project | MIT | LICENSE |
|
|
23
|
+
| `yaml` (2.9.1) | embedded in the `@praeko/studio` bundle | ISC | package.json |
|
|
24
|
+
| `@elevenlabs/elevenlabs-js` (2.x) | dependency of `@praeko/studio` | MIT | package.json |
|
|
25
|
+
| `hyperframes` (0.9.13, optional engine) | downloaded by `npx` the first time you use `--engine hyperframes` | Apache-2.0 | github.com/heygen-com/hyperframes |
|
|
26
|
+
| `typescript`, `esbuild`, `@types/*` | development/build only | Apache-2.0 / MIT | package.json |
|
|
27
|
+
| ffmpeg / ffprobe | you install | LGPL or GPL, depending on the build | ffmpeg.org/legal.html |
|
|
28
|
+
| Chrome Headless Shell / Chromium | Remotion downloads it on the first render | Chromium/Google licenses | remotion.dev/docs/miscellaneous/chrome-headless-shell |
|
|
29
|
+
|
|
30
|
+
## Services and models (you sign up)
|
|
31
|
+
- **ElevenLabs**: its terms and your plan (voice cloning only with rights over the voice). The key is yours; Studio never prints it.
|
|
32
|
+
- **Local VoiceStudio** (optional): the OmniVoice model is **CC-BY-NC (non-commercial use)**. It is not the default in any Studio configuration and **must not be used in monetized content**. Check the license of any local model you use.
|
|
33
|
+
- **YouTube, Instagram, TikTok**: each network's terms and synthetic-content rules (see `praeko studio docs publish`).
|
|
34
|
+
|
|
35
|
+
## Fonts and media
|
|
36
|
+
No font is distributed. The project template uses system fonts (`Arial Black`, `Menlo`) that `doctor` checks; swap them in `brand.json` for open fonts (OFL) if you want the video to look the same on every system. Music, images and clips you add are your responsibility (rights and licenses).
|
package/README.md
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
# @praeko/studio-core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The Praeko Studio engine (Remotion parts): voice-driven episodes with timed words, word-by-word captions, editing hits, outro and brand theme. A library for projects created with [`@praeko/studio`](https://www.npmjs.com/package/@praeko/studio) (`praeko studio init` installs and configures it).
|
|
4
4
|
|
|
5
|
-
> **
|
|
6
|
-
>
|
|
7
|
-
> **Licenses.** This package is MIT and uses **Remotion** as a peer dependency that **you** install. Remotion is **not MIT**: free for individuals, non-profits and companies of up to 3 employees; companies of 4+ need the Company License.
|
|
5
|
+
> **Licenses.** This package is MIT and uses **Remotion** as a peer dependency that **you** install. Remotion is **not MIT**: free for individuals, non-profits and companies of up to 3 employees; companies of 4 or more people need the Company License ([remotion.pro/license](https://www.remotion.pro/license)). Details in [LICENSES.md](./LICENSES.md).
|
|
8
6
|
|
|
9
|
-
##
|
|
7
|
+
## Install
|
|
10
8
|
```sh
|
|
11
9
|
# npm
|
|
12
10
|
npm install @praeko/studio-core remotion react
|
|
@@ -17,4 +15,4 @@ pnpm add @praeko/studio-core remotion react
|
|
|
17
15
|
# bun
|
|
18
16
|
bun add @praeko/studio-core remotion react
|
|
19
17
|
```
|
|
20
|
-
Peers: `remotion` ^4.0.539 (
|
|
18
|
+
Peers: `remotion` ^4.0.539 (all `@remotion/*` packages on the same version) and `react` ^19. Node 22 or newer. Usage: `praeko studio docs episode-yaml`.
|
package/dist/engine.d.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { Word } from "./word.js";
|
|
2
|
+
/**
|
|
3
|
+
* PRAEKO STUDIO ENGINE.
|
|
4
|
+
* An episode is just data (EpisodeData): voice, timed words, scenes (card + label + sounds) and outro.
|
|
5
|
+
* The rest (background, captions, progress bar, outro, effects) is shared and uses the brand theme.
|
|
6
|
+
*/
|
|
7
|
+
export type Sound = {
|
|
8
|
+
at: number;
|
|
9
|
+
sound: "pop" | "ding" | "thump" | "whoosh";
|
|
10
|
+
v?: number;
|
|
11
|
+
};
|
|
12
|
+
export type Hit = {
|
|
13
|
+
at: number;
|
|
14
|
+
type: "shake" | "zoom" | "flash" | "all";
|
|
15
|
+
};
|
|
16
|
+
export type EpisodeScene = {
|
|
17
|
+
a: number;
|
|
18
|
+
b: number;
|
|
19
|
+
C: React.FC<{
|
|
20
|
+
dur: number;
|
|
21
|
+
}>;
|
|
22
|
+
label?: string;
|
|
23
|
+
/** sounds relative to the start of the scene (s) */
|
|
24
|
+
sounds?: Sound[];
|
|
25
|
+
};
|
|
26
|
+
export type EpisodeData = {
|
|
27
|
+
id: string;
|
|
28
|
+
durS: number;
|
|
29
|
+
voice: string;
|
|
30
|
+
words: Word[];
|
|
31
|
+
scenes: EpisodeScene[];
|
|
32
|
+
outro: {
|
|
33
|
+
a: number;
|
|
34
|
+
cta: string;
|
|
35
|
+
};
|
|
36
|
+
/** blurred "terminal" lines in the background */
|
|
37
|
+
backgroundLines?: string[];
|
|
38
|
+
/** caption size (default 90) */
|
|
39
|
+
captionPx?: number;
|
|
40
|
+
/** editing hits: quick shake, flash and zoom at instants (s) of the narration */
|
|
41
|
+
hits?: Hit[];
|
|
42
|
+
};
|
|
43
|
+
export declare const Label: React.FC<{
|
|
44
|
+
text: string;
|
|
45
|
+
}>;
|
|
46
|
+
/** Clean background: dark, a pulsing amber glow and dots drifting up slowly. */
|
|
47
|
+
export declare const Background: React.FC;
|
|
48
|
+
export type Group = {
|
|
49
|
+
words: Word[];
|
|
50
|
+
a: number;
|
|
51
|
+
b: number;
|
|
52
|
+
};
|
|
53
|
+
export declare const groupWords: (ps: Word[]) => Group[];
|
|
54
|
+
export declare const Episode: React.FC<{
|
|
55
|
+
d: EpisodeData;
|
|
56
|
+
}>;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
export * from "./
|
|
2
|
-
export * from "./
|
|
3
|
-
export * from "./
|
|
4
|
-
export * from "./
|
|
5
|
-
export * from "./
|
|
1
|
+
export * from "./engine.js";
|
|
2
|
+
export * from "./word.js";
|
|
3
|
+
export * from "./theme.js";
|
|
4
|
+
export * from "./theme-context.js";
|
|
5
|
+
export * from "./util.js";
|
|
6
|
+
export * from "./long.js";
|
package/dist/index.js
CHANGED
|
@@ -1,56 +1,61 @@
|
|
|
1
|
-
// src/
|
|
1
|
+
// src/engine.tsx
|
|
2
2
|
import { AbsoluteFill, Audio, Img, Sequence, interpolate as interpolate2, spring, staticFile, useCurrentFrame, useVideoConfig } from "remotion";
|
|
3
3
|
|
|
4
|
-
// src/
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
texto: "#f3ece0",
|
|
12
|
-
suave: "#a89c8a",
|
|
4
|
+
// src/theme.ts
|
|
5
|
+
var BASE = {
|
|
6
|
+
background: "#14110f",
|
|
7
|
+
coverBackground: "#0e0c14",
|
|
8
|
+
accent: "#f4b942",
|
|
9
|
+
text: "#f3ece0",
|
|
10
|
+
muted: "#a89c8a",
|
|
13
11
|
ok: "#7ee787",
|
|
14
|
-
|
|
12
|
+
error: "#ff6b5e",
|
|
15
13
|
info: "#7fd7ff",
|
|
16
14
|
extra: "#a98bff",
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
box: "#2a2118",
|
|
16
|
+
code: "#1b1610",
|
|
17
|
+
font: "'Arial Black', 'Helvetica Neue', system-ui, sans-serif",
|
|
20
18
|
mono: "Menlo, 'Courier New', monospace",
|
|
21
|
-
handle: "@
|
|
22
|
-
avatar: "
|
|
19
|
+
handle: "@yourchannel",
|
|
20
|
+
avatar: "cover/avatar.png",
|
|
21
|
+
mascotPath: "mascots/{name}.png"
|
|
23
22
|
};
|
|
24
|
-
var
|
|
25
|
-
var
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
...
|
|
29
|
-
...m.
|
|
30
|
-
...m.fontes?.titulo ? { fonte: m.fontes.titulo } : {},
|
|
31
|
-
...m.fontes?.mono ? { mono: m.fontes.mono } : {},
|
|
23
|
+
var DEFAULT_THEME = BASE;
|
|
24
|
+
var themeFromBrand = (m) => ({
|
|
25
|
+
...DEFAULT_THEME,
|
|
26
|
+
...m.colors ?? {},
|
|
27
|
+
...m.fonts?.title ? { font: m.fonts.title } : {},
|
|
28
|
+
...m.fonts?.mono ? { mono: m.fonts.mono } : {},
|
|
32
29
|
...m.handle ? { handle: m.handle } : {},
|
|
33
|
-
...m.avatar ? { avatar: m.avatar } : {}
|
|
30
|
+
...m.avatar ? { avatar: m.avatar } : {},
|
|
31
|
+
...m.mascotPath ? { mascotPath: m.mascotPath } : {}
|
|
34
32
|
});
|
|
35
33
|
var rgba = (hex, a) => {
|
|
36
34
|
const n = parseInt(hex.slice(1), 16);
|
|
37
35
|
return `rgba(${n >> 16 & 255},${n >> 8 & 255},${n & 255},${a})`;
|
|
38
36
|
};
|
|
39
37
|
|
|
38
|
+
// src/theme-context.tsx
|
|
39
|
+
import { createContext, useContext } from "react";
|
|
40
|
+
import { jsx } from "react/jsx-runtime";
|
|
41
|
+
var Ctx = createContext(DEFAULT_THEME);
|
|
42
|
+
var ThemeProvider = ({ theme, children }) => /* @__PURE__ */ jsx(Ctx.Provider, { value: theme, children });
|
|
43
|
+
var useTheme = () => useContext(Ctx);
|
|
44
|
+
|
|
40
45
|
// src/util.ts
|
|
41
46
|
import { Easing, interpolate } from "remotion";
|
|
42
47
|
var FPS = 30;
|
|
43
48
|
var clamp = { extrapolateLeft: "clamp", extrapolateRight: "clamp" };
|
|
44
49
|
var EO = Easing.bezier(0.16, 1, 0.3, 1);
|
|
45
|
-
var
|
|
50
|
+
var appear = (t, q, d = 0.35) => interpolate(t, [q, q + d], [0, 1], { ...clamp, easing: EO });
|
|
46
51
|
var ramp = (t, a, b) => interpolate(t, [a, b], [0, 1], { ...clamp, easing: Easing.out(Easing.cubic) });
|
|
47
52
|
var pop = (t, q) => interpolate(t, [q, q + 0.18, q + 0.4], [0.6, 1.14, 1], { ...clamp, easing: Easing.out(Easing.cubic) });
|
|
48
53
|
var fmt = (n) => String(Math.round(n)).replace(/\B(?=(\d{3})+(?!\d))/g, ".");
|
|
49
54
|
var norm = (s) => s.toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/[^a-z0-9]/g, "");
|
|
50
|
-
var
|
|
55
|
+
var makeAt = (P, S) => {
|
|
51
56
|
const W = P.map((p) => norm(p.t));
|
|
52
|
-
return (i) => (
|
|
53
|
-
const f2 =
|
|
57
|
+
return (i) => (phrase, nth = 0) => {
|
|
58
|
+
const f2 = phrase.split(/\s+/).map(norm).filter(Boolean);
|
|
54
59
|
let c = 0;
|
|
55
60
|
for (let k = 0; k <= W.length - f2.length; k++) {
|
|
56
61
|
if (f2.every((w, j) => W[k + j] === w)) {
|
|
@@ -58,17 +63,17 @@ var mkAt = (P, S) => {
|
|
|
58
63
|
c++;
|
|
59
64
|
}
|
|
60
65
|
}
|
|
61
|
-
throw new Error("
|
|
66
|
+
throw new Error("phrase not found: " + phrase);
|
|
62
67
|
};
|
|
63
68
|
};
|
|
64
69
|
|
|
65
|
-
// src/
|
|
70
|
+
// src/engine.tsx
|
|
66
71
|
import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
67
72
|
var f = (s) => Math.round(s * FPS);
|
|
68
73
|
var DUR_SOM = { pop: 0.16, ding: 0.9, thump: 0.4, whoosh: 0.55 };
|
|
69
74
|
var VOL_SOM = { pop: 0.22, ding: 0.26, thump: 0.33, whoosh: 0.28 };
|
|
70
|
-
var
|
|
71
|
-
const {
|
|
75
|
+
var Label = ({ text }) => {
|
|
76
|
+
const { accent: LAMP, background: BG, text: INK, font: FONT, ok: OK, box: BOX } = useTheme();
|
|
72
77
|
const frame = useCurrentFrame();
|
|
73
78
|
const { fps } = useVideoConfig();
|
|
74
79
|
const pop2 = spring({ frame: frame - 3, fps, config: { damping: 11, stiffness: 200 } });
|
|
@@ -100,21 +105,21 @@ var Rotulo = ({ texto }) => {
|
|
|
100
105
|
boxShadow: "0 10px 30px rgba(0,0,0,.55)",
|
|
101
106
|
whiteSpace: "nowrap"
|
|
102
107
|
},
|
|
103
|
-
children:
|
|
108
|
+
children: text
|
|
104
109
|
}
|
|
105
110
|
)
|
|
106
111
|
}
|
|
107
112
|
);
|
|
108
113
|
};
|
|
109
|
-
var
|
|
110
|
-
const {
|
|
114
|
+
var Background = () => {
|
|
115
|
+
const { accent: LAMP, background: BG, text: INK, font: FONT, ok: OK, box: BOX } = useTheme();
|
|
111
116
|
const frame = useCurrentFrame();
|
|
112
|
-
const
|
|
117
|
+
const pulse = 0.16 + 0.05 * Math.sin(frame / 22);
|
|
113
118
|
return /* @__PURE__ */ jsx2(
|
|
114
119
|
AbsoluteFill,
|
|
115
120
|
{
|
|
116
121
|
style: {
|
|
117
|
-
background: `radial-gradient(ellipse at 50% 20%, ${rgba(LAMP,
|
|
122
|
+
background: `radial-gradient(ellipse at 50% 20%, ${rgba(LAMP, pulse)} 0%, ${rgba(LAMP, 0)} 55%), radial-gradient(ellipse at 50% 100%, rgba(80,60,120,.22) 0%, rgba(0,0,0,0) 60%), ${BG}`
|
|
118
123
|
},
|
|
119
124
|
children: Array.from({ length: 26 }).map((_, i) => {
|
|
120
125
|
const x = i * 137 % 1080;
|
|
@@ -141,32 +146,32 @@ var Fundo = () => {
|
|
|
141
146
|
}
|
|
142
147
|
);
|
|
143
148
|
};
|
|
144
|
-
var
|
|
149
|
+
var groupWords = (ps) => {
|
|
145
150
|
const g = [];
|
|
146
151
|
let cur = [];
|
|
147
|
-
const
|
|
148
|
-
if (cur.length) g.push({
|
|
152
|
+
const close = () => {
|
|
153
|
+
if (cur.length) g.push({ words: cur, a: cur[0].a, b: cur[cur.length - 1].b });
|
|
149
154
|
cur = [];
|
|
150
155
|
};
|
|
151
156
|
ps.forEach((p, i) => {
|
|
152
157
|
cur.push(p);
|
|
153
158
|
const gap = i < ps.length - 1 ? ps[i + 1].a - p.b : 1;
|
|
154
|
-
const
|
|
155
|
-
const
|
|
156
|
-
if (/[.,?!:]$/.test(p.t) || cur.length >= 3 ||
|
|
159
|
+
const letters = cur.reduce((n, x) => n + x.t.length, 0);
|
|
160
|
+
const next = ps[i + 1]?.t.length ?? 0;
|
|
161
|
+
if (/[.,?!:]$/.test(p.t) || cur.length >= 3 || letters + next > 15 || gap > 0.3) close();
|
|
157
162
|
});
|
|
158
|
-
|
|
163
|
+
close();
|
|
159
164
|
return g;
|
|
160
165
|
};
|
|
161
|
-
var
|
|
162
|
-
const {
|
|
166
|
+
var TerminalBackground = ({ lines }) => {
|
|
167
|
+
const { accent: LAMP, ok: OK } = useTheme();
|
|
163
168
|
const frame = useCurrentFrame();
|
|
164
|
-
const n = Math.max(1,
|
|
165
|
-
const
|
|
169
|
+
const n = Math.max(1, lines.length);
|
|
170
|
+
const step = 64, cycle = 34 * step + 0;
|
|
166
171
|
return /* @__PURE__ */ jsxs(AbsoluteFill, { style: { overflow: "hidden" }, children: [
|
|
167
|
-
/* @__PURE__ */ jsx2(
|
|
172
|
+
/* @__PURE__ */ jsx2(Background, {}),
|
|
168
173
|
/* @__PURE__ */ jsx2("div", { style: { position: "absolute", inset: 0, opacity: 0.09, filter: "blur(1.2px)" }, children: Array.from({ length: 34 }).map((_, i) => {
|
|
169
|
-
const y = ((i *
|
|
174
|
+
const y = ((i * step - frame * 0.8) % cycle + cycle) % cycle - 160;
|
|
170
175
|
const col = (i + Math.floor(i / n)) % 2;
|
|
171
176
|
return /* @__PURE__ */ jsx2(
|
|
172
177
|
"div",
|
|
@@ -180,26 +185,26 @@ var FundoTerminal = ({ linhas }) => {
|
|
|
180
185
|
color: i % 4 === 0 ? LAMP : OK,
|
|
181
186
|
whiteSpace: "nowrap"
|
|
182
187
|
},
|
|
183
|
-
children:
|
|
188
|
+
children: lines[i % n]
|
|
184
189
|
},
|
|
185
190
|
i
|
|
186
191
|
);
|
|
187
192
|
}) })
|
|
188
193
|
] });
|
|
189
194
|
};
|
|
190
|
-
var
|
|
191
|
-
const {
|
|
195
|
+
var CaptionEp = ({ words, bottom, px = 90 }) => {
|
|
196
|
+
const { accent: LAMP, background: BG, text: INK, font: FONT, ok: OK, box: BOX } = useTheme();
|
|
192
197
|
const frame = useCurrentFrame();
|
|
193
198
|
const t = frame / FPS;
|
|
194
|
-
const
|
|
195
|
-
const idx =
|
|
196
|
-
const
|
|
197
|
-
return t >= g2.a - 0.05 && t < Math.max(g2.b + 0.15, Math.min(
|
|
199
|
+
const groups = groupWords(words);
|
|
200
|
+
const idx = groups.findIndex((g2, i) => {
|
|
201
|
+
const next = groups[i + 1]?.a ?? g2.b + 0.4;
|
|
202
|
+
return t >= g2.a - 0.05 && t < Math.max(g2.b + 0.15, Math.min(next, g2.b + 0.5));
|
|
198
203
|
});
|
|
199
204
|
if (idx < 0) return null;
|
|
200
|
-
const g =
|
|
201
|
-
const
|
|
202
|
-
const
|
|
205
|
+
const g = groups[idx];
|
|
206
|
+
const inside = frame - f(g.a);
|
|
207
|
+
const enter = interpolate2(inside, [-2, 4], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp" });
|
|
203
208
|
return /* @__PURE__ */ jsx2(
|
|
204
209
|
"div",
|
|
205
210
|
{
|
|
@@ -212,12 +217,12 @@ var LegendaEp = ({ palavras, bottom, px = 90 }) => {
|
|
|
212
217
|
justifyContent: "center",
|
|
213
218
|
gap: px < 90 ? 64 : 44,
|
|
214
219
|
flexWrap: "wrap",
|
|
215
|
-
opacity:
|
|
216
|
-
transform: `translateY(${(1 -
|
|
220
|
+
opacity: enter,
|
|
221
|
+
transform: `translateY(${(1 - enter) * 24}px)`
|
|
217
222
|
},
|
|
218
|
-
children: g.
|
|
219
|
-
const
|
|
220
|
-
const
|
|
223
|
+
children: g.words.map((p, i) => {
|
|
224
|
+
const active = t >= p.a && t < p.b + 0.05;
|
|
225
|
+
const spoken = t >= p.a;
|
|
221
226
|
return /* @__PURE__ */ jsx2(
|
|
222
227
|
"span",
|
|
223
228
|
{
|
|
@@ -227,8 +232,8 @@ var LegendaEp = ({ palavras, bottom, px = 90 }) => {
|
|
|
227
232
|
fontSize: px,
|
|
228
233
|
lineHeight: 1.05,
|
|
229
234
|
textTransform: "uppercase",
|
|
230
|
-
color:
|
|
231
|
-
transform: `scale(${
|
|
235
|
+
color: active ? LAMP : spoken ? INK : rgba(INK, 0.55),
|
|
236
|
+
transform: `scale(${active ? 1.12 : 1})`,
|
|
232
237
|
textShadow: "0 6px 0 #000, 0 0 24px rgba(0,0,0,.85), 4px 0 0 #000, -4px 0 0 #000"
|
|
233
238
|
},
|
|
234
239
|
children: p.t
|
|
@@ -239,19 +244,19 @@ var LegendaEp = ({ palavras, bottom, px = 90 }) => {
|
|
|
239
244
|
}
|
|
240
245
|
);
|
|
241
246
|
};
|
|
242
|
-
var
|
|
243
|
-
const
|
|
244
|
-
const {
|
|
247
|
+
var OutroEp = ({ cta }) => {
|
|
248
|
+
const theme = useTheme();
|
|
249
|
+
const { accent: LAMP, background: BG, text: INK, font: FONT, ok: OK, box: BOX } = useTheme();
|
|
245
250
|
const frame = useCurrentFrame();
|
|
246
251
|
const { fps } = useVideoConfig();
|
|
247
252
|
const logo = spring({ frame, fps, config: { damping: 10, stiffness: 150 } });
|
|
248
|
-
const
|
|
253
|
+
const handleSpring = spring({ frame: frame - 8, fps, config: { damping: 200 } });
|
|
249
254
|
const pill = spring({ frame: frame - 20, fps, config: { damping: 12, stiffness: 160 } });
|
|
250
255
|
return /* @__PURE__ */ jsxs(
|
|
251
256
|
AbsoluteFill,
|
|
252
257
|
{
|
|
253
258
|
style: {
|
|
254
|
-
background: `radial-gradient(circle at 50% 40%, ${
|
|
259
|
+
background: `radial-gradient(circle at 50% 40%, ${BOX} 0%, ${BG} 72%)`,
|
|
255
260
|
justifyContent: "center",
|
|
256
261
|
alignItems: "center",
|
|
257
262
|
fontFamily: FONT,
|
|
@@ -271,7 +276,7 @@ var FechamentoEp = ({ cta }) => {
|
|
|
271
276
|
transform: `scale(${0.6 + 0.4 * logo})`,
|
|
272
277
|
opacity: Math.min(1, logo * 1.5)
|
|
273
278
|
},
|
|
274
|
-
children: /* @__PURE__ */ jsx2(Img, { src: staticFile(
|
|
279
|
+
children: /* @__PURE__ */ jsx2(Img, { src: staticFile(theme.avatar), style: { width: 420, height: 420, imageRendering: "pixelated" } })
|
|
275
280
|
}
|
|
276
281
|
),
|
|
277
282
|
/* @__PURE__ */ jsx2(
|
|
@@ -282,10 +287,10 @@ var FechamentoEp = ({ cta }) => {
|
|
|
282
287
|
fontSize: 92,
|
|
283
288
|
fontWeight: 900,
|
|
284
289
|
color: LAMP,
|
|
285
|
-
opacity: Math.max(0,
|
|
286
|
-
transform: `translateY(${(1 - Math.max(0,
|
|
290
|
+
opacity: Math.max(0, handleSpring),
|
|
291
|
+
transform: `translateY(${(1 - Math.max(0, handleSpring)) * 30}px)`
|
|
287
292
|
},
|
|
288
|
-
children:
|
|
293
|
+
children: theme.handle
|
|
289
294
|
}
|
|
290
295
|
),
|
|
291
296
|
/* @__PURE__ */ jsx2(
|
|
@@ -309,63 +314,63 @@ var FechamentoEp = ({ cta }) => {
|
|
|
309
314
|
}
|
|
310
315
|
);
|
|
311
316
|
};
|
|
312
|
-
var
|
|
313
|
-
const {
|
|
317
|
+
var ProgressBar = ({ durS }) => {
|
|
318
|
+
const { accent: LAMP, background: BG, text: INK, font: FONT, ok: OK, box: BOX } = useTheme();
|
|
314
319
|
const frame = useCurrentFrame();
|
|
315
320
|
const w = Math.min(100, frame / f(durS) * 100);
|
|
316
321
|
return /* @__PURE__ */ jsx2("div", { style: { position: "absolute", top: 0, left: 0, right: 0, height: 10, background: "rgba(0,0,0,.4)" }, children: /* @__PURE__ */ jsx2("div", { style: { width: `${w}%`, height: "100%", background: LAMP } }) });
|
|
317
322
|
};
|
|
318
|
-
var
|
|
323
|
+
var Hits = ({ hits, children }) => {
|
|
319
324
|
const t = useCurrentFrame() / FPS;
|
|
320
325
|
let sx = 0, sy = 0, zoom = 1, flash = 0;
|
|
321
|
-
for (const g of
|
|
326
|
+
for (const g of hits) {
|
|
322
327
|
const dt = t - g.at;
|
|
323
328
|
if (dt < 0 || dt > 0.45) continue;
|
|
324
329
|
const k = (1 - dt / 0.45) ** 2;
|
|
325
|
-
if (g.
|
|
330
|
+
if (g.type === "shake" || g.type === "all") {
|
|
326
331
|
sx += Math.sin(dt * 95) * 16 * k;
|
|
327
332
|
sy += Math.cos(dt * 120) * 9 * k;
|
|
328
333
|
}
|
|
329
|
-
if (g.
|
|
330
|
-
if (g.
|
|
334
|
+
if (g.type === "zoom" || g.type === "all") zoom = Math.max(zoom, 1 + 0.055 * k);
|
|
335
|
+
if (g.type === "flash" || g.type === "all") flash = Math.max(flash, 0.33 * k);
|
|
331
336
|
}
|
|
332
337
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
333
338
|
/* @__PURE__ */ jsx2(AbsoluteFill, { style: { transform: `translate(${sx}px, ${sy}px) scale(${zoom})` }, children }),
|
|
334
339
|
flash > 0.01 ? /* @__PURE__ */ jsx2(AbsoluteFill, { style: { background: `rgba(255,244,214,${flash})`, pointerEvents: "none" } }) : null
|
|
335
340
|
] });
|
|
336
341
|
};
|
|
337
|
-
var
|
|
338
|
-
const {
|
|
342
|
+
var Episode = ({ d }) => {
|
|
343
|
+
const { background: BG } = useTheme();
|
|
339
344
|
return /* @__PURE__ */ jsxs(AbsoluteFill, { style: { backgroundColor: BG }, children: [
|
|
340
|
-
/* @__PURE__ */ jsx2(Audio, { src: staticFile(d.
|
|
341
|
-
/* @__PURE__ */ jsxs(
|
|
342
|
-
/* @__PURE__ */ jsx2(
|
|
345
|
+
/* @__PURE__ */ jsx2(Audio, { src: staticFile(d.voice) }),
|
|
346
|
+
/* @__PURE__ */ jsxs(Hits, { hits: d.hits ?? [], children: [
|
|
347
|
+
/* @__PURE__ */ jsx2(TerminalBackground, { lines: d.backgroundLines ?? ["\u2713 ok"] }),
|
|
343
348
|
/* @__PURE__ */ jsx2(Sequence, { from: 0, durationInFrames: f(DUR_SOM.whoosh) + 2, children: /* @__PURE__ */ jsx2(Audio, { src: staticFile("sfx/whoosh.wav"), volume: VOL_SOM.whoosh }) }),
|
|
344
|
-
d.
|
|
345
|
-
const
|
|
346
|
-
return
|
|
349
|
+
d.scenes.flatMap((c, i) => {
|
|
350
|
+
const sounds = [{ at: 0, sound: "pop" }, ...c.sounds ?? []];
|
|
351
|
+
return sounds.map((s, k) => /* @__PURE__ */ jsx2(Sequence, { from: f(c.a + s.at), durationInFrames: f(DUR_SOM[s.sound]) + 2, children: /* @__PURE__ */ jsx2(Audio, { src: staticFile(`sfx/${s.sound}.wav`), volume: s.v ?? VOL_SOM[s.sound] }) }, `s${i}-${k}`));
|
|
347
352
|
}),
|
|
348
|
-
d.
|
|
349
|
-
d.
|
|
350
|
-
(c, i) => c.
|
|
353
|
+
d.scenes.map((c, i) => /* @__PURE__ */ jsx2(Sequence, { from: f(c.a), durationInFrames: f(c.b - c.a), premountFor: 10, children: /* @__PURE__ */ jsx2(c.C, { dur: c.b - c.a }) }, "c" + i)),
|
|
354
|
+
d.scenes.map(
|
|
355
|
+
(c, i) => c.label ? /* @__PURE__ */ jsx2(Sequence, { from: f(c.a), durationInFrames: f(c.b - c.a), children: /* @__PURE__ */ jsx2(Label, { text: c.label }) }, "r" + i) : null
|
|
351
356
|
)
|
|
352
357
|
] }),
|
|
353
|
-
/* @__PURE__ */ jsx2(Sequence, { from: f(d.
|
|
354
|
-
/* @__PURE__ */ jsx2(Sequence, { from: 0, durationInFrames: f(d.durS), children: /* @__PURE__ */ jsx2(
|
|
355
|
-
/* @__PURE__ */ jsx2(Sequence, { from: f(d.
|
|
356
|
-
/* @__PURE__ */ jsx2(
|
|
358
|
+
/* @__PURE__ */ jsx2(Sequence, { from: f(d.outro.a), durationInFrames: f(d.durS - d.outro.a), children: /* @__PURE__ */ jsx2(OutroEp, { cta: d.outro.cta }) }),
|
|
359
|
+
/* @__PURE__ */ jsx2(Sequence, { from: 0, durationInFrames: f(d.durS), children: /* @__PURE__ */ jsx2(CaptionEp, { words: d.words, bottom: 190, px: d.captionPx }) }),
|
|
360
|
+
/* @__PURE__ */ jsx2(Sequence, { from: f(d.outro.a), durationInFrames: 2, children: /* @__PURE__ */ jsx2(Audio, { src: staticFile("sfx/ding.wav"), volume: 0.22 }) }),
|
|
361
|
+
/* @__PURE__ */ jsx2(ProgressBar, { durS: d.durS })
|
|
357
362
|
] });
|
|
358
363
|
};
|
|
359
364
|
|
|
360
|
-
// src/
|
|
365
|
+
// src/long.tsx
|
|
361
366
|
import { AbsoluteFill as AbsoluteFill2, Audio as Audio2, Img as Img2, interpolate as interpolate3, spring as spring2, staticFile as staticFile2, useCurrentFrame as useCurrentFrame2, useVideoConfig as useVideoConfig2 } from "remotion";
|
|
362
367
|
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
363
368
|
var clamp2 = { extrapolateLeft: "clamp", extrapolateRight: "clamp" };
|
|
364
|
-
var
|
|
365
|
-
var
|
|
369
|
+
var fadeWindow = (t, a, b, d = 0.4) => interpolate3(t, [a, a + d, b - d, b], [0, 1, 1, 0], clamp2);
|
|
370
|
+
var makeAtGlobal = (P) => {
|
|
366
371
|
const W = P.map((p) => norm(p.t));
|
|
367
|
-
return (
|
|
368
|
-
const f2 =
|
|
372
|
+
return (phrase, nth = 0, from = 0) => {
|
|
373
|
+
const f2 = phrase.split(/\s+/).map(norm).filter(Boolean);
|
|
369
374
|
let c = 0;
|
|
370
375
|
for (let i = 0; i <= W.length - f2.length; i++) {
|
|
371
376
|
if (P[i].a < from) continue;
|
|
@@ -374,21 +379,21 @@ var mkAtG = (P) => {
|
|
|
374
379
|
c++;
|
|
375
380
|
}
|
|
376
381
|
}
|
|
377
|
-
throw new Error("
|
|
382
|
+
throw new Error("phrase not found: " + phrase);
|
|
378
383
|
};
|
|
379
384
|
};
|
|
380
|
-
var
|
|
381
|
-
const T =
|
|
385
|
+
var SceneFrame = ({ t, a, b, n, title, children }) => {
|
|
386
|
+
const T = useTheme();
|
|
382
387
|
if (t < a - 0.1 || t > b + 0.1) return null;
|
|
383
|
-
return /* @__PURE__ */ jsxs2(AbsoluteFill2, { style: { opacity:
|
|
384
|
-
/* @__PURE__ */ jsxs2("div", { style: { position: "absolute", left: 100, top: 46, display: "flex", alignItems: "center", gap: 18, fontFamily: T.
|
|
385
|
-
/* @__PURE__ */ jsx3("span", { style: { background: T.
|
|
386
|
-
|
|
388
|
+
return /* @__PURE__ */ jsxs2(AbsoluteFill2, { style: { opacity: fadeWindow(t, a, b) }, children: [
|
|
389
|
+
/* @__PURE__ */ jsxs2("div", { style: { position: "absolute", left: 100, top: 46, display: "flex", alignItems: "center", gap: 18, fontFamily: T.font, fontSize: 30, letterSpacing: 4, color: T.accent, whiteSpace: "nowrap" }, children: [
|
|
390
|
+
/* @__PURE__ */ jsx3("span", { style: { background: T.accent, color: T.background, borderRadius: 12, padding: "4px 16px" }, children: n }),
|
|
391
|
+
title
|
|
387
392
|
] }),
|
|
388
393
|
children
|
|
389
394
|
] });
|
|
390
395
|
};
|
|
391
|
-
var
|
|
396
|
+
var splitGroups = (P) => {
|
|
392
397
|
const g = [];
|
|
393
398
|
let cur = [];
|
|
394
399
|
P.forEach((p) => {
|
|
@@ -401,60 +406,60 @@ var grupos = (P) => {
|
|
|
401
406
|
if (cur.length) g.push(cur);
|
|
402
407
|
return g;
|
|
403
408
|
};
|
|
404
|
-
var
|
|
405
|
-
const T =
|
|
406
|
-
let
|
|
407
|
-
for (const g of G) if (t >= g[0].a - 0.05 && t <= g[g.length - 1].b + 0.4)
|
|
408
|
-
if (!
|
|
409
|
-
return /* @__PURE__ */ jsx3("div", { style: { position: "absolute", left: 160, right: 160, bottom: 56, textAlign: "center", fontFamily: T.
|
|
409
|
+
var Captions = ({ t, G }) => {
|
|
410
|
+
const T = useTheme();
|
|
411
|
+
let group = null;
|
|
412
|
+
for (const g of G) if (t >= g[0].a - 0.05 && t <= g[g.length - 1].b + 0.4) group = g;
|
|
413
|
+
if (!group) return null;
|
|
414
|
+
return /* @__PURE__ */ jsx3("div", { style: { position: "absolute", left: 160, right: 160, bottom: 56, textAlign: "center", fontFamily: T.font, fontSize: 46, lineHeight: 1.2, textShadow: "0 3px 12px rgba(0,0,0,.9)" }, children: group.map((p, i) => /* @__PURE__ */ jsxs2("span", { style: { color: t >= p.a && t < p.b + 0.05 ? T.accent : T.text }, children: [
|
|
410
415
|
p.t,
|
|
411
|
-
i <
|
|
416
|
+
i < group.length - 1 ? " " : ""
|
|
412
417
|
] }, i)) });
|
|
413
418
|
};
|
|
414
|
-
var
|
|
415
|
-
const T =
|
|
419
|
+
var EndcardScreen = ({ f: f2, t }) => {
|
|
420
|
+
const T = useTheme();
|
|
416
421
|
const { fps } = useVideoConfig2();
|
|
417
|
-
const k = (
|
|
422
|
+
const k = (delay) => spring2({ frame: (t - f2.a - delay) * fps, fps, config: { damping: 14, stiffness: 140 } });
|
|
418
423
|
const op = interpolate3(t, [f2.a, f2.a + 0.5], [0, 1], clamp2);
|
|
419
|
-
return /* @__PURE__ */ jsxs2(AbsoluteFill2, { style: { opacity: op, background: `radial-gradient(circle at 50% 40%, ${T.
|
|
420
|
-
/* @__PURE__ */ jsx3("div", { style: { width: 230, height: 230, borderRadius: 44, overflow: "hidden", border: `8px solid ${f2.
|
|
421
|
-
f2.
|
|
422
|
-
/* @__PURE__ */ jsx3("div", { style: { marginTop: 28, fontSize: 54, fontWeight: 900, color: T.
|
|
423
|
-
f2.link ? /* @__PURE__ */ jsx3("div", { style: { marginTop: 18, fontSize: 38, fontFamily: T.mono, color: T.
|
|
424
|
+
return /* @__PURE__ */ jsxs2(AbsoluteFill2, { style: { opacity: op, background: `radial-gradient(circle at 50% 40%, ${T.box} 0%, ${T.background} 75%)`, alignItems: "center", justifyContent: "center", fontFamily: T.font, textAlign: "center", paddingBottom: 90 }, children: [
|
|
425
|
+
/* @__PURE__ */ jsx3("div", { style: { width: 230, height: 230, borderRadius: 44, overflow: "hidden", border: `8px solid ${f2.image ? "#3a3566" : T.accent}`, background: f2.image ? "#1c1a33" : void 0, boxShadow: `0 0 70px ${rgba(T.accent, 0.3)}`, transform: `scale(${0.6 + 0.4 * k(0)})`, opacity: Math.min(1, k(0) * 1.5) }, children: /* @__PURE__ */ jsx3(Img2, { src: staticFile2(f2.image ?? T.avatar), style: { width: 230, height: 230, imageRendering: "pixelated", objectFit: "contain" } }) }),
|
|
426
|
+
f2.text ? /* @__PURE__ */ jsx3("div", { style: { marginTop: 44, fontSize: 104, fontWeight: 900, color: T.accent, opacity: Math.max(0, k(0.15)), transform: `translateY(${(1 - Math.max(0, k(0.15))) * 24}px)` }, children: f2.text }) : null,
|
|
427
|
+
/* @__PURE__ */ jsx3("div", { style: { marginTop: 28, fontSize: 54, fontWeight: 900, color: T.text, opacity: Math.max(0, k(0.3)) }, children: T.handle }),
|
|
428
|
+
f2.link ? /* @__PURE__ */ jsx3("div", { style: { marginTop: 18, fontSize: 38, fontFamily: T.mono, color: T.muted, opacity: Math.max(0, k(0.45)) }, children: f2.link }) : null
|
|
424
429
|
] });
|
|
425
430
|
};
|
|
426
|
-
var
|
|
427
|
-
const T =
|
|
431
|
+
var Long = ({ d }) => {
|
|
432
|
+
const T = useTheme();
|
|
428
433
|
const t = useCurrentFrame2() / FPS;
|
|
429
|
-
const G =
|
|
430
|
-
return /* @__PURE__ */ jsxs2(AbsoluteFill2, { style: { background: T.
|
|
431
|
-
/* @__PURE__ */ jsx3(AbsoluteFill2, { style: { background: `radial-gradient(1200px 700px at 70% 20%, ${rgba(T.
|
|
432
|
-
/* @__PURE__ */ jsx3(Audio2, { src: staticFile2(d.
|
|
433
|
-
d.
|
|
434
|
-
d.
|
|
435
|
-
/* @__PURE__ */ jsx3(
|
|
436
|
-
/* @__PURE__ */ jsx3("div", { style: { position: "absolute", left: 0, top: 0, height: 8, width: `${t / d.durS * 100}%`, background: T.
|
|
434
|
+
const G = splitGroups(d.words);
|
|
435
|
+
return /* @__PURE__ */ jsxs2(AbsoluteFill2, { style: { background: T.background, color: T.text, fontFamily: T.font }, children: [
|
|
436
|
+
/* @__PURE__ */ jsx3(AbsoluteFill2, { style: { background: `radial-gradient(1200px 700px at 70% 20%, ${rgba(T.accent, 0.07)}, transparent 70%)` } }),
|
|
437
|
+
/* @__PURE__ */ jsx3(Audio2, { src: staticFile2(d.voice) }),
|
|
438
|
+
d.scenes.map((c) => /* @__PURE__ */ jsx3(SceneFrame, { t, a: c.a, b: c.b, n: c.n, title: c.title, children: /* @__PURE__ */ jsx3(c.C, { t }) }, c.n)),
|
|
439
|
+
d.endcard && t >= d.endcard.a ? /* @__PURE__ */ jsx3(EndcardScreen, { f: d.endcard, t }) : null,
|
|
440
|
+
/* @__PURE__ */ jsx3(Captions, { t, G }),
|
|
441
|
+
/* @__PURE__ */ jsx3("div", { style: { position: "absolute", left: 0, top: 0, height: 8, width: `${t / d.durS * 100}%`, background: T.accent } })
|
|
437
442
|
] });
|
|
438
443
|
};
|
|
439
444
|
export {
|
|
440
|
-
|
|
445
|
+
Background,
|
|
446
|
+
DEFAULT_THEME,
|
|
447
|
+
Episode,
|
|
441
448
|
FPS,
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
TemaProvider,
|
|
447
|
-
agrupar,
|
|
449
|
+
Label,
|
|
450
|
+
Long,
|
|
451
|
+
ThemeProvider,
|
|
452
|
+
appear,
|
|
448
453
|
clamp,
|
|
454
|
+
fadeWindow,
|
|
449
455
|
fmt,
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
456
|
+
groupWords,
|
|
457
|
+
makeAt,
|
|
458
|
+
makeAtGlobal,
|
|
453
459
|
norm,
|
|
454
460
|
pop,
|
|
455
461
|
ramp,
|
|
456
462
|
rgba,
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
useTema
|
|
463
|
+
themeFromBrand,
|
|
464
|
+
useTheme
|
|
460
465
|
};
|
package/dist/long.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Word } from "./word.js";
|
|
2
|
+
/**
|
|
3
|
+
* LONG ENGINE (16:9, several minutes). A long video is a list of scenes in GLOBAL time: each scene receives `t` (s since the start of the video)
|
|
4
|
+
* and ties itself to the voice with `makeAtGlobal(WORDS)(phrase, nth, from)`. The engine handles the background, the voice, the scene header (number + title),
|
|
5
|
+
* grouped captions and the progress bar. Scenes are project TSX, built with the parts of `@praeko/studio-kit`.
|
|
6
|
+
*/
|
|
7
|
+
export type LongScene = {
|
|
8
|
+
n: string;
|
|
9
|
+
title: string;
|
|
10
|
+
a: number;
|
|
11
|
+
b: number;
|
|
12
|
+
C: React.FC<{
|
|
13
|
+
t: number;
|
|
14
|
+
}>;
|
|
15
|
+
};
|
|
16
|
+
/** Optional end card: avatar, text (e.g. the signature), brand handle and link. Starts at `a` (s) and stays until the end. */
|
|
17
|
+
export type LongEndcard = {
|
|
18
|
+
a: number;
|
|
19
|
+
text?: string;
|
|
20
|
+
link?: string;
|
|
21
|
+
image?: string;
|
|
22
|
+
};
|
|
23
|
+
export type LongData = {
|
|
24
|
+
id: string;
|
|
25
|
+
durS: number;
|
|
26
|
+
voice: string;
|
|
27
|
+
words: Word[];
|
|
28
|
+
scenes: LongScene[];
|
|
29
|
+
width?: number;
|
|
30
|
+
height?: number;
|
|
31
|
+
endcard?: LongEndcard;
|
|
32
|
+
};
|
|
33
|
+
/** Fades in and out over `d` s at the ends of the window [a, b]. */
|
|
34
|
+
export declare const fadeWindow: (t: number, a: number, b: number, d?: number) => number;
|
|
35
|
+
/** Global anchor: `at("phrase", nth, from)` = instant (s) at which the phrase starts; `from` ignores occurrences before `from` s. Fails loudly if not found. */
|
|
36
|
+
export declare const makeAtGlobal: (P: Word[]) => (phrase: string, nth?: number, from?: number) => number;
|
|
37
|
+
export declare const Long: React.FC<{
|
|
38
|
+
d: LongData;
|
|
39
|
+
}>;
|
package/dist/theme.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** Brand tokens. Everything that is a color, font, name or avatar comes from here; the engine and the kit know no brand. */
|
|
2
|
+
export type Theme = {
|
|
3
|
+
background: string;
|
|
4
|
+
coverBackground: string;
|
|
5
|
+
accent: string;
|
|
6
|
+
text: string;
|
|
7
|
+
muted: string;
|
|
8
|
+
ok: string;
|
|
9
|
+
error: string;
|
|
10
|
+
info: string;
|
|
11
|
+
extra: string;
|
|
12
|
+
box: string;
|
|
13
|
+
longBox?: string;
|
|
14
|
+
code: string;
|
|
15
|
+
font: string;
|
|
16
|
+
mono: string;
|
|
17
|
+
handle: string;
|
|
18
|
+
avatar: string;
|
|
19
|
+
/** Mascot image path template under public/; `{name}` is replaced. Default `mascots/{name}.png`. */
|
|
20
|
+
mascotPath?: string;
|
|
21
|
+
};
|
|
22
|
+
export declare const DEFAULT_THEME: Theme;
|
|
23
|
+
type BrandLike = {
|
|
24
|
+
colors?: Partial<Theme>;
|
|
25
|
+
fonts?: {
|
|
26
|
+
title?: string;
|
|
27
|
+
mono?: string;
|
|
28
|
+
};
|
|
29
|
+
handle?: string;
|
|
30
|
+
avatar?: string;
|
|
31
|
+
mascotPath?: string;
|
|
32
|
+
};
|
|
33
|
+
/** Reads a project's `brand.json` (missing fields use the default theme). */
|
|
34
|
+
export declare const themeFromBrand: (m: BrandLike) => Theme;
|
|
35
|
+
/** "#f4b942" + 0.16 -> "rgba(244,185,66,0.16)". */
|
|
36
|
+
export declare const rgba: (hex: string, a: number) => string;
|
|
37
|
+
export {};
|
package/dist/util.d.ts
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { Word } from "./word.js";
|
|
2
2
|
export declare const FPS = 30;
|
|
3
3
|
export declare const clamp: {
|
|
4
4
|
readonly extrapolateLeft: "clamp";
|
|
5
5
|
readonly extrapolateRight: "clamp";
|
|
6
6
|
};
|
|
7
|
-
/** 0
|
|
8
|
-
export declare const
|
|
7
|
+
/** 0 -> 1 from instant `q` (s), with a soft ease-out. */
|
|
8
|
+
export declare const appear: (t: number, q: number, d?: number) => number;
|
|
9
9
|
export declare const ramp: (t: number, a: number, b: number) => number;
|
|
10
10
|
export declare const pop: (t: number, q: number) => number;
|
|
11
|
+
/** Number with "." as thousands separator (pt-BR style): 13500 -> "13.500". */
|
|
11
12
|
export declare const fmt: (n: number) => string;
|
|
12
13
|
export declare const norm: (s: string) => string;
|
|
13
14
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
15
|
+
* Phrase anchors: `makeAt(words, starts)(i)(phrase, n)` returns the LOCAL instant (s since the start of scene i) at which the phrase
|
|
16
|
+
* starts in the voice. Fails loudly if the phrase does not exist (the aligner sometimes writes numbers as digits).
|
|
16
17
|
*/
|
|
17
|
-
export declare const
|
|
18
|
+
export declare const makeAt: (P: Word[], S: number[]) => (i: number) => (phrase: string, nth?: number) => number;
|
package/dist/word.d.ts
ADDED
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@praeko/studio-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "The Praeko Studio engine: voice-driven episodes with timed words, word-by-word captions, editing hits, outro and brand theme (Remotion parts). A library for projects created with `praeko studio init`.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"praeko",
|
|
@@ -44,7 +44,8 @@
|
|
|
44
44
|
"scripts": {
|
|
45
45
|
"build": "node ../../../scripts/build-studio.mjs core",
|
|
46
46
|
"prepack": "npm run build",
|
|
47
|
-
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
47
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
48
|
+
"test": "node --test test/*.test.ts"
|
|
48
49
|
},
|
|
49
50
|
"peerDependencies": {
|
|
50
51
|
"react": "^19.0.0",
|
package/dist/longo.d.ts
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { Palavra } from "./palavra";
|
|
2
|
-
/**
|
|
3
|
-
* MOTOR DE LONGO (16:9, vários minutos). Um longo é uma lista de cenas em tempo GLOBAL: cada cena recebe `t` (s desde o início do vídeo)
|
|
4
|
-
* e se liga à voz com `mkAtG(PALAVRAS)(frase, nth, from)`. O motor cuida do fundo, da voz, do cabeçalho da cena (número + título),
|
|
5
|
-
* da legenda por grupos e da barra de progresso. As cenas são TSX do projeto, montadas com as peças de `@praeko/studio-kit/longo`.
|
|
6
|
-
*/
|
|
7
|
-
export type CenaLongo = {
|
|
8
|
-
n: string;
|
|
9
|
-
titulo: string;
|
|
10
|
-
a: number;
|
|
11
|
-
b: number;
|
|
12
|
-
C: React.FC<{
|
|
13
|
-
t: number;
|
|
14
|
-
}>;
|
|
15
|
-
};
|
|
16
|
-
/** Tela final opcional: avatar, texto (ex.: a assinatura), handle da marca e link. Entra em `a` (s) e fica até o fim. */
|
|
17
|
-
export type FechoLongo = {
|
|
18
|
-
a: number;
|
|
19
|
-
texto?: string;
|
|
20
|
-
link?: string;
|
|
21
|
-
imagem?: string;
|
|
22
|
-
};
|
|
23
|
-
export type LongoData = {
|
|
24
|
-
id: string;
|
|
25
|
-
durS: number;
|
|
26
|
-
voz: string;
|
|
27
|
-
palavras: Palavra[];
|
|
28
|
-
cenas: CenaLongo[];
|
|
29
|
-
largura?: number;
|
|
30
|
-
altura?: number;
|
|
31
|
-
fecho?: FechoLongo;
|
|
32
|
-
};
|
|
33
|
-
/** Entra e sai com fade de `d` s nas pontas da janela [a, b]. */
|
|
34
|
-
export declare const janela: (t: number, a: number, b: number, d?: number) => number;
|
|
35
|
-
/** Âncora global: `at("frase", nth, from)` = instante (s) em que a frase começa; `from` ignora ocorrências antes de `from` s. Falha alto se não achar. */
|
|
36
|
-
export declare const mkAtG: (P: Palavra[]) => (frase: string, nth?: number, from?: number) => number;
|
|
37
|
-
export declare const Longo: React.FC<{
|
|
38
|
-
d: LongoData;
|
|
39
|
-
}>;
|
package/dist/motor.d.ts
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { Palavra } from "./palavra";
|
|
2
|
-
/**
|
|
3
|
-
* NÚCLEO DO PRAEKO STUDIO.
|
|
4
|
-
* Um episódio é só dados (EpData): voz, palavras com tempo, cenas (cartão + rótulo + sons) e fechamento.
|
|
5
|
-
* O resto (fundo, legenda, barra de progresso, fechamento, efeitos) é compartilhado e usa o tema da marca.
|
|
6
|
-
*/
|
|
7
|
-
export type Som = {
|
|
8
|
-
at: number;
|
|
9
|
-
f: "pop" | "ding" | "thump" | "whoosh";
|
|
10
|
-
v?: number;
|
|
11
|
-
};
|
|
12
|
-
export type Golpe = {
|
|
13
|
-
at: number;
|
|
14
|
-
tipo: "shake" | "zoom" | "flash" | "tudo";
|
|
15
|
-
};
|
|
16
|
-
export type EpCena = {
|
|
17
|
-
a: number;
|
|
18
|
-
b: number;
|
|
19
|
-
C: React.FC<{
|
|
20
|
-
dur: number;
|
|
21
|
-
}>;
|
|
22
|
-
rotulo?: string;
|
|
23
|
-
/** sons relativos ao início da cena (s) */
|
|
24
|
-
sons?: Som[];
|
|
25
|
-
};
|
|
26
|
-
export type EpData = {
|
|
27
|
-
id: string;
|
|
28
|
-
durS: number;
|
|
29
|
-
voz: string;
|
|
30
|
-
palavras: Palavra[];
|
|
31
|
-
cenas: EpCena[];
|
|
32
|
-
fechamento: {
|
|
33
|
-
a: number;
|
|
34
|
-
cta: string;
|
|
35
|
-
};
|
|
36
|
-
/** linhas de "terminal" desfocadas no fundo */
|
|
37
|
-
fundoLinhas?: string[];
|
|
38
|
-
/** tamanho da legenda (padrão 90) */
|
|
39
|
-
legendaPx?: number;
|
|
40
|
-
/** golpes de edição: tremor, clarão e zoom rápidos em instantes (s) da narração */
|
|
41
|
-
golpes?: Golpe[];
|
|
42
|
-
};
|
|
43
|
-
export declare const Rotulo: React.FC<{
|
|
44
|
-
texto: string;
|
|
45
|
-
}>;
|
|
46
|
-
/** Fundo limpo: escuro, brilho âmbar que pulsa e pontinhos que sobem devagar. */
|
|
47
|
-
export declare const Fundo: React.FC;
|
|
48
|
-
export type Grupo = {
|
|
49
|
-
palavras: Palavra[];
|
|
50
|
-
a: number;
|
|
51
|
-
b: number;
|
|
52
|
-
};
|
|
53
|
-
export declare const agrupar: (ps: Palavra[]) => Grupo[];
|
|
54
|
-
export declare const Episodio: React.FC<{
|
|
55
|
-
d: EpData;
|
|
56
|
-
}>;
|
package/dist/palavra.d.ts
DELETED
package/dist/tema.d.ts
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
/** Tokens de marca. Tudo que é cor, fonte, nome e avatar vem daqui; o motor e o kit não conhecem nenhuma marca. */
|
|
2
|
-
export type Tema = {
|
|
3
|
-
fundo: string;
|
|
4
|
-
fundoCapa: string;
|
|
5
|
-
destaque: string;
|
|
6
|
-
texto: string;
|
|
7
|
-
suave: string;
|
|
8
|
-
ok: string;
|
|
9
|
-
erro: string;
|
|
10
|
-
info: string;
|
|
11
|
-
extra: string;
|
|
12
|
-
caixa: string;
|
|
13
|
-
caixaLongo?: string;
|
|
14
|
-
codigo: string;
|
|
15
|
-
fonte: string;
|
|
16
|
-
mono: string;
|
|
17
|
-
handle: string;
|
|
18
|
-
avatar: string;
|
|
19
|
-
};
|
|
20
|
-
export declare const TEMA_PADRAO: Tema;
|
|
21
|
-
export declare const TemaProvider: React.FC<{
|
|
22
|
-
tema: Tema;
|
|
23
|
-
children: React.ReactNode;
|
|
24
|
-
}>;
|
|
25
|
-
export declare const useTema: () => Tema;
|
|
26
|
-
/** Lê o `marca.json` de um projeto (campos que faltarem usam o tema padrão). */
|
|
27
|
-
export declare const temaDaMarca: (m: {
|
|
28
|
-
cores?: Partial<Tema>;
|
|
29
|
-
fontes?: {
|
|
30
|
-
titulo?: string;
|
|
31
|
-
mono?: string;
|
|
32
|
-
};
|
|
33
|
-
handle?: string;
|
|
34
|
-
avatar?: string;
|
|
35
|
-
}) => Tema;
|
|
36
|
-
/** "#f4b942" + 0.16 → "rgba(244,185,66,0.16)". */
|
|
37
|
-
export declare const rgba: (hex: string, a: number) => string;
|