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 }