cut-the-crap 1.2.0 → 1.3.0
raw patch · 7 files changed
+159/−184 lines, 7 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
- Cut.Lib: combineDir :: Options -> FilePath -> Sh ()
- Cut.Lib: entryPoint :: (MonadMask m, MonadUnliftIO m) => m ()
- Cut.SplitVideo: split :: FilePath -> Options -> Sh ()
+ Cut.Crap: combineDir :: Options -> FilePath -> Sh ()
+ Cut.Crap: entryPoint :: (MonadMask m, MonadUnliftIO m) => m ()
+ Cut.Crap: runCrap :: (MonadMask m, MonadUnliftIO m) => Options -> m ()
+ Cut.Crap: runEdit :: Options -> [Interval Sound] -> FilePath -> IO ()
+ Cut.CutVideo: extractDir :: FilePath
+ Cut.Options: getOutFileName :: Options -> FilePath
Files
- app/exe.hs +1/−1
- cut-the-crap.cabal +5/−7
- src/Cut/Crap.hs +126/−0
- src/Cut/CutVideo.hs +14/−9
- src/Cut/Lib.hs +0/−122
- src/Cut/Options.hs +13/−13
- src/Cut/SplitVideo.hs +0/−32
app/exe.hs view
@@ -1,6 +1,6 @@ module Main where -import qualified Cut.Lib as Lib+import qualified Cut.Crap as Lib main :: IO () main = Lib.entryPoint
cut-the-crap.cabal view
@@ -1,13 +1,13 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.33.0.+-- This file has been generated from package.yaml by hpack version 0.34.2. -- -- see: https://github.com/sol/hpack ----- hash: 1250d94c516268de8b0a4c1ee8c7600bca2b27ba1ff011c15e836402301f160f+-- hash: 08b09429fe51925e641cb423204fc08004d168fc0e79a7dbc750ea69c9e350d4 name: cut-the-crap-version: 1.2.0+version: 1.3.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@@ -24,11 +24,10 @@ library exposed-modules: Cut.Analyze+ Cut.Crap Cut.CutVideo Cut.Ffmpeg- Cut.Lib Cut.Options- Cut.SplitVideo other-modules: Paths_cut_the_crap hs-source-dirs:@@ -79,11 +78,10 @@ Test.MatchLineSpec Test.TestSpec Cut.Analyze+ Cut.Crap Cut.CutVideo Cut.Ffmpeg- Cut.Lib Cut.Options- Cut.SplitVideo Paths_cut_the_crap hs-source-dirs: test
+ src/Cut/Crap.hs view
@@ -0,0 +1,126 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TypeOperators #-}+{-# OPTIONS_GHC -w #-}++module Cut.Crap+ ( entryPoint+ , combineDir+ , runCrap+ , runEdit+ )+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 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 Shelly hiding (FilePath)+import System.IO.Temp+import Text.Regex.TDFA hiding (empty, extract)++entryPoint :: (MonadMask m, MonadUnliftIO m) => m ()+entryPoint = runCrap =<< liftIO readSettings++runCrap :: (MonadMask m, MonadUnliftIO m) => Options -> m ()+runCrap options = do+ liftIO $ putStr "started with options: "+ liftIO $ print options++ -- first figure out what's up in the vid+ parsed <- detect options++ -- then do stuff to it+ case parsed of+ [] ->+ liftIO+ $ putStr+ "\n\nNo silence in input video detected. There is nothing to be cut so exiting.\n\n"+ _ -> 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 tempDir = do+ extract options tempDir parsed+ shelly $ combineDir options tempDir+ getMusic options tempDir++combineDir :: Options -> FilePath -> Sh ()+combineDir options tempDir = do+ res <- lsT $ fromText $ Text.pack (tempDir <> extractDir)+ let paths = Text.unlines $ flip (<>) "'" . ("file '" <>) <$> res+ writefile (fromText $ Text.pack $ tempDir <> "/input.txt") paths+ combine tempDir++readSettings :: IO Options+readSettings = customExecParser (prefs showHelpOnError) $ info+ (parseRecord <**> helper)+ (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' tempDir = do+ res <- case opt' ^. music_track of+ Nothing -> pure $ Text.pack combinedFile+ Just x -> do+ shelly $ extractMusicTrack x (opt' ^. in_file) tempDir+ shelly $ mergeMusicAndVideo tempDir+ pure $ Text.pack (tempDir <> "/" <> withMusicFile)+ putStrLn "done get music"+ shelly $ cp (fromText res) (opt' ^. out_file . packed . to fromText)+ pure ()+ where combinedFile = tempDir <> "/" <> combineOutput++extractMusicTrack :: Int -> FilePath -> FilePath -> Sh ()+extractMusicTrack musicTrack inputFile tempDir = void $ ffmpeg args+ where -- https://stackoverflow.com/questions/7333232/how-to-concatenate-two-mp4-files-using-ffmpeg+ args =+ [ "-i"+ , Text.pack inputFile+ , "-map"+ , "0:" <> Text.pack (show musicTrack)+ , Text.pack (tempDir <> "/" <> musicFile)+ ]++mergeMusicAndVideo :: FilePath -> Sh ()+mergeMusicAndVideo tempDir = void $ ffmpeg args+ where -- https://stackoverflow.com/questions/7333232/how-to-concatenate-two-mp4-files-using-ffmpeg+ args =+ [ "-i"+ , Text.pack $ tempDir <> "/" <> combineOutput+ , "-i"+ , Text.pack $ tempDir <> "/" <> 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 (tempDir <> "/" <> withMusicFile)+ ]
src/Cut/CutVideo.hs view
@@ -7,6 +7,7 @@ , Sound , combine , combineOutput+ , extractDir ) where @@ -17,11 +18,11 @@ import Cut.Ffmpeg import Cut.Options import Data.Foldable-import Data.Text ( Text )-import qualified Data.Text as Text+import Data.Text (Text)+import qualified Data.Text as Text import Data.Text.Lens-import Shelly hiding ( FilePath )-import Text.Printf ( printf )+import Shelly hiding (FilePath)+import Text.Printf (printf) specifyTracks :: Options -> [Text] specifyTracks options =@@ -39,9 +40,7 @@ <> specifyTracks options <> [ Text.pack tmp <> "/"- <> options- ^. out_file- . packed+ <> Text.pack (getOutFileName options) <> "-" <> fname <> ".mkv"@@ -52,8 +51,12 @@ duration = floatToText $ interval_duration inter fname = Text.pack $ printf "%010d" (truncate $ interval_start inter * 100 :: Integer) +extractDir :: FilePath+extractDir = "/extract"+ extract :: Options -> FilePath -> [Interval Sound] -> IO () extract options tempDir intervals = do+ shelly $ mkdir_p exdir traverse_ (\(inter, args) -> void $ catch (shelly $ ffmpeg args) $ \exec -> do liftIO@@ -61,9 +64,11 @@ ) pure ["expection"] )- $ toArgs options tempDir- <$> intervals+ $ toArgs options exdir <$> intervals liftIO $ putStrLn "finish extracting"++ where+ exdir = tempDir <> extractDir combineOutput :: FilePath combineOutput = "combined-output.mkv"
− src/Cut/Lib.hs
@@ -1,122 +0,0 @@-{-# 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 Shelly hiding ( FilePath )-import System.IO.Temp-import Text.Regex.TDFA hiding ( empty- , extract- )--entryPoint :: (MonadMask m, MonadUnliftIO m) => m ()-entryPoint = do- options <- liftIO readSettings- liftIO $ putStr "started with options: "- liftIO $ print options-- parsed <- detect options- case parsed of- [] ->- liftIO- $ putStr- "\n\nNo silence in input video detected. There is nothing to be cut so exiting.\n\n"- _ -> 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 tempDir = do- extract options tempDir parsed- shelly $ combineDir options tempDir- getMusic options tempDir--combineDir :: Options -> FilePath -> Sh ()-combineDir options tempDir = do- res <- lsT $ fromText $ Text.pack tempDir- let paths = Text.unlines $ flip (<>) "'" . ("file '" <>) <$> res- writefile (fromText $ Text.pack $ tempDir <> "/input.txt") paths- combine tempDir--readSettings :: IO Options-readSettings = customExecParser (prefs showHelpOnError) $ info- (parseRecord <**> helper)- (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' tempDir = do- res <- case opt' ^. music_track of- Nothing -> pure $ Text.pack combinedFile- Just x -> do- shelly $ extractMusicTrack x (opt' ^. in_file) tempDir- shelly $ mergeMusicAndVideo tempDir- pure $ Text.pack (tempDir <> "/" <> withMusicFile)- putStrLn "done get music"- shelly $ cp (fromText res) (opt' ^. out_file . packed . to fromText)- pure ()- where combinedFile = tempDir <> "/" <> combineOutput--extractMusicTrack :: Int -> FilePath -> FilePath -> Sh ()-extractMusicTrack musicTrack inputFile tempDir = void $ ffmpeg args- where -- https://stackoverflow.com/questions/7333232/how-to-concatenate-two-mp4-files-using-ffmpeg- args =- [ "-i"- , Text.pack inputFile- , "-map"- , "0:" <> Text.pack (show musicTrack)- , Text.pack (tempDir <> "/" <> musicFile)- ]--mergeMusicAndVideo :: FilePath -> Sh ()-mergeMusicAndVideo tempDir = void $ ffmpeg args- where -- https://stackoverflow.com/questions/7333232/how-to-concatenate-two-mp4-files-using-ffmpeg- args =- [ "-i"- , Text.pack $ tempDir <> "/" <> combineOutput- , "-i"- , Text.pack $ tempDir <> "/" <> 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 (tempDir <> "/" <> withMusicFile)- ]
src/Cut/Options.hs view
@@ -16,14 +16,11 @@ , cut_noise , work_dir , simpleOptions+ , getOutFileName ) where -import Control.Lens ( (#)- , Lens'- , _Just- , non- )+import Control.Lens (Lens', non, view, ( # ), _Just) import Data.Generics.Product.Fields import GHC.Generics import Options.Applicative@@ -41,17 +38,20 @@ , workDir = Nothing } +getOutFileName :: Options -> FilePath+getOutFileName = reverse . takeWhile ((/=) '/') . reverse . view out_file+ data Options = Options- { inFile :: FilePath- , outFile :: FilePath- , segmentSize :: Maybe Int+ { inFile :: FilePath+ , outFile :: FilePath+ , segmentSize :: Maybe Int , silentTreshold :: Maybe Double , silentDuration :: Maybe Double- , detectMargin :: Maybe Double- , voiceTrack :: Maybe Int- , musicTrack :: Maybe Int- , cutNoise :: Bool- , workDir :: Maybe FilePath+ , detectMargin :: Maybe Double+ , voiceTrack :: Maybe Int+ , musicTrack :: Maybe Int+ , cutNoise :: Bool+ , workDir :: Maybe FilePath } deriving (Show, Generic) in_file :: Lens' Options FilePath
− src/Cut/SplitVideo.hs
@@ -1,32 +0,0 @@-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"- ]