001    /////////////////////////////////////////////////
002    // This file is part of Sears project.
003    // Subtitle Editor And Re-Synch
004    // A tool to easily modify and resynch movies subtitles.
005    /////////////////////////////////////////////////
006    //This program is free software; 
007    //you can redistribute it and/or modify it under the terms 
008    //of the GNU General Public License 
009    //as published by the Free Software Foundation; 
010    //either version 2 of the License, or (at your option) any later version.
011    /////////////////////////////////////////////////
012    //Sears project is available under sourceforge
013    // at adress: http://sourceforge.net/projects/sears/
014    //Copyright (C) 2005 Booba Skaya
015    //Mail: booba.skaya@gmail.com
016    ////////////////////////////////////////////////
017    package sears.file;
018    
019    import java.io.File;
020    import java.util.ArrayList;
021    import java.util.Collections;
022    import java.util.Iterator;
023    import java.util.NoSuchElementException;
024    import java.util.StringTokenizer;
025    
026    import sears.tools.LinearInterpolation;
027    import sears.tools.Trace;
028    
029    /**
030     * Class SubtitleFile.
031     * <br><b>Summary:</b><br>
032     * This method represent a subtitle file.
033     * It provides facilities on subtitles, as delay, resynchro.
034     * It must be specialized to the subtitle file type you want to open.
035     */
036    public abstract class SubtitleFile {
037        /**The system File that contains the file*/
038        protected File file;
039    
040        /**The ArrayList of subtitles found*/
041        protected ArrayList<Subtitle> subtitleList;
042        
043        /**A boolean to know if file has changed*/
044        protected boolean fileChanged;
045    
046        /**The temporary file */
047            protected File temporaryFile = null;
048    
049            /** (<b>LinearInterpolation</b>) LinearInterpolation: The LinearInterpolation used for magic resynchro. */
050            private LinearInterpolation linearInterpolation;
051    
052            /** (<b>String</b>) END_OF_LINE: The system END_OF_LINE */
053            protected final String END_OF_LINE = System.getProperty("line.separator");
054            
055        /**
056         * Constructor SubtitleFile.
057         * <br><b>Summary:</b><br>
058         * Constructor of the class.
059         * Beware not to use this file directly, because it does contains no ST.
060         * You will have to fill the list of ST, and save the File first.
061         */
062        public SubtitleFile() {
063            //set file to null
064            file = null;
065            //Construct an empty list.
066            subtitleList = new ArrayList<Subtitle>();
067            //File is changed
068            fileChanged = true;
069        }
070    
071        /**
072         * Constructor SubtitleFile.
073         * <br><b>Summary:</b><br>
074         * Constructor of the class.
075         * @param fileToOpen         The <b>(String)</b> path to file to open.
076         * @param _subtitlesList        The <b>(ArrayList)</b> List of subtitles.
077         */
078        public SubtitleFile(String fileToOpen, ArrayList<Subtitle> _subtitlesList) {
079            construct(new File(fileToOpen), _subtitlesList);
080        }
081    
082        /**
083         * Constructor SubtitleFile.
084         * <br><b>Summary:</b><br>
085         * Constructor of the class.    
086         * @param _file              The <b>(File)</b> file to open.
087         * @param _subtitlesList        The <b>(ArrayList)</b> List of subtitles.     
088         */
089        public SubtitleFile(File _file, ArrayList<Subtitle> _subtitlesList) {
090            construct(_file, _subtitlesList);
091        }
092    
093        /**
094         * Method construct.
095         * <br><b>Summary:</b><br>
096         * Construct the file.
097         * @param _srtFile              The <b>(File)</b> file to open.
098         * @param _subtitlesList        The <b>(ArrayList<Subtitle>)</b> List of subtitles.     
099         */
100        private void construct(File _srtFile, ArrayList<Subtitle> _subtitlesList) {
101            if (!_srtFile.exists())
102                Trace.trace(("File " + _srtFile.getAbsolutePath() + " Does not exist !"), Trace.WARNING_PRIORITY);
103            if (_subtitlesList == null)
104                Trace.trace("The given subtitleList is null !", Trace.WARNING_PRIORITY);
105            file = _srtFile;
106            subtitleList = _subtitlesList;
107            parse();
108            //File is not changed
109            fileChanged = false;
110    
111        }
112    
113        /**
114         * Method parse.
115         * <br><b>Summary:</b><br>
116         * This method parse the current file, and construct the subtitleList.
117         */
118        protected abstract void parse();
119    
120        /**
121         * Method getContentDirectory.
122         * <br><b>Summary:</b><br>
123         * This method return the file's parent folder.
124         * @return  <b>(File)</b>   The parent of the current file.
125         */
126        public File getContentDirectory() {
127            return this.getFile().getParentFile();
128        }
129    
130        /**
131         * Method getFile.
132         * <br><b>Summary:</b><br>
133         * Return the current file.
134         * @return  <b>(File)</b>   The current file.
135         */
136        public File getFile() {
137            return file;
138        }
139    
140        /**
141         * Method getTemporaryFile.
142         * <br><b>Summary:</b><br>
143         * Return the temporary file.
144         * @return  <b>(File)</b>   The temporary file.
145         */
146        public File getTemporaryFile() {
147            return temporaryFile;
148        }
149    
150        /**
151         * Method timeToString.
152         * <br><b>Summary:</b><br>
153         * This method transform a number of milliseconds in a string representation.
154         * @param milliseconds               The number of milliseconds to transform
155         * @return  <b>(String)</b>     The corresponding String representation of the number of milliseconds.
156         */
157        public static String timeToString(int milliseconds){
158            //keep the sign in memory.
159            boolean positive = milliseconds >= 0;
160            //And set time aboslute, not to be annoyed with signs.
161            milliseconds = Math.abs(milliseconds);
162            //Compute seconds/minutes/hours from milliseconds.
163            int seconds = milliseconds / 1000;
164            milliseconds = milliseconds - seconds * 1000;
165            int hours = (seconds - seconds % 3600) / 3600;
166            seconds -= hours * 3600;
167            int minutes = (seconds - seconds % 60) / 60;
168            seconds -= minutes * 60;
169            //Compute String representation of these values.
170            //Using 2 digits formatting
171            String hoursString = "";
172            hours = Math.abs(hours);
173            if (hours < 10){
174                hoursString += "0" + hours;
175            }
176            else{
177                hoursString += hours;
178            }
179            String minutesString = "";
180            if (minutes < 10){
181                minutesString += "0" + minutes;
182            }else{
183                minutesString += minutes;
184            }
185            String secondsString = "";
186            if (seconds < 10){
187                secondsString += "0" + seconds;
188            }
189            else{
190                secondsString += seconds;
191            }
192            String millisecondsString ="";
193            if (milliseconds < 10){
194                millisecondsString += "00" + milliseconds;
195            }else if (milliseconds < 100){
196                millisecondsString += "0" + milliseconds;
197            }else{
198                millisecondsString += "" +milliseconds;
199            }
200            String result = "";
201            //Remember to set the String negative, if it was negative at the begining.
202            if(!positive){
203                    result = "-";
204            }
205            result = result + hoursString + ":" + minutesString + ":" + secondsString + "," +millisecondsString;
206            return result;
207    
208        }
209    
210        /**
211         * Method stringToTime.
212         * <br><b>Summary:</b><br>
213         * Return the number of miliseconds that correspond to the given String time representation.
214         * @param time              The string srt time representation.
215         * @return <b>(int)</b>     The corresponding number of miliseconds.
216         */
217        public static int stringToTime(String time) throws NumberFormatException{
218            //the result of the method.
219            int result = 0;
220            //Check the sign.
221            boolean positive = true;
222            //If time start with a '-', it means it is a negative time.
223            if(time != null && time.trim().startsWith("-")){
224                    positive =false;
225                    //remove the '-' char.
226                    int indexOfMinus = time.indexOf("-");
227                    if(indexOfMinus != -1){
228                            time = time.substring(indexOfMinus+1);
229                    }
230            }
231            //Parse the time String.
232            StringTokenizer stk = new StringTokenizer(time, ":,. ");
233            try {
234                result += 3600 * Integer.parseInt(stk.nextToken());
235                result += 60 * Integer.parseInt(stk.nextToken());
236                result += Integer.parseInt(stk.nextToken());
237                result = result*1000;
238                result += Integer.parseInt(stk.nextToken());
239                //restore the sign.
240                if(!positive){
241                    result =  -result;
242                }
243            } catch (NoSuchElementException e) {
244               //If there is a time error, throw a number format exception.
245               throw new NumberFormatException("Bad Time Format found:"+time);
246            }
247            //return the result of the method.
248            return result;
249        }
250    
251        /**
252         * Method writeToFile.
253         * <br><b>Summary:</b><br>
254         * Use this method to write subtitle file to the given File.
255         * @param fileToWrite       The File to  write the file.
256         */
257        public abstract void writeToFile(File fileToWrite);
258    
259        /**
260         * Method writeToTemporaryFile.
261         * <br><b>Summary:</b><br>
262         * Use this method to write subtitle file to the temporary File.
263         */
264        public abstract void writeToTemporaryFile();
265    
266        /**
267         * Method addFakeSub.
268         * <br><b>Summary:</b><br>
269         * Use this method to create an empty subtitle file.
270          *                                                 
271        */
272        public void addFakeSub(){
273            subtitleList.add(new Subtitle(0,0,1,""));
274        }
275    
276        /**
277         * Method delay.
278         * <br><b>Summary:</b><br>
279         * Apply a delay on the given range subtitles.
280         * @param beginIndex    The first index to begin delay.
281         * @param endIndex      The last index to put a delay
282         * @param delay         The delay to Apply.
283         */
284        public void delay(int beginIndex, int endIndex, int delay) {
285            for (int index = beginIndex; index <= endIndex; index++) {
286                delaySubtitle(delay, index);
287            }
288        }
289    
290    
291    
292        /**
293         * Method delay.
294         * <br><b>Summary:</b><br>
295         * Delay a list of subtitles, idetified by their index.
296         * @param indexToDelay      The array of subtitle's index to be delayed.
297         * @param delay             The delay to apply.
298         */
299        public void delay(int[] indexToDelay, int delay) {
300            if (indexToDelay.length == 0){
301                delay(delay);
302            }else {
303                for (int i = 0; i < indexToDelay.length; i++) {
304                    delaySubtitle(delay, indexToDelay[i]);
305                }
306            }
307        }
308    
309        /**
310         * Method delay.
311         * <br><b>Summary:</b><br>
312         * Use this method to delay whole file.
313         * @param delay     The delay to apply.
314         */
315        public void delay(int delay) {
316            delay(0, subtitleList.size() - 1, delay);
317        }
318    
319            /**
320             * Method delaySubtitle.
321             * <br><b>Summary:</b><br>
322             * Delay a given subtitle, idetified by its index.
323             * @param delay         The delay to apply.
324             * @param index         The index of the subtitle to delay.
325             */
326            private void delaySubtitle(int delay, int index) {
327                    Subtitle subtitle = (Subtitle) subtitleList.get(index);
328                    subtitle.delay(delay);
329                    //indicate the file has changed.
330                    fileChanged = true;
331            }    
332        
333        /**
334         * Method normalizeDuration.
335         * <br><b>Summary:</b><br>
336         * This method permits to normalize the subtitle duration for whole file.
337         * It ensures that subtitle display duration is beetween given minDuration, and maxDuration.
338         * It raise or lower it to fit in the interval.
339         * It takes care that subtitle do not ends before the start of its follower.
340         * If minDuration equals to -1, it does not checks the minDuration.
341         * If maxDuration equals to -1, it does not checks the maxDuration.
342         * @param minDuration       The min duration to ensure.
343         * @param maxDuration       The max duration to ensure.
344         */
345        public void normalizeDuration(int minDuration, int maxDuration){
346            normalizeDuration(0, subtitleList.size() - 1, minDuration, maxDuration);
347        }
348        
349        /**
350         * Method normalizeDuration.
351         * <br><b>Summary:</b><br>
352         * This method permits to normalize the subtitle duration for given index interval.
353         * It ensures that subtitle display duration is beetween given minDuration, and maxDuration.
354         * It raise or lower it to fit in the interval.
355         * It takes care that subtitle do not ends before the start of its follower.
356         * If minDuration equals to -1, it does not checks the minDuration.
357         * If maxDuration equals to -1, it does not checks the maxDuration.
358         * @param beginIndex        The start index to begin the normalization.
359         * @param endIndex          The end index to finish the normalization.
360         * @param minDuration       The min duration to ensure.
361         * @param maxDuration       The max duration to ensure.
362         */
363        private void normalizeDuration(int beginIndex, int endIndex, int minDuration, int maxDuration) {
364            for (int index = beginIndex; index <= endIndex; index++) {
365                normalizeSubtitleDuration(minDuration, maxDuration, index);
366            }
367            }
368    
369            /**
370         * Method normalizeDuration.
371         * <br><b>Summary:</b><br>
372         * This method permits to normalize the subtitle duration for given subtitle indexes.
373         * It ensures that subtitle display duration is beetween given minDuration, and maxDuration.
374         * It raise or lower it to fit in the interval.
375         * It takes care that subtitle do not ends before the start of its follower.
376         * If minDuration equals to -1, it does not checks the minDuration.
377         * If maxDuration equals to -1, it does not checks the maxDuration.
378         * If given indexes array is empty, it normalizes the whole file.
379         * @param indexToNormalize  The array of subtitle index to be normalized.
380         * @param minDuration               The min duration to ensure.
381         * @param maxDuration               The max duration to ensure.
382         */
383        public void normalizeDuration(int[] indexToNormalize, int minDuration, int maxDuration) {
384            if (indexToNormalize.length == 0){
385                    normalizeDuration(minDuration, maxDuration);
386            }else {
387                for (int i = 0; i < indexToNormalize.length; i++) {
388                    normalizeSubtitleDuration(minDuration, maxDuration, indexToNormalize[i]);
389                }
390            }
391            }
392    
393        /**
394         * Method normalizeSubtitleDuration.
395         * <br><b>Summary:</b><br>
396         * This method permits to normalize the subtitle duration.
397         * It ensures that subtitle display duration is beetween given minDuration, and maxDuration.
398         * It raise or lower it to fit in the interval.
399         * It takes care that subtitle do not ends before the start of its follower.
400         * If minDuration equals to -1, it does not checks the minDuration.
401         * If maxDuration equals to -1, it does not checks the maxDuration.
402         * @param minDuration               The min duration to ensure.
403         * @param maxDuration               The max duration to ensure.
404         * @param index                             The index of the subtitle to be normalized.
405         */
406        private void normalizeSubtitleDuration(int minDuration, int maxDuration, int index) {
407                    //retrieve the Subtitle at the given index.
408            Subtitle subtitle = (Subtitle) subtitleList.get(index);
409                    //Compute its duration.
410            int endDate = subtitle.getEndDate();
411            int startDate = subtitle.getStartDate();
412            int duration = endDate - startDate;
413            int newEndDate = endDate;
414            //Then check the duration with given criterias.
415            //The minduration.
416            if(minDuration != -1 && duration < minDuration){
417                    //The duration is not enough long.
418                    //We must get the next subtitle start date, not to have the end date after
419                    //The next subtitle start date.
420                    newEndDate = startDate + minDuration;
421                    
422            }
423            //The max duration.
424            if(maxDuration != -1 && duration > maxDuration){
425                    //The duration is too long.
426                    //Normalize !
427                    newEndDate = startDate + maxDuration;
428            }
429            //Beware not to ovveride the next subtitle startDate.
430            //check that subtitle is not the last one.
431                    if(index < getSubtitles().size()-1){
432                            //retrieve next subtitle start date.
433                            int nextSubtitleStartDate = ((Subtitle) getSubtitles().get(index+1)).getStartDate();
434                            if(nextSubtitleStartDate <= newEndDate){
435                                    //If overriding problem may occurs, readjust newEndDate.
436                                    newEndDate = nextSubtitleStartDate - 1;
437                            }
438                    }
439                    //And beware not to be inferior to current start date
440                    //Yeah i know, its a lot of verification.
441                    if(newEndDate < startDate){
442                            //There is a glitch with next subtitle. do nothing.
443                            //The file need a time repair.
444                            newEndDate = endDate;
445                    }
446                    //Normalize !
447                    subtitle.setEndDate(newEndDate);
448            //indicate that the file has changed.
449            fileChanged = true;
450            }
451        
452            /**
453         * Method resynchro.
454         * <br><b>Summary:</b><br>
455         * Use this method to apply a resynchronisation.
456         * @param result    The resynchro parameter, an int array organized like this:
457         *                  [0]:The source 1
458         *                  [1]:The destination 1
459         *                  [2]:The source 2
460         *                  [3]:The destination 2
461         */
462        public void resynchro(int[] result) {
463            int source1 = result[0];
464            int dest1 = result[1];
465            int source2 = result[2];
466            int dest2 = result[3];
467            int delay1 = dest1 - source1;
468            int delay2 = dest2 - source2;
469            float scale = (float) (delay2 - delay1) / ((float) source2 - (float) source1);
470            Trace.trace("Computed scale : " + scale, Trace.ALGO_PRIORITY);
471            Iterator subtitles = subtitleList.iterator();
472            while (subtitles.hasNext()) {
473                Subtitle subtitle = (Subtitle) subtitles.next();
474                int localDelay = Math.round(((float) (subtitle.getStartDate() - source1) * scale) + (float) delay1);
475                subtitle.delay(localDelay);
476            }
477            //file has changed.
478            fileChanged = true;
479        }
480    
481        /**
482         * Method setFile.
483         * <br><b>Summary:</b><br>
484         * Set the file to the given file.
485         * @param file      The file to set.
486         */
487        public void setFile(File file) {
488            this.file = file;
489            // file has changed.
490            fileChanged = true;
491        }
492    
493        /**
494         * Method addSubtitle.
495         * <br><b>Summary:</b><br>
496         * Add a subtitle to subtitle list.
497         * It may update the given subtitle number if needed.
498         * @param subtitle   The <b>Subtitle</b> to add to the file.
499         */
500        public void addSubtitle(Subtitle subtitle) {
501            addSubtitle(subtitle, true);
502        }
503    
504        /**
505         * Method addSubtitle.
506         * <br><b>Summary:</b><br>
507         * Add a subtitle to subtitle list, recomputing its sequence number.
508         * @param subtitle          The <b>Subtitle</b> to add to the file.
509         * @param updateNumber     A <b>boolean</b>, true if want to update the number with its index. False not to update it.
510         */
511        public void addSubtitle(Subtitle subtitle, boolean updateNumber) {
512            subtitleList.add(subtitle);
513            if(updateNumber){
514                subtitle.setNumber(subtitleList.size());
515            }
516            // file has changed.
517            fileChanged = true;
518        }
519    
520        /**
521         * Method split.
522         * <br><b>Summary:</b><br>
523         * This method split the subtitle file in two part at the given subtitle index.
524         * The two part will be saved in the given destination files, and a delay will be applied to the second part.
525         * @param destinationFiles      The <b>File[]</b> where to save the two parts of the file.
526         * @param subtitleIndex         The <b>int</b> subtitle index, from wich create the second part of the subtitle.
527         * @param secondPartDelay       The <b>int</b> initial delay to apply to the second part.
528         */
529        public SubtitleFile[] split(File[] destinationFiles, int subtitleIndex, int secondPartDelay) {
530            //The result of the method
531            SubtitleFile[] result = new SubtitleFile[2];
532            //Construct first part SrtFile.
533            result[0] = getNewInstance();
534            //set its file.
535            result[0].setFile(destinationFiles[0]);
536            //and second part.
537            result[1] = getNewInstance();
538            //set its file.
539            result[1].setFile(destinationFiles[1]);
540            //Fill in the srtFiles.
541            int index = 0;
542            //by parsing current STs list, and add to one or other file.
543            for (Subtitle currentSubtitle : subtitleList) {
544                //If number is before limit, add to first part.
545                if (index < subtitleIndex) {
546                    result[0].addSubtitle(new Subtitle(currentSubtitle));
547                } else {
548                    //else, add to the second part.
549                    result[1].addSubtitle(new Subtitle(currentSubtitle), true);
550                }
551                index++;
552            }
553            
554            //Apply delay.
555            if(secondPartDelay >= 0){
556                //Delay second part, so first ST is at time 0
557                result[1].shiftToZero();
558                result[1].delay(secondPartDelay);
559            }
560            //return the result;
561            return result;
562        }
563    
564        /**
565         * Method shiftToZero.
566         * <br><b>Summary:</b><br>
567         * This method delays the subtitles, so the first one appears at time 0:00:00.000
568         */
569        protected void shiftToZero() {
570            //get the first subtile time.
571            int firstTime = ((Subtitle) subtitleList.get(0)).getStartDate();
572            //delay whole file from this delay.
573            delay(-firstTime);
574            //file has changed.
575            fileChanged = true;
576        }
577    
578        /**
579         * Method getNewInstance.
580         * <br><b>Summary:</b><br>
581         * This method should return a new instance of the current SubtitleFile class.
582         * @return <b>SubtitleFile</b>    A new instance of the current SubtitleFile class.  
583         */
584        protected abstract SubtitleFile getNewInstance();
585    
586        /**
587         * Method append.
588         * <br><b>Summary:</b><br>
589         * Use this method to append a Subtitle file, to the current one.
590         * Using the given delay before last ST of current one and first ST of the one to append.
591         * @param subtitleFileToAppend  The <b>SubtitleFile</b> subtitle file to append.   
592         * @param delay                 The <b>int</b> delay to use.
593         */
594        public void append(SubtitleFile subtitleFileToAppend, int delay) {
595            //First is to shift to zero the file to append.
596            subtitleFileToAppend.shiftToZero();
597            //Then Delay using the last ST time end date.
598            //SO first ST of the appended file, start after the last ST of this file.
599            int lastSubtitleEndDate = ((Subtitle) subtitleList.get(subtitleList.size()-1)).getEndDate();
600            subtitleFileToAppend.delay(lastSubtitleEndDate);
601            if(delay >0){
602                //Then delay it.
603                subtitleFileToAppend.delay(delay);
604            }
605            //Then parse all the subtitles, and add them to current subtitle file.
606            for(Subtitle currentSubtitle : subtitleFileToAppend.getSubtitles()){
607                //add it, updating its number.
608                addSubtitle(currentSubtitle, true);
609            }
610        }
611    
612        /**
613         * Method getSubtitles.
614         * <br><b>Summary:</b><br>
615         * return the subtitle list.
616         * @return  <b>ArrayList</b>        The subtitle list.
617         */
618        protected ArrayList<Subtitle> getSubtitles() {
619            return subtitleList;
620        }
621    
622        /**
623         * @return Returns the fileChanged.
624         */
625        public boolean isFileChanged() {
626            return fileChanged;
627        }
628        
629        
630        
631        /**
632         * Method <b>fileChanged</b>
633         * <br><b>Summary:</b><br>
634         * Set the fileChanged status flag to true.
635         */
636        public void fileChanged(){
637            fileChanged = true;
638        }
639    
640        /**
641         * Method accentRepair.
642         * <br><b>Summary:</b><br>
643         * This method is called when user want to remove 
644         * the accents and special characters from the given index.
645         * If no index is precised, it will remove accents from all the Subtitles.
646         * @param selectedIndex     The index to remove the accents.
647         */
648        public void accentRepair(int[] selectedIndex) {
649            Trace.trace("Accent repair.", Trace.ALGO_PRIORITY);
650            if(selectedIndex == null || selectedIndex.length == 0){
651                //There is no index, so we will proceed on whole file.
652                for (Subtitle currentSubtitle : subtitleList) {
653                    currentSubtitle.accentRemove();
654                }
655            }else{
656                for (int i = 0; i < selectedIndex.length; i++) {
657                    Subtitle currentSubtitle = (Subtitle) subtitleList.get(selectedIndex[i]);
658                    currentSubtitle.accentRemove();
659                }
660            }
661            //file has changed.
662            fileChanged = true;
663        }
664    
665        /**
666         * Method htmlRepair.
667         * <br><b>Summary:</b><br>
668         * This method is called when user want to remove 
669         * the htmls.
670         * If no index is precised, it will remove html tags from all the Subtitles.
671         * @param selectedIndex     The index to remove the accents.
672         */
673        public void htmlRepair(int[] selectedIndex) {
674            Trace.trace("HTML repair.", Trace.ALGO_PRIORITY);
675            if(selectedIndex == null || selectedIndex.length == 0){
676                //There is no index, so we will proceed on whole file.
677                for (Subtitle currentSubtitle : subtitleList) {
678                    currentSubtitle.htmlRemove();
679                }
680            }else{
681                for (int i = 0; i < selectedIndex.length; i++) {
682                    Subtitle currentSubtitle = (Subtitle) subtitleList.get(selectedIndex[i]);
683                    currentSubtitle.htmlRemove();
684                }
685            }
686            //file has changed.
687            fileChanged = true;
688        }
689    
690        /**
691         * Method timeRepair.
692         * <br><b>Summary:</b><br>
693         * This method is called when user want to time repair.
694         * It will correct the time superposition problem.
695         * When subtitle ends after next ST start time.
696         */
697        public void timeRepair() {
698            Trace.trace("Time repair.", Trace.ALGO_PRIORITY);
699            //The time that will be used to keep ends time
700            //It is initialized to 0, so frist ST will not start before 0.
701            int time =0;
702            //Just have to parse all ST.
703            for (Subtitle currentSubtitle : subtitleList) {
704                //if it starts before time, fix start time
705                if(currentSubtitle.getStartDate() < time){
706                    //Add 1 to avoid player issues.
707                    currentSubtitle.setStartDate(time+1);
708                }
709                //check that start date is not after end date.
710                if(currentSubtitle.getEndDate() < currentSubtitle.getStartDate()){
711                    //If that happens, re shift end date, just after start date.
712                    //St will not be visible, but player will not crash.
713                    currentSubtitle.setEndDate(currentSubtitle.getStartDate()+1);
714                }
715                //Keep end date in time var.
716                time = currentSubtitle.getEndDate();
717            }
718                    //indicate the file has changed.
719                    fileChanged = true;
720        }
721    
722        /**
723         * Method orderRepair.
724         * <br><b>Summary:</b><br>
725         * This method is called when user want to repair the order of the subtitle file.
726         * It will check chronology, order ST's with their start time, and finally fix ST's numbers.
727         */
728        public void orderRepair() {
729            Trace.trace("Order repair.", Trace.ALGO_PRIORITY);
730            //Just sort the list.
731            //subtitles are comparable elements, ordered with their start date.
732            Collections.sort(subtitleList);
733            //then fix ST numbers.
734            //Just have to parse all ST.
735            for (int i = 0; i < subtitleList.size(); i++) {
736                //get current subtitle.
737                Subtitle currentSubtitle = (Subtitle) subtitleList.get(i);
738                //And fix its number, add 1 because ST number starts at 1.
739                currentSubtitle.setNumber(i+1);
740            }
741                    //indicate the file has changed.
742                    fileChanged = true;
743        }
744    
745        /**
746         * Method getSubtitleIndex.
747         * <br><b>Summary:</b><br>
748         * This method is used to know the subtitle that should be active at the given date.
749         * @param date              The date (in milliseconds).
750         * @return <b>Subtitle</b>  The subtitle.
751         */
752        public Subtitle getSubtitleAtDate(int date) {
753            //The result of the method.
754            Subtitle result = null;
755            for (int i = 0; i < subtitleList.size(); i++) {
756                //get subtitle.
757                Subtitle subtitle = (Subtitle) subtitleList.get(i);
758                if ((result == null) 
759                            || ((subtitle.getStartDate() > (result.getStartDate())) && (subtitle.getStartDate() <= date))) {
760                    result = subtitle;
761                }
762            }
763            //return the result.
764            return result;
765        }
766    
767            /**
768             * Method magicResynchro.
769             * <br><b>Summary:</b><br>
770             * This method permits to perform a magic resynchro, using the defined anchors.
771             */
772            public void magicResynchro() {
773                    //First is to get the defined anchored delays.
774                    ArrayList<Double> xList = new ArrayList<Double>(); 
775                    ArrayList<Double> yList = new ArrayList<Double>(); 
776                    for(Subtitle subtitle : subtitleList){
777                            if(subtitle.isAnchored()){
778                                    xList.add((double) subtitle.getStartDate());
779                                    yList.add((double)(subtitle.getAnchor() - subtitle.getStartDate()));
780                            }
781                    }
782                    //Then create the linear interpolation.
783                    double[] x = new double[xList.size()];
784                    double[] y = new double[yList.size()];
785                    for (int i = 0; i < x.length; i++) {
786                            x[i] = xList.get(i).doubleValue();
787                            y[i] = yList.get(i).doubleValue();
788                    }
789                    getLinearInterpolation(x, y);
790                    //Then parse all the subtitles, and apply the delay computed by the linear interpolation.
791                    for(Subtitle subtitle : subtitleList){
792                            subtitle.delay((int) linearInterpolation.interpolate(subtitle.getStartDate()));
793                    }
794                    //magic Interpolation done !
795                    //indicate the file has changed.
796                    fileChanged = true;
797            }
798    
799            private void getLinearInterpolation(double[] x, double[] y) {
800                    if(linearInterpolation==null){
801                            linearInterpolation = new LinearInterpolation(x, y);
802                    }
803            }
804    }