1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package org.opencastproject.caption.impl;
23
24 import static org.opencastproject.util.MimeType.mimeType;
25
26 import org.opencastproject.caption.api.Caption;
27 import org.opencastproject.caption.api.CaptionConverter;
28 import org.opencastproject.caption.api.CaptionConverterException;
29 import org.opencastproject.caption.api.CaptionService;
30 import org.opencastproject.caption.api.UnsupportedCaptionFormatException;
31 import org.opencastproject.job.api.AbstractJobProducer;
32 import org.opencastproject.job.api.Job;
33 import org.opencastproject.mediapackage.MediaPackageElement;
34 import org.opencastproject.mediapackage.MediaPackageElementBuilder;
35 import org.opencastproject.mediapackage.MediaPackageElementBuilderFactory;
36 import org.opencastproject.mediapackage.MediaPackageElementFlavor;
37 import org.opencastproject.mediapackage.MediaPackageElementParser;
38 import org.opencastproject.mediapackage.MediaPackageException;
39 import org.opencastproject.security.api.OrganizationDirectoryService;
40 import org.opencastproject.security.api.SecurityService;
41 import org.opencastproject.security.api.UserDirectoryService;
42 import org.opencastproject.serviceregistry.api.ServiceRegistry;
43 import org.opencastproject.serviceregistry.api.ServiceRegistryException;
44 import org.opencastproject.util.IoSupport;
45 import org.opencastproject.util.LoadUtil;
46 import org.opencastproject.util.NotFoundException;
47 import org.opencastproject.workspace.api.Workspace;
48
49 import org.apache.commons.io.FilenameUtils;
50 import org.apache.commons.io.IOUtils;
51 import org.apache.commons.lang3.StringUtils;
52 import org.osgi.framework.InvalidSyntaxException;
53 import org.osgi.framework.ServiceReference;
54 import org.osgi.service.cm.ConfigurationException;
55 import org.osgi.service.cm.ManagedService;
56 import org.osgi.service.component.ComponentContext;
57 import org.osgi.service.component.annotations.Activate;
58 import org.osgi.service.component.annotations.Component;
59 import org.osgi.service.component.annotations.Reference;
60 import org.slf4j.Logger;
61 import org.slf4j.LoggerFactory;
62
63 import java.io.ByteArrayInputStream;
64 import java.io.ByteArrayOutputStream;
65 import java.io.File;
66 import java.io.FileInputStream;
67 import java.io.FileNotFoundException;
68 import java.io.IOException;
69 import java.net.URI;
70 import java.util.Arrays;
71 import java.util.Dictionary;
72 import java.util.HashMap;
73 import java.util.List;
74
75 import javax.activation.FileTypeMap;
76
77
78
79
80
81
82
83 @Component(
84 immediate = true,
85 service = { CaptionService.class,ManagedService.class },
86 property = {
87 "service.description=Caption Converter Service",
88 "service.pid=org.opencastproject.caption.impl.CaptionServiceImpl"
89 }
90 )
91 public class CaptionServiceImpl extends AbstractJobProducer implements CaptionService, ManagedService {
92
93
94
95
96 public CaptionServiceImpl() {
97 super(JOB_TYPE);
98 }
99
100
101 private static final Logger logger = LoggerFactory.getLogger(CaptionServiceImpl.class);
102
103
104 private enum Operation {
105 Convert, ConvertWithLanguage
106 };
107
108
109 public static final String COLLECTION = "captions";
110
111
112 public static final float DEFAULT_CAPTION_JOB_LOAD = 0.1f;
113
114
115 public static final String CAPTION_JOB_LOAD_KEY = "job.load.caption";
116
117
118 private float captionJobLoad = DEFAULT_CAPTION_JOB_LOAD;
119
120
121 protected Workspace workspace;
122
123
124 protected ServiceRegistry serviceRegistry;
125
126
127 protected SecurityService securityService = null;
128
129
130 protected UserDirectoryService userDirectoryService = null;
131
132
133 protected OrganizationDirectoryService organizationDirectoryService = null;
134
135
136 protected ComponentContext componentContext = null;
137
138
139
140
141
142
143
144 @Override
145 @Activate
146 public void activate(ComponentContext componentContext) {
147 super.activate(componentContext);
148 this.componentContext = componentContext;
149 }
150
151
152
153
154
155
156
157 @Override
158 public Job convert(MediaPackageElement input, String inputFormat, String outputFormat)
159 throws UnsupportedCaptionFormatException,
160 CaptionConverterException, MediaPackageException {
161
162 if (input == null) {
163 throw new IllegalArgumentException("Input catalog can't be null");
164 }
165 if (StringUtils.isBlank(inputFormat)) {
166 throw new IllegalArgumentException("Input format is null");
167 }
168 if (StringUtils.isBlank(outputFormat)) {
169 throw new IllegalArgumentException("Output format is null");
170 }
171
172 try {
173 return serviceRegistry.createJob(JOB_TYPE, Operation.Convert.toString(),
174 Arrays.asList(MediaPackageElementParser.getAsXml(input), inputFormat, outputFormat), captionJobLoad);
175 } catch (ServiceRegistryException e) {
176 throw new CaptionConverterException("Unable to create a job", e);
177 }
178 }
179
180
181
182
183
184
185
186 @Override
187 public Job convert(MediaPackageElement input, String inputFormat, String outputFormat, String language)
188 throws UnsupportedCaptionFormatException, CaptionConverterException, MediaPackageException {
189
190 if (input == null) {
191 throw new IllegalArgumentException("Input catalog can't be null");
192 }
193 if (StringUtils.isBlank(inputFormat)) {
194 throw new IllegalArgumentException("Input format is null");
195 }
196 if (StringUtils.isBlank(outputFormat)) {
197 throw new IllegalArgumentException("Output format is null");
198 }
199 if (StringUtils.isBlank(language)) {
200 throw new IllegalArgumentException("Language format is null");
201 }
202
203 try {
204 return serviceRegistry.createJob(JOB_TYPE, Operation.ConvertWithLanguage.toString(),
205 Arrays.asList(MediaPackageElementParser.getAsXml(input), inputFormat, outputFormat, language),
206 captionJobLoad);
207 } catch (ServiceRegistryException e) {
208 throw new CaptionConverterException("Unable to create a job", e);
209 }
210 }
211
212
213
214
215
216
217 protected MediaPackageElement convert(Job job, MediaPackageElement input, String inputFormat, String outputFormat,
218 String language)
219 throws UnsupportedCaptionFormatException, CaptionConverterException, MediaPackageException {
220 try {
221
222
223 if (input == null) {
224 throw new IllegalArgumentException("Input element can't be null");
225 }
226 if (StringUtils.isBlank(inputFormat)) {
227 throw new IllegalArgumentException("Input format is null");
228 }
229 if (StringUtils.isBlank(outputFormat)) {
230 throw new IllegalArgumentException("Output format is null");
231 }
232
233
234 File captionsFile;
235 try {
236 captionsFile = workspace.get(input.getURI());
237 } catch (NotFoundException e) {
238 throw new CaptionConverterException("Requested media package element " + input + " could not be found.");
239 } catch (IOException e) {
240 throw new CaptionConverterException("Requested media package element " + input + "could not be accessed.");
241 }
242
243 logger.debug("Atempting to convert from {} to {}...", inputFormat, outputFormat);
244
245 List<Caption> collection = null;
246 try {
247 collection = importCaptions(captionsFile, inputFormat, language);
248 logger.debug("Parsing to collection succeeded.");
249 } catch (UnsupportedCaptionFormatException e) {
250 throw new UnsupportedCaptionFormatException(inputFormat);
251 } catch (CaptionConverterException e) {
252 throw e;
253 }
254
255 URI exported;
256 try {
257 exported = exportCaptions(collection,
258 job.getId() + "." + FilenameUtils.getExtension(captionsFile.getAbsolutePath()), outputFormat, language);
259 logger.debug("Exporting captions succeeding.");
260 } catch (UnsupportedCaptionFormatException e) {
261 throw new UnsupportedCaptionFormatException(outputFormat);
262 } catch (IOException e) {
263 throw new CaptionConverterException("Could not export caption collection.", e);
264 }
265
266
267 CaptionConverter converter = getCaptionConverter(outputFormat);
268 MediaPackageElementBuilder elementBuilder = MediaPackageElementBuilderFactory.newInstance().newElementBuilder();
269 MediaPackageElement mpe = elementBuilder.elementFromURI(exported, converter.getElementType(),
270 new MediaPackageElementFlavor(
271 "captions", outputFormat + (language == null ? "" : "+" + language)));
272 if (mpe.getMimeType() == null) {
273 String[] mimetype = FileTypeMap.getDefaultFileTypeMap().getContentType(exported.getPath()).split("/");
274 mpe.setMimeType(mimeType(mimetype[0], mimetype[1]));
275 }
276
277 if (language != null && !isNumeric(language)) {
278 mpe.addTag("lang:" + language);
279 }
280
281 return mpe;
282
283 } catch (Exception e) {
284 logger.warn("Error converting captions in " + input, e);
285 if (e instanceof CaptionConverterException) {
286 throw (CaptionConverterException) e;
287 } else if (e instanceof UnsupportedCaptionFormatException) {
288 throw (UnsupportedCaptionFormatException) e;
289 } else {
290 throw new CaptionConverterException(e);
291 }
292 }
293 }
294
295
296
297
298
299
300 @Override
301 public String[] getLanguageList(MediaPackageElement input, String format) throws UnsupportedCaptionFormatException,
302 CaptionConverterException {
303
304 if (format == null) {
305 throw new UnsupportedCaptionFormatException("<null>");
306 }
307 CaptionConverter converter = getCaptionConverter(format);
308 if (converter == null) {
309 throw new UnsupportedCaptionFormatException(format);
310 }
311
312 File captions;
313 try {
314 captions = workspace.get(input.getURI());
315 } catch (NotFoundException e) {
316 throw new CaptionConverterException("Requested media package element " + input + " could not be found.");
317 } catch (IOException e) {
318 throw new CaptionConverterException("Requested media package element " + input + "could not be accessed.");
319 }
320
321 FileInputStream stream = null;
322 String[] languageList;
323 try {
324 stream = new FileInputStream(captions);
325 languageList = converter.getLanguageList(stream);
326 } catch (FileNotFoundException e) {
327 throw new CaptionConverterException("Requested file " + captions + "could not be found.");
328 } finally {
329 IoSupport.closeQuietly(stream);
330 }
331
332 return languageList == null ? new String[0] : languageList;
333 }
334
335
336
337
338 protected HashMap<String, CaptionConverter> getAvailableCaptionConverters() {
339 HashMap<String, CaptionConverter> captionConverters = new HashMap<String, CaptionConverter>();
340 ServiceReference[] refs = null;
341 try {
342 refs = componentContext.getBundleContext().getServiceReferences(CaptionConverter.class.getName(), null);
343 } catch (InvalidSyntaxException e) {
344
345 }
346
347 if (refs != null) {
348 for (ServiceReference ref : refs) {
349 CaptionConverter converter = (CaptionConverter) componentContext.getBundleContext().getService(ref);
350 String format = (String) ref.getProperty("caption.format");
351 if (captionConverters.containsKey(format)) {
352 logger.warn("Caption converter with format {} has already been registered. Ignoring second definition.",
353 format);
354 } else {
355 captionConverters.put((String) ref.getProperty("caption.format"), converter);
356 }
357 }
358 }
359
360 return captionConverters;
361 }
362
363
364
365
366
367
368
369
370
371
372
373 protected CaptionConverter getCaptionConverter(String formatName) {
374 ServiceReference[] ref = null;
375 try {
376 ref = componentContext.getBundleContext().getServiceReferences(CaptionConverter.class.getName(),
377 "(caption.format=" + formatName + ")");
378 } catch (InvalidSyntaxException e) {
379 throw new RuntimeException(e);
380 }
381 if (ref == null) {
382 logger.warn("No caption format available for {}.", formatName);
383 return null;
384 }
385 if (ref.length > 1) {
386 logger.warn("Multiple references for caption format {}! Returning first service reference.", formatName);
387 }
388 CaptionConverter converter = (CaptionConverter) componentContext.getBundleContext().getService(ref[0]);
389 return converter;
390 }
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407 private List<Caption> importCaptions(File input, String inputFormat, String language)
408 throws UnsupportedCaptionFormatException, CaptionConverterException {
409
410 CaptionConverter converter = getCaptionConverter(inputFormat);
411 if (converter == null) {
412 logger.error("No available caption format found for {}.", inputFormat);
413 throw new UnsupportedCaptionFormatException(inputFormat);
414 }
415
416 FileInputStream fileStream = null;
417 try {
418 fileStream = new FileInputStream(input);
419 List<Caption> collection = converter.importCaption(fileStream, language);
420 return collection;
421 } catch (FileNotFoundException e) {
422 throw new CaptionConverterException("Could not locate file " + input);
423 } finally {
424 IOUtils.closeQuietly(fileStream);
425 }
426 }
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446 private URI exportCaptions(List<Caption> captions, String outputName, String outputFormat, String language)
447 throws UnsupportedCaptionFormatException, IOException {
448 CaptionConverter converter = getCaptionConverter(outputFormat);
449 if (converter == null) {
450 logger.error("No available caption format found for {}.", outputFormat);
451 throw new UnsupportedCaptionFormatException(outputFormat);
452 }
453
454
455 ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
456 try {
457 converter.exportCaption(outputStream, captions, language);
458 } catch (IOException e) {
459
460 }
461 ByteArrayInputStream in = new ByteArrayInputStream(outputStream.toByteArray());
462 return workspace.putInCollection(COLLECTION, outputName + "." + converter.getExtension(), in);
463 }
464
465 private boolean isNumeric(String str) {
466 try {
467 Integer.parseInt(str);
468 } catch (NumberFormatException e) {
469 return false;
470 }
471 return true;
472 }
473
474
475
476
477
478 @Override
479 protected String process(Job job) throws Exception {
480 Operation op = null;
481 String operation = job.getOperation();
482 List<String> arguments = job.getArguments();
483 try {
484 op = Operation.valueOf(operation);
485
486 MediaPackageElement catalog = MediaPackageElementParser.getFromXml(arguments.get(0));
487 String inputFormat = arguments.get(1);
488 String outputFormat = arguments.get(2);
489
490 MediaPackageElement resultingCatalog = null;
491
492 switch (op) {
493 case Convert:
494 resultingCatalog = convert(job, catalog, inputFormat, outputFormat, null);
495 return MediaPackageElementParser.getAsXml(resultingCatalog);
496 case ConvertWithLanguage:
497 String language = arguments.get(3);
498 resultingCatalog = convert(job, catalog, inputFormat, outputFormat, language);
499 return MediaPackageElementParser.getAsXml(resultingCatalog);
500 default:
501 throw new IllegalStateException("Don't know how to handle operation '" + operation + "'");
502 }
503 } catch (IllegalArgumentException e) {
504 throw new ServiceRegistryException("This service can't handle operations of type '" + op + "'", e);
505 } catch (IndexOutOfBoundsException e) {
506 throw new ServiceRegistryException("This argument list for operation '" + op + "' does not meet expectations", e);
507 } catch (Exception e) {
508 throw new ServiceRegistryException("Error handling operation '" + op + "'", e);
509 }
510 }
511
512
513
514
515 @Reference
516 protected void setWorkspace(Workspace workspace) {
517 this.workspace = workspace;
518 }
519
520
521
522
523 @Reference
524 protected void setServiceRegistry(ServiceRegistry serviceRegistry) {
525 this.serviceRegistry = serviceRegistry;
526 }
527
528
529
530
531
532
533
534 @Reference
535 public void setSecurityService(SecurityService securityService) {
536 this.securityService = securityService;
537 }
538
539
540
541
542
543
544
545 @Reference
546 public void setUserDirectoryService(UserDirectoryService userDirectoryService) {
547 this.userDirectoryService = userDirectoryService;
548 }
549
550
551
552
553
554
555
556 @Reference
557 public void setOrganizationDirectoryService(OrganizationDirectoryService organizationDirectory) {
558 this.organizationDirectoryService = organizationDirectory;
559 }
560
561
562
563
564
565
566 @Override
567 protected SecurityService getSecurityService() {
568 return securityService;
569 }
570
571
572
573
574
575
576 @Override
577 protected OrganizationDirectoryService getOrganizationDirectoryService() {
578 return organizationDirectoryService;
579 }
580
581
582
583
584
585
586 @Override
587 protected UserDirectoryService getUserDirectoryService() {
588 return userDirectoryService;
589 }
590
591
592
593
594
595
596 @Override
597 protected ServiceRegistry getServiceRegistry() {
598 return serviceRegistry;
599 }
600
601 @Override
602 public void updated(@SuppressWarnings("rawtypes") Dictionary properties) throws ConfigurationException {
603 captionJobLoad = LoadUtil.getConfiguredLoadValue(properties, CAPTION_JOB_LOAD_KEY, DEFAULT_CAPTION_JOB_LOAD,
604 serviceRegistry);
605 }
606
607 }