Skip to content

Commit 7b42fc6

Browse files
omitsu-devclaude
authored andcommitted
feat: add 6 new commands — motion-blur, border, multi-audio, duration, sample-rate, bit-depth (v2.2.0)
97 total commands! New commands: - motion-blur: Temporal motion blur effect - border: Decorative border/frame around video - multi-audio: Add multiple audio tracks (multilingual) - duration: Show media file duration - sample-rate: Change audio sample rate - bit-depth: Change audio bit depth (16/24/32) https://claude.ai/code/session_01BPZRs1uut8bAVcEQsjWjsw
1 parent 0f2c19e commit 7b42fc6

9 files changed

Lines changed: 175 additions & 2 deletions

File tree

.github/workflows/ci.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,12 @@ jobs:
119119
node bin/ffmpeg-quick.js progress test.mp4 10 --dry-run
120120
node bin/ffmpeg-quick.js backdrop test.mp4 --dry-run
121121
node bin/ffmpeg-quick.js count-frames test.mp4 --dry-run
122+
node bin/ffmpeg-quick.js motion-blur test.mp4 --dry-run
123+
node bin/ffmpeg-quick.js border test.mp4 --dry-run
124+
node bin/ffmpeg-quick.js multi-audio test.mp4 test.mp3 --dry-run
125+
node bin/ffmpeg-quick.js duration test.mp4 --dry-run
126+
node bin/ffmpeg-quick.js sample-rate test.mp4 44100 --dry-run
127+
node bin/ffmpeg-quick.js bit-depth test.mp4 16 --dry-run
122128
123129
- name: Test real encode (compress a generated video)
124130
run: |
@@ -176,3 +182,6 @@ jobs:
176182
node bin/ffmpeg-quick.js backdrop test-input.mp4 --size 640x480 -y
177183
node bin/ffmpeg-quick.js gif-to-video test-input-gif.gif -y
178184
node bin/ffmpeg-quick.js count-frames test-input.mp4
185+
node bin/ffmpeg-quick.js motion-blur test-input.mp4 -y
186+
node bin/ffmpeg-quick.js border test-input.mp4 -y
187+
node bin/ffmpeg-quick.js duration test-input.mp4

bin/ffmpeg-quick.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,13 +92,19 @@ import { register as gifToVideo } from "../src/commands/gif-to-video.js";
9292
import { register as progress } from "../src/commands/progress.js";
9393
import { register as backdrop } from "../src/commands/backdrop.js";
9494
import { register as countFrames } from "../src/commands/count-frames.js";
95+
import { register as motionBlur } from "../src/commands/motion-blur.js";
96+
import { register as border } from "../src/commands/border.js";
97+
import { register as multiAudio } from "../src/commands/multi-audio.js";
98+
import { register as duration } from "../src/commands/duration.js";
99+
import { register as sampleRate } from "../src/commands/sample-rate.js";
100+
import { register as bitDepth } from "../src/commands/bit-depth.js";
95101

96102
const program = new Command();
97103

98104
program
99105
.name("ffmpeg-quick")
100106
.description("Quick FFmpeg presets for common video tasks")
101-
.version("2.1.0");
107+
.version("2.2.0");
102108

103109
compress(program);
104110
gif(program);
@@ -191,5 +197,11 @@ gifToVideo(program);
191197
progress(program);
192198
backdrop(program);
193199
countFrames(program);
200+
motionBlur(program);
201+
border(program);
202+
multiAudio(program);
203+
duration(program);
204+
sampleRate(program);
205+
bitDepth(program);
194206

195207
program.parse();

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ffmpeg-quick",
3-
"version": "2.1.0",
3+
"version": "2.2.0",
44
"description": "Quick FFmpeg presets for common video tasks / FFmpegプリセットCLI",
55
"type": "module",
66
"bin": {

src/commands/bit-depth.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { run } from "../run.js";
2+
import { outputName } from "../utils.js";
3+
4+
export function register(program) {
5+
program
6+
.command("bit-depth")
7+
.description("Change audio bit depth (16, 24, 32)")
8+
.argument("<input>", "Input video/audio file")
9+
.argument("<bits>", "Target bit depth: 16, 24, or 32")
10+
.option("-o, --output <path>", "Output file path")
11+
.option("--dry-run", "Print the FFmpeg command without running it")
12+
.option("-y", "Overwrite output without asking")
13+
.action((input, bits, opts) => {
14+
const formatMap = {
15+
"16": "s16",
16+
"24": "s24",
17+
"32": "s32",
18+
};
19+
20+
const fmt = formatMap[bits];
21+
if (!fmt) {
22+
console.error("Error: bit depth must be 16, 24, or 32.");
23+
process.exit(1);
24+
}
25+
26+
const out = opts.output || outputName(input, `${bits}bit`);
27+
const args = ["-i", input, "-sample_fmt", fmt, "-c:v", "copy"];
28+
29+
if (opts.y) args.push("-y");
30+
args.push(out);
31+
run(args, { dryRun: opts.dryRun });
32+
});
33+
}

src/commands/border.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { run } from "../run.js";
2+
import { outputName } from "../utils.js";
3+
4+
export function register(program) {
5+
program
6+
.command("border")
7+
.description("Add a decorative border / frame around video")
8+
.argument("<input>", "Input video file")
9+
.option("--width <px>", "Border width in pixels", "20")
10+
.option("--color <name>", "Border color", "white")
11+
.option("-o, --output <path>", "Output file path")
12+
.option("--dry-run", "Print the FFmpeg command without running it")
13+
.option("-y", "Overwrite output without asking")
14+
.action((input, opts) => {
15+
const w = opts.width;
16+
const filter = `pad=iw+${w}*2:ih+${w}*2:${w}:${w}:color=${opts.color}`;
17+
const out = opts.output || outputName(input, "bordered");
18+
const args = ["-i", input, "-vf", filter, "-c:a", "copy"];
19+
20+
if (opts.y) args.push("-y");
21+
args.push(out);
22+
run(args, { dryRun: opts.dryRun });
23+
});
24+
}

src/commands/duration.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { run } from "../run.js";
2+
3+
export function register(program) {
4+
program
5+
.command("duration")
6+
.description("Show duration of a media file")
7+
.argument("<input>", "Input media file")
8+
.option("--dry-run", "Print the ffprobe command without running it")
9+
.action((input, opts) => {
10+
const args = [
11+
"-v", "error",
12+
"-show_entries", "format=duration",
13+
"-of", "csv=p=0",
14+
"-sexagesimal",
15+
input,
16+
];
17+
18+
run(args, { bin: "ffprobe", dryRun: opts.dryRun });
19+
});
20+
}

src/commands/motion-blur.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { run } from "../run.js";
2+
import { outputName } from "../utils.js";
3+
4+
export function register(program) {
5+
program
6+
.command("motion-blur")
7+
.description("Apply motion blur effect to video")
8+
.argument("<input>", "Input video file")
9+
.option("--frames <n>", "Number of frames to blend (higher = more blur)", "5")
10+
.option("-o, --output <path>", "Output file path")
11+
.option("--dry-run", "Print the FFmpeg command without running it")
12+
.option("-y", "Overwrite output without asking")
13+
.action((input, opts) => {
14+
const out = opts.output || outputName(input, "motion-blur");
15+
const args = ["-i", input, "-vf", `tmix=frames=${opts.frames}`, "-c:a", "copy"];
16+
17+
if (opts.y) args.push("-y");
18+
args.push(out);
19+
run(args, { dryRun: opts.dryRun });
20+
});
21+
}

src/commands/multi-audio.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { run } from "../run.js";
2+
import { outputName } from "../utils.js";
3+
4+
export function register(program) {
5+
program
6+
.command("multi-audio")
7+
.description("Add multiple audio tracks to a video (e.g. multilingual)")
8+
.argument("<video>", "Input video file")
9+
.argument("<audios...>", "Audio files to add as separate tracks")
10+
.option("-o, --output <path>", "Output file path (.mkv recommended)")
11+
.option("--dry-run", "Print the FFmpeg command without running it")
12+
.option("-y", "Overwrite output without asking")
13+
.action((video, audios, opts) => {
14+
const out = opts.output || outputName(video, "multi-audio", ".mkv");
15+
const args = ["-i", video];
16+
17+
for (const a of audios) {
18+
args.push("-i", a);
19+
}
20+
21+
args.push("-map", "0:v");
22+
args.push("-map", "0:a?");
23+
for (let i = 0; i < audios.length; i++) {
24+
args.push("-map", `${i + 1}:a`);
25+
}
26+
args.push("-c", "copy");
27+
28+
if (opts.y) args.push("-y");
29+
args.push(out);
30+
run(args, { dryRun: opts.dryRun });
31+
});
32+
}

src/commands/sample-rate.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { run } from "../run.js";
2+
import { outputName, parsePositiveNumber } from "../utils.js";
3+
4+
export function register(program) {
5+
program
6+
.command("sample-rate")
7+
.description("Change audio sample rate (e.g. 44100, 48000, 96000)")
8+
.argument("<input>", "Input video/audio file")
9+
.argument("<rate>", "Target sample rate in Hz (e.g. 44100, 48000)")
10+
.option("-o, --output <path>", "Output file path")
11+
.option("--dry-run", "Print the FFmpeg command without running it")
12+
.option("-y", "Overwrite output without asking")
13+
.action((input, rate, opts) => {
14+
const sr = parsePositiveNumber(rate, "sample rate");
15+
const out = opts.output || outputName(input, `${Math.floor(sr)}hz`);
16+
const args = ["-i", input, "-ar", String(Math.floor(sr)), "-c:v", "copy"];
17+
18+
if (opts.y) args.push("-y");
19+
args.push(out);
20+
run(args, { dryRun: opts.dryRun });
21+
});
22+
}

0 commit comments

Comments
 (0)