From f5d9069647f70b7aab8e656f59cf42176c419461 Mon Sep 17 00:00:00 2001 From: Ben Hutchings Date: Sat, 14 Oct 2006 01:03:15 +0000 Subject: [PATCH] Moved generation of menu VOBs from videolink_window to dvd_generator. Fixed operation of "top menu" or "title menu" button. Added explicit aspect ratio to menu streams encoded with ffmpeg, since some players will default to 16:9 otherwise. Fixed off-by-one error in link coordinates passed to spumux. Changed default padding for the body element to 10% of frame dimensions, a common rule for the title-safe area. Added explicit assignment of actions to direction buttons since dvdauthor's policy doesn't seem to work that well. Documented limit of 119 menus discovered by Mark Burton. Fixed an occasional problem of links appearing in their hover state initially due to the pointer being in the middle of the virtual framebuffer. --- ChangeLog | 17 ++ Makefile | 10 +- README | 20 +- TODO | 7 +- debian/changelog | 6 + generate_dvd.cpp | 313 ++++++++++++++++++++++++++----- generate_dvd.hpp | 66 ++++++- geometry.hpp | 51 ++++++ ntsc.css | 4 + pal.css | 4 + pixbufs.cpp | 16 +- pixbufs.hpp | 8 +- video.hpp | 2 +- videolink.cpp | 469 ++++++++++++++++++----------------------------- videolink.css | 2 - warp_pointer.cpp | 16 ++ warp_pointer.hpp | 10 + 17 files changed, 648 insertions(+), 373 deletions(-) create mode 100644 geometry.hpp create mode 100644 ntsc.css create mode 100644 pal.css create mode 100644 warp_pointer.cpp create mode 100644 warp_pointer.hpp diff --git a/ChangeLog b/ChangeLog index 8482890..2ad2df2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,20 @@ +videolink (0.11) unstable; urgency=low + + * Fixed operation of "top menu" or "title menu" button + * Added explicit aspect ratio to menu streams encoded with ffmpeg, + since some players will default to 16:9 otherwise + * Fixed off-by-one error in link coordinates passed to spumux + * Changed default padding for the body element to 10% of frame + dimensions, a common rule for the title-safe area + * Added explicit assignment of actions to direction buttons + since dvdauthor's policy doesn't seem to work that well + * Documented limit of 119 menus discovered by Mark Burton + * Fixed an occasional problem of links appearing in their hover state + initially due to the pointer being in the middle of the virtual + framebuffer + + -- Ben Hutchings Mon, 25 Sep 2006 18:11:07 +0100 + videolink (0.10) unstable; urgency=low * Changed style-sheet application in Mozila/XULRunner 1.8 to override diff --git a/Makefile b/Makefile index e001b0a..02a8030 100644 --- a/Makefile +++ b/Makefile @@ -41,8 +41,8 @@ endif cxxsources := \ auto_proc.cpp browser_widget.cpp child_iterator.cpp generate_dvd.cpp \ link_iterator.cpp null_prompt_service.cpp pixbufs.cpp style_sheets.cpp \ - temp_file.cpp video.cpp vob_list.cpp videolink.cpp x_frame_buffer.cpp \ - xml_utils.cpp xpcom_support.cpp + temp_file.cpp video.cpp vob_list.cpp videolink.cpp warp_pointer.cpp \ + x_frame_buffer.cpp xml_utils.cpp xpcom_support.cpp csources := jquant2.c videolink : $(cxxsources:%.cpp=.objs/%.o) $(csources:%.c=.objs/%.o) @@ -63,7 +63,7 @@ install : gzip -c9 videolink.1 >$(DESTDIR)$(mandir)/man1/videolink.1.gz chmod 644 $(DESTDIR)$(mandir)/man1/videolink.1.gz mkdir -p -m 755 $(DESTDIR)$(sharedir)/videolink - install -m 644 videolink.css $(DESTDIR)$(sharedir)/videolink + install -m 644 *.css $(DESTDIR)$(sharedir)/videolink .PHONY : clean install @@ -72,8 +72,8 @@ install : .objs/videolink.% \ : CPPFLAGS += -DVIDEOLINK_SHARE_DIR='"$(sharedir)/videolink"' -.objs/browser_widget.% .objs/generate_dvd.% .objs/pixbufs.% \ -.objs/temp_file.% .objs/vob_list.% .objs/videolink.% \ +.objs/browser_widget.% .objs/generate_dvd.% .objs/pixbufs.% \ +.objs/temp_file.% .objs/vob_list.% .objs/videolink.% .objs/warp_pointer.% \ : CPPFLAGS += $(shell pkg-config --cflags gtkmm-2.0) .objs/browser_widget.% .objs/child_iterator.% .objs/link_iterator.% \ diff --git a/README b/README index c273e43..e51a3e6 100644 --- a/README +++ b/README @@ -42,10 +42,10 @@ below. 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 and link to a VOB-list file (explained below) -whose name ends in ".voblist". +".mpeg", ".mpeg2" or ".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 and link to a VOB-list file +(explained below) whose name ends in ".voblist". VOB-lists @@ -135,9 +135,9 @@ multiple menus. The frame size is dictated by the video standard; see above. The exact visible area varies between TVs so the background should cover all or very nearly all the frame whereas the important content such as text must not be placed near the edge. For this -reason VideoLink applies a stylesheet to all pages that adds 60 pixels of -padding on all sides of the body; this doesn't apply to the -background. +reason VideoLink applies a stylesheet to all pages that adds padding +equal to 10% of the frame dimension on each side of the body; this +doesn't apply to the background. Prior to version 1.8, Mozilla may signal that a page is completely loaded before any background images are loaded and displayed. This @@ -176,6 +176,12 @@ aspect ratio and sample rate. However, each chapter will run into the next. If this is a real problem, let me know, and I may be able to provide a better solution in a later version of VideoLink. +There is a limit of 128 menus in each "domain" of a DVD, which is +further reduced by dvdauthor to a practical limit of 119 menus. +Currently VideoLink generates all menus in a single domain (the VMGM +domain). Later versions of VideoLink should support larger numbers of +menus by using multiple domains. + Bugs ---- diff --git a/TODO b/TODO index 41c7770..18c4fb9 100644 --- a/TODO +++ b/TODO @@ -4,13 +4,14 @@ Priority 2 Use ffmpeg to convert unsuitable video files (how do we check this?). Add keyboard command for "top menu" in preview mode. Add support for videos in preview mode. -Support more than 99 videos somehow (grouping them into titlesets won't help) -Allow explicit ordering of videos. +Support more than 99 titles somehow (grouping them into titlesets won't help) +Support more than 119 menus somehow. +Track down and fix/suppress the NS_BINDING_ABORTED (0x804b002) error that occasionally appears in preview mode. Priority 3 (lowest) Provide a means to specify menu & audio language. -May need to override default menu navigation actions because dvdauthor seems to cause rather undesirable behaviour when buttons are not arranged in a grid. For any page with no links, add a "back" link (this raises the issue of localisation, of course). If a page is too large, split it and add "next" and "previous" links (seems very difficult). Set Mozilla's idea of the pixel aspect ratio to match video pixels. (For extra credit, scale preview mode to correct for the differing aspect ratio.) This seems to be impossible. Avoid running Xvfb if we're only going to display an error message. +Allow explicit ordering of titles. diff --git a/debian/changelog b/debian/changelog index e840519..f1e7959 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +videolink (0.11-1) unstable; urgency=low + + * New upstream version + + -- Ben Hutchings Tue, 26 Sep 2006 02:21:15 +0100 + videolink (0.10-1) unstable; urgency=low * New upstream version - closes: #386832 diff --git a/generate_dvd.cpp b/generate_dvd.cpp index 32ce0f2..ff1b635 100644 --- a/generate_dvd.cpp +++ b/generate_dvd.cpp @@ -2,22 +2,241 @@ // See the file "COPYING" for licence details. #include +#include +#include #include +#include #include #include "dvd.hpp" #include "generate_dvd.hpp" #include "xml_utils.hpp" -dvd_contents::menu::menu() +namespace +{ + // Return a closeness metric of an "end" rectangle to a "start" + // rectangle in the upward (-1) or downward (+1) direction. Given + // several possible "end" rectangles, the one that seems visually + // closest in the given direction should have the highest value of + // this metric. This is necessarily a heuristic function! + double directed_closeness(const rectangle & start, const rectangle & end, + int y_dir) + { + // The obvious approach is to use the centres of the + // rectangles. However, for the "end" rectangle, using the + // horizontal position nearest the centre of the "start" + // rectangle seems to produce more reasonable results. For + // example, if there are two "end" rectangles equally near to + // the "start" rectangle in terms of vertical distance and one + // of them horizontally overlaps the centre of the "start" + // rectangle, we want to pick that one even if the centre of + // that rectangle is further away from the centre of the + // "start" rectangle. + int start_x = (start.left + start.right) / 2; + int start_y = (start.top + start.bottom) / 2; + int end_y = (end.top + end.bottom) / 2; + int end_x; + if (end.right < start_x) + end_x = end.right; + else if (end.left > start_x) + end_x = end.left; + else + end_x = start_x; + + // Return cosine of angle between the line between these points + // and the vertical, divided by the distance between the points + // if that is defined and positive; otherwise return 0. + int vertical_distance = (end_y - start_y) * y_dir; + if (vertical_distance <= 0) + return 0.0; + double distance_squared = + (end_x - start_x) * (end_x - start_x) + + (end_y - start_y) * (end_y - start_y); + return vertical_distance / distance_squared; + } +} + +dvd_generator::menu::menu() : vob_temp(new temp_file("videolink-vob-")) { vob_temp->close(); } -void generate_dvd(const dvd_contents & contents, - const std::string & output_dir) +dvd_generator::pgc_ref dvd_generator::add_menu() +{ + pgc_ref next_menu(menu_pgc, menus_.size()); + + // Check against maximum number of menus. It appears that no more + // than 128 menus are reachable through LinkPGCN instructions, and + // dvdauthor uses some menu numbers for special purposes, resulting + // in a practical limit of 119 per domain. We can work around this + // later by spreading some menus across titlesets. + if (next_menu.index == 119) + throw std::runtime_error("No more than 119 menus can be used"); + + menus_.resize(next_menu.index + 1); + return next_menu; +} + +void dvd_generator::add_menu_entry(unsigned index, + const rectangle & area, + const pgc_ref & target) +{ + assert(index < menus_.size()); + assert(target.type == menu_pgc && target.index < menus_.size() + || target.type == title_pgc && target.index < titles_.size()); + menu_entry new_entry = { area, target }; + menus_[index].entries.push_back(new_entry); +} + +void dvd_generator::generate_menu_vob(unsigned index, + Glib::RefPtr background, + Glib::RefPtr highlights) + const +{ + assert(index < menus_.size()); + const menu & this_menu = menus_[index]; + + temp_file background_temp("videolink-back-"); + background_temp.close(); + std::cout << "saving " << background_temp.get_name() << std::endl; + background->save(background_temp.get_name(), "png"); + + temp_file highlights_temp("videolink-links-"); + highlights_temp.close(); + std::cout << "saving " << highlights_temp.get_name() << std::endl; + highlights->save(highlights_temp.get_name(), "png"); + + temp_file spumux_temp("videolink-spumux-"); + spumux_temp.close(); + std::ofstream spumux_file(spumux_temp.get_name().c_str()); + spumux_file << + "\n" + " \n" + " \n"; + int button_count = this_menu.entries.size(); + for (int i = 0; i != button_count; ++i) + { + const menu_entry & this_entry = this_menu.entries[i]; + + // We program left and right to cycle through the buttons in + // the order the entries were added. This should result in + // left and right behaving like the tab and shift-tab keys + // would in the browser. Hopefully that's a sensible order. + // We program up and down to behave geometrically. + int up_button = i, down_button = i; + double up_closeness = 0.0, down_closeness = 0.0; + for (int j = 0; j != button_count; ++j) + { + const menu_entry & other_entry = this_menu.entries[j]; + double closeness = directed_closeness( + this_entry.area, other_entry.area, -1); + if (closeness > up_closeness) + { + up_button = j; + up_closeness = closeness; + } + else + { + closeness = directed_closeness( + this_entry.area, other_entry.area, 1); + if (closeness > down_closeness) + { + down_button = j; + down_closeness = closeness; + } + } + } + spumux_file << " \n"; + } + spumux_file << + " \n" + " \n" + "\n"; + spumux_file.close(); + if (!spumux_file) + throw std::runtime_error("Failed to write control file for spumux"); + + std::ostringstream command_stream; + if (encoder_ == mpeg_encoder_ffmpeg) + { + command_stream + << "ffmpeg" + << " -f image2 -vcodec png -i " + << background_temp.get_name() + << " -target " << frame_params_.common_name << "-dvd" + << " -vcodec mpeg2video -aspect 4:3 -an -y /dev/stdout"; + } + else + { + assert(encoder_ == mpeg_encoder_mjpegtools_old + || encoder_ == mpeg_encoder_mjpegtools_new); + 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 "; + // The chroma subsampling keywords changed between + // versions 1.6.2 and 1.8 of mjpegtools. There is no + // keyword that works with both. + if (encoder_ == mpeg_encoder_mjpegtools_old) + command_stream << "-S420_mpeg2"; + else + command_stream << "-S420mpeg2"; + command_stream << + " | mpeg2enc -v0 -f8 -a2 -o/dev/stdout" + " | mplex -v0 -f8 -o/dev/stdout /dev/stdin"; + } + command_stream + << " | spumux -v0 -mdvd " << spumux_temp.get_name() + << " > " << this_menu.vob_temp->get_name(); + std::string command(command_stream.str()); + const char * argv[] = { + "/bin/sh", "-c", command.c_str(), 0 + }; + std::cout << "running " << command << std::endl; + int command_result; + Glib::spawn_sync(".", + Glib::ArrayHandle( + argv, sizeof(argv)/sizeof(argv[0]), + Glib::OWNERSHIP_NONE), + Glib::SPAWN_STDOUT_TO_DEV_NULL, + SigC::Slot0(), + 0, 0, + &command_result); + if (command_result != 0) + throw std::runtime_error("spumux pipeline failed"); +} + +dvd_generator::pgc_ref dvd_generator::add_title(vob_list & content) +{ + pgc_ref next_title(title_pgc, titles_.size()); + + // Check against maximum number of titles. + if (next_title.index == 99) + throw std::runtime_error("No more than 99 titles can be used"); + + titles_.resize(next_title.index + 1); + titles_[next_title.index].swap(content); + return next_title; +} + +void dvd_generator::generate(const std::string & output_dir) const { temp_file temp("videolink-dvdauthor-"); temp.close(); @@ -26,8 +245,8 @@ void generate_dvd(const dvd_contents & contents, // We generate code that uses registers in the following way: // // g0: scratch - // g1: current location - // g2: location that last jumped to a title + // g1: target menu location + // g2: source/return menu location for title // g3: target chapter number // // All locations are divided into two bitfields: the least @@ -45,11 +264,9 @@ void generate_dvd(const dvd_contents & contents, " \n" " \n"; - for (unsigned menu_index = 0; - menu_index != contents.menus.size(); - ++menu_index) + for (unsigned menu_index = 0; menu_index != menus_.size(); ++menu_index) { - const dvd_contents::menu & menu = contents.menus[menu_index]; + const menu & this_menu = menus_[menu_index]; if (menu_index == 0) { @@ -58,8 +275,9 @@ void generate_dvd(const dvd_contents & contents, file << " \n" "
\n"
-		// Initialise the current location if it is not set
-		// (all general registers are initially 0).
+		// Set a default target location if none is set.
+		// This covers first play and use of the "top menu"
+		// button.
 		"          if (g1 eq 0)\n"
 		"            g1 = " << 1 + button_mult << ";\n";
 	}
@@ -70,7 +288,7 @@ void generate_dvd(const dvd_contents & contents,
 		"        
\n";
 	}
 
-	// When a title finishes or the user presses the menu
+	// When a title finishes or the user presses the "menu"
 	// button, this always jumps to the titleset's root menu.
 	// We want to return the user to the last menu they used.
 	// So we arrange for each titleset's root menu to return
@@ -95,7 +313,7 @@ void generate_dvd(const dvd_contents & contents,
 	     menu_incr != 0;
 	     menu_incr /= 2)
 	{
-	    if (menu_index + menu_incr < contents.menus.size()
+	    if (menu_index + menu_incr < menus_.size()
 		&& (menu_index & (menu_incr * 2 - 1)) == 0)
 	    {
 		file <<
@@ -109,19 +327,22 @@ void generate_dvd(const dvd_contents & contents,
 	file <<
 	    // Highlight the appropriate button.
 	    "          s8 = g1 & " << button_mask << ";\n"
+	    // Forget the link target.  If we don't do this, pressing
+	    // the "top menu" button will result in jumping back to
+	    // this same menu!
+	    "          g1 = 0;\n"
 	    "        
\n" - " \n"; + " \n"; for (unsigned button_index = 0; - button_index != menu.entries.size(); + button_index != this_menu.entries.size(); ++button_index) { - const dvd_contents::pgc_ref & target = - menu.entries[button_index]; + const pgc_ref & target = this_menu.entries[button_index].target; file << "