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):
|
def load_config(self, config_path=None):
|
||||||
"""Load configuration from file or use defaults."""
|
"""Load configuration from file or use defaults."""
|
||||||
default_config = {
|
default_config = {
|
||||||
"download_dir": str(Path.home() / "Downloads" / "youtube"),
|
"download_dir": "/Volumes/MediaServer/Youtube/",
|
||||||
"default_locations": [
|
"default_locations": [
|
||||||
str(Path.home() / "Downloads" / "youtube"),
|
"/Volumes/MediaServer/Youtube/",
|
||||||
str(Path.home() / "Movies" / "youtube"),
|
"/Volumes/MediaServer/Youtube/Tech",
|
||||||
"/tmp/youtube",
|
"/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,
|
"max_videos_per_page": 15,
|
||||||
"yt_dlp_args": {
|
"yt_dlp_args": {
|
||||||
@ -527,6 +545,13 @@ class YouTubeCLI:
|
|||||||
return
|
return
|
||||||
|
|
||||||
if valid_videos:
|
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)
|
# Ask user for network folder name (optional)
|
||||||
network_folder = None
|
network_folder = None
|
||||||
console.print("[blue]Choose download destination:[/blue]")
|
console.print("[blue]Choose download destination:[/blue]")
|
||||||
@ -544,7 +569,7 @@ class YouTubeCLI:
|
|||||||
network_folder = None
|
network_folder = None
|
||||||
|
|
||||||
console.print(
|
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):
|
for i, selected_video in enumerate(valid_videos):
|
||||||
console.print(
|
console.print(
|
||||||
@ -553,13 +578,14 @@ class YouTubeCLI:
|
|||||||
# Check if this is a playlist and download accordingly
|
# Check if this is a playlist and download accordingly
|
||||||
if selected_video.get("is_playlist", False):
|
if selected_video.get("is_playlist", False):
|
||||||
self.download_playlist(
|
self.download_playlist(
|
||||||
selected_video["url"], config, network_folder
|
selected_video["url"], config, network_folder, selected_category
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.download_video(
|
self.download_video(
|
||||||
selected_video["url"],
|
selected_video["url"],
|
||||||
config,
|
config,
|
||||||
network_folder=network_folder,
|
network_folder=network_folder,
|
||||||
|
category=selected_category,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Give a small pause between downloads to avoid rate limiting
|
# 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]"
|
"[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."""
|
"""Download a video using yt-dlp with progress bar."""
|
||||||
|
|
||||||
# Validate URL before proceeding
|
# Validate URL before proceeding
|
||||||
@ -601,8 +662,28 @@ class YouTubeCLI:
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Set download directory
|
# Set download directory - always use category folder
|
||||||
download_dir = Path(config["download_dir"])
|
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)
|
download_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
# Prepare yt-dlp command with better handling for JS challenges
|
# Prepare yt-dlp command with better handling for JS challenges
|
||||||
@ -709,7 +790,7 @@ class YouTubeCLI:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
console.print(f"[red]Error during download: {str(e)}[/red]")
|
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."""
|
"""Download a YouTube playlist into a dedicated folder."""
|
||||||
console.print(f"[blue]Preparing to download playlist:[/blue] {url}")
|
console.print(f"[blue]Preparing to download playlist:[/blue] {url}")
|
||||||
|
|
||||||
@ -748,8 +829,31 @@ class YouTubeCLI:
|
|||||||
except Exception:
|
except Exception:
|
||||||
playlist_title = "Unknown Playlist"
|
playlist_title = "Unknown Playlist"
|
||||||
|
|
||||||
# Set download directory
|
# Set download directory with category support
|
||||||
download_dir = Path(config["download_dir"])
|
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 = download_dir / playlist_title
|
||||||
playlist_dir.mkdir(parents=True, exist_ok=True)
|
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]"
|
"[red]Error: You must provide a video URL for downloading[/red]"
|
||||||
)
|
)
|
||||||
return
|
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
|
return
|
||||||
|
|
||||||
# Default behavior - show search results
|
# Default behavior - show search results
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user