/***************************************************************************** * stl.c: EBU STL demuxer ***************************************************************************** * Copyright (C) 2010 Laurent Aimar * $Id$ * * Authors: Laurent Aimar * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser 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. *****************************************************************************/ /***************************************************************************** * Preamble *****************************************************************************/ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include /***************************************************************************** * Module descriptor *****************************************************************************/ static int Open (vlc_object_t *); static void Close(vlc_object_t *); vlc_module_begin() set_description(N_("EBU STL subtitles parser")) set_category(CAT_INPUT) set_subcategory(SUBCAT_INPUT_DEMUX) set_capability("demux", 1) set_callbacks(Open, Close) add_shortcut("stl", "subtitle") vlc_module_end() /***************************************************************************** * Local definitions/prototypes *****************************************************************************/ typedef struct { vlc_tick_t start; vlc_tick_t stop; size_t blocknumber; size_t count; } stl_entry_t; struct demux_sys_t { size_t count; stl_entry_t *index; es_out_id_t *es; size_t current; int64_t next_date; bool b_slave; bool b_first_time; }; static size_t ParseInteger(uint8_t *data, size_t size) { char tmp[16]; assert(size < sizeof(tmp)); memcpy(tmp, data, size); tmp[size] = '\0'; return strtol(tmp, NULL, 10); } static int64_t ParseTimeCode(uint8_t *data, double fps) { return INT64_C(1000000) * (data[0] * 3600 + data[1] * 60 + data[2] * 1 + data[3] / fps); } static int64_t ParseTextTimeCode(uint8_t *data, double fps) { uint8_t tmp[4]; for (int i = 0; i < 4; i++) tmp[i] = ParseInteger(&data[2 * i], 2); return ParseTimeCode(tmp, fps); } static int Control(demux_t *demux, int query, va_list args) { demux_sys_t *sys = demux->p_sys; switch(query) { case DEMUX_CAN_SEEK: return vlc_stream_vaControl(demux->s, query, args); case DEMUX_GET_LENGTH: { int64_t *l = va_arg(args, int64_t *); *l = sys->count > 0 ? sys->index[sys->count-1].stop : 0; return VLC_SUCCESS; } case DEMUX_GET_TIME: { int64_t *t = va_arg(args, int64_t *); *t = sys->next_date - var_GetInteger(demux->obj.parent, "spu-delay"); if( *t < 0 ) *t = sys->next_date; return VLC_SUCCESS; } case DEMUX_SET_NEXT_DEMUX_TIME: { sys->b_slave = true; sys->next_date = va_arg(args, int64_t); return VLC_SUCCESS; } case DEMUX_SET_TIME: { int64_t t = va_arg(args, int64_t); for( size_t i = 0; i + 1< sys->count; i++ ) { if( sys->index[i + 1].start >= t && vlc_stream_Seek(demux->s, 1024 + 128LL * sys->index[i].blocknumber) == VLC_SUCCESS ) { sys->current = i; sys->next_date = t; sys->b_first_time = true; return VLC_SUCCESS; } } break; } case DEMUX_SET_POSITION: { double f = va_arg( args, double ); if(sys->count && sys->index[sys->count-1].stop > 0) { int64_t i64 = f * sys->index[sys->count-1].stop; return demux_Control(demux, DEMUX_SET_TIME, i64); } break; } case DEMUX_GET_POSITION: { double *pf = va_arg(args, double *); if(sys->current >= sys->count) { *pf = 1.0; } else if(sys->count > 0 && sys->index[sys->count-1].stop > 0) { *pf = sys->next_date - var_GetInteger(demux->obj.parent, "spu-delay"); if(*pf < 0) *pf = sys->next_date; *pf /= sys->index[sys->count-1].stop; } else { *pf = 0.0; } return VLC_SUCCESS; } default: break; } return VLC_EGENERIC; } static int Demux(demux_t *demux) { demux_sys_t *sys = demux->p_sys; int64_t i_barrier = sys->next_date - var_GetInteger(demux->obj.parent, "spu-delay"); if(i_barrier < 0) i_barrier = sys->next_date; while(sys->current < sys->count && sys->index[sys->current].start <= i_barrier) { stl_entry_t *s = &sys->index[sys->current]; if (!sys->b_slave && sys->b_first_time) { es_out_SetPCR(demux->out, VLC_TICK_0 + i_barrier); sys->b_first_time = false; } /* Might be a gap in block # */ const uint64_t i_pos = 1024 + 128LL * s->blocknumber; if(i_pos != vlc_stream_Tell(demux->s) && vlc_stream_Seek( demux->s, i_pos ) != VLC_SUCCESS ) return VLC_DEMUXER_EOF; block_t *b = vlc_stream_Block(demux->s, 128); if (b && b->i_buffer == 128) { b->i_dts = b->i_pts = VLC_TICK_0 + s->start; if (s->stop > s->start) b->i_length = s->stop - s->start; es_out_Send(demux->out, sys->es, b); } else { if(b) block_Release(b); return VLC_DEMUXER_EOF; } sys->current++; } if (!sys->b_slave) { es_out_SetPCR(demux->out, VLC_TICK_0 + i_barrier); sys->next_date += CLOCK_FREQ / 8; } return sys->current < sys->count ? VLC_DEMUXER_SUCCESS : VLC_DEMUXER_EOF; } static int Open(vlc_object_t *object) { demux_t *demux = (demux_t*)object; const uint8_t *peek; if (vlc_stream_Peek(demux->s, &peek, 11) != 11) return VLC_EGENERIC; bool is_stl_25 = !memcmp(&peek[3], "STL25.01", 8); bool is_stl_30 = !memcmp(&peek[3], "STL30.01", 8); if (!is_stl_25 && !is_stl_30) return VLC_EGENERIC; const double fps = is_stl_25 ? 25 : 30; uint8_t header[1024]; if (vlc_stream_Read(demux->s, header, sizeof(header)) != sizeof(header)) { msg_Err(demux, "Incomplete EBU STL header"); return VLC_EGENERIC; } const int cct = ParseInteger(&header[12], 2); const vlc_tick_t program_start = ParseTextTimeCode(&header[256], fps); const size_t tti_count = ParseInteger(&header[238], 5); if (!tti_count) return VLC_EGENERIC; msg_Dbg(demux, "Detected EBU STL : CCT=%d TTI=%zu start=%8.8s %"PRId64, cct, tti_count, &header[256], program_start); demux_sys_t *sys = malloc(sizeof(*sys)); if(!sys) return VLC_EGENERIC; sys->b_slave = false; sys->b_first_time = true; sys->next_date = 0; sys->current = 0; sys->count = 0; sys->index = calloc(tti_count, sizeof(*sys->index)); if(!sys->index) { free(sys); return VLC_EGENERIC; } bool comment = false; stl_entry_t *s = &sys->index[0]; s->count = 0; for (size_t i = 0; i < tti_count; i++) { uint8_t tti[16]; if (vlc_stream_Read(demux->s, tti, 16) != 16 || vlc_stream_Read(demux->s, NULL, 112) != 112) { msg_Warn(demux, "Incomplete EBU STL file"); break; } const int ebn = tti[3]; if (ebn >= 0xf0 && ebn <= 0xfd) continue; if (ebn == 0xfe) continue; if (s->count <= 0) { comment = tti[15] != 0; s->start = ParseTimeCode(&tti[5], fps) - program_start; s->stop = ParseTimeCode(&tti[9], fps) - program_start; s->blocknumber = i; } s->count++; if (ebn == 0xff && !comment) s = &sys->index[++sys->count]; if (ebn == 0xff && sys->count < tti_count) s->count = 0; } demux->p_sys = sys; if (sys->count == 0 || vlc_stream_Seek(demux->s, 1024 + 128LL * sys->index[0].blocknumber) != VLC_SUCCESS) { Close(object); return VLC_EGENERIC; } es_format_t fmt; es_format_Init(&fmt, SPU_ES, VLC_CODEC_EBU_STL); fmt.i_extra = sizeof(header); fmt.p_extra = header; sys->es = es_out_Add(demux->out, &fmt); fmt.i_extra = 0; fmt.p_extra = NULL; es_format_Clean(&fmt); if(sys->es == NULL) { Close(object); return VLC_EGENERIC; } demux->p_sys = sys; demux->pf_demux = Demux; demux->pf_control = Control; return VLC_SUCCESS; } static void Close(vlc_object_t *object) { demux_t *demux = (demux_t*)object; demux_sys_t *sys = demux->p_sys; free(sys->index); free(sys); }