X-Git-Url: https://git.decadent.org.uk/gitweb/?p=videolink.git;a=blobdiff_plain;f=generate_dvd.cpp;h=371894d41edc23abd7ac34abee8337fd52b64711;hp=ff1b63574eaf0f01bb7fae9bb78e3b8b061fcd9a;hb=HEAD;hpb=f5d9069647f70b7aab8e656f59cf42176c419461 diff --git a/generate_dvd.cpp b/generate_dvd.cpp index ff1b635..371894d 100644 --- a/generate_dvd.cpp +++ b/generate_dvd.cpp @@ -1,12 +1,22 @@ -// Copyright 2005-6 Ben Hutchings . +// Copyright 2005-8 Ben Hutchings . // See the file "COPYING" for licence details. +#include +#include +#include #include +#include #include +#include #include #include +#include +#include +#include + #include +#include #include #include "dvd.hpp" @@ -55,25 +65,77 @@ namespace + (end_y - start_y) * (end_y - start_y); return vertical_distance / distance_squared; } -} -dvd_generator::menu::menu() - : vob_temp(new temp_file("videolink-vob-")) -{ - vob_temp->close(); + std::string temp_file_name(const temp_dir & dir, + std::string base_name, + unsigned index=0) + { + if (index != 0) + { + std::size_t index_pos = base_name.find("%3d"); + assert(index_pos != std::string::npos); + base_name[index_pos] = '0' + index / 100; + base_name[index_pos + 1] = '0' + (index / 10) % 10; + base_name[index_pos + 2] = '0' + index % 10; + } + + return Glib::build_filename(dir.get_name(), base_name); + } + + // We would like to use just a single frame for the menu but this + // seems not to be legal or compatible. The minimum length of a + // cell is 0.4 seconds but I've seen a static menu using 12 frames + // on a commercial "PAL" disc so let's use 12 frames regardless. + unsigned menu_duration_frames(const video::frame_params & params) + { + return 12; + } + double menu_duration_seconds(const video::frame_params & params) + { + return double(menu_duration_frames(params)) + / double(params.rate_numer) + * double(params.rate_denom); + } + + void throw_length_error(const char * limit_type, std::size_t limit) + { + std::ostringstream oss; + oss << "exceeded DVD limit: " << limit_type << " > " << limit; + throw std::length_error(oss.str()); + } + + // dvdauthor uses some menu numbers to represent entry points - + // distinct from the actual numbers of the menus assigned as those + // entry points - resulting in a practical limit of 119 per + // domain. This seems to be an oddity of the parser that could be + // fixed, but for now we'll have to work with it. + const unsigned dvdauthor_anonymous_menus_max = dvd::domain_pgcs_max - 8; + + // The current navigation code packs menu and button number into a + // single register, so the number of menus is limited to + // dvd::reg_s8_button_mult - 1 == 1023. However temp_file_name() + // is limited to 999 numbered files and it seems pointless to + // change it to get another 24. + // If people really need more we could use separate menu and + // button number registers, possibly allowing up to 11900 menus + // (the size of the indirect jump tables might become a problem + // though). + const unsigned menus_max = 999; } +dvd_generator::dvd_generator(const video::frame_params & frame_params, + mpeg_encoder encoder) + : temp_dir_("videolink-"), + frame_params_(frame_params), + encoder_(encoder) +{} + dvd_generator::pgc_ref dvd_generator::add_menu() { pgc_ref next_menu(menu_pgc, menus_.size()); - // Check against maximum number of menus. It appears that no more - // than 128 menus are reachable through LinkPGCN instructions, and - // dvdauthor uses some menu numbers for special purposes, resulting - // in a practical limit of 119 per domain. We can work around this - // later by spreading some menus across titlesets. - if (next_menu.index == 119) - throw std::runtime_error("No more than 119 menus can be used"); + if (next_menu.index == menus_max) + throw_length_error("number of menus", menus_max); menus_.resize(next_menu.index + 1); return next_menu; @@ -86,6 +148,10 @@ void dvd_generator::add_menu_entry(unsigned index, assert(index < menus_.size()); assert(target.type == menu_pgc && target.index < menus_.size() || target.type == title_pgc && target.index < titles_.size()); + + if (menus_[index].entries.size() == dvd::menu_buttons_max) + throw_length_error("number of buttons", dvd::menu_buttons_max); + menu_entry new_entry = { area, target }; menus_[index].entries.push_back(new_entry); } @@ -98,25 +164,25 @@ void dvd_generator::generate_menu_vob(unsigned index, assert(index < menus_.size()); const menu & this_menu = menus_[index]; - temp_file background_temp("videolink-back-"); - background_temp.close(); - std::cout << "saving " << background_temp.get_name() << std::endl; - background->save(background_temp.get_name(), "png"); + std::string background_name( + temp_file_name(temp_dir_, "menu-%3d-back.png", 1 + index)); + std::cout << "INFO: Saving " << background_name << std::endl; + background->save(background_name, "png"); - temp_file highlights_temp("videolink-links-"); - highlights_temp.close(); - std::cout << "saving " << highlights_temp.get_name() << std::endl; - highlights->save(highlights_temp.get_name(), "png"); + std::string highlights_name( + temp_file_name(temp_dir_, "menu-%3d-links.png", 1 + index)); + std::cout << "INFO: Saving " << highlights_name << std::endl; + highlights->save(highlights_name, "png"); - temp_file spumux_temp("videolink-spumux-"); - spumux_temp.close(); - std::ofstream spumux_file(spumux_temp.get_name().c_str()); + std::string spumux_name( + temp_file_name(temp_dir_, "menu-%3d.subpictures", 1 + index)); + std::ofstream spumux_file(spumux_name.c_str()); spumux_file << "\n" " \n" " \n"; + " highlight='" << highlights_name << "'\n" + " select='" << highlights_name << "'>\n"; int button_count = this_menu.entries.size(); for (int i = 0; i != button_count; ++i) { @@ -150,11 +216,15 @@ void dvd_generator::generate_menu_vob(unsigned index, } } } + // Pad vertically to even y coordinates since dvdauthor claims + // odd values may result in incorrect display. + // XXX This may cause overlappping where it wasn't previously + // a problem. spumux_file << " " << this_menu.vob_temp->get_name(); + << " | spumux -v0 -mdvd " << spumux_name << " > " << output_name; std::string command(command_stream.str()); const char * argv[] = { "/bin/sh", "-c", command.c_str(), 0 }; - std::cout << "running " << command << std::endl; + std::cout << "INFO: Running " << command << std::endl; int command_result; Glib::spawn_sync(".", Glib::ArrayHandle( argv, sizeof(argv)/sizeof(argv[0]), Glib::OWNERSHIP_NONE), Glib::SPAWN_STDOUT_TO_DEV_NULL, - SigC::Slot0(), + sigc::slot(), 0, 0, &command_result); - if (command_result != 0) + struct stat stat_buf; + if (command_result != 0 || stat(output_name.c_str(), &stat_buf) != 0 + || stat_buf.st_size == 0) throw std::runtime_error("spumux pipeline failed"); } @@ -228,8 +294,8 @@ dvd_generator::pgc_ref dvd_generator::add_title(vob_list & content) pgc_ref next_title(title_pgc, titles_.size()); // Check against maximum number of titles. - if (next_title.index == 99) - throw std::runtime_error("No more than 99 titles can be used"); + if (next_title.index == dvd::titles_max) + throw_length_error("number of titles", dvd::titles_max); titles_.resize(next_title.index + 1); titles_[next_title.index].swap(content); @@ -238,260 +304,397 @@ dvd_generator::pgc_ref dvd_generator::add_title(vob_list & content) void dvd_generator::generate(const std::string & output_dir) const { - temp_file temp("videolink-dvdauthor-"); - temp.close(); - std::ofstream file(temp.get_name().c_str()); + // This function uses a mixture of 0-based and 1-based numbering, + // due to the differing conventions of the language and the DVD + // format. Variable names ending in "_index" indicate 0-based + // indices and variable names ending in "_num" indicate 1-based + // numbers. + + std::string name(temp_file_name(temp_dir_, "videolink.dvdauthor")); + std::ofstream file(name.c_str()); + file << "\n"; // We generate code that uses registers in the following way: // - // g0: scratch - // g1: target menu location - // g2: source/return menu location for title - // g3: target chapter number - // - // 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, and numbering - // starts at 1, not 0. This is done for compatibility with - // the encoding of the s8 (button) register. - // - 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; - - file << - "\n" - " \n" - " \n"; - - for (unsigned menu_index = 0; menu_index != menus_.size(); ++menu_index) + // g0: Scratch. + // g1: Target location when jumping between menus. Top 6 bits are + // the button number (like s8) and bottom 10 bits are the menu + // number. This is used for selecting the appropriate button + // when entering a menu, for completing indirect jumps between + // domains, and for jumping to the correct menu after exiting a + // title. This is set to 0 in the pre-routine of the target + // menu. + // g2: Current location in menus. This is used for jumping to the + // correct menu when the player exits a title. + // g3: Target chapter number plus 1 when jumping to a title. + // This is used to jump to the correct chapter and to + // distinguish between indirect jumps to menus and titles. + // This is set to 0 in the pre-routine of the target title. + // g4: Source menu location used to jump to a title. This is + // compared with g2 to determine whether to increment the + // button number if the title is played to the end. + + static const unsigned button_mult = dvd::reg_s8_button_mult; + static const unsigned menu_mask = button_mult - 1; + static const unsigned button_mask = (1U << dvd::reg_bits) - button_mult; + + // Iterate over VMGM and titlesets. For these purposes, we + // consider the VMGM to be titleset 0. + + // We need a titleset for each title, and we may also need titlesets to + // hold extra menus if we have too many for the VMGM. + // Also, we need at least one titleset. + const unsigned titleset_end = std::max( + 1U + std::max(1U, titles_.size()), + (menus_.size() + dvdauthor_anonymous_menus_max - 1) + / dvdauthor_anonymous_menus_max); + + for (unsigned titleset_num = 0; + titleset_num != titleset_end; + ++titleset_num) { - const menu & this_menu = menus_[menu_index]; - - if (menu_index == 0) - { - // This is the first (title) menu, displayed when the - // disc is first played. - file << - " \n" - "
\n"
-		// Set a default target location if none is set.
-		// This covers first play and use of the "top menu"
-		// button.
-		"          if (g1 eq 0)\n"
-		"            g1 = " << 1 + button_mult << ";\n";
-	}
-	else
+	const char * const outer_element_name =
+	    titleset_num == 0 ? "vmgm" : "titleset";
+	const bool have_real_title =
+	    titleset_num != 0 && titleset_num <= titles_.size();
+	const bool have_real_menus =
+	    titleset_num * dvdauthor_anonymous_menus_max < menus_.size();
+
+	file << "  <" << outer_element_name << ">\n" <<
+	     "    \n"
+	     "