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 				// forget everything if any dynamic properties are present
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 }