Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/bin/bash
- SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
- log_info() {
- echo -e "\e[36m[INFO] $1\e[0m"
- }
- log_success() {
- echo -e "\e[32m[SUCCESS] $1\e[0m"
- }
- log_warning() {
- echo -e "\e[33m[WARNING] $1\e[0m"
- }
- log_error() {
- echo -e "\e[31m[ERROR] $1\e[0m"
- }
- log_section() {
- echo ""
- echo -e "\e[35m═══════════════════════════════════════════════════════════════\e[0m"
- echo -e "\e[35m$1\e[0m"
- echo -e "\e[35m═══════════════════════════════════════════════════════════════\e[0m"
- }
- # Environment Checking
- if [ ! -f "$SCRIPT_DIR/.songs-env" ]; then
- log_error "File Not Found at $SCRIPT_DIR/.songs-env"
- echo -e "Please Create one and Add This Content:"
- echo -e ""
- echo -e "\e[33mexport BASE_FOLDER=\"/songs/All Songs\""
- echo -e "export DOWNLOADER_PATH=\"/songs/Binary/downloader\""
- echo -e "export RECORD_FILE_NAME=\".downloaded_videos.txt\""
- echo -e "export PLAYLIST_M3U_FOLDER=\"/songs/Playlists\""
- echo -e "export MUSIC_MOUNT_PATH=\"/music\""
- echo -e "declare -a PLAYLISTS=(\nPLAYLIST-1-URL\nPLAYLIST-2-URL\nPLAYLIST-3-URL\n)\e[0m"
- exit 1
- fi
- source "$SCRIPT_DIR/.songs-env"
- # Set default music mount path if not defined
- if [ -z "$MUSIC_MOUNT_PATH" ]; then
- MUSIC_MOUNT_PATH="/music"
- log_warning "MUSIC_MOUNT_PATH not defined in .songs-env, using default: $MUSIC_MOUNT_PATH"
- fi
- # Set default parallel limit if not defined
- if [ -z "$PARALLEL_LIMIT" ]; then
- PARALLEL_LIMIT=4
- log_warning "PARALLEL_LIMIT not defined in .songs-env, using default: $PARALLEL_LIMIT"
- fi
- # Set default playlist M3U folder if not defined
- if [ -z "$PLAYLIST_M3U_FOLDER" ]; then
- PLAYLIST_M3U_FOLDER="/data/songs/Playlists"
- log_warning "PLAYLIST_M3U_FOLDER not defined in .songs-env, using default: $PLAYLIST_M3U_FOLDER"
- fi
- JOB_COUNT=0
- wait_for_jobs() {
- while [ $(jobs -r | wc -l) -ge $PARALLEL_LIMIT ]; do
- sleep 1
- done
- }
- GLOBAL_RECORD_FILE="$BASE_FOLDER/$RECORD_FILE_NAME"
- if [ -z "$RECORD_FILE_NAME" ]; then
- RECORD_FILE_NAME=".downloaded_videos.txt"
- log_warning "RECORD_FILE_NAME not defined in .songs-env, using default: $RECORD_FILE_NAME"
- fi
- if [ -z "$TMUX" ] && [ "$1" != "--inside-tmux" ]; then
- log_info "Starting download script in a tmux session..."
- if ! command -v tmux &> /dev/null; then
- log_error "tmux is not installed. Please install it with: apt-get install tmux"
- exit 1
- fi
- tmux new-session -d -s songs-download "$0 --inside-tmux"
- log_success "Download process started in tmux session"
- log_info "To view progress, run: tmux attach -t songs-download"
- exit 0
- fi
- if ! command -v "$DOWNLOADER_PATH" &> /dev/null; then
- log_error "downloader is not installed or not in the specified location. Please verify the binary location."
- exit 1
- fi
- log_info "Updating downloader..."
- "$DOWNLOADER_PATH" -U
- if [ $? -ne 0 ]; then
- log_error "Failed to update the downloader. Please check for errors."
- exit 1
- fi
- log_success "Downloader updated successfully"
- get_video_id() {
- local URL="$1"
- local VIDEO_ID=""
- if [[ "$URL" == *"youtu.be/"* ]]; then
- VIDEO_ID=$(echo "$URL" | sed -E 's|.*youtu\.be/([^?&]+).*|\1|')
- elif [[ "$URL" == *"youtube.com/watch"* ]]; then
- VIDEO_ID=$(echo "$URL" | sed -E 's|.*[?&]v=([^&]+).*|\1|')
- fi
- echo "$VIDEO_ID"
- }
- song_exists() {
- local VIDEO_ID="$1"
- # Search for files with the video ID in their filename
- if find "$BASE_FOLDER" -type f -name "*${VIDEO_ID}.mp3" 2>/dev/null | grep -q .; then
- log_info "Song with ID $VIDEO_ID already exists"
- if [[ -f "$GLOBAL_RECORD_FILE" ]]; then
- if ! grep -q "^$VIDEO_ID$" "$GLOBAL_RECORD_FILE"; then
- echo "$VIDEO_ID" >> "$GLOBAL_RECORD_FILE"
- fi
- fi
- return 0
- fi
- return 1
- }
- find_song_by_id() {
- local VIDEO_ID="$1"
- # Find the song file with the given video ID
- local found_file=$(find "$BASE_FOLDER" -type f -name "*${VIDEO_ID}.mp3" 2>/dev/null | head -n 1)
- echo "$found_file"
- }
- record_downloaded_song() {
- local VIDEO_ID="$1"
- if [[ -n "$VIDEO_ID" ]]; then
- mkdir -p "$BASE_FOLDER"
- echo "$VIDEO_ID" >> "$GLOBAL_RECORD_FILE"
- log_success "Recorded downloaded song ID: $VIDEO_ID"
- fi
- }
- update_song_metadata() {
- local SONG_FILE="$1"
- local ALBUM_NAME="$2"
- local TRACK_NUMBER="$3"
- if ! command -v ffmpeg &> /dev/null; then
- log_warning "ffmpeg not available, skipping metadata update"
- return 1
- fi
- log_info "Updating metadata: Album='$ALBUM_NAME', Track=$TRACK_NUMBER"
- local TEMP_FILE="${SONG_FILE}.tmp.mp3"
- # Update metadata using ffmpeg
- if ffmpeg -i "$SONG_FILE" \
- -metadata album="$ALBUM_NAME" \
- -metadata track="$TRACK_NUMBER" \
- -c copy -y "$TEMP_FILE" 2>/dev/null; then
- mv "$TEMP_FILE" "$SONG_FILE"
- log_success "Metadata updated successfully"
- return 0
- else
- log_error "Failed to update metadata"
- rm -f "$TEMP_FILE"
- return 1
- fi
- }
- move_song_to_album() {
- local SONG_FILE="$1"
- local TARGET_FOLDER="$2"
- local ALBUM_NAME="$3"
- local TRACK_NUMBER="$4"
- if [[ ! -f "$SONG_FILE" ]]; then
- log_error "Song file not found: $SONG_FILE"
- return 1
- fi
- local filename=$(basename "$SONG_FILE")
- local destination="$TARGET_FOLDER/$filename"
- log_info "Moving song to album folder: $(basename "$TARGET_FOLDER")"
- if mv "$SONG_FILE" "$destination"; then
- log_success "Moved: $filename"
- update_song_metadata "$destination" "$ALBUM_NAME" "$TRACK_NUMBER"
- return 0
- else
- log_error "Failed to move song to album folder"
- return 1
- fi
- }
- clean_title() {
- echo "$1" | tr -d '[:punct:]' | tr '[:upper:]' '[:lower:]' | tr -s ' '
- }
- extract_playlist_id() {
- local URL="$1"
- local PLAYLIST_ID=""
- if [[ "$URL" =~ list=([^&]+) ]]; then
- PLAYLIST_ID="${BASH_REMATCH[1]}"
- fi
- echo "$PLAYLIST_ID"
- }
- is_album_url() {
- local URL="$1"
- # Check if URL contains album indicators
- if [[ "$URL" =~ /album/ ]] || [[ "$URL" =~ OLAK5uy_ ]]; then
- return 0 # It's an album
- fi
- return 1 # It's a playlist
- }
- generate_m3u_playlist() {
- local PLAYLIST_NAME="$1"
- local PLAYLIST_URL="$2"
- local SONG_LIST="$3"
- # Skip album URLs
- if is_album_url "$PLAYLIST_URL"; then
- log_info "Skipping M3U generation for album: $PLAYLIST_NAME"
- return
- fi
- log_info "Generating M3U playlist for: $PLAYLIST_NAME"
- # Create playlists directory if it doesn't exist
- mkdir -p "$PLAYLIST_M3U_FOLDER"
- # Extract playlist ID from URL
- local PLAYLIST_ID=$(extract_playlist_id "$PLAYLIST_URL")
- if [[ -z "$PLAYLIST_ID" ]]; then
- log_warning "Could not extract playlist ID from URL: $PLAYLIST_URL"
- return
- fi
- local M3U_FILE="$PLAYLIST_M3U_FOLDER/${PLAYLIST_ID}.m3u"
- # Remove existing M3U file if it exists
- if [[ -f "$M3U_FILE" ]]; then
- log_info "Removing existing M3U file"
- rm -f "$M3U_FILE"
- fi
- # Create M3U file with Gonic format
- {
- echo "#GONIC-NAME:\"$PLAYLIST_NAME\""
- echo "#GONIC-COMMENT:\"\""
- echo "#GONIC-IS-PUBLIC:\"false\""
- # Process songs from the song list and add to M3U if they exist
- echo "$SONG_LIST" | while IFS=: read -r INDEX TITLE VIDEO_ID; do
- if [[ -z "$TITLE" || -z "$VIDEO_ID" ]]; then
- continue
- fi
- # Look for the song file by video ID across all folders
- local found_file=$(find_song_by_id "$VIDEO_ID")
- if [[ -n "$found_file" && -f "$found_file" ]]; then
- # Convert absolute path to docker mount path
- local relative_path=$(echo "$found_file" | sed "s|$BASE_FOLDER|$MUSIC_MOUNT_PATH|")
- echo "$relative_path"
- fi
- done
- } > "$M3U_FILE"
- log_success "Created M3U playlist: $(basename "$M3U_FILE")"
- }
- download_album_artwork() {
- local ALBUM_URL="$1"
- local ALBUM_FOLDER="$2"
- log_info "Downloading album artwork"
- local ARTWORK_FILE="$ALBUM_FOLDER/folder.png"
- # Skip if artwork already exists
- if [[ -f "$ARTWORK_FILE" ]]; then
- log_info "Album artwork already exists"
- return 0
- fi
- # Clean up any existing artwork files first
- rm -f "$ALBUM_FOLDER"/folder.*
- # Download thumbnail without complex cropping
- if "$DOWNLOADER_PATH" --write-thumbnail \
- --convert-thumbnails png \
- --skip-download \
- -o "$ALBUM_FOLDER/folder.%(ext)s" \
- "$ALBUM_URL" 2>/dev/null; then
- # Clean up any other image formats that might have been created
- find "$ALBUM_FOLDER" -name "folder.*" ! -name "folder.png" -delete 2>/dev/null
- # Use ImageMagick to crop to square if available
- if command -v convert &> /dev/null; then
- local TEMP_FILE="$ALBUM_FOLDER/folder_temp.png"
- if convert "$ARTWORK_FILE" -gravity center -crop 1:1 +repage "$TEMP_FILE" 2>/dev/null; then
- mv "$TEMP_FILE" "$ARTWORK_FILE"
- log_success "Album artwork cropped to square"
- else
- log_info "Keeping original aspect ratio"
- fi
- else
- log_info "ImageMagick not available, keeping original aspect ratio"
- fi
- return 0
- else
- log_error "Failed to download album artwork"
- return 1
- fi
- }
- mkdir -p "$BASE_FOLDER"
- mkdir -p "$BASE_FOLDER/Unsorted Songs"
- ALBUM_URLS=()
- PLAYLIST_URLS=()
- log_section "Categorizing URLs into albums and playlists"
- for URL in "${PLAYLISTS[@]}"; do
- if is_album_url "$URL"; then
- ALBUM_URLS+=("$URL")
- log_info "Album: $URL"
- else
- PLAYLIST_URLS+=("$URL")
- log_info "Playlist: $URL"
- fi
- done
- log_info "Found ${#ALBUM_URLS[@]} album(s) and ${#PLAYLIST_URLS[@]} playlist(s)"
- # PASS 1: Process all albums first
- if [ ${#ALBUM_URLS[@]} -gt 0 ]; then
- log_section "PASS 1: Processing Albums"
- for URL in "${ALBUM_URLS[@]}"; do
- log_section "Processing Album: $URL"
- PLAYLIST_NAME=$(timeout 30s "$DOWNLOADER_PATH" --print "%(playlist_title)s" "$URL" 2>/dev/null | head -n 1)
- if [[ -z "$PLAYLIST_NAME" ]]; then
- log_error "Failed to fetch album title, skipping..."
- continue
- fi
- PLAYLIST_NAME=$(echo "$PLAYLIST_NAME" | sed 's/^Album - //' | tr -cd '[:alnum:][:space:]._-' | sed 's/[[:space:]]\+/ /g')
- TARGET_FOLDER="$BASE_FOLDER/$PLAYLIST_NAME"
- mkdir -p "$TARGET_FOLDER"
- log_info "Album: '$PLAYLIST_NAME'"
- log_info "Folder: $TARGET_FOLDER"
- log_info "Retrieving song list..."
- SONG_LIST=$(timeout 60s "$DOWNLOADER_PATH" --flat-playlist --print "%(playlist_index)s:%(title)s:%(id)s" "$URL" 2>/dev/null)
- if [[ -z "$SONG_LIST" ]]; then
- log_error "Failed to retrieve song list, skipping..."
- continue
- fi
- SONG_COUNT=$(echo "$SONG_LIST" | wc -l)
- log_success "Found $SONG_COUNT songs in album"
- TEMP_SONG_FILE=$(mktemp)
- echo "$SONG_LIST" > "$TEMP_SONG_FILE"
- TRACK_COUNTER=1
- while IFS=: read -r INDEX TITLE VIDEO_ID; do
- if [[ -z "$INDEX" || -z "$TITLE" || -z "$VIDEO_ID" ]]; then
- continue
- fi
- log_info "[$TRACK_COUNTER/$SONG_COUNT] $TITLE (ID: $VIDEO_ID)"
- VIDEO_URL="https://www.youtube.com/watch?v=${VIDEO_ID}"
- # Check if song already exists anywhere
- if song_exists "$VIDEO_ID"; then
- # Find where the song is currently located
- EXISTING_FILE=$(find_song_by_id "$VIDEO_ID")
- CURRENT_FOLDER=$(dirname "$EXISTING_FILE")
- if [[ "$CURRENT_FOLDER" == "$TARGET_FOLDER" ]]; then
- log_success "Song already in album folder"
- log_info "Updating metadata to ensure correctness..."
- update_song_metadata "$EXISTING_FILE" "$PLAYLIST_NAME" "$TRACK_COUNTER"
- else
- log_info "Song exists in: $(basename "$CURRENT_FOLDER")"
- log_info "Moving to album and updating metadata..."
- if move_song_to_album "$EXISTING_FILE" "$TARGET_FOLDER" "$PLAYLIST_NAME" "$TRACK_COUNTER"; then
- log_success "Song relocated to album"
- fi
- fi
- else
- log_info "Downloading new song..."
- # Download with video ID in filename
- OUTPUT_TEMPLATE="$TARGET_FOLDER/%(artist)s - %(title)s - ${VIDEO_ID}.%(ext)s"
- if "$DOWNLOADER_PATH" -o "$OUTPUT_TEMPLATE" \
- --format "bestaudio[ext=m4a]/best" \
- --extract-audio \
- --audio-format mp3 \
- --audio-quality 0 \
- --embed-thumbnail \
- --convert-thumbnail png \
- --add-metadata \
- --parse-metadata "%(title)s:%(meta_title)s" \
- --parse-metadata "%(artist)s:%(meta_artist)s" \
- --parse-metadata "%(album)s:%(meta_album)s" \
- --parse-metadata "$TRACK_COUNTER:%(meta_track)s" \
- --ppa "EmbedThumbnail+ffmpeg_o:-c:v png -vf crop=\"'if(gt(ih,iw),iw,ih)':'if(gt(iw,ih),ih,iw)'\"" \
- --no-overwrites \
- "$VIDEO_URL" 2>/dev/null; then
- record_downloaded_song "$VIDEO_ID"
- log_success "Downloaded successfully"
- else
- log_error "Download failed"
- fi
- fi
- ((TRACK_COUNTER++))
- done < "$TEMP_SONG_FILE"
- download_album_artwork "$URL" "$TARGET_FOLDER"
- rm -f "$TEMP_SONG_FILE"
- done
- fi
- # PASS 2: Process all playlists
- if [ ${#PLAYLIST_URLS[@]} -gt 0 ]; then
- log_section "PASS 2: Processing Playlists"
- for URL in "${PLAYLIST_URLS[@]}"; do
- log_section "Processing Playlist: $URL"
- PLAYLIST_NAME=$(timeout 30s "$DOWNLOADER_PATH" --print "%(playlist_title)s" "$URL" 2>/dev/null | head -n 1)
- if [[ -z "$PLAYLIST_NAME" ]]; then
- log_error "Failed to fetch playlist title, skipping..."
- continue
- fi
- PLAYLIST_NAME=$(echo "$PLAYLIST_NAME" | tr -cd '[:alnum:][:space:]._-' | sed 's/[[:space:]]\+/ /g')
- TARGET_FOLDER="$BASE_FOLDER/Unsorted Songs"
- log_info "Playlist: '$PLAYLIST_NAME'"
- log_info "Folder: $TARGET_FOLDER"
- log_info "Retrieving song list..."
- SONG_LIST=$(timeout 60s "$DOWNLOADER_PATH" --flat-playlist --print "%(playlist_index)s:%(title)s:%(id)s" "$URL" 2>/dev/null)
- if [[ -z "$SONG_LIST" ]]; then
- log_error "Failed to retrieve song list, skipping..."
- continue
- fi
- SONG_COUNT=$(echo "$SONG_LIST" | wc -l)
- log_success "Found $SONG_COUNT songs in playlist"
- TEMP_SONG_FILE=$(mktemp)
- echo "$SONG_LIST" > "$TEMP_SONG_FILE"
- SONG_COUNTER=1
- while IFS=: read -r INDEX TITLE VIDEO_ID; do
- if [[ -z "$INDEX" || -z "$TITLE" || -z "$VIDEO_ID" ]]; then
- continue
- fi
- log_info "[$SONG_COUNTER/$SONG_COUNT] $TITLE (ID: $VIDEO_ID)"
- VIDEO_URL="https://www.youtube.com/watch?v=${VIDEO_ID}"
- if song_exists "$VIDEO_ID"; then
- EXISTING_FILE=$(find_song_by_id "$VIDEO_ID")
- log_success "Song already exists at: $(basename "$(dirname "$EXISTING_FILE")")/$(basename "$EXISTING_FILE")"
- else
- log_info "Downloading new song..."
- # Download with video ID in filename
- OUTPUT_TEMPLATE="$TARGET_FOLDER/%(artist)s - %(title)s - ${VIDEO_ID}.%(ext)s"
- if "$DOWNLOADER_PATH" -o "$OUTPUT_TEMPLATE" \
- --format "bestaudio[ext=m4a]/best" \
- --extract-audio \
- --audio-format mp3 \
- --audio-quality 0 \
- --embed-thumbnail \
- --convert-thumbnail png \
- --add-metadata \
- --parse-metadata "%(title)s:%(meta_title)s" \
- --parse-metadata "%(artist)s:%(meta_artist)s" \
- --parse-metadata "Unsorted Songs:%(meta_album)s" \
- --ppa "EmbedThumbnail+ffmpeg_o:-c:v png -vf crop=\"'if(gt(ih,iw),iw,ih)':'if(gt(iw,ih),ih,iw)'\"" \
- --no-overwrites \
- "$VIDEO_URL" 2>/dev/null; then
- record_downloaded_song "$VIDEO_ID"
- log_success "Downloaded successfully"
- else
- log_error "Download failed"
- fi
- fi
- ((SONG_COUNTER++))
- done < "$TEMP_SONG_FILE"
- generate_m3u_playlist "$PLAYLIST_NAME" "$URL" "$SONG_LIST"
- rm -f "$TEMP_SONG_FILE"
- done
- fi
- log_section "Processing Complete"
- log_success "All downloads completed!"
- log_info "Songs saved to: $BASE_FOLDER"
- log_info "M3U playlists saved to: $PLAYLIST_M3U_FOLDER"
- if [ -n "$TMUX" ] && [ "$1" == "--inside-tmux" ]; then
- log_info "Automatically terminating tmux session in 10 seconds..."
- sleep 10
- tmux kill-session -t songs-download
- fi
Advertisement
Add Comment
Please, Sign In to add comment