Add some logging to the KDM creator.
[dcpomatic.git] / test / reels_test.cc
1 /*
2     Copyright (C) 2015-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 /** @file  test/reels_test.cc
23  *  @brief Check manipulation of reels in various ways.
24  *  @ingroup feature
25  */
26
27
28 #include "lib/content_factory.h"
29 #include "lib/dcp_content.h"
30 #include "lib/dcp_content_type.h"
31 #include "lib/ffmpeg_content.h"
32 #include "lib/film.h"
33 #include "lib/image_content.h"
34 #include "lib/make_dcp.h"
35 #include "lib/ratio.h"
36 #include "lib/string_text_file_content.h"
37 #include "lib/video_content.h"
38 #include "test.h"
39 #include <dcp/cpl.h>
40 #include <dcp/reel.h>
41 #include <dcp/reel_picture_asset.h>
42 #include <dcp/reel_sound_asset.h>
43 #include <boost/test/unit_test.hpp>
44 #include <iostream>
45
46
47 using std::cout;
48 using std::function;
49 using std::list;
50 using std::make_shared;
51 using std::shared_ptr;
52 using std::string;
53 using std::vector;
54 using namespace dcpomatic;
55
56
57 /** Test Film::reels() */
58 BOOST_AUTO_TEST_CASE (reels_test1)
59 {
60         auto film = new_test_film ("reels_test1");
61         film->set_container (Ratio::from_id ("185"));
62         auto A = make_shared<FFmpegContent>("test/data/test.mp4");
63         film->examine_and_add_content (A);
64         auto B = make_shared<FFmpegContent>("test/data/test.mp4");
65         film->examine_and_add_content (B);
66         BOOST_REQUIRE (!wait_for_jobs());
67         BOOST_CHECK_EQUAL (A->full_length(film).get(), 288000);
68
69         film->set_reel_type (ReelType::SINGLE);
70         auto r = film->reels ();
71         BOOST_CHECK_EQUAL (r.size(), 1U);
72         BOOST_CHECK_EQUAL (r.front().from.get(), 0);
73         BOOST_CHECK_EQUAL (r.front().to.get(), 288000 * 2);
74
75         film->set_reel_type (ReelType::BY_VIDEO_CONTENT);
76         r = film->reels ();
77         BOOST_CHECK_EQUAL (r.size(), 2U);
78         BOOST_CHECK_EQUAL (r.front().from.get(), 0);
79         BOOST_CHECK_EQUAL (r.front().to.get(), 288000);
80         BOOST_CHECK_EQUAL (r.back().from.get(), 288000);
81         BOOST_CHECK_EQUAL (r.back().to.get(), 288000 * 2);
82
83         film->set_j2k_bandwidth (100000000);
84         film->set_reel_type (ReelType::BY_LENGTH);
85         /* This is just over 2.5s at 100Mbit/s; should correspond to 60 frames */
86         film->set_reel_length (31253154);
87         r = film->reels ();
88         BOOST_CHECK_EQUAL (r.size(), 3U);
89         auto i = r.begin ();
90         BOOST_CHECK_EQUAL (i->from.get(), 0);
91         BOOST_CHECK_EQUAL (i->to.get(), DCPTime::from_frames(60, 24).get());
92         ++i;
93         BOOST_CHECK_EQUAL (i->from.get(), DCPTime::from_frames(60, 24).get());
94         BOOST_CHECK_EQUAL (i->to.get(), DCPTime::from_frames(120, 24).get());
95         ++i;
96         BOOST_CHECK_EQUAL (i->from.get(), DCPTime::from_frames(120, 24).get());
97         BOOST_CHECK_EQUAL (i->to.get(), DCPTime::from_frames(144, 24).get());
98 }
99
100
101 /** Make a short DCP with multi reels split by video content, then import
102  *  this into a new project and make a new DCP referencing it.
103  */
104 BOOST_AUTO_TEST_CASE (reels_test2)
105 {
106         auto film = new_test_film ("reels_test2");
107         film->set_name ("reels_test2");
108         film->set_container (Ratio::from_id ("185"));
109         film->set_interop (false);
110         film->set_dcp_content_type (DCPContentType::from_isdcf_name ("TST"));
111
112         {
113                 shared_ptr<ImageContent> c (new ImageContent("test/data/flat_red.png"));
114                 film->examine_and_add_content (c);
115                 BOOST_REQUIRE (!wait_for_jobs());
116                 c->video->set_length (24);
117         }
118
119         {
120                 shared_ptr<ImageContent> c (new ImageContent("test/data/flat_green.png"));
121                 film->examine_and_add_content (c);
122                 BOOST_REQUIRE (!wait_for_jobs());
123                 c->video->set_length (24);
124         }
125
126         {
127                 shared_ptr<ImageContent> c (new ImageContent("test/data/flat_blue.png"));
128                 film->examine_and_add_content (c);
129                 BOOST_REQUIRE (!wait_for_jobs());
130                 c->video->set_length (24);
131         }
132
133         film->set_reel_type (ReelType::BY_VIDEO_CONTENT);
134         BOOST_CHECK_EQUAL (film->reels().size(), 3U);
135         BOOST_REQUIRE (!wait_for_jobs());
136
137         make_and_verify_dcp (film);
138
139         check_dcp ("test/data/reels_test2", film->dir (film->dcp_name()));
140
141         auto c = make_shared<DCPContent>(film->dir(film->dcp_name()));
142         auto film2 = new_test_film2 ("reels_test2b", {c});
143         film2->set_reel_type (ReelType::BY_VIDEO_CONTENT);
144
145         auto r = film2->reels ();
146         BOOST_CHECK_EQUAL (r.size(), 3U);
147         auto i = r.begin ();
148         BOOST_CHECK_EQUAL (i->from.get(), 0);
149         BOOST_CHECK_EQUAL (i->to.get(), 96000);
150         ++i;
151         BOOST_CHECK_EQUAL (i->from.get(), 96000);
152         BOOST_CHECK_EQUAL (i->to.get(), 96000 * 2);
153         ++i;
154         BOOST_CHECK_EQUAL (i->from.get(), 96000 * 2);
155         BOOST_CHECK_EQUAL (i->to.get(), 96000 * 3);
156
157         c->set_reference_video (true);
158         c->set_reference_audio (true);
159
160         make_and_verify_dcp (film2, {dcp::VerificationNote::Code::EXTERNAL_ASSET});
161 }
162
163
164 /** Check that ReelType::BY_VIDEO_CONTENT adds an extra reel, if necessary, at the end
165  *  of all the video content to mop up anything afterward.
166  */
167 BOOST_AUTO_TEST_CASE (reels_test3)
168 {
169         auto dcp = make_shared<DCPContent>("test/data/reels_test2");
170         auto sub = make_shared<StringTextFileContent>("test/data/subrip.srt");
171         auto film = new_test_film2 ("reels_test3", {dcp, sub});
172         film->set_reel_type (ReelType::BY_VIDEO_CONTENT);
173
174         auto reels = film->reels();
175         BOOST_REQUIRE_EQUAL (reels.size(), 4U);
176         auto i = reels.begin ();
177         BOOST_CHECK_EQUAL (i->from.get(), 0);
178         BOOST_CHECK_EQUAL (i->to.get(), 96000);
179         ++i;
180         BOOST_CHECK_EQUAL (i->from.get(), 96000);
181         BOOST_CHECK_EQUAL (i->to.get(), 96000 * 2);
182         ++i;
183         BOOST_CHECK_EQUAL (i->from.get(), 96000 * 2);
184         BOOST_CHECK_EQUAL (i->to.get(), 96000 * 3);
185         ++i;
186         BOOST_CHECK_EQUAL (i->from.get(), 96000 * 3);
187         BOOST_CHECK_EQUAL (i->to.get(), sub->full_length(film).ceil(film->video_frame_rate()).get());
188 }
189
190
191 /** Check creation of a multi-reel DCP with a single .srt subtitle file;
192  *  make sure that the reel subtitle timing is done right.
193  */
194 BOOST_AUTO_TEST_CASE (reels_test4)
195 {
196         auto film = new_test_film2 ("reels_test4");
197         film->set_reel_type (ReelType::BY_VIDEO_CONTENT);
198         film->set_interop (false);
199
200         /* 4 piece of 1s-long content */
201         shared_ptr<ImageContent> content[4];
202         for (int i = 0; i < 4; ++i) {
203                 content[i].reset (new ImageContent("test/data/flat_green.png"));
204                 film->examine_and_add_content (content[i]);
205                 BOOST_REQUIRE (!wait_for_jobs());
206                 content[i]->video->set_length (24);
207         }
208
209         auto subs = make_shared<StringTextFileContent>("test/data/subrip3.srt");
210         film->examine_and_add_content (subs);
211         BOOST_REQUIRE (!wait_for_jobs());
212
213         auto reels = film->reels();
214         BOOST_REQUIRE_EQUAL (reels.size(), 4U);
215         auto i = reels.begin ();
216         BOOST_CHECK_EQUAL (i->from.get(), 0);
217         BOOST_CHECK_EQUAL (i->to.get(), 96000);
218         ++i;
219         BOOST_CHECK_EQUAL (i->from.get(), 96000);
220         BOOST_CHECK_EQUAL (i->to.get(), 96000 * 2);
221         ++i;
222         BOOST_CHECK_EQUAL (i->from.get(), 96000 * 2);
223         BOOST_CHECK_EQUAL (i->to.get(), 96000 * 3);
224         ++i;
225         BOOST_CHECK_EQUAL (i->from.get(), 96000 * 3);
226         BOOST_CHECK_EQUAL (i->to.get(), 96000 * 4);
227
228         make_and_verify_dcp (
229                 film,
230                 {
231                         dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE,
232                         dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME,
233                         dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION
234                 });
235
236         check_dcp ("test/data/reels_test4", film->dir (film->dcp_name()));
237 }
238
239
240 BOOST_AUTO_TEST_CASE (reels_test5)
241 {
242         auto dcp = make_shared<DCPContent>("test/data/reels_test4");
243         dcp->check_font_ids();
244         auto film = new_test_film2 ("reels_test5", {dcp});
245         film->set_sequence (false);
246
247         /* Set to 2123 but it will be rounded up to the next frame (4000) */
248         dcp->set_position(film, DCPTime(2123));
249
250         {
251                 auto p = dcp->reels (film);
252                 BOOST_REQUIRE_EQUAL (p.size(), 4U);
253                 auto i = p.begin();
254                 BOOST_CHECK (*i++ == DCPTimePeriod (DCPTime(4000 + 0), DCPTime(4000 + 96000)));
255                 BOOST_CHECK (*i++ == DCPTimePeriod (DCPTime(4000 + 96000), DCPTime(4000 + 192000)));
256                 BOOST_CHECK (*i++ == DCPTimePeriod (DCPTime(4000 + 192000), DCPTime(4000 + 288000)));
257                 BOOST_CHECK (*i++ == DCPTimePeriod (DCPTime(4000 + 288000), DCPTime(4000 + 384000)));
258         }
259
260         {
261                 dcp->set_trim_start (ContentTime::from_seconds (0.5));
262                 auto p = dcp->reels (film);
263                 BOOST_REQUIRE_EQUAL (p.size(), 4U);
264                 auto i = p.begin();
265                 BOOST_CHECK (*i++ == DCPTimePeriod (DCPTime(4000 + 0), DCPTime(4000 + 48000)));
266                 BOOST_CHECK (*i++ == DCPTimePeriod (DCPTime(4000 + 48000), DCPTime(4000 + 144000)));
267                 BOOST_CHECK (*i++ == DCPTimePeriod (DCPTime(4000 + 144000), DCPTime(4000 + 240000)));
268                 BOOST_CHECK (*i++ == DCPTimePeriod (DCPTime(4000 + 240000), DCPTime(4000 + 336000)));
269         }
270
271         {
272                 dcp->set_trim_end (ContentTime::from_seconds (0.5));
273                 auto p = dcp->reels (film);
274                 BOOST_REQUIRE_EQUAL (p.size(), 4U);
275                 auto i = p.begin();
276                 BOOST_CHECK (*i++ == DCPTimePeriod (DCPTime(4000 + 0), DCPTime(4000 + 48000)));
277                 BOOST_CHECK (*i++ == DCPTimePeriod (DCPTime(4000 + 48000), DCPTime(4000 + 144000)));
278                 BOOST_CHECK (*i++ == DCPTimePeriod (DCPTime(4000 + 144000), DCPTime(4000 + 240000)));
279                 BOOST_CHECK (*i++ == DCPTimePeriod (DCPTime(4000 + 240000), DCPTime(4000 + 288000)));
280         }
281
282         {
283                 dcp->set_trim_start (ContentTime::from_seconds (1.5));
284                 auto p = dcp->reels (film);
285                 BOOST_REQUIRE_EQUAL (p.size(), 3U);
286                 auto i = p.begin();
287                 BOOST_CHECK (*i++ == DCPTimePeriod (DCPTime(4000 + 0), DCPTime(4000 + 48000)));
288                 BOOST_CHECK (*i++ == DCPTimePeriod (DCPTime(4000 + 48000), DCPTime(4000 + 144000)));
289                 BOOST_CHECK (*i++ == DCPTimePeriod (DCPTime(4000 + 144000), DCPTime(4000 + 192000)));
290         }
291 }
292
293
294 /** Check reel split with a muxed video/audio source */
295 BOOST_AUTO_TEST_CASE (reels_test6)
296 {
297         auto A = make_shared<FFmpegContent>("test/data/test2.mp4");
298         auto film = new_test_film2 ("reels_test6", {A});
299
300         film->set_j2k_bandwidth (100000000);
301         film->set_reel_type (ReelType::BY_LENGTH);
302         /* This is just over 2.5s at 100Mbit/s; should correspond to 60 frames */
303         film->set_reel_length (31253154);
304         make_and_verify_dcp (
305                 film,
306                 {
307                         dcp::VerificationNote::Code::INVALID_INTRINSIC_DURATION,
308                         dcp::VerificationNote::Code::INVALID_DURATION,
309                 });
310 }
311
312
313 /** Check the case where the last bit of audio hangs over the end of the video
314  *  and we are using ReelType::BY_VIDEO_CONTENT.
315  */
316 BOOST_AUTO_TEST_CASE (reels_test7)
317 {
318         auto A = content_factory("test/data/flat_red.png")[0];
319         auto B = content_factory("test/data/awkward_length.wav")[0];
320         auto film = new_test_film2 ("reels_test7", { A, B });
321         film->set_video_frame_rate (24);
322         A->video->set_length (2 * 24);
323
324         film->set_reel_type (ReelType::BY_VIDEO_CONTENT);
325         BOOST_REQUIRE_EQUAL (film->reels().size(), 2U);
326         BOOST_CHECK (film->reels().front() == DCPTimePeriod(DCPTime(0), DCPTime::from_frames(2 * 24, 24)));
327         BOOST_CHECK (film->reels().back() == DCPTimePeriod(DCPTime::from_frames(2 * 24, 24), DCPTime::from_frames(3 * 24 + 1, 24)));
328
329         make_and_verify_dcp (film);
330 }
331
332
333 /** Check a reels-related error; make_dcp() would raise a ProgrammingError */
334 BOOST_AUTO_TEST_CASE (reels_test8)
335 {
336         auto A = make_shared<FFmpegContent>("test/data/test2.mp4");
337         auto film = new_test_film2 ("reels_test8", {A});
338
339         A->set_trim_end (ContentTime::from_seconds (1));
340         make_and_verify_dcp (film);
341 }
342
343
344 /** Check another reels-related error; make_dcp() would raise a ProgrammingError */
345 BOOST_AUTO_TEST_CASE (reels_test9)
346 {
347         auto A = make_shared<FFmpegContent>("test/data/flat_red.png");
348         auto film = new_test_film2("reels_test9a", {A});
349         A->video->set_length(5 * 24);
350         film->set_video_frame_rate(24);
351         make_and_verify_dcp (film);
352
353         auto B = make_shared<DCPContent>(film->dir(film->dcp_name()));
354         auto film2 = new_test_film2("reels_test9b", {B, content_factory("test/data/dcp_sub4.xml")[0]});
355         B->set_reference_video(true);
356         B->set_reference_audio(true);
357         film2->set_reel_type(ReelType::BY_VIDEO_CONTENT);
358         film2->write_metadata();
359         make_and_verify_dcp (
360                 film2,
361                 {
362                         dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE,
363                         dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME
364                 });
365 }
366
367
368 /** Another reels-related error; make_dcp() would raise a ProgrammingError
369  *  in AudioBuffers::allocate due to an attempt to allocate a negatively-sized buffer.
370  *  This was triggered by a VF where there are referenced audio reels followed by
371  *  VF audio.  When the VF audio arrives the Writer did not correctly skip over the
372  *  referenced reels.
373  */
374 BOOST_AUTO_TEST_CASE (reels_test10)
375 {
376         /* Make the OV */
377         auto A = make_shared<FFmpegContent>("test/data/flat_red.png");
378         auto B = make_shared<FFmpegContent>("test/data/flat_red.png");
379         auto ov = new_test_film2("reels_test10_ov", {A, B});
380         A->video->set_length (5 * 24);
381         B->video->set_length (5 * 24);
382
383         ov->set_reel_type (ReelType::BY_VIDEO_CONTENT);
384         make_and_verify_dcp (ov);
385         ov->write_metadata ();
386
387         /* Now try to make the VF; this used to fail */
388         auto ov_dcp = make_shared<DCPContent>(ov->dir(ov->dcp_name()));
389         auto vf = new_test_film2("reels_test10_vf", {ov_dcp, content_factory("test/data/15s.srt")[0]});
390         vf->set_reel_type (ReelType::BY_VIDEO_CONTENT);
391         ov_dcp->set_reference_video (true);
392         ov_dcp->set_reference_audio (true);
393
394         make_and_verify_dcp (
395                 vf,
396                 {
397                         dcp::VerificationNote::Code::EXTERNAL_ASSET,
398                         dcp::VerificationNote::Code::MISSING_SUBTITLE_LANGUAGE,
399                         dcp::VerificationNote::Code::INVALID_SUBTITLE_FIRST_TEXT_TIME,
400                         dcp::VerificationNote::Code::INVALID_SUBTITLE_DURATION,
401                 });
402 }
403
404
405 /** Another reels error; ReelType::BY_VIDEO_CONTENT when the first content is not
406  *  at time 0.
407  */
408 BOOST_AUTO_TEST_CASE (reels_test11)
409 {
410         auto A = make_shared<FFmpegContent>("test/data/flat_red.png");
411         auto film = new_test_film2 ("reels_test11", {A});
412         film->set_video_frame_rate (24);
413         A->video->set_length (240);
414         A->set_video_frame_rate (24);
415         A->set_position (film, DCPTime::from_seconds(1));
416         film->set_reel_type (ReelType::BY_VIDEO_CONTENT);
417         make_and_verify_dcp (film);
418         BOOST_CHECK_EQUAL (A->position().get(), DCPTime::from_seconds(1).get());
419         BOOST_CHECK_EQUAL (A->end(film).get(), DCPTime::from_seconds(1 + 10).get());
420
421         auto r = film->reels ();
422         BOOST_CHECK_EQUAL (r.size(), 2U);
423         BOOST_CHECK_EQUAL (r.front().from.get(), 0);
424         BOOST_CHECK_EQUAL (r.front().to.get(), DCPTime::from_seconds(1).get());
425         BOOST_CHECK_EQUAL (r.back().from.get(), DCPTime::from_seconds(1).get());
426         BOOST_CHECK_EQUAL (r.back().to.get(), DCPTime::from_seconds(1 + 10).get());
427 }
428
429
430 /** For VFs to work right we have to make separate reels for empty bits between
431  *  video content.
432  */
433 BOOST_AUTO_TEST_CASE (reels_test12)
434 {
435         auto A = make_shared<FFmpegContent>("test/data/flat_red.png");
436         auto B = make_shared<FFmpegContent>("test/data/flat_red.png");
437         auto film = new_test_film2 ("reels_test12", {A, B});
438         film->set_video_frame_rate (24);
439         film->set_reel_type (ReelType::BY_VIDEO_CONTENT);
440         film->set_sequence (false);
441
442         A->video->set_length (240);
443         A->set_video_frame_rate (24);
444         A->set_position (film, DCPTime::from_seconds(1));
445
446         B->video->set_length (120);
447         B->set_video_frame_rate (24);
448         B->set_position (film, DCPTime::from_seconds(14));
449
450         auto r = film->reels ();
451         BOOST_REQUIRE_EQUAL (r.size(), 4U);
452         auto i = r.begin ();
453
454         BOOST_CHECK_EQUAL (i->from.get(), 0);
455         BOOST_CHECK_EQUAL (i->to.get(),   DCPTime::from_seconds(1).get());
456         ++i;
457         BOOST_CHECK_EQUAL (i->from.get(), DCPTime::from_seconds(1).get());
458         BOOST_CHECK_EQUAL (i->to.get(),   DCPTime::from_seconds(11).get());
459         ++i;
460         BOOST_CHECK_EQUAL (i->from.get(), DCPTime::from_seconds(11).get());
461         BOOST_CHECK_EQUAL (i->to.get(),   DCPTime::from_seconds(14).get());
462         ++i;
463         BOOST_CHECK_EQUAL (i->from.get(), DCPTime::from_seconds(14).get());
464         BOOST_CHECK_EQUAL (i->to.get(),   DCPTime::from_seconds(19).get());
465 }
466
467
468 static void
469 no_op ()
470 {
471
472 }
473
474 static void
475 dump_notes (vector<dcp::VerificationNote> const & notes)
476 {
477         for (auto i: notes) {
478                 std::cout << dcp::note_to_string(i) << "\n";
479         }
480 }
481
482
483 /** Using less than 1 second's worth of content should not result in a reel
484  *  of less than 1 second's duration.
485  */
486 BOOST_AUTO_TEST_CASE (reels_should_not_be_short1)
487 {
488         auto A = make_shared<FFmpegContent>("test/data/flat_red.png");
489         auto B = make_shared<FFmpegContent>("test/data/flat_red.png");
490         auto film = new_test_film2 ("reels_should_not_be_short1", {A, B});
491         film->set_video_frame_rate (24);
492
493         A->video->set_length (23);
494
495         B->video->set_length (23);
496         B->set_position (film, DCPTime::from_frames(23, 24));
497
498         make_and_verify_dcp (film);
499
500         vector<boost::filesystem::path> dirs = { film->dir(film->dcp_name(false)) };
501         auto notes = dcp::verify(dirs, boost::bind(&no_op), boost::bind(&no_op), TestPaths::xsd());
502         dump_notes (notes);
503         BOOST_REQUIRE (notes.empty());
504 }
505
506
507 /** Leaving less than 1 second's gap between two pieces of content with
508  *  ReelType::BY_VIDEO_CONTENT should not make a <1s reel.
509  */
510 BOOST_AUTO_TEST_CASE (reels_should_not_be_short2)
511 {
512         auto A = make_shared<FFmpegContent>("test/data/flat_red.png");
513         auto B = make_shared<FFmpegContent>("test/data/flat_red.png");
514         auto film = new_test_film2 ("reels_should_not_be_short2", {A, B});
515         film->set_video_frame_rate (24);
516         film->set_reel_type (ReelType::BY_VIDEO_CONTENT);
517
518         A->video->set_length (240);
519
520         B->video->set_length (240);
521         B->set_position (film, DCPTime::from_seconds(10.2));
522
523         make_and_verify_dcp (film);
524
525         vector<boost::filesystem::path> dirs = { film->dir(film->dcp_name(false)) };
526         auto const notes = dcp::verify(dirs, boost::bind(&no_op), boost::bind(&no_op), TestPaths::xsd());
527         dump_notes (notes);
528         BOOST_REQUIRE (notes.empty());
529 }
530
531
532 /** Setting ReelType::BY_LENGTH and using a small length value should not make
533  *  <1s reels.
534  */
535 BOOST_AUTO_TEST_CASE (reels_should_not_be_short3)
536 {
537         auto A = make_shared<FFmpegContent>("test/data/flat_red.png");
538         auto film = new_test_film2 ("reels_should_not_be_short3", {A});
539         film->set_video_frame_rate (24);
540         film->set_reel_type (ReelType::BY_LENGTH);
541         film->set_reel_length (1024 * 1024 * 10);
542
543         A->video->set_length (240);
544
545         make_and_verify_dcp (film);
546
547         auto const notes = dcp::verify({}, boost::bind(&no_op), boost::bind(&no_op), TestPaths::xsd());
548         dump_notes (notes);
549         BOOST_REQUIRE (notes.empty());
550 }
551
552
553 /** Having one piece of content less than 1s long in ReelType::BY_VIDEO_CONTENT
554  *  should not make a reel less than 1s long.
555  */
556 BOOST_AUTO_TEST_CASE (reels_should_not_be_short4)
557 {
558         auto A = make_shared<FFmpegContent>("test/data/flat_red.png");
559         auto B = make_shared<FFmpegContent>("test/data/flat_red.png");
560         auto film = new_test_film2 ("reels_should_not_be_short4", {A, B});
561         film->set_video_frame_rate (24);
562         film->set_reel_type (ReelType::BY_VIDEO_CONTENT);
563
564         A->video->set_length (240);
565
566         B->video->set_length (23);
567         B->set_position (film, DCPTime::from_frames(240, 24));
568
569         BOOST_CHECK_EQUAL (film->reels().size(), 1U);
570         BOOST_CHECK (film->reels().front() == dcpomatic::DCPTimePeriod(dcpomatic::DCPTime(), dcpomatic::DCPTime::from_frames(263, 24)));
571
572         film->write_metadata ();
573         make_dcp (film, TranscodeJob::ChangedBehaviour::IGNORE);
574         BOOST_REQUIRE (!wait_for_jobs());
575
576         vector<boost::filesystem::path> dirs = { film->dir(film->dcp_name(false)) };
577         auto const notes = dcp::verify(dirs, boost::bind(&no_op), boost::bind(&no_op), TestPaths::xsd());
578         dump_notes (notes);
579         BOOST_REQUIRE (notes.empty());
580 }
581
582
583 /** Create a long DCP A then insert it repeatedly into a new project, trimming it differently each time.
584  *  Make a DCP B from that project which refers to A and splits into reels.  This was found to go wrong
585  *  when looking at #2268.
586  */
587 BOOST_AUTO_TEST_CASE (repeated_dcp_into_reels)
588 {
589         /* Make a 20s DCP */
590         auto A = make_shared<FFmpegContent>("test/data/flat_red.png");
591         auto film1 = new_test_film2("repeated_dcp_into_reels1", { A });
592         auto constexpr frame_rate = 24;
593         auto constexpr length_in_seconds = 20;
594         auto constexpr total_frames = frame_rate * length_in_seconds;
595         film1->set_video_frame_rate(frame_rate);
596         A->video->set_length(total_frames);
597         make_and_verify_dcp(film1);
598
599         /* Make a new project that includes this long DCP 4 times, each
600          * trimmed to a quarter of the original, i.e.
601          * /----------------------|----------------------|----------------------|----------------------\
602          * | 1st quarter of film1 | 2nd quarter of film1 | 3rd quarter of film1 | 4th quarter of film1 |
603          * \----------------------|----------------------|----------------------|_---------------------/
604          */
605
606         shared_ptr<DCPContent> original_dcp[4] = {
607                  make_shared<DCPContent>(film1->dir(film1->dcp_name(false))),
608                  make_shared<DCPContent>(film1->dir(film1->dcp_name(false))),
609                  make_shared<DCPContent>(film1->dir(film1->dcp_name(false))),
610                  make_shared<DCPContent>(film1->dir(film1->dcp_name(false)))
611         };
612
613         auto film2 = new_test_film2("repeated_dcp_into_reels2", { original_dcp[0], original_dcp[1], original_dcp[2], original_dcp[3] });
614         film2->set_reel_type(ReelType::BY_VIDEO_CONTENT);
615         film2->set_video_frame_rate(frame_rate);
616         film2->set_sequence(false);
617
618         for (int i = 0; i < 4; ++i) {
619                 original_dcp[i]->set_position(film2, DCPTime::from_frames(total_frames * i / 4, frame_rate));
620                 original_dcp[i]->set_trim_start(ContentTime::from_frames(total_frames * i / 4, frame_rate));
621                 original_dcp[i]->set_trim_end  (ContentTime::from_frames(total_frames * (4 - i - 1) / 4, frame_rate));
622                 original_dcp[i]->set_reference_video(true);
623                 original_dcp[i]->set_reference_audio(true);
624         }
625
626         make_and_verify_dcp(film2, { dcp::VerificationNote::Code::EXTERNAL_ASSET });
627
628         dcp::DCP check1(film1->dir(film1->dcp_name()));
629         check1.read();
630         BOOST_REQUIRE(!check1.cpls().empty());
631         BOOST_REQUIRE(!check1.cpls()[0]->reels().empty());
632         auto picture = check1.cpls()[0]->reels()[0]->main_picture()->asset();
633         BOOST_REQUIRE(picture);
634         auto sound = check1.cpls()[0]->reels()[0]->main_sound()->asset();
635         BOOST_REQUIRE(sound);
636
637         dcp::DCP check2(film2->dir(film2->dcp_name()));
638         check2.read();
639         BOOST_REQUIRE(!check2.cpls().empty());
640         auto cpl = check2.cpls()[0];
641         BOOST_REQUIRE_EQUAL(cpl->reels().size(), 4U);
642         for (int i = 0; i < 4; ++i) {
643                 BOOST_REQUIRE_EQUAL(cpl->reels()[i]->main_picture()->entry_point().get_value_or(0), total_frames * i / 4);
644                 BOOST_REQUIRE_EQUAL(cpl->reels()[i]->main_picture()->duration().get_value_or(0), total_frames / 4);
645                 BOOST_REQUIRE_EQUAL(cpl->reels()[i]->main_picture()->id(), picture->id());
646                 BOOST_REQUIRE_EQUAL(cpl->reels()[i]->main_sound()->entry_point().get_value_or(0), total_frames * i / 4);
647                 BOOST_REQUIRE_EQUAL(cpl->reels()[i]->main_sound()->duration().get_value_or(0), total_frames / 4);
648                 BOOST_REQUIRE_EQUAL(cpl->reels()[i]->main_sound()->id(), sound->id());
649         }
650 }