diff --git a/CHANGELOG.md b/CHANGELOG.md index 75c5c7d..7d0106b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,28 @@ ------------------------------------------------------------------------------- +### 3.0.0.1 + +#### Added + +- Added project website under docs + +#### Changed + +- Changed docs to haddock +- Increased base optimization with fuzz and map +- Increased default frame rate to 15 +- Set tighter bounds on number of colors +- Set tighter bounds on quality percent clamp +- Query stream for duration and then container for duration +- Fixed empty file name issue + +#### Removed + +- + +------------------------------------------------------------------------------- + ### 3.0.0.0 #### Added diff --git a/Gifcurry.cabal b/Gifcurry.cabal index ddbfcd2..3aa8c32 100644 --- a/Gifcurry.cabal +++ b/Gifcurry.cabal @@ -1,5 +1,5 @@ name: Gifcurry -version: 3.0.0.0 +version: 3.0.0.1 synopsis: GIF creation utility. description: Your open source video to GIF maker. homepage: https://github.com/lettier/gifcurry diff --git a/README.md b/README.md index f386878..85ea22e 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ Is Gifcurry another Electron app? No way! Gifcurry is 100% #electronfree. No need to download more RAM, Gifcurry is light as a feather. Run it all day, run it all year—you'll never notice. -I know what your're thinkin', "Gifcurry is just FFMpeg and ImageMagick," but you'd be wrong. +I know what you're thinkin', "Gifcurry is just FFMpeg and ImageMagick," but you'd be wrong. Gifcurry hides all the goofy details so you can concentrate on what matters—the almighty GIF. Making GIFs with Gifcurry is fun so try it out! @@ -39,6 +39,8 @@ Your template doesn't allow video in the hero image? Gifcurry. No GIF of your favorite movie scene? Gifcurry. Need a custom animated emoji for Slack? Gifcurry. Have an idea of the perfect GIF to close out that email? Gifcurry. +Your README needs a GIF? Gifcurry. +Video doesn't auto play on iOS? Gifcurry. Gifcurry comes in handy for all sorts of scenarios. @@ -111,7 +113,7 @@ gifcurry_cli \ `'╙╙╙╙'`` -Gifcurry 3.0.0.0 +Gifcurry 3.0.0.1 (C) 2016 David Lettier lettier.com @@ -155,7 +157,7 @@ Bottom Crop: 25.000 ## How do I get a copy of Gifcurry? -Gifcurry works on Linux, Mac, and probably Windows (left me know). +Gifcurry works on Linux, Mac, and most likely Windows. Make sure you have FFmpeg, GStreamer, ImageMagick, and GTK+ installed on your machine. To find the latest version of Gifcurry, head over to the [releases page](https://github.com/lettier/gifcurry/releases). @@ -163,15 +165,15 @@ To find the latest version of Gifcurry, head over to the ### I use Linux. If you use Linux then the easiest way to grab a copy of Gifcurry is by downloading the -[AppImage](https://github.com/lettier/gifcurry/releases/download/3.0.0.0/gifcurry-3.0.0.0-x86_64.AppImage). +[AppImage](https://github.com/lettier/gifcurry/releases/download/3.0.0.1/gifcurry-3.0.0.1-x86_64.AppImage). After you download the -[AppImage](https://github.com/lettier/gifcurry/releases/download/3.0.0.0/gifcurry-3.0.0.0-x86_64.AppImage), +[AppImage](https://github.com/lettier/gifcurry/releases/download/3.0.0.1/gifcurry-3.0.0.1-x86_64.AppImage), right click on it, select permissions, and check the box near execute. With that out of the way—you're all set—just double click on the AppImage and the GUI will fire right up. You can also download and install the -[AppImage](https://github.com/lettier/gifcurry/releases/download/3.0.0.0/gifcurry-3.0.0.0-x86_64.AppImage) +[AppImage](https://github.com/lettier/gifcurry/releases/download/3.0.0.1/gifcurry-3.0.0.1-x86_64.AppImage) using the handy [AppImage install script](https://raw.githubusercontent.com/lettier/gifcurry/master/packaging/linux/app-image/gifcurry-app-image-install.sh) (right click and save link as). @@ -179,7 +181,7 @@ Download the script, right click on it, select permissions, check the box near e You should now see Gifcurry listed alongside your other installed programs. If you want the CLI then download the -[prebuilt version](https://github.com/lettier/gifcurry/releases/download/3.0.0.0/gifcurry-linux-3.0.0.0.tar.gz) +[prebuilt version](https://github.com/lettier/gifcurry/releases/download/3.0.0.1/gifcurry-linux-3.0.0.1.tar.gz) for Linux, extract it, open up your terminal, `cd` to the bin folder, and then run `gifcurry_cli -?`. As an added bonus, inside the bin directory is the GUI version @@ -191,17 +193,14 @@ If you'd rather install Gifcurry via pacman then copy the following into your te ```bash cd -# Install git. sudo pacman -S git -# Install Gifcurry. -mkdir -p build_gifcurry -cd build_gifcurry +mkdir -p build-gifcurry +cd build-gifcurry git clone https://aur.archlinux.org/gifcurry.git cd gifcurry makepkg -sic -# Run Gifcurry CLI and GUI. cd -rm -rf build_gifcurry +rm -rf build-gifcurry gifcurry_cli -? gifcurry_gui ``` @@ -228,94 +227,15 @@ The [Gifcurry snap](https://snapcraft.io/gifcurry) only comes with the GUI. If you want the CLI, download the -[prebuilt version](https://github.com/lettier/gifcurry/releases/download/3.0.0.0/gifcurry-linux-3.0.0.0.tar.gz) +[prebuilt version](https://github.com/lettier/gifcurry/releases/download/3.0.0.1/gifcurry-linux-3.0.0.1.tar.gz) for Linux. ### I use Mac. -If you use Mac then you'll need to find the terminal. -Open Spotlight, type `Terminal`, and press enter. -You should see a window open that has black text -on a white background. - -With the terminal open, copy the following into the terminal to install Homebrew. - -```bash -/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" -brew update -``` - -After installing Homebrew, you'll need to install the right dependencies. - -```bash -xcode-select --install -brew install \ - wget \ - git \ - libffi \ - libsvg \ - librsvg \ - libav \ - libogg \ - libvorbis \ - pkg-config \ - gobject-introspection \ - cairo \ - gdk-pixbuf \ - gsettings-desktop-schemas \ - gtk+3 \ - gtk-mac-integration \ - gnome-icon-theme \ - openh264 \ - theora \ - ffmpeg \ - imagemagick \ - ghostscript \ - gstreamer \ - gst-libav \ - gst-plugins-base \ - gst-plugins-good -brew install --with-gtk+3 gst-plugins-bad -wget -qO- https://get.haskellstack.org/ | sh -s - -f -``` - -The next step is to download the Gifcurry source with a program called `git`. -After downloading the source, change directory (`cd`) into the Gifcurry folder. - -```bash -git clone https://github.com/lettier/gifcurry.git -cd gifcurry/ -``` - -Now that you have the source, copy this into the terminal. -It's scary lookin' but it is telling the program that builds -Gifcurry where the `libffi` package configuration is. - -```bash -LIBFFIPKGCONFIG=`find /usr/local/Cellar -path '*libffi*' -type d -name 'pkgconfig' 2>/dev/null | tr '\n' ':' | sed 's/:$//'` -export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$LIBFFIPKGCONFIG -``` - -We're almost there. -With the `stack` program, install the Haskell specific dependencies and build Gifcurry. - -```bash -stack setup -stack install alex happy -stack install gtk2hs-buildtools -stack install hsc2hs -stack install -``` - -`stack` places the two Gifcurry programs into a special folder. -Copy the following into the terminal to create two shortcuts on your desktop. - -```bash -ln -s $HOME/.local/bin/gifcurry_cli $HOME/Desktop/gifcurry_cli -ln -s $HOME/.local/bin/gifcurry_gui $HOME/Desktop/gifcurry_gui -``` - -You can now click on `gifcurry_gui` from your desktop or run `gifcurry_cli` from the terminal. +Mac users can download and run the +[Mac install script](https://raw.githubusercontent.com/lettier/gifcurry/master/packaging/mac/gifcurry-mac-install-script.command) +(hold down control, click the link, and select "Save Link As..."). +After running the install script, a shortcut to both the Gifcurry GUI and CLI will be on your desktop. ### I'm a Haskell developer. @@ -324,7 +244,7 @@ If you develop Haskell programs then the easiest way to build Gifcurry is with Copy the following into your terminal. ```bash -git clone https://aur.archlinux.org/gifcurry.git +git clone https://github.com/lettier/gifcurry.git cd gifcurry stack setup stack install alex happy @@ -347,7 +267,7 @@ $HOME/.local/bin/gifcurry_gui ### To build Gifcurry. * [GObject Introspection](https://wiki.gnome.org/action/show/Projects/GObjectIntrospection) -* [Haskell](https://docs.haskellstack.org/en/stable/README/) +* [Haskell Stack](https://docs.haskellstack.org/en/stable/README/) ## What is the license? @@ -355,5 +275,5 @@ For license information, see [LICENSE](LICENSE). ## Who wrote Gifcurry? -_(C) 2016 David Lettier_ +(C) 2016 David Lettier [lettier.com](http://www.lettier.com/) diff --git a/docs/index.html b/docs/index.html index 5ff6566..5064355 100644 --- a/docs/index.html +++ b/docs/index.html @@ -146,8 +146,8 @@

ImageMagick.

Linux users can download the - AppImage or - the prebuilt binaries. + AppImage or + the prebuilt binaries. If you'd rather install it, you can do so via pacman (Arch) or diff --git a/makefile b/makefile index dda69a6..194f6ef 100644 --- a/makefile +++ b/makefile @@ -9,14 +9,20 @@ STACK_GHC_EXE=`$(STACK) path --compiler-exe` STACK_GHC_BIN=`$(STACK) path --compiler-bin` STACK_PATHS=$(STACK_PATH_LOCAL_BIN):$(STACK_GHC_BIN) CABAL=env PATH=$(PATH):$(STACK_PATHS) $(STACK_PATH_LOCAL_BIN)/cabal -VERSION='3.0.0.0' +CABAL_SANDBOX_DIR=".cabal-sandbox" +_APPLICATIONS_DESKTOP_DIR="$(CABAL_SANDBOX_DIR)/share/applications" +_ICONS_HICOLOR_SCALABLE_APPS_DIR="$(CABAL_SANDBOX_DIR)/share/icons/hicolor/scalable/apps" +_PACKAGING_LINUX_COMMON_DIR="./packaging/linux/common" +VERSION='3.0.0.1' export PATH := $(PATH):$(STACK_PATH_LOCAL_BIN) -all: setup update sandbox_clean clean alex happy haskell_gi gtk2hs_buildtools install_dependencies configure build install +all: setup update sandbox_clean clean alex happy haskell_gi gtk2hs_buildtools install_dependencies configure build cabal_install setup: - $(STACK) setup && $(STACK) update && $(STACK) install Cabal && $(STACK) install cabal-install + $(STACK) setup && $(STACK) update && \ + $(STACK) install Cabal && \ + $(STACK) install cabal-install alex: setup $(STACK) install alex @@ -51,41 +57,34 @@ install_dependencies: sandbox configure: sandbox $(CABAL) --require-sandbox configure -w $(STACK_GHC_EXE) +applications_desktop: sandbox + mkdir -p $(_APPLICATIONS_DESKTOP_DIR) && \ + cp $(_PACKAGING_LINUX_COMMON_DIR)/gifcurry.desktop $(_APPLICATIONS_DESKTOP_DIR)/ + +icons_hicolor_scalable_apps: applications_desktop + mkdir -p $(_ICONS_HICOLOR_SCALABLE_APPS_DIR) && \ + cp $(_PACKAGING_LINUX_COMMON_DIR)/gifcurry-icon.svg $(_ICONS_HICOLOR_SCALABLE_APPS_DIR)/ + build: configure $(CABAL) --require-sandbox build -j -install: build +cabal_install: applications_desktop icons_hicolor_scalable_apps build $(CABAL) --require-sandbox install -j -w $(STACK_GHC_EXE) --enable-relocatable release: check build $(CABAL) sdist -run_gui: install +run_gui: cabal_install ./.cabal-sandbox/bin/gifcurry_gui -run_cli: install +run_cli: cabal_install ./.cabal-sandbox/bin/gifcurry_cli $(CLI_ARGS) build_docs: setup $(CABAL) haddock --hyperlink-source \ --html-location='http://hackage.haskell.org/package/Gifcurry/docs' \ --contents-location='http://hackage.haskell.org/package/Gifcurry' && \ + mkdir -p ./haddock && \ cp -R ./dist/doc/html/Gifcurry/ ./haddock/Gifcurry-$(VERSION)-docs && \ cd ./haddock && \ tar --format=ustar -cvf ./Gifcurry-$(VERSION)-docs.tar Gifcurry-$(VERSION)-docs - -# Begin Arch Linux Specific -arch_os_build_gifcurry: setup update clean sandbox_clean alex happy haskell_gi gtk2hs_buildtools arch_os_install_dependencies arch_os_configure arch_os_build - -arch_os_install_dependencies: sandbox - $(CABAL) --require-sandbox install -j -w $(STACK_GHC_EXE) --force-reinstalls --reinstall --only-dependencies - -arch_os_configure: sandbox - $(CABAL) --require-sandbox configure -w $(STACK_GHC_EXE) --prefix=$(PREFIX) - -arch_os_build: arch_os_configure - $(CABAL) --require-sandbox build -j - -arch_os_install_gifcurry: arch_os_build - $(CABAL) --require-sandbox copy --destdir=$(DESTDIR) -# End Arch Linux Specific diff --git a/packaging/linux/app-image/gifcurry-app-image-install.sh b/packaging/linux/app-image/gifcurry-app-image-install.sh index c475954..a0cabba 100755 --- a/packaging/linux/app-image/gifcurry-app-image-install.sh +++ b/packaging/linux/app-image/gifcurry-app-image-install.sh @@ -3,7 +3,7 @@ # (C) 2017 David Lettier # lettier.com -GIFCURRY_VERSION="3.0.0.0" +GIFCURRY_VERSION="3.0.0.1" GIFCURRY_RELEASES_DOWNLOAD="https://github.com/lettier/gifcurry/releases/download/$GIFCURRY_VERSION" GIFCURRY_PACKAGING_LINUX_COMMON="https://raw.githubusercontent.com/lettier/gifcurry/master/packaging/linux/common" GIFCURRY_APP_IMAGE="gifcurry-$GIFCURRY_VERSION-x86_64.AppImage" diff --git a/packaging/linux/arch-aur/PKGBUILD b/packaging/linux/arch-aur/PKGBUILD index 20e05d1..6fa2e10 100644 --- a/packaging/linux/arch-aur/PKGBUILD +++ b/packaging/linux/arch-aur/PKGBUILD @@ -1,7 +1,7 @@ # Maintainer: Lettier _name=gifcurry -_ver=3.0.0.0 +_ver=3.0.0.1 _xrev=0 pkgname=${_name} diff --git a/packaging/linux/snap/snapcraft.yaml b/packaging/linux/snap/snapcraft.yaml index d365c87..1817b90 100644 --- a/packaging/linux/snap/snapcraft.yaml +++ b/packaging/linux/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: gifcurry -version: '3.0.0.0' +version: '3.0.0.1' summary: Your open source video to GIF maker. type: app description: | diff --git a/packaging/mac/gifcurry-mac-install-script.command b/packaging/mac/gifcurry-mac-install-script.command new file mode 100644 index 0000000..05c76fc --- /dev/null +++ b/packaging/mac/gifcurry-mac-install-script.command @@ -0,0 +1,48 @@ +#!/usr/bin/env bash + +# Gifcurry +# (C) 2018 David Lettier +# lettier.com + +/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" +brew update +xcode-select --install +brew install \ + wget \ + git \ + libffi \ + libsvg \ + librsvg \ + libav \ + libogg \ + libvorbis \ + pkg-config \ + gobject-introspection \ + cairo \ + gdk-pixbuf \ + gsettings-desktop-schemas \ + gtk+3 \ + gtk-mac-integration \ + gnome-icon-theme \ + openh264 \ + theora \ + ffmpeg \ + imagemagick \ + ghostscript \ + gstreamer \ + gst-libav \ + gst-plugins-base \ + gst-plugins-good +brew install --with-gtk+3 gst-plugins-bad +wget -qO- https://get.haskellstack.org/ | sh -s - -f +git clone https://github.com/lettier/gifcurry.git +cd gifcurry/ +LIBFFIPKGCONFIG=`find /usr/local/Cellar -path '*libffi*' -type d -name 'pkgconfig' 2>/dev/null | tr '\n' ':' | sed 's/:$//'` +export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$LIBFFIPKGCONFIG +stack setup +stack install alex happy +stack install gtk2hs-buildtools +stack install hsc2hs +stack install +ln -s $HOME/.local/bin/gifcurry_cli $HOME/Desktop/gifcurry_cli +ln -s $HOME/.local/bin/gifcurry_gui $HOME/Desktop/gifcurry_gui diff --git a/src/data/gui.glade b/src/data/gui.glade index 15bdc0d..c0e52c3 100644 --- a/src/data/gui.glade +++ b/src/data/gui.glade @@ -1224,7 +1224,7 @@ Author: David Lettier gifcurry-window gifcurry-window Gifcurry - 3.0.0.0 + 3.0.0.1 (C) 2016 David Lettier lettier.com https://github.com/lettier/gifcurry diff --git a/src/gui/Main.hs b/src/gui/Main.hs index 3db5827..b4381af 100644 --- a/src/gui/Main.hs +++ b/src/gui/Main.hs @@ -237,6 +237,7 @@ handleInputFileChoice Gifcurry.getVideoDurationInSeconds Gifcurry.defaultGifParams { Gifcurry.inputFile = inFilePath } + print maybeVideoDuration maybeWidthHeight <- Gifcurry.getVideoWidthAndHeight Gifcurry.defaultGifParams @@ -712,7 +713,7 @@ outFileChooserButtonGetFilePath :: GI.Gtk.FileChooserButton -> GI.Gtk.Entry -> I outFileChooserButtonGetFilePath outFileChooserButton outFileNameEntry = do filePath <- fileChooserButtonGetString outFileChooserButton fileName <- Data.Text.unpack . Data.Text.strip <$> GI.Gtk.entryGetText outFileNameEntry - if Data.List.null filePath + if Data.List.null filePath || Data.List.null fileName then return fileName else do isDirectory <- System.Directory.doesDirectoryExist filePath diff --git a/src/lib/Gifcurry.hs b/src/lib/Gifcurry.hs index 4e1580f..9de2ba0 100644 --- a/src/lib/Gifcurry.hs +++ b/src/lib/Gifcurry.hs @@ -60,7 +60,7 @@ data GifParams = -- | The version number. versionNumber :: String -versionNumber = "3.0.0.0" +versionNumber = "3.0.0.1" -- | Specifies default parameters for 'startTime', 'durationTime', 'widthSize', 'qualityPercent', and 'fontChoice'. defaultGifParams :: GifParams @@ -239,17 +239,38 @@ gifParamsValid -- Just float -> float -- @ getVideoDurationInSeconds :: GifParams -> IO (Maybe Float) -getVideoDurationInSeconds GifParams { inputFile } = tryFfprobe params >>= result +getVideoDurationInSeconds GifParams { inputFile } = do + streamResult <- result <$> tryFfprobe streamParams + if streamResult <= 0.0 + then do + containerResult <- result <$> tryFfprobe containerParams + if containerResult <= 0.0 + then return Nothing + else return $ Just containerResult + else return $ Just streamResult where - result :: Either IOError String -> IO (Maybe Float) - result (Left _) = return Nothing - result (Right durationString) = return (readMaybe durationString :: Maybe Float) - params :: [String] - params = + result :: Either IOError String -> Float + result (Left _) = 0.0 + result (Right durationString) = fromMaybe 0.0 (readMaybe durationString :: Maybe Float) + streamParams :: [String] + streamParams = [ "-i" , inputFile , "-v" - , "quiet" + , "error" + , "-select_streams" + , "v:0" + , "-show_entries" + , "stream=duration" + , "-of" + , "default=noprint_wrappers=1:nokey=1" + ] + containerParams :: [String] + containerParams = + [ "-i" + , inputFile + , "-v" + , "error" , "-show_entries" , "format=duration" , "-of" @@ -334,7 +355,7 @@ videoOutputFile outputFile = defaultGifParams { outputFile = outputFile, saveAsVideo = True } defaultFrameRate :: Float -defaultFrameRate = 12.0 +defaultFrameRate = 15.0 validateAndAdjustFrameRate :: GifParams -> Maybe Float -> Float validateAndAdjustFrameRate gifParams = @@ -530,10 +551,10 @@ mergeFramesIntoGif frameRate = do maybeWidthHeight <- - maybeGetFirstFrameFilePath tempDir - >>= maybeGetFirstFrameWidthHeight - let frameRate' = maybeFrameRateOrDefaultFrameRate (Just frameRate) - let delay = show $ 100.0 / frameRate' + maybeGetFirstFrameFilePath tempDir >>= + maybeGetFirstFrameWidthHeight + let frameRate' = maybeFrameRateOrDefaultFrameRate (Just frameRate) + let delay = show $ 100.0 / frameRate' let outputFile' = if saveAsVideo then tempDir ++ "/finished-result.gif" @@ -543,28 +564,28 @@ mergeFramesIntoGif , "-delay" , delay , tempDir ++ "/*." ++ frameFileExtension - , "-coalesce" + ] + ++ annotate fontChoice maybeWidthHeight topText "north" + ++ annotate fontChoice maybeWidthHeight bottomText "south" + ++ [ "+dither" , "-colors" - , show $ ncolors qualityPercent - , "-dither" - , "FloydSteinberg" + , show $ numberOfColors qualityPercent + , "-fuzz" + , "2%" , "-layers" - , "remove-dups" + , "OptimizeFrame" , "-layers" - , "compare-any" - , "-layers" - , "optimize-transparency" + , "OptimizeTransparency" , "-loop" , "0" + , "+map" + , outputFile' ] - ++ annotate fontChoice maybeWidthHeight topText "north" - ++ annotate fontChoice maybeWidthHeight bottomText "south" - ++ [outputFile'] putStrLn $ "[INFO] Saving your GIF to: " ++ outputFile' result <- try $ readProcess "convert" params [] if isLeft result then return result - else return (Right outputFile') + else return $ Right outputFile' convertGifToVideo :: GifParams -> String -> IO (Either IOError String) convertGifToVideo GifParams { outputFile } gifFilePath = do @@ -591,50 +612,49 @@ convertGifToVideo GifParams { outputFile } gifFilePath = do else return (Right outputFile') qualityPercentClamp :: Float -> Float -qualityPercentClamp qp - | qp > 100.0 = 100.0 - | qp < 0.0 = 2.0 - | otherwise = qp - -ncolors :: Float -> Int -ncolors qp - | qpc < 0.0 = 1 - | qpc >= 100.0 = 256 - | otherwise = truncate (qpc / 100.0 * 256.0) +qualityPercentClamp qualityPercent + | qualityPercent > 100.0 = 100.0 + | qualityPercent < 0.0 = 1.0 + | otherwise = qualityPercent + +numberOfColors :: Float -> Int +numberOfColors qualityPercent + | qualityPercentClamp qualityPercent <= 1.0 = 2 + | qualityPercentClamp qualityPercent >= 100.0 = floor maxColors + | otherwise = truncate $ (qualityPercent / 100.0) * maxColors where - qpc :: Float - qpc = qualityPercentClamp qp + maxColors :: Float + maxColors = 256.0 annotate :: String -> Maybe (Int, Int) -> String -> String -> [String] annotate fontChoiceArg maybeWidthHeight text topBottom = - [ "-gravity" - , topBottom - ] - ++ fontSetting fontChoiceArg - ++ - [ "-stroke" - , "#000C" - , "-strokewidth" - , "10" - , "-density" - , "96" - , "-pointsize" - , pointsize - , "-annotate" - , "+0+10" - , text - , "-stroke" - , "none" - , "-fill" - , "white" - , "-density" - , "96" - , "-pointsize" - , pointsize - , "-annotate" - , "+0+10" - , text - ] + [ "-gravity" + , topBottom + ] + ++ fontSetting fontChoiceArg + ++ [ "-stroke" + , "#000C" + , "-strokewidth" + , "10" + , "-density" + , "96" + , "-pointsize" + , pointsize + , "-annotate" + , "+0+10" + , text + , "-stroke" + , "none" + , "-fill" + , "white" + , "-density" + , "96" + , "-pointsize" + , pointsize + , "-annotate" + , "+0+10" + , text + ] where pointsize :: String pointsize = show $ pointSize maybeWidthHeight text