diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,359 @@
+# [Gifcurry](https://github.com/lettier/gifcurry)
+
+## Changelog
+
+### Version
+
+2.2.0.0
+
+#### Downstreams
+
+- [Hackage](https://hackage.haskell.org/package/Gifcurry-2.2.0.0)
+- [Docker](https://hub.docker.com/r/lettier/gifcurry/)
+- [Arch Linux AUR](https://aur.archlinux.org/packages/gifcurry)
+
+#### Added
+
+- Loading a video now populates the start and duration fields
+    - Duration is the length of the entire video in seconds
+- Confirmation dialog if duration is >= 10 seconds
+- `getVideoDurationInSeconds` to the library public API
+- Error checking
+- File path compatibility
+- About dialog
+- GNU Make targets for Arch Linux
+- `stack.yaml`
+- CHANGELOG.md
+
+#### Changed
+
+- Icon file
+- Switched from gtk2hs to haskell-gi
+- Switched from Cabal to Haskell stack
+- Code clean up
+- README install and run instructions
+- README graphics
+- GUI widget IDs
+- Fixed the ImageMagick null font issue
+    - ImageMagick assumes `~/.magick/type.xml` exists
+    - Instead of `default`, it searches for the first match to `sans` if no font is specified
+- Temporary file directory names where frames and GIFs are built
+
+#### Removed
+
+- Old icon from cabal file
+- Altered icon from GUI file
+
+-------------------------------------------------------------------------------
+
+### Version
+
+2.1.1.0
+
+#### Downstreams
+
+- [Hackage](https://hackage.haskell.org/package/Gifcurry-2.1.1.0)
+
+#### Added
+
+-
+
+#### Changed
+
+- Fixed cabal file
+- Altered optimization
+- Updated to work with GHC 8.0.2
+- Updated README to work with Hackage
+
+#### Removed
+
+-
+
+-------------------------------------------------------------------------------
+
+### Version
+
+2.1.0.0
+
+#### Downstreams
+
+- [Hackage](https://hackage.haskell.org/package/Gifcurry-2.1.0.0)
+
+#### Added
+
+- Font selection capabilities
+- Font selection to the GUI
+- A fontChoice parameter to the CLI
+- A makefile
+
+#### Changed
+
+- Updated the logo
+- Updated the icon
+- Fixed font scaling
+- Updated dependencies for macOS Sierra
+- Updated README
+- Reorganized project structure
+- Fixed compiler warnings
+
+#### Removed
+
+-
+
+-------------------------------------------------------------------------------
+
+### Version
+
+2.0.0.2
+
+#### Downstreams
+
+- [Hackage](https://hackage.haskell.org/package/Gifcurry-2.0.0.2)
+
+#### Added
+
+-
+
+#### Changed
+
+- Fixed a bug where the first and last frame previews were not updating
+
+#### Removed
+
+-
+
+-------------------------------------------------------------------------------
+
+### Version
+
+2.0.0.1
+
+#### Downstreams
+
+- [Hackage](https://hackage.haskell.org/package/Gifcurry-2.0.0.1)
+
+#### Added
+
+- `postGUIAsync`
+
+#### Changed
+
+- Bumped dependencies up (mainly GHC 8.0)
+- [Char] to String
+
+#### Removed
+
+-
+
+-------------------------------------------------------------------------------
+
+### Version
+
+2.0.0.0
+
+#### Downstreams
+
+- [Hackage](https://hackage.haskell.org/package/Gifcurry-2.0.0.0)
+
+#### Added
+
+- Type signatures
+- More error checking to the GUI
+
+#### Changed
+
+- Refactored code
+- Made CLI more flexible concerning input options
+
+#### Removed
+
+-
+
+-------------------------------------------------------------------------------
+
+### Version
+
+0.1.1.0
+
+#### Downstreams
+
+- [Hackage](https://hackage.haskell.org/package/Gifcurry-0.1.1.0)
+
+#### Added
+
+- First and last frame preview
+- Further file exists error checking
+
+#### Changed
+
+- Disabled GUI re-sizing
+- Simplified GUI
+
+#### Removed
+
+-
+
+-------------------------------------------------------------------------------
+
+### Version
+
+0.1.0.6
+
+#### Downstreams
+
+-
+
+#### Added
+
+-
+
+#### Changed
+
+- Updated GIF open method to work for Mac OS X
+
+#### Removed
+
+-
+
+-------------------------------------------------------------------------------
+
+### Version
+
+0.1.0.5
+
+#### Downstreams
+
+- [Hackage](https://hackage.haskell.org/package/Gifcurry-0.1.0.5)
+
+#### Added
+
+-
+
+#### Changed
+
+- Downgraded GTK requirement from 3.16 to 3.10
+
+#### Removed
+
+-
+
+-------------------------------------------------------------------------------
+
+### Version
+
+0.1.0.4
+
+#### Downstreams
+
+- [Hackage](https://hackage.haskell.org/package/Gifcurry-0.1.0.4)
+
+#### Added
+
+-
+
+#### Changed
+
+-
+
+#### Removed
+
+-
+
+-------------------------------------------------------------------------------
+
+### Version
+
+0.1.0.3
+
+#### Downstreams
+
+- [Hackage](https://hackage.haskell.org/package/Gifcurry-0.1.0.3)
+
+#### Added
+
+-
+
+#### Changed
+
+-
+
+#### Removed
+
+-
+
+-------------------------------------------------------------------------------
+
+### Version
+
+0.1.0.2
+
+#### Downstreams
+
+- [Hackage](https://hackage.haskell.org/package/Gifcurry-0.1.0.2)
+
+#### Added
+
+-
+
+#### Changed
+
+-
+
+#### Removed
+
+-
+
+-------------------------------------------------------------------------------
+
+### Version
+
+0.1.0.1
+
+#### Downstreams
+
+- [Hackage](https://hackage.haskell.org/package/Gifcurry-0.1.0.1)
+
+#### Added
+
+-
+
+#### Changed
+
+-
+
+#### Removed
+
+-
+
+-------------------------------------------------------------------------------
+
+### Version
+
+0.1.0.0
+
+#### Downstreams
+
+- [Hackage](https://hackage.haskell.org/package/Gifcurry-0.1.0.0)
+
+#### Added
+
+-
+
+#### Changed
+
+-
+
+#### Removed
+
+-
+
+-------------------------------------------------------------------------------
+
+## License
+
+See [LICENSE](LICENSE).
+
+## Copyright
+
+(C) 2017 David Lettier  
+[lettier.com](http://www.lettier.com/)
diff --git a/Gifcurry.cabal b/Gifcurry.cabal
--- a/Gifcurry.cabal
+++ b/Gifcurry.cabal
@@ -1,9 +1,9 @@
 name:                 Gifcurry
-version:              2.1.1.0
+version:              2.2.0.0
 synopsis:             GIF creation utility.
 description:          Create animated GIFs, optionally overlaid with text, from video files.
 homepage:             https://github.com/lettier/gifcurry
-license:              Apache-2.0
+license:              BSD3
 license-file:         LICENSE
 author:               Lettier
 maintainer:           Lettier
@@ -13,12 +13,15 @@
                       , Graphics
 build-type:           Simple
 extra-source-files:     README.md
+                      , LICENSE
+                      , CHANGELOG.md
+                      , ./lib/GtkMainSyncAsync.hs
+                      , ./lib/LICENSE
                       , ./src/dev/Paths_Gifcurry.hs
                       , ./src/data/icon.ico
-                      , ./src/data/icon2.ico
+                      , ./makefile
 data-files:             data/gui.glade
                       , data/icon.ico
-                      , data/icon2.ico
 data-dir:             ./src/
 cabal-version:        >=1.10
 
@@ -36,21 +39,26 @@
                       , filepath == 1.4.*
   hs-source-dirs:       ./src
                       , ./src/lib/
-  ghc-options:        -Wall
+  ghc-options:        -Wall -freverse-errors
   default-language:   Haskell2010
 
 executable            gifcurry_gui
   main-is:            Main.hs
   build-depends:        base >= 4.7 && < 5
-                      , gtk3 >= 0.14 && < 0.15
+                      , haskell-gi-base == 0.20.*
+                      , gi-glib == 2.0.*
+                      , gi-gobject == 2.0.*
+                      , gi-gtk == 3.0.*
+                      , gi-gdk == 3.0.*
                       , process >= 1.2 && <= 1.4.4
                       , temporary >= 1.2 && < 1.3
                       , directory == 1.3.*
                       , text == 1.2.*
                       , filepath == 1.4.*
-  other-modules:      Paths_Gifcurry, Gifcurry
-  ghc-options:        -threaded -with-rtsopts=-N -Wall
-  hs-source-dirs:       ./src/
+  other-modules:      Paths_Gifcurry, GtkMainSyncAsync, Gifcurry
+  ghc-options:        -threaded -with-rtsopts=-N -Wall -freverse-errors
+  hs-source-dirs:       ./lib/
+                      , ./src/
                       , ./src/gui/
                       , ./src/lib/
   default-language:   Haskell2010
@@ -65,7 +73,7 @@
                       , text == 1.2.*
                       , filepath == 1.4.*
   other-modules:      Gifcurry
-  ghc-options:        -Wall
+  ghc-options:        -Wall -freverse-errors
   hs-source-dirs:       ./src/
                       , ./src/cli/
                       , ./src/lib/
diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,202 +1,29 @@
-
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
+BSD 3-Clause License
 
-   APPENDIX: How to apply the Apache License to your work.
+Copyright (c) 2016, David Lettier
+All rights reserved.
 
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
 
-   Copyright 2016 David Lettier
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
 
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
+* 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.
 
-       http://www.apache.org/licenses/LICENSE-2.0
+* Neither the name of the copyright holder nor the names of its
+  contributors may be used to endorse or promote products derived from
+  this software without specific prior written permission.
 
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
 
 ## Screenshots
 
-![GUI](https://i.imgur.com/QxX0eXT.gif)
+![GUI](https://i.imgur.com/U8xnK82.gif)
 
 ## Sample GIF
 
@@ -51,10 +51,12 @@
                                   __/ |
                                  |___/ 
 
-Gifcurry 2.1.1.0
+Gifcurry 2.2.0.0
 (C) 2016 David Lettier
 lettier.com
 
+----------------------------------------
+
 Input file: ./03_caminandes_llamigos_1080p.mp4
 Output file: ./out.gif
 Start second: 42.000
@@ -74,52 +76,57 @@
 ## Dependencies
 
 * [Haskell](https://www.haskell.org/platform/)
-    * [alex](https://hackage.haskell.org/package/alex)
-    * [happy](https://hackage.haskell.org/package/happy)
-    * [gtk2hs-buildtools](https://hackage.haskell.org/package/gtk2hs-buildtools)
-    * [cmdargs](https://hackage.haskell.org/package/cmdargs)
-    * [System.IO.Temp (temporary)](https://hackage.haskell.org/package/temporary)
-    * [Graphics.UI.Gtk (gtk)](https://hackage.haskell.org/package/gtk3)
-    * [System.Directory (directory)](https://hackage.haskell.org/package/directory)
-    * [System.FilePath (filepath)](https://hackage.haskell.org/package/filepath)
 * [FFmpeg](https://www.ffmpeg.org/download.html)
 * [ImageMagick](http://www.imagemagick.org/script/download.php)
 * [GTK+](http://www.gtk.org/download/index.php)
 
 ## Install & Run
 
-### Ubuntu/Mint
+### Linux
 
+#### Ubuntu/Mint
+
 ```bash
-# Install FFmpeg & ImageMagick
+# Install FFmpeg (https://www.ffmpeg.org/download.html)
+# Install ImageMagick (https://www.imagemagick.org/script/download.php)
 sudo add-apt-repository ppa:kirillshkrogalev/ffmpeg-next
 sudo apt-get update
 sudo apt-get install ffmpeg imagemagick
+# Install Haskell (https://www.haskell.org/platform/)
+# Install GNU Wget (https://www.gnu.org/software/wget/)
 # Find the latest release at https://github.com/lettier/gifcurry/releases
-wget https://github.com/lettier/gifcurry/releases/download/*/gifcurry-linux*.tar.gz
-tar xvfz gifcurry-linux*.tar.gz
-cd gifcurry-linux*/bin
+# * 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_gui
 ./gifcurry_cli -?
 ```
 
-### Arch Linux
+#### Arch/Manjaro
 
 ```bash
-# Install FFmpeg and ImageMagick
-# Install yaourt (https://archlinux.fr/yaourt-en)
-# AUR package: https://aur.archlinux.org/packages/gifcurry/
-yaourt -S gifcurry
+cd
+# Install Git
+sudo pacman -S git
+# Install Gifcurry from AUR
+mkdir -p build_gifcurry
+cd build_gifcurry
+git clone https://aur.archlinux.org/gifcurry.git
+cd gifcurry
+makepkg -sic
+cd
+rm -rf build_gifcurry
 gifcurry_gui
 gifcurry_cli -?
 ```
 
-### macOS Sierra
+### Mac
 
 #### Binaries
 
 ```bash
-# If you don't have Homebrew
+# If you do not have Homebrew
 /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
 brew install ffmpeg
 brew install imagemagick
@@ -127,9 +134,10 @@
 brew install wget
 brew install gnome-icon-theme
 # Find the latest release at https://github.com/lettier/gifcurry/releases
-wget https://github.com/lettier/gifcurry/releases/download/*/gifcurry-macosx*.tar.gz
-tar xvfz gifcurry-macosx*.tar.gz
-cd gifcurry-macosx*/bin
+# * 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_gui
 ./gifcurry_cli -?
 ```
@@ -137,7 +145,7 @@
 #### Compile
 
 ```bash
-# If you don't have Homebrew
+# If you do not have Homebrew
 /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
 brew update
 brew install xcode
@@ -160,7 +168,6 @@
 cabal sandbox init
 cabal update
 cabal install alex happy -j
-cabal install gtk2hs-buildtools -j
 cabal install gifcurry -j
 cd .cabal-sandbox/bin/
 ./gifcurry_gui
@@ -169,12 +176,32 @@
 
 ### Hackage
 
+#### Stack
+
 ```bash
-# Install ghc and cabal-install
-# Install ffmpeg and imagemagick
+# 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
+stack exec -- gifcurry_gui
+stack exec -- gifcurry_cli -?
+```
+
+#### Cabal
+
+```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 gtk2hs-buildtools -j
 cabal install gifcurry -j
 cd ~/.cabal/bin
 ./gifcurry_gui
@@ -184,31 +211,15 @@
 ### Github
 
 ```bash
-# Install ghc and cabal-install
-# Install ffmpeg and imagemagick
-# Install GNU Make
+# 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_gui
 make run_cli CLI_ARGS='-?'
-
-# Or you can do
-
-# Install ghc and cabal-install
-# Install ffmpeg and imagemagick
-git clone git@github.com:lettier/gifcurry.git
-cd gifcurry/
-cabal sandbox init
-cabal update
-cabal install alex happy -j
-cabal install gtk2hs-buildtools -j
-cabal install -j --dependencies-only
-cabal configure
-cabal build -j
-cabal install -j --enable-relocatable
-./.cabal-sandbox/bin/gifcurry_gui
-./.cabal-sandbox/bin/gifcurry_cli -?
 ```
 
 ## License
diff --git a/lib/GtkMainSyncAsync.hs b/lib/GtkMainSyncAsync.hs
new file mode 100644
--- /dev/null
+++ b/lib/GtkMainSyncAsync.hs
@@ -0,0 +1,62 @@
+{-
+  Gifcurry
+  (C) 2017 David Lettier
+  lettier.com
+
+  Based on the following works.
+
+    - https://github.com/gtk2hs/gtk2hs/blob/master/gtk/Graphics/UI/Gtk/General/General.chs#L177
+    - https://github.com/gtk2hs/gtk2hs/blob/master/gtk/Graphics/UI/Gtk/General/General.chs#L188
+
+  Modified to work with haskell-gi (https://github.com/haskell-gi).
+
+  Original license:
+
+  GIMP Toolkit (GTK) General
+
+  Author : Axel Simon, Manuel M. T. Chakravarty
+
+  Created: 8 December 1998
+
+  Copyright (C) 2000..2005 Axel Simon, Manuel M. T. Chakravarty
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Lesser General Public
+  License as published by the Free Software Foundation; either
+  version 2.1 of the License, or (at your option) any later version.
+
+  This library is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  Lesser General Public License for more details.
+-}
+
+{-# LANGUAGE OverloadedStrings #-}
+
+module GtkMainSyncAsync (
+      gtkMainSync
+    , gtkMainAsync
+  ) where
+
+import Control.Monad
+import Control.Concurrent
+import GI.GLib hiding (String, IOError)
+
+gtkMainSync :: IO a -> IO a
+gtkMainSync callback =
+  newEmptyMVar >>=
+  gtkMainSyncSetupCallBack callback >>=
+  takeMVar
+
+gtkMainAsync :: IO () -> IO ()
+gtkMainAsync callback = gtkMainAddCallback (callback >> return False)
+
+gtkMainSyncSetupCallBack :: IO a -> MVar a -> IO (MVar a)
+gtkMainSyncSetupCallBack callback mVar =
+  gtkMainAddCallback (gtkMainSyncBuildCallBack callback mVar) >> return mVar
+
+gtkMainSyncBuildCallBack :: IO a -> MVar a -> IO Bool
+gtkMainSyncBuildCallBack callback mVar = callback >>= putMVar mVar >> return False
+
+gtkMainAddCallback :: GI.GLib.SourceFunc -> IO ()
+gtkMainAddCallback callback = void $ GI.GLib.idleAdd GI.GLib.PRIORITY_DEFAULT callback
diff --git a/lib/LICENSE b/lib/LICENSE
new file mode 100644
--- /dev/null
+++ b/lib/LICENSE
@@ -0,0 +1,165 @@
+		   GNU LESSER GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+  This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+  0. Additional Definitions.
+
+  As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+  "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+  An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+  A "Combined Work" is a work produced by combining or linking an
+Application with the Library.  The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+  The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+  The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+  1. Exception to Section 3 of the GNU GPL.
+
+  You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+  2. Conveying Modified Versions.
+
+  If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+   a) under this License, provided that you make a good faith effort to
+   ensure that, in the event an Application does not supply the
+   function or data, the facility still operates, and performs
+   whatever part of its purpose remains meaningful, or
+
+   b) under the GNU GPL, with none of the additional permissions of
+   this License applicable to that copy.
+
+  3. Object Code Incorporating Material from Library Header Files.
+
+  The object code form of an Application may incorporate material from
+a header file that is part of the Library.  You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+   a) Give prominent notice with each copy of the object code that the
+   Library is used in it and that the Library and its use are
+   covered by this License.
+
+   b) Accompany the object code with a copy of the GNU GPL and this license
+   document.
+
+  4. Combined Works.
+
+  You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+   a) Give prominent notice with each copy of the Combined Work that
+   the Library is used in it and that the Library and its use are
+   covered by this License.
+
+   b) Accompany the Combined Work with a copy of the GNU GPL and this license
+   document.
+
+   c) For a Combined Work that displays copyright notices during
+   execution, include the copyright notice for the Library among
+   these notices, as well as a reference directing the user to the
+   copies of the GNU GPL and this license document.
+
+   d) Do one of the following:
+
+       0) Convey the Minimal Corresponding Source under the terms of this
+       License, and the Corresponding Application Code in a form
+       suitable for, and under terms that permit, the user to
+       recombine or relink the Application with a modified version of
+       the Linked Version to produce a modified Combined Work, in the
+       manner specified by section 6 of the GNU GPL for conveying
+       Corresponding Source.
+
+       1) Use a suitable shared library mechanism for linking with the
+       Library.  A suitable mechanism is one that (a) uses at run time
+       a copy of the Library already present on the user's computer
+       system, and (b) will operate properly with a modified version
+       of the Library that is interface-compatible with the Linked
+       Version.
+
+   e) Provide Installation Information, but only if you would otherwise
+   be required to provide such information under section 6 of the
+   GNU GPL, and only to the extent that such information is
+   necessary to install and execute a modified version of the
+   Combined Work produced by recombining or relinking the
+   Application with a modified version of the Linked Version. (If
+   you use option 4d0, the Installation Information must accompany
+   the Minimal Corresponding Source and Corresponding Application
+   Code. If you use option 4d1, you must provide the Installation
+   Information in the manner specified by section 6 of the GNU GPL
+   for conveying Corresponding Source.)
+
+  5. Combined Libraries.
+
+  You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+   a) Accompany the combined library with a copy of the same work based
+   on the Library, uncombined with any other library facilities,
+   conveyed under the terms of this License.
+
+   b) Give prominent notice with the combined library that part of it
+   is a work based on the Library, and explaining where to find the
+   accompanying uncombined form of the same work.
+
+  6. Revised Versions of the GNU Lesser General Public License.
+
+  The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+  Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+  If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
diff --git a/makefile b/makefile
new file mode 100644
--- /dev/null
+++ b/makefile
@@ -0,0 +1,85 @@
+# (C) 2016 David Lettier
+# lettier.com
+
+.RECIPEPREFIX != ps
+
+STACK=stack --allow-different-user
+STACK_PATH_LOCAL_BIN=`$(STACK) path --local-bin`
+STACK_GHC_EXE=`$(STACK) path --compiler-exe`
+STACK_GHC_BIN=`$(STACK) path --compiler-bin`
+STACK_PATHS=$(STACK_PATH_LOCAL_BIN):$(STACK_GHC_BIN)
+CABAL=env PATH=$(PATH):$(STACK_PATHS) $(STACK_PATH_LOCAL_BIN)/cabal
+VERSION='2.2.0.0'
+
+export PATH := $(PATH):$(STACK_PATH_LOCAL_BIN)
+
+all: setup update sandbox_clean clean alex happy install_dependencies configure build install
+
+setup:
+
+  $(STACK) setup && $(STACK) update && $(STACK) install Cabal && $(STACK) install cabal-install
+
+alex: setup
+  $(STACK) install alex
+
+happy: setup
+  $(STACK) install happy
+
+sandbox: setup
+  $(CABAL) sandbox init
+
+clean: setup
+  $(CABAL) clean
+
+check: setup
+  $(CABAL) check
+
+sandbox_clean: setup
+  $(CABAL) sandbox init && $(CABAL) sandbox delete && $(CABAL) sandbox init
+
+update: sandbox
+  $(CABAL) --require-sandbox update
+
+install_dependencies: sandbox
+  $(CABAL) --require-sandbox install -j -w $(STACK_GHC_EXE) --only-dependencies
+
+configure: sandbox
+  $(CABAL) --require-sandbox configure -w $(STACK_GHC_EXE)
+
+build: configure
+  $(CABAL) --require-sandbox build -j
+
+install: build
+  $(CABAL) --require-sandbox install -j -w $(STACK_GHC_EXE) --enable-relocatable
+
+release: check build
+  $(CABAL) sdist
+
+run_gui: install
+  ./.cabal-sandbox/bin/gifcurry_gui
+
+run_cli: install
+  ./.cabal-sandbox/bin/gifcurry_cli $(CLI_ARGS)
+
+build_docs: setup
+  $(CABAL) haddock --hyperlink-source \
+  --html-location='http://hackage.haskell.org/package/Gifcurry/docs' \
+  --contents-location='http://hackage.haskell.org/package/Gifcurry' && \
+  cp -R ./dist/doc/html/Gifcurry/ ./docs/Gifcurry-$(VERSION)-docs && \
+  tar --format=ustar -zcvf './docs/Gifcurry-$(VERSION)-docs.tar.gz' './docs/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_install_dependencies: sandbox
+  $(CABAL) --require-sandbox install -j -w $(STACK_GHC_EXE) --force-reinstalls --reinstall --only-dependencies
+
+arch_os_configure: sandbox
+  $(CABAL) --require-sandbox configure -w $(STACK_GHC_EXE) --prefix=$(PREFIX)
+
+arch_os_build: arch_os_configure
+  $(CABAL) --require-sandbox build -j
+
+arch_os_install_gifcurry: arch_os_build
+  $(CABAL) --require-sandbox copy --destdir=$(DESTDIR)
+# End Arch Linux Specific
diff --git a/src/data/gui.glade b/src/data/gui.glade
--- a/src/data/gui.glade
+++ b/src/data/gui.glade
@@ -2,59 +2,99 @@
 <!-- Generated with glade 3.20.0 -->
 <interface>
   <requires lib="gtk+" version="3.10"/>
-  <object class="GtkImage" id="create_icon">
+  <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="GtkImage" id="open_icon">
+  <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">
+  <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 - lettier.com</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">icon2.ico</property>
+    <property name="icon">icon.ico</property>
     <property name="gravity">center</property>
     <child>
-      <object class="GtkBox" id="app_box">
+      <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="GtkFileChooserButton" id="input_file_button">
+          <object class="GtkBox" id="file-input-about-box">
             <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">Select an Input File</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">True</property>
-            <property name="fill">True</property>
+            <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">
+          <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">
+              <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>
@@ -70,7 +110,7 @@
               </packing>
             </child>
             <child>
-              <object class="GtkEntry" id="duration_text_entry">
+              <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>
@@ -89,16 +129,16 @@
           <packing>
             <property name="expand">False</property>
             <property name="fill">True</property>
-            <property name="position">1</property>
+            <property name="position">2</property>
           </packing>
         </child>
         <child>
-          <object class="GtkBox" id="width_quality_box">
+          <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">
+              <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>
@@ -114,7 +154,7 @@
               </packing>
             </child>
             <child>
-              <object class="GtkEntry" id="quality_text_entry">
+              <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>
@@ -135,11 +175,11 @@
           <packing>
             <property name="expand">True</property>
             <property name="fill">True</property>
-            <property name="position">2</property>
+            <property name="position">3</property>
           </packing>
         </child>
         <child>
-          <object class="GtkFontButton" id="font_chooser_button">
+          <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>
@@ -147,7 +187,7 @@
             <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">Choose a Font</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>
@@ -159,7 +199,7 @@
           </packing>
         </child>
         <child>
-          <object class="GtkEntry" id="top_text_text_entry">
+          <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>
@@ -173,7 +213,7 @@
           </packing>
         </child>
         <child>
-          <object class="GtkEntry" id="bottom_text_text_entry">
+          <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>
@@ -188,7 +228,7 @@
           </packing>
         </child>
         <child>
-          <object class="GtkSeparator" id="separator1">
+          <object class="GtkSeparator" id="separator-1">
             <property name="visible">True</property>
             <property name="can_focus">False</property>
           </object>
@@ -199,12 +239,12 @@
           </packing>
         </child>
         <child>
-          <object class="GtkFileChooserButton" id="output_file_path_button">
+          <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">Select an Output Folder</property>
+            <property name="title" translatable="yes">Gifcurry - Select an Output Folder</property>
           </object>
           <packing>
             <property name="expand">True</property>
@@ -213,7 +253,7 @@
           </packing>
         </child>
         <child>
-          <object class="GtkEntry" id="output_file_name_text_entry">
+          <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>
@@ -228,11 +268,11 @@
           </packing>
         </child>
         <child>
-          <object class="GtkBox" id="frame_images_box">
+          <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">
+              <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>
@@ -249,7 +289,7 @@
               </packing>
             </child>
             <child>
-              <object class="GtkImage" id="last_frame_image">
+              <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>
@@ -286,18 +326,18 @@
           </packing>
         </child>
         <child>
-          <object class="GtkBox" id="save_open_box">
+          <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">
+              <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="image">create-icon</property>
                 <property name="relief">half</property>
                 <property name="image_position">top</property>
                 <property name="always_show_image">True</property>
@@ -309,13 +349,13 @@
               </packing>
             </child>
             <child>
-              <object class="GtkButton" id="open_button">
+              <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="image">open-icon</property>
                 <property name="relief">half</property>
                 <property name="image_position">top</property>
                 <property name="always_show_image">True</property>
@@ -335,12 +375,12 @@
           </packing>
         </child>
         <child>
-          <object class="GtkBox" id="link_box">
+          <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">
+              <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>
@@ -372,7 +412,7 @@
               </packing>
             </child>
             <child>
-              <object class="GtkLinkButton" id="imgur_link_button">
+              <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>
@@ -396,7 +436,7 @@
           </packing>
         </child>
         <child>
-          <object class="GtkBox" id="status_box">
+          <object class="GtkBox" id="status-box">
             <property name="visible">True</property>
             <property name="sensitive">False</property>
             <property name="can_focus">False</property>
@@ -404,7 +444,7 @@
             <property name="vexpand">True</property>
             <property name="baseline_position">top</property>
             <child>
-              <object class="GtkEntry" id="status_text_entry">
+              <object class="GtkEntry" id="status-text-entry">
                 <property name="visible">True</property>
                 <property name="sensitive">False</property>
                 <property name="app_paintable">True</property>
@@ -435,5 +475,132 @@
         </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">2.2.0.0</property>
+    <property name="copyright" translatable="yes">(C) 2016 David Lettier
+lettier.com</property>
+    <property name="website">https://github.com/lettier/gifcurry</property>
+    <property name="website_label" translatable="yes">Update</property>
+    <property name="license">BSD 3-Clause License
+
+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 copyright holder 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 THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.</property>
+    <property name="authors"> David Lettier</property>
+    <property name="logo">icon.ico</property>
+    <property name="license_type">custom</property>
+    <child internal-child="vbox">
+      <object class="GtkBox">
+        <property name="can_focus">False</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">2</property>
+        <child internal-child="action_area">
+          <object class="GtkButtonBox">
+            <property name="can_focus">False</property>
+            <property name="layout_style">end</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <placeholder/>
+        </child>
+      </object>
+    </child>
+    <child>
+      <placeholder/>
+    </child>
+  </object>
+  <object class="GtkMessageDialog" id="long-gif-message-dialog">
+    <property name="can_focus">False</property>
+    <property name="title" translatable="yes">Gifcurry - Click Yes or No</property>
+    <property name="window_position">center</property>
+    <property name="destroy_with_parent">True</property>
+    <property name="icon_name">dialog-question</property>
+    <property name="type_hint">dialog</property>
+    <property name="urgency_hint">True</property>
+    <property name="gravity">center</property>
+    <property name="transient_for">gifcurry-window</property>
+    <property name="attached_to">gifcurry-window</property>
+    <property name="message_type">warning</property>
+    <property name="text" translatable="yes">Are you sure you want to make a GIF that long?</property>
+    <child internal-child="vbox">
+      <object class="GtkBox">
+        <property name="can_focus">False</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">2</property>
+        <child internal-child="action_area">
+          <object class="GtkButtonBox">
+            <property name="can_focus">False</property>
+            <property name="homogeneous">True</property>
+            <property name="layout_style">end</property>
+            <child>
+              <placeholder/>
+            </child>
+            <child>
+              <placeholder/>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <child>
+      <placeholder/>
+    </child>
+  </object>
+  <object class="GtkButton" id="yes-button">
+    <property name="label">gtk-yes</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>
 </interface>
diff --git a/src/data/icon.ico b/src/data/icon.ico
Binary files a/src/data/icon.ico and b/src/data/icon.ico differ
diff --git a/src/data/icon2.ico b/src/data/icon2.ico
deleted file mode 100644
Binary files a/src/data/icon2.ico and /dev/null differ
diff --git a/src/gui/Main.hs b/src/gui/Main.hs
--- a/src/gui/Main.hs
+++ b/src/gui/Main.hs
@@ -4,216 +4,285 @@
   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 Graphics.UI.Gtk
+import Data.GI.Base
+import GI.GObject
+import qualified GI.Gtk
 
 import Paths_Gifcurry
-import qualified Gifcurry (gif, GifParams(..), defaultGifParams, gifParamsValid)
+import qualified Gifcurry (
+      gif
+    , GifParams(..)
+    , defaultGifParams
+    , gifParamsValid
+    , getVideoDurationInSeconds
+  )
+import qualified GtkMainSyncAsync (gtkMainSync, gtkMainAsync)
 
 main :: IO ()
 main = do
-  _ <- initGUI
+  _ <- GI.Gtk.init Nothing
 
   builder <- buildBuilder
 
-  window               <- loadWindow   builder "gifcurry_window"
-  startTimeEntry       <- loadEntry    builder "start_time_text_entry"
-  durationTimeEntry    <- loadEntry    builder "duration_text_entry"
-  widthEntry           <- loadEntry    builder "width_text_entry"
-  qualityEntry         <- loadEntry    builder "quality_text_entry"
-  topTextEntry         <- loadEntry    builder "top_text_text_entry"
-  bottomTextEntry      <- loadEntry    builder "bottom_text_text_entry"
-  outputFileNameEntry  <- loadEntry    builder "output_file_name_text_entry"
-  statusEntry          <- loadEntry    builder "status_text_entry"
-  inputFileButton      <- loadFileChooserButton builder "input_file_button"
-  outputFilePathButton <- loadFileChooserButton builder "output_file_path_button"
-  fontChooserButton    <- loadFontChooserButton builder "font_chooser_button"
-  createButton         <- loadButton   builder "create_button"
-  openButton           <- loadButton   builder "open_button"
-  giphyButton          <- loadButton   builder "giphy_link_button"
-  imgurButton          <- loadButton   builder "imgur_link_button"
-  firstFrameImage      <- loadImage    builder "first_frame_image"
-  lastFrameImage       <- loadImage    builder "last_frame_image"
+  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.
-  buttonSetLabel giphyButton "Giphy"
-  buttonSetLabel imgurButton "Imgur"
+  GI.Gtk.buttonSetLabel giphyButton "Giphy"
+  GI.Gtk.buttonSetLabel imgurButton "Imgur"
 
-  entrySetText qualityEntry "100"
-  entrySetText widthEntry   "500"
+  GI.Gtk.entrySetText qualityEntry "100"
+  GI.Gtk.entrySetText widthEntry   "500"
 
-  _ <- inputFileButton `on` fileChooserButtonFileSet $ do
-    imageSetFromIconName firstFrameImage blankPreviewIcon IconSizeButton
-    imageSetFromIconName lastFrameImage  blankPreviewIcon IconSizeButton
+  -- 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)
 
-  _ <- startTimeEntry `on` editableChanged $
+  _ <- 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
+      )
+
+  _ <- GI.Gtk.onEditableChanged startTimeEntry $
     makeFirstFramePreview inputFileButton startTimeEntry durationTimeEntry firstFrameImage lastFrameImage
 
-  _ <- durationTimeEntry `on` editableChanged $
+  _ <- GI.Gtk.onEditableChanged durationTimeEntry $
     makeLastFramePreview inputFileButton startTimeEntry durationTimeEntry lastFrameImage
 
-  _ <- createButton `on` buttonActivated $ do
+  _ <- 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 <- fontButtonGetFontName fontChooserButton
-    topText <- entryGetText topTextEntry
-    bottomText <- entryGetText bottomTextEntry
+    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.inputFile      = inputFilePathName
+      , Gifcurry.outputFile     = outputFilePathName
+      , Gifcurry.startTime      = startTime
+      , Gifcurry.durationTime   = durationTime
+      , Gifcurry.widthSize      = widthSize
       , Gifcurry.qualityPercent = qualityPercent
-      , Gifcurry.fontChoice = fontChoice
-      , Gifcurry.topText = topText
-      , Gifcurry.bottomText = bottomText
+      , Gifcurry.fontChoice     = unpack fontChoice
+      , Gifcurry.topText        = unpack topText
+      , Gifcurry.bottomText     = unpack bottomText
     }
     paramsValid <- Gifcurry.gifParamsValid params
+    setStatusEntryReady statusEntry
     if paramsValid
-      then void $ forkOS $ do
-        postGUIAsync $ entrySetText statusEntry "One GIF coming up!"
-        success <- (ioSuccess . Gifcurry.gif) params
-        if not success
-          then postGUIAsync $ entrySetText statusEntry "Did not work. Check your settings."
-          else do
-            _ <- forkOS $ postGUIAsync $ openGifCommand outputFilePathName
-            postGUIAsync $ entrySetText statusEntry "Ready."
-      else entrySetText statusEntry "Settings are wrong."
+      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
 
-  _ <- openButton `on` buttonActivated $ do
+  _ <- GI.Gtk.onWidgetButtonReleaseEvent openButton $ \ _ -> do
     (_, outputFilePathName) <- assembleOutputFilePathName outputFilePathButton outputFileNameEntry
     fileExists <- doesFileExist outputFilePathName
     if fileExists
-      then void $ forkIO $ postGUIAsync $ openGifCommand outputFilePathName
-      else entrySetText statusEntry "GIF does not exist. Check your settings."
-
-  _ <- on window objectDestroy mainQuit
-
-  widgetShowAll window
-  mainGUI
-
-loadWindow :: Builder -> (String -> IO Window)
-loadWindow b = builderGetObject b castToWindow
-
-loadEntry :: Builder -> (String -> IO Entry)
-loadEntry b = builderGetObject b castToEntry
+      then void $ forkIO $ GtkMainSyncAsync.gtkMainAsync $ openGifCommand outputFilePathName
+      else GI.Gtk.entrySetText statusEntry "GIF does not exist. Check your settings."
+    return True
 
-loadFileChooserButton :: Builder -> (String -> IO FileChooserButton)
-loadFileChooserButton b = builderGetObject b castToFileChooserButton
+  _ <- GI.Gtk.onWidgetButtonReleaseEvent aboutButton $ \ _ -> GI.Gtk.dialogRun aboutDialog >> return True
 
-loadFontChooserButton :: Builder -> (String -> IO FontButton)
-loadFontChooserButton b = builderGetObject b castToFontButton
+  _ <- GI.Gtk.onDialogResponse longGifGtkMessageDialog (\ _ -> GI.Gtk.widgetHide longGifGtkMessageDialog)
+  _ <- GI.Gtk.onDialogResponse aboutDialog             (\ _ -> GI.Gtk.widgetHide aboutDialog)
 
-loadButton :: Builder -> (String -> IO Button)
-loadButton b = builderGetObject b castToButton
+  _ <- GI.Gtk.onWidgetDestroy window GI.Gtk.mainQuit
 
-loadImage :: Builder -> (String -> IO Image)
-loadImage b = builderGetObject b castToImage
+  GI.Gtk.widgetShowAll window
+  GI.Gtk.main
 
-buildBuilder :: IO Builder
+buildBuilder :: IO GI.Gtk.Builder
 buildBuilder = do
-  builder <- builderNew
   gladeFile <- getDataFileName "data/gui.glade"
-  builderAddFromFile builder gladeFile
-  return builder
+  GI.Gtk.builderNewFromFile (pack gladeFile)
 
-inputFileButtonGetText :: FileChooserButton -> IO String
+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
-  inputFileButtonText <- fileChooserGetFilename inputFileButton
-  inputFilePathName <- case inputFileButtonText of
-    Nothing -> return ""
-    Just inputFilePathName -> return inputFilePathName
+  inputFilePathName <- fileChooserButtonGetText inputFileButton
   fileExist <- doesFileExist inputFilePathName
   if fileExist then return inputFilePathName else return ""
 
-entryGetFloat :: Entry -> Float -> IO Float
-entryGetFloat e nothing = fmap (\ t -> fromMaybe nothing (readMaybe t :: Maybe Float)) (entryGetText e)
+durationTimeWarningLevel :: Float
+durationTimeWarningLevel = 10.0
 
-assembleOutputFilePathName :: FileChooserButton -> Entry -> IO (String, String)
+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
-  outputFilePathText <- fileChooserGetFilename outputFilePathButton
-  outputFilePath <- case outputFilePathText of
-    Nothing -> return ""
-    Just dir -> return dir
-  outputFileName <- entryGetText outputFileNameEntry
+  outputFilePath <- fileChooserButtonGetText outputFilePathButton
+  outputFileName <- unpack . Data.Text.strip <$> GI.Gtk.entryGetText outputFileNameEntry
   let outputGifFileName = outputFileName ++ ".gif"
-  let outputFilePathName = outputFilePath ++ "/" ++ outputGifFileName
+  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))
+  when fileExists $ void (spawnCommand (command ++ "\"" ++ outputFilePathName ++ "\""))
   where
     command :: String
-    command = if "linux" `isInfixOf` fmap toLower System.Info.os then "xdg-open " else "open "
+    command = if "linux" `Data.List.isInfixOf` fmap Data.Char.toLower System.Info.os
+                then "xdg-open "
+                else "open "
 
 blankPreviewIcon :: String
 blankPreviewIcon = "gtk-discard"
 
-resetImage :: Image -> IO ()
-resetImage image = imageSetFromIconName image blankPreviewIcon IconSizeButton
+resetImage :: GI.Gtk.Image -> IO ()
+resetImage image =
+  GI.Gtk.imageSetFromIconName
+    image
+    (Just $ pack blankPreviewIcon)
+    (enumToInt32 GI.Gtk.IconSizeButton)
 
-makeGifPreview :: String -> String -> Float -> String -> String -> IO (Either IOError String)
-makeGifPreview inputFile outputFile startTime fontChoice bottomText = Gifcurry.gif $ Gifcurry.defaultGifParams {
-      Gifcurry.inputFile = inputFile
-    , Gifcurry.outputFile = outputFile
-    , Gifcurry.startTime = startTime
-    , Gifcurry.durationTime = 0.001
-    , Gifcurry.widthSize = 200
+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.fontChoice = fontChoice
-    , Gifcurry.bottomText = bottomText
+    , Gifcurry.bottomText     = bottomText
   }
 
 ioSuccess :: IO (Either IOError String) -> IO Bool
 ioSuccess = fmap isRight
 
-makeLastFramePreview :: FileChooserButton -> Entry -> Entry -> Image -> IO ()
-makeLastFramePreview inputFileButton startTimeEntry durationTimeEntry lastFrameImage = do
-  _ <- forkIO $
-    withTempDirectory "." "previews" $ \tmpdir -> do
-      inputFilePathName <- inputFileButtonGetText inputFileButton
-      startTime <- entryGetFloat startTimeEntry (-1.0)
-      durationTime <- entryGetFloat durationTimeEntry 0.001
-      let startTime' = startTime + durationTime
-      if not (null inputFilePathName) && (startTime' > 0.0) then do
-        let outputFilePathName = tmpdir ++ "/end.gif"
-        success <- ioSuccess $ makeGifPreview inputFilePathName outputFilePathName startTime' "default" " LAST FRAME  "
-        if success
-          then postGUISync $ imageSetFromFile lastFrameImage outputFilePathName
-          else postGUISync $ resetImage lastFrameImage
-      else postGUIAsync $ resetImage lastFrameImage
-  return ()
+framePreviewDirectoryName :: String
+framePreviewDirectoryName = "gifcurry-frame-previews"
 
-makeFirstFramePreview :: FileChooserButton -> Entry -> Entry -> Image -> Image -> IO ()
-makeFirstFramePreview inputFileButton startTimeEntry durationTimeEntry firstFrameImage lastFrameImage = do
-  _ <- forkIO $ do
-    withTempDirectory "." "previews" $ \tmpDir -> do
-      inputFilePathName <- inputFileButtonGetText inputFileButton
-      startTime <- entryGetFloat startTimeEntry (-1.0)
-      if not (null inputFilePathName) && (startTime >= 0.0) then do
-        let outputFilePathName = tmpDir ++ "/start.gif"
-        success <- ioSuccess $ makeGifPreview inputFilePathName outputFilePathName startTime "default" " FIRST FRAME "
-        if success
-          then postGUISync $ imageSetFromFile firstFrameImage outputFilePathName
-          else postGUISync $ resetImage firstFrameImage
-      else postGUISync $ resetImage firstFrameImage
+makeLastFramePreview :: GI.Gtk.FileChooserButton -> GI.Gtk.Entry -> GI.Gtk.Entry -> GI.Gtk.Image -> IO ()
+makeLastFramePreview inputFileButton startTimeEntry durationTimeEntry lastFrameImage =
+  void $ forkIO $
+    withTempDirectory "." 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 -> IO ()
+makeFirstFramePreview inputFileButton startTimeEntry durationTimeEntry firstFrameImage lastFrameImage =
+  void $ forkIO $ do
+    withTempDirectory "." 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
-  return ()
+
+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
diff --git a/src/lib/Gifcurry.hs b/src/lib/Gifcurry.hs
--- a/src/lib/Gifcurry.hs
+++ b/src/lib/Gifcurry.hs
@@ -14,6 +14,7 @@
     , defaultGifParams
     , gifParamsValid
     , versionNumber
+    , getVideoDurationInSeconds
   ) where
 
 import System.Process
@@ -43,7 +44,7 @@
 
 -- | The version number.
 versionNumber :: String
-versionNumber = "2.1.1.0"
+versionNumber = "2.2.0.0"
 
 -- | Specifies default parameters for 'startTime', 'durationTime', 'widthSize', 'qualityPercent', and 'fontChoice'.
 defaultGifParams :: GifParams
@@ -54,7 +55,7 @@
     , durationTime = 1.0
     , widthSize = 500
     , qualityPercent = 100.0
-    , fontChoice = "default"
+    , fontChoice = defaultFontChoice
     , topText = ""
     , bottomText = ""
   }
@@ -75,7 +76,7 @@
 -- @
 gif :: GifParams -> IO (Either IOError String)
 gif gifParams =
-  withTempDirectory "." "frames" $ \tmpdir -> do
+  withTempDirectory "." "gifcurry-frames" $ \tmpdir -> do
     printGifParams gifParams tmpdir
     validParams <- gifParamsValid gifParams
     if validParams
@@ -86,7 +87,8 @@
           then do
             fontMatch <- getFontMatch gifParams
             let gifParams' = gifParams { fontChoice = fontMatch }
-            putStrLn $ "Writing your GIF to... " ++ outputFile gifParams
+            putStrLn $ "Writing your GIF to: " ++
+                       outputFile gifParams
             convertResult <- tryConvert gifParams' tmpdir
             let convertSuccess = eitherBool convertResult
             if convertSuccess
@@ -98,21 +100,15 @@
             return fFMpegResult
       else return $ Left (userError "[Error] Invalid params.")
   where
-    runFontMatch :: GifParams -> Bool
-    runFontMatch GifParams { fontChoice = _, topText = "", bottomText = "" } = False
-    runFontMatch GifParams { fontChoice = "default", topText = _, bottomText = _ } = False
-    runFontMatch _ = True
     getFontMatch :: GifParams -> IO String
-    getFontMatch gifParams'
-      | runFontMatch gifParams' = do
-        fontNames <- getListOfFontNames
-        let match = bestFontNameMatch (fontChoice gifParams') fontNames
-        putStrLn $ "Font matched: " ++ match
-        return match
-      | otherwise = defaultAction
-      where
-        defaultAction :: IO String
-        defaultAction = putStrLn "Using the default font." >> return "default"
+    getFontMatch GifParams { topText = "", bottomText = "" } = defaultFontMatch
+    getFontMatch gifParams' = do
+      fontNames <- getListOfFontNames
+      let match = bestFontNameMatch (fontChoiceOrDefault gifParams') fontNames
+      putStrLn $ "Font matched: " ++ 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)
 
@@ -133,12 +129,32 @@
       0 -> return False
       _ -> doesFileExist ipf
     unless inputFileExists $ putStrLn "\n[Error] Input video file does not exist."
-    let outputFileValid = Prelude.length opf > 5
-    unless outputFileValid $ putStrLn "\n[Error] Output GIF file is blank."
-    let valid = inputFileExists && outputFileValid && (st >= 0.0) && (dt >= 0.0) && (ws > 0) && (qp > 0.0)
+    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
 
+-- | Returns the duration of the video in seconds if successful.
+--
+-- @
+--    import qualified Gifcurry (getVideoDurationInSeconds)
+--    -- ...
+--    let params = Gifcurry.defaultGifParams { Gifcurry.inputFile = ".\/in.mov" }
+--    maybeDuration <- Gifcurry.getVideoDurationInSeconds params
+--    let duration = case maybeDuration of
+--                      Nothing    -> 0.0
+--                      Just float -> float
+-- @
+getVideoDurationInSeconds :: GifParams -> IO (Maybe Float)
+getVideoDurationInSeconds gifParams = tryFfprobe gifParams >>= result
+  where
+    result :: Either IOError String -> IO (Maybe Float)
+    result (Left _)               = return Nothing
+    result (Right durationString) = return (readMaybe durationString :: Maybe Float)
+
 printGifParams :: GifParams -> String -> IO ()
 printGifParams
   GifParams {
@@ -153,7 +169,8 @@
     , bottomText = bt
   }
   tmpdir = mapM_ putStrLn [
-        "\nInput file: " ++ ipf
+        "\n----------------------------------------\n"
+      , "Input file: " ++ ipf
       , "Output file: " ++ opf
       , "Start second: " ++ printf "%.3f" st
       , "Duration: " ++ printf "%.3f" dt ++ " seconds"
@@ -162,7 +179,8 @@
       , "Font Choice: " ++ fc
       , "Top text: " ++ tt
       , "Bottom text: " ++ bt
-      , "\nWriting temporary frames to... " ++ tmpdir
+      , ""
+      , "Writing temporary frames to: " ++ tmpdir
     ]
 
 frameFileExtension :: String
@@ -203,6 +221,20 @@
         , tmpdir ++ "/%010d." ++ frameFileExtension
       ]
 
+tryFfprobe :: GifParams -> IO (Either IOError String)
+tryFfprobe GifParams { inputFile = ipf } = try(readProcess "ffprobe" params [])
+  where
+    params = [
+          "-i"
+        , ipf
+        , "-v"
+        , "quiet"
+        , "-show_entries"
+        , "format=duration"
+        , "-of"
+        , "default=noprint_wrappers=1:nokey=1"
+      ]
+
 tryConvert :: GifParams -> String -> IO (Either IOError String)
 tryConvert
   GifParams {
@@ -307,8 +339,7 @@
 
 fontSetting :: String -> [String]
 fontSetting ""        = []
-fontSetting "default" = []
-fontSetting fc        = ["-font", fc]
+fontSetting font      = ["-font", font]
 
 bestFontNameMatch :: String -> [Text] -> String
 bestFontNameMatch _ []            = "default"
@@ -380,8 +411,19 @@
         splitOnX :: [Text]
         splitOnX = splitOn "x" $ Data.Text.toLower s
         pluckWidth :: [Text] -> Int
-        pluckWidth  (x:_:_) = read (Data.Text.unpack x) :: Int
+        pluckWidth (x:_:_) = read (Data.Text.unpack x) :: Int
         pluckWidth _        = 0
         pluckHeight :: [Text] -> Int
         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 = (Data.Text.unpack . Data.Text.strip . Data.Text.pack) fontName
