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
|
Download (optional), transcribe, and summarize audio/video files fully on
|
||||||
Apple Silicon via MLX. Models are selected automatically from the detected
|
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
|
import argparse
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from .device import select_models
|
from .models import select_models
|
||||||
from .download import download_from_youtube
|
from .pipeline import download_from_youtube, summarize_text, transcribe_file
|
||||||
from .summarization import summarize_text
|
from .utils import (
|
||||||
from .transcription import transcribe_file
|
ensure_directory,
|
||||||
|
print_model_banner,
|
||||||
DEFAULT_LANGUAGE = "en" # Use "auto" on the CLI for automatic detection.
|
resolve_language,
|
||||||
|
write_summary,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _resolve_language(arg_language: str | None) -> str | None:
|
def _parse_args() -> argparse.Namespace:
|
||||||
"""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:
|
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Download, transcribe, and summarize audio or video files "
|
description="Download, transcribe, and summarize audio or video files "
|
||||||
"on Apple Silicon (MLX)."
|
"on Apple Silicon (MLX)."
|
||||||
@@ -49,24 +43,19 @@ def main() -> None:
|
|||||||
help="Language code for transcription (e.g. 'en', 'fr', 'es', "
|
help="Language code for transcription (e.g. 'en', 'fr', 'es', "
|
||||||
"or 'auto' for detection).",
|
"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 ---
|
# --- Automatic model selection based on detected unified memory ---
|
||||||
ram_gb, tier = select_models()
|
ram_gb, tier = select_models()
|
||||||
print("=" * 60)
|
print_model_banner(ram_gb, tier)
|
||||||
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)
|
|
||||||
|
|
||||||
# --- Working directory for intermediate files ---
|
# --- Working directory for intermediate files ---
|
||||||
data_directory = Path("tmp")
|
data_directory = ensure_directory("tmp")
|
||||||
if not data_directory.exists():
|
|
||||||
data_directory.mkdir(parents=True)
|
|
||||||
print(f"Created directory: {data_directory}")
|
|
||||||
|
|
||||||
# --- Resolve input file ---
|
# --- Resolve input file ---
|
||||||
if args.from_youtube:
|
if args.from_youtube:
|
||||||
@@ -91,11 +80,7 @@ def main() -> None:
|
|||||||
# --- Summarization ---
|
# --- Summarization ---
|
||||||
print("Generating summary...")
|
print("Generating summary...")
|
||||||
summary = summarize_text(transcript, tier.summarization_repo)
|
summary = summarize_text(transcript, tier.summarization_repo)
|
||||||
|
write_summary(args.output, summary)
|
||||||
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}")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
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.
|
* ``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:
|
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