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
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,17 @@ project adheres to [Semantic Versioning](https://semver.org/).
- ESLint (flat config, typescript-eslint recommended) with a `lint` script, run
in CI (#23). Tooling only; not part of the published package.

## [0.8.11]

### Fixed

- **`sample_frames` (timestamps) no longer returns paths for frames past the end
of the video** (#35). A timestamp beyond the video length makes ffmpeg exit 0
without writing a file, but the tool still reported that (nonexistent) path.
It now returns only frames that actually exist on disk and lists any
out-of-range timestamps under `skippedTimestamps` with a note. Mirrors the
fps-mode fix in #20.

## [0.8.10]

### Fixed
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@tmhs/screencast-mcp",
"version": "0.8.10",
"version": "0.8.11",
"description": "MCP server for Windows screen recording, frame sampling, and minimal ffmpeg edits",
"type": "module",
"main": "dist/index.js",
Expand Down
19 changes: 17 additions & 2 deletions src/tools/sampleFrames.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { z } from "zod";
import { join } from "node:path";
import { mkdirSync, readdirSync, existsSync } from "node:fs";
import { mkdirSync, readdirSync, existsSync, rmSync } from "node:fs";
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { errorResponse, okResponse, ScreencastError } from "../utils/errors.js";
import { requireFfmpeg, runFfmpeg } from "../utils/ffmpeg.js";
Expand Down Expand Up @@ -52,6 +52,7 @@ export function register(server: McpServer): void {
mkdirSync(dir, { recursive: true });

let frames: string[] = [];
const skipped: number[] = [];
if (hasFps) {
const isPng = (f: string) => f.endsWith(".png");
// Snapshot any pre-existing PNGs so a reused outputDir does not
Expand All @@ -68,8 +69,14 @@ export function register(server: McpServer): void {
const ts = args.timestamps!;
for (let i = 0; i < ts.length; i++) {
const out = join(dir, `frame_${String(i).padStart(3, "0")}_${ts[i]}s.png`);
// Clear any stale file at this path (reused outputDir) so existence
// after the run reflects only what this invocation wrote.
rmSync(out, { force: true });
await runFfmpeg(buildSampleAtTimestampArgs(args.input, ts[i], out), 60_000);
frames.push(out);
// A timestamp past the end of the video makes ffmpeg exit 0 without
// writing a file. Report only frames that actually exist (#35).
if (existsSync(out)) frames.push(out);
else skipped.push(ts[i]);
}
}

Expand All @@ -78,6 +85,14 @@ export function register(server: McpServer): void {
frameCount: frames.length,
frames,
mode: hasFps ? `fps=${args.fps}` : `timestamps=[${args.timestamps!.join(", ")}]`,
...(skipped.length > 0
? {
skippedTimestamps: skipped,
note:
`No frame was written for ${skipped.length} timestamp(s) past the ` +
`end of the video: [${skipped.join(", ")}].`,
}
: {}),
});
} catch (error) {
return errorResponse(error);
Expand Down
Loading