Skill: volcengine-skills/byted-tos-image-process (version 1.0.0)

--- name: byted-tos-image-process version: 1.0.0 description: Provides image processing capabilities for objects in Bytedance TOS using the official SDK. Supports getting image info, format conversion, resizing, and watermarking. Use when you need to analyze or transform images stored in TOS. homepage: https://www.volcengine.com/docs/6349/153623 --- # Bytedance TOS Image Process Skill This skill provides essential image processing functions for files stored in Bytedance's TOS (TeraObjectStore). It allows you to retrieve image metadata, convert formats, resize, and apply watermarks directly using the Volcengine TOS SDK. ## Quick Start ### 1. Client Initialization The following Python snippet demonstrates how to initialize the `TosClientV2` from environment variables. ```python import os import tos from tos.exceptions import TosClientError, TosServerError def create_client() -> tos.TosClientV2: """Initializes a TosClientV2 using AK/SK (and optional STS token) from environment variables.""" try: ak = os.getenv('TOS_ACCESS_KEY') sk = os.getenv('TOS_SECRET_KEY') endpoint = os.getenv('TOS_ENDPOINT') region = os.getenv('TOS_REGION') security_token = os.getenv('TOS_SECURITY_TOKEN') # Optional, for STS if not all([ak, sk, endpoint, region]): raise ValueError("Required environment variables are missing (AK, SK, Endpoint, Region).") return tos.TosClientV2( ak=ak, sk=sk, endpoint=endpoint, region=region, security_token=security_token, ) except (ValueError, ImportError) as e: print(f"Error initializing client: {e}") return None # Create the client client = create_client() ``` ### 2. Basic Workflow ```python # (Assumes 'client' is initialized and 'bucket_name', 'object_key' are set) # 1. Get Image Info try: response = client.get_object(bucket_name, object_key, process="image/info") info_data = response.read() print("Image Info:", info_data.decode('utf-8')) except TosServerError as e: print(f"Error getting image info: {e}") # 2. Resize an Image and save locally try: client.get_object_to_file( bucket_name, object_key, "resized_image.jpg", process="image/resize,w_500,m_lfit" # Resize to 500px width, lfit mode ) print("Resized image saved to resized_image.jpg") except TosServerError as e: print(f"Error resizing image: {e}") # 3. Convert Image to WebP and save back to TOS try: response = client.get_object( bucket_name, object_key, process="image/format,f_webp,q_80", # Convert to WebP, quality 80 save_bucket="my-output-bucket", save_object="processed/image.webp" ) save_result = response.read() print("Converted image saved to TOS:", save_result.decode('utf-8')) except TosServerError as e: print(f"Error saving converted image to TOS: {e}") ``` ## Core Operations All image processing is achieved by passing a `process` string to the `get_object` or `get_object_to_file` SDK methods. ### 1. Get Image Info (`ImageInfo`) Retrieves metadata of an image file, such as format, dimensions, and EXIF data. **SDK Method**: `client.get_object(..., process="image/info")` ```python response = client.get_object(bucket_name, object_key, process="image/info") image_metadata = response.read().decode('utf-8') print(image_metadata) ``` ### 2. Convert Image Format (`ImageFormat`) Converts an image to a different format (e.g., JPEG, PNG, WebP) and adjusts quality. **SDK Method**: `client.get_object_to_file(..., process="image/format,f_webp,q_80")` ```python # Convert to PNG format client.get_object_to_file( bucket_name, object_key, "output.png", process="image/format,f_png" ) ``` ### 3. Resize Image (`ImageResize`) Resizes an image based on specified width, height, and resizing mode. **SDK Method**: `client.get_object_to_file(..., process="image/resize,w_800,h_600,m_fill")` ```python # Resize to a maximum width of 1024px, maintaining aspect ratio client.get_object_to_file( bucket_name, object_key, "resized_1024.jpg", process="image/resize,w_1024" ) ``` ### 4. Apply Watermark (`ImageWatermark` & `ImageBlindWatermark`) Adds a visible or blind watermark to an image. Parameters are complex and should be constructed according to the official TOS documentation. **SDK Method**: `client.get_object_to_file(..., process="image/watermark,...")` ```python # Example for a text watermark (parameters must be Base64-encoded) # This is a conceptual example. Refer to official docs for exact keys. import base64 text_b64 = base64.b64encode("My Watermark".encode()).decode() process_rule = f"image/watermark,type_1,text_{text_b64},size_40,p_9" client.get_object_to_file( bucket_name, object_key, "watermarked.jpg", process=process_rule ) ``` ### 5. Generic Image Processing (`ImageProcess`) A flexible entry point that accepts any valid image processing string. **SDK Method**: `client.get_object(..., process="<full-process-string>")` ```python # Example: Apply a Gaussian blur (hypothetical parameters) client.get_object_to_file( bucket_name, object_key, "blurred.jpg", process="image/blur,r_5,s_2" ) ``` ## Authorization Authentication is handled by `tos.TosClientV2`. Provide credentials via environment variables. ### Required Environment Variables - `TOS_ACCESS_KEY` - `TOS_SECRET_KEY` - `TOS_ENDPOINT` - `TOS_REGION` ### Optional for STS - `TOS_SECURITY_TOKEN` ## Best Practices - **Error Handling**: Wrap SDK calls in `try...except` blocks to handle `TosClientError` and `TosServerError`. - **Parameter Construction**: For complex operations like watermarking, carefully construct the `process` string according to the official TOS documentation. Base64-encode parameter values where required. - **Client Reuse**: Initialize the `TosClientV2` once and reuse it for multiple operations. ## Additional Resources - For detailed parameters of each operation, see [REFERENCE.md](REFERENCE.md). - For common end-to-end examples, see [WORKFLOWS.md](WORKFLOWS.md). - For executable Python examples, see the `scripts/` directory. - **For the definitive list of all processing parameters, always consult the official Volcengine TOS Image Processing documentation.**

path: WORKFLOWS.md
content: # 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-1-getting-image-information)
- [Workflow 2: Resizing an Image and Saving Locally](#workflow-2-resizing-an-image-and-saving-locally)
- [Workflow 3: Converting Format and Saving back to TOS](#workflow-3-converting-format-and-saving-back-to-tos)
- [Workflow 4: Applying a Text Watermark](#workflow-4-applying-a-text-watermark)
- [Workflow 5: Batch Processing Multiple Images](#workflow-5-batch-processing-multiple-images)
- [Workflow 6: Handling Errors](#workflow-6-handling-errors)

---

### 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**:
    ```bash
    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**:
    ```bash
    python3 scripts/image_info.py
    ```

3.  **SDK Logic (`image_info.py`)**:
    ```python
    # Initializes client from env vars
    client = create_client()
    
    # Calls get_object with the specific process string
    response = client.get_object(
        bucket=os.getenv("TOS_BUCKET"),
        key=os.getenv("TOS_OBJECT_KEY"),
        process="image/info"
    )
    
    # Reads and parses the JSON response
    info = json.loads(response.read().decode('utf-8'))
    print(json.dumps(info, indent=2))
    ```

---

### 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**:
    ```bash
    python3 scripts/image_resize.py --w 500 --output "resized_local.jpg"
    ```

3.  **SDK Logic (`image_resize.py`)**:
    ```python
    # Constructs the process string from CLI arguments
    process_rule = "image/resize,w_500"
    
    # Calls get_object_to_file to stream the result directly to a local file
    client.get_object_to_file(
        bucket=os.getenv("TOS_BUCKET"),
        key=os.getenv("TOS_OBJECT_KEY"),
        file_path="resized_local.jpg",
        process=process_rule
    )
    
    print("Image saved to resized_local.jpg")
    ```

---

### 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**:
    ```bash
    python3 scripts/image_format.py \
      --f webp \
      --q 80 \
      --saveas-bucket "my-output-bucket" \
      --saveas-object "processed/photo-01.webp"
    ```

3.  **SDK Logic (`image_format.py`)**:
    ```python
    # Constructs the process string
    process_rule = "image/format,f_webp,q_80"
    
    # Calls get_object with save_bucket and save_object to persist in TOS
    response = client.get_object(
        bucket=os.getenv("TOS_BUCKET"),
        key=os.getenv("TOS_OBJECT_KEY"),
        process=process_rule,
        save_bucket="my-output-bucket",
        save_object="processed/photo-01.webp"
    )
    
    # The response contains JSON metadata about the saved object
    save_result = json.loads(response.read().decode('utf-8'))
    print("Save result:", save_result)
    ```

---

### Workflow 4: 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.
    ```bash
    # Text: "© 2026 MyCorp"
    TEXT_B64=$(echo -n "© 2026 MyCorp" | base64)
    
    # Color: White (#FFFFFF)
    COLOR_B64=$(echo -n "#FFFFFF" | base64)
    ```

3.  **Execute**:
    ```bash
    python3 scripts/image_watermark.py \
      --kv type=1 \
      --kv text=${TEXT_B64} \
      --kv fill=${COLOR_B64} \
      --kv size=30 \
      --kv p=9 \
      --kv dx=20 \
      --kv dy=20 \
      --output "watermarked.jpg"
    ```
    *Note: The `kv` keys (`type`, `text`, `fill`, `size`, `p`, `dx`, `dy`) must match the official TOS documentation for text watermarks.*

4.  **SDK Logic (`image_watermark.py`)**:
    ```python
    # Constructs the process string from all --kv arguments
    process_rule = "image/watermark,type_1,text_wr...,fill_I0Z...,size_30,p_9,dx_20,dy_20"
    
    client.get_object_to_file(
        bucket=...,
        key=...,
        file_path="watermarked.jpg",
        process=process_rule
    )
    ```

---

### Workflow 5: 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)**:
    ```python
    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 6: 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**:
```python
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.


path: REFERENCE.md
content: # 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](#authentication)
- [Core Operations](#core-operations)
  - [1. `ImageInfo`](#1-imageinfo)
  - [2. `ImageFormat`](#2-imageformat)
  - [3. `ImageResize`](#3-imageresize)
  - [4. `ImageWatermark`](#4-imagewatermark)
  - [5. `ImageBlindWatermark`](#5-imageblindwatermark)
  - [6. `ImageProcess` (Generic)](#6-imageprocess-generic)
- [Data Models](#data-models)
  - [ImageInfo Object](#imageinfo-object)
  - [ProcessSaveResult 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:**
- The SDK response body contains a `bytes` object with a JSON string.
- This JSON string represents the [ImageInfo Object](#imageinfo-object).

### 2. `ImageFormat`

Converts the image to a different format and/or adjusts its quality.

**Process String:** `image/format,f_<format>,q_<quality>`

**Common Options:**

| Option | SDK Equivalent    | Description                                       |
|--------|-------------------|---------------------------------------------------|
| `f`    | `format` (string) | Target format. Common values: `jpg`, `png`, `webp`. |
| `q`    | `quality` (int)   | Quality for lossy formats (e.g., 1-100 for JPG).  |

**Example `process` string:** `"image/format,f_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. `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):**

- `type`: Type of watermark (e.g., text, image).
- `text`/`image`: The content of the watermark. **Values must be Base64-encoded.**
- `size`/`font`: Font size or image size.
- `color`/`fill`: Color of the text.
- `p`: Position of the watermark (e.g., `1` for top-left, `9` for bottom-right).
- `dx`/`dy`: Offsets in pixels.

**Example `process` string (conceptual):** `"image/watermark,type_1,text_SGVsbG8gV29ybGQ=,p_9,size_40"`

### 5. `ImageBlindWatermark`

Adds or extracts a blind (invisible) watermark.

**Process String:** `image/blindwatermark,<param1>_<value1>,...`

**Conceptual Parameters (refer to official docs):**

- `type`: Operation type (e.g., `1` for encoding, `2` for decoding).
- `text`/`image`: Watermark content to embed (Base64-encoded).

### 6. `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,f_png"` (Syntax may vary, check official docs for chaining rules).

---

## Data Models

### ImageInfo Object

A JSON object containing detailed information about the image, as returned by the `image/info` process.

| 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).|

**Example Snippet:**
```json
{
  "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.                     |


path: requirements.txt
content: tos


path: README.md
content: # Bytedance TOS Image Process Skill

This skill enables an agent to perform basic image processing tasks on files stored in Bytedance's TOS (TeraObjectStore) by directly using the Volcengine TOS Python SDK.

## Overview

The skill allows an agent to perform various image processing operations by constructing a `process` string and passing it to the TOS SDK. Key capabilities include:

1.  **Get Image Information**: Retrieve metadata like format, size, and EXIF info (`image/info`).
2.  **Format Conversion**: Convert images between formats like JPG, PNG, and WebP (`image/format`).
3.  **Resizing**: Scale images with various modes (`image/resize`).
4.  **Watermarking**: Apply text or image watermarks (`image/watermark`).
5.  **Blind Watermarking**: Embed or extract blind watermarks (`image/blindwatermark`).
6.  **Generic Processing**: A general-purpose entry point to support any other `image/...` operations available in TOS.

All operations support saving the output locally or persisting it back to a TOS bucket.

## Skill Structure

```
byted-tos-image-process/
├── SKILL.md              # Main skill file with quick start and core operations
├── README.md             # This file: detailed setup and environment guide
├── REFERENCE.md          # Complete parameter reference for each operation
├── WORKFLOWS.md          # Common end-to-end workflow examples
├── scripts/              # Executable Python scripts demonstrating each core capability
│   ├── image_info.py         # Script to get image information
│   ├── image_format.py       # Script for format conversion
│   ├── image_resize.py       # Script for resizing
│   ├── image_watermark.py    # Script for visible watermarks
│   ├── image_blindwatermark.py # Script for blind watermarks
│   └── image_process.py      # Generic script for any image process string
├── requirements.txt      # Python dependencies for the scripts
```

## Setup and Configuration

### Requirements
- Python 3.7+
- `tos` Python SDK

### Installation

Install the necessary Python library:
```bash
pip install tos
```

### Environment Variables

All scripts rely on environment variables for configuration.

| Variable                 | Description                                                  | Example                                    |
| ------------------------ | ------------------------------------------------------------ | ------------------------------------------ |
| `TOS_ACCESS_KEY`         | Your Access Key ID for TOS authentication.                   | `AKL...`                                   |
| `TOS_SECRET_KEY`         | Your Secret Access Key for TOS authentication.               | `your-secret-key`                          |
| `TOS_SECURITY_TOKEN`     | (Optional) The session token for temporary STS credentials.  | `STS...`                                   |
| `TOS_ENDPOINT`           | The full URL of the TOS service endpoint.                    | `https://tos-cn-beijing.volces.com`        |
| `TOS_REGION`             | The region of the TOS service.                               | `cn-beijing`                               |
| `TOS_BUCKET`             | The name of the bucket where the target image is stored.     | `my-image-assets`                          |
| `TOS_OBJECT_KEY`         | The full path (key) of the image file within the bucket.     | `input/photos/landscape.jpg`               |
| `MAX_OBJECT_SIZE`        | (Optional) Safeguard for local file size, in bytes.          | `262144` (default)                         |
| `TOS_SAVEAS_BUCKET`      | (Optional) The default bucket to save processed results into.| `my-processed-images`                      |
| `TOS_SAVEAS_OBJECT_PREFIX`| (Optional) A prefix for the object key when saving results.  | `output/resized/`                          |

**Security Note**: For production environments, use short-lived STS (Security Token Service) credentials. The SDK automatically uses `TOS_SECURITY_TOKEN` if present.

## Quick Start

### 1. Set Environment Variables

```bash
# --- Required ---
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"
```

### 2. Run Example Scripts

The `scripts/` directory contains ready-to-run examples.

**Get Image Info:**
```bash
python3 scripts/image_info.py
```

**Convert Image to WebP with Quality 80:**
```bash
python3 scripts/image_format.py --f webp --q 80 --output converted.webp
```

**Resize Image to 500px Width:**
```bash
python3 scripts/image_resize.py --w 500 --output resized.jpg
```

**Add a Text Watermark and Save back to TOS:**
```bash
# Parameters `type`, `text`, `size` etc., are passed via --kv
# The keys and values must match TOS API documentation for image watermarks.
# NOTE: Text for watermarks must be Base64-encoded.
TEXT_B64=$(echo -n "© My Brand" | base64)

python3 scripts/image_watermark.py \
  --kv type=1 \
  --kv text=${TEXT_B64} \
  --kv fill=I0ZGRkZGRg== \
  --kv size=40 \
  --kv p=9 \
  --saveas-bucket "your-output-bucket" \
  --saveas-object "watermarked/image.jpg"
```

**Use the Generic Processor:**
This script allows you to pass any valid process string.
```bash
python3 scripts/image_process.py \
  --process "image/resize,w_300,h_300,m_fill" \
  --output "filled_300x300.jpg"
```

## Note on Parameters

The specific key-value pairs for image processing (`format`, `resize`, `watermark`, etc.) are defined by the TOS service. **This skill's scripts provide common arguments (`--w`, `--h`, `--f`, etc.) for convenience, but all other parameters must be passed using the `--kv key=value` syntax.** Always refer to the official Volcengine TOS documentation for the most up-to-date and complete list of parameters for each image processing operation.


path: scripts/image_process.py
content: # 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
  - MAX_OBJECT_SIZE (default: 262144)

Note: The process syntax and option keys are subject to the official TOS documentation.
"""

import argparse
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")
    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}")

        try:
            output = client.get_object(
                bucket=bucket,
                key=key,
                process=args.process,
                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)

        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)

    max_object_size = int(os.getenv("MAX_OBJECT_SIZE", "262144"))
    size = os.path.getsize(output_path)
    if size > max_object_size:
        print(
            f"[ERROR] Output size ({size} bytes) exceeds MAX_OBJECT_SIZE={max_object_size}. Deleting local file.",
            file=sys.stderr,
        )
        try:
            os.remove(output_path)
        except OSError:
            pass
        sys.exit(1)

    print(f"[OK] Output saved to {output_path} ({size} bytes)")


if __name__ == "__main__":
    main()


path: scripts/image_info.py
content: # 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 the JSON result.

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

Optional:
  - MAX_OBJECT_SIZE       Maximum allowed local output size in bytes (default: 262144)

This script supports:
  - local save: via `get_object_to_file` 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 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 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 JSON response 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")
    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)

        try:
            data = load_json_file(output_path)
        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)

        max_object_size = int(os.getenv("MAX_OBJECT_SIZE", "262144"))
        size = os.path.getsize(output_path)
        if size > max_object_size:
            print(
                f"[ERROR] Output size ({size} bytes) exceeds MAX_OBJECT_SIZE={max_object_size}.",
                file=sys.stderr,
            )
            sys.exit(1)

        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)

    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:")
    print(json.dumps(data, indent=2, ensure_ascii=False))


if __name__ == "__main__":
    main()


path: scripts/image_blindwatermark.py
content: # 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.

Environment variables:
  - TOS_ACCESS_KEY, TOS_SECRET_KEY, TOS_SECURITY_TOKEN(optional)
  - TOS_ENDPOINT, TOS_REGION
  - TOS_BUCKET, TOS_OBJECT_KEY
  - MAX_OBJECT_SIZE (default: 262144)

Note: Parameter semantics are subject to the official TOS documentation.
"""

import argparse
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")
    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}")

        try:
            output = client.get_object(
                bucket=bucket,
                key=key,
                process=process_value,
                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 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:
        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)

    max_object_size = int(os.getenv("MAX_OBJECT_SIZE", "262144"))
    size = os.path.getsize(output_path)
    if size > max_object_size:
        print(
            f"[ERROR] Output size ({size} bytes) exceeds MAX_OBJECT_SIZE={max_object_size}. Deleting local file.",
            file=sys.stderr,
        )
        try:
            os.remove(output_path)
        except OSError:
            pass
        sys.exit(1)

    print(f"[OK] Image saved to {output_path} ({size} bytes)")


if __name__ == "__main__":
    main()


path: scripts/image_format.py
content: # 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)

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
  - MAX_OBJECT_SIZE (default: 262144)

Note: Parameter semantics are subject to the official TOS documentation.
"""

import argparse
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, 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")
    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}")

        try:
            output = client.get_object(
                bucket=bucket,
                key=key,
                process=process_value,
                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 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)

    max_object_size = int(os.getenv("MAX_OBJECT_SIZE", "262144"))
    size = os.path.getsize(output_path)
    if size > max_object_size:
        print(
            f"[ERROR] Output size ({size} bytes) exceeds MAX_OBJECT_SIZE={max_object_size}. Deleting local file.",
            file=sys.stderr,
        )
        try:
            os.remove(output_path)
        except OSError:
            pass
        sys.exit(1)

    print(f"[OK] Image saved to {output_path} ({size} bytes)")


if __name__ == "__main__":
    main()


path: scripts/image_watermark.py
content: # 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=...)`.

Watermark parameters vary by watermark type (text/image/mix) and are subject to
TOS official documentation. This script intentionally avoids hardcoding all
parameter names.

Pass watermark parameters using repeated `--kv key=value`, and the script will
append them as `key_value` segments 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
  - MAX_OBJECT_SIZE (default: 262144)

Note: Parameter semantics are subject to the official TOS documentation.
"""

import argparse
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 "watermarked_output"
    return f"watermarked_{base}"


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("--kv", action="append", default=[], help="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")
    args = parser.parse_args()

    if not args.kv:
        print("[ERROR] At least one --kv key=value is required for 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("watermark", 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"watermarked_{os.path.basename(key)}"

        print(f"[INFO] Watermarking {bucket}/{key} -> {save_bucket}/{save_object}")
        print(f"[INFO] process = {process_value}")

        try:
            output = client.get_object(
                bucket=bucket,
                key=key,
                process=process_value,
                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 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] 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)

    max_object_size = int(os.getenv("MAX_OBJECT_SIZE", "262144"))
    size = os.path.getsize(output_path)
    if size > max_object_size:
        print(
            f"[ERROR] Output size ({size} bytes) exceeds MAX_OBJECT_SIZE={max_object_size}. Deleting local file.",
            file=sys.stderr,
        )
        try:
            os.remove(output_path)
        except OSError:
            pass
        sys.exit(1)

    print(f"[OK] Image saved to {output_path} ({size} bytes)")


if __name__ == "__main__":
    main()


path: scripts/image_resize.py
content: # 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
  - MAX_OBJECT_SIZE (default: 262144)

Note: Parameter semantics are subject to the official TOS documentation.
"""

import argparse
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")
    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}")

        try:
            output = client.get_object(
                bucket=bucket,
                key=key,
                process=process_value,
                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 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)

    max_object_size = int(os.getenv("MAX_OBJECT_SIZE", "262144"))
    size = os.path.getsize(output_path)
    if size > max_object_size:
        print(
            f"[ERROR] Output size ({size} bytes) exceeds MAX_OBJECT_SIZE={max_object_size}. Deleting local file.",
            file=sys.stderr,
        )
        try:
            os.remove(output_path)
        except OSError:
            pass
        sys.exit(1)

    print(f"[OK] Image saved to {output_path} ({size} bytes)")


if __name__ == "__main__":
    main()


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)