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 }