]> git.decadent.org.uk Git - videolink.git/blobdiff - webdvd.cpp
Added support for "VOB lists" (files containing <vob> elements to be inserted into...
[videolink.git] / webdvd.cpp
index 113198c6a27ecdf4aae4db906ed6bcb8734be907..cf71bb61843abc64a455ed3db46fd045a036ad16 100644 (file)
@@ -2,6 +2,7 @@
 // See the file "COPYING" for licence details.
 
 #include <cassert>
+#include <cstring>
 #include <exception>
 #include <fstream>
 #include <iomanip>
@@ -13,7 +14,6 @@
 #include <string>
 
 #include <stdlib.h>
-#include <unistd.h>
 
 #include <boost/shared_ptr.hpp>
 
@@ -29,6 +29,7 @@
 #include <nsIContent.h>
 #include <nsIDocShell.h>
 #include <nsIDOMAbstractView.h>
+#include <nsIDOMBarProp.h>
 #include <nsIDOMDocumentEvent.h>
 #include <nsIDOMDocumentView.h>
 #include <nsIDOMElement.h>
@@ -110,6 +111,7 @@ namespace
     {
        rectangle result;
 
+       // Start with this element's bounding box
        nsCOMPtr<nsIBoxObject> box;
        check(ns_doc->GetBoxObjectFor(elem, getter_AddRefs(box)));
        int width, height;
@@ -120,6 +122,7 @@ namespace
        result.right = result.left + width;
        result.bottom = result.top + height;
 
+       // Merge bounding boxes of all child elements
        for (ChildIterator it = ChildIterator(elem), end; it != end; ++it)
        {
            nsCOMPtr<nsIDOMNode> child_node(*it);
@@ -149,6 +152,7 @@ namespace
        void add_video(const std::string & uri);
        void load_next_page();
        void on_net_state_change(const char * uri, gint flags, guint status);
+       bool process_page();
        void save_screenshot();
        void process_links(nsIPresShell * pres_shell,
                           nsIPresContext * pres_context,
@@ -167,9 +171,10 @@ namespace
        std::vector<std::string> video_paths_;
        bool pending_window_update_;
        int pending_req_count_;
+       bool have_tweaked_page_;
        std::auto_ptr<temp_file> background_temp_;
-       struct link_state;
-       std::auto_ptr<link_state> link_state_;
+       struct page_state;
+       std::auto_ptr<page_state> page_state_;
        std::vector<boost::shared_ptr<temp_file> > page_temp_files_;
     };
 
@@ -181,9 +186,12 @@ namespace
              output_dir_(output_dir),
              stylesheet_(load_css("file://" WEBDVD_LIB_DIR "/webdvd.css")),
              pending_window_update_(false),
-             pending_req_count_(0)
+             pending_req_count_(0),
+             have_tweaked_page_(false)
     {
-       set_default_size(frame_params_.width, frame_params_.height);
+       set_size_request(frame_params_.width, frame_params_.height);
+       set_resizable(false);
+
        add(browser_widget_);
        browser_widget_.show();
        browser_widget_.signal_net_state().connect(
@@ -210,11 +218,13 @@ namespace
                                                  video_paths_.size() + 1)))
            .second)
        {
-           // FIXME: Should accept some slightly different URI prefixes
-           // (e.g. file://localhost/) and decode any URI-escaped
-           // characters in the path.
-           assert(uri.compare(0, 8, "file:///") == 0);
-           video_paths_.push_back(uri.substr(7));
+           Glib::ustring hostname;
+           std::string filename(Glib::filename_from_uri(uri, hostname));
+           // FIXME: Should check the hostname
+           if (!Glib::file_test(filename, Glib::FILE_TEST_IS_REGULAR))
+               throw std::runtime_error(
+                   filename + " is missing or not a regular file");
+           video_paths_.push_back(filename);
        }
     }
 
@@ -228,7 +238,6 @@ namespace
        resource_map_[uri].second = ++page_count;
        page_links_.resize(page_count);
 
-       pending_window_update_ = true;
        browser_widget_.load_uri(uri);
     }
 
@@ -247,62 +256,30 @@ namespace
            }
        }
            
-       if (flags & GTK_MOZ_EMBED_FLAG_STOP
-           && flags & GTK_MOZ_EMBED_FLAG_IS_WINDOW)
+       if (flags & GTK_MOZ_EMBED_FLAG_IS_DOCUMENT
+           && flags & GTK_MOZ_EMBED_FLAG_START)
+       {
+           pending_window_update_ = true;
+           have_tweaked_page_ = false;
+       }
+
+       if (flags & GTK_MOZ_EMBED_FLAG_IS_WINDOW
+           && flags & GTK_MOZ_EMBED_FLAG_STOP)
+       {
+           // Check whether the load was successful, ignoring this
+           // pseudo-error.
+           if (status != NS_IMAGELIB_ERROR_LOAD_ABORTED)
+               check(status);
+
            pending_window_update_ = false;
+       }
 
        if (pending_req_count_ == 0 && !pending_window_update_)
        {
-           assert(!page_queue_.empty());
-
            try
            {
-               // Check whether the load was successful, ignoring this
-               // pseudo-error.
-               if (status != NS_IMAGELIB_ERROR_LOAD_ABORTED)
-                   check(status);
-
-               nsCOMPtr<nsIWebBrowser> browser(
-                   browser_widget_.get_browser());
-               nsCOMPtr<nsIDocShell> doc_shell(do_GetInterface(browser));
-               assert(doc_shell);
-               nsCOMPtr<nsIPresShell> pres_shell;
-               check(doc_shell->GetPresShell(getter_AddRefs(pres_shell)));
-               nsCOMPtr<nsIPresContext> pres_context;
-               check(doc_shell->GetPresContext(
-                         getter_AddRefs(pres_context)));
-               nsCOMPtr<nsIDOMWindow> dom_window;
-               check(browser->GetContentDOMWindow(
-                         getter_AddRefs(dom_window)));
-
-               if (output_dir_.empty())
-               {
-                   apply_style_sheet(stylesheet_, pres_shell);
-               }
-               else
-               {
-                   if (!link_state_.get())
-                   {
-                       apply_style_sheet(stylesheet_, pres_shell);
-                       save_screenshot();
-                   }
-
-                   process_links(pres_shell, pres_context, dom_window);
-
-                   if (!link_state_.get())
-                   {
-                       page_queue_.pop();
-                       if (page_queue_.empty())
-                       {
-                           generate_dvd();
-                           Gtk::Main::quit();
-                       }
-                       else
-                       {
-                           load_next_page();
-                       }
-                   }
-               }
+               if (!process_page())
+                   Gtk::Main::quit();
            }
            catch (std::exception & e)
            {
@@ -316,6 +293,70 @@ namespace
        }
     }
 
+    bool WebDvdWindow::process_page()
+    {
+       assert(!page_queue_.empty());
+
+       nsCOMPtr<nsIWebBrowser> browser(browser_widget_.get_browser());
+       nsCOMPtr<nsIDocShell> doc_shell(do_GetInterface(browser));
+       assert(doc_shell);
+       nsCOMPtr<nsIPresShell> pres_shell;
+       check(doc_shell->GetPresShell(getter_AddRefs(pres_shell)));
+       nsCOMPtr<nsIPresContext> pres_context;
+       check(doc_shell->GetPresContext(getter_AddRefs(pres_context)));
+       nsCOMPtr<nsIDOMWindow> dom_window;
+       check(browser->GetContentDOMWindow(getter_AddRefs(dom_window)));
+
+       // If we haven't done so already, apply the stylesheet and
+       // disable scrollbars.
+       if (!have_tweaked_page_)
+       {
+           apply_style_sheet(stylesheet_, pres_shell);
+
+           // This actually only needs to be done once.
+           nsCOMPtr<nsIDOMBarProp> dom_bar_prop;
+           check(dom_window->GetScrollbars(getter_AddRefs(dom_bar_prop)));
+           check(dom_bar_prop->SetVisible(false));
+
+           have_tweaked_page_ = true;
+
+           // Might need to wait a while for things to load or more
+           // likely for a re-layout.
+           if (pending_req_count_ > 0)
+               return true;
+       }
+
+       // All further work should only be done if we're not in preview mode.
+       if (!output_dir_.empty())
+       {
+           // If we haven't already started work on this page, save a
+           // screenshot of its normal appearance.
+           if (!page_state_.get())
+               save_screenshot();
+
+           // Start or continue processing links.
+           process_links(pres_shell, pres_context, dom_window);
+
+           // If we've finished work on the links, move on to the
+           // next page, if any, or else generate the DVD filesystem.
+           if (!page_state_.get())
+           {
+               page_queue_.pop();
+               if (page_queue_.empty())
+               {
+                   generate_dvd();
+                   return false;
+               }
+               else
+               {
+                   load_next_page();
+               }
+           }
+       }
+
+       return true;
+    }
+
     void WebDvdWindow::save_screenshot()
     {
        Glib::RefPtr<Gdk::Window> window(get_window());
@@ -332,14 +373,29 @@ namespace
            ->save(background_temp_->get_name(), "png");
     }
 
-    struct WebDvdWindow::link_state
+    struct WebDvdWindow::page_state
     {
+       page_state(nsIDOMDocument * doc, int width, int height)
+               : diff_pixbuf(Gdk::Pixbuf::create(
+                                 Gdk::COLORSPACE_RGB,
+                                 true, 8, // has_alpha, bits_per_sample
+                                 width, height)),
+                 spumux_temp("webdvd-spumux-"),
+                 links_temp("webdvd-links-"),
+                 link_num(0),
+                 links_it(doc),
+                 link_changing(false)
+           {
+               spumux_temp.close();
+               links_temp.close();
+           }
+
        Glib::RefPtr<Gdk::Pixbuf> diff_pixbuf;
 
-       std::auto_ptr<temp_file> spumux_temp;
+       temp_file spumux_temp;
        std::ofstream spumux_file;
 
-       std::auto_ptr<temp_file> links_temp;
+       temp_file links_temp;
 
        int link_num;
        LinkIterator links_it, links_end;
@@ -372,37 +428,22 @@ namespace
        check(doc_view->GetDefaultView(getter_AddRefs(view)));
 
        // Set up or recover our iteration state.
-       std::auto_ptr<link_state> state(link_state_);
+       std::auto_ptr<page_state> state(page_state_);
        if (!state.get())
        {
-           state.reset(new link_state);
-
-           state->diff_pixbuf = Gdk::Pixbuf::create(
-               Gdk::COLORSPACE_RGB,
-               true, 8, // has_alpha, bits_per_sample
-               frame_params_.width, frame_params_.height);
-
-           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.reset(
+               new page_state(
+                   basic_doc, frame_params_.width, frame_params_.height));
            
-           state->spumux_file.open(state->spumux_temp->get_name().c_str());
+           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='" << state->links_temp->get_name()
-                              << "'\n"
-               "        select='" << state->links_temp->get_name()
-                              << "'>\n";
-
-           state->link_num = 0;
-           state->links_it = LinkIterator(basic_doc);
-           state->link_changing = false;
+               "        highlight='" << state->links_temp.get_name() << "'\n"
+               "        select='" << state->links_temp.get_name() << "'>\n";
        }
-           
+
        rectangle window_rect = {
            0, 0, frame_params_.width, frame_params_.height
        };
@@ -461,8 +502,11 @@ namespace
                check(uri->GetPath(path));
                // FIXME: This is a bit of a hack.  Perhaps we could decide
                // later based on the MIME type determined by Mozilla?
-               if (path.Length() > 4
-                   && std::strcmp(path.EndReading() - 4, ".vob") == 0)
+               if ((path.Length() > 4
+                    && std::strcmp(path.EndReading() - 4, ".vob") == 0)
+                   || (path.Length() > 8
+                       && std::strcmp(path.EndReading() - 8, ".voblist")
+                          == 0))
                {
                    PRBool is_file;
                    check(uri->SchemeIs("file", &is_file));
@@ -529,7 +573,7 @@ namespace
                if (pending_req_count_ > 0)
                {
                    state->link_changing = true;
-                   link_state_ = state;
+                   page_state_ = state;
                    return;
                }
            }
@@ -567,9 +611,9 @@ namespace
 
        quantise_rgba_pixbuf(state->diff_pixbuf, dvd::button_n_colours);
 
-       std::cout << "saving " << state->links_temp->get_name()
+       std::cout << "saving " << state->links_temp.get_name()
                  << std::endl;
-       state->diff_pixbuf->save(state->links_temp->get_name(), "png");
+       state->diff_pixbuf->save(state->links_temp.get_name(), "png");
 
        state->spumux_file <<
            "    </spu>\n"
@@ -595,7 +639,7 @@ namespace
                               " | mpeg2enc -v0 -f8 -a2 -o/dev/stdout"
                               " | mplex -v0 -f8 -o/dev/stdout /dev/stdin"
                               " | spumux -v0 -mdvd ")
-                          << state->spumux_temp->get_name()
+                          << state->spumux_temp.get_name()
                           << " > " << vob_temp->get_name();
            std::string command(command_stream.str());
            const char * argv[] = {
@@ -769,10 +813,25 @@ namespace
                "    <titles>\n"
                "      <pgc>\n"
                // Record calling page/menu.
-               "        <pre> g12 = g1; </pre>\n"
+               "        <pre> g12 = g1; </pre>\n";
+
+           // Write a reference to a linked VOB file, or the contents
+           // of a linked VOB list file.
+           const std::string & video_path = video_paths_[video_num - 1];
+           if (video_path.compare(video_path.size() - 4, 4, ".vob") == 0)
+           {
                // FIXME: Should XML-escape the path
-               "        <vob file='" << video_paths_[video_num - 1]
-                << "'/>\n"
+               file << "        <vob file='" << video_path << "'/>\n";
+           }
+           else
+           {
+               assert(video_path.compare(video_path.size() - 8, 8,
+                                         ".voblist") == 0);
+               // TODO: Validate the file contents;
+               file << Glib::file_get_contents(video_path);
+           }
+
+           file <<
                // If page/menu location has not been changed during the
                // video, change the location to be the following
                // link/button when returning to it.  In any case,
@@ -885,8 +944,10 @@ int main(int argc, char ** argv)
     {
        video::frame_params frame_params = video::pal_params;
        bool preview_mode = false;
+       std::string menu_url;
+       std::string output_dir;
 
-       // Do initial argument parsing.  We have to do this before
+       // Do initial option parsing.  We have to do this before
        // letting Gtk parse the arguments since we may need to spawn
        // Xvfb first.
        int argi = 1;
@@ -938,7 +999,7 @@ int main(int argc, char ** argv)
        // Initialise Gtk
        Gtk::Main kit(argc, argv);
 
-       // Complete argument parsing with Gtk's options out of the way.
+       // Complete option parsing with Gtk's options out of the way.
        argi = 1;
        while (argi != argc)
        {
@@ -966,18 +1027,35 @@ int main(int argc, char ** argv)
                break;
            }
        }
-       if (argc - argi != (preview_mode ? 1 : 2))
+
+       // Look for a starting URL or filename and (except in preview
+       // mode) an output directory after the options.
+        if (argc - argi != (preview_mode ? 1 : 2))
        {
            print_usage(std::cerr, argv[0]);
            return EXIT_FAILURE;
        }
+       if (std::strstr(argv[argi], "://"))
+       {
+           // It appears to be an absolute URL, so use it as-is.
+           menu_url = argv[argi];
+       }
+       else
+       {
+           // Assume it's a filename.  Resolve it to an absolute URL.
+           std::string path(argv[argi]);
+           if (!Glib::path_is_absolute(path))
+               path = Glib::build_filename(Glib::get_current_dir(), path);
+           menu_url = Glib::filename_to_uri(path);             
+       }
+       if (!preview_mode)
+           output_dir = argv[argi + 1];
 
        // Initialise Mozilla
        BrowserWidget::init();
 
-       WebDvdWindow window(frame_params,
-                           argv[argi],
-                           preview_mode ? "" : argv[argi + 1]);
+       // Run the browser/converter
+       WebDvdWindow window(frame_params, menu_url, output_dir);
        Gtk::Main::run(window);
     }
     catch (std::exception & e)