diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,39 @@
 
 ## Changelog
 
+-------------------------------------------------------------------------------
+
+### 3.0.0.0
+
+#### Added
+
+- Added a video preview using GStreamer
+- Added start time and end time indicators to UI
+- Added a save as video option to both the GUI and CLI
+- Added a crop mode to the UI
+- Added crop parameters to the CLI
+- Added the following to the library interface
+    - `getOutputFileWithExtension`
+    - `getVideoWidthAndHeight`
+    - `defaultFontChoice`
+- Added snap distribution
+
+#### Changed
+
+- Reconfigured the UI to show the preview off to the right along with the start and duration controls
+- Changed the image first and last frame preview to be the fallback if GStreamer errors
+- Changed text entries to spin buttons for better error checking
+    - The spin buttons provide better feedback about the min and max settings
+- Moved the GUI preview code into a separate module
+- Locked the GHC version to avoid build issues concerning haskell-gi and GHC 8.2.X
+- Changed the logo and icon
+
+#### Removed
+
+-
+
+-------------------------------------------------------------------------------
+
 ### 2.3.0.0
 
 #### Added
diff --git a/Gifcurry.cabal b/Gifcurry.cabal
--- a/Gifcurry.cabal
+++ b/Gifcurry.cabal
@@ -1,7 +1,7 @@
 name:                 Gifcurry
-version:              2.3.0.0
+version:              3.0.0.0
 synopsis:             GIF creation utility.
-description:          Create animated GIFs, optionally overlaid with text, from video files.
+description:          Your open source video to GIF maker.
 homepage:             https://github.com/lettier/gifcurry
 license:              BSD3
 license-file:         LICENSE
@@ -16,16 +16,17 @@
                       , LICENSE
                       , CHANGELOG.md
                       , ./lib/GtkMainSyncAsync.hs
+                      , ./lib/GiCairoCairoBridge.hs
                       , ./lib/LICENSE
                       , ./src/dev/Paths_Gifcurry.hs
                       , ./src/data/gifcurry-logo.svg
-                      , ./src/data/gifcurry-icon.png
+                      , ./src/data/gifcurry-icon.svg
                       , ./makefile
 data-files:             data/gui.glade
                       , data/gifcurry-logo.svg
-                      , data/gifcurry-icon.png
+                      , data/gifcurry-icon.svg
 data-dir:             ./src/
-cabal-version:        >=1.10
+cabal-version:        >= 1.10
 
 source-repository head
   type:     git
@@ -46,19 +47,33 @@
 
 executable            gifcurry_gui
   main-is:            Main.hs
-  build-depends:        base >= 4.7 && < 5
-                      , haskell-gi-base == 0.20.*
-                      , gi-glib == 2.0.*
+  build-depends:        base == 4.9.*
+                      , haskell-gi == 0.21.*
+                      , haskell-gi-base == 0.21.*
                       , gi-gobject == 2.0.*
-                      , gi-gtk == 3.0.*
+                      , gi-glib == 2.0.*
                       , gi-gdk == 3.0.*
+                      , gi-gtk == 3.0.*
+                      , gi-cairo == 1.0.*
+                      , gi-gst == 1.0.*
+                      , gi-gstvideo == 1.0.*
+                      , cairo == 0.13.*
+                      , bytestring == 0.10.*
                       , process >= 1.2 && <= 1.4.4
                       , temporary >= 1.2 && < 1.3
                       , directory == 1.3.*
                       , text == 1.2.*
                       , filepath == 1.4.*
-  other-modules:      Paths_Gifcurry, GtkMainSyncAsync, Gifcurry
-  ghc-options:        -threaded -with-rtsopts=-N -Wall -freverse-errors
+                      , transformers == 0.5.*
+  other-modules:        Paths_Gifcurry
+                      , GuiRecords
+                      , Gifcurry
+                      , GuiPreview
+                      , GuiMisc
+
+                      , GtkMainSyncAsync
+                      , GiCairoCairoBridge
+  ghc-options:        -threaded -rtsopts -with-rtsopts=-N -Wall -freverse-errors
   hs-source-dirs:       ./lib/
                       , ./src/
                       , ./src/gui/
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,254 +1,359 @@
-![Gifcurry](http://i.imgur.com/jhU7puN.png)
+![Gifcurry](https://i.imgur.com/t6zJGR9.png)
 
-# Gifcurry
+# Tell me about Gifcurry.
 
-Create animated GIFs, optionally overlaid with text, from video files.
+Gifcurry is your only open source video to GIF maker built with Haskell.
+Load a video, make some edits, and save it as a GIF—it's that easy.
+Most video formats should work so go wild.
+And since it's made with Haskell, you know it's good.
 
-## Screenshots
+For the command line averse, there is a GUI.
+Die-hard terminal aficionado? Gifcurry has you covered with its CLI.
+And for the Haskell programmers out there, there is also a library API.
 
-![GUI](https://i.imgur.com/U8xnK82.gif)
+Gifcurry can save your creation as a GIF or as a video.
+So if you hate GIFs with a passion—no problem!
+Just select "save as video" and do your part to rid the world of GIFs.
 
-## Sample GIF
+Enjoy memes? Great! Gifcurry can add text to the top and/or the bottom of your GIF.
+Just type in some text for the top or type in some text for the bottom or type in
+some pithy text for both the top and bottom—Gifcurry don't care.
+Oh and you can select the font too so you're never too far from Comic Sans.
 
-![Caminandes 3: Llamigos (2016) - Blender Foundation](https://i.imgur.com/FJW2gBc.gif)  
-Credit: [Caminandes 3: Llamigos (2016) - Blender Foundation](http://www.caminandes.com/)
+Gifcurry caters to the power user with its crop tool.
+You can crop from the left, the right, the top, and/or the bottom.
+With Gifcurry, you can cut out anything you don't want.
 
-## CLI Usage
+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 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!
+
+## What do I need Gifcurry for?
+
+Need to show off that new UI feature in a pull request? Gifcurry.  
+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.  
+
+Gifcurry comes in handy for all sorts of scenarios.
+
+## What does the GUI look like?
+
+![GUI](https://i.imgur.com/oYQAyQY.png)
+![GUI](https://i.imgur.com/er3AQjP.png)
+![GUI](https://i.imgur.com/mW1Rsd1.png)
+![GUI](https://i.imgur.com/IQqkxfB.png)
+![GUI](https://i.imgur.com/NMadQxQ.png)
+
+## Got any sample GIFs?
+
+![GIF](https://i.imgur.com/alxcMli.gif)
+![GIF](https://i.imgur.com/FUjIBm2.gif)
+
+## How do I use the command line interface (CLI)?
+
 ```bash
 gifcurry_cli [OPTIONS]
 
-Common flags:
-  -i --inputfile=FILE      The input video file path and name.
-  -o --outputfile=ITEM     The output GIF file path and name.
-  -s --starttime=NUM       The start time (in seconds) for the first frame.
-  -d --durationtime=NUM    How long the GIF lasts (in seconds) from the start
-                           time.
-  -w --widthsize=INT       How wide the GIF needs to be. Height will scale to
-                           match.
-  -q --qualitypercent=NUM  Ranges from 0.0 to 100.0.
-  -f --fontchoice=ITEM     Choose your desired font for the top and bottom
-                           text.
-  -t --toptext=ITEM        The text you wish to add to the top of the GIF.
-  -b --bottomtext=ITEM     The text you wish to add to the bottom of the GIF.
-  -? --help                Display help message
-  -V --version             Print version information
-     --numeric-version     Print just the version number
+FILE IO:
+  -i --input-file=FILE      The input video file path and name.
+  -o --output-file=FILE     The output GIF file path and name.
+  -m --save-as-video        If present, saves the GIF as a video.
+TIME:
+  -s --start-time=NUM       The start time (in seconds) for the first frame.
+  -d --duration-time=NUM    How long the GIF lasts (in seconds) from the
+                            start time.
+OUTPUT FILE SIZE:
+  -w --width-size=INT       How wide the GIF needs to be. Height will scale
+                            to match.
+  -q --quality-percent=NUM  From 1 (very low quality) to 100 (the best
+                            quality). Controls how many colors are used and how
+                            many frames per second there are.
+TEXT:
+  -f --font-choice=TEXT     Choose your desired font for the top and bottom
+                            text.
+  -t --top-text=TEXT        The text you wish to add to the top of the GIF.
+  -b --bottom-text=TEXT     The text you wish to add to the bottom of the
+                            GIF.
+CROP:
+  -L --left-crop=NUM        The amount you wish to crop from the left.
+  -R --right-crop=NUM       The amount you wish to crop from the right.
+  -T --top-crop=NUM         The amount you wish to crop from the top.
+  -B --bottom-crop=NUM      The amount you wish to crop from the bottom.
+INFO:
+  -? --help                 Display help message
+  -V --version              Print version information
 ```
 
-## CLI Example
+## Got a CLI example?
 
-```Bash
-~/gifcurry ❯❯❯ ./gifcurry_cli \
-  -i ./03_caminandes_llamigos_1080p.mp4 \
-  -o ./out.gif \
-  -s 42 \
-  -d 4 \
-  -w 600 \
-  -q 100 \
-  -f 'Roboto Condensed Bold Italic' \
-  -t 'Download' \
-  -b 'Gifcurry'
+```text
+gifcurry_cli \
+-i ~/Videos/video.mp4 -o ~/tmp/test -m \
+-L 25 -R 25 -T 25 -B 25 \
+-s 149.11 -d 1 \
+-f 'fira sans' -t 'Top Text' -b 'Bottom Text'
 
- _____ _  __                           
-|  __ (_)/ _|                          
-| |  \/_| |_ ___ _   _ _ __ _ __ _   _ 
-| | __| |  _/ __| | | | '__| '__| | | |
-| |_\ \ | || (__| |_| | |  | |  | |_| |
- \____/_|_| \___|\__,_|_|  |_|   \__, |
-                                  __/ |
-                                 |___/ 
+       ╓╦NÑ╫╫╫╫╫ÑNw,                                                           
+    , `Ñ╫╫╫╫Ñ `'╩╫╫╫Ñw      ,╓ææµ  ║▓▓⌐ ,▄▄▓▄                                  
+   ]╫Ñ  Ñ╫╫╫ ,╫N ╙╫╫╫╫Ñ   ╓▓▓▀╙╙▀╨ .╓, ,▓▓▌,  ,╓╥╓, ,,  ,,, ,, ,╓ ,, ,╓ ,,  ., 
+  ]╫╫╫Ñ '╫╫⌐ Ñ╫⌐  Ñ╫╫╫╫Ñ -▓▓M ╥╥╥╥ ▐▓▓ ▀▓▓▀▀ ▄▓▀╙▀▀ ▓▓Γ ╫▓▌ ╫▓▓▌▀ ▓▓▓▀▀╙▓▓  ▓▓▀
+  ╩╫╫╫╫H ╠Ñ 1╫H ╫ '╫╫╫╫╣  ▓▓H ╙▐▓▓ ▐▓▓  ▓▓Γ ▐▓▓⌐    ▓▓Γ ╫▓▌ ╫▓▌   ▓▓▌   ║▓▌╢▓▌ 
+  ]╫╫╫╫╫ ' j╫Å j╫H ╠╫╫╫╫  ╙▓▓▄▄▄▓▓ ▐▓▓  ▓▓Γ  ▀▓▌▄▄▄ ▓▓▌▄▓▓▌ ╫▓▌   ▓▓▌    ▓▓▓▓  
+   Ñ╫╫╫╫N jÑ╬ .╫╫╫⌐ Ñ╫╬┘    └╙╙╙└  `└└  └└`    ╙╙╙`  ╙╙└`└` `└└   └└`    ,▓▓▀  
+    ╨╫╫╫╫N '┘ ╬╫╫╫╫  ╩`                                                 ▀▀▀└   
+     `╙Ñ╫╫╫N╦╦╫╫╫╫╫╩                                                           
+         `'╙╙╙╙'``                                                             
 
-Gifcurry 2.3.0.0
+
+Gifcurry 3.0.0.0
 (C) 2016 David Lettier
 lettier.com
 
-----------------------------------------
+[INFO] Here are your settings.
 
-Input file: ./03_caminandes_llamigos_1080p.mp4
-Output file: ./out.gif
-Start second: 42.000
-Duration: 4.000 seconds
-GIF width: 600px
-Quality: 100.0%
-Font Choice: Roboto Condensed Bold Italic
-Top text: Download
-Bottom text: Gifcurry
+FILE IO:
 
-Writing temporary frames to... ./frames13465
-Font matched: Roboto-Condensed-Bold-Italic
-Writing your GIF to... ./out.gif
-Done.
-```
+Input File: /home/Videos/video.mp4
+Output File: /home/tmp/test.mp4
+Save As Video: Yes
 
-## Dependencies
+TIME:
 
-### Run
+Start Second: 149.110
+Duration Time: 1.000 seconds
 
-* [GTK+](http://www.gtk.org/download/index.php)
-* [FFmpeg](https://www.ffmpeg.org/download.html)
-* [ImageMagick](http://www.imagemagick.org/script/download.php)
+OUTPUT FILE SIZE:
 
-### Build
+Width Size: 500px
+Quality Percent: 100.0%
 
-* [Haskell](https://www.haskell.org/platform/)
+TEXT:
 
-## Install & Run
+Font Choice: fira sans
+Top Text: Top Text
+Bottom Text: Bottom Text
 
-### Linux
+CROP:
 
-#### [AppImage (GUI Only)](https://appimage.github.io/gifcurry/)
+Left Crop: 25.000
+Right crop: 25.000
+Top Crop: 25.000
+Bottom Crop: 25.000
 
-```bash
-# Install FFmpeg (https://www.ffmpeg.org/download.html)
-# Install ImageMagick (https://www.imagemagick.org/script/download.php)
-# Install GNU Wget (https://www.gnu.org/software/wget/) or download the AppImage with your browser
-# Find the latest release at https://github.com/lettier/gifcurry/releases
-# * equals the version number
-# Download gifcurry-*-x86_64.AppImage
-# Right click on the AppImage and allow the file to be executed as a program
-wget https://github.com/lettier/gifcurry/releases/download/*/gifcurry-*-x86_64.AppImage
-chmod a+x gifcurry-*-x86_64.AppImage
-./gifcurry-*-x86_64.AppImage
-# If you would like to install the AppImage, you can run the following
-cd
-wget "https://raw.githubusercontent.com/lettier/gifcurry/master/packaging/linux/gifcurry-gui-install.sh" -O "gifcurry-gui-install.sh"
-chmod +x "gifcurry-gui-install.sh"
-./gifcurry-gui-install.sh
+[INFO] Writing the temporary frames to: /home/.cache/gifcurry/gifcurry-frames17389
+[INFO] Your font choice matched to "Fira-Sans".
+[INFO] Saving your GIF to: /home/.cache/gifcurry/gifcurry-frames17389/finished-result.gif
+[INFO] Saving your video to: /home/tmp/test.mp4
+[INFO] All done.
 ```
 
-#### Ubuntu/Mint
+## How do I get a copy of Gifcurry?
 
-```bash
-# Install FFmpeg (https://www.ffmpeg.org/download.html)
-# Install ImageMagick (https://www.imagemagick.org/script/download.php)
-sudo add-apt-repository ppa:jonathonf/ffmpeg-3
-sudo apt-get update
-sudo apt-get install ffmpeg imagemagick
-# Install GNU Wget (https://www.gnu.org/software/wget/)
-# Find the latest release at https://github.com/lettier/gifcurry/releases
-# * equals the version number
-wget https://github.com/lettier/gifcurry/releases/download/*/gifcurry-linux-*.tar.gz
-tar -xvfz gifcurry-linux*.tar.gz
-cd gifcurry-linux-*/bin
-./gifcurry_cli -?
-./gifcurry_gui
-```
+Gifcurry works on Linux, Mac, and probably Windows (left me know).
+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).
 
-#### Arch/Manjaro/Antergos
+### 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).
+After you download the
+[AppImage](https://github.com/lettier/gifcurry/releases/download/3.0.0.0/gifcurry-3.0.0.0-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)
+using the handy
+[AppImage install script](https://raw.githubusercontent.com/lettier/gifcurry/master/distribution/linux/app-image/gifcurry-app-image-install.sh)
+(right click and save link as).
+Download the script, right click on it, select permissions, check the box near execute, and double click on it.
+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)
+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
+too so now you have both.
+
+#### I use Arch/Manjaro/Antergos/pacman.
+
+If you'd rather install Gifcurry via pacman then copy the following into your terminal.
+
 ```bash
 cd
-# Install Git
+# Install git.
 sudo pacman -S git
-# Install Gifcurry from AUR
+# Install 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
 gifcurry_cli -?
 gifcurry_gui
 ```
 
-### Mac
+#### I use Ubuntu/Mint/Debian/Deepin/snap.
 
-#### Binaries
+Gifcurry is available as a snap from [Snapcraft](https://snapcraft.io/).
+If you don't already have `snap`, go ahead and install it using the command `sudo apt install snapd`.
 
+You can install the
+[Gifcurry snap](https://snapcraft.io/gifcurry)
+right from your browser or via the command line.
+For the command line route, paste the following into your terminal.
+
 ```bash
-# Install Homebrew
-/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
-brew update
-brew install ffmpeg
-brew install imagemagick
-brew install ghostscript
-brew install wget
-brew install gnome-icon-theme
-# Find the latest release at https://github.com/lettier/gifcurry/releases
-# * equals the version number
-wget https://github.com/lettier/gifcurry/releases/download/*/gifcurry-macosx-*.tar.gz
-tar xvfz gifcurry-macosx-*.tar.gz
-cd gifcurry-macosx-*/bin
-./gifcurry_cli -?
-./gifcurry_gui
+snap install gifcurry
+sudo snap connect gifcurry:mount-observe
+sudo snap connect gifcurry:removable-media
+sudo snap connect gifcurry:raw-usb
+gifcurry
 ```
 
-#### Compile
+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)
+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
-# Install Homebrew
 /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
 brew update
-brew install xcode
-brew install git
-git clone git@github.com:lettier/gifcurry.git
+```
+
+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/
-brew cask install haskell-platform
-brew install pkg-config gobject-introspection cairo gdk-pixbuf gsettings-desktop-schemas \
-  gtk+3 gtk-mac-integration ffmpeg imagemagick ghostscript gnome-icon-theme
-stack setup
-stack install hsc2hs
-stack install
-export PATH=$PATH:"$HOME/.local/bin/"
-gifcurry_cli -?
-gifcurry_gui
 ```
 
-### [Hackage](https://hackage.haskell.org/package/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.
 
-#### Stack
+```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
-# Install GNU Wget (https://www.gnu.org/software/wget/)
-# Install FFmpeg (https://www.ffmpeg.org/download.html)
-# Install ImageMagick (https://www.imagemagick.org/script/download.php)
-# Install Haskell (https://www.haskell.org/platform/)
-# Find the latest version number at https://hackage.haskell.org/package/Gifcurry
-# * equals the version number
-wget https://hackage.haskell.org/package/Gifcurry-*/Gifcurry-*.tar.gz
-tar -xvzf Gifcurry-*.tar.gz
-cd Gifcurry-*
 stack setup
+stack install alex happy
+stack install gtk2hs-buildtools
+stack install hsc2hs
 stack install
-stack exec -- gifcurry_cli -?
-stack exec -- gifcurry_gui
 ```
 
-#### Cabal
+`stack` places the two Gifcurry programs into a special folder.
+Copy the following into the terminal to create two shortcuts on your desktop.
 
 ```bash
-# Install FFmpeg (https://www.ffmpeg.org/download.html)
-# Install ImageMagick (https://www.imagemagick.org/script/download.php)
-# Install Haskell (https://www.haskell.org/platform/)
-cabal update
-cabal install alex happy -j
-cabal install gifcurry -j
-cd ~/.cabal/bin
-./gifcurry_cli -?
-./gifcurry_gui
+ln -s $HOME/.local/bin/gifcurry_cli $HOME/Desktop/gifcurry_cli
+ln -s $HOME/.local/bin/gifcurry_gui $HOME/Desktop/gifcurry_gui
 ```
 
-### [Github](https://github.com/lettier/gifcurry)
+You can now click on `gifcurry_gui` from your desktop or run `gifcurry_cli` from the terminal.
 
+### I'm a Haskell developer.
+
+If you develop Haskell programs then the easiest way to build Gifcurry is with
+[Haskell Stack](https://docs.haskellstack.org/en/stable/README/).
+Copy the following into your terminal.
+
 ```bash
-# Install GNU Make (https://www.gnu.org/software/make/)
-# Install FFmpeg (https://www.ffmpeg.org/download.html)
-# Install ImageMagick (https://www.imagemagick.org/script/download.php)
-# Install Haskell (https://www.haskell.org/platform/)
-git clone git@github.com:lettier/gifcurry.git
-cd gifcurry/
-make
-make run_cli CLI_ARGS='-?'
-make run_gui
+git clone https://aur.archlinux.org/gifcurry.git
+cd gifcurry
+stack setup
+stack install alex happy
+stack install gtk2hs-buildtools
+stack install hsc2hs
+stack install
+$HOME/.local/bin/gifcurry_cli -?
+$HOME/.local/bin/gifcurry_gui
 ```
 
-## License
+## What dependencies does Gifcurry use?
 
+### To run Gifcurry.
+
+* [GTK+](http://www.gtk.org/download/index.php)
+* [FFmpeg](https://www.ffmpeg.org/download.html)
+* [GStreamer](https://gstreamer.freedesktop.org/download/)
+* [ImageMagick](http://www.imagemagick.org/script/download.php)
+
+### To build Gifcurry.
+
+* [GObject Introspection](https://wiki.gnome.org/action/show/Projects/GObjectIntrospection)
+* [Haskell](https://docs.haskellstack.org/en/stable/README/)
+
+## What is the license?
+
 For license information, see [LICENSE](LICENSE).
 
-## Copyright
+## Who wrote Gifcurry?
 
 _(C) 2016 David Lettier_  
 [lettier.com](http://www.lettier.com/)
diff --git a/lib/GiCairoCairoBridge.hs b/lib/GiCairoCairoBridge.hs
new file mode 100644
--- /dev/null
+++ b/lib/GiCairoCairoBridge.hs
@@ -0,0 +1,31 @@
+-- original author:
+--    Mirco "MacSlow" Mueller <macslow@bangang.de>
+--
+-- created:
+--    10.1.2006 (or so)
+--
+-- http://www.gnu.org/licenses/licenses.html#GPL
+--
+-- ported to Haskell (gtk2hs) by:
+--    Duncan Coutts <duncan.coutts@worc.ox.ac.uk>
+--
+-- ported to haskell-gi from the gtk2hs port by:
+--    Iñaki García Etxebarria <garetxe@gmail.com>
+--
+-- Modified by David Lettier
+
+module GiCairoCairoBridge (renderWithContext) where
+
+import Control.Monad.Trans.Reader (runReaderT)
+import qualified GI.Cairo
+import Graphics.Rendering.Cairo.Internal (Render(runRender))
+import Graphics.Rendering.Cairo.Types (Cairo(Cairo))
+import Foreign.Ptr (castPtr)
+
+-- | This function bridges gi-cairo with the hand-written cairo
+-- package. It takes a `GI.Cairo.Context` (as it appears in gi-cairo),
+-- and a `Render` action (as in the cairo lib), and renders the
+-- `Render` action into the given context.
+renderWithContext :: GI.Cairo.Context -> Render () -> IO ()
+renderWithContext ct r = GI.Cairo.withManagedPtr ct $ \p ->
+  runReaderT (runRender r) (Cairo (castPtr p))
diff --git a/lib/GtkMainSyncAsync.hs b/lib/GtkMainSyncAsync.hs
--- a/lib/GtkMainSyncAsync.hs
+++ b/lib/GtkMainSyncAsync.hs
@@ -33,10 +33,11 @@
 
 {-# LANGUAGE OverloadedStrings #-}
 
-module GtkMainSyncAsync (
-      gtkMainSync
-    , gtkMainAsync
-  ) where
+module GtkMainSyncAsync
+  ( gtkMainSync
+  , gtkMainAsync
+  )
+  where
 
 import Control.Monad
 import Control.Concurrent
@@ -59,4 +60,5 @@
 gtkMainSyncBuildCallBack callback mVar = callback >>= putMVar mVar >> return False
 
 gtkMainAddCallback :: GI.GLib.SourceFunc -> IO ()
-gtkMainAddCallback callback = void $ GI.GLib.idleAdd GI.GLib.PRIORITY_DEFAULT callback
+gtkMainAddCallback callback =
+  void $ GI.GLib.idleAdd GI.GLib.PRIORITY_DEFAULT callback
diff --git a/makefile b/makefile
--- a/makefile
+++ b/makefile
@@ -9,11 +9,11 @@
 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='2.3.0.0'
+VERSION='3.0.0.0'
 
 export PATH := $(PATH):$(STACK_PATH_LOCAL_BIN)
 
-all: setup update sandbox_clean clean alex happy install_dependencies configure build install
+all: setup update sandbox_clean clean alex happy haskell_gi gtk2hs_buildtools install_dependencies configure build install
 
 setup:
   $(STACK) setup && $(STACK) update && $(STACK) install Cabal && $(STACK) install cabal-install
@@ -24,6 +24,12 @@
 happy: setup
   $(STACK) install happy
 
+haskell_gi: setup
+  $(STACK) install haskell-gi
+
+gtk2hs_buildtools: setup
+  $(STACK) install gtk2hs-buildtools
+
 sandbox: setup
   $(CABAL) sandbox init
 
@@ -65,10 +71,11 @@
   --html-location='http://hackage.haskell.org/package/Gifcurry/docs' \
   --contents-location='http://hackage.haskell.org/package/Gifcurry' && \
   cp -R ./dist/doc/html/Gifcurry/ ./docs/Gifcurry-$(VERSION)-docs && \
-  tar --format=ustar -zcvf './docs/Gifcurry-$(VERSION)-docs.tar.gz' './docs/Gifcurry-$(VERSION)-docs'
+  cd ./docs && \
+  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 arch_os_install_dependencies arch_os_configure arch_os_build
+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
diff --git a/src/cli/Main.hs b/src/cli/Main.hs
--- a/src/cli/Main.hs
+++ b/src/cli/Main.hs
@@ -4,114 +4,190 @@
   lettier.com
 -}
 
-{-# LANGUAGE DeriveDataTypeable #-}
-{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE
+    DeriveDataTypeable
+  , NamedFieldPuns
+#-}
+{-# OPTIONS_GHC -fno-cse #-}
 
+import Control.Monad
 import System.Console.CmdArgs
 
 import qualified Gifcurry
 
-data CliArgs = CliArgs {
-      inputFile :: String
-    , outputFile :: String
-    , startTime :: Float
-    , durationTime :: Float
-    , widthSize :: Int
-    , qualityPercent :: Float
-    , fontChoice :: String
-    , topText :: String
-    , bottomText :: String
-  } deriving (Data, Typeable, Show, Eq)
+data CliArgs =
+  CliArgs
+    { input_file :: String
+    , output_file :: String
+    , save_as_video :: Bool
+    , start_time :: Float
+    , duration_time :: Float
+    , width_size :: Int
+    , quality_percent :: Float
+    , font_choice :: String
+    , top_text :: String
+    , bottom_text :: String
+    , left_crop :: Float
+    , right_crop :: Float
+    , top_crop :: Float
+    , bottom_crop :: Float
+    }
+  deriving (Data, Typeable, Show, Eq)
 
+programName :: String
+programName = "gifcurry_cli"
+
 info :: String
-info = "Gifcurry " ++ Gifcurry.versionNumber ++ "\n(C) 2016 David Lettier\nlettier.com"
+info =
+  unlines
+    [ logo
+    , "Gifcurry" ++ " " ++ Gifcurry.versionNumber
+    , "(C) 2016 David Lettier"
+    ,"lettier.com"
+    ]
 
 cliArgs :: CliArgs
-cliArgs = CliArgs {
-      inputFile      = ""        &= typFile &= help "The input video file path and name."
-    , outputFile     = ""        &= typFile &= help "The output GIF file path and name."
-    , startTime      = 0.0                  &= help "The start time (in seconds) for the first frame."
-    , durationTime   = 1.0                  &= help "How long the GIF lasts (in seconds) from the start time."
-    , widthSize      = 500                  &= help "How wide the GIF needs to be. Height will scale to match."
-    , qualityPercent = 100.0                &= help "Ranges from 0.0 to 100.0."
-    , fontChoice     = "default"            &= help "Choose your desired font for the top and bottom text."
-    , topText        = ""                   &= help "The text you wish to add to the top of the GIF."
-    , bottomText     = ""                   &= help "The text you wish to add to the bottom of the GIF."
-  -- VERSION
-  } &= summary info
-    &= program "gifcurry_cli"
+cliArgs =
+  CliArgs
+    { input_file
+        = ""
+        &= groupname "FILE IO"
+        &= typFile
+        &= help "The input video file path and name."
+    , output_file
+        = ""
+        &= groupname "FILE IO"
+        &= typFile
+        &= help "The output GIF file path and name."
+    , save_as_video
+        = False
+        &= groupname "FILE IO"
+        &= name "m"
+        &= help "If present, saves the GIF as a video."
+    , start_time
+        = 0.0
+        &= groupname "TIME"
+        &= name "s"
+        &= help "The start time (in seconds) for the first frame."
+    , duration_time
+        = 1.0
+        &= groupname "TIME"
+        &= help "How long the GIF lasts (in seconds) from the start time."
+    , width_size
+        = 500
+        &= groupname "OUTPUT FILE SIZE"
+        &= help "How wide the GIF needs to be. Height will scale to match."
+    , quality_percent
+        = 100.0
+        &= groupname "OUTPUT FILE SIZE"
+        &= help
+          (  "From 1 (very low quality) to 100 (the best quality). "
+          ++ "Controls how many colors are used and how many frames per second there are."
+          )
+    , font_choice
+        = Gifcurry.defaultFontChoice
+        &= groupname "TEXT"
+        &= typ "TEXT"
+        &= help "Choose your desired font for the top and bottom text."
+    , top_text
+        = ""
+        &= groupname "TEXT"
+        &= typ "TEXT"
+        &= name "t"
+        &= help "The text you wish to add to the top of the GIF."
+    , bottom_text
+        = ""
+        &= groupname "TEXT"
+        &= typ "TEXT"
+        &= name "b"
+        &= help "The text you wish to add to the bottom of the GIF."
+    , left_crop
+        = 0.0
+        &= groupname "CROP"
+        &= name "L"
+        &= help "The amount you wish to crop from the left."
+    , right_crop
+        = 0.0
+        &= groupname "CROP"
+        &= name "R"
+        &= help "The amount you wish to crop from the right."
+    , top_crop
+        = 0.0
+        &= groupname "CROP"
+        &= name "T"
+        &= help "The amount you wish to crop from the top."
+    , bottom_crop
+        = 0.0
+        &= groupname "CROP"
+        &= name "B"
+        &= help "The amount you wish to crop from the bottom."
+    }
+    &= summary info
+    &= program programName
+    &= details ["Visit https://github.com/lettier/gifcurry for more information.", ""]
 
 main :: IO ()
 main = do
   cliArgs' <- cmdArgs cliArgs
   let params = makeGifParams cliArgs'
-  printHeader
+  putStrLn info
   paramsValid <- Gifcurry.gifParamsValid params
   if paramsValid
-    then do
-      _ <- Gifcurry.gif params
-      return ()
-    else printUsage
+    then void $ Gifcurry.gif params
+    else
+      putStrLn $
+        "[INFO] Type \"" ++ programName ++ " -?\" for help."
   return ()
 
 makeGifParams :: CliArgs -> Gifcurry.GifParams
-makeGifParams CliArgs {
-      inputFile
-    , outputFile
-    , startTime
-    , durationTime
-    , widthSize
-    , qualityPercent
-    , fontChoice
-    , topText
-    , bottomText
-  } = Gifcurry.GifParams {
-      Gifcurry.inputFile      = inputFile
-    , Gifcurry.outputFile     = outputFile
-    , Gifcurry.startTime      = startTime
-    , Gifcurry.durationTime   = durationTime
-    , Gifcurry.widthSize      = widthSize
-    , Gifcurry.qualityPercent = qualityPercent
-    , Gifcurry.fontChoice     = fontChoice
-    , Gifcurry.topText        = topText
-    , Gifcurry.bottomText     = bottomText
-  }
-
-printUsage :: IO ()
-printUsage = putStrLn $ unwords [
-        "\n"
-      , "Usage:"
-      , "\n\t"
-      , "$ gifcurry_cli \\"
-      , "\n\t"
-      , "-i inputFile \\"
-      , "\n\t"
-      , "-o outputFile \\"
-      , "\n\t"
-      , "-s startTime \\"
-      , "\n\t"
-      , "-d durationTime \\"
-      , "\n\t"
-      , "-w widthSize \\"
-      , "\n\t"
-      , "-q qualityPercent \\"
-      , "\n\t"
-      , "-f fontChoice \\"
-      , "\n\t"
-      , "-t topText \\"
-      , "\n\t"
-      , "-b bottomText"
-  ]
+makeGifParams
+  CliArgs
+    { input_file
+    , output_file
+    , save_as_video
+    , start_time
+    , duration_time
+    , width_size
+    , quality_percent
+    , font_choice
+    , top_text
+    , bottom_text
+    , left_crop
+    , right_crop
+    , top_crop
+    , bottom_crop
+    }
+  =
+  Gifcurry.GifParams
+    { Gifcurry.inputFile      = input_file
+    , Gifcurry.outputFile     = output_file
+    , Gifcurry.saveAsVideo    = save_as_video
+    , Gifcurry.startTime      = start_time
+    , Gifcurry.durationTime   = duration_time
+    , Gifcurry.widthSize      = width_size
+    , Gifcurry.qualityPercent = quality_percent
+    , Gifcurry.fontChoice     = font_choice
+    , Gifcurry.topText        = top_text
+    , Gifcurry.bottomText     = bottom_text
+    , Gifcurry.leftCrop       = left_crop
+    , Gifcurry.rightCrop      = right_crop
+    , Gifcurry.topCrop        = top_crop
+    , Gifcurry.bottomCrop     = bottom_crop
+    }
 
-printHeader :: IO ()
-printHeader = mapM_ putStrLn [
-      " _____ _  __                           "
-    , "|  __ (_)/ _|                          "
-    , "| |  \\/_| |_ ___ _   _ _ __ _ __ _   _ "
-    , "| | __| |  _/ __| | | | '__| '__| | | |"
-    , "| |_\\ \\ | || (__| |_| | |  | |  | |_| |"
-    , " \\____/_|_| \\___|\\__,_|_|  |_|   \\__, |"
-    , "                                  __/ |"
-    , "                                 |___/ "
-    , info
-  ]
+logo :: String
+logo =
+  unlines
+    [ ""
+    , "       ╓╦NÑ╫╫╫╫╫ÑNw,                                                           "
+    , "    , `Ñ╫╫╫╫Ñ `'╩╫╫╫Ñw      ,╓ææµ  ║▓▓⌐ ,▄▄▓▄                                  "
+    , "   ]╫Ñ  Ñ╫╫╫ ,╫N ╙╫╫╫╫Ñ   ╓▓▓▀╙╙▀╨ .╓, ,▓▓▌,  ,╓╥╓, ,,  ,,, ,, ,╓ ,, ,╓ ,,  ., "
+    , "  ]╫╫╫Ñ '╫╫⌐ Ñ╫⌐  Ñ╫╫╫╫Ñ -▓▓M ╥╥╥╥ ▐▓▓ ▀▓▓▀▀ ▄▓▀╙▀▀ ▓▓Γ ╫▓▌ ╫▓▓▌▀ ▓▓▓▀▀╙▓▓  ▓▓▀"
+    , "  ╩╫╫╫╫H ╠Ñ 1╫H ╫ '╫╫╫╫╣  ▓▓H ╙▐▓▓ ▐▓▓  ▓▓Γ ▐▓▓⌐    ▓▓Γ ╫▓▌ ╫▓▌   ▓▓▌   ║▓▌╢▓▌ "
+    , "  ]╫╫╫╫╫ ' j╫Å j╫H ╠╫╫╫╫  ╙▓▓▄▄▄▓▓ ▐▓▓  ▓▓Γ  ▀▓▌▄▄▄ ▓▓▌▄▓▓▌ ╫▓▌   ▓▓▌    ▓▓▓▓  "
+    , "   Ñ╫╫╫╫N jÑ╬ .╫╫╫⌐ Ñ╫╬┘    └╙╙╙└  `└└  └└`    ╙╙╙`  ╙╙└`└` `└└   └└`    ,▓▓▀  "
+    , "    ╨╫╫╫╫N '┘ ╬╫╫╫╫  ╩`                                                 ▀▀▀└   "
+    , "     `╙Ñ╫╫╫N╦╦╫╫╫╫╫╩                                                           "
+    , "         `'╙╙╙╙'``                                                             "
+    , ""
+    ]
diff --git a/src/data/gifcurry-icon.png b/src/data/gifcurry-icon.png
deleted file mode 100644
Binary files a/src/data/gifcurry-icon.png and /dev/null differ
diff --git a/src/data/gifcurry-icon.svg b/src/data/gifcurry-icon.svg
new file mode 100644
--- /dev/null
+++ b/src/data/gifcurry-icon.svg
@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   id="svg8"
+   version="1.1"
+   viewBox="0 0 57.000001 57"
+   height="57mm"
+   width="57mm">
+  <defs
+     id="defs2" />
+  <metadata
+     id="metadata5">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     id="g4638"
+     style="display:inline"
+     transform="translate(-2.857145,-2.6012127)">
+    <path
+       id="path6070"
+       d="M 30.00902,4.9971347 A 25.513393,25.513393 0 0 0 14.616162,10.591115 c 0.133368,0.18103 0.263304,0.36156 0.393258,0.54208 A 25.513393,25.513393 0 0 1 30.693217,5.0178047 25.513393,25.513393 0 0 0 30.00902,4.9971047 Z M 11.255127,13.816235 a 25.513393,25.513393 0 0 0 -6.250264,17.18603 25.513393,25.513393 0 0 0 25.997379,25.01191 25.513393,25.513393 0 0 0 0.382921,-0.0207 25.513393,25.513393 0 0 1 -25.321967,-24.99124 25.513393,25.513393 0 0 1 5.66632,-16.52147 c -0.155829,-0.22395 -0.313022,-0.44708 -0.474389,-0.66456 z"
+       style="display:inline;opacity:1;fill:#ff29ac;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" />
+    <path
+       id="path6068"
+       d="m 32.584056,4.9971347 a 25.513393,25.513393 0 0 0 -0.433565,0.0238 25.513393,25.513393 0 0 1 25.219134,24.9726403 l 5.3e-4,0.031 a 25.513393,25.513393 0 0 1 -5.50561,16.28325 c 0.1834,0.26966 0.3692,0.53514 0.56018,0.79478 a 25.513393,25.513393 0 0 0 6.17275,-17.07803 l -5.3e-4,-0.031 A 25.513393,25.513393 0 0 0 32.584056,4.9971347 Z M 48.613575,49.702415 a 25.513393,25.513393 0 0 1 -15.819712,6.28747 25.513393,25.513393 0 0 0 0.783415,0.0243 25.513393,25.513393 0 0 0 15.502927,-5.66839 c -0.1577,-0.21469 -0.31368,-0.42907 -0.46663,-0.64337 z"
+       style="display:inline;opacity:1;fill:#29ffa4;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" />
+    <path
+       style="display:inline;opacity:1;fill:#1b59a2;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
+       d="M 31.59652,4.9971347 A 25.513393,25.513393 0 0 0 15.533936,11.126475 c 3.454616,4.79733 5.736982,9.58836 8.496639,23.93859 l 7.284826,-24.57937 0.451135,0.11162 0.02223,-0.11162 c 7.861179,1.46742 10.534349,4.71687 12.525849,13.20694 3.15251,13.38741 5.09705,18.65842 7.79436,22.62188 a 25.513393,25.513393 0 0 0 5.50095,-16.28997 l -5.3e-4,-0.031 A 25.513393,25.513393 0 0 0 31.59652,4.9971347 Z M 12.254549,14.474595 a 25.513393,25.513393 0 0 0 -5.662186,16.52767 25.513393,25.513393 0 0 0 25.997379,25.01191 25.513393,25.513393 0 0 0 16.268773,-6.304 c -3.38483,-4.74042 -5.64381,-9.58763 -8.370031,-23.76393 l -7.28431,24.57938 -0.451652,-0.11162 -0.02222,0.11162 c -7.861171,-1.46742 -10.534351,-4.71687 -12.52585,-13.20695 -3.212296,-13.64125 -5.171564,-18.85363 -7.949903,-22.84408 z m 22.52679,0.85214 -7.606255,23.40684 H 25.02638 c 0.8393,3.5098 2.013317,5.65789 4.711855,6.95152 l 7.605739,-23.40736 h 2.149221 c -0.839229,-3.50953 -2.013754,-5.65733 -4.711856,-6.951 z"
+       id="path4679" />
+    <path
+       id="path4681"
+       d="M 31.350966,4.9971347 A 25.513393,25.513393 0 0 0 15.288382,11.126475 c 3.454616,4.79733 5.736982,9.58836 8.496639,23.93859 l 7.284826,-24.57937 0.451135,0.11162 0.02223,-0.11162 c 7.861179,1.46742 10.534343,4.71687 12.525843,13.20694 3.15251,13.38741 5.09705,18.65842 7.79436,22.62188 a 25.513393,25.513393 0 0 0 5.50095,-16.28997 l -5.3e-4,-0.031 A 25.513393,25.513393 0 0 0 31.350966,4.9971347 Z M 12.008995,14.474595 a 25.513393,25.513393 0 0 0 -5.662186,16.52767 25.513393,25.513393 0 0 0 25.997379,25.01191 25.513393,25.513393 0 0 0 16.268767,-6.304 c -3.38483,-4.74042 -5.64381,-9.58763 -8.370025,-23.76393 l -7.28431,24.57938 -0.451652,-0.11162 -0.02222,0.11162 c -7.861171,-1.46742 -10.534351,-4.71687 -12.52585,-13.20695 -3.212296,-13.64125 -5.171564,-18.85363 -7.949903,-22.84408 z m 22.52679,0.85214 -7.606255,23.40684 h -2.148704 c 0.8393,3.5098 2.013317,5.65789 4.711855,6.95152 l 7.605739,-23.40736 h 2.149221 c -0.839229,-3.50953 -2.013754,-5.65733 -4.711856,-6.951 z"
+       style="display:inline;opacity:1;fill:#1b59a2;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" />
+    <path
+       id="path4648"
+       d="M 31.067353,4.9971347 A 25.513393,25.513393 0 0 0 15.004769,11.126475 c 3.454616,4.79733 5.736982,9.58836 8.496639,23.93859 l 7.284826,-24.57937 0.451135,0.11162 0.02223,-0.11162 c 7.861179,1.46742 10.534346,4.71687 12.525846,13.20694 3.15251,13.38741 5.09705,18.65842 7.79436,22.62188 a 25.513393,25.513393 0 0 0 5.50095,-16.28997 l -5.3e-4,-0.031 A 25.513393,25.513393 0 0 0 31.067353,4.9971347 Z M 11.725382,14.474595 a 25.513393,25.513393 0 0 0 -5.662186,16.52767 25.513393,25.513393 0 0 0 25.997379,25.01191 25.513393,25.513393 0 0 0 16.26877,-6.304 c -3.38483,-4.74042 -5.64381,-9.58763 -8.370028,-23.76393 l -7.28431,24.57938 -0.451652,-0.11162 -0.02222,0.11162 c -7.861171,-1.46742 -10.534351,-4.71687 -12.52585,-13.20695 -3.212296,-13.64125 -5.171564,-18.85363 -7.949903,-22.84408 z m 22.52679,0.85214 -7.606255,23.40684 h -2.148704 c 0.8393,3.5098 2.013317,5.65789 4.711855,6.95152 l 7.605739,-23.40736 h 2.149221 c -0.839229,-3.50953 -2.013754,-5.65733 -4.711856,-6.951 z"
+       style="display:inline;opacity:1;fill:#5294e2;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" />
+  </g>
+  <g
+     id="g4631"
+     style="display:inline"
+     transform="translate(-3.1131187,-1.2102795)">
+    <text
+       id="text6160"
+       y="38.733902"
+       x="61.539043"
+       style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:35.27777863px;line-height:125%;font-family:'Fira Sans';-inkscape-font-specification:'Fira Sans Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;display:inline;fill:#2e323c;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       xml:space="preserve"><tspan
+         style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:35.27777863px;font-family:'Fira Sans';-inkscape-font-specification:'Fira Sans Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#2e323c;fill-opacity:1;stroke-width:0.26458332px"
+         y="38.733902"
+         x="61.539043"
+         id="tspan6158">Gifcurr</tspan></text>
+    <text
+       transform="scale(1,-1)"
+       id="text6166"
+       y="-20.001196"
+       x="172.94928"
+       style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:35.27777863px;line-height:125%;font-family:'Fira Sans';-inkscape-font-specification:'Fira Sans Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;display:inline;fill:#2e323c;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       xml:space="preserve"><tspan
+         style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:35.27777863px;font-family:'Fira Sans';-inkscape-font-specification:'Fira Sans Bold';fill:#2e323c;fill-opacity:1;stroke-width:0.26458332px"
+         y="-20.001196"
+         x="172.94928"
+         id="tspan6164">λ</tspan></text>
+    <text
+       xml:space="preserve"
+       style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:35.27777863px;line-height:125%;font-family:'Fira Sans';-inkscape-font-specification:'Fira Sans Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;display:inline;fill:#2e323c;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       x="61.375141"
+       y="38.733902"
+       id="text6170"><tspan
+         id="tspan6168"
+         x="61.375141"
+         y="38.733902"
+         style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:35.27777863px;font-family:'Fira Sans';-inkscape-font-specification:'Fira Sans Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#2e323c;fill-opacity:1;stroke-width:0.26458332px">Gifcurr</tspan></text>
+    <text
+       xml:space="preserve"
+       style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:35.27777863px;line-height:125%;font-family:'Fira Sans';-inkscape-font-specification:'Fira Sans Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;display:inline;fill:#2e323c;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       x="173.11317"
+       y="-20.001196"
+       id="text6156"
+       transform="scale(1,-1)"><tspan
+         id="tspan6154"
+         x="173.11317"
+         y="-20.001196"
+         style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:35.27777863px;font-family:'Fira Sans';-inkscape-font-specification:'Fira Sans Bold';fill:#2e323c;fill-opacity:1;stroke-width:0.26458332px">λ</tspan></text>
+    <text
+       transform="scale(1,-1)"
+       id="text6125"
+       y="-20.001196"
+       x="172.80458"
+       style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:35.27777863px;line-height:125%;font-family:'Fira Sans';-inkscape-font-specification:'Fira Sans Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;display:inline;fill:#444a58;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       xml:space="preserve"><tspan
+         style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:35.27777863px;font-family:'Fira Sans';-inkscape-font-specification:'Fira Sans Bold';fill:#444a58;fill-opacity:1;stroke-width:0.26458332px"
+         y="-20.001196"
+         x="172.80458"
+         id="tspan6123">λ</tspan></text>
+    <text
+       xml:space="preserve"
+       style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:35.27777863px;line-height:125%;font-family:'Fira Sans';-inkscape-font-specification:'Fira Sans Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;display:inline;fill:#444a58;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       x="61.230457"
+       y="38.733902"
+       id="text6129"><tspan
+         id="tspan6127"
+         x="61.230457"
+         y="38.733902"
+         style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:35.27777863px;font-family:'Fira Sans';-inkscape-font-specification:'Fira Sans Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#444a58;fill-opacity:1;stroke-width:0.26458332px">Gifcurr</tspan></text>
+  </g>
+  <g
+     id="g4644"
+     transform="translate(-2.857145,-2.6012127)">
+    <path
+       id="path4614"
+       d="M 23.810702,55.129827 C 17.778485,53.503104 9.0148866,48.566654 5.6981798,36.315722"
+       style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1107.35998535;stroke-opacity:0" />
+    <text
+       transform="translate(-0.40960069,0.2054888)"
+       id="text4587"
+       style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82222223px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#e8e8e8;fill-opacity:1;stroke:none;stroke-width:0.26458335px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       xml:space="preserve"><textPath
+         id="textPath4616"
+         xlink:href="#path4614"><tspan
+   id="tspan4585"
+   style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82222223px;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;fill:#e8e8e8;fill-opacity:1;stroke-width:0.26458335px"
+   dy="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0">© 2018 David Lettier</tspan></textPath></text>
+  </g>
+</svg>
diff --git a/src/data/gifcurry-logo.svg b/src/data/gifcurry-logo.svg
--- a/src/data/gifcurry-logo.svg
+++ b/src/data/gifcurry-logo.svg
@@ -5,13 +5,15 @@
    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
    xmlns:svg="http://www.w3.org/2000/svg"
    xmlns="http://www.w3.org/2000/svg"
-   viewBox="0 0 384 384"
-   height="384"
-   width="384"
+   width="190.297mm"
+   height="54.21814mm"
+   viewBox="0 0 190.297 54.21814"
    version="1.1"
-   id="svg4247">
+   id="svg8">
+  <defs
+     id="defs2" />
   <metadata
-     id="metadata4253">
+     id="metadata5">
     <rdf:RDF>
       <cc:Work
          rdf:about="">
@@ -22,122 +24,192 @@
       </cc:Work>
     </rdf:RDF>
   </metadata>
-  <defs
-     id="defs4251" />
   <path
-     style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
-     d="m 125.90653,283.63875 39.795,55.55914"
-     id="path4242" />
+     id="path6070"
+     d="M 26.895901,1.0049911 A 25.513393,25.513393 0 0 0 11.503043,6.5989714 c 0.133368,0.18103 0.263304,0.36156 0.393258,0.54208 a 25.513393,25.513393 0 0 1 15.683797,-6.1153903 25.513393,25.513393 0 0 0 -0.684197,-0.0207 z M 8.1420083,9.8240914 a 25.513393,25.513393 0 0 0 -6.250264,17.1860296 25.513393,25.513393 0 0 0 25.9973787,25.01191 25.513393,25.513393 0 0 0 0.382921,-0.0207 25.513393,25.513393 0 0 1 -25.3219667,-24.99124 25.513393,25.513393 0 0 1 5.66632,-16.52147 c -0.155829,-0.22395 -0.313022,-0.44708 -0.474389,-0.6645596 z"
+     style="display:inline;opacity:1;fill:#ff29ac;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" />
   <path
-     id="path4244"
-     d="m 165.39543,339.58456 58.9867,-88.19368"
-     style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.02706873px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+     id="path6068"
+     d="m 29.470937,1.0049911 a 25.513393,25.513393 0 0 0 -0.433565,0.0238 25.513393,25.513393 0 0 1 25.219134,24.9726399 l 5.3e-4,0.031 a 25.513393,25.513393 0 0 1 -5.50561,16.28325 c 0.1834,0.26966 0.3692,0.53514 0.56018,0.79478 a 25.513393,25.513393 0 0 0 6.17275,-17.07803 l -5.3e-4,-0.031 A 25.513393,25.513393 0 0 0 29.470937,1.0049911 Z M 45.500456,45.710271 a 25.513393,25.513393 0 0 1 -15.819712,6.28747 25.513393,25.513393 0 0 0 0.783415,0.0243 25.513393,25.513393 0 0 0 15.502927,-5.66839 c -0.1577,-0.21469 -0.31368,-0.42907 -0.46663,-0.64337 z"
+     style="display:inline;opacity:1;fill:#29ffa4;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" />
   <path
-     id="path4246"
-     d="M 106.73243,76.488293 224.38453,252.68766"
-     style="fill:none;fill-rule:evenodd;stroke:#ea2a89;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+     style="display:inline;opacity:1;fill:#1b59a2;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke"
+     d="M 28.483401,1.0049911 A 25.513393,25.513393 0 0 0 12.420817,7.1343314 c 3.454616,4.7973296 5.736982,9.5883596 8.496639,23.9385896 l 7.284826,-24.5793696 0.451135,0.11162 0.02223,-0.11162 c 7.861179,1.46742 10.534349,4.7168696 12.525849,13.2069396 3.15251,13.38741 5.09705,18.65842 7.79436,22.62188 a 25.513393,25.513393 0 0 0 5.50095,-16.28997 l -5.3e-4,-0.031 A 25.513393,25.513393 0 0 0 28.483401,1.0049911 Z M 9.1414303,10.482451 a 25.513393,25.513393 0 0 0 -5.662186,16.52767 25.513393,25.513393 0 0 0 25.9973787,25.01191 25.513393,25.513393 0 0 0 16.268773,-6.304 c -3.38483,-4.74042 -5.64381,-9.58763 -8.370031,-23.76393 l -7.28431,24.57938 -0.451652,-0.11162 -0.02222,0.11162 c -7.861171,-1.46742 -10.534351,-4.71687 -12.52585,-13.20695 -3.212296,-13.64125 -5.171564,-18.85363 -7.9499027,-22.84408 z m 22.5267897,0.85214 -7.606255,23.40684 h -2.148704 c 0.8393,3.5098 2.013317,5.65789 4.711855,6.95152 l 7.605739,-23.40736 h 2.149221 c -0.839229,-3.50953 -2.013754,-5.65733 -4.711856,-6.951 z"
+     id="path4679" />
   <path
-     id="path4248"
-     d="m 105.45393,253.63953 57.4321,-85.86931"
-     style="fill:none;fill-rule:evenodd;stroke:#ea2a89;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+     id="path4681"
+     d="M 28.237847,1.0049911 A 25.513393,25.513393 0 0 0 12.175263,7.1343314 c 3.454616,4.7973296 5.736982,9.5883596 8.496639,23.9385896 l 7.284826,-24.5793696 0.451135,0.11162 0.02223,-0.11162 c 7.861179,1.46742 10.534343,4.7168696 12.525843,13.2069396 3.15251,13.38741 5.09705,18.65842 7.79436,22.62188 a 25.513393,25.513393 0 0 0 5.50095,-16.28997 l -5.3e-4,-0.031 A 25.513393,25.513393 0 0 0 28.237847,1.0049911 Z M 8.8958763,10.482451 a 25.513393,25.513393 0 0 0 -5.662186,16.52767 25.513393,25.513393 0 0 0 25.9973787,25.01191 25.513393,25.513393 0 0 0 16.268767,-6.304 c -3.38483,-4.74042 -5.64381,-9.58763 -8.370025,-23.76393 l -7.28431,24.57938 -0.451652,-0.11162 -0.02222,0.11162 c -7.861171,-1.46742 -10.534351,-4.71687 -12.52585,-13.20695 -3.212296,-13.64125 -5.171564,-18.85363 -7.9499027,-22.84408 z m 22.5267897,0.85214 -7.606255,23.40684 h -2.148704 c 0.8393,3.5098 2.013317,5.65789 4.711855,6.95152 l 7.605739,-23.40736 h 2.149221 c -0.839229,-3.50953 -2.013754,-5.65733 -4.711856,-6.951 z"
+     style="display:inline;opacity:1;fill:#1b59a2;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" />
   <path
-     id="path4250"
-     d="m 105.62833,253.21481 -80.554303,-118.985 61.3566,-89.60207 12.02058,19.213246"
-     style="fill:none;fill-rule:evenodd;stroke:#3f3f3f;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+     id="path4648"
+     d="M 27.954234,1.0049911 A 25.513393,25.513393 0 0 0 11.89165,7.1343314 c 3.454616,4.7973296 5.736982,9.5883596 8.496639,23.9385896 l 7.284826,-24.5793696 0.451135,0.11162 0.02223,-0.11162 c 7.861179,1.46742 10.534346,4.7168696 12.525846,13.2069396 3.15251,13.38741 5.09705,18.65842 7.79436,22.62188 a 25.513393,25.513393 0 0 0 5.50095,-16.28997 l -5.3e-4,-0.031 A 25.513393,25.513393 0 0 0 27.954234,1.0049911 Z M 8.6122633,10.482451 a 25.513393,25.513393 0 0 0 -5.662186,16.52767 25.513393,25.513393 0 0 0 25.9973787,25.01191 25.513393,25.513393 0 0 0 16.26877,-6.304 c -3.38483,-4.74042 -5.64381,-9.58763 -8.370028,-23.76393 l -7.28431,24.57938 -0.451652,-0.11162 -0.02222,0.11162 C 21.226845,45.066061 18.553665,41.816611 16.562166,33.326531 13.34987,19.685281 11.390602,14.472901 8.6122633,10.482451 Z m 22.5267897,0.85214 -7.606255,23.40684 h -2.148704 c 0.8393,3.5098 2.013317,5.65789 4.711855,6.95152 l 7.605739,-23.40736 h 2.149221 c -0.839229,-3.50953 -2.013754,-5.65733 -4.711856,-6.951 z"
+     style="display:inline;opacity:1;fill:#5294e2;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:10;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" />
   <path
-     id="path4252"
-     d="m 125.90653,283.63875 39.64195,55.75247 58.83365,-88.00034"
-     style="fill:none;fill-rule:evenodd;stroke:#3f3f3f;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+     d="m 71.302315,9.6945374 c -6.561666,0 -11.712222,4.5155556 -11.712222,12.8058336 0,8.360834 3.739444,12.841111 11.218334,12.841111 3.069166,0 6.244166,-0.9525 8.784166,-2.575277 V 20.665927 h -9.348611 l 0.564445,4.092222 h 3.175 v 5.538611 c -1.023056,0.564445 -2.116667,0.811389 -3.316112,0.811389 -3.386666,0 -5.08,-2.187222 -5.08,-8.607778 0,-6.067778 2.54,-8.607778 5.961945,-8.607778 1.975555,0 3.316111,0.599722 4.974167,1.869722 l 2.928055,-3.069166 C 77.440649,10.92926 74.865371,9.6945374 71.302315,9.6945374 Z"
+     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:35.27777863px;line-height:125%;font-family:'Fira Sans';-inkscape-font-specification:'Fira Sans Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#2e323c;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     id="path5020" />
   <path
-     id="path4266"
-     d="M 130.78343,76.488293 248.43553,252.68766"
-     style="fill:none;fill-rule:evenodd;stroke:#5cb86b;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+     d="m 86.102446,6.1314818 c -1.940278,0 -3.280833,1.3758334 -3.280833,3.1750001 0,1.7991671 1.340555,3.1750001 3.280833,3.1750001 1.940278,0 3.316111,-1.375833 3.316111,-3.1750001 0,-1.7991667 -1.375833,-3.1750001 -3.316111,-3.1750001 z m 2.822222,9.8777782 h -5.573889 v 18.7325 h 5.573889 z"
+     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:35.27777863px;line-height:125%;font-family:'Fira Sans';-inkscape-font-specification:'Fira Sans Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#2e323c;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     id="path5022" />
   <path
-     id="path4268"
-     d="m 129.50493,253.63953 56.9392,-85.08075"
-     style="fill:none;fill-rule:evenodd;stroke:#5cb86b;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+     d="m 101.84516,12.093426 c 0.81139,0 1.86972,0.176389 2.92805,0.705556 l 1.51695,-3.6336112 c -1.37584,-0.7055556 -3.175,-1.1994445 -5.11528,-1.1994445 -4.797779,0 -7.16139,2.7869447 -7.16139,6.3852777 v 1.658056 h -2.822223 v 3.880555 H 94.01349 V 34.74176 h 5.573889 V 19.889815 h 3.810001 l 0.59972,-3.880555 h -4.409721 v -1.446389 c 0,-1.763889 0.635001,-2.469445 2.257781,-2.469445 z"
+     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:35.27777863px;line-height:125%;font-family:'Fira Sans';-inkscape-font-specification:'Fira Sans Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#2e323c;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     id="path5024" />
   <path
-     id="path4270"
-     d="m 129.67923,253.21481 -80.554203,-118.985 61.356503,-89.60207 12.4395,19.804663"
-     style="fill:none;fill-rule:evenodd;stroke:#3f3f3f;stroke-width:4;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+     d="m 113.58218,15.409538 c -5.60916,0 -9.17222,4.162777 -9.17222,10.124722 0,5.926667 3.52778,9.807222 9.27806,9.807222 2.57527,0 4.58611,-0.846666 6.31472,-2.2225 l -2.43417,-3.457222 c -1.34055,0.846667 -2.25778,1.27 -3.59833,1.27 -2.2225,0 -3.70417,-1.27 -3.70417,-5.432778 0,-4.198055 1.37583,-5.820833 3.77472,-5.820833 1.27,0 2.36361,0.423333 3.56306,1.340555 l 2.39889,-3.316111 c -1.79917,-1.516944 -3.81,-2.293055 -6.42056,-2.293055 z"
+     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:35.27777863px;line-height:125%;font-family:'Fira Sans';-inkscape-font-specification:'Fira Sans Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#2e323c;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     id="path5026" />
   <path
-     id="path4272"
-     d="m 149.95743,283.63875 39.64195,55.75247 58.83365,-88.00034"
-     style="fill:none;fill-rule:evenodd;stroke:#3f3f3f;stroke-width:4;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+     d="m 137.80919,16.00926 h -5.57388 v 13.0175 c -0.81139,1.411111 -1.86973,2.187222 -3.03389,2.187222 -1.16417,0 -1.79917,-0.564444 -1.79917,-2.469444 V 16.00926 h -5.57389 v 13.440833 c 0,3.633612 1.72861,5.891389 5.22111,5.891389 2.46945,0 4.30389,-0.987777 5.67972,-2.963333 l 0.24695,2.363611 h 4.83305 z"
+     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:35.27777863px;line-height:125%;font-family:'Fira Sans';-inkscape-font-specification:'Fira Sans Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#2e323c;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     id="path5028" />
   <path
-     style="fill:none;fill-rule:evenodd;stroke:#3f9ce6;stroke-width:6;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
-     d="M 158.38283,76.488293 276.03493,252.68766"
-     id="path4286" />
+     d="m 152.35301,15.480093 c -2.15194,0 -3.98639,1.552222 -4.83305,4.162778 l -0.45861,-3.633611 h -4.86834 v 18.7325 h 5.57389 v -9.278056 c 0.59972,-2.8575 1.55222,-4.586111 3.88056,-4.586111 0.635,0 1.09361,0.105834 1.69333,0.246945 l 0.88195,-5.3975 c -0.59973,-0.176389 -1.16417,-0.246945 -1.86973,-0.246945 z"
+     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:35.27777863px;line-height:125%;font-family:'Fira Sans';-inkscape-font-specification:'Fira Sans Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#2e323c;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     id="path5030" />
   <path
-     style="fill:none;fill-rule:evenodd;stroke:#3f9ce6;stroke-width:6;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
-     d="m 157.10433,253.63953 56.2492,-84.19363"
-     id="path4288" />
+     d="m 166.47791,15.480093 c -2.15195,0 -3.98639,1.552222 -4.83306,4.162778 l -0.45861,-3.633611 h -4.86833 v 18.7325 h 5.57389 v -9.278056 c 0.59972,-2.8575 1.55222,-4.586111 3.88055,-4.586111 0.635,0 1.09361,0.105834 1.69333,0.246945 l 0.88195,-5.3975 c -0.59972,-0.176389 -1.16417,-0.246945 -1.86972,-0.246945 z"
+     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:35.27777863px;line-height:125%;font-family:'Fira Sans';-inkscape-font-specification:'Fira Sans Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#2e323c;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     id="path5032" />
   <path
-     style="fill:none;fill-rule:evenodd;stroke:#3f3f3f;stroke-width:6;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
-     d="m 157.27873,253.21481 -80.554303,-118.985 61.356603,-89.60207 12.4395,19.804663"
-     id="path4290" />
+     d="m 172.69367,42.749608 -0.67028,-3.81 c 3.59833,-0.564444 4.72722,-1.516944 5.75028,-4.198055 h -1.97556 l -5.67972,-18.062223 5.50333,-1.27 3.42195,15.063612 c 1.34055,-8.11389 2.46944,-11.430001 4.72722,-15.063612 l 5.36222,1.763889 c -2.50472,3.386667 -3.66889,6.914445 -5.89139,16.333611 -1.51694,6.561667 -4.48028,8.360834 -10.54805,9.242778 z"
+     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:35.27777863px;line-height:125%;font-family:'Fira Sans';-inkscape-font-specification:'Fira Sans Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;display:inline;fill:#2e323c;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     id="path4938" />
   <path
-     style="fill:none;fill-rule:evenodd;stroke:#3f3f3f;stroke-width:6;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
-     d="m 177.55693,283.63875 39.6419,55.75247 58.8337,-88.00034"
-     id="path4292" />
-  <g
-     id="text4306"
-     style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:0%;font-family:'Roboto Condensed';-inkscape-font-specification:'Roboto Condensed, ';letter-spacing:0px;word-spacing:0px;opacity:1;fill:#373737;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:6;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
-     transform="rotate(0.19415858)"
-     aria-label="Gifcurry">
-    <path
-       id="path4507"
-       style="font-size:40px;line-height:1.25"
-       d="m 257.95808,163.97894 q -0.35156,0.46875 -0.95703,1.15235 -0.58593,0.66406 -1.58203,1.28906 -0.99609,0.60547 -2.46094,1.05469 -1.44531,0.42968 -3.51562,0.42968 -2.20703,0 -4.04297,-0.74218 -1.81641,-0.74219 -3.125,-2.22657 -1.30859,-1.48437 -2.03125,-3.73046 -0.72266,-2.2461 -0.72266,-5.23438 v -5.35156 q 0,-11.9336 9.55079,-11.9336 2.07031,0 3.67187,0.60547 1.62109,0.58594 2.73438,1.71875 1.11328,1.11328 1.73828,2.73438 0.625,1.62109 0.74218,3.67187 h -3.57421 q -0.21485,-2.65625 -1.48438,-4.14062 -1.25,-1.50391 -3.80859,-1.50391 -1.64063,0 -2.77344,0.54688 -1.13281,0.54687 -1.85547,1.66015 -0.70312,1.09375 -1.03516,2.75391 -0.3125,1.64062 -0.3125,3.84766 v 5.39062 q 0,2.26563 0.44922,3.94531 0.44922,1.67969 1.26953,2.77344 0.83985,1.09375 2.01172,1.64063 1.17188,0.52734 2.61719,0.52734 1.23047,0 2.07031,-0.21484 0.83985,-0.21485 1.38672,-0.52735 0.56641,-0.3125 0.89844,-0.625 0.33203,-0.3125 0.56641,-0.52734 v -6.5625 h -5.19532 v -3.04688 h 8.76953 z" />
-    <path
-       id="path4509"
-       style="font-size:40px;line-height:1.25"
-       d="m 267.07918,167.5141 h -3.45703 v -21.13281 h 3.45703 z m -3.65235,-26.73828 q 0,-0.87891 0.48829,-1.48438 0.48828,-0.60547 1.46484,-0.60547 0.97656,0 1.46484,0.625 0.50782,0.60547 0.50782,1.46485 0,0.8789 -0.48829,1.46484 -0.48828,0.58594 -1.48437,0.58594 -0.97656,0 -1.46484,-0.58594 -0.48829,-0.58594 -0.48829,-1.46484 z" />
-    <path
-       id="path4511"
-       style="font-size:40px;line-height:1.25"
-       d="m 274.01277,167.5141 v -18.33984 h -2.7539 v -2.79297 h 2.7539 v -2.22657 q 0,-1.69921 0.41016,-3.00781 0.42969,-1.32812 1.21094,-2.22656 0.78125,-0.89844 1.91406,-1.34766 1.13281,-0.46875 2.53906,-0.46875 1.03516,0 2.08984,0.33203 l -0.17578,2.92969 q -0.33203,-0.0781 -0.76172,-0.11719 -0.42968,-0.0586 -0.82031,-0.0586 -1.40625,0 -2.1875,1.03516 -0.76172,1.01562 -0.76172,2.92968 v 2.22657 h 3.61328 v 2.79297 h -3.61328 v 18.33984 z" />
-    <path
-       id="path4513"
-       style="font-size:40px;line-height:1.25"
-       d="m 292.02058,164.95551 q 0.76172,0 1.44532,-0.23438 0.68359,-0.23437 1.21093,-0.72266 0.52735,-0.50781 0.83985,-1.26953 0.33203,-0.78125 0.39062,-1.875 h 3.26172 q -0.0586,1.58203 -0.66406,2.89063 -0.58594,1.28906 -1.5625,2.22656 -0.95703,0.91797 -2.22656,1.42578 -1.26953,0.50781 -2.69532,0.50781 -2.05078,0 -3.57421,-0.66406 -1.50391,-0.66406 -2.5,-1.91406 -0.9961,-1.25 -1.48438,-3.06641 -0.48828,-1.8164 -0.48828,-4.12109 v -2.38281 q 0,-2.30469 0.48828,-4.10157 0.48828,-1.8164 1.48438,-3.0664 0.99609,-1.26953 2.5,-1.9336 1.5039,-0.66406 3.55468,-0.66406 1.60157,0 2.89063,0.52735 1.30859,0.50781 2.22656,1.5039 0.9375,0.97656 1.46484,2.40235 0.52735,1.42578 0.58594,3.22265 h -3.26172 q -0.11718,-2.34375 -1.13281,-3.51562 -0.99609,-1.17188 -2.77344,-1.17188 -1.36718,0 -2.24609,0.48828 -0.87891,0.48828 -1.40625,1.38672 -0.50781,0.87891 -0.72266,2.12891 -0.19531,1.25 -0.19531,2.79297 v 2.38281 q 0,1.5625 0.19531,2.83203 0.19532,1.25 0.70313,2.14844 0.52734,0.8789 1.40625,1.36719 0.89844,0.46875 2.28515,0.46875 z" />
-    <path
-       id="path4515"
-       style="font-size:40px;line-height:1.25"
-       d="m 313.97371,165.65863 q -0.82031,1.07422 -2.07031,1.66016 -1.23047,0.58593 -2.94922,0.58593 -1.3086,0 -2.38281,-0.42968 -1.07422,-0.44922 -1.83594,-1.38672 -0.76172,-0.95703 -1.19141,-2.44141 -0.41015,-1.5039 -0.41015,-3.61328 v -13.65234 h 3.4375 v 13.6914 q 0,1.42578 0.2539,2.36328 0.27344,0.91797 0.6836,1.46485 0.42968,0.52734 0.95703,0.74219 0.54687,0.19531 1.07422,0.19531 1.71875,0 2.7539,-0.76172 1.03516,-0.76172 1.58203,-2.05078 v -15.64453 h 3.45703 v 21.13281 h -3.28125 z" />
-    <path
-       id="path4517"
-       style="font-size:40px;line-height:1.25"
-       d="m 331.29793,149.62347 q -0.41016,-0.0781 -0.76172,-0.0976 -0.35156,-0.0391 -0.80078,-0.0391 -1.40625,0 -2.32422,0.78125 -0.89844,0.78125 -1.38672,2.1289 v 15.11719 h -3.45703 v -21.13281 h 3.35937 l 0.0586,2.14843 q 0.64453,-1.17187 1.60156,-1.85546 0.97656,-0.6836 2.32422,-0.6836 0.15625,0 0.35156,0.0391 0.21485,0.0195 0.41016,0.0586 0.19531,0.0391 0.35156,0.0976 0.17578,0.0391 0.25391,0.0781 z" />
-    <path
-       id="path4519"
-       style="font-size:40px;line-height:1.25"
-       d="m 343.48543,149.62347 q -0.41016,-0.0781 -0.76172,-0.0976 -0.35156,-0.0391 -0.80078,-0.0391 -1.40625,0 -2.32422,0.78125 -0.89844,0.78125 -1.38672,2.1289 v 15.11719 h -3.45703 v -21.13281 h 3.35937 l 0.0586,2.14843 q 0.64453,-1.17187 1.60156,-1.85546 0.97656,-0.6836 2.32422,-0.6836 0.15625,0 0.35156,0.0391 0.21485,0.0195 0.41016,0.0586 0.19531,0.0391 0.35156,0.0976 0.17578,0.0391 0.25391,0.0781 z" />
-    <path
-       id="path4521"
-       style="font-size:40px;line-height:1.25"
-       d="m 353.11433,161.77191 3.84766,-15.39062 h 3.67188 l -6.99219,24.39453 q -0.23438,0.78125 -0.66406,1.69922 -0.41016,0.91797 -1.05469,1.69922 -0.64453,0.80078 -1.5625,1.32812 -0.89844,0.54688 -2.08985,0.54688 -0.19531,0 -0.44921,-0.0391 -0.25391,-0.0391 -0.50782,-0.0976 -0.2539,-0.0391 -0.48828,-0.0977 -0.21484,-0.0391 -0.35156,-0.0781 v -2.92969 q 0.11719,0.0391 0.37109,0.0586 0.27344,0.0195 0.39063,0.0195 0.76172,0 1.34765,-0.17578 0.58594,-0.17578 1.03516,-0.58594 0.46875,-0.39062 0.80078,-1.05468 0.35156,-0.66407 0.625,-1.64063 l 0.60547,-2.08984 -6.21094,-20.95703 h 3.76953 z" />
-  </g>
-  <text
-     xml:space="preserve"
-     style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:0%;font-family:'Roboto Condensed';-inkscape-font-specification:'Roboto Condensed, ';letter-spacing:0px;word-spacing:0px;opacity:1;fill:#373737;fill-opacity:0.284153;fill-rule:nonzero;stroke:none;stroke-width:6;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
-     x="-160.13664"
-     y="380.55551"
-     id="text4502"
-     transform="rotate(-56.324282)"><tspan
-       style="font-size:7.5px;line-height:1.25"
-       id="tspan4504"
-       x="-160.13664"
-       y="380.55551">(C) DAVID LETTIER</tspan></text>
-  <text
-     xml:space="preserve"
-     style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:0%;font-family:'Roboto Condensed';-inkscape-font-specification:'Roboto Condensed, ';letter-spacing:0px;word-spacing:0px;opacity:1;fill:#373737;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:6;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
-     x="236.35316"
-     y="-52.491615"
-     id="text4505"
-     transform="rotate(0.19415858)"><tspan
-       id="tspan4503"
-       x="236.35316"
-       y="-52.491615"
-       style="font-size:40px;line-height:1.25">Gifcurry</tspan></text>
+     d="m 71.138413,9.6945374 c -6.561667,0 -11.712222,4.5155556 -11.712222,12.8058336 0,8.360834 3.739444,12.841111 11.218333,12.841111 3.069167,0 6.244167,-0.9525 8.784167,-2.575277 V 20.665927 H 70.08008 l 0.564444,4.092222 h 3.175 v 5.538611 c -1.023055,0.564445 -2.116666,0.811389 -3.316111,0.811389 -3.386667,0 -5.08,-2.187222 -5.08,-8.607778 0,-6.067778 2.54,-8.607778 5.961945,-8.607778 1.975555,0 3.316111,0.599722 4.974166,1.869722 L 79.28758,12.693149 C 77.276747,10.92926 74.701469,9.6945374 71.138413,9.6945374 Z"
+     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:35.27777863px;line-height:125%;font-family:'Fira Sans';-inkscape-font-specification:'Fira Sans Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#2e323c;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     id="path5042" />
+  <path
+     d="m 85.938544,6.1314818 c -1.940278,0 -3.280834,1.3758334 -3.280834,3.1750001 0,1.7991671 1.340556,3.1750001 3.280834,3.1750001 1.940278,0 3.316111,-1.375833 3.316111,-3.1750001 0,-1.7991667 -1.375833,-3.1750001 -3.316111,-3.1750001 z m 2.822222,9.8777782 h -5.573889 v 18.7325 h 5.573889 z"
+     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:35.27777863px;line-height:125%;font-family:'Fira Sans';-inkscape-font-specification:'Fira Sans Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#2e323c;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     id="path5044" />
+  <path
+     d="m 101.68125,12.093426 c 0.81139,0 1.86973,0.176389 2.92806,0.705556 l 1.51694,-3.6336112 c -1.37583,-0.7055556 -3.175,-1.1994445 -5.11527,-1.1994445 -4.797782,0 -7.161393,2.7869447 -7.161393,6.3852777 v 1.658056 h -2.822222 v 3.880555 h 2.822222 V 34.74176 h 5.573889 V 19.889815 h 3.810004 l 0.59972,-3.880555 h -4.409724 v -1.446389 c 0,-1.763889 0.635004,-2.469445 2.257774,-2.469445 z"
+     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:35.27777863px;line-height:125%;font-family:'Fira Sans';-inkscape-font-specification:'Fira Sans Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#2e323c;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     id="path5046" />
+  <path
+     d="m 113.41828,15.409538 c -5.60917,0 -9.17222,4.162777 -9.17222,10.124722 0,5.926667 3.52778,9.807222 9.27805,9.807222 2.57528,0 4.58611,-0.846666 6.31473,-2.2225 l -2.43417,-3.457222 c -1.34056,0.846667 -2.25778,1.27 -3.59833,1.27 -2.2225,0 -3.70417,-1.27 -3.70417,-5.432778 0,-4.198055 1.37583,-5.820833 3.77472,-5.820833 1.27,0 2.36361,0.423333 3.56306,1.340555 l 2.39889,-3.316111 c -1.79917,-1.516944 -3.81,-2.293055 -6.42056,-2.293055 z"
+     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:35.27777863px;line-height:125%;font-family:'Fira Sans';-inkscape-font-specification:'Fira Sans Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#2e323c;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     id="path5048" />
+  <path
+     d="m 137.64529,16.00926 h -5.57389 v 13.0175 c -0.81139,1.411111 -1.86972,2.187222 -3.03389,2.187222 -1.16416,0 -1.79916,-0.564444 -1.79916,-2.469444 V 16.00926 h -5.57389 v 13.440833 c 0,3.633612 1.72861,5.891389 5.22111,5.891389 2.46944,0 4.30389,-0.987777 5.67972,-2.963333 l 0.24695,2.363611 h 4.83305 z"
+     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:35.27777863px;line-height:125%;font-family:'Fira Sans';-inkscape-font-specification:'Fira Sans Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#2e323c;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     id="path5050" />
+  <path
+     d="m 152.18911,15.480093 c -2.15194,0 -3.98639,1.552222 -4.83305,4.162778 l -0.45862,-3.633611 h -4.86833 v 18.7325 h 5.57389 v -9.278056 c 0.59972,-2.8575 1.55222,-4.586111 3.88056,-4.586111 0.635,0 1.09361,0.105834 1.69333,0.246945 l 0.88194,-5.3975 c -0.59972,-0.176389 -1.16416,-0.246945 -1.86972,-0.246945 z"
+     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:35.27777863px;line-height:125%;font-family:'Fira Sans';-inkscape-font-specification:'Fira Sans Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#2e323c;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     id="path5052" />
+  <path
+     d="m 166.314,15.480093 c -2.15194,0 -3.98638,1.552222 -4.83305,4.162778 L 161.02234,16.00926 H 156.154 v 18.7325 h 5.57389 v -9.278056 c 0.59973,-2.8575 1.55223,-4.586111 3.88056,-4.586111 0.635,0 1.09361,0.105834 1.69333,0.246945 l 0.88195,-5.3975 C 167.584,15.550649 167.01956,15.480093 166.314,15.480093 Z"
+     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:35.27777863px;line-height:125%;font-family:'Fira Sans';-inkscape-font-specification:'Fira Sans Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#2e323c;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     id="path5054" />
+  <path
+     d="m 172.85756,42.749608 -0.67028,-3.81 c 3.59834,-0.564444 4.72723,-1.516944 5.75028,-4.198055 h -1.97555 l -5.67973,-18.062223 5.50334,-1.27 3.42194,15.063612 c 1.34056,-8.11389 2.46945,-11.430001 4.72722,-15.063612 l 5.36223,1.763889 c -2.50473,3.386667 -3.66889,6.914445 -5.89139,16.333611 -1.51695,6.561667 -4.48028,8.360834 -10.54806,9.242778 z"
+     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:35.27777863px;line-height:125%;font-family:'Fira Sans';-inkscape-font-specification:'Fira Sans Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#2e323c;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     id="path5016" />
+  <path
+     d="m 172.54897,42.749608 -0.67028,-3.81 c 3.59833,-0.564444 4.72722,-1.516944 5.75028,-4.198055 h -1.97556 l -5.67972,-18.062223 5.50333,-1.27 3.42195,15.063612 c 1.34055,-8.11389 2.46944,-11.430001 4.72722,-15.063612 l 5.36222,1.763889 c -2.50472,3.386667 -3.66889,6.914445 -5.89139,16.333611 -1.51694,6.561667 -4.48027,8.360834 -10.54805,9.242778 z"
+     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:35.27777863px;line-height:125%;font-family:'Fira Sans';-inkscape-font-specification:'Fira Sans Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;display:inline;fill:#444a58;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     id="path4934" />
+  <path
+     d="m 70.993729,9.6945374 c -6.561667,0 -11.712222,4.5155556 -11.712222,12.8058336 0,8.360834 3.739444,12.841111 11.218333,12.841111 3.069167,0 6.244167,-0.9525 8.784167,-2.575277 V 20.665927 h -9.348611 l 0.564444,4.092222 h 3.175 v 5.538611 c -1.023055,0.564445 -2.116666,0.811389 -3.316111,0.811389 -3.386666,0 -5.08,-2.187222 -5.08,-8.607778 0,-6.067778 2.54,-8.607778 5.961945,-8.607778 1.975555,0 3.316111,0.599722 4.974167,1.869722 l 2.928055,-3.069166 C 77.132063,10.92926 74.556785,9.6945374 70.993729,9.6945374 Z"
+     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:35.27777863px;line-height:125%;font-family:'Fira Sans';-inkscape-font-specification:'Fira Sans Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#444a58;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     id="path4942" />
+  <path
+     d="m 85.79386,6.1314818 c -1.940278,0 -3.280833,1.3758334 -3.280833,3.1750001 0,1.7991671 1.340555,3.1750001 3.280833,3.1750001 1.940278,0 3.316111,-1.375833 3.316111,-3.1750001 0,-1.7991667 -1.375833,-3.1750001 -3.316111,-3.1750001 z m 2.822222,9.8777782 h -5.573889 v 18.7325 h 5.573889 z"
+     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:35.27777863px;line-height:125%;font-family:'Fira Sans';-inkscape-font-specification:'Fira Sans Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#444a58;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     id="path4944" />
+  <path
+     d="m 101.53657,12.093426 c 0.81139,0 1.86972,0.176389 2.92806,0.705556 l 1.51694,-3.6336112 c -1.37583,-0.7055556 -3.175,-1.1994445 -5.11528,-1.1994445 -4.797775,0 -7.161387,2.7869447 -7.161387,6.3852777 v 1.658056 h -2.822222 v 3.880555 h 2.822222 V 34.74176 h 5.573889 V 19.889815 h 3.809998 l 0.59972,-3.880555 h -4.409718 v -1.446389 c 0,-1.763889 0.635001,-2.469445 2.257778,-2.469445 z"
+     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:35.27777863px;line-height:125%;font-family:'Fira Sans';-inkscape-font-specification:'Fira Sans Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#444a58;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     id="path4946" />
+  <path
+     d="m 113.2736,15.409538 c -5.60917,0 -9.17223,4.162777 -9.17223,10.124722 0,5.926667 3.52778,9.807222 9.27806,9.807222 2.57528,0 4.58611,-0.846666 6.31472,-2.2225 l -2.43417,-3.457222 c -1.34055,0.846667 -2.25777,1.27 -3.59833,1.27 -2.2225,0 -3.70417,-1.27 -3.70417,-5.432778 0,-4.198055 1.37584,-5.820833 3.77473,-5.820833 1.27,0 2.36361,0.423333 3.56305,1.340555 l 2.39889,-3.316111 c -1.79917,-1.516944 -3.81,-2.293055 -6.42055,-2.293055 z"
+     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:35.27777863px;line-height:125%;font-family:'Fira Sans';-inkscape-font-specification:'Fira Sans Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#444a58;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     id="path4948" />
+  <path
+     d="m 137.50061,16.00926 h -5.57389 v 13.0175 c -0.81139,1.411111 -1.86972,2.187222 -3.03389,2.187222 -1.16417,0 -1.79917,-0.564444 -1.79917,-2.469444 V 16.00926 h -5.57388 v 13.440833 c 0,3.633612 1.72861,5.891389 5.22111,5.891389 2.46944,0 4.30389,-0.987777 5.67972,-2.963333 l 0.24694,2.363611 h 4.83306 z"
+     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:35.27777863px;line-height:125%;font-family:'Fira Sans';-inkscape-font-specification:'Fira Sans Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#444a58;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     id="path4950" />
+  <path
+     d="m 152.04443,15.480093 c -2.15195,0 -3.98639,1.552222 -4.83306,4.162778 l -0.45861,-3.633611 h -4.86833 v 18.7325 h 5.57389 v -9.278056 c 0.59972,-2.8575 1.55222,-4.586111 3.88055,-4.586111 0.635,0 1.09361,0.105834 1.69333,0.246945 l 0.88195,-5.3975 c -0.59972,-0.176389 -1.16417,-0.246945 -1.86972,-0.246945 z"
+     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:35.27777863px;line-height:125%;font-family:'Fira Sans';-inkscape-font-specification:'Fira Sans Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#444a58;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     id="path4952" />
+  <path
+     d="m 166.16932,15.480093 c -2.15194,0 -3.98639,1.552222 -4.83306,4.162778 l -0.45861,-3.633611 h -4.86833 v 18.7325 h 5.57389 v -9.278056 c 0.59972,-2.8575 1.55222,-4.586111 3.88055,-4.586111 0.635,0 1.09362,0.105834 1.69334,0.246945 l 0.88194,-5.3975 c -0.59972,-0.176389 -1.16416,-0.246945 -1.86972,-0.246945 z"
+     style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:35.27777863px;line-height:125%;font-family:'Fira Sans';-inkscape-font-specification:'Fira Sans Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#444a58;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     id="path4954" />
+  <path
+     id="path4614"
+     d="M 20.697583,51.137683 C 14.665366,49.51096 5.9017679,44.57451 2.5850611,32.323578"
+     style="display:inline;opacity:1;vector-effect:none;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:1107.35998535;stroke-opacity:0" />
+  <path
+     d="m 18.047816,51.681233 q 0.07334,-0.228402 0.207256,-0.386963 0.133051,-0.155874 0.299898,-0.241612 0.165985,-0.08305 0.354155,-0.09673 0.187308,-0.01099 0.37003,0.04768 0.182721,0.05868 0.328611,0.176663 0.145028,0.120675 0.231628,0.284835 0.08574,0.166848 0.103165,0.371042 0.01656,0.206882 -0.05678,0.435284 -0.07334,0.228402 -0.206393,0.384276 -0.133914,0.158561 -0.299898,0.241612 -0.166848,0.08574 -0.354156,0.09673 -0.18817,0.01368 -0.370892,-0.045 -0.182721,-0.05868 -0.327748,-0.17935 -0.145891,-0.117988 -0.231629,-0.284835 -0.0866,-0.164161 -0.103164,-0.371042 -0.01743,-0.204195 0.05592,-0.432597 z m 0.196156,0.06299 q -0.05695,0.177348 -0.0498,0.339707 0.0063,0.165047 0.06937,0.300906 0.0604,0.134997 0.17663,0.234569 0.113547,0.09871 0.272084,0.149618 0.158538,0.05091 0.311012,0.03762 0.149788,-0.01415 0.280174,-0.08788 0.1277,-0.0746 0.228899,-0.205128 0.100336,-0.127845 0.157286,-0.305192 0.05695,-0.177348 0.05066,-0.342394 -0.0071,-0.16236 -0.06754,-0.297357 -0.06308,-0.135859 -0.17663,-0.234568 -0.116234,-0.09957 -0.274771,-0.150481 -0.158538,-0.05091 -0.308325,-0.03676 -0.152474,0.01329 -0.280174,0.08788 -0.130387,0.07373 -0.230723,0.201578 -0.101199,0.130532 -0.158149,0.307879 z m 0.869193,-0.334468 q 0.115545,0.0371 0.197513,0.104922 0.08197,0.06782 0.129397,0.160118 0.04474,0.09144 0.04957,0.205625 0.004,0.116876 -0.03918,0.25123 -0.04228,0.131667 -0.115375,0.220833 -0.07578,0.0883 -0.168081,0.135733 -0.09316,0.05012 -0.199289,0.05754 -0.106128,0.0074 -0.210924,-0.02623 -0.06986,-0.02243 -0.122842,-0.0513 -0.05566,-0.02973 -0.09348,-0.05966 -0.03868,-0.02724 -0.06037,-0.05199 -0.02524,-0.02293 -0.0334,-0.03444 l 0.113575,-0.141379 q 0.02621,0.02916 0.09099,0.07665 0.06124,0.0493 0.155283,0.07951 0.137041,0.04401 0.259465,-0.02339 0.121561,-0.06471 0.180236,-0.247431 0.0535,-0.166599 0.0047,-0.300831 -0.04965,-0.131545 -0.210872,-0.183317 -0.104796,-0.03365 -0.178876,-0.03373 -0.07763,0.0017 -0.121289,0.0085 l 7.17e-4,-0.177619 q 0.01602,-0.0037 0.05345,-0.0095 0.03387,-0.0039 0.08387,-0.0027 0.04913,0.0039 0.111699,0.01215 0.05902,0.01006 0.123507,0.03077 z"
+     style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82222223px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#e8e8e8;fill-opacity:1;stroke:none;stroke-width:0.26458335px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     id="path4964" />
+  <path
+     d="m 15.776358,51.246383 q 0.04087,-0.09302 0.118222,-0.163841 0.07363,-0.06937 0.173109,-0.127396 0.09834,-0.05544 0.212181,-0.104065 0.112707,-0.04604 0.224278,-0.0895 0.06322,-0.02463 0.148762,-0.05795 0.08295,-0.03445 0.163011,-0.07635 0.08006,-0.04189 0.142341,-0.08543 0.0597,-0.04467 0.079,-0.0886 l -0.824246,-0.362131 0.08968,-0.204123 1.085214,0.476786 q -0.0042,0.01664 -0.01104,0.03214 -0.0068,0.0155 -0.01249,0.02842 -0.05335,0.121441 -0.137521,0.207767 -0.08788,0.08777 -0.193348,0.152412 -0.105464,0.06464 -0.221889,0.11213 -0.116426,0.04749 -0.227998,0.09095 -0.09184,0.03363 -0.174795,0.06809 -0.08667,0.0359 -0.156708,0.07603 -0.07375,0.04158 -0.127973,0.09482 -0.0568,0.05211 -0.08859,0.124457 -0.03406,0.07751 -0.03206,0.143128 -5.85e-4,0.06448 0.02486,0.118814 0.02545,0.05434 0.07548,0.09481 0.04631,0.04193 0.108326,0.06917 0.07493,0.03292 0.146026,0.0395 0.07109,0.0066 0.132674,-2.79e-4 0.06044,-0.0043 0.107657,-0.02052 0.04608,-0.01366 0.07098,-0.02122 l 0.04393,0.222753 q -0.03461,0.01562 -0.09701,0.03136 -0.06354,0.01832 -0.142891,0.02353 -0.07935,0.0052 -0.169669,-0.0067 -0.09032,-0.01194 -0.183335,-0.0528 -0.281639,-0.123737 -0.363271,-0.316814 -0.08535,-0.191629 0.01909,-0.429342 z"
+     style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82222223px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#e8e8e8;fill-opacity:1;stroke:none;stroke-width:0.26458335px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     id="path4966" />
+  <path
+     d="m 15.534593,50.607467 q -0.225197,0.436386 -0.499841,0.593186 -0.277152,0.155507 -0.550521,0.01443 -0.273368,-0.141072 -0.304696,-0.455769 -0.03384,-0.315991 0.191361,-0.752377 0.225197,-0.436386 0.502349,-0.591893 0.274644,-0.1568 0.548012,-0.01573 0.273369,0.141072 0.307205,0.457063 0.03133,0.314697 -0.193869,0.751084 z m -0.917916,-0.47369 q -0.07377,0.142954 -0.122222,0.27992 -0.05096,0.135671 -0.06001,0.251682 -0.01156,0.114716 0.02517,0.203542 0.03544,0.09133 0.130743,0.140514 0.0953,0.04918 0.190277,0.02515 0.09368,-0.02153 0.182989,-0.09612 0.0868,-0.07589 0.170362,-0.194735 0.08105,-0.120141 0.154825,-0.263095 0.07377,-0.142954 0.124729,-0.278626 0.04845,-0.136966 0.06001,-0.251682 0.0091,-0.116011 -0.02639,-0.207344 -0.03673,-0.08882 -0.132037,-0.138006 -0.0953,-0.04918 -0.188983,-0.02766 -0.09497,0.02403 -0.181775,0.09992 -0.08931,0.0746 -0.170362,0.194736 -0.08356,0.118847 -0.157333,0.261801 z"
+     style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82222223px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#e8e8e8;fill-opacity:1;stroke:none;stroke-width:0.26458335px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     id="path4968" />
+  <path
+     d="m 13.671005,50.26862 q -0.169925,-0.0293 -0.34997,-0.02519 -0.181495,0.0065 -0.365821,0.06728 l -0.159849,-0.09562 1.004032,-1.678414 0.225241,0.13474 -0.817134,1.365982 q 0.045,-0.0093 0.105982,-0.01224 0.05856,-0.0044 0.125357,-6.5e-4 0.06438,0.0023 0.134569,0.01144 0.06777,0.0077 0.128774,0.02113 z"
+     style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82222223px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#e8e8e8;fill-opacity:1;stroke:none;stroke-width:0.26458335px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     id="path4970" />
+  <path
+     d="m 11.853963,47.857966 q 0.06716,-0.09767 0.159609,-0.157403 0.08852,-0.05901 0.199138,-0.07542 0.10669,-0.01569 0.229073,0.02051 0.123982,0.03388 0.25421,0.123424 0.151158,0.103939 0.224697,0.216156 0.06961,0.112944 0.0853,0.219634 0.01569,0.106689 -0.0115,0.201021 -0.02879,0.09666 -0.07676,0.166421 -0.04637,0.06744 -0.108878,0.113511 -0.06643,0.0468 -0.13809,0.0763 -0.07399,0.0279 -0.14928,0.03778 -0.07922,0.0106 -0.15509,0.0064 0.138351,0.286935 -0.03595,0.540415 -0.06076,0.08837 -0.150014,0.14345 -0.09085,0.05741 -0.195214,0.0747 -0.106689,0.01569 -0.22137,-0.01179 -0.116279,-0.02515 -0.227903,-0.10191 -0.130228,-0.08955 -0.196064,-0.193043 -0.06584,-0.103496 -0.0808,-0.206261 -0.01889,-0.102038 0.0067,-0.194044 0.02326,-0.0936 0.07124,-0.16337 0.04637,-0.06744 0.108877,-0.113511 0.05858,-0.04535 0.128643,-0.07252 0.06774,-0.02877 0.139104,-0.03793 0.06904,-0.01075 0.136341,-0.009 -0.184862,-0.318917 0.004,-0.603501 z m 0.846775,0.561707 q 0.02878,-0.04186 0.04681,-0.09796 0.0141,-0.05538 0.0041,-0.120496 -0.01162,-0.06279 -0.05217,-0.12835 -0.04055,-0.06556 -0.128917,-0.126321 -0.07907,-0.05437 -0.153195,-0.07109 -0.07645,-0.01832 -0.137647,-0.009 -0.06279,0.01162 -0.113378,0.04534 -0.05058,0.03372 -0.08417,0.08255 -0.05117,0.07442 -0.06164,0.149417 -0.01047,0.075 0.01423,0.153638 0.02238,0.07704 0.07761,0.156114 0.0513,0.0798 0.129931,0.164694 0.144624,0.01382 0.261927,-0.03225 0.114977,-0.04767 0.196529,-0.166267 z m -1.149041,0.425789 q -0.02399,0.03488 -0.03649,0.08794 -0.01483,0.05145 -0.0063,0.108723 0.007,0.0596 0.04214,0.118029 0.03357,0.06076 0.107987,0.111929 0.07442,0.05117 0.143893,0.06469 0.06715,0.01192 0.125294,-0.0029 0.05654,-0.0125 0.103203,-0.04549 0.04274,-0.03226 0.07152,-0.07412 0.03518,-0.05116 0.05175,-0.115114 0.01657,-0.06395 0.0074,-0.135322 -0.0092,-0.07137 -0.04795,-0.149426 -0.04113,-0.07966 -0.115105,-0.16135 -0.137648,-0.009 -0.248554,0.02774 -0.112506,0.03909 -0.198855,0.164667 z"
+     style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82222223px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#e8e8e8;fill-opacity:1;stroke:none;stroke-width:0.26458335px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     id="path4972" />
+  <path
+     d="m 9.4441014,46.52114 q 0.1636636,-0.194243 0.3459334,-0.287929 0.180451,-0.09153 0.3705352,-0.09375 0.188266,-6.2e-5 0.381508,0.08157 0.191424,0.08379 0.374876,0.23836 0.09065,0.07638 0.193443,0.177752 0.102457,0.0974 0.172761,0.193539 L 10.061137,48.28103 Q 9.9544628,48.228054 9.8410948,48.143605 9.7237508,48.059495 9.6331037,47.983119 9.4496523,47.828548 9.336423,47.651954 9.2213752,47.477518 9.1913176,47.289813 9.1594416,47.104265 9.2208489,46.908744 9.2804378,46.715382 9.4441014,46.52114 Z m 1.3010156,0.107156 q -0.297839,-0.25095 -0.571894,-0.22353 -0.2758742,0.02958 -0.5068216,0.303677 -0.2309475,0.274098 -0.2114802,0.548833 0.017649,0.276893 0.3154876,0.527844 0.08849,0.07456 0.139947,0.114224 0.0493,0.03785 0.07054,0.05206 L 10.944692,46.80753 q -0.01761,-0.01852 -0.06327,-0.06069 -0.04782,-0.04398 -0.136309,-0.118542 z"
+     style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82222223px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#e8e8e8;fill-opacity:1;stroke:none;stroke-width:0.26458335px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     id="path4974" />
+  <path
+     d="m 9.3347336,45.317914 q -0.066929,-0.06476 -0.1195953,-0.111799 -0.056657,-0.04697 -0.098986,-0.07222 l -0.3041929,0.314363 q 0.010469,0.02977 0.051229,0.08099 0.036769,0.05129 0.1016701,0.11409 0.042591,0.04121 0.097154,0.08223 0.052535,0.03905 0.1127235,0.05802 0.056198,0.01904 0.1159267,0.01007 0.057766,-0.0069 0.110755,-0.0617 0.098127,-0.101408 0.072477,-0.204772 -0.027613,-0.101336 -0.139161,-0.209275 z m -0.8983159,0.960818 q -0.1135765,-0.109901 -0.1612082,-0.2149 -0.051622,-0.104933 -0.045281,-0.204831 0.00235,-0.09983 0.054682,-0.1945 0.05037,-0.09264 0.1367213,-0.181877 l 0.6378237,-0.65915 q 0.028263,0.0195 0.080732,0.05456 0.048479,0.03513 0.1091264,0.08203 0.060648,0.0469 0.1294736,0.105648 0.06876,0.05475 0.131633,0.115593 0.089239,0.08635 0.1466174,0.177219 0.057378,0.09087 0.07485,0.18239 0.01551,0.09355 -0.01687,0.18789 -0.03238,0.09434 -0.1206928,0.185606 -0.084389,0.08721 -0.1797063,0.11672 -0.097345,0.02755 -0.195478,0.0072 -0.098133,-0.02034 -0.1969215,-0.08059 -0.098789,-0.06025 -0.1880275,-0.1466 -0.028394,-0.02748 -0.054891,-0.06097 -0.02846,-0.03147 -0.048938,-0.06306 -0.024469,-0.03153 -0.040826,-0.05521 -0.016356,-0.02368 -0.022506,-0.03356 l -0.051026,0.05273 q -0.045138,0.04665 -0.078173,0.10108 -0.034998,0.05646 -0.041995,0.116452 -0.00896,0.06202 0.018062,0.127439 0.023031,0.06549 0.096045,0.136137 0.093295,0.09028 0.178018,0.144768 0.080732,0.05456 0.1289487,0.07372 l -0.1288057,0.177756 q -0.052207,-0.0191 -0.151127,-0.08733 -0.1008826,-0.0662 -0.200262,-0.162366 z"
+     style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82222223px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#e8e8e8;fill-opacity:1;stroke:none;stroke-width:0.26458335px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     id="path4976" />
+  <path
+     d="m 6.9504777,44.676491 q 0.3776384,-0.198203 0.7571358,-0.3444 0.3775854,-0.148274 0.6913119,-0.249237 l 0.1606125,0.174368 Q 8.4331927,44.561615 8.2563731,44.927848 8.0776414,45.292005 7.8491433,45.652122 L 7.6579378,45.444541 q 0.07877,-0.1186 0.1644226,-0.258888 0.081665,-0.140452 0.1614171,-0.28298 0.075765,-0.142691 0.145302,-0.279646 0.069537,-0.136955 0.1200648,-0.248726 -0.1155374,0.04119 -0.2577327,0.09927 -0.1421953,0.05808 -0.288706,0.123961 -0.1504986,0.06572 -0.295261,0.137673 -0.1487503,0.07179 -0.2734096,0.140567 z"
+     style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82222223px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#e8e8e8;fill-opacity:1;stroke:none;stroke-width:0.26458335px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     id="path4978" />
+  <path
+     d="m 7.690007,43.271166 0.1693753,0.200502 -1.1210824,0.947044 -0.1693753,-0.200501 z m -1.2381414,1.21957 q 0.045531,0.0539 0.043818,0.121845 -0.00569,0.06761 -0.066056,0.118607 -0.060366,0.05099 -0.1258219,0.04348 -0.069433,-0.0078 -0.1149641,-0.06174 -0.045531,-0.0539 -0.039841,-0.12151 0.00171,-0.06795 0.062079,-0.118941 0.060366,-0.051 0.129799,-0.04315 0.065456,0.0075 0.1109869,0.06141 z"
+     style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82222223px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#e8e8e8;fill-opacity:1;stroke:none;stroke-width:0.26458335px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     id="path4980" />
+  <path
+     d="m 5.9011197,42.875617 q -0.00217,0.06211 0.023657,0.156111 0.025826,0.094 0.091072,0.17911 0.06868,0.08959 0.1520703,0.128793 0.079434,0.03868 0.1685753,0.03435 0.086902,-0.0026 0.179032,-0.04478 0.09213,-0.04218 0.1817236,-0.110856 0.203825,-0.156247 0.2385519,-0.342889 0.032487,-0.184926 -0.096288,-0.352914 -0.065246,-0.08511 -0.1148903,-0.135958 -0.053601,-0.05137 -0.086599,-0.07586 z M 5.1171775,43.476566 4.9216594,43.295733 6.6261739,41.989099 q 0.075102,0.05622 0.1811108,0.152761 0.1060083,0.09654 0.2176131,0.242128 0.1030198,0.13439 0.1428787,0.274525 0.039859,0.140135 0.019843,0.276384 -0.020016,0.136249 -0.099385,0.264656 -0.083326,0.127884 -0.2244353,0.236055 -0.1343901,0.103019 -0.2721368,0.155272 -0.1394637,0.05001 -0.2716076,0.04463 -0.132144,-0.0054 -0.2507715,-0.06736 -0.1203446,-0.06421 -0.2164964,-0.189644 -0.077265,-0.100792 -0.1087647,-0.197551 -0.033217,-0.099 -0.037768,-0.155962 z"
+     style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82222223px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#e8e8e8;fill-opacity:1;stroke:none;stroke-width:0.26458335px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     id="path4982" />
+  <path
+     d="M 5.188436,40.177027 5.3882578,40.049466 6.0215086,41.041438 4.3729794,42.093819 4.2256765,41.863072 5.6743839,40.938253 Z"
+     style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82222223px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#e8e8e8;fill-opacity:1;stroke:none;stroke-width:0.26458335px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     id="path4984" />
+  <path
+     d="m 4.6713638,40.270735 q -0.1698284,0.09529 -0.3229728,0.116489 -0.1556057,0.02258 -0.2848608,-0.01169 -0.129255,-0.03427 -0.2304778,-0.116629 -0.1012228,-0.08236 -0.1633658,-0.193116 -0.1450002,-0.258435 -0.059889,-0.487409 0.08265,-0.227594 0.4124614,-0.412642 0.014768,-0.0083 0.039381,-0.0221 0.022152,-0.01243 0.043223,-0.02101 l 0.4916197,0.876216 Q 4.7875599,39.872223 4.8343146,39.71331 4.8810693,39.554398 4.7609262,39.340266 4.6932595,39.219663 4.6279977,39.149489 4.5588938,39.078234 4.5157273,39.04744 l 0.1740275,-0.139711 q 0.045628,0.02941 0.1296216,0.121438 0.082613,0.08957 0.1571844,0.222474 0.093905,0.167367 0.113727,0.31805 0.01598,0.149603 -0.028436,0.278078 -0.044416,0.128475 -0.1429231,0.235523 -0.1023497,0.105967 -0.2475649,0.187443 z M 4.0528377,39.462486 q -0.1564416,0.08454 -0.2093214,0.221 -0.056722,0.13538 0.021993,0.275673 0.044191,0.07876 0.110711,0.12234 0.062678,0.0425 0.1353801,0.05672 0.072702,0.01422 0.1505047,0.0029 0.076422,-0.01375 0.1470193,-0.04366 z"
+     style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82222223px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#e8e8e8;fill-opacity:1;stroke:none;stroke-width:0.26458335px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     id="path4986" />
+  <path
+     d="m 3.0888372,38.892274 -0.2476331,-0.497785 0.1970922,-0.09805 0.2476331,0.497784 0.6064377,-0.301685 q 0.098546,-0.04902 0.1567014,-0.09687 0.055628,-0.04659 0.078447,-0.09576 0.020291,-0.04792 0.012826,-0.100945 -0.00747,-0.05302 -0.037634,-0.113669 -0.052795,-0.106126 -0.1069618,-0.157984 -0.057951,-0.05313 -0.080628,-0.07337 l 0.171939,-0.142273 q 0.035286,0.02658 0.10455,0.102445 0.071791,0.07461 0.1283568,0.188318 0.066622,0.133922 0.077769,0.238702 0.00736,0.10351 -0.034464,0.190513 -0.041827,0.087 -0.1290598,0.158768 -0.091017,0.0705 -0.2224116,0.135861 L 2.8393507,39.309536 2.6820186,39.094654 Z"
+     style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82222223px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#e8e8e8;fill-opacity:1;stroke:none;stroke-width:0.26458335px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     id="path4988" />
+  <path
+     d="m 2.5751938,37.814121 -0.2274015,-0.507346 0.2008781,-0.09004 0.2274015,0.507346 0.6180863,-0.277037 q 0.1004391,-0.04502 0.1604725,-0.09048 0.057458,-0.04431 0.082237,-0.09253 0.022203,-0.04706 0.016878,-0.100347 -0.00533,-0.05328 -0.033029,-0.115091 -0.048481,-0.108165 -0.1005178,-0.16216 -0.055766,-0.05542 -0.077611,-0.07655 l 0.1775249,-0.135239 q 0.034188,0.02797 0.1003429,0.106569 0.06873,0.07744 0.1206747,0.193332 0.061179,0.136494 0.068101,0.241637 0.00319,0.103723 -0.042102,0.188972 -0.045294,0.08525 -0.1353441,0.153447 -0.09378,0.06677 -0.2276985,0.1268 L 2.3091198,38.22101 2.1605621,37.99997 Z"
+     style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82222223px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#e8e8e8;fill-opacity:1;stroke:none;stroke-width:0.26458335px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     id="path4990" />
+  <path
+     d="m 3.4602876,36.154213 0.099655,0.242812 -1.3576587,0.55721 -0.099655,-0.242812 z M 1.9075692,36.93486 q 0.026789,0.06527 0.0043,0.129413 -0.026169,0.0626 -0.099273,0.0926 -0.073105,0.03 -0.1330953,0.0028 -0.063673,-0.02878 -0.090462,-0.09405 -0.026789,-0.06527 -6.204e-4,-0.127873 0.022486,-0.06414 0.095591,-0.09414 0.073105,-0.03 0.1367777,-0.0012 0.059991,0.02724 0.08678,0.09251 z"
+     style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82222223px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#e8e8e8;fill-opacity:1;stroke:none;stroke-width:0.26458335px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     id="path4992" />
+  <path
+     d="m 2.6303493,36.062347 q -0.182839,0.06701 -0.3374055,0.06355 -0.1572162,-0.0025 -0.2793613,-0.05692 -0.122145,-0.05442 -0.208955,-0.151855 -0.08681,-0.09743 -0.1305152,-0.216675 -0.1019787,-0.278234 0.018522,-0.490726 0.117851,-0.21152 0.4729298,-0.341665 0.015899,-0.0058 0.042397,-0.01554 0.023849,-0.0087 0.046019,-0.01386 l 0.3457563,0.943343 q 0.2088098,-0.09457 0.2802829,-0.244003 0.071473,-0.149435 -0.013024,-0.379971 -0.04759,-0.129842 -0.1008392,-0.209517 -0.05687,-0.08135 -0.09458,-0.118631 l 0.1940617,-0.110204 q 0.040359,0.03631 0.1086205,0.140537 0.06729,0.101581 0.1197361,0.244673 0.066043,0.180189 0.061608,0.332106 -0.00806,0.150237 -0.072372,0.269997 -0.064315,0.119759 -0.1786177,0.209746 -0.1179237,0.08831 -0.2742643,0.145611 z M 2.1484798,35.165886 q -0.1679112,0.05854 -0.2418544,0.184832 -0.077564,0.124615 -0.022204,0.275656 0.031079,0.08479 0.089808,0.138415 0.055108,0.05194 0.1246152,0.07756 0.069508,0.02562 0.1481158,0.02687 0.077637,-0.0014 0.1520963,-0.01968 z"
+     style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82222223px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#e8e8e8;fill-opacity:1;stroke:none;stroke-width:0.26458335px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     id="path4994" />
+  <path
+     d="m 1.1680111,34.187928 q -0.010285,-0.03227 -0.017764,-0.07431 -0.011025,-0.04388 -0.015815,-0.08678 -0.00748,-0.04205 -0.0097,-0.07689 -0.00576,-0.03667 -0.00553,-0.05452 l 0.2315164,-0.0264 q -0.00133,0.03301 0.00751,0.107204 0.00529,0.07237 0.039579,0.179923 0.022285,0.06991 0.060703,0.134681 0.034872,0.06294 0.047107,0.08273 l 1.1750584,-0.374559 0.079712,0.25007 -1.3390826,0.426842 Q 1.3607594,34.597467 1.2918717,34.474281 1.220295,34.351952 1.1680111,34.187928 Z"
+     style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.82222223px;line-height:125%;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#e8e8e8;fill-opacity:1;stroke:none;stroke-width:0.26458335px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+     id="path4996" />
 </svg>
diff --git a/src/data/gui.glade b/src/data/gui.glade
--- a/src/data/gui.glade
+++ b/src/data/gui.glade
@@ -1,531 +1,1230 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!-- Generated with glade 3.20.0 
 
-Copyright (c) 2017 David Lettier
-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 the <organization> nor the
-      names of its 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 <COPYRIGHT HOLDER> 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.
-
-Author: David Lettier
-
--->
-<interface>
-  <requires lib="gtk+" version="3.10"/>
-  <!-- interface-license-type bsd3c -->
-  <!-- interface-name Gifcurry -->
-  <!-- interface-description Open source video to GIF maker, written in Haskell, that allows overlaid text. -->
-  <!-- interface-copyright 2017 David Lettier -->
-  <!-- interface-authors David Lettier -->
-  <object class="GtkImage" id="about-image">
-    <property name="visible">True</property>
-    <property name="can_focus">False</property>
-    <property name="stock">gtk-about</property>
-  </object>
-  <object class="GtkImage" id="create-icon">
-    <property name="visible">True</property>
-    <property name="can_focus">False</property>
-    <property name="margin_top">5</property>
-    <property name="margin_bottom">5</property>
-    <property name="stock">gtk-save</property>
-    <property name="icon_size">2</property>
-  </object>
-  <object class="GtkButton" id="no-button">
-    <property name="label">gtk-no</property>
-    <property name="visible">True</property>
-    <property name="can_focus">True</property>
-    <property name="receives_default">True</property>
-    <property name="use_stock">True</property>
-    <property name="always_show_image">True</property>
-  </object>
-  <object class="GtkImage" id="open-icon">
-    <property name="visible">True</property>
-    <property name="can_focus">False</property>
-    <property name="margin_top">5</property>
-    <property name="margin_bottom">5</property>
-    <property name="stock">gtk-open</property>
-    <property name="icon_size">2</property>
-  </object>
-  <object class="GtkWindow" id="gifcurry-window">
-    <property name="width_request">400</property>
-    <property name="can_focus">False</property>
-    <property name="valign">center</property>
-    <property name="title">Gifcurry </property>
-    <property name="resizable">False</property>
-    <property name="window_position">mouse</property>
-    <property name="default_width">400</property>
-    <property name="icon">gifcurry-icon.png</property>
-    <property name="gravity">center</property>
-    <child>
-      <object class="GtkBox" id="app-box">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
-        <property name="orientation">vertical</property>
-        <child>
-          <object class="GtkBox" id="file-input-about-box">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <child>
-              <object class="GtkFileChooserButton" id="input-file-button">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="tooltip_text" translatable="yes">Select an input file.</property>
-                <property name="do_overwrite_confirmation">True</property>
-                <property name="local_only">False</property>
-                <property name="title" translatable="yes">Gifcurry - Select an Input File</property>
-              </object>
-              <packing>
-                <property name="expand">True</property>
-                <property name="fill">True</property>
-                <property name="position">0</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkButton" id="about-button">
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="receives_default">True</property>
-                <property name="tooltip_text" translatable="yes">Information about Gifcurry.</property>
-                <property name="image">about-image</property>
-                <property name="always_show_image">True</property>
-              </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">False</property>
-                <property name="pack_type">end</property>
-                <property name="position">1</property>
-              </packing>
-            </child>
-          </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="fill">False</property>
-            <property name="position">0</property>
-          </packing>
-        </child>
-        <child>
-          <object class="GtkBox" id="start-duration-box">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="homogeneous">True</property>
-            <child>
-              <object class="GtkEntry" id="start-time-text-entry">
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="tooltip_text" translatable="yes">Start time.</property>
-                <property name="shadow_type">none</property>
-                <property name="primary_icon_stock">gtk-media-play</property>
-                <property name="placeholder_text" translatable="yes">Start Time (seconds)</property>
-                <property name="input_purpose">number</property>
-              </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">True</property>
-                <property name="position">0</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkEntry" id="duration-text-entry">
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="tooltip_text" translatable="yes">Duration.</property>
-                <property name="width_chars">0</property>
-                <property name="primary_icon_stock">gtk-media-stop</property>
-                <property name="placeholder_text" translatable="yes">Duration (seconds)</property>
-                <property name="input_purpose">number</property>
-              </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">True</property>
-                <property name="position">1</property>
-              </packing>
-            </child>
-          </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="fill">True</property>
-            <property name="position">2</property>
-          </packing>
-        </child>
-        <child>
-          <object class="GtkBox" id="width-quality-box">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="homogeneous">True</property>
-            <child>
-              <object class="GtkEntry" id="width-text-entry">
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="tooltip_text" translatable="yes">GIF width in px.</property>
-                <property name="shadow_type">none</property>
-                <property name="primary_icon_stock">gtk-justify-fill</property>
-                <property name="placeholder_text" translatable="yes">Width (pixels)</property>
-                <property name="input_purpose">number</property>
-              </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">True</property>
-                <property name="position">0</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkEntry" id="quality-text-entry">
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="tooltip_text" translatable="yes">GIF quality from 1 to 100.</property>
-                <property name="max_length">3</property>
-                <property name="width_chars">3</property>
-                <property name="max_width_chars">3</property>
-                <property name="primary_icon_stock">gtk-preferences</property>
-                <property name="placeholder_text" translatable="yes">Quality [1-100]</property>
-                <property name="input_purpose">number</property>
-              </object>
-              <packing>
-                <property name="expand">True</property>
-                <property name="fill">True</property>
-                <property name="position">1</property>
-              </packing>
-            </child>
-          </object>
-          <packing>
-            <property name="expand">True</property>
-            <property name="fill">True</property>
-            <property name="position">3</property>
-          </packing>
-        </child>
-        <child>
-          <object class="GtkFontButton" id="font-chooser-button">
-            <property name="name">font_chooser_button</property>
-            <property name="visible">True</property>
-            <property name="can_focus">True</property>
-            <property name="receives_default">True</property>
-            <property name="tooltip_text" translatable="yes">Choose your desired font for the bottom and top text.</property>
-            <property name="font">Sans 12</property>
-            <property name="preview_text">I love making GIFs with Gifcurry!</property>
-            <property name="title" translatable="yes">Gifcurry - Choose a Font</property>
-            <property name="use_font">True</property>
-            <property name="show_style">False</property>
-            <property name="show_size">False</property>
-          </object>
-          <packing>
-            <property name="expand">True</property>
-            <property name="fill">True</property>
-            <property name="position">4</property>
-          </packing>
-        </child>
-        <child>
-          <object class="GtkEntry" id="top-text-text-entry">
-            <property name="visible">True</property>
-            <property name="can_focus">True</property>
-            <property name="tooltip_text" translatable="yes">Optional top text.</property>
-            <property name="primary_icon_stock">gtk-italic</property>
-            <property name="placeholder_text" translatable="yes">Optional top text.</property>
-          </object>
-          <packing>
-            <property name="expand">True</property>
-            <property name="fill">True</property>
-            <property name="position">5</property>
-          </packing>
-        </child>
-        <child>
-          <object class="GtkEntry" id="bottom-text-text-entry">
-            <property name="visible">True</property>
-            <property name="can_focus">True</property>
-            <property name="tooltip_text" translatable="yes">Optional bottom text.</property>
-            <property name="primary_icon_stock">gtk-italic</property>
-            <property name="secondary_icon_tooltip_text" translatable="yes">Text will show up at the bottom of the GIF.</property>
-            <property name="placeholder_text" translatable="yes">Optional bottom text.</property>
-          </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="fill">True</property>
-            <property name="position">6</property>
-          </packing>
-        </child>
-        <child>
-          <object class="GtkSeparator" id="separator-1">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-          </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="fill">True</property>
-            <property name="position">7</property>
-          </packing>
-        </child>
-        <child>
-          <object class="GtkFileChooserButton" id="output-file-path-button">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="tooltip_text" translatable="yes">Select an output folder.</property>
-            <property name="action">select-folder</property>
-            <property name="title" translatable="yes">Gifcurry - Select an Output Folder</property>
-          </object>
-          <packing>
-            <property name="expand">True</property>
-            <property name="fill">True</property>
-            <property name="position">8</property>
-          </packing>
-        </child>
-        <child>
-          <object class="GtkEntry" id="output-file-name-text-entry">
-            <property name="visible">True</property>
-            <property name="can_focus">True</property>
-            <property name="tooltip_text" translatable="yes">Output file name.</property>
-            <property name="shadow_type">none</property>
-            <property name="primary_icon_stock">gtk-file</property>
-            <property name="placeholder_text" translatable="yes">Output file name.</property>
-          </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="fill">True</property>
-            <property name="position">9</property>
-          </packing>
-        </child>
-        <child>
-          <object class="GtkBox" id="frame-images-box">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <child>
-              <object class="GtkImage" id="first-frame-image">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="tooltip_text" translatable="yes">First frame preview.</property>
-                <property name="halign">center</property>
-                <property name="valign">center</property>
-                <property name="stock">gtk-discard</property>
-                <property name="pixel_size">0</property>
-                <property name="icon_size">0</property>
-              </object>
-              <packing>
-                <property name="expand">True</property>
-                <property name="fill">True</property>
-                <property name="position">0</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkImage" id="last-frame-image">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="tooltip_text" translatable="yes">Last frame preview.</property>
-                <property name="halign">center</property>
-                <property name="valign">center</property>
-                <property name="stock">gtk-discard</property>
-                <property name="pixel_size">0</property>
-                <property name="icon_size">0</property>
-              </object>
-              <packing>
-                <property name="expand">True</property>
-                <property name="fill">True</property>
-                <property name="pack_type">end</property>
-                <property name="position">1</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkSeparator" id="separator-2">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="orientation">vertical</property>
-              </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">True</property>
-                <property name="position">2</property>
-              </packing>
-            </child>
-          </object>
-          <packing>
-            <property name="expand">True</property>
-            <property name="fill">True</property>
-            <property name="position">10</property>
-          </packing>
-        </child>
-        <child>
-          <object class="GtkBox" id="save-open-box">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="homogeneous">True</property>
-            <child>
-              <object class="GtkButton" id="create-button">
-                <property name="label">Create</property>
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="receives_default">True</property>
-                <property name="tooltip_text" translatable="yes">Create the GIF.</property>
-                <property name="image">create-icon</property>
-                <property name="relief">half</property>
-                <property name="image_position">top</property>
-                <property name="always_show_image">True</property>
-              </object>
-              <packing>
-                <property name="expand">True</property>
-                <property name="fill">True</property>
-                <property name="position">0</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkButton" id="open-button">
-                <property name="label" translatable="yes">Open</property>
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="receives_default">True</property>
-                <property name="tooltip_text" translatable="yes">Open the GIF.</property>
-                <property name="image">open-icon</property>
-                <property name="relief">half</property>
-                <property name="image_position">top</property>
-                <property name="always_show_image">True</property>
-              </object>
-              <packing>
-                <property name="expand">True</property>
-                <property name="fill">True</property>
-                <property name="pack_type">end</property>
-                <property name="position">1</property>
-              </packing>
-            </child>
-          </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="fill">True</property>
-            <property name="position">11</property>
-          </packing>
-        </child>
-        <child>
-          <object class="GtkBox" id="link-box">
-            <property name="visible">True</property>
-            <property name="can_focus">False</property>
-            <property name="homogeneous">True</property>
-            <child>
-              <object class="GtkLinkButton" id="giphy-link-button">
-                <property name="label" translatable="yes">button</property>
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="receives_default">True</property>
-                <property name="tooltip_text" translatable="yes">https://giphy.com/create/upload</property>
-                <property name="halign">end</property>
-                <property name="uri">https://giphy.com/create/upload</property>
-              </object>
-              <packing>
-                <property name="expand">True</property>
-                <property name="fill">True</property>
-                <property name="position">0</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkSeparator" id="separator-3">
-                <property name="visible">True</property>
-                <property name="sensitive">False</property>
-                <property name="can_focus">False</property>
-                <property name="double_buffered">False</property>
-                <property name="halign">baseline</property>
-                <property name="valign">baseline</property>
-                <property name="orientation">vertical</property>
-              </object>
-              <packing>
-                <property name="expand">False</property>
-                <property name="fill">False</property>
-                <property name="position">1</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkLinkButton" id="imgur-link-button">
-                <property name="label" translatable="yes">button</property>
-                <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="receives_default">True</property>
-                <property name="tooltip_text" translatable="yes">https://www.imgur.com/</property>
-                <property name="halign">start</property>
-                <property name="uri">https://imgur.com/upload</property>
-              </object>
-              <packing>
-                <property name="expand">True</property>
-                <property name="fill">True</property>
-                <property name="pack_type">end</property>
-                <property name="position">2</property>
-              </packing>
-            </child>
-          </object>
-          <packing>
-            <property name="expand">False</property>
-            <property name="fill">True</property>
-            <property name="position">12</property>
-          </packing>
-        </child>
-        <child>
-          <object class="GtkBox" id="status-box">
-            <property name="visible">True</property>
-            <property name="sensitive">False</property>
-            <property name="can_focus">False</property>
-            <property name="hexpand">True</property>
-            <property name="vexpand">True</property>
-            <property name="baseline_position">top</property>
-            <child>
-              <object class="GtkEntry" id="status-text-entry">
-                <property name="visible">True</property>
-                <property name="sensitive">False</property>
-                <property name="app_paintable">True</property>
-                <property name="can_focus">False</property>
-                <property name="hexpand">True</property>
-                <property name="vexpand">True</property>
-                <property name="editable">False</property>
-                <property name="has_frame">False</property>
-                <property name="text" translatable="yes">Ready.</property>
-                <property name="shadow_type">none</property>
-                <property name="caps_lock_warning">False</property>
-                <property name="primary_icon_stock">gtk-about</property>
-                <property name="secondary_icon_activatable">False</property>
-                <property name="secondary_icon_sensitive">False</property>
-              </object>
-              <packing>
-                <property name="expand">True</property>
-                <property name="fill">True</property>
-                <property name="position">0</property>
-              </packing>
-            </child>
-          </object>
-          <packing>
-            <property name="expand">True</property>
-            <property name="fill">True</property>
-            <property name="position">13</property>
-          </packing>
-        </child>
-      </object>
-    </child>
-    <child>
-      <placeholder/>
-    </child>
-  </object>
-  <object class="GtkAboutDialog" id="about-dialog">
-    <property name="can_focus">False</property>
-    <property name="title" translatable="yes">Gifcurry - About</property>
-    <property name="window_position">center</property>
-    <property name="destroy_with_parent">True</property>
-    <property name="icon_name">dialog-information</property>
-    <property name="type_hint">dialog</property>
-    <property name="skip_taskbar_hint">True</property>
-    <property name="gravity">center</property>
-    <property name="transient_for">gifcurry-window</property>
-    <property name="attached_to">gifcurry-window</property>
-    <property name="program_name">Gifcurry</property>
-    <property name="version">2.3.0.0</property>
+Copyright (c) 2016 David Lettier
+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 the <organization> nor the
+      names of its 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 <COPYRIGHT HOLDER> 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.
+
+Author: David Lettier
+
+-->
+<interface>
+  <requires lib="gtk+" version="3.10"/>
+  <!-- interface-license-type bsd3c -->
+  <!-- interface-name Gifcurry -->
+  <!-- interface-description Your open source video to GIF maker built with Haskell. -->
+  <!-- interface-copyright 2016 David Lettier -->
+  <!-- interface-authors David Lettier -->
+  <object class="GtkImage" id="about-image">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="stock">gtk-about</property>
+    <property name="icon_size">3</property>
+  </object>
+  <object class="GtkAdjustment" id="bottom-crop-adjustment">
+    <property name="upper">100</property>
+    <property name="step_increment">1</property>
+  </object>
+  <object class="GtkImage" id="crop-toggle-button-image">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="stock">gtk-cut</property>
+  </object>
+  <object class="GtkAdjustment" id="duration-time-adjustment">
+    <property name="step_increment">1</property>
+  </object>
+  <object class="GtkImage" id="gif-icon-image">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="stock">gtk-dnd-multiple</property>
+  </object>
+  <object class="GtkBox" id="images-preview-box">
+    <property name="visible">True</property>
+    <property name="app_paintable">True</property>
+    <property name="can_focus">False</property>
+    <property name="hexpand">True</property>
+    <property name="vexpand">True</property>
+    <property name="orientation">vertical</property>
+    <child>
+      <object class="GtkBox" id="first-frame-preview-box">
+        <property name="visible">True</property>
+        <property name="app_paintable">True</property>
+        <property name="can_focus">False</property>
+        <child>
+          <object class="GtkOverlay" id="first-frame-preview-image-overlay">
+            <property name="visible">True</property>
+            <property name="app_paintable">True</property>
+            <property name="can_focus">False</property>
+            <child>
+              <object class="GtkImage" id="first-frame-image">
+                <property name="visible">True</property>
+                <property name="app_paintable">True</property>
+                <property name="can_focus">False</property>
+                <property name="tooltip_text" translatable="yes">This is the first frame of the GIF.</property>
+                <property name="hexpand">True</property>
+                <property name="stock">gtk-missing-image</property>
+                <property name="pixel_size">0</property>
+              </object>
+              <packing>
+                <property name="index">-1</property>
+              </packing>
+            </child>
+            <child type="overlay">
+              <object class="GtkDrawingArea" id="first-frame-preview-image-drawing-area">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="hexpand">True</property>
+              </object>
+              <packing>
+                <property name="index">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">True</property>
+            <property name="fill">True</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkSeparator" id="first-last-frame-preview-image-separator">
+            <property name="visible">True</property>
+            <property name="sensitive">False</property>
+            <property name="can_focus">False</property>
+            <property name="orientation">vertical</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkOverlay" id="last-frame-preview-image-overlay">
+            <property name="visible">True</property>
+            <property name="app_paintable">True</property>
+            <property name="can_focus">False</property>
+            <child>
+              <object class="GtkImage" id="last-frame-image">
+                <property name="visible">True</property>
+                <property name="app_paintable">True</property>
+                <property name="can_focus">False</property>
+                <property name="tooltip_text" translatable="yes">This is the last frame of the GIF.</property>
+                <property name="hexpand">True</property>
+                <property name="stock">gtk-missing-image</property>
+                <property name="pixel_size">0</property>
+              </object>
+              <packing>
+                <property name="index">-1</property>
+              </packing>
+            </child>
+            <child type="overlay">
+              <object class="GtkDrawingArea" id="last-frame-preview-image-drawing-area">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="hexpand">True</property>
+              </object>
+              <packing>
+                <property name="index">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">True</property>
+            <property name="fill">True</property>
+            <property name="position">2</property>
+          </packing>
+        </child>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="fill">True</property>
+        <property name="position">1</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkBox" id="last-frame-preview-box">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="homogeneous">True</property>
+        <child>
+          <object class="GtkButton" id="first-frame-preview-label-button">
+            <property name="label" translatable="yes">First Frame</property>
+            <property name="visible">True</property>
+            <property name="sensitive">False</property>
+            <property name="can_focus">False</property>
+            <property name="receives_default">False</property>
+            <property name="tooltip_text" translatable="yes">Last Frame</property>
+            <property name="opacity">0.98999999999999999</property>
+            <property name="hexpand">True</property>
+            <property name="vexpand">True</property>
+          </object>
+          <packing>
+            <property name="expand">True</property>
+            <property name="fill">True</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="last-frame-preview-label-button">
+            <property name="label" translatable="yes">Last Frame</property>
+            <property name="visible">True</property>
+            <property name="sensitive">False</property>
+            <property name="can_focus">False</property>
+            <property name="focus_on_click">False</property>
+            <property name="receives_default">False</property>
+            <property name="tooltip_text" translatable="yes">First Frame</property>
+            <property name="hexpand">True</property>
+            <property name="vexpand">True</property>
+          </object>
+          <packing>
+            <property name="expand">True</property>
+            <property name="fill">True</property>
+            <property name="pack_type">end</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+      <packing>
+        <property name="expand">True</property>
+        <property name="fill">True</property>
+        <property name="pack_type">end</property>
+        <property name="position">4</property>
+      </packing>
+    </child>
+  </object>
+  <object class="GtkAdjustment" id="left-crop-adjustment">
+    <property name="upper">100</property>
+    <property name="step_increment">1</property>
+  </object>
+  <object class="GtkButton" id="no-button">
+    <property name="label">gtk-no</property>
+    <property name="visible">True</property>
+    <property name="can_focus">True</property>
+    <property name="receives_default">True</property>
+    <property name="use_stock">True</property>
+    <property name="always_show_image">True</property>
+  </object>
+  <object class="GtkImage" id="open-icon">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="margin_top">5</property>
+    <property name="margin_bottom">5</property>
+    <property name="stock">gtk-open</property>
+    <property name="icon_size">2</property>
+  </object>
+  <object class="GtkAdjustment" id="quality-percent-adjustment">
+    <property name="lower">1</property>
+    <property name="upper">100</property>
+    <property name="value">100</property>
+    <property name="step_increment">1</property>
+  </object>
+  <object class="GtkAdjustment" id="right-crop-adjustment">
+    <property name="upper">100</property>
+    <property name="step_increment">1</property>
+  </object>
+  <object class="GtkImage" id="save-icon">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="margin_top">5</property>
+    <property name="margin_bottom">5</property>
+    <property name="stock">gtk-save</property>
+    <property name="icon_size">2</property>
+  </object>
+  <object class="GtkImage" id="save-toggle-button-image">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="stock">gtk-save</property>
+  </object>
+  <object class="GtkAdjustment" id="start-time-adjustment">
+    <property name="step_increment">1</property>
+  </object>
+  <object class="GtkImage" id="top-bottom-text-toggle-button-image">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="stock">gtk-italic</property>
+  </object>
+  <object class="GtkAdjustment" id="top-crop-adjustment">
+    <property name="upper">100</property>
+    <property name="step_increment">1</property>
+  </object>
+  <object class="GtkImage" id="upload-toggle-button-image">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="stock">gtk-sort-descending</property>
+  </object>
+  <object class="GtkImage" id="video-icon-image">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="stock">gtk-media-record</property>
+  </object>
+  <object class="GtkBox" id="video-preview-box">
+    <property name="visible">True</property>
+    <property name="app_paintable">True</property>
+    <property name="can_focus">False</property>
+    <property name="tooltip_text" translatable="yes">This is a video preview of the GIF.</property>
+    <property name="hexpand">True</property>
+    <property name="vexpand">True</property>
+    <property name="orientation">vertical</property>
+    <child>
+      <object class="GtkOverlay" id="video-preview-overlay">
+        <property name="visible">True</property>
+        <property name="app_paintable">True</property>
+        <property name="can_focus">False</property>
+        <child>
+          <object class="GtkBox" id="video-preview-overlay-child-box">
+            <property name="visible">True</property>
+            <property name="app_paintable">True</property>
+            <property name="can_focus">False</property>
+            <property name="orientation">vertical</property>
+            <child>
+              <placeholder/>
+            </child>
+          </object>
+          <packing>
+            <property name="index">-1</property>
+          </packing>
+        </child>
+        <child type="overlay">
+          <object class="GtkDrawingArea" id="video-preview-drawing-area">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+          </object>
+        </child>
+      </object>
+      <packing>
+        <property name="expand">True</property>
+        <property name="fill">True</property>
+        <property name="position">0</property>
+      </packing>
+    </child>
+  </object>
+  <object class="GtkAdjustment" id="width-size-adjustment">
+    <property name="lower">1</property>
+    <property name="upper">3840</property>
+    <property name="value">500</property>
+    <property name="step_increment">1</property>
+  </object>
+  <object class="GtkImage" id="width-toggle-button-image">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="stock">gtk-justify-fill</property>
+  </object>
+  <object class="GtkWindow" id="gifcurry-window">
+    <property name="width_request">900</property>
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="title">Gifcurry </property>
+    <property name="window_position">mouse</property>
+    <property name="default_width">1000</property>
+    <property name="icon">gifcurry-icon.svg</property>
+    <property name="gravity">center</property>
+    <child>
+      <object class="GtkBox" id="app-box">
+        <property name="visible">True</property>
+        <property name="app_paintable">True</property>
+        <property name="can_focus">False</property>
+        <property name="hexpand">True</property>
+        <property name="vexpand">True</property>
+        <property name="orientation">vertical</property>
+        <child>
+          <object class="GtkBox" id="in-file-chooser-button-about-button-box">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <child>
+              <object class="GtkFileChooserButton" id="in-file-chooser-button">
+                <property name="name">in-file-chooser-button</property>
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="tooltip_text" translatable="yes">Where is the video you want to make a GIF from?</property>
+                <property name="hexpand">True</property>
+                <property name="do_overwrite_confirmation">True</property>
+                <property name="title" translatable="yes">Gifcurry - Select an Input Video</property>
+              </object>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="about-button">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="tooltip_text" translatable="yes">What is Gifcurry?</property>
+                <property name="image">about-image</property>
+                <property name="always_show_image">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="pack_type">end</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox" id="sidebar-controls-preview-box">
+            <property name="visible">True</property>
+            <property name="app_paintable">True</property>
+            <property name="can_focus">False</property>
+            <property name="hexpand">True</property>
+            <property name="vexpand">True</property>
+            <child>
+              <object class="GtkBox" id="sidebar-controls-box">
+                <property name="width_request">400</property>
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="vexpand">True</property>
+                <property name="orientation">vertical</property>
+                <child>
+                  <object class="GtkToggleButton" id="width-quality-percent-toggle-button">
+                    <property name="label" translatable="yes"> Width &amp; Quality</property>
+                    <property name="name">width-quality-percent-toggle-button</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="focus_on_click">False</property>
+                    <property name="receives_default">True</property>
+                    <property name="image">width-toggle-button-image</property>
+                    <property name="always_show_image">True</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkBox" id="width-quality-percent-box">
+                    <property name="can_focus">False</property>
+                    <property name="homogeneous">True</property>
+                    <child>
+                      <object class="GtkSpinButton" id="width-size-spin-button">
+                        <property name="name">width-size-spin-button</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="tooltip_text" translatable="yes">How wide is the GIF?</property>
+                        <property name="text" translatable="yes">500</property>
+                        <property name="progress_fraction">0.13</property>
+                        <property name="primary_icon_stock">gtk-justify-fill</property>
+                        <property name="primary_icon_activatable">False</property>
+                        <property name="secondary_icon_activatable">False</property>
+                        <property name="secondary_icon_sensitive">False</property>
+                        <property name="placeholder_text" translatable="yes">Width Size (pixels)</property>
+                        <property name="input_purpose">number</property>
+                        <property name="adjustment">width-size-adjustment</property>
+                        <property name="climb_rate">2</property>
+                        <property name="snap_to_ticks">True</property>
+                        <property name="numeric">True</property>
+                        <property name="update_policy">if-valid</property>
+                        <property name="value">500</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">2</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkSpinButton" id="quality-percent-spin-button">
+                        <property name="name">quality-percent-spin-button</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="tooltip_text" translatable="yes">What is the quality?</property>
+                        <property name="max_length">3</property>
+                        <property name="width_chars">3</property>
+                        <property name="text" translatable="yes">100</property>
+                        <property name="progress_fraction">1</property>
+                        <property name="primary_icon_stock">gtk-select-color</property>
+                        <property name="primary_icon_activatable">False</property>
+                        <property name="secondary_icon_activatable">False</property>
+                        <property name="secondary_icon_sensitive">False</property>
+                        <property name="placeholder_text" translatable="yes">Quality Percent</property>
+                        <property name="input_purpose">number</property>
+                        <property name="adjustment">quality-percent-adjustment</property>
+                        <property name="climb_rate">2</property>
+                        <property name="snap_to_ticks">True</property>
+                        <property name="numeric">True</property>
+                        <property name="update_policy">if-valid</property>
+                        <property name="value">100</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">3</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkSeparator" id="width-quality-crop-separator">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">2</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkToggleButton" id="crop-toggle-button">
+                    <property name="label" translatable="yes"> Crop</property>
+                    <property name="name">crop-toggle-button</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="focus_on_click">False</property>
+                    <property name="receives_default">True</property>
+                    <property name="image">crop-toggle-button-image</property>
+                    <property name="always_show_image">True</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">3</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkBox" id="crop-spin-buttons-box">
+                    <property name="can_focus">False</property>
+                    <property name="orientation">vertical</property>
+                    <child>
+                      <object class="GtkBox" id="left-right-crop-spin-buttons-box">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="homogeneous">True</property>
+                        <child>
+                          <object class="GtkSpinButton" id="left-crop-spin-button">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="tooltip_text" translatable="yes">How much to crop from the left?</property>
+                            <property name="primary_icon_stock">gtk-goto-last</property>
+                            <property name="primary_icon_activatable">False</property>
+                            <property name="secondary_icon_activatable">False</property>
+                            <property name="secondary_icon_sensitive">False</property>
+                            <property name="placeholder_text" translatable="yes">Left Crop</property>
+                            <property name="input_purpose">digits</property>
+                            <property name="adjustment">left-crop-adjustment</property>
+                            <property name="climb_rate">2</property>
+                            <property name="digits">2</property>
+                            <property name="numeric">True</property>
+                            <property name="update_policy">if-valid</property>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">True</property>
+                            <property name="position">0</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkSpinButton" id="right-crop-spin-button">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="tooltip_text" translatable="yes">How much to crop from the right?</property>
+                            <property name="primary_icon_stock">gtk-goto-first</property>
+                            <property name="primary_icon_activatable">False</property>
+                            <property name="secondary_icon_activatable">False</property>
+                            <property name="secondary_icon_sensitive">False</property>
+                            <property name="placeholder_text" translatable="yes">Right Crop</property>
+                            <property name="input_purpose">digits</property>
+                            <property name="adjustment">right-crop-adjustment</property>
+                            <property name="climb_rate">2</property>
+                            <property name="digits">2</property>
+                            <property name="numeric">True</property>
+                            <property name="update_policy">if-valid</property>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">True</property>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkBox" id="top-bottom-crop-spin-buttons-box">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="homogeneous">True</property>
+                        <child>
+                          <object class="GtkSpinButton" id="top-crop-spin-button">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="tooltip_text" translatable="yes">How much to crop from the top?</property>
+                            <property name="primary_icon_stock">gtk-goto-bottom</property>
+                            <property name="primary_icon_activatable">False</property>
+                            <property name="secondary_icon_activatable">False</property>
+                            <property name="secondary_icon_sensitive">False</property>
+                            <property name="placeholder_text" translatable="yes">Top Crop</property>
+                            <property name="input_purpose">digits</property>
+                            <property name="adjustment">top-crop-adjustment</property>
+                            <property name="climb_rate">2</property>
+                            <property name="digits">2</property>
+                            <property name="numeric">True</property>
+                            <property name="update_policy">if-valid</property>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">True</property>
+                            <property name="position">0</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkSpinButton" id="bottom-crop-spin-button">
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="tooltip_text" translatable="yes">How much to crop from the bottom?</property>
+                            <property name="primary_icon_stock">gtk-goto-top</property>
+                            <property name="primary_icon_activatable">False</property>
+                            <property name="secondary_icon_activatable">False</property>
+                            <property name="secondary_icon_sensitive">False</property>
+                            <property name="input_purpose">digits</property>
+                            <property name="adjustment">bottom-crop-adjustment</property>
+                            <property name="climb_rate">2</property>
+                            <property name="digits">2</property>
+                            <property name="numeric">True</property>
+                            <property name="update_policy">if-valid</property>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">True</property>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">4</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkSeparator" id="crop-top-bottom-text-separator">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">6</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkToggleButton" id="top-bottom-text-toggle-button">
+                    <property name="label" translatable="yes"> Top &amp; Bottom Text</property>
+                    <property name="name">top-bottom-text-toggle-button</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="focus_on_click">False</property>
+                    <property name="receives_default">True</property>
+                    <property name="image">top-bottom-text-toggle-button-image</property>
+                    <property name="always_show_image">True</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">7</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkBox" id="top-bottom-text-font-chooser-box">
+                    <property name="can_focus">False</property>
+                    <property name="orientation">vertical</property>
+                    <child>
+                      <object class="GtkEntry" id="top-text-entry">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="tooltip_text" translatable="yes">What text should go on top?</property>
+                        <property name="primary_icon_stock">gtk-italic</property>
+                        <property name="primary_icon_activatable">False</property>
+                        <property name="secondary_icon_activatable">False</property>
+                        <property name="secondary_icon_sensitive">False</property>
+                        <property name="placeholder_text" translatable="yes">Top Text (optional)</property>
+                      </object>
+                      <packing>
+                        <property name="expand">True</property>
+                        <property name="fill">True</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkEntry" id="bottom-text-entry">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="tooltip_text" translatable="yes">What text should go on the bottom?</property>
+                        <property name="primary_icon_stock">gtk-italic</property>
+                        <property name="primary_icon_activatable">False</property>
+                        <property name="secondary_icon_activatable">False</property>
+                        <property name="secondary_icon_sensitive">False</property>
+                        <property name="placeholder_text" translatable="yes">Bottom Text (optional)</property>
+                      </object>
+                      <packing>
+                        <property name="expand">True</property>
+                        <property name="fill">True</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkFontButton" id="font-chooser-button">
+                        <property name="name">font_chooser_button</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">True</property>
+                        <property name="tooltip_text" translatable="yes">What font would you like?</property>
+                        <property name="font">Sans 12</property>
+                        <property name="preview_text">I love making GIFs with Gifcurry!</property>
+                        <property name="title" translatable="yes">Gifcurry - Choose a Font</property>
+                        <property name="use_font">True</property>
+                        <property name="show_style">False</property>
+                        <property name="show_size">False</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">2</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">8</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkSeparator" id="top-bottom-text-save-open-separator">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">9</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkToggleButton" id="save-open-toggle-button">
+                    <property name="label" translatable="yes"> Save &amp; Open</property>
+                    <property name="name">save-open-toggle-button</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="focus_on_click">False</property>
+                    <property name="receives_default">True</property>
+                    <property name="image">save-toggle-button-image</property>
+                    <property name="always_show_image">True</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">10</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkBox" id="save-open-box">
+                    <property name="can_focus">False</property>
+                    <property name="orientation">vertical</property>
+                    <child>
+                      <object class="GtkBox" id="save-as-gif-or-video-box">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="homogeneous">True</property>
+                        <child>
+                          <object class="GtkRadioButton" id="save-as-gif-radio-button">
+                            <property name="label" translatable="yes"> Save as GIF</property>
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="receives_default">False</property>
+                            <property name="tooltip_text" translatable="yes">Save the file as a GIF?</property>
+                            <property name="image">gif-icon-image</property>
+                            <property name="always_show_image">True</property>
+                            <property name="draw_indicator">False</property>
+                            <property name="group">save-as-video-radio-button</property>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">True</property>
+                            <property name="position">0</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkRadioButton" id="save-as-video-radio-button">
+                            <property name="label" translatable="yes"> Save as Video</property>
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="receives_default">False</property>
+                            <property name="tooltip_text" translatable="yes">Save the file as a video?</property>
+                            <property name="image">video-icon-image</property>
+                            <property name="always_show_image">True</property>
+                            <property name="draw_indicator">False</property>
+                            <property name="group">save-as-gif-radio-button</property>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">True</property>
+                            <property name="pack_type">end</property>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkFileChooserButton" id="out-file-chooser-button">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="tooltip_text" translatable="yes">Which folder do you want to save to?</property>
+                        <property name="action">select-folder</property>
+                        <property name="title" translatable="yes">Gifcurry - Pick a Folder</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkEntry" id="out-file-name-entry">
+                        <property name="name">out-file-name-entry</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="tooltip_text" translatable="yes">What is the file name?</property>
+                        <property name="primary_icon_stock">gtk-file</property>
+                        <property name="primary_icon_activatable">False</property>
+                        <property name="placeholder_text" translatable="yes">Output File Name</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">2</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkBox" id="spinner-save-open-box">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="homogeneous">True</property>
+                        <child>
+                          <object class="GtkSpinner" id="save-spinner">
+                            <property name="can_focus">False</property>
+                          </object>
+                          <packing>
+                            <property name="expand">True</property>
+                            <property name="fill">True</property>
+                            <property name="position">0</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkButton" id="save-button">
+                            <property name="label"> Save</property>
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="receives_default">True</property>
+                            <property name="tooltip_text" translatable="yes">Click here to save the file.</property>
+                            <property name="image">save-icon</property>
+                            <property name="always_show_image">True</property>
+                          </object>
+                          <packing>
+                            <property name="expand">True</property>
+                            <property name="fill">True</property>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkButton" id="open-button">
+                            <property name="label" translatable="yes"> Open</property>
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="receives_default">True</property>
+                            <property name="tooltip_text" translatable="yes">Click here to open the file.</property>
+                            <property name="image">open-icon</property>
+                            <property name="always_show_image">True</property>
+                          </object>
+                          <packing>
+                            <property name="expand">True</property>
+                            <property name="fill">True</property>
+                            <property name="pack_type">end</property>
+                            <property name="position">2</property>
+                          </packing>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">3</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">11</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkSeparator" id="save-open-upload-separator">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">12</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkToggleButton" id="upload-toggle-button">
+                    <property name="label" translatable="yes"> Upload</property>
+                    <property name="name">upload-toggle-button</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="focus_on_click">False</property>
+                    <property name="receives_default">True</property>
+                    <property name="image">upload-toggle-button-image</property>
+                    <property name="always_show_image">True</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">13</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkBox" id="upload-box">
+                    <property name="can_focus">False</property>
+                    <child>
+                      <object class="GtkButton" id="giphy-upload-button">
+                        <property name="label" translatable="yes">Giphy</property>
+                        <property name="name">giphy-upload-button</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">True</property>
+                        <property name="tooltip_text" translatable="yes">Click here to upload to Giphy.</property>
+                        <property name="hexpand">True</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkSeparator" id="giphy-imgur-upload-separator">
+                        <property name="visible">True</property>
+                        <property name="sensitive">False</property>
+                        <property name="can_focus">False</property>
+                        <property name="double_buffered">False</property>
+                        <property name="valign">baseline</property>
+                        <property name="orientation">vertical</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkButton" id="imgur-upload-button">
+                        <property name="label" translatable="yes">Imgur</property>
+                        <property name="name">imgur-upload-button</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">True</property>
+                        <property name="tooltip_text" translatable="yes">Click here to upload to Imgur.</property>
+                        <property name="hexpand">True</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="pack_type">end</property>
+                        <property name="position">2</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">14</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkSeparator" id="sidebar-preview-separator">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="orientation">vertical</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkBox" id="preview-start-duration-time-box">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="hexpand">True</property>
+                <property name="vexpand">True</property>
+                <property name="orientation">vertical</property>
+                <child>
+                  <object class="GtkBox" id="main-preview-box">
+                    <property name="width_request">600</property>
+                    <property name="visible">True</property>
+                    <property name="app_paintable">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="hexpand">True</property>
+                    <property name="vexpand">True</property>
+                    <property name="orientation">vertical</property>
+                    <child>
+                      <placeholder/>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">True</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkBox" id="start-duration-time-video-duration-box">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="hexpand">True</property>
+                    <property name="orientation">vertical</property>
+                    <child>
+                      <object class="GtkBox" id="start-duration-time-box">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="homogeneous">True</property>
+                        <child>
+                          <object class="GtkSpinButton" id="start-time-spin-button">
+                            <property name="name">start-time-spin-button</property>
+                            <property name="width_request">150</property>
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="tooltip_text" translatable="yes">What is the start time?</property>
+                            <property name="hexpand">True</property>
+                            <property name="text" translatable="yes">0.00</property>
+                            <property name="truncate_multiline">True</property>
+                            <property name="primary_icon_stock">gtk-indent</property>
+                            <property name="primary_icon_activatable">False</property>
+                            <property name="secondary_icon_activatable">False</property>
+                            <property name="secondary_icon_sensitive">False</property>
+                            <property name="placeholder_text" translatable="yes">Start Time (seconds)</property>
+                            <property name="input_purpose">digits</property>
+                            <property name="adjustment">start-time-adjustment</property>
+                            <property name="climb_rate">1</property>
+                            <property name="digits">2</property>
+                            <property name="numeric">True</property>
+                            <property name="update_policy">if-valid</property>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">True</property>
+                            <property name="position">0</property>
+                          </packing>
+                        </child>
+                        <child>
+                          <object class="GtkSpinButton" id="duration-time-spin-button">
+                            <property name="name">duration-time-spin-button</property>
+                            <property name="width_request">150</property>
+                            <property name="visible">True</property>
+                            <property name="can_focus">True</property>
+                            <property name="tooltip_text" translatable="yes">How long does the GIF run?</property>
+                            <property name="hexpand">True</property>
+                            <property name="text" translatable="yes">0.00</property>
+                            <property name="primary_icon_stock">gtk-refresh</property>
+                            <property name="primary_icon_activatable">False</property>
+                            <property name="secondary_icon_activatable">False</property>
+                            <property name="secondary_icon_sensitive">False</property>
+                            <property name="placeholder_text" translatable="yes">Duration Time (seconds)</property>
+                            <property name="input_purpose">digits</property>
+                            <property name="adjustment">duration-time-adjustment</property>
+                            <property name="climb_rate">1</property>
+                            <property name="digits">2</property>
+                            <property name="numeric">True</property>
+                            <property name="update_policy">if-valid</property>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">True</property>
+                            <property name="pack_type">end</property>
+                            <property name="position">1</property>
+                          </packing>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLevelBar" id="start-time-level-bar">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="hexpand">True</property>
+                        <property name="value">0.029999999999999999</property>
+                        <property name="max_value">0.029999999999999999</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">3</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLevelBar" id="end-time-level-bar">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="hexpand">True</property>
+                        <property name="value">0.01</property>
+                        <property name="max_value">0.01</property>
+                        <property name="inverted">True</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="pack_type">end</property>
+                        <property name="position">4</property>
+                      </packing>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="pack_type">end</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
+                <property name="pack_type">end</property>
+                <property name="position">2</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">True</property>
+            <property name="fill">True</property>
+            <property name="position">2</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox" id="status-box">
+            <property name="visible">True</property>
+            <property name="sensitive">False</property>
+            <property name="can_focus">False</property>
+            <property name="hexpand">True</property>
+            <property name="baseline_position">top</property>
+            <child>
+              <object class="GtkEntry" id="status-entry">
+                <property name="visible">True</property>
+                <property name="sensitive">False</property>
+                <property name="app_paintable">True</property>
+                <property name="can_focus">True</property>
+                <property name="hexpand">True</property>
+                <property name="editable">False</property>
+                <property name="has_frame">False</property>
+                <property name="text" translatable="yes">Ready.</property>
+                <property name="shadow_type">none</property>
+                <property name="caps_lock_warning">False</property>
+                <property name="primary_icon_stock">gtk-info</property>
+                <property name="primary_icon_activatable">False</property>
+                <property name="secondary_icon_activatable">False</property>
+                <property name="primary_icon_sensitive">False</property>
+                <property name="secondary_icon_sensitive">False</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="pack_type">end</property>
+            <property name="position">3</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <child type="titlebar">
+      <placeholder/>
+    </child>
+  </object>
+  <object class="GtkAboutDialog" id="about-dialog">
+    <property name="can_focus">False</property>
+    <property name="title" translatable="yes">Gifcurry - About</property>
+    <property name="window_position">center</property>
+    <property name="destroy_with_parent">True</property>
+    <property name="icon_name">dialog-information</property>
+    <property name="type_hint">dialog</property>
+    <property name="skip_taskbar_hint">True</property>
+    <property name="gravity">center</property>
+    <property name="transient_for">gifcurry-window</property>
+    <property name="attached_to">gifcurry-window</property>
+    <property name="program_name">Gifcurry</property>
+    <property name="version">3.0.0.0</property>
     <property name="copyright" translatable="yes">(C) 2016 David Lettier
 lettier.com</property>
     <property name="website">https://github.com/lettier/gifcurry</property>
diff --git a/src/gui/GuiMisc.hs b/src/gui/GuiMisc.hs
new file mode 100644
--- /dev/null
+++ b/src/gui/GuiMisc.hs
@@ -0,0 +1,66 @@
+{-
+  Gifcurry
+  (C) 2018 David Lettier
+  lettier.com
+-}
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module GuiMisc where
+
+import System.Directory
+import Text.Read
+import Data.Int
+import Data.Maybe
+import Data.Text
+import qualified GI.Gtk
+
+enumToInt32 :: (Enum a, Ord a) => a -> Int32
+enumToInt32 enum = fromIntegral (fromEnum enum) :: Int32
+
+floatToInt32 :: Float -> Int32
+floatToInt32 f = enumToInt32 (round f :: Int)
+
+int32ToDouble :: Int32 -> Double
+int32ToDouble = fromIntegral
+
+int32ToFloat :: Int32 -> Float
+int32ToFloat = fromIntegral
+
+entryGetMaybeFloat :: GI.Gtk.Entry -> IO (Maybe Float)
+entryGetMaybeFloat entry = do
+  text <- Data.Text.strip <$> GI.Gtk.entryGetText entry
+  let containsPeriod = isJust $ Data.Text.find (== '.') text
+  let string = Data.Text.unpack text
+  let maybeFloat = readMaybe (string ++ if containsPeriod then "0" else "") :: Maybe Float
+  return maybeFloat
+
+entryGetFloat :: GI.Gtk.Entry -> Float -> IO Float
+entryGetFloat entry nothing = do
+  maybeFloat <- entryGetMaybeFloat entry
+  return $ fromMaybe nothing maybeFloat
+
+entryGetMaybeInt :: GI.Gtk.Entry -> IO (Maybe Int)
+entryGetMaybeInt entry = do
+  text <- Data.Text.strip <$> GI.Gtk.entryGetText entry
+  let string = Data.Text.unpack text
+  let maybeInt = readMaybe string :: Maybe Int
+  return maybeInt
+
+fileChooserButtonGetString :: GI.Gtk.FileChooserButton -> IO String
+fileChooserButtonGetString =
+  fmap
+      (Data.Text.unpack . Data.Text.strip . Data.Text.pack . fromMaybe "")
+    . GI.Gtk.fileChooserGetFilename
+
+fileChooserButtonGetFilePath :: GI.Gtk.FileChooserButton -> IO String
+fileChooserButtonGetFilePath fileChooserButton = do
+  result <- fileChooserButtonGetString fileChooserButton
+  fileExist <- doesFileExist result
+  return $
+    if fileExist
+      then result
+      else ""
+
+safeDivide :: (Fractional a, Eq a) => a -> a -> Maybe a
+safeDivide n d = if d == 0.0 then Nothing else Just $ n / d
diff --git a/src/gui/GuiPreview.hs b/src/gui/GuiPreview.hs
new file mode 100644
--- /dev/null
+++ b/src/gui/GuiPreview.hs
@@ -0,0 +1,591 @@
+{-
+  Gifcurry
+  (C) 2018 David Lettier
+  lettier.com
+-}
+
+{-# LANGUAGE
+    OverloadedStrings
+  , NamedFieldPuns
+#-}
+
+module GuiPreview where
+
+import GHC.Float
+import System.FilePath
+import System.IO.Temp
+import Data.Int
+import Data.Maybe
+import Data.Text
+import Data.List
+import Data.Bits
+import Data.IORef
+import Data.GI.Base.Properties
+import Control.Monad
+import Control.Concurrent
+import qualified GI.GLib
+import qualified GI.Gtk
+import qualified GI.Gst
+import qualified GI.Cairo
+import qualified GiCairoCairoBridge
+import qualified Graphics.Rendering.Cairo as GRC
+
+import qualified Gifcurry
+  ( gif
+  , GifParams(..)
+  , defaultGifParams
+  )
+import qualified GtkMainSyncAsync (gtkMainSync, gtkMainAsync)
+import qualified GuiRecords as GR
+import GuiMisc
+
+blankPreviewIcon :: String
+blankPreviewIcon = "gtk-discard"
+
+framePreviewDirectoryName :: String
+framePreviewDirectoryName = "gifcurry-frame-previews"
+
+buildVideoPreviewWidgetAndPlaybinElement :: IO (Maybe GI.Gtk.Widget, Maybe GI.Gst.Element)
+buildVideoPreviewWidgetAndPlaybinElement = do
+  maybeGtkSink <- GI.Gst.elementFactoryMake "gtksink" (Just "MultimediaPlayerGtkSink")
+  maybeVideoPreviewWidget <-
+    case maybeGtkSink of
+      Nothing      -> return Nothing
+      Just gtkSink ->
+        Data.GI.Base.Properties.getObjectPropertyObject gtkSink "widget" GI.Gtk.Widget
+  maybePlaybinElement <- GI.Gst.elementFactoryMake "playbin" (Just "MultimediaPlayerPlaybin")
+  case (maybeVideoPreviewWidget, maybePlaybinElement) of
+    (Just videoPreviewWidget, Just playbinElement) -> do
+        -- Turns off the subtitles.
+        let flags = flip setBit 10 $ flip setBit 9 $ flip setBit 4 $ flip setBit 1 $ bit 0
+        _ <- Data.GI.Base.Properties.setObjectPropertyObject playbinElement "video-sink" maybeGtkSink
+        _ <- Data.GI.Base.Properties.setObjectPropertyBool   playbinElement "force-aspect-ratio" True
+        _ <- Data.GI.Base.Properties.setObjectPropertyDouble playbinElement "volume" 0.0
+        _ <- Data.GI.Base.Properties.setObjectPropertyInt    playbinElement "flags" flags
+        _ <- GI.Gtk.widgetShow videoPreviewWidget
+        return ()
+    _ -> return ()
+  return (maybeVideoPreviewWidget, maybePlaybinElement)
+
+runGuiPreview :: GR.GuiComponents -> IO ()
+runGuiPreview
+  guiComponents@GR.GuiComponents
+    { GR.maybeVideoPreviewWidget = (Just videoPreviewWidget)
+    , GR.maybePlaybinElement = (Just _)
+    , GR.mainPreviewBox
+    , GR.videoPreviewBox
+    , GR.videoPreviewOverlayChildBox
+    , GR.videoPreviewDrawingArea
+    }
+  = do
+  mainPreviewBoxChildCount <-
+    Data.List.length <$> GI.Gtk.containerGetChildren mainPreviewBox
+  when (mainPreviewBoxChildCount <= 0) $
+    GI.Gtk.boxPackStart mainPreviewBox videoPreviewBox True True 0
+  videoPreviewOverlayChildBoxChildCount <-
+    Data.List.length <$> GI.Gtk.containerGetChildren videoPreviewOverlayChildBox
+  when (videoPreviewOverlayChildBoxChildCount <= 0) $ do
+    _ <- GI.Gtk.boxPackStart videoPreviewOverlayChildBox videoPreviewWidget True True 0
+    _ <- GI.Gtk.onWidgetDraw
+      videoPreviewDrawingArea
+      (drawCropGrid guiComponents videoPreviewDrawingArea)
+    return ()
+  runPreviewLoopIfNotRunning
+    guiComponents $
+    preview guiComponents videoPreview
+runGuiPreview
+  guiComponents@GR.GuiComponents
+    { GR.mainPreviewBox
+    , GR.imagesPreviewBox
+    , GR.firstFramePreviewImageDrawingArea
+    , GR.lastFramePreviewImageDrawingArea
+    }
+  = do
+  childrenCount <- Data.List.length <$> GI.Gtk.containerGetChildren mainPreviewBox
+  when (childrenCount <= 0) $ do
+    _ <- GI.Gtk.boxPackStart mainPreviewBox imagesPreviewBox True True 0
+    _ <- GI.Gtk.onWidgetDraw
+      firstFramePreviewImageDrawingArea
+      (drawCropGrid guiComponents firstFramePreviewImageDrawingArea)
+    _ <- GI.Gtk.onWidgetDraw
+      lastFramePreviewImageDrawingArea
+      (drawCropGrid guiComponents lastFramePreviewImageDrawingArea)
+    return ()
+  runPreviewLoopIfNotRunning
+    guiComponents $
+    preview guiComponents firstAndLastFramePreview
+
+runPreviewLoopIfNotRunning :: GR.GuiComponents -> IO Bool -> IO ()
+runPreviewLoopIfNotRunning
+  GR.GuiComponents
+    { GR.guiPreviewStateRef }
+  f
+  = do
+  loopRunning <- getLoopRunning
+  unless loopRunning $
+    void $
+      GI.GLib.timeoutAdd
+        GI.GLib.PRIORITY_DEFAULT
+        1
+        f
+  where
+    getLoopRunning :: IO Bool
+    getLoopRunning =
+      readIORef guiPreviewStateRef >>=
+        \ x -> return $ GR.loopRunning x
+
+preview
+  ::  GR.GuiComponents
+  ->  (  GR.GuiComponents
+      -> GR.GuiPreviewState
+      -> String
+      -> Float
+      -> Float
+      -> Float
+      -> Float
+      -> IO ()
+      )
+  ->  IO Bool
+preview
+  guiComponents@GR.GuiComponents
+    { GR.inFileChooserButton
+    , GR.startTimeSpinButton
+    , GR.durationTimeSpinButton
+    , GR.guiPreviewStateRef
+    , GR.inVideoPropertiesRef
+    }
+  f
+  = do
+  GR.InVideoProperties
+    { GR.inVideoWidth
+    , GR.inVideoHeight
+    } <- readIORef inVideoPropertiesRef
+  guiPreviewState <- readIORef guiPreviewStateRef
+  inFilePath      <- fileChooserButtonGetFilePath inFileChooserButton
+  startTime       <- double2Float <$> GI.Gtk.spinButtonGetValue startTimeSpinButton
+  durationTime    <- double2Float <$> GI.Gtk.spinButtonGetValue durationTimeSpinButton
+  let invalidInFilePath    = inFilePath    == ""
+  let invalidStartTime     = startTime     < 0.0
+  let invalidDurationTime  = durationTime  <= 0.0
+  let invalidInVideoWidth  = inVideoWidth  <= 0.0
+  let invalidInVideoHeight = inVideoHeight <= 0.0
+  let inputInvalid =
+           invalidInFilePath
+        || invalidStartTime
+        || invalidDurationTime
+        || invalidInVideoWidth
+        || invalidInVideoHeight
+  if not inputInvalid
+    then
+      f
+        guiComponents
+        guiPreviewState
+        inFilePath
+        startTime
+        durationTime
+        inVideoWidth
+        inVideoHeight
+    else
+      resetWindow guiComponents
+  atomicWriteIORef guiPreviewStateRef
+    guiPreviewState
+      { GR.maybeInFilePath   = if invalidInFilePath   then Nothing else Just inFilePath
+      , GR.maybeStartTime    = if invalidStartTime    then Nothing else Just startTime
+      , GR.maybeDurationTime = if invalidDurationTime then Nothing else Just durationTime
+      , GR.loopRunning = True
+      }
+  return True
+
+videoPreview
+  ::  GR.GuiComponents
+  ->  GR.GuiPreviewState
+  ->  String
+  ->  Float
+  ->  Float
+  ->  Float
+  ->  Float
+  ->  IO ()
+videoPreview
+  GR.GuiComponents
+    { GR.window
+    , GR.cropToggleButton
+    , GR.mainPreviewBox
+    , GR.videoPreviewBox
+    , GR.maybePlaybinElement = (Just playbinElement)
+    }
+  GR.GuiPreviewState
+    { GR.maybeInFilePath
+    , GR.maybeStartTime
+    }
+  inFilePath
+  startTime
+  durationTime
+  inVideoWidth
+  inVideoHeight
+  = do
+  GI.Gtk.widgetShow videoPreviewBox
+  sizePreviewAndWindow
+  handleChanges
+  handleCropMode
+  where
+    sizePreviewAndWindow :: IO ()
+    sizePreviewAndWindow = do
+      let widthRatio  = inVideoWidth  / inVideoHeight
+      let heightRatio = inVideoHeight / inVideoWidth
+      let previewWidth =
+            if inVideoWidth >= inVideoHeight
+              then desiredPreviewSize
+              else desiredPreviewSize * widthRatio
+      let previewHeight =
+            if inVideoWidth < inVideoHeight
+              then desiredPreviewSize
+              else desiredPreviewSize * heightRatio
+      let previewWidth'  = floatToInt32 previewWidth
+      let previewHeight' = floatToInt32 previewHeight
+      GI.Gtk.widgetSetSizeRequest
+        window
+        previewWidth'
+        (-1)
+      GI.Gtk.widgetSetSizeRequest
+        mainPreviewBox
+        previewWidth'
+        previewHeight'
+      resizeWindow window
+    handleChanges :: IO ()
+    handleChanges = do
+      (couldQueryDuration, videoDuration) <-
+        GI.Gst.elementQueryDuration playbinElement GI.Gst.FormatTime
+      (couldQueryPosition, videoPosition) <-
+        GI.Gst.elementQueryPosition playbinElement GI.Gst.FormatTime
+      let endTime = startTime + durationTime
+      let startTimeInNano = secondsToNanoseconds startTime
+      let endTimeInNano   = secondsToNanoseconds endTime
+      let inFilePathChanged = fromMaybe "" maybeInFilePath /= inFilePath
+      let startTimeChanged  = fromMaybe (-1.0) maybeStartTime /= startTime
+      let startOver =
+               (couldQueryDuration && couldQueryPosition && videoDuration > 0)
+            && (  videoPosition >= videoDuration
+               || videoPosition >= endTimeInNano
+               || videoPosition < startTimeInNano
+               )
+      let seekToStart = startTimeChanged || inFilePathChanged || startOver
+      when inFilePathChanged $ do
+        _ <- GI.Gst.elementSetState playbinElement GI.Gst.StateNull
+        Data.GI.Base.Properties.setObjectPropertyString
+          playbinElement
+          "uri"
+          (Just $ pack $ "file://" ++ inFilePath)
+        _ <- Data.GI.Base.Properties.setObjectPropertyDouble playbinElement "volume" 0.0
+        resizeWindow window
+      when seekToStart $ do
+        _ <- GI.Gst.elementSetState playbinElement GI.Gst.StatePaused
+        _ <- GI.Gst.elementSeekSimple
+          playbinElement
+          GI.Gst.FormatTime
+          [GI.Gst.SeekFlagsFlush]
+          startTimeInNano
+        _ <- GI.Gst.elementSetState playbinElement GI.Gst.StatePlaying
+        return ()
+    handleCropMode :: IO ()
+    handleCropMode = do
+      cropModeEnabled <- GI.Gtk.getToggleButtonActive cropToggleButton
+      if cropModeEnabled
+        then
+        void $ Data.GI.Base.Properties.setObjectPropertyBool
+          playbinElement
+          "force-aspect-ratio"
+          False
+        else
+          void $ Data.GI.Base.Properties.setObjectPropertyBool
+            playbinElement
+            "force-aspect-ratio"
+            True
+videoPreview _ _ _ _ _ _ _ = return ()
+
+firstAndLastFramePreview
+  ::  GR.GuiComponents
+  ->  GR.GuiPreviewState
+  ->  String
+  ->  Float
+  ->  Float
+  ->  Float
+  ->  Float
+  ->  IO ()
+firstAndLastFramePreview
+  GR.GuiComponents
+    { GR.window
+    , GR.imagesPreviewBox
+    , GR.firstFrameImage
+    , GR.lastFrameImage
+    , GR.firstFramePreviewImageDrawingArea
+    , GR.lastFramePreviewImageDrawingArea
+    , GR.temporaryDirectory
+    }
+  GR.GuiPreviewState
+    { GR.maybeInFilePath
+    , GR.maybeStartTime
+    , GR.maybeDurationTime
+    }
+  inFilePath
+  startTime
+  durationTime
+  _
+  _
+  = do
+  GI.Gtk.widgetShow imagesPreviewBox
+  handleChanges
+  redrawDrawingAreas
+  where
+    handleChanges :: IO ()
+    handleChanges = do
+      let inFilePathChanged =
+            fromMaybe "" maybeInFilePath /= inFilePath
+      let startTimeChanged =
+            fromMaybe (-1.0) maybeStartTime /= startTime
+      let durationTimeChanged =
+            fromMaybe (-1.0) maybeDurationTime /= durationTime
+      let firstAndLastFrameDirty = inFilePathChanged || startTimeChanged
+      let lastFrameDirty = not firstAndLastFrameDirty && durationTimeChanged
+      when firstAndLastFrameDirty $
+        makeFirstAndLastFramePreview
+          inFilePath
+          startTime
+          durationTime
+          firstFrameImage
+          lastFrameImage
+          temporaryDirectory
+          window
+      when lastFrameDirty $
+        makeLastFramePreview
+          inFilePath
+          startTime
+          durationTime
+          lastFrameImage
+          temporaryDirectory
+          window
+    redrawDrawingAreas :: IO ()
+    redrawDrawingAreas = do
+      GI.Gtk.widgetQueueDraw firstFramePreviewImageDrawingArea
+      GI.Gtk.widgetQueueDraw lastFramePreviewImageDrawingArea
+
+drawCropGrid :: GR.GuiComponents -> GI.Gtk.DrawingArea -> GI.Cairo.Context -> IO Bool
+drawCropGrid
+  GR.GuiComponents
+    { GR.cropToggleButton
+    , GR.leftCropSpinButton
+    , GR.rightCropSpinButton
+    , GR.topCropSpinButton
+    , GR.bottomCropSpinButton
+    , GR.inVideoPropertiesRef
+    }
+  drawingArea
+  context
+  = do
+  GR.InVideoProperties
+    { GR.inVideoWidth
+    , GR.inVideoHeight
+    } <- readIORef inVideoPropertiesRef
+  cropModeEnabled <- GI.Gtk.getToggleButtonActive cropToggleButton
+  drawingAreaWidth <-
+    int32ToDouble <$> GI.Gtk.widgetGetAllocatedWidth drawingArea
+  drawingAreaHeight <-
+    int32ToDouble <$> GI.Gtk.widgetGetAllocatedHeight drawingArea
+  left   <- (/ 100.0) <$> GI.Gtk.spinButtonGetValue leftCropSpinButton
+  right  <- (/ 100.0) <$> GI.Gtk.spinButtonGetValue rightCropSpinButton
+  top    <- (/ 100.0) <$> GI.Gtk.spinButtonGetValue topCropSpinButton
+  bottom <- (/ 100.0) <$> GI.Gtk.spinButtonGetValue bottomCropSpinButton
+  when (cropModeEnabled && inVideoWidth >= 0.0 && inVideoHeight >= 0.0) $
+    GiCairoCairoBridge.renderWithContext context $ do
+      GRC.setSourceRGBA 0.0 0.0 0.0 0.8
+      GRC.setLineWidth 1.0
+      -- Left
+      GRC.rectangle 0.0 0.0 (left * drawingAreaWidth) drawingAreaHeight
+      GRC.fill
+      -- Right
+      GRC.rectangle
+        (drawingAreaWidth - right * drawingAreaWidth)
+        0.0
+        (right * drawingAreaWidth)
+        drawingAreaHeight
+      GRC.fill
+      -- Top
+      GRC.rectangle 0.0 0.0 drawingAreaWidth (top * drawingAreaHeight)
+      GRC.fill
+      -- Bottom
+      GRC.rectangle
+        0.0
+        (drawingAreaHeight - bottom * drawingAreaHeight)
+        drawingAreaWidth
+        (bottom * drawingAreaHeight)
+      GRC.fill
+  return False
+
+resetWindow :: GR.GuiComponents -> IO ()
+resetWindow
+  GR.GuiComponents
+    { GR.window
+    , GR.mainPreviewBox
+    , GR.imagesPreviewBox
+    , GR.videoPreviewBox
+    }
+  = do
+  GI.Gtk.widgetSetSizeRequest
+    window
+    900
+    (-1)
+  GI.Gtk.widgetSetSizeRequest
+    mainPreviewBox
+    (floatToInt32 desiredPreviewSize)
+    (-1)
+  GI.Gtk.widgetHide imagesPreviewBox
+  GI.Gtk.widgetHide videoPreviewBox
+  resizeWindow window
+
+resizeWindow :: GI.Gtk.Window -> IO ()
+resizeWindow window =
+  void $ GI.Gtk.windowResize window 1 1
+
+makeFirstAndLastFramePreview
+  ::  String
+  ->  Float
+  ->  Float
+  ->  GI.Gtk.Image
+  ->  GI.Gtk.Image
+  ->  System.FilePath.FilePath
+  ->  GI.Gtk.Window
+  ->  IO ()
+makeFirstAndLastFramePreview
+  inFilePath
+  startTime
+  durationTime
+  firstFrameImage
+  lastFrameImage
+  temporaryDirectory
+  window
+  = do
+  void $ forkIO $
+    withTempDirectory
+      temporaryDirectory
+      framePreviewDirectoryName $
+        \ tmpDir -> do
+          let outFilePath = tmpDir ++ "/gifcurry-first-frame-preview.gif"
+          let inputValid  = not (Data.List.null inFilePath) && startTime >= 0.0
+          void $
+            setOrResetFramePrevew
+              inputValid
+              inFilePath
+              outFilePath
+              startTime
+              firstFrameImage
+              ""
+              window
+  makeLastFramePreview
+    inFilePath
+    startTime
+    durationTime
+    lastFrameImage
+    temporaryDirectory
+    window
+
+makeLastFramePreview
+  ::  String
+  ->  Float
+  ->  Float
+  ->  GI.Gtk.Image
+  ->  System.FilePath.FilePath
+  ->  GI.Gtk.Window
+  ->  IO ()
+makeLastFramePreview
+  inFilePath
+  startTime
+  durationTime
+  lastFrameImage
+  temporaryDirectory
+  window
+  =
+  void $ forkIO $
+    withTempDirectory
+      temporaryDirectory
+      framePreviewDirectoryName $
+        \ tmpdir -> do
+          let startTime'  = startTime + durationTime
+          let outFilePath = tmpdir ++ "/gifcurry-last-frame-preview.gif"
+          let inputValid  = startTime' > 0.0 && not (Data.List.null inFilePath)
+          void $
+            setOrResetFramePrevew
+              inputValid
+              inFilePath
+              outFilePath
+              startTime'
+              lastFrameImage
+              ""
+              window
+
+setOrResetFramePrevew
+  ::  Bool
+  ->  String
+  ->  String
+  ->  Float
+  ->  GI.Gtk.Image
+  ->  String
+  ->  GI.Gtk.Window
+  ->  IO ()
+setOrResetFramePrevew False _ _ _ image _ window =
+  GtkMainSyncAsync.gtkMainAsync $ do
+    resetImage image
+    resizeWindow window
+setOrResetFramePrevew
+  True
+  inFilePath
+  outFilePath
+  time
+  image
+  overlay
+  window
+  = do
+  result <- makeImagePreview inFilePath outFilePath time overlay
+  case result of
+    Left _ ->
+      void $ updatePreviewFrame "" image False
+    Right filePath ->
+      void $ updatePreviewFrame filePath image True
+  GtkMainSyncAsync.gtkMainAsync $ resizeWindow window
+
+makeImagePreview
+  ::  String
+  ->  String
+  ->  Float
+  ->  String
+  ->  IO (Either IOError String)
+makeImagePreview inputFile outputFile startTime bottomText =
+  Gifcurry.gif $
+    Gifcurry.defaultGifParams
+      { Gifcurry.inputFile      = inputFile
+      , Gifcurry.outputFile     = outputFile
+      , Gifcurry.saveAsVideo    = False
+      , Gifcurry.startTime      = startTime
+      , Gifcurry.durationTime   = 0.001
+      , Gifcurry.widthSize      = 300
+      , Gifcurry.qualityPercent = 50.0
+      , Gifcurry.bottomText     = bottomText
+      }
+
+updatePreviewFrame :: String -> GI.Gtk.Image -> Bool -> IO ()
+updatePreviewFrame filePath image True =
+  GtkMainSyncAsync.gtkMainSync (GI.Gtk.imageSetFromFile image (Just filePath))
+updatePreviewFrame _ image False =
+  GtkMainSyncAsync.gtkMainSync (resetImage image)
+
+resetImage :: GI.Gtk.Image -> IO ()
+resetImage image =
+  GI.Gtk.imageSetFromIconName
+    image
+    (Just $ pack blankPreviewIcon)
+    (enumToInt32 GI.Gtk.IconSizeButton)
+
+secondsToNanoseconds :: Float -> Int64
+secondsToNanoseconds s =
+  fromIntegral (round (s * 1000000000.0) :: Integer) :: Int64
+
+desiredPreviewSize :: Float
+desiredPreviewSize = 600.0
diff --git a/src/gui/GuiRecords.hs b/src/gui/GuiRecords.hs
new file mode 100644
--- /dev/null
+++ b/src/gui/GuiRecords.hs
@@ -0,0 +1,104 @@
+{-
+  Gifcurry
+  (C) 2018 David Lettier
+  lettier.com
+-}
+
+module GuiRecords where
+
+import Data.IORef
+import qualified GI.Gtk
+import GI.Gst
+
+data GuiComponents =
+  GuiComponents
+    { window                            :: GI.Gtk.Window
+    , startTimeSpinButton               :: GI.Gtk.SpinButton
+    , durationTimeSpinButton            :: GI.Gtk.SpinButton
+    , widthSizeSpinButton               :: GI.Gtk.SpinButton
+    , qualityPercentSpinButton          :: GI.Gtk.SpinButton
+    , leftCropSpinButton                :: GI.Gtk.SpinButton
+    , rightCropSpinButton               :: GI.Gtk.SpinButton
+    , topCropSpinButton                 :: GI.Gtk.SpinButton
+    , bottomCropSpinButton              :: GI.Gtk.SpinButton
+    , inFileChooserButton               :: GI.Gtk.FileChooserButton
+    , outFileChooserButton              :: GI.Gtk.FileChooserButton
+    , fontChooserButton                 :: GI.Gtk.FontButton
+    , saveButton                        :: GI.Gtk.Button
+    , openButton                        :: GI.Gtk.Button
+    , yesGtkButton                      :: GI.Gtk.Button
+    , noGtkButton                       :: GI.Gtk.Button
+    , aboutButton                       :: GI.Gtk.Button
+    , giphyUploadButton                 :: GI.Gtk.Button
+    , imgurUploadButton                 :: GI.Gtk.Button
+    , saveAsVideoRadioButton            :: GI.Gtk.RadioButton
+    , widthQualityPercentToggleButton   :: GI.Gtk.ToggleButton
+    , cropToggleButton                  :: GI.Gtk.ToggleButton
+    , topBottomTextToggleButton         :: GI.Gtk.ToggleButton
+    , saveOpenToggleButton              :: GI.Gtk.ToggleButton
+    , uploadToggleButton                :: GI.Gtk.ToggleButton
+    , startTimeAdjustment               :: GI.Gtk.Adjustment
+    , durationTimeAdjustment            :: GI.Gtk.Adjustment
+    , widthSizeAdjustment               :: GI.Gtk.Adjustment
+    , qualityPercentAdjustment          :: GI.Gtk.Adjustment
+    , outFileNameEntry                  :: GI.Gtk.Entry
+    , topTextEntry                      :: GI.Gtk.Entry
+    , bottomTextEntry                   :: GI.Gtk.Entry
+    , statusEntry                       :: GI.Gtk.Entry
+    , mainPreviewBox                    :: GI.Gtk.Box
+    , imagesPreviewBox                  :: GI.Gtk.Box
+    , videoPreviewBox                   :: GI.Gtk.Box
+    , videoPreviewOverlayChildBox       :: GI.Gtk.Box
+    , widthQualityPercentBox            :: GI.Gtk.Box
+    , cropSpinButtonsBox                :: GI.Gtk.Box
+    , topBottomTextFontChooserBox       :: GI.Gtk.Box
+    , saveOpenBox                       :: GI.Gtk.Box
+    , uploadBox                         :: GI.Gtk.Box
+    , videoPreviewDrawingArea           :: GI.Gtk.DrawingArea
+    , firstFramePreviewImageDrawingArea :: GI.Gtk.DrawingArea
+    , lastFramePreviewImageDrawingArea  :: GI.Gtk.DrawingArea
+    , firstFrameImage                   :: GI.Gtk.Image
+    , lastFrameImage                    :: GI.Gtk.Image
+    , longGifGtkMessageDialog           :: GI.Gtk.MessageDialog
+    , aboutDialog                       :: GI.Gtk.AboutDialog
+    , startTimeLevelBar                 :: GI.Gtk.LevelBar
+    , endTimeLevelBar                   :: GI.Gtk.LevelBar
+    , saveSpinner                       :: GI.Gtk.Spinner
+    , maybeVideoPreviewWidget           :: Maybe GI.Gtk.Widget
+    , maybePlaybinElement               :: Maybe GI.Gst.Element
+    , temporaryDirectory                :: FilePath
+    , guiPreviewStateRef                :: IORef GuiPreviewState
+    , inVideoPropertiesRef              :: IORef InVideoProperties
+    }
+
+data GuiPreviewState =
+  GuiPreviewState
+    { maybeInFilePath   :: Maybe String
+    , maybeStartTime    :: Maybe Float
+    , maybeDurationTime :: Maybe Float
+    , loopRunning       :: Bool
+    }
+
+data InVideoProperties =
+  InVideoProperties
+    { inVideoDuration :: Float
+    , inVideoWidth    :: Float
+    , inVideoHeight   :: Float
+    }
+
+defaultGuiPreviewState :: GuiPreviewState
+defaultGuiPreviewState =
+  GuiPreviewState
+    { maybeInFilePath = Nothing
+    , maybeStartTime = Nothing
+    , maybeDurationTime = Nothing
+    , loopRunning = False
+    }
+
+defaultInVideoProperties :: InVideoProperties
+defaultInVideoProperties =
+  InVideoProperties
+    { inVideoDuration = 0.0
+    , inVideoWidth = 0.0
+    , inVideoHeight = 0.0
+    }
diff --git a/src/gui/Main.hs b/src/gui/Main.hs
--- a/src/gui/Main.hs
+++ b/src/gui/Main.hs
@@ -4,336 +4,870 @@
   lettier.com
 -}
 
-{-# LANGUAGE OverloadedStrings #-}
-
-import System.Directory
-import System.Process
-import System.FilePath
-import System.Info
-import System.IO.Temp
-import Text.Read
-import Data.Int
-import Data.Maybe
-import Data.Either
-import Data.Char
-import Data.Text
-import Data.List
-import Control.Monad
-import Control.Concurrent
-import Data.GI.Base
-import GI.GObject
-import qualified GI.Gtk
-
-import Paths_Gifcurry
-import qualified Gifcurry (
-      gif
-    , GifParams(..)
-    , defaultGifParams
-    , gifParamsValid
-    , getVideoDurationInSeconds
-    , findOrCreateTemporaryDirectory
-  )
-import qualified GtkMainSyncAsync (gtkMainSync, gtkMainAsync)
-
-main :: IO ()
-main = do
-  _ <- GI.Gtk.init Nothing
-
-  builder <- buildBuilder
-
-  window                  <- builderGetObject GI.Gtk.Window            builder "gifcurry-window"
-  startTimeEntry          <- builderGetObject GI.Gtk.Entry             builder "start-time-text-entry"
-  durationTimeEntry       <- builderGetObject GI.Gtk.Entry             builder "duration-text-entry"
-  widthEntry              <- builderGetObject GI.Gtk.Entry             builder "width-text-entry"
-  qualityEntry            <- builderGetObject GI.Gtk.Entry             builder "quality-text-entry"
-  topTextEntry            <- builderGetObject GI.Gtk.Entry             builder "top-text-text-entry"
-  bottomTextEntry         <- builderGetObject GI.Gtk.Entry             builder "bottom-text-text-entry"
-  outputFileNameEntry     <- builderGetObject GI.Gtk.Entry             builder "output-file-name-text-entry"
-  statusEntry             <- builderGetObject GI.Gtk.Entry             builder "status-text-entry"
-  inputFileButton         <- builderGetObject GI.Gtk.FileChooserButton builder "input-file-button"
-  outputFilePathButton    <- builderGetObject GI.Gtk.FileChooserButton builder "output-file-path-button"
-  fontChooserButton       <- builderGetObject GI.Gtk.FontButton        builder "font-chooser-button"
-  createButton            <- builderGetObject GI.Gtk.Button            builder "create-button"
-  openButton              <- builderGetObject GI.Gtk.Button            builder "open-button"
-  giphyButton             <- builderGetObject GI.Gtk.Button            builder "giphy-link-button"
-  imgurButton             <- builderGetObject GI.Gtk.Button            builder "imgur-link-button"
-  firstFrameImage         <- builderGetObject GI.Gtk.Image             builder "first-frame-image"
-  lastFrameImage          <- builderGetObject GI.Gtk.Image             builder "last-frame-image"
-  longGifGtkMessageDialog <- builderGetObject GI.Gtk.MessageDialog     builder "long-gif-message-dialog"
-  yesGtkButton            <- builderGetObject GI.Gtk.Button            builder "yes-button"
-  noGtkButton             <- builderGetObject GI.Gtk.Button            builder "no-button"
-  aboutButton             <- builderGetObject GI.Gtk.Button            builder "about-button"
-  aboutDialog             <- builderGetObject GI.Gtk.AboutDialog       builder "about-dialog"
-
-  -- Bug in Glade does not allow setting the link button label.
-  GI.Gtk.buttonSetLabel giphyButton "Giphy"
-  GI.Gtk.buttonSetLabel imgurButton "Imgur"
-
-  GI.Gtk.entrySetText qualityEntry "100"
-  GI.Gtk.entrySetText widthEntry   "500"
-
-  -- Glade does not allow us to use the response ID nicknames so we setup them up programmatically here.
-  GI.Gtk.dialogAddActionWidget longGifGtkMessageDialog yesGtkButton (enumToInt32 GI.Gtk.ResponseTypeYes)
-  GI.Gtk.dialogAddActionWidget longGifGtkMessageDialog noGtkButton  (enumToInt32 GI.Gtk.ResponseTypeNo)
-
-  temporaryDirectory <- Gifcurry.findOrCreateTemporaryDirectory
-
-  _ <- GI.Gtk.onFileChooserButtonFileSet inputFileButton $ do
-    setStatusEntryReady statusEntry
-    GI.Gtk.imageSetFromIconName firstFrameImage
-      (Just $ pack blankPreviewIcon) (enumToInt32 GI.Gtk.IconSizeButton)
-    GI.Gtk.imageSetFromIconName lastFrameImage
-      (Just $ pack blankPreviewIcon) (enumToInt32 GI.Gtk.IconSizeButton)
-    inputFilePathName <- inputFileButtonGetText inputFileButton
-    maybeDuration <- Gifcurry.getVideoDurationInSeconds (
-        Gifcurry.defaultGifParams { Gifcurry.inputFile = inputFilePathName }
-      )
-    let duration = fromMaybe 0.5 maybeDuration - 0.5
-    let durationText = if duration == 0.0 then "" else pack (show duration)
-    let startText = if duration == 0.0 then "" else "0.0"
-    _ <- GI.Gtk.entrySetText startTimeEntry startText
-    _ <- GI.Gtk.entrySetText durationTimeEntry durationText
-    unless (Data.Text.null startText || Data.Text.null durationText) (
-        makeFirstFramePreview
-          inputFileButton
-          startTimeEntry
-          durationTimeEntry
-          firstFrameImage
-          lastFrameImage
-          temporaryDirectory
-      )
-
-  _ <- GI.Gtk.onEditableChanged startTimeEntry $
-    makeFirstFramePreview
-      inputFileButton
-      startTimeEntry
-      durationTimeEntry
-      firstFrameImage
-      lastFrameImage
-      temporaryDirectory
-
-  _ <- GI.Gtk.onEditableChanged durationTimeEntry $
-    makeLastFramePreview
-      inputFileButton
-      startTimeEntry
-      durationTimeEntry
-      lastFrameImage
-      temporaryDirectory
-
-  _ <- GI.Gtk.onWidgetButtonReleaseEvent createButton $ \ _ -> do
-    inputFilePathName <- inputFileButtonGetText inputFileButton
-    startTime         <- entryGetFloat startTimeEntry (-1.0)
-    durationTime      <- entryGetFloat durationTimeEntry (-1.0)
-    widthSize'        <- entryGetFloat widthEntry 0.0
-    let widthSize     = truncate widthSize'
-    qualityPercent    <- entryGetFloat qualityEntry (-1.0)
-    fontChoice        <- GI.Gtk.fontButtonGetFontName fontChooserButton
-    topText           <- GI.Gtk.entryGetText topTextEntry
-    bottomText        <- GI.Gtk.entryGetText bottomTextEntry
-    (_, outputFilePathName) <- assembleOutputFilePathName outputFilePathButton outputFileNameEntry
-    let params = Gifcurry.defaultGifParams {
-        Gifcurry.inputFile      = inputFilePathName
-      , Gifcurry.outputFile     = outputFilePathName
-      , Gifcurry.startTime      = startTime
-      , Gifcurry.durationTime   = durationTime
-      , Gifcurry.widthSize      = widthSize
-      , Gifcurry.qualityPercent = qualityPercent
-      , Gifcurry.fontChoice     = unpack fontChoice
-      , Gifcurry.topText        = unpack topText
-      , Gifcurry.bottomText     = unpack bottomText
-    }
-    paramsValid <- Gifcurry.gifParamsValid params
-    setStatusEntryReady statusEntry
-    if paramsValid
-      then do
-        longGifGtkMessageDialogResponse <- if durationTime >= durationTimeWarningLevel
-                                              then GI.Gtk.dialogRun longGifGtkMessageDialog
-                                              else return (enumToInt32 GI.Gtk.ResponseTypeYes)
-        when (longGifGtkMessageDialogResponse == enumToInt32 GI.Gtk.ResponseTypeYes) $
-          void $ forkOS $ do
-            GtkMainSyncAsync.gtkMainAsync $ GI.Gtk.entrySetText statusEntry "One GIF coming up!"
-            success <- (ioSuccess . Gifcurry.gif) params
-            if not success
-              then GtkMainSyncAsync.gtkMainAsync $ GI.Gtk.entrySetText statusEntry "Did not work. Check your settings."
-              else do
-                _ <- forkOS $ GtkMainSyncAsync.gtkMainAsync $ openGifCommand outputFilePathName
-                GtkMainSyncAsync.gtkMainAsync $ setStatusEntryReady statusEntry
-      else GI.Gtk.entrySetText statusEntry "Settings are wrong."
-    return True
-
-  _ <- GI.Gtk.onWidgetButtonReleaseEvent openButton $ \ _ -> do
-    (_, outputFilePathName) <- assembleOutputFilePathName outputFilePathButton outputFileNameEntry
-    fileExists <- doesFileExist outputFilePathName
-    if fileExists
-      then void $ forkIO $ GtkMainSyncAsync.gtkMainAsync $ openGifCommand outputFilePathName
-      else GI.Gtk.entrySetText statusEntry "GIF does not exist. Check your settings."
-    return True
-
-  _ <- GI.Gtk.onWidgetButtonReleaseEvent aboutButton $ \ _ -> GI.Gtk.dialogRun aboutDialog >> return True
-
-  _ <- GI.Gtk.onDialogResponse longGifGtkMessageDialog (\ _ -> GI.Gtk.widgetHide longGifGtkMessageDialog)
-  _ <- GI.Gtk.onDialogResponse aboutDialog             (\ _ -> GI.Gtk.widgetHide aboutDialog)
-
-  _ <- GI.Gtk.onWidgetDestroy window GI.Gtk.mainQuit
-
-  GI.Gtk.widgetShowAll window
-  GI.Gtk.main
-
-buildBuilder :: IO GI.Gtk.Builder
-buildBuilder = do
-  gladeFile <- getDataFileName "data/gui.glade"
-  GI.Gtk.builderNewFromFile (pack gladeFile)
-
-builderGetObject ::
-  (GI.GObject.GObject b, GI.Gtk.IsBuilder a) =>
-  (Data.GI.Base.ManagedPtr b -> b) ->
-  a ->
-  Prelude.String ->
-  IO b
-builderGetObject objectTypeClass builder objectId =
-  fromJust <$> GI.Gtk.builderGetObject builder (pack objectId) >>=
-  GI.Gtk.unsafeCastTo objectTypeClass
-
-inputFileButtonGetText :: GI.Gtk.FileChooserButton -> IO String
-inputFileButtonGetText inputFileButton = do
-  inputFilePathName <- fileChooserButtonGetText inputFileButton
-  fileExist <- doesFileExist inputFilePathName
-  if fileExist then return inputFilePathName else return ""
-
-durationTimeWarningLevel :: Float
-durationTimeWarningLevel = 10.0
-
-entryGetFloat :: GI.Gtk.Entry -> Float -> IO Float
-entryGetFloat e nothing = fmap (\ t -> fromMaybe nothing (readMaybe (unpack t) :: Maybe Float)) (GI.Gtk.entryGetText e)
-
-assembleOutputFilePathName :: GI.Gtk.FileChooserButton -> GI.Gtk.Entry -> IO (String, String)
-assembleOutputFilePathName outputFilePathButton outputFileNameEntry = do
-  outputFilePath <- fileChooserButtonGetText outputFilePathButton
-  outputFileName <- unpack . Data.Text.strip <$> GI.Gtk.entryGetText outputFileNameEntry
-  let outputGifFileName = outputFileName ++ ".gif"
-  let outputFilePathName =
-        if (not . Data.List.null) outputFilePath
-          then outputFilePath ++ [System.FilePath.pathSeparator] ++ outputGifFileName
-          else outputGifFileName
-  return (outputGifFileName, outputFilePathName)
-
-fileChooserButtonGetText :: GI.Gtk.FileChooserButton -> IO String
-fileChooserButtonGetText =
-  fmap (Data.Text.unpack . Data.Text.strip . Data.Text.pack . fromMaybe "") . GI.Gtk.fileChooserGetFilename
-
-openGifCommand :: String -> IO ()
-openGifCommand outputFilePathName = do
-  fileExists <- doesFileExist outputFilePathName
-  when fileExists $ void (spawnCommand (command ++ "\"" ++ outputFilePathName ++ "\""))
-  where
-    command :: String
-    command = if "linux" `Data.List.isInfixOf` fmap Data.Char.toLower System.Info.os
-                then "xdg-open "
-                else "open "
-
-blankPreviewIcon :: String
-blankPreviewIcon = "gtk-discard"
-
-resetImage :: GI.Gtk.Image -> IO ()
-resetImage image =
-  GI.Gtk.imageSetFromIconName
-    image
-    (Just $ pack blankPreviewIcon)
-    (enumToInt32 GI.Gtk.IconSizeButton)
-
-makeGifPreview :: String -> String -> Float -> String -> IO (Either IOError String)
-makeGifPreview inputFile outputFile startTime bottomText = Gifcurry.gif $ Gifcurry.defaultGifParams {
-      Gifcurry.inputFile      = inputFile
-    , Gifcurry.outputFile     = outputFile
-    , Gifcurry.startTime      = startTime
-    , Gifcurry.durationTime   = 0.001
-    , Gifcurry.widthSize      = 200
-    , Gifcurry.qualityPercent = 50.0
-    , Gifcurry.bottomText     = bottomText
-  }
-
-ioSuccess :: IO (Either IOError String) -> IO Bool
-ioSuccess = fmap isRight
-
-framePreviewDirectoryName :: String
-framePreviewDirectoryName = "gifcurry-frame-previews"
-
-makeLastFramePreview ::
-  GI.Gtk.FileChooserButton ->
-  GI.Gtk.Entry ->
-  GI.Gtk.Entry ->
-  GI.Gtk.Image ->
-  System.FilePath.FilePath ->
-  IO ()
-makeLastFramePreview
-  inputFileButton
-  startTimeEntry
-  durationTimeEntry
-  lastFrameImage
-  temporaryDirectory
-  =
-  void $ forkIO $
-    withTempDirectory temporaryDirectory framePreviewDirectoryName $ \ tmpdir -> do
-      inputFilePathName           <- inputFileButtonGetText inputFileButton
-      startTimeText               <- GI.Gtk.entryGetText startTimeEntry
-      durationTimeText            <- GI.Gtk.entryGetText durationTimeEntry
-      startTime'                  <- entryGetFloat startTimeEntry (-1.0)
-      durationTime                <- entryGetFloat durationTimeEntry 0.001
-      let startTimeTextValid      = (not . Data.List.null) (unpack startTimeText)
-      let durationTimeTextValid   = (not . Data.List.null) (unpack durationTimeText)
-      let startTime               = startTime' + durationTime
-      let outputFilePathName      = tmpdir ++ "/gifcurry-last-frame-preview.gif"
-      let doSet                   = startTimeTextValid &&
-                                    durationTimeTextValid &&
-                                    not (Data.List.null inputFilePathName) &&
-                                    (startTime > 0.0)
-      _ <- setOrResetFramePrevew doSet inputFilePathName outputFilePathName startTime lastFrameImage " LAST  FRAME "
-      return()
-
-makeFirstFramePreview ::
-  GI.Gtk.FileChooserButton ->
-  GI.Gtk.Entry ->
-  GI.Gtk.Entry ->
-  GI.Gtk.Image ->
-  GI.Gtk.Image ->
-  System.FilePath.FilePath ->
-  IO ()
-makeFirstFramePreview
-  inputFileButton
-  startTimeEntry
-  durationTimeEntry
-  firstFrameImage
-  lastFrameImage
-  temporaryDirectory
-  =
-  void $ forkIO $ do
-    withTempDirectory temporaryDirectory framePreviewDirectoryName $ \ tmpDir -> do
-      inputFilePathName       <- inputFileButtonGetText inputFileButton
-      startTime               <- entryGetFloat startTimeEntry (-1.0)
-      let outputFilePathName  = tmpDir ++ "/gifcurry-first-frame-preview.gif"
-      let doSet               = not (Data.List.null inputFilePathName) && (startTime >= 0.0)
-      _ <- setOrResetFramePrevew doSet inputFilePathName outputFilePathName startTime firstFrameImage " FIRST FRAME "
-      return ()
-    makeLastFramePreview
-      inputFileButton
-      startTimeEntry
-      durationTimeEntry
-      lastFrameImage
-      temporaryDirectory
-
-setOrResetFramePrevew :: Bool -> String -> String -> Float -> GI.Gtk.Image -> String -> IO ()
-setOrResetFramePrevew False _ _ _ image _ = GtkMainSyncAsync.gtkMainAsync $ resetImage image
-setOrResetFramePrevew True inputFilePathName outputFilePathName time image overlay = do
-  success <- ioSuccess (makeGifPreview inputFilePathName outputFilePathName time overlay)
-  _ <- updatePreviewFrame outputFilePathName image success
-  return()
-
-updatePreviewFrame :: String -> GI.Gtk.Image -> Bool -> IO ()
-updatePreviewFrame filePathName image True  = GtkMainSyncAsync.gtkMainSync (GI.Gtk.imageSetFromFile image (Just filePathName))
-updatePreviewFrame _            image False = GtkMainSyncAsync.gtkMainSync (resetImage image)
-
-setStatusEntryReady :: GI.Gtk.Entry -> IO ()
-setStatusEntryReady = flip GI.Gtk.entrySetText "Ready."
-
-enumToInt32 :: (Enum a, Ord a) => a -> Int32
-enumToInt32 enum = fromIntegral (fromEnum enum) :: Int32
+{-# LANGUAGE
+    OverloadedStrings
+  , NamedFieldPuns
+#-}
+
+import GHC.Float
+import System.Directory
+import System.FilePath
+import System.Process
+import Text.Printf
+import Data.Maybe
+import Data.Text
+import qualified Data.ByteString.Char8
+import Data.List
+import Data.IORef
+import Data.Word
+import Control.Monad
+import Control.Concurrent
+import Control.Exception
+import Data.GI.Base
+import qualified GI.GLib
+import qualified GI.GObject
+import qualified GI.Gdk
+import qualified GI.Gtk
+import GI.Gst
+
+import Paths_Gifcurry
+import qualified Gifcurry
+  ( gif
+  , GifParams(..)
+  , defaultGifParams
+  , gifParamsValid
+  , getVideoDurationInSeconds
+  , getOutputFileWithExtension
+  , getVideoWidthAndHeight
+  , findOrCreateTemporaryDirectory
+  )
+import qualified GtkMainSyncAsync (gtkMainAsync)
+import GuiMisc
+import qualified GuiRecords as GR
+import qualified GuiPreview
+
+durationTimeWarningLevel :: Float
+durationTimeWarningLevel = 10.0
+
+main :: IO ()
+main = do
+  _ <- GI.Gst.init Nothing
+  _ <- GI.Gtk.init Nothing
+
+  builder <- buildBuilder
+
+  window                            <- builderGetObject GI.Gtk.Window            builder "gifcurry-window"
+  startTimeSpinButton               <- builderGetObject GI.Gtk.SpinButton        builder "start-time-spin-button"
+  durationTimeSpinButton            <- builderGetObject GI.Gtk.SpinButton        builder "duration-time-spin-button"
+  widthSizeSpinButton               <- builderGetObject GI.Gtk.SpinButton        builder "width-size-spin-button"
+  qualityPercentSpinButton          <- builderGetObject GI.Gtk.SpinButton        builder "quality-percent-spin-button"
+  leftCropSpinButton                <- builderGetObject GI.Gtk.SpinButton        builder "left-crop-spin-button"
+  rightCropSpinButton               <- builderGetObject GI.Gtk.SpinButton        builder "right-crop-spin-button"
+  topCropSpinButton                 <- builderGetObject GI.Gtk.SpinButton        builder "top-crop-spin-button"
+  bottomCropSpinButton              <- builderGetObject GI.Gtk.SpinButton        builder "bottom-crop-spin-button"
+  inFileChooserButton               <- builderGetObject GI.Gtk.FileChooserButton builder "in-file-chooser-button"
+  outFileChooserButton              <- builderGetObject GI.Gtk.FileChooserButton builder "out-file-chooser-button"
+  fontChooserButton                 <- builderGetObject GI.Gtk.FontButton        builder "font-chooser-button"
+  saveButton                        <- builderGetObject GI.Gtk.Button            builder "save-button"
+  openButton                        <- builderGetObject GI.Gtk.Button            builder "open-button"
+  yesGtkButton                      <- builderGetObject GI.Gtk.Button            builder "yes-button"
+  noGtkButton                       <- builderGetObject GI.Gtk.Button            builder "no-button"
+  aboutButton                       <- builderGetObject GI.Gtk.Button            builder "about-button"
+  giphyUploadButton                 <- builderGetObject GI.Gtk.Button            builder "giphy-upload-button"
+  imgurUploadButton                 <- builderGetObject GI.Gtk.Button            builder "imgur-upload-button"
+  saveAsVideoRadioButton            <- builderGetObject GI.Gtk.RadioButton       builder "save-as-video-radio-button"
+  widthQualityPercentToggleButton   <- builderGetObject GI.Gtk.ToggleButton      builder "width-quality-percent-toggle-button"
+  cropToggleButton                  <- builderGetObject GI.Gtk.ToggleButton      builder "crop-toggle-button"
+  topBottomTextToggleButton         <- builderGetObject GI.Gtk.ToggleButton      builder "top-bottom-text-toggle-button"
+  saveOpenToggleButton              <- builderGetObject GI.Gtk.ToggleButton      builder "save-open-toggle-button"
+  uploadToggleButton                <- builderGetObject GI.Gtk.ToggleButton      builder "upload-toggle-button"
+  startTimeAdjustment               <- builderGetObject GI.Gtk.Adjustment        builder "start-time-adjustment"
+  durationTimeAdjustment            <- builderGetObject GI.Gtk.Adjustment        builder "duration-time-adjustment"
+  widthSizeAdjustment               <- builderGetObject GI.Gtk.Adjustment        builder "width-size-adjustment"
+  qualityPercentAdjustment          <- builderGetObject GI.Gtk.Adjustment        builder "quality-percent-adjustment"
+  outFileNameEntry                  <- builderGetObject GI.Gtk.Entry             builder "out-file-name-entry"
+  topTextEntry                      <- builderGetObject GI.Gtk.Entry             builder "top-text-entry"
+  bottomTextEntry                   <- builderGetObject GI.Gtk.Entry             builder "bottom-text-entry"
+  statusEntry                       <- builderGetObject GI.Gtk.Entry             builder "status-entry"
+  mainPreviewBox                    <- builderGetObject GI.Gtk.Box               builder "main-preview-box"
+  imagesPreviewBox                  <- builderGetObject GI.Gtk.Box               builder "images-preview-box"
+  videoPreviewBox                   <- builderGetObject GI.Gtk.Box               builder "video-preview-box"
+  videoPreviewOverlayChildBox       <- builderGetObject GI.Gtk.Box               builder "video-preview-overlay-child-box"
+  widthQualityPercentBox            <- builderGetObject GI.Gtk.Box               builder "width-quality-percent-box"
+  cropSpinButtonsBox                <- builderGetObject GI.Gtk.Box               builder "crop-spin-buttons-box"
+  topBottomTextFontChooserBox       <- builderGetObject GI.Gtk.Box               builder "top-bottom-text-font-chooser-box"
+  saveOpenBox                       <- builderGetObject GI.Gtk.Box               builder "save-open-box"
+  uploadBox                         <- builderGetObject GI.Gtk.Box               builder "upload-box"
+  videoPreviewDrawingArea           <- builderGetObject GI.Gtk.DrawingArea       builder "video-preview-drawing-area"
+  firstFramePreviewImageDrawingArea <- builderGetObject GI.Gtk.DrawingArea       builder "first-frame-preview-image-drawing-area"
+  lastFramePreviewImageDrawingArea  <- builderGetObject GI.Gtk.DrawingArea       builder "last-frame-preview-image-drawing-area"
+  firstFrameImage                   <- builderGetObject GI.Gtk.Image             builder "first-frame-image"
+  lastFrameImage                    <- builderGetObject GI.Gtk.Image             builder "last-frame-image"
+  longGifGtkMessageDialog           <- builderGetObject GI.Gtk.MessageDialog     builder "long-gif-message-dialog"
+  aboutDialog                       <- builderGetObject GI.Gtk.AboutDialog       builder "about-dialog"
+  startTimeLevelBar                 <- builderGetObject GI.Gtk.LevelBar          builder "start-time-level-bar"
+  endTimeLevelBar                   <- builderGetObject GI.Gtk.LevelBar          builder "end-time-level-bar"
+  saveSpinner                       <- builderGetObject GI.Gtk.Spinner           builder "save-spinner"
+
+  -- Glade does not allow us to use the response ID nicknames so we setup them up programmatically here.
+  GI.Gtk.dialogAddActionWidget longGifGtkMessageDialog yesGtkButton (enumToInt32 GI.Gtk.ResponseTypeYes)
+  GI.Gtk.dialogAddActionWidget longGifGtkMessageDialog noGtkButton  (enumToInt32 GI.Gtk.ResponseTypeNo)
+
+  (maybeVideoPreviewWidget, maybePlaybinElement) <-
+    GuiPreview.buildVideoPreviewWidgetAndPlaybinElement
+
+  temporaryDirectory <- Gifcurry.findOrCreateTemporaryDirectory
+
+  guiPreviewStateRef <- newIORef GR.defaultGuiPreviewState
+
+  inVideoPropertiesRef <- newIORef GR.defaultInVideoProperties
+
+  let guiComponents =
+        GR.GuiComponents
+          { GR.window                            = window
+          , GR.startTimeSpinButton               = startTimeSpinButton
+          , GR.durationTimeSpinButton            = durationTimeSpinButton
+          , GR.widthSizeSpinButton               = widthSizeSpinButton
+          , GR.qualityPercentSpinButton          = qualityPercentSpinButton
+          , GR.leftCropSpinButton                = leftCropSpinButton
+          , GR.rightCropSpinButton               = rightCropSpinButton
+          , GR.topCropSpinButton                 = topCropSpinButton
+          , GR.bottomCropSpinButton              = bottomCropSpinButton
+          , GR.inFileChooserButton               = inFileChooserButton
+          , GR.outFileChooserButton              = outFileChooserButton
+          , GR.fontChooserButton                 = fontChooserButton
+          , GR.saveButton                        = saveButton
+          , GR.openButton                        = openButton
+          , GR.yesGtkButton                      = yesGtkButton
+          , GR.noGtkButton                       = noGtkButton
+          , GR.aboutButton                       = aboutButton
+          , GR.giphyUploadButton                 = giphyUploadButton
+          , GR.imgurUploadButton                 = imgurUploadButton
+          , GR.saveAsVideoRadioButton            = saveAsVideoRadioButton
+          , GR.widthQualityPercentToggleButton   = widthQualityPercentToggleButton
+          , GR.cropToggleButton                  = cropToggleButton
+          , GR.topBottomTextToggleButton         = topBottomTextToggleButton
+          , GR.saveOpenToggleButton              = saveOpenToggleButton
+          , GR.uploadToggleButton                = uploadToggleButton
+          , GR.startTimeAdjustment               = startTimeAdjustment
+          , GR.durationTimeAdjustment            = durationTimeAdjustment
+          , GR.widthSizeAdjustment               = widthSizeAdjustment
+          , GR.qualityPercentAdjustment          = qualityPercentAdjustment
+          , GR.outFileNameEntry                  = outFileNameEntry
+          , GR.topTextEntry                      = topTextEntry
+          , GR.bottomTextEntry                   = bottomTextEntry
+          , GR.statusEntry                       = statusEntry
+          , GR.mainPreviewBox                    = mainPreviewBox
+          , GR.imagesPreviewBox                  = imagesPreviewBox
+          , GR.videoPreviewBox                   = videoPreviewBox
+          , GR.videoPreviewOverlayChildBox       = videoPreviewOverlayChildBox
+          , GR.widthQualityPercentBox            = widthQualityPercentBox
+          , GR.cropSpinButtonsBox                = cropSpinButtonsBox
+          , GR.topBottomTextFontChooserBox       = topBottomTextFontChooserBox
+          , GR.saveOpenBox                       = saveOpenBox
+          , GR.uploadBox                         = uploadBox
+          , GR.videoPreviewDrawingArea           = videoPreviewDrawingArea
+          , GR.firstFramePreviewImageDrawingArea = firstFramePreviewImageDrawingArea
+          , GR.lastFramePreviewImageDrawingArea  = lastFramePreviewImageDrawingArea
+          , GR.firstFrameImage                   = firstFrameImage
+          , GR.lastFrameImage                    = lastFrameImage
+          , GR.longGifGtkMessageDialog           = longGifGtkMessageDialog
+          , GR.aboutDialog                       = aboutDialog
+          , GR.startTimeLevelBar                 = startTimeLevelBar
+          , GR.endTimeLevelBar                   = endTimeLevelBar
+          , GR.saveSpinner                       = saveSpinner
+          , GR.maybeVideoPreviewWidget           = maybeVideoPreviewWidget
+          , GR.maybePlaybinElement               = maybePlaybinElement
+          , GR.temporaryDirectory                = temporaryDirectory
+          , GR.guiPreviewStateRef                = guiPreviewStateRef
+          , GR.inVideoPropertiesRef              = inVideoPropertiesRef
+          }
+
+  _ <- hideWidgetsOnRealize guiComponents
+
+  _ <- handleInputFileChoice guiComponents
+  _ <- handleSpinButtons guiComponents
+  _ <- handleSaveButtonClick guiComponents
+  _ <- handleOpenButtonClick guiComponents
+  _ <- handleDialogs guiComponents
+  _ <- handleSidebarSectionToggleButtons guiComponents
+  _ <- handleUploadButtons guiComponents
+  _ <- handleWindow guiComponents
+  _ <- handleGuiPreview guiComponents
+
+  GI.Gtk.main
+
+buildBuilder :: IO GI.Gtk.Builder
+buildBuilder = do
+  gladeFile <- getDataFileName "data/gui.glade"
+  GI.Gtk.builderNewFromFile (pack gladeFile)
+
+builderGetObject
+  ::  (GI.GObject.GObject b, GI.Gtk.IsBuilder a)
+  =>  (Data.GI.Base.ManagedPtr b -> b)
+  -> a
+  -> String
+  -> IO b
+builderGetObject objectTypeClass builder objectId =
+  fromJust <$> GI.Gtk.builderGetObject builder (pack objectId) >>=
+  GI.Gtk.unsafeCastTo objectTypeClass
+
+handleInputFileChoice :: GR.GuiComponents -> IO ()
+handleInputFileChoice
+  guiComponents@GR.GuiComponents
+    { GR.inFileChooserButton
+    , GR.startTimeSpinButton
+    , GR.durationTimeSpinButton
+    , GR.leftCropSpinButton
+    , GR.rightCropSpinButton
+    , GR.topCropSpinButton
+    , GR.bottomCropSpinButton
+    , GR.widthSizeSpinButton
+    , GR.qualityPercentSpinButton
+    , GR.topTextEntry
+    , GR.bottomTextEntry
+    , GR.outFileNameEntry
+    , GR.statusEntry
+    , GR.inVideoPropertiesRef
+    }
+  =
+  void $ GI.Gtk.onFileChooserButtonFileSet inFileChooserButton $ do
+    inFilePath <- fileChooserButtonGetFilePath inFileChooserButton
+    maybeVideoDuration <-
+      Gifcurry.getVideoDurationInSeconds
+        Gifcurry.defaultGifParams
+          { Gifcurry.inputFile = inFilePath }
+    maybeWidthHeight <-
+      Gifcurry.getVideoWidthAndHeight
+        Gifcurry.defaultGifParams
+          { Gifcurry.inputFile = inFilePath }
+    case (maybeVideoDuration, maybeWidthHeight) of
+      (Just videoDuration', Just (width, height)) -> do
+        atomicWriteIORef inVideoPropertiesRef $
+          GR.defaultInVideoProperties
+            { GR.inVideoDuration = videoDuration'
+            , GR.inVideoWidth    = width
+            , GR.inVideoHeight   = height
+            }
+        let videoDuration = float2Double videoDuration'
+        let startTimeFraction = 0.25
+        let startTime = videoDuration * startTimeFraction
+        let endTime = startTime * 3
+        let durationTime = endTime - startTime
+        let videoDurationText = Data.Text.pack $ printf "%.3f" videoDuration
+        _ <- updateStartAndDurationTimeSpinButtonRanges guiComponents
+        _ <- GI.Gtk.spinButtonSetValue startTimeSpinButton startTime
+        _ <- GI.Gtk.spinButtonSetValue durationTimeSpinButton durationTime
+        _ <- updateStatusEntryAsync statusEntry 1 $
+          Data.Text.concat
+            [ "That video is about "
+            , videoDurationText
+            , " seconds long."
+            ]
+        return ()
+      _ -> do
+        atomicWriteIORef inVideoPropertiesRef GR.defaultInVideoProperties
+        _ <- updateStartAndDurationTimeSpinButtonRanges guiComponents
+        _ <- GI.Gtk.spinButtonSetValue startTimeSpinButton 0.0
+        _ <- GI.Gtk.spinButtonSetValue durationTimeSpinButton 0.0
+        _ <- updateStatusEntryAsync statusEntry 1 "Is that a video?"
+        return ()
+    syncStartAndDurationTimeSpinButtons guiComponents
+    resetTextEntries
+    resetWidthAndQualityPercentSpinButtons
+    resetCropSpinButtons
+  where
+    resetTextEntries :: IO ()
+    resetTextEntries = do
+      let textEntries =
+            [ topTextEntry
+            , bottomTextEntry
+            , outFileNameEntry
+            ]
+      mapM_
+        (flip GI.Gtk.entrySetText "")
+        textEntries
+    resetWidthAndQualityPercentSpinButtons :: IO ()
+    resetWidthAndQualityPercentSpinButtons = do
+      GI.Gtk.spinButtonSetValue widthSizeSpinButton 500
+      GI.Gtk.spinButtonSetValue qualityPercentSpinButton 100
+    resetCropSpinButtons :: IO ()
+    resetCropSpinButtons = do
+      let spinButtons =
+            [ leftCropSpinButton
+            , rightCropSpinButton
+            , topCropSpinButton
+            , bottomCropSpinButton
+            ]
+      mapM_
+        (flip GI.Gtk.spinButtonSetValue 0.0)
+        spinButtons
+
+handleSpinButtons :: GR.GuiComponents -> IO ()
+handleSpinButtons
+  guiComponents@GR.GuiComponents
+    { GR.startTimeSpinButton
+    , GR.durationTimeSpinButton
+    , GR.widthSizeSpinButton
+    , GR.qualityPercentSpinButton
+    , GR.leftCropSpinButton
+    , GR.rightCropSpinButton
+    , GR.topCropSpinButton
+    , GR.bottomCropSpinButton
+    , GR.statusEntry
+    , GR.inVideoPropertiesRef
+    }
+  = do
+  _ <- GI.Gtk.onSpinButtonValueChanged
+    startTimeSpinButton
+    handleStartTimeSpinButton
+  _ <- GI.Gtk.onSpinButtonValueChanged
+    durationTimeSpinButton
+    handleDurationTimeSpinButton
+  _ <- GI.Gtk.onSpinButtonValueChanged
+    widthSizeSpinButton
+    handleWidthSizeSpinButton
+  _ <- GI.Gtk.onSpinButtonValueChanged
+    qualityPercentSpinButton
+    handleQualityPercentSpinButton
+  _ <- GI.Gtk.onSpinButtonValueChanged
+    leftCropSpinButton
+    (handleCropSpinButton leftCropSpinButton rightCropSpinButton "left")
+  _ <- GI.Gtk.onSpinButtonValueChanged
+    rightCropSpinButton
+    (handleCropSpinButton rightCropSpinButton leftCropSpinButton "right")
+  _ <- GI.Gtk.onSpinButtonValueChanged
+    topCropSpinButton
+    (handleCropSpinButton topCropSpinButton bottomCropSpinButton "top")
+  _ <- GI.Gtk.onSpinButtonValueChanged
+    bottomCropSpinButton
+    (handleCropSpinButton bottomCropSpinButton topCropSpinButton "bottom")
+  return ()
+  where
+    handleStartTimeSpinButton :: IO ()
+    handleStartTimeSpinButton = do
+      startTime <- double2Float <$> GI.Gtk.spinButtonGetValue startTimeSpinButton
+      _ <- setSpinButtonFraction startTimeSpinButton
+      if startTime < 0.0
+        then do
+          GI.Gtk.entrySetText statusEntry "The start time is wrong."
+          highlightSpinButton startTimeSpinButton
+        else do
+          GI.Gtk.entrySetText statusEntry "Ready."
+          unhighlightSpinButton startTimeSpinButton
+      void $ syncStartAndDurationTimeSpinButtons guiComponents
+    handleDurationTimeSpinButton :: IO ()
+    handleDurationTimeSpinButton = do
+      startTime <- double2Float <$> GI.Gtk.spinButtonGetValue startTimeSpinButton
+      durationTime <- double2Float <$> GI.Gtk.spinButtonGetValue durationTimeSpinButton
+      videoDuration <- inVideoDuration
+      _ <- GI.Gtk.setEntryProgressFraction durationTimeSpinButton 0.5
+      _ <- setSpinButtonFraction durationTimeSpinButton
+      if
+           durationTime < 0.0
+        || durationTime > (videoDuration - startTime)
+        || durationTime > videoDuration
+        then do
+          GI.Gtk.entrySetText statusEntry "The duration time is wrong."
+          highlightSpinButton durationTimeSpinButton
+        else do
+          GI.Gtk.entrySetText statusEntry "Ready."
+          unhighlightSpinButton durationTimeSpinButton
+      void $ syncStartAndDurationTimeSpinButtons guiComponents
+    handleWidthSizeSpinButton :: IO ()
+    handleWidthSizeSpinButton = do
+      widthSize <- double2Float <$> GI.Gtk.spinButtonGetValue widthSizeSpinButton
+      _ <- setSpinButtonFraction widthSizeSpinButton
+      if widthSize <= 0.0
+        then do
+          GI.Gtk.entrySetText statusEntry "The width size is wrong."
+          highlightSpinButton widthSizeSpinButton
+        else do
+          GI.Gtk.entrySetText statusEntry "Ready."
+          unhighlightSpinButton widthSizeSpinButton
+    handleQualityPercentSpinButton :: IO ()
+    handleQualityPercentSpinButton = do
+      qualityPercent <- double2Float <$> GI.Gtk.spinButtonGetValue qualityPercentSpinButton
+      _ <- setSpinButtonFraction qualityPercentSpinButton
+      if qualityPercent <= 0.0 || qualityPercent > 100.0
+        then do
+          GI.Gtk.entrySetText statusEntry "The quality percent is wrong."
+          highlightSpinButton qualityPercentSpinButton
+        else do
+          GI.Gtk.entrySetText statusEntry "Ready."
+          unhighlightSpinButton qualityPercentSpinButton
+    handleCropSpinButton :: GI.Gtk.SpinButton -> GI.Gtk.SpinButton -> Text -> IO ()
+    handleCropSpinButton a b t = do
+      cropValue <- double2Float <$> GI.Gtk.spinButtonGetValue a
+      _ <- setSpinButtonFraction a
+      if cropValue < 0.0 || cropValue > 100.0
+        then do
+          GI.Gtk.entrySetText statusEntry $ Data.Text.concat ["The ", t, " crop is wrong."]
+          highlightSpinButton a
+        else do
+          GI.Gtk.entrySetText statusEntry "Ready."
+          unhighlightSpinButton a
+      syncCropSpinButtons a b
+    syncCropSpinButtons :: GI.Gtk.SpinButton -> GI.Gtk.SpinButton -> IO ()
+    syncCropSpinButtons a b = do
+      aValue <- GI.Gtk.spinButtonGetValue a
+      bValue <- GI.Gtk.spinButtonGetValue b
+      when (aValue + bValue >= 100) $ do
+        let newValue  = 100.0 - aValue - 1.0
+        let newValue' = if newValue < 0.0 then 0.0 else newValue
+        void $ GI.Gtk.spinButtonSetValue b newValue'
+      return ()
+    inVideoDuration :: IO Float
+    inVideoDuration =
+      GR.inVideoDuration <$> readIORef inVideoPropertiesRef
+
+handleSaveButtonClick :: GR.GuiComponents -> IO ()
+handleSaveButtonClick
+  GR.GuiComponents
+    { GR.inFileChooserButton
+    , GR.outFileChooserButton
+    , GR.outFileNameEntry
+    , GR.saveButton
+    , GR.openButton
+    , GR.fontChooserButton
+    , GR.saveAsVideoRadioButton
+    , GR.startTimeSpinButton
+    , GR.durationTimeSpinButton
+    , GR.widthSizeSpinButton
+    , GR.qualityPercentSpinButton
+    , GR.leftCropSpinButton
+    , GR.rightCropSpinButton
+    , GR.topCropSpinButton
+    , GR.bottomCropSpinButton
+    , GR.topTextEntry
+    , GR.bottomTextEntry
+    , GR.statusEntry
+    , GR.longGifGtkMessageDialog
+    , GR.saveSpinner
+    }
+  =
+  void $ GI.Gtk.onWidgetButtonReleaseEvent saveButton $ \ _ -> do
+    inFilePath     <- fileChooserButtonGetFilePath inFileChooserButton
+    startTime      <- double2Float <$> GI.Gtk.spinButtonGetValue startTimeSpinButton
+    durationTime   <- double2Float <$> GI.Gtk.spinButtonGetValue durationTimeSpinButton
+    widthSize      <- double2Float <$> GI.Gtk.spinButtonGetValue widthSizeSpinButton
+    qualityPercent <- double2Float <$> GI.Gtk.spinButtonGetValue qualityPercentSpinButton
+    leftCrop       <- double2Float <$> GI.Gtk.spinButtonGetValue leftCropSpinButton
+    rightCrop      <- double2Float <$> GI.Gtk.spinButtonGetValue rightCropSpinButton
+    topCrop        <- double2Float <$> GI.Gtk.spinButtonGetValue topCropSpinButton
+    bottomCrop     <- double2Float <$> GI.Gtk.spinButtonGetValue bottomCropSpinButton
+    fontChoice     <- GI.Gtk.fontButtonGetFontName fontChooserButton
+    topText        <- GI.Gtk.entryGetText topTextEntry
+    bottomText     <- GI.Gtk.entryGetText bottomTextEntry
+    saveAsVideo    <- GI.Gtk.toggleButtonGetActive saveAsVideoRadioButton
+    outFilePath    <- outFileChooserButtonGetFilePath outFileChooserButton outFileNameEntry
+    let params =
+          Gifcurry.defaultGifParams
+            { Gifcurry.inputFile      = inFilePath
+            , Gifcurry.outputFile     = outFilePath
+            , Gifcurry.saveAsVideo    = saveAsVideo
+            , Gifcurry.startTime      = startTime
+            , Gifcurry.durationTime   = durationTime
+            , Gifcurry.widthSize      = truncate widthSize
+            , Gifcurry.qualityPercent = qualityPercent
+            , Gifcurry.fontChoice     = unpack fontChoice
+            , Gifcurry.topText        = unpack topText
+            , Gifcurry.bottomText     = unpack bottomText
+            , Gifcurry.leftCrop       = leftCrop
+            , Gifcurry.rightCrop      = rightCrop
+            , Gifcurry.topCrop        = topCrop
+            , Gifcurry.bottomCrop     = bottomCrop
+            }
+    paramsValid <- Gifcurry.gifParamsValid params
+    GI.Gtk.entrySetText statusEntry "Ready."
+    if paramsValid
+      then do
+        longGifGtkMessageDialogResponse <-
+          if durationTime >= durationTimeWarningLevel
+            then GI.Gtk.dialogRun longGifGtkMessageDialog
+            else return (enumToInt32 GI.Gtk.ResponseTypeYes)
+        when (longGifGtkMessageDialogResponse == enumToInt32 GI.Gtk.ResponseTypeYes) $ do
+          GI.Gtk.widgetSetSensitive saveButton False
+          GI.Gtk.widgetSetSensitive openButton False
+          GI.Gtk.widgetHide saveButton
+          GI.Gtk.widgetShow saveSpinner
+          GI.Gtk.setSpinnerActive saveSpinner True
+          void $ forkOS $ do
+            GtkMainSyncAsync.gtkMainAsync $
+              GI.Gtk.entrySetText statusEntry "One GIF coming up!"
+            result <- Gifcurry.gif params
+            case result of
+              Left _ ->
+                GtkMainSyncAsync.gtkMainAsync $
+                  GI.Gtk.entrySetText statusEntry "Didn't work. Check your settings."
+              Right filePath -> do
+                _ <-
+                  forkOS $
+                    GtkMainSyncAsync.gtkMainAsync $
+                      openLocalFileWithDefaultProgram filePath
+                GtkMainSyncAsync.gtkMainAsync $ GI.Gtk.entrySetText statusEntry "Ready."
+            GtkMainSyncAsync.gtkMainAsync $ do
+              GI.Gtk.setSpinnerActive saveSpinner False
+              GI.Gtk.widgetHide saveSpinner
+              GI.Gtk.widgetShow saveButton
+              GI.Gtk.widgetSetSensitive saveButton True
+              GI.Gtk.widgetSetSensitive openButton True
+      else GI.Gtk.entrySetText statusEntry "The settings are wrong."
+    return True
+
+handleOpenButtonClick :: GR.GuiComponents -> IO ()
+handleOpenButtonClick
+  GR.GuiComponents
+    { GR.openButton
+    , GR.saveAsVideoRadioButton
+    , GR.outFileChooserButton
+    , GR.outFileNameEntry
+    , GR.statusEntry
+    }
+  =
+  void $GI.Gtk.onWidgetButtonReleaseEvent openButton $ \ _ -> do
+    saveAsVideo <- GI.Gtk.toggleButtonGetActive saveAsVideoRadioButton
+    outFilePath <- outFileChooserButtonGetFilePath outFileChooserButton outFileNameEntry
+    let outFilePath' =
+          Gifcurry.getOutputFileWithExtension $
+          Gifcurry.defaultGifParams
+            { Gifcurry.outputFile = outFilePath, Gifcurry.saveAsVideo = saveAsVideo }
+    fileExists <- doesFileExist outFilePath'
+    if fileExists
+      then do
+        GI.Gtk.entrySetText statusEntry "Ready."
+        void $
+          forkIO $
+            GtkMainSyncAsync.gtkMainAsync $
+              openLocalFileWithDefaultProgram outFilePath'
+      else GI.Gtk.entrySetText statusEntry "Couldn't find the file. Check your settings."
+    return True
+
+handleDialogs :: GR.GuiComponents -> IO ()
+handleDialogs
+  GR.GuiComponents
+    { GR.aboutDialog
+    , GR.longGifGtkMessageDialog
+    , GR.aboutButton
+    }
+  = do
+  _ <- GI.Gtk.onWidgetButtonReleaseEvent
+    aboutButton
+    (\ _ -> GI.Gtk.dialogRun aboutDialog >> return True)
+  _ <- GI.Gtk.onDialogResponse
+    longGifGtkMessageDialog
+    (\ _ -> GI.Gtk.widgetHide longGifGtkMessageDialog)
+  _ <- GI.Gtk.onDialogResponse
+    aboutDialog
+    (\ _ -> GI.Gtk.widgetHide aboutDialog)
+  return ()
+
+-- GI.Gtk.Expander does not work.
+-- While it visually hides its children, they are still present
+-- in terms of activation. This prevents the children of
+-- other expanded sections from working.
+-- This workaround emulates the expander functionality.
+handleSidebarSectionToggleButtons :: GR.GuiComponents -> IO ()
+handleSidebarSectionToggleButtons
+  GR.GuiComponents
+    { GR.widthQualityPercentToggleButton
+    , GR.cropToggleButton
+    , GR.topBottomTextToggleButton
+    , GR.saveOpenToggleButton
+    , GR.uploadToggleButton
+    , GR.widthQualityPercentBox
+    , GR.cropSpinButtonsBox
+    , GR.topBottomTextFontChooserBox
+    , GR.saveOpenBox
+    , GR.uploadBox
+    }
+  = do
+  let toggleButtons =
+        [ widthQualityPercentToggleButton
+        , cropToggleButton
+        , topBottomTextToggleButton
+        , saveOpenToggleButton
+        , uploadToggleButton
+        ]
+  let boxes =
+        [ widthQualityPercentBox
+        , cropSpinButtonsBox
+        , topBottomTextFontChooserBox
+        , saveOpenBox
+        , uploadBox
+        ]
+  mapM_
+    (\ (toggleButton, box) ->
+      void $
+        GI.Gtk.onToggleButtonToggled toggleButton $
+          handleOnToggle
+            toggleButton
+            box
+            toggleButtons
+    ) $
+    Data.List.zip toggleButtons boxes
+  where
+    handleOnToggle :: GI.Gtk.ToggleButton -> GI.Gtk.Box -> [GI.Gtk.ToggleButton] -> IO ()
+    handleOnToggle toggleButton box toggleButtons = do
+      active <- GI.Gtk.toggleButtonGetActive toggleButton
+      if active
+        then do
+          GI.Gtk.widgetShow box
+          mapM_
+            (\ x ->
+              isNotToggleButton x >>= \ b -> when b $ GI.Gtk.setToggleButtonActive x False
+            )
+            toggleButtons
+        else
+          GI.Gtk.widgetHide box
+      where
+        isNotToggleButton :: GI.Gtk.ToggleButton -> IO Bool
+        isNotToggleButton x = do
+          a <- GI.Gtk.widgetGetName toggleButton
+          b <- GI.Gtk.widgetGetName x
+          return $ a /= b
+
+handleUploadButtons :: GR.GuiComponents -> IO ()
+handleUploadButtons
+  GR.GuiComponents
+    { GR.giphyUploadButton
+    , GR.imgurUploadButton
+    }
+  =
+  mapM_
+    (\ (button, link) ->
+      GI.Gtk.onButtonClicked button $ openUriWithDefaultProgram link
+    )
+    [ (giphyUploadButton, "https://giphy.com/upload/")
+    , (imgurUploadButton, "https://imgur.com/upload")
+    ]
+
+handleGuiPreview :: GR.GuiComponents -> IO ()
+handleGuiPreview = GuiPreview.runGuiPreview
+
+handleWindow :: GR.GuiComponents -> IO ()
+handleWindow GR.GuiComponents { GR.window } = do
+  -- Setting the window to resizable false causes the video
+  -- to load at its natural size.
+  -- This workaround locks the window size which is required
+  -- to get accurate crop measurements.
+  geometry <- GI.Gdk.newZeroGeometry
+  GI.Gdk.setGeometryMinWidth  geometry 1
+  GI.Gdk.setGeometryMaxWidth  geometry 1
+  GI.Gdk.setGeometryMinHeight geometry 1
+  GI.Gdk.setGeometryMaxHeight geometry 1
+  GI.Gtk.windowSetGeometryHints
+    window
+    (Just window)
+    (Just geometry)
+    [GI.Gdk.WindowHintsMinSize, GI.Gdk.WindowHintsMaxSize]
+  _ <- GI.Gtk.onWidgetDestroy window GI.Gtk.mainQuit
+  GI.Gtk.widgetShowAll window
+
+openLocalFileWithDefaultProgram :: String -> IO ()
+openLocalFileWithDefaultProgram filePath = do
+  fileExists <- doesFileExist filePath
+  when fileExists $ openUriWithDefaultProgram filePath
+
+openUriWithDefaultProgram :: String -> IO ()
+openUriWithDefaultProgram uri = do
+  maybeFileOpenCommand <- determineFileOpenCommand
+  case maybeFileOpenCommand of
+    Just fileOpenCommand ->
+      void $ spawnCommand $ fileOpenCommand ++ " \"" ++ uri ++ "\""
+    _ -> return ()
+  where
+    determineFileOpenCommand :: IO (Maybe String)
+    determineFileOpenCommand = do
+      uname  <- tryReadProcess "uname"   ["-a"]
+      swVers <- tryReadProcess "sw_vers" []
+      ver    <- tryReadProcess "ver"     []
+      let linuxCommand   = "xdg-open"
+      let macCommand     = "open"
+      let windowsCommand = "explorer"
+      case (uname, swVers, ver) of
+        (Right s, _, _) ->
+          case (containsText "darwin" s, containsText "linux" s) of
+            (True, _)   -> return $ Just macCommand
+            (_, True)   -> return $ Just linuxCommand
+            _           -> return Nothing
+        (_, Right s, _) -> returnIf "mac"     s macCommand
+        (_, _, Right s) -> returnIf "windows" s windowsCommand
+        _               -> return Nothing
+      where
+        tryReadProcess :: String -> [String] -> IO (Either IOError String)
+        tryReadProcess process params = try $ readProcess process params []
+        returnIf :: Text -> String -> String -> IO (Maybe String)
+        returnIf needle haystack command =
+          if containsText needle haystack
+            then return $ Just command
+            else return Nothing
+        containsText :: Text -> String -> Bool
+        containsText needle haystack =
+          Data.Text.isInfixOf
+            needle
+            (Data.Text.toLower $ Data.Text.pack haystack)
+
+outFileChooserButtonGetFilePath :: GI.Gtk.FileChooserButton -> GI.Gtk.Entry -> IO String
+outFileChooserButtonGetFilePath outFileChooserButton outFileNameEntry = do
+  filePath <- fileChooserButtonGetString outFileChooserButton
+  fileName <- Data.Text.unpack . Data.Text.strip <$> GI.Gtk.entryGetText outFileNameEntry
+  if Data.List.null filePath
+    then return fileName
+    else do
+      isDirectory <- System.Directory.doesDirectoryExist filePath
+      if isDirectory
+        then return $ System.FilePath.combine filePath fileName
+        else return fileName
+
+highlightSpinButton :: GI.Gtk.SpinButton -> IO ()
+highlightSpinButton = highlightWidget "spinbutton" " entry "
+
+unhighlightSpinButton :: GI.Gtk.SpinButton -> IO ()
+unhighlightSpinButton = unhighlightWidget "spinbutton" " entry "
+
+highlightWidget :: GI.Gtk.IsWidget a => Text -> Text -> a -> IO ()
+highlightWidget namePrefix namePostfix =
+  styleWidget namePrefix namePostfix "{ background-color: #ff4757; color: #f1f2f6; }"
+
+unhighlightWidget :: GI.Gtk.IsWidget a => Text -> Text -> a -> IO ()
+unhighlightWidget namePrefix namePostfix =
+  styleWidget namePrefix namePostfix "{}"
+
+styleWidget :: GI.Gtk.IsWidget a => Text -> Text -> Text -> a -> IO()
+styleWidget namePrefix namePostfix style widget = do
+  name <- GI.Gtk.widgetGetName widget
+  provider <- GI.Gtk.cssProviderGetDefault
+  let css =
+        Data.ByteString.Char8.pack $
+        Data.Text.unpack $
+        Data.Text.concat
+          [ namePrefix
+          , "#"
+          , name
+          , namePostfix
+          , " "
+          , style
+          ]
+  GI.Gtk.cssProviderLoadFromData provider css
+  styleContext <- GI.Gtk.widgetGetStyleContext widget
+  void $
+    GI.Gtk.styleContextAddProvider
+      styleContext
+      provider
+      (fromIntegral GI.Gtk.STYLE_PROVIDER_PRIORITY_USER :: Word32)
+
+updateStatusEntryAsync :: GI.Gtk.Entry -> Word32 -> Text -> IO ()
+updateStatusEntryAsync statusEntry seconds message =
+  void $ GI.GLib.timeoutAdd
+    GI.GLib.PRIORITY_DEFAULT
+    seconds $ do
+      GtkMainSyncAsync.gtkMainAsync $
+        GI.Gtk.entrySetText statusEntry message
+      return False
+
+setSpinButtonFraction :: GI.Gtk.SpinButton -> IO ()
+setSpinButtonFraction spinButton = do
+  (_, maxValue) <- GI.Gtk.spinButtonGetRange spinButton
+  value <- GI.Gtk.spinButtonGetValue spinButton
+  let fraction = if maxValue <= 0.0 then 0.0 else abs $ value / maxValue
+  void $ GI.Gtk.setEntryProgressFraction spinButton fraction
+
+updateStartAndDurationTimeSpinButtonRanges :: GR.GuiComponents -> IO ()
+updateStartAndDurationTimeSpinButtonRanges
+  GR.GuiComponents
+    { GR.startTimeSpinButton
+    , GR.durationTimeSpinButton
+    , GR.inVideoPropertiesRef
+    }
+  = do
+  videoDuration <- float2Double . GR.inVideoDuration <$> readIORef inVideoPropertiesRef
+  let buffer = if videoDuration * 0.01 > 0.1 then 0.1 else videoDuration * 0.01
+  _ <- GI.Gtk.spinButtonSetRange startTimeSpinButton 0.0 (videoDuration - buffer)
+  _ <- GI.Gtk.spinButtonSetRange durationTimeSpinButton buffer videoDuration
+  return ()
+
+updateStartAndDurationTimeSpinButtonFractions :: GR.GuiComponents -> IO ()
+updateStartAndDurationTimeSpinButtonFractions
+  GR.GuiComponents
+    { GR.startTimeSpinButton
+    , GR.durationTimeSpinButton
+    }
+  = do
+  _ <- setSpinButtonFraction startTimeSpinButton
+  _ <- setSpinButtonFraction durationTimeSpinButton
+  return ()
+
+syncStartAndDurationTimeSpinButtons :: GR.GuiComponents -> IO ()
+syncStartAndDurationTimeSpinButtons
+  guiComponents@GR.GuiComponents
+    { GR.startTimeSpinButton
+    , GR.durationTimeSpinButton
+    , GR.inVideoPropertiesRef
+    }
+  = do
+  startTime <- GI.Gtk.spinButtonGetValue startTimeSpinButton
+  durationTime <- GI.Gtk.spinButtonGetValue durationTimeSpinButton
+  videoDuration <- float2Double . GR.inVideoDuration <$> readIORef inVideoPropertiesRef
+  let maxDurationTime =
+        if videoDuration - startTime <= 0.0 then 0.0 else videoDuration - startTime
+  let durationTime' =
+        if durationTime > maxDurationTime then maxDurationTime else durationTime
+  let startTime' = if startTime >= videoDuration then videoDuration else startTime
+  _ <- updateStartAndDurationTimeSpinButtonRanges guiComponents
+  _ <- GI.Gtk.spinButtonSetValue startTimeSpinButton startTime'
+  _ <- GI.Gtk.spinButtonSetValue durationTimeSpinButton durationTime'
+  updateStartAndDurationTimeSpinButtonFractions guiComponents
+  updateStartAndEndTimeLevelBars guiComponents
+
+updateStartAndEndTimeLevelBars :: GR.GuiComponents -> IO ()
+updateStartAndEndTimeLevelBars
+  GR.GuiComponents
+    { GR.startTimeSpinButton
+    , GR.durationTimeSpinButton
+    , GR.startTimeLevelBar
+    , GR.endTimeLevelBar
+    , GR.inVideoPropertiesRef
+    }
+  = do
+  startTime <- GI.Gtk.spinButtonGetValue startTimeSpinButton
+  durationTime <- GI.Gtk.spinButtonGetValue durationTimeSpinButton
+  videoDuration <- float2Double . GR.inVideoDuration <$> readIORef inVideoPropertiesRef
+  let endTime = startTime + durationTime
+  let endTime' = videoDuration - endTime
+  _ <- GI.Gtk.levelBarSetMinValue startTimeLevelBar 0.0
+  _ <- GI.Gtk.levelBarSetMinValue endTimeLevelBar 0.0
+  _ <- GI.Gtk.levelBarSetMaxValue startTimeLevelBar $ checkValue videoDuration
+  _ <- GI.Gtk.levelBarSetMaxValue endTimeLevelBar $ checkValue videoDuration
+  _ <- GI.Gtk.levelBarSetValue startTimeLevelBar $ checkValue startTime
+  _ <- GI.Gtk.levelBarSetValue endTimeLevelBar $ checkValue endTime'
+  return ()
+  where
+    checkValue :: Double -> Double
+    checkValue v = if v <= levelBarBuffer then levelBarBuffer else v
+    levelBarBuffer :: Double
+    levelBarBuffer = 0.03
+
+hideWidgetsOnRealize :: GR.GuiComponents -> IO ()
+hideWidgetsOnRealize
+  GR.GuiComponents
+    { GR.saveSpinner
+    , GR.widthQualityPercentBox
+    , GR.cropSpinButtonsBox
+    , GR.topBottomTextFontChooserBox
+    , GR.saveOpenBox
+    , GR.uploadBox
+    }
+  = do
+  hideOnRealize saveSpinner
+  let boxes =
+        [ widthQualityPercentBox
+        , cropSpinButtonsBox
+        , topBottomTextFontChooserBox
+        , saveOpenBox
+        , uploadBox
+        ]
+  mapM_ hideOnRealize boxes
+  where
+    hideOnRealize :: GI.Gtk.IsWidget a => a -> IO ()
+    hideOnRealize w = void $ GI.Gtk.onWidgetRealize w $ GI.Gtk.widgetHide w
diff --git a/src/lib/Gifcurry.hs b/src/lib/Gifcurry.hs
--- a/src/lib/Gifcurry.hs
+++ b/src/lib/Gifcurry.hs
@@ -4,19 +4,26 @@
   lettier.com
 -}
 
-{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE
+    OverloadedStrings
+  , NamedFieldPuns
+#-}
 
 -- | Produces GIFs using FFmpeg and ImageMagick.
 -- The main function is 'gif'.
-module Gifcurry (
-      gif
-    , GifParams(..)
-    , defaultGifParams
-    , gifParamsValid
-    , versionNumber
-    , getVideoDurationInSeconds
-    , findOrCreateTemporaryDirectory
-  ) where
+module Gifcurry
+  ( gif
+  , GifParams(..)
+  , defaultGifParams
+  , defaultFontChoice
+  , gifParamsValid
+  , versionNumber
+  , getVideoDurationInSeconds
+  , getOutputFileWithExtension
+  , getVideoWidthAndHeight
+  , findOrCreateTemporaryDirectory
+  )
+where
 
 import System.Process
 import System.IO.Temp
@@ -26,14 +33,17 @@
 import Data.Maybe
 import Data.List
 import Data.Text
+import Data.Either
 import Text.Printf
 import Control.Exception
 import Control.Monad
 
 -- | The data type record required by 'gif'.
-data GifParams = GifParams {
-      inputFile :: String
+data GifParams =
+  GifParams
+    { inputFile :: String
     , outputFile :: String
+    , saveAsVideo :: Bool
     , startTime :: Float
     , durationTime :: Float
     , widthSize :: Int
@@ -41,17 +51,24 @@
     , fontChoice :: String
     , topText :: String
     , bottomText :: String
-  } deriving (Show, Read)
+    , leftCrop :: Float
+    , rightCrop :: Float
+    , topCrop :: Float
+    , bottomCrop :: Float
+    }
+  deriving (Show, Read)
 
 -- | The version number.
 versionNumber :: String
-versionNumber = "2.3.0.0"
+versionNumber = "3.0.0.0"
 
 -- | Specifies default parameters for 'startTime', 'durationTime', 'widthSize', 'qualityPercent', and 'fontChoice'.
 defaultGifParams :: GifParams
-defaultGifParams = GifParams {
-      inputFile = ""
+defaultGifParams =
+  GifParams
+    { inputFile = ""
     , outputFile = ""
+    , saveAsVideo = False
     , startTime = 0.0
     , durationTime = 1.0
     , widthSize = 500
@@ -59,7 +76,11 @@
     , fontChoice = defaultFontChoice
     , topText = ""
     , bottomText = ""
-  }
+    , leftCrop = 0.0
+    , rightCrop = 0.0
+    , topCrop = 0.0
+    , bottomCrop = 0.0
+    }
 
 -- | Inputs 'GifParams' and outputs either an IO IOError or IO String.
 --
@@ -76,68 +97,135 @@
 --        else return ()
 -- @
 gif :: GifParams -> IO (Either IOError String)
-gif gifParams = do
+gif
+  gifParams@GifParams { saveAsVideo }
+  = do
   temporaryDirectory <- findOrCreateTemporaryDirectory
-  withTempDirectory temporaryDirectory "gifcurry-frames" $ \tmpdir -> do
-    printGifParams gifParams tmpdir
-    validParams <- gifParamsValid gifParams
-    if validParams
-      then do
-        fFMpegResult <- tryFfmpeg gifParams tmpdir
-        let fFMpegSuccess = eitherBool fFMpegResult
-        if fFMpegSuccess
-          then do
-            fontMatch <- getFontMatch gifParams
-            let gifParams' = gifParams { fontChoice = fontMatch }
-            putStrLn $ "Writing your GIF to: " ++
-                       outputFile gifParams
-            convertResult <- tryConvert gifParams' tmpdir
-            let convertSuccess = eitherBool convertResult
-            if convertSuccess
-              then putStrLn "Done."
-              else putStrLn "[Error] Something went wrong with ImageMagick."
-            return convertResult
-          else do
-            putStrLn "[Error] Something went wrong with FFmpeg."
-            return fFMpegResult
-      else return $ Left (userError "[Error] Invalid params.")
+  withTempDirectory temporaryDirectory "gifcurry-frames" $ \ tempDir ->
+        handleFrameExtraction tempDir
+    >>= handleFrameMerge tempDir
+    >>= handleGifToVideoConversion
   where
+    handleFrameExtraction :: String -> IO (Either IOError Float)
+    handleFrameExtraction tempDir = do
+      printGifParams gifParams
+      validParams <- gifParamsValid gifParams
+      if validParams
+        then do
+          frameRate <-
+            validateAndAdjustFrameRate gifParams <$>
+              getVideoAverageFrameRateInSeconds gifParams
+          result <- extractFrames gifParams tempDir frameRate
+          case result of
+            Left x -> do
+              putStrLn "[ERROR] Something went wrong with FFmpeg."
+              return $ Left x
+            Right _ -> return $ Right frameRate
+        else return $ Left $ userError "Invalid params."
+    handleFrameMerge :: String -> Either IOError Float -> IO (Either IOError String)
+    handleFrameMerge tempDir (Right frameRate) = do
+      fontMatch <- getFontMatch gifParams
+      let gifParams' = gifParams { fontChoice = fontMatch }
+      result <- mergeFramesIntoGif gifParams' tempDir frameRate
+      case result of
+        Left x -> do
+          putStrLn "[ERROR] Something went wrong with ImageMagick."
+          return $ Left x
+        Right gifFilePath -> return $ Right gifFilePath
+    handleFrameMerge _ (Left x) = return $ Left x
+    handleGifToVideoConversion :: Either IOError String -> IO (Either IOError String)
+    handleGifToVideoConversion (Right gifFilePath) =
+      if saveAsVideo
+        then do
+          result <- convertGifToVideo gifParams gifFilePath
+          case result of
+            Left x -> do
+              putStrLn "[ERROR] Something went wrong with FFmpeg."
+              return $ Left x
+            Right outputFileWithExtension -> do
+              putStrLn "[INFO] All done."
+              return $ Right outputFileWithExtension
+        else do
+          putStrLn "[INFO] All done."
+          return $ Right gifFilePath
+    handleGifToVideoConversion result@(Left _) = return result
     getFontMatch :: GifParams -> IO String
     getFontMatch GifParams { topText = "", bottomText = "" } = defaultFontMatch
     getFontMatch gifParams' = do
       fontNames <- getListOfFontNames
       let match = bestFontNameMatch (fontChoiceOrDefault gifParams') fontNames
-      putStrLn $ "Font matched: " ++ match
+      putStrLn $ "[INFO] Your font choice matched to \"" ++ match ++ "\"."
       return match
     defaultFontMatch :: IO String
-    defaultFontMatch = putStrLn "Using the default font." >> return defaultFontChoice
-    eitherBool :: Either a b -> Bool
-    eitherBool = either (const False) (const True)
+    defaultFontMatch = putStrLn "[INFO] Using the default font." >> return defaultFontChoice
 
--- | Outputs True or False if the parameters in the GifParams record are valid.
--- Looks at 'inputFile', 'outputFile', 'startTime', 'durationTime', 'widthSize', and 'qualityPercent'.
+-- | Outputs `True` or `False` if the parameters in the `GifParams` record are valid.
 gifParamsValid :: GifParams -> IO Bool
-gifParamsValid GifParams {
-      inputFile = ipf
-    , outputFile = opf
-    , startTime = st
-    , durationTime = dt
-    , widthSize = ws
-    , qualityPercent = qp
-    , topText = _
-    , bottomText = _
-  } = do
-    inputFileExists <- case Prelude.length ipf of
+gifParamsValid
+  GifParams
+    { inputFile
+    , outputFile
+    , startTime
+    , durationTime
+    , widthSize
+    , qualityPercent
+    , leftCrop
+    , rightCrop
+    , topCrop
+    , bottomCrop
+    }
+  = do
+  inputFileExists <-
+    case Prelude.length inputFile of
       0 -> return False
-      _ -> doesFileExist ipf
-    unless inputFileExists $ putStrLn "\n[Error] Input video file does not exist."
-    let fileNameValid = (not . Data.Text.null . Data.Text.strip . Data.Text.pack . System.FilePath.takeBaseName) opf
-    let fileNameExtValid = ".gif" == (Data.Text.toLower . Data.Text.pack . System.FilePath.takeExtension) opf
-    let outputFileValid = fileNameValid && fileNameExtValid
-    unless outputFileValid $ putStrLn "\n[Error] Output GIF file is invalid."
-    let valid = inputFileExists && outputFileValid && (st >= 0.0) && (dt > 0.0) && (ws > 0) && (qp > 0.0)
-    unless valid $ putStrLn "\n[Error] Invalid params."
-    return valid
+      _ -> doesFileExist inputFile
+  let widthSize' = fromIntegral widthSize :: Float
+  let outputFileValid = not $ Data.Text.null $ Data.Text.strip $ Data.Text.pack outputFile
+  let startTimeValid = startTime >= 0.0
+  let durationTimeValid = durationTime > 0.0
+  let widthSizeValid = widthSize >= 1
+  let qualityPercentValid = qualityPercent >= 1.0 && qualityPercent <= 100.0
+  let leftCropValid      = cropValid leftCrop
+  let rightCropValid     = cropValid rightCrop
+  let topCropValid       = cropValid topCrop
+  let bottomCropValid    = cropValid bottomCrop
+  let leftRightCropValid = cropValid (leftCrop + rightCrop)
+  let topBottomCropValid = cropValid (topCrop + bottomCrop)
+  let widthLeftRightCropSizeValid =
+        (widthSize' - (widthSize' * (leftCrop / 100.0)) - (widthSize' * (rightCrop / 100.0))) >= 1.0
+  unless inputFileExists              $ printError   "Input video file does not exist."
+  unless outputFileValid              $ printInvalid "Output File"
+  unless startTimeValid               $ printInvalid "Start Time"
+  unless durationTimeValid            $ printInvalid "Duration Time"
+  unless widthSizeValid               $ printInvalid "Width Size"
+  unless qualityPercentValid          $ printInvalid "Quality Percent"
+  unless leftCropValid                $ printInvalid "Left Crop"
+  unless rightCropValid               $ printInvalid "Right Crop"
+  unless topCropValid                 $ printInvalid "Top Crop"
+  unless bottomCropValid              $ printInvalid "Bottom Crop"
+  unless leftRightCropValid           $ printInvalid "Left and Right Crop"
+  unless topBottomCropValid           $ printInvalid "Top and Bottom Crop"
+  unless widthLeftRightCropSizeValid  $ printError   "Width Size too small with Left and Right Crop."
+  let valid =
+           inputFileExists
+        && outputFileValid
+        && startTimeValid
+        && durationTimeValid
+        && widthSizeValid
+        && qualityPercentValid
+        && leftCropValid
+        && rightCropValid
+        && topCropValid
+        && bottomCropValid
+        && widthLeftRightCropSizeValid
+  return valid
+  where
+    cropValid :: Float -> Bool
+    cropValid c = c >= 0.0 && c <= 100.0
+    printInvalid :: String -> IO ()
+    printInvalid s = printError $ s ++ " is invalid."
+    printError :: String -> IO ()
+    printError s = putStrLn $ "[ERROR] " ++ s
 
 -- | Returns the duration of the video in seconds if successful.
 --
@@ -151,12 +239,71 @@
 --                      Just float -> float
 -- @
 getVideoDurationInSeconds :: GifParams -> IO (Maybe Float)
-getVideoDurationInSeconds gifParams = tryFfprobe gifParams >>= result
+getVideoDurationInSeconds GifParams { inputFile } = tryFfprobe params >>= result
   where
     result :: Either IOError String -> IO (Maybe Float)
     result (Left _)               = return Nothing
     result (Right durationString) = return (readMaybe durationString :: Maybe Float)
+    params :: [String]
+    params =
+      [ "-i"
+      , inputFile
+      , "-v"
+      , "quiet"
+      , "-show_entries"
+      , "format=duration"
+      , "-of"
+      , "default=noprint_wrappers=1:nokey=1"
+      ]
 
+-- | Returns the width and height of the video if successful.
+-- If the width and/or height of the video is <= 0, it will
+-- return nothing.
+getVideoWidthAndHeight :: GifParams -> IO (Maybe (Float, Float))
+getVideoWidthAndHeight GifParams { inputFile } = tryFfprobe params >>= result
+  where
+    result :: Either IOError String -> IO (Maybe (Float, Float))
+    result (Left _)                  = return Nothing
+    result (Right widthHeightString) =
+      case (maybeWidth, maybeHeight) of
+        (Just width, Just height) ->
+          if width >= 0.0 && height > 0.0
+            then return $ Just (width, height)
+            else return Nothing
+        _                         -> return Nothing
+      where
+        maybeWidth :: Maybe Float
+        maybeWidth =
+          case widthHeightTexts of
+            (widthText:_) -> maybeFloat widthText
+            _             -> Nothing
+        maybeHeight :: Maybe Float
+        maybeHeight =
+          case widthHeightTexts of
+            (_:heightText:_) -> maybeFloat heightText
+            _                -> Nothing
+        maybeFloat :: Text -> Maybe Float
+        maybeFloat t = readMaybe (Data.Text.unpack t) :: Maybe Float
+        widthHeightTexts :: [Text]
+        widthHeightTexts =
+          (Data.List.map Data.Text.strip . Data.Text.lines) widthHeightText
+        widthHeightText :: Text
+        widthHeightText =
+          Data.Text.strip $ Data.Text.pack widthHeightString
+    params :: [String]
+    params =
+      [ "-i"
+      , inputFile
+      , "-v"
+      , "error"
+      , "-select_streams"
+      , "v:0"
+      , "-show_entries"
+      , "stream=width,height"
+      , "-of"
+      , "default=noprint_wrappers=1:nokey=1"
+      ]
+
 -- | Finds or creates the temporary directory for Gifcurry.
 -- This directory is used for storing temporary frames.
 findOrCreateTemporaryDirectory :: IO FilePath
@@ -165,121 +312,284 @@
   System.Directory.createDirectoryIfMissing True filePath
   return filePath
 
-printGifParams :: GifParams -> String -> IO ()
+-- | Adds the proper file extension to the 'outputFile' depending on 'saveAsVideo'.
+getOutputFileWithExtension :: GifParams -> String
+getOutputFileWithExtension GifParams { outputFile, saveAsVideo } =
+      outputFile
+  ++  "."
+  ++  (if saveAsVideo then videoExtension else gifExtension)
+
+-- | Returns the default font choice used if no font choice is specified.
+defaultFontChoice :: String
+defaultFontChoice = "sans"
+
+gifOutputFile :: String -> String
+gifOutputFile outputFile =
+  getOutputFileWithExtension $
+  defaultGifParams { outputFile = outputFile, saveAsVideo = False }
+
+videoOutputFile :: String -> String
+videoOutputFile outputFile =
+  getOutputFileWithExtension $
+  defaultGifParams { outputFile = outputFile, saveAsVideo = True }
+
+defaultFrameRate :: Float
+defaultFrameRate = 12.0
+
+validateAndAdjustFrameRate :: GifParams -> Maybe Float -> Float
+validateAndAdjustFrameRate gifParams =
+  frameRateBasedOnQualityPercent gifParams . maybeFrameRateOrDefaultFrameRate
+
+maybeFrameRateOrDefaultFrameRate :: Maybe Float -> Float
+maybeFrameRateOrDefaultFrameRate (Just frameRate) =
+  if frameRate <= defaultFrameRate then defaultFrameRate else frameRate
+maybeFrameRateOrDefaultFrameRate Nothing = defaultFrameRate
+
+frameRateBasedOnQualityPercent :: GifParams -> Float -> Float
+frameRateBasedOnQualityPercent GifParams { qualityPercent } frameRate =
+  if result <= defaultFrameRate then defaultFrameRate else result
+  where
+    result :: Float
+    result = frameRate * (qualityPercent / 100.0)
+
+getVideoAverageFrameRateInSeconds :: GifParams -> IO (Maybe Float)
+getVideoAverageFrameRateInSeconds GifParams { inputFile } = tryFfprobe params >>= result
+  where
+    result :: Either IOError String -> IO (Maybe Float)
+    result (Left _)                   = return Nothing
+    result (Right avgFrameRateString) = return $ processString avgFrameRateString
+      where
+        processString :: String -> Maybe Float
+        processString =
+          divideMaybeFloats . textsToMaybeFloats . filterNullTexts . splitText . cleanString
+        cleanString :: String -> Text
+        cleanString = Data.Text.strip . Data.Text.pack
+        splitText :: Text -> [Text]
+        splitText = Data.Text.split (== '/')
+        filterNullTexts :: [Text] -> [Text]
+        filterNullTexts = Data.List.filter (not . Data.Text.null)
+        textsToMaybeFloats :: [Text] -> [Maybe Float]
+        textsToMaybeFloats =
+            Data.List.filter isJust
+          . Data.List.map (\ s -> readMaybe (Data.Text.unpack s) :: Maybe Float)
+        divideMaybeFloats :: [Maybe Float] -> Maybe Float
+        divideMaybeFloats (Just n:Just d:_) =
+          if d <= 0 || n <= 0 then Nothing else Just $ n / d
+        divideMaybeFloats _ = Nothing
+    params :: [String]
+    params =
+      [ "-v"
+      , "error"
+      , "-select_streams"
+      , "v:0"
+      , "-show_entries"
+      , "stream=avg_frame_rate"
+      , "-of"
+      , "default=noprint_wrappers=1:nokey=1"
+      , inputFile
+      ]
+
+tryFfprobe :: [String] -> IO (Either IOError String)
+tryFfprobe params = try $ readProcess "ffprobe" params []
+
+printGifParams :: GifParams -> IO ()
 printGifParams
-  GifParams {
-      inputFile = ipf
-    , outputFile = opf
-    , startTime = st
-    , durationTime = dt
-    , widthSize = ws
-    , qualityPercent = qp
-    , fontChoice = fc
-    , topText = tt
-    , bottomText = bt
-  }
-  tmpdir = mapM_ putStrLn [
-        "\n----------------------------------------\n"
-      , "Input file: " ++ ipf
-      , "Output file: " ++ opf
-      , "Start second: " ++ printf "%.3f" st
-      , "Duration: " ++ printf "%.3f" dt ++ " seconds"
-      , "GIF width: " ++ show ws ++ "px"
-      , "Quality: " ++ show (qualityPercentClamp qp) ++ "%"
-      , "Font Choice: " ++ fc
-      , "Top text: " ++ tt
-      , "Bottom text: " ++ bt
+  gifParams@GifParams
+    { inputFile
+    , saveAsVideo
+    , startTime
+    , durationTime
+    , widthSize
+    , qualityPercent
+    , fontChoice
+    , topText
+    , bottomText
+    , leftCrop
+    , rightCrop
+    , topCrop
+    , bottomCrop
+    }
+  =
+  putStrLn $
+    Prelude.unlines
+      [ "[INFO] Here are your settings."
       , ""
-      , "Writing temporary frames to: " ++ tmpdir
-    ]
+      , "FILE IO:"
+      , ""
+      , "Input File: " ++ inputFile
+      , "Output File: " ++ getOutputFileWithExtension gifParams
+      , "Save As Video: " ++ if saveAsVideo then "Yes" else "No"
+      , ""
+      , "TIME:"
+      , ""
+      , "Start Second: " ++ printFloat startTime
+      , "Duration Time: " ++ printFloat durationTime ++ " seconds"
+      , ""
+      , "OUTPUT FILE SIZE:"
+      , ""
+      , "Width Size: " ++ show widthSize ++ "px"
+      , "Quality Percent: " ++ show (qualityPercentClamp qualityPercent) ++ "%"
+      , ""
+      , "TEXT:"
+      , ""
+      , "Font Choice: " ++ fontChoice
+      , "Top Text: " ++ topText
+      , "Bottom Text: " ++ bottomText
+      , ""
+      , "CROP:"
+      , ""
+      , "Left Crop: " ++ printFloat leftCrop
+      , "Right crop: " ++ printFloat rightCrop
+      , "Top Crop: " ++ printFloat topCrop
+      , "Bottom Crop: " ++ printFloat bottomCrop
+      ]
+  where
+    printFloat :: Float -> String
+    printFloat = printf "%.3f"
 
 frameFileExtension :: String
 frameFileExtension = "png"
 
-tryFfmpeg :: GifParams -> String -> IO (Either IOError String)
-tryFfmpeg
-  GifParams {
-      inputFile = ipf
-    , startTime = st
-    , durationTime = dt
-    , widthSize = ws
-  }
-  tmpdir = try(readProcess "ffmpeg" params [])
-  where
-    sts = printf "%.3f" st
-    dts = printf "%.3f" dt
-    wss = show ws
-    params = [
-          "-nostats"
-        , "-loglevel"
-        , "panic"
-        , "-an"
-        , "-ss"
-        , sts
-        , "-i"
-        , ipf
-        , "-t"
-        , dts
-        , "-r"
-        , "12"
-        , "-q:v"
-        , "31"
-        , "-vf"
-        , "scale=" ++ wss ++ ":-1"
-        , "-f"
-        , "image2"
-        , tmpdir ++ "/%010d." ++ frameFileExtension
-      ]
+gifExtension :: String
+gifExtension = "gif"
 
-tryFfprobe :: GifParams -> IO (Either IOError String)
-tryFfprobe GifParams { inputFile = ipf } = try(readProcess "ffprobe" params [])
+videoExtension :: String
+videoExtension = "mp4"
+
+extractFrames :: GifParams -> String -> Float -> IO (Either IOError String)
+extractFrames
+  GifParams
+    { inputFile
+    , startTime
+    , durationTime
+    , widthSize
+    , leftCrop
+    , rightCrop
+    , topCrop
+    , bottomCrop
+    }
+  tempDir
+  frameRate
+  = do
+  putStrLn $ "[INFO] Writing the temporary frames to: " ++ tempDir
+  try $ readProcess "ffmpeg" params []
   where
-    params = [
-          "-i"
-        , ipf
-        , "-v"
-        , "quiet"
-        , "-show_entries"
-        , "format=duration"
-        , "-of"
-        , "default=noprint_wrappers=1:nokey=1"
+    startTime' :: String
+    startTime' = printf "%.3f" startTime
+    durationTime' :: String
+    durationTime' = printf "%.3f" durationTime
+    widthSize' :: String
+    widthSize' = show widthSize
+    frameRate' :: String
+    frameRate' = show $ maybeFrameRateOrDefaultFrameRate (Just frameRate)
+    params :: [String]
+    params =
+      [ "-nostats"
+      , "-loglevel"
+      , "fatal"
+      , "-an"
+      , "-ss"
+      , startTime'
+      , "-i"
+      , inputFile
+      , "-t"
+      , durationTime'
+      , "-r"
+      , frameRate'
+      , "-q:v"
+      , "31"
+      , "-vf"
+      , "scale="
+        ++ widthSize'
+        ++ ":-1"
+        ++",crop=w=iw*(1-"
+        ++ show ((leftCrop + rightCrop) / 100.0)
+        ++ "):h=ih*(1-"
+        ++ show ((topCrop + bottomCrop) / 100.0)
+        ++ "):x=iw*"
+        ++ show (leftCrop / 100.0)
+        ++ ":y=ih*"
+        ++ show (topCrop / 100.0)
+        ++ ":exact=1"
+      , "-f"
+      , "image2"
+      , tempDir ++ "/%010d." ++ frameFileExtension
       ]
 
-tryConvert :: GifParams -> String -> IO (Either IOError String)
-tryConvert
-  GifParams {
-      outputFile = opf
-    , qualityPercent = qp
-    , fontChoice = fc
-    , topText = tt
-    , bottomText = bt
-  }
-  tmpdir = do
-    maybeWidthHeight <- maybeGetFirstFrameFilePath tmpdir >>= maybeGetFirstFrameWidthHeight
-    let params = [
-                    "-quiet"
-                  , "-delay"
-                  , "8.3"
-                  , tmpdir ++ "/*." ++ frameFileExtension
-                  , "-coalesce"
-                  , "-colors"
-                  , show $ ncolors qp
-                  , "-dither"
-                  , "FloydSteinberg"
-                  , "-layers"
-                  , "remove-dups"
-                  , "-layers"
-                  , "compare-any"
-                  , "-layers"
-                  , "optimize-transparency"
-                  , "-loop"
-                  , "0"
-                ]
-                ++ annotate fc maybeWidthHeight tt "north"
-                ++ annotate fc maybeWidthHeight bt "south"
-                ++ [opf]
-    try (readProcess "convert" params [])
+mergeFramesIntoGif :: GifParams -> String -> Float -> IO (Either IOError String)
+mergeFramesIntoGif
+  GifParams
+    { outputFile
+    , saveAsVideo
+    , qualityPercent
+    , fontChoice
+    , topText
+    , bottomText
+    }
+  tempDir
+  frameRate
+  = do
+  maybeWidthHeight <-
+        maybeGetFirstFrameFilePath tempDir
+    >>= maybeGetFirstFrameWidthHeight
+  let frameRate' = maybeFrameRateOrDefaultFrameRate (Just frameRate)
+  let delay = show $ 100.0 / frameRate'
+  let outputFile' =
+        if saveAsVideo
+          then tempDir ++ "/finished-result.gif"
+          else gifOutputFile outputFile
+  let params =
+            [ "-quiet"
+            , "-delay"
+            , delay
+            , tempDir ++ "/*." ++ frameFileExtension
+            , "-coalesce"
+            , "-colors"
+            , show $ ncolors qualityPercent
+            , "-dither"
+            , "FloydSteinberg"
+            , "-layers"
+            , "remove-dups"
+            , "-layers"
+            , "compare-any"
+            , "-layers"
+            , "optimize-transparency"
+            , "-loop"
+            , "0"
+            ]
+        ++  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')
 
+convertGifToVideo :: GifParams -> String -> IO (Either IOError String)
+convertGifToVideo GifParams { outputFile } gifFilePath = do
+  let outputFile' = videoOutputFile outputFile
+  let params =
+        [ "-nostats"
+        , "-loglevel"
+        , "fatal"
+        , "-y"
+        , "-i"
+        , gifFilePath
+        , "-movflags"
+        , "faststart"
+        , "-pix_fmt"
+        , "yuv420p"
+        , "-vf"
+        , "scale=trunc(iw/2)*2:trunc(ih/2)*2"
+        , outputFile'
+        ]
+  putStrLn $ "[INFO] Saving your video to: " ++ outputFile'
+  result <- try $ readProcess "ffmpeg" params []
+  if isLeft result
+    then return result
+    else return (Right outputFile')
+
 qualityPercentClamp :: Float -> Float
 qualityPercentClamp qp
   | qp > 100.0   = 100.0
@@ -292,36 +602,39 @@
   | qpc >= 100.0 = 256
   | otherwise  = truncate (qpc / 100.0 * 256.0)
   where
+    qpc :: Float
     qpc = qualityPercentClamp qp
 
 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
+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
+  ]
   where
     pointsize :: String
     pointsize = show $ pointSize maybeWidthHeight text
@@ -385,15 +698,17 @@
 getListOfFontNames :: IO [Text]
 getListOfFontNames = do
   (_, stdout, _) <- readProcessWithExitCode "convert" ["-list", "font"] []
-  let fontNames = Prelude.map (Data.Text.strip . Data.Text.drop 5 . Data.Text.strip) $
-                    Prelude.filter (Data.Text.isInfixOf "font:" . Data.Text.toLower) $
-                      Data.Text.splitOn "\n" $
-                        Data.Text.strip $
-                          Data.Text.pack stdout
+  let fontNames =
+        Prelude.map (Data.Text.strip . Data.Text.drop 5 . Data.Text.strip) $
+        Prelude.filter (Data.Text.isInfixOf "font:" . Data.Text.toLower) $
+        Data.Text.splitOn "\n" $
+        Data.Text.strip $
+        Data.Text.pack stdout
   return fontNames
 
 maybeGetFirstFrameFilePath :: String -> IO (Maybe FilePath)
-maybeGetFirstFrameFilePath tmpdir = try (makeAbsolute tmpdir) >>= tryListDir >>= maybeFirstFilePath
+maybeGetFirstFrameFilePath tempDir =
+  try (makeAbsolute tempDir) >>= tryListDir >>= maybeFirstFilePath
   where
     tryListDir :: Either IOError FilePath -> IO (FilePath, Either IOError [FilePath])
     tryListDir (Left y) = return ("", Left y)
@@ -407,16 +722,21 @@
 maybeGetFirstFrameWidthHeight Nothing = return Nothing
 maybeGetFirstFrameWidthHeight (Just dir) =
   readProcessWithExitCode "identify" [dir] [] >>=
-    \ (_, stdout, _) -> maybeConvertWidthHeightString $ findWidthHeightString $ splitOn " " $ Data.Text.pack stdout
+    \ (_, stdout, _) ->
+      maybeConvertWidthHeightString $
+      findWidthHeightString $
+      splitOn " " $
+      Data.Text.pack stdout
   where
     findWidthHeightString :: [Text] -> Text
     findWidthHeightString (_:_:c:_:_:_:_:_:_:_) = c
     findWidthHeightString _ = ""
     maybeConvertWidthHeightString :: Text -> IO (Maybe (Int, Int))
     maybeConvertWidthHeightString "" = return Nothing
-    maybeConvertWidthHeightString s = if Prelude.length splitOnX == 2
-                                        then return (Just (pluckWidth splitOnX, pluckHeight splitOnX))
-                                        else return Nothing
+    maybeConvertWidthHeightString s =
+      if Prelude.length splitOnX == 2
+        then return (Just (pluckWidth splitOnX, pluckHeight splitOnX))
+        else return Nothing
       where
         splitOnX :: [Text]
         splitOnX = splitOn "x" $ Data.Text.toLower s
@@ -427,13 +747,11 @@
         pluckHeight (_:y:_) = read (Data.Text.unpack y) :: Int
         pluckHeight _       = 0
 
-defaultFontChoice :: String
-defaultFontChoice = "sans"
-
 fontChoiceOrDefault :: GifParams -> String
 fontChoiceOrDefault GifParams { fontChoice = fontName } =
   if Data.List.null cleanedFontName
     then defaultFontChoice
     else cleanedFontName
   where
+    cleanedFontName :: String
     cleanedFontName = (Data.Text.unpack . Data.Text.strip . Data.Text.pack) fontName
