Last active
December 1, 2025 16:49
-
-
Save phnahes/6696ec1194768293908ecc387a1b5e04 to your computer and use it in GitHub Desktop.
Download Youtube Playlist mp3 files
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| ################################################################################ | |
| # YouTube Playlist to MP3 Downloader Script | |
| # | |
| # Description: | |
| # This script automatically downloads YouTube playlists and converts | |
| # them to high-quality MP3 files. Uses yt-dlp for downloading | |
| # and audio conversion. | |
| # | |
| # Dependencies: | |
| # - yt-dlp: video download tool | |
| # - jq: command-line JSON processor | |
| # - ffmpeg: audio/video converter (used by yt-dlp) | |
| # | |
| # Author: Paulo Nahes | |
| # Date: 11/30/2025 | |
| ################################################################################ | |
| # ==================== DEFAULT SETTINGS ==================== | |
| # Default playlist URL (can be overridden with -u) | |
| DEFAULT_PLAYLIST_URL="https://www.youtube.com/watch?v=&list=xxxxxxxxxxx" | |
| # Default destination directory (can be overridden with -d) | |
| DEFAULT_DEST_DIR="musicas" | |
| # Default audio format (can be overridden with -f) | |
| DEFAULT_AUDIO_FORMAT="mp3" | |
| # Default audio quality: 0 (best) to 9 (worst) | |
| DEFAULT_AUDIO_QUALITY="0" | |
| # Default delay between downloads in seconds (can be overridden with -w) | |
| DEFAULT_DELAY="5" | |
| # Random delay (0 = disabled, 1 = enabled) | |
| RANDOM_DELAY="1" | |
| # Random delay range (minimum and maximum in seconds) | |
| RANDOM_DELAY_MIN="0" | |
| RANDOM_DELAY_MAX="10" | |
| # Filename sanitization (0 = disabled, 1 = enabled) | |
| SANITIZE_NAMES="1" | |
| # Resume/checkpoint system | |
| ENABLE_RESUME="1" | |
| # Force re-download even if file exists (0 = no, 1 = yes) | |
| FORCE_REDOWNLOAD="0" | |
| # Temporary file for URLs | |
| TEMP_URLS_FILE="urls_temp_$$.txt" | |
| # Download progress log file | |
| DOWNLOAD_LOG_FILE=".download_progress.log" | |
| # Failed downloads log file | |
| FAILED_LOG_FILE=".download_failed.log" | |
| # ==================== OUTPUT COLORS ==================== | |
| RED='\033[0;31m' | |
| GREEN='\033[0;32m' | |
| YELLOW='\033[1;33m' | |
| BLUE='\033[0;34m' | |
| NC='\033[0m' # No Color | |
| # ==================== FUNCTIONS ==================== | |
| show_help() { | |
| echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" | |
| echo -e "${GREEN}Script de Download de Playlists do YouTube em MP3${NC}" | |
| echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" | |
| echo "" | |
| echo -e "${YELLOW}DESCRIÇÃO:${NC}" | |
| echo " Este script baixa automaticamente playlists do YouTube e converte" | |
| echo " os vídeos para arquivos MP3 de alta qualidade. Cada faixa é salva" | |
| echo " com índice da playlist e título do vídeo." | |
| echo "" | |
| echo -e "${YELLOW}USO:${NC}" | |
| echo " $0 [opções]" | |
| echo "" | |
| echo -e "${YELLOW}OPÇÕES:${NC}" | |
| echo " -u URL URL da playlist do YouTube" | |
| echo " (padrão: $DEFAULT_PLAYLIST_URL)" | |
| echo "" | |
| echo " -d DIR Diretório de destino para os arquivos MP3" | |
| echo " (padrão: $DEFAULT_DEST_DIR)" | |
| echo "" | |
| echo " -f FORMAT Formato de áudio (mp3, aac, m4a, opus, vorbis, wav)" | |
| echo " (padrão: $DEFAULT_AUDIO_FORMAT)" | |
| echo "" | |
| echo " -q QUALITY Qualidade de áudio (0-9, onde 0 é a melhor)" | |
| echo " (padrão: $DEFAULT_AUDIO_QUALITY)" | |
| echo "" | |
| echo " -w SECONDS Tempo de espera entre downloads em segundos" | |
| echo " (padrão: $DEFAULT_DELAY)" | |
| echo "" | |
| echo " -r Ativa delay ALEATÓRIO entre downloads" | |
| echo " (entre $RANDOM_DELAY_MIN e $RANDOM_DELAY_MAX segundos, ignora -w)" | |
| echo "" | |
| echo " -s Desabilita sanitização de nomes de arquivos" | |
| echo " (por padrão, caracteres especiais são removidos)" | |
| echo "" | |
| echo " -c Desabilita sistema de checkpoint/resume" | |
| echo " (por padrão, downloads já feitos são pulados)" | |
| echo "" | |
| echo " -F FORÇA re-download de todas as faixas" | |
| echo " (ignora arquivos existentes e checkpoint)" | |
| echo "" | |
| echo " -h Mostra esta mensagem de ajuda" | |
| echo "" | |
| echo -e "${YELLOW}EXEMPLOS:${NC}" | |
| echo " # Usar configurações padrão" | |
| echo " $0" | |
| echo "" | |
| echo " # Baixar playlist específica para diretório customizado" | |
| echo " $0 -u \"https://www.youtube.com/playlist?list=PLxxxxxxxx\" -d \"minhas_musicas\"" | |
| echo "" | |
| echo " # Baixar com qualidade média e sem delay" | |
| echo " $0 -q 5 -w 0" | |
| echo "" | |
| echo " # Baixar em formato AAC de alta qualidade" | |
| echo " $0 -f aac -q 0" | |
| echo "" | |
| echo " # Usar delay aleatório entre downloads (0-15 segundos)" | |
| echo " $0 -r" | |
| echo "" | |
| echo " # Combinar delay aleatório com outras opções" | |
| echo " $0 -u \"URL_DA_PLAYLIST\" -d \"musicas\" -r" | |
| echo "" | |
| echo " # Retomar download interrompido (checkpoint automático)" | |
| echo " $0" | |
| echo "" | |
| echo " # Forçar re-download de tudo" | |
| echo " $0 -F" | |
| echo "" | |
| echo -e "${YELLOW}DEPENDÊNCIAS NECESSÁRIAS:${NC}" | |
| echo " - yt-dlp (instalação: brew install yt-dlp ou pip install yt-dlp)" | |
| echo " - jq (instalação: brew install jq ou apt-get install jq)" | |
| echo " - ffmpeg (instalação: brew install ffmpeg ou apt-get install ffmpeg)" | |
| echo "" | |
| echo -e "${YELLOW}NOTAS:${NC}" | |
| echo " - Os arquivos são salvos como: \"001 - Titulo da Musica.mp3\"" | |
| echo " - Caracteres especiais são removidos automaticamente (use -s para desabilitar)" | |
| echo " - Índices são formatados com zeros à esquerda (001, 002, etc.)" | |
| echo " - Sistema de checkpoint salva progresso automaticamente" | |
| echo " - Downloads já concluídos são pulados automaticamente" | |
| echo " - O script aguarda entre downloads para evitar sobrecarga" | |
| echo " - URLs inválidas ou downloads com falha são reportados" | |
| echo " - Arquivos temporários são limpos automaticamente" | |
| echo "" | |
| echo -e "${YELLOW}FALHAS COMUNS DE DOWNLOAD:${NC}" | |
| echo " - Vídeos com restrição de idade: Não podem ser baixados" | |
| echo " - Vídeos privados/deletados: Não estão mais disponíveis" | |
| echo " - Conteúdo com restrição geográfica: Bloqueado na sua região" | |
| echo " - Conteúdo premium: Requer YouTube Premium" | |
| echo " - Lives ao vivo: Podem falhar se estiverem transmitindo" | |
| echo " - Limite de requisições: Muitas requisições (use -w para delay)" | |
| echo " - Problemas de rede: Conexão instável ou timeout" | |
| echo "" | |
| echo " Falhas são registradas em: .download_failed.log" | |
| echo " Verifique este arquivo para mensagens de erro detalhadas" | |
| echo "" | |
| echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" | |
| exit 0 | |
| } | |
| log_info() { | |
| echo -e "${BLUE}[INFO]${NC} $1" | |
| } | |
| log_success() { | |
| echo -e "${GREEN}[✓]${NC} $1" | |
| } | |
| log_warning() { | |
| echo -e "${YELLOW}[⚠]${NC} $1" | |
| } | |
| log_error() { | |
| echo -e "${RED}[✗]${NC} $1" | |
| } | |
| sanitize_filename() { | |
| local filename="$1" | |
| # Convert to lowercase if desired (commented out by default) | |
| # filename=$(echo "$filename" | tr '[:upper:]' '[:lower:]') | |
| # Remove problematic special characters | |
| # Removes: / \ : * ? " < > | [ ] { } ! @ # $ % ^ & = + ` ~ ; ' | |
| filename=$(echo "$filename" | sed 's/[\/\\:*?"<>|]/ /g') | |
| filename=$(echo "$filename" | sed 's/[\[\]{}!@#$%^&=+`~;'\'']//g') | |
| # Replace parentheses with spaces | |
| filename=$(echo "$filename" | sed 's/[()]/ /g') | |
| # Remove extra underscores and replace with spaces | |
| filename=$(echo "$filename" | sed 's/_/ /g') | |
| # Remove commas and semicolons | |
| filename=$(echo "$filename" | sed 's/[,;]//g') | |
| # Replace multiple spaces with a single space | |
| filename=$(echo "$filename" | sed 's/ */ /g') | |
| # Replace multiple hyphens with a single hyphen | |
| filename=$(echo "$filename" | sed 's/--*/-/g') | |
| # Remove leading and trailing spaces | |
| filename=$(echo "$filename" | sed 's/^ *//;s/ *$//') | |
| # Remove leading and trailing hyphens | |
| filename=$(echo "$filename" | sed 's/^-*//;s/-*$//') | |
| # Remove leading dots (but keep at the end for extensions) | |
| filename=$(echo "$filename" | sed 's/^\.*//g') | |
| # Limit filename length (maximum 150 characters to avoid problems) | |
| if [ ${#filename} -gt 150 ]; then | |
| filename="${filename:0:150}" | |
| # Remove spaces/hyphens that may remain at the end after truncation | |
| filename=$(echo "$filename" | sed 's/[ -]*$//') | |
| fi | |
| # If the name is empty, use a default name | |
| if [ -z "$filename" ]; then | |
| filename="track" | |
| fi | |
| echo "$filename" | |
| } | |
| check_dependencies() { | |
| log_info "Checking required dependencies..." | |
| local missing_deps=0 | |
| # Check yt-dlp | |
| if ! command -v yt-dlp &> /dev/null; then | |
| log_error "yt-dlp not found!" | |
| echo " Install with: brew install yt-dlp OR pip install yt-dlp" | |
| missing_deps=1 | |
| else | |
| log_success "yt-dlp found: $(command -v yt-dlp)" | |
| fi | |
| # Check jq | |
| if ! command -v jq &> /dev/null; then | |
| log_error "jq not found!" | |
| echo " Install with: brew install jq OR apt-get install jq" | |
| missing_deps=1 | |
| else | |
| log_success "jq found: $(command -v jq)" | |
| fi | |
| # Check ffmpeg | |
| if ! command -v ffmpeg &> /dev/null; then | |
| log_error "ffmpeg not found!" | |
| echo " Install with: brew install ffmpeg OR apt-get install ffmpeg" | |
| missing_deps=1 | |
| else | |
| log_success "ffmpeg found: $(command -v ffmpeg)" | |
| fi | |
| if [ $missing_deps -eq 1 ]; then | |
| log_error "Missing dependencies. Please install them before continuing." | |
| exit 1 | |
| fi | |
| log_success "All dependencies are installed!" | |
| echo "" | |
| } | |
| validate_url() { | |
| local url="$1" | |
| # Check if URL contains youtube.com or youtu.be | |
| if [[ ! "$url" =~ youtube\.com|youtu\.be ]]; then | |
| log_error "Invalid URL. Must be a YouTube URL." | |
| return 1 | |
| fi | |
| # Check if contains 'list=' (playlist indicator) | |
| if [[ ! "$url" =~ list= ]]; then | |
| log_warning "The URL may not be a playlist (missing 'list=' in URL)." | |
| log_warning "Script will continue, but may process only one video." | |
| fi | |
| return 0 | |
| } | |
| create_directory() { | |
| local dir="$1" | |
| if [ -d "$dir" ]; then | |
| log_info "Directory already exists: $dir" | |
| else | |
| log_info "Creating directory: $dir" | |
| if mkdir -p "$dir"; then | |
| log_success "Directory created successfully!" | |
| else | |
| log_error "Failed to create directory: $dir" | |
| exit 1 | |
| fi | |
| fi | |
| # Check write permissions | |
| if [ ! -w "$dir" ]; then | |
| log_error "No write permission for directory: $dir" | |
| exit 1 | |
| fi | |
| } | |
| cleanup() { | |
| if [ -f "$TEMP_URLS_FILE" ]; then | |
| log_info "Removing temporary file..." | |
| rm -f "$TEMP_URLS_FILE" | |
| fi | |
| } | |
| is_track_downloaded() { | |
| local track_index="$1" | |
| local log_file="$2" | |
| # Check in log file | |
| if [ -f "$log_file" ] && grep -q "^${track_index}|" "$log_file" 2>/dev/null; then | |
| return 0 # Already downloaded | |
| fi | |
| return 1 # Not downloaded | |
| } | |
| mark_track_downloaded() { | |
| local track_index="$1" | |
| local title="$2" | |
| local log_file="$3" | |
| # Add to log: INDEX|TITLE|TIMESTAMP | |
| echo "${track_index}|${title}|$(date '+%Y-%m-%d %H:%M:%S')" >> "$log_file" | |
| } | |
| mark_track_failed() { | |
| local track_index="$1" | |
| local url="$2" | |
| local error_msg="$3" | |
| local log_file="$4" | |
| # Add to failed log: INDEX|URL|ERROR|TIMESTAMP | |
| echo "${track_index}|${url}|${error_msg}|$(date '+%Y-%m-%d %H:%M:%S')" >> "$log_file" | |
| } | |
| check_file_exists() { | |
| local dest_dir="$1" | |
| local track_index="$2" | |
| local format="$3" | |
| # Search for file starting with track index | |
| local found_file=$(find "$dest_dir" -name "${track_index} - *.$format" -type f 2>/dev/null | head -n 1) | |
| if [ -n "$found_file" ]; then | |
| echo "$found_file" | |
| return 0 | |
| fi | |
| return 1 | |
| } | |
| load_checkpoint_info() { | |
| local log_file="$1" | |
| if [ -f "$log_file" ]; then | |
| local completed_count=$(wc -l < "$log_file" | tr -d ' ') | |
| echo "$completed_count" | |
| else | |
| echo "0" | |
| fi | |
| } | |
| # Ensure cleanup on interruption | |
| trap cleanup EXIT INT TERM | |
| # ==================== ARGUMENT PROCESSING ==================== | |
| PLAYLIST_URL="$DEFAULT_PLAYLIST_URL" | |
| DEST_DIR="$DEFAULT_DEST_DIR" | |
| AUDIO_FORMAT="$DEFAULT_AUDIO_FORMAT" | |
| AUDIO_QUALITY="$DEFAULT_AUDIO_QUALITY" | |
| DELAY="$DEFAULT_DELAY" | |
| USE_RANDOM_DELAY="$RANDOM_DELAY" | |
| USE_SANITIZE="$SANITIZE_NAMES" | |
| USE_RESUME="$ENABLE_RESUME" | |
| FORCE_DOWNLOAD="$FORCE_REDOWNLOAD" | |
| while getopts "u:d:f:q:w:rscFh" opt; do | |
| case $opt in | |
| u) | |
| PLAYLIST_URL="$OPTARG" | |
| ;; | |
| d) | |
| DEST_DIR="$OPTARG" | |
| ;; | |
| f) | |
| AUDIO_FORMAT="$OPTARG" | |
| ;; | |
| q) | |
| AUDIO_QUALITY="$OPTARG" | |
| ;; | |
| w) | |
| DELAY="$OPTARG" | |
| ;; | |
| r) | |
| USE_RANDOM_DELAY="1" | |
| ;; | |
| s) | |
| USE_SANITIZE="0" | |
| ;; | |
| c) | |
| USE_RESUME="0" | |
| ;; | |
| F) | |
| FORCE_DOWNLOAD="1" | |
| ;; | |
| h) | |
| show_help | |
| ;; | |
| \?) | |
| log_error "Invalid option: -$OPTARG" | |
| echo "Use -h for help" | |
| exit 1 | |
| ;; | |
| :) | |
| log_error "Option -$OPTARG requires an argument" | |
| echo "Use -h for help" | |
| exit 1 | |
| ;; | |
| esac | |
| done | |
| # ==================== MAIN SCRIPT ==================== | |
| echo "" | |
| log_info "═══════════════════════════════════════════════════════════" | |
| log_info " Starting YouTube Playlist Download" | |
| log_info "═══════════════════════════════════════════════════════════" | |
| echo "" | |
| # Check dependencies | |
| check_dependencies | |
| # Validate URL | |
| log_info "Validating playlist URL..." | |
| if ! validate_url "$PLAYLIST_URL"; then | |
| exit 1 | |
| fi | |
| log_success "URL validated!" | |
| echo "" | |
| # Create/check destination directory | |
| log_info "Setting up destination directory..." | |
| create_directory "$DEST_DIR" | |
| echo "" | |
| # Configure checkpoint log file | |
| CHECKPOINT_LOG="$DEST_DIR/$DOWNLOAD_LOG_FILE" | |
| FAILED_LOG="$DEST_DIR/$FAILED_LOG_FILE" | |
| # Check if previous checkpoint exists | |
| PREVIOUSLY_DOWNLOADED=0 | |
| if [ "$USE_RESUME" -eq 1 ] && [ "$FORCE_DOWNLOAD" -eq 0 ]; then | |
| PREVIOUSLY_DOWNLOADED=$(load_checkpoint_info "$CHECKPOINT_LOG") | |
| if [ "$PREVIOUSLY_DOWNLOADED" -gt 0 ]; then | |
| log_info "Checkpoint found: $PREVIOUSLY_DOWNLOADED track(s) already downloaded" | |
| fi | |
| fi | |
| # If force re-download, clear checkpoint | |
| if [ "$FORCE_DOWNLOAD" -eq 1 ]; then | |
| if [ -f "$CHECKPOINT_LOG" ]; then | |
| log_warning "FORCE mode enabled: removing previous checkpoint..." | |
| rm -f "$CHECKPOINT_LOG" | |
| fi | |
| fi | |
| # Display settings | |
| log_info "Download settings:" | |
| echo " Playlist URL: $PLAYLIST_URL" | |
| echo " Directory: $DEST_DIR" | |
| echo " Format: $AUDIO_FORMAT" | |
| echo " Quality: $AUDIO_QUALITY (0=best, 9=worst)" | |
| if [ "$USE_RANDOM_DELAY" -eq 1 ]; then | |
| echo " Delay between downloads: RANDOM (${RANDOM_DELAY_MIN}-${RANDOM_DELAY_MAX}s)" | |
| else | |
| echo " Delay between downloads: ${DELAY}s" | |
| fi | |
| if [ "$USE_SANITIZE" -eq 1 ]; then | |
| echo " Name sanitization: ENABLED" | |
| else | |
| echo " Name sanitization: DISABLED" | |
| fi | |
| if [ "$USE_RESUME" -eq 1 ] && [ "$FORCE_DOWNLOAD" -eq 0 ]; then | |
| echo " Resume system: ENABLED" | |
| else | |
| echo " Resume system: DISABLED" | |
| fi | |
| if [ "$FORCE_DOWNLOAD" -eq 1 ]; then | |
| echo " Force mode: ENABLED (re-download everything)" | |
| fi | |
| echo "" | |
| # Extract playlist URLs list | |
| log_info "Extracting playlist URLs..." | |
| if ! yt-dlp --flat-playlist -J "$PLAYLIST_URL" 2>/dev/null | jq -r '.entries[] | .url' > "$TEMP_URLS_FILE"; then | |
| log_error "Failed to extract playlist URLs. Check the URL." | |
| exit 1 | |
| fi | |
| # Check if there are URLs | |
| if [ ! -s "$TEMP_URLS_FILE" ]; then | |
| log_error "No URLs found in playlist." | |
| exit 1 | |
| fi | |
| TOTAL_TRACKS=$(wc -l < "$TEMP_URLS_FILE" | tr -d ' ') | |
| log_success "Found $TOTAL_TRACKS tracks in playlist!" | |
| echo "" | |
| # Process each track | |
| CURRENT_TRACK=0 | |
| SUCCESSFUL_DOWNLOADS=0 | |
| FAILED_DOWNLOADS=0 | |
| SKIPPED_DOWNLOADS=0 | |
| while IFS= read -r URL; do | |
| CURRENT_TRACK=$((CURRENT_TRACK + 1)) | |
| echo "" | |
| log_info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" | |
| log_info "Processing track $CURRENT_TRACK of $TOTAL_TRACKS" | |
| log_info "URL: $URL" | |
| log_info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" | |
| # Format index with leading zeros (001, 002, etc.) | |
| TRACK_INDEX=$(printf "%03d" $CURRENT_TRACK) | |
| # Check if track was already downloaded (checkpoint) | |
| SKIP_DOWNLOAD=0 | |
| if [ "$USE_RESUME" -eq 1 ] && [ "$FORCE_DOWNLOAD" -eq 0 ]; then | |
| # Check in checkpoint log | |
| if is_track_downloaded "$TRACK_INDEX" "$CHECKPOINT_LOG"; then | |
| log_warning "Track already downloaded (checkpoint). Skipping..." | |
| SKIPPED_DOWNLOADS=$((SKIPPED_DOWNLOADS + 1)) | |
| SKIP_DOWNLOAD=1 | |
| else | |
| # Check if file exists in directory | |
| EXISTING_FILE=$(check_file_exists "$DEST_DIR" "$TRACK_INDEX" "$AUDIO_FORMAT") | |
| if [ -n "$EXISTING_FILE" ]; then | |
| log_warning "File already exists: $(basename "$EXISTING_FILE")" | |
| log_info "Updating checkpoint..." | |
| # Add to checkpoint | |
| EXISTING_TITLE=$(basename "$EXISTING_FILE" | sed "s/${TRACK_INDEX} - //;s/\.$AUDIO_FORMAT$//") | |
| mark_track_downloaded "$TRACK_INDEX" "$EXISTING_TITLE" "$CHECKPOINT_LOG" | |
| SKIPPED_DOWNLOADS=$((SKIPPED_DOWNLOADS + 1)) | |
| SKIP_DOWNLOAD=1 | |
| fi | |
| fi | |
| fi | |
| # If should skip, continue to next track | |
| if [ "$SKIP_DOWNLOAD" -eq 1 ]; then | |
| continue | |
| fi | |
| # Execute download | |
| DOWNLOAD_SUCCESS=0 | |
| TRACK_TITLE="" | |
| ERROR_LOG_FILE="$DEST_DIR/.error_${TRACK_INDEX}.log" | |
| if [ "$USE_SANITIZE" -eq 1 ]; then | |
| # Download with temporary template and rename later | |
| TEMP_TEMPLATE="$DEST_DIR/temp_${CURRENT_TRACK}_%(title)s.%(ext)s" | |
| if yt-dlp -x --audio-format "$AUDIO_FORMAT" --audio-quality "$AUDIO_QUALITY" \ | |
| -o "$TEMP_TEMPLATE" "$URL" 2>"$ERROR_LOG_FILE"; then | |
| # Find downloaded file | |
| DOWNLOADED_FILE=$(find "$DEST_DIR" -name "temp_${CURRENT_TRACK}_*.$AUDIO_FORMAT" -type f | head -n 1) | |
| if [ -n "$DOWNLOADED_FILE" ]; then | |
| # Extract title from filename | |
| FILENAME=$(basename "$DOWNLOADED_FILE") | |
| TITLE=$(echo "$FILENAME" | sed "s/temp_${CURRENT_TRACK}_//;s/\.$AUDIO_FORMAT$//") | |
| # Sanitize title | |
| CLEAN_TITLE=$(sanitize_filename "$TITLE") | |
| # New filename | |
| NEW_FILENAME="${TRACK_INDEX} - ${CLEAN_TITLE}.${AUDIO_FORMAT}" | |
| NEW_FILEPATH="$DEST_DIR/$NEW_FILENAME" | |
| # Rename file | |
| mv "$DOWNLOADED_FILE" "$NEW_FILEPATH" | |
| log_success "Download completed: $NEW_FILENAME" | |
| SUCCESSFUL_DOWNLOADS=$((SUCCESSFUL_DOWNLOADS + 1)) | |
| DOWNLOAD_SUCCESS=1 | |
| TRACK_TITLE="$CLEAN_TITLE" | |
| # Remove error log if successful | |
| rm -f "$ERROR_LOG_FILE" | |
| else | |
| log_error "Downloaded file not found." | |
| FAILED_DOWNLOADS=$((FAILED_DOWNLOADS + 1)) | |
| # Log failure | |
| if [ -f "$ERROR_LOG_FILE" ]; then | |
| ERROR_MSG=$(tail -n 1 "$ERROR_LOG_FILE" | tr -d '\n') | |
| log_warning "Error: $ERROR_MSG" | |
| mark_track_failed "$TRACK_INDEX" "$URL" "$ERROR_MSG" "$FAILED_LOG" | |
| fi | |
| fi | |
| else | |
| FAILED_DOWNLOADS=$((FAILED_DOWNLOADS + 1)) | |
| log_error "Failed to download this track." | |
| # Read and display error | |
| if [ -f "$ERROR_LOG_FILE" ]; then | |
| ERROR_MSG=$(grep -i "error\|ERROR" "$ERROR_LOG_FILE" | tail -n 1 | tr -d '\n') | |
| if [ -z "$ERROR_MSG" ]; then | |
| ERROR_MSG=$(tail -n 1 "$ERROR_LOG_FILE" | tr -d '\n') | |
| fi | |
| log_warning "Reason: ${ERROR_MSG:0:200}" | |
| mark_track_failed "$TRACK_INDEX" "$URL" "$ERROR_MSG" "$FAILED_LOG" | |
| else | |
| mark_track_failed "$TRACK_INDEX" "$URL" "Unknown error" "$FAILED_LOG" | |
| fi | |
| fi | |
| # Clean up error log | |
| rm -f "$ERROR_LOG_FILE" | |
| else | |
| # Download without sanitization | |
| OUTPUT_TEMPLATE="$DEST_DIR/${TRACK_INDEX} - %(title)s.%(ext)s" | |
| if yt-dlp -x --audio-format "$AUDIO_FORMAT" --audio-quality "$AUDIO_QUALITY" \ | |
| -o "$OUTPUT_TEMPLATE" "$URL" 2>"$ERROR_LOG_FILE"; then | |
| SUCCESSFUL_DOWNLOADS=$((SUCCESSFUL_DOWNLOADS + 1)) | |
| log_success "Download completed successfully!" | |
| DOWNLOAD_SUCCESS=1 | |
| # Try to extract title from created file | |
| CREATED_FILE=$(find "$DEST_DIR" -name "${TRACK_INDEX} - *.$AUDIO_FORMAT" -type f | head -n 1) | |
| if [ -n "$CREATED_FILE" ]; then | |
| TRACK_TITLE=$(basename "$CREATED_FILE" | sed "s/${TRACK_INDEX} - //;s/\.$AUDIO_FORMAT$//") | |
| fi | |
| # Remove error log if successful | |
| rm -f "$ERROR_LOG_FILE" | |
| else | |
| FAILED_DOWNLOADS=$((FAILED_DOWNLOADS + 1)) | |
| log_error "Failed to download this track." | |
| # Read and display error | |
| if [ -f "$ERROR_LOG_FILE" ]; then | |
| ERROR_MSG=$(grep -i "error\|ERROR" "$ERROR_LOG_FILE" | tail -n 1 | tr -d '\n') | |
| if [ -z "$ERROR_MSG" ]; then | |
| ERROR_MSG=$(tail -n 1 "$ERROR_LOG_FILE" | tr -d '\n') | |
| fi | |
| log_warning "Reason: ${ERROR_MSG:0:200}" | |
| mark_track_failed "$TRACK_INDEX" "$URL" "$ERROR_MSG" "$FAILED_LOG" | |
| else | |
| mark_track_failed "$TRACK_INDEX" "$URL" "Unknown error" "$FAILED_LOG" | |
| fi | |
| fi | |
| # Clean up error log | |
| rm -f "$ERROR_LOG_FILE" | |
| fi | |
| # Register in checkpoint if download successful | |
| if [ "$DOWNLOAD_SUCCESS" -eq 1 ] && [ "$USE_RESUME" -eq 1 ]; then | |
| if [ -z "$TRACK_TITLE" ]; then | |
| TRACK_TITLE="Unknown Track" | |
| fi | |
| mark_track_downloaded "$TRACK_INDEX" "$TRACK_TITLE" "$CHECKPOINT_LOG" | |
| log_info "Checkpoint updated." | |
| fi | |
| # Delay between downloads (except for the last one) | |
| if [ $CURRENT_TRACK -lt $TOTAL_TRACKS ]; then | |
| if [ "$USE_RANDOM_DELAY" -eq 1 ]; then | |
| # Generate random number between RANDOM_DELAY_MIN and RANDOM_DELAY_MAX | |
| CURRENT_DELAY=$((RANDOM % (RANDOM_DELAY_MAX - RANDOM_DELAY_MIN + 1) + RANDOM_DELAY_MIN)) | |
| log_info "Waiting ${CURRENT_DELAY}s (random) before next track..." | |
| sleep "$CURRENT_DELAY" | |
| elif [ "$DELAY" -gt 0 ]; then | |
| log_info "Waiting ${DELAY}s before next track..." | |
| sleep "$DELAY" | |
| fi | |
| fi | |
| done < "$TEMP_URLS_FILE" | |
| # Final summary | |
| echo "" | |
| echo "" | |
| log_info "═══════════════════════════════════════════════════════════" | |
| log_info " DOWNLOAD SUMMARY" | |
| log_info "═══════════════════════════════════════════════════════════" | |
| log_success "Successful downloads: $SUCCESSFUL_DOWNLOADS" | |
| if [ $SKIPPED_DOWNLOADS -gt 0 ]; then | |
| log_warning "Skipped tracks (already exist): $SKIPPED_DOWNLOADS" | |
| fi | |
| if [ $FAILED_DOWNLOADS -gt 0 ]; then | |
| log_error "Failed downloads: $FAILED_DOWNLOADS" | |
| fi | |
| log_info "Total tracks: $TOTAL_TRACKS" | |
| log_info "Destination directory: $(cd "$DEST_DIR" && pwd)" | |
| if [ "$USE_RESUME" -eq 1 ]; then | |
| log_info "Checkpoint saved at: $CHECKPOINT_LOG" | |
| fi | |
| if [ $FAILED_DOWNLOADS -gt 0 ] && [ -f "$FAILED_LOG" ]; then | |
| log_info "Failed tracks log: $FAILED_LOG" | |
| fi | |
| log_info "═══════════════════════════════════════════════════════════" | |
| echo "" | |
| TOTAL_COMPLETED=$((SUCCESSFUL_DOWNLOADS + SKIPPED_DOWNLOADS)) | |
| if [ $TOTAL_COMPLETED -eq $TOTAL_TRACKS ]; then | |
| log_success "All tracks have been processed! 🎵" | |
| elif [ $SUCCESSFUL_DOWNLOADS -gt 0 ]; then | |
| log_success "Download completed with $SUCCESSFUL_DOWNLOADS new track(s)! 🎵" | |
| if [ $FAILED_DOWNLOADS -gt 0 ]; then | |
| log_warning "Run again to retry failed tracks." | |
| fi | |
| else | |
| if [ $SKIPPED_DOWNLOADS -eq $TOTAL_TRACKS ]; then | |
| log_success "All tracks were already downloaded previously! 🎵" | |
| else | |
| log_error "No new downloads completed successfully." | |
| if [ $FAILED_DOWNLOADS -gt 0 ]; then | |
| log_warning "Run again to retry failed tracks." | |
| fi | |
| exit 1 | |
| fi | |
| fi |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment