module ShakeRules where import Development.Shake -- import Development.Shake.Rule -- import Development.Shake.Command import Development.Shake.FilePath -- import Development.Shake.Util import Control.Monad (guard, when) import Text.Printf (printf) import qualified Data.ByteString.Lazy as BS import qualified Data.List as List data Configuration = Configuration { useBackground :: Bool, frameRate :: Integer, resolution :: Integer, antialias :: [String], pages :: [String] } defaultConfig :: Configuration defaultConfig = Configuration { useBackground = True, frameRate = 50, resolution = 108, antialias = ["-dTextAlphaBits=2", "-dGraphicsAlphaBits=2"], pages = [] } previewConfig :: Configuration previewConfig = Configuration { useBackground = False, frameRate = 25, resolution = 54, antialias = [], pages = [] -- ["--last-page", "1000"] } tmpRoot :: FilePath -- tmpRoot = "/tmp" tmpRoot = "/var/tmp" -- ToDo: Is there some system agnostic temporary path definition? tmpFramesDir :: String -> FilePath tmpFramesDir stem = tmpRoot stem <> "-frames" {- tif24nc and ppmraw are much faster than png16m, but occupy much more temporary space, can be tens of gigabytes for 10 minutes of FullHD. $ du -sk StilleNacht-frames/ StilleNacht.flv 41813712 StilleNacht-frames/ 3519192 StilleNacht.flv LZ4 cannot compress much, even in best mode. LZip can compress to about 50% in fast mode. -} framePattern :: FilePath; frameFormat :: String (framePattern, frameFormat) = case id 'p' of 'p' -> ("%04d.png", "png16m") 't' -> ("%04d.tif", "tif24nc") 'j' -> ("%04d.jpg", "jpeg") _ -> ("%04d.ppm", "ppmraw") require :: FilePath -> Action FilePath require path = do need [path] return path whenAvailable :: FilePath -> (FilePath -> Action ()) -> Action () whenAvailable path act = do available <- doesFileExist path when available $ act path chooseAudioSource :: FilePath -> [String] -> Action FilePath chooseAudioSource stem audioFormats = do let audioSrcs = map (stem <.>) audioFormats available <- traverse doesFileExist audioSrcs case filter fst $ zip available audioSrcs of (_,audioSrc):_ -> return audioSrc _ -> fail "No audio source available" configured :: Configuration -> FilePath -> Rules () configured config prefix = do prefix <> "*.pdf" %> \dst -> do let base = takeBaseName dst midi <- require $ base <.> "mid" let background = base <.> "jpeg" backgroundAvailable <- if useBackground config then doesFileExist background else return False let optionsFile = base <.> "options" optionsAvailable <- doesFileExist optionsFile options <- if optionsAvailable then readFile' optionsFile else return "" let titleFile = base <.> "title" titleAvailable <- doesFileExist titleFile title <- if titleAvailable then fmap (\str -> ["--title", str]) $ readFile' titleFile else return [] cmd_ "boomwhacker" title options "--rate" (show $ frameRate config) (guard backgroundAvailable >> ["--background", background]) [midi, dst] -- ToDo: we could use the flashsv codec to archive the frames only -- This requires as much time as transcoding to H265. tmpRoot prefix <> "*.frames" %> \dst -> do let stem = maybe (error "cannot strip temporary directory prefix") (dropExtension . dropWhile (pathSeparator==)) (List.stripPrefix tmpRoot dst) pdf <- require $ stem <.> "pdf" let framesDir = tmpFramesDir stem cmd_ "mkdir" "-p" [framesDir] -- avoid failure of 'rm -r' cmd_ "rm" "-r" [framesDir] cmd_ "mkdir" "-p" [framesDir] -- cmd_ "echo rm" =<< getDirectoryFiles framesDir ["*"] -- ToDo: remove lines starting with "Loading font" -- Stdout gsStdout <- cmd_ "gs-parallel" "-j6" "--max-chunk-size=100" (pages config) pdf (framesDir framePattern) "--" "-dSAFER" "-dNOPAUSE" "-dBATCH" "-P" "-dNEWPDF=true" ("-sDEVICE="++frameFormat) (antialias config) [printf "-dDEVICEHEIGHT=%d" (resolution config * 10) :: String] [printf "-r%d" (resolution config) :: String] -- Shake is not happy with a symbolic link as result -- cmd_ "ln -s" [framesDir printf framePattern (0::Integer), dst] -- cmd_ "cat" (StdinBS gsStdout) cmd_ "touch" [dst] prefix <> "*.flv" %> \dst -> do let stem = dropExtension dst need [tmpRoot stem <.> "frames"] let framesDir = tmpFramesDir stem audioSrc <- chooseAudioSource (takeBaseName dst) ["mp3", "aac", "wav"] cmd_ "ffmpeg" "-r" (show $ frameRate config) "-f" "image2" "-i" [framesDir framePattern] "-i" [audioSrc] "-vcodec" "flashsv" "-acodec" "copy" "-y" [dst] prefix <> "*.mp4" %> \dst -> do let stem = dropExtension dst need [tmpRoot stem <.> "frames"] let framesDir = tmpFramesDir stem audioSrc <- chooseAudioSource (takeBaseName dst) ["mp3", "aac", "flac", "wav"] cmd_ "ffmpeg" "-r" (show $ frameRate config) "-f" "image2" "-i" [framesDir framePattern] "-i" [audioSrc] "-b:v" "8000k" "-acodec" "copy" "-y" [dst] generic :: Rules () generic = do "//*.inspect-csv" %> \dst -> do let Just stem = stripExtension "inspect-csv" dst src <- require $ stem <.> "mid" cmd_ "midicsv" [src, dst] "//*.mid" %> \dst -> whenAvailable (dst -<.> "csv") $ \src -> do cmd_ "csvmidi" [src, dst] "//*.csv" %> \dst -> whenAvailable (dst -<.> "fods") $ \src -> do -- FixMe: They do not run concurrently! Stdout csvUtf8 <- cmd "ods2csv" "--sheetnumber" "1" [src] cmd_ "iconv" "-f" "utf8" "-t" "latin1" (StdinBS csvUtf8) (FileStdout dst) "//*.wav" %> \dst -> whenAvailable (dst -<.> "mid") $ \src -> do cmd_ "timidity" "--preserve-silence" "-A300" "-Ow" [src]