defmodule LastfmArchive.Transform do @moduledoc """ This module provides functions for reading and transforming downloaded Lastfm data. """ alias LastfmArchive.Utils @default_delimiter "\t" @tsv_headers "id\tname\tscrobble_date\tscrobble_date_iso\tmbid\turl\tartist\tartist_mbid\tartist_url\talbum\talbum_mbid" @doc """ Transform a downloaded raw JSON page into a list of tab-delimited track data. ### Example ``` # transform a page of scrobbles from the file archive LastfmArchive.Transform.transform("a_lastfm_user", "2007/200_1.gz") ``` A row of tab-delimited track currently contains (if any): - `id` auto-generated by the system to uniquely identify a scrobble - `name` the track name - `scrobble_date` Unix timestamp of the scrobble date - `scrobble_date_iso` scrobble date in ISO 8601 datetime format - `mbid` MusicBrainz identifier for the track - `url` web address of the track on Last.fm - `artist` - `artist_mbid` MusicBrainz identifier for the artist - `artist_url` web address of the artist on Last.fm - `album` - `album_mbid` MusicBrainz identifier for the album """ @spec transform(binary, binary, :atom) :: list(binary) | {:error, :file.posix()} def transform(user, filename, mode \\ :tsv) def transform(user, filename, :tsv) do case Utils.read(user, filename) do {:ok, resp} -> tracks = resp |> Jason.decode!() index = initial_index(tracks["recenttracks"]["@attr"]) [track | rest] = tracks["recenttracks"]["track"] if track["@attr"]["nowplaying"], do: _transform(user, rest, index, [@tsv_headers]), else: _transform(user, tracks, index, [@tsv_headers]) error -> error end end def tsv_headers(), do: @tsv_headers defp _transform(_user, [], _index, acc), do: acc defp _transform(user, [track | tracks], index, acc) do next_index = index + 1 _transform(user, tracks, next_index, acc ++ [_transform(user, track, index)]) end # id,name,scrobble_date,date_iso,mbid,url,artist,artist_mbid,artist_url,album,album_mbid defp _transform(user, track, index) do id = "#{user}_#{track["date"]["uts"]}_#{index |> to_string}" uts = if is_binary(track["date"]["uts"]), do: String.to_integer(track["date"]["uts"]), else: track["date"]["uts"] date_s = uts |> DateTime.from_unix!() |> DateTime.to_iso8601() track_info = [id, track["name"] |> String.trim(), track["date"]["uts"], date_s, track["mbid"], track["url"]] artist_info = [track["artist"]["name"], track["artist"]["mbid"], track["artist"]["url"]] album_info = [track["album"]["#text"], track["album"]["mbid"]] Enum.join(track_info ++ artist_info ++ album_info, @default_delimiter) end defp initial_index(%{"page" => page, "perPage" => per_page}) when is_binary(page) and is_binary(per_page) do (String.to_integer(page) - 1) * String.to_integer(per_page) + 1 end defp initial_index(info), do: (info["page"] - 1) * info["perPage"] + 1 end