1 // Copyright 2005-6 Ben Hutchings <ben@decadentplace.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("webdvd-vob-"))
19 void generate_dvd(const dvd_contents & contents,
20 const std::string & output_dir)
22 temp_file temp("webdvd-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 // g12: location that last jumped to a video
32 // All locations are divided into two bitfields: the least
33 // significant 10 bits are a page/menu number and the most
34 // significant 6 bits are a link/button number, and numbering
35 // starts at 1, not 0. This is done for compatibility with
36 // the encoding of the s8 (button) register.
38 static const int button_mult = dvd::reg_s8_button_mult;
39 static const int menu_mask = button_mult - 1;
40 static const int button_mask = (1 << dvd::reg_bits) - button_mult;
47 for (unsigned menu_num = 0;
48 menu_num != contents.menus.size();
51 const dvd_contents::menu & menu = contents.menus[menu_num];
55 // This is the first (title) menu, displayed when the
56 // disc is first played.
58 " <pgc entry='title' pause='inf'>\n"
60 // Initialise the current location if it is not set
61 // (all general registers are initially 0).
63 " g1 = " << 1 + button_mult << ";\n";
68 " <pgc pause='inf'>\n"
72 // When a title finishes or the user presses the menu
73 // button, this always jumps to the titleset's root menu.
74 // We want to return the user to the last menu they used.
75 // So we arrange for each titleset's root menu to return
76 // to the vmgm title menu and then dispatch from there to
77 // whatever the correct menu is. We determine the correct
78 // menu by looking at the menu part of g1.
80 file << " g0 = g1 & " << menu_mask << ";\n";
82 // There is a limit of 128 VM instructions in each PGC.
83 // Therefore in each menu's <pre> section we generate
84 // jumps to menus with numbers greater by 512, 256, 128,
85 // ..., 1 where (a) such a menu exists, (b) this menu
86 // number is divisible by twice that increment and (c) the
87 // correct menu is that or a later menu. Thus each menu
88 // has at most 10 such conditional jumps and is reachable
89 // by at most 10 jumps from the title menu. This chain of
90 // jumps might take too long on some players; this has yet
91 // to be investigated.
93 for (std::size_t menu_incr = (menu_mask + 1) / 2;
97 if (menu_num + menu_incr < contents.menus.size()
98 && (menu_num & (menu_incr * 2 - 1)) == 0)
101 " if (g0 ge " << 1 + menu_num + menu_incr
103 " jump menu " << 1 + menu_num + menu_incr
109 // Highlight the appropriate button.
110 " s8 = g1 & " << button_mask << ";\n"
112 " <vob file='" << menu.vob_temp->get_name() << "'/>\n";
114 for (unsigned button_num = 0;
115 button_num != menu.entries.size();
118 const dvd_contents::pgc_ref & target =
119 menu.entries[button_num];
121 file << " <button> ";
123 if (target.type == dvd_contents::menu_pgc)
125 unsigned target_button_num;
127 if (target.sub_index)
129 target_button_num = target.sub_index;
133 // Look for a button on the new menu that links
134 // back to this one. If there is one, set that to
135 // be the highlighted button; otherwise, use the
137 const std::vector<dvd_contents::pgc_ref> &
138 target_menu_entries =
139 contents.menus[target.index].entries;
140 dvd_contents::pgc_ref this_pgc(dvd_contents::menu_pgc,
142 target_button_num = target_menu_entries.size();
143 while (target_button_num != 0
144 && (target_menu_entries[--target_button_num]
147 target_button_num += 1;
152 + target_button_num * button_mult)
153 << "; jump menu " << 1 + target.index << ";";
157 assert(target.type == dvd_contents::title_pgc);
160 << 1 + menu_num + (1 + button_num) * button_mult
163 // FIXME: Here we should check target.sub_index and
164 // jump to a specific chapter if it is non-zero.
165 // However, we can't jump directly to chapters from
170 file << " </button>\n";
174 // Some DVD players don't seem to obey pause='inf' so make
186 // Generate a titleset for each title. This appears to make
187 // jumping to titles a whole lot simpler (but limits us to 99
189 for (unsigned title_num = 0;
190 title_num != contents.titles.size();
195 // Generate a dummy menu so that the menu button on the
196 // remote control will work.
198 " <pgc entry='root'>\n"
199 " <pre> jump vmgm menu; </pre>\n"
204 // Record calling location.
205 " <pre> g12 = g1; </pre>\n";
207 for (vob_list::const_iterator it = contents.titles[title_num].begin(),
208 end = contents.titles[title_num].end();
212 file << " <vob file='" << xml_escape(it->file) << "'";
213 if (!it->chapters.empty())
214 file << " chapters='" << xml_escape(it->chapters) << "'";
215 if (!it->pause.empty())
216 file << " pause='" << xml_escape(it->pause) << "'";
221 // If the menu location has not been changed during
222 // the title, set the location to be the following
223 // button in the menu. In any case, return to some
225 " <post> if (g1 eq g12) g1 = g1 + " << button_mult
226 << "; call menu; </post>\n"
238 const char * argv[] = {
240 "-o", output_dir.c_str(),
241 "-x", temp.get_name().c_str(),
245 Glib::spawn_sync(".",
246 Glib::ArrayHandle<std::string>(
247 argv, sizeof(argv)/sizeof(argv[0]),
248 Glib::OWNERSHIP_NONE),
249 Glib::SPAWN_SEARCH_PATH
250 | Glib::SPAWN_STDOUT_TO_DEV_NULL,
254 if (command_result != 0)
255 throw std::runtime_error("dvdauthor failed");