Changed voblist implementation to expect a real XML document and to resolve VOB filenames relative to the voblist.
WebDVD is written in C++ and requires a recent C++ compiler e.g. g++
3.3.
-It requires headers and libraries for Boost, gtkmm and Mozilla. I
-have developed and tested it with Boost 1.32, gtkmm 2.2.12 and Mozilla
-1.7.8 but it may well work with earlier or later versions of these.
+It requires headers and libraries for Boost, gtkmm, Mozilla and expat.
+I have developed and tested it with Boost 1.32, gtkmm 2.2.12, Mozilla
+1.7.8 and expat 1.95.8 but it may well work with earlier or later
+versions of these.
I use Debian Linux and have not yet attempted to build it on other
systems, but it should work on most modern Unix-like systems.
# instances are only ever called by the Release function which is virtual.
CXXFLAGS := -Wall -Wno-non-virtual-dtor
LDFLAGS := -lpthread $(shell pkg-config --libs gtkmm-2.0 mozilla-gtkmozembed) \
- -Wl,-rpath -Wl,$(moz_lib_dir)
+ -Wl,-rpath -Wl,$(moz_lib_dir) -lexpat
ifdef NDEBUG
CPPFLAGS += -DNDEBUG
cxxsources := \
auto_proc.cpp browser_widget.cpp child_iterator.cpp generate_dvd.cpp \
link_iterator.cpp pixbufs.cpp style_sheets.cpp temp_file.cpp video.cpp \
- webdvd.cpp x_frame_buffer.cpp xpcom_support.cpp
+ vob_list.cpp webdvd.cpp x_frame_buffer.cpp xml_utils.cpp xpcom_support.cpp
csources := jquant2.c
webdvd : $(cxxsources:.cpp=.o) $(csources:.c=.o)
-DMOZ_VERSION_MINOR=$(moz_version_minor) \
-DMOZ_VERSION_PATCHLEVEL=$(moz_version_patchlevel)
-browser_widget.% generate_dvd.% pixbufs.% temp_file.% webdvd.% \
+browser_widget.% generate_dvd.% pixbufs.% temp_file.% vob_list.% webdvd.% \
: CPPFLAGS += $(shell pkg-config --cflags gtkmm-2.0)
browser_widget.% child_iterator.o link_iterator.% style_sheets.% webdvd.% \
WebDVD depends on the following software:
- dvdauthor
+- expat 1.x
- Gtkmm 2.0
- mjpegtools
- Mozilla 1.7.x (later versions may work but are untested)
You can link directly to local MPEG video files whose names end in
".vob". If you wish to combine multiple files into a single video
sequence ("title" in DVD terminology) or to add chapter marks to a
-video sequence, create a text file whose name ends in ".voblist" and
-containg <vob> elements as described in the dvdauthor manual page, and
-link to that. Note that these list files are currently included
-directly in the control file that WebDVD passes to dvdauthor, which
-means that file names in them will be resolved relative to the current
-directory rather than the directory containing the list file. This is
-probably a bug.
+video sequence, create and link to a VOB-list file (explained below)
+whose name ends in ".voblist".
You can link to a title and begin playback at the beginning of a
specific chapter by adding "#" and then the chapter number to the end
of the URL.
+VOB-lists
+
+A VOB-list file is an XML file with the document element <vob-list>
+and containing <vob> elements as described in the dvdauthor manual
+page. The file names in a VOB-list file are resolved relative to the
+directory containing the list file.
+
Video standards
By default, webdvd generates PAL/SECAM video. If you wish to produce
Priority 1 (highest)
-Fix resolution of relative filenames in VOB lists.
Provide a means to specify video aspect and menu & audio language.
Suppress Mozilla error dialogs when processing (file not found etc).
Maintainer: Ben Hutchings <ben@decadentplace.org.uk>
Section: graphics
Priority: extra
-Build-Depends: debhelper (>=4), libboost-dev, libgtkmm2.0-dev, mozilla-dev
+Build-Depends: debhelper (>=4), libboost-dev, libgtkmm2.0-dev, mozilla-dev, libexpat1-dev
Standards-Version: 3.6.2
Package: webdvd
#include "dvd.hpp"
#include "generate_dvd.hpp"
+#include "xml_utils.hpp"
dvd_contents::menu::menu()
: vob_temp(new temp_file("webdvd-vob-"))
" <titles>\n"
" <pgc>\n"
// Record calling location.
- " <pre> g12 = g1; </pre>\n"
- << contents.titles[title_num].vob_list <<
+ " <pre> g12 = g1; </pre>\n";
+
+ for (vob_list::const_iterator it = contents.titles[title_num].begin(),
+ end = contents.titles[title_num].end();
+ it != end;
+ ++it)
+ {
+ file << " <vob file='" << xml_escape(it->file) << "'";
+ if (!it->chapters.empty())
+ file << " chapters='" << xml_escape(it->chapters) << "'";
+ if (!it->pause.empty())
+ file << " pause='" << xml_escape(it->pause) << "'";
+ file << "/>\n";
+ }
+
+ file <<
// If the menu location has not been changed during
// the title, set the location to be the following
// button in the menu. In any case, return to some
#include <boost/shared_ptr.hpp>
#include "temp_file.hpp"
+#include "vob_list.hpp"
// Description of menus and titles to go on a DVD.
std::vector<pgc_ref> entries;
};
- // Title definition. This is currently just an XML fragment
- // consisting of one of more <vob> elements. It is included
- // directly within the corresponding <pgc> element in the file
- // passed to dvdauthor.
- struct title
- {
- explicit title(const std::string & vob_list)
- : vob_list(vob_list)
- {}
-
- std::string vob_list;
- };
-
std::vector<menu> menus;
- std::vector<title> titles;
+ std::vector<vob_list> titles;
};
// Use dvdauthor to generate a DVD filesystem with the given contents.
--- /dev/null
+// Copyright 2006 Ben Hutchings <ben@decadentplace.org.uk>.
+// See the file "COPYING" for licence details.
+
+#include <cerrno>
+#include <cstring>
+#include <sstream>
+
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <expat.h>
+
+#include <glibmm/fileutils.h>
+#include <glibmm/miscutils.h>
+
+#include "auto_fd.hpp"
+#include "auto_handle.hpp"
+#include "vob_list.hpp"
+
+namespace
+{
+ struct xml_parser_closer
+ {
+ void operator()(XML_Parser parser) const
+ {
+ if (parser)
+ XML_ParserFree(parser);
+ }
+ };
+
+ struct xml_parser_factory
+ {
+ XML_Parser operator()() const
+ {
+ return NULL;
+ }
+ };
+
+ typedef auto_handle<XML_Parser, xml_parser_closer, xml_parser_factory>
+ auto_xml_parser;
+
+ struct parse_context
+ {
+ parse_context(XML_Parser parser,
+ const std::string & list_path,
+ vob_list & list)
+ : parser(parser),
+ list_path(list_path),
+ base_path(Glib::path_get_dirname(list_path)),
+ list(list),
+ level(0)
+ {}
+ XML_Parser parser;
+ const std::string & list_path;
+ std::string base_path;
+ vob_list & list;
+ std::auto_ptr<xml_error> parse_error;
+ unsigned level;
+ };
+
+ void start_element_handler(void * user_data,
+ const char * name,
+ const char ** attributes)
+ {
+ parse_context & context = *static_cast<parse_context *>(user_data);
+
+ if (context.level == 0 && std::strcmp(name, "vob-list") == 0)
+ {
+ // We don't expect (and will ignore) any attributes.
+ }
+ else if (context.level == 1 && std::strcmp(name, "vob") == 0)
+ {
+ vob_ref ref;
+
+ while (attributes[0] != NULL)
+ {
+ if (std::strcmp(attributes[0], "file") == 0)
+ {
+ ref.file = attributes[1];
+ if (!Glib::path_is_absolute(ref.file))
+ ref.file = Glib::build_filename(context.base_path,
+ ref.file);
+ }
+ else if (std::strcmp(attributes[0], "chapters") == 0)
+ {
+ ref.chapters = attributes[1];
+ }
+ else if (std::strcmp(attributes[0], "pause") == 0)
+ {
+ ref.pause = attributes[1];
+ }
+
+ attributes += 2;
+ }
+
+ if (ref.file.empty())
+ {
+ context.parse_error.reset(
+ new xml_error(
+ context.list_path,
+ XML_GetCurrentLineNumber(context.parser),
+ "<vob> element missing file attribute"));
+ }
+ else
+ {
+ context.list.push_back(ref);
+ }
+ }
+ else // not root <vob-list> or child <vob>
+ {
+ context.parse_error.reset(
+ new xml_error(
+ context.list_path,
+ XML_GetCurrentLineNumber(context.parser),
+ std::string("unexpected element: <").append(name)
+ .append(">")));
+ }
+
+ ++context.level;
+ }
+
+ void end_element_handler(void * user_data,
+ const char * name)
+ {
+ parse_context & context = *static_cast<parse_context *>(user_data);
+ --context.level;
+ }
+}
+
+vob_list read_vob_list(const std::string & path)
+{
+ vob_list result;
+
+ auto_fd fd(open(path.c_str(), O_RDONLY, 0));
+ if (fd.get() < 0)
+ // FIXME: look up proper error code
+ throw Glib::FileError(Glib::FileError::FAILED,
+ std::strerror(errno));
+
+ auto_xml_parser parser(XML_ParserCreate(NULL));
+ if (parser.get() == NULL)
+ throw std::bad_alloc(); // any other reason?
+
+ parse_context context(parser.get(), path, result);
+ XML_SetUserData(parser.get(), &context);
+ XML_SetStartElementHandler(parser.get(), start_element_handler);
+ XML_SetEndElementHandler(parser.get(), end_element_handler);
+
+ for (;;)
+ {
+ static const int buffer_size = 1024;
+ void * buffer = XML_GetBuffer(parser.get(), buffer_size);
+ if (buffer == NULL)
+ throw std::bad_alloc();
+ int read_size = read(fd.get(), buffer, buffer_size);
+ if (read_size < 0)
+ // FIXME: look up proper error code
+ throw Glib::FileError(Glib::FileError::FAILED,
+ std::strerror(errno));
+ bool is_final = read_size < buffer_size;
+ if (XML_ParseBuffer(parser.get(), read_size, is_final)
+ == XML_STATUS_ERROR)
+ throw xml_error(path,
+ XML_GetCurrentLineNumber(parser.get()),
+ XML_ErrorString(XML_GetErrorCode(parser.get())));
+ if (context.parse_error.get())
+ throw *context.parse_error;
+ if (is_final)
+ break;
+ }
+
+ return result;
+}
+
+namespace
+{
+ std::string make_xml_error_message(const std::string & path, int line,
+ const std::string & message)
+ {
+ std::ostringstream os;
+ os << path << ":" << line << ": " << message;
+ return os.str();
+ }
+}
+
+xml_error::xml_error(const std::string & path, int line,
+ const std::string & message)
+ : std::runtime_error(make_xml_error_message(path, line, message))
+{}
--- /dev/null
+// Copyright 2006 Ben Hutchings <ben@decadentplace.org.uk>.
+// See the file "COPYING" for licence details.
+
+#ifndef INC_VOB_LIST_HPP
+#define INC_VOB_LIST_HPP
+
+#include <list>
+#include <stdexcept>
+#include <string>
+
+struct vob_ref
+{
+ std::string file; // file name (absolute, resolved rel. to list)
+ std::string chapters; // chapters attribute, unmodified
+ std::string pause; // pause attribute, unmodified
+};
+
+typedef std::list<vob_ref> vob_list;
+
+vob_list read_vob_list(const std::string & file_name);
+
+struct xml_error : public std::runtime_error
+{
+ xml_error(const std::string & path, int line, const std::string & message);
+};
+
+#endif // !INC_VOB_LIST_HPP
-// Copyright 2005 Ben Hutchings <ben@decadentplace.org.uk>.
+// Copyright 2005-6 Ben Hutchings <ben@decadentplace.org.uk>.
// See the file "COPYING" for licence details.
#include <cassert>
#include "temp_file.hpp"
#include "video.hpp"
#include "x_frame_buffer.hpp"
+#include "xml_utils.hpp"
#include "xpcom_support.hpp"
using xpcom_support::check;
}
- std::string xml_escape(const std::string & str)
- {
- std::string result;
- std::size_t begin = 0;
-
- for (;;)
- {
- std::size_t end = str.find_first_of("\"&'<>", begin);
- result.append(str, begin, end - begin);
- if (end == std::string::npos)
- return result;
-
- const char * entity = NULL;
- switch (str[end])
- {
- case '"': entity = """; break;
- case '&': entity = "&"; break;
- case '\'': entity = "'"; break;
- case '<': entity = "<"; break;
- case '>': entity = ">"; break;
- }
- assert(entity);
- result.append(entity);
-
- begin = end + 1;
- }
- }
-
-
class webdvd_window : public Gtk::Window
{
public:
else
{
Glib::ustring hostname;
- std::string filename(Glib::filename_from_uri(uri, hostname));
+ std::string path(Glib::filename_from_uri(uri, hostname));
// FIXME: Should check the hostname
- std::string vob_list;
+ vob_list list;
// Store a reference to a linked VOB file, or the contents
// of a linked VOB list file.
- if (filename.compare(filename.size() - 4, 4, ".vob") == 0)
+ if (path.compare(path.size() - 4, 4, ".vob") == 0)
{
- if (!Glib::file_test(filename, Glib::FILE_TEST_IS_REGULAR))
+ if (!Glib::file_test(path, Glib::FILE_TEST_IS_REGULAR))
throw std::runtime_error(
- filename + " is missing or not a regular file");
- vob_list
- .append("<vob file='")
- .append(xml_escape(filename))
- .append("'/>\n");
+ path + " is missing or not a regular file");
+ vob_ref ref;
+ ref.file = path;
+ list.push_back(ref);
}
else
{
- assert(filename.compare(filename.size() - 8, 8, ".voblist")
- == 0);
- // TODO: Validate the file contents
- vob_list.assign(Glib::file_get_contents(filename));
+ assert(path.compare(path.size() - 8, 8, ".voblist") == 0);
+ read_vob_list(path).swap(list);
}
- contents_.titles.push_back(dvd_contents::title(vob_list));
+ contents_.titles.resize(contents_.titles.size() + 1);
+ contents_.titles.back().swap(list);
return next_title;
}
}
--- /dev/null
+// Copyright 2005-6 Ben Hutchings <ben@decadentplace.org.uk>.
+// See the file "COPYING" for licence details.
+
+#include <cassert>
+#include <cstddef>
+
+#include "xml_utils.hpp"
+
+std::string xml_escape(const std::string & str)
+{
+ std::string result;
+ std::size_t begin = 0;
+
+ for (;;)
+ {
+ std::size_t end = str.find_first_of("\"&'<>", begin);
+ result.append(str, begin, end - begin);
+ if (end == std::string::npos)
+ return result;
+
+ const char * entity = NULL;
+ switch (str[end])
+ {
+ case '"': entity = """; break;
+ case '&': entity = "&"; break;
+ case '\'': entity = "'"; break;
+ case '<': entity = "<"; break;
+ case '>': entity = ">"; break;
+ }
+ assert(entity);
+ result.append(entity);
+
+ begin = end + 1;
+ }
+}
--- /dev/null
+// Copyright 2006 Ben Hutchings <ben@decadentplace.org.uk>.
+// See the file "COPYING" for licence details.
+
+#ifndef INC_XML_UTILS_HPP
+#define INC_XML_UTILS_HPP
+
+#include <string>
+
+std::string xml_escape(const std::string & str);
+
+#endif // !INC_XML_UTILS_HPP