~alcinnz/rhapsode

4ab0d7f7da55b567f018b2b4e2f2f494a82b467f — Adrian Cochrane 2 years ago f53c0d8
Initialize Voice2JSON for speech recognition!
4 files changed, 83 insertions(+), 13 deletions(-)

M rhapsode.cabal
M src/CExports.hs
M src/Links.hs
M src/main.c
M rhapsode.cabal => rhapsode.cabal +1 -1
@@ 65,7 65,7 @@ library
        network-uri,
        stylist >= 2.4 && <3, css-syntax, xml-conduit-stylist >= 2.3 && <3, scientific,
        async, hurl >= 2, filepath, temporary,
        file-embed >= 0.0.9 && < 0.1, time, parallel >= 1
        file-embed >= 0.0.9 && < 0.1, time, parallel >= 1, process
  
  -- Directories containing source files.
  hs-source-dirs:      src

M src/CExports.hs => src/CExports.hs +3 -3
@@ 38,10 38,10 @@ import Foreign.Marshal.Array
--pair a b = (a, b)

-- FIXME: Segfaults, was intended for the sake of easy concurrency.
foreign export ccall c_docLinksAndRendering :: StablePtr Session -> StablePtr Page -> Bool -> IO (CArray CString)
foreign export ccall c_docLinksAndRendering :: StablePtr Session -> StablePtr Page -> Bool -> CString -> IO (CArray CString)

c_docLinksAndRendering c_session c_page rewriteUrls = do
    c_links <- c_extractLinks c_page
c_docLinksAndRendering c_session c_page rewriteUrls c_v2jProfile = do
    c_links <- c_extractLinks c_page c_v2jProfile
    ssml <- c_renderDoc c_session c_page rewriteUrls
    -- (c_links, ssml) <- c_extractLinks c_page `concurrently` c_renderDoc c_session c_page rewriteUrls
    nil <- newCString ""

M src/Links.hs => src/Links.hs +35 -3
@@ 29,6 29,10 @@ import qualified Data.Set as Set
import Data.List (nub, intercalate)
import Control.Concurrent (forkIO)

-- For Voice2Json's sentences.ini
import Data.Char
import System.Process (callProcess)

data Link = Link {
    label :: Text,
    title :: Text,


@@ 208,14 212,34 @@ updateSuggestions page = do
    createDirectoryIfMissing True dir
    Prelude.writeFile path $ unlines $ map unwords suggestions'

-------
--- Voice2Json language models
-------

-- | Output links to a Voice2Json sentences.ini grammar.
outputSentences _ "" = return ()
outputSentences links dir = do
        Prelude.writeFile (dir </> "sentences.ini") $ unlines sentences
        callProcess "voice2json" ["voice2json", "--profile", dir, "train-profile"]
    where
      sentences = "[links]" : [
            unwords $ words $ filter validChar line -- Enforce valid sentences.ini syntax.
            | line@(_:_) <- map (unpack . label) links ++ map (unpack . title) links ++ map (show . href) links
        ]
      -- | Can this character appear in a sentences.ini rule without escaping?
      validChar ch = not (isAscii ch) || isSpace ch || isAlphaNum ch

-- C API
foreign export ccall c_extractLinks :: StablePtr Page -> IO (CArray CString)
foreign export ccall c_extractLinks :: StablePtr Page -> CString -> IO (CArray CString)

c_extractLinks c_page = do
c_extractLinks c_page c_v2jProfile = do
    page <- deRefStablePtr c_page
    v2jProfile <- peekCString c_v2jProfile
    forkIO $ updateSuggestions page -- background process for useful navigation aid.
    bookmarks <- readBookmarks
    ret <- forM (linksFromPage page ++ extractLinks bookmarks) $ \link -> do
    let links = linksFromPage page ++ extractLinks bookmarks
    forkIO $ outputSentences links v2jProfile
    ret <- forM links $ \link -> do
        c_label <- text2cstring $ strip $ label link
        c_title <- text2cstring $ strip $ title link
        c_href <- newCString $ uriToString id (href link) ""


@@ 248,3 272,11 @@ c_formatLink c_label c_title c_url = do
    audio src = el "audio" [("src", pack src)] []
    prosody attrs txt = el "prosody" attrs [NodeContent txt]
    style field mode inner = el "tts:style" [("field", field), ("mode", mode)] [NodeElement inner]

foreign export ccall c_dataDir :: CString -> IO CString

-- | Used to find Voice2Json profile
c_dataDir c_subdir = do
    subdir <- peekCString c_subdir
    cache <- getXdgDirectory XdgData "rhapsode"
    newCString (cache </> subdir)

M src/main.c => src/main.c +44 -6
@@ 6,6 6,9 @@
#include <termios.h>
#include <limits.h>

#include <dirent.h>
#include <errno.h>

#include "HsFFI.h"
#include <espeak-ng/espeak_ng.h>
#include <sndfile.h>


@@ 31,10 34,11 @@ struct session *c_enableLogging(struct session*);
void c_writeLog(char*, struct session*);

char *c_renderDoc(struct session*, struct page*, _Bool);
char **c_extractLinks(struct page*);
char **c_extractLinks(struct page*, char *v2jProfile);
char **c_docLinksAndRendering(struct session*, struct page*, _Bool); // FIXME segfaults.
int c_ssmlHasMark(char*, char*);
char *c_formatLink(char *label, char *title, char *url);
char *c_dataDir(char *subdir);

char *c_lastVisited(char*);



@@ 322,16 326,25 @@ FILE *parse_opt_file() {
    return ret;
}

int dir_exists(char *path) {
    DIR *dir = opendir(path);
    if (dir) closedir(dir);
    else return ENOENT == errno;
    return 1;
}

int main(int argc, char **argv) {
    int speak_err = 0;
    struct session *session = NULL;
    struct page *referer = NULL;

    int use_espeak = 0;
    int validate_v2j_profile = 0;
    char *logpath = NULL;
    char *mimes;
    FILE *fd_ssml = NULL;
    FILE *fd_links = NULL;
    char *v2j_profile = c_dataDir("voice2json");

    hs_init(&argc, &argv);



@@ 344,9 357,9 @@ int main(int argc, char **argv) {
    int c;
    opterr = 0;
    #ifdef WITH_SPEECHD
    while ((c = getopt(argc, argv, "xs::l::L:kKw::dh")) != -1) {
    while ((c = getopt(argc, argv, "xs::l::L:kKvVw::dh")) != -1) {
    #else
    while ((c = getopt(argc, argv, "xs::l::kKw::h")) != -1) {
    while ((c = getopt(argc, argv, "xs::l::kKv::Vw::h")) != -1) {
    #endif
        switch (c) {
        case 'x':


@@ 367,6 380,14 @@ int main(int argc, char **argv) {
        case 'k':
            read_keyboard = 1;
            break;
        case 'V':
            v2j_profile = "";
            validate_v2j_profile = 0;
            break;
        case 'v':
            if (optarg != NULL) v2j_profile = optarg;
            validate_v2j_profile = 1;
            break;
        case 'w':
            use_espeak = 1;
            path_wav = optarg;


@@ 387,8 408,10 @@ int main(int argc, char **argv) {
            fprintf(stderr, "\t\t\thttps://xkcd.com/1692/\n");
            fprintf(stderr, "\t-l\tlinks\tWrite extracted links to specifed file or stdout as TSV.\n");
            fprintf(stderr, "\t-L\tlog\tWrite (append) network request timing to specified filepath.\n");
            fprintf(stderr, "\t-k\tkeyboard\tRead arrow key navigation & links from stdin. Default behaviour, noop.\n");
            fprintf(stderr, "\t-K\tDon't read input from stdin.");
            fprintf(stderr, "\t-k\tkeyboard in\tRead arrow key navigation & links from stdin. Default behaviour, noop.\n");
            fprintf(stderr, "\t-K\t\tDon't read input from stdin.");
            fprintf(stderr, "\t-v\tvoice in\tEnsure voice input is enabled & optionally sets the Voice2JSON profile.\n");
            fprintf(stderr, "\t-V\t\tDon't listen for voice input.\n");
            fprintf(stderr, "\t-w\t.wav\tWrite an audio recording of the webpage, or (DEFAULT) immediately output through speakers.\n");
            #ifdef WITH_SPEECHD
            fprintf(stderr, "\t-d\tSpeechD\tSchedule page read via the SpeechD daemon. (BROKEN)\n");


@@ 431,6 454,21 @@ int main(int argc, char **argv) {
    referer = c_initialReferer();

    if (use_espeak) speak_err = speak_initialize();

    struct stat sb;
    if (validate_v2j_profile && dir_exists(v2j_profile)) {
        // TODO Would packagers want a way to configure autodownloading of these profiles?
        fprintf(stderr, "Voice2JSON profile is uninitialized! Voice recognition is now disabled.\n");
        fprintf(stderr, "Please download a profile from: http://voice2json.org/#supported-languages\n");
        fprintf(stderr, "And save the download in: %s\n", v2j_profile);
        if (use_espeak) {
            speak_text("Voice2JSON profile is uninitialized! Voice recognition is now disabled.",
                espeakRATE, 0);
        }

        v2j_profile = "";
    }

    char *ssml, **links, *uri;
    int read_links = 0;
    if (optind >= argc) {


@@ 451,8 489,8 @@ read_uri:
        if (logpath != NULL) session = c_enableLogging(session);

        struct page *page = c_fetchURL(session, mimes, referer, uri);
        links = c_extractLinks(page, v2j_profile);
        ssml = c_renderDoc(session, page, use_espeak);
        links = c_extractLinks(page);

        if (logpath != NULL) c_writeLog(logpath, session);