/*
 * 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 java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;  //test

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.ListIterator;
import java.util.List;
import java.util.Arrays;

import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.StringTokenizer;

import javax.imageio.ImageIO;

import net.pms.PMS;
import net.pms.configuration.RendererConfiguration;
import net.pms.formats.AudioAsVideo;
import net.pms.formats.Format;
import net.pms.io.OutputParams;
import net.pms.io.ProcessWrapperImpl;
import net.pms.network.HTTPResource;
import net.pms.util.AVCHeader;
import net.pms.util.CoverUtil;
import net.pms.util.FileUtil;
import net.pms.util.MpegUtil;
import net.pms.util.ProcessUtil;
import net.pms.util.StringUtil;

import org.apache.commons.lang.StringUtils;
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.jaudiotagger.tag.TagField;
import org.jaudiotagger.tag.TagOptionSingleton;  //add, regzam
import org.jaudiotagger.tag.TagTextField; 

//import org.jaudiotagger.tag.id3.ID3TextEncodingConversion;  //add, regzam
//import org.jaudiotagger.tag.id3.valuepair.TextEncoding;
//import org.jaudiotagger.tag.id3.AbstractID3v2Tag;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.sun.jna.Platform;

/**
 * This class keeps track of scanned MediaInfo library information.
 * 
 * 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);
	public static final long ENDFILE_POS = 99999475712L;
	public static final long TRANS_SIZE = 100000000000L;

	// Stored in database
	private Double durationSec;

	/**
	 * @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;

	/**
	 * @deprecated Use standard getter and setter to access this variable.
	 */
	@Deprecated
	public String aspect="0";
	
	@Deprecated
	public Float aspect_f=0f;	//regzamod
	
	/**
	 * @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;

	/**
	 * @deprecated Use standard getter and setter to access this variable.
	 */
	@Deprecated
	public ArrayList<DLNAMediaAudio> audioCodes = new ArrayList<DLNAMediaAudio>();

	/**
	 * @deprecated Use standard getter and setter to access this variable.
	 */
	@Deprecated
	public ArrayList<DLNAMediaSubtitle> subtitlesCodes = 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 boolean muxable;
	private Map<String, String> extras;

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

	public boolean isMuxable(RendererConfiguration mediaRenderer) {
		//this is must but not sufficient condition for remux
		if(getCodecV()==null) return false;
		if(getCodecV().startsWith("mpeg2")) {
			//all DLNA cmpliant renderer must support mpeg2
			return true;
		}
		else if(getCodecV().startsWith("h264")) {
			if(mediaRenderer.isMuxH264MpegTS()) {
				//for more accurate judge, you shoud check for Level of H264, here
				return true;
			}
		}
		return false;
	}
	
	public boolean isMuxable_ORG(RendererConfiguration mediaRenderer) {
		// temporary fix, MediaInfo support will take care of that in the future
		// for now, http://ps3mediaserver.org/forum/viewtopic.php?f=11&t=6361&start=0
		if (mediaRenderer.isBRAVIA() && getCodecV() != null && getCodecV().startsWith("mpeg2")) {
			muxable = true;
		}
		if (mediaRenderer.isBRAVIA() && getHeight() < 288) // not supported for these small heights
		{
			muxable = false;
		}
		if (mediaRenderer.isRZ_RemuxJudgeForRegza()) {	//regzamod
			if(getCodecV() != null && (getCodecV().startsWith("mpeg2")
				||getCodecV().startsWith("h264"))) {
				muxable = true;	
			}
		}
		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) {
				}
			}
		}
	}

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

	public void generateThumbnail(InputFile input, Format ext, int type, DLNAResource dlna) {
		DLNAMediaInfo forThumbnail = new DLNAMediaInfo();
		forThumbnail.durationSec = durationSec;
		forThumbnail.parse(input, ext, type, true,null);
		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 && StringUtils.isNotBlank(PMS.getConfiguration().getFfmpegAlternativePath())) {
			args[0] = PMS.getConfiguration().getFfmpegAlternativePath();
		}
		args[1] = "-ss";
		args[2] = "" + PMS.getConfiguration().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:";
		if (!PMS.getConfiguration().getThumbnailsEnabled() 
			|| (PMS.getConfiguration().isUseMplayerForVideoThumbs() && !dvrms)) {
			args[2] = "0";
			for (int i = 5; i <= 13; i++) {
				args[i] = "-an";
			}
		}
		OutputParams params = new OutputParams(PMS.getConfiguration());
		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);
		ffmpeg_failure = false;
		Runnable r = new Runnable() {

			public void run() {
				boolean interrupted=false;
				try {
					//Thread.sleep(10000);//origin
					Thread.sleep(30000);//regzamod, some type of file needs more than 10sec
				} catch (InterruptedException e) {
					interrupted=true;
				}
				pw.stopProcess();
				if(!interrupted) {
					ffmpeg_failure = true;
					if(PMS.rz_debug>1) PMS.dbg("getFFMpegThumbnail: parse time overed!");
				}
				setParsing(false);
			}
		};
		Thread failsafe = new Thread(r);
		failsafe.start();
		pw.run();
		failsafe.interrupt();
		setParsing(false);
		if(ffmpeg_failure) return null;
		return pw;
	}

	private ProcessWrapperImpl getMplayerThumbnail(InputFile media) throws IOException {
		String args[] = new String[14];
		args[0] = PMS.getConfiguration().getMplayerPath();
		args[1] = "-ss";
		boolean toolong = getDurationInSeconds() < PMS.getConfiguration().getThumbnailSeekPos();
		args[2] = "" + (toolong ? (getDurationInSeconds() / 2) : PMS.getConfiguration().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(PMS.getConfiguration());
		params.workDir = PMS.getConfiguration().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() {
			public void run() {
				try {
					Thread.sleep(3000);
					//mplayer_thumb_failure = true;
				} catch (InterruptedException e) {
				}
				pw.stopProcess();
				setParsing(false);
			}
		};
		Thread failsafe = new Thread(r);
		failsafe.start();
		pw.run();
		setParsing(false);
		return pw;
	}

	private ProcessWrapperImpl getImageMagickThumbnail(InputFile media) throws IOException {
	// convert -size 320x180  hatching_orig.jpg  -auto-orient -thumbnail 160x90   -unsharp 0x.5  thumbnail.gif
		String args [] = new String[10];
		args[0] = PMS.getConfiguration().getIMConvertPath();
		args[1] = "-size";
		args[2] = "320x180";
		if (media.getFile() != null) {
			//---- force ShortFileName(non-unicode): ProcessBuilder doesn't pass unicode!!
			//---- suffix[0]: force generate only 1st frame, if src has multi-frames (ex. animation-gif)
			args[3] = ProcessUtil.getShortFileNameIfWideChars(media.getFile().getAbsolutePath())+"[0]";
			//args[3] = media.getFile().getAbsolutePath();
		}
		else
			args[3] = "-";
		args[4] = "-auto-orient";
		args[5] = "-thumbnail";
		args[6] = "160x90";
		args[7] = "-unsharp";
		args[8] = "-0x.5";
		
		if(true) {
			//---- force ShortFileName(non-unicode): ProcessBuilder doesn't pass unicode!!
			String name="img_"+media.getFile().hashCode();
			args[9] =PMS.getConfiguration().getTempFolder() + "/imagemagick_thumbs/" + name + ".jpg";
		}
		else {
			args[9] = PMS.getConfiguration().getTempFolder() + "/imagemagick_thumbs/" + media.getFile().getName() + ".jpg";
		}
		
		OutputParams params = new OutputParams(PMS.getConfiguration());
		params.workDir = new File(PMS.getConfiguration().getTempFolder().getAbsolutePath() + "/imagemagick_thumbs/");
		if (!params.workDir.exists())
			params.workDir.mkdirs();
		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() {
			public void run() {
				try {
					Thread.sleep(7000);
					ffmpeg_failure = true;
				} catch (InterruptedException e) {}
				pw.stopProcess();
				setParsing(false);
			}
		};
		Thread failsafe = new Thread(r);
		failsafe.start();
		pw.run();  // kick and wait proc end (see ProcessWrapperImpl.run())
		setParsing(false);
		return pw;
	}

	private String getFfmpegPath() {
		//String value = PMS.getConfiguration().getFfmpegPath_new();
		String value = PMS.getConfiguration().getFfmpegPath();
		if (value == null) {
			logger.error("No ffmpeg - unable to thumbnail");
			throw new RuntimeException("No ffmpeg - unable to thumbnail");
		} else {
			return value;
		}
	}

	public void parse (InputFile f, Format ext, int type, boolean thumbOnly,DLNAResource dlna) {
		if(PMS.rz_debug>1) PMS.dbg("DLNAMediainfo.parse: start dlna="+dlna);
		
		boolean AutoJudgeTagEncode=true;
		int i = 0;
		while (isParsing()) {
			if (i == 5) {
				//setMediaparsed(true); //why? parse may not succeeded, --> delete by regzamod 
				break;
			}
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
			}
			i++;
		}
		if (isMediaparsed()) {
			//PMS.dbg("DLNAMediainfo.parse: End (Already Parsed) file="+f.getFilename());
			return;
		}

		if (f != null) {
			if(PMS.rz_debug>1) PMS.dbg("===== DLNAMediainfo: parse file="+f.getFilename()+", thumbOnly="+thumbOnly );	// regzamod

			if (f.getFile() != null) {
				setSize(f.getFile().length());
			} else {
				setSize(f.getSize());
			}
			ProcessWrapperImpl pw = null;
			boolean ffmpeg_parsing = true;
			boolean parse_failed=false;
			//ID3TextEncodingConversion tagc=new ID3TextEncodingConversion();  //add, regzam
			
			if (type == Format.AUDIO || ext instanceof AudioAsVideo) {
				ffmpeg_parsing = false;
				DLNAMediaAudio audio = new DLNAMediaAudio();
				if (f.getFile() != null) {
					
					//TagOptionSingleton.getInstance().setId3v23DefaultTextEncoding(TextEncoding.ISO_8859_1);  //test
					//TagOptionSingleton.getInstance().setId3v23DefaultTextEncoding(TextEncoding.UTF_16);  //test
					//TagOptionSingleton.getInstance().setId3v23DefaultTextEncoding(TextEncoding.UTF_8);  //test
					
					try {
						AudioFile af = AudioFileIO.read(f.getFile());
						AudioHeader ah = af.getAudioHeader();
						//---- regzam, check tag encode
						/*
						//byte[] enc=getTextEncoding(AbstractTagFrame header,byte textEncoding);
						byte[] enc=tagc.getTextEncoding(af,"UTF-8");
						PMS.dbg("parse: file="+f.getFilename()+", tag encode="+enc);
						*/
						//PMS.dbg("getAudioHeader="+ah);
						//---------------------
						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());
							setBitrate((int) (ah.getBitRateAsNumber()*1000));  //regzam, ah.getBitRateAsNumber() returns kbps, not bps
							//PMS.dbg("DLNAMediainfo: AudioFileIO setBitrate="+getBitrate());
							audio.setNrAudioChannels(2);
							audio.setCodecA(ah.getEncodingType().toLowerCase());
							
							try {  //Integer.parseInt may cause Exception
								if (ah.getChannels() != null && ah.getChannels().toLowerCase().contains("mono")) {
									audio.setNrAudioChannels(1);
								} else if (ah.getChannels() != null && ah.getChannels().toLowerCase().contains("stereo")) {
									audio.setNrAudioChannels(2);
								} else if (ah.getChannels() != null) {
									//PMS.dbg("pos5 passed: ah.getChannels()="+ah.getChannels());
									audio.setNrAudioChannels(Integer.parseInt(ah.getChannels()));
								}
							} catch (Throwable e) {
								logger.debug("error in parsing unimportant metadata1: " + e.getMessage() + " - " + (e.getCause() != null ? e.getCause().getMessage() : ""));
								//logger.debug("error in parsing unimportant metadata1: " + e.getMessage());
							}
							if (audio.getCodecA().contains("(windows media")) {
								audio.setCodecA(audio.getCodecA().substring(0, audio.getCodecA().indexOf("(windows media")).trim());
							}
						}
						
						Tag t=null;
						t = af.getTag();

						if (t != null) {
							if (t.getArtworkList().size() > 0) {
								setThumb(t.getArtworkList().get(0).getBinaryData());
							} else {
								if (PMS.getConfiguration().getAudioThumbnailMethod() > 0) {
									setThumb(CoverUtil.get().getThumbnailFromArtistAlbum(PMS.getConfiguration().getAudioThumbnailMethod() == 1 ? CoverUtil.AUDIO_AMAZON : CoverUtil.AUDIO_DISCOGS, audio.getArtist(), audio.getAlbum()));
								}
							}
							if (!thumbOnly) {
								String str_title=t.getFirst(FieldKey.TITLE);
								String str_artist=t.getFirst(FieldKey.ARTIST);
								String str_album=t.getFirst(FieldKey.ALBUM);
								String str_genre=t.getFirst(FieldKey.GENRE);
								
								audio.setSongname(str_title);
								audio.setArtist(str_artist);
								audio.setAlbum(str_album);
								audio.setGenre(str_genre);
								
								//---- for non-unicode tag strings
								if(AutoJudgeTagEncode) {
									//------------------------------------------------------------
									//Judge TagEncoding by TagTextField.getEncoding()
									//But, TagTextField.getEncoding() returns only 2 encodes:
									//A) "UTF-16","UTF-8" for unicodes
									//B) "ISO-8859-1" for All of OtherEncodes (include Ascii,Shift-jis)
									//So, Following is a roufgh/danger judge that assuming ISO-8859-1 always replesents Shift-jis
									//More acculate judge will need more CPU costs
									//------------------------------------------------------------
									TagField tf;
									byte[] bdt;
									String str;
									
									//PMS.dbg("===== TAGS ==================");
									//---- Title
									tf= t.getFirstField(FieldKey.TITLE);
									if(tf!=null && (tf instanceof TagTextField) && ((TagTextField)tf).getEncoding().equals("ISO-8859-1")) {
										/*
										bdt=tf.getRawContent();  //for judge encoding here
										int spos=0;
										for(int k=bdt.length-1;k>=0;k--) {
											if(bdt[k]==0) {
												spos=k+1;
												break;
											}
										}
										bdt=Arrays.copyOfRange(bdt,spos,bdt.length);
										str=new String(bdt,"MS932");
										audio.setSongname(str);
										PMS.dbg("=== sj_title="+str);
										*/
										//str_title=((TagTextField)tf).getContent();
										if(str_title!=null) {
											bdt=str_title.getBytes("ISO-8859-1");  //pass through?
											str=new String(bdt,"MS932");
											audio.setSongname(str);
											//PMS.dbg("Title="+str);
										}
									}
									//---- Artist
									tf= t.getFirstField(FieldKey.ARTIST);
									if(tf!=null && (tf instanceof TagTextField) && ((TagTextField)tf).getEncoding().equals("ISO-8859-1")) {
										if(str_artist!=null) {
											bdt=str_artist.getBytes("ISO-8859-1");  //pass through?
											str=new String(bdt,"MS932");
											audio.setArtist(str);
											//PMS.dbg("Artist="+str);
										}
									}
									//---- Album
									tf= t.getFirstField(FieldKey.ALBUM);
									if(tf!=null && (tf instanceof TagTextField) && ((TagTextField)tf).getEncoding().equals("ISO-8859-1")) {
										if(str_album!=null) {
											bdt=str_album.getBytes("ISO-8859-1");  //pass through?
											str=new String(bdt,"MS932");
											audio.setAlbum(str);
											//PMS.dbg("Album="+str);
										}
									}
									//---- Genre
									tf= t.getFirstField(FieldKey.GENRE);
									if(tf!=null && (tf instanceof TagTextField) && ((TagTextField)tf).getEncoding().equals("ISO-8859-1")) {
										if(str_genre!=null) {
											bdt=str_genre.getBytes("ISO-8859-1");  //pass through?
											str=new String(bdt,"MS932");
											audio.setGenre(str);
											//PMS.dbg("Genre="+str);
										}
									}
								}
								
								String y = t.getFirst(FieldKey.YEAR);
								try { //Integer.parseInt() may cause Exception
									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")));
								} catch (Throwable e) {
									logger.debug("error in parsing unimportant metadata2: " + e.getMessage() + " - " + (e.getCause() != null ? e.getCause().getMessage() : ""));
									//logger.debug("error in parsing unimportant metadata2: " + e.getMessage());
								}
							}
						}
					} catch (Throwable e) {
						logger.debug("Error in parsing audio file: " + e.getMessage() + " - " + (e.getCause() != null ? e.getCause().getMessage() : ""));
						ffmpeg_parsing = false;
					}
					if (audio.getSongname() == null || audio.getSongname().length() == 0) {
						audio.setSongname(f.getFile().getName());
					}
					if (!ffmpeg_parsing) {
						getAudioCodes().add(audio);
					}
				}
			}
			if (type == Format.IMAGE) {
				if (f.getFile() != null) {
					try {
						ffmpeg_parsing = false;
						ImageInfo info = Sanselan.getImageInfo(f.getFile());
						setWidth(info.getWidth());
						setHeight(info.getHeight());
						setBitsPerPixel(info.getBitsPerPixel());
						String formatName = info.getFormatName();
						if (formatName.startsWith("JPEG")) {
							setCodecV("jpg");
							IImageMetadata meta = Sanselan.getMetadata(f.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) {
									setIso(tf.getIntValue());
								}
							}
						} else if (formatName.startsWith("PNG")) {
							setCodecV("png");
						} else if (formatName.startsWith("BMP")) { //add, regzam
							setCodecV("bmp");
						} else if (formatName.startsWith("GIF")) {
							setCodecV("gif");
						} else if (formatName.startsWith("TIF")) {
							setCodecV("tiff");
						}
						setContainer(getCodecV());
					} catch (Throwable e) {
						// ffmpeg_parsing = true;
						logger.info("Error during the parsing of image with Sanselan... switching to Ffmpeg: " + e.getMessage());
					}
					try {
						if(PMS.getConfiguration().getImageThumbnailsEnabled()) {
							getImageMagickThumbnail(f);
							String frameName=null;
							if(true) {
								//---- force ShortFileName(non-unicode): ProcessWrapperImpl(ProcessBuilder) doesn't pass unicode!!
								String name="img_"+f.getFile().hashCode();
								frameName = PMS.getConfiguration().getTempFolder() + "/imagemagick_thumbs/" + name + ".jpg";
								//PMS.dbg("DLNAMediainfo.pars: thumbnail outfile="+frameName);
							}
							else {
								frameName = PMS.getConfiguration().getTempFolder() + "/imagemagick_thumbs/" + f.getFile().getName() + ".jpg";
							}
							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();
							}
							else {
								if(PMS.rz_debug>1) logger.warn("DLNAMediainfo.parse: Error on get image's thumbnail, file="+f.getFile().getName());
							}
						}
					} catch (Throwable e) {
						logger.info("Error during the generating thumbnail of image with ImageMagick...: " + e.getMessage());
					
					}
				}
			}
			if (ffmpeg_parsing) {
				if (!thumbOnly || !PMS.getConfiguration().isUseMplayerForVideoThumbs()) {
					pw = getFFMpegThumbnail(f);
				}
				if(thumbOnly || PMS.getConfiguration().getRZ_thumbnails_getlater()==0) {
					setThumbready(true);  //get thumbnail
				}
				int	lcnt=0;	//regzamod
				int	vcnt=0;	//regzamod
				String input = "-";
				boolean dvrms = false;
				if (f.getFile() != null) {
					input = ProcessUtil.getShortFileNameIfWideChars(f.getFile().getAbsolutePath());
					dvrms = f.getFile().getAbsolutePath().toLowerCase().endsWith("dvr-ms");
				}
				if (pw!=null && !ffmpeg_failure && !thumbOnly) {
					//PMS.dbg("DLNAmediainfo.parse: start parse media");

					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) {
						//PMS.dbg("DLNAmediainfo.parse: line="+line);
						lcnt++;	//regzamod2
						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) {
							//PMS.dbg("DLNAmediainfo.parse: matchs="+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));
											
											//PMS.dbg("DLNAMediainfo: ffmpegParse setBitrate="+getBitrate()+", unit="+unit);

											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();
								if(false) {	//regzamod2, deleted on official v1.72.0
									if (langId == 0 && (getContainer().equals("avi") || getContainer().equals("ogm") || getContainer().equals("mov") || getContainer().equals("flv") || getContainer().equals("mp4"))) {	
										langId++; 
									}
								}
								audio.setId(langId++);
								if (a > -1 && b > a) {
									String str=line.substring(a + 1, b);
									if(str.length()>3) {
										logger.warn("Subtitle.lang length over limit(3)="+str);
										audio.setLang(DLNAMediaLang.UND);
									}
									audio.setLang(str);
								} 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 in parsing Stream ID: " + idString);
									}
								}

								while (st.hasMoreTokens()) {
									String token = st.nextToken().trim();
									if (token.startsWith("Stream")) {
										int pos1=token.indexOf("Audio: ");
										int pos2=token.indexOf(")",pos1);
										if(pos2>-1)
											audio.setCodecA(token.substring( pos1 + 7,pos2+1));
										else
											audio.setCodecA(token.substring( pos1 + 7));
									} else if (token.endsWith("Hz")) {
										audio.setSampleFrequency(token.substring(0, token.indexOf("Hz")).trim());
									} else if (token.equals("mono")) {
										audio.setNrAudioChannels(1);
									} else if (token.equals("stereo")) {
										audio.setNrAudioChannels(2);
									} else if (token.equals("5:1") || token.startsWith("5.1") || token.equals("6 channels")) {
										audio.setNrAudioChannels(6);
									} else if (token.startsWith("7.1")) {  //regzamod
										audio.setNrAudioChannels(8);
									} else if (token.equals("5 channels")) {
										audio.setNrAudioChannels(5);
									} else if (token.equals("4 channels")) {
										audio.setNrAudioChannels(4);
									} else if (token.equals("2 channels")) {
										audio.setNrAudioChannels(2);
									} 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);
										}
									}
								}
								getAudioCodes().add(audio);
							} else if (line.indexOf("Video:") > -1) {
								//regzamod2
								//---- here's sample line --------------------------
								// Stream #0.0: Video: h264 (Main), 
								//		yuv420p, 
								//		640x480 [SAR 1:1 DAR 4:3], 
								//		714 kb/s, 
								//		29.92 tbr, 
								//		1k tbn, 
								//		59.83 tbc
								//---- following is not standard, created by yamb
								// Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), 
								//		yuv420p, 
								//		1440x978, 
								//		1870 kb/s, 
								//		SAR 40:33 DAR 3200:1793, 
								//		29.97 fps, 29.97 tbr, 
								//		90k tbn, 180k tbc (default)
								//-------------------------------------------------

								vcnt++;
								String codecvw=null;
								String frameratew=null;
								String	aspectw=null;
								float aspect_fw=0.0f;
								int		widthw=0;
								int		heightw=0;
    									
								StringTokenizer st = new StringTokenizer(line, ",");
								while (st.hasMoreTokens()) {
									String token = st.nextToken().trim();
									if (token.startsWith("Stream")) {
										String s=token.substring(token.indexOf("Video: ") + 7);
										if(s.startsWith("h264") && s.indexOf(")") > -1) {  //contain suffixes
											s=s.substring(0, s.indexOf(")")+1);
										}
										else if (s.indexOf(" ") > -1) {
											// strip off tail(1st space & followers)
											s=s.substring(0, s.indexOf(" "));
										}
										//setCodecV(s);
										codecvw=s;	//regzamod2
									} else if ((token.indexOf("fps") > -1 || token.indexOf("fps(r)") > -1) ) { // dvr-ms ? // regzamod, moved to top judge
										//setFrameRate(token.substring(0, token.indexOf("fps")).trim());
										frameratew=token.substring(0, token.indexOf("fps")).trim();//regzamod2
										setFrameRate(frameratew);
									//} else if ((token.indexOf("tbc") > -1 || token.indexOf("tb(c)") > -1)&& getFrameRate() == null) {
									} else if ((token.indexOf("tbc") > -1 || token.indexOf("tb(c)") > -1)&& getFrameRate()==null) {
										// 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();
										//PMS.dbg("found tbc str="+frameRateDoubleString); 
										try {
											//if (!frameRateDoubleString.equals(getFrameRate())) {// tbc taken into account only if different than tbr
											if (!frameRateDoubleString.equals(frameratew)) {// tbc taken into account only if different than tbr
												Double frameRateDouble = Double.parseDouble(frameRateDoubleString);
												//setFrameRate(String.format(Locale.ENGLISH, "%.2f", frameRateDouble / 2));
												frameratew=String.format(Locale.ENGLISH, "%.2f", frameRateDouble / 2);	//regzamod2	
												setFrameRate(frameratew);
												//PMS.dbg("found tbc frameratew="+frameratew);
											}
										} catch (NumberFormatException nfe) {
											// No need to log, could happen if tbc is "1k" or something like that, no big deal
										}

									//} else if ((token.indexOf("tbr") > -1 || token.indexOf("tb(r)") > -1) && getFrameRate() == null) {
									} else if ((token.indexOf("tbr") > -1 || token.indexOf("tb(r)") > -1) && getFrameRate() == null) {
										//setFrameRate(token.substring(0, token.indexOf("tb")).trim());
										frameratew=token.substring(0, token.indexOf("tb")).trim();//regzamod2
										setFrameRate(frameratew);
										//PMS.dbg("found tbr frameratew="+frameratew);
									} else {
										if (token.indexOf("x") > -1) {
											String resolution = token.trim();
											//String dars=resolution;	//regzamod, for later use
											
											if (resolution.indexOf(" [") > -1) {
												resolution = resolution.substring(0, resolution.indexOf(" ["));
											}
											try {
												//setWidth(Integer.parseInt(resolution.substring(0, resolution.indexOf("x"))));
												//setHeight(Integer.parseInt(resolution.substring(resolution.indexOf("x") + 1)));
												widthw=Integer.parseInt(resolution.substring(0, resolution.indexOf("x")));//regzamod2
												heightw=Integer.parseInt(resolution.substring(resolution.indexOf("x") + 1));//regzamod2
													
											} catch (NumberFormatException nfe) {
											}
										}
										
										// token SAR/DAR may not always binded with token resolution like (A), 
										// i.e. may be separeted like (B)
										// A) 640x480 [SAR 1:1 DAR 4:3], 
										// B) SAR 40:33 DAR 3200:1793, 
										try {
											int	p1,p2,p3;
											String d1,d2,s1,s2,dars=token;
											//--- DAR 
											p1=token.indexOf("DAR");
											if(p1 > -1) {
												dars=dars.substring(p1+4,dars.length());
												if(PMS.rz_debug>1) PMS.dbg("found DAR="+dars);	//regzamod
												p2=dars.indexOf(":");
												p3=dars.indexOf("]");
												d1=dars.substring(0,p2);
												if(p3>-1) d2=dars.substring(p2+1,p3).trim();
												else d2=dars.substring(p2+1).trim();
												aspect_fw=Float.parseFloat(d1)/Float.parseFloat(d2);
												if(PMS.rz_debug>1) PMS.dbg("DLNAMediaInfo.pars: found DAR, aspect_f="+aspect_fw);	//regzamod
											}
											//--- SAR 
											else if(heightw>0.0f) {
												p1=token.indexOf("SAR");
												if(p1 > -1) {
													dars=dars.substring(p1+4,dars.length());
													p2=dars.indexOf(":");
													p3=dars.indexOf("]");
													s1=dars.substring(p1+4,p2);
													if(p3>-1) s2=dars.substring(p2+1,p3).trim();
													else s2=dars.substring(p2+1).trim();
													aspect_fw=Float.parseFloat(s1)*(float)widthw
														/(Float.parseFloat(s2)*(float)heightw);
													if(PMS.rz_debug>1) PMS.dbg("DLNAMediaInfo.pars: found SAR, aspect_f="+aspect_fw);
												}
											}
											
										} catch (NumberFormatException nfe) {
										}
										//---- regzamod add end
									}
								}
								if(aspect_fw==0) {
									aspect_fw=(float)widthw/(float)heightw;
									if(PMS.rz_debug>1) PMS.dbg("DLNAMediaInfo.pars: DAR Not found ,aspect_f="+aspect_fw);
								}
								aspectw=String.valueOf(aspect_fw);
								if(PMS.rz_debug>1) {
									PMS.dbg("vcnt="+vcnt+", new height="+heightw+", prev height="+getHeight());
									PMS.dbg("width="+widthw+", aspect="+aspectw+", aspect_f="+aspect_fw);
								}
								if(heightw>getHeight()) {	//regzamod2 override by larger profile
									setCodecV(codecvw);
									setFrameRate(frameratew);
									//PMS.dbg("setFrameRate="+frameratew);
									aspect_f=aspect_fw;
									setAspect(aspectw);
									setWidth(widthw);
									setHeight(heightw);
								}
							} else if (line.indexOf("Subtitle:") > -1 && !line.contains("tx3g")) {
								DLNAMediaSubtitle lang = new DLNAMediaSubtitle();
								lang.setType((line.contains("dvdsub") && Platform.isWindows() ? DLNAMediaSubtitle.VOBSUB : DLNAMediaSubtitle.EMBEDDED));
								int a = line.indexOf("(");
								int b = line.indexOf("):", a);
								if (a > -1 && b > a) {
									String str=line.substring(a + 1, b);
									if(str.length()>3) {
										logger.warn("Subtitle.lang length over limit(3)="+str);
										lang.setLang(DLNAMediaLang.UND);
									}
									lang.setLang(str);
								} 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);
										}
									}
								}
								getSubtitlesCodes().add(lang);
							}
						}
					}
				}
				else {
					parse_failed=true;
				}

				if (!thumbOnly && getContainer() != null && f.getFile() != null && getContainer().equals("mpegts") && isH264() && getDurationInSeconds() == 0) {
					// let's do the parsing for getting the duration...
					try {
						int length = MpegUtil.getDurationFromMpeg(f.getFile());
						if (length > 0) {
							setDuration((double) length);
						}
					} catch (IOException e) {
						logger.trace("Error in retrieving length: " + e.getMessage());
					}
				}
				if(type == Format.VIDEO && getDurationInSeconds() == 0) {  //regzamod
					float mb=1024*1024;  //1 Mbps
					float bps;
					if(getBitrate()<mb*0.2) {  //200kbps
						//get duration failed may means get bitrate also failed, 
						//so this bitrate may be audio, instead of video. 
						//consequencery, this estimation is not accurate at all!!
						bps=2*mb;  //3Mbps
						setBitrate((int)bps);
					}
					else {
						bps=getBitrate();
					}
					double dur=getSize()/(bps/8);
					if(PMS.rz_debug>2) {
						PMS.dbg("Seems failed to get duration normally, let's 2nd way");
						PMS.dbg("  size(bytes)="+getSize()+", Bitrate(bps)="+getBitrate()+", Duration(sec)="+dur);
					}
					setDuration(dur);
				}

				if ((PMS.getConfiguration().getRZ_thumbnails_getlater()==0 || thumbOnly)
					&& PMS.getConfiguration().isUseMplayerForVideoThumbs() 
					&& type == Format.VIDEO && !dvrms) {  //false: will be got later on demand
					try {
						getMplayerThumbnail(f);
						String frameName = "" + f.hashCode();
						frameName = PMS.getConfiguration().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();
							}
							if (!jpg.getParentFile().delete()) {
								jpg.getParentFile().delete();
							}
						}
					} catch (IOException e) {
						logger.debug("Error while read thumbnail : " + e.getMessage());
					}
				}

				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 && !java.awt.GraphicsEnvironment.isHeadless()) {  //origin 
						//if (false && sz > 0 && !java.awt.GraphicsEnvironment.isHeadless()) {  
							// add some text in thumbnail image : is it needed??
							//PMS.dbg("DLNAMediainfo.parse: NOT java.awt.GraphicsEnvironment.isHeadless() --> Do some tricks");
							
							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", 2, low += 18);
									} else if (getWidth() == 1280) {
										g.drawString("720p", 2, 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());
					}
				}
				
				//---- Thumbnail dump --------------
				//boolean test=true;
				if((PMS.rz_stream_dump & 0x02)!=0 && getThumb()!=null && getThumb().length>0) {
					String name="Unknown";
					if(f.getFile()!=null) {
						name=f.getFile().getName();
					}
					//PMS.dbg("DLNAMediainfo.parse: file="+name+", Dump Thumbnail, size="+getThumb().length);
					
					try {
						File df=new File(PMS.getConfiguration().getTempFolder(),"thumb_dump_("+name+").jpg");
						FileOutputStream dout =new FileOutputStream(df);
						dout.write(getThumb(), 0, getThumb().length);
						dout.flush();
						dout.close();
					} catch (IOException e) {
						logger.debug("Error while dumping thumbnail : " + e.getMessage());
					}
				}
			}
			
			//PMS.dbg("DLNAMediainfo.parse: ffmpeg_failure="+ffmpeg_failure);
			if(!parse_failed) { //add judge, regzamod
				if(PMS.rz_debug>1) PMS.dbg("DLNAMediainfo.parse: exec finalize");
				finalize(type, f,dlna);
				setMediaparsed(true);
			}
			else {
				ffmpeg_failure=false; // try next
				setMediaparsed(false);
				//String path="Unknown";
				//if(f.getFile()!=null) path=f.getFile().getAbsolutePath();
				//logger.warn("DLNAMediainfo.parse: failed, may be timeouted: try later, file="+path
				//	+", media="+this);
			}
		}
		//PMS.dbg("DLNAMediainfo.parse: End file="+f.getFilename());
	}

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

	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) {
		}
		return null;
	}

	public void finalize(int type, InputFile f) {
		finalize(type, f, null, null);
	}
	public void finalize(int type, InputFile f, DLNAResource dlna) {
		finalize(type, f, null, dlna);
	}
	public void finalize(int type, InputFile f, String fname) {
		finalize(type, f, fname, null);
	}
	
	/*---------------------------------------------
	Video Type      Sfx	 	MIME Type
	Flash           .flv 	video/x-flv
	MPEG-4          .mp4    video/mp4
	iPhone Index    .m3u8 	application/x-mpegURL
	iPhone Segment  .ts 	video/MP2T
	3GP Mobile      .3gp 	video/3gpp
	QuickTime       .mov 	video/quicktime
	A/V Interleave  .avi 	video/x-msvideo
	Windows Media   .wmv 	video/x-ms-wmv	
	----------------------------------------------*/

	public void finalize (int type, InputFile f, String fname, DLNAResource dlna) {
		String cont=getContainer();
		String codecV=getCodecV();
		String codecA = null;
		
		if(codecV!=null) codecV=codecV.toLowerCase();
		if (getFirstAudioTrack() != null) {
			codecA = getFirstAudioTrack().getCodecA();
		}
		
		if(codecA != null && (codecV == null || codecV.equals(DLNAMediaLang.UND)) ) {
			//---- Seems to be Audio --------
			if(codecA.contains("mp3")) {
				setMimeType(HTTPResource.AUDIO_MP3_TYPEMIME);
			} else if (codecA.contains("aac")) {
				setMimeType(HTTPResource.AUDIO_MP4_TYPEMIME);
			} else if (codecA.contains("alac")) {  //regzam
				setMimeType(HTTPResource.AUDIO_MP4_TYPEMIME);
			} else if (codecA.contains("flac")) {
				setMimeType(HTTPResource.AUDIO_FLAC_TYPEMIME);
			} else if (codecA.contains("vorbis")) {
				setMimeType(HTTPResource.AUDIO_OGG_TYPEMIME);
			} else if ((codecA.contains("asf") || codecA.startsWith("wm"))) {
				setMimeType(HTTPResource.AUDIO_WMA_TYPEMIME);
			} else if ((codecA.startsWith("pcm") || codecA.contains("wav"))) {
				setMimeType(HTTPResource.AUDIO_WAV_TYPEMIME);
			}
		} 
		else if(cont!=null) {
			//---- Seems to be Video -------
			if(cont.equals("mov")) {
				setMimeType(HTTPResource.MP4_TYPEMIME);
			} 
			else if (cont.startsWith("mpeg")) {
				setMimeType(HTTPResource.MPEG_TYPEMIME);
			}
			else if (cont.equals("matroska") || cont.equals("mkv")) {
				setMimeType(HTTPResource.MATROSKA_TYPEMIME);
			}
			else if (cont.equals("flv")) {
				setMimeType(HTTPResource.FLV_TYPEMIME);
			}
			else if (cont.equals("avi")) {
				setMimeType(HTTPResource.AVI_TYPEMIME);
			}
			else if (cont.equals("asf") || cont.equals("wmv")) {
				setMimeType(HTTPResource.WMV_TYPEMIME);
			}
			else if (codecV!=null && codecA!=null) {
				if (codecV.startsWith("h264") || codecV.equals("h263") 
					|| codecV.equals("mpeg4") || codecV.equals("mp4")) {
					setMimeType(HTTPResource.MP4_TYPEMIME);
				} else if (codecV.indexOf("mpeg") > -1 || codecV.indexOf("mpg") > -1) {
					setMimeType(HTTPResource.MPEG_TYPEMIME);
				}
			}
			//---- Seems to be Image -------
			else if(codecV!=null) {
				if (codecV.equals("jpg") || codecV.equals("jpeg") || cont.contains("jpg")) {
					setMimeType(HTTPResource.JPEG_TYPEMIME);
				} else if (codecV.equals("png") || cont.equals("png")) {
					setMimeType(HTTPResource.PNG_TYPEMIME);
				} else if (codecV.equals("bmp") || cont.equals("bmp")) {  //add, regzam
					setMimeType(HTTPResource.BMP_TYPEMIME);
				} else if (codecV.equals("gif") || cont.equals("gif")) {
					setMimeType(HTTPResource.GIF_TYPEMIME);
				}
			}
		}

		if(getMimeType()==null) { //not found yet
			setMimeType(new HTTPResource().getDefaultMimeType(type));
		}

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

		// Check for external subs here, why?
		if (f.getFile() != null && type == Format.VIDEO && PMS.getConfiguration().getUseSubtitles()) {
			/*
			int onBDMV=0;
			try {
				if(Platform.isWindows() && f.getFile().getCanonicalPath().matches("^[A-Z]:\\\\BDMV\\\\STREAM\\\\.*")) {
					if(PMS.rz_debug>0) PMS.dbg("RealFile.isValid: File seems to be in BDMV, will avoid auto search ext_subs, name="+f.getFilename());
					onBDMV=1;
				}
			} catch (IOException e) {
				logger.error("IOException error="+e);
			}
			if(onBDMV==0) {
				boolean b=FileUtil.doesSubtitlesExists(f.getFile(), this, fname);
				if(PMS.rz_debug>1) PMS.dbg("DLNAMediainfo.finalize: doesSubtitlesExists="+b+", dlna="+dlna);
				if(dlna!=null) dlna.setSrtFile(b); 
			}
			*/
			if(dlna!=null && !dlna.isOnSlowMedia()) {
				boolean b=FileUtil.doesSubtitlesExists(f.getFile(), this, fname);
				if(PMS.rz_debug>1) PMS.dbg("DLNAMediainfo.finalize: doesSubtitlesExists="+b+", dlna="+dlna);
				dlna.setSrtFile(b); 
			}
		}
		//PMS.dbg("DLNAMediaInfo.finalize: name="+f.getFilename()+", ExtType="+type+", MimeType="+getMimeType());

	}

	/*
	public void finalize(int type, InputFile f, String fname) {
		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 (getCodecV() != null && (getCodecV().startsWith("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 ("png".equals(getCodecV()) || "png".equals(getContainer())) {
			setMimeType(HTTPResource.PNG_TYPEMIME);
		} else if ("bmp".equals(getCodecV()) || "bmp".equals(getContainer())) {  //add, regzam
			setMimeType(HTTPResource.BMP_TYPEMIME);
		} else if ("gif".equals(getCodecV()) || "gif".equals(getContainer())) {
			setMimeType(HTTPResource.GIF_TYPEMIME);
		} 
		else if (getCodecV() == null || getCodecV().equals(DLNAMediaLang.UND)) {
			if (codecA != null && codecA.contains("mp3")) {
				setMimeType(HTTPResource.AUDIO_MP3_TYPEMIME);
			} else if (codecA != null && codecA.contains("aac")) {
				setMimeType(HTTPResource.AUDIO_MP4_TYPEMIME);
			} else if (codecA != null && codecA.contains("alac")) {  //regzam
				setMimeType(HTTPResource.AUDIO_MP4_TYPEMIME);
			} else if (codecA != null && codecA.contains("flac")) {
				setMimeType(HTTPResource.AUDIO_FLAC_TYPEMIME);
			} else if (codecA != null && codecA.contains("vorbis")) {
				setMimeType(HTTPResource.AUDIO_OGG_TYPEMIME);
			} else if (codecA != null && (codecA.contains("asf") || codecA.startsWith("wm"))) {
				setMimeType(HTTPResource.AUDIO_WMA_TYPEMIME);
			} else if (codecA != null && (codecA.startsWith("pcm") || codecA.contains("wav"))) {
				setMimeType(HTTPResource.AUDIO_WAV_TYPEMIME);
			} else {
				setMimeType(new HTTPResource().getDefaultMimeType(type));
			}
		} else {
			setMimeType(new 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 && PMS.getConfiguration().getUseSubtitles()) {
			FileUtil.doesSubtitlesExists(f.getFile(), this, fname);
		}
		//PMS.dbg("DLNAMediaInfo.finalize: name="+f.getFilename()+", ExtType="+type+", MimeType="+getMimeType());

	}
	*/
		
	public boolean h264_parsed;
	public boolean h264_L41Compatible; //regzamod
	public int  h264_level; //regzamod

	public boolean isVideoPS3Compatible(InputFile f, DLNAResource dlna) {  //regzamod
		if(getCodecV() != null && (getCodecV().equals("h264") || getCodecV().startsWith("mpeg2"))) {
			muxable=true;
			if(getCodecV().startsWith("mpeg2")) return true;
			if(getCodecV().startsWith("h264")) {
				//PMS.dbg("isVideoPS3Compatible: getCodecV="+getCodecV());
				if(getCodecV().contains("High 4:4:4")) {
					muxable=false;
					return false;
				}
				getH264Details(f,dlna);
				if(h264_level<=41) return true;
				if(h264_L41Compatible) return true;
				muxable=false;
			}
		}
		return muxable;
	}

	public void getH264Details(InputFile f, DLNAResource dlna) {  //regzamod
		int rc=getH264Details_in(f);
		if(rc==1 && PMS.getConfiguration().getUseCache()) {  
			//update DB, should do from here?
			DLNAMediaDatabase database = PMS.get().getDatabase();
			if(database != null && dlna instanceof RealFile && dlna.getMedia()!=null) {
				File file = ((RealFile)dlna).getFile();
				String fileName = file.getAbsolutePath();
				String vcodec=getVcodecInDBfmt();
				dlna.getMedia().setCodecV(vcodec);
				database.updateDataVcodec(fileName,vcodec);
			}
		}
	}
	
	public String getVcodecInDBfmt() {
		String codecv=getCodecV();
		if(codecv!=null && codecv.startsWith("h264")) {
			if(h264_level>0) {
				if(h264_level>41) {
					codecv +=",Lev="+h264_level+",Cmp="+(h264_L41Compatible?"1":"0");
				}
				else {
					codecv +=",Lev="+h264_level+",Cmp=1";
				}
			}
			if(PMS.rz_debug>1) PMS.dbg("MediaInfo: VcodecInDBfmt="+codecv);
		}
		return codecv;
	}

	public int getH264Details_in(InputFile f) {  //regzamod
		boolean muxable=true;
		int rc=0;
		if (!h264_parsed) {
			if (getCodecV() != null) { // what about VC1 ?
				ffmpeg_annexb_failure=false;
				if (getCodecV().startsWith("h264") 
					&& getContainer() != null 
					&& (getContainer().equals("matroska") 
					|| getContainer().equals("mkv") 
					|| getContainer().equals("mov") 
					|| getContainer().equals("mp4")
					|| getContainer().equals("flv") 	//regzamod
					|| getContainer().equals("mpegts")  //regzamod
					)) { // containers without h264_annexB ??
					
					byte headers[][] = getAnnexBFrameHeader(f);
					if (ffmpeg_annexb_failure) {
						logger.error("Fatal error when retrieving AVC informations !");
						return -1;  //try later
					}
					if (headers != null) {
						rc=1;  //new got info
						setH264AnnexB(headers[1]);
						if (getH264AnnexB() != null) {
							int skip = 5;
							if (getH264AnnexB()[2] == 1) {
								skip = 4;
							}
							byte header[] = new byte[getH264AnnexB().length - skip];
							System.arraycopy(getH264AnnexB(), skip, header, 0, header.length);
							AVCHeader avcHeader = new AVCHeader(header);
							avcHeader.parse();
							logger.trace("H264 file: " + f.getFilename() + ": Profile: " + avcHeader.getProfile() + " / level: " + avcHeader.getLevel() + " / ref frames: " + avcHeader.getRef_frames());
							h264_level=avcHeader.getLevel();
							if(avcHeader.getLevel() >= 41) { // Check if file is compliant with Level4.1
								muxable = true;
								if(getWidth() > 0 && getHeight() > 0) {
									int maxref = (int) Math.floor(8388608 / (getWidth() * getHeight()));
									if (avcHeader.getRef_frames() > maxref) {
										muxable = false;
									}
								}
								if(muxable) {
									h264_L41Compatible=true;
								}
							}
							if (!muxable) {
								logger.debug("H264 file: " + f.getFilename() + " is not ps3 compatible !");
							}
						} else {
							muxable = false;
						}
					} else {
						muxable = false;
					}
				}
			}
			h264_parsed = true;
		}
		return rc;
	}

	/*
	public boolean isVideoPS3Compatible_ORG(InputFile f) {  //origin
		if (!h264_parsed) {
			if (getCodecV() != null && (getCodecV().equals("h264") || getCodecV().startsWith("mpeg2"))) { // what about VC1 ?
				ffmpeg_annexb_failure=false;
				muxable = true;
				if (getCodecV().equals("h264") && getContainer() != null && (getContainer().equals("matroska") 
					|| getContainer().equals("mkv") || getContainer().equals("mov") || getContainer().equals("mp4"))) { // containers without h264_annexB
					byte headers[][] = getAnnexBFrameHeader(f);
					if (ffmpeg_annexb_failure) {
						logger.error("Fatal error when retrieving AVC informations !");
						return true;  //try later
					}
					if (headers != null) {
						setH264AnnexB(headers[1]);
						if (getH264AnnexB() != null) {
							int skip = 5;
							if (getH264AnnexB()[2] == 1) {
								skip = 4;
							}
							byte header[] = new byte[getH264AnnexB().length - skip];
							System.arraycopy(getH264AnnexB(), skip, header, 0, header.length);
							AVCHeader avcHeader = new AVCHeader(header);
							avcHeader.parse();
							logger.trace("H264 file: " + f.getFilename() + ": Profile: " + avcHeader.getProfile() + " / level: " + avcHeader.getLevel() + " / ref frames: " + avcHeader.getRef_frames());
							muxable = true;
							h264_level=avcHeader.getLevel();
							if (avcHeader.getLevel() >= 41) { // Check if file is compliant with Level4.1
								if (getWidth() > 0 && getHeight() > 0) {
									int maxref = (int) Math.floor(8388608 / (getWidth() * getHeight()));
									if (avcHeader.getRef_frames() > maxref) {
										muxable = false;
									}
								}
							}
							if (!muxable) {
								logger.debug("H264 file: " + f.getFilename() + " is not ps3 compatible !");
							}
						} else {
							muxable = false;
						}
					} else {
						muxable = false;
					}
				}
			}
			h264_parsed = true;
		}
		return muxable;
	}
	*/

	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");
	}

	public String toString () {
		String s = "container: " + getContainer() + " / bitrate: " + getBitrate() + " / size: " + getSize() + " / codecV: " + getCodecV() +" / duration: " + getDurationString() + " / width: " + getWidth() + " / height: " + getHeight() + " / frameRate: " + getFrameRate() + " / thumb size : " + (getThumb() != null ? getThumb().length : 0) + " / muxingMode: " + getMuxingMode();
		for (DLNAMediaAudio audio : getAudioCodes()) {
			s += "\n\taudio: id=" + audio.getId() + " / lang: " + audio.getLang() + " / flavor: " + audio.getFlavor() + " / codec: " + audio.getCodecA() + " / sf:" + audio.getSampleFrequency() + " / na: " + audio.getNrAudioChannels() + " / bs: " + audio.getBitsperSample();
			if (audio.getArtist() != null) {
				s += " / " + audio.getArtist() + "|" + audio.getAlbum() + "|" + audio.getSongname() + "|" + audio.getYear() + "|" + audio.getTrack();
			}
		}
		for (DLNAMediaSubtitle sub : getSubtitlesCodes()) {
			s += "\n\tsub: id=" + sub.getId() + " / lang: " + sub.getLang() + " / flavor: " + sub.getFlavor() + " / type: " + sub.getType();
		}
		return s;
	}

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

	//----- regzamod add caution
	// getValidFps used for -ofps of Mencoder, and -ofps must be "valid values" instead of "real value".
	// Real value may cause abend of MEncoder!!
	public String getValidFps(boolean ratios) {
		String validFrameRate = null;
		if (getFrameRate() != null && getFrameRate().length() > 0) {
			try {
				double fr = Double.parseDouble(getFrameRate());
				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.9 && 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 (getAudioCodes().size() > 0) {
			return getAudioCodes().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 (int) (getBitrate() / 8);	//origin
			return getBitrate();	//origin
		}
		int realBitrate = 10000000;
		try {
			realBitrate = (int) (getSize() / getDurationInSeconds());
		} catch (Throwable t) {
		}
		return realBitrate;
	}

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

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

	public byte[][] getAnnexBFrameHeader(InputFile f) {
		String cmdArray[] = new String[16];
		cmdArray[0] = 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:";
		cmdArray[13] = "-loglevel";
		cmdArray[14] = "fatal";
		cmdArray[15] = "pipe:";


		byte returnData[][] = new byte[2][];

		OutputParams params = new OutputParams(PMS.getConfiguration());
		params.maxBufferSize = 1;
		params.stdin = f.getPush();

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

		Runnable r = new Runnable() {

			public void run() {
				try {
					//Thread.sleep(3000);
					Thread.sleep(5000);
					ffmpeg_annexb_failure = true;
				} catch (InterruptedException e) {
				}
				pw.stopProcess();
			}
		};
		Thread failsafe = new Thread(r);
		failsafe.start();
		pw.run();
		if (ffmpeg_annexb_failure) {
			return null;
		}

		InputStream is = null;
		ByteArrayOutputStream baot = new ByteArrayOutputStream();
		try {
			is = pw.getInputStream(0);
			byte b[] = new byte[4096];
			int n = -1;
			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;
					}
				}
			}
			//logger.info("==> getAnnexBFrameHeader: found=" + found + ", kf=" + kf + ", st=" + st);//regzamod	
			//logger.info("==> getAnnexBFrameHeader: boat size=" + baot.size() + ", naiyo=" + baot.toString("MS932"));//regzamod	
				
			if (found) {
				byte header[] = new byte[kf - st];
				System.arraycopy(data, st, header, 0, kf - st);
				returnData[1] = header;
			}
		} catch (IOException e) {
		}
		return returnData;
	}

	@Override
	protected Object clone() throws CloneNotSupportedException {
		Object cloned = super.clone();
		if (cloned instanceof DLNAMediaInfo) {
			DLNAMediaInfo mediaCloned = ((DLNAMediaInfo) cloned);
			mediaCloned.setAudioCodes(new ArrayList<DLNAMediaAudio>());
			for (DLNAMediaAudio audio : getAudioCodes()) {
				mediaCloned.getAudioCodes().add((DLNAMediaAudio) audio.clone());
			}
			mediaCloned.setSubtitlesCodes(new ArrayList<DLNAMediaSubtitle>());
			for (DLNAMediaSubtitle sub : getSubtitlesCodes()) {
				mediaCloned.getSubtitlesCodes().add((DLNAMediaSubtitle) sub.clone());
			}
		}

		return cloned;
	}

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

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

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

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

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

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

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

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

	/**
	 * @return the codecV
	 * @since 1.50
	 */
	public String getCodecV () {
		return codecV;
	}
	public String getVideoCodec() {  //regzamod
		if(codecV==null) return null;
		if(codecV.startsWith("h264")) return "h264";
		if(codecV.startsWith("mpeg2")) return "mpeg2";
		return codecV;
	}

	/**
	 * @param codecV the codecV to set
	 * @since 1.50
	 */
	public void setCodecV(String codecV) {
		//regzamod, cut at max, see DLNAMediaDatabase CODECV VARCHAR2(64)
		if(codecV!=null && codecV.length()>64) this.codecV = codecV.substring(0,64);
		else this.codecV = codecV;
	}

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

	/**
	 * @param frameRate the frameRate to set
	 * @since 1.50
	 */
	public void setFrameRate(String frameRate) {
		if(frameRate!=null && 
			(frameRate.endsWith("k") || Double.parseDouble(frameRate) >=1000.0f || Double.parseDouble(frameRate) <=2.0f)) {	// add regzamod
			//k means *1000:maybe VFR like wmv, or something wrong
			logger.warn("setFrameRate: Illegal frameRate="+frameRate+" --> set to default");
			this.frameRate=null;
		}
		else {	//original
			this.frameRate = frameRate;
		}
	}

	/**
	 * @return the aspect
	 * @since 1.50
	 */
	public String getAspect() {
		return aspect;
	}
	public Float getAspect_f() {	// regzamod
		if(aspect_f<=0) {	// not setted
			if(aspect==null) {
					return	0f;	//fail safe
			}
			return Float.parseFloat(aspect);
		}
		return aspect_f;
	}

	/**
	 * @param aspect the aspect to set
	 * @since 1.50
	 */
	public void setAspect(String aspect) {
		this.aspect = aspect;
	}
	public void setAspect_f(Float aspect_f) {	//regzamod
		this.aspect_f = aspect_f;
	}

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

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

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

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

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

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

	/**
	 * @return the audioCodes
	 * @since 1.50
	 */
	public ArrayList<DLNAMediaAudio> getAudioCodes() {
		return audioCodes;
	}

	/**
	 * @param audioCodes the audioCodes to set
	 * @since 1.50
	 */
	public void setAudioCodes(ArrayList<DLNAMediaAudio> audioCodes) {
		this.audioCodes = audioCodes;
	}

	/**
	 * @return the subtitlesCodes
	 * @since 1.50
	 */
	public ArrayList<DLNAMediaSubtitle> getSubtitlesCodes() {
		return subtitlesCodes;
	}

	/**
	 * @param subtitlesCodes the subtitlesCodes to set
	 * @since 1.50
	 */
	public void setSubtitlesCodes(ArrayList<DLNAMediaSubtitle> subtitlesCodes) {
		this.subtitlesCodes = subtitlesCodes;
	}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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