ghostscript-parallel 0.0 → 0.0.1
raw patch · 3 files changed
+89/−15 lines, 3 filesdep +non-emptydep ~optparse-applicative
Dependencies added: non-empty
Dependency ranges changed: optparse-applicative
Files
- ReadMe.md +38/−0
- ghostscript-parallel.cabal +5/−4
- src/Main.hs +46/−11
+ ReadMe.md view
@@ -0,0 +1,38 @@+# Usage++The most simple call is:+~~~~+gs-parallel input.pdf page%04d.png -j4 -- -sDEVICE=png16m -dNOPAUSE -dBATCH+~~~~+This converts the document `input.pdf` to a series of PNGs, one for each page.+It splits the range of all pages in four almost equally sized ranges+and forks a ghostscript instance for each of these ranges.+++If there is a cluster of pages that requires an especially long render time,+then it might be useful to chop the page set into more smaller chunks.+~~~~+gs-parallel input.pdf page%04d.ppm -j4 --chunk-size 100 \+ -- -sDEVICE=ppmraw -dNOPAUSE -dBATCH+~~~~+This will process a document with 910 pages+in chunks of sizes 100, 100, ..., 100, 10.++If you want a more balanced series of chunk sizes,+use the option `--max-chunk-size` instead:+~~~~+gs-parallel input.pdf page%04d.tif -j4 --max-chunk-size 100 \+ -- -sDEVICE=tif24nc -dNOPAUSE -dBATCH+~~~~+It will process 10 chunks of size 91.+++For selecting a subrange of pages,+you must use the options `--first-page` and `--last-page`+instead of the `gs` options `-dFirstPage` and `-dLastPage`,+because `gs-parallel` must know the selected pages.+++# Known issues++We do not escape the pdf file path for page counting, this may cause problems.
ghostscript-parallel.cabal view
@@ -1,5 +1,5 @@ Name: ghostscript-parallel-Version: 0.0+Version: 0.0.1 Synopsis: Let Ghostscript render pages in parallel Description: This is intended for rendering PostScript or PDF documents using Ghostscript.@@ -16,8 +16,7 @@ . Run it like so: .- > gs-parallel input.pdf frames-%04d.png \- > -- -sDEVICE=png16m -dNOPAUSE -dBATCH+ > gs-parallel input.pdf page%04d.png -j4 -- -sDEVICE=png16m -dNOPAUSE -dBATCH Homepage: https://hub.darcs.net/thielema/ghostscript-parallel License: BSD3 License-File: LICENSE@@ -28,9 +27,10 @@ Cabal-Version: >=1.10 Extra-Source-Files: Makefile+ ReadMe.md Source-Repository this- Tag: 0.0+ Tag: 0.0.1 Type: darcs Location: https://hub.darcs.net/thielema/ghostscript-parallel @@ -45,6 +45,7 @@ optparse-applicative >=0.11 && <0.19, directory >=1.3 && <1.4, process >=1.6 && <1.7,+ non-empty >=0.3.4 && <0.4, utility-ht >=0.0.10 && <0.1, base >=4.5 && <5 Hs-Source-Dirs: src
src/Main.hs view
@@ -12,12 +12,13 @@ import System.Process (callProcess, readProcess) import qualified Data.Foldable as Fold-import qualified Data.List.HT as ListHT+import qualified Data.NonEmpty.Class as NonEmptyC+import qualified Data.NonEmpty as NonEmpty import qualified Data.Ix as Ix import Data.Tuple.HT (mapSnd) import Data.Monoid ((<>)) -import Control.Applicative (pure, (<*>))+import Control.Applicative (pure, (<*>), (<|>)) import Text.Printf (printf) @@ -45,18 +46,31 @@ divUp :: (Integral a) => a -> a -> a divUp x y = - div (-x) y +splitPages :: Int -> Int -> NonEmpty.T [] Int+splitPages numJobs numPages =+ fmap (flip divUp numJobs) $+ NonEmpty.mapTail (take numJobs) $ NonEmptyC.iterate (numPages+) 0++splitInChunks :: Int -> Int -> NonEmpty.T [] Int+splitInChunks chunkSize numPages =+ flip NonEmpty.snoc numPages $ takeWhile (<numPages) $ iterate (chunkSize+) 0++splitInBoundedChunks :: Int -> Int -> NonEmpty.T [] Int+splitInBoundedChunks chunkSize numPages =+ splitPages (divUp numPages chunkSize) numPages+ run :: Int -> Int -> Maybe Int ->+ (Int -> Int -> NonEmpty.T [] Int) -> FilePath -> FilePath -> [String] -> IO ()-run numJobs firstPage mLastPage input output gsArgs = do+run numJobs firstPage mLastPage split input output gsArgs = do lastPage <- maybe (countPages input) return mLastPage let num = Ix.rangeSize (firstPage, lastPage) let ranges = zip [0::Integer ..] $- ListHT.mapAdjacent+ NonEmpty.mapAdjacent (\p0 p1 -> (p0,p1-1))- (map ((firstPage+) . flip divUp numJobs) $- take (numJobs+1) $ iterate (num+) 0)+ (fmap (firstPage+) $ split numJobs num) Pool.runException (Just numJobs) $ flip map ranges $ \(job,(p0,p1)) -> do callProcess "gs" $@@ -75,30 +89,51 @@ (printf output dstPage) +parsePositiveNumber :: (Read a, Ord a, Num a) => String -> OP.ReadM a+parsePositiveNumber name =+ OP.eitherReader $ parseNumber name (0<) "positive"+ parser :: OP.Parser ([String] -> IO ()) parser = pure run <*>- (OP.option- (OP.eitherReader $ parseNumber "number of jobs" (0<) "positive") $+ (OP.option (parsePositiveNumber "number of jobs") $ OP.short 'j' <> OP.long "jobs" <> OP.metavar "NUMBER" <> OP.value 1 <> OP.help "Number of parallel threads") <*>- (OP.option (OP.eitherReader $ parseNumber "page" (0<) "positive") $+ (OP.option (parsePositiveNumber "page") $ OP.long "first-page" <> OP.metavar "ONEBASED" <> OP.value 1 <> OP.help "First page to render") <*>- (OP.option- (fmap Just $ OP.eitherReader $ parseNumber "page" (0<) "positive") $+ (OP.option (fmap Just $ parsePositiveNumber "page") $ OP.long "last-page" <> OP.metavar "ONEBASED" <> OP.value Nothing <> OP.help "Last page to render")+ <*>+ (+ (OP.option+ (fmap (\chunkSize _numJobs numPages ->+ splitInChunks chunkSize numPages) $+ parsePositiveNumber "number of pages") $+ OP.long "chunk-size" <>+ OP.metavar "NUMBER" <>+ OP.help "Split pages into chunks of NUMBER pages, except the last chunk")+ <|>+ (OP.option+ (fmap (\chunkSize _numJobs numPages ->+ splitInBoundedChunks chunkSize numPages) $+ parsePositiveNumber "number of pages") $+ OP.long "max-chunk-size" <>+ OP.metavar "NUMBER" <>+ OP.value splitPages <>+ OP.help "Split pages into chunks of balanced size containing at most NUMBER pages")+ ) <*> OP.strArgument (OP.metavar "INPUT" <>