Files
wehub-resource-sync 461bf6fd40
CI / lint (3.11) (push) Has been cancelled
CI / lint (3.12) (push) Has been cancelled
CI / lint (3.13) (push) Has been cancelled
CI / shellcheck (push) Has been cancelled
CI / shfmt (push) Has been cancelled
CI / setup (3.11) (push) Has been cancelled
CI / setup (3.12) (push) Has been cancelled
CI / setup (3.13) (push) Has been cancelled
CI / check-licenses (3.12) (push) Has been cancelled
CI / test_unit (3.11) (push) Has been cancelled
CI / test_unit (3.12) (push) Has been cancelled
CI / test_unit (3.13) (push) Has been cancelled
CI / test_unit_no_extras (3.11) (push) Has been cancelled
CI / test_unit_no_extras (3.12) (push) Has been cancelled
CI / test_json_to_html (3.12) (push) Has been cancelled
CI / test_unit_no_extras (3.13) (push) Has been cancelled
CI / test_unit_dependency_extras (csv, 3.12, --extra csv) (push) Has been cancelled
CI / test_unit_dependency_extras (xlsx, 3.11, --extra xlsx) (push) Has been cancelled
CI / test_unit_dependency_extras (xlsx, 3.12, --extra xlsx) (push) Has been cancelled
CI / test_unit_dependency_extras (csv, 3.11, --extra csv) (push) Has been cancelled
CI / test_unit_dependency_extras (csv, 3.13, --extra csv) (push) Has been cancelled
CI / test_unit_dependency_extras (docx, 3.11, --extra docx) (push) Has been cancelled
CI / test_unit_dependency_extras (docx, 3.12, --extra docx) (push) Has been cancelled
CI / test_unit_dependency_extras (docx, 3.13, --extra docx) (push) Has been cancelled
CI / test_unit_dependency_extras (markdown, 3.11, --extra md) (push) Has been cancelled
CI / test_unit_dependency_extras (markdown, 3.12, --extra md) (push) Has been cancelled
CI / test_unit_dependency_extras (markdown, 3.13, --extra md) (push) Has been cancelled
CI / test_unit_dependency_extras (odt, 3.11, --extra odt) (push) Has been cancelled
CI / test_unit_dependency_extras (odt, 3.12, --extra odt) (push) Has been cancelled
CI / test_unit_dependency_extras (odt, 3.13, --extra odt) (push) Has been cancelled
CI / test_unit_dependency_extras (pdf-image, 3.11, --extra pdf --extra image --extra paddleocr) (push) Has been cancelled
CI / test_unit_dependency_extras (pdf-image, 3.12, --extra pdf --extra image --extra paddleocr) (push) Has been cancelled
CI / test_unit_dependency_extras (pdf-image, 3.13, --extra pdf --extra image --extra paddleocr) (push) Has been cancelled
CI / test_unit_dependency_extras (pptx, 3.11, --extra pptx) (push) Has been cancelled
CI / test_unit_dependency_extras (pptx, 3.12, --extra pptx) (push) Has been cancelled
CI / test_unit_dependency_extras (pptx, 3.13, --extra pptx) (push) Has been cancelled
CI / test_unit_dependency_extras (pypandoc, 3.11, --extra epub --extra org --extra rtf --extra rst) (push) Has been cancelled
CI / test_unit_dependency_extras (pypandoc, 3.12, --extra epub --extra org --extra rtf --extra rst) (push) Has been cancelled
CI / test_unit_dependency_extras (pypandoc, 3.13, --extra epub --extra org --extra rtf --extra rst) (push) Has been cancelled
Build And Push Docker Image / set-short-sha (push) Has been cancelled
Partition Benchmark / setup (push) Has been cancelled
Partition Benchmark / Measure and compare partition() runtime (push) Has been cancelled
CI / test_unit_dependency_extras (xlsx, 3.13, --extra xlsx) (push) Has been cancelled
CI / test_ingest_src (3.12) (push) Has been cancelled
CI / test_json_to_markdown (3.12) (push) Has been cancelled
CI / changelog (push) Has been cancelled
CI / test_dockerfile (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Build And Push Docker Image / build-images (linux/amd64, opensource-linux-8core) (push) Has been cancelled
Build And Push Docker Image / build-images (linux/arm64, ubuntu-24.04-arm) (push) Has been cancelled
Build And Push Docker Image / publish-images (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:33:56 +08:00

146 lines
5.4 KiB
Python

"""Partition audio files into elements using speech-to-text transcription."""
from __future__ import annotations
import shutil
import tempfile
from pathlib import Path
from typing import IO, Any
from unstructured.chunking import add_chunking_strategy
from unstructured.documents.elements import Element, ElementMetadata, NarrativeText
from unstructured.file_utils.filetype import detect_filetype
from unstructured.file_utils.model import FileType
from unstructured.partition.common.common import exactly_one
from unstructured.partition.common.metadata import apply_metadata, get_last_modified_date
from unstructured.partition.utils.speech_to_text.speech_to_text_interface import (
SpeechToTextAgent,
)
from unstructured.utils import is_temp_file_path
_AUDIO_MIME_TYPE_FALLBACK = FileType.WAV.mime_type
@apply_metadata()
@add_chunking_strategy
def partition_audio(
filename: str | None = None,
*,
file: IO[bytes] | None = None,
language: str | None = None,
stt_agent: str | None = None,
metadata_filename: str | None = None,
metadata_last_modified: str | None = None,
**kwargs: Any,
) -> list[Element]:
"""Partition an audio file into elements using speech-to-text transcription.
Supports any audio format handled by the configured STT agent. The default Whisper
agent accepts any format that ffmpeg supports (WAV, MP3, FLAC, M4A, OGG, OPUS, WEBM,
and more). Transcription produces one NarrativeText element per segment, preserving
segment-level timestamps in ``metadata.segment_start_seconds`` and
``metadata.segment_end_seconds``. Requires the optional ``audio`` extra:
``pip install "unstructured[audio]"``.
.. note::
**Chunking drops segment timestamps.** When elements are merged by a chunking
strategy, ``segment_start_seconds`` and ``segment_end_seconds`` are currently
discarded (consolidation strategy ``DROP``). If audio timeline alignment matters
for your use-case, consume the un-chunked elements directly.
Parameters
----------
filename
Path to the audio file.
file
File-like object opened in binary mode (e.g. ``open("audio.mp3", "rb")``).
language
Optional ISO 639-1 language code for the spoken language (e.g. ``"en"``).
When ``None``, the speech-to-text agent may auto-detect.
stt_agent
Optional fully-qualified class name of the SpeechToTextAgent implementation.
Defaults to the Whisper agent when the ``audio`` extra is installed.
metadata_filename
Filename to store in element metadata when partitioning from a file object.
metadata_last_modified
Last modified date to store in element metadata.
"""
exactly_one(filename=filename, file=file)
# Metadata from original input only (not the temp path); compute before any temp file lifecycle.
last_modified = get_last_modified_date(filename) if filename else None
mime_type = _audio_mime_type(filename, file, metadata_filename)
audio_path: str
if filename is not None:
audio_path = filename
else:
assert file is not None # guaranteed by exactly_one()
file.seek(0)
suffix = _audio_suffix(file, metadata_filename)
with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as tmp:
shutil.copyfileobj(file, tmp)
audio_path = tmp.name
try:
agent = SpeechToTextAgent.get_agent(stt_agent)
segments = agent.transcribe_segments(audio_path, language=language)
finally:
if filename is None and is_temp_file_path(audio_path):
Path(audio_path).unlink(missing_ok=True)
if not segments:
return []
elements: list[Element] = []
for seg in segments:
text = seg["text"].strip()
if not text:
continue
element = NarrativeText(text=text)
element.metadata = ElementMetadata(
filetype=mime_type,
last_modified=last_modified,
segment_start_seconds=seg["start"],
segment_end_seconds=seg["end"],
)
element.metadata.detection_origin = "speech_to_text"
elements.append(element)
return elements
def _audio_mime_type(
filename: str | None,
file: IO[bytes] | None,
metadata_filename: str | None,
) -> str:
"""Return the MIME type for the audio source, falling back to WAV when undetectable."""
detected: FileType | None = None
if filename is not None:
detected = detect_filetype(file_path=filename)
elif file is not None:
detected = detect_filetype(
file=file,
metadata_file_path=metadata_filename,
)
if detected is not None and detected not in (FileType.UNK, FileType.EMPTY):
return detected.mime_type
return _AUDIO_MIME_TYPE_FALLBACK
def _audio_suffix(file: IO[bytes], metadata_filename: str | None) -> str:
"""Return the file-extension suffix for a temp file that wraps `file`.
Preference order:
1. Extension of `metadata_filename` when provided (e.g. ".mp3" from "recording.mp3").
2. Extension of `file.name` when the file object exposes a name (e.g. a real opened file).
3. ".wav" as a safe fallback recognised by all STT backends.
"""
for name in (metadata_filename, getattr(file, "name", None)):
if name:
suffix = Path(name).suffix
if suffix:
return suffix
return ".wav"