
Byted Tos Image Process
- 31 installs
- 411 repo stars
- Updated August 4, 2026
- bytedance/agentkit-samples
byted-tos-image-process is a Claude skill that inspects, transforms, and watermarks images in Volcengine TOS and runs VLM-based image understanding.
About
This skill inspects and transforms images stored in Volcengine TOS. A developer uses it to read metadata, convert formats, resize and crop, draw markers, add visible or blind watermarks, and run AI image understanding such as OCR, description, face detection, and visual Q&A through a VLM. It builds TOS image-process strings and can persist results back to TOS.
- Inspects and transforms TOS-stored images: convert, resize, crop, watermark
- Embeds and extracts blind watermarks
- AI image understanding via VLM for OCR, description, and visual Q&A
Byted Tos Image Process by the numbers
- 31 all-time installs (skills.sh)
- Ranked #964 of 1,335 Generative Media skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
byted-tos-image-process capabilities & compatibility
Requires Volcengine TOS credentials; VLM and blind watermark need account whitelist/permission
- Capabilities
- image processing · watermarking · ocr · image understanding
- Use cases
- image generation · pdf parsing
- Pricing
- Bring your own API key
What byted-tos-image-process says it does
Inspects and transforms images stored in Volcengine TOS: read metadata, convert formats, resize, draw points and lines
AI-powered image understanding via VLM (doubao-seed-1.6-vision). Supports description, OCR, face detection, and visual Q&A
npx skills add https://github.com/bytedance/agentkit-samples --skill byted-tos-image-processAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 31 |
|---|---|
| repo stars | ★ 411 |
| Last updated | August 4, 2026 |
| Repository | bytedance/agentkit-samples ↗ |
What it does
Inspect, transform, watermark, and run AI understanding on images stored in Volcengine TOS.
Who is it for?
Skip if: Editing images with local desktop tooling outside TOS, video or document processing, or non-TOS storage providers.
When should I use this skill?
You need to get image info, convert or resize images, annotate or watermark them, extract blind watermarks, or run OCR/description/face detection on TOS images.
What you get
Runs TOS image-process operations and VLM understanding, saving results locally or back to TOS.
By the numbers
- Bundles 9 image scripts
- Blind watermark requires images at least 512x512 px
Files
Volcengine TOS Image Process
Inspect and transform images stored in Volcengine TOS — metadata, format conversion, resize, watermark, blind watermark, and AI-powered image understanding.
Setup (once per environment)
Install dependencies on first use:
cd {baseDir}
pip install -r {baseDir}/requirements.txtThen run scripts with Python 3.7+:
python3 {baseDir}/scripts/<script>.py <args>If you see a ModuleNotFoundError for tos, reinstall dependencies.
Environment Variables
This skill relies on the TOS identity declared in the metadata block. Common runtime variables are:
| Environment Variable | Required | Description |
|---|---|---|
TOS_ACCESS_KEY | Yes | TOS access key ID |
TOS_SECRET_KEY | Yes | TOS secret access key |
TOS_ENDPOINT | Yes | TOS endpoint URL |
TOS_REGION | Yes | TOS region |
TOS_BUCKET | Yes | Source bucket that stores the image |
TOS_OBJECT_KEY | No | Source object key of the image. Can be overridden with --key |
TOS_SECURITY_TOKEN | No | STS session token when using temporary credentials |
TOS_SAVEAS_BUCKET | No | Default target bucket for saving processed results |
TOS_SAVEAS_OBJECT_PREFIX | No | Default key prefix for saving processed results |
Quick start (common tasks)
# Read image metadata
python3 {baseDir}/scripts/image_info.py --key photo.jpg
# Convert to WebP
python3 {baseDir}/scripts/image_format.py --key photo.jpg --f webp --output converted.webp
# Resize to width 500
python3 {baseDir}/scripts/image_resize.py --key photo.jpg --w 500 --output resized.jpg
# Draw points and connecting lines
python3 {baseDir}/scripts/image_draw.py --key photo.jpg \
--points 50x50-200x120-320x220 --line --color FF0000 --output draw.jpg
# Zoom by resize + crop
python3 {baseDir}/scripts/image_zoom.py --key photo.jpg \
--resize-w 1200 --crop-w 500 --crop-h 400 --gravity center --output zoom.jpg
# Add visible text watermark
python3 {baseDir}/scripts/image_watermark.py --key photo.jpg \
--text "My Brand" --font fangzhengshusong --color FF0000 --size 72 \
--gravity center --output watermarked.jpg
# Embed blind watermark (requires ≥512×512 image and account permission)
python3 {baseDir}/scripts/image_blindwatermark.py --key photo.jpg \
--kv text=HelloBlind --output blind.jpg
# Run a custom process string
python3 {baseDir}/scripts/image_process.py --key photo.jpg \
--process "image/resize,w_300,h_300,m_fill" --output filled.jpg
# AI-powered image understanding (describe, OCR, face detection, etc.)
python3 {baseDir}/scripts/image_understanding.py --key photo.jpg \
--prompt "Describe this image in detail"
python3 {baseDir}/scripts/image_understanding.py --key document.png \
--prompt "识别图片中的所有文字内容"Available scripts
| Script | Purpose |
|---|---|
scripts/image_info.py | Read image metadata (format, dimensions, size). Falls back to local parsing when TOS returns raw bytes. |
scripts/image_format.py | Convert format (jpg, png, webp) with optional quality setting. |
scripts/image_resize.py | Resize by width/height/mode. |
scripts/image_draw.py | Draw points and optional connecting lines on an image with image/draw. |
scripts/image_zoom.py | Build agent-friendly zoom results by chaining image/resize and crop. |
scripts/image_watermark.py | Add visible text or image watermark with positioning, rotation, tiling, and opacity. |
scripts/image_blindwatermark.py | Embed blind watermark. Requires account-level permission and image ≥512×512 px. |
scripts/image_process.py | Pass any raw image/... process string. |
scripts/image_understanding.py | AI-powered image understanding via VLM (doubao-seed-1.6-vision). Supports description, OCR, face detection, and visual Q&A through natural language prompts. Requires account whitelist. |
All scripts support --key to override TOS_OBJECT_KEY, --output for local save, and --saveas-bucket/--saveas-object for TOS-to-TOS persistence. Most scripts also support --json for machine-readable output, and the process-building scripts support --dry-run to preview the resolved request. Run any script with -h for full usage.
Out of scope
- Editing images with local desktop tooling outside TOS.
- Video or document processing (use
byted-tos-video-processorbyted-tos-doc-process). - Non-TOS storage providers.
Rules
- Authentication: Authentication is provided by the TOS identity declared in the
metadatablock above. Object selection can be overridden per script with--key. - Parameter source of truth: The exact
processstring syntax is defined by official Volcengine TOS documentation. When uncertain, check REFERENCE.md. - Watermark encoding: Text and font parameters in
image/watermarkrequire URL-safe Base64 encoding. The watermark script handles this automatically when you pass--textand--font. - Blind watermark constraints: The source image must be at least 512×512 pixels, and the account must have the blind watermark capability enabled. If the capability is missing, the script exits with
[SKIP](use--strictto fail hard). - Image understanding: Uses
image/understandingwith thedoubao-seed-1.6-visionVLM model. The--promptparameter is required. Supports description, OCR, face detection, and any visual Q&A task. Requires account whitelist. Response time is typically 10-60 seconds. - Language: Reply in the user's preferred language.
Further reading
- Setup and environment: README.md
- Parameter reference: REFERENCE.md
- End-to-end workflows: WORKFLOWS.md
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Bytedance TOS Image Process Skill
This skill provides a clean, reusable image-processing toolkit for files stored in Bytedance TOS. It focuses on the Volcengine TOS image processing syntax and demonstrates practical ways to inspect, convert, resize, draw annotations, build zoom-style crops, watermark, and persist processed results.
When To Use
Use this skill when you need to:
- Read image metadata with
image/info - Convert formats such as
jpg,png, andwebp - Generate thumbnails or resize images
- Draw points or connecting lines on an image
- Produce zoom-like results with resize plus crop
- Apply visible text or image watermarks
- Embed or extract blind watermarks
- Run a custom
image/...process rule and save the result locally or back to TOS
Do not use this skill for:
- Video snapshot workflows
- Document preview or office conversion
- Generic storage operations that do not involve TOS image processing
How It Works
Image processing is performed by passing a formatted process string to the Volcengine TOS SDK. Common examples include:
image/infoimage/format,webp,q_80image/resize,w_500,m_lfitimage/watermark,...image/blindwatermark,...
The scripts in this skill package show how to construct these rules, execute them through the SDK, and handle local or TOS-based outputs.
Directory Layout
byted-tos-image-process/
├── SKILL.md
├── README.md
├── REFERENCE.md
├── WORKFLOWS.md
├── LICENSE
├── requirements.txt
└── scripts/
├── image_info.py
├── image_format.py
├── image_resize.py
├── image_draw.py
├── image_zoom.py
├── image_watermark.py
├── image_blindwatermark.py
├── image_understanding.py
└── image_process.pyRequirements
- Python 3.7+
- Access to Volcengine TOS
- Valid AK/SK or STS credentials
- Network access to the target TOS endpoint
Install dependencies:
pip install -r requirements.txtEnvironment Variables
| Variable | Required | Description | Example |
|---|---|---|---|
TOS_ACCESS_KEY | Yes | TOS access key ID. | AK... |
TOS_SECRET_KEY | Yes | TOS secret access key. | your-secret-key |
TOS_ENDPOINT | Yes | TOS endpoint URL. | https://tos-cn-beijing.volces.com |
TOS_REGION | Yes | TOS region. | cn-beijing |
TOS_BUCKET | Yes | Source bucket that stores the image. | my-image-bucket |
TOS_OBJECT_KEY | Yes | Source object key of the image. | input/photos/landscape.jpg |
TOS_SECURITY_TOKEN | No | STS session token when using temporary credentials. | STS... |
TOS_SAVEAS_BUCKET | No | Default target bucket for saving processed results. | my-output-bucket |
TOS_SAVEAS_OBJECT_PREFIX | No | Default key prefix for saving processed results. | processed/images/ |
For production usage, prefer short-lived STS credentials. The SDK automatically uses TOS_SECURITY_TOKEN when it is present.
Quick Start
Export the minimum required configuration:
export TOS_ACCESS_KEY="YOUR_AK"
export TOS_SECRET_KEY="YOUR_SK"
export TOS_ENDPOINT="https://tos-cn-beijing.volces.com"
export TOS_REGION="cn-beijing"
export TOS_BUCKET="your-image-bucket"
export TOS_OBJECT_KEY="path/to/your/image.jpg"Run one of the ready-to-use examples:
Read image metadata:
python3 scripts/image_info.pyIf the service returns raw image bytes instead of a JSON metadata payload, the script falls back to local parsing and prints basic information such as format, file size, width, and height.
Convert to WebP:
python3 scripts/image_format.py --f webp --q 80 --output converted.webpResize to width 500:
python3 scripts/image_resize.py --w 500 --output resized.jpgDraw points and lines:
python3 scripts/image_draw.py \
--points 50x50-200x120-320x220 \
--line \
--color FF0000 \
--output draw.jpgCreate a zoom-style crop:
python3 scripts/image_zoom.py \
--resize-w 1200 \
--crop-w 500 \
--crop-h 400 \
--gravity center \
--output zoom.jpgApply a visible watermark and save back to TOS:
python3 scripts/image_watermark.py \
--text "My Brand" \
--font fangzhengshusong \
--color FF0000 \
--size 72 \
--gravity se \
--x 20 \
--y 20 \
--saveas-bucket "your-output-bucket" \
--saveas-object "watermarked/image.jpg"If you already have official URL-safe Base64 values, use --text-b64, --font-b64, or --image-b64 directly. For image watermarks, pass the raw watermark object reference with --image, and the script will URL-safe-Base64 encode it for the image parameter.
Apply a blind watermark:
python3 scripts/image_blindwatermark.py \
--kv text=HelloBlind \
--output blindwatermarked.jpgBlind watermark prerequisites:
- The account/bucket must have blind watermark capability enabled in the TOS console.
- The source image must be at least 512×512 pixels.
If the capability is not enabled, the script prints [SKIP] and exits successfully by default. Use --strict to treat that as a hard failure. If the image is too small, a clear error message is printed.
Run a custom processing rule:
python3 scripts/image_process.py \
--process "image/resize,w_300,h_300,m_fill" \
--output filled_300x300.jpgDocument Roles
SKILL.md: trigger-oriented instructions for agents deciding whether to load this skillREADME.md: setup guide and runnable entry points for humans and agentsREFERENCE.md: parameter reference and result semanticsWORKFLOWS.md: common image-processing patternsscripts/: executable examples for common image-processing tasks
Usage Notes
- The exact
processstring syntax is defined by TOS and should be treated as authoritative. - Dedicated scripts expose common arguments directly, while advanced parameters can usually be passed with
--kv key=value. - The image scripts now consistently support
--bucket/--key,--outputfor local save,--saveas-bucket/--saveas-objectfor TOS persistence, and--jsonfor machine-readable output. Several scripts also support--dry-runto show the resolved request before calling TOS. image_draw.pyis useful for agent outputs such as marking detections, key points, or polygon-like paths usingimage/draw.image_zoom.pywraps the common "resize first, then crop the interesting region" pattern that agents often need for visual focus.- The watermark helper now follows the official parameter model:
text/type/color/size/shadow/rotate/fillfor text,imagefor image watermark,g/x/y/voffset/tfor base placement, andorder/align/intervalfor mixed watermark. - Saving results back to TOS is often more efficient than downloading locally for downstream workflows.
- Some environments may return raw image bytes for
image/info; the script prints fallback metadata in that case instead of failing.
Related Files
- Parameter reference: REFERENCE.md
- Workflow guide: WORKFLOWS.md
License
This skill is licensed under the Apache License 2.0. See LICENSE.
Bytedance TOS Image Process SDK Reference
This document provides a reference for the parameters and return values of the core image processing operations, as implemented via the Volcengine TOS Python SDK.
Crucial Note: The image processing capabilities of TOS are extensive. This document covers the high-level structure and common parameters. For an exhaustive list of all parameters, options, and their exact syntax (e.g., for watermarks, custom cuts), you must refer to the official Volcengine TOS image processing documentation.
Table of Contents
- Authentication
- Core Operations
- 1. `ImageInfo`
- 2. `ImageFormat`
- 3. `ImageResize`
- 4. `ImageDraw`
- 5. `ImageZoom`
- 6. `ImageWatermark`
- 7. `ImageBlindWatermark`
- 8. `ImageProcess` (Generic)
- 9. `ImageUnderstanding`
- Data Models
- ImageInfo Object
- ProcessSaveResult Object
---
Authentication
Authentication is handled automatically by the tos.TosClientV2 client during initialization. Credentials should be provided via environment variables as described in the README.md.
---
Core Operations
Image processing is invoked by passing a specially formatted process string to the get_object, get_object_to_file, or other relevant methods of the TOS SDK client.
1. ImageInfo
Retrieves metadata for a specified image object in TOS.
Process String: image/info
SDK Method: client.get_object()
Key Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
bucket | string | Yes | The name of the bucket containing the image. |
key | string | Yes | The full object key (path) of the image file. |
process | string | Yes | Must be the exact string "image/info". |
Success Response:
- In the ideal case, the SDK response body contains a
bytesobject with a JSON string. - In some environments,
image/infomay return raw image bytes instead of a JSON payload. The companion scriptscripts/image_info.pydetects that case and falls back to local parsing for basic fields such as format, width, height, and file size.
2. ImageFormat
Converts the image to a different format and/or adjusts its quality.
Process String: image/format,<format>,q_<quality>
Common Options:
| Option | SDK Equivalent | Description |
|---|---|---|
f | format (string) | Target format. The current service behavior maps this to a plain segment such as image/format,webp. Common values: jpg, png, webp. |
q | quality (int) | Quality for lossy formats (e.g., 1-100 for JPG). |
Example `process` string: "image/format,webp,q_85"
3. ImageResize
Resizes an image with various scaling options.
Process String: image/resize,w_<width>,h_<height>,m_<mode>
Common Options:
| Option | SDK Equivalent | Description |
|---|---|---|
w | width (int) | Target width in pixels. |
h | height (int) | Target height in pixels. |
m | mode (string) | Resize mode (e.g., lfit, mfit, fill, fixed). See official docs for all modes. |
Example `process` string: "image/resize,w_800,m_lfit" (Resize to width 800, maintain aspect ratio)
4. ImageDraw
Draws points and optional connecting lines directly on the image.
Process String: image/draw,p_<points>,r_<radius>,l_<true|false>,lw_<line_width>,color_<RRGGBB>
Common Options:
| Option | Meaning | Example |
|---|---|---|
p | Point list formatted as x1xy1-x2xy2-... | 50x50-200x120-320x220 |
r | Point radius in pixels | r_6 |
l | Whether to connect points with lines | l_true |
lw | Line width in pixels | lw_3 |
color | RGB hex color | color_FF0000 |
Example `process` string: "image/draw,p_50x50-200x120-320x220,r_6,l_true,lw_3,color_FF0000"
5. ImageZoom
This is a script-level pattern rather than a standalone server primitive. The helper composes:
1. image/resize,... 2. /crop,...
to create a zoom-like final image focused on a target region.
Example `process` string: "image/resize,w_1200,m_fill/crop,w_500,h_400,g_center"
6. ImageWatermark
Applies a visible watermark (text or image) to the image. The parameter set is extensive.
Process String: image/watermark,<param1>_<value1>,<param2>_<value2>,...
Conceptual Parameters (refer to official docs for actual keys and values):
text: Text watermark content. Value must be URL-safe Base64 encoded.type: Text watermark font. Value must be URL-safe Base64 encoded.color: Text color inRRGGBB.size: Text size in px.shadow: Shadow opacity in[0,100].rotate: Clockwise rotation angle in[0,360].fill: Whether to tile text watermark across the full image:0or1.image: Watermark image reference in the same bucket. Value must be URL-safe Base64 encoded. If preprocessing is needed, encode the full watermark reference string including?x-tos-process=....t: Watermark opacity in[0,100].g: Placement. Common values:nw,north,ne,west,center,east,sw,south,se.x/y: Horizontal and vertical margins in px.voffset: Vertical offset from the center line.order/align/interval: Mixed text+image watermark layout controls.
Example `process` string: "image/watermark,text_SGVsbG8,type_ZmFuZ3poZW5nc2h1c29uZw,color_FF0000,size_72,g_center,rotate_45"
7. ImageBlindWatermark
Adds a blind (invisible) watermark to an image.
Process String: image/blindwatermark,<param1>_<value1>,...
Conceptual Parameters (refer to official docs):
text: Watermark text content to embed.- Other parameters as documented by the official TOS blind watermark API.
Prerequisites:
- The blind watermark capability must be enabled at the account/bucket level in the TOS console.
- The source image must be at least 512×512 pixels.
Behavior Notes:
- If the capability is not enabled,
scripts/image_blindwatermark.pyprints[SKIP]and exits successfully by default. Use--strictto make this a hard failure. - If the image is smaller than 512×512, the script prints a clear error message with the actual image dimensions and the minimum requirement.
8. ImageProcess (Generic)
This is not a specific operation but a generic entry point to use any process string. It allows for combining operations or using newly introduced features not explicitly covered by the other scripts.
Example `process` string (chaining resize and format): "image/resize,w_500|image/format,png" (Syntax may vary, check official docs for chaining rules).
---
9. ImageUnderstanding
AI-powered image understanding via VLM (Vision Language Model). Supports description, OCR, face detection, and any visual Q&A through natural language prompts.
SDK Method: client.get_object() with request_timeout=120
Key Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
bucket | string | Yes | The bucket containing the image. |
key | string | Yes | The object key of the image. |
process | string | Yes | Constructed as image/understanding,m_<b64_model>,p_<b64_prompt> |
save_bucket | string | No | Base64-encoded destination bucket for saving result. |
save_object | string | No | Base64-encoded destination object key for saving result. |
Constructing the `process` Parameter:
The m (model) and p (prompt) values must be URL-Safe Base64 encoded with `=` padding removed:
import base64
model = "doubao-seed-1.6-vision"
prompt = "Describe this image"
model_b64 = base64.urlsafe_b64encode(model.encode()).decode().rstrip("=")
prompt_b64 = base64.urlsafe_b64encode(prompt.encode()).decode().rstrip("=")
process_str = f"image/understanding,m_{model_b64},p_{prompt_b64}"Optional `d` parameter: Detail level — auto, low, or high. Append ,d_high to the process string.
CLI Mapping:
| CLI argument | Meaning | Notes |
|---|---|---|
--key | Source image object key | Required |
--prompt | Natural language prompt | Required |
--model | VLM model name | Default doubao-seed-1.6-vision |
--detail | Detail level | auto/low/high; optional |
--output | Local output file | Optional |
--saveas-bucket | Save result to TOS bucket | Optional |
--saveas-object | Save result as TOS object key | Optional |
Response Format:
{
"content": "The image features a cute plush toy..."
}Important Notes:
- Response time is typically 10-60 seconds. Set
request_timeout=120on the client. - Requires account whitelist. If not whitelisted, returns
"The account: xxx is not in the whitelist." - Image must meet minimum size requirements (width/height/pixels).
Script: scripts/image_understanding.py
---
Data Models
ImageInfo Object
A JSON object containing detailed information about the image, when image/info returns structured metadata.
| Field | Type | Description |
|---|---|---|
Format | string | The format of the image (e.g., "jpeg", "png"). |
ImageWidth | int | The width of the image in pixels. |
ImageHeight | int | The height of the image in pixels. |
FileSize | int | The size of the image file in bytes. |
Orientation | int | The EXIF orientation tag. |
... | ... | Other fields may be present (e.g., EXIF data). |
If the service returns raw bytes instead of JSON, the helper script prints a fallback object shaped like:
{
"source": "fallback-local-parse",
"format": "jpeg",
"bytes": 214513,
"width": 640,
"height": 427
}Example Snippet:
{
"FileSize": {
"Value": "102400"
},
"Format": {
"Value": "jpeg"
},
"ImageHeight": {
"Value": "800"
},
"ImageWidth": {
"Value": "1200"
}
}ProcessSaveResult Object
A JSON object returned when an image processing operation is successfully saved directly to TOS using the save_bucket and save_object parameters.
| Field | Type | Description |
|---|---|---|
ETag | string | The ETag of the saved object. |
Bucket | string | The bucket where the result was saved. |
Object | string | The object key of the saved result. |
VersionId | string | The version ID if the bucket has versioning enabled. |
HashCrc64ecma | string | The CRC64 checksum of the object. |
tos
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#!/usr/bin/env python3
"""Example script: apply image blind watermark using TOS image processing.
Builds `process="image/blindwatermark,..."` and either:
- saves the processed image locally via `get_object_to_file` (default), or
- saves it back to TOS via `get_object(..., save_bucket=..., save_object=...)`.
Blind watermark parameters are subject to the official TOS documentation.
Pass parameters using repeated `--kv key=value`, and the script will append them
as `key_value` segments in the process string.
If blind watermark is not enabled for the current account or bucket, the script
prints `[SKIP]` and exits successfully by default. Use `--strict` to convert
that case into a hard failure.
Environment variables:
- TOS_ACCESS_KEY, TOS_SECRET_KEY, TOS_SECURITY_TOKEN(optional)
- TOS_ENDPOINT, TOS_REGION
- TOS_BUCKET, TOS_OBJECT_KEY
Note: Blind watermark requires account-level permission to be enabled.
The source image must be at least 512x512 pixels.
Parameter semantics are subject to the official TOS documentation.
"""
import argparse
import base64
import json
import os
import sys
from typing import Optional
import tos
from tos.exceptions import TosClientError, TosServerError
def get_env(name: str, required: bool = True, default: Optional[str] = None) -> str:
value = os.getenv(name, default)
if required and not value:
print(f"[ERROR] Environment variable {name} is required.", file=sys.stderr)
sys.exit(1)
return value # type: ignore[return-value]
def create_client() -> tos.TosClientV2:
ak = get_env("TOS_ACCESS_KEY")
sk = get_env("TOS_SECRET_KEY")
endpoint = get_env("TOS_ENDPOINT")
region = get_env("TOS_REGION")
security_token = os.getenv("TOS_SECURITY_TOKEN")
print(
f"[INFO] Initializing TOS client for endpoint={endpoint}, region={region} ..."
)
return tos.TosClientV2(
ak=ak,
sk=sk,
endpoint=endpoint,
region=region,
security_token=security_token,
)
def parse_kv_list(items: list[str]) -> list[tuple[str, str]]:
pairs: list[tuple[str, str]] = []
for item in items:
if "=" not in item:
raise ValueError(f"Invalid --kv '{item}', expected key=value")
k, v = item.split("=", 1)
k = k.strip()
v = v.strip()
if not k:
raise ValueError(f"Invalid --kv '{item}', key is empty")
pairs.append((k, v))
return pairs
def build_process(op: str, pairs: list[tuple[str, str]]) -> str:
base = f"image/{op}"
if not pairs:
return base
return base + "," + ",".join([f"{k}_{v}" for k, v in pairs])
def default_output_path(key: str) -> str:
base = os.path.basename(key)
if not base:
return "blindwatermarked_output"
return f"blindwatermarked_{base}"
def main() -> None:
parser = argparse.ArgumentParser(
description="Apply blind watermark via TOS process=image/blindwatermark"
)
parser.add_argument("--bucket", type=str, default=None, help="Override TOS_BUCKET")
parser.add_argument("--key", type=str, default=None, help="Override TOS_OBJECT_KEY")
parser.add_argument(
"--kv", action="append", default=[], help="Blind watermark option: key=value"
)
parser.add_argument("--output", type=str, default=None, help="Local output file")
parser.add_argument(
"--saveas-bucket", type=str, default=None, help="Save result to this bucket"
)
parser.add_argument(
"--saveas-object", type=str, default=None, help="Save result as this object key"
)
parser.add_argument(
"--strict",
action="store_true",
help="If set, treat AccessDenied (blind watermark not enabled) as a hard failure.",
)
parser.add_argument("--json", action="store_true", help="Print machine-readable JSON only")
parser.add_argument("--dry-run", action="store_true", help="Print resolved request and exit")
args = parser.parse_args()
if not args.kv:
print(
"[ERROR] At least one --kv key=value is required for blind watermark parameters.",
file=sys.stderr,
)
sys.exit(1)
client = create_client()
bucket = args.bucket or get_env("TOS_BUCKET")
key = args.key or get_env("TOS_OBJECT_KEY")
try:
pairs = parse_kv_list(args.kv)
except ValueError as e:
print(f"[ERROR] {e}", file=sys.stderr)
sys.exit(1)
process_value = build_process("blindwatermark", pairs)
save_bucket = args.saveas_bucket
save_object = args.saveas_object
persist_to_tos = bool(save_bucket or save_object)
if persist_to_tos:
save_bucket = save_bucket or bucket
if not save_object:
save_object = f"blindwatermarked_{os.path.basename(key)}"
print(
f"[INFO] Blind-watermarking {bucket}/{key} -> {save_bucket}/{save_object}"
)
print(f"[INFO] process = {process_value}")
encoded_bucket = base64.urlsafe_b64encode(save_bucket.encode()).decode()
encoded_object = base64.urlsafe_b64encode(save_object.encode()).decode()
try:
output = client.get_object(
bucket=bucket,
key=key,
process=process_value,
save_bucket=encoded_bucket,
save_object=encoded_object,
)
raw = output.read()
except TosServerError as e:
print(
f"[ERROR] TOS server error: code={e.code}, status={e.status_code}, "
f"request_id={e.request_id}, message={e.message}",
file=sys.stderr,
)
sys.exit(1)
except TosClientError as e:
print(f"[ERROR] TOS client error: {e}", file=sys.stderr)
sys.exit(1)
try:
data = json.loads(raw.decode("utf-8"))
except Exception as exc: # noqa: BLE001
print("[ERROR] Failed to parse save result as JSON:", file=sys.stderr)
print(exc, file=sys.stderr)
print(raw[:200], file=sys.stderr)
sys.exit(1)
print("[OK] Image saved to TOS:")
print(json.dumps(data, indent=2, ensure_ascii=False))
return
output_path = args.output or default_output_path(key)
print(f"[INFO] Blind-watermarking {bucket}/{key} -> {output_path}")
print(f"[INFO] process = {process_value}")
try:
client.get_object_to_file(
bucket=bucket, key=key, file_path=output_path, process=process_value
)
except TosServerError as e:
if e.code == "AccessDenied":
print(
"[SKIP] Blind watermark is not enabled for this account/bucket.",
file=sys.stderr,
)
print(
"[SKIP] Please enable the blind watermark feature in the TOS console, "
"or re-run with --strict to treat this as a hard failure.",
file=sys.stderr,
)
if args.strict:
sys.exit(1)
sys.exit(0)
if e.code == "InvalidBlindWatermarkParam" and "should larger than" in (
e.message or ""
):
print(
f"[ERROR] {e.message}",
file=sys.stderr,
)
print(
"[ERROR] The source image is too small. Blind watermark requires "
"the image to be at least 512x512 pixels.",
file=sys.stderr,
)
sys.exit(1)
print(
f"[ERROR] TOS server error: code={e.code}, status={e.status_code}, "
f"request_id={e.request_id}, message={e.message}",
file=sys.stderr,
)
sys.exit(1)
except TosClientError as e:
print(f"[ERROR] TOS client error: {e}", file=sys.stderr)
sys.exit(1)
size = os.path.getsize(output_path)
print(f"[OK] Image saved to {output_path} ({size} bytes)")
if __name__ == "__main__":
main()
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#!/usr/bin/env python3
"""Draw points and optional lines on an image stored in TOS.
This script wraps the TOS `image/draw` process:
- p: points, e.g. 50x50-100x100-200x200
- r: point radius
- l: whether to connect points with lines
- lw: line width
- color: RGB hex without '#'
It can save the result locally or persist the processed image back to TOS.
"""
import argparse
import base64
import json
import os
import re
import sys
from typing import Optional
import tos
from tos.exceptions import TosClientError, TosServerError
POINT_RE = re.compile(r"^\d+x\d+(?:-\d+x\d+)*$")
COLOR_RE = re.compile(r"^[0-9A-Fa-f]{6}$")
def get_env(name: str, required: bool = True, default: Optional[str] = None) -> str:
value = os.getenv(name, default)
if required and not value:
print(f"[ERROR] Environment variable {name} is required.", file=sys.stderr)
sys.exit(1)
return value # type: ignore[return-value]
def create_client() -> tos.TosClientV2:
ak = get_env("TOS_ACCESS_KEY")
sk = get_env("TOS_SECRET_KEY")
endpoint = get_env("TOS_ENDPOINT")
region = get_env("TOS_REGION")
security_token = os.getenv("TOS_SECURITY_TOKEN")
print(
f"[INFO] Initializing TOS client for endpoint={endpoint}, region={region} ..."
)
return tos.TosClientV2(
ak=ak,
sk=sk,
endpoint=endpoint,
region=region,
security_token=security_token,
)
def maybe_print_json(raw: bytes) -> bool:
try:
text = raw.decode("utf-8")
data = json.loads(text)
print(json.dumps(data, indent=2, ensure_ascii=False))
return True
except Exception:
return False
def build_process_value(
points: str, radius: int, draw_line: bool, line_width: int, color: str
) -> str:
parts = [
f"p_{points}",
f"r_{radius}",
f"l_{str(draw_line).lower()}",
f"lw_{line_width}",
f"color_{color.upper()}",
]
return "image/draw," + ",".join(parts)
def main() -> None:
parser = argparse.ArgumentParser(description="Draw points/lines on a TOS image")
parser.add_argument("--bucket", type=str, default=None, help="Override TOS_BUCKET")
parser.add_argument("--key", type=str, default=None, help="Override TOS_OBJECT_KEY")
parser.add_argument(
"--points",
type=str,
required=True,
help="Point list, e.g. 50x50-100x100-200x200",
)
parser.add_argument("--radius", type=int, default=6, help="Point radius in pixels")
parser.add_argument(
"--line",
action="store_true",
help="Connect points with lines",
)
parser.add_argument(
"--line-width", type=int, default=3, help="Line width in pixels"
)
parser.add_argument(
"--color",
type=str,
default="FFFFFF",
help="RGB hex color without '#', e.g. FF0000",
)
parser.add_argument(
"--output", type=str, default=None, help="Local output file path"
)
parser.add_argument(
"--saveas-bucket", type=str, default=None, help="Save result to this bucket"
)
parser.add_argument(
"--saveas-object", type=str, default=None, help="Save result as this object key"
)
parser.add_argument("--json", action="store_true", help="Print machine-readable JSON only")
parser.add_argument("--dry-run", action="store_true", help="Print resolved request and exit")
args = parser.parse_args()
if not POINT_RE.match(args.points):
print("[ERROR] --points must look like 50x50-100x100-200x200", file=sys.stderr)
sys.exit(1)
if args.radius < 0:
print("[ERROR] --radius must be >= 0", file=sys.stderr)
sys.exit(1)
if args.line_width < 0:
print("[ERROR] --line-width must be >= 0", file=sys.stderr)
sys.exit(1)
if not COLOR_RE.match(args.color):
print(
"[ERROR] --color must be a 6-digit RGB hex string, e.g. FF0000",
file=sys.stderr,
)
sys.exit(1)
client = create_client()
bucket = args.bucket or get_env("TOS_BUCKET")
key = args.key or get_env("TOS_OBJECT_KEY")
process_value = build_process_value(
points=args.points,
radius=args.radius,
draw_line=args.line,
line_width=args.line_width,
color=args.color,
)
save_bucket = args.saveas_bucket
save_object = args.saveas_object
persist_to_tos = bool(save_bucket or save_object)
print(f"[INFO] process = {process_value}")
if persist_to_tos:
save_bucket = save_bucket or bucket
if not save_object:
save_object = f"draw_{os.path.basename(key)}"
encoded_bucket = base64.urlsafe_b64encode(save_bucket.encode()).decode()
encoded_object = base64.urlsafe_b64encode(save_object.encode()).decode()
print(f"[INFO] Processing {bucket}/{key} -> {save_bucket}/{save_object}")
try:
output = client.get_object(
bucket=bucket,
key=key,
process=process_value,
save_bucket=encoded_bucket,
save_object=encoded_object,
)
raw = output.read()
except TosServerError as e:
print(
f"[ERROR] TOS server error: code={e.code}, status={e.status_code}, "
f"request_id={e.request_id}, message={e.message}",
file=sys.stderr,
)
sys.exit(1)
except TosClientError as e:
print(f"[ERROR] TOS client error: {e}", file=sys.stderr)
sys.exit(1)
print("[OK] Save result:")
if not maybe_print_json(raw):
print(raw.decode("utf-8", errors="replace"))
return
if not args.output:
print(
"[ERROR] --output is required when not saving back to TOS.", file=sys.stderr
)
sys.exit(1)
print(f"[INFO] Processing {bucket}/{key} -> {args.output}")
try:
client.get_object_to_file(
bucket=bucket,
key=key,
file_path=args.output,
process=process_value,
)
except TosServerError as e:
print(
f"[ERROR] TOS server error: code={e.code}, status={e.status_code}, "
f"request_id={e.request_id}, message={e.message}",
file=sys.stderr,
)
sys.exit(1)
except TosClientError as e:
print(f"[ERROR] TOS client error: {e}", file=sys.stderr)
sys.exit(1)
size = os.path.getsize(args.output)
print(f"[OK] Output saved to {args.output} ({size} bytes)")
if __name__ == "__main__":
main()
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#!/usr/bin/env python3
"""Example script: convert image format using TOS image processing.
Builds `process="image/format,..."` and either:
- saves the processed image locally via `get_object_to_file` (default), or
- saves it back to TOS via `get_object(..., save_bucket=..., save_object=...)`.
Common parameters:
- f: output format (jpg/png/webp)
- q: output quality (integer, range subject to official documentation)
The current service behavior used by this script expects the target format as a
plain segment, for example `image/format,webp,q_80`.
For any additional parameters, pass `--kv key=value` and the script will append it
as `key_value` in the process string.
Environment variables:
- TOS_ACCESS_KEY, TOS_SECRET_KEY, TOS_SECURITY_TOKEN(optional)
- TOS_ENDPOINT, TOS_REGION
- TOS_BUCKET, TOS_OBJECT_KEY
Note: Parameter semantics are subject to the official TOS documentation.
"""
import argparse
import base64
import json
import os
import sys
from typing import Optional
import tos
from tos.exceptions import TosClientError, TosServerError
def get_env(name: str, required: bool = True, default: Optional[str] = None) -> str:
value = os.getenv(name, default)
if required and not value:
print(f"[ERROR] Environment variable {name} is required.", file=sys.stderr)
sys.exit(1)
return value # type: ignore[return-value]
def create_client() -> tos.TosClientV2:
ak = get_env("TOS_ACCESS_KEY")
sk = get_env("TOS_SECRET_KEY")
endpoint = get_env("TOS_ENDPOINT")
region = get_env("TOS_REGION")
security_token = os.getenv("TOS_SECURITY_TOKEN")
print(
f"[INFO] Initializing TOS client for endpoint={endpoint}, region={region} ..."
)
return tos.TosClientV2(
ak=ak,
sk=sk,
endpoint=endpoint,
region=region,
security_token=security_token,
)
def parse_kv_list(items: list[str]) -> list[tuple[str, str]]:
pairs: list[tuple[str, str]] = []
for item in items:
if "=" not in item:
raise ValueError(f"Invalid --kv '{item}', expected key=value")
k, v = item.split("=", 1)
k = k.strip()
v = v.strip()
if not k:
raise ValueError(f"Invalid --kv '{item}', key is empty")
pairs.append((k, v))
return pairs
def build_process(op: str, pairs: list[tuple[str, str]]) -> str:
base = f"image/{op}"
if not pairs:
return base
if op == "format":
rendered: list[str] = []
for k, v in pairs:
if k == "f":
# The current service accepts the target format as a plain segment:
# image/format,png
rendered.append(v)
else:
rendered.append(f"{k}_{v}")
return base + "," + ",".join(rendered)
return base + "," + ",".join([f"{k}_{v}" for k, v in pairs])
def default_output_path(key: str, fmt: str) -> str:
base = os.path.basename(key)
name, _dot, _ext = base.partition(".")
if not name:
name = "output"
return f"{name}.{fmt}"
def main() -> None:
parser = argparse.ArgumentParser(
description="Convert image format via TOS process=image/format"
)
parser.add_argument("--bucket", type=str, default=None, help="Override TOS_BUCKET")
parser.add_argument("--key", type=str, default=None, help="Override TOS_OBJECT_KEY")
parser.add_argument(
"--f",
dest="fmt",
choices=["jpg", "png", "webp"],
required=True,
help="Target format",
)
parser.add_argument(
"--q", dest="quality", type=int, default=None, help="Output quality"
)
parser.add_argument(
"--kv", action="append", default=[], help="Extra process option: key=value"
)
parser.add_argument("--output", type=str, default=None, help="Local output file")
parser.add_argument(
"--saveas-bucket", type=str, default=None, help="Save result to this bucket"
)
parser.add_argument(
"--saveas-object", type=str, default=None, help="Save result as this object key"
)
parser.add_argument("--json", action="store_true", help="Print machine-readable JSON only")
parser.add_argument("--dry-run", action="store_true", help="Print resolved request and exit")
args = parser.parse_args()
client = create_client()
bucket = args.bucket or get_env("TOS_BUCKET")
key = args.key or get_env("TOS_OBJECT_KEY")
pairs: list[tuple[str, str]] = [("f", args.fmt)]
if args.quality is not None:
pairs.append(("q", str(args.quality)))
try:
pairs.extend(parse_kv_list(args.kv))
except ValueError as e:
print(f"[ERROR] {e}", file=sys.stderr)
sys.exit(1)
process_value = build_process("format", pairs)
save_bucket = args.saveas_bucket
save_object = args.saveas_object
persist_to_tos = bool(save_bucket or save_object)
if persist_to_tos:
save_bucket = save_bucket or bucket
if not save_object:
save_object = f"format_{os.path.basename(key)}.{args.fmt}"
print(f"[INFO] Converting {bucket}/{key} -> {save_bucket}/{save_object}")
print(f"[INFO] process = {process_value}")
encoded_bucket = base64.urlsafe_b64encode(save_bucket.encode()).decode()
encoded_object = base64.urlsafe_b64encode(save_object.encode()).decode()
try:
output = client.get_object(
bucket=bucket,
key=key,
process=process_value,
save_bucket=encoded_bucket,
save_object=encoded_object,
)
raw = output.read()
except TosServerError as e:
print(
f"[ERROR] TOS server error: code={e.code}, status={e.status_code}, "
f"request_id={e.request_id}, message={e.message}",
file=sys.stderr,
)
sys.exit(1)
except TosClientError as e:
print(f"[ERROR] TOS client error: {e}", file=sys.stderr)
sys.exit(1)
try:
data = json.loads(raw.decode("utf-8"))
except Exception as exc: # noqa: BLE001
print("[ERROR] Failed to parse save result as JSON:", file=sys.stderr)
print(exc, file=sys.stderr)
print(raw[:200], file=sys.stderr)
sys.exit(1)
print("[OK] Image saved to TOS:")
print(json.dumps(data, indent=2, ensure_ascii=False))
return
output_path = args.output or default_output_path(key, args.fmt)
print(f"[INFO] Converting {bucket}/{key} -> {output_path}")
print(f"[INFO] process = {process_value}")
try:
client.get_object_to_file(
bucket=bucket, key=key, file_path=output_path, process=process_value
)
except TosServerError as e:
print(
f"[ERROR] TOS server error: code={e.code}, status={e.status_code}, "
f"request_id={e.request_id}, message={e.message}",
file=sys.stderr,
)
sys.exit(1)
except TosClientError as e:
print(f"[ERROR] TOS client error: {e}", file=sys.stderr)
sys.exit(1)
size = os.path.getsize(output_path)
print(f"[OK] Image saved to {output_path} ({size} bytes)")
if __name__ == "__main__":
main()
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#!/usr/bin/env python3
"""Example script: get image information from TOS using the Python SDK.
Calls TOS image processing with `process="image/info"` and prints metadata.
If the service returns JSON, the script prints that JSON. If the service returns
raw image bytes instead, the script falls back to local parsing for basic info.
Environment variables:
- TOS_ACCESS_KEY Access key ID (AK) or STS AccessKeyId
- TOS_SECRET_KEY Secret access key (SK) or STS SecretAccessKey
- TOS_SECURITY_TOKEN (optional) STS session token
- TOS_ENDPOINT TOS endpoint, e.g. https://tos-cn-beijing.volces.com
- TOS_REGION TOS region, e.g. cn-beijing
- TOS_BUCKET Bucket name that stores the image
- TOS_OBJECT_KEY Object key of the image file in the bucket
This script supports:
- local save: saves the response body locally when `--output` is provided
- save back to TOS: via `get_object(..., save_bucket=..., save_object=...)`
Note: The exact response schema is subject to the official TOS documentation.
"""
import argparse
import json
import os
import sys
from pathlib import Path
from typing import Any
from typing import Optional
import tos
from tos.exceptions import TosClientError, TosServerError
def get_env(name: str, required: bool = True, default: Optional[str] = None) -> str:
value = os.getenv(name, default)
if required and not value:
print(f"[ERROR] Environment variable {name} is required.", file=sys.stderr)
sys.exit(1)
return value # type: ignore[return-value]
def create_client() -> tos.TosClientV2:
ak = get_env("TOS_ACCESS_KEY")
sk = get_env("TOS_SECRET_KEY")
endpoint = get_env("TOS_ENDPOINT")
region = get_env("TOS_REGION")
security_token = os.getenv("TOS_SECURITY_TOKEN")
print(
f"[INFO] Initializing TOS client for endpoint={endpoint}, region={region} ..."
)
return tos.TosClientV2(
ak=ak,
sk=sk,
endpoint=endpoint,
region=region,
security_token=security_token,
)
def load_json_file(path: str) -> object:
with open(path, "rb") as f:
raw = f.read()
return json.loads(raw.decode("utf-8"))
def _is_likely_json(raw: bytes) -> bool:
s = raw.lstrip()
return s.startswith(b"{") or s.startswith(b"[")
def _sniff_image_kind(raw: bytes) -> Optional[str]:
if raw.startswith(b"\xff\xd8\xff"):
return "jpeg"
if raw.startswith(b"\x89PNG\r\n\x1a\n"):
return "png"
if raw.startswith(b"GIF87a") or raw.startswith(b"GIF89a"):
return "gif"
if raw.startswith(b"RIFF") and raw[8:12] == b"WEBP":
return "webp"
return None
def _parse_dimensions(raw: bytes) -> tuple[Optional[int], Optional[int]]:
kind = _sniff_image_kind(raw)
if kind == "png" and len(raw) >= 24:
# IHDR chunk: width/height big-endian at offset 16.
w = int.from_bytes(raw[16:20], "big")
h = int.from_bytes(raw[20:24], "big")
return w, h
if kind == "gif" and len(raw) >= 10:
# Logical Screen Width/Height little-endian at offset 6.
w = int.from_bytes(raw[6:8], "little")
h = int.from_bytes(raw[8:10], "little")
return w, h
if kind == "jpeg":
# Scan for SOF marker (baseline/progressive) to read width/height.
i = 2
n = len(raw)
while i + 1 < n:
if raw[i] != 0xFF:
i += 1
continue
# Skip padding FFs.
while i < n and raw[i] == 0xFF:
i += 1
if i >= n:
break
marker = raw[i]
i += 1
# Standalone markers.
if marker in (0xD8, 0xD9):
continue
if i + 1 >= n:
break
seg_len = int.from_bytes(raw[i : i + 2], "big")
if seg_len < 2 or i + seg_len > n:
break
# SOF0/1/2/3/5/6/7/9/A/B/C/D/E/F
if marker in (
0xC0,
0xC1,
0xC2,
0xC3,
0xC5,
0xC6,
0xC7,
0xC9,
0xCA,
0xCB,
0xCD,
0xCE,
0xCF,
):
# segment layout: [len(2)] [precision(1)] [height(2)] [width(2)] ...
if i + 7 <= n:
h = int.from_bytes(raw[i + 3 : i + 5], "big")
w = int.from_bytes(raw[i + 5 : i + 7], "big")
return w, h
break
i += seg_len
return None, None
# WebP parsing is chunk-type dependent; keep it unknown for now.
return None, None
def _fallback_local_info(raw: bytes) -> dict[str, Any]:
kind = _sniff_image_kind(raw) or "unknown"
w, h = _parse_dimensions(raw)
return {
"source": "fallback-local-parse",
"format": kind,
"bytes": len(raw),
"width": w,
"height": h,
"note": "TOS image/info did not return JSON; computed basic info from response bytes.",
}
def main() -> None:
parser = argparse.ArgumentParser(
description="Get image info via TOS process=image/info"
)
parser.add_argument("--bucket", type=str, default=None, help="Override TOS_BUCKET")
parser.add_argument("--key", type=str, default=None, help="Override TOS_OBJECT_KEY")
parser.add_argument(
"--output",
type=str,
default=None,
help="If set, save the response body locally using get_object_to_file",
)
parser.add_argument(
"--saveas-bucket", type=str, default=None, help="Persist result to this bucket"
)
parser.add_argument(
"--saveas-object",
type=str,
default=None,
help="Persist result as this object key",
)
parser.add_argument("--json", action="store_true", help="Print machine-readable JSON only")
parser.add_argument("--dry-run", action="store_true", help="Print resolved request and exit")
args = parser.parse_args()
client = create_client()
bucket = args.bucket or get_env("TOS_BUCKET")
key = args.key or get_env("TOS_OBJECT_KEY")
save_bucket = args.saveas_bucket
save_object = args.saveas_object
persist_to_tos = bool(save_bucket or save_object)
if persist_to_tos:
save_bucket = save_bucket or bucket
if not save_object:
base = os.path.basename(key).replace("/", "_")
save_object = f"image_info_{base}.json"
print(
f"[INFO] Requesting image info for {bucket}/{key} -> {save_bucket}/{save_object}"
)
print("[INFO] process = image/info")
try:
output = client.get_object(
bucket=bucket,
key=key,
process="image/info",
save_bucket=save_bucket,
save_object=save_object,
)
raw = output.read()
except TosServerError as e:
print(
f"[ERROR] TOS server error: code={e.code}, status={e.status_code}, "
f"request_id={e.request_id}, message={e.message}",
file=sys.stderr,
)
sys.exit(1)
except TosClientError as e:
print(f"[ERROR] TOS client error: {e}", file=sys.stderr)
sys.exit(1)
try:
data = json.loads(raw.decode("utf-8"))
except Exception as exc: # noqa: BLE001
print("[ERROR] Failed to parse response as JSON:", file=sys.stderr)
print(exc, file=sys.stderr)
print(raw[:200], file=sys.stderr)
sys.exit(1)
print("[OK] Image info saved to TOS:")
print(json.dumps(data, indent=2, ensure_ascii=False))
return
if args.output:
output_path = args.output
print(f"[INFO] Requesting image info for {bucket}/{key} -> {output_path}")
print("[INFO] process = image/info")
try:
client.get_object_to_file(
bucket=bucket, key=key, file_path=output_path, process="image/info"
)
except TosServerError as e:
print(
f"[ERROR] TOS server error: code={e.code}, status={e.status_code}, "
f"request_id={e.request_id}, message={e.message}",
file=sys.stderr,
)
sys.exit(1)
except TosClientError as e:
print(f"[ERROR] TOS client error: {e}", file=sys.stderr)
sys.exit(1)
raw = Path(output_path).read_bytes()
if _is_likely_json(raw):
try:
data = json.loads(raw.decode("utf-8"))
except Exception as exc: # noqa: BLE001
print("[ERROR] Failed to parse local file as JSON:", file=sys.stderr)
print(exc, file=sys.stderr)
sys.exit(1)
else:
data = _fallback_local_info(raw)
print("[OK] Image info:")
print(json.dumps(data, indent=2, ensure_ascii=False))
return
print(f"[INFO] Requesting image info for {bucket}/{key} ...")
print("[INFO] process = image/info")
try:
output = client.get_object(bucket=bucket, key=key, process="image/info")
raw = output.read()
except TosServerError as e:
print(
f"[ERROR] TOS server error: code={e.code}, status={e.status_code}, "
f"request_id={e.request_id}, message={e.message}",
file=sys.stderr,
)
sys.exit(1)
except TosClientError as e:
print(f"[ERROR] TOS client error: {e}", file=sys.stderr)
sys.exit(1)
if _is_likely_json(raw):
try:
data = json.loads(raw.decode("utf-8"))
except Exception as exc: # noqa: BLE001
print("[ERROR] Failed to parse response as JSON:", file=sys.stderr)
print(exc, file=sys.stderr)
print(raw[:200], file=sys.stderr)
sys.exit(1)
else:
data = _fallback_local_info(raw)
print("[OK] Image info:")
print(json.dumps(data, indent=2, ensure_ascii=False))
if __name__ == "__main__":
main()
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#!/usr/bin/env python3
"""Generic image process entrypoint for TOS image processing.
Accepts a full process string (e.g. "image/resize,w_100,h_100"), then either:
- saves the processed output locally via `get_object_to_file` (default), or
- saves it back to TOS via `get_object(..., save_bucket=..., save_object=...)`.
Environment variables:
- TOS_ACCESS_KEY, TOS_SECRET_KEY, TOS_SECURITY_TOKEN(optional)
- TOS_ENDPOINT, TOS_REGION
- TOS_BUCKET, TOS_OBJECT_KEY
Note: The process syntax and option keys are subject to the official TOS documentation.
"""
import argparse
import base64
import json
import os
import sys
from typing import Optional
import tos
from tos.exceptions import TosClientError, TosServerError
def get_env(name: str, required: bool = True, default: Optional[str] = None) -> str:
value = os.getenv(name, default)
if required and not value:
print(f"[ERROR] Environment variable {name} is required.", file=sys.stderr)
sys.exit(1)
return value # type: ignore[return-value]
def create_client() -> tos.TosClientV2:
ak = get_env("TOS_ACCESS_KEY")
sk = get_env("TOS_SECRET_KEY")
endpoint = get_env("TOS_ENDPOINT")
region = get_env("TOS_REGION")
security_token = os.getenv("TOS_SECURITY_TOKEN")
print(
f"[INFO] Initializing TOS client for endpoint={endpoint}, region={region} ..."
)
return tos.TosClientV2(
ak=ak,
sk=sk,
endpoint=endpoint,
region=region,
security_token=security_token,
)
def maybe_print_json(raw: bytes) -> bool:
try:
text = raw.decode("utf-8")
data = json.loads(text)
print(json.dumps(data, indent=2, ensure_ascii=False))
return True
except Exception:
return False
def main() -> None:
parser = argparse.ArgumentParser(description="Generic TOS image process entrypoint")
parser.add_argument("--bucket", type=str, default=None, help="Override TOS_BUCKET")
parser.add_argument("--key", type=str, default=None, help="Override TOS_OBJECT_KEY")
parser.add_argument(
"--process",
type=str,
required=True,
help="Full process string, e.g. image/info",
)
parser.add_argument(
"--output",
type=str,
default=None,
help="Local output file (required if not saving to TOS)",
)
parser.add_argument(
"--saveas-bucket", type=str, default=None, help="Save result to this bucket"
)
parser.add_argument(
"--saveas-object", type=str, default=None, help="Save result as this object key"
)
parser.add_argument("--json", action="store_true", help="Print machine-readable JSON only")
parser.add_argument("--dry-run", action="store_true", help="Print resolved request and exit")
args = parser.parse_args()
client = create_client()
bucket = args.bucket or get_env("TOS_BUCKET")
key = args.key or get_env("TOS_OBJECT_KEY")
save_bucket = args.saveas_bucket
save_object = args.saveas_object
persist_to_tos = bool(save_bucket or save_object)
if persist_to_tos:
save_bucket = save_bucket or bucket
if not save_object:
save_object = f"processed_{os.path.basename(key)}"
print(f"[INFO] Processing {bucket}/{key} -> {save_bucket}/{save_object}")
print(f"[INFO] process = {args.process}")
encoded_bucket = base64.urlsafe_b64encode(save_bucket.encode()).decode()
encoded_object = base64.urlsafe_b64encode(save_object.encode()).decode()
try:
output = client.get_object(
bucket=bucket,
key=key,
process=args.process,
save_bucket=encoded_bucket,
save_object=encoded_object,
)
raw = output.read()
except TosServerError as e:
print(
f"[ERROR] TOS server error: code={e.code}, status={e.status_code}, "
f"request_id={e.request_id}, message={e.message}",
file=sys.stderr,
)
sys.exit(1)
except TosClientError as e:
print(f"[ERROR] TOS client error: {e}", file=sys.stderr)
sys.exit(1)
print("[OK] Save result:")
if not maybe_print_json(raw):
print(raw.decode("utf-8", errors="replace"))
return
output_path = args.output
if not output_path:
print(
"[ERROR] --output is required when not saving back to TOS.", file=sys.stderr
)
sys.exit(1)
print(f"[INFO] Processing {bucket}/{key} -> {output_path}")
print(f"[INFO] process = {args.process}")
try:
client.get_object_to_file(
bucket=bucket, key=key, file_path=output_path, process=args.process
)
except TosServerError as e:
print(
f"[ERROR] TOS server error: code={e.code}, status={e.status_code}, "
f"request_id={e.request_id}, message={e.message}",
file=sys.stderr,
)
sys.exit(1)
except TosClientError as e:
print(f"[ERROR] TOS client error: {e}", file=sys.stderr)
sys.exit(1)
size = os.path.getsize(output_path)
print(f"[OK] Output saved to {output_path} ({size} bytes)")
if __name__ == "__main__":
main()
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#!/usr/bin/env python3
"""Example script: resize image using TOS image processing.
Builds `process="image/resize,..."` and either:
- saves the processed image locally via `get_object_to_file` (default), or
- saves it back to TOS via `get_object(..., save_bucket=..., save_object=...)`.
Common parameters:
- w: target width
- h: target height
- m: resize mode (string; exact options are subject to official documentation)
For any additional parameters, pass `--kv key=value` and the script will append it
as `key_value` in the process string.
Environment variables:
- TOS_ACCESS_KEY, TOS_SECRET_KEY, TOS_SECURITY_TOKEN(optional)
- TOS_ENDPOINT, TOS_REGION
- TOS_BUCKET, TOS_OBJECT_KEY
Note: Parameter semantics are subject to the official TOS documentation.
"""
import argparse
import base64
import json
import os
import sys
from typing import Optional
import tos
from tos.exceptions import TosClientError, TosServerError
def get_env(name: str, required: bool = True, default: Optional[str] = None) -> str:
value = os.getenv(name, default)
if required and not value:
print(f"[ERROR] Environment variable {name} is required.", file=sys.stderr)
sys.exit(1)
return value # type: ignore[return-value]
def create_client() -> tos.TosClientV2:
ak = get_env("TOS_ACCESS_KEY")
sk = get_env("TOS_SECRET_KEY")
endpoint = get_env("TOS_ENDPOINT")
region = get_env("TOS_REGION")
security_token = os.getenv("TOS_SECURITY_TOKEN")
print(
f"[INFO] Initializing TOS client for endpoint={endpoint}, region={region} ..."
)
return tos.TosClientV2(
ak=ak,
sk=sk,
endpoint=endpoint,
region=region,
security_token=security_token,
)
def parse_kv_list(items: list[str]) -> list[tuple[str, str]]:
pairs: list[tuple[str, str]] = []
for item in items:
if "=" not in item:
raise ValueError(f"Invalid --kv '{item}', expected key=value")
k, v = item.split("=", 1)
k = k.strip()
v = v.strip()
if not k:
raise ValueError(f"Invalid --kv '{item}', key is empty")
pairs.append((k, v))
return pairs
def build_process(op: str, pairs: list[tuple[str, str]]) -> str:
base = f"image/{op}"
if not pairs:
return base
return base + "," + ",".join([f"{k}_{v}" for k, v in pairs])
def default_output_path(key: str) -> str:
base = os.path.basename(key)
if not base:
return "resized_output"
return f"resized_{base}"
def main() -> None:
parser = argparse.ArgumentParser(
description="Resize image via TOS process=image/resize"
)
parser.add_argument("--bucket", type=str, default=None, help="Override TOS_BUCKET")
parser.add_argument("--key", type=str, default=None, help="Override TOS_OBJECT_KEY")
parser.add_argument(
"--w", dest="width", type=int, default=None, help="Target width"
)
parser.add_argument(
"--h", dest="height", type=int, default=None, help="Target height"
)
parser.add_argument("--m", dest="mode", type=str, default=None, help="Resize mode")
parser.add_argument(
"--kv", action="append", default=[], help="Extra process option: key=value"
)
parser.add_argument("--output", type=str, default=None, help="Local output file")
parser.add_argument(
"--saveas-bucket", type=str, default=None, help="Save result to this bucket"
)
parser.add_argument(
"--saveas-object", type=str, default=None, help="Save result as this object key"
)
parser.add_argument("--json", action="store_true", help="Print machine-readable JSON only")
parser.add_argument("--dry-run", action="store_true", help="Print resolved request and exit")
args = parser.parse_args()
client = create_client()
bucket = args.bucket or get_env("TOS_BUCKET")
key = args.key or get_env("TOS_OBJECT_KEY")
pairs: list[tuple[str, str]] = []
if args.width is not None:
pairs.append(("w", str(args.width)))
if args.height is not None:
pairs.append(("h", str(args.height)))
if args.mode:
pairs.append(("m", args.mode))
try:
pairs.extend(parse_kv_list(args.kv))
except ValueError as e:
print(f"[ERROR] {e}", file=sys.stderr)
sys.exit(1)
process_value = build_process("resize", pairs)
save_bucket = args.saveas_bucket
save_object = args.saveas_object
persist_to_tos = bool(save_bucket or save_object)
if persist_to_tos:
save_bucket = save_bucket or bucket
if not save_object:
save_object = f"resized_{os.path.basename(key)}"
print(f"[INFO] Resizing {bucket}/{key} -> {save_bucket}/{save_object}")
print(f"[INFO] process = {process_value}")
encoded_bucket = base64.urlsafe_b64encode(save_bucket.encode()).decode()
encoded_object = base64.urlsafe_b64encode(save_object.encode()).decode()
try:
output = client.get_object(
bucket=bucket,
key=key,
process=process_value,
save_bucket=encoded_bucket,
save_object=encoded_object,
)
raw = output.read()
except TosServerError as e:
print(
f"[ERROR] TOS server error: code={e.code}, status={e.status_code}, "
f"request_id={e.request_id}, message={e.message}",
file=sys.stderr,
)
sys.exit(1)
except TosClientError as e:
print(f"[ERROR] TOS client error: {e}", file=sys.stderr)
sys.exit(1)
try:
data = json.loads(raw.decode("utf-8"))
except Exception as exc: # noqa: BLE001
print("[ERROR] Failed to parse save result as JSON:", file=sys.stderr)
print(exc, file=sys.stderr)
print(raw[:200], file=sys.stderr)
sys.exit(1)
print("[OK] Image saved to TOS:")
print(json.dumps(data, indent=2, ensure_ascii=False))
return
output_path = args.output or default_output_path(key)
print(f"[INFO] Resizing {bucket}/{key} -> {output_path}")
print(f"[INFO] process = {process_value}")
try:
client.get_object_to_file(
bucket=bucket, key=key, file_path=output_path, process=process_value
)
except TosServerError as e:
print(
f"[ERROR] TOS server error: code={e.code}, status={e.status_code}, "
f"request_id={e.request_id}, message={e.message}",
file=sys.stderr,
)
sys.exit(1)
except TosClientError as e:
print(f"[ERROR] TOS client error: {e}", file=sys.stderr)
sys.exit(1)
size = os.path.getsize(output_path)
print(f"[OK] Image saved to {output_path} ({size} bytes)")
if __name__ == "__main__":
main()
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#!/usr/bin/env python3
"""Image understanding entrypoint for TOS image processing.
Uses the image/understanding sync operation to invoke a VLM (Vision Language Model)
for intelligent image comprehension.
Environment variables:
- TOS_ACCESS_KEY, TOS_SECRET_KEY, TOS_SECURITY_TOKEN(optional)
- TOS_ENDPOINT, TOS_REGION
- TOS_BUCKET
"""
import argparse
import base64
import json
import os
import sys
from typing import Optional
import tos
from tos.exceptions import TosClientError, TosServerError
def get_env(name: str, required: bool = True, default: Optional[str] = None) -> str:
value = os.getenv(name, default)
if required and not value:
print(f"[ERROR] Environment variable {name} is required.", file=sys.stderr)
sys.exit(1)
return value # type: ignore[return-value]
def create_client() -> tos.TosClientV2:
ak = get_env("TOS_ACCESS_KEY")
sk = get_env("TOS_SECRET_KEY")
endpoint = get_env("TOS_ENDPOINT")
region = get_env("TOS_REGION")
security_token = os.getenv("TOS_SECURITY_TOKEN")
print(
f"[INFO] Initializing TOS client for endpoint={endpoint}, region={region} ..."
)
return tos.TosClientV2(
ak=ak,
sk=sk,
endpoint=endpoint,
region=region,
security_token=security_token,
socket_timeout=120,
)
def b64url_encode(data: str) -> str:
return base64.urlsafe_b64encode(data.encode()).decode().rstrip("=")
def main() -> None:
parser = argparse.ArgumentParser(
description="TOS image understanding (VLM) entrypoint"
)
parser.add_argument("--key", type=str, required=True, help="Image object key")
parser.add_argument(
"--bucket", type=str, default=None, help="TOS bucket (default: TOS_BUCKET env)"
)
parser.add_argument("--prompt", type=str, required=True, help="Prompt for VLM")
parser.add_argument(
"--model", type=str, default="doubao-seed-1.6-vision", help="VLM model name"
)
parser.add_argument(
"--detail",
type=str,
default=None,
choices=["auto", "low", "high"],
help="Detail level",
)
parser.add_argument(
"--saveas-bucket", type=str, default=None, help="Save result to this bucket"
)
parser.add_argument(
"--saveas-object", type=str, default=None, help="Save result as this object key"
)
parser.add_argument("--output", type=str, default=None, help="Local output file")
parser.add_argument("--json", action="store_true", help="Print machine-readable JSON only")
parser.add_argument("--dry-run", action="store_true", help="Print resolved request and exit")
args = parser.parse_args()
client = create_client()
bucket = args.bucket or get_env("TOS_BUCKET")
key = args.key
encoded_model = b64url_encode(args.model)
encoded_prompt = b64url_encode(args.prompt)
process = f"image/understanding,m_{encoded_model},p_{encoded_prompt}"
if args.detail:
process += f",d_{args.detail}"
save_bucket = args.saveas_bucket
save_object = args.saveas_object
persist_to_tos = bool(save_bucket or save_object)
if persist_to_tos:
save_bucket = save_bucket or bucket
if not save_object:
save_object = f"understanding_{os.path.basename(key)}"
print(f"[INFO] Understanding {bucket}/{key} -> {save_bucket}/{save_object}")
print(f"[INFO] process = {process}")
encoded_bucket = base64.urlsafe_b64encode(save_bucket.encode()).decode()
encoded_object = base64.urlsafe_b64encode(save_object.encode()).decode()
try:
output = client.get_object(
bucket=bucket,
key=key,
process=process,
save_bucket=encoded_bucket,
save_object=encoded_object,
)
raw = output.read()
except TosServerError as e:
print(
f"[ERROR] TOS server error: code={e.code}, status={e.status_code}, "
f"request_id={e.request_id}, message={e.message}",
file=sys.stderr,
)
sys.exit(1)
except TosClientError as e:
print(f"[ERROR] TOS client error: {e}", file=sys.stderr)
sys.exit(1)
print("[OK] Save result:")
try:
data = json.loads(raw.decode("utf-8"))
print(json.dumps(data, indent=2, ensure_ascii=False))
except Exception:
print(raw.decode("utf-8", errors="replace"))
return
print(f"[INFO] Understanding {bucket}/{key}")
print(f"[INFO] process = {process}")
try:
output = client.get_object(
bucket=bucket,
key=key,
process=process,
)
raw = output.read()
except TosServerError as e:
print(
f"[ERROR] TOS server error: code={e.code}, status={e.status_code}, "
f"request_id={e.request_id}, message={e.message}",
file=sys.stderr,
)
sys.exit(1)
except TosClientError as e:
print(f"[ERROR] TOS client error: {e}", file=sys.stderr)
sys.exit(1)
text = raw.decode("utf-8", errors="replace")
if args.output:
with open(args.output, "w", encoding="utf-8") as f:
f.write(text)
print(f"[OK] Output saved to {args.output}")
try:
data = json.loads(text)
content = data.get("content", "")
if content:
print(f"\n[Result]\n{content}")
except Exception:
pass
else:
try:
data = json.loads(text)
print(json.dumps(data, indent=2, ensure_ascii=False))
content = data.get("content", "")
if content:
print(f"\n[Result]\n{content}")
except Exception:
print(text)
if __name__ == "__main__":
main()
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#!/usr/bin/env python3
"""Example script: apply image watermark using TOS image processing.
Builds `process="image/watermark,..."` and either:
- saves the processed image locally via `get_object_to_file` (default), or
- saves it back to TOS via `get_object(..., save_bucket=..., save_object=...)`.
This implementation follows the official TOS watermark model:
- text watermark: text/type/color/size/shadow/rotate/fill
- image watermark: image plus optional base placement params
- mixed watermark: specify both text and image, then use order/align/interval
- base params: t/g/x/y/voffset
For advanced scenarios, repeated `--kv key=value` is still supported and will be
appended verbatim after the modeled parameters.
Environment variables:
- TOS_ACCESS_KEY, TOS_SECRET_KEY, TOS_SECURITY_TOKEN(optional)
- TOS_ENDPOINT, TOS_REGION
- TOS_BUCKET, TOS_OBJECT_KEY
Note: Parameter semantics are subject to the official TOS documentation.
"""
import argparse
import base64
import json
import os
import re
import sys
from typing import Optional
import tos
from tos.exceptions import TosClientError, TosServerError
def get_env(name: str, required: bool = True, default: Optional[str] = None) -> str:
value = os.getenv(name, default)
if required and not value:
print(f"[ERROR] Environment variable {name} is required.", file=sys.stderr)
sys.exit(1)
return value # type: ignore[return-value]
def create_client() -> tos.TosClientV2:
ak = get_env("TOS_ACCESS_KEY")
sk = get_env("TOS_SECRET_KEY")
endpoint = get_env("TOS_ENDPOINT")
region = get_env("TOS_REGION")
security_token = os.getenv("TOS_SECURITY_TOKEN")
print(
f"[INFO] Initializing TOS client for endpoint={endpoint}, region={region} ..."
)
return tos.TosClientV2(
ak=ak,
sk=sk,
endpoint=endpoint,
region=region,
security_token=security_token,
)
def parse_kv_list(items: list[str]) -> list[tuple[str, str]]:
pairs: list[tuple[str, str]] = []
for item in items:
if "=" not in item:
raise ValueError(f"Invalid --kv '{item}', expected key=value")
k, v = item.split("=", 1)
k = k.strip()
v = v.strip()
if not k:
raise ValueError(f"Invalid --kv '{item}', key is empty")
pairs.append((k, v))
return pairs
FONT_TO_B64 = {
"wqy-zenhei": "d3F5LXplbmhlaQ",
"wqy-microhei": "d3F5LW1pY3JvaGVp",
"fangzhengshusong": "ZmFuZ3poZW5nc2h1c29uZw",
"fangzhengkaiti": "ZmFuZ3poZW5na2FpdGk",
"fangzhengheiti": "ZmFuZ3poZW5naGVpdGk",
"fangzhengfangsong": "ZmFuZ3poZW5nZmFuZ3Nvbmc",
"droidsansfallback": "ZHJvaWRzYW5zZmFsbGJhY2s",
}
GRAVITY_CHOICES = ["nw", "north", "ne", "west", "center", "east", "sw", "south", "se"]
def urlsafe_b64_no_padding(value: str) -> str:
return base64.urlsafe_b64encode(value.encode("utf-8")).decode("ascii").rstrip("=")
def normalize_hex_color(value: str) -> str:
color = value.strip().lstrip("#").upper()
if not re.fullmatch(r"[0-9A-F]{6}", color):
raise ValueError(
f"Invalid color '{value}', expected 6-digit hex RGB like FF0000"
)
return color
def encoded_or_raw(
raw_value: Optional[str], encoded_value: Optional[str]
) -> Optional[str]:
if encoded_value:
return encoded_value.strip()
if raw_value:
return urlsafe_b64_no_padding(raw_value)
return None
def build_modeled_pairs(args: argparse.Namespace) -> list[tuple[str, str]]:
pairs: list[tuple[str, str]] = []
image_value = encoded_or_raw(args.image, args.image_b64)
text_value = encoded_or_raw(args.text, args.text_b64)
font_value = args.font_b64.strip() if args.font_b64 else None
if args.font:
font_value = FONT_TO_B64[args.font]
if image_value:
pairs.append(("image", image_value))
if text_value:
pairs.append(("text", text_value))
if font_value:
pairs.append(("type", font_value))
if args.color:
pairs.append(("color", normalize_hex_color(args.color)))
if args.size is not None:
pairs.append(("size", str(args.size)))
if args.shadow is not None:
pairs.append(("shadow", str(args.shadow)))
if args.rotate is not None:
pairs.append(("rotate", str(args.rotate)))
if args.fill is not None:
pairs.append(("fill", str(args.fill)))
if args.opacity is not None:
pairs.append(("t", str(args.opacity)))
if args.gravity:
pairs.append(("g", args.gravity))
if args.x is not None:
pairs.append(("x", str(args.x)))
if args.y is not None:
pairs.append(("y", str(args.y)))
if args.voffset is not None:
pairs.append(("voffset", str(args.voffset)))
if args.order is not None:
pairs.append(("order", str(args.order)))
if args.align is not None:
pairs.append(("align", str(args.align)))
if args.interval is not None:
pairs.append(("interval", str(args.interval)))
return pairs
def build_process(op: str, pairs: list[tuple[str, str]]) -> str:
base = f"image/{op}"
if not pairs:
return base
return base + "," + ",".join([f"{k}_{v}" for k, v in pairs])
def default_output_path(key: str) -> str:
base = os.path.basename(key)
if not base:
return "watermarked_output"
return f"watermarked_{base}"
def emit(payload: dict, json_only: bool, heading=None) -> None:
if json_only:
print(json.dumps(payload, ensure_ascii=False))
return
if heading:
print(heading)
print(json.dumps(payload, indent=2, ensure_ascii=False))
def main() -> None:
parser = argparse.ArgumentParser(
description="Apply watermark via TOS process=image/watermark"
)
parser.add_argument("--bucket", type=str, default=None, help="Override TOS_BUCKET")
parser.add_argument("--key", type=str, default=None, help="Override TOS_OBJECT_KEY")
parser.add_argument(
"--text",
type=str,
default=None,
help="Raw text watermark content; the script converts it to URL-safe Base64.",
)
parser.add_argument(
"--text-b64",
type=str,
default=None,
help="Pre-encoded URL-safe Base64 watermark text.",
)
parser.add_argument(
"--font",
choices=sorted(FONT_TO_B64.keys()),
default=None,
help="Font name for text watermark. Encoded automatically using the official mapping.",
)
parser.add_argument(
"--font-b64",
type=str,
default=None,
help="Pre-encoded URL-safe Base64 font identifier for the type parameter.",
)
parser.add_argument(
"--color",
type=str,
default=None,
help="Text color as RRGGBB or #RRGGBB.",
)
parser.add_argument("--size", type=int, default=None, help="Text size in px.")
parser.add_argument(
"--shadow", type=int, default=None, help="Text shadow opacity [0,100]."
)
parser.add_argument(
"--rotate", type=int, default=None, help="Clockwise rotation angle [0,360]."
)
parser.add_argument(
"--fill",
type=int,
choices=[0, 1],
default=None,
help="Whether to tile text watermark across the source image: 0 or 1.",
)
parser.add_argument(
"--image",
type=str,
default=None,
help=(
"Raw watermark image reference string. Must point to an object in the same bucket. "
"For image preprocessing, pass the full reference including '?x-tos-process=...'; "
"the script converts it to URL-safe Base64."
),
)
parser.add_argument(
"--image-b64",
type=str,
default=None,
help="Pre-encoded URL-safe Base64 watermark image reference string.",
)
parser.add_argument(
"--opacity", type=int, default=None, help="Watermark opacity `t` in [0,100]."
)
parser.add_argument(
"--gravity",
choices=GRAVITY_CHOICES,
default=None,
help="Watermark placement `g`: nw, north, ne, west, center, east, sw, south, se.",
)
parser.add_argument(
"--x", type=int, default=None, help="Horizontal margin `x` in px."
)
parser.add_argument(
"--y", type=int, default=None, help="Vertical margin `y` in px."
)
parser.add_argument(
"--voffset",
type=int,
default=None,
help="Vertical offset from center line in px.",
)
parser.add_argument(
"--order",
type=int,
choices=[0, 1],
default=None,
help="Mixed watermark order: 0 means image first, 1 means text first.",
)
parser.add_argument(
"--align",
type=int,
choices=[0, 1, 2],
default=None,
help="Mixed watermark alignment: 0 top, 1 middle, 2 bottom.",
)
parser.add_argument(
"--interval", type=int, default=None, help="Mixed watermark spacing in px."
)
parser.add_argument(
"--kv",
action="append",
default=[],
help="Advanced watermark option appended verbatim as key=value -> key_value.",
)
parser.add_argument("--output", type=str, default=None, help="Local output file")
parser.add_argument(
"--saveas-bucket", type=str, default=None, help="Save result to this bucket"
)
parser.add_argument(
"--saveas-object", type=str, default=None, help="Save result as this object key"
)
parser.add_argument(
"--dry-run",
action="store_true",
help="Only print the generated process string and exit without calling TOS.",
)
parser.add_argument(
"--json", action="store_true", help="Print machine-readable JSON only"
)
args = parser.parse_args()
if args.text and args.text_b64:
print("[ERROR] Use only one of --text or --text-b64.", file=sys.stderr)
sys.exit(1)
if args.font and args.font_b64:
print("[ERROR] Use only one of --font or --font-b64.", file=sys.stderr)
sys.exit(1)
if args.image and args.image_b64:
print("[ERROR] Use only one of --image or --image-b64.", file=sys.stderr)
sys.exit(1)
client = create_client()
bucket = args.bucket or get_env("TOS_BUCKET")
key = args.key or get_env("TOS_OBJECT_KEY")
try:
pairs = build_modeled_pairs(args)
pairs.extend(parse_kv_list(args.kv))
except ValueError as e:
print(f"[ERROR] {e}", file=sys.stderr)
sys.exit(1)
has_modeled_source = bool(
args.text or args.text_b64 or args.image or args.image_b64
)
has_advanced_source = any(k in {"text", "image"} for k, _ in pairs)
if not has_modeled_source and not has_advanced_source:
print(
"[ERROR] Specify a watermark source with --text/--text-b64/--image/--image-b64, "
"or provide advanced --kv text=... / --kv image=... parameters.",
file=sys.stderr,
)
sys.exit(1)
process_value = build_process("watermark", pairs)
plan = {
"ok": True,
"operation": "image_watermark",
"bucket": bucket,
"key": key,
"process": process_value,
"saveas_bucket": args.saveas_bucket or bucket
if (args.saveas_bucket or args.saveas_object)
else None,
"saveas_object": args.saveas_object,
}
if args.dry_run:
emit(plan, args.json, "[OK] Resolved request:")
return
save_bucket = args.saveas_bucket
save_object = args.saveas_object
persist_to_tos = bool(save_bucket or save_object)
if persist_to_tos:
save_bucket = save_bucket or bucket
if not save_object:
save_object = f"watermarked_{os.path.basename(key)}"
if not args.json:
print(f"[INFO] Watermarking {bucket}/{key} -> {save_bucket}/{save_object}")
print(f"[INFO] process = {process_value}")
encoded_bucket = base64.urlsafe_b64encode(save_bucket.encode()).decode()
encoded_object = base64.urlsafe_b64encode(save_object.encode()).decode()
try:
output = client.get_object(
bucket=bucket,
key=key,
process=process_value,
save_bucket=encoded_bucket,
save_object=encoded_object,
)
raw = output.read()
except TosServerError as e:
print(
f"[ERROR] TOS server error: code={e.code}, status={e.status_code}, "
f"request_id={e.request_id}, message={e.message}",
file=sys.stderr,
)
sys.exit(1)
except TosClientError as e:
print(f"[ERROR] TOS client error: {e}", file=sys.stderr)
sys.exit(1)
try:
data = json.loads(raw.decode("utf-8"))
except Exception as exc: # noqa: BLE001
print("[ERROR] Failed to parse save result as JSON:", file=sys.stderr)
print(exc, file=sys.stderr)
print(raw[:200], file=sys.stderr)
sys.exit(1)
emit(
{
**plan,
"saveas_bucket": save_bucket,
"saveas_object": save_object,
"result": data,
},
args.json,
"[OK] Image saved to TOS:",
)
return
output_path = args.output or default_output_path(key)
if not args.json:
print(f"[INFO] Watermarking {bucket}/{key} -> {output_path}")
print(f"[INFO] process = {process_value}")
try:
client.get_object_to_file(
bucket=bucket, key=key, file_path=output_path, process=process_value
)
except TosServerError as e:
print(
f"[ERROR] TOS server error: code={e.code}, status={e.status_code}, "
f"request_id={e.request_id}, message={e.message}",
file=sys.stderr,
)
sys.exit(1)
except TosClientError as e:
print(f"[ERROR] TOS client error: {e}", file=sys.stderr)
sys.exit(1)
size = os.path.getsize(output_path)
emit(
{**plan, "output_path": output_path, "size": size},
args.json,
f"[OK] Image saved to {output_path} ({size} bytes)",
)
if __name__ == "__main__":
main()
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#!/usr/bin/env python3
"""Zoom an image by chaining resize and crop operations.
This script is designed for agent-friendly "zoom" usage where the final result
is typically produced by:
1. Resize the source image.
2. Crop a target region from the resized image.
The generated process string looks like:
image/resize,.../crop,...
"""
import argparse
import base64
import json
import os
import re
import sys
from typing import Optional
import tos
from tos.exceptions import TosClientError, TosServerError
COLOR_RE = re.compile(r"^[0-9A-Fa-f]{6}$")
VALID_GRAVITIES = {
"northwest",
"north",
"northeast",
"west",
"center",
"east",
"southwest",
"south",
"southeast",
}
def get_env(name: str, required: bool = True, default: Optional[str] = None) -> str:
value = os.getenv(name, default)
if required and not value:
print(f"[ERROR] Environment variable {name} is required.", file=sys.stderr)
sys.exit(1)
return value # type: ignore[return-value]
def create_client() -> tos.TosClientV2:
ak = get_env("TOS_ACCESS_KEY")
sk = get_env("TOS_SECRET_KEY")
endpoint = get_env("TOS_ENDPOINT")
region = get_env("TOS_REGION")
security_token = os.getenv("TOS_SECURITY_TOKEN")
print(
f"[INFO] Initializing TOS client for endpoint={endpoint}, region={region} ..."
)
return tos.TosClientV2(
ak=ak,
sk=sk,
endpoint=endpoint,
region=region,
security_token=security_token,
)
def maybe_print_json(raw: bytes) -> bool:
try:
text = raw.decode("utf-8")
data = json.loads(text)
print(json.dumps(data, indent=2, ensure_ascii=False))
return True
except Exception:
return False
def build_process_value(args: argparse.Namespace) -> str:
resize_parts = []
if args.resize_w is not None:
resize_parts.append(f"w_{args.resize_w}")
if args.resize_h is not None:
resize_parts.append(f"h_{args.resize_h}")
if args.resize_l is not None:
resize_parts.append(f"l_{args.resize_l}")
if args.resize_s is not None:
resize_parts.append(f"s_{args.resize_s}")
if args.resize_wp is not None:
resize_parts.append(f"wp_{args.resize_wp}")
if args.resize_hp is not None:
resize_parts.append(f"hp_{args.resize_hp}")
if args.resize_p is not None:
resize_parts.append(f"p_{args.resize_p}")
if args.resize_area is not None:
resize_parts.append(f"area_{args.resize_area}")
if args.resize_mode:
resize_parts.append(f"m_{args.resize_mode}")
if args.resize_color:
resize_parts.append(f"color_{args.resize_color.upper()}")
crop_parts = []
if args.crop_w is not None:
crop_parts.append(f"w_{args.crop_w}")
if args.crop_h is not None:
crop_parts.append(f"h_{args.crop_h}")
if args.crop_l is not None:
crop_parts.append(f"l_{args.crop_l}")
if args.crop_s is not None:
crop_parts.append(f"s_{args.crop_s}")
if args.x is not None:
crop_parts.append(f"x_{args.x}")
if args.y is not None:
crop_parts.append(f"y_{args.y}")
if args.gravity:
crop_parts.append(f"g_{args.gravity}")
process = "image/resize," + ",".join(resize_parts)
if crop_parts:
process += "/crop," + ",".join(crop_parts)
return process
def main() -> None:
parser = argparse.ArgumentParser(description="Zoom image by resize + crop")
parser.add_argument("--bucket", type=str, default=None, help="Override TOS_BUCKET")
parser.add_argument("--key", type=str, default=None, help="Override TOS_OBJECT_KEY")
parser.add_argument("--resize-w", type=int, default=None, help="Resize width")
parser.add_argument("--resize-h", type=int, default=None, help="Resize height")
parser.add_argument("--resize-l", type=int, default=None, help="Resize long side")
parser.add_argument("--resize-s", type=int, default=None, help="Resize short side")
parser.add_argument(
"--resize-wp", type=int, default=None, help="Resize width percentage"
)
parser.add_argument(
"--resize-hp", type=int, default=None, help="Resize height percentage"
)
parser.add_argument("--resize-p", type=int, default=None, help="Resize percentage")
parser.add_argument(
"--resize-area", type=int, default=None, help="Resize area in pixels"
)
parser.add_argument(
"--resize-mode",
type=str,
default="fill",
help="Resize mode, e.g. fill, pad, lfit, mfit",
)
parser.add_argument(
"--resize-color",
type=str,
default=None,
help="Optional resize background color for pad-like modes, e.g. FFFFFF",
)
parser.add_argument("--crop-w", type=int, default=None, help="Crop width")
parser.add_argument("--crop-h", type=int, default=None, help="Crop height")
parser.add_argument("--crop-l", type=int, default=None, help="Crop long side")
parser.add_argument("--crop-s", type=int, default=None, help="Crop short side")
parser.add_argument("--x", type=int, default=None, help="Crop x offset")
parser.add_argument("--y", type=int, default=None, help="Crop y offset")
parser.add_argument(
"--gravity",
type=str,
default="center",
help="Crop gravity: northwest/north/northeast/west/center/east/southwest/south/southeast",
)
parser.add_argument(
"--output", type=str, default=None, help="Local output file path"
)
parser.add_argument(
"--saveas-bucket", type=str, default=None, help="Save result to this bucket"
)
parser.add_argument(
"--saveas-object", type=str, default=None, help="Save result as this object key"
)
parser.add_argument("--json", action="store_true", help="Print machine-readable JSON only")
parser.add_argument("--dry-run", action="store_true", help="Print resolved request and exit")
args = parser.parse_args()
if not any(
value is not None
for value in (
args.resize_w,
args.resize_h,
args.resize_l,
args.resize_s,
args.resize_wp,
args.resize_hp,
args.resize_p,
args.resize_area,
)
):
print("[ERROR] At least one resize argument must be provided.", file=sys.stderr)
sys.exit(1)
if args.resize_color and not COLOR_RE.match(args.resize_color):
print(
"[ERROR] --resize-color must be a 6-digit RGB hex string.", file=sys.stderr
)
sys.exit(1)
if args.gravity not in VALID_GRAVITIES:
print(
f"[ERROR] --gravity must be one of: {', '.join(sorted(VALID_GRAVITIES))}",
file=sys.stderr,
)
sys.exit(1)
client = create_client()
bucket = args.bucket or get_env("TOS_BUCKET")
key = args.key or get_env("TOS_OBJECT_KEY")
process_value = build_process_value(args)
save_bucket = args.saveas_bucket
save_object = args.saveas_object
persist_to_tos = bool(save_bucket or save_object)
print(f"[INFO] process = {process_value}")
if persist_to_tos:
save_bucket = save_bucket or bucket
if not save_object:
save_object = f"zoom_{os.path.basename(key)}"
encoded_bucket = base64.urlsafe_b64encode(save_bucket.encode()).decode()
encoded_object = base64.urlsafe_b64encode(save_object.encode()).decode()
print(f"[INFO] Processing {bucket}/{key} -> {save_bucket}/{save_object}")
try:
output = client.get_object(
bucket=bucket,
key=key,
process=process_value,
save_bucket=encoded_bucket,
save_object=encoded_object,
)
raw = output.read()
except TosServerError as e:
print(
f"[ERROR] TOS server error: code={e.code}, status={e.status_code}, "
f"request_id={e.request_id}, message={e.message}",
file=sys.stderr,
)
sys.exit(1)
except TosClientError as e:
print(f"[ERROR] TOS client error: {e}", file=sys.stderr)
sys.exit(1)
print("[OK] Save result:")
if not maybe_print_json(raw):
print(raw.decode("utf-8", errors="replace"))
return
if not args.output:
print(
"[ERROR] --output is required when not saving back to TOS.", file=sys.stderr
)
sys.exit(1)
print(f"[INFO] Processing {bucket}/{key} -> {args.output}")
try:
client.get_object_to_file(
bucket=bucket,
key=key,
file_path=args.output,
process=process_value,
)
except TosServerError as e:
print(
f"[ERROR] TOS server error: code={e.code}, status={e.status_code}, "
f"request_id={e.request_id}, message={e.message}",
file=sys.stderr,
)
sys.exit(1)
except TosClientError as e:
print(f"[ERROR] TOS client error: {e}", file=sys.stderr)
sys.exit(1)
size = os.path.getsize(args.output)
print(f"[OK] Output saved to {args.output} ({size} bytes)")
if __name__ == "__main__":
main()
Bytedance TOS Image Process Workflows
This document illustrates common end-to-end workflows for image processing using the TOS Python SDK and the scripts provided in this skill.
Table of Contents
- Workflow 1: Getting Image Information
- Workflow 2: Resizing an Image and Saving Locally
- Workflow 3: Converting Format and Saving back to TOS
- Workflow 4: Drawing Points and Lines
- Workflow 5: Creating a Zoom Crop
- Workflow 6: Applying a Text Watermark
- Workflow 7: Batch Processing Multiple Images
- Workflow 8: Handling Errors
- Workflow 9: AI-Powered Image Understanding
- Workflow 10: Smart Annotation Pipeline (Orchestration)
---
Workflow 1: Getting Image Information
Goal: Retrieve the metadata (format, dimensions, etc.) of an image stored in TOS.
Script: scripts/image_info.py
1. Set Environment:
export TOS_ACCESS_KEY="YOUR_AK"
export TOS_SECRET_KEY="YOUR_SK"
export TOS_ENDPOINT="https://tos-cn-beijing.volces.com"
export TOS_REGION="cn-beijing"
export TOS_BUCKET="my-source-bucket"
export TOS_OBJECT_KEY="images/archive/photo-01.jpg"2. Execute:
python3 scripts/image_info.py --key images/archive/photo-01.jpg --json3. Behavior Notes:
--bucket/--keyoverride environment defaults.--jsonreturns a machine-readable payload.- If
image/inforeturns raw bytes rather than JSON, the script falls back to local parsing and reports basic metadata.
---
Workflow 2: Resizing an Image and Saving Locally
Goal: Resize an image to a 500-pixel width and save the result to the local filesystem.
Script: scripts/image_resize.py
1. Set Environment: (Same as Workflow 1)
2. Execute:
python3 scripts/image_resize.py --key images/archive/photo-01.jpg --w 500 --output resized_local.jpg --dry-run
python3 scripts/image_resize.py --key images/archive/photo-01.jpg --w 500 --output resized_local.jpg3. Behavior Notes:
--dry-runprints the resolved process and output target before calling TOS.--jsonreturns a machine-readable payload for either local save or TOS persistence mode.
---
Workflow 3: Converting Format and Saving back to TOS
Goal: Convert an image to WebP format with 80% quality and save it to another location in TOS.
Script: scripts/image_format.py
1. Set Environment: (Same as Workflow 1)
2. Execute:
python3 scripts/image_format.py \
--key images/archive/photo-01.jpg \
--f webp \
--q 80 \
--saveas-bucket my-output-bucket \
--saveas-object processed/photo-01.webp \
--json3. Behavior Notes:
--saveas-bucket/--saveas-objectswitch the script into TOS-to-TOS mode.--jsonreturns a structured payload including the resolved process string and TOS save result.--dry-runprints the resolved request without calling TOS.
---
Workflow 4: Drawing Points and Lines
Goal: Mark several points and connect them with lines so an agent can highlight positions of interest.
Script: scripts/image_draw.py
# Draw a polyline (open path)
python3 scripts/image_draw.py \
--key test.jpg \
--points 50x50-200x120-320x220 \
--line \
--color FF0000 \
--output draw.jpgNote: --line connects points in order but does not auto-close. To draw a closed rectangle, repeat the first point at the end:
# Draw a closed rectangle: A-B-C-D-A
python3 scripts/image_draw.py \
--key test.jpg \
--points 100x100-400x100-400x300-100x300-100x100 \
--line \
--line-width 3 \
--color FF0000 \
--saveas-bucket my-bucket \
--saveas-object output/boxed.jpg---
Workflow 5: Creating a Zoom Crop
Goal: First enlarge the source view and then crop a focused center region.
Script: scripts/image_zoom.py
python3 scripts/image_zoom.py \
--key test.jpg \
--resize-w 1200 \
--crop-w 500 \
--crop-h 400 \
--gravity center \
--output zoom.jpg---
Workflow 6: Applying a Text Watermark
Goal: Add a semi-transparent text watermark to the bottom-right corner of an image.
Script: scripts/image_watermark.py
1. Set Environment: (Same as Workflow 1)
2. Prepare Parameters: Watermark text and colors must be Base64-encoded. The helper script can encode raw text and font names for you, so you can usually provide readable values directly.
3. Execute:
python3 scripts/image_watermark.py \
--text "2026 MyCorp" \
--font fangzhengshusong \
--color FF0000 \
--size 72 \
--gravity se \
--x 20 \
--y 20 \
--output "watermarked.jpg"Note: For pre-encoded official values, you can switch to `--text-b64`, `--font-b64`, and `--image-b64`.
4. SDK Logic (`image_watermark.py`):
# Constructs the process string from the official watermark model
process_rule = "image/watermark,text_MjAyNiBNeUNvcnA,type_ZmFuZ3poZW5nc2h1c29uZw,color_FF0000,size_72,g_se,x_20,y_20"
client.get_object_to_file(
bucket=...,
key=...,
file_path="watermarked.jpg",
process=process_rule
)---
Workflow 6A: Applying a Blind Watermark
Goal: Embed a blind watermark when the capability is enabled for the current account or bucket.
Script: scripts/image_blindwatermark.py
Prerequisites:
- Blind watermark capability must be enabled in the TOS console.
- The source image must be at least 512×512 pixels.
1. Execute:
python3 scripts/image_blindwatermark.py \
--kv text=HelloBlind \
--output "blindwatermarked.jpg"2. Behavior Notes:
- If the capability is enabled and the image is large enough, the script saves the output and prints
[OK]. - If the capability is not enabled, the script prints
[SKIP]and exits successfully by default. Add--strictto fail the command instead. - If the image is smaller than 512×512 pixels, the script prints a clear error message and exits with code 1.
---
Workflow 7: Batch Processing Multiple Images
Goal: Resize a list of images from a source folder in TOS to a destination folder.
This requires a custom script that iterates and calls the SDK.
1. Set Environment: (Same as Workflow 1, but TOS_OBJECT_KEY is not used).
2. Custom Batch Script (Conceptual):
import os
import tos
# Assumes client is initialized
source_bucket = "my-source-bucket"
dest_bucket = "my-output-bucket"
image_keys = ["source/img1.jpg", "source/img2.jpg", "source/img3.jpg"]
for key in image_keys:
dest_key = key.replace("source/", "resized/")
print(f"Processing {source_bucket}/{key} -> {dest_bucket}/{dest_key}")
try:
client.get_object(
bucket=source_bucket,
key=key,
process="image/resize,w_1200",
save_bucket=dest_bucket,
save_object=dest_key
)
except tos.exceptions.TosServerError as e:
print(f" [ERROR] Failed for {key}: {e.message}")
print("Batch processing complete.")---
Workflow 8: Handling Errors
Goal: Gracefully handle potential errors from the TOS SDK.
All provided scripts include try...except blocks to catch TosServerError and TosClientError.
SDK Logic:
try:
# ... SDK call ...
client.get_object(bucket="non-existent-bucket", key="invalid-key", process="image/info")
except tos.exceptions.TosServerError as e:
# For server-side errors (e.g., object not found, invalid parameters)
print(f"TOS Server Error:")
print(f" - Status: {e.status_code}")
print(f" - Code: {e.code}")
print(f" - Message: {e.message}")
print(f" - Request ID: {e.request_id}")
sys.exit(1)
except tos.exceptions.TosClientError as e:
# For client-side issues (e.g., network error, invalid credentials)
print(f"TOS Client Error: {e}")
sys.exit(1)This ensures that failures are caught and reported with meaningful diagnostic information, such as the request_id, which is crucial for troubleshooting with support teams.
---
Workflow 9: AI-Powered Image Understanding
Goal: Use a VLM (Vision Language Model) to understand image content — describe, OCR, detect faces, or answer visual questions.
Script: scripts/image_understanding.py
1. Set Environment: (Same as Workflow 1)
2. Execute (describe image):
python3 scripts/image_understanding.py \
--key photo.jpg \
--prompt "Describe this image in detail"3. Execute (OCR):
python3 scripts/image_understanding.py \
--key document.png \
--prompt "识别图片中的所有文字内容"4. Execute (save result to TOS):
python3 scripts/image_understanding.py \
--key photo.jpg \
--prompt "What objects are in this image?" \
--saveas-bucket my-output-bucket \
--saveas-object results/understanding.json5. Behavior Notes:
- The
--promptparameter is required. It accepts any natural language question or instruction. - Default model is
doubao-seed-1.6-vision. Override with--model. --jsonreturns a machine-readable payload.--dry-runprints the resolved process string before execution.- Response time is typically 10-60 seconds due to VLM inference.
- Requires account whitelist. If not whitelisted, the script will return an error.
- The result is a JSON object with a
contentfield containing the model's response.
---
Workflow 10: Smart Annotation Pipeline (Orchestration)
Goal: Automatically understand an image and produce an annotated, watermarked result — a multi-step pipeline an agent can orchestrate.
Pipeline: image_info → image_understanding → image_draw → image_watermark
Step 1: Get image dimensions
python3 scripts/image_info.py --key test.jpg --jsonUse the returned ImageWidth and ImageHeight to decide annotation coordinates for the next steps.
Step 2: Understand image content with VLM
python3 scripts/image_understanding.py \
--key test.jpg \
--prompt "请描述这张图片的内容,包括场景、主体物体和氛围" \
--jsonThe content field contains the model's description. Use this to decide what label to draw and where to place the bounding box.
Step 3: Draw a bounding box around the subject
python3 scripts/image_draw.py \
--key test.jpg \
--points 200x80-520x80-520x380-200x380-200x80 \
--line \
--line-width 3 \
--color FF0000 \
--saveas-bucket my-bucket \
--saveas-object smart-annotate/boxed.jpgImportant: Repeat the first point at the end to close the rectangle (the --line flag does not auto-close).
Step 4: Add a text label as watermark
python3 scripts/image_watermark.py \
--key smart-annotate/boxed.jpg \
--text "Go Gopher" \
--font wqy-zenhei \
--color FFFFFF \
--size 30 \
--opacity 80 \
--gravity nw \
--x 10 \
--y 10 \
--saveas-bucket my-bucket \
--saveas-object smart-annotate/final.jpg \
--jsonThe final result is a single image with a red bounding box around the detected subject and a text label in the corner.
Agent orchestration notes
- All steps support
--jsonfor machine-readable output, making it easy for an agent to parse results and feed them into the next step. - Use
--dry-runon any step to preview the resolved request before execution. - Step 3 depends on Step 1 (image dimensions) and Step 2 (content understanding) to determine coordinates and label text. An agent should extract
ImageWidth/ImageHeightfrom Step 1 andcontentfrom Step 2, then compute bounding box coordinates accordingly.