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      |  |
|  +--------------+----------+-------------------------------+  |
+---------------------------------------------------------------+
|  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

Feature QLM v2 PDF
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

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.2.tar.gz (10.8 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.2-py3-none-any.whl (8.5 kB view details)

Uploaded Python 3

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

Hashes for qlam-2.1.2.tar.gz
Algorithm Hash digest
SHA256 a4fb85d80d9a84623500dbe363086ebec41a1109ddb4c164e147a1c934a06364
MD5 9ec96526d7de5fa62216835b4e9fedf5
BLAKE2b-256 c3c4d74ed81f968169ac817e14762597d3d729ed9f6f7802fa6f4cbf30202644

See more details on using hashes here.

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

Hashes for qlam-2.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 6cfccdfe70c7255cb68059669282a3c126e95ca9a5171270c88221dad052bb42
MD5 e11cc27eb3344f598c9721de033255cd
BLAKE2b-256 8692d5cf9f535b7b5d5296b5f9aef45b78e090a96e4f14b44ae18e2534cb0288

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