diff --git a/README.md b/README.md index e91a094..b0543d5 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ The project is based on NestJS and Angular. - [Process](#requirements) - [Requirements](#process) - [Environment variables](#environment-variables) + - [YouTube cookies](#youtube-cookies) - [⚖️ License](#-license) ## 🚀 Installation @@ -52,6 +53,7 @@ For detailed configuration, see available [environment variables](#environment-v ```shell docker run -d -p 3000:3000 \ -v /path/to/downloads:/spooty/backend/downloads \ + -v /path/to/cookies.txt:/spooty/config/cookies.txt \ -e SPOTIFY_CLIENT_ID=your_client_id \ -e SPOTIFY_CLIENT_SECRET=your_client_secret \ raiper34/spooty:latest @@ -68,6 +70,7 @@ services: - "3000:3000" volumes: - /path/to/downloads:/spooty/backend/downloads + - /path/to/cookies.txt:/spooty/config/cookies.txt environment: - SPOTIFY_CLIENT_ID=your_client_id - SPOTIFY_CLIENT_SECRET=your_client_secret @@ -101,30 +104,54 @@ Spooty can be also build from source files on your own. Some behaviour and settings of Spooty can be configured using environment variables and `.env` file. - Name | Default | Description | -----------------------|---------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------| - DB_PATH | `./config/db.sqlite` (relative to backend) | Path where Spooty database will be stored | - FE_PATH | `../frontend/browser` (relative to backend) | Path to frontend part of application | - DOWNLOADS_PATH | `./downloads` (relative to backend) | Path where downaloded files will be stored | - FORMAT | `mp3` | Format of downloaded files ('aac', 'flac', 'mp3', 'm4a', 'opus', 'vorbis', 'wav', 'alac') | - QUALITY | undefined | Audio quality (0-9 VBR or specific bitrate) of downloaded files | - PORT | 3000 | Port of Spooty server | - REDIS_PORT | 6379 | Port of Redis server | - REDIS_HOST | localhost | Host of Redis server | - RUN_REDIS | false | Whenever Redis server should be started from backend (recommended for Docker environment) | - SPOTIFY_CLIENT_ID | your_client_id | Client ID of your Spotify application (required) | - SPOTIFY_CLIENT_SECRET| your_client_secret | Client Secret of your Spotify application (required) | - YT_DOWNLOADS_PER_MINUTE | 3 | Set the maximum number of YouTube downloads started per minute | - YT_COOKIES | | Allows you to pass your YouTube cookies to bypass some download restrictions. See [below](#how-to-get-your-youtube-cookies) for instructions. | + Name | Default | Description | +-------------------------|---------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------| + DB_PATH | `./config/db.sqlite` (relative to backend) | Path where Spooty database will be stored | + FE_PATH | `../frontend/browser` (relative to backend) | Path to frontend part of application | + DOWNLOADS_PATH | `./downloads` (relative to backend) | Path where downaloded files will be stored | + FORMAT | `mp3` | Format of downloaded files ('aac', 'flac', 'mp3', 'm4a', 'opus', 'vorbis', 'wav', 'alac') | + QUALITY | undefined | Audio quality (0-9 VBR or specific bitrate) of downloaded files | + PORT | 3000 | Port of Spooty server | + REDIS_PORT | 6379 | Port of Redis server | + REDIS_HOST | localhost | Host of Redis server | + RUN_REDIS | false | Whenever Redis server should be started from backend (recommended for Docker environment) | + SPOTIFY_CLIENT_ID | your_client_id | Client ID of your Spotify application (required) | + SPOTIFY_CLIENT_SECRET | your_client_secret | Client Secret of your Spotify application (required) | + YT_DOWNLOADS_PER_MINUTE | 3 | Set the maximum number of YouTube downloads started per minute | + YT_COOKIES | | Browser name to automatically extract YouTube cookies from (e.g. `chrome`, `firefox`). Only works when running Spooty natively (not in Docker). See [below](#yt_cookies---browser-based-cookies-non-docker). | + YT_COOKIES_FILE | `./config/cookies.txt` | Path to a Netscape-format `cookies.txt` file. Recommended for Docker deployments. See [below](#yt_cookies_file---cookies-file-recommended-for-docker). | -### How to get your YouTube cookies (using browser dev tools): -1. Go to https://www.youtube.com and log in if needed. -2. Open the browser developer tools (F12 or right click > Inspect). -3. Go to the "Application" tab (in Chrome) or "Storage" (in Firefox). -4. In the left menu, find "Cookies" and select https://www.youtube.com. -5. Copy all the cookies (name=value) and join them with a semicolon and a space, like: - VISITOR_INFO1_LIVE=xxxx; YSC=xxxx; SID=xxxx; ... -6. Paste this string into the YT_COOKIES environment variable (in your .env or Docker config). +### YouTube cookies + +YouTube may block or throttle downloads without authentication cookies. Spooty supports two ways to provide them — use the one that fits your setup. + +#### `YT_COOKIES` — browser-based cookies (non-Docker) + +Set `YT_COOKIES` to the name of your browser and yt-dlp will automatically read cookies directly from it. +Supported values: `chrome`, `firefox`, `edge`, `safari`, `brave`, `opera`, `chromium`. + +``` +YT_COOKIES=chrome +``` + +> [!NOTE] +> This only works when Spooty runs on the same machine as your browser (i.e. not in Docker, where no browser is present). + +#### `YT_COOKIES_FILE` — cookies file (recommended for Docker) + +Export your YouTube cookies as a Netscape `cookies.txt` file and provide its path. This is the recommended approach for Docker deployments. + +**How to get your `cookies.txt` file:** +1. Install a browser extension that exports cookies in Netscape format, e.g. [Get cookies.txt LOCALLY](https://chrome.google.com/webstore/detail/get-cookiestxt-locally/cclelndahbckbenkjhflpdbgdldlbecc) for Chrome or [cookies.txt](https://addons.mozilla.org/en-US/firefox/addon/cookies-txt/) for Firefox. +2. Go to https://www.youtube.com and log in. +3. Use the extension to export cookies for `youtube.com` and save the file as `cookies.txt`. + +**Docker usage:** + +Bind mount the `cookies.txt` file into the container and set `YT_COOKIES_FILE` to its path inside the container. See the [Environment variables](#environment-variables) section for details. + +> [!NOTE] +> `YT_COOKIES` takes priority over `YT_COOKIES_FILE` if both are set. # ⚖️ License [MIT](https://choosealicense.com/licenses/mit/) diff --git a/src/backend/.env.default b/src/backend/.env.default index 2bbe253..1aa8fb9 100644 --- a/src/backend/.env.default +++ b/src/backend/.env.default @@ -12,5 +12,6 @@ REDIS_RUN=false SPOTIFY_CLIENT_ID=your_client_id SPOTIFY_CLIENT_SECRET=your_client_secret -YT_COOKIES="AEC=xxx; APISID=xxx; APISID=xxx; HSID=xxx; HSID=xxx; LOGIN_INFO=xxx; NID=xxx; PREF=xxx; SAPISID=xxx; SAPISID=Nxxx; SEARCH_SAMESITE=xxx; SID=xxx; SID=xxx; SIDCC=xxx; SIDCC=xxx; SSID=xxx; SSID=xxx; STRP=xxx; VISITOR_PRIVACY_METADATA=xxx; YSC=xxx" +YT_COOKIES= +YT_COOKIES_FILE=./config/cookies.txt YT_DOWNLOADS_PER_MINUTE=3 \ No newline at end of file diff --git a/src/backend/src/environmentEnum.ts b/src/backend/src/environmentEnum.ts index 8c6d08e..627bc6c 100644 --- a/src/backend/src/environmentEnum.ts +++ b/src/backend/src/environmentEnum.ts @@ -6,4 +6,6 @@ export enum EnvironmentEnum { REDIS_PORT = 'REDIS_PORT', REDIS_HOST = 'REDIS_HOST', REDIS_RUN = 'REDIS_RUN', + YT_COOKIES = 'YT_COOKIES', + YT_COOKIES_FILE = 'YT_COOKIES_FILE', } diff --git a/src/backend/src/shared/youtube.service.ts b/src/backend/src/shared/youtube.service.ts index 0320b5b..27bfc30 100644 --- a/src/backend/src/shared/youtube.service.ts +++ b/src/backend/src/shared/youtube.service.ts @@ -5,6 +5,7 @@ import { TrackService } from '../track/track.service'; import { ConfigService } from '@nestjs/config'; import { YtDlp } from 'ytdlp-nodejs'; import * as yts from 'yt-search'; +import * as fs from 'fs'; const NodeID3 = require('node-id3'); const HEADERS = { @@ -25,6 +26,27 @@ export class YoutubeService { return url; } + private getCookiesOptions(): { + cookiesFromBrowser?: string; + cookies?: string; + } { + const cookiesBrowser = this.configService.get( + EnvironmentEnum.YT_COOKIES, + ); + if (cookiesBrowser) { + this.logger.debug(`Using cookies from browser: ${cookiesBrowser}`); + return { cookiesFromBrowser: cookiesBrowser }; + } + const cookiesFile = this.configService.get( + EnvironmentEnum.YT_COOKIES_FILE, + ); + if (cookiesFile && fs.existsSync(cookiesFile)) { + this.logger.debug(`Using cookies file: ${cookiesFile}`); + return { cookies: cookiesFile }; + } + return {}; + } + async downloadAndFormat(track: TrackEntity, output: string): Promise { this.logger.debug( `Downloading ${track.artist} - ${track.name} (${track.youtubeUrl}) from YT`, @@ -39,7 +61,7 @@ export class YoutubeService { this.configService.get<'m4a'>(EnvironmentEnum.FORMAT), { output, - cookiesFromBrowser: this.configService.get('YT_COOKIES'), + ...this.getCookiesOptions(), headers: HEADERS, jsRuntime: 'node', audioQuality: this.configService.get('QUALITY'),