Browse Source

build: scaffold typed application core

master
Yutsuo 4 days ago
parent
commit
0f5901b681
  1. 12
      .gitignore
  2. 27
      pyproject.toml
  3. 6
      requirements.txt
  4. 4
      src/voice_transcriptor/__init__.py
  5. 20
      src/voice_transcriptor/formatting.py
  6. 28
      src/voice_transcriptor/models.py
  7. 16
      tests/test_formatting.py

12
.gitignore vendored

@ -0,0 +1,12 @@
__pycache__/
*.py[cod]
.pytest_cache/
.venv/
venv/
dist/
build/
*.spec
.coverage
.idea/
.vscode/

27
pyproject.toml

@ -0,0 +1,27 @@
[build-system]
requires = ["setuptools>=75"]
build-backend = "setuptools.build_meta"
[project]
name = "voice-transcriptor"
version = "0.1.0"
description = "Windows desktop media transcription client"
requires-python = ">=3.12"
dependencies = [
"openai>=1.0",
"PySide6>=6.7",
"keyring>=25.0",
]
[project.scripts]
voice-transcriptor = "voice_transcriptor.app:main"
[tool.setuptools.packages.find]
where = ["src"]
[tool.pytest.ini_options]
addopts = "-ra"
testpaths = ["tests"]
pythonpath = ["src"]
qt_api = "pyside6"

6
requirements.txt

@ -0,0 +1,6 @@
openai>=1.0
PySide6>=6.7
keyring>=25.0
pytest>=8.0
pytest-qt>=4.4

4
src/voice_transcriptor/__init__.py

@ -0,0 +1,4 @@
"""Voice Transcriptor desktop application."""
__version__ = "0.1.0"

20
src/voice_transcriptor/formatting.py

@ -0,0 +1,20 @@
def format_file_size(size_bytes: int) -> str:
if size_bytes < 0:
raise ValueError("File size cannot be negative.")
if size_bytes < 1024:
return f"{size_bytes} B"
value = float(size_bytes)
for unit in ("KiB", "MiB", "GiB", "TiB"):
value /= 1024
if value < 1024 or unit == "TiB":
return f"{value:.2f} {unit}"
raise AssertionError("unreachable")
def format_duration(duration_seconds: float | None) -> str:
if duration_seconds is None:
return "Unknown"
seconds = max(0, round(duration_seconds))
hours, remainder = divmod(seconds, 3600)
minutes, seconds = divmod(remainder, 60)
return f"{hours:02d}:{minutes:02d}:{seconds:02d}"

28
src/voice_transcriptor/models.py

@ -0,0 +1,28 @@
from dataclasses import dataclass
from pathlib import Path
@dataclass(frozen=True, slots=True)
class AppSettings:
model: str
language: str
output_directory: Path
@dataclass(frozen=True, slots=True)
class ToolStatus:
ffmpeg_path: Path | None
ffprobe_path: Path | None
@property
def available(self) -> bool:
return self.ffmpeg_path is not None and self.ffprobe_path is not None
@dataclass(frozen=True, slots=True)
class MediaInfo:
path: Path
size_bytes: int
duration_seconds: float | None
audio_codec: str | None

16
tests/test_formatting.py

@ -0,0 +1,16 @@
from unittest import TestCase
from voice_transcriptor.formatting import format_duration, format_file_size
class FormattingTests(TestCase):
def test_formats_binary_file_sizes(self) -> None:
self.assertEqual(format_file_size(0), "0 B")
self.assertEqual(format_file_size(1536), "1.50 KiB")
self.assertEqual(format_file_size(2 * 1024**3), "2.00 GiB")
def test_formats_multi_hour_duration(self) -> None:
self.assertEqual(format_duration(3 * 3600 + 5 * 60 + 9.8), "03:05:10")
def test_formats_unavailable_duration(self) -> None:
self.assertEqual(format_duration(None), "Unknown")
Loading…
Cancel
Save