Skip to content
Snippets Groups Projects
Commit 73fc46fc authored by srosse's avatar srosse
Browse files

Merge OpenOLAT 12.2 to OpenOLAT default branch with 7ad87ef98dc4e26872f03dc02be59226664923ba

parents 796ee91a e74dd4b6
No related branches found
No related tags found
No related merge requests found
Showing
with 186 additions and 103 deletions
......@@ -39,15 +39,23 @@ public interface MovieService {
*/
public Size getSize(VFSLeaf image, String suffix);
/**
* Calculate the duration of the given movie
* Calculate the duration of the given movie.
*
* @param media
* @param suffix
* @return long duration in milliseconds
*/
public long getDuration(VFSLeaf media, String suffix);
/**
* Calculate the number of frames for the given movie.
*
* @param media
* @param suffix
* @return long duration in milliseconds
*/
public long getFrameCount(VFSLeaf media, String suffix);
/**
* Checks if a file is really an mp4 file we can handle
......
......@@ -177,6 +177,33 @@ public class MovieServiceImpl implements MovieService, ThumbnailSPI {
return -1;
}
@Override
public long getFrameCount(VFSLeaf media, String suffix) {
File file = null;
if(media instanceof VFSCPNamedItem) {
media = ((VFSCPNamedItem)media).getDelegate();
}
if(media instanceof LocalFileImpl) {
file = ((LocalFileImpl)media).getBasefile();
}
if(file == null) {
return -1;
}
if(extensions.contains(suffix)) {
try(RandomAccessFile accessFile = new RandomAccessFile(file, "r")) {
FileChannel ch = accessFile.getChannel();
FileChannelWrapper in = new FileChannelWrapper(ch);
MP4Demuxer demuxer1 = new MP4Demuxer(in);
return demuxer1.getVideoTrack().getFrameCount();
} catch (Exception | AssertionError e) {
log.error("Cannot extract num. of frames of: " + media, e);
}
}
return -1;
}
@Override
public boolean isMP4(VFSLeaf media, String fileName) {
......
......@@ -54,21 +54,21 @@ public interface VideoManager {
* @param videoResource the video resource
* @return true, if successful
*/
public abstract boolean hasVideoFile(OLATResource videoResource);
public boolean hasVideoFile(OLATResource videoResource);
/**
* get Videofile as File representation
* @param videoResource
* @return File
*/
public abstract File getVideoFile(OLATResource videoResource);
public File getVideoFile(OLATResource videoResource);
/**
* get actually configured posterframe as VFSLeaf representation
* @param videoResource
* @return VFSLeaf
*/
public abstract VFSLeaf getPosterframe(OLATResource videoResource);
public VFSLeaf getPosterframe(OLATResource videoResource);
/**
* set posterframe for given videoResource
......@@ -354,7 +354,9 @@ public interface VideoManager {
* @param OLATResource videoResource
* @return the video duration
*/
public abstract long getVideoDuration(OLATResource videoResource);
public long getVideoDuration(OLATResource videoResource);
public long getVideoFrameCount(OLATResource videoResource);
/**
* Gets the video resolution from olat resource.
......
......@@ -350,7 +350,6 @@ public class VideoManagerImpl implements VideoManager {
return videoFile.getBasefile();
}
/**
* Resolve the given path to a file in the master directory and return it
*
......@@ -889,17 +888,22 @@ public class VideoManagerImpl implements VideoManager {
}
@Override
public long getVideoDuration (OLATResource videoResource){
public long getVideoDuration(OLATResource videoResource){
VFSContainer masterContainer = getMasterContainer(videoResource);
VFSLeaf video = (VFSLeaf) masterContainer.resolve(FILENAME_VIDEO_MP4);
long duration = movieService.getDuration(video, FILETYPE_MP4);
return duration;
VFSLeaf video = (VFSLeaf)masterContainer.resolve(FILENAME_VIDEO_MP4);
return movieService.getDuration(video, FILETYPE_MP4);
}
@Override
public long getVideoFrameCount(OLATResource videoResource) {
VFSContainer masterContainer = getMasterContainer(videoResource);
VFSLeaf video = (VFSLeaf)masterContainer.resolve(FILENAME_VIDEO_MP4);
return movieService.getFrameCount(video, FILETYPE_MP4);
}
@Override
public List<VideoMetaImpl> getAllVideoResourcesMetadata() {
List<VideoMetaImpl> metadata = videoMetadataDao.getAllVideoResourcesMetadata();
return metadata;
return videoMetadataDao.getAllVideoResourcesMetadata();
}
@Override
......
......@@ -49,7 +49,7 @@ public class VideoPosterEditController extends FormBasicController {
@Autowired
private VideoManager videoManager;
private VFSLeaf posterFile;
private OLATResource videoResource;
private FormLayoutContainer displayContainer;
private FormLink replaceImage;
......@@ -73,7 +73,6 @@ public class VideoPosterEditController extends FormBasicController {
displayContainer.contextPut("hint", translate("video.config.poster.hint"));
posterFile = videoManager.getPosterframe(videoResource);
updatePosterImage(ureq, videoResource);
displayContainer.setLabel("video.config.poster", null);
formLayout.add(displayContainer);
......@@ -111,59 +110,65 @@ public class VideoPosterEditController extends FormBasicController {
public void event(UserRequest ureq, Controller source, Event event) {
if(source == posterUploadForm || source == posterSelectionForm){
if(event instanceof FolderEvent){
posterFile = (VFSLeaf) ((FolderEvent) event).getItem();
flc.setDirty(true);
cmc.deactivate();
VFSLeaf newPosterFile = posterFile;
VFSLeaf posterFile = (VFSLeaf) ((FolderEvent) event).getItem();
if(source == posterUploadForm){
videoManager.setPosterframeResizeUploadfile(videoResource, newPosterFile);
videoManager.setPosterframeResizeUploadfile(videoResource, posterFile);
posterFile.delete();
} else {
videoManager.setPosterframe(videoResource, newPosterFile);
videoManager.setPosterframe(videoResource, posterFile);
}
updatePosterImage(ureq, videoResource);
// cleanup controllers
if (posterSelectionForm != null) {
removeAsListenerAndDispose(posterSelectionForm);
posterSelectionForm = null;
}
if (posterUploadForm != null) {
removeAsListenerAndDispose(posterUploadForm);
posterUploadForm = null;
}
if (cmc != null) {
removeAsListenerAndDispose(cmc);
cmc = null;
}
}
cmc.deactivate();
cleanUp();
} else if(cmc == source) {
cleanUp();
}
}
private void cleanUp() {
removeAsListenerAndDispose(posterSelectionForm);
removeAsListenerAndDispose(posterUploadForm);
removeAsListenerAndDispose(cmc);
posterSelectionForm = null;
posterUploadForm = null;
cmc = null;
}
private void doReplaceVideo(UserRequest ureq){
posterSelectionForm = new VideoPosterSelectionForm(ureq, getWindowControl(), videoResource);
listenTo(posterSelectionForm);
cmc = new CloseableModalController(getWindowControl(), "close", posterSelectionForm.getInitialComponent());
listenTo(cmc);
cmc.activate();
if(posterSelectionForm.hasProposals()) {
String title = translate("video.config.poster.replace");
cmc = new CloseableModalController(getWindowControl(), "close", posterSelectionForm.getInitialComponent(), true, title, true);
listenTo(cmc);
cmc.activate();
} else {
showWarning("warning.no.poster.proposals");
cleanUp();
}
}
private void doUploadVideo(UserRequest ureq){
posterUploadForm = new VideoPosterUploadForm(ureq, getWindowControl(), videoResource);
listenTo(posterUploadForm);
cmc = new CloseableModalController(getWindowControl(), "close", posterUploadForm.getInitialComponent());
String title = translate("video.config.poster.upload");
cmc = new CloseableModalController(getWindowControl(), "close", posterUploadForm.getInitialComponent(),
true, title, true);
listenTo(cmc);
cmc.activate();
}
private void updatePosterImage(UserRequest ureq, OLATResource video){
posterFile = videoManager.getPosterframe(video);
VFSLeaf posterFile = videoManager.getPosterframe(video);
VFSContainer masterContainer = posterFile.getParentContainer();
VideoMediaMapper mediaMapper = new VideoMediaMapper(masterContainer);
String mediaUrl = registerMapper(ureq, mediaMapper);
String serverUrl = Settings.createServerURI();
displayContainer.contextPut("serverUrl", serverUrl);
displayContainer.contextPut("mediaUrl", mediaUrl);
displayContainer.setDirty(true);
}
}
\ No newline at end of file
......@@ -21,14 +21,14 @@ package org.olat.modules.video.ui;
import java.io.File;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.util.HashMap;
import java.util.Map;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import org.jcodec.common.FileChannelWrapper;
import org.jcodec.containers.mp4.demuxer.MP4Demuxer;
import org.olat.core.commons.modules.bc.FolderEvent;
import org.olat.core.dispatcher.mapper.Mapper;
import org.olat.core.gui.UserRequest;
import org.olat.core.gui.components.Component;
import org.olat.core.gui.components.link.Link;
......@@ -37,13 +37,16 @@ import org.olat.core.gui.components.velocity.VelocityContainer;
import org.olat.core.gui.control.Event;
import org.olat.core.gui.control.WindowControl;
import org.olat.core.gui.control.controller.BasicController;
import org.olat.core.helpers.Settings;
import org.olat.core.util.CodeHelper;
import org.olat.core.gui.media.ForbiddenMediaResource;
import org.olat.core.gui.media.MediaResource;
import org.olat.core.gui.media.NotFoundMediaResource;
import org.olat.core.util.WebappHelper;
import org.olat.core.util.vfs.LocalFolderImpl;
import org.olat.core.util.vfs.VFSContainer;
import org.olat.core.util.vfs.VFSItem;
import org.olat.core.util.vfs.VFSLeaf;
import org.olat.core.util.vfs.VFSMediaResource;
import org.olat.modules.video.VideoManager;
import org.olat.modules.video.manager.VideoMediaMapper;
import org.olat.resource.OLATResource;
import org.springframework.beans.factory.annotation.Autowired;
......@@ -57,76 +60,87 @@ public class VideoPosterSelectionForm extends BasicController {
private static final String FILENAME_PREFIX_PROPOSAL_POSTER = "proposalPoster";
private VFSContainer tmpContainer;
@Autowired
private VideoManager videoManager;
private VelocityContainer proposalLayout = createVelocityContainer("video_poster_proposal");
private final VelocityContainer proposalLayout;
private Map<String, String> generatedPosters = new HashMap<String, String>();
private static final int STEP = 24;
private final boolean hasProposals;
public VideoPosterSelectionForm(UserRequest ureq, WindowControl wControl, OLATResource videoResource) {
super(ureq, wControl);
proposalLayout = createVelocityContainer("video_poster_proposal");
// posters are generated in tmp.
File tmp = new File(System.getProperty("java.io.tmpdir"), CodeHelper.getGlobalForeverUniqueID());
tmp.mkdirs();
tmpContainer = new LocalFolderImpl(tmp);
long duration = 1000;
tmpContainer = new LocalFolderImpl(new File(WebappHelper.getTmpDir(), "poster_" + UUID.randomUUID()));
File videoFile = videoManager.getVideoFile(videoResource);
RandomAccessFile accessFile;
try {
accessFile = new RandomAccessFile(videoFile,"r");
FileChannel ch = accessFile.getChannel();
FileChannelWrapper in = new FileChannelWrapper(ch);
MP4Demuxer demuxer1 = new MP4Demuxer(in);
duration = demuxer1.getVideoTrack().getFrameCount();
} catch (Exception | AssertionError e) {
logError("Error while accessing master video::" + videoFile.getAbsolutePath(), e);
List<String> proposals = generatePosterProposals(videoResource);
proposalLayout.contextPut("proposals", proposals);
hasProposals = !proposals.isEmpty();
if(!proposals.isEmpty()) {
String mediaUrl = registerMapper(ureq, new PosterMapper());
proposalLayout.contextPut("mediaUrl", mediaUrl);
}
putInitialPanel(proposalLayout);
}
public boolean hasProposals() {
return hasProposals;
}
private List<String> generatePosterProposals(OLATResource videoResource) {
long frames = videoManager.getVideoFrameCount(videoResource);
long firstThirdDuration = duration/7;
for (int currentFrame = 0; currentFrame <= duration; currentFrame += firstThirdDuration) {
long framesStepping = frames / 7;
if(framesStepping == 0) {
framesStepping = 256;
}
long maxAdjust = framesStepping / STEP;
int proposalCounter = 0;
List<String> generatedPosters = new ArrayList<>();
a_a:
for (int currentFrame = 0; currentFrame <= frames && generatedPosters.size() < 8; currentFrame += framesStepping) {
try {
String fileName;
boolean imgBlack;
int adjust = 0;
do {
fileName = FILENAME_PREFIX_PROPOSAL_POSTER + (currentFrame+adjust) + FILENAME_POSTFIX_JPG;
VFSLeaf posterProposal = tmpContainer.createChildLeaf(fileName);
imgBlack = videoManager.getFrameWithFilter(videoResource, (currentFrame+adjust), duration, posterProposal);
int step = 24;
if (currentFrame + step <= duration) {
adjust += step;
String fileName = FILENAME_PREFIX_PROPOSAL_POSTER + (proposalCounter++) + FILENAME_POSTFIX_JPG;
VFSLeaf posterProposal = tmpContainer.createChildLeaf(fileName);
boolean imgBlack = true;
for(int i=0; i<maxAdjust && imgBlack; i++) {
imgBlack = videoManager.getFrameWithFilter(videoResource, (currentFrame+adjust), frames, posterProposal);
if (currentFrame + STEP <= frames) {
adjust += STEP;
} else {
adjust -= step;
adjust -= STEP;
}
// set lower bound to avoid endless loop
if (currentFrame+adjust < 0) {
if (currentFrame + adjust < 0) {
// if all poster images are mostly black just take current frame
videoManager.getFrame(videoResource, currentFrame, posterProposal);
break;
if(videoManager.getFrame(videoResource, currentFrame, posterProposal)) {
break;
} else {
break a_a;
}
}
} while (imgBlack);
VideoMediaMapper mediaMapper = new VideoMediaMapper(tmpContainer);
String mediaUrl = registerMapper(ureq, mediaMapper);
String serverUrl = Settings.createServerURI();
proposalLayout.contextPut("serverUrl", serverUrl);
}
Link button = LinkFactory.createButton(String.valueOf(currentFrame), proposalLayout, this);
Link button = LinkFactory.createButton(fileName, proposalLayout, this);
button.setCustomEnabledLinkCSS("o_video_poster_selct");
button.setCustomDisplayText(translate("poster.select"));
button.setUserObject(fileName);
generatedPosters.put(mediaUrl + "/" + fileName, String.valueOf(currentFrame));
generatedPosters.add(fileName);
} catch (Exception | AssertionError e) {
logError("Error while creating poster images for video::" + videoFile.getAbsolutePath(), e);
logError("Error while creating poster images for video: " + videoResource, e);
}
}
proposalLayout.contextPut("pics", generatedPosters);
putInitialPanel(proposalLayout);
return generatedPosters;
}
@Override
......@@ -142,10 +156,24 @@ public class VideoPosterSelectionForm extends BasicController {
protected void event(UserRequest ureq, Component source, Event event) {
if (source instanceof Link) {
Link button = (Link) source;
VFSLeaf posterFile = (VFSLeaf)tmpContainer.resolve((String)button.getUserObject());
if (posterFile != null) {
VFSItem posterFile = tmpContainer.resolve((String)button.getUserObject());
if (posterFile instanceof VFSLeaf) {
fireEvent(ureq, new FolderEvent(FolderEvent.UPLOAD_EVENT, posterFile));
}
}
}
private class PosterMapper implements Mapper {
@Override
public MediaResource handle(String relPath, HttpServletRequest request) {
if(relPath != null && relPath.contains("..") && !relPath.endsWith(FILENAME_POSTFIX_JPG)) {
return new ForbiddenMediaResource(relPath);
}
VFSItem mediaFile = tmpContainer.resolve(relPath);
if (mediaFile instanceof VFSLeaf){
return new VFSMediaResource((VFSLeaf)mediaFile);
}
return new NotFoundMediaResource(relPath);
}
}
}
\ No newline at end of file
......@@ -30,6 +30,7 @@ import org.olat.core.gui.components.form.flexible.impl.FormBasicController;
import org.olat.core.gui.components.form.flexible.impl.FormEvent;
import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer;
import org.olat.core.gui.control.Controller;
import org.olat.core.gui.control.Event;
import org.olat.core.gui.control.WindowControl;
import org.olat.core.util.vfs.LocalFolderImpl;
import org.olat.core.util.vfs.Quota;
......@@ -43,7 +44,6 @@ import org.olat.resource.OLATResource;
* @author dfurrer, dirk.furrer@frentix.com, http://www.frentix.com
*
*/
public class VideoPosterUploadForm extends FormBasicController {
private OLATResource videoResource;
private long remainingSpace;
......@@ -82,26 +82,32 @@ public class VideoPosterUploadForm extends FormBasicController {
FormLayoutContainer buttonGroupLayout = FormLayoutContainer.createButtonLayout("buttonGroupLayout", getTranslator());
formLayout.add(buttonGroupLayout);
buttonGroupLayout.setElementCssClass("o_sel_upload_buttons");
uifactory.addFormCancelButton("cancel", buttonGroupLayout, ureq, getWindowControl());
uifactory.addFormSubmitButton("track.upload", buttonGroupLayout);
}
@Override
protected void formOK(UserRequest ureq) {
if ( posterField.isUploadSuccess()) {
if (posterField.isUploadSuccess()) {
if (remainingSpace != -1) {
if (posterField.getUploadFile().length() / 1024 > remainingSpace) {
posterField.setErrorKey("QuotaExceeded", null);
posterField.getUploadFile().delete();
return;
}
}else{
} else {
fireEvent(ureq, new FolderEvent(FolderEvent.UPLOAD_EVENT, posterField.moveUploadFileTo(metaDataFolder)));
}
}
}
@Override
protected void formCancelled(UserRequest ureq) {
fireEvent(ureq, Event.CANCELLED_EVENT);
}
@Override
protected void doDispose() {
// TODO Auto-generated method stub
//
}
}
\ No newline at end of file
<div class="o_video_poster_select">
<h2>$r.translate("poster.select")</h2>
#foreach( $poster in $pics.entrySet())
<div class="o_video_poster" style="background-image: url('$poster.key')">
$r.render($poster.value)
#foreach( $poster in $proposals)
<div class="o_video_poster" style="background-image: url('${mediaUrl}/$poster')">
$r.render($poster)
</div>
#end
</div>
......@@ -121,3 +121,4 @@ video.replace.desc=Bitte w\u00E4hlen Sie eine Video-Datei aus. Um die alte Datei
video.replace.upload=Video Datei Upload
video.replaced=Ihre Video-Datei wurde ersetzt und weitere Aufl\u00F6sungen werden transkodiert.
resource.error=Konnte Resource nicht \u00F6ffnen.
warning.no.poster.proposals=Posterbilder konnte nicht generiert werden.
......@@ -121,3 +121,5 @@ video.replace.desc=Please choose a Video-File from your File-System. To replace
video.replace.upload=Video File Upload
video.replaced=Your Video file has been replaced, and new Resolutions are beeing transcoded.
resource.error=Could not open resource.
warning.no.poster.proposals=The proposals for poster frame could be generated.
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