/* * @(#)StreamParser * * Copyright (c) 2005-2009 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 * */ /* * multicolor subtitling patch by Duncan (Shannock9) UK * 2008-12 */ 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.RandomAccessFile; import java.io.ByteArrayOutputStream; import java.io.BufferedOutputStream; import java.io.FileOutputStream; import java.io.PrintStream; import java.util.Arrays; import java.util.List; import java.util.ArrayList; import java.util.Hashtable; import java.util.Date; import java.util.TimeZone; import java.util.Enumeration; import java.util.StringTokenizer; import java.text.DateFormat; import java.text.SimpleDateFormat; 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.IDDBufferedOutputStream; import net.sourceforge.dvb.projectx.xinput.XInputFile; 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.StreamProcessBase; import net.sourceforge.dvb.projectx.subtitle.Subpicture; import net.sourceforge.dvb.projectx.subtitle.BMP; import net.sourceforge.dvb.projectx.subtitle.Bitmap; import net.sourceforge.dvb.projectx.subtitle.Sup2VobSub; import net.sourceforge.dvb.projectx.subtitle.ColorAreas; //S9 import net.sourceforge.dvb.projectx.thirdparty.Ifo; /** * main thread */ public class StreamProcessSubpicture extends StreamProcessBase { private final String subdecode_errors[] = { "", "", // -1 = correct decoded dvb-subpicture segments, can export "", // -2 = correct decoded dvb-subpicture segments, w/o export Resource.getString("subpicture.msg.error3"), // -3 = error while decoding dvb-subpicture Resource.getString("subpicture.msg.error4"), Resource.getString("subpicture.msg.error5"), Resource.getString("subpicture.msg.error6"), Resource.getString("subpicture.msg.error7"), Resource.getString("subpicture.msg.error8"), Resource.getString("subpicture.msg.error9") }; private String Extension = ".new"; private boolean debug; private boolean KeepOriginalTimecode; private boolean ExportAsVobSub; private boolean UseAdditionalOffset; private boolean ShowSubpictureWindow; private boolean Message_2; private int AdditionalOffset_Value; private int X_Offset = 0; private int Y_Offset = 0; private int DisplayMode = 0; private int ExportType = 0; private int Pictures = 0; private int LastPGCSet = 0; private String SubpictureColorModel; private String PageId_Value; private String SubtitleExportFormat; private String FileParent; /** * */ public StreamProcessSubpicture(String extension) { super(); Extension = extension; } /** * */ public StreamProcessSubpicture(JobCollection collection, XInputFile xInputFile, String filename_pts, String filename_type, String videofile_pts, int isElementaryStream) { super(); get_XY_Offset(collection, isElementaryStream); getDisplayMode(collection, isElementaryStream); String SubtitleExportFormat = collection.getSettings().getProperty(Keys.KEY_SubtitleExportFormat); processStream(collection, xInputFile, filename_pts, filename_type, videofile_pts, isElementaryStream, SubtitleExportFormat); // 2nd export format, new run SubtitleExportFormat = collection.getSettings().getProperty(Keys.KEY_SubtitleExportFormat_2); if (!SubtitleExportFormat.equalsIgnoreCase("null")) processStream(collection, xInputFile, filename_pts, filename_type, videofile_pts, isElementaryStream, SubtitleExportFormat); } /** * set new X with or without offset */ public void set_XY_Offset(int x_value, int y_value) { X_Offset = x_value; Y_Offset = y_value; } /** * set new X with or without offset */ private void get_XY_Offset(JobCollection collection, int isElementaryStream) { //if (isElementaryStream != CommonParsing.ES_TYPE) // return; StringTokenizer st = new StringTokenizer(collection.getSettings().getProperty(Keys.KEY_SubtitleMovePosition_Value), ","); int a = 0; int[] values = new int[2]; while (st.hasMoreTokens() && a < values.length) { try { values[a] = Integer.parseInt(st.nextToken()); } catch (Exception e) {} a++; } X_Offset = values[0]; Y_Offset = values[1]; } /** * set new display to be forced or not */ private void getDisplayMode(JobCollection collection, int isElementaryStream) { //if (isElementaryStream != CommonParsing.ES_TYPE) // return; DisplayMode = collection.getSettings().getIntProperty(Keys.KEY_SubtitleChangeDisplay); } /** * decoding subpicture stream, forces SUP, simple method for modifying a SUP stream from teletext process */ public void processStream(JobCollection collection, XInputFile xInputFile, int isElementaryStream) { processStream(collection, xInputFile, "-1", "sp", "-1", isElementaryStream, Keys.ITEMS_SubtitleExportFormat[7].toString()); } /** * decoding subpicture stream */ private void processStream(JobCollection collection, XInputFile xInputFile, String filename_pts, String filename_type, String videofile_pts, int isElementaryStream, String SubtitleExportFormat) { Subpicture subpicture = Common.getSubpictureClass(); JobProcessing job_processing = collection.getJobProcessing(); String fchild = isElementaryStream == CommonParsing.ES_TYPE ? collection.getOutputName(xInputFile.getName()) : xInputFile.getName(); FileParent = collection.getOutputNameParent(fchild); FileParent += isElementaryStream == CommonParsing.ES_TYPE ? Extension : ""; String subfile = FileParent + ".sup"; long size = xInputFile.length(); byte[] parse12 = new byte[12]; byte[] packet = new byte[0]; long count = 0; long startPoint = 0; long time_difference = 0; long display_time = 0; long source_pts = 0; long new_pts = 0; long first_pts = -1; long last_pts = 0; int x = 0; int v = 0; int packetlength = 0; ExportType = 0; Pictures = 0; LastPGCSet = 0; boolean vptsdata = false; boolean ptsdata = false; boolean write = false; boolean missing_syncword = false; boolean DVBpicture = false; debug = collection.getSettings().getBooleanProperty(Keys.KEY_DebugLog); KeepOriginalTimecode = isElementaryStream == CommonParsing.ES_TYPE ? true : collection.getSettings().getBooleanProperty(Keys.KEY_SubtitlePanel_keepOriginalTimecode); ExportAsVobSub = collection.getSettings().getBooleanProperty(Keys.KEY_SubtitlePanel_exportAsVobSub); UseAdditionalOffset = collection.getSettings().getBooleanProperty(Keys.KEY_additionalOffset); ShowSubpictureWindow = collection.getSettings().getBooleanProperty(Keys.KEY_showSubpictureWindow); Message_2 = collection.getSettings().getBooleanProperty(Keys.KEY_MessagePanel_Msg2); AdditionalOffset_Value = collection.getSettings().getIntProperty(Keys.KEY_ExportPanel_additionalOffset_Value); SubpictureColorModel = collection.getSettings().getProperty(Keys.KEY_SubpictureColorModel); PageId_Value = collection.getSettings().getProperty(Keys.KEY_SubtitlePanel_PageId_Value); // SubtitleExportFormat = collection.getSettings().getProperty(Keys.KEY_SubtitleExportFormat); try { if (ShowSubpictureWindow) Common.getGuiInterface().showSubpicture(); Hashtable user_table = new Hashtable(); ArrayList subpicture_colormodel = Common.getColorModelsList(); if (subpicture_colormodel.indexOf(SubpictureColorModel) > 2) // 0,1,2 internal user_table = Common.getUserColourTable(SubpictureColorModel); subpicture.reset(); subpicture.dvb.setIRD(2<<subpicture_colormodel.indexOf(SubpictureColorModel), user_table, debug, PageId_Value); Common.setMessage(Resource.getString("subpicture.msg.model", SubpictureColorModel) + " " + PageId_Value); if (SubtitleExportFormat.equalsIgnoreCase(Keys.ITEMS_SubtitleExportFormat[6].toString())) { subfile = FileParent + ".son"; ExportType = 1; } // Common.setMessage(""); Common.setMessage(Resource.getString("subpicture.msg.output") + " " + subfile.substring(subfile.length() - 3)); PushbackInputStream in = new PushbackInputStream(xInputFile.getInputStream(), 65536); IDDBufferedOutputStream out = new IDDBufferedOutputStream(new FileOutputStream(subfile),65536); PrintStream print_out = new PrintStream(out); Common.setMessage(Resource.getString("subpicture.msg.tmpfile", xInputFile.getName(), "" + size)); // SUP with changed settings if (ExportType == 0) { subpicture.set_XY_Offset(X_Offset, Y_Offset); subpicture.setDisplayMode(DisplayMode); } if (X_Offset != 0 || Y_Offset != 0) Common.setMessage("-> move source picture position: X " + X_Offset + ", Y " + Y_Offset); if (DisplayMode != 0) Common.setMessage("-> set new picture display mode: " + Keys.ITEMS_SubtitleChangeDisplay[DisplayMode]); Common.updateProgressBar(Resource.getString("subpicture.progress") + " " + xInputFile.getName(), 0, 0); long[] ptsval = {0}; long[] ptspos = {0}; long[] vptsval = {0}; long pts_offset = UseAdditionalOffset ? 90L * AdditionalOffset_Value : 0; //System.gc(); long[][] obj = loadTempOtherPts(filename_pts, "subpicture.msg.discard", "audio.msg.pts.firstonly", "subpicture.msg.pts.start_end", "", CommonParsing.SUBPICTURE, false, debug); if (obj != null) { ptsval = obj[0]; ptspos = obj[1]; ptsdata = true; obj = null; } ptsdata = true; obj = loadTempVideoPts(videofile_pts, debug); if (obj != null) { vptsval = obj[0]; vptsdata = true; obj = null; } //System.gc(); if (vptsdata && ptsdata) { int jump = checkPTSMatch(vptsval, ptsval); if (jump < 0) { Common.setMessage(Resource.getString("subpicture.msg.pts.mismatch")); vptsdata = false; x = 0; } else x = jump; } if (vptsdata && ptsdata) { Common.setMessage(Resource.getString("subpicture.msg.adjust.at.video")); time_difference = vptsval[0]; } if (!vptsdata && ptsdata) { Common.setMessage(Resource.getString("subpicture.msg.adjust.at.own")); time_difference = 0; } if (ptsdata) { source_pts = ptsval[x]; startPoint = ptspos[x]; } //don't need it anymore ptsval = null; ptspos = null; while (count < startPoint) count += in.skip(startPoint-count); //yield(); readloop: while ( count < size ) { Common.updateProgressBar(count, size); //yield(); while (pause()) {} if (CommonParsing.isProcessCancelled()) { CommonParsing.setProcessCancelled(false); job_processing.setSplitSize(0); break readloop; } in.read(parse12, 0, 12); if (parse12[0] != 0x53 || parse12[1] != 0x50) // find "SP" { if (Message_2 && !missing_syncword) Common.setMessage(Resource.getString("subpicture.msg.syncword.lost") + " " + count); missing_syncword = true; count++; in.unread(parse12, 1, 11); continue readloop; } if (Message_2 && missing_syncword) Common.setMessage(Resource.getString("subpicture.msg.syncword.found") + " " + count); in.unread(parse12, 0, 12); missing_syncword = false; packetlength = ((0xFF & parse12[10])<<8 | (0xFF & parse12[11])) + 10; packet = new byte[packetlength]; in.read(packet); count += packetlength; source_pts = 0; for (int a = 0; a < 5; a++) // 5bytes for pts, maybe wrong source_pts |= (0xFFL & packet[2+a])<<(a*8); //DM15072004 081.7 int06 add, use add. time offset if not applied by class-piddemux (e.g. ES) if (filename_pts.equals("-1")) source_pts += pts_offset; // if (source_pts == pts_offset) // first pts would be 0, ever if (source_pts == pts_offset && first_pts != -1) source_pts = last_pts; else last_pts = source_pts; //remember pts of 1st packet if (first_pts == -1) first_pts = source_pts; if (debug) System.out.println(" " + (count - packetlength) + "/ " + packetlength + "/ " + source_pts); if (ptsdata) { write = !vptsdata; rangeloop: while (vptsdata && v < vptsval.length) //pic_start_pts must be in range ATM { if (source_pts < vptsval[v]) break rangeloop; else if (source_pts == vptsval[v] || source_pts < vptsval[v + 1]) { write = true; break rangeloop; } v += 2; if (v < vptsval.length) time_difference += (vptsval[v] - vptsval[v-1]); } } else write = true; if (!vptsdata && time_difference == 0 && !KeepOriginalTimecode) time_difference = source_pts; new_pts = source_pts - time_difference; if ((display_time = subpicture.decode_picture(packet, 10, Common.getGuiInterface().isSubpictureVisible(), job_processing.getStatusStrings(), new_pts, write, Common.getGuiInterface().isSubpictureVisible())) < -2) Common.setMessage(Resource.getString("subpicture.msg.error", subdecode_errors[Math.abs((int)display_time)], String.valueOf(count - packetlength))); if (debug) System.out.println("PTS: source " + Common.formatTime_1(source_pts / 90) + "(" + source_pts + ")" + " /new " + Common.formatTime_1(new_pts / 90) + "(" + new_pts + ")" + " / write: " + write + " / dec.state: " + display_time); if (display_time < 0) //dvb_subpic { if (!DVBpicture) Common.setMessage(Resource.getString("subpicture.msg.dvbsource")); DVBpicture = true; if (display_time == -1) // -1 full data, -2 forced end_time process_dvbsubpicture(job_processing, print_out, out, subpicture, subfile, new_pts); } else if (write) //dvd_subpic { for (int a = 0; a < 8; a++) packet[2 + a] = (byte)(0xFFL & new_pts>>>(a * 8)); //later, to allow overlapping on cut boundaries //if (display_time > 0) // packet = subpicture.setTime(packet,display_time); out.write(packet); Common.getGuiInterface().showExportStatus(Resource.getString("subpicture.status"), ++Pictures); Common.getGuiInterface().setSubpictureTitle(" " + Resource.getString("subpicture.preview.title.dvdexport", "" + Pictures, Common.formatTime_1(new_pts / 90)) + " " + Common.formatTime_1(display_time / 90)); String str = subpicture.isForced_Msg(); if (str != null) Common.setMessage(str + " " + Resource.getString("subpicture.msg.forced") + " " + Pictures); } else Common.getGuiInterface().setSubpictureTitle(" " + Resource.getString("subpicture.preview.title.noexport")); if (debug) { System.out.println("-> wr " + write + " /v " + v + " /npts " + new_pts + " /tdif " + time_difference + " /pic " + Pictures + " /dtim " + display_time); System.out.println(""); } } // check whether there is still a pic waiting from dvb subpic , assume max 10sec display time if (display_time == -2) { // if ((display_time = subpicture.decode_picture(packet, 10, Common.getGuiInterface().isSubpictureVisible(), job_processing.getStatusStrings(), new_pts + 1024000, write, Common.getGuiInterface().isSubpictureVisible())) < -2) if ((display_time = subpicture.decode_picture(packet, 10, Common.getGuiInterface().isSubpictureVisible(), job_processing.getStatusStrings(), -1, write, Common.getGuiInterface().isSubpictureVisible())) < -2) Common.setMessage(Resource.getString("subpicture.msg.error", subdecode_errors[Math.abs((int)display_time)], String.valueOf(count - packetlength))); if (debug) System.out.println("last picture in memory PTS: source " + Common.formatTime_1(source_pts / 90) + "(" + source_pts + ")" + " /new " + Common.formatTime_1((new_pts) / 90) + "(" + new_pts + ")" + " / write: " + write + " / dec.state: " + display_time); if (display_time == -1) process_dvbsubpicture(job_processing, print_out, out, subpicture, subfile, new_pts); } in.close(); print_out.flush(); print_out.close(); out.flush(); out.close(); if (filename_pts.equals("-1")) Common.setMessage(Resource.getString("subpicture.msg.pts.start_end", Common.formatTime_1(first_pts / 90)) + " " + Common.formatTime_1(source_pts / 90)); Common.setMessage(Resource.getString("subpicture.msg.summary", "" + Pictures)); if (!DVBpicture && ExportType == 1) { String renamed_file = subfile.substring(0, subfile.length() - 3) + "sup"; Common.renameTo(subfile, renamed_file); subfile = renamed_file; } File subfile1 = new File(subfile); if (Pictures == 0) subfile1.delete(); else { if (DVBpicture && ExportType == 0) { if (ColorAreas.active) //multicolor DVB to SUP active //S9 job_processing.countMediaFilesExportLength(Ifo.createIfo(subfile, ColorAreas.clut_pgc)); //S9 else job_processing.countMediaFilesExportLength(Ifo.createIfo(subfile, subpicture.getUserColorTableArray())); //S9 } else if (DVBpicture && ExportType == 1) job_processing.countMediaFilesExportLength(new File( BMP.write_ColorTable(FileParent, subpicture.getUserColorTable(), 256)).length()); Common.setMessage(Resource.getString("msg.newfile") + " " + subfile); job_processing.countMediaFilesExportLength(subfile1.length()); job_processing.addSummaryInfo(Resource.getString("subpicture.summary", Common.adaptString(job_processing.countPictureStream(), 2), "" + Pictures, infoPTSMatch(filename_pts, videofile_pts, vptsdata, ptsdata)) + "'" + subfile1 + "'"); //vobsub if (ExportType == 0 && ExportAsVobSub) new Sup2VobSub(subfile, subpicture.getUserColorTableArray()); } Common.updateProgressBar(size, size); } catch (IOException e2) { Common.setExceptionMessage(e2); } if (ShowSubpictureWindow) Common.getGuiInterface().hideSubpicture(); ColorAreas.active = false; //S9 } private void process_dvbsubpicture(JobProcessing job_processing, PrintStream print_out, IDDBufferedOutputStream out, Subpicture subpicture, String subfile, long new_pts) { try { String num = "00000" + Pictures; String outfile_base = FileParent + "_st" + num.substring(num.length() - 5); String key, object_id_str, outfile; int object_id; for (Enumeration e = BMP.getKeys(); e.hasMoreElements() ; ) { key = e.nextElement().toString(); object_id = Integer.parseInt(key); object_id_str = Integer.toHexString(object_id).toUpperCase(); outfile = outfile_base + "p" + object_id_str; Bitmap bitmap = BMP.getBitmap(object_id); if (ExportType == 0) //.sup out.write( subpicture.writeRLE(bitmap)); else //.son + .bmp { if (Pictures == 0) { String[] SONhead = Common.getTeletextClass().getSONHead(new File(subfile).getParent(), (long)CommonParsing.getVideoFramerate()); for (int a=0; a < SONhead.length; a++) print_out.println(SONhead[a]); } subpicture.updateUserColorTable(bitmap); outfile = BMP.buildBMP_palettized(outfile, bitmap, subpicture.getUserColorTable(), 256); job_processing.countMediaFilesExportLength(new File(outfile).length()); int pgc_values = subpicture.setPGClinks(); // a change in color_links if ((0xFFFF & pgc_values) != (0xFFFF & LastPGCSet)) { String pgc_colors = ""; for (int a = 0; a < 4; a++) pgc_colors += "" + (0xF & pgc_values>>>(a * 4)) + " "; print_out.println("Color\t\t(" + pgc_colors.trim() + ")"); } // a change in alpha_links if ((0xFFFF0000 & pgc_values) != (0xFFFF0000 & LastPGCSet)) { String pgc_alphas = ""; for (int a = 0; a < 4; a++) pgc_alphas += "" + (0xF & pgc_values>>>((4 + a) * 4)) + " "; print_out.println("Contrast\t(" + pgc_alphas.trim() + ")"); } LastPGCSet = pgc_values; print_out.println("Display_Area\t(" + Common.adaptString(bitmap.getX(), 3) + " " + Common.adaptString(bitmap.getY(), 3) + " " + Common.adaptString(bitmap.getMaxX(), 3) + " " + Common.adaptString(bitmap.getMaxY(), 3) + ")"); print_out.println(outfile_base.substring(outfile_base.length() - 4) + "\t\t" + Common.formatTime_2(bitmap.getInTime() / 90, (long)CommonParsing.getVideoFramerate()) + "\t" + Common.formatTime_2((bitmap.getInTime() / 90) + (bitmap.getPlayTime() * 10), (long)CommonParsing.getVideoFramerate()) + "\t" + new File(outfile).getName()); if (debug) System.out.println("-> " + outfile); } Common.getGuiInterface().setSubpictureTitle(" " + Resource.getString("subpicture.preview.title.dvbexport", "" + bitmap.getPageId(), "" + Pictures, Common.formatTime_1(new_pts / 90)) + " " + Common.formatTime_1(bitmap.getPlayTime() * 10)); } if (!BMP.isEmpty()) Common.getGuiInterface().showExportStatus(Resource.getString("subpicture.status"), ++Pictures); BMP.clear(); } catch (IOException e) { Common.setExceptionMessage(e); } } }