ytxt 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ytxt-0.1.0/PKG-INFO +67 -0
- ytxt-0.1.0/README.md +52 -0
- ytxt-0.1.0/pyproject.toml +32 -0
- ytxt-0.1.0/setup.cfg +4 -0
- ytxt-0.1.0/src/ytxt/__init__.py +12 -0
- ytxt-0.1.0/src/ytxt/cache.py +21 -0
- ytxt-0.1.0/src/ytxt/cli.py +72 -0
- ytxt-0.1.0/src/ytxt/downloader.py +33 -0
- ytxt-0.1.0/src/ytxt/formatter.py +28 -0
- ytxt-0.1.0/src/ytxt/transcriber.py +17 -0
- ytxt-0.1.0/src/ytxt.egg-info/PKG-INFO +67 -0
- ytxt-0.1.0/src/ytxt.egg-info/SOURCES.txt +14 -0
- ytxt-0.1.0/src/ytxt.egg-info/dependency_links.txt +1 -0
- ytxt-0.1.0/src/ytxt.egg-info/entry_points.txt +2 -0
- ytxt-0.1.0/src/ytxt.egg-info/requires.txt +2 -0
- ytxt-0.1.0/src/ytxt.egg-info/top_level.txt +1 -0
ytxt-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ytxt
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Local audio and YouTube transcription CLI
|
|
5
|
+
Author: Rayan Rane
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/rayanrane/ytxt
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/rayanrane/ytxt/issues
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.7
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
Requires-Dist: yt-dlp
|
|
14
|
+
Requires-Dist: faster-whisper
|
|
15
|
+
|
|
16
|
+
# ytxt
|
|
17
|
+
|
|
18
|
+
`ytxt` is a local, private CLI tool for transcribing audio from YouTube, web URLs, or local files using `faster-whisper`. All processing happens securely on your machine—no external APIs or cloud services required.
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
Ensure you have `ffmpeg` installed on your system. Then, install the project:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install .
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Alternatively, if you are running from the source:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install -r requirements.txt
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
Basic usage:
|
|
37
|
+
```bash
|
|
38
|
+
ytxt <url_or_local_file_path>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Options
|
|
42
|
+
|
|
43
|
+
- `--format [text|markdown|srt|json]`: Specify output format.
|
|
44
|
+
- `--model [base|small|medium|large]`: Choose the Whisper model size.
|
|
45
|
+
- `--timestamps`: Include start and end times in text/markdown output.
|
|
46
|
+
- `--output <path>`: Save the transcript to a file.
|
|
47
|
+
- `--no-cache`: Force a re-transcription by ignoring existing cache.
|
|
48
|
+
|
|
49
|
+
### Examples
|
|
50
|
+
|
|
51
|
+
**Transcribe a web URL (YouTube or other):**
|
|
52
|
+
```bash
|
|
53
|
+
ytxt https://www.example.com/audio.mp3 --format markdown --timestamps --output transcript.md
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Transcribe a local audio file:**
|
|
57
|
+
```bash
|
|
58
|
+
ytxt path/to/audio.mp3 --model medium --output transcript.txt
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Features
|
|
62
|
+
|
|
63
|
+
- **Local-Only:** Privacy-first; no data leaves your machine.
|
|
64
|
+
- **Universal Support:** Transcribe YouTube videos, web audio URLs, and local files.
|
|
65
|
+
- **Caching:** Efficiently caches results to avoid redundant work.
|
|
66
|
+
- **Formats:** Supports text, Markdown, SRT, and JSON.
|
|
67
|
+
- **Model Selection:** Flexibility to balance accuracy and performance by choosing your desired Whisper model size.
|
ytxt-0.1.0/README.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# ytxt
|
|
2
|
+
|
|
3
|
+
`ytxt` is a local, private CLI tool for transcribing audio from YouTube, web URLs, or local files using `faster-whisper`. All processing happens securely on your machine—no external APIs or cloud services required.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Ensure you have `ffmpeg` installed on your system. Then, install the project:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install .
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Alternatively, if you are running from the source:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install -r requirements.txt
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
Basic usage:
|
|
22
|
+
```bash
|
|
23
|
+
ytxt <url_or_local_file_path>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Options
|
|
27
|
+
|
|
28
|
+
- `--format [text|markdown|srt|json]`: Specify output format.
|
|
29
|
+
- `--model [base|small|medium|large]`: Choose the Whisper model size.
|
|
30
|
+
- `--timestamps`: Include start and end times in text/markdown output.
|
|
31
|
+
- `--output <path>`: Save the transcript to a file.
|
|
32
|
+
- `--no-cache`: Force a re-transcription by ignoring existing cache.
|
|
33
|
+
|
|
34
|
+
### Examples
|
|
35
|
+
|
|
36
|
+
**Transcribe a web URL (YouTube or other):**
|
|
37
|
+
```bash
|
|
38
|
+
ytxt https://www.example.com/audio.mp3 --format markdown --timestamps --output transcript.md
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Transcribe a local audio file:**
|
|
42
|
+
```bash
|
|
43
|
+
ytxt path/to/audio.mp3 --model medium --output transcript.txt
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Features
|
|
47
|
+
|
|
48
|
+
- **Local-Only:** Privacy-first; no data leaves your machine.
|
|
49
|
+
- **Universal Support:** Transcribe YouTube videos, web audio URLs, and local files.
|
|
50
|
+
- **Caching:** Efficiently caches results to avoid redundant work.
|
|
51
|
+
- **Formats:** Supports text, Markdown, SRT, and JSON.
|
|
52
|
+
- **Model Selection:** Flexibility to balance accuracy and performance by choosing your desired Whisper model size.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ytxt"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name="Rayan Rane" },
|
|
10
|
+
]
|
|
11
|
+
description = "Local audio and YouTube transcription CLI"
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
requires-python = ">=3.7"
|
|
14
|
+
license = {text = "MIT"}
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
]
|
|
19
|
+
dependencies = [
|
|
20
|
+
"yt-dlp",
|
|
21
|
+
"faster-whisper",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[project.urls]
|
|
25
|
+
"Homepage" = "https://github.com/rayanrane/ytxt"
|
|
26
|
+
"Bug Tracker" = "https://github.com/rayanrane/ytxt/issues"
|
|
27
|
+
|
|
28
|
+
[project.scripts]
|
|
29
|
+
ytxt = "ytxt.cli:main"
|
|
30
|
+
|
|
31
|
+
[tool.setuptools.packages.find]
|
|
32
|
+
where = ["src"]
|
ytxt-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from .downloader import download_audio
|
|
2
|
+
from .transcriber import transcribe_audio
|
|
3
|
+
from .formatter import format_transcript
|
|
4
|
+
from .cache import read_cache, write_cache
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"download_audio",
|
|
8
|
+
"transcribe_audio",
|
|
9
|
+
"format_transcript",
|
|
10
|
+
"read_cache",
|
|
11
|
+
"write_cache",
|
|
12
|
+
]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import hashlib
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Optional, List, Dict
|
|
5
|
+
|
|
6
|
+
CACHE_DIR = Path("cache")
|
|
7
|
+
|
|
8
|
+
def get_cache_path(video_id: str) -> Path:
|
|
9
|
+
CACHE_DIR.mkdir(exist_ok=True)
|
|
10
|
+
return CACHE_DIR / f"{video_id}.json"
|
|
11
|
+
|
|
12
|
+
def read_cache(video_id: str) -> Optional[List[Dict]]:
|
|
13
|
+
path = get_cache_path(video_id)
|
|
14
|
+
if path.exists():
|
|
15
|
+
with open(path, "r") as f:
|
|
16
|
+
return json.load(f)
|
|
17
|
+
return None
|
|
18
|
+
|
|
19
|
+
def write_cache(video_id: str, transcript: List[Dict]):
|
|
20
|
+
with open(get_cache_path(video_id), "w") as f:
|
|
21
|
+
json.dump(transcript, f)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import sys
|
|
3
|
+
import hashlib
|
|
4
|
+
import re
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from .downloader import download_audio
|
|
7
|
+
from .transcriber import transcribe_audio
|
|
8
|
+
from .formatter import format_transcript
|
|
9
|
+
from .cache import read_cache, write_cache
|
|
10
|
+
|
|
11
|
+
def parse_args():
|
|
12
|
+
parser = argparse.ArgumentParser(description="ytxt: Local YouTube transcript CLI")
|
|
13
|
+
parser.add_argument("url", help="YouTube/Audio URL or local file path to transcribe")
|
|
14
|
+
parser.add_argument("--output", help="Output file path")
|
|
15
|
+
parser.add_argument("--format", choices=["text", "markdown", "srt", "json"], default="text", help="Output format")
|
|
16
|
+
parser.add_argument("--model", default="base", help="Whisper model to use")
|
|
17
|
+
parser.add_argument("--timestamps", action="store_true", help="Include timestamps in output")
|
|
18
|
+
parser.add_argument("--no-cache", action="store_true", help="Skip cache usage")
|
|
19
|
+
|
|
20
|
+
return parser.parse_args()
|
|
21
|
+
|
|
22
|
+
def main():
|
|
23
|
+
args = parse_args()
|
|
24
|
+
|
|
25
|
+
input_path = Path(args.url)
|
|
26
|
+
|
|
27
|
+
# Check if local file
|
|
28
|
+
if input_path.exists() and input_path.is_file():
|
|
29
|
+
audio_file = input_path
|
|
30
|
+
# Use absolute path for consistent cache key
|
|
31
|
+
cache_key = hashlib.md5(str(input_path.absolute()).encode()).hexdigest()
|
|
32
|
+
print(f"Using local file: {input_path}")
|
|
33
|
+
else:
|
|
34
|
+
# It's a URL
|
|
35
|
+
# Use URL as cache key for web sources
|
|
36
|
+
cache_key = hashlib.md5(args.url.encode()).hexdigest()
|
|
37
|
+
|
|
38
|
+
# Check cache first
|
|
39
|
+
transcript = None
|
|
40
|
+
if not args.no_cache:
|
|
41
|
+
transcript = read_cache(cache_key)
|
|
42
|
+
if transcript:
|
|
43
|
+
print("Using cached transcript...")
|
|
44
|
+
|
|
45
|
+
if not transcript:
|
|
46
|
+
print(f"Downloading audio from {args.url}...")
|
|
47
|
+
audio_file = download_audio(args.url)
|
|
48
|
+
print("Transcribing...")
|
|
49
|
+
transcript = transcribe_audio(audio_file, args.model)
|
|
50
|
+
write_cache(cache_key, transcript)
|
|
51
|
+
# Cleanup temp file
|
|
52
|
+
if audio_file.exists():
|
|
53
|
+
audio_file.unlink()
|
|
54
|
+
else:
|
|
55
|
+
# We already have the transcript from cache
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
# Ensure transcript is loaded (from file transcription or cached/downloaded)
|
|
59
|
+
if 'transcript' not in locals():
|
|
60
|
+
print("Transcribing...")
|
|
61
|
+
transcript = transcribe_audio(audio_file, args.model)
|
|
62
|
+
|
|
63
|
+
output = format_transcript(transcript, args.format, args.timestamps)
|
|
64
|
+
|
|
65
|
+
if args.output:
|
|
66
|
+
out_path = Path(args.output)
|
|
67
|
+
out_path.parent.mkdir(parents=True, exist_ok=True)
|
|
68
|
+
out_path.write_text(output)
|
|
69
|
+
print(f"Transcript saved to {out_path}")
|
|
70
|
+
else:
|
|
71
|
+
print(output)
|
|
72
|
+
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import yt_dlp
|
|
2
|
+
import uuid
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
def download_audio(url: str, output_dir: Path = Path("temp")) -> Path:
|
|
6
|
+
"""Downloads audio from any yt-dlp supported site."""
|
|
7
|
+
output_dir.mkdir(exist_ok=True)
|
|
8
|
+
|
|
9
|
+
# Use a unique ID for the filename to prevent collisions
|
|
10
|
+
unique_id = str(uuid.uuid4())
|
|
11
|
+
|
|
12
|
+
ydl_opts = {
|
|
13
|
+
'format': 'bestaudio/best',
|
|
14
|
+
'outtmpl': str(output_dir / f"{unique_id}.%(ext)s"),
|
|
15
|
+
'postprocessors': [{
|
|
16
|
+
'key': 'FFmpegExtractAudio',
|
|
17
|
+
'preferredcodec': 'mp3',
|
|
18
|
+
'preferredquality': '192',
|
|
19
|
+
}],
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
|
23
|
+
info = ydl.extract_info(url, download=True)
|
|
24
|
+
|
|
25
|
+
# yt-dlp returns info about the download.
|
|
26
|
+
# If it was a playlist, we might need to handle multiple files,
|
|
27
|
+
# but for now we assume a single download.
|
|
28
|
+
if 'entries' in info:
|
|
29
|
+
# Handle playlist: get the first item
|
|
30
|
+
info = info['entries'][0]
|
|
31
|
+
|
|
32
|
+
audio_file = output_dir / f"{unique_id}.mp3"
|
|
33
|
+
return audio_file
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import List, Dict
|
|
4
|
+
|
|
5
|
+
def format_transcript(transcript: List[Dict], format_type: str, include_timestamps: bool = False) -> str:
|
|
6
|
+
"""Formats transcript data into requested format."""
|
|
7
|
+
if format_type == "json":
|
|
8
|
+
return json.dumps(transcript, indent=2)
|
|
9
|
+
|
|
10
|
+
output = []
|
|
11
|
+
for segment in transcript:
|
|
12
|
+
line = ""
|
|
13
|
+
if include_timestamps:
|
|
14
|
+
line += f"[{segment['start']:.2f} - {segment['end']:.2f}] "
|
|
15
|
+
line += segment['text']
|
|
16
|
+
output.append(line)
|
|
17
|
+
|
|
18
|
+
if format_type == "markdown":
|
|
19
|
+
return "\n\n".join(output)
|
|
20
|
+
elif format_type == "srt":
|
|
21
|
+
srt_output = []
|
|
22
|
+
for i, segment in enumerate(transcript, 1):
|
|
23
|
+
start = f"{int(segment['start']//3600):02}:{int((segment['start']%3600)//60):02}:{int(segment['start']%60):02},{int((segment['start']*1000)%1000):03}"
|
|
24
|
+
end = f"{int(segment['end']//3600):02}:{int((segment['end']%3600)//60):02}:{int(segment['end']%60):02},{int((segment['end']*1000)%1000):03}"
|
|
25
|
+
srt_output.append(f"{i}\n{start} --> {end}\n{segment['text']}\n")
|
|
26
|
+
return "\n".join(srt_output)
|
|
27
|
+
|
|
28
|
+
return "\n".join(output)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from faster_whisper import WhisperModel
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import List, Dict
|
|
4
|
+
|
|
5
|
+
def transcribe_audio(audio_path: Path, model_size: str = "base") -> List[Dict]:
|
|
6
|
+
"""Transcribes audio using faster-whisper on CPU."""
|
|
7
|
+
model = WhisperModel(model_size, device="cpu", compute_type="int8")
|
|
8
|
+
segments, info = model.transcribe(str(audio_path), beam_size=5)
|
|
9
|
+
|
|
10
|
+
transcript = []
|
|
11
|
+
for segment in segments:
|
|
12
|
+
transcript.append({
|
|
13
|
+
"start": segment.start,
|
|
14
|
+
"end": segment.end,
|
|
15
|
+
"text": segment.text.strip()
|
|
16
|
+
})
|
|
17
|
+
return transcript
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ytxt
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Local audio and YouTube transcription CLI
|
|
5
|
+
Author: Rayan Rane
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/rayanrane/ytxt
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/rayanrane/ytxt/issues
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.7
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
Requires-Dist: yt-dlp
|
|
14
|
+
Requires-Dist: faster-whisper
|
|
15
|
+
|
|
16
|
+
# ytxt
|
|
17
|
+
|
|
18
|
+
`ytxt` is a local, private CLI tool for transcribing audio from YouTube, web URLs, or local files using `faster-whisper`. All processing happens securely on your machine—no external APIs or cloud services required.
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
Ensure you have `ffmpeg` installed on your system. Then, install the project:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install .
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Alternatively, if you are running from the source:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install -r requirements.txt
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
Basic usage:
|
|
37
|
+
```bash
|
|
38
|
+
ytxt <url_or_local_file_path>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Options
|
|
42
|
+
|
|
43
|
+
- `--format [text|markdown|srt|json]`: Specify output format.
|
|
44
|
+
- `--model [base|small|medium|large]`: Choose the Whisper model size.
|
|
45
|
+
- `--timestamps`: Include start and end times in text/markdown output.
|
|
46
|
+
- `--output <path>`: Save the transcript to a file.
|
|
47
|
+
- `--no-cache`: Force a re-transcription by ignoring existing cache.
|
|
48
|
+
|
|
49
|
+
### Examples
|
|
50
|
+
|
|
51
|
+
**Transcribe a web URL (YouTube or other):**
|
|
52
|
+
```bash
|
|
53
|
+
ytxt https://www.example.com/audio.mp3 --format markdown --timestamps --output transcript.md
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Transcribe a local audio file:**
|
|
57
|
+
```bash
|
|
58
|
+
ytxt path/to/audio.mp3 --model medium --output transcript.txt
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Features
|
|
62
|
+
|
|
63
|
+
- **Local-Only:** Privacy-first; no data leaves your machine.
|
|
64
|
+
- **Universal Support:** Transcribe YouTube videos, web audio URLs, and local files.
|
|
65
|
+
- **Caching:** Efficiently caches results to avoid redundant work.
|
|
66
|
+
- **Formats:** Supports text, Markdown, SRT, and JSON.
|
|
67
|
+
- **Model Selection:** Flexibility to balance accuracy and performance by choosing your desired Whisper model size.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/ytxt/__init__.py
|
|
4
|
+
src/ytxt/cache.py
|
|
5
|
+
src/ytxt/cli.py
|
|
6
|
+
src/ytxt/downloader.py
|
|
7
|
+
src/ytxt/formatter.py
|
|
8
|
+
src/ytxt/transcriber.py
|
|
9
|
+
src/ytxt.egg-info/PKG-INFO
|
|
10
|
+
src/ytxt.egg-info/SOURCES.txt
|
|
11
|
+
src/ytxt.egg-info/dependency_links.txt
|
|
12
|
+
src/ytxt.egg-info/entry_points.txt
|
|
13
|
+
src/ytxt.egg-info/requires.txt
|
|
14
|
+
src/ytxt.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ytxt
|