1 /*
2    mkvextract -- extract tracks from Matroska files into other files
3 
4    Distributed under the GPL v2
5    see the file COPYING for details
6    or visit https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
7 
8    Extraction of Blu-Ray text subtitles.
9 
10    Written by Moritz Bunkus and Mike Chen.
11 */
12 
13 #include "common/common_pch.h"
14 
15 #include <algorithm>
16 #include <matroska/KaxBlock.h>
17 
18 #include "common/ebml.h"
19 #include "common/endian.h"
20 #include "common/hdmv_textst.h"
21 #include "extract/xtr_hdmv_textst.h"
22 
xtr_hdmv_textst_c(const std::string & codec_id,int64_t tid,track_spec_t & tspec)23 xtr_hdmv_textst_c::xtr_hdmv_textst_c(const std::string &codec_id,
24                                int64_t tid,
25                                track_spec_t &tspec)
26   : xtr_base_c{codec_id, tid, tspec}
27   , m_num_presentation_segments{}
28   , m_num_presentation_segment_position{}
29 {
30 }
31 
32 void
create_file(xtr_base_c * master,libmatroska::KaxTrackEntry & track)33 xtr_hdmv_textst_c::create_file(xtr_base_c *master,
34                                libmatroska::KaxTrackEntry &track) {
35   xtr_base_c::create_file(master, track);
36 
37   auto priv = FindChild<libmatroska::KaxCodecPrivate>(&track);
38   if (!priv)
39     mxerror(fmt::format(Y("Track {0} with the CodecID '{1}' is missing the \"codec private\" element and cannot be extracted.\n"), m_tid, m_codec_id));
40 
41   auto mpriv = decode_codec_private(priv);
42 
43   if (mpriv->get_size() < 6)
44     mxerror(fmt::format(Y("Track {0} CodecPrivate is too small.\n"), m_tid));
45 
46   // Older files created by MakeMKV have a different layout:
47   // 1 byte language code
48   // segment descriptor:
49   //   1 byte segment type (dialog style segment)
50   //   2 bytes segment size
51   // x bytes dialog style segment data
52   // 2 bytes frame count
53 
54   // Newer files will only contain the dialog style segment's
55   // descriptor and data
56 
57   auto buf                 = mpriv->get_buffer();
58   auto old_style           = buf[0] && (buf[0] < 0x10);
59   auto style_segment_start = old_style ? 1 : 0;
60   auto style_segment_size  = get_uint16_be(&buf[style_segment_start + 1]);
61 
62   if (mpriv->get_size() < static_cast<uint64_t>(3 + style_segment_size + (old_style ? 1 + 2 : 0)))
63     mxerror(fmt::format(Y("Track {0} CodecPrivate is too small.\n"), m_tid));
64 
65   m_out->write("TextST"s);
66 
67   // The dialog style segment.
68   m_out->write(&buf[style_segment_start], 3 + style_segment_size);
69 
70   // Remember the position for fixing the number of presentation
71   // segments.
72   unsigned char const zero[2] = { 0, 0 };
73   m_num_presentation_segment_position = m_out->getFilePointer();
74   m_out->write(&zero[0], 2);
75 }
76 
77 void
finish_file()78 xtr_hdmv_textst_c::finish_file() {
79   unsigned char buf[2];
80 
81   put_uint16_be(&buf[0], m_num_presentation_segments);
82   m_out->setFilePointer(m_num_presentation_segment_position);
83   m_out->write(&buf[0], 2);
84 }
85 
86 void
handle_frame(xtr_frame_t & f)87 xtr_hdmv_textst_c::handle_frame(xtr_frame_t &f) {
88   if (f.frame->get_size() < 13)
89     return;
90 
91   ++m_num_presentation_segments;
92 
93   auto buf       = f.frame->get_buffer();
94   auto start_pts = timestamp_c::ns(f.timestamp);
95   auto duration  = std::max<int64_t>(f.duration, 0);
96   auto end_pts   = start_pts + (duration != 0 ? timestamp_c::ns(duration) : (mtx::hdmv_textst::get_timestamp(&buf[3 + 5]) - mtx::hdmv_textst::get_timestamp(&buf[3 + 0])).abs());
97 
98   mtx::hdmv_textst::put_timestamp(&buf[3 + 0], start_pts);
99   mtx::hdmv_textst::put_timestamp(&buf[3 + 5], end_pts);
100 
101   m_out->write(f.frame);
102 }
103