attractive and usable menus. It converts HTML pages into DVD menus by
rendering them in Mozilla and reproducing their link structure. This
allows you to design DVDs using familiar HTML editing tools or your
-favourite text editor. This very early version produces a set of
-files suitable for passing to the "dvdauthor" and "spumux" programs,
-but in future it is intended to run the necessary external programs
-automatically.
+favourite text editor.
Requirements
------------
WebDVD depends on the following software:
+- dvdauthor
- Gtkmm 2.0
+- mjpegtools
- Mozilla 1.7.x (later versions may work but are untested)
+- netpbm
- Xvfb (from XFree86 or X.org)
To build a complete DVD image you will also need:
-- dvdauthor
-- mjpegtools
- mkisofs
-- netpbm
You will also need a program such as ffmpeg or mencoder for producing
DVD-suitable MPEG-1 or MPEG-2 video files.
Usage
-----
-Run "webdvd URL" where URL is the URL for the page that is to be the
-top menu of the DVD. It will automatically follow links to other
-pages and to video files, rendering each page. You must be careful
-not to link to pages that you do not want to appear on the disc, such
-as normal web sites.
-
-By default, webdvd uses a frame size of 720x576, which is suitable for
-PAL DVDs. If you wish to produce NTSC DVDs you must override this
-by adding the option "-geometry 720x480".
-
-This will create the following files (with NNNNNN replaced with each
-successive page number):
-
-- webdvd.dvdauthor: This is an XML file to be passed to dvdauthor.
-- page_NNNNNN.spumux: These are XML files to be passed to spumux.
-- page_NNNNNN_back.png: This is a static image of the page, which
- becomes the menu background.
-- page_NNNNNN_links.png: This is an image of the change in appearance
- of each link when the pointer is over it.
-
-Currently, you must run commands along the following lines to produce
-a complete DVD image:
-
-for spumux in page_??????.spumux; do
- page=$(basename $spumux .spumux)
- pngtopnm ${page}_back.png \
- | ppmtoy4m -v0 -n 1 -F 25:1 -A 59:54 -I p -S 420_mpeg2 \
- | mpeg2enc -v0 -f 8 -a 2 -o /dev/stdout \
- | mplex -v0 -f 8 -o /dev/stdout /dev/stdin \
- | spumux -v0 -m dvd $spumux > ${page}.vob
-done
-rm -rf dvd-temp
-dvdauthor -o dvd-temp -x webdvd.dvdauthor
-mkisofs -dvd-video dvd-temp >dvd.iso
-rm -rf dvd-temp
-
-Adjust the name of the temporary directory (here "dvd-temp") and the
-output file ("dvd.iso") as you please.
-
-If you are using NTSC video, you will need to change the ppmtoy4m
-parameters. Use "-F 30000:1001 -A 10:11" instead of "-F 25:1 -A 59:54".
+Run "webdvd URL DIR" where URL is the URL for the page that is to be
+the top menu of the DVD and DIR is the directory in which to create
+the DVD filesystem (which should be missing or empty). It will
+automatically follow links to other pages and to video files,
+rendering each page. You must be careful not to link to pages that
+you do not want to appear on the disc, such as normal web sites.
+
+By default, webdvd generates PAL/SECAM video. If you wish to produce
+NTSC DVDs you can override this by adding the option "--video-std ntsc".
+
+If webdvd runs successfully you can use mkisofs to create a DVD image:
+
+ mkisofs -dvd-video DIR > IMAGE
+ rm -rf DIR
+
+Alternately you can write the filesystem directly to a writable DVD
+with growisofs or mkisofs plus a suitable version of cdrecord.
Limitations
-----------
#include <memory>
#include <queue>
#include <set>
+#include <sstream>
#include <string>
#include <stdlib.h>
#include <unistd.h>
+#include <boost/shared_ptr.hpp>
+
#include <gdkmm/pixbuf.h>
+#include <glibmm/convert.h>
+#include <glibmm/spawn.h>
#include <gtkmm/main.h>
#include <gtkmm/window.h>
#include "linkiterator.hpp"
#include "pixbufs.hpp"
#include "stylesheets.hpp"
+#include "temp_file.hpp"
#include "video.hpp"
#include "xpcom_support.hpp"
public:
WebDvdWindow(
const video::frame_params & frame_params,
- const std::string & main_page_uri);
+ const std::string & main_page_uri,
+ const std::string & output_dir);
private:
void add_page(const std::string & uri);
void process_links(nsIPresShell * pres_shell,
nsIPresContext * pres_context,
nsIDOMWindow * dom_window);
- void generate_dvdauthor_file();
+ void generate_dvd();
enum ResourceType { page_resource, video_resource };
typedef std::pair<ResourceType, int> ResourceEntry;
video::frame_params frame_params_;
+ std::string output_dir_;
BrowserWidget browser_widget_;
nsCOMPtr<nsIStyleSheet> stylesheet_;
std::queue<std::string> page_queue_;
std::vector<std::string> video_paths_;
bool loading_;
int pending_req_count_;
+ std::auto_ptr<temp_file> background_temp_;
struct link_state;
std::auto_ptr<link_state> link_state_;
+ std::vector<boost::shared_ptr<temp_file> > page_temp_files_;
};
WebDvdWindow::WebDvdWindow(
const video::frame_params & frame_params,
- const std::string & main_page_uri)
+ const std::string & main_page_uri,
+ const std::string & output_dir)
: frame_params_(frame_params),
+ output_dir_(output_dir),
stylesheet_(load_css("file://" WEBDVD_LIB_DIR "/webdvd.css")),
loading_(false),
pending_req_count_(0)
page_queue_.pop();
if (page_queue_.empty())
{
- generate_dvdauthor_file();
+ generate_dvd();
Gtk::Main::quit();
}
else
void WebDvdWindow::save_screenshot()
{
- char filename[25];
- std::sprintf(filename, "page_%06d_back.png", page_links_.size());
Glib::RefPtr<Gdk::Window> window(get_window());
assert(window);
window->process_updates(true);
- std::cout << "saving " << filename << std::endl;
+
+ background_temp_.reset(new temp_file("webdvd-back-"));
+ background_temp_->close();
+ std::cout << "saving " << background_temp_->get_name() << std::endl;
Gdk::Pixbuf::create(Glib::RefPtr<Gdk::Drawable>(window),
window->get_colormap(),
0, 0, 0, 0,
frame_params_.width, frame_params_.height)
- ->save(filename, "png");
+ ->save(background_temp_->get_name(), "png");
}
struct WebDvdWindow::link_state
{
Glib::RefPtr<Gdk::Pixbuf> diff_pixbuf;
+ std::auto_ptr<temp_file> spumux_temp;
std::ofstream spumux_file;
+ std::auto_ptr<temp_file> links_temp;
+
int link_num;
LinkIterator links_it, links_end;
true, 8, // has_alpha, bits_per_sample
frame_params_.width, frame_params_.height);
- char spumux_filename[20];
- std::sprintf(spumux_filename,
- "page_%06d.spumux", page_links_.size());
- state->spumux_file.open(spumux_filename);
+ state->spumux_temp.reset(new temp_file("webdvd-spumux-"));
+ state->spumux_temp->close();
+
+ state->links_temp.reset(new temp_file("webdvd-links-"));
+ state->links_temp->close();
+
+ state->spumux_file.open(state->spumux_temp->get_name().c_str());
state->spumux_file <<
"<subpictures>\n"
" <stream>\n"
" <spu force='yes' start='00:00:00.00'\n"
- " highlight='page_" << std::setfill('0')
- << std::setw(6) << page_links_.size()
- << "_links.png'\n"
- " select='page_" << std::setfill('0')
- << std::setw(6) << page_links_.size()
- << "_links.png'>\n";
+ " highlight='" << state->links_temp->get_name()
+ << "'\n"
+ " select='" << state->links_temp->get_name()
+ << "'>\n";
state->link_num = 0;
state->links_it = LinkIterator(basic_doc);
quantise_rgba_pixbuf(state->diff_pixbuf, dvd::button_n_colours);
- char filename[25];
- std::sprintf(filename, "page_%06d_links.png", page_links_.size());
- std::cout << "saving " << filename << std::endl;
- state->diff_pixbuf->save(filename, "png");
+ std::cout << "saving " << state->links_temp->get_name()
+ << std::endl;
+ state->diff_pixbuf->save(state->links_temp->get_name(), "png");
state->spumux_file <<
" </spu>\n"
" </stream>\n"
"</subpictures>\n";
+
+ state->spumux_file.close();
+
+ // TODO: if (!state->spumux_file) throw ...
+
+ {
+ boost::shared_ptr<temp_file> vob_temp(
+ new temp_file("webdvd-vob-"));
+ std::ostringstream command_stream;
+ 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 -S420_mpeg2"
+ " | mpeg2enc -v0 -f8 -a2 -o/dev/stdout"
+ " | mplex -v0 -f8 -o/dev/stdout /dev/stdin"
+ " | spumux -v0 -mdvd ")
+ << state->spumux_temp->get_name()
+ << " > " << vob_temp->get_name();
+ std::string command(command_stream.str());
+ const char * argv[] = {
+ "/bin/sh", "-c", command.c_str(), 0
+ };
+ std::cout << "running " << argv[2] << 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");
+
+ page_temp_files_.push_back(vob_temp);
+ }
}
void generate_page_dispatch(std::ostream &, int indent,
int first_page, int last_page);
- void WebDvdWindow::generate_dvdauthor_file()
+ void WebDvdWindow::generate_dvd()
{
- std::ofstream file("webdvd.dvdauthor");
+ temp_file temp("webdvd-dvdauthor-");
+ temp.close();
+ std::ofstream file(temp.get_name().c_str());
// We generate code that uses registers in the following way:
//
// appropriate link/button.
" g0 = 0; s8 = g1 & " << link_mask << ";\n"
" </pre>\n"
- " <vob file='page_"
- << std::setfill('0') << std::setw(6) << page_num
- << ".vob'/>\n";
+ " <vob file='"
+ << page_temp_files_[page_num - 1]->get_name() << "'/>\n";
for (std::size_t link_num = 1;
link_num <= page_links.size();
file <<
"</dvdauthor>\n";
+
+ file.close();
+
+ {
+ const char * argv[] = {
+ "dvdauthor",
+ "-o", output_dir_.c_str(),
+ "-x", temp.get_name().c_str(),
+ 0
+ };
+ int command_result;
+ Glib::spawn_sync(".",
+ Glib::ArrayHandle<std::string>(
+ argv, sizeof(argv)/sizeof(argv[0]),
+ Glib::OWNERSHIP_NONE),
+ Glib::SPAWN_SEARCH_PATH
+ | Glib::SPAWN_STDOUT_TO_DEV_NULL,
+ SigC::Slot0<void>(),
+ 0, 0,
+ &command_result);
+ if (command_result != 0)
+ throw std::runtime_error("dvdauthor failed");
+ }
}
void generate_page_dispatch(std::ostream & file, int indent,
// Initialise Gtk
Gtk::Main kit(argc, argv);
-
+
// Check we have the right number of arguments. We can't
// do this earlier because we need to let Gtk read and remove
// any of the many options it understands.
else
break;
}
- if (argi != argc - 1)
+ if (argi != argc - 2)
{
std::cerr << "Usage: " << argv[0]
<< (" [gtk-options] [--video-std std-name]"
- "front-page-url\n");
+ "front-page-url output-dir\n");
return EXIT_FAILURE;
}
// Initialise Mozilla
BrowserWidget::init();
- WebDvdWindow window(frame_params, argv[argi]);
+ WebDvdWindow window(frame_params, argv[argi], argv[argi + 1]);
Gtk::Main::run(window);
}
catch (std::exception & e)