
æåŸã«Movie Monadã«ç«ã¡å¯ã£ããšãããã¹ãŠã®Webãã¯ãããžãŒïŒHTMLãCSSãJavaScriptãããã³ElectronïŒã䜿çšããŠãã¹ã¯ããããããªãã¬ãŒã€ãŒãäœæããŸããã ç§Theã¯ããããžã§ã¯ãã®ãã¹ãŠã®ãœãŒã¹ã³ãŒããHaskellã§æžãããŠããããšã§ããã
Webã¢ãããŒãã®å¶éã®1ã€ã¯ããããªãã¡ã€ã«ã®ãµã€ãºã倧ãããªããããªãããšã§ããããããªããšãã¢ããªã±ãŒã·ã§ã³ãã¯ã©ãã·ã¥ããŸããã ãããåé¿ããããã«ããã¡ã€ã«ãµã€ãºã®æ€èšŒãå®è£
ããå¶éãè¶
ããããšã«ã€ããŠãŠãŒã¶ãŒã«èŠåããŸããã
ãããªãã¡ã€ã«ãHTML5ãµãŒããŒã«ã¹ããªãŒãã³ã°ããããã¯ãšã³ããã»ããã¢ãããããµãŒããŒãšElectronã¢ããªã±ãŒã·ã§ã³ã䞊è¡ããŠå®è¡ããWebã䜿çšããã¢ãããŒãã®éçºãç¶ããããšãã§ããŸãã 代ããã«ãWebãã¯ãããžãŒãæŸæ£ããGTK +ãGstreamerãããã³X11ãŠã£ã³ããŠã·ã¹ãã ã䜿çšããŸãã

WaylandãQuartzãWinAPIãªã©ã®å¥ã®ãŠã£ã³ããŠç®¡çã·ã¹ãã ã䜿çšããå Žåããã®ã¢ãããŒããGDKããã¯ãšã³ãã§åäœããããã«é©åãããããšãã§ããŸãã é©å¿ã¯ã GStreamer playbinãããªåºåãMovie MonadãŠã£ã³ããŠã«åã蟌ãããšã§ãã
GDKã¯GTK +移æ€æ§ã®éèŠãªåŽé¢ã§ãã Glibã¯ãã§ã«äœã¬ãã«ã®ã¯ãã¹ãã©ãããã©ãŒã æ©èœãæäŸããŠãããããGTK +ãä»ã®ãã©ãããã©ãŒã ã§åäœãããããã«ã¯ãGDKããªãã¬ãŒãã£ã³ã°ã·ã¹ãã ã®åºæ¬çãªã°ã©ãã£ãã¯ã¬ãã«ã«ç§»æ€ããã ãã§ãã ã€ãŸããGTK +ã¢ããªã±ãŒã·ã§ã³ãWindowsããã³macOSïŒ ãœãŒã¹ ïŒã§å®è¡ã§ããããã«ããã®ã¯ãWindows APIããã³Quartzã®GDKããŒãã§ãã
ãã®èšäºã®å¯Ÿè±¡è
- GTK +ãŠãŒã¶ãŒã€ã³ã¿ãŒãã§ã€ã¹ãå®è£
ããHaskellããã°ã©ããŒåãã
- é¢æ°åããã°ã©ãã³ã°ã«èå³ã®ããããã°ã©ãåãã
- GUIã®äœæè
åãã
- GitHub Electronã®ä»£æ¿åãæ¢ããŠãã人åãã
- ãããªãã¬ãŒã€ãŒã®ãã¡ã³åãã
æ€èšããããš
- ã¹ã¿ãã¯ã
- Haskell-giãã€ã³ãã£ã³ã°
- ããŸããŸãªããŒã¿ãšãããã®ãã¡ã€ã«ã®ãã£ã¬ã¯ããªã
- 空ãå°
- GTK +ã
- Gstreamerã
- Movie Monadã®äœææ¹æ³ã
ãããžã§ã¯ãã®ã»ããã¢ãã
æåã«ãHaskellããã°ã©ã ãéçºããããã«ãã·ã³ãæ§æãããããžã§ã¯ããã£ã¬ã¯ããªã®ãã¡ã€ã«ãšäŸåé¢ä¿ãæ§æããå¿
èŠããããŸãã
ãã·ã³ããŸã Haskellããã°ã©ã ãéçºããæºåãæŽã£ãŠããªãå Žåã¯ãHaskellãã©ãããã©ãŒã ãããŠã³ããŒãããŠã€ã³ã¹ããŒã«ããããšã§ãå¿
èŠãªãã®ããã¹ãŠå
¥æã§ããŸã ã
ã¹ã¿ãã¯
StackããŸã ãæã¡ã§ãªãå Žåã¯ãéçºãéå§ããåã«å¿
ãStackãã€ã³ã¹ããŒã«ããŠãã ããã ãã ããæ¢ã«Haskellãã©ãããã©ãŒã ã䜿çšããŠããå Žåã¯ããã§ã«StackããããŸãã
Movie Monadã§ãããªãåçããåã«ããŠãŒã¶ãŒãéžæãããã¡ã€ã«ã«é¢ããæ
å ±ãåéããå¿
èŠããããŸãã ããã«ã¯ExifToolã䜿çšããŸãã Linuxã§äœæ¥ããŠããå Žåã¯ããã®ããŒã«ïŒ which exiftool
ïŒããã§ã«ããå¯èœæ§ããããŸãã ExifToolã¯ãWindowsãMacãããã³Linuxã§äœ¿çšã§ããŸãã
ãããžã§ã¯ããã¡ã€ã«
ãããžã§ã¯ããã¡ã€ã«ãååŸããã«ã¯ã3ã€ã®æ¹æ³ããããŸãã
wget https://github.com/lettier/movie-monad/archive/master.zip unzip master.zip mv movie-monad-master movie-monad cd movie-monad/
ZIPã¢ãŒã«ã€ããããŠã³ããŒãããŠå±éã§ããŸãã
git clone git@github.com:lettier/movie-monad.git cd movie-monad/
SSHã䜿çšããŠgitã¯ããŒã³ãäœæã§ããŸãã
git clone https://github.com/lettier/movie-monad.git cd movie-monad/
HTTPSçµç±ã§gitã®ã¯ããŒã³ãäœæã§ããŸãã
ãã¹ã±ã«ã®
haskell-gi㯠ãèªå·±èšºæçšã®ããã«ãŠã§ã¢GObjectïŒã€ã³ããã¹ãã¯ã·ã§ã³ããã«ãŠã§ã¢ïŒã䜿çšããŠãHaskellãã€ã³ãã£ã³ã°ãã©ã€ãã©ãªã«çæã§ããŸãã å·çæç¹ã§ã¯ãå¿
èŠãªãã€ã³ãã£ã³ã°ã¯ãã¹ãŠHackageã§å©çšã§ããŸãã
äŸåé¢ä¿
次ã«ããããžã§ã¯ãã®äŸåé¢ä¿ãã€ã³ã¹ããŒã«ããŸãã
cd movie-monad/ stack install
ã³ãŒã
次ã«ãMovie Monadã®å®è£
ãã«ã¹ã¿ãã€ãºããŸãã ãœãŒã¹ãã¡ã€ã«ãåé€ããŠåäœæããããæ瀺ã«åŸã£ãŠãã ããã
Paths_movie_monad.hs
Paths_movie_monad.hs
ãå®è¡æã«Glade XML GUIãã¡ã€ã«ãèŠã€ããããã«äœ¿çšãããŸãã éçºäžãªã®ã§ããããŒã¢ãžã¥ãŒã«ïŒ movie-monad/src/dev/Paths_movie_monad.hs
ïŒã䜿çšããŠmovie-monad/src/data/gui.glade
movie-monad/src/dev/Paths_movie_monad.hs
movie-monad/src/data/gui.glade
ãæ€çŽ¢ãmovie-monad/src/data/gui.glade
ã ãããžã§ã¯ãããã«ã/ã€ã³ã¹ããŒã«ãããšãå®éã®Paths_movie_monad
ã¢ãžã¥ãŒã«ãèªåçã«çæãããŸãã getDataFileName
é¢æ°ãæäŸãããŸãã åºåããŒã¿ã«ã data-dir (movie-monad/src/) data-files
ã³ããŒãŸãã¯ã€ã³ã¹ããŒã«ããã絶察ãã¹ã®åœ¢åŒã§ãã¬ãã£ãã¯ã¹ãå²ãåœãŠdata-dir (movie-monad/src/) data-files
ã
{-# LANGUAGE OverloadedStrings #-} module Paths_movie_monad where dataDir :: String dataDir = "./src/" getDataFileName :: FilePath -> IO FilePath getDataFileName a = do putStrLn "You are using a fake Paths_movie_monad." return (dataDir ++ "/" ++ a)
ãããŒã¢ãžã¥ãŒã«Paths_movie_monad
{-# LANGUAGE CPP #-} {-# OPTIONS_GHC -fno-warn-missing-import-lists #-} {-# OPTIONS_GHC -fno-warn-implicit-prelude #-} module Paths_movie_monad ( version, getBinDir, getLibDir, getDynLibDir, getDataDir, getLibexecDir, getDataFileName, getSysconfDir ) where import qualified Control.Exception as Exception import Data.Version (Version(..)) import System.Environment (getEnv) import Prelude #if defined(VERSION_base) #if MIN_VERSION_base(4,0,0) catchIO :: IO a -> (Exception.IOException -> IO a) -> IO a #else catchIO :: IO a -> (Exception.Exception -> IO a) -> IO a #endif #else catchIO :: IO a -> (Exception.IOException -> IO a) -> IO a #endif catchIO = Exception.catch version :: Version version = Version [0,0,0,0] [] bindir, libdir, dynlibdir, datadir, libexecdir, sysconfdir :: FilePath bindir = "/home/<snip>/.stack-work/install/x86_64-linux-nopie/lts-9.1/8.0.2/bin" libdir = "/home/<snip>/.stack-work/install/x86_64-linux-nopie/lts-9.1/8.0.2/lib/x86_64-linux-ghc-8.0.2/movie-monad-0.0.0.0" dynlibdir = "/home/<snip>/.stack-work/install/x86_64-linux-nopie/lts-9.1/8.0.2/lib/x86_64-linux-ghc-8.0.2" datadir = "/home/<snip>/.stack-work/install/x86_64-linux-nopie/lts-9.1/8.0.2/share/x86_64-linux-ghc-8.0.2/movie-monad-0.0.0.0" libexecdir = "/home/<snip>/.stack-work/install/x86_64-linux-nopie/lts-9.1/8.0.2/libexec" sysconfdir = "/home/<snip>/.stack-work/install/x86_64-linux-nopie/lts-9.1/8.0.2/etc" getBinDir, getLibDir, getDynLibDir, getDataDir, getLibexecDir, getSysconfDir :: IO FilePath getBinDir = catchIO (getEnv "movie_monad_bindir") (\_ -> return bindir) getLibDir = catchIO (getEnv "movie_monad_libdir") (\_ -> return libdir) getDynLibDir = catchIO (getEnv "movie_monad_dynlibdir") (\_ -> return dynlibdir) getDataDir = catchIO (getEnv "movie_monad_datadir") (\_ -> return datadir) getLibexecDir = catchIO (getEnv "movie_monad_libexecdir") (\_ -> return libexecdir) getSysconfDir = catchIO (getEnv "movie_monad_sysconfdir") (\_ -> return sysconfdir) getDataFileName :: FilePath -> IO FilePath getDataFileName name = do dir <- getDataDir return (dir ++ "/" ++ name)
èªåçæã¢ãžã¥ãŒã«Paths_movie_monad
ã
Main.hs
Main.hs
ã¯ãMovie Monadã®ãšã³ããªãã€ã³ãã§ãã ãã®ãã¡ã€ã«ã§ã¯ãç°ãªããŠã£ãžã§ããã䜿çšããŠãŠã£ã³ããŠãæ§æããGStreamerãæ¥ç¶ãããŠãŒã¶ãŒãçµäºãããšãŠã£ã³ããŠãç Žå£ããŸãã
ãã©ã°ã
ãªãŒããŒããŒããããæååãšã¬ãã·ã«ã«ã¹ã³ãŒãã®åå€æ°ãå¿
èŠã§ããããšãã³ã³ãã€ã©ãŒïŒGHCïŒã«äŒããå¿
èŠããããŸãã
OverloadedStrings
ã䜿çšãããšã String/[Char]
ãŸãã¯Textãå¿
èŠãªå Žæã§ã String/[Char]
åãªãã©ã«ïŒ "Literal"
ïŒã䜿çšã§ããŸãã ScopedTypeVariables
ã䜿çšãããšãExifToolãåŒã³åºããããšãã«ã€ã³ã¿ãŒã»ããããããã«æž¡ãããã©ã ãé¢æ°ã®ãã©ã¡ãŒã¿ãŒãã¿ãŒã³ã§åã·ã°ããã£ã䜿çšã§ããŸãã
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-}
茞å
¥å
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 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 Paths_movie_monad
Cãã€ã³ãã£ã³ã°ã䜿çšããããããã®èšèªã«æ¢ã«ååšããåã䜿çšããå¿
èŠããããŸãã ã€ã³ããŒãã®å€§éšåã¯ãhaskell-giã«ãã£ãŠçæããããã€ã³ãã£ã³ã°ã§ãã
IsVideoOverlay
GStreamerãããªgi-gstvideo
ïŒ gi-gstvideo
ïŒã«ã¯ãã¿ã€ãïŒã€ã³ã¿ãŒãã§ã€ã¹ïŒ IsVideoOverlay
ã¯ã©ã¹ãå«ãŸããŠããŸãã GStreamerãã€ã³ãã£ã³ã°ïŒ gi-gst
ïŒã«ã¯èŠçŽ ã¿ã€ããå«ãŸããŸãã playbin
é¢æ°ã§playbin
èŠçŽ ã䜿çšããã«ã¯ã playbin
åïŒåã€ã³ã¹ã¿ã³ã¹ïŒ IsVideoOverlay
ã€ã³ã¹ã¿ã³ã¹ã宣èšããå¿
èŠããããŸãã ãŸããCåŽã§ã¯ã playbin
ã¯VideoOverlay
ã€ã³ã¿ãŒãã§ã€ã¹ãå®è£
ããŸãã
newtype GstElement = GstElement GI.Gst.Element instance GI.GstVideo.IsVideoOverlay GstElement
haskell-giãã€ã³ãã£ã³ã°ã®å€éšã§ã€ã³ã¹ã¿ã³ã¹ã宣èšããéã«ã倱ãããïŒå€ç«ããïŒã€ã³ã¹ã¿ã³ã¹ã®åºçŸãé¿ããããã«ã GI.Gst.Element
ãæ°ããåïŒnewtypeïŒã§ã©ããããããšã«æ³šæããŠãã ããã
ã¡ã€ã³
Main
ã¯ç§ãã¡ã®æ倧ã®æ©èœã§ãã ãã®äžã§ããã¹ãŠã®GUIãŠã£ãžã§ãããåæåããç¹å®ã®ã€ãã³ãã«åºã¥ããŠã³ãŒã«ããã¯ããã·ãŒãžã£ãå®çŸ©ããŸãã
main :: IO () main = do
GIåæå
_ <- GI.Gst.init Nothing _ <- GI.Gtk.init Nothing
ããã§ãGStreamerãšGTK +ãåæåããŸããã
GUIãŠã£ãžã§ããã®æ§ç¯
gladeFile <- getDataFileName "data/gui.glade" 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" seekScale <- builderGetObject GI.Gtk.Scale builder "seek-scale" onOffSwitch <- builderGetObject GI.Gtk.Switch builder "on-off-switch" volumeButton <- builderGetObject GI.Gtk.VolumeButton builder "volume-button" desiredVideoWidthComboBox <- builderGetObject GI.Gtk.ComboBoxText builder "desired-video-width-combo-box" fullscreenButton <- builderGetObject GI.Gtk.Button builder "fullscreen-button" 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"
ãã§ã«è¿°ã¹ãããã«ããã¹ãŠã®GUIãŠã£ãžã§ãããèšè¿°ããdata/gui.glade
XMLãã¡ã€ã«ãžã®çµ¶å¯Ÿãã¹ãååŸããŸãã 次ã«ããã®ãã¡ã€ã«ããã³ã³ã¹ãã©ã¯ã¿ãŒãäœæãããŠã£ãžã§ãããååŸããŸãã Gladeã䜿çšããŠããªãã£ãå Žåã¯ãæåã§äœæããå¿
èŠããããããªãé¢åã§ãã
ãã¬ã€ãã³
playbin <- fromJust <$> GI.Gst.elementFactoryMake "playbin" (Just "MultimediaPlayer")
ããã§ã¯ã playbin
GStreamerãã€ãã©ã€ã³ãäœæããŸãã ããŸããŸãªããŒãºã解決ããããã«èšèšãããŠãããç¬èªã®ã³ã³ãã¢ãäœæããæéãç¯çŽã§ããŸãã ãã®èŠçŽ ãMultimediaPlayer
åŒã³ãŸãã
GStreameråºåã®åã蟌ã¿
GTK +ãšGStreamerãé£ââæºãããã«ã¯ãGStreamerã«ãããªã®æ£ç¢ºãªåºåå
ãæ瀺ããå¿
èŠããããŸãã ãããè¡ããªããšã playbin
ã䜿çšãããããGStreamerã¯ç¬èªã®ãŠã£ã³ããŠãäœæããŸãã
_ <- GI.Gtk.onWidgetRealize drawingArea $ onDrawingAreaRealize drawingArea playbin fullscreenButton -- ... 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
drawingAreaãŠã£ãžã§ããã®drawingArea
ãdrawingArea
ããšãã®ã³ãŒã«ããã¯ã®ã»ããã¢ããã衚瀺ãããŸãã GStreamerããããªã衚瀺ããã®ã¯ãã®ãŠã£ãžã§ããã§ãã ã¬ã³ããŒãšãªã¢ãŠã£ãžã§ããã®èŠªGDKãŠã£ã³ããŠãååŸããŸãã 次ã«ããŠã£ã³ããŠãã³ãã©ãŒããŸãã¯GTK +ãŠã£ã³ããŠã®X11ã·ã¹ãã ã®XID
ãååŸããŸãã æååCUIntPtr
ã¯IDãCULong
ããCULong
ã«å€æããŸããããã¯CUIntPtr
ã«å¿
èŠã§ãã æ£ããåãåãåã£playbin
ã xid'
ãã³ãã©ãŒã®å©ããåããŠããŠã£ã³ããŠã«playbin
ã®åºåãæç»ã§ããããšãplaybin
ããŸãã
Gladeã®ãã°ã«ãããããã°ã©ã ã§ãã«ã¹ã¯ãªãŒã³ãŠã£ãžã§ãããé衚瀺ã«ããŸããGladeã®è¡šç€ºããã¯ã¹ããªãã«ããŠãããŠã£ãžã§ããã¯é衚瀺ã«ãªããªãããã§ãã
Xã·ã¹ãã ã§ã¯ãªãä»ã®ã·ã¹ãã ã䜿çšããŠããå Žåã¯ãããã§Movie MonadããŠã£ã³ããŠã·ã¹ãã ã§åäœããããã«èª¿æŽããå¿
èŠããããŸãã
ãã¡ã€ã«éžæ
_ <- GI.Gtk.onFileChooserButtonFileSet fileChooserButton $ onFileChooserButtonFileSet playbin fileChooserButton volumeButton isWindowFullScreenRef desiredVideoWidthComboBox onOffSwitch fullscreenButton drawingArea window errorMessageDialog -- ... 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
ãããªåçã»ãã·ã§ã³ãéå§ããã«ã¯ããŠãŒã¶ãŒã¯ãããªãã¡ã€ã«ãéžæã§ããå¿
èŠããããŸãã ãã¡ã€ã«ãéžæããåŸããã¹ãŠãæ£åžžã«æ©èœããããã«å¿
èŠãªããã€ãã®ã¢ã¯ã·ã§ã³ãå®è¡ããå¿
èŠããããŸãã
- ãã¡ã€ã«éžæãŠã£ãžã§ãããããã¡ã€ã«åãååŸããŸãã
- ã©ã®ãã¡ã€ã«ãåçãããã
playbin
ã«playbin
ãŸãã - ãŠã£ãžã§ããã®é³éã¬ãã«ã
playbin
ãšåãã«ãplaybin
ã - ç®çã®ç»åã®å¹
ãšãããªã®ãµã€ãºã«åºã¥ããŠããŠã£ã³ããŠã®é©åãªå¹
ãšé«ãã決å®ããŸãã
- ãŠã£ã³ããŠã®å¯žæ³ãæ£åžžã«åä¿¡ãããå ŽåïŒ
- ãã¡ã€ã«ã®åçãéå§ããŸãã
- äžæåæ¢/åçãã¿ã³ãããªã³ãç¶æ
ã«èšå®ããŸãã
- å
šç»é¢ãŠã£ãžã§ããã衚瀺ããŸãã
- ãããªãå
šç»é¢è¡šç€ºã§ãªãå ŽåïŒ
- ãããªã®çžå¯Ÿãµã€ãºã«äžèŽããããã«ãŠã£ã³ããŠã®ãµã€ãºãå€æŽããŸãã
- ãŠã£ã³ããŠã®å¯žæ³ãååŸã§ããªãã£ãå ŽåïŒ
playbin
ã- ã¹ã€ãããããªããã®äœçœ®ã«åããŸãã
- å¯èœã§ããã°ããŠã£ã³ããŠãå
šç»é¢ã¢ãŒããã解é€ããŸãã
- ãŠã£ã³ããŠãµã€ãºããªã»ããããŸãã
- å°ããªãã€ã¢ãã°ãšã©ãŒã¡ãã»ãŒãžã衚瀺ããŸãã
äžæåæ¢ããŠåç
_ <- GI.Gtk.onSwitchStateSet onOffSwitch (onSwitchStateSet playbin) -- ... 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
ãã¹ãŠãã·ã³ãã«ã§ãã ã¹ã€ãããããªã³ãã®äœçœ®ã«ããå Žåã playbin
èŠçŽ ãplaybin
ç¶æ
ã«èšå®ããŸãã ãã以å€ã®å Žåã¯ãäžæåæ¢ç¶æ
ãäžããŸãã
é³éèšå®
_ <- GI.Gtk.onScaleButtonValueChanged volumeButton (onScaleButtonValueChanged playbin) -- ... onScaleButtonValueChanged :: GI.Gst.Element -> Double -> IO () onScaleButtonValueChanged playbin volume = void $ Data.GI.Base.Properties.setObjectPropertyDouble playbin "volume" volume
ãŠã£ãžã§ããã®é³éã¬ãã«ãå€æŽããããšããã®å€ãGStreamerã«æž¡ããŠãåçé³éã調æŽã§ããããã«ããŸãã
ãããªããã²ãŒã·ã§ã³
seekScaleHandlerId <- GI.Gtk.onRangeValueChanged seekScale (onRangeValueChanged playbin seekScale) -- ... 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
Movie Monadã«ã¯ãã¹ã©ã€ããŒãååŸã«ç§»åããŠãããªãã¬ãŒã ã移åã§ããåçããŒããããŸãã
0ã100ïŒ
ã®ã¹ã±ãŒã«ã¯ããããªãã¡ã€ã«ã®åèšæéãè¡šããŸãã ããšãã°ãã¹ã©ã€ããŒã50ã«ç§»åãããšãéå§ãšçµäºã®äžéã«ããã¿ã€ã ã¹ã¿ã³ãã«ç§»åããŸãã ã¹ã±ãŒã«ããŒããããããªã®é·ããŸã§èª¿æŽã§ããŸããã説æããæ¹æ³ã¯ããäžè¬çã§ãã
ãã®ã³ãŒã«ããã¯ã§ã¯ãåŸã§å¿
èŠã«ãªããããã·ã°ãã«IDïŒ seekScaleHandlerId
ïŒã䜿çšããããšã«æ³šæããŠãã ããã
åçããŒã®æŽæ°
_ <- GI.GLib.timeoutAddSeconds GI.GLib.PRIORITY_DEFAULT 1 (updateSeekScale playbin seekScale seekScaleHandlerId) -- ... 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
ã¹ã±ãŒã«ãšãããªåçããã»ã¹ãåæããã«ã¯ãGTK +ãšGStreamerã®éã§ã¡ãã»ãŒãžã転éããå¿
èŠããããŸãã æ¯ç§ãçŸåšã®åçäœçœ®ãèŠæ±ããããã«å¿ããŠã¹ã±ãŒã«ãæŽæ°ããŸãã ãã®ããããã¡ã€ã«ã®ã©ã®éšåããã§ã«è¡šç€ºãããŠãããããŠãŒã¶ãŒã«ç€ºããã¹ã©ã€ããŒã¯åžžã«å®éã®åçäœçœ®ã«å¯Ÿå¿ããŸãã
以åã«æ§æãããã³ãŒã«ããã¯ãéå§ããªãããã«ãåçããŒãæŽæ°ãããšãã«onRangeValueChanged
ã·ã°ãã«onRangeValueChanged
ãç¡å¹ã«ããŸãã onRangeValueChanged onRangeValueChanged
ã¯ã ãŠãŒã¶ãŒãã¹ã©ã€ããŒã®äœçœ®ãå€æŽããå Žåã«ã®ã¿å®è¡ããå¿
èŠããããŸã ã
ãããªã®ãµã€ãºãå€æŽãã
_ <- GI.Gtk.onComboBoxChanged desiredVideoWidthComboBox $ onComboBoxChanged fileChooserButton desiredVideoWidthComboBox drawingArea window -- ... 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
ãã®ãŠã£ãžã§ããã䜿çšãããšããŠãŒã¶ãŒã¯ç®çã®ãããªå¹
ãéžæã§ããŸãã é«ãã¯ããããªãã¡ã€ã«ã®ã¢ã¹ãã¯ãæ¯ã«åºã¥ããŠèªåçã«éžæãããŸãã
å
šç»é¢ã¢ãŒã
_ <- GI.Gtk.onWidgetButtonReleaseEvent fullscreenButton (onFullscreenButtonRelease isWindowFullScreenRef desiredVideoWidthComboBox fileChooserButton 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
ãŠãŒã¶ãŒãå
šç»é¢ã¢ãŒããŠã£ãžã§ããã®ãã¿ã³ãé¢ããšããŠã£ã³ããŠã®å
šç»é¢ã¢ãŒãã®ç¶æ
ãåãæ¿ããããã¡ã€ã«éžæããã«ãšãããªå¹
éžæãŠã£ãžã§ãããé衚瀺ã«ãªããŸãã å
šç»é¢ã¢ãŒããçµäºãããšãããã«ãšãŠã£ãžã§ããã埩å
ãããŸãã
ãããªããªãå Žåããã«ã¹ã¯ãªãŒã³ãŠã£ãžã§ããã¯è¡šç€ºãããªãããšã«æ³šæããŠãã ããã
_ <- GI.Gtk.onWidgetWindowStateEvent window (onWidgetWindowStateEvent isWindowFullScreenRef) -- ... 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
ãŠã£ã³ããŠã®ãã«ã¹ã¯ãªãŒã³ç¶æ
ãå¶åŸ¡ããã«ã¯ããŠã£ã³ããŠã®ç¶æ
ãå€ãããã³ã«éå§ããããã«ã³ãŒã«ããã¯ãæ§æããå¿
èŠããããŸãã ããŸããŸãªã³ãŒã«ããã¯ã¯ããŠã£ã³ããŠã®ãã«ã¹ã¯ãªãŒã³ç¶æ
ã«é¢ããæ
å ±ã«äŸåããŠããŸãã IORef
ãè£å©ãšããŠäœ¿çšããããããåé¢æ°ãèªã¿åããã³ãŒã«ããã¯ãæžã蟌ãŸããŸãã ãã®IORef
ã¯å¯å€ïŒããã³æ±çšïŒãªã³ã¯ã§ãã çæ³çã«ã¯ããã«ã¹ã¯ãªãŒã³ã¢ãŒãã®ãšãã«ãŠã£ã³ããŠãæ£ç¢ºã«èŠæ±ããå¿
èŠããããŸããããã®ããã®APIã¯ãããŸããã ãããã£ãŠãå¯å€ãªã³ã¯ã䜿çšããŸãã
ã¡ã€ã³ã¹ã¬ããã§1ã€ã®ã©ã€ã¿ãŒãšããŒãã®ã·ã°ãã«ã³ãŒã«ããã¯ã䜿çšããããšã«ãããäžè¬çãªå¯å€ç¶æ
ã®ãã©ãããåé¿ã§ããŸãã å®è¡ã¹ã¬ããã®å®å
šæ§ãå¿é
ãªå Žåã¯ã代ããã«MVar
ã TVar
ãŸãã¯atomicModifyIORef
䜿çšã§ããŸãã
ããã°ã©ã ã«ã€ããŠ
_ <- GI.Gtk.onWidgetButtonReleaseEvent aboutButton (onAboutButtonRelease aboutDialog) -- ... 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
åé¡ã®æåŸã®ãŠã£ãžã§ããã¯ãAboutãã€ã¢ãã°ã§ãã ããã§ã¯ããã€ã¢ãã°ããã¯ã¹ãã¡ã€ã³ãŠã£ã³ããŠã«è¡šç€ºããã[About]ãã¿ã³ã«é¢é£ä»ããŸãã
ãŠã£ã³ããŠãéãã
_ <- GI.Gtk.onWidgetDestroy window (onWindowDestroy playbin) -- ... onWindowDestroy :: GI.Gst.Element -> IO () onWindowDestroy playbin = do _ <- GI.Gst.elementSetState playbin GI.Gst.StateNull _ <- GI.Gst.objectUnref playbin GI.Gtk.mainQuit
ãŠãŒã¶ãŒããŠã£ã³ããŠãéãããšã playbin
ãã€ãã©ã€ã³ãç Žæ£ãããã¡ã€ã³ã®GTKã«ãŒããçµäºããŸãã
æã¡äžã
GI.Gtk.widgetShowAll window GI.Gtk.main
æåŸã«ãã¡ã€ã³ãŠã£ã³ããŠã衚瀺ãŸãã¯æç»ããã¡ã€ã³ã®GTK +ãµã€ã¯ã«ãéå§ããŸãã mainQuit
ãŸã§ãããã¯ããŸãã
å®å
šãªMain.hsãã¡ã€ã«
以äžã¯ã movie-monad/src/Main.hs
ã main
é¢é£ããããŸããŸãªãã«ããŒé¢æ°ã¯è¡šç€ºãããŠããŸããã
{- Movie Monad (C) 2017 David lettier lettier.com -} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} 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 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 Paths_movie_monad -- Declare Element a type instance of IsVideoOverlay via a newtype wrapper -- Our GStreamer element is playbin -- Playbin implements the GStreamer VideoOverlay interface newtype GstElement = GstElement GI.Gst.Element instance GI.GstVideo.IsVideoOverlay GstElement main :: IO () main = do _ <- GI.Gst.init Nothing _ <- GI.Gtk.init Nothing gladeFile <- getDataFileName "data/gui.glade" 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" seekScale <- builderGetObject GI.Gtk.Scale builder "seek-scale" onOffSwitch <- builderGetObject GI.Gtk.Switch builder "on-off-switch" volumeButton <- builderGetObject GI.Gtk.VolumeButton builder "volume-button" desiredVideoWidthComboBox <- builderGetObject GI.Gtk.ComboBoxText builder "desired-video-width-combo-box" fullscreenButton <- builderGetObject GI.Gtk.Button builder "fullscreen-button" 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 _ <- GI.Gtk.onFileChooserButtonFileSet fileChooserButton $ onFileChooserButtonFileSet playbin fileChooserButton volumeButton isWindowFullScreenRef desiredVideoWidthComboBox onOffSwitch fullscreenButton drawingArea window errorMessageDialog _ <- GI.Gtk.onSwitchStateSet onOffSwitch (onSwitchStateSet playbin) _ <- GI.Gtk.onScaleButtonValueChanged volumeButton (onScaleButtonValueChanged playbin) seekScaleHandlerId <- GI.Gtk.onRangeValueChanged seekScale (onRangeValueChanged playbin seekScale) _ <- GI.GLib.timeoutAddSeconds GI.GLib.PRIORITY_DEFAULT 1 (updateSeekScale playbin seekScale seekScaleHandlerId) _ <- GI.Gtk.onComboBoxChanged desiredVideoWidthComboBox $ onComboBoxChanged fileChooserButton desiredVideoWidthComboBox drawingArea window _ <- GI.Gtk.onWidgetButtonReleaseEvent fullscreenButton (onFullscreenButtonRelease isWindowFullScreenRef desiredVideoWidthComboBox fileChooserButton window) _ <- GI.Gtk.onWidgetWindowStateEvent window (onWidgetWindowStateEvent isWindowFullScreenRef) _ <- GI.Gtk.onWidgetButtonReleaseEvent aboutButton (onAboutButtonRelease aboutDialog) _ <- GI.Gtk.onWidgetDestroy window (onWindowDestroy playbin) GI.Gtk.widgetShowAll window GI.Gtk.main builderGetObject :: (GI.GObject.GObject b, GI.Gtk.IsBuilder a) => (Data.GI.Base.ManagedPtr b -> b) -> a -> Prelude.String -> IO b builderGetObject objectTypeClass builder objectId = fromJust <$> GI.Gtk.builderGetObject builder (pack objectId) >>= GI.Gtk.unsafeCastTo objectTypeClass 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
Movie Monad
, Movie Monad .
cd movie-monad/ stack clean stack install stack exec -- movie-monad # Or just `movie-monad` if `stack path | grep local-bin-path` is in your `echo $PATH`
, Movie Monad .
ãããã«
Movie Monad , GTK+ GStreamer. , Electron-. Movie Monad .
GTK+ . , GTK+ ~50 , Electron â ~300 (500%- ).
, GTK+ . , Electron - . haskell-gi .
, GTK+ Haskell, Gifcurry . .