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:
@@ -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);
|
||||
});
|
||||
Reference in New Issue
Block a user