View Javadoc
1   /*
2    * Licensed to The Apereo Foundation under one or more contributor license
3    * agreements. See the NOTICE file distributed with this work for additional
4    * information regarding copyright ownership.
5    *
6    *
7    * The Apereo Foundation licenses this file to you under the Educational
8    * Community License, Version 2.0 (the "License"); you may not use this file
9    * except in compliance with the License. You may obtain a copy of the License
10   * at:
11   *
12   *   http://opensource.org/licenses/ecl2.txt
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
17   * License for the specific language governing permissions and limitations under
18   * the License.
19   *
20   */
21  
22  package org.opencastproject.composer.remote;
23  
24  import org.opencastproject.composer.api.ComposerService;
25  import org.opencastproject.composer.api.EncoderException;
26  import org.opencastproject.composer.api.EncodingProfile;
27  import org.opencastproject.composer.api.EncodingProfileBuilder;
28  import org.opencastproject.composer.api.EncodingProfileImpl;
29  import org.opencastproject.composer.api.EncodingProfileList;
30  import org.opencastproject.composer.api.LaidOutElement;
31  import org.opencastproject.composer.layout.Dimension;
32  import org.opencastproject.composer.layout.Serializer;
33  import org.opencastproject.job.api.Job;
34  import org.opencastproject.job.api.JobParser;
35  import org.opencastproject.mediapackage.Attachment;
36  import org.opencastproject.mediapackage.MediaPackageElementParser;
37  import org.opencastproject.mediapackage.MediaPackageException;
38  import org.opencastproject.mediapackage.Track;
39  import org.opencastproject.security.api.TrustedHttpClient;
40  import org.opencastproject.serviceregistry.api.RemoteBase;
41  import org.opencastproject.serviceregistry.api.ServiceRegistry;
42  import org.opencastproject.smil.entity.api.Smil;
43  import org.opencastproject.util.data.Option;
44  
45  import org.apache.commons.io.IOUtils;
46  import org.apache.commons.lang3.StringUtils;
47  import org.apache.http.HttpResponse;
48  import org.apache.http.HttpStatus;
49  import org.apache.http.client.entity.UrlEncodedFormEntity;
50  import org.apache.http.client.methods.HttpGet;
51  import org.apache.http.client.methods.HttpPost;
52  import org.apache.http.message.BasicNameValuePair;
53  import org.apache.http.util.EntityUtils;
54  import org.osgi.service.component.annotations.Component;
55  import org.osgi.service.component.annotations.Reference;
56  import org.slf4j.Logger;
57  import org.slf4j.LoggerFactory;
58  
59  import java.io.IOException;
60  import java.nio.charset.Charset;
61  import java.util.ArrayList;
62  import java.util.Arrays;
63  import java.util.List;
64  import java.util.Locale;
65  import java.util.Map;
66  import java.util.Map.Entry;
67  import java.util.stream.Collectors;
68  
69  /**
70   * Proxies a set of remote composer services for use as a JVM-local service. Remote services are selected at random.
71   */
72  @Component(
73    property = {
74      "service.description=Composer (Encoder) Remote Service Proxy"
75    },
76    immediate = true,
77    service = { ComposerService.class }
78  )
79  public class ComposerServiceRemoteImpl extends RemoteBase implements ComposerService {
80  
81    /** The logger */
82    private static final Logger logger = LoggerFactory.getLogger(ComposerServiceRemoteImpl.class);
83  
84    public ComposerServiceRemoteImpl() {
85      super(JOB_TYPE);
86    }
87  
88    /**
89     * Sets the trusted http client
90     *
91     * @param client
92     */
93    @Override
94    @Reference
95    public void setTrustedHttpClient(TrustedHttpClient client) {
96      this.client = client;
97    }
98  
99    /**
100    * Sets the remote service manager.
101    *
102    * @param remoteServiceManager
103    */
104   @Override
105   @Reference
106   public void setRemoteServiceManager(ServiceRegistry remoteServiceManager) {
107     this.remoteServiceManager = remoteServiceManager;
108   }
109 
110   /**
111    * {@inheritDoc}
112    *
113    * @see org.opencastproject.composer.api.ComposerService#encode(org.opencastproject.mediapackage.Track,
114    *      java.lang.String)
115    */
116   @Override
117   public Job encode(Track sourceTrack, String profileId) throws EncoderException {
118     HttpPost post = new HttpPost("/encode");
119     try {
120       List<BasicNameValuePair> params = new ArrayList<>();
121       params.add(new BasicNameValuePair("sourceTrack", MediaPackageElementParser.getAsXml(sourceTrack)));
122       params.add(new BasicNameValuePair("profileId", profileId));
123       post.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
124     } catch (Exception e) {
125       throw new EncoderException("Unable to assemble a remote composer request for track " + sourceTrack, e);
126     }
127     HttpResponse response = null;
128     try {
129       response = getResponse(post);
130       if (response != null) {
131         String content = EntityUtils.toString(response.getEntity());
132         Job r = JobParser.parseJob(content);
133         logger.info("Encoding job {} started on a remote composer", r.getId());
134         return r;
135       }
136     } catch (Exception e) {
137       throw new EncoderException("Unable to encode track " + sourceTrack + " using a remote composer service", e);
138     } finally {
139       closeConnection(response);
140     }
141     throw new EncoderException("Unable to encode track " + sourceTrack + " using a remote composer service");
142   }
143 
144   /**
145    * {@inheritDoc}
146    */
147   @Override
148   public Job parallelEncode(Track sourceTrack, String profileId) throws EncoderException {
149     HttpPost post = new HttpPost("/parallelencode");
150     try {
151       List<BasicNameValuePair> params = new ArrayList<>();
152       params.add(new BasicNameValuePair("sourceTrack", MediaPackageElementParser.getAsXml(sourceTrack)));
153       params.add(new BasicNameValuePair("profileId", profileId));
154       post.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
155     } catch (Exception e) {
156       throw new EncoderException("Unable to assemble a remote composer request for track " + sourceTrack, e);
157     }
158     HttpResponse response = null;
159     try {
160       response = getResponse(post);
161       if (response != null) {
162         String content = EntityUtils.toString(response.getEntity());
163         Job r = JobParser.parseJob(content);
164         logger.info("Encoding job {} started on a remote composer", r.getId());
165         return r;
166       }
167     } catch (Exception e) {
168       throw new EncoderException("Unable to encode track " + sourceTrack + " using a remote composer service", e);
169     } finally {
170       closeConnection(response);
171     }
172     throw new EncoderException("Unable to encode track " + sourceTrack + " using a remote composer service");
173   }
174 
175   /**
176    * {@inheritDoc}
177    *
178    * @see org.opencastproject.composer.api.ComposerService#trim(Track, String, long, long)
179    */
180   @Override
181   public Job trim(Track sourceTrack, String profileId, long start, long duration) throws EncoderException {
182     HttpPost post = new HttpPost("/trim");
183     try {
184       List<BasicNameValuePair> params = new ArrayList<>();
185       params.add(new BasicNameValuePair("sourceTrack", MediaPackageElementParser.getAsXml(sourceTrack)));
186       params.add(new BasicNameValuePair("profileId", profileId));
187       params.add(new BasicNameValuePair("start", Long.toString(start)));
188       params.add(new BasicNameValuePair("duration", Long.toString(duration)));
189       post.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
190     } catch (Exception e) {
191       throw new EncoderException("Unable to assemble a remote composer request for track " + sourceTrack, e);
192     }
193     HttpResponse response = null;
194     try {
195       response = getResponse(post);
196       if (response != null) {
197         String content = EntityUtils.toString(response.getEntity());
198         Job r = JobParser.parseJob(content);
199         logger.info("Trimming job {} started on a remote composer", r.getId());
200         return r;
201       }
202     } catch (Exception e) {
203       throw new EncoderException("Unable to trim track " + sourceTrack + " using a remote composer service", e);
204     } finally {
205       closeConnection(response);
206     }
207     throw new EncoderException("Unable to trim track " + sourceTrack + " using a remote composer service");
208   }
209 
210   /**
211    * {@inheritDoc}
212    *
213    * @see org.opencastproject.composer.api.ComposerService#mux(org.opencastproject.mediapackage.Track,
214    *      org.opencastproject.mediapackage.Track, java.lang.String)
215    */
216   @Override
217   public Job mux(Track sourceVideoTrack, Track sourceAudioTrack, String profileId) throws EncoderException {
218     HttpPost post = new HttpPost("/mux");
219     try {
220       List<BasicNameValuePair> params = new ArrayList<BasicNameValuePair>();
221       params.add(new BasicNameValuePair("videoSourceTrack", MediaPackageElementParser.getAsXml(sourceVideoTrack)));
222       params.add(new BasicNameValuePair("audioSourceTrack", MediaPackageElementParser.getAsXml(sourceAudioTrack)));
223       params.add(new BasicNameValuePair("profileId", profileId));
224       post.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
225     } catch (Exception e) {
226       throw new EncoderException("Unable to assemble a remote composer request", e);
227     }
228     HttpResponse response = null;
229     try {
230       response = getResponse(post);
231       if (response != null) {
232         String content = EntityUtils.toString(response.getEntity());
233         Job r = JobParser.parseJob(content);
234         logger.info("Muxing job {} started on a remote composer", r.getId());
235         return r;
236       }
237     } catch (IOException e) {
238       throw new EncoderException(e);
239     } finally {
240       closeConnection(response);
241     }
242     throw new EncoderException("Unable to mux tracks " + sourceVideoTrack + " and " + sourceAudioTrack
243             + " using a remote composer");
244   }
245 
246   /**
247    * {@inheritDoc}
248    *
249    * @see org.opencastproject.composer.api.ComposerService#mux(java.util.Map, java.lang.String)
250    */
251   @Override
252   public Job mux(Map<String, Track> sourceTracks, String profileId) throws EncoderException {
253     HttpPost post = new HttpPost("/mux");
254     try {
255       List<BasicNameValuePair> params = new ArrayList<BasicNameValuePair>();
256       List<String> sourceTracksEntries = new ArrayList<>();
257       for (Entry<String, Track> sourceTrack : sourceTracks.entrySet()) {
258         String sourceTrackXml = MediaPackageElementParser.getAsXml(sourceTrack.getValue());
259         sourceTracksEntries.add(StringUtils.join(sourceTrack.getKey(), "#=#", sourceTrackXml));
260       }
261       params.add(new BasicNameValuePair("sourceTracks", StringUtils.join(sourceTracksEntries, "#|#")));
262       params.add(new BasicNameValuePair("profileId", profileId));
263       post.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
264     } catch (Exception e) {
265       throw new EncoderException("Unable to assemble a remote composer request", e);
266     }
267     HttpResponse response = null;
268     try {
269       response = getResponse(post);
270       if (response != null) {
271         String content = EntityUtils.toString(response.getEntity());
272         Job r = JobParser.parseJob(content);
273         logger.info("Muxing job {} started on a remote composer", r.getId());
274         return r;
275       }
276     } catch (IOException e) {
277       throw new EncoderException(e);
278     } finally {
279       closeConnection(response);
280     }
281     throw new EncoderException("Unable to mux tracks " + sourceTracks.entrySet().stream()
282         .map(entry -> String.format("%s: %s", entry.getKey(), entry.getValue().getIdentifier())).collect(
283         Collectors.joining(", ")) + " using a remote composer");
284   }
285 
286   /**
287    * {@inheritDoc}
288    *
289    * @see org.opencastproject.composer.api.ComposerService#getProfile(java.lang.String)
290    */
291   @Override
292   public EncodingProfile getProfile(String profileId) {
293     HttpGet get = new HttpGet("/profile/" + profileId + ".xml");
294     HttpResponse response = null;
295     try {
296       response = getResponse(get, HttpStatus.SC_OK, HttpStatus.SC_NOT_FOUND);
297       if (response != null && response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
298         return EncodingProfileBuilder.getInstance().parseProfile(response.getEntity().getContent());
299       } else {
300         return null;
301       }
302     } catch (Exception e) {
303       throw new RuntimeException(e);
304     } finally {
305       closeConnection(response);
306     }
307   }
308 
309   /**
310    * {@inheritDoc}
311    */
312   @Override
313   public Job image(Track sourceTrack, String profileId, double... times) throws EncoderException {
314     HttpPost post = new HttpPost("/image");
315     try {
316       List<BasicNameValuePair> params = new ArrayList<BasicNameValuePair>();
317       params.add(new BasicNameValuePair("sourceTrack", MediaPackageElementParser.getAsXml(sourceTrack)));
318       params.add(new BasicNameValuePair("profileId", profileId));
319       params.add(new BasicNameValuePair("time", buildTimeArray(times)));
320       post.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
321     } catch (Exception e) {
322       throw new EncoderException(e);
323     }
324     HttpResponse response = null;
325     try {
326       response = getResponse(post);
327       if (response != null) {
328         Job r = JobParser.parseJob(response.getEntity().getContent());
329         logger.info("Image extraction job {} started on a remote composer", r.getId());
330         return r;
331       }
332     } catch (Exception e) {
333       throw new EncoderException(e);
334     } finally {
335       closeConnection(response);
336     }
337     throw new EncoderException("Unable to compose an image from track " + sourceTrack
338             + " using the remote composer service proxy");
339   }
340 
341   @Override
342   public List<Attachment> imageSync(Track sourceTrack, String profileId, double... times) throws EncoderException, MediaPackageException {
343     HttpPost post = new HttpPost("/imagesync");
344     try {
345       List<BasicNameValuePair> params = new ArrayList<BasicNameValuePair>();
346       params.add(new BasicNameValuePair("sourceTrack", MediaPackageElementParser.getAsXml(sourceTrack)));
347       params.add(new BasicNameValuePair("profileId", profileId));
348       params.add(new BasicNameValuePair("time", buildTimeArray(times)));
349       post.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
350     } catch (Exception e) {
351       throw new EncoderException(e);
352     }
353     HttpResponse response = null;
354     try {
355       response = getResponse(post);
356       if (response != null) {
357         final String xml = IOUtils.toString(response.getEntity().getContent(), Charset.forName("utf-8"));
358         return MediaPackageElementParser.getArrayFromXml(xml)
359             .stream().map(e -> (Attachment)e)
360             .collect(Collectors.toList());
361       }
362     } catch (Exception e) {
363       throw new EncoderException(e);
364     } finally {
365       closeConnection(response);
366     }
367     throw new EncoderException("Unable to compose an image from track " + sourceTrack
368         + " using the remote composer service proxy");
369   }
370 
371   /**
372    * {@inheritDoc}
373    *
374    * @see org.opencastproject.composer.api.ComposerService#image(Track, String, Map)
375    */
376   @Override
377   public Job image(Track sourceTrack, String profileId, Map<String, String> properties) throws EncoderException,
378           MediaPackageException {
379     HttpPost post = new HttpPost("/image");
380     try {
381       List<BasicNameValuePair> params = new ArrayList<BasicNameValuePair>();
382       params.add(new BasicNameValuePair("sourceTrack", MediaPackageElementParser.getAsXml(sourceTrack)));
383       params.add(new BasicNameValuePair("profileId", profileId));
384       if (properties != null)
385         params.add(new BasicNameValuePair("properties", mapToString(properties)));
386       post.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
387     } catch (Exception e) {
388       throw new EncoderException(e);
389     }
390     HttpResponse response = null;
391     try {
392       response = getResponse(post);
393       if (response != null) {
394         Job r = JobParser.parseJob(response.getEntity().getContent());
395         logger.info("Image extraction job {} started on a remote composer", r.getId());
396         return r;
397       }
398     } catch (Exception e) {
399       throw new EncoderException(e);
400     } finally {
401       closeConnection(response);
402     }
403     throw new EncoderException("Unable to compose an image from track " + sourceTrack
404             + " using the remote composer service proxy");
405   }
406 
407   /**
408    * {@inheritDoc}
409    *
410    * @see org.opencastproject.composer.api.ComposerService#convertImage(org.opencastproject.mediapackage.Attachment,
411    *      java.lang.String...)
412    */
413   @Override
414   public Job convertImage(Attachment image, String... profileIds) throws EncoderException, MediaPackageException {
415     HttpPost post = new HttpPost("/convertimage");
416     try {
417       List<BasicNameValuePair> params = new ArrayList<BasicNameValuePair>();
418       params.add(new BasicNameValuePair("sourceImage", MediaPackageElementParser.getAsXml(image)));
419       params.add(new BasicNameValuePair("profileId", StringUtils.join(profileIds, ',')));
420       post.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
421     } catch (Exception e) {
422       throw new EncoderException(e);
423     }
424     HttpResponse response = null;
425     try {
426       response = getResponse(post);
427       if (response != null) {
428         Job r = JobParser.parseJob(response.getEntity().getContent());
429         logger.info("Image conversion job {} started on a remote composer", r.getId());
430         return r;
431       }
432     } catch (Exception e) {
433       throw new EncoderException(e);
434     } finally {
435       closeConnection(response);
436     }
437     throw new EncoderException("Unable to convert image at " + image + " using the remote composer service proxy");
438   }
439 
440   /**
441    * {@inheritDoc}
442    *
443    * @see org.opencastproject.composer.api.ComposerService#convertImageSync(
444    *      org.opencastproject.mediapackage.Attachment, java.lang.String...)
445    */
446   @Override
447   public List<Attachment> convertImageSync(Attachment image, String... profileIds) throws EncoderException, MediaPackageException {
448     HttpPost post = new HttpPost("/convertimagesync");
449     try {
450       List<BasicNameValuePair> params = new ArrayList<BasicNameValuePair>();
451       params.add(new BasicNameValuePair("sourceImage", MediaPackageElementParser.getAsXml(image)));
452       params.add(new BasicNameValuePair("profileIds", StringUtils.join(profileIds, ',')));
453       post.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
454     } catch (Exception e) {
455       throw new EncoderException(e);
456     }
457     HttpResponse response = null;
458     try {
459       response = getResponse(post);
460       if (response != null) {
461         final String xml = IOUtils.toString(response.getEntity().getContent(), Charset.forName("utf-8"));
462         return MediaPackageElementParser.getArrayFromXml(xml).stream().map(a -> (Attachment) a).collect(Collectors.toList());
463       }
464     } catch (Exception e) {
465       throw new EncoderException(e);
466     } finally {
467       closeConnection(response);
468     }
469     throw new EncoderException("Unable to convert image at " + image + " using the remote composer service proxy");
470   }
471 
472   /**
473    * {@inheritDoc}
474    *
475    * @see org.opencastproject.composer.api.ComposerService#listProfiles()
476    */
477   @Override
478   public EncodingProfile[] listProfiles() {
479     HttpGet get = new HttpGet("/profiles.xml");
480     HttpResponse response = null;
481     try {
482       response = getResponse(get);
483       if (response != null) {
484         EncodingProfileList profileList = EncodingProfileBuilder.getInstance().parseProfileList(
485                 response.getEntity().getContent());
486         List<EncodingProfileImpl> list = profileList.getProfiles();
487         return list.toArray(new EncodingProfile[list.size()]);
488       }
489     } catch (Exception e) {
490       throw new RuntimeException(
491               "Unable to list the encoding profiles registered with the remote composer service proxy", e);
492     } finally {
493       closeConnection(response);
494     }
495     throw new RuntimeException("Unable to list the encoding profiles registered with the remote composer service proxy");
496   }
497 
498   /**
499    * Builds string containing times in seconds separated by comma.
500    *
501    * @param times
502    *          time array to be converted to string
503    * @return string represented specified time array
504    */
505   protected String buildTimeArray(double[] times) {
506     if (times.length == 0)
507       return "";
508 
509     StringBuilder builder = new StringBuilder();
510     builder.append(Double.toString(times[0]));
511     for (int i = 1; i < times.length; i++) {
512       builder.append(";" + Double.toString(times[i]));
513     }
514     return builder.toString();
515   }
516 
517   @Override
518   public Job composite(Dimension compositeTrackSize, Option<LaidOutElement<Track>> upperTrack,
519           LaidOutElement<Track> lowerTrack, Option<LaidOutElement<Attachment>> watermark, String profileId,
520           String background, String sourceAudioName) throws EncoderException, MediaPackageException {
521     HttpPost post = new HttpPost("/composite");
522     try {
523       List<BasicNameValuePair> params = new ArrayList<BasicNameValuePair>();
524       params.add(new BasicNameValuePair("compositeSize", Serializer.json(compositeTrackSize).toJson()));
525       params.add(new BasicNameValuePair("lowerTrack", MediaPackageElementParser.getAsXml(lowerTrack.getElement())));
526       params.add(new BasicNameValuePair("lowerLayout", Serializer.json(lowerTrack.getLayout()).toJson()));
527       if (upperTrack.isSome()) {
528         params.add(new BasicNameValuePair("upperTrack", MediaPackageElementParser.getAsXml(upperTrack.get()
529                 .getElement())));
530         params.add(new BasicNameValuePair("upperLayout", Serializer.json(upperTrack.get().getLayout()).toJson()));
531       }
532 
533       if (watermark.isSome()) {
534         params.add(new BasicNameValuePair("watermarkAttachment", MediaPackageElementParser.getAsXml(watermark.get()
535                 .getElement())));
536         params.add(new BasicNameValuePair("watermarkLayout", Serializer.json(watermark.get().getLayout()).toJson()));
537       }
538       params.add(new BasicNameValuePair("profileId", profileId));
539       params.add(new BasicNameValuePair("background", background));
540       params.add(new BasicNameValuePair("sourceAudioName", sourceAudioName));
541       post.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
542     } catch (Exception e) {
543       throw new EncoderException(e);
544     }
545     HttpResponse response = null;
546     try {
547       response = getResponse(post);
548       if (response != null) {
549         Job r = JobParser.parseJob(response.getEntity().getContent());
550         logger.info("Composite video job {} started on a remote composer", r.getId());
551         return r;
552       }
553     } catch (Exception e) {
554       throw new EncoderException(e);
555     } finally {
556       closeConnection(response);
557     }
558     if (upperTrack.isSome()) {
559       throw new EncoderException("Unable to composite video from track " + lowerTrack.getElement() + " and "
560               + upperTrack.get().getElement() + " using the remote composer service proxy");
561     } else {
562       throw new EncoderException("Unable to composite video from track " + lowerTrack.getElement()
563               + " using the remote composer service proxy");
564     }
565   }
566 
567   @Override
568   public Job concat(String profileId, Dimension outputDimension, boolean sameCodec, Track... tracks)
569           throws EncoderException, MediaPackageException {
570     return concat(profileId, outputDimension, -1.0f, sameCodec, tracks);
571   }
572 
573   @Override
574   public Job concat(String profileId, Dimension outputDimension, float outputFrameRate, boolean sameCodec,
575           Track... tracks)
576           throws EncoderException, MediaPackageException {
577     HttpPost post = new HttpPost("/concat");
578     try {
579       List<BasicNameValuePair> params = new ArrayList<BasicNameValuePair>();
580       params.add(new BasicNameValuePair("profileId", profileId));
581       if (outputDimension != null)
582         params.add(new BasicNameValuePair("outputDimension", Serializer.json(outputDimension).toJson()));
583       params.add(new BasicNameValuePair("outputFrameRate", String.format(Locale.US, "%f", outputFrameRate)));
584       params.add(new BasicNameValuePair("sourceTracks", MediaPackageElementParser.getArrayAsXml(Arrays.asList(tracks))));
585       if (sameCodec)
586         params.add(new BasicNameValuePair("sameCodec", "true"));
587       post.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
588     } catch (Exception e) {
589       throw new EncoderException(e);
590     }
591     HttpResponse response = null;
592     try {
593       response = getResponse(post);
594       if (response != null) {
595         Job r = JobParser.parseJob(response.getEntity().getContent());
596         logger.info("Concat video job {} started on a remote composer", r.getId());
597         return r;
598       }
599     } catch (Exception e) {
600       throw new EncoderException(e);
601     } finally {
602       closeConnection(response);
603     }
604     throw new EncoderException("Unable to concat videos from tracks " + tracks
605             + " using the remote composer service proxy");
606   }
607 
608   @Override
609   public Job imageToVideo(Attachment sourceImageAttachment, String profileId, double time) throws EncoderException,
610           MediaPackageException {
611     HttpPost post = new HttpPost("/imagetovideo");
612     try {
613       List<BasicNameValuePair> params = new ArrayList<BasicNameValuePair>();
614       params.add(new BasicNameValuePair("sourceAttachment", MediaPackageElementParser.getAsXml(sourceImageAttachment)));
615       params.add(new BasicNameValuePair("profileId", profileId));
616       params.add(new BasicNameValuePair("time", Double.toString(time)));
617       post.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
618     } catch (Exception e) {
619       throw new EncoderException(e);
620     }
621     HttpResponse response = null;
622     try {
623       response = getResponse(post);
624       if (response != null) {
625         Job r = JobParser.parseJob(response.getEntity().getContent());
626         logger.info("Image to video converting job {} started on a remote composer", r.getId());
627         return r;
628       }
629     } catch (Exception e) {
630       throw new EncoderException(e);
631     } finally {
632       closeConnection(response);
633     }
634     throw new EncoderException("Unable to convert an image to a video from attachment " + sourceImageAttachment
635             + " using the remote composer service proxy");
636   }
637 
638   @Override
639   public Job demux(Track sourceTrack, String profileId) throws EncoderException, MediaPackageException {
640     HttpPost post = new HttpPost("/demux");
641     try {
642       List<BasicNameValuePair> params = new ArrayList<BasicNameValuePair>();
643       params.add(new BasicNameValuePair("sourceTrack", MediaPackageElementParser.getAsXml(sourceTrack)));
644       params.add(new BasicNameValuePair("profileId", profileId));
645       post.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
646     } catch (Exception e) {
647       throw new EncoderException("Unable to assemble a remote demux request for track " + sourceTrack, e);
648     }
649     HttpResponse response = null;
650     try {
651       response = getResponse(post);
652       if (response != null) {
653         String content = EntityUtils.toString(response.getEntity());
654         Job r = JobParser.parseJob(content);
655         logger.info("Demuxing job {} started on a remote service ", r.getId());
656         return r;
657       }
658     } catch (Exception e) {
659       throw new EncoderException("Unable to demux track " + sourceTrack + " using a remote composer service", e);
660     } finally {
661       closeConnection(response);
662     }
663     throw new EncoderException("Unable to demux track " + sourceTrack + " using a remote composer service");
664   }
665 
666   @Override
667   public Job processSmil(Smil smil, String trackParamGroupId, String mediaType, List<String> profileIds)
668           throws EncoderException, MediaPackageException {
669     HttpPost post = new HttpPost("/processsmil");
670     try {
671       List<BasicNameValuePair> params = new ArrayList<BasicNameValuePair>();
672       params.add(new BasicNameValuePair("smilAsXml", smil.toXML()));
673       params.add(new BasicNameValuePair("trackId", trackParamGroupId));
674       params.add(new BasicNameValuePair("mediaType", mediaType));
675       params.add(new BasicNameValuePair("profileIds", StringUtils.join(profileIds, ","))); // comma separated profiles
676       post.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
677     } catch (Exception e) {
678       throw new EncoderException(e);
679     }
680     HttpResponse response = null;
681     try {
682       response = getResponse(post);
683       if (response != null) {
684         Job r = JobParser.parseJob(response.getEntity().getContent());
685         logger.info("Concat video job {} started on a remote composer", r.getId());
686         return r;
687       }
688     } catch (Exception e) {
689       throw new EncoderException(e);
690     } finally {
691       closeConnection(response);
692     }
693     throw new EncoderException("Unable to edit video group(" + trackParamGroupId + ") from smil " + smil
694             + " using the remote composer service proxy");
695   }
696 
697   @Override
698   public Job multiEncode(Track sourceTrack, List<String> profileIds) throws EncoderException, MediaPackageException {
699     HttpPost post = new HttpPost("/multiencode");
700     try {
701       List<BasicNameValuePair> params = new ArrayList<BasicNameValuePair>();
702       params.add(new BasicNameValuePair("sourceTrack", MediaPackageElementParser.getAsXml(sourceTrack)));
703       params.add(new BasicNameValuePair("profileIds", StringUtils.join(profileIds, ","))); // comma separated profiles
704       post.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
705     } catch (Exception e) {
706       throw new EncoderException("Unable to assemble a remote demux request for track " + sourceTrack, e);
707     }
708     HttpResponse response = null;
709     try {
710       response = getResponse(post);
711       if (response != null) {
712         String content = EntityUtils.toString(response.getEntity());
713         Job job = JobParser.parseJob(content);
714         logger.info("Encoding job {} started on a remote multiencode", job.getId());
715         return job;
716       }
717     } catch (Exception e) {
718       throw new EncoderException("Unable to multiencode track " + sourceTrack + " using a remote composer service", e);
719     } finally {
720       closeConnection(response);
721     }
722     throw new EncoderException("Unable to multiencode track " + sourceTrack + " using a remote composer service");
723   }
724 
725   /**
726    * Converts a Map<String, String> to s key=value\n string, suitable for the properties form parameter expected by the
727    * workflow rest endpoint.
728    *
729    * @param props
730    *          The map of strings
731    * @return the string representation
732    */
733   private String mapToString(Map<String, String> props) {
734     StringBuilder sb = new StringBuilder();
735     for (Entry<String, String> entry : props.entrySet()) {
736       sb.append(entry.getKey());
737       sb.append("=");
738       sb.append(entry.getValue());
739       sb.append("\n");
740     }
741     return sb.toString();
742   }
743 
744 }