return result;
}
+ struct dvd_contents
+ {
+ enum pgc_type { menu_pgc, title_pgc };
+ typedef std::pair<pgc_type, int> pgc_ref;
+
+ struct menu
+ {
+ menu()
+ : vob_temp(new temp_file("webdvd-vob-"))
+ {
+ vob_temp->close();
+ }
+
+ boost::shared_ptr<temp_file> vob_temp;
+ std::vector<pgc_ref> entries;
+ };
+
+ struct title
+ {
+ explicit title(const std::string & vob_list)
+ : vob_list(vob_list)
+ {}
+
+ std::string vob_list;
+ };
+
+ std::vector<menu> menus;
+ std::vector<title> titles;
+ };
+
class webdvd_window : public Gtk::Window
{
public:
const std::string & output_dir);
private:
- void add_page(const std::string & uri);
- void add_video(const std::string & uri);
+ dvd_contents::pgc_ref add_menu(const std::string & uri);
+ dvd_contents::pgc_ref add_title(const std::string & uri);
void load_next_page();
void on_net_state_change(const char * uri, gint flags, guint status);
bool process_page();
nsIDOMWindow * dom_window);
void generate_dvd();
- enum resource_type { page_resource, video_resource };
- typedef std::pair<resource_type, int> resource_entry;
video::frame_params frame_params_;
std::string output_dir_;
browser_widget browser_widget_;
nsCOMPtr<nsIStyleSheet> stylesheet_;
+
+ dvd_contents contents_;
+ typedef std::map<std::string, dvd_contents::pgc_ref> resource_map_type;
+ resource_map_type resource_map_;
+
std::queue<std::string> page_queue_;
- std::map<std::string, resource_entry> resource_map_;
- std::vector<std::vector<std::string> > page_links_;
- std::vector<std::string> video_paths_;
bool pending_window_update_;
int pending_req_count_;
bool have_tweaked_page_;
std::auto_ptr<temp_file> background_temp_;
struct page_state;
std::auto_ptr<page_state> page_state_;
- std::vector<boost::shared_ptr<temp_file> > page_temp_files_;
};
webdvd_window::webdvd_window(
browser_widget_.signal_net_state().connect(
SigC::slot(*this, &webdvd_window::on_net_state_change));
- add_page(main_page_uri);
+ add_menu(main_page_uri);
load_next_page();
}
- void webdvd_window::add_page(const std::string & uri)
+ dvd_contents::pgc_ref webdvd_window::add_menu(const std::string & uri)
{
- if (resource_map_.insert(
- std::make_pair(uri, resource_entry(page_resource, 0)))
- .second)
+ dvd_contents::pgc_ref next_menu(dvd_contents::menu_pgc,
+ contents_.menus.size());
+ std::pair<resource_map_type::iterator, bool> insert_result(
+ resource_map_.insert(std::make_pair(uri, next_menu)));
+
+ if (!insert_result.second)
+ {
+ return insert_result.first->second;
+ }
+ else
{
page_queue_.push(uri);
+ contents_.menus.resize(contents_.menus.size() + 1);
+ return next_menu;
}
}
- void webdvd_window::add_video(const std::string & uri)
+ dvd_contents::pgc_ref webdvd_window::add_title(const std::string & uri)
{
- if (resource_map_.insert(
- std::make_pair(uri, resource_entry(video_resource,
- video_paths_.size() + 1)))
- .second)
+ dvd_contents::pgc_ref next_title(dvd_contents::title_pgc,
+ contents_.titles.size());
+ std::pair<resource_map_type::iterator, bool> insert_result(
+ resource_map_.insert(std::make_pair(uri, next_title)));
+
+ if (!insert_result.second)
+ {
+ return insert_result.first->second;
+ }
+ else
{
Glib::ustring hostname;
std::string filename(Glib::filename_from_uri(uri, hostname));
// FIXME: Should check the hostname
- if (!Glib::file_test(filename, Glib::FILE_TEST_IS_REGULAR))
- throw std::runtime_error(
- filename + " is missing or not a regular file");
- video_paths_.push_back(filename);
+
+ std::string vob_list;
+
+ // Store a reference to a linked VOB file, or the contents
+ // of a linked VOB list file.
+ if (filename.compare(filename.size() - 4, 4, ".vob") == 0)
+ {
+ if (!Glib::file_test(filename, Glib::FILE_TEST_IS_REGULAR))
+ throw std::runtime_error(
+ filename + " is missing or not a regular file");
+ vob_list
+ .append("<vob file='")
+ // FIXME: Should XML-escape the path
+ .append(filename)
+ .append("'/>\n");
+ }
+ else
+ {
+ assert(filename.compare(filename.size() - 8, 8, ".voblist")
+ == 0);
+ // TODO: Validate the file contents
+ vob_list.assign(Glib::file_get_contents(filename));
+ }
+
+ contents_.titles.push_back(dvd_contents::title(vob_list));
+ return next_title;
}
}
const std::string & uri = page_queue_.front();
std::cout << "loading " << uri << std::endl;
- std::size_t page_count = page_links_.size();
- resource_map_[uri].second = ++page_count;
- page_links_.resize(page_count);
-
browser_widget_.load_uri(uri);
}
// All further work should only be done if we're not in preview mode.
if (!output_dir_.empty())
{
- // If we haven't already started work on this page, save a
+ // If we haven't already started work on this menu, save a
// screenshot of its normal appearance.
if (!page_state_.get())
save_screenshot();
0, 0, frame_params_.width, frame_params_.height
};
+ int menu_num = resource_map_[page_queue_.front()].second;
+
for (/* no initialisation */;
state->links_it != state->links_end;
++state->links_it)
{
if (state->link_num == dvd::menu_buttons_max)
std::cerr << "No more than " << dvd::menu_buttons_max
- << " buttons can be placed on a page\n";
+ << " buttons can be placed on a menu\n";
std::cerr << "Ignoring link to " << uri_string << "\n";
continue;
}
+ state->spumux_file <<
+ " <button x0='" << state->link_rect.left << "'"
+ " y0='" << state->link_rect.top << "'"
+ " x1='" << state->link_rect.right - 1 << "'"
+ " y1='" << state->link_rect.bottom - 1 << "'/>\n";
+
// Check whether this is a link to a video or a page then
- // add it to the known resources if not already seen.
+ // add it to the known resources if not already seen; then
+ // add it to the menu entries.
nsCString path;
check(uri->GetPath(path));
+ dvd_contents::pgc_ref dest_pgc;
// FIXME: This is a bit of a hack. Perhaps we could decide
// later based on the MIME type determined by Mozilla?
if ((path.Length() > 4
<< " scheme\n";
continue;
}
- add_video(uri_sans_fragment);
+ dest_pgc = add_title(uri_sans_fragment);
}
else
{
- add_page(uri_sans_fragment);
+ dest_pgc = add_menu(uri_sans_fragment);
}
+ contents_.menus[menu_num].entries.push_back(dest_pgc);
nsCOMPtr<nsIContent> content(do_QueryInterface(node));
assert(content);
state->link_rect.top,
state->link_rect.right - state->link_rect.left,
state->link_rect.bottom - state->link_rect.top);
-
- state->spumux_file <<
- " <button x0='" << state->link_rect.left << "'"
- " y0='" << state->link_rect.top << "'"
- " x1='" << state->link_rect.right - 1 << "'"
- " y1='" << state->link_rect.bottom - 1 << "'/>\n";
-
- // Add to the page's links, ignoring any fragment (for now).
- page_links_.back().push_back(uri_sans_fragment);
}
quantise_rgba_pixbuf(state->diff_pixbuf, dvd::button_n_colours);
// TODO: if (!state->spumux_file) throw ...
{
- boost::shared_ptr<temp_file> vob_temp(
- new temp_file("webdvd-vob-"));
std::ostringstream command_stream;
command_stream << "pngtopnm "
<< background_temp_->get_name()
" | mplex -v0 -f8 -o/dev/stdout /dev/stdin"
" | spumux -v0 -mdvd ")
<< state->spumux_temp.get_name()
- << " > " << vob_temp->get_name();
+ << " > "
+ << contents_.menus[menu_num].vob_temp->get_name();
std::string command(command_stream.str());
const char * argv[] = {
"/bin/sh", "-c", command.c_str(), 0
&command_result);
if (command_result != 0)
throw std::runtime_error("spumux pipeline failed");
-
- page_temp_files_.push_back(vob_temp);
}
}
- void generate_page_dispatch(std::ostream &, int indent,
- int first_page, int last_page);
+ void generate_menu_dispatch(std::ostream &, int indent,
+ int first_menu, int last_menu);
void webdvd_window::generate_dvd()
{
// We generate code that uses registers in the following way:
//
- // g0: link destination (when jumping to menu 1), then scratch
+ // g0: button destination (when jumping to menu 1), then scratch
// g1: current location
// g2-g11: location history (g2 = most recent)
// g12: location that last linked to a video
//
// All locations are divided into two bitfields: the least
// significant 10 bits are a page/menu number and the most
- // significant 6 bits are a link/button number. This is
- // chosen for compatibility with the encoding of the s8
- // (button) register.
+ // significant 6 bits are a link/button number, and numbering
+ // starts at 1, not 0. This is done for compatibility with
+ // the encoding of the s8 (button) register.
//
- static const int link_mult = dvd::reg_s8_button_mult;
- static const int page_mask = link_mult - 1;
- static const int link_mask = (1 << dvd::reg_bits) - link_mult;
+ static const int button_mult = dvd::reg_s8_button_mult;
+ static const int menu_mask = button_mult - 1;
+ static const int button_mask = (1 << dvd::reg_bits) - button_mult;
+ static const int location_bias = button_mult + 1;
file <<
"<dvdauthor>\n"
" <vmgm>\n"
" <menus>\n";
- for (std::size_t page_num = 1;
- page_num <= page_links_.size();
- ++page_num)
+ for (std::size_t menu_num = 0;
+ menu_num != contents_.menus.size();
+ ++menu_num)
{
- std::vector<std::string> & page_links =
- page_links_[page_num - 1];
+ dvd_contents::menu & menu = contents_.menus[menu_num];
- if (page_num == 1)
+ if (menu_num == 0)
{
- // This is the first page (root menu) which needs to
- // include initialisation and dispatch code.
+ // This is the first (title) menu, which needs to include
+ // initialisation and dispatch code.
file <<
" <pgc entry='title'>\n"
// Has the location been set yet?
" if (g1 eq 0)\n"
" {\n"
- // Initialise the current location to first link on
- // this page.
- " g1 = " << 1 * link_mult + 1 << ";\n"
+ // Initialise the current location to first button on
+ // this menu.
+ " g1 = " << location_bias << ";\n"
" }\n"
" else\n"
" {\n"
" {\n"
// First update the history.
// Does link go to the last page in the history?
- " if (((g0 ^ g2) & " << page_mask
+ " if (((g0 ^ g2) & " << menu_mask
<< ") == 0)\n"
// It does; we treat this as going back and pop the old
// location off the history stack into the current
" }\n"
" }\n"
// Find the target page number.
- " g0 = g1 & " << page_mask << ";\n";
+ " g0 = g1 & " << menu_mask << ";\n";
// There seems to be no way to perform a computed jump,
// so we generate all possible jumps and a binary search
// to select the correct one.
- generate_page_dispatch(file, 12, 1, page_links_.size());
+ generate_menu_dispatch(file, 12,
+ 0, contents_.menus.size() - 1);
file <<
" }\n";
}
- else // page_num != 1
+ else // menu_num != 0
{
file <<
" <pgc>\n"
file <<
// Clear link indicator and highlight the
// appropriate link/button.
- " g0 = 0; s8 = g1 & " << link_mask << ";\n"
+ " g0 = 0; s8 = g1 & " << button_mask << ";\n"
" </pre>\n"
" <vob file='"
- << page_temp_files_[page_num - 1]->get_name() << "'/>\n";
+ << menu.vob_temp->get_name() << "'/>\n";
- for (std::size_t link_num = 1;
- link_num <= page_links.size();
- ++link_num)
+ for (std::size_t button_num = 0;
+ button_num != menu.entries.size();
+ ++button_num)
{
- file <<
- " <button>"
+ file << " <button> "
// Update current location.
- " g1 = " << link_num * link_mult + page_num << ";";
+ " g1 = "
+ << location_bias + button_num * button_mult + menu_num
+ << ";";
// Jump to appropriate resource.
- const resource_entry & resource_loc =
- resource_map_[page_links[link_num - 1]];
- if (resource_loc.first == page_resource)
- file <<
- " g0 = " << 1 * link_mult + resource_loc.second << ";"
- " jump menu 1;";
- else if (resource_loc.first == video_resource)
- file << " jump title " << resource_loc.second << ";";
+ if (menu.entries[button_num].first == dvd_contents::menu_pgc)
+ {
+ file << " g0 = "
+ << location_bias + menu.entries[button_num].second
+ << "; jump menu 1;";
+ }
+ else
+ {
+ assert(menu.entries[button_num].first
+ == dvd_contents::title_pgc);
+ file << " jump title "
+ << 1 + menu.entries[button_num].second << ";";
+ }
file << " </button>\n";
}
" </menus>\n"
" </vmgm>\n";
- // Generate a titleset for each video. This appears to make
- // jumping to titles a whole lot simpler.
- for (std::size_t video_num = 1;
- video_num <= video_paths_.size();
- ++video_num)
+ // Generate a titleset for each title. This appears to make
+ // jumping to titles a whole lot simpler (but limits us to 99
+ // titles).
+ for (std::size_t title_num = 0;
+ title_num != contents_.titles.size();
+ ++title_num)
{
file <<
" <titleset>\n"
" <titles>\n"
" <pgc>\n"
// Record calling page/menu.
- " <pre> g12 = g1; </pre>\n";
-
- // Write a reference to a linked VOB file, or the contents
- // of a linked VOB list file.
- const std::string & video_path = video_paths_[video_num - 1];
- if (video_path.compare(video_path.size() - 4, 4, ".vob") == 0)
- {
- // FIXME: Should XML-escape the path
- file << " <vob file='" << video_path << "'/>\n";
- }
- else
- {
- assert(video_path.compare(video_path.size() - 8, 8,
- ".voblist") == 0);
- // TODO: Validate the file contents;
- file << Glib::file_get_contents(video_path);
- }
-
- file <<
+ " <pre> g12 = g1; </pre>\n"
+ << contents_.titles[title_num].vob_list <<
// If page/menu location has not been changed during the
// video, change the location to be the following
// link/button when returning to it. In any case,
// return to a page/menu.
- " <post> if (g1 eq g12) g1 = g1 + " << link_mult
+ " <post> if (g1 eq g12) g1 = g1 + " << button_mult
<< "; call menu; </post>\n"
" </pgc>\n"
" </titles>\n"
}
}
- void generate_page_dispatch(std::ostream & file, int indent,
- int first_page, int last_page)
+ void generate_menu_dispatch(std::ostream & file, int indent,
+ int first_menu, int last_menu)
{
- if (first_page == 1 && last_page == 1)
+ if (first_menu == last_menu)
{
- // The dispatch code is *on* page 1 so we must not dispatch to
- // page 1 since that would cause an infinite loop. This case
- // should be unreachable if there is more than one page due
- // to the following case.
- }
- else if (first_page == 1 && last_page == 2)
- {
- // dvdauthor doesn't allow empty blocks or null statements so
- // when selecting between pages 1 and 2 we don't use an "else"
- // part. We must use braces so that a following "else" will
- // match the right "if".
- file << std::setw(indent) << "" << "{\n"
- << std::setw(indent) << "" << "if (g0 eq 2)\n"
- << std::setw(indent + 2) << "" << "jump menu 2;\n"
- << std::setw(indent) << "" << "}\n";
- }
- else if (first_page == last_page)
- {
- file << std::setw(indent) << ""
- << "jump menu " << first_page << ";\n";
+ if (first_menu == 0)
+ {
+ // This dispatch code is generated *on* the first menu
+ // so don't create an infinite loop.
+ }
+ else
+ {
+ file << std::setw(indent) << ""
+ << "jump menu " << 1 + first_menu << ";\n";
+ }
}
- else
+ else // first_menu != last_menu
{
- int middle = (first_page + last_page) / 2;
- file << std::setw(indent) << "" << "if (g0 le " << middle << ")\n";
- generate_page_dispatch(file, indent + 2, first_page, middle);
- file << std::setw(indent) << "" << "else\n";
- generate_page_dispatch(file, indent + 2, middle + 1, last_page);
+ if (first_menu == 0 && last_menu == 1)
+ {
+ // dvdauthor doesn't allow empty blocks or null
+ // statements so when selecting between the first 2
+ // menus we don't use an "else" part. We must use
+ // braces so that a following "else" will match the
+ // right "if".
+ file << std::setw(indent) << "" << "{\n"
+ << std::setw(indent) << "" << "if (g0 eq 2)\n"
+ << std::setw(indent + 2) << "" << "jump menu 2;\n"
+ << std::setw(indent) << "" << "}\n";
+ }
+ else
+ {
+ int middle = (first_menu + last_menu) / 2;
+ file << std::setw(indent) << "" << "if (g0 le " << 1 + middle
+ << ")\n";
+ generate_menu_dispatch(file, indent + 2,
+ first_menu, middle);
+ file << std::setw(indent) << "" << "else\n";
+ generate_menu_dispatch(file, indent + 2,
+ middle + 1, last_menu);
+ }
}
}