yt-cli-terminal 1.4.0
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.
- package/LICENSE +21 -0
- package/README.md +442 -0
- package/npm/bin/_run.js +31 -0
- package/npm/bin/ytcli.js +3 -0
- package/npm/bin/ytmusic.js +3 -0
- package/npm/bin/ytplayer.js +3 -0
- package/npm/bin/ytsong.js +3 -0
- package/npm/install.js +209 -0
- package/package.json +43 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ping-Phantom39
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
# β‘ YouTube Terminal Suite (yt-song-cli)
|
|
2
|
+
[](https://youtu.be/aq0VeB1LSt8?si=epWLrQFFAEb2CtD4)
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
[](https://golang.org/)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
[](https://github.com/Ping-Phantom39/yt-cli)
|
|
8
|
+
[](https://github.com/charmbracelet/bubbletea)
|
|
9
|
+
|
|
10
|
+
## π₯οΈ Supported Operating Systems
|
|
11
|
+
|
|
12
|
+
The tools are written in Go and rely on native audio/video backβends. They have been tested and are officially supported on:
|
|
13
|
+
|
|
14
|
+
- **Linux** (Ubuntu, Debian, Fedora, etc.) β requires ALSA/PulseAudio/pipewire libraries.
|
|
15
|
+
- **macOS** β uses CoreAudio via the `beep` library.
|
|
16
|
+
- **Windows** β builds with CGO and uses WASAPI; however the current CI and packaging focus on Linux/macOS.
|
|
17
|
+
|
|
18
|
+
For other platforms you may need to install the required audio/video dependencies manually.
|
|
19
|
+
|
|
20
|
+
Welcome to the **YouTube Terminal Suite** (`yt-song-cli`), a professional monorepo housing two high-performance, keyboard-driven Go CLI applications styled with a sleek cyberpunk aesthetic. The suite features a video streaming player (`ytplayer`) and an offline audio player/downloader (`ytmusic`), providing the ultimate terminal-native YouTube media experience.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## π§ Monorepo Overview
|
|
25
|
+
|
|
26
|
+
This repository contains two decoupled, highly modular Go utilities:
|
|
27
|
+
|
|
28
|
+
| Component | Target Directory | Binary Name | Description | Key Technologies |
|
|
29
|
+
|---|---|---|---|---|
|
|
30
|
+
| **Video Client** | [`/yt-player`](yt-player) | `ytplayer` | Cyberpunk terminal video searcher, downloader, and mpv-backed streaming player. | `yt-dlp`, Bubble Tea, `mpv` |
|
|
31
|
+
| **Audio Client** | [`/yt-song`](yt-song) | `ytmusic` | Highly optimized music player with low-level audio streaming and local MP3 caching. | `yt-dlp`, Bubble Tea, `gopxl/beep` |
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## π¨ System Architecture
|
|
36
|
+
|
|
37
|
+
The following diagram illustrates how the CLI tools leverage standard Go layouts and external binaries for media scraping, multiplexing, transcoding, and low-level playback:
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
```mermaid
|
|
41
|
+
graph TD
|
|
42
|
+
subgraph Monorepo["yt-song-cli Workspace"]
|
|
43
|
+
direction TB
|
|
44
|
+
subgraph YTPlayer["ytplayer (Video Client)"]
|
|
45
|
+
VP_Cmd["Cobra CLI Bootstrapper"] --> VP_UI["Bubble Tea UI"]
|
|
46
|
+
VP_UI --> VP_DL["Downloader Engine"]
|
|
47
|
+
VP_UI --> VP_Exec["mpv Exec Process"]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
subgraph YTMusic["ytmusic (Audio Client)"]
|
|
51
|
+
MU_Cmd["Cobra CLI Bootstrapper"] --> MU_UI["Bubble Tea UI"]
|
|
52
|
+
MU_UI --> MU_DL["Downloader Engine"]
|
|
53
|
+
MU_UI --> MU_Play["Beep Audio Engine"]
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
%% External Processes
|
|
58
|
+
VP_DL -->|exec.Command| YTDLP["yt-dlp Binary"]
|
|
59
|
+
MU_DL -->|exec.Command| YTDLP
|
|
60
|
+
YTDLP -->|Raw Streams| FFMPEG["ffmpeg Transcoder"]
|
|
61
|
+
FFMPEG -->|Merge MP4| VP_Local["Local downloads / Cache"]
|
|
62
|
+
FFMPEG -->|Extract MP3| MU_Local["Local downloads / Cache"]
|
|
63
|
+
|
|
64
|
+
VP_Exec -->|Stream Video/Audio| MPV["mpv Engine"]
|
|
65
|
+
MU_Play -->|Volume & Resampling| Speaker["gopxl/beep Speaker"]
|
|
66
|
+
Speaker -->|Audio Output| Output["OS Sound Output"]
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### βοΈ Deep-Dive Engine Architecture (Under the Hood)
|
|
70
|
+
|
|
71
|
+
#### 1. π Cobra CLI Bootstrapper (`cmd/`)
|
|
72
|
+
* **Flag & Argument Parsing**: Built on top of `spf13/cobra`, the bootstrapper parses runtime configuration parameters (`--limit`, `--cookies`, `--cookies-from-browser`, `--vo`, `--check`) and environment overrides.
|
|
73
|
+
* **Environment & Dependency Check**: Executes pre-flight scans verifying that external binaries (`yt-dlp`, `ffmpeg`, `mpv`) and CGO audio headers exist on the host system before spawning the interface.
|
|
74
|
+
* **TUI Lifecyle Management**: Initializes application state structs and bootstraps the main event loop.
|
|
75
|
+
|
|
76
|
+
#### 2. π₯οΈ Bubble Tea Reactive TUI Engine (`internal/ui/`)
|
|
77
|
+
* **Model-View-Update (MVU) Loop**: Powered by `charmbracelet/bubbletea` (Elm architecture pattern). User keystrokes are received as `tea.Msg` events, processed in pure `Update()` functions, and rendered deterministically in `View()`.
|
|
78
|
+
* **Asynchronous Command Orchestration**: Non-blocking network queries and media downloads run in isolated goroutines, dispatching `tea.Cmd` response messages back into the main event loop without freezing user interface animations.
|
|
79
|
+
* **Styling & Layout Rendering**: Uses `charmbracelet/lipgloss` for cyberpunk neon borders, HSL gradient text, and dynamic viewport calculation, alongside `charmbracelet/bubbles` for text input inputs and progress bar components.
|
|
80
|
+
|
|
81
|
+
#### 3. π₯ Asynchronous Downloader Engine (`internal/downloader/`)
|
|
82
|
+
* **Metadata Extraction**: Spawns sub-processes of `yt-dlp` using Go's `os/exec.Command` with flags (`--dump-json`, `--flat-playlist`). Reads standard output streams in real-time to parse JSON video metadata (ID, title, channel, duration, view count) into strongly-typed Go structs.
|
|
83
|
+
* **Transcoding & Multiplexing Pipe**:
|
|
84
|
+
* **Video (`ytplayer`)**: Invokes `yt-dlp` to download best-quality separate video and audio streams, piping them to `ffmpeg` for container multiplexing into high-definition `.mp4` files inside `downloads/`.
|
|
85
|
+
* **Audio (`ytmusic`)**: Triggers `yt-dlp` audio extraction and uses `ffmpeg` to transcode streams into `.mp3` files formatted specifically for low-latency PCM decoding.
|
|
86
|
+
* **Real-time Progress Parsing**: Intercepts `stdout` lines matching percentage regex patterns (`[download] XX.X%`), emitting real-time progress update messages to the TUI progress bar component.
|
|
87
|
+
|
|
88
|
+
#### 4. π¬ `mpv` Video Process Controller (`yt-player/internal/player/`)
|
|
89
|
+
* **Terminal Handover Protocol**: Uses Bubble Tea's `tea.ExecProcess` wrapper. When a user streams a video, the TUI loop is cleanly suspended, handing standard input/output over to the native `mpv` binary.
|
|
90
|
+
* **Hardware & Terminal Video Drivers**: Leverages `mpv`'s video output (`--vo`) capabilities, allowing seamless switching between external GUI windows (`gpu`, `x11`) and terminal graphics rendering (`tct`, `sixel`, `kitty`).
|
|
91
|
+
* **Auto-Resume**: Upon `mpv` exit or termination signal, the terminal buffer is restored, and the Bubble Tea TUI seamlessly resumes playback state.
|
|
92
|
+
|
|
93
|
+
#### 5. π Beep Audio Engine & CGO Speaker (`yt-song/internal/player/`)
|
|
94
|
+
* **PCM Buffer Decoding**: Built using `gopxl/beep`. Reads local `.mp3` audio files and streams them into floating-point PCM audio buffers.
|
|
95
|
+
* **Logarithmic Volume Control**: Rather than simple linear volume scaling, volume adjustments use a logarithmic scale (`math.Pow`) to match natural human auditory perception curves.
|
|
96
|
+
* **Low-Level OS Sound Integration**: Uses CGO bindings to interface directly with OS native sound system APIs:
|
|
97
|
+
* **Linux**: ALSA (`libasound2`) / PulseAudio / PipeWire.
|
|
98
|
+
* **macOS**: CoreAudio.
|
|
99
|
+
* **Windows**: WASAPI.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## π System Requirements & Setup
|
|
104
|
+
|
|
105
|
+
Before compiling, make sure your operating system has the necessary external libraries and executable tools installed.
|
|
106
|
+
|
|
107
|
+
### 1. External Media CLI Utilities
|
|
108
|
+
Both applications require **`yt-dlp`** and **`ffmpeg`**. In addition, `ytplayer` requires **`mpv`**.
|
|
109
|
+
|
|
110
|
+
#### **Debian/Ubuntu**
|
|
111
|
+
```bash
|
|
112
|
+
sudo apt-get update
|
|
113
|
+
sudo apt-get install -y mpv ffmpeg nodejs
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
#### **macOS (via Homebrew)**
|
|
117
|
+
```bash
|
|
118
|
+
brew install mpv ffmpeg nodejs
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
> [!TIP]
|
|
122
|
+
> Node.js or Deno are optional but highly recommended to help `yt-dlp` bypass YouTube's signature bot blocks.
|
|
123
|
+
|
|
124
|
+
#### **Installing/Updating `yt-dlp` (Recommended)**
|
|
125
|
+
Ensure you have the latest `yt-dlp` build to handle changing YouTube API formats. The tools will check `./bin/yt-dlp` first, falling back to your system `$PATH`.
|
|
126
|
+
```bash
|
|
127
|
+
sudo curl -L https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp -o /usr/local/bin/yt-dlp
|
|
128
|
+
sudo chmod a+rx /usr/local/bin/yt-dlp
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### 2. Audio Library Headers (Linux Only - Required for compilation)
|
|
132
|
+
`ytmusic` compiles low-level Go bindings to talk directly to your system's ALSA speakers:
|
|
133
|
+
|
|
134
|
+
* **Debian/Ubuntu**:
|
|
135
|
+
```bash
|
|
136
|
+
sudo apt-get install -y libasound2-dev
|
|
137
|
+
```
|
|
138
|
+
* **Fedora/CentOS/RHEL**:
|
|
139
|
+
```bash
|
|
140
|
+
sudo dnf install alsa-lib-devel
|
|
141
|
+
```
|
|
142
|
+
* **macOS**: No additional headers needed (native CoreAudio support).
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## π οΈ Compilation & Installation
|
|
147
|
+
|
|
148
|
+
### π¦ Download via Ubuntu PPA
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
sudo add-apt-repository ppa:kamalchad/ytmusic
|
|
152
|
+
sudo apt update
|
|
153
|
+
sudo apt install ytmusic
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
> [!WARNING]
|
|
157
|
+
> **Disclaimer**: Installing via PPA automatically includes all necessary packages (`yt-dlp`, `mpv`, `ffmpeg`, `libasound2-dev`) along with the binary, which may be **600 β 700 MB** in total download size.
|
|
158
|
+
|
|
159
|
+
#### β οΈ Possible Errors & Troubleshooting
|
|
160
|
+
|
|
161
|
+
* **Outdated `yt-dlp` Version (`403 Forbidden` / Bot Block Errors)**:
|
|
162
|
+
System or PPA repositories may package an older version of `yt-dlp` that causes playback/search failures with YouTube API changes. Upgrade `yt-dlp` to the latest release using pip:
|
|
163
|
+
```bash
|
|
164
|
+
python3 -m pip install -U yt-dlp
|
|
165
|
+
```
|
|
166
|
+
or update directly via official GitHub binary:
|
|
167
|
+
```bash
|
|
168
|
+
sudo curl -L https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp -o /usr/local/bin/yt-dlp
|
|
169
|
+
sudo chmod a+rx /usr/local/bin/yt-dlp
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
* **Missing Audio Backend Libraries**:
|
|
173
|
+
If audio output or CGO bindings fail to launch, ensure sound packages are installed:
|
|
174
|
+
```bash
|
|
175
|
+
sudo apt-get install -y libasound2-dev alsa-utils
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
### π¦ Download via npm
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
npm install -g yt-cli-terminal
|
|
184
|
+
ytplayer --check
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
This installs the `ytplayer`/`ytcli` (video) and `ytmusic`/`ytsong` (audio) commands. The postinstall step downloads the prebuilt binaries for your platform from the matching GitHub release and verifies their SHA-256 checksums. Supported platforms follow the release matrix (Linux amd64, macOS amd64/arm64, Windows amd64); on Debian/Ubuntu arm64 use the `.deb` asset instead.
|
|
188
|
+
|
|
189
|
+
> [!NOTE]
|
|
190
|
+
> npm only delivers the two Go binaries. The runtime dependencies must still be on your `PATH`: **`mpv`** (video playback), **`ffmpeg`** and **`yt-dlp`** (both tools) β see [System Requirements](#-system-requirements--setup). Run `ytplayer --check` (or `ytmusic --check`) to verify.
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
### π΅ `ytmusic` (Audio Client via Script)
|
|
195
|
+
```bash
|
|
196
|
+
curl -fsSL https://raw.githubusercontent.com/Ping-Phantom39/yt-cli/main/yt-song/scripts/install.sh | bash
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
### π¬ `ytplayer` (Video Client via Script)
|
|
202
|
+
```bash
|
|
203
|
+
curl -fsSL https://raw.githubusercontent.com/Ping-Phantom39/yt-cli/main/yt-player/scripts/install.sh | bash
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Manual Compilation from Source
|
|
207
|
+
Ensure you have **Go 1.22+** installed on your system. Navigate to the desired module directory to build:
|
|
208
|
+
|
|
209
|
+
#### Build `ytplayer`
|
|
210
|
+
```bash
|
|
211
|
+
cd yt-player
|
|
212
|
+
go build -o ../bin/ytplayer main.go
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
#### Build `ytmusic`
|
|
216
|
+
```bash
|
|
217
|
+
cd yt-song
|
|
218
|
+
go build -o ../bin/ytmusic main.go
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## πΉοΈ Interactive Controls & Usage
|
|
224
|
+
|
|
225
|
+
### Run ytmusic from anywhere(global access)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
### 1. `ytplayer` (Video Client)
|
|
229
|
+
|
|
230
|
+
Start the player and launch the interactive terminal interface:
|
|
231
|
+
```bash
|
|
232
|
+
./bin/ytplayer
|
|
233
|
+
```
|
|
234
|
+
Or search directly from startup:
|
|
235
|
+
```bash
|
|
236
|
+
./bin/ytplayer "cyberpunk synthwave lofi"
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
#### **Options and Flags**
|
|
240
|
+
* `-l, --limit int`: Maximum search results to query (default: `15`).
|
|
241
|
+
* `-q, --quality string`: Max playback/download quality (e.g. `best`, `1080`, `720`, `480`, `360`, `audio`) (default: `best`).
|
|
242
|
+
* `-m, --local`: Start directly in Local Offline Video mode.
|
|
243
|
+
* `--cookies string`: Custom file path containing exported session cookies.
|
|
244
|
+
* `--cookies-from-browser string`: Extract session cookies directly from a specific browser (e.g. `chrome`, `firefox`, `safari`, `brave`, `edge`).
|
|
245
|
+
* `--vo string`: Override the `mpv` video output driver (e.g., `tct`, `sixel`, `kitty`, `gpu`). Useful for streaming inside headless/SSH sessions.
|
|
246
|
+
* `--check`: Quick check of local media tools and dependency status.
|
|
247
|
+
|
|
248
|
+
#### **TUI Keyboard Shortcuts**
|
|
249
|
+
* `[/]` — Focus the search bar to enter queries or filter local files.
|
|
250
|
+
* `[m]` — Toggle between **Online YouTube Mode** and **Local Offline Video Mode**.
|
|
251
|
+
* `[v]` or `[Tab]` — Cycle through video quality presets (**Best/4K** β **1080p** β **720p** β **480p** β **360p** β **Audio Only π΅**).
|
|
252
|
+
* `[1] - [6]` — Quick jump to quality preset (`1: 4K/Best`, `2: 1080p`, `3: 720p`, `4: 480p`, `5: 360p`, `6: Audio Only`).
|
|
253
|
+
* `[Enter]` — Stream the highlighted video using `mpv` with the selected quality preset.
|
|
254
|
+
* `[d]` — Background download the video as high-quality `.mp4` into `downloads/`.
|
|
255
|
+
* `[Esc]` — Unfocus search bar and return to result browsing.
|
|
256
|
+
* `[q]` or `[Ctrl+C]` — Quit the application.
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
### 2. `ytmusic` (Audio Client)
|
|
261
|
+
|
|
262
|
+
Start the music player:
|
|
263
|
+
```bash
|
|
264
|
+
./bin/ytmusic
|
|
265
|
+
```
|
|
266
|
+
Or start with a search query:
|
|
267
|
+
```bash
|
|
268
|
+
./bin/ytmusic "lofi beats for studying"
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
#### **Options and Flags**
|
|
272
|
+
* `-l, --limit int`: Maximum search results to query (default: `15`).
|
|
273
|
+
* `-m, --local`: Start directly in Local Offline Music mode.
|
|
274
|
+
* `-v, --volume float`: Starting volume level (0.0 to 1.0) (default: `0.8`).
|
|
275
|
+
* `--cookies string`: Custom Netscape format cookies file path.
|
|
276
|
+
* `--cookies-from-browser string`: Load cookies from a browser profile to avoid bot bans.
|
|
277
|
+
* `--check`: Verify local configuration, FFmpeg, yt-dlp, and audio device capability.
|
|
278
|
+
|
|
279
|
+
#### **TUI Keyboard Shortcuts**
|
|
280
|
+
* `[/]` — Focus the search bar to search online tracks or filter local library.
|
|
281
|
+
* `[m]` — Toggle between **Online YouTube Mode** and **Local Offline Music Mode**.
|
|
282
|
+
* `[Enter]` (on result) — Download/buffer and stream audio directly via system speaker.
|
|
283
|
+
* `[d]` — Download song permanently to `./downloads/<Song_Title>.mp3`.
|
|
284
|
+
* `[Space]` — Pause/Resume playback.
|
|
285
|
+
* `[s]` — Stop playback.
|
|
286
|
+
* `[Left]` / `[Right]` or `[h]` / `[l]` — Seek backward/forward by 5 seconds.
|
|
287
|
+
* `[` / `]` — Adjust volume down/up by 5% (Logarithmic scale).
|
|
288
|
+
* `[Esc]` — Unfocus search input.
|
|
289
|
+
* `[q]` or `[Ctrl+C]` — Stop playback, cancel active downloads, and exit.
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
## πͺ Bypassing Anti-Bot Blocking
|
|
294
|
+
|
|
295
|
+
When running these tools on servers (VPS, Cloud environments) or restricted subnets, YouTube may throw HTTP `403 Forbidden` or CAPTCHA errors. Use the following integration options to supply personal session cookies:
|
|
296
|
+
|
|
297
|
+
1. **Browser Extraction (Local Client Only)**:
|
|
298
|
+
Instruct the downloader to pull cookies directly from your active browser profile:
|
|
299
|
+
```bash
|
|
300
|
+
./bin/ytmusic --cookies-from-browser chrome "vaporwave"
|
|
301
|
+
```
|
|
302
|
+
2. **Netscape Cookies File (Server/Remote)**:
|
|
303
|
+
Export cookies using a browser extension (such as *Get cookies.txt LOCALLY*) to a text file (e.g., `cookies.txt`). Place it in the app directory or pass it as a flag:
|
|
304
|
+
```bash
|
|
305
|
+
./bin/ytplayer --cookies ./cookies.txt "synthwave"
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
## π Codebase Layout
|
|
311
|
+
|
|
312
|
+
```text
|
|
313
|
+
yt-song-cli/
|
|
314
|
+
βββ yt-player/ # Video TUI Application (ytplayer module)
|
|
315
|
+
β βββ cmd/ # Cobra commands & flags definitions
|
|
316
|
+
β βββ internal/ # Core business logic
|
|
317
|
+
β βββ go.mod # Module requirements
|
|
318
|
+
β βββ README.md # Video project documentation
|
|
319
|
+
β
|
|
320
|
+
βββ yt-song/ # Audio TUI Application (ytmusic module)
|
|
321
|
+
βββ cmd/ # Cobra CLI commands & flags
|
|
322
|
+
βββ internal/ # Core business logic
|
|
323
|
+
βββ go.mod # Module requirements
|
|
324
|
+
βββ README.md # Audio project documentation
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### π Quick Navigation
|
|
328
|
+
|
|
329
|
+
* **Video Player (`yt-player`):**
|
|
330
|
+
* Entry point: [yt-player/main.go](yt-player/main.go)
|
|
331
|
+
* Readme: [yt-player/README.md](yt-player/README.md)
|
|
332
|
+
* **Audio Player (`yt-song`):**
|
|
333
|
+
* Entry point: [yt-song/main.go](yt-song/main.go)
|
|
334
|
+
* Readme: [yt-song/README.md](yt-song/README.md)
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
# Run `ytmusic` Globally Using a Wrapper Script(Possible Encounter Issue)
|
|
339
|
+
|
|
340
|
+
If `ytmusic` requires a local `--cookies.txt` file, creating a symbolic link with `ln -s` is **not enough**. A symbolic link only points to the executableβit **does not change the current working directory**.
|
|
341
|
+
|
|
342
|
+
As a result, when you run:
|
|
343
|
+
|
|
344
|
+
```bash
|
|
345
|
+
ytmusic
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
from another directory, the application searches for `--cookies.txt` in your **current working directory** instead of the directory containing the binary.
|
|
349
|
+
|
|
350
|
+
## Solution
|
|
351
|
+
|
|
352
|
+
Create a **wrapper script** in `/usr/local/bin` that changes to the directory containing the binary before executing it.
|
|
353
|
+
|
|
354
|
+
### Step 1: Create the wrapper script
|
|
355
|
+
|
|
356
|
+
```bash
|
|
357
|
+
sudo nano /usr/local/bin/ytmusic
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### Step 2: Add the following contents
|
|
361
|
+
|
|
362
|
+
```bash
|
|
363
|
+
#!/bin/bash
|
|
364
|
+
|
|
365
|
+
cd /home/codespace/ || exit 1
|
|
366
|
+
exec ./ytmusic "$@"
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
> **Note:** Replace `/home/codespace/` with the directory where your `ytmusic` binary and `.env` file are located.
|
|
370
|
+
|
|
371
|
+
### Step 3: Make the wrapper executable
|
|
372
|
+
|
|
373
|
+
```bash
|
|
374
|
+
sudo chmod +x /usr/local/bin/ytmusic
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### Step 4: Run the application
|
|
378
|
+
|
|
379
|
+
Now you can run the application from **any directory**:
|
|
380
|
+
|
|
381
|
+
```bash
|
|
382
|
+
ytmusic
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
## How It Works
|
|
386
|
+
|
|
387
|
+
The wrapper script performs the following steps:
|
|
388
|
+
|
|
389
|
+
1. Changes the current working directory to the directory containing the binary.
|
|
390
|
+
2. Executes the `ytmusic` binary.
|
|
391
|
+
3. Forwards any command-line arguments to the application using `"$@"`.
|
|
392
|
+
|
|
393
|
+
Because the working directory is correct, the application can successfully locate the local `.env` file.
|
|
394
|
+
|
|
395
|
+
## Why Not Use a Symbolic Link?
|
|
396
|
+
|
|
397
|
+
For example:
|
|
398
|
+
|
|
399
|
+
```bash
|
|
400
|
+
sudo ln -s /home/codespace/ytmusic /usr/local/bin/ytmusic
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
Although this allows the command to be found in your `PATH`, it **does not** change the working directory.
|
|
404
|
+
|
|
405
|
+
If the Go application loads `.env` like this:
|
|
406
|
+
|
|
407
|
+
```go
|
|
408
|
+
godotenv.Load()
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
or
|
|
412
|
+
|
|
413
|
+
```go
|
|
414
|
+
os.Open(".env")
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
it searches for:
|
|
418
|
+
|
|
419
|
+
```
|
|
420
|
+
<current-working-directory>/.env
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
instead of:
|
|
424
|
+
|
|
425
|
+
```
|
|
426
|
+
/home/codespace/.env
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
Therefore, a wrapper script is the simplest solution when you do not want to modify the Go source code.
|
|
430
|
+
|
|
431
|
+
## Similar work for the ytplayer binary file too
|
|
432
|
+
|
|
433
|
+
## Summary
|
|
434
|
+
|
|
435
|
+
- β
Works globally from any directory.
|
|
436
|
+
- β
No changes to the Go application are required.
|
|
437
|
+
- β
Ensures `.env` is always loaded from the correct location.
|
|
438
|
+
- β
Passes all command-line arguments to the application.
|
|
439
|
+
|
|
440
|
+
## π License
|
|
441
|
+
|
|
442
|
+
This project is licensed under the MIT License. See individual directories for any localized licenses or dependency notes.
|
package/npm/bin/_run.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Shared launcher for the npm bin shims. Spawns the postinstall-downloaded
|
|
3
|
+
// binary with inherited stdio so the Bubble Tea TUI works normally.
|
|
4
|
+
const { spawnSync } = require('node:child_process');
|
|
5
|
+
const fs = require('node:fs');
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
|
|
8
|
+
function run(binary) {
|
|
9
|
+
const exe = process.platform === 'win32' ? `${binary}.exe` : binary;
|
|
10
|
+
const bin = path.join(__dirname, '..', 'vendor', exe);
|
|
11
|
+
if (!fs.existsSync(bin)) {
|
|
12
|
+
console.error(
|
|
13
|
+
`[yt-cli-terminal] ${exe} not found.\n` +
|
|
14
|
+
'The postinstall download probably failed (no network, or no prebuilt\n' +
|
|
15
|
+
'binary for this platform). Reinstall with network access:\n' +
|
|
16
|
+
' npm install -g yt-cli-terminal\n' +
|
|
17
|
+
'You still need the runtime dependencies on PATH: mpv (ytplayer),\n' +
|
|
18
|
+
'ffmpeg and yt-dlp (both tools). Run `ytplayer --check` to verify.\n' +
|
|
19
|
+
'See https://github.com/Ping-Phantom39/yt-cli#readme for alternatives.'
|
|
20
|
+
);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
const result = spawnSync(bin, process.argv.slice(2), { stdio: 'inherit' });
|
|
24
|
+
if (result.error) {
|
|
25
|
+
console.error(`[yt-cli-terminal] failed to launch ${exe}: ${result.error.message}`);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
process.exit(result.status === null ? 1 : result.status);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = { run };
|
package/npm/bin/ytcli.js
ADDED
package/npm/install.js
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
/*
|
|
4
|
+
* postinstall downloader for the `yt-cli-terminal` npm package.
|
|
5
|
+
*
|
|
6
|
+
* Fetches the prebuilt `ytplayer` / `ytmusic` binaries for the current
|
|
7
|
+
* platform from the GitHub release matching this package's version and
|
|
8
|
+
* places them in ./vendor. No runtime dependencies, Node >= 16 only.
|
|
9
|
+
*
|
|
10
|
+
* Environment overrides:
|
|
11
|
+
* YT_CLI_SKIP_POSTINSTALL=1 skip downloading (leave vendor/ empty)
|
|
12
|
+
* YT_CLI_VERSION=x.y.z release tag to download (default: package.json version)
|
|
13
|
+
* YT_CLI_REPO=owner/repo GitHub repo (default: Ping-Phantom39/yt-cli)
|
|
14
|
+
* YT_CLI_BASE_URL=<url> override release base URL (trailing /v<tag>/<asset> appended)
|
|
15
|
+
* YT_CLI_VENDOR_DIR=<dir> override download directory (default: ./vendor)
|
|
16
|
+
* YT_CLI_PLATFORM / YT_CLI_ARCH override platform detection (testing)
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const crypto = require('crypto');
|
|
20
|
+
const fs = require('fs');
|
|
21
|
+
const http = require('http');
|
|
22
|
+
const https = require('https');
|
|
23
|
+
const path = require('path');
|
|
24
|
+
|
|
25
|
+
const REPO = process.env.YT_CLI_REPO || 'Ping-Phantom39/yt-cli';
|
|
26
|
+
const PKG_VERSION = (process.env.YT_CLI_VERSION || require('../package.json').version).replace(/^v/, '');
|
|
27
|
+
const TAG = `v${PKG_VERSION}`;
|
|
28
|
+
const BASE_URL = (process.env.YT_CLI_BASE_URL || `https://github.com/${REPO}/releases/download`).replace(/\/+$/, '');
|
|
29
|
+
const VENDOR_DIR = process.env.YT_CLI_VENDOR_DIR || path.join(__dirname, 'vendor');
|
|
30
|
+
|
|
31
|
+
const BINARIES = ['ytplayer', 'ytmusic'];
|
|
32
|
+
|
|
33
|
+
// Release matrix in .github/workflows/release.yml ships raw binaries for:
|
|
34
|
+
const SUPPORTED = new Set(['linux/amd64', 'darwin/amd64', 'darwin/arm64', 'windows/amd64']);
|
|
35
|
+
|
|
36
|
+
function currentPlatform() {
|
|
37
|
+
return {
|
|
38
|
+
platform: process.env.YT_CLI_PLATFORM || process.platform,
|
|
39
|
+
arch: process.env.YT_CLI_ARCH || process.arch,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function targetTriple() {
|
|
44
|
+
const { platform, arch } = currentPlatform();
|
|
45
|
+
let osPart;
|
|
46
|
+
if (platform === 'linux') osPart = 'linux';
|
|
47
|
+
else if (platform === 'darwin') osPart = 'darwin';
|
|
48
|
+
else if (platform === 'win32') osPart = 'windows';
|
|
49
|
+
else {
|
|
50
|
+
throw new Error(
|
|
51
|
+
`Unsupported platform "${platform}" (arch "${arch}"). ` +
|
|
52
|
+
`Prebuilt binaries exist for: ${[...SUPPORTED].join(', ')}. ` +
|
|
53
|
+
'See https://github.com/Ping-Phantom39/yt-cli#readme for other install methods.'
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
let archPart;
|
|
57
|
+
if (arch === 'x64') archPart = 'amd64';
|
|
58
|
+
else if (arch === 'arm64') archPart = 'arm64';
|
|
59
|
+
else {
|
|
60
|
+
throw new Error(
|
|
61
|
+
`Unsupported architecture "${arch}" on "${platform}". ` +
|
|
62
|
+
`Prebuilt binaries exist for: ${[...SUPPORTED].join(', ')}.`
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
const triple = `${osPart}/${archPart}`;
|
|
66
|
+
if (!SUPPORTED.has(triple)) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
`No prebuilt binaries for ${triple}. ` +
|
|
69
|
+
'On Debian/Ubuntu arm64 use the .deb release asset, otherwise build from source: ' +
|
|
70
|
+
'https://github.com/Ping-Phantom39/yt-cli#readme'
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
return { osPart, archPart, isWindows: osPart === 'windows' };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Release asset filename, e.g. ytplayer-linux-amd64 / ytmusic-windows-amd64.exe
|
|
77
|
+
function assetName(binary) {
|
|
78
|
+
const { osPart, archPart, isWindows } = targetTriple();
|
|
79
|
+
return `${binary}-${osPart}-${archPart}${isWindows ? '.exe' : ''}`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// On-disk filename inside vendor/, e.g. ytplayer / ytmusic.exe
|
|
83
|
+
function vendorName(binary) {
|
|
84
|
+
return binary + (targetTriple().isWindows ? '.exe' : '');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function get(url, redirects = 5) {
|
|
88
|
+
return new Promise((resolve, reject) => {
|
|
89
|
+
const lib = url.startsWith('https:') ? https : http;
|
|
90
|
+
const req = lib.get(
|
|
91
|
+
url,
|
|
92
|
+
{ headers: { 'User-Agent': 'yt-cli-terminal-installer' } },
|
|
93
|
+
(res) => {
|
|
94
|
+
if ([301, 302, 303, 307, 308].includes(res.statusCode) && res.headers.location && redirects > 0) {
|
|
95
|
+
res.resume();
|
|
96
|
+
resolve(get(new URL(res.headers.location, url).toString(), redirects - 1));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (res.statusCode !== 200) {
|
|
100
|
+
res.resume();
|
|
101
|
+
reject(new Error(`HTTP ${res.statusCode} for ${url}`));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
resolve(res);
|
|
105
|
+
}
|
|
106
|
+
);
|
|
107
|
+
req.on('error', reject);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function downloadFile(url, dest) {
|
|
112
|
+
const res = await get(url);
|
|
113
|
+
await fs.promises.mkdir(path.dirname(dest), { recursive: true });
|
|
114
|
+
const tmp = `${dest}.part`;
|
|
115
|
+
try {
|
|
116
|
+
await new Promise((resolve, reject) => {
|
|
117
|
+
const out = fs.createWriteStream(tmp, { mode: 0o755 });
|
|
118
|
+
out.on('finish', resolve);
|
|
119
|
+
out.on('error', reject);
|
|
120
|
+
res.on('error', reject);
|
|
121
|
+
res.pipe(out);
|
|
122
|
+
});
|
|
123
|
+
await fs.promises.rename(tmp, dest);
|
|
124
|
+
if (process.platform !== 'win32') {
|
|
125
|
+
await fs.promises.chmod(dest, 0o755);
|
|
126
|
+
}
|
|
127
|
+
} finally {
|
|
128
|
+
await fs.promises.rm(tmp, { force: true });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function fetchText(url) {
|
|
133
|
+
const res = await get(url);
|
|
134
|
+
const chunks = [];
|
|
135
|
+
for await (const chunk of res) chunks.push(chunk);
|
|
136
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function sha256File(file) {
|
|
140
|
+
return new Promise((resolve, reject) => {
|
|
141
|
+
const hash = crypto.createHash('sha256');
|
|
142
|
+
const stream = fs.createReadStream(file);
|
|
143
|
+
stream.on('error', reject);
|
|
144
|
+
stream.on('data', (d) => hash.update(d));
|
|
145
|
+
stream.on('end', () => resolve(hash.digest('hex')));
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Strictly verify against the release SHA256SUMS.txt when available;
|
|
150
|
+
// older tags predate it, so only warn if the sums file itself is missing.
|
|
151
|
+
async function verifyChecksums(pairs) {
|
|
152
|
+
let sums;
|
|
153
|
+
try {
|
|
154
|
+
sums = await fetchText(`${BASE_URL}/${TAG}/SHA256SUMS.txt`);
|
|
155
|
+
} catch (err) {
|
|
156
|
+
console.warn(`[yt-cli-terminal] warning: no SHA256SUMS.txt for ${TAG} (${err.message}); skipping checksum verification`);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const expected = new Map();
|
|
160
|
+
for (const line of sums.split('\n')) {
|
|
161
|
+
const m = line.match(/^([0-9a-f]{64})\s+\*?(\S+)\s*$/i);
|
|
162
|
+
if (m) expected.set(m[2], m[1].toLowerCase());
|
|
163
|
+
}
|
|
164
|
+
for (const { asset, file } of pairs) {
|
|
165
|
+
const sum = expected.get(asset);
|
|
166
|
+
if (!sum) {
|
|
167
|
+
console.warn(`[yt-cli-terminal] warning: no checksum entry for ${asset}; skipping`);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
const actual = await sha256File(file);
|
|
171
|
+
if (actual !== sum) {
|
|
172
|
+
throw new Error(
|
|
173
|
+
`Checksum mismatch for ${asset} (expected ${sum}, got ${actual}). ` +
|
|
174
|
+
'Delete node_modules and reinstall, or report an issue at ' +
|
|
175
|
+
'https://github.com/Ping-Phantom39/yt-cli/issues'
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
console.log(`[yt-cli-terminal] checksum OK: ${asset}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async function main() {
|
|
183
|
+
if (process.env.YT_CLI_SKIP_POSTINSTALL) {
|
|
184
|
+
console.log('[yt-cli-terminal] YT_CLI_SKIP_POSTINSTALL set; skipping binary download');
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
await fs.promises.mkdir(VENDOR_DIR, { recursive: true });
|
|
188
|
+
const pairs = [];
|
|
189
|
+
for (const binary of BINARIES) {
|
|
190
|
+
const asset = assetName(binary); // throws on unsupported platform
|
|
191
|
+
const url = `${BASE_URL}/${TAG}/${asset}`;
|
|
192
|
+
const file = path.join(VENDOR_DIR, vendorName(binary));
|
|
193
|
+
console.log(`[yt-cli-terminal] downloading ${asset} ...`);
|
|
194
|
+
await downloadFile(url, file);
|
|
195
|
+
pairs.push({ asset, file });
|
|
196
|
+
}
|
|
197
|
+
await verifyChecksums(pairs);
|
|
198
|
+
console.log(`[yt-cli-terminal] installed ytplayer + ytmusic ${TAG} to ${VENDOR_DIR}`);
|
|
199
|
+
console.log('[yt-cli-terminal] note: ytplayer needs mpv, and both tools need ffmpeg + yt-dlp on PATH (try `ytplayer --check`)');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (require.main === module) {
|
|
203
|
+
main().catch((err) => {
|
|
204
|
+
console.error(`[yt-cli-terminal] install failed: ${err.message}`);
|
|
205
|
+
process.exit(1);
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
module.exports = { assetName, vendorName, targetTriple, TAG, BASE_URL, VENDOR_DIR, BINARIES };
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "yt-cli-terminal",
|
|
3
|
+
"version": "1.4.0",
|
|
4
|
+
"description": "Cyberpunk terminal YouTube video player (ytplayer) and music player (ytmusic). Installs prebuilt binaries from GitHub releases.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/Ping-Phantom39/yt-cli.git"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/Ping-Phantom39/yt-cli#readme",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/Ping-Phantom39/yt-cli/issues"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"youtube",
|
|
16
|
+
"terminal",
|
|
17
|
+
"tui",
|
|
18
|
+
"mpv",
|
|
19
|
+
"music",
|
|
20
|
+
"video",
|
|
21
|
+
"cli",
|
|
22
|
+
"yt-dlp"
|
|
23
|
+
],
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=16"
|
|
26
|
+
},
|
|
27
|
+
"bin": {
|
|
28
|
+
"ytplayer": "./npm/bin/ytplayer.js",
|
|
29
|
+
"ytcli": "./npm/bin/ytcli.js",
|
|
30
|
+
"ytmusic": "./npm/bin/ytmusic.js",
|
|
31
|
+
"ytsong": "./npm/bin/ytsong.js"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"postinstall": "node ./npm/install.js",
|
|
35
|
+
"test": "node --test npm/"
|
|
36
|
+
},
|
|
37
|
+
"files": [
|
|
38
|
+
"npm/bin/",
|
|
39
|
+
"npm/install.js",
|
|
40
|
+
"README.md",
|
|
41
|
+
"LICENSE"
|
|
42
|
+
]
|
|
43
|
+
}
|