diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 Jappie Klooster
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Readme.md b/Readme.md
new file mode 100644
--- /dev/null
+++ b/Readme.md
@@ -0,0 +1,139 @@
+![logo](doc/logo.png)
+
+[![Jappiejappie](https://img.shields.io/badge/twitch.tv-jappiejappie-purple?logo=twitch)](https://www.twitch.tv/jappiejappie)
+[![Jappiejappie](https://img.shields.io/badge/Youtube-jappieklooster-red?logo=Youtube)](https://www.Youtube.com/channel/UCQxmXSQEYyCeBC6urMWRPVw)
+[![Build status](https://img.shields.io/travis/jappeace/cut-the-crap)](https://travis-ci.org/jappeace/cut-the-crap/builds/)
+[![Jappiejappie](https://img.shields.io/badge/discord-jappiejappie-black?logo=discord)](https://discord.gg/Hp4agqy)
+
+> Bless This Mess
+
+Cut the crap is an automatic video editing program for streamers.
+It can cut out uninteresting parts by detecting silences.
+This was inspired by [jumpcutter](https://github.com/carykh/jumpcutter),
+where this program can get better quality results
+by using an (optional) dedicated microphone track.
+This prevents cutting of [quieter consonants](https://youtu.be/DQ8orIurGxw?t=675)
+for example.
+Using ffmpeg more efficiently also produces faster results and
+is less error prone.
+
+Youtube has different requirements from streams then twitch does.
+We want to cut out boring parts.
+Jumpcut has solved that problem partly and this program
+builds on top of that idea.
+At the moment we use ffmpeg for silence detection, 
+then we do some maths to figure out which segments are sounded,
+which is combined into the output video.
+
+In the future we will add support for a music track
+which will not be chopped up.
+
+# Install
+
+## Nix/Nixos
++ Apply [overlay](https://nixos.wiki/wiki/Overlays) found [here](https://github.com/jappeace/cut-the-crap/tree/master/overlay).
++ Run `nix-env -iA cut-the-crap` or add to systemPackages.
++ simply run `cut-the-crap` to display usage instructions.
+
+## Ubuntu
+Download the .deb file from the release page.
+Install with:
+
+```shell
+apt install ffmpeg
+dpkg -i ./cut-the-crap_1.0.0_amd64.deb 
+```
+
+Execute with:
+```shell
+cut-the-crap
+```
+# Usage notes
+
+## Noise gate
+Make sure to record with a noise gate on your microphone.
+This will cut out background buzzing and allow you to use a more aggressive
+threshold on noise detection.
+
+## OBS tracks
+
+Setup OBS so that you record the microphone and the desktop audio
+on [separate tracks](https://obsproject.com/forum/resources/obs-studio-high-quality-recording-and-multiple-audio-tracks.221/).
+In my own setup I have track 1 for combining all audio, track 2 for just the microphone and track 3 for desktop audio.
+Then I can use:
+
+```shell
+    cut-the-crap --inputFile ./recordFromObs.mkv --outputFile ./someOut.mkv --voiceTrack 2 --musicTrack 3
+```
+
+So we throw away track 1, we use track 2 for silence detection, and track 3 get's mixed in after cutting is complete.
+If you don't want music being mixed back into the result,
+for example for further editing,
+you can also leave that argument out.
+I did this for example to mix back in the music of the original file later.
+
+# Use case
+I'm using this program to record my [stream](https://www.twitch.tv/jappiejappie)
+and upload it to my
+[Youtube channel](https://www.Youtube.com/channel/UCQxmXSQEYyCeBC6urMWRPVw).
+
+The concrete result is that your audience retention percentage will go up since the videos
+will be shorter, and more engaging.
+Sometimes on stream I have intro screens for example which completely get removed,
+and other times I'm simply thinking.
+Reducing videos by 30% is not uncommon in my case, which means by default
+30% more retention.
+You could even decide to edit after that which means you have to spend less time
+on cutting out silences and more time on making it look cool.
+
+Feel free to use or modify this program however you like.
+Pull requests are appreciated.
+
+# Features
+
+## Track based silence detection
+It is possible to specify one audio output as speech track.
+This will be used to for silence detection only.
+The result is very precise silence detection.
+
+## Separate music track
+Another track would be background and won't be modified at all.
+In the end it just get's cut of how far it is.
+
+This way we get good music and interesting stream.
+Another idea is to remix an entirely different source of music
+into the video, so we can play copyrighted music on stream
+and Youtube friendly music on Youtube.
+
+# Design
+This project is mostly a wrapper around ffmpeg.
+We use Haskell for shell programming.
+
+The [shelly](http://hackage.haskell.org/package/shelly) library was chosen in support of shell programming.
+Originally we used [turtle](http://hackage.haskell.org/package/turtle),
+but that library is much more complicated to use because it assumes you
+want to do stream programming,
+creating several unexpected bugs.
+So we replaced it with shelly and noticabally reduced code complexity.
+Now it's truly a 'dumb' wrapper around ffmpeg.
+
+## Why not to extend jumpcutter directly?
+I wish to build out this idea more to essentially
+make all streams look like human edited Youtube videos.
+Although I'm familiar with python,
+I (am or feel) more productive in haskell,
+therefore I chose to integrate with,
+and eventually replace jumpcutter.
+On stream we've determined most of the functionality is basically
+ffmpeg.
+Haskell also opens up the ability to do direct native ffmpeg
+integration,
+where we use ffmpeg as a library instead of calling it as a CLI
+program.
+
+One glaring limitation I've encountered with jumpcutter is that
+it can't handle larger video files (2 hour 30 minutes +).
+Scipy throws an exception complaining the wav is to big.
+Since this program doesn't use scipy it doesn't have that issue.
+
+It also appears like jumpcutter is unmaintained.
diff --git a/app/exe.hs b/app/exe.hs
new file mode 100644
--- /dev/null
+++ b/app/exe.hs
@@ -0,0 +1,6 @@
+module Main where
+
+import qualified Cut.Lib                       as Lib
+
+main :: IO ()
+main = Lib.entryPoint
diff --git a/cut-the-crap.cabal b/cut-the-crap.cabal
new file mode 100644
--- /dev/null
+++ b/cut-the-crap.cabal
@@ -0,0 +1,110 @@
+cabal-version: 1.12
+
+-- This file has been generated from package.yaml by hpack version 0.33.0.
+--
+-- see: https://github.com/sol/hpack
+--
+-- hash: 080b14b4ca581bc972d8d8ae8b9ef84e51078211786c6c8657e2c39a77628fb6
+
+name:           cut-the-crap
+version:        1.0.0
+synopsis:       Cuts out uninteresting parts of videos by detecting silences.
+description:    Cut the crap is an automatic video editing program for streamers. It can cut out uninteresting parts by detecting silences. This was inspired by [jumpcutter](https://github.com/carykh/jumpcutter), where this program can get better quality results by using an (optional) dedicated microphone track. This prevents cutting of [quieter consonants](https://youtu.be/DQ8orIurGxw?t=675) for example. Using ffmpeg more efficiently also produces faster results and is less error prone.
+category:       video
+author:         Jappie Klooster
+maintainer:     jappieklooster@hotmail.com
+copyright:      2019 Jappie Klooster
+license:        MIT
+license-file:   LICENSE
+build-type:     Simple
+extra-source-files:
+    Readme.md
+    LICENSE
+
+library
+  exposed-modules:
+      Cut.Analyze
+      Cut.CutVideo
+      Cut.Ffmpeg
+      Cut.Lib
+      Cut.Options
+      Cut.SplitVideo
+  other-modules:
+      Paths_cut_the_crap
+  hs-source-dirs:
+      src
+  default-extensions: EmptyCase FlexibleContexts FlexibleInstances InstanceSigs MultiParamTypeClasses LambdaCase MultiWayIf NamedFieldPuns TupleSections DeriveFoldable DeriveFunctor DeriveGeneric DeriveLift DeriveTraversable DerivingStrategies GeneralizedNewtypeDeriving StandaloneDeriving OverloadedStrings TypeApplications
+  ghc-options: -Wall -Wcompat -Wincomplete-uni-patterns -Wredundant-constraints -Wincomplete-record-updates -Widentities
+  build-depends:
+      base >=4.7 && <5
+    , exceptions
+    , generic-lens
+    , lens
+    , optparse-applicative
+    , optparse-generic
+    , regex-tdfa
+    , shelly
+    , system-filepath
+    , temporary
+    , text
+    , unliftio-core
+  default-language: Haskell2010
+
+executable cut-the-crap
+  main-is: exe.hs
+  other-modules:
+      Paths_cut_the_crap
+  hs-source-dirs:
+      app
+  default-extensions: EmptyCase FlexibleContexts FlexibleInstances InstanceSigs MultiParamTypeClasses LambdaCase MultiWayIf NamedFieldPuns TupleSections DeriveFoldable DeriveFunctor DeriveGeneric DeriveLift DeriveTraversable DerivingStrategies GeneralizedNewtypeDeriving StandaloneDeriving OverloadedStrings TypeApplications
+  ghc-options: -Wall -Wcompat -Wincomplete-uni-patterns -Wredundant-constraints -Wincomplete-record-updates -Widentities -threaded -rtsopts -with-rtsopts=-N
+  build-depends:
+      base >=4.7 && <5
+    , cut-the-crap
+    , exceptions
+    , generic-lens
+    , lens
+    , optparse-applicative
+    , optparse-generic
+    , regex-tdfa
+    , shelly
+    , system-filepath
+    , temporary
+    , text
+    , unliftio-core
+  default-language: Haskell2010
+
+test-suite unit
+  type: exitcode-stdio-1.0
+  main-is: Spec.hs
+  other-modules:
+      Test.MatchLineSpec
+      Test.TestSpec
+      Cut.Analyze
+      Cut.CutVideo
+      Cut.Ffmpeg
+      Cut.Lib
+      Cut.Options
+      Cut.SplitVideo
+      Paths_cut_the_crap
+  hs-source-dirs:
+      test
+      src
+  default-extensions: EmptyCase FlexibleContexts FlexibleInstances InstanceSigs MultiParamTypeClasses LambdaCase MultiWayIf NamedFieldPuns TupleSections DeriveFoldable DeriveFunctor DeriveGeneric DeriveLift DeriveTraversable DerivingStrategies GeneralizedNewtypeDeriving StandaloneDeriving OverloadedStrings TypeApplications
+  ghc-options: -Wall -Wcompat -Wincomplete-uni-patterns -Wredundant-constraints -Wincomplete-record-updates -Widentities
+  build-depends:
+      base >=4.7 && <5
+    , exceptions
+    , generic-lens
+    , hspec
+    , hspec-core
+    , lens
+    , optparse-applicative
+    , optparse-generic
+    , regex-tdfa
+    , shelly
+    , system-filepath
+    , temporary
+    , text
+    , unliftio-core
+  default-language: Haskell2010
diff --git a/src/Cut/Analyze.hs b/src/Cut/Analyze.hs
new file mode 100644
--- /dev/null
+++ b/src/Cut/Analyze.hs
@@ -0,0 +1,173 @@
+module Cut.Analyze
+  ( detect
+  , Interval(..)
+  , Sound
+  , Silent
+  , getStart
+  , getEnd
+  , getDuration
+  , takeOnlyLines
+  )
+where
+
+import           Control.Lens
+import           Control.Monad
+import           Control.Monad.Catch
+import           Control.Monad.IO.Unlift
+import           Cut.Ffmpeg
+import           Cut.Options
+import           Data.Foldable
+import           Data.Maybe
+import           Data.Text                      ( Text )
+import qualified Data.Text                     as Text
+import qualified Data.Text.IO                  as Text
+import           Data.Text.Lens
+import           Shelly                  hiding ( find )
+import           Text.Regex.TDFA         hiding ( empty )
+
+data Silent
+data Sound
+
+data Interval e = Interval
+  { interval_start       :: Double
+  , interval_end         :: Double
+  , interval_duration    :: Double
+  , interval_input_start :: Text
+  , interval_input_end   :: Text
+  } deriving Show
+
+detect :: (MonadMask m, MonadUnliftIO m) => Options -> m [Interval Sound]
+detect opts = do
+  lines'' <- shelly $ detectShell opts
+  let linesRes = do
+        line <- lines''
+        if takeOnlyLines line then pure $ Right line else pure $ Left line
+      lines' = linesRes ^.. traversed . _Right
+
+  liftIO $ putStrLn "-----------------------------------------"
+  liftIO $ putStrLn "-----------------actual lines-----------------"
+  liftIO $ putStrLn "-----------------------------------------"
+  liftIO $ Text.putStrLn $ Text.unlines lines'
+  liftIO $ putStrLn "-----------------------------------------"
+  liftIO $ putStrLn "-----------------filtered lines-----------------"
+  liftIO $ putStrLn "-----------------------------------------"
+  liftIO $ Text.putStrLn $ Text.unlines (linesRes ^.. traversed . _Left)
+
+  let linedUp        = zipped lines'
+      parsed         = parse <$> linedUp
+      fancyResult    = detectSound opts parsed
+      negativeResult = find ((0 >) . interval_duration) fancyResult
+
+  liftIO $ putStrLn "-----------------------------------------"
+  liftIO $ putStrLn "-----------------lined up-----------------"
+  liftIO $ putStrLn "-----------------------------------------"
+  liftIO $ traverse_ print linedUp
+
+  liftIO $ putStrLn "-----------------------------------------"
+  liftIO $ putStrLn "-----------------parsed-----------------"
+  liftIO $ putStrLn "-----------------------------------------"
+  liftIO $ traverse_ print parsed
+
+  if isJust negativeResult
+    then do
+      liftIO $ traverse_ print fancyResult
+      liftIO $ print negativeResult
+      error "Found negative durations"
+    else pure fancyResult
+
+takeOnlyLines :: Text -> Bool
+takeOnlyLines matchWith = matches
+ where
+  silenceRegex :: String
+  silenceRegex = ".*silencedetect.*"
+  matches :: Bool
+  matches = Text.unpack matchWith =~ silenceRegex
+
+zipped :: [Text] -> [(Text, Text)]
+zipped []                 = mempty
+zipped [_               ] = []
+zipped (one : two : rem') = (one, two) : zipped rem'
+
+detectSound :: Options -> [Interval Silent] -> [Interval Sound]
+detectSound opts =
+  --  -- TODO figure out why these durations get recorded as < 0
+  reverse . snd . foldl' (flip (compare' opts)) (Interval 0 0 0 "" "", [])
+
+compare'
+  :: Options
+  -> Interval Silent
+  -> (Interval Silent, [Interval Sound])
+  -> (Interval Silent, [Interval Sound])
+compare' opts current prev = (current, soundedInterval : snd prev)
+ where
+  soundedInterval = Interval
+    { interval_start       = interval_end $ fst prev
+    , interval_end         = interval_start current - margin
+    , interval_duration    = (soundEnd - soundStart) + margin
+    , interval_input_start =
+      interval_input_start (fst prev) <> "," <> interval_input_end (fst prev)
+    , interval_input_end   = interval_input_start current
+                             <> ","
+                             <> interval_input_end current
+    }
+  soundEnd   = interval_start current
+  soundStart = interval_end $ fst prev
+
+  margin     = opts ^. detect_margin
+
+
+detectShell :: Options -> Sh [Text]
+detectShell opt' = ffmpeg
+  [ "-i"
+  , opt' ^. in_file . packed
+  , "-map"
+  , "0:" <> opt' ^. voice_track . to show . packed
+  , "-filter:a"
+                 -- , "silencedetect=noise=-30dB:d=0.5"
+  , "silencedetect=noise="
+  <> (opt' ^. silent_treshold . to floatToText)
+  <> ":d="
+  <> (opt' ^. silent_duration . to floatToText)
+  , "-f"
+  , "null"
+  , "-"
+  ]
+
+parse :: (Text, Text) -> Interval Silent
+parse xx = Interval { interval_start       = getStart $ fst xx
+                    , interval_end         = getEnd $ snd xx
+                    , interval_duration    = getDuration $ snd xx
+                    , interval_input_start = fst xx
+                    , interval_input_end   = snd xx
+                    }
+
+getStart :: Text -> Double
+getStart line = read $ takeWhile (/= '\'') $ matches ^. _3
+ where
+  str = Text.unpack line
+  matches :: (String, String, String)
+  matches = str =~ startMatch
+
+startMatch :: String
+startMatch = "(.*)?: "
+
+pipe :: String
+pipe = " \\| "
+
+getDuration :: Text -> Double
+getDuration line = read $ takeWhile (/= '\'') $ match2 ^. _1
+ where
+  str = Text.unpack line
+  match1 :: (String, String, String)
+  match1 = str =~ startMatch
+  match2 :: (String, String, String)
+  match2 = (match1 ^. _3) =~ pipe
+
+getEnd :: Text -> Double
+getEnd line = read $ match2 ^. _3
+ where
+  str = Text.unpack line
+  match1 :: (String, String, String)
+  match1 = str =~ pipe
+  match2 :: (String, String, String)
+  match2 = (match1 ^. _1) =~ startMatch
diff --git a/src/Cut/CutVideo.hs b/src/Cut/CutVideo.hs
new file mode 100644
--- /dev/null
+++ b/src/Cut/CutVideo.hs
@@ -0,0 +1,86 @@
+{-# OPTIONS_GHC -Wno-type-defaults #-}
+
+module Cut.CutVideo
+  ( extract
+  , Interval(..)
+  , Silent
+  , Sound
+  , combine
+  , combineOutput
+  )
+where
+
+import           Control.Lens
+import           Control.Monad
+import           Control.Monad.Catch
+import           Cut.Analyze
+import           Cut.Ffmpeg
+import           Cut.Options
+import           Data.Foldable
+import           Data.Text                      ( Text )
+import qualified Data.Text                     as Text
+import           Data.Text.Lens
+import           Shelly                  hiding ( FilePath )
+
+specifyTracks :: Options -> [Text]
+specifyTracks options =
+  [ "-map"
+  , "0:0"
+  , "-map"  -- then copy only the voice track
+  , "0:" <> options ^. voice_track . to show . packed
+  ]
+
+toArgs :: Options -> FilePath -> Interval Sound -> (Interval Sound, [Text])
+toArgs options tmp inter =
+  ( inter
+  , -- keep ter interval for debugging
+    ["-y", "-ss", start, "-t", duration, "-i", options ^. in_file . packed]
+    <> specifyTracks options
+    <> [ Text.pack tmp
+         <> "/"
+         <> options
+         ^. out_file
+         .  packed
+         <> "-"
+         <> fname
+         <> ".mkv"
+       ]
+  )
+ where
+  start    = floatToText $ interval_start inter
+  duration = floatToText $ interval_duration inter
+  fname    = Text.pack $ show $ truncate $ interval_start inter * 100
+
+extract :: Options -> FilePath -> [Interval Sound] -> IO ()
+extract options tempDir intervals = do
+  traverse_
+      (\(inter, args) -> void $ catch (shelly $ ffmpeg args) $ \exec -> do
+        liftIO
+          (print ("expection during edit: ", exec :: SomeException, args, inter)
+          )
+        pure ["expection"]
+      )
+    $   toArgs options tempDir
+    <$> intervals
+  liftIO $ putStrLn "finish extracting"
+
+combineOutput :: FilePath
+combineOutput = "combined-output.mkv"
+
+combine :: FilePath -> Sh ()
+combine tempfiles = do
+  output' <- ffmpeg args
+  liftIO $ print ("output", output')
+ where -- https://stackoverflow.com/questions/7333232/how-to-concatenate-two-mp4-files-using-ffmpeg
+  args =
+    [ "-y"
+    , "-f"
+    , "concat"
+    , "-safe"
+    , "0"
+    , "-i"
+    , Text.pack (tempfiles <> "/input.txt")
+    , "-c"
+    , "copy"
+    , Text.pack $ tempfiles <> "/" <> combineOutput
+    ]
diff --git a/src/Cut/Ffmpeg.hs b/src/Cut/Ffmpeg.hs
new file mode 100644
--- /dev/null
+++ b/src/Cut/Ffmpeg.hs
@@ -0,0 +1,22 @@
+module Cut.Ffmpeg
+  ( ffmpeg
+  , floatToText
+  )
+where
+
+import           Data.Text                      ( Text )
+import qualified Data.Text                     as Text
+import           Numeric
+import           Shelly
+
+-- | Wrap ffmpeg for convenience and logging
+ffmpeg :: [Text] -> Sh [Text]
+ffmpeg args = do
+  liftIO $ putStr "Running: "
+  liftIO $ print $ "ffmpeg " <> Text.unwords args
+  run_ "ffmpeg" args
+  Text.lines <$> lastStderr
+
+-- | Format floats for cli
+floatToText :: Double -> Text
+floatToText = Text.pack . flip (showFFloat (Just 10)) ""
diff --git a/src/Cut/Lib.hs b/src/Cut/Lib.hs
new file mode 100644
--- /dev/null
+++ b/src/Cut/Lib.hs
@@ -0,0 +1,120 @@
+{-# LANGUAGE DataKinds     #-}
+{-# LANGUAGE TypeOperators #-}
+{-# OPTIONS_GHC -w #-}
+
+module Cut.Lib
+  ( entryPoint
+  , combineDir
+  )
+where
+
+import           Control.Lens
+import           Control.Monad
+import           Control.Monad.Catch
+import           Control.Monad.IO.Class
+import           Control.Monad.IO.Unlift
+import           Cut.Analyze
+import           Cut.CutVideo
+import           Cut.Ffmpeg
+import           Cut.Options
+import           Cut.SplitVideo
+import           Data.Bifunctor
+import           Data.Either
+import qualified Data.Text                     as Text
+import qualified Data.Text.IO                  as Text
+import           Data.Text.Lens
+import           Options.Applicative
+import           Options.Generic
+import           Shelly                  hiding ( FilePath )
+import           System.IO.Temp
+import           Text.Regex.TDFA         hiding ( empty
+                                                , extract
+                                                )
+
+entryPoint :: (MonadMask m, MonadUnliftIO m) => m ()
+entryPoint = catch main
+  $ \exec -> liftIO (print ("Uncaught exception: ", exec :: SomeException))
+
+main :: (MonadMask m, MonadUnliftIO m) => m ()
+main = do
+  options <- liftIO readSettings
+  liftIO $ putStr "started with options: "
+  liftIO $ print options
+
+  parsed <- detect options
+  case options ^. work_dir of
+    Nothing ->
+      withTempDirectory "/tmp" "streamedit" $ liftIO . runEdit options parsed
+    Just x -> liftIO $ runEdit options parsed x
+
+runEdit :: Options -> [Interval Sound] -> FilePath -> IO ()
+runEdit options parsed temp = do
+  extract options temp parsed
+  shelly $ combineDir options temp
+  getMusic options temp
+
+combineDir :: Options -> FilePath -> Sh ()
+combineDir options temp = do
+  res <- lsT $ fromText $ Text.pack temp
+  let paths :: Text
+      paths = Text.unlines $ flip (<>) "'" . ("file '" <>) <$> res
+  writefile (fromText $ Text.pack $ temp <> "/input.txt") paths
+  combine temp
+
+readSettings :: IO Options
+readSettings = customExecParser (prefs showHelpOnError) $ info
+  parseRecord
+  (fullDesc <> Options.Applicative.header "Cut the crap" <> progDesc
+    "Automated video extracting, can cut out silences"
+  )
+
+musicFile :: FilePath
+musicFile = "music.mp3"
+
+withMusicFile :: FilePath
+withMusicFile = "combined.mkv"
+
+getMusic :: Options -> FilePath -> IO ()
+getMusic opt' tempfiles = do
+  res <- case opt' ^. music_track of
+    Nothing -> pure $ Text.pack combinedFile
+    Just x  -> do
+      shelly $ ffmpeg $ args x
+      shelly $ combineMusic tempfiles
+      pure $ Text.pack (tempfiles <> "/" <> withMusicFile)
+  putStrLn "done get music"
+  shelly $ cp (fromText res) (opt' ^. out_file . packed . to fromText)
+  pure ()
+ where -- https://stackoverflow.com/questions/7333232/how-to-concatenate-two-mp4-files-using-ffmpeg
+  combinedFile = tempfiles <> "/" <> combineOutput
+  args x' =
+    [ "-i"
+    , opt' ^. in_file . packed
+    , "-map"
+    , "0:" <> Text.pack (show x')
+    , Text.pack (tempfiles <> "/" <> musicFile)
+    ]
+
+combineMusic :: FilePath -> Sh ()
+combineMusic tempfiles = void $ ffmpeg args
+ where -- https://stackoverflow.com/questions/7333232/how-to-concatenate-two-mp4-files-using-ffmpeg
+  args =
+    [ "-i"
+    , Text.pack $ tempfiles <> "/" <> combineOutput
+    , "-i"
+    , Text.pack $ tempfiles <> "/" <> musicFile
+    , "-filter_complex"
+    , "[0:a][1:a]amerge=inputs=2[a]"
+    , "-map"
+    , "0:v"
+    , "-map"
+    , "[a]"
+    , "-c:v"
+    , "copy"
+    , "-c:a"
+    , "mp3"
+    , "-ac"
+    , "2"
+    , "-shortest"
+    , Text.pack (tempfiles <> "/" <> withMusicFile)
+    ]
diff --git a/src/Cut/Options.hs b/src/Cut/Options.hs
new file mode 100644
--- /dev/null
+++ b/src/Cut/Options.hs
@@ -0,0 +1,93 @@
+{-# LANGUAGE DataKinds        #-}
+{-# LANGUAGE TypeApplications #-}
+{-# LANGUAGE TypeOperators    #-}
+
+module Cut.Options
+  ( Options
+  , in_file
+  , out_file
+  , seg_size
+  , silent_treshold
+  , detect_margin
+  , voice_track
+  , music_track
+  , silent_duration
+  , work_dir
+  , simpleOptions
+  )
+where
+
+import           Control.Lens
+import           Data.Generics.Product.Fields
+import           Options.Generic
+
+simpleOptions :: Options
+simpleOptions = Options { inFile         = halp # "in.mkv"
+                        , outFile        = halp # "out.mkv"
+                        , segmentSize    = halp . _Just # def_seg_size
+                        , silentTreshold = halp . _Just # def_silent
+                        , detectMargin   = halp . _Just # def_margin
+                        , voiceTrack     = halp . _Just # 2
+                        , musicTrack     = halp # Nothing
+                        , silentDuration = halp . _Just # def_duration
+                        , workDir        = halp # Nothing
+                        }
+
+data Options = Options
+  { inFile :: FilePath <?> "The input video"
+  , outFile :: FilePath <?> "The output name without format"
+  , segmentSize :: Maybe Int <?> "The size of video segments in minutes"
+  , silentTreshold :: Maybe Double <?> "The treshold for determining intersting sections, closer to zero is detects more audio (n: https://ffmpeg.org/ffmpeg-filters.html#silencedetect)"
+  , silentDuration :: Maybe Double <?> "The duration before soemthing can be considered a silence (d: https://ffmpeg.org/ffmpeg-filters.html#silencedetect)"
+  , detectMargin :: Maybe Double <?> "Margin seconds around detection"
+  , voiceTrack :: Maybe Int <?> "The track to detect audio upon"
+  , musicTrack :: Maybe Int <?> "The track to detect audio upon"
+  , workDir :: Maybe FilePath <?> "If specified will use this as temporty directory to store intermeidate files in, good for debugging. Needs to be absolute"
+  } deriving (Show, Generic)
+
+halp :: Iso' (a <?> b) a
+halp = iso unHelpful Helpful
+
+in_file :: Lens' Options FilePath
+in_file = field @"inFile" . halp
+
+out_file :: Lens' Options FilePath
+out_file = field @"outFile" . halp
+
+def_seg_size :: Int
+def_seg_size = 20
+
+def_margin :: Double
+def_margin = 0.05
+
+def_silent :: Double
+def_silent = 0.0001
+
+def_duration :: Double
+def_duration = 0.25
+
+def_voice :: Int
+def_voice = 1
+
+seg_size :: Lens' Options Int
+seg_size = field @"segmentSize" . halp . non def_seg_size
+
+detect_margin :: Lens' Options Double
+detect_margin = field @"detectMargin" . halp . non def_margin
+
+silent_treshold :: Lens' Options Double
+silent_treshold = field @"silentTreshold" . halp . non def_silent
+
+silent_duration :: Lens' Options Double
+silent_duration = field @"silentDuration" . halp . non def_duration
+
+voice_track :: Lens' Options Int
+voice_track = field @"voiceTrack" . halp . non def_voice
+
+music_track :: Lens' Options (Maybe Int)
+music_track = field @"musicTrack" . halp
+
+work_dir :: Lens' Options (Maybe FilePath)
+work_dir = field @"workDir" . halp
+
+instance ParseRecord Options
diff --git a/src/Cut/SplitVideo.hs b/src/Cut/SplitVideo.hs
new file mode 100644
--- /dev/null
+++ b/src/Cut/SplitVideo.hs
@@ -0,0 +1,32 @@
+module Cut.SplitVideo
+  ( split
+  )
+where
+
+import           Control.Lens
+import           Control.Monad
+import           Cut.Ffmpeg
+import           Cut.Options
+import           Data.Text.Lens
+import           Shelly                  hiding ( FilePath )
+
+-- | Splits a video into segments
+split :: FilePath -> Options -> Sh ()
+split tmp opt' = do
+  cp (fromText (tmp ^. packed))
+     (fromText (opt' ^. out_file . packed <> "-full.mp4"))
+  void $ ffmpeg
+    [ "-i"
+    , tmp ^. packed
+    , "-c"
+    , "copy"
+    , "-map"
+    , "0"
+    , "-segment_time"
+    , "00:" <> (opt' ^. seg_size . to show . packed) <> ":00"
+    , "-f"
+    , "segment"
+    , "-reset_timestamps"
+    , "1"
+    , opt' ^. out_file . packed <> "%03d.mp4"
+    ]
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,1 @@
+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
diff --git a/test/Test/MatchLineSpec.hs b/test/Test/MatchLineSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Test/MatchLineSpec.hs
@@ -0,0 +1,36 @@
+module Test.MatchLineSpec
+  ( spec
+  )
+where
+
+import           Cut.Analyze
+import           Test.Hspec
+
+spec :: Spec
+spec = describe "Match a line" $ do
+  it "should satisfy equality"
+    $          getStart "[silencedetect @ 0xad44e0] silence_start: -0.012381"
+    `shouldBe` (-0.012381)
+
+  it "Should deal with rng output"
+    $ getStart "[silencedetect @ 0x10b0280] silence_start: 430.41'peed= 858x"
+    `shouldBe` 430.41
+  it "Should match the end line as well"
+    $          getEnd
+                 "[silencedetect @ 0x23a94e0] silence_end: 148.515 | silence_duration: 0.825079"
+    `shouldBe` 148.515
+  it "Should match the duration"
+    $          getDuration
+                 "[silencedetect @ 0x23a94e0] silence_end: 148.515 | silence_duration: 0.825079"
+    `shouldBe` 0.825079
+
+  it "Should deal with quotes on end as well"
+    $          getDuration
+                 "[silencedetect @ 0x1900280] silence_end: 1249.91 | silence_duration: 15.7841'"
+    `shouldBe` 15.7841
+
+  it "Shouldn't filter this line"
+    $          takeOnlyLines
+                 "[silencedetect @ 0xcf3280] silence_end: 415.498 | silence_duration: 69.6312'"
+    `shouldBe` True
+
diff --git a/test/Test/TestSpec.hs b/test/Test/TestSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/Test/TestSpec.hs
@@ -0,0 +1,16 @@
+module Test.TestSpec
+  ( spec
+  )
+where
+
+import           Test.Hspec
+
+one :: Int
+one = 1
+
+spec :: Spec
+spec =
+  describe "The sanity of our test setup"
+    $          it "should satisfy equality"
+    $          one
+    `shouldBe` 1
