
Byted Tos Doc Process
- 33 installs
- 411 repo stars
- Updated August 4, 2026
- bytedance/agentkit-samples
byted-tos-doc-process is a Claude skill that previews and exports documents stored in Volcengine TOS as PDF, PNG, or JPG and reads page counts.
About
This skill previews and exports office documents (Word, Excel, PPT, PDF) stored in Volcengine TOS. A developer uses it to generate PDF, PNG, or JPG previews, read page counts, resolve HTML preview URLs, and batch-export specific PDF pages as images. It is read-only preview and conversion built on TOS doc-preview pre-signed URLs.
- Previews and exports TOS-stored office documents as PDF, PNG, or JPG
- Reads page counts and resolves HTML preview URLs
- Batch-exports PDF page ranges as images to TOS
Byted Tos Doc Process by the numbers
- 33 all-time installs (skills.sh)
- Ranked #403 of 688 Office & Documents skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
byted-tos-doc-process capabilities & compatibility
Requires Volcengine TOS credentials (access key, secret, endpoint, region, bucket)
- Capabilities
- document preview · document conversion · pdf export · page count
- Use cases
- pdf parsing · documentation
- Pricing
- Bring your own API key
What byted-tos-doc-process says it does
Previews and exports documents stored in Volcengine TOS: generate PDF/PNG/JPG previews, read page counts, resolve HTML preview URLs
Editing document contents (this skill is read-only preview/conversion).
npx skills add https://github.com/bytedance/agentkit-samples --skill byted-tos-doc-processAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 33 |
|---|---|
| repo stars | ★ 411 |
| Last updated | August 4, 2026 |
| Repository | bytedance/agentkit-samples ↗ |
What it does
Preview and export Volcengine TOS office documents as PDF, PNG, or JPG and read their page counts.
Who is it for?
Skip if: Editing document contents, generic object-storage tasks, or local office conversion that does not involve TOS.
When should I use this skill?
You need to convert or preview office documents in TOS, export specific pages as images, get page counts, or generate HTML preview links.
What you get
Generates PDF/PNG/JPG previews, page counts, and HTML preview URLs for TOS documents, with batch page-range export.
By the numbers
- Bundles 8 doc-processing scripts
- Supports PDF, PNG, and JPG output formats
Files
Volcengine TOS Document Process
Preview and convert office documents stored in Volcengine TOS — PDF/PNG/JPG conversion, page count, HTML preview URL, and page-range export.
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 document |
TOS_OBJECT_KEY | No | Source object key of the document. Can be overridden with --key |
TOS_SECURITY_TOKEN | No | STS session token when using temporary credentials |
Quick start (common tasks)
# Convert document to PDF
python3 {baseDir}/scripts/doc_preview_pdf.py --key report.docx --output preview.pdf
# Preview page 2 as PNG
python3 {baseDir}/scripts/doc_preview_png.py --key report.docx --page 2 --output page_2.png
# Preview page 2 as JPG
python3 {baseDir}/scripts/doc_preview_jpg.py --key report.docx --page 2 --output page_2.jpg
# Get total page count
python3 {baseDir}/scripts/doc_total_page.py --key report.docx --dest-type pdf
# Resolve HTML preview URL
python3 {baseDir}/scripts/doc_preview_html_url.py --key report.docx
# Batch export page range to TOS
python3 {baseDir}/scripts/doc_preview_process.py --key report.docx \
--dest-type jpg --img-mode 1 --start-page 1 --end-page 3 \
--saveas-bucket "output-bucket" --saveas-object "export/page_{Page}.jpg"
# Batch screenshot a PDF page range
python3 {baseDir}/scripts/doc_batch_screenshot.py --key test.pdf \
--format png --start-page 1 --end-page 3 \
--saveas-object "skill-test/doc/{Page}.png"Available scripts
| Script | Purpose |
|---|---|
scripts/doc_preview_pdf.py | Convert document to PDF and save locally. |
scripts/doc_preview_png.py | Render a single page as PNG. |
scripts/doc_preview_jpg.py | Render a single page as JPG. |
scripts/doc_total_page.py | Read total page count via x-tos-total-page header. |
scripts/doc_preview_html_url.py | Resolve the final HTML preview URL (follows redirects and extracts tokens). |
scripts/doc_preview_process.py | Generic doc-preview with full parameter control; supports TOS-to-TOS export. |
scripts/doc_preview_params.py | Helper library to build x-tos-doc-* query parameters consistently. |
scripts/doc_batch_screenshot.py | Batch-export PDF pages as images through doc-preview batch mode. |
All scripts support --key to override TOS_OBJECT_KEY. Structured scripts also support --json for machine-readable output, and doc_preview_process.py / doc_total_page.py support --dry-run to preview the resolved request. Run any script with -h for full usage.
Out of scope
- Editing document contents (this skill is read-only preview/conversion).
- Generic object storage tasks unrelated to document preview.
- Local office conversion workflows that do not involve TOS.
Rules
- Authentication: Authentication is provided by the TOS identity declared in the
metadatablock above. Object selection can be overridden per script with--key. - Pre-signed URL pattern: Document preview uses pre-signed URLs with
x-tos-process=doc-previewandx-tos-doc-*query parameters, not the directprocesskeyword ofget_object. Usedoc_preview_params.pyto build parameters consistently. - Batch screenshot constraint:
doc_batch_screenshot.pycurrently requires a PDF source object and persists results back to TOS with a{Page}placeholder. - Save-as encoding: For
doc-previewquery-based save-as, the helper script automatically URL-safe-Base64 encodesx-tos-save-bucketandx-tos-save-objectto match backend expectations. - Custom domain note: For buckets created after Jan 03, 2024, online HTML preview may require a custom domain rather than the default TOS domain.
- Parameter source of truth: Official Volcengine TOS documentation is authoritative for the full
doc-previewparameter matrix. When uncertain, check REFERENCE.md. - 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 Document Process Skill
This skill provides a polished document-processing toolkit for files stored in Bytedance TOS. It covers synchronous doc-preview flows for format conversion, page preview, and batch export.
When To Use
Use this skill when you need to:
- Convert office documents in TOS to
pdf,png,jpg, orhtml - Preview a single page as an image
- Read document page count from
x-tos-total-page - Export a page range back to another TOS location
- Batch screenshot PDF pages to TOS
- Debug or construct
doc-previewrequests andx-tos-doc-*parameters
Do not use this skill for:
- Editing document contents
- Generic object storage tasks unrelated to document preview
- Local office conversion workflows that do not involve TOS
Why This Skill Exists
The Volcengine TOS Python SDK supports object processing, but document-specific preview parameters are not exposed as direct get_object(...) keyword arguments. For doc-preview, the reliable pattern is:
1. Build the required x-tos-process and x-tos-doc-* query parameters. 2. Generate a pre-signed URL with pre_signed_url(...). 3. Fetch the processed result over HTTP.
That pattern is the core of this skill.
Directory Layout
byted-tos-doc-process/
├── SKILL.md
├── README.md
├── REFERENCE.md
├── WORKFLOWS.md
├── LICENSE
├── requirements.txt
└── scripts/
├── doc_preview_params.py
├── doc_preview_pdf.py
├── doc_preview_png.py
├── doc_preview_jpg.py
├── doc_preview_html_url.py
├── doc_preview_process.py
├── doc_total_page.py
└── doc_batch_screenshot.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 document. | my-doc-bucket |
TOS_OBJECT_KEY | Yes | Source object key of the document. | reports/q1-review.docx |
TOS_SECURITY_TOKEN | No | STS session token when using temporary credentials. | STS... |
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-doc-bucket"
export TOS_OBJECT_KEY="path/to/your/document.docx"Run one of the ready-to-use examples:
Convert to PDF:
python3 scripts/doc_preview_pdf.py --output preview.pdfPreview page 2 as PNG:
python3 scripts/doc_preview_png.py --page 2 --output page_2.pngPreview page 2 as JPG:
python3 scripts/doc_preview_jpg.py --page 2 --output page_2.jpgResolve the final HTML preview URL:
python3 scripts/doc_preview_html_url.pyRead total page count:
python3 scripts/doc_total_page.py --dest-type pdf --jsonBatch export a page range to TOS:
python3 scripts/doc_preview_process.py \
--dest-type jpg \
--img-mode 1 \
--start-page 1 \
--end-page 3 \
--saveas-bucket "your-output-bucket" \
--saveas-object "export/page_{Page}.jpg"Batch screenshot a PDF page range:
python3 scripts/doc_batch_screenshot.py \
--key test.pdf \
--format png \
--start-page 1 \
--end-page 3 \
--saveas-object "skill-test/doc/{Page}.png"Parse a direct HTML preview link without credentials:
python3 scripts/doc_preview_html_url.py \
--direct-url "https://your-bucket.tos-cn-beijing.volces.com/doc.docx?x-tos-process=doc-preview&x-tos-doc-dst-type=html"Document 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 mapping and response semanticsWORKFLOWS.md: end-to-end examples and usage patternsscripts/: executable examples for common document-processing tasks
Usage Notes
- For
doc-preview, pass all document-processing options in the signed URL query string. doc_preview_params.pyis the preferred way to build query parameters consistently.- The helper now automatically URL-safe-Base64 encodes query-based
x-tos-save-bucketandx-tos-save-object, which is required for the batch screenshot path to succeed. doc_batch_screenshot.pycurrently enforces the backend contract for PDF-only batch image export and requires{Page}in the destination object key.- HTML preview flows may require extracting and decoding a token from the returned HTML.
- For buckets created after Jan 03, 2024, online preview may require a custom domain rather than the default TOS domain.
- Official Volcengine TOS documentation remains the source of truth for the full parameter matrix.
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 Document Process (doc-preview) SDK Reference
This document provides a reference for the doc-preview feature, as implemented via pre-signed URLs with the Volcengine TOS Python SDK.
Crucial Note: The official Python SDK's get_object method does not directly support doc_* keyword arguments. Therefore, all document processing parameters must be passed as query parameters in a URL generated by client.pre_signed_url. For an exhaustive list of all parameters and their valid values, you must refer to the official Volcengine TOS `doc-preview` documentation.
Table of Contents
- Authentication
- SDK Pre-signed URL Approach
- `doc_preview_params.py` Helper
- Parameter Mapping
- Special Parameters and Behaviors
- `x-tos-total-page` Header
- HTML Preview URL Parsing
- Data Models
- BatchSaveResult Object
---
Authentication
Authentication is handled by the tos.TosClientV2 client when it generates the pre-signed URL. The required TOS identity is declared in this skill's metadata block.
---
SDK Pre-signed URL Approach
The doc-preview feature is invoked by generating a pre-signed URL with x-tos-process=doc-preview and other x-tos-doc-* parameters in the query string.
doc_preview_params.py Helper
To simplify this, the skill provides a helper function build_doc_preview_query_params in scripts/doc_preview_params.py. This function takes Python-style arguments and returns a dictionary of query parameters ready to be passed to the SDK.
Example Usage:
from doc_preview_params import build_doc_preview_query_params
# Build query dict for page 2 of a PNG preview
query_params = build_doc_preview_query_params(
dest_type="png",
page=2,
image_dpi=150
)
# query_params will be:
# {
# "x-tos-process": "doc-preview",
# "x-tos-doc-dst-type": "png",
# "x-tos-doc-page": "2",
# "x-tos-doc-image-dpi": "150"
# }
presigned_url_output = client.pre_signed_url(
http_method, bucket, key, query=query_params
)Parameter Mapping
The build_doc_preview_query_params function maps Python-style arguments to HTTP query parameters as follows:
Core Parameters
build_doc_preview_query_params kwarg | HTTP Query Parameter | Type | Description |
|---|---|---|---|
process | x-tos-process | string | Required. Defaults to "doc-preview". |
dest_type | x-tos-doc-dst-type | string | Required. Target format. Valid values: pdf, png, jpg, html. |
page | x-tos-doc-page | int | Optional. The 1-based index of the page to preview. |
src_type | x-tos-doc-src-type | string | Optional. Source document type hint (e.g., docx, pptx). |
save_bucket | x-tos-save-bucket | string | Optional. If provided, saves the result to this bucket instead of returning it. |
save_object | x-tos-save-object | string | Optional. The object key for the saved result. Supports {Page} placeholder. |
Image-Specific Parameters
build_doc_preview_query_params kwarg | HTTP Query Parameter | Type | Description |
|---|---|---|---|
image_dpi | x-tos-doc-image-dpi | int | Sets the DPI for the output image. Recommended range: [96, 600]. |
image_quality | x-tos-doc-image-quality | int | For JPEG output, sets the quality from 0-100. For PNG, affects compression. |
image_params | image-params | string | Advanced image processing parameters, passed as a raw string. |
Batch Export Parameters
build_doc_preview_query_params kwarg | HTTP Query Parameter | Type | Description |
|---|---|---|---|
img_mode | image-mode | int | Activates batch mode. A value of 1 is common for page range export. |
start_page | start-page | int | The 1-based starting page of the range to export. |
end_page | end-page | int | The 1-based ending page. A value of -1 may denote "to the end". |
Encoding Note
For query-based save-as in doc-preview, the helper script URL-safe-Base64 encodes both x-tos-save-bucket and x-tos-save-object before signing the URL. This is required by the backend save-as parser for the batch screenshot path.
---
Special Parameters and Behaviors
x-tos-total-page Header
When you make a doc-preview request via a pre-signed URL, the TOS server response may include the x-tos-total-page HTTP header.
- Purpose: It indicates the total number of pages in the source document.
- Availability: It is typically available on the first successful conversion. On subsequent identical requests that hit the server cache, the header will also be present.
- Accessing:
from urllib.request import urlopen
with urlopen(presigned_url.signed_url) as response:
total_pages = response.headers.get("x-tos-total-page")HTML Preview URL Parsing
When dest_type is html, the response body is a temporary HTML page.
- Extraction Logic:
1. The HTML contains: window.open("/index.html?token=<BASE64_TOKEN>","_self"); 2. Extract the <BASE64_TOKEN>. 3. This token is a URL-safe Base64 encoded string.
- Decoding:
1. Decode using base64.urlsafe_b64decode(). You may need to add padding (=). 2. The decoded result is the final, signed preview URL.
- `--direct-url` Support: The
scripts/doc_preview_html_url.pyscript includes a--direct-urlflag to parse an existing HTML preview link without needing credentials, which is useful for debugging. - Domain Restriction: Be mindful of the custom domain requirement for recent buckets.
---
Data Models
BatchSaveResult Object
When you perform a batch export, the response body is a JSON object detailing the result.
| Field | Type | Description |
|---|---|---|
TotalPageCount | int | The total number of pages attempted. |
SuccPageCount | int | The number of pages successfully converted. |
FailPageCount | int | The number of pages that failed. |
SuccConvertResult | array | An array of objects detailing successful conversions. |
FailedPagesDetails | array | An array of objects detailing failures. |
`SuccConvertResult` Item:
| Field | Type | Description |
|---|---|---|
Bucket | string | Destination bucket. |
Object | string | Key of the saved image. |
PageNum | int | Page number converted. |
ETag | string | ETag of the saved object. |
Example JSON Snippet:
{
"TotalPageCount": 3,
"SuccPageCount": 3,
"FailPageCount": 0,
"SuccConvertResult": [
{
"Bucket": "output-bucket",
"Object": "export/page_1.jpg",
"PageNum": 1,
"ETag": "..."
}
],
"FailedPagesDetails": []
}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
"""Export multiple PDF pages as images through doc-preview batch mode.
The backend requires batch export to:
1. use a PDF source object;
2. save results back to TOS;
3. include the `{Page}` placeholder in the destination object key.
"""
import argparse
import os
import sys
from urllib.error import HTTPError, URLError
from urllib.request import urlopen
from doc_preview_params import build_doc_preview_query_params
from doc_preview_process import create_client, get_env, pre_signed_request
def main() -> None:
parser = argparse.ArgumentParser(
description="Batch export document pages as images"
)
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(
"--format", choices=["png", "jpg"], default="png", help="Image format"
)
parser.add_argument(
"--src-type", type=str, default=None, help="Optional source type override"
)
parser.add_argument("--start-page", type=int, default=1, help="Start page, 1-based")
parser.add_argument(
"--end-page", type=int, default=-1, help="End page, -1 means last page"
)
parser.add_argument("--dpi", type=int, default=200, help="DocImageDpi")
parser.add_argument("--quality", type=int, default=90, help="DocImageQuality")
parser.add_argument(
"--img-mode",
type=int,
default=1,
help="Batch image mode, defaults to 1 for multi-page export",
)
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 object template, must contain {Page}, e.g. previews/test_{Page}.png",
)
args = parser.parse_args()
if args.start_page <= 0:
print("[ERROR] --start-page must be >= 1", file=sys.stderr)
sys.exit(1)
if args.end_page != -1 and args.end_page < args.start_page:
print("[ERROR] --end-page must be >= --start-page or -1", 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")
inferred_src_type = args.src_type or os.path.splitext(key)[1].lstrip(".").lower()
if inferred_src_type != "pdf":
print(
"[ERROR] Batch screenshot currently requires a PDF source object.",
file=sys.stderr,
)
sys.exit(1)
save_bucket = args.saveas_bucket or bucket
save_object = args.saveas_object
if not save_object:
base = os.path.splitext(os.path.basename(key) or "document")[0]
save_object = f"doc-batch/{base}_{{Page}}.{args.format}"
if "{Page}" not in save_object:
print(
"[ERROR] --saveas-object must contain the {Page} placeholder.",
file=sys.stderr,
)
sys.exit(1)
print(
f"[INFO] Running doc batch screenshot for {bucket}/{key} -> {save_bucket}/{save_object}"
)
params = build_doc_preview_query_params(
dest_type=args.format,
src_type=inferred_src_type,
image_dpi=args.dpi,
image_quality=args.quality,
img_mode=args.img_mode,
start_page=args.start_page,
end_page=args.end_page,
save_bucket=save_bucket,
save_object=save_object,
)
req = pre_signed_request(client, bucket, key, params)
try:
with urlopen(req) as resp:
body = resp.read().decode("utf-8", errors="replace")
except HTTPError as exc:
print(
f"[ERROR] HTTP error: status={exc.code}, reason={exc.reason}",
file=sys.stderr,
)
sys.exit(1)
except URLError as exc:
print(f"[ERROR] Request failed: {exc.reason}", file=sys.stderr)
sys.exit(1)
print("[OK] Batch export result from TOS:")
print(body)
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: fetch HTML doc-preview and extract the real preview URL.
This script uses the TOS Python SDK's **pre-signed URL** capability to call
`doc-preview` with `DocDestType=html`. It then:
1. Generates a signed URL with all doc-preview parameters encoded as
`x-tos-*` query parameters (including `x-tos-doc-dst-type=html`, optional
`x-tos-doc-src-type`, `x-tos-doc-page`, etc.).
2. Performs an HTTP GET against that URL to retrieve the temporary HTML
page.
3. Uses a regular expression to extract the embedded
`window.open("<LINK>","_self")` HTML preview link.
4. Parses the `token` query parameter from that link and decodes it
(Base64 URL-safe) to obtain the actual preview URL.
It also supports a **direct URL** mode where you can pass the HTML URL
(e.g. an example link provided by TOS) and skip the pre-signed step.
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 source document
- TOS_OBJECT_KEY Object key of the document in the bucket
CLI parameters:
--bucket Override TOS_BUCKET
--key Override TOS_OBJECT_KEY
--src-type Optional DocSrcType, e.g. docx/pptx/xlsx (see official docs)
--page Optional DocPage (1-based) for HTML preview
--direct-url If provided, skip SDK and fetch this HTML URL directly
The script prints:
- The HTML preview link extracted from the HTML page
- The raw token found in the link
- The decoded preview URL (which can be accessed directly by browser/HTTP).
Notes:
- This implementation is compatible with the internal
test case `tc_func_doc_html_url.py`, but it now uses a more robust
regex-based extraction of the preview link and parses the `token`
from the link's query string.
- For buckets created after the domain restriction change (2024-01-03),
you must use a custom domain for the preview URL; otherwise the preview
might fall back to downloading the original file. See README/REFERENCE
for details.
- We do **not** pass doc-related parameters as explicit `get_object`
keyword arguments. Instead, we always encode them into the query string
of a pre-signed URL via `TosClientV2.pre_signed_url`.
"""
import argparse
import base64
import os
import re
import sys
from typing import Optional
from urllib.error import HTTPError, URLError
from urllib.parse import parse_qs, urlparse
from urllib.request import Request, urlopen
import tos
from tos.enum import HttpMethodType
from tos.exceptions import TosClientError, TosServerError
from doc_preview_params import build_doc_preview_query_params
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 extract_preview_link(html: str) -> str:
"""Extract the full HTML preview link from a window.open call.
Expected pattern (whitespace around comma is allowed):
window.open("<LINK>","_self");
or
window.open("<LINK>", "_self");
The regex is DOTALL-enabled so that the call can safely span multiple lines.
"""
pattern = r'window\.open\("([^\"]+)",\s*"_self"\)'
match = re.search(pattern, html, re.DOTALL)
if not match:
raise ValueError(
'Could not find preview link via window.open("...","_self") in HTML content',
)
return match.group(1)
def extract_token_from_html(html: str) -> str:
"""Extract the Base64 URL-safe token from the HTML doc-preview page.
For backward compatibility, this helper remains available, but it now
works by:
1. Extracting the preview link via :func:`extract_preview_link`.
2. Parsing the `token` query parameter from that link using
``urllib.parse.urlparse`` / ``parse_qs``.
This is more robust than directly slicing the raw HTML and avoids
accidentally including trailing characters such as `","_self")` in
the token.
"""
preview_link = extract_preview_link(html)
parsed = urlparse(preview_link)
if not parsed.query:
raise ValueError(f"No query string found in preview link: {preview_link!r}")
qs = parse_qs(parsed.query)
token_list = qs.get("token")
if not token_list:
raise ValueError(
f"No 'token' parameter found in preview link: {preview_link!r}"
)
token = token_list[0]
if not token:
raise ValueError("Token parameter in preview link is empty")
return token
def decode_preview_url(token: str) -> str:
"""Decode Base64 URL-safe token to get the real preview URL.
The token is encoded using URL-safe Base64 without padding. We restore
padding and decode it with ``base64.urlsafe_b64decode``.
"""
# Restore padding to multiple of 4
padding = "=" * ((4 - len(token) % 4) % 4)
try:
raw = base64.urlsafe_b64decode(token + padding)
except Exception as exc: # noqa: BLE001
raise ValueError(f"Failed to decode token as URL-safe Base64: {exc}") from exc
try:
return raw.decode("utf-8")
except UnicodeDecodeError as exc: # noqa: BLE001
raise ValueError("Decoded token is not valid UTF-8") from exc
def fetch_html_via_presigned(
client: tos.TosClientV2,
bucket: str,
key: str,
src_type: Optional[str],
page: Optional[int],
) -> str:
"""Generate a pre-signed URL for HTML doc-preview and fetch the HTML body."""
params = build_doc_preview_query_params(
dest_type="html",
src_type=src_type,
page=page,
)
print("[INFO] Generating pre-signed URL for doc-preview (html)...")
try:
presigned = client.pre_signed_url(
HttpMethodType.Http_Method_Get,
bucket=bucket,
key=key,
query=params,
)
except TosServerError as e:
print(
f"[ERROR] Failed to generate pre-signed URL: "
f"code={e.code}, status={e.status_code}, request_id={e.request_id}, message={e.message}",
file=sys.stderr,
)
sys.exit(1)
except TosClientError as e:
print(
f"[ERROR] TOS client error when generating pre-signed URL: {e}",
file=sys.stderr,
)
sys.exit(1)
except Exception as exc: # noqa: BLE001
print(
f"[ERROR] Unexpected error when generating pre-signed URL: {exc}",
file=sys.stderr,
)
sys.exit(1)
req = Request(presigned.signed_url, headers=presigned.signed_header)
try:
with urlopen(req) as resp:
html_bytes = resp.read()
except HTTPError as e:
print(
f"[ERROR] HTTP error when fetching HTML via pre-signed URL: "
f"status={e.code}, reason={e.reason}",
file=sys.stderr,
)
sys.exit(1)
except URLError as e:
print(
f"[ERROR] Failed to fetch HTML via pre-signed URL: {e.reason}",
file=sys.stderr,
)
sys.exit(1)
except Exception as exc: # noqa: BLE001
print(f"[ERROR] Unexpected error when fetching HTML: {exc}", file=sys.stderr)
sys.exit(1)
return html_bytes.decode("utf-8", errors="replace")
def fetch_html_from_direct_url(url: str) -> str:
"""Fetch HTML directly from a user-provided URL (no SDK involved)."""
print(f"[INFO] Fetching HTML from direct URL: {url}")
req = Request(url)
try:
with urlopen(req) as resp:
html_bytes = resp.read()
except HTTPError as e:
print(
f"[ERROR] HTTP error when fetching direct HTML URL: "
f"status={e.code}, reason={e.reason}",
file=sys.stderr,
)
sys.exit(1)
except URLError as e:
print(f"[ERROR] Failed to fetch direct HTML URL: {e.reason}", file=sys.stderr)
sys.exit(1)
except Exception as exc: # noqa: BLE001
print(
f"[ERROR] Unexpected error when fetching direct HTML URL: {exc}",
file=sys.stderr,
)
sys.exit(1)
return html_bytes.decode("utf-8", errors="replace")
def main() -> None:
parser = argparse.ArgumentParser(
description="Fetch HTML doc-preview and extract the real preview URL",
)
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(
"--src-type",
dest="src_type",
type=str,
default=None,
help="Optional DocSrcType, e.g. docx/pptx/xlsx; see official docs",
)
parser.add_argument(
"--page",
type=int,
default=None,
help="Optional DocPage (1-based) for HTML preview",
)
parser.add_argument(
"--direct-url",
dest="direct_url",
type=str,
default=None,
help=(
"If provided, skip SDK and use this HTML URL directly. "
"Useful for debugging sample links such as the ones in official docs."
),
)
args = parser.parse_args()
if args.page is not None and args.page <= 0:
print("[ERROR] --page must be >= 1 when specified", file=sys.stderr)
sys.exit(1)
if args.direct_url:
# Direct URL mode: no SDK required.
html = fetch_html_from_direct_url(args.direct_url)
else:
client = create_client()
bucket = args.bucket or get_env("TOS_BUCKET")
key = args.key or get_env("TOS_OBJECT_KEY")
print(f"[INFO] Requesting HTML doc-preview for {bucket}/{key} ...")
print("[INFO] Using pre-signed URL with doc_dest_type=html")
html = fetch_html_via_presigned(client, bucket, key, args.src_type, args.page)
try:
preview_link = extract_preview_link(html)
except ValueError as exc:
print(
f"[ERROR] Failed to extract preview link from HTML: {exc}", file=sys.stderr
)
# Optionally dump a small prefix of HTML to help debugging
print(html[:400], file=sys.stderr)
sys.exit(1)
try:
token = extract_token_from_html(html)
except ValueError as exc:
print(f"[ERROR] Failed to extract token from HTML: {exc}", file=sys.stderr)
# Optionally dump a small prefix of HTML to help debugging
print(html[:400], file=sys.stderr)
sys.exit(1)
try:
preview_url = decode_preview_url(token)
except ValueError as exc:
print(
f"[ERROR] Failed to decode preview URL from token: {exc}", file=sys.stderr
)
sys.exit(1)
print("[OK] Extracted preview information:")
print(f" HTML Link : {preview_link}")
print(f" Token : {token}")
print(f" Preview URL : {preview_url}")
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: preview a document page as JPG using TOS doc-preview.
This script calls the TOS `doc-preview` feature via a **pre-signed URL**
with `x-tos-doc-dst-type=jpg` and related parameters, then saves the
converted page image locally.
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 source document
- TOS_OBJECT_KEY Object key of the document in the bucket
CLI parameters:
--bucket Override TOS_BUCKET
--key Override TOS_OBJECT_KEY
--src-type Optional DocSrcType, e.g. docx/pptx/xlsx (see official docs)
--page DocPage (1-based page index), default: 1
--dpi DocImageDpi (96-600 recommended; see official docs)
--quality DocImageQuality (0-100; see official docs)
--image-params DocImageParams raw string, forwarded as-is
--output Local output JPG path (default: <basename>_p<page>.jpg)
For batch export of page ranges (DocImgMode + DocStartPage/DocEndPage),
prefer using `doc_preview_process.py`, which exposes the full parameter set.
"""
import argparse
import os
import sys
from typing import Optional
from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen
import tos
from tos.enum import HttpMethodType
from tos.exceptions import TosClientError, TosServerError
from doc_preview_params import build_doc_preview_query_params
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 default_output_path(key: str, page: int) -> str:
base = os.path.basename(key)
if not base:
return f"preview_p{page}.jpg"
root, _ = os.path.splitext(base)
return f"{root}_p{page}.jpg"
def download_via_presigned_to_file(
client: tos.TosClientV2,
bucket: str,
key: str,
params: dict[str, str],
output_path: str,
) -> None:
"""Generate a pre-signed URL for doc-preview and download to a local file."""
print("[INFO] Generating pre-signed URL for doc-preview (jpg)...")
try:
presigned = client.pre_signed_url(
HttpMethodType.Http_Method_Get,
bucket=bucket,
key=key,
query=params,
)
except TosServerError as e:
print(
f"[ERROR] Failed to generate pre-signed URL: "
f"code={e.code}, status={e.status_code}, request_id={e.request_id}, message={e.message}",
file=sys.stderr,
)
sys.exit(1)
except TosClientError as e:
print(
f"[ERROR] TOS client error when generating pre-signed URL: {e}",
file=sys.stderr,
)
sys.exit(1)
except Exception as exc: # noqa: BLE001
print(
f"[ERROR] Unexpected error when generating pre-signed URL: {exc}",
file=sys.stderr,
)
sys.exit(1)
req = Request(presigned.signed_url, headers=presigned.signed_header)
try:
with urlopen(req) as resp, open(output_path, "wb") as out_f:
while True:
chunk = resp.read(64 * 1024)
if not chunk:
break
out_f.write(chunk)
except HTTPError as e:
print(
f"[ERROR] HTTP error when downloading JPG via pre-signed URL: "
f"status={e.code}, reason={e.reason}",
file=sys.stderr,
)
try:
os.remove(output_path)
except OSError:
pass
sys.exit(1)
except URLError as e:
print(
f"[ERROR] Failed to download JPG via pre-signed URL: {e.reason}",
file=sys.stderr,
)
try:
os.remove(output_path)
except OSError:
pass
sys.exit(1)
except Exception as exc: # noqa: BLE001
print(f"[ERROR] Unexpected error when downloading JPG: {exc}", file=sys.stderr)
try:
os.remove(output_path)
except OSError:
pass
sys.exit(1)
def main() -> None:
parser = argparse.ArgumentParser(
description="Preview a single document page as JPG via TOS doc-preview",
)
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(
"--src-type",
dest="src_type",
type=str,
default=None,
help="Optional DocSrcType, e.g. docx/pptx/xlsx; see official docs",
)
parser.add_argument(
"--page",
type=int,
default=1,
help="DocPage (1-based), default 1",
)
parser.add_argument(
"--dpi",
dest="dpi",
type=int,
default=None,
help="DocImageDpi, recommended range [96, 600]",
)
parser.add_argument(
"--quality",
dest="quality",
type=int,
default=None,
help="DocImageQuality, recommended range [0, 100]",
)
parser.add_argument(
"--image-params",
dest="image_params",
type=str,
default=None,
help="DocImageParams raw string, forwarded to TOS (see official docs)",
)
parser.add_argument(
"--output",
type=str,
default=None,
help="Local output JPG path (default: <basename>_p<page>.jpg)",
)
args = parser.parse_args()
if args.page <= 0:
print("[ERROR] --page must be >= 1", 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")
output_path = args.output or default_output_path(key, args.page)
print(
f"[INFO] Requesting doc-preview JPG for {bucket}/{key}, page={args.page} -> {output_path}",
)
params = build_doc_preview_query_params(
dest_type="jpg",
src_type=args.src_type,
page=args.page,
image_dpi=args.dpi,
image_quality=args.quality,
image_params=args.image_params,
)
download_via_presigned_to_file(client, bucket, key, params, output_path)
try:
size = os.path.getsize(output_path)
except OSError:
size = -1
print(f"[OK] JPG 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
"""Shared helpers for TOS doc-preview scripts.
This module provides a small utility to translate high-level doc-preview
arguments into the exact `x-tos-*` HTTP query parameters required by TOS.
All scripts in this skill should use this function when generating
pre-signed URLs for doc-preview, so that query construction is consistent
and easy to maintain.
"""
from __future__ import annotations
import base64
from typing import Dict, Optional
_DEFAULT_PROCESS = "doc-preview"
def build_doc_preview_query_params(
*,
process: str = _DEFAULT_PROCESS,
dest_type: Optional[str] = None,
src_type: Optional[str] = None,
page: Optional[int] = None,
image_dpi: Optional[int] = None,
image_quality: Optional[int] = None,
img_mode: Optional[int] = None,
start_page: Optional[int] = None,
end_page: Optional[int] = None,
image_params: Optional[str] = None,
save_bucket: Optional[str] = None,
save_object: Optional[str] = None,
) -> Dict[str, str]:
"""Build the query parameters for a doc-preview pre-signed URL.
The returned dict is ready to be passed as the ``query`` argument to
:meth:`tos.TosClientV2.pre_signed_url`.
Mapping (see also REFERENCE.md and official doc-preview docs):
- ``process`` -> ``x-tos-process`` (typically ``"doc-preview"``)
- ``dest_type`` -> ``x-tos-doc-dst-type`` (pdf/png/jpg/html)
- ``src_type`` -> ``x-tos-doc-src-type``
- ``page`` -> ``x-tos-doc-page``
- ``image_dpi`` -> ``x-tos-doc-image-dpi``
- ``image_quality``-> ``x-tos-doc-image-quality``
- ``img_mode`` -> ``image-mode``
- ``start_page`` -> ``start-page``
- ``end_page`` -> ``end-page``
- ``image_params`` -> ``image-params``
- ``save_bucket`` -> ``x-tos-save-bucket``
- ``save_object`` -> ``x-tos-save-object``
"""
params: Dict[str, str] = {}
if process:
params["x-tos-process"] = process
if dest_type:
params["x-tos-doc-dst-type"] = dest_type
if src_type:
params["x-tos-doc-src-type"] = src_type
if page is not None:
params["x-tos-doc-page"] = str(page)
if image_dpi is not None:
params["x-tos-doc-image-dpi"] = str(image_dpi)
if image_quality is not None:
params["x-tos-doc-image-quality"] = str(image_quality)
if img_mode is not None:
params["image-mode"] = str(img_mode)
if start_page is not None:
params["start-page"] = str(start_page)
if end_page is not None:
params["end-page"] = str(end_page)
if image_params:
params["image-params"] = image_params
if save_bucket:
params["x-tos-save-bucket"] = base64.urlsafe_b64encode(
save_bucket.encode("utf-8")
).decode("utf-8")
if save_object:
# For query-based save-as, backend expects URL-safe base64 for object key.
params["x-tos-save-object"] = base64.urlsafe_b64encode(
save_object.encode("utf-8")
).decode("utf-8")
return params
# 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: preview a document as PDF using TOS doc-preview.
This script now uses the TOS SDK's **pre-signed URL** feature to call
`doc-preview` with `x-tos-doc-dst-type=pdf` and related parameters. The
flow is:
1. Build a query parameter dict containing:
- ``x-tos-process=doc-preview``
- ``x-tos-doc-dst-type=pdf``
- Optional ``x-tos-doc-src-type``
2. Generate a pre-signed URL via ``TosClientV2.pre_signed_url``.
3. Use standard-library HTTP (urllib) to download the converted PDF and
save it locally.
This avoids relying on the Python SDK's ``get_object_to_file`` keyword
arguments for doc-specific parameters, which currently only supports
conventional parameters like ``x-tos-process`` and ``x-tos-save-*``.
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 source document
- TOS_OBJECT_KEY Object key of the document in the bucket
CLI parameters:
--bucket Override TOS_BUCKET
--key Override TOS_OBJECT_KEY
--src-type Optional DocSrcType, e.g. docx/pptx/xlsx (see official docs)
--output Local output path for the generated PDF
Note:
- This script only handles `DocDestType=pdf`. For image outputs or
advanced parameters (page ranges, DPI, quality, etc.), use
`doc_preview_png.py`, `doc_preview_jpg.py` or `doc_preview_process.py`.
"""
import argparse
import os
import sys
from typing import Optional
from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen
import tos
from tos.enum import HttpMethodType
from tos.exceptions import TosClientError, TosServerError
from doc_preview_params import build_doc_preview_query_params
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:
"""Initialize a TosClientV2 using AK/SK (and optional STS token)."""
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 default_output_path(key: str) -> str:
base = os.path.basename(key)
if not base:
return "preview.pdf"
root, _ = os.path.splitext(base)
return f"{root}.pdf"
def download_via_presigned_to_file(
client: tos.TosClientV2,
bucket: str,
key: str,
params: dict[str, str],
output_path: str,
) -> None:
"""Generate a pre-signed URL for doc-preview and download to a local file."""
print("[INFO] Generating pre-signed URL for doc-preview (pdf)...")
try:
presigned = client.pre_signed_url(
HttpMethodType.Http_Method_Get,
bucket=bucket,
key=key,
query=params,
)
except TosServerError as e:
print(
f"[ERROR] Failed to generate pre-signed URL: "
f"code={e.code}, status={e.status_code}, request_id={e.request_id}, message={e.message}",
file=sys.stderr,
)
sys.exit(1)
except TosClientError as e:
print(
f"[ERROR] TOS client error when generating pre-signed URL: {e}",
file=sys.stderr,
)
sys.exit(1)
except Exception as exc: # noqa: BLE001
print(
f"[ERROR] Unexpected error when generating pre-signed URL: {exc}",
file=sys.stderr,
)
sys.exit(1)
req = Request(presigned.signed_url, headers=presigned.signed_header)
try:
with urlopen(req) as resp, open(output_path, "wb") as out_f:
while True:
chunk = resp.read(64 * 1024)
if not chunk:
break
out_f.write(chunk)
except HTTPError as e:
print(
f"[ERROR] HTTP error when downloading PDF via pre-signed URL: "
f"status={e.code}, reason={e.reason}",
file=sys.stderr,
)
# Best-effort cleanup
try:
os.remove(output_path)
except OSError:
pass
sys.exit(1)
except URLError as e:
print(
f"[ERROR] Failed to download PDF via pre-signed URL: {e.reason}",
file=sys.stderr,
)
try:
os.remove(output_path)
except OSError:
pass
sys.exit(1)
except Exception as exc: # noqa: BLE001
print(f"[ERROR] Unexpected error when downloading PDF: {exc}", file=sys.stderr)
try:
os.remove(output_path)
except OSError:
pass
sys.exit(1)
def main() -> None:
parser = argparse.ArgumentParser(
description="Preview a document as PDF via TOS doc-preview (doc_dest_type=pdf)",
)
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(
"--src-type",
dest="src_type",
type=str,
default=None,
help="Optional DocSrcType, e.g. docx/pptx/xlsx; see official doc-preview docs",
)
parser.add_argument(
"--output",
type=str,
default=None,
help="Local output PDF path (default: <object_basename>.pdf)",
)
args = parser.parse_args()
client = create_client()
bucket = args.bucket or get_env("TOS_BUCKET")
key = args.key or get_env("TOS_OBJECT_KEY")
output_path = args.output or default_output_path(key)
print(f"[INFO] Requesting doc-preview PDF for {bucket}/{key} -> {output_path}")
params = build_doc_preview_query_params(
dest_type="pdf",
src_type=args.src_type,
)
download_via_presigned_to_file(client, bucket, key, params, output_path)
try:
size = os.path.getsize(output_path)
except OSError:
size = -1
print(f"[OK] PDF 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: preview a document page as PNG using TOS doc-preview.
This script calls the TOS `doc-preview` feature via a **pre-signed URL**
with `x-tos-doc-dst-type=png` and related parameters, then saves the
converted page image locally.
Typical use cases:
- Generate a thumbnail or preview for a specific page of a document.
- Control output DPI and JPEG/PNG quality for downstream OCR or UI display.
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 source document
- TOS_OBJECT_KEY Object key of the document in the bucket
CLI parameters:
--bucket Override TOS_BUCKET
--key Override TOS_OBJECT_KEY
--src-type Optional DocSrcType, e.g. docx/pptx/xlsx (see official docs)
--page DocPage (1-based page index), default: 1
--dpi DocImageDpi (96-600 recommended; see official docs)
--quality DocImageQuality (0-100; see official docs)
--image-params DocImageParams raw string, forwarded as-is
--output Local output PNG path (default: <basename>_p<page>.png)
For batch export of page ranges (DocImgMode + DocStartPage/DocEndPage),
prefer using `doc_preview_process.py`, which exposes the full parameter set.
"""
import argparse
import os
import sys
from typing import Optional
from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen
import tos
from tos.enum import HttpMethodType
from tos.exceptions import TosClientError, TosServerError
from doc_preview_params import build_doc_preview_query_params
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 default_output_path(key: str, page: int) -> str:
base = os.path.basename(key)
if not base:
return f"preview_p{page}.png"
root, _ = os.path.splitext(base)
return f"{root}_p{page}.png"
def download_via_presigned_to_file(
client: tos.TosClientV2,
bucket: str,
key: str,
params: dict[str, str],
output_path: str,
) -> None:
"""Generate a pre-signed URL for doc-preview and download to a local file."""
print("[INFO] Generating pre-signed URL for doc-preview (png)...")
try:
presigned = client.pre_signed_url(
HttpMethodType.Http_Method_Get,
bucket=bucket,
key=key,
query=params,
)
except TosServerError as e:
print(
f"[ERROR] Failed to generate pre-signed URL: "
f"code={e.code}, status={e.status_code}, request_id={e.request_id}, message={e.message}",
file=sys.stderr,
)
sys.exit(1)
except TosClientError as e:
print(
f"[ERROR] TOS client error when generating pre-signed URL: {e}",
file=sys.stderr,
)
sys.exit(1)
except Exception as exc: # noqa: BLE001
print(
f"[ERROR] Unexpected error when generating pre-signed URL: {exc}",
file=sys.stderr,
)
sys.exit(1)
req = Request(presigned.signed_url, headers=presigned.signed_header)
try:
with urlopen(req) as resp, open(output_path, "wb") as out_f:
while True:
chunk = resp.read(64 * 1024)
if not chunk:
break
out_f.write(chunk)
except HTTPError as e:
print(
f"[ERROR] HTTP error when downloading PNG via pre-signed URL: "
f"status={e.code}, reason={e.reason}",
file=sys.stderr,
)
try:
os.remove(output_path)
except OSError:
pass
sys.exit(1)
except URLError as e:
print(
f"[ERROR] Failed to download PNG via pre-signed URL: {e.reason}",
file=sys.stderr,
)
try:
os.remove(output_path)
except OSError:
pass
sys.exit(1)
except Exception as exc: # noqa: BLE001
print(f"[ERROR] Unexpected error when downloading PNG: {exc}", file=sys.stderr)
try:
os.remove(output_path)
except OSError:
pass
sys.exit(1)
def main() -> None:
parser = argparse.ArgumentParser(
description="Preview a single document page as PNG via TOS doc-preview",
)
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(
"--src-type",
dest="src_type",
type=str,
default=None,
help="Optional DocSrcType, e.g. docx/pptx/xlsx; see official docs",
)
parser.add_argument(
"--page",
type=int,
default=1,
help="DocPage (1-based), default 1",
)
parser.add_argument(
"--dpi",
dest="dpi",
type=int,
default=None,
help="DocImageDpi, recommended range [96, 600]",
)
parser.add_argument(
"--quality",
dest="quality",
type=int,
default=None,
help="DocImageQuality, recommended range [0, 100]",
)
parser.add_argument(
"--image-params",
dest="image_params",
type=str,
default=None,
help="DocImageParams raw string, forwarded to TOS (see official docs)",
)
parser.add_argument(
"--output",
type=str,
default=None,
help="Local output PNG path (default: <basename>_p<page>.png)",
)
args = parser.parse_args()
if args.page <= 0:
print("[ERROR] --page must be >= 1", 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")
output_path = args.output or default_output_path(key, args.page)
print(
f"[INFO] Requesting doc-preview PNG for {bucket}/{key}, page={args.page} -> {output_path}",
)
params = build_doc_preview_query_params(
dest_type="png",
src_type=args.src_type,
page=args.page,
image_dpi=args.dpi,
image_quality=args.quality,
image_params=args.image_params,
)
download_via_presigned_to_file(client, bucket, key, params, output_path)
try:
size = os.path.getsize(output_path)
except OSError:
size = -1
print(f"[OK] PNG 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
"""Generic entrypoint for TOS document preview (doc-preview).
This script exposes the full set of commonly used doc-preview parameters and
implements them using the TOS SDK's **pre-signed URL** capability. It allows
you to:
- Preview a document as PDF/PNG/JPG/HTML and save the result locally.
- Or, ask TOS to save the converted result back into TOS (via
`x-tos-save-bucket` / `x-tos-save-object`).
Instead of passing doc-specific parameters (DocDestType/DocPage/DocImgMode,
...) as keyword arguments to `get_object` / `get_object_to_file`, this script
builds the corresponding `x-tos-*` query parameters and calls
`TosClientV2.pre_signed_url`, then performs the HTTP request via stdlib.
Supported doc-preview parameters (CLI → doc-preview):
- DocDestType -> --dest-type (pdf/png/jpg/html)
- DocSrcType -> --src-type
- DocPage -> --page
- DocImgMode -> --img-mode
- DocStartPage -> --start-page
- DocEndPage -> --end-page
- DocImageDpi -> --dpi
- DocImageQuality -> --quality
- DocImageParams -> --image-params
Environment variables:
- TOS_ACCESS_KEY, TOS_SECRET_KEY, TOS_SECURITY_TOKEN(optional)
- TOS_ENDPOINT, TOS_REGION
- TOS_BUCKET, TOS_OBJECT_KEY
See REFERENCE.md and WORKFLOWS.md for detailed parameter mapping and examples.
"""
import argparse
import json
import os
import sys
from typing import Optional
from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen
import tos
from tos.enum import HttpMethodType
from tos.exceptions import TosClientError, TosServerError
from doc_preview_params import build_doc_preview_query_params
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 default_output_path(key: str, dest_type: str) -> str:
base = os.path.basename(key)
if not base:
return f"preview.{dest_type}"
root, _ = os.path.splitext(base)
return f"{root}.{dest_type}"
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 pre_signed_request(
client: tos.TosClientV2,
bucket: str,
key: str,
params: dict[str, str],
) -> Request:
"""Helper: generate a pre-signed GET request for doc-preview."""
try:
presigned = client.pre_signed_url(
HttpMethodType.Http_Method_Get,
bucket=bucket,
key=key,
query=params,
)
except TosServerError as e:
print(
f"[ERROR] Failed to generate pre-signed URL: "
f"code={e.code}, status={e.status_code}, request_id={e.request_id}, message={e.message}",
file=sys.stderr,
)
sys.exit(1)
except TosClientError as e:
print(
f"[ERROR] TOS client error when generating pre-signed URL: {e}",
file=sys.stderr,
)
sys.exit(1)
except Exception as exc: # noqa: BLE001
print(
f"[ERROR] Unexpected error when generating pre-signed URL: {exc}",
file=sys.stderr,
)
sys.exit(1)
return Request(presigned.signed_url, headers=presigned.signed_header)
def main() -> None:
parser = argparse.ArgumentParser(
description="Generic TOS doc-preview (doc-preview) 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(
"--dest-type",
dest="dest_type",
type=str,
choices=["pdf", "png", "jpg", "html"],
required=True,
help="DocDestType: target format (pdf/png/jpg/html)",
)
parser.add_argument(
"--src-type",
dest="src_type",
type=str,
default=None,
help="DocSrcType, e.g. docx/pptx/xlsx; maps to x-tos-doc-src-type",
)
parser.add_argument(
"--page",
type=int,
default=None,
help="DocPage (1-based). If set, preview a single page.",
)
parser.add_argument(
"--img-mode",
dest="img_mode",
type=int,
default=None,
help="DocImgMode for batch export (see official docs, e.g. 1 for all pages)",
)
parser.add_argument(
"--start-page",
dest="start_page",
type=int,
default=None,
help="DocStartPage (1-based). Use together with --img-mode.",
)
parser.add_argument(
"--end-page",
dest="end_page",
type=int,
default=None,
help="DocEndPage. Use -1 to denote 'last page' where supported.",
)
parser.add_argument(
"--dpi",
dest="dpi",
type=int,
default=None,
help="DocImageDpi, recommended range [96, 600] for image outputs.",
)
parser.add_argument(
"--quality",
dest="quality",
type=int,
default=None,
help="DocImageQuality, recommended range [0, 100] for image outputs.",
)
parser.add_argument(
"--image-params",
dest="image_params",
type=str,
default=None,
help="DocImageParams raw string, forwarded as-is.",
)
parser.add_argument(
"--output",
type=str,
default=None,
help=(
"Local output file path. For png/jpg/pdf/html this defaults to "
"<basename>.<dest-type> if not provided. Required when not saving back 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()
if args.page is not None and args.page <= 0:
print("[ERROR] --page must be >= 1 when specified", 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")
save_bucket = args.saveas_bucket
save_object = args.saveas_object
persist_to_tos = bool(save_bucket or save_object)
output_path = None if persist_to_tos else args.output or default_output_path(key, args.dest_type)
resolved_save_bucket = save_bucket or bucket if persist_to_tos else None
resolved_save_object = None
if persist_to_tos:
resolved_save_object = save_object or f"doc-preview-{os.path.basename(key) or 'document'}.{args.dest_type}"
params = build_doc_preview_query_params(
dest_type=args.dest_type,
src_type=args.src_type,
page=args.page,
image_dpi=args.dpi,
image_quality=args.quality,
img_mode=args.img_mode,
start_page=args.start_page,
end_page=args.end_page,
image_params=args.image_params,
save_bucket=resolved_save_bucket,
save_object=resolved_save_object,
)
if args.dry_run:
payload = {
"ok": True,
"operation": "doc_preview",
"bucket": bucket,
"key": key,
"dest_type": args.dest_type,
"mode": "save_to_tos" if persist_to_tos else "save_local",
"output_path": output_path,
"saveas_bucket": resolved_save_bucket,
"saveas_object": resolved_save_object,
"params": params,
}
if args.json:
print(json.dumps(payload, ensure_ascii=False))
else:
print(json.dumps(payload, indent=2, ensure_ascii=False))
return
client = create_client()
if persist_to_tos:
if not args.json:
print(
f"[INFO] Running doc-preview for {bucket}/{key} -> {resolved_save_bucket}/{resolved_save_object}",
)
else:
if not args.json:
print(
f"[INFO] Running doc-preview for {bucket}/{key} -> {output_path}",
)
if not args.json:
print("[INFO] process = doc-preview")
print(
f"[INFO] DocDestType={args.dest_type}, DocPage={args.page}, "
f"DocImgMode={args.img_mode}, DocStartPage={args.start_page}, DocEndPage={args.end_page}",
)
req = pre_signed_request(client, bucket, key, params)
if persist_to_tos:
# We expect a JSON body describing the save result
try:
with urlopen(req) as resp:
raw = resp.read()
except HTTPError as e:
print(
f"[ERROR] HTTP error when calling doc-preview (save-to-TOS): "
f"status={e.code}, reason={e.reason}",
file=sys.stderr,
)
sys.exit(1)
except URLError as e:
print(
f"[ERROR] Failed to call doc-preview (save-to-TOS): {e.reason}",
file=sys.stderr,
)
sys.exit(1)
except Exception as exc: # noqa: BLE001
print(
f"[ERROR] Unexpected error when calling doc-preview (save-to-TOS): {exc}",
file=sys.stderr,
)
sys.exit(1)
print("[OK] Save result from TOS:")
if not maybe_print_json(raw):
# Fallback: print raw text
print(raw.decode("utf-8", errors="replace"))
return
# Local save path branch
try:
with urlopen(req) as resp, open(output_path, "wb") as out_f:
while True:
chunk = resp.read(64 * 1024)
if not chunk:
break
out_f.write(chunk)
except HTTPError as e:
print(
f"[ERROR] HTTP error when downloading preview via pre-signed URL: "
f"status={e.code}, reason={e.reason}",
file=sys.stderr,
)
try:
os.remove(output_path)
except OSError:
pass
sys.exit(1)
except URLError as e:
print(
f"[ERROR] Failed to download preview via pre-signed URL: {e.reason}",
file=sys.stderr,
)
try:
os.remove(output_path)
except OSError:
pass
sys.exit(1)
except Exception as exc: # noqa: BLE001
print(
f"[ERROR] Unexpected error when downloading preview: {exc}", file=sys.stderr
)
try:
os.remove(output_path)
except OSError:
pass
sys.exit(1)
try:
size = os.path.getsize(output_path)
except OSError:
size = -1
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: query x-tos-total-page from TOS doc-preview response headers.
This script now uses a **pre-signed URL** to call `doc-preview` and prints
the `x-tos-total-page` header, which indicates the total page count of the
source document for the given preview configuration (DocDestType/DocPage,
etc.).
Instead of passing doc-specific parameters directly to `get_object`, we
build the corresponding `x-tos-*` query parameters and generate a
pre-signed URL via `TosClientV2.pre_signed_url`, then perform the HTTP
request with the standard library.
Environment variables:
- TOS_ACCESS_KEY, TOS_SECRET_KEY, TOS_SECURITY_TOKEN(optional)
- TOS_ENDPOINT, TOS_REGION
- TOS_BUCKET, TOS_OBJECT_KEY
CLI parameters:
--bucket Override TOS_BUCKET
--key Override TOS_OBJECT_KEY
--dest-type DocDestType: pdf/png/jpg (required)
--src-type Optional DocSrcType, e.g. docx/pptx/xlsx
--page Optional DocPage for single-page preview
The script is intentionally lightweight: it focuses on header inspection
rather than saving the preview body. See WORKFLOWS.md for how caching
affects `x-tos-total-page` when calling doc-preview multiple times.
"""
import argparse
import json
import os
import sys
from typing import Optional
from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen
import tos
from tos.enum import HttpMethodType
from tos.exceptions import TosClientError, TosServerError
from doc_preview_params import build_doc_preview_query_params
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 main() -> None:
parser = argparse.ArgumentParser(
description="Read x-tos-total-page from doc-preview response headers",
)
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(
"--dest-type",
dest="dest_type",
type=str,
choices=["pdf", "png", "jpg"],
required=True,
help="DocDestType used for preview (pdf/png/jpg)",
)
parser.add_argument(
"--src-type",
dest="src_type",
type=str,
default=None,
help="Optional DocSrcType, e.g. docx/pptx/xlsx",
)
parser.add_argument(
"--page",
type=int,
default=None,
help="Optional DocPage (1-based) for page-specific preview",
)
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 args.page is not None and args.page <= 0:
print("[ERROR] --page must be >= 1 when specified", 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")
params = build_doc_preview_query_params(
dest_type=args.dest_type,
src_type=args.src_type,
page=args.page,
)
plan = {
"ok": True,
"operation": "doc_total_page",
"bucket": bucket,
"key": key,
"dest_type": args.dest_type,
"src_type": args.src_type,
"page": args.page,
"params": params,
}
if args.dry_run:
if args.json:
print(json.dumps(plan, ensure_ascii=False))
else:
print(json.dumps(plan, indent=2, ensure_ascii=False))
return
if not args.json:
print(
f"[INFO] Requesting doc-preview headers for {bucket}/{key}, "
f"DocDestType={args.dest_type}, DocPage={args.page}",
)
# Generate pre-signed URL and perform a HEAD-equivalent GET (we only care about headers).
try:
presigned = client.pre_signed_url(
HttpMethodType.Http_Method_Get,
bucket=bucket,
key=key,
query=params,
)
except TosServerError as e:
print(
f"[ERROR] Failed to generate pre-signed URL: "
f"code={e.code}, status={e.status_code}, request_id={e.request_id}, message={e.message}",
file=sys.stderr,
)
sys.exit(1)
except TosClientError as e:
print(
f"[ERROR] TOS client error when generating pre-signed URL: {e}",
file=sys.stderr,
)
sys.exit(1)
except Exception as exc: # noqa: BLE001
print(
f"[ERROR] Unexpected error when generating pre-signed URL: {exc}",
file=sys.stderr,
)
sys.exit(1)
req = Request(presigned.signed_url, headers=presigned.signed_header)
try:
# We are interested in headers. Reading zero bytes is enough to ensure the request is issued.
with urlopen(req) as resp:
# Simply capture headers; body is ignored.
headers = resp.headers
except HTTPError as e:
print(
f"[ERROR] HTTP error when calling doc-preview for headers: "
f"status={e.code}, reason={e.reason}",
file=sys.stderr,
)
sys.exit(1)
except URLError as e:
print(
f"[ERROR] Failed to call doc-preview for headers: {e.reason}",
file=sys.stderr,
)
sys.exit(1)
except Exception as exc: # noqa: BLE001
print(
f"[ERROR] Unexpected error when calling doc-preview for headers: {exc}",
file=sys.stderr,
)
sys.exit(1)
total_page = None
try:
if headers is not None:
total_page = headers.get("x-tos-total-page")
except Exception:
total_page = None
if total_page is None:
payload = {
**plan,
"ok": False,
"warning": "x-tos-total-page header not found on response.",
"total_page": None,
}
if args.json:
print(json.dumps(payload, ensure_ascii=False))
else:
print("[WARN] x-tos-total-page header not found on response.")
return
payload = {
**plan,
"total_page": total_page,
}
if args.json:
print(json.dumps(payload, ensure_ascii=False))
else:
print(f"[OK] x-tos-total-page = {total_page}")
if __name__ == "__main__":
main()
Bytedance TOS Document Process Workflows
This document illustrates common workflows for document processing using the doc-preview feature via pre-signed URLs with the Volcengine TOS Python SDK.
Table of Contents
- Workflow 1: Previewing a Document as a Single PDF
- Workflow 2: Previewing a Specific Page as a PNG Image
- Workflow 3: Getting the HTML Preview URL
- Workflow 4: Batch Exporting a Page Range to TOS
- Workflow 5: Batch Screenshot a PDF Page Range
- Workflow 6: Reading the Total Page Count Header
- Workflow 7: Handling Errors
- Workflow 8: Document to Image Set Pipeline (Orchestration)
---
Prerequisite: Client Initialization
All workflows assume you have a tos.TosClientV2 instance initialized. See README.md for details.
import os
import tos
from tos.enum import HttpMethodType
from urllib.request import urlopen, Request
from urllib.error import HTTPError, URLError
from doc_preview_params import build_doc_preview_query_params
# ... (create_client function)
client = create_client()
bucket_name = os.getenv("TOS_BUCKET")
object_key = os.getenv("TOS_OBJECT_KEY")---
Workflow 1: Previewing a Document as a Single PDF
Goal: Convert a DOCX, PPTX, or other office document into a single PDF file and save it locally.
Script: scripts/doc_preview_pdf.py
Steps: 1. Build query parameters for PDF preview using build_doc_preview_query_params. 2. Generate a pre-signed URL with client.pre_signed_url(). 3. Use urllib.request.urlopen to download the content from the signed URL and save it to a file.
Python Example (`scripts/doc_preview_pdf.py` logic):
# Assumes 'client', 'bucket_name', 'object_key' are initialized
output_pdf_path = "document_preview.pdf"
try:
print(f"Converting {bucket_name}/{object_key} to PDF -> {output_pdf_path}...")
params = build_doc_preview_query_params(dest_type="pdf")
presigned = client.pre_signed_url(HttpMethodType.Http_Method_Get, bucket_name, object_key, query=params)
req = Request(presigned.signed_url, headers=presigned.signed_header)
with urlopen(req) as response, open(output_pdf_path, "wb") as f_out:
f_out.write(response.read())
print(f"Successfully saved PDF to {output_pdf_path}")
except (HTTPError, URLError, tos.exceptions.TosServerError) as e:
print(f"Error during PDF conversion: {e}")---
Workflow 2: Previewing a Specific Page as a PNG Image
Goal: Generate a high-quality PNG image of the 5th page of a document.
Script: scripts/doc_preview_png.py
Steps: 1. Build query parameters, specifying dest_type="png", page=5, and optional image_dpi. 2. Generate and use the pre-signed URL to download the image.
Python Example (`scripts/doc_preview_png.py` logic):
page_to_preview = 5
output_image_path = f"page_{page_to_preview}.png"
try:
params = build_doc_preview_query_params(
dest_type="png",
page=page_to_preview,
image_dpi=200
)
presigned = client.pre_signed_url(HttpMethodType.Http_Method_Get, bucket_name, object_key, query=params)
req = Request(presigned.signed_url, headers=presigned.signed_header)
with urlopen(req) as response, open(output_image_path, "wb") as f_out:
f_out.write(response.read())
print(f"Successfully saved page {page_to_preview} to {output_image_path}")
except Exception as e:
print(f"Error during PNG conversion: {e}")---
Workflow 3: Getting the HTML Preview URL
Goal: Obtain the final, accessible URL for an HTML-based document preview.
Script: scripts/doc_preview_html_url.py
Steps: 1. Generate a pre-signed URL with dest_type="html". 2. Fetch the temporary HTML content from this URL. 3. Parse the HTML to find the token value. 4. URL-safe Base64 decode the token to get the final preview URL.
Python Example (`scripts/doc_preview_html_url.py` logic):
import base64
try:
params = build_doc_preview_query_params(dest_type="html")
presigned = client.pre_signed_url(HttpMethodType.Http_Method_Get, bucket_name, object_key, query=params)
req = Request(presigned.signed_url, headers=presigned.signed_header)
with urlopen(req) as response:
html_content = response.read().decode('utf-8')
# ... logic to extract and decode token ...
# final_url = decode_preview_url(token)
# print(f"Successfully extracted HTML preview URL: {final_url}")
except Exception as e:
print(f"An error occurred while getting HTML preview URL: {e}")---
Workflow 4: Batch Exporting a Page Range to TOS
Goal: Convert pages 1 through 5 of a document into JPG images and save them directly into a destination bucket in TOS.
Script: scripts/doc_preview_process.py
python3 scripts/doc_preview_process.py \
--key report.docx \
--dest-type jpg \
--img-mode 1 \
--start-page 1 \
--end-page 5 \
--saveas-bucket my-output-bucket \
--saveas-object processed/{Page}.jpg \
--jsonBehavior Notes:
--jsonreturns a structured payload for downstream agent steps.--dry-runprints the resolvedx-tos-doc-*parameters without making the request.- Without save-as parameters, the script writes the converted output locally.
---
Workflow 5: Batch Screenshot a PDF Page Range
Goal: Export multiple PDF pages to TOS as image objects with one request.
Script: scripts/doc_batch_screenshot.py
python3 scripts/doc_batch_screenshot.py \
--key test.pdf \
--format png \
--start-page 1 \
--end-page 2 \
--saveas-object "skill-test/doc/{Page}.png"Important constraints:
- Source object must be a PDF.
- Destination object must contain
{Page}. - Results are always persisted back to TOS via
--saveas-bucket/--saveas-object. - The helper handles save-as query encoding automatically.
---
Workflow 6: Reading the Total Page Count Header
Goal: Efficiently determine the number of pages in a document.
Script: scripts/doc_total_page.py
python3 scripts/doc_total_page.py --key report.docx --dest-type pdf --json
python3 scripts/doc_total_page.py --key report.docx --dest-type pdf --dry-runBehavior Notes:
--jsonreturns a structured payload with the resolved query params andtotal_pagewhen available.--dry-runprints the resolved request without issuing the HTTP call.- If the header is absent, the script returns a warning payload instead of crashing.
---
Workflow 7: Validate a doc-preview Request Before Execution
Goal: Let an agent inspect the resolved doc-preview parameters before downloading or exporting.
python3 scripts/doc_preview_process.py \
--key report.docx \
--dest-type png \
--page 2 \
--dry-runBehavior Notes:
- Useful for agent planning when the next step depends on resolved
x-tos-doc-*parameters. - Combine with
--jsonfor machine-readable planning output.
---
Workflow 8: Handling Errors
Goal: Gracefully handle common errors during the process.
Strategy:
- Wrap pre-signed URL generation in a
try...exceptblock forTosServerErrorandTosClientError. - Wrap HTTP requests (
urlopen) in atry...exceptblock forHTTPErrorandURLError.
Python Example:
try:
# ... build params ...
presigned = client.pre_signed_url(...)
req = Request(presigned.signed_url, headers=presigned.signed_header)
with urlopen(req) as response:
# ... process response ...
except tos.exceptions.TosServerError as e:
print(f"TOS SDK Error: {e.message}")
except HTTPError as e:
print(f"HTTP Error: Status {e.code}, Reason: {e.reason}")
except URLError as e:
print(f"URL Error: {e.reason}")
except Exception as e:
print(f"An unexpected error occurred: {e}")This ensures that failures from both the SDK and the subsequent HTTP call are caught and reported.
---
Workflow 8: Document to Image Set Pipeline (Orchestration)
Goal: Convert a document (docx/pptx/xlsx) into a set of page images packaged as a zip archive — a multi-step, multi-skill pipeline an agent can orchestrate.
Pipeline: doc_total_page → doc_preview_process (docx→PDF) → doc_batch_screenshot (PDF→JPG) → file_compress (JPG→zip)
Skills involved: byted-tos-doc-process, byted-tos-file-process
Important constraints:
doc_batch_screenshotonly accepts PDF as the source file (backend restriction). Non-PDF documents (docx, pptx, etc.) must be converted to PDF first.- The sync
doc-preview+save-asmode does not reliably persist PDF output to TOS. Use local download + upload instead.
Step 1: Get total page count
python3 scripts/doc_total_page.py --key report.docx --dest-type pdf --jsonExtract total_page from the JSON output to determine the page range.
Step 2: Convert document to PDF locally, then upload
# Download PDF to local filesystem
python3 scripts/doc_preview_process.py \
--key report.docx \
--dest-type pdf \
--output /tmp/report.pdf
# Upload PDF to TOS for batch screenshot input
# (Use TOS SDK put_object or any upload method)Note: The sync doc-preview + save-as-bucket/save-as-object mode returns PDF binary in the response body but may not correctly persist it to TOS. Downloading locally and uploading is the reliable approach.
Step 3: Batch screenshot all pages
python3 scripts/doc_batch_screenshot.py \
--key path/to/report.pdf \
--format jpg \
--start-page 1 \
--end-page 14 \
--saveas-bucket my-bucket \
--saveas-object "output/pages/{Page}.jpg"This produces one JPG per page in TOS. The {Page} placeholder is required.
Step 4: Compress page images into a zip
python3 /path/to/byted-tos-file-process/scripts/file_compress.py \
--keys output/pages/1.jpg,output/pages/2.jpg,...,output/pages/14.jpg \
--format zip \
--saveas-bucket my-bucket \
--saveas-object output/pages.zip \
--wait --jsonThe final result is a single zip archive containing all page images.
Agent orchestration notes
- Step 1 determines the page range; the agent should use the
total_pagevalue for--end-pageand to build the--keyslist in Step 4. - Step 2 requires an intermediate upload not covered by this skill alone — the agent should use the TOS SDK or another upload method.
- Step 3 depends on Step 2 (PDF must exist in TOS before batch screenshot).
- Step 4 depends on Step 3 (all page images must exist before compression).