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