Created
December 8, 2024 02:39
-
-
Save mtfurlan/1fa16b4d00aff25dd32c5cbb2fe795fb to your computer and use it in GitHub Desktop.
fetch mixcloud mixes to mp3 and tag
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
#!/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