]> git.decadent.org.uk Git - videolink.git/blob - generate_dvd.cpp
6144ef5ba0b78e06a2d5568154f03ca1eeaa0e7d
[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
12 dvd_contents::menu::menu()
13         : vob_temp(new temp_file("webdvd-vob-"))
14 {
15     vob_temp->close();
16 }
17
18 void generate_dvd(const dvd_contents & contents,
19                   const std::string & output_dir)
20 {
21     temp_file temp("webdvd-dvdauthor-");
22     temp.close();
23     std::ofstream file(temp.get_name().c_str());
24
25     // We generate code that uses registers in the following way:
26     //
27     // g0:     scratch
28     // g1:     current location
29     // g12:    location that last jumped to a video
30     //
31     // All locations are divided into two bitfields: the least
32     // significant 10 bits are a page/menu number and the most
33     // significant 6 bits are a link/button number, and numbering
34     // starts at 1, not 0.  This is done for compatibility with
35     // the encoding of the s8 (button) register.
36     //
37     static const int button_mult = dvd::reg_s8_button_mult;
38     static const int menu_mask = button_mult - 1;
39     static const int button_mask = (1 << dvd::reg_bits) - button_mult;
40
41     file <<
42         "<dvdauthor>\n"
43         "  <vmgm>\n"
44         "    <menus>\n";
45             
46     for (unsigned menu_num = 0;
47          menu_num != contents.menus.size();
48          ++menu_num)
49     {
50         const dvd_contents::menu & menu = contents.menus[menu_num];
51
52         if (menu_num == 0)
53         {
54             // This is the first (title) menu, displayed when the
55             // disc is first played.
56             file <<
57                 "      <pgc entry='title'>\n"
58                 "        <pre>\n"
59                 // Initialise the current location if it is not set
60                 // (all general registers are initially 0).
61                 "          if (g1 eq 0)\n"
62                 "            g1 = " << 1 + button_mult << ";\n";
63         }
64         else
65         {
66             file <<
67                 "      <pgc>\n"
68                 "        <pre>\n";
69         }
70
71         // When a title finishes or the user presses the menu
72         // button, this always jumps to the titleset's root menu.
73         // We want to return the user to the last menu they used.
74         // So we arrange for each titleset's root menu to return
75         // to the vmgm title menu and then dispatch from there to
76         // whatever the correct menu is.  We determine the correct
77         // menu by looking at the menu part of g1.
78
79         file << "          g0 = g1 &amp; " << menu_mask << ";\n";
80
81         // There is a limit of 128 VM instructions in each PGC.
82         // Therefore in each menu's <pre> section we generate
83         // jumps to menus with numbers greater by 512, 256, 128,
84         // ..., 1 where (a) such a menu exists, (b) this menu
85         // number is divisible by twice that increment and (c) the
86         // correct menu is that or a later menu.  Thus each menu
87         // has at most 10 such conditional jumps and is reachable
88         // by at most 10 jumps from the title menu.  This chain of
89         // jumps might take too long on some players; this has yet
90         // to be investigated.
91             
92         for (std::size_t menu_incr = (menu_mask + 1) / 2;
93              menu_incr != 0;
94              menu_incr /= 2)
95         {
96             if (menu_num + menu_incr < contents.menus.size()
97                 && (menu_num & (menu_incr * 2 - 1)) == 0)
98             {
99                 file <<
100                     "          if (g0 ge " << 1 + menu_num + menu_incr
101                                            << ")\n"
102                     "            jump menu " << 1 + menu_num + menu_incr
103                                            << ";\n";
104             }
105         }
106
107         file <<
108             // Highlight the appropriate button.
109             "          s8 = g1 &amp; " << button_mask << ";\n"
110             "        </pre>\n"
111             "        <vob file='" << menu.vob_temp->get_name() << "'/>\n";
112
113         for (unsigned button_num = 0;
114              button_num != menu.entries.size();
115              ++button_num)
116         {
117             const dvd_contents::pgc_ref & target =
118                 menu.entries[button_num];
119
120             file << "        <button> ";
121
122             if (target.type == dvd_contents::menu_pgc)
123             {
124                 unsigned target_button_num;
125
126                 if (target.sub_index)
127                 {
128                     target_button_num = target.sub_index;
129                 }
130                 else
131                 {
132                     // Look for a button on the new menu that links
133                     // back to this one.  If there is one, set that to
134                     // be the highlighted button; otherwise, use the
135                     // first button.
136                     const std::vector<dvd_contents::pgc_ref> &
137                         target_menu_entries =
138                         contents.menus[target.index].entries;
139                     dvd_contents::pgc_ref this_pgc(dvd_contents::menu_pgc,
140                                                    menu_num);
141                     target_button_num = target_menu_entries.size();
142                     while (target_button_num != 0
143                            && (target_menu_entries[--target_button_num]
144                                != this_pgc))
145                         ;
146                     target_button_num += 1;
147                 }
148                          
149                 file << "g1 = "
150                      << (1 + target.index
151                          + target_button_num * button_mult)
152                      << "; jump menu " << 1 + target.index << ";";
153             }
154             else
155             {
156                 assert(target.type == dvd_contents::title_pgc);
157
158                 file << "g1 = "
159                      << 1 + menu_num + (1 + button_num) * button_mult
160                      << "; jump title "
161                      << 1 + target.index;
162                 if (target.sub_index)
163                     file << " chapter " << target.sub_index;
164                 file << ";";
165             }
166
167             file <<  " </button>\n";
168         }
169
170         file << "      </pgc>\n";
171     }
172
173     file <<
174         "    </menus>\n"
175         "  </vmgm>\n";
176
177     // Generate a titleset for each title.  This appears to make
178     // jumping to titles a whole lot simpler (but limits us to 99
179     // titles).
180     for (unsigned title_num = 0;
181          title_num != contents.titles.size();
182          ++title_num)
183     {
184         file <<
185             "  <titleset>\n"
186             // Generate a dummy menu so that the menu button on the
187             // remote control will work.
188             "    <menus>\n"
189             "      <pgc entry='root'>\n"
190             "        <pre> jump vmgm menu; </pre>\n"
191             "      </pgc>\n"
192             "    </menus>\n"
193             "    <titles>\n"
194             "      <pgc>\n"
195             // Record calling location.
196             "        <pre> g12 = g1; </pre>\n"
197              << contents.titles[title_num].vob_list <<
198             // If the menu location has not been changed during
199             // the title, set the location to be the following
200             // button in the menu.  In any case, return to some
201             // menu.
202             "        <post> if (g1 eq g12) g1 = g1 + " << button_mult
203              << "; call menu; </post>\n"
204             "      </pgc>\n"
205             "    </titles>\n"
206             "  </titleset>\n";
207     }
208
209     file <<
210         "</dvdauthor>\n";
211
212     file.close();
213
214     {
215         const char * argv[] = {
216             "dvdauthor",
217             "-o", output_dir.c_str(),
218             "-x", temp.get_name().c_str(),
219             0
220         };
221         int command_result;
222         Glib::spawn_sync(".",
223                          Glib::ArrayHandle<std::string>(
224                              argv, sizeof(argv)/sizeof(argv[0]),
225                              Glib::OWNERSHIP_NONE),
226                          Glib::SPAWN_SEARCH_PATH
227                          | Glib::SPAWN_STDOUT_TO_DEV_NULL,
228                          SigC::Slot0<void>(),
229                          0, 0,
230                          &command_result);
231         if (command_result != 0)
232             throw std::runtime_error("dvdauthor failed");
233     }
234 }