Skip to content
Snippets Groups Projects
Commit f4954dd8 authored by gnaegi's avatar gnaegi
Browse files

OO-725 rename type to resolution, use getter/setter conventions, missing i18n...

OO-725 rename type to resolution, use getter/setter conventions, missing i18n keys, fix modul initialization
parent a4f0e588
No related branches found
No related tags found
No related merge requests found
Showing
with 170 additions and 101 deletions
......@@ -148,11 +148,14 @@ public interface VideoManager {
* Create and add a new transcoding version to the video resource. The
* version is set to "isTransforming".
*
* @param video The video resource
* @param resolution The resolution for the transcoding process
* @param video
* The video resource
* @param resolution
* The resolution for the transcoding process. This is the height
* of the target video size (e.g. 720 for 720p resolution)
* @return VideoQualityVersion for this new version
*/
public abstract VideoQualityVersion addNewVersionForTranscoding(OLATResource video, String resolution);
public abstract VideoQualityVersion addNewVersionForTranscoding(OLATResource video, int resolution);
/**
* Update an already existing version or add as new version.
......
......@@ -19,11 +19,18 @@
*/
package org.olat.modules.video;
import java.util.Arrays;
import org.olat.core.configuration.AbstractSpringModule;
import org.olat.core.logging.OLog;
import org.olat.core.logging.Tracing;
import org.olat.core.util.StringHelper;
import org.olat.core.util.coordinate.CoordinatorManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.core.convert.ConversionService;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.stereotype.Service;
/**
......@@ -39,8 +46,6 @@ public class VideoModule extends AbstractSpringModule {
private static final String VIDEO_ENABLED = "video.enabled";
private static final String VIDEOCOURSENODE_ENABLED = "video.coursenode.enabled";
private static final String VIDEOTRANSCODING_ENABLED = "video.transcoding.enabled";
@Value("${video.transcoding.provider:handbrake}")
private String transcodingProvider;
@Value("${video.enabled:true}")
private boolean enabled;
......@@ -48,7 +53,18 @@ public class VideoModule extends AbstractSpringModule {
private boolean coursenodeEnabled;
@Value("${video.transcoding.enabled:false}")
private boolean transcodingEnabled;
@Value("${video.transcoding.resolutions}")
private int[] transcodingResolutions;
@Value("${video.transcoding.taskset.cpuconfig}")
private String transcodingTasksetConfig;
@Bean
public static ConversionService conversionService() {
// needed to create the transcodingResolutions property by spring
return new DefaultFormattingConversionService();
}
private static final OLog log = Tracing.createLoggerFor(VideoModule.class);
@Autowired
public VideoModule(CoordinatorManager coordinatorManager) {
......@@ -72,7 +88,33 @@ public class VideoModule extends AbstractSpringModule {
transcodingEnabled = "true".equals(enabledTranscodingObj);
}
log.info("video.enabled=" + isEnabled());
log.info("video.coursenode.enabled=" + isCoursenodeEnabled());
log.info("video.transcoding.enabled=" + isTranscodingEnabled());
log.info("video.transcoding.resolutions=" + Arrays.toString(getTranscodingResolutions()));
log.info("video.transcoding.taskset.cpuconfig=" + getTranscodingTasksetConfig());
}
/**
* @return Array of transcoding resolutions. The values represent the target
* height of the transcoded video, 1080 for 1080p video size etc.
*/
public int[] getTranscodingResolutions() {
return transcodingResolutions;
}
/**
* @return null to indicate that taskset is disabled or the -c options to control the number of cores, e.g. "0,1"
*/
public String getTranscodingTasksetConfig() {
if (StringHelper.containsNonWhitespace(transcodingTasksetConfig)) {
return transcodingTasksetConfig.trim();
} else {
return null;
}
}
@Override
protected void initFromChangedProperties() {
init();
......@@ -107,5 +149,6 @@ public class VideoModule extends AbstractSpringModule {
public void setTranscodingEnabled(boolean transcodingEnabled) {
this.transcodingEnabled = transcodingEnabled;
setStringProperty(VIDEOTRANSCODING_ENABLED, Boolean.toString(transcodingEnabled), true);
//TODO: check all video resources if there are missing versions
}
}
......@@ -40,7 +40,6 @@ import javax.imageio.ImageIO;
import org.jcodec.api.FrameGrab;
import org.jcodec.common.FileChannelWrapper;
import org.olat.core.CoreSpringFactory;
import org.olat.core.commons.services.image.Size;
import org.olat.core.commons.services.taskexecutor.TaskExecutorManager;
import org.olat.core.commons.services.video.MovieService;
......@@ -88,6 +87,8 @@ public class VideoManagerImpl implements VideoManager {
private VideoModule videoModule;
@Autowired
private RepositoryManager repositoryManager;
@Autowired
private TaskExecutorManager taskManager;
private static final OLog log = Tracing.createLoggerFor(VideoManagerImpl.class);
......@@ -327,14 +328,13 @@ public class VideoManagerImpl implements VideoManager {
//TODO: check for existing version, add option to force rebuild of all versions
Size size = getVideoSize(video);
int height = size.getHeight();
TaskExecutorManager taskManager = CoreSpringFactory.getImpl(TaskExecutorManager.class);
//TODO: add to module and admin console which version to generate
int[] resolutions = {1080, 720, 480, 360, 240, 144};
//TODO: GUI to admin console to manage transcoding resolutions
int[] resolutions = videoModule.getTranscodingResolutions();
for (int resolution : resolutions) {
if (height < resolution) {
continue;
}
VideoQualityVersion version = addNewVersionForTranscoding(video, Integer.toString(resolution));
VideoQualityVersion version = addNewVersionForTranscoding(video, resolution);
VideoTranscodingTask task = new VideoTranscodingTask(video, version);
taskManager.execute(task, null, video, null, new Date());
}
......@@ -554,10 +554,10 @@ public class VideoManagerImpl implements VideoManager {
@Override
public VideoQualityVersion addNewVersionForTranscoding(OLATResource video, String resolution) {
public VideoQualityVersion addNewVersionForTranscoding(OLATResource video, int resolution) {
List<VideoQualityVersion> versions = getQualityVersions(video);
VideoQualityVersion version = new VideoQualityVersion(resolution, null, null, VideoManagerImpl.FILETYPE_MP4);
version.setIsTransforming(true);
version.setTranscodingStatus(VideoQualityVersion.TRANSCODING_STATUS_WAITING);
versions.add(version);
// Store on disk
VFSContainer optimizedDataContainer = getOptimizedDataContainer(video);
......@@ -573,12 +573,11 @@ public class VideoManagerImpl implements VideoManager {
List<VideoQualityVersion> versions = getQualityVersions(video);
boolean found = false;
for (VideoQualityVersion existingVersion : versions) {
if (updatedVersion.getType().equals(existingVersion.getType())) {
if (updatedVersion.getResolution() == existingVersion.getResolution()) {
// update properties
existingVersion.setDimension(updatedVersion.getDimension());
existingVersion.setFileSize(updatedVersion.getFileSize());
existingVersion.setFormat(updatedVersion.getFormat());
existingVersion.setIsTransforming(updatedVersion.getIsTransforming());
existingVersion.setTranscodingStatus(updatedVersion.getTranscodingStatus());
found = true;
break;
......
......@@ -30,8 +30,6 @@ import org.olat.core.CoreSpringFactory;
import org.olat.core.commons.services.image.Size;
import org.olat.core.commons.services.taskexecutor.LongRunnable;
import org.olat.core.commons.services.taskexecutor.Sequential;
import org.olat.core.commons.services.taskexecutor.Task;
import org.olat.core.commons.services.taskexecutor.TaskAwareRunnable;
import org.olat.core.commons.services.video.MovieService;
import org.olat.core.logging.OLog;
import org.olat.core.logging.Tracing;
......@@ -39,49 +37,52 @@ import org.olat.core.util.Formatter;
import org.olat.core.util.vfs.LocalFileImpl;
import org.olat.fileresource.FileResourceManager;
import org.olat.modules.video.VideoManager;
import org.olat.modules.video.VideoModule;
import org.olat.modules.video.model.VideoQualityVersion;
import org.olat.resource.OLATResource;
/**
* This task implements transcoding of a single video file using the Handbrake CLI.
*
* Initial date: 22.04.2016<br>
* @author gnaegi, gnaegi@frentix.com, http://www.frentix.com
*
*/
public class VideoTranscodingTask implements LongRunnable, TaskAwareRunnable, Sequential {
public class VideoTranscodingTask implements LongRunnable, Sequential {
private static final long serialVersionUID = 2982868860465334552L;
private static final OLog log = Tracing.createLoggerFor(VideoTranscodingTask.class);
private transient Task task;
private OLATResource video;
private String resolution;
private VideoQualityVersion version;
private File transcodedFile;
/**
*
* @param video
* @param version
*/
VideoTranscodingTask(OLATResource video, VideoQualityVersion version) {
this.video = video;
this.version = version;
this.resolution = version.getType();
}
@Override
public void setTask(Task task) {
this.task = task;
}
@Override
public void run() {
VideoModule videoModule = CoreSpringFactory.getImpl(VideoModule.class);
VideoManager videoManager = CoreSpringFactory.getImpl(VideoManager.class);
File masterFile = videoManager.getVideoFile(video);
FileResourceManager fileResourceManager = CoreSpringFactory.getImpl(FileResourceManager.class);
File videoResourceFileroot = fileResourceManager.getFileResourceRoot(video);
File optimizedFolder = new File(videoResourceFileroot, VideoManagerImpl.DIRNAME_OPTIMIZED_VIDEO_DATA);
transcodedFile = new File(optimizedFolder, resolution + masterFile.getName());
transcodedFile = new File(optimizedFolder, Integer.toString(version.getResolution()) + masterFile.getName());
ArrayList<String> cmd = new ArrayList<String>();
//TODO make configurable (taskset on osx not available)
// cmd.add("taskset");
// cmd.add("-c");
// cmd.add("0,1");
ArrayList<String> cmd = new ArrayList<>();
String tasksetConfig = videoModule.getTranscodingTasksetConfig();
if (tasksetConfig != null) {
cmd.add("taskset");
cmd.add("-c");
cmd.add(tasksetConfig);
}
cmd.add("HandBrakeCLI");
cmd.add("-i");
cmd.add(masterFile.getAbsolutePath());
......@@ -91,7 +92,7 @@ public class VideoTranscodingTask implements LongRunnable, TaskAwareRunnable, Se
cmd.add("--preset");
cmd.add("Normal");
cmd.add("--height");
cmd.add(resolution);
cmd.add(Integer.toString(version.getResolution()));
cmd.add("--deinterlace");
cmd.add("--crop");
cmd.add("0:0:0:0");
......@@ -110,11 +111,15 @@ public class VideoTranscodingTask implements LongRunnable, TaskAwareRunnable, Se
process.destroy();
process = null;
}
//TODO: remove version file, cleanup
//TODO: remove version file, cleanup, remove job
}
}
/**
* Internal helper to deal with the handbrake console output and update the transcoding metadata
* @param proc
*/
private final void executeProcess(Process proc) {
VideoManager videoManager = CoreSpringFactory.getImpl(VideoManager.class);
......@@ -122,6 +127,7 @@ public class VideoTranscodingTask implements LongRunnable, TaskAwareRunnable, Se
StringBuilder output = new StringBuilder();
String line;
// Read from standard input and parse percentages of transcoding process
InputStream stdout = proc.getInputStream();
InputStreamReader isr = new InputStreamReader(stdout);
BufferedReader br = new BufferedReader(isr);
......@@ -148,7 +154,9 @@ public class VideoTranscodingTask implements LongRunnable, TaskAwareRunnable, Se
//
}
InputStream stderr = proc.getErrorStream();
// Read and ignore errors, Handbrake outputs a lot info on startup. Only
// display errors in debug level
InputStream stderr = proc.getErrorStream();
InputStreamReader iserr = new InputStreamReader(stderr);
BufferedReader berr = new BufferedReader(iserr);
line = null;
......@@ -162,22 +170,19 @@ public class VideoTranscodingTask implements LongRunnable, TaskAwareRunnable, Se
}
try {
// On finish, update metadata file
int exitValue = proc.waitFor();
if (exitValue == 0) {
// done, update metadata file
MovieService movieService = CoreSpringFactory.getImpl(MovieService.class);
Size videoSize = movieService.getSize(new LocalFileImpl(transcodedFile), VideoManagerImpl.FILETYPE_MP4);
version.setDimension(videoSize);
version.setFileSize(Formatter.formatBytes(transcodedFile.length()));
version.setIsTransforming(false);
version.setTranscodingStatus(100);
version.setTranscodingStatus(VideoQualityVersion.TRANSCODING_STATUS_DONE);
videoManager.updateVersion(video, version);
//TODO: do I need to remove task from DB?
}
} catch (InterruptedException e) {
//
//TODO: do I need to remove task from DB?
}
}
}
......@@ -22,33 +22,46 @@ package org.olat.modules.video.model;
import org.olat.core.commons.services.image.Size;
/**
* Model of quality-versions to save in a seperate xml-file
* Model of quality-versions to save in a separate xml-file
*
* @author Dirk Furrer, dirk.furrer@frentix.com, http://www.frentix.com
*
*/
public class VideoQualityVersion{
public class VideoQualityVersion {
public static final int TRANSCODING_STATUS_WAITING = 0;
public static final int TRANSCODING_STATUS_DONE = 100;
// Properties
private String type;
private int resolution;
private String fileSize;
private Size dimension;
private String format;
private boolean isTransforming;
private int transcodingStatus = 0;
public VideoQualityVersion(String type, String fileSize, Size dimension, String format){
this.type = type;
public VideoQualityVersion(int resolution, String fileSize, Size dimension, String format) {
this.resolution = resolution;
this.fileSize = fileSize;
this.dimension = dimension;
this.format = format;
}
public String getType() {
return type;
/**
* The resolution of the transcoded video. The resolution if defined using
* the video height. E.g. a 1080p video has a resolution of 1080
*
* @return The height of the transcoded video
*/
public int getResolution() {
return resolution;
}
public void setType(String type) {
this.type = type;
/**
* @param resolution
* The resolution if defined using the video height. E.g. a 1080p
* video has a resolution of 1080
*/
public void setResolution(int resolution) {
this.resolution = resolution;
}
public String getFileSize() {
......@@ -74,15 +87,7 @@ public class VideoQualityVersion{
public void setFormat(String format) {
this.format = format;
}
public boolean getIsTransforming() {
return isTransforming;
}
public void setIsTransforming(boolean isTranscoding) {
this.isTransforming = isTranscoding;
}
/**
* @return 0: transcoding has not yet startet; 100: transcoding is done
*/
......@@ -91,10 +96,14 @@ public class VideoQualityVersion{
}
/**
* Set transcoding status in percent
* Set transcoding status in percent (0-100)
*
* @param status
*/
public void setTranscodingStatus(int status) {
if (status > 100 || status < 0) {
status = TRANSCODING_STATUS_DONE;
}
this.transcodingStatus = status;
}
}
\ No newline at end of file
......@@ -31,7 +31,7 @@ import org.olat.core.gui.components.form.flexible.elements.FormLink;
*/
public class QualityTableRow {
String type;
String resolution;
String dimension;
String size;
String format;
......@@ -39,8 +39,8 @@ public class QualityTableRow {
protected FormUIFactory uifactory = FormUIFactory.getInstance();
public QualityTableRow(String type, String dimension, String size, String format, FormLink viewLink) {
this.type = type;
public QualityTableRow(String resolution, String dimension, String size, String format, FormLink viewLink) {
this.resolution = resolution;
this.dimension = dimension;
this.size = size;
this.format = format;
......@@ -49,12 +49,12 @@ public class QualityTableRow {
}
public String getType() {
return type;
public String getResolution() {
return resolution;
}
public void setType(String type) {
this.type = type;
this.resolution = type;
}
public String getDimension() {
......
......@@ -80,7 +80,7 @@ public class VideoQualityTableFormController extends FormBasicController {
formLayout.add(generalCont);
FlexiTableColumnModel columnsModel = FlexiTableDataModelFactory.createFlexiTableColumnModel();
columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(true, QualityTableCols.type.i18nKey(), QualityTableCols.type.ordinal(), true, QualityTableCols.type.name()));
columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(true, QualityTableCols.resolution.i18nKey(), QualityTableCols.resolution.ordinal(), true, QualityTableCols.resolution.name()));
columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(true, QualityTableCols.dimension.i18nKey(), QualityTableCols.dimension.ordinal(), true, QualityTableCols.dimension.name()));
columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(true, QualityTableCols.size.i18nKey(), QualityTableCols.size.ordinal(), true, QualityTableCols.size.name()));
columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(true, QualityTableCols.format.i18nKey(), QualityTableCols.format.ordinal(), true, QualityTableCols.format.name()));
......@@ -92,11 +92,11 @@ public class VideoQualityTableFormController extends FormBasicController {
Size origSize = videoManager.getVideoSize(videoResource);
viewButton = uifactory.addFormLink("view", "viewQuality", "quality.view", "qulaity.view", null, Link.LINK);
rows.add(new QualityTableRow("original", origSize.getWidth() +"x"+ origSize.getHeight(), FileUtils.byteCountToDisplaySize(videoManager.getVideoFile(videoResource).length()), "mp4",viewButton));
rows.add(new QualityTableRow(translate("quality.resolution.original"), origSize.getWidth() +"x"+ origSize.getHeight(), FileUtils.byteCountToDisplaySize(videoManager.getVideoFile(videoResource).length()), "mp4",viewButton));
List<VideoQualityVersion> versions = videoManager.getQualityVersions(videoResource);
for(VideoQualityVersion version:versions){
viewButton = uifactory.addFormLink(version.getType(), "viewQuality", "quality.view", "qulaity.view", null, Link.LINK);
viewButton = uifactory.addFormLink(Integer.toString(version.getResolution()), "viewQuality", "quality.view", "qulaity.view", null, Link.LINK);
Size size = version.getDimension();
String dimension = "";
if (size != null) {
......@@ -105,15 +105,12 @@ public class VideoQualityTableFormController extends FormBasicController {
String fileSize = "";
if (version.getFileSize() != null) {
fileSize = version.getFileSize();
} else if (version.getIsTransforming()) {
// TODO refactor to separate column
if (version.getTranscodingStatus() == 0) {
fileSize = translate("transcoding.waiting");
} else {
fileSize = translate("transcoding.processing") + ": " + version.getTranscodingStatus() + "%";
}
} else if (version.getTranscodingStatus() == VideoQualityVersion.TRANSCODING_STATUS_WAITING) {
fileSize = translate("transcoding.waiting");
} else if (version.getTranscodingStatus() == VideoQualityVersion.TRANSCODING_STATUS_DONE){
fileSize = translate("transcoding.processing") + ": " + version.getTranscodingStatus() + "%";
}
rows.add(new QualityTableRow(translate("quality.type." + version.getType()), dimension, fileSize, version.getFormat(),viewButton));
rows.add(new QualityTableRow(translate("quality.resolution." + version.getResolution()), dimension, fileSize, version.getFormat(),viewButton));
}
tableModel.setObjects(rows);
......
......@@ -50,7 +50,7 @@ public class VideoQualityTableModel extends DefaultFlexiTableDataModel<QualityTa
public Object getValueAt(int row, int col) {
QualityTableRow video = getObject(row);
switch(QualityTableCols.values()[col]) {
case type: return video.getType();
case resolution: return video.getResolution();
case dimension: return video.getDimension();
case size: return video.getSize();
case format: return video.getFormat();
......@@ -60,7 +60,7 @@ public class VideoQualityTableModel extends DefaultFlexiTableDataModel<QualityTa
}
public enum QualityTableCols {
type("quality.table.header.type"),
resolution("quality.table.header.resolution"),
dimension("quality.table.header.dimension"),
size("quality.table.header.size"),
format("quality.table.header.format"),
......
<div class="o_video_run o_clearfix">
<div class="olatFlashMovieViewer">
<video id="$r.getId("o_vid")" width="$width" height="$height" poster="$mediaUrl/poster.jpg" controls #if( $autoplay ) autoplay #end class="o_video">
#set( $hasOptimizedVideo = false)
#foreach( $video in $videos)
#if(!$video.getIsTransforming())
#if($video.getTranscodingStatus() == 100)
#set( $hasOptimizedVideo = true)
<source type="video/mp4" src="$mediaUrl/optimizedVideoData/${video.getType()}video.mp4" title="$r.translate("quality.type.${video.getType()}") (${video.getFileSize()})"/>
<source type="video/mp4" src="$mediaUrl/optimizedVideoData/${video.getResolution()}video.mp4" title="$r.translate("quality.type.${video.getType()}") (${video.getFileSize()})"/>
#end
#end
## Use master video file if not optimized video is found
......
......@@ -10,15 +10,14 @@ poster.select=Dieses Vorschaubild ausw\u00E4hlen
quality.table.header.dimension=Dimension
quality.table.header.format=Format
quality.table.header.size=Gr\u00F6sse
quality.table.header.type=Typ
quality.table.header.resolution=Auflsung
quality.table.header.view=ansehen
quality.view=vorschau
quality.type.1080=1080p Full-HD
quality.type.720=720p HD
quality.type.480=480p
quality.type.360=360p
quality.type.240=240p
quality.type.144=144p
quality.resolution.original=Master Video
quality.resolution.1080=1080p Full-HD
quality.resolution.720=720p HD
quality.resolution.480=480p
quality.resolution.360=360p
transcoding.waiting=In Warteschlange
transcoding.processing=In Bearbeitung
tab.video.metaDataConfig=Metadaten konfigurieren
......
......@@ -11,6 +11,7 @@ tab.video.metaDataConfig=Metadata configuration
tab.video.posterConfig=Poster configuration
tab.video.settings=Video settings
tab.video.trackConfig=Subtitle configuration
tab.video.qualityConfig=Video quality
table.header.delete=Delete
table.header.file=File
table.header.language=Language
......@@ -37,11 +38,16 @@ video.config.tracks.table.add=add
video.config.tracks.table.delete=delete
video.config.tracks.table.lang=language
video.config.width=Width
quality.type.1080=1080p Full-HD
quality.type.720=720p HD
quality.type.480=480p
quality.type.360=360p
quality.type.240=240p
quality.type.144=144p
quality.resolution.original=Master video
quality.resolution.1080=1080p Full-HD
quality.resolution.720=720p HD
quality.resolution.480=480p
quality.resolution.360=360p
quality.table.header.dimension=Dimension
quality.table.header.format=Format
quality.table.header.size=Size
quality.table.header.resolution=Resolution
quality.table.header.view=Preview
quality.view=preview
transcoding.waiting=Queuing
transcoding.processing=Processing
......@@ -1157,7 +1157,6 @@ vc.openmeetings.customerid=1
vc.gotomeetings.enabled=false
vc.gotomeetings.consumerKey=
########################################
# Options for monitoring
########################################
......@@ -1166,6 +1165,14 @@ monitoring.instance.description=OpenOLAT instance
monitored.probes=Runtime,System,Database,Memory,OpenOLAT,Release,Environment,Indexer
monitoring.dependency.server=myserver
# Video resource
video.enabled=true
video.coursenode.enabled=true
# The binary "HandBrakeCLI" must be installed to make transcoding work
video.transcoding.enabled=false
video.transcoding.resolutions=1080,720,480,360
# Use tasklist to limit CPU usage.
video.transcoding.taskset.cpuconfig=0,1
video.transcoding.taskset.cpuconfig.values=0,1 to indicate usage of 2 cores, set empty value to disable taskset (e.g. on osx not available)
#
video.transcoding.provider=handbrake
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment