001/** 002 * 003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved. 004 * 005 * This library is free software; you can redistribute it and/or 006 * modify it under the terms of the GNU Lesser General Public 007 * License as published by the Free Software Foundation; either 008 * version 2.1 of the License, or (at your option) any later version. 009 * 010 * This library is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013 * Lesser General Public License for more details. 014 * 015 * You should have received a copy of the GNU Lesser General Public 016 * License along with this library. If not, see <http://www.gnu.org/licenses/>. 017 * 018 **/ 019package lucee.runtime.video; 020 021import java.io.IOException; 022import java.io.InputStream; 023import java.io.OutputStream; 024import java.util.ArrayList; 025import java.util.LinkedHashMap; 026import java.util.Map; 027import java.util.StringTokenizer; 028 029import lucee.commons.io.IOUtil; 030import lucee.commons.io.res.Resource; 031import lucee.loader.util.Util; 032import lucee.runtime.config.Config; 033import lucee.runtime.config.ConfigImpl; 034import lucee.runtime.exp.ApplicationException; 035import lucee.runtime.exp.PageException; 036import lucee.runtime.op.Caster; 037import lucee.runtime.text.xml.XMLUtil; 038 039import org.w3c.dom.Document; 040import org.w3c.dom.Element; 041import org.w3c.dom.Node; 042import org.w3c.dom.NodeList; 043import org.xml.sax.InputSource; 044import org.xml.sax.SAXException; 045 046 047public class ProfileCollection { 048 049 private static VideoUtil util=VideoUtilImpl.getInstance(); 050 051 private Map<String,VideoProfile> profiles; 052 053 public ProfileCollection(Config config) throws ApplicationException { 054 init(config,true); 055 } 056 057 private void init(Config config, boolean initProfiles) throws ApplicationException { 058 // get the video directory 059 Resource dir = ((ConfigImpl)config).getVideoDirectory(); 060 061 // get the video.xml 062 Resource xml=dir.getRealResource("video.xml"); 063 064 // create (if not exist) and return video xml as dom 065 Element video; 066 try { 067 video = getVideoXML(xml); 068 } catch (Exception e) { 069 throw new ApplicationException("can not load video xml file ["+xml+"]",Caster.toClassName(e)+":"+e.getMessage()); 070 } 071 072 // translate form DOM to a List of VideoProfile 073 if(initProfiles){ 074 try { 075 profiles=translateVideoXML(video); 076 } catch (PageException e) { 077 throw new ApplicationException("can not load profiles from video xml file ["+xml+"] a type is invalid",e.getMessage()); 078 } 079 } 080 } 081 082 /** 083 * @return the qualities 084 */ 085 public Map<String,VideoProfile> getProfiles() { 086 return profiles; 087 } 088 089 090 091 /** 092 * translate form DOM to a List of VideoProfile 093 * @param video 094 * @return 095 * @throws PageException 096 */ 097 private static Map<String,VideoProfile> translateVideoXML(Element video) throws PageException { 098 Map<String,VideoProfile> profiles=new LinkedHashMap<String,VideoProfile>(); 099 // quality 100 Element qd = getChildByName(video, "profiles", false); 101 Element[] items = getChildren(qd, "profile"); 102 Element item; 103 VideoProfile vq; 104 String value; 105 for(int i=0;i<items.length;i++) { 106 item=items[i]; 107 vq=new VideoProfileImpl(); 108 // aspect-ratio 109 value=item.getAttribute("aspect-ratio"); 110 if(!Util.isEmpty(value))vq.setAspectRatio(value); 111 112 // aspect-ratio 113 value=item.getAttribute("audio-bitrate"); 114 if(!Util.isEmpty(value))vq.setAudioBitrate(util.toBytes(value)); 115 116 // audio-samplerate 117 value=item.getAttribute("audio-samplerate"); 118 if(!Util.isEmpty(value))vq.setAudioSamplerate(util.toHerz(value)); 119 120 // dimension 121 String w = item.getAttribute("width"); 122 String h = item.getAttribute("height"); 123 if(!Util.isEmpty(w) && !Util.isEmpty(h)) { 124 vq.setDimension(Caster.toIntValue(w), Caster.toIntValue(h)); 125 } 126 127 // framerate 128 value=item.getAttribute("framerate"); 129 String value2 = item.getAttribute("fps"); 130 if(!Util.isEmpty(value))vq.setFramerate(Caster.toDoubleValue(value)); 131 else if(!Util.isEmpty(value2))vq.setFramerate(Caster.toDoubleValue(value2)); 132 133 // video-bitrate 134 value=item.getAttribute("video-bitrate"); 135 if(!Util.isEmpty(value))vq.setVideoBitrate(util.toBytes(value)); 136 137 // video-bitrate-max 138 value=item.getAttribute("video-bitrate-max"); 139 if(!Util.isEmpty(value))vq.setVideoBitrateMax(util.toBytes(value)); 140 141 // video-bitrate-min 142 value=item.getAttribute("video-bitrate-min"); 143 if(!Util.isEmpty(value))vq.setVideoBitrateMin(util.toBytes(value)); 144 145 // video-bitrate-tolerance 146 value=item.getAttribute("video-bitrate-tolerance"); 147 if(!Util.isEmpty(value))vq.setVideoBitrateTolerance(util.toBytes(value)); 148 149 // video-codec 150 value=item.getAttribute("video-codec"); 151 //print.out("video-codec:"+value); 152 if(!Util.isEmpty(value))vq.setVideoCodec(value); 153 154 // audio-codec 155 value=item.getAttribute("audio-codec"); 156 if(!Util.isEmpty(value))vq.setAudioCodec(value); 157 158 // 159 value=item.getAttribute("label"); 160 //print.out("label:"+value); 161 if(!Util.isEmpty(value)) { 162 String[] arr=toArray(value); 163 for(int y=0;y<arr.length;y++) { 164 profiles.put(arr[y].trim().toLowerCase(), vq); 165 } 166 } 167 } 168 return profiles; 169 } 170 171 172 private static Element getChildByName(Node parent, String nodeName,boolean insertBefore) { 173 if(parent==null) return null; 174 NodeList list=parent.getChildNodes(); 175 int len=list.getLength(); 176 177 for(int i=0;i<len;i++) { 178 Node node=list.item(i); 179 180 if(node.getNodeType()==Node.ELEMENT_NODE && node.getNodeName().equalsIgnoreCase(nodeName)) { 181 return (Element) node; 182 } 183 } 184 Element newEl = parent.getOwnerDocument().createElement(nodeName); 185 if(insertBefore)parent.insertBefore(newEl, parent.getFirstChild()); 186 else parent.appendChild(newEl); 187 return newEl; 188 } 189 190 private static Element[] getChildren(Node parent, String nodeName) { 191 if(parent==null) return new Element[0]; 192 NodeList list=parent.getChildNodes(); 193 int len=list.getLength(); 194 ArrayList rtn=new ArrayList(); 195 196 for(int i=0;i<len;i++) { 197 Node node=list.item(i); 198 if(node.getNodeType()==Node.ELEMENT_NODE && node.getNodeName().equalsIgnoreCase(nodeName)) { 199 rtn.add(node); 200 } 201 } 202 return (Element[]) rtn.toArray(new Element[rtn.size()]); 203 } 204 205 private static String[] toArray(String str) { 206 StringTokenizer st=new StringTokenizer(str,","); 207 ArrayList list=new ArrayList(); 208 while(st.hasMoreTokens()) { 209 list.add(str=st.nextToken()); 210 } 211 return (String[]) list.toArray(new String[list.size()]); 212 } 213 214 /** 215 * create (if not exist) and return video xml as dom 216 * @param xml 217 * @return 218 * @throws IOException 219 * @throws SAXException 220 */ 221 private static Element getVideoXML(Resource xml) throws IOException, SAXException { 222 if(!xml.exists()) { 223 createFileFromResource("/resource/video/video.xml",xml); 224 } 225 Document doc = loadDocument(xml); 226 return doc.getDocumentElement(); 227 } 228 229 public static void createFileFromResource(String path, Resource bin) throws IOException { 230 InputStream is=null; 231 OutputStream os=null; 232 233 if(bin.exists()) return; 234 235 try { 236 Util.copy( 237 is=new VideoInputImpl(null).getClass().getResourceAsStream(path), 238 os=bin.getOutputStream() 239 ); 240 } 241 finally { 242 Util.closeEL(is); 243 Util.closeEL(os); 244 } 245 } 246 247 private static Document loadDocument(Resource xmlFile) throws SAXException, IOException { 248 InputStream is=null; 249 try { 250 return loadDocument(is=xmlFile.getInputStream()); 251 } 252 finally { 253 Util.closeEL(is); 254 } 255 } 256 257 private static Document loadDocument(InputStream is) throws SAXException, IOException { 258 try{ 259 InputSource source = new InputSource(is); 260 return XMLUtil.parse(source, null, false); 261 } 262 finally { 263 IOUtil.closeEL(is); 264 } 265 } 266}