Merge branch 'develop' of ssh://git@192.168.1.128:220/mbrucedogs/KaraokeVideoDownloader.git into develop

This commit is contained in:
mbrucedogs 2025-08-05 16:31:03 -05:00
commit b0eb76930a
16 changed files with 568 additions and 31 deletions

47
PRD.md
View File

@ -1,8 +1,8 @@
# 🎤 Karaoke Video Downloader PRD (v3.4.3) # 🎤 Karaoke Video Downloader PRD (v3.4.4)
## ✅ Overview ## ✅ Overview
A Python-based Windows CLI tool to download karaoke videos from YouTube channels/playlists using `yt-dlp.exe`, with advanced tracking, songlist prioritization, and flexible configuration. The codebase has been comprehensively refactored into a modular architecture with centralized utilities for improved maintainability, error handling, and code reuse. A Python-based cross-platform CLI tool to download karaoke videos from YouTube channels/playlists using `yt-dlp`, with advanced tracking, songlist prioritization, and flexible configuration. Supports Windows and macOS with automatic platform detection. The codebase has been comprehensively refactored into a modular architecture with centralized utilities for improved maintainability, error handling, and code reuse.
--- ---
@ -63,9 +63,9 @@ The codebase has been refactored into focused modules with centralized utilities
--- ---
## ⚙️ Platform & Stack ## ⚙️ Platform & Stack
- **Platform:** Windows - **Platform:** Windows, macOS
- **Interface:** Command-line (CLI) - **Interface:** Command-line (CLI)
- **Tech Stack:** Python 3.7+, yt-dlp.exe, mutagen (for ID3 tagging) - **Tech Stack:** Python 3.7+, yt-dlp (platform-specific binary), mutagen (for ID3 tagging)
--- ---
@ -164,9 +164,11 @@ KaroakeVideoDownloader/
├── downloads/ # All video output ├── downloads/ # All video output
│ └── [ChannelName]/ # Per-channel folders │ └── [ChannelName]/ # Per-channel folders
├── logs/ # Download logs ├── logs/ # Download logs
├── downloader/yt-dlp.exe # yt-dlp binary ├── downloader/yt-dlp.exe # yt-dlp binary (Windows)
├── tests/ # Diagnostic and test scripts ├── downloader/yt-dlp_macos # yt-dlp binary (macOS)
│ └── test_installation.py ├── src/tests/ # Test scripts
│ ├── test_macos.py # macOS setup and functionality tests
│ └── test_platform.py # Platform detection tests
├── download_karaoke.py # Main entry point (thin wrapper) ├── download_karaoke.py # Main entry point (thin wrapper)
├── README.md ├── README.md
├── PRD.md ├── PRD.md
@ -530,6 +532,37 @@ def download_new_mode(self, ...):
- [ ] Advanced configuration UI - [ ] Advanced configuration UI
- [ ] Real-time download progress visualization - [ ] Real-time download progress visualization
## 🔧 Recent Bug Fixes & Improvements (v3.4.4)
### **macOS Support with Automatic Platform Detection**
- **Cross-platform compatibility**: Added support for macOS alongside Windows
- **Automatic platform detection**: Detects operating system and selects appropriate yt-dlp binary
- **Flexible yt-dlp integration**: Supports both binary files (`yt-dlp_macos`) and pip installation (`python3 -m yt_dlp`)
- **Setup automation**: `setup_macos.py` script for easy macOS setup with FFmpeg and yt-dlp installation
- **Command parsing**: Intelligent parsing of yt-dlp commands (file paths vs. module commands)
- **Enhanced validation**: Platform-specific error messages and validation in CLI
- **Backward compatibility**: Maintains full compatibility with existing Windows installations
### **Benefits of macOS Support**
- **Native macOS experience**: No need for Windows compatibility layers or virtualization
- **Automatic setup**: Simple setup script handles all dependencies
- **Flexible installation**: Choose between binary download or pip installation
- **Consistent functionality**: All features work identically on both platforms
- **Easy maintenance**: Platform detection handles configuration automatically
### **Setup Instructions**
```bash
# Automatic setup (recommended)
python3 setup_macos.py
# Test installation
python3 src/tests/test_macos.py
# Manual setup options
# 1. Install yt-dlp via pip: pip3 install yt-dlp
# 2. Download binary: curl -L -o downloader/yt-dlp_macos https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_macos
# 3. Install FFmpeg: brew install ffmpeg
```
## 🔧 Recent Bug Fixes & Improvements (v3.4.6) ## 🔧 Recent Bug Fixes & Improvements (v3.4.6)
### **Dry Run Mode** ### **Dry Run Mode**
- **New `--dry-run` parameter**: Build download plan and show what would be downloaded without actually downloading anything - **New `--dry-run` parameter**: Build download plan and show what would be downloaded without actually downloading anything

View File

@ -1,6 +1,6 @@
# 🎤 Karaoke Video Downloader # 🎤 Karaoke Video Downloader
A Python-based Windows CLI tool to download karaoke videos from YouTube channels/playlists using `yt-dlp.exe`, with advanced tracking, songlist prioritization, and flexible configuration. A Python-based cross-platform CLI tool to download karaoke videos from YouTube channels/playlists using `yt-dlp`, with advanced tracking, songlist prioritization, and flexible configuration. Supports Windows and macOS with automatic platform detection.
## ✨ Features ## ✨ Features
- 🎵 **Channel & Playlist Downloads**: Download all videos from a YouTube channel or playlist - 🎵 **Channel & Playlist Downloads**: Download all videos from a YouTube channel or playlist
@ -24,6 +24,7 @@ A Python-based Windows CLI tool to download karaoke videos from YouTube channels
- 📊 **Unmatched Songs Reports**: Generate detailed reports of songs that couldn't be found in any channel with `--generate-unmatched-report` - 📊 **Unmatched Songs Reports**: Generate detailed reports of songs that couldn't be found in any channel with `--generate-unmatched-report`
- 🛡️ **Duplicate File Prevention**: Automatically detects and prevents duplicate files with `(2)`, `(3)` suffixes, with cleanup utility for existing duplicates - 🛡️ **Duplicate File Prevention**: Automatically detects and prevents duplicate files with `(2)`, `(3)` suffixes, with cleanup utility for existing duplicates
- 🏷️ **Consistent Metadata**: Filename and ID3 tag use identical artist/title format for clear file identification - 🏷️ **Consistent Metadata**: Filename and ID3 tag use identical artist/title format for clear file identification
- 🍎 **macOS Support**: Automatic platform detection and setup with native macOS binaries and FFmpeg integration
## 🏗️ Architecture ## 🏗️ Architecture
The codebase has been comprehensively refactored into a modular architecture with centralized utilities for improved maintainability, error handling, and code reuse: The codebase has been comprehensively refactored into a modular architecture with centralized utilities for improved maintainability, error handling, and code reuse:
@ -185,13 +186,53 @@ python data/cleanup_duplicate_files.py
``` ```
## 📋 Requirements ## 📋 Requirements
- **Windows 10/11** - **Windows 10/11 or macOS 10.14+**
- **Python 3.7+** - **Python 3.7+**
- **yt-dlp.exe** (in `downloader/`) - **yt-dlp binary** (platform-specific, see setup instructions below)
- **mutagen** (for ID3 tagging, optional) - **mutagen** (for ID3 tagging, optional)
- **ffmpeg/ffprobe** (for video validation, optional but recommended) - **ffmpeg/ffprobe** (for video validation, optional but recommended)
- **rapidfuzz** (for fuzzy matching, optional, falls back to difflib) - **rapidfuzz** (for fuzzy matching, optional, falls back to difflib)
## 🍎 macOS Setup
### Automatic Setup (Recommended)
Run the macOS setup script to automatically set up yt-dlp and FFmpeg:
```bash
python3 setup_macos.py
```
This script will:
- Detect your macOS version
- Offer installation options for yt-dlp (pip or binary download)
- Install FFmpeg via Homebrew
- Test the installation
### Manual Setup
If you prefer to set up manually:
#### Option 1: Install yt-dlp via pip
```bash
pip3 install yt-dlp
```
#### Option 2: Download yt-dlp binary
```bash
mkdir -p downloader
curl -L -o downloader/yt-dlp_macos https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_macos
chmod +x downloader/yt-dlp_macos
```
#### Install FFmpeg
```bash
brew install ffmpeg
```
### Test Installation
```bash
python3 src/tests/test_macos.py
```
## 🚀 Quick Start ## 🚀 Quick Start
> **💡 Pro Tip**: For a complete list of all available commands, see `commands.txt` - you can copy/paste any command directly into your terminal! > **💡 Pro Tip**: For a complete list of all available commands, see `commands.txt` - you can copy/paste any command directly into your terminal!
@ -370,9 +411,11 @@ KaroakeVideoDownloader/
├── downloads/ # All video output ├── downloads/ # All video output
│ └── [ChannelName]/ # Per-channel folders │ └── [ChannelName]/ # Per-channel folders
├── logs/ # Download logs ├── logs/ # Download logs
├── downloader/yt-dlp.exe # yt-dlp binary ├── downloader/yt-dlp.exe # yt-dlp binary (Windows)
├── tests/ # Diagnostic and test scripts ├── downloader/yt-dlp_macos # yt-dlp binary (macOS)
│ └── test_installation.py ├── src/tests/ # Test scripts
│ ├── test_macos.py # macOS setup and functionality tests
│ └── test_platform.py # Platform detection tests
├── download_karaoke.py # Main entry point (thin wrapper) ├── download_karaoke.py # Main entry point (thin wrapper)
├── README.md ├── README.md
├── PRD.md ├── PRD.md
@ -574,7 +617,8 @@ The codebase has been comprehensively refactored to improve maintainability and
- **Robust download plan execution:** Fixed index management in download plan execution to prevent errors during interrupted downloads. - **Robust download plan execution:** Fixed index management in download plan execution to prevent errors during interrupted downloads.
## 🐞 Troubleshooting ## 🐞 Troubleshooting
- Ensure `yt-dlp.exe` is in the `downloader/` folder - **Windows**: Ensure `yt-dlp.exe` is in the `downloader/` folder
- **macOS**: Run `python3 setup_macos.py` to set up yt-dlp and FFmpeg
- Check `logs/` for error details - Check `logs/` for error details
- Use `python -m karaoke_downloader.check_resolution` to verify video quality - Use `python -m karaoke_downloader.check_resolution` to verify video quality
- If you see errors about ffmpeg/ffprobe, install [ffmpeg](https://ffmpeg.org/download.html) and ensure it is in your PATH - If you see errors about ffmpeg/ffprobe, install [ffmpeg](https://ffmpeg.org/download.html) and ensure it is in your PATH

View File

@ -1,6 +1,6 @@
# 🎤 Karaoke Video Downloader - CLI Commands Reference # 🎤 Karaoke Video Downloader - CLI Commands Reference
# Copy and paste these commands into your terminal # Copy and paste these commands into your terminal
# Updated: v3.4.4 (includes all videos download mode, manual video collection, channel parsing rules, and all previous improvements) # Updated: v3.4.4 (includes macOS support, all videos download mode, manual video collection, channel parsing rules, and all previous improvements)
## 📥 BASIC DOWNLOADS ## 📥 BASIC DOWNLOADS
@ -313,6 +313,24 @@ python download_karaoke.py --parallel --workers 3 --manual --limit 5
# 7c. Manual videos with fuzzy matching # 7c. Manual videos with fuzzy matching
python download_karaoke.py --manual --fuzzy-match --fuzzy-threshold 85 --limit 10 python download_karaoke.py --manual --fuzzy-match --fuzzy-threshold 85 --limit 10
## 🍎 macOS SETUP COMMANDS
# Automatic macOS setup (detects OS and installs yt-dlp + FFmpeg)
python3 setup_macos.py
# Test macOS setup and functionality
python3 src/tests/test_macos.py
# Manual macOS setup options
# Install yt-dlp via pip
pip3 install yt-dlp
# Download yt-dlp binary for macOS
mkdir -p downloader && curl -L -o downloader/yt-dlp_macos https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_macos && chmod +x downloader/yt-dlp_macos
# Install FFmpeg via Homebrew
brew install ffmpeg
## 🔧 TROUBLESHOOTING COMMANDS ## 🔧 TROUBLESHOOTING COMMANDS
# Check if everything is working # Check if everything is working
@ -352,6 +370,7 @@ python download_karaoke.py --clear-server-duplicates
# - Use --fuzzy-match for better song discovery # - Use --fuzzy-match for better song discovery
# - Use --refresh sparingly (forces re-scan) # - Use --refresh sparingly (forces re-scan)
# - Clear cache if you encounter issues # - Clear cache if you encounter issues
# - macOS users: Run `python3 setup_macos.py` for automatic setup
# Parallel download tips: # Parallel download tips:
# - Start with --workers 3 for conservative approach # - Start with --workers 3 for conservative approach

View File

@ -0,0 +1,19 @@
{
"channel_id": "@LetsSingKaraoke",
"videos": [
{
"title": "Sub Urban - Cradles | Karaoke (instrumental)",
"id": "8uj7IzhdiO4"
},
{
"title": "Sia - Snowman | Karaoke (instrumental)",
"id": "ZbWHuncTgsM"
},
{
"title": "Trevor Daniel - Falling | Karaoke (Instrumental)",
"id": "nU7n2aq7f98"
}
],
"last_updated": "2025-08-05T15:59:09.280488",
"video_count": 3
}

View File

@ -0,0 +1,10 @@
# Raw yt-dlp output for @LetsSingKaraoke
# Channel URL: https://www.youtube.com/@LetsSingKaraoke/videos
# Command: downloader/yt-dlp_macos --flat-playlist --print %(title)s|%(id)s|%(url)s --verbose https://www.youtube.com/@LetsSingKaraoke/videos
# Timestamp: 2025-08-05T15:59:09.280155
# Total lines: 3
################################################################################
1: Sub Urban - Cradles | Karaoke (instrumental)|8uj7IzhdiO4|https://www.youtube.com/watch?v=8uj7IzhdiO4
2: Sia - Snowman | Karaoke (instrumental)|ZbWHuncTgsM|https://www.youtube.com/watch?v=ZbWHuncTgsM
3: Trevor Daniel - Falling | Karaoke (Instrumental)|nU7n2aq7f98|https://www.youtube.com/watch?v=nU7n2aq7f98

View File

@ -34,5 +34,12 @@
"include_console": true, "include_console": true,
"include_file": true "include_file": true
}, },
"platform_settings": {
"auto_detect_platform": true,
"yt_dlp_paths": {
"windows": "downloader/yt-dlp.exe",
"macos": "downloader/yt-dlp_macos"
}
},
"yt_dlp_path": "downloader/yt-dlp.exe" "yt_dlp_path": "downloader/yt-dlp.exe"
} }

View File

@ -0,0 +1,78 @@
{
"timestamp": "2025-08-05T16:01:09.018725",
"download_plan": [
{
"video_id": "oHV8Iw0R4BY",
"artist": "Shaboozey, Jelly Roll",
"title": "Amen",
"filename": "Shaboozey, Jelly Roll - Amen.mp4",
"channel_name": "@SingKingKaraoke",
"video_title": "Shaboozey, Jelly Roll - Amen (Karaoke Version)",
"force_download": false
},
{
"video_id": "Jm3a-VAomH0",
"artist": "Pet Shop Boys",
"title": "Domino Dancing",
"filename": "Pet Shop Boys - Domino Dancing.mp4",
"channel_name": "@KaraokeOnVEVO",
"video_title": "Pet Shop Boys - Domino Dancing (Karaoke)",
"force_download": false
},
{
"video_id": "6Vb0igX0-Ss",
"artist": "Chappell Roan",
"title": "The Giver",
"filename": "Chappell Roan - The Giver.mp4",
"channel_name": "@StingrayKaraoke",
"video_title": "Chappell Roan - The Giver (Karaoke Version)",
"force_download": false
},
{
"video_id": "b1k2_B9oCr4",
"artist": "James Arthur",
"title": "Train Wreck",
"filename": "James Arthur - Train Wreck.mp4",
"channel_name": "@sing2karaoke",
"video_title": "James Arthur Train Wreck",
"force_download": false
},
{
"video_id": "cg10FeEYSSQ",
"artist": "Caesars",
"title": "Jerk It Out",
"filename": "Caesars - Jerk It Out.mp4",
"channel_name": "@ZoomKaraokeOfficial",
"video_title": "Caesars - Jerk It Out - Karaoke Version from Zoom Karaoke",
"force_download": false
},
{
"video_id": "m51bbu2ghp4",
"artist": "Jin",
"title": "Don't Say You Love Me",
"filename": "Jin - Dont Say You Love Me.mp4",
"channel_name": "@VocalStarKaraoke",
"video_title": "Don't Say You Love Me - Jin KARAOKE With Vocal Guide",
"force_download": false
},
{
"video_id": "qegLWI99Wg0",
"artist": "Ed Sheeran & Beyoncé",
"title": "Perfect Duet",
"filename": "Ed Sheeran & Beyoncé - Perfect Duet.mp4",
"channel_name": "Unknown",
"video_title": "Ed Sheeran & Beyoncé - Perfect Duet",
"force_download": false
},
{
"video_id": "ZbWHuncTgsM",
"artist": "Sia",
"title": "Snowman | Karaoke (instrumental)",
"filename": "Sia - Snowman Karaoke (instrumental).mp4",
"channel_name": "@LetsSingKaraoke",
"video_title": "Sia - Snowman | Karaoke (instrumental)",
"force_download": false
}
],
"unmatched": []
}

View File

@ -31110,5 +31110,29 @@
"channel": "@SingKingKaraoke", "channel": "@SingKingKaraoke",
"marked_at": "2025-07-29T14:46:07.860090", "marked_at": "2025-07-29T14:46:07.860090",
"reason": "already_on_server" "reason": "already_on_server"
},
"teddy swims_lose control": {
"artist": "Teddy Swims",
"title": "Lose Control",
"video_title": "Teddy Swims - Lose Control",
"channel": "songlist",
"marked_at": "2025-08-05T16:07:47.891258",
"reason": "already_on_server"
},
"benson boone_mystical magical": {
"artist": "Benson Boone",
"title": "Mystical Magical",
"video_title": "Benson Boone - Mystical Magical",
"channel": "songlist",
"marked_at": "2025-08-05T16:07:47.906113",
"reason": "already_on_server"
},
"the weeknd_the hills": {
"artist": "The Weeknd",
"title": "The Hills",
"video_title": "The Weeknd - The Hills",
"channel": "songlist",
"marked_at": "2025-08-05T16:07:47.922750",
"reason": "already_on_server"
} }
} }

View File

@ -0,0 +1,12 @@
{
"generated_at": "2025-08-05T16:07:48.031279",
"total_unmatched": 1,
"unmatched_songs": [
{
"artist": "SZA",
"title": "30 For 30",
"position": 3,
"search_key": "sza_30 for 30"
}
]
}

BIN
downloader/yt-dlp_macos Executable file

Binary file not shown.

View File

@ -328,11 +328,36 @@ Examples:
print("❌ Error: --channel-workers must be between 1 and 10") print("❌ Error: --channel-workers must be between 1 and 10")
sys.exit(1) sys.exit(1)
yt_dlp_path = Path("downloader/yt-dlp.exe") # Load configuration to get platform-aware yt-dlp path
if not yt_dlp_path.exists(): from karaoke_downloader.config_manager import load_config
print("❌ Error: yt-dlp.exe not found in downloader/ directory") config = load_config()
print("Please ensure yt-dlp.exe is present in the downloader/ folder") yt_dlp_path = config.yt_dlp_path
sys.exit(1)
# Check if it's a command string (like "python3 -m yt_dlp") or a file path
if yt_dlp_path.startswith(('python', 'python3')):
# It's a command string, test if it works
try:
import subprocess
cmd = yt_dlp_path.split() + ["--version"]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
if result.returncode != 0:
raise Exception(f"Command failed: {result.stderr}")
except Exception as e:
platform_name = "macOS" if sys.platform == "darwin" else "Windows"
print(f"❌ Error: yt-dlp command failed: {yt_dlp_path}")
print(f"Please ensure yt-dlp is properly installed for {platform_name}")
print(f"Error: {e}")
sys.exit(1)
else:
# It's a file path, check if it exists
yt_dlp_file = Path(yt_dlp_path)
if not yt_dlp_file.exists():
platform_name = "macOS" if sys.platform == "darwin" else "Windows"
binary_name = yt_dlp_file.name
print(f"❌ Error: {binary_name} not found in downloader/ directory")
print(f"Please ensure {binary_name} is present in the downloader/ folder for {platform_name}")
print(f"Expected path: {yt_dlp_file}")
sys.exit(1)
downloader = KaraokeDownloader() downloader = KaraokeDownloader()

View File

@ -4,6 +4,8 @@ Provides centralized configuration loading, validation, and management.
""" """
import json import json
import platform
import sys
from dataclasses import dataclass, field from dataclasses import dataclass, field
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
@ -42,6 +44,13 @@ DEFAULT_CONFIG = {
"include_console": True, "include_console": True,
"include_file": True, "include_file": True,
}, },
"platform_settings": {
"auto_detect_platform": True,
"yt_dlp_paths": {
"windows": "downloader/yt-dlp.exe",
"macos": "downloader/yt-dlp_macos"
}
},
"yt_dlp_path": "downloader/yt-dlp.exe", "yt_dlp_path": "downloader/yt-dlp.exe",
} }
@ -55,6 +64,23 @@ RESOLUTION_MAP = {
} }
def detect_platform() -> str:
"""Detect the current platform and return platform name."""
system = platform.system().lower()
if system == "windows":
return "windows"
elif system == "darwin":
return "macos"
else:
return "windows" # Default to Windows for other platforms
def get_platform_yt_dlp_path(platform_paths: Dict[str, str]) -> str:
"""Get the appropriate yt-dlp path for the current platform."""
platform_name = detect_platform()
return platform_paths.get(platform_name, platform_paths.get("windows", "downloader/yt-dlp.exe"))
@dataclass @dataclass
class DownloadSettings: class DownloadSettings:
"""Configuration for download settings.""" """Configuration for download settings."""
@ -234,11 +260,21 @@ class ConfigManager:
folder_structure = FolderStructure(**config_data.get("folder_structure", {})) folder_structure = FolderStructure(**config_data.get("folder_structure", {}))
logging_config = LoggingConfig(**config_data.get("logging", {})) logging_config = LoggingConfig(**config_data.get("logging", {}))
# Handle platform-specific yt-dlp path
yt_dlp_path = config_data.get("yt_dlp_path", "downloader/yt-dlp.exe")
# Check if platform auto-detection is enabled
platform_settings = config_data.get("platform_settings", {})
if platform_settings.get("auto_detect_platform", True):
platform_paths = platform_settings.get("yt_dlp_paths", {})
if platform_paths:
yt_dlp_path = get_platform_yt_dlp_path(platform_paths)
return AppConfig( return AppConfig(
download_settings=download_settings, download_settings=download_settings,
folder_structure=folder_structure, folder_structure=folder_structure,
logging=logging_config, logging=logging_config,
yt_dlp_path=config_data.get("yt_dlp_path", "downloader/yt-dlp.exe"), yt_dlp_path=yt_dlp_path,
_config_file=self.config_file, _config_file=self.config_file,
) )

View File

@ -80,7 +80,7 @@ class KaraokeDownloader:
self.config = self.config_manager.load_config() self.config = self.config_manager.load_config()
# Initialize paths # Initialize paths
self.yt_dlp_path = Path(self.config.yt_dlp_path) self.yt_dlp_path = self.config.yt_dlp_path # Keep as string for command parsing
self.downloads_dir = Path(self.config.folder_structure.downloads_dir) self.downloads_dir = Path(self.config.folder_structure.downloads_dir)
self.logs_dir = Path(self.config.folder_structure.logs_dir) self.logs_dir = Path(self.config.folder_structure.logs_dir)
@ -799,7 +799,6 @@ class KaraokeDownloader:
from karaoke_downloader.cache_manager import get_download_plan_cache_file, save_plan_cache from karaoke_downloader.cache_manager import get_download_plan_cache_file, save_plan_cache
plan_kwargs = { plan_kwargs = {
"mode": "latest_per_channel",
"channels": len(channel_urls), "channels": len(channel_urls),
"limit_per_channel": limit, "limit_per_channel": limit,
"force_download": force_download, "force_download": force_download,

View File

@ -551,10 +551,10 @@ class TrackingManager:
print(f" 📡 Channel URL: {channel_url}") print(f" 📡 Channel URL: {channel_url}")
import subprocess import subprocess
from karaoke_downloader.youtube_utils import _parse_yt_dlp_command
# First, let's get the total count to show progress # First, let's get the total count to show progress
count_cmd = [ count_cmd = _parse_yt_dlp_command(yt_dlp_path) + [
yt_dlp_path,
"--flat-playlist", "--flat-playlist",
"--print", "--print",
"%(title)s", "%(title)s",
@ -576,8 +576,7 @@ class TrackingManager:
print(f" ⚠️ Channel test error: {e}") print(f" ⚠️ Channel test error: {e}")
# Now fetch all videos with progress indicators # Now fetch all videos with progress indicators
cmd = [ cmd = _parse_yt_dlp_command(yt_dlp_path) + [
yt_dlp_path,
"--flat-playlist", "--flat-playlist",
"--print", "--print",
"%(title)s|%(id)s|%(url)s", "%(title)s|%(id)s|%(url)s",

View File

@ -9,6 +9,19 @@ from typing import Any, Dict, List, Optional, Union
from karaoke_downloader.config_manager import AppConfig from karaoke_downloader.config_manager import AppConfig
def _parse_yt_dlp_command(yt_dlp_path: str) -> List[str]:
"""
Parse yt-dlp path/command into a list of command arguments.
Handles both file paths and command strings like 'python3 -m yt_dlp'.
"""
if yt_dlp_path.startswith(('python', 'python3')):
# It's a Python module command
return yt_dlp_path.split()
else:
# It's a file path
return [yt_dlp_path]
def get_channel_info( def get_channel_info(
channel_url: str, yt_dlp_path: str = "downloader/yt-dlp.exe" channel_url: str, yt_dlp_path: str = "downloader/yt-dlp.exe"
) -> tuple[str, str]: ) -> tuple[str, str]:
@ -43,7 +56,7 @@ def get_playlist_info(
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
"""Get playlist information using yt-dlp.""" """Get playlist information using yt-dlp."""
try: try:
cmd = [yt_dlp_path, "--dump-json", "--flat-playlist", playlist_url] cmd = _parse_yt_dlp_command(yt_dlp_path) + ["--dump-json", "--flat-playlist", playlist_url]
result = subprocess.run(cmd, capture_output=True, text=True, check=True) result = subprocess.run(cmd, capture_output=True, text=True, check=True)
videos = [] videos = []
for line in result.stdout.strip().split("\n"): for line in result.stdout.strip().split("\n"):
@ -75,8 +88,7 @@ def build_yt_dlp_command(
Returns: Returns:
List of command arguments for subprocess.run List of command arguments for subprocess.run
""" """
cmd = [ cmd = _parse_yt_dlp_command(yt_dlp_path) + [
str(yt_dlp_path),
"--no-check-certificates", "--no-check-certificates",
"--ignore-errors", "--ignore-errors",
"--no-warnings", "--no-warnings",
@ -128,7 +140,7 @@ def show_available_formats(
timeout: Timeout in seconds timeout: Timeout in seconds
""" """
print(f"🔍 Checking available formats for: {video_url}") print(f"🔍 Checking available formats for: {video_url}")
format_cmd = [str(yt_dlp_path), "--list-formats", video_url] format_cmd = _parse_yt_dlp_command(yt_dlp_path) + ["--list-formats", video_url]
try: try:
format_result = subprocess.run( format_result = subprocess.run(
format_cmd, capture_output=True, text=True, timeout=timeout format_cmd, capture_output=True, text=True, timeout=timeout

220
setup_macos.py Normal file
View File

@ -0,0 +1,220 @@
#!/usr/bin/env python3
"""
macOS setup script for Karaoke Video Downloader.
This script helps users set up yt-dlp and FFmpeg on macOS.
"""
import os
import sys
import subprocess
from pathlib import Path
def check_ffmpeg():
"""Check if FFmpeg is installed."""
try:
result = subprocess.run(["ffmpeg", "-version"], capture_output=True, text=True, timeout=10)
return result.returncode == 0
except (subprocess.TimeoutExpired, FileNotFoundError):
return False
def check_yt_dlp():
"""Check if yt-dlp is installed via pip or binary."""
# Check pip installation
try:
result = subprocess.run([sys.executable, "-m", "yt_dlp", "--version"],
capture_output=True, text=True, timeout=10)
if result.returncode == 0:
return True
except (subprocess.TimeoutExpired, subprocess.CalledProcessError):
pass
# Check binary file
binary_path = Path("downloader/yt-dlp_macos")
if binary_path.exists():
try:
result = subprocess.run([str(binary_path), "--version"],
capture_output=True, text=True, timeout=10)
return result.returncode == 0
except (subprocess.TimeoutExpired, subprocess.CalledProcessError):
pass
return False
def install_ffmpeg():
"""Install FFmpeg via Homebrew."""
print("🎬 Installing FFmpeg...")
# Check if Homebrew is installed
try:
subprocess.run(["brew", "--version"], capture_output=True, check=True)
except (subprocess.CalledProcessError, FileNotFoundError):
print("❌ Homebrew is not installed. Please install Homebrew first:")
print(" /bin/bash -c \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"")
return False
try:
print("🍺 Installing FFmpeg via Homebrew...")
result = subprocess.run(["brew", "install", "ffmpeg"],
capture_output=True, text=True, check=True)
print("✅ FFmpeg installed successfully!")
return True
except subprocess.CalledProcessError as e:
print(f"❌ Failed to install FFmpeg: {e}")
return False
def download_yt_dlp_binary():
"""Download yt-dlp binary for macOS."""
print("📥 Downloading yt-dlp binary for macOS...")
# Create downloader directory if it doesn't exist
downloader_dir = Path("downloader")
downloader_dir.mkdir(exist_ok=True)
# Download yt-dlp binary
binary_path = downloader_dir / "yt-dlp_macos"
url = "https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp_macos"
try:
print(f"📡 Downloading from: {url}")
result = subprocess.run(["curl", "-L", "-o", str(binary_path), url],
capture_output=True, text=True, check=True)
# Make it executable
binary_path.chmod(0o755)
print(f"✅ yt-dlp binary downloaded to: {binary_path}")
# Test the binary
test_result = subprocess.run([str(binary_path), "--version"],
capture_output=True, text=True, timeout=10)
if test_result.returncode == 0:
version = test_result.stdout.strip()
print(f"✅ Binary test successful! Version: {version}")
return True
else:
print(f"❌ Binary test failed: {test_result.stderr}")
return False
except subprocess.CalledProcessError as e:
print(f"❌ Failed to download yt-dlp binary: {e}")
return False
except Exception as e:
print(f"❌ Error downloading binary: {e}")
return False
def install_yt_dlp():
"""Install yt-dlp via pip."""
print("📦 Installing yt-dlp...")
try:
result = subprocess.run([sys.executable, "-m", "pip", "install", "yt-dlp"],
capture_output=True, text=True, check=True)
print("✅ yt-dlp installed successfully!")
return True
except subprocess.CalledProcessError as e:
print(f"❌ Failed to install yt-dlp: {e}")
return False
def test_installation():
"""Test the installation."""
print("\n🧪 Testing installation...")
# Test FFmpeg
if check_ffmpeg():
print("✅ FFmpeg is working!")
else:
print("❌ FFmpeg is not working")
return False
# Test yt-dlp
if check_yt_dlp():
print("✅ yt-dlp is working!")
else:
print("❌ yt-dlp is not working")
return False
return True
def main():
print("🍎 macOS Setup for Karaoke Video Downloader")
print("=" * 50)
# Check current status
print("🔍 Checking current installation...")
ffmpeg_installed = check_ffmpeg()
yt_dlp_installed = check_yt_dlp()
print(f"FFmpeg: {'✅ Installed' if ffmpeg_installed else '❌ Not installed'}")
print(f"yt-dlp: {'✅ Installed' if yt_dlp_installed else '❌ Not installed'}")
if ffmpeg_installed and yt_dlp_installed:
print("\n🎉 Everything is already installed and working!")
return
# Install missing components
print("\n🚀 Installing missing components...")
# Install FFmpeg if needed
if not ffmpeg_installed:
print("\n🎬 FFmpeg Installation Options:")
print("1. Install via Homebrew (recommended)")
print("2. Download from ffmpeg.org")
print("3. Skip FFmpeg installation")
choice = input("\nChoose an option (1-3): ").strip()
if choice == "1":
if not install_ffmpeg():
print("❌ FFmpeg installation failed")
return
elif choice == "2":
print("📥 Please download FFmpeg from: https://ffmpeg.org/download.html")
print(" Extract and add to your PATH, then run this script again.")
return
elif choice == "3":
print("⚠️ FFmpeg is required for video processing. Some features may not work.")
else:
print("❌ Invalid choice")
return
# Install yt-dlp if needed
if not yt_dlp_installed:
print("\n📦 yt-dlp Installation Options:")
print("1. Install via pip (recommended)")
print("2. Download binary file")
print("3. Skip yt-dlp installation")
choice = input("\nChoose an option (1-3): ").strip()
if choice == "1":
if not install_yt_dlp():
print("❌ yt-dlp installation failed")
return
elif choice == "2":
if not download_yt_dlp_binary():
print("❌ yt-dlp binary download failed")
return
elif choice == "3":
print("❌ yt-dlp is required for video downloading.")
return
else:
print("❌ Invalid choice")
return
# Test installation
if test_installation():
print("\n🎉 Setup completed successfully!")
print("You can now use the Karaoke Video Downloader on macOS.")
print("Run: python download_karaoke.py --help")
else:
print("\n❌ Setup failed. Please check the error messages above.")
if __name__ == "__main__":
main()