---
title: "Ingest Audio Transcripts into Qdrant — EnConvert API"
description: "Ingest audio transcripts into Qdrant with semantic chunks and speaker boundaries preserved. No mid-sentence cuts. Free tier, 500 ops per month."
canonical: "https://www.enconvert.com/solutions/ingest-audio-transcripts-into-qdrant-enconvert-api"
locale: "en"
---

# How to Ingest Audio Transcripts into Qdrant with EnConvert

Raw Whisper output is a single continuous string with no structural boundaries. Fixed-size chunking cuts across sentence boundaries and speaker turns, producing fragments that lose the conversational unit they came from — a query about a decision made halfway through a meeting retrieves the setup without the conclusion. Splitting on speaker labels helps when formatting is consistent, but speaker turns vary from one word to several paragraphs, so chunk sizes stay arbitrary. Topic shifts within a single speaker's monologue are invisible to a splitter that only sees whitespace and punctuation. The result is a Qdrant collection where retrieval matches surface-level token overlap rather than semantic coherence.

## Example

```python
import os
import json
import re
import time
import uuid

import requests
from openai import OpenAI
from qdrant_client import QdrantClient
from qdrant_client.models import Distance, PointStruct, VectorParams

ENCONVERT_API_KEY = os.environ["ENCONVERT_API_KEY"]
# A diarized transcript as plain text, one speaker turn per line, for example
# "Alice: We should ship on Friday."
TRANSCRIPT_PATH = "/path/to/your/transcript.txt"
COLLECTION_NAME = "transcripts"
BATCH_SIZE = 100

# Step 1: Turn each "Speaker:" label into a "## Speaker" heading. EnConvert's
# chunker splits only on #, ## and ### headings, so a bare "Alice:" label would
# not start a new chunk. Each heading becomes a section boundary and lands in
# headings_path and section.
with open(TRANSCRIPT_PATH, encoding="utf-8") as transcript_file:
    transcript = transcript_file.read()
markdown = re.sub(
    r"^([A-Z][\w .'-]{0,40}):[ \t]*",
    r"\n## \1\n\n",
    transcript,
    flags=re.MULTILINE,
)

# Step 2: Upload the Markdown to EnConvert (the multipart field is "files").
# The .md filename tells EnConvert how to read the upload.
submit_response = requests.post(
    "https://api.enconvert.com/v2/ingest/files",
    headers={"X-API-Key": ENCONVERT_API_KEY},
    files={"files": ("transcript.md", markdown.encode("utf-8"))},
    timeout=60,
)
submit_response.raise_for_status()
job_id = submit_response.json()["job_id"]

# Step 3: Poll until the job reaches a final status (gives up after about 10 minutes)
for _ in range(200):
    time.sleep(3)
    poll_response = requests.get(
        f"https://api.enconvert.com/v2/ingest/{job_id}",
        headers={"X-API-Key": ENCONVERT_API_KEY},
        timeout=60,
    )
    poll_response.raise_for_status()
    job = poll_response.json()
    if job["status"] in ("completed", "failed", "canceled"):
        break
else:
    raise TimeoutError(f"Ingest job {job_id} did not finish in time")

if job["status"] != "completed":
    raise RuntimeError(f"Ingest {job['status']}: {job.get('error_message')}")

# Step 4: Download the JSONL output, one chunk per line. The output_url is a
# signed URL (valid 15 minutes). Plain GET, no API key.
jsonl_response = requests.get(job["output_url"], timeout=60)
jsonl_response.raise_for_status()
chunks = [
    json.loads(line)
    for line in jsonl_response.content.decode("utf-8").split("\n")
    if line.strip()
]

# Step 5: Embed each chunk and upsert it into Qdrant
openai_client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
qdrant = QdrantClient(
    url=os.environ["QDRANT_URL"],
    api_key=os.environ["QDRANT_API_KEY"],
    timeout=60,
)

if not qdrant.collection_exists(COLLECTION_NAME):
    qdrant.create_collection(
        collection_name=COLLECTION_NAME,
        vectors_config=VectorParams(size=1536, distance=Distance.COSINE),
    )

for start in range(0, len(chunks), BATCH_SIZE):
    batch = chunks[start : start + BATCH_SIZE]
    embeddings = openai_client.embeddings.create(
        model="text-embedding-3-small",
        input=[chunk["content"] for chunk in batch],
    )
    qdrant.upsert(
        collection_name=COLLECTION_NAME,
        points=[
            PointStruct(
                # Qdrant ids must be UUIDs or integers, so derive one from the chunk id
                id=str(uuid.uuid5(uuid.NAMESPACE_URL, chunk["id"])),
                vector=item.embedding,
                # metadata has source_url, title, headings_path (a list, which
                # Qdrant payloads accept), section, word_count and chunk_index.
                # The speaker name lives only in headings_path and section, so it
                # is stored in the payload but not embedded.
                payload={"text": chunk["content"], **chunk["metadata"]},
            )
            for chunk, item in zip(batch, embeddings.data)
        ],
    )

print(f"Upserted {len(chunks)} transcript chunks into {COLLECTION_NAME}")
```

## Output

Fixed-size chunking turns a raw transcript into fragments that break mid-sentence and ignore every speaker turn and topic shift, versus EnConvert returning semantically coherent chunks split on speaker labels and topic headings with the full headings_path and section metadata attached to every Qdrant point.

## Get started

[Get free API key — 500 ops per month, no card required.](https://www.enconvert.com/auth?mode=signup)

## Related solutions

- [How to Scrape Wikipedia with LlamaIndex](https://www.enconvert.com/solutions/scrape-wikipedia-with-llamaindex-enconvert.md)
- [How to Ingest Markdown into LangChain with EnConvert](https://www.enconvert.com/solutions/ingest-markdown-into-langchain-with-heading-context.md)
- [Best JigsawStack Alternative for Structured Data Extraction](https://www.enconvert.com/solutions/jigsawstack-alternative-for-structured-data-enconvert.md)
- [How to Ingest a CSV into pgvector Without Losing Row Context](https://www.enconvert.com/solutions/ingest-csv-into-pgvector-with-row-context-enconvert-api.md)
- [How to Ingest CSV into CrewAI with EnConvert](https://www.enconvert.com/solutions/ingest-csv-into-crewai-with-enconvert.md)
- [How to Ingest CSV into LlamaIndex with EnConvert](https://www.enconvert.com/solutions/ingest-csv-into-llamaindex-with-enconvert.md)
