feat: initial commit - Jhonny Editor

- Adicionado estrutura completa do projeto
- Configurado MCP server para Premiere Pro
- Adicionado documentação e skills
- Configurado Gitignore para o projeto
This commit is contained in:
João Henrique
2026-09-08 09:59:31 -04:00
commit b541f502ba
1507 changed files with 387650 additions and 0 deletions
+151
View File
@@ -0,0 +1,151 @@
#!/usr/bin/env node
/**
* Aplica um plano de cortes de silêncio (gerado pelo SilenceCutter em Python)
* numa sequência do Premiere Pro, reaproveitando o bridge já usado pelo
* servidor MCP (dist/tools/*.js).
*
* Fase A: divide (split) vídeo e áudio em todos os limites de corte, usando
* as coordenadas originais do plano (splits não deslocam nada).
* Fase B: remove os cortes em ordem DECRESCENTE de início (ripple=true por
* track), para que cada remoção só desloque conteúdo já processado.
*
* Uso:
* node scripts/apply-silence-cuts.mjs --plan <plano.json> --sequence "<nome>"
* [--video-track 0] [--audio-track 0] [--dry-run]
*/
import { readFileSync } from "node:fs";
import { getTimelineTools } from "../dist/tools/timeline.js";
import { getDiscoveryTools } from "../dist/tools/discovery.js";
import { getProjectTools } from "../dist/tools/project.js";
// Progresso para a barra do painel CEP: linhas @@PROGRESS em stderr, que o painel
// intercepta em vez de despejar no log. Fases A/B/C viram uma barra única.
function reportProgress(stage, pct, detail) {
const payload = { stage };
if (typeof pct === "number" && Number.isFinite(pct)) payload.pct = Math.max(0, Math.min(100, Math.round(pct * 10) / 10));
if (detail) payload.detail = detail;
console.error("@@PROGRESS " + JSON.stringify(payload));
}
function parseArgs(argv) {
const args = { videoTrack: 0, audioTrack: 0, dryRun: false };
for (let i = 0; i < argv.length; i++) {
const a = argv[i];
if (a === "--plan") args.plan = argv[++i];
else if (a === "--sequence") args.sequence = argv[++i];
else if (a === "--video-track") args.videoTrack = Number(argv[++i]);
else if (a === "--audio-track") args.audioTrack = Number(argv[++i]);
else if (a === "--dry-run") args.dryRun = true;
}
if (!args.plan || !args.sequence) {
console.error("Uso: --plan <plano.json> --sequence <nome> [--video-track N] [--audio-track N] [--dry-run]");
process.exit(1);
}
return args;
}
function unwrap(result, label) {
if (!result || result.success === false) {
throw new Error(`${label} falhou: ${result?.error ?? JSON.stringify(result)}`);
}
return result.data ?? result;
}
async function main() {
const args = parseArgs(process.argv.slice(2));
const plan = JSON.parse(readFileSync(args.plan, "utf-8"));
const cuts = plan.cuts;
if (!Array.isArray(cuts) || cuts.length === 0) {
console.log(JSON.stringify({ ok: true, message: "Nenhum corte no plano." }));
return;
}
const bridgeOptions = {};
reportProgress("Conectando ao Premiere", 0);
const timeline = getTimelineTools(bridgeOptions);
const discovery = getDiscoveryTools(bridgeOptions);
const project = getProjectTools(bridgeOptions);
console.error(`[apply-silence-cuts] sequência="${args.sequence}" cortes=${cuts.length} dryRun=${args.dryRun}`);
if (args.dryRun) {
console.log(JSON.stringify({ ok: true, dryRun: true, numCuts: cuts.length, plan: plan.summary }, null, 2));
return;
}
unwrap(await project.set_active_sequence.handler({ sequence_id: args.sequence }), "set_active_sequence");
// Fase A: splits em todos os limites, coordenadas originais (ordem não importa)
console.error(`[apply-silence-cuts] Fase A: ${cuts.length * 4} splits...`);
reportProgress("Dividindo a timeline", 0, `0 de ${cuts.length} cortes`);
for (let i = 0; i < cuts.length; i++) {
const { start, end } = cuts[i];
for (const time_seconds of [end, start]) {
unwrap(
await timeline.split_clip.handler({ time_seconds, track_index: args.videoTrack, track_type: "video" }),
`split_clip video @${time_seconds}`
);
unwrap(
await timeline.split_clip.handler({ time_seconds, track_index: args.audioTrack, track_type: "audio" }),
`split_clip audio @${time_seconds}`
);
}
reportProgress("Dividindo a timeline", ((i + 1) / cuts.length) * 50, `${i + 1} de ${cuts.length} cortes`);
if ((i + 1) % 10 === 0 || i === cuts.length - 1) {
console.error(`[apply-silence-cuts] Fase A: ${i + 1}/${cuts.length} cortes divididos`);
}
}
// Fase B: remoções em ordem decrescente de start
const ordered = [...cuts].sort((a, b) => b.start - a.start);
console.error(`[apply-silence-cuts] Fase B: removendo ${ordered.length} cortes (ordem decrescente)...`);
let removed = 0;
for (const cut of ordered) {
const mid = (cut.start + cut.end) / 2;
const videoClip = unwrap(
await discovery.get_clip_at_position.handler({ time_seconds: mid, track_index: args.videoTrack, track_type: "video" }),
`get_clip_at_position video @${mid}`
);
unwrap(
await timeline.remove_from_timeline.handler({ node_id: videoClip.nodeId, ripple: true }),
`remove_from_timeline video ${videoClip.nodeId}`
);
const audioClip = unwrap(
await discovery.get_clip_at_position.handler({ time_seconds: mid, track_index: args.audioTrack, track_type: "audio" }),
`get_clip_at_position audio @${mid}`
);
unwrap(
await timeline.remove_from_timeline.handler({ node_id: audioClip.nodeId, ripple: true }),
`remove_from_timeline audio ${audioClip.nodeId}`
);
removed++;
reportProgress("Removendo os silêncios", 50 + (removed / ordered.length) * 50, `${removed} de ${ordered.length} cortes`);
if (removed % 10 === 0 || removed === ordered.length) {
console.error(`[apply-silence-cuts] Fase B: ${removed}/${ordered.length} cortes removidos`);
}
}
reportProgress("Conferindo o resultado", 100);
const finalSeq = unwrap(await discovery.get_active_sequence.handler({}), "get_active_sequence");
console.log(
JSON.stringify(
{
ok: true,
cutsApplied: cuts.length,
expectedDurationSeconds: plan.summary?.resulting_duration_seconds,
finalSequenceEndSeconds: finalSeq.end,
},
null,
2
)
);
}
main().catch((err) => {
console.error("[apply-silence-cuts] ERRO:", err.message);
process.exit(1);
});