@omelhorsite/video-sdk 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.
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Legendas karaoke: agrupamento, estados de realce, e - o que interessa mesmo -
3
+ * o remapeamento das palavras do tempo do SOURCE para o tempo da timeline.
4
+ *
5
+ * A ideia que governa este ficheiro: as palavras sao guardadas em tempo do
6
+ * material bruto, nao do video montado. Assim, cortar, reordenar ou apertar um
7
+ * bloco nao invalida a transcricao - as palavras andam com os clips, e as que
8
+ * caem num pedaco cortado desaparecem sozinhas. Transcreve-se uma vez por
9
+ * fonte, e nunca mais.
10
+ *
11
+ * A matematica do agrupamento e' a mesma do motor de legendas do omelhorsite,
12
+ * para o resultado ser identico ao que ja foi aprovado.
13
+ */
14
+ import type { Project } from "./types";
15
+ /** Uma palavra em tempo do source a que pertence. */
16
+ export interface CaptionWord {
17
+ t0: number;
18
+ t1: number;
19
+ text: string;
20
+ }
21
+ /** Transcricao de um asset. Vive no Project e sobrevive a` edicao. */
22
+ export interface CaptionSource {
23
+ /** Asset a que estes tempos se referem. */
24
+ assetId: string;
25
+ words: CaptionWord[];
26
+ /** Idioma com que foi transcrito, so' para referencia. */
27
+ language?: string;
28
+ }
29
+ export interface CaptionStyle {
30
+ /** Tamanho da fonte como fraccao da LARGURA do projecto. */
31
+ fontScale: number;
32
+ /** Espessura do contorno como fraccao da fonte. */
33
+ strokeScale: number;
34
+ /** Centro vertical do texto (fraccao da altura). */
35
+ pos: number;
36
+ maxWords: number;
37
+ maxChars: number;
38
+ /** Corta a legenda em silencios maiores que isto. */
39
+ gap: number;
40
+ /** Tempo extra dado a` ultima palavra do grupo. */
41
+ tail: number;
42
+ color: string;
43
+ highlight: string;
44
+ stroke: string;
45
+ /** Largura maxima do texto como fraccao da largura do projecto. */
46
+ maxWidth: number;
47
+ fontFamily: string;
48
+ }
49
+ export declare const DEFAULT_CAPTION_STYLE: CaptionStyle;
50
+ /** Corta a lista de palavras em grupos curtos (o que aparece de cada vez). */
51
+ export declare const groupWords: (words: CaptionWord[], style: CaptionStyle) => CaptionWord[][];
52
+ /** Um grupo no ecra com uma palavra realcada. */
53
+ export interface CaptionState {
54
+ words: CaptionWord[];
55
+ /** Indice, dentro de `words`, da palavra realcada. */
56
+ active: number;
57
+ t0: number;
58
+ t1: number;
59
+ }
60
+ /**
61
+ * Um estado por palavra: o grupo inteiro fica no ecra e o realce salta de
62
+ * palavra em palavra. A ultima de cada grupo ganha `tail` segundos, mas nunca
63
+ * entra pelo grupo seguinte adentro.
64
+ */
65
+ export declare const captionStates: (chunks: CaptionWord[][], tail: number) => CaptionState[];
66
+ /**
67
+ * Um pedaco do source que sobreviveu a` montagem: `[in, out]` no material
68
+ * bruto, a comecar em `start` na timeline.
69
+ */
70
+ export interface SourceSpan {
71
+ in: number;
72
+ out: number;
73
+ start: number;
74
+ }
75
+ /** Os pedacos de um asset que estao na timeline, por ordem de montagem. */
76
+ export declare const sourceSpans: (project: Project, assetId: string) => SourceSpan[];
77
+ export declare const remapWords: (words: CaptionWord[], spans: SourceSpan[], borda?: number) => CaptionWord[];
78
+ /** Todas as legendas do projecto, ja em tempo de timeline e por ordem. */
79
+ export declare const timelineWords: (project: Project) => CaptionWord[];
@@ -0,0 +1,257 @@
1
+ /**
2
+ * A unica porta do oms-video para a cloud do omelhorsite.
3
+ *
4
+ * Tudo passa pelo SDK oficial (`@omelhorsite/sdk`): o device grant para
5
+ * entrar, o storage para os projectos, as ferramentas para transcrever e
6
+ * legendar. Este modulo nao sabe o que e' um ficheiro nem uma janela: recebe
7
+ * bytes e um `fetch`, e devolve valores.
8
+ *
9
+ * Dois clientes, de proposito (ver OAuthTokenProvider no SDK): um ANONIMO
10
+ * para as rotas /oauth/* (que recusam Authorization) e um credenciado para o
11
+ * resto. O provider de tokens renova-se sozinho e persiste no `store` que o
12
+ * host injecta.
13
+ */
14
+ import { OAuthTokenProvider, Oms, type CaptionJob, type CaptionStyle as SdkCaptionStyle, type CaptionWord, type DeviceAuthorization, type FetchLike, type FsNode, type Progress, type TokenSet, type TokenStore } from "@omelhorsite/sdk";
15
+ import { type CloudWord, type SubtitleCue } from "./srt";
16
+ /**
17
+ * O client_id OAuth do editor de video. Nao e' um segredo (viaja em claro em
18
+ * todos os pedidos), mas tem de estar registado no servidor de autorizacao
19
+ * como cliente publico; um id desconhecido responde `invalid_client`.
20
+ */
21
+ export declare const DEFAULT_CLIENT_ID = "oms-video";
22
+ /** Os escopos mais estreitos que servem: identidade, storage e ferramentas. */
23
+ export declare const OMS_VIDEO_SCOPES: readonly ["openid", "storage:read", "storage:write", "tools:read", "tools:write"];
24
+ /** Pasta no storage do utilizador onde os .omsv vivem. */
25
+ export declare const PROJECTS_FOLDER = "oms-video";
26
+ export declare const PROJECT_EXTENSION = ".omsv";
27
+ export interface CloudOptions {
28
+ /** Onde os tokens vivem. Obrigatorio: sem store nao ha' sessao que sobreviva. */
29
+ readonly store: TokenStore;
30
+ readonly clientId?: string;
31
+ /** Omissao: o baseUrl do SDK (https://backend.omelhorsite.pt). */
32
+ readonly baseUrl?: string;
33
+ /** Omissao: globalThis.fetch. Um host fora do sandbox do browser injecta o seu. */
34
+ readonly fetch?: FetchLike;
35
+ /** Vai no X-Oms-Client, ex. "oms-video-cli/0.1.0". */
36
+ readonly clientName?: string;
37
+ readonly scopes?: readonly string[];
38
+ /**
39
+ * Um token de sessao legado (UUID) em vez do OAuth. So' para automacao do
40
+ * dono; o fluxo normal e' `login()`. Com ele as rotas que recusam OAuth
41
+ * (upload por partes das captions) passam a estar ao alcance.
42
+ */
43
+ readonly sessionToken?: string;
44
+ }
45
+ /** Quem esta' autenticado, lido do id token (sub) e, se pedido, do /account. */
46
+ export interface CloudSession {
47
+ /** Identificador ESTAVEL do utilizador. O handle e o email mudam; isto nao. */
48
+ readonly sub: string;
49
+ readonly scopes: readonly string[];
50
+ /** Expiracao do access token (epoch ms); o provider renova-o sozinho. */
51
+ readonly expiresAt?: number;
52
+ readonly handle?: string;
53
+ readonly email?: string;
54
+ }
55
+ /** O que o host mostra durante o device flow. */
56
+ export interface LoginPrompt extends DeviceAuthorization {
57
+ /** O URL a abrir: o completo (codigo pre-preenchido) quando o servidor o da'. */
58
+ readonly url: string;
59
+ }
60
+ export interface LoginOptions {
61
+ /** Chamado UMA vez com o codigo e o URL. O host mostra-os e/ou abre o browser. */
62
+ readonly onPrompt: (prompt: LoginPrompt) => void | Promise<void>;
63
+ readonly onPoll?: (state: "pending" | "slow_down") => void;
64
+ readonly signal?: AbortSignal;
65
+ /**
66
+ * Substitui o intervalo que o servidor pediu. SO' para testes: em producao
67
+ * honra-se o `interval` do grant, e um `slow_down` sobe-o de qualquer forma.
68
+ */
69
+ readonly pollIntervalMs?: number;
70
+ }
71
+ /** Um .omsv na cloud. */
72
+ export interface CloudProject {
73
+ readonly id: string;
74
+ readonly name: string;
75
+ readonly size: number;
76
+ readonly createdAt: string;
77
+ readonly updatedAt: string;
78
+ }
79
+ /** Bytes com nome: o que o host tem de dar para subir alguma coisa. */
80
+ export interface CloudFile {
81
+ readonly data: Blob | Uint8Array;
82
+ readonly name: string;
83
+ /** Tamanho em bytes quando se sabe: deixa o SDK escolher multipart sem ler tudo. */
84
+ readonly size?: number;
85
+ readonly contentType?: string;
86
+ }
87
+ export interface TransferOptions {
88
+ readonly onProgress?: (progress: Progress) => void;
89
+ readonly signal?: AbortSignal;
90
+ }
91
+ export interface WaitTransferOptions extends TransferOptions {
92
+ /** Tecto para a espera pelo job; sem ele espera-se o que for preciso. */
93
+ readonly waitTimeoutMs?: number;
94
+ }
95
+ export interface TranscribeOptions extends WaitTransferOptions {
96
+ /** ISO. Omissao: deteccao automatica. */
97
+ readonly language?: string;
98
+ }
99
+ export interface CloudTranscription {
100
+ readonly id: string;
101
+ readonly text: string;
102
+ readonly language?: string;
103
+ readonly cues: SubtitleCue[];
104
+ /**
105
+ * Palavras com tempos APROXIMADOS, repartidos por deixa. A ferramenta de
106
+ * transcricao nao devolve tempos por palavra (lacuna do SDK/backend); para
107
+ * tempos exactos ha' `captionsTranscribe`.
108
+ */
109
+ readonly words: CloudWord[];
110
+ readonly approximate: true;
111
+ readonly durationSeconds: number;
112
+ }
113
+ export interface CaptionsTranscribeOptions extends WaitTransferOptions {
114
+ /** Janela do video a transcrever, em segundos do inicio. Omissao: o video todo (max 15 min). */
115
+ readonly start?: number;
116
+ readonly end?: number;
117
+ readonly language?: string;
118
+ }
119
+ export interface CaptionsRenderOptions extends WaitTransferOptions {
120
+ /** Palavras a queimar. Omissao: as que o job ja' tem. */
121
+ readonly words?: CaptionWord[];
122
+ readonly style?: SdkCaptionStyle;
123
+ }
124
+ export interface CaptionsOptions extends CaptionsTranscribeOptions, CaptionsRenderOptions {
125
+ /** Mantem o job no servidor depois de descarregar o resultado. Omissao: apaga-o. */
126
+ readonly keepJob?: boolean;
127
+ }
128
+ export interface CaptionsResult {
129
+ readonly job: CaptionJob;
130
+ readonly words: CaptionWord[];
131
+ /** O mp4 legendado. */
132
+ readonly output: Blob;
133
+ }
134
+ export declare class Cloud {
135
+ readonly clientId: string;
136
+ readonly baseUrl: string;
137
+ readonly scopes: readonly string[];
138
+ /** Sem credencial: /oauth/* recusa um Authorization. */
139
+ readonly anon: Oms;
140
+ /** O provider que renova e persiste. `null` quando se usa um token de sessao. */
141
+ readonly tokens: OAuthTokenProvider | null;
142
+ /** O cliente credenciado. E' o que `client()` devolve. */
143
+ readonly oms: Oms;
144
+ /** `true` no fluxo normal (device grant). Decide o que esta' ao alcance. */
145
+ readonly usesOAuth: boolean;
146
+ private readonly store;
147
+ private folderId;
148
+ constructor(options: CloudOptions);
149
+ /** O `Oms` autenticado, para o que este pacote ainda nao embrulha. */
150
+ client(): Oms;
151
+ /**
152
+ * Device grant completo: pede o codigo, entrega-o ao host, espera a
153
+ * aprovacao, guarda os tokens. Resolve com a sessao.
154
+ */
155
+ login(options: LoginOptions): Promise<CloudSession>;
156
+ /** Revoga o grant (best-effort) e esquece os tokens. */
157
+ logout(): Promise<void>;
158
+ /** A sessao guardada, sem rede. `null` quando ninguem entrou. */
159
+ session(): Promise<CloudSession | null>;
160
+ /**
161
+ * Confirma a sessao no servidor. Lanca OmsAuthError se morreu.
162
+ *
163
+ * Vai ao `/oauth/userinfo`, que so' pede `openid`; o `/account` do SDK
164
+ * (`auth.whoami`) exige o escopo `profile`, que este cliente nao pede por
165
+ * omissao. So' quando `profile` foi concedido e' que o handle e o email
166
+ * vem preenchidos.
167
+ */
168
+ whoami(): Promise<CloudSession>;
169
+ /** A pasta `oms-video/` na home do utilizador; cria-a a primeira vez. */
170
+ projectsFolder(): Promise<FsNode>;
171
+ /** Os .omsv da pasta, mais recentes primeiro. */
172
+ listProjects(): Promise<CloudProject[]>;
173
+ /** Um projecto pelo id, ou pelo nome (com ou sem .omsv) dentro da pasta. */
174
+ findProject(idOrName: string): Promise<CloudProject | null>;
175
+ /**
176
+ * Sobe um .omsv inteiro como UM ficheiro para `oms-video/`. Acima de 32 MiB
177
+ * o SDK vai por multipart sozinho (passa-se `size` para nao ler tudo antes).
178
+ *
179
+ * Um projecto com o mesmo nome e' SUBSTITUIDO: o antigo e' renomeado antes,
180
+ * apagado (lixo) depois de o novo estar em cima, e reposto se o upload
181
+ * falhar. Assim nunca fica um nome ocupado por meio ficheiro.
182
+ */
183
+ uploadProject(input: CloudFile, options?: TransferOptions): Promise<CloudProject>;
184
+ /** Desce um .omsv inteiro para memoria, com progresso por bytes lidos. */
185
+ downloadProject(id: string, options?: TransferOptions): Promise<{
186
+ data: Uint8Array;
187
+ name: string;
188
+ project: CloudProject;
189
+ }>;
190
+ /**
191
+ * O URL assinado dos bytes (6 horas), para um host que prefere descarregar
192
+ * ele proprio, por exemplo directo para disco.
193
+ */
194
+ downloadUrl(id: string): Promise<{
195
+ url: string;
196
+ project: CloudProject;
197
+ }>;
198
+ /**
199
+ * Transcricao pela ferramenta `transcriptions` (o servidor delega no
200
+ * transcriber; nunca ha' Whisper de outro lado). Devolve o texto, as deixas
201
+ * do SRT e palavras com tempos aproximados - ver CloudTranscription.words.
202
+ */
203
+ transcribe(media: CloudFile, options?: TranscribeOptions): Promise<CloudTranscription>;
204
+ /**
205
+ * Passo 1 das captions: sobe o video. Com um token OAuth so' o `create`
206
+ * (um POST, ate' ~100 MB) esta' ao alcance; as tres rotas do upload por
207
+ * partes respondem 403 a OAuth, portanto acima de CAPTION_CHUNKED_THRESHOLD
208
+ * isto falha AQUI, com a razao, em vez de depois de subir 64 MiB.
209
+ */
210
+ captionsUpload(video: CloudFile, options?: TransferOptions): Promise<CaptionJob>;
211
+ /**
212
+ * Palavras com tempos EXACTOS (o transcriber com `words`), via um caption
213
+ * job: sobe o video e transcreve uma janela. Os tempos vem em segundos do
214
+ * inicio do video. Quota: a janela, nao o ficheiro.
215
+ */
216
+ captionsTranscribe(video: CloudFile, options?: CaptionsTranscribeOptions): Promise<{
217
+ job: CaptionJob;
218
+ words: CaptionWord[];
219
+ }>;
220
+ /** Passo 2 sobre um job ja' carregado. */
221
+ captionsTranscribeJob(job: CaptionJob, options?: CaptionsTranscribeOptions): Promise<{
222
+ job: CaptionJob;
223
+ words: CaptionWord[];
224
+ }>;
225
+ /** Passo 3: queima as palavras e devolve o mp4. */
226
+ captionsRender(jobId: string, options?: CaptionsRenderOptions): Promise<{
227
+ job: CaptionJob;
228
+ output: Blob;
229
+ }>;
230
+ /**
231
+ * Os tres passos: sobe, transcreve (so' se nao vierem `words`) e renderiza.
232
+ * Com `words` da timeline do projecto, a cloud so' faz a render: e' assim
233
+ * que as legendas do omelhorsite viram uma camada por cima do oms-video.
234
+ */
235
+ captions(video: CloudFile, options?: CaptionsOptions): Promise<CaptionsResult>;
236
+ }
237
+ /** Le a sessao de um TokenSet: o `sub` do id token e os escopos concedidos. */
238
+ export declare const sessionFrom: (set: TokenSet) => CloudSession;
239
+ /** Estilo das legendas do oms-video (fraccoes da LARGURA) em estilo da cloud (fraccoes da ALTURA). */
240
+ export declare const cloudCaptionStyle: (style: {
241
+ fontScale?: number;
242
+ strokeScale?: number;
243
+ pos?: number;
244
+ maxWords?: number;
245
+ gap?: number;
246
+ color?: string;
247
+ highlight?: string;
248
+ stroke?: string;
249
+ }, frame: {
250
+ width: number;
251
+ height: number;
252
+ }) => SdkCaptionStyle;
253
+ /**
254
+ * Uma frase em portugues para cada falha que o utilizador pode resolver.
255
+ * O resto sai com a mensagem original, que e' a que ajuda a depurar.
256
+ */
257
+ export declare const describeCloudError: (e: unknown) => string;
@@ -0,0 +1,4 @@
1
+ export * from "./cloud";
2
+ export * from "./srt";
3
+ export * from "./store";
4
+ export { DEFAULT_BASE_URL, memoryTokenStore, OmsApiError, OmsAuthError, OmsError, OmsNetworkError, OmsQuotaError, OmsTimeoutError, type CaptionJob, type CaptionStyle as CloudCaptionStyle, type FetchLike, type FsNode, type Progress, type TokenSet, type TokenStore, } from "@omelhorsite/sdk";
@@ -0,0 +1,29 @@
1
+ /**
2
+ * SubRip -> palavras.
3
+ *
4
+ * A ferramenta de transcricao do omelhorsite devolve texto e legendas por
5
+ * SEGMENTO (SRT/VTT), nao por palavra. As legendas karaoke do oms-video
6
+ * precisam de um tempo por palavra, por isso cada deixa e' repartida pelas
7
+ * suas palavras em proporcao ao numero de caracteres. E' uma aproximacao, e
8
+ * vem marcada como tal: quem tem o audio pode encostar estas fronteiras aos
9
+ * silencios reais, e quem quer tempos exactos usa o caminho das captions
10
+ * (`captionsTranscribe`), que devolve tempos por palavra.
11
+ */
12
+ export interface SubtitleCue {
13
+ t0: number;
14
+ t1: number;
15
+ text: string;
16
+ }
17
+ export interface CloudWord {
18
+ t0: number;
19
+ t1: number;
20
+ text: string;
21
+ }
22
+ /** Le um SRT (ou um VTT simples) com tolerancia a CRLF, indices em falta e tags. */
23
+ export declare const parseSrt: (text: string) => SubtitleCue[];
24
+ /**
25
+ * Reparte cada deixa pelas suas palavras, proporcionalmente aos caracteres
26
+ * (mais um por palavra, a fazer de espaco). Nunca deixa uma palavra com
27
+ * duracao zero e nunca ultrapassa a deixa seguinte.
28
+ */
29
+ export declare const wordsFromCues: (cues: SubtitleCue[]) => CloudWord[];
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Stores de tokens para os hosts.
3
+ *
4
+ * O SDK so' pede um `TokenStore` (load/save/clear de um `TokenSet`). Onde isso
5
+ * vive e' decisao do host: um ficheiro com permissoes apertadas, um keychain.
6
+ * Este modulo da' a ponte: um store de STRING (o host so' tem de
7
+ * guardar e devolver texto) vira um `TokenStore` tipado, com validacao do que
8
+ * volta - um ficheiro corrompido ou de outra versao le-se como "ninguem
9
+ * autenticado" e nao como uma excepcao no arranque.
10
+ */
11
+ import type { TokenSet, TokenStore } from "@omelhorsite/sdk";
12
+ /** O que o host guarda. Versionado para o dia em que o formato mudar. */
13
+ export interface StoredCredentials {
14
+ version: 1;
15
+ /** API a que estes tokens pertencem; um store partilhado entre ambientes nao se mistura. */
16
+ baseUrl: string;
17
+ clientId: string;
18
+ tokens: TokenSet;
19
+ }
20
+ export declare const CREDENTIALS_VERSION = 1;
21
+ /** Tres funcoes de texto que o host implementa. Todas podem ser async. */
22
+ export interface StringStore {
23
+ load(): string | null | Promise<string | null>;
24
+ save(text: string): void | Promise<void>;
25
+ clear(): void | Promise<void>;
26
+ }
27
+ /** Le com tolerancia: qualquer coisa que nao seja um TokenSet valido conta como nada. */
28
+ export declare const parseStoredCredentials: (text: string | null) => StoredCredentials | null;
29
+ /**
30
+ * Um `TokenStore` por cima de um store de texto. Tokens de OUTRA API ou de
31
+ * outro client_id sao ignorados (e substituidos no proximo login), para um
32
+ * ficheiro de staging nunca autenticar contra producao.
33
+ *
34
+ * O `clear()` so' apaga o que e' desta identidade. O provider do SDK chama
35
+ * clear num 401 sem tokens - e se o ficheiro fosse de outro client_id, um
36
+ * comando lancado com o client_id errado apagava a sessao do certo.
37
+ */
38
+ export declare const stringTokenStore: (inner: StringStore, identity: {
39
+ baseUrl: string;
40
+ clientId: string;
41
+ }) => TokenStore;
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Compila um Project num comando ffmpeg (argv, sem shell). Corre igual no
3
+ * export local e no render do servidor; o preview usa a mesma matematica via
4
+ * placement.ts.
5
+ */
6
+ import { type Project } from "./types";
7
+ export interface CompileOptions {
8
+ output: string;
9
+ /**
10
+ * Clips de texto entram no ffmpeg como PNGs ja rasterizados (pelo canvas do
11
+ * preview, ou por PIL no servidor) - paridade perfeita e zero dependencia
12
+ * de drawtext/freetype no binario. Chave = id do TextClip; a imagem vem a
13
+ * escala de pixels do PROJECTO e e composta 1:1, centrada em (x, y).
14
+ */
15
+ textImages?: Record<string, {
16
+ path: string;
17
+ width: number;
18
+ height: number;
19
+ }>;
20
+ crf?: number;
21
+ preset?: string;
22
+ /**
23
+ * Bitrate do AAC (ex.: "256k"). O default fica em 192k para nao mexer no
24
+ * que ja existe, mas um master nao deve ficar ABAIXO da fonte: re-codificar
25
+ * um 256k para 192k perde por nada.
26
+ */
27
+ audioBitrate?: string;
28
+ /**
29
+ * Algoritmo de reamostragem do `scale`. Lanczos por omissao: a media destes
30
+ * videos vem a 720p de telemovel e sobe para 1080, e nessa subida o lanczos
31
+ * ganha visivelmente ao bicubico do ffmpeg (olhos, cabelo, cantaria).
32
+ */
33
+ scaleFlags?: string;
34
+ /**
35
+ * Encoder de video. `x264` (omissao) da o melhor bitrate/qualidade e demora;
36
+ * `videotoolbox` empurra o encode para o bloco de hardware do Mac e faz um
37
+ * master de 84s em ~12s em vez de ~150s. A qualidade por bit e pior, por
38
+ * isso o hardware trabalha a bitrate fixo (`bitrate`) e nao a CRF.
39
+ */
40
+ videoCodec?: "x264" | "videotoolbox";
41
+ /** Bitrate alvo do encoder de hardware (ex.: "14000k"). */
42
+ bitrate?: string;
43
+ /**
44
+ * Faixa de legendas ja rasterizada (video com alfa, largura do projecto).
45
+ * Entra como um input so' e um overlay so', para as legendas serem queimadas
46
+ * NO MESMO passe da render em vez de num segundo encode por cima. Quem a
47
+ * constroi e' o chamador, a partir das palavras que vivem no proprio
48
+ * projecto (`timelineWords`, `groupWords`, `captionStates`).
49
+ */
50
+ captionOverlay?: {
51
+ path: string;
52
+ width: number;
53
+ height: number;
54
+ x: number;
55
+ y: number;
56
+ };
57
+ }
58
+ export interface CompiledCommand {
59
+ /** Argumentos para o binario ffmpeg (sem o proprio "ffmpeg"). */
60
+ args: string[];
61
+ duration: number;
62
+ }
63
+ export declare const compile: (project: Project, options: CompileOptions) => CompiledCommand;
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Efeitos de ecra inteiro. Hoje so existe um: o "flash de onda de calor" que
3
+ * se usa nas transicoes destes videos verticais.
4
+ *
5
+ * A forma vem MEDIDA de uma transicao de referencia (exportada sobre fundo
6
+ * verde e analisada frame a frame): nao e um veu branco chapado, e um
7
+ * brilho amarelo palido que nasce a baixo e a esquerda, cresce ate engolir o
8
+ * frame em branco por um instante curto, e recolhe pelo mesmo caminho. Por
9
+ * baixo, a imagem sofre um empurrao de zoom e a ondulacao de calor.
10
+ *
11
+ * REGRA DE OURO: o preview e o export tem de ver a
12
+ * mesma coisa. Por isso a matematica vive aqui uma vez so, em duas linguagens
13
+ * postas lado a lado: `flashStateAt`/`flashGlowAt` para o canvas e
14
+ * `flashGeqExpr` para o ffmpeg. Sao a MESMA formula - se mexeres numa, mexes
15
+ * na outra, e test/fx.test.ts fica vermelho se se separarem.
16
+ */
17
+ import type { FxClip } from "./types";
18
+ /**
19
+ * A ondulacao e quantizada em bandas horizontais em vez de ser continua: o
20
+ * canvas desenha uma banda por `drawImage` (barato) e o ffmpeg quantiza a
21
+ * coordenada com o mesmo `floor`, por isso os dois lados batem certo.
22
+ */
23
+ export declare const FLASH_STRIPS = 96;
24
+ /** Ciclos que a onda percorre de cima a baixo durante a transicao. */
25
+ export declare const FLASH_WAVE_SPEED = 1.5;
26
+ /**
27
+ * Onde cai o CORTE dentro da transicao - e tambem onde dispara o obturador.
28
+ * Medido na referencia: som aos 6.000s numa transicao de 4.700 a 7.433.
29
+ * `add-flash --at` recebe o instante do corte e recua o clip por esta fraccao.
30
+ */
31
+ export declare const FLASH_CUT_AT = 0.476;
32
+ export declare const DEFAULT_FLASH: {
33
+ readonly intensity: 0.85;
34
+ readonly punch: 0.06;
35
+ readonly wave: 0.02;
36
+ readonly freq: 3.2;
37
+ readonly color: "#ffffff";
38
+ readonly warm: "#f8f399";
39
+ readonly focusX: 0.34;
40
+ readonly focusY: 0.72;
41
+ };
42
+ /**
43
+ * Duracao por omissao. A transicao de referencia foi exportada no ajuste mais
44
+ * lento que ha (2.73s) para se ver bem a forma; a velocidade a que isto se usa
45
+ * e um quarto de segundo - sete frames e meio, um golpe e nao uma passagem. A
46
+ * forma e a mesma, so o relogio e que muda: `duration` estica ou encolhe tudo
47
+ * em proporcao, e o som NAO vai atras (ver soundAssetId em types.ts).
48
+ */
49
+ export declare const DEFAULT_FLASH_DURATION = 0.25;
50
+ /**
51
+ * Envelope 0..1 ao longo do clip: sobe ate PEAK_AT, satura em cima (o
52
+ * patamar) e desce. O patamar e curto de proposito - ver HOLD_GAIN.
53
+ */
54
+ export declare const flashEnvelope: (p: number) => number;
55
+ export interface FlashState {
56
+ /** Progresso 0..1 dentro do clip. */
57
+ progress: number;
58
+ /** Envelope 0..1 do efeito. */
59
+ envelope: number;
60
+ /** Multiplicador de zoom sobre o centro (>= 1). */
61
+ zoom: number;
62
+ /** Amplitude da ondulacao, em fraccao da LARGURA. */
63
+ amp: number;
64
+ /** Fase da onda, em radianos. */
65
+ phase: number;
66
+ }
67
+ /** Estado do flash num instante da timeline, ou null se o clip nao esta activo. */
68
+ export declare const flashStateAt: (t: number, clip: FxClip) => FlashState | null;
69
+ export interface FlashGlow {
70
+ /** Opacidade do veu neste ponto, 0..1. */
71
+ alpha: number;
72
+ /** 0 = amarelo da orla, 1 = branco do nucleo. */
73
+ hot: number;
74
+ }
75
+ /**
76
+ * O brilho a uma distancia `r` do foco, medida em ALTURAS de ecra. E isto que
77
+ * faz o efeito parecer luz a entrar e nao um rectangulo branco: perto do foco
78
+ * chega a branco cedo, longe so mais tarde, e a orla fica amarela.
79
+ *
80
+ * Como o brilho so depende da distancia, o canvas nao precisa de andar pixel a
81
+ * pixel: desenha um gradiente radial com uns quantos stops tirados desta
82
+ * funcao, que e a mesma que o `geq` avalia por pixel no export.
83
+ */
84
+ export declare const flashGlowAtRadius: (state: FlashState, clip: FxClip, r: number) => FlashGlow;
85
+ /** O brilho num ponto do ecra (coordenadas 0..1). */
86
+ export declare const flashGlowAt: (state: FlashState, clip: FxClip, xNormalized: number, yNormalized: number, aspect: number) => FlashGlow;
87
+ /** Distancia (em alturas de ecra) a que o brilho ja nao pinta nada. */
88
+ export declare const flashGlowReach: (state: FlashState) => number;
89
+ /** Opacidade da sombra de um clip em primeiro plano. */
90
+ export declare const SHADOW_OPACITY = 0.5;
91
+ /** Quanto a sombra cai para baixo, em multiplos do raio. */
92
+ export declare const SHADOW_DROP = 0.45;
93
+ /**
94
+ * Margem que a sombra precisa a toda a volta do clip para nao ficar cortada.
95
+ * O canvas nao precisa (desenha para fora sozinho), o ffmpeg precisa de um
96
+ * `pad` explicito antes do desfoque - por isso a conta vive aqui.
97
+ */
98
+ export declare const shadowMargin: (sigmaPixels: number) => number;
99
+ /** Deslocamento horizontal da banda `band` (0..1), em fraccao da largura. */
100
+ export declare const flashBandOffset: (state: FlashState, freq: number, band: number) => number;
101
+ /** Banda (0..1) a que pertence uma linha, com a MESMA quantizacao do ffmpeg. */
102
+ export declare const flashBandOf: (yNormalized: number) => number;
103
+ /** Um flash pronto a por na timeline, com o CORTE em `cutAt`. */
104
+ export declare const makeFlashClip: (id: string, cutAt: number, overrides?: Partial<Omit<FxClip, "id" | "kind" | "fx">>) => FxClip;
105
+ /** O instante do corte (e do obturador) de um flash ja colocado. */
106
+ export declare const flashCutTime: (clip: FxClip) => number;
107
+ /** #rrggbb -> valores de plano (Y, Cb, Cr) em gama completa, BT.601. */
108
+ export declare const hexToYuv: (hex: string) => [number, number, number];
109
+ /**
110
+ * A mesma formula escrita na linguagem de expressoes do ffmpeg, para o filtro
111
+ * `geq`. Corre uma vez por pixel e por plano; como usa `p(x,y)`, `W` e `H` do
112
+ * plano CORRENTE, serve tal e qual para o luma e para os dois planos de croma
113
+ * (que sao metade do tamanho) sem contas extra.
114
+ *
115
+ * `warmValue`/`coldValue` sao o valor do amarelo e do branco NESTE plano.
116
+ *
117
+ * ATENCAO: o `eval` do ffmpeg so tem DEZ registos, st(0) a st(9). Um st(10)
118
+ * nao da erro - escreve em cima do 9 e o efeito sai calado e errado. Se
119
+ * precisares de mais uma variavel, inlina uma, nao inventes um registo.
120
+ *
121
+ * Registos: 1 progresso 2 envelope 3 zoom 4 "quente" no tempo 5 alpha
122
+ * 6 offset x 7 distancia ao foco 8 brilho 9 "quente" final
123
+ */
124
+ export declare const flashGeqExpr: (clip: FxClip, warmValue: number, coldValue: number) => string;
@@ -0,0 +1,8 @@
1
+ export * from "./types";
2
+ export * from "./placement";
3
+ export * from "./compile";
4
+ export * from "./packfile";
5
+ export * from "./fx";
6
+ export * from "./captions";
7
+ export * from "./slice";
8
+ export * from "./cloud/index";
@@ -0,0 +1,44 @@
1
+ /**
2
+ * O formato de ficheiro .omsv: um ZIP (como um .jar) com TUDO dentro -
3
+ * timeline + media. Portavel, inspeccionavel por humanos (unzip) e por
4
+ * agentes. Media guardada SEM compressao (ja vem comprimida;
5
+ * gravar fica a velocidade de copia de disco).
6
+ *
7
+ * projecto.omsv
8
+ * ├── manifest.json { format: "omsv", schema, savedAt }
9
+ * ├── timeline.json Project com asset.path RELATIVOS (assets/...)
10
+ * └── assets/<id>-<nome> a media embebida
11
+ *
12
+ * Este modulo e puro (bytes para dentro, bytes para fora): quem chama trata
13
+ * do filesystem.
14
+ */
15
+ import type { Asset, Project } from "./types";
16
+ export declare const OMSV_SCHEMA = 1;
17
+ export interface OmsvManifest {
18
+ format: "omsv";
19
+ schema: number;
20
+ savedAt?: string;
21
+ appVersion?: string;
22
+ }
23
+ /** Um .omsv comeca por "PK"; o formato legado era JSON puro ("{"). */
24
+ export declare const isZipData: (bytes: Uint8Array) => boolean;
25
+ /** Nome canonico da entrada de um asset dentro do zip. */
26
+ export declare const assetEntryName: (asset: Asset) => string;
27
+ export interface PackInput {
28
+ project: Project;
29
+ /** Bytes de cada asset, por id. Assets ausentes ficam de fora (com aviso do chamador). */
30
+ assetBytes: Map<string, Uint8Array>;
31
+ appVersion?: string;
32
+ /** Instante da gravacao em ISO, se o chamador o quiser registar. */
33
+ nowIso?: string;
34
+ }
35
+ /** Serializa o projecto num .omsv. Os paths sao reescritos para relativos. */
36
+ export declare const packProject: ({ project, assetBytes, appVersion, nowIso }: PackInput) => Uint8Array;
37
+ export interface UnpackResult {
38
+ manifest: OmsvManifest;
39
+ /** Project tal como gravado: asset.path RELATIVOS (assets/...). */
40
+ project: Project;
41
+ /** Bytes por entrada relativa (assets/...). */
42
+ assetBytes: Map<string, Uint8Array>;
43
+ }
44
+ export declare const unpackProject: (bytes: Uint8Array) => UnpackResult;