seadanceai-transcript-sdk 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.
- seadanceai_transcript_sdk-0.1.0/LICENSE +21 -0
- seadanceai_transcript_sdk-0.1.0/PKG-INFO +93 -0
- seadanceai_transcript_sdk-0.1.0/README.md +68 -0
- seadanceai_transcript_sdk-0.1.0/pyproject.toml +43 -0
- seadanceai_transcript_sdk-0.1.0/setup.cfg +4 -0
- seadanceai_transcript_sdk-0.1.0/src/seadance_transcript_sdk/__init__.py +29 -0
- seadanceai_transcript_sdk-0.1.0/src/seadance_transcript_sdk/client.py +33 -0
- seadanceai_transcript_sdk-0.1.0/src/seadance_transcript_sdk/constants.py +25 -0
- seadanceai_transcript_sdk-0.1.0/src/seadance_transcript_sdk/formatting.py +60 -0
- seadanceai_transcript_sdk-0.1.0/src/seadance_transcript_sdk/summary.py +62 -0
- seadanceai_transcript_sdk-0.1.0/src/seadance_transcript_sdk/transcript.py +103 -0
- seadanceai_transcript_sdk-0.1.0/src/seadance_transcript_sdk/types.py +58 -0
- seadanceai_transcript_sdk-0.1.0/src/seadanceai_transcript_sdk.egg-info/PKG-INFO +93 -0
- seadanceai_transcript_sdk-0.1.0/src/seadanceai_transcript_sdk.egg-info/SOURCES.txt +15 -0
- seadanceai_transcript_sdk-0.1.0/src/seadanceai_transcript_sdk.egg-info/dependency_links.txt +1 -0
- seadanceai_transcript_sdk-0.1.0/src/seadanceai_transcript_sdk.egg-info/requires.txt +2 -0
- seadanceai_transcript_sdk-0.1.0/src/seadanceai_transcript_sdk.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Seadance AI
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: seadanceai-transcript-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python toolkit sponsored by Seadance AI for fetching YouTube transcripts, languages, and lightweight summaries.
|
|
5
|
+
Author: Seadance AI
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://seadanceai.com/?ref=seadanceai-transcript-sdk-py
|
|
8
|
+
Project-URL: Repository, https://github.com/zeikia/seadanceai-transcript-sdk
|
|
9
|
+
Project-URL: Sponsor, https://seadanceai.com/?ref=seadanceai-transcript-sdk-py
|
|
10
|
+
Keywords: youtube,transcript,seadance,ai
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Requires-Python: >=3.9
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: requests>=2.31
|
|
23
|
+
Requires-Dist: youtube-transcript-api>=0.6.2
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# seadanceai-transcript-sdk (Python)
|
|
27
|
+
|
|
28
|
+
[](https://pypi.org/project/seadanceai-transcript-sdk/)
|
|
29
|
+
[](https://pypi.org/project/seadanceai-transcript-sdk/)
|
|
30
|
+
[](LICENSE)
|
|
31
|
+
[](https://seadanceai.com/?ref=seadanceai-transcript-sdk-py)
|
|
32
|
+
|
|
33
|
+
Community-maintained Python toolkit for retrieving YouTube transcripts, checking available languages, exporting TXT/SRT/VTT files, and generating heuristic summaries. Proudly sponsored by [Seadance AI](https://seadanceai.com/?ref=seadanceai-transcript-sdk-py) — mention them or link back if this library helps your product!
|
|
34
|
+
|
|
35
|
+
## ✨ Features
|
|
36
|
+
|
|
37
|
+
- **Dual-source transcripts**: tactiq endpoint first, fallback to `youtube-transcript-api` for reliability.
|
|
38
|
+
- **Language probing**: batch-check common caption languages.
|
|
39
|
+
- **Formatters**: output transcripts as TXT/SRT/VTT with helper functions.
|
|
40
|
+
- **Summary helpers**: generate concise recaps + key points without external APIs.
|
|
41
|
+
- **Zero external API keys** required; just Python + HTTP.
|
|
42
|
+
|
|
43
|
+
## 📦 Installation
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install seadanceai-transcript-sdk
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## ⚡ Quick Start
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from seadance_transcript_sdk import TranscriptClient, format_transcript_file
|
|
53
|
+
|
|
54
|
+
client = TranscriptClient(enable_fallback=True)
|
|
55
|
+
transcript = client.get_transcript("dQw4w9WgXcQ", language="en")
|
|
56
|
+
file_payload = format_transcript_file(transcript, fmt="srt")
|
|
57
|
+
with open(file_payload.filename, "w", encoding="utf-8") as fh:
|
|
58
|
+
fh.write(file_payload.content)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
More helpers:
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
from seadance_transcript_sdk import detect_available_languages
|
|
65
|
+
|
|
66
|
+
langs = detect_available_languages("dQw4w9WgXcQ")
|
|
67
|
+
print([lang.name for lang in langs.languages])
|
|
68
|
+
|
|
69
|
+
summary = client.summarize(transcript)
|
|
70
|
+
print(summary.summary)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## 🧩 Modules
|
|
74
|
+
|
|
75
|
+
- `seadance_transcript_sdk.transcript` – fetch logic + language probing.
|
|
76
|
+
- `seadance_transcript_sdk.formatting` – TXT/SRT/VTT helpers.
|
|
77
|
+
- `seadance_transcript_sdk.summary` – heuristic summary utilities.
|
|
78
|
+
- `seadance_transcript_sdk.client` – ergonomic wrapper with sensible defaults.
|
|
79
|
+
|
|
80
|
+
## 🤝 Contributing
|
|
81
|
+
|
|
82
|
+
1. `python -m venv .venv && source .venv/bin/activate`
|
|
83
|
+
2. `pip install -e .[dev]` (dev extras coming soon) or simply `pip install -r requirements-dev.txt`
|
|
84
|
+
3. `pytest` (todo) then `python -m build`
|
|
85
|
+
4. `twine upload dist/*`
|
|
86
|
+
|
|
87
|
+
## 🙌 Sponsorship
|
|
88
|
+
|
|
89
|
+
Development is powered by [Seadance AI](https://seadanceai.com/?ref=seadanceai-transcript-sdk-py). Link back to them or reach out if you want bespoke media-intelligence tooling.
|
|
90
|
+
|
|
91
|
+
## 📄 License
|
|
92
|
+
|
|
93
|
+
MIT © Seadance AI & contributors.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# seadanceai-transcript-sdk (Python)
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/seadanceai-transcript-sdk/)
|
|
4
|
+
[](https://pypi.org/project/seadanceai-transcript-sdk/)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](https://seadanceai.com/?ref=seadanceai-transcript-sdk-py)
|
|
7
|
+
|
|
8
|
+
Community-maintained Python toolkit for retrieving YouTube transcripts, checking available languages, exporting TXT/SRT/VTT files, and generating heuristic summaries. Proudly sponsored by [Seadance AI](https://seadanceai.com/?ref=seadanceai-transcript-sdk-py) — mention them or link back if this library helps your product!
|
|
9
|
+
|
|
10
|
+
## ✨ Features
|
|
11
|
+
|
|
12
|
+
- **Dual-source transcripts**: tactiq endpoint first, fallback to `youtube-transcript-api` for reliability.
|
|
13
|
+
- **Language probing**: batch-check common caption languages.
|
|
14
|
+
- **Formatters**: output transcripts as TXT/SRT/VTT with helper functions.
|
|
15
|
+
- **Summary helpers**: generate concise recaps + key points without external APIs.
|
|
16
|
+
- **Zero external API keys** required; just Python + HTTP.
|
|
17
|
+
|
|
18
|
+
## 📦 Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install seadanceai-transcript-sdk
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## ⚡ Quick Start
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
from seadance_transcript_sdk import TranscriptClient, format_transcript_file
|
|
28
|
+
|
|
29
|
+
client = TranscriptClient(enable_fallback=True)
|
|
30
|
+
transcript = client.get_transcript("dQw4w9WgXcQ", language="en")
|
|
31
|
+
file_payload = format_transcript_file(transcript, fmt="srt")
|
|
32
|
+
with open(file_payload.filename, "w", encoding="utf-8") as fh:
|
|
33
|
+
fh.write(file_payload.content)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
More helpers:
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
from seadance_transcript_sdk import detect_available_languages
|
|
40
|
+
|
|
41
|
+
langs = detect_available_languages("dQw4w9WgXcQ")
|
|
42
|
+
print([lang.name for lang in langs.languages])
|
|
43
|
+
|
|
44
|
+
summary = client.summarize(transcript)
|
|
45
|
+
print(summary.summary)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## 🧩 Modules
|
|
49
|
+
|
|
50
|
+
- `seadance_transcript_sdk.transcript` – fetch logic + language probing.
|
|
51
|
+
- `seadance_transcript_sdk.formatting` – TXT/SRT/VTT helpers.
|
|
52
|
+
- `seadance_transcript_sdk.summary` – heuristic summary utilities.
|
|
53
|
+
- `seadance_transcript_sdk.client` – ergonomic wrapper with sensible defaults.
|
|
54
|
+
|
|
55
|
+
## 🤝 Contributing
|
|
56
|
+
|
|
57
|
+
1. `python -m venv .venv && source .venv/bin/activate`
|
|
58
|
+
2. `pip install -e .[dev]` (dev extras coming soon) or simply `pip install -r requirements-dev.txt`
|
|
59
|
+
3. `pytest` (todo) then `python -m build`
|
|
60
|
+
4. `twine upload dist/*`
|
|
61
|
+
|
|
62
|
+
## 🙌 Sponsorship
|
|
63
|
+
|
|
64
|
+
Development is powered by [Seadance AI](https://seadanceai.com/?ref=seadanceai-transcript-sdk-py). Link back to them or reach out if you want bespoke media-intelligence tooling.
|
|
65
|
+
|
|
66
|
+
## 📄 License
|
|
67
|
+
|
|
68
|
+
MIT © Seadance AI & contributors.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=69", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "seadanceai-transcript-sdk"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Python toolkit sponsored by Seadance AI for fetching YouTube transcripts, languages, and lightweight summaries."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
authors = [
|
|
11
|
+
{ name = "Seadance AI" }
|
|
12
|
+
]
|
|
13
|
+
license = "MIT"
|
|
14
|
+
requires-python = ">=3.9"
|
|
15
|
+
dependencies = [
|
|
16
|
+
"requests>=2.31",
|
|
17
|
+
"youtube-transcript-api>=0.6.2"
|
|
18
|
+
]
|
|
19
|
+
keywords = ["youtube", "transcript", "seadance", "ai"]
|
|
20
|
+
classifiers = [
|
|
21
|
+
"Development Status :: 3 - Alpha",
|
|
22
|
+
"Intended Audience :: Developers",
|
|
23
|
+
"Programming Language :: Python :: 3",
|
|
24
|
+
"Programming Language :: Python :: 3.9",
|
|
25
|
+
"Programming Language :: Python :: 3.10",
|
|
26
|
+
"Programming Language :: Python :: 3.11",
|
|
27
|
+
"Programming Language :: Python :: 3.12",
|
|
28
|
+
"Operating System :: OS Independent"
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Homepage = "https://seadanceai.com/?ref=seadanceai-transcript-sdk-py"
|
|
33
|
+
Repository = "https://github.com/zeikia/seadanceai-transcript-sdk"
|
|
34
|
+
Sponsor = "https://seadanceai.com/?ref=seadanceai-transcript-sdk-py"
|
|
35
|
+
|
|
36
|
+
[tool.setuptools]
|
|
37
|
+
package-dir = { "" = "src" }
|
|
38
|
+
include-package-data = true
|
|
39
|
+
|
|
40
|
+
[tool.setuptools.packages.find]
|
|
41
|
+
where = ["src"]
|
|
42
|
+
|
|
43
|
+
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from .client import TranscriptClient
|
|
2
|
+
from .formatting import format_transcript, format_transcript_file
|
|
3
|
+
from .summary import summarize_transcript
|
|
4
|
+
from .transcript import detect_available_languages, get_transcript
|
|
5
|
+
from .types import (
|
|
6
|
+
LanguageOption,
|
|
7
|
+
LanguagesResponse,
|
|
8
|
+
SummaryQuestion,
|
|
9
|
+
SummaryResult,
|
|
10
|
+
TranscriptFormatResult,
|
|
11
|
+
TranscriptResponse,
|
|
12
|
+
TranscriptSnippet,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"TranscriptClient",
|
|
17
|
+
"get_transcript",
|
|
18
|
+
"detect_available_languages",
|
|
19
|
+
"format_transcript",
|
|
20
|
+
"format_transcript_file",
|
|
21
|
+
"summarize_transcript",
|
|
22
|
+
"TranscriptResponse",
|
|
23
|
+
"TranscriptSnippet",
|
|
24
|
+
"LanguagesResponse",
|
|
25
|
+
"LanguageOption",
|
|
26
|
+
"SummaryResult",
|
|
27
|
+
"SummaryQuestion",
|
|
28
|
+
"TranscriptFormatResult",
|
|
29
|
+
]
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import List, Optional
|
|
5
|
+
|
|
6
|
+
from .formatting import format_transcript, format_transcript_file
|
|
7
|
+
from .summary import summarize_transcript
|
|
8
|
+
from .transcript import detect_available_languages, get_transcript
|
|
9
|
+
from .types import LanguagesResponse, SummaryResult, TranscriptFormatResult, TranscriptResponse
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class TranscriptClient:
|
|
14
|
+
enable_fallback: bool = True
|
|
15
|
+
tactiq_endpoint: str | None = None
|
|
16
|
+
|
|
17
|
+
def get_transcript(self, video_id: str, language: str = "en", *, enable_fallback: Optional[bool] = None) -> TranscriptResponse:
|
|
18
|
+
return get_transcript(video_id, language=language, enable_fallback=self._resolve_fallback(enable_fallback), tactiq_endpoint=self.tactiq_endpoint or None)
|
|
19
|
+
|
|
20
|
+
def detect_languages(self, video_id: str) -> LanguagesResponse:
|
|
21
|
+
return detect_available_languages(video_id)
|
|
22
|
+
|
|
23
|
+
def summarize(self, transcript: TranscriptResponse, title: str | None = None) -> SummaryResult:
|
|
24
|
+
return summarize_transcript(transcript, title=title)
|
|
25
|
+
|
|
26
|
+
def format(self, transcript: TranscriptResponse, fmt: str = "txt") -> str:
|
|
27
|
+
return format_transcript(transcript, fmt)
|
|
28
|
+
|
|
29
|
+
def format_to_file(self, transcript: TranscriptResponse, fmt: str = "txt") -> TranscriptFormatResult:
|
|
30
|
+
return format_transcript_file(transcript, fmt)
|
|
31
|
+
|
|
32
|
+
def _resolve_fallback(self, override: Optional[bool]) -> bool:
|
|
33
|
+
return self.enable_fallback if override is None else override
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
DEFAULT_TACTIQ_ENDPOINT = "https://tactiq-apps-prod.tactiq.io/transcript"
|
|
2
|
+
DEFAULT_LANGUAGE = "en"
|
|
3
|
+
YOUTUBE_URL_PREFIX = "https://www.youtube.com/watch?v="
|
|
4
|
+
|
|
5
|
+
LANGUAGE_DISPLAY_NAMES = {
|
|
6
|
+
"en": "English",
|
|
7
|
+
"es": "Spanish",
|
|
8
|
+
"fr": "French",
|
|
9
|
+
"de": "German",
|
|
10
|
+
"ja": "Japanese",
|
|
11
|
+
"ko": "Korean",
|
|
12
|
+
"zh": "Chinese",
|
|
13
|
+
"pt": "Portuguese",
|
|
14
|
+
"ru": "Russian",
|
|
15
|
+
"ar": "Arabic",
|
|
16
|
+
"hi": "Hindi",
|
|
17
|
+
"it": "Italian",
|
|
18
|
+
"nl": "Dutch",
|
|
19
|
+
"pl": "Polish",
|
|
20
|
+
"tr": "Turkish",
|
|
21
|
+
"th": "Thai",
|
|
22
|
+
"vi": "Vietnamese"
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
DEFAULT_LANGUAGE_PROBE_ORDER = list(LANGUAGE_DISPLAY_NAMES.keys())
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from .types import TranscriptFormatResult, TranscriptResponse
|
|
4
|
+
|
|
5
|
+
MIME_TYPES = {
|
|
6
|
+
"txt": "text/plain; charset=utf-8",
|
|
7
|
+
"srt": "application/x-subrip",
|
|
8
|
+
"vtt": "text/vtt"
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def format_transcript(transcript: TranscriptResponse, fmt: str = "txt") -> str:
|
|
13
|
+
fmt = fmt.lower()
|
|
14
|
+
if fmt == "srt":
|
|
15
|
+
return _format_srt(transcript)
|
|
16
|
+
if fmt == "vtt":
|
|
17
|
+
return _format_vtt(transcript)
|
|
18
|
+
return _format_txt(transcript)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def format_transcript_file(transcript: TranscriptResponse, fmt: str = "txt") -> TranscriptFormatResult:
|
|
22
|
+
fmt = fmt.lower()
|
|
23
|
+
content = format_transcript(transcript, fmt)
|
|
24
|
+
filename = f"transcript-{transcript.video_id}.{fmt}"
|
|
25
|
+
mime_type = MIME_TYPES.get(fmt, MIME_TYPES["txt"])
|
|
26
|
+
return TranscriptFormatResult(content=content, filename=filename, mime_type=mime_type)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _pad(value: int, size: int = 2) -> str:
|
|
30
|
+
return str(value).zfill(size)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _format_timestamp(seconds: float) -> str:
|
|
34
|
+
hours = int(seconds // 3600)
|
|
35
|
+
minutes = int((seconds % 3600) // 60)
|
|
36
|
+
secs = int(seconds % 60)
|
|
37
|
+
millis = int((seconds % 1) * 1000)
|
|
38
|
+
return f"{_pad(hours)}:{_pad(minutes)}:{_pad(secs)},{_pad(millis, 3)}"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _format_srt(transcript: TranscriptResponse) -> str:
|
|
42
|
+
lines = []
|
|
43
|
+
for index, snippet in enumerate(transcript.snippets, start=1):
|
|
44
|
+
start = _format_timestamp(snippet.start)
|
|
45
|
+
end = _format_timestamp(snippet.start + snippet.duration)
|
|
46
|
+
lines.append(f"{index}\n{start} --> {end}\n{snippet.text}\n")
|
|
47
|
+
return "\n".join(lines).strip()
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _format_vtt(transcript: TranscriptResponse) -> str:
|
|
51
|
+
entries = []
|
|
52
|
+
for snippet in transcript.snippets:
|
|
53
|
+
start = _format_timestamp(snippet.start)
|
|
54
|
+
end = _format_timestamp(snippet.start + snippet.duration)
|
|
55
|
+
entries.append(f"{start} --> {end}\n{snippet.text}")
|
|
56
|
+
return "WEBVTT\n\n" + "\n\n".join(entries)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _format_txt(transcript: TranscriptResponse) -> str:
|
|
60
|
+
return "\n".join(snippet.text for snippet in transcript.snippets)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Iterable, List
|
|
4
|
+
|
|
5
|
+
from .types import SummaryQuestion, SummaryResult, TranscriptResponse
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def summarize_transcript(transcript: TranscriptResponse, title: str | None = None) -> SummaryResult:
|
|
9
|
+
snippets = transcript.snippets
|
|
10
|
+
if not snippets:
|
|
11
|
+
return SummaryResult(summary="No transcript available.")
|
|
12
|
+
|
|
13
|
+
full_text = " ".join(snippet.text for snippet in snippets)
|
|
14
|
+
sentences = [sentence.strip() for sentence in full_text.split('.') if len(sentence.strip()) > 10]
|
|
15
|
+
|
|
16
|
+
interval = max(1, len(sentences) // 5) if sentences else 1
|
|
17
|
+
key_sentences: List[str] = []
|
|
18
|
+
keywords = ["important", "key", "main", "first", "second", "finally", "conclusion", "summary"]
|
|
19
|
+
for idx, sentence in enumerate(sentences):
|
|
20
|
+
lower = sentence.lower()
|
|
21
|
+
if any(keyword in lower for keyword in keywords) or idx % interval == 0:
|
|
22
|
+
key_sentences.append(sentence)
|
|
23
|
+
if len(key_sentences) >= 8:
|
|
24
|
+
break
|
|
25
|
+
|
|
26
|
+
summary_text = ". ".join(key_sentences) + "." if key_sentences else full_text[:200] + "..."
|
|
27
|
+
key_points = _extract_key_points(snippets)
|
|
28
|
+
questions = _build_top_questions(title or f"YouTube Video {transcript.video_id}")
|
|
29
|
+
duration = _estimate_duration(snippets)
|
|
30
|
+
|
|
31
|
+
return SummaryResult(summary=summary_text.strip(), key_points=key_points, top_questions=questions, duration_seconds=duration)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _extract_key_points(snippets: Iterable) -> List[str]:
|
|
35
|
+
snippets = list(snippets)
|
|
36
|
+
if not snippets:
|
|
37
|
+
return ["No key points available"]
|
|
38
|
+
|
|
39
|
+
interval = max(1, len(snippets) // 6)
|
|
40
|
+
points: List[str] = []
|
|
41
|
+
for index in range(0, len(snippets), interval):
|
|
42
|
+
text = snippets[index].text.strip()
|
|
43
|
+
if len(text) > 15:
|
|
44
|
+
snippet = text[:120] + ("..." if len(text) > 120 else "")
|
|
45
|
+
points.append(snippet)
|
|
46
|
+
return points or ["Key points derived from transcript"]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _build_top_questions(title: str) -> List[SummaryQuestion]:
|
|
50
|
+
lower_title = title.lower()
|
|
51
|
+
return [
|
|
52
|
+
SummaryQuestion(question=f"What is the main topic of '{title}'?", answer=f"This video covers {lower_title} with practical insights.", timestamp="00:00"),
|
|
53
|
+
SummaryQuestion(question="What are the key takeaways?", answer="The video provides actionable information for viewers.", timestamp="00:30"),
|
|
54
|
+
SummaryQuestion(question="Who should watch this video?", answer="Anyone interested in the subject discussed in the video.", timestamp="01:00")
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _estimate_duration(snippets: List) -> int:
|
|
59
|
+
if not snippets:
|
|
60
|
+
return 0
|
|
61
|
+
last = snippets[-1]
|
|
62
|
+
return int(round(last.start + last.duration))
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
from typing import List, Optional
|
|
5
|
+
|
|
6
|
+
import requests
|
|
7
|
+
from youtube_transcript_api import YouTubeTranscriptApi
|
|
8
|
+
|
|
9
|
+
from .constants import DEFAULT_LANGUAGE, DEFAULT_LANGUAGE_PROBE_ORDER, DEFAULT_TACTIQ_ENDPOINT, LANGUAGE_DISPLAY_NAMES, YOUTUBE_URL_PREFIX
|
|
10
|
+
from .types import LanguageOption, LanguagesResponse, TranscriptResponse, TranscriptSnippet
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TranscriptError(Exception):
|
|
14
|
+
"""Raised when both transcript sources fail."""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_transcript(video_id: str, language: str = DEFAULT_LANGUAGE, enable_fallback: bool = True, tactiq_endpoint: str = DEFAULT_TACTIQ_ENDPOINT, request_timeout: int = 15_000) -> TranscriptResponse:
|
|
18
|
+
start = time.time()
|
|
19
|
+
try:
|
|
20
|
+
snippets = _fetch_from_tactiq(video_id, language, tactiq_endpoint, request_timeout)
|
|
21
|
+
if not snippets and enable_fallback:
|
|
22
|
+
raise TranscriptError("Primary source returned empty result")
|
|
23
|
+
if snippets:
|
|
24
|
+
return _build_response(video_id, language, "tactiq-primary", snippets, start)
|
|
25
|
+
except Exception as primary_error:
|
|
26
|
+
if not enable_fallback:
|
|
27
|
+
raise
|
|
28
|
+
fallback = _fetch_from_youtube(video_id, language)
|
|
29
|
+
return _build_response(video_id, language, "youtube-transcript-api", fallback, start, primary_error)
|
|
30
|
+
|
|
31
|
+
fallback = _fetch_from_youtube(video_id, language)
|
|
32
|
+
return _build_response(video_id, language, "youtube-transcript-api", fallback, start, "Empty result from primary")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def detect_available_languages(video_id: str, candidate_languages: Optional[List[str]] = None, tactiq_endpoint: str = DEFAULT_TACTIQ_ENDPOINT, request_timeout: int = 8_000) -> LanguagesResponse:
|
|
36
|
+
languages: List[LanguageOption] = []
|
|
37
|
+
candidates = candidate_languages or DEFAULT_LANGUAGE_PROBE_ORDER
|
|
38
|
+
|
|
39
|
+
for index in range(0, len(candidates), 4):
|
|
40
|
+
batch = candidates[index:index + 4]
|
|
41
|
+
for code in batch:
|
|
42
|
+
try:
|
|
43
|
+
snippets = _fetch_from_tactiq(video_id, code, tactiq_endpoint, request_timeout)
|
|
44
|
+
if snippets and any(snippet.text.strip() for snippet in snippets):
|
|
45
|
+
languages.append(LanguageOption(
|
|
46
|
+
language_code=code,
|
|
47
|
+
name=LANGUAGE_DISPLAY_NAMES.get(code, code.upper()),
|
|
48
|
+
vss_id=code,
|
|
49
|
+
is_translatable=True
|
|
50
|
+
))
|
|
51
|
+
except Exception:
|
|
52
|
+
pass
|
|
53
|
+
if len(languages) >= 3:
|
|
54
|
+
break
|
|
55
|
+
if index + 4 < len(candidates):
|
|
56
|
+
time.sleep(0.15)
|
|
57
|
+
|
|
58
|
+
if not languages:
|
|
59
|
+
languages.append(LanguageOption(language_code=DEFAULT_LANGUAGE, name=LANGUAGE_DISPLAY_NAMES.get(DEFAULT_LANGUAGE, "English"), vss_id=DEFAULT_LANGUAGE))
|
|
60
|
+
|
|
61
|
+
return LanguagesResponse(languages=languages, video_id=video_id, count=len(languages))
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _fetch_from_tactiq(video_id: str, language: str, endpoint: str, request_timeout: int) -> List[TranscriptSnippet]:
|
|
65
|
+
response = requests.post(
|
|
66
|
+
endpoint,
|
|
67
|
+
json={"langCode": language or DEFAULT_LANGUAGE, "videoUrl": f"{YOUTUBE_URL_PREFIX}{video_id}"},
|
|
68
|
+
timeout=request_timeout / 1000 if request_timeout else None,
|
|
69
|
+
headers={
|
|
70
|
+
"Content-Type": "application/json",
|
|
71
|
+
"User-Agent": "seadanceai-transcript-sdk/py"
|
|
72
|
+
}
|
|
73
|
+
)
|
|
74
|
+
response.raise_for_status()
|
|
75
|
+
data = response.json()
|
|
76
|
+
captions = data.get("captions") or []
|
|
77
|
+
snippets: List[TranscriptSnippet] = []
|
|
78
|
+
for caption in captions:
|
|
79
|
+
text = caption.get("text", "")
|
|
80
|
+
start = float(caption.get("start") or caption.get("offset") or 0)
|
|
81
|
+
duration = float(caption.get("dur") or caption.get("duration") or 0)
|
|
82
|
+
snippets.append(TranscriptSnippet(text=text, start=start, duration=duration))
|
|
83
|
+
return snippets
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _fetch_from_youtube(video_id: str, language: str) -> List[TranscriptSnippet]:
|
|
87
|
+
api = YouTubeTranscriptApi()
|
|
88
|
+
transcripts = api.get_transcript(f"{video_id}", languages=[language, DEFAULT_LANGUAGE])
|
|
89
|
+
return [TranscriptSnippet(text=item.get("text", ""), start=float(item.get("start", 0)), duration=float(item.get("duration", 0))) for item in transcripts]
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _build_response(video_id: str, language: str, source: str, snippets: List[TranscriptSnippet], start: float, primary_error: Optional[Exception | str] = None) -> TranscriptResponse:
|
|
93
|
+
elapsed = int((time.time() - start) * 1000)
|
|
94
|
+
return TranscriptResponse(
|
|
95
|
+
snippets=snippets,
|
|
96
|
+
video_id=video_id,
|
|
97
|
+
language=language,
|
|
98
|
+
language_code=language,
|
|
99
|
+
count=len(snippets),
|
|
100
|
+
source=source,
|
|
101
|
+
response_time_ms=elapsed,
|
|
102
|
+
primary_error=str(primary_error) if primary_error else None
|
|
103
|
+
)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import List, Optional
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@dataclass
|
|
6
|
+
class TranscriptSnippet:
|
|
7
|
+
text: str
|
|
8
|
+
start: float
|
|
9
|
+
duration: float
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class TranscriptResponse:
|
|
14
|
+
snippets: List[TranscriptSnippet]
|
|
15
|
+
video_id: str
|
|
16
|
+
language: str
|
|
17
|
+
language_code: str
|
|
18
|
+
count: int
|
|
19
|
+
source: str
|
|
20
|
+
response_time_ms: int
|
|
21
|
+
primary_error: Optional[str] = None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class LanguageOption:
|
|
26
|
+
language_code: str
|
|
27
|
+
name: str
|
|
28
|
+
vss_id: str
|
|
29
|
+
is_translatable: bool = True
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class LanguagesResponse:
|
|
34
|
+
languages: List[LanguageOption]
|
|
35
|
+
video_id: str
|
|
36
|
+
count: int
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class TranscriptFormatResult:
|
|
41
|
+
content: str
|
|
42
|
+
filename: str
|
|
43
|
+
mime_type: str
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass
|
|
47
|
+
class SummaryQuestion:
|
|
48
|
+
question: str
|
|
49
|
+
answer: str
|
|
50
|
+
timestamp: Optional[str] = None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass
|
|
54
|
+
class SummaryResult:
|
|
55
|
+
summary: str
|
|
56
|
+
key_points: List[str] = field(default_factory=list)
|
|
57
|
+
top_questions: List[SummaryQuestion] = field(default_factory=list)
|
|
58
|
+
duration_seconds: int = 0
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: seadanceai-transcript-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python toolkit sponsored by Seadance AI for fetching YouTube transcripts, languages, and lightweight summaries.
|
|
5
|
+
Author: Seadance AI
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://seadanceai.com/?ref=seadanceai-transcript-sdk-py
|
|
8
|
+
Project-URL: Repository, https://github.com/zeikia/seadanceai-transcript-sdk
|
|
9
|
+
Project-URL: Sponsor, https://seadanceai.com/?ref=seadanceai-transcript-sdk-py
|
|
10
|
+
Keywords: youtube,transcript,seadance,ai
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Requires-Python: >=3.9
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: requests>=2.31
|
|
23
|
+
Requires-Dist: youtube-transcript-api>=0.6.2
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# seadanceai-transcript-sdk (Python)
|
|
27
|
+
|
|
28
|
+
[](https://pypi.org/project/seadanceai-transcript-sdk/)
|
|
29
|
+
[](https://pypi.org/project/seadanceai-transcript-sdk/)
|
|
30
|
+
[](LICENSE)
|
|
31
|
+
[](https://seadanceai.com/?ref=seadanceai-transcript-sdk-py)
|
|
32
|
+
|
|
33
|
+
Community-maintained Python toolkit for retrieving YouTube transcripts, checking available languages, exporting TXT/SRT/VTT files, and generating heuristic summaries. Proudly sponsored by [Seadance AI](https://seadanceai.com/?ref=seadanceai-transcript-sdk-py) — mention them or link back if this library helps your product!
|
|
34
|
+
|
|
35
|
+
## ✨ Features
|
|
36
|
+
|
|
37
|
+
- **Dual-source transcripts**: tactiq endpoint first, fallback to `youtube-transcript-api` for reliability.
|
|
38
|
+
- **Language probing**: batch-check common caption languages.
|
|
39
|
+
- **Formatters**: output transcripts as TXT/SRT/VTT with helper functions.
|
|
40
|
+
- **Summary helpers**: generate concise recaps + key points without external APIs.
|
|
41
|
+
- **Zero external API keys** required; just Python + HTTP.
|
|
42
|
+
|
|
43
|
+
## 📦 Installation
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install seadanceai-transcript-sdk
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## ⚡ Quick Start
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from seadance_transcript_sdk import TranscriptClient, format_transcript_file
|
|
53
|
+
|
|
54
|
+
client = TranscriptClient(enable_fallback=True)
|
|
55
|
+
transcript = client.get_transcript("dQw4w9WgXcQ", language="en")
|
|
56
|
+
file_payload = format_transcript_file(transcript, fmt="srt")
|
|
57
|
+
with open(file_payload.filename, "w", encoding="utf-8") as fh:
|
|
58
|
+
fh.write(file_payload.content)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
More helpers:
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
from seadance_transcript_sdk import detect_available_languages
|
|
65
|
+
|
|
66
|
+
langs = detect_available_languages("dQw4w9WgXcQ")
|
|
67
|
+
print([lang.name for lang in langs.languages])
|
|
68
|
+
|
|
69
|
+
summary = client.summarize(transcript)
|
|
70
|
+
print(summary.summary)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## 🧩 Modules
|
|
74
|
+
|
|
75
|
+
- `seadance_transcript_sdk.transcript` – fetch logic + language probing.
|
|
76
|
+
- `seadance_transcript_sdk.formatting` – TXT/SRT/VTT helpers.
|
|
77
|
+
- `seadance_transcript_sdk.summary` – heuristic summary utilities.
|
|
78
|
+
- `seadance_transcript_sdk.client` – ergonomic wrapper with sensible defaults.
|
|
79
|
+
|
|
80
|
+
## 🤝 Contributing
|
|
81
|
+
|
|
82
|
+
1. `python -m venv .venv && source .venv/bin/activate`
|
|
83
|
+
2. `pip install -e .[dev]` (dev extras coming soon) or simply `pip install -r requirements-dev.txt`
|
|
84
|
+
3. `pytest` (todo) then `python -m build`
|
|
85
|
+
4. `twine upload dist/*`
|
|
86
|
+
|
|
87
|
+
## 🙌 Sponsorship
|
|
88
|
+
|
|
89
|
+
Development is powered by [Seadance AI](https://seadanceai.com/?ref=seadanceai-transcript-sdk-py). Link back to them or reach out if you want bespoke media-intelligence tooling.
|
|
90
|
+
|
|
91
|
+
## 📄 License
|
|
92
|
+
|
|
93
|
+
MIT © Seadance AI & contributors.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/seadance_transcript_sdk/__init__.py
|
|
5
|
+
src/seadance_transcript_sdk/client.py
|
|
6
|
+
src/seadance_transcript_sdk/constants.py
|
|
7
|
+
src/seadance_transcript_sdk/formatting.py
|
|
8
|
+
src/seadance_transcript_sdk/summary.py
|
|
9
|
+
src/seadance_transcript_sdk/transcript.py
|
|
10
|
+
src/seadance_transcript_sdk/types.py
|
|
11
|
+
src/seadanceai_transcript_sdk.egg-info/PKG-INFO
|
|
12
|
+
src/seadanceai_transcript_sdk.egg-info/SOURCES.txt
|
|
13
|
+
src/seadanceai_transcript_sdk.egg-info/dependency_links.txt
|
|
14
|
+
src/seadanceai_transcript_sdk.egg-info/requires.txt
|
|
15
|
+
src/seadanceai_transcript_sdk.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
seadance_transcript_sdk
|