-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathInit.hs
205 lines (171 loc) · 7.07 KB
/
Init.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
module Init(
Conf(..), MpvArgs(..), parseArgs, createAndSortLoopArrays) where
import System.Environment
import MpvFFI
import Control.Monad.State
import Data.List.Split (splitOn)
import Data.List (isPrefixOf,elemIndex)
import Text.Read (readMaybe)
import Control.Exception.Base (Exception,throwIO)
import SrtFile (loadSrtFile, Srt)
import qualified SrtFile as SF
import Loops
import Control.Monad.Trans.Either
import Foreign (Ptr)
import EventLoop (EventLoop,createEventLoop,Track(..))
type MpvFlag = String
type MpvOption = (String,String)
data MpvArgs = MpvArgs { flags :: [MpvFlag], opts :: [MpvOption], singleArgs :: [String] } deriving (Show)
data Conf = Conf { subfiles :: [String], tracks :: [Track], mpvArgs :: MpvArgs } deriving (Show)
--[subtitleid/none[:speed[:lead_secs[:tail_secs]]]](repeatable) -- (mpv args)
readTrack :: String -> Maybe Track
readTrack str =
do
(track, _) <- runStateT rt2 (splitOn ":" str) -- returns Maybe (Track, [String])
return track
where
rt2 :: StateT [String] Maybe Track
rt2 = do
sids <- popRead readSids Nothing
speed <- popRead readOrNothing $ Just 1.0
leadSecs <- popRead readOrNothing $ Just 0.0
tailSecs <- popRead readOrNothing $ Just 0.0
return $ Track sids speed leadSecs tailSecs
joinMaybe :: [Maybe a] -> Maybe [a]
joinMaybe [] = Just []
joinMaybe (Nothing : _) = Nothing
joinMaybe ((Just x) : xs) = joinMaybe xs >>= (\xs -> return (x : xs))
readSids "none" = Just []
readSids s = joinMaybe (fmap readMaybe (splitOn "," s))
popRead :: Read a => (String -> Maybe a) -> (Maybe a) -> StateT [String] Maybe a
popRead reader def =
StateT (doit reader def)
where
doit :: Read a => (String -> Maybe a) -> Maybe a -> ([String] -> Maybe (a, [String]))
doit reader def = ( \x -> case x of
[] -> (def >>= (\v -> Just (v, [])))
(s : ss) -> (reader s)
>>= (\v -> Just (v, ss))
)
readOrNothing :: Read a => String -> Maybe a
readOrNothing x =
case (reads x) of
[(v, [])] -> Just v
_ -> Nothing
parseTracks :: [String] -> Either String [Track]
parseTracks x = doit x
where
doit [] = Right []
doit (x : xs) =
do
t <- (case (readTrack x) of
Nothing -> Left $ "Error, can't parse: "++x
Just t -> Right t)
ts <- parseTracks xs
Right (t:ts)
--splits a list by a marker into sublists
splitList :: (a -> Bool) -> [a] -> [[a]]
splitList f [] = [[]]
splitList f (x : xs) | (f x) = [] : splitList f xs
| True =
let (s : ss) = (splitList f xs)
in ((x:s) : ss)
isOption x = (isPrefixOf "-" x)
--parses flags and options meant for mpv, returns State with
-- remaining args
--CAVEAT: options must use --<opt>=<value> format
parseMpvOptions :: StateT [String] (Either String) ([MpvFlag],[MpvOption])
parseMpvOptions =
do
args <- get
(_, (flags, options, rem_args)) <- (runStateT doit ([],[],args))
put rem_args
return (reverse flags,reverse options)
where
removeDash ('-' : '-' : xs) = xs
removeDash ('-' : xs) = xs
parseMpvOption x =
let opt = removeDash x
in
do
eqlPos <- elemIndex '=' opt
return (take eqlPos opt, drop (eqlPos + 1) opt)
doit =
do
(flags,options,args) <- (get)
case args of
[] -> return () --no args left
(x : xs) | (not (isOption x)) -> return () --end of option args
(x : xs) -> case parseMpvOption x of
Nothing -> put ( (removeDash x): flags, options, xs) >> doit
Just o -> put (flags, o : options, xs) >> doit
parseMpvArgs :: [String] -> Either String MpvArgs
parseMpvArgs args =
do
(mpvargs, _) <- (runStateT doit args)
return mpvargs
where
doit :: StateT [String] (Either String) MpvArgs
doit =
do
(flags,opts) <- parseMpvOptions
singleArgs <- get
return $ MpvArgs flags opts singleArgs
parseArgs :: [String] -> Either String Conf
parseArgs args = do
splitArgs <- return (splitList (== "--") args)
if (length splitArgs) /= 3 then (Left "Args must be in format: <srt subtitle files> -- <tracks> -- <mpv args>") else Right ()
(subfiles : tracksStr : mpvArgsStr : []) <- return splitArgs
tracks <- parseTracks tracksStr
mpvArgs <- parseMpvArgs mpvArgsStr
return $ Conf subfiles tracks mpvArgs
--creates loop arrays, but does not add gap (which must be done after loops are sorted
--in playback order)
createLoopArraysForTrack :: [[Srt]] -> Track -> [EventLoop]
createLoopArraysForTrack srtss t =
let timingSid =
case (sids t) of
[] -> 1
x : xs -> x
srts = srtss !! (timingSid-1)
in
fmap (\srt -> createEventLoop t (SF.startTime srt) (SF.endTime srt)) srts
createLoopArrays :: [[Srt]] -> [Track] -> [[EventLoop]]
createLoopArrays srtss tracks = fmap (createLoopArraysForTrack srtss) tracks
--the guaranteed gap betwen subtitles (so we don't flicker other subtitles on the screen)
--during the inbetween times
srtGap = 0.1
addGapsToLoops :: [EventLoop] -> [EventLoop]
addGapsToLoops els =
let tels = (fmap (addTailGap els) [0..(length els)-1])
in
fmap (addHeadGap tels) [0..(length tels)-1]
where
addTailGap :: [EventLoop] -> Int -> EventLoop
addTailGap els index =
let el = els !! index
nel = earliestLaterTime (endTime el) els index
wantedEndTime = (endTime el) + (tailSecs (val el))
in el { endTime = min wantedEndTime (nel - srtGap) }
addHeadGap :: [EventLoop] -> Int -> EventLoop
addHeadGap els index =
let el = els !! index
pel = latestEarlierTime (startTime el) els index
wantedStartTime = (startTime el) - (leadSecs (val el))
in el { startTime = max wantedStartTime (pel + srtGap) }
--TODO PERF goes through entire list for each element
earliestLaterTime :: Double -> [EventLoop] -> Int -> Double
earliestLaterTime endTime els index =
foldl (\b t -> if t >= endTime then (min t b) else b)
9999999.0
(fmap startTime (drop index els))
latestEarlierTime :: Double -> [EventLoop] -> Int -> Double
latestEarlierTime startTime els index =
foldl (\b t -> if t <= startTime then (max t b) else b)
0
(fmap endTime (take index els))
createAndSortLoopArrays :: [[Srt]] -> [Track] -> [EventLoop]
createAndSortLoopArrays srts tracks = (addGapsToLoops
(sortLoopsForPlay
(createLoopArrays srts tracks)))
--TODO 1.5 does not work with baked in subs