Implement category-based folder organization for downloads with single category selection for multiple videos
This commit is contained in:
parent
1d0364cbfc
commit
56e77c19b1
@ -102,11 +102,29 @@ class YouTubeCLI:
|
||||
def load_config(self, config_path=None):
|
||||
"""Load configuration from file or use defaults."""
|
||||
default_config = {
|
||||
"download_dir": str(Path.home() / "Downloads" / "youtube"),
|
||||
"download_dir": "/Volumes/MediaServer/Youtube/",
|
||||
"default_locations": [
|
||||
str(Path.home() / "Downloads" / "youtube"),
|
||||
str(Path.home() / "Movies" / "youtube"),
|
||||
"/tmp/youtube",
|
||||
"/Volumes/MediaServer/Youtube/",
|
||||
"/Volumes/MediaServer/Youtube/Tech",
|
||||
"/Volumes/MediaServer/Youtube/AI",
|
||||
"/Volumes/MediaServer/Youtube/Art",
|
||||
"/Volumes/MediaServer/Youtube/Homes",
|
||||
"/Volumes/MediaServer/Youtube/Cooking",
|
||||
"/Volumes/MediaServer/Youtube/Fitness",
|
||||
"/Volumes/MediaServer/Youtube/Music",
|
||||
"/Volumes/MediaServer/Youtube/Gaming",
|
||||
"/Volumes/MediaServer/Youtube/Education",
|
||||
"/Volumes/MediaServer/Youtube/Travel",
|
||||
"/Volumes/MediaServer/Youtube/Business",
|
||||
"/Volumes/MediaServer/Youtube/Science",
|
||||
"/Volumes/MediaServer/Youtube/History",
|
||||
"/Volumes/MediaServer/Youtube/Comedy",
|
||||
"/Volumes/MediaServer/Youtube/News",
|
||||
"/Volumes/MediaServer/Youtube/Sports",
|
||||
"/Volumes/MediaServer/Youtube/Nature",
|
||||
"/Volumes/MediaServer/Youtube/Photography",
|
||||
"/Volumes/MediaServer/Youtube/Language",
|
||||
"/Volumes/MediaServer/Youtube/Automotive",
|
||||
],
|
||||
"max_videos_per_page": 15,
|
||||
"yt_dlp_args": {
|
||||
@ -527,6 +545,13 @@ class YouTubeCLI:
|
||||
return
|
||||
|
||||
if valid_videos:
|
||||
# Ask user for category selection first
|
||||
console.print("[blue]Select category for all downloads:[/blue]")
|
||||
selected_category = self.select_category(config)
|
||||
if not selected_category:
|
||||
console.print("[yellow]Download cancelled.[/yellow]")
|
||||
return
|
||||
|
||||
# Ask user for network folder name (optional)
|
||||
network_folder = None
|
||||
console.print("[blue]Choose download destination:[/blue]")
|
||||
@ -544,7 +569,7 @@ class YouTubeCLI:
|
||||
network_folder = None
|
||||
|
||||
console.print(
|
||||
f"[blue]Downloading {len(valid_videos)} videos in sequence...[/blue]"
|
||||
f"[blue]Downloading {len(valid_videos)} videos in sequence to category '{Path(selected_category).name}'...[/blue]"
|
||||
)
|
||||
for i, selected_video in enumerate(valid_videos):
|
||||
console.print(
|
||||
@ -553,13 +578,14 @@ class YouTubeCLI:
|
||||
# Check if this is a playlist and download accordingly
|
||||
if selected_video.get("is_playlist", False):
|
||||
self.download_playlist(
|
||||
selected_video["url"], config, network_folder
|
||||
selected_video["url"], config, network_folder, selected_category
|
||||
)
|
||||
else:
|
||||
self.download_video(
|
||||
selected_video["url"],
|
||||
config,
|
||||
network_folder=network_folder,
|
||||
category=selected_category,
|
||||
)
|
||||
|
||||
# Give a small pause between downloads to avoid rate limiting
|
||||
@ -582,7 +608,42 @@ class YouTubeCLI:
|
||||
"[red]Invalid input. Please enter a number or range of numbers, 'n', 's', or 'q'.[/red]"
|
||||
)
|
||||
|
||||
def download_video(self, url, config, network_folder=None):
|
||||
def get_categories(self, config):
|
||||
"""Get list of available categories from config."""
|
||||
return config.get("default_locations", [])
|
||||
|
||||
def select_category(self, config):
|
||||
"""Allow user to select a category for download."""
|
||||
categories = self.get_categories(config)
|
||||
|
||||
if not categories:
|
||||
console.print("[red]No categories found in configuration[/red]")
|
||||
return None
|
||||
|
||||
console.print("\n[blue]Available Categories:[/blue]")
|
||||
for i, category in enumerate(categories, 1):
|
||||
# Extract just the folder name for display
|
||||
folder_name = Path(category).name if Path(category).name else "Root"
|
||||
console.print(f" [green]{i}[/green] - {folder_name}")
|
||||
|
||||
while True:
|
||||
try:
|
||||
console.print("\n[blue]Select a category (enter number):[/blue]")
|
||||
choice = input().strip()
|
||||
choice_num = int(choice)
|
||||
if 1 <= choice_num <= len(categories):
|
||||
selected_category = categories[choice_num - 1]
|
||||
console.print(f"[green]Selected category: {Path(selected_category).name}[/green]")
|
||||
return selected_category
|
||||
else:
|
||||
console.print("[red]Invalid selection. Please try again.[/red]")
|
||||
except ValueError:
|
||||
console.print("[red]Please enter a valid number.[/red]")
|
||||
except KeyboardInterrupt:
|
||||
console.print("\n[yellow]Operation cancelled.[/yellow]")
|
||||
return None
|
||||
|
||||
def download_video(self, url, config, network_folder=None, category=None):
|
||||
"""Download a video using yt-dlp with progress bar."""
|
||||
|
||||
# Validate URL before proceeding
|
||||
@ -601,8 +662,28 @@ class YouTubeCLI:
|
||||
)
|
||||
return
|
||||
|
||||
# Set download directory
|
||||
download_dir = Path(config["download_dir"])
|
||||
# Set download directory - always use category folder
|
||||
if category:
|
||||
# Ensure we're not downloading directly to base path
|
||||
base_dir = Path(config["download_dir"])
|
||||
category_path = Path(category)
|
||||
|
||||
# If category is just the base path, we need to select a different category
|
||||
if str(category_path) == str(base_dir):
|
||||
console.print("[yellow]Cannot download directly to base path. Please select a category.[/yellow]")
|
||||
selected_category = self.select_category(config)
|
||||
if not selected_category:
|
||||
return
|
||||
download_dir = base_dir / selected_category
|
||||
else:
|
||||
download_dir = base_dir / category
|
||||
else:
|
||||
# If no category specified, ask user to select one
|
||||
selected_category = self.select_category(config)
|
||||
if not selected_category:
|
||||
return
|
||||
download_dir = Path(config["download_dir"]) / selected_category
|
||||
|
||||
download_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Prepare yt-dlp command with better handling for JS challenges
|
||||
@ -709,7 +790,7 @@ class YouTubeCLI:
|
||||
except Exception as e:
|
||||
console.print(f"[red]Error during download: {str(e)}[/red]")
|
||||
|
||||
def download_playlist(self, url, config, network_folder=None):
|
||||
def download_playlist(self, url, config, network_folder=None, category=None):
|
||||
"""Download a YouTube playlist into a dedicated folder."""
|
||||
console.print(f"[blue]Preparing to download playlist:[/blue] {url}")
|
||||
|
||||
@ -748,8 +829,31 @@ class YouTubeCLI:
|
||||
except Exception:
|
||||
playlist_title = "Unknown Playlist"
|
||||
|
||||
# Set download directory
|
||||
download_dir = Path(config["download_dir"])
|
||||
# Set download directory with category support
|
||||
if category:
|
||||
# Ensure we're not downloading directly to base path
|
||||
base_dir = Path(config["download_dir"])
|
||||
category_path = Path(category)
|
||||
|
||||
# If category is just the base path, we need to select a different category
|
||||
if str(category_path) == str(base_dir):
|
||||
console.print("[yellow]Cannot download directly to base path. Please select a category.[/yellow]")
|
||||
selected_category = self.select_category(config)
|
||||
if not selected_category:
|
||||
return
|
||||
download_dir = base_dir / selected_category
|
||||
else:
|
||||
download_dir = base_dir / category
|
||||
else:
|
||||
# If no category specified, ask user to select one
|
||||
selected_category = self.select_category(config)
|
||||
if not selected_category:
|
||||
return
|
||||
download_dir = Path(config["download_dir"]) / selected_category
|
||||
|
||||
download_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Create playlist directory within the category folder
|
||||
playlist_dir = download_dir / playlist_title
|
||||
playlist_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
@ -896,7 +1000,8 @@ def main():
|
||||
"[red]Error: You must provide a video URL for downloading[/red]"
|
||||
)
|
||||
return
|
||||
cli.download_video(args.query, cli.config)
|
||||
# For direct download, we'll ask for category selection
|
||||
cli.download_video(args.query, cli.config, category=None)
|
||||
return
|
||||
|
||||
# Default behavior - show search results
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user