Subversion Repositories Kolibri OS

Rev

Blame | Last modification | View Log | RSS feed

  1. /*
  2.  * SSA/ASS muxer
  3.  * Copyright (c) 2008 Michael Niedermayer
  4.  *
  5.  * This file is part of FFmpeg.
  6.  *
  7.  * FFmpeg is free software; you can redistribute it and/or
  8.  * modify it under the terms of the GNU Lesser General Public
  9.  * License as published by the Free Software Foundation; either
  10.  * version 2.1 of the License, or (at your option) any later version.
  11.  *
  12.  * FFmpeg is distributed in the hope that it will be useful,
  13.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  15.  * Lesser General Public License for more details.
  16.  *
  17.  * You should have received a copy of the GNU Lesser General Public
  18.  * License along with FFmpeg; if not, write to the Free Software
  19.  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  20.  */
  21.  
  22. #include "libavutil/avstring.h"
  23. #include "avformat.h"
  24. #include "internal.h"
  25.  
  26. #include "libavutil/opt.h"
  27.  
  28. typedef struct DialogueLine {
  29.     int readorder;
  30.     char *line;
  31.     struct DialogueLine *prev, *next;
  32. } DialogueLine;
  33.  
  34. typedef struct ASSContext {
  35.     const AVClass *class;
  36.     int write_ts; // 0: ssa (timing in payload), 1: ass (matroska like)
  37.     int expected_readorder;
  38.     DialogueLine *dialogue_cache;
  39.     DialogueLine *last_added_dialogue;
  40.     int cache_size;
  41.     int ssa_mode;
  42.     int ignore_readorder;
  43.     uint8_t *trailer;
  44.     size_t trailer_size;
  45. } ASSContext;
  46.  
  47. static int write_header(AVFormatContext *s)
  48. {
  49.     ASSContext *ass = s->priv_data;
  50.     AVCodecContext *avctx = s->streams[0]->codec;
  51.  
  52.     if (s->nb_streams != 1 || (avctx->codec_id != AV_CODEC_ID_SSA &&
  53.                                avctx->codec_id != AV_CODEC_ID_ASS)) {
  54.         av_log(s, AV_LOG_ERROR, "Exactly one ASS/SSA stream is needed.\n");
  55.         return AVERROR(EINVAL);
  56.     }
  57.     ass->write_ts = avctx->codec_id == AV_CODEC_ID_ASS;
  58.     avpriv_set_pts_info(s->streams[0], 64, 1, 100);
  59.     if (avctx->extradata_size > 0) {
  60.         size_t header_size = avctx->extradata_size;
  61.         uint8_t *trailer = strstr(avctx->extradata, "\n[Events]");
  62.  
  63.         if (trailer)
  64.             trailer = strstr(trailer, "Format:");
  65.         if (trailer)
  66.             trailer = strstr(trailer, "\n");
  67.  
  68.         if (trailer++) {
  69.             header_size = (trailer - avctx->extradata);
  70.             ass->trailer_size = avctx->extradata_size - header_size;
  71.             if (ass->trailer_size)
  72.                 ass->trailer = trailer;
  73.         }
  74.  
  75.         avio_write(s->pb, avctx->extradata, header_size);
  76.         if (avctx->extradata[header_size - 1] != '\n')
  77.             avio_write(s->pb, "\r\n", 2);
  78.         ass->ssa_mode = !strstr(avctx->extradata, "\n[V4+ Styles]");
  79.         if (!strstr(avctx->extradata, "\n[Events]"))
  80.             avio_printf(s->pb, "[Events]\r\nFormat: %s, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\r\n",
  81.                         ass->ssa_mode ? "Marked" : "Layer");
  82.     }
  83.     avio_flush(s->pb);
  84.  
  85.     return 0;
  86. }
  87.  
  88. static void purge_dialogues(AVFormatContext *s, int force)
  89. {
  90.     int n = 0;
  91.     ASSContext *ass = s->priv_data;
  92.     DialogueLine *dialogue = ass->dialogue_cache;
  93.  
  94.     while (dialogue && (dialogue->readorder == ass->expected_readorder || force)) {
  95.         DialogueLine *next = dialogue->next;
  96.         if (dialogue->readorder != ass->expected_readorder) {
  97.             av_log(s, AV_LOG_WARNING, "ReadOrder gap found between %d and %d\n",
  98.                    ass->expected_readorder, dialogue->readorder);
  99.             ass->expected_readorder = dialogue->readorder;
  100.         }
  101.         avio_printf(s->pb, "Dialogue: %s\r\n", dialogue->line);
  102.         if (dialogue == ass->last_added_dialogue)
  103.             ass->last_added_dialogue = next;
  104.         av_freep(&dialogue->line);
  105.         av_free(dialogue);
  106.         if (next)
  107.             next->prev = NULL;
  108.         dialogue = ass->dialogue_cache = next;
  109.         ass->expected_readorder++;
  110.         n++;
  111.     }
  112.     ass->cache_size -= n;
  113.     if (n > 1)
  114.         av_log(s, AV_LOG_DEBUG, "wrote %d ASS lines, cached dialogues: %d, waiting for event id %d\n",
  115.                n, ass->cache_size, ass->expected_readorder);
  116. }
  117.  
  118. static void insert_dialogue(ASSContext *ass, DialogueLine *dialogue)
  119. {
  120.     DialogueLine *cur, *next = NULL, *prev = NULL;
  121.  
  122.     /* from the last added to the end of the list */
  123.     if (ass->last_added_dialogue) {
  124.         for (cur = ass->last_added_dialogue; cur; cur = cur->next) {
  125.             if (cur->readorder > dialogue->readorder)
  126.                 break;
  127.             prev = cur;
  128.             next = cur->next;
  129.         }
  130.     }
  131.  
  132.     /* from the beginning to the last one added */
  133.     if (!prev) {
  134.         next = ass->dialogue_cache;
  135.         for (cur = next; cur != ass->last_added_dialogue; cur = cur->next) {
  136.             if (cur->readorder > dialogue->readorder)
  137.                 break;
  138.             prev = cur;
  139.             next = cur->next;
  140.         }
  141.     }
  142.  
  143.     if (prev) {
  144.         prev->next = dialogue;
  145.         dialogue->prev = prev;
  146.     } else {
  147.         dialogue->prev = ass->dialogue_cache;
  148.         ass->dialogue_cache = dialogue;
  149.     }
  150.     if (next) {
  151.         next->prev = dialogue;
  152.         dialogue->next = next;
  153.     }
  154.     ass->cache_size++;
  155.     ass->last_added_dialogue = dialogue;
  156. }
  157.  
  158. static int write_packet(AVFormatContext *s, AVPacket *pkt)
  159. {
  160.     ASSContext *ass = s->priv_data;
  161.  
  162.     if (ass->write_ts) {
  163.         long int layer;
  164.         char *p = pkt->data;
  165.         int64_t start = pkt->pts;
  166.         int64_t end   = start + pkt->duration;
  167.         int hh1, mm1, ss1, ms1;
  168.         int hh2, mm2, ss2, ms2;
  169.         DialogueLine *dialogue = av_mallocz(sizeof(*dialogue));
  170.  
  171.         if (!dialogue)
  172.             return AVERROR(ENOMEM);
  173.  
  174.         dialogue->readorder = strtol(p, &p, 10);
  175.         if (dialogue->readorder < ass->expected_readorder)
  176.             av_log(s, AV_LOG_WARNING, "Unexpected ReadOrder %d\n",
  177.                    dialogue->readorder);
  178.         if (*p == ',')
  179.             p++;
  180.  
  181.         if (ass->ssa_mode && !strncmp(p, "Marked=", 7))
  182.             p += 7;
  183.  
  184.         layer = strtol(p, &p, 10);
  185.         if (*p == ',')
  186.             p++;
  187.         hh1 = (int)(start / 360000);    mm1 = (int)(start / 6000) % 60;
  188.         hh2 = (int)(end   / 360000);    mm2 = (int)(end   / 6000) % 60;
  189.         ss1 = (int)(start / 100) % 60;  ms1 = (int)(start % 100);
  190.         ss2 = (int)(end   / 100) % 60;  ms2 = (int)(end   % 100);
  191.         if (hh1 > 9) hh1 = 9, mm1 = 59, ss1 = 59, ms1 = 99;
  192.         if (hh2 > 9) hh2 = 9, mm2 = 59, ss2 = 59, ms2 = 99;
  193.  
  194.         dialogue->line = av_asprintf("%s%ld,%d:%02d:%02d.%02d,%d:%02d:%02d.%02d,%s",
  195.                                      ass->ssa_mode ? "Marked=" : "",
  196.                                      layer, hh1, mm1, ss1, ms1, hh2, mm2, ss2, ms2, p);
  197.         if (!dialogue->line) {
  198.             av_free(dialogue);
  199.             return AVERROR(ENOMEM);
  200.         }
  201.         insert_dialogue(ass, dialogue);
  202.         purge_dialogues(s, ass->ignore_readorder);
  203.     } else {
  204.         avio_write(s->pb, pkt->data, pkt->size);
  205.     }
  206.  
  207.     return 0;
  208. }
  209.  
  210. static int write_trailer(AVFormatContext *s)
  211. {
  212.     ASSContext *ass = s->priv_data;
  213.  
  214.     purge_dialogues(s, 1);
  215.  
  216.     if (ass->trailer) {
  217.         avio_write(s->pb, ass->trailer, ass->trailer_size);
  218.     }
  219.  
  220.     return 0;
  221. }
  222.  
  223. #define OFFSET(x) offsetof(ASSContext, x)
  224. #define E AV_OPT_FLAG_ENCODING_PARAM
  225. static const AVOption options[] = {
  226.     { "ignore_readorder", "write events immediately, even if they're out-of-order", OFFSET(ignore_readorder), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, E },
  227.     { NULL },
  228. };
  229.  
  230. static const AVClass ass_class = {
  231.     .class_name = "ass muxer",
  232.     .item_name  = av_default_item_name,
  233.     .option     = options,
  234.     .version    = LIBAVUTIL_VERSION_INT,
  235. };
  236.  
  237. AVOutputFormat ff_ass_muxer = {
  238.     .name           = "ass",
  239.     .long_name      = NULL_IF_CONFIG_SMALL("SSA (SubStation Alpha) subtitle"),
  240.     .mime_type      = "text/x-ssa",
  241.     .extensions     = "ass,ssa",
  242.     .priv_data_size = sizeof(ASSContext),
  243.     .subtitle_codec = AV_CODEC_ID_SSA,
  244.     .write_header   = write_header,
  245.     .write_packet   = write_packet,
  246.     .write_trailer  = write_trailer,
  247.     .flags          = AVFMT_GLOBALHEADER | AVFMT_NOTIMESTAMPS | AVFMT_TS_NONSTRICT,
  248.     .priv_class     = &ass_class,
  249. };
  250.