remove-ac 1.1.0__tar.gz
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.
- remove_ac-1.1.0/LICENSE +21 -0
- remove_ac-1.1.0/PKG-INFO +160 -0
- remove_ac-1.1.0/README.md +145 -0
- remove_ac-1.1.0/pyproject.toml +23 -0
- remove_ac-1.1.0/remove_ac/__init__.py +15 -0
- remove_ac-1.1.0/remove_ac/client.py +433 -0
- remove_ac-1.1.0/remove_ac.egg-info/PKG-INFO +160 -0
- remove_ac-1.1.0/remove_ac.egg-info/SOURCES.txt +11 -0
- remove_ac-1.1.0/remove_ac.egg-info/dependency_links.txt +1 -0
- remove_ac-1.1.0/remove_ac.egg-info/top_level.txt +2 -0
- remove_ac-1.1.0/removebg.py +3 -0
- remove_ac-1.1.0/setup.cfg +4 -0
- remove_ac-1.1.0/tests/test_python_sdk.py +138 -0
remove_ac-1.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 remove.ac
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
remove_ac-1.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: remove-ac
|
|
3
|
+
Version: 1.1.0
|
|
4
|
+
Summary: Official zero-dependency Python SDK & removebg 1:1 drop-in replacement for remove.ac
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://remove.ac
|
|
7
|
+
Project-URL: Documentation, https://remove.ac/api-info
|
|
8
|
+
Project-URL: Benchmarks, https://remove.ac/benchmarks
|
|
9
|
+
Project-URL: Repository, https://github.com/szp2005/remove-ac-sdk
|
|
10
|
+
Keywords: remove.ac,remove.bg,background-removal,matting,image-segmentation
|
|
11
|
+
Requires-Python: >=3.8
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
# remove.ac Official SDKs, CLI & MCP Server (Node.js · Python · MCP · Drop-in `remove.bg` Migration)
|
|
17
|
+
|
|
18
|
+
Zero-dependency official developer SDKs, hot-folder CLI, and Model Context Protocol (`MCP`) server for **[remove.ac](https://remove.ac)** — the dual-mode background removal platform pairing a free on-device WebAssembly studio with a `remove.bg`-compatible Cloud GPU REST API (`POST https://remove.ac/v1.0/removebg`).
|
|
19
|
+
|
|
20
|
+
- **Live Web Workbench (Free On-Device WASM + Batch 50 ZIP):** https://remove.ac
|
|
21
|
+
- **1-Line `remove.bg` API Migration Guide (Dec 1, 2026 Shutdown):** https://remove.ac/guides/remove-bg-api-migration
|
|
22
|
+
- **Interactive API Sandbox & Docs:** https://remove.ac/api-info
|
|
23
|
+
- **Figma, Photoshop UXP, GIMP, Sketch & Make/n8n Integrations:** https://remove.ac/plugins
|
|
24
|
+
- **Open Inference & Pricing Dataset (`CC BY 4.0`):** https://remove.ac/benchmarks
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Key Capabilities
|
|
29
|
+
|
|
30
|
+
- **1:1 `remove.bg` v1.0 Field Compatibility:** Swap `https://api.remove.bg/v1.0/removebg` to `https://remove.ac/v1.0/removebg` using standard `X-Api-Key: rac_live_...` headers and form fields (`image_file`, `image_file_b64`, `image_url`, `size`, `format`, `channels`, `type`, `roi`, `crop`, `crop_margin`, `scale`, `position`, `bg_color`, `bg_image_url`, `add_shadow`, `shadow_type`, `shadow_opacity`, `semitransparency`).
|
|
31
|
+
- **Up to 25 MB / 40 MP Zero-Disk Direct GPU Streaming:** Payloads above 4 MB automatically negotiate a short-lived HMAC ticket (`POST /v1.0/removebg/ticket`) and stream directly to ephemeral GPU RAM without disk or database blob persistence.
|
|
32
|
+
- **Atomic Credit Holds (`Idempotency-Key`):** Upstream timeouts or errors automatically release held credits with zero charge. Every verified account starts with **1 free credit**; pay-as-you-go credit packs never expire.
|
|
33
|
+
- **Zero External Runtime Dependencies:** The Node.js 18+ SDK, CLI, MCP stdio server, and Python 3.8+ SDK run purely on standard library primitives (`fetch`/`crypto` in Node, `urllib` in Python).
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 1. Quick `curl` Migration
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
curl -X POST https://remove.ac/v1.0/removebg \
|
|
41
|
+
-H "X-Api-Key: rac_live_your_key_here" \
|
|
42
|
+
-F "image_file=@photo.jpg" \
|
|
43
|
+
-F "size=full" \
|
|
44
|
+
-o cutout.png
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 2. Node.js / TypeScript SDK (`Node 18+`)
|
|
50
|
+
|
|
51
|
+
### Install directly via GitHub
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npm install github:szp2005/remove-ac-sdk
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Usage
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import fs from "node:fs";
|
|
61
|
+
import { RemoveAcClient } from "remove-ac";
|
|
62
|
+
|
|
63
|
+
const client = new RemoveAcClient({
|
|
64
|
+
apiKey: process.env.REMOVE_AC_API_KEY, // rac_live_...
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Files > 4 MB (up to 25 MB) automatically use /v1.0/removebg/ticket zero-disk GPU streaming
|
|
68
|
+
const result = await client.removeBackground("./studio-shot-18mb.png", {
|
|
69
|
+
size: "full",
|
|
70
|
+
format: "png",
|
|
71
|
+
shadowType: "contact",
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
fs.writeFileSync("./studio-shot-cutout.png", result.buffer);
|
|
75
|
+
console.log(`Saved ${result.width}x${result.height} (${result.creditsCharged} credit)`);
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## 3. CLI & `--watch` Hot-Folder Batch Tool
|
|
81
|
+
|
|
82
|
+
Run directly with `npx` or after installing:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
export REMOVE_AC_API_KEY="rac_live_..."
|
|
86
|
+
|
|
87
|
+
# Process an entire folder with 4 parallel workers
|
|
88
|
+
npx github:szp2005/remove-ac-sdk ./input-photos ./output-pngs --concurrency=4 --size=full
|
|
89
|
+
|
|
90
|
+
# Watch a hot folder continuously and output transparent WebP cutouts
|
|
91
|
+
npx github:szp2005/remove-ac-sdk ./incoming ./cutouts --watch --format=webp --crop
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## 4. Python SDK (`Python 3.8+`, Zero External Dependencies)
|
|
97
|
+
|
|
98
|
+
### Install directly via GitHub
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
pip install git+https://github.com/szp2005/remove-ac-sdk.git
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Native Client
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
from remove_ac import RemoveAcClient
|
|
108
|
+
|
|
109
|
+
client = RemoveAcClient() # reads REMOVE_AC_API_KEY
|
|
110
|
+
png_bytes = client.remove_background(
|
|
111
|
+
"product.jpg",
|
|
112
|
+
size="full",
|
|
113
|
+
shadow_type="contact",
|
|
114
|
+
)
|
|
115
|
+
with open("product-nobg.png", "wb") as fh:
|
|
116
|
+
fh.write(png_bytes)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### 1:1 `removebg` Drop-In Replacement Shim
|
|
120
|
+
|
|
121
|
+
Existing scripts written for `from removebg import RemoveBg` work without rewriting call sites:
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
from removebg import RemoveBg
|
|
125
|
+
|
|
126
|
+
rmbg = RemoveBg("rac_live_your_key_here")
|
|
127
|
+
rmbg.remove_background_from_img_file("car.jpg", size="full", new_file_name="car-cutout.png")
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## 5. MCP Server (`Claude Desktop` · `Cursor` · `Windsurf` · `Cline`)
|
|
133
|
+
|
|
134
|
+
`remove-ac` includes a zero-dependency Model Context Protocol (`MCP`) stdio server (`remove-ac-mcp`) exposing three tools to AI coding and design agents:
|
|
135
|
+
|
|
136
|
+
- `remove_background_file` — remove background from any local `.png` / `.jpg` / `.webp` up to `25 MB / 40 MP` (auto-streams `>4 MB` via `/v1.0/removebg/ticket`)
|
|
137
|
+
- `remove_background_url` — remove background from a public `https://` image URL and save locally
|
|
138
|
+
- `get_account_balance` — inspect remaining `remove.ac` API credits
|
|
139
|
+
|
|
140
|
+
### Claude Desktop / Cursor Configuration
|
|
141
|
+
|
|
142
|
+
```json
|
|
143
|
+
{
|
|
144
|
+
"mcpServers": {
|
|
145
|
+
"remove-ac": {
|
|
146
|
+
"command": "npx",
|
|
147
|
+
"args": ["-y", "-p", "github:szp2005/remove-ac-sdk", "remove-ac-mcp"],
|
|
148
|
+
"env": {
|
|
149
|
+
"REMOVE_AC_API_KEY": "rac_live_your_key_here"
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## License
|
|
159
|
+
|
|
160
|
+
MIT © [remove.ac](https://remove.ac)
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# remove.ac Official SDKs, CLI & MCP Server (Node.js · Python · MCP · Drop-in `remove.bg` Migration)
|
|
2
|
+
|
|
3
|
+
Zero-dependency official developer SDKs, hot-folder CLI, and Model Context Protocol (`MCP`) server for **[remove.ac](https://remove.ac)** — the dual-mode background removal platform pairing a free on-device WebAssembly studio with a `remove.bg`-compatible Cloud GPU REST API (`POST https://remove.ac/v1.0/removebg`).
|
|
4
|
+
|
|
5
|
+
- **Live Web Workbench (Free On-Device WASM + Batch 50 ZIP):** https://remove.ac
|
|
6
|
+
- **1-Line `remove.bg` API Migration Guide (Dec 1, 2026 Shutdown):** https://remove.ac/guides/remove-bg-api-migration
|
|
7
|
+
- **Interactive API Sandbox & Docs:** https://remove.ac/api-info
|
|
8
|
+
- **Figma, Photoshop UXP, GIMP, Sketch & Make/n8n Integrations:** https://remove.ac/plugins
|
|
9
|
+
- **Open Inference & Pricing Dataset (`CC BY 4.0`):** https://remove.ac/benchmarks
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Key Capabilities
|
|
14
|
+
|
|
15
|
+
- **1:1 `remove.bg` v1.0 Field Compatibility:** Swap `https://api.remove.bg/v1.0/removebg` to `https://remove.ac/v1.0/removebg` using standard `X-Api-Key: rac_live_...` headers and form fields (`image_file`, `image_file_b64`, `image_url`, `size`, `format`, `channels`, `type`, `roi`, `crop`, `crop_margin`, `scale`, `position`, `bg_color`, `bg_image_url`, `add_shadow`, `shadow_type`, `shadow_opacity`, `semitransparency`).
|
|
16
|
+
- **Up to 25 MB / 40 MP Zero-Disk Direct GPU Streaming:** Payloads above 4 MB automatically negotiate a short-lived HMAC ticket (`POST /v1.0/removebg/ticket`) and stream directly to ephemeral GPU RAM without disk or database blob persistence.
|
|
17
|
+
- **Atomic Credit Holds (`Idempotency-Key`):** Upstream timeouts or errors automatically release held credits with zero charge. Every verified account starts with **1 free credit**; pay-as-you-go credit packs never expire.
|
|
18
|
+
- **Zero External Runtime Dependencies:** The Node.js 18+ SDK, CLI, MCP stdio server, and Python 3.8+ SDK run purely on standard library primitives (`fetch`/`crypto` in Node, `urllib` in Python).
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 1. Quick `curl` Migration
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
curl -X POST https://remove.ac/v1.0/removebg \
|
|
26
|
+
-H "X-Api-Key: rac_live_your_key_here" \
|
|
27
|
+
-F "image_file=@photo.jpg" \
|
|
28
|
+
-F "size=full" \
|
|
29
|
+
-o cutout.png
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## 2. Node.js / TypeScript SDK (`Node 18+`)
|
|
35
|
+
|
|
36
|
+
### Install directly via GitHub
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npm install github:szp2005/remove-ac-sdk
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Usage
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
import fs from "node:fs";
|
|
46
|
+
import { RemoveAcClient } from "remove-ac";
|
|
47
|
+
|
|
48
|
+
const client = new RemoveAcClient({
|
|
49
|
+
apiKey: process.env.REMOVE_AC_API_KEY, // rac_live_...
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Files > 4 MB (up to 25 MB) automatically use /v1.0/removebg/ticket zero-disk GPU streaming
|
|
53
|
+
const result = await client.removeBackground("./studio-shot-18mb.png", {
|
|
54
|
+
size: "full",
|
|
55
|
+
format: "png",
|
|
56
|
+
shadowType: "contact",
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
fs.writeFileSync("./studio-shot-cutout.png", result.buffer);
|
|
60
|
+
console.log(`Saved ${result.width}x${result.height} (${result.creditsCharged} credit)`);
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## 3. CLI & `--watch` Hot-Folder Batch Tool
|
|
66
|
+
|
|
67
|
+
Run directly with `npx` or after installing:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
export REMOVE_AC_API_KEY="rac_live_..."
|
|
71
|
+
|
|
72
|
+
# Process an entire folder with 4 parallel workers
|
|
73
|
+
npx github:szp2005/remove-ac-sdk ./input-photos ./output-pngs --concurrency=4 --size=full
|
|
74
|
+
|
|
75
|
+
# Watch a hot folder continuously and output transparent WebP cutouts
|
|
76
|
+
npx github:szp2005/remove-ac-sdk ./incoming ./cutouts --watch --format=webp --crop
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## 4. Python SDK (`Python 3.8+`, Zero External Dependencies)
|
|
82
|
+
|
|
83
|
+
### Install directly via GitHub
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
pip install git+https://github.com/szp2005/remove-ac-sdk.git
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Native Client
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
from remove_ac import RemoveAcClient
|
|
93
|
+
|
|
94
|
+
client = RemoveAcClient() # reads REMOVE_AC_API_KEY
|
|
95
|
+
png_bytes = client.remove_background(
|
|
96
|
+
"product.jpg",
|
|
97
|
+
size="full",
|
|
98
|
+
shadow_type="contact",
|
|
99
|
+
)
|
|
100
|
+
with open("product-nobg.png", "wb") as fh:
|
|
101
|
+
fh.write(png_bytes)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 1:1 `removebg` Drop-In Replacement Shim
|
|
105
|
+
|
|
106
|
+
Existing scripts written for `from removebg import RemoveBg` work without rewriting call sites:
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
from removebg import RemoveBg
|
|
110
|
+
|
|
111
|
+
rmbg = RemoveBg("rac_live_your_key_here")
|
|
112
|
+
rmbg.remove_background_from_img_file("car.jpg", size="full", new_file_name="car-cutout.png")
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## 5. MCP Server (`Claude Desktop` · `Cursor` · `Windsurf` · `Cline`)
|
|
118
|
+
|
|
119
|
+
`remove-ac` includes a zero-dependency Model Context Protocol (`MCP`) stdio server (`remove-ac-mcp`) exposing three tools to AI coding and design agents:
|
|
120
|
+
|
|
121
|
+
- `remove_background_file` — remove background from any local `.png` / `.jpg` / `.webp` up to `25 MB / 40 MP` (auto-streams `>4 MB` via `/v1.0/removebg/ticket`)
|
|
122
|
+
- `remove_background_url` — remove background from a public `https://` image URL and save locally
|
|
123
|
+
- `get_account_balance` — inspect remaining `remove.ac` API credits
|
|
124
|
+
|
|
125
|
+
### Claude Desktop / Cursor Configuration
|
|
126
|
+
|
|
127
|
+
```json
|
|
128
|
+
{
|
|
129
|
+
"mcpServers": {
|
|
130
|
+
"remove-ac": {
|
|
131
|
+
"command": "npx",
|
|
132
|
+
"args": ["-y", "-p", "github:szp2005/remove-ac-sdk", "remove-ac-mcp"],
|
|
133
|
+
"env": {
|
|
134
|
+
"REMOVE_AC_API_KEY": "rac_live_your_key_here"
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## License
|
|
144
|
+
|
|
145
|
+
MIT © [remove.ac](https://remove.ac)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=64"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "remove-ac"
|
|
7
|
+
version = "1.1.0"
|
|
8
|
+
description = "Official zero-dependency Python SDK & removebg 1:1 drop-in replacement for remove.ac"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
requires-python = ">=3.8"
|
|
12
|
+
dependencies = []
|
|
13
|
+
keywords = ["remove.ac", "remove.bg", "background-removal", "matting", "image-segmentation"]
|
|
14
|
+
|
|
15
|
+
[project.urls]
|
|
16
|
+
Homepage = "https://remove.ac"
|
|
17
|
+
Documentation = "https://remove.ac/api-info"
|
|
18
|
+
Benchmarks = "https://remove.ac/benchmarks"
|
|
19
|
+
Repository = "https://github.com/szp2005/remove-ac-sdk"
|
|
20
|
+
|
|
21
|
+
[tool.setuptools]
|
|
22
|
+
packages = ["remove_ac"]
|
|
23
|
+
py-modules = ["removebg"]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from .client import (
|
|
2
|
+
RemoveAcClient,
|
|
3
|
+
RemoveAcError,
|
|
4
|
+
RemoveBg,
|
|
5
|
+
derive_sub_idempotency_key,
|
|
6
|
+
parse_image_dimensions,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"RemoveAcClient",
|
|
11
|
+
"RemoveBg",
|
|
12
|
+
"RemoveAcError",
|
|
13
|
+
"parse_image_dimensions",
|
|
14
|
+
"derive_sub_idempotency_key",
|
|
15
|
+
]
|
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
"""remove.ac Official Zero-Dependency Python SDK + 1:1 removebg drop-in client."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import base64
|
|
5
|
+
import hashlib
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import struct
|
|
9
|
+
import urllib.error
|
|
10
|
+
import urllib.request
|
|
11
|
+
import uuid
|
|
12
|
+
from typing import Any, Callable, Dict, Optional, Tuple, Union
|
|
13
|
+
|
|
14
|
+
DEFAULT_BASE_URL = "https://remove.ac"
|
|
15
|
+
DIRECT_THRESHOLD_BYTES = 4 * 1024 * 1024
|
|
16
|
+
DIRECT_RGBA_PNG_PIXELS = 5_000_000
|
|
17
|
+
DIRECT_CROPPED_RGBA_PNG_PIXELS = 10_000_000
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def parse_image_dimensions(data: bytes) -> Optional[Tuple[int, int]]:
|
|
21
|
+
if not data or len(data) < 10:
|
|
22
|
+
return None
|
|
23
|
+
if len(data) >= 24 and data[:8] == b"\x89PNG\r\n\x1a\n" and data[12:16] == b"IHDR":
|
|
24
|
+
w, h = struct.unpack(">II", data[16:24])
|
|
25
|
+
return (w, h) if 0 < w <= 1_000_000 and 0 < h <= 1_000_000 else None
|
|
26
|
+
if data[:6] in (b"GIF87a", b"GIF89a"):
|
|
27
|
+
w, h = struct.unpack("<HH", data[6:10])
|
|
28
|
+
return (w, h) if w > 0 and h > 0 else None
|
|
29
|
+
if len(data) >= 25 and data[:4] == b"RIFF" and data[8:12] == b"WEBP":
|
|
30
|
+
chunk = data[12:16]
|
|
31
|
+
if chunk == b"VP8 " and len(data) >= 30 and data[23:26] == b"\x9d\x01\x2a":
|
|
32
|
+
w, h = struct.unpack("<HH", data[26:30])
|
|
33
|
+
w &= 0x3FFF
|
|
34
|
+
h &= 0x3FFF
|
|
35
|
+
return (w, h) if w > 0 and h > 0 else None
|
|
36
|
+
if chunk == b"VP8L" and data[20] == 0x2F:
|
|
37
|
+
b0, b1, b2, b3 = data[21], data[22], data[23], data[24]
|
|
38
|
+
w = 1 + (b0 | ((b1 & 0x3F) << 8))
|
|
39
|
+
h = 1 + ((b1 >> 6) | (b2 << 2) | ((b3 & 0x0F) << 10))
|
|
40
|
+
return (w, h) if w > 0 and h > 0 else None
|
|
41
|
+
if chunk == b"VP8X" and len(data) >= 30:
|
|
42
|
+
w = 1 + (data[24] | (data[25] << 8) | (data[26] << 16))
|
|
43
|
+
h = 1 + (data[27] | (data[28] << 8) | (data[29] << 16))
|
|
44
|
+
return (w, h) if w > 0 and h > 0 else None
|
|
45
|
+
if data[:2] == b"\xff\xd8":
|
|
46
|
+
pos = 2
|
|
47
|
+
steps = 0
|
|
48
|
+
while steps < 512 and pos + 3 < len(data):
|
|
49
|
+
steps += 1
|
|
50
|
+
if data[pos] != 0xFF:
|
|
51
|
+
break
|
|
52
|
+
while pos < len(data) and data[pos] == 0xFF:
|
|
53
|
+
pos += 1
|
|
54
|
+
if pos >= len(data):
|
|
55
|
+
break
|
|
56
|
+
marker = data[pos]
|
|
57
|
+
pos += 1
|
|
58
|
+
if marker in (0xD9, 0xDA):
|
|
59
|
+
break
|
|
60
|
+
if marker in (0x00, 0x01) or (0xD0 <= marker <= 0xD7):
|
|
61
|
+
continue
|
|
62
|
+
if pos + 1 >= len(data):
|
|
63
|
+
break
|
|
64
|
+
seg_len = (data[pos] << 8) | data[pos + 1]
|
|
65
|
+
if seg_len < 2:
|
|
66
|
+
break
|
|
67
|
+
if marker in (0xC0, 0xC1, 0xC2, 0xC3, 0xC5, 0xC6, 0xC7, 0xC9, 0xCA, 0xCB, 0xCD, 0xCE, 0xCF):
|
|
68
|
+
if pos + 6 >= len(data):
|
|
69
|
+
break
|
|
70
|
+
h = (data[pos + 3] << 8) | data[pos + 4]
|
|
71
|
+
w = (data[pos + 5] << 8) | data[pos + 6]
|
|
72
|
+
return (w, h) if w > 0 and h > 0 else None
|
|
73
|
+
pos += seg_len
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _should_preflight_via_ticket(
|
|
78
|
+
img_bytes: Optional[bytes], size: str, fmt: str, channels: str, crop: bool
|
|
79
|
+
) -> bool:
|
|
80
|
+
if not img_bytes:
|
|
81
|
+
return False
|
|
82
|
+
if fmt not in ("png", "auto", "zip") or channels == "alpha":
|
|
83
|
+
return False
|
|
84
|
+
dims = parse_image_dimensions(img_bytes)
|
|
85
|
+
if not dims:
|
|
86
|
+
return False
|
|
87
|
+
input_px = dims[0] * dims[1]
|
|
88
|
+
if size in ("preview", "small", "regular"):
|
|
89
|
+
eff = min(input_px, 250_000)
|
|
90
|
+
elif size == "medium":
|
|
91
|
+
eff = min(input_px, 1_500_000)
|
|
92
|
+
elif size == "hd":
|
|
93
|
+
eff = min(input_px, 4_000_000)
|
|
94
|
+
elif size == "4k":
|
|
95
|
+
eff = min(input_px, 10_000_000)
|
|
96
|
+
else:
|
|
97
|
+
eff = input_px
|
|
98
|
+
return eff > (DIRECT_CROPPED_RGBA_PNG_PIXELS if crop else DIRECT_RGBA_PNG_PIXELS)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class RemoveAcError(Exception):
|
|
102
|
+
def __init__(self, status_code: int, message: str):
|
|
103
|
+
super().__init__(f"[HTTP {status_code}] {message}")
|
|
104
|
+
self.status_code = status_code
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def derive_sub_idempotency_key(idem: str, suffix: str) -> str:
|
|
108
|
+
trimmed = (idem or "").strip()
|
|
109
|
+
if not trimmed or len(trimmed) > 255:
|
|
110
|
+
raise RemoveAcError(400, "idempotency_key must be between 1 and 255 characters")
|
|
111
|
+
direct = f"{trimmed}:{suffix}"
|
|
112
|
+
if len(direct) <= 255:
|
|
113
|
+
return direct
|
|
114
|
+
digest = hashlib.sha256(trimmed.encode("utf-8")).hexdigest()[:32]
|
|
115
|
+
return f"{trimmed[:180]}:{digest}:{suffix}"
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _encode_multipart(
|
|
119
|
+
fields: Dict[str, str],
|
|
120
|
+
file_tuple: Optional[Tuple[str, str, bytes]] = None,
|
|
121
|
+
) -> Tuple[bytes, str]:
|
|
122
|
+
boundary = f"----RemoveAcBoundary{uuid.uuid4().hex}"
|
|
123
|
+
chunks = []
|
|
124
|
+
for key, val in fields.items():
|
|
125
|
+
chunks.append(f"--{boundary}\r\n".encode("utf-8"))
|
|
126
|
+
chunks.append(f'Content-Disposition: form-data; name="{key}"\r\n\r\n'.encode("utf-8"))
|
|
127
|
+
chunks.append(f"{val}\r\n".encode("utf-8"))
|
|
128
|
+
if file_tuple is not None:
|
|
129
|
+
field_name, filename, raw_bytes = file_tuple
|
|
130
|
+
safe_name = filename.replace('"', "_")
|
|
131
|
+
chunks.append(f"--{boundary}\r\n".encode("utf-8"))
|
|
132
|
+
chunks.append(
|
|
133
|
+
f'Content-Disposition: form-data; name="{field_name}"; filename="{safe_name}"\r\n'
|
|
134
|
+
f"Content-Type: application/octet-stream\r\n\r\n".encode("utf-8")
|
|
135
|
+
)
|
|
136
|
+
chunks.append(raw_bytes)
|
|
137
|
+
chunks.append(b"\r\n")
|
|
138
|
+
chunks.append(f"--{boundary}--\r\n".encode("utf-8"))
|
|
139
|
+
return b"".join(chunks), f"multipart/form-data; boundary={boundary}"
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
TransportFn = Callable[[str, str, Dict[str, str], Optional[bytes], int], Tuple[int, Dict[str, str], bytes]]
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _default_transport(
|
|
146
|
+
method: str,
|
|
147
|
+
url: str,
|
|
148
|
+
headers: Dict[str, str],
|
|
149
|
+
body: Optional[bytes],
|
|
150
|
+
timeout: int,
|
|
151
|
+
) -> Tuple[int, Dict[str, str], bytes]:
|
|
152
|
+
req = urllib.request.Request(url, data=body, headers=headers, method=method)
|
|
153
|
+
try:
|
|
154
|
+
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
|
155
|
+
resp_headers = {k.lower(): v for k, v in resp.headers.items()}
|
|
156
|
+
return resp.status, resp_headers, resp.read()
|
|
157
|
+
except urllib.error.HTTPError as exc:
|
|
158
|
+
err_body = exc.read() if exc.fp else b""
|
|
159
|
+
resp_headers = {k.lower(): v for k, v in (exc.headers.items() if exc.headers else [])}
|
|
160
|
+
return exc.code, resp_headers, err_body
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class RemoveAcClient:
|
|
164
|
+
def __init__(
|
|
165
|
+
self,
|
|
166
|
+
api_key: Optional[str] = None,
|
|
167
|
+
base_url: Optional[str] = None,
|
|
168
|
+
transport: Optional[TransportFn] = None,
|
|
169
|
+
):
|
|
170
|
+
self.api_key = (
|
|
171
|
+
api_key
|
|
172
|
+
or os.environ.get("REMOVE_AC_API_KEY")
|
|
173
|
+
or os.environ.get("REMOVE_BG_API_KEY")
|
|
174
|
+
or ""
|
|
175
|
+
)
|
|
176
|
+
resolved_base = base_url or os.environ.get("REMOVE_AC_BASE_URL") or DEFAULT_BASE_URL
|
|
177
|
+
self.base_url = resolved_base.rstrip("/")
|
|
178
|
+
self._transport = transport or _default_transport
|
|
179
|
+
|
|
180
|
+
def get_account(self, timeout: int = 15) -> Dict[str, Any]:
|
|
181
|
+
if not self.api_key:
|
|
182
|
+
raise RemoveAcError(403, "Missing remove.ac API key")
|
|
183
|
+
status, _, body = self._transport(
|
|
184
|
+
"GET",
|
|
185
|
+
f"{self.base_url}/v1.0/account",
|
|
186
|
+
{"X-Api-Key": self.api_key},
|
|
187
|
+
None,
|
|
188
|
+
timeout,
|
|
189
|
+
)
|
|
190
|
+
if status < 200 or status >= 300:
|
|
191
|
+
raise RemoveAcError(status, body.decode("utf-8", errors="replace"))
|
|
192
|
+
return json.loads(body.decode("utf-8"))
|
|
193
|
+
|
|
194
|
+
def remove_background(
|
|
195
|
+
self,
|
|
196
|
+
image: Union[str, bytes],
|
|
197
|
+
*,
|
|
198
|
+
size: str = "auto",
|
|
199
|
+
format: str = "png",
|
|
200
|
+
channels: str = "rgba",
|
|
201
|
+
type: str = "auto",
|
|
202
|
+
type_level: Optional[str] = None,
|
|
203
|
+
roi: Optional[str] = None,
|
|
204
|
+
scale: Optional[str] = None,
|
|
205
|
+
position: Optional[str] = None,
|
|
206
|
+
bg_color: Optional[str] = None,
|
|
207
|
+
bg_image_url: Optional[str] = None,
|
|
208
|
+
crop: bool = False,
|
|
209
|
+
crop_margin: Optional[str] = None,
|
|
210
|
+
add_shadow: bool = False,
|
|
211
|
+
shadow_type: Optional[str] = None,
|
|
212
|
+
shadow_opacity: Optional[float] = None,
|
|
213
|
+
semitransparency: bool = True,
|
|
214
|
+
direct: bool = False,
|
|
215
|
+
idempotency_key: Optional[str] = None,
|
|
216
|
+
timeout: int = 90,
|
|
217
|
+
) -> bytes:
|
|
218
|
+
if not self.api_key:
|
|
219
|
+
raise RemoveAcError(403, "Missing remove.ac API key")
|
|
220
|
+
if idempotency_key is not None:
|
|
221
|
+
trimmed_idem = idempotency_key.strip()
|
|
222
|
+
if not trimmed_idem or len(trimmed_idem) > 255:
|
|
223
|
+
raise RemoveAcError(400, "idempotency_key must be between 1 and 255 characters")
|
|
224
|
+
idem = trimmed_idem
|
|
225
|
+
else:
|
|
226
|
+
idem = f"pysdk-{uuid.uuid4()}"
|
|
227
|
+
img_bytes: Optional[bytes] = None
|
|
228
|
+
img_name = "image.png"
|
|
229
|
+
img_url: Optional[str] = None
|
|
230
|
+
|
|
231
|
+
if isinstance(image, (bytes, bytearray)):
|
|
232
|
+
img_bytes = bytes(image)
|
|
233
|
+
elif isinstance(image, str) and image.startswith(("http://", "https://")):
|
|
234
|
+
img_url = image
|
|
235
|
+
elif isinstance(image, str):
|
|
236
|
+
img_name = os.path.basename(image)
|
|
237
|
+
with open(image, "rb") as fh:
|
|
238
|
+
img_bytes = fh.read()
|
|
239
|
+
else:
|
|
240
|
+
raise RemoveAcError(400, "Unsupported image input")
|
|
241
|
+
|
|
242
|
+
def _via_ticket(suffix: str) -> bytes:
|
|
243
|
+
if img_bytes is None:
|
|
244
|
+
raise RemoveAcError(413, "Ticket mode requires binary input")
|
|
245
|
+
payload: Dict[str, Any] = {
|
|
246
|
+
"foregroundSha256": hashlib.sha256(img_bytes).hexdigest(),
|
|
247
|
+
"foregroundByteLength": len(img_bytes),
|
|
248
|
+
"size": size,
|
|
249
|
+
"format": format,
|
|
250
|
+
"channels": channels,
|
|
251
|
+
"semitransparency": semitransparency,
|
|
252
|
+
}
|
|
253
|
+
if type and type != "auto":
|
|
254
|
+
payload["type"] = type
|
|
255
|
+
if type_level:
|
|
256
|
+
payload["type_level"] = type_level
|
|
257
|
+
if roi:
|
|
258
|
+
payload["roi"] = roi
|
|
259
|
+
if scale:
|
|
260
|
+
payload["scale"] = scale
|
|
261
|
+
if position:
|
|
262
|
+
payload["position"] = position
|
|
263
|
+
if bg_color:
|
|
264
|
+
payload["bg_color"] = bg_color
|
|
265
|
+
if bg_image_url:
|
|
266
|
+
payload["bg_image_url"] = bg_image_url
|
|
267
|
+
if crop:
|
|
268
|
+
payload["crop"] = True
|
|
269
|
+
if crop_margin:
|
|
270
|
+
payload["crop_margin"] = crop_margin
|
|
271
|
+
if add_shadow:
|
|
272
|
+
payload["add_shadow"] = True
|
|
273
|
+
if shadow_type:
|
|
274
|
+
payload["shadow_type"] = shadow_type
|
|
275
|
+
if shadow_opacity is not None:
|
|
276
|
+
payload["shadow_opacity"] = shadow_opacity
|
|
277
|
+
|
|
278
|
+
t_status, _, t_body = self._transport(
|
|
279
|
+
"POST",
|
|
280
|
+
f"{self.base_url}/v1.0/removebg/ticket",
|
|
281
|
+
{
|
|
282
|
+
"Content-Type": "application/json",
|
|
283
|
+
"X-Api-Key": self.api_key,
|
|
284
|
+
"Idempotency-Key": derive_sub_idempotency_key(idem, suffix),
|
|
285
|
+
},
|
|
286
|
+
json.dumps(payload).encode("utf-8"),
|
|
287
|
+
30,
|
|
288
|
+
)
|
|
289
|
+
if t_status < 200 or t_status >= 300:
|
|
290
|
+
raise RemoveAcError(t_status, t_body.decode("utf-8", errors="replace"))
|
|
291
|
+
t_data = json.loads(t_body.decode("utf-8"))["data"]
|
|
292
|
+
mp_body, mp_ct = _encode_multipart({}, ("image_file", img_name, img_bytes))
|
|
293
|
+
d_status, _, d_body = self._transport(
|
|
294
|
+
"POST",
|
|
295
|
+
t_data["directEndpoint"],
|
|
296
|
+
{
|
|
297
|
+
"Authorization": f"Bearer {t_data['ticket']}",
|
|
298
|
+
"Content-Type": mp_ct,
|
|
299
|
+
},
|
|
300
|
+
mp_body,
|
|
301
|
+
timeout,
|
|
302
|
+
)
|
|
303
|
+
if d_status < 200 or d_status >= 300:
|
|
304
|
+
raise RemoveAcError(d_status, d_body.decode("utf-8", errors="replace"))
|
|
305
|
+
return d_body
|
|
306
|
+
|
|
307
|
+
if img_bytes is not None and (
|
|
308
|
+
direct
|
|
309
|
+
or len(img_bytes) > DIRECT_THRESHOLD_BYTES
|
|
310
|
+
or _should_preflight_via_ticket(img_bytes, size, format, channels, crop)
|
|
311
|
+
):
|
|
312
|
+
return _via_ticket("direct")
|
|
313
|
+
|
|
314
|
+
fields: Dict[str, str] = {
|
|
315
|
+
"size": size,
|
|
316
|
+
"format": format,
|
|
317
|
+
"channels": channels,
|
|
318
|
+
"semitransparency": str(semitransparency).lower(),
|
|
319
|
+
}
|
|
320
|
+
if img_url:
|
|
321
|
+
fields["image_url"] = img_url
|
|
322
|
+
if type and type != "auto":
|
|
323
|
+
fields["type"] = type
|
|
324
|
+
if type_level:
|
|
325
|
+
fields["type_level"] = type_level
|
|
326
|
+
if roi:
|
|
327
|
+
fields["roi"] = roi
|
|
328
|
+
if scale:
|
|
329
|
+
fields["scale"] = scale
|
|
330
|
+
if position:
|
|
331
|
+
fields["position"] = position
|
|
332
|
+
if bg_color:
|
|
333
|
+
fields["bg_color"] = bg_color
|
|
334
|
+
if bg_image_url:
|
|
335
|
+
fields["bg_image_url"] = bg_image_url
|
|
336
|
+
if crop:
|
|
337
|
+
fields["crop"] = "true"
|
|
338
|
+
if crop_margin:
|
|
339
|
+
fields["crop_margin"] = crop_margin
|
|
340
|
+
if add_shadow:
|
|
341
|
+
fields["add_shadow"] = "true"
|
|
342
|
+
if shadow_type:
|
|
343
|
+
fields["shadow_type"] = shadow_type
|
|
344
|
+
if shadow_opacity is not None:
|
|
345
|
+
fields["shadow_opacity"] = str(shadow_opacity)
|
|
346
|
+
|
|
347
|
+
file_tuple = ("image_file", img_name, img_bytes) if img_bytes is not None else None
|
|
348
|
+
mp_body, mp_ct = _encode_multipart(fields, file_tuple)
|
|
349
|
+
status, _, body = self._transport(
|
|
350
|
+
"POST",
|
|
351
|
+
f"{self.base_url}/v1.0/removebg",
|
|
352
|
+
{
|
|
353
|
+
"X-Api-Key": self.api_key,
|
|
354
|
+
"Idempotency-Key": idem,
|
|
355
|
+
"Content-Type": mp_ct,
|
|
356
|
+
},
|
|
357
|
+
mp_body,
|
|
358
|
+
timeout,
|
|
359
|
+
)
|
|
360
|
+
if (
|
|
361
|
+
status == 409
|
|
362
|
+
and idempotency_key is None
|
|
363
|
+
and b"idempotency_replay_unavailable" in body
|
|
364
|
+
):
|
|
365
|
+
status, _, body = self._transport(
|
|
366
|
+
"POST",
|
|
367
|
+
f"{self.base_url}/v1.0/removebg",
|
|
368
|
+
{
|
|
369
|
+
"X-Api-Key": self.api_key,
|
|
370
|
+
"Idempotency-Key": f"pysdk-{uuid.uuid4()}",
|
|
371
|
+
"Content-Type": mp_ct,
|
|
372
|
+
},
|
|
373
|
+
mp_body,
|
|
374
|
+
timeout,
|
|
375
|
+
)
|
|
376
|
+
if status == 413 and img_bytes is not None:
|
|
377
|
+
return _via_ticket("fallback413")
|
|
378
|
+
if status < 200 or status >= 300:
|
|
379
|
+
raise RemoveAcError(status, body.decode("utf-8", errors="replace"))
|
|
380
|
+
return body
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
class RemoveBg:
|
|
384
|
+
"""1:1 drop-in replacement for brilam/remove-bg Python package."""
|
|
385
|
+
|
|
386
|
+
def __init__(
|
|
387
|
+
self,
|
|
388
|
+
api_key: str,
|
|
389
|
+
error_log_file: Optional[str] = None,
|
|
390
|
+
base_url: str = DEFAULT_BASE_URL,
|
|
391
|
+
transport: Optional[TransportFn] = None,
|
|
392
|
+
):
|
|
393
|
+
self.client = RemoveAcClient(api_key=api_key, base_url=base_url, transport=transport)
|
|
394
|
+
self.error_log_file = error_log_file
|
|
395
|
+
|
|
396
|
+
def remove_background_from_img_file(
|
|
397
|
+
self,
|
|
398
|
+
img_file_path: str,
|
|
399
|
+
size: str = "regular",
|
|
400
|
+
new_file_name: str = "no-bg.png",
|
|
401
|
+
**kwargs: Any,
|
|
402
|
+
) -> bytes:
|
|
403
|
+
out = self.client.remove_background(img_file_path, size=size, **kwargs)
|
|
404
|
+
if new_file_name:
|
|
405
|
+
with open(new_file_name, "wb") as fh:
|
|
406
|
+
fh.write(out)
|
|
407
|
+
return out
|
|
408
|
+
|
|
409
|
+
def remove_background_from_img_url(
|
|
410
|
+
self,
|
|
411
|
+
img_url: str,
|
|
412
|
+
size: str = "regular",
|
|
413
|
+
new_file_name: str = "no-bg.png",
|
|
414
|
+
**kwargs: Any,
|
|
415
|
+
) -> bytes:
|
|
416
|
+
out = self.client.remove_background(img_url, size=size, **kwargs)
|
|
417
|
+
if new_file_name:
|
|
418
|
+
with open(new_file_name, "wb") as fh:
|
|
419
|
+
fh.write(out)
|
|
420
|
+
return out
|
|
421
|
+
|
|
422
|
+
def remove_background_from_base64_img(
|
|
423
|
+
self,
|
|
424
|
+
base64_img: str,
|
|
425
|
+
size: str = "regular",
|
|
426
|
+
new_file_name: str = "no-bg.png",
|
|
427
|
+
**kwargs: Any,
|
|
428
|
+
) -> bytes:
|
|
429
|
+
out = self.client.remove_background(base64.b64decode(base64_img), size=size, **kwargs)
|
|
430
|
+
if new_file_name:
|
|
431
|
+
with open(new_file_name, "wb") as fh:
|
|
432
|
+
fh.write(out)
|
|
433
|
+
return out
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: remove-ac
|
|
3
|
+
Version: 1.1.0
|
|
4
|
+
Summary: Official zero-dependency Python SDK & removebg 1:1 drop-in replacement for remove.ac
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://remove.ac
|
|
7
|
+
Project-URL: Documentation, https://remove.ac/api-info
|
|
8
|
+
Project-URL: Benchmarks, https://remove.ac/benchmarks
|
|
9
|
+
Project-URL: Repository, https://github.com/szp2005/remove-ac-sdk
|
|
10
|
+
Keywords: remove.ac,remove.bg,background-removal,matting,image-segmentation
|
|
11
|
+
Requires-Python: >=3.8
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
# remove.ac Official SDKs, CLI & MCP Server (Node.js · Python · MCP · Drop-in `remove.bg` Migration)
|
|
17
|
+
|
|
18
|
+
Zero-dependency official developer SDKs, hot-folder CLI, and Model Context Protocol (`MCP`) server for **[remove.ac](https://remove.ac)** — the dual-mode background removal platform pairing a free on-device WebAssembly studio with a `remove.bg`-compatible Cloud GPU REST API (`POST https://remove.ac/v1.0/removebg`).
|
|
19
|
+
|
|
20
|
+
- **Live Web Workbench (Free On-Device WASM + Batch 50 ZIP):** https://remove.ac
|
|
21
|
+
- **1-Line `remove.bg` API Migration Guide (Dec 1, 2026 Shutdown):** https://remove.ac/guides/remove-bg-api-migration
|
|
22
|
+
- **Interactive API Sandbox & Docs:** https://remove.ac/api-info
|
|
23
|
+
- **Figma, Photoshop UXP, GIMP, Sketch & Make/n8n Integrations:** https://remove.ac/plugins
|
|
24
|
+
- **Open Inference & Pricing Dataset (`CC BY 4.0`):** https://remove.ac/benchmarks
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Key Capabilities
|
|
29
|
+
|
|
30
|
+
- **1:1 `remove.bg` v1.0 Field Compatibility:** Swap `https://api.remove.bg/v1.0/removebg` to `https://remove.ac/v1.0/removebg` using standard `X-Api-Key: rac_live_...` headers and form fields (`image_file`, `image_file_b64`, `image_url`, `size`, `format`, `channels`, `type`, `roi`, `crop`, `crop_margin`, `scale`, `position`, `bg_color`, `bg_image_url`, `add_shadow`, `shadow_type`, `shadow_opacity`, `semitransparency`).
|
|
31
|
+
- **Up to 25 MB / 40 MP Zero-Disk Direct GPU Streaming:** Payloads above 4 MB automatically negotiate a short-lived HMAC ticket (`POST /v1.0/removebg/ticket`) and stream directly to ephemeral GPU RAM without disk or database blob persistence.
|
|
32
|
+
- **Atomic Credit Holds (`Idempotency-Key`):** Upstream timeouts or errors automatically release held credits with zero charge. Every verified account starts with **1 free credit**; pay-as-you-go credit packs never expire.
|
|
33
|
+
- **Zero External Runtime Dependencies:** The Node.js 18+ SDK, CLI, MCP stdio server, and Python 3.8+ SDK run purely on standard library primitives (`fetch`/`crypto` in Node, `urllib` in Python).
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 1. Quick `curl` Migration
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
curl -X POST https://remove.ac/v1.0/removebg \
|
|
41
|
+
-H "X-Api-Key: rac_live_your_key_here" \
|
|
42
|
+
-F "image_file=@photo.jpg" \
|
|
43
|
+
-F "size=full" \
|
|
44
|
+
-o cutout.png
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 2. Node.js / TypeScript SDK (`Node 18+`)
|
|
50
|
+
|
|
51
|
+
### Install directly via GitHub
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npm install github:szp2005/remove-ac-sdk
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Usage
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import fs from "node:fs";
|
|
61
|
+
import { RemoveAcClient } from "remove-ac";
|
|
62
|
+
|
|
63
|
+
const client = new RemoveAcClient({
|
|
64
|
+
apiKey: process.env.REMOVE_AC_API_KEY, // rac_live_...
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Files > 4 MB (up to 25 MB) automatically use /v1.0/removebg/ticket zero-disk GPU streaming
|
|
68
|
+
const result = await client.removeBackground("./studio-shot-18mb.png", {
|
|
69
|
+
size: "full",
|
|
70
|
+
format: "png",
|
|
71
|
+
shadowType: "contact",
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
fs.writeFileSync("./studio-shot-cutout.png", result.buffer);
|
|
75
|
+
console.log(`Saved ${result.width}x${result.height} (${result.creditsCharged} credit)`);
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## 3. CLI & `--watch` Hot-Folder Batch Tool
|
|
81
|
+
|
|
82
|
+
Run directly with `npx` or after installing:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
export REMOVE_AC_API_KEY="rac_live_..."
|
|
86
|
+
|
|
87
|
+
# Process an entire folder with 4 parallel workers
|
|
88
|
+
npx github:szp2005/remove-ac-sdk ./input-photos ./output-pngs --concurrency=4 --size=full
|
|
89
|
+
|
|
90
|
+
# Watch a hot folder continuously and output transparent WebP cutouts
|
|
91
|
+
npx github:szp2005/remove-ac-sdk ./incoming ./cutouts --watch --format=webp --crop
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## 4. Python SDK (`Python 3.8+`, Zero External Dependencies)
|
|
97
|
+
|
|
98
|
+
### Install directly via GitHub
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
pip install git+https://github.com/szp2005/remove-ac-sdk.git
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Native Client
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
from remove_ac import RemoveAcClient
|
|
108
|
+
|
|
109
|
+
client = RemoveAcClient() # reads REMOVE_AC_API_KEY
|
|
110
|
+
png_bytes = client.remove_background(
|
|
111
|
+
"product.jpg",
|
|
112
|
+
size="full",
|
|
113
|
+
shadow_type="contact",
|
|
114
|
+
)
|
|
115
|
+
with open("product-nobg.png", "wb") as fh:
|
|
116
|
+
fh.write(png_bytes)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### 1:1 `removebg` Drop-In Replacement Shim
|
|
120
|
+
|
|
121
|
+
Existing scripts written for `from removebg import RemoveBg` work without rewriting call sites:
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
from removebg import RemoveBg
|
|
125
|
+
|
|
126
|
+
rmbg = RemoveBg("rac_live_your_key_here")
|
|
127
|
+
rmbg.remove_background_from_img_file("car.jpg", size="full", new_file_name="car-cutout.png")
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## 5. MCP Server (`Claude Desktop` · `Cursor` · `Windsurf` · `Cline`)
|
|
133
|
+
|
|
134
|
+
`remove-ac` includes a zero-dependency Model Context Protocol (`MCP`) stdio server (`remove-ac-mcp`) exposing three tools to AI coding and design agents:
|
|
135
|
+
|
|
136
|
+
- `remove_background_file` — remove background from any local `.png` / `.jpg` / `.webp` up to `25 MB / 40 MP` (auto-streams `>4 MB` via `/v1.0/removebg/ticket`)
|
|
137
|
+
- `remove_background_url` — remove background from a public `https://` image URL and save locally
|
|
138
|
+
- `get_account_balance` — inspect remaining `remove.ac` API credits
|
|
139
|
+
|
|
140
|
+
### Claude Desktop / Cursor Configuration
|
|
141
|
+
|
|
142
|
+
```json
|
|
143
|
+
{
|
|
144
|
+
"mcpServers": {
|
|
145
|
+
"remove-ac": {
|
|
146
|
+
"command": "npx",
|
|
147
|
+
"args": ["-y", "-p", "github:szp2005/remove-ac-sdk", "remove-ac-mcp"],
|
|
148
|
+
"env": {
|
|
149
|
+
"REMOVE_AC_API_KEY": "rac_live_your_key_here"
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## License
|
|
159
|
+
|
|
160
|
+
MIT © [remove.ac](https://remove.ac)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
removebg.py
|
|
5
|
+
remove_ac/__init__.py
|
|
6
|
+
remove_ac/client.py
|
|
7
|
+
remove_ac.egg-info/PKG-INFO
|
|
8
|
+
remove_ac.egg-info/SOURCES.txt
|
|
9
|
+
remove_ac.egg-info/dependency_links.txt
|
|
10
|
+
remove_ac.egg-info/top_level.txt
|
|
11
|
+
tests/test_python_sdk.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import json
|
|
3
|
+
import unittest
|
|
4
|
+
|
|
5
|
+
from remove_ac import (
|
|
6
|
+
RemoveAcClient,
|
|
7
|
+
RemoveAcError,
|
|
8
|
+
RemoveBg,
|
|
9
|
+
derive_sub_idempotency_key,
|
|
10
|
+
parse_image_dimensions,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class PythonSdkTest(unittest.TestCase):
|
|
15
|
+
def test_standard_removebg_call(self):
|
|
16
|
+
calls = []
|
|
17
|
+
|
|
18
|
+
def fake_transport(method, url, headers, body, timeout):
|
|
19
|
+
calls.append((method, url, headers, body))
|
|
20
|
+
return 200, {"content-type": "image/png"}, b"\x89PNG\r\n"
|
|
21
|
+
|
|
22
|
+
client = RemoveAcClient(api_key="rac_live_test_example", transport=fake_transport)
|
|
23
|
+
out = client.remove_background(b"abc", size="full", shadow_type="contact")
|
|
24
|
+
self.assertEqual(out, b"\x89PNG\r\n")
|
|
25
|
+
self.assertEqual(len(calls), 1)
|
|
26
|
+
self.assertEqual(calls[0][1], "https://remove.ac/v1.0/removebg")
|
|
27
|
+
self.assertEqual(calls[0][2]["X-Api-Key"], "rac_live_test_example")
|
|
28
|
+
|
|
29
|
+
def test_large_file_uses_direct_ticket(self):
|
|
30
|
+
calls = []
|
|
31
|
+
big = b"x" * (4 * 1024 * 1024 + 16)
|
|
32
|
+
|
|
33
|
+
def fake_transport(method, url, headers, body, timeout):
|
|
34
|
+
calls.append((method, url, headers))
|
|
35
|
+
if url.endswith("/v1.0/removebg/ticket"):
|
|
36
|
+
payload = json.dumps(
|
|
37
|
+
{
|
|
38
|
+
"data": {
|
|
39
|
+
"ticket": "tkt_123",
|
|
40
|
+
"directEndpoint": "https://gpu.example.invalid/v1.0/direct-cutout",
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
).encode("utf-8")
|
|
44
|
+
return 200, {"content-type": "application/json"}, payload
|
|
45
|
+
return 200, {"content-type": "image/png"}, b"\x89PNG_DIRECT"
|
|
46
|
+
|
|
47
|
+
client = RemoveAcClient(api_key="rac_live_test_example", transport=fake_transport)
|
|
48
|
+
out = client.remove_background(big, size="full")
|
|
49
|
+
self.assertEqual(out, b"\x89PNG_DIRECT")
|
|
50
|
+
self.assertEqual(len(calls), 2)
|
|
51
|
+
self.assertTrue(calls[0][1].endswith("/v1.0/removebg/ticket"))
|
|
52
|
+
self.assertEqual(calls[1][1], "https://gpu.example.invalid/v1.0/direct-cutout")
|
|
53
|
+
self.assertEqual(calls[1][2]["Authorization"], "Bearer tkt_123")
|
|
54
|
+
|
|
55
|
+
def test_removebg_drop_in_shim_and_missing_key(self):
|
|
56
|
+
with self.assertRaises(RemoveAcError):
|
|
57
|
+
RemoveAcClient(api_key="").remove_background(b"abc")
|
|
58
|
+
|
|
59
|
+
rmbg = RemoveBg(
|
|
60
|
+
"rac_live_test_example",
|
|
61
|
+
transport=lambda m, u, h, b, t: (200, {"content-type": "image/png"}, b"OK"),
|
|
62
|
+
)
|
|
63
|
+
res = rmbg.remove_background_from_base64_img(
|
|
64
|
+
base64.b64encode(b"raw").decode("ascii"),
|
|
65
|
+
new_file_name="",
|
|
66
|
+
)
|
|
67
|
+
self.assertEqual(res, b"OK")
|
|
68
|
+
|
|
69
|
+
def test_high_mp_small_file_preflights_via_ticket(self):
|
|
70
|
+
# Minimal JPEG with SOF0 6000x4000 (24 MP)
|
|
71
|
+
jpeg_24mp = (
|
|
72
|
+
b"\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00"
|
|
73
|
+
b"\xff\xc0\x00\x11\x08\x0f\xa0\x17\x70\x03\x01\x11\x00\x02\x11\x00\x03\x11\x00"
|
|
74
|
+
)
|
|
75
|
+
calls = []
|
|
76
|
+
|
|
77
|
+
def fake_transport(method, url, headers, body, timeout):
|
|
78
|
+
calls.append(url)
|
|
79
|
+
if url.endswith("/v1.0/removebg/ticket"):
|
|
80
|
+
payload = json.dumps(
|
|
81
|
+
{
|
|
82
|
+
"data": {
|
|
83
|
+
"ticket": "tkt_py_24mp",
|
|
84
|
+
"directEndpoint": "https://gpu.example.invalid/v1.0/direct-cutout",
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
).encode("utf-8")
|
|
88
|
+
return 200, {"content-type": "application/json"}, payload
|
|
89
|
+
return 200, {"content-type": "image/png"}, b"DIRECT_PY_PNG"
|
|
90
|
+
|
|
91
|
+
client = RemoveAcClient(api_key="rac_live_test_example", transport=fake_transport)
|
|
92
|
+
out = client.remove_background(jpeg_24mp, size="auto", format="png")
|
|
93
|
+
self.assertEqual(out, b"DIRECT_PY_PNG")
|
|
94
|
+
self.assertEqual(
|
|
95
|
+
calls,
|
|
96
|
+
[
|
|
97
|
+
"https://remove.ac/v1.0/removebg/ticket",
|
|
98
|
+
"https://gpu.example.invalid/v1.0/direct-cutout",
|
|
99
|
+
],
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
def test_long_idempotency_key_stays_under_255_chars_on_ticket(self):
|
|
103
|
+
long_250 = "p" * 250
|
|
104
|
+
derived = derive_sub_idempotency_key(long_250, "fallback413")
|
|
105
|
+
self.assertLessEqual(len(derived), 255)
|
|
106
|
+
with self.assertRaises(RemoveAcError):
|
|
107
|
+
derive_sub_idempotency_key("p" * 256, "direct")
|
|
108
|
+
self.assertIsNone(
|
|
109
|
+
parse_image_dimensions(b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\xff\xff\xff\xff\x00\x00\x01\x00")
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
seen_headers = []
|
|
113
|
+
|
|
114
|
+
def fake_transport(method, url, headers, body, timeout):
|
|
115
|
+
seen_headers.append((url, headers))
|
|
116
|
+
if url.endswith("/v1.0/removebg"):
|
|
117
|
+
return 413, {"content-type": "application/json"}, b'{"errors":[{"code":"result_too_large"}]}'
|
|
118
|
+
if url.endswith("/v1.0/removebg/ticket"):
|
|
119
|
+
payload = json.dumps(
|
|
120
|
+
{
|
|
121
|
+
"data": {
|
|
122
|
+
"ticket": "tkt_long_idem",
|
|
123
|
+
"directEndpoint": "https://gpu.example.invalid/v1.0/direct-cutout",
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
).encode("utf-8")
|
|
127
|
+
return 200, {"content-type": "application/json"}, payload
|
|
128
|
+
return 200, {"content-type": "image/png"}, b"OK_FALLBACK"
|
|
129
|
+
|
|
130
|
+
client = RemoveAcClient(api_key="rac_live_test_example", transport=fake_transport)
|
|
131
|
+
res = client.remove_background(b"small_input", idempotency_key=long_250)
|
|
132
|
+
self.assertEqual(res, b"OK_FALLBACK")
|
|
133
|
+
ticket_hdr = seen_headers[1][1]["Idempotency-Key"]
|
|
134
|
+
self.assertLessEqual(len(ticket_hdr), 255)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
if __name__ == "__main__":
|
|
138
|
+
unittest.main()
|