+ // 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 << " <button"
+ " x0='" << this_entry.area.left << "'"
+ " y0='" << (this_entry.area.top & ~1) << "'"
+ " x1='" << this_entry.area.right << "'"
+ " y1='" << ((this_entry.area.bottom + 1) & ~1) << "'"
+ " left='" << (i == 0 ? button_count : i) << "'"
+ " right='" << 1 + (i + 1) % button_count << "'"
+ " up='" << 1 + up_button << "'"
+ " down='" << 1 + down_button << "'"
+ "/>\n";
+ }
+ spumux_file <<
+ " </spu>\n"
+ " </stream>\n"
+ "</subpictures>\n";
+ spumux_file.close();
+ if (!spumux_file)
+ throw std::runtime_error("Failed to write control file for spumux");
+
+ 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.