+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");
+
+ menus_.resize(next_menu.index + 1);
+ return next_menu;
+}
+
+void dvd_generator::add_menu_entry(unsigned index,
+ const rectangle & area,
+ const pgc_ref & target)
+{
+ assert(index < menus_.size());
+ assert(target.type == menu_pgc && target.index < menus_.size()
+ || target.type == title_pgc && target.index < titles_.size());
+ menu_entry new_entry = { area, target };
+ menus_[index].entries.push_back(new_entry);
+}
+
+void dvd_generator::generate_menu_vob(unsigned index,
+ Glib::RefPtr<Gdk::Pixbuf> background,
+ Glib::RefPtr<Gdk::Pixbuf> highlights)
+ const
+{
+ 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");
+
+ 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");
+
+ temp_file spumux_temp("videolink-spumux-");
+ spumux_temp.close();
+ std::ofstream spumux_file(spumux_temp.get_name().c_str());
+ spumux_file <<
+ "<subpictures>\n"
+ " <stream>\n"
+ " <spu force='yes' start='00:00:00.00'\n"
+ " highlight='" << highlights_temp.get_name() << "'\n"
+ " select='" << highlights_temp.get_name() << "'>\n";
+ int button_count = this_menu.entries.size();
+ for (int i = 0; i != button_count; ++i)
+ {
+ const menu_entry & this_entry = this_menu.entries[i];
+
+ // We program left and right to cycle through the buttons in
+ // the order the entries were added. This should result in
+ // left and right behaving like the tab and shift-tab keys
+ // would in the browser. Hopefully that's a sensible order.
+ // We program up and down to behave geometrically.
+ int up_button = i, down_button = i;
+ double up_closeness = 0.0, down_closeness = 0.0;
+ for (int j = 0; j != button_count; ++j)
+ {
+ const menu_entry & other_entry = this_menu.entries[j];
+ double closeness = directed_closeness(
+ this_entry.area, other_entry.area, -1);
+ if (closeness > up_closeness)
+ {
+ up_button = j;
+ up_closeness = closeness;
+ }
+ else
+ {
+ closeness = directed_closeness(
+ this_entry.area, other_entry.area, 1);
+ if (closeness > down_closeness)
+ {
+ down_button = j;
+ down_closeness = closeness;
+ }
+ }
+ }
+ spumux_file << " <button"
+ " x0='" << this_entry.area.left << "'"
+ " y0='" << this_entry.area.top << "'"
+ " x1='" << this_entry.area.right << "'"
+ " y1='" << this_entry.area.bottom << "'"
+ " 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::ostringstream command_stream;
+ if (encoder_ == mpeg_encoder_ffmpeg)
+ {
+ command_stream
+ << "ffmpeg"
+ << " -f image2 -vcodec png -i "
+ << background_temp.get_name()
+ << " -target " << frame_params_.common_name << "-dvd"
+ << " -vcodec mpeg2video -aspect 4:3 -an -y /dev/stdout";
+ }
+ else
+ {
+ assert(encoder_ == mpeg_encoder_mjpegtools_old
+ || encoder_ == mpeg_encoder_mjpegtools_new);
+ command_stream
+ << "pngtopnm "
+ << background_temp.get_name()
+ << " | ppmtoy4m -v0 -n1 -F"
+ << frame_params_.rate_numer << ":" << frame_params_.rate_denom
+ << " -A" << frame_params_.pixel_ratio_width
+ << ":" << frame_params_.pixel_ratio_height
+ << " -Ip ";
+ // The chroma subsampling keywords changed between
+ // versions 1.6.2 and 1.8 of mjpegtools. There is no
+ // keyword that works with both.
+ if (encoder_ == mpeg_encoder_mjpegtools_old)
+ command_stream << "-S420_mpeg2";
+ else
+ command_stream << "-S420mpeg2";
+ command_stream <<
+ " | mpeg2enc -v0 -f8 -a2 -o/dev/stdout"
+ " | mplex -v0 -f8 -o/dev/stdout /dev/stdin";
+ }
+ command_stream
+ << " | spumux -v0 -mdvd " << spumux_temp.get_name()
+ << " > " << this_menu.vob_temp->get_name();
+ std::string command(command_stream.str());
+ const char * argv[] = {
+ "/bin/sh", "-c", command.c_str(), 0
+ };
+ std::cout << "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::Slot0<void>(),
+ 0, 0,
+ &command_result);
+ if (command_result != 0)
+ throw std::runtime_error("spumux pipeline failed");
+}
+
+dvd_generator::pgc_ref dvd_generator::add_title(vob_list & content)