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
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:** Windows
- **Platform:** Windows, macOS
- **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
│ └── [ChannelName]/ # Per-channel folders
├── logs/ # Download logs
├── downloader/yt-dlp.exe # yt-dlp binary
├── tests/ # Diagnostic and test scripts
│ └── test_installation.py
├── downloader/yt-dlp.exe # yt-dlp binary (Windows)
├── downloader/yt-dlp_macos # yt-dlp binary (macOS)
├── 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)
├── README.md
├── PRD.md
@ -530,6 +532,37 @@ def download_new_mode(self, ...):
- [ ] Advanced configuration UI
- [ ] 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)
### **Dry Run Mode**
- **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
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
- 🎵 **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`
- 🛡️ **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
- 🍎 **macOS Support**: Automatic platform detection and setup with native macOS binaries and FFmpeg integration
## 🏗️ Architecture
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
- **Windows 10/11**
- **Windows 10/11 or macOS 10.14+**
- **Python 3.7+**
- **yt-dlp.exe** (in `downloader/`)
- **yt-dlp binary** (platform-specific, see setup instructions below)
- **mutagen** (for ID3 tagging, optional)
- **ffmpeg/ffprobe** (for video validation, optional but recommended)
- **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
> **💡 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
│ └── [ChannelName]/ # Per-channel folders
├── logs/ # Download logs
├── downloader/yt-dlp.exe # yt-dlp binary
├── tests/ # Diagnostic and test scripts
│ └── test_installation.py
├── downloader/yt-dlp.exe # yt-dlp binary (Windows)
├── downloader/yt-dlp_macos # yt-dlp binary (macOS)
├── 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)
├── README.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.
## 🐞 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
- 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

View File

@ -1,6 +1,6 @@
# 🎤 Karaoke Video Downloader - CLI Commands Reference
# 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
@ -313,6 +313,24 @@ python download_karaoke.py --parallel --workers 3 --manual --limit 5
# 7c. Manual videos with fuzzy matching
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
# Check if everything is working
@ -352,6 +370,7 @@ python download_karaoke.py --clear-server-duplicates
# - Use --fuzzy-match for better song discovery
# - Use --refresh sparingly (forces re-scan)
# - Clear cache if you encounter issues
# - macOS users: Run `python3 setup_macos.py` for automatic setup
# Parallel download tips:
# - 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_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"
}

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",
"marked_at": "2025-07-29T14:46:07.860090",
"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")
sys.exit(1)
yt_dlp_path = Path("downloader/yt-dlp.exe")
if not yt_dlp_path.exists():
print("❌ Error: yt-dlp.exe not found in downloader/ directory")
print("Please ensure yt-dlp.exe is present in the downloader/ folder")
sys.exit(1)
# Load configuration to get platform-aware yt-dlp path
from karaoke_downloader.config_manager import load_config
config = load_config()
yt_dlp_path = config.yt_dlp_path
# 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()

View File

@ -4,6 +4,8 @@ Provides centralized configuration loading, validation, and management.
"""
import json
import platform
import sys
from dataclasses import dataclass, field
from datetime import datetime
from pathlib import Path
@ -42,6 +44,13 @@ DEFAULT_CONFIG = {
"include_console": 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",
}
@ -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
class DownloadSettings:
"""Configuration for download settings."""
@ -234,11 +260,21 @@ class ConfigManager:
folder_structure = FolderStructure(**config_data.get("folder_structure", {}))
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(
download_settings=download_settings,
folder_structure=folder_structure,
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,
)

View File

@ -80,7 +80,7 @@ class KaraokeDownloader:
self.config = self.config_manager.load_config()
# 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.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
plan_kwargs = {
"mode": "latest_per_channel",
"channels": len(channel_urls),
"limit_per_channel": limit,
"force_download": force_download,

View File

@ -551,10 +551,10 @@ class TrackingManager:
print(f" 📡 Channel URL: {channel_url}")
import subprocess
from karaoke_downloader.youtube_utils import _parse_yt_dlp_command
# First, let's get the total count to show progress
count_cmd = [
yt_dlp_path,
count_cmd = _parse_yt_dlp_command(yt_dlp_path) + [
"--flat-playlist",
"--print",
"%(title)s",
@ -576,8 +576,7 @@ class TrackingManager:
print(f" ⚠️ Channel test error: {e}")
# Now fetch all videos with progress indicators
cmd = [
yt_dlp_path,
cmd = _parse_yt_dlp_command(yt_dlp_path) + [
"--flat-playlist",
"--print",
"%(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
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(
channel_url: str, yt_dlp_path: str = "downloader/yt-dlp.exe"
) -> tuple[str, str]:
@ -43,7 +56,7 @@ def get_playlist_info(
) -> List[Dict[str, Any]]:
"""Get playlist information using yt-dlp."""
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)
videos = []
for line in result.stdout.strip().split("\n"):
@ -75,8 +88,7 @@ def build_yt_dlp_command(
Returns:
List of command arguments for subprocess.run
"""
cmd = [
str(yt_dlp_path),
cmd = _parse_yt_dlp_command(yt_dlp_path) + [
"--no-check-certificates",
"--ignore-errors",
"--no-warnings",
@ -128,7 +140,7 @@ def show_available_formats(
timeout: Timeout in seconds
"""
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:
format_result = subprocess.run(
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()