primocode 8.36.0 → 8.38.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.
@@ -115,6 +115,13 @@ function aplicarFundo(no, fundo) {
115
115
  // Era metade do "os vídeos ficaram todos iguais".
116
116
  if (/^(malha|brilho|grade|pontos|suave|vinheta|aurora|nevoa|ondas|raios|xadrez)$/.test(fundo)) {
117
117
  no.setAttribute("data-fundo", fundo);
118
+ } else if (fundo === "marca") {
119
+ /* A cor da casa, chapada e sem efeito nenhum. Num anúncio de marca é o
120
+ fundo mais usado de todos — e escrevê-lo como `"#21C25E"` obrigaria a
121
+ repetir o hexadecimal em cada cena, que é como uma delas acaba com a
122
+ cor de ontem depois de a marca mudar de tom. */
123
+ no.style.background = "var(--c-tom)";
124
+ no.setAttribute("data-fundo", "marca");
118
125
  } else no.style.background = fundo;
119
126
  return;
120
127
  }
@@ -214,11 +221,30 @@ function svgGrafico(e, l, a) {
214
221
 
215
222
  /* -------------------------------------------------------------- elementos */
216
223
 
224
+ /* O KIT DE ANÚNCIO — os quatro últimos da lista abaixo.
225
+ *
226
+ * Nasceram de um vídeo de referência mandado pelo dono — um comercial de
227
+ * fintech — com uma frase só: "ainda não tá bom, tá genérico. Quero nesse
228
+ * nível."
229
+ *
230
+ * Medindo a peça de hoje contra aquela, o que faltava não era tema nem cor:
231
+ * tema claro e `marca.cor` já existiam e resolvem o mundo da marca. O que não
232
+ * existia era o PRODUTO. Um anúncio de cartão mostra o cartão, mostra o app
233
+ * funcionando dentro do celular, mostra a linha de produtos e assina no fim.
234
+ * Com o vocabulário antigo, "o cartão" era um retângulo verde com a palavra
235
+ * "cartão" escrita dentro — e é exatamente isso que se lê como genérico.
236
+ *
237
+ * ── A LISTA ABAIXO É LIDA POR UM TESTE, e por isso não tem comentário dentro.
238
+ * O `vocabulario.test.js` recorta este objeto com uma expressão regular e
239
+ * pega as palavras: qualquer texto entre as chaves vira "tipo que o servidor
240
+ * precisa aceitar", e o teste passa a cobrar `fintech`, `genérico` e `dono`.
241
+ * Comentário sobre os tipos mora AQUI FORA. */
217
242
  var TIPOS_CONHECIDOS = { texto: 1, lista: 1, cartoes: 1, numeros: 1, tabela: 1, codigo: 1,
218
243
  citacao: 1, forma: 1, imagem: 1, icone: 1, grafico: 1, no: 1, conector: 1,
219
244
  objeto3d: 1, clipe: 1,
220
245
  selo: 1, progresso: 1, avatar: 1, dispositivo: 1, conversa: 1, passos: 1,
221
- opcoes: 1 };
246
+ opcoes: 1,
247
+ cartao: 1, app: 1, vitrine: 1, assinatura: 1 };
222
248
  /* Os sete últimos são o vocabulário do vídeo de referência: pílula de estado,
223
249
  trilha de progresso, foto de pessoa, celular/notebook com conteúdo dentro,
224
250
  balões de conversa, etapas com uma acesa, e pergunta com alternativas.
@@ -955,12 +981,212 @@ function renderElemento(e, ctx) {
955
981
  break;
956
982
  }
957
983
 
984
+ /* ── O CARTÃO ────────────────────────────────────────────────────────
985
+ Um produto desenhado, não um retângulo com o nome dele escrito dentro.
986
+ O que faz o olho ler "cartão" são quatro detalhes pequenos e nenhum
987
+ deles é o formato: o CHIP, o símbolo de aproximação, a numeração
988
+ mascarada e o pé com nome e validade. Sem eles o desenho é uma caixa;
989
+ com eles, qualquer pessoa reconhece em um quadro de vídeo.
990
+
991
+ A cor sai da marca (`--c-tom`) quando ninguém diz outra, para o cartão
992
+ pertencer à peça em vez de ser mais um objeto colorido nela. */
993
+ case "cartao": {
994
+ var cc = e.cor || "var(--c-tom)";
995
+ no.style.setProperty("--cc", cc);
996
+ if (e.largura) no.style.width = _px(e.largura);
997
+ if (e.movimento) no.setAttribute("data-mov", e.movimento);
998
+ if (e.face === "verso") no.setAttribute("data-face", "verso");
999
+
1000
+ var corpo = document.createElement("div");
1001
+ corpo.className = "cartao-corpo";
1002
+
1003
+ var topo = document.createElement("div");
1004
+ topo.className = "cartao-topo";
1005
+ var marca = document.createElement("span");
1006
+ marca.className = "cartao-marca";
1007
+ marca.textContent = e.nome || "";
1008
+ var vari = document.createElement("span");
1009
+ vari.className = "cartao-variante";
1010
+ vari.textContent = e.variante || "";
1011
+ topo.appendChild(marca); topo.appendChild(vari);
1012
+
1013
+ var meio = document.createElement("div");
1014
+ meio.className = "cartao-meio";
1015
+ var chip = document.createElement("i");
1016
+ chip.className = "cartao-chip";
1017
+ // O símbolo de aproximação: três arcos. Em SVG porque em texto ele
1018
+ // depende de uma fonte que a máquina pode não ter.
1019
+ var onda = document.createElement("span");
1020
+ onda.className = "cartao-onda";
1021
+ onda.innerHTML = '<svg viewBox="0 0 24 24" aria-hidden="true">'
1022
+ + '<path d="M8 5a10 10 0 0 1 0 14M13 8a5.6 5.6 0 0 1 0 8M17.6 2.4a15 15 0 0 1 0 19.2"'
1023
+ + ' fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>';
1024
+ meio.appendChild(chip); meio.appendChild(onda);
1025
+
1026
+ var num = document.createElement("div");
1027
+ num.className = "cartao-numero";
1028
+ // Os quatro últimos dígitos e mais nada: é o que aparece num cartão de
1029
+ // verdade em foto de campanha, e é o que não vira dado de ninguém.
1030
+ num.textContent = "•••• •••• •••• " + String(e.numero || "4821").slice(-4);
1031
+
1032
+ var pe = document.createElement("div");
1033
+ pe.className = "cartao-pe";
1034
+ var quem = document.createElement("span");
1035
+ quem.textContent = e.titular || "SEU NOME AQUI";
1036
+ var val = document.createElement("span");
1037
+ val.textContent = e.validade || "12/29";
1038
+ pe.appendChild(quem); pe.appendChild(val);
1039
+
1040
+ var brilho = document.createElement("i");
1041
+ brilho.className = "cartao-brilho";
1042
+
1043
+ corpo.appendChild(brilho);
1044
+ corpo.appendChild(topo);
1045
+ corpo.appendChild(meio);
1046
+ corpo.appendChild(num);
1047
+ corpo.appendChild(pe);
1048
+ no.appendChild(corpo);
1049
+ break;
1050
+ }
1051
+
1052
+ /* ── A TELA DO APP ───────────────────────────────────────────────────
1053
+ O `dispositivo` já desenhava o aparelho; o que ele mostrava DENTRO era
1054
+ texto centralizado no meio do vidro, que não se parece com aplicativo
1055
+ nenhum. O que faz uma tela parecer um app é a ARRUMAÇÃO: cabeçalho em
1056
+ cima, um número grande, linhas com o nome à esquerda e o valor à
1057
+ direita separadas por fio, e um botão cheio embaixo.
1058
+
1059
+ Vale sozinho na cena e vale dentro do `dispositivo` — é o mesmo
1060
+ elemento, e por isso a tela do celular e a do print da campanha não
1061
+ divergem. */
1062
+ case "app": {
1063
+ if (e.escuro) no.setAttribute("data-escuro", "1");
1064
+ if (e.topo) {
1065
+ var cab = document.createElement("div");
1066
+ cab.className = "app-topo";
1067
+ cab.innerHTML = "<span>" + _fugir(e.topo) + "</span><i></i>";
1068
+ no.appendChild(cab);
1069
+ }
1070
+ if (e.valor) {
1071
+ var caixaV = document.createElement("div");
1072
+ caixaV.className = "app-valor";
1073
+ if (e.rotulo) {
1074
+ var rot = document.createElement("small");
1075
+ rot.textContent = e.rotulo;
1076
+ caixaV.appendChild(rot);
1077
+ }
1078
+ var vv = document.createElement("strong");
1079
+ vv.textContent = e.valor;
1080
+ caixaV.appendChild(vv);
1081
+ no.appendChild(caixaV);
1082
+ }
1083
+ var linhas = e.linhas || e.itens || [];
1084
+ if (linhas.length) {
1085
+ var lista = document.createElement("div");
1086
+ lista.className = "app-linhas";
1087
+ linhas.slice(0, 8).forEach(function (li) {
1088
+ /* Aceita ["nome", "valor"] e "nome|valor" — o segundo é a forma que
1089
+ todo o resto da spec usa (lista, cartoes, passos), e quem escreve
1090
+ o documento vai chutar a que já conhece. */
1091
+ var nome = li, valor = "";
1092
+ if (Array.isArray(li)) { nome = li[0]; valor = li[1] || ""; }
1093
+ else if (typeof li === "string" && li.indexOf("|") > -1) {
1094
+ var par = li.split("|"); nome = par[0]; valor = par.slice(1).join("|");
1095
+ }
1096
+ var linha = document.createElement("div");
1097
+ linha.className = "app-linha";
1098
+ linha.innerHTML = "<span>" + _fugir(String(nome).trim()) + "</span>"
1099
+ + "<b>" + _fugir(String(valor).trim()) + "</b>";
1100
+ lista.appendChild(linha);
1101
+ });
1102
+ no.appendChild(lista);
1103
+ }
1104
+ if (e.botao) {
1105
+ var bt = document.createElement("div");
1106
+ bt.className = "app-botao";
1107
+ bt.textContent = e.botao;
1108
+ no.appendChild(bt);
1109
+ }
1110
+ break;
1111
+ }
1112
+
1113
+ /* ── A VITRINE ───────────────────────────────────────────────────────
1114
+ A linha de produtos, com um escolhido. O `cartoes` genérico resolvia
1115
+ isso com três caixinhas de vidro do tamanho de um selo; aqui os itens
1116
+ são CARTÕES, o atual vem à frente e os outros recuam — que é o desenho
1117
+ de toda tela de "escolha o seu". */
1118
+ case "vitrine": {
1119
+ var itens = (e.itens || []).slice(0, 5);
1120
+ var atual = e.atual != null ? Number(e.atual) : 0;
1121
+ if (e.titulo) {
1122
+ var tt = document.createElement("div");
1123
+ tt.className = "vitrine-titulo";
1124
+ tt.textContent = e.titulo;
1125
+ no.appendChild(tt);
1126
+ }
1127
+ var fila = document.createElement("div");
1128
+ fila.className = "vitrine-fila";
1129
+ itens.forEach(function (it, i) {
1130
+ var dado = typeof it === "string"
1131
+ ? { nome: it.split("|")[0], detalhe: it.split("|").slice(1).join("|") }
1132
+ : (it || {});
1133
+ var mini = renderElemento({
1134
+ tipo: "cartao", nome: e.nome || dado.marca || "",
1135
+ variante: dado.nome || dado.variante || "",
1136
+ cor: dado.cor || e.cor, numero: dado.numero,
1137
+ titular: " ", validade: " ",
1138
+ }, ctx);
1139
+ var col = document.createElement("div");
1140
+ col.className = "vitrine-item" + (i === atual ? " atual" : "");
1141
+ col.appendChild(mini);
1142
+ if (dado.detalhe) {
1143
+ var d = document.createElement("small");
1144
+ d.textContent = dado.detalhe;
1145
+ col.appendChild(d);
1146
+ }
1147
+ fila.appendChild(col);
1148
+ });
1149
+ no.appendChild(fila);
1150
+ break;
1151
+ }
1152
+
1153
+ /* ── A ASSINATURA ────────────────────────────────────────────────────
1154
+ O fim do anúncio. Toda peça de marca fecha assim — o logo sozinho,
1155
+ grande, sobre a cor da casa, e nada mais na tela. Sem este elemento a
1156
+ última cena do vídeo era mais uma frase, e a peça acabava sem assinar
1157
+ quem falou. */
1158
+ case "assinatura": {
1159
+ no.setAttribute("data-sobre", /^(marca|claro|escuro)$/.test(e.sobre || "") ? e.sobre : "marca");
1160
+ if (e.logo) {
1161
+ var img2 = document.createElement("img");
1162
+ img2.className = "assinatura-logo";
1163
+ img2.src = e.logo; img2.alt = e.nome || "";
1164
+ no.appendChild(img2);
1165
+ } else if (e.nome) {
1166
+ var nm = document.createElement("div");
1167
+ nm.className = "assinatura-nome";
1168
+ nm.textContent = e.nome;
1169
+ no.appendChild(nm);
1170
+ }
1171
+ if (e.frase) {
1172
+ var fr = document.createElement("div");
1173
+ fr.className = "assinatura-frase";
1174
+ fr.textContent = e.frase;
1175
+ no.appendChild(fr);
1176
+ }
1177
+ break;
1178
+ }
1179
+
958
1180
  case "icone": {
959
1181
  var t = e.tamanho || 90;
960
1182
  no.style.width = _px(e.l || t);
961
1183
  no.style.height = _px(e.a || t);
962
1184
  no.style.color = e.cor || "var(--c-tom)";
963
- no.innerHTML = svgIcone(e.nome || "estrela");
1185
+ // `caminho`: o ícone desenhado pelo modelo (path SVG 24×24) — ver
1186
+ // icones.js. Sem ele, um da lista pelo nome.
1187
+ no.innerHTML = e.caminho
1188
+ ? svgCaminho(e.caminho, { preenchido: !!e.preenchido, traco: e.traco })
1189
+ : svgIcone(e.nome || "estrela");
964
1190
  var s = no.querySelector("svg");
965
1191
  if (s) s.setAttribute("stroke-width", e.traco || 1.5);
966
1192
  break;
@@ -1188,6 +1414,11 @@ function renderCena(cena, doc, op) {
1188
1414
  elementos = elementos.filter(function (e) {
1189
1415
  if (!e || e.x != null || e.y != null) return true;
1190
1416
  if (e.tipo === "clipe") { deChao.push(e); return false; }
1417
+ /* A ASSINATURA É A CENA INTEIRA, e por isso entra pelo chão e não pelo
1418
+ fluxo. Pelo fluxo ela vira uma caixinha colorida no meio da tela, com a
1419
+ margem do layout em volta — que é o oposto de assinar: o logo tem de
1420
+ encostar nas bordas do quadro, como em qualquer fim de comercial. */
1421
+ if (e.tipo === "assinatura") { deChao.push(e); return false; }
1191
1422
  // Legenda de fala mora no terço de baixo, como toda legenda de vídeo.
1192
1423
  // Pelo fluxo ela iria para o meio da tela, em cima do rosto de quem fala.
1193
1424
  if (e.tipo === "texto" && e.papel === "fala") { falas.push(e); return false; }
@@ -1203,6 +1434,21 @@ function renderCena(cena, doc, op) {
1203
1434
  // tela vertical pede tipografia maior: os padrões são calibrados em 1920x1080
1204
1435
  var escalaTipo = H > W ? 1.35 : 1;
1205
1436
  no.style.setProperty("--esc", escalaTipo);
1437
+ // A altura do quadro, para o CSS dimensionar o que precisa ocupar a cena
1438
+ // (o aparelho, hoje) em proporção e não em pixels calibrados num formato só.
1439
+ no.style.setProperty("--alto", H + "px");
1440
+ /* A UNIDADE DA CENA: 1% da largura do quadro.
1441
+
1442
+ Sem ela, tudo o que o CSS escreve em `em` cai na fonte herdada — que numa
1443
+ cena é a do navegador, ~14px — e o desenho colapsa sem erro nenhum. Foi
1444
+ medido num quadro de 1080x1920: a linha do extrato saiu com 7,84px e o
1445
+ nome da assinatura com 30,8px, num quadro de 1920 de altura. Na tela
1446
+ aquilo não é "pequeno": é invisível.
1447
+
1448
+ O resto do sistema resolve isso mandando PIXEL do documento (`tamanho`).
1449
+ Peça que não tem `tamanho` — a tela do app, a vitrine, a assinatura — se
1450
+ mede por aqui, e assim acompanha o formato em vez de ser calibrada num só. */
1451
+ no.style.setProperty("--u", (W / 100) + "px");
1206
1452
  var ctx = {
1207
1453
  i: 0, escalaTipo: escalaTipo,
1208
1454
  animPadrao: op.animar ? (cena.anim || "subir") : null,
@@ -1328,10 +1574,12 @@ function renderCena(cena, doc, op) {
1328
1574
  cols.style.gridTemplateColumns = "repeat(" + (layout === "duas-colunas" ? 2 : 3) + ", 1fr)";
1329
1575
  solta(corpo, cols);
1330
1576
  fluxo.appendChild(cols);
1577
+ comporCabecaCorpo(fluxo, layout, cabecalho.length, 1);
1331
1578
  } else {
1332
1579
  // um bloco só (cartoes, numeros…) já se distribui sozinho: não engaiolar
1333
1580
  solta(cabecalho, fluxo);
1334
1581
  solta(corpo, fluxo);
1582
+ comporCabecaCorpo(fluxo, layout, cabecalho.length, corpo.length);
1335
1583
  }
1336
1584
 
1337
1585
  flutuantes.forEach(function (e) {
@@ -1341,8 +1589,54 @@ function renderCena(cena, doc, op) {
1341
1589
  no.appendChild(el);
1342
1590
  });
1343
1591
 
1592
+ /* O RODAPÉ DA APRESENTAÇÃO: nome do deck à esquerda, "03 / 08" à direita.
1593
+ É o que ancora a composição no quadro — sem ele, uma cena de título e
1594
+ lista é um bloco flutuando no meio do preto, e todo deck parece o mesmo
1595
+ deck. Quem manda é quem chama (o player, quando a peça é prisma): o
1596
+ vídeo não tem rodapé, e a capa e as seções também não — são as cenas
1597
+ que se sustentam sozinhas. */
1598
+ if (op.rodape && layout !== "capa" && layout !== "secao") {
1599
+ var rodape = document.createElement("div");
1600
+ rodape.className = "rodape";
1601
+ rodape.style.left = Math.round(W * 0.075) + "px";
1602
+ rodape.style.right = Math.round(W * 0.075) + "px";
1603
+ rodape.style.bottom = Math.round(H * 0.048) + "px";
1604
+ var esq = document.createElement("span");
1605
+ esq.textContent = op.rodape.titulo || "";
1606
+ var dir = document.createElement("span");
1607
+ if (op.rodape.indice && op.rodape.total) {
1608
+ dir.textContent = String(op.rodape.indice).padStart(2, "0") + " / " + String(op.rodape.total).padStart(2, "0");
1609
+ }
1610
+ rodape.appendChild(esq);
1611
+ rodape.appendChild(dir);
1612
+ no.appendChild(rodape);
1613
+ }
1614
+
1344
1615
  desenharConectores(no, elementos, W, H);
1345
1616
  return no;
1346
1617
  }
1347
1618
 
1619
+ /* A COMPOSIÇÃO DE SLIDE: cabeça em cima, corpo no meio do que sobra.
1620
+
1621
+ Tudo centrado verticalmente — que era o padrão para todo layout — deixa um
1622
+ título com três linhas de lista ocupando 30% do quadro, com 350px de nada
1623
+ em cima e 350px embaixo: a cara de "slide genérico" que o dono apontou. A
1624
+ composição de quem faz deck de verdade ancora o título no alto e dá o resto
1625
+ do quadro ao conteúdo, que se centra ali.
1626
+
1627
+ Só quando há as duas metades (cabeça E corpo) e o layout não é o dos que se
1628
+ sustentam centrados (capa, centro, citação, seção): uma cena de vídeo com um
1629
+ título sozinho continua no meio, onde um título sozinho fica bem. O corpo é
1630
+ marcado no primeiro e no último filho (`margin: auto` no CSS), e o DOM
1631
+ continua chapado — nada de embrulho novo em volta dos elementos. */
1632
+ function comporCabecaCorpo(fluxo, layout, nCabeca, nCorpo) {
1633
+ if (!nCabeca || !nCorpo) return;
1634
+ if (layout === "capa" || layout === "centro" || layout === "citacao" || layout === "secao") return;
1635
+ var filhos = fluxo.children;
1636
+ if (filhos.length <= nCabeca) return;
1637
+ fluxo.setAttribute("data-composicao", "cabeca-corpo");
1638
+ filhos[nCabeca].classList.add("corpo-inicio");
1639
+ filhos[filhos.length - 1].classList.add("corpo-fim");
1640
+ }
1641
+
1348
1642
  if (typeof module !== "undefined" && module.exports) module.exports = { renderCena: renderCena };
@@ -347,6 +347,23 @@ var VideoExport = (function () {
347
347
  return new Promise(function (pronto) {
348
348
  var t0 = performance.now();
349
349
  var primeiro = true;
350
+ /* A camada de cima de uma cena com clipe (título, legenda, marca),
351
+ já rasterizada. Enquanto nada nela anima, só o quadro do vídeo
352
+ muda — e rasterizar o SVG inteiro trinta vezes por segundo era o
353
+ que engasgava a máquina: o decodificador do <video> ficava sem
354
+ CPU, o clipe atrasava, o player o puxava de volta e a busca não
355
+ terminava nunca (medido: seeking=true e readyState=1 do 0,7s ao
356
+ fim, arquivo com o chão da cena no lugar da gravação). Com a
357
+ camada guardada, cada quadro é dois drawImage. */
358
+ var camada = null;
359
+
360
+ function pintarComClipe(no, img) {
361
+ // chão do tema → gravação do usuário → cena sem fundo
362
+ ctx2d.fillStyle = corDeChao(no);
363
+ ctx2d.fillRect(0, 0, L, A);
364
+ desenharClipes(ctx2d, no, L, A);
365
+ ctx2d.drawImage(img, 0, 0, L, A);
366
+ }
350
367
 
351
368
  function volta() {
352
369
  if (cancelado) return pronto();
@@ -365,16 +382,20 @@ var VideoExport = (function () {
365
382
  primeiro = false;
366
383
 
367
384
  var comVideo = temClipe(no);
385
+ if (comVideo && camada && camada.no === no && !aindaAnima(no)) {
386
+ pintarComClipe(no, camada.img);
387
+ return setTimeout(volta, 1000 / FPS);
388
+ }
389
+
368
390
  Exportar.retrato(no, L, A, folhaCss, false, comVideo ? semFundo : null)
369
391
  .then(function (img) {
370
392
  if (cancelado) return pronto();
371
393
  if (comVideo) {
372
- // chão do tema → gravação do usuário → cena sem fundo
373
- ctx2d.fillStyle = corDeChao(no);
374
- ctx2d.fillRect(0, 0, L, A);
375
- desenharClipes(ctx2d, no, L, A);
394
+ camada = aindaAnima(no) ? null : { no: no, img: img };
395
+ pintarComClipe(no, img);
396
+ } else {
397
+ ctx2d.drawImage(img, 0, 0, L, A);
376
398
  }
377
- ctx2d.drawImage(img, 0, 0, L, A);
378
399
  volta();
379
400
  }).catch(function () {
380
401
  // Um quadro que não rasterizou não derruba o vídeo: segue, e o
@@ -412,25 +433,30 @@ var VideoExport = (function () {
412
433
  return promessa;
413
434
  }
414
435
 
436
+ /* Entrega ao usuário o arquivo que `gerar` montou. Separado de `baixar`
437
+ porque a exportação ESCONDIDA (studio_exportar) gera pelo mesmo caminho e
438
+ não baixa: manda o blob de volta ao estúdio. Um só `gerar` para os dois. */
439
+ function salvar(r, nome) {
440
+ var url = URL.createObjectURL(r.blob);
441
+ var a = document.createElement("a");
442
+ a.href = url;
443
+ a.download = (nome || "video")
444
+ .replace(/[^\w\-]+/g, "-").replace(/^-+|-+$/g, "").toLowerCase() + r.formato;
445
+ document.body.appendChild(a);
446
+ a.click();
447
+ a.remove();
448
+ setTimeout(function () { URL.revokeObjectURL(url); }, 8000);
449
+ return r;
450
+ }
451
+
415
452
  /* Gera e entrega o arquivo. É isto que o botão chama. */
416
453
  function baixar(arq, op) {
417
454
  op = op || {};
418
- return gerar(arq, op).then(function (r) {
419
- var url = URL.createObjectURL(r.blob);
420
- var a = document.createElement("a");
421
- a.href = url;
422
- a.download = (op.nome || arq.titulo || "video")
423
- .replace(/[^\w\-]+/g, "-").replace(/^-+|-+$/g, "").toLowerCase() + r.formato;
424
- document.body.appendChild(a);
425
- a.click();
426
- a.remove();
427
- setTimeout(function () { URL.revokeObjectURL(url); }, 8000);
428
- return r;
429
- });
455
+ return gerar(arq, op).then(function (r) { return salvar(r, op.nome || arq.titulo); });
430
456
  }
431
457
 
432
458
  return {
433
- disponivel: disponivel, formato: formato, gerar: gerar, baixar: baixar,
459
+ disponivel: disponivel, formato: formato, gerar: gerar, baixar: baixar, salvar: salvar,
434
460
  FORMATOS: FORMATOS, _aindaAnima: aindaAnima,
435
461
  };
436
462
  })();