primocode 8.35.0 → 8.37.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/lib/act.js +68 -0
- package/lib/browser.js +292 -1
- package/lib/catalogo.js +11 -2
- package/lib/claude-engine.js +25 -2
- package/lib/codex-engine.js +9 -2
- package/lib/pipeline.js +447 -0
- package/lib/studio.js +16 -3
- package/lib/tools.js +2 -1
- package/package.json +2 -2
- package/studio/ferramentas/corte.md +173 -10
- package/studio/server.py +107 -4
- package/studio/web/js/analise.js +319 -11
- package/studio/web/js/audio.js +367 -55
- package/studio/web/js/render.js +352 -3
- package/studio/web/js/video.js +4 -0
- package/studio/web/render.css +227 -3
|
@@ -84,12 +84,12 @@ cena. Se o assunto não empurrar para nenhuma, escolha a que você NÃO usou da
|
|
|
84
84
|
| Assunto | `tema` | `trilha` | Estrutura |
|
|
85
85
|
|---|---|---|---|
|
|
86
86
|
| produto, tecnologia, lançamento | `meia-noite` ou `neon` | `pulso` | **Gancho** |
|
|
87
|
-
| institucional, quem somos, serviço | `oceano` ou `floresta` | `
|
|
87
|
+
| institucional, quem somos, serviço | `oceano` ou `floresta` | `foco` | **Retrato** |
|
|
88
88
|
| urgência, notícia, alerta, dado duro | `brasa` | `tenso` | **Manchete** |
|
|
89
|
-
| explicação, tutorial, passo a passo | `papel` ou `claro` | `
|
|
89
|
+
| explicação, tutorial, passo a passo | `papel` ou `claro` | `foco` | **Aula** |
|
|
90
90
|
| conquista, agradecimento, celebração | `doce` ou `neon` | `alegre` | **Brinde** |
|
|
91
91
|
| memória, história, retrospectiva | `retro` ou `mono` | `lofi` | **Linha** |
|
|
92
|
-
| curso, aula, treinamento, método | `ambar` | `
|
|
92
|
+
| curso, aula, treinamento, método | `ambar` | `suave` | **Aula** |
|
|
93
93
|
| relatório, número, análise técnica | `grafite` | `ambiente` | **Manchete** |
|
|
94
94
|
|
|
95
95
|
### As seis estruturas
|
|
@@ -161,9 +161,16 @@ Música sintetizada na hora, sem arquivo e sem licença de terceiro.
|
|
|
161
161
|
| `epico` | grave, crescendo | 88 |
|
|
162
162
|
| `alegre` | maior, arpejo rápido | 124 |
|
|
163
163
|
| `tenso` | menor, suspense | 100 |
|
|
164
|
+
| `suave` | cama grave, sem bateria, quase não se nota | 60 |
|
|
165
|
+
| `foco` | pulso leve, sem caixa, para tutorial e institucional | 96 |
|
|
164
166
|
|
|
165
167
|
A trilha gerada não acaba — acompanha o vídeo até a última cena.
|
|
166
168
|
|
|
169
|
+
**Vídeo de alguém falando pede `suave` ou `foco`.** As duas foram feitas para
|
|
170
|
+
ficar EMBAIXO de uma voz: moram fora da faixa da fala e não têm caixa batendo
|
|
171
|
+
em cima dela. `epico` e `tenso` são faixas grandes — bonitas num teaser sem
|
|
172
|
+
narração, e uma disputa com quem fala em qualquer outro caso.
|
|
173
|
+
|
|
167
174
|
### Arquivo do usuário
|
|
168
175
|
|
|
169
176
|
```json
|
|
@@ -213,9 +220,135 @@ Isso vale para o projeto do usuário, não para o PrimoCode.
|
|
|
213
220
|
|
|
214
221
|
## Elementos que dão cara de produção
|
|
215
222
|
|
|
216
|
-
Estes
|
|
223
|
+
Estes existem porque um vídeo genérico é feito só de título + texto. São
|
|
217
224
|
baratos de escrever e mudam completamente o resultado.
|
|
218
225
|
|
|
226
|
+
**Todo vídeo leva pelo menos um.** Texto sobre fundo, do primeiro segundo ao
|
|
227
|
+
último, é o vídeo que o usuário chamou de "sem efeito nenhum" — e não é falta
|
|
228
|
+
de recurso: é este bloco não ter sido lido.
|
|
229
|
+
|
|
230
|
+
## Anúncio de marca — o kit
|
|
231
|
+
|
|
232
|
+
Quando o pedido é o COMERCIAL de um produto (um cartão, um app, um plano), o
|
|
233
|
+
que separa uma peça de agência de um slide com música são quatro coisas, e as
|
|
234
|
+
quatro têm elemento próprio. Uma peça de marca sem elas sai genérica por mais
|
|
235
|
+
bonito que esteja o texto.
|
|
236
|
+
|
|
237
|
+
**Primeiro, declare a marca no documento.** Ela pinta o destaque de tudo —
|
|
238
|
+
número, botão, detalhe, o cartão — e libera o fundo `"marca"`:
|
|
239
|
+
|
|
240
|
+
```json
|
|
241
|
+
"marca": { "cor": "#21C25E", "logo": "/midia/logo.svg" },
|
|
242
|
+
"tema": "claro"
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Tema CLARO na maioria dos anúncios de marca. O escuro é para a cena de
|
|
246
|
+
produto e para a vitrine; o resto da peça respira no branco.
|
|
247
|
+
|
|
248
|
+
### `cartao` — o produto
|
|
249
|
+
|
|
250
|
+
```json
|
|
251
|
+
{ "tipo": "cartao", "nome": "PicPay", "variante": "Gold", "movimento": "flutuar" }
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Sai um cartão desenhado: chip, símbolo de aproximação, numeração mascarada,
|
|
255
|
+
nome e validade, com luz e sombra. `cor` (sem ela, a da marca), `largura`,
|
|
256
|
+
`numero` (só os 4 últimos aparecem), `titular`, `validade`, `face: "verso"`,
|
|
257
|
+
`movimento`: `flutuar` | `girar`.
|
|
258
|
+
|
|
259
|
+
**Mostre o produto antes de falar dele.** Um anúncio de cartão que só tem
|
|
260
|
+
frases é um anúncio sem produto.
|
|
261
|
+
|
|
262
|
+
### `app` — a tela do aplicativo
|
|
263
|
+
|
|
264
|
+
```json
|
|
265
|
+
{ "tipo": "app", "topo": "Minha conta", "rotulo": "saldo disponível",
|
|
266
|
+
"valor": "R$ 1.284,90",
|
|
267
|
+
"linhas": [["Café da manhã", "R$ 18,90"], ["Mercado", "R$ 92,40"]],
|
|
268
|
+
"botao": "Pagar com o cartão" }
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
Cabeçalho, um número grande, linhas com o nome à esquerda e o valor à direita
|
|
272
|
+
separadas por fio, e o botão cheio embaixo — que é o que faz o olho ler
|
|
273
|
+
"aplicativo". Aceita `["nome", "valor"]` e `"nome|valor"`. `escuro: true` para
|
|
274
|
+
a versão noturna.
|
|
275
|
+
|
|
276
|
+
**Ele vai DENTRO do `dispositivo`**, e aí ocupa a tela inteira do aparelho:
|
|
277
|
+
|
|
278
|
+
```json
|
|
279
|
+
{ "tipo": "dispositivo", "forma": "celular", "dentro": [ { "tipo": "app", ... } ] }
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
Solto na cena ele vira o print da campanha. É o mesmo elemento de propósito:
|
|
283
|
+
com dois, a tela do celular e o print divergiriam na primeira mudança.
|
|
284
|
+
|
|
285
|
+
### `vitrine` — a linha de produtos
|
|
286
|
+
|
|
287
|
+
```json
|
|
288
|
+
{ "tipo": "vitrine", "titulo": "Escolha seu cartão", "atual": 1, "nome": "PicPay",
|
|
289
|
+
"itens": [ { "nome": "Gold", "cor": "#C9A227", "detalhe": "sem anuidade" },
|
|
290
|
+
{ "nome": "Verde", "detalhe": "cashback" },
|
|
291
|
+
{ "nome": "Black", "cor": "#181B1F", "detalhe": "sala VIP" } ] }
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
Os cartões numa fila, o `atual` à frente e os outros recuados. Vai bem sobre
|
|
295
|
+
fundo escuro (`"fundo": "#0b0f0c"`), que é como toda tela de "escolha o seu" é
|
|
296
|
+
desenhada.
|
|
297
|
+
|
|
298
|
+
### `assinatura` — o ponto final
|
|
299
|
+
|
|
300
|
+
```json
|
|
301
|
+
{ "tipo": "assinatura", "nome": "PicPay", "frase": "o jeito fácil de pagar", "sobre": "marca" }
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Ela toma a CENA INTEIRA: o logo sozinho, grande, sobre a cor da casa. `sobre`:
|
|
305
|
+
`marca` | `claro` | `escuro`. Com `logo` ela usa a imagem; sem, o `nome`.
|
|
306
|
+
|
|
307
|
+
**Todo anúncio termina assim.** Sem a assinatura a peça acaba numa frase, e
|
|
308
|
+
ninguém fica sabendo quem falou.
|
|
309
|
+
|
|
310
|
+
### A ordem que funciona
|
|
311
|
+
|
|
312
|
+
Uma peça de marca de 20 a 30 segundos, com 8 a 10 cenas:
|
|
313
|
+
|
|
314
|
+
| # | cena | o que entra |
|
|
315
|
+
|---|---|---|
|
|
316
|
+
| 1 | a promessa, em uma frase curta | `texto` pequeno, muito ar, fundo claro |
|
|
317
|
+
| 2 | o produto | `cartao` com `movimento` |
|
|
318
|
+
| 3 | o mote, na cor da casa | `fundo: "marca"`, `texto` branco, `anim: "palavra"` |
|
|
319
|
+
| 4 | funcionando | `dispositivo` + `app` |
|
|
320
|
+
| 5 | o benefício | `texto` |
|
|
321
|
+
| 6 | a linha | `vitrine` sobre fundo escuro |
|
|
322
|
+
| 7 | a última razão | `texto` |
|
|
323
|
+
| 8 | assinatura | `assinatura` |
|
|
324
|
+
|
|
325
|
+
E o rosto de quem fala, quando houver, entra por `clipe` entre a 4 e a 5 — o
|
|
326
|
+
depoimento é o que dá confiança, e nenhum desenho substitui.
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
### `icone` — o desenho ao lado do argumento
|
|
331
|
+
|
|
332
|
+
```json
|
|
333
|
+
{ "tipo": "icone", "nome": "raio", "tamanho": 120 }
|
|
334
|
+
{ "tipo": "icone", "nome": "crescimento", "tamanho": 90, "cor": "#22d3ee", "traco": 2 }
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
O jeito mais barato de uma cena deixar de ser um slide. Um ícone grande acima
|
|
338
|
+
do título, ou um por item de uma lista de três, e a mesma frase passa a ter
|
|
339
|
+
imagem.
|
|
340
|
+
|
|
341
|
+
Os nomes que existem (traço, no tom do tema):
|
|
342
|
+
|
|
343
|
+
`raio` `alvo` `foguete` `escudo` `relogio` `usuario` `pessoas` `coracao`
|
|
344
|
+
`estrela` `nuvem` `engrenagem` `lampada` `cadeado` `chave` `casa` `email`
|
|
345
|
+
`telefone` `camera` `fogo` `globo` `presente` `sino` `mapa` `dinheiro`
|
|
346
|
+
`crescimento` `balao` `pasta` `livro` `check` `grafico` `codigo` `video`
|
|
347
|
+
`play` `olho` `baixar` `tela` `cartao` `numero` `tabela` `imagem` `lista`
|
|
348
|
+
|
|
349
|
+
Nome que não existe desenha a estrela — então use os daqui, e não o que
|
|
350
|
+
parecer natural.
|
|
351
|
+
|
|
219
352
|
### `selo` — a pílula de estado
|
|
220
353
|
|
|
221
354
|
```json
|
|
@@ -373,16 +506,46 @@ trecho é longo, quebre em mais cenas do mesmo `src` em vez de encher a tela.
|
|
|
373
506
|
|
|
374
507
|
### Só a voz dele
|
|
375
508
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
509
|
+
Três combinações, e elas resolvem coisas diferentes:
|
|
510
|
+
|
|
511
|
+
**A voz dele com a imagem dele** (o padrão) — `"mudo": false` no clipe e a
|
|
512
|
+
trilha em volume baixo (`"volume": 0.15`) ou `"trilha": {"tipo": "nenhuma"}`.
|
|
513
|
+
|
|
514
|
+
**A imagem dele com a sua narração por cima** — `"mudo": true` no clipe e
|
|
379
515
|
`narracao` na cena.
|
|
380
516
|
|
|
517
|
+
**A VOZ DELE COM A SUA TELA POR CIMA** — `"apenas": "som"` no clipe:
|
|
518
|
+
|
|
519
|
+
```json
|
|
520
|
+
{ "layout": "centro", "elementos": [
|
|
521
|
+
{ "tipo": "clipe", "src": "/midia/gravacao.mp4", "de": 9.5, "ate": 17.1, "apenas": "som" },
|
|
522
|
+
{ "tipo": "icone", "nome": "crescimento", "tamanho": 120 },
|
|
523
|
+
{ "tipo": "texto", "papel": "titulo", "texto": "Três vezes mais rápido" },
|
|
524
|
+
{ "tipo": "numeros", "itens": ["3x|velocidade", "0|instalação"], "contar": true } ] }
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
O clipe some do quadro e continua tocando: a cena dura o trecho, o áudio dele
|
|
528
|
+
entra no arquivo exportado, e o que se VÊ é o que você montou. É assim que se
|
|
529
|
+
entrega "só a voz, com uma tela de exemplos" — a gravação vira a narração da
|
|
530
|
+
sua peça.
|
|
531
|
+
|
|
532
|
+
Use na parte em que ele EXPLICA, e não na em que ele aparece: o rosto de quem
|
|
533
|
+
fala é o que dá confiança, e escondê-lo o vídeo inteiro joga isso fora. O
|
|
534
|
+
desenho que costuma funcionar é alternar — ele na tela quando se apresenta e
|
|
535
|
+
quando conclui, a tela montada enquanto ele explica.
|
|
536
|
+
|
|
537
|
+
Não junte `"apenas": "som"` com `"mudo": true`: um cancela o outro e a cena
|
|
538
|
+
vira um silêncio do tamanho do trecho.
|
|
539
|
+
|
|
381
540
|
### A música que combina
|
|
382
541
|
|
|
383
|
-
Não repita a mesma trilha em todo vídeo do usuário.
|
|
384
|
-
|
|
385
|
-
|
|
542
|
+
Não repita a mesma trilha em todo vídeo do usuário.
|
|
543
|
+
|
|
544
|
+
A primeira pergunta é se **alguém fala** no vídeo — narração ou o áudio da
|
|
545
|
+
gravação. Se fala, a trilha é cama: `suave` (mais discreta) ou `foco` (com
|
|
546
|
+
andamento). Só depois vem o assunto: depoimento e história pessoal pedem `lofi`
|
|
547
|
+
ou `ambiente`; anúncio e lançamento pedem `pulso` ou `epico`; algo leve pede
|
|
548
|
+
`alegre`. A tabela de estilos está acima.
|
|
386
549
|
|
|
387
550
|
---
|
|
388
551
|
|
package/studio/server.py
CHANGED
|
@@ -751,11 +751,19 @@ def criar_analise(dados):
|
|
|
751
751
|
for velho in sorted(ANALISES, key=lambda k: ANALISES[k]["criado"])[:len(ANALISES) - LIMITE_ANALISES + 1]:
|
|
752
752
|
ANALISES.pop(velho, None)
|
|
753
753
|
|
|
754
|
+
# `manter` e `repetida` viajam SEM valor padrão aqui, e isso é de propósito:
|
|
755
|
+
# quem decide quanto de pausa fica é a página (ela conhece o tamanho de cada
|
|
756
|
+
# uma), e um padrão escrito também deste lado seria um segundo dono da mesma
|
|
757
|
+
# escolha — os dois divergem na primeira mudança e o que vale passa a ser o
|
|
758
|
+
# que ninguém lembra de olhar.
|
|
759
|
+
pedido = dados or {}
|
|
754
760
|
ident = uuid.uuid4().hex[:10]
|
|
755
761
|
ANALISES[ident] = {
|
|
756
762
|
"id": ident, "arquivo": arquivo, "estado": "pedido",
|
|
757
|
-
"silencio": float(
|
|
758
|
-
"pausa": float(
|
|
763
|
+
"silencio": float(pedido.get("silencio") or 0.45),
|
|
764
|
+
"pausa": float(pedido.get("pausa") or 0.6),
|
|
765
|
+
"manter": None if pedido.get("manter") is None else float(pedido["manter"]),
|
|
766
|
+
"repetida": pedido.get("repetida") is not False,
|
|
759
767
|
"criado": time.time(), "resultado": None,
|
|
760
768
|
}
|
|
761
769
|
return {"id": ident, "pagina": "/analise#" + ident, "estado": "pedido"}, 201
|
|
@@ -770,10 +778,16 @@ def gravar_analise(ident, dados):
|
|
|
770
778
|
tarefa["resultado"] = {"erro": str(dados["erro"])[:300]}
|
|
771
779
|
else:
|
|
772
780
|
tarefa["estado"] = "pronto"
|
|
781
|
+
# ESTE DICIONÁRIO É UMA PENEIRA, e campo que não está aqui morre em
|
|
782
|
+
# silêncio. Foi assim que a lista de falas repetidas sumiu no dia em que
|
|
783
|
+
# a página passou a devolvê-la: o vídeo saía com o tropeço já cortado e
|
|
784
|
+
# nada na tela dizia o que tinha sido tirado. Campo novo na página entra
|
|
785
|
+
# AQUI junto, ou não existe.
|
|
773
786
|
tarefa["resultado"] = {
|
|
774
787
|
"duracao": dados.get("duracao"),
|
|
775
788
|
"trechos": dados.get("trechos") or [],
|
|
776
789
|
"pausas": dados.get("pausas") or [],
|
|
790
|
+
"repetidos": dados.get("repetidos") or [],
|
|
777
791
|
"cortado": dados.get("cortado"),
|
|
778
792
|
}
|
|
779
793
|
return {"ok": True, "estado": tarefa["estado"]}, 200
|
|
@@ -820,8 +834,10 @@ def revisar_monotonia(doc, ferramenta):
|
|
|
820
834
|
|
|
821
835
|
if ferramenta == "corte" and not doc.get("trilha"):
|
|
822
836
|
avisos.append(
|
|
823
|
-
"o vídeo está sem trilha.
|
|
824
|
-
"lofi, epico, alegre, tenso) e nenhum custa arquivo nem licença
|
|
837
|
+
"o vídeo está sem trilha. Oito estilos gerados existem (pulso, ambiente, "
|
|
838
|
+
"lofi, epico, alegre, tenso, suave, foco) e nenhum custa arquivo nem licença. "
|
|
839
|
+
"Se alguém FALA no vídeo, use suave ou foco: as duas foram feitas para ficar "
|
|
840
|
+
"embaixo de uma voz. Escolha "
|
|
825
841
|
"pelo assunto.")
|
|
826
842
|
|
|
827
843
|
duracoes = [c.get("duracao") for c in cenas if isinstance(c.get("duracao"), (int, float))]
|
|
@@ -840,6 +856,77 @@ def revisar_monotonia(doc, ferramenta):
|
|
|
840
856
|
'toda animação de entrada é "%s". Varie: subir, surgir, esquerda, direita, '
|
|
841
857
|
"zoom, desfoque, cair, girar, saltar, revelar, cortina, elastico." % anims[0])
|
|
842
858
|
|
|
859
|
+
# ── O QUE FALTA, E NÃO SÓ O QUE SE REPETE ───────────────────────────
|
|
860
|
+
#
|
|
861
|
+
# "Falta de efeitos visuais: não adiciona efeitos, não insere ícones ou
|
|
862
|
+
# elementos visuais de apoio no vídeo final."
|
|
863
|
+
#
|
|
864
|
+
# Os avisos acima cobram VARIEDADE — eles só disparam quando alguma coisa
|
|
865
|
+
# foi usada e usada sempre igual. Um vídeo que não usa NADA passava por
|
|
866
|
+
# todos eles calado, e era esse o vídeo do relato: texto sobre fundo, do
|
|
867
|
+
# primeiro segundo ao último. O que falta não aparece contando repetição;
|
|
868
|
+
# aparece contando ausência.
|
|
869
|
+
todos = [e for c in cenas for e in (c.get("elementos") or []) if isinstance(e, dict)]
|
|
870
|
+
tipos = {e.get("tipo") for e in todos}
|
|
871
|
+
|
|
872
|
+
if not doc.get("acabamento") and not any(c.get("acabamento") for c in cenas):
|
|
873
|
+
avisos.append(
|
|
874
|
+
"nenhuma cena tem acabamento. É a textura por cima do quadro inteiro "
|
|
875
|
+
"(grao, vinheta, scanlines, luz, brilho, moldura) e é o que tira a cara "
|
|
876
|
+
'de template: "acabamento": "grao" no documento vale para o vídeo todo.')
|
|
877
|
+
|
|
878
|
+
apoio = {"icone", "selo", "forma", "grafico", "progresso", "dispositivo",
|
|
879
|
+
"conversa", "passos", "opcoes", "numeros", "avatar", "citacao"}
|
|
880
|
+
if not (tipos & apoio):
|
|
881
|
+
avisos.append(
|
|
882
|
+
"o vídeo é só texto e imagem: nenhum elemento de apoio em %d cenas. "
|
|
883
|
+
'Um "icone" ao lado do argumento (raio, alvo, foguete, escudo, relogio, '
|
|
884
|
+
"lampada, cadeado, crescimento, pessoas, dinheiro, globo, fogo…), um "
|
|
885
|
+
'"selo" de estado, um "numeros" com "contar": true — é isto que faz a '
|
|
886
|
+
"tela parecer produzida em vez de um slide com legenda." % len(cenas))
|
|
887
|
+
|
|
888
|
+
movimento = any(e.get("chaves") or e.get("movimento") for e in todos)
|
|
889
|
+
if not movimento and not anims:
|
|
890
|
+
avisos.append(
|
|
891
|
+
"nada se mexe: nenhum elemento tem anim, chaves ou movimento. Imagem "
|
|
892
|
+
'parada em vídeo denuncia preguiça — toda "imagem" leva "movimento" '
|
|
893
|
+
"(aproximar, afastar, esquerda, direita, cima), e uma ou duas peças por "
|
|
894
|
+
"vídeo levam chaves de animação.")
|
|
895
|
+
|
|
896
|
+
# ── ANÚNCIO DE MARCA SEM PRODUTO E SEM ASSINATURA ───────────────────
|
|
897
|
+
#
|
|
898
|
+
# "Ainda não tá bom, tá genérico. Quero nesse nível." — o dono, sobre um
|
|
899
|
+
# comercial de fintech mandado como referência.
|
|
900
|
+
#
|
|
901
|
+
# O que separava a peça daqui daquela não era tema nem cor: era não ter o
|
|
902
|
+
# PRODUTO na tela e não assinar no fim. Quem declarou uma `marca` está
|
|
903
|
+
# fazendo peça de marca, e aí estas duas ausências são o defeito inteiro.
|
|
904
|
+
if doc.get("marca") and len(cenas) >= 4:
|
|
905
|
+
if not (tipos & {"cartao", "app", "vitrine", "dispositivo", "imagem", "clipe"}):
|
|
906
|
+
avisos.append(
|
|
907
|
+
"esta peça tem marca declarada e nenhuma cena mostra o PRODUTO — são %d cenas "
|
|
908
|
+
"de texto sobre fundo. Um anúncio de cartão mostra o cartão (`cartao`), o app "
|
|
909
|
+
"funcionando dentro do celular (`dispositivo` + `app`) e a linha de produtos "
|
|
910
|
+
"(`vitrine`). É essa ausência que se lê como genérico." % len(cenas))
|
|
911
|
+
if "assinatura" not in tipos:
|
|
912
|
+
avisos.append(
|
|
913
|
+
'o anúncio acaba sem assinar. A última cena de uma peça de marca é '
|
|
914
|
+
'{"tipo":"assinatura","nome":"…","sobre":"marca"} — o logo sozinho, grande, '
|
|
915
|
+
"sobre a cor da casa. Sem ela a peça termina numa frase e ninguém fica "
|
|
916
|
+
"sabendo quem falou.")
|
|
917
|
+
|
|
918
|
+
# Só a voz com tela montada: existe e quase nunca é lembrado, porque é o
|
|
919
|
+
# contrário do `mudo` e o contrário não estava no vocabulário até agora.
|
|
920
|
+
clipes = [e for e in todos if e.get("tipo") == "clipe"]
|
|
921
|
+
if clipes and len(clipes) >= 3 and not any(
|
|
922
|
+
e.get("apenas") == "som" or e.get("som") is True for e in clipes):
|
|
923
|
+
avisos.append(
|
|
924
|
+
"todos os %d clipes mostram a gravação. Se o que vale é o que ele DIZ, "
|
|
925
|
+
'"apenas": "som" no clipe mantém a voz e libera o quadro para a tela que '
|
|
926
|
+
"você montar por cima — é o vídeo dele virando narração da sua peça. "
|
|
927
|
+
"Vale para a parte em que ele explica, não para a em que ele aparece."
|
|
928
|
+
% len(clipes))
|
|
929
|
+
|
|
843
930
|
return avisos
|
|
844
931
|
|
|
845
932
|
|
|
@@ -962,6 +1049,15 @@ def _clipe_invalido(e, i, j):
|
|
|
962
1049
|
'"de": 12.5, "ate": 19.' % (onde, campo, v))
|
|
963
1050
|
if v < 0:
|
|
964
1051
|
return '%s tem "%s": %r — segundo negativo não existe.' % (onde, campo, v)
|
|
1052
|
+
# "apenas": "som" entrega a VOZ da gravação com a tela montada por cima.
|
|
1053
|
+
# Junto com "mudo": true ele não entrega nada: sem imagem porque foi
|
|
1054
|
+
# escondido, sem som porque foi silenciado. A cena sairia com a duração do
|
|
1055
|
+
# trecho e o conteúdo de um silêncio — que é exatamente o tipo de defeito
|
|
1056
|
+
# que só aparece quando alguém assiste o vídeo pronto.
|
|
1057
|
+
if (e.get("apenas") == "som" or e.get("som") is True) and e.get("mudo"):
|
|
1058
|
+
return ('%s tem "apenas": "som" com "mudo": true. Um cancela o outro: o clipe some da '
|
|
1059
|
+
'tela E fica sem áudio, e a cena vira um silêncio do tamanho do trecho. Para a '
|
|
1060
|
+
'voz dele com a sua tela por cima, tire o "mudo".' % onde)
|
|
965
1061
|
de, ate = e.get("de"), e.get("ate")
|
|
966
1062
|
if isinstance(de, (int, float)) and isinstance(ate, (int, float)) and ate <= de:
|
|
967
1063
|
return ('%s tem "ate": %r antes ou igual a "de": %r. O trecho ficaria com duração zero ou '
|
|
@@ -987,9 +1083,16 @@ def validar_doc(modelo, doc):
|
|
|
987
1083
|
# default do renderizador: texto cru de 16px num canvas de 1920 — ou nada,
|
|
988
1084
|
# quando o texto estava noutro campo. Era o "vídeo com música mas tudo
|
|
989
1085
|
# preto": documento válido na forma, invisível na tela.
|
|
1086
|
+
# O kit de anúncio (cartao, app, vitrine, assinatura) entra na lista abaixo.
|
|
1087
|
+
#
|
|
1088
|
+
# A LISTA É LIDA POR UM TESTE (`vocabulario.test.js`), que a recorta com uma
|
|
1089
|
+
# expressão regular e pega as palavras — então comentário DENTRO dela vira
|
|
1090
|
+
# "tipo que o render precisa desenhar", e o teste passa a cobrar `produto`,
|
|
1091
|
+
# `linha` e `fim`. Texto sobre os tipos mora aqui fora.
|
|
990
1092
|
TIPOS_VALIDOS = {"texto", "lista", "cartoes", "numeros", "tabela", "codigo",
|
|
991
1093
|
"citacao", "forma", "imagem", "icone", "grafico", "no", "conector",
|
|
992
1094
|
"objeto3d", "clipe",
|
|
1095
|
+
"cartao", "app", "vitrine", "assinatura",
|
|
993
1096
|
"selo", "progresso", "avatar", "dispositivo", "conversa",
|
|
994
1097
|
"passos", "opcoes"}
|
|
995
1098
|
# Os sete últimos são o vocabulário do vídeo de referência. O comentário
|