- // When a title finishes or the user presses the menu
- // button, this always jumps to the titleset's root menu.
- // We want to return the user to the last menu they used.
- // So we arrange for each titleset's root menu to return
- // to the vmgm title menu and then dispatch from there to
- // whatever the correct menu is. We determine the correct
- // menu by looking at the menu part of g1.
-
- file << " g0 = g1 & " << menu_mask << ";\n";
-
- // There is a limit of 128 VM instructions in each PGC.
- // Therefore in each menu's <pre> section we generate
- // jumps to menus with numbers greater by 512, 256, 128,
- // ..., 1 where (a) such a menu exists, (b) this menu
- // number is divisible by twice that increment and (c) the
- // correct menu is that or a later menu. Thus each menu
- // has at most 10 such conditional jumps and is reachable
- // by at most 10 jumps from the title menu. This chain of
- // jumps might take too long on some players; this has yet
- // to be investigated.
-
- for (std::size_t menu_incr = (menu_mask + 1) / 2;
- menu_incr != 0;
- menu_incr /= 2)
+ std::string output_name(
+ temp_file_name(temp_dir_, "menu-%3d.mpeg", 1 + index));
+
+ std::ostringstream command_stream;
+ if (encoder_ == mpeg_encoder_ffmpeg)
+ {
+ command_stream <<
+ "ffmpeg -f image2 -vcodec png"
+ " -r " << frame_params_.rate_numer <<
+ "/" << frame_params_.rate_denom <<
+ " -loop_input -i " << background_name <<
+ " -t " << menu_duration_seconds(frame_params_) <<
+ " -target " << frame_params_.common_name << "-dvd"
+ " -aspect 4:3 -an -y /dev/stdout";
+ }
+ else
+ {
+ assert(encoder_ == mpeg_encoder_mjpegtools);
+ command_stream
+ << "pngtopnm " << background_name
+ << " | ppmtoy4m -v0 -n" << menu_duration_frames(frame_params_)
+ << " -F" << frame_params_.rate_numer << ":" << frame_params_.rate_denom
+ << " -A" << frame_params_.pixel_ratio_width
+ << ":" << frame_params_.pixel_ratio_height
+ << " -Ip -S420mpeg2"
+ " | mpeg2enc -v0 -f8 -a2 -o/dev/stdout"
+ " | mplex -v0 -f8 -o/dev/stdout /dev/stdin";
+ }
+ command_stream
+ << " | 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 << "INFO: Running " << command << std::endl;
+ int command_result;
+ Glib::spawn_sync(".",
+ Glib::ArrayHandle<std::string>(
+ argv, sizeof(argv)/sizeof(argv[0]),
+ Glib::OWNERSHIP_NONE),
+ Glib::SPAWN_STDOUT_TO_DEV_NULL,
+ sigc::slot<void>(),
+ 0, 0,
+ &command_result);
+ 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");
+}
+
+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 == dvd::titles_max)
+ throw_length_error("number of titles", dvd::titles_max);
+
+ titles_.resize(next_title.index + 1);
+ titles_[next_title.index].swap(content);
+ return next_title;
+}
+
+void dvd_generator::generate(const std::string & output_dir) const
+{
+ // 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 << "<dvdauthor>\n";
+
+ // We generate code that uses registers in the following way:
+ //
+ // 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<unsigned>(
+ 1U + std::max<unsigned>(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 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" <<
+ " <menus>\n"
+ " <video format='" << frame_params_.common_name << "'/>\n";
+
+ const unsigned menu_begin = titleset_num * dvdauthor_anonymous_menus_max;
+ const unsigned menu_end =
+ have_real_menus
+ ? std::min<unsigned>(
+ (titleset_num + 1) * dvdauthor_anonymous_menus_max,
+ menus_.size())
+ : menu_begin + 1;
+
+ for (unsigned menu_index = menu_begin;
+ menu_index != menu_end;
+ ++menu_index)