ghostscript-parallel (empty) → 0.0
raw patch · 5 files changed
+244/−0 lines, 5 filesdep +basedep +directorydep +optparse-applicativesetup-changed
Dependencies added: base, directory, optparse-applicative, pooled-io, process, shell-utility, utility-ht
Files
- LICENSE +30/−0
- Makefile +33/−0
- Setup.lhs +3/−0
- ghostscript-parallel.cabal +54/−0
- src/Main.hs +124/−0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2023, Henning Thielemann++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Henning Thielemann nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Makefile view
@@ -0,0 +1,33 @@+.SECONDARY: %.frames++CABALOPTS =++RESOLUTION = 108+#RESOLUTION = 72+#RESOLUTION = 54++PAGES = --last-page=1000+#PAGES = --first-page=100 --last-page=200++ANTIALIAS = -dTextAlphaBits=2 -dGraphicsAlphaBits=2++%.frames: %.pdf src/Main.hs+ mkdir -p /tmp/$*-frames+ rm -f /tmp/$*-frames/*+ cabal run gs-parallel $(CABALOPTS) -- \+ -j4 $(PAGES) $< "/tmp/$*-frames/%04d.png" -- \+ -dNOPAUSE -dBATCH \+ -sDEVICE=png16m $(ANTIALIAS) -r$(RESOLUTION)+ touch $@++# although I request 4 pages and ghostscript claims,+# that it processed 4 pages, it emits only one.+# Also happens when rendering the PDF-1.5 reference.+# It seems that PageList option is seriously broken in gs version 9.53.3 (2020-10-01).+# Seems to be fixed in version 10.01.2 (2023-06-21), though.+%0001.png: %.pdf src/Main.hs+ gs -dNOPAUSE -dBATCH \+ -sDEVICE=png16m $(ANTIALIAS) \+ -dDEVICEHEIGHT=$(RESOLUTION)0 -r$(RESOLUTION) \+ -sOutputFile=$*%04d.png -sPageList=1,5,9,13 $<+# -sOutputFile=$*%04d.png -dFirstPage=1000 -dLastPage=1100 $<
+ Setup.lhs view
@@ -0,0 +1,3 @@+#! /usr/bin/env runhaskell+> import Distribution.Simple+> main = defaultMain
+ ghostscript-parallel.cabal view
@@ -0,0 +1,54 @@+Name: ghostscript-parallel+Version: 0.0+Synopsis: Let Ghostscript render pages in parallel+Description:+ This is intended for rendering PostScript or PDF documents using Ghostscript.+ Ghostscript has the option @-dNumRenderingThreads@+ but it does not speedup pretty much,+ because it renders each page using multiple threads.+ .+ This program runs Ghostscript multiple times on distinct page subsets,+ such that Ghostscript can render pages in parallel.+ .+ @gs-parallel@ has its own options,+ interprets them and converts and passes them to @gs@,+ but it also allows to pass custom options to @gs@ after @--@.+ .+ Run it like so:+ .+ > gs-parallel input.pdf frames-%04d.png \+ > -- -sDEVICE=png16m -dNOPAUSE -dBATCH+Homepage: https://hub.darcs.net/thielema/ghostscript-parallel+License: BSD3+License-File: LICENSE+Author: Henning Thielemann+Maintainer: haskell@henning-thielemann.de+Category: Graphics+Build-Type: Simple+Cabal-Version: >=1.10+Extra-Source-Files:+ Makefile++Source-Repository this+ Tag: 0.0+ Type: darcs+ Location: https://hub.darcs.net/thielema/ghostscript-parallel++Source-Repository head+ Type: darcs+ Location: https://hub.darcs.net/thielema/ghostscript-parallel++Executable gs-parallel+ Build-Depends:+ pooled-io >=0.0.2 && <0.1,+ shell-utility >=0.1 && <0.2,+ optparse-applicative >=0.11 && <0.19,+ directory >=1.3 && <1.4,+ process >=1.6 && <1.7,+ utility-ht >=0.0.10 && <0.1,+ base >=4.5 && <5+ Hs-Source-Dirs: src+ Main-Is: Main.hs+ Default-Language: Haskell2010+ GHC-Options:+ -Wall -fwarn-incomplete-uni-patterns -fwarn-tabs -threaded
+ src/Main.hs view
@@ -0,0 +1,124 @@+module Main where++import qualified Options.Applicative as OP++import Shell.Utility.ParseArgument (parseNumber)+import Shell.Utility.Exit (exitFailureMsg)++import qualified Control.Concurrent.PooledIO.Independent as Pool++import qualified System.Environment as Env+import System.Directory (renameFile)+import System.Process (callProcess, readProcess)++import qualified Data.Foldable as Fold+import qualified Data.List.HT as ListHT+import qualified Data.Ix as Ix+import Data.Tuple.HT (mapSnd)+import Data.Monoid ((<>))++import Control.Applicative (pure, (<*>))++import Text.Printf (printf)+++{-+qpdf --show-npages input.pdf++gs -q -dNODISPLAY --permit-file-read=$PATH_TO_PDF -c "($PATH_TO_PDF) (r) file runpdfbegin pdfpagecount = quit"+-}+countPages :: FilePath -> IO Int+countPages path = do+ countStr <-+ readProcess "gs"+ ["-q", "-dNODISPLAY", "--permit-file-read=" ++ path, "-c",+ "(" ++ path ++ ") (r) file runpdfbegin pdfpagecount = quit"]+ ""+ case reads countStr of+ [(count, _)] -> return count+ _ ->+ exitFailureMsg $+ "'gs' returned an incomprehensible page count, namely: " ++ countStr+++-- cf. numeric-prelude+divUp :: (Integral a) => a -> a -> a+divUp x y = - div (-x) y++run ::+ Int -> Int -> Maybe Int ->+ FilePath -> FilePath -> [String] -> IO ()+run numJobs firstPage mLastPage input output gsArgs = do+ lastPage <- maybe (countPages input) return mLastPage+ let num = Ix.rangeSize (firstPage, lastPage)+ let ranges =+ zip [0::Integer ..] $+ ListHT.mapAdjacent+ (\p0 p1 -> (p0,p1-1))+ (map ((firstPage+) . flip divUp numJobs) $+ take (numJobs+1) $ iterate (num+) 0)+ Pool.runException (Just numJobs) $+ flip map ranges $ \(job,(p0,p1)) -> do+ callProcess "gs" $+ ("-dFirstPage=" ++ show p0) :+ ("-dLastPage=" ++ show p1) :+ (printf "-sOutputFile=%s-%d" output job) :+ gsArgs +++ [input]+ let pages = do+ (job,range) <- ranges+ page <- Ix.range (1, Ix.rangeSize range)+ return (job, page)+ Fold.for_ (zip [1::Integer ..] pages) $ \(dstPage, (job, srcPage)) -> do+ renameFile+ (printf output srcPage ++ "-" ++ printf "%d" job)+ (printf output dstPage)+++parser :: OP.Parser ([String] -> IO ())+parser =+ pure run+ <*>+ (OP.option+ (OP.eitherReader $ parseNumber "number of jobs" (0<) "positive") $+ 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.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.long "last-page" <>+ OP.metavar "ONEBASED" <>+ OP.value Nothing <>+ OP.help "Last page to render")+ <*>+ OP.strArgument+ (OP.metavar "INPUT" <>+ OP.help "Input Document")+ <*>+ OP.strArgument+ (OP.metavar "OUTPUT" <>+ OP.help "Output Filename Pattern")++info :: OP.Parser a -> OP.ParserInfo a+info p =+ OP.info+ (OP.helper <*> p)+ (OP.fullDesc <>+ OP.progDesc "Let Ghostscript render pages in parallel.")++main :: IO ()+main = do+ args <- Env.getArgs+ let (optParseArgs, gsArgs) = mapSnd (drop 1) $ break ("--" ==) args+ ($gsArgs) =<<+ OP.handleParseResult+ (OP.execParserPure OP.defaultPrefs (info parser) optParseArgs)