1 package org.daisy.pipeline.tts;
2
3 import static java.lang.Math.addExact;
4 import static java.lang.Math.multiplyExact;
5 import static java.lang.Math.toIntExact;
6 import java.util.HashMap;
7 import java.util.Map;
8
9 import org.daisy.pipeline.tts.TTSEngine.SynthesisResult;
10 import org.daisy.pipeline.tts.TTSRegistry.TTSResource;
11 import org.daisy.pipeline.tts.TTSService.SynthesisException;
12
13 import net.sf.saxon.s9api.XdmNode;
14
15 public class TimedTTSExecutor {
16
17 private static final int FIRST_CHARACTERS = 2500;
18 private static final int SHORT_SENTENCE_THRESHOLD = 25;
19 private Map<TTSEngine,Integer> totalCharacters = new HashMap<>();
20 private Map<TTSEngine,Integer> maxMillisecPerCharacter = new HashMap<>();
21 private Map<TTSEngine,Integer> maxMillisecOfShortSentence = new HashMap<>();
22
23
24
25
26
27
28
29 private int maximumMillisec(TTSEngine engine, int sentenceSize) {
30 int wordCount = 1 + sentenceSize / 6;
31 Integer n;
32 synchronized(totalCharacters) {
33 n = totalCharacters.get(engine);
34 Integer safeMillisec = null;
35 if (n == null || n < FIRST_CHARACTERS)
36
37
38
39 safeMillisec = addExact(5000,
40 multiplyExact(wordCount,
41 multiplyExact(10,
42 engine.expectedMillisecPerWord())));
43 if (n == null)
44 return safeMillisec;
45 else {
46
47 Integer estimatedMillisec = multiplyExact(maxMillisecPerCharacter.get(engine), sentenceSize);
48 Integer minMillisec = maxMillisecOfShortSentence.get(engine);
49 if (minMillisec != null) {
50 if (estimatedMillisec < minMillisec)
51 estimatedMillisec = minMillisec;
52 } else if (sentenceSize < SHORT_SENTENCE_THRESHOLD)
53 estimatedMillisec = multiplyExact(maxMillisecPerCharacter.get(engine), SHORT_SENTENCE_THRESHOLD);
54 if (n < FIRST_CHARACTERS)
55
56 return toIntExact(
57 addExact(
58 multiplyExact((long)safeMillisec, FIRST_CHARACTERS - n) / FIRST_CHARACTERS,
59 multiplyExact((long)estimatedMillisec, 3 * n) / FIRST_CHARACTERS));
60 else
61 return multiplyExact(3, estimatedMillisec);
62 }
63 }
64 }
65
66 public SynthesisResult synthesizeWithTimeout(
67 TTSTimeout timeout, TTSTimeout.ThreadFreeInterrupter interrupter, TTSLog.Entry log,
68 XdmNode sentence, int sentenceSize, TTSEngine engine, Voice voice,
69 TTSResource threadResources
70 ) throws SynthesisException, TimeoutException {
71 long startTime = System.currentTimeMillis();
72 int timeoutSec = maximumMillisec(engine, sentenceSize) / 1000;
73
74 timeoutSec += 1;
75 if (log != null)
76 log.setTimeout(timeoutSec);
77 timeout.enableForCurrentThread(interrupter, timeoutSec);
78 try {
79 SynthesisResult result = engine.synthesize(
80 sentence, voice, threadResources);
81 Long millisecElapsed = System.currentTimeMillis() - startTime;
82 if (log != null)
83 log.setTimeElapsed((float)millisecElapsed / 1000);
84 synchronized(totalCharacters) {
85 Long millisecElapsedPerCharacter = sentenceSize == 0
86 ? 0
87 : millisecElapsed / sentenceSize;
88 int max = 1;
89 if (maxMillisecPerCharacter.containsKey(engine))
90 max = maxMillisecPerCharacter.get(engine);
91 else
92 maxMillisecPerCharacter.put(engine, max);
93 if (millisecElapsedPerCharacter > max)
94 maxMillisecPerCharacter.put(engine, toIntExact(millisecElapsedPerCharacter));
95 if (sentenceSize < SHORT_SENTENCE_THRESHOLD
96 && maxMillisecOfShortSentence.containsKey(engine)
97 && millisecElapsed > maxMillisecOfShortSentence.get(engine))
98 maxMillisecOfShortSentence.put(engine, toIntExact(millisecElapsed));
99 Integer n = totalCharacters.get(engine);
100 if (n == null) n = 0;
101 if (n < FIRST_CHARACTERS)
102 totalCharacters.put(engine, n + sentenceSize);
103 }
104 return result;
105 } catch (InterruptedException e) {
106
107 throw new TimeoutException(timeoutSec, e);
108 } finally {
109 timeout.disable();
110 }
111 }
112
113 public static class TimeoutException extends RuntimeException {
114
115 private final int seconds;
116
117 private TimeoutException(int seconds, InterruptedException cause) {
118 super("TTS timed out after " + seconds + " seconds", cause);
119 this.seconds = seconds;
120 }
121
122
123
124
125 public int getSeconds() {
126 return seconds;
127 }
128 }
129 }