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