From 090a35d3ba6f58d9488fa6a9c64c1a4a0bd34c91 Mon Sep 17 00:00:00 2001 From: fkiefer <none@none> Date: Mon, 30 Jan 2017 14:59:33 +0100 Subject: [PATCH] OO-2454 Improve performance of video management: replace metadata xml with database adapt track files --- .../org/olat/modules/video/VideoManager.java | 123 +++++++-- .../org/olat/modules/video/VideoMeta.java | 115 +++++++++ .../video/manager/VideoManagerImpl.java | 233 ++++++++++++------ .../video/manager/VideoMetadataDAO.java | 142 +++++++++++ .../video/manager/VideoTranscodingDAO.java | 66 ++++- .../modules/video/model/TranscodingCount.java | 23 ++ .../modules/video/model/VideoMetaImpl.java | 190 ++++++++++++++ .../video/ui/VideoAdminController.java | 2 +- .../ui/VideoAdminTranscodingController.java | 132 +++++----- .../video/ui/VideoDisplayController.java | 9 +- .../ui/VideoMetaDataEditFormController.java | 4 +- .../ui/VideoQualityTableFormController.java | 7 +- .../video/ui/VideoResourceEditController.java | 31 ++- .../video/ui/VideoTrackEditController.java | 3 +- .../video/ui/VideoTrackUploadForm.java | 12 +- .../repository/handlers/VideoHandler.java | 14 +- .../org/olat/upgrade/OLATUpgrade_11_3_0.java | 67 ++++- src/main/resources/META-INF/persistence.xml | 1 + .../database/mysql/alter_11_2_x_to_11_3_0.sql | 16 ++ .../database/mysql/setupDatabase.sql | 16 ++ .../oracle/alter_11_2_x_to_11_3_0.sql | 15 ++ .../database/oracle/setupDatabase.sql | 15 ++ .../postgresql/alter_11_2_x_to_11_3_0.sql | 15 ++ .../database/postgresql/setupDatabase.sql | 15 ++ .../video/manager/VideoMetadataDAOTest.java | 102 ++++++++ .../manager/VideoTranscodingDAOTest.java | 6 +- .../java/org/olat/test/AllTestsJunit4.java | 1 + 27 files changed, 1166 insertions(+), 209 deletions(-) create mode 100644 src/main/java/org/olat/modules/video/VideoMeta.java create mode 100644 src/main/java/org/olat/modules/video/manager/VideoMetadataDAO.java create mode 100644 src/main/java/org/olat/modules/video/model/TranscodingCount.java create mode 100644 src/main/java/org/olat/modules/video/model/VideoMetaImpl.java create mode 100644 src/test/java/org/olat/modules/video/manager/VideoMetadataDAOTest.java diff --git a/src/main/java/org/olat/modules/video/VideoManager.java b/src/main/java/org/olat/modules/video/VideoManager.java index affe3a378ec..2a0470e58cd 100644 --- a/src/main/java/org/olat/modules/video/VideoManager.java +++ b/src/main/java/org/olat/modules/video/VideoManager.java @@ -21,14 +21,17 @@ package org.olat.modules.video; import java.io.File; import java.io.IOException; -import java.util.HashMap; import java.util.List; +import java.util.Map; +import org.olat.core.commons.services.image.Size; import org.olat.core.gui.translator.Translator; import org.olat.core.util.vfs.VFSContainer; import org.olat.core.util.vfs.VFSLeaf; import org.olat.fileresource.types.ResourceEvaluation; import org.olat.modules.video.manager.VideoExportMediaResource; +import org.olat.modules.video.model.TranscodingCount; +import org.olat.modules.video.model.VideoMetaImpl; import org.olat.modules.video.ui.VideoChapterTableRow; import org.olat.repository.RepositoryEntry; import org.olat.resource.OLATResource; @@ -76,15 +79,15 @@ public interface VideoManager { * @param videoResource * @return HashMap<String, VFSLeaf> */ - public abstract HashMap<String, VFSLeaf> getAllTracks(OLATResource videoResource); - - /** - * add track file for given language to videoResource - * @param videoResource - * @param lang - * @param trackFile - */ - public abstract void addTrack(OLATResource videoResource, String lang, VFSLeaf trackFile); + public abstract Map<String, VFSLeaf> getAllTracks(OLATResource videoResource); +// +// /** +// * add track file for given language to videoResource +// * @param videoResource +// * @param lang +// * @param trackFile +// */ +// public abstract void addTrack(OLATResource videoResource, String lang, VFSLeaf trackFile); /** * get Track in given lang as VFSLeaf @@ -134,6 +137,27 @@ public interface VideoManager { * @return */ public abstract List<VideoTranscoding> getVideoTranscodings(OLATResource video); + + /** + * Gets the all vidoe transcodings. + * + * @return the all vidoe transcodings + */ + public abstract List<VideoTranscoding> getAllVideoTranscodings(); + + /** + * Gets the one video resolution. + * + * @return the one video resolution + */ + public abstract List<VideoTranscoding> getOneVideoResolution(int resolution); + + /** + * Gets the all video transcodings. + * + * @return the all video transcodings + */ + public abstract List<TranscodingCount> getAllVideoTranscodingsCount(); /** * Get a human readable aspect ratio from the given video size. Recognizes @@ -297,12 +321,83 @@ public interface VideoManager { * @return the video duration */ public abstract long getVideoDuration(OLATResource videoResource); + + /** + * Gets the video resolution from olat resource. + * + * @param videoResource the video resource + * @return the video resolution from olat resource + */ + public Size getVideoResolutionFromOLATResource (OLATResource videoResource); + +// /** +// * Gets the meta data from olat resource. +// * +// * @param OLATResource videoResource the video resource +// * @return the metadata from videoResource +// */ +// public abstract VideoMetadata getMetaDataFromOLATResource(OLATResource videoResource); +// + /** + * Gets the all video resources metadata. + * + * @return the all video resources metadata + */ + public abstract List<VideoMetaImpl> getAllVideoResourcesMetadata(); + + /** + * Gets the video meta data. + * + * @param videoResource the video resource + * @return the video meta data + */ + public abstract VideoMetaImpl getVideoMetadata(OLATResource videoResource); + + /** + * Exchange poster of the new resource. + * + * @param videoResource the OLATResource + */ + public void exchangePoster(OLATResource videoResource); + + /** + * Update video metadata. + * + * @param videoResource the OLATResource + * @param uploadVideo the upload video + */ + public void updateVideoMetadata(OLATResource videoResource, VFSLeaf uploadVideo); + + /** + * Gets the all video repo entries. + * + * @param typename of a type + * @return repo entries + */ + public List<RepositoryEntry> getAllVideoRepoEntries (String typename); /** - * Gets the meta data from olat resource. + * Delete video metadata. * - * @param OLATResource videoResource the video resource - * @return the metadata from videoResource + * @param videoResource the video resource */ - public abstract VideoMetadata getMetaDataFromOLATResource(OLATResource videoResource); + public boolean deleteVideoMetadata(OLATResource videoResource); + + /** + * Creates the video metadata. + * + * @param repoEntry the repo entry + * @param size the size + * @param fileName the file name + * @return the video meta + */ + public VideoMeta createVideoMetadata(RepositoryEntry repoEntry, long size, String fileName); + + /** + * Start transcoding process if enabled. + * + * @param video the video + */ + public void startTranscodingProcessIfEnabled(OLATResource video); + } \ No newline at end of file diff --git a/src/main/java/org/olat/modules/video/VideoMeta.java b/src/main/java/org/olat/modules/video/VideoMeta.java new file mode 100644 index 00000000000..74f152c11db --- /dev/null +++ b/src/main/java/org/olat/modules/video/VideoMeta.java @@ -0,0 +1,115 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.modules.video; + +import java.util.Date; + +import org.olat.core.id.CreateInfo; +import org.olat.resource.OLATResource; + +/** + * Represents the metadata of a transcoded video file + * + * Initial date: 19.01.2017<br> + * @author fkiefer, fabian.kiefer@frentix.com, http://www.frentix.com + * + */ +public interface VideoMeta extends CreateInfo { + public static final String FORMAT_MP4 = "mp4"; + + /** + * @return key, the database identifier + */ + public Long getKey(); + + /** + * @return The video resource of the master video + */ + public OLATResource getVideoResource(); + + /** + * @return width of transcoded video in pixel + */ + public int getWidth(); + + /** + * @param width of video in pixel after transcoding + */ + public void setWidth(int width); + + /** + * @return height of transcoded video in pixel + */ + public int getHeight(); + + /** + * @param height of video in pixel after transcoding + */ + public void setHeight(int height); + + /** + * @return the video file size in bytes + */ + public long getSize(); + + /** + * @param size the file size (bytes) of the transcoded video + */ + public void setSize(long size); + + /** + * @return the transcoding format, e.g. mp4 + */ + public String getFormat(); + + /** + * @return format the transcoding format, e.g. mp4 + */ + public void setFormat(String format); + + /** + * Gets the length of the video as string. + * + * @return the length + */ + public String getLength(); + + /** + * Sets the length. + * + * @param length the new length + */ + public void setLength(String length); + + /** + * Sets the creation date. + * + * @param creationdate + */ + public void setCreationDate(Date creationdate); + + /** + * Sets the video resource. + * + * @param videoResource the new video resource + */ + public void setVideoResource(OLATResource videoResource); + +} diff --git a/src/main/java/org/olat/modules/video/manager/VideoManagerImpl.java b/src/main/java/org/olat/modules/video/manager/VideoManagerImpl.java index f054c21a1f0..a8504c6bc0f 100644 --- a/src/main/java/org/olat/modules/video/manager/VideoManagerImpl.java +++ b/src/main/java/org/olat/modules/video/manager/VideoManagerImpl.java @@ -36,7 +36,7 @@ import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.List; -import java.util.Map.Entry; +import java.util.Map; import java.util.TimeZone; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -45,6 +45,7 @@ import java.util.zip.ZipFile; import javax.imageio.ImageIO; +import org.apache.commons.io.FilenameUtils; import org.jcodec.api.FrameGrab; import org.jcodec.common.FileChannelWrapper; import org.olat.core.commons.modules.bc.vfs.OlatRootFolderImpl; @@ -66,13 +67,17 @@ import org.olat.core.util.vfs.VFSItem; import org.olat.core.util.vfs.VFSLeaf; import org.olat.core.util.vfs.VFSManager; import org.olat.core.util.vfs.VFSStatus; +import org.olat.core.util.vfs.filters.VFSItemSuffixFilter; import org.olat.core.util.xml.XStreamHelper; import org.olat.fileresource.FileResourceManager; import org.olat.fileresource.types.ResourceEvaluation; import org.olat.modules.video.VideoManager; +import org.olat.modules.video.VideoMeta; import org.olat.modules.video.VideoMetadata; import org.olat.modules.video.VideoModule; import org.olat.modules.video.VideoTranscoding; +import org.olat.modules.video.model.TranscodingCount; +import org.olat.modules.video.model.VideoMetaImpl; import org.olat.modules.video.model.VideoMetadataImpl; import org.olat.modules.video.ui.VideoChapterTableRow; import org.olat.repository.RepositoryEntry; @@ -99,11 +104,14 @@ public class VideoManagerImpl implements VideoManager { protected static final String DIRNAME_REPOENTRY = "repoentry"; public static final String FILETYPE_MP4 = "mp4"; private static final String FILETYPE_JPG = "jpg"; + private static final String FILETYPE_SRT = "srt"; private static final String FILENAME_POSTER_JPG = "poster.jpg"; private static final String FILENAME_VIDEO_MP4 = "video.mp4"; private static final String FILENAME_CHAPTERS_VTT = "chapters.vtt"; private static final String FILENAME_VIDEO_METADATA_XML = "video_metadata.xml"; private static final String DIRNAME_MASTER = "master"; + public static final String TRACK = "track_"; + public static final String DOT = "." ; private static final SimpleDateFormat displayDateFormat = new SimpleDateFormat("HH:mm:ss"); private static final SimpleDateFormat vttDateFormat = new SimpleDateFormat("HH:mm:ss.SSS"); @@ -117,6 +125,8 @@ public class VideoManagerImpl implements VideoManager { @Autowired private VideoTranscodingDAO videoTranscodingDao; @Autowired + private VideoMetadataDAO videoMetadataDao; + @Autowired private Scheduler scheduler; @Autowired private ImageService imageHelper; @@ -156,7 +166,7 @@ public class VideoManagerImpl implements VideoManager { * @param posterframe the newPosterFile */ public void setPosterframeResizeUploadfile(OLATResource videoResource, VFSLeaf newPosterFile) { - VideoMetadata videoMetadata = readVideoMetadataFile(videoResource); + VideoMeta videoMetadata = getVideoMetadata(videoResource); Size posterRes = imageHelper.getSize(newPosterFile, FILETYPE_JPG); // file size needs to be bigger than target resolution, otherwise use image as it comes if (posterRes != null @@ -180,23 +190,23 @@ public class VideoManagerImpl implements VideoManager { } } - /** - * add a subtitle-track to the videoresource - */ - @Override - public void addTrack(OLATResource videoResource, String lang, VFSLeaf trackFile){ - VideoMetadata metaData = readVideoMetadataFile(videoResource); - metaData.addTrack(lang, trackFile.getName()); - writeVideoMetadataFile(metaData, videoResource); - } +// /** +// * add a subtitle-track to the videoresource +// */ +// @Override +// public void addTrack(OLATResource videoResource, String lang, VFSLeaf trackFile){ +// VideoMetadata metaData = readVideoMetadataFile(videoResource); +// metaData.addTrack(lang, trackFile.getName()); +// writeVideoMetadataFile(metaData, videoResource); +// } /** * get a specific subtitle-track of the videoresource */ @Override public VFSLeaf getTrack(OLATResource videoResource, String lang) { - VideoMetadata metaData = readVideoMetadataFile(videoResource); - return resolveFromMasterContainer(videoResource, metaData.getTrack(lang)); + String path = TRACK + lang + DOT + FILETYPE_SRT; + return resolveFromMasterContainer(videoResource, path); } /** @@ -204,22 +214,31 @@ public class VideoManagerImpl implements VideoManager { */ @Override public void removeTrack(OLATResource videoResource, String lang){ - VideoMetadata metaData = readVideoMetadataFile(videoResource); - resolveFromMasterContainer(videoResource, metaData.getTrack(lang)).delete(); - metaData.removeTrack(lang); - writeVideoMetadataFile(metaData, videoResource); + VFSContainer vfsContainer = getMasterContainer(videoResource); + for (VFSItem item : vfsContainer.getItems(new VFSItemSuffixFilter(new String[]{FILETYPE_SRT}))) { + if (item.getName().contains(lang)) { + item.delete(); + } + } } /** * get all tracks saved in the video metadata as map */ @Override - public HashMap<String, VFSLeaf> getAllTracks(OLATResource videoResource) { - VideoMetadata metaData = readVideoMetadataFile(videoResource); - HashMap<String, VFSLeaf> tracks = new HashMap<String, VFSLeaf>(); - for(Entry<String, String> trackEntry : metaData.getAllTracks().entrySet()){ - tracks.put(trackEntry.getKey(), resolveFromMasterContainer(videoResource, trackEntry.getValue())); + public Map<String, VFSLeaf> getAllTracks(OLATResource videoResource) { + Map<String, VFSLeaf> tracks = new HashMap<>(); + VFSContainer vfsContainer = getMasterContainer(videoResource); + for (VFSItem item : vfsContainer.getItems(new VFSItemSuffixFilter(new String[]{FILETYPE_SRT}))) { + String itemname = item.getName(); + String key = itemname.substring(itemname.indexOf("_") + 1, itemname.indexOf(".")); + tracks.put(key, resolveFromMasterContainer(videoResource, itemname)); } +// VideoMetadata metaData = readVideoMetadataFile(videoResource); +// for(Entry<String, String> trackEntry : metaData.getAllTracks().entrySet()){ +// for(Entry<String, String> trackEntry : alltracks.entrySet()){ +// tracks.put(trackEntry.getKey(), resolveFromMasterContainer(videoResource, trackEntry.getValue())); +// } return tracks; } @@ -247,7 +266,7 @@ public class VideoManagerImpl implements VideoManager { FileChannel ch = randomAccessFile.getChannel(); FileChannelWrapper in = new FileChannelWrapper(ch); FrameGrab frameGrab = new FrameGrab(in).seekToFrameSloppy(frameNumber); - OutputStream frameOutputStream = frame.getOutputStream(true); + OutputStream frameOutputStream = frame.getOutputStream(false); BufferedImage bufImg = frameGrab.getFrame(); ImageIO.write(bufImg, "JPG", frameOutputStream); @@ -294,16 +313,16 @@ public class VideoManagerImpl implements VideoManager { } } - /** - * Write the metdatadata-xml in the videoresource folder - * @param metaData - * @param videoResource - */ - private void writeVideoMetadataFile(VideoMetadata metaData, OLATResource videoResource){ - VFSContainer baseContainer= FileResourceManager.getInstance().getFileResourceRootImpl(videoResource); - VFSLeaf metaDataFile = VFSManager.resolveOrCreateLeafFromPath(baseContainer, FILENAME_VIDEO_METADATA_XML); - XStreamHelper.writeObject(XStreamHelper.createXStreamInstance(), metaDataFile, metaData); - } +// /** +// * Write the metdatadata-xml in the videoresource folder +// * @param metaData +// * @param videoResource +// */ +// private void writeVideoMetadataFile(VideoMetadata metaData, OLATResource videoResource){ +// VFSContainer baseContainer= FileResourceManager.getInstance().getFileResourceRootImpl(videoResource); +// VFSLeaf metaDataFile = VFSManager.resolveOrCreateLeafFromPath(baseContainer, FILENAME_VIDEO_METADATA_XML); +// XStreamHelper.writeObject(XStreamHelper.createXStreamInstance(), metaDataFile, metaData); +// } @Override public VideoMetadata readVideoMetadataFile(OLATResource videoResource){ @@ -321,10 +340,17 @@ public class VideoManagerImpl implements VideoManager { } } + @Override + public void startTranscodingProcessIfEnabled(OLATResource video) { + if (videoModule.isTranscodingEnabled()) { + startTranscodingProcess(video); + } + } + @Override public void startTranscodingProcess(OLATResource video) { List<VideoTranscoding> existingTranscodings = getVideoTranscodings(video); - VideoMetadata videoMetadata = readVideoMetadataFile(video); + VideoMeta videoMetadata = getVideoMetadata(video); int height = videoMetadata.getHeight(); // 1) setup transcoding job for original file size createTranscodingIfNotCreatedAlready(video, height, VideoTranscoding.FORMAT_MP4, existingTranscodings); @@ -374,6 +400,24 @@ public class VideoManagerImpl implements VideoManager { return videoTranscodings; } + @Override + public List<VideoTranscoding> getAllVideoTranscodings() { + List<VideoTranscoding> videoTranscodings = videoTranscodingDao.getAllVideoTranscodings(); + return videoTranscodings; + } + + @Override + public List<TranscodingCount> getAllVideoTranscodingsCount() { + List<TranscodingCount> allVideoTranscodings = videoTranscodingDao.getAllVideoTranscodingsCount(); + return allVideoTranscodings; + } + + @Override + public List<VideoTranscoding> getOneVideoResolution(int resolution) { + List<VideoTranscoding> oneResolution = videoTranscodingDao.getOneVideoResolution(resolution); + return oneResolution; + } + @Override public String getAspectRatio(int width, int height) { @@ -489,15 +533,15 @@ public class VideoManagerImpl implements VideoManager { try { zipFile = new ZipFile(file); // 1) Check if it contains a metadata file - ZipEntry metadataEntry = zipFile.getEntry(VideoManagerImpl.FILENAME_VIDEO_METADATA_XML); - VideoMetadata videoMetadataImpl = null; - if (metadataEntry != null) { - InputStream metaDataStream = zipFile.getInputStream(metadataEntry); - videoMetadataImpl = (VideoMetadata) XStreamHelper.readObject(XStreamHelper.createXStreamInstance(), metaDataStream); - if (videoMetadataImpl != null) { - eval.setValid(true); - } - } +// ZipEntry metadataEntry = zipFile.getEntry(VideoManagerImpl.FILENAME_VIDEO_METADATA_XML); +// VideoMetadata videoMetadataImpl = null; +// if (metadataEntry != null) {// does no harm +// InputStream metaDataStream = zipFile.getInputStream(metadataEntry); +// videoMetadataImpl = (VideoMetadata) XStreamHelper.readObject(XStreamHelper.createXStreamInstance(), metaDataStream); +// if (videoMetadataImpl != null) { +// eval.setValid(true); +// } +// } // 2) Propose title from repo metadata ZipEntry repoMetadataEntry = zipFile.getEntry(DIRNAME_REPOENTRY + "/" + RepositoryEntryImportExport.PROPERTIES_FILE); RepositoryEntryImport repoMetadata = null; @@ -525,17 +569,6 @@ public class VideoManagerImpl implements VideoManager { VFSManager.copyContent(masterVideo, targetFile); masterVideo.delete(); - // 2) generate Metadata file - VideoMetadata metaData = new VideoMetadataImpl(); - // calculate video size - Size videoSize = movieService.getSize(targetFile, FILETYPE_MP4); - if (videoSize != null) { - metaData.setWidth(videoSize.getWidth()); - metaData.setHeight(videoSize.getHeight()); - } else { - metaData.setHeight(600); - metaData.setWidth(800); - } // calculate video duration long duration = movieService.getDuration(targetFile, FILETYPE_MP4); if (duration != -1) { @@ -544,42 +577,56 @@ public class VideoManagerImpl implements VideoManager { // generate a poster image, use 20th frame as a default VFSLeaf posterResource = VFSManager.resolveOrCreateLeafFromPath(masterContainer, FILENAME_POSTER_JPG); getFrame(videoResource, 20, posterResource); - // finally safe to disk - writeVideoMetadataFile(metaData, videoResource); - // 4) Set poster image for repo entry + // 2) Set poster image for repo entry VFSLeaf posterImage = (VFSLeaf)masterContainer.resolve(FILENAME_POSTER_JPG); if (posterImage != null) { repositoryManager.setImage(posterImage, repoEntry); } - - // 5) start transcoding process - if (videoModule.isTranscodingEnabled()) { - startTranscodingProcess(videoResource); - } - + return true; } @Override - public VideoMetadata getMetaDataFromOLATResource (OLATResource videoResource){ + public Size getVideoResolutionFromOLATResource (OLATResource videoResource) { VFSContainer masterContainer = getMasterContainer(videoResource); VFSLeaf targetFile = (VFSLeaf) masterContainer.resolve(FILENAME_VIDEO_MP4); - - // 1) generate Metadata file - VideoMetadata metaData = new VideoMetadataImpl(); - // calculate video size Size videoSize = movieService.getSize(targetFile, FILETYPE_MP4); - if (videoSize != null) { - metaData.setWidth(videoSize.getWidth()); - metaData.setHeight(videoSize.getHeight()); - } else { - metaData.setWidth(800); - metaData.setHeight(600); + if (videoSize == null) { + videoSize = new Size(800, 600, false); + } + return videoSize; + } + + @Override + public void exchangePoster (OLATResource videoResource) { + VFSContainer masterContainer = getMasterContainer(videoResource); + VFSLeaf posterResource = VFSManager.resolveOrCreateLeafFromPath(masterContainer, FILENAME_POSTER_JPG); + getFrame(videoResource, 20, posterResource); + // Update also repository entry image, use new posterframe + VFSLeaf posterImage = (VFSLeaf)masterContainer.resolve(FILENAME_POSTER_JPG); + if (posterImage != null) { + RepositoryEntry repoEntry = repositoryManager.lookupRepositoryEntry(videoResource, true); + repositoryManager.setImage(posterImage, repoEntry); + } + } + + @Override + public void updateVideoMetadata (OLATResource videoResource,VFSLeaf uploadVideo) { + VideoMeta meta = getVideoMetadata(videoResource); + + Size dimensions = movieService.getSize(uploadVideo, VideoManagerImpl.FILETYPE_MP4); + // update video duration + long duration = movieService.getDuration(uploadVideo, VideoTranscoding.FORMAT_MP4); + + if (duration != -1) { + String length = Formatter.formatTimecode(duration); + meta.setSize(uploadVideo.getSize()); + meta.setWidth(dimensions.getWidth()); + meta.setHeight(dimensions.getHeight()); + meta.setFormat(FilenameUtils.getExtension(uploadVideo.getName())); + meta.setLength(length); } - // 2) update XML file - writeVideoMetadataFile(metaData, videoResource); - return metaData; } @Override @@ -605,10 +652,6 @@ public class VideoManagerImpl implements VideoManager { if (posterImage != null) { repositoryManager.setImage(posterImage, repoEntry); } - // 3) start transcoding process - if (videoModule.isTranscodingEnabled()) { - startTranscodingProcess(videoResource); - } return true; } @@ -636,6 +679,12 @@ public class VideoManagerImpl implements VideoManager { VFSStatus deleteStatus = getTranscodingContainer(videoResource).delete(); return (deleteStatus == VFSConstants.YES ? true : false); } + + @Override + public boolean deleteVideoMetadata(OLATResource videoResource) { + int deleted = videoMetadataDao.deleteVideoMetadata(videoResource); + return 0 < deleted; + } @Override public List<VideoTranscoding> getVideoTranscodingsPendingAndInProgress() { @@ -760,5 +809,27 @@ public class VideoManagerImpl implements VideoManager { long duration = movieService.getDuration(video, FILETYPE_MP4); return duration; } + + @Override + public List<VideoMetaImpl> getAllVideoResourcesMetadata() { + List<VideoMetaImpl> metadata = videoMetadataDao.getAllVideoResourcesMetadata(); + return metadata; + } + + @Override + public VideoMetaImpl getVideoMetadata(OLATResource videoResource) { + VideoMetaImpl meta = videoMetadataDao.getVideoMetadata(videoResource); + return meta; + } + + @Override + public VideoMeta createVideoMetadata(RepositoryEntry repoEntry, long size, String fileName) { + return videoMetadataDao.createVideoMetadata(repoEntry, size, fileName); + } + + @Override + public List<RepositoryEntry> getAllVideoRepoEntries(String typename) { + return videoMetadataDao.getAllVideoRepoEntries(typename); + } } diff --git a/src/main/java/org/olat/modules/video/manager/VideoMetadataDAO.java b/src/main/java/org/olat/modules/video/manager/VideoMetadataDAO.java new file mode 100644 index 00000000000..992d757d8a7 --- /dev/null +++ b/src/main/java/org/olat/modules/video/manager/VideoMetadataDAO.java @@ -0,0 +1,142 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.modules.video.manager; + +import java.util.Date; +import java.util.List; + +import org.apache.commons.io.FilenameUtils; +import org.olat.core.commons.persistence.DB; +import org.olat.core.commons.services.image.Size; +import org.olat.modules.video.VideoManager; +import org.olat.modules.video.model.VideoMetaImpl; +import org.olat.repository.RepositoryEntry; +import org.olat.resource.OLATResource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * DAO implementation for manipulating VideoMetadata objects + * + * Initial date: January 2017<br> + * + * @author fkiefer, fabian.kiefer@frentix.com, http://www.frentix.com + * + */ +@Service("videoMetadataDao") +public class VideoMetadataDAO { + + @Autowired + private DB dbInstance; + @Autowired + private VideoManager videoManager; + + + /** + * Gets the video meta data. + * + * @param videoResource the OLATResource + * @return the videometadata or null + */ + VideoMetaImpl getVideoMetadata(OLATResource videoResource) { + StringBuilder sb = new StringBuilder(); + sb.append("select meta from videometadata as meta") + .append(" where meta.videoResource=:videoresource"); + List<VideoMetaImpl> metadata = dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(),VideoMetaImpl.class) + .setParameter("videoresource", videoResource) + .getResultList(); + if (metadata.size() > 0) { + return metadata.get(0); + } else { + return null; + } + } + + /** + * Gets the all video resources metadata. + * + * @return the all video resources metadata + */ + List<VideoMetaImpl> getAllVideoResourcesMetadata () { + StringBuilder sb = new StringBuilder(); + sb.append("select meta from videometadata as meta") + .append(" order by meta.creationDate asc, meta.id asc"); + return dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(),VideoMetaImpl.class) + .getResultList(); + } + + /** + * Gets the all video repo entries by type. + * + * @param typename + * @return all video repo entries + */ + List<RepositoryEntry> getAllVideoRepoEntries (String typename) { + StringBuilder sb = new StringBuilder(); + sb.append("select v from ").append(RepositoryEntry.class.getName()).append(" v ") + .append(" inner join fetch v.olatResource as ores") + .append(" where ores.resName = :type"); + List<RepositoryEntry> result = dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), RepositoryEntry.class) + .setParameter("type",typename) + .getResultList(); + return result; + } + + /** + * Delete all video metadata objects for a given video resource + * + * @param videoResource + * @return the number of entities updated or deleted + */ + int deleteVideoMetadata(OLATResource videoResource) { + String deleteQuery = "delete from videometadata where fk_resource_id=:resourceKey"; + return dbInstance.getCurrentEntityManager().createQuery(deleteQuery) + .setParameter("resourceKey", videoResource.getKey()).executeUpdate(); + } + + /** + * Creates and persists the video metadata. + * + * @param videoResource + * @param size + * @param filename + * @return metadata + */ + VideoMetaImpl createVideoMetadata(RepositoryEntry repoEntry, long size, String fileName) { + VideoMetaImpl videometa = new VideoMetaImpl(); + OLATResource videoResource = repoEntry.getOlatResource(); + videometa.setVideoResource(videoResource); + String format = FilenameUtils.getExtension(fileName); + videometa.setFormat(format); + videometa.setCreationDate(new Date()); + videometa.setLastModified(new Date()); + Size resolution = videoManager.getVideoResolutionFromOLATResource(videoResource); + videometa.setHeight(resolution.getHeight()); + videometa.setWidth(resolution.getWidth()); + videometa.setSize(size); + videometa.setLength(repoEntry.getExpenditureOfWork()); + dbInstance.getCurrentEntityManager().persist(videometa); + return videometa; + } + +} diff --git a/src/main/java/org/olat/modules/video/manager/VideoTranscodingDAO.java b/src/main/java/org/olat/modules/video/manager/VideoTranscodingDAO.java index 3e2a3572620..a4a8091b1bf 100644 --- a/src/main/java/org/olat/modules/video/manager/VideoTranscodingDAO.java +++ b/src/main/java/org/olat/modules/video/manager/VideoTranscodingDAO.java @@ -19,13 +19,16 @@ */ package org.olat.modules.video.manager; +import java.util.ArrayList; import java.util.Date; import java.util.List; import javax.persistence.EntityNotFoundException; import org.olat.core.commons.persistence.DB; +import org.olat.modules.video.VideoManager; import org.olat.modules.video.VideoTranscoding; +import org.olat.modules.video.model.TranscodingCount; import org.olat.modules.video.model.VideoTranscodingImpl; import org.olat.resource.OLATResource; import org.springframework.beans.factory.annotation.Autowired; @@ -44,6 +47,8 @@ public class VideoTranscodingDAO { @Autowired private DB dbInstance; + @Autowired + private VideoManager videoManager; /** * Factory method to create and persist new video transcoding objects for a @@ -117,8 +122,65 @@ public class VideoTranscodingDAO { .append(" inner join fetch trans.videoResource as res") .append(" where res.key=:resourceKey") .append(" order by trans.resolution desc"); - return dbInstance.getCurrentEntityManager().createQuery(sb.toString(), VideoTranscoding.class) - .setParameter("resourceKey", videoResource.getKey()).getResultList(); + return dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), VideoTranscoding.class) + .setParameter("resourceKey", videoResource.getKey()) + .getResultList(); + } + + /** + * Gets all video transcodings. + * + * @return all video transcodings + */ + List<VideoTranscoding> getAllVideoTranscodings() { + StringBuilder sb = new StringBuilder(); + sb.append("select trans from videotranscoding as trans") + .append(" order by trans.resolution desc"); + return dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), VideoTranscoding.class) + .getResultList(); + } + + /** + * Gets all transcodings of one video resolution. + * + * @param resolution + * @return all videos of one resolution + */ + List<VideoTranscoding> getOneVideoResolution (int resolution) { + StringBuilder sb = new StringBuilder(); + sb.append("select trans from videotranscoding as trans") + .append(" where trans.resolution=:resolution") + .append(" order by trans.lastModified desc"); + return dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), VideoTranscoding.class) + .setParameter("resolution", resolution) + .getResultList(); + } + + /** + * Gets the all video transcodings count. + * + * @return the all video transcodings count + */ + List<TranscodingCount> getAllVideoTranscodingsCount() { + StringBuilder sb = new StringBuilder(); + //[0] count of a distinct [1] resolution + sb.append("select count(trans.key), trans.resolution from videotranscoding as trans") + .append(" group by trans.resolution"); + + List<Object[]> rawData = dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), Object[].class) + .getResultList(); + + List<TranscodingCount> allTranscodings = new ArrayList<>(); + for (Object[] data : rawData) { + Long count = (Long) data[0]; + Integer resolution = (Integer) data[1]; + allTranscodings.add(new TranscodingCount(count, resolution)); + } + return allTranscodings; } /** diff --git a/src/main/java/org/olat/modules/video/model/TranscodingCount.java b/src/main/java/org/olat/modules/video/model/TranscodingCount.java new file mode 100644 index 00000000000..96885fa4725 --- /dev/null +++ b/src/main/java/org/olat/modules/video/model/TranscodingCount.java @@ -0,0 +1,23 @@ +package org.olat.modules.video.model; + +public class TranscodingCount { + + private Long count; + private Integer resolution; + + public TranscodingCount(Long count, Integer resolution) { + this.count = count; + this.resolution = resolution; + } + + public int getCount() { + return Math.toIntExact(count); + } + + public int getResolution() { + return resolution; + } + + + +} diff --git a/src/main/java/org/olat/modules/video/model/VideoMetaImpl.java b/src/main/java/org/olat/modules/video/model/VideoMetaImpl.java new file mode 100644 index 00000000000..31b049cd440 --- /dev/null +++ b/src/main/java/org/olat/modules/video/model/VideoMetaImpl.java @@ -0,0 +1,190 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.modules.video.model; + +import java.util.Date; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToOne; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import org.olat.core.id.ModifiedInfo; +import org.olat.core.id.Persistable; +import org.olat.modules.video.VideoMeta; +import org.olat.resource.OLATResource; +import org.olat.resource.OLATResourceImpl; + +/** + * The Class VideoMetaImpl. + * Initial Date: January 2017 + * @author fkiefer fabian.kiefer@frentix.com + */ +@Entity(name="videometadata") +@Table(name="o_vid_metadata") +public class VideoMetaImpl implements VideoMeta, Persistable, ModifiedInfo { + + private static final long serialVersionUID = 8360426958L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name="id", nullable=false, unique=true, insertable=true, updatable=false) + private Long key; + + @Temporal(TemporalType.TIMESTAMP) + @Column(name="creationdate", nullable=false, insertable=true, updatable=false) + private Date creationDate; + @Temporal(TemporalType.TIMESTAMP) + @Column(name="lastmodified", nullable=false, insertable=true, updatable=true) + private Date lastModified; + + @OneToOne(targetEntity=OLATResourceImpl.class,fetch=FetchType.LAZY,optional=false) + @JoinColumn(name="fk_resource_id", nullable=false, insertable=true, updatable=false) + private OLATResource videoResource; + + @Column(name="vid_width", nullable=true, insertable=true, updatable=true) + private int width; + @Column(name="vid_height", nullable=true, insertable=true, updatable=true) + private int height; + @Column(name="vid_size", nullable=true, insertable=true, updatable=true) + private long size; + @Column(name="vid_format", nullable=true, insertable=true, updatable=true) + private String format; + @Column(name="vid_length", nullable=true, insertable=true, updatable=true) + private String length; + + + + public VideoMetaImpl(OLATResource videoResource, int width, int height, long size, String format, String length) { + super(); + this.creationDate = new Date(); + this.lastModified = new Date(); + this.videoResource = videoResource; + this.width = width; + this.height = height; + this.size = size; + this.format = format; + this.length = length; + } + + + public VideoMetaImpl() { + // make JAXB happy + } + + @Override + public void setVideoResource(OLATResource videoResource) { + this.videoResource = videoResource; + } + + @Override + public void setCreationDate(Date creationDate) { + this.creationDate = creationDate; + } + + @Override + public Date getCreationDate() { + return creationDate; + } + + @Override + public void setLastModified(Date date) { + this.lastModified = date; + } + + @Override + public boolean equalsByPersistableKey(Persistable persistable) { + return false; + } + + @Override + public Long getKey() { + return key; + } + + @Override + public Date getLastModified() { + return lastModified; + } + + @Override + public OLATResource getVideoResource() { + return videoResource; + } + + @Override + public int getWidth() { + return width; + } + + @Override + public void setWidth(int width) { + this.width = width; + } + + @Override + public int getHeight() { + return height; + } + + @Override + public void setHeight(int height) { + this.height = height; + } + + @Override + public long getSize() { + return size; + } + + @Override + public void setSize(long size) { + this.size = size; + } + + @Override + public String getFormat() { + return format; + } + + @Override + public void setFormat(String format) { + this.format = format; + } + + @Override + public String getLength() { + return length; + } + + @Override + public void setLength(String length) { + this.length = length; + } + + +} diff --git a/src/main/java/org/olat/modules/video/ui/VideoAdminController.java b/src/main/java/org/olat/modules/video/ui/VideoAdminController.java index 0fcc0a9204c..8060dadc148 100644 --- a/src/main/java/org/olat/modules/video/ui/VideoAdminController.java +++ b/src/main/java/org/olat/modules/video/ui/VideoAdminController.java @@ -107,7 +107,7 @@ public class VideoAdminController extends BasicController { listenTo(adminTranscodingController); } mainVC.put("segmentCmp", adminTranscodingController.getInitialComponent()); - adminTranscodingController.setChecks(); + adminTranscodingController.reloadTable(); } @Override diff --git a/src/main/java/org/olat/modules/video/ui/VideoAdminTranscodingController.java b/src/main/java/org/olat/modules/video/ui/VideoAdminTranscodingController.java index 91d64738283..f0d8d4f29da 100644 --- a/src/main/java/org/olat/modules/video/ui/VideoAdminTranscodingController.java +++ b/src/main/java/org/olat/modules/video/ui/VideoAdminTranscodingController.java @@ -41,12 +41,12 @@ import org.olat.core.gui.components.form.flexible.impl.elements.table.StaticFlex import org.olat.core.gui.control.Controller; import org.olat.core.gui.control.WindowControl; import org.olat.modules.video.VideoManager; -import org.olat.modules.video.VideoMetadata; import org.olat.modules.video.VideoModule; import org.olat.modules.video.VideoTranscoding; +import org.olat.modules.video.model.TranscodingCount; +import org.olat.modules.video.model.VideoMetaImpl; import org.olat.modules.video.ui.TranscodingTableModel.TranscodingCols; import org.olat.resource.OLATResource; -import org.olat.resource.OLATResourceManager; import org.springframework.beans.factory.annotation.Autowired; /** @@ -58,15 +58,11 @@ import org.springframework.beans.factory.annotation.Autowired; */ public class VideoAdminTranscodingController extends FormBasicController { - private Map<Integer,Set<OLATResource>> availableTranscodings; - private List<OLATResource> olatresources; private TranscodingTableModel tableModel; private FlexiTableElement transcodingTable; - private List<TranscodingRow> resolutions; + private Map<OLATResource,Integer> nativeResolutions; - @Autowired - private OLATResourceManager olatresourceManager; @Autowired private VideoManager videoManager; @Autowired @@ -74,8 +70,13 @@ public class VideoAdminTranscodingController extends FormBasicController { public VideoAdminTranscodingController(UserRequest ureq, WindowControl wControl) { super(ureq, wControl, "transcoding_admin"); - resolutions = new ArrayList<>(); - generateStatusOfTranscodings(); + nativeResolutions = new HashMap<>(); + + List<VideoMetaImpl> olatresources = videoManager.getAllVideoResourcesMetadata(); + //cache native resolutions + for (VideoMetaImpl videoResource : olatresources) { + nativeResolutions.put(videoResource.getVideoResource(), videoResource.getHeight()); + } initForm(ureq); } @@ -101,7 +102,7 @@ public class VideoAdminTranscodingController extends FormBasicController { transcodingTable.setCustomizeColumns(false); transcodingTable.setNumOfRowsEnabled(false); - setChecks(); + loadTable(); } private boolean mayTranscode(int resolution){ @@ -116,35 +117,37 @@ public class VideoAdminTranscodingController extends FormBasicController { } return false; } - - private void loadTable(){ - //Hardcoded same as VideoAdminSetController - int[] resolution = {2160, 1080, 720, 480, 360, 240}; - //FIXME:FK fetch using one single SQL query - for (int i = 0; i < resolution.length; i++) { - int sizeOfTranscodings = availableTranscodings.get(resolution[i]).size(); + + private void loadTable() { + List<TranscodingRow> resolutions = new ArrayList<>(); + // Hardcoded same as VideoAdminSetController + int[] fixresolution = { 2160, 1080, 720, 480, 360, 240 }; + Map<Integer, Integer> resCount = new HashMap<>(); + for (TranscodingCount transcodingCount : videoManager.getAllVideoTranscodingsCount()) { + resCount.put(transcodingCount.getResolution(), transcodingCount.getCount()); + } + for (int i = 0; i < fixresolution.length; i++) { int counter = 0; - for (OLATResource videoResource : olatresources) { - VideoMetadata videoMetadata = videoManager.readVideoMetadataFile(videoResource); - if (videoMetadata != null && videoMetadata.getHeight() >= resolution[i]) counter++; + for (OLATResource videoResource : nativeResolutions.keySet()) { + if (nativeResolutions.get(videoResource) >= fixresolution[i]) counter++; } - resolutions.add(new TranscodingRow(resolution[i], sizeOfTranscodings, counter, mayTranscode(resolution[i]))); - } - if (resolutions != null) tableModel.setObjects(resolutions); - transcodingTable.reset(true, true, true); + int rescount = resCount.get(fixresolution[i]) != null ? resCount.get(fixresolution[i]) : 0; + resolutions.add(new TranscodingRow(fixresolution[i], rescount, counter, mayTranscode(fixresolution[i]))); + } + if (resolutions != null){ + tableModel.setObjects(resolutions); + } + transcodingTable.reset(true, true, true); } + /** * Update Table Content of all available Transcodings */ - public void setChecks(){ - generateStatusOfTranscodings(); - resolutions.clear(); + public void reloadTable(){ loadTable(); } - - @Override protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { if(source == transcodingTable) { @@ -160,65 +163,40 @@ public class VideoAdminTranscodingController extends FormBasicController { } } } - - //refresh checks - setChecks(); + reloadTable(); } - private void generateStatusOfTranscodings() { - //FIXME:FK fetch using one single SQL query - availableTranscodings = new HashMap<>(); - availableTranscodings.put(240, new HashSet<OLATResource>()); - availableTranscodings.put(360, new HashSet<OLATResource>()); - availableTranscodings.put(480, new HashSet<OLATResource>()); - availableTranscodings.put(720, new HashSet<OLATResource>()); - availableTranscodings.put(1080, new HashSet<OLATResource>()); - availableTranscodings.put(2160, new HashSet<OLATResource>()); - //determine resource type of interest - List<String> types = new ArrayList<>(); - types.add("FileResource.VIDEO"); - //retrieve all resources of type video - olatresources = olatresourceManager.findResourceByTypes(types); - //go through all video resources - for (OLATResource videoResource : olatresources) { - //retrieve all transcodings for each video resource - List<VideoTranscoding> transcodings = videoManager.getVideoTranscodings(videoResource); - //map resource IDs to resolution - for (VideoTranscoding videoTranscoding : transcodings) { - if (videoTranscoding != null) { - Set<OLATResource> oneResolution = availableTranscodings.get(videoTranscoding.getResolution()); - if (oneResolution != null) { - oneResolution.add(videoTranscoding.getVideoResource()); - } - } + + // state orders for inexistent transcodings + private void queueCreateTranscoding(TranscodingRow source) { + List<VideoTranscoding> allVideoTranscodings = videoManager.getOneVideoResolution(source.getResolution()); + Map<OLATResource, Set<Integer>> availableTranscodings = new HashMap<>(); + for (VideoTranscoding videoTranscoding : allVideoTranscodings) { + if (availableTranscodings.containsKey(videoTranscoding.getVideoResource())) { + availableTranscodings.get(videoTranscoding.getVideoResource()).add(videoTranscoding.getResolution()); + } else { + Set<Integer> availableresolutions = new HashSet<>(); + availableresolutions.add(videoTranscoding.getResolution()); + availableTranscodings.put(videoTranscoding.getVideoResource(), availableresolutions); } } - } - - - //state orders for inexistent transcodings - private void queueCreateTranscoding(TranscodingRow source){ - for (OLATResource videoResource : olatresources) { - if (!availableTranscodings.get(source.getResolution()).contains(videoResource)){ - VideoMetadata videoMetadata = videoManager.readVideoMetadataFile(videoResource); - if (videoMetadata != null && videoMetadata.getHeight() >= source.getResolution()) { - videoManager.createTranscoding(videoResource, source.getResolution(), "mp4"); + for (OLATResource videoResource : nativeResolutions.keySet()) { + if (availableTranscodings.get(videoResource) == null || + !availableTranscodings.get(videoResource).contains(source.getResolution())) { + if (nativeResolutions.get(videoResource) >= source.getResolution()) { + videoManager.createTranscoding(videoResource, source.getResolution(), "mp4"); } } } } + //go through all and delete selection private void queueDeleteTranscoding(TranscodingRow source) { - for (OLATResource videoResource : olatresources) { - if (availableTranscodings.get(source.getResolution()).contains(videoResource)) { - List<VideoTranscoding> videoTranscodings = videoManager.getVideoTranscodings(videoResource); - - for (VideoTranscoding videoTranscoding : videoTranscodings) { - if (videoTranscoding.getResolution() == source.getResolution()) { - videoManager.deleteVideoTranscoding(videoTranscoding); - } - } + List<VideoTranscoding> allVideoTranscodings = videoManager.getOneVideoResolution(source.getResolution()); + for (VideoTranscoding videoTranscoding : allVideoTranscodings) { + if (videoTranscoding.getResolution() == source.getResolution()) { + videoManager.deleteVideoTranscoding(videoTranscoding); } } } diff --git a/src/main/java/org/olat/modules/video/ui/VideoDisplayController.java b/src/main/java/org/olat/modules/video/ui/VideoDisplayController.java index 9ca6d4ecca0..cd3abd12cb5 100644 --- a/src/main/java/org/olat/modules/video/ui/VideoDisplayController.java +++ b/src/main/java/org/olat/modules/video/ui/VideoDisplayController.java @@ -22,6 +22,7 @@ package org.olat.modules.video.ui; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Map; import org.olat.core.commons.services.commentAndRating.CommentAndRatingDefaultSecurityCallback; import org.olat.core.commons.services.commentAndRating.CommentAndRatingSecurityCallback; @@ -45,7 +46,7 @@ import org.olat.core.util.vfs.VFSContainer; import org.olat.core.util.vfs.VFSContainerMapper; import org.olat.core.util.vfs.VFSLeaf; import org.olat.modules.video.VideoManager; -import org.olat.modules.video.VideoMetadata; +import org.olat.modules.video.VideoMeta; import org.olat.modules.video.VideoModule; import org.olat.modules.video.VideoTranscoding; import org.olat.modules.video.manager.VideoMediaMapper; @@ -115,7 +116,7 @@ public class VideoDisplayController extends BasicController { VFSLeaf video = videoManager.getMasterVideoFile(entry.getOlatResource()); if(video != null) { - VideoMetadata videoMetadata = videoManager.readVideoMetadataFile(entry.getOlatResource()); + VideoMeta videoMetadata = videoManager.getVideoMetadata(entry.getOlatResource()); if(autoWidth){ mainVC.contextPut("height", 480); mainVC.contextPut("width", "100%"); @@ -230,8 +231,8 @@ public class VideoDisplayController extends BasicController { mainVC.contextPut("usePoster", Boolean.valueOf(poster != null && poster.getSize() > 0)); // Load the track from config - HashMap<String, String> trackfiles = new HashMap<String, String>(); - HashMap<String, VFSLeaf> configTracks = videoManager.getAllTracks(entry.getOlatResource()); + Map<String, String> trackfiles = new HashMap<String, String>(); + Map<String, VFSLeaf> configTracks = videoManager.getAllTracks(entry.getOlatResource()); for (HashMap.Entry<String, VFSLeaf> track : configTracks.entrySet()) { trackfiles.put(track.getKey(), track.getValue().getName()); } diff --git a/src/main/java/org/olat/modules/video/ui/VideoMetaDataEditFormController.java b/src/main/java/org/olat/modules/video/ui/VideoMetaDataEditFormController.java index cb0e145a27f..9e32fcba28f 100644 --- a/src/main/java/org/olat/modules/video/ui/VideoMetaDataEditFormController.java +++ b/src/main/java/org/olat/modules/video/ui/VideoMetaDataEditFormController.java @@ -27,7 +27,7 @@ import org.olat.core.gui.control.WindowControl; import org.olat.core.util.Formatter; import org.olat.core.util.StringHelper; import org.olat.modules.video.VideoManager; -import org.olat.modules.video.VideoMetadata; +import org.olat.modules.video.VideoMeta; import org.olat.repository.RepositoryEntry; import org.olat.resource.OLATResource; import org.springframework.beans.factory.annotation.Autowired; @@ -56,7 +56,7 @@ public class VideoMetaDataEditFormController extends FormBasicController { OLATResource videoResource = repoEntry.getOlatResource(); - VideoMetadata videoMetadata = videoManager.readVideoMetadataFile(videoResource); + VideoMeta videoMetadata = videoManager.getVideoMetadata(videoResource); uifactory.addStaticTextElement("video.config.duration", repoEntry.getExpenditureOfWork(), formLayout); uifactory.addStaticTextElement("video.config.width", String.valueOf(videoMetadata.getWidth()) + "px", formLayout); diff --git a/src/main/java/org/olat/modules/video/ui/VideoQualityTableFormController.java b/src/main/java/org/olat/modules/video/ui/VideoQualityTableFormController.java index e0a95042d37..eff5876ed81 100644 --- a/src/main/java/org/olat/modules/video/ui/VideoQualityTableFormController.java +++ b/src/main/java/org/olat/modules/video/ui/VideoQualityTableFormController.java @@ -45,7 +45,7 @@ import org.olat.core.logging.Tracing; import org.olat.core.util.Formatter; import org.olat.core.util.vfs.VFSContainer; import org.olat.modules.video.VideoManager; -import org.olat.modules.video.VideoMetadata; +import org.olat.modules.video.VideoMeta; import org.olat.modules.video.VideoModule; import org.olat.modules.video.VideoTranscoding; import org.olat.modules.video.manager.VideoMediaMapper; @@ -103,7 +103,7 @@ public class VideoQualityTableFormController extends FormBasicController { private void initTable(){ List<QualityTableRow> rows = new ArrayList<>(); - VideoMetadata videoMetadata = videoManager.readVideoMetadataFile(videoResource); + VideoMeta videoMetadata = videoManager.getVideoMetadata(videoResource); // Add master video file FormLink previewMasterLink = uifactory.addFormLink("view", "viewQuality", "quality.master", "quality.master", flc, Link.LINK); rows.add(new QualityTableRow(previewMasterLink, videoMetadata.getWidth() +"x"+ videoMetadata.getHeight(), Formatter.formatBytes(videoManager.getVideoFile(videoResource).length()), "mp4",null)); @@ -200,7 +200,8 @@ public class VideoQualityTableFormController extends FormBasicController { VideoTranscoding videoTranscoding = (VideoTranscoding) link.getUserObject(); if (videoTranscoding == null) { // this is the master video - VideoMetadata videoMetadata = videoManager.readVideoMetadataFile(videoResource); +// VideoMetadata videoMetadata = videoManager.readVideoMetadataFile(videoResource); + VideoMeta videoMetadata = videoManager.getVideoMetadata(videoResource); previewVC.contextPut("width", videoMetadata.getWidth()); previewVC.contextPut("height", videoMetadata.getHeight()); previewVC.contextPut("filename", "video.mp4"); diff --git a/src/main/java/org/olat/modules/video/ui/VideoResourceEditController.java b/src/main/java/org/olat/modules/video/ui/VideoResourceEditController.java index 40c6284bd44..f551b32e398 100644 --- a/src/main/java/org/olat/modules/video/ui/VideoResourceEditController.java +++ b/src/main/java/org/olat/modules/video/ui/VideoResourceEditController.java @@ -24,6 +24,8 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import org.apache.commons.io.FilenameUtils; +import org.olat.core.commons.services.image.Size; import org.olat.core.commons.services.video.MovieService; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.form.flexible.FormItemContainer; @@ -39,9 +41,10 @@ import org.olat.core.util.vfs.VFSContainer; import org.olat.core.util.vfs.VFSLeaf; import org.olat.core.util.vfs.VFSManager; import org.olat.modules.video.VideoManager; -import org.olat.modules.video.VideoMetadata; +import org.olat.modules.video.VideoMeta; import org.olat.modules.video.VideoModule; import org.olat.modules.video.VideoTranscoding; +import org.olat.modules.video.manager.VideoManagerImpl; import org.olat.repository.RepositoryEntry; import org.olat.resource.OLATResource; import org.springframework.beans.factory.annotation.Autowired; @@ -108,19 +111,32 @@ public class VideoResourceEditController extends FormBasicController { } - private void doReplace() { + private int doReplaceFileAndUpdateMetadata() { VFSLeaf video = (VFSLeaf) vfsContainer.resolve(VIDEO_RESOURCE); File uploadFile = uploadFileEl.getUploadFile(); +// VideoMetadata videoMetadata = videoManager.getMetaDataFromOLATResource(videoResource); + VideoMeta meta = videoManager.getVideoMetadata(videoResource); if (uploadFileEl.getUploadSize() > 0 && uploadFile.exists()){ video.delete(); VFSLeaf uploadVideo = vfsContainer.createChildLeaf(VIDEO_RESOURCE); VFSManager.copyContent(uploadFile, uploadVideo); + //update video dimensions + Size dimensions = movieService.getSize(uploadVideo, VideoManagerImpl.FILETYPE_MP4); // update video duration long duration = movieService.getDuration(uploadVideo, VideoTranscoding.FORMAT_MP4); + // exchange poster + videoManager.exchangePoster(videoResource); if (duration != -1) { - entry.setExpenditureOfWork(Formatter.formatTimecode(duration)); + String length = Formatter.formatTimecode(duration); + entry.setExpenditureOfWork(length); + meta.setSize(uploadFile.length()); + meta.setWidth(dimensions.getWidth()); + meta.setHeight(dimensions.getHeight()); + meta.setFormat(FilenameUtils.getExtension(uploadVideo.getName())); + meta.setLength(length); } } + return meta.getHeight(); } private void queueDeleteTranscoding() { @@ -130,10 +146,9 @@ public class VideoResourceEditController extends FormBasicController { } } - private void queueCreateTranscoding() { + private void queueCreateTranscoding(int height) { List<Integer> missingResolutions = videoManager.getMissingTranscodings(videoResource); - VideoMetadata videoMetadata = videoManager.getMetaDataFromOLATResource(videoResource); - int height = videoMetadata.getHeight(); + if (videoModule.isTranscodingEnabled()) { // 1) setup transcoding job for original file size videoManager.createTranscoding(videoResource, height, VideoTranscoding.FORMAT_MP4); @@ -150,8 +165,8 @@ public class VideoResourceEditController extends FormBasicController { protected void formOK(UserRequest ureq) { if (uploadFileEl.getUploadFile() != null && uploadFileEl.isUploadSuccess()) { queueDeleteTranscoding(); - doReplace(); - queueCreateTranscoding(); + int height = doReplaceFileAndUpdateMetadata(); + queueCreateTranscoding(height); typeEl.setValue(translate("admin.menu.title")); typeEl.setVisible(true); showInfo("video.replaced"); diff --git a/src/main/java/org/olat/modules/video/ui/VideoTrackEditController.java b/src/main/java/org/olat/modules/video/ui/VideoTrackEditController.java index b5f59345900..426c6928592 100644 --- a/src/main/java/org/olat/modules/video/ui/VideoTrackEditController.java +++ b/src/main/java/org/olat/modules/video/ui/VideoTrackEditController.java @@ -91,7 +91,7 @@ public class VideoTrackEditController extends FormBasicController { tableEl = uifactory.addTableElement(getWindowControl(), "tracks", tableModel, getTranslator(), generalCont); tableEl.setCustomizeColumns(false); - HashMap<String, VFSLeaf> tracks = videoManager.getAllTracks(videoResource); + Map<String, VFSLeaf> tracks = videoManager.getAllTracks(videoResource); rows = new HashMap<String,TrackTableRow>(tracks.size()); if (!tracks.isEmpty()) { for (Map.Entry<String, VFSLeaf> entry : tracks.entrySet()) { @@ -135,7 +135,6 @@ public class VideoTrackEditController extends FormBasicController { @Override public void event(UserRequest ureq, Controller source, Event event) { if(source == trackUploadForm){ - videoManager.addTrack(videoResource, trackUploadForm.getLang(),(VFSLeaf) ((FolderEvent) event).getItem()); rows.put(trackUploadForm.getLang(), new TrackTableRow(trackUploadForm.getLang(), (VFSLeaf) ((FolderEvent) event).getItem(), uifactory.addFormLink(trackUploadForm.getLang(),"deleteTrack", "track.delete", "track.delete", null, Link.BUTTON))); tableModel.setObjects(new ArrayList<TrackTableRow>(rows.values())); tableEl.reset(); diff --git a/src/main/java/org/olat/modules/video/ui/VideoTrackUploadForm.java b/src/main/java/org/olat/modules/video/ui/VideoTrackUploadForm.java index 72d7451d63a..26cbc5529ca 100644 --- a/src/main/java/org/olat/modules/video/ui/VideoTrackUploadForm.java +++ b/src/main/java/org/olat/modules/video/ui/VideoTrackUploadForm.java @@ -27,6 +27,7 @@ import java.util.Locale; import java.util.Set; import java.util.stream.Collectors; +import org.apache.commons.io.FilenameUtils; import org.olat.core.commons.modules.bc.FolderEvent; import org.olat.core.gui.UserRequest; import org.olat.core.gui.components.form.flexible.FormItemContainer; @@ -39,6 +40,7 @@ import org.olat.core.gui.control.WindowControl; import org.olat.core.util.vfs.Quota; import org.olat.core.util.vfs.VFSContainer; import org.olat.modules.video.VideoManager; +import org.olat.modules.video.manager.VideoManagerImpl; import org.olat.resource.OLATResource; import org.springframework.beans.factory.annotation.Autowired; @@ -51,6 +53,7 @@ import org.springframework.beans.factory.annotation.Autowired; */ public class VideoTrackUploadForm extends FormBasicController { + private FileElement fileEl; private SingleSelection langsItem; private long remainingSpace; @@ -107,10 +110,13 @@ public class VideoTrackUploadForm extends FormBasicController { fileEl.getUploadFile().delete(); return; } - }else{ + } else { + String uploadfilename = VideoManagerImpl.TRACK + langsItem.getSelectedKey() + VideoManagerImpl.DOT + + FilenameUtils.getExtension(fileEl.getUploadFileName()); + fileEl.setUploadFileName(uploadfilename); fireEvent(ureq, new FolderEvent(FolderEvent.UPLOAD_EVENT, fileEl.moveUploadFileTo(mediaContainer))); } - }else{ + } else { fileEl.setErrorKey("track.upload.error.nofile", null); } @@ -124,7 +130,7 @@ public class VideoTrackUploadForm extends FormBasicController { @Override protected void doDispose() { - // TODO Auto-generated method stub + // nothing to dispose } } \ No newline at end of file diff --git a/src/main/java/org/olat/repository/handlers/VideoHandler.java b/src/main/java/org/olat/repository/handlers/VideoHandler.java index e0191d3e440..75587262be9 100644 --- a/src/main/java/org/olat/repository/handlers/VideoHandler.java +++ b/src/main/java/org/olat/repository/handlers/VideoHandler.java @@ -116,14 +116,15 @@ public class VideoHandler extends FileHandler { OLATResource resource = OLATResourceManager.getInstance().createAndPersistOLATResourceInstance(ores); RepositoryEntry repoEntry = CoreSpringFactory.getImpl(RepositoryService.class).create(initialAuthor, null, "", displayname, description, resource, RepositoryEntry.ACC_OWNERS); - DBFactory.getInstance().commit(); if(fileName == null) { fileName = file.getName(); } fileName = fileName.toLowerCase(); - VFSLeaf importFile = new LocalFileImpl(file); + VFSLeaf importFile = new LocalFileImpl(file); + long filesize = importFile.getSize(); VideoManager videoManager = CoreSpringFactory.getImpl(VideoManager.class); + if (fileName.endsWith(".mp4")|| fileName.endsWith(".mov")) { // 2a) import video from raw mp4 master video file videoManager.importFromMasterFile(repoEntry, importFile); @@ -131,7 +132,12 @@ public class VideoHandler extends FileHandler { } else if (fileName.endsWith(".zip")) { // 2b) import video from archive from another OpenOLAT instance videoManager.importFromExportArchive(repoEntry, importFile); - } + } + // 3) Persist Meta data + videoManager.createVideoMetadata(repoEntry, filesize, fileName); + DBFactory.getInstance().commit(); + // 4) start transcoding process if enabled + videoManager.startTranscodingProcessIfEnabled(resource); return repoEntry; } @@ -231,6 +237,8 @@ public class VideoHandler extends FileHandler { // remove transcodings VideoManager videoManager = CoreSpringFactory.getImpl(VideoManager.class); success = videoManager.deleteVideoTranscodings(entry.getOlatResource()); + //remove metadata + success &= videoManager.deleteVideoMetadata(entry.getOlatResource()); } return success; } diff --git a/src/main/java/org/olat/upgrade/OLATUpgrade_11_3_0.java b/src/main/java/org/olat/upgrade/OLATUpgrade_11_3_0.java index 74fb1295578..01fb9951136 100644 --- a/src/main/java/org/olat/upgrade/OLATUpgrade_11_3_0.java +++ b/src/main/java/org/olat/upgrade/OLATUpgrade_11_3_0.java @@ -19,11 +19,25 @@ */ package org.olat.upgrade; -import java.util.ArrayList; +import java.io.File; +import java.util.Date; import java.util.List; +import java.util.Map.Entry; +import org.apache.commons.io.FilenameUtils; import org.olat.core.commons.persistence.DB; +import org.olat.core.commons.services.image.Size; +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.VFSManager; +import org.olat.fileresource.types.VideoFileResource; +import org.olat.modules.video.VideoManager; +import org.olat.modules.video.VideoMetadata; +import org.olat.modules.video.manager.VideoManagerImpl; +import org.olat.modules.video.model.VideoMetaImpl; import org.olat.repository.RepositoryEntry; +import org.olat.resource.OLATResource; import org.springframework.beans.factory.annotation.Autowired; /** @@ -39,7 +53,9 @@ public class OLATUpgrade_11_3_0 extends OLATUpgrade { @Autowired private DB dbInstance; - + @Autowired + private VideoManager videoManager; + public OLATUpgrade_11_3_0() { super(); } @@ -81,11 +97,11 @@ public class OLATUpgrade_11_3_0 extends OLATUpgrade { boolean allOk = true; if (!uhd.getBooleanDataValue(VIDEO_XML)) { - List<RepositoryEntry> entries = new ArrayList<>(); + List<RepositoryEntry> entries = videoManager.getAllVideoRepoEntries(VideoFileResource.TYPE_NAME); for(RepositoryEntry entry:entries) { if(entry == null) continue; - processVideoResource(entry); + allOk &= processVideoResource(entry); dbInstance.commitAndCloseSession(); } @@ -95,7 +111,46 @@ public class OLATUpgrade_11_3_0 extends OLATUpgrade { return allOk; } - private void processVideoResource(RepositoryEntry entry) { - //TODO + private boolean processVideoResource(RepositoryEntry entry) { + try { + OLATResource videoResource = entry.getOlatResource(); + // update trackfiles on filesystem + VFSContainer masterContainer = videoManager.getMasterContainer(videoResource); + VideoMetadata metafromXML = videoManager.readVideoMetadataFile(videoResource); + for (Entry<String, String> track : metafromXML.getAllTracks().entrySet()) { + VFSItem item = masterContainer.resolve(track.getValue()); + if (item != null && item instanceof VFSLeaf) { + String path = VideoManagerImpl.TRACK + track.getKey() + VideoManagerImpl.DOT + + FilenameUtils.getExtension(track.getValue()); + //check if modified track file already exists + if (masterContainer.resolve(path) == null) { + VFSLeaf target = masterContainer.createChildLeaf(path); + VFSManager.copyContent((VFSLeaf) item, target); + } + } + } + // create entries on database + File videoFile = videoManager.getVideoFile(videoResource); + String fileName = videoFile.getName(); + long size = videoFile.length(); + String format = FilenameUtils.getExtension(fileName); + if (videoManager.getVideoMetadata(videoResource) == null) { + VideoMetaImpl entity = new VideoMetaImpl(); + entity.setVideoResource(videoResource); + entity.setFormat(format); + entity.setCreationDate(new Date()); + entity.setLastModified(new Date()); + Size resolution = videoManager.getVideoResolutionFromOLATResource(videoResource); + entity.setHeight(resolution.getHeight()); + entity.setWidth(resolution.getWidth()); + entity.setSize(size); + entity.setLength(entry.getExpenditureOfWork()); + dbInstance.getCurrentEntityManager().persist(entity); + } + return true; + } catch (Exception e) { + log.error("Update Metadata failed",e); + return false; + } } } diff --git a/src/main/resources/META-INF/persistence.xml b/src/main/resources/META-INF/persistence.xml index 8dc5fa1791b..f6056eab24c 100644 --- a/src/main/resources/META-INF/persistence.xml +++ b/src/main/resources/META-INF/persistence.xml @@ -188,6 +188,7 @@ <class>org.olat.modules.reminder.model.ReminderImpl</class> <class>org.olat.modules.reminder.model.SentReminderImpl</class> <class>org.olat.modules.video.model.VideoTranscodingImpl</class> + <class>org.olat.modules.video.model.VideoMetaImpl</class> <class>org.olat.ims.lti.model.LTIOutcomeImpl</class> <class>org.olat.portfolio.model.InvitationImpl</class> <class>org.olat.portfolio.model.structel.EPStructureElementToGroupRelation</class> diff --git a/src/main/resources/database/mysql/alter_11_2_x_to_11_3_0.sql b/src/main/resources/database/mysql/alter_11_2_x_to_11_3_0.sql index e69de29bb2d..c021aae0b4f 100644 --- a/src/main/resources/database/mysql/alter_11_2_x_to_11_3_0.sql +++ b/src/main/resources/database/mysql/alter_11_2_x_to_11_3_0.sql @@ -0,0 +1,16 @@ +create table o_vid_metadata ( + id bigint not null auto_increment, + creationdate timestamp not null, + lastmodified timestamp not null, + vid_width bigint default null, + vid_height bigint default null, + vid_size bigint default null, + vid_format varchar(32) default null, + vid_length varchar(32) default null, + fk_resource_id bigint not null, + primary key (id) +); + +alter table o_vid_metadata ENGINE = InnoDB; + +alter table o_vid_metadata add constraint vid_meta_rsrc_idx foreign key (fk_resource_id) references o_olatresource (resource_id); diff --git a/src/main/resources/database/mysql/setupDatabase.sql b/src/main/resources/database/mysql/setupDatabase.sql index dc7212ceb78..1d9106e9683 100644 --- a/src/main/resources/database/mysql/setupDatabase.sql +++ b/src/main/resources/database/mysql/setupDatabase.sql @@ -1312,6 +1312,19 @@ create table o_vid_transcoding ( primary key (id) ); +create table o_vid_metadata ( + id bigint not null auto_increment, + creationdate timestamp not null, + lastmodified timestamp not null, + vid_width bigint default null, + vid_height bigint default null, + vid_size bigint default null, + vid_format varchar(32) default null, + vid_length varchar(32) default null, + fk_resource_id bigint not null, + primary key (id) +); + -- calendar create table o_cal_use_config ( id bigint not null, @@ -2208,6 +2221,7 @@ alter table o_goto_organizer ENGINE = InnoDB; alter table o_goto_meeting ENGINE = InnoDB; alter table o_goto_registrant ENGINE = InnoDB; alter table o_vid_transcoding ENGINE = InnoDB; +alter table o_vid_metadata ENGINE = InnoDB; alter table o_pf_category_relation ENGINE = InnoDB; alter table o_pf_category ENGINE = InnoDB; alter table o_pf_media ENGINE = InnoDB; @@ -2549,6 +2563,8 @@ alter table o_goto_registrant add constraint goto_regis_ident_idx foreign key (f alter table o_vid_transcoding add constraint fk_resource_id_idx foreign key (fk_resource_id) references o_olatresource (resource_id); create index vid_status_trans_idx on o_vid_transcoding(vid_status); create index vid_transcoder_trans_idx on o_vid_transcoding(vid_transcoder); +alter table o_vid_metadata add constraint vid_meta_rsrc_idx foreign key (fk_resource_id) references o_olatresource (resource_id); + -- calendar alter table o_cal_use_config add constraint cal_u_conf_to_ident_idx foreign key (fk_identity) references o_bs_identity (id); diff --git a/src/main/resources/database/oracle/alter_11_2_x_to_11_3_0.sql b/src/main/resources/database/oracle/alter_11_2_x_to_11_3_0.sql index e69de29bb2d..9bbd58a54bd 100644 --- a/src/main/resources/database/oracle/alter_11_2_x_to_11_3_0.sql +++ b/src/main/resources/database/oracle/alter_11_2_x_to_11_3_0.sql @@ -0,0 +1,15 @@ +create table o_vid_metadata ( + id number(20) GENERATED ALWAYS AS IDENTITY, + creationdate date not null, + lastmodified date not null, + vid_width number(20) default null, + vid_height number(20) default null, + vid_size number(20) default null, + vid_format varchar2(32 char) default null, + vid_length varchar2(32 char) default null, + fk_resource_id number(20) not null, + primary key (id) +); + +alter table o_vid_metadata add constraint vid_meta_rsrc_idx foreign key (fk_resource_id) references o_olatresource (resource_id); +create index idx_vid_meta_rsrc_idx on o_vid_metadata(fk_resource_id); \ No newline at end of file diff --git a/src/main/resources/database/oracle/setupDatabase.sql b/src/main/resources/database/oracle/setupDatabase.sql index e725348ec6a..9aaa09011fb 100644 --- a/src/main/resources/database/oracle/setupDatabase.sql +++ b/src/main/resources/database/oracle/setupDatabase.sql @@ -1341,6 +1341,19 @@ create table o_vid_transcoding ( primary key (id) ); +create table o_vid_metadata ( + id number(20) GENERATED ALWAYS AS IDENTITY, + creationdate date not null, + lastmodified date not null, + vid_width number(20) default null, + vid_height number(20) default null, + vid_size number(20) default null, + vid_format varchar2(32 char) default null, + vid_length varchar2(32 char) default null, + fk_resource_id number(20) not null, + primary key (id) +); + -- calendar create table o_cal_use_config ( id number(20) not null, @@ -2668,6 +2681,8 @@ alter table o_vid_transcoding add constraint fk_resource_id_idx foreign key (fk_ create index idx_vid_trans_resource_idx on o_vid_transcoding(fk_resource_id); create index vid_status_trans_idx on o_vid_transcoding(vid_status); create index vid_transcoder_trans_idx on o_vid_transcoding(vid_transcoder); +alter table o_vid_metadata add constraint vid_meta_rsrc_idx foreign key (fk_resource_id) references o_olatresource (resource_id); +create index idx_vid_meta_rsrc_idx on o_vid_metadata(fk_resource_id); -- calendar alter table o_cal_use_config add constraint cal_u_conf_to_ident_idx foreign key (fk_identity) references o_bs_identity (id); diff --git a/src/main/resources/database/postgresql/alter_11_2_x_to_11_3_0.sql b/src/main/resources/database/postgresql/alter_11_2_x_to_11_3_0.sql index e69de29bb2d..8d495aff18d 100644 --- a/src/main/resources/database/postgresql/alter_11_2_x_to_11_3_0.sql +++ b/src/main/resources/database/postgresql/alter_11_2_x_to_11_3_0.sql @@ -0,0 +1,15 @@ +create table o_vid_metadata ( + id bigserial not null, + creationdate timestamp not null, + lastmodified timestamp not null, + vid_width bigint default null, + vid_height bigint default null, + vid_size bigint default null, + vid_format varchar(32) default null, + vid_length varchar(32) default null, + fk_resource_id bigint not null, + primary key (id) +); + +alter table o_vid_metadata add constraint vid_meta_rsrc_idx foreign key (fk_resource_id) references o_olatresource (resource_id); +create index idx_vid_meta_rsrc_idx on o_vid_metadata(fk_resource_id); \ No newline at end of file diff --git a/src/main/resources/database/postgresql/setupDatabase.sql b/src/main/resources/database/postgresql/setupDatabase.sql index be6b731f600..3488c69638a 100644 --- a/src/main/resources/database/postgresql/setupDatabase.sql +++ b/src/main/resources/database/postgresql/setupDatabase.sql @@ -1311,6 +1311,19 @@ create table o_vid_transcoding ( primary key (id) ); +create table o_vid_metadata ( + id bigserial not null, + creationdate timestamp not null, + lastmodified timestamp not null, + vid_width bigint default null, + vid_height bigint default null, + vid_size bigint default null, + vid_format varchar(32) default null, + vid_length varchar(32) default null, + fk_resource_id bigint not null, + primary key (id) +); + -- calendar create table o_cal_use_config ( id int8 not null, @@ -2546,6 +2559,8 @@ alter table o_vid_transcoding add constraint fk_resource_id_idx foreign key (fk_ create index idx_vid_trans_resource_idx on o_vid_transcoding(fk_resource_id); create index vid_status_trans_idx on o_vid_transcoding(vid_status); create index vid_transcoder_trans_idx on o_vid_transcoding(vid_transcoder); +alter table o_vid_metadata add constraint vid_meta_rsrc_idx foreign key (fk_resource_id) references o_olatresource (resource_id); +create index idx_vid_meta_rsrc_idx on o_vid_metadata(fk_resource_id); -- mapper create index o_mapper_uuid_idx on o_mapper (mapper_uuid); diff --git a/src/test/java/org/olat/modules/video/manager/VideoMetadataDAOTest.java b/src/test/java/org/olat/modules/video/manager/VideoMetadataDAOTest.java new file mode 100644 index 00000000000..f43aaa38176 --- /dev/null +++ b/src/test/java/org/olat/modules/video/manager/VideoMetadataDAOTest.java @@ -0,0 +1,102 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.modules.video.manager; + +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; +import org.olat.core.commons.persistence.DB; +import org.olat.modules.video.VideoMeta; +import org.olat.modules.video.model.VideoMetaImpl; +import org.olat.repository.RepositoryEntry; +import org.olat.test.JunitTestHelper; +import org.olat.test.OlatTestCase; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Initial date: January 2017<br> + * @author fkiefer, fabian.kiefer@frentix.com, http://www.frentix.com + * + */ +public class VideoMetadataDAOTest extends OlatTestCase { + + @Autowired + private DB dbInstance; + @Autowired + private VideoMetadataDAO videoMetadataDao; + + + @Test + public void createVideoMetadata () { + RepositoryEntry entry = JunitTestHelper.createAndPersistRepositoryEntry(); + RepositoryEntry entry1 = JunitTestHelper.createAndPersistRepositoryEntry(); + //create metadata entries + VideoMeta meta = videoMetadataDao.createVideoMetadata(entry, 1500, "vid.mp4"); + Assert.assertNotNull(meta); + VideoMeta meta1 = videoMetadataDao.createVideoMetadata(entry1, 5500, "vid.mov"); + Assert.assertNotNull(meta1); + dbInstance.commitAndCloseSession(); + //retrieve by olatresource + VideoMeta meta2 = videoMetadataDao.getVideoMetadata(entry.getOlatResource()); + Assert.assertNotNull(meta2); + Assert.assertTrue(meta2.getSize() == 1500); + //update value + meta2.setSize(2500); + Assert.assertTrue(meta2.getSize() == 2500); + dbInstance.commitAndCloseSession(); + } + + @Test + public void deleteVideoMetadata () { + RepositoryEntry entry0 = JunitTestHelper.createAndPersistRepositoryEntry(); + RepositoryEntry entry1 = JunitTestHelper.createAndPersistRepositoryEntry(); + RepositoryEntry entry2 = JunitTestHelper.createAndPersistRepositoryEntry(); + RepositoryEntry entry3 = JunitTestHelper.createAndPersistRepositoryEntry(); + //create metadata entries + VideoMeta meta0 = videoMetadataDao.createVideoMetadata(entry0, 1500, "vid.mp4"); + VideoMeta meta1 = videoMetadataDao.createVideoMetadata(entry1, 1100, "vide.mp4"); + VideoMeta meta2 = videoMetadataDao.createVideoMetadata(entry2, 1200, "video.mov"); + VideoMeta meta3 = videoMetadataDao.createVideoMetadata(entry3, 4500, "videos.mp4"); + Assert.assertNotNull(meta1); + Assert.assertNotNull(meta3); + Assert.assertNotNull(meta2); + Assert.assertNotNull(meta0); + dbInstance.commitAndCloseSession(); + //retrieve list of entries + List<VideoMetaImpl> metadata = videoMetadataDao.getAllVideoResourcesMetadata(); + Assert.assertEquals(4, metadata.size()); + dbInstance.commitAndCloseSession(); + //delete entries + int deleted0 = videoMetadataDao.deleteVideoMetadata(entry0.getOlatResource()); + int deleted1 = videoMetadataDao.deleteVideoMetadata(entry3.getOlatResource()); + Assert.assertEquals(1, deleted0); + Assert.assertNotEquals(0, deleted1); + dbInstance.commitAndCloseSession(); + //retrieve new list + List<VideoMetaImpl> metadata1 = videoMetadataDao.getAllVideoResourcesMetadata(); + Assert.assertEquals(2, metadata1.size()); + Assert.assertEquals("mov", metadata1.get(1).getFormat()); + Assert.assertEquals(1100, metadata1.get(0).getSize()); + dbInstance.commitAndCloseSession(); + } + +} diff --git a/src/test/java/org/olat/modules/video/manager/VideoTranscodingDAOTest.java b/src/test/java/org/olat/modules/video/manager/VideoTranscodingDAOTest.java index 0cb92ab0366..1f67d5d8750 100644 --- a/src/test/java/org/olat/modules/video/manager/VideoTranscodingDAOTest.java +++ b/src/test/java/org/olat/modules/video/manager/VideoTranscodingDAOTest.java @@ -90,11 +90,11 @@ public class VideoTranscodingDAOTest extends OlatTestCase { videoTranscodingDao.deleteVideoTranscoding(vTranscoding1); dbInstance.commitAndCloseSession(); List<VideoTranscoding> results = videoTranscodingDao.getVideoTranscodings(resource); - Assert.assertTrue(results.size() == 3); - // delte all transcodings of resource + Assert.assertEquals(3,results.size()); + // delete all transcodings of resource videoTranscodingDao.deleteVideoTranscodings(resource); dbInstance.commitAndCloseSession(); results = videoTranscodingDao.getVideoTranscodings(resource); - Assert.assertTrue(results.size() == 0); + Assert.assertEquals(0,results.size()); } } diff --git a/src/test/java/org/olat/test/AllTestsJunit4.java b/src/test/java/org/olat/test/AllTestsJunit4.java index 4ee2bf8ccd3..26bd9fd12a1 100644 --- a/src/test/java/org/olat/test/AllTestsJunit4.java +++ b/src/test/java/org/olat/test/AllTestsJunit4.java @@ -170,6 +170,7 @@ import org.junit.runners.Suite; org.olat.modules.reminder.manager.ReminderDAOTest.class, org.olat.modules.reminder.manager.ReminderRuleEngineTest.class, org.olat.modules.video.manager.VideoTranscodingDAOTest.class, + org.olat.modules.video.manager.VideoMetadataDAOTest.class, org.olat.properties.PropertyTest.class, org.olat.search.service.document.file.FileDocumentFactoryTest.class, org.olat.search.service.indexer.repository.course.SPCourseNodeIndexerTest.class, -- GitLab