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             * @return                                      an instance of a <code>SubtitleFile</code> implementation class, 
952             *                                                      null if an error occurs
953             * @throws FileConversionException if a limitation when reading the file appears
954             * @throws Exception // --temporary-- //
955             */
956            public static SubtitleFile getInstance(File file, ArrayList<Subtitle> subtitleList, String charset) throws FileConversionException, Exception {
957                    SubtitleFile result = null;
958                    // get back and format extension:
959                    String extension = Utils.getExtension(file);       
960                    extension = extension.substring(0, 1).toUpperCase() + extension.substring(1).toLowerCase();
961                    try {
962                            // we get back the appropriate class:
963                            Class<?> subtitleFileClass = Class.forName(
964                                            SubtitleFile.class.getPackage().getName() 
965                                            + "."
966                                            + extension 
967                                            + "File");
968                            // it constructor:
969                            Constructor<?> constructorClass = subtitleFileClass.getConstructor(
970                                            File.class, 
971                                            ArrayList.class,
972                                            String.class);
973                            // and instantiate it:
974                            result = (SubtitleFile) constructorClass.newInstance(file, subtitleList, charset);
975    
976                    } catch ( ClassNotFoundException e ) {
977                            // there's no class to support this file format
978                            throw FileConversionException.getUnsupportedFileFormatException(file, extension);
979                    } catch ( InvocationTargetException e ) {
980                            // An exception is wrapped, it comes from #parse() method.
981                            // Opening file failed:                         
982                            Throwable wrappedException = e.getCause();                              
983                            if( wrappedException instanceof FileConversionException ) {
984                                    // re dispatch exception
985                                    throw (FileConversionException) wrappedException;
986                            } else {
987                                    throw (Exception) e; 
988                            }
989                    } catch ( NullPointerException e) {
990                            // file or/and subtitleList is null
991                            //
992                            throw e;
993                    } catch ( Exception e ) {
994                            // DEVELOPPER: implement solutions if these exceptions occurs
995                            //
996                            // SecurityException
997                            // NoSuchMethodException
998                            // IllegalArgumentException
999                            // InstantiationException
1000                            // IllegalAccessException
1001                            throw e;
1002                    } 
1003                    return result;
1004            }
1005    
1006            /**
1007             * Same that {@link #getInstance(File, ArrayList, String)}.
1008             * <br>Charset is set to the default one.
1009             * @param file                  the subtitle file
1010             * @param subtitleList  the subtitle list to fill
1011             * @return                              the new created <tt>SubtitleFile</tt> object
1012             * @throws FileConversionException      if an error occurs
1013             * @throws Exception                            if an untraitable error occurs [to solved]
1014             */
1015            public static SubtitleFile getInstance(File file, ArrayList<Subtitle> subtitleList) throws FileConversionException, Exception {
1016                    return getInstance(file, subtitleList, null);
1017            }
1018    
1019            /**
1020             * Method getSubtitleList.
1021             * <br><b>Summary:</b><br>
1022             * Return the subtitleList.
1023             * @return the subtitleList
1024             */
1025            public ArrayList<Subtitle> getSubtitleList() {
1026                    return subtitleList;
1027            }
1028    
1029            /**
1030             * Method setSubtitleList.
1031             * <br><b>Summary:</b><br>
1032             * Set the subtitleList.
1033             * @param subtitleList the subtitleList to set
1034             */
1035            public void setSubtitleList(ArrayList<Subtitle> subtitleList) {
1036                    this.subtitleList.clear();
1037                    this.subtitleList.addAll(subtitleList);
1038                    //indicate the file has changed.
1039                    fileChanged = true;
1040            }
1041    
1042            /**
1043             * Method getSubtitleListClone.
1044             * <br><b>Summary:</b><br>
1045             * returns a clone of the subtitle list.
1046             * All the Subtitles are cloned.
1047             * @return  (<b>ArrayList<Subtitle></b>)   A clone of the subtitle list. All Subtitles are cloned.
1048             */
1049            public ArrayList<Subtitle> getSubtitleListClone() {
1050                    //The result
1051                    ArrayList<Subtitle> result = new ArrayList<Subtitle>();
1052                    for(Subtitle subtitle : subtitleList){
1053                            result.add(subtitle.cloneSubtitle());
1054                    }
1055                    //return the result
1056                    return result;
1057            }
1058    }