You are not logged in.

#1 2010-09-20 06:10:18

Wintervenom
Member
Registered: 2008-08-20
Posts: 1,011

Export or Make an M3U Playlist From Your Loved Tracks on Last.fm

Here is another little spawn of boredom that Last.fm users may enjoy.  This script will export your loved tracks to a text file, and, optionally, scan your music library and generate an M3U playlist from it.  Scroll down a bit for the parameter syntax.

As with the [chat log context script], I could probably use a bit lot of improvement in the efficiency everywhere department, so if you have any tips to give me, go for it!  tongue

EDIT:  New version, now uses Last.fm's XML data instead of parsing HTML.  It no longer explicitly depends on agrep from tre and will fall back to using fgrep.  Also, it will now only scan for common file-types, so it will no longer think your album artwork of the same name counts as a track.  wink

#!/bin/bash
### Last.fm Loved Tracks to Playlist ##
# Version 0.3 by Scott Garrett        #
# Winterenom [(at)] gmail.com         #
#######################################
# Optional dependencies:              #
# - agrep (from 'tre')                #
#   Much smarter matching than fgrep. #
#######################################

###############
### Globals ###
###############
scan_filetypes='mp[34]|flac|og[ga]|wma'
if [[ -d "/dev/shm" ]]; then
  # It's faster to read from RAM than a hard drive.
  out_temp="/dev/shm/$(basename "$0").tmp"
else
  out_temp="/tmp/$(basename "$0").tmp"
fi


#################
### Functions ###
#################
dout () {
  [[ "${DEBUG}" ]] && echo "DEBUG: $*"
}


############
### Main ###
############
# First, let's do some sanity-checking of the user's arguments.
if [[ -z "$2" || "$3" && -z "$4" ]]; then
  echo 'Last.fm Loved Tracks to Playlist Converter'
  echo 'Version 0.2 by Scott Garrett'
  echo
  echo "Syntax: $(basename "$0") USERNAME OUTFILE [LIBRARY [M3U_OUT]]"
  echo '   USERNAME: Last.fm username.'
  echo '    OUTFILE: Path to save loved tracks list.'
  echo '    LIBRARY: Path to music library to scan for tracks.'
  echo '         *!* Be sure to USE AN ABSOLUTE PATH if you do'
  echo '         *!* not a relative playlist!'
  echo '    M3U_OUT: Path to save loved tracks M3U playlist.'
  echo
  echo "Environment varaibles (careful or you'll shoot your eye out):"
  echo 'AGREP_PARAMS: Smart-matching parameters.'
  echo '              Default: 8wiB'
  echo '              man agrep for more info.'
  echo '    NO_AGREP: Do not use smart-matching.'
  echo '              Will use fgrep instead.'
  echo '              Results will be filtered by title then artist.'
  echo '              The first match will be used.'
  echo '              This can give you odd results.'
  echo '       DEBUG: Be more verbose.'
  exit 1
fi
if [[ ! -d "./$(dirname "$2")" ]]; then
  echo 'Not possible to write to outfile path:'
  echo "\"$(dirname "$2")\" is not a directory."
  exit 1
fi
if [[ "$3" ]]; then
  if [[ ! -d "$3" ]]; then
    echo 'Not possible to read from library path:'
    echo "\"$3\" is not a directory."
    exit 1
  fi
  if [[ ! -d "./$(dirname "$4")" ]]; then
    echo 'Not possible to write to M3U output path:'
    echo "\"$(dirname "$4")\" is not a directory."
    exit 1
  fi
fi

# Great, now we can do work.
username="$1"
out_loved="$2"
library="$3"
out_playlist="$4"
xml_uri="http://ws.audioscrobbler.com/2.0/?method=user.getlovedtracks&user=${username}&api_key=b25b959554ed76058ac220b7b2e0a026&limit=1000&page="
xml_page=1
dout "    Last.fm username: ${username}"
dout "   Track output path: ${out_loved}"
dout "  Music library path: ${library}"
dout "Playlist output path: ${out_playlist}"
dout "Tempfile output path: ${out_temp}"
dout "  Track List XML URI: ${xml_uri}"
dout " Track List XML page: ${xml_page}"
if [[ "${AGREP_PARAMS}" ]]; then
  [[ "${AGREP_PARAMS:0:1}" != '-' ]] && AGREP_PARAMS="-${AGREP_PARAMS}"
else
  AGREP_PARAMS='-4wiB'
fi
dout "    agrep parameters: ${AGREP_PARAMS}"
if ! which agrep &> /dev/null; then
  NO_AGREP=1
  dout "      Smart-matching: off (forced)"
else
  dout "      Smart-matching: $([[ -z "${NO_AGREP}" ]] && echo 'on' || echo 'off')"
fi

dout 'Removing temp and and track output files.'
rm -f "${out_temp}"
rm -f "${out_loved}"

while :; do
  echo "Retreiving page ${xml_page} of track list..."
  wget $([[ -z "${DEBUG}" ]] && echo '-q') "${xml_uri}${xml_page}" -O "${out_temp}"
  dout 'Formatting output...'
  # We only want the artists and song titles...
  fgrep '<name>' "${out_temp}" |
    # ...minus the tags and trailing whitespace...
    sed 's:<[^>]*>::g;s:^[ \t]*::' |
    # ...and in "${ARTIST} - ${TITLE}" format.
    awk '{ getline nxt; printf("%s|",nxt); print $0 }' >> "${out_loved}"
  # In case the user has a rediculous amount of loved tracks and we need to get the rest...
  [[ -z "${xml_pages}" ]] && xml_pages=$(grep -o 'totalPages=".*"' "${out_temp}" | sed 's:[^0-9]::g')
  dout " Track List XML page: ${xml_page} of ${xml_pages}"
  ((xml_page++ >= xml_pages)) && break
done

# Put everything in a neat order.
dout 'Sorting track list...'
sort -u "${out_loved}" > "${out_loved}.sorted"
mv "${out_loved}.sorted" "${out_loved}"

echo 'Done.'
echo

# If this is all the user wanted, we stop here.
# Otherwise, let's keep going!
if [[ "${library}" ]]; then
  echo
  dout 'Creating M3U playlist.'
  echo '#EXTM3U' > "${out_playlist}"
  echo 'Indexing and sorting music library...'
  find "${library}" -type f | egrep "\.(${scan_filetypes})$" | sort > "${out_temp}"
  echo 'Scanning your music library for matching songs.'
  if [[ "${NO_AGREP}" ]]; then
    echo 'WARNING: agrep fuzzy-matching disabled.'
    echo 'Using fgrep instead.  You might get strange results!'
  fi
  cat "${out_loved}" | while read entry; do
    entry="${entry/|/ - }"
    echo
    echo "Scanning for: ${entry}"
    if [[ -z "${NO_AGREP}" ]]; then
      dout 'Best matches from agrep:'
      [[ "${DEBUG}" ]] && agrep ${AGREP_PARAMS}s "${entry}" "${out_temp}" | awk '{print "DEBUG:", $0}'
      result=$(agrep ${AGREP_PARAMS} "${entry}" "${out_temp}" | head -1)
    else
      # Try to accomodate for user's library organization by splitting up fields.
      artist="${entry% - *}"
      title="${entry#* - }"
      # Since the list is already sorted, the first result is most likely correct.
      dout 'Matches from fgrep:'
      [[ "${DEBUG}" ]] && fgrep "${title}" "${out_temp}" | fgrep "${artist}" | awk '{print "DEBUG:", $0}'
      result=$(fgrep "${title}" "${out_temp}" | fgrep "${artist}" | head -1)
    fi
    if [[ "${result}" ]]; then
      echo "  Found: ${result}"
      echo "${result}" >> "${out_playlist}"
    else
      echo '  No relevant match.'
    fi
  done
  echo 'Done.'
fi
dout "Library index preserved at ${out_temp}"
[[ -z "${DEBUG}" ]] && rm -f "${out_temp}"

Last edited by Wintervenom (2010-09-20 17:31:55)

Offline

Board footer

Powered by FluxBB