I've developed a Python script that takes your playlist from any service (Apple Music, Spotify, etc.) and automatically downloads the songs in Hi-Fidelity, full lossless (FLAC) to your computer.
Anyone who loves music knows that Full Lossless is the best listening experience for any music.
Disclaimer:
The API used to auto-solve reCAPTCHA costs money. It's $0.80 per 1000 solves. For example, if you have a playlist that has 2000 songs you want to download, the automation ends up being about $1.60. I have no direct control over this, but will try to implement a better workaround in the future.
If you are a TTG Gold member and would like a key for free, feel free to DM me!
import os
import sys
import time
import subprocess
import requests
from datetime import datetime
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import NoSuchElementException, TimeoutException, ElementNotInteractableException
from tkinter import filedialog, messagebox
import customtkinter as ctk
import tkinter as tk
from colorama import Fore, Back, Style, init
import json
# Label to display the song count with rainbow effect
self.song_count_var = tk.StringVar(value="")
self.song_count_label = ctk.CTkLabel(self.main_frame, textvariable=self.song_count_var, font=("Open Sans", 14))
self.song_count_label.grid(row=1, column=0, pady=(10, 0), sticky='n') # Centered in the grid
# Adjust column and row weights
self.main_frame.columnconfigure(0, weight=1)
self.main_frame.rowconfigure(0, weight=0) # For playlist_button
self.main_frame.rowconfigure(1, weight=0) # For song_count_label (minimal spacing)
self.main_frame.rowconfigure(2, weight=2) # For directory_button
self.main_frame.rowconfigure(3, weight=1) # For api_entry_label and api_entry
self.main_frame.rowconfigure(4, weight=1) # For start_button
# Initialize the rainbow effect on the song count label
self.rainbow_colors = generate_full_rgb_spectrum()
self.current_color_index = 0
self.update_rainbow_effect()
def update_rainbow_effect(self):
"""Updates song count color with a dynamic rainbow effect."""
self.song_count_label.configure(text_color=self.rainbow_colors[self.current_color_index])
self.current_color_index = (self.current_color_index + 1) % len(self.rainbow_colors)
self.song_count_label.after(4, self.update_rainbow_effect) # Change color every 300 milliseconds
def set_song_count(self, count):
"""Updates the song count label."""
self.song_count_var.set(count)
def select_playlist(self):
self.playlist_path = filedialog.askopenfilename()
if self.playlist_path:
with open(self.playlist_path, 'r', encoding='utf-8-sig') as file:
content = file.readlines()
songs_count = sum(1 for line in content if ' - ' in line)
self.song_count_var.set(f"Songs found: {songs_count} ")
self.playlist_button.configure(text=f"Selected: {os.path.basename(self.playlist_path)}")
def select_directory(self):
self.download_directory = filedialog.askdirectory().replace("/", "\\")
if self.download_directory:
self.directory_button.configure(text=f"Selected: {self.download_directory}")
def start_download(self):
global CAPSOLVER_API_KEY
CAPSOLVER_API_KEY = self.api_key.get()
global options
if not self.playlist_path or not self.download_directory or not self.api_key.get():
messagebox.showwarning("Warning", "Please provide all necessary information before starting.")
return
# You can now start the download process and close the UI
root.destroy()
root = ctk.CTk()
app = App(root)
root.after(1000, app.update_rainbow_effect) # Start the animation
root.title("Playlist to FLAC Downloader")
# Set window size
window_width = 600 # You can adjust this value
window_height = 400 # You can adjust this value
root.geometry(f"{window_width}x{window_height}")
# Center the window on the screen
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
x_position = int((screen_width - window_width) / 2)
y_position = int((screen_height - window_height) / 2)
root.geometry(f"+{x_position}+{y_position}")
root.mainloop()
errors = []
failed_count = 0
def is_download_active(directory):
for filename in os.listdir(directory):
if filename.endswith(('.part', '.crdownload', '.tmp')):
return True
return False
def download_song(driver, artist, song):
global failed_count
start_time = datetime.now()
headers = {"Content-Type": "application/json"}
# Navigate to the site
driver.get("https://free-mp3-download.net/")
try:
search_box = driver.find_element(By.CSS_SELECTOR, "#q")
search_box.clear()
search_box.send_keys(f"{song} - {artist}")
search_box.send_keys(Keys.RETURN)
print(Fore.LIGHTYELLOW_EX + "Searching...\n")
# Wait for the search results to load
download_btn = WebDriverWait(driver, 20).until(
EC.presence_of_element_located((By.CSS_SELECTOR, "#results_t > tr:nth-child(1) > td:nth-child(3) > a:nth-child(1) > button:nth-child(1)"))
)
# Scroll to the button and click it
driver.execute_script("arguments[0].scrollIntoView();", download_btn)
time.sleep(1)
driver.execute_script("arguments[0].click();", download_btn)
print(Fore.LIGHTYELLOW_EX + "Song Found...\n")
# Check for the presence of CAPTCHA by looking for the reCAPTCHA iframe
try:
captcha_frame = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.XPATH, "//iframe[contains(@src, 'https://www.google.com/recaptcha/api2/anchor')]"))
)
captcha_present = True
except TimeoutException:
captcha_present = False
# If CAPTCHA is present, solve it
if captcha_present:
# Solve the CAPTCHA using CapSolver
site_key = "6LfzIW4UAAAAAM_JBVmQuuOAw4QEA1MfXVZuiO2A"
url = driver.current_url
payload = {
"clientKey": CAPSOLVER_API_KEY,
"task": {
"type": "ReCaptchaV2Task",
"websiteURL": url,
"websiteKey": site_key,
}
}
response = requests.post("https://api.capsolver.com/createTask", json=payload)
response_data = response.json()
if response_data.get("errorId") == 0:
task_id = response_data.get("taskId")
# Wait for the solution
while True:
solution_response = requests.post("https://api.capsolver.com/getTaskResult", json={
"clientKey": CAPSOLVER_API_KEY,
"taskId": task_id
})
solution_data = solution_response.json()
if solution_data.get("status") == "ready":
break
time.sleep(5)
# Input the solution into the CAPTCHA form
recaptcha_solution = solution_data.get("solution", {}).get("gRecaptchaResponse")
if recaptcha_solution:
driver.execute_script(f"document.getElementById('g-recaptcha-response').innerHTML='{recaptcha_solution}';")
print(Fore.LIGHTGREEN_EX + "CAPTCHA solved!\n")
# Click the final download button
final_download_btn = WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((By.CSS_SELECTOR, ".dl"))
)
final_download_btn.click()
print(Fore.LIGHTYELLOW_EX + "Attempting to Download...\n")
# Wait for the download to start
download_start_time = time.time()
download_directory = app.download_directory
while not is_download_active(download_directory) and time.time() - download_start_time < MAX_WAIT_TIME:
print(Fore.LIGHTYELLOW_EX + "Checking for active download...\n")
time.sleep(1)
if time.time() - download_start_time >= MAX_WAIT_TIME:
print(Fore.RED + f"Download for '{song} - {artist}' did not start in the expected time. Skipping...")
errors.append(f"{song} - {artist}")
failed_count += 1
return
# Wait for the download to finish
download_completion_time = time.time()
while is_download_active(download_directory) and time.time() - download_completion_time < MAX_WAIT_TIME:
print(Fore.LIGHTYELLOW_EX + "Waiting for the download to finish...\n")
time.sleep(5)
if time.time() - download_completion_time >= MAX_WAIT_TIME:
print(Fore.RED + f"Download for '{song} - {artist}' did not finish in the expected time. Skipping...")
errors.append(f"{song} - {artist}")
failed_count += 1
return
except TimeoutException:
print(f"Timeout error for {song} - {artist}")
failed_count += 1
except NoSuchElementException:
print(f"Element not found error for {song} - {artist}")
failed_count += 1
except ElementNotInteractableException:
print(f"Element not interactable error for {song} - {artist}")
failed_count += 1
except Exception as e:
print(f"General error for {song} - {artist}: {str(e)}")
failed_count += 1
# Return the elapsed time for the song download
return (datetime.now() - start_time).seconds
# Extract the songs and artists from the file
with open(app.playlist_path, 'r', encoding='utf-8-sig') as file:
content = file.readlines()
songs_and_artists = [(line.split(' - ')[0].strip(), line.split(' - ')[1].strip()) for line in content if ' - ' in line]
def format_time(seconds):
"""Format time to be readable, converting to hours/minutes/seconds when appropriate."""
hours = seconds // 3600
minutes = (seconds % 3600) // 60
seconds = seconds % 60
Python and Required Packages:
- Open Command Prompt and use Chocolatey and pip to install Python and the necessary packages:
choco install python
Geckodriver:
- Geckodriver is essential for the script to interface with Firefox to run headless in the background. Install it using:
choco install geckodriver
Modules:
- Use 'cd' in command prompt to change the directory to where you saved the requirements.txt file and run this command to install all dependencies:
pip install -r requirements.txt
2. Exporting Your Playlist:
TuneMyMusic:
- Head to TuneMyMusic.
- Sign in or create an account.
- Choose the platform/source from which you want to transfer a playlist.
- Log in to the account.
- Select the specific playlist you wish to transfer.
- As the destination, choose "Export to a File" at the bottom.
- Select to export to a `.txt` format.
- Download the exported `.txt` file.
3. CapSolver Configuration:
The FLAC Downloader utilizes the CapSolver API to automate the CAPTCHA solving process, ensuring a smooth downloading experience.
CapSolver charges $6 per 7500 CAPTCHA solves, so basically $0.80 for 1000 songs downloaded.