cut-the-crap-2.3.1: src/Cut/Options.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE DeriveAnyClass #-}
-- | This module defines which options exists, and provides
-- functions for parsing cli options.
module Cut.Options
( parseProgram
, specifyTracks
, getOutFileName
-- * Program options
, ProgramOptions(..)
, gnerate_sub_prism
, listen_cut_prism
-- * fileio, deal with input output files
, FileIO
, lc_fileio
, in_file
, out_file
, work_dir
-- * listen cut, options for video editing by audio
, ListenCutOptionsT
, ListenCutOptions
, silent_treshold
, detect_margin
, voice_track
, music_track
, silent_duration
, cut_noise
, voice_track_map
-- * input source prisms
, InputSource
, input_src_remote
, input_src_local_file
-- * defaults
, simpleOptions
)
where
import Control.Lens hiding (argument)
import Data.Generics.Product.Fields
import Data.Generics.Sum
import qualified Data.Text as Text
import Data.Text.Lens
import GHC.Generics (Generic)
import Options.Applicative
import Network.URI
simpleFileIO :: (FileIO InputSource)
simpleFileIO = FileIO { fi_inFile = LocalFile "in.mkv" , fi_outFile = "out.mkv"
, fi_workDir = Nothing
}
-- type Lens s t a b = forall f. Functor f => (a -> f b) -> s -> f t
in_file :: Lens (FileIO a) (FileIO b) a b
in_file = field @"fi_inFile"
out_file :: Lens' (FileIO a) FilePath
out_file = field @"fi_outFile"
work_dir :: Lens' (FileIO a) (Maybe FilePath)
work_dir = field @"fi_workDir"
simpleOptions :: ListenCutOptionsT InputSource
simpleOptions = ListenCutOptions
{ lc_fileIO = simpleFileIO
, lc_silentTreshold = _Just # def_silent
, lc_detectMargin = _Just # def_detect_margin
, lc_voiceTrack = _Just # def_voice_track
, lc_musicTrack = Nothing
, lc_silentDuration = _Just # def_duration
, lc_cutNoise = def_cut_noise
}
getOutFileName :: ListenCutOptionsT a -> FilePath
getOutFileName = reverse . takeWhile ('/' /=) . reverse . view (lc_fileio . out_file)
-- | Deals with having an input file and a target output file
data FileIO a = FileIO
{ fi_inFile :: a
, fi_outFile :: FilePath
, fi_workDir :: Maybe FilePath -- ^ for consistency (or debugging) we may want to specify this.
}
deriving (Show, Generic)
type ListenCutOptions = ListenCutOptionsT FilePath
-- | Cut out by listening to sound options
data ListenCutOptionsT a = ListenCutOptions
{ lc_fileIO :: FileIO a
, lc_silentTreshold :: Maybe Double
, lc_silentDuration :: Maybe Double
, lc_detectMargin :: Maybe Double
, lc_voiceTrack :: Maybe Int
, lc_musicTrack :: Maybe Int
, lc_cutNoise :: Bool
}
deriving (Show, Generic)
data ProgramOptions a = ListenCut (ListenCutOptionsT a)
| GenerateSubtitles (FileIO a)
deriving (Show, Generic)
listen_cut_prism :: Prism' (ProgramOptions a) (ListenCutOptionsT a)
listen_cut_prism = _Ctor @"ListenCut"
gnerate_sub_prism :: Prism' (ProgramOptions a) (FileIO a)
gnerate_sub_prism = _Ctor @"GenerateSubtitles"
def_voice_track :: Int
def_voice_track = 1
def_detect_margin :: Double
def_detect_margin = def_duration / 2
def_cut_noise :: Bool
def_cut_noise = False
def_silent :: Double
def_silent = 0.075
def_duration :: Double
def_duration = 0.5
def_voice :: Int
def_voice = 1
lc_fileio :: Lens (ListenCutOptionsT a) (ListenCutOptionsT b) (FileIO a) (FileIO b)
lc_fileio = field @"lc_fileIO"
detect_margin :: Lens' (ListenCutOptionsT a) Double
detect_margin = field @"lc_detectMargin" . non def_detect_margin
silent_treshold :: Lens' (ListenCutOptionsT a) Double
silent_treshold = field @"lc_silentTreshold" . non def_silent
silent_duration :: Lens' (ListenCutOptionsT a) Double
silent_duration = field @"lc_silentDuration" . non def_duration
voice_track :: Lens' (ListenCutOptionsT a) Int
voice_track = field @"lc_voiceTrack" . non def_voice
music_track :: Lens' (ListenCutOptionsT a) (Maybe Int)
music_track = field @"lc_musicTrack"
cut_noise :: Lens' (ListenCutOptionsT a) Bool
cut_noise = field @"lc_cutNoise"
voice_track_map :: (ListenCutOptionsT a) -> Text.Text
voice_track_map = mappend "0:" . view (voice_track . to show . packed)
specifyTracks :: (ListenCutOptionsT a) -> [Text.Text]
specifyTracks options =
[ "-map"
, "0:0"
, "-map" -- then copy only the voice track
, voice_track_map options
]
data InputSource = LocalFile FilePath
| Remote URI
deriving (Show, Generic)
input_src_local_file :: Prism' InputSource FilePath
input_src_local_file = _Ctor @"LocalFile"
input_src_remote :: Prism' InputSource URI
input_src_remote = _Ctor @"Remote"
readFileSource :: ReadM InputSource
readFileSource = eitherReader $
\x ->
maybe (Left "unlikely error") Right $
(Remote <$> parseURI x) <|> Just (LocalFile x)
parseFile :: Parser (FileIO InputSource)
parseFile = FileIO
<$> argument readFileSource (metavar "INPUT" <> help "The input video, either a file or a uri. This program has tested best with the mkv container type, you can use ffmpeg to convert containers, for example \"ffmpeg -i input.mp4 output.mkv\", see https://opensource.com/article/17/6/ffmpeg-convert-media-file-formats")
<*> argument str (metavar "OUTPUT_FILE" <> help "The output name without format" <> value "out.mkv" <> showDefault)
<*> optional
(option
str
( long "workDir"
<> help
"If specified will use this as temporary directory to store intermeidate files in, good for debugging. Needs to be absolute"
)
)
parseProgram :: Parser (ProgramOptions InputSource)
parseProgram =
subparser $
command "listen" (info (ListenCut <$> parseSound) $ progDesc "Cut out by listening to sound options. We listen for silences and cut out the parts that are silenced.")
<>
command "subtitles" (info (GenerateSubtitles <$> parseFile) $ progDesc "Generate subtiles for a video. This is an intermediate (but usefull) feature developed for recognizing human speech vs background noise.")
parseSound :: Parser (ListenCutOptionsT InputSource)
parseSound = ListenCutOptions
<$> parseFile
<*> optional
(option
auto
( long "silentTreshold"
<> help
"The treshold for determining intersting sections, closer to zero is detects more audio (n: https://ffmpeg.org/ffmpeg-filters.html#silencedetect), you may wish to tweak this variable a bit depending on your mic."
<> value def_silent <> showDefault
)
)
<*> optional
(option
auto
( long "silentDuration"
<> help
"The duration before something can be considered a silence (https://ffmpeg.org/ffmpeg-filters.html#silencedetect)"
<> value def_duration <> showDefault
)
)
<*> optional
(option
auto
(long "detectMargin" <> help "Margin seconds around detection"
<> value def_detect_margin <> showDefault
)
)
<*> optional
(option
auto
(long "voiceTrack" <> help "The track to detect the silences upon"
<> value def_voice_track <> showDefault)
)
<*> optional
(option auto (long "musicTrack" <> help "The track to integrate"))
<*> switch
(long "cutNoise" <> help "Do the opposite: Cut noise instead of silence")