Surn commited on
Commit
4736c5d
·
1 Parent(s): 1e4f3ac

Update cookies.txt and remove obsolete build and test files

Browse files
Files changed (3) hide show
  1. modules/cookies.txt +4 -4
  2. specs/build.md +0 -275
  3. specs/test_gallery.py +0 -105
modules/cookies.txt CHANGED
@@ -7,10 +7,10 @@
7
  .youtube.com TRUE / TRUE 1809672953 __Secure-1PSIDTS sidts-CjQBhkeRdzNqWsCZmJ3-gkQi5XfDWk2kp9iiQgPaUxmrZjRZ_3ej2OAxYImFfurTjFOv5VO9EAA
8
  .youtube.com TRUE / TRUE 1809672953 __Secure-3PSIDTS sidts-CjQBhkeRdzNqWsCZmJ3-gkQi5XfDWk2kp9iiQgPaUxmrZjRZ_3ej2OAxYImFfurTjFOv5VO9EAA
9
  .youtube.com TRUE / TRUE 1809673265 __Secure-3PSIDCC AKEyXzWBoEdxrm5iyQK7QFba8BjySeE4Hvz49k1X8zhdAxwfePNf2bUDY2yaA8Buaq0yV5a9IA
10
- .youtube.com TRUE / TRUE 1794094142 VISITOR_INFO1_LIVE 0HOxtcrbVL8
11
- .youtube.com TRUE / TRUE 1794094142 VISITOR_PRIVACY_METADATA CgJVUxIEGgAgaw%3D%3D
12
  .youtube.com TRUE / TRUE 1794094141 __Secure-ROLLOUT_TOKEN CObY3I_n2pG-tQEQ1bTnseyZkwMY0Kqi5LGylAM%3D
13
  .youtube.com TRUE / TRUE 1794094141 __Secure-YNID 18.YT=b-lrNBnuRKt_r9WoZ7T7I0GR0J5azTIUrpcRZeJeqAfDrrUM1cVn7_xjiC_i_0mO8vWkoTy_jFwbaSDg-9hpggq1Pdr5KkkOBgEB18j6UWjFaZsTH6Q__a3cPcUBh-thOeSUlgKvelUWMdLEEncS2ptmEjc4074HKbdCo95b7EZttu1K8tU64uFMwKi2iq-TDsrSFUon4XDw1b1Ex1QRHhNQrc71OR-EV8gcUKCLK4N3nQvp7HVTFBiY5TcAcH0J0suYVQHGZN-TsWXzcctxvBfz6-x-ViMGD0ogmxoW6pa98u4jv9n8oSm1aNnbzIliKWzLis-h9Rfi8tm6WKnTgQ
14
  .youtube.com TRUE / TRUE 0 SOCS CAI
15
- .youtube.com TRUE / TRUE 1778543941 GPS 1
16
- .youtube.com TRUE / TRUE 0 YSC qSj_I1jBT9w
 
7
  .youtube.com TRUE / TRUE 1809672953 __Secure-1PSIDTS sidts-CjQBhkeRdzNqWsCZmJ3-gkQi5XfDWk2kp9iiQgPaUxmrZjRZ_3ej2OAxYImFfurTjFOv5VO9EAA
8
  .youtube.com TRUE / TRUE 1809672953 __Secure-3PSIDTS sidts-CjQBhkeRdzNqWsCZmJ3-gkQi5XfDWk2kp9iiQgPaUxmrZjRZ_3ej2OAxYImFfurTjFOv5VO9EAA
9
  .youtube.com TRUE / TRUE 1809673265 __Secure-3PSIDCC AKEyXzWBoEdxrm5iyQK7QFba8BjySeE4Hvz49k1X8zhdAxwfePNf2bUDY2yaA8Buaq0yV5a9IA
10
+ .youtube.com TRUE / TRUE 1794177400 VISITOR_INFO1_LIVE 0HOxtcrbVL8
11
+ .youtube.com TRUE / TRUE 1794177400 VISITOR_PRIVACY_METADATA CgJVUxIEGgAgaw%3D%3D
12
  .youtube.com TRUE / TRUE 1794094141 __Secure-ROLLOUT_TOKEN CObY3I_n2pG-tQEQ1bTnseyZkwMY0Kqi5LGylAM%3D
13
  .youtube.com TRUE / TRUE 1794094141 __Secure-YNID 18.YT=b-lrNBnuRKt_r9WoZ7T7I0GR0J5azTIUrpcRZeJeqAfDrrUM1cVn7_xjiC_i_0mO8vWkoTy_jFwbaSDg-9hpggq1Pdr5KkkOBgEB18j6UWjFaZsTH6Q__a3cPcUBh-thOeSUlgKvelUWMdLEEncS2ptmEjc4074HKbdCo95b7EZttu1K8tU64uFMwKi2iq-TDsrSFUon4XDw1b1Ex1QRHhNQrc71OR-EV8gcUKCLK4N3nQvp7HVTFBiY5TcAcH0J0suYVQHGZN-TsWXzcctxvBfz6-x-ViMGD0ogmxoW6pa98u4jv9n8oSm1aNnbzIliKWzLis-h9Rfi8tm6WKnTgQ
14
  .youtube.com TRUE / TRUE 0 SOCS CAI
15
+ .youtube.com TRUE / TRUE 1778627200 GPS 1
16
+ .youtube.com TRUE / TRUE 0 YSC huWgPv2lTCk
specs/build.md DELETED
@@ -1,275 +0,0 @@
1
- # SeparateTracks — Build Plan
2
-
3
- ## Goal
4
-
5
- Produce a running Gradio application (`app.py`) that accepts a YouTube ID,
6
- YouTube URL, or uploaded `.wav`/`.mp3` audio, separates it into instrument
7
- stems via Demucs, displays results in an `AudioGallery` UI, and exposes an MCP
8
- server endpoint — deployable locally and as a HuggingFace Docker Space
9
- (`Surn/SeparateTracks`).
10
-
11
- ---
12
-
13
- ## Project Map
14
-
15
- | File | Status | Purpose |
16
- | ---- | ------ | ------- |
17
- | `app.py` | ✅ created | Gradio UI entry point + MCP server |
18
- | `modules/AudioGallery.py` | ✅ created | `AudioGallery(gr.HTML)` — 7-stem audio grid with play and download controls |
19
- | `modules/AudioGallery.pyi` | ✅ created | Type stub for AudioGallery |
20
- | `modules/yt_audio_get_tracks.py` | ✅ moved + updated | `download_audio()` + `separate_tracks()` with progress callbacks |
21
- | `modules/constants.py` | exists | Env vars, shared constants |
22
- | `modules/version_info.py` | exists | Footer HTML with versions |
23
- | `modules/file_utils.py` | exists | File helper utilities |
24
- | `requirements.txt` | ✅ updated | gradio[mcp], python-dotenv, numpy, Pillow, requests added |
25
- | `Dockerfile` | ✅ updated | ffmpeg apt, git, proper pip install order |
26
- | `.gitignore` | ✅ updated | `.env` entry added |
27
-
28
- > **Removed:** Root-level `yt_audio_get_tracks.py` — replaced by
29
- > `modules/yt_audio_get_tracks.py`.
30
-
31
- ---
32
-
33
- ## Step 1 — Fix `.gitignore`
34
-
35
- **Problem:** `.env` contains real credentials (`HF_TOKEN`, `CRYPTO_PK`) and is not
36
- excluded from git tracking.
37
-
38
- **Action:** Add `.env` to `.gitignore`.
39
-
40
- ```gitignore
41
- .env
42
- separated/
43
- *.webm
44
- ```
45
-
46
- > **Warning:** Rotate or regenerate the `HF_TOKEN` and `CRYPTO_PK` values in `.env`
47
- > if they have ever been committed to git or shared publicly.
48
-
49
- ---
50
-
51
- ## Step 2 — Fix `requirements.txt`
52
-
53
- Current file is missing packages that `modules/` and the planned `app.py` need.
54
-
55
- ```txt
56
- # core audio pipeline
57
- yt-dlp
58
- demucs
59
- pydub
60
- youtube-transcript-api
61
- youtube-channel-transcript-api
62
-
63
- # gradio UI + MCP
64
- gradio[mcp]>=5.0
65
-
66
- # utility deps used by modules/
67
- python-dotenv
68
- numpy
69
- Pillow
70
- requests
71
- ```
72
-
73
- > `ffmpeg` must be installed at the OS level, not via pip; handle that in
74
- > `Dockerfile`. `torch` and `torchaudio` are installed separately in Docker.
75
-
76
- ---
77
-
78
- ## Step 3 — Fix `Dockerfile`
79
-
80
- Current Dockerfile:
81
-
82
- - Missing `apt-get install ffmpeg`.
83
- - Missing `pip install -r requirements.txt`.
84
- - Missing demucs, yt-dlp, and pydub installs.
85
-
86
- Updated Dockerfile structure:
87
-
88
- ```dockerfile
89
- FROM python:3.12-slim
90
-
91
- RUN apt-get update && apt-get install -y --no-install-recommends \
92
- ffmpeg curl unzip git \
93
- && curl -fsSL https://deno.land/install.sh | sh \
94
- && cp /root/.deno/bin/deno /usr/local/bin/ \
95
- && rm -rf /var/lib/apt/lists/*
96
-
97
- WORKDIR /app
98
- COPY requirements.txt .
99
-
100
- RUN pip install --no-cache-dir torch torchaudio \
101
- --index-url https://download.pytorch.org/whl/cpu
102
- RUN pip install --no-cache-dir gradio[mcp] transformers
103
- RUN pip install --no-cache-dir -r requirements.txt
104
-
105
- COPY . .
106
-
107
- EXPOSE 7860
108
- CMD ["python", "app.py"]
109
- ```
110
-
111
- > For HF Spaces GPU, the base image and torch install may be handled by the
112
- > runtime instead.
113
-
114
- ---
115
-
116
- ## Step 4 — Create `app.py` ✅ COMPLETE
117
-
118
- **Actual implementation** (differs from the original skeleton):
119
-
120
- - Imports from `modules.AudioGallery` and `modules.yt_audio_get_tracks`.
121
- - `SEPARATED_DIR = Path("separated").resolve()` is used in `allowed_paths`.
122
- - `audio_gallery_head = f"<script>{modules.AudioGallery.GALLERY_JS}</script>"`
123
- is injected via `demo.launch(head=...)`.
124
- - `_extract_video_id(video_input)` accepts raw YouTube IDs plus supported
125
- YouTube URL formats and returns the canonical video ID.
126
- - `_prepare_uploaded_audio(uploaded_audio)` copies `.wav`/`.mp3` uploads into
127
- `separated/` and derives a sanitized local `job_id`.
128
- - Two processing functions:
129
- - `process_video(video_id)` — simple, MCP-exposed tool.
130
- - `process_video_with_progress(video_id, uploaded_audio)` — UI handler.
131
- - UI: `YouTube Video ID or URL` input + `Separate Tracks` button +
132
- `Audio File Override (.wav or .mp3)` upload → Progress textbox →
133
- AudioGallery HTML → footer.
134
- - UI handler uses `progress=gr.Progress(track_tqdm=True)`.
135
- - If an upload is present, it overrides the YouTube field and skips
136
- `download_audio()`.
137
- - Button is wired to `process_video_with_progress` →
138
- `[audio_output, progress_output]`.
139
- - `demo.launch(mcp_server=True, head=audio_gallery_head, allowed_paths=[str(SEPARATED_DIR)])`.
140
- - Audio URLs are built with `modules.file_utils.make_gradio_file_url()` so the
141
- `/gradio_api/file=` endpoint receives a safe relative path.
142
- - `gr.set_static_paths(paths=["separated/", ".separated/"])` registers local
143
- output folders for direct Gradio serving.
144
-
145
- ---
146
-
147
- ## Step 5 — Implement `AudioGallery` Component ✅ COMPLETE
148
-
149
- **Actual implementation** — moved to `modules/AudioGallery.py`:
150
-
151
- - `_CSS` — module-level string covering the gallery grid and controls.
152
- - `GALLERY_JS` — module-level string loaded globally through
153
- `demo.launch(head=...)`; defines `formatTime()`, `drawWaveform()`,
154
- `initAudioItem()`, and a `MutationObserver`.
155
- - `AudioGallery(gr.HTML)`:
156
- - `DEFAULT_LABELS = ["Drums", "Vocals", "Guitar", "Bass", "Other",
157
- "Piano", "Music"]`
158
- - `__init__(audio_urls, *, labels, columns=3, ...)`
159
- - `_build_html(audio_urls, labels, columns)`
160
- - `data-initialized="false"` prevents double event binding on Gradio re-renders.
161
- - `app.py` calls `AudioGallery._build_html(...)` directly.
162
- - Play buttons use `type="button"` so they do not submit the Gradio form.
163
- - Each stem card also renders a download link directly below the play button.
164
-
165
- > The time display is client-side and comes from the `<audio>` element runtime
166
- > playback state, not from the URL string.
167
-
168
- ---
169
-
170
- ## Step 6 — MCP Server Integration ✅ COMPLETE
171
-
172
- - `demo.launch(mcp_server=True)` exposes `/gradio_api/mcp/sse`.
173
- - `process_video()` is the MCP-exposed tool.
174
- - jCodeMunch MCP server is also configured in `.claude/settings.json`.
175
-
176
- ---
177
-
178
- ## Step 7 — Fix `modules/constants.py` for Local Dev ✅ COMPLETE
179
-
180
- `.env` is present with `HF_TOKEN`, so no code change was needed.
181
-
182
- **Note:** `constants.py` also imports `numpy` and `python-dotenv`, both of which
183
- must remain in `requirements.txt`.
184
-
185
- ---
186
-
187
- ## Step 8 — Local Run Verification
188
-
189
- ```bash
190
- # Prerequisites
191
- # - Python 3.12
192
- # - ffmpeg in PATH
193
- # - .env file with HF_TOKEN set
194
-
195
- pip install -r requirements.txt
196
- python app.py
197
- # → Open http://localhost:7860
198
- # → Enter a YouTube video ID or full URL, or upload a .wav/.mp3 file
199
- # → Click "Separate Tracks"
200
- # → Verify 7 stems appear in AudioGallery
201
- # → Verify each stem includes a working download link below the play button
202
- # → Verify MCP endpoint at http://localhost:7860/gradio_api/mcp/sse
203
- ```
204
-
205
- ---
206
-
207
- ## Step 9 — Docker Verification
208
-
209
- ```bash
210
- docker build -t separatetracks .
211
- docker run -p 7860:7860 --env-file .env separatetracks
212
- # → Open http://localhost:7860 and verify the same behavior as Step 8
213
- ```
214
-
215
- ---
216
-
217
- ## Step 10 — HuggingFace Space Deployment
218
-
219
- 1. `README.md` already has the correct HF Space header (`sdk: docker`,
220
- `app_file: app.py`).
221
- 2. Push to the `Surn/SeparateTracks` HF Space repo.
222
- 3. Set Space secrets: `HF_TOKEN`, `CRYPTO_PK`, `HF_REPO_ID`, `SPACE_NAME`.
223
- 4. Space auto-builds from `Dockerfile` on push.
224
-
225
- ---
226
-
227
- ## Dependency Map
228
-
229
- ```text
230
- app.py
231
- ├── modules/AudioGallery.py
232
- │ └── gradio (pip)
233
- ├── modules/yt_audio_get_tracks.py
234
- │ ├── yt-dlp (pip)
235
- │ ├── pydub (pip) → ffmpeg (apt)
236
- │ └── demucs (pip) → torch (pip)
237
- ├── modules/constants.py
238
- │ ├── python-dotenv (pip)
239
- │ └── numpy (pip)
240
- ├── modules/version_info.py
241
- │ └── gradio + torch (pip)
242
- └── modules/file_utils.py
243
- ├── Pillow (pip)
244
- └── requests (pip)
245
- ```
246
-
247
- ---
248
-
249
- ## File Checklist
250
-
251
- | # | File | Action | Done |
252
- | - | ---- | ------ | ---- |
253
- | 1 | `.gitignore` | Add `.env` entry | [x] |
254
- | 2 | `requirements.txt` | Add gradio, dotenv, numpy, Pillow, requests | [x] |
255
- | 3 | `Dockerfile` | Add ffmpeg apt, fix pip installs | [x] |
256
- | 4 | `app.py` | Create Gradio app with AudioGallery + MCP | [x] |
257
- | 5 | `modules/AudioGallery.py` | AudioGallery(gr.HTML) component | [x] |
258
- | 6 | `modules/AudioGallery.pyi` | Type stub | [x] |
259
- | 7 | `modules/yt_audio_get_tracks.py` | Moved from root + progress callbacks added | [x] |
260
- | 8 | `.claude/settings.json` | jCodeMunch MCP server config | [x] |
261
- | 9 | `modules/constants.py` | Verify local-safe | [x] |
262
- | 10 | Local run | Step 8 verification | [ ] |
263
- | 11 | Docker build | Step 9 verification | [ ] |
264
- | 12 | HF Space deploy | Step 10 push | [ ] |
265
-
266
- ---
267
-
268
- ## Notes
269
-
270
- - **Deno:** Required by yt-dlp for some YouTube JS extraction. Docker installs it
271
- from `deno.land/install.sh`. Locally, download `deno.exe` and add it to PATH.
272
- - **Demucs model:** `htdemucs_6s` downloads on first run unless pre-cached.
273
- - **Python style:** Black + ruff + isort per agent conventions.
274
- - **AudioGallery JS:** Use `{{ }}` for JS template literals inside Python
275
- f-strings.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
specs/test_gallery.py DELETED
@@ -1,105 +0,0 @@
1
- """
2
- Test: AudioGallery file serving and UI flow for video ID f-H9bbi0Vyw.
3
- Server must already be running on http://localhost:7860.
4
- """
5
- import sys
6
- from pathlib import Path
7
- from playwright.sync_api import sync_playwright, expect
8
-
9
- VIDEO_ID = "f-H9bbi0Vyw"
10
- BASE_URL = "http://localhost:7860"
11
- STEMS = ["bass", "drums", "guitar", "music", "other", "piano", "vocals"]
12
- SEPARATED = Path("D:/Projects/SeparateTracks/separated/htdemucs_6s") / VIDEO_ID
13
-
14
-
15
- def test_file_endpoint(page):
16
- """Part 1: verify each /file= URL returns audio data (200 + audio MIME)."""
17
- print("\n=== Part 1: /file= endpoint ===")
18
- all_ok = True
19
- for stem in STEMS:
20
- path = (SEPARATED / f"{stem}.mp3").as_posix()
21
- url = f"{BASE_URL}/file={path}"
22
- resp = page.request.head(url)
23
- status = resp.status
24
- ct = resp.headers.get("content-type", "")
25
- ok = status == 200 and "audio" in ct
26
- symbol = "OK" if ok else "FAIL"
27
- print(f" {symbol} {stem:8s} HTTP {status} {ct}")
28
- if not ok:
29
- all_ok = False
30
- return all_ok
31
-
32
-
33
- def test_ui_flow(page):
34
- """Part 2: enter video ID, click button, wait for gallery, verify audio elements."""
35
- print("\n=== Part 2: UI flow ===")
36
-
37
- page.goto(BASE_URL)
38
- page.wait_for_load_state("networkidle")
39
- page.screenshot(path="specs/screenshots/01_initial.png", full_page=True)
40
- print(" Screenshot: 01_initial.png")
41
-
42
- # Fill in the video ID
43
- textbox = page.get_by_label("YouTube Video ID")
44
- textbox.fill(VIDEO_ID)
45
- page.screenshot(path="specs/screenshots/02_filled.png", full_page=True)
46
- print(f" Entered video ID: {VIDEO_ID}")
47
-
48
- # Click Separate Tracks
49
- page.get_by_role("button", name="Separate Tracks").click()
50
- print(" Clicked 'Separate Tracks' — waiting for pipeline (CPU may take ~10 min)…")
51
-
52
- # Wait for the AudioGallery HTML to appear (long timeout for CPU demucs)
53
- try:
54
- page.wait_for_selector(".audio-gallery-container", timeout=720_000)
55
- except Exception:
56
- page.screenshot(path="specs/screenshots/03_timeout.png", full_page=True)
57
- print(" ❌ Timed out waiting for .audio-gallery-container")
58
- return False
59
-
60
- page.screenshot(path="specs/screenshots/03_gallery.png", full_page=True)
61
- print(" Screenshot: 03_gallery.png")
62
-
63
- # Count audio elements
64
- audio_els = page.locator("audio").all()
65
- print(f" Found {len(audio_els)} <audio> element(s)")
66
-
67
- # Check each audio src
68
- all_ok = True
69
- for i, el in enumerate(audio_els):
70
- src = el.get_attribute("src") or ""
71
- # Verify src ends in .mp3 and contains the video ID or /file=
72
- ok = ".mp3" in src and ("/file=" in src or VIDEO_ID in src)
73
- symbol = "OK" if ok else "FAIL"
74
- print(f" {symbol} audio[{i}] src={src[:80]}")
75
- if not ok:
76
- all_ok = False
77
-
78
- # Verify progress textbox shows "Done."
79
- progress = page.get_by_label("Progress").input_value()
80
- done_ok = "Done." in progress or "Separation complete" in progress
81
- print(f" {'OK' if done_ok else 'FAIL'} Progress box: {progress[-60:].strip()!r}")
82
-
83
- return all_ok and len(audio_els) == 7
84
-
85
-
86
- def main():
87
- Path("specs/screenshots").mkdir(parents=True, exist_ok=True)
88
-
89
- with sync_playwright() as p:
90
- browser = p.chromium.launch(headless=True)
91
- page = browser.new_page()
92
-
93
- endpoint_ok = test_file_endpoint(page)
94
- ui_ok = test_ui_flow(page)
95
-
96
- browser.close()
97
-
98
- print("\n=== Summary ===")
99
- print(f" /file= endpoint: {'PASS' if endpoint_ok else 'FAIL'}")
100
- print(f" UI flow: {'PASS' if ui_ok else 'FAIL'}")
101
- sys.exit(0 if (endpoint_ok and ui_ok) else 1)
102
-
103
-
104
- if __name__ == "__main__":
105
- main()