001    /////////////////////////////////////////////////
002    //This file is part of Sears project.
003    //Subtitle Editor And Re-Synch
004    //A tool to easily modify and resynch movies subtitles.
005    /////////////////////////////////////////////////
006    //This program is free software; 
007    //you can redistribute it and/or modify it under the terms 
008    //of the GNU General Public License 
009    //as published by the Free Software Foundation; 
010    //either version 2 of the License, or (at your option) any later version.
011    /////////////////////////////////////////////////
012    //Sears project is available under sourceforge
013    //at adress: http://sourceforge.net/projects/sears/
014    //Copyright (C) 2005 Booba Skaya
015    //Mail: booba.skaya@gmail.com
016    
017    package sears.file;
018    
019    import java.io.BufferedWriter;
020    import java.io.File;
021    import java.io.FileOutputStream;
022    import java.io.IOException;
023    import java.io.OutputStreamWriter;
024    import java.util.ArrayList;
025    import java.util.Iterator;
026    import java.util.StringTokenizer;
027    
028    import sears.file.exception.io.FileConversionException;
029    import sears.tools.Trace;
030    import sears.tools.Utils;
031    
032    /**
033     * Class SrtFile.
034     * <br><b>Summary:</b><br>
035     * This class represent a srt subtitle file.
036     * Specialize the SubtitleFile for srt type subtitles.
037     */
038    public class SrtFile extends SubtitleFile {
039    
040            protected static final String TIME_SEPARATOR = " --> ";
041    
042            protected static final String TOKEN_TIME_DELIMITER = "-> ";
043    
044            protected static final String TIME_PATTERN = "^((\\d{2}\\:d{2}){2}\\d{2}\\,\\d{3})";
045    
046            //protected static final String TIME_LINE_PATTERN = "";
047    
048            /**
049             * Constructor SrtFile.
050             * <br><b>Summary:</b><br>
051             * Constructor of the class.
052             * Beware not to use this file directly, because it does contains no ST.
053             * You will have to fill the list of ST, and save the File first.
054             */
055    
056            public SrtFile(){
057                    super();
058            }
059    
060            /**
061             * Constructor SrtFile.
062             * <br><b>Summary:</b><br>
063             * Constructor of the class.
064             * @param file            The <b>(File)</b> to open.
065             * @param subtitleList        The <b>(ArrayList)</b> List of subtitles.
066             * @throws IOException 
067             * @throws MalformedSubtitleFileException 
068             */
069            public SrtFile(File file, ArrayList<Subtitle> subtitleList) throws FileConversionException {
070                    super(file, subtitleList);
071            }
072    
073            /**
074             * Constructor SrtFile.
075             * <br><b>Summary:</b><br>
076             * Constructor of the class.
077             * @param file                The <b>(String)</b> path to file to open.
078             * @param subtitleList        The <b>(ArrayList)</b> List of subtitles.
079             * @throws IOException 
080             * @throws MalformedSubtitleFileException 
081             */
082            public SrtFile(String file, ArrayList<Subtitle> subtitleList) throws FileConversionException {
083                    super(file, subtitleList);
084            }
085    
086            public SrtFile(File file, ArrayList<Subtitle> subtitleList, String charset) throws FileConversionException {
087                    super(file, subtitleList, charset);
088            }
089    
090            /* (non-Javadoc)
091             * @see sears.file.SubtitleFile#parse()
092             */
093            protected void parse() throws FileConversionException {         
094                    
095                    FileConversion pm = null;
096                    String charset = DEFAULT_CHARSET;
097                    if( !charset.equals(getCharset()) ) {
098                            pm = new FileToSrtFile(file, getCharset());
099                            // try to fill the list of subtitles:
100                            pm.parse(subtitleList);
101                    } else {        
102                            int count = -1;
103                            while( count < BASIC_CHARSETS.length ) {
104                                    try {
105                                            pm = new FileToSrtFile(file, charset);
106                                            // try to fill the list of subtitles:
107                                            pm.parse(subtitleList);
108                                            // parse succeed so, stop the loop:
109                                            count = BASIC_CHARSETS.length;
110                                            setCharset(charset);
111                                    } catch (FileConversionException e) {
112                                            // NOTE FOR DEVELOPER:
113                                            //
114                                            // the catching exception must be tested
115                                            // if the nature of the nature is a basic IO exception
116                                            // it is not necessary to parse again with a different charset
117                                            // the error will occur again                                   
118                                            count++;
119                                            if( count >= BASIC_CHARSETS.length ) {
120                                                    // stop trying to decode:
121                                                    throw e;
122                                            } else {
123                                                    // change the charset
124                                                    charset = BASIC_CHARSETS[count];
125                                            }
126                                    }
127                            }
128                    }
129            }
130    
131            /* (non-Javadoc)
132             * @see sears.file.SubtitleFile#writeToFile(java.io.File)
133             */
134            public void writeToFile(File fileToWrite) throws FileConversionException {
135                    {
136                            //First is to know which end of line to use.
137                            //by default use the linux one.
138                            String lineSeparator = getLineSeparator();
139                            try {
140                                    // keep the initial encoding:
141                                    BufferedWriter out = new BufferedWriter(
142                                                    new OutputStreamWriter(
143                                                                    new FileOutputStream(fileToWrite), super.getCharset()));
144                                    Iterator<Subtitle> subtitles = subtitleList.iterator();
145                                    while (subtitles.hasNext()) {
146                                            Subtitle subtitle = subtitles.next();
147                                            out.write(subtitle.getNumber() + lineSeparator);
148                                            out.write(timeToString(subtitle.getStartDate()) + TIME_SEPARATOR + timeToString(subtitle.getEndDate()) + lineSeparator);
149                                            out.write(subtitle.getSubtitle().replaceAll(Utils.LINE_SEPARATOR, lineSeparator));
150                                            //Check wether subtitle has an end of line.
151                                            if( !subtitle.getSubtitle().endsWith(Utils.LINE_SEPARATOR) ){
152                                                    //If it doesn't have one, put one.
153                                                    out.write(lineSeparator);
154                                            }
155                                            out.write(lineSeparator);
156                                    }
157                                    out.close();
158                                    //indicate file is good.
159                                    fileChanged = false;
160                            } catch( IOException e ) {
161                                    // FileNotFoundException
162                                    // IOException
163                                    throw FileConversionException.getAccessException(
164                                                    FileConversionException.WRITE_ACCESS, file);
165                            } catch( SecurityException e) {
166                                    throw FileConversionException.getAccessException(
167                                                    FileConversionException.WRITE_ACCESS, file);
168                            } catch( NumberFormatException e ) {
169                                    // corrupt time in arraylist !!
170                                    e.printStackTrace();
171                            }
172                    }
173            }
174    
175            /* (non-Javadoc)
176             * @see sears.file.SubtitleFile#writeToFile(java.io.File)
177             */
178            public void writeToTemporaryFile() {
179                    try {
180                            // Create the temporary subtitle file
181                            if (temporaryFile == null) {
182                                    temporaryFile = java.io.File.createTempFile("SRTSubtitle", null);
183                                    temporaryFile.deleteOnExit();
184                            }
185                            // Store to the temporary file
186                            boolean oldFileChangedStatus = fileChanged;
187                            writeToFile(temporaryFile);
188                            // Restore the old file changed value
189                            fileChanged = oldFileChangedStatus;
190                    } catch (IOException e) {
191                            Trace.trace("Error while writing temporary SRT file !",
192                                            Trace.ERROR_PRIORITY);
193                            Trace.trace(e.getMessage(), Trace.ERROR_PRIORITY);
194                    }
195            }
196    
197            /* (non-Javadoc)
198             * @see sears.file.SubtitleFile#getNewInstance()
199             */
200            protected SubtitleFile getNewInstance() {
201                    return new SrtFile();
202            }
203    
204            /*
205             * (non-Javadoc)
206             * @see sears.file.SubtitleFile#extension()
207             */
208            public String extension() {
209                    return "srt";
210            }
211    }
212    
213    class FileToSrtFile extends FileConversion {
214    
215            public static final int START_DATE = 0;
216            public static final int END_DATE = 1;
217    
218            public FileToSrtFile(File file, String charset) throws FileConversionException {
219                    super(file, charset);
220            }
221    
222            /*
223             * (non-Javadoc)
224             * @see sears.file.ParseModule#getSubtitle(java.lang.String)
225             */
226            protected Subtitle getSubtitle(String line) throws FileConversionException {
227                    Subtitle newSubtitle = null;
228                    boolean endOfFile = false;
229                    if( line.trim().length() == 0 ) {
230                            // line is empty, try to go to the next non empty line
231                            line = getTheNextNonEmptyLine();
232                            if( line == null ) {
233                                    // END OF FILE
234                                    // WE STOP PARSING FILE
235                                    endOfFile = true;       
236                            }                       
237                    }
238                    if( !endOfFile ) {                      
239                            // *************
240                            // GETS NUMBER *
241                            // *************                        
242                            // Read the first line, it must contain a number
243                            // line is non null and it is a non empty line
244                            // if the line is not a number, an exception is throw by the method
245                            int numberFound = getSubtitleNumberFromString(line);
246    
247    
248                            // ****************
249                            // GETS TIME LINE *
250                            // ****************
251                            // Read the next non empty line, it must contain the start and end date
252                            line = getTheNextNonEmptyLine();
253                            if( line == null ) {
254                                    // there's no more line where a time line must be:
255                                    throw FileConversionException.getMalformedSubtitleFileException(
256                                                    FileConversionException.NO_SUBTITLE_TIME, file, lineCount, line);
257                            }               
258                            StringTokenizer stk = getTime(line, SrtFile.TOKEN_TIME_DELIMITER);
259    
260                            // *****************
261                            // GETS START DATE *
262                            // *****************
263                            // if an error occurs when convert string information to date
264                            // a MalformedSubtitleFileException is throw
265                            int startDate = getDateFromString(stk.nextToken(), START_DATE);
266    
267                            // ***************
268                            // GETS END DATE *
269                            // ***************
270                            // if an error occurs when convert string information to date
271                            // a MalformedSubtitleFileException is throw
272                            int endDate = getDateFromString(stk.nextToken(), END_DATE);
273    
274                            // ********************
275                            // GETS SUBTITLE TEXT *
276                            // ********************
277                            // First is to read till we found a subtitle, to avoid error in files with an empty line
278                            // after the time definition.
279                            line = getTheNextNonEmptyLine();
280                            // AUTO CORRECTION, subtitle text isn't found, it become an empty string
281                            if( line == null ) {
282                                    line = "";
283                            }
284                            // here we have found a subtitle, or reached end of file.
285                            String subtitle = getSubtitleTextFromString(line);
286                            // creates the new subtitle:
287                            newSubtitle = new Subtitle(numberFound, startDate, endDate, subtitle);
288                    }
289    
290                    return newSubtitle;
291            }
292    
293            protected StringTokenizer getTime(String str, String timeDelimiter) throws FileConversionException {
294                    StringTokenizer stk = null;
295                    ensureStringIsValid(str);
296                    stk = new StringTokenizer(str, timeDelimiter);
297                    if( stk.countTokens() != 2 ) {
298                            // there's more or less information than start and end date (if there is :) )
299                            throw FileConversionException.getMalformedSubtitleFileException(
300                                            FileConversionException.MALFORMED_TIME_LINE, file, lineCount, subString(str));
301                    }
302                    return stk;
303            }
304    
305            private int getSubtitleNumberFromString(String str) throws FileConversionException {
306                    int subtitleNumber = 0;
307                    try {
308                            subtitleNumber =  Integer.parseInt(str);
309                            Trace.trace("Number: " + subtitleNumber, Trace.ALGO_PRIORITY);
310                    } catch ( NumberFormatException e ) {
311                            // str do not correspond to a number
312                            // "sub string str in case of the string length is too bigger (process safe)"
313                            // throw new NoSubtitleNumberException(file, lineCount, subString(str));
314                            throw FileConversionException.getMalformedSubtitleFileException(
315                                            FileConversionException.MALFORMED_SUBTITLE_NUMBER, 
316                                            file, lineCount, subString(str));
317                    }
318                    return subtitleNumber;
319            }
320    
321            private int getDateFromString(String str, int dateType) throws FileConversionException {
322                    int date = 0;
323                    // DEVELOPER:
324                    //
325                    // catch exception means that 'this' creates an object.
326                    // This object creates another object (ParseSubtitleFileException instance).
327                    // Maybe (sure) is too much, some test in #stringToTime method could be create 
328                    // directly the FileConversionException object and so, a more efficient error report
329                    // could be done.
330                    //
331                    if( dateType == START_DATE ) {
332                            try {
333                                    date = SubtitleFile.stringToTime(str);
334                            } catch ( NumberFormatException e) {
335                                    //throw new MalformedStartTimeException(file, lineCount,  subString(str));
336                                    throw FileConversionException.getMalformedSubtitleFileException(
337                                                    FileConversionException.MALFORMED_START_TIME, 
338                                                    file, lineCount, subString(str));
339                            }
340                    } else if( dateType == END_DATE ) {
341                            try {
342                                    date = SubtitleFile.stringToTime(str);
343                            } catch ( NumberFormatException e) {
344                                    //throw new MalformedEndTimeException(file, lineCount,  subString(str));
345                                    throw FileConversionException.getMalformedSubtitleFileException(
346                                                    FileConversionException.MALFORMED_END_TIME, 
347                                                    file, lineCount, subString(str));
348                            }
349                    } else {
350                            throw new IllegalArgumentException(dateType 
351                                            + " is not a valid type of date, please see the class constants");
352                    }
353                    return date;
354            }
355    
356            private String getSubtitleTextFromString(String str) throws FileConversionException {
357                    String subtitle = "";
358                    // add the first subtitle line
359                    subtitle += str + Utils.LINE_SEPARATOR;
360                    // and read the rest of the subtitle.
361                    for( str = readLine(); ( str != null && !str.equals("") ); str = readLine() ) {
362                            subtitle += str + Utils.LINE_SEPARATOR;
363                    }
364                    return subtitle;
365            }       
366    
367    
368    }