// Copyright (c) 2005, Rodrigo Braz Monteiro // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // * Neither the name of the Aegisub Group nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. // // ----------------------------------------------------------------------------- // // AEGISUB // // Website: http://aegisub.cellosoft.com // Contact: mailto:zeratul@cellosoft.com // //////////// // Includes #include "dialog_timing_processor.h" #include "subs_grid.h" #include "ass_file.h" #include "options.h" #include "validators.h" #include "video_display.h" #include "video_box.h" #include "ass_dialogue.h" #include "ass_time.h" #include "vfr.h" #include "utils.h" /////////////// // Constructor DialogTimingProcessor::DialogTimingProcessor(wxWindow *parent,SubtitlesGrid *_grid) : wxDialog(parent,-1,_("Timing Post-Processor"),wxDefaultPosition,wxSize(400,250),wxDEFAULT_DIALOG_STYLE) { // Set variables grid = _grid; leadInTime = Options.AsText(_T("Audio lead in")); leadOutTime = Options.AsText(_T("Audio lead out")); thresOverLen = Options.AsText(_T("Timing processor key overlen thres")); thresUnderLen = Options.AsText(_T("Timing processor key underlen thres")); adjsThresTime = Options.AsText(_T("Timing processor adjascent thres")); // Lead-in/out box wxSizer *LeadSizer = new wxStaticBoxSizer(wxHORIZONTAL,this,_("Lead-in/Lead-out")); hasLeadIn = new wxCheckBox(this,CHECK_ENABLE_LEADIN,_("Lead in:")); hasLeadIn->SetToolTip(_("Enable adding of lead-ins to lines.")); hasLeadIn->SetValue(Options.AsBool(_T("Timing processor Enable lead-in"))); leadIn = new wxTextCtrl(this,-1,_T(""),wxDefaultPosition,wxSize(80,-1),0,NumValidator(&leadInTime)); leadIn->SetToolTip(_("Lead in to be added, in miliseconds.")); hasLeadOut = new wxCheckBox(this,CHECK_ENABLE_LEADOUT,_("Lead out:")); hasLeadOut->SetToolTip(_("Enable adding of lead-outs to lines.")); hasLeadOut->SetValue(Options.AsBool(_T("Timing processor Enable lead-out"))); leadOut = new wxTextCtrl(this,-1,_T(""),wxDefaultPosition,wxSize(80,-1),0,NumValidator(&leadOutTime)); leadOut->SetToolTip(_("Lead out to be added, in miliseconds.")); LeadSizer->Add(hasLeadIn,0,wxRIGHT|wxEXPAND,5); LeadSizer->Add(leadIn,0,wxRIGHT|wxEXPAND,5); LeadSizer->Add(hasLeadOut,0,wxRIGHT|wxEXPAND,5); LeadSizer->Add(leadOut,0,wxRIGHT|wxEXPAND,0); LeadSizer->AddStretchSpacer(1); // Keyframes sizer KeyframesSizer = new wxStaticBoxSizer(wxHORIZONTAL,this,_("Keyframe snapping")); keysEnable = new wxCheckBox(this,CHECK_ENABLE_KEYFRAME,_("Enable")); keysEnable->SetToolTip(_("Enable snapping of subtitles to nearest keyframe, if distance is within threshold.")); keysEnable->SetValue(Options.AsBool(_T("Timing processor Enable keyframe"))); wxStaticText *thresOver = new wxStaticText(this,-1,_("Overlength thres.:"),wxDefaultPosition,wxDefaultSize,wxALIGN_CENTRE); keysThresOver = new wxTextCtrl(this,-1,_T(""),wxDefaultPosition,wxSize(60,-1),0,NumValidator(&thresOverLen)); keysThresOver->SetToolTip(_("Threshold for 'overlength' distance, that is, starting before keyframe or ending after. In frames (inclusive).")); wxStaticText *thresUnder = new wxStaticText(this,-1,_("Underlength thres.:"),wxDefaultPosition,wxDefaultSize,wxALIGN_CENTRE); keysThresUnder = new wxTextCtrl(this,-1,_T(""),wxDefaultPosition,wxSize(60,-1),0,NumValidator(&thresUnderLen)); keysThresUnder->SetToolTip(_("Threshold for 'underlength' distance, that is, starting after keyframe or ending before. In frames (inclusive).")); KeyframesSizer->Add(keysEnable,0,wxRIGHT|wxEXPAND,10); KeyframesSizer->Add(thresOver,0,wxRIGHT|wxALIGN_CENTER,5); KeyframesSizer->Add(keysThresOver,0,wxRIGHT|wxEXPAND,5); KeyframesSizer->Add(thresUnder,0,wxRIGHT|wxALIGN_CENTER,5); KeyframesSizer->Add(keysThresUnder,0,wxRIGHT|wxEXPAND,0); KeyframesSizer->AddStretchSpacer(1); // Adjascent subs sizer wxSizer *AdjascentSizer = new wxStaticBoxSizer(wxHORIZONTAL,this,_("Make adjascent subtitles continuous")); adjsEnable = new wxCheckBox(this,CHECK_ENABLE_ADJASCENT,_("Enable")); adjsEnable->SetToolTip(_("Enable snapping of subtitles together if they are within a certain distance of each other.")); adjsEnable->SetValue(Options.AsBool(_T("Timing processor Enable adjascent"))); wxStaticText *adjsThresText = new wxStaticText(this,-1,_("Threshold:"),wxDefaultPosition,wxDefaultSize,wxALIGN_CENTRE); adjascentThres = new wxTextCtrl(this,-1,_T(""),wxDefaultPosition,wxSize(60,-1),0,NumValidator(&adjsThresTime)); adjascentThres->SetToolTip(_("Maximum difference between start and end time for two subtitles to be made continuous, in miliseconds.")); AdjascentSizer->Add(adjsEnable,0,wxRIGHT|wxEXPAND,10); AdjascentSizer->Add(adjsThresText,0,wxRIGHT|wxALIGN_CENTER,5); AdjascentSizer->Add(adjascentThres,0,wxRIGHT|wxEXPAND,5); AdjascentSizer->AddStretchSpacer(1); // Button sizer wxSizer *ButtonSizer = new wxBoxSizer(wxHORIZONTAL); ButtonSizer->AddStretchSpacer(1); ApplyButton = new wxButton(this,wxID_APPLY); ButtonSizer->Add(ApplyButton,0,wxRIGHT,5); ButtonSizer->Add(new wxButton(this,wxID_CANCEL),0,0,0); // Right Sizer wxSizer *RightSizer = new wxBoxSizer(wxVERTICAL); RightSizer->Add(LeadSizer,0,wxBOTTOM|wxEXPAND,5); RightSizer->Add(KeyframesSizer,0,wxBOTTOM|wxEXPAND,5); RightSizer->Add(AdjascentSizer,0,wxBOTTOM|wxEXPAND,5); RightSizer->AddStretchSpacer(1); RightSizer->Add(ButtonSizer,0,wxLEFT|wxRIGHT|wxBOTTOM|wxEXPAND,0); // Style buttons sizer wxSizer *StyleButtonsSizer = new wxBoxSizer(wxHORIZONTAL); wxButton *all = new wxButton(this,BUTTON_SELECT_ALL,_("All")); all->SetToolTip(_("Select all styles.")); wxButton *none = new wxButton(this,BUTTON_SELECT_NONE,_("None")); none->SetToolTip(_("Deselect all styles.")); StyleButtonsSizer->Add(all,1,0,0); StyleButtonsSizer->Add(none,1,0,0); // Left sizer wxSizer *LeftSizer = new wxStaticBoxSizer(wxVERTICAL,this,_("Apply to styles")); wxArrayString styles = grid->ass->GetStyles(); StyleList = new wxCheckListBox(this,TIMING_STYLE_LIST,wxDefaultPosition,wxSize(150,150),styles); StyleList->SetToolTip(_("Select styles to process. Unchecked ones will be ignored.")); size_t len = StyleList->GetCount(); for (size_t i=0;iCheck(i); } LeftSizer->Add(StyleList,1,wxBOTTOM|wxEXPAND,0); LeftSizer->Add(StyleButtonsSizer,0,wxEXPAND,0); // Top Sizer wxSizer *TopSizer = new wxBoxSizer(wxHORIZONTAL); TopSizer->Add(LeftSizer,0,wxRIGHT|wxEXPAND,5); TopSizer->Add(RightSizer,1,wxALL|wxEXPAND,0); // Main Sizer wxSizer *MainSizer = new wxBoxSizer(wxVERTICAL); MainSizer->Add(TopSizer,1,wxALL|wxEXPAND,5); MainSizer->SetSizeHints(this); SetSizer(MainSizer); CenterOnParent(); // Update UpdateControls(); } /////////////////// // Update controls void DialogTimingProcessor::UpdateControls() { // Boxes leadIn->Enable(hasLeadIn->IsChecked()); leadOut->Enable(hasLeadOut->IsChecked()); adjascentThres->Enable(adjsEnable->IsChecked()); // Keyframes are only available if timecodes are loaded bool keysAvailable = VFR_Output.loaded; bool enableKeys = keysEnable->IsChecked() && keysAvailable; keysThresOver->Enable(enableKeys); keysThresUnder->Enable(enableKeys); if (!keysAvailable) { keysEnable->SetValue(false); keysEnable->Enable(false); } // Apply button int checked = 0; size_t len = StyleList->GetCount(); for (size_t i=0;iIsChecked(i)) checked++; } ApplyButton->Enable(checked && (hasLeadIn->IsChecked() | hasLeadOut->IsChecked() | keysEnable->IsChecked() | adjsEnable->IsChecked())); } /////////////// // Event table BEGIN_EVENT_TABLE(DialogTimingProcessor,wxDialog) EVT_CHECKBOX(CHECK_ENABLE_LEADIN,DialogTimingProcessor::OnCheckBox) EVT_CHECKBOX(CHECK_ENABLE_LEADOUT,DialogTimingProcessor::OnCheckBox) EVT_CHECKBOX(CHECK_ENABLE_KEYFRAME,DialogTimingProcessor::OnCheckBox) EVT_CHECKBOX(CHECK_ENABLE_ADJASCENT,DialogTimingProcessor::OnCheckBox) EVT_CHECKLISTBOX(TIMING_STYLE_LIST,DialogTimingProcessor::OnCheckBox) EVT_BUTTON(wxID_APPLY,DialogTimingProcessor::OnApply) EVT_BUTTON(BUTTON_SELECT_ALL,DialogTimingProcessor::OnSelectAll) EVT_BUTTON(BUTTON_SELECT_NONE,DialogTimingProcessor::OnSelectNone) END_EVENT_TABLE() //////////////////// // Checkbox clicked void DialogTimingProcessor::OnCheckBox(wxCommandEvent &event) { UpdateControls(); } ///////////////////// // Select all styles void DialogTimingProcessor::OnSelectAll(wxCommandEvent &event) { size_t len = StyleList->GetCount(); for (size_t i=0;iCheck(i); } UpdateControls(); } /////////////////////// // Unselect all styles void DialogTimingProcessor::OnSelectNone(wxCommandEvent &event) { size_t len = StyleList->GetCount(); for (size_t i=0;iCheck(i,false); } UpdateControls(); } //////////////////////// // Apply button pressed void DialogTimingProcessor::OnApply(wxCommandEvent &event) { // Save settings long temp = 0; leadIn->GetValue().ToLong(&temp); Options.SetInt(_T("Audio lead in"),temp); leadOut->GetValue().ToLong(&temp); Options.SetInt(_T("Audio lead out"),temp); keysThresOver->GetValue().ToLong(&temp); Options.SetInt(_T("Timing processor key overlen thres"),temp); keysThresUnder->GetValue().ToLong(&temp); Options.SetInt(_T("Timing processor key underlen thres"),temp); adjascentThres->GetValue().ToLong(&temp); Options.SetInt(_T("Timing processor adjascent thres"),temp); Options.SetBool(_T("Timing processor Enable lead-in"),hasLeadIn->IsChecked()); Options.SetBool(_T("Timing processor Enable lead-out"),hasLeadOut->IsChecked()); if (keysEnable->IsEnabled()) Options.SetBool(_T("Timing processor Enable keyframe"),keysEnable->IsChecked()); Options.SetBool(_T("Timing processor Enable adjascent"),adjsEnable->IsChecked()); Options.Save(); // Check if rows are valid bool valid = true; AssDialogue *tempDiag; int i = 0; for (std::list::iterator cur=grid->ass->Line.begin();cur!=grid->ass->Line.end();cur++) { tempDiag = AssEntry::GetAsDialogue(*cur); if (tempDiag) { i++; if (tempDiag->Start.GetMS() > tempDiag->End.GetMS()) { valid = false; break; } } } // Process if (valid) Process(); // Error message else wxMessageBox(wxString::Format(_("One of the lines in the file (%i) has negative duration. Aborting."),i),_("Invalid script"),wxICON_ERROR|wxOK); // Close dialogue EndModal(0); } //////////////////////// // Get closest keyframe int DialogTimingProcessor::GetClosestKeyFrame(int frame) { // Linear dumb search, not very efficient, but it doesn't really matter int closest = 0; size_t n = KeyFrames.Count(); for (size_t i=0;iGetCount(); for (size_t i=0;iGetString(i) == styleName && StyleList->IsChecked(i)) return true; } return false; } ////////////////// // Sort dialogues void DialogTimingProcessor::SortDialogues() { // Copy from original to temporary list std::list temp; AssDialogue *tempDiag; for (std::list::iterator cur=grid->ass->Line.begin();cur!=grid->ass->Line.end();cur++) { tempDiag = AssEntry::GetAsDialogue(*cur); // Only add valid lines if (tempDiag && StyleOK(tempDiag->Style) && !tempDiag->Comment) { tempDiag->StartMS = tempDiag->Start.GetMS(); temp.push_back(tempDiag); } } // Sort temporary list temp.sort(LessByPointedToValue()); // Copy temporary list to final vector for (std::list::iterator cur=temp.begin();cur!=temp.end();cur++) { Sorted.push_back(*cur); } } //////////////////////// // Gets sorted dialogue AssDialogue *DialogTimingProcessor::GetSortedDialogue(int n) { try { return Sorted.at(n); } catch (...) { return NULL; } } ////////////////////////////// // Actually process subtitles void DialogTimingProcessor::Process() { // Sort rows SortDialogues(); int rows = Sorted.size(); // Options long inVal = 0; long outVal = 0; leadIn->GetValue().ToLong(&inVal); leadOut->GetValue().ToLong(&outVal); bool addIn = hasLeadIn->IsChecked() && inVal; bool addOut = hasLeadOut->IsChecked() && outVal; // Add lead-in/out if (addIn || addOut) { // Variables AssDialogue *cur; AssDialogue *comp; int start,end; int startLead,endLead; int compStart,compEnd; // For each row for (int i=0;iStart.GetMS(); end = cur->End.GetMS(); if (addIn) startLead = start - inVal; else startLead = start; if (addOut) endLead = end + outVal; else endLead = end; // Compare to every previous line (yay for O(n^2)!) to see if it's OK to add lead-in if (addIn) { for (int j=0;jCollidesWith(comp)) continue; // Get comparison times compEnd = comp->End.GetMS(); // Limit lead-in if needed if (compEnd > startLead) startLead = compEnd; } } // Compare to every line to see how far can lead-out be extended if (addOut) { for (int j=i+1;jCollidesWith(comp)) continue; // Get comparison times compStart = comp->Start.GetMS(); // Limit lead-in if needed if (compStart < endLead) endLead = compStart; } } // Set times cur->Start.SetMS(startLead); cur->End.SetMS(endLead); cur->UpdateData(); } } // Make adjascent if (adjsEnable->IsChecked()) { // Variables AssDialogue *cur; AssDialogue *prev = NULL; int curStart,prevEnd; long adjsThres = 0; int dist; adjascentThres->GetValue().ToLong(&adjsThres); // For each row for (int i=0;iCollidesWith(prev)) continue; // Compare distance curStart = cur->Start.GetMS(); prevEnd = prev->End.GetMS(); dist = curStart-prevEnd; if (dist > 0 && dist < adjsThres) { cur->Start.SetMS(curStart-dist/2); cur->UpdateData(); prev->End.SetMS(curStart-dist/2); prev->UpdateData(); } // Set previous prev = cur; } } // Keyframe snapping if (keysEnable->IsChecked()) { // Get keyframes KeyFrames = grid->video->KeyFrames; KeyFrames.Add(grid->video->length-1); // Variables int startF,endF; int closest; bool changed; AssDialogue *cur; // Get variables long underThres = 0; long overThres = 0; keysThresOver->GetValue().ToLong(&overThres); keysThresUnder->GetValue().ToLong(&underThres); // For each row for (int i=0;iStart.GetMS(),true); endF = VFR_Output.CorrectFrameAtTime(cur->End.GetMS(),false); changed = false; // Get closest for start closest = GetClosestKeyFrame(startF); if ((closest > startF && closest-startF <= overThres) || (closest < startF && startF-closest <= underThres)) { cur->Start.SetMS(VFR_Output.CorrectTimeAtFrame(closest,true)); changed = true; } // Get closest for end closest = GetClosestKeyFrame(endF)-1; if ((closest > endF && closest-endF <= overThres) || (closest < endF && endF-closest <= underThres)) { cur->End.SetMS(VFR_Output.CorrectTimeAtFrame(closest,false)); changed = true; } // Apply changes if (changed) { cur->UpdateData(); } } } // Update grid //grid->ass->FlagAsModified(); //grid->CommitChanges(); grid->LoadFromAss(NULL,true,false); }