X-Git-Url: https://git.decadent.org.uk/gitweb/?a=blobdiff_plain;f=videolink.cpp;h=ef30510149905c0ce7a4f277cbbe68a9c57be934;hb=801ab856156749cab907fb209e81c082c3c990c4;hp=bda78ce113c51bca35894e3db3e14795eb7455b6;hpb=277a4f13736cd3eac53d1301dddc2e0eecba9dba;p=videolink.git diff --git a/videolink.cpp b/videolink.cpp index bda78ce..ef30510 100644 --- a/videolink.cpp +++ b/videolink.cpp @@ -15,8 +15,7 @@ #include -#include - +#include #include #include #include @@ -43,19 +42,10 @@ #include // required before nsILink.h #include #include -#if MOZ_VERSION_MAJOR > 1 || (MOZ_VERSION_MAJOR == 1 && MOZ_VERSION_MINOR >= 8) -# include -#else -# include - typedef nsIPresContext nsPresContext; // ugh -#endif +#include #include #include -#if MOZ_VERSION_MAJOR > 1 || (MOZ_VERSION_MAJOR == 1 && MOZ_VERSION_MINOR >= 8) -# include -#else -# include -#endif +#include #include #include @@ -63,12 +53,14 @@ #include "child_iterator.hpp" #include "dvd.hpp" #include "generate_dvd.hpp" +#include "geometry.hpp" #include "link_iterator.hpp" #include "null_prompt_service.hpp" #include "pixbufs.hpp" #include "style_sheets.hpp" #include "temp_file.hpp" #include "video.hpp" +#include "warp_pointer.hpp" #include "x_frame_buffer.hpp" #include "xml_utils.hpp" #include "xpcom_support.hpp" @@ -77,58 +69,6 @@ using xpcom_support::check; namespace { - // We can try using any of these encoders to convert PNG to MPEG. - enum mpeg_encoder - { - mpeg_encoder_ffmpeg, // ffmpeg - mpeg_encoder_mjpegtools_old, // mjpegtools before version 1.8 - mpeg_encoder_mjpegtools_new // mjpegtools from version 1.8 - }; - - struct rectangle - { - int left, top; // inclusive - int right, bottom; // exclusive - - rectangle operator|=(const rectangle & other) - { - if (other.empty()) - { - // use current extents unchanged - } - else if (empty()) - { - // use other extents - *this = other; - } - else - { - // find rectangle enclosing both extents - left = std::min(left, other.left); - top = std::min(top, other.top); - right = std::max(right, other.right); - bottom = std::max(bottom, other.bottom); - } - - return *this; - } - - rectangle operator&=(const rectangle & other) - { - // find rectangle enclosed in both extents - left = std::max(left, other.left); - top = std::max(top, other.top); - right = std::max(left, std::min(right, other.right)); - bottom = std::max(top, std::min(bottom, other.bottom)); - return *this; - } - - bool empty() const - { - return left == right || bottom == top; - } - }; - rectangle get_elem_rect(nsIDOMNSDocument * ns_doc, nsIDOMElement * elem) { @@ -163,118 +103,208 @@ namespace } - class videolink_window : public Gtk::Window + enum video_format + { + video_format_none, + video_format_mpeg2_ps, + video_format_vob_list + }; + + video_format video_format_from_uri(const std::string & uri) + { + // FIXME: This is a bit of a hack. Perhaps we could decide + // later based on the MIME type determined by Mozilla? + static struct { + const char * extension; + video_format format; + } const mapping[] = { + {".vob", video_format_mpeg2_ps}, + {".mpeg", video_format_mpeg2_ps}, + {".mpeg2", video_format_mpeg2_ps}, + {".voblist", video_format_vob_list} + }; + for (std::size_t i = 0; + i != sizeof(mapping) / sizeof(mapping[0]); + ++i) + { + std::size_t ext_len = std::strlen(mapping[i].extension); + if (uri.size() > ext_len + && uri.compare(uri.size() - ext_len, ext_len, + mapping[i].extension) == 0) + return mapping[i].format; + } + return video_format_none; + } + + + class base_window : public Gtk::Window { public: - videolink_window( - const video::frame_params & frame_params, - const std::string & main_page_uri, - const std::string & output_dir, - mpeg_encoder encoder); + base_window(const video::frame_params & frame_params); + + protected: + video::frame_params frame_params_; + browser_widget browser_widget_; + + private: + bool on_idle(); + virtual void do_late_initialisation() = 0; + }; + + base_window::base_window(const video::frame_params & frame_params) + : frame_params_(frame_params) + { + set_size_request(frame_params_.width, frame_params_.height); + set_resizable(false); + + add(browser_widget_); + browser_widget_.show(); + + Glib::signal_idle().connect( + sigc::mem_fun(this, &base_window::on_idle)); + } + + bool base_window::on_idle() + { + do_late_initialisation(); + return false; // don't call again thankyou + } + + class preview_window : public base_window + { + public: + preview_window(const video::frame_params & frame_params, + const std::string & main_page_uri); + + private: + virtual void do_late_initialisation(); + bool on_key_press(GdkEventKey *); + + std::string main_page_uri_; + }; + + preview_window::preview_window(const video::frame_params & frame_params, + const std::string & main_page_uri) + : base_window(frame_params), + main_page_uri_(main_page_uri) + { + signal_key_press_event().connect( + sigc::mem_fun(this, &preview_window::on_key_press)); + } + + void preview_window::do_late_initialisation() + { + browser_widget_.load_uri(main_page_uri_); + } + + bool preview_window::on_key_press(GdkEventKey * event) + { + switch (event->keyval) + { + case GDK_t: // = top menu + browser_widget_.load_uri(main_page_uri_); + return true; + case GDK_q: // = quit + Gtk::Main::quit(); + return true; + default: + return false; + } + } + + class conversion_window : public base_window + { + public: + conversion_window(const video::frame_params & frame_params, + const std::string & main_page_uri, + const std::string & output_dir, + dvd_generator::mpeg_encoder encoder); bool is_finished() const; private: - dvd_contents::pgc_ref add_menu(const std::string & uri); - dvd_contents::pgc_ref add_title(const std::string & uri); + struct page_state; + + dvd_generator::pgc_ref add_menu(const std::string & uri); + dvd_generator::pgc_ref add_title(const std::string & uri, + video_format format); void load_next_page(); - bool on_idle(); + void do_late_initialisation(); void on_net_state_change(const char * uri, gint flags, guint status); bool browser_is_busy() const { return pending_window_update_ || pending_req_count_; } - bool process_page(); - void save_screenshot(); - void process_links(nsIPresShell * pres_shell, - nsPresContext * pres_context, - nsIDOMWindow * dom_window); + // Do as much processing as possible. Return a flag indicating + // whether to call again once the browser is idle. + bool process(); + // Return a Pixbuf containing a copy of the window contents. + Glib::RefPtr get_screenshot(); + // Do as much processing as possible on the page links. Return + // a flag indicating whether to call again once the browser is + // idle. + bool process_links( + page_state * state, + nsIDOMDocument * basic_doc, + nsIPresShell * pres_shell, + nsPresContext * pres_context, + nsIDOMWindow * dom_window); - video::frame_params frame_params_; std::string output_dir_; - mpeg_encoder encoder_; - browser_widget browser_widget_; - agent_style_sheet_holder style_sheet_; - dvd_contents contents_; - typedef std::map resource_map_type; + dvd_generator generator_; + typedef std::map + resource_map_type; resource_map_type resource_map_; std::queue page_queue_; bool pending_window_update_; int pending_req_count_; - bool have_tweaked_page_; - std::auto_ptr background_temp_; - struct page_state; std::auto_ptr page_state_; bool finished_; }; - videolink_window::videolink_window( + conversion_window::conversion_window( const video::frame_params & frame_params, const std::string & main_page_uri, const std::string & output_dir, - mpeg_encoder encoder) - : frame_params_(frame_params), - output_dir_(output_dir), - encoder_(encoder), - style_sheet_(init_agent_style_sheet( - "file://"VIDEOLINK_SHARE_DIR"/videolink.css")), - pending_window_update_(false), - pending_req_count_(0), - have_tweaked_page_(false), - finished_(false) + dvd_generator::mpeg_encoder encoder) + : base_window(frame_params), + output_dir_(output_dir), + generator_(frame_params, encoder), + pending_window_update_(false), + pending_req_count_(0), + finished_(false) { - set_size_request(frame_params_.width, frame_params_.height); - set_resizable(false); - - add(browser_widget_); - browser_widget_.show(); - Glib::signal_idle().connect( - SigC::slot(*this, &videolink_window::on_idle)); browser_widget_.signal_net_state().connect( - SigC::slot(*this, &videolink_window::on_net_state_change)); + sigc::mem_fun(this, &conversion_window::on_net_state_change)); add_menu(main_page_uri); } - bool videolink_window::is_finished() const + bool conversion_window::is_finished() const { return finished_; } - dvd_contents::pgc_ref videolink_window::add_menu(const std::string & uri) + dvd_generator::pgc_ref conversion_window::add_menu(const std::string & uri) { - dvd_contents::pgc_ref next_menu(dvd_contents::menu_pgc, - contents_.menus.size()); - std::pair insert_result( - resource_map_.insert(std::make_pair(uri, next_menu))); - - if (!insert_result.second) - { - return insert_result.first->second; - } - else + dvd_generator::pgc_ref & pgc_ref = resource_map_[uri]; + if (pgc_ref.type == dvd_generator::unknown_pgc) { + pgc_ref = generator_.add_menu(); page_queue_.push(uri); - contents_.menus.resize(contents_.menus.size() + 1); - return next_menu; } + return pgc_ref; } - dvd_contents::pgc_ref videolink_window::add_title(const std::string & uri) + dvd_generator::pgc_ref conversion_window::add_title(const std::string & uri, + video_format format) { - dvd_contents::pgc_ref next_title(dvd_contents::title_pgc, - contents_.titles.size()); - std::pair insert_result( - resource_map_.insert(std::make_pair(uri, next_title))); + dvd_generator::pgc_ref & pgc_ref = resource_map_[uri]; - if (!insert_result.second) - { - return insert_result.first->second; - } - else + if (pgc_ref.type == dvd_generator::unknown_pgc) { Glib::ustring hostname; std::string path(Glib::filename_from_uri(uri, hostname)); @@ -284,7 +314,7 @@ namespace // Store a reference to a linked VOB file, or the contents // of a linked VOB list file. - if (path.compare(path.size() - 4, 4, ".vob") == 0) + if (format == video_format_mpeg2_ps) { if (!Glib::file_test(path, Glib::FILE_TEST_IS_REGULAR)) throw std::runtime_error( @@ -293,19 +323,22 @@ namespace ref.file = path; list.push_back(ref); } - else + else if (format == video_format_vob_list) { - assert(path.compare(path.size() - 8, 8, ".voblist") == 0); read_vob_list(path).swap(list); } + else + { + assert(!"unrecognised format in add_title"); + } - contents_.titles.resize(contents_.titles.size() + 1); - contents_.titles.back().swap(list); - return next_title; + pgc_ref = generator_.add_title(list); } + + return pgc_ref; } - void videolink_window::load_next_page() + void conversion_window::load_next_page() { assert(!page_queue_.empty()); const std::string & uri = page_queue_.front(); @@ -314,17 +347,21 @@ namespace browser_widget_.load_uri(uri); } - bool videolink_window::on_idle() + void conversion_window::do_late_initialisation() { + // Put pointer in the top-left so that no links appear in + // the hover state when we take a screenshot. + warp_pointer(get_window(), + -frame_params_.width, -frame_params_.height); + load_next_page(); - return false; // don't call again thankyou } - void videolink_window::on_net_state_change(const char * uri, - gint flags, guint status) + void conversion_window::on_net_state_change(const char * uri, + gint flags, guint status) { # ifdef DEBUG_ON_NET_STATE_CHANGE - std::cout << "videolink_window::on_net_state_change("; + std::cout << "conversion_window::on_net_state_change("; if (uri) std::cout << '"' << uri << '"'; else @@ -378,7 +415,6 @@ namespace && flags & GTK_MOZ_EMBED_FLAG_START) { pending_window_update_ = true; - have_tweaked_page_ = false; } if (flags & GTK_MOZ_EMBED_FLAG_IS_WINDOW @@ -396,7 +432,7 @@ namespace { try { - if (!process_page()) + if (!process()) { finished_ = true; Gtk::Main::quit(); @@ -423,7 +459,30 @@ namespace } } - bool videolink_window::process_page() + struct conversion_window::page_state + { + page_state(Glib::RefPtr norm_pixbuf, + nsIDOMDocument * doc, int width, int height) + : norm_pixbuf(norm_pixbuf), + diff_pixbuf(Gdk::Pixbuf::create( + Gdk::COLORSPACE_RGB, + true, 8, // has_alpha, bits_per_sample + width, height)), + links_it(doc), + link_changing(false) + { + } + + Glib::RefPtr norm_pixbuf; + Glib::RefPtr diff_pixbuf; + + link_iterator links_it, links_end; + + rectangle link_rect; + bool link_changing; + }; + + bool conversion_window::process() { assert(!page_queue_.empty()); @@ -437,113 +496,72 @@ namespace nsCOMPtr 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_agent_style_sheet(style_sheet_, pres_shell); - - // This actually only needs to be done once. - nsCOMPtr dom_bar_prop; - check(dom_window->GetScrollbars(getter_AddRefs(dom_bar_prop))); - check(dom_bar_prop->SetVisible(false)); - - have_tweaked_page_ = true; + nsCOMPtr basic_doc; + check(dom_window->GetDocument(getter_AddRefs(basic_doc))); - // Might need to wait a while for things to load or more - // likely for a re-layout. - if (browser_is_busy()) - return true; + // Start or continue processing links. + std::auto_ptr state(page_state_); + if (!state.get()) + state.reset( + new page_state( + get_screenshot(), + basic_doc, frame_params_.width, frame_params_.height)); + if (process_links( + state.get(), + basic_doc, pres_shell, pres_context, dom_window)) + { + // Save iteration state for later. + page_state_ = state; } - - // All further work should only be done if we're not in preview mode. - if (!output_dir_.empty()) + else { - // If we haven't already started work on this menu, 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()) + // We've finished work on the links so generate the + // menu VOB. + quantise_rgba_pixbuf(state->diff_pixbuf, + dvd::button_n_colours); + generator_.generate_menu_vob( + resource_map_[page_queue_.front()].index, + state->norm_pixbuf, state->diff_pixbuf); + + // Move on to the next page, if any, or else generate + // the DVD filesystem. + page_queue_.pop(); + if (!page_queue_.empty()) { - page_queue_.pop(); - if (page_queue_.empty()) - { - generate_dvd(contents_, output_dir_); - return false; - } - else - { - load_next_page(); - } + load_next_page(); + } + else + { + generator_.generate(output_dir_); + return false; } } return true; } - void videolink_window::save_screenshot() + Glib::RefPtr conversion_window::get_screenshot() { Glib::RefPtr window(get_window()); assert(window); window->process_updates(true); - background_temp_.reset(new temp_file("videolink-back-")); - background_temp_->close(); - std::cout << "saving " << background_temp_->get_name() << std::endl; - Gdk::Pixbuf::create(Glib::RefPtr(window), - window->get_colormap(), - 0, 0, 0, 0, - frame_params_.width, frame_params_.height) - ->save(background_temp_->get_name(), "png"); + return Gdk::Pixbuf::create(Glib::RefPtr(window), + window->get_colormap(), + 0, 0, 0, 0, + frame_params_.width, frame_params_.height); } - struct videolink_window::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("videolink-spumux-"), - links_temp("videolink-links-"), - link_num(0), - links_it(doc), - link_changing(false) - { - spumux_temp.close(); - links_temp.close(); - } - - Glib::RefPtr diff_pixbuf; - - temp_file spumux_temp; - std::ofstream spumux_file; - - temp_file links_temp; - - unsigned link_num; - link_iterator links_it, links_end; - - rectangle link_rect; - bool link_changing; - Glib::RefPtr norm_pixbuf; - }; - - void videolink_window::process_links(nsIPresShell * pres_shell, - nsPresContext * pres_context, - nsIDOMWindow * dom_window) + bool conversion_window::process_links( + page_state * state, + nsIDOMDocument * basic_doc, + nsIPresShell * pres_shell, + nsPresContext * pres_context, + nsIDOMWindow * dom_window) { Glib::RefPtr window(get_window()); assert(window); - nsCOMPtr basic_doc; - check(dom_window->GetDocument(getter_AddRefs(basic_doc))); nsCOMPtr ns_doc(do_QueryInterface(basic_doc)); assert(ns_doc); nsCOMPtr event_state_man( @@ -557,28 +575,11 @@ namespace nsCOMPtr view; check(doc_view->GetDefaultView(getter_AddRefs(view))); - // Set up or recover our iteration state. - std::auto_ptr state(page_state_); - if (!state.get()) - { - 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 << - "\n" - " \n" - " \n"; - } - rectangle window_rect = { 0, 0, frame_params_.width, frame_params_.height }; - unsigned menu_num = resource_map_[page_queue_.front()].index; + unsigned menu_index = resource_map_[page_queue_.front()].index; for (/* no initialisation */; state->links_it != state->links_end; @@ -622,34 +623,12 @@ namespace continue; } - ++state->link_num; - - if (state->link_num >= unsigned(dvd::menu_buttons_max)) - { - if (state->link_num == unsigned(dvd::menu_buttons_max)) - std::cerr << "No more than " << dvd::menu_buttons_max - << " buttons can be placed on a menu\n"; - std::cerr << "Ignoring link to " << uri_and_fragment - << "\n"; - continue; - } - - state->spumux_file << - "