packages feed

netease-fm-1.2.2: lib/FM/Song.hs

{-# LANGUAGE RecordWildCards #-}

module FM.Song (
  SongId
, Song (..)
, Lyrics (..)
, parseLyrics
) where

import Data.Default.Class
import Data.List (sort, intercalate)
import Text.Parsec

type SongId = Int

data Song = Song {
  uid     :: SongId
, title   :: String
, artists :: [String]
, album   :: String
, url     :: Maybe String
}

newtype Lyrics = Lyrics [(Double, String)]

instance Show Song where
  show Song {..} = unlines [ "Song {"
                           , "  uid = " ++ show uid ++ ","
                           , "  title = " ++ title ++ ","
                           , "  artists = " ++ dumpList artists ++ ","
                           , "  album = " ++ album ++ ","
                           , "  url = " ++ show url ++ ","
                           , "}"
                           ]
    where
      dumpList :: [String] -> String
      dumpList [] = "(null)"
      dumpList xs = intercalate " / " xs

instance Show Lyrics where
  show (Lyrics lyrics) = unlines [ "Lyrics {", unlines $ map (\(t, l) -> "  [" ++ show t ++ "]" ++ l) lyrics, "}" ]

instance Default Lyrics where
  def = Lyrics [ (0, "No Lyrics") ]

type Parser = Parsec String ()

parseLyrics :: [String] -> Lyrics
parseLyrics = Lyrics . sort . concatMap (either (const []) id . runParser parser () [])
  where
    number :: Parser Double
    number = read <$> do
      a <- many1 digit
      b <- option [] $ try $ do
        char '.'
        ('.' :) <$> many1 digit
      return (a ++ b)

    parser :: Parser [(Double, String)]
    parser = do
      times <- many1 $ between (char '[') (char ']') $ do
        mm <- number
        char ':'
        ss <- number
        return $ mm * 60 + ss
      body <- manyTill anyToken eof
      return $ zip times $ repeat body