diff --git a/docs/API.md b/docs/API.md index aaa6df1..2d6d031 100644 --- a/docs/API.md +++ b/docs/API.md @@ -70,9 +70,11 @@ curl -H "Authorization: Bearer your_api_token_here" http://localhost:8888/endpoi - [Health & Info](#health--info) - [Download Endpoints](#download-endpoints) - [Transcription Endpoints](#transcription-endpoints) +- [Conversion Endpoints](#conversion-endpoints) - [Translation Endpoints](#translation-endpoints) - [Summarization Endpoints](#summarization-endpoints) - [File Management](#file-management) +- [Admin Endpoints](#admin-endpoints) - [Security Configuration](#security-configuration) --- @@ -341,6 +343,182 @@ curl -X POST http://localhost:8888/process \ } ``` +### POST /upload-process +**đ¯ Smart endpoint that auto-detects input and processes accordingly:** +- **Video files** (MP4, AVI, MKV, etc.) â Convert to MP3 â Transcribe +- **Audio files** (MP3, WAV, M4A, etc.) â Transcribe directly +- **URL parameter** â Download from YouTube â Transcribe +- **Mixed input** â Process both uploaded files AND URL + +This endpoint intelligently handles whatever you send it! + +**Form Data:** +- `files`: Video or audio file(s) (optional, multiple files supported, max 50) +- `url`: YouTube URL (optional) +- `language`: Language code for transcription (optional) +- `model`: Transcription model (optional, default: gpt-4o-mini-transcribe) +- `outputPath`: Custom output directory (optional) + +**Note:** You must provide either `files`, `url`, or both. + +**Example 1: Upload video files** +```bash +curl -H "X-API-Key: your_token" \ + -X POST http://localhost:8888/upload-process \ + -F "files=@meeting.mp4" \ + -F "files=@interview.avi" \ + -F "language=en" \ + -F "model=gpt-4o-mini-transcribe" +``` + +**Example 2: Upload audio files** +```bash +curl -H "X-API-Key: your_token" \ + -X POST http://localhost:8888/upload-process \ + -F "files=@podcast.mp3" \ + -F "files=@lecture.wav" \ + -F "language=fr" +``` + +**Example 3: Process YouTube URL** +```bash +curl -H "X-API-Key: your_token" \ + -X POST http://localhost:8888/upload-process \ + -F "url=https://www.youtube.com/watch?v=VIDEO_ID" \ + -F "language=en" +``` + +**Example 4: Mixed - Files AND URL** +```bash +curl -H "X-API-Key: your_token" \ + -X POST http://localhost:8888/upload-process \ + -F "files=@local_video.mp4" \ + -F "url=https://www.youtube.com/watch?v=VIDEO_ID" \ + -F "language=en" +``` + +**Response:** +```json +{ + "success": true, + "totalFiles": 3, + "successCount": 3, + "failCount": 0, + "results": [ + { + "success": true, + "source": "upload", + "sourceType": "video", + "fileName": "meeting.mp4", + "converted": true, + "audioPath": "./output/meeting.mp3", + "audioUrl": "/files/meeting.mp3", + "transcriptionPath": "./output/meeting.txt", + "transcriptionUrl": "/files/meeting.txt", + "text": "Transcribed content..." + }, + { + "success": true, + "source": "upload", + "sourceType": "audio", + "fileName": "podcast.mp3", + "converted": false, + "audioPath": "./output/podcast.mp3", + "audioUrl": "/files/podcast.mp3", + "transcriptionPath": "./output/podcast.txt", + "transcriptionUrl": "/files/podcast.txt", + "text": "Transcribed content..." + }, + { + "success": true, + "source": "url", + "sourceType": "youtube", + "title": "Video Title from YouTube", + "audioPath": "./output/video_title.mp3", + "audioUrl": "/files/video_title.mp3", + "transcriptionPath": "./output/video_title.txt", + "transcriptionUrl": "/files/video_title.txt", + "text": "Transcribed content..." + } + ] +} +``` + +**Supported Video Formats:** +- MP4, AVI, MKV, MOV, WMV, FLV, WebM, M4V + +**Supported Audio Formats:** +- MP3, WAV, M4A, FLAC, OGG, AAC + +--- + +## Conversion Endpoints + +### POST /convert-to-mp3 +Upload video or audio files and convert them to MP3 format. + +**Form Data:** +- `files`: Video or audio file(s) (multiple files supported, max 50) +- `bitrate`: Audio bitrate (optional, default: 192k) +- `quality`: Audio quality 0-9, where 0 is best (optional, default: 2) + +**Example:** +```bash +curl -H "X-API-Key: your_token" \ + -X POST http://localhost:8888/convert-to-mp3 \ + -F "files=@video.mp4" \ + -F "files=@another_video.avi" \ + -F "bitrate=320k" \ + -F "quality=0" +``` + +**Response:** +```json +{ + "success": true, + "totalFiles": 2, + "successCount": 2, + "failCount": 0, + "results": [ + { + "success": true, + "fileName": "video.mp4", + "inputPath": "./output/video.mp4", + "outputPath": "./output/video.mp3", + "outputUrl": "/files/video.mp3", + "size": "5.2 MB" + }, + { + "success": true, + "fileName": "another_video.avi", + "inputPath": "./output/another_video.avi", + "outputPath": "./output/another_video.mp3", + "outputUrl": "/files/another_video.mp3", + "size": "3.8 MB" + } + ] +} +``` + +### GET /supported-formats +Get list of supported video and audio formats for conversion. + +**Example:** +```bash +curl -H "X-API-Key: your_token" \ + http://localhost:8888/supported-formats +``` + +**Response:** +```json +{ + "formats": { + "video": [".mp4", ".avi", ".mkv", ".mov", ".wmv", ".flv", ".webm", ".m4v"], + "audio": [".m4a", ".wav", ".flac", ".ogg", ".aac", ".wma", ".opus"] + } +} +``` + --- ## Translation Endpoints @@ -628,6 +806,140 @@ Ensure `OPENAI_API_KEY` is set in your `.env` file for transcription, translatio --- +## Admin Endpoints + +### POST /admin/upload-cookies +Upload YouTube cookies file to enable authentication bypass for bot detection. + +**Purpose**: When YouTube blocks downloads with "Sign in to confirm you're not a bot", this endpoint allows you to upload cookies from your browser to authenticate requests. + +**Authentication**: Required (use your API token) + +**Request:** +- Method: `POST` +- Content-Type: `multipart/form-data` +- Body: File upload with field name `cookies` + +**Example (cURL):** +```bash +# Upload cookies file +curl -X POST \ + -H "X-API-Key: your_api_token" \ + -F "cookies=@youtube-cookies.txt" \ + http://localhost:8888/admin/upload-cookies +``` + +**Example (Using the automation script):** +```bash +# Extract cookies from browser and upload automatically +export API_TOKEN="your_api_token" +export API_URL="http://localhost:8888" +./extract-and-upload-cookies.sh +``` + +**Response (Success - 200):** +```json +{ + "success": true, + "message": "Cookies uploaded successfully", + "paths": { + "local": "/home/user/project/youtube-cookies.txt", + "persistent": "/tmp/share/youtube-cookies.txt" + }, + "note": "Cookies are now active. No restart required." +} +``` + +**Response (Error - 400):** +```json +{ + "error": "No file uploaded", + "message": "Please upload a cookies.txt file", + "help": "Export cookies from your browser using a 'Get cookies.txt' extension" +} +``` + +**Response (Error - 500):** +```json +{ + "error": "Failed to upload cookies", + "message": "Error details..." +} +``` + +### How to Get YouTube Cookies + +**Method 1: Automated Script (Recommended)** + +Use the provided `extract-and-upload-cookies.sh` script: + +```bash +# Set your API credentials +export API_TOKEN="your_api_token" +export API_URL="http://localhost:8888" + +# Run the script - it will auto-detect your browser +./extract-and-upload-cookies.sh +``` + +The script will: +1. Detect installed browsers (Chrome, Firefox, Edge) +2. Extract cookies using yt-dlp +3. Upload them to the API automatically + +**Method 2: Manual Export** + +1. **Install browser extension:** + - Chrome/Edge: [Get cookies.txt LOCALLY](https://chrome.google.com/webstore/detail/get-cookiestxt-locally/cclelndahbckbenkjhflpdbgdldlbecc) + - Firefox: [cookies.txt](https://addons.mozilla.org/en-US/firefox/addon/cookies-txt/) + +2. **Login to YouTube:** + - Visit https://www.youtube.com + - Make sure you're logged into your account + +3. **Export cookies:** + - Click the extension icon + - Click "Export" or "Download" + - Save the file as `youtube-cookies.txt` + +4. **Upload via API:** + ```bash + curl -X POST \ + -H "X-API-Key: your_api_token" \ + -F "cookies=@youtube-cookies.txt" \ + http://localhost:8888/admin/upload-cookies + ``` + +### Cookie Storage + +Cookies are saved to two locations: + +1. **Local project directory**: `/path/to/project/youtube-cookies.txt` + - Used immediately by the API + - Active without restart + +2. **Persistent storage**: `/tmp/share/youtube-cookies.txt` + - Persists across server restarts + - Automatically loaded on startup (via `refresh-cookies.sh`) + +### Cookie Expiration + +- YouTube cookies typically expire after **2-4 weeks** +- When expired, you'll see "YouTube Bot Detection" errors +- Re-upload fresh cookies using the same method + +### Security Notes + +â ī¸ **Important Cookie Security:** + +- Cookies = Your YouTube session (treat like a password) +- Never commit `youtube-cookies.txt` to git (already in .gitignore) +- Don't share publicly +- File permissions are automatically set to `600` (owner read/write only) +- Re-export periodically when they expire + +--- + ## Security Configuration ### Environment Variables diff --git a/extract-and-upload-cookies.sh b/extract-and-upload-cookies.sh new file mode 100755 index 0000000..ae7da47 --- /dev/null +++ b/extract-and-upload-cookies.sh @@ -0,0 +1,135 @@ +#!/bin/bash +# Extract and Upload Cookies - All-in-one script +# This script extracts YouTube cookies from your browser and uploads them to the API + +set -e # Exit on error + +# Colors for pretty output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Configuration +API_URL="${API_URL:-http://localhost:8888}" +API_TOKEN="${API_TOKEN:-}" +TEMP_COOKIES="/tmp/youtube-cookies-temp.txt" + +echo -e "${BLUE}========================================${NC}" +echo -e "${BLUE}YouTube Cookies Extractor & Uploader${NC}" +echo -e "${BLUE}========================================${NC}" +echo "" + +# Step 1: Check if API token is set +if [ -z "$API_TOKEN" ]; then + echo -e "${YELLOW}â ī¸ API_TOKEN not set in environment${NC}" + echo -e "Please enter your API token:" + read -s API_TOKEN + echo "" +fi + +# Step 2: Detect available browsers +echo -e "${BLUE}đ Detecting browsers...${NC}" +AVAILABLE_BROWSERS=() + +if command -v google-chrome &> /dev/null || command -v chromium &> /dev/null || [ -d "$HOME/.config/google-chrome" ] || [ -d "$HOME/.config/chromium" ]; then + AVAILABLE_BROWSERS+=("chrome") +fi + +if command -v firefox &> /dev/null || [ -d "$HOME/.mozilla/firefox" ]; then + AVAILABLE_BROWSERS+=("firefox") +fi + +if command -v microsoft-edge &> /dev/null || [ -d "$HOME/.config/microsoft-edge" ]; then + AVAILABLE_BROWSERS+=("edge") +fi + +if [ ${#AVAILABLE_BROWSERS[@]} -eq 0 ]; then + echo -e "${RED}â No supported browsers found${NC}" + echo -e "${YELLOW}Supported browsers: Chrome, Firefox, Edge${NC}" + echo "" + echo -e "${YELLOW}Alternative: Manually export cookies${NC}" + echo -e "1. Install browser extension 'Get cookies.txt LOCALLY'" + echo -e "2. Visit youtube.com and login" + echo -e "3. Export cookies to a file" + echo -e "4. Run: curl -X POST -H \"X-API-Key: \$API_TOKEN\" -F \"cookies=@youtube-cookies.txt\" $API_URL/admin/upload-cookies" + exit 1 +fi + +echo -e "${GREEN}â Found browsers:${NC} ${AVAILABLE_BROWSERS[*]}" +echo "" + +# Step 3: Let user choose browser +if [ ${#AVAILABLE_BROWSERS[@]} -eq 1 ]; then + BROWSER="${AVAILABLE_BROWSERS[0]}" + echo -e "${GREEN}Using browser: $BROWSER${NC}" +else + echo -e "${YELLOW}Multiple browsers found. Choose one:${NC}" + select BROWSER in "${AVAILABLE_BROWSERS[@]}"; do + if [ -n "$BROWSER" ]; then + break + fi + done +fi + +echo "" + +# Step 4: Check if yt-dlp is installed +if ! command -v yt-dlp &> /dev/null; then + echo -e "${RED}â yt-dlp is not installed${NC}" + echo -e "${YELLOW}Install with: pip install yt-dlp${NC}" + echo -e "${YELLOW}Or: sudo apt install yt-dlp${NC}" + exit 1 +fi + +# Step 5: Extract cookies using yt-dlp +echo -e "${BLUE}đĒ Extracting cookies from $BROWSER...${NC}" + +if yt-dlp --cookies-from-browser "$BROWSER" --cookies "$TEMP_COOKIES" --no-download "https://www.youtube.com" > /dev/null 2>&1; then + echo -e "${GREEN}â Cookies extracted successfully${NC}" +else + echo -e "${RED}â Failed to extract cookies${NC}" + echo -e "${YELLOW}Make sure:${NC}" + echo -e " 1. You're logged into YouTube in $BROWSER" + echo -e " 2. $BROWSER is closed (some browsers lock the cookies database)" + echo -e " 3. You have the necessary permissions" + exit 1 +fi + +# Step 6: Upload cookies to API +echo -e "${BLUE}đ¤ Uploading cookies to API...${NC}" + +UPLOAD_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \ + -H "X-API-Key: $API_TOKEN" \ + -F "cookies=@$TEMP_COOKIES" \ + "$API_URL/admin/upload-cookies") + +HTTP_CODE=$(echo "$UPLOAD_RESPONSE" | tail -n1) +RESPONSE_BODY=$(echo "$UPLOAD_RESPONSE" | head -n-1) + +if [ "$HTTP_CODE" = "200" ]; then + echo -e "${GREEN}â Cookies uploaded successfully!${NC}" + echo "" + echo -e "${GREEN}âââââââââââââââââââââââââââââââââââââââ${NC}" + echo -e "${GREEN}SUCCESS! You're all set!${NC}" + echo -e "${GREEN}âââââââââââââââââââââââââââââââââââââââ${NC}" + echo "" + echo "$RESPONSE_BODY" | python3 -m json.tool 2>/dev/null || echo "$RESPONSE_BODY" + echo "" + echo -e "${BLUE}âšī¸ Your cookies are now active${NC}" + echo -e "${BLUE}âšī¸ YouTube downloads should work without bot detection${NC}" + echo -e "${BLUE}âšī¸ Cookies expire after a few weeks - run this script again if needed${NC}" +else + echo -e "${RED}â Failed to upload cookies${NC}" + echo -e "${RED}HTTP Status: $HTTP_CODE${NC}" + echo "" + echo "$RESPONSE_BODY" + exit 1 +fi + +# Step 7: Cleanup +rm -f "$TEMP_COOKIES" + +echo "" +echo -e "${GREEN}đ All done!${NC}" diff --git a/public/app.js b/public/app.js index a9218d5..323d577 100644 --- a/public/app.js +++ b/public/app.js @@ -35,6 +35,67 @@ async function apiFetch(url, options = {}) { return response; } +// Helper: Format YouTube enhanced errors +function formatYouTubeError(errorData) { + // Check if it's an enhanced YouTube bot detection error + if (errorData.error === 'YouTube Bot Detection' || errorData.solution) { + return ` +
${errorData.reason}
+ ${errorData.solution.quick}
+Alternative: ${errorData.solution.alternative}
+ ` : ''} + ${errorData.solution.documentation ? ` +đ ${errorData.solution.documentation}
+ ` : ''} +${errorData.message}
+ ${errorData.solution ? `${errorData.solution}
` : ''} + `; + } + + // Default error + return `${errorData.message || errorData.error || 'Unknown error'}
`; +} + // API Configuration Panel const configPanel = document.getElementById('api-config-panel'); const configHeader = document.querySelector('.config-header'); @@ -363,11 +424,16 @@ document.getElementById('download-form').addEventListener('submit', async (e) => }); eventSource.addEventListener('error', (e) => { - let errorMsg = 'Download failed'; - try { errorMsg = JSON.parse(e.data).message || errorMsg; } catch {} + let errorHtml = 'Download failed
'; + try { + const errorData = JSON.parse(e.data); + errorHtml = formatYouTubeError(errorData); + } catch { + // Keep default error + } eventSource.close(); progressContainer.style.display = 'none'; - showResult('download-result', false, `${errorMsg}
`); + showResult('download-result', false, errorHtml); setLoading(button, false); }); @@ -788,11 +854,16 @@ document.getElementById('process-form').addEventListener('submit', async (e) => }); eventSource.addEventListener('error', (e) => { - let errorMsg = 'Processing failed'; - try { errorMsg = JSON.parse(e.data).message || errorMsg; } catch {} + let errorHtml = 'Processing failed
'; + try { + const errorData = JSON.parse(e.data); + errorHtml = formatYouTubeError(errorData); + } catch { + // Keep default error + } eventSource.close(); processProgress.style.display = 'none'; - showResult('process-result', false, `${errorMsg}
`); + showResult('process-result', false, errorHtml); setLoading(button, false); }); @@ -1328,11 +1399,16 @@ if (summarizeLinkForm) { }); eventSource.addEventListener('error', (e) => { - let errorMsg = 'Processing failed'; - try { errorMsg = JSON.parse(e.data).message || errorMsg; } catch {} + let errorHtml = 'Processing failed
'; + try { + const errorData = JSON.parse(e.data); + errorHtml = formatYouTubeError(errorData); + } catch { + // Keep default error + } eventSource.close(); linkProgress.style.display = 'none'; - showResult('summarize-link-result', false, `${errorMsg}
`); + showResult('summarize-link-result', false, errorHtml); setLoading(button, false); }); diff --git a/src/server.js b/src/server.js index 90d833e..879e614 100644 --- a/src/server.js +++ b/src/server.js @@ -134,6 +134,14 @@ const authenticate = (req, res, next) => { // Apply authentication to all routes app.use(authenticate); +// Helper function to handle YouTube enhanced errors +function handleYouTubeError(error, res, defaultMessage = 'Operation failed') { + if (error.isEnhanced && error.details) { + return res.status(503).json(error.details); + } + return res.status(500).json({ error: error.message || defaultMessage }); +} + // Serve static files (HTML interface) const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -153,6 +161,7 @@ app.get('/api', (req, res) => { 'POST /download': 'Download as MP3', 'POST /transcribe': 'Transcribe audio file', 'POST /process': 'Download + transcribe', + 'POST /upload-process': 'Smart: Upload video/audio OR URL -> Transcribe', 'GET /files-list': 'List downloaded files', 'GET /files/