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