1 /*
2    mkvmerge -- utility for splicing together matroska files
3    from component media subtypes
4 
5    Distributed under the GPL v2
6    see the file COPYING for details
7    or visit https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
8 
9    helper functions for SPU data (SubPicture Units — subtitles on DVDs)
10 
11    Written by Moritz Bunkus <moritz@bunkus.org>.
12 */
13 
14 #include "common/common_pch.h"
15 
16 #include "common/endian.h"
17 #include "common/spu.h"
18 #include "common/strings/formatting.h"
19 
20 namespace mtx::spu {
21 
22 static std::optional<std::size_t>
find_stop_display_position(unsigned char const * data,std::size_t const buf_size)23 find_stop_display_position(unsigned char const *data,
24                            std::size_t const buf_size) {
25   static debugging_option_c debug{"spu|spu_find_stop_display_position"};
26 
27   if (buf_size < 4)
28     return {};
29 
30   uint32_t control_start = get_uint16_be(&data[2]);
31   uint32_t start_off     = 0;
32   auto next_off          = control_start;
33 
34   while ((start_off != next_off) && (next_off < buf_size)) {
35     start_off = next_off;
36     next_off  = get_uint16_be(&data[start_off + 2]);
37 
38     if (next_off < start_off) {
39       mxdebug_if(debug, "spu::extraction_duration: Encountered broken SPU packet (next_off < start_off)\n");
40       return {};
41     }
42 
43     auto off = start_off + 4;
44     for (auto type = data[off++]; type != 0xff; type = data[off++]) {
45       auto info    = fmt::format("spu_extraction_duration: cmd = {0:02x} ", static_cast<unsigned int>(type));
46       auto unknown = false;
47       switch(type) {
48         case 0x00:
49           // Menu ID, 1 byte
50           info += "menu ID";
51           break;
52         case 0x01:
53           // Start display
54           info += "start display";
55           break;
56         case 0x02: {
57           // Stop display
58           auto date = timestamp_c::mpeg(static_cast<int64_t>(get_uint16_be(&data[start_off])) * 1024);
59           info     += fmt::format("stop display: {0}\n", mtx::string::format_timestamp(date));
60           mxdebug_if(debug, info);
61           return start_off;
62         }
63         case 0x03:
64           // Palette
65           info += "palette";
66           off += 2;
67           break;
68         case 0x04:
69           // Alpha
70           info += "alpha";
71           off += 2;
72           break;
73         case 0x05:
74           info += "coords";
75           off += 6;
76           break;
77         case 0x06:
78           info += "graphic lines";
79           off += 4;
80           break;
81         case 0xff:
82           // All done, bye-bye
83           info += "done";
84           return {};
85         default:
86           info += fmt::format("unknown (0x{0:02x}), skipping {1} bytes.", type, next_off - off);
87           unknown = true;
88       }
89       mxdebug_if(debug, fmt::format("{0}\n", info));
90       if (unknown)
91         break;
92     }
93   }
94 
95   return {};
96 }
97 
98 timestamp_c
get_duration(unsigned char const * data,std::size_t const buf_size)99 get_duration(unsigned char const *data,
100              std::size_t const buf_size) {
101   auto position = find_stop_display_position(data, buf_size);
102   if (!position || ((*position + 2) > buf_size))
103     return {};
104 
105   return timestamp_c::mpeg(static_cast<int64_t>(get_uint16_be(&data[*position])) * 1024);
106 }
107 
108 void
set_duration(unsigned char * data,std::size_t const buf_size,timestamp_c const & duration)109 set_duration(unsigned char *data,
110              std::size_t const buf_size,
111              timestamp_c const &duration) {
112   auto position = find_stop_display_position(data, buf_size);
113 
114   if (position && ((*position + 2) <= buf_size))
115     put_uint16_be(&data[*position], duration.to_mpeg() / 1024);
116 }
117 
118 }
119