{-# LANGUAGE DeriveDataTypeable #-} import CD.AccurateRip import CD.CDDB import Control.DeepSeq import Data.Binary import Data.Bits import Data.String import Data.Version import Network.HTTP import System.Console.CmdArgs import System.IO import System.Environment import System.Exit import System.Process progName :: String progName = "flAccurateRip" exeName :: String exeName = "flaccuraterip" version :: Version version = Version [0,1] [] intro :: String intro = progName ++ " " ++ showVersion version ++ "\n\ \Copyright (C) 2012 Nicola Squartini.\n\ \License GPLv3+: GNU GPL version 3 or \ \later \n\ \This is free software: you are free to change and redistribute it.\n\ \There is NO WARRANTY, to the extent permitted by law." data Options = Options { optOffset :: Int , opt30Samples :: Bool , optFiles :: [String] } deriving (Data, Typeable) options :: Options options = Options { optOffset = def &= explicit &= opt "O" &= name "sample-offset" &= help "Set ripping offset to N" &= typ "N" , opt30Samples = False &= explicit &= name "with-30-samples-correction" &= help "Add 30 samples to the offset (use if the CD was \ \ripped using the correct offset, which is 30 \ \samples less than that in \ \http://www.accuraterip.com/driveoffsets.htm)" , optFiles = def &= args } lengthsToOffsets :: Num a => [a] -> [a] lengthsToOffsets = scanl1 (+) offsetsToLengths :: Num a => [a] -> [a] offsetsToLengths [] = [] offsetsToLengths [x] = [x] offsetsToLengths xs = (offsetsToLengths $ i) ++ [(last xs) - (last i)] where i = init xs getFlacSampleNumber :: FilePath -> IO (Int) getFlacSampleNumber file = do (_,b, _) <- readProcessWithExitCode "metaflac" ["--show-total-samples", file] "" return (read b) getOffsetsFromFlacs :: [FilePath] -> IO ([Int]) getOffsetsFromFlacs [file] = do (a,_,_) <- readProcessWithExitCode "metaflac" ["--export-cuesheet-to=-", file] "" if a == ExitSuccess then do prog <- getProgName putStrLn (prog ++ ": sorry, rips to single file are not supported yet") exitFailure else do l <- getFlacSampleNumber file return [quot l samplesPerSector] getOffsetsFromFlacs filelist = do s <- mapM getFlacSampleNumber filelist return $ lengthsToOffsets (map (\a -> quot a samplesPerSector) s) stringToWord32List :: String -> [Word32] stringToWord32List [] = [] stringToWord32List (x1:x2:x3:x4:xs) = (fourCharToWord32 x1 x2 x3 x4):stringToWord32List xs where fourCharToWord32 c1 c2 c3 c4 = (fromIntegral $ fromEnum c1) + (fromIntegral $ fromEnum c2) `shiftL` 8 + (fromIntegral $ fromEnum c3) `shiftL` 16 + (fromIntegral $ fromEnum c4) `shiftL` 24 stringToWord32List _ = error "stringToWord32List: length of String should be \ \divisible by 4." pipeFlacs :: [FilePath] -> IO Handle pipeFlacs xs = do (_,Just b,_,_) <- createProcess (CreateProcess (RawCommand "flac" ("-s":"-c":"-d": "--force-raw-format": "--endian=little": "--sign=signed":xs)) Nothing Nothing Inherit CreatePipe Inherit True True) return b showAccuracy :: ArData -> RipHash -> IO () showAccuracy ar hash = showRes (verifyTracks ar hash) (ripCrc hash) showRes :: [(Word,Word)] -> [(CRC,CRC)] -> IO () showRes x y = do putStrLn "Track No.\tAccuracy (v.1 sum)\tAccuracy (v.2 sum)\n" sR (1::Int) x y where sR _ [] _ = return () sR _ _ [] = return () sR i ((c1,c2):cs) ((s1,s2):ss) = do putStrLn ("Track " ++ show i ++ ":\t" ++ show c1 ++ " (" ++ show s1 ++ ")\t\t" ++ show c2 ++ " (" ++ show s2 ++ ")") sR (i+1) cs ss noInput :: [String] -> IO () noInput [] = do prog <- getProgName putStrLn (prog ++ ": no input files") exitFailure noInput _ = return () main :: IO () main = do opts <- cmdArgs (options &= program exeName &= summary intro &= verbosity) let flaclist = optFiles opts noInput flaclist list <- getOffsetsFromFlacs flaclist putStr "Connecting to AccurateRip database" whenLoud $ putStr (" (" ++ arUrl list ++ ")") putStr "..." hFlush stdout res <- simpleHTTP (getRequest $ arUrl list) case res of Right (Response (4,0,4) _ _ _) -> do putStrLn " no pressing found." exitFailure _ -> do ardatastr <- getResponseBody res putStrLn " OK" let ardata = decode $ fromString ardatastr let offset = (optOffset opts) + 30*(fromEnum $ opt30Samples opts) putStr "Computing track checksums from input files" whenLoud $ putStr (" (using " ++ show offset ++ " samples offset)") putStrLn "..." a <- pipeFlacs flaclist hSetBinaryMode a True str <- hGetContents a let cs = listPairCrcs offset (map (\n -> samplesPerSector * n) (offsetsToLengths list)) (stringToWord32List str) (\x -> showAccuracy ardata (RipHash (discId1 list) (discId2 list) (cddbDiscId list) x)) $!! cs hClose a