Package net.pms.dlna

Source Code of net.pms.dlna.DLNAMediaInfo

/*
* PS3 Media Server, for streaming any medias to your PS3.
* Copyright (C) 2008  A.Brochard
*
* 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.dlna;

import com.sun.jna.Platform;

import net.coobird.thumbnailator.Thumbnails;
import net.coobird.thumbnailator.Thumbnails.Builder;
import net.coobird.thumbnailator.tasks.UnsupportedFormatException;
import net.pms.PMS;
import net.pms.configuration.PmsConfiguration;
import net.pms.configuration.RendererConfiguration;
import net.pms.formats.AudioAsVideo;
import net.pms.formats.Format;
import net.pms.formats.v2.SubtitleType;
import net.pms.io.OutputParams;
import net.pms.io.ProcessWrapperImpl;
import net.pms.network.HTTPResource;
import net.pms.util.CoverUtil;
import net.pms.util.FileUtil;
import net.pms.util.MpegUtil;
import net.pms.util.ProcessUtil;

import org.apache.sanselan.ImageInfo;
import org.apache.sanselan.Sanselan;
import org.apache.sanselan.common.IImageMetadata;
import org.apache.sanselan.formats.jpeg.JpegImageMetadata;
import org.apache.sanselan.formats.tiff.TiffField;
import org.apache.sanselan.formats.tiff.constants.TiffConstants;
import org.jaudiotagger.audio.AudioFile;
import org.jaudiotagger.audio.AudioFileIO;
import org.jaudiotagger.audio.AudioHeader;
import org.jaudiotagger.tag.FieldKey;
import org.jaudiotagger.tag.Tag;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.imageio.IIOException;
import javax.imageio.ImageIO;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.*;
import java.util.List;

import static org.apache.commons.lang3.StringUtils.*;

/**
* This class keeps track of media file metadata scanned by the MediaInfo library.
*
* TODO: Change all instance variables to private. For backwards compatibility
* with external plugin code the variables have all been marked as deprecated
* instead of changed to private, but this will surely change in the future.
* When everything has been changed to private, the deprecated note can be
* removed.
*/
public class DLNAMediaInfo implements Cloneable {
  private static final Logger logger = LoggerFactory.getLogger(DLNAMediaInfo.class);
  private static final String THUMBNAIL_DIRECTORY_NAME = "thumbs";
  private static final PmsConfiguration configuration = PMS.getConfiguration();

  /**
   * XXX: Not sure why this magical number is relevant. It is only used in
   * constructs like <code>lowRange != DLNAMediaInfo.ENDFILE_POS</code>, but
   * dig deeper and you will find that <code>lowRange</code> is never set to
   * that specific value. So unless there is a (particular?) renderer that
   * uses this exact value when it does not want to receive any bytes, we
   * might be better off deleting this.
   */
  public static final long ENDFILE_POS = 99999475712L;

  /**
   * Maximum size of a stream, taking into account that some renderers (like
   * the PS3) will convert this <code>long</code> to <code>int</code>.
   * Truncating this value will still return the maximum value that an
   * <code>int</code> can contain.
   */
  public static final long TRANS_SIZE = Long.MAX_VALUE - Integer.MAX_VALUE - 1;
 
  // Stored in database
  private Double durationSec;

  private static final Map<String, Integer> audioChannelLayout = new HashMap<String, Integer>();

  // map ffmpeg's audio layout field to the corresponding number of channels
  // see: libavutil/channel_layout.c
  static {
    audioChannelLayout.put("mono", 1);
    audioChannelLayout.put("downmix", 2);
    audioChannelLayout.put("stereo", 2);
    audioChannelLayout.put("2.1", 3);
    audioChannelLayout.put("3.0", 3);
    audioChannelLayout.put("3.0(back)", 3);
    audioChannelLayout.put("4.0", 4);
    audioChannelLayout.put("quad", 4);
    audioChannelLayout.put("quad(side)", 4);
    audioChannelLayout.put("3.1", 4);
    audioChannelLayout.put("5.0", 5);
    audioChannelLayout.put("5.0(side)", 5);
    audioChannelLayout.put("4.1", 5);
    audioChannelLayout.put("5:1", 6);
    audioChannelLayout.put("5.1", 6);
    audioChannelLayout.put("5.1(side)", 6);
    audioChannelLayout.put("6.0", 6);
    audioChannelLayout.put("6.0(front)", 6);
    audioChannelLayout.put("hexagonal", 6);
    audioChannelLayout.put("6.1", 7);
    audioChannelLayout.put("6.1(front)", 7);
    audioChannelLayout.put("7.0", 7);
    audioChannelLayout.put("7.0(front)", 7);
    audioChannelLayout.put("7.1", 8);
    audioChannelLayout.put("7.1(wide)", 8);
    audioChannelLayout.put("octagonal", 8);
  }

  /**
   * @deprecated Use standard getter and setter to access this variable.
   */
  @Deprecated
  public int bitrate;

  /**
   * @deprecated Use standard getter and setter to access this variable.
   */
  @Deprecated
  public int width;

  /**
   * @deprecated Use standard getter and setter to access this variable.
   */
  @Deprecated
  public int height;

  /**
   * @deprecated Use standard getter and setter to access this variable.
   */
  @Deprecated
  public long size;

  /**
   * @deprecated Use standard getter and setter to access this variable.
   */
  @Deprecated
  public String codecV;

  /**
   * @deprecated Use standard getter and setter to access this variable.
   */
  @Deprecated
  public String frameRate;

  private String frameRateMode;

  /**
   * @deprecated Use standard getter and setter to access this variable.
   */
  @Deprecated
  public String aspect;

  public String aspectRatioContainer;
  public String aspectRatioVideoTrack;

  /**
   * @deprecated Use standard getter and setter to access this variable.
   */
  @Deprecated
  public byte thumb[];

  /**
   * @deprecated Use standard getter and setter to access this variable.
   */
  @Deprecated
  public String mimeType;

  /**
   * @deprecated Use standard getter and setter to access this variable.
   */
  @Deprecated
  public int bitsPerPixel;

  private byte referenceFrameCount = -1;
  private String avcLevel = null;

  private List<DLNAMediaAudio> audioTracks = new ArrayList<DLNAMediaAudio>();
  private List<DLNAMediaSubtitle> subtitleTracks = new ArrayList<DLNAMediaSubtitle>();

  /**
   * @deprecated Use standard getter and setter to access this variable.
   */
  @Deprecated
  public String model;

  /**
   * @deprecated Use standard getter and setter to access this variable.
   */
  @Deprecated
  public int exposure;

  /**
   * @deprecated Use standard getter and setter to access this variable.
   */
  @Deprecated
  public int orientation;

  /**
   * @deprecated Use standard getter and setter to access this variable.
   */
  @Deprecated
  public int iso;

  /**
   * @deprecated Use standard getter and setter to access this variable.
   */
  @Deprecated
  public String muxingMode;

  /**
   * @deprecated Use standard getter and setter to access this variable.
   */
  @Deprecated
  public String muxingModeAudio;

  /**
   * @deprecated Use standard getter and setter to access this variable.
   */
  @Deprecated
  public String container;

  /**
   * @deprecated Use {@link #getH264AnnexB()} and {@link #setH264AnnexB(byte[])} to access this variable.
   */
  @Deprecated
  public byte[] h264_annexB;

  /**
   * Not stored in database.
   * @deprecated Use standard getter and setter to access this variable.
   */
  @Deprecated
  public boolean mediaparsed;

  /**
   * isMediaParserV2 related, used to manage thumbnail management separated
   * from the main parsing process.
   * @deprecated Use standard getter and setter to access this variable.
   */
  @Deprecated
  public boolean thumbready;

  /**
   * @deprecated Use standard getter and setter to access this variable.
   */
  @Deprecated
  public int dvdtrack;

  /**
   * @deprecated Use standard getter and setter to access this variable.
   */
  @Deprecated
  public boolean secondaryFormatValid = true;

  /**
   * @deprecated Use standard getter and setter to access this variable.
   */
  @Deprecated
  public boolean parsing = false;

  private boolean ffmpeg_failure;
  private boolean ffmpeg_annexb_failure;
  private Map<String, String> extras;

  /**
   * @deprecated Use standard getter and setter to access this variable.
   */
  @Deprecated
  public boolean encrypted;

  /**
   * Used to determine whether tsMuxeR can mux the file instead of transcoding.
   * Also used by DLNAResource to help determine the DLNA.ORG_PN (file type)
   * value to send to the renderer, which is confusing.
   *
   * Some of this code is repeated in isVideoWithinH264LevelLimits(), and since
   * both functions are sometimes (but not always) used together, this is
   * not an efficient use of code.
   * TODO: Fix the above situation.
   * TODO: Now that FFmpeg is muxing without tsMuxeR, we should make a separate
   *       function for that, or even better, re-think this whole approach.
   */
  public boolean isMuxable(RendererConfiguration mediaRenderer) {
    boolean muxable = false;
    // Allow the formats in the following list
    if (
        getContainer() != null &&
            (
                getContainer().equals("mkv") ||
                    getContainer().equals("mp4") ||
                    getContainer().equals("mov") ||
                    getContainer().equals("ts") ||
                    getContainer().equals("m2ts")
            )
        ) {
      if (
          getCodecV() != null &&
              (
                  getCodecV().equals("h264") ||
                      getCodecV().equals("vc1") ||
                      getCodecV().equals("mpeg2")
              )
          ) {
        if (getFirstAudioTrack() != null) {
          String codecA;
          codecA = getFirstAudioTrack().getCodecA();
          if (
              codecA != null &&
                  (
                      codecA.equals("aac") ||
                          codecA.equals("ac3") ||
                          codecA.equals("dca") ||
                          codecA.equals("dts") ||
                          codecA.equals("eac3")
                  )
              ) {
            muxable = true;
          }
        }
      }
    }

    // Temporary fix: MediaInfo support will take care of this in the future
    // For now, http://ps3mediaserver.org/forum/viewtopic.php?f=11&t=6361&start=0
    // Bravia does not support AVC video at less than 288px high
    if (mediaRenderer.isBRAVIA() && getHeight() < 288) {
      muxable = false;
    }

    return muxable;
  }

  public Map<String, String> getExtras() {
    return extras;
  }

  public void putExtra(String key, String value) {
    if (extras == null) {
      extras = new HashMap<String, String>();
    }

    extras.put(key, value);
  }

  public String getExtrasAsString() {
    if (extras == null) {
      return null;
    }

    StringBuilder sb = new StringBuilder();

    for (Map.Entry<String, String> entry : extras.entrySet()) {
      sb.append(entry.getKey());
      sb.append("|");
      sb.append(entry.getValue());
      sb.append("|");
    }

    return sb.toString();
  }

  public void setExtrasAsString(String value) {
    if (value != null) {
      StringTokenizer st = new StringTokenizer(value, "|");

      while (st.hasMoreTokens()) {
        try {
          putExtra(st.nextToken(), st.nextToken());
        } catch (NoSuchElementException nsee) {
          logger.debug("Caught exception", nsee);
        }
      }
    }
  }

  public DLNAMediaInfo() {
    setThumbready(true); // this class manages thumbnails by default with the parser_v1 method
  }

  public void generateThumbnail(InputFile input, Format ext, int type) {
    DLNAMediaInfo forThumbnail = new DLNAMediaInfo();
    forThumbnail.durationSec = durationSec;
    forThumbnail.parse(input, ext, type, true);
    setThumb(forThumbnail.getThumb());
  }

  private ProcessWrapperImpl getFFmpegThumbnail(InputFile media) {
    String args[] = new String[14];
    args[0] = getFfmpegPath();
    boolean dvrms = media.getFile() != null && media.getFile().getAbsolutePath().toLowerCase().endsWith("dvr-ms");

    if (dvrms && isNotBlank(configuration.getFfmpegAlternativePath())) {
      args[0] = configuration.getFfmpegAlternativePath();
    }

    args[1] = "-ss";
    args[2] = "" + configuration.getThumbnailSeekPos();
    args[3] = "-i";

    if (media.getFile() != null) {
      args[4] = ProcessUtil.getShortFileNameIfWideChars(media.getFile().getAbsolutePath());
    } else {
      args[4] = "-";
    }

    args[5] = "-an";
    args[6] = "-an";
    args[7] = "-s";
    args[8] = "320x180";
    args[9] = "-vframes";
    args[10] = "1";
    args[11] = "-f";
    args[12] = "image2";
    args[13] = "pipe:";

    // FIXME MPlayer should not be used if thumbnail generation is disabled (and it should be disabled in the GUI)
    if (!configuration.isThumbnailGenerationEnabled() || (configuration.isUseMplayerForVideoThumbs() && !dvrms)) {
      args[2] = "0";
      for (int i = 5; i <= 13; i++) {
        args[i] = "-an";
      }
    }

    OutputParams params = new OutputParams(configuration);
    params.maxBufferSize = 1;
    params.stdin = media.getPush();
    params.noexitcheck = true; // not serious if anything happens during the thumbnailer

    // true: consume stderr on behalf of the caller i.e. parse()
    final ProcessWrapperImpl pw = new ProcessWrapperImpl(args, params, false, true);

    // FAILSAFE
    setParsing(true);
    Runnable r = new Runnable() {
      @Override
      public void run() {
        try {
          Thread.sleep(10000);
          ffmpeg_failure = true;
        } catch (InterruptedException e) { }

        pw.stopProcess();
        setParsing(false);
      }
    };

    Thread failsafe = new Thread(r, "FFmpeg Thumbnail Failsafe");
    failsafe.start();
    pw.runInSameThread();
    setParsing(false);
    return pw;
  }

  private ProcessWrapperImpl getMplayerThumbnail(InputFile media) throws IOException {
    String args[] = new String[14];
    args[0] = configuration.getMplayerPath();
    args[1] = "-ss";
    boolean toolong = getDurationInSeconds() < configuration.getThumbnailSeekPos();
    args[2] = "" + (toolong ? (getDurationInSeconds() / 2) : configuration.getThumbnailSeekPos());
    args[3] = "-quiet";

    if (media.getFile() != null) {
      args[4] = ProcessUtil.getShortFileNameIfWideChars(media.getFile().getAbsolutePath());
    } else {
      args[4] = "-";
    }

    args[5] = "-msglevel";
    args[6] = "all=4";
    args[7] = "-vf";
    args[8] = "scale=320:-2,expand=:180";
    args[9] = "-frames";
    args[10] = "1";
    args[11] = "-vo";
    String frameName = "" + media.hashCode();
    frameName = "mplayer_thumbs:subdirs=\"" + frameName + "\"";
    frameName = frameName.replace(',', '_');
    args[12] = "jpeg:outdir=" + frameName;
    args[13] = "-nosound";
    OutputParams params = new OutputParams(configuration);
    params.workDir = configuration.getTempFolder();
    params.maxBufferSize = 1;
    params.stdin = media.getPush();
    params.log = true;
    params.noexitcheck = true; // not serious if anything happens during the thumbnailer
    final ProcessWrapperImpl pw = new ProcessWrapperImpl(args, params);

    // FAILSAFE
    setParsing(true);
    Runnable r = new Runnable() {
      @Override
      public void run() {
        try {
          Thread.sleep(3000);
          //mplayer_thumb_failure = true;
        } catch (InterruptedException e) { }

        pw.stopProcess();
        setParsing(false);
      }
    };

    Thread failsafe = new Thread(r, "MPlayer Thumbnail Failsafe");
    failsafe.start();
    pw.runInSameThread();
    setParsing(false);
    return pw;
  }

  private String getFfmpegPath() {
    String value = configuration.getFfmpegPath();

    if (value == null) {
      logger.info("No FFmpeg - unable to thumbnail");
      throw new RuntimeException("No FFmpeg - unable to thumbnail");
    } else {
      return value;
    }
  }

  public void parse(InputFile inputFile, Format ext, int type, boolean thumbOnly) {
    int i = 0;

    while (isParsing()) {
      if (i == 5) {
        setMediaparsed(true);
        break;
      }

      try {
        Thread.sleep(1000);
      } catch (InterruptedException e) { }

      i++;
    }

    if (isMediaparsed()) {
      return;
    }

    if (inputFile != null) {
      if (inputFile.getFile() != null) {
        setSize(inputFile.getFile().length());
      } else {
        setSize(inputFile.getSize());
      }

      ProcessWrapperImpl pw = null;
      boolean ffmpeg_parsing = true;

      if (type == Format.AUDIO || ext instanceof AudioAsVideo) {
        ffmpeg_parsing = false;
        DLNAMediaAudio audio = new DLNAMediaAudio();

        if (inputFile.getFile() != null) {
          try {
            AudioFile af = AudioFileIO.read(inputFile.getFile());
            AudioHeader ah = af.getAudioHeader();

            if (ah != null && !thumbOnly) {
              int length = ah.getTrackLength();
              int rate = ah.getSampleRateAsNumber();

              if (ah.getEncodingType().toLowerCase().contains("flac 24")) {
                audio.setBitsperSample(24);
              }

              audio.setSampleFrequency("" + rate);
              setDuration((double) length);
              setBitrate((int) ah.getBitRateAsNumber());
              audio.getAudioProperties().setNumberOfChannels(2);

              if (ah.getChannels() != null && ah.getChannels().toLowerCase().contains("mono")) {
                audio.getAudioProperties().setNumberOfChannels(1);
              } else if (ah.getChannels() != null && ah.getChannels().toLowerCase().contains("stereo")) {
                audio.getAudioProperties().setNumberOfChannels(2);
              } else if (ah.getChannels() != null) {
                audio.getAudioProperties().setNumberOfChannels(Integer.parseInt(ah.getChannels()));
              }

              audio.setCodecA(ah.getEncodingType().toLowerCase());

              if (audio.getCodecA().contains("(windows media")) {
                audio.setCodecA(audio.getCodecA().substring(0, audio.getCodecA().indexOf("(windows media")).trim());
              }
            }

            Tag t = af.getTag();

            if (t != null) {
              if (t.getArtworkList().size() > 0) {
                setThumb(t.getArtworkList().get(0).getBinaryData());
              } else {
                if (configuration.getAudioThumbnailMethod() > 0) {
                  setThumb(
                    CoverUtil.get().getThumbnailFromArtistAlbum(
                      configuration.getAudioThumbnailMethod() == 1 ?
                        CoverUtil.AUDIO_AMAZON :
                        CoverUtil.AUDIO_DISCOGS,
                      audio.getArtist(), audio.getAlbum()
                    )
                  );
                }
              }

              if (!thumbOnly) {
                audio.setAlbum(t.getFirst(FieldKey.ALBUM));
                audio.setArtist(t.getFirst(FieldKey.ARTIST));
                audio.setSongname(t.getFirst(FieldKey.TITLE));
                String y = t.getFirst(FieldKey.YEAR);

                try {
                  if (y.length() > 4) {
                    y = y.substring(0, 4);
                  }
                  audio.setYear(Integer.parseInt(((y != null && y.length() > 0) ? y : "0")));
                  y = t.getFirst(FieldKey.TRACK);
                  audio.setTrack(Integer.parseInt(((y != null && y.length() > 0) ? y : "1")));
                  audio.setGenre(t.getFirst(FieldKey.GENRE));
                } catch (Throwable e) {
                  logger.debug("Error parsing unimportant metadata: " + e.getMessage());
                }
              }
            }
          } catch (Throwable e) {
            logger.debug("Error parsing audio file: {} - {}", e.getMessage(), e.getCause() != null ? e.getCause().getMessage() : "");
            ffmpeg_parsing = false;
          }

          if (audio.getSongname() == null || audio.getSongname().length() == 0) {
            audio.setSongname(inputFile.getFile().getName());
          }

          if (!ffmpeg_parsing) {
            getAudioTracksList().add(audio);
          }
        }
      }

      if (type == Format.IMAGE && inputFile.getFile() != null) {
        try {
          ffmpeg_parsing = false;
          ImageInfo info = Sanselan.getImageInfo(inputFile.getFile());
          setWidth(info.getWidth());
          setHeight(info.getHeight());
          setBitsPerPixel(info.getBitsPerPixel());
          String formatName = info.getFormatName();

          if (formatName.startsWith("JPEG")) {
            setCodecV("jpg");
            IImageMetadata meta = Sanselan.getMetadata(inputFile.getFile());

            if (meta != null && meta instanceof JpegImageMetadata) {
              JpegImageMetadata jpegmeta = (JpegImageMetadata) meta;
              TiffField tf = jpegmeta.findEXIFValue(TiffConstants.EXIF_TAG_MODEL);

              if (tf != null) {
                setModel(tf.getStringValue().trim());
              }

              tf = jpegmeta.findEXIFValue(TiffConstants.EXIF_TAG_EXPOSURE_TIME);
              if (tf != null) {
                setExposure((int) (1000 * tf.getDoubleValue()));
              }

              tf = jpegmeta.findEXIFValue(TiffConstants.EXIF_TAG_ORIENTATION);
              if (tf != null) {
                setOrientation(tf.getIntValue());
              }

              tf = jpegmeta.findEXIFValue(TiffConstants.EXIF_TAG_ISO);
              if (tf != null) {
                // Galaxy Nexus jpg pictures may contain multiple values, take the first
                int[] isoValues = tf.getIntArrayValue();
                setIso(isoValues[0]);
              }
            }
          } else if (formatName.startsWith("PNG")) {
            setCodecV("png");
          } else if (formatName.startsWith("GIF")) {
            setCodecV("gif");
          } else if (formatName.startsWith("TIF")) {
            setCodecV("tiff");
          }

          setContainer(getCodecV());
        } catch (Throwable e) {
          logger.info("Error parsing image ({}) with Sanselan, switching to FFmpeg.", inputFile.getFile().getAbsolutePath());
        }
      }

      if (configuration.getImageThumbnailsEnabled() && type != Format.VIDEO && type != Format.AUDIO) {
        try {
          File thumbDir = new File(configuration.getTempFolder(), THUMBNAIL_DIRECTORY_NAME);

          logger.trace("Generating thumbnail for: {}", inputFile.getFile().getAbsolutePath());

          if (!thumbDir.exists() && !thumbDir.mkdirs()) {
            logger.warn("Could not create thumbnail directory: {}", thumbDir.getAbsolutePath());
          } else {
            File thumbFile = new File(thumbDir, inputFile.getFile().getName() + ".jpg");
            String thumbFilename = thumbFile.getAbsolutePath();

            logger.trace("Creating (temporary) thumbnail: {}", thumbFilename);

            // Create the thumbnail image using the Thumbnailator library
            final Builder<File> thumbnail = Thumbnails.of(inputFile.getFile());
            thumbnail.size(320, 180);
            thumbnail.outputFormat("jpg");
            thumbnail.outputQuality(1.0f);

            try {
              thumbnail.toFile(thumbFilename);
            } catch (IIOException e) {
              logger.debug("Error generating thumbnail for: " + inputFile.getFile().getName());
              logger.debug("The full error was: " + e);
            }

            File jpg = new File(thumbFilename);

            if (jpg.exists()) {
              InputStream is = new FileInputStream(jpg);
              int sz = is.available();

              if (sz > 0) {
                setThumb(new byte[sz]);
                is.read(getThumb());
              }

              is.close();

              if (!jpg.delete()) {
                jpg.deleteOnExit();
              }
            }
          }
        } catch (UnsupportedFormatException ufe) {
          logger.debug("Thumbnailator does not support the format of {}: {}", inputFile.getFile().getAbsolutePath(), ufe.getMessage());
        } catch (Exception e) {
          logger.debug("Thumbnailator could not generate a thumbnail for: {}", inputFile.getFile().getAbsolutePath(), e);
        }
      }

      if (ffmpeg_parsing) {
        if (!thumbOnly || !configuration.isUseMplayerForVideoThumbs()) {
          pw = getFFmpegThumbnail(inputFile);
        }

        String input = "-";
        boolean dvrms = false;

        if (inputFile.getFile() != null) {
          input = ProcessUtil.getShortFileNameIfWideChars(inputFile.getFile().getAbsolutePath());
          dvrms = inputFile.getFile().getAbsolutePath().toLowerCase().endsWith("dvr-ms");
        }

        if (!ffmpeg_failure && !thumbOnly) {
          if (input.equals("-")) {
            input = "pipe:";
          }

          boolean matchs = false;
          ArrayList<String> lines = (ArrayList<String>) pw.getResults();
          int langId = 0;
          int subId = 0;
          ListIterator<String> FFmpegMetaData = lines.listIterator();

          for (String line : lines) {
            FFmpegMetaData.next();
            line = line.trim();
            if (line.startsWith("Output")) {
              matchs = false;
            } else if (line.startsWith("Input")) {
              if (line.indexOf(input) > -1) {
                matchs = true;
                setContainer(line.substring(10, line.indexOf(",", 11)).trim());
              } else {
                matchs = false;
              }
            } else if (matchs) {
              if (line.indexOf("Duration") > -1) {
                StringTokenizer st = new StringTokenizer(line, ",");
                while (st.hasMoreTokens()) {
                  String token = st.nextToken().trim();
                  if (token.startsWith("Duration: ")) {
                    String durationStr = token.substring(10);
                    int l = durationStr.substring(durationStr.indexOf(".") + 1).length();
                    if (l < 4) {
                      durationStr = durationStr + "00".substring(0, 3 - l);
                    }
                    if (durationStr.indexOf("N/A") > -1) {
                      setDuration(null);
                    } else {
                      setDuration(parseDurationString(durationStr));
                    }
                  } else if (token.startsWith("bitrate: ")) {
                    String bitr = token.substring(9);
                    int spacepos = bitr.indexOf(" ");
                    if (spacepos > -1) {
                      String value = bitr.substring(0, spacepos);
                      String unit = bitr.substring(spacepos + 1);
                      setBitrate(Integer.parseInt(value));
                      if (unit.equals("kb/s")) {
                        setBitrate(1024 * getBitrate());
                      }
                      if (unit.equals("mb/s")) {
                        setBitrate(1048576 * getBitrate());
                      }
                    }
                  }
                }
              } else if (line.indexOf("Audio:") > -1) {
                StringTokenizer st = new StringTokenizer(line, ",");
                int a = line.indexOf("(");
                int b = line.indexOf("):", a);
                DLNAMediaAudio audio = new DLNAMediaAudio();
                audio.setId(langId++);
                if (a > -1 && b > a) {
                  audio.setLang(line.substring(a + 1, b));
                } else {
                  audio.setLang(DLNAMediaLang.UND);
                }

                // Get TS IDs
                a = line.indexOf("[0x");
                b = line.indexOf("]", a);
                if (a > -1 && b > a + 3) {
                  String idString = line.substring(a + 3, b);
                  try {
                    audio.setId(Integer.parseInt(idString, 16));
                  } catch (NumberFormatException nfe) {
                    logger.debug("Error parsing Stream ID: " + idString);
                  }
                }

                while (st.hasMoreTokens()) {
                  String token = st.nextToken().trim();
                  Integer nChannels;

                  if (token.startsWith("Stream")) {
                    audio.setCodecA(token.substring(token.indexOf("Audio: ") + 7));
                  } else if (token.endsWith("Hz")) {
                    audio.setSampleFrequency(token.substring(0, token.indexOf("Hz")).trim());
                  } else if ((nChannels = audioChannelLayout.get(token)) != null) {
                    audio.getAudioProperties().setNumberOfChannels(nChannels);
                  } else if (token.matches("\\d+(?:\\s+channels?)")) { // implicitly anchored at both ends e.g. ^ ... $
                    // setNumberOfChannels(String) parses the number out of the string
                    audio.getAudioProperties().setNumberOfChannels(token);
                  } else if (token.equals("s32")) {
                    audio.setBitsperSample(32);
                  } else if (token.equals("s24")) {
                    audio.setBitsperSample(24);
                  } else if (token.equals("s16")) {
                    audio.setBitsperSample(16);
                  }
                }
                int FFmpegMetaDataNr = FFmpegMetaData.nextIndex();

                if (FFmpegMetaDataNr > -1) {
                  line = lines.get(FFmpegMetaDataNr);
                }

                if (line.indexOf("Metadata:") > -1) {
                  FFmpegMetaDataNr = FFmpegMetaDataNr + 1;
                  line = lines.get(FFmpegMetaDataNr);
                  while (line.indexOf("      ") == 0) {
                    if (line.toLowerCase().indexOf("title           :") > -1) {
                      int aa = line.indexOf(": ");
                      int bb = line.length();
                      if (aa > -1 && bb > aa) {
                        audio.setFlavor(line.substring(aa+2, bb));
                        break;
                      }
                    } else {
                      FFmpegMetaDataNr = FFmpegMetaDataNr + 1;
                      line = lines.get(FFmpegMetaDataNr);
                    }
                  }
                }

                getAudioTracksList().add(audio);
              } else if (line.indexOf("Video:") > -1) {
                StringTokenizer st = new StringTokenizer(line, ",");
                while (st.hasMoreTokens()) {
                  String token = st.nextToken().trim();
                  if (token.startsWith("Stream")) {
                    setCodecV(token.substring(token.indexOf("Video: ") + 7));
                  } else if ((token.indexOf("tbc") > -1 || token.indexOf("tb(c)") > -1)) {
                    // A/V sync issues with newest FFmpeg, due to the new tbr/tbn/tbc outputs
                    // Priority to tb(c)
                    String frameRateDoubleString = token.substring(0, token.indexOf("tb")).trim();
                    try {
                      if (!frameRateDoubleString.equals(getFrameRate())) {// tbc taken into account only if different than tbr
                        Double frameRateDouble = Double.parseDouble(frameRateDoubleString);
                        setFrameRate(String.format(Locale.ENGLISH, "%.2f", frameRateDouble / 2));
                      }
                    } catch (NumberFormatException nfe) {
                      // Could happen if tbc is "1k" or something like that, no big deal
                      logger.debug("Could not parse frame rate \"" + frameRateDoubleString + "\"");
                    }

                  } else if ((token.indexOf("tbr") > -1 || token.indexOf("tb(r)") > -1) && getFrameRate() == null) {
                    setFrameRate(token.substring(0, token.indexOf("tb")).trim());
                  } else if ((token.indexOf("fps") > -1 || token.indexOf("fps(r)") > -1) && getFrameRate() == null) { // dvr-ms ?
                    setFrameRate(token.substring(0, token.indexOf("fps")).trim());
                  } else if (token.indexOf("x") > -1) {
                    String resolution = token.trim();
                    if (resolution.indexOf(" [") > -1) {
                      resolution = resolution.substring(0, resolution.indexOf(" ["));
                    }
                    try {
                      setWidth(Integer.parseInt(resolution.substring(0, resolution.indexOf("x"))));
                    } catch (NumberFormatException nfe) {
                      logger.debug("Could not parse width from \"" + resolution.substring(0, resolution.indexOf("x")) + "\"");
                    }
                    try {
                      setHeight(Integer.parseInt(resolution.substring(resolution.indexOf("x") + 1)));
                    } catch (NumberFormatException nfe) {
                      logger.debug("Could not parse height from \"" + resolution.substring(resolution.indexOf("x") + 1) + "\"");
                    }
                  }
                }
              } else if (line.indexOf("Subtitle:") > -1 && !line.contains("tx3g")) {
                DLNAMediaSubtitle lang = new DLNAMediaSubtitle();
                lang.setType((line.contains("dvdsub") && Platform.isWindows() ? SubtitleType.VOBSUB : SubtitleType.UNKNOWN));
                int a = line.indexOf("(");
                int b = line.indexOf("):", a);
                if (a > -1 && b > a) {
                  lang.setLang(line.substring(a + 1, b));
                } else {
                  lang.setLang(DLNAMediaLang.UND);
                }

                lang.setId(subId++);
                int FFmpegMetaDataNr = FFmpegMetaData.nextIndex();

                if (FFmpegMetaDataNr > -1) {
                  line = lines.get(FFmpegMetaDataNr);
                }

                if (line.indexOf("Metadata:") > -1) {
                  FFmpegMetaDataNr = FFmpegMetaDataNr + 1;
                  line = lines.get(FFmpegMetaDataNr);

                  while (line.indexOf("      ") == 0) {
                    if (line.toLowerCase().indexOf("title           :") > -1) {
                      int aa = line.indexOf(": ");
                      int bb = line.length();
                      if (aa > -1 && bb > aa) {
                        lang.setFlavor(line.substring(aa+2, bb));
                        break;
                      }
                    } else {
                      FFmpegMetaDataNr = FFmpegMetaDataNr + 1;
                      line = lines.get(FFmpegMetaDataNr);
                    }
                  }
                }
                getSubtitleTracksList().add(lang);
              }
            }
          }
        }

        if (!thumbOnly
            && getContainer() != null
            && inputFile.getFile() != null
            && getContainer().equals("mpegts")
            && isH264()
            && getDurationInSeconds() == 0) {
          // Parse the duration
          try {
            int length = MpegUtil.getDurationFromMpeg(inputFile.getFile());
            if (length > 0) {
              setDuration((double) length);
            }
          } catch (IOException e) {
            logger.trace("Error retrieving length: " + e.getMessage());
          }
        }

        if (configuration.isUseMplayerForVideoThumbs() && type == Format.VIDEO && !dvrms) {
          try {
            getMplayerThumbnail(inputFile);
            String frameName = "" + inputFile.hashCode();
            frameName = configuration.getTempFolder() + "/mplayer_thumbs/" + frameName + "00000001/00000001.jpg";
            frameName = frameName.replace(',', '_');
            File jpg = new File(frameName);

            if (jpg.exists()) {
              InputStream is = new FileInputStream(jpg);
              int sz = is.available();

              if (sz > 0) {
                setThumb(new byte[sz]);
                is.read(getThumb());
              }

              is.close();

              if (!jpg.delete()) {
                jpg.deleteOnExit();
              }

              // Try and retry
              if (!jpg.getParentFile().delete() && !jpg.getParentFile().delete()) {
                logger.debug("Failed to delete \"" + jpg.getParentFile().getAbsolutePath() + "\"");
              }
            }
          } catch (IOException e) {
            logger.debug("Caught exception", e);
          }
        }

        if (type == Format.VIDEO && pw != null && getThumb() == null) {
          InputStream is;
          try {
            is = pw.getInputStream(0);
            int sz = is.available();
            if (sz > 0) {
              setThumb(new byte[sz]);
              is.read(getThumb());
            }
            is.close();

            if (sz > 0 && !net.pms.PMS.isHeadless()) {
              BufferedImage image = ImageIO.read(new ByteArrayInputStream(getThumb()));
              if (image != null) {
                Graphics g = image.getGraphics();
                g.setColor(Color.WHITE);
                g.setFont(new Font("Arial", Font.PLAIN, 14));
                int low = 0;
                if (getWidth() > 0) {
                  if (getWidth() == 1920 || getWidth() == 1440) {
                    g.drawString("1080p", 0, low += 18);
                  } else if (getWidth() == 1280) {
                    g.drawString("720p", 0, low += 18);
                  }
                }
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                ImageIO.write(image, "jpeg", out);
                setThumb(out.toByteArray());
              }
            }
          } catch (IOException e) {
            logger.debug("Error while decoding thumbnail: " + e.getMessage());
          }
        }
      }

      finalize(type, inputFile);
      setMediaparsed(true);
    }
  }

  public boolean isH264() {
    return getCodecV() != null && getCodecV().contains("264");
  }

  public int getFrameNumbers() {
    double fr = Double.parseDouble(getFrameRate());
    return (int) (getDurationInSeconds() * fr);
  }

  public void setDuration(Double d) {
    this.durationSec = d;
  }

  public Double getDuration() {
    return durationSec;
  }

  /**
   * @return 0 if nothing is specified, otherwise the duration
   */
  public double getDurationInSeconds() {
    return durationSec != null ? durationSec : 0;
  }

  public String getDurationString() {
    return durationSec != null ? getDurationString(durationSec) : null;
  }

  public static String getDurationString(double d) {
    int s = ((int) d) % 60;
    int h = (int) (d / 3600);
    int m = ((int) (d / 60)) % 60;
    return String.format("%02d:%02d:%02d.00", h, m, s);
  }

  public static Double parseDurationString(String duration) {
    if (duration == null) {
      return null;
    }

    StringTokenizer st = new StringTokenizer(duration, ":");

    try {
      int h = Integer.parseInt(st.nextToken());
      int m = Integer.parseInt(st.nextToken());
      double s = Double.parseDouble(st.nextToken());
      return h * 3600 + m * 60 + s;
    } catch (NumberFormatException nfe) {
      logger.debug("Failed to parse duration \"" + duration + "\"");
    }

    return null;
  }

  public void finalize(int type, InputFile f) {
    String codecA = null;

    if (getFirstAudioTrack() != null) {
      codecA = getFirstAudioTrack().getCodecA();
    }

    if (getContainer() != null && getContainer().equals("avi")) {
      setMimeType(HTTPResource.AVI_TYPEMIME);
    } else if (getContainer() != null && (getContainer().equals("asf") || getContainer().equals("wmv"))) {
      setMimeType(HTTPResource.WMV_TYPEMIME);
    } else if (getContainer() != null && (getContainer().equals("matroska") || getContainer().equals("mkv"))) {
      setMimeType(HTTPResource.MATROSKA_TYPEMIME);
    } else if (getCodecV() != null && getCodecV().equals("mjpeg")) {
      setMimeType(HTTPResource.JPEG_TYPEMIME);
    } else if ("png".equals(getCodecV()) || "png".equals(getContainer())) {
      setMimeType(HTTPResource.PNG_TYPEMIME);
    } else if ("gif".equals(getCodecV()) || "gif".equals(getContainer())) {
      setMimeType(HTTPResource.GIF_TYPEMIME);
    } else if (getCodecV() != null && (getCodecV().equals("h264") || getCodecV().equals("h263") || getCodecV().toLowerCase().equals("mpeg4") || getCodecV().toLowerCase().equals("mp4"))) {
      setMimeType(HTTPResource.MP4_TYPEMIME);
    } else if (getCodecV() != null && (getCodecV().indexOf("mpeg") > -1 || getCodecV().indexOf("mpg") > -1)) {
      setMimeType(HTTPResource.MPEG_TYPEMIME);
    } else if (getCodecV() == null && codecA != null && codecA.contains("mp3")) {
      setMimeType(HTTPResource.AUDIO_MP3_TYPEMIME);
    } else if (getCodecV() == null && contains(codecA, "aac")) {
      setMimeType(HTTPResource.AUDIO_MP4_TYPEMIME);
    } else if (getCodecV() == null && contains(codecA, "flac")) {
      setMimeType(HTTPResource.AUDIO_FLAC_TYPEMIME);
    } else if (getCodecV() == null && contains(codecA, "vorbis")) {
      setMimeType(HTTPResource.AUDIO_OGG_TYPEMIME);
    } else if (getCodecV() == null && (contains(codecA, "asf") || startsWith(codecA, "wm"))) {
      setMimeType(HTTPResource.AUDIO_WMA_TYPEMIME);
    } else if (getCodecV() == null && (contains(codecA, "wav") || startsWith(codecA, "pcm"))) {
      setMimeType(HTTPResource.AUDIO_WAV_TYPEMIME);
    } else {
      setMimeType(HTTPResource.getDefaultMimeType(type));
    }

    if (getFirstAudioTrack() == null || !(type == Format.AUDIO && getFirstAudioTrack().getBitsperSample() == 24 && getFirstAudioTrack().getSampleRate() > 48000)) {
      setSecondaryFormatValid(false);
    }

    // Check for external subs here
    if (f.getFile() != null && type == Format.VIDEO && configuration.isAutoloadExternalSubtitles()) {
      FileUtil.isSubtitlesExists(f.getFile(), this);
    }
  }

  /**
   * Checks whether the video has too many reference frames per pixels for the renderer
   * TODO move to PlayerUtil
   */
  public synchronized boolean isVideoWithinH264LevelLimits(InputFile f, RendererConfiguration mediaRenderer) {
    if ("h264".equals(getCodecV())) {
      if (getReferenceFrameCount() > -1) {
        logger.debug("H.264 file: {} level {} / ref frames {}", f.getFilename(), defaultString(getAvcLevel(), "N/A"), getReferenceFrameCount());
       
        // shagrath : for tsmuxer to work natively with h264, we still need to retrieve some AVC header informations to emulate the h264_mp4toannexb filter in mencoder (via the H264InputStream class)
        // I would love for mencoder to support it natively but I don't think it will ever happens
        byte headers[][] = getAnnexBFrameHeader(f);
        if (ffmpeg_annexb_failure) {
          logger.info("Error parsing information from the file: " + f.getFilename());
        } else
          setH264AnnexB(headers[1]);

        if (("4.1".equals(getAvcLevel())
            || "4.2".equals(getAvcLevel())
            || "5".equals(getAvcLevel())
            || "5.0".equals(getAvcLevel())
            || "5.1".equals(getAvcLevel())
            || "5.2".equals(getAvcLevel()))
            && getWidth() > 0
            && getHeight() > 0) {

          int maxref;
          if (mediaRenderer == null || mediaRenderer.isPS3()) {
            /**
             * 2013-01-25: Confirmed maximum reference frames on PS3:
             *    - 4 for 1920x1080
             *    - 11 for 1280x720
             * Meaning this math is correct
             */
            maxref = (int) Math.floor(10252743 / (getWidth() * getHeight()));
          } else {
            /**
             * This is the math for level 4.1, which results in:
             *    - 4 for 1920x1080
             *    - 9 for 1280x720
             */
            maxref = (int) Math.floor(8388608 / (getWidth() * getHeight()));
          }
          if (getReferenceFrameCount() > maxref) {
            logger.info("H.264 file ({}) is not compatible with this renderer because it can only take {} reference frames at this resolution while this file has {} reference frames.", f.getFilename(), maxref, getReferenceFrameCount());
            return false;
          }
        }
        return true;
      } else {
        logger.warn("H.264 file ({}): Unparsed reference frame count. Remuxing may not work.", f.getFilename());
        return true;
      }
    } else {
      logger.debug("Non-H.264 file ({}): Do not check ref limits.", f.getFilename());
      return true;
    }
  }

  public boolean isMuxable(String filename, String codecA) {
    return codecA != null && (codecA.startsWith("dts") || codecA.equals("dca"));
  }

  public boolean isLossless(String codecA) {
    return codecA != null && (codecA.contains("pcm") || codecA.startsWith("dts") || codecA.equals("dca") || codecA.contains("flac")) && !codecA.contains("pcm_u8") && !codecA.contains("pcm_s8");
  }

  @Override
  public String toString() {
    StringBuilder result = new StringBuilder();
    result.append("container: ");
    result.append(getContainer());
    result.append(", bitrate: ");
    result.append(getBitrate());
    result.append(", size: ");
    result.append(getSize());
    result.append(", video codec: ");
    result.append(getCodecV());
    result.append(", duration: ");
    result.append(getDurationString());
    result.append(", width: ");
    result.append(getWidth());
    result.append(", height: ");
    result.append(getHeight());
    result.append(", frame rate: ");
    result.append(getFrameRate());

    if (getThumb() != null) {
      result.append(", thumb size : ");
      result.append(getThumb().length);
    }

    result.append(", muxing mode: ");
    result.append(getMuxingMode());
    result.append(", mime type: ");
    result.append(getMimeType());

    for (DLNAMediaAudio audio : getAudioTracksList()) {
      result.append("\n\tAudio track ");
      result.append(audio.toString());
    }

    for (DLNAMediaSubtitle sub : getSubtitleTracksList()) {
      result.append("\n\tSubtitle track ");
      result.append(sub.toString());
    }

    return result.toString();
  }

  public InputStream getThumbnailInputStream() {
    return new ByteArrayInputStream(getThumb());
  }

  public String getValidFps(boolean ratios) {
    String validFrameRate = null;

    if (getFrameRate() != null && getFrameRate().length() > 0) {
      try {
        double fr = Double.parseDouble(getFrameRate().replace(',', '.'));

        if (fr >= 14.99 && fr < 15.1) {
          validFrameRate = "15";
        } else if (fr > 23.9 && fr < 23.99) {
          validFrameRate = ratios ? "24000/1001" : "23.976";
        } else if (fr > 23.99 && fr < 24.1) {
          validFrameRate = "24";
        } else if (fr >= 24.99 && fr < 25.1) {
          validFrameRate = "25";
        } else if (fr > 29.9 && fr < 29.99) {
          validFrameRate = ratios ? "30000/1001" : "29.97";
        } else if (fr >= 29.99 && fr < 30.1) {
          validFrameRate = "30";
        } else if (fr > 47.9 && fr < 47.99) {
          validFrameRate = ratios ? "48000/1001" : "47.952";
        } else if (fr > 49.9 && fr < 50.1) {
          validFrameRate = "50";
        } else if (fr > 59.8 && fr < 59.99) {
          validFrameRate = ratios ? "60000/1001" : "59.94";
        } else if (fr >= 59.99 && fr < 60.1) {
          validFrameRate = "60";
        }
      } catch (NumberFormatException nfe) {
        logger.error(null, nfe);
      }
    }

    return validFrameRate;
  }

  public DLNAMediaAudio getFirstAudioTrack() {
    if (getAudioTracksList().size() > 0) {
      return getAudioTracksList().get(0);
    }
    return null;
  }

  public String getValidAspect(boolean ratios) {
    String a = null;

    if (getAspect() != null) {
      double ar = Double.parseDouble(getAspect());

      if (ar > 1.7 && ar < 1.8) {
        a = ratios ? "16/9" : "1.777777777777777";
      }

      if (ar > 1.3 && ar < 1.4) {
        a = ratios ? "4/3" : "1.333333333333333";
      }
    }

    return a;
  }

  public String getResolution() {
    if (getWidth() > 0 && getHeight() > 0) {
      return getWidth() + "x" + getHeight();
    }

    return null;
  }

  public int getRealVideoBitrate() {
    if (getBitrate() > 0) {
      return (getBitrate() / 8);
    }

    int realBitrate = 10000000;

    if (getDurationInSeconds() != 0) {
      realBitrate = (int) (getSize() / getDurationInSeconds());
    }

    return realBitrate;
  }

  public boolean isHDVideo() {
    return (getWidth() > 1200 || getHeight() > 700);
  }

  public boolean isMpegTS() {
    return getContainer() != null && getContainer().equals("mpegts");
  }

  public byte[][] getAnnexBFrameHeader(InputFile f) {
    String[] cmdArray = new String[14];
    cmdArray[0] = configuration.getFfmpegPath();
    cmdArray[1] = "-i";

    if (f.getPush() == null && f.getFilename() != null) {
      cmdArray[2] = f.getFilename();
    } else {
      cmdArray[2] = "-";
    }

    cmdArray[3] = "-vframes";
    cmdArray[4] = "1";
    cmdArray[5] = "-vcodec";
    cmdArray[6] = "copy";
    cmdArray[7] = "-f";
    cmdArray[8] = "h264";
    cmdArray[9] = "-vbsf";
    cmdArray[10] = "h264_mp4toannexb";
    cmdArray[11] = "-an";
    cmdArray[12] = "-y";
    cmdArray[13] = "pipe:";

    byte[][] returnData = new byte[2][];
    OutputParams params = new OutputParams(configuration);
    params.maxBufferSize = 1;
    params.stdin = f.getPush();

    final ProcessWrapperImpl pw = new ProcessWrapperImpl(cmdArray, params);

    Runnable r = new Runnable() {
      @Override
      public void run() {
        try {
          Thread.sleep(3000);
          ffmpeg_annexb_failure = true;
        } catch (InterruptedException e) { }
        pw.stopProcess();
      }
    };

    Thread failsafe = new Thread(r, "FFmpeg AnnexB Frame Header Failsafe");
    failsafe.start();
    pw.runInSameThread();

    if (ffmpeg_annexb_failure) {
      return null;
    }

    InputStream is;
    ByteArrayOutputStream baot = new ByteArrayOutputStream();

    try {
      is = pw.getInputStream(0);
      byte b[] = new byte[4096];
      int n;

      while ((n = is.read(b)) > 0) {
        baot.write(b, 0, n);
      }

      byte data[] = baot.toByteArray();
      baot.close();
      returnData[0] = data;
      is.close();
      int kf = 0;

      for (int i = 3; i < data.length; i++) {
        if (data[i - 3] == 1 && (data[i - 2] & 37) == 37 && (data[i - 1] & -120) == -120) {
          kf = i - 2;
          break;
        }
      }

      int st = 0;
      boolean found = false;

      if (kf > 0) {
        for (int i = kf; i >= 5; i--) {
          if (data[i - 5] == 0 && data[i - 4] == 0 && data[i - 3] == 0 && (data[i - 2] & 1) == 1 && (data[i - 1] & 39) == 39) {
            st = i - 5;
            found = true;
            break;
          }
        }
      }

      if (found) {
        byte header[] = new byte[kf - st];
        System.arraycopy(data, st, header, 0, kf - st);
        returnData[1] = header;
      }
    } catch (IOException e) {
      logger.debug("Caught exception", e);
    }

    return returnData;
  }

  @Override
  protected Object clone() throws CloneNotSupportedException {
    Object cloned = super.clone();

    if (cloned instanceof DLNAMediaInfo) {
      DLNAMediaInfo mediaCloned = ((DLNAMediaInfo) cloned);
      mediaCloned.setAudioTracksList(new ArrayList<DLNAMediaAudio>());

      for (DLNAMediaAudio audio : getAudioTracksList()) {
        mediaCloned.getAudioTracksList().add((DLNAMediaAudio) audio.clone());
      }

      mediaCloned.setSubtitleTracksList(new ArrayList<DLNAMediaSubtitle>());

      for (DLNAMediaSubtitle sub : getSubtitleTracksList()) {
        mediaCloned.getSubtitleTracksList().add((DLNAMediaSubtitle) sub.clone());
      }
    }

    return cloned;
  }

  /**
   * @return the bitrate
   * @since 1.50.0
   */
  public int getBitrate() {
    return bitrate;
  }

  /**
   * @param bitrate the bitrate to set
   * @since 1.50.0
   */
  public void setBitrate(int bitrate) {
    this.bitrate = bitrate;
  }

  /**
   * @return the width
   * @since 1.50.0
   */
  public int getWidth() {
    return width;
  }

  /**
   * @param width the width to set
   * @since 1.50.0
   */
  public void setWidth(int width) {
    this.width = width;
  }

  /**
   * @return the height
   * @since 1.50.0
   */
  public int getHeight() {
    return height;
  }

  /**
   * @param height the height to set
   * @since 1.50.0
   */
  public void setHeight(int height) {
    this.height = height;
  }

  /**
   * @return the size
   * @since 1.50.0
   */
  public long getSize() {
    return size;
  }

  /**
   * @param size the size to set
   * @since 1.50.0
   */
  public void setSize(long size) {
    this.size = size;
  }

  /**
   * @return the codecV
   * @since 1.50.0
   */
  public String getCodecV() {
    return codecV;
  }

  /**
   * @param codecV the codecV to set
   * @since 1.50.0
   */
  public void setCodecV(String codecV) {
    this.codecV = codecV;
  }

  /**
   * @return the frameRate
   * @since 1.50.0
   */
  public String getFrameRate() {
    return frameRate;
  }

  /**
   * @param frameRate the frameRate to set
   * @since 1.50.0
   */
  public void setFrameRate(String frameRate) {
    this.frameRate = frameRate;
  }

  /**
   * @return the frameRateMode
   * @since 1.55.0
   */
  public String getFrameRateMode() {
    return frameRateMode;
  }

  /**
   * @param frameRateMode the frameRateMode to set
   * @since 1.55.0
   */
  public void setFrameRateMode(String frameRateMode) {
    this.frameRateMode = frameRateMode;
  }

  /**
   * @return the aspect
   * @since 1.50.0
   */
  public String getAspect() {
    return aspect;
  }

  /**
   * @param aspect the aspect to set
   * @since 1.50.0
   */
  public void setAspect(String aspect) {
    this.aspect = aspect;
  }

  /**
   * @return the aspect ratio reported by the container
   */
  public String getAspectRatioContainer() {
    return aspectRatioContainer;
  }

  /**
   * @param aspect the aspect ratio to set
   */
  public void setAspectRatioContainer(String aspect) {
    this.aspectRatioContainer = aspect;
  }

  /**
   * @return the aspect ratio of the video track
   */
  public String getAspectRatioVideoTrack() {
    return aspectRatioVideoTrack;
  }

  /**
   * @param aspect the aspect ratio to set
   */
  public void setAspectRatioVideoTrack(String aspect) {
    this.aspectRatioVideoTrack = aspect;
  }


  /**
   * Check if media has different Aspect Ratios for container and video track.
   *
   * @return true if container's AR is set and differs from video AR, false otherwise.
   */
  public boolean isAspectRatioMismatch() {
    return isNotBlank(getAspectRatioContainer()) && !equalsIgnoreCase(getAspectRatioContainer(), getAspectRatioVideoTrack());
  }

  /**
   * @return the thumb
   * @since 1.50.0
   */
  public byte[] getThumb() {
    return thumb;
  }

  /**
   * @param thumb the thumb to set
   * @since 1.50.0
   */
  public void setThumb(byte[] thumb) {
    this.thumb = thumb;
  }

  /**
   * @return the mimeType
   * @since 1.50.0
   */
  public String getMimeType() {
    return mimeType;
  }

  /**
   * @param mimeType the mimeType to set
   * @since 1.50.0
   */
  public void setMimeType(String mimeType) {
    this.mimeType = mimeType;
  }

  /**
   * @return the bitsPerPixel
   * @since 1.50.0
   */
  public int getBitsPerPixel() {
    return bitsPerPixel;
  }

  /**
   * @param bitsPerPixel the bitsPerPixel to set
   * @since 1.50.0
   */
  public void setBitsPerPixel(int bitsPerPixel) {
    this.bitsPerPixel = bitsPerPixel;
  }

  /**
   * @return reference frame count for video stream or {@code -1} if not parsed.
   */
  public synchronized byte getReferenceFrameCount() {
    return referenceFrameCount;
  }

  /**
   * Sets reference frame count for video stream or {@code -1} if not parsed.
   *
   * @param referenceFrameCount reference frame count.
   */
  public synchronized void setReferenceFrameCount(byte referenceFrameCount) {
    if (referenceFrameCount < -1) {
      throw new IllegalArgumentException("referenceFrameCount should be >= -1.");
    }
    this.referenceFrameCount = referenceFrameCount;
  }

  /**
   * @return AVC level for video stream or {@code null} if not parsed.
   */
  public synchronized String getAvcLevel() {
    return avcLevel;
  }

  /**
   * Sets AVC level for video stream or {@code null} if not parsed.
   *
   * @param avcLevel AVC level.
   */
  public synchronized void setAvcLevel(String avcLevel) {
    this.avcLevel = avcLevel;
  }

  /**
   * @return the audioTracks
   * @since 1.60.0
   */
  // TODO (breaking change): rename to getAudioTracks
  public List<DLNAMediaAudio> getAudioTracksList() {
    return audioTracks;
  }

  /**
   * @return the audioTracks
   * @deprecated use getAudioTracksList() instead
   */
  @Deprecated
  public ArrayList<DLNAMediaAudio> getAudioCodes() {
    if (audioTracks instanceof ArrayList) {
      return (ArrayList<DLNAMediaAudio>) audioTracks;
    } else {
      return new ArrayList<DLNAMediaAudio>();
    }
  }

  /**
   * @param audioTracks the audioTracks to set
   * @since 1.60.0
   */
  // TODO (breaking change): rename to setAudioTracks
  public void setAudioTracksList(List<DLNAMediaAudio> audioTracks) {
    this.audioTracks = audioTracks;
  }

  /**
   * @param audioTracks the audioTracks to set
   * @deprecated use setAudioTracksList(ArrayList<DLNAMediaAudio> audioTracks) instead
   */
  @Deprecated
  public void setAudioCodes(List<DLNAMediaAudio> audioTracks) {
    setAudioTracksList(audioTracks);
  }

  /**
   * @return the subtitleTracks
   * @since 1.60.0
   */
  // TODO (breaking change): rename to getSubtitleTracks
  public List<DLNAMediaSubtitle> getSubtitleTracksList() {
    return subtitleTracks;
  }

  /**
   * @return the subtitleTracks
   * @deprecated use getSubtitleTracksList() instead
   */
  @Deprecated
  public ArrayList<DLNAMediaSubtitle> getSubtitlesCodes() {
    if (subtitleTracks instanceof ArrayList) {
      return (ArrayList<DLNAMediaSubtitle>) subtitleTracks;
    } else {
      return new ArrayList<DLNAMediaSubtitle>();
    }
  }

  /**
   * @param subtitleTracks the subtitleTracks to set
   * @since 1.60.0
   */
  // TODO (breaking change): rename to setSubtitleTracks
  public void setSubtitleTracksList(List<DLNAMediaSubtitle> subtitleTracks) {
    this.subtitleTracks = subtitleTracks;
  }

  /**
   * @param subtitleTracks the subtitleTracks to set
   * @deprecated use setSubtitleTracksList(ArrayList<DLNAMediaSubtitle> subtitleTracks) instead
   */
  @Deprecated
  public void setSubtitlesCodes(List<DLNAMediaSubtitle> subtitleTracks) {
    setSubtitleTracksList(subtitleTracks);
  }

  /**
   * @return the model
   * @since 1.50.0
   */
  public String getModel() {
    return model;
  }

  /**
   * @param model the model to set
   * @since 1.50.0
   */
  public void setModel(String model) {
    this.model = model;
  }

  /**
   * @return the exposure
   * @since 1.50.0
   */
  public int getExposure() {
    return exposure;
  }

  /**
   * @param exposure the exposure to set
   * @since 1.50.0
   */
  public void setExposure(int exposure) {
    this.exposure = exposure;
  }

  /**
   * @return the orientation
   * @since 1.50.0
   */
  public int getOrientation() {
    return orientation;
  }

  /**
   * @param orientation the orientation to set
   * @since 1.50.0
   */
  public void setOrientation(int orientation) {
    this.orientation = orientation;
  }

  /**
   * @return the iso
   * @since 1.50.0
   */
  public int getIso() {
    return iso;
  }

  /**
   * @param iso the iso to set
   * @since 1.50.0
   */
  public void setIso(int iso) {
    this.iso = iso;
  }

  /**
   * @return the muxingMode
   * @since 1.50.0
   */
  public String getMuxingMode() {
    return muxingMode;
  }

  /**
   * @param muxingMode the muxingMode to set
   * @since 1.50.0
   */
  public void setMuxingMode(String muxingMode) {
    this.muxingMode = muxingMode;
  }

  /**
   * @return the muxingModeAudio
   * @since 1.50.0
   */
  public String getMuxingModeAudio() {
    return muxingModeAudio;
  }

  /**
   * @param muxingModeAudio the muxingModeAudio to set
   * @since 1.50.0
   */
  public void setMuxingModeAudio(String muxingModeAudio) {
    this.muxingModeAudio = muxingModeAudio;
  }

  /**
   * @return the container
   * @since 1.50.0
   */
  public String getContainer() {
    return container;
  }

  /**
   * @param container the container to set
   * @since 1.50.0
   */
  public void setContainer(String container) {
    this.container = container;
  }

  /**
   * @return the h264_annexB
   * @since 1.50.0
   */
  public byte[] getH264AnnexB() {
    return h264_annexB;
  }

  /**
   * @param h264AnnexB the h264_annexB to set
   * @since 1.50.0
   */
  public void setH264AnnexB(byte[] h264AnnexB) {
    this.h264_annexB = h264AnnexB;
  }

  /**
   * @return the mediaparsed
   * @since 1.50.0
   */
  public boolean isMediaparsed() {
    return mediaparsed;
  }

  /**
   * @param mediaparsed the mediaparsed to set
   * @since 1.50.0
   */
  public void setMediaparsed(boolean mediaparsed) {
    this.mediaparsed = mediaparsed;
  }

  /**
   * @return the thumbready
   * @since 1.50.0
   */
  public boolean isThumbready() {
    return thumbready;
  }

  /**
   * @param thumbready the thumbready to set
   * @since 1.50.0
   */
  public void setThumbready(boolean thumbready) {
    this.thumbready = thumbready;
  }

  /**
   * @return the dvdtrack
   * @since 1.50.0
   */
  public int getDvdtrack() {
    return dvdtrack;
  }

  /**
   * @param dvdtrack the dvdtrack to set
   * @since 1.50.0
   */
  public void setDvdtrack(int dvdtrack) {
    this.dvdtrack = dvdtrack;
  }

  /**
   * @return the secondaryFormatValid
   * @since 1.50.0
   */
  public boolean isSecondaryFormatValid() {
    return secondaryFormatValid;
  }

  /**
   * @param secondaryFormatValid the secondaryFormatValid to set
   * @since 1.50.0
   */
  public void setSecondaryFormatValid(boolean secondaryFormatValid) {
    this.secondaryFormatValid = secondaryFormatValid;
  }

  /**
   * @return the parsing
   * @since 1.50.0
   */
  public boolean isParsing() {
    return parsing;
  }

  /**
   * @param parsing the parsing to set
   * @since 1.50.0
   */
  public void setParsing(boolean parsing) {
    this.parsing = parsing;
  }

  /**
   * @return the encrypted
   * @since 1.50.0
   */
  public boolean isEncrypted() {
    return encrypted;
  }

  /**
   * @param encrypted the encrypted to set
   * @since 1.50.0
   */
  public void setEncrypted(boolean encrypted) {
    this.encrypted = encrypted;
  }
}
TOP

Related Classes of net.pms.dlna.DLNAMediaInfo

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.