yt2term 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.
- yt2term-0.1.0/LICENSE +21 -0
- yt2term-0.1.0/PKG-INFO +173 -0
- yt2term-0.1.0/README.md +160 -0
- yt2term-0.1.0/pyproject.toml +19 -0
- yt2term-0.1.0/setup.cfg +4 -0
- yt2term-0.1.0/src/yt2term/__init__.py +0 -0
- yt2term-0.1.0/src/yt2term/audio.py +67 -0
- yt2term-0.1.0/src/yt2term/cli.py +41 -0
- yt2term-0.1.0/src/yt2term/main.py +47 -0
- yt2term-0.1.0/src/yt2term/render.py +73 -0
- yt2term-0.1.0/src/yt2term/video.py +27 -0
- yt2term-0.1.0/src/yt2term.egg-info/PKG-INFO +173 -0
- yt2term-0.1.0/src/yt2term.egg-info/SOURCES.txt +15 -0
- yt2term-0.1.0/src/yt2term.egg-info/dependency_links.txt +1 -0
- yt2term-0.1.0/src/yt2term.egg-info/entry_points.txt +2 -0
- yt2term-0.1.0/src/yt2term.egg-info/requires.txt +4 -0
- yt2term-0.1.0/src/yt2term.egg-info/top_level.txt +1 -0
yt2term-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Henri Nuortila
|
|
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.
|
yt2term-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: yt2term
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: YouTube video playback in terminal as ASCII
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Dist: opencv-python
|
|
9
|
+
Requires-Dist: asciifyy
|
|
10
|
+
Requires-Dist: pillow
|
|
11
|
+
Requires-Dist: yt-dlp
|
|
12
|
+
Dynamic: license-file
|
|
13
|
+
|
|
14
|
+
# yt2term
|
|
15
|
+
|
|
16
|
+
`yt2term` streams YouTube videos (or direct video links) as real-time ASCII art directly inside your terminal.
|
|
17
|
+
|
|
18
|
+
## Entirely impractical - Weirdly meaningful
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+

|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Features
|
|
27
|
+
|
|
28
|
+
- Real-time video → ASCII conversion
|
|
29
|
+
- ~30 FPS rendering loop
|
|
30
|
+
- Optional color rendering
|
|
31
|
+
- Adjustable output width
|
|
32
|
+
- Optional audio playback via `ffplay`
|
|
33
|
+
- Automatic FFmpeg detection
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
### Development install (recommended)
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
git clone https://github.com/<your-username>/yt2term.git
|
|
43
|
+
cd yt2term
|
|
44
|
+
|
|
45
|
+
pip install -e .
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
This installs the CLI command:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
yt2term
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Standard install
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install .
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Use this if you just want a normal installed package.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Python Version
|
|
65
|
+
|
|
66
|
+
- Requires Python ≥ 3.10
|
|
67
|
+
- Tested on Python 3.10+ / 3.12
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Dependencies
|
|
72
|
+
|
|
73
|
+
Installed automatically via `pyproject.toml`:
|
|
74
|
+
|
|
75
|
+
- `opencv-python`
|
|
76
|
+
- `yt-dlp`
|
|
77
|
+
- `pillow`
|
|
78
|
+
- `asciifyy`
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Audio Support
|
|
83
|
+
|
|
84
|
+
Audio playback uses `ffplay` from FFmpeg.
|
|
85
|
+
|
|
86
|
+
Audio is disabled by default.
|
|
87
|
+
|
|
88
|
+
Enable it with:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
yt2term <link> --audio
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
If FFmpeg is missing, the application will:
|
|
95
|
+
|
|
96
|
+
1. Detect it automatically
|
|
97
|
+
2. Explain what is missing
|
|
98
|
+
3. Prompt installation
|
|
99
|
+
4. Attempt installation using the system package manager
|
|
100
|
+
|
|
101
|
+
No digging through random forum posts from 2014 required.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Usage
|
|
106
|
+
|
|
107
|
+
### Basic playback
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
yt2term <youtube-or-video-url>
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Enable audio
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
yt2term <url> --audio
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Disable color output
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
yt2term <url> --no-color
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Set ASCII width
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
yt2term <url> --width 120
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Combined options
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
yt2term <url> --audio --width 100 --no-color
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Notes
|
|
140
|
+
|
|
141
|
+
- Uses `yt-dlp` to resolve streaming URLs
|
|
142
|
+
- Requires an internet connection during playback
|
|
143
|
+
- Performance depends heavily on terminal rendering speed
|
|
144
|
+
- Audio/video sync is approximate
|
|
145
|
+
- Designed for fun, experimentation, and terminal abuse
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Exit
|
|
150
|
+
|
|
151
|
+
Press `Ctrl+C` to stop playback and return to reality.
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Project Structure
|
|
156
|
+
|
|
157
|
+
```text
|
|
158
|
+
yt2term/
|
|
159
|
+
├── src/
|
|
160
|
+
│ └── yt2term/
|
|
161
|
+
│ ├── main.py
|
|
162
|
+
│ ├── cli.py
|
|
163
|
+
│ ├── video.py
|
|
164
|
+
│ ├── audio.py
|
|
165
|
+
│ ├── render.py
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## License
|
|
171
|
+
|
|
172
|
+
MIT
|
|
173
|
+
|
yt2term-0.1.0/README.md
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# yt2term
|
|
2
|
+
|
|
3
|
+
`yt2term` streams YouTube videos (or direct video links) as real-time ASCII art directly inside your terminal.
|
|
4
|
+
|
|
5
|
+
## Entirely impractical - Weirdly meaningful
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
- Real-time video → ASCII conversion
|
|
16
|
+
- ~30 FPS rendering loop
|
|
17
|
+
- Optional color rendering
|
|
18
|
+
- Adjustable output width
|
|
19
|
+
- Optional audio playback via `ffplay`
|
|
20
|
+
- Automatic FFmpeg detection
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
### Development install (recommended)
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
git clone https://github.com/<your-username>/yt2term.git
|
|
30
|
+
cd yt2term
|
|
31
|
+
|
|
32
|
+
pip install -e .
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
This installs the CLI command:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
yt2term
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Standard install
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install .
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Use this if you just want a normal installed package.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Python Version
|
|
52
|
+
|
|
53
|
+
- Requires Python ≥ 3.10
|
|
54
|
+
- Tested on Python 3.10+ / 3.12
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Dependencies
|
|
59
|
+
|
|
60
|
+
Installed automatically via `pyproject.toml`:
|
|
61
|
+
|
|
62
|
+
- `opencv-python`
|
|
63
|
+
- `yt-dlp`
|
|
64
|
+
- `pillow`
|
|
65
|
+
- `asciifyy`
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Audio Support
|
|
70
|
+
|
|
71
|
+
Audio playback uses `ffplay` from FFmpeg.
|
|
72
|
+
|
|
73
|
+
Audio is disabled by default.
|
|
74
|
+
|
|
75
|
+
Enable it with:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
yt2term <link> --audio
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
If FFmpeg is missing, the application will:
|
|
82
|
+
|
|
83
|
+
1. Detect it automatically
|
|
84
|
+
2. Explain what is missing
|
|
85
|
+
3. Prompt installation
|
|
86
|
+
4. Attempt installation using the system package manager
|
|
87
|
+
|
|
88
|
+
No digging through random forum posts from 2014 required.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Usage
|
|
93
|
+
|
|
94
|
+
### Basic playback
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
yt2term <youtube-or-video-url>
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Enable audio
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
yt2term <url> --audio
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Disable color output
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
yt2term <url> --no-color
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Set ASCII width
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
yt2term <url> --width 120
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Combined options
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
yt2term <url> --audio --width 100 --no-color
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Notes
|
|
127
|
+
|
|
128
|
+
- Uses `yt-dlp` to resolve streaming URLs
|
|
129
|
+
- Requires an internet connection during playback
|
|
130
|
+
- Performance depends heavily on terminal rendering speed
|
|
131
|
+
- Audio/video sync is approximate
|
|
132
|
+
- Designed for fun, experimentation, and terminal abuse
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Exit
|
|
137
|
+
|
|
138
|
+
Press `Ctrl+C` to stop playback and return to reality.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Project Structure
|
|
143
|
+
|
|
144
|
+
```text
|
|
145
|
+
yt2term/
|
|
146
|
+
├── src/
|
|
147
|
+
│ └── yt2term/
|
|
148
|
+
│ ├── main.py
|
|
149
|
+
│ ├── cli.py
|
|
150
|
+
│ ├── video.py
|
|
151
|
+
│ ├── audio.py
|
|
152
|
+
│ ├── render.py
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## License
|
|
158
|
+
|
|
159
|
+
MIT
|
|
160
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "yt2term"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "YouTube video playback in terminal as ASCII"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"opencv-python",
|
|
13
|
+
"asciifyy",
|
|
14
|
+
"pillow",
|
|
15
|
+
"yt-dlp",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[project.scripts]
|
|
19
|
+
yt2term = "yt2term.main:main"
|
yt2term-0.1.0/setup.cfg
ADDED
|
File without changes
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import platform
|
|
2
|
+
import shutil
|
|
3
|
+
import subprocess
|
|
4
|
+
import time
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def ensure_ffplay() -> bool:
|
|
8
|
+
if shutil.which("ffplay") is not None:
|
|
9
|
+
return True
|
|
10
|
+
|
|
11
|
+
print("ffplay not found.")
|
|
12
|
+
|
|
13
|
+
answer: str = input(
|
|
14
|
+
"Would you like to install FFmpeg? (y/n): "
|
|
15
|
+
).strip().lower()
|
|
16
|
+
|
|
17
|
+
if answer != "y":
|
|
18
|
+
return False
|
|
19
|
+
|
|
20
|
+
system: str = platform.system()
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
if system == "Windows":
|
|
24
|
+
subprocess.run(
|
|
25
|
+
["winget", "install", "Gyan.FFmpeg"],
|
|
26
|
+
check=True
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
elif system == "Darwin":
|
|
30
|
+
subprocess.run(
|
|
31
|
+
["brew", "install", "ffmpeg"],
|
|
32
|
+
check=True
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
elif system == "Linux":
|
|
36
|
+
subprocess.run(
|
|
37
|
+
["sudo", "apt", "install", "-y", "ffmpeg"],
|
|
38
|
+
check=True
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
else:
|
|
42
|
+
print("Unsupported operating system.")
|
|
43
|
+
return False
|
|
44
|
+
|
|
45
|
+
except Exception as e:
|
|
46
|
+
print(f"Installation failed: {e}")
|
|
47
|
+
return False
|
|
48
|
+
|
|
49
|
+
return shutil.which("ffplay") is not None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def get_audio_process(stream_url: str) -> subprocess.Popen | None:
|
|
53
|
+
if not ensure_ffplay():
|
|
54
|
+
print("Resuming play without audio.")
|
|
55
|
+
time.sleep(2)
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
return subprocess.Popen(
|
|
59
|
+
[
|
|
60
|
+
"ffplay",
|
|
61
|
+
"-nodisp",
|
|
62
|
+
"-autoexit",
|
|
63
|
+
"-loglevel",
|
|
64
|
+
"quiet",
|
|
65
|
+
stream_url
|
|
66
|
+
]
|
|
67
|
+
)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import shutil
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def get_args() -> argparse.Namespace:
|
|
6
|
+
parser = argparse.ArgumentParser()
|
|
7
|
+
|
|
8
|
+
parser.add_argument(
|
|
9
|
+
"link",
|
|
10
|
+
type=str,
|
|
11
|
+
nargs="?",
|
|
12
|
+
default="https://www.youtube.com/watch?v=IxX_QHay02M",
|
|
13
|
+
help="YouTube/video link"
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
parser.add_argument(
|
|
17
|
+
"--no-color",
|
|
18
|
+
action="store_true",
|
|
19
|
+
help="Disable color output"
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
parser.add_argument(
|
|
23
|
+
"--width",
|
|
24
|
+
type=int,
|
|
25
|
+
help="ASCII width"
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
parser.add_argument(
|
|
29
|
+
"--audio",
|
|
30
|
+
action="store_true",
|
|
31
|
+
help="Enable audio playback (requires ffplay)"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
return parser.parse_args()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def get_width(args: argparse.Namespace) -> int:
|
|
38
|
+
if args.width is not None:
|
|
39
|
+
return args.width
|
|
40
|
+
|
|
41
|
+
return shutil.get_terminal_size().columns
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import subprocess
|
|
3
|
+
|
|
4
|
+
import cv2
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
from yt2term import (
|
|
8
|
+
cli,
|
|
9
|
+
audio,
|
|
10
|
+
video,
|
|
11
|
+
render,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def main() -> None:
|
|
16
|
+
args: argparse.Namespace = cli.get_args()
|
|
17
|
+
color: bool = not args.no_color
|
|
18
|
+
|
|
19
|
+
stream_url: str = video.get_stream_url(link=args.link)
|
|
20
|
+
|
|
21
|
+
cap: cv2.VideoCapture = cv2.VideoCapture(stream_url)
|
|
22
|
+
|
|
23
|
+
audio_process: subprocess.Popen | None = None
|
|
24
|
+
if args.audio:
|
|
25
|
+
audio_process = audio.get_audio_process(stream_url)
|
|
26
|
+
|
|
27
|
+
render.clear_screen()
|
|
28
|
+
try:
|
|
29
|
+
while True:
|
|
30
|
+
width: int = cli.get_width(args)
|
|
31
|
+
|
|
32
|
+
frame: np.ndarray | None = video.read_frame(cap)
|
|
33
|
+
|
|
34
|
+
if not render.process_frame(frame, width, color):
|
|
35
|
+
break
|
|
36
|
+
|
|
37
|
+
except KeyboardInterrupt:
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
finally:
|
|
41
|
+
cap.release()
|
|
42
|
+
if audio_process is not None:
|
|
43
|
+
audio_process.kill()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
if __name__ == "__main__":
|
|
47
|
+
main()
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from typing import Callable
|
|
3
|
+
|
|
4
|
+
import cv2
|
|
5
|
+
import asciify
|
|
6
|
+
import numpy as np
|
|
7
|
+
from PIL import Image
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# CONSTANTS
|
|
11
|
+
TARGET_HZ: int = 30
|
|
12
|
+
FRAME_DELTA_TIME: float = 1.0 / TARGET_HZ
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def clear_screen() -> None:
|
|
16
|
+
print("\033[2J\033[H", end="")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def control_frame_rate(func: Callable) -> Callable:
|
|
20
|
+
def wrapper(*args, **kwargs):
|
|
21
|
+
start: float = time.perf_counter()
|
|
22
|
+
|
|
23
|
+
result = func(*args, **kwargs)
|
|
24
|
+
|
|
25
|
+
elapsed: float = time.perf_counter() - start
|
|
26
|
+
sleep_time: float = FRAME_DELTA_TIME - elapsed
|
|
27
|
+
|
|
28
|
+
if sleep_time > 0:
|
|
29
|
+
time.sleep(sleep_time)
|
|
30
|
+
|
|
31
|
+
return result
|
|
32
|
+
|
|
33
|
+
return wrapper
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def frame_to_ascii(
|
|
37
|
+
frame: np.ndarray,
|
|
38
|
+
width: int,
|
|
39
|
+
color: bool
|
|
40
|
+
) -> str:
|
|
41
|
+
|
|
42
|
+
rgb: np.ndarray = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
|
43
|
+
|
|
44
|
+
pil_image: Image.Image = Image.fromarray(rgb)
|
|
45
|
+
|
|
46
|
+
image = asciify.resize_image(pil_image, width)
|
|
47
|
+
|
|
48
|
+
return asciify.pixels_to_colored_ascii(
|
|
49
|
+
image,
|
|
50
|
+
color
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def render_frame(art: str) -> None:
|
|
55
|
+
print("\033[H", end="") # Clear
|
|
56
|
+
print(art, end="", flush=True)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@control_frame_rate
|
|
60
|
+
def process_frame(
|
|
61
|
+
frame: np.ndarray | None,
|
|
62
|
+
width: int,
|
|
63
|
+
color: bool
|
|
64
|
+
) -> bool:
|
|
65
|
+
|
|
66
|
+
if frame is None:
|
|
67
|
+
return False
|
|
68
|
+
|
|
69
|
+
art: str = frame_to_ascii(frame, width, color)
|
|
70
|
+
|
|
71
|
+
render_frame(art)
|
|
72
|
+
|
|
73
|
+
return True
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import cv2
|
|
2
|
+
import numpy as np
|
|
3
|
+
from yt_dlp import YoutubeDL
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def read_frame(cap: cv2.VideoCapture) -> np.ndarray | None:
|
|
7
|
+
ret, frame = cap.read()
|
|
8
|
+
|
|
9
|
+
if not ret:
|
|
10
|
+
return None
|
|
11
|
+
|
|
12
|
+
return frame
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_stream_url(link: str) -> str:
|
|
16
|
+
ydl_opts = {
|
|
17
|
+
"format": "best[ext=mp4]",
|
|
18
|
+
"quiet": True,
|
|
19
|
+
"no_warnings": True,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
with YoutubeDL(ydl_opts) as ydl:
|
|
23
|
+
info = ydl.extract_info(link, download=False)
|
|
24
|
+
|
|
25
|
+
stream_url: str = info["url"]
|
|
26
|
+
|
|
27
|
+
return stream_url
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: yt2term
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: YouTube video playback in terminal as ASCII
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Dist: opencv-python
|
|
9
|
+
Requires-Dist: asciifyy
|
|
10
|
+
Requires-Dist: pillow
|
|
11
|
+
Requires-Dist: yt-dlp
|
|
12
|
+
Dynamic: license-file
|
|
13
|
+
|
|
14
|
+
# yt2term
|
|
15
|
+
|
|
16
|
+
`yt2term` streams YouTube videos (or direct video links) as real-time ASCII art directly inside your terminal.
|
|
17
|
+
|
|
18
|
+
## Entirely impractical - Weirdly meaningful
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+

|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Features
|
|
27
|
+
|
|
28
|
+
- Real-time video → ASCII conversion
|
|
29
|
+
- ~30 FPS rendering loop
|
|
30
|
+
- Optional color rendering
|
|
31
|
+
- Adjustable output width
|
|
32
|
+
- Optional audio playback via `ffplay`
|
|
33
|
+
- Automatic FFmpeg detection
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
### Development install (recommended)
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
git clone https://github.com/<your-username>/yt2term.git
|
|
43
|
+
cd yt2term
|
|
44
|
+
|
|
45
|
+
pip install -e .
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
This installs the CLI command:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
yt2term
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Standard install
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install .
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Use this if you just want a normal installed package.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Python Version
|
|
65
|
+
|
|
66
|
+
- Requires Python ≥ 3.10
|
|
67
|
+
- Tested on Python 3.10+ / 3.12
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Dependencies
|
|
72
|
+
|
|
73
|
+
Installed automatically via `pyproject.toml`:
|
|
74
|
+
|
|
75
|
+
- `opencv-python`
|
|
76
|
+
- `yt-dlp`
|
|
77
|
+
- `pillow`
|
|
78
|
+
- `asciifyy`
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Audio Support
|
|
83
|
+
|
|
84
|
+
Audio playback uses `ffplay` from FFmpeg.
|
|
85
|
+
|
|
86
|
+
Audio is disabled by default.
|
|
87
|
+
|
|
88
|
+
Enable it with:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
yt2term <link> --audio
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
If FFmpeg is missing, the application will:
|
|
95
|
+
|
|
96
|
+
1. Detect it automatically
|
|
97
|
+
2. Explain what is missing
|
|
98
|
+
3. Prompt installation
|
|
99
|
+
4. Attempt installation using the system package manager
|
|
100
|
+
|
|
101
|
+
No digging through random forum posts from 2014 required.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Usage
|
|
106
|
+
|
|
107
|
+
### Basic playback
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
yt2term <youtube-or-video-url>
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Enable audio
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
yt2term <url> --audio
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Disable color output
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
yt2term <url> --no-color
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Set ASCII width
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
yt2term <url> --width 120
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Combined options
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
yt2term <url> --audio --width 100 --no-color
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Notes
|
|
140
|
+
|
|
141
|
+
- Uses `yt-dlp` to resolve streaming URLs
|
|
142
|
+
- Requires an internet connection during playback
|
|
143
|
+
- Performance depends heavily on terminal rendering speed
|
|
144
|
+
- Audio/video sync is approximate
|
|
145
|
+
- Designed for fun, experimentation, and terminal abuse
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Exit
|
|
150
|
+
|
|
151
|
+
Press `Ctrl+C` to stop playback and return to reality.
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Project Structure
|
|
156
|
+
|
|
157
|
+
```text
|
|
158
|
+
yt2term/
|
|
159
|
+
├── src/
|
|
160
|
+
│ └── yt2term/
|
|
161
|
+
│ ├── main.py
|
|
162
|
+
│ ├── cli.py
|
|
163
|
+
│ ├── video.py
|
|
164
|
+
│ ├── audio.py
|
|
165
|
+
│ ├── render.py
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## License
|
|
171
|
+
|
|
172
|
+
MIT
|
|
173
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/yt2term/__init__.py
|
|
5
|
+
src/yt2term/audio.py
|
|
6
|
+
src/yt2term/cli.py
|
|
7
|
+
src/yt2term/main.py
|
|
8
|
+
src/yt2term/render.py
|
|
9
|
+
src/yt2term/video.py
|
|
10
|
+
src/yt2term.egg-info/PKG-INFO
|
|
11
|
+
src/yt2term.egg-info/SOURCES.txt
|
|
12
|
+
src/yt2term.egg-info/dependency_links.txt
|
|
13
|
+
src/yt2term.egg-info/entry_points.txt
|
|
14
|
+
src/yt2term.egg-info/requires.txt
|
|
15
|
+
src/yt2term.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
yt2term
|