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}