Skip to main content

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, tags
  • doc.nodes — List of QlmNode objects
  • doc.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 .qlm files 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 start │  │
│  └──────────────┴──────────┴─────────────────────────────────┘  │
├─────────────────────────────────────────────────────────────────┤
│  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 \x00 bytes until offset % 8 == 0
  • The manifest records asset_start_byte (absolute offset from file start) and asset_length for 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 .qlm file, 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

  1. Magic bytes MUST be exactly b"QLAM" (4 bytes, ASCII)
  2. Version MUST be 2 encoded as u16 little-endian (\x02\x00)
  3. Manifest offset MUST be u64 little-endian (8 bytes)
  4. Total header size = 14 bytes exactly (4 + 2 + 8)
  5. asset_start_byte is the absolute file offset, not relative to payload section
  6. All binary assets must be 8-byte aligned — pad with \x00 bytes as needed
  7. The JSON manifest is appended last, after all binary payloads
  8. asset_length is the original (unpadded) byte length of the asset
  9. Node IDs must be unique — use os.urandom(8).hex() or similar
  10. Text nodes have NO binary asset — only manifest JSON data.content

Design Goals

Goal QLM v2 PDF
Embed 3D models ✅ Native GLB/OBJ support ❌ Not possible
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'd offset slicing
Human-readable manifest ✅ JSON index ❌ Binary xref
Pure Python SDK ✅ No dependencies ❌ Needs PyMuPDF
Cross-platform ✅ Windows / 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

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

qlam-2.1.0.tar.gz (11.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

qlam-2.1.0-py3-none-any.whl (8.8 kB view details)

Uploaded Python 3

File details

Details for the file qlam-2.1.0.tar.gz.

File metadata

  • Download URL: qlam-2.1.0.tar.gz
  • Upload date:
  • Size: 11.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.2

File hashes

Hashes for qlam-2.1.0.tar.gz
Algorithm Hash digest
SHA256 d04541ac85942a4f57902516f484dec22aa6c3b6b8655749367d24a0bee948b5
MD5 a1e10ba169712acd752ee034036b6caf
BLAKE2b-256 b1d81ddcfaa4d902c9812eaae3f2c5c18877b8d1bfdcf24b35ffee450ee66bc5

See more details on using hashes here.

File details

Details for the file qlam-2.1.0-py3-none-any.whl.

File metadata

  • Download URL: qlam-2.1.0-py3-none-any.whl
  • Upload date:
  • Size: 8.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.2

File hashes

Hashes for qlam-2.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d48f963ecf6323696b556fa13aba2650c886d4c865cc45a3d2e0f7628ec9ee0f
MD5 87abe15c3a30e55ed8d51a03888631b9
BLAKE2b-256 44ad1731015323b5889e38add7b1f19367bd868ddadb02ec490459810ead7e85

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page