/*
 * @(#)StreamParser
 *
 * Copyright (c) 2005 by dvb.matt, All rights reserved.
 * 
 * This file is part of ProjectX, a free Java based demux utility.
 * By the authors, ProjectX is intended for educational purposes only, 
 * as a non-commercial test project.
 * 
 *
 * 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; either version 2 of the License, or
 * (at your option) any later version.
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

package net.sourceforge.dvb.projectx.parser;

import java.io.File;
import java.io.InputStream;
import java.io.IOException;
import java.io.PushbackInputStream;
import java.io.FileOutputStream;

import java.util.Arrays;
import java.util.List;
import java.util.ArrayList;
import java.util.Hashtable;

import net.sourceforge.dvb.projectx.common.Common;
import net.sourceforge.dvb.projectx.common.Resource;
import net.sourceforge.dvb.projectx.common.Keys;
import net.sourceforge.dvb.projectx.common.JobCollection;
import net.sourceforge.dvb.projectx.common.JobProcessing;

import net.sourceforge.dvb.projectx.io.StandardBuffer;

import net.sourceforge.dvb.projectx.xinput.XInputFile;
import net.sourceforge.dvb.projectx.xinput.StreamInfo;

import net.sourceforge.dvb.projectx.parser.CommonParsing;
import net.sourceforge.dvb.projectx.parser.StreamConverter;
import net.sourceforge.dvb.projectx.parser.StreamDemultiplexer;
import net.sourceforge.dvb.projectx.parser.StreamParserBase;
import net.sourceforge.dvb.projectx.parser.StreamProcess;


/**
 * main thread
 */
public class StreamParserPESPrimary extends StreamParserBase {

	/**
	 * 
	 */
	public StreamParserPESPrimary()
	{
		super();
	}

	/**
	 * primary PES Parser
	 */
	public String parseStream(JobCollection collection, XInputFile aXInputFile, int _pes_streamtype, int action, String vptslog)
	{
		String fchild = collection.getOutputName(aXInputFile.getName());
		String fparent = collection.getOutputNameParent(fchild);

		JobProcessing job_processing = collection.getJobProcessing();

		/**
		 * split part 
		 */
		fparent += job_processing.getSplitSize() > 0 ? "(" + job_processing.getSplitPart() + ")" : "" ;

		boolean Message_2 = Common.getSettings().getBooleanProperty(Keys.KEY_MessagePanel_Msg2);
		boolean Debug = collection.DebugMode();
		boolean SimpleMPG = Common.getSettings().getBooleanProperty(Keys.KEY_simpleMPG);
		boolean GetEnclosedPackets = Common.getSettings().getBooleanProperty(Keys.KEY_Input_getEnclosedPackets);
		boolean IgnoreScrambledPackets = Common.getSettings().getBooleanProperty(Keys.KEY_TS_ignoreScrambled);
		boolean PreviewAllGops = Common.getSettings().getBooleanProperty(Keys.KEY_Preview_AllGops);
		boolean DumpDroppedGop = Common.getSettings().getBooleanProperty(Keys.KEY_dumpDroppedGop);
		boolean CreateD2vIndex = Common.getSettings().getBooleanProperty(Keys.KEY_ExternPanel_createD2vIndex);
		boolean SplitProjectFile = Common.getSettings().getBooleanProperty(Keys.KEY_ExternPanel_splitProjectFile);
		boolean Overlap = Common.getSettings().getBooleanProperty(Keys.KEY_ExportPanel_Export_Overlap);

		boolean isTeletext = false;
		boolean missing_startcode = false;
		boolean scrambling_messaged = false;
		boolean pes_isMpeg2;
		boolean pes_alignment;
		boolean pes_scrambled;
		boolean containsPts = false;
		boolean usePidfilter = false;
		boolean isZeroPacket = false;
		boolean isZeroSubPacket = false;
		boolean ende = false;
		boolean foundObject;

		int Infoscan_Value = Integer.parseInt(Common.getSettings().getProperty(Keys.KEY_ExportPanel_Infoscan_Value));
		int CutMode = Common.getSettings().getIntProperty(Keys.KEY_CutMode);
		int pes_streamtype = _pes_streamtype;
		int pes_payloadlength;
		int pes_packetlength;
		int pes_extensionlength;
		int pes_headerlength = 9;
		int pes_packetoffset = 6;
		int pes_extension2_id;
		int pesID;
		int subID;
		int offset;
		int returncode = 0;
		int vob_ID = 0;
		int cell_ID = 0;
		int ZeroPacketPayload = 0x17FA;

		int pesID0 = 0; //first video stream ID
		int dumped_packets = 0;

		job_processing.clearStatusVariables();
		int[] clv = job_processing.getStatusVariables();

		int[] newID = { 0x80, 0x90, 0xC0, 0xE0, 0xA0, 0x20 }; 

		byte[] pes_packet = new byte[0x10010];
		byte[] buffered_data;

		long count = 0;
		long base;
		long size = count;
		long qexit;
		long ptsoffset = 0;
		long lastpts = 0;
		long startPoint = 0;
		long starts[] = new long[collection.getPrimaryInputFileSegments()];
		long Overlap_Value = 1048576L * (Common.getSettings().getIntProperty(Keys.KEY_ExportPanel_Overlap_Value) + 1);

		vptslog = "-1"; //fix

		StreamDemultiplexer streamdemultiplexer = null;
		StreamConverter streamconverter = new StreamConverter();

		Hashtable substreams = new Hashtable();
		StandardBuffer sb;

		List demuxList = job_processing.getPrimaryPESDemuxList();

		/**
		 * re-read old streams, for next split part
		 */
		if (job_processing.getSplitPart() == 0) 
			demuxList.clear();

		else
		{
			for (int i = 0; i < demuxList.size(); i++)
			{
				streamdemultiplexer = (StreamDemultiplexer) demuxList.get(i);

				if (streamdemultiplexer.getnewID() != 0)
					newID[streamdemultiplexer.getType()]++;

				if (streamdemultiplexer.getNum() == -1) 
					continue;

				if (streamdemultiplexer.getType() == CommonParsing.MPEG_VIDEO)
				{ 
					streamdemultiplexer.initVideo2(fparent);

					if (pesID0 == 0) //?
						pesID0 = streamdemultiplexer.getID();
				}
				else 
					streamdemultiplexer.init2(fparent);
			}
		}

		String mpeg_type_str = (Keys.ITEMS_FileTypes[pes_streamtype]).toString().toLowerCase();
		mpeg_type_str = "[" + mpeg_type_str.substring(0, mpeg_type_str.indexOf(' ')) + "]";

		switch (action)
		{
		case CommonParsing.ACTION_TO_VDR:
			streamconverter.init(fparent + (job_processing.getSplitSize() == 0 ? mpeg_type_str : "") + ".vdr", MainBufferSize, action, job_processing.getSplitPart());
			break;

		case CommonParsing.ACTION_TO_M2P:
			streamconverter.init(fparent + (job_processing.getSplitSize() == 0 ? mpeg_type_str : "") + ".m2p", MainBufferSize, action, job_processing.getSplitPart());
			break;

		case CommonParsing.ACTION_TO_PVA:
			streamconverter.init(fparent + ".pva", MainBufferSize, action, job_processing.getSplitPart());
			break;

		case CommonParsing.ACTION_TO_TS:
			streamconverter.init(fparent + ".ts", MainBufferSize, action, job_processing.getSplitPart());
			break;

		case CommonParsing.ACTION_FILTER:
			streamconverter.init(fparent + "[filtered].pes", MainBufferSize, action, job_processing.getSplitPart());
		}

		/**
		 * d2v projectfile
	 	 */
		if (CreateD2vIndex || SplitProjectFile)
			job_processing.getProjectFileD2V().Init(fparent);

		job_processing.setMinBitrate(CommonParsing.MAX_BITRATE_VALUE);
		job_processing.setMaxBitrate(0);
		job_processing.setExportedVideoFrameNumber(0);
		job_processing.setEndPtsOfGop(-10000);
		job_processing.setSequenceHeader(true);
		job_processing.setAllMediaFilesExportLength(0);
		job_processing.setProjectFileExportLength(0);
		job_processing.setCutByteposition(0);

		/**
		 * pid inclusion 
		 */
		int[] predefined_Pids = collection.getPIDsAsInteger();
		int[] include = new int[predefined_Pids.length];

		for (int i = 0; i < include.length; i++) 
			include[i] = 0xFF & predefined_Pids[i];

		if (include.length > 0)
		{
			Arrays.sort(include);

			String str = " ";

			for (int i = 0; i < include.length; i++)
				str += "0x" + Integer.toHexString(include[i]).toUpperCase() + " ";

			Common.setMessage(Resource.getString("parsePrimaryPES.special.pes") + ": {" + str + "}");

			usePidfilter = true;
		}


		try {

			/**
			 * determine start & end byte pos. of each file segment
			 */
			for (int i = 0; i < starts.length; i++)
			{
				aXInputFile = (XInputFile) collection.getInputFile(i);
				starts[i] = size;
				size += aXInputFile.length();
			}

			aXInputFile = (XInputFile) collection.getInputFile(job_processing.getFileNumber());

			/**
			 * set start & end byte pos. of first file segment
			 */
			count = starts[job_processing.getFileNumber()];
			size = count + aXInputFile.length();

			/**
			 * split skipping first, for next split part
			 */
			if (job_processing.getSplitSize() > 0)
			{
				startPoint = job_processing.getLastHeaderBytePosition();
				startPoint -= !Overlap ? 0 : Overlap_Value;

				job_processing.setLastGopTimecode(0);
				job_processing.setLastGopPts(0);
				job_processing.setLastSimplifiedPts(0);
			}

			List CutpointList = collection.getCutpointList();
			List ChapterpointList = collection.getChapterpointList();

			/** 
			 * jump near to first cut-in point to collect more audio
			 */
			if (CutMode == CommonParsing.CUTMODE_BYTE && CutpointList.size() > 0 && CommonParsing.getCutCounter() == 0  && (!PreviewAllGops || action != CommonParsing.ACTION_DEMUX))
				startPoint = Long.parseLong(CutpointList.get(CommonParsing.getCutCounter()).toString()) - ((action == CommonParsing.ACTION_DEMUX) ? 2048000: 0);

			if (startPoint < 0)
				startPoint = count;  // =0

			else if (startPoint < count)
			{
				for (int i = starts.length; i > 0; i--)
					if (starts[i - 1] > startPoint)
						job_processing.countFileNumber(-1);
			}

			else if (startPoint > count)
			{
				for (int i = job_processing.getFileNumber() + 1; i < starts.length; i++)
				{
					if (starts[i] > startPoint)
						break;
					else 
						job_processing.countFileNumber(+1);
				}
			}

			aXInputFile = (XInputFile) collection.getInputFile(job_processing.getFileNumber());
			count = starts[job_processing.getFileNumber()];

			if (job_processing.getFileNumber() > 0)
				Common.setMessage(Resource.getString("parsePrimaryPES.continue") + ": " + aXInputFile);

			base = count;
			size = count + aXInputFile.length();

			PushbackInputStream in = new PushbackInputStream(aXInputFile.getInputStream(startPoint - base), pes_packet.length);

			count += (startPoint - base);

			Common.updateProgressBar((action == CommonParsing.ACTION_DEMUX ? Resource.getString("parsePrimaryPES.demuxing") : Resource.getString("parsePrimaryPES.converting")) + " " + Resource.getString("parsePrimaryPES.avpes.file") + " " + aXInputFile.getName(), (count - base), (size - base));

			qexit = count + (0x100000L * Infoscan_Value);

			//yield();

			bigloop:
			while (true)
			{
				loop:
				while (count < size)
				{
					pes_streamtype = _pes_streamtype;  // reset to original type

					Common.updateProgressBar(count, size);

					//yield();

					while (pause())
					{}

					if (CommonParsing.isProcessCancelled() || (CommonParsing.isInfoScan() && count > qexit))
					{ 
						CommonParsing.setProcessCancelled(false);
						job_processing.setSplitSize(0); 

						break bigloop; 
					}

					/**
					 * after last cut out, still read some packets to collect some audio
					 */
					if (job_processing.getCutComparePoint() + 20 < job_processing.getSourceVideoFrameNumber())
					{
						ende = true;
						break bigloop; 
					}

					/**
					 * cut mode bytepos + min 1 cutpoint
					 */
					if (CutMode == CommonParsing.CUTMODE_BYTE && CutpointList.size() > 0)
					{
						if (CommonParsing.getCutCounter() == CutpointList.size() && (CommonParsing.getCutCounter() & 1) == 0)
							if (count > Long.parseLong(CutpointList.get(CommonParsing.getCutCounter() - 1).toString()) + (action == CommonParsing.ACTION_DEMUX ? 2048000 : 64000))
							{
								ende = true;
								break bigloop;
							}
					}

					in.read(pes_packet, 0, pes_packetoffset);

					pesID = CommonParsing.getPES_IdField(pes_packet, 0);

					/**
					 *
					 */
					if ((returncode = CommonParsing.validateStartcode(pes_packet, 0)) < 0 || pesID < CommonParsing.SYSTEM_END_CODE)
					{
						returncode = returncode < 0 ? -returncode : 4;

						if (Message_2 && !missing_startcode)
							Common.setMessage(Resource.getString("parsePrimaryPES.missing.startcode") + " " + count);

						in.read(pes_packet, pes_packetoffset, pes_packet.length - pes_packetoffset);

						int i = returncode;

						for (; i < pes_packet.length - 3; )
						{
							returncode = CommonParsing.validateStartcode(pes_packet, i);

							if (returncode < 0)
							{
								i += -returncode;
								continue;
							}

							else
							{
								in.unread(pes_packet, i, pes_packet.length - i);

								count += i;
								missing_startcode = true;

								continue loop;
							}
						}

						in.unread(pes_packet, i, pes_packet.length - i);

						count += i;
						missing_startcode = true;

						continue loop;
					}

					if (Message_2 && missing_startcode)
						Common.setMessage(Resource.getString("parsePrimaryPES.found.startcode") + " " + count);

					missing_startcode = false;
	
					if (pes_streamtype == CommonParsing.MPEG1PS_TYPE || pes_streamtype == CommonParsing.MPEG2PS_TYPE || SimpleMPG)
					{
						switch (pesID)
						{
						case CommonParsing.SYSTEM_END_CODE: 
							in.unread(pes_packet, 4, 2);
							Common.setMessage("-> skip system_end_code @ " + count);
							count += 4;
							continue loop;

						case CommonParsing.PACK_START_CODE: 
							if ((0xC0 & pes_packet[4]) == 0)  //mpg1
							{
								in.skip(pes_packetoffset);
								count += 12;
								continue loop;
							}

							else if ((0xC0 & pes_packet[4]) == 0x40) //mpg2
							{
								in.read(pes_packet, pes_packetoffset, 8);

								offset = 7 & pes_packet[13];

								count += 14;

								in.read(pes_packet, 14, offset);

								if (offset > 0)
								{
									for (int i = 0; i < offset; i++)
										if (pes_packet[14 + i] != -1)
										{
											in.unread(pes_packet, 14, offset);

											Common.setMessage("!> wrong pack header stuffing @ " + count);
											missing_startcode = true;
											continue loop;
										}
								}

								count += offset;

								continue loop;
							}

							else
							{
								in.unread(pes_packet, 4, 2);
								count += 4;
								continue loop;
							}

						case CommonParsing.PRIVATE_STREAM_2_CODE: //determine cellids
							pes_payloadlength = CommonParsing.getPES_LengthField(pes_packet, 0);

							in.read(pes_packet, pes_packetoffset, pes_payloadlength);

							if (pes_packet[pes_packetoffset] == 1) //substream id 1
							{
								int cellid = 0xFF & pes_packet[0x22];
								int vobid = (0xFF & pes_packet[0x1F])<<8 | (0xFF & pes_packet[0x20]);

								if (cell_ID != cellid || vob_ID != vobid)
								{
									Common.setMessage(Resource.getString("parsePrimaryPES.split.cellids", "" + vobid, "" + cellid, "" + count, "" + clv[6], "" + job_processing.getExportedVideoFrameNumber()));

									if (Common.getSettings().getBooleanProperty(Keys.KEY_VOB_resetPts))
									{
										ptsoffset = nextFilePTS(collection, CommonParsing.PRIMARY_PES_PARSER, pes_streamtype, lastpts, job_processing.getFileNumber(), (count - base));

										if (ptsoffset == -1) 
											ptsoffset = 0; 

										else
										{
											for (int i = 0; i < demuxList.size(); i++)
											{
												streamdemultiplexer = (StreamDemultiplexer)demuxList.get(i);
												streamdemultiplexer.PTSOffset(ptsoffset);

												if (streamdemultiplexer.getID() == pesID0) 
													streamdemultiplexer.resetVideo();
											}

											job_processing.setSequenceHeader(true);
											job_processing.setNewVideoStream(true);
										//	job_processing.addCellTime(String.valueOf(job_processing.getExportedVideoFrameNumber()));
										}
									}
								}

								cell_ID = cellid;
								vob_ID = vobid;
							}

							count += (pes_packetoffset + pes_payloadlength);
							continue loop;

						case CommonParsing.SYSTEM_START_CODE:
						case CommonParsing.PROGRAM_STREAM_MAP_CODE:
						case CommonParsing.PADDING_STREAM_CODE:
						case CommonParsing.ECM_STREAM_CODE:
						case CommonParsing.EMM_STREAM_CODE:
						case CommonParsing.DSM_CC_STREAM_CODE:
						case 0xF3:
						case 0xF4:
						case 0xF5:
						case 0xF6:
						case 0xF7:
						case 0xF8:
						case 0xF9:
						case 0xFA:
						case 0xFB:
						case 0xFC:
						case 0xFD:
						case 0xFE:
						case 0xFF:
							pes_payloadlength = CommonParsing.getPES_LengthField(pes_packet, 0);

							in.skip(pes_payloadlength);

							count += (pes_packetoffset + pes_payloadlength);
							continue loop;
						}
					}

					if ( (0xF0 & pesID) != 0xE0 && (0xE0 & pesID) != 0xC0 && pesID != CommonParsing.PRIVATE_STREAM_1_CODE)
					{
						in.unread(pes_packet, 3, pes_packetoffset - 3);
						count += 3;
						continue loop;
					}

					/**
					 * mark for split at sequenceheader 
					 */
					job_processing.setLastHeaderBytePosition(count);
					job_processing.setCutByteposition(count);

					pes_payloadlength = CommonParsing.getPES_LengthField(pes_packet, 0);

					/**
					 * zeropackets of video only as PES from TS allowed
					 */
					isZeroPacket = pes_payloadlength == 0 && (0xF0 & pesID) == 0xE0;

					if (isZeroPacket)
						pes_payloadlength = ZeroPacketPayload;

					in.read(pes_packet, pes_packetoffset, pes_payloadlength + 4);

					pes_packetlength = pes_packetoffset + pes_payloadlength;

					/**
					 * determine next startcode in zero packets
					 */
					if (isZeroPacket)
					{
						if (Debug) 
							System.out.println("A " + Resource.getString("parsePrimaryPES.packet.length") + " " + count);

						for (int i = pes_packetoffset; isZeroPacket && i <= pes_packetlength; )
						{
							if ((returncode = CommonParsing.validateStartcode(pes_packet, i)) < 0 || CommonParsing.getPES_IdField(pes_packet, i) < CommonParsing.SYSTEM_END_CODE)
							{
								i += (returncode < 0 ? -returncode : 4);
								continue;
							}

							/**
							 * next header found before max. size of ZeroPacketPayload
							 * handle packet as normal, the 4 added bytes will be unread in the next startcode check
							 */
							in.unread(pes_packet, i + 4, pes_packetlength - i);

							pes_packetlength = i;
							pes_payloadlength = pes_packetlength - pes_packetoffset;

							isZeroPacket = false;
						}

						CommonParsing.setPES_LengthField(pes_packet, 0, pes_payloadlength);
					}

					/**
					 * check next startcode
					 */
					if (GetEnclosedPackets && !isZeroPacket && CommonParsing.validateStartcode(pes_packet, pes_packetlength) < 0)
					{
						if (count + pes_packetlength < size)
						{
							if (Message_2 && !missing_startcode)
								Common.setMessage(Resource.getString("parsePrimaryPES.miss.startcode2", String.valueOf(count + pes_packetlength), String.valueOf(count), Integer.toHexString(pesID).toUpperCase()));

							missing_startcode = true;

							in.unread(pes_packet, pes_packetoffset, pes_payloadlength + 4);
							count += pes_packetoffset;

							continue loop;
						}
					}

					else 
						in.unread(pes_packet, pes_packetlength, 4);


					clv[5]++;

					if (Debug) 
						System.out.print("\r"+Resource.getString("parsePrimaryPES.packs") + ": " + pesID + "/" + clv[5] + "/" + (pes_packetlength) + "/" + ((count * 100 / size)) + "% " + (count));

					pes_extensionlength = CommonParsing.getPES_ExtensionLengthField(pes_packet, 0);

					pes_isMpeg2 = (0xC0 & pes_packet[6]) == 0x80;
					pes_alignment = pes_isMpeg2 && (4 & pes_packet[6]) != 0;
					pes_scrambled = pes_isMpeg2 && (0x30 & pes_packet[6]) != 0;

					count += pes_packetlength;

					zeropacketloop:
					do {
						/**
						 * exit loop
						 */
						if (isZeroPacket && count >= size)
							break zeropacketloop;

						/**
						 * read videos zerosubpacket data in a loop, if packet data is greater than max. ZeroPacketPayload
						 * the first read header data is re-used, we read only the rest
						 */
						if (isZeroSubPacket)
						{
							if (Debug) 
								System.out.println("B " + Resource.getString("parsePrimaryPES.packet.length") + " " + count);

							pes_payloadlength = ZeroPacketPayload;

							pes_packetlength = pes_packetoffset + pes_payloadlength;

							offset = pes_isMpeg2 ? 3 : 1;

							in.read(pes_packet, pes_packetoffset + offset, pes_payloadlength - offset + 4);
							in.unread(pes_packet, pes_packetlength, 4);

							for (int i = pes_packetoffset + offset; isZeroPacket && i <= pes_packetlength; )
							{
								if ((returncode = CommonParsing.validateStartcode(pes_packet, i)) < 0 || CommonParsing.getPES_IdField(pes_packet, i) < CommonParsing.SYSTEM_END_CODE)
								{
									i += (returncode < 0 ? -returncode : 4);
									continue;
								}

								/**
								 * next header found before max. size of ZeroPacketPayload
								 * handle packet as normal, the 4 added bytes will be unread in the next startcode check
								 */
								in.unread(pes_packet, i, pes_packetlength - i);

								pes_packetlength = i;
								pes_payloadlength = pes_packetlength - pes_packetoffset;

								// set isZeroPacket to false, if next startcode was found
								isZeroPacket = false;
							}

							CommonParsing.setValue(pes_packet, pes_packetoffset, offset, false, pes_isMpeg2 ? (0xF3 & pes_packet[pes_packetoffset])<<16 : 0x0F); // values of pes scrambling and copyright/copy prot. are taken from orig.
							CommonParsing.setPES_LengthField(pes_packet, 0, pes_payloadlength);

							count += (pes_payloadlength - offset);
						}

						isZeroSubPacket = isZeroPacket;

						/**
						 * check scrambling
						 */
						if (IgnoreScrambledPackets)
						{
							// cannot work with scrambled data
							if (pes_scrambled)
							{
								if (!scrambling_messaged)
								{
									scrambling_messaged = true;

									Common.setMessage(Resource.getString("parseTS.scrambled", Integer.toHexString(pesID).toUpperCase(), String.valueOf(clv[5]), String.valueOf(count - pes_packetlength)));
								}

								continue zeropacketloop;
							}

							else if (scrambling_messaged)
							{
								Common.setMessage(Resource.getString("parseTS.clear", Integer.toHexString(pesID).toUpperCase(), String.valueOf(clv[5]), String.valueOf(count - pes_packetlength)));
								scrambling_messaged = false;
							}
						}

						/**
						 * vdr_dvbsub determination
						 */
						pes_extension2_id = CommonParsing.getExtension2_Id(pes_packet, pes_headerlength, pes_payloadlength, pesID, pes_isMpeg2);

						isTeletext = false;
						subID = 0;

						if (pesID == CommonParsing.PRIVATE_STREAM_1_CODE && pes_payloadlength > 2)
						{
							offset = pes_headerlength + pes_extensionlength;

							if (offset < pes_packetlength)
							{
								subID = 0xFF & pes_packet[offset];
								isTeletext = pes_extensionlength == 0x24 && subID>>>4 == 1; 

								//subpic in vdr_pes
								if (pes_alignment && !isTeletext && (subID>>>4 == 2 || subID>>>4 == 3))
									pes_streamtype = CommonParsing.MPEG2PS_TYPE;  //will be resetted before next packet

								if (pes_streamtype != CommonParsing.MPEG1PS_TYPE && pes_streamtype != CommonParsing.MPEG2PS_TYPE && !isTeletext) 
									subID = 0; //disables LPCM too 
							}

							else if (pes_streamtype != CommonParsing.MPEG1PS_TYPE) //?
							{
								pes_extensionlength = pes_payloadlength - 3;
								pes_packet[8] = (byte)(pes_extensionlength);
							}


							/**
							 * packet buffering esp. of subpics from vdr or other pes
							 */
							if (pes_extension2_id != -1)
							{
								String str = String.valueOf(pes_extension2_id);
								offset = pes_headerlength + pes_extensionlength;

								if ( !substreams.containsKey(str))
									substreams.put(str, new StandardBuffer());

								sb = (StandardBuffer) substreams.get(str);

								// buffer raw packet data
								if (!pes_alignment)
								{
									sb.write(pes_packet, offset, pes_packetlength - offset);
									continue zeropacketloop;
								}

								// start packet, buffer this and get last completed packet
								else
								{
									buffered_data = sb.getData();

									sb.reset();
									sb.write(pes_packet, 0, pes_packetlength);

									if (buffered_data == null || buffered_data.length < 10)
										continue zeropacketloop;

									pes_packetlength = buffered_data.length;

									if (pes_packetlength > 0x10005)
									{
										Common.setMessage("!> sub packet too long: 0x" + Integer.toHexString(pesID).toUpperCase() + " /ext2_id " + pes_extension2_id);
										pes_packetlength = 0x10005;
									}

									pes_payloadlength = pes_packetlength - pes_packetoffset;

									System.arraycopy(buffered_data, 0, pes_packet, 0, pes_packetlength);

									CommonParsing.setPES_LengthField(pes_packet, 0, pes_payloadlength);

									buffered_data = null;
								}
							}
						}

						/**
						 * pesID, subID inclusion 
						 */
						if (usePidfilter)
						{
							if (Arrays.binarySearch(include, pesID) < 0 && Arrays.binarySearch(include, subID) < 0)
								continue zeropacketloop;
						}

						/**
						 * raw id filter extraction
						 */
						if (action == CommonParsing.ACTION_FILTER)
						{
							if (subID != 0)
								pes_packet[6] |= 4; //set alignment

							streamconverter.write(job_processing, pes_packet, 0, pes_packetlength, null, job_processing.getCutByteposition(), CommonParsing.isInfoScan(), CutpointList);
							continue zeropacketloop;
						}

						foundObject = false;

						/**
						 * find ID object
						 */
						for (int i = 0; i < demuxList.size(); i++)
						{
							streamdemultiplexer = (StreamDemultiplexer) demuxList.get(i);

							foundObject = pesID == streamdemultiplexer.getID() && subID == streamdemultiplexer.subID() && isTeletext == streamdemultiplexer.isTTX();

							if (foundObject)
								break; 
						}

						/**
						 * create new ID object
						 */
						if (!foundObject)
						{
							/**
							 * dump startpacket 
							 */
							if (DumpDroppedGop)
							{
								String dumpname = fparent + "(" + Integer.toHexString(pesID) + "-" + Integer.toHexString(subID) + "#" + (dumped_packets++) + "@" + (count - pes_packetlength) + ").bin";

								FileOutputStream dump = new FileOutputStream(dumpname);

								dump.write(pes_packet, 0, pes_packetlength); 
								dump.flush(); 
								dump.close();

								Common.setMessage(Resource.getString("parsePrimaryPES.dump.1st") + ": " + dumpname);
							}

							String IDtype = "";

							switch (0xF0 & pesID)
							{
							case 0xE0:
								IDtype = Resource.getString("idtype.mpeg.video");

								streamdemultiplexer = new StreamDemultiplexer(ptsoffset);
								streamdemultiplexer.setID(pesID);
								streamdemultiplexer.setType(CommonParsing.MPEG_VIDEO);
								streamdemultiplexer.setnewID(newID[CommonParsing.MPEG_VIDEO]++);
								streamdemultiplexer.setsubID(0);
								streamdemultiplexer.setStreamType(pes_streamtype);

								demuxList.add(streamdemultiplexer);

								if (pesID0 == 0 || pesID0 == pesID)
								{ 
									if (action == CommonParsing.ACTION_DEMUX) 
										streamdemultiplexer.initVideo(fparent, MainBufferSize, demuxList.size(), CommonParsing.PRIMARY_PES_PARSER);

									else
										IDtype += " " + Resource.getString("idtype.mapped.to") + Integer.toHexString(streamdemultiplexer.getnewID()).toUpperCase();

									pesID0 = pesID;
								}

								else 
									IDtype += Resource.getString("idtype.ignored");

								break; 

							case 0xC0:
							case 0xD0:
								IDtype = Resource.getString("idtype.mpeg.audio"); 

								streamdemultiplexer = new StreamDemultiplexer(ptsoffset);
								streamdemultiplexer.setID(pesID);
								streamdemultiplexer.setType(CommonParsing.MPEG_AUDIO);
								streamdemultiplexer.setnewID(newID[CommonParsing.MPEG_AUDIO]++);
								streamdemultiplexer.setsubID(0);
								streamdemultiplexer.setStreamType(pes_streamtype);

								demuxList.add(streamdemultiplexer);

								if (action == CommonParsing.ACTION_DEMUX) 
									streamdemultiplexer.init(fparent, MainBufferSize / demuxList.size(), demuxList.size(), CommonParsing.PRIMARY_PES_PARSER);
	
								else
									IDtype += " " + Resource.getString("idtype.mapped.to") + Integer.toHexString(streamdemultiplexer.getnewID()).toUpperCase();

								break; 
							}

							switch (pesID)
							{
							case CommonParsing.PRIVATE_STREAM_1_CODE:
								IDtype = Resource.getString("idtype.private.stream");
								IDtype += (isTeletext ? " TTX " : "") + (subID != 0 ? " (SubID 0x" + Integer.toHexString(subID).toUpperCase() + ")" : ""); 

								streamdemultiplexer = new StreamDemultiplexer(ptsoffset);
								streamdemultiplexer.setID(pesID);
								streamdemultiplexer.setsubID(subID);
								streamdemultiplexer.setTTX(isTeletext);

								if (isTeletext)
								{
									streamdemultiplexer.setnewID(newID[CommonParsing.TELETEXT]++);
									streamdemultiplexer.setType(CommonParsing.TELETEXT);
								}

								else
								{
									switch(subID>>>4)
									{
									case 0:
										if (pes_streamtype != CommonParsing.MPEG1PS_TYPE && pes_streamtype != CommonParsing.MPEG2PS_TYPE)
										{
											streamdemultiplexer.setnewID(newID[CommonParsing.AC3_AUDIO]++);
											streamdemultiplexer.setType(CommonParsing.AC3_AUDIO);
										}
										break;

									case 8:
										streamdemultiplexer.setnewID(newID[CommonParsing.AC3_AUDIO]++);
										streamdemultiplexer.setType(CommonParsing.AC3_AUDIO);
										break;

									case 0xA:
										streamdemultiplexer.setnewID(newID[CommonParsing.LPCM_AUDIO]++);
										streamdemultiplexer.setType(CommonParsing.LPCM_AUDIO);
										break;

									case 2:
									case 3:
										streamdemultiplexer.setnewID(newID[CommonParsing.SUBPICTURE]++);
										streamdemultiplexer.setType(CommonParsing.SUBPICTURE);
										break;

									default:
										streamdemultiplexer.setType(CommonParsing.UNKNOWN);
									}
								}

								streamdemultiplexer.setStreamType(pes_streamtype);
								demuxList.add(streamdemultiplexer);

								if (action == CommonParsing.ACTION_DEMUX)
								{
									switch (streamdemultiplexer.getType())
									{
									case CommonParsing.AC3_AUDIO:
									case CommonParsing.DTS_AUDIO:
									case CommonParsing.TELETEXT:
									case CommonParsing.SUBPICTURE:
									case CommonParsing.LPCM_AUDIO:
										streamdemultiplexer.init(fparent, MainBufferSize / demuxList.size(), demuxList.size(), CommonParsing.PRIMARY_PES_PARSER);
										break;

									default:
										IDtype += Resource.getString("idtype.ignored");
									}
								}

								else
								{
									if (pes_streamtype != CommonParsing.MPEG1PS_TYPE && pes_streamtype != CommonParsing.MPEG2PS_TYPE)
										IDtype += " " + Resource.getString("idtype.mapped.to") + Integer.toHexString(streamdemultiplexer.getnewID()).toUpperCase();

									else if (isTeletext || subID>>>4 == 8)
										IDtype += " " + Resource.getString("idtype.mapped.to") + Integer.toHexString(streamdemultiplexer.getnewID()).toUpperCase();

									else
										IDtype += Resource.getString("idtype.ignored"); 
								}

								if (action == CommonParsing.ACTION_TO_VDR)
								{
									if ((pes_streamtype == CommonParsing.MPEG1PS_TYPE || pes_streamtype == CommonParsing.MPEG2PS_TYPE) && subID>>>4 != 8)
										IDtype += Resource.getString("idtype.ignored"); 
								}

								break; 
							}

							Common.setMessage(Resource.getString("parsePrimaryPES.found.pesid") + Integer.toHexString(pesID).toUpperCase() + " " + IDtype + " @ " + job_processing.getCutByteposition());
						}


						if (!streamdemultiplexer.StreamEnabled())
							continue zeropacketloop;

						/**
						 * packet to streamdemultiplexer
						 */
						if (action == CommonParsing.ACTION_DEMUX)
						{
							if (streamdemultiplexer.getType() == CommonParsing.MPEG_VIDEO)
							{ 
								if (pesID0 != streamdemultiplexer.getID()) 
									continue zeropacketloop;

								streamdemultiplexer.writeVideo(job_processing, pes_packet, 0, pes_packetlength, true, CutpointList, ChapterpointList);
							}
							else 
								streamdemultiplexer.write(job_processing, pes_packet, 0, pes_packetlength, true);

							if (streamdemultiplexer.getPTS() > lastpts) 
								lastpts = streamdemultiplexer.getPTS();
						}

						/**
						 * packet to streamconverter
						 */
						else
							streamconverter.write(job_processing, pes_packet, streamdemultiplexer, job_processing.getCutByteposition(), CommonParsing.isInfoScan(), CutpointList);

					} while (isZeroPacket);


					if (action != CommonParsing.ACTION_DEMUX) 
						job_processing.setLastHeaderBytePosition(count);

					/**
					 * split size reached 
					 */
					if (job_processing.getSplitSize() > 0 && job_processing.getSplitSize() < job_processing.getAllMediaFilesExportLength()) 
						break loop;
				}


				if (job_processing.getSplitSize() > 0 && job_processing.getSplitSize() < job_processing.getAllMediaFilesExportLength())
					break bigloop;

				/**
				 * next file segment
				 */
				if (job_processing.getFileNumber() < collection.getPrimaryInputFileSegments() - 1)
				{ 
					in.close();
					//System.gc();

					if (Common.getSettings().getBooleanProperty(Keys.KEY_Input_concatenateForeignRecords) && action == CommonParsing.ACTION_DEMUX)
					{
						ptsoffset = nextFilePTS(collection, CommonParsing.PRIMARY_PES_PARSER, pes_streamtype, lastpts, job_processing.getFileNumber() + 1);

						if (ptsoffset == -1) 
							ptsoffset = 0; 

						else
						{
							for (int i = 0; i < demuxList.size(); i++)
							{
								streamdemultiplexer = (StreamDemultiplexer) demuxList.get(i);
								streamdemultiplexer.PTSOffset(ptsoffset);

								if (streamdemultiplexer.getID() == pesID0) // ??
									streamdemultiplexer.resetVideo();
							}

							job_processing.setSequenceHeader(true);
							job_processing.setNewVideoStream(true);
					//		job_processing.addCellTime(String.valueOf(job_processing.getExportedVideoFrameNumber()));
						}
					}

					XInputFile nextXInputFile = (XInputFile) collection.getInputFile(job_processing.countFileNumber(+1));
					count = size;
					base = count;

					size += nextXInputFile.length();
					in = new PushbackInputStream(nextXInputFile.getInputStream(), 0x10010);

					Common.setMessage(Resource.getString("parsePrimaryPES.actual.written") + " " + job_processing.getExportedVideoFrameNumber());
					Common.setMessage(Resource.getString("parsePrimaryPES.switch") + " " + nextXInputFile + " (" + Common.formatNumber(nextXInputFile.length()) + " bytes) @ " + base);

					Common.updateProgressBar((action == CommonParsing.ACTION_DEMUX ? Resource.getString("parsePrimaryPES.demuxing") : Resource.getString("parsePrimaryPES.converting")) + " " + Resource.getString("parsePrimaryPES.avpes.file") + " " + nextXInputFile.getName());
				}

				else 
					break bigloop;

			}


			/**
			 * file end reached for split 
			 */
			if ( (count >= size || ende) && job_processing.getSplitSize() > 0 )
				job_processing.setSplitLoopActive(false);

			in.close(); 


			if (action != CommonParsing.ACTION_DEMUX) 
				streamconverter.close(job_processing, CommonParsing.isInfoScan());

			else
			{
				for (int i = 0, NumberOfVideostreams = 0; i < demuxList.size(); i++)
				{
					streamdemultiplexer = (StreamDemultiplexer) demuxList.get(i);

					if (streamdemultiplexer.getType() == CommonParsing.MPEG_VIDEO)
					{ 
						/**
						 * accept only first video
						 */
						if (NumberOfVideostreams > 0)
						{
							Common.setMessage("!> further videostream found (ID 0x" + Integer.toHexString(streamdemultiplexer.getID()).toUpperCase() + ") -> ignored");
							continue;
						}

						/**
						 * d2v project 
						 */
						if (CreateD2vIndex || SplitProjectFile)
							job_processing.getProjectFileD2V().write(job_processing.getProjectFileExportLength(), job_processing.getExportedVideoFrameNumber());

						Common.setMessage("");
						Common.setMessage(Resource.getString("video.msg.summary") + " " + job_processing.getExportedVideoFrameNumber() + "/ " + clv[0] + "/ " + clv[1] + "/ " + clv[2] + "/ " + clv[3] + "/ " + clv[4]);

						vptslog = streamdemultiplexer.closeVideo(job_processing, collection.getOutputDirectory() + collection.getFileSeparator());

						NumberOfVideostreams++;
					}
				} 

				//System.gc();

				int[] stream_number = new int[10]; 

				for (int i = 0, es_streamtype; i < demuxList.size(); i++)
				{
					streamdemultiplexer = (StreamDemultiplexer) demuxList.get(i);
					es_streamtype = streamdemultiplexer.getType();

					if (es_streamtype ==CommonParsing.MPEG_VIDEO) 
						continue;

					String[] values = streamdemultiplexer.close(job_processing, vptslog);

					if (values[0].equals("")) 
					{
						Common.setMessage(Resource.getString("parsePrimaryPES.msg.noexport") + Integer.toHexString(streamdemultiplexer.getID()).toUpperCase() + ")");
						continue;
					}

					String newfile = values[3] + (stream_number[es_streamtype] > 0 ? ("[" + stream_number[es_streamtype] + "]") : "") + "." + values[2];

					Common.renameTo(values[0], newfile);
		
					values[0] = newfile;
					values[3] = vptslog;

					switch (es_streamtype)
					{
					case CommonParsing.AC3_AUDIO:
					case CommonParsing.DTS_AUDIO:
						if (streamdemultiplexer.subID() != 0 && (0xF0 & streamdemultiplexer.subID()) != 0x80) 
							break;

						Common.setMessage("");
						Common.setMessage(Resource.getString("parsePrimaryPES.ac3") + " " + (streamdemultiplexer.subID() != 0 ? ("(SubID 0x" + Integer.toHexString(streamdemultiplexer.subID()).toUpperCase() + ")") : ""));

						new StreamProcess(es_streamtype, collection, values[0], values[1], values[2], values[3]);
						break;

					case CommonParsing.TELETEXT: 
						Common.setMessage("");
						Common.setMessage(Resource.getString("parsePrimaryPES.teletext") + Integer.toHexString(streamdemultiplexer.subID()).toUpperCase() + ")");

						new StreamProcess(es_streamtype, collection, values[0], values[1], values[2], values[3]);
						break;

					case CommonParsing.MPEG_AUDIO: 
						Common.setMessage("");
						Common.setMessage(Resource.getString("parsePrimaryPES.mpeg.audio") + Integer.toHexString(streamdemultiplexer.getID()).toUpperCase() + ")");

						new StreamProcess(es_streamtype, collection, values[0], values[1], values[2], values[3]);
						break;
	
					case CommonParsing.LPCM_AUDIO:
						Common.setMessage("");
						Common.setMessage(Resource.getString("parsePrimaryPES.lpcm.audio") + Integer.toHexString(streamdemultiplexer.subID()).toUpperCase() + ")");

						new StreamProcess(es_streamtype, collection, values[0], values[1], values[2], values[3]);
						break;

					case CommonParsing.SUBPICTURE:
						Common.setMessage("");
						Common.setMessage(Resource.getString("parsePrimaryPES.subpic") + Integer.toHexString(streamdemultiplexer.subID()).toUpperCase() + ")");

						new StreamProcess(es_streamtype, collection, values[0], values[1], values[2], values[3]);
						break;
					}

					stream_number[es_streamtype]++;

					new File(newfile).delete();
					new File(values[1]).delete();
				}
			}

		} catch (IOException e2) { 

			Common.setExceptionMessage(e2);
		}

		return vptslog;
	}

}
