/**
* Licensed to The Apereo Foundation under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
*
* The Apereo Foundation licenses this file to you under the Educational
* Community License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License
* at:
*
* http://opensource.org/licenses/ecl2.txt
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*
*/
package org.opencastproject.composer.impl;
import org.opencastproject.composer.api.EncoderEngine;
import org.opencastproject.composer.api.EncoderListener;
import org.opencastproject.composer.api.EncodingProfile;
import org.opencastproject.composer.api.EncodingProfile.MediaType;
import org.opencastproject.mediapackage.Track;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* Abstract base class for encoder engines.
*/
public abstract class AbstractEncoderEngine implements EncoderEngine {
/** The registered encoder listeners */
protected List<EncoderListener> listeners = new CopyOnWriteArrayList<EncoderListener>();
/** True if the engine supports multiple jobs at once */
protected boolean supportsMultithreading = false;
/** The supported profiles */
protected Map<String, EncodingProfile> supportedProfiles = null;
/** the logging facility provided by log4j */
private static Logger logger = LoggerFactory.getLogger(AbstractEncoderEngine.class.getName());
/**
* Creates a new abstract encoder engine with or without support for multiple job submission.
*
* @param supportsMultithreading
* <code>true</code> if this engine supports more than one job at a time
*/
public AbstractEncoderEngine(boolean supportsMultithreading) {
this.supportsMultithreading = supportsMultithreading;
this.supportedProfiles = new HashMap<String, EncodingProfile>();
}
/**
* {@inheritDoc}
*
* @see org.opencastproject.composer.api.EncoderEngine#addEncoderListener(org.opencastproject.composer.api.EncoderListener)
*/
public void addEncoderListener(EncoderListener listener) {
if (!listeners.contains(listener))
listeners.add(listener);
}
/**
* {@inheritDoc}
*
* @see org.opencastproject.composer.api.EncoderEngine#removeEncoderListener(org.opencastproject.composer.api.EncoderListener)
*/
public void removeEncoderListener(EncoderListener listener) {
listeners.remove(listener);
}
/**
* Returns the location of the output file.
*
* @param source
* the source file
* @param profile
* the encoding profile
* @return the output file
*/
protected abstract File getOutputFile(File source, EncodingProfile profile);
/**
* {@inheritDoc}
*
* @see org.opencastproject.composer.api.EncoderEngine#supportsMultithreading()
*/
public boolean supportsMultithreading() {
return supportsMultithreading;
}
/**
* Sets the supported profiles for this encoder engine. The method is called by the corresponding factory that knows
* about the relation between encoding engine and encoding profiles.
*
* @param profiles
* the supported profiles
*/
void setSupportedProfiles(Map<String, EncodingProfile> profiles) {
if (profiles == null) {
profiles = new HashMap<String, EncodingProfile>();
}
this.supportedProfiles = profiles;
}
/**
* {@inheritDoc}
*
* @see org.opencastproject.composer.api.EncoderEngine#supportsProfile(java.lang.String,
* org.opencastproject.composer.api.EncodingProfile.MediaType)
*/
@Override
public boolean supportsProfile(String profile, MediaType type) {
if (supportedProfiles.containsKey(profile)) {
EncodingProfile p = supportedProfiles.get(profile);
return p.isApplicableTo(type);
}
return false;
}
/**
* Downloads the track to the <code>java.io.tmpdir</code> directory if does not not already have a <code>file:</code>
* url associated.
*
* @param track
* the track object
* @return a file reference to the track
* @throws IOException
* if downloading the file fails
*
*/
protected File download(Track track) throws IOException {
if (track == null || track.getURI() == null) {
throw new IOException("Caller provided either a null track or a track without a URI");
}
if ("file".equals(track.getURI().getScheme()))
return new File(track.getURI().getPath());
// The file does not seem to be inside the local filesystem.
// Let's download it and log a warning, since this shouldn't happen.
logger.warn("Downloading track " + track.getURI().toString() + " to temp directory");
File f = File.createTempFile(track.getURI().toString(), null);
logger.info("Temporary file created at " + f.toString());
FileOutputStream fos = new FileOutputStream(f);
InputStream is = track.getURI().toURL().openStream();
byte[] bytes = new byte[2048];
while (is.read(bytes) >= 0) {
fos.write(bytes);
}
fos.flush();
fos.close();
is.close();
return f;
}
/**
* This method is called to send the <code>formatEncoded</code> event to registered encoding listeners.
*
* @param engine
* the encoding engine
* @param profile
* the media format
* @param sourceFiles
* the source files encoded
*/
protected void fireEncoded(EncoderEngine engine, EncodingProfile profile, File... sourceFiles) {
for (EncoderListener l : listeners) {
try {
l.fileEncoded(engine, profile, sourceFiles);
} catch (Throwable t) {
logger.error("Encoder listener " + l + " threw exception while handling callback");
}
}
}
/**
* This method is called to send the <code>trackEncodingFailed</code> event to registered encoding listeners.
*
* @param engine
* the encoding engine
* @param sourceFiles
* the files that were encoded
* @param profile
* the media format
* @param cause
* the reason of failure
*/
protected void fireEncodingFailed(EncoderEngine engine, EncodingProfile profile, Throwable cause, File... sourceFiles) {
for (EncoderListener l : listeners) {
try {
l.fileEncodingFailed(engine, profile, cause, sourceFiles);
} catch (Throwable t) {
logger.error("Encoder listener {} threw exception while handling callback", l);
}
}
}
/**
* This method is called to send the <code>trackEncodingProgressed</code> event to registered encoding listeners.
*
* @param engine
* the encoding engine
* @param sourceFile
* the file that is being encoded
* @param profile
* the media format
* @param progress
* the progress value
*/
protected void fireEncodingProgressed(EncoderEngine engine, File sourceFile, EncodingProfile profile, int progress) {
for (EncoderListener l : listeners) {
try {
l.fileEncodingProgressed(engine, sourceFile, profile, progress);
} catch (Throwable t) {
logger.error("Encoder listener " + l + " threw exception while handling callback");
}
}
}
}