Created
January 25, 2025 18:23
-
-
Save Strajk/dda5f9474c125f3c8ced0caecf915684 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Name: Speed Up Video | |
// Description: Speed up a video, optionally keeping the first and last X seconds at original speed | |
// Author: Strajk | |
import '@johnlindquist/kit'; | |
// Get the video file path, either from selection or prompt | |
import { Choice } from '@johnlindquist/kit'; | |
const videoPath = await getSelectedFile() || await path({ placeholder: 'Select a video file' }); | |
const { stdout: videoDurationRaw } = await $`ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 ${videoPath}` | |
const videoDuration = parseFloat(videoDurationRaw) | |
// Not sure if best way of handling error states | |
if (!videoDuration || videoDuration < 1) { | |
await div(`${videoPath} does not look like a video file, exiting`) | |
exit() | |
} | |
const { dir, name, ext } = path.parse(videoPath); | |
const outputPath = path.join(dir, `${name}-adjusted${ext}`); | |
const speedOptions: Choice[] = [ | |
{ name: `2x (${Math.round(videoDuration / 2)}s)`, value: 2 }, | |
{ name: `3x (${Math.round(videoDuration / 3)}s)`, value: 3 }, | |
{ name: `4x (${Math.round(videoDuration / 4)}s)`, value: 4 }, | |
{ name: `5x (${Math.round(videoDuration / 5)}s)`, value: 5 }, | |
]; | |
// Prompt user to select a speed | |
const speedRaw = await arg({ | |
placeholder: `Select new playback speed, or enter a custom value`, | |
hint: `Any number or ";5;3;6" for 5x speed-up, but first 3 and last 6s are kept at original speed`, | |
choices: speedOptions, | |
strict: false, // allow arbitrary input | |
}); | |
let mode: 'simple' | 'complex' = speedRaw.includes?.(';') ? 'complex' : 'simple' | |
if (mode === 'simple') { | |
let speed: number = parseFloat(speedRaw) | |
// -filter:v means video filter | |
// -filter:a means audio filter | |
// for uploading to twitter, i'm setting fps to 30 | |
await term(`ffmpeg \ | |
-i "${videoPath}" \ | |
-filter:v "setpts=${1 / speed}*PTS,fps=30" \ | |
-filter:a "atempo=${speed}" \ | |
-y "${outputPath}" | |
`) | |
} else { | |
let [speedStr, introStr, outroStr] = speedRaw.split(';') | |
let speed = parseFloat(speedStr) | |
let intro = parseInt(introStr) | |
let outro = parseInt(outroStr) | |
// Note: ffmpeg is savage | |
await term(`ffmpeg -i "${videoPath}" -filter_complex " | |
[0:v]split=3[v1][v2][v3]; | |
[v1]trim=0:${intro},setpts=PTS-STARTPTS,fps=30[first]; | |
[v2]trim=${intro}:${videoDuration - outro},setpts=PTS-STARTPTS,setpts=${1 / speed}*PTS,fps=30[middle]; | |
[v3]trim=${videoDuration - outro},setpts=PTS-STARTPTS,fps=30[last]; | |
[first][middle][last]concat=n=3:v=1:a=0[outv] | |
" -y -map "[outv]" "${outputPath}" | |
`) | |
} | |
// Reveal output file in the system's file explorer | |
await revealFile(outputPath); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment