Spaces:
Sleeping
Sleeping
Update cookies.txt and remove obsolete build and test files
Browse files- modules/cookies.txt +4 -4
- specs/build.md +0 -275
- 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
|
| 11 |
-
.youtube.com TRUE / TRUE
|
| 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
|
| 16 |
-
.youtube.com TRUE / TRUE 0 YSC
|
|
|
|
| 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()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|