Fix subtitle generation
[lsnes.git] / src / core / subtitles.cpp
blob5643f21caac80b8c9b3a3f35baee17d19f78410e
1 #include "core/command.hpp"
2 #include "core/dispatch.hpp"
3 #include "core/framebuffer.hpp"
4 #include "core/moviedata.hpp"
5 #include "core/subtitles.hpp"
6 #include "core/window.hpp"
7 #include "library/string.hpp"
8 #include "fonts/wrapper.hpp"
9 #include <fstream>
11 moviefile_subtiming::moviefile_subtiming(uint64_t _frame)
13 position_only = true;
14 frame = _frame;
15 length = 0;
18 moviefile_subtiming::moviefile_subtiming(uint64_t first, uint64_t _length)
20 position_only = false;
21 frame = first;
22 length = _length;
25 bool moviefile_subtiming::operator<(const moviefile_subtiming& a) const
27 //This goes in inverse order due to behaviour of lower_bound/upper_bound.
28 if(frame > a.frame)
29 return true;
30 if(frame < a.frame)
31 return false;
32 if(position_only && a.position_only)
33 return false;
34 //Position only compares greater than any of same frame.
35 if(position_only != a.position_only)
36 return position_only;
37 //Break ties on length.
38 return (length > a.length);
41 bool moviefile_subtiming::operator==(const moviefile_subtiming& a) const
43 if(frame != a.frame)
44 return false;
45 if(position_only && a.position_only)
46 return true;
47 if(position_only != a.position_only)
48 return false;
49 return (length != a.length);
52 bool moviefile_subtiming::inrange(uint64_t x) const
54 if(position_only)
55 return false;
56 return (x >= frame && x < frame + length);
59 uint64_t moviefile_subtiming::get_frame() const { return frame; }
60 uint64_t moviefile_subtiming::get_length() const { return length; }
62 namespace
64 std::string s_subescape(std::string x)
66 std::string y;
67 for(size_t i = 0; i < x.length(); i++) {
68 char ch = x[i];
69 if(ch == '\n')
70 y += "|";
71 else if(ch == '|')
72 y += "⎢";
73 else
74 y.append(1, ch);
76 return y;
79 struct render_object_subtitle : public render_object
81 render_object_subtitle(int32_t _x, int32_t _y, const std::string& _text) throw()
82 : x(_x), y(_y), text(_text), fg(0xFFFF80), bg(-1) {}
83 ~render_object_subtitle() throw() {}
84 template<bool X> void op(struct framebuffer<X>& scr) throw()
86 fg.set_palette(scr);
87 bg.set_palette(scr);
88 main_font.render(scr, x, y, text, fg, bg, false, false);
90 void operator()(struct framebuffer<true>& scr) throw() { op(scr); }
91 void operator()(struct framebuffer<false>& scr) throw() { op(scr); }
92 void clone(render_queue& q) const throw(std::bad_alloc) { q.clone_helper(this); }
93 private:
94 int32_t x;
95 int32_t y;
96 std::string text;
97 premultiplied_color fg;
98 premultiplied_color bg;
102 function_ptr_command<const std::string&> edit_subtitle(lsnes_cmd, "edit-subtitle", "Edit a subtitle",
103 "Syntax: edit-subtitle <first> <length> <text>\nAdd/Edit subtitle\n"
104 "Syntax: edit-subtitle <first> <length>\nADelete subtitle\n",
105 [](const std::string& args) throw(std::bad_alloc, std::runtime_error) {
106 auto r = regex("([0-9]+)[ \t]+([0-9]+)([ \t]+(.*))?", args, "Bad syntax");
107 uint64_t frame = parse_value<uint64_t>(r[1]);
108 uint64_t length = parse_value<uint64_t>(r[2]);
109 std::string text = r[4];
110 moviefile_subtiming key(frame, length);
111 if(text == "")
112 our_movie.subtitles.erase(key);
113 else
114 our_movie.subtitles[key] = s_unescape(text);
115 information_dispatch::do_subtitle_change();
116 redraw_framebuffer();
119 function_ptr_command<> list_subtitle(lsnes_cmd, "list-subtitle", "List the subtitles",
120 "Syntax: list-subtitle\nList the subtitles.\n",
121 []() throw(std::bad_alloc, std::runtime_error) {
122 for(auto i = our_movie.subtitles.rbegin(); i != our_movie.subtitles.rend(); i++) {
123 messages << i->first.get_frame() << " " << i->first.get_length() << " "
124 << s_escape(i->second) << std::endl;
128 function_ptr_command<arg_filename> save_s(lsnes_cmd, "save-subtitle", "Save subtitles in .sub format",
129 "Syntax: save-subtitle <file>\nSaves subtitles in .sub format to <file>\n",
130 [](arg_filename args) throw(std::bad_alloc, std::runtime_error) {
131 if(our_movie.subtitles.empty())
132 return;
133 auto i = our_movie.subtitles.begin();
134 uint64_t lastframe = i->first.get_frame() + i->first.get_length();
135 std::ofstream y(std::string(args).c_str());
136 if(!y)
137 throw std::runtime_error("Can't open output file");
138 std::string lasttxt = "";
139 uint64_t since = 0;
140 for(uint64_t i = 1; i < lastframe; i++) {
141 moviefile_subtiming posmarker(i);
142 auto j = our_movie.subtitles.upper_bound(posmarker);
143 if(j == our_movie.subtitles.end())
144 continue;
145 if(lasttxt != j->second || !j->first.inrange(i)) {
146 if(lasttxt != "")
147 y << "{" << since << "}{" << i - 1 << "}" << s_subescape(lasttxt)
148 << std::endl;
149 since = i;
150 lasttxt = j->first.inrange(i) ? j->second : "";
153 if(lasttxt != "")
154 y << "{" << since << "}{" << lastframe - 1 << "}" << s_subescape(lasttxt)
155 << std::endl;
156 messages << "Saved subtitles to " << std::string(args) << std::endl;
160 std::string s_escape(std::string x)
162 std::string y;
163 for(size_t i = 0; i < x.length(); i++) {
164 char ch = x[i];
165 if(ch == '\n')
166 y += "\\n";
167 else if(ch == '\\')
168 y += "\\";
169 else
170 y.append(1, ch);
172 return y;
175 std::string s_unescape(std::string x)
177 bool escape = false;
178 std::string y;
179 for(size_t i = 0; i < x.length(); i++) {
180 char ch = x[i];
181 if(escape) {
182 if(ch == 'n')
183 y.append(1, '\n');
184 if(ch == '\\')
185 y.append(1, '\\');
186 escape = false;
187 } else {
188 if(ch == '\\')
189 escape = true;
190 else
191 y.append(1, ch);
194 return y;
197 void render_subtitles(lua_render_context& ctx)
199 if(our_movie.subtitles.empty())
200 return;
201 if(ctx.bottom_gap < 32)
202 ctx.bottom_gap = 32;
203 uint64_t curframe = movb.get_movie().get_current_frame() + 1;
204 moviefile_subtiming posmarker(curframe);
205 auto i = our_movie.subtitles.upper_bound(posmarker);
206 if(i != our_movie.subtitles.end() && i->first.inrange(curframe)) {
207 std::string subtxt = i->second;
208 int32_t y = ctx.height;
209 ctx.queue->create_add<render_object_subtitle>(0, y, subtxt);
213 std::set<std::pair<uint64_t, uint64_t>> get_subtitles()
215 std::set<std::pair<uint64_t, uint64_t>> r;
216 for(auto i = our_movie.subtitles.rbegin(); i != our_movie.subtitles.rend(); i++)
217 r.insert(std::make_pair(i->first.get_frame(), i->first.get_length()));
218 return r;
221 std::string get_subtitle_for(uint64_t f, uint64_t l)
223 moviefile_subtiming key(f, l);
224 if(!our_movie.subtitles.count(key))
225 return "";
226 else
227 return s_escape(our_movie.subtitles[key]);
230 void set_subtitle_for(uint64_t f, uint64_t l, const std::string& x)
232 moviefile_subtiming key(f, l);
233 if(x == "")
234 our_movie.subtitles.erase(key);
235 else
236 our_movie.subtitles[key] = s_unescape(x);
237 information_dispatch::do_subtitle_change();
238 redraw_framebuffer();