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
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;
}
if (_transcoding || transcoder != nullptr) {
float w_height = 6.1f * ImGui::GetFrameHeightWithSpacing();
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 (transcoder != nullptr) {
ImGuiToolkit::Icon(12,11);
} else {
if (ImGuiToolkit::IconButton(4,16))
_transcoding = false;
}
ImGui::SameLine();
ImGui::Text(ICON_FA_FILM " Transcoding ");
if ( ImGui::Selectable( (_transcoding ? ICON_FA_CHEVRON_DOWN " Transcoding": " " ICON_FA_CHEVRON_RIGHT " Transcoding"), false,
(transcoder != nullptr ? ImGuiSelectableFlags_Disabled : 0)) )
_transcoding = !_transcoding;
ImGui::EndMenuBar();
}
static bool _optim_kf = true;
static bool _optim_still = false;
static bool _reduce_size = false;
static bool _no_audio = false;
if (_transcoding || transcoder != nullptr) {
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,13 +809,11 @@ void ImGuiVisitor::visit (MediaSource& s)
transcoder->stop();
}
}
}
}
ImGui::EndChild();
ImGui::PopStyleColor();
}
// ImGui::PopStyleColor();
}
else
ImGui::SetCursorPos(botom);
}

View File

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

View File

@@ -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(),

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_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)

View File

@@ -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);

View File

@@ -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<string> &filelist, Ordering m)
{

View File

@@ -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,

View File

@@ -19,6 +19,7 @@
#include "Transcoder.h"
#include "Log.h"
#include "SystemToolkit.h"
#include <sys/stat.h>
#include <glib.h>
@@ -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, "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);
}

View File

@@ -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);
///

View File

@@ -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();