]> git.decadent.org.uk Git - videolink.git/blob - generate_dvd.cpp
Changed to ensure menus are displayed indefinitely.
[videolink.git] / generate_dvd.cpp
1 // Copyright 2005-6 Ben Hutchings <ben@decadentplace.org.uk>.
2 // See the file "COPYING" for licence details.
3
4 #include <fstream>
5 #include <stdexcept>
6
7 #include <glibmm/spawn.h>
8
9 #include "dvd.hpp"
10 #include "generate_dvd.hpp"
11 #include "xml_utils.hpp"
12
13 dvd_contents::menu::menu()
14         : vob_temp(new temp_file("webdvd-vob-"))
15 {
16     vob_temp->close();
17 }
18
19 void generate_dvd(const dvd_contents & contents,
20                   const std::string & output_dir)
21 {
22     temp_file temp("webdvd-dvdauthor-");
23     temp.close();
24     std::ofstream file(temp.get_name().c_str());
25
26     // We generate code that uses registers in the following way:
27     //
28     // g0:     scratch
29     // g1:     current location
30     // g12:    location that last jumped to a video
31     //
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.
37     //
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;
41
42     file <<
43         "<dvdauthor>\n"
44         "  <vmgm>\n"
45         "    <menus>\n";
46             
47     for (unsigned menu_num = 0;
48          menu_num != contents.menus.size();
49          ++menu_num)
50     {
51         const dvd_contents::menu & menu = contents.menus[menu_num];
52
53         if (menu_num == 0)
54         {
55             // This is the first (title) menu, displayed when the
56             // disc is first played.
57             file <<
58                 "      <pgc entry='title' pause='inf'>\n"
59                 "        <pre>\n"
60                 // Initialise the current location if it is not set
61                 // (all general registers are initially 0).
62                 "          if (g1 eq 0)\n"
63                 "            g1 = " << 1 + button_mult << ";\n";
64         }
65         else
66         {
67             file <<
68                 "      <pgc pause='inf'>\n"
69                 "        <pre>\n";
70         }
71
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.
79
80         file << "          g0 = g1 &amp; " << menu_mask << ";\n";
81
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.
92             
93         for (std::size_t menu_incr = (menu_mask + 1) / 2;
94              menu_incr != 0;
95              menu_incr /= 2)
96         {
97             if (menu_num + menu_incr < contents.menus.size()
98                 && (menu_num & (menu_incr * 2 - 1)) == 0)
99             {
100                 file <<
101                     "          if (g0 ge " << 1 + menu_num + menu_incr
102                                            << ")\n"
103                     "            jump menu " << 1 + menu_num + menu_incr
104                                            << ";\n";
105             }
106         }
107
108         file <<
109             // Highlight the appropriate button.
110             "          s8 = g1 &amp; " << button_mask << ";\n"
111             "        </pre>\n"
112             "        <vob file='" << menu.vob_temp->get_name() << "'/>\n";
113
114         for (unsigned button_num = 0;
115              button_num != menu.entries.size();
116              ++button_num)
117         {
118             const dvd_contents::pgc_ref & target =
119                 menu.entries[button_num];
120
121             file << "        <button> ";
122
123             if (target.type == dvd_contents::menu_pgc)
124             {
125                 unsigned target_button_num;
126
127                 if (target.sub_index)
128                 {
129                     target_button_num = target.sub_index;
130                 }
131                 else
132                 {
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
136                     // first button.
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,
141                                                    menu_num);
142                     target_button_num = target_menu_entries.size();
143                     while (target_button_num != 0
144                            && (target_menu_entries[--target_button_num]
145                                != this_pgc))
146                         ;
147                     target_button_num += 1;
148                 }
149                          
150                 file << "g1 = "
151                      << (1 + target.index
152                          + target_button_num * button_mult)
153                      << "; jump menu " << 1 + target.index << ";";
154             }
155             else
156             {
157                 assert(target.type == dvd_contents::title_pgc);
158
159                 file << "g1 = "
160                      << 1 + menu_num + (1 + button_num) * button_mult
161                      << "; jump title "
162                      << 1 + target.index;
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
166                 // the VMGM.
167                 file << ";";
168             }
169
170             file <<  " </button>\n";
171         }
172
173         file <<
174             // Some DVD players don't seem to obey pause='inf' so make
175             // them loop.
176             "        <post>\n"
177             "          jump cell 1;\n"
178             "        </post>\n"
179             "      </pgc>\n";
180     }
181
182     file <<
183         "    </menus>\n"
184         "  </vmgm>\n";
185
186     // Generate a titleset for each title.  This appears to make
187     // jumping to titles a whole lot simpler (but limits us to 99
188     // titles).
189     for (unsigned title_num = 0;
190          title_num != contents.titles.size();
191          ++title_num)
192     {
193         file <<
194             "  <titleset>\n"
195             // Generate a dummy menu so that the menu button on the
196             // remote control will work.
197             "    <menus>\n"
198             "      <pgc entry='root'>\n"
199             "        <pre> jump vmgm menu; </pre>\n"
200             "      </pgc>\n"
201             "    </menus>\n"
202             "    <titles>\n"
203             "      <pgc>\n"
204             // Record calling location.
205             "        <pre> g12 = g1; </pre>\n";
206
207         for (vob_list::const_iterator it = contents.titles[title_num].begin(),
208                  end = contents.titles[title_num].end();
209              it != end;
210              ++it)
211         {
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) << "'";
217             file << "/>\n";
218         }
219
220         file <<
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
224             // menu.
225             "        <post> if (g1 eq g12) g1 = g1 + " << button_mult
226              << "; call menu; </post>\n"
227             "      </pgc>\n"
228             "    </titles>\n"
229             "  </titleset>\n";
230     }
231
232     file <<
233         "</dvdauthor>\n";
234
235     file.close();
236
237     {
238         const char * argv[] = {
239             "dvdauthor",
240             "-o", output_dir.c_str(),
241             "-x", temp.get_name().c_str(),
242             0
243         };
244         int command_result;
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,
251                          SigC::Slot0<void>(),
252                          0, 0,
253                          &command_result);
254         if (command_result != 0)
255             throw std::runtime_error("dvdauthor failed");
256     }
257 }