diff --git a/src/ImGuiVisitor.cpp b/src/ImGuiVisitor.cpp index 9637b21..f42c5da 100644 --- a/src/ImGuiVisitor.cpp +++ b/src/ImGuiVisitor.cpp @@ -735,81 +735,71 @@ void ImGuiVisitor::visit (MediaSource& s) // transcoding panel static Transcoder *transcoder = nullptr; + static MediaSource *transcode_source = nullptr; static bool _transcoding = false; - - if (!_transcoding) { - if (ImGui::Button(ICON_FA_FILM " Transcoding", ImVec2(IMGUI_RIGHT_ALIGN,0))) - _transcoding = true; + float w_height = (!_transcoding || (transcode_source != nullptr && transcode_source != &s)) ? + ImGui::GetFrameHeight() : (6.1f * ImGui::GetFrameHeightWithSpacing()); + + ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::GetColorU32(ImGuiCol_PopupBg)); + ImGui::BeginChild("transcode_child", ImVec2(0, w_height), + true, ImGuiWindowFlags_MenuBar); + if (ImGui::BeginMenuBar()) + { + if ( ImGui::Selectable( (_transcoding ? ICON_FA_CHEVRON_DOWN " Transcoding": " " ICON_FA_CHEVRON_RIGHT " Transcoding"), false, + (transcoder != nullptr ? ImGuiSelectableFlags_Disabled : 0)) ) + _transcoding = !_transcoding; + ImGui::EndMenuBar(); } if (_transcoding || transcoder != nullptr) { - float w_height = 6.1f * ImGui::GetFrameHeightWithSpacing(); - - ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::GetColorU32(ImGuiCol_PopupBg)); - ImGui::BeginChild("transcode_child", ImVec2(0, w_height), - true, ImGuiWindowFlags_MenuBar); - - if (ImGui::BeginMenuBar()) - { - if (transcoder != nullptr) { - ImGuiToolkit::Icon(12,11); - } else { - if (ImGuiToolkit::IconButton(4,16)) - _transcoding = false; - } - ImGui::SameLine(); - ImGui::Text(ICON_FA_FILM " Transcoding "); - ImGui::EndMenuBar(); - } - - static bool _optim_kf = true; - static bool _optim_still = false; - static bool _reduce_size = false; - static bool _no_audio = false; - - ImGuiToolkit::ButtonSwitch( "Backward playback", &_optim_kf); - ImGuiToolkit::ButtonSwitch( "Still images content", &_optim_still); - ImGuiToolkit::ButtonSwitch( "Remove audio", &_no_audio); - ImGuiToolkit::ButtonSwitch( "Reduce size", &_reduce_size); + ImGuiToolkit::ButtonSwitch( "Backward playback", &Settings::application.transcode_options[0]); + ImGuiToolkit::ButtonSwitch( "Animation content", &Settings::application.transcode_options[1]); + ImGuiToolkit::ButtonSwitch( "Constant Quality", &Settings::application.transcode_options[2]); + ImGuiToolkit::ButtonSwitch( "Remove audio", &Settings::application.transcode_options[3]); if (transcoder == nullptr) { if (ImGui::Button(ICON_FA_COG " Re-encode", ImVec2(IMGUI_RIGHT_ALIGN,0))) { + transcode_source = &s; transcoder = new Transcoder(mp->filename()); - TranscoderOptions transcode_options(_optim_kf, - _optim_still ? PsyTuning::STILL_IMAGE : PsyTuning::NONE, - _reduce_size ? 25 : -1, - _no_audio); + TranscoderOptions transcode_options(Settings::application.transcode_options[0], + Settings::application.transcode_options[1] ? PsyTuning::ANIMATION : PsyTuning::NONE, + Settings::application.transcode_options[2] ? 19 : -1, + Settings::application.transcode_options[3]); if (!transcoder->start(transcode_options)) { Log::Warning("Failed to start transcoding: %s", transcoder->error().c_str()); delete transcoder; transcoder = nullptr; + transcode_source = nullptr; } } ImGui::SameLine(); - ImGuiToolkit::HelpToolTip("Re-encode the source video\n" - "to optimize backward playback\n" - "and/or reduce file size\n" - "and/or remove audio track\n" - "and/or optimize for still images.\n\n" - "The new file will replace the one in the source " - "once the transcoding is successfully completed."); + ImGuiToolkit::HelpToolTip("Re-encode the source video in MP4 " + "(H.264 video + AAC audio) using specific options " + "to optimize for Backward playback (add keyframes), " + "and/or optimize for Animation content, " + "and/or preserve visuals with Constant Quality encoding " + "(produces larger files), " + "and/or Remove the audio track.\n\n " + ICON_FA_FILM " The new file will replace the one in the source " + "once the transcoding is successfully completed. " + "The current file remains untouched."); } if (transcoder != nullptr) { if (transcoder->finished()) { - if (transcoder->success()) { Log::Notify("Transcoding successful : %s", transcoder->outputFilename().c_str()); // reload source with new file - s.setPath( transcoder->outputFilename() ); + transcode_source->setPath( transcoder->outputFilename() ); info.reset(); _transcoding = false; } - + // all done in any case delete transcoder; transcoder = nullptr; + transcode_source = nullptr; } else { float progress = transcoder->progress(); @@ -819,12 +809,10 @@ void ImGuiVisitor::visit (MediaSource& s) transcoder->stop(); } } - } - ImGui::EndChild(); - ImGui::PopStyleColor(); } - // ImGui::PopStyleColor(); + ImGui::EndChild(); + ImGui::PopStyleColor(); } else ImGui::SetCursorPos(botom); diff --git a/src/ImGuiVisitor.h b/src/ImGuiVisitor.h index 2874eae..a7c7499 100644 --- a/src/ImGuiVisitor.h +++ b/src/ImGuiVisitor.h @@ -13,6 +13,7 @@ class ImGuiVisitor: public Visitor public: ImGuiVisitor(); + inline void reset () { info.reset(); } // Elements of Scene void visit (Scene& n) override; diff --git a/src/MediaPlayer.cpp b/src/MediaPlayer.cpp index e7f4a84..4bc36d4 100644 --- a/src/MediaPlayer.cpp +++ b/src/MediaPlayer.cpp @@ -512,8 +512,9 @@ void MediaPlayer::execute_open() #endif // all good - Log::Info("MediaPlayer %s Opened '%s' (%s %d x %d)", std::to_string(id_).c_str(), - SystemToolkit::filename(uri_).c_str(), media_.codec_name.c_str(), media_.width, media_.height); + Log::Info("MediaPlayer %s Opened '%s' (%s %d x %d, %d kbps)", std::to_string(id_).c_str(), + SystemToolkit::filename(uri_).c_str(), media_.codec_name.c_str(), + media_.width, media_.height, media_.bitrate / 1000); if (!singleFrame()) Log::Info("MediaPlayer %s Timeline [%ld %ld] %ld frames, %d gaps", std::to_string(id_).c_str(), diff --git a/src/Settings.cpp b/src/Settings.cpp index f9b0d58..1657198 100644 --- a/src/Settings.cpp +++ b/src/Settings.cpp @@ -170,6 +170,14 @@ void Settings::Save(uint64_t runtime, const std::string &filename) applicationNode->SetAttribute("shm_socket_path", application.shm_socket_path.c_str()); applicationNode->SetAttribute("shm_method", application.shm_method); applicationNode->SetAttribute("accept_audio", application.accept_audio); + + XMLElement *transcodeNode = xmlDoc.NewElement( "Transcode" ); + transcodeNode->SetAttribute("option_0", application.transcode_options[0]); + transcodeNode->SetAttribute("option_1", application.transcode_options[1]); + transcodeNode->SetAttribute("option_2", application.transcode_options[2]); + transcodeNode->SetAttribute("option_3", application.transcode_options[3]); + applicationNode->InsertEndChild(transcodeNode); + pRoot->InsertEndChild(applicationNode); // Widgets @@ -506,6 +514,14 @@ void Settings::Load(const std::string &filename) applicationNode->QueryBoolAttribute("accept_audio", &application.accept_audio); applicationNode->QueryIntAttribute("shm_method", &application.shm_method); + XMLElement * transcodeNode = applicationNode->FirstChildElement("Transcode"); + if (transcodeNode != nullptr) { + transcodeNode->QueryBoolAttribute("option_0", &application.transcode_options[0]) ; + transcodeNode->QueryBoolAttribute("option_1", &application.transcode_options[1]) ; + transcodeNode->QueryBoolAttribute("option_2", &application.transcode_options[2]) ; + transcodeNode->QueryBoolAttribute("option_3", &application.transcode_options[3]) ; + } + // text attributes const char *tmpstr = applicationNode->Attribute("shm_socket_path"); if (tmpstr) diff --git a/src/Settings.h b/src/Settings.h index b8b779b..4a03bdd 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -369,6 +369,9 @@ struct Application InputMappingConfig mapping; int gamepad_id; + // transcoding options + bool transcode_options[4]; + Application() : fresh_start(false), instance_id(0), name(APP_NAME), executable(APP_NAME) { total_runtime = 0; scale = 1.f; @@ -394,6 +397,14 @@ struct Application pannel_session[0] = true; pannel_session[1] = false; pannel_session[2] = false; + pannel_settings[0] = false; + pannel_settings[1] = false; + pannel_settings[2] = false; + pannel_settings[3] = false; + transcode_options[0] = true; + transcode_options[1] = false; + transcode_options[2] = false; + transcode_options[3] = false; current_view = 1; current_workspace= 3; brush = glm::vec3(0.5f, 0.1f, 0.f); diff --git a/src/SystemToolkit.cpp b/src/SystemToolkit.cpp index e8c19eb..65851bd 100644 --- a/src/SystemToolkit.cpp +++ b/src/SystemToolkit.cpp @@ -317,6 +317,20 @@ std::string SystemToolkit::file_modification_time_string(const std::string& path return oss.str(); } +unsigned long long SystemToolkit::file_size(const std::string& path) +{ + if (file_exists(path)) { + struct stat statsfile; + // fill statistics of given file path + if( stat( path.c_str(), &statsfile) > -1 ) { + // return file size in bytes + return (unsigned long long) statsfile.st_size; + } + } + + return 0; +} + void SystemToolkit::reorder_file_list(std::list &filelist, Ordering m) { diff --git a/src/SystemToolkit.h b/src/SystemToolkit.h index 5aaacd4..8a006b5 100644 --- a/src/SystemToolkit.h +++ b/src/SystemToolkit.h @@ -65,6 +65,9 @@ namespace SystemToolkit unsigned long file_modification_time(const std::string& path); std::string file_modification_time_string(const std::string& path); + // Get file size in bytes, returns 0 if file doesn't exist or on error + unsigned long long file_size(const std::string& path); + typedef enum { ALPHA = 0, diff --git a/src/Transcoder.cpp b/src/Transcoder.cpp index a9d2df3..256c838 100644 --- a/src/Transcoder.cpp +++ b/src/Transcoder.cpp @@ -19,6 +19,7 @@ #include "Transcoder.h" #include "Log.h" +#include "SystemToolkit.h" #include #include @@ -62,7 +63,7 @@ std::string Transcoder::generateOutputFilename(const std::string& input, const T // Add force keyframes indicator if (options.force_keyframes) { - suffix += "_kf"; + suffix += "_bidir"; } // Add psy-tune indicator @@ -163,6 +164,7 @@ bool Transcoder::start(const TranscoderOptions& options) guint source_audio_bitrate = 0; bool has_audio = false; GstClockTime duration = GST_CLOCK_TIME_NONE; + guint frame_height = 0; if (disc_info) { GstDiscovererResult result = gst_discoverer_info_get_result(disc_info); @@ -189,6 +191,7 @@ bool Transcoder::start(const TranscoderOptions& options) if (source_video_bitrate == 0) { source_video_bitrate = gst_discoverer_video_info_get_max_bitrate(vinfo); } + frame_height = gst_discoverer_video_info_get_height(vinfo); gst_discoverer_stream_info_list_free(video_streams); } else { Log::Warning("Transcoder: No video stream detected"); @@ -217,24 +220,24 @@ bool Transcoder::start(const TranscoderOptions& options) // If bitrate not available from metadata, calculate from file size and duration if (source_video_bitrate == 0 && duration != GST_CLOCK_TIME_NONE) { - struct stat st; - if (stat(input_filename_.c_str(), &st) == 0) { - guint64 file_size_bits = st.st_size * 8; + unsigned long long file_size_bytes = SystemToolkit::file_size(input_filename_); + if (file_size_bytes > 0) { + guint64 file_size_bits = file_size_bytes * 8; double duration_seconds = (double)duration / GST_SECOND; guint total_bitrate = (guint)(file_size_bits / duration_seconds); // Subtract audio bitrate to estimate video bitrate source_video_bitrate = total_bitrate - source_audio_bitrate; - Log::Info("Transcoder: Calculated video bitrate from file size: %u bps (file: %ld bytes, duration: %.2f sec)", - source_video_bitrate, st.st_size, duration_seconds); - } + Log::Info("Transcoder: Calculated video bitrate from file size: %u bps (file: %llu bytes, duration: %.2f sec)", + source_video_bitrate, file_size_bytes, duration_seconds); + } } // Set target bitrates (use source bitrate or reasonable defaults) guint target_video_bitrate = source_video_bitrate > 0 ? source_video_bitrate : 5000000; // 5 Mbps default (in bps) - // Apply a quality factor (1.1 = 10% higher to ensure no quality loss) - const float quality_factor = 1.1f; + // Apply a quality factor (1.05 = 5% higher to ensure no quality loss) + const float quality_factor = 1.05f; target_video_bitrate = (guint)(target_video_bitrate * quality_factor / 1000); // convert to kbps Log::Info("Transcoder: Target video bitrate: %u kbps", target_video_bitrate); @@ -260,12 +263,13 @@ bool Transcoder::start(const TranscoderOptions& options) } // Configure x264enc properties - g_object_set(x264_preset, "speed-preset", 3, NULL); // medium + g_object_set(x264_preset, "speed-preset", 5, NULL); // fast // Use CRF mode if specified, otherwise use bitrate mode if (options.crf >= 0 && options.crf <= 51) { - g_object_set(x264_preset, "pass", 4, NULL); - g_object_set(x264_preset, "quantizer", options.crf, NULL); + g_object_set(x264_preset, "pass", 5, NULL); + g_object_set(x264_preset, "quantizer", options.crf, NULL); + g_object_set(x264_preset, "bitrate", 2 * target_video_bitrate, NULL); // kbps Log::Info("Transcoder: Using CRF mode with value: %d", options.crf); } else { g_object_set(x264_preset, "pass", 0, NULL); @@ -275,7 +279,10 @@ bool Transcoder::start(const TranscoderOptions& options) // Configure keyframes if (options.force_keyframes) { - g_object_set(x264_preset, "key-int-max", 30, NULL); + g_object_set(x264_preset, "key-int-max", + frame_height > 1400 ? 15 : 30, NULL); + Log::Info("Transcoder: Add a keyframe every %d frames", frame_height > 1400 ? 15 : 30); + } else { g_object_set(x264_preset, "key-int-max", 250, NULL); } diff --git a/src/UserInterfaceManager.cpp b/src/UserInterfaceManager.cpp index 8e7c241..3824e19 100644 --- a/src/UserInterfaceManager.cpp +++ b/src/UserInterfaceManager.cpp @@ -3414,35 +3414,42 @@ void Navigator::Render() view_pannel_visible = false; } + static bool reset_visitor = true; + // pannel menu if (selected_button[NAV_MENU]) { RenderMainPannel(iconsize); + reset_visitor = true; } // pannel to manage transition else if (selected_button[NAV_TRANS]) { RenderTransitionPannel(iconsize); + reset_visitor = true; } // pannel to create a source else if (selected_button[NAV_NEW]) { RenderNewPannel(iconsize); + reset_visitor = true; } // pannel to configure a selected source else { - if ( selected_index < 0 ) + if ( selected_index < 0 ) { showPannelSource(NAV_MENU); + } // most often, render current sources else if ( selected_index == Mixer::manager().indexCurrentSource()) - RenderSourcePannel(Mixer::manager().currentSource(), iconsize); + RenderSourcePannel(Mixer::manager().currentSource(), iconsize, reset_visitor); // rarely its not the current source that is selected else { SourceList::iterator cs = Mixer::manager().session()->at( selected_index ); if (cs != Mixer::manager().session()->end() ) - RenderSourcePannel(*cs, iconsize); + RenderSourcePannel(*cs, iconsize, reset_visitor); } + reset_visitor = false; } } @@ -3486,7 +3493,7 @@ void Navigator::RenderViewOptions(uint *timeout, const ImVec2 &pos, const ImVec2 } // Source pannel : *s was checked before -void Navigator::RenderSourcePannel(Source *s, const ImVec2 &iconsize) +void Navigator::RenderSourcePannel(Source *s, const ImVec2 &iconsize, bool reset) { if (s == nullptr || Settings::application.current_view == View::TRANSITION) return; @@ -3522,6 +3529,8 @@ void Navigator::RenderSourcePannel(Source *s, const ImVec2 &iconsize) // Source pannel static ImGuiVisitor v; + if (reset) + v.reset(); s->accept(v); /// diff --git a/src/UserInterfaceManager.h b/src/UserInterfaceManager.h index 8f44ee7..90a3d89 100644 --- a/src/UserInterfaceManager.h +++ b/src/UserInterfaceManager.h @@ -64,7 +64,7 @@ class Navigator void applyButtonSelection(int index); // side pannels - void RenderSourcePannel(Source *s, const ImVec2 &iconsize); + void RenderSourcePannel(Source *s, const ImVec2 &iconsize, bool reset = false); void RenderMainPannel(const ImVec2 &iconsize); void RenderMainPannelSession(); void RenderMainPannelPlaylist();