From c13714f6498df33e02635421354f5fb88a60eb3d Mon Sep 17 00:00:00 2001 From: Ben Hutchings Date: Sun, 26 Feb 2006 19:45:47 +0000 Subject: [PATCH] Moved xml_escape into a separate file. Changed voblist implementation to expect a real XML document and to resolve VOB filenames relative to the voblist. --- INSTALL | 7 +- Makefile | 6 +- README | 17 +++-- TODO | 1 - debian/control | 2 +- generate_dvd.cpp | 19 ++++- generate_dvd.hpp | 16 +--- vob_list.cpp | 189 +++++++++++++++++++++++++++++++++++++++++++++++ vob_list.hpp | 27 +++++++ webdvd.cpp | 58 ++++----------- xml_utils.cpp | 35 +++++++++ xml_utils.hpp | 11 +++ 12 files changed, 313 insertions(+), 75 deletions(-) create mode 100644 vob_list.cpp create mode 100644 vob_list.hpp create mode 100644 xml_utils.cpp create mode 100644 xml_utils.hpp diff --git a/INSTALL b/INSTALL index 4f87e47..666e2d1 100644 --- a/INSTALL +++ b/INSTALL @@ -4,9 +4,10 @@ Building WebDVD 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. diff --git a/Makefile b/Makefile index 37018f6..1383056 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,7 @@ CPPFLAGS := -D_REENTRANT # 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 @@ -33,7 +33,7 @@ endif 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) @@ -60,7 +60,7 @@ webdvd.% \ -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.% \ diff --git a/README b/README index e075060..3cd0e12 100644 --- a/README +++ b/README @@ -13,6 +13,7 @@ Requirements 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) @@ -40,18 +41,20 @@ Linking to video 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 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 +and containing 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 diff --git a/TODO b/TODO index 6e74f0e..e60b7b6 100644 --- a/TODO +++ b/TODO @@ -1,5 +1,4 @@ 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). diff --git a/debian/control b/debian/control index 4bd2e13..af7a4dc 100644 --- a/debian/control +++ b/debian/control @@ -2,7 +2,7 @@ Source: webdvd Maintainer: Ben Hutchings 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 diff --git a/generate_dvd.cpp b/generate_dvd.cpp index 6144ef5..eeadc48 100644 --- a/generate_dvd.cpp +++ b/generate_dvd.cpp @@ -8,6 +8,7 @@ #include "dvd.hpp" #include "generate_dvd.hpp" +#include "xml_utils.hpp" dvd_contents::menu::menu() : vob_temp(new temp_file("webdvd-vob-")) @@ -193,8 +194,22 @@ void generate_dvd(const dvd_contents & contents, " \n" " \n" // Record calling location. - "
 g12 = g1; 
\n" - << contents.titles[title_num].vob_list << + "
 g12 = g1; 
\n"; + + for (vob_list::const_iterator it = contents.titles[title_num].begin(), + end = contents.titles[title_num].end(); + it != end; + ++it) + { + file << " 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 diff --git a/generate_dvd.hpp b/generate_dvd.hpp index 8ce8f7b..f77d7da 100644 --- a/generate_dvd.hpp +++ b/generate_dvd.hpp @@ -10,6 +10,7 @@ #include #include "temp_file.hpp" +#include "vob_list.hpp" // Description of menus and titles to go on a DVD. @@ -54,21 +55,8 @@ struct dvd_contents std::vector entries; }; - // Title definition. This is currently just an XML fragment - // consisting of one of more elements. It is included - // directly within the corresponding 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 menus; - std::vector titles; + std::vector<vob_list> titles; }; // Use dvdauthor to generate a DVD filesystem with the given contents. diff --git a/vob_list.cpp b/vob_list.cpp new file mode 100644 index 0000000..086e1a0 --- /dev/null +++ b/vob_list.cpp @@ -0,0 +1,189 @@ +// 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)) +{} diff --git a/vob_list.hpp b/vob_list.hpp new file mode 100644 index 0000000..aaa58b5 --- /dev/null +++ b/vob_list.hpp @@ -0,0 +1,27 @@ +// 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 diff --git a/webdvd.cpp b/webdvd.cpp index 8019007..43f54c7 100644 --- a/webdvd.cpp +++ b/webdvd.cpp @@ -1,4 +1,4 @@ -// 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> @@ -60,6 +60,7 @@ #include "temp_file.hpp" #include "video.hpp" #include "x_frame_buffer.hpp" +#include "xml_utils.hpp" #include "xpcom_support.hpp" using xpcom_support::check; @@ -144,35 +145,6 @@ namespace } - 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: @@ -270,32 +242,30 @@ namespace 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; } } diff --git a/xml_utils.cpp b/xml_utils.cpp new file mode 100644 index 0000000..981feba --- /dev/null +++ b/xml_utils.cpp @@ -0,0 +1,35 @@ +// 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; + } +} diff --git a/xml_utils.hpp b/xml_utils.hpp new file mode 100644 index 0000000..e61e387 --- /dev/null +++ b/xml_utils.hpp @@ -0,0 +1,11 @@ +// 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 -- 2.39.5