Speculative fix for assertion failure (DoM #2839).
[libdcp.git] / src / verify.cc
index 0ef4ada5c59b9add2a576e9e6596b84d410b188e..ec8925f2ffba6a5a6c2713d0f7d6a4dd8cb39a16 100644 (file)
@@ -81,6 +81,7 @@
 #include <boost/algorithm/string.hpp>
 #include <iostream>
 #include <map>
+#include <numeric>
 #include <regex>
 #include <set>
 #include <vector>
@@ -1182,18 +1183,9 @@ verify_closed_caption_details (
 }
 
 
-struct LinesCharactersResult
-{
-       bool warning_length_exceeded = false;
-       bool error_length_exceeded = false;
-       bool line_count_exceeded = false;
-};
-
-
-static
 void
-verify_text_lines_and_characters (
-       shared_ptr<SubtitleAsset> asset,
+dcp::verify_text_lines_and_characters(
+       shared_ptr<const SubtitleAsset> asset,
        int warning_length,
        int error_length,
        LinesCharactersResult* result
@@ -1214,8 +1206,8 @@ verify_text_lines_and_characters (
                {}
 
                Time time;
-               int position; //< position from 0 at top of screen to 100 at bottom
-               int characters;
+               int position = 0;   ///< vertical position from 0 at top of screen to 100 at bottom
+               int characters = 0; ///< number of characters in the text of this event
                shared_ptr<Event> start;
        };
 
@@ -1234,6 +1226,7 @@ verify_text_lines_and_characters (
                return 0L;
        };
 
+       /* Make a list of "subtitle starts" and "subtitle ends" events */
        for (auto j: asset->subtitles()) {
                auto text = dynamic_pointer_cast<const SubtitleString>(j);
                if (text) {
@@ -1243,20 +1236,25 @@ verify_text_lines_and_characters (
                }
        }
 
-       std::sort(events.begin(), events.end(), [](shared_ptr<Event> const& a, shared_ptr<Event>const& b) {
+       std::sort(events.begin(), events.end(), [](shared_ptr<Event> const& a, shared_ptr<Event> const& b) {
                return a->time < b->time;
        });
 
-       map<int, int> current;
+       /* The number of characters currently displayed at different vertical positions, i.e. on
+        * what we consider different lines.  Key is the vertical position (0 to 100) and the value
+        * is a list of the active subtitles in that position.
+        */
+       map<int, vector<shared_ptr<Event>>> current;
        for (auto i: events) {
                if (current.size() > 3) {
                        result->line_count_exceeded = true;
                }
                for (auto j: current) {
-                       if (j.second > warning_length) {
+                       int length = std::accumulate(j.second.begin(), j.second.end(), 0, [](int total, shared_ptr<const Event> event) { return total + event->characters; });
+                       if (length > warning_length) {
                                result->warning_length_exceeded = true;
                        }
-                       if (j.second > error_length) {
+                       if (length > error_length) {
                                result->error_length_exceeded = true;
                        }
                }
@@ -1264,17 +1262,20 @@ verify_text_lines_and_characters (
                if (i->start) {
                        /* end of a subtitle */
                        DCP_ASSERT (current.find(i->start->position) != current.end());
-                       if (current[i->start->position] == i->start->characters) {
+                       auto current_position = current[i->start->position];
+                       auto iter = std::find(current_position.begin(), current_position.end(), i->start);
+                       if (iter != current_position.end()) {
+                               current_position.erase(iter);
+                       }
+                       if (current_position.empty()) {
                                current.erase(i->start->position);
-                       } else {
-                               current[i->start->position] -= i->start->characters;
                        }
                } else {
                        /* start of a subtitle */
                        if (current.find(i->position) == current.end()) {
-                               current[i->position] = i->characters;
+                               current[i->position] = vector<shared_ptr<Event>>{i};
                        } else {
-                               current[i->position] += i->characters;
+                               current[i->position].push_back(i);
                        }
                }
        }
@@ -1904,16 +1905,20 @@ dcp::verify (
                }
 
                for (auto cpl: dcp->cpls()) {
-                       verify_cpl(
-                               dcp,
-                               cpl,
-                               stage,
-                               *xsd_dtd_directory,
-                               progress,
-                               options,
-                               notes,
-                               state
-                               );
+                       try {
+                               verify_cpl(
+                                       dcp,
+                                       cpl,
+                                       stage,
+                                       *xsd_dtd_directory,
+                                       progress,
+                                       options,
+                                       notes,
+                                       state
+                                       );
+                       } catch (ReadError& e) {
+                               notes.push_back({VerificationNote::Type::ERROR, VerificationNote::Code::FAILED_READ, string(e.what())});
+                       }
                }
 
                for (auto pkl: dcp->pkls()) {
@@ -2174,6 +2179,10 @@ dcp::note_to_string (VerificationNote note)
                return String::compose("The asset with ID %1 in the asset map actually has an id of %2", note.id().get(), note.other_id().get());
        case VerificationNote::Code::EMPTY_CONTENT_VERSION_LABEL_TEXT:
                return String::compose("The <LabelText> in a <ContentVersion> in CPL %1 is empty", note.id().get());
+       case VerificationNote::Code::INVALID_CPL_NAMESPACE:
+               return String::compose("The namespace %1 in CPL %2 is invalid", note.note().get(), note.file()->filename());
+       case VerificationNote::Code::MISSING_CPL_CONTENT_VERSION:
+               return String::compose("The CPL %1 has no <ContentVersion> tag", note.note().get());
        }
 
        return "";