From 5394cbf8c313f4f801f6642d0d3824c7d72bf80f Mon Sep 17 00:00:00 2001 From: omigamedev Date: Tue, 26 May 2020 08:58:52 +0200 Subject: [PATCH] fix export animation frames and add export mp4 --- data/layout.xml | 3 + src/app.h | 1 + src/app_dialogs.cpp | 214 ++++++++----------------------------- src/app_layout.cpp | 9 +- src/canvas.cpp | 67 +++++++++++- src/canvas.h | 4 + src/node_panel_animation.h | 1 + 7 files changed, 129 insertions(+), 170 deletions(-) diff --git a/data/layout.xml b/data/layout.xml index f43d180..b1015e3 100644 --- a/data/layout.xml +++ b/data/layout.xml @@ -171,6 +171,9 @@ + + + diff --git a/src/app.h b/src/app.h index d8fb0cd..8d3a90b 100644 --- a/src/app.h +++ b/src/app.h @@ -254,6 +254,7 @@ public: void dialog_browse(); void dialog_export(std::string ext); void dialog_export_layers(); + void dialog_export_anim_frames(); void dialog_export_depth(); void dialog_export_cube_faces(); void dialog_layer_rename(); diff --git a/src/app_dialogs.cpp b/src/app_dialogs.cpp index 263ae03..39c980b 100644 --- a/src/app_dialogs.cpp +++ b/src/app_dialogs.cpp @@ -496,6 +496,36 @@ void App::dialog_export_layers() } } +void App::dialog_export_anim_frames() +{ + if (!check_license()) + { + message_box("License", "This function is disabled in demo mode."); + return; + } + + if (canvas) + { +#if defined(__IOS__) + auto dir = work_path + "/" + doc_name + "_frames"; + if (Asset::create_dir(dir)) + { + auto p = dir + "/" + doc_name; + canvas->m_canvas->export_anim_frames(p, [this, p] { + message_box("Export Layers", "Image layers exported to Files/PanoPainter"); + }); + } +#else + pick_dir([this](std::string path) { + auto p = path + "/" + doc_name; + canvas->m_canvas->export_anim_frames(p, [this, p] { + message_box("Export Layers", "Layers exported to: " + p); + }); + }); +#endif + } +} + void App::dialog_export_depth() { if (!check_license()) @@ -657,7 +687,7 @@ void App::dialog_timelapse_export() rec_export(path); }, [this](const std::string& path, bool saved) { - message_box("Export Timelapse", "Timelapse exported succesfully."); + message_box("Export Timelapse", "Timelapse exported successfully."); } ); #else @@ -673,174 +703,22 @@ void App::dialog_timelapse_export() void App::dialog_export_mp4() { - std::thread([this] { - BT_SetTerminate(); - const int video_width = 1024; - const int video_height = 512; - const int video_frames = 500; - - auto pb = show_progress("Export MP4", video_frames); - - ISVCEncoder* encoder; - int rv = WelsCreateSVCEncoder(&encoder); - - //SEncParamBase param; - //memset(¶m, 0, sizeof(SEncParamBase)); - //param.iUsageType = EUsageType::CAMERA_VIDEO_REAL_TIME; //from EUsageType enum - //param.fMaxFrameRate = 25; - //param.iPicWidth = 400; - //param.iPicHeight = 400; - //param.iTargetBitrate = 5000000; - //param.iRCMode = RC_TIMESTAMP_MODE; - //encoder->Initialize(¶m); - - //Encoder params - SEncParamExt param; - encoder->GetDefaultParams(¶m); - param.iUsageType = CAMERA_VIDEO_REAL_TIME; - param.fMaxFrameRate = 25.f; - param.iLtrMarkPeriod = 75; - param.iPicWidth = 1024; - param.iPicHeight = 512; - param.iTargetBitrate = 1000 << 10; - param.bEnableDenoise = false; - param.iSpatialLayerNum = 1; - param.bUseLoadBalancing = false; - param.bEnableSceneChangeDetect = false; - param.bEnableBackgroundDetection = false; - param.bEnableAdaptiveQuant = false; - param.bEnableFrameSkip = false; - param.iMultipleThreadIdc = 0; - //param.uiIntraPeriod = 10; - - for (int i = 0; i < param.iSpatialLayerNum; i++) - { - param.sSpatialLayers[i].iVideoWidth = param.iPicWidth >> (param.iSpatialLayerNum - 1 - i); - param.sSpatialLayers[i].iVideoHeight = param.iPicHeight >> (param.iSpatialLayerNum - 1 - i); - param.sSpatialLayers[i].fFrameRate = 25.f; - param.sSpatialLayers[i].iSpatialBitrate = param.iTargetBitrate; - param.sSpatialLayers[i].uiProfileIdc = PRO_BASELINE; - param.sSpatialLayers[i].uiLevelIdc = LEVEL_4_2; - param.sSpatialLayers[i].iDLayerQp = 42; - - //SSliceArgument sliceArg; - //sliceArg.uiSliceMode = SM_FIXEDSLCNUM_SLICE; - //sliceArg.uiSliceNum = 0; - //param.sSpatialLayers[i].sSliceArgument = sliceArg; +#if __IOS__ || __WEB__ + pick_file_save("mp4", doc_name + "-animation", + [this](std::string path) { + export_anim_mp4(path); + }, + [this](const std::string& path, bool saved) { + message_box("Export Animation", "Animation exported successfully."); } - - param.uiMaxNalSize = 1500; - param.iTargetBitrate *= param.iSpatialLayerNum; - encoder->InitializeExt(¶m); - - - int trace_level = WELS_LOG_ERROR; - encoder->SetOption(ENCODER_OPTION_TRACE_LEVEL, &trace_level); - int videoFormat = videoFormatI420; - encoder->SetOption(ENCODER_OPTION_DATAFORMAT, &videoFormat); - - int frameSize = param.iPicWidth * param.iPicHeight * 3 / 2; - std::vector buf(frameSize); - - SFrameBSInfo info; - memset(&info, 0, sizeof(SFrameBSInfo)); - SSourcePicture pic; - memset(&pic, 0, sizeof(SSourcePicture)); - pic.iPicWidth = param.iPicWidth; - pic.iPicHeight = param.iPicHeight; - pic.iColorFormat = videoFormatI420; - pic.iStride[0] = pic.iPicWidth; - pic.iStride[1] = pic.iStride[2] = pic.iPicWidth >> 1; - pic.pData[0] = buf.data(); - pic.pData[1] = pic.pData[0] + (param.iPicWidth * param.iPicHeight); - pic.pData[2] = pic.pData[1] + (param.iPicWidth * param.iPicHeight >> 2); - - std::string mp4_path = data_path + "/out.mp4"; - - MP4FileHandle mp4 = MP4Create(mp4_path.c_str()); - MP4TrackId mp4_track = -1; - MP4SetTimeScale(mp4, 90000); - - const int frames = 500; - //std::ofstream f("out.h264", std::ios::binary); - for (int num = 0; num < frames; num++) - { - pb->increment(); - printf("encoding %.2f%%\r", (float)num / (float)(frames - 1) * 100.f); - - float value = sinf((float)num / (float)frames * 10.f); - for (int y = 0; y < param.iPicHeight; y++) - for (int x = 0; x < param.iPicWidth; x++) - buf[y * param.iPicWidth + x] = (((y + num / 4) / 10) % 2) * (255.f * value); - - //prepare input data - rv = encoder->EncodeFrame(&pic, &info); - //pic.uiTimeStamp += (1.f/25.f) * 1000.f; - //assert (rv == cmResultSuccess); - if (info.eFrameType != videoFrameTypeSkip) - { - //output bitstream handling - for (int layer = 0; layer < info.iLayerNum; layer++) - { - size_t bs_size = 0; - for (int nal = 0; nal < info.sLayerInfo[layer].iNalCount; nal++) - { - std::array nalu_bytes; - for (int i = 0; i < nalu_bytes.size(); i++) - nalu_bytes[i] = info.sLayerInfo[layer].pBsBuf[bs_size + i]; - if (nalu_bytes[4] == 0x67) // SPS - { - uint8_t avc_profile = info.sLayerInfo[layer].pBsBuf[bs_size + 5]; - uint8_t avc_profile_compat = info.sLayerInfo[layer].pBsBuf[bs_size + 6]; - uint8_t avc_level = info.sLayerInfo[layer].pBsBuf[bs_size + 7]; - if (mp4_track == -1) - { - mp4_track = MP4AddH264VideoTrack(mp4, 90000, 90000 / 25, - param.iPicWidth, param.iPicHeight, - avc_profile, avc_profile_compat, avc_level, 3); - MP4SetVideoProfileLevel(mp4, 1); - } - MP4AddH264SequenceParameterSet(mp4, mp4_track, info.sLayerInfo[layer].pBsBuf + bs_size + 4, - info.sLayerInfo[layer].pNalLengthInByte[nal] - 4); - } - else if (nalu_bytes[4] == 0x68) // PPS - { - MP4AddH264PictureParameterSet(mp4, mp4_track, info.sLayerInfo[layer].pBsBuf + bs_size + 4, - info.sLayerInfo[layer].pNalLengthInByte[nal] - 4); - } - else - { - int nalu_sz = info.sLayerInfo[layer].pNalLengthInByte[nal]; - uint8_t* data = info.sLayerInfo[layer].pBsBuf + bs_size; - *(uint32_t*)data = BinaryStream::htonx(nalu_sz - 4); - bool sync = false; - if (nalu_bytes[4] == 0x65) // I-frame - sync = true; - else // 0x61 P-frame - sync = false; - MP4WriteSample(mp4, mp4_track, data, nalu_sz, MP4_INVALID_DURATION, 0, sync); - } - //printf("nalu %x\n", nalu_bytes[4]); - bs_size += info.sLayerInfo[layer].pNalLengthInByte[nal]; - } - //f.write((const char*)info.sLayerInfo[layer].pBsBuf, bs_size); - } - } - } - //f.close(); - - printf("\n"); - - MP4Close(mp4); - - if (encoder) - { - encoder->Uninitialize(); - WelsDestroySVCEncoder(encoder); - } - - pb->destroy(); - }).detach(); + ); +#else + pick_file_save({ "mp4" }, [this](std::string path) { + Canvas::I->export_anim_mp4(path, [this, path] { + message_box("Export Animation", "Animation exported to: " + path); + }); + }); +#endif } void App::dialog_whatsnew(bool force_show) diff --git a/src/app_layout.cpp b/src/app_layout.cpp index 859e971..8840278 100644 --- a/src/app_layout.cpp +++ b/src/app_layout.cpp @@ -654,7 +654,14 @@ void App::init_menu_file() subpopup->destroy(); }; subpopup->find("file-submenu-export-anim")->on_click = [this, subpopup, popup](Node*) { - dialog_export_layers(); + dialog_export_anim_frames(); + popup->mouse_release(); + popup->destroy(); + subpopup->mouse_release(); + subpopup->destroy(); + }; + subpopup->find("file-submenu-export-anim-mp4")->on_click = [this, subpopup, popup](Node*) { + dialog_export_mp4(); popup->mouse_release(); popup->destroy(); subpopup->mouse_release(); diff --git a/src/canvas.cpp b/src/canvas.cpp index 34c4bdf..6804bf5 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -2023,6 +2023,71 @@ void Canvas::export_layers_thread(std::string path) pb->destroy(); } +void Canvas::export_anim_frames(std::string path, std::function on_complete) +{ + if (App::I->check_license()) + { + std::thread t([=] { + BT_SetTerminate(); + export_anim_frames_thread(path); + if (on_complete) + on_complete(); + }); + t.detach(); + } +} + +void Canvas::export_anim_frames_thread(std::string path) +{ + auto pb = App::I->show_progress("Export Frames", anim_duration()); + for (int i = 0; i < anim_duration(); i++) + { + anim_goto_frame(i); + export_equirectangular_thread(fmt::format("{}-{:02d}.png", path, i)); + pb->increment(); + } + pb->destroy(); +} + +void Canvas::export_anim_mp4(std::string path, std::function on_complete) +{ + if (App::I->check_license()) + { + std::thread t([=] { + BT_SetTerminate(); + export_anim_mp4_thread(path); + if (on_complete) + on_complete(); + }); + t.detach(); + } +} + +void Canvas::export_anim_mp4_thread(std::string path) +{ + auto pb = App::I->show_progress("Export Animation", anim_duration()); + int fps = App::I->animation->get_fps(); + MP4Encoder mp4; + int res = std::min(1024, m_width); + mp4.init(res * 4, res * 2, 30, 2 << 20); + for (int i = 0; i < anim_duration(); i++) + { + Image data; + App::I->render_task([&] + { + anim_goto_frame(i); + draw_merge(false); + Texture2D equirect = m_layers_merge.gen_equirect({ res, res }); + data = equirect.get_image(); + }); + for (int j = 0; j < 30/fps; j++) + mp4.encode(data); + pb->increment(); + } + mp4.write_mp4(path); + pb->destroy(); +} + void Canvas::export_cube_faces(std::string file_name, std::function on_complete) { if (App::I->check_license()) @@ -2032,7 +2097,7 @@ void Canvas::export_cube_faces(std::string file_name, std::function on_c export_cube_faces_thread(file_name); if (on_complete) on_complete(); - }); + }); t.detach(); } } diff --git a/src/canvas.h b/src/canvas.h index 251fb41..a79d350 100644 --- a/src/canvas.h +++ b/src/canvas.h @@ -229,6 +229,10 @@ public: void export_equirectangular_thread(std::string file_path); void export_layers(std::string path, std::function on_complete = nullptr); void export_layers_thread(std::string path); + void export_anim_frames(std::string path, std::function on_complete = nullptr); + void export_anim_frames_thread(std::string path); + void export_anim_mp4(std::string path, std::function on_complete = nullptr); + void export_anim_mp4_thread(std::string path); void export_depth(std::string file_name, std::function on_complete = nullptr); void export_depth_thread(std::string file_name); void export_cube_faces(std::string file_name, std::function on_complete); diff --git a/src/node_panel_animation.h b/src/node_panel_animation.h index aa5cd9b..8ba671b 100644 --- a/src/node_panel_animation.h +++ b/src/node_panel_animation.h @@ -79,6 +79,7 @@ public: void load_layers(); void update_frames(); int get_onion_size() const noexcept { return m_onion->get_int(); } + int get_fps() const noexcept { return m_fps->get_int(); } }; //////////////////////////////////////////////////////////////////////////