LCOV - code coverage report
Current view: top level - filters - write_vtt.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 149 158 94.3 %
Date: 2021-04-29 23:48:07 Functions: 8 8 100.0 %

          Line data    Source code
       1             : /*
       2             :  *                      GPAC - Multimedia Framework C SDK
       3             :  *
       4             :  *                      Authors: Jean Le Feuvre
       5             :  *                      Copyright (c) Telecom ParisTech 2000-2021
       6             :  *                                      All rights reserved
       7             :  *
       8             :  *  This file is part of GPAC / WebVTT stream to file filter
       9             :  *
      10             :  *  GPAC is free software; you can redistribute it and/or modify
      11             :  *  it under the terms of the GNU Lesser General Public License as published by
      12             :  *  the Free Software Foundation; either version 2, or (at your option)
      13             :  *  any later version.
      14             :  *
      15             :  *  GPAC is distributed in the hope that it will be useful,
      16             :  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
      17             :  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      18             :  *  GNU Lesser General Public License for more details.
      19             :  *
      20             :  *  You should have received a copy of the GNU Lesser General Public
      21             :  *  License along with this library; see the file COPYING.  If not, write to
      22             :  *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
      23             :  *
      24             :  */
      25             : 
      26             : #include <gpac/filters.h>
      27             : #include <gpac/constants.h>
      28             : #include <gpac/bitstream.h>
      29             : #include <gpac/webvtt.h>
      30             : #include <gpac/internal/media_dev.h>
      31             : 
      32             : 
      33             : typedef struct
      34             : {
      35             :         //opts
      36             :         Bool exporter, merge;
      37             : 
      38             :         //only one input pid declared
      39             :         GF_FilterPid *ipid;
      40             :         //only one output pid declared
      41             :         GF_FilterPid *opid;
      42             : 
      43             :         u32 codecid;
      44             :         Bool first;
      45             : 
      46             :         GF_Fraction64 duration;
      47             :         char *dcd;
      48             : 
      49             :         GF_BitStream *bs_w;
      50             :         u8 *cues_buffer;
      51             :         u32 cues_buffer_size;
      52             : 
      53             :         GF_WebVTTParser *parser;
      54             : 
      55             : } GF_WebVTTMxCtx;
      56             : 
      57             : static void vttmx_write_cue(void *ctx, GF_WebVTTCue *cue);
      58             : 
      59           4 : GF_Err vttmx_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove)
      60             : {
      61             :         const GF_PropertyValue *p;
      62           4 :         GF_WebVTTMxCtx *ctx = gf_filter_get_udta(filter);
      63             : 
      64           4 :         if (is_remove) {
      65           0 :                 ctx->ipid = NULL;
      66           0 :                 if (ctx->opid) {
      67           0 :                         gf_filter_pid_remove(ctx->opid);
      68           0 :                         ctx->opid = NULL;
      69             :                 }
      70             :                 return GF_OK;
      71             :         }
      72           4 :         if (! gf_filter_pid_check_caps(pid))
      73             :                 return GF_NOT_SUPPORTED;
      74             : 
      75           4 :         p = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID);
      76           4 :         if (!p) return GF_NOT_SUPPORTED;
      77           4 :         ctx->codecid = p->value.uint;
      78             : 
      79           4 :         p = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG);
      80           4 :         if (!p) return GF_NOT_SUPPORTED;
      81           4 :         if (ctx->dcd && !strcmp(ctx->dcd, p->value.string)) return GF_OK;
      82           4 :         if (ctx->dcd) gf_free(ctx->dcd);
      83             : 
      84           4 :         if ((p->type==GF_PROP_DATA) || (p->type==GF_PROP_DATA_NO_COPY)) {
      85           4 :                 ctx->dcd = gf_malloc(p->value.data.size+1);
      86           4 :                 memcpy(ctx->dcd, p->value.data.ptr, p->value.data.size);
      87           4 :                 ctx->dcd[p->value.data.size]=0;
      88             :         } else {
      89           0 :                 ctx->dcd = gf_strdup(p->value.string);
      90             :         }
      91           4 :         if (!ctx->opid) {
      92           4 :                 ctx->opid = gf_filter_pid_new(filter);
      93           4 :                 gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_FILE) );
      94           4 :                 gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_FILE_EXT, &PROP_STRING("vtt") );
      95           4 :                 gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_MIME, &PROP_STRING("text/vtt") );
      96           4 :                 ctx->first = GF_TRUE;
      97             : 
      98           4 :                 if (ctx->exporter) {
      99           3 :                         GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("Exporting %s\n", gf_codecid_name(ctx->codecid)));
     100             :                 }
     101             : 
     102             :         }
     103           4 :         ctx->ipid = pid;
     104           4 :         p = gf_filter_pid_get_property(pid, GF_PROP_PID_DURATION);
     105           4 :         if (p) ctx->duration = p->value.lfrac;
     106             : 
     107           4 :         if (ctx->parser) gf_webvtt_parser_del(ctx->parser);
     108           4 :         ctx->parser = NULL;
     109           4 :         if (ctx->merge) {
     110           1 :                 ctx->parser = gf_webvtt_parser_new();
     111           1 :                 gf_webvtt_parser_cue_callback(ctx->parser, vttmx_write_cue, ctx);
     112             :         }
     113           4 :         gf_filter_pid_set_framing_mode(pid, GF_TRUE);
     114           4 :         return GF_OK;
     115             : }
     116             : 
     117          96 : static void vttmx_timestamp_dump(GF_BitStream *bs, GF_WebVTTTimestamp *ts, Bool dump_hour, Bool write_srt)
     118             : {
     119             :         char szTS[200];
     120          96 :         szTS[0] = 0;
     121          96 :         if (dump_hour) {
     122          14 :                 sprintf(szTS, "%02u:", ts->hour);
     123          14 :                 gf_bs_write_data(bs, szTS, (u32) strlen(szTS) );
     124             :         }
     125          96 :         sprintf(szTS, "%02u:%02u%c%03u", ts->min, ts->sec, write_srt ? ',' : '.', ts->ms);
     126          96 :         gf_bs_write_data(bs, szTS, (u32) strlen(szTS) );
     127          96 : }
     128             : 
     129          48 : void webvtt_write_cue(GF_BitStream *bs, GF_WebVTTCue *cue, Bool write_srt)
     130             : {
     131             :         Bool write_hour = GF_FALSE;
     132          48 :         if (!cue) return;
     133          48 :         if (!write_srt && cue->pre_text) {
     134           0 :                 gf_bs_write_data(bs, cue->pre_text, (u32) strlen(cue->pre_text));
     135           0 :                 gf_bs_write_data(bs, "\n\n", 2);
     136             :         }
     137          48 :         if (!write_srt && cue->id) {
     138          24 :                 u32 len = (u32) strlen(cue->id) ;
     139          24 :                 gf_bs_write_data(bs, cue->id, len);
     140          24 :                 if (len && (cue->id[len-1]!='\n'))
     141          24 :                         gf_bs_write_data(bs, "\n", 1);
     142             :         }
     143             : 
     144          48 :         if (gf_opts_get_bool("core", "webvtt-hours")) write_hour = GF_TRUE;
     145          48 :         else if (cue->start.hour || cue->end.hour) write_hour = GF_TRUE;
     146          48 :         else if (write_srt) write_hour = GF_TRUE;
     147             : 
     148          48 :         vttmx_timestamp_dump(bs, &cue->start, write_hour, write_srt);
     149          48 :         gf_bs_write_data(bs, " --> ", 5);
     150          48 :         vttmx_timestamp_dump(bs, &cue->end, write_hour, write_srt);
     151             : 
     152          48 :         if (!write_srt && cue->settings) {
     153           9 :                 gf_bs_write_data(bs, " ", 1);
     154           9 :                 gf_bs_write_data(bs, cue->settings, (u32) strlen(cue->settings));
     155             :         }
     156          48 :         gf_bs_write_data(bs, "\n", 1);
     157          48 :         if (cue->text)
     158          48 :                 gf_bs_write_data(bs, cue->text, (u32) strlen(cue->text));
     159             : 
     160          48 :         if (!write_srt)
     161          41 :                 gf_bs_write_data(bs, "\n\n", 2);
     162             :         else
     163           7 :                 gf_bs_write_data(bs, "\n", 1);
     164             : 
     165          48 :         if (!write_srt && cue->post_text) {
     166           0 :                 gf_bs_write_data(bs, cue->post_text, (u32) strlen(cue->post_text));
     167           0 :                 gf_bs_write_data(bs, "\n\n", 2);
     168             :         }
     169             : }
     170             : 
     171           6 : static void vttmx_write_cue(void *udta, GF_WebVTTCue *cue)
     172             : {
     173             :         GF_WebVTTMxCtx *ctx = (GF_WebVTTMxCtx *)udta;
     174          36 :         webvtt_write_cue(ctx->bs_w, cue, GF_FALSE);
     175           6 : }
     176             : 
     177           1 : void vttmx_parser_flush(GF_WebVTTMxCtx *ctx)
     178             : {
     179             :         u8 *output;
     180           1 :         u64 duration = ctx->duration.num;
     181           1 :         duration *= 1000;
     182           1 :         duration /= ctx->duration.den;
     183             : 
     184           1 :         if (!ctx->bs_w) ctx->bs_w = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE);
     185           1 :         else gf_bs_reassign_buffer(ctx->bs_w, ctx->cues_buffer, ctx->cues_buffer_size);
     186             : 
     187           1 :         gf_webvtt_parser_finalize(ctx->parser, duration);
     188             : 
     189           1 :         if (gf_bs_get_position(ctx->bs_w)) {
     190             :                 GF_FilterPacket *dst_pck;
     191             :                 u32 size;
     192           1 :                 gf_bs_get_content_no_truncate(ctx->bs_w, &ctx->cues_buffer, &size, &ctx->cues_buffer_size);
     193           1 :                 dst_pck = gf_filter_pck_new_alloc(ctx->opid, size, &output);
     194           1 :                 if (dst_pck) {
     195           1 :                         memcpy(output, ctx->cues_buffer, size);
     196             : 
     197           1 :                         gf_filter_pck_set_byte_offset(dst_pck, GF_FILTER_NO_BO);
     198             : 
     199           1 :                         gf_filter_pck_set_framing(dst_pck, GF_FALSE, GF_TRUE);
     200           1 :                         gf_filter_pck_send(dst_pck);
     201             :                 }
     202             :         }
     203             : 
     204           1 :         gf_webvtt_parser_del(ctx->parser);
     205           1 :         ctx->parser = NULL;
     206             : 
     207           1 : }
     208          56 : GF_Err vttmx_process(GF_Filter *filter)
     209             : {
     210          56 :         GF_WebVTTMxCtx *ctx = gf_filter_get_udta(filter);
     211             :         GF_FilterPacket *pck, *dst_pck;
     212             :         u8 *data, *output;
     213             :         u64 start_ts, end_ts;
     214             :         u32 i, pck_size, size, timescale;
     215             :         GF_List *cues;
     216             : 
     217          56 :         pck = gf_filter_pid_get_packet(ctx->ipid);
     218          56 :         if (!pck) {
     219           5 :                 if (gf_filter_pid_is_eos(ctx->ipid)) {
     220           5 :                         gf_filter_pid_set_eos(ctx->opid);
     221           5 :                         if (ctx->parser) {
     222           1 :                                 vttmx_parser_flush(ctx);
     223             :                         }
     224             :                         return GF_EOS;
     225             :                 }
     226             :                 return GF_OK;
     227             :         }
     228             : 
     229          51 :         data = (char *) gf_filter_pck_get_data(pck, &pck_size);
     230             : 
     231          51 :         if (ctx->first && ctx->dcd) {
     232           4 :                 size = (u32) strlen(ctx->dcd)+2;
     233           4 :                 dst_pck = gf_filter_pck_new_alloc(ctx->opid, size, &output);
     234           4 :                 if (!dst_pck) return GF_OUT_OF_MEM;
     235             : 
     236           4 :                 memcpy(output, ctx->dcd, size-2);
     237           4 :                 output[size-2] = '\n';
     238           4 :                 output[size-1] = '\n';
     239           4 :                 gf_filter_pck_merge_properties(pck, dst_pck);
     240           4 :                 gf_filter_pck_set_byte_offset(dst_pck, GF_FILTER_NO_BO);
     241           4 :                 gf_filter_pck_set_framing(dst_pck, ctx->first, GF_FALSE);
     242           4 :                 ctx->first = GF_FALSE;
     243           4 :                 gf_filter_pck_send(dst_pck);
     244             :         }
     245             : 
     246          51 :         if (!ctx->bs_w) ctx->bs_w = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE);
     247          47 :         else gf_bs_reassign_buffer(ctx->bs_w, ctx->cues_buffer, ctx->cues_buffer_size);
     248             : 
     249          51 :         start_ts = gf_filter_pck_get_cts(pck);
     250          51 :         end_ts = start_ts + gf_filter_pck_get_duration(pck);
     251          51 :         start_ts *= 1000;
     252          51 :         end_ts *= 1000;
     253          51 :         timescale = gf_filter_pck_get_timescale(pck);
     254          51 :         if (!timescale) timescale=1000;
     255          51 :         start_ts /= timescale;
     256          51 :         end_ts /= timescale;
     257             : 
     258          51 :         cues = gf_webvtt_parse_cues_from_data(data, pck_size, start_ts, end_ts);
     259          51 :         if (ctx->parser) {
     260          11 :                 gf_webvtt_merge_cues(ctx->parser, start_ts, cues);
     261             :         } else {
     262          30 :                 for (i = 0; i < gf_list_count(cues); i++) {
     263          30 :                         GF_WebVTTCue *cue = (GF_WebVTTCue *)gf_list_get(cues, i);
     264             :                         vttmx_write_cue(ctx, cue);
     265          30 :                         gf_webvtt_cue_del(cue);
     266             :                 }
     267             :         }
     268          51 :         gf_list_del(cues);
     269             : 
     270          51 :         gf_bs_get_content_no_truncate(ctx->bs_w, &ctx->cues_buffer, &size, &ctx->cues_buffer_size);
     271          51 :         if (size) {
     272          32 :                 dst_pck = gf_filter_pck_new_alloc(ctx->opid, size, &output);
     273          32 :                 if (!dst_pck) return GF_OUT_OF_MEM;
     274             : 
     275          32 :                 memcpy(output, ctx->cues_buffer, size);
     276          32 :                 gf_filter_pck_merge_properties(pck, dst_pck);
     277          32 :                 gf_filter_pck_set_byte_offset(dst_pck, GF_FILTER_NO_BO);
     278             : 
     279          32 :                 gf_filter_pck_set_framing(dst_pck, ctx->first, GF_FALSE);
     280          32 :                 ctx->first = GF_FALSE;
     281             : 
     282          32 :                 gf_filter_pck_send(dst_pck);
     283             :         }
     284             : 
     285          51 :         if (ctx->exporter) {
     286          40 :                 u64 ts = gf_filter_pck_get_cts(pck);
     287          40 :                 timescale = gf_filter_pck_get_timescale(pck);
     288          40 :                 gf_set_progress("Exporting", ts*ctx->duration.den, ctx->duration.num*timescale);
     289             :         }
     290             : 
     291          51 :         gf_filter_pid_drop_packet(ctx->ipid);
     292             : 
     293          51 :         return GF_OK;
     294             : }
     295             : 
     296           4 : static void vttmx_finalize(GF_Filter *filter)
     297             : {
     298           4 :         GF_WebVTTMxCtx *ctx = gf_filter_get_udta(filter);
     299           4 :         if (ctx->bs_w) gf_bs_del(ctx->bs_w);
     300           4 :         if (ctx->dcd) gf_free(ctx->dcd);
     301           4 :         if (ctx->cues_buffer) gf_free(ctx->cues_buffer);
     302             : 
     303           4 :         if (ctx->parser) gf_webvtt_parser_del(ctx->parser);
     304           4 : }
     305             : 
     306             : static const GF_FilterCapability WebVTTMxCaps[] =
     307             : {
     308             :         CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_TEXT),
     309             :         CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_WEBVTT),
     310             :         CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE),
     311             :         CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_FILE),
     312             :         CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_FILE_EXT, "vtt"),
     313             :         CAP_STRING(GF_CAPS_OUTPUT, GF_PROP_PID_MIME, "subtitle/vtt|text/vtt"),
     314             : };
     315             : 
     316             : 
     317             : #define OFFS(_n)        #_n, offsetof(GF_WebVTTMxCtx, _n)
     318             : static const GF_FilterArgs WebVTTMxArgs[] =
     319             : {
     320             :         { OFFS(exporter), "compatibility with old exporter, displays export results", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED},
     321             :         { OFFS(merge), "merge VTT cue if needed", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED},
     322             :         {0}
     323             : };
     324             : 
     325             : 
     326             : GF_FilterRegister WebVTTMxRegister = {
     327             :         .name = "writevtt",
     328             :         GF_FS_SET_DESCRIPTION("WebVTT writer")
     329             :         GF_FS_SET_HELP("This filter converts a single stream to a WebVTT output file.")
     330             :         .private_size = sizeof(GF_WebVTTMxCtx),
     331             :         .args = WebVTTMxArgs,
     332             :         .finalize = vttmx_finalize,
     333             :         SETCAPS(WebVTTMxCaps),
     334             :         .configure_pid = vttmx_configure_pid,
     335             :         .process = vttmx_process
     336             : };
     337             : 
     338             : 
     339        2877 : const GF_FilterRegister *vttmx_register(GF_FilterSession *session)
     340             : {
     341        2877 :         return &WebVTTMxRegister;
     342             : }

Generated by: LCOV version 1.13