Guest User

songs-download.sh

a guest
Nov 10th, 2025
81
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 18.50 KB | Source Code | 0 0
  1. #!/bin/bash
  2.  
  3. SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
  4.  
  5. log_info() {
  6. echo -e "\e[36m[INFO] $1\e[0m"
  7. }
  8.  
  9. log_success() {
  10. echo -e "\e[32m[SUCCESS] $1\e[0m"
  11. }
  12.  
  13. log_warning() {
  14. echo -e "\e[33m[WARNING] $1\e[0m"
  15. }
  16.  
  17. log_error() {
  18. echo -e "\e[31m[ERROR] $1\e[0m"
  19. }
  20.  
  21. log_section() {
  22. echo ""
  23. echo -e "\e[35m═══════════════════════════════════════════════════════════════\e[0m"
  24. echo -e "\e[35m$1\e[0m"
  25. echo -e "\e[35m═══════════════════════════════════════════════════════════════\e[0m"
  26. }
  27.  
  28. # Environment Checking
  29. if [ ! -f "$SCRIPT_DIR/.songs-env" ]; then
  30. log_error "File Not Found at $SCRIPT_DIR/.songs-env"
  31. echo -e "Please Create one and Add This Content:"
  32. echo -e ""
  33. echo -e "\e[33mexport BASE_FOLDER=\"/songs/All Songs\""
  34. echo -e "export DOWNLOADER_PATH=\"/songs/Binary/downloader\""
  35. echo -e "export RECORD_FILE_NAME=\".downloaded_videos.txt\""
  36. echo -e "export PLAYLIST_M3U_FOLDER=\"/songs/Playlists\""
  37. echo -e "export MUSIC_MOUNT_PATH=\"/music\""
  38. echo -e "declare -a PLAYLISTS=(\nPLAYLIST-1-URL\nPLAYLIST-2-URL\nPLAYLIST-3-URL\n)\e[0m"
  39. exit 1
  40. fi
  41.  
  42. source "$SCRIPT_DIR/.songs-env"
  43.  
  44. # Set default music mount path if not defined
  45. if [ -z "$MUSIC_MOUNT_PATH" ]; then
  46. MUSIC_MOUNT_PATH="/music"
  47. log_warning "MUSIC_MOUNT_PATH not defined in .songs-env, using default: $MUSIC_MOUNT_PATH"
  48. fi
  49.  
  50. # Set default parallel limit if not defined
  51. if [ -z "$PARALLEL_LIMIT" ]; then
  52. PARALLEL_LIMIT=4
  53. log_warning "PARALLEL_LIMIT not defined in .songs-env, using default: $PARALLEL_LIMIT"
  54. fi
  55.  
  56. # Set default playlist M3U folder if not defined
  57. if [ -z "$PLAYLIST_M3U_FOLDER" ]; then
  58. PLAYLIST_M3U_FOLDER="/data/songs/Playlists"
  59. log_warning "PLAYLIST_M3U_FOLDER not defined in .songs-env, using default: $PLAYLIST_M3U_FOLDER"
  60. fi
  61.  
  62.  
  63. JOB_COUNT=0
  64.  
  65. wait_for_jobs() {
  66. while [ $(jobs -r | wc -l) -ge $PARALLEL_LIMIT ]; do
  67. sleep 1
  68. done
  69. }
  70.  
  71. GLOBAL_RECORD_FILE="$BASE_FOLDER/$RECORD_FILE_NAME"
  72.  
  73. if [ -z "$RECORD_FILE_NAME" ]; then
  74. RECORD_FILE_NAME=".downloaded_videos.txt"
  75. log_warning "RECORD_FILE_NAME not defined in .songs-env, using default: $RECORD_FILE_NAME"
  76. fi
  77.  
  78. if [ -z "$TMUX" ] && [ "$1" != "--inside-tmux" ]; then
  79. log_info "Starting download script in a tmux session..."
  80.  
  81. if ! command -v tmux &> /dev/null; then
  82. log_error "tmux is not installed. Please install it with: apt-get install tmux"
  83. exit 1
  84. fi
  85.  
  86. tmux new-session -d -s songs-download "$0 --inside-tmux"
  87. log_success "Download process started in tmux session"
  88. log_info "To view progress, run: tmux attach -t songs-download"
  89. exit 0
  90. fi
  91.  
  92. if ! command -v "$DOWNLOADER_PATH" &> /dev/null; then
  93. log_error "downloader is not installed or not in the specified location. Please verify the binary location."
  94. exit 1
  95. fi
  96.  
  97. log_info "Updating downloader..."
  98. "$DOWNLOADER_PATH" -U
  99. if [ $? -ne 0 ]; then
  100. log_error "Failed to update the downloader. Please check for errors."
  101. exit 1
  102. fi
  103. log_success "Downloader updated successfully"
  104.  
  105. get_video_id() {
  106. local URL="$1"
  107. local VIDEO_ID=""
  108.  
  109. if [[ "$URL" == *"youtu.be/"* ]]; then
  110. VIDEO_ID=$(echo "$URL" | sed -E 's|.*youtu\.be/([^?&]+).*|\1|')
  111. elif [[ "$URL" == *"youtube.com/watch"* ]]; then
  112. VIDEO_ID=$(echo "$URL" | sed -E 's|.*[?&]v=([^&]+).*|\1|')
  113. fi
  114.  
  115. echo "$VIDEO_ID"
  116. }
  117.  
  118. song_exists() {
  119. local VIDEO_ID="$1"
  120.  
  121. # Search for files with the video ID in their filename
  122. if find "$BASE_FOLDER" -type f -name "*${VIDEO_ID}.mp3" 2>/dev/null | grep -q .; then
  123. log_info "Song with ID $VIDEO_ID already exists"
  124. if [[ -f "$GLOBAL_RECORD_FILE" ]]; then
  125. if ! grep -q "^$VIDEO_ID$" "$GLOBAL_RECORD_FILE"; then
  126. echo "$VIDEO_ID" >> "$GLOBAL_RECORD_FILE"
  127. fi
  128. fi
  129. return 0
  130. fi
  131.  
  132. return 1
  133. }
  134.  
  135. find_song_by_id() {
  136. local VIDEO_ID="$1"
  137.  
  138. # Find the song file with the given video ID
  139. local found_file=$(find "$BASE_FOLDER" -type f -name "*${VIDEO_ID}.mp3" 2>/dev/null | head -n 1)
  140.  
  141. echo "$found_file"
  142. }
  143.  
  144. record_downloaded_song() {
  145. local VIDEO_ID="$1"
  146.  
  147. if [[ -n "$VIDEO_ID" ]]; then
  148. mkdir -p "$BASE_FOLDER"
  149. echo "$VIDEO_ID" >> "$GLOBAL_RECORD_FILE"
  150. log_success "Recorded downloaded song ID: $VIDEO_ID"
  151. fi
  152. }
  153.  
  154. update_song_metadata() {
  155. local SONG_FILE="$1"
  156. local ALBUM_NAME="$2"
  157. local TRACK_NUMBER="$3"
  158.  
  159. if ! command -v ffmpeg &> /dev/null; then
  160. log_warning "ffmpeg not available, skipping metadata update"
  161. return 1
  162. fi
  163.  
  164. log_info "Updating metadata: Album='$ALBUM_NAME', Track=$TRACK_NUMBER"
  165.  
  166. local TEMP_FILE="${SONG_FILE}.tmp.mp3"
  167.  
  168. # Update metadata using ffmpeg
  169. if ffmpeg -i "$SONG_FILE" \
  170. -metadata album="$ALBUM_NAME" \
  171. -metadata track="$TRACK_NUMBER" \
  172. -c copy -y "$TEMP_FILE" 2>/dev/null; then
  173.  
  174. mv "$TEMP_FILE" "$SONG_FILE"
  175. log_success "Metadata updated successfully"
  176. return 0
  177. else
  178. log_error "Failed to update metadata"
  179. rm -f "$TEMP_FILE"
  180. return 1
  181. fi
  182. }
  183.  
  184. move_song_to_album() {
  185. local SONG_FILE="$1"
  186. local TARGET_FOLDER="$2"
  187. local ALBUM_NAME="$3"
  188. local TRACK_NUMBER="$4"
  189.  
  190. if [[ ! -f "$SONG_FILE" ]]; then
  191. log_error "Song file not found: $SONG_FILE"
  192. return 1
  193. fi
  194.  
  195. local filename=$(basename "$SONG_FILE")
  196. local destination="$TARGET_FOLDER/$filename"
  197.  
  198. log_info "Moving song to album folder: $(basename "$TARGET_FOLDER")"
  199.  
  200. if mv "$SONG_FILE" "$destination"; then
  201. log_success "Moved: $filename"
  202. update_song_metadata "$destination" "$ALBUM_NAME" "$TRACK_NUMBER"
  203. return 0
  204. else
  205. log_error "Failed to move song to album folder"
  206. return 1
  207. fi
  208. }
  209.  
  210. clean_title() {
  211. echo "$1" | tr -d '[:punct:]' | tr '[:upper:]' '[:lower:]' | tr -s ' '
  212. }
  213.  
  214. extract_playlist_id() {
  215. local URL="$1"
  216. local PLAYLIST_ID=""
  217.  
  218. if [[ "$URL" =~ list=([^&]+) ]]; then
  219. PLAYLIST_ID="${BASH_REMATCH[1]}"
  220. fi
  221.  
  222. echo "$PLAYLIST_ID"
  223. }
  224.  
  225. is_album_url() {
  226. local URL="$1"
  227.  
  228. # Check if URL contains album indicators
  229. if [[ "$URL" =~ /album/ ]] || [[ "$URL" =~ OLAK5uy_ ]]; then
  230. return 0 # It's an album
  231. fi
  232.  
  233. return 1 # It's a playlist
  234. }
  235.  
  236. generate_m3u_playlist() {
  237. local PLAYLIST_NAME="$1"
  238. local PLAYLIST_URL="$2"
  239. local SONG_LIST="$3"
  240.  
  241. # Skip album URLs
  242. if is_album_url "$PLAYLIST_URL"; then
  243. log_info "Skipping M3U generation for album: $PLAYLIST_NAME"
  244. return
  245. fi
  246.  
  247. log_info "Generating M3U playlist for: $PLAYLIST_NAME"
  248.  
  249. # Create playlists directory if it doesn't exist
  250. mkdir -p "$PLAYLIST_M3U_FOLDER"
  251.  
  252. # Extract playlist ID from URL
  253. local PLAYLIST_ID=$(extract_playlist_id "$PLAYLIST_URL")
  254. if [[ -z "$PLAYLIST_ID" ]]; then
  255. log_warning "Could not extract playlist ID from URL: $PLAYLIST_URL"
  256. return
  257. fi
  258.  
  259. local M3U_FILE="$PLAYLIST_M3U_FOLDER/${PLAYLIST_ID}.m3u"
  260.  
  261. # Remove existing M3U file if it exists
  262. if [[ -f "$M3U_FILE" ]]; then
  263. log_info "Removing existing M3U file"
  264. rm -f "$M3U_FILE"
  265. fi
  266.  
  267. # Create M3U file with Gonic format
  268. {
  269. echo "#GONIC-NAME:\"$PLAYLIST_NAME\""
  270. echo "#GONIC-COMMENT:\"\""
  271. echo "#GONIC-IS-PUBLIC:\"false\""
  272.  
  273. # Process songs from the song list and add to M3U if they exist
  274. echo "$SONG_LIST" | while IFS=: read -r INDEX TITLE VIDEO_ID; do
  275. if [[ -z "$TITLE" || -z "$VIDEO_ID" ]]; then
  276. continue
  277. fi
  278.  
  279. # Look for the song file by video ID across all folders
  280. local found_file=$(find_song_by_id "$VIDEO_ID")
  281.  
  282. if [[ -n "$found_file" && -f "$found_file" ]]; then
  283. # Convert absolute path to docker mount path
  284. local relative_path=$(echo "$found_file" | sed "s|$BASE_FOLDER|$MUSIC_MOUNT_PATH|")
  285. echo "$relative_path"
  286. fi
  287. done
  288. } > "$M3U_FILE"
  289.  
  290. log_success "Created M3U playlist: $(basename "$M3U_FILE")"
  291. }
  292.  
  293. download_album_artwork() {
  294. local ALBUM_URL="$1"
  295. local ALBUM_FOLDER="$2"
  296.  
  297. log_info "Downloading album artwork"
  298.  
  299. local ARTWORK_FILE="$ALBUM_FOLDER/folder.png"
  300.  
  301. # Skip if artwork already exists
  302. if [[ -f "$ARTWORK_FILE" ]]; then
  303. log_info "Album artwork already exists"
  304. return 0
  305. fi
  306.  
  307. # Clean up any existing artwork files first
  308. rm -f "$ALBUM_FOLDER"/folder.*
  309.  
  310. # Download thumbnail without complex cropping
  311. if "$DOWNLOADER_PATH" --write-thumbnail \
  312. --convert-thumbnails png \
  313. --skip-download \
  314. -o "$ALBUM_FOLDER/folder.%(ext)s" \
  315. "$ALBUM_URL" 2>/dev/null; then
  316.  
  317. # Clean up any other image formats that might have been created
  318. find "$ALBUM_FOLDER" -name "folder.*" ! -name "folder.png" -delete 2>/dev/null
  319.  
  320. # Use ImageMagick to crop to square if available
  321. if command -v convert &> /dev/null; then
  322. local TEMP_FILE="$ALBUM_FOLDER/folder_temp.png"
  323. if convert "$ARTWORK_FILE" -gravity center -crop 1:1 +repage "$TEMP_FILE" 2>/dev/null; then
  324. mv "$TEMP_FILE" "$ARTWORK_FILE"
  325. log_success "Album artwork cropped to square"
  326. else
  327. log_info "Keeping original aspect ratio"
  328. fi
  329. else
  330. log_info "ImageMagick not available, keeping original aspect ratio"
  331. fi
  332.  
  333. return 0
  334. else
  335. log_error "Failed to download album artwork"
  336. return 1
  337. fi
  338. }
  339.  
  340. mkdir -p "$BASE_FOLDER"
  341. mkdir -p "$BASE_FOLDER/Unsorted Songs"
  342.  
  343. ALBUM_URLS=()
  344. PLAYLIST_URLS=()
  345.  
  346. log_section "Categorizing URLs into albums and playlists"
  347.  
  348. for URL in "${PLAYLISTS[@]}"; do
  349. if is_album_url "$URL"; then
  350. ALBUM_URLS+=("$URL")
  351. log_info "Album: $URL"
  352. else
  353. PLAYLIST_URLS+=("$URL")
  354. log_info "Playlist: $URL"
  355. fi
  356. done
  357.  
  358. log_info "Found ${#ALBUM_URLS[@]} album(s) and ${#PLAYLIST_URLS[@]} playlist(s)"
  359.  
  360. # PASS 1: Process all albums first
  361. if [ ${#ALBUM_URLS[@]} -gt 0 ]; then
  362. log_section "PASS 1: Processing Albums"
  363.  
  364. for URL in "${ALBUM_URLS[@]}"; do
  365. log_section "Processing Album: $URL"
  366.  
  367. PLAYLIST_NAME=$(timeout 30s "$DOWNLOADER_PATH" --print "%(playlist_title)s" "$URL" 2>/dev/null | head -n 1)
  368.  
  369. if [[ -z "$PLAYLIST_NAME" ]]; then
  370. log_error "Failed to fetch album title, skipping..."
  371. continue
  372. fi
  373.  
  374. PLAYLIST_NAME=$(echo "$PLAYLIST_NAME" | sed 's/^Album - //' | tr -cd '[:alnum:][:space:]._-' | sed 's/[[:space:]]\+/ /g')
  375.  
  376. TARGET_FOLDER="$BASE_FOLDER/$PLAYLIST_NAME"
  377. mkdir -p "$TARGET_FOLDER"
  378. log_info "Album: '$PLAYLIST_NAME'"
  379. log_info "Folder: $TARGET_FOLDER"
  380.  
  381. log_info "Retrieving song list..."
  382. SONG_LIST=$(timeout 60s "$DOWNLOADER_PATH" --flat-playlist --print "%(playlist_index)s:%(title)s:%(id)s" "$URL" 2>/dev/null)
  383.  
  384. if [[ -z "$SONG_LIST" ]]; then
  385. log_error "Failed to retrieve song list, skipping..."
  386. continue
  387. fi
  388.  
  389. SONG_COUNT=$(echo "$SONG_LIST" | wc -l)
  390. log_success "Found $SONG_COUNT songs in album"
  391.  
  392. TEMP_SONG_FILE=$(mktemp)
  393. echo "$SONG_LIST" > "$TEMP_SONG_FILE"
  394.  
  395. TRACK_COUNTER=1
  396.  
  397. while IFS=: read -r INDEX TITLE VIDEO_ID; do
  398. if [[ -z "$INDEX" || -z "$TITLE" || -z "$VIDEO_ID" ]]; then
  399. continue
  400. fi
  401.  
  402. log_info "[$TRACK_COUNTER/$SONG_COUNT] $TITLE (ID: $VIDEO_ID)"
  403.  
  404. VIDEO_URL="https://www.youtube.com/watch?v=${VIDEO_ID}"
  405.  
  406. # Check if song already exists anywhere
  407. if song_exists "$VIDEO_ID"; then
  408. # Find where the song is currently located
  409. EXISTING_FILE=$(find_song_by_id "$VIDEO_ID")
  410. CURRENT_FOLDER=$(dirname "$EXISTING_FILE")
  411.  
  412. if [[ "$CURRENT_FOLDER" == "$TARGET_FOLDER" ]]; then
  413. log_success "Song already in album folder"
  414. log_info "Updating metadata to ensure correctness..."
  415. update_song_metadata "$EXISTING_FILE" "$PLAYLIST_NAME" "$TRACK_COUNTER"
  416. else
  417. log_info "Song exists in: $(basename "$CURRENT_FOLDER")"
  418. log_info "Moving to album and updating metadata..."
  419.  
  420. if move_song_to_album "$EXISTING_FILE" "$TARGET_FOLDER" "$PLAYLIST_NAME" "$TRACK_COUNTER"; then
  421. log_success "Song relocated to album"
  422. fi
  423. fi
  424. else
  425. log_info "Downloading new song..."
  426.  
  427. # Download with video ID in filename
  428. OUTPUT_TEMPLATE="$TARGET_FOLDER/%(artist)s - %(title)s - ${VIDEO_ID}.%(ext)s"
  429.  
  430. if "$DOWNLOADER_PATH" -o "$OUTPUT_TEMPLATE" \
  431. --format "bestaudio[ext=m4a]/best" \
  432. --extract-audio \
  433. --audio-format mp3 \
  434. --audio-quality 0 \
  435. --embed-thumbnail \
  436. --convert-thumbnail png \
  437. --add-metadata \
  438. --parse-metadata "%(title)s:%(meta_title)s" \
  439. --parse-metadata "%(artist)s:%(meta_artist)s" \
  440. --parse-metadata "%(album)s:%(meta_album)s" \
  441. --parse-metadata "$TRACK_COUNTER:%(meta_track)s" \
  442. --ppa "EmbedThumbnail+ffmpeg_o:-c:v png -vf crop=\"'if(gt(ih,iw),iw,ih)':'if(gt(iw,ih),ih,iw)'\"" \
  443. --no-overwrites \
  444. "$VIDEO_URL" 2>/dev/null; then
  445.  
  446. record_downloaded_song "$VIDEO_ID"
  447. log_success "Downloaded successfully"
  448. else
  449. log_error "Download failed"
  450. fi
  451. fi
  452.  
  453. ((TRACK_COUNTER++))
  454.  
  455. done < "$TEMP_SONG_FILE"
  456.  
  457. download_album_artwork "$URL" "$TARGET_FOLDER"
  458.  
  459. rm -f "$TEMP_SONG_FILE"
  460.  
  461. done
  462. fi
  463.  
  464. # PASS 2: Process all playlists
  465. if [ ${#PLAYLIST_URLS[@]} -gt 0 ]; then
  466. log_section "PASS 2: Processing Playlists"
  467.  
  468. for URL in "${PLAYLIST_URLS[@]}"; do
  469. log_section "Processing Playlist: $URL"
  470.  
  471. PLAYLIST_NAME=$(timeout 30s "$DOWNLOADER_PATH" --print "%(playlist_title)s" "$URL" 2>/dev/null | head -n 1)
  472.  
  473. if [[ -z "$PLAYLIST_NAME" ]]; then
  474. log_error "Failed to fetch playlist title, skipping..."
  475. continue
  476. fi
  477.  
  478. PLAYLIST_NAME=$(echo "$PLAYLIST_NAME" | tr -cd '[:alnum:][:space:]._-' | sed 's/[[:space:]]\+/ /g')
  479.  
  480. TARGET_FOLDER="$BASE_FOLDER/Unsorted Songs"
  481. log_info "Playlist: '$PLAYLIST_NAME'"
  482. log_info "Folder: $TARGET_FOLDER"
  483.  
  484. log_info "Retrieving song list..."
  485. SONG_LIST=$(timeout 60s "$DOWNLOADER_PATH" --flat-playlist --print "%(playlist_index)s:%(title)s:%(id)s" "$URL" 2>/dev/null)
  486.  
  487. if [[ -z "$SONG_LIST" ]]; then
  488. log_error "Failed to retrieve song list, skipping..."
  489. continue
  490. fi
  491.  
  492. SONG_COUNT=$(echo "$SONG_LIST" | wc -l)
  493. log_success "Found $SONG_COUNT songs in playlist"
  494.  
  495. TEMP_SONG_FILE=$(mktemp)
  496. echo "$SONG_LIST" > "$TEMP_SONG_FILE"
  497.  
  498. SONG_COUNTER=1
  499.  
  500. while IFS=: read -r INDEX TITLE VIDEO_ID; do
  501. if [[ -z "$INDEX" || -z "$TITLE" || -z "$VIDEO_ID" ]]; then
  502. continue
  503. fi
  504.  
  505. log_info "[$SONG_COUNTER/$SONG_COUNT] $TITLE (ID: $VIDEO_ID)"
  506.  
  507. VIDEO_URL="https://www.youtube.com/watch?v=${VIDEO_ID}"
  508.  
  509. if song_exists "$VIDEO_ID"; then
  510. EXISTING_FILE=$(find_song_by_id "$VIDEO_ID")
  511. log_success "Song already exists at: $(basename "$(dirname "$EXISTING_FILE")")/$(basename "$EXISTING_FILE")"
  512. else
  513. log_info "Downloading new song..."
  514.  
  515. # Download with video ID in filename
  516. OUTPUT_TEMPLATE="$TARGET_FOLDER/%(artist)s - %(title)s - ${VIDEO_ID}.%(ext)s"
  517.  
  518. if "$DOWNLOADER_PATH" -o "$OUTPUT_TEMPLATE" \
  519. --format "bestaudio[ext=m4a]/best" \
  520. --extract-audio \
  521. --audio-format mp3 \
  522. --audio-quality 0 \
  523. --embed-thumbnail \
  524. --convert-thumbnail png \
  525. --add-metadata \
  526. --parse-metadata "%(title)s:%(meta_title)s" \
  527. --parse-metadata "%(artist)s:%(meta_artist)s" \
  528. --parse-metadata "Unsorted Songs:%(meta_album)s" \
  529. --ppa "EmbedThumbnail+ffmpeg_o:-c:v png -vf crop=\"'if(gt(ih,iw),iw,ih)':'if(gt(iw,ih),ih,iw)'\"" \
  530. --no-overwrites \
  531. "$VIDEO_URL" 2>/dev/null; then
  532.  
  533. record_downloaded_song "$VIDEO_ID"
  534. log_success "Downloaded successfully"
  535. else
  536. log_error "Download failed"
  537. fi
  538. fi
  539.  
  540. ((SONG_COUNTER++))
  541.  
  542. done < "$TEMP_SONG_FILE"
  543.  
  544. generate_m3u_playlist "$PLAYLIST_NAME" "$URL" "$SONG_LIST"
  545.  
  546. rm -f "$TEMP_SONG_FILE"
  547.  
  548. done
  549. fi
  550.  
  551. log_section "Processing Complete"
  552.  
  553. log_success "All downloads completed!"
  554. log_info "Songs saved to: $BASE_FOLDER"
  555. log_info "M3U playlists saved to: $PLAYLIST_M3U_FOLDER"
  556.  
  557. if [ -n "$TMUX" ] && [ "$1" == "--inside-tmux" ]; then
  558. log_info "Automatically terminating tmux session in 10 seconds..."
  559. sleep 10
  560. tmux kill-session -t songs-download
  561. fi
  562.  
Advertisement
Add Comment
Please, Sign In to add comment