~alcinnz/rhapsode

487a2880b3579b86d0080453a93c6b2c3849e88a — Adrian Cochrane 4 years ago 395b9e4
Embed espeak-ng
2 files changed, 122 insertions(+), 16 deletions(-)

M rhapsode.cabal
M src/main.c
M rhapsode.cabal => rhapsode.cabal +1 -0
@@ 75,6 75,7 @@ library
executable rhapsode
  main-is: main.c
  ghc-options: -no-hs-main
  extra-libraries: espeak-ng
  build-depends:       base >=4.9 && <=4.12, rhapsode
  other-modules: Stub
  hs-source-dirs: src

M src/main.c => src/main.c +121 -16
@@ 5,7 5,9 @@
#include <fcntl.h>

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

/* Exported Haskell functions/types */
struct session;
struct session *c_newSession();
void c_freeSession(struct session*);


@@ 20,13 22,116 @@ char *c_renderDoc(struct session*, struct page*);
char **c_extractLinks(struct page*);
char **c_docLinksAndRendering(struct session*, struct page*); // FIXME segfaults.

/* espeak-ng integration. Based on the espeak-ng command source code. */
FILE *fd_wav = NULL;
static int samplerate;
espeak_ng_ERROR_CONTEXT context;

void write4b(int value) {
    // Write 4 bytes to a file, least significant first
	int ix;

	for (ix = 0; ix < 4; ix++) {
		fputc(value & 0xff, fd_wav);
		value = value >> 8;
	}
}

int wrote_header = 0;
void write_header() {
    static unsigned char wave_hdr[44] = {
    		'R', 'I', 'F', 'F', 0x24, 0xf0, 0xff, 0x7f, 'W', 'A', 'V', 'E', 'f', 'm', 't', ' ',
    		0x10, 0, 0, 0, 1, 0, 1, 0,  9, 0x3d, 0, 0, 0x12, 0x7a, 0, 0,
    		2, 0, 0x10, 0, 'd', 'a', 't', 'a',  0x00, 0xf0, 0xff, 0x7f
    	};

    fwrite(wave_hdr, 1, 24, fd_wav);
	write4b(samplerate);
	write4b(samplerate * 2);
	fwrite(&wave_hdr[32], 1, 12, fd_wav);
}
void close_wav_file() {
    fflush(fd_wav);
	unsigned int pos = ftell(fd_wav);

	if (fseek(fd_wav, 4, SEEK_SET) != -1) write4b(pos - 8);
	if (fseek(fd_wav, 40, SEEK_SET) != -1) write4b(pos - 44);

	fclose(fd_wav);
}

int synth_callback(short *wav, int numsamples, espeak_EVENT *events) {
    if (wav == NULL) return 0;

    while (events->type != 0) {
        if (events->type == espeakEVENT_SAMPLERATE) samplerate = events->id.number;
        events++;
    }

    if (!wrote_header) write_header();
    if (numsamples > 0) fwrite(wav, numsamples*2, 1, fd_wav);
    return 0;
}

int speak_initialize() {
    espeak_ng_InitializePath(NULL);
    context = NULL;
    espeak_ng_STATUS result = espeak_ng_Initialize(&context);
    if (result != ENS_OK) {
        espeak_ng_PrintStatusCodeMessage(result, stderr, context);
		espeak_ng_ClearErrorContext(&context);
		return 2;
    }

    if (fd_wav != stdout) {
        result = espeak_ng_InitializeOutput(ENOUTPUT_MODE_SYNCHRONOUS, 0, NULL);
        espeak_SetSynthCallback(synth_callback);
    } else {
        result = espeak_ng_InitializeOutput(ENOUTPUT_MODE_SPEAK_AUDIO, 0, NULL);
    }
    if (result != ENS_OK) {
        espeak_ng_PrintStatusCodeMessage(result, stderr, context);
        return 3;
    }

    samplerate = espeak_ng_GetSampleRate();
    return 0;
}
void speak(char *ssml) {
    int flags = espeakCHARS_AUTO | espeakPHONEMES | espeakENDPAUSE | espeakCHARS_UTF8 | espeakSSML;
    espeak_Synth(ssml, strlen(ssml)+1, 0, POS_CHARACTER, 0, flags, NULL, NULL);
}
int speak_finalize() {
    espeak_ng_STATUS result = espeak_ng_Synchronize();
    if (result != ENS_OK) {
        espeak_ng_PrintStatusCodeMessage(result, stderr, context);
        return 4;
    }

    if (fd_wav != stdout) close_wav_file();
    espeak_ng_Terminate();
    return 0;
}

/* Main driver */
void write_links(FILE *dest, char **links) {
    for (int i = 0; strcmp(links[i], " ") != 0; i++) {
        fprintf(dest, "%s%c", links[i], (i % 3) == 2 ? '\n' : '\t');
    }
}

FILE *parse_opt_file(char *mode) {
    FILE *ret = optarg != NULL ? fopen(optarg, mode) : stdout;
    if (ret == NULL) {
      fprintf(stderr, "Failed to open file %s\n", optarg);
      hs_exit();
      exit(-1);
    }
    return ret;
}

int main(int argc, char **argv) {
    int speak_err = 0;
    hs_init(&argc, &argv);

    char *mimes = "text/html text/xml application/xml application/xhtml+xml text/plain";


@@ 35,26 140,19 @@ int main(int argc, char **argv) {

    int c;
    opterr = 0;
    while ((c = getopt(argc, argv, "xs::l::h")) != -1) {
    while ((c = getopt(argc, argv, "xs::l::w::h")) != -1) {
        switch (c) {
        case 'x':
            mimes = "text/xml application/xml application/xhtml+xml text/html text/plain";
            break;
        case 's':
            fd_ssml = optarg != NULL ? fopen(optarg, "w") : stdout;
            if (fd_ssml == NULL) {
                fprintf(stderr, "Failed to open file %s\n", optarg);
                hs_exit();
                return -1;
            }
            fd_ssml = parse_opt_file("w");
            break;
        case 'l':
            fd_links = optarg != NULL ? fopen(optarg, "w") : stdout;
            if (fd_links == NULL) {
                fprintf(stderr, "Failed to open file %s\n", optarg);
                hs_exit();
                return -1;
            }
            fd_links = parse_opt_file("w");
            break;
        case 'w':
            fd_wav = parse_opt_file("wb");
            break;
        case '?':
            fprintf(stderr, "Invalid flag %c\n\n", optopt);


@@ 68,28 166,35 @@ int main(int argc, char **argv) {
            fprintf(stderr, "\t\t\tIf both -s & -l are enabled without an argument, writes to stderr instead.\n");

            hs_exit();
            return c == 'h' ? 0 : -1;
            return c == 'h' ? 0 : 1;
        }
    }
    if (fd_ssml == stdout && fd_links == stdout) fd_links = stderr;
    if (fd_ssml == NULL && fd_links == NULL) fd_ssml = stdout;
    if (fd_ssml == NULL && fd_links == NULL && fd_wav == NULL) fd_wav = stdout;

    struct session *session = c_newSession();
    struct page *referer = c_initialReferer();

    if (fd_wav != NULL) speak_err = speak_initialize();
    for (int i = optind; i < argc; i++) {
        printf("%s\n", argv[i]);
        if (fd_wav != NULL && speak_err == 0) speak(argv[i]);
        else printf("%s\n", argv[i]);

        struct page *page = c_fetchURL(session, mimes, referer, argv[i]);
        char *ssml = c_renderDoc(session, page);
        char **links = c_extractLinks(page);

        if (fd_ssml != NULL) fprintf(fd_ssml, "%s\n", ssml);
        if (fd_links != NULL) write_links(fd_links, links);
        if (fd_wav != NULL& speak_err == 0) speak(ssml);

        c_freePage(page);
    }
    if (fd_wav != NULL & speak_err == 0) speak_err = speak_finalize();

    c_freePage(referer);
    c_freeSession(session);
    hs_exit();

    return speak_err;
}