feat/mlx-native #2
1
src/audio_summary/__init__.py
Normal file
1
src/audio_summary/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Transcribe and summarize audio/video files on Apple Silicon via MLX."""
|
||||
@@ -2,29 +2,23 @@
|
||||
|
||||
Download (optional), transcribe, and summarize audio/video files fully on
|
||||
Apple Silicon via MLX. Models are selected automatically from the detected
|
||||
unified memory (see ``config`` and ``device``).
|
||||
unified memory (see ``models.config`` and ``models.device``).
|
||||
"""
|
||||
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
|
||||
from .device import select_models
|
||||
from .download import download_from_youtube
|
||||
from .summarization import summarize_text
|
||||
from .transcription import transcribe_file
|
||||
|
||||
DEFAULT_LANGUAGE = "en" # Use "auto" on the CLI for automatic detection.
|
||||
from .models import select_models
|
||||
from .pipeline import download_from_youtube, summarize_text, transcribe_file
|
||||
from .utils import (
|
||||
ensure_directory,
|
||||
print_model_banner,
|
||||
resolve_language,
|
||||
write_summary,
|
||||
)
|
||||
|
||||
|
||||
def _resolve_language(arg_language: str | None) -> str | None:
|
||||
"""Map the CLI ``--language`` value to a code or ``None`` (auto-detect)."""
|
||||
language = arg_language if arg_language else DEFAULT_LANGUAGE
|
||||
if language and language.lower() == "auto":
|
||||
return None
|
||||
return language
|
||||
|
||||
|
||||
def main() -> None:
|
||||
def _parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Download, transcribe, and summarize audio or video files "
|
||||
"on Apple Silicon (MLX)."
|
||||
@@ -49,24 +43,19 @@ def main() -> None:
|
||||
help="Language code for transcription (e.g. 'en', 'fr', 'es', "
|
||||
"or 'auto' for detection).",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
return parser.parse_args()
|
||||
|
||||
language = _resolve_language(args.language)
|
||||
|
||||
def main() -> None:
|
||||
args = _parse_args()
|
||||
language = resolve_language(args.language)
|
||||
|
||||
# --- Automatic model selection based on detected unified memory ---
|
||||
ram_gb, tier = select_models()
|
||||
print("=" * 60)
|
||||
print(f"Detected unified memory : {ram_gb:.1f} GB")
|
||||
print(f"Selected hardware tier : >= {tier.min_ram_gb} GB")
|
||||
print(f"STT model : {tier.stt.repo} ({tier.stt.engine})")
|
||||
print(f"Summarization model : {tier.summarization_repo}")
|
||||
print("=" * 60)
|
||||
print_model_banner(ram_gb, tier)
|
||||
|
||||
# --- Working directory for intermediate files ---
|
||||
data_directory = Path("tmp")
|
||||
if not data_directory.exists():
|
||||
data_directory.mkdir(parents=True)
|
||||
print(f"Created directory: {data_directory}")
|
||||
data_directory = ensure_directory("tmp")
|
||||
|
||||
# --- Resolve input file ---
|
||||
if args.from_youtube:
|
||||
@@ -91,11 +80,7 @@ def main() -> None:
|
||||
# --- Summarization ---
|
||||
print("Generating summary...")
|
||||
summary = summarize_text(transcript, tier.summarization_repo)
|
||||
|
||||
with open(args.output, "w") as md_file:
|
||||
md_file.write("# Summary\n\n")
|
||||
md_file.write(summary)
|
||||
print(f"Summary written to {args.output}")
|
||||
write_summary(args.output, summary)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
13
src/audio_summary/models/__init__.py
Normal file
13
src/audio_summary/models/__init__.py
Normal file
@@ -0,0 +1,13 @@
|
||||
"""Model configuration and hardware-based model selection."""
|
||||
|
||||
from .config import TIERS, STTModel, Tier
|
||||
from .device import detect_ram_gb, select_models, select_tier
|
||||
|
||||
__all__ = [
|
||||
"TIERS",
|
||||
"STTModel",
|
||||
"Tier",
|
||||
"detect_ram_gb",
|
||||
"select_models",
|
||||
"select_tier",
|
||||
]
|
||||
11
src/audio_summary/pipeline/__init__.py
Normal file
11
src/audio_summary/pipeline/__init__.py
Normal file
@@ -0,0 +1,11 @@
|
||||
"""The download -> transcribe -> summarize processing pipeline."""
|
||||
|
||||
from .download import download_from_youtube
|
||||
from .summarization import summarize_text
|
||||
from .transcription import transcribe_file
|
||||
|
||||
__all__ = [
|
||||
"download_from_youtube",
|
||||
"summarize_text",
|
||||
"transcribe_file",
|
||||
]
|
||||
@@ -7,7 +7,7 @@ Two engines are supported, selected through ``config.STTModel.engine``:
|
||||
* ``whisper`` -> ``mlx-whisper`` fallback for memory-constrained Macs.
|
||||
"""
|
||||
|
||||
from .config import STTModel
|
||||
from ..models import STTModel
|
||||
|
||||
|
||||
def _transcribe_voxtral(file_path: str, model_repo: str, language: str | None) -> str:
|
||||
15
src/audio_summary/utils/__init__.py
Normal file
15
src/audio_summary/utils/__init__.py
Normal file
@@ -0,0 +1,15 @@
|
||||
"""Cross-cutting helpers (I/O, formatting, CLI argument resolution)."""
|
||||
|
||||
from .helpers import (
|
||||
ensure_directory,
|
||||
print_model_banner,
|
||||
resolve_language,
|
||||
write_summary,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"ensure_directory",
|
||||
"print_model_banner",
|
||||
"resolve_language",
|
||||
"write_summary",
|
||||
]
|
||||
46
src/audio_summary/utils/helpers.py
Normal file
46
src/audio_summary/utils/helpers.py
Normal file
@@ -0,0 +1,46 @@
|
||||
"""Cross-cutting helpers: CLI argument resolution, I/O, and console output.
|
||||
|
||||
These keep ``cli.py`` focused on orchestration and the ``pipeline`` modules
|
||||
focused on the actual ML work.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from ..models import Tier
|
||||
|
||||
DEFAULT_LANGUAGE = "en" # Use "auto" on the CLI for automatic detection.
|
||||
|
||||
|
||||
def resolve_language(arg_language: str | None) -> str | None:
|
||||
"""Map the CLI ``--language`` value to a code or ``None`` (auto-detect)."""
|
||||
language = arg_language if arg_language else DEFAULT_LANGUAGE
|
||||
if language and language.lower() == "auto":
|
||||
return None
|
||||
return language
|
||||
|
||||
|
||||
def ensure_directory(path: str | Path) -> Path:
|
||||
"""Create ``path`` (and parents) if missing and return it as a ``Path``."""
|
||||
directory = Path(path)
|
||||
if not directory.exists():
|
||||
directory.mkdir(parents=True)
|
||||
print(f"Created directory: {directory}")
|
||||
return directory
|
||||
|
||||
|
||||
def print_model_banner(ram_gb: float, tier: Tier) -> None:
|
||||
"""Print the detected hardware and the models selected for it."""
|
||||
print("=" * 60)
|
||||
print(f"Detected unified memory : {ram_gb:.1f} GB")
|
||||
print(f"Selected hardware tier : >= {tier.min_ram_gb} GB")
|
||||
print(f"STT model : {tier.stt.repo} ({tier.stt.engine})")
|
||||
print(f"Summarization model : {tier.summarization_repo}")
|
||||
print("=" * 60)
|
||||
|
||||
|
||||
def write_summary(output_path: str | Path, summary: str) -> None:
|
||||
"""Write ``summary`` to ``output_path`` as a titled markdown document."""
|
||||
with open(output_path, "w") as md_file:
|
||||
md_file.write("# Summary\n\n")
|
||||
md_file.write(summary)
|
||||
print(f"Summary written to {output_path}")
|
||||
Reference in New Issue
Block a user