1 package org.daisy.pipeline.tts.impl;
2
3 import java.io.IOException;
4 import java.io.StringReader;
5 import java.util.Collections;
6 import java.util.HashMap;
7 import java.util.Map;
8
9 import javax.sound.sampled.AudioFileFormat;
10 import javax.xml.transform.sax.SAXSource;
11
12 import net.sf.saxon.s9api.Processor;
13 import net.sf.saxon.s9api.SaxonApiException;
14 import net.sf.saxon.s9api.XdmNode;
15
16 import org.daisy.pipeline.audio.AudioEncoder;
17 import org.daisy.pipeline.audio.AudioServices;
18 import org.daisy.pipeline.tts.config.ConfigReader;
19 import org.daisy.pipeline.tts.config.DynamicPropertiesExtension;
20 import org.daisy.pipeline.tts.config.VoiceConfigExtension;
21 import org.daisy.pipeline.tts.TTSLog;
22 import org.daisy.pipeline.tts.TTSRegistry;
23 import org.daisy.pipeline.tts.VoiceManager;
24 import org.daisy.pipeline.webservice.restlet.WebServiceExtension;
25
26 import org.restlet.routing.Router;
27
28 import org.osgi.service.component.annotations.Component;
29 import org.osgi.service.component.annotations.Reference;
30 import org.osgi.service.component.annotations.ReferenceCardinality;
31 import org.osgi.service.component.annotations.ReferencePolicy;
32
33 import org.slf4j.Logger;
34
35 import org.xml.sax.InputSource;
36
37 @Component(
38 name = "voices-web-service-extension",
39 service = { WebServiceExtension.class }
40 )
41 public class VoicesWebServiceExtension implements WebServiceExtension {
42
43 static final String VOICES_ROUTE = "/voices";
44 static final String VOICE_ROUTE = "/voices/{id}";
45 static final String VOICE_PREVIEW_ROUTE = "/voices/{id}/preview";
46
47 private TTSRegistry ttsRegistry;
48
49 @Reference(
50 name = "TTSRegistry",
51 unbind = "-",
52 service = TTSRegistry.class,
53 cardinality = ReferenceCardinality.MANDATORY,
54 policy = ReferencePolicy.STATIC
55 )
56 protected void setTTSRegistry(TTSRegistry registry) {
57 ttsRegistry = registry;
58 }
59
60 private AudioServices audioServices;
61
62 @Reference(
63 name = "AudioServices",
64 unbind = "-",
65 service = AudioServices.class,
66 cardinality = ReferenceCardinality.MANDATORY,
67 policy = ReferencePolicy.STATIC
68 )
69 protected void setAudioServices(AudioServices services) {
70 audioServices = services;
71 }
72
73 static final String CONTEXT_ATTRIBUTE_KEY = "voices-web-service-extension";
74
75 public void attachTo(Router router) {
76 router.getContext().getAttributes().put(CONTEXT_ATTRIBUTE_KEY, this);
77 router.attach(VOICES_ROUTE, VoicesResource.class);
78 router.attach(VOICE_ROUTE, VoiceResource.class);
79 router.attach(VOICE_PREVIEW_ROUTE, VoicePreviewResource.class);
80 }
81
82 private VoiceManager rememberVoiceManager = null;
83 private Map<String,String> rememberProperties = null;
84
85 boolean hasRememberedVoiceManager() {
86 return rememberVoiceManager != null;
87 }
88
89 VoiceManager getRememberedVoiceManager(Map<String,String> properties) {
90 if (rememberProperties == null)
91 return null;
92 if (rememberProperties.equals(properties))
93 return rememberVoiceManager;
94 else
95 return null;
96 }
97
98 VoiceManager getVoiceManager(Map<String,String> properties, XdmNode configXML, Logger logger) {
99 VoiceConfigExtension voiceConfigExt = new VoiceConfigExtension();
100 DynamicPropertiesExtension propsExt = new DynamicPropertiesExtension();
101 new ConfigReader(saxonProcessor, configXML, properties, voiceConfigExt, propsExt);
102 if (configXML != null) {
103 logger.debug("Voice configuration XML:\n" + configXML);
104 logger.debug("Parsed voice configuration:\n" + voiceConfigExt.getVoiceDeclarations());
105 }
106 Map<String,String> dynProperties = propsExt.getDynamicProperties();
107 synchronized (this) {
108 if (dynProperties != null && !dynProperties.isEmpty()) {
109 properties = new HashMap<>(properties);
110 properties.putAll(dynProperties);
111
112 rememberProperties = null;
113 rememberVoiceManager = null;
114 } else if (rememberProperties != null && rememberProperties.equals(properties))
115 return rememberVoiceManager;
116 else
117 rememberProperties = properties;
118 }
119 VoiceManager m = new VoiceManager(
120 ttsRegistry.getWorkingEngines(properties, new TTSLog(logger), logger),
121 voiceConfigExt.getVoiceDeclarations());
122 if (rememberProperties != null)
123 rememberVoiceManager = m;
124 return m;
125 }
126
127 AudioEncoder getAudioEncoder(AudioFileFormat.Type audioFileType) {
128 return audioServices.newEncoder(audioFileType, Collections.emptyMap()).orElse(null);
129 }
130
131 private static final Processor saxonProcessor = new Processor(false);
132
133 XdmNode getConfigXML(String ttsConfig) throws IOException, SaxonApiException {
134 if (ttsConfig == null)
135 return null;
136 return saxonProcessor.newDocumentBuilder().build(
137 new SAXSource(new InputSource(new StringReader(ttsConfig))));
138 }
139
140 }