]> git.decadent.org.uk Git - videolink.git/blobdiff - generate_dvd.cpp
Corrected formula in menu_duration_seconds. It looks like any sufficiently large...
[videolink.git] / generate_dvd.cpp
index aa57775e22639bdce088710cf24fc542cfd9315e..5d357e6d39c385fe6eb76a89551bd5c115340e64 100644 (file)
@@ -1,8 +1,12 @@
 // Copyright 2005-6 Ben Hutchings <ben@decadent.org.uk>.
 // See the file "COPYING" for licence details.
 
+#include <cerrno>
+#include <cstring>
 #include <fstream>
+#include <iomanip>
 #include <iostream>
+#include <ostream>
 #include <sstream>
 #include <stdexcept>
 
@@ -72,6 +76,35 @@ namespace
 
        return Glib::build_filename(dir.get_name(), base_name);
     }
+
+    // We would like to use just a single frame for the menu but this
+    // seems not to be legal or compatible.  The minimum length of a
+    // cell is 0.4 seconds but I've seen a static menu using 12 frames
+    // on a commercial "PAL" disc so let's use 12 frames regardless.
+    unsigned menu_duration_frames(const video::frame_params & params)
+    {
+       return 12;
+    }
+    double menu_duration_seconds(const video::frame_params & params)
+    {
+       return double(menu_duration_frames(params))
+           / double(params.rate_numer)
+           * double(params.rate_denom);
+    }
+
+    void throw_length_error(const char * limit_type, std::size_t limit)
+    {
+       std::ostringstream oss;
+       oss << "exceeded DVD limit: " << limit_type << " > " << limit;
+       throw std::length_error(oss.str());
+    }
+
+    // dvdauthor uses some menu numbers to represent entry points -
+    // distinct from the actual numbers of the menus assigned as those
+    // entry points - resulting in a practical limit of 119 per
+    // domain.  This seems to be an oddity of the parser that could be
+    // fixed, but for now we'll have to work with it.
+    const unsigned dvdauthor_anonymous_menus_max = dvd::domain_pgcs_max - 8;
 }
 
 dvd_generator::dvd_generator(const video::frame_params & frame_params,
@@ -85,13 +118,8 @@ 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");
+    if (next_menu.index == dvdauthor_anonymous_menus_max)
+       throw_length_error("number of menus", dvdauthor_anonymous_menus_max);
 
     menus_.resize(next_menu.index + 1);
     return next_menu;
@@ -104,6 +132,10 @@ void dvd_generator::add_menu_entry(unsigned index,
     assert(index < menus_.size());
     assert(target.type == menu_pgc && target.index < menus_.size()
           || target.type == title_pgc && target.index < titles_.size());
+
+    if (menus_[index].entries.size() == dvd::menu_buttons_max)
+       throw_length_error("number of buttons", dvd::menu_buttons_max);
+
     menu_entry new_entry = { area, target };
     menus_[index].entries.push_back(new_entry);
 }
@@ -188,23 +220,34 @@ void dvd_generator::generate_menu_vob(unsigned index,
        throw std::runtime_error("Failed to write control file for spumux");
 
     std::ostringstream command_stream;
+    unsigned frame_count(menu_duration_frames(frame_params_));
     if (encoder_ == mpeg_encoder_ffmpeg)
     {
-       command_stream
-           << "ffmpeg"
-           << " -f image2 -vcodec png -i "
-           << background_name
-           << " -target " << frame_params_.common_name <<  "-dvd"
-           << " -vcodec mpeg2video -aspect 4:3 -an -y /dev/stdout";
+       for (unsigned i = 0; i != frame_count; ++i)
+       {
+           std::string frame_name(background_name);
+           frame_name.push_back('-');
+           frame_name.push_back('0' + i / 10);
+           frame_name.push_back('0' + i % 10);
+           if (symlink(background_name.c_str(), frame_name.c_str()) != 0)
+               throw std::runtime_error(
+                   std::string("symlink: ").append(std::strerror(errno)));
+       }
+       command_stream <<
+           "ffmpeg -f image2 -vcodec png"
+           " -r " << frame_params_.rate_numer <<
+           "/" << frame_params_.rate_denom <<
+           " -i " << background_name << "-%02d"
+           " -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_name
-           << " | ppmtoy4m -v0 -n1 -F"
+           << "pngtopnm " << background_name
+           << " | ppmtoy4m -v0 -n" << frame_count << " -F"
            << frame_params_.rate_numer << ":" << frame_params_.rate_denom
            << " -A" << frame_params_.pixel_ratio_width
            << ":" << frame_params_.pixel_ratio_height
@@ -246,8 +289,8 @@ 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");
+    if (next_title.index == dvd::titles_max)
+       throw_length_error("number of titles", dvd::titles_max);
 
     titles_.resize(next_title.index + 1);
     titles_[next_title.index].swap(content);
@@ -290,7 +333,7 @@ void dvd_generator::generate(const std::string & output_dir) const
            // This is the first (title) menu, displayed when the
            // disc is first played.
            file <<
-               "      <pgc entry='title' pause='inf'>\n"
+               "      <pgc entry='title'>\n"
                "        <pre>\n"
                // Set a default target location if none is set.
                // This covers first play and use of the "top menu"
@@ -301,7 +344,7 @@ void dvd_generator::generate(const std::string & output_dir) const
        else
        {
            file <<
-               "      <pgc pause='inf'>\n"
+               "      <pgc>\n"
                "        <pre>\n";
        }
 
@@ -351,7 +394,15 @@ void dvd_generator::generate(const std::string & output_dir) const
            "        </pre>\n"
            "        <vob file='"
             << temp_file_name(temp_dir_, "menu-%3d.mpeg", 1 + menu_index)
-            << "'/>\n";
+            << "'>\n"
+           // Define a cell covering the whole menu and set a still
+           // time at the end of that, since it seems all players
+           // support that but some ignore a still time set on a PGC.
+           "          <cell start='0' end='"
+            << std::fixed << std::setprecision(4)
+            << menu_duration_seconds(frame_params_) << "'"
+           " chapter='yes' pause='inf'/>\n"
+           "        </vob>\n";
 
        for (unsigned button_index = 0;
             button_index != this_menu.entries.size();
@@ -410,11 +461,6 @@ void dvd_generator::generate(const std::string & output_dir) const
        }
 
        file <<
-           // Some DVD players don't seem to obey pause='inf' so make
-           // them loop.
-           "        <post>\n"
-           "          jump cell 1;\n"
-           "        </post>\n"
            "      </pgc>\n";
     }