b3e6d75531bcbf4fd86ffd0d812aae9558048f19
[dcpomatic.git] / src / lib / dcp_subtitle_decoder.cc
1 /*
2     Copyright (C) 2014-2021 Carl Hetherington <cth@carlh.net>
3
4     This file is part of DCP-o-matic.
5
6     DCP-o-matic is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     DCP-o-matic is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
18
19 */
20
21
22 #include "dcp_subtitle_content.h"
23 #include "dcp_subtitle_decoder.h"
24 #include "film.h"
25 #include "font.h"
26 #include "text_content.h"
27 #include "util.h"
28 #include <dcp/interop_subtitle_asset.h>
29 #include <dcp/load_font_node.h>
30
31
32 using std::dynamic_pointer_cast;
33 using std::list;
34 using std::make_shared;
35 using std::shared_ptr;
36 using std::string;
37 using std::vector;
38 using boost::optional;
39 using namespace dcpomatic;
40
41
42 DCPSubtitleDecoder::DCPSubtitleDecoder (shared_ptr<const Film> film, shared_ptr<const DCPSubtitleContent> content)
43         : Decoder (film)
44 {
45         /* Load the XML or MXF file */
46         auto const asset = load (content->path(0));
47         asset->fix_empty_font_ids ();
48         _subtitles = asset->subtitles ();
49         _next = _subtitles.begin ();
50
51         _subtitle_standard = asset->subtitle_standard();
52
53         text.push_back (make_shared<TextDecoder>(this, content->only_text()));
54         update_position();
55 }
56
57
58 void
59 DCPSubtitleDecoder::seek (ContentTime time, bool accurate)
60 {
61         Decoder::seek (time, accurate);
62
63         _next = _subtitles.begin ();
64         auto i = _subtitles.begin ();
65         while (i != _subtitles.end() && ContentTime::from_seconds ((*_next)->in().as_seconds()) < time) {
66                 ++i;
67         }
68
69         update_position();
70 }
71
72
73 bool
74 DCPSubtitleDecoder::pass ()
75 {
76         if (_next == _subtitles.end ()) {
77                 return true;
78         }
79
80         /* Gather all subtitles with the same time period that are next
81            on the list.  We must emit all subtitles for the same time
82            period with the same emit*() call otherwise the
83            TextDecoder will assume there is nothing else at the
84            time of emitting the first.
85         */
86
87         vector<dcp::SubtitleString> s;
88         vector<dcp::SubtitleImage> i;
89         auto const p = content_time_period (*_next);
90
91         while (_next != _subtitles.end () && content_time_period (*_next) == p) {
92                 auto ns = dynamic_pointer_cast<const dcp::SubtitleString>(*_next);
93                 if (ns) {
94                         s.push_back (*ns);
95                         ++_next;
96                 } else {
97                         /* XXX: perhaps these image subs should also be collected together like the string ones are;
98                            this would need to be done both here and in DCPDecoder.
99                         */
100
101                         auto ni = dynamic_pointer_cast<const dcp::SubtitleImage>(*_next);
102                         if (ni) {
103                                 emit_subtitle_image (p, *ni, film()->frame_size(), only_text());
104                                 ++_next;
105                         }
106                 }
107         }
108
109         only_text()->emit_plain(p, s, _subtitle_standard);
110
111         update_position();
112
113         return false;
114 }
115
116
117 ContentTimePeriod
118 DCPSubtitleDecoder::content_time_period (shared_ptr<const dcp::Subtitle> s) const
119 {
120         return {
121                 ContentTime::from_seconds(s->in().as_seconds()),
122                 ContentTime::from_seconds(s->out().as_seconds())
123         };
124 }
125
126
127 /** @return time of first subtitle, if there is one */
128 optional<ContentTime>
129 DCPSubtitleDecoder::first () const
130 {
131         if (_subtitles.empty()) {
132                 return {};
133         }
134
135         return ContentTime::from_seconds(_subtitles[0]->in().as_seconds());
136 }
137
138
139 void
140 DCPSubtitleDecoder::update_position()
141 {
142         if (_next != _subtitles.end()) {
143                 only_text()->maybe_set_position(
144                         ContentTime::from_seconds((*_next)->in().as_seconds())
145                         );
146         }
147 }
148