1 // Copyright 2005-6 Ben Hutchings <ben@decadent.org.uk>.
2 // See the file "COPYING" for licence details.
7 #include <glibmm/spawn.h>
10 #include "generate_dvd.hpp"
11 #include "xml_utils.hpp"
13 dvd_contents::menu::menu()
14 : vob_temp(new temp_file("videolink-vob-"))
19 void generate_dvd(const dvd_contents & contents,
20 const std::string & output_dir)
22 temp_file temp("videolink-dvdauthor-");
24 std::ofstream file(temp.get_name().c_str());
26 // We generate code that uses registers in the following way:
29 // g1: current location
30 // g2: location that last jumped to a title
31 // g3: target chapter number
33 // All locations are divided into two bitfields: the least
34 // significant 10 bits are a page/menu number and the most
35 // significant 6 bits are a link/button number, and numbering
36 // starts at 1, not 0. This is done for compatibility with
37 // the encoding of the s8 (button) register.
39 static const int button_mult = dvd::reg_s8_button_mult;
40 static const int menu_mask = button_mult - 1;
41 static const int button_mask = (1 << dvd::reg_bits) - button_mult;
48 for (unsigned menu_index = 0;
49 menu_index != contents.menus.size();
52 const dvd_contents::menu & menu = contents.menus[menu_index];
56 // This is the first (title) menu, displayed when the
57 // disc is first played.
59 " <pgc entry='title' pause='inf'>\n"
61 // Initialise the current location if it is not set
62 // (all general registers are initially 0).
64 " g1 = " << 1 + button_mult << ";\n";
69 " <pgc pause='inf'>\n"
73 // When a title finishes or the user presses the menu
74 // button, this always jumps to the titleset's root menu.
75 // We want to return the user to the last menu they used.
76 // So we arrange for each titleset's root menu to return
77 // to the vmgm title menu and then dispatch from there to
78 // whatever the correct menu is. We determine the correct
79 // menu by looking at the menu part of g1.
81 file << " g0 = g1 & " << menu_mask << ";\n";
83 // There is a limit of 128 VM instructions in each PGC.
84 // Therefore in each menu's <pre> section we generate
85 // jumps to menus with numbers greater by 512, 256, 128,
86 // ..., 1 where (a) such a menu exists, (b) this menu
87 // number is divisible by twice that increment and (c) the
88 // correct menu is that or a later menu. Thus each menu
89 // has at most 10 such conditional jumps and is reachable
90 // by at most 10 jumps from the title menu. This chain of
91 // jumps might take too long on some players; this has yet
92 // to be investigated.
94 for (std::size_t menu_incr = (menu_mask + 1) / 2;
98 if (menu_index + menu_incr < contents.menus.size()
99 && (menu_index & (menu_incr * 2 - 1)) == 0)
102 " if (g0 ge " << 1 + menu_index + menu_incr
104 " jump menu " << 1 + menu_index + menu_incr
110 // Highlight the appropriate button.
111 " s8 = g1 & " << button_mask << ";\n"
113 " <vob file='" << menu.vob_temp->get_name() << "'/>\n";
115 for (unsigned button_index = 0;
116 button_index != menu.entries.size();
119 const dvd_contents::pgc_ref & target =
120 menu.entries[button_index];
122 file << " <button> ";
124 if (target.type == dvd_contents::menu_pgc)
126 unsigned target_button_num;
128 if (target.sub_index)
130 target_button_num = target.sub_index;
134 // Look for a button on the new menu that links
135 // back to this one. If there is one, set that to
136 // be the highlighted button; otherwise, use the
138 const std::vector<dvd_contents::pgc_ref> &
139 target_menu_entries =
140 contents.menus[target.index].entries;
141 dvd_contents::pgc_ref this_pgc(dvd_contents::menu_pgc,
143 target_button_num = target_menu_entries.size();
144 while (target_button_num != 0
145 && (target_menu_entries[target_button_num - 1]
151 // Set new menu location.
152 "g1 = " << (1 + target.index
153 + target_button_num * button_mult) << "; "
154 // Jump to the target menu.
155 "jump menu " << 1 + target.index << "; ";
159 assert(target.type == dvd_contents::title_pgc);
162 // Record current menu location (g1 specifies this
163 // menu but not necessarily this button).
164 "g1 = " << (1 + menu_index
165 + (1 + button_index) * button_mult) << "; "
166 // Set target chapter number.
167 "g3 = " << target.sub_index << "; "
168 // Jump to the target title.
169 "jump title " << 1 + target.index << "; ";
172 file << "</button>\n";
176 // Some DVD players don't seem to obey pause='inf' so make
188 // Generate a titleset for each title. This appears to make
189 // jumping to titles a whole lot simpler (but limits us to 99
191 for (unsigned title_index = 0;
192 title_index != contents.titles.size();
197 // Generate a dummy menu so that the menu button on the
198 // remote control will work.
200 " <pgc entry='root'>\n"
201 " <pre> jump vmgm menu; </pre>\n"
207 // Record calling location.
210 // Count chapters in the title.
211 unsigned n_chapters = 0;
212 for (vob_list::const_iterator
213 it = contents.titles[title_index].begin(),
214 end = contents.titles[title_index].end();
218 // Chapter start times may be specified in the "chapters"
219 // attribute as a comma-separated list. If this is not
220 // specified then the beginning of each file starts a new
221 // chapter. Thus the number of chapters in each file is
222 // the number of commas in the chapter attribute, plus 1.
225 while ((pos = it->chapters.find(',', pos)) != std::string::npos)
232 // Generate jump "table" for chapters.
233 for (unsigned chapter_num = 1;
234 chapter_num <= n_chapters;
237 " if (g3 == " << chapter_num << ")\n"
238 " jump chapter " << chapter_num << ";\n";
243 for (vob_list::const_iterator
244 it = contents.titles[title_index].begin(),
245 end = contents.titles[title_index].end();
249 file << " <vob file='" << xml_escape(it->file) << "'";
250 if (!it->chapters.empty())
251 file << " chapters='" << xml_escape(it->chapters) << "'";
252 if (!it->pause.empty())
253 file << " pause='" << xml_escape(it->pause) << "'";
259 // If the menu location has not been changed during
260 // the title, set the location to be the following
261 // button in the menu.
263 " g1 = g1 + " << button_mult << ";\n"
264 // In any case, return to some menu.
278 const char * argv[] = {
280 "-o", output_dir.c_str(),
281 "-x", temp.get_name().c_str(),
285 Glib::spawn_sync(".",
286 Glib::ArrayHandle<std::string>(
287 argv, sizeof(argv)/sizeof(argv[0]),
288 Glib::OWNERSHIP_NONE),
289 Glib::SPAWN_SEARCH_PATH
290 | Glib::SPAWN_STDOUT_TO_DEV_NULL,
294 if (command_result != 0)
295 throw std::runtime_error("dvdauthor failed");