packages feed

isiz-0.0.1: src/isiz.hs

{-# OPTIONS -O -Wall #-}

import Control.Applicative ((<$>), (<*>), (<|>))
import Control.Monad (foldM, join)
import Data.List (find)
import Data.Maybe (catMaybes)
import System.Console.GetOpt
import System.Environment (getArgs)
import System.Exit (ExitCode (..), exitSuccess, exitWith)
import Text.Printf
import PixbufExtras

data RunMode = RunModeDefault
             | RunModeList Bool
             | RunModeSilent Bool
               deriving (Eq, Show)

data ImageSize = ZeroSized
               | Ratio4_3
               | Ratio5_4
               | Ratio16_9
               | Ratio16_10
               | Ratio21_9
               | NonStdHVGA
               | NonStdWVGA
               | NonStdWVGAPlus
               | NonStdFWVGA
               | NonStdWSVGA
               | NonStdWXGA
               | NonStdUnnamed
               | NonStdWSXGA
               | OtherSize
                 deriving (Eq, Show)

sizeName :: ImageSize -> String
sizeName ZeroSized      = "(zero-sized)"
sizeName Ratio4_3       = "4:3"
sizeName Ratio5_4       = "5:4"
sizeName Ratio16_9      = "16:9"
sizeName Ratio16_10     = "16:10"
sizeName Ratio21_9      = "21:9"
sizeName NonStdHVGA     = "HVGA"
sizeName NonStdWVGA     = "WVGA"
sizeName NonStdWVGAPlus = "WVGA+"
sizeName NonStdFWVGA    = "FWVGA"
sizeName NonStdWSVGA    = "WSVGA"
sizeName NonStdWXGA     = "WXGA"
sizeName NonStdUnnamed  = "(unnamed)"
sizeName NonStdWSXGA    = "WSXGA"
sizeName OtherSize      = "(other)"

nonStdSizes :: [(Int, Int, ImageSize)]
nonStdSizes = [ ( 480, 320, NonStdHVGA    )
              , ( 800, 480, NonStdWVGA    )
              , ( 854, 480, NonStdWVGAPlus)
              , ( 864, 480, NonStdFWVGA   )
              , (1024, 600, NonStdWSVGA   )
              , (1280, 768, NonStdWXGA    )
              , (1366, 768, NonStdUnnamed )
              , (1280, 854, NonStdWSXGA   ) ]

isWallpaper :: ImageSize -> Bool
isWallpaper ZeroSized      = False
isWallpaper OtherSize      = False
isWallpaper _              = True

checkSize :: Int -> Int -> Maybe ImageSize
checkSize w h
    | w <  h    = checkSize h w
    | h <  0    = Nothing
    | h == 0    = Just ZeroSized
    | otherwise =
        let ratio       = fromIntegral h / fromIntegral w
            minDelta    = 1e-2 :: Double
            testD s     = abs (ratio - s) < minDelta
            trd (_,_,c) = c
        in case () of
             _ | testD (3/ 4) -> Just Ratio4_3
             _ | testD (4/ 5) -> Just Ratio5_4
             _ | testD (9/16) -> Just Ratio16_9
             _ | testD (5/ 8) -> Just Ratio16_10
             _ | testD (3/ 7) -> Just Ratio21_9
             _                ->
                 (trd <$>
                  find (\(u, v, _) -> u == w && v == h)
                       nonStdSizes) <|> Just OtherSize

-- | read from file and return its size and whether suitable for wallpaper
checkFile :: FilePath -> IO (Maybe (ImageSize, Bool, Int, Int))
checkFile path = do
  mg <- pixbufGetSizeInfoFile path
  let ms = join $ uncurry checkSize <$> mg
      mp = isWallpaper <$> ms
  return $ (\(w, h) s p -> (s, p, w, h)) <$> mg <*> ms <*> mp

-- | flipped <$>
(<$$>) :: Functor m => m a -> (a -> b) -> m b
(<$$>) = flip fmap
infixl 5 <$$>

-- | perform action corresponding to RunMode on each file
checkFiles :: RunMode -> [FilePath] -> IO ()
checkFiles RunModeDefault paths =
    mapM_ (\path ->
           checkFile path >>=
           maybe (printf "%s: read error or not a image file\n" path)
                     (\(s, p, w, h) ->
                          printf "%s: %s, %s, %dx%d\n" path
                                 (if p then "wallpaper" else "not wallpaper")
                                 (sizeName s) w h)) paths
checkFiles (RunModeList v) paths =
    mapM (\path -> do
            res <- checkFile path
            return $ (\(_, p, _, _) ->
                          (path, if v then not p else p)) <$> res) paths
                   <$$> catMaybes
                   <$$> filter snd
                   >>= mapM_ (putStrLn . fst)
checkFiles (RunModeSilent v) paths =
    mapM (\path -> do
            res <- checkFile path
            return $ (\(_, p, _, _) ->
                          if v then not p else p) <$> res) paths
                   <$$> catMaybes
                   <$$> and
                   >>= \q -> if q then exitSuccess
                             else exitWith (ExitFailure 1)

data Opts = Help
          | Version
          | ListMode
          | SilentMode
          | InvPred

options :: [OptDescr Opts]
options = [ Option "h" ["help"]    (NoArg Help)
                       "Show help message and exit"
          , Option "V" ["version"] (NoArg Version)
                       "Show version info and exit"
          , Option "v" ["invert"]  (NoArg InvPred)
                       "Invert filtering condition for `-l' and `-q'"
          , Option "l" ["list"]    (NoArg ListMode)
                       "Just list files suitable for wallpapers"
          , Option "q" ["quiet"]   (NoArg SilentMode)
                       "Return successful status if all images suitable" ]

-- | parse commandline arguments
getOpts :: [String] -> IO (RunMode, [String])
getOpts argv =
    case getOpt Permute options argv of
      (o, n, []  ) -> foldOpts o <$$> toRunMode <$$> (\o' -> (o', n))
      (_, _, errs) -> ioError (userError $ concat errs)
    where foldOpts :: [Opts] -> IO (RunMode, Bool)
          foldOpts =
              foldM (\(m, v) e ->
                         case e of
                           Help       -> putStrLn usage
                                         >> exitSuccess
                                         >> return (m, v)
                           Version    -> putStrLn version
                                         >> exitSuccess
                                         >> return (m, v)
                           ListMode   -> return (RunModeList   False, v)
                           SilentMode -> return (RunModeSilent False, v)
                           InvPred    -> return (m, not v))
                        (RunModeDefault, False)
          toRunMode :: (RunMode, Bool) -> RunMode
          toRunMode (m, v) = case m of
                               RunModeDefault  -> RunModeDefault
                               RunModeList   _ -> RunModeList   v
                               RunModeSilent _ -> RunModeSilent v

usage :: String
usage = usageInfo "isiz [options] [files]\n\nOptions:" options

version :: String
version = "isiz version 0.0.1"

main :: IO ()
main = getArgs >>= getOpts >>= uncurry checkFiles