]> git.decadent.org.uk Git - videolink.git/blob - generate_dvd.cpp
Renamed package due to name clash.
[videolink.git] / generate_dvd.cpp
1 // Copyright 2005-6 Ben Hutchings <ben@decadent.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("videolink-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("videolink-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     // g2:     location that last jumped to a title
31     // g3:     target chapter number
32     //
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.
38     //
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;
42
43     file <<
44         "<dvdauthor>\n"
45         "  <vmgm>\n"
46         "    <menus>\n";
47             
48     for (unsigned menu_index = 0;
49          menu_index != contents.menus.size();
50          ++menu_index)
51     {
52         const dvd_contents::menu & menu = contents.menus[menu_index];
53
54         if (menu_index == 0)
55         {
56             // This is the first (title) menu, displayed when the
57             // disc is first played.
58             file <<
59                 "      <pgc entry='title' pause='inf'>\n"
60                 "        <pre>\n"
61                 // Initialise the current location if it is not set
62                 // (all general registers are initially 0).
63                 "          if (g1 eq 0)\n"
64                 "            g1 = " << 1 + button_mult << ";\n";
65         }
66         else
67         {
68             file <<
69                 "      <pgc pause='inf'>\n"
70                 "        <pre>\n";
71         }
72
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.
80
81         file << "          g0 = g1 &amp; " << menu_mask << ";\n";
82
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.
93             
94         for (std::size_t menu_incr = (menu_mask + 1) / 2;
95              menu_incr != 0;
96              menu_incr /= 2)
97         {
98             if (menu_index + menu_incr < contents.menus.size()
99                 && (menu_index & (menu_incr * 2 - 1)) == 0)
100             {
101                 file <<
102                     "          if (g0 ge " << 1 + menu_index + menu_incr
103                                            << ")\n"
104                     "            jump menu " << 1 + menu_index + menu_incr
105                                            << ";\n";
106             }
107         }
108
109         file <<
110             // Highlight the appropriate button.
111             "          s8 = g1 &amp; " << button_mask << ";\n"
112             "        </pre>\n"
113             "        <vob file='" << menu.vob_temp->get_name() << "'/>\n";
114
115         for (unsigned button_index = 0;
116              button_index != menu.entries.size();
117              ++button_index)
118         {
119             const dvd_contents::pgc_ref & target =
120                 menu.entries[button_index];
121
122             file << "        <button> ";
123
124             if (target.type == dvd_contents::menu_pgc)
125             {
126                 unsigned target_button_num;
127
128                 if (target.sub_index)
129                 {
130                     target_button_num = target.sub_index;
131                 }
132                 else
133                 {
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
137                     // first button.
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,
142                                                    menu_index);
143                     target_button_num = target_menu_entries.size();
144                     while (target_button_num != 0
145                            && (target_menu_entries[target_button_num - 1]
146                                != this_pgc))
147                         --target_button_num;
148                 }
149                          
150                 file <<
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 << "; ";
156             }
157             else
158             {
159                 assert(target.type == dvd_contents::title_pgc);
160
161                 file <<
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 << "; ";
170             }
171
172             file <<  "</button>\n";
173         }
174
175         file <<
176             // Some DVD players don't seem to obey pause='inf' so make
177             // them loop.
178             "        <post>\n"
179             "          jump cell 1;\n"
180             "        </post>\n"
181             "      </pgc>\n";
182     }
183
184     file <<
185         "    </menus>\n"
186         "  </vmgm>\n";
187
188     // Generate a titleset for each title.  This appears to make
189     // jumping to titles a whole lot simpler (but limits us to 99
190     // titles).
191     for (unsigned title_index = 0;
192          title_index != contents.titles.size();
193          ++title_index)
194     {
195         file <<
196             "  <titleset>\n"
197             // Generate a dummy menu so that the menu button on the
198             // remote control will work.
199             "    <menus>\n"
200             "      <pgc entry='root'>\n"
201             "        <pre> jump vmgm menu; </pre>\n"
202             "      </pgc>\n"
203             "    </menus>\n"
204             "    <titles>\n"
205             "      <pgc>\n"
206             "        <pre>\n"
207             // Record calling location.
208             "          g2 = g1;\n";
209
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();
215              it != end;
216              ++it)
217         {
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.
223             ++n_chapters;
224             std::size_t pos = 0;
225             while ((pos = it->chapters.find(',', pos)) != std::string::npos)
226             {
227                 ++n_chapters;
228                 ++pos;
229             }
230         }
231
232         // Generate jump "table" for chapters.
233         for (unsigned chapter_num = 1;
234              chapter_num <= n_chapters;
235              ++chapter_num)
236             file <<
237                 "          if (g3 == " << chapter_num << ")\n"
238                 "            jump chapter " << chapter_num << ";\n";
239
240         file <<
241             "        </pre>\n";
242
243         for (vob_list::const_iterator
244                  it = contents.titles[title_index].begin(),
245                  end = contents.titles[title_index].end();
246              it != end;
247              ++it)
248         {
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) << "'";
254             file << "/>\n";
255         }
256
257         file <<
258             "        <post>\n"
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.
262             "          if (g1 eq g2)\n"
263             "            g1 = g1 + " << button_mult << ";\n"
264             // In any case, return to some menu.
265             "          call menu;\n"
266             "        </post>\n"
267             "      </pgc>\n"
268             "    </titles>\n"
269             "  </titleset>\n";
270     }
271
272     file <<
273         "</dvdauthor>\n";
274
275     file.close();
276
277     {
278         const char * argv[] = {
279             "dvdauthor",
280             "-o", output_dir.c_str(),
281             "-x", temp.get_name().c_str(),
282             0
283         };
284         int command_result;
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,
291                          SigC::Slot0<void>(),
292                          0, 0,
293                          &command_result);
294         if (command_result != 0)
295             throw std::runtime_error("dvdauthor failed");
296     }
297 }