playlists 0.4.1.0 → 0.5.0.0
raw patch · 9 files changed
+97/−48 lines, 9 filesdep ~optparse-applicativePVP ok
version bump matches the API change (PVP)
Dependency ranges changed: optparse-applicative
API changes (from Hackage documentation)
+ Text.Playlist: [trackDuration] :: Track -> Maybe Float
- Text.Playlist: Track :: Text -> Maybe Text -> Track
+ Text.Playlist: Track :: Text -> Maybe Text -> Maybe Float -> Track
Files
- CHANGES.md +6/−0
- playlists.cabal +3/−3
- src/Text/Playlist/M3U/Reader.hs +32/−27
- src/Text/Playlist/M3U/Writer.hs +20/−6
- src/Text/Playlist/PLS/Reader.hs +7/−4
- src/Text/Playlist/PLS/Writer.hs +13/−1
- src/Text/Playlist/Types.hs +3/−2
- test/Examples.hs +10/−2
- test/ResolveSpec.hs +3/−3
CHANGES.md view
@@ -1,3 +1,9 @@+# Version 0.5.0.0 (December 1, 2017)++ * Add support for track length (thanks to Catherine Galkina)++ * Widen dependency versions to latest available.+ # Version 0.4.1.0 (February 5, 2017) * Remove redundant constraints as reported by GHC 8.X.
playlists.cabal view
@@ -1,5 +1,5 @@ name: playlists-version: 0.4.1.0+version: 0.5.0.0 synopsis: Library and executable for working with playlist files. homepage: https://github.com/pjones/playlists license: BSD3@@ -10,7 +10,7 @@ category: Text build-type: Simple cabal-version: >= 1.18-tested-with: GHC==7.10.3, GHC==8.0.1+tested-with: GHC==7.10.3, GHC==8.0.2 description: Playlists is a library for working with media playlist files. The original motivation for the library was extracting URLs for@@ -104,7 +104,7 @@ build-depends: base , bytestring- , optparse-applicative >= 0.10.0 && < 0.14.0+ , optparse-applicative >= 0.10 && < 0.15 , playlists , text
src/Text/Playlist/M3U/Reader.hs view
@@ -16,11 +16,12 @@ -------------------------------------------------------------------------------- import Control.Applicative-import Control.Monad (msum, void)+import Control.Monad (void) import Data.Attoparsec.ByteString+import Data.Attoparsec.ByteString.Char8 (signed, double)+import Data.Maybe (catMaybes) import Data.Text (Text) import Data.Text.Encoding (decodeUtf8)-import Data.Word8 (isDigit) import Text.Playlist.Internal.Attoparsec import Text.Playlist.Types @@ -29,21 +30,25 @@ parsePlaylist :: Parser Playlist parsePlaylist = do ts <- many1 parseTrack- void (many' commentOrTitle) -- Trailing comments.+ void (many' commentOrDirective) -- Trailing comments. return ts -------------------------------------------------------------------------------- -- | Parser for a single track in a M3U file. parseTrack :: Parser Track parseTrack = do- -- Get the title closest to the URL or Nothing.- title <- msum . reverse <$> many' commentOrTitle+ -- Get the length and title closest to the URL or Nothing.+ (title, len) <- maybeTitleAndLength . reverse <$> (many' commentOrDirective) url <- parseURL-- return Track { trackURL = url- , trackTitle = title+ return Track { trackURL = url+ , trackTitle = title+ , trackDuration = len }-+ where+ maybeTitleAndLength lst =+ case catMaybes lst of+ [] -> (Nothing, Nothing)+ x : _ -> x -------------------------------------------------------------------------------- -- | Parser for URL or file name in a M3U file. The URL is the entire -- line so this parser extracts the entire line and decodes it.@@ -54,21 +59,21 @@ -- | Comment parser with a twist. In the extended M3U format metadata -- for a track can be placed in a comment that appears just before the -- URL. This parser succeeds if the current line is a comment, and--- always skips over the entire comment. If the comment represents a--- track title then that information will be returned in a @Just@. If--- it's just a regular comment then @Nothing@ is returned.-commentOrTitle :: Parser (Maybe Text)-commentOrTitle = do- skipSpace- skip (== 35) -- Comment character "#"- istitle <- (string "EXTINF:" >> return True) <|> return False- if istitle then title <|> comment else comment- where- comment = skipLine >> return Nothing- title = do skip (== 45) <|> return () -- Skip optional negative sign.- skipWhile isDigit -- Skip length.- skip (== 44) -- Skip comma.- skipSpace- text <- decodeUtf8 <$> takeWhile1 (not . isEOL)- skipSpace- return (Just text)+-- always skips over the entire comment. If the comment represents an+-- EXTINF directive then that information will be returned in a @Just@.+-- If it's just a regular comment then @Nothing@ is returned.+commentOrDirective :: Parser (Maybe (Maybe Text, Maybe Float))+commentOrDirective = do+ skipSpace+ skip (== 35) -- Comment character "#"+ isDirective <- (string "EXTINF:" >> return True) <|> return False+ if isDirective then directive <|> comment else comment+ where+ comment = skipLine >> return Nothing+ directive = do+ mlen <- (Just . realToFrac <$> signed double) <|> return Nothing -- Parse length.+ skip (== 44) -- Skip comma.+ mtext <- (Just . decodeUtf8 <$> takeWhile1 (not . isEOL)) <|> (return Nothing)+ skipLine+ return (Just (mtext, mlen))+
src/Text/Playlist/M3U/Writer.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-} {- @@ -29,14 +30,27 @@ -------------------------------------------------------------------------------- writeTrack :: Track -> Builder writeTrack x =- writeTitle (trackTitle x) <>+ writeTitleAndLength x <> B.byteString (encodeUtf8 $ trackURL x) <> B.charUtf8 '\n' ---------------------------------------------------------------------------------writeTitle :: Maybe Text -> Builder-writeTitle Nothing = mempty-writeTitle (Just x) =- B.byteString "#EXTINF:-1," <>- B.byteString (encodeUtf8 x) <>+writeTitleAndLength :: Track -> Builder+writeTitleAndLength (Track _ Nothing Nothing) = mempty+writeTitleAndLength Track{..} =+ B.byteString "#EXTINF:" <>+ writeLength trackDuration <>+ B.byteString "," <>+ writeTitle trackTitle <> B.charUtf8 '\n'++--------------------------------------------------------------------------------+writeLength :: Maybe Float -> Builder+writeLength Nothing = mempty+writeLength (Just l) = B.stringUtf8 (show l)++--------------------------------------------------------------------------------+writeTitle :: Maybe Text -> Builder+writeTitle Nothing = mempty+writeTitle (Just x) = B.byteString (encodeUtf8 x)+
src/Text/Playlist/PLS/Reader.hs view
@@ -18,6 +18,7 @@ import Control.Applicative import Control.Monad (void) import Data.Attoparsec.ByteString+import Data.Attoparsec.ByteString.Char8 (signed, double) import Data.ByteString (ByteString) import Data.Text (Text) import Data.Text.Encoding (decodeUtf8)@@ -50,11 +51,13 @@ (n, url) <- parseFileN title <- (Just <$> parseTitle n) <|> return Nothing - -- Skip optional length field, we don't use it.- (skipSpace >> string "Length" >> skipLine) <|> return ()+ -- Parse track length.+ mlen <- (Just . realToFrac <$> (skipSpace >> string "Length" >> skipWhile isDigit >> string "=" >> signed double))+ <|> return Nothing - return Track { trackURL = url- , trackTitle = title+ return Track { trackURL = url+ , trackTitle = title+ , trackDuration = mlen } --------------------------------------------------------------------------------
src/Text/Playlist/PLS/Writer.hs view
@@ -35,7 +35,7 @@ -------------------------------------------------------------------------------- writeTrack :: (Track, Int) -> Builder-writeTrack x = writeFileN x <> writeTitle x+writeTrack x = writeFileN x <> writeTitle x <> writeLength x -------------------------------------------------------------------------------- writeFileN :: (Track, Int) -> Builder@@ -56,3 +56,15 @@ B.charUtf8 '=' <> B.byteString (encodeUtf8 y) <> B.charUtf8 '\n'++--------------------------------------------------------------------------------+writeLength :: (Track, Int) -> Builder+writeLength (x, n) =+ case trackDuration x of+ Nothing -> mempty+ Just l -> B.byteString "Length" <>+ B.stringUtf8 (show n) <>+ B.charUtf8 '=' <>+ B.stringUtf8 (show l) <>+ B.charUtf8 '\n'+
src/Text/Playlist/Types.hs view
@@ -22,8 +22,9 @@ -------------------------------------------------------------------------------- -- | A single music file or streaming URL. data Track = Track- { trackURL :: Text -- ^ URL for a file or streaming resource.- , trackTitle :: Maybe Text -- ^ Optional title.+ { trackURL :: Text -- ^ URL for a file or streaming resource.+ , trackTitle :: Maybe Text -- ^ Optional title.+ , trackDuration :: Maybe Float -- ^ Optional duration in seconds. } deriving (Show, Eq) --------------------------------------------------------------------------------
test/Examples.hs view
@@ -29,14 +29,17 @@ secretAgent = [ Track { trackURL = "http://mp2.somafm.com:9016" , trackTitle = Just "SomaFM: Secret Agent (#1 128k mp3): The soundtrack for your stylish, mysterious, dangerous life. For Spies and PIs too!"+ , trackDuration = Just (-1) } , Track { trackURL = "http://mp3.somafm.com:443" , trackTitle = Just "SomaFM: Secret Agent (#2 128k mp3): The soundtrack for your stylish, mysterious, dangerous life. For Spies and PIs too!"+ , trackDuration = Just (-1) } , Track { trackURL = "http://ice.somafm.com/secretagent" , trackTitle = Just "SomaFM: Secret Agent (Firewall-friendly 128k mp3) The soundtrack for your stylish, mysterious, dangerous life. For Spies and PIs too!"+ , trackDuration = Just (-1) } ] @@ -45,10 +48,12 @@ pigRadio = [ Track { trackURL = "http://s6.mediastreaming.it:8080" , trackTitle = Just "Pig Radio - Devoted in Playing the Best Indie Pop/Rock & Electronic. 24/7"+ , trackDuration = Just 0 } , Track { trackURL = "http://s1.viastreaming.net:7480" , trackTitle = Just "Pig Radio - Devoted in Playing the Best Indie Pop/Rock & Electronic. 24/7"+ , trackDuration = Just (-1) } ] @@ -57,6 +62,7 @@ utf8Radio = [ Track { trackURL = "http://fake.com" , trackTitle = Just "Alle otto i bambini erano già in costume da bagno"+ , trackDuration = Nothing } ] @@ -65,17 +71,19 @@ hitParty = [ Track { trackURL = "http://firewall.hitparty.net:443" , trackTitle = Just "(#1 - Generique) HITPARTY HIT - Pas de pub Que du HIT - Only Hits"+ , trackDuration = Just (-1) } , Track { trackURL = "http://icecast.pulsradio.com:80/hitpartyHD.mp3.m3u" , trackTitle = Just "(#1 - Generique) HITPARTY HIT - Pas de pub Que du HIT - Only Hits"+ , trackDuration = Just (-1) } ] -------------------------------------------------------------------------------- aaFile :: Playlist aaFile =- [ Track { trackURL = "http://foo.com", trackTitle = Nothing}- , Track { trackURL = "http://bar.com", trackTitle = Nothing}+ [ Track { trackURL = "http://foo.com", trackTitle = Nothing, trackDuration = Nothing}+ , Track { trackURL = "http://bar.com", trackTitle = Nothing, trackDuration = Nothing} ] --------------------------------------------------------------------------------
test/ResolveSpec.hs view
@@ -42,7 +42,7 @@ -------------------------------------------------------------------------------- resolved :: Playlist-resolved = [ Track "http://foo.com" Nothing- , Track "http://bar.com" Nothing- , Track "http://foo.com/aa.baz.live" Nothing+resolved = [ Track "http://foo.com" Nothing Nothing+ , Track "http://bar.com" Nothing Nothing+ , Track "http://foo.com/aa.baz.live" Nothing Nothing ]