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.util.Map;
022
023import lucee.commons.io.res.Resource;
024import lucee.commons.io.res.type.file.FileResource;
025import lucee.commons.io.res.type.http.HTTPResource;
026import lucee.commons.lang.ClassException;
027import lucee.commons.lang.ClassUtil;
028import lucee.commons.lang.StringUtil;
029import lucee.runtime.PageContext;
030import lucee.runtime.config.Config;
031import lucee.runtime.exp.ApplicationException;
032import lucee.runtime.exp.ExpressionException;
033import lucee.runtime.exp.PageException;
034import lucee.runtime.functions.string.Hash;
035import lucee.runtime.op.Caster;
036
037import org.apache.commons.collections.map.ReferenceMap;
038
039public class VideoUtilImpl implements VideoUtil {
040
041
042        private static Map sizes=new ReferenceMap(ReferenceMap.SOFT,ReferenceMap.SOFT);
043        private static VideoUtilImpl instance=new VideoUtilImpl();
044
045        private VideoUtilImpl(){                }
046
047        public static VideoUtilImpl getInstance() {
048                return instance;
049        }
050        
051        /**
052         * @see lucee.runtime.video.VideoUtil#createVideoInput(lucee.commons.io.res.Resource)
053         */
054        public VideoInput createVideoInput(Resource input) {
055                return new VideoInputImpl(input);
056        }
057
058        /**
059         * @see lucee.runtime.video.VideoUtil#createVideoOutput(lucee.commons.io.res.Resource)
060         */
061        public VideoOutput createVideoOutput(Resource output) {
062                return new VideoOutputImpl(output);
063        }
064
065        /**
066         * @see lucee.runtime.video.VideoUtil#createVideoProfile()
067         */
068        public VideoProfile createVideoProfile() {
069                return new VideoProfileImpl();
070        }
071
072        
073        public long toBytes(String byt) throws PageException {
074                byt=byt.trim().toLowerCase();
075                if(byt.endsWith("kb/s") || byt.endsWith("kbps")) {
076                        return (long)(Caster.toDoubleValue(byt.substring(0,byt.length()-4).trim())*1024);
077                }
078                if(byt.endsWith("mb/s") || byt.endsWith("mbps")) {
079                        return (long)(Caster.toDoubleValue(byt.substring(0,byt.length()-4).trim())*1024*1024);
080                }
081                if(byt.endsWith("gb/s") || byt.endsWith("gbps")) {
082                        return (long)(Caster.toDoubleValue(byt.substring(0,byt.length()-4).trim())*1024*1024*1024);
083                }
084                if(byt.endsWith("b/s") || byt.endsWith("bps")) {
085                        return (long)(Caster.toDoubleValue(byt.substring(0,byt.length()-3).trim()));
086                }
087                
088                if(byt.endsWith("kbit/s")) {
089                        return (long)(Caster.toDoubleValue(byt.substring(0,byt.length()-6).trim())*1024);
090                }
091                if(byt.endsWith("mbit/s")) {
092                        return (long)(Caster.toDoubleValue(byt.substring(0,byt.length()-6).trim())*1024*1024);
093                }
094                if(byt.endsWith("gbit/s")) {
095                        return (long)(Caster.toDoubleValue(byt.substring(0,byt.length()-6).trim())*1024*1024*1024);
096                }
097                if(byt.endsWith("bit/s")) {
098                        return (long)(Caster.toDoubleValue(byt.substring(0,byt.length()-5).trim()));
099                }
100                
101
102                if(byt.endsWith("kb")) {
103                        return (long)(Caster.toDoubleValue(byt.substring(0,byt.length()-2).trim())*1024);
104                }
105                if(byt.endsWith("mb")) {
106                        return (long)(Caster.toDoubleValue(byt.substring(0,byt.length()-2).trim())*1024*1024);
107                }
108                if(byt.endsWith("gb")) {
109                        return (long)(Caster.toDoubleValue(byt.substring(0,byt.length()-2).trim())*1024*1024*1024);
110                }
111
112                if(byt.endsWith("g")) {
113                        return (long)(Caster.toDoubleValue(byt.substring(0,byt.length()-1).trim())*1024*1024*1024);
114                }
115                if(byt.endsWith("m")) {
116                        return (long)(Caster.toDoubleValue(byt.substring(0,byt.length()-1).trim())*1024*1024);
117                }
118                if(byt.endsWith("k")) {
119                        return (long)(Caster.toDoubleValue(byt.substring(0,byt.length()-1).trim())*1024);
120                }
121                if(byt.endsWith("b")) {
122                        return (long)(Caster.toDoubleValue(byt.substring(0,byt.length()-1).trim()));
123                }
124                return Caster.toLongValue(byt);
125        }
126        
127        public long toHerz(String byt) throws PageException {
128                byt=byt.trim().toLowerCase();
129                if(byt.endsWith("mhz")) {
130                        return (long)(Caster.toDoubleValue(byt.substring(0,byt.length()-3).trim())*1000*1000);
131                }
132                if(byt.endsWith("khz")) {
133                        return (long)(Caster.toDoubleValue(byt.substring(0,byt.length()-3).trim())*1000);
134                }
135                if(byt.endsWith("hz")) {
136                        return (long)(Caster.toDoubleValue(byt.substring(0,byt.length()-2).trim()));
137                }
138                return Caster.toLongValue(byt);
139        }
140        
141        public long toMillis(String time) throws PageException {
142                int last=0,index=time.indexOf(':');
143                long hour=Caster.toIntValue(time.substring(last,index).trim());
144                last=index+1;
145                
146                index=time.indexOf(':',last);
147                long minute=Caster.toIntValue(time.substring(last,index).trim());
148                
149                double seconds=Caster.toDoubleValue(time.substring(index+1).trim());
150                return (hour*60L*60L*1000L)+(minute*60L*1000L)+((int)(seconds*1000F));
151        }
152        
153        public static VideoExecuter createVideoExecuter(Config config) throws ClassException {
154                Class clazz = config.getVideoExecuterClass();
155                return (VideoExecuter) ClassUtil.loadInstance(clazz);
156        }
157
158        public int[] calculateDimension(PageContext pc,VideoInput[] sources,int width, String strWidth,int height, String strHeight) throws PageException {
159                int[] rtn;
160                
161                if(width!=-1 && height!=-1) {
162                        return new int[]{width,height};
163                }
164                
165                // video component not installed
166                try {
167                        if(VideoUtilImpl.createVideoExecuter(pc.getConfig()) instanceof VideoExecuterNotSupported){
168                                throw new ApplicationException("attributes width/height are required when no video analyser is installed");
169                        }
170                } catch (ClassException e) {
171                        
172                }
173                
174                
175                VideoInput source;
176                
177                // hash
178                StringBuffer sb=new StringBuffer(strHeight+"-"+strWidth);
179                for(int i=0;i<sources.length;i++) {
180                        sb.append(sources[i].getResource().toString());
181                }
182                
183                // get from casche
184                String key = Hash.call(pc, sb.toString());
185                
186                int[] ci=(int[]) sizes.get(key);
187                if(ci!=null) {
188                        return ci;
189                }
190                // getSize
191                int w=0,h=0;
192                try {
193                        for(int i=0;i<sources.length;i++) {
194                                source = sources[i];
195                                checkResource(source.getResource());
196                                
197                                
198                                VideoInfo info = VideoUtilImpl.createVideoExecuter(pc.getConfig()).info(pc.getConfig(),source);
199                                
200                                if(w<info.getWidth()) {
201                                        h=info.getHeight();
202                                        w=info.getWidth();
203                                }
204                                
205                                
206                        }
207                }
208                catch(Exception ve) {
209                        throw Caster.toPageException(ve);
210                }
211
212                // calculate only height
213                if(width!=-1) {
214                        height=calculateSingle(w,width,strHeight,h);
215                }
216                // calculate only height
217                else if(height!=-1) {
218                        width=calculateSingle(h,height,strWidth,w);
219                }
220                else {
221                        width=procent2pixel(strWidth,w);
222                        height=procent2pixel(strHeight,h);
223                        if(width!=-1 && height!=-1) {}
224                        else if(width==-1 && height==-1) {
225                                width=w;
226                                height=h;
227                        }
228                        else if(width!=-1) 
229                                height=calucalteFromOther(h,w,width);
230                        else 
231                                width=calucalteFromOther(w,h,height);
232                        
233                        
234                }
235                sizes.put(key, rtn=new int[]{width,height});
236                return rtn;
237        }
238        
239        private static int procent2pixel(String str, int source) throws ExpressionException {
240                if(!StringUtil.isEmpty(str)) {
241                        if(StringUtil.endsWith(str, '%')) {
242                                str=str.substring(0,str.length()-1).trim();
243                                double procent = Caster.toDoubleValue(str);
244                                if(procent<0 )
245                                        throw new ExpressionException("procent has to be positive number (now "+str+")");
246                                return (int)(source*(procent/100D));
247                        }
248                        return Caster.toIntValue(str);
249                }
250                return -1;
251        }
252
253        private static int calculateSingle(int srcOther,int destOther, String strDim, int srcDim) throws ExpressionException {
254                int res = procent2pixel(strDim, srcDim);
255                if(res!=-1) return res;
256                return calucalteFromOther(srcDim,srcOther,destOther);//(int)(Caster.toDoubleValue(srcDim)*Caster.toDoubleValue(destOther)/Caster.toDoubleValue(srcOther));
257        }
258
259        private static int calucalteFromOther(int srcDim,int srcOther,int destOther) {
260                return (int)(Caster.toDoubleValue(srcDim)*Caster.toDoubleValue(destOther)/Caster.toDoubleValue(srcOther));
261        }
262        
263
264        private static void checkResource(Resource resource) throws ApplicationException {
265                if(resource instanceof FileResource)return;
266                if(resource instanceof HTTPResource)
267                        throw new ApplicationException("attribute width and height are required when external sources are invoked");
268                
269                throw new ApplicationException("the resource type ["+resource.getResourceProvider().getScheme()+"] is not supported");
270        }
271}