packages feed

vorbiscomment (empty) → 0.0.1

raw patch · 4 files changed

+235/−0 lines, 4 filesdep +basedep +binary-strictdep +bytestringsetup-changed

Dependencies added: base, binary-strict, bytestring, mtl, utf8-string

Files

+ LICENSE view
@@ -0,0 +1,19 @@+Copyright (c) 2010 Chris Wagner++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to deal+in the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in+all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN+THE SOFTWARE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ Vorbis.hs view
@@ -0,0 +1,197 @@+module Vorbis (readAudioInfoAndComment, AudioInfo(..)) where++import qualified Codec.Binary.UTF8.String as UTF8+--import Control.Monad (when)+import Control.Monad.Error -- For Either monad instance+--import qualified Data.ByteString.Lazy as B+import qualified Data.ByteString as B+--import qualified Data.ByteString.Lazy.Char8 as Char8+import qualified Data.ByteString.Char8 as Char8+import Data.Binary.Strict.Get+import Data.Char (toLower)+import Data.Word++data AudioInfo = AudioInfo {channels :: Word8, sampleRate :: Word32, maxBitrate :: Word32,+                            nominalBitrate :: Word32, minBitrate :: Word32,+                            playtime :: Maybe Double}+  deriving Show++readAudioInfoAndComment :: FilePath -> IO (Either String (AudioInfo, [(String, String)]))+readAudioInfoAndComment fname = do+  content <- B.readFile fname+  return $ case readVorbisAudioInfo content of+             Left e -> Left e+             Right audioInfo -> case readVorbisCommentFromOgg content of+                                  Left e -> Left e+                                  Right comment -> Right (audioInfo, comment)++readVorbisCommentFromOgg :: Char8.ByteString -> Either String [(String, String)]+readVorbisCommentFromOgg fileContent = case runGet readVorbisCommentOggPage fileContent of+    (Left e, _) -> Left e+    (Right p2, _) ->+      -- XXX: This won't work right if packet is spanned across multiple pages...+      case runGet (readVorbisComment False) commentData of+        (Left e, _) -> Left e+        (Right comment, _) -> Right comment+      where commentData = B.drop 7 $ packetData p2 -- 'drop 7' strips off "\x03vorbis"++readVorbisCommentOggPage = do+  p1 <- readOggPage+  p2 <- readOggPage+  when (not $ isVorbisPage p1) $ fail "First page is not a vorbis page"+  when (serialNum p1 /= serialNum p2) $ fail "First two pages do not have same serial number"+  --when (not $ isComplete p2) $ fail "Second vorbis page is not a complete packet"+  return p2++{-+readVorbisCommentFromOgg :: Get [(String, String)]+readVorbisCommentFromOgg = do+  p1 <- readOggPage+  p2 <- readOggPage+  when (not $ isVorbisPage p1) $ fail "First page is not a vorbis page"+  when (serialNum p1 /= serialNum p2) $ fail "First two pages do not have same serial number"+  when (not $ isComplete p2) $ fail "Second vorbis page is not a complete packet"+  -- XXX: This won't work right if packet is spanned across multiple pages...+  let commentData = B.drop 7 $ packetData p2 -- 'drop 7' strips off "\x03vorbis"+  let (comment, _) = runGet (readVorbisComment False) commentData+  return comment+-}++++{-+readVorbisCommentFromOgg = do+  allPages <- readOggPages+  -- XXX: This could be wrong, if the first page is not a "\x01vorbis" one...+  let serial = serialNum $ head allPages+  let relevantPages = filter (\p -> serialNum p == serial) allPages+  let secondVorbisPage = head $ tail relevantPages+  -- XXX: This won't work right if packet is spanned across multiple pages...+  let commentData = B.drop 7 $ packetData secondVorbisPage -- 'drop 7' strips off "\x03vorbis"+  return $ runGet (readVorbisComment False) commentData+-}++readVorbisComment framingXXX = do+  vendorLength <- getWord32le+  vendor <- getAByteString $ fromIntegral vendorLength -- XXX: Need to decode from utf-8+  numItems <- getWord32le+  readEntries numItems+  --if framing and not ord(fileobj.read(1)) & 0x01:+  --   raise VorbisUnsetFrameError("framing bit was unset")+  where+    readEntries 0 = return []+    readEntries numItems = do+      len <- getWord32le+      rawStr <- getAByteString $ fromIntegral len -- XXX: Need to decode from utf-8+      let ustr = UTF8.decode $ B.unpack rawStr+      restOfEntries <- readEntries (numItems - 1)+      return $ (downcase $ takeWhile (/= '=') ustr, tail $ dropWhile (/= '=') ustr) : restOfEntries+      -- XXX: tag, value = string.split('=', 1)+      -- XXX: tag = tag.encode('ascii', errors)+    downcase = map toLower++--readVorbisAudioInfo :: Get (Word8, Word32, Word32, Word32, Word32)+readVorbisAudioInfo :: Char8.ByteString -> Either String AudioInfo+readVorbisAudioInfo fileContent = do+  firstPage <- readOggPageOrBust fileContent "Failed to parse first Ogg page"+  let reversed = B.reverse fileContent+  let oggs = Char8.pack "OggS"+  let lastPageData = B.concat [oggs, B.reverse $ fst $ B.breakSubstring (B.reverse oggs) reversed]+  {- lastPageData <- case findSubstring (B.reverse $ Char8.pack "OggS") reversed of+                    Nothing -> Left "Could not find any Ogg pages in file"+                    --Just pos -> Left $ (show $ snd $ B.breakSubstring (B.reverse $ Char8.pack "OggS") reversed) +                    Just pos -> Right $ B.reverse $ B.take (B.length fileContent - pos) reversed -}+  lastPage <- readOggPageOrBust lastPageData "Failed to parse last Ogg page"+  let (maybeInfo, _) = runGet (readFromVorbisPacket (granulePosition lastPage)) (packetData firstPage)+  maybeInfo++  --pages <- readOggPages+  --let vorbisPages = filter isVorbisPage pages+  --when (length vorbisPages < 1) $ fail "No Vorbis pages found in file"+  --when (length vorbisPages > 1) $ fail "More than one Vorbis page found!"+  +  --vorbisPage <- readOggPage+  --return $ case runGet readFromVorbisPacket (packetData vorbisPage) of+  --  (Left e, _) -> e+  --  (Right audioInfo, _) -> audioInfo++  where+    readFromVorbisPacket totalNumSamples = do+      skip 11+      channels <- getWord8+      sampleRate <- getWord32le+      maxBitrate <- getWord32le+      nominalBitrate <- getWord32le+      minBitrate <- getWord32le+      let playtime = (toDouble totalNumSamples) / (toDouble sampleRate) :: Double+      return $ AudioInfo channels sampleRate maxBitrate nominalBitrate minBitrate (Just playtime)+    toDouble :: Integral a => a -> Double+    toDouble = fromInteger . toInteger++readOggPageOrBust :: Char8.ByteString -> String -> Either String OggPage+readOggPageOrBust content err = case maybePage of+                                  Left e -> Left $ err ++ ": " ++ e+                                  Right p -> Right p+  where (maybePage, _) = runGet readOggPage content++{- findSubstring s l =+    if B.null s then Just 0+              else case B.breakSubstring s l of+                     (x,y) | B.null y  -> Nothing+                           | otherwise -> Just (B.length x) -}++--serialNum (_, _, _, _, sn, _, _, _, _) = sn+--packetData (_, _, _, _, _, _, _, _, d) = d++{- readOggVorbisPages = do+  p <- readOggPage+  if isVorbisPage p+    then do+      otherPages <- readPagesWithSerialNum $ serialNum p+      return $ p : otherPages+    else fail "first page wasn't a vorbis page"+      --readOggVorbisPages+  where+    readPagesWithSerialNum sn = do+      p <- readOggPage+      if serialNum p == sn+        then do+          others <- readPagesWithSerialNum sn+          return $ p : others+        else return [] -}++isVorbisPage page = B.take 7 (packetData page) == Char8.pack "\x01vorbis"++{- readOggPages = do+  p <- readOggPage+  rest <- if lastPage p then (return []) else readOggPages+  return $ p : rest+  where lastPage (_, _, flags, _, _, _, _, _, _) = flags == 4 -}++data OggPage = OggPage { version :: Word8, typeFlags :: Word8, granulePosition :: Word64,+                         serialNum :: Word32, pageNum :: Word32, checkSum :: Word32,+                         segmentsCount :: Word8, packetData :: B.ByteString+                         {- isComplete :: Bool -} }++--readOggPage :: Get (ByteString, Word8, xxx)+readOggPage = do+  oggs <- getAByteString 4+  version <- getWord8+  typeFlags <- getWord8+  granulePosition <- getWord64le+  serialNum <- getWord32le+  pageNum <- getWord32le+  checkSum <- getWord32le+  segmentsCount <- getWord8+  lacingBytes <- getAByteString $ fromIntegral $ toInteger segmentsCount+  when (B.length lacingBytes /= (fromIntegral $ toInteger segmentsCount)) $+    fail "Unable to read sufficient \"lacing\" bytes"+  --let isCompletePacket = B.last lacingBytes < 255+  packetData <- getAByteString $ fromIntegral $ byteSum lacingBytes+  return $ OggPage version typeFlags granulePosition serialNum pageNum checkSum segmentsCount+                   packetData {-(isCompletePacket)-}++byteSum :: B.ByteString -> Integer+byteSum bs = B.foldl (\total byte -> total + (toInteger byte)) 0 bs++getAByteString = getByteString
+ vorbiscomment.cabal view
@@ -0,0 +1,17 @@+Name:                vorbiscomment+Version:             0.0.1+Synopsis:            Reading of Vorbis comments from Ogg Vorbis files+Description:         This library supports reading of Vorbis comments from Ogg Vorbis files, and+                     will perhaps support writing of such comments in the future; as well as,+                     perhaps, reading/writing Vorbis comments to other file formats+                     (FLAC, Theora, etc).+Category:            Text, Sound+License:             MIT+License-file:        LICENSE+Author:              Chris Wagner+Maintainer:          christopher.t.wagner@gmail.com+Build-Depends:       base >= 3 && < 5, bytestring, binary-strict, mtl, utf8-string+Build-Type:          Simple+Cabal-Version:       >= 1.4+Exposed-modules:     Vorbis+ghc-options:         -Wall