| /* | |
| * GPAC - Multimedia Framework C SDK | |
| * | |
| * Authors: Jean Le Feuvre | |
| * Copyright (c) Telecom ParisTech 2000-2025 | |
| * All rights reserved | |
| * | |
| * This file is part of GPAC / ISO Media File Format sub-project | |
| * | |
| * GPAC 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, or (at your option) | |
| * any later version. | |
| * | |
| * GPAC 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 library; see the file COPYING. If not, write to | |
| * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. | |
| * | |
| */ | |
| #include <gpac/internal/isomedia_dev.h> | |
| #include <gpac/constants.h> | |
| #ifndef GPAC_DISABLE_ISOM | |
| static u32 rgb_48_to_32(char *val) | |
| { | |
| u32 res = 0x0; | |
| u32 i; | |
| for (i=0; i<3; i++) { | |
| u32 v = val[2*i]; | |
| v<<=8; | |
| v|=val[2*i + 1]; | |
| v/=0xFF; | |
| res <<= 8; | |
| res |= v; | |
| } | |
| return res; | |
| } | |
| GF_EXPORT | |
| GF_Err gf_isom_get_text_description(GF_ISOFile *movie, u32 trackNumber, u32 descriptionIndex, GF_TextSampleDescriptor **out_desc) | |
| { | |
| GF_TrackBox *trak; | |
| u32 i; | |
| GF_Tx3gSampleEntryBox *txt = NULL; | |
| GF_TextSampleEntryBox *qt_txt = NULL; | |
| if (!descriptionIndex || !out_desc) return GF_BAD_PARAM; | |
| trak = gf_isom_get_track_box(movie, trackNumber); | |
| if (!trak || !trak->Media) return GF_BAD_PARAM; | |
| switch (trak->Media->handler->handlerType) { | |
| case GF_ISOM_MEDIA_TEXT: | |
| case GF_ISOM_MEDIA_SUBT: | |
| case GF_ISOM_MEDIA_MPEG_SUBT: | |
| break; | |
| default: | |
| return GF_BAD_PARAM; | |
| } | |
| txt = (GF_Tx3gSampleEntryBox*)gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, descriptionIndex - 1); | |
| if (!txt) return GF_BAD_PARAM; | |
| switch (txt->type) { | |
| case GF_ISOM_BOX_TYPE_TX3G: | |
| break; | |
| case GF_ISOM_BOX_TYPE_TEXT: | |
| qt_txt = (GF_TextSampleEntryBox *)txt; | |
| txt = NULL; | |
| break; | |
| default: | |
| return GF_BAD_PARAM; | |
| } | |
| (*out_desc) = (GF_TextSampleDescriptor *) gf_odf_desc_new(GF_ODF_TX3G_TAG); | |
| if (! (*out_desc) ) return GF_OUT_OF_MEM; | |
| if (qt_txt) { | |
| (*out_desc)->back_color = rgb_48_to_32(qt_txt->background_color); | |
| (*out_desc)->default_pos = qt_txt->default_box; | |
| (*out_desc)->default_style.style_flags = 0; //todo, expose qt_txt->fontFace; | |
| (*out_desc)->default_style.text_color = rgb_48_to_32(qt_txt->foreground_color); | |
| (*out_desc)->displayFlags = qt_txt->displayFlags; | |
| (*out_desc)->vert_justif = -1; | |
| (*out_desc)->horiz_justif = qt_txt->textJustification; | |
| if (qt_txt->textName) { | |
| (*out_desc)->font_count = 1; | |
| (*out_desc)->fonts = (GF_FontRecord *) gf_malloc(sizeof(GF_FontRecord)); | |
| (*out_desc)->fonts[0].fontName = gf_strdup(qt_txt->textName); | |
| } | |
| } else { | |
| (*out_desc)->back_color = txt->back_color; | |
| (*out_desc)->default_pos = txt->default_box; | |
| (*out_desc)->default_style = txt->default_style; | |
| (*out_desc)->displayFlags = txt->displayFlags; | |
| (*out_desc)->vert_justif = txt->vertical_justification; | |
| (*out_desc)->horiz_justif = txt->horizontal_justification; | |
| if (txt->font_table && txt->font_table->entry_count) { | |
| (*out_desc)->font_count = txt->font_table->entry_count; | |
| (*out_desc)->fonts = (GF_FontRecord *) gf_malloc(sizeof(GF_FontRecord) * txt->font_table->entry_count); | |
| for (i=0; i<txt->font_table->entry_count; i++) { | |
| (*out_desc)->fonts[i].fontID = txt->font_table->fonts[i].fontID; | |
| (*out_desc)->fonts[i].fontName = NULL; | |
| if (txt->font_table->fonts[i].fontName) | |
| (*out_desc)->fonts[i].fontName = gf_strdup(txt->font_table->fonts[i].fontName); | |
| } | |
| } | |
| } | |
| return GF_OK; | |
| } | |
| #ifndef GPAC_DISABLE_ISOM_WRITE | |
| #if 0 //unused | |
| /*! updates text sample description | |
| \param isom_file the target ISO file | |
| \param trackNumber the target track | |
| \param sampleDescriptionIndex the target sample description index | |
| \param desc the text sample descriptor to use | |
| \return error if any | |
| */ | |
| GF_Err gf_isom_update_text_description(GF_ISOFile *movie, u32 trackNumber, u32 descriptionIndex, GF_TextSampleDescriptor *desc) | |
| { | |
| GF_TrackBox *trak; | |
| GF_Err e; | |
| Bool is_qt_text = GF_FALSE; | |
| u32 i; | |
| GF_Tx3gSampleEntryBox *txt; | |
| if (!descriptionIndex || !desc) return GF_BAD_PARAM; | |
| e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); | |
| if (e) return e; | |
| trak = gf_isom_get_track_box(movie, trackNumber); | |
| if (!trak || !trak->Media || !desc->font_count) return GF_BAD_PARAM; | |
| switch (trak->Media->handler->handlerType) { | |
| case GF_ISOM_MEDIA_TEXT: | |
| case GF_ISOM_MEDIA_SUBT: | |
| case GF_ISOM_MEDIA_MPEG_SUBT: | |
| break; | |
| default: | |
| return GF_BAD_PARAM; | |
| } | |
| txt = (GF_Tx3gSampleEntryBox*)gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, descriptionIndex - 1); | |
| if (!txt) return GF_BAD_PARAM; | |
| switch (txt->type) { | |
| case GF_ISOM_BOX_TYPE_TX3G: | |
| break; | |
| case GF_ISOM_BOX_TYPE_TEXT: | |
| is_qt_text = GF_TRUE; | |
| break; | |
| default: | |
| return GF_BAD_PARAM; | |
| } | |
| if (!movie->keep_utc) | |
| trak->Media->mediaHeader->modificationTime = gf_isom_get_mp4time(); | |
| txt->back_color = desc->back_color; | |
| txt->default_box = desc->default_pos; | |
| txt->default_style = desc->default_style; | |
| txt->displayFlags = desc->displayFlags; | |
| txt->vertical_justification = desc->vert_justif; | |
| txt->horizontal_justification = desc->horiz_justif; | |
| if (is_qt_text) { | |
| GF_TextSampleEntryBox *qtt = (GF_TextSampleEntryBox *) txt; | |
| if (qtt->textName) gf_free(qtt->textName); | |
| qtt->textName = NULL; | |
| if (desc->font_count) { | |
| qtt->textName = gf_strdup(desc->fonts[0].fontName); | |
| } | |
| } else { | |
| if (txt->font_table) gf_isom_box_del_parent(&txt->child_boxes, (GF_Box*)txt->font_table); | |
| txt->font_table = (GF_FontTableBox *)gf_isom_box_new_parent(&txt->child_boxes, GF_ISOM_BOX_TYPE_FTAB); | |
| txt->font_table->entry_count = desc->font_count; | |
| txt->font_table->fonts = (GF_FontRecord *) gf_malloc(sizeof(GF_FontRecord) * desc->font_count); | |
| for (i=0; i<desc->font_count; i++) { | |
| txt->font_table->fonts[i].fontID = desc->fonts[i].fontID; | |
| if (desc->fonts[i].fontName) txt->font_table->fonts[i].fontName = gf_strdup(desc->fonts[i].fontName); | |
| } | |
| } | |
| return e; | |
| } | |
| #endif //unused | |
| GF_EXPORT | |
| GF_Err gf_isom_new_text_description(GF_ISOFile *movie, u32 trackNumber, GF_TextSampleDescriptor *desc, const char *URLname, const char *URNname, u32 *outDescriptionIndex) | |
| { | |
| GF_TrackBox *trak; | |
| GF_Err e; | |
| u32 dataRefIndex, i; | |
| GF_Tx3gSampleEntryBox *txt; | |
| e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); | |
| if (e) return e; | |
| trak = gf_isom_get_track_box(movie, trackNumber); | |
| if (!trak || !trak->Media || !desc) return GF_BAD_PARAM; | |
| switch (trak->Media->handler->handlerType) { | |
| case GF_ISOM_MEDIA_TEXT: | |
| case GF_ISOM_MEDIA_SUBT: | |
| case GF_ISOM_MEDIA_MPEG_SUBT: | |
| break; | |
| default: | |
| return GF_BAD_PARAM; | |
| } | |
| //get or create the data ref | |
| e = Media_FindDataRef(trak->Media->information->dataInformation->dref, (char *)URLname, (char *)URNname, &dataRefIndex); | |
| if (e) return e; | |
| if (!dataRefIndex) { | |
| e = Media_CreateDataRef(movie, trak->Media->information->dataInformation->dref, (char *)URLname, (char *)URNname, &dataRefIndex); | |
| if (e) return e; | |
| } | |
| if (!movie->keep_utc) | |
| trak->Media->mediaHeader->modificationTime = gf_isom_get_mp4time(); | |
| txt = (GF_Tx3gSampleEntryBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_TX3G); | |
| if (!txt) return GF_OUT_OF_MEM; | |
| txt->dataReferenceIndex = dataRefIndex; | |
| gf_list_add(trak->Media->information->sampleTable->SampleDescription->child_boxes, txt); | |
| if (outDescriptionIndex) *outDescriptionIndex = gf_list_count(trak->Media->information->sampleTable->SampleDescription->child_boxes); | |
| txt->back_color = desc->back_color; | |
| txt->default_box = desc->default_pos; | |
| txt->default_style = desc->default_style; | |
| txt->displayFlags = desc->displayFlags; | |
| txt->vertical_justification = desc->vert_justif; | |
| txt->horizontal_justification = desc->horiz_justif; | |
| txt->font_table = (GF_FontTableBox *)gf_isom_box_new_parent(&txt->child_boxes, GF_ISOM_BOX_TYPE_FTAB); | |
| if (!txt->font_table) return GF_OUT_OF_MEM; | |
| txt->font_table->entry_count = desc->font_count; | |
| if (!desc->font_count) return GF_OK; | |
| txt->font_table->fonts = (GF_FontRecord *) gf_malloc(sizeof(GF_FontRecord) * desc->font_count); | |
| if (!txt->font_table->fonts) return GF_OUT_OF_MEM; | |
| for (i=0; i<desc->font_count; i++) { | |
| txt->font_table->fonts[i].fontID = desc->fonts[i].fontID; | |
| if (desc->fonts[i].fontName) txt->font_table->fonts[i].fontName = gf_strdup(desc->fonts[i].fontName); | |
| } | |
| return e; | |
| } | |
| /*blindly adds text - note we don't rely on terminaison characters to handle utf8 and utf16 data | |
| in the same way. It is the user responsibility to signal UTF16*/ | |
| GF_EXPORT | |
| GF_Err gf_isom_text_add_text(GF_TextSample *samp, char *text_data, u32 text_len) | |
| { | |
| if (!samp) return GF_BAD_PARAM; | |
| if (!text_len) return GF_OK; | |
| samp->text = (char*)gf_realloc(samp->text, sizeof(char) * (samp->len + text_len) ); | |
| memcpy(samp->text + samp->len, text_data, sizeof(char) * text_len); | |
| samp->len += text_len; | |
| return GF_OK; | |
| } | |
| #if 0 //unused | |
| /*! sets UTF16 marker for text data. This MUST be called on an empty sample. If text data added later | |
| on (cf below) is not formatted as UTF16 data(2 bytes char) the resulting text sample won't be compliant, | |
| but this library won't warn | |
| \param tx_samp the target text sample | |
| \return error if any | |
| */ | |
| GF_Err gf_isom_text_set_utf16_marker(GF_TextSample *samp) | |
| { | |
| /*we MUST have an empty sample*/ | |
| if (!samp || samp->text) return GF_BAD_PARAM; | |
| samp->text = (char*)gf_malloc(sizeof(char) * 2); | |
| if (!samp->text) return GF_OUT_OF_MEM; | |
| samp->text[0] = (char) 0xFE; | |
| samp->text[1] = (char) 0xFF; | |
| samp->len = 2; | |
| return GF_OK; | |
| } | |
| #endif //unused | |
| GF_EXPORT | |
| GF_Err gf_isom_text_add_style(GF_TextSample *samp, GF_StyleRecord *rec) | |
| { | |
| if (!samp || !rec) return GF_BAD_PARAM; | |
| if (!samp->styles) { | |
| samp->styles = (GF_TextStyleBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_STYL); | |
| if (!samp->styles) return GF_OUT_OF_MEM; | |
| } | |
| samp->styles->styles = (GF_StyleRecord*)gf_realloc(samp->styles->styles, sizeof(GF_StyleRecord)*(samp->styles->entry_count+1)); | |
| if (!samp->styles->styles) return GF_OUT_OF_MEM; | |
| samp->styles->styles[samp->styles->entry_count] = *rec; | |
| samp->styles->entry_count++; | |
| return GF_OK; | |
| } | |
| GF_EXPORT | |
| GF_Err gf_isom_text_add_highlight(GF_TextSample *samp, u16 start_char, u16 end_char) | |
| { | |
| GF_TextHighlightBox *a; | |
| if (!samp) return GF_BAD_PARAM; | |
| if (start_char == end_char) return GF_BAD_PARAM; | |
| a = (GF_TextHighlightBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_HLIT); | |
| if (!a) return GF_OUT_OF_MEM; | |
| a->startcharoffset = start_char; | |
| a->endcharoffset = end_char; | |
| return gf_list_add(samp->others, a); | |
| } | |
| GF_EXPORT | |
| GF_Err gf_isom_text_set_highlight_color(GF_TextSample *samp, u32 argb) | |
| { | |
| if (!samp) return GF_BAD_PARAM; | |
| if (!samp->highlight_color) { | |
| samp->highlight_color = (GF_TextHighlightColorBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_HCLR); | |
| if (!samp->highlight_color) return GF_OUT_OF_MEM; | |
| } | |
| samp->highlight_color->hil_color = argb; | |
| return GF_OK; | |
| } | |
| /*3GPP spec is quite obscur here*/ | |
| GF_EXPORT | |
| GF_Err gf_isom_text_add_karaoke(GF_TextSample *samp, u32 start_time) | |
| { | |
| if (!samp) return GF_BAD_PARAM; | |
| samp->cur_karaoke = (GF_TextKaraokeBox *) gf_isom_box_new(GF_ISOM_BOX_TYPE_KROK); | |
| if (!samp->cur_karaoke) return GF_OUT_OF_MEM; | |
| samp->cur_karaoke->highlight_starttime = start_time; | |
| return gf_list_add(samp->others, samp->cur_karaoke); | |
| } | |
| GF_EXPORT | |
| GF_Err gf_isom_text_set_karaoke_segment(GF_TextSample *samp, u32 end_time, u16 start_char, u16 end_char) | |
| { | |
| if (!samp || !samp->cur_karaoke) return GF_BAD_PARAM; | |
| samp->cur_karaoke->records = (KaraokeRecord*)gf_realloc(samp->cur_karaoke->records, sizeof(KaraokeRecord)*(samp->cur_karaoke->nb_entries+1)); | |
| if (!samp->cur_karaoke->records) return GF_OUT_OF_MEM; | |
| samp->cur_karaoke->records[samp->cur_karaoke->nb_entries].end_charoffset = end_char; | |
| samp->cur_karaoke->records[samp->cur_karaoke->nb_entries].start_charoffset = start_char; | |
| samp->cur_karaoke->records[samp->cur_karaoke->nb_entries].highlight_endtime = end_time; | |
| samp->cur_karaoke->nb_entries++; | |
| return GF_OK; | |
| } | |
| GF_EXPORT | |
| GF_Err gf_isom_text_set_scroll_delay(GF_TextSample *samp, u32 scroll_delay) | |
| { | |
| if (!samp) return GF_BAD_PARAM; | |
| if (!samp->scroll_delay) { | |
| samp->scroll_delay = (GF_TextScrollDelayBox*) gf_isom_box_new(GF_ISOM_BOX_TYPE_DLAY); | |
| if (!samp->scroll_delay) return GF_OUT_OF_MEM; | |
| } | |
| samp->scroll_delay->scroll_delay = scroll_delay; | |
| return GF_OK; | |
| } | |
| GF_EXPORT | |
| GF_Err gf_isom_text_add_hyperlink(GF_TextSample *samp, char *URL, char *altString, u16 start_char, u16 end_char) | |
| { | |
| GF_TextHyperTextBox*a; | |
| if (!samp) return GF_BAD_PARAM; | |
| a = (GF_TextHyperTextBox*) gf_isom_box_new(GF_ISOM_BOX_TYPE_HREF); | |
| if (!a) return GF_OUT_OF_MEM; | |
| a->startcharoffset = start_char; | |
| a->endcharoffset = end_char; | |
| a->URL = URL ? gf_strdup(URL) : NULL; | |
| a->URL_hint = altString ? gf_strdup(altString) : NULL; | |
| return gf_list_add(samp->others, a); | |
| } | |
| GF_EXPORT | |
| GF_Err gf_isom_text_set_box(GF_TextSample *samp, s16 top, s16 left, s16 bottom, s16 right) | |
| { | |
| if (!samp) return GF_BAD_PARAM; | |
| if (!samp->box) { | |
| samp->box = (GF_TextBoxBox*) gf_isom_box_new(GF_ISOM_BOX_TYPE_TBOX); | |
| if (!samp->box) return GF_OUT_OF_MEM; | |
| } | |
| samp->box->box.top = top; | |
| samp->box->box.left = left; | |
| samp->box->box.bottom = bottom; | |
| samp->box->box.right = right; | |
| return GF_OK; | |
| } | |
| GF_EXPORT | |
| GF_Err gf_isom_text_add_blink(GF_TextSample *samp, u16 start_char, u16 end_char) | |
| { | |
| GF_TextBlinkBox *a; | |
| if (!samp) return GF_BAD_PARAM; | |
| a = (GF_TextBlinkBox*) gf_isom_box_new(GF_ISOM_BOX_TYPE_BLNK); | |
| if (!a) return GF_OUT_OF_MEM; | |
| a->startcharoffset = start_char; | |
| a->endcharoffset = end_char; | |
| return gf_list_add(samp->others, a); | |
| } | |
| GF_EXPORT | |
| GF_Err gf_isom_text_set_wrap(GF_TextSample *samp, u8 wrap_flags) | |
| { | |
| if (!samp) return GF_BAD_PARAM; | |
| if (!samp->wrap) { | |
| samp->wrap = (GF_TextWrapBox*) gf_isom_box_new(GF_ISOM_BOX_TYPE_TWRP); | |
| if (!samp->wrap) return GF_OUT_OF_MEM; | |
| } | |
| samp->wrap->wrap_flag = wrap_flags; | |
| return GF_OK; | |
| } | |
| GF_EXPORT | |
| GF_Err gf_isom_text_set_forced(GF_TextSample *samp, Bool is_forced) | |
| { | |
| if (!samp) return GF_BAD_PARAM; | |
| samp->is_forced = is_forced; | |
| return GF_OK; | |
| } | |
| static GFINLINE GF_Err gpp_write_modifier(GF_BitStream *bs, GF_Box *a) | |
| { | |
| GF_Err e; | |
| if (!a) return GF_OK; | |
| e = gf_isom_box_size(a); | |
| if (!e) e = gf_isom_box_write(a, bs); | |
| return e; | |
| } | |
| GF_EXPORT | |
| GF_Err gf_isom_text_sample_write_bs(const GF_TextSample *samp, GF_BitStream *bs) | |
| { | |
| GF_Err e; | |
| u32 i; | |
| if (!samp) return GF_BAD_PARAM; | |
| gf_bs_write_u16(bs, samp->len); | |
| if (samp->len) gf_bs_write_data(bs, samp->text, samp->len); | |
| e = gpp_write_modifier(bs, (GF_Box *)samp->styles); | |
| if (!e) e = gpp_write_modifier(bs, (GF_Box *)samp->highlight_color); | |
| if (!e) e = gpp_write_modifier(bs, (GF_Box *)samp->scroll_delay); | |
| if (!e) e = gpp_write_modifier(bs, (GF_Box *)samp->box); | |
| if (!e) e = gpp_write_modifier(bs, (GF_Box *)samp->wrap); | |
| if (!e && samp->is_forced) { | |
| gf_bs_write_u32(bs, 8); | |
| gf_bs_write_u32(bs, GF_QT_BOX_TYPE_FRCD); | |
| } | |
| if (!e) { | |
| GF_Box *a; | |
| i=0; | |
| while ((a = (GF_Box*)gf_list_enum(samp->others, &i))) { | |
| e = gpp_write_modifier(bs, a); | |
| if (e) break; | |
| } | |
| } | |
| return e; | |
| } | |
| GF_EXPORT | |
| GF_ISOSample *gf_isom_text_to_sample(const GF_TextSample *samp) | |
| { | |
| GF_Err e; | |
| GF_ISOSample *res; | |
| GF_BitStream *bs; | |
| if (!samp) return NULL; | |
| bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); | |
| e = gf_isom_text_sample_write_bs(samp, bs); | |
| if (e) { | |
| gf_bs_del(bs); | |
| return NULL; | |
| } | |
| res = gf_isom_sample_new(); | |
| if (!res) { | |
| gf_bs_del(bs); | |
| return NULL; | |
| } | |
| gf_bs_get_content(bs, &res->data, &res->dataLength); | |
| gf_bs_del(bs); | |
| res->IsRAP = RAP; | |
| return res; | |
| } | |
| u32 gf_isom_text_sample_size(GF_TextSample *samp) | |
| { | |
| GF_Box *a; | |
| u32 i, size; | |
| if (!samp) return 0; | |
| size = 2 + samp->len; | |
| if (samp->styles) { | |
| gf_isom_box_size((GF_Box *)samp->styles); | |
| size += (u32) samp->styles->size; | |
| } | |
| if (samp->highlight_color) { | |
| gf_isom_box_size((GF_Box *)samp->highlight_color); | |
| size += (u32) samp->highlight_color->size; | |
| } | |
| if (samp->scroll_delay) { | |
| gf_isom_box_size((GF_Box *)samp->scroll_delay); | |
| size += (u32) samp->scroll_delay->size; | |
| } | |
| if (samp->box) { | |
| gf_isom_box_size((GF_Box *)samp->box); | |
| size += (u32) samp->box->size; | |
| } | |
| if (samp->wrap) { | |
| gf_isom_box_size((GF_Box *)samp->wrap); | |
| size += (u32) samp->wrap->size; | |
| } | |
| if (samp->is_forced) { | |
| size += 8; | |
| } | |
| i=0; | |
| while ((a = (GF_Box*)gf_list_enum(samp->others, &i))) { | |
| gf_isom_box_size((GF_Box *)a); | |
| size += (u32) a->size; | |
| } | |
| return size; | |
| } | |
| #if 0 //unused | |
| /*! checks if this text description is already inserted | |
| \param isom_file the target ISO file | |
| \param trackNumber the target track | |
| \param desc the 3GPP text sample description to check | |
| \param outDescIdx set to 0 if not found, or index of the matching sample description | |
| \param same_styles indicates if default styles matches | |
| \param same_box indicates if default box matches | |
| */ | |
| GF_Err gf_isom_text_has_similar_description(GF_ISOFile *movie, u32 trackNumber, GF_TextSampleDescriptor *desc, u32 *outDescIdx, Bool *same_box, Bool *same_styles) | |
| { | |
| GF_TrackBox *trak; | |
| GF_Err e; | |
| u32 i, j, count; | |
| GF_Tx3gSampleEntryBox *txt; | |
| *same_box = *same_styles = 0; | |
| *outDescIdx = 0; | |
| if (!desc) return GF_BAD_PARAM; | |
| e = gf_isom_can_access_movie(movie, GF_ISOM_OPEN_WRITE); | |
| if (e) return GF_BAD_PARAM; | |
| trak = gf_isom_get_track_box(movie, trackNumber); | |
| if (!trak || !trak->Media || !desc->font_count) return GF_BAD_PARAM; | |
| switch (trak->Media->handler->handlerType) { | |
| case GF_ISOM_MEDIA_TEXT: | |
| case GF_ISOM_MEDIA_SUBT: | |
| case GF_ISOM_MEDIA_MPEG_SUBT: | |
| break; | |
| default: | |
| return GF_BAD_PARAM; | |
| } | |
| count = gf_list_count(trak->Media->information->sampleTable->SampleDescription->child_boxes); | |
| for (i=0; i<count; i++) { | |
| Bool same_fonts; | |
| txt = (GF_Tx3gSampleEntryBox*)gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, i); | |
| if (!txt) continue; | |
| if ((txt->type != GF_ISOM_BOX_TYPE_TX3G) && (txt->type != GF_ISOM_BOX_TYPE_TEXT)) continue; | |
| if (txt->back_color != desc->back_color) continue; | |
| if (txt->displayFlags != desc->displayFlags) continue; | |
| if (txt->vertical_justification != desc->vert_justif) continue; | |
| if (txt->horizontal_justification != desc->horiz_justif) continue; | |
| if (txt->font_table->entry_count != desc->font_count) continue; | |
| same_fonts = 1; | |
| for (j=0; j<desc->font_count; j++) { | |
| if (txt->font_table->fonts[j].fontID != desc->fonts[j].fontID) same_fonts = 0; | |
| else if (strcmp(desc->fonts[j].fontName, txt->font_table->fonts[j].fontName)) same_fonts = 0; | |
| } | |
| if (same_fonts) { | |
| *outDescIdx = i+1; | |
| if (!memcmp(&txt->default_box, &desc->default_pos, sizeof(GF_BoxRecord))) *same_box = 1; | |
| if (!memcmp(&txt->default_style, &desc->default_style, sizeof(GF_StyleRecord))) *same_styles = 1; | |
| return GF_OK; | |
| } | |
| } | |
| return GF_OK; | |
| } | |
| #endif | |
| #endif /*GPAC_DISABLE_ISOM_WRITE*/ | |
| GF_EXPORT | |
| GF_TextSample *gf_isom_new_text_sample() | |
| { | |
| GF_TextSample *res; | |
| GF_SAFEALLOC(res, GF_TextSample); | |
| if (!res) return NULL; | |
| res->others = gf_list_new(); | |
| return res; | |
| } | |
| GF_EXPORT | |
| GF_Err gf_isom_text_reset_styles(GF_TextSample *samp) | |
| { | |
| if (!samp) return GF_BAD_PARAM; | |
| if (samp->box) gf_isom_box_del((GF_Box *)samp->box); | |
| samp->box = NULL; | |
| if (samp->highlight_color) gf_isom_box_del((GF_Box *)samp->highlight_color); | |
| samp->highlight_color = NULL; | |
| if (samp->scroll_delay) gf_isom_box_del((GF_Box *)samp->scroll_delay); | |
| samp->scroll_delay = NULL; | |
| if (samp->wrap) gf_isom_box_del((GF_Box *)samp->wrap); | |
| samp->wrap = NULL; | |
| if (samp->styles) gf_isom_box_del((GF_Box *)samp->styles); | |
| samp->styles = NULL; | |
| samp->cur_karaoke = NULL; | |
| while (gf_list_count(samp->others)) { | |
| GF_Box *a = (GF_Box*)gf_list_get(samp->others, 0); | |
| gf_list_rem(samp->others, 0); | |
| gf_isom_box_del(a); | |
| } | |
| samp->is_forced = GF_FALSE; | |
| return GF_OK; | |
| } | |
| GF_EXPORT | |
| GF_Err gf_isom_text_reset(GF_TextSample *samp) | |
| { | |
| if (!samp) return GF_BAD_PARAM; | |
| if (samp->text) gf_free(samp->text); | |
| samp->text = NULL; | |
| samp->len = 0; | |
| return gf_isom_text_reset_styles(samp); | |
| } | |
| GF_EXPORT | |
| void gf_isom_delete_text_sample(GF_TextSample * tx_samp) | |
| { | |
| gf_isom_text_reset(tx_samp); | |
| gf_list_del(tx_samp->others); | |
| gf_free(tx_samp); | |
| } | |
| GF_EXPORT | |
| GF_TextSample *gf_isom_parse_text_sample(GF_BitStream *bs) | |
| { | |
| GF_TextSample *s = gf_isom_new_text_sample(); | |
| /*empty sample*/ | |
| if (!bs || !gf_bs_available(bs)) return s; | |
| s->len = gf_bs_read_u16(bs); | |
| if (s->len) { | |
| /*2 extra bytes for UTF-16 term char just in case (we don't know if a BOM marker is present or | |
| not since this may be a sample carried over RTP*/ | |
| s->text = (char *) gf_malloc(sizeof(char)*(s->len+2) ); | |
| if (!s->text) return NULL; | |
| s->text[s->len] = 0; | |
| s->text[s->len+1] = 0; | |
| gf_bs_read_data(bs, s->text, s->len); | |
| } | |
| while (gf_bs_available(bs)) { | |
| GF_Box *a = NULL; | |
| GF_Err e = gf_isom_box_parse(&a, bs); | |
| if (e) { | |
| if (a) gf_isom_box_del(a); | |
| break; | |
| } | |
| switch (a->type) { | |
| case GF_ISOM_BOX_TYPE_STYL: | |
| if (s->styles) { | |
| GF_TextStyleBox *st2 = (GF_TextStyleBox *)a; | |
| if (!s->styles->entry_count) { | |
| gf_isom_box_del((GF_Box*)s->styles); | |
| s->styles = st2; | |
| } else { | |
| s->styles->styles = (GF_StyleRecord*)gf_realloc(s->styles->styles, sizeof(GF_StyleRecord) * (s->styles->entry_count + st2->entry_count)); | |
| memcpy(&s->styles->styles[s->styles->entry_count], st2->styles, sizeof(GF_StyleRecord) * st2->entry_count); | |
| s->styles->entry_count += st2->entry_count; | |
| gf_isom_box_del(a); | |
| } | |
| } else { | |
| s->styles = (GF_TextStyleBox*)a; | |
| } | |
| break; | |
| case GF_ISOM_BOX_TYPE_KROK: | |
| s->cur_karaoke = (GF_TextKaraokeBox*)a; | |
| case GF_ISOM_BOX_TYPE_HLIT: | |
| case GF_ISOM_BOX_TYPE_HREF: | |
| case GF_ISOM_BOX_TYPE_BLNK: | |
| gf_list_add(s->others, a); | |
| break; | |
| case GF_ISOM_BOX_TYPE_HCLR: | |
| if (s->highlight_color) gf_isom_box_del(a); | |
| else s->highlight_color = (GF_TextHighlightColorBox *) a; | |
| break; | |
| case GF_ISOM_BOX_TYPE_DLAY: | |
| if (s->scroll_delay) gf_isom_box_del(a); | |
| else s->scroll_delay= (GF_TextScrollDelayBox*) a; | |
| break; | |
| case GF_ISOM_BOX_TYPE_TBOX: | |
| if (s->box) gf_isom_box_del(a); | |
| else s->box= (GF_TextBoxBox *) a; | |
| break; | |
| case GF_ISOM_BOX_TYPE_TWRP: | |
| if (s->wrap) gf_isom_box_del(a); | |
| else s->wrap= (GF_TextWrapBox*) a; | |
| break; | |
| case GF_QT_BOX_TYPE_FRCD: | |
| s->is_forced = GF_TRUE; | |
| gf_isom_box_del(a); | |
| break; | |
| default: | |
| gf_isom_box_del(a); | |
| break; | |
| } | |
| } | |
| return s; | |
| } | |
| #if 0 //unused | |
| GF_TextSample *gf_isom_parse_text_sample_from_data(u8 *data, u32 dataLength) | |
| { | |
| GF_TextSample *s; | |
| GF_BitStream *bs; | |
| /*empty text sample*/ | |
| if (!data || !dataLength) { | |
| return gf_isom_new_text_sample(); | |
| } | |
| bs = gf_bs_new(data, dataLength, GF_BITSTREAM_READ); | |
| s = gf_isom_parse_text_sample(bs); | |
| gf_bs_del(bs); | |
| return s; | |
| } | |
| #endif | |
| /*out-of-band sample desc (128 and 255 reserved in RFC)*/ | |
| #define SAMPLE_INDEX_OFFSET 129 | |
| static void gf_isom_write_tx3g(GF_Tx3gSampleEntryBox *_a, GF_BitStream *bs, u32 sidx, u32 sidx_offset) | |
| { | |
| u32 size, j, fount_count; | |
| const char *qt_fontname = NULL; | |
| void gpp_write_rgba(GF_BitStream *bs, u32 col); | |
| void gpp_write_box(GF_BitStream *bs, GF_BoxRecord *rec); | |
| void gpp_write_style(GF_BitStream *bs, GF_StyleRecord *rec); | |
| GF_TextSampleEntryBox *qt = (_a->type==GF_ISOM_BOX_TYPE_TEXT) ? (GF_TextSampleEntryBox *)_a : NULL; | |
| GF_Tx3gSampleEntryBox *ttxt = (_a->type!=GF_ISOM_BOX_TYPE_TEXT) ? (GF_Tx3gSampleEntryBox *)_a : NULL; | |
| if (sidx_offset) gf_bs_write_u8(bs, sidx + sidx_offset); | |
| /*SINCE WINCE HAS A READONLY VERSION OF MP4 WE MUST DO IT BY HAND*/ | |
| size = 8 + 18 + 8 + 12; | |
| size += 8 + 2; | |
| fount_count = 0; | |
| if (qt && qt->textName) { | |
| qt_fontname = qt->textName; | |
| fount_count = 1; | |
| } else if (ttxt && ttxt->font_table) { | |
| fount_count = ttxt->font_table->entry_count; | |
| for (j=0; j<fount_count; j++) { | |
| size += 3; | |
| if (ttxt->font_table->fonts[j].fontName) | |
| size += (u32) strlen(ttxt->font_table->fonts[j].fontName); | |
| } | |
| } | |
| /*write TextSampleEntry box*/ | |
| gf_bs_write_u32(bs, size); | |
| gf_bs_write_u32(bs, GF_ISOM_BOX_TYPE_TX3G); | |
| gf_bs_write_data(bs, _a->reserved, 6); | |
| gf_bs_write_u16(bs, _a->dataReferenceIndex); | |
| gf_bs_write_u32(bs, _a->displayFlags); | |
| if (qt) { | |
| GF_StyleRecord sr; | |
| memset(&sr, 0, sizeof(GF_StyleRecord)); | |
| gf_bs_write_u8(bs, qt->textJustification); | |
| gf_bs_write_u8(bs, (u8) -1); | |
| gpp_write_rgba(bs, rgb_48_to_32(qt->background_color) ); | |
| gpp_write_box(bs, &qt->default_box); | |
| sr.text_color = rgb_48_to_32(qt->foreground_color); | |
| sr.style_flags = 0; //todo expose qt->fontFace; | |
| gpp_write_style(bs, &sr); | |
| } else { | |
| gf_bs_write_u8(bs, ttxt->horizontal_justification); | |
| gf_bs_write_u8(bs, ttxt->vertical_justification); | |
| gpp_write_rgba(bs, ttxt->back_color); | |
| gpp_write_box(bs, &ttxt->default_box); | |
| gpp_write_style(bs, &ttxt->default_style); | |
| } | |
| /*write font table box*/ | |
| size -= (8 + 18 + 8 + 12); | |
| gf_bs_write_u32(bs, size); | |
| gf_bs_write_u32(bs, GF_ISOM_BOX_TYPE_FTAB); | |
| gf_bs_write_u16(bs, fount_count); | |
| for (j=0; j<fount_count; j++) { | |
| if (qt) { | |
| gf_bs_write_u16(bs, 0); | |
| if (qt_fontname) { | |
| u32 len = (u32) strlen(qt_fontname); | |
| gf_bs_write_u8(bs, len); | |
| gf_bs_write_data(bs, qt_fontname, len); | |
| } else { | |
| gf_bs_write_u8(bs, 0); | |
| } | |
| } else { | |
| gf_bs_write_u16(bs, ttxt->font_table->fonts[j].fontID); | |
| if (ttxt->font_table->fonts[j].fontName) { | |
| u32 len = (u32) strlen(ttxt->font_table->fonts[j].fontName); | |
| gf_bs_write_u8(bs, len); | |
| gf_bs_write_data(bs, ttxt->font_table->fonts[j].fontName, len); | |
| } else { | |
| gf_bs_write_u8(bs, 0); | |
| } | |
| } | |
| } | |
| } | |
| GF_Err gf_isom_get_ttxt_esd(GF_MediaBox *mdia, GF_ESD **out_esd) | |
| { | |
| GF_BitStream *bs; | |
| u32 count, i; | |
| Bool has_v_info; | |
| GF_List *sampleDesc; | |
| GF_ESD *esd; | |
| GF_TrackBox *tk; | |
| *out_esd = NULL; | |
| sampleDesc = mdia->information->sampleTable->SampleDescription->child_boxes; | |
| count = gf_list_count(sampleDesc); | |
| if (!count) return GF_ISOM_INVALID_MEDIA; | |
| esd = gf_odf_desc_esd_new(2); | |
| esd->decoderConfig->streamType = GF_STREAM_TEXT; | |
| esd->decoderConfig->objectTypeIndication = GF_CODECID_TEXT_MPEG4; | |
| bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); | |
| /*Base3GPPFormat*/ | |
| gf_bs_write_u8(bs, 0x10); | |
| /*MPEGExtendedFormat*/ | |
| gf_bs_write_u8(bs, 0x10); | |
| /*profileLevel*/ | |
| gf_bs_write_u8(bs, 0x10); | |
| gf_bs_write_u24(bs, mdia->mediaHeader->timeScale); | |
| gf_bs_write_int(bs, 0, 1); /*no alt formats*/ | |
| gf_bs_write_int(bs, 2, 2); /*only out-of-band-band sample desc*/ | |
| gf_bs_write_int(bs, 1, 1); /*we will write sample desc*/ | |
| /*write v info if any visual track in this movie*/ | |
| has_v_info = 0; | |
| i=0; | |
| while ((tk = (GF_TrackBox*)gf_list_enum(mdia->mediaTrack->moov->trackList, &i))) { | |
| if (tk->Media->handler && (tk->Media->handler->handlerType == GF_ISOM_MEDIA_VISUAL)) { | |
| has_v_info = 1; | |
| } | |
| } | |
| gf_bs_write_int(bs, has_v_info, 1); | |
| gf_bs_write_int(bs, 0, 3); /*reserved, spec doesn't say the values*/ | |
| gf_bs_write_u8(bs, mdia->mediaTrack->Header->layer); | |
| gf_bs_write_u16(bs, mdia->mediaTrack->Header->width>>16); | |
| gf_bs_write_u16(bs, mdia->mediaTrack->Header->height>>16); | |
| /*write desc*/ | |
| gf_bs_write_u8(bs, count); | |
| for (i=0; i<count; i++) { | |
| GF_Tx3gSampleEntryBox *a; | |
| a = (GF_Tx3gSampleEntryBox *) gf_list_get(sampleDesc, i); | |
| if ((a->type != GF_ISOM_BOX_TYPE_TX3G) && (a->type != GF_ISOM_BOX_TYPE_TEXT) ) continue; | |
| gf_isom_write_tx3g(a, bs, i+1, SAMPLE_INDEX_OFFSET); | |
| } | |
| if (has_v_info) { | |
| u32 trans; | |
| /*which video shall we pick for MPEG-4, and how is the associations indicated in 3GP ???*/ | |
| gf_bs_write_u16(bs, 0); | |
| gf_bs_write_u16(bs, 0); | |
| trans = mdia->mediaTrack->Header->matrix[6]; | |
| trans >>= 16; | |
| gf_bs_write_u16(bs, trans); | |
| trans = mdia->mediaTrack->Header->matrix[7]; | |
| trans >>= 16; | |
| gf_bs_write_u16(bs, trans); | |
| } | |
| gf_bs_get_content(bs, &esd->decoderConfig->decoderSpecificInfo->data, &esd->decoderConfig->decoderSpecificInfo->dataLength); | |
| gf_bs_del(bs); | |
| *out_esd = esd; | |
| return GF_OK; | |
| } | |
| GF_Err gf_isom_rewrite_text_sample(GF_ISOSample *samp, u32 sampleDescriptionIndex, u32 sample_dur) | |
| { | |
| GF_BitStream *bs; | |
| u32 pay_start, txt_size; | |
| Bool is_utf_16 = 0; | |
| if (!samp || !samp->data || !samp->dataLength) return GF_OK; | |
| bs = gf_bs_new(samp->data, samp->dataLength, GF_BITSTREAM_READ); | |
| txt_size = gf_bs_read_u16(bs); | |
| gf_bs_del(bs); | |
| /*remove BOM*/ | |
| pay_start = 2; | |
| if (txt_size>2) { | |
| /*seems 3GP only accepts BE UTF-16 (no LE, no UTF32)*/ | |
| if (((u8) samp->data[2]==(u8) 0xFE) && ((u8)samp->data[3]==(u8) 0xFF)) { | |
| is_utf_16 = 1; | |
| pay_start = 4; | |
| txt_size -= 2; | |
| } | |
| } | |
| /*rewrite as TTU(1)*/ | |
| bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); | |
| gf_bs_write_int(bs, is_utf_16, 1); | |
| gf_bs_write_int(bs, 0, 4); | |
| gf_bs_write_int(bs, 1, 3); | |
| gf_bs_write_u16(bs, 8 + samp->dataLength - pay_start); | |
| gf_bs_write_u8(bs, sampleDescriptionIndex + SAMPLE_INDEX_OFFSET); | |
| gf_bs_write_u24(bs, sample_dur); | |
| /*write text size*/ | |
| gf_bs_write_u16(bs, txt_size); | |
| if (txt_size) gf_bs_write_data(bs, samp->data + pay_start, samp->dataLength - pay_start); | |
| gf_free(samp->data); | |
| samp->data = NULL; | |
| gf_bs_get_content(bs, &samp->data, &samp->dataLength); | |
| gf_bs_del(bs); | |
| return GF_OK; | |
| } | |
| GF_Err gf_isom_text_get_encoded_tx3g(GF_ISOFile *file, u32 track, u32 sidx, u32 sidx_offset, u8 **tx3g, u32 *tx3g_size) | |
| { | |
| GF_BitStream *bs; | |
| GF_TrackBox *trak; | |
| GF_Tx3gSampleEntryBox *a; | |
| *tx3g = NULL; | |
| *tx3g_size = 0; | |
| trak = gf_isom_get_track_box(file, track); | |
| if (!trak) return GF_BAD_PARAM; | |
| a = (GF_Tx3gSampleEntryBox *) gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, sidx-1); | |
| if (!a) return GF_BAD_PARAM; | |
| if ((a->type != GF_ISOM_BOX_TYPE_TX3G) && (a->type != GF_ISOM_BOX_TYPE_TEXT)) return GF_BAD_PARAM; | |
| bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE); | |
| gf_isom_write_tx3g(a, bs, sidx, sidx_offset); | |
| gf_bs_get_content(bs, tx3g, tx3g_size); | |
| gf_bs_del(bs); | |
| return GF_OK; | |
| } | |
| GF_EXPORT | |
| GF_Err gf_isom_set_forced_text(GF_ISOFile *file, u32 track, u32 stsd_idx, u32 flags) | |
| { | |
| GF_TrackBox *trak; | |
| GF_Tx3gSampleEntryBox *a; | |
| trak = gf_isom_get_track_box(file, track); | |
| if (!trak) return GF_BAD_PARAM; | |
| a = (GF_Tx3gSampleEntryBox *) gf_list_get(trak->Media->information->sampleTable->SampleDescription->child_boxes, stsd_idx-1); | |
| if (!a) return GF_BAD_PARAM; | |
| if ((a->type != GF_ISOM_BOX_TYPE_TX3G) && (a->type != GF_ISOM_BOX_TYPE_TEXT)) return GF_BAD_PARAM; | |
| switch (flags) { | |
| case 2: | |
| a->displayFlags |= GF_TXT_SOME_SAMPLES_FORCED | GF_TXT_ALL_SAMPLES_FORCED; | |
| break; | |
| case 1: | |
| a->displayFlags |= GF_TXT_SOME_SAMPLES_FORCED; | |
| a->displayFlags &= ~GF_TXT_ALL_SAMPLES_FORCED; | |
| break; | |
| default: | |
| a->displayFlags &= ~GF_TXT_SOME_SAMPLES_FORCED; | |
| a->displayFlags &= ~GF_TXT_ALL_SAMPLES_FORCED; | |
| break; | |
| } | |
| return GF_OK; | |
| } | |
| #endif /*GPAC_DISABLE_ISOM*/ |