Rewrite 7digital in haskell
This commit is contained in:
105
7digital/Main.hs
105
7digital/Main.hs
@@ -1,9 +1,102 @@
|
||||
import Control.Monad (void)
|
||||
import System.Process (rawSystem)
|
||||
module Main
|
||||
( main
|
||||
) where
|
||||
|
||||
import Codec.Archive.Zip
|
||||
( saveEntry
|
||||
, unEntrySelector
|
||||
, forEntries
|
||||
, withArchive
|
||||
, getEntryName
|
||||
)
|
||||
import Data.Text (StrictText)
|
||||
import qualified Data.Text as StrictText
|
||||
import qualified Data.Text.IO as StrictText
|
||||
import System.Directory (createDirectoryIfMissing, listDirectory, renameFile)
|
||||
import System.Process (readProcessWithExitCode)
|
||||
import System.Environment (getArgs)
|
||||
import System.IO (hPutStrLn, stderr)
|
||||
import System.FilePath ((<.>), (</>), takeFileName, takeExtension)
|
||||
import System.Exit (ExitCode(..), exitWith)
|
||||
import Control.Monad (foldM, when)
|
||||
import Control.Monad.IO.Class (MonadIO(..))
|
||||
import Data.List (find)
|
||||
import Data.Bifunctor (Bifunctor(..))
|
||||
import Data.Map (Map)
|
||||
import qualified Data.Map as Map
|
||||
|
||||
main :: IO ()
|
||||
main = do
|
||||
arguments <- getArgs
|
||||
void $ rawSystem "bundle"
|
||||
$ "exec" : "./bin/7digital.rb" : arguments
|
||||
main = getArgs >>= withArguments
|
||||
where
|
||||
withArguments [sourceArchive, musicDirectory]
|
||||
= createDirectoryIfMissing True musicDirectory
|
||||
>> extract sourceArchive musicDirectory
|
||||
>> probeDirectory musicDirectory
|
||||
>>= withMetadata musicDirectory
|
||||
withArguments _ = hPutStrLn stderr "Usage: 7digital MUSIC_ARCHIVE.zip DIRECTORY"
|
||||
>> exitWith (ExitFailure 1)
|
||||
withMetadata musicDirectory (metadata, anyErrors)
|
||||
= Map.traverseWithKey (renameSong musicDirectory) metadata
|
||||
>> when anyErrors (exitWith (ExitFailure 2))
|
||||
|
||||
data Song = Song
|
||||
{ album :: StrictText
|
||||
, title :: StrictText
|
||||
, track :: StrictText
|
||||
} deriving (Eq, Show)
|
||||
|
||||
renameSong :: FilePath -> FilePath -> Song -> IO ()
|
||||
renameSong musicDirectory songPath Song{..} =
|
||||
let newFileName = StrictText.unpack (paddedTrack <> " - " <> title)
|
||||
<.> takeExtension songPath
|
||||
newDirectory = musicDirectory </> StrictText.unpack album
|
||||
in createDirectoryIfMissing False newDirectory
|
||||
>> renameFile songPath (newDirectory </> newFileName)
|
||||
>> putStrLn ("\"" <> newFileName <> "\"" <> " renamed.")
|
||||
where
|
||||
paddedTrack
|
||||
| StrictText.length track <= 1 = "0" <> track
|
||||
| otherwise = track
|
||||
|
||||
probeDirectory :: FilePath -> IO (Map FilePath Song, Bool)
|
||||
probeDirectory musicDirectory =
|
||||
listDirectory musicDirectory >>= foldM probeEach (Map.empty, False)
|
||||
where
|
||||
probeEach (metadata, _) "MissingFiles.txt"
|
||||
= StrictText.readFile (musicDirectory </> "MissingFiles.txt")
|
||||
>>= StrictText.hPutStr stderr
|
||||
>> pure (metadata, True)
|
||||
probeEach metadata songFile =
|
||||
let metadataKey = musicDirectory </> songFile
|
||||
in readProcessWithExitCode "ffprobe" [metadataKey] ""
|
||||
>>= handleProbeResult metadata metadataKey . parseProbe
|
||||
handleProbeResult (metadata, anyErrors) metadataKey (Right songProbe) =
|
||||
pure (Map.insert metadataKey songProbe metadata, anyErrors)
|
||||
handleProbeResult (metadata, _) metadataKey (Left missingField) =
|
||||
let message = "Metadata \"" <> missingField
|
||||
<> "\" is not available for " <> StrictText.pack metadataKey
|
||||
<> "."
|
||||
in StrictText.hPutStrLn stderr message >> pure (metadata, True)
|
||||
parseProbe (_, _, probeOutput) = createSong
|
||||
$ filter fieldFilter
|
||||
$ bimap normalizeName stripValue . StrictText.breakOn ":"
|
||||
<$> StrictText.lines (StrictText.pack probeOutput)
|
||||
fieldFilter (name, _) = name `elem` ["title", "album", "track"]
|
||||
createSong songFields = Song
|
||||
<$> findByField "album" songFields
|
||||
<*> findByField "title" songFields
|
||||
<*> findByField "track" songFields
|
||||
findByField needle songFields
|
||||
| Just found <- find ((== needle) . fst) songFields = Right $ snd found
|
||||
| otherwise = Left needle
|
||||
normalizeName = StrictText.toLower . StrictText.strip
|
||||
stripValue = StrictText.strip . StrictText.drop 1
|
||||
|
||||
extract :: MonadIO m => FilePath -> FilePath -> m ()
|
||||
extract sourceArchive musicDirectory = do
|
||||
withArchive sourceArchive (forEntries forSong)
|
||||
where
|
||||
forSong selector =
|
||||
let entryName = takeFileName (unEntrySelector selector)
|
||||
in liftIO (StrictText.putStrLn $ "Inflating " <> getEntryName selector)
|
||||
>> saveEntry selector (musicDirectory </> entryName)
|
||||
|
||||
Reference in New Issue
Block a user