diff options
author | Sven Göthel <[email protected]> | 2024-02-02 08:27:49 +0100 |
---|---|---|
committer | Sven Göthel <[email protected]> | 2024-02-02 08:27:49 +0100 |
commit | 23cf5279472d3ae1b2d8d1904e6b1f1e7fd8f012 (patch) | |
tree | 7f943da4a1a6c8fd917eff160b45b700a125f258 /src/jogl/classes/com/jogamp/opengl | |
parent | 9ff736464e0d2762c424bab66bc6d03ccc6e6d11 (diff) |
Bug 1494 - GLMediaPlayer/GraphUI: Support Displaying Bitmap'ed Subtitles (PGS ..) via FFMPEGFMediaPlayer/FFmpeg
FFMPEGFMediaPlayer related changes:
- Add libswscale (6th FFmpeg lib used) for sws_getCachedContext(), sws_scale() and sws_freeContext(),
used natively to convert the palette'ed bitmap into RGBA colorspace -> GL texture
- Handling AVSubtitleRect.type SUBTITLE_BITMAP
-- only handled if libswscale is available
-- config/adjust texture object
-- sws_scale palette'ed bitmap to texture
-- intermediate memory is cached, may be resized and free'ed at destroy
-- texture objects are managed and passed from GLMediaPlayerImpl,
as they are also forwarded to player client via SubBitmapEvent
- Passing the AVCodecID to GLMediaPlayerImpl, converted to our CodecID enum.
- Unifying creation and opening of AVCodecContext with 'createOpenedAVCodecContext(..)'
+++
SubtitleEvent*
- SubTextEvent now also handles ASS.Dialogue (FFmpeg 4)
besides ASS.Event (FFmpeg 5, 6, ..).
+++
GLMediaPlayerImpl
- Added ringbuffer subTexFree, managing Texture for bitmap'ed subtitles
-- Uses 1 bitmap-subtitle Texture per used textureCount in cache,
as one bitmap-subtile can be displayed per frame.
Could be potentially reduced to just 2 .. but resources used are
relatively low here.
- Validating subTexFree + videoFramesFree usage,
use blocking get/put ringbuffer due to utilization from different threads.
- Receives subtitle content from native getNextPacket0() via callback,
creates SubtitleEvent instance and passes it to a SubtitleEventListener - if exists.
(See MediaButton example)
-- SubBitmapEvent also gets its special SubBitmapEvent.TextureOwner to handle client releasing
the event and allowing us to put back the Texture resource to 'subTexFree'.
This passing through of the Texture object is probably a weakness of this lifecycle
and requires the client to ensure SubtitleEvent.release() gets called.
See MediaButton example!
- Exposing CodecID, allowing clients like MediaButton to handle SubtitleEvent content according to codec
Diffstat (limited to 'src/jogl/classes/com/jogamp/opengl')
6 files changed, 299 insertions, 176 deletions
diff --git a/src/jogl/classes/com/jogamp/opengl/util/av/GLMediaPlayer.java b/src/jogl/classes/com/jogamp/opengl/util/av/GLMediaPlayer.java index 2767ea7ef..4011bddcb 100644 --- a/src/jogl/classes/com/jogamp/opengl/util/av/GLMediaPlayer.java +++ b/src/jogl/classes/com/jogamp/opengl/util/av/GLMediaPlayer.java @@ -40,6 +40,7 @@ import com.jogamp.common.av.AudioSink; import com.jogamp.common.av.PTS; import com.jogamp.common.av.TimeFrameI; import com.jogamp.common.net.Uri; +import com.jogamp.math.Vec4f; import com.jogamp.opengl.util.texture.Texture; import com.jogamp.opengl.util.texture.TextureSequence; @@ -286,7 +287,7 @@ public interface GLMediaPlayer extends TextureSequence { } @Override public String toString() { - return String.format("%02d: [%s .. %s] %s", id, PTS.millisToTimeStr(start), PTS.millisToTimeStr(end), title); + return String.format("%02d: [%s .. %s] %s", id, PTS.toTimeStr(start), PTS.toTimeStr(end), title); } } @@ -326,17 +327,19 @@ public interface GLMediaPlayer extends TextureSequence { /** Attribute change bits */ public static enum Bit { /** State changed to {@link State#Initialized}. See <a href="#lifecycle">Lifecycle</a>.*/ - Init ( 1<<0 ), + Init ( 1<<0 ), /** State changed to {@link State#Uninitialized}. See <a href="#lifecycle">Lifecycle</a>.*/ Uninit ( 1<<1 ), /** State changed to {@link State#Playing}. See <a href="#lifecycle">Lifecycle</a>.*/ - Play ( 1<<2 ), + Play ( 1<<2 ), /** State changed to {@link State#Paused}. See <a href="#lifecycle">Lifecycle</a>.*/ Pause ( 1<<3 ), + /** Time position has changed, e.g. via {@link GLMediaPlayer#seek(int)}.*/ + Seek ( 1<<4 ), /** End of stream reached. See <a href("#lifecycle">Lifecycle</a>.*/ - EOS ( 1<<4 ), + EOS ( 1<<5 ), /** An error occurred, e.g. during off-thread initialization. See {@link StreamException} and <a href("#lifecycle">Lifecycle</a>. */ - Error ( 1<<5 ), + Error ( 1<<6 ), /** Stream video id change. */ VID ( 1<<16 ), @@ -352,8 +355,14 @@ public interface GLMediaPlayer extends TextureSequence { BPS ( 1<<21 ), /** Stream length change. */ Length ( 1<<22 ), - /** Stream codec change. */ - Codec ( 1<<23 ); + /** Audio, video or subtitle stream codec change. */ + Codec ( 1<<23 ), + /** Audio stream codec change. */ + ACodec ( 1<<24 ), + /** Video stream codec change. */ + VCodec ( 1<<25 ), + /** Subtitle stream codec change. */ + SCodec ( 1<<26 ); Bit(final int v) { value = v; } public final int value; @@ -833,18 +842,36 @@ public interface GLMediaPlayer extends TextureSequence { /** * <i>Warning:</i> Optional information, may not be supported by implementation. + * @return the {@link CodecID} of the video stream, if available + */ + public CodecID getVideoCodecID(); + + /** + * <i>Warning:</i> Optional information, may not be supported by implementation. * @return the codec of the video stream, if available */ public String getVideoCodec(); /** * <i>Warning:</i> Optional information, may not be supported by implementation. + * @return the {@link CodecID} of the audio stream, if available + */ + public CodecID getAudioCodecID(); + + /** + * <i>Warning:</i> Optional information, may not be supported by implementation. * @return the codec of the audio stream, if available */ public String getAudioCodec(); /** * <i>Warning:</i> Optional information, may not be supported by implementation. + * @return the {@link CodecID} of the subtitle stream, if available + */ + public CodecID getSubtitleCodecID(); + + /** + * <i>Warning:</i> Optional information, may not be supported by implementation. * @return the codec of the subtitle stream, if available */ public String getSubtitleCodec(); diff --git a/src/jogl/classes/com/jogamp/opengl/util/av/SubBitmapEvent.java b/src/jogl/classes/com/jogamp/opengl/util/av/SubBitmapEvent.java index 68f25d046..0032aeffc 100644 --- a/src/jogl/classes/com/jogamp/opengl/util/av/SubBitmapEvent.java +++ b/src/jogl/classes/com/jogamp/opengl/util/av/SubBitmapEvent.java @@ -61,20 +61,13 @@ public class SubBitmapEvent extends SubtitleEvent { * @param owner {@link Texture} owner code-stub to release the texture */ public SubBitmapEvent(final CodecID codec, final Vec2i pos, final Vec2i dim, final Texture tex, final int pts_start, final int pts_end, final TextureOwner owner) { - super(codec, pts_start, pts_end); + super(SubtitleEvent.Type.Bitmap, codec, pts_start, pts_end); position = pos; dimension = dim; texture = tex; this.owner = owner; } - @Override - public final boolean isTextASS() { return false; } - @Override - public final boolean isBitmap() { return true; } - @Override - public final boolean isEmpty() { return false; } - /** * {@inheritDoc} * <p> diff --git a/src/jogl/classes/com/jogamp/opengl/util/av/SubEmptyEvent.java b/src/jogl/classes/com/jogamp/opengl/util/av/SubEmptyEvent.java index c49558c57..d6796dc4d 100644 --- a/src/jogl/classes/com/jogamp/opengl/util/av/SubEmptyEvent.java +++ b/src/jogl/classes/com/jogamp/opengl/util/av/SubEmptyEvent.java @@ -34,17 +34,10 @@ public class SubEmptyEvent extends SubtitleEvent { * Empty event ctor */ public SubEmptyEvent(final int pts_start, final int pts_end) { - super(CodecID.NONE, pts_start, pts_end); + super(SubtitleEvent.Type.Empty, CodecID.NONE, pts_start, pts_end); } @Override - public final boolean isTextASS() { return false; } - @Override - public final boolean isBitmap() { return false; } - @Override - public final boolean isEmpty() { return true; } - - @Override public void release() {} // nothing to be released back to the owner @Override diff --git a/src/jogl/classes/com/jogamp/opengl/util/av/SubTextEvent.java b/src/jogl/classes/com/jogamp/opengl/util/av/SubTextEvent.java new file mode 100644 index 000000000..d699d9300 --- /dev/null +++ b/src/jogl/classes/com/jogamp/opengl/util/av/SubTextEvent.java @@ -0,0 +1,243 @@ +/** + * Copyright 2024 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package com.jogamp.opengl.util.av; + +import java.time.format.DateTimeParseException; + +import com.jogamp.common.av.PTS; + +/** + * Text Event Line including ASS/SAA of {@link SubtitleEvent} + * <p> + * See http://www.tcax.org/docs/ass-specs.htm + * </p> + */ +public class SubTextEvent extends SubtitleEvent { + /** Text formatting */ + public enum TextFormat { + /** Multiple ASS formats may be passed, see {@link ASSType}. */ + ASS, + /** Just plain text */ + TEXT, + }; + /** ASS Formatting Type */ + public enum ASSType { + /** + * ASS dialogue-line output w/ start and end (Given by FFmpeg 4.*) + * <pre> + 0 1 2 3 4 5 6 7 8 9 + Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text + + 'Dialogue: 0,0:02:02.15,0:02:02.16,Default,,0,0,0,,trying to force him to travel to that' + * </pre> + */ + DIALOGUE, + /** + * FFMpeg ASS event-line output w/o start, end (Given by FFmpeg 5.*, 6.*, ..) + * <pre> + 0 1 2 3 4 5 6 7 8 + Seq, Layer, Style, Name, MarginL, MarginR, MarginV, Effect, TEXT + * </pre> + */ + EVENT, + /** Just plain text */ + TEXT + } + /** {@link TextFormat} of this text subtitle event. */ + public final TextFormat textFormat; + /** {@link ASSType} sub-type */ + public final ASSType assType; + /** Start time in milliseconds, or -1. */ + public final int start; + /** End time in milliseconds, or -1. */ + public final int end; + public final String style; + + public final int seqnr; + public final int layer; + + public final String name; + public final String effect; + /** Actual subtitle text */ + public final String text; + /** Number of lines of {@link #text}, i.e. occurrence of {@code \n} + 1. */ + public final int lines; + + private static boolean DEBUG = false; + + /** + * ASS/SAA Event Line ctor + * @param codec the {@link CodecID} + * @param fmt input format of {@code ass}, currently only {@link SubTextEvent.TextFormat#ASS} and {@link SubTextEvent.TextFormat#TEXT} is supported + * @param ass ASS/SAA compatible event line according to {@link ASSType} + * @param pts_start pts start in ms, provided for {@link SubTextEvent.TextFormat#ASS} and {@link SubTextEvent.TextFormat#TEXT} + * @param pts_end pts end in ms, provided for {@link SubTextEvent.TextFormat#ASS} and {@link SubTextEvent.TextFormat#TEXT} + */ + public SubTextEvent(final CodecID codec, final TextFormat fmt, final String ass, final int pts_start, final int pts_end) { + super(SubtitleEvent.Type.Text, codec, pts_start, pts_end); + this.textFormat = fmt; + ASSType assType = ASSType.TEXT; + int start = -1; + int end = -1; + int seqnr = 0; + int layer = 0; + String style = "Default"; + String name = ""; + String effect = ""; + String text = ""; + boolean done = false; + if( TextFormat.ASS == fmt ) { + final int len = null != ass ? ass.length() : 0; + { + // ASSType.DIALOGUE + int part = 0; + for(int i=0; 10 > part && len > i; ) { + if( 9 == part ) { + text = ass.substring(i); + done = true; + assType = ASSType.DIALOGUE; + } else { + final int j = ass.indexOf(',', i); + if( 0 > j ) { + break; + } + final String v = ass.substring(i, j); + try { + switch(part) { + case 1: + start = PTS.toMillis(v, true); + break; + case 2: + end = PTS.toMillis(v, true); + break; + case 3: + style = v; + break; + case 4: + name = v; + break; + case 8: + effect = v; + break; + } + } catch(final DateTimeParseException pe) { + if( DEBUG ) { + System.err.println("ASS.DIALG parsing error of part "+part+" '"+v+"' of '"+ass+"'"); + } + break; + } + i = j + 1; + } + ++part; + } + } + if( !done ) { + // ASSType.EVENT + int part = 0; + for(int i=0; 9 > part && len > i; ) { + if( 8 == part ) { + text = ass.substring(i); + done = true; + assType = ASSType.EVENT; + } else { + final int j = ass.indexOf(',', i); + if( 0 > j ) { + break; + } + final String v = ass.substring(i, j); + try { + switch(part) { + case 0: + seqnr = Integer.valueOf(v); + break; + case 1: + layer = Integer.valueOf(v); + break; + case 2: + style = v; + break; + case 3: + name = v; + break; + case 7: + effect = v; + break; + } + } catch(final NumberFormatException nfe) { + if( DEBUG ) { + System.err.println("ASS.EVENT parsing error of part "+part+" '"+v+"' of '"+ass+"'"); + } + break; + } + i = j + 1; + } + ++part; + } + } + } + if( !done && TextFormat.TEXT == fmt ) { + text = ass; + done = true; + assType = ASSType.TEXT; + } + this.assType = assType; + this.start = start; + this.end = end; + this.seqnr = seqnr; + this.layer = layer; + this.style = style; + this.name = name; + this.effect = effect; + this.text = text.replace("\\N", "\n"); + { + final int len = this.text.length(); + int lc = 1; + for(int i=0; len > i; ) { + final int j = this.text.indexOf("\n", i); + if( 0 > j ) { + break; + } + ++lc; + i = j + 1; + } + this.lines = lc; + } + } + + @Override + public void release() {} // nothing to be released back to the owner + + @Override + public String toString() { + final String start_s = 0 <= start ? PTS.toTimeStr(start, true) : "undef"; + final String end_s = 0 <= end ? PTS.toTimeStr(end, true) : "undef"; + final String fms_s = TextFormat.ASS == textFormat ? "ASS("+assType+")" : textFormat.toString(); + return getStartString()+", "+fms_s+", #"+seqnr+", l_"+layer+ + ", ["+start_s+".."+end_s+"], style "+style+", name '"+name+"', effect '"+effect+"': '"+text+"' ("+lines+")]"; + } +} diff --git a/src/jogl/classes/com/jogamp/opengl/util/av/SubTextEventLine.java b/src/jogl/classes/com/jogamp/opengl/util/av/SubTextEventLine.java deleted file mode 100644 index c867dea9c..000000000 --- a/src/jogl/classes/com/jogamp/opengl/util/av/SubTextEventLine.java +++ /dev/null @@ -1,142 +0,0 @@ -/** - * Copyright 2024 JogAmp Community. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are - * permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of - * conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list - * of conditions and the following disclaimer in the documentation and/or other materials - * provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND - * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * The views and conclusions contained in the software and documentation are those of the - * authors and should not be interpreted as representing official policies, either expressed - * or implied, of JogAmp Community. - */ -package com.jogamp.opengl.util.av; - -/** - * Text Event Line including ASS/SAA of {@link SubtitleEvent} - * <p> - * See http://www.tcax.org/docs/ass-specs.htm - * </p> - */ -public class SubTextEventLine extends SubtitleEvent { - public enum Format { - /** Denoting {@link SubASSEventLine} using FFMpeg output w/o start, end: - * <pre> - 0 1 2 3 4 5 6 7 8 - Seq, Layer, Style, Name, MarginL, MarginR, MarginV, Effect, TEXT - * </pre> - */ - ASS_FFMPEG, - /** Denoting {@link SubASSEventLine}, just the plain text part */ - TEXT, - }; - /** {@link Format} of this text subtitle event. */ - public final Format format; - public final int seqnr; - public final int layer; - public final String style; - public final String name; - /** Actual subtitle text */ - public final String text; - /** Number of lines of {@link #text}, i.e. occurrence of {@code \n} + 1. */ - public final int lines; - - /** - * ASS/SAA Event Line ctor - * @param codec the {@link CodecID} - * @param fmt input format of {@code ass}, currently only {@link SubTextEventLine.Format#ASS_FFMPEG} and {@link SubTextEventLine.Format#TEXT} is supported - * @param ass ASS/SAA compatible event line according to {@code fmt} - * @param pts_start pts start in ms, provided for {@link SubTextEventLine.Format#ASS_FFMPEG} and {@link SubTextEventLine.Format#TEXT} - * @param pts_end pts end in ms, provided for {@link SubTextEventLine.Format#ASS_FFMPEG} and {@link SubTextEventLine.Format#TEXT} - */ - public SubTextEventLine(final CodecID codec, final Format fmt, final String ass, final int pts_start, final int pts_end) { - super(codec, pts_start, pts_end); - this.format = fmt; - int seqnr = 0; - int layer = 0; - String style = "Default"; - String name = ""; - String text = ""; - if( Format.ASS_FFMPEG == fmt ) { - final int len = null != ass ? ass.length() : 0; - int part = 0; - for(int i=0; 9 > part && len > i; ) { - if( 8 == part ) { - text = ass.substring(i); - } else { - final int j = ass.indexOf(',', i); - if( 0 > j ) { - break; - } - final String v = ass.substring(i, j); - switch(part) { - case 0: - seqnr = Integer.valueOf(v); - break; - case 1: - layer = Integer.valueOf(v); - break; - case 2: - style = v; - break; - case 3: - name = v; - break; - } - i = j + 1; - } - ++part; - } - } else if( Format.TEXT == fmt ) { - text = ass; - } - this.seqnr = seqnr; - this.layer = layer; - this.style = style; - this.name = name; - this.text = text.replace("\\N", "\n"); - { - final int len = this.text.length(); - int lc = 1; - for(int i=0; len > i; ) { - final int j = this.text.indexOf("\n", i); - if( 0 > j ) { - break; - } - ++lc; - i = j + 1; - } - this.lines = lc; - } - } - - @Override - public final boolean isTextASS() { return true; } - @Override - public final boolean isBitmap() { return false; } - @Override - public final boolean isEmpty() { return false; } - - @Override - public void release() {} // nothing to be released back to the owner - - @Override - public String toString() { - return getStartString()+", "+format+", #"+seqnr+", l_"+layer+", style "+style+", name '"+name+"': '"+text+"' ("+lines+")]"; - } -} diff --git a/src/jogl/classes/com/jogamp/opengl/util/av/SubtitleEvent.java b/src/jogl/classes/com/jogamp/opengl/util/av/SubtitleEvent.java index f24246a70..e75fff3f8 100644 --- a/src/jogl/classes/com/jogamp/opengl/util/av/SubtitleEvent.java +++ b/src/jogl/classes/com/jogamp/opengl/util/av/SubtitleEvent.java @@ -1,5 +1,7 @@ package com.jogamp.opengl.util.av; +import com.jogamp.common.av.PTS; + /** * Generic subtitle event * <p> @@ -8,12 +10,23 @@ package com.jogamp.opengl.util.av; * </p> */ public abstract class SubtitleEvent { + /** {@link SubtitleEvent} Implementation Type */ + public enum Type { + /** {@link SubTextEvent} */ + Text, + /** {@link SubBitmapEvent} */ + Bitmap, + /** {@link SubEmptyEvent} */ + Empty + }; + /** Implementation {@link Type} of this instance. */ + public final Type type; /** {@link CodecID} of this subtitle event. */ public final CodecID codec; - /** PTS start time to start showing this subtitle event. */ + /** PTS start time in milliseconds to start showing this subtitle event. */ public final int pts_start; /** - * PTS start time to end showing this subtitle event. + * PTS end time in milliseconds to end showing this subtitle event. * <p> * {@link SubBitmapEvent} often (e.g. {@link CodecID#HDMV_PGS}) have an infinite end-time, i.e. ({@link Integer#MAX_VALUE}, * and shall be overwritten by the next one or {@link SubEmptyEvent}. @@ -22,7 +35,8 @@ public abstract class SubtitleEvent { */ public final int pts_end; - public SubtitleEvent(final CodecID codec, final int pts_start, final int pts_end) { + public SubtitleEvent(final Type type, final CodecID codec, final int pts_start, final int pts_end) { + this.type = type; this.codec = codec; this.pts_start = pts_start; this.pts_end = pts_end; @@ -36,16 +50,11 @@ public abstract class SubtitleEvent { /** See {@link #pts_end}. */ public final boolean isEndDefined() { return pts_end < Integer.MAX_VALUE; } - /** Returns {@code true} if Text/ASS/SAA subtitle type, o.e. {@link SubTextEvent}. */ - public abstract boolean isTextASS(); - /** Returns {@code true} if bitmap subtitle type, o.e. {@link SubBitmapEvent}. */ - public abstract boolean isBitmap(); - /** Returns {@code true} if empty subtitle type, o.e. {@link SubEmptyEvent}. */ - public abstract boolean isEmpty(); - public final String getStartString() { final boolean ied = isEndDefined(); - return "Sub["+codec+", ["+pts_start+".."+(ied?pts_end:"undef")+"] "+(ied?getDuration():"undef")+" ms"; + final String pts_start_s = 0 <= pts_start ? PTS.toTimeStr(pts_start, true) : "undef"; + final String pts_end_s = 0 <= pts_end && ied ? PTS.toTimeStr(pts_end, true) : "undef"; + return "Sub[codec "+codec+", type "+type+", ["+pts_start_s+".."+pts_end_s+"] "+(ied?getDuration():"undef")+" ms"; } |