Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,13 +148,13 @@ Concurrency defaults differ by media kind, because the two libraries behave diff

**Video options**

| Option | Default | Description |
| ---------------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `--codec <name>` | per container | `libx264`, `libx265`, `libvpx-vp9`, `libvpx`, `libsvtav1`, `libaom-av1`, `mpeg4`, `libtheora`. Must be legal for the container. |
| `--audio-codec <name>` | per container | `aac`, `libopus`, or `copy` to pass the original track through. |
| `--fps <n>` | source rate | Cap the frame rate. Left alone by default. |
| `--preset <name>` | per codec | Encoder speed/efficiency tradeoff. Codec-specific. |
| `--ffmpeg-path <path>` | `$FFMPEG_PATH`, then `PATH` | Path to the ffmpeg binary. |
| Option | Default | Description |
| ---------------------- | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--codec <name>` | per container | Any video encoder this ffmpeg reports for open-tier containers. Curated suggestions: `libx264`, `libx265`, `libvpx-vp9`, `libvpx`, `libsvtav1`, `libaom-av1`, `mpeg4`, `libtheora`; curated containers enforce the matrix below. |
| `--audio-codec <name>` | per container | `aac`, `libopus`, `libmp3lame`, `libvorbis`, `flac`, or `copy` to pass compatible source tracks through. The target container and local ffmpeg build are both checked. |
| `--fps <n>` | source rate | Cap the frame rate. Left alone by default. |
| `--preset <name>` | per codec | Encoder speed/efficiency tradeoff. Codec-specific. |
| `--ffmpeg-path <path>` | `$FFMPEG_PATH`, then `PATH` | Path to the ffmpeg binary. |

### Examples

Expand Down Expand Up @@ -203,7 +203,9 @@ imgvidcompress formats --json # machine-readable
| `.avi` | H.264, MPEG-4 | MP3 |
| `.ogv` | Theora | Vorbis, Opus |

Beyond those, **any muxer your ffmpeg reports is a valid `--to`**. A stock Homebrew build carries 184 of them, with around 100 video encoders available to `--codec`. For an uncurated container the muxer's own defaults apply, which are muxable by construction.
Beyond those, **any muxer your ffmpeg reports is a valid `--to`**, and `--codec` accepts any video encoder that build reports instead of imposing a fixed CLI list. A stock Homebrew build carries 184 muxers and around 100 video encoders. For an uncurated container the muxer's own defaults apply when no codec is requested, which are muxable by construction.

Before the worker pool starts, the run checks every planned codec against that ffmpeg build. A missing explicitly requested encoder fails once with the available alternatives; if a container's default encoder is missing, an available legal codec is substituted and reported as a warning.

WebM genuinely cannot carry H.264. For curated containers that restriction is enforced by the compiler rather than by a runtime check: an impossible pairing is a build error in library code and a clear message on the CLI.

Expand Down
2 changes: 1 addition & 1 deletion mcp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ Parameters: `path` (required), `to` (required, a curated container), `quality`,

What this machine can genuinely encode and decode.

Worth calling before choosing an output format. Support is build-dependent rather than fixed by the package: AVIF, JPEG XL and HEIC are frequently missing from sharp, and a minimal ffmpeg carries a fraction of the containers and codecs a full one does. The image list is produced by actually encoding a pixel with each candidate, so it reflects the binary you have rather than what the package hopes is there.
Worth calling before choosing an output format. Support is build-dependent rather than fixed by the package: AVIF, JPEG XL and HEIC are frequently missing from sharp, and a minimal ffmpeg carries a fraction of the containers and codecs a full one does. The response includes the actual muxer, video encoder and audio encoder names as well as their counts. The image list is produced by actually encoding a pixel with each candidate, so it reflects the binary you have rather than what the package hopes is there.

Parameters: `ffmpegPath` (optional).

Expand Down
13 changes: 11 additions & 2 deletions mcp/src/server.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { stat } from "node:fs/promises";
import { createRequire } from "node:module";
import { z } from "zod";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import {
Expand Down Expand Up @@ -27,6 +28,9 @@ import {
type CompressionReport,
} from "image-and-video-compressor";

const require = createRequire(import.meta.url);
const MCP_VERSION = (require("../package.json") as { version: string }).version;

/** Library errors carry a stable `code`; anything else is unknown. */
function errorCode(err: unknown): string {
return typeof err === "object" && err !== null && "code" in err
Expand Down Expand Up @@ -148,7 +152,7 @@ function summarise(report: CompressionReport) {

export function createServer(): McpServer {
const server = new McpServer(
{ name: "image-and-video-compressor", version: "0.1.0" },
{ name: "image-and-video-compressor", version: MCP_VERSION },
{
instructions:
"Compress images and videos on this machine. Compression never modifies the " +
Expand Down Expand Up @@ -254,7 +258,9 @@ export function createServer(): McpServer {
audioCodec: z
.string()
.optional()
.describe("Audio encoder: 'aac', 'libopus', or 'copy'."),
.describe(
"Audio encoder, e.g. 'aac', 'libopus', 'libmp3lame', 'libvorbis', 'flac', or 'copy'.",
),
fps: z
.number()
.int()
Expand Down Expand Up @@ -681,6 +687,9 @@ export function createServer(): McpServer {
muxerCount: caps.muxers.size,
videoEncoderCount: caps.videoEncoders.size,
audioEncoderCount: caps.audioEncoders.size,
muxers: [...caps.muxers.keys()].sort(),
videoEncoders: [...caps.videoEncoders].sort(),
audioEncoders: [...caps.audioEncoders].sort(),
},
});
} catch {
Expand Down
27 changes: 18 additions & 9 deletions src/cli/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ import { compress, compressImages, compressVideos } from "../core/compress.js";
import { CompressorError } from "../core/errors.js";
import { toQuality, toPixels, RangeValidationError } from "../types/brand.js";
import { CURATED_IMAGE_FORMATS } from "../types/image-formats.js";
import { VIDEO_CODECS, VIDEO_OUTPUT_FORMATS } from "../types/video-formats.js";
import {
AUDIO_CODECS,
VIDEO_CODECS,
VIDEO_OUTPUT_FORMATS,
} from "../types/video-formats.js";
import { Renderer } from "./render.js";
import { emitReport, emitError, emitFormats, collectCapabilities } from "./json.js";
import type {
Expand Down Expand Up @@ -139,19 +143,24 @@ function addImageOptions(cmd: Command): Command {
}

function addVideoOptions(cmd: Command): Command {
// Encoder availability is a property of the local ffmpeg build. These names
// make useful starting points in help, but a Commander choice list would
// reject valid runtime encoders and promise unavailable ones.
const videoSuggestions = Object.keys(VIDEO_CODECS).join(", ");
const audioSuggestions = Object.keys(AUDIO_CODECS).join(", ");

return cmd
.addOption(
new Option(
"--codec <name>",
"video codec (must be legal for the container)",
).choices(Object.keys(VIDEO_CODECS)),
`video codec (e.g. ${videoSuggestions}; checked against ffmpeg)`,
),
)
.addOption(
new Option("--audio-codec <name>", "audio codec").choices([
"aac",
"libopus",
"copy",
]),
new Option(
"--audio-codec <name>",
`audio codec (e.g. ${audioSuggestions}; checked against ffmpeg)`,
),
)
.option(
"--fps <n>",
Expand Down Expand Up @@ -385,7 +394,7 @@ async function printFormats(ffmpegPath?: string): Promise<void> {
` ${pc.dim("any of them can be used as --to; ffmpeg's own defaults apply.")}\n`,
);
out.write(
` ${pc.dim(`${report.video.videoEncoders.length} video and ${report.video.audioEncoders.length} audio encoders available for --codec.`)}\n`,
` ${pc.dim(`${report.video.videoEncoders.length} video encoders available for --codec and ${report.video.audioEncoders.length} audio encoders for --audio-codec.`)}\n`,
);

out.write(
Expand Down
1 change: 1 addition & 0 deletions src/cli/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export class Renderer {

switch (event.type) {
case "run-start":
for (const warning of event.warnings ?? []) this.warn(warning);
this.start(event.total);
break;
case "job-start":
Expand Down
Loading
Loading