packages feed

movie-monad 0.0.1.0 → 0.0.2.0

raw patch · 25 files changed

+2994/−524 lines, 25 filesdep +filepathdep +gi-gdkpixbufdep +network-uridep −gi-gdkx11dep −gi-xlibdep ~gi-gtkdep ~haskell-gi-basebinary-added

Dependencies added: filepath, gi-gdkpixbuf, network-uri, system-fileio, system-filepath, time

Dependencies removed: gi-gdkx11, gi-xlib

Dependency ranges changed: gi-gtk, haskell-gi-base

Files

+ CHANGELOG.md view
@@ -0,0 +1,84 @@+# [Movie Monad](https://github.com/lettier/movie-monad)++## Changelog++### 0.0.2.0++#### Added++- Hides the cursor and the on-screen controls when the mouse is idle+- New Logo+- URL support+- Keyboard shortcuts+- makefile+- Icon+- AppImage+- AppImage installation script+- Support for GTK 3.10+- macOS screenshots+- macOS build instructions+- Screensaver and power management enable/disable+- Video file path command line option that, if present, plays the video on window realized++#### Changed++- Cabal file+    - Corrected source-repository+- Logo+- README+- Dependencies+- Icon+- Play/Pause toggle to button+- ICO file to PNG+- Switched from GstVideoOverlay to GtkSink++#### Removed++- ExifTool requirement+- Old logo PNG file+- X11 dependency++-------------------------------------------------------------------------------++### 0.0.1.0++#### Added++- Fullscreen mode++#### Changed++- Screenshot+- Main function+    - Extracted callback functions into their own functions+- Dependencies to match Hackage++#### Removed++- `lib` directory++-------------------------------------------------------------------------------++### 0.0.0.0++#### Added++- File Loader+- Seek+- Pause+- Play+- Window Width Selector+- About+- Screenshot+- Logo+- README+- LICENSE+- Documentation++#### Changed++-++#### Removed++-
README.md view
@@ -1,4 +1,4 @@-![Movie Monad](https://i.imgur.com/Vt9Bipy.png)+![Movie Monad](https://i.imgur.com/gdsyIMv.png)  # Movie Monad @@ -6,30 +6,157 @@  ## Screenshots -![GUI showing Sintel from the Blender Foundation](https://i.imgur.com/SLse3s9.jpg)+![GUI showing Sintel from the Blender Foundation](https://i.imgur.com/UBNYbER.jpg) -## Documentation+![GUI showing Big Buck Bunny from the Blender Foundation](https://i.imgur.com/Tgmk7SW.png) -[Let's make a GTK Video Player with Haskell](https://lettier.github.io/posts/2017-08-30-haskell-gtk-video-player.html)+![GUI showing the About window as seen on the Mac](https://i.imgur.com/hiix2Wm.png) -## Install+## Features -### GitHub+* Local or remote video file playback+    * `file://`+    * `http://`+    * `https://`+* Window size selection+* Fullscreen mode+* Seek+* Play and pause+* Volume+* Command line play+    * `movie-monad ./path/to/video/file.mp4`+    * `movie-monad http://www.domain.tld/path/to/video/file.mp4` +## Keyboard Shortcuts++* Mute/Unmute+    * `m`+    * `AudioMute`+* Fullscreen/Windowed+    * `f`+* Show Controls+    * `c`+* Volume Up+    * `ArrowUp`+    * `AudioRaiseVolume`+* Volume Down+    * `ArrowDown`+    * `AudioLowerVolume`+* Play/Pause+    * `Space`+    * `AudioPlay`++## Tested On++### Linux++* [Ubuntu 17.10](https://www.ubuntu.com/desktop)+* [Ubuntu 16.04](https://www.ubuntu.com/desktop)+* [Ubuntu 14.04](https://www.ubuntu.com/desktop)+* [Linux Mint 18.2](https://linuxmint.com/)+* [Manjaro 17.04](https://manjaro.org/)+* [Deepin 15.4.1](https://www.deepin.org/en/dde/)+* [Antergos 17.10](https://antergos.com/)++### Mac++* [macOS Sierra 10.12.6](https://en.wikipedia.org/wiki/MacOS_Sierra)++## Dependencies++### Run++* [GTK+ >= 3.10](https://www.gtk.org/download/index.php)+* [GStreamer >= 1.0](https://gstreamer.freedesktop.org/download/)+    * [GStreamer Good Plug-ins](https://gstreamer.freedesktop.org/modules/gst-plugins-good.html)+    * [GStreamer Bad Plug-ins](https://gstreamer.freedesktop.org/modules/gst-plugins-bad.html)++### Build++* [Haskell](https://www.haskell.org/platform/prior.html)+    * GHC == 8.0.2++## Install & Run++### Linux++#### AppImage+ ```bash-# Install Git-# Install XQuartz (https://www.xquartz.org/) if using Mac OSX or macOS-# Install GTK+ 3.* (https://www.gtk.org/download/index.php)-# Install Haskell-# Install Haksell Stack-# Install ExifTool+# Install GTK+ >= 3.10 (https://www.gtk.org/download/index.php)+# Install GStreamer >= 1.0 (https://gstreamer.freedesktop.org/download/)+# Install GStreamer Good Plug-ins (https://gstreamer.freedesktop.org/modules/gst-plugins-good.html)+# Install GStreamer Bad Plug-ins >= 1.8 (https://gstreamer.freedesktop.org/modules/gst-plugins-bad.html)+# Visit https://github.com/lettier/movie-monad/releases+# * equals the current version+# Download the latest AppImage movie-monad-*-x86_64.AppImage+wget https://github.com/lettier/movie-monad/releases/download/*/movie-monad-*-x86_64.AppImage+chmod a+x movie-monad-*-x86_64.AppImage+./movie-monad-*-x86_64.AppImage+# If you would like to install the AppImage, you can run the following+cd+wget "https://raw.githubusercontent.com/lettier/movie-monad/master/packaging/linux/movie-monad-install.sh" -O "movie-monad-install.sh"+chmod +x "movie-monad-install.sh"+./movie-monad-install.sh+```++### Mac++```bash+# Install Homebrew (https://brew.sh/#install)+cd ~/Downloads+brew update && brew upgrade+brew install git git clone https://github.com/lettier/movie-monad.git-cd movie-monad/+cd movie-monad+brew cask install haskell-platform+brew install pkg-config libffi libav libsvg librsvg libogg libvorbis openh264 theora \+  gobject-introspection cairo gdk-pixbuf gsettings-desktop-schemas gtk+3 gtk-mac-integration \+  gstreamer gst-libav gst-plugins-base gst-plugins-good+brew install --with-gtk+3 gst-plugins-bad stack setup+stack install hsc2hs stack install-stack exec -- movie-monad-# Or just `movie-monad` if `stack path | grep local-bin-path` is in your `echo $PATH`+export PATH=$PATH:"$HOME/.local/bin/"+movie-monad ```++### [Hackage](https://hackage.haskell.org/package/movie-monad)++```bash+# Install GObject Introspection (https://wiki.gnome.org/action/show/Projects/GObjectIntrospection)+# Install GTK+ >= 3.10 (https://www.gtk.org/download/index.php)+# Install GStreamer >= 1.0 (https://gstreamer.freedesktop.org/download/)+# Install GStreamer Good Plug-ins (https://gstreamer.freedesktop.org/modules/gst-plugins-good.html)+# Install GStreamer Bad Plug-ins >= 1.8 (https://gstreamer.freedesktop.org/modules/gst-plugins-bad.html)+# Install Cabal, GHC == 8.0.2 (https://www.haskell.org/platform/prior.html)+# Install Cabal Install (https://www.haskell.org/platform/prior.html)+cabal install movie-monad+movie-monad+```++### [GitHub](https://github.com/lettier/movie-monad)++```bash+# Install GObject Introspection (https://wiki.gnome.org/action/show/Projects/GObjectIntrospection)+# Install GTK+ >= 3.10 (https://www.gtk.org/download/index.php)+# Install GStreamer >= 1.0 (https://gstreamer.freedesktop.org/download/)+# Install GStreamer Good Plug-ins (https://gstreamer.freedesktop.org/modules/gst-plugins-good.html)+# Install GStreamer Bad Plug-ins >= 1.8 (https://gstreamer.freedesktop.org/modules/gst-plugins-bad.html)+# Install Git (https://git-scm.com/downloads)+# Install GNU Make (https://www.gnu.org/software/make/)+# Install Haskell, GHC == 8.0.2 (https://www.haskell.org/platform/prior.html)+# Install Haksell Stack (https://www.haskell.org/platform/)+git clone https://github.com/lettier/movie-monad.git+cd movie-monad/+make+make install+make run+```++## Developer Documentation++[Let's make a GTK Video Player with Haskell](https://lettier.github.io/posts/2017-08-30-haskell-gtk-video-player.html)  ## License 
movie-monad.cabal view
@@ -1,5 +1,5 @@ name:                 movie-monad-version:              0.0.1.0+version:              0.0.2.0 synopsis:             Plays videos using GStreamer and GTK+. description:          Desktop video player that uses GStreamer and GTK+. homepage:             https://github.com/lettier/movie-monad@@ -8,38 +8,84 @@ author:               Lettier maintainer:           Lettier copyright:            2017 David Lettier-category:               Multimedia+category:               Application+                      , Multimedia                       , Multimedia Player                       , Video build-type:           Simple-cabal-version:        >=1.10+cabal-version:        >= 1.10 extra-source-files:     README.md                       , LICENSE+                      , CHANGELOG.md                       , ./src/dev/Paths_movie_monad.hs                       , ./src/data/gui.glade-data-files:           data/gui.glade+                      , ./src/data/movie-monad-logo.svg+                      , ./src/data/movie-monad-icon.png+                      , ./src/Records.hs+                      , ./src/Constants.hs+                      , ./src/Window.hs+                      , ./src/CommandLine.hs+                      , ./src/Keyboard.hs+                      , ./src/Mouse.hs+                      , ./src/FileChooser.hs+                      , ./src/Seek.hs+                      , ./src/PlayPause.hs+                      , ./src/VideoSizeSelector.hs+                      , ./src/Fullscreen.hs+                      , ./src/ErrorMessage.hs+                      , ./src/About.hs+                      , ./src/VideoInfo.hs+                      , ./src/Playbin.hs+                      , ./src/Uri.hs+                      , ./src/Utils.hs+                      , ./src/ScreensaverAndPowerManagement.hs+data-files:             data/gui.glade+                      , data/movie-monad-logo.svg+                      , data/movie-monad-icon.png data-dir:             ./src/  source-repository     head   type:               git-  location:           https://github.com/lettier/gifcurry.git+  location:           https://github.com/lettier/movie-monad.git  executable            movie-monad   main-is:              Main.hs   default-language:     Haskell2010   build-depends:        base >= 4.7 && < 5+                      , filepath == 1.4.*+                      , system-fileio == 0.3.*+                      , system-filepath == 0.4.*                       , text == 1.2.*+                      , time == 1.6.*                       , process == 1.4.*                       , MissingH == 1.4.*-                      , haskell-gi-base == 0.20.*-                      , gi-xlib == 2.0.*+                      , network-uri == 2.6.*+                      , haskell-gi-base == 0.20.4                       , gi-gobject == 2.0.*                       , gi-glib == 2.0.*-                      , gi-gdkx11 == 3.0.*-                      , gi-gstvideo == 1.0.*                       , gi-gdk == 3.0.*+                      , gi-gdkpixbuf == 2.0.*                       , gi-gst == 1.0.*-                      , gi-gtk == 3.0.*-  ghc-options:        -threaded -with-rtsopts=-N -Wall-  other-modules:      Paths_movie_monad+                      , gi-gstvideo == 1.0.*+                      , gi-gtk == 3.0.18+  ghc-options:        -threaded -with-rtsopts=-N -Wall -freverse-errors+  other-modules:        Paths_movie_monad+                      , Records+                      , Constants+                      , Window+                      , CommandLine+                      , Keyboard+                      , Mouse+                      , FileChooser+                      , Seek+                      , PlayPause+                      , VideoSizeSelector+                      , Fullscreen+                      , ErrorMessage+                      , About+                      , VideoInfo+                      , Uri+                      , Playbin+                      , Utils+                      , ScreensaverAndPowerManagement   hs-source-dirs:     ./src/
+ src/About.hs view
@@ -0,0 +1,39 @@+{-+  Movie Monad+  (C) 2017 David lettier+  lettier.com+-}++module About where++import Control.Monad+import qualified GI.Gdk+import qualified GI.Gtk++import qualified Records as R++addAboutHandler :: R.Application -> IO ()+addAboutHandler+  R.Application {+        R.guiObjects = R.GuiObjects {+              R.aboutButton = aboutButton+            , R.aboutDialog = aboutDialog+          }+    }+  =+  void (+      GI.Gtk.onWidgetButtonReleaseEvent aboutButton (aboutButtonReleaseHandler aboutDialog)+    )++aboutButtonReleaseHandler ::+  GI.Gtk.AboutDialog ->+  GI.Gdk.EventButton ->+  IO Bool+aboutButtonReleaseHandler aboutDialog _ =+  void (+      GI.Gtk.onDialogResponse aboutDialog (\ _ -> GI.Gtk.widgetHide aboutDialog)+    ) >>+  void (+      GI.Gtk.dialogRun aboutDialog+    ) >>+  return True
+ src/CommandLine.hs view
@@ -0,0 +1,32 @@+{-+  Movie Monad+  (C) 2017 David lettier+  lettier.com+-}++module CommandLine where++import System.Environment+import Data.Text+import GI.Gtk++import qualified Records as R+import FileChooser+import Uri+import Utils++playVideoFromCommandLineIfNeeded :: R.Application -> IO ()+playVideoFromCommandLineIfNeeded+  application@R.Application {+        R.guiObjects = R.GuiObjects {+              R.fileChooserEntry = fileChooserEntry+          }+    }+  = do+  args <- getArgs+  case args of+    [] -> return ()+    (filePath':_) -> do+      filePath <- toAbsoluteUri filePath'+      GI.Gtk.entrySetText fileChooserEntry (Data.Text.pack filePath)+      fileChooserDialogResponseHandler application (enumToInt32 GI.Gtk.ResponseTypeOk)
+ src/Constants.hs view
@@ -0,0 +1,17 @@+{-+  Movie Monad+  (C) 2017 David lettier+  lettier.com+-}++{-# LANGUAGE OverloadedStrings #-}++module Constants where++import Data.Text++hideOnScreenControlsInterval :: Integral a => a+hideOnScreenControlsInterval = 5++invalidVideoWidgetName :: Data.Text.Text+invalidVideoWidgetName = "invalid-video-widget"
+ src/ErrorMessage.hs view
@@ -0,0 +1,42 @@+{-+  Movie Monad+  (C) 2017 David lettier+  lettier.com+-}++{-# LANGUAGE OverloadedStrings #-}++module ErrorMessage where++import Control.Monad+import Data.Text+import Data.Int+import qualified GI.Gtk++import qualified Records as R++addErrorMessageDialogHandler :: R.Application -> IO ()+addErrorMessageDialogHandler+  R.Application {+        R.guiObjects = R.GuiObjects {+              R.errorMessageDialog = errorMessageDialog+          }+    }+  =+  void (GI.Gtk.onDialogResponse errorMessageDialog (errorMessageDialogResponseHandler errorMessageDialog))++errorMessageDialogResponseHandler ::+  GI.Gtk.MessageDialog ->+  Int32 ->+  IO ()+errorMessageDialogResponseHandler errorMessageDialog _ =+  GI.Gtk.widgetHide errorMessageDialog >>+  GI.Gtk.setMessageDialogText errorMessageDialog "There was an error."++runErrorMessageDialog ::+  GI.Gtk.MessageDialog ->+  Text ->+  IO ()+runErrorMessageDialog errorMessageDialog text =+  GI.Gtk.setMessageDialogText errorMessageDialog text >>+  void (GI.Gtk.dialogRun errorMessageDialog)
+ src/FileChooser.hs view
@@ -0,0 +1,190 @@+{-+  Movie Monad+  (C) 2017 David lettier+  lettier.com+-}++{-# LANGUAGE OverloadedStrings #-}++module FileChooser where++import Control.Monad+import Data.Int+import Data.Text+import Data.IORef+import qualified Network.URI+import qualified GI.Gdk+import qualified GI.Gtk+import qualified GI.Gst++import qualified Records as R+import Constants+import Window+import PlayPause+import ErrorMessage+import VideoInfo+import Playbin+import Uri+import Utils++addFileChooserHandlers :: R.Application -> IO ()+addFileChooserHandlers+  application@R.Application {+        R.guiObjects = R.GuiObjects {+              R.fileChooserButton = fileChooserButton+            , R.fileChooserDialog = fileChooserDialog+            , R.fileChooserWidget = fileChooserWidget+            , R.fileChooserEntry = fileChooserEntry+          }+    }+  =+  void (GI.Gtk.onWidgetButtonReleaseEvent fileChooserButton (fileChooserButtonClickHandler application)) >>+  void (GI.Gtk.onDialogResponse fileChooserDialog (fileChooserDialogResponseHandler application)) >>+  void (GI.Gtk.onFileChooserSelectionChanged fileChooserWidget (fileChooserSelectionChangedHandler application)) >>+  void (GI.Gtk.onEntryIconRelease fileChooserEntry (\ _ _ -> GI.Gtk.entrySetText fileChooserEntry ""))++fileChooserDialogResponseHandler ::+  R.Application ->+  Int32 ->+  IO ()+fileChooserDialogResponseHandler+  R.Application {+        R.guiObjects = guiObjects@R.GuiObjects {+              R.window = window+            , R.fileChooserButton = fileChooserButton+            , R.videoWidget = videoWidget+            , R.seekScale = seekScale+            , R.playPauseButton = playPauseButton+            , R.videoWidthSelectionComboBox = videoWidthSelectionComboBox+            , R.fullscreenButton = fullscreenButton+            , R.fileChooserEntry = fileChooserEntry+            , R.fileChooserButtonLabel = fileChooserButtonLabel+            , R.volumeButton = volumeButton+            , R.errorMessageDialog = errorMessageDialog+            , R.fileChooserDialog = fileChooserDialog+            , R.playImage = playImage+            , R.pauseImage = pauseImage+          }+      , R.ioRefs = R.IORefs {+              R.isWindowFullScreenRef = isWindowFullScreenRef+            , R.videoInfoRef = videoInfoRef+            , R.previousFileNamePathRef = previousFileNamePathRef+          }+      , R.playbin = playbin+    }+  responseId+  = do+  GI.Gtk.widgetHide fileChooserDialog+  handleResponseType GI.Gtk.ResponseTypeOk+  where+    handleResponseType :: (Enum a, Ord a) => a -> IO ()+    handleResponseType enum+      | enumToInt32 enum == responseId = do+        _ <- GI.Gst.elementSetState playbin GI.Gst.StateNull+        filePathName <- GI.Gtk.entryGetText fileChooserEntry+        let filePathNameStr = Data.Text.unpack filePathName+        (_, fileNameEmpty) <- setFileChooserButtonLabel fileChooserButtonLabel filePathName+        isWindowFullScreen <- readIORef isWindowFullScreenRef+        videoWidthSelection <- getSelectedVideoWidth videoWidthSelectionComboBox+        setPlaybinUriAndVolume playbin filePathNameStr volumeButton+        handleFileName+          fileNameEmpty+          filePathNameStr+          isWindowFullScreen+          videoWidthSelection+      | otherwise = do+        filePathName <- readIORef previousFileNamePathRef+        _ <- setFileChooserButtonLabel fileChooserButtonLabel filePathName+        GI.Gtk.entrySetText fileChooserEntry filePathName+    handleFileName ::+      Bool ->+      Prelude.String ->+      Bool ->+      Int ->+      IO ()+    handleFileName True _ _ _ = atomicWriteIORef videoInfoRef R.defaultVideoInfo >> resetWindow guiObjects+    handleFileName+      _+      filePathNameStr+      isWindowFullScreen+      videoWidthSelection+      = do+      retrievedVideoInfo <- getVideoInfo videoInfoRef filePathNameStr+      maybeWindowSize <- calculateWindowSize videoWidthSelection retrievedVideoInfo+      GI.Gtk.widgetSetSensitive seekScale True+      if R.isSeekable retrievedVideoInfo+        then GI.Gtk.widgetShow seekScale+        else GI.Gtk.widgetHide seekScale+      case maybeWindowSize of+        Nothing -> do+          resetWindow guiObjects+          runErrorMessageDialog+            errorMessageDialog+            (Data.Text.pack $ Prelude.concat ["\"", filePathNameStr, "\" is not a video."])+        Just (width, height) -> do+          videoWidgetName <- GI.Gtk.widgetGetName videoWidget+          if videoWidgetName == invalidVideoWidgetName+            then do+              resetWindow guiObjects+              runErrorMessageDialog+                errorMessageDialog+                "Cannot play the video. Please install the bad plugins, version 1.8 or higher, for GStreamer version 1."+            else do+              GI.Gtk.widgetShow videoWidget+              GI.Gtk.widgetShow playPauseButton+              GI.Gtk.widgetShow fullscreenButton+              setPlayPauseButton playPauseButton playImage pauseImage True+              unless isWindowFullScreen $+                setWindowSize width height fileChooserButton videoWidget window+              void $ GI.Gst.elementSetState playbin GI.Gst.StatePlaying++fileChooserButtonClickHandler ::+  R.Application ->+  GI.Gdk.EventButton ->+  IO Bool+fileChooserButtonClickHandler+  R.Application {+        R.guiObjects = R.GuiObjects {+              R.fileChooserEntry = fileChooserEntry+            , R.fileChooserDialog = fileChooserDialog+          }+      , R.ioRefs = R.IORefs {+              R.previousFileNamePathRef = previousFileNamePathRef+          }+    }+  _+  = do+  text <- GI.Gtk.entryGetText fileChooserEntry+  atomicWriteIORef previousFileNamePathRef text+  _ <- GI.Gtk.dialogRun fileChooserDialog+  return True++fileChooserSelectionChangedHandler ::+  R.Application ->+  IO ()+fileChooserSelectionChangedHandler+  R.Application {+        R.guiObjects = R.GuiObjects {+              R.fileChooserWidget = fileChooserWidget+            , R.fileChooserEntry = fileChooserEntry+          }+      , R.ioRefs = R.IORefs {+              R.videoInfoRef = videoInfoRef+          }+    }+  = do+  maybeUri <- GI.Gtk.fileChooserGetUri fileChooserWidget+  case maybeUri of+    Nothing -> return ()+    Just uri' -> do+      let uri = Network.URI.unEscapeString $ Data.Text.unpack uri'+      local <- isLocalFile uri+      video <- R.isVideo <$> getVideoInfo videoInfoRef uri+      GI.Gtk.entrySetText fileChooserEntry (if local && video then Data.Text.pack uri else "")++setFileChooserButtonLabel :: GI.Gtk.Label -> Data.Text.Text -> IO (Data.Text.Text, Bool)+setFileChooserButtonLabel fileChooserButtonLabel filePathName = do+  let fileName = fileNameFromFilePathName filePathName+  let fileNameEmpty = isTextEmpty fileName+  GI.Gtk.labelSetText fileChooserButtonLabel (if fileNameEmpty then "Open" else fileName)+  return (fileName, fileNameEmpty)
+ src/Fullscreen.hs view
@@ -0,0 +1,53 @@+{-+  Movie Monad+  (C) 2017 David lettier+  lettier.com+-}++module Fullscreen where++import Control.Monad+import Data.IORef+import qualified GI.Gdk+import qualified GI.Gtk++import qualified Records as R++addFullscreenButtonReleaseHandler :: R.Application -> IO ()+addFullscreenButtonReleaseHandler+  application@R.Application {+        R.guiObjects = R.GuiObjects {+              R.fullscreenButton = fullscreenButton+          }+    }+  =+  void $ GI.Gtk.onWidgetButtonReleaseEvent fullscreenButton (fullscreenButtonReleaseHandler application)++fullscreenButtonReleaseHandler ::+  R.Application ->+  GI.Gdk.EventButton ->+  IO Bool+fullscreenButtonReleaseHandler+  R.Application {+        R.guiObjects = R.GuiObjects {+              R.window = window+            , R.fileChooserButton = fileChooserButton+            , R.videoWidthSelectionComboBox = videoWidthSelectionComboBox+          }+      , R.ioRefs = R.IORefs {+            R.isWindowFullScreenRef = isWindowFullScreenRef+        }+    }+  _+  = do+  isWindowFullScreen <- readIORef isWindowFullScreenRef+  if isWindowFullScreen+    then do+      GI.Gtk.widgetShow videoWidthSelectionComboBox+      GI.Gtk.widgetShow fileChooserButton+      void $ GI.Gtk.windowUnfullscreen window+    else do+      GI.Gtk.widgetHide videoWidthSelectionComboBox+      GI.Gtk.widgetHide fileChooserButton+      void $ GI.Gtk.windowFullscreen window+  return True
+ src/Keyboard.hs view
@@ -0,0 +1,74 @@+{-+  Movie Monad+  (C) 2017 David lettier+  lettier.com+-}++module Keyboard where++import Control.Monad+import Data.IORef+import qualified GI.Gdk+import qualified GI.Gtk++import qualified Records as R+import Mouse+import PlayPause+import Fullscreen++addKeyboardEventHandler :: R.Application -> IO ()+addKeyboardEventHandler+  application@R.Application {+    R.guiObjects = R.GuiObjects {+        R.window = window+      }+    }+  =+  void $ GI.Gtk.onWidgetKeyPressEvent window $ keyboardEventHandler application++keyboardEventHandler ::+  R.Application ->+  GI.Gdk.EventKey ->+  IO Bool+keyboardEventHandler+  application@R.Application {+        R.guiObjects = R.GuiObjects {+              R.volumeButton = volumeButton+          }+      , R.ioRefs = R.IORefs {+            R.videoInfoRef = videoInfoRef+        }+    }+  eventKey+  = do+  videoInfoGathered <- readIORef videoInfoRef+  let isVideo = R.isVideo videoInfoGathered+  let volumeDelta = 0.05+  oldVolume <- GI.Gtk.scaleButtonGetValue volumeButton+  keyValue <- GI.Gdk.getEventKeyKeyval eventKey+  eventButton <- GI.Gdk.newZeroEventButton+  -- Mute Toggle+  when (keyValue == GI.Gdk.KEY_m || keyValue == GI.Gdk.KEY_AudioMute) $ do+    let newVolume = if oldVolume <= 0.0 then 0.5 else 0.0+    GI.Gtk.scaleButtonSetValue volumeButton newVolume+  -- Play/Pause Toggle+  when ((keyValue == GI.Gdk.KEY_space || keyValue == GI.Gdk.KEY_AudioPlay) && isVideo) $+    void $ playPauseButtonClickHandler application eventButton+  -- Volume Up+  when (keyValue == GI.Gdk.KEY_Up || keyValue == GI.Gdk.KEY_AudioRaiseVolume) $ do+    let newVolume = if oldVolume >= 1.0 then 1.0 else oldVolume + volumeDelta+    GI.Gtk.scaleButtonSetValue volumeButton newVolume+  -- Volume Down+  when (keyValue == GI.Gdk.KEY_Down || keyValue == GI.Gdk.KEY_AudioLowerVolume) $ do+    let newVolume = if oldVolume <= 0.0 then 0.0 else oldVolume - volumeDelta+    GI.Gtk.scaleButtonSetValue volumeButton newVolume+  -- Show Controls+  when (keyValue == GI.Gdk.KEY_c) $ do+    eventMotion <- GI.Gdk.newZeroEventMotion+    void $ windowMouseMoveHandler application eventMotion+  -- Fullscreen Toggle+  when (keyValue == GI.Gdk.KEY_f && isVideo) $ do+    eventMotion <- GI.Gdk.newZeroEventMotion+    void $ windowMouseMoveHandler application eventMotion+    void $ fullscreenButtonReleaseHandler application eventButton+  return True
src/Main.hs view
@@ -10,26 +10,35 @@ module Main where  import Prelude-import Foreign.C.Types-import System.Process-import System.Exit-import Control.Monad-import Control.Exception-import Text.Read+import System.Info import Data.IORef import Data.Maybe-import Data.Int import Data.Text import Data.GI.Base-import Data.GI.Base.Signals import Data.GI.Base.Properties-import GI.GLib import GI.GObject import qualified GI.Gtk import GI.Gst import GI.GstVideo-import GI.Gdk-import GI.GdkX11+import GI.GdkPixbuf++import qualified Records as R+import Constants+import Window+import CommandLine+import Mouse+import Keyboard+import FileChooser+import Seek+import PlayPause+import Fullscreen+import ErrorMessage+import About+import VideoSizeSelector+import Playbin+import ScreensaverAndPowerManagement (disable, enable)+import Utils+ import Paths_movie_monad  -- Declare Element a type instance of IsVideoOverlay via a newtype wrapper@@ -47,59 +56,119 @@   builder <- GI.Gtk.builderNewFromFile (pack gladeFile)    window <- builderGetObject GI.Gtk.Window builder "window"-  fileChooserButton <- builderGetObject GI.Gtk.FileChooserButton builder "file-chooser-button"-  drawingArea <- builderGetObject GI.Gtk.Widget builder "drawing-area"+  fileChooserButton <- builderGetObject GI.Gtk.Button builder "file-chooser-button"+  fileChooserButtonLabel <- builderGetObject GI.Gtk.Label builder "file-chooser-button-label"+  fileChooserDialog <- builderGetObject GI.Gtk.Dialog builder "file-chooser-dialog"+  fileChooserEntry <- builderGetObject GI.Gtk.Entry builder "file-chooser-entry"+  fileChooserWidget <- builderGetObject GI.Gtk.FileChooserWidget builder "file-chooser-widget"+  fileChooserCancelButton <- builderGetObject GI.Gtk.Button builder "file-chooser-cancel-button"+  fileChooserOpenButton <- builderGetObject GI.Gtk.Button builder "file-chooser-open-button"+  videoWidgetBox <- builderGetObject GI.Gtk.Box builder "video-widget-box"+  bottomControlsGtkBox <- builderGetObject GI.Gtk.Box builder "bottom-controls-gtk-box"   seekScale <- builderGetObject GI.Gtk.Scale builder "seek-scale"-  onOffSwitch <- builderGetObject GI.Gtk.Switch builder "on-off-switch"+  playPauseButton <- builderGetObject GI.Gtk.Button builder "play-pause-button"+  playImage <- builderGetObject GI.Gtk.Image builder "play-image"+  pauseImage <- builderGetObject GI.Gtk.Image builder "pause-image"   volumeButton <- builderGetObject GI.Gtk.VolumeButton builder "volume-button"-  desiredVideoWidthComboBox <- builderGetObject GI.Gtk.ComboBoxText builder "desired-video-width-combo-box"+  videoWidthSelectionComboBox <- builderGetObject GI.Gtk.ComboBoxText builder "video-width-selection-combo-box"   fullscreenButton <- builderGetObject GI.Gtk.Button builder "fullscreen-button"+  bufferingSpinner <- builderGetObject GI.Gtk.Spinner builder "buffering-spinner"   errorMessageDialog <- builderGetObject GI.Gtk.MessageDialog builder "error-message-dialog"   aboutButton <- builderGetObject GI.Gtk.Button builder "about-button"   aboutDialog <- builderGetObject GI.Gtk.AboutDialog builder "about-dialog" -  playbin <- fromJust <$> GI.Gst.elementFactoryMake "playbin" (Just "MultimediaPlayer")--  isWindowFullScreenRef <- newIORef False--  _ <- GI.Gtk.onWidgetRealize drawingArea $ onDrawingAreaRealize drawingArea playbin fullscreenButton+  logoFile <- getDataFileName "data/movie-monad-logo.svg"+  logo <- GI.GdkPixbuf.pixbufNewFromFile (pack logoFile)+  GI.Gtk.aboutDialogSetLogo aboutDialog (Just logo) -  _ <- GI.Gtk.onFileChooserButtonFileSet fileChooserButton $-    onFileChooserButtonFileSet-      playbin-      fileChooserButton-      volumeButton-      isWindowFullScreenRef-      desiredVideoWidthComboBox-      onOffSwitch-      fullscreenButton-      drawingArea-      window-      errorMessageDialog+  -- Glade does not allow us to use the response ID nicknames so we programmatically set them here.+  GI.Gtk.dialogAddActionWidget fileChooserDialog fileChooserCancelButton (enumToInt32 GI.Gtk.ResponseTypeCancel)+  GI.Gtk.dialogAddActionWidget fileChooserDialog fileChooserOpenButton   (enumToInt32 GI.Gtk.ResponseTypeOk) -  _ <- GI.Gtk.onSwitchStateSet onOffSwitch (onSwitchStateSet playbin)+  isWindowFullScreenRef <- newIORef False+  mouseMovedLastRef <- newIORef 0+  previousFileNamePathRef <- newIORef ""+  videoInfoRef <- newIORef R.defaultVideoInfo -  _ <- GI.Gtk.onScaleButtonValueChanged volumeButton (onScaleButtonValueChanged playbin)+  let ioRefs = R.IORefs {+        R.isWindowFullScreenRef = isWindowFullScreenRef+      , R.mouseMovedLastRef = mouseMovedLastRef+      , R.previousFileNamePathRef = previousFileNamePathRef+      , R.videoInfoRef = videoInfoRef+    } -  seekScaleHandlerId <- GI.Gtk.onRangeValueChanged seekScale (onRangeValueChanged playbin seekScale)+  playbin <- fromJust <$> GI.Gst.elementFactoryMake "playbin" (Just "MultimediaPlayerPlaybin")+  maybeGtkSink <- GI.Gst.elementFactoryMake "gtksink" (Just "MultimediaPlayerGtkSink")+  videoWidget <-+    case maybeGtkSink of+      Nothing -> do+        putStrLn "Could not create a GtkSink. Please install the bad plugins, version 1.8 or higher, for GStreamer 1."+        drawingArea <- GI.Gtk.drawingAreaNew+        GI.Gtk.widgetSetName drawingArea invalidVideoWidgetName+        GI.Gtk.unsafeCastTo GI.Gtk.Widget drawingArea+      Just gtkSink ->+        fromJust <$> Data.GI.Base.Properties.getObjectPropertyObject gtkSink "widget" GI.Gtk.Widget -  _ <- GI.GLib.timeoutAddSeconds GI.GLib.PRIORITY_DEFAULT 1 (updateSeekScale playbin seekScale seekScaleHandlerId)+  Data.GI.Base.Properties.setObjectPropertyObject playbin "video-sink" maybeGtkSink+  Data.GI.Base.Properties.setObjectPropertyBool   playbin "force-aspect-ratio" True+  GI.Gtk.boxPackStart videoWidgetBox videoWidget True True 0+  GI.Gtk.widgetSetHexpand videoWidget True+  GI.Gtk.widgetSetVexpand videoWidget True+  GI.Gtk.widgetSetSensitive videoWidget True -  _ <- GI.Gtk.onComboBoxChanged desiredVideoWidthComboBox $-      onComboBoxChanged fileChooserButton desiredVideoWidthComboBox drawingArea window+  playbinBus <- GI.Gst.elementGetBus playbin -  _ <- GI.Gtk.onWidgetButtonReleaseEvent fullscreenButton-      (onFullscreenButtonRelease isWindowFullScreenRef desiredVideoWidthComboBox fileChooserButton window)+  let guiObjects = R.GuiObjects {+        R.window = window+      , R.fileChooserButton = fileChooserButton+      , R.fileChooserButtonLabel = fileChooserButtonLabel+      , R.fileChooserDialog = fileChooserDialog+      , R.fileChooserEntry = fileChooserEntry+      , R.fileChooserWidget = fileChooserWidget+      , R.fileChooserCancelButton = fileChooserCancelButton+      , R.fileChooserOpenButton = fileChooserOpenButton+      , R.videoWidget = videoWidget+      , R.bottomControlsGtkBox = bottomControlsGtkBox+      , R.seekScale = seekScale+      , R.playPauseButton = playPauseButton+      , R.playImage = playImage+      , R.pauseImage = pauseImage+      , R.volumeButton = volumeButton+      , R.videoWidthSelectionComboBox = videoWidthSelectionComboBox+      , R.fullscreenButton = fullscreenButton+      , R.bufferingSpinner = bufferingSpinner+      , R.errorMessageDialog = errorMessageDialog+      , R.aboutButton = aboutButton+      , R.aboutDialog = aboutDialog+    } -  _ <- GI.Gtk.onWidgetWindowStateEvent window (onWidgetWindowStateEvent isWindowFullScreenRef)+  let application = R.Application {+        R.guiObjects = guiObjects+      , R.ioRefs = ioRefs+      , R.playbin = playbin+      , R.playbinBus = playbinBus+    } -  _ <- GI.Gtk.onWidgetButtonReleaseEvent aboutButton (onAboutButtonRelease aboutDialog)+  addWindowHandlers application [playVideoFromCommandLineIfNeeded]+  addPlaybinHandler application+  addFileChooserHandlers application+  addPlayPauseButtonClickHandler application+  addSeekHandlers application+  addVideoSizeSelectorHandler application+  addFullscreenButtonReleaseHandler application+  addWindowMouseMoveHandlers application+  addAboutHandler application+  addKeyboardEventHandler application+  addErrorMessageDialogHandler application -  _ <- GI.Gtk.onWidgetDestroy window (onWindowDestroy playbin)+  let operatingSystem = System.Info.os+  screenAndPowerManagementActions <- ScreensaverAndPowerManagement.disable operatingSystem    GI.Gtk.widgetShowAll window   GI.Gtk.main +  ScreensaverAndPowerManagement.enable operatingSystem screenAndPowerManagementActions+ builderGetObject ::   (GI.GObject.GObject b, GI.Gtk.IsBuilder a) =>   (Data.GI.Base.ManagedPtr b -> b) ->@@ -109,284 +178,3 @@ builderGetObject objectTypeClass builder objectId =   fromJust <$> GI.Gtk.builderGetObject builder (pack objectId) >>=     GI.Gtk.unsafeCastTo objectTypeClass--onDrawingAreaRealize ::-  GI.Gtk.Widget ->-  GI.Gst.Element ->-  GI.Gtk.Button ->-  GI.Gtk.WidgetRealizeCallback-onDrawingAreaRealize drawingArea playbin fullscreenButton = do-  gdkWindow <- fromJust <$> GI.Gtk.widgetGetWindow drawingArea-  x11Window <- GI.Gtk.unsafeCastTo GI.GdkX11.X11Window gdkWindow--  xid <- GI.GdkX11.x11WindowGetXid x11Window-  let xid' = fromIntegral xid :: CUIntPtr--  GI.GstVideo.videoOverlaySetWindowHandle (GstElement playbin) xid'--  GI.Gtk.widgetHide fullscreenButton--onFileChooserButtonFileSet ::-  GI.Gst.Element ->-  GI.Gtk.FileChooserButton ->-  GI.Gtk.VolumeButton ->-  IORef Bool ->-  GI.Gtk.ComboBoxText ->-  GI.Gtk.Switch ->-  GI.Gtk.Button ->-  GI.Gtk.Widget ->-  GI.Gtk.Window ->-  GI.Gtk.MessageDialog ->-  GI.Gtk.FileChooserButtonFileSetCallback-onFileChooserButtonFileSet-  playbin-  fileChooserButton-  volumeButton-  isWindowFullScreenRef-  desiredVideoWidthComboBox-  onOffSwitch-  fullscreenButton-  drawingArea-  window-  errorMessageDialog-  = do-  _ <- GI.Gst.elementSetState playbin GI.Gst.StateNull--  filename <- fromJust <$> GI.Gtk.fileChooserGetFilename fileChooserButton--  setPlaybinUriAndVolume playbin filename volumeButton--  isWindowFullScreen <- readIORef isWindowFullScreenRef--  desiredVideoWidth <- getDesiredVideoWidth desiredVideoWidthComboBox-  maybeWindowSize <- getWindowSize desiredVideoWidth filename--  case maybeWindowSize of-    Nothing -> do-      _ <- GI.Gst.elementSetState playbin GI.Gst.StatePaused-      GI.Gtk.windowUnfullscreen window-      GI.Gtk.switchSetActive onOffSwitch False-      GI.Gtk.widgetHide fullscreenButton-      GI.Gtk.widgetShow desiredVideoWidthComboBox-      resetWindowSize desiredVideoWidth fileChooserButton drawingArea window-      _ <- GI.Gtk.onDialogResponse errorMessageDialog (\ _ -> GI.Gtk.widgetHide errorMessageDialog)-      void $ GI.Gtk.dialogRun errorMessageDialog-    Just (width, height) -> do-      _ <- GI.Gst.elementSetState playbin GI.Gst.StatePlaying-      GI.Gtk.switchSetActive onOffSwitch True-      GI.Gtk.widgetShow fullscreenButton-      unless isWindowFullScreen $ setWindowSize width height fileChooserButton drawingArea window--onSwitchStateSet ::-  GI.Gst.Element ->-  Bool ->-  IO Bool-onSwitchStateSet playbin switchOn = do-  if switchOn-    then void $ GI.Gst.elementSetState playbin GI.Gst.StatePlaying-    else void $ GI.Gst.elementSetState playbin GI.Gst.StatePaused-  return switchOn--onScaleButtonValueChanged ::-  GI.Gst.Element ->-  Double ->-  IO ()-onScaleButtonValueChanged playbin volume =-    void $ Data.GI.Base.Properties.setObjectPropertyDouble playbin "volume" volume--onRangeValueChanged ::-  GI.Gst.Element ->-  GI.Gtk.Scale ->-  IO ()-onRangeValueChanged playbin seekScale = do-  (couldQueryDuration, duration) <- GI.Gst.elementQueryDuration playbin GI.Gst.FormatTime--  when couldQueryDuration $ do-    percentage' <- GI.Gtk.rangeGetValue seekScale-    let percentage = percentage' / 100.0-    let position = fromIntegral (round ((fromIntegral duration :: Double) * percentage) :: Int) :: Int64-    void $ GI.Gst.elementSeekSimple playbin GI.Gst.FormatTime [ GI.Gst.SeekFlagsFlush ] position--updateSeekScale ::-  GI.Gst.Element ->-  GI.Gtk.Scale ->-  Data.GI.Base.Signals.SignalHandlerId ->-  IO Bool-updateSeekScale playbin seekScale seekScaleHandlerId = do-  (couldQueryDuration, duration) <- GI.Gst.elementQueryDuration playbin GI.Gst.FormatTime-  (couldQueryPosition, position) <- GI.Gst.elementQueryPosition playbin GI.Gst.FormatTime--  let percentage =-        if couldQueryDuration && couldQueryPosition && duration > 0-          then 100.0 * (fromIntegral position / fromIntegral duration :: Double)-          else 0.0--  GI.GObject.signalHandlerBlock seekScale seekScaleHandlerId-  GI.Gtk.rangeSetValue seekScale percentage-  GI.GObject.signalHandlerUnblock seekScale seekScaleHandlerId--  return True--onComboBoxChanged ::-  GI.Gtk.FileChooserButton ->-  GI.Gtk.ComboBoxText ->-  GI.Gtk.Widget ->-  GI.Gtk.Window ->-  IO ()-onComboBoxChanged-  fileChooserButton-  desiredVideoWidthComboBox-  drawingArea-  window-  = do-  filename' <- GI.Gtk.fileChooserGetFilename fileChooserButton-  let filename = fromMaybe "" filename'--  desiredVideoWidth <- getDesiredVideoWidth desiredVideoWidthComboBox-  maybeWindowSize <- getWindowSize desiredVideoWidth filename--  case maybeWindowSize of-    Nothing -> resetWindowSize desiredVideoWidth fileChooserButton drawingArea window-    Just (width, height) -> setWindowSize width height fileChooserButton drawingArea window--onFullscreenButtonRelease ::-  IORef Bool ->-  GI.Gtk.ComboBoxText ->-  GI.Gtk.FileChooserButton ->-  GI.Gtk.Window ->-  GI.Gdk.EventButton ->-  IO Bool-onFullscreenButtonRelease-  isWindowFullScreenRef-  desiredVideoWidthComboBox-  fileChooserButton-  window-  _-  = do-  isWindowFullScreen <- readIORef isWindowFullScreenRef-  if isWindowFullScreen-    then do-      GI.Gtk.widgetShow desiredVideoWidthComboBox-      GI.Gtk.widgetShow fileChooserButton-      void $ GI.Gtk.windowUnfullscreen window-    else do-      GI.Gtk.widgetHide desiredVideoWidthComboBox-      GI.Gtk.widgetHide fileChooserButton-      void $ GI.Gtk.windowFullscreen window-  return True--onWidgetWindowStateEvent ::-  IORef Bool ->-  GI.Gdk.EventWindowState ->-  IO Bool-onWidgetWindowStateEvent isWindowFullScreenRef eventWindowState = do-  windowStates <- GI.Gdk.getEventWindowStateNewWindowState eventWindowState-  let isWindowFullScreen = Prelude.foldl (\ acc x -> acc || GI.Gdk.WindowStateFullscreen == x) False windowStates-  writeIORef isWindowFullScreenRef isWindowFullScreen-  return True--onAboutButtonRelease ::-  GI.Gtk.AboutDialog ->-  GI.Gdk.EventButton ->-  IO Bool-onAboutButtonRelease aboutDialog _ = do-  _ <- GI.Gtk.onDialogResponse aboutDialog (\ _ -> GI.Gtk.widgetHide aboutDialog)-  _ <- GI.Gtk.dialogRun aboutDialog-  return True--onWindowDestroy ::-  GI.Gst.Element ->-  IO ()-onWindowDestroy playbin = do-  _ <- GI.Gst.elementSetState playbin GI.Gst.StateNull-  _ <- GI.Gst.objectUnref playbin-  GI.Gtk.mainQuit--setPlaybinUriAndVolume ::-  GI.Gst.Element ->-  Prelude.String ->-  GI.Gtk.VolumeButton ->-  IO ()-setPlaybinUriAndVolume playbin filename volumeButton = do-  let uri = "file://" ++ filename-  volume <- GI.Gtk.scaleButtonGetValue volumeButton-  Data.GI.Base.Properties.setObjectPropertyDouble playbin "volume" volume-  Data.GI.Base.Properties.setObjectPropertyString playbin "uri" (Just $ pack uri)--getVideoInfo :: Prelude.String -> Prelude.String -> IO (Maybe Prelude.String)-getVideoInfo flag filename = do-  (code, out, _) <- catch (-      readProcessWithExitCode-        "exiftool"-        [flag, "-s", "-S", filename]-        ""-    ) (\ (_ :: Control.Exception.IOException) -> return (ExitFailure 1, "", ""))-  if code == System.Exit.ExitSuccess-    then return (Just out)-    else return Nothing--isVideo :: Prelude.String -> IO Bool-isVideo filename = do-  maybeOut <- getVideoInfo "-MIMEType" filename-  case maybeOut of-    Nothing -> return False-    Just out -> return ("video" `isInfixOf` pack out)--getWindowSize :: Int -> Prelude.String -> IO (Maybe (Int32, Int32))-getWindowSize desiredVideoWidth filename =-  isVideo filename >>=-  getWidthHeightString >>=-  splitWidthHeightString >>=-  widthHeightToDouble >>=-  ratio >>=-  windowSize-  where-    getWidthHeightString :: Bool -> IO (Maybe Prelude.String)-    getWidthHeightString False = return Nothing-    getWidthHeightString True = getVideoInfo "-ImageSize" filename-    splitWidthHeightString :: Maybe Prelude.String -> IO (Maybe [Text])-    splitWidthHeightString Nothing = return Nothing-    splitWidthHeightString (Just string) = return (Just (Data.Text.splitOn "x" (pack string)))-    widthHeightToDouble :: Maybe [Text] -> IO (Maybe Double, Maybe Double)-    widthHeightToDouble (Just (x:y:_)) = return (readMaybe (unpack x) :: Maybe Double, readMaybe (unpack y) :: Maybe Double)-    widthHeightToDouble _ = return (Nothing, Nothing)-    ratio :: (Maybe Double, Maybe Double) -> IO (Maybe Double)-    ratio (Just width, Just height) =-      if width <= 0.0 then return Nothing else return (Just (height / width))-    ratio _ = return Nothing-    windowSize :: Maybe Double -> IO (Maybe (Int32, Int32))-    windowSize Nothing = return Nothing-    windowSize (Just ratio') =-      return (Just (fromIntegral desiredVideoWidth :: Int32, round ((fromIntegral desiredVideoWidth :: Double) *  ratio') :: Int32))--getDesiredVideoWidth :: GI.Gtk.ComboBoxText -> IO Int-getDesiredVideoWidth = fmap (\ x -> read (Data.Text.unpack x) :: Int) . GI.Gtk.comboBoxTextGetActiveText--setWindowSize ::-  Int32 ->-  Int32 ->-  GI.Gtk.FileChooserButton ->-  GI.Gtk.Widget ->-  GI.Gtk.Window ->-  IO ()-setWindowSize width height fileChooserButton drawingArea window = do-  GI.Gtk.setWidgetWidthRequest fileChooserButton width--  GI.Gtk.setWidgetWidthRequest drawingArea width-  GI.Gtk.setWidgetHeightRequest drawingArea height--  GI.Gtk.setWidgetWidthRequest window width-  GI.Gtk.setWidgetHeightRequest window height-  GI.Gtk.windowResize window width (if height <= 0 then 1 else height)--resetWindowSize ::-  (Integral a) =>-  a ->-  GI.Gtk.FileChooserButton ->-  GI.Gtk.Widget ->-  GI.Gtk.Window ->-  IO ()-resetWindowSize width' fileChooserButton drawingArea window = do-  let width = fromIntegral width' :: Int32-  GI.Gtk.widgetQueueDraw drawingArea-  setWindowSize width 0 fileChooserButton drawingArea window
+ src/Mouse.hs view
@@ -0,0 +1,69 @@+{-+  Movie Monad+  (C) 2017 David lettier+  lettier.com+-}++module Mouse where++import Control.Monad+import Data.Maybe+import Data.Text+import Data.IORef+import Data.Time.Clock.POSIX+import qualified GI.Gdk+import qualified GI.Gtk++import qualified Records as R++addWindowMouseMoveHandlers :: R.Application -> IO ()+addWindowMouseMoveHandlers+  application@R.Application {+        R.guiObjects = R.GuiObjects {+              R.videoWidget = videoWidget+            , R.seekScale = seekScale+          }+    }+  =+  void (GI.Gtk.onWidgetMotionNotifyEvent videoWidget (windowMouseMoveHandler application)) >>+  void (GI.Gtk.onWidgetMotionNotifyEvent seekScale (windowMouseMoveHandler application))++windowMouseMoveHandler ::+  R.Application ->+  GI.Gdk.EventMotion ->+  IO Bool+windowMouseMoveHandler+  R.Application {+        R.guiObjects = R.GuiObjects {+              R.window = window+            , R.fileChooserButton = fileChooserButton+            , R.bottomControlsGtkBox = bottomControlsGtkBox+          }+      , R.ioRefs = R.IORefs {+            R.isWindowFullScreenRef = isWindowFullScreenRef+          , R.mouseMovedLastRef = mouseMovedLastRef+        }+    }+  _+  = do+  isWindowFullScreen <- readIORef isWindowFullScreenRef+  unless isWindowFullScreen $ GI.Gtk.widgetShow fileChooserButton+  GI.Gtk.widgetShow bottomControlsGtkBox+  setCursor window Nothing+  timeNow <- getPOSIXTime+  atomicWriteIORef mouseMovedLastRef (round timeNow)+  return False++setCursor :: GI.Gtk.Window -> Maybe Text -> IO ()+setCursor window Nothing =+  fromJust <$> GI.Gtk.widgetGetWindow window >>=+  flip GI.Gdk.windowSetCursor (Nothing :: Maybe GI.Gdk.Cursor)+setCursor window (Just cursorType) = do+  gdkWindow <- fromJust <$> GI.Gtk.widgetGetWindow window+  maybeCursor <- makeCursor gdkWindow cursorType+  GI.Gdk.windowSetCursor gdkWindow maybeCursor++makeCursor :: GI.Gdk.Window -> Text -> IO (Maybe GI.Gdk.Cursor)+makeCursor window cursorType =+  GI.Gdk.windowGetDisplay window >>=+  flip GI.Gdk.cursorNewFromName cursorType
+ src/PlayPause.hs view
@@ -0,0 +1,81 @@+{-+  Movie Monad+  (C) 2017 David lettier+  lettier.com+-}++{-# LANGUAGE OverloadedStrings #-}++module PlayPause where++import Control.Monad+import Data.Text+import qualified GI.Gdk+import qualified GI.Gtk+import qualified GI.Gst++import qualified Records as R++addPlayPauseButtonClickHandler :: R.Application -> IO ()+addPlayPauseButtonClickHandler+  application@R.Application {+        R.guiObjects = R.GuiObjects {+              R.playPauseButton = playPauseButton+          }+    }+  =+  void $ GI.Gtk.onWidgetButtonReleaseEvent playPauseButton (playPauseButtonClickHandler application)++playPauseButtonClickHandler ::+  R.Application ->+  GI.Gdk.EventButton ->+  IO Bool+playPauseButtonClickHandler+  R.Application {+        R.guiObjects = R.GuiObjects {+              R.playPauseButton = playPauseButton+            , R.playImage = playImage+            , R.pauseImage = pauseImage+          }+      , R.playbin = playbin+    }+  _+  = do+  isPlaying <- isPlayPauseButtonPlaying playPauseButton+  if isPlaying+    then do+      setPlayPauseButton playPauseButton playImage pauseImage False+      void $ GI.Gst.elementSetState playbin GI.Gst.StatePaused+    else do+      setPlayPauseButton playPauseButton playImage pauseImage True+      void $ GI.Gst.elementSetState playbin GI.Gst.StatePlaying+  return False++setPlayPauseButton ::+  GI.Gtk.Button ->+  GI.Gtk.Image ->+  GI.Gtk.Image ->+  Bool ->+  IO ()+setPlayPauseButton playPauseButton _ pauseImage True = do+  GI.Gtk.buttonSetImage playPauseButton (Just pauseImage)+  GI.Gtk.widgetSetTooltipText playPauseButton (Just "Click to pause")+setPlayPauseButton playPauseButton playImage _ False = do+  GI.Gtk.buttonSetImage playPauseButton (Just playImage)+  GI.Gtk.widgetSetTooltipText playPauseButton (Just "Click to play")++isPlayPauseButtonPlaying ::+  GI.Gtk.Button ->+  IO Bool+isPlayPauseButtonPlaying playPauseButton =+  GI.Gtk.buttonGetImage playPauseButton >>= getImage >>= getName >>= getMatch+  where+    getImage :: Maybe GI.Gtk.Widget -> IO (Maybe GI.Gtk.Image)+    getImage Nothing       = return Nothing+    getImage (Just widget) = GI.Gtk.castTo GI.Gtk.Image widget+    getName :: Maybe GI.Gtk.Image -> IO (Maybe Text)+    getName Nothing        = return Nothing+    getName (Just image)   = GI.Gtk.getImageStock image+    getMatch :: Maybe Text -> IO Bool+    getMatch Nothing       = return False+    getMatch (Just text)   = return ("gtk-media-pause" == text)
+ src/Playbin.hs view
@@ -0,0 +1,122 @@+{-+  Movie Monad+  (C) 2017 David lettier+  lettier.com+-}++{-# LANGUAGE OverloadedStrings #-}++module Playbin where++import Control.Monad+import Data.Text+import Data.GI.Base.Properties+import GI.GLib+import qualified GI.Gtk+import qualified GI.Gst++import qualified Records as R+import Window+import PlayPause+import ErrorMessage+import Uri++addPlaybinHandler :: R.Application -> IO ()+addPlaybinHandler+  R.Application {+        R.guiObjects = guiObjects@R.GuiObjects {+              R.volumeButton = volumeButton+          }+      , R.playbin = playbin+      , R.playbinBus = playbinBus+    }+  =+  void(+      GI.Gst.busAddWatch playbinBus GI.GLib.PRIORITY_DEFAULT (+          pipelineBusMessageHandler+            guiObjects+            playbin+        )+    ) >>+  void (+      GI.Gtk.onScaleButtonValueChanged volumeButton (volumeButtonValueChangedHandler playbin)+    )++pipelineBusMessageHandler ::+  R.GuiObjects ->+  GI.Gst.Element ->+  GI.Gst.Bus ->+  GI.Gst.Message ->+  IO Bool+pipelineBusMessageHandler+  guiObjects@R.GuiObjects {+        R.seekScale = seekScale+      , R.fileChooserEntry = fileChooserEntry+      , R.fileChooserButtonLabel = fileChooserButtonLabel+      , R.volumeButton = volumeButton+      , R.errorMessageDialog = errorMessageDialog+      , R.bufferingSpinner = bufferingSpinner+      , R.playPauseButton = playPauseButton+    }+  playbin+  _+  message+  = do+  messageTypes <- GI.Gst.getMessageType message+  let messageType = case messageTypes of+                      [] -> GI.Gst.MessageTypeUnknown+                      (msg:_) -> msg+  entryText <- GI.Gtk.entryGetText fileChooserEntry+  labelText <- GI.Gtk.labelGetText fileChooserButtonLabel+  when (+      messageType == GI.Gst.MessageTypeError &&+      (+        (not . Data.Text.null) entryText ||+        labelText /= "Open"+      )+    ) $ do+      (gError, text) <- GI.Gst.messageParseError message+      gErrorText <- GI.Gst.gerrorMessage gError+      Prelude.mapM_ print [text, "\n", gErrorText]+      GI.Gtk.entrySetText fileChooserEntry ""+      GI.Gtk.labelSetText fileChooserButtonLabel "Open"+      _ <- GI.Gst.elementSetState playbin GI.Gst.StateNull+      setPlaybinUriAndVolume playbin "" volumeButton+      resetWindow guiObjects+      runErrorMessageDialog+        errorMessageDialog+        (Data.Text.concat ["There was a problem trying to play the video \"", entryText, "\"."])+  when (messageType == GI.Gst.MessageTypeBuffering) $ do+    percent <- GI.Gst.messageParseBuffering message+    isPlaying <- isPlayPauseButtonPlaying playPauseButton+    if percent >= 100+      then do+        GI.Gtk.widgetHide bufferingSpinner+        GI.Gtk.setSpinnerActive bufferingSpinner False+        GI.Gtk.widgetSetSensitive seekScale True+        when isPlaying $ void $ GI.Gst.elementSetState playbin GI.Gst.StatePlaying+      else do+        GI.Gtk.widgetShow bufferingSpinner+        GI.Gtk.setSpinnerActive bufferingSpinner True+        GI.Gtk.widgetSetSensitive seekScale False+        void $ GI.Gst.elementSetState playbin GI.Gst.StatePaused+    return ()+  return True++volumeButtonValueChangedHandler ::+  GI.Gst.Element ->+  Double ->+  IO ()+volumeButtonValueChangedHandler playbin volume =+    void $ Data.GI.Base.Properties.setObjectPropertyDouble playbin "volume" volume++setPlaybinUriAndVolume ::+  GI.Gst.Element ->+  Prelude.String ->+  GI.Gtk.VolumeButton ->+  IO ()+setPlaybinUriAndVolume playbin fileName volumeButton = do+  uri <- addUriSchemeIfNone fileName+  volume <- GI.Gtk.scaleButtonGetValue volumeButton+  Data.GI.Base.Properties.setObjectPropertyDouble playbin "volume" volume+  Data.GI.Base.Properties.setObjectPropertyString playbin "uri" (Just $ pack uri)
+ src/Records.hs view
@@ -0,0 +1,76 @@+{-+  Movie Monad+  (C) 2017 David lettier+  lettier.com+-}++module Records where++import Data.IORef+import Data.Text+import qualified GI.Gtk+import qualified GI.Gst++data VideoInfo = VideoInfo {+      uri         :: Prelude.String+    , isLocalFile :: Bool+    , isVideo     :: Bool+    , isSeekable  :: Bool+    , videoWidth  :: Int+    , videoHeight :: Int+  }++defaultVideoInfo :: VideoInfo+defaultVideoInfo  = VideoInfo {+      uri         = ""+    , isLocalFile = False+    , isVideo     = False+    , isSeekable  = False+    , videoWidth  = 800+    , videoHeight = 600+  }++data GuiObjects = GuiObjects {+      window :: GI.Gtk.Window+    , fileChooserButton :: GI.Gtk.Button+    , fileChooserButtonLabel :: GI.Gtk.Label+    , fileChooserDialog :: GI.Gtk.Dialog+    , fileChooserEntry :: GI.Gtk.Entry+    , fileChooserWidget :: GI.Gtk.FileChooserWidget+    , fileChooserCancelButton :: GI.Gtk.Button+    , fileChooserOpenButton :: GI.Gtk.Button+    , videoWidget :: GI.Gtk.Widget+    , bottomControlsGtkBox :: GI.Gtk.Box+    , seekScale :: GI.Gtk.Scale+    , playPauseButton :: GI.Gtk.Button+    , playImage :: GI.Gtk.Image+    , pauseImage :: GI.Gtk.Image+    , volumeButton :: GI.Gtk.VolumeButton+    , videoWidthSelectionComboBox :: GI.Gtk.ComboBoxText+    , fullscreenButton :: GI.Gtk.Button+    , bufferingSpinner :: GI.Gtk.Spinner+    , errorMessageDialog :: GI.Gtk.MessageDialog+    , aboutButton :: GI.Gtk.Button+    , aboutDialog :: GI.Gtk.AboutDialog+  }++data IORefs = IORefs {+      isWindowFullScreenRef :: IORef Bool+    , mouseMovedLastRef :: IORef Integer+    , previousFileNamePathRef :: IORef Data.Text.Text+    , videoInfoRef :: IORef VideoInfo+  }++data ScreensaverAndPowerManagementActions = ScreensaverAndPowerManagementActions {+      disabledPowerManagement :: Bool+    , disabledXScreensaver :: Bool+    , disabledCinnamonScreensaver :: Bool+    , disabledGnomeScreensaver :: Bool+  }++data Application = Application {+      guiObjects :: GuiObjects+    , ioRefs :: IORefs+    , playbin :: GI.Gst.Element+    , playbinBus :: GI.Gst.Bus+  }
+ src/ScreensaverAndPowerManagement.hs view
@@ -0,0 +1,108 @@+{-+  Movie Monad+  (C) 2017 David lettier+  lettier.com+-}++{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}++module ScreensaverAndPowerManagement where++import System.Process+import Control.Monad+import Data.Text++import qualified Records as R (ScreensaverAndPowerManagementActions(..))+import Utils (safeRunProcessGetOutput, linuxProcessIsRunning, spawnLinuxProcessAndDisown)++disable :: String -> IO R.ScreensaverAndPowerManagementActions+disable "linux" = do+  disabledPowerManagement <- disablePowerManagement+  disabledXScreensaver <- disableXScreensaver+  disabledCinnamonScreensaver <- disableCinnamonScreensaver+  disabledGnomeScreensaver <- disableGnomeScreensaver+  return R.ScreensaverAndPowerManagementActions {+        R.disabledPowerManagement = disabledPowerManagement+      , R.disabledXScreensaver = disabledXScreensaver+      , R.disabledCinnamonScreensaver = disabledCinnamonScreensaver+      , R.disabledGnomeScreensaver = disabledGnomeScreensaver+    }+disable _ =+  return R.ScreensaverAndPowerManagementActions {+        R.disabledPowerManagement = False+      , R.disabledXScreensaver = False+      , R.disabledCinnamonScreensaver = False+      , R.disabledGnomeScreensaver = False+    }++enable :: String -> R.ScreensaverAndPowerManagementActions -> IO ()+enable+  "linux"+  R.ScreensaverAndPowerManagementActions {+        R.disabledPowerManagement = disabledPowerManagement+      , R.disabledXScreensaver = disabledXScreensaver+      , R.disabledCinnamonScreensaver = disabledCinnamonScreensaver+      , R.disabledGnomeScreensaver = disabledGnomeScreensaver+    }+  = do+  when disabledPowerManagement enablePowerManagement+  when disabledXScreensaver enableXScreensaver+  when disabledCinnamonScreensaver enableCinnamonScreensaver+  when disabledGnomeScreensaver enableGnomeScreensaver+  return ()+enable _ _ = return ()++disablePowerManagement :: IO Bool+disablePowerManagement = do+  (_, out, _) <- safeRunProcessGetOutput "xset" ["-q"]+  let isEnabled =+        Data.Text.isInfixOf "dpms is enabled" $+          Data.Text.toLower $+            Data.Text.pack out+  if isEnabled+    then do+      _ <- System.Process.spawnCommand "sleep 1; xset -dpms s off"+      return True+    else return False++enablePowerManagement :: IO ()+enablePowerManagement =+  void $+    System.Process.spawnCommand "sleep 1; xset +dpms s on"++disableXScreensaver :: IO Bool+disableXScreensaver =+  disableScreensaver+    "xscreensaver"+    "xscreensaver-command --exit"++enableXScreensaver :: IO ()+enableXScreensaver = spawnLinuxProcessAndDisown "xscreensaver -no-splash"++disableCinnamonScreensaver :: IO Bool+disableCinnamonScreensaver =+  disableScreensaver+    "cinnamon-screensaver"+    "cinnamon-screensaver-command --exit"++enableCinnamonScreensaver :: IO ()+enableCinnamonScreensaver = spawnLinuxProcessAndDisown "cinnamon-screensaver"++disableGnomeScreensaver :: IO Bool+disableGnomeScreensaver =+  disableScreensaver+    "gnome-screensaver"+    "gnome-screensaver-command --exit"++enableGnomeScreensaver :: IO ()+enableGnomeScreensaver = spawnLinuxProcessAndDisown "gnome-screensaver"++disableScreensaver :: String -> String -> IO Bool+disableScreensaver processName disableCommand = do+  isRunning <- linuxProcessIsRunning processName+  if isRunning+    then do+      spawnLinuxProcessAndDisown disableCommand+      return True+    else return False
+ src/Seek.hs view
@@ -0,0 +1,93 @@+{-+  Movie Monad+  (C) 2017 David lettier+  lettier.com+-}++module Seek where++import Control.Monad+import Data.IORef+import Data.Int+import Data.Time.Clock.POSIX+import Data.GI.Base.Signals+import GI.GLib+import GI.GObject+import qualified GI.Gtk+import qualified GI.Gst++import qualified Records as R++addSeekHandlers :: R.Application -> IO ()+addSeekHandlers+  R.Application {+        R.guiObjects = R.GuiObjects {+              R.seekScale = seekScale+          }+      , R.ioRefs = R.IORefs {+              R.videoInfoRef = videoInfoRef+        }+      , R.playbin = playbin+    }+  = do+  seekScaleHandlerId <- GI.Gtk.onRangeValueChanged seekScale (+      onRangeValueChanged+        playbin+        seekScale+        videoInfoRef+    )+  void (+      GI.GLib.timeoutAdd GI.GLib.PRIORITY_DEFAULT 41 (+          updateSeekScale+            playbin+            seekScale+            seekScaleHandlerId+        )+    )++seekRequestLastDeltaThreshold :: R.VideoInfo -> POSIXTime+seekRequestLastDeltaThreshold R.VideoInfo { R.isLocalFile = True }  = 0.0+seekRequestLastDeltaThreshold R.VideoInfo { R.isLocalFile = False } = 0.0++onRangeValueChanged ::+  GI.Gst.Element ->+  GI.Gtk.Scale ->+  IORef R.VideoInfo ->+  IO ()+onRangeValueChanged+  playbin+  seekScale+  videoInfoRef+  = do+  (couldQueryDuration, duration) <- GI.Gst.elementQueryDuration playbin GI.Gst.FormatTime+  when couldQueryDuration $ do+    percentage' <- GI.Gtk.rangeGetValue seekScale+    let percentage = percentage' / 100.0+    let position = fromIntegral (round ((fromIntegral duration :: Double) * percentage) :: Int) :: Int64+    void $ GI.Gst.elementSeekSimple playbin GI.Gst.FormatTime [ GI.Gst.SeekFlagsFlush ] position+    videoInfoGathered <- readIORef videoInfoRef+    -- When playing over a network (not local),+    -- sending too many asynchronous seek requests will cause GStreamer to error.+    -- Block until this seek request finishes which will block the main GTK main thread.+    -- Note that by blocking here, the seek scale will not slide.+    -- The user will have to point and click on the scale track.+    unless (R.isLocalFile videoInfoGathered) $ void $ GI.Gst.elementGetState playbin GI.Gst.CLOCK_TIME_NONE++updateSeekScale ::+  GI.Gst.Element ->+  GI.Gtk.Scale ->+  Data.GI.Base.Signals.SignalHandlerId ->+  IO Bool+updateSeekScale+  playbin+  seekScale+  seekScaleHandlerId+  = do+  (couldQueryDuration, duration) <- GI.Gst.elementQueryDuration playbin GI.Gst.FormatTime+  (couldQueryPosition, position) <- GI.Gst.elementQueryPosition playbin GI.Gst.FormatTime+  when (couldQueryDuration && couldQueryPosition && duration > 0) $ do+    let percentage = 100.0 * (fromIntegral position / fromIntegral duration :: Double)+    GI.GObject.signalHandlerBlock seekScale seekScaleHandlerId+    GI.Gtk.rangeSetValue seekScale percentage+    GI.GObject.signalHandlerUnblock seekScale seekScaleHandlerId+  return True
+ src/Uri.hs view
@@ -0,0 +1,66 @@+{-+  Movie Monad+  (C) 2017 David lettier+  lettier.com+-}++{-# LANGUAGE OverloadedStrings #-}++module Uri where++import System.FilePath+import Filesystem+import Filesystem.Path.CurrentOS+import Data.Text++removeFileUriScheme :: Prelude.String -> Prelude.String+removeFileUriScheme str =+  if hasFileUriScheme str+    then Data.Text.unpack $ Data.Text.replace "file://" "" uri+    else Data.Text.unpack uri+  where+    uri :: Data.Text.Text+    uri = Data.Text.pack str++hasFileUriScheme :: Prelude.String -> Bool+hasFileUriScheme = hasUriScheme "file://"++hasHttpUriScheme :: Prelude.String -> Bool+hasHttpUriScheme str = isHttp || isHttps+  where+    isHttp :: Bool+    isHttp  = hasUriScheme "http://"  str+    isHttps :: Bool+    isHttps = hasUriScheme "https://" str++hasUriScheme :: Prelude.String -> Prelude.String -> Bool+hasUriScheme scheme uri = toLower' scheme `Data.Text.isPrefixOf` toLower' uri+  where+    toLower' :: Prelude.String -> Text+    toLower' = Data.Text.toLower . Data.Text.pack++addUriSchemeIfNone :: Prelude.String -> IO Prelude.String+addUriSchemeIfNone filePathName =+  isLocalFile filePathName >>=+  \ local ->+    return $+      if local+        then if hasFileUriScheme filePathName then filePathName else "file://" ++ filePathName+        else if hasHttpUriScheme filePathName then filePathName else "http://" ++ filePathName++isLocalFile :: Prelude.String -> IO Bool+isLocalFile = Filesystem.isFile . Filesystem.Path.CurrentOS.fromText . Data.Text.pack . removeFileUriScheme++toAbsoluteUri :: String -> IO String+toAbsoluteUri uri = do+  cwd <- getWorkingDirectory+  let cwd' = case toText cwd of+              Right x -> x+              Left  y -> y+  isLocal <- isLocalFile uri+  if not isLocal+    then return uri+    else+      if isRelative uri+        then return $ normalise $ combine (Data.Text.unpack cwd') uri+        else return uri
+ src/Utils.hs view
@@ -0,0 +1,58 @@+{-+  Movie Monad+  (C) 2017 David lettier+  lettier.com+-}++{-# LANGUAGE ScopedTypeVariables #-}++module Utils where++import System.Exit+import System.Process+import System.FilePath+import Control.Monad+import Control.Exception+import Data.List+import Data.Text+import Data.Int+import qualified GI.Gtk++fileNameFromFilePathName :: Data.Text.Text -> Data.Text.Text+fileNameFromFilePathName = Data.Text.pack . System.FilePath.takeFileName . Data.Text.unpack++enumToInt32 :: (Enum a, Ord a) => a -> Int32+enumToInt32 enum = fromIntegral (fromEnum enum) :: Int32++isTextEmpty :: Data.Text.Text -> Bool+isTextEmpty = Data.Text.null . Data.Text.strip++getSelectedVideoWidth :: GI.Gtk.ComboBoxText -> IO Int+getSelectedVideoWidth = fmap (\ x -> read (Data.Text.unpack x) :: Int) . GI.Gtk.comboBoxTextGetActiveText++linuxProcessIsRunning :: String -> IO Bool+linuxProcessIsRunning processName = do+  (_, out, _) <- safeRunProcessGetOutput "pgrep" ["-f", processName]+  return (not $ Data.List.null out)++spawnLinuxProcessAndDisown :: String -> IO ()+spawnLinuxProcessAndDisown processCommand =+  void $+    System.Process.spawnCommand+      (+              "nohup "+          ++  processCommand+          ++  " </dev/null &>/dev/null & disown"+        )++safeRunProcessGetOutput :: String -> [String] -> IO (System.Exit.ExitCode, String, String)+safeRunProcessGetOutput processName args =+  catch (+      readProcessWithExitCode+        processName+        args+        ""+    ) (\ (e :: Control.Exception.IOException) ->+        print e >>+        return (ExitFailure 1, "", "")+      )
+ src/VideoInfo.hs view
@@ -0,0 +1,76 @@+{-+  Movie Monad+  (C) 2017 David lettier+  lettier.com+-}++{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}++module VideoInfo where++import System.Exit+import Text.Read+import Data.Maybe+import Data.Text+import Data.IORef++import qualified Records as R+import Uri+import Utils++getVideoInfoRaw :: Prelude.String -> IO (Maybe Prelude.String)+getVideoInfoRaw uri = do+  (code, out, _) <- safeRunProcessGetOutput "gst-discoverer-1.0" [uri, "-v"]+  if code == System.Exit.ExitSuccess+    then return (Just out)+    else return Nothing++getVideoInfo :: IORef R.VideoInfo -> Prelude.String -> IO R.VideoInfo+getVideoInfo videoInfoRef filePathName = do+  videoInfoGathered <- readIORef videoInfoRef+  uri <- addUriSchemeIfNone filePathName+  cacheDo (R.uri videoInfoGathered == uri) videoInfoGathered uri+  where+    widthField :: Data.Text.Text+    widthField = "width: "+    heightField :: Data.Text.Text+    heightField = "height: "+    seekableField :: Data.Text.Text+    seekableField = "seekable: "+    cacheDo :: Bool -> R.VideoInfo -> Prelude.String -> IO R.VideoInfo+    cacheDo True  videoInfoGathered _   = return videoInfoGathered+    cacheDo False _                 uri = do+      videoInfoRaw <- getVideoInfoRaw uri+      processVideoInfoRaw videoInfoRaw uri+    processVideoInfoRaw :: Maybe Prelude.String -> Prelude.String -> IO R.VideoInfo+    processVideoInfoRaw Nothing _ = do+      let videoInfoGathered = R.defaultVideoInfo+      atomicWriteIORef videoInfoRef videoInfoGathered+      return videoInfoGathered+    processVideoInfoRaw (Just str) uri = do+      let text      = Data.Text.toLower $ Data.Text.pack str+      let textLines = Data.Text.lines text+      let videoInfoGathered = R.VideoInfo {+              R.uri          = uri+            , R.isLocalFile  = hasFileUriScheme uri+            , R.isVideo      = "video: video/" `Data.Text.isInfixOf` text+            , R.isSeekable   = "yes" == getField seekableField textLines+            , R.videoWidth   = getDimension R.videoWidth  widthField textLines+            , R.videoHeight  = getDimension R.videoHeight heightField textLines+          }+      atomicWriteIORef videoInfoRef videoInfoGathered+      return videoInfoGathered+    getDimension :: (R.VideoInfo -> Int) -> Data.Text.Text -> [Data.Text.Text] -> Int+    getDimension f field lines' = fromMaybe (+        f R.defaultVideoInfo+      ) (readMaybe (Data.Text.unpack $ getField field lines') :: Maybe Int)+    getField :: Data.Text.Text -> [Data.Text.Text] -> Data.Text.Text+    getField field =+      Prelude.foldl (\ acc l ->+          if not $ Data.Text.null acc+            then acc+            else if field `Data.Text.isInfixOf` l+              then Data.Text.replace field "" $ Data.Text.strip l+              else ""+        ) ""
+ src/VideoSizeSelector.hs view
@@ -0,0 +1,54 @@+{-+  Movie Monad+  (C) 2017 David lettier+  lettier.com+-}++module VideoSizeSelector where++import Control.Monad+import Data.Text+import qualified GI.Gtk++import qualified Records as R+import Window+import VideoInfo+import Utils++addVideoSizeSelectorHandler :: R.Application -> IO ()+addVideoSizeSelectorHandler+  application@R.Application {+        R.guiObjects = R.GuiObjects {+              R.videoWidthSelectionComboBox = videoWidthSelectionComboBox+          }+    }+  = void (+        GI.Gtk.onComboBoxChanged+          videoWidthSelectionComboBox+          (videoSizeSelectionHandler application)+      )++videoSizeSelectionHandler ::+  R.Application ->+  IO ()+videoSizeSelectionHandler+  R.Application {+        R.guiObjects = guiObjects@R.GuiObjects {+              R.window = window+            , R.fileChooserButton = fileChooserButton+            , R.videoWidget = videoWidget+            , R.videoWidthSelectionComboBox = videoWidthSelectionComboBox+            , R.fileChooserEntry = fileChooserEntry+          }+      , R.ioRefs = R.IORefs {+              R.videoInfoRef = videoInfoRef+        }+    }+  = do+  filePathName <- Data.Text.unpack <$> GI.Gtk.entryGetText fileChooserEntry+  videoWidthSelection <- getSelectedVideoWidth videoWidthSelectionComboBox+  retrievedVideoInfo <- getVideoInfo videoInfoRef filePathName+  maybeWindowSize <- calculateWindowSize videoWidthSelection retrievedVideoInfo+  case maybeWindowSize of+    Nothing -> resetWindow guiObjects+    Just (width, height) -> setWindowSize width height fileChooserButton videoWidget window
+ src/Window.hs view
@@ -0,0 +1,195 @@+{-+  Movie Monad+  (C) 2017 David lettier+  lettier.com+-}++{-# LANGUAGE OverloadedStrings #-}++module Window where++import GHC.Word+import Control.Monad+import Data.Int+import Data.IORef+import Data.Time.Clock.POSIX+import GI.GLib+import qualified GI.Gdk+import qualified GI.Gtk+import qualified GI.Gst++import qualified Records as R+import Constants+import Mouse+import PlayPause+import Utils++addWindowHandlers :: R.Application -> [R.Application -> IO ()] -> IO ()+addWindowHandlers+  application@R.Application {+        R.guiObjects = R.GuiObjects {+              R.window = window+            , R.videoWidget = videoWidget+          }+      , R.ioRefs = R.IORefs {+              R.isWindowFullScreenRef = isWindowFullScreenRef+          }+      , R.playbin = playbin+    }+  functionsToRunOnWindowRealized+  =+  void (+      GI.Gtk.onWidgetRealize videoWidget (+          windowRealizedHandler+            application+            functionsToRunOnWindowRealized+        )+    ) >>+  void (+      GI.Gtk.onWidgetWindowStateEvent window (widgetWindowStateEventHandler isWindowFullScreenRef)+    ) >>+  void (+      GI.Gtk.onWidgetDestroy window (windowDestroyHandler playbin)+    ) >>+  void (+      GI.GLib.timeoutAddSeconds GI.GLib.PRIORITY_DEFAULT 1 (hideOnScreenControls application)+    )++windowRealizedHandler ::+  R.Application ->+  [R.Application -> IO ()] ->+  GI.Gtk.WidgetRealizeCallback+windowRealizedHandler+  application@R.Application {+        R.guiObjects = guiObjects@R.GuiObjects {+              R.videoWidget = videoWidget+            , R.seekScale = seekScale+          }+    }+  functionsToRunOnWindowRealized+  = do+  let eventMask = enumToInt32 GI.Gdk.EventMaskAllEventsMask+  GI.Gtk.widgetAddEvents videoWidget eventMask+  GI.Gtk.widgetAddEvents seekScale eventMask+  resetWindow guiObjects+  mapM_ (\ f -> f application) functionsToRunOnWindowRealized++widgetWindowStateEventHandler ::+  IORef Bool ->+  GI.Gdk.EventWindowState ->+  IO Bool+widgetWindowStateEventHandler isWindowFullScreenRef eventWindowState = do+  windowStates <- GI.Gdk.getEventWindowStateNewWindowState eventWindowState+  let isWindowFullScreen = Prelude.foldl (\ acc x ->+          acc || GI.Gdk.WindowStateFullscreen == x+        ) False windowStates+  atomicWriteIORef isWindowFullScreenRef isWindowFullScreen+  return True++windowDestroyHandler ::+  GI.Gst.Element ->+  IO ()+windowDestroyHandler playbin = do+  _ <- GI.Gst.elementSetState playbin GI.Gst.StateNull+  _ <- GI.Gst.objectUnref playbin+  GI.Gtk.mainQuit++hideOnScreenControls ::+  R.Application ->+  IO Bool+hideOnScreenControls+  R.Application {+        R.guiObjects = R.GuiObjects {+              R.window = window+            , R.fileChooserButton = fileChooserButton+            , R.bottomControlsGtkBox = bottomControlsGtkBox+          }+      , R.ioRefs = R.IORefs {+            R.videoInfoRef = videoInfoRef+          , R.mouseMovedLastRef = mouseMovedLastRef+        }+      , R.playbin = playbin+    }+  = do+  isVideo <- R.isVideo <$> readIORef videoInfoRef+  mouseMovedLast <- readIORef mouseMovedLastRef+  timeNow <- fmap round getPOSIXTime+  (_, playBinState, _) <- GI.Gst.elementGetState playbin (fromIntegral GI.Gst.MSECOND :: GHC.Word.Word64)+  let isPlaying = GI.Gst.StatePlaying == playBinState+  let delta = timeNow - mouseMovedLast+  when (isPlaying && isVideo && delta >= hideOnScreenControlsInterval) $ do+    GI.Gtk.widgetHide fileChooserButton+    GI.Gtk.widgetHide bottomControlsGtkBox+    setCursor window (Just "none")+    atomicWriteIORef mouseMovedLastRef timeNow+  return True++calculateWindowSize :: Int -> R.VideoInfo -> IO (Maybe (Int32, Int32))+calculateWindowSize videoWidthSelection retrievedVideoInfo =+  widthHeightToDouble retrievedVideoInfo >>=+  ratio >>=+  windowSize+  where+    widthHeightToDouble :: R.VideoInfo -> IO (Maybe Double, Maybe Double)+    widthHeightToDouble R.VideoInfo { R.isVideo = False } = return (Nothing, Nothing)+    widthHeightToDouble R.VideoInfo { R.videoWidth = w, R.videoHeight = h } =+      return (Just $ fromIntegral w :: Maybe Double, Just $ fromIntegral h :: Maybe Double)+    ratio :: (Maybe Double, Maybe Double) -> IO (Maybe Double)+    ratio (Just width, Just height) =+      if width <= 0.0 then return Nothing else return (Just (height / width))+    ratio _ = return Nothing+    windowSize :: Maybe Double -> IO (Maybe (Int32, Int32))+    windowSize Nothing = return Nothing+    windowSize (Just ratio') =+      return (+          Just (+                fromIntegral videoWidthSelection :: Int32+              , round ((fromIntegral videoWidthSelection :: Double) *  ratio') :: Int32+            )+        )++setWindowSize ::+  Int32 ->+  Int32 ->+  GI.Gtk.Button ->+  GI.Gtk.Widget ->+  GI.Gtk.Window ->+  IO ()+setWindowSize width height fileChooserButton videoWidget window = do+  GI.Gtk.setWidgetWidthRequest fileChooserButton width+  GI.Gtk.setWidgetWidthRequest videoWidget width+  GI.Gtk.setWidgetHeightRequest videoWidget height+  GI.Gtk.setWidgetWidthRequest window width+  GI.Gtk.setWidgetHeightRequest window height+  GI.Gtk.windowResize window width (if height <= 0 then 1 else height)++resetWindow ::+  R.GuiObjects ->+  IO ()+resetWindow+  R.GuiObjects {+        R.window = window+      , R.fileChooserButton = fileChooserButton+      , R.videoWidget = videoWidget+      , R.seekScale = seekScale+      , R.playPauseButton = playPauseButton+      , R.videoWidthSelectionComboBox = videoWidthSelectionComboBox+      , R.fullscreenButton = fullscreenButton+      , R.playImage = playImage+      , R.pauseImage = pauseImage+      , R.bottomControlsGtkBox = bottomControlsGtkBox+    }+  = do+  videoWidthSelection <- getSelectedVideoWidth videoWidthSelectionComboBox+  let width = fromIntegral videoWidthSelection :: Int32+  GI.Gtk.windowUnfullscreen window+  GI.Gtk.widgetHide videoWidget+  GI.Gtk.widgetHide seekScale+  GI.Gtk.widgetHide playPauseButton+  GI.Gtk.widgetHide fullscreenButton+  GI.Gtk.widgetShow fileChooserButton+  GI.Gtk.widgetShow bottomControlsGtkBox+  GI.Gtk.widgetShow videoWidthSelectionComboBox+  setCursor window Nothing+  setPlayPauseButton playPauseButton playImage pauseImage False+  setWindowSize width 0 fileChooserButton videoWidget window
src/data/gui.glade view
@@ -1,66 +1,165 @@ <?xml version="1.0" encoding="UTF-8"?>-<!-- Generated with glade 3.20.0 -->+<!-- 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.20"/>+  <requires lib="gtk+" version="3.10"/>+  <!-- interface-license-type bsd3c -->+  <!-- interface-name Movie Monad -->+  <!-- interface-description A desktop video player built with Haskell that uses GStreamer and GTK+. -->+  <!-- 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-dialog-question</property>   </object>-  <object class="GtkAdjustment" id="adjustment1">-    <property name="upper">100</property>-    <property name="step_increment">1</property>-    <property name="page_increment">10</property>+  <object class="GtkButton" id="file-chooser-cancel-button">+    <property name="label">gtk-cancel</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="GtkButton" id="file-chooser-open-button">+    <property name="label">gtk-open</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="fullscreen-image">     <property name="visible">True</property>     <property name="can_focus">False</property>     <property name="stock">gtk-fullscreen</property>   </object>-  <object class="GtkWindow" id="window">+  <object class="GtkImage" id="pause-image">+    <property name="visible">True</property>     <property name="can_focus">False</property>+    <property name="stock">gtk-media-pause</property>+  </object>+  <object class="GtkImage" id="play-image">+    <property name="visible">True</property>+    <property name="can_focus">False</property>+    <property name="stock">gtk-media-play</property>+  </object>+  <object class="GtkAdjustment" id="seek-scale-adjustment">+    <property name="upper">100</property>+    <property name="step_increment">1</property>+    <property name="page_increment">10</property>+  </object>+  <object class="GtkWindow" id="window">+    <property name="can_focus">True</property>     <property name="title" translatable="yes">Movie Monad</property>     <property name="window_position">center</property>-    <property name="icon_name">applications-multimedia</property>+    <property name="icon">movie-monad-icon.png</property>     <property name="gravity">center</property>     <child>-      <object class="GtkBox">+      <object class="GtkBox" id="box-1">         <property name="visible">True</property>         <property name="can_focus">False</property>         <property name="orientation">vertical</property>         <child>-          <object class="GtkBox">+          <object class="GtkBox" id="box-2">             <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="GtkFileChooserButton" id="file-chooser-button">+              <object class="GtkButton" id="file-chooser-button">                 <property name="width_request">800</property>                 <property name="visible">True</property>                 <property name="app_paintable">True</property>-                <property name="can_focus">False</property>-                <property name="tooltip_text" translatable="yes">Select a video file.</property>+                <property name="can_focus">True</property>+                <property name="receives_default">True</property>+                <property name="tooltip_text" translatable="yes">Open a video file</property>                 <property name="valign">start</property>                 <property name="hexpand">True</property>-                <property name="create_folders">False</property>-                <property name="title" translatable="yes">Movie Monad</property>+                <property name="relief">half</property>+                <property name="image_position">right</property>+                <property name="always_show_image">True</property>+                <child>+                  <object class="GtkBox" id="box-3">+                    <property name="visible">True</property>+                    <property name="can_focus">False</property>+                    <child>+                      <object class="GtkLabel" id="file-chooser-button-label">+                        <property name="visible">True</property>+                        <property name="can_focus">False</property>+                        <property name="label" translatable="yes">Open</property>+                        <property name="justify">fill</property>+                        <property name="ellipsize">end</property>+                        <property name="single_line_mode">True</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="open-image">+                        <property name="visible">True</property>+                        <property name="can_focus">False</property>+                        <property name="stock">gtk-open</property>+                      </object>+                      <packing>+                        <property name="expand">False</property>+                        <property name="fill">False</property>+                        <property name="pack_type">end</property>+                        <property name="position">2</property>+                      </packing>+                    </child>+                  </object>+                </child>               </object>               <packing>                 <property name="expand">False</property>-                <property name="fill">True</property>+                <property name="fill">False</property>                 <property name="position">0</property>               </packing>             </child>             <child>-              <object class="GtkDrawingArea" id="drawing-area">+              <object class="GtkBox" id="video-widget-box">                 <property name="visible">True</property>-                <property name="sensitive">False</property>                 <property name="app_paintable">True</property>-                <property name="can_focus">False</property>+                <property name="can_focus">True</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>@@ -69,163 +168,209 @@               </packing>             </child>             <child>-              <object class="GtkScale" id="seek-scale">+              <object class="GtkBox" id="bottom-controls-gtk-box">                 <property name="visible">True</property>-                <property name="can_focus">True</property>+                <property name="can_focus">False</property>                 <property name="valign">end</property>-                <property name="margin_left">5</property>-                <property name="margin_right">5</property>-                <property name="hexpand">True</property>-                <property name="adjustment">adjustment1</property>-                <property name="show_fill_level">True</property>-                <property name="fill_level">100</property>-                <property name="round_digits">0</property>-                <property name="digits">-1</property>-                <property name="draw_value">False</property>-                <property name="value_pos">left</property>-              </object>-              <packing>-                <property name="expand">False</property>-                <property name="fill">False</property>-                <property name="position">2</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="GtkBox">-            <property name="visible">True</property>-            <property name="can_focus">False</property>-            <property name="valign">end</property>-            <property name="margin_bottom">5</property>-            <property name="hexpand">True</property>-            <child>-              <object class="GtkSwitch" id="on-off-switch">-                <property name="visible">True</property>-                <property name="can_focus">True</property>-                <property name="tooltip_text" translatable="yes">Toggle on to play. Toggle off to pause.</property>-                <property name="margin_left">5</property>-              </object>-              <packing>-                <property name="expand">False</property>-                <property name="fill">False</property>-                <property name="position">0</property>-              </packing>-            </child>-            <child>-              <object class="GtkVolumeButton" id="volume-button">-                <property name="visible">True</property>-                <property name="app_paintable">True</property>-                <property name="can_focus">True</property>-                <property name="focus_on_click">False</property>-                <property name="receives_default">True</property>-                <property name="double_buffered">False</property>-                <property name="margin_left">5</property>-                <property name="margin_right">5</property>-                <property name="relief">none</property>-                <property name="value">0.5</property>-                <property name="size">button</property>-                <property name="icons">audio-volume-muted-symbolic+                <property name="orientation">vertical</property>+                <property name="baseline_position">bottom</property>+                <child>+                  <object class="GtkScale" id="seek-scale">+                    <property name="visible">True</property>+                    <property name="app_paintable">True</property>+                    <property name="can_focus">False</property>+                    <property name="valign">end</property>+                    <property name="margin_left">5</property>+                    <property name="margin_right">5</property>+                    <property name="hexpand">True</property>+                    <property name="adjustment">seek-scale-adjustment</property>+                    <property name="show_fill_level">True</property>+                    <property name="fill_level">100</property>+                    <property name="round_digits">0</property>+                    <property name="digits">-1</property>+                    <property name="draw_value">False</property>+                    <property name="value_pos">left</property>+                  </object>+                  <packing>+                    <property name="expand">False</property>+                    <property name="fill">False</property>+                    <property name="position">0</property>+                  </packing>+                </child>+                <child>+                  <object class="GtkBox" id="box-4">+                    <property name="visible">True</property>+                    <property name="can_focus">False</property>+                    <property name="valign">end</property>+                    <property name="margin_top">5</property>+                    <property name="margin_bottom">5</property>+                    <property name="hexpand">True</property>+                    <child>+                      <object class="GtkButton" id="play-pause-button">+                        <property name="visible">True</property>+                        <property name="can_focus">True</property>+                        <property name="receives_default">True</property>+                        <property name="tooltip_text" translatable="yes">Click to play</property>+                        <property name="margin_left">5</property>+                        <property name="margin_right">5</property>+                        <property name="image">play-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="GtkVolumeButton" id="volume-button">+                        <property name="visible">True</property>+                        <property name="app_paintable">True</property>+                        <property name="can_focus">True</property>+                        <property name="focus_on_click">False</property>+                        <property name="receives_default">True</property>+                        <property name="double_buffered">False</property>+                        <property name="margin_left">5</property>+                        <property name="margin_right">5</property>+                        <property name="relief">half</property>+                        <property name="orientation">vertical</property>+                        <property name="value">0.5</property>+                        <property name="size">button</property>+                        <property name="icons">audio-volume-muted-symbolic audio-volume-high-symbolic audio-volume-low-symbolic audio-volume-medium-symbolic</property>-                <child internal-child="plus_button">-                  <object class="GtkButton">-                    <property name="can_focus">True</property>-                    <property name="receives_default">True</property>-                    <property name="halign">center</property>-                    <property name="valign">center</property>-                    <property name="relief">none</property>-                  </object>-                </child>-                <child internal-child="minus_button">-                  <object class="GtkButton">-                    <property name="can_focus">True</property>-                    <property name="receives_default">True</property>-                    <property name="halign">center</property>-                    <property name="valign">center</property>-                    <property name="relief">none</property>+                        <child internal-child="plus_button">+                          <object class="GtkButton" id="volume-up-button">+                            <property name="can_focus">True</property>+                            <property name="receives_default">True</property>+                            <property name="halign">center</property>+                            <property name="valign">center</property>+                            <property name="relief">none</property>+                          </object>+                        </child>+                        <child internal-child="minus_button">+                          <object class="GtkButton" id="volume-down-button">+                            <property name="can_focus">True</property>+                            <property name="receives_default">True</property>+                            <property name="halign">center</property>+                            <property name="valign">center</property>+                            <property name="relief">none</property>+                          </object>+                        </child>+                      </object>+                      <packing>+                        <property name="expand">False</property>+                        <property name="fill">False</property>+                        <property name="position">1</property>+                      </packing>+                    </child>+                    <child>+                      <object class="GtkComboBoxText" id="video-width-selection-combo-box">+                        <property name="visible">True</property>+                        <property name="app_paintable">True</property>+                        <property name="can_focus">False</property>+                        <property name="tooltip_text" translatable="yes">Set the video width</property>+                        <property name="margin_left">5</property>+                        <property name="margin_right">5</property>+                        <property name="active">1</property>+                        <property name="has_frame">False</property>+                        <property name="active_id">800</property>+                        <items>+                          <item id="320" translatable="yes">320</item>+                          <item id="800" translatable="yes">800</item>+                          <item id="1024" translatable="yes">1024</item>+                          <item id="1152" translatable="yes">1152</item>+                          <item id="1280" translatable="yes">1280</item>+                          <item id="1360" translatable="yes">1360</item>+                          <item id="1366" translatable="yes">1366</item>+                          <item id="1440" translatable="yes">1440</item>+                          <item id="1536" translatable="yes">1536</item>+                          <item id="1600" translatable="yes">1600</item>+                          <item id="1680" translatable="yes">1680</item>+                          <item id="1920" translatable="yes">1920</item>+                          <item id="2560" translatable="yes">2560</item>+                          <item id="3440" translatable="yes">3440</item>+                          <item id="3840" translatable="yes">3840</item>+                        </items>+                      </object>+                      <packing>+                        <property name="expand">False</property>+                        <property name="fill">False</property>+                        <property name="position">2</property>+                      </packing>+                    </child>+                    <child>+                      <object class="GtkButton" id="fullscreen-button">+                        <property name="app_paintable">True</property>+                        <property name="can_focus">False</property>+                        <property name="receives_default">False</property>+                        <property name="tooltip_text" translatable="yes">Toggle on or off fullscreen</property>+                        <property name="margin_left">5</property>+                        <property name="margin_right">5</property>+                        <property name="image">fullscreen-image</property>+                        <property name="relief">half</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="GtkSpinner" id="buffering-spinner">+                        <property name="sensitive">False</property>+                        <property name="app_paintable">True</property>+                        <property name="can_focus">False</property>+                        <property name="margin_left">5</property>+                      </object>+                      <packing>+                        <property name="expand">False</property>+                        <property name="fill">False</property>+                        <property name="position">4</property>+                      </packing>+                    </child>+                    <child>+                      <object class="GtkButton" id="about-button">+                        <property name="visible">True</property>+                        <property name="app_paintable">True</property>+                        <property name="can_focus">False</property>+                        <property name="receives_default">False</property>+                        <property name="tooltip_text" translatable="yes">About Movie Monad</property>+                        <property name="margin_left">5</property>+                        <property name="margin_right">5</property>+                        <property name="image">about-image</property>+                        <property name="relief">half</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">5</property>+                      </packing>+                    </child>                   </object>+                  <packing>+                    <property name="expand">False</property>+                    <property name="fill">False</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="GtkComboBoxText" id="desired-video-width-combo-box">-                <property name="visible">True</property>-                <property name="can_focus">False</property>-                <property name="tooltip_text" translatable="yes">Set the max width.</property>-                <property name="active">1</property>-                <property name="has_frame">False</property>-                <property name="active_id">800</property>-                <items>-                  <item id="320" translatable="yes">320</item>-                  <item id="800" translatable="yes">800</item>-                  <item id="1024" translatable="yes">1024</item>-                  <item id="1152" translatable="yes">1152</item>-                  <item id="1280" translatable="yes">1280</item>-                  <item id="1360" translatable="yes">1360</item>-                  <item id="1366" translatable="yes">1366</item>-                  <item id="1440" translatable="yes">1440</item>-                  <item id="1536" translatable="yes">1536</item>-                  <item id="1600" translatable="yes">1600</item>-                  <item id="1680" translatable="yes">1680</item>-                  <item id="1920" translatable="yes">1920</item>-                  <item id="2560" translatable="yes">2560</item>-                  <item id="3440" translatable="yes">3440</item>-                  <item id="3840" translatable="yes">3840</item>-                </items>-              </object>-              <packing>-                <property name="expand">False</property>-                <property name="fill">False</property>-                <property name="position">2</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="margin_right">5</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">3</property>               </packing>             </child>-            <child>-              <object class="GtkButton" id="fullscreen-button">-                <property name="can_focus">True</property>-                <property name="receives_default">True</property>-                <property name="tooltip_text" translatable="yes">Toggle on or off fullscreen.</property>-                <property name="image">fullscreen-image</property>-                <property name="always_show_image">True</property>-              </object>-              <packing>-                <property name="expand">False</property>-                <property name="fill">True</property>-                <property name="position">4</property>-              </packing>-            </child>           </object>           <packing>-            <property name="expand">False</property>-            <property name="fill">False</property>-            <property name="position">1</property>+            <property name="expand">True</property>+            <property name="fill">True</property>+            <property name="position">0</property>           </packing>         </child>       </object>@@ -236,7 +381,7 @@   </object>   <object class="GtkAboutDialog" id="about-dialog">     <property name="can_focus">False</property>-    <property name="title" translatable="yes">Movie Monad</property>+    <property name="title" translatable="yes">Movie Monad - About</property>     <property name="window_position">center</property>     <property name="icon_name">dialog-information</property>     <property name="type_hint">dialog</property>@@ -244,8 +389,9 @@     <property name="transient_for">window</property>     <property name="attached_to">window</property>     <property name="program_name">Movie Monad</property>-    <property name="version">0.0.1.0</property>-    <property name="copyright" translatable="yes">(C) 2017 David Lettier</property>+    <property name="version">0.0.2.0</property>+    <property name="copyright" translatable="yes">(C) 2017 David Lettier+lettier.com</property>     <property name="website">https://github.com/lettier/movie-monad</property>     <property name="website_label" translatable="yes">Update</property>     <property name="license">BSD 3-Clause License@@ -278,15 +424,15 @@ 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_name">applications-multimedia</property>+    <property name="logo">movie-monad-logo.svg</property>     <property name="license_type">custom</property>     <child internal-child="vbox">-      <object class="GtkBox">+      <object class="GtkBox" id="box-5">         <property name="can_focus">False</property>         <property name="orientation">vertical</property>         <property name="spacing">2</property>         <child internal-child="action_area">-          <object class="GtkButtonBox">+          <object class="GtkButtonBox" id="about-dialog-button-box">             <property name="can_focus">False</property>             <property name="layout_style">end</property>           </object>@@ -296,9 +442,6 @@             <property name="position">0</property>           </packing>         </child>-        <child>-          <placeholder/>-        </child>       </object>     </child>     <child>@@ -307,7 +450,7 @@   </object>   <object class="GtkMessageDialog" id="error-message-dialog">     <property name="can_focus">False</property>-    <property name="title" translatable="yes">Movie Monad</property>+    <property name="title" translatable="yes">Movie Monad - Error</property>     <property name="window_position">center</property>     <property name="destroy_with_parent">True</property>     <property name="icon_name">dialog-error</property>@@ -319,15 +462,17 @@     <property name="message_type">error</property>     <property name="buttons">close</property>     <property name="text" translatable="yes">There was an error.</property>-    <property name="secondary_text" translatable="yes">Did you select a video file?-Did you install ExifTool?</property>+    <property name="secondary_text">If you feel like this is a bug, please visit &lt;a href="https://github.com/lettier/movie-monad/issues"&gt;the issue page&lt;/a&gt; to file a report.++Thank you.</property>+    <property name="secondary_use_markup">True</property>     <child internal-child="vbox">-      <object class="GtkBox">+      <object class="GtkBox" id="box-6">         <property name="can_focus">False</property>         <property name="orientation">vertical</property>         <property name="spacing">2</property>         <child internal-child="action_area">-          <object class="GtkButtonBox">+          <object class="GtkButtonBox" id="error-message-dialog-action-area">             <property name="can_focus">False</property>             <property name="homogeneous">True</property>             <property name="layout_style">expand</property>@@ -336,6 +481,73 @@             <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="GtkDialog" id="file-chooser-dialog">+    <property name="can_focus">False</property>+    <property name="title" translatable="yes">Movie Monad - Open a Video File</property>+    <property name="type_hint">dialog</property>+    <property name="transient_for">window</property>+    <child internal-child="vbox">+      <object class="GtkBox" id="box-7">+        <property name="can_focus">False</property>+        <property name="orientation">vertical</property>+        <property name="spacing">2</property>+        <property name="baseline_position">top</property>+        <child internal-child="action_area">+          <object class="GtkButtonBox" id="file-chooser-dialog-action-area">+            <property name="can_focus">False</property>+            <property name="baseline_position">bottom</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>+        <child>+          <object class="GtkEntry" id="file-chooser-entry">+            <property name="visible">True</property>+            <property name="can_focus">True</property>+            <property name="tooltip_text" translatable="yes">Type in the path to a local file or URL</property>+            <property name="has_frame">False</property>+            <property name="truncate_multiline">True</property>+            <property name="primary_icon_stock">gtk-edit</property>+            <property name="secondary_icon_stock">gtk-clear</property>+            <property name="primary_icon_activatable">False</property>+            <property name="primary_icon_sensitive">False</property>+            <property name="secondary_icon_tooltip_text" translatable="yes">Clear the video location</property>+            <property name="placeholder_text" translatable="yes">Video Location</property>+          </object>+          <packing>+            <property name="expand">False</property>+            <property name="fill">True</property>+            <property name="position">0</property>+          </packing>+        </child>+        <child>+          <object class="GtkFileChooserWidget" id="file-chooser-widget">+            <property name="visible">True</property>+            <property name="can_focus">False</property>+            <property name="create_folders">False</property>+          </object>+          <packing>+            <property name="expand">False</property>+            <property name="fill">True</property>+            <property name="position">1</property>           </packing>         </child>       </object>
+ src/data/movie-monad-icon.png view

binary file changed (absent → 24223 bytes)

+ src/data/movie-monad-logo.svg view
@@ -0,0 +1,768 @@+<?xml version="1.0" encoding="UTF-8" standalone="no"?>+<!-- Created with Inkscape (http://www.inkscape.org/) -->++<svg+   xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"+   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"+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"+   width="200mm"+   height="100mm"+   viewBox="0 0 200 100"+   version="1.1"+   id="svg8"+   inkscape:version="0.92.1 r"+   sodipodi:docname="movie-monad-logo.svg"+   inkscape:export-filename="/home/lettier/tmp/movie-monad-logo.png"+   inkscape:export-xdpi="88.900002"+   inkscape:export-ydpi="88.900002">+  <defs+     id="defs2">+    <inkscape:path-effect+       effect="bend_path"+       id="path-effect10605"+       is_visible="true"+       bendpath="m 424.1184,110.88365 c 102.124,-4.64842 205.00309,-10.80704 299.39935,0"+       prop_scale="1"+       scale_y_rel="false"+       vertical="false"+       bendpath-nodetypes="cc" />+    <inkscape:path-effect+       effect="bounding_box"+       id="path-effect10603"+       is_visible="true"+       linkedpath=""+       visualbounds="false" />+    <inkscape:path-effect+       effect="fill_between_many"+       id="path-effect10601"+       is_visible="true"+       linkedpaths="" />+    <inkscape:path-effect+       effect="rough_hatches"+       id="path-effect10599"+       is_visible="true"+       direction="573.81807,105.88365 , 74.849838,0"+       dist_rdm="75;1"+       growth="0"+       do_bend="true"+       bender="573.81807,110.88365 , 5,0"+       bottom_edge_variation="13.03522;1"+       top_edge_variation="13.03522;1"+       bottom_tgt_variation="0;1"+       top_tgt_variation="0;1"+       scale_bf="1"+       scale_bb="1"+       scale_tf="1"+       scale_tb="1"+       top_smth_variation="0;1"+       bottom_smth_variation="0;1"+       fat_output="true"+       stroke_width_top="1"+       stroke_width_bottom="1"+       front_thickness="1"+       back_thickness="0.25" />+    <inkscape:path-effect+       effect="powerstroke"+       id="path-effect10597"+       is_visible="true"+       offset_points="0.2,1.5 | 1.5,1.5 | 2.8,1.5"+       sort_points="true"+       interpolator_type="CubicBezierFit"+       interpolator_beta="0.81"+       start_linecap_type="peak"+       linejoin_type="extrp_arc"+       miter_limit="4"+       end_linecap_type="butt" />+    <inkscape:path-effect+       effect="roughen"+       id="path-effect10595"+       is_visible="true"+       method="segments"+       max_segment_size="10"+       segments="2"+       displace_x="10;1"+       displace_y="10;1"+       global_randomize="1;1"+       handles="along"+       shift_nodes="true"+       fixed_displacement="false"+       spray_tool_friendly="false" />+    <inkscape:path-effect+       effect="simplify"+       id="path-effect10593"+       is_visible="true"+       steps="0"+       threshold="0.002"+       smooth_angles="0"+       helper_size="5"+       simplify_individual_paths="false"+       simplify_just_coalesce="false" />+    <marker+       inkscape:stockid="Arrow1Mend"+       orient="auto"+       refY="0.0"+       refX="0.0"+       id="Arrow1Mend"+       style="overflow:visible;"+       inkscape:isstock="true">+      <path+         id="path10335"+         d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "+         style="fill-rule:evenodd;stroke:#57ace3;stroke-width:1pt;stroke-opacity:1;fill:#57ace3;fill-opacity:1"+         transform="scale(0.4) rotate(180) translate(10,0)" />+    </marker>+    <marker+       inkscape:stockid="Arrow1Lstart"+       orient="auto"+       refY="0.0"+       refX="0.0"+       id="marker10589"+       style="overflow:visible"+       inkscape:isstock="true">+      <path+         id="path10326"+         d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "+         style="fill-rule:evenodd;stroke:#57ace3;stroke-width:1pt;stroke-opacity:1;fill:#57ace3;fill-opacity:1"+         transform="scale(0.8) translate(12.5,0)" />+    </marker>+    <inkscape:path-effect+       tgtlength_rdm="1;499169899"+       tgtlength="60"+       tgtscale="5"+       tgt_places_rdmness="1;2340095931"+       nbtangents="3"+       tremble_frequency="0.01"+       tremble_size="1;2147483646"+       parallel_offset="0;618914966"+       ends_tolerance="1;1"+       strokeoverlap_rdm="1;1527224402"+       strokeoverlap="1"+       strokelength_rdm="1;693460889"+       strokelength="100"+       nbiter_approxstrokes="2"+       is_visible="true"+       id="path-effect10204"+       effect="sketch" />+    <linearGradient+       id="linearGradient10200"+       inkscape:collect="always">+      <stop+         id="stop10196"+         offset="0"+         style="stop-color:#57ace3;stop-opacity:1" />+      <stop+         id="stop10198"+         offset="1"+         style="stop-color:#e0437f;stop-opacity:0.89019608" />+    </linearGradient>+    <inkscape:path-effect+       effect="taper_stroke"+       id="path-effect10194"+       is_visible="true"+       stroke_width="5"+       attach_start="0.99999"+       end_offset="0.99999"+       smoothing="0.1"+       jointype="bevel"+       miter_limit="100" />+    <inkscape:path-effect+       effect="taper_stroke"+       id="path-effect10171"+       is_visible="true"+       stroke_width="1"+       attach_start="0.2"+       end_offset="0.2"+       smoothing="0.5"+       jointype="extrapolated"+       miter_limit="100" />+    <inkscape:path-effect+       effect="simplify"+       id="path-effect10169"+       is_visible="true"+       steps="19"+       threshold="0.002"+       smooth_angles="0"+       helper_size="5"+       simplify_individual_paths="false"+       simplify_just_coalesce="false" />+    <inkscape:path-effect+       effect="roughen"+       id="path-effect10167"+       is_visible="true"+       method="segments"+       max_segment_size="9"+       segments="2"+       displace_x="1;1"+       displace_y="1;1"+       global_randomize="1;1"+       handles="along"+       shift_nodes="true"+       fixed_displacement="false"+       spray_tool_friendly="false" />+    <inkscape:path-effect+       effect="powerstroke"+       id="path-effect10165"+       is_visible="true"+       offset_points="7.5,0.13229166"+       sort_points="true"+       interpolator_type="CubicBezierFit"+       interpolator_beta="0.24"+       start_linecap_type="butt"+       linejoin_type="extrp_arc"+       miter_limit="4"+       end_linecap_type="butt" />+    <inkscape:path-effect+       effect="bend_path"+       id="path-effect10163"+       is_visible="true"+       bendpath="M 6.8171912,228.1058 H 126.12427"+       prop_scale="1"+       scale_y_rel="false"+       vertical="false" />+    <inkscape:path-effect+       effect="construct_grid"+       id="path-effect10161"+       is_visible="true"+       nr_x="5"+       nr_y="5" />+    <inkscape:path-effect+       effect="rough_hatches"+       id="path-effect10159"+       is_visible="true"+       direction="66.722547,223.63972 , 30.669258,0"+       dist_rdm="75;1"+       growth="0"+       do_bend="true"+       bender="66.722547,228.63972 , 5,0"+       bottom_edge_variation="4.25;1"+       top_edge_variation="4.0490918;1"+       bottom_tgt_variation="0;1"+       top_tgt_variation="0;1"+       scale_bf="1"+       scale_bb="1"+       scale_tf="1"+       scale_tb="1"+       top_smth_variation="0;1"+       bottom_smth_variation="0;1"+       fat_output="true"+       stroke_width_top="1"+       stroke_width_bottom="1"+       front_thickness="1"+       back_thickness="0.25" />+    <inkscape:path-effect+       effect="sketch"+       id="path-effect10157"+       is_visible="true"+       nbiter_approxstrokes="5"+       strokelength="10"+       strokelength_rdm="0.3;1264792834"+       strokeoverlap="0.3"+       strokeoverlap_rdm="0.3;1"+       ends_tolerance="0.1;1"+       parallel_offset="1;1"+       tremble_size="1;1"+       tremble_frequency="0.01"+       nbtangents="5"+       tgt_places_rdmness="1;1"+       tgtscale="10"+       tgtlength="5"+       tgtlength_rdm="0.3;1" />+    <inkscape:path-effect+       effect="sketch"+       id="path-effect10155"+       is_visible="true"+       nbiter_approxstrokes="9"+       strokelength="20"+       strokelength_rdm="0.3;1580765317"+       strokeoverlap="0"+       strokeoverlap_rdm="0;797676426"+       ends_tolerance="0;84877062"+       parallel_offset="0.1;2147483646"+       tremble_size="2;1740572465"+       tremble_frequency="1"+       nbtangents="4"+       tgt_places_rdmness="1;1"+       tgtscale="10"+       tgtlength="10"+       tgtlength_rdm="0.3;1" />+    <inkscape:path-effect+       effect="sketch"+       id="path-effect10153"+       is_visible="true"+       nbiter_approxstrokes="1"+       strokelength="100"+       strokelength_rdm="1;693460889"+       strokeoverlap="1"+       strokeoverlap_rdm="1;1527224402"+       ends_tolerance="1;1"+       parallel_offset="1;618914966"+       tremble_size="3;2147483646"+       tremble_frequency="0.01"+       nbtangents="3"+       tgt_places_rdmness="1;2340095931"+       tgtscale="5"+       tgtlength="60"+       tgtlength_rdm="1;499169899" />+    <inkscape:path-effect+       effect="taper_stroke"+       id="path-effect9006"+       is_visible="true"+       stroke_width="20"+       attach_start="0.99999"+       end_offset="3.99999"+       smoothing="0.5"+       jointype="extrapolated"+       miter_limit="200" />+    <inkscape:path-effect+       effect="roughen"+       id="path-effect9004"+       is_visible="true"+       method="segments"+       max_segment_size="10"+       segments="2"+       displace_x="10;1"+       displace_y="10;1"+       global_randomize="1;1"+       handles="along"+       shift_nodes="true"+       fixed_displacement="false"+       spray_tool_friendly="false" />+    <inkscape:path-effect+       effect="spiro"+       id="path-effect9002"+       is_visible="true" />+    <inkscape:path-effect+       effect="sketch"+       id="path-effect9000"+       is_visible="true"+       nbiter_approxstrokes="3"+       strokelength="500"+       strokelength_rdm="0.3;2971216357"+       strokeoverlap="0.3"+       strokeoverlap_rdm="0.3;2083033150"+       ends_tolerance="0.1;1404504716"+       parallel_offset="5;4166125628"+       tremble_size="5;1181147172"+       tremble_frequency="1"+       nbtangents="20"+       tgt_places_rdmness="1;296553611"+       tgtscale="5"+       tgtlength="100"+       tgtlength_rdm="0.3;1794606228" />+    <inkscape:path-effect+       effect="ruler"+       id="path-effect8998"+       is_visible="true"+       unit="px"+       mark_distance="20"+       mark_length="14"+       minor_mark_length="7"+       major_mark_steps="5"+       shift="0"+       offset="0"+       mark_dir="left"+       border_marks="both" />+    <inkscape:path-effect+       effect="simplify"+       id="path-effect8996"+       is_visible="true"+       steps="29"+       threshold="0.002"+       smooth_angles="0"+       helper_size="5"+       simplify_individual_paths="false"+       simplify_just_coalesce="false" />+    <inkscape:path-effect+       effect="show_handles"+       id="path-effect8994"+       is_visible="true"+       nodes="true"+       handles="true"+       original_path="true"+       scale_nodes_and_handles="10" />+    <inkscape:path-effect+       effect="sketch"+       id="path-effect8992"+       is_visible="true"+       nbiter_approxstrokes="1"+       strokelength="20"+       strokelength_rdm="1;1"+       strokeoverlap="1"+       strokeoverlap_rdm="1;1"+       ends_tolerance="0.1;1"+       parallel_offset="0;1"+       tremble_size="5;1"+       tremble_frequency="1"+       nbtangents="20"+       tgt_places_rdmness="1;1"+       tgtscale="10"+       tgtlength="50"+       tgtlength_rdm="0.3;1" />+    <inkscape:path-effect+       effect="powerstroke"+       id="path-effect8990"+       is_visible="true"+       offset_points="3,0.25"+       sort_points="true"+       interpolator_type="Linear"+       interpolator_beta="0.2"+       start_linecap_type="butt"+       linejoin_type="extrp_arc"+       miter_limit="4"+       end_linecap_type="butt" />+    <inkscape:path-effect+       effect="fill_between_strokes"+       id="path-effect8988"+       is_visible="true"+       linkedpath=""+       secondpath=""+       reversesecond="false" />+    <inkscape:path-effect+       effect="fill_between_many"+       id="path-effect8986"+       is_visible="true"+       linkedpaths="" />+    <inkscape:path-effect+       effect="gears"+       id="path-effect8984"+       is_visible="true"+       teeth="10"+       phi="5"+       min_radius="5" />+    <inkscape:path-effect+       effect="rough_hatches"+       id="path-effect8982"+       is_visible="true"+       direction="317.46248,263.61816 , 144.59689,0"+       dist_rdm="75;1924315346"+       growth="0"+       do_bend="true"+       bender="317.46248,268.61816 , 5,0"+       bottom_edge_variation="13.811914;896150574"+       top_edge_variation="13.811914;2070401107"+       bottom_tgt_variation="0;815658193"+       top_tgt_variation="0;1753223962"+       scale_bf="1"+       scale_bb="1"+       scale_tf="1"+       scale_tb="1"+       top_smth_variation="0;1"+       bottom_smth_variation="0;1"+       fat_output="true"+       stroke_width_top="1"+       stroke_width_bottom="1"+       front_thickness="1"+       back_thickness="0.25" />+    <inkscape:path-effect+       effect="construct_grid"+       id="path-effect8980"+       is_visible="true"+       nr_x="5"+       nr_y="5" />+    <linearGradient+       id="linearGradient8935"+       osb:paint="solid">+      <stop+         style="stop-color:#373737;stop-opacity:1;"+         offset="0"+         id="stop8933" />+    </linearGradient>+    <linearGradient+       inkscape:collect="always"+       id="linearGradient5282">+      <stop+         style="stop-color:#814dd2;stop-opacity:1;"+         offset="0"+         id="stop5278" />+      <stop+         style="stop-color:#b46bd3;stop-opacity:0.89019608"+         offset="1"+         id="stop5280" />+    </linearGradient>+    <marker+       inkscape:stockid="Arrow1Lend"+       orient="auto"+       refY="0"+       refX="0"+       id="Arrow1Lend"+       style="overflow:visible"+       inkscape:isstock="true">+      <path+         id="path4997"+         d="M 0,0 5,-5 -12.5,0 5,5 Z"+         style="fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt"+         transform="matrix(-0.8,0,0,-0.8,-10,0)"+         inkscape:connector-curvature="0" />+    </marker>+    <marker+       inkscape:stockid="Arrow1Lstart"+       orient="auto"+       refY="0"+       refX="0"+       id="Arrow1Lstart"+       style="overflow:visible"+       inkscape:isstock="true">+      <path+         id="path4994"+         d="M 0,0 5,-5 -12.5,0 5,5 Z"+         style="fill-rule:evenodd;stroke:#000000;stroke-width:1.00000003pt"+         transform="matrix(0.8,0,0,0.8,10,0)"+         inkscape:connector-curvature="0" />+    </marker>+    <linearGradient+       inkscape:collect="always"+       xlink:href="#linearGradient5282"+       id="linearGradient5284"+       x1="-119.13808"+       y1="53.642838"+       x2="-64.290207"+       y2="53.285694"+       gradientUnits="userSpaceOnUse" />+    <linearGradient+       inkscape:collect="always"+       xlink:href="#linearGradient5282"+       id="linearGradient5300"+       gradientUnits="userSpaceOnUse"+       x1="-119.13808"+       y1="53.642838"+       x2="-58.218777"+       y2="55.071407" />+    <linearGradient+       inkscape:collect="always"+       xlink:href="#linearGradient5282"+       id="linearGradient5352"+       gradientUnits="userSpaceOnUse"+       x1="-119.13808"+       y1="53.642838"+       x2="-58.218777"+       y2="55.071407" />+    <linearGradient+       inkscape:collect="always"+       xlink:href="#linearGradient5282"+       id="linearGradient5354"+       gradientUnits="userSpaceOnUse"+       x1="-119.13808"+       y1="53.642838"+       x2="-64.290207"+       y2="53.285694" />+    <linearGradient+       inkscape:collect="always"+       xlink:href="#linearGradient5282"+       id="linearGradient4670"+       gradientUnits="userSpaceOnUse"+       x1="-119.13808"+       y1="53.642838"+       x2="-64.290207"+       y2="53.285694" />+    <linearGradient+       inkscape:collect="always"+       xlink:href="#linearGradient5282"+       id="linearGradient4678"+       gradientUnits="userSpaceOnUse"+       x1="-119.13808"+       y1="53.642838"+       x2="-64.290207"+       y2="53.285694" />+    <linearGradient+       inkscape:collect="always"+       xlink:href="#linearGradient10200"+       id="linearGradient10134"+       gradientUnits="userSpaceOnUse"+       x1="109.73602"+       y1="227.8952"+       x2="192.22939"+       y2="227.8952"+       gradientTransform="matrix(3.7865563,-0.0047396,0.01655021,3.7724911,0.1767767,-743.59466)" />+  </defs>+  <sodipodi:namedview+     id="base"+     pagecolor="#ffffff"+     bordercolor="#666666"+     borderopacity="1.0"+     inkscape:pageopacity="0"+     inkscape:pageshadow="2"+     inkscape:zoom="1"+     inkscape:cx="150.91612"+     inkscape:cy="299.37266"+     inkscape:document-units="mm"+     inkscape:current-layer="layer1"+     showgrid="false"+     showguides="false"+     inkscape:guide-bbox="true"+     inkscape:window-width="1920"+     inkscape:window-height="1156"+     inkscape:window-x="0"+     inkscape:window-y="344"+     inkscape:window-maximized="1"+     inkscape:snap-global="false">+    <sodipodi:guide+       position="43.418068,28.252304"+       orientation="1,0"+       id="guide5364"+       inkscape:locked="false" />+    <sodipodi:guide+       position="7.4001037,11.264559"+       orientation="1,0"+       id="guide4628"+       inkscape:locked="false" />+    <sodipodi:guide+       position="76.491454,48.978922"+       orientation="1,0"+       id="guide4630"+       inkscape:locked="false" />+    <sodipodi:guide+       position="86.775231,47.758654"+       orientation="1,0"+       id="guide4645"+       inkscape:locked="false" />+    <sodipodi:guide+       position="95.872541,45.692643"+       orientation="1,0"+       id="guide4647"+       inkscape:locked="false" />+    <sodipodi:guide+       position="119.3082,86.310238"+       orientation="0,1"+       id="guide4649"+       inkscape:locked="false" />+    <sodipodi:guide+       position="87.006575,49.298283"+       orientation="0,1"+       id="guide4669"+       inkscape:locked="false" />+    <sodipodi:guide+       position="104.75846,51.385422"+       orientation="1,0"+       id="guide4671"+       inkscape:locked="false" />+    <sodipodi:guide+       position="41.353548,11.513509"+       orientation="0,1"+       id="guide4673"+       inkscape:locked="false" />+    <sodipodi:guide+       position="86.73606,46.345371"+       orientation="0,1"+       id="guide4675"+       inkscape:locked="false" />+    <sodipodi:guide+       position="38.89398,84.137282"+       orientation="0,1"+       id="guide4677"+       inkscape:locked="false" />+    <sodipodi:guide+       position="102.68449,51.22072"+       orientation="1,0"+       id="guide4693"+       inkscape:locked="false" />+    <inkscape:grid+       type="xygrid"+       id="grid4698" />+    <sodipodi:guide+       position="159.87448,27.351302"+       orientation="1,0"+       id="guide4710"+       inkscape:locked="false" />+    <sodipodi:guide+       position="112.62418,73.773699"+       orientation="0,1"+       id="guide8975"+       inkscape:locked="false" />+    <sodipodi:guide+       position="122.24405,66.315737"+       orientation="0,1"+       id="guide9906"+       inkscape:locked="false" />+    <sodipodi:guide+       position="120.16154,57.421367"+       orientation="0,1"+       id="guide9908"+       inkscape:locked="false" />+    <sodipodi:guide+       position="113.00189,59.859912"+       orientation="1,0"+       id="guide9910"+       inkscape:locked="false" />+    <sodipodi:guide+       position="127.5477,68.84863"+       orientation="0,1"+       id="guide9916"+       inkscape:locked="false" />+  </sodipodi:namedview>+  <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+     inkscape:label="Layer 1"+     inkscape:groupmode="layer"+     id="layer1"+     transform="translate(0,-197)">+    <path+       id="path4657"+       style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:42.66666794px;font-family:'Fira Sans';-inkscape-font-specification:'Fira Sans Bold';text-align:start;letter-spacing:-2.850003px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#2b2b2b;fill-opacity:1;fill-rule:nonzero;stroke:#0f0f0f;stroke-width:0.26458332;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"+       d="m 132.06601,248.47691 v 25.06876 l 2.94142,9.76581 h 7.51427 c 8.89726,0 17.3421,-3.56874 17.3421,-17.59324 0,-14.37636 -8.79666,-17.24133 -18.09606,-17.24133 z m 8.24394,5.98155 h 2.61379 c 4.82563,0 8.39484,1.91012 8.39484,11.25977 0,9.24913 -3.31763,11.61169 -8.29407,11.61169 h -2.71456 z m -27.95075,-5.98155 -7.59592,25.18607 0.003,9.6485 h 5.63273 l 1.75958,-7.48947 h 10.1539 l 1.80971,7.48947 h 8.72195 l -4.27932,-14.21257 0.016,-0.005 -6.21822,-20.61735 z m 4.87567,5.98155 3.66955,15.38148 h -7.33909 z m -41.385799,-5.98137 v 34.83455 h 7.339086 v -12.76769 c 0,-6.43418 -0.653097,-11.05867 -1.055232,-14.22549 l 10.053132,26.99318 h 10.505823 0.003 l -0.001,-2.78846 -0.002,0.008 v -11.04016 l -5.3e-4,-2.46031 h 5.3e-4 v -18.55339 h -7.389133 v 14.07459 c 0,5.57964 0.653473,10.15378 1.105877,12.96873 L 86.103741,248.47709 Z m -66.4031819,0 -2.6137936,34.83455 h 8.0424025 l 0.603581,-13.52165 c 0.201068,-4.8759 0.200974,-9.19902 -0.150895,-14.32626 l 5.730399,24.17889 h 7.791257 l 5.378484,-24.17889 c -0.251338,4.32296 -0.150566,9.35012 0.100767,14.22601 l 0.603062,13.6219 h 8.093046 l -2.61379,-34.83456 H 29.954716 l -4.875673,23.62543 -5.228104,-23.62543 z m 49.0809429,-0.8549 c -9.802067,0 -15.68328,6.88659 -15.68328,18.24695 0,11.61167 5.881213,18.29707 15.68328,18.29707 9.852331,0 15.733924,-6.88647 15.733924,-18.29707 0,-11.61169 -5.881593,-18.24695 -15.733924,-18.24695 z m 0,6.03219 c 4.67483,0 7.087938,3.41802 7.087938,12.21476 0,8.84699 -2.36284,12.26488 -7.087938,12.26488 -4.524031,0 -7.037298,-3.41789 -7.037298,-12.26488 0,-8.79674 2.412733,-12.21476 7.037298,-12.21476 z"+       inkscape:connector-curvature="0" />+    <g+       id="flowRoot4616"+       style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:40px;font-family:'Fira Sans';-inkscape-font-specification:'Fira Sans Bold';text-align:start;letter-spacing:0.38999996px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient4670);stroke-width:1.13385832;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers"+       transform="matrix(1.1781329,0,0,1.1781329,271.19496,167.28142)"+       aria-label=" &gt; " />+    <g+       id="flowRoot4650"+       style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:40px;font-family:'Fira Sans';-inkscape-font-specification:'Fira Sans Bold';text-align:start;letter-spacing:0.38999996px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient4678);stroke-width:1.13385832;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:stroke fill markers"+       transform="matrix(1.1781329,0,0,1.1781329,268.90451,167.19108)"+       aria-label="   &gt; " />+    <g+       id="flowRoot4642"+       style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:42.66666794px;font-family:'Fira Sans';-inkscape-font-specification:'Fira Sans Bold';text-align:start;letter-spacing:-2.850003px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#373737;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"+       transform="matrix(1.1781329,0,0,1.1781329,258.78415,166.40418)"+       aria-label="E" />+    <path+       id="path4667"+       style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:42.66666794px;font-family:'Fira Sans';-inkscape-font-specification:'Fira Sans Bold';text-align:start;letter-spacing:-2.850003px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#2b2b2b;fill-opacity:1;fill-rule:nonzero;stroke:#0f0f0f;stroke-width:0.26458333;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;stroke-dashoffset:0"+       d="m 75.849287,210.69008 v 18.55338 h 5.3e-4 l -5.3e-4,2.46032 v 11.04015 l -0.0021,-0.008 -0.0011,2.78846 h 0.0031 10.505818 l 5.727817,-15.37994 7.234184,-19.45464 h -6.879603 l -10.304798,27.04331 c 0.4524,-2.81495 1.105876,-7.38907 1.105876,-12.9687 v -14.07459 z m 25.443413,0 -5.690095,15.30345 c -0.147037,1.99205 -0.249597,4.23297 -0.249597,6.76341 v 12.7677 h 7.339082 v -34.83457 z m 23.97704,5.88046 0.85453,-5.88124 h -21.36346 v 34.83503 h 21.36346 v -5.9315 H 113.0046 v -8.89726 h 10.70687 v -5.78071 H 113.0046 v -8.34432 z M 43.009437,245.52332 40.395553,210.68829 H 29.940016 l -4.875902,23.62549 -5.227765,-23.6255 H 9.431079 L 6.8171912,245.52332 H 14.859916 L 15.46312,232.0015 c 0.201072,-4.8759 0.201072,-9.19886 -0.150801,-14.3261 l 5.730438,24.17843 h 7.791382 l 5.378577,-24.17843 c -0.251343,4.32296 -0.150802,9.34967 0.100494,14.22557 l 0.603205,13.62235 z m 15.502409,-35.68953 c -9.802067,0 -15.68328,6.8866 -15.68328,18.24694 0,11.6117 5.881213,18.29708 15.68328,18.29708 9.852331,0 15.733924,-6.88646 15.733924,-18.29708 0,-11.61168 -5.881593,-18.24694 -15.733924,-18.24694 z m 0,6.0322 c 4.67483,0 7.087938,3.41802 7.087938,12.21475 0,8.847 -2.36284,12.26489 -7.087938,12.26489 -4.524031,0 -7.037298,-3.41789 -7.037298,-12.26489 0,-8.79672 2.412733,-12.21475 7.037298,-12.21475 z"+       inkscape:connector-curvature="0" />+    <path+       style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-size:57.54236603px;line-height:125%;font-family:'Fira Sans';-inkscape-font-specification:'Fira Sans Ultra-Bold';text-align:start;letter-spacing:-1.70070565px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:none;fill-opacity:1;stroke:url(#linearGradient10134);stroke-width:1.00000001;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"+       d="m 648.5341,87.310007 c -9.7121,4.127281 -19.42418,8.254548 -29.13624,12.381802 0.0453,11.073141 0.0905,22.146271 0.13585,33.219381 10.56235,4.42211 21.12472,8.84421 31.68712,13.2663 M 633.36752,96.140968 c -4.23915,1.801771 -8.47831,3.603539 -12.71747,5.405302 0.0167,3.72718 0.0333,7.45435 0.05,11.18152 m 2.04302,-3.85006 c 0.0397,8.61454 0.0794,17.22908 0.11908,25.84362 14.76048,6.17481 29.52092,12.34963 44.28132,18.52444 M 632.90195,139.0934 c 25.71117,10.75354 51.42244,21.50716 77.13382,32.26084 1.22807,-2.85848 2.45614,-5.71696 3.68421,-8.57544 m -6.90261,9.20272 c 1.03894,0.43471 2.07789,0.86942 3.11683,1.30413 3.95555,-9.19321 7.91108,-18.38642 11.86659,-27.57964 -7.57971,-2.95242 -15.15943,-5.90484 -22.73917,-8.85727 m 2.15824,-2.09073 c -15.67825,-6.11232 -31.35655,-12.22458 -47.03491,-18.33676 12.36993,-5.10497 24.73982,-10.20989 37.10967,-15.31477 m -16.53251,25.20037 c -6.44424,-2.51117 -12.8885,-5.02234 -19.33276,-7.53351 22.69625,-9.36273 45.39238,-18.72545 68.0884,-28.088151 -0.29141,-0.632498 -0.58281,-1.264996 -0.87422,-1.897494 m -69.22838,29.392715 c -0.24128,-0.094 -0.48257,-0.18796 -0.72385,-0.28193 22.68902,-9.36125 45.37812,-18.72258 68.06732,-28.083988 -3.57014,-7.743386 -7.14027,-15.486781 -10.71039,-23.230186 m -23.22126,33.437724 c 10.77256,-4.446834 21.54514,-8.893648 32.31774,-13.340443 -0.57171,-1.240078 -1.14341,-2.480156 -1.71512,-3.720233 m 1.23863,9.141077 c 0.75978,-0.313671 1.51956,-0.627341 2.27934,-0.941012 -4.03208,-8.753337 -8.06417,-17.506664 -12.09627,-26.259983 -0.56183,0.238567 -1.12367,0.477135 -1.68551,0.715703 m 12.5016,22.097459 c 0.0802,-0.03313 0.1605,-0.06626 0.24075,-0.09939 -4.03607,-8.751509 -8.07213,-17.503012 -12.10817,-26.254508 -9.87001,4.191186 -19.74,8.38238 -29.60997,12.573582 m 12.85578,-2.389769 c -22.05754,9.364824 -44.11497,18.729734 -66.17231,28.094728 m -107.274,4.187127 c -0.84131,0.3573 -1.68261,0.7146 -2.52392,1.07191 0.0163,3.90357 0.0326,7.80715 0.049,11.71072 m -3.93823,-4.9983 c 0.0401,8.6513 0.0803,17.3026 0.12039,25.9539 19.88934,8.31229 39.77866,16.6246 59.66795,24.93692 m -4.00821,-7.74504 c 11.9072,4.98365 23.81441,9.96729 35.72164,14.95091 0.91916,-2.13701 1.83832,-4.27401 2.75748,-6.41102 m -16.85174,2.4761 c 4.93606,2.06386 9.87212,4.12772 14.80817,6.19159 3.9554,-9.19637 7.91078,-18.39274 11.86615,-27.5891 -15.16944,-5.91319 -30.33891,-11.82637 -45.50842,-17.73953 m -0.0175,-0.31222 c -9.09947,-3.54582 -18.19894,-7.09162 -27.29839,-10.63741 9.38846,-3.87537 18.77693,-7.75073 28.1654,-11.62607 m -3.45166,4.69425 C 582.61769,102.37262 598.07784,95.991254 613.53799,89.609887 610.07395,82.0933 606.6099,74.576713 603.14586,67.060124 m 13.27545,22.437871 c 0.0236,-0.0097 0.0473,-0.0195 0.0709,-0.02925 -4.03355,-8.751194 -8.0671,-17.502379 -12.10065,-26.253557 -13.39917,5.691875 -26.79835,11.383767 -40.19755,17.075676 m 45.83217,-4.772545 c -1.7876,-3.875989 -3.57519,-7.751978 -5.36278,-11.627969 -9.37578,3.983924 -18.75156,7.967844 -28.12733,11.95176 m 31.84948,-3.206604 c -1.78706,-3.876259 -3.57412,-7.752517 -5.36119,-11.628776 -18.00377,7.649915 -36.00753,15.299838 -54.01126,22.94977 m 26.05047,-5.71335 c -6.70982,2.853463 -13.41964,5.706914 -20.12947,8.560353 m 12.32275,-9.896003 c -17.37721,7.384846 -34.75442,14.769727 -52.13162,22.154645 m -34.54308,3.084334 c 6.67582,4.1e-4 13.35163,8.2e-4 20.02744,0.001 1.3e-4,-1.26754 2.6e-4,-2.535071 4e-4,-3.802607 m -9.40351,8.965447 c 2.67224,-6.9e-4 5.34448,-0.001 8.01672,-0.002 -0.002,-10.545713 -0.004,-21.091419 -0.006,-31.637124 -13.34915,-0.0035 -26.69828,-0.0069 -40.04738,-0.01038 m 38.46844,20.870619 c 8.9e-4,-7.92388 0.002,-15.847764 0.003,-23.77165 -0.53954,3e-6 -1.07909,7e-6 -1.61863,1e-5 m 2.93627,18.341405 c -5.6e-4,-6.302839 -0.001,-12.60568 -0.002,-18.908526 -15.4422,0.0027 -30.88439,0.0054 -46.32658,0.0081 m 45.48451,12.828552 c 9.6e-4,-4.350837 0.002,-8.701673 0.003,-13.052506 -20.90549,-0.0024 -41.81103,-0.0048 -62.71661,-0.0072 m 65.58699,-1.54598 c -17.31771,-0.0021 -34.63544,-0.0042 -51.95318,-0.0062 m 32.6192,6.513975 c -17.50497,0.0034 -35.00998,0.0067 -52.51502,0.0099 2.4e-4,1.894165 4.7e-4,3.78833 7.1e-4,5.682494 m 36.51675,-11.129932 c -13.26226,-0.0036 -26.52453,-0.0071 -39.78682,-0.01065 7.6e-4,10.540165 0.002,21.080346 0.002,31.620542 0.54277,-1.4e-4 1.08555,-2.8e-4 1.62833,-4.3e-4 m 1.41106,-28.98446 c -1.56444,-8.4e-5 -3.12888,-1.68e-4 -4.69332,-2.52e-4 -6.9e-4,10.5424 -0.001,21.084802 -0.002,31.627202 8.20467,-4.4e-4 16.40934,-8.7e-4 24.61401,-0.001 M 426.5625,75.431744 c -4e-5,0.260418 -7e-5,0.520835 -10e-5,0.781252 m 2.74229,1.077147 c 4.6e-4,4.94823 9.2e-4,9.896459 0.001,14.844686 m -5.18108,-9.621664 c -0.002,8.346092 -0.004,16.692179 -0.007,25.038265 10.40075,-5.5e-4 20.80152,-0.001 31.20231,-0.002 M 429.5625,79.743633 c 4.7e-4,7.466811 9.4e-4,14.933626 0.001,22.400447 22.87234,-0.002 45.74463,-0.005 68.61687,-0.007 m -69.06358,44.11522 c 0.002,6.03419 0.003,12.06836 0.005,18.10254 8.29248,0.002 16.58496,0.005 24.87742,0.007 m -23.34184,-3.12116 c 20.17874,0.001 40.35759,0.002 60.53655,0.003 m -23.77935,-1.18297 c 10.52815,-0.001 21.0563,-0.002 31.58447,-0.003 -0.002,-11.50253 -0.005,-23.00505 -0.007,-34.50757 -1.27582,-1.3e-4 -2.55165,-2.7e-4 -3.82747,-4e-4 m -20.02132,38.25354 c 6.77082,-4.3e-4 13.54163,-8.6e-4 20.31244,-0.001 m -7.31097,-4.31121 c 4.10849,-4.6e-4 8.21698,-9.2e-4 12.32546,-0.001 0.003,-11.50258 0.005,-23.00515 0.008,-34.50769 -0.5369,-6e-5 -1.0738,-1.2e-4 -1.6107,-1.8e-4 m 2.79501,32.87573 c 2.9e-4,-1.82248 5.7e-4,-3.64497 8.6e-4,-5.46745 m -3.39149,0.62821 c 6.8e-4,-9.24988 0.001,-18.49975 0.002,-27.74963 -16.2697,1.5e-4 -32.53942,3e-4 -48.80914,4.5e-4 m 17.20351,1.51492 c -12.6271,-0.003 -25.25422,-0.006 -37.88136,-0.008 m 26.95033,1.07868 c -8.46134,5.8e-4 -16.92269,0.001 -25.38406,0.002 m 15.02401,-4.6814 c -6.03953,-0.001 -12.07906,-0.002 -18.1186,-0.004 2e-5,0.9914 4e-5,1.9828 6e-5,2.97421 m 1.86366,1.09184 c -0.98597,0 -1.97193,0 -2.9579,0 -2.1e-4,11.5013 -4.3e-4,23.0026 -6.5e-4,34.50389 12.38231,0 24.76462,-1e-5 37.14693,-3e-5 M 643.21578,45.707549 539.24227,89.88274 m 107.52106,-0.09641 -5.60853,2.382893 m -6.93873,48.252292 85.18969,35.63824"+       transform="matrix(0.26458333,0,0,0.26458333,0,197)"+       id="text10004"+       inkscape:path-effect="#path-effect10153"+       inkscape:original-d="M 708.97266 63.355469 L 619.80469 101.24023 L 619.94922 134.45312 L 709.44922 171.89453 L 721.31055 144.31055 L 652.99609 117.69531 L 721.07227 89.607422 L 708.97266 63.355469 z M 601.36133 63.490234 L 512.19336 101.375 L 512.33984 134.58789 L 601.83789 172.0293 L 613.70117 144.44531 L 545.38477 117.83008 L 613.46094 89.742188 L 601.36133 63.490234 z M 426.85156 73.822266 L 426.85156 105.45117 L 500.01367 105.45117 L 500.01367 73.822266 L 426.85156 73.822266 z M 426.47656 126.69727 L 426.47656 161.20117 L 500.01367 161.20117 L 500.01367 126.69727 L 426.47656 126.69727 z " />+    <path+       inkscape:original-d="m 708.97266,63.355469 -89.16797,37.884761 0.14453,33.21289 89.5,37.44141 11.86133,-27.58398 -68.31446,-26.61524 68.07618,-28.087888 z m -107.61133,0.134765 -89.16797,37.884766 0.14648,33.21289 89.49805,37.44141 11.86328,-27.58399 -68.3164,-26.61523 68.07617,-28.087892 z M 426.85156,73.822266 v 31.628904 h 73.16211 V 73.822266 Z m -0.375,52.875004 v 34.5039 h 73.53711 v -34.5039 z"+       inkscape:path-effect="#path-effect10204;#path-effect10605"+       id="path10202"+       d="m 650.24158,82.877982 c -10.149,3.781746 -20.1801,7.698359 -30.11816,11.721213 3e-5,-0.006 -0.20722,33.232025 -0.20718,33.226045 10.46503,4.53182 20.80539,9.18012 30.97848,13.96782 M 633.98845,89.915578 c -4.37615,1.710896 -8.7325,3.44455 -13.07111,5.19858 1e-5,-4.22e-4 -0.0739,11.184852 -0.0739,11.184432 m 0.37278,-3.80672 c 1e-5,-0.002 -0.17855,25.85224 -0.17854,25.85018 14.60623,6.34072 28.96616,12.91035 42.96367,19.77137 m -31.31836,-14.0728 c 25.19686,11.26895 49.54864,23.28313 72.44078,36.37319 1.425,-2.72943 2.86187,-5.45508 4.31094,-8.17685 m -6.97533,6.68321 c 0.92922,0.52549 1.85609,1.05271 2.78058,1.58169 4.58684,-8.77946 9.29667,-17.51974 14.14,-26.21726 -6.95272,-3.77255 -14.01859,-7.44365 -21.18283,-11.02194 m 1.28755,-0.18554 c -14.8103,-7.42641 -30.04349,-14.4533 -45.56854,-21.15719 12.62298,-4.62803 25.39915,-9.03748 38.37817,-13.169505 m -18.7384,22.832935 c -6.26317,-2.87365 -12.58539,-5.68727 -18.95756,-8.44619 23.14078,-8.47619 46.79403,-16.213872 71.26656,-22.849679 -0.22872,-0.662637 -0.45816,-1.32517 -0.68831,-1.987601 m -71.06157,24.84294 c -2.7e-4,-1.2e-4 -0.71567,-0.30912 -0.71594,-0.30924 23.13582,-8.50099 46.78195,-16.269483 71.24507,-22.942194 -2.81444,-8.108272 -5.73549,-16.201083 -8.75553,-24.2782 m -26.09484,33.263656 c 11.29107,-3.630566 22.7343,-7.053436 34.3625,-10.229723 -0.45247,-1.298072 -0.90767,-2.595747 -1.36557,-3.893024 m -0.61775,6.067313 c 0.81699,-0.225925 1.63488,-0.450636 2.45368,-0.674118 -3.17802,-9.163387 -6.49223,-18.307041 -9.93162,-27.430627 -0.62069,0.179564 -1.24081,0.359777 -1.86037,0.540633 M 724.0723,88.77051 c -3e-5,7e-6 0.2599,-0.07099 0.25987,-0.07098 -3.19166,-9.161723 -6.51908,-18.303673 -9.97123,-27.425513 -10.91455,3.157907 -21.65447,6.515727 -32.24586,10.043417 m 11.85695,-2.48494 c -23.89196,7.592096 -46.99908,16.087727 -69.6127,25.151573 m -0.43513,1.234862 c -0.86165,0.346041 -1.7226,0.692905 -2.58285,1.040573 0,-6.33e-4 -0.0825,11.715567 -0.0825,11.714937 m -1.14486,-4.62264 c 2e-5,-0.003 -0.15936,25.9702 -0.15934,25.96762 19.67736,8.52605 38.90805,17.46409 57.40797,26.96703 m -5.17959,-4.09691 c 11.17635,5.63101 22.10189,11.45969 32.71559,17.51889 1.06572,-2.04091 2.13807,-4.0797 3.21719,-6.11634 m -16.16717,-0.69843 c 4.49732,2.4364 8.94519,4.91012 13.33928,7.42352 4.5879,-8.78027 9.29885,-17.52135 14.14335,-26.21966 -13.9167,-7.55194 -28.28634,-14.69746 -42.99029,-21.50586 m 3.21061,0.9872 c -8.79427,-4.11274 -17.71151,-8.10234 -26.72613,-11.98375 9.58254,-3.52225 19.25313,-6.91934 29.03349,-10.16553 m -6.29154,3.40821 c 16.00756,-5.449894 32.29758,-10.506011 48.96708,-15.053449 -2.73184,-7.869814 -5.56409,-15.725064 -8.48978,-23.565537 m 9.71116,23.601543 c -10e-6,10e-7 0.0757,-0.02052 0.0757,-0.02052 -3.1675,-9.168181 -6.47185,-18.316577 -9.90208,-27.444853 -14.82033,4.260532 -29.31841,8.892287 -43.55961,13.820057 m 47.96444,-1.481223 c -1.46923,-4.051074 -2.96412,-8.098229 -4.4837,-12.141437 -10.36636,2.98215 -20.57513,6.14594 -30.64872,9.46561 m 34.56626,1.430692 c -1.47476,-4.050136 -2.97506,-8.096352 -4.49994,-12.138618 -19.92545,5.740294 -39.26773,12.148987 -58.18539,19.043646 m 27.30172,-7.79642 c -7.20395,2.383141 -14.33903,4.845786 -21.41343,7.378487 m 13.91967,-6.239516 c -18.5565,6.309449 -36.66702,13.130413 -54.47416,20.298837 m -99.88694,-2.65901 c -3.7155,1.714952 -7.42448,3.435794 -11.12812,5.161624 -5e-5,-0.0011 0.51174,11.701562 0.51169,11.700442 m -1.16626,-9.974288 c -2e-5,7e-6 -0.10031,0.04677 -0.10032,0.04677 -4.9e-4,-0.01105 1.45405,33.189175 1.45357,33.178125 14.55592,5.39361 29.0225,10.86451 43.33592,16.46681 m -44.30591,-34.9574 c -1.2e-4,-0.003 0.76588,17.43323 0.76576,17.43045 2.49468,0.92414 4.98679,1.85054 7.47599,2.77947 m -7.79893,-15.73125 c -7e-5,-0.001 0.55089,12.57745 0.55083,12.57602 16.53412,6.12709 32.95515,12.35391 49.1696,18.75966 m -49.998,-25.64703 c -2e-5,-4.2e-4 0.29586,6.72017 0.29584,6.71975 21.69241,8.03149 43.19188,16.23317 64.28749,24.78382 m -55.73364,-22.29341 c 16.28094,6.07855 32.438,12.26676 48.38137,18.64074 M 538.6902,140.8996 c 13.05629,4.98649 26.008,10.06328 38.80781,15.27048 4.95988,2.02065 9.89517,4.0586 14.80138,6.11628 m -42.55708,-18.2693 c 9.31176,3.60951 18.56357,7.2712 27.73805,10.99979 8.32758,3.38914 16.58663,6.82714 24.75588,10.32545 2.01093,-4.82094 4.03838,-9.63839 6.08388,-14.45184 m -27.09202,6.54812 c 6.76764,2.77547 13.48728,5.58447 20.1474,8.43321 3.83317,-9.20422 7.72597,-18.39597 11.68893,-27.57166 -2.66267,-1.06719 -5.33325,-2.12724 -8.0111,-3.18056 m -17.67453,24.80734 c -4e-4,-1.6e-4 0.71086,0.29527 0.71045,0.2951 m 0.14227,0.48986 c 4.4971,1.87026 8.97094,3.75642 13.41807,5.66035 m -10.077,-3.99188 c 3.30432,1.38397 6.59552,2.77682 9.87223,4.17929 3.83114,-9.20405 7.72187,-18.39563 11.68269,-27.57116 -4.68134,-1.87728 -9.38733,-3.73251 -14.11434,-5.56781 m -3.47274,28.46872 c 2.50316,1.05575 4.99856,2.11681 7.4856,3.18351 3.84303,-9.20071 7.74681,-18.38849 11.72182,-27.55979 -12.43487,-4.99967 -25.04372,-9.84155 -37.75845,-14.5654 -3.78675,-1.40608 -7.58297,-2.80119 -11.38756,-4.18633 m 48.53565,21.61219 c -1.7e-4,4e-4 0.39568,-0.91478 0.39551,-0.91439 -12.35213,-4.96947 -24.87863,-9.78356 -37.51252,-14.48141 0,0 0,0 0,0 -0.002,-5.9e-4 -1.49386,-0.55438 -1.49544,-0.55497 m 14.92803,5.28113 c -4.46369,-1.70194 -8.94392,-3.38741 -13.43767,-5.05816 -10.77759,-4.00456 -21.6331,-7.92011 -32.54145,-11.76948 3.60412,-1.59924 7.21392,-3.19123 10.83049,-4.77515 m 3.29049,8.8856 c -4.50077,-1.62071 -9.01238,-3.22836 -13.53303,-4.82458 10.46006,-4.6382 20.96883,-9.21505 31.55249,-13.710221 6.70151,-2.847434 13.43404,-5.663734 20.20532,-8.439763 m -42.45719,26.470664 c -2.78088,-0.99541 -5.56571,-1.98604 -8.35408,-2.97227 3.53015,-1.56453 7.06581,-3.12201 10.60798,-4.67164 m -6.28221,1.12087 c 8.71898,-3.84186 17.47467,-7.63886 26.28214,-11.379291 6.23906,-2.650669 12.50495,-5.274355 18.80392,-7.863686 m -30.08413,13.695717 c 1.69004,-0.7302 3.38175,-1.45849 5.07524,-2.18477 m -3.70952,0.27076 c 3.29638,-1.42091 6.59931,-2.83445 9.90956,-4.240003 12.08898,-5.135115 24.2791,-10.168894 36.61561,-15.047723 -3.81907,-8.196246 -7.69879,-16.377835 -11.63022,-24.544496 m 7.66355,15.993468 c -2.66832,-5.660129 -5.36415,-11.313206 -8.08454,-16.959141 -6.13435,2.520141 -12.22725,5.075193 -18.28428,7.658738 m 21.8501,0.239974 c -1.19988,-2.513918 -2.40488,-5.026433 -3.61474,-7.537535 -5.45054,2.240469 -10.86844,4.508482 -16.25759,6.799535 m 12.33589,-6.475006 c -6.71417,2.780884 -13.37956,5.60218 -20.00355,8.455418 m 5.22362,-1.424866 c -2.09601,0.894819 -4.18772,1.793033 -6.27538,2.694375 -21.33032,9.189731 -42.27864,18.699376 -63.05589,28.36997 m 73.14903,-32.387689 c -3.37392,1.430271 -6.73645,2.869706 -10.08856,4.317204 -14.05547,6.056609 -27.94537,12.252172 -41.73006,18.541186 m 34.13573,-15.100677 c -13.55115,5.910939 -26.95746,11.944435 -40.27371,18.059191 M 567.455,71.414256 c -10.01024,4.371971 -19.94169,8.810224 -29.81644,13.298103 m 19.59924,-7.3737 c -15.52737,6.889358 -30.88337,13.926195 -46.1519,21.047284 -10e-5,-0.0022 0.70264,16.025187 0.70254,16.022947 m 0.87651,-18.197631 c -1.9e-4,8.6e-5 -0.35812,0.16672 -0.3583,0.166806 m 1.08448,0.822714 c -0.004,0.0019 -1.69243,0.788488 -1.69657,0.790417 -1.3e-4,-0.0029 0.84103,19.229244 0.8409,19.226364 m 0.27212,-20.309867 c -7.4e-4,3.43e-4 -0.71392,0.332461 -0.71466,0.332804 -4e-4,-0.0093 1.44469,33.189763 1.44429,33.180483 11.28017,4.18412 22.50656,8.41567 33.64949,12.71984 m -35.32215,-28.47471 c -1.1e-4,-0.002 0.69794,15.953 0.69784,15.95059 17.67814,6.55284 35.2254,13.22036 52.52747,20.09941 m -13.02727,-5.4797 c 6.64819,2.58659 13.26492,5.20045 19.84385,7.84699 m -14.54026,-5.47963 c 3.37527,1.32489 6.74192,2.65722 10.09913,3.9977 m -5.39559,-1.55982 c 5.16536,2.04368 10.30944,4.10566 15.42921,6.18851 8.03258,3.27252 16.00065,6.59042 23.88508,9.96399 3.57375,-8.58209 7.19933,-17.15333 10.88525,-25.71084 m -48.34658,8.62447 c 4.53399,1.79806 9.0515,3.61046 13.55048,5.43895 8.57906,3.49155 17.0857,7.03497 25.49668,10.6428 1.91851,-4.59483 3.85215,-9.18644 5.80223,-13.77439 m -41.52536,0.52971 c 3.42676,1.36777 6.84367,2.74402 10.24984,4.1295 8.35495,3.40329 16.64023,6.85569 24.83429,10.36877 3.83709,-9.20147 7.73433,-18.39024 11.70222,-27.56272 -5.91943,-2.37828 -11.87854,-4.72102 -17.86996,-7.03252 m 13.95351,4.36339 c -0.002,-8.8e-4 -1.79934,-0.7141 -1.80156,-0.71498 m 0.25261,1.27066 c -10.41553,-4.13 -20.94603,-8.15491 -31.55191,-12.09788 -10.5318,-3.91299 -21.13804,-7.741 -31.79532,-11.50531 -0.0133,0.006 3.63504,-1.60824 3.62177,-1.60235 m 48.0644,20.84878 c -4.71741,-1.82326 -9.45512,-3.62703 -14.20953,-5.41341 m 2.40101,-0.2426 c -2.69367,-1.01619 -5.39282,-2.02666 -8.09682,-3.03181 -10.38933,-3.85968 -20.85046,-7.63674 -31.36094,-11.3516 -0.0155,0.007 3.94411,-1.74382 3.92864,-1.73696 m 20.404,11.06404 c -7.93015,-2.91003 -15.89918,-5.77486 -23.89719,-8.60348 5.07413,-2.2482 10.15969,-4.48179 15.25969,-6.69847 m -13.61649,6.6743 c -0.009,-0.003 -3.05451,-1.07992 -3.06321,-1.08301 10.59224,-4.6966 21.23429,-9.33057 31.95333,-13.88081 7.21165,-3.062608 14.45934,-6.08915 21.75269,-9.068245 m -8.83881,3.866756 c 7.8818,-3.27547 15.81275,-6.500433 23.80517,-9.66031 -0.90933,-1.951335 -1.82211,-3.901839 -2.73821,-5.851509 m 2.26326,3.123733 c -3.697,-7.91409 -7.45009,-15.814457 -11.25119,-23.700854 -8.5632,3.512555 -17.0451,7.093405 -25.46084,10.725134 -4.85982,2.092681 -9.69974,4.201958 -14.52227,6.325955 M 445.3614,104.95449 c 9.76321,-0.4568 1.68049,-0.0787 11.4437,-0.53547 m -10.32307,0.17019 c 17.33768,-0.81149 34.68495,-1.62843 52.0441,-2.35925 0,2e-5 -0.46244,-11.003335 -0.46244,-11.003316 m -45.86213,13.509466 c 10.91926,-0.51038 21.84211,-1.01894 32.76916,-1.5028 m -10.72125,-0.82512 c 8.068,-0.3673 16.13869,-0.72521 24.21227,-1.06452 0.002,0.0444 -1.33216,-31.644111 -1.33029,-31.599679 -6.59025,0.277249 -13.17067,0.566548 -19.74138,0.862977 m 2.25651,0.535676 c -7.30576,0.326528 -36.16329,1.682218 -43.46906,2.008746 m 35.77617,-0.870836 c -10.80151,0.493925 -31.83544,1.492911 -42.63695,1.986836 m 3.35975,-1.504342 c 0.39499,-0.01836 -9.27129,0.428216 -8.87629,0.409857 -0.001,-0.02207 1.44614,31.619645 1.44513,31.597575 0.10366,-0.005 5.20914,-0.23889 5.31279,-0.24362 m -0.2042,-30.76454 c 0.20369,-0.0094 -6.92249,0.318369 -6.71879,0.308951 -9.8e-4,-0.02146 1.44069,31.616219 1.43972,31.594769 0.1859,-0.008 6.89529,-0.31723 7.08119,-0.32573 m -6.69291,-6.800598 c 0,-4e-6 0.3181,6.962618 0.3181,6.962618 0.24448,-0.0112 7.70616,-0.35518 7.95064,-0.36637 m -8.35402,-1.37166 c 0,0 0.0827,1.80758 0.0827,1.80758 3.0696,-0.14043 22.32683,-1.04088 25.39644,-1.18132 m 20.54416,-1.203 c 8.98418,-0.41193 17.97134,-0.81355 26.9618,-1.19215 0.002,0.0378 -1.33217,-31.639357 -1.33058,-31.601546 -10.17056,0.427545 -20.31809,0.883734 -30.44304,1.350483 m 30.68664,-0.72718 c -20.40193,0.858688 -40.71263,1.833171 -60.93582,2.777436 m 28.41997,-1.684025 c -4.1856,0.193669 -19.90118,0.931629 -24.08678,1.125298 m 22.17602,-1.068122 c -22.28414,1.034703 -17.10212,0.801742 -39.38626,1.836445 -1.4e-4,-0.0031 0.53538,11.761564 0.53524,11.758433 m 32.28599,-14.126315 c -95.89965,4.469127 62.94442,-2.937182 -32.95522,1.531945 -1e-4,-0.0021 0.45257,9.905323 0.45248,9.903238 M 448.9164,72.551881 c 11.72288,-0.549159 -36.94844,1.722573 -25.22557,1.173414 -0.001,-0.0234 1.44333,31.619995 1.44226,31.596595 6.6714,-0.30451 28.44286,-1.3283 35.11426,-1.63281 m -34.01902,1.55845 c 13.52181,-0.61825 30.67232,-1.43431 44.19413,-2.05256 m -9.57078,1.2155 c 12.84618,-0.5986 25.69748,-1.18633 38.55486,-1.72596 0.003,0.069 -1.20208,-28.704378 -1.19919,-28.635343 m -20.193,84.780593 c -0.21015,0.01 8.44865,-0.38035 8.2385,-0.37079 m -8.41912,0.49633 c 7.79028,-0.35489 15.56278,-0.70005 23.3177,-1.02705 0.007,0.17784 -1.4534,-34.65264 -1.44594,-34.47479 -7.23278,0.30417 -14.47002,0.62366 -21.7119,0.95178 m 9.43891,34.49345 c 4.35631,-0.19375 8.70702,-0.38336 13.05218,-0.56734 0.003,0.066 -1.45708,-34.542 -1.4543,-34.47599 -0.0565,0.002 -5.57864,0.23801 -5.63515,0.24039 m 7.67428,28.58029 c 0,0 -0.40982,-9.7569 -0.40982,-9.7569 m 0.84061,6.75643 c 0.001,0.0346 -1.11214,-26.54197 -1.1107,-26.50738 -17.97917,0.75429 -35.98538,1.60458 -54.02121,2.44789 m 55.01929,23.80495 c 0.007,0.15617 -0.85568,-20.45303 -0.84915,-20.29687 m 0.21924,1.51522 c 3.1e-4,0.008 -0.35042,-8.37985 -0.3501,-8.37232 -10.5658,0.44215 -21.14036,0.91746 -31.72418,1.40505 m 9.07703,1.51937 c -16.95737,0.77063 -35.92067,1.68342 -52.87805,2.45405 -0.001,-0.0286 1.56923,34.49986 1.56793,34.4713 0.058,-0.003 4.17146,-0.18972 4.22949,-0.19234 m -4.98992,-19.88834 c -3.8e-4,-0.008 0.87723,19.28496 0.87684,19.27656 18.6697,-0.84824 35.2157,-1.64809 53.8854,-2.49633 m 11.79856,0.1906 c -0.15774,0.007 8.99135,-0.38633 8.83362,-0.37944 -10e-6,-8e-5 -1.43579,-34.1165 -1.43579,-34.11659 m -1.69603,33.35019 c -0.0262,0.001 3.81643,-0.16203 3.79027,-0.16091 0.0157,0.37447 -1.4585,-34.85081 -1.44283,-34.47634 -0.0214,8.9e-4 -3.5105,0.14826 -3.53186,0.14915 m 4.61922,19.10912 c 0.004,0.0858 -0.78458,-18.76752 -0.78099,-18.68177 -24.53892,1.02893 -49.13037,2.23745 -73.78111,3.36364 m 73.39687,-3.66582 c 0,0 -0.0473,-1.127 -0.0473,-1.127 -8.93557,0.37427 -17.87744,0.77214 -26.82593,1.18099 m 15.15007,0.12688 c -0.22906,0.0101 -9.6177,0.43036 -9.84676,0.44045 m 1.52061,0.34501 c -16.24181,0.73155 -38.96197,1.82062 -55.20378,2.55217 -0.001,-0.0237 1.47431,32.45127 1.47323,32.42759 m 40.34148,-35.16514 c -25.12414,1.16377 -16.34142,0.76449 -41.46556,1.92826 -7.8e-4,-0.017 1.2093,26.53281 1.20852,26.51577 m 24.69571,-28.18027 c 9.53772,-0.44541 -35.81094,1.66128 -26.27322,1.21588 -0.001,-0.0299 1.57115,34.49543 1.56979,34.46556 0.0762,-0.003 4.73457,-0.2163 4.81073,-0.21977 m -2.79221,-33.16409 c 0.0241,-0.001 -2.59964,0.11865 -2.57549,0.11755 -4e-4,-0.009 0.8109,17.75591 0.8105,17.74721 m -1.05404,-3.95994 c -4.1e-4,-0.009 0.89319,19.58425 0.89278,19.57525 0.1,-0.005 5.38575,-0.24536 5.48572,-0.24989 m -0.33745,0.29462 c 18.27801,-0.83737 27.86561,-1.30104 46.14361,-2.13842 m -30.04463,0.96977 c 1.40741,-0.0658 6.09013,-0.28494 7.49755,-0.35072 m -6.93473,1.30994 c 17.84317,-0.83318 35.5931,-1.67079 53.25223,-2.41325 0.001,0.0251 -0.89222,-21.27408 -0.89117,-21.24899 M 645.91022,40.260018 C 621.98143,48.971525 598.82716,58.393162 576.151,68.183886 563.38251,73.685152 550.75083,79.301063 538.21074,84.99752 m 110.06472,-0.559322 c -1.94975,0.731393 -3.89521,1.467714 -5.83656,2.208758 m -8.17032,48.075284 c 27.78415,12.47907 54.52224,25.87525 79.38684,40.63376"+       style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-size:57.54236603px;line-height:125%;font-family:'Fira Sans';-inkscape-font-specification:'Fira Sans Ultra-Bold';text-align:start;letter-spacing:-1.70070565px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient10134);stroke-width:2.00000003;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"+       transform="matrix(0.26458333,0,0,0.26458333,0,197)"+       inkscape:connector-curvature="0" />+    <text+       xml:space="preserve"+       style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;line-height:125%;font-family:'Fira Sans';-inkscape-font-specification:'Fira Sans';text-align:start;letter-spacing:0px;word-spacing:-0.26458333px;writing-mode:lr-tb;text-anchor:start;fill:#b2b2b2;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;"+       x="247.48691"+       y="151.86511"+       id="text4786"+       transform="rotate(23.112766)"><tspan+         sodipodi:role="line"+         id="tspan4784"+         x="247.48691"+         y="151.86511"+         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:2.11666656px;font-family:'Fira Sans';-inkscape-font-specification:'Fira Sans';word-spacing:-0.26458333px;stroke-width:0.26458332px;fill:#b2b2b2;fill-opacity:1;">(C) DAVID LETTIER</tspan></text>+  </g>+</svg>