Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed Best Fit to consider minimum subtitle duration. #52

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
379 changes: 206 additions & 173 deletions plugins/actions/bestfit/bestfit.cc
Original file line number Diff line number Diff line change
Expand Up @@ -33,186 +33,219 @@ class BestFitPlugin : public Action {
update_ui();
}

~BestFitPlugin() {
deactivate();
}

void activate() {
se_dbg(SE_DBG_PLUGINS);

// actions
action_group = Gtk::ActionGroup::create("BestFitPlugin");

action_group->add(Gtk::Action::create(
"best-fit", _("_Best Fit Subtitles"),
_("Best fit the selected subtitles between the start "
"of the first and the end of the last one.")),
sigc::mem_fun(*this, &BestFitPlugin::on_best_fit));

// ui
Glib::RefPtr<Gtk::UIManager> ui = get_ui_manager();

ui_id = ui->new_merge_id();

ui->insert_action_group(action_group);

ui->add_ui(ui_id, "/menubar/menu-timings/best-fit", "best-fit", "best-fit");
}

void deactivate() {
se_dbg(SE_DBG_PLUGINS);

Glib::RefPtr<Gtk::UIManager> ui = get_ui_manager();

ui->remove_ui(ui_id);
ui->remove_action_group(action_group);
}

void update_ui() {
se_dbg(SE_DBG_PLUGINS);

bool visible = (get_current_document() != NULL);

action_group->get_action("best-fit")->set_sensitive(visible);
}

protected:
void on_best_fit() {
se_dbg(SE_DBG_PLUGINS);

Document *doc = get_current_document();
g_return_if_fail(doc);

// Check if the selection is contiguous and complain if it isn't.
// Can work on multiple contiguous subtitles
std::list<std::vector<Subtitle> > contiguous_selection;
if (get_contiguous_selection(contiguous_selection) == false)
return;

doc->start_command(_("Best fit"));

for (auto &subtitles : contiguous_selection) {
bestfit(subtitles);
}

doc->emit_signal("subtitle-time-changed");
doc->finish_command();
}

bool get_contiguous_selection(
std::list<std::vector<Subtitle> > &contiguous_selection) {
Document *doc = get_current_document();

std::vector<Subtitle> selection = doc->subtitles().get_selection();
if (selection.size() < 2) {
doc->flash_message(_("Best Fit needs at least 2 subtitles to work on."));
return false;
}

contiguous_selection.push_back(std::vector<Subtitle>());

guint last_id = 0;

for (const auto &sub : selection) {
// Is the next subtitle?
if (sub.get_num() == last_id + 1) {
contiguous_selection.back().push_back(sub);
++last_id;
} else {
// Create new list only if the previous is empty.
if (!contiguous_selection.back().empty()) {
contiguous_selection.push_back(std::vector<Subtitle>());
}
contiguous_selection.back().push_back(sub);

last_id = sub.get_num();
}
}

// We check if we have at least one contiguous subtitles.
for (const auto &subs : contiguous_selection) {
if (subs.size() >= 2)
return true;
}
doc->flash_message(
_("Best Fit only works on an uninterrupted selection of subtitles."));
return false;
}

void bestfit(std::vector<Subtitle> &subtitles) {
if (subtitles.size() < 2)
return;
~BestFitPlugin()
{
deactivate();
}

/*
*/
void activate()
{
se_dbg(SE_DBG_PLUGINS);

// actions
action_group = Gtk::ActionGroup::create("BestFitPlugin");

action_group->add(
Gtk::Action::create("best-fit", _("_Best Fit Subtitles"),
_("Best fit the selected subtitles between the start of the first and the end of the last one.")),
sigc::mem_fun(*this, &BestFitPlugin::on_best_fit));

// ui
Glib::RefPtr<Gtk::UIManager> ui = get_ui_manager();

ui_id = ui->new_merge_id();

ui->insert_action_group(action_group);

ui->add_ui(ui_id, "/menubar/menu-timings/best-fit", "best-fit", "best-fit");
}

/*
*/
void deactivate()
{
se_dbg(SE_DBG_PLUGINS);

Glib::RefPtr<Gtk::UIManager> ui = get_ui_manager();

ui->remove_ui(ui_id);
ui->remove_action_group(action_group);
}

/*
*/
void update_ui()
{
se_dbg(SE_DBG_PLUGINS);

bool visible = (get_current_document() != NULL);

action_group->get_action("best-fit")->set_sensitive(visible);
}

protected:

/*
*/
void on_best_fit()
{
se_dbg(SE_DBG_PLUGINS);

Document *doc = get_current_document();
g_return_if_fail(doc);

// Check if the selection is contiguous and complain if it isn't.
// Can work on multiple contiguous subtitles
std::list< std::vector<Subtitle> > contiguous_selection;
if(get_contiguous_selection(contiguous_selection) == false)
return;

doc->start_command(_("Best fit"));

for(std::list< std::vector<Subtitle> >::iterator it = contiguous_selection.begin(); it != contiguous_selection.end(); ++it)
{
bestfit(*it);
}

doc->emit_signal("subtitle-time-changed");
doc->finish_command();
}

/*
*/
bool get_contiguous_selection(std::list< std::vector<Subtitle> > &contiguous_selection)
{
Document* doc = get_current_document();

std::vector<Subtitle> selection = doc->subtitles().get_selection();
if(selection.size() < 2)
{
doc->flash_message(_("Best Fit needs at least 2 subtitles to work on."));
return false;
}

contiguous_selection.push_back( std::vector<Subtitle> () );

guint last_id = 0;

for(guint i=0; i<selection.size(); ++i)
{
Subtitle &sub = selection[i];
// Is the next subtitle?
if(sub.get_num() == last_id + 1)
{
contiguous_selection.back().push_back( sub );
++last_id;
}
else
{
// Create new list only if the previous is empty.
if(!contiguous_selection.back().empty())
contiguous_selection.push_back( std::vector<Subtitle> () );

contiguous_selection.back().push_back( sub );

last_id = sub.get_num();
}
}

// We check if we have at least one contiguous subtitles.
for(std::list< std::vector<Subtitle> >::iterator it = contiguous_selection.begin(); it != contiguous_selection.end(); ++it)
{
if((*it).size() >= 2)
return true;
}
doc->flash_message(_("Best Fit only works on an uninterrupted selection of subtitles."));
return false;
}

/*
*/
void bestfit(std::vector<Subtitle> &subtitles)
{
if(subtitles.size() < 2)
return;

// Get relevant preferences
SubtitleTime gap = cfg::get_int("timing", "min-gap-between-subtitles");
double mincps = cfg::get_double("timing", "min-characters-per-second");
SubtitleTime minlen = cfg::get_int("timing", "min-display");
long minmsecs = minlen.totalmsecs;

// SubtitleTime minlen = cfg::get_int("timing", "min-display");
// double mincps = cfg::get_double("timing", "min-characters-per-second");
// long maxcpl = cfg::get_int("timing", "max-characters-per-line");
// long maxcps = cfg::get_int("timing", "max-characters-per-second");

SubtitleTime startime = subtitles.front().get_start();
SubtitleTime endtime = subtitles.back().get_end();
SubtitleTime grosstime = endtime - startime;
SubtitleTime allgaps = gap * (subtitles.size() - 1);
SubtitleTime nettime = grosstime - allgaps;

// Get the total of characters counts
long totalchars = 0;
for (const auto &sub : subtitles) {
totalchars += utility::get_text_length_for_timing(sub.get_text());
}

// Avoid divide by zero
// Fix bug #23151 : Using best fit subtitles on zero-length subtitles
// crashes subtitleeditor
if (totalchars == 0)
return;

// Distribute available time between selected subtitles in proportion to the
// length of their text
long subchars = 0;
long prevchars = 0;
SubtitleTime intime;
SubtitleTime prevend;
SubtitleTime dur;
SubtitleTime maxdur;

for (unsigned int i = 0; i < subtitles.size(); ++i) {
Subtitle &sub = subtitles[i];

subchars = utility::get_text_length_for_timing(sub.get_text());

// give this subtitle a fair share of the net time available
dur = (nettime * subchars) / totalchars;
// calculate proportionate start time
intime = startime + ((grosstime * prevchars) / totalchars);

// make sure we're not under the minimum cps
maxdur.totalmsecs = static_cast<long>(
floor((1000.0 * static_cast<double>(subchars)) / mincps));

if (dur > maxdur)
dur = maxdur;

// make sure minimum gap is preserved
// (rounding errors could've shortened it)
if (i > 0 && (intime - prevend) < gap) {
intime = prevend + gap;
}

sub.set_start_and_end(intime, intime + dur);

prevend = intime + dur;
}
// reset the end time of the last subtitle to make sure rounding errors
// didn't move it
subtitles.back().set_end(endtime);
}

protected:
Gtk::UIManager::ui_merge_id ui_id;
Glib::RefPtr<Gtk::ActionGroup> action_group;
SubtitleTime startime = subtitles.front().get_start();
SubtitleTime endtime = subtitles.back().get_end();
SubtitleTime grosstime = endtime - startime;
long allgaps = gap.totalmsecs * (subtitles.size()-1);
long nettime = grosstime.totalmsecs - allgaps;

std::vector<long> durations( subtitles.size() );
std::vector<long> charcounts( subtitles.size() );

// Get the total character count
long totalchars = 0;
for(guint i=0; i< subtitles.size(); ++i)
{
charcounts[i] = utility::get_text_length_for_timing( subtitles[i].get_text() );
durations[i] = 0;
totalchars += charcounts[i];
}

// Avoid divide by zero
// Fix bug #23151 : Using best fit subtitles on zero-length subtitles crashes subtitleeditor
if(totalchars == 0)
return;

long charsleft = totalchars;
long timeleft = nettime;
bool done = false; //we're done if no subtitle duration hit the minimum limit
bool hopeless = false; //if all durations hit the minimum limit, it's hopeless
while( !done && !hopeless ) {
done = true;
hopeless = true;
for( guint i = 0; i < durations.size(); ++i ) {
if( charcounts[i] >= 0 ) {
long d = charcounts[i] * timeleft / charsleft;
if( d < minmsecs ) {
charsleft -= charcounts[i];
timeleft -= minmsecs;
durations[i] = minmsecs;
charcounts[i] = -1;
done = false;
} else {
durations[i] = d;
hopeless = false;
}
}
}
}

if( hopeless ) {
//forget subtitle minimum duration and just distribute the time evenly
for( guint i = 0; i < durations.size(); ++i ) {
durations[i] = utility::get_text_length_for_timing( subtitles[i].get_text() ) * nettime / totalchars;
}
}

//time subtitles according to calculated durations
SubtitleTime intime = subtitles[0].get_start();
for( guint i = 0; i < durations.size(); ++i ) {
subtitles[i].set_start_and_end( intime, intime + durations[i] );
intime.totalmsecs += durations[i] + gap.totalmsecs;
}

//reset the end time of the last time to original
//in case rounding errors made it drift away
subtitles[ subtitles.size() - 1 ].set_end( endtime );
}

protected:
Gtk::UIManager::ui_merge_id ui_id;
Glib::RefPtr<Gtk::ActionGroup> action_group;
};

REGISTER_EXTENSION(BestFitPlugin)
Loading