loader image

Reply To: How to Stop Ad Blocker Detection on Websites Like YouTube

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

#8342
thumbtak
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

 

TAKs Shack