@pilio/gemini-watermark-remover 1.0.20 → 1.0.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,32 +1,34 @@
1
- [中文文档](README_zh.md)
2
-
3
- > 🔥 Tired of Gemini watermarks? Try the more powerful **GPT Image 2** at [pilio.ai](https://pilio.ai) — free for a limited time.
4
-
5
- # Gemini Watermark Remover — Lossless Watermark Removal Tool
6
-
7
- An open-source tool to **remove Gemini watermarks** from AI-generated images with high-fidelity, reproducible results on supported outputs. Built with pure JavaScript, the engine uses a mathematically exact **Reverse Alpha Blending** algorithm instead of unpredictable AI inpainting.
8
-
9
- > **🚀 Looking for the `Online Gemini Watermark Remover (Recommended)`? Try [geminiwatermarkremover.io](https://geminiwatermarkremover.io/)** — free, no install, works directly in your browser.
10
- >
11
- > 💡 Got watermarks that Gemini Watermark Remover can't handle? Try the general-purpose AI watermark remover: [pilio.ai/image-watermark-remover](https://pilio.ai/image-watermark-remover)
12
-
13
- <p align="center">
1
+ [中文文档](README_zh.md)
2
+
3
+ > 🔥 Tired of Gemini watermarks? Try the more powerful **GPT Image 2** at [pilio.ai](https://pilio.ai) — free for a limited time.
4
+
5
+ # Gemini Watermark Remover — Lossless Watermark Removal Tool
6
+
7
+ An open-source tool to **remove Gemini watermarks** from AI-generated images with high-fidelity, reproducible results on supported outputs. Built with pure JavaScript, the engine uses a mathematically exact **Reverse Alpha Blending** algorithm instead of unpredictable AI inpainting.
8
+
9
+ > **🚀 Looking for the `Online Gemini Watermark Remover (Recommended)`? Try [geminiwatermarkremover.io](https://geminiwatermarkremover.io/)** — free, no install, works directly in your browser.
10
+ >
11
+ > 💡 Got watermarks that Gemini Watermark Remover can't handle? Try the general-purpose AI watermark remover: [pilio.ai/image-watermark-remover](https://pilio.ai/image-watermark-remover)
12
+
13
+ <p align="center">
14
14
  <a href="https://geminiwatermarkremover.io/"><img src="https://img.shields.io/badge/🛠️_Online_Tool-geminiwatermarkremover.io-blue?style=for-the-badge" alt="Online Tool"></a>&nbsp;
15
+ <a href="https://geminiwatermarkremover.io/video"><img src="https://img.shields.io/badge/🎬_Video_Remover-New!-ff6b6b?style=for-the-badge" alt="Video Watermark Remover"></a>&nbsp;
15
16
  <a href="https://chromewebstore.google.com/detail/gemini-watermark-remover/cjlmnfcfnofnglkphbcdclbpimdjkmdf"><img src="https://img.shields.io/badge/🧩_Chrome_Extension-Install-blueviolet?style=for-the-badge" alt="Chrome Extension"></a>&nbsp;
16
17
  <a href="https://geminiwatermarkremover.io/userscript/gemini-watermark-remover.user.js"><img src="https://img.shields.io/badge/🐒_Userscript-Install-green?style=for-the-badge" alt="Userscript"></a>&nbsp;
17
18
  <a href="https://pilio.ai/image-watermark-remover"><img src="https://img.shields.io/badge/🧹_General_Remover-pilio.ai-gray?style=for-the-badge" alt="General Watermark Remover"></a>
18
19
  </p>
19
-
20
- <p align="center">
21
- <img src="https://count.getloli.com/@gemini-watermark-remover?name=gemini-watermark-remover&theme=minecraft&padding=7&offset=0&align=top&scale=1&pixelated=1&darkmode=auto" width="400">
22
- </p>
23
-
24
- ## Features
25
-
26
- - ✅ **100% Local Processing** - All image processing happens locally in your browser or on your machine. Nothing is uploaded.
27
- - ✅ **Mathematical Precision** - Based on the Reverse Alpha Blending formula, not "hallucinating" AI models.
28
- - ✅ **Auto-Detection** - Automatically identifies watermark size and position using Gemini's known output catalog and local anchor search.
20
+
21
+ <p align="center">
22
+ <img src="https://count.getloli.com/@gemini-watermark-remover?name=gemini-watermark-remover&theme=minecraft&padding=7&offset=0&align=top&scale=1&pixelated=1&darkmode=auto" width="400">
23
+ </p>
24
+
25
+ ## Features
26
+
27
+ - ✅ **100% Local Processing** - All image processing happens locally in your browser or on your machine. Nothing is uploaded.
28
+ - ✅ **Mathematical Precision** - Based on the Reverse Alpha Blending formula, not "hallucinating" AI models.
29
+ - ✅ **Auto-Detection** - Automatically identifies watermark size and position using Gemini's known output catalog and local anchor search.
29
30
  - ✅ **Flexible Usage** - Online tool for quick use, Chrome extension or userscript for seamless Gemini page integration, CLI and Skill for scripting and automation.
31
+ - ✅ **Video Support** - Remove watermarks from Gemini-generated videos at [geminiwatermarkremover.io/video](https://geminiwatermarkremover.io/video).
30
32
  - ✅ **Cross-Platform** - Works in modern browsers (Chrome, Firefox, Safari, Edge) and Node.js environments.
31
33
 
32
34
  ## Gemini Watermark Removal Examples
@@ -66,6 +68,17 @@ For all users — the fastest and easiest way to remove Gemini watermarks from i
66
68
  3. The engine will automatically process and remove the watermark.
67
69
  4. Download the cleaned image.
68
70
 
71
+ ### Online Video Watermark Remover (New!)
72
+
73
+ For Gemini-generated **videos** with visible watermarks:
74
+
75
+ 1. Open **[geminiwatermarkremover.io/video](https://geminiwatermarkremover.io/video)**.
76
+ 2. Upload or drag-and-drop your Gemini-generated video.
77
+ 3. The tool will automatically detect and remove the watermark from the video.
78
+ 4. Download the cleaned video.
79
+
80
+ > **Note**: Video watermark removal runs entirely in your browser — no files are uploaded to any server.
81
+
69
82
  ### Chrome Extension
70
83
 
71
84
  Use this if you do not want a userscript manager but still want automatic Gemini page integration for previews, copy, and download actions.
@@ -371,6 +384,7 @@ gemini-watermark-remover/
371
384
  ## Limitations
372
385
 
373
386
  - Only removes **Gemini visible watermarks** <small>(the semi-transparent logo in bottom-right)</small>
387
+ - Supports both **image** and **video** watermark removal — video processing is available via the [online tool](https://geminiwatermarkremover.io/video)
374
388
  - Does not remove invisible/steganographic watermarks. <small>[(Learn more about SynthID)](https://support.google.com/gemini/answer/16722517)</small>
375
389
  - Designed for Gemini's current visible watermark pattern <small>(validated against this repo through April 2026)</small>
376
390
 
package/README_zh.md CHANGED
@@ -12,6 +12,7 @@
12
12
 
13
13
  <p align="center">
14
14
  <a href="https://geminiwatermarkremover.io/"><img src="https://img.shields.io/badge/🛠️_在线工具-geminiwatermarkremover.io-blue?style=for-the-badge" alt="在线工具"></a>&nbsp;
15
+ <a href="https://geminiwatermarkremover.io/video"><img src="https://img.shields.io/badge/🎬_视频去水印-New!-ff6b6b?style=for-the-badge" alt="视频去水印"></a>&nbsp;
15
16
  <a href="https://chromewebstore.google.com/detail/gemini-watermark-remover/cjlmnfcfnofnglkphbcdclbpimdjkmdf"><img src="https://img.shields.io/badge/🧩_Chrome插件-安装-blueviolet?style=for-the-badge" alt="Chrome 插件"></a>&nbsp;
16
17
  <a href="https://geminiwatermarkremover.io/userscript/gemini-watermark-remover.user.js"><img src="https://img.shields.io/badge/🐒_油猴脚本-安装-green?style=for-the-badge" alt="油猴脚本"></a>&nbsp;
17
18
  <a href="https://pilio.ai/image-watermark-remover"><img src="https://img.shields.io/badge/🧹_通用去水印-pilio.ai-gray?style=for-the-badge" alt="通用去水印"></a>
@@ -27,6 +28,7 @@
27
28
  - ✅ **数学精确** - 基于反向 Alpha 混合算法,而非 AI 修复模型
28
29
  - ✅ **自动检测** - 基于 Gemini 已知输出尺寸目录和局部锚点搜索,自动识别水印大小与位置
29
30
  - ✅ **灵活使用** - 在线工具快速上手、Chrome 插件或油猴脚本无缝集成 Gemini 页面、CLI 和 Skill 支持脚本化与自动化
31
+ - ✅ **视频支持** - 支持去除 Gemini 生成视频中的水印,访问 [geminiwatermarkremover.io/video](https://geminiwatermarkremover.io/video) 即可使用
30
32
  - ✅ **跨平台** - 支持现代浏览器(Chrome、Firefox、Safari、Edge)和 Node.js 环境
31
33
 
32
34
  ## Gemini 去水印效果示例
@@ -66,6 +68,17 @@
66
68
  3. 图片会自动开始处理,移除水印
67
69
  4. 下载处理后的图片
68
70
 
71
+ ### 在线视频去水印工具(新功能!)
72
+
73
+ 针对带有可见水印的 Gemini 生成**视频**:
74
+
75
+ 1. 浏览器打开 **[geminiwatermarkremover.io/video](https://geminiwatermarkremover.io/video)**
76
+ 2. 拖拽或点击选择带水印的 Gemini 生成视频
77
+ 3. 工具会自动检测并去除视频中的水印
78
+ 4. 下载处理后的视频
79
+
80
+ > **说明**:视频去水印处理完全在浏览器本地完成,不会上传任何文件到服务器。
81
+
69
82
  ### Chrome 插件
70
83
 
71
84
  适合不想安装油猴脚本、但希望在 Gemini 页面里实时自动处理预览图、复制和下载动作的用户。
@@ -371,6 +384,7 @@ gemini-watermark-remover/
371
384
  ## 局限性
372
385
 
373
386
  - 只去除了 **Gemini 可见的水印**<small>(即右下角的半透明 Logo)</small>
387
+ - 同时支持**图片**和**视频**水印去除 — 视频处理请使用[在线工具](https://geminiwatermarkremover.io/video)
374
388
  - 无法去除隐形或隐写水印。<small>[(了解更多关于 SynthID 的信息)](https://support.google.com/gemini/answer/16722517)</small>
375
389
  - 针对 Gemini 当前的可见水印模式设计<small>(本仓库验证范围截至 2026 年 4 月)</small>
376
390
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pilio/gemini-watermark-remover",
3
3
  "description": "Automatically removes watermarks from Gemini AI generated images",
4
- "version": "1.0.20",
4
+ "version": "1.0.22",
5
5
  "author": "GargantuaX",
6
6
  "sideEffects": false,
7
7
  "files": [
@@ -45,6 +45,10 @@
45
45
  "types": "./src/sdk/node.d.ts",
46
46
  "import": "./src/sdk/node.js"
47
47
  },
48
+ "./video": {
49
+ "types": "./src/sdk/video.d.ts",
50
+ "import": "./src/sdk/video.js"
51
+ },
48
52
  "./runtime-browser": {
49
53
  "types": "./src/runtime/browser.d.ts",
50
54
  "import": "./src/runtime/browser.js"
@@ -65,10 +69,14 @@
65
69
  "devDependencies": {
66
70
  "@types/node": "^24.6.0",
67
71
  "esbuild": "^0.24.0",
72
+ "onnxruntime-web": "^1.26.0",
68
73
  "playwright": "^1.58.2",
69
74
  "sharp": "^0.34.5",
70
75
  "typescript": "^5.9.3"
71
76
  },
77
+ "dependencies": {
78
+ "mediabunny": "^1.46.0"
79
+ },
72
80
  "scripts": {
73
81
  "dev": "node build.js",
74
82
  "build": "node build.js --prod",
@@ -84,12 +92,69 @@
84
92
  "probe:real-page:compare": "node scripts/real-page-pixel-compare.js",
85
93
  "benchmark:samples": "node scripts/sample-benchmark.js",
86
94
  "benchmark:userscript": "node scripts/userscript-benchmark.js",
95
+ "compare:allenk-v2": "node scripts/create-allenk-v2-comparison-report.js",
96
+ "release:readiness": "node scripts/create-release-readiness-report.js",
97
+ "release:quality-gate": "pnpm compare:allenk-v2 -- --fail-on-incomplete && pnpm release:readiness -- --fail-on-not-ready",
98
+ "release:goal-audit": "node scripts/create-release-goal-audit-report.js",
99
+ "release:preflight": "pnpm test && pnpm build && pnpm package:extension && pnpm release:quality-gate && pnpm release:goal-audit -- --fail-on-incomplete",
100
+ "visible-residual:loop": "node scripts/run-visible-residual-loop.js",
101
+ "visible-residual:verify": "node scripts/verify-visible-residual-loop.js",
102
+ "visible-residual:validate-human-review": "node scripts/validate-visible-residual-human-review.js",
103
+ "visible-residual:create-gold-manifest": "node scripts/create-visible-residual-gold-manifest.js",
104
+ "visible-residual:review-status": "node scripts/report-visible-residual-review-progress.js",
105
+ "visible-residual:apply-focused-batch": "node scripts/apply-visible-residual-focused-review-batch.js",
106
+ "visible-residual:review-worksheet": "node scripts/create-visible-residual-review-worksheet.js",
107
+ "visible-residual:admission-report": "node scripts/create-visible-residual-admission-report.js",
108
+ "visible-residual:goal-audit": "node scripts/create-visible-residual-goal-audit-report.js",
109
+ "visible-residual:cluster-report": "node scripts/create-visible-residual-cluster-report.js",
110
+ "visible-residual:geometry-audit": "node scripts/audit-visible-residual-geometry.js",
87
111
  "scan:sample-watermarks": "node scripts/scan-sample-watermarks.js",
88
112
  "render:skipped-crops": "node scripts/render-skipped-candidate-crops.js",
113
+ "render:video-crops": "node scripts/render-video-crop-sheet.js",
114
+ "analyze:video-residual": "node scripts/analyze-video-residual.js",
115
+ "benchmark:video-crops": "node scripts/video-crop-benchmark.js",
116
+ "report:video-crops": "node scripts/report-video-crop-benchmark.js",
117
+ "diagnose:video-lowbody": "node scripts/diagnose-video-lowbody-regression.js",
118
+ "lab:video-frames": "node scripts/run-video-frame-backend-lab.js",
119
+ "lab:allenk-fdncnn-onnx-frames": "node scripts/run-allenk-fdncnn-onnx-frame-lab.js",
120
+ "lab:video-alpha-profile": "node scripts/run-video-alpha-profile-lab.js",
121
+ "lab:video-temporal-residual": "node scripts/run-video-temporal-residual-lab.js",
122
+ "fit:video-alpha-shape": "node scripts/fit-video-alpha-shape.js",
123
+ "gate:video-alpha-shape": "node scripts/gate-video-alpha-shape-candidates.js",
124
+ "gate:video-denoise": "node scripts/gate-video-denoise-candidates.js",
125
+ "gate:video-delivery": "node scripts/run-video-delivery-gate.js",
126
+ "report:video-alpha-policy-evidence": "node scripts/create-video-alpha-policy-evidence-report.js",
127
+ "report:video-frame-lab": "node scripts/report-video-frame-lab-sweep.js",
128
+ "report:video-review-pack": "node scripts/create-video-review-pack.js",
129
+ "report:video-review-index": "node scripts/create-video-review-index.js",
130
+ "report:video-review-screenshot": "node scripts/create-video-review-screenshot.js",
131
+ "report:video-review-thumbnail-sheet": "node scripts/create-video-review-thumbnail-sheet.js",
132
+ "report:video-dashboard-screenshot": "node scripts/create-video-review-screenshot.js --html .artifacts/video-delivery-dashboard/latest-video-dashboard.html --output .artifacts/video-delivery-dashboard/latest-video-dashboard.png --report .artifacts/video-delivery-dashboard/latest-video-dashboard-screenshot.json",
133
+ "report:video-review-decision": "node scripts/create-video-review-decision-report.js",
134
+ "report:video-acceptance-quickstart": "node scripts/create-video-acceptance-quickstart.js",
135
+ "report:video-pending-review-decision": "node scripts/create-video-pending-review-decision.js",
136
+ "report:video-alpha-policy-review-pack": "node scripts/create-video-alpha-policy-review-pack.js",
137
+ "report:video-light-polish-review-pack": "node scripts/create-video-light-polish-review-pack.js",
138
+ "report:video-polish-sweep-review-pack": "node scripts/create-video-polish-sweep-review-pack.js",
139
+ "report:video-delivery-dashboard": "node scripts/create-video-delivery-dashboard.js",
140
+ "report:video-goal-status": "node scripts/create-video-goal-status-report.js",
141
+ "report:video-delivery-bundle": "node scripts/create-video-delivery-bundle.js",
142
+ "verify:video-delivery-bundle": "node scripts/verify-video-delivery-bundle.js",
143
+ "export:video-backend": "node scripts/export-video-backend-variant.js",
144
+ "export:allenk-fdncnn-onnx-video": "node scripts/export-allenk-fdncnn-onnx-video.js",
145
+ "export:allenk-fdncnn-onnx-frame-video": "node scripts/export-allenk-fdncnn-onnx-frame-video.js",
146
+ "report:allenk-fdncnn-onnx-video-evidence": "node scripts/create-allenk-fdncnn-onnx-video-evidence.js",
147
+ "export:video-ui-preset": "node scripts/export-video-ui-preset.js",
148
+ "render:video-comparison-grid": "node scripts/render-video-comparison-grid.js",
89
149
  "package:extension": "node scripts/package-extension-release.js",
90
150
  "cli:smoke": "node bin/gwr.mjs --help",
91
151
  "serve": "pnpm dlx serve dist",
92
152
  "export:samples": "node scripts/export-fixed-samples.js",
93
- "calibrate:preview-alpha": "node scripts/calibrate-preview-alpha.js"
153
+ "calibrate:preview-alpha": "node scripts/calibrate-preview-alpha.js",
154
+ "extract:allenk-fdncnn": "node scripts/extract-allenk-fdncnn-model.js",
155
+ "export:allenk-fdncnn-onnx": "node scripts/export-allenk-fdncnn-onnx.js",
156
+ "smoke:allenk-fdncnn-onnx-runtime": "node scripts/smoke-allenk-fdncnn-onnx-runtime.js",
157
+ "report:allenk-fdncnn-browser-spike": "node scripts/create-allenk-fdncnn-browser-spike-report.js",
158
+ "report:allenk-fdncnn-runtime-seam": "node scripts/create-allenk-fdncnn-runtime-seam-report.js"
94
159
  }
95
160
  }
package/src/cli/gwrCli.js CHANGED
@@ -2,7 +2,7 @@ import { runRemoveCommand } from './gwrRemoveCommand.js';
2
2
 
3
3
  export async function main(argv, io) {
4
4
  if (argv.length === 0 || argv[0] === '--help' || argv[0] === '-h') {
5
- io.stdout.write('Usage: gwr remove <input> [--output <file> | --out-dir <dir>] [--overwrite] [--json]\n');
5
+ io.stdout.write('Usage: gwr remove <input> [--output <file> | --out-dir <dir>] [--overwrite] [--json] [--video-page <url-or-file>]\n');
6
6
  return 0;
7
7
  }
8
8
 
@@ -1,10 +1,16 @@
1
1
  import path from 'node:path';
2
2
  import { mkdir, readdir, stat } from 'node:fs/promises';
3
3
 
4
- import { inferMimeTypeFromPath, removeWatermarkFromFile } from '../sdk/node.js';
4
+ import {
5
+ inferMimeTypeFromPath,
6
+ inferVideoMimeTypeFromPath,
7
+ isVideoMimeType,
8
+ removeVideoWatermarkFromFile,
9
+ removeWatermarkFromFile
10
+ } from '../sdk/node.js';
5
11
 
6
12
  const REMOVE_USAGE =
7
- 'Usage: gwr remove <input> [--output <file> | --out-dir <dir>] [--overwrite] [--json]';
13
+ 'Usage: gwr remove <input> [--output <file> | --out-dir <dir>] [--overwrite] [--json] [--video-page <url-or-file>]';
8
14
 
9
15
  export async function runRemoveCommand(argv, io) {
10
16
  if (argv.length === 0 || argv[0] === '--help' || argv[0] === '-h') {
@@ -18,15 +24,7 @@ export async function runRemoveCommand(argv, io) {
18
24
  return 2;
19
25
  }
20
26
 
21
- const codec = await resolveCodecOptions(options).catch((error) => {
22
- io.stderr.write(`${error.message}\n`);
23
- return null;
24
- });
25
- if (!codec) {
26
- return 2;
27
- }
28
-
29
- const commandOptions = { ...options, codec };
27
+ const commandOptions = { ...options, imageCodecPromise: null };
30
28
  const inputStats = await stat(commandOptions.input).catch(() => null);
31
29
  if (!inputStats) {
32
30
  io.stderr.write(`Input not found: ${commandOptions.input}\n`);
@@ -64,7 +62,11 @@ function parseRemoveArgs(argv) {
64
62
  overwrite: false,
65
63
  json: false,
66
64
  decoder: null,
67
- encoder: null
65
+ encoder: null,
66
+ videoPage: null,
67
+ videoDenoiseBackend: null,
68
+ videoTimeoutMs: null,
69
+ allowLowConfidence: false
68
70
  };
69
71
 
70
72
  let parseOptions = true;
@@ -116,6 +118,30 @@ function parseRemoveArgs(argv) {
116
118
  continue;
117
119
  }
118
120
 
121
+ if (token === '--video-page') {
122
+ const parsed = parseOptionValue(argv, index, '--video-page');
123
+ if (!parsed.ok) return parsed;
124
+ options.videoPage = parsed.value;
125
+ index = parsed.index;
126
+ continue;
127
+ }
128
+
129
+ if (token === '--video-denoise-backend') {
130
+ const parsed = parseOptionValue(argv, index, '--video-denoise-backend');
131
+ if (!parsed.ok) return parsed;
132
+ options.videoDenoiseBackend = parsed.value;
133
+ index = parsed.index;
134
+ continue;
135
+ }
136
+
137
+ if (token === '--video-timeout-ms') {
138
+ const parsed = parseOptionValue(argv, index, '--video-timeout-ms');
139
+ if (!parsed.ok) return parsed;
140
+ options.videoTimeoutMs = Number(parsed.value);
141
+ index = parsed.index;
142
+ continue;
143
+ }
144
+
119
145
  if (token === '--overwrite') {
120
146
  options.overwrite = true;
121
147
  continue;
@@ -126,6 +152,11 @@ function parseRemoveArgs(argv) {
126
152
  continue;
127
153
  }
128
154
 
155
+ if (token === '--allow-low-confidence') {
156
+ options.allowLowConfidence = true;
157
+ continue;
158
+ }
159
+
129
160
  return { ok: false, error: `Unknown option: ${token}` };
130
161
  }
131
162
 
@@ -145,6 +176,10 @@ function parseRemoveArgs(argv) {
145
176
  return { ok: false, error: '--decoder and --encoder must be used together.' };
146
177
  }
147
178
 
179
+ if (options.videoTimeoutMs !== null && (!Number.isFinite(options.videoTimeoutMs) || options.videoTimeoutMs <= 0)) {
180
+ return { ok: false, error: '--video-timeout-ms must be a positive number.' };
181
+ }
182
+
148
183
  return options;
149
184
  }
150
185
 
@@ -301,17 +336,50 @@ async function processOneFile(inputPath, outputPath, options) {
301
336
  }
302
337
 
303
338
  await mkdir(path.dirname(outputPath), { recursive: true });
339
+ if (isVideoPath(inputPath) || isVideoPath(outputPath)) {
340
+ const result = await removeVideoWatermarkFromFile(inputPath, {
341
+ outputPath,
342
+ mimeType: inferVideoMimeTypeFromPath(outputPath || inputPath),
343
+ pagePath: options.videoPage || undefined,
344
+ denoiseBackend: options.videoDenoiseBackend || undefined,
345
+ timeoutMs: Number.isFinite(options.videoTimeoutMs) && options.videoTimeoutMs > 0
346
+ ? options.videoTimeoutMs
347
+ : undefined,
348
+ allowLowConfidence: options.allowLowConfidence
349
+ });
350
+
351
+ return {
352
+ input: inputPath,
353
+ output: outputPath,
354
+ kind: 'video',
355
+ meta: result.meta
356
+ };
357
+ }
358
+
359
+ const codec = await getImageCodec(options);
304
360
  const mimeType = inferMimeTypeFromPath(outputPath);
305
361
  const result = await removeWatermarkFromFile(inputPath, {
306
362
  outputPath,
307
363
  mimeType,
308
- decodeImageData: options.codec.decodeImageData,
309
- encodeImageData: options.codec.encodeImageData
364
+ decodeImageData: codec.decodeImageData,
365
+ encodeImageData: codec.encodeImageData
310
366
  });
311
367
 
312
368
  return {
313
369
  input: inputPath,
314
370
  output: outputPath,
371
+ kind: 'image',
315
372
  meta: result.meta
316
373
  };
317
374
  }
375
+
376
+ function isVideoPath(filePath) {
377
+ return isVideoMimeType(inferVideoMimeTypeFromPath(filePath));
378
+ }
379
+
380
+ async function getImageCodec(options) {
381
+ if (!options.imageCodecPromise) {
382
+ options.imageCodecPromise = resolveCodecOptions(options);
383
+ }
384
+ return options.imageCodecPromise;
385
+ }
@@ -7,6 +7,8 @@ import { resolveGeminiWatermarkSearchConfigs } from './geminiSizeCatalog.js';
7
7
 
8
8
  const DEFAULT_THRESHOLD = 0.35;
9
9
  const EPSILON = 1e-8;
10
+ const REFERENCE_WATERMARK_SIZE = 96;
11
+ const MIN_COARSE_ADJUSTED_SCORE = 0.08;
10
12
 
11
13
  const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
12
14
 
@@ -172,6 +174,23 @@ function createScaleList(minSize, maxSize) {
172
174
  return [...set].sort((a, b) => a - b);
173
175
  }
174
176
 
177
+ export function computeSizeAdjustedConfidence(confidence, size, referenceSize = REFERENCE_WATERMARK_SIZE) {
178
+ if (
179
+ !Number.isFinite(confidence) ||
180
+ !Number.isFinite(size) ||
181
+ !Number.isFinite(referenceSize) ||
182
+ size <= 0 ||
183
+ referenceSize <= 0
184
+ ) {
185
+ return 0;
186
+ }
187
+
188
+ // NCC tends to favor tiny templates. A cube-root penalty keeps that bias in
189
+ // check without crushing legitimate preview-tier watermarks.
190
+ const sizeWeight = Math.min(1, Math.cbrt(size / referenceSize));
191
+ return confidence * sizeWeight;
192
+ }
193
+
175
194
  function buildSeedConfigs(width, height, defaultConfig) {
176
195
  // Start adaptive search from both the coarse default anchor and any
177
196
  // catalog-projected anchors for official or near-official Gemini sizes.
@@ -407,7 +426,7 @@ export function detectAdaptiveWatermarkRegion({
407
426
  size: seedCandidate.size,
408
427
  x: seedCandidate.x,
409
428
  y: seedCandidate.y,
410
- adjustedScore: seedCandidate.confidence * Math.min(1, Math.sqrt(seedCandidate.size / 96))
429
+ adjustedScore: computeSizeAdjustedConfidence(seedCandidate.confidence, seedCandidate.size)
411
430
  });
412
431
  }
413
432
 
@@ -424,8 +443,8 @@ export function detectAdaptiveWatermarkRegion({
424
443
  if (!score) continue;
425
444
 
426
445
  // Prefer sizes close to known watermark scales to avoid tiny-template bias.
427
- const adjustedScore = score.confidence * Math.min(1, Math.sqrt(size / 96));
428
- if (adjustedScore < 0.08) continue;
446
+ const adjustedScore = computeSizeAdjustedConfidence(score.confidence, size);
447
+ if (adjustedScore < MIN_COARSE_ADJUSTED_SCORE) continue;
429
448
 
430
449
  pushTopK({
431
450
  size,