Package net.pms.formats.v2

Source Code of net.pms.formats.v2.SubtitleUtils

/*
* PS3 Media Server, for streaming any medias to your PS3.
* Copyright (C) 2012  I. Sokolov
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 2
* of the License only.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
package net.pms.formats.v2;

import java.awt.Color;
import java.io.*;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.pms.PMS;
import net.pms.configuration.PmsConfiguration;
import net.pms.dlna.DLNAMediaInfo;
import net.pms.dlna.DLNAResource;
import net.pms.dlna.DLNAMediaInfo.Mode3D;
import net.pms.dlna.DLNAMediaSubtitle;
import net.pms.io.OutputParams;
import net.pms.io.ProcessWrapperImpl;
import net.pms.util.FileUtil;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.mozilla.universalchardet.Constants.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SubtitleUtils {
  private final static PmsConfiguration configuration = PMS.getConfiguration();
  private static final Logger LOGGER = LoggerFactory.getLogger(SubtitleUtils.class);
  private final static Map<String, String> fileCharsetToMencoderSubcpOptionMap = new HashMap<String, String>() {
    private static final long serialVersionUID = 1L;

    {
      // Cyrillic / Russian
      put(CHARSET_IBM855, "enca:ru:cp1251");
      put(CHARSET_ISO_8859_5, "enca:ru:cp1251");
      put(CHARSET_KOI8_R, "enca:ru:cp1251");
      put(CHARSET_MACCYRILLIC, "enca:ru:cp1251");
      put(CHARSET_WINDOWS_1251, "enca:ru:cp1251");
      put(CHARSET_IBM866, "enca:ru:cp1251");
      // Greek
      put(CHARSET_WINDOWS_1253, "cp1253");
      put(CHARSET_ISO_8859_7, "ISO-8859-7");
      // Western Europe
      put(CHARSET_WINDOWS_1252, "cp1252");
      // Hebrew
      put(CHARSET_WINDOWS_1255, "cp1255");
      put(CHARSET_ISO_8859_8, "ISO-8859-8");
      // Chinese
      put(CHARSET_ISO_2022_CN, "ISO-2022-CN");
      put(CHARSET_BIG5, "enca:zh:big5");
      put(CHARSET_GB18030, "enca:zh:big5");
      put(CHARSET_EUC_TW, "enca:zh:big5");
      put(CHARSET_HZ_GB_2312, "enca:zh:big5");
      // Korean
      put(CHARSET_ISO_2022_KR, "cp949");
      put(CHARSET_EUC_KR, "euc-kr");
      // Japanese
      put(CHARSET_ISO_2022_JP, "ISO-2022-JP");
      put(CHARSET_EUC_JP, "euc-jp");
      put(CHARSET_SHIFT_JIS, "shift-jis");
    }
  };
  private static final String SUB_DIR = "subs";

  /**
   * Returns value for -subcp option for non UTF-8 external subtitles based on
   * detected charset.
   *
   * @param dlnaMediaSubtitle DLNAMediaSubtitle with external subtitles file.
   * @return value for mencoder's -subcp option or null if can't determine.
   */
  public static String getSubCpOptionForMencoder(DLNAMediaSubtitle dlnaMediaSubtitle) {
    if (dlnaMediaSubtitle == null) {
      throw new NullPointerException("dlnaMediaSubtitle can't be null.");
    }
    if (isBlank(dlnaMediaSubtitle.getExternalFileCharacterSet())) {
      return null;
    }
    return fileCharsetToMencoderSubcpOptionMap.get(dlnaMediaSubtitle.getExternalFileCharacterSet());
  }

  /**
   * Applies codepage conversion to subtitles file
   *
   * @param fileToConvert Subtitles file to convert
   * @param outputSubs Converted subtitles file
   * @return Converted subtitles file
   * @throws IOException
   */
  public static File applyCodepageConversion(File fileToConvert, File outputSubs) throws IOException {
    String line;
    BufferedReader reader;
    String cp = configuration.getSubtitlesCodepage();
    final boolean isSubtitlesCodepageForcedInConfigurationAndSupportedByJVM = isNotBlank(cp) && Charset.isSupported(cp);
    if (isSubtitlesCodepageForcedInConfigurationAndSupportedByJVM) {
      reader = new BufferedReader(new InputStreamReader(new FileInputStream(fileToConvert), Charset.forName(cp)));
    } else {
      reader = FileUtil.bufferedReaderWithCorrectCharset(fileToConvert);
    }

    try (BufferedWriter output = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outputSubs), Charset.forName(CHARSET_UTF_8)))) {
      while ((line = reader.readLine()) != null) {
        output.write(line + "\n");
      }

      output.flush();
      output.close();
    }

    reader.close();
    return outputSubs;
  }

  /**
   * Extracts embedded subtitles from video to file in SSA/ASS format, converts external SRT
   * subtitles file to SSA/ASS format and applies fontconfig setting to that converted file
   * and applies timeseeking when required.
   *
   * @param dlna DLNAResource
   * @param media DLNAMediaInfo
   * @param params Output parameters
   * @param configuration
   * @return Converted subtitle file
   * @throws IOException
   */
  public static File getSubtitles(DLNAResource dlna, DLNAMediaInfo media, OutputParams params, PmsConfiguration configuration, SubtitleType subtitleType) throws IOException {
    if (media == null || params.sid.getId() == -1 || !params.sid.getType().isText()) {
      return null;
    }

    String dir = configuration.getDataFile(SUB_DIR);
    File subsPath = new File(dir);
    if (!subsPath.exists()) {
      subsPath.mkdirs();
    }

    boolean applyFontConfig = configuration.isFFmpegFontConfig();
    boolean isEmbeddedSource = params.sid.getId() < 100;
    boolean is3D = media.is3d() && !media.stereoscopyIsAnaglyph();

    String filename = isEmbeddedSource ?
      dlna.getSystemName() : params.sid.getExternalFile().getAbsolutePath();

    String basename;

    long modId = new File(filename).lastModified();
    if (modId != 0) {
      // We have a real file
      basename = FilenameUtils.getBaseName(filename);
    } else {
      // It's something else, e.g. a url or psuedo-url without meaningful
      // lastmodified and (maybe) basename characteristics.
      basename = dlna.getName().replaceAll("[<>:\"\\\\/|?*+\\[\\]\n\r ']", "").trim();
      modId = filename.hashCode();
    }

    File convertedSubs;
    if (applyFontConfig || isEmbeddedSource || is3D || params.sid.getType() != subtitleType) {
      convertedSubs = new File(subsPath.getAbsolutePath() + File.separator + basename + "_ID" + params.sid.getId() + "_" + modId + "." + subtitleType.getExtension());
    } else {
      String tmp = params.sid.getExternalFile().getName().replaceAll("[<>:\"\\\\/|?*+\\[\\]\n\r ']", "").trim();
      convertedSubs = new File(subsPath.getAbsolutePath() + File.separator + modId + "_" + tmp);
    }

    if (convertedSubs.canRead()) {
      // subs are already converted
      if (applyFontConfig || isEmbeddedSource || is3D) {
        params.sid.setType(SubtitleType.ASS);
        params.sid.setExternalFileCharacterSet(CHARSET_UTF_8);
        if (is3D) {
          try {
            convertedSubs = convertASSToASS3D(convertedSubs, media, params);
          } catch (IOException | NullPointerException e) {
            LOGGER.debug("Converting to ASS3D format ends with error: " + e);
            return null;
          }
        }
      }

      return convertedSubs;
    }

    boolean isExternalAss = false;
    if (
      params.sid.getType() == SubtitleType.ASS &&
      params.sid.isExternal() &&
      !isEmbeddedSource
    ) {
      isExternalAss = true;
    }

    File tempSubs;
    if (
      isExternalAss ||
      (
        !applyFontConfig &&
        !isEmbeddedSource &&
        (params.sid.getType() == subtitleType) &&
        (params.sid.getType() == SubtitleType.SUBRIP || params.sid.getType() == SubtitleType.WEBVTT) &&
        !is3D
      )
    ) {
      tempSubs = params.sid.getExternalFile();
    } else {
      tempSubs = convertSubsToSubtitleType(filename, media, params, configuration, subtitleType);
    }

    if (tempSubs == null) {
      return null;
    }

    if (!FileUtil.isFileUTF8(tempSubs)) {
      try {
        tempSubs = applyCodepageConversion(tempSubs, convertedSubs);
        params.sid.setExternalFileCharacterSet(CHARSET_UTF_8);
      } catch (IOException ex) {
        params.sid.setExternalFileCharacterSet(null);
        LOGGER.warn("Exception during external file charset detection.", ex);
      }
    } else {
      FileUtils.copyFile(tempSubs, convertedSubs);
      tempSubs = convertedSubs;
    }

    // Now we're sure we actually have our own modifiable file
    if (
      applyFontConfig &&
      !(
        configuration.isUseEmbeddedSubtitlesStyle() &&
        params.sid.getType() == SubtitleType.ASS
      )
    ) {
      try {
        tempSubs = applyFontconfigToASSTempSubsFile(tempSubs, media, configuration);
        params.sid.setExternalFileCharacterSet(CHARSET_UTF_8);
      } catch (IOException e) {
        LOGGER.debug("Applying subs setting ends with error: " + e);
        return null;
      }
    }

    if (is3D) {
      try {
        tempSubs = convertASSToASS3D(tempSubs, media, params);
      } catch (IOException | NullPointerException e) {
        LOGGER.debug("Converting to ASS3D format ends with error: " + e);
        return null;
      }
    }

    if (isEmbeddedSource) {
//      params.sid.setExternalFile(tempSubs);
      params.sid.setType(SubtitleType.ASS);
    }

    PMS.get().addTempFile(tempSubs, 30 * 24 * 3600 * 1000);
    return tempSubs;
  }

  /**
   * Converts external subtitles or extract embedded subs to the requested subtitle type
   *
   * @param fileName subtitles file or video file with embedded subs
   * @param media
   * @param params output parameters
   * @param configuration
   * @param outputSubtitleType requested subtitle type
   * @return Converted subtitles file in requested type
   */
  public static File convertSubsToSubtitleType(String fileName, DLNAMediaInfo media, OutputParams params, PmsConfiguration configuration, SubtitleType outputSubtitleType) {
    if (!params.sid.getType().isText()) {
      return null;
    }
    List<String> cmdList = new ArrayList<>();
    File tempSubsFile;
    cmdList.add(configuration.getFfmpegPath());
    cmdList.add("-y");
    cmdList.add("-loglevel");
    if (LOGGER.isTraceEnabled()) { // Set -loglevel in accordance with LOGGER setting
      cmdList.add("info"); // Could be changed to "verbose" or "debug" if "info" level is not enough
    } else {
      cmdList.add("fatal");
    }

    // Try to specify input encoding if we have a non utf-8 external sub
    if (params.sid.getId() >= 100 && !params.sid.isExternalFileUtf8()) {
      String encoding = isNotBlank(configuration.getSubtitlesCodepage()) ?
          // Prefer the global user-specified encoding if we have one.
          // Note: likely wrong if the file isn't supplied by the user.
          configuration.getSubtitlesCodepage() :
        params.sid.getExternalFileCharacterSet() != null ?
          // Fall back on the actually detected encoding if we have it.
          // Note: accuracy isn't 100% guaranteed.
          params.sid.getExternalFileCharacterSet() :
        null; // Otherwise we're out of luck!
      if (encoding != null) {
        cmdList.add("-sub_charenc");
        cmdList.add(encoding);
      }
    }

    cmdList.add("-i");
    cmdList.add(fileName);

    if (params.sid.isEmbedded()) {
      cmdList.add("-map");
      cmdList.add("0:s:" + (media.getSubtitleTracksList().indexOf(params.sid)));
    }

    try {
      tempSubsFile = new File(configuration.getTempFolder(), FilenameUtils.getBaseName(fileName) + "." + outputSubtitleType.getExtension());
    } catch (IOException e1) {
      LOGGER.debug("Subtitles conversion finished wih error: " + e1);
      return null;
    }
    cmdList.add(tempSubsFile.getAbsolutePath());

    String[] cmdArray = new String[cmdList.size()];
    cmdList.toArray(cmdArray);

    ProcessWrapperImpl pw = new ProcessWrapperImpl(cmdArray, params);
    pw.runInNewThread();

    try {
      pw.join(); // Wait until the conversion is finished
      pw.stopProcess(); // Avoid creating a pipe for this process and messing up with buffer progress bar
    } catch (InterruptedException e) {
      LOGGER.debug("Subtitles conversion finished wih error: " + e);
      return null;
    }

    tempSubsFile.deleteOnExit();
    return tempSubsFile;
  }

  public static File applyFontconfigToASSTempSubsFile(File tempSubs, DLNAMediaInfo media, PmsConfiguration configuration) throws IOException {
    LOGGER.debug("Applying fontconfig to subtitles " + tempSubs.getName());
    File outputSubs = tempSubs;
    StringBuilder outputString = new StringBuilder();
    File temp = new File(configuration.getTempFolder(), tempSubs.getName() + ".tmp");
    FileUtils.copyFile(tempSubs, temp);
    BufferedReader input = FileUtil.bufferedReaderWithCorrectCharset(temp);
    BufferedWriter output = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outputSubs), CHARSET_UTF_8));
    try {
      String line;
      String[] format = null;
      int i;
      boolean playResIsSet = false; // do not apply font size change when video resolution is set
      while ((line = input.readLine()) != null) {
        outputString.setLength(0);
        if (line.contains("[Script Info]")) {
          outputString.append(line).append("\n");
          output.write(outputString.toString());
          while ((line = input.readLine()) != null) {
            outputString.setLength(0);
            if (isNotBlank(line)) {
              if (line.contains("PlayResY:") || line.contains("PlayResX:")) {
                playResIsSet = true;
              }
              outputString.append(line).append("\n");
              output.write(outputString.toString());
            } else {
              if (!playResIsSet) {
                outputString.append("PlayResY: ").append(media.getHeight()).append("\n");
                outputString.append("PlayResX: ").append(media.getWidth()).append("\n");
              }
              break;
            }
          }
        }

        if (line != null && line.contains("Format:")) {
          format = line.split(",");
          outputString.append(line).append("\n");
          output.write(outputString.toString());
          continue;
        }

        if (line != null && line.contains("Style: Default")) {
          String[] params = line.split(",");

          for (i = 0; i < format.length; i++) {
            switch (format[i].trim()) {
              case "Fontname":
                if (!configuration.getFont().isEmpty()) {
                  params[i] = configuration.getFont();
                }

                break;
              case "Fontsize":
                if (!playResIsSet) {
                  params[i] = Integer.toString((int) ((Integer.parseInt(params[i]) * media.getHeight() / (double) 288 * Double.parseDouble(configuration.getAssScale()))));
                } else {
                  params[i] = Integer.toString((int) (Integer.parseInt(params[i]) * Double.parseDouble(configuration.getAssScale())));
                }

                break;
              case "PrimaryColour":
                String primaryColour = Integer.toHexString(configuration.getSubsColor());
                params[i] = "&H" + primaryColour.substring(6, 8) + primaryColour.substring(4, 6) + primaryColour.substring(2, 4);
                break;
              case "Outline":
                params[i] = configuration.getAssOutline();
                break;
              case "Shadow":
                params[i] = configuration.getAssShadow();
                break;
              case "MarginV":
                params[i] = configuration.getAssMargin();
                break;
              default:
                break;
            }
          }

          outputString.append(StringUtils.join(params, ",")).append("\n");
          output.write(outputString.toString());
          continue;
        }

        outputString.append(line).append("\n");
        output.write(outputString.toString());
      }
    } finally {
      input.close();
      output.flush();
      output.close();
      temp.deleteOnExit();
    }

    return outputSubs;
  }

  /**
   * Converts ASS/SSA subtitles to 3D ASS/SSA subtitles.
   * Based on https://bitbucket.org/r3pek/srt2ass3d
   *
   * @param tempSubs Subtitles file to convert
   * @param media Information about video
   * @return Converted subtitles file
   * @throws IOException
   */
  public static File convertASSToASS3D(File tempSubs, DLNAMediaInfo media, OutputParams params) throws IOException, NullPointerException {
    File outputSubs = new File(FilenameUtils.getFullPath(tempSubs.getPath()), FilenameUtils.getBaseName(tempSubs.getName()) + "_3D.ass");
    StringBuilder outputString = new StringBuilder();
    String subsFileCharset = FileUtil.getFileCharset(tempSubs);
    if (subsFileCharset == null) {
      subsFileCharset = CHARSET_UTF_8;
    }
    BufferedWriter output;
    Mode3D mode3D = media.get3DLayout();
    if (mode3D == null) {
      LOGGER.debug("The 3D layout not recognized for the 3D video");
      throw new NullPointerException("The 3D layout not recognized for the 3D video");
    }

    int playResX;
    int playResY;
    if (mode3D == Mode3D.OUL || mode3D == Mode3D.OUR) {
      playResX = media.getWidth();
      playResY = media.getHeight() / 2;
    } else {
      playResX = media.getWidth() / 2;
      playResY = media.getHeight();
    }

    // First try to calculate subtitles position and depth
    // Max depth - 2% ... + 2%
    int depth3D = (int) - (((double) playResX /(double) 100) * Double.valueOf(configuration.getDepth3D()));
    int offset = (playResX / 100) * 2;
    int bottomSubsPosition = (int) ((playResY /(double) 100) * Double.valueOf(configuration.getAssMargin()));
    int topSubsPositionTb = playResY + bottomSubsPosition;
    int middleSbs = media.getWidth() / 2;
    Pattern timePattern = Pattern.compile("[0-9]:[0-9]{2}:[0-9]{2}.[0-9]{2},[0-9]:[0-9]{2}:[0-9]{2}.[0-9]{2},");
    try (BufferedReader input = new BufferedReader(new InputStreamReader(new FileInputStream(tempSubs), Charset.forName(subsFileCharset)))) {
      output = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outputSubs), Charset.forName(CHARSET_UTF_8)));
      String line;
      outputString.append("[Script Info]\n");
      outputString.append("ScriptType: v4.00+\n");
      outputString.append("WrapStyle: 0\n");
      outputString.append("PlayResX: ").append(media.getWidth()).append("\n");
      outputString.append("PlayResY: ").append(media.getHeight()).append("\n");
      outputString.append("ScaledBorderAndShadow: yes\n\n");
      outputString.append("[V4+ Styles]\n");
      outputString.append("Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding\n");
      String fontScale = Double.toString(100 * Double.parseDouble(configuration.getAssScale()));
      String primaryColour = convertColorToAssHexFormat(new Color(configuration.getSubsColor()));
      String outline = configuration.getAssOutline();
      String shadow = configuration.getAssShadow();
      String fontSize = Integer.toString((int) (16 * media.getHeight() / (double) 288));
      outputString.append("Style: 3D1,Arial,").append(fontSize).append(",").append(primaryColour).append(",&H000000FF,&H00000000,&H00000000,0,0,0,0,").append(fontScale).append(",").append(fontScale).append(",0,0,1,").append(outline).append(",").append(shadow).append(",2,0,0,0,1\n");
      outputString.append("Style: 3D2,Arial,").append(fontSize).append(",").append(primaryColour).append(",&H000000FF,&H00000000,&H00000000,0,0,0,0,").append(fontScale).append(",").append(fontScale).append(",0,0,1,").append(outline).append(",").append(shadow).append(",2,0,0,0,1\n\n");

      outputString.append("[Events]\n");
      outputString.append("Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\n\n");
      output.write(outputString.toString());
      int textPosition = 0;
      while ((line = input.readLine()) != null) {
        if (line.startsWith("[Events]")) {
          line = input.readLine();
          if (line != null && line.startsWith("Format:")) {
            String[] formatPattern = line.split(",");
            int i = 0;
            for (String component : formatPattern) {
              if (component.trim().equals("Text")) {
                textPosition = i;
              }
              i++;
            }
          }
        }

        outputString.setLength(0);
        if (line.startsWith("Dialogue:") && line.contains("Default")) { // TODO: For now convert only Default style. For other styles must be position and font size recalculated
          String[] dialogPattern = line.split(",");
          String text = StringUtils.join(dialogPattern, ",", textPosition, dialogPattern.length);
          Matcher timeMatcher = timePattern.matcher(line);
          if (timeMatcher.find()) { 
            if (mode3D == Mode3D.OUL) {
              outputString.append("Dialogue: 0,")
              .append(timeMatcher.group())
              .append("3D1,,")
              .append(String.format("%04d,", offset - depth3D))
              .append(String.format("%04d,", offset + depth3D))
              .append(String.format("%04d,,", topSubsPositionTb))
              .append(text).append("\n");
              outputString.append("Dialogue: 0,")
              .append(timeMatcher.group())
              .append("3D2,,")
              .append(String.format("%04d,", offset + depth3D))
              .append(String.format("%04d,", offset - depth3D))
              .append(String.format("%04d,,", bottomSubsPosition))
              .append(text).append("\n");
            } else if (mode3D == Mode3D.OUR) {
              outputString.append("Dialogue: 0,")
              .append(timeMatcher.group())
              .append("3D1,,")
              .append(String.format("%04d,", offset + depth3D))
              .append(String.format("%04d,", offset - depth3D))
              .append(String.format("%04d,,", topSubsPositionTb))
              .append(text).append("\n");
              outputString.append("Dialogue: 0,")
              .append(timeMatcher.group())
              .append("3D2,,")
              .append(String.format("%04d,", offset - depth3D))
              .append(String.format("%04d,", offset + depth3D))
              .append(String.format("%04d,,", bottomSubsPosition))
              .append(text).append("\n");
            } else if (mode3D == Mode3D.SBSL) {
              int marginR1 = playResX + offset + depth3D;
              int marginL2 = playResX + offset + depth3D;
              outputString.append("Dialogue: 0,")
              .append(timeMatcher.group())
              .append("3D1,,")
              .append(String.format("%04d,", offset - depth3D))
              .append(String.format("%04d,", marginR1))
              .append(String.format("%04d,,", bottomSubsPosition))
              .append(text).append("\n");
              outputString.append("Dialogue: 0,")
              .append(timeMatcher.group())
              .append("3D2,,")
              .append(String.format("%04d,", marginL2))
              .append(String.format("%04d,", offset - depth3D))
              .append(String.format("%04d,,", bottomSubsPosition))
              .append(text).append("\n");
            } else if (mode3D == Mode3D.SBSR) {
              outputString.append("Dialogue: 0,")
              .append(timeMatcher.group())
              .append("3D1,,")
              .append(String.format("%04d,", offset - depth3D))
              .append(String.format("%04d,", middleSbs - offset + depth3D))
              .append(String.format("%04d,,", bottomSubsPosition))
              .append(text).append("\n");
              outputString.append("Dialogue: 0,")
              .append(timeMatcher.group())
              .append("3D2,,")
              .append(String.format("%04d,", middleSbs - offset + depth3D))
              .append(String.format("%04d,", offset - depth3D))
              .append(String.format("%04d,,", bottomSubsPosition))
              .append(text).append("\n");
            }
          }
           
          output.write(outputString.toString());
        }
      }
    }

    LOGGER.debug("Subtitles converted to 3DASS format and stored in the file: " + outputSubs.getName());
    output.flush();
    output.close();
    tempSubs.deleteOnExit();
    return outputSubs;
  }

  public static String convertColorToAssHexFormat(Color color) {
    String colour = Integer.toHexString(color.getRGB());
    return "&H" + colour.substring(6, 8) + colour.substring(4, 6) + colour.substring(2, 4);
  }
}
TOP

Related Classes of net.pms.formats.v2.SubtitleUtils

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.