packages feed

musicScroll 0.2.2.0 → 0.2.3.0

raw patch · 11 files changed

+160/−101 lines, 11 files

Files

CHANGELOG.md view
@@ -1,5 +1,9 @@ # Revision history for music-scroll +## 0.2.3.0++* Added other music providers+ ## 0.2.0.0  * SQLite cache
musicScroll.cabal view
@@ -4,7 +4,7 @@ -- http://haskell.org/cabal/users-guide/  name:                musicScroll-version:             0.2.2.0+version:             0.2.3.0 synopsis:            Supply your tunes info without leaving your music player.  description:         Automatically retrive the lyrics of of your current                      song on SMPlayer/VLC and update it on each change. See the@@ -52,12 +52,11 @@                        bytestring,                        directory,                        deepseq-  exposed-modules:     MusicScroll.UI,-                       MusicScroll.RealMain+  exposed-modules:     MusicScroll.RealMain+  other-modules:       MusicScroll.UI                        MusicScroll.MPRIS-                       MusicScroll.AZLyrics+                       MusicScroll.Web                        MusicScroll.LyricsPipeline-                       MusicScroll.TagParsing                        MusicScroll.TrackInfo                        MusicScroll.UIEvent                        MusicScroll.DBusNames@@ -65,7 +64,10 @@                        MusicScroll.ConnState                        MusicScroll.DatabaseUtils                        MusicScroll.TrackSuplement-  other-modules:       Paths_musicScroll+                       MusicScroll.Providers.AZLyrics+                       MusicScroll.Providers.MusiXMatch+                       MusicScroll.Providers.Utils+                       Paths_musicScroll  -- test-suite playground --     type:       exitcode-stdio-1.0
− src/MusicScroll/AZLyrics.hs
@@ -1,44 +0,0 @@-{-# language OverloadedStrings, DataKinds, NamedFieldPuns, TypeApplications #-}-module MusicScroll.AZLyrics (getLyricsFromWeb) where--import Control.Exception (try, SomeException)-import Control.Applicative (Alternative(empty))-import Control.Monad.Trans.Reader (ReaderT)-import Control.Monad.IO.Class (MonadIO(..))-import Data.Text (Text)-import Data.Text as T hiding (filter, tail, map, empty)-import Data.Text.Encoding (decodeUtf8)-import Network.HTTP.Req-import Database.SQLite.Simple (Connection)--import MusicScroll.TrackInfo (TrackInfo(..))-import MusicScroll.TagParsing-import MusicScroll.DatabaseUtils (insertDBLyrics)--getLyricsFromWeb :: TrackInfo -> ReaderT Connection IO Lyrics-getLyricsFromWeb track@(TrackInfo {tArtist, tTitle}) =-  do let songUrl = toUrl tArtist tTitle-     resp <- liftIO $ try @SomeException (getPage songUrl)-     let notValid = either (const True)-                      ((/= 200) . responseStatusCode) resp-     if notValid then empty-       else let Right realResp = resp-                body   = decodeUtf8 (responseBody realResp)-                lyrics = extractLyricsFromPage body-            in insertDBLyrics track lyrics *> pure lyrics--getPage :: Url 'Https -> IO BsResponse-getPage url = runReq defaultHttpConfig $-  req GET url NoReqBody bsResponse mempty--toUrl :: Text -> Text -> Url 'Https-toUrl artist song =-  let base :: Url 'Https-      base = https "www.azlyrics.com"--      quotedArtist = normalize artist-      quotedSong = normalize song <> ".html"-  in base /: "lyrics" /: quotedArtist /: quotedSong--normalize :: Text -> Text-normalize = let noSpaces = replace " " "" in noSpaces . toLower
src/MusicScroll/DatabaseUtils.hs view
@@ -23,7 +23,7 @@ import           System.Directory (createDirectory)  import           MusicScroll.TrackInfo (TrackInfo(..), SongFilePath)-import           MusicScroll.TagParsing (Lyrics(..))+import           MusicScroll.Providers.Utils (Lyrics(..))  getDBLyrics :: SongFilePath -> ReaderT Connection IO Lyrics getDBLyrics songUrl = snd <$> getDBSong songUrl
src/MusicScroll/LyricsPipeline.hs view
@@ -17,10 +17,12 @@ import Data.Maybe (isJust) import Database.SQLite.Simple -import MusicScroll.DatabaseUtils +import MusicScroll.DatabaseUtils import MusicScroll.TrackInfo import MusicScroll.TrackSuplement-import MusicScroll.AZLyrics (getLyricsFromWeb)+import MusicScroll.Web (getLyricsFromWeb)+import MusicScroll.Providers.AZLyrics (azLyricsInstance)+import MusicScroll.Providers.MusiXMatch (musiXMatchInstance) import MusicScroll.UIEvent  sizeOfQueue :: Natural@@ -77,7 +79,9 @@  caseByInfo :: TrackInfo -> ReaderT Connection IO UIEvent caseByInfo track =-  let tryGetLyrics = getDBLyrics (tUrl track) <|> getLyricsFromWeb track+  let tryGetLyrics = getDBLyrics (tUrl track)+                     <|> getLyricsFromWeb azLyricsInstance track+                     <|> getLyricsFromWeb musiXMatchInstance track   in (GotLyric track <$> tryGetLyrics) <|> pure (ErrorOn (NoLyricsOnWeb track))  caseByPath :: TrackByPath -> ReaderT Connection IO UIEvent
+ src/MusicScroll/Providers/AZLyrics.hs view
@@ -0,0 +1,43 @@+{-# language OverloadedStrings, DataKinds #-}+module MusicScroll.Providers.AZLyrics (azLyricsInstance) where++import Control.Category hiding ((.), id)+import Data.Maybe (catMaybes)+import Data.Text (Text)+import Data.Text as T hiding (filter, tail, map, mapAccumL)+-- import Data.Text.IO as T (readFile)+import Data.Traversable (mapAccumL)+import Network.HTTP.Req+import Text.HTML.TagSoup+import Text.HTML.TagSoup.Match (tagOpenLit)++import MusicScroll.TrackInfo (TrackInfo(..))+import MusicScroll.Providers.Utils++azLyricsInstance :: Provider+azLyricsInstance = Provider+  { toUrl = toUrl'+  , extractLyricsFromPage = pipeline }++toUrl' :: TrackInfo -> Url 'Https+toUrl' track =+  let base :: Url 'Https+      base = https "www.azlyrics.com"++      quotedArtist = normalize (tArtist track)+      quotedSong = normalize (tTitle track) <> ".html"+  in base /: "lyrics" /: quotedArtist /: quotedSong++normalize :: Text -> Text+normalize = let noSpaces = replace " " "" in noSpaces . toLower++pipeline :: Text -> Lyrics+pipeline = parseTags >>> mapAccumL discriminate False >>> snd+             >>> catMaybes >>> innerText >>> stripStart >>> Lyrics++discriminate :: Bool -> Tag Text -> (Bool, Maybe (Tag Text))+discriminate onDiv@True tag | isTagText tag = (onDiv, pure tag)+discriminate onDiv tag+  | tagOpenLit "div" (== []) tag  = (True, Nothing)+  | isTagCloseName "div" tag      = (False, Nothing)+  | otherwise                     = (onDiv, Nothing)
+ src/MusicScroll/Providers/MusiXMatch.hs view
@@ -0,0 +1,51 @@+{-# language OverloadedStrings, DataKinds #-}+module MusicScroll.Providers.MusiXMatch (musiXMatchInstance) where++import Control.Category hiding ((.))+import Data.Maybe (catMaybes)+import Data.Text (Text, replace, toTitle)+import Network.HTTP.Req+import Text.HTML.TagSoup+import Text.HTML.TagSoup.Match (tagOpenAttrLit)+import Data.Traversable (mapAccumL)+-- import Data.Text.IO as T (readFile)++import MusicScroll.TrackInfo (TrackInfo(..))+import MusicScroll.Providers.Utils++musiXMatchInstance :: Provider+musiXMatchInstance = Provider+  { toUrl = toUrl'+  , extractLyricsFromPage = pipeline }++toUrl' :: TrackInfo -> Url 'Https+toUrl' track =+  let base :: Url 'Https+      base = https "www.musixmatch.com"++      quotedArtist = normalize (tArtist track)+      quotedSong = normalize (tTitle track)+  in base /: "lyrics" /: quotedArtist /: quotedSong++normalize :: Text -> Text+normalize = let noSpaces = replace " " "-" in noSpaces . toTitle++-- exampleTrack = TrackInfo "hey jude" "the beatles" "/home"++-- testOnFile fp =+--   do contents <- T.readFile fp+--      return (pipeline contents)++pipeline :: Text -> Lyrics+pipeline = parseTags >>> mapAccumL discriminate False+             >>> snd >>> catMaybes >>> innerText >>> Lyrics++discriminate :: Bool -> Tag Text -> (Bool, Maybe (Tag Text))+discriminate onSpan@True tag | isTagText tag = (onSpan, pure tag)+discriminate onSpan tag+  | tagOpenAttrLit "span" spanDiscr tag = (True, Nothing)+  | isTagCloseName "span" tag           = (False, Nothing)+  | otherwise                           = (onSpan, Nothing)+  where+    spanDiscr = ("class", "lyrics__content__ok")+
+ src/MusicScroll/Providers/Utils.hs view
@@ -0,0 +1,13 @@+{-# language DataKinds #-}+module MusicScroll.Providers.Utils where++import Data.Text (Text)+import MusicScroll.TrackInfo (TrackInfo)+import Network.HTTP.Req++newtype Lyrics = Lyrics Text++data Provider = Provider+  { toUrl :: TrackInfo -> Url 'Https+  , extractLyricsFromPage :: Text -> Lyrics+  }
− src/MusicScroll/TagParsing.hs
@@ -1,46 +0,0 @@-{-# language OverloadedStrings #-}-module MusicScroll.TagParsing (Lyrics(..), extractLyricsFromPage) where--import qualified Data.Char as C-import           Data.Text (Text)-import           Data.Text as T hiding (filter, tail, map)-import           Text.HTML.TagSoup--newtype Lyrics = Lyrics Text--extractLyricsFromPage :: Text -> Lyrics-extractLyricsFromPage page =-  let stream = parseTags page-      pass1  = flip filter stream-        (\t -> (not (isScript t)) && noEmptyText t && validTags t)-      stream2 = zip3 pass1 (tail pass1) (tail (tail pass1))-      pass2   = map (\(t, _, _) -> t) $ filter isStrophe stream2-  in Lyrics . T.unlines . cleanOut $ pass2---- Pass 1-validTags, noEmptyText :: Tag Text -> Bool-validTags t = isTagOpenName "br" t   || isTagCloseName "div" t-            || isTagOpenName "div" t || isTagCloseName "div" t-            || isTagText t--noEmptyText =-  let invalidChars c = C.isSpace c || c == '\\' || c == 'n'-                       || c == 'r' || c == 't'-  in maybe True (not . T.all invalidChars) . maybeTagText---- Pass 2-isScript :: Tag Text -> Bool-isScript = let invalidChars c = c == '>' || c == '{'-           in maybe False (T.any invalidChars) . maybeTagText---- Pass 3-isStrophe :: (Tag Text, Tag Text, Tag Text) -> Bool-isStrophe (TagText _, TagOpen "br" _, TagText _) = True-isStrophe (TagText _, TagOpen "br" _, TagOpen "br" _) = True-isStrophe (TagOpen "br" _, TagOpen "br" _, _) = True -- breakline-isStrophe _ = False---- Cleaning output-cleanOut :: [Tag Text] -> [Text]-cleanOut =   map (T.strip . T.replace "\\n" "")-           . map (maybe "\n" id . maybeTagText)
src/MusicScroll/UIEvent.hs view
@@ -9,7 +9,7 @@ import           Data.GI.Gtk.Threading (postGUISync)  import           MusicScroll.TrackInfo (TrackInfo(..), TrackByPath(..))-import           MusicScroll.TagParsing (Lyrics(..))+import           MusicScroll.Providers.Utils (Lyrics(..))  data UIEvent = GotLyric TrackInfo Lyrics              | ErrorOn ErrorCause
+ src/MusicScroll/Web.hs view
@@ -0,0 +1,32 @@+{-# language OverloadedStrings, NamedFieldPuns, RecordWildCards #-}+{-# language TypeApplications, DataKinds #-}+module MusicScroll.Web (getLyricsFromWeb) where++import Control.Exception (try, SomeException)+import Control.Applicative (Alternative(empty))+import Control.Monad.Trans.Reader (ReaderT)+import Control.Monad.IO.Class (MonadIO(..))+import Data.Text.Encoding (decodeUtf8)+import Network.HTTP.Req+import Database.SQLite.Simple (Connection)++import MusicScroll.TrackInfo (TrackInfo(..))+import MusicScroll.DatabaseUtils (insertDBLyrics)+import MusicScroll.Providers.Utils++getLyricsFromWeb :: Provider -> TrackInfo -> ReaderT Connection IO Lyrics+getLyricsFromWeb (Provider {..}) track =+  do let songUrl = toUrl track+     resp <- liftIO $ try @SomeException (getPage songUrl)+     let notValid = either (const True)+                      ((/= 200) . responseStatusCode) resp+     if notValid then empty+       else let Right realResp = resp+                body   = decodeUtf8 (responseBody realResp)+                lyrics = extractLyricsFromPage body+            in insertDBLyrics track lyrics *> pure lyrics++getPage :: Url 'Https -> IO BsResponse+getPage url = runReq defaultHttpConfig $+  req GET url NoReqBody bsResponse mempty+