Last active
February 25, 2018 06:15
-
-
Save anacrolix/a2ba0efa98b68201f84113ae28543a39 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{-# LANGUAGE OverloadedStrings #-} | |
{-# OPTIONS_GHC -fwarn-incomplete-patterns #-} | |
-- {-# LANGUAGE TDNR #-} | |
module Main where | |
import Control.Concurrent.STM | |
import Control.Exception | |
import Control.Monad | |
import Crypto.Hash.MD5 as MD5 | |
import Data.ByteString | |
import qualified Data.ByteString.Char8 as C | |
import qualified Data.ByteString.Lazy as LBS | |
import Data.Char | |
import Data.Hex | |
import Data.List as List | |
import Data.Maybe | |
import Data.Monoid | |
import Data.Set as Set | |
import Network.HTTP.Types | |
import Network.Wai as Wai | |
import Network.Wai.Handler.Warp | |
import Network.Wreq | |
import System.Directory | |
import System.Process | |
import Control.Monad.Trans.Except | |
import Control.Lens | |
import Control.Arrow ((>>>)) | |
main :: IO () | |
main = do | |
ops <- newTVarIO Set.empty | |
run 3000 $ app ops | |
type OpId = String | |
app :: TVar (Set OpId) -> Request -> (Wai.Response -> IO b) -> IO b | |
app ops req respond = respond =<< serveTranscode ops req | |
badParam :: ByteString -> Wai.Response | |
badParam name = responseLBS status400 [] $ LBS.fromStrict $ "bad " <> name | |
serveTranscode :: TVar (Set OpId) -> Request -> IO Wai.Response | |
serveTranscode ops req = do | |
e <- runExceptT $ do | |
i <- queryValue "i" | |
f <- queryValue "f" | |
let outputName = C.unpack $ getOutputName i opt f | |
return $ bracket_ (claimOp outputName ops) (releaseOp outputName ops) $ do | |
ready <- doesFileExist outputName | |
unless ready $ transcode outputName i opt | |
return $ responseFile status200 [] outputName Nothing | |
mergeEither e | |
where | |
qs = queryString req | |
queryValue :: ByteString -> ExceptT (IO Wai.Response) IO ByteString | |
queryValue k = case getFirstQueryValue k qs of | |
Just v -> return v | |
Nothing -> throwE $ return $ badParam k | |
opt = getQueryValues "opt" qs | |
maybeToEither l Nothing = Left l | |
maybeToEither _ (Just r) = Right r | |
mergeEither :: Either a a -> a | |
mergeEither e = case e of | |
Left v -> v | |
Right v -> v | |
transcode name i opts = do | |
callProcess (List.head args) (List.tail args) | |
where | |
args = ffmpegArgs name (C.unpack i) $ List.map C.unpack opts | |
download i file = do | |
r <- get i | |
LBS.writeFile file $ r ^. responseBody | |
ffmpegArgs outputName i opts = | |
["nice", "ffmpeg", "-hide_banner", "-i", i] ++ opts ++ ["-y", outputName] | |
getFirstQueryValue :: ByteString -> Query -> Maybe ByteString | |
getFirstQueryValue key = List.find (\(k, _) -> k == key) >>> fmap snd >>> join | |
getQueryValues :: ByteString -> Query -> [ByteString] | |
getQueryValues key = mapMaybe f | |
where | |
f (k, Just v) = | |
if k == key | |
then Just v | |
else Nothing | |
f (_, Nothing) = Nothing | |
claimOp :: String -> TVar (Set OpId) -> IO () | |
claimOp op ops = | |
atomically $ do | |
opsval <- readTVar ops | |
if member op opsval | |
then retry | |
else modifyTVar ops $ Set.insert op | |
releaseOp :: String -> TVar (Set OpId) -> IO () | |
releaseOp file active = atomically $ modifyTVar active $ Set.delete file | |
getOutputName :: ByteString -> [ByteString] -> ByteString -> ByteString | |
getOutputName i opts f = | |
(C.pack . List.map toLower . C.unpack . hex $ hashStrings (i : opts)) <> "." <> | |
f | |
hashStrings :: [ByteString] -> ByteString | |
hashStrings = updates MD5.init >>> finalize |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment