verda-watermark 0.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.
@@ -0,0 +1,116 @@
1
+ Metadata-Version: 2.4
2
+ Name: verda-watermark
3
+ Version: 0.1.0
4
+ Summary: Python SDK for the Verda Enterprise Watermarking API
5
+ Author-email: Verda AI <support@verda.ai>
6
+ License: MIT
7
+ Project-URL: Homepage, https://verda.ai
8
+ Project-URL: Documentation, https://enterprise.verda.ai/api-docs
9
+ Project-URL: Repository, https://github.com/VerdaAI/sdk-python
10
+ Keywords: watermark,watermarking,content-protection,verda,ai
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Multimedia
21
+ Classifier: Topic :: Security
22
+ Requires-Python: >=3.9
23
+ Description-Content-Type: text/markdown
24
+ Requires-Dist: requests>=2.28.0
25
+
26
+ # Verda Python SDK
27
+
28
+ Python SDK for the [Verda Enterprise Watermarking API](https://enterprise.verda.ai/api-docs).
29
+
30
+ Embed imperceptible watermarks into images, videos, and audio. Decode watermarks to trace content back to its source.
31
+
32
+ ## Install
33
+
34
+ ```bash
35
+ pip install verda
36
+ ```
37
+
38
+ ## Quick Start
39
+
40
+ ```python
41
+ from verda import VerdaClient
42
+
43
+ client = VerdaClient(api_key="vk_...")
44
+
45
+ # Encode a watermark into a local file
46
+ result = client.encode("photo.jpg")
47
+ print(result.watermark_id) # 40-bit watermark identifier
48
+ print(result.download_url) # Download the watermarked file
49
+
50
+ # Encode from a URL
51
+ result = client.encode(url="https://cdn.example.com/video.mp4")
52
+
53
+ # Decode / verify a watermark
54
+ result = client.decode("suspect.jpg")
55
+ print(result.match) # True if watermark found
56
+ print(result.watermark_id) # Original watermark ID
57
+
58
+ # Check credits
59
+ credits = client.credits()
60
+ print(f"${credits.balance_dollars:.2f} remaining")
61
+ ```
62
+
63
+ ## Configuration
64
+
65
+ ```python
66
+ client = VerdaClient(
67
+ api_key="vk_...", # or set VERDA_API_KEY env var
68
+ base_url="https://api.verda.ai/api/v2/enterprise", # default
69
+ poll_interval=5, # seconds between job polls
70
+ poll_timeout=300, # max seconds to wait for a job
71
+ )
72
+ ```
73
+
74
+ ## Async Jobs
75
+
76
+ By default, `encode()` and `decode()` wait for the job to complete. For long-running jobs (video), you can use `wait=False`:
77
+
78
+ ```python
79
+ # Start encode without waiting
80
+ job_id = client.encode("long_video.mp4", wait=False)
81
+
82
+ # Check status later
83
+ job = client.get_job(job_id)
84
+ print(job.status) # QUEUED, PROCESSING, COMPLETED, or FAILED
85
+
86
+ # Or poll manually
87
+ import time
88
+ while True:
89
+ job = client.get_job(job_id)
90
+ if job.status in ("COMPLETED", "FAILED"):
91
+ break
92
+ time.sleep(10)
93
+ ```
94
+
95
+ ## Supported Formats
96
+
97
+ | Type | Formats |
98
+ |------|---------|
99
+ | Image | jpg, png, webp, bmp, gif, tiff, heic |
100
+ | Video | mp4, mov, avi, mkv, webm |
101
+ | Audio | mp3, wav, flac, aac, ogg, m4a |
102
+
103
+ ## Pricing
104
+
105
+ | Operation | Price | Free Tier |
106
+ |-----------|-------|-----------|
107
+ | Image encode | $0.005/file | 100/month |
108
+ | Audio encode | $0.01/minute | 10 min/month |
109
+ | Video encode | $0.10/minute | 10 min/month |
110
+ | Image decode | $0.001/file | 1,000/month |
111
+ | Audio decode | $0.001/minute | 1,000/month |
112
+ | Video decode | $0.001/minute | 1,000/month |
113
+
114
+ ## License
115
+
116
+ MIT
@@ -0,0 +1,91 @@
1
+ # Verda Python SDK
2
+
3
+ Python SDK for the [Verda Enterprise Watermarking API](https://enterprise.verda.ai/api-docs).
4
+
5
+ Embed imperceptible watermarks into images, videos, and audio. Decode watermarks to trace content back to its source.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ pip install verda
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ```python
16
+ from verda import VerdaClient
17
+
18
+ client = VerdaClient(api_key="vk_...")
19
+
20
+ # Encode a watermark into a local file
21
+ result = client.encode("photo.jpg")
22
+ print(result.watermark_id) # 40-bit watermark identifier
23
+ print(result.download_url) # Download the watermarked file
24
+
25
+ # Encode from a URL
26
+ result = client.encode(url="https://cdn.example.com/video.mp4")
27
+
28
+ # Decode / verify a watermark
29
+ result = client.decode("suspect.jpg")
30
+ print(result.match) # True if watermark found
31
+ print(result.watermark_id) # Original watermark ID
32
+
33
+ # Check credits
34
+ credits = client.credits()
35
+ print(f"${credits.balance_dollars:.2f} remaining")
36
+ ```
37
+
38
+ ## Configuration
39
+
40
+ ```python
41
+ client = VerdaClient(
42
+ api_key="vk_...", # or set VERDA_API_KEY env var
43
+ base_url="https://api.verda.ai/api/v2/enterprise", # default
44
+ poll_interval=5, # seconds between job polls
45
+ poll_timeout=300, # max seconds to wait for a job
46
+ )
47
+ ```
48
+
49
+ ## Async Jobs
50
+
51
+ By default, `encode()` and `decode()` wait for the job to complete. For long-running jobs (video), you can use `wait=False`:
52
+
53
+ ```python
54
+ # Start encode without waiting
55
+ job_id = client.encode("long_video.mp4", wait=False)
56
+
57
+ # Check status later
58
+ job = client.get_job(job_id)
59
+ print(job.status) # QUEUED, PROCESSING, COMPLETED, or FAILED
60
+
61
+ # Or poll manually
62
+ import time
63
+ while True:
64
+ job = client.get_job(job_id)
65
+ if job.status in ("COMPLETED", "FAILED"):
66
+ break
67
+ time.sleep(10)
68
+ ```
69
+
70
+ ## Supported Formats
71
+
72
+ | Type | Formats |
73
+ |------|---------|
74
+ | Image | jpg, png, webp, bmp, gif, tiff, heic |
75
+ | Video | mp4, mov, avi, mkv, webm |
76
+ | Audio | mp3, wav, flac, aac, ogg, m4a |
77
+
78
+ ## Pricing
79
+
80
+ | Operation | Price | Free Tier |
81
+ |-----------|-------|-----------|
82
+ | Image encode | $0.005/file | 100/month |
83
+ | Audio encode | $0.01/minute | 10 min/month |
84
+ | Video encode | $0.10/minute | 10 min/month |
85
+ | Image decode | $0.001/file | 1,000/month |
86
+ | Audio decode | $0.001/minute | 1,000/month |
87
+ | Video decode | $0.001/minute | 1,000/month |
88
+
89
+ ## License
90
+
91
+ MIT
@@ -0,0 +1,37 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "verda-watermark"
7
+ version = "0.1.0"
8
+ description = "Python SDK for the Verda Enterprise Watermarking API"
9
+ readme = "README.md"
10
+ license = {text = "MIT"}
11
+ requires-python = ">=3.9"
12
+ authors = [{name = "Verda AI", email = "support@verda.ai"}]
13
+ keywords = ["watermark", "watermarking", "content-protection", "verda", "ai"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.9",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ "Topic :: Multimedia",
25
+ "Topic :: Security",
26
+ ]
27
+ dependencies = [
28
+ "requests>=2.28.0",
29
+ ]
30
+
31
+ [project.urls]
32
+ Homepage = "https://verda.ai"
33
+ Documentation = "https://enterprise.verda.ai/api-docs"
34
+ Repository = "https://github.com/VerdaAI/sdk-python"
35
+
36
+ [tool.setuptools.packages.find]
37
+ include = ["verda*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,5 @@
1
+ from verda.client import VerdaClient
2
+ from verda.models import EncodeResult, DecodeResult, Job, CreditBalance
3
+
4
+ __version__ = "0.1.0"
5
+ __all__ = ["VerdaClient", "EncodeResult", "DecodeResult", "Job", "CreditBalance"]
@@ -0,0 +1,278 @@
1
+ import os
2
+ import time
3
+ import mimetypes
4
+ from pathlib import Path
5
+ from typing import Optional, Union
6
+
7
+ import requests
8
+
9
+ from verda.models import EncodeResult, DecodeResult, Job, CreditBalance
10
+
11
+ DEFAULT_BASE_URL = "https://api.verda.ai/api/v2/enterprise"
12
+ DEFAULT_TIMEOUT = 30
13
+ DEFAULT_POLL_INTERVAL = 5
14
+ DEFAULT_POLL_TIMEOUT = 300 # 5 minutes
15
+
16
+
17
+ class VerdaError(Exception):
18
+ def __init__(self, message: str, status_code: int = 0, error_code: str = ""):
19
+ self.message = message
20
+ self.status_code = status_code
21
+ self.error_code = error_code
22
+ super().__init__(message)
23
+
24
+
25
+ class InsufficientCreditsError(VerdaError):
26
+ pass
27
+
28
+
29
+ class VerdaClient:
30
+ """Verda Enterprise API client for watermarking media.
31
+
32
+ Usage:
33
+ client = VerdaClient(api_key="vk_...")
34
+
35
+ # Encode a local file (uploads + encodes + waits for result)
36
+ result = client.encode("photo.jpg")
37
+ print(result.watermark_id)
38
+ print(result.download_url)
39
+
40
+ # Encode from a URL
41
+ result = client.encode(url="https://cdn.example.com/image.png")
42
+
43
+ # Decode / verify
44
+ result = client.decode("suspect.jpg")
45
+ print(result.match)
46
+ print(result.watermark_id)
47
+
48
+ # Check credits
49
+ credits = client.credits()
50
+ print(f"${credits.balance_dollars:.4f} remaining")
51
+ """
52
+
53
+ def __init__(
54
+ self,
55
+ api_key: Optional[str] = None,
56
+ base_url: str = DEFAULT_BASE_URL,
57
+ timeout: int = DEFAULT_TIMEOUT,
58
+ poll_interval: int = DEFAULT_POLL_INTERVAL,
59
+ poll_timeout: int = DEFAULT_POLL_TIMEOUT,
60
+ ):
61
+ self.api_key = api_key or os.environ.get("VERDA_API_KEY", "")
62
+ if not self.api_key:
63
+ raise VerdaError("API key required. Pass api_key= or set VERDA_API_KEY env var.")
64
+ self.base_url = base_url.rstrip("/")
65
+ self.timeout = timeout
66
+ self.poll_interval = poll_interval
67
+ self.poll_timeout = poll_timeout
68
+ self._session = requests.Session()
69
+ self._session.headers.update({
70
+ "X-API-Key": self.api_key,
71
+ "User-Agent": "verda-python/0.1.0",
72
+ })
73
+
74
+ def _request(self, method: str, path: str, **kwargs) -> dict:
75
+ kwargs.setdefault("timeout", self.timeout)
76
+ resp = self._session.request(method, f"{self.base_url}{path}", **kwargs)
77
+
78
+ if resp.status_code == 402:
79
+ raise InsufficientCreditsError("Insufficient credits", status_code=402)
80
+
81
+ if resp.status_code >= 400:
82
+ body = {}
83
+ try:
84
+ body = resp.json()
85
+ except Exception:
86
+ pass
87
+ msg = body.get("message", body.get("error", f"HTTP {resp.status_code}"))
88
+ raise VerdaError(msg, status_code=resp.status_code, error_code=body.get("error_code", ""))
89
+
90
+ data = resp.json()
91
+ return data.get("data", data)
92
+
93
+ def _get(self, path: str, **kwargs) -> dict:
94
+ return self._request("GET", path, **kwargs)
95
+
96
+ def _post(self, path: str, **kwargs) -> dict:
97
+ return self._request("POST", path, **kwargs)
98
+
99
+ def _delete(self, path: str, **kwargs) -> dict:
100
+ return self._request("DELETE", path, **kwargs)
101
+
102
+ # --- Upload ---
103
+
104
+ def _upload_file(self, file_path: str) -> tuple[str, str]:
105
+ """Upload a local file to Verda S3 and return (content_id, file_format)."""
106
+ p = Path(file_path)
107
+ if not p.exists():
108
+ raise VerdaError(f"File not found: {file_path}")
109
+
110
+ file_format = p.suffix.lstrip(".").lower()
111
+ content_type = _guess_content_type(file_format)
112
+
113
+ resp = self._get("/watermark/upload-url", params={
114
+ "filename": p.name,
115
+ "content_type": content_type,
116
+ "file_format": file_format,
117
+ })
118
+
119
+ upload_url = resp.get("upload_url", resp.get("uploadUrl", ""))
120
+ content_id = resp.get("content_id", resp.get("contentId", ""))
121
+
122
+ if not upload_url or not content_id:
123
+ raise VerdaError("Failed to get upload URL")
124
+
125
+ mime_type = mimetypes.guess_type(file_path)[0] or "application/octet-stream"
126
+ with open(file_path, "rb") as f:
127
+ put_resp = requests.put(upload_url, data=f, headers={"Content-Type": mime_type}, timeout=120)
128
+ if put_resp.status_code >= 400:
129
+ raise VerdaError(f"Upload failed: HTTP {put_resp.status_code}")
130
+
131
+ return content_id, file_format
132
+
133
+ # --- Jobs ---
134
+
135
+ def get_job(self, job_id: str) -> Job:
136
+ """Get the status of a watermark job."""
137
+ resp = self._get(f"/watermark/jobs/{job_id}")
138
+ job_data = resp.get("job", resp)
139
+ return Job.from_dict(job_data)
140
+
141
+ def _wait_for_job(self, job_id: str) -> Job:
142
+ """Poll a job until it completes or fails."""
143
+ deadline = time.time() + self.poll_timeout
144
+ while time.time() < deadline:
145
+ job = self.get_job(job_id)
146
+ if job.status in ("COMPLETED", "FAILED"):
147
+ return job
148
+ time.sleep(self.poll_interval)
149
+ raise VerdaError(f"Job {job_id} timed out after {self.poll_timeout}s")
150
+
151
+ # --- Encode ---
152
+
153
+ def encode(
154
+ self,
155
+ file: Optional[str] = None,
156
+ url: Optional[str] = None,
157
+ content_type: Optional[str] = None,
158
+ file_format: Optional[str] = None,
159
+ wait: bool = True,
160
+ ) -> Union[EncodeResult, str]:
161
+ """Encode a watermark into media.
162
+
163
+ Args:
164
+ file: Path to a local file to watermark.
165
+ url: Public URL of media to watermark.
166
+ content_type: "image", "video", or "audio". Auto-detected from file extension if not provided.
167
+ file_format: File extension (e.g. "jpg", "mp4"). Auto-detected if not provided.
168
+ wait: If True (default), poll until the job completes and return the result.
169
+ If False, return the job_id immediately.
170
+
171
+ Returns:
172
+ EncodeResult if wait=True, job_id string if wait=False.
173
+ """
174
+ body = {}
175
+
176
+ if file:
177
+ content_id, fmt = self._upload_file(file)
178
+ body["content_id"] = content_id
179
+ body["file_format"] = file_format or fmt
180
+ body["content_type"] = content_type or _guess_content_type(fmt)
181
+ elif url:
182
+ body["media_url"] = url
183
+ if file_format:
184
+ body["file_format"] = file_format
185
+ else:
186
+ body["file_format"] = _format_from_url(url)
187
+ body["content_type"] = content_type or _guess_content_type(body["file_format"])
188
+ else:
189
+ raise VerdaError("Provide either file= or url=")
190
+
191
+ resp = self._post("/watermark/encode", json=body)
192
+ job_id = resp.get("job_id", resp.get("jobId", ""))
193
+
194
+ if not wait:
195
+ return job_id
196
+
197
+ job = self._wait_for_job(job_id)
198
+ if job.status == "FAILED":
199
+ raise VerdaError(f"Encode failed: {job.error_message}", error_code="ENCODE_FAILED")
200
+
201
+ return EncodeResult.from_job(job.__dict__)
202
+
203
+ # --- Decode ---
204
+
205
+ def decode(
206
+ self,
207
+ file: Optional[str] = None,
208
+ url: Optional[str] = None,
209
+ content_type: Optional[str] = None,
210
+ file_format: Optional[str] = None,
211
+ wait: bool = True,
212
+ ) -> Union[DecodeResult, str]:
213
+ """Decode / verify a watermark from media.
214
+
215
+ Args:
216
+ file: Path to a local file to check.
217
+ url: Public URL of media to check.
218
+ content_type: "image", "video", or "audio". Auto-detected if not provided.
219
+ file_format: File extension. Auto-detected if not provided.
220
+ wait: If True (default), poll until the job completes. If False, return job_id.
221
+
222
+ Returns:
223
+ DecodeResult if wait=True, job_id string if wait=False.
224
+ """
225
+ body = {}
226
+
227
+ if file:
228
+ content_id, fmt = self._upload_file(file)
229
+ body["content_id"] = content_id
230
+ body["file_format"] = file_format or fmt
231
+ body["content_type"] = content_type or _guess_content_type(fmt)
232
+ elif url:
233
+ body["media_url"] = url
234
+ if file_format:
235
+ body["file_format"] = file_format
236
+ else:
237
+ body["file_format"] = _format_from_url(url)
238
+ body["content_type"] = content_type or _guess_content_type(body["file_format"])
239
+ else:
240
+ raise VerdaError("Provide either file= or url=")
241
+
242
+ resp = self._post("/watermark/decode", json=body)
243
+ job_id = resp.get("job_id", resp.get("jobId", ""))
244
+
245
+ if not wait:
246
+ return job_id
247
+
248
+ job = self._wait_for_job(job_id)
249
+ if job.status == "FAILED":
250
+ raise VerdaError(f"Decode failed: {job.error_message}", error_code="DECODE_FAILED")
251
+
252
+ return DecodeResult.from_job(job.__dict__)
253
+
254
+ # --- Credits ---
255
+
256
+ def credits(self) -> CreditBalance:
257
+ """Check your credit balance."""
258
+ resp = self._get("/credits")
259
+ credit_data = resp.get("credits", resp)
260
+ return CreditBalance.from_dict(credit_data)
261
+
262
+
263
+ def _guess_content_type(file_format: str) -> str:
264
+ fmt = file_format.lower().strip(".")
265
+ if fmt in ("jpg", "jpeg", "png", "webp", "bmp", "gif", "tiff", "heic"):
266
+ return "image"
267
+ elif fmt in ("mp4", "mov", "avi", "mkv", "webm", "ts", "m4v"):
268
+ return "video"
269
+ elif fmt in ("mp3", "wav", "flac", "aac", "ogg", "m4a", "wma"):
270
+ return "audio"
271
+ return "image"
272
+
273
+
274
+ def _format_from_url(url: str) -> str:
275
+ path = url.split("?")[0].split("#")[0]
276
+ if "." in path:
277
+ return path.rsplit(".", 1)[-1].lower()
278
+ return "jpg"
@@ -0,0 +1,86 @@
1
+ from dataclasses import dataclass
2
+ from typing import Optional
3
+
4
+
5
+ @dataclass
6
+ class EncodeResult:
7
+ job_id: str
8
+ watermark_id: str
9
+ download_url: str
10
+ status: str
11
+
12
+ @classmethod
13
+ def from_job(cls, job: dict) -> "EncodeResult":
14
+ return cls(
15
+ job_id=job.get("job_id", ""),
16
+ watermark_id=job.get("watermark_id", ""),
17
+ download_url=job.get("download_url", ""),
18
+ status=job.get("status", ""),
19
+ )
20
+
21
+
22
+ @dataclass
23
+ class DecodeResult:
24
+ job_id: str
25
+ match: bool
26
+ watermark_id: Optional[str]
27
+ confidence: float
28
+ status: str
29
+
30
+ @classmethod
31
+ def from_job(cls, job: dict) -> "DecodeResult":
32
+ return cls(
33
+ job_id=job.get("job_id", ""),
34
+ match=job.get("match", False),
35
+ watermark_id=job.get("watermark_id") or None,
36
+ confidence=job.get("confidence", 0.0),
37
+ status=job.get("status", ""),
38
+ )
39
+
40
+
41
+ @dataclass
42
+ class Job:
43
+ job_id: str
44
+ job_type: str
45
+ status: str
46
+ watermark_id: Optional[str]
47
+ download_url: Optional[str]
48
+ match: bool
49
+ confidence: float
50
+ error_message: Optional[str]
51
+ created_at: int
52
+ completed_at: Optional[int]
53
+
54
+ @classmethod
55
+ def from_dict(cls, d: dict) -> "Job":
56
+ status_map = {0: "UNSPECIFIED", 1: "QUEUED", 2: "PROCESSING", 3: "COMPLETED", 4: "FAILED"}
57
+ raw_status = d.get("status", 0)
58
+ status = status_map.get(raw_status, str(raw_status)) if isinstance(raw_status, int) else raw_status
59
+ return cls(
60
+ job_id=d.get("job_id", ""),
61
+ job_type=d.get("job_type", ""),
62
+ status=status,
63
+ watermark_id=d.get("watermark_id") or None,
64
+ download_url=d.get("download_url") or None,
65
+ match=d.get("match", False),
66
+ confidence=d.get("confidence", 0.0),
67
+ error_message=d.get("error_message") or None,
68
+ created_at=d.get("created_at", 0),
69
+ completed_at=d.get("completed_at") or None,
70
+ )
71
+
72
+
73
+ @dataclass
74
+ class CreditBalance:
75
+ balance_microdollars: int
76
+ balance_dollars: float
77
+ free_tier_reset_at: int
78
+
79
+ @classmethod
80
+ def from_dict(cls, d: dict) -> "CreditBalance":
81
+ micros = d.get("balance_microdollars", d.get("balanceMicrodollars", 0))
82
+ return cls(
83
+ balance_microdollars=micros,
84
+ balance_dollars=micros / 1_000_000,
85
+ free_tier_reset_at=d.get("free_tier_reset_at", d.get("freeTierResetAt", 0)),
86
+ )
@@ -0,0 +1,116 @@
1
+ Metadata-Version: 2.4
2
+ Name: verda-watermark
3
+ Version: 0.1.0
4
+ Summary: Python SDK for the Verda Enterprise Watermarking API
5
+ Author-email: Verda AI <support@verda.ai>
6
+ License: MIT
7
+ Project-URL: Homepage, https://verda.ai
8
+ Project-URL: Documentation, https://enterprise.verda.ai/api-docs
9
+ Project-URL: Repository, https://github.com/VerdaAI/sdk-python
10
+ Keywords: watermark,watermarking,content-protection,verda,ai
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Multimedia
21
+ Classifier: Topic :: Security
22
+ Requires-Python: >=3.9
23
+ Description-Content-Type: text/markdown
24
+ Requires-Dist: requests>=2.28.0
25
+
26
+ # Verda Python SDK
27
+
28
+ Python SDK for the [Verda Enterprise Watermarking API](https://enterprise.verda.ai/api-docs).
29
+
30
+ Embed imperceptible watermarks into images, videos, and audio. Decode watermarks to trace content back to its source.
31
+
32
+ ## Install
33
+
34
+ ```bash
35
+ pip install verda
36
+ ```
37
+
38
+ ## Quick Start
39
+
40
+ ```python
41
+ from verda import VerdaClient
42
+
43
+ client = VerdaClient(api_key="vk_...")
44
+
45
+ # Encode a watermark into a local file
46
+ result = client.encode("photo.jpg")
47
+ print(result.watermark_id) # 40-bit watermark identifier
48
+ print(result.download_url) # Download the watermarked file
49
+
50
+ # Encode from a URL
51
+ result = client.encode(url="https://cdn.example.com/video.mp4")
52
+
53
+ # Decode / verify a watermark
54
+ result = client.decode("suspect.jpg")
55
+ print(result.match) # True if watermark found
56
+ print(result.watermark_id) # Original watermark ID
57
+
58
+ # Check credits
59
+ credits = client.credits()
60
+ print(f"${credits.balance_dollars:.2f} remaining")
61
+ ```
62
+
63
+ ## Configuration
64
+
65
+ ```python
66
+ client = VerdaClient(
67
+ api_key="vk_...", # or set VERDA_API_KEY env var
68
+ base_url="https://api.verda.ai/api/v2/enterprise", # default
69
+ poll_interval=5, # seconds between job polls
70
+ poll_timeout=300, # max seconds to wait for a job
71
+ )
72
+ ```
73
+
74
+ ## Async Jobs
75
+
76
+ By default, `encode()` and `decode()` wait for the job to complete. For long-running jobs (video), you can use `wait=False`:
77
+
78
+ ```python
79
+ # Start encode without waiting
80
+ job_id = client.encode("long_video.mp4", wait=False)
81
+
82
+ # Check status later
83
+ job = client.get_job(job_id)
84
+ print(job.status) # QUEUED, PROCESSING, COMPLETED, or FAILED
85
+
86
+ # Or poll manually
87
+ import time
88
+ while True:
89
+ job = client.get_job(job_id)
90
+ if job.status in ("COMPLETED", "FAILED"):
91
+ break
92
+ time.sleep(10)
93
+ ```
94
+
95
+ ## Supported Formats
96
+
97
+ | Type | Formats |
98
+ |------|---------|
99
+ | Image | jpg, png, webp, bmp, gif, tiff, heic |
100
+ | Video | mp4, mov, avi, mkv, webm |
101
+ | Audio | mp3, wav, flac, aac, ogg, m4a |
102
+
103
+ ## Pricing
104
+
105
+ | Operation | Price | Free Tier |
106
+ |-----------|-------|-----------|
107
+ | Image encode | $0.005/file | 100/month |
108
+ | Audio encode | $0.01/minute | 10 min/month |
109
+ | Video encode | $0.10/minute | 10 min/month |
110
+ | Image decode | $0.001/file | 1,000/month |
111
+ | Audio decode | $0.001/minute | 1,000/month |
112
+ | Video decode | $0.001/minute | 1,000/month |
113
+
114
+ ## License
115
+
116
+ MIT
@@ -0,0 +1,10 @@
1
+ README.md
2
+ pyproject.toml
3
+ verda/__init__.py
4
+ verda/client.py
5
+ verda/models.py
6
+ verda_watermark.egg-info/PKG-INFO
7
+ verda_watermark.egg-info/SOURCES.txt
8
+ verda_watermark.egg-info/dependency_links.txt
9
+ verda_watermark.egg-info/requires.txt
10
+ verda_watermark.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ requests>=2.28.0