bootphon/phonemizer

number of lines in input and output must be equal, we have: input=X, output=Y

Closed this issue · 2 comments

Describe the bug

number of lines in input and output must be equal, we have: input=2, output=4

Phonemizer version

phonemizer-3.2.1
available backends: espeak-1.48.3, segments-2.2.1
uninstalled backends: espeak-mbrola, festival

System

Linux a9033ed59475 5.4.0-153-generic #170-Ubuntu SMP Fri Jun 16 13:43:31 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux

To reproduce

from phonemizer.backend import EspeakBackend

the_one = EspeakBackend(
    language="en-us",
    preserve_punctuation=True,
    with_stress=True,
    words_mismatch="ignore",
)

out = the_one.phonemize(
    [
        "You know where I am if you change your mind, right? Remember, bowl or eat tonight? …".strip().replace(
            '"', ""
        )
    ]
)

print(out[0])

Expected behavior

juː nˈoʊ wˌɛɹ aɪɐm ɪf juː tʃˈeɪndʒ jʊɹ mˈaɪnd, ɹˈaɪt? ɹɪmˈɛmbɚ, bˈoʊl ɔːɹ ˈiːt tənˈaɪt? … 

Additional context
Running into this issue in context of a web server serving requests in parallel, the above script is a reproduction based on the code used within the server.

When I manually run the script, I can't reproduce the issue, however, in the server environment it pops up quite frequently.

Any ideas how to further debug this?

As far as I understand the setup, the input is not in alignment with what's expected for the output.

Could it be something messed up in communication with the underlying espeak backend.

Or some weird characters that can't be translated?

Thx a lot

Hi, espeak (and so phonemizer) does not support multi-threaded operations. If you want parallelism you need to use multiple processes instead. By so, there is a separate instance of the backend in each process.

import concurrent.futures

from phonemizer.backend import EspeakBackend


backend = EspeakBackend(
    language="en-us",
    preserve_punctuation=True,
    with_stress=True,
    words_mismatch="ignore")


def threads_run(sentences, ntimes, njobs):
    data = []
    with concurrent.futures.ThreadPoolExecutor(max_workers=njobs) as executor:
        futures = [
            executor.submit(backend.phonemize, sent)
            for sent in [sentences] * ntimes]
        for future in concurrent.futures.as_completed(futures):
            data.append(future.result())
    return data


def processes_run(sentences, ntimes, njobs):
    data = []
    with concurrent.futures.ProcessPoolExecutor(max_workers=njobs) as executor:
        futures = [
            executor.submit(backend.phonemize, sent)
            for sent in [sentences] * ntimes]
        for future in concurrent.futures.as_completed(futures):
            data.append(future.result())
    return data


if __name__ == '__main__':
    sentences = [
        "You know where I am if you change your mind, right?",
        "Remember, bowl or eat tonight? …"]

    # serial operation for reference
    ref = backend.phonemize(sentences)

    # repeat 100 times on 4 subprocesses. Ok.
    test2 = processes_run(sentences, 100, 4)
    assert all(t == ref for t in test2)

    # repeat 100 times on 4 threads. Will most likely fail.
    test3 = threads_run(sentences, 100, 4)
    assert all(t == ref for t in test3)

Thanks @mmmaat - that was it!