Python SDK for Qalam Document Format (.qlm) — unified multi-asset binary document container
Project description
qlam — Qalam Document Format Python SDK
"Her türlü dünyayı tek bir dosyada yaşamak." "Living every kind of world inside a single file."
qlam is the official Python SDK for reading and writing QLM (Qalam Document Format) files — a unified, binary-indexed document format designed to replace PDF by supporting rich multimedia, 3D models, interactive content, and embedded assets all in one portable file.
Installation
pip install qlam
No external dependencies. Pure Python 3.6+ standard library only.
Quick Start
import qlam
# Create a document
doc = qlam.QlmDocument(title="My First Document", author="Your Name")
# Add text blocks
doc.add_text("Introduction", "This document was created with qlam SDK.", level=1)
doc.add_text("Section 1", "Hello world! QLM supports rich content.", level=2)
# Embed any file (image, 3D model, PDF, video, Word doc, zip...)
doc.add_embed("image", "Cover Photo", "photo.png", "image/png")
doc.add_embed("model3d", "3D Object", "object.glb", "model/gltf-binary")
doc.add_embed("docx", "Report", "report.docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document")
# Compile to a single .qlm file
doc.compile("my_document.qlm")
# Load it back
loaded = qlam.QlmDocument.load("my_document.qlm")
print(loaded.metadata["title"])
for node in loaded.nodes:
print(f"[{node.type}] {node.title}")
if node.asset_bytes:
print(f" Asset: {len(node.asset_bytes)} bytes")
Supported Node Types
node_type |
Description | MIME Example |
|---|---|---|
text |
Structured text / heading block | — |
image |
PNG, JPEG, WebP, SVG, GIF | image/png |
model3d |
GLTF/GLB, OBJ, FBX 3D models | model/gltf-binary |
video |
MP4, WebM video | video/mp4 |
audio |
MP3, WAV, OGG audio | audio/mpeg |
pdf |
Embedded PDF document | application/pdf |
docx |
Microsoft Word document | application/vnd.openxmlformats-... |
zip |
ZIP archive (multi-file bundle) | application/zip |
code |
Source code block with syntax metadata | — |
table |
Structured data table | — |
chart |
Chart / graph data | — |
Full API Reference
QlmDocument(title, author="Anonim")
Creates a new QLM document.
doc = qlam.QlmDocument(title="Project Report", author="Emre Yılmaz")
Properties:
doc.id— Unique document UUID (auto-generated)doc.metadata— Dict with keys:title,author,description,language,created_at,version,is_encrypted,tagsdoc.nodes— List ofQlmNodeobjectsdoc.relationships— List of relationship dicts (optional)
doc.add_text(title, content, level=3, parent_id=None) → QlmNode
Adds a structured text node.
node = doc.add_text("Chapter 1", "Once upon a time...", level=1)
level: Heading depth (1 = H1, 2 = H2, ... 6 = H6)parent_id: ID of a parent node (for nesting)
doc.add_embed(node_type, title, asset_path, mime_type, parent_id=None) → QlmNode
Embeds any binary asset file into the document.
img_node = doc.add_embed("image", "Diagram", "diagram.png", "image/png")
model_node = doc.add_embed("model3d", "3D View", "object.glb", "model/gltf-binary")
word_node = doc.add_embed("docx", "Contract", "contract.docx","application/vnd.openxmlformats-officedocument.wordprocessingml.document")
doc.compile(output_path)
Compiles the document into a binary .qlm file.
doc.compile("output.qlm")
QlmDocument.load(file_path) → QlmDocument
Loads and parses an existing .qlm file.
doc = qlam.QlmDocument.load("output.qlm")
QlmNode Properties
| Property | Type | Description |
|---|---|---|
node.id |
str |
Unique node identifier |
node.type |
str |
Node type (text, image, model3d, etc.) |
node.title |
str |
Human-readable label |
node.data |
dict |
Type-specific payload (content, mime, offsets...) |
node.parent_id |
str or None |
Parent node ID (for tree structure) |
node.asset_bytes |
bytes or None |
Raw binary asset data (after loading) |
node.display_style |
dict |
Rendering hints (width_ratio, collapsed, etc.) |
QLM Binary Format Specification
For AI Models and Developers: This section documents the exact binary structure of
.qlmfiles so that LLMs and automated tools can generate, parse, and validate them correctly.
Overview
A .qlm file is a manifest-indexed binary container. It stores binary payloads first, then ends with a JSON manifest that describes the document structure and indexes all embedded assets.
+---------------------------------------------------------------+
| HEADER (14 bytes, fixed) |
| +--------------+----------+-------------------------------+ |
| | Magic (4B) | Ver (2B) | Manifest Offset (8B, u64 LE) | |
| | "QLAM" | 0x0002 | pointer to JSON manifest | |
| +--------------+----------+-------------------------------+ |
+---------------------------------------------------------------+
| BINARY PAYLOADS (variable, 8-byte aligned) |
| +-----------------------------+ |
| | Asset 0 raw bytes | <- node[0].data.asset_start |
| | [padding to 8-byte boundary]| |
| +-----------------------------+ |
| | Asset 1 raw bytes | <- node[1].data.asset_start |
| | [padding] | |
| +-----------------------------+ |
+---------------------------------------------------------------+
| JSON MANIFEST (UTF-8 encoded, till end of file) |
| { "id": "...", "metadata": {...}, "nodes": [...], ... } |
+---------------------------------------------------------------+
Header (Bytes 0–13)
| Offset | Size | Type | Value | Description |
|---|---|---|---|---|
| 0 | 4 | bytes |
b"QLAM" |
Magic signature |
| 4 | 2 | u16 LE |
2 |
Format version (currently 2) |
| 6 | 8 | u64 LE |
<computed> |
Byte offset of the JSON manifest |
Python reading:
import struct
with open("file.qlm", "rb") as f:
data = f.read()
magic = data[0:4] # b"QLAM"
version = struct.unpack("<H", data[4:6])[0] # 2
manifest_offset = struct.unpack("<Q", data[6:14])[0]
Binary Payload Section (Bytes 14 to manifest_offset)
Each embedded asset is stored consecutively after the header:
- No length prefix — the exact byte range is recorded in the JSON manifest
- 8-byte alignment — each asset is padded with
\x00bytes untiloffset % 8 == 0 - The manifest records
asset_start_byte(absolute offset from file start) andasset_lengthfor each node
Python extracting an asset:
manifest_json = data[manifest_offset:].decode("utf-8")
manifest = json.loads(manifest_json)
for node in manifest["nodes"]:
if "asset_start_byte" in node["data"]:
start = node["data"]["asset_start_byte"]
length = node["data"]["asset_length"]
asset = data[start:start + length] # raw bytes of the asset
JSON Manifest Schema
The manifest is a UTF-8 JSON object starting at manifest_offset and continuing to the end of the file:
{
"id": "doc_<16hex>",
"metadata": {
"title": "string",
"author": "string",
"description": "string",
"language": "tr",
"created_at": 0,
"version": "2.0.0",
"is_encrypted": false,
"tags": []
},
"nodes": [
{
"id": "node_<16hex>",
"parent_id": null,
"type": "text",
"title": "Section Title",
"data": { "content": "...", "level": 1 },
"display_style": { "width_ratio": 1.0, "collapsed": false }
},
{
"id": "node_<16hex>",
"parent_id": null,
"type": "image",
"title": "Diagram",
"data": {
"mime": "image/png",
"asset_start_byte": 14,
"asset_length": 48291
},
"display_style": { "width_ratio": 1.0, "collapsed": false }
}
],
"relationships": []
}
Node data field by type
type |
Required data keys |
|---|---|
text |
content (str), level (int 1–6) |
image |
mime (str), asset_start_byte (int), asset_length (int) |
model3d |
mime (str), asset_start_byte (int), asset_length (int) |
video |
mime (str), asset_start_byte (int), asset_length (int) |
audio |
mime (str), asset_start_byte (int), asset_length (int) |
pdf |
mime (str), asset_start_byte (int), asset_length (int) |
docx |
mime (str), asset_start_byte (int), asset_length (int) |
zip |
mime (str), asset_start_byte (int), asset_length (int) |
code |
content (str), language (str, e.g. "python") |
table |
headers (list[str]), rows (list[list]) |
AI Guidance — How to Generate a Valid .qlm File
Note for AI models (Claude, ChatGPT, Gemini, etc.): If you are asked to produce a
.qlmfile, follow this section precisely.
Option A — Use the qlam Python SDK (Recommended)
import qlam
doc = qlam.QlmDocument(title="Example Doc", author="AI")
doc.add_text("Intro", "This is generated by an AI.", level=1)
# Embed a file if it exists
# doc.add_embed("image", "Screenshot", "screen.png", "image/png")
doc.compile("ai_generated.qlm")
The SDK handles all binary layout, offset calculation, and JSON manifest writing automatically.
Option B — Construct Binary Manually (Advanced)
If you must construct a .qlm file from scratch (e.g., no SDK available):
import json, struct, os
def create_qlm(output_path, title, author, text_nodes=None, asset_nodes=None):
"""
Manually creates a valid .qlm v2 file.
text_nodes: list of (title, content, level)
asset_nodes: list of (type, title, mime, raw_bytes)
"""
text_nodes = text_nodes or []
asset_nodes = asset_nodes or []
buf = bytearray()
# --- Header ---
buf.extend(b"QLAM") # magic
buf.extend(struct.pack("<H", 2)) # version = 2
manifest_offset_pos = len(buf)
buf.extend(struct.pack("<Q", 0)) # placeholder for manifest offset
# --- Binary payloads ---
nodes_manifest = []
for title_n, content, level in text_nodes:
nodes_manifest.append({
"id": f"node_{os.urandom(8).hex()}",
"parent_id": None,
"type": "text",
"title": title_n,
"data": {"content": content, "level": level},
"display_style": {"width_ratio": 1.0, "collapsed": False}
})
for ntype, title_n, mime, raw_bytes in asset_nodes:
start = len(buf)
buf.extend(raw_bytes)
# 8-byte alignment padding
pad = (8 - (len(buf) % 8)) % 8
buf.extend(b"\x00" * pad)
nodes_manifest.append({
"id": f"node_{os.urandom(8).hex()}",
"parent_id": None,
"type": ntype,
"title": title_n,
"data": {"mime": mime, "asset_start_byte": start, "asset_length": len(raw_bytes)},
"display_style": {"width_ratio": 1.0, "collapsed": False}
})
# --- Manifest JSON ---
manifest_offset = len(buf)
manifest = {
"id": f"doc_{os.urandom(8).hex()}",
"metadata": {
"title": title, "author": author, "description": "",
"language": "tr", "created_at": 0,
"version": "2.0.0", "is_encrypted": False, "tags": []
},
"nodes": nodes_manifest,
"relationships": []
}
buf.extend(json.dumps(manifest).encode("utf-8"))
# --- Fill in manifest offset ---
struct.pack_into("<Q", buf, manifest_offset_pos, manifest_offset)
with open(output_path, "wb") as f:
f.write(buf)
print(f"Written {len(buf)} bytes → {output_path}")
# Example usage
create_qlm(
"manual.qlm",
title="Manual QLM",
author="AI Model",
text_nodes=[
("Introduction", "This document was created manually.", 1),
("Details", "Section two details...", 2),
],
asset_nodes=[
("image", "Logo", "image/png", open("logo.png", "rb").read()),
]
)
Critical Rules for AI Models
- Magic bytes MUST be exactly
b"QLAM"(4 bytes, ASCII) - Version MUST be
2encoded asu16 little-endian(\x02\x00) - Manifest offset MUST be
u64 little-endian(8 bytes) - Total header size = 14 bytes exactly (4 + 2 + 8)
asset_start_byteis the absolute file offset, not relative to payload section- All binary assets must be 8-byte aligned — pad with
\x00bytes as needed - The JSON manifest is appended last, after all binary payloads
asset_lengthis the original (unpadded) byte length of the asset- Node IDs must be unique — use
os.urandom(8).hex()or similar - Text nodes have NO binary asset — only manifest JSON
data.content
Design Goals
| Feature | QLM v2 | |
|---|---|---|
| Embed 3D models | ✅ Native GLB/OBJ | ❌ Not supported |
| Embed video/audio | ✅ MP4, WebM, MP3 | ⚠️ Limited |
| Embed Word/ZIP files | ✅ Any binary format | ❌ |
| Single portable file | ✅ Everything in one .qlm |
✅ (limited) |
| Zero-copy binary read | ✅ mmap offset slicing | ❌ |
| Human-readable manifest | ✅ JSON index | ❌ Binary xref |
| Pure Python SDK | ✅ No dependencies | ❌ Needs PyMuPDF |
| Cross-platform | ✅ Win / macOS / Linux / Android | ✅ |
Ecosystem
| Component | Technology | Description |
|---|---|---|
| Python SDK | Python 3.6+ | This package (qlam) |
| Desktop Viewer | Rust + egui | Native GPU-accelerated viewer with 3D viewport |
| Mobile App | Flutter | Android/iOS viewer with FFI bridge |
| CLI Tool | Rust | qlm-cli for compile/inspect from terminal |
License
MIT License — Copyright © 2025 Qalam Project
Links
- PyPI: https://pypi.org/project/qlam/
- GitHub: (coming soon)
- Documentation: (coming soon)
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file qlam-2.1.2.tar.gz.
File metadata
- Download URL: qlam-2.1.2.tar.gz
- Upload date:
- Size: 10.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a4fb85d80d9a84623500dbe363086ebec41a1109ddb4c164e147a1c934a06364
|
|
| MD5 |
9ec96526d7de5fa62216835b4e9fedf5
|
|
| BLAKE2b-256 |
c3c4d74ed81f968169ac817e14762597d3d729ed9f6f7802fa6f4cbf30202644
|
File details
Details for the file qlam-2.1.2-py3-none-any.whl.
File metadata
- Download URL: qlam-2.1.2-py3-none-any.whl
- Upload date:
- Size: 8.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6cfccdfe70c7255cb68059669282a3c126e95ca9a5171270c88221dad052bb42
|
|
| MD5 |
e11cc27eb3344f598c9721de033255cd
|
|
| BLAKE2b-256 |
8692d5cf9f535b7b5d5296b5f9aef45b78e090a96e4f14b44ae18e2534cb0288
|