// 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>
#include <gdkmm/pixbuf.h>
+#include <glibmm/miscutils.h>
#include <glibmm/spawn.h>
#include "dvd.hpp"
+ (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);
+ }
}
+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());
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 << "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 << "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 <<
"<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";
+ " highlight='" << highlights_name << "'\n"
+ " select='" << highlights_name << "'>\n";
int button_count = this_menu.entries.size();
for (int i = 0; i != button_count; ++i)
{
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_temp.get_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_temp.get_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
" | mplex -v0 -f8 -o/dev/stdout /dev/stdin";
}
command_stream
- << " | spumux -v0 -mdvd " << spumux_temp.get_name()
- << " > " << this_menu.vob_temp->get_name();
+ << " | spumux -v0 -mdvd " << spumux_name
+ << " > " << temp_file_name(temp_dir_, "menu-%3d.mpeg", 1 + index);
std::string command(command_stream.str());
const char * argv[] = {
"/bin/sh", "-c", command.c_str(), 0
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());
+ std::string name(temp_file_name(temp_dir_, "videolink.dvdauthor"));
+ std::ofstream file(name.c_str());
// We generate code that uses registers in the following way:
//
// 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";
}
// this same menu!
" g1 = 0;\n"
" </pre>\n"
- " <vob file='" << this_menu.vob_temp->get_name() << "'/>\n";
+ " <vob file='"
+ << temp_file_name(temp_dir_, "menu-%3d.mpeg", 1 + menu_index)
+ << "'>\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";
}
const char * argv[] = {
"dvdauthor",
"-o", output_dir.c_str(),
- "-x", temp.get_name().c_str(),
+ "-x", name.c_str(),
0
};
int command_result;