Skip to content

Instantly share code, notes, and snippets.

@mattpocock
Created October 18, 2022 15:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mattpocock/cefe26e9a9e6b3250ccccee6e15652ce to your computer and use it in GitHub Desktop.
Save mattpocock/cefe26e9a9e6b3250ccccee6e15652ce to your computer and use it in GitHub Desktop.
// Name: Trim latest OBS video
import "@johnlindquist/kit";
import { z } from "zod";
const { stdout } = await $`ls -t ~/Movies/*.mp4 | head -n 1`;
const inputVideo = stdout.trim();
const THRESH = "-40";
const DURATION = "1";
const obsTrimOutputDb = await db("obs-trim-output", {
folder: home("Movies", "trimmed"),
});
const result: { activeTextEditorFilePath: string } = await readJson(
home(`.kit`, "db", "vscode.json"),
);
const vscodeResultSchema = z.object({
activeTextEditorFilePath: z.string(),
});
const defaultFilename = path.parse(
vscodeResultSchema.parse(result).activeTextEditorFilePath,
).name;
const outputFolder = obsTrimOutputDb.folder;
const output =
await $`ffmpeg -hide_banner -vn -i ${inputVideo} -af "silencedetect=n=${THRESH}dB:d=${DURATION}" -f null - 2>&1 | grep "silence_end" | awk '{print $5 " " $8}'`;
let silence = output.stdout
.trim()
.split("\n")
.map((line) => line.split(" "))
.map(([silenceEnd, duration]) => {
return {
silenceEnd: parseFloat(silenceEnd),
duration: parseFloat(duration),
};
});
let foundFirstPeriodOfTalking = false;
while (!foundFirstPeriodOfTalking) {
// Unshift the first silence if the noise afterwards
// is less than 1 second long
const silenceElem = silence[0];
const nextSilenceElem = silence[1];
const nextSilenceStartTime =
nextSilenceElem.silenceEnd - nextSilenceElem.duration;
const lengthOfNoise = nextSilenceStartTime - silenceElem.silenceEnd;
if (lengthOfNoise < 2) {
silence.shift();
} else {
foundFirstPeriodOfTalking = true;
}
}
const PADDING = 0.3;
const startTime = silence[0].silenceEnd - PADDING;
const endTime =
silence[silence.length - 1].silenceEnd -
silence[silence.length - 1].duration +
PADDING;
const totalDuration = endTime - startTime;
const formatFloatForFFmpeg = (num: number) => {
return num.toFixed(3);
};
const filenameWithoutExtension = await arg({
placeholder: defaultFilename,
name: "Filename without extension",
});
const filename = `${filenameWithoutExtension || defaultFilename}.mp4`;
await ensureDir(outputFolder);
const outputVideo = path.resolve(outputFolder, filename);
await ensureDir(path.resolve(outputFolder, "tests"));
const testOutputVideo = path.resolve(outputFolder, "tests", filename);
await $`ffmpeg -y -hide_banner -ss ${formatFloatForFFmpeg(
startTime,
)} -to ${formatFloatForFFmpeg(
endTime,
)} -i ${inputVideo} -c copy ${outputVideo}`;
await revealInFinder(outputVideo);
await $`ffmpeg -y -i ${outputVideo} \
-vf "select='between(t,0,2)+between(t,${(totalDuration - 2).toFixed(
1,
)},${totalDuration.toFixed(1)})',
setpts=N/FRAME_RATE/TB" \
-af "aselect='between(t,0,2)+between(t,${(totalDuration - 2).toFixed(
1,
)},${totalDuration.toFixed(1)})',
asetpts=N/SR/TB" ${testOutputVideo}`;
await $`open ${testOutputVideo}`;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment