Last active
January 27, 2025 14:20
-
-
Save SixBeeps/1632ae049c93f34ddf0feec4c673066f to your computer and use it in GitHub Desktop.
Mixxx Scrobbler
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
# Mixxx Scrobbler - SixBeeps 2024 | |
# I can't be bothered to write a proper license, so do whatever you want with this code. | |
# Steps: | |
# | |
# 1. Grab an API key and secret, for Last.fm go to https://www.last.fm/api/account/create | |
# 2. Install the pylast library with `pip install pylast` | |
# 3. Create a .env file in the same directory as this script with the following contents | |
# API_KEY=your_api_key | |
# API_SECRET=your_api_secret | |
# NETWORK=lastfm OR librefm | |
# NETWORK_USERNAME=your_username | |
# NETWORK_PASSWORD=your_password (optional, if not provided, the script will ask for it) | |
# 4. (Optional) Create a file named regexes.json in the same directory as this script with the following contents | |
# { | |
# "artist": { | |
# "test_regex": "replace_regex" | |
# }, | |
# "title": { | |
# "test_regex": "replace_regex" | |
# } | |
# } | |
# This file is used to replace artist and title names with regexes before scrobbling | |
# 5. Run the script with the cue file as an argument | |
# python mixxx_scrobbler.py /path/to/cuefile.cue | |
import getpass | |
import json | |
import os | |
import pylast | |
import re | |
import sys | |
import time | |
from dotenv import load_dotenv | |
# Load the environment variables | |
load_dotenv() | |
API_KEY = os.getenv("API_KEY") | |
API_SECRET = os.getenv("API_SECRET") | |
NETWORK_USERNAME = os.getenv("NETWORK_USERNAME") | |
USE_NETWORK = os.getenv("NETWORK") or "lastfm" | |
# Check if the environment variables are set correctly | |
if API_KEY is None or API_SECRET is None: | |
print("Please set the API_KEY and API_SECRET environment variables in the .env file.") | |
exit() | |
if len(sys.argv) < 2: | |
print("Please provide the cue file as a command line argument.") | |
exit() | |
if USE_NETWORK not in ["lastfm", "librefm"]: | |
print("NETWORK must be either lastfm or librefm.") | |
exit() | |
# Ask for the password | |
password = pylast.md5(os.getenv("NETWORK_PASSWORD")) | |
if password is None: | |
password = pylast.md5(getpass.getpass(f"Please enter the password for {NETWORK_USERNAME} on {USE_NETWORK}: ")) | |
# Connect to the network | |
network = (pylast.LastFMNetwork(api_key=API_KEY, api_secret=API_SECRET, username=NETWORK_USERNAME, password_hash=password) if USE_NETWORK == "lastfm" | |
else pylast.LibreFMNetwork(api_key=API_KEY, api_secret=API_SECRET, username=NETWORK_USERNAME, password_hash=password) if USE_NETWORK == "librefm" | |
else None) | |
# Load the regexes | |
regexes = { | |
"artist": {}, | |
"title": {} | |
} | |
if os.path.exists("regexes.json"): | |
with open("regexes.json", "r") as f: | |
regexes = json.load(f) | |
# Find the time when the cue file was created | |
# This is used to calculate the scrobble time | |
created = os.path.getctime(sys.argv[1]) | |
# Parse the cue file | |
cue_file = sys.argv[1] | |
tracks = [] | |
with open(cue_file, "r") as f: | |
lines = f.readlines() | |
title, artist, timestamp = None, None, None | |
def try_scrobble(title, artist, timestamp): | |
if title is not None and artist is not None: | |
# Apply regexes | |
for key in regexes["artist"]: | |
artist = re.sub(key, regexes["artist"][key], artist) | |
for key in regexes["title"]: | |
title = re.sub(key, regexes["title"][key], title) | |
# Scrobble the track | |
print(f"Scrobbling {artist} - {title} at {time.ctime(created + timestamp)}") | |
tracks.append({ | |
"title": title, | |
"artist": artist, | |
"timestamp": int(created + timestamp) | |
}) | |
for line in lines: | |
line = line.lstrip() | |
if line.startswith("TRACK"): | |
# Scrobble the previous track | |
try_scrobble(title, artist, timestamp) | |
title, artist, timestamp = None, None, None | |
elif line.startswith("TITLE"): | |
title = ' '.join(line.split(" ")[1:]).strip().replace('"', "") | |
elif line.startswith("PERFORMER"): | |
artist = ' '.join(line.split(" ")[1:]).strip().replace('"', "") | |
elif line.startswith("INDEX"): | |
# MM:SS:FF to Epoch time | |
friendly_time = line.split(" ")[2].strip().split(":") | |
timestamp = int(friendly_time[0]) * 60 + int(friendly_time[1]) + int(friendly_time[2]) / 75 | |
# Scrobble the last track | |
try_scrobble(title, artist, timestamp) | |
# Scrobble the tracks | |
print(f"Scrobbling {len(tracks)} tracks...") | |
network.scrobble_many(tracks) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment