Thumbnail API
The sb-thumbnails crate provides a unified thumbnail generation pipeline shared by
desktop, mobile (Tauri), and cloud worker pods. All outputs are capped at 512×512 —
AI tagging and vision embeddings consume the 512 thumbnail, never the full-resolution source.
Canonical crate: crates/ai/crates/sb-thumbnails/ in
studiobrain-cloud
(package sb-thumbnails, license UNLICENSED). This page documents that
crate, including the pdf and video feature flags.
SBAI-2149 moved the crate from studiobrain-core into studiobrain-ai.
studiobrain-ai is archived (SBAI-10112). Do not add a git dependency on it.
The commercial crate now lives in this repo. A smaller Apache-2.0 subset lives
in studiobrain-core again, at crates/sb-thumbnails (image, svg, and
model3d only — no pdf or video modules).
| Copy | Path | Who depends on it today |
|---|---|---|
| Commercial (this page) | studiobrain-cloud/crates/ai/crates/sb-thumbnails | crates/sb-thumbnail-worker path-depends with feature full. crates/sb-enterprise path-depends with feature image only. |
| Open-core subset | studiobrain-core/crates/sb-thumbnails | crates/sb-server path-depends on ../sb-thumbnails with feature image, behind the thumbnails feature. |
| Desktop and mobile manifests | path = "../../../crates/ai/crates/sb-thumbnails" (SBAI-12187, #2359), features image and svg | clients/desktop/src-tauri/Cargo.toml and clients/mobile/src-tauri/Cargo.toml still declare that dependency. It points at the archived repo. It is not the canonical path. |
3D Model Upload Auto-Enqueue
When a 3D model file is uploaded via POST /api/assets/upload, the server automatically
enqueues it into the AI tag queue for thumbnail generation. Supported formats: gltf, glb,
obj, fbx, dae, blend, 3ds, ply — the set defined at
crates/sb-server/src/services/asset_types.rs:34. The enqueue is best-effort: the upload
always succeeds even if the queue is unavailable.
See Asset Tag Queue for the internal queue flow.
Feature Flags
The crate uses feature-gated backends. The default feature set is image only.
Which features each package turns on is in Edition Differences.
pdf and video exist on the commercial crate, but their native backends are not linked yet
(pdf = [] and video = [] in crates/ai/crates/sb-thumbnails/Cargo.toml).
| Feature | Backend | System dependencies |
|---|---|---|
image | image crate | none (pure Rust) |
svg | resvg + usvg | none (pure Rust) |
pdf | stub (planned: pdfium-render) | none linked today |
video | stub (planned: ffmpeg-next) | none linked today |
model3d | gltf (parser) | none for parsing; render stub |
Supported Formats
| Format kind | Formats | Backend feature |
|---|---|---|
| Raster | jpg, jpeg, png, webp, gif, bmp, heic, tiff | image |
| SVG | svg | svg |
pdf | pdf | |
| Video | mp4, mov, mkv, webm, avi | video |
| 3D Model | gltf, glb, obj, fbx | model3d |
Public API
Core Types
pub use sb_thumbnails::{
Pipeline, ThumbnailRequest, ThumbnailResult,
CacheLayout, Error, Result,
MAX_DIM, STANDARD_SIZES, THUMBNAIL_CONTENT_TYPE, THUMBNAIL_EXTENSION,
detect_format, clamp_size, FormatKind,
};Constants
| Constant | Value | Description |
|---|---|---|
MAX_DIM | 512 | Maximum thumbnail dimension (hard cap) |
STANDARD_SIZES | [64, 256, 512] | Default thumbnail sizes produced by the pipeline |
THUMBNAIL_CONTENT_TYPE | "image/webp" | Media type for still thumbnails |
THUMBNAIL_EXTENSION | "webp" | File extension for still thumbnails |
Pipeline
The top-level pipeline orchestrator.
pub struct Pipeline {
cache: CacheLayout,
}
impl Pipeline {
pub fn new(cache: CacheLayout) -> Self;
pub fn cache(&self) -> &CacheLayout;
pub fn supported_formats(&self) -> Vec<&'static str>;
pub async fn generate(&self, req: ThumbnailRequest) -> Result<ThumbnailResult>;
}ThumbnailRequest
A request to generate thumbnails for a single source asset.
pub struct ThumbnailRequest {
pub source_path: PathBuf, // Path to the source file
pub tenant_id: String, // Tenant scope for cache layout
pub asset_id: String, // Stable asset ID for output keys
pub format_hint: Option<String>, // Optional mime type or extension hint
pub sizes: Vec<u32>, // Sizes to generate (default: 64, 256, 512)
pub generate_gif_preview: bool, // Refused by the Cloudflare isolate; see below
pub generate_3d_views: bool, // Refused by the Cloudflare isolate; see below
}Cloudflare isolate rejection
The SaaS thumbnail isolate (packages/cloudflare-worker/sb-thumbnail-worker) renders still lossless WebP for raster and SVG only. It does not produce {asset}_gif.gif or {asset}_3d_v1..v4.webp, and it does not ignore the flags.
If generate_gif_preview or generate_3d_views is true, validate_job rejects the whole job before any still is written. The Durable Object stores a permanent failure. Branch on code and features. Do not match reason; that string is human text.
{
"status": "failed",
"code": "unsupported_feature",
"features": ["generate_gif_preview", "generate_3d_views"],
"reason": "generate_gif_preview and generate_3d_views are not implemented by the isolate codec",
"permanent": true,
"exhausted": false,
"attempt": 0
}features contains only the flags that were set, in the order generate_gif_preview, then generate_3d_views. GET / on the worker advertises the same names:
{
"codecs": ["raster", "svg"],
"unsupported_features": ["generate_gif_preview", "generate_3d_views"],
"unsupported_feature_code": "unsupported_feature"
}To get still WebP from the isolate, submit the job again with both flags false. CacheLayout::gif_preview_key and CacheLayout::model3d_view_key stay reserved for a native renderer. The isolate never writes those keys. The native GIF backend (video.rs) and 3D renderer (model3d.rs) are stubs today, so those keys are not produced there either.
There is no with_generate_gif_preview or with_generate_3d_views builder. Set the public fields.
ThumbnailResult
pub struct ThumbnailResult {
pub asset_id: String, // The asset this result is for
pub format: Option<String>, // Detected format slug
pub generated: Vec<PathBuf>, // List of generated thumbnail file paths
pub errors: Vec<String>, // Per-size errors (does not short-circuit)
}CacheLayout
Computes key paths for the per-tenant cache. Keys are forward-slash-delimited
(suitable for S3/Garage). local_path() converts to OS-native separators.
pub struct CacheLayout {
// Private fields
}
impl CacheLayout {
pub fn new(root: impl Into<PathBuf>, tenant_id: impl Into<String>) -> Result<Self>;
pub fn root(&self) -> &Path;
pub fn tenant_id(&self) -> &str;
pub fn entity_key(&self, entity_type: &str, id: &str) -> String;
pub fn thumbnail_key(&self, asset_id: &str, size: u32) -> String;
pub fn gif_preview_key(&self, asset_id: &str) -> String;
pub fn model3d_view_key(&self, asset_id: &str, view: Model3dView) -> String;
pub fn local_path(&self, key: &str) -> PathBuf;
}Format Detection
pub fn detect_format(path: &Path, hint: Option<&str>) -> Option<String>;
pub fn clamp_size(size: u32) -> u32;
pub enum FormatKind {
Raster,
Svg,
Pdf,
Video,
Model3d,
}
impl FormatKind {
pub fn from_slug(slug: &str) -> Option<Self>;
}Cache Layout
Generated thumbnails are written to a per-tenant local cache directory and mirrored
to the sb-tenant-cache Garage S3 bucket so devices can pull each other’s thumbnails:
~/.studiobrain/cache/{tenant_id}/
thumbnails/{asset_id}_64.webp
thumbnails/{asset_id}_256.webp
thumbnails/{asset_id}_512.webp
thumbnails/{asset_id}_gif.gif // animated preview (video/gif)
thumbnails/{asset_id}_3d_v1.webp // front
thumbnails/{asset_id}_3d_v2.webp // side
thumbnails/{asset_id}_3d_v3.webp // top
thumbnails/{asset_id}_3d_v4.webp // isoStill thumbnails from the commercial crate use .webp (THUMBNAIL_EXTENSION in
crates/ai/crates/sb-thumbnails/src/lib.rs). The open-core subset in
studiobrain-core still writes .jpg.
The _gif.gif and _3d_v* names are reserved. The Cloudflare isolate does not write them. A job that sets generate_gif_preview or generate_3d_views fails with code: "unsupported_feature". See Cloudflare isolate rejection.
3D Model Views
Rust cache-key contract
The _3d_v{1..4} key scheme is defined by this crate’s Model3dView enum —
Front = 1, Side = 2, Top = 3, Iso = 4 — in
crates/ai/crates/sb-thumbnails/src/cache.rs. CacheLayout::model3d_view_key builds
{tenant}/thumbnails/{asset_id}_3d_v{N}.{THUMBNAIL_EXTENSION} from it:
Model3dView variant | Index | Key suffix |
|---|---|---|
Front | 1 | _3d_v1 |
Side | 2 | _3d_v2 |
Top | 3 | _3d_v3 |
Iso | 4 | _3d_v4 |
Example: model3d_view_key("m1", Model3dView::Top) → tenant-abc/thumbnails/m1_3d_v3.webp.
Rendering is stubbed. model3d.rs parses glTF/GLB, but render_view / render_views
return Error::Model3dError("3d render backend not yet implemented; bevy/wgpu system deps pending"). The SaaS isolate rejects generate_3d_views (see above). These keys are
the cache-layout contract, not files produced today. See the SBAI-1443 note below.
Historical Python renderer (retired)
The retired Python renderer used the following four views. This table is historical reference, not the current Rust contract:
| View | Renderer view key | Key suffix | Description |
|---|---|---|---|
| Front | front | _3d_v1.jpg | Default orientation, used as primary thumbnail |
| Side | side | _3d_v2.jpg | 90° Y-axis rotation |
| Back | back | _3d_v3.jpg | 180° Y-axis rotation |
| Three-quarter | three_quarter | _3d_v4.jpg | 45° Y + 30° X rotation (isometric) |
Historical source for this table: archived studiobrain-ai processors/model3d/renderer.py, _VIEW_ORDER = ("front", "side", "back", "three_quarter"). That file is not in studiobrain-cloud or studiobrain-core. Nothing in the active trees renders these four Python views.
The historical Python views differ from the Rust API. Model3dView in
crates/ai/crates/sb-thumbnails/src/cache.rs is Front / Side / Top / Iso, and it is
the source for the _3d_v{N} keys above. The Python table’s view order
(front/side/back/three-quarter) is not. crates/ai/crates/sb-thumbnails/src/model3d.rs:1
says “glTF/GLB parsing ships; rendering is stubbed”, and :59 / :66 return
"3d render backend not yet implemented; bevy/wgpu system deps pending".
SBAI-1443 — 3D view rendering is not live. Rechecked 2026-10-10 against studiobrain-cloud and studiobrain-core. The commercial pipeline still returns a stub error, so it does not produce the four views.
| path | state | evidence |
|---|---|---|
Commercial Rust sb-thumbnails | render stubbed | crates/ai/crates/sb-thumbnails/src/model3d.rs:1, :59, and :66. pipeline.rs:318 honours generate_3d_views by recording that the renderer is not implemented, then returns Model3dError. |
| Open-core Rust copy | same stub | studiobrain-core crates/sb-thumbnails/src/pipeline.rs:264 |
Python trimesh renderer | not in the active repos | No renderer.py under studiobrain-cloud or studiobrain-core. studiobrain-core crates/sb-server/src/services/asset_tag_worker.rs:8 and :443 say the Python /api/asset/analyze/queue path was retired in SBAI-5714. The old routes/assets.rs:3396 quote is no longer in that file. |
| Core request flag | production passes false | crates/sb-server/src/routes/assets.rs:4642. Another false at :6912 is inside the test module that starts at :6850. |
| Cloud thumbnail worker | forwards the job flag | crates/sb-thumbnail-worker/src/worker.rs:278 copies job.generate_3d_views into the pipeline request. The only true in that crate is a serde test at src/queue.rs:323. The pipeline still errors. |
The renderer description below is the old studiobrain-ai Python implementation. It is not in the active trees. Treat the rest of this section as history, not current behaviour.
SBAI-3371 (historical — this path has no live caller): The AI-side asset_processor.py handled 3D model rendering directly via the
processors/model3d/ module. The Model3DRenderer class produces deterministic front/side/back/three-quarter
views using trimesh.save_image when pyrender is available, falling back to a PIL wireframe projector
(no GPU needed), and finally a blank placeholder. All four rendered PNGs are handed through
AssetProcessor.process_image() — the same path as normal image uploads — so 3D assets receive
identical RAM tagging, captioning, and embedding vectors as 2D images. The result dict includes
thumbnails (all four views as base64 PNGs), thumbnail (front view), tags (deduplicated across
all views), and ai_analysis (per-view analysis keyed by thumbnail_front, thumbnail_side, etc.).
Thumbnails are stored in the _thumbnails subfolder alongside the asset (e.g.
Characters/hero/_thumbnails/front.png). Supported formats: .glb, .gltf, .fbx, .obj.
Usage Example
use sb_thumbnails::{CacheLayout, Pipeline, ThumbnailRequest, STANDARD_SIZES};
// Set up cache for a tenant
let cache = CacheLayout::new("/home/user/.studiobrain/cache", "tenant-abc")?;
let pipeline = Pipeline::new(cache);
// Generate thumbnails for a source asset
let req = ThumbnailRequest::new(
"/path/to/source/image.png",
"tenant-abc",
"asset-123",
);
let result = pipeline.generate(req).await?;
println!("Generated {} thumbnails", result.generated.len());GIF preview and 3D views
Do not expect these calls to write files. The Cloudflare isolate rejects either flag with code: "unsupported_feature" and writes nothing. The native pipeline’s GIF and 3D backends are stubs.
let mut req = ThumbnailRequest::new(
"/path/to/video.mp4",
"tenant-abc",
"asset-456",
);
req.generate_gif_preview = true;
// Isolate: permanent failure, features: ["generate_gif_preview"].
// Native video backend: Error::VideoError (ffmpeg stub). No asset-456_gif.gif.
let mut model = ThumbnailRequest::new(
"/path/to/model.glb",
"tenant-abc",
"asset-789",
);
model.generate_3d_views = true;
// Isolate: permanent failure, features: ["generate_3d_views"].
// Native model3d backend: Error::Model3dError. No asset-789_3d_v{1..4} keys.Error Types
#[derive(Debug, thiserror::Error)]
pub enum Error {
UnsupportedFormat(String), // Format not recognized
Io(io::Error), // Filesystem / IO error
ImageError(String), // image crate backend error
SvgError(String), // SVG backend error
PdfError(String), // PDF backend error
VideoError(String), // Video backend error
Model3dError(String), // 3D model backend error
CacheError(String), // Cache layout / write error
MissingFeature(&'static str), // Feature flag not enabled in this build
InvalidInput(String), // Malformed request
}Edition Differences
The commercial crate is studiobrain-cloud/crates/ai/crates/sb-thumbnails.
studiobrain-ai is archived. Do not cargo-depend on it. Each package below
enables a different feature set. pdf and video are stubs even when full
is on, and they are absent from the open-core subset.
| Package | Dependency today | Features |
|---|---|---|
Desktop clients/desktop/src-tauri | path ../../../crates/ai/crates/sb-thumbnails (SBAI-12187) | image, svg declared. |
Mobile clients/mobile/src-tauri | path ../../../crates/ai/crates/sb-thumbnails (SBAI-12187) | image, svg |
Cloud crates/sb-thumbnail-worker | path ../ai/crates/sb-thumbnails | full (image, svg, pdf, video, model3d) |
Cloud crates/sb-enterprise | path ../ai/crates/sb-thumbnails | image only |
Self-hosted studiobrain-core sb-server | path ../sb-thumbnails (Apache-2.0 subset) | image only, behind the thumbnails feature |
Related
- Vision API — AI vision embedding and tagging
- Storage Unification — FileServer thumbnail caching
- Architecture Summary — System overview including thumbnail flow