// Copyright 2005-6 Ben Hutchings <ben@decadent.org.uk>.
// See the file "COPYING" for licence details.
+#include <cerrno>
+#include <cstring>
#include <fstream>
+#include <iomanip>
#include <iostream>
+#include <ostream>
#include <sstream>
#include <stdexcept>
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;
}
dvd_generator::dvd_generator(const video::frame_params & frame_params,
{
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 == dvdauthor_anonymous_menus_max)
+ throw_length_error("number of menus", dvdauthor_anonymous_menus_max);
menus_.resize(next_menu.index + 1);
return next_menu;
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);
}
throw std::runtime_error("Failed to write control file for spumux");
std::ostringstream command_stream;
+ unsigned frame_count(menu_duration_frames(frame_params_));
if (encoder_ == mpeg_encoder_ffmpeg)
{
- command_stream
- << "ffmpeg"
- << " -f image2 -vcodec png -i "
- << background_name
- << " -target " << frame_params_.common_name << "-dvd"
- << " -vcodec mpeg2video -aspect 4:3 -an -y /dev/stdout";
+ for (unsigned i = 0; i != frame_count; ++i)
+ {
+ std::string frame_name(background_name);
+ frame_name.push_back('-');
+ frame_name.push_back('0' + i / 10);
+ frame_name.push_back('0' + i % 10);
+ if (symlink(background_name.c_str(), frame_name.c_str()) != 0)
+ throw std::runtime_error(
+ std::string("symlink: ").append(std::strerror(errno)));
+ }
+ command_stream <<
+ "ffmpeg -f image2 -vcodec png"
+ " -r " << frame_params_.rate_numer <<
+ "/" << frame_params_.rate_denom <<
+ " -i " << background_name << "-%02d"
+ " -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_name
- << " | ppmtoy4m -v0 -n1 -F"
+ << "pngtopnm " << background_name
+ << " | ppmtoy4m -v0 -n" << frame_count << " -F"
<< frame_params_.rate_numer << ":" << frame_params_.rate_denom
<< " -A" << frame_params_.pixel_ratio_width
<< ":" << frame_params_.pixel_ratio_height
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);
// This is the first (title) menu, displayed when the
// disc is first played.
file <<
- " <pgc entry='title' pause='inf'>\n"
+ " <pgc entry='title'>\n"
" <pre>\n"
// Set a default target location if none is set.
// This covers first play and use of the "top menu"
else
{
file <<
- " <pgc pause='inf'>\n"
+ " <pgc>\n"
" <pre>\n";
}
" </pre>\n"
" <vob file='"
<< temp_file_name(temp_dir_, "menu-%3d.mpeg", 1 + menu_index)
- << "'/>\n";
+ << "'>\n"
+ // Define a cell covering the whole menu and set a still
+ // time at the end of that, since it seems all players
+ // support that but some ignore a still time set on a PGC.
+ " <cell start='0' end='"
+ << std::fixed << std::setprecision(4)
+ << menu_duration_seconds(frame_params_) << "'"
+ " chapter='yes' pause='inf'/>\n"
+ " </vob>\n";
for (unsigned button_index = 0;
button_index != this_menu.entries.size();
}
file <<
- // Some DVD players don't seem to obey pause='inf' so make
- // them loop.
- " <post>\n"
- " jump cell 1;\n"
- " </post>\n"
" </pgc>\n";
}