{-# LANGUAGE OverloadedStrings #-}
module Main ( main ) where
import qualified Network.MPD as MPD
import Network.MPD
( Metadata(..), PlaybackState(Stopped, Playing, Paused) )
import Data.Aeson ( object, KeyValue((.=)) )
import Data.Aeson.Encode.Pretty
( defConfig, encodePretty', keyOrder, Config(confCompare) )
import qualified Data.ByteString.Lazy.Char8 as C
import Text.Printf ( printf )
import Options
( optsParserInfo, execParser, Opts(optPass, optHost, optPort) )
import Network.MPD.Parse ( getStatusItem
, getTag
, maybePathCurrentSong
, maybePathNextPlaylistSong
, (.=?)
, objectJson
, getStatusIdInt )
import Text.Read (readMaybe)
import Data.Maybe (fromMaybe)
{- | Where the program connects to MPD and uses the helper functions to
extract values, organize them into a list of key/value pairs, make
them a 'Data.Aeson.Value' using 'Data.Aeson.object', then encode it to
a conventional JSON @ByteString@ with
'Data.Aeson.Encode.Pretty.encodePretty' for the pretty-print version.
-}
main :: IO ()
main = do
opts <- execParser optsParserInfo
let withMpdOpts = MPD.withMPDEx (optHost opts) (optPort opts) (optPass opts)
cs <- withMpdOpts MPD.currentSong
st <- withMpdOpts MPD.status
let artist = getTag Artist cs
artistSort = getTag ArtistSort cs
album = getTag Album cs
albumSort = getTag AlbumSort cs
albumArtist = getTag AlbumArtist cs
albumArtistSort = getTag AlbumArtistSort cs
title = getTag Title cs
track = getTag Track cs
name = getTag Name cs
genre = getTag Genre cs
date = getTag Date cs
originalDate = getTag OriginalDate cs
composer = getTag Composer cs
performer = getTag Performer cs
conductor = getTag Conductor cs
work = getTag Work cs
grouping = getTag Grouping cs
comment = getTag Comment cs
disc = getTag Disc cs
label = getTag Label cs
musicbrainz_Artistid = getTag MUSICBRAINZ_ARTISTID cs
musicbrainz_Albumid = getTag MUSICBRAINZ_ALBUMID cs
musicbrainz_Albumartistid = getTag MUSICBRAINZ_ALBUMARTISTID cs
musicbrainz_Trackid = getTag MUSICBRAINZ_TRACKID cs
musicbrainz_Releasetrackid = getTag MUSICBRAINZ_RELEASETRACKID cs
musicbrainz_Workid = getTag MUSICBRAINZ_WORKID cs
let state :: Maybe String
state = case getStatusItem st MPD.stState of
Just ps -> case ps of
Playing -> Just "playing"
Paused -> Just "paused"
Stopped -> Just "stopped"
Nothing -> Nothing
time = getStatusItem st MPD.stTime
elapsed = case time of
Just t -> case t of
Just (e, _) -> Just e
_noTag -> Nothing
Nothing -> Nothing
duration = case time of
Just t -> case t of
Just (_, d) -> Just d
_noTag -> Nothing
Nothing -> Nothing
elapsedPercent :: Maybe Double
elapsedPercent = case time of
Just t -> case t of
Just t1 -> readMaybe $ printf "%.2f" (uncurry (/) t1 * 100)
Nothing -> Just 0
Nothing -> Nothing
repeatSt = getStatusItem st MPD.stRepeat
randomSt = getStatusItem st MPD.stRandom
singleSt = getStatusItem st MPD.stSingle
consumeSt = getStatusItem st MPD.stConsume
bitrate = getStatusItem st MPD.stBitrate
audioFormat = getStatusItem st MPD.stAudio
errorSt = getStatusItem st MPD.stError
-- positon is an index starting from 0. Id starts from 1
let pos = getStatusItem st MPD.stSongPos
nextPos = fromMaybe Nothing $ getStatusItem st MPD.stNextSongPos
songId = getStatusIdInt MPD.stSongID st
nextId = getStatusIdInt MPD.stNextSongID st
playlistLength = getStatusItem st MPD.stPlaylistLength
nextPlaylistSong <- withMpdOpts $ MPD.playlistInfo nextId
let filename = maybePathCurrentSong cs
filenameNext = maybePathNextPlaylistSong nextPlaylistSong
-- sgTags
let jTags = objectJson
[ "artist" .=? artist
, "artist_sort" .=? artistSort
, "album" .=? album
, "album_sort" .=? albumSort
, "album_artist" .=? albumArtist
, "album_artist_sort" .=? albumArtistSort
, "title" .=? title
, "track" .=? track
, "name" .=? name
, "genre" .=? genre
, "date" .=? date
, "original_date" .=? originalDate
, "composer" .=? composer
, "performer" .=? performer
, "conductor" .=? conductor
, "work" .=? work
, "grouping" .=? grouping
, "comment" .=? comment
, "disc" .=? disc
, "label" .=? label
, "musicbrainz_artistid" .=? musicbrainz_Artistid
, "musicbrainz_albumid" .=? musicbrainz_Albumid
, "musicbrainz_albumartistid" .=? musicbrainz_Albumartistid
, "musicbrainz_trackid" .=? musicbrainz_Trackid
, "musicbrainz_releasetrackid" .=? musicbrainz_Releasetrackid
, "musicbrainz_workid" .=? musicbrainz_Workid
]
-- status
let jStatus = objectJson
[ "state" .=? state
, "repeat" .=? repeatSt
, "random" .=? randomSt
, "single" .=? singleSt
, "consume" .=? consumeSt
, "duration" .=? duration
, "elapsed" .=? elapsed
, "elapsed_percent" .=? elapsedPercent
, "audio_format" .=? audioFormat
, "bitrate" .=? bitrate
, "error" .=? errorSt
]
-- let jFilename = objectJson [ "file" .=? filename ]
let jPlaylist = objectJson
[ "position" .=? pos -- current song position
, "next_position" .=? nextPos
, "id" .=? songId -- current song id
, "next_id" .=? nextId
, "length" .=? playlistLength
]
let jObject = object [ "filename" .= filename
, "next_filename" .= filenameNext
, "playlist" .= jPlaylist
, "status" .= jStatus
, "tags" .= jTags
]
C.putStrLn $ encodePretty' customEncodeConf jObject
customEncodeConf :: Config
customEncodeConf = defConfig
{ confCompare = keyOrder [ "title", "name"
, "artist", "album_artist", "artist_sort", "album_artist_sort"
, "album", "album_sort"
, "track", "disc"
, "date", "original_date"
, "genre", "composer", "performer", "conductor"
, "work", "grouping", "label"
, "comment"
, "musicbrainz_artistid"
, "musicbrainz_albumid"
, "musicbrainz_albumartistid"
, "musicbrainz_trackid"
, "musicbrainz_releasetrackid"
, "musicbrainz_workid"
-- status
, "state", "repeat", "random", "single", "consume"
, "duration", "elapsed", "elapsed_percent"
, "audio_format", "bitrate"
, "error"
-- playlist
, "position", "next_position", "id", "next_id"
, "length"
]
}