voicebase (empty) → 0.1.1.0
raw patch · 11 files changed
+719/−0 lines, 11 filesdep +HsOpenSSLdep +aesondep +basesetup-changed
Dependencies added: HsOpenSSL, aeson, base, bytestring, http-client, http-client-openssl, json-autotype, lens, mime-types, options, text, voicebase, wreq
Files
- ChangeLog.md +3/−0
- LICENSE +30/−0
- README.md +42/−0
- Setup.hs +2/−0
- app/Main.hs +35/−0
- src/Json/ProgressTypes.hs +80/−0
- src/Json/SubmitMediaTypes.hs +95/−0
- src/Json/TranscriptTypes.hs +234/−0
- src/VoicebaseClient.hs +122/−0
- test/Spec.hs +2/−0
- voicebase.cabal +74/−0
+ ChangeLog.md view
@@ -0,0 +1,3 @@+# Changelog for voicebase++## Unreleased changes
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Daisee (c) 2018++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Daisee nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,42 @@+# Haskell bindings for voice base api++http://voicebase.readthedocs.io/en/v2-beta/++## Usage++```haskell+ import VoicebaseClient+ import Json.TranscriptTypes++ main = do+ result <- transcribe "your bearer token" "./audio.mp3"+ -- get validated response or error+ case result of+ Left err -> print $ err+ Right val -> putStrLn val++ -- get a parsed structure+ parsed <- transcribeParse "your bearer token" "./audio.mp3"+ case parsed of + Left err -> print $ err+ Right val -> print $ latestTranscriptsWordsTranscripts . transcriptsLatestTranscripts . mediaTranscripts . topLevelMedia val++```++There is also a main file which shows usage+++## features+++ Post audio file to voicebase++ Poll for progress++ Get resulting transcript or valid json or an error.++## Trivia+### wreq client instead of servant ###++The reason httplib is used rather than servant is because of multipart posting.+It was asserted that servant is incapable of doing that.+This is a requirement for the voicebase api however.+Http client does support this however we need to write more code as a result of this.+
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ app/Main.hs view
@@ -0,0 +1,35 @@+{-# LANGUAGE OverloadedStrings #-}++module Main where++import Network.Mime(defaultMimeLookup)+import VoicebaseClient+import Options+import qualified Data.ByteString.Lazy as LBS++data MainOptions = MainOptions+ { + token :: Maybe BearerToken,+ targetFile :: Maybe FilePath+ }++instance Options MainOptions where+ defineOptions = pure MainOptions+ <*> simpleOption "bearer-token" Nothing+ "The bearer token obtained from voicebase"+ <*> simpleOption "file" Nothing+ "The file to be posted"+main :: IO ()+main = runCommand $ \opts args -> do+ case token opts of+ Just bearToken -> case targetFile opts of + Just file -> withBearAndFile bearToken file+ Nothing -> putStrLn "file required"+ Nothing -> putStrLn "bearer token required"++withBearAndFile :: BearerToken -> FilePath -> IO() +withBearAndFile bearToken file = do + -- result <- transcribeFile bearToken file + bytes <- LBS.readFile file+ result <- transcribeBytes bearToken (LazyByteFile bytes (defaultMimeLookup "f.mp3"))+ putStrLn . show $ result
+ src/Json/ProgressTypes.hs view
@@ -0,0 +1,80 @@+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE DeriveGeneric #-}++-- | Auto generated module by json-autotype+module Json.ProgressTypes where++import System.Exit (exitFailure, exitSuccess)+import System.IO (stderr, hPutStrLn)+import qualified Data.ByteString.Lazy.Char8 as BSL+import Data.ByteString.Lazy+import System.Environment (getArgs)+import Control.Monad (forM_, mzero, join)+import Control.Applicative+import Data.Aeson.AutoType.Alternative+import Data.Aeson(eitherDecode, Value(..), FromJSON(..), ToJSON(..),+ pairs,+ (.:), (.:?), (.=), object)+import Data.Monoid+import Data.Text (Text)+import qualified GHC.Generics++-- | Workaround for https://github.com/bos/aeson/issues/287.+o .:?? val = fmap join (o .:? val)+++data Tasks = Tasks { ++ } deriving (Show,Eq,GHC.Generics.Generic)+++instance FromJSON Tasks where+ parseJSON (Object v) = return Tasks+ parseJSON _ = mzero+++instance ToJSON Tasks where+ toJSON (Tasks {}) = object []++++data Progress = Progress { + progressStatus :: Text,+ progressJobId :: Text,+ progressTasks :: Tasks+ } deriving (Show,Eq,GHC.Generics.Generic)+++instance FromJSON Progress where+ parseJSON (Object v) = Progress <$> v .: "status" <*> v .: "jobId" <*> v .: "tasks"+ parseJSON _ = mzero+++instance ToJSON Progress where+ toJSON (Progress {..}) = object ["status" .= progressStatus, "jobId" .= progressJobId, "tasks" .= progressTasks]+ toEncoding (Progress {..}) = pairs ("status" .= progressStatus<>"jobId" .= progressJobId<>"tasks" .= progressTasks)+++data TopLevel = TopLevel { + topLevelStatus :: Text,+ topLevelProgress :: Progress,+ topLevelMediaId :: Text+ } deriving (Show,Eq,GHC.Generics.Generic)+++instance FromJSON TopLevel where+ parseJSON (Object v) = TopLevel <$> v .: "status" <*> v .: "progress" <*> v .: "mediaId"+ parseJSON _ = mzero+++instance ToJSON TopLevel where+ toJSON (TopLevel {..}) = object ["status" .= topLevelStatus, "progress" .= topLevelProgress, "mediaId" .= topLevelMediaId]+ toEncoding (TopLevel {..}) = pairs ("status" .= topLevelStatus<>"progress" .= topLevelProgress<>"mediaId" .= topLevelMediaId)+++parse :: ByteString -> Either String TopLevel+parse = eitherDecode
+ src/Json/SubmitMediaTypes.hs view
@@ -0,0 +1,95 @@+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE DeriveGeneric #-}++-- | Auto generated module by json-autotype+module Json.SubmitMediaTypes where++import System.Exit (exitFailure, exitSuccess)+import System.IO (stderr, hPutStrLn)+import qualified Data.ByteString.Lazy.Char8 as BSL+import Data.ByteString.Lazy+import System.Environment (getArgs)+import Control.Monad (forM_, mzero, join)+import Control.Applicative+import Data.Aeson.AutoType.Alternative+import Data.Aeson(eitherDecode, Value(..), FromJSON(..), ToJSON(..),+ pairs,+ (.:), (.:?), (.=), object)+import Data.Monoid+import Data.Text (Text)+import qualified GHC.Generics++-- | Workaround for https://github.com/bos/aeson/issues/287.+o .:?? val = fmap join (o .:? val)+++data Self = Self { + selfHref :: Text+ } deriving (Show,Eq,GHC.Generics.Generic)+++instance FromJSON Self where+ parseJSON (Object v) = Self <$> v .: "href"+ parseJSON _ = mzero+++instance ToJSON Self where+ toJSON (Self {..}) = object ["href" .= selfHref]+ toEncoding (Self {..}) = pairs ("href" .= selfHref)+++data Links = Links { + linksSelf :: Self+ } deriving (Show,Eq,GHC.Generics.Generic)+++instance FromJSON Links where+ parseJSON (Object v) = Links <$> v .: "self"+ parseJSON _ = mzero+++instance ToJSON Links where+ toJSON (Links {..}) = object ["self" .= linksSelf]+ toEncoding (Links {..}) = pairs ("self" .= linksSelf)+++data Metadata = Metadata { ++ } deriving (Show,Eq,GHC.Generics.Generic)+++instance FromJSON Metadata where+ parseJSON (Object v) = return Metadata+ parseJSON _ = mzero+++instance ToJSON Metadata where+ toJSON (Metadata {}) = object []++++data TopLevel = TopLevel { + topLevelStatus :: Text,+ topLevelMediaId :: Text,+ topLevelLinks :: Links,+ topLevelMetadata :: Metadata+ } deriving (Show,Eq,GHC.Generics.Generic)+++instance FromJSON TopLevel where+ parseJSON (Object v) = TopLevel <$> v .: "status" <*> v .: "mediaId" <*> v .: "_links" <*> v .: "metadata"+ parseJSON _ = mzero+++instance ToJSON TopLevel where+ toJSON (TopLevel {..}) = object ["status" .= topLevelStatus, "mediaId" .= topLevelMediaId, "_links" .= topLevelLinks, "metadata" .= topLevelMetadata]+ toEncoding (TopLevel {..}) = pairs ("status" .= topLevelStatus<>"mediaId" .= topLevelMediaId<>"_links" .= topLevelLinks<>"metadata" .= topLevelMetadata)++++parse :: ByteString -> Either String TopLevel+parse = eitherDecode
+ src/Json/TranscriptTypes.hs view
@@ -0,0 +1,234 @@+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE DeriveGeneric #-}++-- | Auto generated module by json-autotype+module Json.TranscriptTypes where++import System.Exit (exitFailure, exitSuccess)+import System.IO (stderr, hPutStrLn)+import qualified Data.ByteString.Lazy.Char8 as BSL+import Data.ByteString.Lazy+import System.Environment (getArgs)+import Control.Monad (forM_, mzero, join)+import Control.Applicative+import Data.Aeson.AutoType.Alternative+import Data.Aeson(eitherDecode, Value(..), FromJSON(..), ToJSON(..),+ pairs,+ (.:), (.:?), (.=), object)+import Data.Monoid+import Data.Text (Text)+import qualified GHC.Generics++-- | Workaround for https://github.com/bos/aeson/issues/287.+o .:?? val = fmap join (o .:? val)++data LatestTopics = LatestTopics { + latestTopicsTopics :: [(Maybe Value)],+ latestTopicsRevision :: Text+ } deriving (Show,Eq,GHC.Generics.Generic)+++instance FromJSON LatestTopics where+ parseJSON (Object v) = LatestTopics <$> v .: "topics" <*> v .: "revision"+ parseJSON _ = mzero+++data Topics = Topics { + topicsLatestTopics :: LatestTopics+ } deriving (Show,Eq,GHC.Generics.Generic)+++instance FromJSON Topics where+ parseJSON (Object v) = Topics <$> v .: "latest"+ parseJSON _ = mzero+++data Tasks = Tasks { +} deriving (Show,Eq,GHC.Generics.Generic)+++instance FromJSON Tasks where+ parseJSON (Object v) = return Tasks+ parseJSON _ = mzero++data Progress = Progress { + progressStatus :: Text,+ progressJobId :: Text,+ progressTasks :: Tasks+ } deriving (Show,Eq,GHC.Generics.Generic)+++instance FromJSON Progress where+ parseJSON (Object v) = Progress <$> v .: "status" <*> v .: "jobId" <*> v .: "tasks"+ parseJSON _ = mzero+++data Job = Job { + jobProgress :: Progress+ } deriving (Show,Eq,GHC.Generics.Generic)+++instance FromJSON Job where+ parseJSON (Object v) = Job <$> v .: "progress"+ parseJSON _ = mzero+++data T = T { + tUnknown :: [Text]+ } deriving (Show,Eq,GHC.Generics.Generic)+++instance FromJSON T where+ parseJSON (Object v) = T <$> v .: "unknown"+ parseJSON _ = mzero+++data WordsKeyElt = WordsKeyElt { + wordsKeyEltRelevance :: Text,+ wordsKeyEltT :: T,+ wordsKeyEltName :: Text+ } deriving (Show,Eq,GHC.Generics.Generic)+++instance FromJSON WordsKeyElt where+ parseJSON (Object v) = WordsKeyElt <$> v .: "relevance" <*> v .: "t" <*> v .: "name"+ parseJSON _ = mzero+++data LatestKeywords = LatestKeywords { + latestKeywordsGroups :: [(Maybe Value)],+ latestKeywordsWordsKey :: [WordsKeyElt],+ latestKeywordsRevision :: Text+ } deriving (Show,Eq,GHC.Generics.Generic)+++instance FromJSON LatestKeywords where+ parseJSON (Object v) = LatestKeywords <$> v .: "groups" <*> v .: "words" <*> v .: "revision"+ parseJSON _ = mzero+++data Keywords = Keywords { + keywordsLatestKeywords :: LatestKeywords+ } deriving (Show,Eq,GHC.Generics.Generic)+++instance FromJSON Keywords where+ parseJSON (Object v) = Keywords <$> v .: "latest"+ parseJSON _ = mzero+++data Length = Length { + lengthDescriptive :: Text,+ lengthMilliseconds :: Double+ } deriving (Show,Eq,GHC.Generics.Generic)+++instance FromJSON Length where+ parseJSON (Object v) = Length <$> v .: "descriptive" <*> v .: "milliseconds"+ parseJSON _ = mzero+++data Metadata = Metadata { + metadataLength :: Length,+ metadataContentType :: Text+ } deriving (Show,Eq,GHC.Generics.Generic)+++instance FromJSON Metadata where+ parseJSON (Object v) = Metadata <$> v .: "length" <*> v .: "contentType"+ parseJSON _ = mzero+++data WordsTranscriptsElt = WordsTranscriptsElt { + wordsTranscriptsEltW :: Text,+ wordsTranscriptsEltM :: (Maybe (Text:|:[(Maybe Value)])),+ wordsTranscriptsEltP :: Double,+ wordsTranscriptsEltE :: Double,+ wordsTranscriptsEltC :: Double,+ wordsTranscriptsEltS :: Double+ } deriving (Show,Eq,GHC.Generics.Generic)+++instance FromJSON WordsTranscriptsElt where+ parseJSON (Object v) = WordsTranscriptsElt <$> v .: "w" <*> v .:?? "m" <*> v .: "p" <*> v .: "e" <*> v .: "c" <*> v .: "s"+ parseJSON _ = mzero+++data LatestTranscripts = LatestTranscripts { + latestTranscriptsEngine :: Text,+ latestTranscriptsConfidence :: Double,+ latestTranscriptsName :: Text,+ latestTranscriptsWordsTranscripts :: [WordsTranscriptsElt],+ latestTranscriptsRevision :: Text+ } deriving (Show,Eq,GHC.Generics.Generic)+++instance FromJSON LatestTranscripts where+ parseJSON (Object v) = LatestTranscripts <$> v .: "engine" <*> v .: "confidence" <*> v .: "name" <*> v .: "words" <*> v .: "revision"+ parseJSON _ = mzero+++data Transcripts = Transcripts { + transcriptsLatestTranscripts :: LatestTranscripts+ } deriving (Show,Eq,GHC.Generics.Generic)+++instance FromJSON Transcripts where+ parseJSON (Object v) = Transcripts <$> v .: "latest"+ parseJSON _ = mzero+++data Media = Media { + mediaStatus :: Text,+ mediaTopics :: Topics,+ mediaMediaId :: Text,+ mediaDateCreated :: Text,+ mediaJob :: Job,+ mediaKeywords :: Keywords,+ mediaMetadata :: Metadata,+ mediaTranscripts :: Transcripts,+ mediaDateFinished :: Text+ } deriving (Show,Eq,GHC.Generics.Generic)+++instance FromJSON Media where+ parseJSON (Object v) = Media <$> v .: "status" <*> v .: "topics" <*> v .: "mediaId" <*> v .: "dateCreated" <*> v .: "job" <*> v .: "keywords" <*> v .: "metadata" <*> v .: "transcripts" <*> v .: "dateFinished"+ parseJSON _ = mzero+++data Self = Self { + selfHref :: Text+ } deriving (Show,Eq,GHC.Generics.Generic)+++instance FromJSON Self where+ parseJSON (Object v) = Self <$> v .: "href"+ parseJSON _ = mzero+++data Links = Links { + linksSelf :: Self+ } deriving (Show,Eq,GHC.Generics.Generic)+++instance FromJSON Links where+ parseJSON (Object v) = Links <$> v .: "self"+ parseJSON _ = mzero+++data Transcript = Transcript { + topLevelMedia :: Media,+ topLevelLinks :: Links+ } deriving (Show,Eq,GHC.Generics.Generic)+++instance FromJSON Transcript where+ parseJSON (Object v) = Transcript <$> v .: "media" <*> v .: "_links"+ parseJSON _ = mzero++parse :: ByteString -> Either String Transcript+parse = eitherDecode
+ src/VoicebaseClient.hs view
@@ -0,0 +1,122 @@+{-# LANGUAGE OverloadedStrings #-}++-- | Main module for doing the requests to the voicebase api: http://voicebase.readthedocs.io/en/v2-beta/+module VoicebaseClient+ (+ transcribe, + transcribeFile, + transcribeBytes, + transcribeParse, + BearerToken, + Error(..),+ LazyByteFile(..)+ ) where++import Control.Lens+import Control.Monad+import Control.Applicative++import Data.Maybe+import Data.Monoid+import Data.Text.Encoding as TE+import Data.Bifunctor++import qualified Data.ByteString.Char8 as BS+import qualified Data.ByteString.Lazy.Char8 as C8LBS+import qualified Data.ByteString.Lazy as LBS+import Data.Text (Text, unpack)++import Data.Aeson(Value, eitherDecode, FromJSON(parseJSON))+import Data.Aeson.Types(parseEither)+import Network.Wreq+import Network.Mime(MimeType)+import qualified Network.Wreq.Session as Sess+import Network.HTTP.Client.OpenSSL+import Network.HTTP.Client (defaultManagerSettings, managerResponseTimeout, responseTimeoutMicro)+import OpenSSL.Session (context)++import qualified Json.SubmitMediaTypes as Submit+import qualified Json.ProgressTypes as Progress+import qualified Json.TranscriptTypes as Transcript++import System.IO(fixIO)++-- | get your bearer token at http://voicebase.readthedocs.io/en/v2-beta/how-to-guides/hello-world.html#token+type BearerToken = String++data Error = UnkownResponse | ParseError String+ deriving(Show)++url :: String+url = "https://apis.voicebase.com/v2-beta/media"++timeout = responseTimeoutMicro 10000++makeOpts :: BearerToken -> Options+makeOpts token = withAuthorization token defaults++withAuthorization :: BearerToken -> Options -> Options+withAuthorization token options = options & manager .~ Left (opensslManagerSettings context) + & manager .~ Left (defaultManagerSettings { managerResponseTimeout = timeout} )+ & header "Authorization" .~ [(BS.pack ("Bearer " ++ token))]++-- | transcribes the audio file and puts it into a Transcript+transcribeParse :: BearerToken -> FilePath -> IO(Either Error Transcript.Transcript)+transcribeParse token filepath = fmap (join . second (first ParseError . parseEither parseJSON)) (transcribeFile token filepath)++inputName = "media"+-- | Given a bearer token, and a filepath to an audio file, this function will +-- | eventually return a transcript or times out after 10 seconds+-- | Throws HttpExceptionRequest, IOException (file not found)+transcribeFile :: BearerToken -> FilePath -> IO(Either Error Value)+transcribeFile bearerToken filePath = transcribe bearerToken defaults (+ [partFileSource inputName filePath]+ )++-- | In case of a bytestring, we also need to mimetype to decode the send string+data LazyByteFile = LazyByteFile {+ content :: LBS.ByteString,+ mimetype :: MimeType -- to get this Jappie used defaultMimeLookup "f.mp3"+ }+-- | Transcribe a bytestring+-- | Throws HttpExceptionRequest+transcribeBytes :: BearerToken -> LazyByteFile -> IO(Either Error Value)+transcribeBytes bearerToken (LazyByteFile content mimetype) = + transcribe bearerToken defaults (+ [partLBS inputName content & partContentType .~ Just mimetype ]+ )++-- | Generic transcribe, agnostic of options, will add ssl, +-- | the bearer token and a timeout to the options.+-- | Throws HttpExceptionRequest+transcribe :: BearerToken -> Options -> [Part] -> IO(Either Error Value)+transcribe token options parts = do+ session <- Sess.newSession+ response <- Sess.postWith (withAuthorization token options) session url parts+ case Submit.parse (response ^. responseBody) of+ Left error -> return $ Left $ ParseError error+ Right parsed -> do + -- wait for the service to complete+ waitResult <- pollStatus token session (Submit.topLevelMediaId parsed)+ case waitResult of+ Right _ -> first ParseError . eitherDecode <$> (requestTranscript token session (Submit.topLevelMediaId parsed)) + Left error -> return $ Left $ error+++pollStatus :: BearerToken -> Sess.Session -> Text -> IO(Either Error ())+pollStatus token session mediaId = pollStatus' token session mediaId "pending"++pollStatus' :: BearerToken -> Sess.Session -> Text -> Text -> IO (Either Error ())+pollStatus' token session mediaId "pending" = do+ response <- Sess.getWith (makeOpts token) session (url <> "/" <> (unpack mediaId) <> "/progress")+ case Progress.parse (response ^. responseBody) of + Left error -> return $ Left $ ParseError error+ Right parsed -> pollStatus' token session mediaId ((Progress.progressStatus . Progress.topLevelProgress) $ parsed)+pollStatus' token session mediaId "started" = pollStatus' token session mediaId "pending"+pollStatus' token session mediaId "completed" = return $ Right ()+pollStatus' _ _ _ _ = return $ Left UnkownResponse++requestTranscript :: BearerToken -> Sess.Session -> Text -> IO (C8LBS.ByteString)+requestTranscript token session mediaId = do+ response <- Sess.getWith (makeOpts token) session (url <> "/" <> (unpack mediaId))+ return $ response ^. responseBody
+ test/Spec.hs view
@@ -0,0 +1,2 @@+main :: IO ()+main = putStrLn "Test suite not yet implemented"
+ voicebase.cabal view
@@ -0,0 +1,74 @@+-- This file has been generated from package.yaml by hpack version 0.20.0.+--+-- see: https://github.com/sol/hpack+--+-- hash: 1d2c9271b864ce3c92a5289357729a078bb7fa8814ebedf3ccf470a746a16270++name: voicebase+version: 0.1.1.0+synopsis: Upload audio files to voicebase to get a transcription+description: voicebase bindings for <http://voicebase.readthedocs.io/en/v2-beta/>+category: API+author: Jappie Klooster+maintainer: jappie.klooster@daisee.com+copyright: Daisee Pty Ltd+license: BSD3+license-file: LICENSE+build-type: Simple+cabal-version: >= 1.10++extra-source-files:+ ChangeLog.md+ README.md++library+ hs-source-dirs:+ src+ build-depends:+ HsOpenSSL+ , aeson+ , base >=4.7 && <5+ , bytestring+ , http-client+ , http-client-openssl+ , json-autotype+ , lens+ , mime-types+ , text+ , wreq+ exposed-modules:+ Json.ProgressTypes+ Json.SubmitMediaTypes+ Json.TranscriptTypes+ VoicebaseClient+ other-modules:+ Paths_voicebase+ default-language: Haskell2010++executable voicebase+ main-is: Main.hs+ hs-source-dirs:+ app+ ghc-options: -threaded -rtsopts -with-rtsopts=-N+ build-depends:+ base >=4.7 && <5+ , bytestring+ , mime-types+ , options+ , voicebase+ other-modules:+ Paths_voicebase+ default-language: Haskell2010++test-suite voicebase-test+ type: exitcode-stdio-1.0+ main-is: Spec.hs+ hs-source-dirs:+ test+ ghc-options: -threaded -rtsopts -with-rtsopts=-N+ build-depends:+ base >=4.7 && <5+ , voicebase+ other-modules:+ Paths_voicebase+ default-language: Haskell2010