Fix font_id_map errors when importing DCP subtitles that have no
[dcpomatic.git] / test / video_level_test.cc
index acf7fb4e2919a1084fb5c4667f4b710249a18ea7..e2419d8e734c96f0ef92add497afb2062a0d5f75 100644 (file)
@@ -28,6 +28,7 @@
 #include "lib/content_factory.h"
 #include "lib/content_video.h"
 #include "lib/dcp_content.h"
+#include "lib/decoder_factory.h"
 #include "lib/film.h"
 #include "lib/ffmpeg_content.h"
 #include "lib/ffmpeg_decoder.h"
@@ -37,6 +38,8 @@
 #include "lib/image_decoder.h"
 #include "lib/ffmpeg_encoder.h"
 #include "lib/job_manager.h"
+#include "lib/player.h"
+#include "lib/player_video.h"
 #include "lib/transcode_job.h"
 #include "lib/video_decoder.h"
 #include "test.h"
@@ -51,7 +54,6 @@
 
 
 using std::min;
-using std::make_pair;
 using std::max;
 using std::pair;
 using std::string;
@@ -68,7 +70,7 @@ static
 shared_ptr<Image>
 grey_image (dcp::Size size, uint8_t pixel)
 {
-       auto grey = make_shared<Image>(AV_PIX_FMT_RGB24, size, true);
+       auto grey = make_shared<Image>(AV_PIX_FMT_RGB24, size, Image::Alignment::PADDED);
        for (int y = 0; y < size.height; ++y) {
                uint8_t* p = grey->data()[0] + y * grey->stride()[0];
                for (int x = 0; x < size.width; ++x) {
@@ -90,8 +92,8 @@ BOOST_AUTO_TEST_CASE (ffmpeg_image_full_range_not_changed)
 
        write_image (grey_image(size, grey_pixel), file);
 
-       FFmpegImageProxy proxy (file, VideoRange::FULL);
-       ImageProxy::Result result = proxy.image ();
+       FFmpegImageProxy proxy (file);
+       ImageProxy::Result result = proxy.image (Image::Alignment::COMPACT);
        BOOST_REQUIRE (!result.error);
 
        for (int y = 0; y < size.height; ++y) {
@@ -105,19 +107,30 @@ BOOST_AUTO_TEST_CASE (ffmpeg_image_full_range_not_changed)
 
 BOOST_AUTO_TEST_CASE (ffmpeg_image_video_range_expanded)
 {
-       dcp::Size size(640, 480);
+       dcp::Size size(1998, 1080);
        uint8_t const grey_pixel = 128;
-       uint8_t const expanded_grey_pixel = static_cast<uint8_t>((grey_pixel - 16) * 256.0 / 219);
+       uint8_t const expanded_grey_pixel = static_cast<uint8_t>(lrintf((grey_pixel - 16) * 256.0 / 219));
        boost::filesystem::path const file = "build/test/ffmpeg_image_video_range_expanded.png";
 
-       write_image (grey_image(size, grey_pixel), file);
+       write_image(grey_image(size, grey_pixel), file);
 
-       FFmpegImageProxy proxy (file, VideoRange::VIDEO);
-       ImageProxy::Result result = proxy.image ();
-       BOOST_REQUIRE (!result.error);
+       auto content = content_factory(file);
+       auto film = new_test_film2 ("ffmpeg_image_video_range_expanded", content);
+       content[0]->video->set_range (VideoRange::VIDEO);
+       auto player = make_shared<Player>(film, film->playlist());
+
+       shared_ptr<PlayerVideo> player_video;
+       player->Video.connect([&player_video](shared_ptr<PlayerVideo> pv, dcpomatic::DCPTime) {
+               player_video = pv;
+       });
+       while (!player_video) {
+               BOOST_REQUIRE (!player->pass());
+       }
+
+       auto image = player_video->image ([](AVPixelFormat f) { return f; }, VideoRange::FULL, false);
 
        for (int y = 0; y < size.height; ++y) {
-               uint8_t* p = result.image->data()[0] + y * result.image->stride()[0];
+               uint8_t* p = image->data()[0] + y * image->stride()[0];
                for (int x = 0; x < size.width; ++x) {
                        BOOST_REQUIRE_EQUAL (*p++, expanded_grey_pixel);
                }
@@ -125,14 +138,54 @@ BOOST_AUTO_TEST_CASE (ffmpeg_image_video_range_expanded)
 }
 
 
-static optional<ContentVideo> content_video;
-
-
-static
-void
-video_handler (ContentVideo cv)
+BOOST_AUTO_TEST_CASE(yuv_expanded_into_full_rgb)
 {
-       content_video = cv;
+       auto convert = [](int y_val, int u_val, int v_val, AVPixelFormat pix_fmt) {
+               auto const size = dcp::Size(640, 480);
+               auto yuv = make_shared<Image>(AV_PIX_FMT_YUVA444P12LE, size, Image::Alignment::PADDED);
+               BOOST_REQUIRE_EQUAL(yuv->planes(), 4);
+               for (int y = 0; y < size.height; ++y) {
+                       uint16_t* Y = reinterpret_cast<uint16_t*>(yuv->data()[0] + y * yuv->stride()[0]);
+                       uint16_t* U = reinterpret_cast<uint16_t*>(yuv->data()[1] + y * yuv->stride()[1]);
+                       uint16_t* V = reinterpret_cast<uint16_t*>(yuv->data()[2] + y * yuv->stride()[2]);
+                       uint16_t* A = reinterpret_cast<uint16_t*>(yuv->data()[3] + y * yuv->stride()[3]);
+                       for (int x = 0; x < size.width; ++x) {
+                               *Y++ = y_val;
+                               *U++ = u_val;
+                               *V++ = v_val;
+                               *A++ = 4096;
+                       }
+               }
+
+               return yuv->crop_scale_window(
+                       Crop(), size, size, dcp::YUVToRGB::REC709,
+                       VideoRange::VIDEO,
+                       pix_fmt,
+                       VideoRange::FULL,
+                       Image::Alignment::COMPACT,
+                       false
+                       );
+       };
+
+       auto white24 = convert(3760, 2048, 2048, AV_PIX_FMT_RGB24);
+       BOOST_CHECK_EQUAL(white24->data()[0][0], 255);
+       BOOST_CHECK_EQUAL(white24->data()[0][1], 255);
+       BOOST_CHECK_EQUAL(white24->data()[0][2], 255);
+
+       auto black24 = convert(256, 2048, 2048, AV_PIX_FMT_RGB24);
+       BOOST_CHECK_EQUAL(black24->data()[0][0], 0);
+       BOOST_CHECK_EQUAL(black24->data()[0][1], 0);
+       BOOST_CHECK_EQUAL(black24->data()[0][2], 0);
+
+       auto white48 = convert(3760, 2048, 2048, AV_PIX_FMT_RGB48LE);
+       BOOST_CHECK_EQUAL(reinterpret_cast<uint16_t*>(white48->data()[0])[0], 65283);
+       BOOST_CHECK_EQUAL(reinterpret_cast<uint16_t*>(white48->data()[0])[1], 65283);
+       BOOST_CHECK_EQUAL(reinterpret_cast<uint16_t*>(white48->data()[0])[2], 65283);
+
+       auto black48 = convert(256, 2048, 2048, AV_PIX_FMT_RGB48LE);
+       BOOST_CHECK_EQUAL(reinterpret_cast<uint16_t*>(black48->data()[0])[0], 0);
+       BOOST_CHECK_EQUAL(reinterpret_cast<uint16_t*>(black48->data()[0])[1], 0);
+       BOOST_CHECK_EQUAL(reinterpret_cast<uint16_t*>(black48->data()[0])[2], 0);
 }
 
 
@@ -195,33 +248,23 @@ pixel_range (shared_ptr<const Image> image)
 }
 
 
+/** @return pixel range of the first frame in @ref content in its raw form, i.e.
+ *  straight out of the decoder with no level processing, scaling etc.
+ */
 static
 pair<int, int>
-pixel_range (shared_ptr<Film> film, shared_ptr<const FFmpegContent> content)
-{
-       auto decoder = make_shared<FFmpegDecoder>(film, content, false);
-       decoder->video->Data.connect (bind(&video_handler, _1));
-       content_video = boost::none;
-       while (!content_video) {
-               BOOST_REQUIRE (!decoder->pass());
-       }
-
-       return pixel_range (content_video->image->image().image);
-}
-
-
-static
-pair<int, int>
-pixel_range (shared_ptr<Film> film, shared_ptr<const ImageContent> content)
+pixel_range (shared_ptr<const Film> film, shared_ptr<const Content> content)
 {
-       auto decoder = make_shared<ImageDecoder>(film, content);
-       decoder->video->Data.connect (bind(&video_handler, _1));
-       content_video = boost::none;
+       auto decoder = decoder_factory(film, content, false, false, shared_ptr<Decoder>());
+       optional<ContentVideo> content_video;
+       decoder->video->Data.connect ([&content_video](ContentVideo cv) {
+               content_video = cv;
+       });
        while (!content_video) {
                BOOST_REQUIRE (!decoder->pass());
        }
 
-       return pixel_range (content_video->image->image().image);
+       return pixel_range (content_video->image->image(Image::Alignment::COMPACT).image);
 }
 
 
@@ -268,7 +311,7 @@ shared_ptr<Film>
 movie_V (string name)
 {
        auto film = new_test_film2 (name);
-       auto content = dynamic_pointer_cast<FFmpegContent>(content_factory("test/data/rgb_grey_testcard.mp4").front());
+       auto content = dynamic_pointer_cast<FFmpegContent>(content_factory("test/data/rgb_grey_testcard.mp4")[0]);
        BOOST_REQUIRE (content);
        film->examine_and_add_content (content);
        BOOST_REQUIRE (!wait_for_jobs());
@@ -286,7 +329,7 @@ shared_ptr<Film>
 movie_VoF (string name)
 {
        auto film = new_test_film2 (name);
-       auto content = dynamic_pointer_cast<FFmpegContent>(content_factory("test/data/rgb_grey_testcard.mp4").front());
+       auto content = dynamic_pointer_cast<FFmpegContent>(content_factory("test/data/rgb_grey_testcard.mp4")[0]);
        BOOST_REQUIRE (content);
        film->examine_and_add_content (content);
        BOOST_REQUIRE (!wait_for_jobs());
@@ -305,7 +348,7 @@ shared_ptr<Film>
 movie_F (string name)
 {
        auto film = new_test_film2 (name);
-       auto content = dynamic_pointer_cast<FFmpegContent>(content_factory("test/data/rgb_grey_testcard.mov").front());
+       auto content = dynamic_pointer_cast<FFmpegContent>(content_factory("test/data/rgb_grey_testcard.mov")[0]);
        BOOST_REQUIRE (content);
        film->examine_and_add_content (content);
        BOOST_REQUIRE (!wait_for_jobs());
@@ -323,7 +366,7 @@ shared_ptr<Film>
 movie_FoV (string name)
 {
        auto film = new_test_film2 (name);
-       auto content = dynamic_pointer_cast<FFmpegContent>(content_factory("test/data/rgb_grey_testcard.mov").front());
+       auto content = dynamic_pointer_cast<FFmpegContent>(content_factory("test/data/rgb_grey_testcard.mov")[0]);
        BOOST_REQUIRE (content);
        film->examine_and_add_content (content);
        BOOST_REQUIRE (!wait_for_jobs());
@@ -342,7 +385,7 @@ shared_ptr<Film>
 image_F (string name)
 {
        auto film = new_test_film2 (name);
-       auto content = dynamic_pointer_cast<ImageContent>(content_factory("test/data/rgb_grey_testcard.png").front());
+       auto content = dynamic_pointer_cast<ImageContent>(content_factory("test/data/rgb_grey_testcard.png")[0]);
        BOOST_REQUIRE (content);
        film->examine_and_add_content (content);
        BOOST_REQUIRE (!wait_for_jobs());
@@ -360,15 +403,18 @@ shared_ptr<Film>
 image_FoV (string name)
 {
        auto film = new_test_film2 (name);
-       auto content = dynamic_pointer_cast<ImageContent>(content_factory("test/data/rgb_grey_testcard.png").front());
+       auto content = dynamic_pointer_cast<ImageContent>(content_factory("test/data/rgb_grey_testcard.png")[0]);
        BOOST_REQUIRE (content);
        film->examine_and_add_content (content);
        BOOST_REQUIRE (!wait_for_jobs());
        content->video->set_range (VideoRange::VIDEO);
 
        auto range = pixel_range (film, content);
-       BOOST_CHECK_EQUAL (range.first, 11);
-       BOOST_CHECK_EQUAL (range.second, 250);
+       /* We are taking some full-range content and saying it should be read as video range, after which its
+        * pixels will still be full range.
+        */
+       BOOST_CHECK_EQUAL (range.first, 0);
+       BOOST_CHECK_EQUAL (range.second, 255);
 
        return film;
 }
@@ -411,9 +457,9 @@ static
 pair<int, int>
 V_movie_range (shared_ptr<Film> film)
 {
-       auto job = make_shared<TranscodeJob>(film);
+       auto job = make_shared<TranscodeJob>(film, TranscodeJob::ChangedBehaviour::IGNORE);
        job->set_encoder (
-               make_shared<FFmpegEncoder>(film, job, film->file("export.mov"), ExportFormat::PRORES, true, false, false, 23)
+               make_shared<FFmpegEncoder>(film, job, film->file("export.mov"), ExportFormat::PRORES_HQ, true, false, false, 23)
                );
        JobManager::instance()->add (job);
        BOOST_REQUIRE (!wait_for_jobs());
@@ -474,7 +520,10 @@ BOOST_AUTO_TEST_CASE (image_F_to_dcp)
 BOOST_AUTO_TEST_CASE (image_FoV_to_dcp)
 {
        auto range = dcp_range (image_FoV("image_FoV_to_dcp"));
-       check_int_close (range, {430, 4012}, 2);
+       /* The nearly-full-range of the input has become even more full, and clipped.
+        * XXX: I'm not sure why this doesn't quite hit 4095.
+        */
+       check_int_close (range, {0, 4095}, 16);
 }
 
 
@@ -521,8 +570,8 @@ BOOST_AUTO_TEST_CASE (image_F_to_V_movie)
 BOOST_AUTO_TEST_CASE (image_FoV_to_V_movie)
 {
        auto range = V_movie_range (image_FoV("image_FoV_to_V_movie"));
-       BOOST_CHECK_EQUAL (range.first, 102);
-       BOOST_CHECK_EQUAL (range.second, 923);
+       BOOST_CHECK_EQUAL (range.first, 64);
+       BOOST_CHECK_EQUAL (range.second, 960);
 }