use of org.opencastproject.timelinepreviews.api.TimelinePreviewsException in project opencast by opencast.
the class TimelinePreviewsServiceImpl method createPreviewsFFmpeg.
/**
* Executes the FFmpeg command to generate a timeline previews image
*
* @param track the track to generate the timeline previews image for
* @param seconds the length of a segment that one preview image should represent
* @param width the width of a single preview image
* @param height the height of a single preview image
* @param tileX the horizontal number of preview images that are stored in the timeline previews image
* @param tileY the vertical number of preview images that are stored in the timeline previews image
* @param duration the duration for which preview images should be generated
* @return an attachment containing the timeline previews image
* @throws TimelinePreviewsException
*/
protected Attachment createPreviewsFFmpeg(Track track, double seconds, int width, int height, int tileX, int tileY, double duration) throws TimelinePreviewsException {
// copy source file into workspace
File mediaFile;
try {
mediaFile = workspace.get(track.getURI());
} catch (NotFoundException e) {
throw new TimelinePreviewsException("Error finding the media file in the workspace", e);
} catch (IOException e) {
throw new TimelinePreviewsException("Error reading the media file in the workspace", e);
}
String imageFilePath = FilenameUtils.removeExtension(mediaFile.getAbsolutePath()) + '_' + UUID.randomUUID() + "_timelinepreviews" + outputFormat;
int exitCode = 1;
String[] command = new String[] { binary, "-loglevel", "error", "-t", String.valueOf(duration - seconds / 2.0), "-i", mediaFile.getAbsolutePath(), "-vf", "fps=1/" + seconds + ",scale=" + width + ":" + height + ",tile=" + tileX + "x" + tileY, imageFilePath };
logger.debug("Start timeline previews ffmpeg process: {}", StringUtils.join(command, " "));
logger.info("Create timeline preview images file for track '{}' at {}", track.getIdentifier(), imageFilePath);
ProcessBuilder pbuilder = new ProcessBuilder(command);
pbuilder.redirectErrorStream(true);
Process ffmpegProcess = null;
exitCode = 1;
BufferedReader errStream = null;
try {
ffmpegProcess = pbuilder.start();
errStream = new BufferedReader(new InputStreamReader(ffmpegProcess.getInputStream()));
String line = errStream.readLine();
while (line != null) {
logger.error("FFmpeg error: " + line);
line = errStream.readLine();
}
exitCode = ffmpegProcess.waitFor();
} catch (IOException ex) {
throw new TimelinePreviewsException("Starting ffmpeg process failed", ex);
} catch (InterruptedException ex) {
throw new TimelinePreviewsException("Timeline preview creation was unexpectedly interrupted", ex);
} finally {
IoSupport.closeQuietly(ffmpegProcess);
IoSupport.closeQuietly(errStream);
if (exitCode != 0) {
try {
FileUtils.forceDelete(new File(imageFilePath));
} catch (IOException e) {
// it is ok, no output file was generated by ffmpeg
}
}
}
if (exitCode != 0)
throw new TimelinePreviewsException("Generating timeline preview for track " + track.getIdentifier() + " failed: ffmpeg process exited abnormally with exit code " + exitCode);
// put timeline previews image into workspace
FileInputStream timelinepreviewsFileInputStream = null;
URI previewsFileUri = null;
try {
timelinepreviewsFileInputStream = new FileInputStream(imageFilePath);
previewsFileUri = workspace.putInCollection(COLLECTION_ID, FilenameUtils.getName(imageFilePath), timelinepreviewsFileInputStream);
logger.info("Copied the created timeline preview images file to the workspace {}", previewsFileUri.toString());
} catch (FileNotFoundException ex) {
throw new TimelinePreviewsException(String.format("Timeline previews image file '%s' not found", imageFilePath), ex);
} catch (IOException ex) {
throw new TimelinePreviewsException(String.format("Can't write timeline preview images file '%s' to workspace", imageFilePath), ex);
} catch (IllegalArgumentException ex) {
throw new TimelinePreviewsException(ex);
} finally {
IoSupport.closeQuietly(timelinepreviewsFileInputStream);
logger.info("Deleted local timeline preview images file at {}", imageFilePath);
FileUtils.deleteQuietly(new File(imageFilePath));
}
// create media package element
MediaPackageElementBuilder mpElementBuilder = MediaPackageElementBuilderFactory.newInstance().newElementBuilder();
// it is up to the workflow operation handler to set the attachment flavor
Attachment timelinepreviewsMpe = (Attachment) mpElementBuilder.elementFromURI(previewsFileUri, MediaPackageElement.Type.Attachment, track.getFlavor());
// add reference to track
timelinepreviewsMpe.referTo(track);
// add additional properties to attachment
timelinepreviewsMpe.getProperties().put("imageSizeX", String.valueOf(tileX));
timelinepreviewsMpe.getProperties().put("imageSizeY", String.valueOf(tileY));
timelinepreviewsMpe.getProperties().put("resolutionX", String.valueOf(resolutionX));
timelinepreviewsMpe.getProperties().put("resolutionY", String.valueOf(resolutionY));
// set the flavor and an ID
timelinepreviewsMpe.setFlavor(track.getFlavor());
timelinepreviewsMpe.setIdentifier(IdBuilderFactory.newInstance().newIdBuilder().createNew().compact());
return timelinepreviewsMpe;
}
use of org.opencastproject.timelinepreviews.api.TimelinePreviewsException in project opencast by opencast.
the class TimelinePreviewsServiceRemote method createTimelinePreviewImages.
/**
* Takes the given track and returns the job that will create timeline preview images using a remote service.
*
* @param sourceTrack the track to create preview images from
* @param imageCount number of preview images that will be generated
* @return a job that will create timeline preview images
* @throws MediaPackageException if the serialization of the given track fails
* @throws TimelinePreviewsException if the job can't be created for any reason
*/
@Override
public Job createTimelinePreviewImages(Track sourceTrack, int imageCount) throws MediaPackageException, TimelinePreviewsException {
HttpPost post = new HttpPost("/create");
try {
List<BasicNameValuePair> params = new ArrayList<>();
params.add(new BasicNameValuePair("track", MediaPackageElementParser.getAsXml(sourceTrack)));
params.add(new BasicNameValuePair("imageCount", Integer.toString(imageCount)));
post.setEntity(new UrlEncodedFormEntity(params));
} catch (Exception e) {
throw new TimelinePreviewsException(e);
}
HttpResponse response = null;
try {
response = getResponse(post);
if (response != null) {
try {
Job receipt = JobParser.parseJob(response.getEntity().getContent());
logger.info("Create timeline preview images from {}", sourceTrack);
return receipt;
} catch (Exception e) {
throw new TimelinePreviewsException("Unable to create timeline preview images from " + sourceTrack + " using a remote service", e);
}
}
} finally {
closeConnection(response);
}
throw new TimelinePreviewsException("Unable to create timeline preview images from " + sourceTrack + " using a remote service");
}
use of org.opencastproject.timelinepreviews.api.TimelinePreviewsException in project opencast by opencast.
the class TimelinePreviewsWorkflowOperationHandler method start.
/**
* {@inheritDoc}
*
* @see
* org.opencastproject.workflow.api.WorkflowOperationHandler#start(org.opencastproject.workflow.api.WorkflowInstance,
* org.opencastproject.job.api.JobContext)
*/
@Override
public WorkflowOperationResult start(WorkflowInstance workflowInstance, JobContext context) throws WorkflowOperationException {
MediaPackage mediaPackage = workflowInstance.getMediaPackage();
logger.info("Start timeline previews workflow operation for mediapackage {}", mediaPackage.getIdentifier().compact());
String sourceFlavorProperty = StringUtils.trimToNull(workflowInstance.getCurrentOperation().getConfiguration(SOURCE_FLAVOR_PROPERTY));
String sourceTagsProperty = StringUtils.trimToNull(workflowInstance.getCurrentOperation().getConfiguration(SOURCE_TAGS_PROPERTY));
if (StringUtils.isEmpty(sourceFlavorProperty) && StringUtils.isEmpty(sourceTagsProperty)) {
throw new WorkflowOperationException(String.format("Required property %s or %s not set", SOURCE_FLAVOR_PROPERTY, SOURCE_TAGS_PROPERTY));
}
String targetFlavorProperty = StringUtils.trimToNull(workflowInstance.getCurrentOperation().getConfiguration(TARGET_FLAVOR_PROPERTY));
if (targetFlavorProperty == null) {
throw new WorkflowOperationException(String.format("Required property %s not set", TARGET_FLAVOR_PROPERTY));
}
String targetTagsProperty = StringUtils.trimToNull(workflowInstance.getCurrentOperation().getConfiguration(TARGET_TAGS_PROPERTY));
String imageSizeArg = StringUtils.trimToNull(workflowInstance.getCurrentOperation().getConfiguration(IMAGE_SIZE_PROPERTY));
int imageSize;
if (imageSizeArg != null) {
try {
imageSize = Integer.parseInt(imageSizeArg);
} catch (NumberFormatException e) {
imageSize = DEFAULT_IMAGE_SIZE;
logger.info("No valid integer given for property {}, using default value: {}", IMAGE_SIZE_PROPERTY, DEFAULT_IMAGE_SIZE);
}
} else {
imageSize = DEFAULT_IMAGE_SIZE;
logger.info("Property {} not set, using default value: {}", IMAGE_SIZE_PROPERTY, DEFAULT_IMAGE_SIZE);
}
TrackSelector trackSelector = new TrackSelector();
for (String flavor : asList(sourceFlavorProperty)) {
trackSelector.addFlavor(flavor);
}
for (String tag : asList(sourceTagsProperty)) {
trackSelector.addTag(tag);
}
Collection<Track> sourceTracks = trackSelector.select(mediaPackage, false);
if (sourceTracks.isEmpty()) {
logger.info("No tracks found in mediapackage {} with specified {} {}", mediaPackage.getIdentifier().compact(), SOURCE_FLAVOR_PROPERTY, sourceFlavorProperty);
createResult(mediaPackage, WorkflowOperationResult.Action.SKIP);
}
List<Job> timelinepreviewsJobs = new ArrayList<Job>(sourceTracks.size());
for (Track sourceTrack : sourceTracks) {
try {
// generate timeline preview images
logger.info("Create timeline previews job for track '{}' in mediapackage '{}'", sourceTrack.getIdentifier(), mediaPackage.getIdentifier().compact());
Job timelinepreviewsJob = timelinePreviewsService.createTimelinePreviewImages(sourceTrack, imageSize);
timelinepreviewsJobs.add(timelinepreviewsJob);
} catch (MediaPackageException | TimelinePreviewsException ex) {
logger.error("Creating timeline previews job for track '{}' in media package '{}' failed with error {}", sourceTrack.getIdentifier(), mediaPackage.getIdentifier().compact(), ex.getMessage());
}
}
logger.info("Wait for timeline previews jobs for media package {}", mediaPackage.getIdentifier().compact());
if (!waitForStatus(timelinepreviewsJobs.toArray(new Job[timelinepreviewsJobs.size()])).isSuccess()) {
cleanupWorkspace(timelinepreviewsJobs);
throw new WorkflowOperationException(String.format("Timeline previews jobs for media package '%s' have not completed successfully", mediaPackage.getIdentifier().compact()));
}
try {
// copy timeline previews attachments into workspace and add them to the media package
for (Job job : timelinepreviewsJobs) {
String jobPayload = job.getPayload();
if (StringUtils.isNotEmpty(jobPayload)) {
MediaPackageElement timelinePreviewsMpe = null;
File timelinePreviewsFile = null;
try {
timelinePreviewsMpe = MediaPackageElementParser.getFromXml(jobPayload);
timelinePreviewsFile = workspace.get(timelinePreviewsMpe.getURI());
} catch (MediaPackageException ex) {
// unexpected job payload
throw new WorkflowOperationException("Can't parse timeline previews attachment from job " + job.getId());
} catch (NotFoundException ex) {
throw new WorkflowOperationException("Timeline preview images file '" + timelinePreviewsMpe.getURI() + "' not found", ex);
} catch (IOException ex) {
throw new WorkflowOperationException("Can't get workflow image file '" + timelinePreviewsMpe.getURI() + "' from workspace");
}
FileInputStream timelinePreviewsInputStream = null;
logger.info("Put timeline preview images file {} from media package {} to the media package work space", timelinePreviewsMpe.getURI(), mediaPackage.getIdentifier().compact());
try {
timelinePreviewsInputStream = new FileInputStream(timelinePreviewsFile);
String fileName = FilenameUtils.getName(timelinePreviewsMpe.getURI().getPath());
URI timelinePreviewsWfrUri = workspace.put(mediaPackage.getIdentifier().compact(), timelinePreviewsMpe.getIdentifier(), fileName, timelinePreviewsInputStream);
timelinePreviewsMpe.setURI(timelinePreviewsWfrUri);
} catch (FileNotFoundException ex) {
throw new WorkflowOperationException("Timeline preview images file " + timelinePreviewsFile.getPath() + " not found", ex);
} catch (IOException ex) {
throw new WorkflowOperationException("Can't read just created timeline preview images file " + timelinePreviewsFile.getPath(), ex);
} catch (IllegalArgumentException ex) {
throw new WorkflowOperationException(ex);
} finally {
IoSupport.closeQuietly(timelinePreviewsInputStream);
}
// set the timeline previews attachment flavor and add it to the mediapackage
MediaPackageElementFlavor targetFlavor = MediaPackageElementFlavor.parseFlavor(targetFlavorProperty);
if ("*".equals(targetFlavor.getType())) {
targetFlavor = new MediaPackageElementFlavor(timelinePreviewsMpe.getFlavor().getType(), targetFlavor.getSubtype());
}
if ("*".equals(targetFlavor.getSubtype())) {
targetFlavor = new MediaPackageElementFlavor(targetFlavor.getType(), timelinePreviewsMpe.getFlavor().getSubtype());
}
timelinePreviewsMpe.setFlavor(targetFlavor);
if (!StringUtils.isEmpty(targetTagsProperty)) {
for (String tag : asList(targetTagsProperty)) {
timelinePreviewsMpe.addTag(tag);
}
}
mediaPackage.add(timelinePreviewsMpe);
}
}
} finally {
cleanupWorkspace(timelinepreviewsJobs);
}
logger.info("Timeline previews workflow operation for mediapackage {} completed", mediaPackage.getIdentifier().compact());
return createResult(mediaPackage, WorkflowOperationResult.Action.CONTINUE);
}
use of org.opencastproject.timelinepreviews.api.TimelinePreviewsException in project opencast by opencast.
the class TimelinePreviewsServiceImpl method generatePreviewImages.
/**
* Starts generation of timeline preview images for the given video track
* and returns an attachment containing one image that contains all the
* timeline preview images.
*
* @param job
* @param track the element to analyze
* @param imageCount number of preview images that will be generated
* @return an attachment containing the resulting timeline previews image
* @throws TimelinePreviewsException
* @throws org.opencastproject.mediapackage.MediaPackageException
*/
protected Attachment generatePreviewImages(Job job, Track track, int imageCount) throws TimelinePreviewsException, MediaPackageException {
// Make sure the element can be analyzed using this analysis implementation
if (!track.hasVideo()) {
logger.error("Element {} is not a video track", track.getIdentifier());
throw new TimelinePreviewsException("Element is not a video track");
}
try {
if (track.getDuration() == null)
throw new MediaPackageException("Track " + track + " does not have a duration");
double duration = track.getDuration() / 1000.0;
double seconds = duration / (double) (imageCount);
seconds = seconds <= 0.0 ? 1.0 : seconds;
// calculate number of tiles for row and column in tiled image
int imageSize = (int) Math.ceil(Math.sqrt(imageCount));
Attachment composedImage = createPreviewsFFmpeg(track, seconds, resolutionX, resolutionY, imageSize, imageSize, duration);
if (composedImage == null)
throw new IllegalStateException("Unable to compose image");
// Set the mimetype
try {
composedImage.setMimeType(MimeTypes.parseMimeType(mimetype));
} catch (IllegalArgumentException e) {
logger.warn("Invalid mimetype provided for timeline previews image");
try {
composedImage.setMimeType(MimeTypes.fromURI(composedImage.getURI()));
} catch (UnknownFileTypeException ex) {
logger.warn("No valid mimetype could be found for timeline previews image");
}
}
composedImage.getProperties().put("imageCount", String.valueOf(imageCount));
return composedImage;
} catch (Exception e) {
logger.warn("Error creating timeline preview images for " + track, e);
if (e instanceof TimelinePreviewsException) {
throw (TimelinePreviewsException) e;
} else {
throw new TimelinePreviewsException(e);
}
}
}
Aggregations