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    
018    
019    package sears.file;
020    
021    import java.io.File;
022    import java.lang.reflect.Constructor;
023    import java.lang.reflect.InvocationTargetException;
024    import java.util.ArrayList;
025    import java.util.Collections;
026    import java.util.Iterator;
027    import java.util.NoSuchElementException;
028    import java.util.StringTokenizer;
029    
030    import sears.file.exception.io.FileConversionException;
031    import sears.tools.LinearInterpolation;
032    import sears.tools.SearsProperties;
033    import sears.tools.Trace;
034    import sears.tools.Utils;
035    
036    /**
037     * Class SubtitleFile.
038     * <br><b>Summary:</b><br>
039     * This class represents a subtitle file.
040     * It provides facilities on subtitles, as delay, resynchro.
041     * It must be specialized to the subtitle file type you want to open.
042     * <br>You would have to use the {@link #getInstance(File, ArrayList, String)} static method for this
043     */
044    public abstract class SubtitleFile {
045            /**The system File that contains the file*/
046            protected File file;
047    
048            /**The ArrayList of subtitles found*/
049            protected ArrayList<Subtitle> subtitleList;
050    
051            /**A boolean to know if file has changed*/
052            protected boolean fileChanged;
053    
054            /**The temporary file */
055            protected File temporaryFile = null;
056    
057            /** (<b>LinearInterpolation</b>) LinearInterpolation: The LinearInterpolation used for magic resynchro. */
058            private LinearInterpolation linearInterpolation;
059    
060            /** The default charset that Sears use to parse a subtitle file: "ISO-8859-1" */
061            public static final String DEFAULT_CHARSET = "ISO-8859-1";
062            
063            // makes an array too big may cause damage on the performance: 
064            // #parse() method use this constants in a loop !
065            /** Represents an array of charsets that Sears can use to parse a file if an error occurs with the default one*/
066            public static final String[] BASIC_CHARSETS = {
067                    "UTF-16",
068                    "UTF-8"
069            };
070    
071            private String charset;
072            
073            /**
074             * Constructor SubtitleFile.
075             * <br><b>Summary:</b><br>
076             * Constructor of the class.
077             * Beware not to use this file directly, because it does contains no ST.
078             * You will have to fill the list of ST, and save the File first.
079             */
080            public SubtitleFile() {
081                    //set file to null
082                    file = null;
083                    //Construct an empty list.
084                    subtitleList = new ArrayList<Subtitle>();
085                    //File is changed
086                    fileChanged = true;
087                    charset = DEFAULT_CHARSET;
088            }
089    
090            /**
091             * Constructor SubtitleFile.
092             * <br><b>Summary:</b><br>
093             * Constructor of the class.
094             * @param fileToOpen                    The <b>(String)</b> path to file to open.
095             * @param _subtitlesList                The <b>(ArrayList)</b> List of subtitles.
096             * @throws FileConversionException      if a limitation when reading the file appears
097             */
098            public SubtitleFile(String fileToOpen, ArrayList<Subtitle> _subtitlesList) throws FileConversionException {
099                    construct(new File(fileToOpen), _subtitlesList, null);
100            }
101    
102            /**
103             * Constructor SubtitleFile.
104             * <br><b>Summary:</b><br>
105             * Constructor of the class.    
106             * @param _file                         The <b>(File)</b> file to open.
107             * @param _subtitlesList                The <b>(ArrayList)</b> List of subtitles.     
108             * @throws FileConversionException      if a limitation when reading the file appears
109             */
110            public SubtitleFile(File _file, ArrayList<Subtitle> _subtitlesList) throws FileConversionException {
111                    construct(_file, _subtitlesList, null);
112            }
113    
114            /**
115             * Constructor SubtitleFile.
116             * <br><b>Summary:</b><br>
117             * Constructor of the class.    
118             * @param _file                         The <b>(File)</b> file to open.
119             * @param _subtitlesList                The <b>(ArrayList)</b> List of subtitles.
120             * @param charset                                       The charset to use during operations on the file ...  
121             * @throws FileConversionException      if a limitation when reading the file appears
122             */
123            public SubtitleFile(File _file, ArrayList<Subtitle> _subtitlesList, String charset) throws FileConversionException {
124                    construct(_file, _subtitlesList, charset);
125            }
126    
127            /**
128             * Method construct.
129             * <br><b>Summary:</b><br>
130             * Construct the file.
131             * @param _srtFile                      The <b>(File)</b> file to open.
132             * @param _subtitlesList                The <b>(ArrayList<Subtitle>)</b> List of subtitles.
133             * @param charset                                       the charset to use during operations on file, if null <tt>DEFAULT_CHARSET</tt> is use
134             *                                                                      <br>This parameter is a String, so if it is not denoted a charset, an exception is throws
135             * @throws NullPointerException         if <tt>_srtFile<tt> is null   
136             * @throws FileConversionException      if a limitation when reading the file appears
137             */
138            private void construct(File _srtFile, ArrayList<Subtitle> _subtitlesList, String charset) throws FileConversionException {
139                    if(_srtFile == null ) {
140                            throw new NullPointerException("Cannot constructs from a null file");
141                    }               
142                    if (!_srtFile.exists()) {
143                            Trace.trace(("File " + _srtFile.getAbsolutePath() + " Does not exist !"), Trace.WARNING_PRIORITY);
144                    }
145                    if (_subtitlesList == null) {
146                            Trace.trace("The given subtitleList is null !", Trace.WARNING_PRIORITY);
147                    }
148                    file = _srtFile;
149                    subtitleList = _subtitlesList;
150                    this.charset = getANonNullCharset(charset);
151                    parse();
152                    //File is not changed
153                    fileChanged = false;
154            }
155    
156            /**
157             * Returns <tt>charset</tt> if it is non null, else <tt>DEFAULT_CHARSET<tt> is returned
158             * @param charset       the charset to test
159             * @return                      <tt>DEFAULT_CHARSET<tt> or <tt>charset</tt> if it is non null
160             */
161            protected static String getANonNullCharset(String charset) {
162                    if( charset == null ) {
163                            charset = DEFAULT_CHARSET;
164                    }
165                    return charset;
166            }
167    
168            /**
169             * Returns the charset used for write and read operations
170             * @return the charset used by <tt>this</tt>
171             */
172            public String getCharset() {
173                    return charset;
174            }
175    
176            /**
177             * Changes the charset, if a non null value is given, <tt>DEFAULT_CHARSET</tt> is set as 
178             * the used charset
179             * @param charset       the new charset to use
180             */
181            public void setCharset(String charset) {
182                    this.charset = getANonNullCharset(charset);
183            }
184            
185            /**
186             * Returns the line separator (end of line) to use (depends to the user choice).
187             * <br>By default the system one is return.
188             *  
189             * @return      a string that denoted a line separator
190             */
191            public static String getLineSeparator() {
192                    String lineSeparator = Utils.LINE_SEPARATOR;
193                    if(!SearsProperties.getProperty(SearsProperties.DOS_LINE_SEPARATOR, "0").equals("0")){
194                            //If need to use a DOS end of line, use it.
195                            lineSeparator = Utils.DOS_LINE_SEPARATOR;
196                    }
197                    return lineSeparator;
198            }
199    
200            /**
201             * Returns the extension file
202             * @return the extension file
203             */
204            public abstract String extension();
205    
206            /**
207             * Method parse.
208             * <br><b>Summary:</b><br>
209             * This method parse the current file, and construct the subtitleList.
210             * @throws FileConversionException      if a limitation when reading the file appears
211             */
212            protected abstract void parse() throws FileConversionException;
213    
214            /**
215             * Method getContentDirectory.
216             * <br><b>Summary:</b><br>
217             * This method return the file's parent folder.
218             * @return  <b>(File)</b>   The parent of the current file.
219             */
220            public File getContentDirectory() {
221                    return this.getFile().getParentFile();
222            }
223    
224            /**
225             * Method getFile.
226             * <br><b>Summary:</b><br>
227             * Return the current file.
228             * @return  <b>(File)</b>   The current file.
229             */
230            public File getFile() {
231                    return file;
232            }
233    
234            /**
235             * Method getTemporaryFile.
236             * <br><b>Summary:</b><br>
237             * Return the temporary file.
238             * @return  <b>(File)</b>   The temporary file.
239             */
240            public File getTemporaryFile() {
241                    return temporaryFile;
242            }
243    
244            /**
245             * Method timeToString.
246             * <br><b>Summary:</b><br>
247             * This method transform a number of milliseconds in a string representation.
248             * @param milliseconds               The number of milliseconds to transform
249             * @return  <b>(String)</b>     The corresponding String representation of the number of milliseconds.
250             */
251            public static String timeToString(int milliseconds){
252                    //keep the sign in memory.
253                    boolean positive = milliseconds >= 0;
254                    //And set time aboslute, not to be annoyed with signs.
255                    milliseconds = Math.abs(milliseconds);
256                    //Compute seconds/minutes/hours from milliseconds.
257                    int seconds = milliseconds / 1000;
258                    milliseconds = milliseconds - seconds * 1000;
259                    int hours = (seconds - seconds % 3600) / 3600;
260                    seconds -= hours * 3600;
261                    int minutes = (seconds - seconds % 60) / 60;
262                    seconds -= minutes * 60;
263                    //Compute String representation of these values.
264                    //Using 2 digits formatting
265                    String hoursString = "";
266                    hours = Math.abs(hours);
267                    if (hours < 10){
268                            hoursString += "0" + hours;
269                    }
270                    else{
271                            hoursString += hours;
272                    }
273                    String minutesString = "";
274                    if (minutes < 10){
275                            minutesString += "0" + minutes;
276                    }else{
277                            minutesString += minutes;
278                    }
279                    String secondsString = "";
280                    if (seconds < 10){
281                            secondsString += "0" + seconds;
282                    }
283                    else{
284                            secondsString += seconds;
285                    }
286                    String millisecondsString ="";
287                    if (milliseconds < 10){
288                            millisecondsString += "00" + milliseconds;
289                    }else if (milliseconds < 100){
290                            millisecondsString += "0" + milliseconds;
291                    }else{
292                            millisecondsString += "" +milliseconds;
293                    }
294                    String result = "";
295                    //Remember to set the String negative, if it was negative at the begining.
296                    if(!positive){
297                            result = "-";
298                    }
299                    result = result + hoursString + ":" + minutesString + ":" + secondsString + "," +millisecondsString;
300                    return result;
301    
302            }
303    
304            /**
305             * Method stringToTime.
306             * <br><b>Summary:</b><br>
307             * Return the number of miliseconds that correspond to the given String time representation.
308             * @param time              The string srt time representation.
309             * @return <b>(int)</b>     The corresponding number of miliseconds.
310             * @throws NumberFormatException if there's a time error
311             */
312            public static int stringToTime(String time) throws NumberFormatException{
313                    //the result of the method.
314                    int result = 0;
315                    //Check the sign.
316                    boolean positive = true;
317                    //If time start with a '-', it means it is a negative time.
318                    if(time != null && time.trim().startsWith("-")){
319                            positive =false;
320                            //remove the '-' char.
321                            int indexOfMinus = time.indexOf("-");
322                            if(indexOfMinus != -1){
323                                    time = time.substring(indexOfMinus+1);
324                            }
325                    }
326                    //Parse the time String.
327                    StringTokenizer stk = new StringTokenizer(time, ":,. ");
328                    try {
329                            result += 3600 * Integer.parseInt(stk.nextToken());
330                            result += 60 * Integer.parseInt(stk.nextToken());
331                            result += Integer.parseInt(stk.nextToken());
332                            result = result*1000;
333                            result += Integer.parseInt(stk.nextToken());
334                            //restore the sign.
335                            if(!positive){
336                                    result =  -result;
337                            }
338                    } catch (NoSuchElementException e) {
339                            //If there is a time error, throw a number format exception.
340                            throw new NumberFormatException("Bad Time Format found: "+time);
341                    }
342                    //return the result of the method.
343                    return result;
344            }
345    
346            /**
347             * Method writeToFile.
348             * <br><b>Summary:</b><br>
349             * Use this method to write subtitle file to the given File.
350             * @param fileToWrite       The File to  write the file.
351             * @throws FileConversionException 
352             */
353            public abstract void writeToFile(File fileToWrite) throws FileConversionException;
354    
355            /**
356             * Method writeToTemporaryFile.
357             * <br><b>Summary:</b><br>
358             * Use this method to write subtitle file to the temporary File.
359             */
360            public abstract void writeToTemporaryFile();
361    
362            /**
363             * Method addFakeSub.
364             * <br><b>Summary:</b><br>
365             * Use this method to create an empty subtitle file.
366             *                                              
367             */
368            public void addFakeSub(){
369                    subtitleList.add(new Subtitle(0,0,1,""));
370            }
371    
372            /**
373             * Method delay.
374             * <br><b>Summary:</b><br>
375             * Apply a delay on the given range subtitles.
376             * @param beginIndex    The first index to begin delay.
377             * @param endIndex      The last index to put a delay
378             * @param delay         The delay to Apply.
379             */
380            public void delay(int beginIndex, int endIndex, int delay) {
381                    for (int index = beginIndex; index <= endIndex; index++) {
382                            delaySubtitle(delay, index);
383                    }
384            }
385    
386            /**
387             * Method delay.
388             * <br><b>Summary:</b><br>
389             * Delay a list of subtitles, idetified by their index.
390             * @param indexToDelay      The array of subtitle's index to be delayed.
391             * @param delay             The delay to apply.
392             */
393            public void delay(int[] indexToDelay, int delay) {
394                    if (indexToDelay.length == 0){
395                            delay(delay);
396                    }else {
397                            for (int i = 0; i < indexToDelay.length; i++) {
398                                    delaySubtitle(delay, indexToDelay[i]);
399                            }
400                    }
401            }
402    
403            /**
404             * Method delay.
405             * <br><b>Summary:</b><br>
406             * Use this method to delay whole file.
407             * @param delay     The delay to apply.
408             */
409            public void delay(int delay) {
410                    delay(0, subtitleList.size() - 1, delay);
411            }
412    
413            /**
414             * Method delaySubtitle.
415             * <br><b>Summary:</b><br>
416             * Delay a given subtitle, identified by its index.
417             * @param delay         The delay to apply.
418             * @param index         The index of the subtitle to delay.
419             */
420            private void delaySubtitle(int delay, int index) {
421                    Subtitle subtitle = (Subtitle) subtitleList.get(index);
422                    subtitle.delay(delay);
423                    //indicate the file has changed.
424                    fileChanged = true;
425            }    
426    
427            /**
428             * Method normalizeDuration.
429             * <br><b>Summary:</b><br>
430             * This method permits to normalize the subtitle duration for whole file.
431             * It ensures that subtitle display duration is between given minDuration, and maxDuration.
432             * It raise or lower it to fit in the interval.
433             * It takes care that subtitle do not ends before the start of its follower.
434             * If minDuration equals to -1, it does not checks the minDuration.
435             * If maxDuration equals to -1, it does not checks the maxDuration.
436             * @param minDuration   The min duration to ensure.
437             * @param maxDuration   The max duration to ensure.
438             */
439            public void normalizeDuration(int minDuration, int maxDuration){
440                    normalizeDuration(0, subtitleList.size() - 1, minDuration, maxDuration);
441            }
442    
443            /**
444             * Method normalizeDuration.
445             * <br><b>Summary:</b><br>
446             * This method permits to normalize the subtitle duration for given index interval.
447             * It ensures that subtitle display duration is between given minDuration, and maxDuration.
448             * It raise or lower it to fit in the interval.
449             * It takes care that subtitle do not ends before the start of its follower.
450             * If minDuration equals to -1, it does not checks the minDuration.
451             * If maxDuration equals to -1, it does not checks the maxDuration.
452             * @param beginIndex    The start index to begin the normalization.
453             * @param endIndex              The end index to finish the normalization.
454             * @param minDuration   The min duration to ensure.
455             * @param maxDuration   The max duration to ensure.
456             */
457            private void normalizeDuration(int beginIndex, int endIndex, int minDuration, int maxDuration) {
458                    for (int index = beginIndex; index <= endIndex; index++) {
459                            normalizeSubtitleDuration(minDuration, maxDuration, index);
460                    }
461            }
462    
463            /**
464             * Method normalizeDuration.
465             * <br><b>Summary:</b><br>
466             * This method permits to normalize the subtitle duration for given subtitle indexes.
467             * It ensures that subtitle display duration is between given minDuration, and maxDuration.
468             * It raise or lower it to fit in the interval.
469             * It takes care that subtitle do not ends before the start of its follower.
470             * If minDuration equals to -1, it does not checks the minDuration.
471             * If maxDuration equals to -1, it does not checks the maxDuration.
472             * If given indexes array is empty, it normalizes the whole file.
473             * @param indexToNormalize      The array of subtitle index to be normalized.
474             * @param minDuration           The min duration to ensure.
475             * @param maxDuration           The max duration to ensure.
476             */
477            public void normalizeDuration(int[] indexToNormalize, int minDuration, int maxDuration) {
478                    if (indexToNormalize.length == 0){
479                            normalizeDuration(minDuration, maxDuration);
480                    }else {
481                            for (int i = 0; i < indexToNormalize.length; i++) {
482                                    normalizeSubtitleDuration(minDuration, maxDuration, indexToNormalize[i]);
483                            }
484                    }
485            }
486    
487            /**
488             * Method normalizeSubtitleDuration.
489             * <br><b>Summary:</b><br>
490             * This method permits to normalize the subtitle duration.
491             * It ensures that subtitle display duration is between given minDuration, and maxDuration.
492             * It raise or lower it to fit in the interval.
493             * It takes care that subtitle do not ends before the start of its follower.
494             * If minDuration equals to -1, it does not checks the minDuration.
495             * If maxDuration equals to -1, it does not checks the maxDuration.
496             * @param minDuration           The min duration to ensure.
497             * @param maxDuration           The max duration to ensure.
498             * @param index                         The index of the subtitle to be normalized.
499             */
500            private void normalizeSubtitleDuration(int minDuration, int maxDuration, int index) {
501                    //retrieve the Subtitle at the given index.
502                    Subtitle subtitle = (Subtitle) subtitleList.get(index);
503                    //Compute its duration.
504                    int endDate = subtitle.getEndDate();
505                    int startDate = subtitle.getStartDate();
506                    int duration = endDate - startDate;
507                    int newEndDate = endDate;
508                    //Then check the duration with given criterias.
509                    //The minduration.
510                    if(minDuration != -1 && duration < minDuration){
511                            //The duration is not enough long.
512                            //We must get the next subtitle start date, not to have the end date after
513                            //The next subtitle start date.
514                            newEndDate = startDate + minDuration;
515    
516                    }
517                    //The max duration.
518                    if(maxDuration != -1 && duration > maxDuration){
519                            //The duration is too long.
520                            //Normalize !
521                            newEndDate = startDate + maxDuration;
522                    }
523                    //Beware not to override the next subtitle startDate.
524                    //check that subtitle is not the last one.
525                    if(index < getSubtitles().size()-1){
526                            //retrieve next subtitle start date.
527                            int nextSubtitleStartDate = ((Subtitle) getSubtitles().get(index+1)).getStartDate();
528                            if(nextSubtitleStartDate <= newEndDate){
529                                    //If overriding problem may occurs, readjust newEndDate.
530                                    newEndDate = nextSubtitleStartDate - 1;
531                            }
532                    }
533                    //And beware not to be inferior to current start date
534                    //Yeah i know, its a lot of verification.
535                    if(newEndDate < startDate){
536                            //There is a glitch with next subtitle. do nothing.
537                            //The file need a time repair.
538                            newEndDate = endDate;
539                    }
540                    //Normalize !
541                    subtitle.setEndDate(newEndDate);
542                    //indicate that the file has changed.
543                    fileChanged = true;
544            }
545    
546            /**
547             * Method resynchro.
548             * <br><b>Summary:</b><br>
549             * Use this method to apply a resynchronisation.
550             * @param result    The resynchro parameter, an int array organized like this:
551             *                  [0]:The source 1
552             *                  [1]:The destination 1
553             *                  [2]:The source 2
554             *                  [3]:The destination 2
555             */
556            public void resynchro(int[] result) {
557                    int source1 = result[0];
558                    int dest1 = result[1];
559                    int source2 = result[2];
560                    int dest2 = result[3];
561                    int delay1 = dest1 - source1;
562                    int delay2 = dest2 - source2;
563                    float scale = (float) (delay2 - delay1) / ((float) source2 - (float) source1);
564                    Trace.trace("Computed scale : " + scale, Trace.ALGO_PRIORITY);
565                    Iterator<Subtitle> subtitles = subtitleList.iterator();
566                    while (subtitles.hasNext()) {
567                            Subtitle subtitle = subtitles.next();
568                            int localDelay = Math.round(((float) (subtitle.getStartDate() - source1) * scale) + (float) delay1);
569                            subtitle.delay(localDelay);
570                    }
571                    //file has changed.
572                    fileChanged = true;
573            }
574    
575            /**
576             * Method setFile.
577             * <br><b>Summary:</b><br>
578             * Set the file to the given file.
579             * @param file      The file to set.
580             */
581            public void setFile(File file) {
582                    this.file = file;
583                    // file has changed.
584                    fileChanged = true;
585            }
586    
587            /**
588             * Method addSubtitle.
589             * <br><b>Summary:</b><br>
590             * Add a subtitle to subtitle list.
591             * It may update the given subtitle number if needed.
592             * @param subtitle   The <b>Subtitle</b> to add to the file.
593             */
594            public void addSubtitle(Subtitle subtitle) {
595                    addSubtitle(subtitle, true);
596            }
597    
598            /**
599             * Method addSubtitle.
600             * <br><b>Summary:</b><br>
601             * Add a subtitle to subtitle list, recomputing its sequence number.
602             * @param subtitle          The <b>Subtitle</b> to add to the file.
603             * @param updateNumber     A <b>boolean</b>, true if want to update the number with its index. False not to update it.
604             */
605            public void addSubtitle(Subtitle subtitle, boolean updateNumber) {
606                    subtitleList.add(subtitle);
607                    if(updateNumber){
608                            subtitle.setNumber(subtitleList.size());
609                    }
610                    // file has changed.
611                    fileChanged = true;
612            }
613    
614            /**
615             * Method split.
616             * <br><b>Summary:</b><br>
617             * This method split the subtitle file in two part at the given subtitle index.
618             * The two part will be saved in the given destination files, and a delay will be applied to the second part.
619             * @param destinationFiles      The <b>File[]</b> where to save the two parts of the file.
620             * @param subtitleIndex         The <b>int</b> subtitle index, from wich create the second part of the subtitle.
621             * @param secondPartDelay       The <b>int</b> initial delay to apply to the second part.
622             * @return                                              an array of two <tt>SubtitleFile</tt> object
623             */
624            public SubtitleFile[] split(File[] destinationFiles, int subtitleIndex, int secondPartDelay) {
625                    //The result of the method
626                    SubtitleFile[] result = new SubtitleFile[2];
627                    //Construct first part SrtFile.
628                    result[0] = getNewInstance();
629                    //set its file.
630                    result[0].setFile(destinationFiles[0]);
631                    //and second part.
632                    result[1] = getNewInstance();
633                    //set its file.
634                    result[1].setFile(destinationFiles[1]);
635                    //Fill in the srtFiles.
636                    int index = 0;
637                    //by parsing current STs list, and add to one or other file.
638                    for (Subtitle currentSubtitle : subtitleList) {
639                            //If number is before limit, add to first part.
640                            if (index < subtitleIndex) {
641                                    result[0].addSubtitle(new Subtitle(currentSubtitle));
642                            } else {
643                                    //else, add to the second part.
644                                    result[1].addSubtitle(new Subtitle(currentSubtitle), true);
645                            }
646                            index++;
647                    }
648    
649                    //Apply delay.
650                    if(secondPartDelay >= 0){
651                            //Delay second part, so first ST is at time 0
652                            result[1].shiftToZero();
653                            result[1].delay(secondPartDelay);
654                    }
655                    //return the result;
656                    return result;
657            }
658    
659            /**
660             * Method shiftToZero.
661             * <br><b>Summary:</b><br>
662             * This method delays the subtitles, so the first one appears at time 0:00:00.000
663             */
664            protected void shiftToZero() {
665                    //get the first subtile time.
666                    int firstTime = ((Subtitle) subtitleList.get(0)).getStartDate();
667                    //delay whole file from this delay.
668                    delay(-firstTime);
669                    //file has changed.
670                    fileChanged = true;
671            }
672    
673            /**
674             * Method getNewInstance.
675             * <br><b>Summary:</b><br>
676             * This method should return a new instance of the current SubtitleFile class.
677             * @return <b>SubtitleFile</b>    A new instance of the current SubtitleFile class.  
678             */
679            protected abstract SubtitleFile getNewInstance();
680    
681            /**
682             * Method append.
683             * <br><b>Summary:</b><br>
684             * Use this method to append a Subtitle file, to the current one.
685             * Using the given delay before last ST of current one and first ST of the one to append.
686             * @param subtitleFileToAppend  The <b>SubtitleFile</b> subtitle file to append.   
687             * @param delay                 The <b>int</b> delay to use.
688             */
689            public void append(SubtitleFile subtitleFileToAppend, int delay) {
690                    // First is to shift to zero the file to append.
691                    // if the opened subtitle does not contain subtitles:
692                    if(subtitleFileToAppend.subtitleList.size() != 0) {
693                            subtitleFileToAppend.shiftToZero();
694                    }
695    
696                    //Then Delay using the last ST time end date.
697                    //SO first ST of the appended file, start after the last ST of this file.
698                    int lastSubtitleEndDate = 0;
699                    // if the opened subtitle does not contain subtitles:
700                    if(subtitleList.size() != 0) {  
701                            lastSubtitleEndDate = ((Subtitle) subtitleList.get(subtitleList.size()-1)).getEndDate();
702                    }
703    
704                    subtitleFileToAppend.delay(lastSubtitleEndDate);
705                    if(delay >0){
706                            //Then delay it.
707                            subtitleFileToAppend.delay(delay);
708                    }
709                    //Then parse all the subtitles, and add them to current subtitle file.
710                    for(Subtitle currentSubtitle : subtitleFileToAppend.getSubtitles()){
711                            //add it, updating its number.
712                            addSubtitle(currentSubtitle, true);
713                    }
714            }
715    
716            /**
717             * Method getSubtitles.
718             * <br><b>Summary:</b><br>
719             * return the subtitle list.
720             * @return  <b>ArrayList</b>        The subtitle list.
721             */
722            protected ArrayList<Subtitle> getSubtitles() {
723                    return subtitleList;
724            }
725    
726            /**
727             * @return Returns the fileChanged.
728             */
729            public boolean isFileChanged() {
730                    return fileChanged;
731            }
732    
733    
734    
735            /**
736             * Method <b>fileChanged</b>
737             * <br><b>Summary:</b><br>
738             * Set the fileChanged status flag to true.
739             */
740            public void fileChanged(){
741                    fileChanged = true;
742            }
743    
744            /**
745             * Method accentRepair.
746             * <br><b>Summary:</b><br>
747             * This method is called when user want to remove 
748             * the accents and special characters from the given index.
749             * If no index is precised, it will remove accents from all the Subtitles.
750             * @param selectedIndex     The index to remove the accents.
751             */
752            public void accentRepair(int[] selectedIndex) {
753                    Trace.trace("Accent repair.", Trace.ALGO_PRIORITY);
754                    if(selectedIndex == null || selectedIndex.length == 0){
755                            //There is no index, so we will proceed on whole file.
756                            for (Subtitle currentSubtitle : subtitleList) {
757                                    currentSubtitle.accentRemove();
758                            }
759                    }else{
760                            for (int i = 0; i < selectedIndex.length; i++) {
761                                    Subtitle currentSubtitle = (Subtitle) subtitleList.get(selectedIndex[i]);
762                                    currentSubtitle.accentRemove();
763                            }
764                    }
765                    //file has changed.
766                    fileChanged = true;
767            }
768    
769            /**
770             * Method htmlRepair.
771             * <br><b>Summary:</b><br>
772             * This method is called when user want to remove 
773             * the htmls.
774             * If no index is precised, it will remove html tags from all the Subtitles.
775             * @param selectedIndex     The index to remove the accents.
776             */
777            public void htmlRepair(int[] selectedIndex) {
778                    Trace.trace("HTML repair.", Trace.ALGO_PRIORITY);
779                    if(selectedIndex == null || selectedIndex.length == 0){
780                            //There is no index, so we will proceed on whole file.
781                            for (Subtitle currentSubtitle : subtitleList) {
782                                    currentSubtitle.htmlRemove();
783                            }
784                    }else{
785                            for (int i = 0; i < selectedIndex.length; i++) {
786                                    Subtitle currentSubtitle = (Subtitle) subtitleList.get(selectedIndex[i]);
787                                    currentSubtitle.htmlRemove();
788                            }
789                    }
790                    //file has changed.
791                    fileChanged = true;
792            }
793    
794            /**
795             * Method timeRepair.
796             * <br><b>Summary:</b><br>
797             * This method is called when user want to time repair.
798             * It will correct the time superposition problem.
799             * When subtitle ends after next ST start time.
800             */
801            public void timeRepair() {
802                    Trace.trace("Time repair.", Trace.ALGO_PRIORITY);
803                    //The time that will be used to keep ends time
804                    //It is initialized to 0, so frist ST will not start before 0.
805                    int time =0;
806                    //Just have to parse all ST.
807                    for (Subtitle currentSubtitle : subtitleList) {
808                            //if it starts before time, fix start time
809                            if(currentSubtitle.getStartDate() < time){
810                                    //Add 1 to avoid player issues.
811                                    currentSubtitle.setStartDate(time+1);
812                            }
813                            //check that start date is not after end date.
814                            if(currentSubtitle.getEndDate() < currentSubtitle.getStartDate()){
815                                    //If that happens, re shift end date, just after start date.
816                                    //St will not be visible, but player will not crash.
817                                    currentSubtitle.setEndDate(currentSubtitle.getStartDate()+1);
818                            }
819                            //Keep end date in time var.
820                            time = currentSubtitle.getEndDate();
821                    }
822                    //indicate the file has changed.
823                    fileChanged = true;
824            }
825    
826            /**
827             * Method orderRepair.
828             * <br><b>Summary:</b><br>
829             * This method is called when user want to repair the order of the subtitle file.
830             * It will check chronology, order ST's with their start time, and finally fix ST's numbers.
831             */
832            public void orderRepair() {
833                    Trace.trace("Order repair.", Trace.ALGO_PRIORITY);
834                    //Just sort the list.
835                    //subtitles are comparable elements, ordered with their start date.
836                    Collections.sort(subtitleList);
837                    //then fix ST numbers.
838                    //Just have to parse all ST.
839                    for (int i = 0; i < subtitleList.size(); i++) {
840                            //get current subtitle.
841                            Subtitle currentSubtitle = (Subtitle) subtitleList.get(i);
842                            //And fix its number, add 1 because ST number starts at 1.
843                            currentSubtitle.setNumber(i+1);
844                    }
845                    //indicate the file has changed.
846                    fileChanged = true;
847            }
848    
849            /**
850             * Method getSubtitleIndex.
851             * <br><b>Summary:</b><br>
852             * This method is used to know the subtitle that should be active at the given date.
853             * @param date              The date (in milliseconds).
854             * @return <b>Subtitle</b>  The subtitle.
855             */
856            public Subtitle getSubtitleAtDate(int date) {
857                    //The result of the method.
858                    Subtitle result = null;
859                    for (int i = 0; i < subtitleList.size(); i++) {
860                            //get subtitle.
861                            Subtitle subtitle = (Subtitle) subtitleList.get(i);
862                            if ((result == null) 
863                                            || ((subtitle.getStartDate() > (result.getStartDate())) && (subtitle.getStartDate() <= date))) {
864                                    result = subtitle;
865                            }
866                    }
867                    //return the result.
868                    return result;
869            }
870    
871            /**
872             * Method magicResynchro.
873             * <br><b>Summary:</b><br>
874             * This method permits to perform a magic resynchro, using the defined anchors.
875             */
876            public void magicResynchro() {
877                    //First is to get the defined anchored delays.
878                    ArrayList<Double> xList = new ArrayList<Double>(); 
879                    ArrayList<Double> yList = new ArrayList<Double>(); 
880                    for(Subtitle subtitle : subtitleList){
881                            if(subtitle.isAnchored()){
882                                    xList.add((double) subtitle.getStartDate());
883                                    yList.add((double)(subtitle.getAnchor() - subtitle.getStartDate()));
884                            }
885                    }
886                    //Then create the linear interpolation.
887                    double[] x = new double[xList.size()];
888                    double[] y = new double[yList.size()];
889                    for (int i = 0; i < x.length; i++) {
890                            x[i] = xList.get(i).doubleValue();
891                            y[i] = yList.get(i).doubleValue();
892                    }
893                    getLinearInterpolation(x, y);
894                    //Then parse all the subtitles, and apply the delay computed by the linear interpolation.
895                    for(Subtitle subtitle : subtitleList){
896                            subtitle.delay((int) linearInterpolation.interpolate(subtitle.getStartDate()));
897                    }
898                    //magic Interpolation done !
899                    //indicate the file has changed.
900                    fileChanged = true;
901            }
902    
903            private void getLinearInterpolation(double[] x, double[] y) {
904                    if(linearInterpolation==null){
905                            linearInterpolation = new LinearInterpolation(x, y);
906                    }
907            }
908    
909            /**
910             * Mix subtitle files keeps times and copy subtiles from another subtitle file.
911             * <br>Mix subtitle files keeps subtitles and copy times from another subtitke file.
912             * <br>This method only works with two subtitle file with the same numer of subtitles
913             * @param theOtherSubtitleFile  a <code>SubtitleFile</code> object
914             * @param keepSubtitles                 true, the subtitles are kept, false the times are kept
915             * @return                                              true if <code>this</code> are changed, false if not
916             */
917            public boolean mixWithAnotherSubtitleFile(SubtitleFile theOtherSubtitleFile, boolean keepSubtitles) {
918                    // check subtitles coherence, if they got the same number of subtitles, the process could be done:
919                    if(this.subtitleList.size() == theOtherSubtitleFile.subtitleList.size()) {                      
920                            if(keepSubtitles) {
921                                    Trace.trace("Keep subtitles and change times", Trace.MESSAGE_PRIORITY);
922                                    // we keep subtitles and change times...                                        
923                                    for(int i=0;i<this.subtitleList.size();i++) {
924                                            ((Subtitle)this.subtitleList.get(i)).setStartDate(
925                                                            ((Subtitle)theOtherSubtitleFile.subtitleList.get(i)).getStartDate());
926                                            ((Subtitle)this.subtitleList.get(i)).setEndDate(
927                                                            ((Subtitle)theOtherSubtitleFile.subtitleList.get(i)).getEndDate());
928                                    }                               
929                            } else {
930                                    Trace.trace("Keep times and change subtitles", Trace.MESSAGE_PRIORITY);
931                                    // we keep times and changes all the subtitles...
932                                    for(int i=0;i<this.subtitleList.size();i++) {
933                                            ((Subtitle)this.subtitleList.get(i)).setSubtitle(
934                                                            ((Subtitle)theOtherSubtitleFile.subtitleList.get(i)).getSubtitle());
935                                    }
936                            }
937                            // indicate that the file has changed.
938                            fileChanged = true;
939                    }
940                    return fileChanged;
941            }
942    
943            /**
944             * Returns an instance of a <code>SubtitleFile</code> implementation class
945             * which represents the subtitle type of the given <code>java.io.File</code> object.
946             * <br> The class must be in the same package of the SubtitleFile class
947             * an its name must be format like: "Fileextension" + "File" 
948             * <br>Example: for srt file, <code>SrtFile</code>)
949             * @param file                          the subtitle file
950             * @param subtitleList          the subtitles list
951             * @param useEmptyConstructor   True if you want to call the empty constructor of the instance. false If you want the full constructor of subtitleFile to be called (will parse the file)
952             * @return                                      an instance of a <code>SubtitleFile</code> implementation class, 
953             *                                                      null if an error occurs
954             * @throws FileConversionException if a limitation when reading the file appears
955             * @throws Exception // --temporary-- //
956             */
957            public static SubtitleFile getInstance(File file, ArrayList<Subtitle> subtitleList, String charset, boolean useEmptyConstructor) throws FileConversionException, Exception {
958                    SubtitleFile result = null;
959                    // get back and format extension:
960                    String extension = Utils.getExtension(file);       
961                    extension = extension.substring(0, 1).toUpperCase() + extension.substring(1).toLowerCase();
962                    try {
963                            // we get back the appropriate class:
964                            Class<?> subtitleFileClass = Class.forName(
965                                            SubtitleFile.class.getPackage().getName() 
966                                            + "."
967                                            + extension 
968                                            + "File");
969                            // it constructor:
970                            
971                            // and instantiate it:
972                            if(!useEmptyConstructor){
973                                    Constructor<?> constructorClass = subtitleFileClass.getConstructor(
974                                                    File.class, 
975                                                    ArrayList.class,
976                                                    String.class);
977                                    result = (SubtitleFile) constructorClass.newInstance(file, subtitleList, charset);
978                            }else{
979                                    Constructor<?> constructorClass = subtitleFileClass.getConstructor();
980                                    result = (SubtitleFile) constructorClass.newInstance();
981                            }
982    
983                    } catch ( ClassNotFoundException e ) {
984                            // there's no class to support this file format
985                            throw FileConversionException.getUnsupportedFileFormatException(file, extension);
986                    } catch ( InvocationTargetException e ) {
987                            // An exception is wrapped, it comes from #parse() method.
988                            // Opening file failed:                         
989                            Throwable wrappedException = e.getCause();                              
990                            if( wrappedException instanceof FileConversionException ) {
991                                    // re dispatch exception
992                                    throw (FileConversionException) wrappedException;
993                            } else {
994                                    throw (Exception) e; 
995                            }
996                    } catch ( NullPointerException e) {
997                            // file or/and subtitleList is null
998                            //
999                            throw e;
1000                    } catch ( Exception e ) {
1001                            // DEVELOPPER: implement solutions if these exceptions occurs
1002                            //
1003                            // SecurityException
1004                            // NoSuchMethodException
1005                            // IllegalArgumentException
1006                            // InstantiationException
1007                            // IllegalAccessException
1008                            throw e;
1009                    } 
1010                    return result;
1011            }
1012            
1013            /**
1014             * Returns an instance of a <code>SubtitleFile</code> implementation class
1015             * which represents the subtitle type of the given <code>java.io.File</code> object.
1016             * <br> The class must be in the same package of the SubtitleFile class
1017             * an its name must be format like: "Fileextension" + "File" 
1018             * <br>Example: for srt file, <code>SrtFile</code>)
1019             * @param file                          the subtitle file
1020             * @param subtitleList          the subtitles list
1021             * @return                                      an instance of a <code>SubtitleFile</code> implementation class, 
1022             *                                                      null if an error occurs
1023             * @throws FileConversionException if a limitation when reading the file appears
1024             * @throws Exception // --temporary-- //
1025             */
1026            public static SubtitleFile getInstance(File file, ArrayList<Subtitle> subtitleList, String charset) throws FileConversionException, Exception {
1027                    return getInstance(file, subtitleList, charset, false);
1028            }
1029    
1030            /**
1031             * Same that {@link #getInstance(File, ArrayList, String)}.
1032             * <br>Charset is set to the default one.
1033             * @param file                  the subtitle file
1034             * @param subtitleList  the subtitle list to fill
1035             * @return                              the new created <tt>SubtitleFile</tt> object
1036             * @throws FileConversionException      if an error occurs
1037             * @throws Exception                            if an untraitable error occurs [to solved]
1038             */
1039            public static SubtitleFile getInstance(File file, ArrayList<Subtitle> subtitleList) throws FileConversionException, Exception {
1040                    return getInstance(file, subtitleList, null);
1041            }
1042    
1043            /**
1044             * Method getSubtitleList.
1045             * <br><b>Summary:</b><br>
1046             * Return the subtitleList.
1047             * @return the subtitleList
1048             */
1049            public ArrayList<Subtitle> getSubtitleList() {
1050                    return subtitleList;
1051            }
1052    
1053            /**
1054             * Method setSubtitleList.
1055             * <br><b>Summary:</b><br>
1056             * Set the subtitleList.
1057             * @param subtitleList the subtitleList to set
1058             */
1059            public void setSubtitleList(ArrayList<Subtitle> subtitleList) {
1060                    this.subtitleList.clear();
1061                    this.subtitleList.addAll(subtitleList);
1062                    //indicate the file has changed.
1063                    fileChanged = true;
1064            }
1065    
1066            /**
1067             * Method getSubtitleListClone.
1068             * <br><b>Summary:</b><br>
1069             * returns a clone of the subtitle list.
1070             * All the Subtitles are cloned.
1071             * @return  (<b>ArrayList<Subtitle></b>)   A clone of the subtitle list. All Subtitles are cloned.
1072             */
1073            public ArrayList<Subtitle> getSubtitleListClone() {
1074                    //The result
1075                    ArrayList<Subtitle> result = new ArrayList<Subtitle>();
1076                    for(Subtitle subtitle : subtitleList){
1077                            result.add(subtitle.cloneSubtitle());
1078                    }
1079                    //return the result
1080                    return result;
1081            }
1082    }