BugFix Improve Transcoding UI and options. Remember settings of transcoding. Read file size in SystemToolkit. Ensure reading panel info after source change.

This commit is contained in:
brunoherbelin
2025-11-28 10:40:00 +01:00
parent f4ef19e529
commit 11ee04fe29
10 changed files with 120 additions and 70 deletions

View File

@@ -735,81 +735,71 @@ void ImGuiVisitor::visit (MediaSource& s)
// transcoding panel // transcoding panel
static Transcoder *transcoder = nullptr; static Transcoder *transcoder = nullptr;
static MediaSource *transcode_source = nullptr;
static bool _transcoding = false; static bool _transcoding = false;
float w_height = (!_transcoding || (transcode_source != nullptr && transcode_source != &s)) ?
if (!_transcoding) { ImGui::GetFrameHeight() : (6.1f * ImGui::GetFrameHeightWithSpacing());
if (ImGui::Button(ICON_FA_FILM " Transcoding", ImVec2(IMGUI_RIGHT_ALIGN,0)))
_transcoding = true; 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) { if (_transcoding || transcoder != nullptr) {
float w_height = 6.1f * ImGui::GetFrameHeightWithSpacing(); ImGuiToolkit::ButtonSwitch( "Backward playback", &Settings::application.transcode_options[0]);
ImGuiToolkit::ButtonSwitch( "Animation content", &Settings::application.transcode_options[1]);
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::GetColorU32(ImGuiCol_PopupBg)); ImGuiToolkit::ButtonSwitch( "Constant Quality", &Settings::application.transcode_options[2]);
ImGui::BeginChild("transcode_child", ImVec2(0, w_height), ImGuiToolkit::ButtonSwitch( "Remove audio", &Settings::application.transcode_options[3]);
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);
if (transcoder == nullptr) if (transcoder == nullptr)
{ {
if (ImGui::Button(ICON_FA_COG " Re-encode", ImVec2(IMGUI_RIGHT_ALIGN,0))) { if (ImGui::Button(ICON_FA_COG " Re-encode", ImVec2(IMGUI_RIGHT_ALIGN,0))) {
transcode_source = &s;
transcoder = new Transcoder(mp->filename()); transcoder = new Transcoder(mp->filename());
TranscoderOptions transcode_options(_optim_kf, TranscoderOptions transcode_options(Settings::application.transcode_options[0],
_optim_still ? PsyTuning::STILL_IMAGE : PsyTuning::NONE, Settings::application.transcode_options[1] ? PsyTuning::ANIMATION : PsyTuning::NONE,
_reduce_size ? 25 : -1, Settings::application.transcode_options[2] ? 19 : -1,
_no_audio); Settings::application.transcode_options[3]);
if (!transcoder->start(transcode_options)) { if (!transcoder->start(transcode_options)) {
Log::Warning("Failed to start transcoding: %s", transcoder->error().c_str()); Log::Warning("Failed to start transcoding: %s", transcoder->error().c_str());
delete transcoder; delete transcoder;
transcoder = nullptr; transcoder = nullptr;
transcode_source = nullptr;
} }
} }
ImGui::SameLine(); ImGui::SameLine();
ImGuiToolkit::HelpToolTip("Re-encode the source video\n" ImGuiToolkit::HelpToolTip("Re-encode the source video in MP4 "
"to optimize backward playback\n" "(H.264 video + AAC audio) using specific options "
"and/or reduce file size\n" "to optimize for Backward playback (add keyframes), "
"and/or remove audio track\n" "and/or optimize for Animation content, "
"and/or optimize for still images.\n\n" "and/or preserve visuals with Constant Quality encoding "
"The new file will replace the one in the source " "(produces larger files), "
"once the transcoding is successfully completed."); "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 != nullptr) {
if (transcoder->finished()) { if (transcoder->finished()) {
if (transcoder->success()) { if (transcoder->success()) {
Log::Notify("Transcoding successful : %s", transcoder->outputFilename().c_str()); Log::Notify("Transcoding successful : %s", transcoder->outputFilename().c_str());
// reload source with new file // reload source with new file
s.setPath( transcoder->outputFilename() ); transcode_source->setPath( transcoder->outputFilename() );
info.reset(); info.reset();
_transcoding = false; _transcoding = false;
} }
// all done in any case
delete transcoder; delete transcoder;
transcoder = nullptr; transcoder = nullptr;
transcode_source = nullptr;
} }
else { else {
float progress = transcoder->progress(); float progress = transcoder->progress();
@@ -819,12 +809,10 @@ void ImGuiVisitor::visit (MediaSource& s)
transcoder->stop(); transcoder->stop();
} }
} }
} }
ImGui::EndChild();
ImGui::PopStyleColor();
} }
// ImGui::PopStyleColor(); ImGui::EndChild();
ImGui::PopStyleColor();
} }
else else
ImGui::SetCursorPos(botom); ImGui::SetCursorPos(botom);

View File

@@ -13,6 +13,7 @@ class ImGuiVisitor: public Visitor
public: public:
ImGuiVisitor(); ImGuiVisitor();
inline void reset () { info.reset(); }
// Elements of Scene // Elements of Scene
void visit (Scene& n) override; void visit (Scene& n) override;

View File

@@ -512,8 +512,9 @@ void MediaPlayer::execute_open()
#endif #endif
// all good // all good
Log::Info("MediaPlayer %s Opened '%s' (%s %d x %d)", std::to_string(id_).c_str(), 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); SystemToolkit::filename(uri_).c_str(), media_.codec_name.c_str(),
media_.width, media_.height, media_.bitrate / 1000);
if (!singleFrame()) if (!singleFrame())
Log::Info("MediaPlayer %s Timeline [%ld %ld] %ld frames, %d gaps", std::to_string(id_).c_str(), Log::Info("MediaPlayer %s Timeline [%ld %ld] %ld frames, %d gaps", std::to_string(id_).c_str(),

View File

@@ -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_socket_path", application.shm_socket_path.c_str());
applicationNode->SetAttribute("shm_method", application.shm_method); applicationNode->SetAttribute("shm_method", application.shm_method);
applicationNode->SetAttribute("accept_audio", application.accept_audio); 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); pRoot->InsertEndChild(applicationNode);
// Widgets // Widgets
@@ -506,6 +514,14 @@ void Settings::Load(const std::string &filename)
applicationNode->QueryBoolAttribute("accept_audio", &application.accept_audio); applicationNode->QueryBoolAttribute("accept_audio", &application.accept_audio);
applicationNode->QueryIntAttribute("shm_method", &application.shm_method); 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 // text attributes
const char *tmpstr = applicationNode->Attribute("shm_socket_path"); const char *tmpstr = applicationNode->Attribute("shm_socket_path");
if (tmpstr) if (tmpstr)

View File

@@ -369,6 +369,9 @@ struct Application
InputMappingConfig mapping; InputMappingConfig mapping;
int gamepad_id; int gamepad_id;
// transcoding options
bool transcode_options[4];
Application() : fresh_start(false), instance_id(0), name(APP_NAME), executable(APP_NAME) { Application() : fresh_start(false), instance_id(0), name(APP_NAME), executable(APP_NAME) {
total_runtime = 0; total_runtime = 0;
scale = 1.f; scale = 1.f;
@@ -394,6 +397,14 @@ struct Application
pannel_session[0] = true; pannel_session[0] = true;
pannel_session[1] = false; pannel_session[1] = false;
pannel_session[2] = 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_view = 1;
current_workspace= 3; current_workspace= 3;
brush = glm::vec3(0.5f, 0.1f, 0.f); brush = glm::vec3(0.5f, 0.1f, 0.f);

View File

@@ -317,6 +317,20 @@ std::string SystemToolkit::file_modification_time_string(const std::string& path
return oss.str(); 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<string> &filelist, Ordering m) void SystemToolkit::reorder_file_list(std::list<string> &filelist, Ordering m)
{ {

View File

@@ -65,6 +65,9 @@ namespace SystemToolkit
unsigned long file_modification_time(const std::string& path); unsigned long file_modification_time(const std::string& path);
std::string file_modification_time_string(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 { typedef enum {
ALPHA = 0, ALPHA = 0,

View File

@@ -19,6 +19,7 @@
#include "Transcoder.h" #include "Transcoder.h"
#include "Log.h" #include "Log.h"
#include "SystemToolkit.h"
#include <sys/stat.h> #include <sys/stat.h>
#include <glib.h> #include <glib.h>
@@ -62,7 +63,7 @@ std::string Transcoder::generateOutputFilename(const std::string& input, const T
// Add force keyframes indicator // Add force keyframes indicator
if (options.force_keyframes) { if (options.force_keyframes) {
suffix += "_kf"; suffix += "_bidir";
} }
// Add psy-tune indicator // Add psy-tune indicator
@@ -163,6 +164,7 @@ bool Transcoder::start(const TranscoderOptions& options)
guint source_audio_bitrate = 0; guint source_audio_bitrate = 0;
bool has_audio = false; bool has_audio = false;
GstClockTime duration = GST_CLOCK_TIME_NONE; GstClockTime duration = GST_CLOCK_TIME_NONE;
guint frame_height = 0;
if (disc_info) { if (disc_info) {
GstDiscovererResult result = gst_discoverer_info_get_result(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) { if (source_video_bitrate == 0) {
source_video_bitrate = gst_discoverer_video_info_get_max_bitrate(vinfo); 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); gst_discoverer_stream_info_list_free(video_streams);
} else { } else {
Log::Warning("Transcoder: No video stream detected"); 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 bitrate not available from metadata, calculate from file size and duration
if (source_video_bitrate == 0 && duration != GST_CLOCK_TIME_NONE) { if (source_video_bitrate == 0 && duration != GST_CLOCK_TIME_NONE) {
struct stat st; unsigned long long file_size_bytes = SystemToolkit::file_size(input_filename_);
if (stat(input_filename_.c_str(), &st) == 0) { if (file_size_bytes > 0) {
guint64 file_size_bits = st.st_size * 8; guint64 file_size_bits = file_size_bytes * 8;
double duration_seconds = (double)duration / GST_SECOND; double duration_seconds = (double)duration / GST_SECOND;
guint total_bitrate = (guint)(file_size_bits / duration_seconds); guint total_bitrate = (guint)(file_size_bits / duration_seconds);
// Subtract audio bitrate to estimate video bitrate // Subtract audio bitrate to estimate video bitrate
source_video_bitrate = total_bitrate - source_audio_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)", Log::Info("Transcoder: Calculated video bitrate from file size: %u bps (file: %llu bytes, duration: %.2f sec)",
source_video_bitrate, st.st_size, duration_seconds); source_video_bitrate, file_size_bytes, duration_seconds);
} }
} }
// Set target bitrates (use source bitrate or reasonable defaults) // 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) 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) // Apply a quality factor (1.05 = 5% higher to ensure no quality loss)
const float quality_factor = 1.1f; const float quality_factor = 1.05f;
target_video_bitrate = (guint)(target_video_bitrate * quality_factor / 1000); // convert to kbps target_video_bitrate = (guint)(target_video_bitrate * quality_factor / 1000); // convert to kbps
Log::Info("Transcoder: Target video bitrate: %u kbps", target_video_bitrate); Log::Info("Transcoder: Target video bitrate: %u kbps", target_video_bitrate);
@@ -260,12 +263,13 @@ bool Transcoder::start(const TranscoderOptions& options)
} }
// Configure x264enc properties // 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 // Use CRF mode if specified, otherwise use bitrate mode
if (options.crf >= 0 && options.crf <= 51) { if (options.crf >= 0 && options.crf <= 51) {
g_object_set(x264_preset, "pass", 4, NULL); g_object_set(x264_preset, "pass", 5, NULL);
g_object_set(x264_preset, "quantizer", options.crf, 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); Log::Info("Transcoder: Using CRF mode with value: %d", options.crf);
} else { } else {
g_object_set(x264_preset, "pass", 0, NULL); g_object_set(x264_preset, "pass", 0, NULL);
@@ -275,7 +279,10 @@ bool Transcoder::start(const TranscoderOptions& options)
// Configure keyframes // Configure keyframes
if (options.force_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 { } else {
g_object_set(x264_preset, "key-int-max", 250, NULL); g_object_set(x264_preset, "key-int-max", 250, NULL);
} }

View File

@@ -3414,35 +3414,42 @@ void Navigator::Render()
view_pannel_visible = false; view_pannel_visible = false;
} }
static bool reset_visitor = true;
// pannel menu // pannel menu
if (selected_button[NAV_MENU]) if (selected_button[NAV_MENU])
{ {
RenderMainPannel(iconsize); RenderMainPannel(iconsize);
reset_visitor = true;
} }
// pannel to manage transition // pannel to manage transition
else if (selected_button[NAV_TRANS]) else if (selected_button[NAV_TRANS])
{ {
RenderTransitionPannel(iconsize); RenderTransitionPannel(iconsize);
reset_visitor = true;
} }
// pannel to create a source // pannel to create a source
else if (selected_button[NAV_NEW]) else if (selected_button[NAV_NEW])
{ {
RenderNewPannel(iconsize); RenderNewPannel(iconsize);
reset_visitor = true;
} }
// pannel to configure a selected source // pannel to configure a selected source
else else
{ {
if ( selected_index < 0 ) if ( selected_index < 0 ) {
showPannelSource(NAV_MENU); showPannelSource(NAV_MENU);
}
// most often, render current sources // most often, render current sources
else if ( selected_index == Mixer::manager().indexCurrentSource()) 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 // rarely its not the current source that is selected
else { else {
SourceList::iterator cs = Mixer::manager().session()->at( selected_index ); SourceList::iterator cs = Mixer::manager().session()->at( selected_index );
if (cs != Mixer::manager().session()->end() ) 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 // 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) if (s == nullptr || Settings::application.current_view == View::TRANSITION)
return; return;
@@ -3522,6 +3529,8 @@ void Navigator::RenderSourcePannel(Source *s, const ImVec2 &iconsize)
// Source pannel // Source pannel
static ImGuiVisitor v; static ImGuiVisitor v;
if (reset)
v.reset();
s->accept(v); s->accept(v);
/// ///

View File

@@ -64,7 +64,7 @@ class Navigator
void applyButtonSelection(int index); void applyButtonSelection(int index);
// side pannels // 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 RenderMainPannel(const ImVec2 &iconsize);
void RenderMainPannelSession(); void RenderMainPannelSession();
void RenderMainPannelPlaylist(); void RenderMainPannelPlaylist();