Skip to content

Instantly share code, notes, and snippets.

@mtfurlan
Created December 8, 2024 02:39
Show Gist options
  • Save mtfurlan/1fa16b4d00aff25dd32c5cbb2fe795fb to your computer and use it in GitHub Desktop.
Save mtfurlan/1fa16b4d00aff25dd32c5cbb2fe795fb to your computer and use it in GitHub Desktop.
fetch mixcloud mixes to mp3 and tag
#!/usr/bin/env bash
set -euo pipefail
# shellcheck disable=SC2120
h () {
# if arguments, print them
[ $# == 0 ] || echo "$*"
cat <<EOF
Usage: $(basename "${BASH_SOURCE[0]}") [OPTION]... <album> <mixcloud url>
download a mix from mixcloud and try to tag properly
album
Available options:
-h, --help display this help and exit
EOF
# if args, exit 1 else exit 0
[ $# == 0 ] || exit 1
exit 0
}
msg() {
echo >&2 -e "${*-}"
}
die() {
local msg=$1
local code=${2-1} # default exit status 1
msg "$msg"
exit "$code"
}
# getopt short options go together, long options have commas
TEMP=$(getopt -o h --long help -n "$0" -- "$@")
#shellcheck disable=SC2181
if [ $? != 0 ] ; then
die "something wrong with getopt"
fi
eval set -- "$TEMP"
while true ; do
case "$1" in
-h|--help) h ;;
--) shift ; break ;;
*) die "issue parsing args, unexpected argument '$1'!" ;;
esac
done
album=${1:-}
if [ -z "$album" ]; then
h "need to pass in album"
fi
url=${2:-}
if [ -z "$url" ]; then
h "need to pass in target url"
fi
#url looks like https://www.mixcloud.com/Ishkur/the-ultimate-hi-nrg-mix/
if [[ "$url" =~ https:\/\/www.mixcloud.com\/([^\/]*)\/([^\/]*)\/?$ ]]; then
username="${BASH_REMATCH[1]}"
slug="${BASH_REMATCH[2]}"
else
help "can't match url, either use better input or fix mathcing logic in script"
fi
msg "fetching $username's $slug, putting in album $album"
doGraphQL() {
username=$1
slug=$2
query=$3
headers=(
-H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:133.0) Gecko/20100101 Firefox/133.0'
-H 'Content-Type: application/json'
-H 'Cookie: mx_t=c5cbc44f_b2dd_4e29_afba_a2c9d9d6ed03; csrftoken=4VDmUTpAWggOM3xzMG8Ltx32glJE9kSg; ch=AhcwEy6RKoCd9Dg0Q4PUUj28STBYGAMBmW1E6jFLDMk; c="gAAAAABnVAZ6gC4sfT1vgYcsVh7JAZEsABUXW7TaOv4SlbZXPdyyZmBYriuDBcxCl_EvUUu_0bOc8KySKG5GNqmtUCwo-jq2Ycw3x7fGseN2_wb7GW5QB0VLhqPAdQMcEAOH8cJ1xXLFYVEz8u248zZKKV3F6NmqABHx5eJKFrUTJyKYVCYMn_k="'
)
curl -s 'https://app.mixcloud.com/graphql' \
-X POST \
"${headers[@]}" \
--data-raw "$( jq -cn \
--arg slug "$slug" \
--arg username "$username" \
--arg query "$query" \
'{id: "something", query: $query, variables: { lookup: { username: $username, slug: $slug}}}' )"
}
fetchSongList() {
username=$1
slug=$2
#shellcheck disable=SC2016
doGraphQL "$username" "$slug" \
'query TracklistAudioPageQuery(
$lookup: CloudcastLookup!
) {
cloudcast: cloudcastLookup(lookup: $lookup) {
canShowTracklist
featuringArtistList
moreFeaturingArtists
sections {
__typename
... on TrackSection {
__typename
artistName
songName
}
... on ChapterSection {
chapter
}
... on Node {
__isNode: __typename
id
}
}
id
}
}' \
| jq '[.data.cloudcast.sections[] | select(.artistName != null) | {artist: .artistName, song: .songName}]'
}
fetchDescriptionAndPicture() {
username=$1
slug=$2
#shellcheck disable=SC2016
doGraphQL "$username" "$slug" \
'query cloudcastQuery(
$lookup: CloudcastLookup!
) {
cloudcast: cloudcastLookup(lookup: $lookup) {
tags {
tag {
slug
}
}
name
description
publishDate
picture {
urlRoot
}
}
}
' \
| jq '.data.cloudcast | {tags: [.tags[] | .tag.slug], publishDate: .publishDate, name: .name, description: .description, picture: "https://thumbnailer.mixcloud.com/unsafe/666x666/\(.picture.urlRoot)"}'
}
songList=$(fetchSongList "$username" "$slug" | tee /tmp/songList)
meta=$(fetchDescriptionAndPicture "$username" "$slug" | tee /tmp/meta)
#songList=$(cat /tmp/songList)
#meta=$(cat /tmp/meta)
description="$(echo "$meta" | jq -r '.description')"
if [[ "$(echo "$songList" | jq 'length')" -gt 0 ]]; then
description+="
Incomplete Song List:
$(echo "$songList" | jq -r '.[] | "\(.artist) - \(.song)"')"
fi
title="$(echo "$meta" | jq -r '.name')"
image=$title.jpg
songFile="$title.mp3"
if [[ ! -f "$image" ]]; then
curl -s "$(echo "$meta" | jq -r '.picture')" > "$image"
fi
eyeD3Args=(
--to-v2.4
--remove-all
--artist="$(echo "$songList" | jq --arg artist "$username" -r '[{artist: $artist}] + . | [.[] | .artist] | join(", ")')"
--album-artist="$username"
--album="$album"
--title="$title"
--comment="$description"
--recording-date="$(date -d "$(echo "$meta" | jq -r '.publishDate')" +"%Y-%m-%d")"
--genre="$(echo "$meta" | jq -r '.tags | join("/")')"
--add-image="$image:OTHER:from mixcloud"
)
if [[ ! -f "$songFile" ]]; then
yt-dlp -x --audio-format mp3 --audio-quality 0 -o "$title.%(ext)s" "$url"
fi
eyeD3 "${eyeD3Args[@]}" "$songFile"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment