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 }