Skip to content

Commit e87807e

Browse files
cfsmp3claude
andcommitted
feat(wtv): Add DVB teletext stream detection in WTV files
This commit adds detection and basic handling of DVB teletext streams in WTV (Windows TV) files. Previously, teletext streams were silently ignored. Changes: - Add WTV_STREAM_TELETEXT GUID to wtv_constants.h - Detect teletext streams by examining the format GUID at offset 0x4C in MSTVCAPTION stream metadata - Initialize teletext decoder when teletext stream is found - Add timing support for teletext streams - Wrap teletext data in PES headers for the teletext decoder Limitation: WTV files store teletext in Microsoft's VBI sample format, which differs from standard DVB teletext data units. The decoder will process the data but may not extract subtitles from all WTV files. This is noted in a warning message shown when teletext is detected. Even FFmpeg's libzvbi fails to decode this format in the test sample. Addresses: #1391 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent 87c8984 commit e87807e

File tree

2 files changed

+102
-4
lines changed

2 files changed

+102
-4
lines changed

src/lib_ccx/wtv_constants.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
#define WTV_STREAM_VIDEO "\x76\x69\x64\x73\x00\x00\x10\x00\x80\x00\x00\xAA\x00\x38\x9B\x71"
77
#define WTV_STREAM_AUDIO "\x61\x75\x64\x73\x00\x00\x10\x00\x80\x00\x00\xAA\x00\x38\x9B\x71"
88
#define WTV_STREAM_MSTVCAPTION "\x89\x8A\x8B\xB8\x49\xB0\x80\x4C\xAD\xCF\x58\x98\x98\x5E\x22\xC1"
9+
// DVB Teletext stream type (VBI teletext data in PES format)
10+
#define WTV_STREAM_TELETEXT "\xE3\x76\x2A\xF7\x0A\xEB\xD0\x11\xAC\xE4\x00\x00\xC0\xCC\x16\xBA"
911
#define WTV_EOF "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
1012
#define WTV_TIMING "\x5B\x05\xE6\x1B\x97\xA9\x49\x43\x88\x17\x1A\x65\x5A\x29\x8A\x97"
1113

src/lib_ccx/wtv_functions.c

Lines changed: 100 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,16 @@ int check_stream_id(int stream_id, int video_streams[], int num_streams)
3636
return 0;
3737
}
3838

39+
// Check if the passed stream_id is a teletext stream
40+
int check_teletext_stream_id(int stream_id, int teletext_streams[], int num_teletext_streams)
41+
{
42+
int x;
43+
for (x = 0; x < num_teletext_streams; x++)
44+
if (teletext_streams[x] == stream_id)
45+
return 1;
46+
return 0;
47+
}
48+
3949
// Init passes wtv_chunked_buffer struct
4050
void init_chunked_buffer(struct wtv_chunked_buffer *cb)
4151
{
@@ -335,9 +345,12 @@ int read_header(struct ccx_demuxer *ctx, struct wtv_chunked_buffer *cb)
335345
LLONG get_data(struct lib_ccx_ctx *ctx, struct wtv_chunked_buffer *cb, struct demuxer_data *data)
336346
{
337347
static int video_streams[32];
348+
static int teletext_streams[32];
338349
static int alt_stream; // Stream to use for timestamps if the cc stream has broken timestamps
339350
static int use_alt_stream = 0;
340351
static int num_streams = 0;
352+
static int num_teletext_streams = 0;
353+
static int has_teletext = 0; // Flag to indicate we found teletext streams
341354
int64_t result;
342355
struct lib_cc_decode *dec_ctx = update_decoder_list(ctx);
343356

@@ -403,12 +416,18 @@ LLONG get_data(struct lib_ccx_ctx *ctx, struct wtv_chunked_buffer *cb, struct de
403416
{
404417
// The WTV_STREAM2 GUID appears near the start of the data dir
405418
// It maps stream_ids to the type of stream
419+
// Structure based on WTV file analysis:
420+
// Offset 0x0C: mediatype GUID (16 bytes)
421+
// Offset 0x4C: teletext format GUID (16 bytes) - for MSTVCAPTION streams
422+
// We read enough data (96 bytes) to get all the info we need
406423
dbg_print(CCX_DMT_PARSE, "WTV STREAM2\n");
407-
get_sized_buffer(ctx->demux_ctx, cb, 0xc + 16);
424+
// Read 96 bytes to get mediatype at 0x0C and format_subtype at 0x4C
425+
uint32_t read_size = (len > 96) ? 96 : len;
426+
get_sized_buffer(ctx->demux_ctx, cb, read_size);
408427
if (cb->buffer == NULL)
409428
return CCX_EOF;
410429
static unsigned char stream_type[16];
411-
memcpy(&stream_type, cb->buffer + 0xc, 16); // Read the stream type GUID
430+
memcpy(&stream_type, cb->buffer + 0xc, 16); // Read the mediatype GUID at offset 12
412431
const void *stream_guid;
413432
if (ccx_options.wtvmpeg2)
414433
stream_guid = WTV_STREAM_VIDEO; // We want mpeg2 data if the user set -wtvmpeg2
@@ -419,11 +438,40 @@ LLONG get_data(struct lib_ccx_ctx *ctx, struct wtv_chunked_buffer *cb, struct de
419438
video_streams[num_streams] = stream_id; // We keep a list of stream ids
420439
num_streams++; // Even though there should only be 1
421440
}
441+
// For MSTVCAPTION streams, check if it's teletext by examining the format GUID
442+
// The teletext GUID appears at offset 0x4C from the start of the chunk data
443+
if (!memcmp(stream_type, WTV_STREAM_MSTVCAPTION, 16) && read_size >= 0x4C + 16)
444+
{
445+
static unsigned char format_subtype[16];
446+
memcpy(&format_subtype, cb->buffer + 0x4C, 16); // Read format GUID at offset 0x4C
447+
dbg_print(CCX_DMT_PARSE, "MSTVCAPTION format_subtype=%02X%02X%02X%02X...\n",
448+
format_subtype[0], format_subtype[1], format_subtype[2], format_subtype[3]);
449+
// Check for teletext
450+
if (!memcmp(format_subtype, WTV_STREAM_TELETEXT, 16))
451+
{
452+
dbg_print(CCX_DMT_PARSE, "Found DVB Teletext stream, stream_id: 0x%X\n", stream_id);
453+
mprint("WTV: Found DVB Teletext stream (stream_id=0x%X)\n", stream_id);
454+
mprint(" Note: WTV teletext uses Microsoft VBI format which may not decode correctly.\n");
455+
teletext_streams[num_teletext_streams] = stream_id;
456+
num_teletext_streams++;
457+
has_teletext = 1;
458+
// Initialize teletext decoder context
459+
if (!dec_ctx->private_data)
460+
{
461+
dec_ctx->codec = CCX_CODEC_TELETEXT;
462+
dec_ctx->private_data = telxcc_init();
463+
if (!dec_ctx->private_data)
464+
{
465+
mprint("Error: Failed to initialize teletext decoder\n");
466+
}
467+
}
468+
}
469+
}
422470
if (memcmp(stream_type, WTV_STREAM_AUDIO, 16))
423471
alt_stream = stream_id;
424-
len -= 28;
472+
len -= read_size;
425473
}
426-
if (!memcmp(guid, WTV_TIMING, 16) && ((use_alt_stream < WTV_CC_TIMESTAMP_MAGIC_THRESH && check_stream_id(stream_id, video_streams, num_streams)) || (use_alt_stream == WTV_CC_TIMESTAMP_MAGIC_THRESH && stream_id == alt_stream)))
474+
if (!memcmp(guid, WTV_TIMING, 16) && ((use_alt_stream < WTV_CC_TIMESTAMP_MAGIC_THRESH && (check_stream_id(stream_id, video_streams, num_streams) || check_teletext_stream_id(stream_id, teletext_streams, num_teletext_streams))) || (use_alt_stream == WTV_CC_TIMESTAMP_MAGIC_THRESH && stream_id == alt_stream)))
427475
{
428476
int64_t time;
429477
// The WTV_TIMING GUID contains a timestamp for the given stream_id
@@ -478,6 +526,54 @@ LLONG get_data(struct lib_ccx_ctx *ctx, struct wtv_chunked_buffer *cb, struct de
478526
}
479527
return bytesread;
480528
}
529+
// Handle DVB Teletext data
530+
// Note: WTV teletext format is Microsoft-specific and differs from DVB teletext.
531+
// The data is not in standard DVB teletext data unit format, so decoding support
532+
// is currently limited. The stream is detected and passed to the decoder which
533+
// will process what it can parse.
534+
if (!memcmp(guid, WTV_DATA, 16) && check_teletext_stream_id(stream_id, teletext_streams, num_teletext_streams) && dec_ctx->timing->current_pts != 0)
535+
{
536+
get_sized_buffer(ctx->demux_ctx, cb, len);
537+
if (cb->buffer == NULL)
538+
return CCX_EOF;
539+
540+
// WTV teletext data is raw VBI data, not PES-encapsulated.
541+
// Wrap it in a PES header for the teletext decoder.
542+
uint16_t pes_len = len + 8; // payload + header fields after length
543+
int64_t pts = dec_ctx->timing->current_pts;
544+
545+
// Add PES header (14 bytes)
546+
data->buffer[data->len++] = 0x00; // start code
547+
data->buffer[data->len++] = 0x00;
548+
data->buffer[data->len++] = 0x01;
549+
data->buffer[data->len++] = 0xBD; // Private Stream 1
550+
data->buffer[data->len++] = (pes_len >> 8) & 0xFF;
551+
data->buffer[data->len++] = pes_len & 0xFF;
552+
data->buffer[data->len++] = 0x80; // PES flags
553+
data->buffer[data->len++] = 0x80; // PTS present
554+
data->buffer[data->len++] = 0x05; // header data length
555+
556+
// Encode PTS (33-bit value in 5 bytes)
557+
data->buffer[data->len++] = (uint8_t)(0x21 | ((pts >> 29) & 0x0E));
558+
data->buffer[data->len++] = (uint8_t)((pts >> 22) & 0xFF);
559+
data->buffer[data->len++] = (uint8_t)(0x01 | ((pts >> 14) & 0xFE));
560+
data->buffer[data->len++] = (uint8_t)((pts >> 7) & 0xFF);
561+
data->buffer[data->len++] = (uint8_t)(0x01 | ((pts << 1) & 0xFE));
562+
563+
// Add teletext data
564+
memcpy(data->buffer + data->len, cb->buffer, len);
565+
data->len += len;
566+
bytesread += (int)(14 + len);
567+
data->codec = CCX_CODEC_TELETEXT;
568+
data->bufferdatatype = CCX_TELETEXT;
569+
frames_since_ref_time++;
570+
set_fts(dec_ctx->timing);
571+
if (pad > 0)
572+
{
573+
skip_sized_buffer(ctx->demux_ctx, cb, pad);
574+
}
575+
return bytesread;
576+
}
481577
if (len + pad > 0)
482578
{
483579
// skip any remaining data

0 commit comments

Comments
 (0)