What makes us different from other similar websites? › Forums › Tech › How to Stop Ad Blocker Detection on Websites Like YouTube › Reply To: How to Stop Ad Blocker Detection on Websites Like YouTube
December 2, 2025 at 11:53 am
#8342
Moderator
Bug fixes with line 813, line 29, line 28, and line 768.
#!/bin/bash
# ASCII Art Functions
# Function to print the main ASCII art banner for the script.
print_banner() {
echo "+---------------------------------+"
echo "|===========TAKS SHACK============|"
echo "|======https://taksshack.com======|"
echo "+---------------------------------+"
}
# Function to print a section header with ASCII art.
# Takes the section title as an argument.
print_section_header() {
echo "---=[ $@ ]=---------------------------------------------------"
echo ""
}
# --- Configuration ---
# URL to download the latest yt-dlp binary (Linux/macOS)
YTDLP_URL="https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp"
# Local path where yt-dlp will be saved and executed from
YTDLP_BIN="./yt-dlp"
# Name of the temporary Python script that will handle the download, play, and delete logic
PYTHON_SCRIPT="yt_dlp_player.py"
# Base name for the downloaded video file (e.g., "downloaded_video.mp4")
# yt-dlp will append the correct extension.
OUTPUT_BASENAME="downloaded_video"
# File to store the last used save folder
LAST_SAVE_FOLDER_FILE=".last_save_folder"
# Flag file to indicate if audio tools were installed by this script
AUDIO_TOOLS_INSTALLED_FLAG=".audio_tools_installed_by_script_flag"
# Flag file to indicate if ffmpeg was installed by this script
FFMPEG_INSTALLED_FLAG=".ffmpeg_installed_by_script_flag"
# File for the playlist
PLAYLIST_FILE="video_playlist.txt"
# --- Main Script Execution ---
print_banner
print_section_header "SYSTEM SETUP"
# Step 1: Download yt-dlp if it doesn't exist or isn't executable
if [ ! -f "$YTDLP_BIN" ] || [ ! -x "$YTDLP_BIN" ]; then
echo " yt-dlp binary not found or not executable. Attempting to download..."
if command -v curl &> /dev/null; then
echo " Using 'curl' to download yt-dlp..."
curl -L "$YTDLP_URL" -o "$YTDLP_BIN"
elif command -v wget &> /dev/null; then
echo " Using 'wget' to download yt-dlp..."
wget -O "$YTDLP_BIN" "$YTDLP_URL"
else
echo " Error: Neither 'curl' nor 'wget' found. Please install one of them to download yt-dlp."
echo " Exiting script."
exit 1
fi
if [ $? -eq 0 ]; then
chmod +x "$YTDLP_BIN"
echo " yt-dlp downloaded and made executable."
else
echo " Error: Failed to download yt-dlp. Please check your internet connection or the URL."
echo " Exiting script."
exit 1
fi
else
echo " yt-dlp binary already exists and is executable. Skipping download."
fi
# Step 2: Check and install espeak-ng, aplay, and ffmpeg if not present
ESPEAK_NG_INSTALLED=false
APLAY_INSTALLED=false
FFMPEG_INSTALLED=false
if command -v espeak-ng &> /dev/null; then
ESPEAK_NG_INSTALLED=true
echo " espeak-ng is already installed."
else
echo " espeak-ng is NOT found."
fi
if command -v aplay &> /dev/null; then
APLAY_INSTALLED=true
echo " aplay is already installed."
else
echo " aplay is NOT found."
fi
if command -v ffmpeg &> /dev/null; then
FFMPEG_INSTALLED=true
echo " ffmpeg is already installed."
else
echo " ffmpeg is NOT found. It is required for merging video and audio."
fi
# If any critical tool is missing, offer to install
if [ "$ESPEAK_NG_INSTALLED" = false ] || [ "$APLAY_INSTALLED" = false ] || [ "$FFMPEG_INSTALLED" = false ]; then
read -p " Some required tools (espeak-ng, aplay, ffmpeg) are missing. Do you want to install them? (y/n): " install_tools_choice
if [[ "$install_tools_choice" =~ ^[Yy]$ ]]; then
echo " Attempting to install required tools..."
INSTALL_CMD=""
if command -v apt &> /dev/null; then
INSTALL_CMD="sudo apt install -y espeak-ng alsa-utils ffmpeg"
elif command -v dnf &> /dev/null; then
INSTALL_CMD="sudo dnf install -y espeak-ng alsa-utils ffmpeg"
elif command -v pacman &> /dev/null; then
INSTALL_CMD="sudo pacman -S --noconfirm espeak-ng alsa-utils ffmpeg"
else
echo " Error: No supported package manager (apt, dnf, pacman) found for installing tools."
echo " Please install espeak-ng, alsa-utils, and ffmpeg manually."
fi
if [ -n "$INSTALL_CMD" ]; then
if eval "$INSTALL_CMD"; then
echo " Required tools installed successfully."
touch "$AUDIO_TOOLS_INSTALLED_FLAG"
touch "$FFMPEG_INSTALLED_FLAG"
else
echo " Error: Failed to install required tools. Please check permissions or internet connection."
fi
fi
else
echo " Skipping installation of missing tools. Script functionality may be limited or fail."
# If ffmpeg wasn't installed, exit because it's critical for merging.
if [ "$FFMPEG_INSTALLED" = false ]; then
echo " ffmpeg is critical for downloading videos with sound. Exiting."
exit 1
fi
fi
fi
echo ""
print_section_header "PYTHON SCRIPT CREATION"
# Step 3: Create the Python script dynamically
echo " Creating temporary Python script: $PYTHON_SCRIPT"
cat <<'EOF' > "$PYTHON_SCRIPT"
import subprocess
import os
import sys
import glob
import re
import time
import shutil
# Path to the downloaded yt-dlp binary (relative to where the shell script runs)
YTDLP_PATH = "./yt-dlp"
# Base name for the downloaded video file
OUTPUT_BASENAME = "downloaded_video"
# File to store the last used save folder (Python will now read/write this directly)
LAST_SAVE_FOLDER_FILE = ".last_save_folder"
# File for the playlist
PLAYLIST_FILE = "video_playlist.txt"
# Temporary WAV file for the test sound
TEST_SOUND_FILE = "taks_shack_test_sound.wav"
# Exit code signaling to the bash script to uninstall audio tools and clean up last save folder
UNINSTALL_AUDIO_TOOLS_EXIT_CODE = 5
# Regex to find percentage in yt-dlp download lines
PROGRESS_RE = re.compile(r'\[download\]\s+(\d+\.?\d*)%')
# --- Utility Functions ---
def print_ascii_line(char='-', length=60):
"""Prints a line of ASCII characters."""
print(char * length)
def print_ascii_header(text, char='='):
"""Prints a header with ASCII art."""
print_ascii_line(char)
print(f" {text}")
print_ascii_line(char)
print("") # Add a newline for spacing
def draw_ascii_progress_bar(percentage, bar_length=40):
"""
Draws an ASCII progress bar for the download.
Updates the same line in the terminal using carriage return.
"""
filled_len = int(bar_length * percentage // 100)
bar = '#' * filled_len + '-' * (bar_length - filled_len)
sys.stdout.write(f'\rDownloading: [ {bar} ] {percentage:6.2f}%') # Fixed width for percentage
sys.stdout.flush()
def play_test_sound():
"""
Generates and plays a small test sound using espeak-ng and aplay.
"""
print_ascii_header("AUDIO TEST", '-')
test_text = "Initiating video playback. Stand by."
# Check if espeak-ng and aplay are available before attempting to play
if not (subprocess.run(["which", "espeak-ng"], capture_output=True).returncode == 0 and \
subprocess.run(["which", "aplay"], capture_output=True).returncode == 0):
print(" Skipping audio test: espeak-ng or aplay not found (or not in PATH).")
print_ascii_line('=')
return
try:
# Generate the WAV file
print(f" Generating test sound: '{test_text}'...")
subprocess.run(["espeak-ng", "-w", TEST_SOUND_FILE, test_text], check=True, capture_output=True)
# Play the WAV file
print(f" Playing test sound from {TEST_SOUND_FILE}...")
subprocess.run(["aplay", TEST_SOUND_FILE], check=True, capture_output=True)
print(" Test sound played successfully.")
except FileNotFoundError as e:
print(f" Warning: Audio test tools not found. {e.strerror}: '{e.filename}'.")
print(" This should have been caught by the main bash script. Audio wake-up may be unavailable.")
except subprocess.CalledProcessError as e:
print(f" Warning: Failed to generate or play test sound. Error: {e.stderr.decode().strip()}")
except Exception as e:
print(f" An unexpected error occurred during audio test: {e}")
finally:
# Clean up the temporary sound file
if os.path.exists(TEST_SOUND_FILE):
os.remove(TEST_SOUND_FILE)
print_ascii_line('=') # Separator line
def get_playlist_links():
"""Reads the playlist file and returns a list of video links."""
links = []
if os.path.exists(PLAYLIST_FILE):
try:
with open(PLAYLIST_FILE, 'r') as f:
# Use strip to clean up whitespace and ensure no empty lines are added
links = [line.strip() for line in f if line.strip()]
except Exception as e:
print(f"Warning: Could not read playlist file '{PLAYLIST_FILE}': {e}")
# Always return the list object, even if empty, preventing NoneType error.
return links
def update_playlist_file(links):
"""Writes the current list of links back to the playlist file."""
try:
# 'w' mode truncates the file and writes the new content
with open(PLAYLIST_FILE, 'w') as f:
f.write('\n'.join(links) + '\n')
return True
except Exception as e:
print(f"Error: Could not rewrite playlist file '{PLAYLIST_FILE}': {e}")
return False
def add_to_playlist(youtube_link):
"""Checks for duplicates and appends a link to the playlist file if unique."""
links = get_playlist_links()
# Check for duplicates
if youtube_link in links:
print(f"\n Link already exists in the playlist. Skipping addition.")
return False
# Append the new link
links.append(youtube_link)
if update_playlist_file(links):
print(f"\n Link successfully added to the end of the playlist: {PLAYLIST_FILE}")
return True
return False
def remove_first_from_playlist():
"""Removes the first link from the playlist file and re-writes the file."""
links = get_playlist_links()
if not links:
return None
# Remove the first item
removed_link = links.pop(0)
if update_playlist_file(links):
print(f"\n Link successfully removed from the top of the playlist.")
return removed_link
return None # Indicate failure
def run_yt_dlp(youtube_link, cookie_option=None, is_browser_option=False):
"""
Attempts to download a video using yt-dlp with optional cookies.
Prints download progress to stdout using a custom ASCII bar.
Returns (success_boolean, stderr_output, video_title_suggestion).
"""
# Use --get-filename to preview the filename yt-dlp would use
# Also use --get-title to get the actual title from YouTube
info_command = [
YTDLP_PATH,
'--get-title',
'--print', '%(title)s', # Get title
'--print', '%(id)s.%(ext)s', # Get filename suggestion
youtube_link
]
if cookie_option:
if is_browser_option:
info_command.extend(['--cookies-from-browser', cookie_option])
else:
expanded_cookies_path = os.path.expanduser(cookie_option)
info_command.extend(['--cookies', expanded_cookies_path])
video_title = None
suggested_filename = None
try:
info_process = subprocess.run(info_command, capture_output=True, text=True, check=True)
# Assuming yt-dlp prints title on first line, filename on second
info_lines = info_process.stdout.strip().split('\n')
if len(info_lines) >= 2:
video_title = info_lines[0].strip()
# Sanitize the title for use as a filename
suggested_filename = re.sub(r'[\\/:*?"<>|]', '_', video_title)
# Remove leading/trailing spaces, and ensure it's not empty
suggested_filename = suggested_filename.strip()
if not suggested_filename:
suggested_filename = "youtube_video" # Fallback if title is empty after sanitization
# Append a generic extension for the prompt, actual extension will be handled by yt-dlp
suggested_filename += ".mp4"
else:
print(f"Warning: Could not get full video info. Output: {info_process.stdout.strip()}")
except subprocess.CalledProcessError as e:
print(f"Error getting video info: {e.stderr}")
except Exception as e:
print(f"An unexpected error occurred while getting video info: {e}")
download_command = [
YTDLP_PATH,
# Prioritize separate best video and audio, then merge. Fallback to best overall mp4, then just best.
'-f', 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best',
'--merge-output-format', 'mp4', # Merge audio and video into an MP4 container.
'--output', f"{OUTPUT_BASENAME}.%(ext)s",
'--sponsorblock-remove', 'sponsor',
youtube_link
]
# Add cookies option based on whether it's a browser or a file path
if cookie_option:
if is_browser_option:
download_command.extend(['--cookies-from-browser', cookie_option])
else:
# Expand user's home directory (e.g., '~/.config/cookies.txt')
expanded_cookies_path = os.path.expanduser(cookie_option)
download_command.extend(['--cookies', expanded_cookies_path])
stderr_output = ""
try:
# Use subprocess.Popen to stream output in real-time
process = subprocess.Popen(
download_command,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, # Direct stderr to stdout for real-time reading
text=True, # Decode stdout/stderr as text
bufsize=1 # Line-buffered output for real-time printing
)
is_download_progress_active = False
# Read stdout line by line
for line in iter(process.stdout.readline, ''):
# Check if the line is a progress update from yt-dlp
match = PROGRESS_RE.search(line)
if match:
is_download_progress_active = True
percentage = float(match.group(1))
draw_ascii_progress_bar(percentage)
else:
if is_download_progress_active:
sys.stdout.write('\n') # Move to next line after progress bar
is_download_progress_active = False # Reset flag after progress bar is finalized by a non-progress line
sys.stdout.write(line) # Print other yt-dlp output directly
sys.stdout.flush()
# After the loop, if a progress bar was the last thing printed, ensure a newline
if is_download_progress_active:
sys.stdout.write('\n')
# Wait for the subprocess to complete and get its return code
return_code = process.wait()
return return_code == 0, stderr_output, suggested_filename
except FileNotFoundError:
return False, f"Error: yt-dlp binary not found at '{YTDLP_PATH}'. Ensure it's downloaded and executable.", suggested_filename
except Exception as e:
return False, f"An unexpected error occurred during yt-dlp execution: {e}", suggested_filename
def find_package_manager_install_command(package_name):
"""
Tries to find a supported package manager and returns the installation command list.
"""
if subprocess.run(["which", "apt"], capture_output=True).returncode == 0:
return ["sudo", "apt", "install", "-y", package_name]
elif subprocess.run(["which", "dnf"], capture_output=True).returncode == 0:
return ["sudo", "dnf", "install", "-y", package_name]
elif subprocess.run(["which", "pacman"], capture_output=True).returncode == 0:
return ["sudo", "pacman", "-S", "--noconfirm", package_name]
return None
def _get_player_and_folder():
"""Performs pre-checks and returns the determined media player and last save folder."""
# 1. Read last_save_folder
last_save_folder = ""
if os.path.exists(LAST_SAVE_FOLDER_FILE):
try:
with open(LAST_SAVE_FOLDER_FILE, 'r') as f:
last_save_folder = f.read().strip()
except Exception as e:
print(f"Warning: Could not read last save folder file '{LAST_SAVE_FOLDER_FILE}': {e}")
last_save_folder = "" # Reset if read fails
print_ascii_header("PYTHON DEPENDENCY CHECKS", '-')
# 2. Check for a suitable media player
media_player = None
mpv_available = subprocess.run(["which", "mpv"], capture_output=True).returncode == 0
vlc_available = subprocess.run(["which", "vlc"], capture_output=True).returncode == 0
smplayer_available = subprocess.run(["which", "smplayer"], capture_output=True).returncode == 0
sys.stdout.write(" Checking for media player (mpv, vlc, smplayer)...")
sys.stdout.flush()
if mpv_available:
media_player = "mpv"
print(f" [OK: Using {media_player}]")
elif vlc_available or smplayer_available:
print(" [FAILED to find mpv, alternatives found]")
print_ascii_line('=')
print_ascii_header("MPV MISSING - ACTION REQUIRED", '#')
alt_options = []
if vlc_available: alt_options.append("'v' for VLC")
if smplayer_available: alt_options.append("'s' for SMPlayer")
choice_prompt = (
" The preferred player 'mpv' was not found.\n"
" Do you want to try installing 'mpv' now (requires sudo) or use an alternative player?\n"
f" (Type 'i' for install, {' or '.join(alt_options)}, 'e' to exit): "
)
install_choice = input(choice_prompt).lower().strip()
if install_choice == 'i':
install_cmd = find_package_manager_install_command("mpv")
if install_cmd:
try:
print(f" Attempting to run: {' '.join(install_cmd)}")
subprocess.run(install_cmd, check=True)
print(" mpv installed successfully. Using mpv.")
media_player = "mpv"
except subprocess.CalledProcessError:
print(" Failed to install mpv. Falling back to alternative player.")
except FileNotFoundError:
print(" Failed to run installation command (sudo not found or similar). Falling back.")
else:
print(" No supported package manager (apt, dnf, pacman) found for installation. Falling back to alternative player.")
if not media_player:
if install_choice == 'v' and vlc_available:
media_player = "vlc"
elif install_choice == 's' and smplayer_available:
media_player = "smplayer"
else:
if vlc_available: media_player = "vlc"
elif smplayer_available: media_player = "smplayer"
if not media_player:
print(" No valid player selected or available. Exiting.")
sys.exit(1)
print(f" Using player: {media_player}")
print_ascii_line('=')
else:
# NO players are available (mpv, vlc, or smplayer)
print(" [FAILED]")
print(" Error: No compatible media player ('mpv', 'vlc', or 'smplayer') found in your PATH.")
print(" Please install one of these players to proceed.")
sys.exit(1) # Exit if no player is found
# 3. Check for yt-dlp binary
sys.stdout.write(f" Checking for yt-dlp at '{YTDLP_PATH}'...")
sys.stdout.flush()
if not os.path.exists(YTDLP_PATH) or not os.access(YTDLP_PATH, os.X_OK):
print(" [FAILED]")
print(f" Error: yt-dlp not found or not executable at '{YTDLP_PATH}'.")
sys.exit(1)
print(" [OK]")
# 4. Check for ffmpeg.
sys.stdout.write(" Checking for ffmpeg...")
sys.stdout.flush()
if subprocess.run(["which", "ffmpeg"], capture_output=True).returncode != 0:
print(" [FAILED]")
print(" Error: 'ffmpeg' is not found in your system's PATH.")
sys.exit(1)
print(" [OK]")
print_ascii_line('=')
return media_player, last_save_folder
def _process_link_workflow(youtube_link, media_player, last_save_folder, ontop_choice='n', monitor_choice=None):
"""
Handles the download, play, and optional save/delete for a single video link.
Returns True if successfully downloaded/played/handled, False otherwise.
"""
final_video_file = None
suggested_filename_for_save = None
try:
print_ascii_header(f"PROCESSING: {youtube_link}", '=')
download_attempt_successful = False
# --- Video Download Attempt (with optional cookie retry) ---
print("\n Attempting to download video with best video and separate audio streams...")
success, _, suggested_filename_for_save = run_yt_dlp(youtube_link)
if success:
download_attempt_successful = True
print("\n Video and audio downloaded and merged successfully.")
else:
print("\n Initial video download failed.")
# --- Cookie Retry Logic ---
retry_choice = input(
"\n The download failed. This can happen if the video is private or age-restricted. "
"Do you want to try again with browser cookies? (y/n): "
).lower()
if retry_choice == 'y':
cookie_method_choice = input(
" How do you want to provide cookies?\n"
" 1. From a browser (e.g., firefox, chrome)\n"
" 2. From a cookies.txt file\n"
" Enter 1 or 2: "
).strip()
cookie_option_value = None
is_browser = False
if cookie_method_choice == '1':
browser_options = {
1: "firefox", 2: "chrome", 3: "chromium",
4: "brave", 5: "edge", 6: "opera",
7: "safari", 8: "vivaldi", 9: "librewolf"
}
print("\n Select a browser for cookies:")
for num, browser in browser_options.items():
print(f" {num}. {browser.capitalize()}")
browser_selection_input = input(" Enter the number of your preferred browser: ").strip()
try:
browser_selection_num = int(browser_selection_input)
if browser_selection_num in browser_options:
browser_name = browser_options[browser_selection_num]
cookie_option_value = browser_name
is_browser = True
print(f"\n Attempting to download video using cookies from {browser_name}...")
else:
print(" Invalid browser number. Falling back to cookies.txt file option.")
cookie_method_choice = '2'
except ValueError:
print(" Invalid input. Please enter a number. Falling back to cookies.txt file option.")
cookie_method_choice = '2'
if cookie_method_choice == '2' or (cookie_method_choice == '1' and not is_browser):
cookies_file_path = input(
" Please enter the path to the cookies file "
"(e.g., ~/.config/yt-dlp/cookies.txt or cookies.txt in current directory): "
).strip()
if cookies_file_path:
cookie_option_value = cookies_file_path
is_browser = False
print("\n Attempting to download video with cookies from file...")
else:
print(" No cookies file path provided. Cannot retry with cookies.")
if cookie_option_value:
success_retry, _, suggested_filename_for_save = run_yt_dlp(youtube_link, cookie_option_value, is_browser)
if success_retry:
download_attempt_successful = True
print("\n Video and audio downloaded and merged successfully with cookies.")
else:
print(" Video download failed even with cookies.")
else:
print(" No valid cookie option provided. Cannot retry with cookies.")
else:
print(" Not retrying. Returning to main menu.")
if not download_attempt_successful:
print("\n Failed to download video. Returning to main menu.")
return False
# --- Find Downloaded File ---
print_ascii_header("LOCATING VIDEO", '-')
sys.stdout.write(" Searching for downloaded video file...")
sys.stdout.flush()
downloaded_files = glob.glob(f"{OUTPUT_BASENAME}.*")
for f in downloaded_files:
if f.startswith(OUTPUT_BASENAME) and (f.endswith(".mp4") or f.endswith(".webm") or f.endswith(".mkv")):
final_video_file = f
break
if not final_video_file:
print(" [NOT FOUND]")
print(f" Error: Could not find a video file matching '{OUTPUT_BASENAME}.*' after download and merge.")
return False
print(" [FOUND]")
print(f" Identified downloaded video file: {final_video_file}")
print_ascii_line('=')
# --- Play Test Sound before Video ---
play_test_sound()
# --- Play Video ---
print_ascii_header("PLAYING VIDEO", '-')
player_command = [media_player, final_video_file]
if media_player == "mpv":
# Base command for mpv: fullscreen and ignore aspect ratio
player_command = [media_player, '--fs', '--no-keepaspect', final_video_file]
if ontop_choice == 'y':
# Insert --ontop flag after the player name
player_command.insert(1, '--ontop')
# NEW: Add monitor selection if provided
if monitor_choice is not None:
player_command.insert(1, f'--screen={monitor_choice}')
print(f" MPV will attempt to play on monitor index: {monitor_choice}")
elif media_player == "vlc":
# VLC fullscreen/aspect ratio control is less common via CLI, but can be done.
# Sticking to simple play command for cross-platform robustness.
player_command = [media_player, '--fullscreen', final_video_file]
if ontop_choice == 'y':
# VLC has no direct --ontop flag from CLI, need to rely on settings or other methods.
print(" Note: VLC does not support '--ontop' from command line; ignoring option.")
if monitor_choice is not None:
# VLC has no direct --screen flag from CLI, ignoring option.
print(" Note: VLC does not support monitor selection from command line; ignoring option.")
elif media_player == "smplayer":
# SMPlayer handles its own settings for full screen/always on top
player_command = [media_player, final_video_file]
if ontop_choice == 'y':
# SMPlayer also doesn't have a standardized CLI ontop flag, relying on its internal settings.
print(" Note: SMPlayer does not support '--ontop' from command line; ignoring option.")
if monitor_choice is not None:
# SMPlayer also doesn't have a standardized CLI screen flag, ignoring option.
print(" Note: SMPlayer does not support monitor selection from command line; ignoring option.")
print(f" Playing video with {media_player}: {final_video_file}")
# Execute the detected media player
subprocess.run(player_command, check=True)
print(" Video playback finished.")
print_ascii_line('=')
# --- Save Video Logic ---
print_ascii_header("SAVE VIDEO", '-')
save_choice = input(f" Do you want to save this video (current name: '{suggested_filename_for_save if suggested_filename_for_save else final_video_file}')? (y/n): ").lower()
if save_choice == 'y':
# Ask for new filename, default to YouTube's title
new_filename = input(f" Enter the desired filename (default: '{suggested_filename_for_save}'): ").strip()
if not new_filename:
new_filename = suggested_filename_for_save
# Ensure the new filename has an extension
if not os.path.splitext(new_filename)[1]:
original_ext = os.path.splitext(final_video_file)[1]
new_filename += original_ext
# Ask for save folder
target_folder = ""
if last_save_folder and os.path.isdir(last_save_folder):
use_previous_folder = input(f" Use previous save folder '{last_save_folder}'? (y/n): ").lower()
if use_previous_folder == 'y':
target_folder = last_save_folder
else:
target_folder = input(" Enter the new save folder path: ").strip()
else:
target_folder = input(" Enter the save folder path: ").strip()
if not target_folder:
print(" No save folder specified. Video will not be saved.")
os.remove(final_video_file)
print(f" Deleted unsaved video: {final_video_file}")
return True
os.makedirs(target_folder, exist_ok=True)
destination_path = os.path.join(target_folder, new_filename)
try:
shutil.move(final_video_file, destination_path)
print(f" Video saved successfully to: {destination_path}")
# Update last save folder file
with open(LAST_SAVE_FOLDER_FILE, 'w') as f:
f.write(target_folder)
# NOTE: last_save_folder needs to be updated in the main function's scope too, but
# for now, the main loop re-reads it for the next iteration.
print(f" Last save folder updated to: {target_folder}")
except OSError as e:
print(f" Error saving video: {e}")
print(f" The video was not moved. It remains as '{final_video_file}' in the current directory.")
print_ascii_line('=')
else:
print(" Video will not be saved.")
# --- Delete Downloaded Video if not saved ---
if final_video_file and os.path.exists(final_video_file):
try:
os.remove(final_video_file)
print(f" Deleted unsaved video: {final_video_file}")
except Exception as e:
print(f" Warning: Could not delete {final_video_file}. Reason: {e}")
print_ascii_line('=')
return True # Indicate successful handling of the video flow
except FileNotFoundError as e:
print_ascii_header("RUNTIME ERROR", '#')
print(f" Runtime Error: A required command was not found. Detail: {e}")
print(" Please ensure all necessary applications are installed and in your system's PATH.")
if final_video_file and os.path.exists(final_video_file):
try: os.remove(final_video_file)
except Exception: pass
return False
except subprocess.CalledProcessError as e:
print_ascii_header("EXECUTION ERROR", '#')
print(f" Command Execution Error (Exit code: {e.returncode}):")
print(f" Command: {' '.join(e.cmd)}")
if e.stdout: print(f" Output: {e.stdout.strip()}")
print(" Please review the error messages above for details on what went wrong.")
if final_video_file and os.path.exists(final_video_file):
try: os.remove(final_video_file)
except Exception: pass
return False
except Exception as e:
print_ascii_header("UNEXPECTED ERROR", '#')
print(f" An unexpected error occurred: {e}")
if final_video_file and os.path.exists(final_video_file):
try: os.remove(final_video_file)
except Exception: pass
return False
finally:
# Aggressive cleanup of any residual 'downloaded_video.*' files that might be left over
print_ascii_header("FINAL CLEANUP OF TEMP FILES", '-')
print(" Checking for residual temporary files...")
for f in glob.glob(f"{OUTPUT_BASENAME}.*"):
if os.path.exists(f):
try:
os.remove(f)
print(f" Cleaned up residual temporary file: {f}")
except Exception as e:
print(f" Warning: Could not clean up residual temporary file {f}. Reason: {e}")
if os.path.exists(TEST_SOUND_FILE):
try:
os.remove(TEST_SOUND_FILE)
print(f" Cleaned up temporary sound file: {TEST_SOUND_FILE}")
except Exception as e:
print(f" Warning: Could not clean up temporary sound file {TEST_SOUND_FILE}. Reason: {e}")
print_ascii_line('=')
def process_new_video(media_player, last_save_folder):
"""
Handles the flow for a user-provided new video link.
"""
print_ascii_header("NEW VIDEO LINK", '-')
youtube_link = input(" Please enter the YouTube video link: ")
if not youtube_link:
print(" No link entered. Returning to main menu.")
return False
# 1. Ask about saving to playlist BEFORE download
playlist_choice = input(" Do you want to save this link to the playlist before playing? (y/n): ").lower()
if playlist_choice == 'y':
add_to_playlist(youtube_link)
# 2. Ask about playing AFTER playlist decision
play_choice = input(" Do you want to download and play the video now? (y/n): ").lower()
if play_choice == 'y':
# 3. Ask about Playback Options BEFORE download (if mpv)
ontop_choice = 'n' # Default to no
monitor_choice = None # Default to no monitor specified
if media_player == "mpv":
print_ascii_header("PLAYBACK OPTIONS (MPV)", '-')
# --- Always On Top Option ---
ontop_choice = input(
" Do you want the player window to be 'Always On Top'?\n"
" (This keeps the video visible above all other windows.) (y/n): "
).lower()
# --- Monitor Selection Option ---
monitor_input = input(
" Enter the **monitor number** (e.g., 0, 1) you want to play on, or press Enter to skip: "
).strip()
if monitor_input.isdigit():
monitor_choice = int(monitor_input)
elif monitor_input:
print(" Warning: Invalid monitor number entered. Skipping monitor selection.")
print_ascii_line('=')
return _process_link_workflow(youtube_link, media_player, last_save_folder, ontop_choice, monitor_choice)
else:
print(" Skipping video download and playback. Returning to main menu.")
return True # Handled successfully, but no video processed.
def process_playlist_video(media_player, last_save_folder):
"""
Handles the flow for playing videos from the playlist, including recursion for 'play next'.
Returns True if video processing was completed, False otherwise.
"""
playlist_links = get_playlist_links()
if not playlist_links:
print_ascii_header("PLAYLIST EMPTY", '-')
print(" The playlist is currently empty.")
# --- START OF NEW LOGIC ---
add_link_choice = input(
" The playlist is empty. Do you want to add a link now and play it? (y/n): "
).lower()
if add_link_choice == 'y':
youtube_link = input(" Please enter the YouTube video link: ")
if not youtube_link:
print(" No link entered. Returning to main menu.")
print_ascii_line('=')
return False
# Add the link to the playlist
add_to_playlist(youtube_link)
# Recursive call to process the newly added link (which is now the only one)
# This ensures we proceed to playback options and the download flow
print("\n Link added. Restarting playlist flow to process the video...")
return process_playlist_video(media_player, last_save_folder)
# --- END OF NEW LOGIC ---
print_ascii_line('=')
return False
# Get the top link
youtube_link = playlist_links[0]
print_ascii_header(f"PLAYLIST - TOP VIDEO ({len(playlist_links)} links remaining)", '=')
print(f" Video to play: {youtube_link}")
# 1. Ask about Playback Options BEFORE download (if mpv)
ontop_choice = 'n' # Default to no
monitor_choice = None # Default to no monitor specified
if media_player == "mpv":
print_ascii_header("PLAYBACK OPTIONS (MPV)", '-')
# --- Always On Top Option ---
ontop_choice = input(
" Do you want the player window to be 'Always On Top'?\n"
" (This keeps the video visible above all other windows.) (y/n): "
).lower()
# --- Monitor Selection Option ---
monitor_input = input(
" Enter the **monitor number** (e.g., 0, 1) you want to play on, or press Enter to skip: "
).strip()
if monitor_input.isdigit():
monitor_choice = int(monitor_input)
elif monitor_input:
print(" Warning: Invalid monitor number entered. Skipping monitor selection.")
print_ascii_line('=')
# Run the core download/play/save workflow
video_processed = _process_link_workflow(youtube_link, media_player, last_save_folder, ontop_choice, monitor_choice)
# After playback, handle the list cleanup
if video_processed:
# Ask to delete the link from the list
delete_choice = input(
f" Do you want to delete the played link from the playlist? (y/n): "
).lower()
if delete_choice == 'y':
# This call removes the link that was just played (the first one)
removed = remove_first_from_playlist()
if removed:
playlist_links = get_playlist_links() # Re-read for the next prompt
# Ask to play the next link
if playlist_links:
next_choice = input(f" There are {len(playlist_links)} links remaining. Do you want to play the next one? (y/n): ").lower()
if next_choice == 'y':
# Recursive call to play the next one (which is now at the top)
return process_playlist_video(media_player, last_save_folder)
else:
print(" Playlist is now empty.")
return video_processed
def download_and_play_video():
"""
The main control function that handles the menu loop.
"""
try:
# 1. Perform pre-flight checks and get the necessary system info
# This function handles the media player check and reads the last save folder
media_player, last_save_folder = _get_player_and_folder()
except SystemExit:
# Catch SystemExit from checks if they fail
return False
# Main menu loop
while True:
print_ascii_header("MAIN MENU", '=')
print(" 1. Download and play a NEW YouTube video link.")
playlist_links = get_playlist_links()
num_links = len(playlist_links)
if num_links > 0:
print(f" 2. Play the top video from the PLAYLIST ({num_links} links available).")
else:
print(" 2. Play from PLAYLIST (Playlist is empty).")
print(" 3. Exit the program and clean up.")
choice = input(" Enter your choice (1, 2, 3): ").strip()
video_processed = False
if choice == '1':
video_processed = process_new_video(media_player, last_save_folder)
elif choice == '2':
# The playlist processing handles the inner loop (delete/play next)
video_processed = process_playlist_video(media_player, last_save_folder)
elif choice == '3':
print(" Exiting. Goodbye!")
sys.exit(UNINSTALL_AUDIO_TOOLS_EXIT_CODE)
else:
print(" Invalid choice. Please enter 1, 2, or 3.")
# Update last_save_folder if a save operation was successful (for next loop iteration)
if video_processed and os.path.exists(LAST_SAVE_FOLDER_FILE):
try:
with open(LAST_SAVE_FOLDER_FILE, 'r') as f:
last_save_folder = f.read().strip()
except Exception:
pass # Ignore read errors
print("\n" + "=" * 60) # Main menu separator
# Loop continues to the start of the while True block for the next operation
# Ensure the main function is called when the script is executed.
if __name__ == "__main__":
# Clean up residual files before the main process starts
for f in glob.glob(f"{OUTPUT_BASENAME}.*"):
if os.path.exists(f):
try: os.remove(f)
except Exception: pass
if os.path.exists(TEST_SOUND_FILE):
try: os.remove(TEST_SOUND_FILE)
except Exception: pass
download_and_play_video()
EOF
echo "" # Add a newline for better readability
print_section_header "RUNNING PLAYER"
# Step 4: Run the Python script
echo " Executing Python script: $PYTHON_SCRIPT"
# The Python script will now handle the loop and exit with a specific code when the user is done.
python3 "$PYTHON_SCRIPT"
PYTHON_EXIT_CODE=$? # Capture the exit code of the Python script
echo "" # Add a newline for better readability
print_section_header "FINAL CLEANUP"
# Step 5: Clean up temporary files and potentially uninstall audio tools
echo " Cleaning up shell script's temporary files..."
# Remove the temporary Python script
if [ -f "$PYTHON_SCRIPT" ]; then
rm "$PYTHON_SCRIPT"
echo " Removed temporary Python script: $PYTHON_SCRIPT"
fi
# Remove the yt-dlp binary as requested
if [ -f "$YTDLP_BIN" ]; then
rm "$YTDLP_BIN"
echo " Removed yt-dlp binary: $YTDLP_BIN"
fi
# Condition for removing the last save folder file: only if Python script exited with 'no more videos' signal
if [ $PYTHON_EXIT_CODE -eq 5 ] && [ -f "$LAST_SAVE_FOLDER_FILE" ]; then
rm "$LAST_SAVE_FOLDER_FILE"
echo " Removed last save folder file: $LAST_SAVE_FOLDER_FILE (as requested upon exit)"
elif [ -f "$LAST_SAVE_FOLDER_FILE" ]; then
echo " Keeping last save folder file: $LAST_SAVE_FOLDER_FILE (script did not exit via 'no more videos' option)"
fi
# Check if the Python script signaled for audio tool uninstallation
# And if the flag file exists (meaning *this* script installed them)
if [ $PYTHON_EXIT_CODE -eq 5 ]; then # Only consider uninstalling if Python script signaled end
# --- NEW: Check and uninstall ffmpeg ---
if [ -f "$FFMPEG_INSTALLED_FLAG" ]; then
echo ""
read -p " This script installed 'ffmpeg'. Do you want to uninstall it now? (y/n): " uninstall_ffmpeg_confirm
if [[ "$uninstall_ffmpeg_confirm" =~ ^[Yy]$ ]]; then
echo " Attempting to uninstall ffmpeg..."
UNINSTALL_FFMPEG_CMD=""
if command -v apt &> /dev/null; then
UNINSTALL_FFMPEG_CMD="sudo apt remove -y ffmpeg"
elif command -v dnf &> /dev/null; then
UNINSTALL_FFMPEG_CMD="sudo dnf remove -y ffmpeg"
elif command -v pacman &> /dev/null; then
UNINSTALL_FFMPEG_CMD="sudo pacman -R --noconfirm ffmpeg"
else
echo " Error: No supported package manager found for uninstalling ffmpeg."
echo " Please uninstall ffmpeg manually."
fi
if [ -n "$UNINSTALL_FFMPEG_CMD" ]; then
if eval "$UNINSTALL_FFMPEG_CMD"; then
echo " ffmpeg uninstalled successfully."
rm "$FFMPEG_INSTALLED_FLAG" # Remove flag file
else
echo " Error: Failed to uninstall ffmpeg. Please check permissions."
fi
fi
else
echo " Skipping ffmpeg uninstallation."
fi
fi
# --- Existing: Check and uninstall espeak-ng and alsa-utils ---
if [ -f "$AUDIO_TOOLS_INSTALLED_FLAG" ]; then
echo ""
read -p " This script installed 'espeak-ng' and 'alsa-utils'. Do you want to uninstall them now? (y/n): " uninstall_audio_confirm
if [[ "$uninstall_audio_confirm" =~ ^[Yy]$ ]]; then
echo " Attempting to uninstall audio tools..."
UNINSTALL_AUDIO_CMD=""
if command -v apt &> /dev/null; then
UNINSTALL_AUDIO_CMD="sudo apt remove -y espeak-ng alsa-utils"
elif command -v dnf &> /dev/null; then
UNINSTALL_AUDIO_CMD="sudo dnf remove -y espeak-ng alsa-utils"
elif command -v pacman &> /dev/null; then
UNINSTALL_AUDIO_CMD="sudo pacman -R --noconfirm espeak-ng alsa-utils"
else
echo " Error: No supported package manager found for uninstalling audio tools."
echo " Please install espeak-ng and alsa-utils manually."
fi
if [ -n "$UNINSTALL_AUDIO_CMD" ]; then
if eval "$UNINSTALL_AUDIO_CMD"; then
echo " Audio tools uninstalled successfully."
rm "$AUDIO_TOOLS_INSTALLED_FLAG" # Remove flag file
else
echo " Error: Failed to uninstall audio tools. Please check permissions."
fi
fi
else
echo " Skipping audio tools uninstallation."
fi
fi
else # If Python script did not exit with code 5, do not offer uninstallation
echo " Tool uninstallation not offered as script did not exit via 'no more videos' option or encountered an error."
fi
# Report final status based on the Python script's exit code
if [ $PYTHON_EXIT_CODE -ne 0 ] && [ $PYTHON_EXIT_CODE -ne 5 ]; then
echo " Script finished with errors (exit code: $PYTHON_EXIT_CODE)."
exit $PYTHON_EXIT_CODE
else
echo " Script finished successfully."
exit 0
fi
