diff --git a/src/main/java/org/olat/core/commons/chiefcontrollers/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/core/commons/chiefcontrollers/_i18n/LocalStrings_de.properties index 1c800dff2429a503448a48b7e7e9b5292b533a8d..f773b8d01063ad8c00cd748821ae23f80ba83a4f 100644 --- a/src/main/java/org/olat/core/commons/chiefcontrollers/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/core/commons/chiefcontrollers/_i18n/LocalStrings_de.properties @@ -20,6 +20,7 @@ FileResource.GLOSSARY=Glossar FileResource.IMAGE=Bild FileResource.IMSCP=CP-Lerninhalt FileResource.MOVIE=Film +FileResource.VIDEO=Video FileResource.PDF=PDF FileResource.PODCAST=Podcast FileResource.PPT=PowerPoint diff --git a/src/main/java/org/olat/core/commons/chiefcontrollers/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/core/commons/chiefcontrollers/_i18n/LocalStrings_en.properties index 76b679bc8efce957219a6c5ef5b042ffdab2021f..126cd1bc498d0e6818f1fa99321fd9413df16d3e 100644 --- a/src/main/java/org/olat/core/commons/chiefcontrollers/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/core/commons/chiefcontrollers/_i18n/LocalStrings_en.properties @@ -21,6 +21,7 @@ FileResource.GLOSSARY=Glossary FileResource.IMAGE=Image FileResource.IMSCP=CP learning content FileResource.MOVIE=Movie +FileResource.VIDEO=Video FileResource.PDF=PDF FileResource.PODCAST=Podcast FileResource.PPT=PowerPoint diff --git a/src/main/java/org/olat/course/CourseFactory.java b/src/main/java/org/olat/course/CourseFactory.java index 131065ef0e4dd952cc79271b2b3769b9b50a2649..a5eec4fd52f27cba527c8be97fb207267397dffb 100644 --- a/src/main/java/org/olat/course/CourseFactory.java +++ b/src/main/java/org/olat/course/CourseFactory.java @@ -141,13 +141,13 @@ import org.olat.util.logging.activity.LoggingResourceable; * Description: <BR> * Use the course factory to create course run and edit controllers or to load a * course from disk - * + * * Initial Date: Oct 12, 2004 * @author Felix Jost * @author guido */ public class CourseFactory { - + private static CacheWrapper<Long,PersistingCourseImpl> loadedCourses; private static ConcurrentMap<Long, ModifyCourseEvent> modifyCourseEvents = new ConcurrentHashMap<Long, ModifyCourseEvent>(); @@ -158,8 +158,8 @@ public class CourseFactory { private static RepositoryManager repositoryManager; private static ReferenceManager referenceManager; private static RepositoryService repositoryService; - - + + /** * [used by spring] */ @@ -173,7 +173,7 @@ public class CourseFactory { /** * Create an editor controller for the given course resourceable - * + * * @param ureq * @param wControl * @param courseEntry @@ -206,7 +206,7 @@ public class CourseFactory { /** * Creates an empty course with a single root node. The course is linked to * the resourceable ores. The efficiency statment are enabled per default! - * + * * @param ores * @param shortTitle Short title of root node * @param longTitle Long title of root node @@ -217,9 +217,9 @@ public class CourseFactory { String shortTitle, String longTitle, String learningObjectives) { OLATResource courseResource = courseEntry.getOlatResource(); PersistingCourseImpl newCourse = new PersistingCourseImpl(courseResource); - // Put new course in course cache + // Put new course in course cache loadedCourses.put(newCourse.getResourceableId(), newCourse); - + Structure initialStructure = new Structure(); CourseNode runRootNode = new STCourseNode(); runRootNode.setShortTitle(shortTitle); @@ -234,7 +234,7 @@ public class CourseFactory { editorTreeModel.setRootNode(editorRootNode); newCourse.setEditorTreeModel(editorTreeModel); newCourse.saveEditorTreeModel(); - + //enable efficiency statement per default CourseConfig courseConfig = newCourse.getCourseConfig(); courseConfig.setEfficencyStatementIsEnabled(true); @@ -242,8 +242,8 @@ public class CourseFactory { return newCourse; } - - + + /** * Gets the course from cache if already there, or loads the course and puts it into cache. @@ -263,7 +263,7 @@ public class CourseFactory { // that no invalidate cache event was missed PersistingCourseImpl theCourse = new PersistingCourseImpl(courseEntry); theCourse.load(); - + PersistingCourseImpl cachedCourse = loadedCourses.putIfAbsent(resourceableId, theCourse); if(cachedCourse != null) { course = cachedCourse; @@ -274,10 +274,10 @@ public class CourseFactory { } else { course.updateCourseEntry(courseEntry); } - + return course; } - + public static ICourse loadCourse(final Long resourceableId) { if (resourceableId == null) throw new AssertException("No resourceable ID found."); PersistingCourseImpl course = loadedCourses.get(resourceableId); @@ -287,7 +287,7 @@ public class CourseFactory { OLATResource resource = OLATResourceManager.getInstance().findResourceable(resourceableId, "CourseModule"); PersistingCourseImpl theCourse = new PersistingCourseImpl(resource); theCourse.load(); - + PersistingCourseImpl cachedCourse = loadedCourses.putIfAbsent(resourceableId, theCourse); if(cachedCourse != null) { course = cachedCourse; @@ -300,7 +300,7 @@ public class CourseFactory { /** * Load the course for the given course resourceable - * + * * @param olatResource * @return the course for the given course resourceable */ @@ -308,30 +308,30 @@ public class CourseFactory { Long resourceableId = olatResource.getResourceableId(); return loadCourse(resourceableId); } - + /** - * + * * @param resourceableId */ private static void removeFromCache(Long resourceableId) { //o_clusterOK by: ld - loadedCourses.remove(resourceableId); + loadedCourses.remove(resourceableId); log.debug("removeFromCache"); } - + /** * Puts the current course in the local cache and removes it from other caches (other cluster nodes). * @param resourceableId * @param course */ - private static void updateCourseInCache(Long resourceableId, PersistingCourseImpl course) { //o_clusterOK by:ld - loadedCourses.update(resourceableId, course); + private static void updateCourseInCache(Long resourceableId, PersistingCourseImpl course) { //o_clusterOK by:ld + loadedCourses.update(resourceableId, course); log.debug("updateCourseInCache"); } /** * Delete a course including its course folder and all references to resources * this course holds. - * + * * @param res */ public static void deleteCourse(RepositoryEntry entry, OLATResource res) { @@ -344,7 +344,7 @@ public class CourseFactory { } catch (CorruptedCourseException e) { log.error("Try to delete a corrupted course, I make want I can."); } - + // call cleanupOnDelete for nodes if(course != null) { Visitor visitor = new NodeDeletionVisitor(course); @@ -364,7 +364,7 @@ public class CourseFactory { if(course != null) { CourseConfigManagerImpl.getInstance().deleteConfigOf(course); } - + CoreSpringFactory.getImpl(TaskExecutorManager.class).delete(res); // delete course group- and rightmanagement @@ -405,7 +405,7 @@ public class CourseFactory { CalendarManager calMan = CoreSpringFactory.getImpl(CalendarManager.class); CalendarNotificationManager notificationManager = CoreSpringFactory.getImpl(CalendarNotificationManager.class); NotificationsManager nfm = NotificationsManager.getInstance(); - + if(course != null) { CourseGroupManager courseGroupManager = course.getCourseEnvironment().getCourseGroupManager(); List<BusinessGroup> learningGroups = courseGroupManager.getAllBusinessGroups(); @@ -423,7 +423,7 @@ public class CourseFactory { try { /** * TODO:gs 2010-01-26 - * OLAT-4947: if we do not have an repo entry we get an exception here. + * OLAT-4947: if we do not have an repo entry we get an exception here. * This is normal in the case of courseimport and click canceling. */ KalendarRenderWrapper courseCalendar = calMan.getCalendarForDeletion(res); @@ -436,12 +436,12 @@ public class CourseFactory { //if we have a broken course (e.g. canceled import or no repo entry somehow) skip calendar deletion... } } - + /** * Copies a course. More specifically, the run and editor structures and the * course folder will be copied to create a new course. - * - * + * + * * @param sourceRes * @param ureq * @return copy of the course. @@ -450,10 +450,10 @@ public class CourseFactory { PersistingCourseImpl sourceCourse = (PersistingCourseImpl)loadCourse(sourceRes); PersistingCourseImpl targetCourse = new PersistingCourseImpl(targetRes); File fTargetCourseBasePath = targetCourse.getCourseBaseContainer().getBasefile(); - + //close connection before file copy DBFactory.getInstance().commitAndCloseSession(); - + synchronized (sourceCourse) { // o_clusterNOK - cannot be solved with doInSync since could take too long (leads to error: "Lock wait timeout exceeded") // copy configuration CourseConfig courseConf = CourseConfigManagerImpl.getInstance().copyConfigOf(sourceCourse); @@ -467,7 +467,7 @@ public class CourseFactory { // copy course folder File fSourceCourseFolder = sourceCourse.getIsolatedCourseBaseFolder(); if (fSourceCourseFolder.exists()) FileUtils.copyDirToDir(fSourceCourseFolder, fTargetCourseBasePath, false, "copy course folder"); - + // copy folder nodes directories File fSourceFoldernodesFolder = new File(FolderConfig.getCanonicalRoot() + BCCourseNode.getFoldernodesPathRelToFolderBase(sourceCourse.getCourseEnvironment())); @@ -477,7 +477,7 @@ public class CourseFactory { File fSourceTaskfoldernodesFolder = new File(FolderConfig.getCanonicalRoot() + TACourseNode.getTaskFoldersPathRelToFolderRoot(sourceCourse.getCourseEnvironment())); if (fSourceTaskfoldernodesFolder.exists()) FileUtils.copyDirToDir(fSourceTaskfoldernodesFolder, fTargetCourseBasePath, false, "copy task folder directories"); - + // update references List<Reference> refs = referenceManager.getReferences(sourceCourse); int count = 0; @@ -487,7 +487,7 @@ public class CourseFactory { DBFactory.getInstance().intermediateCommit(); } } - + // set quotas Quota sourceQuota = VFSManager.isTopLevelQuotaContainer(sourceCourse.getCourseFolderContainer()); Quota targetQuota = VFSManager.isTopLevelQuotaContainer(targetCourse.getCourseFolderContainer()); @@ -499,12 +499,12 @@ public class CourseFactory { } } } - return targetRes; + return targetRes; } /** - * Exports an entire course to a zip file. - * + * Exports an entire course to a zip file. + * * @param sourceRes * @param fTargetZIP * @return true if successfully exported, false otherwise. @@ -532,7 +532,7 @@ public class CourseFactory { /** * Import a course from a ZIP file. - * + * * @param ores * @param zipFile * @return New Course. @@ -541,17 +541,17 @@ public class CourseFactory { // Generate course with filesystem PersistingCourseImpl newCourse = new PersistingCourseImpl(ores); CourseConfigManagerImpl.getInstance().deleteConfigOf(newCourse); - + // Unzip course strucure in new course File fCanonicalCourseBasePath = newCourse.getCourseBaseContainer().getBasefile(); if (ZipUtil.unzip(zipFile, fCanonicalCourseBasePath)) { // Load course strucure now try { newCourse.load(); - CourseConfig cc = CourseConfigManagerImpl.getInstance().loadConfigFor(newCourse); + CourseConfig cc = CourseConfigManagerImpl.getInstance().loadConfigFor(newCourse); //newCourse is not in cache yet, so we cannot call setCourseConfig() newCourse.setCourseConfig(cc); - loadedCourses.put(newCourse.getResourceableId(), newCourse); + loadedCourses.put(newCourse.getResourceableId(), newCourse); return newCourse; } catch (AssertException ae) { // ok failed, cleanup below @@ -568,7 +568,7 @@ public class CourseFactory { * Deploys a course from an exported course ZIP file. This process is unatended and * therefore relies on some default assumptions on how to setup the entry and add * any referenced resources to the repository. - * + * * @param exportedCourseZIPFile */ public static RepositoryEntry deployCourseFromZIP(File exportedCourseZIPFile, String softKey, int access) { @@ -582,21 +582,21 @@ public class CourseFactory { log.info("RepositoryEntry with softkey " + softKey + " already exists. Course will not be deployed."); return existingEntry; } - - + + RepositoryHandler courseHandler = RepositoryHandlerFactory.getInstance().getRepositoryHandler(CourseModule.getCourseTypeName()); RepositoryEntry re = courseHandler.importResource(null, importExport.getInitialAuthor(), importExport.getDisplayName(), importExport.getDescription(), true, Locale.ENGLISH, exportedCourseZIPFile, exportedCourseZIPFile.getName()); - + re.setSoftkey(softKey); repositoryService.update(re); - + ICourse course = loadCourse(re); publishCourse(course, access, false, null, Locale.ENGLISH); return re; } - + /** * Publish the course with some standard options * @param course @@ -617,7 +617,7 @@ public class CourseFactory { //RepositoryEntry.ACC_USERS_GUESTS // users and guests can see the course //fxdiff VCRP-1,2: access control of resources publishProcess.changeGeneralAccess(identity, newAccess, membersOnly); - + if (publishTreeModel.hasPublishableChanges()) { List<String>nodeToPublish = new ArrayList<String>(); visitPublishModel(publishTreeModel.getRootNode(), publishTreeModel, nodeToPublish); @@ -632,7 +632,7 @@ public class CourseFactory { return; } } - + try { course = CourseFactory.openCourseEditSession(course.getResourceableId()); publishProcess.applyPublishSet(identity, locale); @@ -646,7 +646,7 @@ public class CourseFactory { /** * Create a user locale dependent help-course run controller - * + * * @param ureq The user request * @param wControl The current window controller * @return The help-course run controller @@ -670,19 +670,19 @@ public class CourseFactory { // Increment launch counter rs.incrementLaunchCounter(entry); ICourse course = loadCourse(entry); - + ContextEntry ce = BusinessControlFactory.getInstance().createContextEntry(entry); - WindowControl bwControl = BusinessControlFactory.getInstance().createBusinessWindowControl(ce, wControl); + WindowControl bwControl = BusinessControlFactory.getInstance().createBusinessWindowControl(ce, wControl); RepositoryEntrySecurity reSecurity = new RepositoryEntrySecurity(false, false, false, false, false, false, false, true); RunMainController launchC = new RunMainController(ureq, bwControl, null, course, entry, reSecurity, null); - return launchC; - } + return launchC; + } } /** * visit all nodes in the specified course and make them archiving any data * into the identity's export directory. - * + * * @param res * @param charset * @param locale @@ -697,11 +697,11 @@ public class CourseFactory { boolean isOresInstitutionalManager = RepositoryManager.getInstance().isInstitutionalRessourceManagerFor(identity, roles, courseRe); archiveCourse(identity, course, charset, locale, exportDirectory, isOLATAdmin, isOresOwner, isOresInstitutionalManager); } - + /** * visit all nodes in the specified course and make them archiving any data * into the identity's export directory. - * + * * @param res * @param charset * @param locale @@ -711,25 +711,25 @@ public class CourseFactory { // archive course results overview List<Identity> users = ScoreAccountingHelper.loadUsers(course.getCourseEnvironment()); List<AssessableCourseNode> nodes = ScoreAccountingHelper.loadAssessableNodes(course.getCourseEnvironment()); - + String result = ScoreAccountingHelper.createCourseResultsOverviewTable(users, nodes, course, locale); String fileName = ExportUtil.createFileNameWithTimeStamp(course.getCourseTitle(), "xls"); ExportUtil.writeContentToFile(fileName, result, exportDirectory, charset); - + // archive all nodes content Visitor archiveV = new NodeArchiveVisitor(locale, course, exportDirectory, charset); TreeVisitor tv = new TreeVisitor(archiveV, course.getRunStructure().getRootNode(), true); tv.visitAll(); // archive all course log files - //OLATadmin gets all logfiles independent of the visibility configuration + //OLATadmin gets all logfiles independent of the visibility configuration boolean isOresOwner = (oresRights.length > 0)?oresRights[0]:false; boolean isOresInstitutionalManager = (oresRights.length > 1)?oresRights[1]:false; - + boolean aLogV = isOresOwner || isOresInstitutionalManager || isOLATAdmin; boolean uLogV = isOLATAdmin; boolean sLogV = isOresOwner || isOresInstitutionalManager || isOLATAdmin; - - // make an intermediate commit here to make sure long running course log export doesn't + + // make an intermediate commit here to make sure long running course log export doesn't // cause db connection timeout to be triggered //@TODO transactions/backgroundjob: // rework when backgroundjob infrastructure exists @@ -741,15 +741,15 @@ public class CourseFactory { }, course.getResourceableId(), exportDirectory.getPath(), null, null, aLogV, uLogV, sLogV, charset, null, null); course.getCourseEnvironment().getCourseGroupManager().archiveCourseGroups(exportDirectory); - + CoreSpringFactory.getImpl(ChatLogHelper.class).archive(course, exportDirectory); - + } /** * Returns the data export directory. If the directory does not yet exist the * directory will be created - * + * * @param ureq The user request * @param courseName The course name or title. Will be used as directory name * @return The file representing the dat export directory @@ -767,11 +767,11 @@ public class CourseFactory { } return exportFolder; } - - + + /** - * Returns the data export directory. - * + * Returns the data export directory. + * * @param ureq The user request * @param courseName The course name or title. Will be used as directory name * @return The file representing the dat export directory @@ -783,7 +783,7 @@ public class CourseFactory { + Formatter.makeStringFilesystemSave(courseName)); return exportFolder; } - + /** * Returns the personal folder of the given identity. * <p> @@ -797,13 +797,13 @@ public class CourseFactory { if (identity==null) { return null; } - return new File(FolderConfig.getCanonicalRoot() + FolderConfig.getUserHomes() + "/" + identity.getName()); + return new File(FolderConfig.getCanonicalRoot() + FolderConfig.getUserHomes() + "/" + identity.getName()); } - + /** * Returns the data export directory. If the directory does not yet exist the * directory will be created - * + * * @param ureq The user request * @param courseName The course name or title. Will be used as directory name * @return The file representing the dat export directory @@ -821,14 +821,14 @@ public class CourseFactory { } return exportFolder; } - + /** * Stores the editor tree model AND the run structure (both xml files). Called at publish. * @param resourceableId */ public static void saveCourse(final Long resourceableId) { if (resourceableId == null) throw new AssertException("No resourceable ID found."); - + PersistingCourseImpl theCourse = getCourseEditSession(resourceableId); if(theCourse!=null) { //o_clusterOK by: ld (although the course is locked for editing, we still have to insure that load course is synchronized) @@ -839,10 +839,10 @@ public class CourseFactory { course.initHasAssessableNodes(); course.saveRunStructure(); course.saveEditorTreeModel(); - + //clear modifyCourseEvents at publish, since the updateCourseInCache is called anyway modifyCourseEvents.remove(resourceableId); - updateCourseInCache(resourceableId, course); + updateCourseInCache(resourceableId, course); } else if(!course.isReadAndWrite()) { throw new AssertException("Cannot saveCourse because theCourse is readOnly! You have to open an courseEditSession first!"); } @@ -852,20 +852,20 @@ public class CourseFactory { throw new AssertException("Cannot saveCourse because theCourse is null! Have you opened a courseEditSession yet?"); } } - + /** * Stores ONLY the editor tree model (e.g. at course tree editing - add/remove/move course nodes). * @param resourceableId */ public static void saveCourseEditorTreeModel(Long resourceableId) { if (resourceableId == null) throw new AssertException("No resourceable ID found."); - - PersistingCourseImpl course = getCourseEditSession(resourceableId); - if(course!=null && course.isReadAndWrite()) { + + PersistingCourseImpl course = getCourseEditSession(resourceableId); + if(course!=null && course.isReadAndWrite()) { synchronized(loadedCourses) { //o_clusterOK by: ld (clusterOK since the course is locked for editing) course.saveEditorTreeModel(); - - modifyCourseEvents.putIfAbsent(resourceableId, new ModifyCourseEvent(resourceableId)); + + modifyCourseEvents.putIfAbsent(resourceableId, new ModifyCourseEvent(resourceableId)); } } else if(course==null) { throw new AssertException("Cannot saveCourseEditorTreeModel because course is null! Have you opened a courseEditSession yet?"); @@ -873,25 +873,25 @@ public class CourseFactory { throw new AssertException("Cannot saveCourse because theCourse is readOnly! You have to open an courseEditSession first!"); } } - + /** * Updates the course cache forcing other cluster nodes to reload this course. <br/> * This is triggered after the course editor is closed. <br/> * It also removes the courseEditSession for this course. - * + * * @param resourceableId */ public static void fireModifyCourseEvent(Long resourceableId) { - ModifyCourseEvent modifyCourseEvent = modifyCourseEvents.get(resourceableId); + ModifyCourseEvent modifyCourseEvent = modifyCourseEvents.get(resourceableId); if(modifyCourseEvent!=null){ synchronized(modifyCourseEvents) { //o_clusterOK by: ld modifyCourseEvent = modifyCourseEvents.remove(resourceableId); - if(modifyCourseEvent != null) { + if(modifyCourseEvent != null) { PersistingCourseImpl course = getCourseEditSession(resourceableId); if(course!=null) { - updateCourseInCache(resourceableId, course); + updateCourseInCache(resourceableId, course); } - } + } } } //close courseEditSession if not already closed @@ -901,7 +901,7 @@ public class CourseFactory { /** * Create a custom css object for the course layout. This can then be set on a * MainLayoutController to activate the course layout - * + * * @param usess The user session * @param courseEnvironment the course environment * @return The custom course css or NULL if no course css is available @@ -931,7 +931,7 @@ public class CourseFactory { throw new OLATRuntimeException(PersistingCourseImpl.class, "Could not resolve course base path:" + courseRootContainer, null); return courseRootContainer; } - + /** * Save courseConfig and update cache. * @param resourceableId @@ -939,7 +939,7 @@ public class CourseFactory { */ public static void setCourseConfig(final Long resourceableId, final CourseConfig cc) { if (resourceableId == null) throw new AssertException("No resourceable ID found."); - + PersistingCourseImpl theCourse = getCourseEditSession(resourceableId); if(theCourse!=null) { //o_clusterOK by: ld (although the course is locked for editing, we still have to insure that load course is synchronized) @@ -954,14 +954,14 @@ public class CourseFactory { }); } else { throw new AssertException("Cannot setCourseConfig because theCourse is null! Have you opened a courseEditSession yet?"); - } + } } - + /** * Loads the course or gets it from cache, and adds it to the courseEditSessionMap. <br/> * It guarantees that the returned value is never null. <br/> * The courseEditSession object should live between acquire course lock and release course lock. - * + * * TODO: remove course from courseEditSessionMap at close course editor * @param resourceableId * @return @@ -976,19 +976,19 @@ public class CourseFactory { course.setReadAndWrite(true); courseEditSessionMap.put(resourceableId, course); log.debug("getCourseEditSession - put course in courseEditSessionMap: " + resourceableId); - } + } return course; } - + public static boolean isCourseEditSessionOpen(Long resourceableId) { return courseEditSessionMap.containsKey(resourceableId); } - + /** * Provides the currently edited course object with this id. <br/> * It guarantees that the returned value is never null if the openCourseEditSession was called first. <br/> * The CourseEditSession object should live between acquire course lock and release course lock. - * + * * TODO: remove course from courseEditSessionMap at close course editor * @param resourceableId * @return @@ -998,10 +998,10 @@ public class CourseFactory { PersistingCourseImpl course = courseEditSessionMap.get(resourceableId); if(course==null) { throw new AssertException("No edit session open for this course: " + resourceableId + " - Open a session first!"); - } + } return course; } - + /** * TODO: remove course from courseEditSessionMap at releaseLock * @param resourceableId @@ -1016,7 +1016,7 @@ public class CourseFactory { log.debug("removeCourseEditSession for course: " + resourceableId); } } - + private static void visitPublishModel(TreeNode node, PublishTreeModel publishTreeModel, Collection<String> nodeToPublish) { int numOfChildren = node.getChildCount(); for (int i = 0; i < numOfChildren; i++) { @@ -1027,7 +1027,7 @@ public class CourseFactory { } } } - + private static class NodeArchiveVisitor implements Visitor { private File exportPath; private Locale locale; @@ -1057,7 +1057,7 @@ public class CourseFactory { String archiveName = cn.getType() + "_" + StringHelper.transformDisplayNameToFileSystemName(cn.getShortName()) + "_" + Formatter.formatDatetimeFilesystemSave(new Date(System.currentTimeMillis())); - + FileOutputStream fileStream = null; ZipOutputStream exportStream = null; try { @@ -1073,14 +1073,14 @@ public class CourseFactory { } } } - + private static class NodeDeletionVisitor implements Visitor { private ICourse course; /** * Constructor of the node deletion visitor - * + * * @param course */ public NodeDeletionVisitor(ICourse course) { @@ -1089,7 +1089,7 @@ public class CourseFactory { /** * Visitor pattern to delete the course nodes - * + * * @see org.olat.core.util.tree.Visitor#visit(org.olat.core.util.nodes.INode) */ public void visit(INode node) { @@ -1100,11 +1100,11 @@ public class CourseFactory { } /** - * + * * Description:<br> - * Event triggered if a course was edited - namely the course tree model have changed + * Event triggered if a course was edited - namely the course tree model have changed * (e.g. nodes added, deleted) - * + * * <P> * Initial Date: 22.07.2008 <br> * @author Lavinia Dumitrescu @@ -1117,9 +1117,9 @@ class ModifyCourseEvent extends MultiUserEvent { */ public ModifyCourseEvent(Long resourceableId) { super("modify_course"); - courseId = resourceableId; + courseId = resourceableId; } - + public Long getCourseId() { return courseId; } diff --git a/src/main/java/org/olat/course/nodes/VideoCourseNode.java b/src/main/java/org/olat/course/nodes/VideoCourseNode.java new file mode 100644 index 0000000000000000000000000000000000000000..73a6011d2471b7cdd1c1a083c5aaa9a172811a37 --- /dev/null +++ b/src/main/java/org/olat/course/nodes/VideoCourseNode.java @@ -0,0 +1,159 @@ +/** +* OLAT - Online Learning and Training<br> +* http://www.olat.org +* <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 +* <p> +* http://www.apache.org/licenses/LICENSE-2.0 +* <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> +* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br> +* University of Zurich, Switzerland. +* <hr> +* <a href="http://www.openolat.org"> +* OpenOLAT - Online Learning and Training</a><br> +* This file has been modified by the OpenOLAT community. Changes are licensed +* under the Apache 2.0 license as the original file. +*/ + +package org.olat.course.nodes; + +import java.io.File; +import java.util.List; +import java.util.Locale; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.stack.BreadcrumbPanel; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.control.generic.tabbable.TabbableController; +import org.olat.core.id.Identity; +import org.olat.core.util.Util; +import org.olat.core.util.vfs.VFSContainer; +import org.olat.course.ICourse; +import org.olat.course.condition.ConditionEditController; +import org.olat.course.editor.CourseEditorEnv; +import org.olat.course.editor.NodeEditController; +import org.olat.course.editor.StatusDescription; +import org.olat.course.nodes.video.VideoEditController; +import org.olat.course.nodes.video.VideoPeekviewController; +import org.olat.course.nodes.video.VideoRunController; +import org.olat.course.run.navigation.NodeRunConstructionResult; +import org.olat.course.run.userview.NodeEvaluation; +import org.olat.course.run.userview.UserCourseEnvironment; +import org.olat.fileresource.FileResourceManager; +import org.olat.fileresource.types.ImsCPFileResource; +import org.olat.repository.RepositoryEntry; +import org.olat.repository.RepositoryEntryImportExport; +import org.olat.repository.handlers.RepositoryHandler; +import org.olat.repository.handlers.RepositoryHandlerFactory; + +public class VideoCourseNode extends AbstractAccessableCourseNode { + + private static final long serialVersionUID = -3808867902051897291L; + private static final String TYPE = "video"; + + public VideoCourseNode() { + super(TYPE); + updateModuleConfigDefaults(true); + } + + @Override + public TabbableController createEditController(UserRequest ureq, WindowControl wControl, BreadcrumbPanel stackPanel, ICourse course, UserCourseEnvironment euce) { + updateModuleConfigDefaults(false); + VideoEditController childTabCntrllr = new VideoEditController(this, ureq, wControl, stackPanel, course, euce); + CourseNode chosenNode = course.getEditorTreeModel().getCourseNode(euce.getCourseEditorEnv().getCurrentCourseNodeId()); + return new NodeEditController(ureq, wControl, course.getEditorTreeModel(), course, chosenNode, euce, childTabCntrllr); + } + + @Override + public RepositoryEntry getReferencedRepositoryEntry() { + RepositoryEntry entry = VideoEditController.getVideoReference(getModuleConfiguration(), false); + return entry; + } + + @Override + public boolean needsReferenceToARepositoryEntry() { + return true; + } + + @Override + public NodeRunConstructionResult createNodeRunConstructionResult( + UserRequest ureq, WindowControl wControl, + UserCourseEnvironment userCourseEnv, NodeEvaluation ne, + String nodecmd) { + NodeRunConstructionResult ncr; + updateModuleConfigDefaults(false); + VideoRunController cprunC = new VideoRunController(getModuleConfiguration(), wControl, ureq, this); + ncr = cprunC.createNodeRunConstructionResult(ureq); + return ncr; + } + + @Override + public StatusDescription isConfigValid(){ + if (oneClickStatusCache != null) { return oneClickStatusCache[0]; } + + StatusDescription sd = StatusDescription.NOERROR; + boolean isValid = VideoEditController.isModuleConfigValid(getModuleConfiguration()); + if (!isValid) { + // FIXME: refine statusdescriptions + String shortKey = "no.video.chosen"; + String longKey = "error.noreference.long"; + String[] params = new String[] { this.getShortTitle() }; + String translPackage = Util.getPackageName(VideoEditController.class); + sd = new StatusDescription(StatusDescription.ERROR, shortKey, longKey, params, translPackage); + sd.setDescriptionForUnit(getIdent()); + // set which pane is affected by error + sd.setActivateableViewIdentifier(VideoEditController.PANE_TAB_VIDEOCONFIG); + } + return sd; + } + + @Override + public StatusDescription[] isConfigValid(CourseEditorEnv cev) { + // only here we know which translator to take for translating condition + // error messages + String translatorStr = Util.getPackageName(ConditionEditController.class); + List<StatusDescription> statusDescs = isConfigValidWithTranslator(cev, translatorStr, getConditionExpressions()); + return StatusDescriptionHelper.sort(statusDescs); + } + + @Override + public void exportNode(File exportDirectory, ICourse course) { + RepositoryEntry re = VideoEditController.getVideoReference(getModuleConfiguration(), false); + if (re == null) return; + File fExportDirectory = new File(exportDirectory, getIdent()); + fExportDirectory.mkdirs(); + RepositoryEntryImportExport reie = new RepositoryEntryImportExport(re, fExportDirectory); + reie.exportDoExport(); + } + + @Override + public void importNode(File importDirectory, ICourse course, Identity owner, Locale locale, boolean withReferences) { + RepositoryEntryImportExport rie = new RepositoryEntryImportExport(importDirectory, getIdent()); + if(withReferences && rie.anyExportedPropertiesAvailable()) { + RepositoryHandler handler = RepositoryHandlerFactory.getInstance().getRepositoryHandler(ImsCPFileResource.TYPE_NAME); + RepositoryEntry re = handler.importResource(owner, rie.getInitialAuthor(), rie.getDisplayName(), + rie.getDescription(), false, locale, rie.importGetExportedFile(), null); + VideoEditController.setVideoReference(re, getModuleConfiguration()); + } else { + VideoEditController.removeVideoReference(getModuleConfiguration()); + } + } + + @Override + public Controller createPeekViewRunController(UserRequest ureq, WindowControl wControl, UserCourseEnvironment userCourseEnv, + NodeEvaluation ne) { +// updateModuleConfigDefaults(false); + VFSContainer mediaFolder = FileResourceManager.getInstance().getFileResourceMedia(getReferencedRepositoryEntry().getOlatResource()); + Controller controller = new VideoPeekviewController(ureq, wControl, mediaFolder); + return controller; + } +} diff --git a/src/main/java/org/olat/course/nodes/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/nodes/_i18n/LocalStrings_de.properties index c020cc1c29988bf615d20907f491259ba51f4d24..c4feec424fd8b38b5bf8a63e2bc77780ca69fa4a 100644 --- a/src/main/java/org/olat/course/nodes/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/course/nodes/_i18n/LocalStrings_de.properties @@ -31,3 +31,4 @@ title_projectbroker=Themenvergabe title_podcast=Podcast title_blog=Blog personal.title=Leistungsübersicht +title_video=Video \ No newline at end of file diff --git a/src/main/java/org/olat/course/nodes/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/nodes/_i18n/LocalStrings_en.properties index 50c0dde4c6090394f8a7e3b61f2454cb4ebf4a45..0ad67566bcd973c4b2be8e1e6d23f9f82ced9151 100644 --- a/src/main/java/org/olat/course/nodes/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/course/nodes/_i18n/LocalStrings_en.properties @@ -30,4 +30,5 @@ title_st=Structure title_ta=<s>Task (deprecated)</s> title_tu=External page title_wiki=Wiki +title_video=Video personal.title=Performance summary \ No newline at end of file diff --git a/src/main/java/org/olat/course/nodes/_spring/buildingblockContext.xml b/src/main/java/org/olat/course/nodes/_spring/buildingblockContext.xml index 3ced0e9cb3c474a0db929aef15cbb14c189e766a..9c91f7dfb363927f6176844b70c9331d6f634594 100644 --- a/src/main/java/org/olat/course/nodes/_spring/buildingblockContext.xml +++ b/src/main/java/org/olat/course/nodes/_spring/buildingblockContext.xml @@ -89,6 +89,10 @@ </property> </bean> + <bean id="video" class="org.olat.course.nodes.video.VideoCourseNodeConfiguration" scope="prototype"> + <property name="order" value="220" /> + </bean> + <bean id="ita" class="org.olat.course.nodes.gta.GTACourseNodeConfiguration" scope="prototype"> <constructor-arg index="0" value="true" /> <property name="order" value="131" /> diff --git a/src/main/java/org/olat/course/nodes/video/VideoCourseNodeConfiguration.java b/src/main/java/org/olat/course/nodes/video/VideoCourseNodeConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..b0ccbdd8dd0704d669abd0d6c54fa549269a830b --- /dev/null +++ b/src/main/java/org/olat/course/nodes/video/VideoCourseNodeConfiguration.java @@ -0,0 +1,53 @@ +package org.olat.course.nodes.video; + +import java.util.Locale; + +import org.olat.core.CoreSpringFactory; +import org.olat.core.gui.translator.Translator; +import org.olat.core.util.Util; +import org.olat.course.nodes.AbstractCourseNodeConfiguration; +import org.olat.course.nodes.CourseNode; +import org.olat.course.nodes.CourseNodeConfiguration; +import org.olat.course.nodes.CourseNodeGroup; +import org.olat.course.nodes.VideoCourseNode; +import org.olat.modules.video.VideoModule; + +public class VideoCourseNodeConfiguration extends AbstractCourseNodeConfiguration { + + private VideoCourseNodeConfiguration() { + super(); + } + + @Override + public CourseNode getInstance() { + return new VideoCourseNode(); + } + + @Override + public String getLinkText(Locale locale) { + Translator fallback = Util.createPackageTranslator(CourseNodeConfiguration.class, locale); + Translator translator = Util.createPackageTranslator(this.getClass(), locale, fallback); + return translator.translate("title_video"); + } + + @Override + public String getIconCSSClass() { + return "o_FileResource-MOVIE_icon"; + } + + @Override + public String getAlias() { + return "video"; + } + + @Override + public String getGroup() { + return CourseNodeGroup.content.name(); + } + + @Override + public boolean isEnabled() { + return CoreSpringFactory.getImpl(VideoModule.class).isCoursenodeEnabled(); + } + +} diff --git a/src/main/java/org/olat/course/nodes/video/VideoEditController.java b/src/main/java/org/olat/course/nodes/video/VideoEditController.java new file mode 100644 index 0000000000000000000000000000000000000000..feffe2b50d13b9e4c753c045ff7fa3c8b4f43841 --- /dev/null +++ b/src/main/java/org/olat/course/nodes/video/VideoEditController.java @@ -0,0 +1,370 @@ +package org.olat.course.nodes.video; + +import java.util.HashMap; +import java.util.Map; + +import org.olat.core.CoreSpringFactory; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.Component; +import org.olat.core.gui.components.form.flexible.FormItem; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.FormUIFactory; +import org.olat.core.gui.components.form.flexible.elements.RichTextElement; +import org.olat.core.gui.components.form.flexible.elements.SelectionElement; +import org.olat.core.gui.components.form.flexible.elements.SingleSelection; +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.link.Link; +import org.olat.core.gui.components.link.LinkFactory; +import org.olat.core.gui.components.panel.Panel; +import org.olat.core.gui.components.stack.BreadcrumbPanel; +import org.olat.core.gui.components.tabbedpane.TabbedPane; +import org.olat.core.gui.components.velocity.VelocityContainer; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.ControllerEventListener; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.control.generic.closablewrapper.CloseableModalController; +import org.olat.core.gui.control.generic.tabbable.ActivateableTabbableDefaultController; +import org.olat.core.logging.AssertException; +import org.olat.core.util.StringHelper; +import org.olat.course.ICourse; +import org.olat.course.assessment.AssessmentHelper; +import org.olat.course.condition.Condition; +import org.olat.course.condition.ConditionEditController; +import org.olat.course.editor.NodeEditController; +import org.olat.course.nodes.VideoCourseNode; +import org.olat.course.run.userview.UserCourseEnvironment; +import org.olat.fileresource.types.VideoFileResource; +import org.olat.modules.ModuleConfiguration; +import org.olat.modules.video.managers.VideoManager; +import org.olat.modules.video.ui.VideoDisplayController; +import org.olat.repository.RepositoryEntry; +import org.olat.repository.RepositoryManager; +import org.olat.repository.controllers.ReferencableEntriesSearchController; +import org.olat.resource.OLATResource; + +public class VideoEditController extends ActivateableTabbableDefaultController implements ControllerEventListener { + + public static final String PANE_TAB_VIDEOCONFIG = "pane.tab.videoconfig"; + private static final String PANE_TAB_ACCESSIBILITY = "pane.tab.accessibility"; + final static String[] paneKeys = { PANE_TAB_VIDEOCONFIG, PANE_TAB_ACCESSIBILITY }; + + // NLS support: + public static final String NLS_ERROR_VIDEOREPOENTRYMISSING = "error.videorepoentrymissing"; + private static final String NLS_CONDITION_ACCESSIBILITY_TITLE = "condition.accessibility.title"; + private static final String NLS_COMMAND_CHOOSEVIDEO = "command.choosevideo"; + private static final String NLS_COMMAND_CREATEVID = "command.createvideo"; + private static final String NLS_COMMAND_CHANGEVID = "command.changevideo"; + + public static final String CONFIG_KEY_REPOSITORY_SOFTKEY = "reporef"; + public static final String CONFIG_KEY_AUTOPLAY = "autoplay"; + public static final String CONFIG_KEY_COMMENTS = "comments"; + public static final String CONFIG_KEY_RATING = "rating"; + public static final String CONFIG_KEY_DESCRIPTION_SELECT = "descriptionSelect"; + public static final String CONFIG_KEY_DESCRIPTION_CUSTOMTEXT = "descriptionText"; + + private static final String VC_CHOSENVIDEO = "chosenvideo"; + private static final String NLS_NO_VIDEO_CHOSEN = "no.video.chosen"; + + protected FormUIFactory uifactory = FormUIFactory.getInstance(); + + private Panel main; + private VelocityContainer videoConfigurationVc; + + private ModuleConfiguration config; + private RepositoryEntry re; + + private ReferencableEntriesSearchController searchController; + + private Link previewLink; + private Link chooseVideoButton; + private Link changeVideoButton; + + private ConditionEditController accessibilityCondContr; + private Controller previewCtr; + private TabbedPane myTabbedPane; + private CloseableModalController cmc; + + public VideoEditController(VideoCourseNode videoNode, UserRequest ureq, WindowControl wControl, BreadcrumbPanel stackPanel,ICourse course, UserCourseEnvironment euce) { + super(ureq, wControl); + this.config = videoNode.getModuleConfiguration(); + main = new Panel("videomain"); + + videoConfigurationVc = createVelocityContainer("edit"); + chooseVideoButton = LinkFactory.createButtonSmall(NLS_COMMAND_CREATEVID, videoConfigurationVc, this); + chooseVideoButton.setElementCssClass("o_sel_cp_choose_repofile"); + changeVideoButton = LinkFactory.createButtonSmall(NLS_COMMAND_CHANGEVID, videoConfigurationVc, this); + changeVideoButton.setElementCssClass("o_sel_cp_change_repofile"); + + if (config.get(CONFIG_KEY_REPOSITORY_SOFTKEY) != null) { + // fetch repository entry to display the repository entry title of the chosen cp + re = getVideoReference(config, false); + if (re == null) { // we cannot display the entries name, because the + // repository entry had been deleted between the time when it was chosen here, and now + showError(NLS_ERROR_VIDEOREPOENTRYMISSING); + videoConfigurationVc.contextPut("showPreviewButton", Boolean.FALSE); + videoConfigurationVc.contextPut(VC_CHOSENVIDEO, translate("no.video.chosen")); + } else { + videoConfigurationVc.contextPut("showPreviewButton", Boolean.TRUE); + String displayname = StringHelper.escapeHtml(re.getDisplayname()); + previewLink = LinkFactory.createCustomLink("command.preview", "command.preview", displayname, Link.NONTRANSLATED, videoConfigurationVc, this); + previewLink.setTitle(getTranslator().translate("command.preview")); + previewLink.setEnabled(true); + } + videoConfigurationVc.contextPut("showOptions", Boolean.TRUE); + VideoOptionsForm videoOptions = new VideoOptionsForm(ureq, getWindowControl(), re.getOlatResource(), config); + videoConfigurationVc.put("videoOptions", videoOptions.getInitialComponent()); + listenTo(videoOptions); + } else { + // no valid config yet + videoConfigurationVc.contextPut("showPreviewButton", Boolean.FALSE); + videoConfigurationVc.contextPut("showOptions", Boolean.FALSE); + videoConfigurationVc.contextPut(VC_CHOSENVIDEO, translate(NLS_NO_VIDEO_CHOSEN)); + } + + + + // Accessibility precondition + Condition accessCondition = videoNode.getPreConditionAccess(); + accessibilityCondContr = new ConditionEditController(ureq, getWindowControl(), + accessCondition, AssessmentHelper.getAssessableNodes(course.getEditorTreeModel(), videoNode), euce); + listenTo(accessibilityCondContr); + + main.setContent(videoConfigurationVc); + } + + @Override + public void addTabs(TabbedPane tabbedPane) { + myTabbedPane = tabbedPane; + + tabbedPane.addTab(translate(PANE_TAB_ACCESSIBILITY), accessibilityCondContr.getWrappedDefaultAccessConditionVC(translate(NLS_CONDITION_ACCESSIBILITY_TITLE))); + tabbedPane.addTab(translate(PANE_TAB_VIDEOCONFIG), main); + } + + @Override + public String[] getPaneKeys() { + return paneKeys; + } + + @Override + public TabbedPane getTabbedPane() { + return myTabbedPane; + } + + @Override + protected void event(UserRequest ureq, Component source, Event event) { + if (source == chooseVideoButton || source == changeVideoButton) { + removeAsListenerAndDispose(searchController); + searchController = new ReferencableEntriesSearchController(getWindowControl(), ureq, new String[] {VideoFileResource.TYPE_NAME}, translate(NLS_COMMAND_CHOOSEVIDEO), true, false, false, false); + listenTo(searchController); + + removeAsListenerAndDispose(cmc); + cmc = new CloseableModalController( + getWindowControl(), translate("close"), searchController.getInitialComponent(), true, translate(NLS_COMMAND_CHOOSEVIDEO) + ); + listenTo(cmc); + cmc.activate(); + } + if(source == previewLink){ + VideoDisplayController previewController = null; + switch(config.getStringValue(VideoEditController.CONFIG_KEY_DESCRIPTION_SELECT)){ + + case "resourceDescription": + previewController = new VideoDisplayController(ureq, getWindowControl(), re, config.getBooleanSafe(VideoEditController.CONFIG_KEY_AUTOPLAY), config.getBooleanSafe(VideoEditController.CONFIG_KEY_COMMENTS), config.getBooleanSafe(VideoEditController.CONFIG_KEY_RATING), "", false, ""); + break; + case "customDescription": + previewController = new VideoDisplayController(ureq, getWindowControl(), re, config.getBooleanSafe(VideoEditController.CONFIG_KEY_AUTOPLAY), config.getBooleanSafe(VideoEditController.CONFIG_KEY_COMMENTS), config.getBooleanSafe(VideoEditController.CONFIG_KEY_RATING), "", true, config.getStringValue(VideoEditController.CONFIG_KEY_DESCRIPTION_CUSTOMTEXT)); + break; + case "none": + previewController = new VideoDisplayController(ureq, getWindowControl(), re, config.getBooleanSafe(VideoEditController.CONFIG_KEY_AUTOPLAY), config.getBooleanSafe(VideoEditController.CONFIG_KEY_COMMENTS), config.getBooleanSafe(VideoEditController.CONFIG_KEY_RATING), "", true, ""); + break; + } + cmc = new CloseableModalController( + getWindowControl(), translate("close"), previewController.getInitialComponent(), true, translate(NLS_COMMAND_CHOOSEVIDEO) + ); + listenTo(cmc); + cmc.activate(); + } + } + + /** + * @see org.olat.core.gui.control.DefaultController#event(org.olat.core.gui.UserRequest, + * org.olat.core.gui.control.Controller, org.olat.core.gui.control.Event) + */ + public void event(UserRequest urequest, Controller source, Event event) { + if (source == searchController) { + if (event == ReferencableEntriesSearchController.EVENT_REPOSITORY_ENTRY_SELECTED) { + // search controller done + // -> close closeable modal controller + cmc.deactivate(); + re = searchController.getSelectedEntry(); + if (re != null) { + setVideoReference(re, config); + videoConfigurationVc.contextPut("showPreviewButton", Boolean.TRUE); + String displayname = StringHelper.escapeHtml(re.getDisplayname()); + previewLink = LinkFactory.createCustomLink("command.preview", "command.preview", displayname, Link.NONTRANSLATED, videoConfigurationVc, this); + previewLink.setCustomEnabledLinkCSS("o_preview"); + previewLink.setTitle(getTranslator().translate("command.preview")); + // fire event so the updated config is saved by the editormaincontroller + fireEvent(urequest, NodeEditController.NODECONFIG_CHANGED_EVENT); + + videoConfigurationVc.contextPut("showOptions", Boolean.TRUE); + VideoOptionsForm videoOptions = new VideoOptionsForm(urequest, getWindowControl(), re.getOlatResource(), config); + videoConfigurationVc.put("videoOptions", videoOptions.getInitialComponent()); + listenTo(videoOptions); + } + } + } + + if (event == NodeEditController.NODECONFIG_CHANGED_EVENT){ + fireEvent(urequest, NodeEditController.NODECONFIG_CHANGED_EVENT); + } + } + @Override + protected void doDispose() { + //child controllers registered with listenTo() get disposed in BasicController + if (previewCtr != null) { + previewCtr.dispose(); + previewCtr = null; + } + } + + public static RepositoryEntry getVideoReference(ModuleConfiguration config, boolean strict) { + if (config == null) { + if (strict) throw new AssertException("missing config in Video"); + else return null; + } + String repoSoftkey = (String) config.get(VideoEditController.CONFIG_KEY_REPOSITORY_SOFTKEY); + if (repoSoftkey == null) { + if (strict) throw new AssertException("invalid config when being asked for references"); + else return null; + } + RepositoryManager rm = RepositoryManager.getInstance(); + RepositoryEntry entry = rm.lookupRepositoryEntryBySoftkey(repoSoftkey, strict); + // entry can be null only if !strict + return entry; + } + + /** + * @param moduleConfiguration + * @return boolean + */ + public static boolean isModuleConfigValid(ModuleConfiguration moduleConfiguration) { + return (moduleConfiguration.get(CONFIG_KEY_REPOSITORY_SOFTKEY) != null); + } + + /** + * Set the referenced repository entry. + * + * @param re + * @param moduleConfiguration + */ + public static void setVideoReference(RepositoryEntry re, ModuleConfiguration moduleConfiguration) { + moduleConfiguration.set(CONFIG_KEY_REPOSITORY_SOFTKEY, re.getSoftkey()); + } + + public static void removeVideoReference(ModuleConfiguration moduleConfiguration){ + moduleConfiguration.remove(CONFIG_KEY_REPOSITORY_SOFTKEY); + } +} + +class VideoOptionsForm extends FormBasicController{ + + /** + * Simple form for the Videooptions + * + * @author Dirk Furrer + */ + protected VideoManager videoManager = CoreSpringFactory.getImpl(VideoManager.class); + + private OLATResource video; + private SelectionElement videoComments; + private SelectionElement videoRating; + private SelectionElement videoAutoplay; + private SingleSelection description; + private RichTextElement descriptionField; + private boolean commentsEnabled; + private boolean ratingEnabled; + private boolean autoplay; + private ModuleConfiguration config; + + + + VideoOptionsForm(UserRequest ureq, WindowControl wControl, OLATResource video, ModuleConfiguration moduleConfiguration) { + super(ureq, wControl); + this.config = moduleConfiguration; + this.video = video; + this.commentsEnabled = config.getBooleanSafe(VideoEditController.CONFIG_KEY_COMMENTS); + this.ratingEnabled = config.getBooleanSafe(VideoEditController.CONFIG_KEY_RATING); + this.autoplay = config.getBooleanSafe(VideoEditController.CONFIG_KEY_AUTOPLAY); + initForm(ureq); + } + + public boolean isVideoComments() { + return videoComments.isSelected(0); + } + + @Override + protected void formOK(UserRequest ureq) { + config.setBooleanEntry(VideoEditController.CONFIG_KEY_COMMENTS, videoComments.isSelected(0)); + config.setBooleanEntry(VideoEditController.CONFIG_KEY_RATING, videoRating.isSelected(0)); + config.setBooleanEntry(VideoEditController.CONFIG_KEY_AUTOPLAY, videoAutoplay.isSelected(0)); + config.setStringValue(VideoEditController.CONFIG_KEY_DESCRIPTION_SELECT, description.getSelectedKey()); + if(description.getSelectedKey() == "customDescription"){ + config.setStringValue(VideoEditController.CONFIG_KEY_DESCRIPTION_CUSTOMTEXT, descriptionField.getValue()); + } + fireEvent(ureq, NodeEditController.NODECONFIG_CHANGED_EVENT); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + videoComments = uifactory.addCheckboxesHorizontal("videoComments", "video.config.comments", formLayout, new String[]{"xx"}, new String[]{null}); + videoComments.select("xx",commentsEnabled); + videoRating = uifactory.addCheckboxesHorizontal("videoRating", "video.config.rating", formLayout, new String[]{"xx"}, new String[]{null}); + videoRating.select("xx",ratingEnabled); + videoAutoplay = uifactory.addCheckboxesHorizontal("videoAutoplay", "video.config.autoplay", formLayout, new String[]{"xx"}, new String[]{null}); + videoAutoplay.select("xx",autoplay); + + Map<String, String> descriptionOptions = new HashMap<String, String>(); + descriptionOptions.put("none" ,"none");//TODO: internationalize + descriptionOptions.put("resourceDescription", "Resource description"); + descriptionOptions.put("customDescription", "custom description"); + + description = uifactory.addDropdownSingleselect("video.config.description", formLayout, descriptionOptions.keySet().toArray(new String[3]), descriptionOptions.values().toArray(new String[3]), new String[3]); + description.addActionListener(FormEvent.ONCHANGE); + description.select(config.getStringValue(VideoEditController.CONFIG_KEY_DESCRIPTION_SELECT,"none"), true); + descriptionField = uifactory.addRichTextElementForStringDataMinimalistic("description", "", videoManager.getDescription(video), -1, -1, formLayout, getWindowControl()); + updateDescriptionField(); + uifactory.addFormSubmitButton("submit", formLayout); + } + + private void updateDescriptionField(){ + switch(description.getSelected()){ + case 2: + descriptionField.setVisible(false); + break; + case 1: + descriptionField.setVisible(true); + descriptionField.setValue(videoManager.getDescription(video)); + descriptionField.setEnabled(false); + break; + case 0: + descriptionField.setVisible(true); + descriptionField.setValue(config.getStringValue(VideoEditController.CONFIG_KEY_DESCRIPTION_CUSTOMTEXT, "")); + descriptionField.setEnabled(true); + break; + } + } + + @Override + protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { + if(source == description){ + updateDescriptionField(); + } + } + @Override + protected void doDispose() { + // + } +} diff --git a/src/main/java/org/olat/course/nodes/video/VideoPeekviewController.java b/src/main/java/org/olat/course/nodes/video/VideoPeekviewController.java new file mode 100644 index 0000000000000000000000000000000000000000..baacc298b7b00e4743a05df4710baf564533dade --- /dev/null +++ b/src/main/java/org/olat/course/nodes/video/VideoPeekviewController.java @@ -0,0 +1,33 @@ +package org.olat.course.nodes.video; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.Component; +import org.olat.core.gui.components.velocity.VelocityContainer; +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.gui.control.controller.BasicController; +import org.olat.core.util.vfs.VFSContainer; +import org.olat.modules.video.managers.MediaMapper; + +public class VideoPeekviewController extends BasicController implements Controller{ + + public VideoPeekviewController(UserRequest ureq, WindowControl wControl, VFSContainer posterFolder) { + super(ureq, wControl); + VelocityContainer peekviewVC = createVelocityContainer("peekview"); + String mediaUrl = registerMapper(ureq, new MediaMapper(posterFolder)); + peekviewVC.contextPut("mediaUrl", mediaUrl); + peekviewVC.contextPut("nodeLink", posterFolder); + putInitialPanel(peekviewVC); + } + + @Override + protected void event(UserRequest ureq, Component source, Event event) { + } + + @Override + protected void doDispose() { + // TODO Auto-generated method stub + } + +} \ No newline at end of file diff --git a/src/main/java/org/olat/course/nodes/video/VideoRunController.java b/src/main/java/org/olat/course/nodes/video/VideoRunController.java new file mode 100644 index 0000000000000000000000000000000000000000..cc65bb9d790b50f63bbd769b9a549f7244336067 --- /dev/null +++ b/src/main/java/org/olat/course/nodes/video/VideoRunController.java @@ -0,0 +1,150 @@ +package org.olat.course.nodes.video; + +import java.io.File; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.Component; +import org.olat.core.gui.components.panel.Panel; +import org.olat.core.gui.components.velocity.VelocityContainer; +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.gui.control.controller.BasicController; +import org.olat.core.id.OLATResourceable; +import org.olat.core.id.context.BusinessControl; +import org.olat.core.id.context.ContextEntry; +import org.olat.core.logging.AssertException; +import org.olat.core.logging.OLog; +import org.olat.core.logging.Tracing; +import org.olat.course.nodes.TitledWrapperHelper; +import org.olat.course.nodes.VideoCourseNode; +import org.olat.course.nodes.cp.CPRunController; +import org.olat.course.run.navigation.NodeRunConstructionResult; +import org.olat.modules.ModuleConfiguration; +import org.olat.modules.video.ui.VideoDisplayController; +import org.olat.repository.RepositoryEntry; +import org.olat.repository.RepositoryManager; +import org.olat.util.logging.activity.LoggingResourceable; +import org.springframework.beans.factory.annotation.Autowired; + +public class VideoRunController extends BasicController { + private static final OLog log = Tracing.createLoggerFor(CPRunController.class); + + private ModuleConfiguration config; + private File videoRoot; + private Panel main; + + private VideoDisplayController videoDispCtr; + private VideoCourseNode videoNode; + + + @Autowired + private RepositoryManager repositoryManager; + + /** + * Constructor for single page run controller + * @param wControl + * @param ureq + * @param userCourseEnv + * @param videoNode + */ + public VideoRunController(ModuleConfiguration config, WindowControl wControl, UserRequest ureq, VideoCourseNode videoNode) { + super(ureq,wControl); + + // assertion to make sure the moduleconfig is valid + if (!VideoEditController.isModuleConfigValid(config)) throw new AssertException("videorun controller had an invalid module config:" + config.toString()); + this.config = config; + this.videoNode = videoNode; + addLoggingResourceable(LoggingResourceable.wrap(videoNode)); + + // jump to either the forum or the folder if the business-launch-path says so. + BusinessControl bc = getWindowControl().getBusinessControl(); + ContextEntry ce = bc.popLauncherContextEntry(); + if ( ce != null ) { // a context path is left for me + if(log.isDebug()) log.debug("businesscontrol (for further jumps) would be:"+bc); + OLATResourceable popOres = ce.getOLATResourceable(); + if(log.isDebug()) log.debug("OLATResourceable=" + popOres); + String typeName = popOres.getResourceableTypeName(); + // typeName format: 'path=/test1/test2/readme.txt' + // First remove prefix 'path=' + String path = typeName.substring("path=".length()); + if (path.length() > 0) { + if(log.isDebug()) log.debug("direct navigation to container-path=" + path); +// this.nodecmd = path; + } + } + + + main = new Panel("videorunmain"); + doLaunch(ureq); + putInitialPanel(main); + } + + /** + * @see org.olat.core.gui.control.DefaultController#event(org.olat.core.gui.UserRequest, org.olat.core.gui.components.Component, org.olat.core.gui.control.Event) + */ + public void event(UserRequest ureq, Component source, Event event) { + + } + + @Override + protected void event(UserRequest ureq, Controller source, Event event) { + if(source == videoDispCtr){ + if(VideoDisplayController.ENDED_EVENT.equals(event)){ + //TODO: catch even fired when video ended + } + } + } + + /** + * + * @see org.olat.core.gui.control.DefaultController#doDispose(boolean) + */ + @Override + protected void doDispose() { + if (videoDispCtr != null) { + videoDispCtr.dispose(); + videoDispCtr = null; + } + } + + private void doLaunch(UserRequest ureq){ + VelocityContainer myContent = createVelocityContainer("run"); + if (videoRoot == null) { + RepositoryEntry re = VideoEditController.getVideoReference(config, false); + if (re == null) { + showError(VideoEditController.NLS_ERROR_VIDEOREPOENTRYMISSING); + return; + } + + + } + switch(config.getStringValue(VideoEditController.CONFIG_KEY_DESCRIPTION_SELECT,"none")){ + case "resourceDescription": + videoDispCtr = new VideoDisplayController(ureq, getWindowControl(), videoNode.getReferencedRepositoryEntry(), config.getBooleanSafe(VideoEditController.CONFIG_KEY_AUTOPLAY), config.getBooleanSafe(VideoEditController.CONFIG_KEY_COMMENTS), config.getBooleanSafe(VideoEditController.CONFIG_KEY_RATING), videoNode.getIdent(), false, ""); + break; + case "customDescription": + videoDispCtr = new VideoDisplayController(ureq, getWindowControl(), videoNode.getReferencedRepositoryEntry(), config.getBooleanSafe(VideoEditController.CONFIG_KEY_AUTOPLAY), config.getBooleanSafe(VideoEditController.CONFIG_KEY_COMMENTS), config.getBooleanSafe(VideoEditController.CONFIG_KEY_RATING), videoNode.getIdent(), true, config.getStringValue(VideoEditController.CONFIG_KEY_DESCRIPTION_CUSTOMTEXT)); + break; + case "none": + videoDispCtr = new VideoDisplayController(ureq, getWindowControl(), videoNode.getReferencedRepositoryEntry(), config.getBooleanSafe(VideoEditController.CONFIG_KEY_AUTOPLAY), config.getBooleanSafe(VideoEditController.CONFIG_KEY_COMMENTS), config.getBooleanSafe(VideoEditController.CONFIG_KEY_RATING), videoNode.getIdent(), true, ""); + break; + } + + videoDispCtr.addControllerListener(this); + + myContent.put("videoDisplay", videoDispCtr.getInitialComponent()); + main.setContent(myContent); + } + + + + public NodeRunConstructionResult createNodeRunConstructionResult(UserRequest ureq) { + NodeRunConstructionResult ncr; + + Controller ctrl = TitledWrapperHelper.getWrapper(ureq, getWindowControl(), this, videoNode, "o_cp_icon"); + ncr = new NodeRunConstructionResult(ctrl); + + return ncr; + } +} diff --git a/src/main/java/org/olat/course/nodes/video/_content/edit.html b/src/main/java/org/olat/course/nodes/video/_content/edit.html new file mode 100644 index 0000000000000000000000000000000000000000..94297ce07a987e6fd966d1fb0570f40f906bf312 --- /dev/null +++ b/src/main/java/org/olat/course/nodes/video/_content/edit.html @@ -0,0 +1,30 @@ +<fieldset class="o_form form-horizontal clearfix"> + <legend> + $r.translate("header")</legend> + + #if ($showPreviewButton) + <div class="form-group"> + <label class="control-label col-sm-3">$r.translate("chosenvideo")</label> + <div class="col-sm-9"><p class="form-control-static">$r.render("command.preview")</p></div> + </div> + <div class="form-group"> + <div class="col-sm-offset-3 col-sm-9"> + $r.render("command.changevideo") + </div> + </div> + #else + <div class="form-group"> + <label class="control-label col-sm-3">$r.translate("chosenvideo")</label> + <div class="col-sm-9"><p class="form-control-static">$chosenvideo</p></div> + </div> + <div class="form-group"> + <div class="col-sm-offset-3 col-sm-9">$r.render("command.createvideo")</div> + </div> + #end + + #if ($showOptions) + <legend>$r.translate("optionsSection")</legend> + $r.render("videoOptions") + #end +</fieldset> + diff --git a/src/main/java/org/olat/course/nodes/video/_content/peekview.html b/src/main/java/org/olat/course/nodes/video/_content/peekview.html new file mode 100644 index 0000000000000000000000000000000000000000..cdb432b9a8bef517600ec117cc68b9c6685f8d7d --- /dev/null +++ b/src/main/java/org/olat/course/nodes/video/_content/peekview.html @@ -0,0 +1,2 @@ + + <img src="$mediaUrl/poster.jpg" alt="poster" /> diff --git a/src/main/java/org/olat/course/nodes/video/_content/run.html b/src/main/java/org/olat/course/nodes/video/_content/run.html new file mode 100644 index 0000000000000000000000000000000000000000..71113c0931420ecf2bcabbee185d5218c78ee791 --- /dev/null +++ b/src/main/java/org/olat/course/nodes/video/_content/run.html @@ -0,0 +1 @@ +$r.render("videoDisplay") diff --git a/src/main/java/org/olat/course/nodes/video/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/course/nodes/video/_i18n/LocalStrings_de.properties new file mode 100644 index 0000000000000000000000000000000000000000..3b81d60bf0d8cc32358091890e2f5264b658b937 --- /dev/null +++ b/src/main/java/org/olat/course/nodes/video/_i18n/LocalStrings_de.properties @@ -0,0 +1,19 @@ +chosenvideo=ausgewählte Videoresource +command.changevideo=Videoresource ersetzten +command.choosevideo=Videoresource auswählen +command.closevideo=Fenster schliessen +command.createvideo=Videoressource auswählen oder importieren +title_video=Video +condition.accessibility.title=Zugang +header=Video auswählen +optionsSection=Optionen +no.video.chosen=Kein Video ausgew\u00E4hlt +preview.video=Vorschau +title_cvideo=Videoressource +pane.tab.accessibility=Zugang +pane.tab.videoconfig=Video +pane.tab.deliveryOptions=$org.olat.core.gui.control.generic.iframe\:option.delivery +video.config.comments=Video Kommentare +video.config.rating=Video Bewertung +video.config.autoplay=Video automatisch wiedergeben +video.config.description=Beschreibung \ No newline at end of file diff --git a/src/main/java/org/olat/course/nodes/video/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/course/nodes/video/_i18n/LocalStrings_en.properties new file mode 100644 index 0000000000000000000000000000000000000000..cf46a52933aa2dfa758c09e40f3445e6c1f12cd1 --- /dev/null +++ b/src/main/java/org/olat/course/nodes/video/_i18n/LocalStrings_en.properties @@ -0,0 +1,19 @@ +chosenvideo=Selected Videoressource +command.changevideo=Replace Videoressource +command.choosevideo=Choose Videoressource +command.closevideo=Close window +command.createvideo=Select or import Videoressource +title_video=Video +condition.accessibility.title=Access +header=Select Video +optionsSection=Options +no.video.chosen=No Videoressource selected +preview.video=Preview +title_cvideo=Videoressource +pane.tab.accessibility=Zugang +pane.tab.videoconfig=Video +pane.tab.deliveryOptions=$org.olat.core.gui.control.generic.iframe\:option.delivery +video.config.comments=Video comments +video.config.rating=Video rating +video.config.autoplay=Video autoplay +video.config.description=Description \ No newline at end of file diff --git a/src/main/java/org/olat/fileresource/types/MovieFileResource.java b/src/main/java/org/olat/fileresource/types/MovieFileResource.java index 1b106df5189171ed32e949bb0d5522474501b46a..3f6e7d779eb5eedd317e3d18693a8955e6a63cbb 100644 --- a/src/main/java/org/olat/fileresource/types/MovieFileResource.java +++ b/src/main/java/org/olat/fileresource/types/MovieFileResource.java @@ -56,7 +56,6 @@ public class MovieFileResource extends FileResource { return f.endsWith(".avi") || f.endsWith(".mpeg") || f.endsWith(".mpg") || f.endsWith(".qt") || f.endsWith(".rm") || f.endsWith(".ram") || - f.endsWith(".mp4") || f.endsWith(".m4v") || - f.endsWith(".mov"); + f.endsWith(".m4v"); } } diff --git a/src/main/java/org/olat/fileresource/types/VideoFileResource.java b/src/main/java/org/olat/fileresource/types/VideoFileResource.java new file mode 100644 index 0000000000000000000000000000000000000000..0b932116944b639318db6a6c8e27790618b90a22 --- /dev/null +++ b/src/main/java/org/olat/fileresource/types/VideoFileResource.java @@ -0,0 +1,64 @@ +/** +* OLAT - Online Learning and Training<br> +* http://www.olat.org +* <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 +* <p> +* http://www.apache.org/licenses/LICENSE-2.0 +* <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> +* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br> +* University of Zurich, Switzerland. +* <hr> +* <a href="http://www.openolat.org"> +* OpenOLAT - Online Learning and Training</a><br> +* This file has been modified by the OpenOLAT community. Changes are licensed +* under the Apache 2.0 license as the original file. +*/ + +package org.olat.fileresource.types; + +import java.io.File; + +import org.olat.core.CoreSpringFactory; +import org.olat.modules.video.VideoModule; + +/** + * Initial Date: Mar 27, 2015 + * + * @author Dirk Furrer + */ +public class VideoFileResource extends FileResource { + + + private static VideoModule videomodule = CoreSpringFactory.getImpl(VideoModule.class); + + /** + * Movie file resource dientifier. + */ + public static final String TYPE_NAME = "FileResource.VIDEO"; + + public VideoFileResource() { + super(TYPE_NAME); + } + + /** + * @param f file to validate + * @return True if is of type. + */ + public static boolean validate(File f) { + return validate(f.getName()); + } + + public static boolean validate(String filename) { + String f = filename.toLowerCase(); + return videomodule.isEnabled() && (f.endsWith(".mp4")|| f.endsWith(".mov")); + } +} diff --git a/src/main/java/org/olat/modules/_spring/modulesContext.xml b/src/main/java/org/olat/modules/_spring/modulesContext.xml index ae070c234f3088a3ed64d5792b3e9d6e5f05e842..09b396dc179c35f017023819add302e43a6ef4a8 100644 --- a/src/main/java/org/olat/modules/_spring/modulesContext.xml +++ b/src/main/java/org/olat/modules/_spring/modulesContext.xml @@ -18,6 +18,7 @@ <import resource="classpath:/org/olat/modules/webFeed/_spring/webFeedContext.xml"/> <import resource="classpath:/org/olat/modules/wiki/_spring/wikiContext.xml"/> <import resource="classpath:/org/olat/modules/reminder/_spring/reminderContext.xml"/> + <import resource="classpath:/org/olat/modules/video/_spring/videoContext.xml"/> diff --git a/src/main/java/org/olat/modules/video/VideoModule.java b/src/main/java/org/olat/modules/video/VideoModule.java new file mode 100644 index 0000000000000000000000000000000000000000..15095212ae984d9fa49bbdd9db7edb9d7a8211ed --- /dev/null +++ b/src/main/java/org/olat/modules/video/VideoModule.java @@ -0,0 +1,107 @@ +/** + * <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 org.olat.core.configuration.AbstractSpringModule; +import org.olat.core.util.StringHelper; +import org.olat.core.util.coordinate.CoordinatorManager; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +/** + * Initial date: 23.2.2016<br> + * @author dfakae, dirk.furrer@frentix.com, http://www.frentix.com + * + */ +@Service +public class VideoModule extends AbstractSpringModule { + + private static final String VIDEO_ENABLED = "video.enabled"; + private static final String VIDEOCOURSENODE_ENABLED = "video.coursenode.enabled"; + private static final String VIDEOTRANSCODING_ENABLED = "video.transcoding.enabled"; + + @Value("${video.enabled:true}") + private boolean enabled; + @Value("${video.coursenode.enabled:true}") + private boolean coursenodeEnabled; + @Value("${video.transcoding.enabled:false}") + private boolean transcodingEnabled; + + + @Autowired + public VideoModule(CoordinatorManager coordinatorManager) { + super(coordinatorManager); + } + + @Override + public void init() { + String enabledObj = getStringPropertyValue(VIDEO_ENABLED, true); + if(StringHelper.containsNonWhitespace(enabledObj)) { + enabled = "true".equals(enabledObj); + } + + String enabledCoursenodeObj = getStringPropertyValue(VIDEOCOURSENODE_ENABLED, true); + if(StringHelper.containsNonWhitespace(enabledCoursenodeObj)) { + coursenodeEnabled = "true".equals(enabledCoursenodeObj); + } + + String enabledTranscodingObj = getStringPropertyValue(VIDEOTRANSCODING_ENABLED, true); + if(StringHelper.containsNonWhitespace(enabledTranscodingObj)) { + transcodingEnabled = "true".equals(enabledTranscodingObj); + } + + } + @Override + protected void initFromChangedProperties() { + init(); + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + setStringProperty(VIDEO_ENABLED, Boolean.toString(enabled), true); + } + + + public boolean isCoursenodeEnabled() { + return (coursenodeEnabled && enabled); + + } + + + public void setCoursenodeEnabled(boolean coursenodeEnabled) { + this.coursenodeEnabled = coursenodeEnabled; + setStringProperty(VIDEOCOURSENODE_ENABLED, Boolean.toString(this.coursenodeEnabled), true); + } + + + public boolean isTranscodingEnabled() { + return (transcodingEnabled && enabled); + } + + public void setTranscodingEnabled(boolean transcodingEnabled) { + this.transcodingEnabled = transcodingEnabled; + setStringProperty(VIDEOTRANSCODING_ENABLED, Boolean.toString(transcodingEnabled), true); + } +} diff --git a/src/main/java/org/olat/modules/video/_spring/videoContext.xml b/src/main/java/org/olat/modules/video/_spring/videoContext.xml new file mode 100644 index 0000000000000000000000000000000000000000..ea8badeec0336981595ec61c1320c7f4e7c6f7bd --- /dev/null +++ b/src/main/java/org/olat/modules/video/_spring/videoContext.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> +<beans xmlns="http://www.springframework.org/schema/beans" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation=" + http://www.springframework.org/schema/beans + http://www.springframework.org/schema/beans/spring-beans.xsd"> + +<bean id="videoManager" class="org.olat.modules.video.managers.VideoManagerImpl"> +</bean> + +<bean id="transcodingService" class="org.olat.modules.video.service.TranscoderService"> +</bean> + + +<bean class="org.olat.core.extensions.action.GenericActionExtension" init-method="initExtensionPoints"> + <property name="order" value="7210" /> + <property name="actionController"> + <bean class="org.olat.core.gui.control.creator.AutoCreator" scope="prototype"> + <property name="className" value="org.olat.modules.video.ui.VideoAdminController"/> + </bean> + </property> + <property name="navigationKey" value="video" /> + <property name="parentTreeNodeIdentifier" value="modulesParent" /> + <property name="i18nActionKey" value="admin.menu.title"/> + <property name="i18nDescriptionKey" value="admin.menu.video.alt"/> + <property name="translationPackage" value="org.olat.modules.video.ui"/> + <property name="extensionPoints"> + <list> + <value>org.olat.admin.SystemAdminMainController</value> + </list> + </property> + </bean> + +</beans> \ No newline at end of file diff --git a/src/main/java/org/olat/modules/video/managers/MediaMapper.java b/src/main/java/org/olat/modules/video/managers/MediaMapper.java new file mode 100644 index 0000000000000000000000000000000000000000..aeb285412fd86b57c4728e518251018573f28f15 --- /dev/null +++ b/src/main/java/org/olat/modules/video/managers/MediaMapper.java @@ -0,0 +1,30 @@ +package org.olat.modules.video.managers; + +import javax.servlet.http.HttpServletRequest; + +import org.olat.core.dispatcher.mapper.Mapper; +import org.olat.core.gui.media.MediaResource; +import org.olat.core.gui.media.NotFoundMediaResource; +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; + +public class MediaMapper implements Mapper { + + private final VFSContainer mediaBase; + + public MediaMapper(VFSContainer mediaBase) { + this.mediaBase = mediaBase; + } + + @Override + public MediaResource handle(String relPath, HttpServletRequest request) { + VFSItem mediaFile = mediaBase.resolve(relPath); + if (mediaFile instanceof VFSLeaf){ + return new VFSMediaResource((VFSLeaf)mediaFile); + } else { + return new NotFoundMediaResource(relPath); + } + } +} diff --git a/src/main/java/org/olat/modules/video/managers/VideoManager.java b/src/main/java/org/olat/modules/video/managers/VideoManager.java new file mode 100644 index 0000000000000000000000000000000000000000..a19f47d8fea7c994d512c8354f7d0f27a61577e7 --- /dev/null +++ b/src/main/java/org/olat/modules/video/managers/VideoManager.java @@ -0,0 +1,80 @@ +/** + * <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.managers; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; + +import org.olat.core.commons.services.image.Size; +import org.olat.core.manager.BasicManager; +import org.olat.core.util.vfs.VFSLeaf; +import org.olat.resource.OLATResource; + +/** + * The <code>VideoManager</code> singleton is responsible for dealing with video + * resources. + * + * Initial date: 01.04.2015<br> + * @author Dirk Furrer, dirk.furrer@frentix.com, http://www.frentix.com + */ +public abstract class VideoManager extends BasicManager { + + protected static VideoManager INSTANCE; + + public abstract File getVideoFile(OLATResource video); + + public abstract Size getVideoSize(OLATResource video); + + public abstract void setVideoSize(OLATResource video, Size size); + + public abstract VFSLeaf getPosterframe(OLATResource video); + + public abstract void setPosterframe(OLATResource video, VFSLeaf posterframe); + + public abstract void setTitle(OLATResource video, String title); + + public abstract String getTitle(OLATResource video); + + public abstract HashMap<String, VFSLeaf> getAllTracks(OLATResource video); + + public abstract void addTrack(OLATResource video, String lang, VFSLeaf trackFile); + + public abstract VFSLeaf getTrack(OLATResource video, String lang); + + public abstract void removeTrack(OLATResource video, String lang); + + public abstract void setCommentsEnabled(OLATResource video, boolean isEnabled); + + public abstract boolean getCommentsEnabled(OLATResource video); + + public abstract void setRatingEnabled(OLATResource video, boolean isEnabled); + + public abstract boolean getRatingEnabled(OLATResource video); + + public abstract boolean getFrame(OLATResource video, int frameNumber, VFSLeaf frame) throws IOException; + + public abstract void setDescription(OLATResource video, String text); + + public abstract String getDescription(OLATResource video); + + public abstract boolean optimizeVideoRessource(OLATResource video); + +} \ No newline at end of file diff --git a/src/main/java/org/olat/modules/video/managers/VideoManagerImpl.java b/src/main/java/org/olat/modules/video/managers/VideoManagerImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..1b1c6481f58b5833ac9ce6d5b370529f4c2216ff --- /dev/null +++ b/src/main/java/org/olat/modules/video/managers/VideoManagerImpl.java @@ -0,0 +1,256 @@ +package org.olat.modules.video.managers; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map.Entry; + +import javax.imageio.ImageIO; + +import org.jcodec.api.FrameGrab; +import org.jcodec.common.FileChannelWrapper; +import org.olat.core.commons.services.image.Size; +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.xml.XStreamHelper; +import org.olat.fileresource.FileResourceManager; +import org.olat.modules.video.models.VideoMetadata; +import org.olat.resource.OLATResource; +import org.springframework.stereotype.Service; + +@Service("videoManager") +public class VideoManagerImpl extends VideoManager { + private FileResourceManager fileResourceManager = FileResourceManager.getInstance(); + private RandomAccessFile randomAccessFile; + + public VideoManagerImpl() { + INSTANCE = this; + } + + @Override + public Size getVideoSize(OLATResource video) { + return readVideoMetadataFile(video).getSize(); + } + + @Override + public void setVideoSize(OLATResource video, Size size){ + VideoMetadata metaData = readVideoMetadataFile(video); + metaData.setSize(size); + writeVideoMetadataFile(metaData, video); + } + + @Override + public VFSLeaf getPosterframe(OLATResource video) { + String posterframePath = readVideoMetadataFile(video).getPosterframe(); + VFSLeaf posterFrame = resolve(video,posterframePath); + return posterFrame; + } + + @Override + public void setPosterframe(OLATResource video, VFSLeaf posterframe){ + VideoMetadata metaData = readVideoMetadataFile(video); + String oldPath = metaData.getPosterframe(); + if(oldPath != null){ + VFSLeaf oldPoster = resolve(video, metaData.getPosterframe()); + if(oldPoster != null){ + oldPoster.delete(); + } + } + + VFSLeaf newPoster = VFSManager.resolveOrCreateLeafFromPath(fileResourceManager.getFileResourceMedia(video), "/poster.jpg"); + + if(!newPoster.isSame(posterframe)){ + VFSManager.copyContent(posterframe, newPoster); + } + metaData.setPosterframe(newPoster.getName()); + writeVideoMetadataFile(metaData, video); + + } + + @Override + public void setTitle(OLATResource video, String title){ + VideoMetadata metaData = readVideoMetadataFile(video); + metaData.setTitle(title); + writeVideoMetadataFile(metaData, video); + } + + @Override + public String getTitle(OLATResource video) { + return readVideoMetadataFile(video).getTitle(); + } + + @Override + public void addTrack(OLATResource video, String lang, VFSLeaf trackFile){ + VideoMetadata metaData = readVideoMetadataFile(video); + metaData.addTrack(lang, trackFile.getName()); + writeVideoMetadataFile(metaData, video); + } + + @Override + public HashMap<String, VFSLeaf> getAllTracks(OLATResource video) { + VideoMetadata metaData = readVideoMetadataFile(video); + HashMap<String, VFSLeaf> tracks = new HashMap<String, VFSLeaf>(); + for(Entry<String, String> trackEntry : metaData.getAllTracks().entrySet()){ + tracks.put(trackEntry.getKey(), resolve(video, trackEntry.getValue())); + } + return tracks; + } + + @Override + public VFSLeaf getTrack(OLATResource video, String lang) { + VideoMetadata metaData = readVideoMetadataFile(video); + return resolve(video, metaData.getTrack(lang)); + } + + @Override + public void removeTrack(OLATResource video, String lang){ + VideoMetadata metaData = readVideoMetadataFile(video); + resolve(video, metaData.getTrack(lang)).delete(); + metaData.removeTrack(lang); + writeVideoMetadataFile(metaData, video); + } + + @Override + public void setCommentsEnabled(OLATResource video, boolean isEnabled) { + VideoMetadata metaData = readVideoMetadataFile(video); + metaData.setCommentsEnabled(isEnabled); + writeVideoMetadataFile(metaData, video); + } + + @Override + public boolean getCommentsEnabled(OLATResource video) { + VideoMetadata metaData = readVideoMetadataFile(video); + return metaData.getCommentsEnabled(); + } + + @Override + public void setRatingEnabled(OLATResource video, boolean isEnabled) { + VideoMetadata metaData = readVideoMetadataFile(video); + metaData.setRatingEnabled(isEnabled); + writeVideoMetadataFile(metaData, video); + } + + @Override + public boolean getRatingEnabled(OLATResource video) { + VideoMetadata metaData = readVideoMetadataFile(video); + return metaData.getRatingEnabled(); + } + + @Override + public boolean getFrame(OLATResource video, int frameNumber, VFSLeaf frame) throws IOException{ + File rootFolder = fileResourceManager.getFileResourceRoot(video); + File metaDataFile = new File(rootFolder, "media"); + File videoFile = new File(metaDataFile, "video.mp4"); + + randomAccessFile = new RandomAccessFile(videoFile, "r"); + FileChannel ch = randomAccessFile.getChannel(); + FileChannelWrapper in = new FileChannelWrapper(ch); + try{ + FrameGrab frameGrab = new FrameGrab(in).seekToFrameSloppy(frameNumber); + OutputStream frameOutputStream = frame.getOutputStream(true); + + BufferedImage bufImg = frameGrab.getFrame(); + ImageIO.write(bufImg, "JPG", frameOutputStream); + + return true; + }catch( Exception e){ + return false; + } + //TODO: throw right exception + } + + @Override + public void setDescription(OLATResource video, String text) { + VideoMetadata metaData = readVideoMetadataFile(video); + metaData.setDescription(text); + writeVideoMetadataFile(metaData, video); + } + + @Override + public String getDescription(OLATResource video) { + VideoMetadata metaData = readVideoMetadataFile(video); + return metaData.getDescription(); + } + + @Override + public File getVideoFile(OLATResource video) { + File rootFolder = fileResourceManager.getFileResourceRoot(video); + File metaDataFile = new File(rootFolder, "media"); + File videoFile = new File(metaDataFile, "video.mp4"); + return videoFile; + } + + + private VFSLeaf resolve(OLATResource video, String path){ + VFSItem item = VFSManager.resolveFile(fileResourceManager.getFileResourceMedia(video), path); + if(item instanceof VFSLeaf){ + return (VFSLeaf) item; + }else{ + return null; + } + } + + private void writeVideoMetadataFile(VideoMetadata metaData, OLATResource video){ + File videoResourceFileroot = FileResourceManager.getInstance().getFileResourceRootImpl(video).getBasefile(); + File metaDataFile = new File(videoResourceFileroot,"video_metadata.xml"); + XStreamHelper.writeObject(XStreamHelper.createXStreamInstance(), metaDataFile, metaData); + } + + private VideoMetadata readVideoMetadataFile(OLATResource video){ + File videoResourceFileroot = FileResourceManager.getInstance().getFileResourceRootImpl(video).getBasefile(); + File metaDataFile = new File(videoResourceFileroot, "video_metadata.xml"); + return (VideoMetadata) XStreamHelper.readObject(XStreamHelper.createXStreamInstance(), metaDataFile); + } + + @Override + public boolean optimizeVideoRessource(OLATResource video) { + File file = getVideoFile(video); + File videoResourceFileroot = fileResourceManager.getFileResourceRoot(video); + File optimizedFolder = new File(videoResourceFileroot, "optimizedVideoData"); + optimizedFolder.mkdirs(); + + ArrayList<String> cmd = new ArrayList<String>(); + + cmd.add("HandBrakeCLI"); + cmd.add("-i "+file.getAbsolutePath()); + cmd.add("-o "+optimizedFolder.getAbsolutePath()+"/optimized_"+file.getName()); + cmd.add("--optimize"); + cmd.add("--preset Normal"); + + ProcessBuilder pb = new ProcessBuilder(cmd); + pb.directory(optimizedFolder); + + pb.redirectErrorStream(true); + pb.inheritIO(); + + + try { + logInfo("+--------------------------HANDBRAKE STARTS TRANSCODING------------------------------------+"); + Runtime.getRuntime().exec("HandBrakeCLI "+" -i "+file.getAbsolutePath()+" -o "+optimizedFolder.getAbsolutePath()+"/optimized_"+file.getName()+" --optimize"+" --preset Normal"); + logInfo("+---------------------------HANDBRAKE TRANSCODING DONE-------------------------------------+"); + return true; + } catch (Exception e) { + System.err.println("Unable to do videotranscoding"); + return false; + } + + +// try { +// logInfo("+--------------------------HANDBRAKE STARTS TRANSCODING------------------------------------+"); +// Process process = pb.start(); +// process.waitFor(); +// logInfo("+---------------------------HANDBRAKE TRANSCODING DONE-------------------------------------+"); +// return true; +// } catch (Exception e) { +// return false; +// } + + } + +} diff --git a/src/main/java/org/olat/modules/video/models/VideoMetadata.java b/src/main/java/org/olat/modules/video/models/VideoMetadata.java new file mode 100644 index 0000000000000000000000000000000000000000..adff9b9123be37779c0ca89bc35e4b7224b00dc3 --- /dev/null +++ b/src/main/java/org/olat/modules/video/models/VideoMetadata.java @@ -0,0 +1,131 @@ +/** +* OLAT - Online Learning and Training<br> +* http://www.olat.org +* <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 +* <p> +* http://www.apache.org/licenses/LICENSE-2.0 +* <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> +* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br> +* University of Zurich, Switzerland. +* <hr> +* <a href="http://www.openolat.org"> +* OpenOLAT - Online Learning and Training</a><br> +* This file has been modified by the OpenOLAT community. Changes are licensed +* under the Apache 2.0 license as the original file. +*/ + +package org.olat.modules.video.models; + +import java.io.Serializable; +import java.util.HashMap; + +import org.olat.core.commons.services.image.Size; +import org.olat.core.id.OLATResourceable; + +/** + * Initial Date: Apr 9, 2015 + * + * @author Dirk Furrer + * + */ + +public class VideoMetadata implements Serializable{ + /** + * + */ + private static final long serialVersionUID = 1L; + + // Data model versioning + public static final int CURRENT_MODEL_VERSION = 1; + + // Properties + private String title; + + private Size size; + private String posterframe; + private HashMap<String, String> tracks; + private boolean commentsEnabled; + private boolean ratingEnabled; + private String description; + + private int modelVersion = 0; + + public VideoMetadata(OLATResourceable resource){ + this.tracks = new HashMap<String, String>(); + // new model constructor, set to current version + this.modelVersion = CURRENT_MODEL_VERSION; + } + + public Size getSize() { + return size; + } + + public void setSize(Size size) { + this.size = size; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getPosterframe() { + return posterframe; + } + + public void setPosterframe(String posterframe) { + this.posterframe = posterframe; + } + + public HashMap<String, String> getAllTracks(){ + return tracks; + } + + public void addTrack(String lang, String trackFile){ + tracks.put(lang, trackFile); + } + + public String getTrack(String lang){ + return tracks.get(lang); + } + + public void removeTrack(String lang){ + tracks.remove(lang); + } + + public void setCommentsEnabled(boolean isEnabled){ + this.commentsEnabled = isEnabled; + } + + public boolean getCommentsEnabled(){ + return this.commentsEnabled; + } + + public void setRatingEnabled(boolean isEnabled){ + this.ratingEnabled = isEnabled; + } + + public boolean getRatingEnabled(){ + return this.ratingEnabled; + } + + public void setDescription(String text){ + this.description = text; + } + + public String getDescription(){ + return this.description; + } +} diff --git a/src/main/java/org/olat/modules/video/models/VideoQualityTableModel.java b/src/main/java/org/olat/modules/video/models/VideoQualityTableModel.java new file mode 100644 index 0000000000000000000000000000000000000000..e112724b6a713ed0fdd98ddc274fdda73da2de71 --- /dev/null +++ b/src/main/java/org/olat/modules/video/models/VideoQualityTableModel.java @@ -0,0 +1,83 @@ +/** + * <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.models; + +import org.olat.core.gui.components.form.flexible.FormUIFactory; +import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiTableDataModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel; +import org.olat.core.gui.translator.Translator; +import org.olat.modules.video.ui.QualityTableRow; + +/** + * + * Initial date: 01.04.2015<br> + * @author Dirk Furrer, dirk.furrer@frentix.com, http://www.frentix.com + * + */ +public class VideoQualityTableModel extends DefaultFlexiTableDataModel<QualityTableRow>{ + + protected FormUIFactory uifactory = FormUIFactory.getInstance(); + private Translator translator; + public VideoQualityTableModel(FlexiTableColumnModel columnModel, Translator translator) { + super(columnModel); + this.translator = translator; + } + + @Override + public VideoQualityTableModel createCopyWithEmptyList() { + return new VideoQualityTableModel(getTableColumnModel(), translator); + } + + + @Override + public Object getValueAt(int row, int col) { + QualityTableRow video = getObject(row); + switch(QualityTableCols.values()[col]) { + case type: return video.getType(); + case dimension: return video.getDimension(); + case size: return video.getSize(); + case format: return video.getFormat(); + case view: return video.getViewLink(); + default: return ""; + } + } + + + + public enum QualityTableCols { + type("quality.table.header.type"), + dimension("quality.table.header.dimension"), + size("quality.table.header.size"), + format("quality.table.header.format"), + view("quality.table.header.view"); + + private final String i18nKey; + + private QualityTableCols(String i18nKey) { + this.i18nKey = i18nKey; + } + + public String i18nKey() { + return i18nKey; + } + } + +} \ No newline at end of file diff --git a/src/main/java/org/olat/modules/video/models/VideoTracksTableModel.java b/src/main/java/org/olat/modules/video/models/VideoTracksTableModel.java new file mode 100644 index 0000000000000000000000000000000000000000..28b058cce30a91e5953a4c6f4121559c0c70f8a7 --- /dev/null +++ b/src/main/java/org/olat/modules/video/models/VideoTracksTableModel.java @@ -0,0 +1,81 @@ +/** + * <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.models; + +import java.util.Locale; + +import org.olat.core.gui.components.form.flexible.FormUIFactory; +import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiTableDataModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel; +import org.olat.core.gui.translator.Translator; +import org.olat.modules.video.ui.TrackTableRow; + +/** + * + * Initial date: 01.04.2015<br> + * @author Dirk Furrer, dirk.furrer@frentix.com, http://www.frentix.com + * + */ +public class VideoTracksTableModel extends DefaultFlexiTableDataModel<TrackTableRow>{ + + protected FormUIFactory uifactory = FormUIFactory.getInstance(); + private Translator translator; + public VideoTracksTableModel(FlexiTableColumnModel columnModel, Translator translator) { + super(columnModel); + this.translator = translator; + } + + @Override + public VideoTracksTableModel createCopyWithEmptyList() { + return new VideoTracksTableModel(getTableColumnModel(), translator); + } + + + @Override + public Object getValueAt(int row, int col) { + TrackTableRow track = getObject(row); + switch(TrackTableCols.values()[col]) { + case file: return track.getTrack(); + case language: return new Locale(track.getLanguage()).getDisplayLanguage(this.translator.getLocale()); + case delete: return track.getDeleteLink(); + default: return ""; + } + } + + + + public enum TrackTableCols { + file("track.table.header.file"), + language("track.table.header.language"), + delete("track.table.header.delete"); + + private final String i18nKey; + + private TrackTableCols(String i18nKey) { + this.i18nKey = i18nKey; + } + + public String i18nKey() { + return i18nKey; + } + } + +} \ No newline at end of file diff --git a/src/main/java/org/olat/modules/video/service/HandbrakeTranscoder.java b/src/main/java/org/olat/modules/video/service/HandbrakeTranscoder.java new file mode 100644 index 0000000000000000000000000000000000000000..b6a3d94692be7cca88bde17e0353adcf78b3faca --- /dev/null +++ b/src/main/java/org/olat/modules/video/service/HandbrakeTranscoder.java @@ -0,0 +1,70 @@ +package org.olat.modules.video.service; + +import java.io.File; +import java.util.ArrayList; +import java.util.Map; + +import org.olat.core.CoreSpringFactory; +import org.olat.core.logging.OLog; +import org.olat.core.logging.Tracing; +import org.olat.fileresource.FileResourceManager; +import org.olat.modules.video.managers.VideoManager; +import org.olat.resource.OLATResource; + +public class HandbrakeTranscoder implements Transcoder { + private OLog logger = Tracing.createLoggerFor(this.getClass()); + private VideoManager videoManager = CoreSpringFactory.getImpl(VideoManager.class); + private FileResourceManager fileResourceManager = FileResourceManager.getInstance(); + + @Override + public boolean transcodeVideoRessource(OLATResource video, Map<String, String> params){ + File file = videoManager.getVideoFile(video); + File videoResourceFileroot = fileResourceManager.getFileResourceRoot(video); + File optimizedFolder = new File(videoResourceFileroot, "optimizedVideoData"); + optimizedFolder.mkdirs(); + + ArrayList<String> cmd = new ArrayList<String>(); + + cmd.add("HandBrakeCLI"); + cmd.add("-i "+file.getAbsolutePath()); + cmd.add("-o "+optimizedFolder.getAbsolutePath()+"/optimized_"+file.getName()); + cmd.add("--optimize"); + cmd.add("--preset Normal"); + + ProcessBuilder pb = new ProcessBuilder(cmd); + pb.directory(optimizedFolder); + + pb.redirectErrorStream(true); + pb.inheritIO(); + + + try { + logger.info("+--------------------------HANDBRAKE STARTS TRANSCODING------------------------------------+"); + Runtime.getRuntime().exec("HandBrakeCLI "+" -i "+file.getAbsolutePath()+" -o "+optimizedFolder.getAbsolutePath()+"/optimized_"+file.getName()+" --optimize"+" --preset Normal"); + logger.info("+---------------------------HANDBRAKE TRANSCODING DONE-------------------------------------+"); + return true; + } catch (Exception e) { + System.err.println("Unable to do videotranscoding"); + return false; + } + } + + + private boolean transcodeVideoRessourceLow(OLATResource video) { + // TODO Auto-generated method stub + return false; + } + + + private boolean transcodeVideoRessourceMid(OLATResource video) { + // TODO Auto-generated method stub + return false; + } + + + private boolean transcodeVideoRessourceHi(OLATResource video) { + // TODO Auto-generated method stub + return false; + } + +} diff --git a/src/main/java/org/olat/modules/video/service/Transcoder.java b/src/main/java/org/olat/modules/video/service/Transcoder.java new file mode 100644 index 0000000000000000000000000000000000000000..5495a140946b395043fec54f6a5ac76ddde09870 --- /dev/null +++ b/src/main/java/org/olat/modules/video/service/Transcoder.java @@ -0,0 +1,9 @@ +package org.olat.modules.video.service; + +import java.util.Map; + +import org.olat.resource.OLATResource; + +public interface Transcoder { + public boolean transcodeVideoRessource(OLATResource video, Map<String, String> params); +} diff --git a/src/main/java/org/olat/modules/video/service/TranscoderService.java b/src/main/java/org/olat/modules/video/service/TranscoderService.java new file mode 100644 index 0000000000000000000000000000000000000000..3983288c4c7e968d70ab6deb996f9af764ddcc6d --- /dev/null +++ b/src/main/java/org/olat/modules/video/service/TranscoderService.java @@ -0,0 +1,27 @@ +package org.olat.modules.video.service; + +import java.util.ServiceLoader; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +@Service +public class TranscoderService{ + + @Value("${video.transcoding.provider:handbrake}") + private static String transcodingProvider; + + private static TranscoderService service; + private ServiceLoader<Transcoder> loader; + + private TranscoderService() { + loader = ServiceLoader.load(Transcoder.class); + } + + public static synchronized TranscoderService getInstance() { + if (service == null) { + service = new TranscoderService(); + } + return service; + } +} diff --git a/src/main/java/org/olat/modules/video/ui/QualityTableRow.java b/src/main/java/org/olat/modules/video/ui/QualityTableRow.java new file mode 100644 index 0000000000000000000000000000000000000000..e7ebd5fa26840d9305ef2c520fec3e12df7085d4 --- /dev/null +++ b/src/main/java/org/olat/modules/video/ui/QualityTableRow.java @@ -0,0 +1,90 @@ +/** + * <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.ui; + +import org.olat.core.gui.components.form.flexible.FormUIFactory; +import org.olat.core.gui.components.form.flexible.elements.FormLink; + +/** + * + * Initial date: 07.04.2015<br> + * @author dfurrer, dirk.furrer@frentix.com, http://www.frentix.com + * + */ +public class QualityTableRow { + + String type; + String dimension; + String size; + String format; + FormLink viewLink; + + protected FormUIFactory uifactory = FormUIFactory.getInstance(); + + public QualityTableRow(String type, String dimension, String size, String format, FormLink viewLink) { + this.type = type; + this.dimension = dimension; + this.size = size; + this.format = format; + this.viewLink = viewLink; + this.viewLink.setIconLeftCSS("o_icon o_icon-fw o_icon_preview"); + + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getDimension() { + return dimension; + } + + public void setDimension(String dimension) { + this.dimension = dimension; + } + + public String getSize() { + return size; + } + + public void setSize(String size) { + this.size = size; + } + + public String getFormat() { + return format; + } + + public void setFormat(String format) { + this.format = format; + } + + public FormLink getViewLink() { + return viewLink; + } + + public void setViewLink(FormLink viewLink) { + this.viewLink = viewLink; + } +} diff --git a/src/main/java/org/olat/modules/video/ui/TrackTableRow.java b/src/main/java/org/olat/modules/video/ui/TrackTableRow.java new file mode 100644 index 0000000000000000000000000000000000000000..0e82e914a5c06648ae0fd64aa4d2a6551bcfe3a0 --- /dev/null +++ b/src/main/java/org/olat/modules/video/ui/TrackTableRow.java @@ -0,0 +1,60 @@ +/** + * <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.ui; + +import org.olat.core.gui.components.form.flexible.FormUIFactory; +import org.olat.core.gui.components.form.flexible.elements.FormLink; +import org.olat.core.util.vfs.VFSLeaf; + +/** + * + * Initial date: 07.04.2015<br> + * @author dfurrer, dirk.furrer@frentix.com, http://www.frentix.com + * + */ +public class TrackTableRow { + + String language; + VFSLeaf track; + FormLink deleteLink; + + protected FormUIFactory uifactory = FormUIFactory.getInstance(); + + public TrackTableRow(String language, VFSLeaf track, FormLink deleteLink) { + this.language = language; + this.track = track; + this.deleteLink = deleteLink; + this.deleteLink.setIconLeftCSS("o_icon o_icon-fw o_icon_delete_item"); + + } + + public VFSLeaf getTrack(){ + return track; + } + + public String getLanguage(){ + return language; + } + + public FormLink getDeleteLink(){ + return deleteLink; + } + +} diff --git a/src/main/java/org/olat/modules/video/ui/VideoAdminController.java b/src/main/java/org/olat/modules/video/ui/VideoAdminController.java new file mode 100644 index 0000000000000000000000000000000000000000..9be57daf39841a707568e9d35717af6d36434415 --- /dev/null +++ b/src/main/java/org/olat/modules/video/ui/VideoAdminController.java @@ -0,0 +1,77 @@ +package org.olat.modules.video.ui; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.form.flexible.FormItem; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.elements.MultipleSelectionElement; +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.control.Controller; +import org.olat.core.gui.control.WindowControl; +import org.olat.modules.video.VideoModule; +import org.springframework.beans.factory.annotation.Autowired; + +public class VideoAdminController extends FormBasicController { + + private MultipleSelectionElement enableEl; + private MultipleSelectionElement enableCourseNodeEl; + private MultipleSelectionElement enableTranscodingEl; + + @Autowired + private VideoModule videoModule; + + public VideoAdminController(UserRequest ureq, WindowControl wControl) { + super(ureq, wControl); + initForm(ureq); + } + + @Override + protected void doDispose() { + // TODO Auto-generated method stub + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + setFormTitle("admin.config.title"); + + String[] enableKeys = new String[]{ "on" }; + String[] enableValues = new String[]{ translate("on") }; + + enableEl = uifactory.addCheckboxesHorizontal("admin.config.enable", formLayout, enableKeys, enableValues); + enableEl.select("on", videoModule.isEnabled()); + enableEl.addActionListener(FormEvent.ONCHANGE); + + enableCourseNodeEl = uifactory.addCheckboxesHorizontal("admin.config.videoNode", formLayout, enableKeys, enableValues); + enableCourseNodeEl.select("on", videoModule.isCoursenodeEnabled()); + enableCourseNodeEl.setVisible(enableEl.isSelected(0)); + enableCourseNodeEl.addActionListener(FormEvent.ONCHANGE); + + enableTranscodingEl = uifactory.addCheckboxesHorizontal("admin.config.transcoding", formLayout, enableKeys, enableValues); + enableTranscodingEl.select("on", videoModule.isTranscodingEnabled()); + enableTranscodingEl.setVisible(enableEl.isSelected(0)); + enableTranscodingEl.addActionListener(FormEvent.ONCHANGE); + + } + + @Override + protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { + if(source == enableEl){ + videoModule.setEnabled(enableEl.isSelected(0)); + enableCourseNodeEl.setVisible(enableEl.isSelected(0)); + enableTranscodingEl.setVisible(enableEl.isSelected(0)); + enableCourseNodeEl.select("on", videoModule.isCoursenodeEnabled()); + enableTranscodingEl.select("on", videoModule.isTranscodingEnabled()); + } + if(source == enableCourseNodeEl){ + videoModule.setCoursenodeEnabled(enableCourseNodeEl.isSelected(0)); + } + if(source == enableTranscodingEl){ + videoModule.setTranscodingEnabled(enableTranscodingEl.isSelected(0)); + } + } + + @Override + protected void formOK(UserRequest ureq) { + + } +} \ No newline at end of file diff --git a/src/main/java/org/olat/modules/video/ui/VideoDisplayController.java b/src/main/java/org/olat/modules/video/ui/VideoDisplayController.java new file mode 100644 index 0000000000000000000000000000000000000000..b58135da6f7537e27fe4e3d691ca6a1a4cbad176 --- /dev/null +++ b/src/main/java/org/olat/modules/video/ui/VideoDisplayController.java @@ -0,0 +1,165 @@ +/** + * <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.ui; + +import java.util.HashMap; +import java.util.Locale; + +import org.olat.core.CoreSpringFactory; +import org.olat.core.commons.services.commentAndRating.CommentAndRatingDefaultSecurityCallback; +import org.olat.core.commons.services.commentAndRating.CommentAndRatingSecurityCallback; +import org.olat.core.commons.services.commentAndRating.ui.UserCommentsAndRatingsController; +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.Component; +import org.olat.core.gui.components.htmlheader.jscss.JSAndCSSComponent; +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.gui.render.Renderer; +import org.olat.core.gui.render.StringOutput; +import org.olat.core.gui.util.CSSHelper; +import org.olat.core.util.FileUtils; +import org.olat.core.util.vfs.VFSContainer; +import org.olat.core.util.vfs.VFSItem; +import org.olat.core.util.vfs.VFSLeaf; +import org.olat.fileresource.FileResourceManager; +import org.olat.modules.video.managers.MediaMapper; +import org.olat.modules.video.managers.VideoManager; +import org.olat.repository.RepositoryEntry; +import org.olat.resource.OLATResource; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Initial date: 01.04.2015<br> + * @author Dirk Furrer, dirk.furrer@frentix.com, http://www.frentix.com + * + */ +public class VideoDisplayController extends BasicController { + + @Autowired + private MovieService movieService; + + private UserCommentsAndRatingsController commentsAndRatingCtr; + private VelocityContainer mainVC; + public static final Event ENDED_EVENT = new Event("videoEnded"); + + + public VideoDisplayController(UserRequest ureq, WindowControl wControl, RepositoryEntry entry) { + this(ureq, wControl, entry, false, false, false, null, false, null); + } + + public VideoDisplayController(UserRequest ureq, WindowControl wControl, RepositoryEntry entry, Boolean autoplay, Boolean showComments, Boolean showRating, String OresSubPath, boolean customDescription, String descriptionText) { + super(ureq, wControl); + + mainVC = createVelocityContainer("video_run"); + mainVC.contextPut("displayName", entry.getDisplayname()); + StringOutput sb = new StringOutput(50); + Renderer.renderStaticURI(sb, "movie/mediaelementplayer.min.css"); + String cssPath = sb.toString(); + JSAndCSSComponent mediaelementjs = new JSAndCSSComponent("mediaelementjs", new String[] { "movie/mediaelement-and-player.min.js" },new String[] {cssPath}); + mainVC.put("mediaelementjs", mediaelementjs); + putInitialPanel(mainVC); + + VideoManager videoManager = CoreSpringFactory.getImpl(VideoManager.class); + + VFSLeaf video = getVideo(entry); + if(video != null) { + String filename = video.getName(); + mainVC.contextPut("filename", filename); + String lowerFilename = filename.toLowerCase(); + String cssClass = CSSHelper.createFiletypeIconCssClassFor(lowerFilename); + mainVC.contextPut("cssClass", cssClass); + + String extension = FileUtils.getFileSuffix(filename); + String mediaUrl = registerMapper(ureq, new MediaMapper(video.getParentContainer())); + mainVC.contextPut("movie", filename); + mainVC.contextPut("mediaUrl", mediaUrl); + Size realSize = movieService.getSize(video, extension); + if(realSize != null) { + mainVC.contextPut("height", realSize.getHeight()); + mainVC.contextPut("width", realSize.getWidth()); + } else { + mainVC.contextPut("height", 480); + mainVC.contextPut("width", 640); + } + + HashMap<String, String> trackfiles = new HashMap<String, String>(); + for(String lang : Locale.getISOLanguages()){ + VFSLeaf track = videoManager.getTrack(entry.getOlatResource(), lang); + if(track != null){ + trackfiles.put(lang, track.getName()); + } + } + mainVC.contextPut("trackfiles",trackfiles); + + CommentAndRatingSecurityCallback ratingSecCallback = new CommentAndRatingDefaultSecurityCallback(getIdentity(), false, false); + commentsAndRatingCtr = new UserCommentsAndRatingsController(ureq, getWindowControl(),entry.getOlatResource(), OresSubPath , ratingSecCallback,showComments, showRating, true); + listenTo(commentsAndRatingCtr); + + mainVC.put("commentsAndRating", commentsAndRatingCtr.getInitialComponent()); + + mainVC.contextPut("autoplay", autoplay); + + if(customDescription){ + mainVC.contextPut("description",descriptionText); + }else{ + mainVC.contextPut("description",videoManager.getDescription(entry.getOlatResource())); + } + } + } + + @Override + protected void doDispose() { + + } + + @Override + protected void event(UserRequest ureq, Component source, Event event) { + if(source == mainVC){ + if("ended".equals(event.getCommand())){ + fireEvent(ureq, ENDED_EVENT); + } + } + } + + private VFSLeaf getVideo(RepositoryEntry entry) { + OLATResource resource = entry.getOlatResource(); + VFSContainer fResourceFileroot = FileResourceManager.getInstance() + .getFileResourceRootImpl(resource); + + VFSLeaf document = null; + for(VFSItem item:fResourceFileroot.getItems()) { + if (item instanceof VFSContainer && item.getName().endsWith("media")) { + VFSContainer mediaFolder = (VFSContainer) item; + for (VFSItem video:mediaFolder.getItems()) { + if (video instanceof VFSLeaf && video.getName().endsWith("mp4")) { + document = (VFSLeaf)video; + } + } + } + } + return document; + } + +} diff --git a/src/main/java/org/olat/modules/video/ui/VideoMetaDataEditFormController.java b/src/main/java/org/olat/modules/video/ui/VideoMetaDataEditFormController.java new file mode 100644 index 0000000000000000000000000000000000000000..ec0a40d5cd799fc4a48b67088a1cb7966782e289 --- /dev/null +++ b/src/main/java/org/olat/modules/video/ui/VideoMetaDataEditFormController.java @@ -0,0 +1,66 @@ +package org.olat.modules.video.ui; + +import java.text.DateFormat; + +import org.apache.commons.io.FileUtils; +import org.olat.core.commons.services.image.Size; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.FormUIFactory; +import org.olat.core.gui.components.form.flexible.elements.RichTextElement; +import org.olat.core.gui.components.form.flexible.elements.StaticTextElement; +import org.olat.core.gui.components.form.flexible.impl.FormBasicController; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.WindowControl; +import org.olat.modules.video.managers.VideoManager; +import org.olat.resource.OLATResource; +import org.springframework.beans.factory.annotation.Autowired; + + +public class VideoMetaDataEditFormController extends FormBasicController { + + protected FormUIFactory uifactory = FormUIFactory.getInstance(); + @Autowired + private VideoManager videoManager; + private OLATResource videoResource; + private StaticTextElement heightField, widthField; + private RichTextElement descriptionField; + + + public VideoMetaDataEditFormController(UserRequest ureq, WindowControl wControl, OLATResource re) { + super(ureq, wControl); + videoResource = re; + initForm(ureq); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, + UserRequest ureq) { + setFormTitle("tab.video.metaDataConfig"); + + Size videoSize; + videoSize = videoManager.getVideoSize(videoResource); + if(videoSize == null){ + videoSize = new Size(0, 0, false); + } + + widthField = uifactory.addStaticTextElement("video.config.width", String.valueOf(videoSize.getWidth()), formLayout); + heightField = uifactory.addStaticTextElement("video.config.height", String.valueOf(videoSize.getHeight()), formLayout); + uifactory.addStaticTextElement("video.config.creationDate",DateFormat.getDateInstance().format(videoResource.getCreationDate()), formLayout); + uifactory.addStaticTextElement("video.config.fileSize", FileUtils.byteCountToDisplaySize(videoManager.getVideoFile(videoResource).length()), formLayout); + descriptionField = uifactory.addRichTextElementForStringDataMinimalistic("description", "video.config.description", videoManager.getDescription(videoResource), -1, -1, formLayout, getWindowControl()); + uifactory.addFormSubmitButton("submit", formLayout); + } + + @Override + protected void formOK(UserRequest ureq) { + videoManager.setVideoSize(videoResource, new Size(Integer.parseInt(widthField.getValue()), Integer.parseInt(heightField.getValue()), true)); + videoManager.setDescription(videoResource, descriptionField.getValue()); + } + + @Override + protected void doDispose() { + + } + +} diff --git a/src/main/java/org/olat/modules/video/ui/VideoPosterEditController.java b/src/main/java/org/olat/modules/video/ui/VideoPosterEditController.java new file mode 100644 index 0000000000000000000000000000000000000000..fb32293e5b08c94fc873268e1a8f066d98606758 --- /dev/null +++ b/src/main/java/org/olat/modules/video/ui/VideoPosterEditController.java @@ -0,0 +1,133 @@ +package org.olat.modules.video.ui; + +import org.olat.core.commons.modules.bc.FolderEvent; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.form.flexible.FormItem; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.elements.FormLink; +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.components.link.Link; +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.gui.control.generic.closablewrapper.CloseableModalController; +import org.olat.core.helpers.Settings; +import org.olat.core.util.vfs.VFSContainer; +import org.olat.core.util.vfs.VFSLeaf; +import org.olat.fileresource.FileResourceManager; +import org.olat.modules.video.managers.MediaMapper; +import org.olat.modules.video.managers.VideoManager; +import org.olat.resource.OLATResource; +import org.springframework.beans.factory.annotation.Autowired; + +public class VideoPosterEditController extends FormBasicController { + + + @Autowired + private VideoManager videoManager; + private VFSLeaf posterFile; + private OLATResource videoResource; + private FormLayoutContainer displayContainer; + private FormLink replaceImage; + private FormLink uploadImage; + private VideoPosterSelectionForm posterSelectionForm; + private CloseableModalController cmc; + private VideoPosterUploadForm posterUploadForm; + + public VideoPosterEditController(UserRequest ureq, WindowControl wControl, OLATResource videoResource) { + super(ureq, wControl); + this.videoResource = videoResource; + initForm(ureq); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + setFormTitle("tab.video.posterConfig"); + + + String posterPage = velocity_root + "/poster_config.html"; + displayContainer = FormLayoutContainer.createCustomFormLayout("tasks", getTranslator(), posterPage); + + displayContainer.contextPut("hint", translate("video.config.poster.hint")); + + posterFile = videoManager.getPosterframe(videoResource); + updatePosterImage(ureq, videoResource); + displayContainer.setLabel("video.config.poster", null); + formLayout.add(displayContainer); + + FormLayoutContainer buttonGroupLayout = FormLayoutContainer.createButtonLayout("buttonGroupLayout", getTranslator()); + formLayout.add(buttonGroupLayout); + + replaceImage = uifactory.addFormLink("replaceimg", "video.config.poster.replace", null, buttonGroupLayout, Link.BUTTON); + replaceImage.setIconLeftCSS("o_icon o_icon_browse o_icon-fw"); + replaceImage.setVisible(true); + uploadImage = uifactory.addFormLink("uploadImage", "video.config.poster.upload", null, buttonGroupLayout, Link.BUTTON); + uploadImage.setIconLeftCSS("o_icon o_icon_upload o_icon-f"); + uploadImage.setVisible(true); + } + + @Override + protected void formOK(UserRequest ureq) { + // TODO Auto-generated method stub + + } + + @Override + protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { + if (source == replaceImage) { + doReplaceVideo(ureq); + } else if (source == uploadImage) { + doUploadVideo(ureq); + } + } + @Override + protected void doDispose() { + // TODO Auto-generated method stub + + } + + @Override + 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; + videoManager.setPosterframe(videoResource, newPosterFile); + if(source == posterUploadForm){ + posterFile.delete(); + } + updatePosterImage(ureq, videoResource); + } + } + } + + private void doReplaceVideo(UserRequest ureq){ + posterSelectionForm = new VideoPosterSelectionForm(ureq, getWindowControl(), videoResource); + listenTo(posterSelectionForm); + cmc = new CloseableModalController(getWindowControl(), "close", posterSelectionForm.getInitialComponent()); + listenTo(cmc); + cmc.activate(); + } + + private void doUploadVideo(UserRequest ureq){ + posterUploadForm = new VideoPosterUploadForm(ureq, getWindowControl(), videoResource); + listenTo(posterUploadForm); + cmc = new CloseableModalController(getWindowControl(), "close", posterUploadForm.getInitialComponent()); + listenTo(cmc); + cmc.activate(); + } + + private void updatePosterImage(UserRequest ureq, OLATResource video){ + posterFile = videoManager.getPosterframe(video); + VFSContainer mediaBase = FileResourceManager.getInstance().getFileResourceMedia(video); + MediaMapper mediaMapper = new MediaMapper(mediaBase); + String mediaUrl = registerMapper(ureq, mediaMapper); + String serverUrl = Settings.createServerURI(); + displayContainer.contextPut("serverUrl", serverUrl); + displayContainer.contextPut("mediaUrl", mediaUrl); + } +} \ No newline at end of file diff --git a/src/main/java/org/olat/modules/video/ui/VideoPosterSelectionForm.java b/src/main/java/org/olat/modules/video/ui/VideoPosterSelectionForm.java new file mode 100644 index 0000000000000000000000000000000000000000..167d3135cf13288a250f5fa13d573cc46b0a9980 --- /dev/null +++ b/src/main/java/org/olat/modules/video/ui/VideoPosterSelectionForm.java @@ -0,0 +1,125 @@ +package org.olat.modules.video.ui; + + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.channels.FileChannel; +import java.util.HashMap; +import java.util.Map; + +import org.jcodec.common.FileChannelWrapper; +import org.jcodec.containers.mp4.demuxer.MP4Demuxer; +import org.olat.core.CoreSpringFactory; +import org.olat.core.commons.modules.bc.FolderEvent; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.Component; +import org.olat.core.gui.components.form.flexible.FormUIFactory; +import org.olat.core.gui.components.link.Link; +import org.olat.core.gui.components.link.LinkFactory; +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.vfs.LocalFolderImpl; +import org.olat.core.util.vfs.VFSContainer; +import org.olat.core.util.vfs.VFSLeaf; +import org.olat.core.util.vfs.VFSManager; +import org.olat.fileresource.FileResourceManager; +import org.olat.modules.video.managers.MediaMapper; +import org.olat.modules.video.managers.VideoManager; +import org.olat.resource.OLATResource; + +public class VideoPosterSelectionForm extends BasicController { + protected FormUIFactory uifactory = FormUIFactory.getInstance(); + long remainingSpace; + private VFSContainer videoResourceFileroot; + private VFSContainer metaDataFolder; + private VideoManager videoManager = CoreSpringFactory.getImpl(VideoManager.class); + VelocityContainer proposalLayout = createVelocityContainer("video_poster_proposal"); + + private Map<String, String> generatedPosters; + private Map<Link, VFSLeaf> buttons = new HashMap<Link, VFSLeaf>(); + + + + public VideoPosterSelectionForm(UserRequest ureq, WindowControl wControl, OLATResource videoResource) { + super(ureq, wControl); + + videoResourceFileroot = new LocalFolderImpl(FileResourceManager.getInstance().getFileResourceRootImpl(videoResource).getBasefile()); + metaDataFolder = VFSManager.getOrCreateContainer(videoResourceFileroot, "media"); + generatedPosters = new HashMap<String, String>(); + + + long duration =1000; + + RandomAccessFile accessFile; + try { + accessFile = new RandomAccessFile(videoManager.getVideoFile(videoResource),"r"); + FileChannel ch = accessFile.getChannel(); + FileChannelWrapper in = new FileChannelWrapper(ch); + MP4Demuxer demuxer1 = new MP4Demuxer(in); + duration = demuxer1.getVideoTrack().getFrameCount(); + } catch (FileNotFoundException e1) { + // TODO Auto-generated catch block + e1.printStackTrace(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + + long firstThirdDuration = duration/3; + for(int x=0; x<=duration;x+=firstThirdDuration ){ + try { + + VFSContainer proposalContainer = VFSManager.getOrCreateContainer(metaDataFolder, "proposalPosters"); + VFSLeaf posterProposal = proposalContainer.createChildLeaf("proposalPoster"+x+".jpg"); + if(posterProposal == null){ + posterProposal = (VFSLeaf) proposalContainer.resolve("/proposalPoster"+x+".jpg"); + }else{ + videoManager.getFrame(videoResource, x, posterProposal); + } + MediaMapper mediaMapper = new MediaMapper(proposalContainer); + String mediaUrl = registerMapper(ureq, mediaMapper); + String serverUrl = Settings.createServerURI(); + proposalLayout.contextPut("serverUrl", serverUrl); + + Link button = LinkFactory.createButton(String.valueOf(x), proposalLayout, this); + button.setCustomDisplayText(translate("poster.select")); + buttons.put(button, posterProposal); +// .addFormLink(posterProposal.getName(), "selectPoster", "track.delete", "track.delete", null, Link.BUTTON); + + generatedPosters.put(mediaUrl+"/proposalPoster"+x+".jpg", String.valueOf(x)); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + proposalLayout.contextPut("pics", generatedPosters); + + + putInitialPanel(proposalLayout); + } + + + + + + @Override + protected void doDispose() { + // TODO Auto-generated method stub + + } + + @Override + protected void event(UserRequest ureq, Component source, Event event) { + for(Link button: buttons.keySet()){ + if(source == button){ + fireEvent(ureq, new FolderEvent(FolderEvent.UPLOAD_EVENT, buttons.get(button))); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/org/olat/modules/video/ui/VideoPosterUploadForm.java b/src/main/java/org/olat/modules/video/ui/VideoPosterUploadForm.java new file mode 100644 index 0000000000000000000000000000000000000000..e6452a2dd7713d1e2c1053785e1038ae6f0474cb --- /dev/null +++ b/src/main/java/org/olat/modules/video/ui/VideoPosterUploadForm.java @@ -0,0 +1,81 @@ +package org.olat.modules.video.ui; + +import java.util.HashSet; +import java.util.Set; + +import org.olat.core.commons.modules.bc.FolderEvent; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.elements.FileElement; +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.WindowControl; +import org.olat.core.util.vfs.LocalFolderImpl; +import org.olat.core.util.vfs.Quota; +import org.olat.core.util.vfs.VFSContainer; +import org.olat.core.util.vfs.VFSManager; +import org.olat.fileresource.FileResourceManager; +import org.olat.resource.OLATResource; + +public class VideoPosterUploadForm extends FormBasicController { + private OLATResource videoResource; + long remainingSpace; + private VFSContainer videoResourceFileroot; + private VFSContainer metaDataFolder; + private FileElement posterField; + + private static final int picUploadlimitKB = 51200; + + + private static final Set<String> imageMimeTypes = new HashSet<String>(); + static { + imageMimeTypes.add("image/jpg"); + imageMimeTypes.add("image/jpeg"); + } + + public VideoPosterUploadForm(UserRequest ureq, WindowControl wControl, OLATResource videoResource) { + super(ureq, wControl); + this.videoResource = videoResource; + initForm(ureq); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + remainingSpace = Quota.UNLIMITED; + videoResourceFileroot = new LocalFolderImpl(FileResourceManager.getInstance().getFileResourceRootImpl(videoResource).getBasefile()); + metaDataFolder = VFSManager.getOrCreateContainer(videoResourceFileroot, "media"); + + posterField = uifactory.addFileElement(getWindowControl(), "poster", "video.config.poster", formLayout); + posterField.limitToMimeType(imageMimeTypes, null, null); + posterField.setMaxUploadSizeKB(picUploadlimitKB, null, null); + posterField.setPreview(ureq.getUserSession(), true); + posterField.addActionListener(FormEvent.ONCHANGE); + + FormLayoutContainer buttonGroupLayout = FormLayoutContainer.createButtonLayout("buttonGroupLayout", getTranslator()); + formLayout.add(buttonGroupLayout); + buttonGroupLayout.setElementCssClass("o_sel_upload_buttons"); + uifactory.addFormSubmitButton("track.upload", buttonGroupLayout); + } + + @Override + protected void formOK(UserRequest ureq) { + if ( posterField.isUploadSuccess()) { + if (remainingSpace != -1) { + if (posterField.getUploadFile().length() / 1024 > remainingSpace) { + posterField.setErrorKey("QuotaExceeded", null); + posterField.getUploadFile().delete(); + return; + } + }else{ + fireEvent(ureq, new FolderEvent(FolderEvent.UPLOAD_EVENT, posterField.moveUploadFileTo(metaDataFolder))); + } + } + } + + @Override + protected void doDispose() { + // TODO Auto-generated method stub + } +} \ No newline at end of file diff --git a/src/main/java/org/olat/modules/video/ui/VideoQualityTableFormController.java b/src/main/java/org/olat/modules/video/ui/VideoQualityTableFormController.java new file mode 100644 index 0000000000000000000000000000000000000000..84bf50fed3d17d1f55713a2f3f9996909ff97769 --- /dev/null +++ b/src/main/java/org/olat/modules/video/ui/VideoQualityTableFormController.java @@ -0,0 +1,82 @@ +package org.olat.modules.video.ui; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.io.FileUtils; +import org.olat.core.commons.services.image.Size; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.elements.FlexiTableElement; +import org.olat.core.gui.components.form.flexible.elements.FormLink; +import org.olat.core.gui.components.form.flexible.impl.FormBasicController; +import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; +import org.olat.core.gui.components.form.flexible.impl.elements.table.DefaultFlexiColumnModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableDataModelFactory; +import org.olat.core.gui.components.link.Link; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.WindowControl; +import org.olat.modules.video.managers.VideoManager; +import org.olat.modules.video.models.VideoQualityTableModel; +import org.olat.modules.video.models.VideoQualityTableModel.QualityTableCols; +import org.olat.resource.OLATResource; +import org.springframework.beans.factory.annotation.Autowired; + + +public class VideoQualityTableFormController extends FormBasicController { + + private FlexiTableElement tableEl; + private VideoQualityTableModel tableModel; + + @Autowired + private VideoManager videoManager; + private OLATResource videoResource; + + + public VideoQualityTableFormController(UserRequest ureq, WindowControl wControl, OLATResource videoResource) { + super(ureq, wControl, LAYOUT_VERTICAL); + this.videoResource = videoResource; + initForm(ureq); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + FormLayoutContainer generalCont = FormLayoutContainer.createVerticalFormLayout("general", getTranslator()); + generalCont.setFormTitle(translate("tab.video.qualityConfig")); + generalCont.setRootForm(mainForm); + generalCont.setFormContextHelp("Video Tracks"); + formLayout.add(generalCont); + + FlexiTableColumnModel columnsModel = FlexiTableDataModelFactory.createFlexiTableColumnModel(); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(true, QualityTableCols.type.i18nKey(), QualityTableCols.type.ordinal(), true, QualityTableCols.type.name())); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(true, QualityTableCols.dimension.i18nKey(), QualityTableCols.dimension.ordinal(), true, QualityTableCols.dimension.name())); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(true, QualityTableCols.size.i18nKey(), QualityTableCols.size.ordinal(), true, QualityTableCols.size.name())); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(true, QualityTableCols.format.i18nKey(), QualityTableCols.format.ordinal(), true, QualityTableCols.format.name())); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(true, QualityTableCols.view.i18nKey(), QualityTableCols.view.ordinal(), true, QualityTableCols.view.name())); + tableModel = new VideoQualityTableModel(columnsModel, getTranslator()); + + List<QualityTableRow> rows = new ArrayList<QualityTableRow>(); + + Size origSize = videoManager.getVideoSize(videoResource); + + FormLink viewButton = uifactory.addFormLink("view", "viewQuality", "quality.view", "qulaity.view", null, Link.LINK); + rows.add(new QualityTableRow("original", origSize.getWidth() +"x"+ origSize.getHeight(), FileUtils.byteCountToDisplaySize(videoManager.getVideoFile(videoResource).length()), "mp4",viewButton)); + tableModel.setObjects(rows); + tableEl = uifactory.addTableElement(getWindowControl(), "qualities", tableModel, getTranslator(), generalCont); + tableEl.setCustomizeColumns(false); + } + + @Override + protected void formOK(UserRequest ureq) { + // TODO Auto-generated method stub + + } + + @Override + protected void doDispose() { + // TODO Auto-generated method stub + + } + +} diff --git a/src/main/java/org/olat/modules/video/ui/VideoRuntimeController.java b/src/main/java/org/olat/modules/video/ui/VideoRuntimeController.java new file mode 100644 index 0000000000000000000000000000000000000000..b3f6ff5bef069697ab9e45f8eb72fdeb37d22c66 --- /dev/null +++ b/src/main/java/org/olat/modules/video/ui/VideoRuntimeController.java @@ -0,0 +1,87 @@ +/** + * <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.ui; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.Component; +import org.olat.core.gui.components.dropdown.Dropdown; +import org.olat.core.gui.components.dropdown.Dropdown.Spacer; +import org.olat.core.gui.components.link.Link; +import org.olat.core.gui.components.link.LinkFactory; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.Event; +import org.olat.core.gui.control.WindowControl; +import org.olat.modules.video.managers.VideoManager; +import org.olat.repository.RepositoryEntry; +import org.olat.repository.model.RepositoryEntrySecurity; +import org.olat.repository.ui.RepositoryEntryRuntimeController; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * The runtime add configuration management and delivery options. + * + * Initial date: 01.04.2015<br> + * @author Dirk Furrer, dirk.furrer@frentix.com, http://www.frentix.com + * + */ +public class VideoRuntimeController extends RepositoryEntryRuntimeController { + + private Link settingsLink; + + @Autowired + private VideoManager videoManager; + + public VideoRuntimeController(UserRequest ureq, WindowControl wControl, + RepositoryEntry re, RepositoryEntrySecurity reSecurity, RuntimeControllerCreator runtimeControllerCreator) { + super(ureq, wControl, re, reSecurity, runtimeControllerCreator); + } + + @Override + protected void initEditionTools(Dropdown settingsDropdown) { + super.initEditionTools(settingsDropdown); + if (reSecurity.isEntryAdmin()) { + settingsLink = LinkFactory.createToolLink("metaDataConfig", translate("tab.video.settings"), this); + settingsLink.setIconLeftCSS("o_icon o_icon-fw o_icon_quota o_icon_settings"); + settingsDropdown.addComponent(4, settingsLink); + + settingsDropdown.addComponent(new Spacer("metadata-poster")); + } + } + + @Override + protected void event(UserRequest ureq, Component source, Event event) { + if(settingsLink == source){ + doSettingsconfig(ureq); + } + else { + super.event(ureq, source, event); + } + } + + + private void doSettingsconfig(UserRequest ureq) { + RepositoryEntry entry = getRepositoryEntry(); + Controller configCtrl = new VideoSettingsController(ureq, getWindowControl(), entry); + pushController(ureq, translate("tab.video.settings"), configCtrl); + setActiveTool(settingsLink); + } + +} \ No newline at end of file diff --git a/src/main/java/org/olat/modules/video/ui/VideoSettingsController.java b/src/main/java/org/olat/modules/video/ui/VideoSettingsController.java new file mode 100644 index 0000000000000000000000000000000000000000..9a90b3ec685e53119c714f21ce6fd848647975e0 --- /dev/null +++ b/src/main/java/org/olat/modules/video/ui/VideoSettingsController.java @@ -0,0 +1,109 @@ +package org.olat.modules.video.ui; + +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.Component; +import org.olat.core.gui.components.link.Link; +import org.olat.core.gui.components.link.LinkFactory; +import org.olat.core.gui.components.segmentedview.SegmentViewComponent; +import org.olat.core.gui.components.segmentedview.SegmentViewEvent; +import org.olat.core.gui.components.segmentedview.SegmentViewFactory; +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.repository.RepositoryEntry; + +public class VideoSettingsController extends BasicController { + + private RepositoryEntry entry; + + private VideoMetaDataEditFormController metaDataController; + private VideoPosterEditController posterEditController; + private VideoTrackEditController trackEditController; + private VideoQualityTableFormController qualityEditController; + + private Link metaDataLink, posterEditLink, trackEditLink, qualityConfig; + + private final VelocityContainer mainVC; + private final SegmentViewComponent segmentView; + + + + public VideoSettingsController(UserRequest ureq, WindowControl wControl, RepositoryEntry entry ) { + super(ureq, wControl); + + this.entry = entry; + mainVC = createVelocityContainer("video_settings"); + + segmentView = SegmentViewFactory.createSegmentView("segments", mainVC, this); + + metaDataLink = LinkFactory.createLink("tab.video.metaDataConfig", mainVC, this); + segmentView.addSegment(metaDataLink, true); + posterEditLink = LinkFactory.createLink("tab.video.posterConfig", mainVC, this); + segmentView.addSegment(posterEditLink, false); + trackEditLink = LinkFactory.createLink("tab.video.trackConfig", mainVC, this); + segmentView.addSegment(trackEditLink, false); + qualityConfig = LinkFactory.createLink("tab.video.qualityConfig", mainVC, this); + segmentView.addSegment(qualityConfig, false); + + doOpenMetaDataConfig(ureq); + + putInitialPanel(mainVC); + + } + + @Override + protected void event(UserRequest ureq, Component source, Event event) { + if(source == segmentView) { + if(event instanceof SegmentViewEvent) { + SegmentViewEvent sve = (SegmentViewEvent)event; + String segmentCName = sve.getComponentName(); + Component clickedLink = mainVC.getComponent(segmentCName); + if (clickedLink == metaDataLink) { + doOpenMetaDataConfig(ureq); + } else if (clickedLink == posterEditLink){ + doOpenPosterConfig(ureq); + } else if (clickedLink == trackEditLink){ + doOpenTrackConfig(ureq); + } else if (clickedLink == qualityConfig){ + doOpenQualityConfig(ureq); + } + } + } + } + + @Override + protected void doDispose() { + // TODO Auto-generated method stub + + } + + private void doOpenMetaDataConfig(UserRequest ureq) { + if(metaDataController == null) { + metaDataController = new VideoMetaDataEditFormController(ureq, getWindowControl(), entry.getOlatResource()); + } + mainVC.put("segmentCmp", metaDataController.getInitialComponent()); + } + + private void doOpenPosterConfig(UserRequest ureq) { + if(posterEditController == null) { + posterEditController = new VideoPosterEditController(ureq, getWindowControl(), entry.getOlatResource()); + } + mainVC.put("segmentCmp", posterEditController.getInitialComponent()); + } + + private void doOpenTrackConfig(UserRequest ureq) { + if(trackEditController == null) { + trackEditController = new VideoTrackEditController(ureq, getWindowControl(), entry.getOlatResource()); + } + mainVC.put("segmentCmp", trackEditController.getInitialComponent()); + } + + private void doOpenQualityConfig(UserRequest ureq) { + if(qualityEditController == null) { + qualityEditController = new VideoQualityTableFormController(ureq, getWindowControl(), entry.getOlatResource()); + } + mainVC.put("segmentCmp", qualityEditController.getInitialComponent()); + } + +} diff --git a/src/main/java/org/olat/modules/video/ui/VideoTrackEditController.java b/src/main/java/org/olat/modules/video/ui/VideoTrackEditController.java new file mode 100644 index 0000000000000000000000000000000000000000..01b309d883d65781d725795be33b577eb6833b0e --- /dev/null +++ b/src/main/java/org/olat/modules/video/ui/VideoTrackEditController.java @@ -0,0 +1,141 @@ +package org.olat.modules.video.ui; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import org.olat.core.commons.modules.bc.FolderEvent; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.form.flexible.FormItem; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.FormUIFactory; +import org.olat.core.gui.components.form.flexible.elements.FlexiTableElement; +import org.olat.core.gui.components.form.flexible.elements.FormLink; +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.components.form.flexible.impl.elements.table.DefaultFlexiColumnModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableColumnModel; +import org.olat.core.gui.components.form.flexible.impl.elements.table.FlexiTableDataModelFactory; +import org.olat.core.gui.components.link.Link; +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.gui.control.generic.closablewrapper.CloseableModalController; +import org.olat.core.util.vfs.VFSLeaf; +import org.olat.modules.video.managers.VideoManager; +import org.olat.modules.video.models.VideoTracksTableModel; +import org.olat.modules.video.models.VideoTracksTableModel.TrackTableCols; +import org.olat.resource.OLATResource; +import org.springframework.beans.factory.annotation.Autowired; + +public class VideoTrackEditController extends FormBasicController { + protected FormUIFactory uifactory = FormUIFactory.getInstance(); + + private FlexiTableElement tableEl; + private VideoTracksTableModel tableModel; + private FormLink addButton; + VideoTrackUploadForm trackUploadForm; + VideoPosterUploadForm posterUploadForm; + VideoPosterSelectionForm posterSelectionForm; + CloseableModalController cmc; + + private Map<String, TrackTableRow> rows; + + @Autowired + private VideoManager videoManager; + private OLATResource videoResource; + + public VideoTrackEditController(UserRequest ureq, WindowControl wControl, OLATResource videoResource) { + super(ureq, wControl, LAYOUT_BAREBONE); + this.videoResource = videoResource; + initForm(ureq); + } + + @Override + protected void doDispose() { + // + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + FormLayoutContainer generalCont = FormLayoutContainer.createVerticalFormLayout("general", getTranslator()); + generalCont.setFormTitle(translate("tab.video.trackConfig")); + generalCont.setRootForm(mainForm); + generalCont.setFormContextHelp("Video Tracks"); + formLayout.add(generalCont); + + FlexiTableColumnModel columnsModel = FlexiTableDataModelFactory.createFlexiTableColumnModel(); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(true, TrackTableCols.file.i18nKey(), TrackTableCols.file.ordinal(), true, TrackTableCols.file.name())); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(TrackTableCols.language.i18nKey(), TrackTableCols.language.ordinal(), true, TrackTableCols.language.name())); + columnsModel.addFlexiColumnModel(new DefaultFlexiColumnModel(true, TrackTableCols.delete.i18nKey(), TrackTableCols.delete.ordinal(),false, TrackTableCols.delete.name())); + tableModel = new VideoTracksTableModel(columnsModel, getTranslator()); + + tableEl = uifactory.addTableElement(getWindowControl(), "tracks", tableModel, getTranslator(), generalCont); + tableEl.setCustomizeColumns(false); + HashMap<String, VFSLeaf> tracks = videoManager.getAllTracks(videoResource); + rows = new HashMap<String,TrackTableRow>(tracks.size()); + if (!tracks.isEmpty()) { + for (Map.Entry<String, VFSLeaf> entry : tracks.entrySet()) { + FormLink delButton = uifactory.addFormLink(entry.getKey(), "deleteTrack", "track.delete", "track.delete", null, Link.BUTTON); + rows.put(entry.getKey(), new TrackTableRow(entry.getKey(), entry.getValue(), delButton)); + + } + tableModel.setObjects(new ArrayList<TrackTableRow>(rows.values())); + } +// tableEl.setVisible(!videoManager.getAllTracks(videoResource).isEmpty()); + tableEl.setVisible(true); + + addButton = uifactory.addFormLink("add.track", generalCont, Link.BUTTON); + } + + @Override + protected boolean validateFormLogic(UserRequest ureq) { + + return true; + } + + @Override + protected void formOK(UserRequest ureq) { + + } + + @Override + protected void formInnerEvent(UserRequest ureq, FormItem source, FormEvent event) { + if (addButton == source) { + doAddTrack(ureq); + }else if (source.getComponent() instanceof Link){ + String lang = rows.get(source.getName()).getLanguage(); + videoManager.removeTrack(videoResource, lang); + rows.remove(rows.get(source.getName()).getLanguage()); + tableModel.setObjects(new ArrayList<TrackTableRow>(rows.values())); + tableEl.reset(); + } + } + + @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(); + tableEl.setVisible(true); + tableEl.setEnabled(true); + cmc.deactivate(); + } + else if(event.getCommand() == "CLOSE_MODAL_EVENT"){ + cmc.deactivate(); + } + } + + + private void doAddTrack(UserRequest ureq) { + trackUploadForm = new VideoTrackUploadForm(ureq, getWindowControl(), videoResource); + listenTo(trackUploadForm); + cmc = new CloseableModalController(getWindowControl(), "close", trackUploadForm.getInitialComponent()); + listenTo(cmc); + cmc.activate(); + } + +} diff --git a/src/main/java/org/olat/modules/video/ui/VideoTrackUploadForm.java b/src/main/java/org/olat/modules/video/ui/VideoTrackUploadForm.java new file mode 100644 index 0000000000000000000000000000000000000000..d431c546262909847b3d1462047f40c6e0388d57 --- /dev/null +++ b/src/main/java/org/olat/modules/video/ui/VideoTrackUploadForm.java @@ -0,0 +1,112 @@ +package org.olat.modules.video.ui; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.stream.Collectors; + +import org.olat.core.commons.modules.bc.FolderEvent; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.form.flexible.FormItemContainer; +import org.olat.core.gui.components.form.flexible.elements.FileElement; +import org.olat.core.gui.components.form.flexible.elements.SingleSelection; +import org.olat.core.gui.components.form.flexible.impl.FormBasicController; +import org.olat.core.gui.components.form.flexible.impl.FormLayoutContainer; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.util.vfs.LocalFolderImpl; +import org.olat.core.util.vfs.Quota; +import org.olat.core.util.vfs.VFSContainer; +import org.olat.core.util.vfs.VFSManager; +import org.olat.fileresource.FileResourceManager; +import org.olat.resource.OLATResource; + +/** + * + * Initial date: 01.04.2015<br> + * @author Dirk Furrer, dirk.furrer@frentix.com, http://www.frentix.com + * + */ + +public class VideoTrackUploadForm extends FormBasicController { + private OLATResource videoResource; + private FileElement fileEl; + SingleSelection langsItem; + long remainingSpace; + private VFSContainer videoResourceFileroot; + private VFSContainer metaDataFolder; + + private static final Set<String> trackMimeTypes = new HashSet<String>(); + static { + trackMimeTypes.add("text/plain"); + } + + public VideoTrackUploadForm(UserRequest ureq, WindowControl wControl, OLATResource videoResource) { + super(ureq, wControl); + this.videoResource = videoResource; + initForm(ureq); + } + + @Override + protected void initForm(FormItemContainer formLayout, Controller listener, UserRequest ureq) { + remainingSpace = Quota.UNLIMITED; + videoResourceFileroot = new LocalFolderImpl(FileResourceManager.getInstance().getFileResourceRootImpl(videoResource).getBasefile()); + metaDataFolder = VFSManager.getOrCreateContainer(videoResourceFileroot, "media"); + List<String> langs = new ArrayList<String>(); + List<String> dispLangs = new ArrayList<String>(); + + + for(Locale locale : SimpleDateFormat.getAvailableLocales()){ + if(locale.hashCode()!=0){ + langs.add(locale.getLanguage()); + dispLangs.add(locale.getDisplayLanguage(getTranslator().getLocale())); + } + } + + List<String> langsWithoutDup = langs.parallelStream().distinct().collect(Collectors.toList()); + List<String> dispLangsWithoutDup = dispLangs.parallelStream().distinct().collect(Collectors.toList()); + + langsItem = uifactory.addDropdownSingleselect("track.langs", formLayout, langsWithoutDup.toArray(new String[langsWithoutDup.size()]), dispLangsWithoutDup.toArray(new String[dispLangsWithoutDup.size()]), null); + fileEl = uifactory.addFileElement(getWindowControl(), "track.upload", formLayout); +// fileEl.limitToMimeType(trackMimeTypes, "video.config.track.error.type", null); + langsItem.setMandatory(true); + + FormLayoutContainer buttonGroupLayout = FormLayoutContainer.createButtonLayout("buttonGroupLayout", getTranslator()); + formLayout.add(buttonGroupLayout); + buttonGroupLayout.setElementCssClass("o_sel_upload_buttons"); + uifactory.addFormSubmitButton("track.upload", buttonGroupLayout); + } + + @Override + protected void formOK(UserRequest ureq) { + if ( fileEl.isUploadSuccess()) { + if (remainingSpace != -1) { + if (fileEl.getUploadFile().length() / 1024 > remainingSpace) { + fileEl.setErrorKey("QuotaExceeded", null); + fileEl.getUploadFile().delete(); + return; + } + }else{ + fireEvent(ureq, new FolderEvent(FolderEvent.UPLOAD_EVENT, fileEl.moveUploadFileTo(metaDataFolder))); + } + }else{ + fileEl.setErrorKey("track.upload.error.nofile", null); + } + + } + + + protected String getLang(){ + return langsItem.getSelectedKey(); + } + + + @Override + protected void doDispose() { + // TODO Auto-generated method stub + + } +} \ No newline at end of file diff --git a/src/main/java/org/olat/modules/video/ui/_content/poster_config.html b/src/main/java/org/olat/modules/video/ui/_content/poster_config.html new file mode 100644 index 0000000000000000000000000000000000000000..0ab46e994b4da953cc771470ad5705b3290eb513 --- /dev/null +++ b/src/main/java/org/olat/modules/video/ui/_content/poster_config.html @@ -0,0 +1,2 @@ +<div class="o_note">$hint</div> +<img src="$mediaUrl/poster.jpg" height="400px" width="400px" /> \ No newline at end of file diff --git a/src/main/java/org/olat/modules/video/ui/_content/video_poster_proposal.html b/src/main/java/org/olat/modules/video/ui/_content/video_poster_proposal.html new file mode 100644 index 0000000000000000000000000000000000000000..60ee404e7d5cf2fd4bb5d3e550cd129d40ad5ee1 --- /dev/null +++ b/src/main/java/org/olat/modules/video/ui/_content/video_poster_proposal.html @@ -0,0 +1,5 @@ +#foreach( $poster in $pics.entrySet()) +<div> +<img src="$poster.key" height="400px" width="400px" />$r.render($poster.value) +</div> +#end diff --git a/src/main/java/org/olat/modules/video/ui/_content/video_run.html b/src/main/java/org/olat/modules/video/ui/_content/video_run.html new file mode 100644 index 0000000000000000000000000000000000000000..d5e9c59e1fe9e06bcc68ead53c7bbfe8fc0ebc0d --- /dev/null +++ b/src/main/java/org/olat/modules/video/ui/_content/video_run.html @@ -0,0 +1,43 @@ + +#if($image) + <img src="$mediaUrl/$filename" /> +#elseif($movie) + <div class="olatFlashMovieViewer"> + <video id="$r.getId("o_vid")" width="$width" height="$height" poster="$mediaUrl/poster.jpg" controls #if( $autoplay ) autoplay #end> + <source type="video/mp4" src="$mediaUrl/$filename" /> + #if( $trackfiles ) + #foreach( $track in $trackfiles.keySet()) + <track kind="subtitles" src="$mediaUrl/$trackfiles.get($track)" srclang="$track" /> + #end + #end + </video> + + + <script defer="defer" type="text/javascript"> + // <![CDATA[ + // using jQuery + jQuery('#$r.getId("o_vid")').mediaelementplayer({ + success: function (mediaElement, domObject) { + // add event listener + mediaElement.addEventListener('ended', function(e) { + if (!o_info.linkbusy) { + window.suppressOlatOnUnloadOnce = true; + o_beforeserver(); + $r.javaScriptBgCommand("ended"); + } + + }, false); + } + + + }); + // ]]> + </script> + </div> +#elseif($r.available("content")) + <div class="o_content clearfix">$r.render("content")</div> +#end +$r.render("commentsAndRating") +#if($description) +$description +#end \ No newline at end of file diff --git a/src/main/java/org/olat/modules/video/ui/_content/video_settings.html b/src/main/java/org/olat/modules/video/ui/_content/video_settings.html new file mode 100644 index 0000000000000000000000000000000000000000..03e5a4e314f4850964d2e4a85ad3a7d47f7be95f --- /dev/null +++ b/src/main/java/org/olat/modules/video/ui/_content/video_settings.html @@ -0,0 +1,7 @@ +<div class="clearfix"> + $r.render("segments") <br/> + + #if($r.available("segmentCmp")) + $r.render("segmentCmp") + #end +</div> \ No newline at end of file diff --git a/src/main/java/org/olat/modules/video/ui/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/modules/video/ui/_i18n/LocalStrings_de.properties new file mode 100644 index 0000000000000000000000000000000000000000..5019e430cb75cdb043eaf4609ef52966d64b521c --- /dev/null +++ b/src/main/java/org/olat/modules/video/ui/_i18n/LocalStrings_de.properties @@ -0,0 +1,46 @@ +tab.video.posterConfig=Poster konfigurieren +tab.video.metaDataConfig=Metadaten konfigurieren +tab.video.trackConfig=Untertitel konfigurieren +tab.video.settings=Videokonfiguration +tab.video.qualityConfig=Qualit\u00E4t konfigurieren +video.config.width=Breite in pixel +video.config.height=H\u00F6he in pixel +video.config.filename=Dateinamen +video.config.creationDate=Erstellungs Datum +video.config.fileSize=Gr\u00F6sse der Videodatei +video.config.description=Beschreibung +video.config.poster.hint=Hier k\u00F6nnen Sie das Titelbild konfigurieren welches vor dem Video angezeigt wird sowie in der Kursnode \u00FCbersicht. +video.config.poster=Poster +video.config.poster.replace=Ersetzte poster +video.config.poster.upload=Upload poster +video.config.tracks=Verf\u00FCgbare Untertiteldateien +video.config.tracks.table.add=hinzuf\u00FCgen +video.config.tracks.table.delete=l\u00F6schen +video.config.tracks.table.lang=Sprache +video.config.track.table.file=Untertitel Datei +add.track=Untertitel hinzuf\u00FCgen +track.table.label=Untertitel +track.table.header.file=Datei +track.table.header.language=Sprache +track.table.header.delete=l\u00F6schen +track.upload=Untertitel Datei +track.langs=Sprache +track.upload=Upload +track.delete=L\u00F6schen +track.upload.error.nolang=Bitte w\u00E4hlen Sie eine Sprache aus dieser Liste aus +track.upload.error.nofile=Bitte w\u00E4hlen Sie eine Datei aus. +poster.select=ausw\u00E4hlen +admin.menu.title=Video +admin.menu.title.alt=Konfiguration der Video-Resource +admin.config.title=Video Konfiguration +admin.config.enable=Videoresource einschalten +admin.config.videoNode=Video Kursbaustein aktivieren +admin.config.transcoding=Transcoding aktivieren +tab.video.qualityConfig=Videoqualit\u00E4ten + +quality.table.header.type=Typ +quality.table.header.dimension=Dimension +quality.table.header.size=Gr\u00F6sse +quality.table.header.format=Format +quality.table.header.view=ansehen +quality.view=vorschau \ No newline at end of file diff --git a/src/main/java/org/olat/modules/video/ui/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/modules/video/ui/_i18n/LocalStrings_en.properties new file mode 100644 index 0000000000000000000000000000000000000000..fe26db9a1e175d666fcdd98e628eeddad601272d --- /dev/null +++ b/src/main/java/org/olat/modules/video/ui/_i18n/LocalStrings_en.properties @@ -0,0 +1,37 @@ +tab.video.posterConfig=Poster configuration +tab.video.metaDataConfig=Metadata configuration +tab.video.trackConfig=Subtitle configuration +tab.video.settings=Video settings +video.config.width=Width in px +video.config.height=Height in px +video.config.filename=Filename +video.config.creationDate=Creation Date +video.config.fileSize=Size of videofile +video.config.description=Description +video.config.poster.hint=Here you can configure the posterframe which will be visible in the videoplayer before the video start playing aswell as in course overview +video.config.poster=Poster frame +video.config.poster.replace=Replace poster +video.config.poster.upload=Upload poster +video.config.tracks=Available Trackfiles +video.config.tracks.table.add=add +video.config.tracks.table.delete=delete +video.config.tracks.table.lang=language +video.config.track.table.file=track file +video.config.track.error.type=only *.vtt and *.srt files are permitted +add.track=Add Subtitle +table.label=Subtitles +table.header.file=File +table.header.language=Language +table.header.delete=Delete +track.upload=track file +track.langs=Language +track.upload=Upload +track.delete=Delete +track.upload.error.nolang=Please select a language form the list +poster.select=select +admin.menu.title=Video +admin.menu.title.alt=Configuration of the Video-Resource +admin.config.title=Video Configuration +admin.config.enable=enable videoresource +admin.config.videoNode=enable video coursenode +admin.config.transcoding=enable video transcoding \ No newline at end of file diff --git a/src/main/java/org/olat/repository/_i18n/LocalStrings_de.properties b/src/main/java/org/olat/repository/_i18n/LocalStrings_de.properties index fadfdfa0ae584ae008572986c9873c842de7ecdb..870ba1dd56ee5bbf6e02157fc0f50779736c564b 100644 --- a/src/main/java/org/olat/repository/_i18n/LocalStrings_de.properties +++ b/src/main/java/org/olat/repository/_i18n/LocalStrings_de.properties @@ -10,6 +10,7 @@ FileResource.GLOSSARY=Glossar FileResource.IMAGE=Bild FileResource.IMSCP=CP-Lerninhalt FileResource.MOVIE=Film +FileResource.VIDEO=Video FileResource.PDF=PDF FileResource.PODCAST=Podcast FileResource.PPT=PowerPoint diff --git a/src/main/java/org/olat/repository/_i18n/LocalStrings_en.properties b/src/main/java/org/olat/repository/_i18n/LocalStrings_en.properties index 37499632484716f3935296abcd39d37888b4a5ad..c498d1de24a8b1c613c669c22cc8cf35c8e986f7 100644 --- a/src/main/java/org/olat/repository/_i18n/LocalStrings_en.properties +++ b/src/main/java/org/olat/repository/_i18n/LocalStrings_en.properties @@ -10,6 +10,7 @@ FileResource.GLOSSARY=Glossary FileResource.IMAGE=Image FileResource.IMSCP=CP learning content FileResource.MOVIE=Movie +FileResource.VIDEO=Video FileResource.PDF=PDF FileResource.PODCAST=Podcast FileResource.PPT=PowerPoint diff --git a/src/main/java/org/olat/repository/controllers/ReferencableEntriesSearchController.java b/src/main/java/org/olat/repository/controllers/ReferencableEntriesSearchController.java index fd7299640a66e25536c92ba28a995247a042b717..88cd42bd6c340a070487d8b44e171e94b858b7f3 100644 --- a/src/main/java/org/olat/repository/controllers/ReferencableEntriesSearchController.java +++ b/src/main/java/org/olat/repository/controllers/ReferencableEntriesSearchController.java @@ -49,6 +49,7 @@ import org.olat.fileresource.types.BlogFileResource; import org.olat.fileresource.types.ImsCPFileResource; import org.olat.fileresource.types.PodcastFileResource; import org.olat.fileresource.types.ScormCPFileResource; +import org.olat.fileresource.types.VideoFileResource; import org.olat.fileresource.types.WikiResource; import org.olat.group.BusinessGroupModule; import org.olat.ims.qti.fileresource.SurveyFileResource; @@ -231,7 +232,8 @@ public class ReferencableEntriesSearchController extends BasicController { ScormCPFileResource.TYPE_NAME, SurveyFileResource.TYPE_NAME, BlogFileResource.TYPE_NAME, - PodcastFileResource.TYPE_NAME + PodcastFileResource.TYPE_NAME, + VideoFileResource.TYPE_NAME }; if (Collections.indexOfSubList(Arrays.asList(importAllowed), limitTypeList) != -1) { return true; } diff --git a/src/main/java/org/olat/repository/handlers/RepositoryHandlerFactory.java b/src/main/java/org/olat/repository/handlers/RepositoryHandlerFactory.java index 2a994e41cbc6abcf16697346ad733a2524133348..56a8932c47b7d7ec0cf161fdc92faa4598a2db1e 100644 --- a/src/main/java/org/olat/repository/handlers/RepositoryHandlerFactory.java +++ b/src/main/java/org/olat/repository/handlers/RepositoryHandlerFactory.java @@ -78,6 +78,7 @@ public class RepositoryHandlerFactory { registerHandler(new SharedFolderHandler(), 40); registerHandler(new GlossaryHandler(), 41); registerHandler(new PortfolioHandler(), 42); + registerHandler(new VideoHandler(), 43); registerHandler(new WebDocumentHandler(DocFileResource.TYPE_NAME), 10001); registerHandler(new WebDocumentHandler(XlsFileResource.TYPE_NAME), 10002); diff --git a/src/main/java/org/olat/repository/handlers/VideoHandler.java b/src/main/java/org/olat/repository/handlers/VideoHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..d6465e105fd0a6d17bab9b11bdea365a2da67779 --- /dev/null +++ b/src/main/java/org/olat/repository/handlers/VideoHandler.java @@ -0,0 +1,259 @@ +/** +* OLAT - Online Learning and Training<br> +* http://www.olat.org +* <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 +* <p> +* http://www.apache.org/licenses/LICENSE-2.0 +* <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> +* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br> +* University of Zurich, Switzerland. +* <hr> +* <a href="http://www.openolat.org"> +* OpenOLAT - Online Learning and Training</a><br> +* This file has been modified by the OpenOLAT community. Changes are licensed +* under the Apache 2.0 license as the original file. +*/ + +package org.olat.repository.handlers; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.util.Locale; + +import org.olat.core.CoreSpringFactory; +import org.olat.core.commons.persistence.DBFactory; +import org.olat.core.commons.services.image.Size; +import org.olat.core.commons.services.video.MovieServiceImpl; +import org.olat.core.gui.UserRequest; +import org.olat.core.gui.components.stack.TooledStackedPanel; +import org.olat.core.gui.control.Controller; +import org.olat.core.gui.control.WindowControl; +import org.olat.core.gui.control.generic.layout.MainLayoutController; +import org.olat.core.gui.control.generic.wizard.StepsMainRunController; +import org.olat.core.gui.media.MediaResource; +import org.olat.core.id.Identity; +import org.olat.core.id.OLATResourceable; +import org.olat.core.logging.AssertException; +import org.olat.core.logging.OLog; +import org.olat.core.logging.Tracing; +import org.olat.core.util.FileUtils; +import org.olat.core.util.StringHelper; +import org.olat.core.util.coordinate.LockResult; +import org.olat.core.util.vfs.LocalFileImpl; +import org.olat.core.util.vfs.VFSLeaf; +import org.olat.core.util.vfs.VFSManager; +import org.olat.core.util.xml.XStreamHelper; +import org.olat.course.assessment.AssessmentMode; +import org.olat.fileresource.FileResourceManager; +import org.olat.fileresource.types.FileResource; +import org.olat.fileresource.types.ResourceEvaluation; +import org.olat.fileresource.types.VideoFileResource; +import org.olat.modules.video.VideoModule; +import org.olat.modules.video.managers.VideoManager; +import org.olat.modules.video.models.VideoMetadata; +import org.olat.modules.video.ui.VideoDisplayController; +import org.olat.modules.video.ui.VideoRuntimeController; +import org.olat.repository.RepositoryEntry; +import org.olat.repository.RepositoryManager; +import org.olat.repository.RepositoryService; +import org.olat.repository.model.RepositoryEntrySecurity; +import org.olat.repository.ui.RepositoryEntryRuntimeController.RuntimeControllerCreator; +import org.olat.resource.OLATResource; +import org.olat.resource.OLATResourceManager; +import org.springframework.beans.factory.annotation.Autowired; + + +/** + * Initial Date: Mar 27, 2015 + * + * @author Dirk Furrer + * + * + */ +public class VideoHandler extends FileHandler { + + private static final OLog log = Tracing.createLoggerFor(VideoHandler.class); + @Autowired + private RepositoryManager repositoryManager; + + private VideoManager videoManager; + private VideoModule videomodule ; + + @Override + public boolean isCreate() { + return false; + } + + @Override + public String getCreateLabelI18nKey() { + return null; + } + + @Override + public RepositoryEntry createResource(Identity initialAuthor, String displayname, String description, Object createObject, Locale locale) { + return null; + } + + @Override + public boolean isPostCreateWizardAvailable() { + return false; + } + + @Override + public ResourceEvaluation acceptImport(File file, String filename) { + if(!StringHelper.containsNonWhitespace(filename)) { + filename = file.getName(); + } + + ResourceEvaluation eval = new ResourceEvaluation(false); + String extension = FileUtils.getFileSuffix(filename); + if(StringHelper.containsNonWhitespace(extension)) { + if (VideoFileResource.validate(filename)) { + eval.setValid(true); + } + } + return eval; + } + + @Override + public RepositoryEntry importResource(Identity initialAuthor, String initialAuthorAlt, String displayname, String description, + boolean withReferences, Locale locale, File file, String filename) { + + FileResource ores; + if (VideoFileResource.validate(filename)) { + ores = new VideoFileResource(); + } else { + return null; + } + + OLATResource resource = OLATResourceManager.getInstance().createAndPersistOLATResourceInstance(ores); + File videoResourceFileroot = FileResourceManager.getInstance().getFileResourceRootImpl(resource).getBasefile(); + File mediaFolder = new File(videoResourceFileroot, "media"); + if(!mediaFolder.exists()) mediaFolder.mkdir(); + + File target = new File(mediaFolder, "video.mp4"); + + + try { + Files.move(file.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + log.error("", e); + } + + RepositoryEntry re = CoreSpringFactory.getImpl(RepositoryService.class) + .create(initialAuthor, null, "", displayname, description, resource, RepositoryEntry.ACC_OWNERS); + DBFactory.getInstance().commit(); + + // generate Metadata + videoManager = CoreSpringFactory.getImpl(VideoManager.class); + videomodule = CoreSpringFactory.getImpl(VideoModule.class); + File metaDataFile = new File(videoResourceFileroot, "video_metadata.xml"); + try{ + XStreamHelper.writeObject(XStreamHelper.createXStreamInstance(), metaDataFile, new VideoMetadata(resource)); + videoManager.setTitle(resource, displayname); + Size videoSize = CoreSpringFactory.getImpl(MovieServiceImpl.class).getSize(new LocalFileImpl(target), "mp4"); + videoManager.setVideoSize(resource, videoSize); + VFSLeaf posterResource = VFSManager.resolveOrCreateLeafFromPath(FileResourceManager.getInstance().getFileResourceMedia(resource), "/poster.jpg"); + + videoManager.getFrame(resource, 20, posterResource); + + videoManager.setPosterframe(resource, posterResource); + }catch(IOException e){ + log.warn("wasnt able to create poster for video"+filename); + } + + if(videomodule.isTranscodingEnabled()){ + videoManager.optimizeVideoRessource(resource); + } + + videoManager.setRatingEnabled(resource, false); + videoManager.setCommentsEnabled(resource, false); + return re; + } + + + + @Override + public MediaResource getAsMediaResource(OLATResourceable res, boolean backwardsCompatible) { + return FileResourceManager.getInstance().getAsDownloadeableMediaResource(res); + } + + @Override + public String getSupportedType() { + return VideoFileResource.TYPE_NAME; + } + + @Override + public boolean supportsDownload() { + return true; + } + + @Override + public EditionSupport supportsEdit(OLATResourceable resource) { + return EditionSupport.no; + } + + @Override + public StepsMainRunController createWizardController(OLATResourceable res, UserRequest ureq, WindowControl wControl) { + throw new AssertException("Trying to get wizard where no creation wizard is provided for this type."); + } + + @Override + public MainLayoutController createLaunchController(RepositoryEntry re, RepositoryEntrySecurity reSecurity, UserRequest ureq, WindowControl wControl) { + return new VideoRuntimeController(ureq, wControl, re, reSecurity, new RuntimeControllerCreator() { + @Override + public Controller create(UserRequest uureq, WindowControl wwControl, TooledStackedPanel toolbarPanel, + RepositoryEntry entry, RepositoryEntrySecurity rereSecurity, AssessmentMode assessmentMode) { + return new VideoDisplayController(uureq, wwControl, entry); + } + }); + } + + @Override + public Controller createEditorController(RepositoryEntry re, UserRequest ureq, WindowControl wControl, TooledStackedPanel toolbar) { + throw new AssertException("a web document is not editable!!! res-id:"+re.getResourceableId()); + } + + protected String getDeletedFilePrefix() { + return "del_video_"; + } + + @Override + public LockResult acquireLock(OLATResourceable ores, Identity identity) { + //nothing to do + return null; + } + + @Override + public void releaseLock(LockResult lockResult) { + //nothing to do since nothing locked + } + + @Override + public boolean isLocked(OLATResourceable ores) { + return false; + } + + @Override + public RepositoryEntry copy(Identity author, RepositoryEntry source, + RepositoryEntry target) { + OLATResource sourceResource = source.getOlatResource(); + OLATResource targetResource = target.getOlatResource(); + File sourceFileroot = FileResourceManager.getInstance().getFileResourceRootImpl(sourceResource).getBasefile(); + File targetFileroot = FileResourceManager.getInstance().getFileResourceRootImpl(targetResource).getBasefile(); + FileUtils.copyDirContentsToDir(sourceFileroot, targetFileroot, false, "copy"); + return target; + } + +} diff --git a/src/main/java/org/olat/repository/ui/WebDocumentRunController.java b/src/main/java/org/olat/repository/ui/WebDocumentRunController.java index 8d72259672836340f374ab8d6a045e7109919e50..b2e13db9c6da79c244746ff1d6f05297866b1e1f 100644 --- a/src/main/java/org/olat/repository/ui/WebDocumentRunController.java +++ b/src/main/java/org/olat/repository/ui/WebDocumentRunController.java @@ -21,7 +21,6 @@ package org.olat.repository.ui; import javax.servlet.http.HttpServletRequest; -import org.olat.core.CoreSpringFactory; import org.olat.core.commons.services.image.Size; import org.olat.core.commons.services.video.MovieService; import org.olat.core.dispatcher.mapper.Mapper; @@ -45,6 +44,7 @@ import org.olat.core.util.vfs.VFSMediaResource; import org.olat.fileresource.FileResourceManager; import org.olat.repository.RepositoryEntry; import org.olat.resource.OLATResource; +import org.springframework.beans.factory.annotation.Autowired; /** * @@ -54,6 +54,9 @@ import org.olat.resource.OLATResource; */ public class WebDocumentRunController extends BasicController { + @Autowired + private MovieService movieService; + public WebDocumentRunController(UserRequest ureq, WindowControl wControl, RepositoryEntry entry) { super(ureq, wControl); @@ -78,7 +81,7 @@ public class WebDocumentRunController extends BasicController { String mediaUrl = registerMapper(ureq, new MediaMapper(document)); mainVC.contextPut("movie", filename); mainVC.contextPut("mediaUrl", Settings.createServerURI() + mediaUrl); - Size realSize = CoreSpringFactory.getImpl(MovieService.class).getSize(document, extension); + Size realSize = movieService.getSize(document, extension); if(realSize != null) { mainVC.contextPut("height", realSize.getHeight()); mainVC.contextPut("width", realSize.getWidth());