From 53fb43c7e65d61a08b78e2665ec6cba8c3f7f4ed Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Thu, 24 Jun 2010 01:24:26 +0000 Subject: [PATCH] Fix significantly incorrect handling of pretty much everything in AssTransformFramerateFilter and make it better at deciding when it actually needs to do anything. Originally committed to SVN as r4578. --- aegisub/src/export_framerate.cpp | 221 +++++++++++------------------ aegisub/src/export_framerate.h | 113 +++++---------- aegisub/src/vfr.cpp | 178 ++--------------------- aegisub/src/vfr.h | 38 ++--- aegisub/src/video_provider_avs.cpp | 2 - 5 files changed, 138 insertions(+), 414 deletions(-) diff --git a/aegisub/src/export_framerate.cpp b/aegisub/src/export_framerate.cpp index aa0e1ac2b..8321f4629 100644 --- a/aegisub/src/export_framerate.cpp +++ b/aegisub/src/export_framerate.cpp @@ -34,29 +34,48 @@ /// @ingroup export /// - -/////////// -// Headers #include "config.h" +#ifndef AGI_PRE +#include + +#include +#include +#include +#include +#include +#include +#include +#endif + #include "ass_dialogue.h" #include "ass_file.h" #include "ass_override.h" #include "export_framerate.h" -#include "vfr.h" +#include "utils.h" - -/// @brief Constructor +/// DOCME +/// @class LineData +/// @brief DOCME /// +/// DOCME +struct LineData { + AssDialogue *line; + int newStart; + int newEnd; + int newK; + int oldK; +}; + +/// IDs +enum { + Get_Input_From_Video = 2000 +}; + AssTransformFramerateFilter::AssTransformFramerateFilter() { initialized = false; } - - -/// @brief Init -/// @return -/// void AssTransformFramerateFilter::Init() { if (initialized) return; initialized = true; @@ -67,27 +86,10 @@ void AssTransformFramerateFilter::Init() { Output = NULL; } - - -/// @brief Process -/// @param subs -/// @param export_dialog -/// void AssTransformFramerateFilter::ProcessSubs(AssFile *subs, wxWindow *export_dialog) { - // Transform frame rate - if (Input->IsLoaded() && Output->IsLoaded()) { - if (Input->GetFrameRateType() == VFR || Output->GetFrameRateType() == VFR || Output->GetAverage() != Input->GetAverage()) { - TransformFrameRate(subs); - } - } + TransformFrameRate(subs); } - - -/// @brief Get dialog -/// @param parent -/// @return -/// wxWindow *AssTransformFramerateFilter::GetConfigDialogWindow(wxWindow *parent) { wxWindow *base = new wxPanel(parent, -1); @@ -146,11 +148,6 @@ wxWindow *AssTransformFramerateFilter::GetConfigDialogWindow(wxWindow *parent) { return base; } - - -/// @brief Load settings -/// @param IsDefault -/// void AssTransformFramerateFilter::LoadSettings(bool IsDefault) { if (IsDefault) { Input = &VFR_Input; @@ -168,149 +165,91 @@ void AssTransformFramerateFilter::LoadSettings(bool IsDefault) { } else Output = &VFR_Output; - // Reverse if (Reverse->IsChecked()) { - FrameRate *temp = Output; - Output = Input; - Input = temp; + std::swap(Input, Output); } } } +/// Truncate a time to centisecond precision +int FORCEINLINE trunc_cs(int time) { + return (time / 10) * 10; +} - -/// @brief Transform framerate in tags -/// @param name -/// @param n -/// @param curParam -/// @param curData -/// @return -/// void AssTransformFramerateFilter::TransformTimeTags (wxString name,int n,AssOverrideParameter *curParam,void *curData) { - // Only modify anything if this is a number VariableDataType type = curParam->GetType(); if (type != VARDATA_INT && type != VARDATA_FLOAT) return; - // Setup - LineData *lineData = (LineData*) curData; - AssDialogue *curDiag = lineData->line;; - bool start = true; - bool karaoke = false; - int mult = 1; - int value; + LineData *lineData = static_cast(curData); + AssDialogue *curDiag = lineData->line; + + int parVal = curParam->Get(); + switch (curParam->classification) { - case PARCLASS_RELATIVE_TIME_START: + case PARCLASS_RELATIVE_TIME_START: { + int value = instance.ConvertTime(trunc_cs(curDiag->Start.GetMS()) + parVal) - lineData->newStart; + + // An end time of 0 is actually the end time of the line, so ensure + // nonzero is never converted to 0 + // Needed here rather than the end case because start/end here mean + // which end of the line the time is relative to, not whether it's + // the start or end time (compare \move and \fad) + if (value == 0 && parVal != 0) value = 1; + curParam->Set(value); break; + } case PARCLASS_RELATIVE_TIME_END: - start = false; + curParam->Set(lineData->newEnd - instance.ConvertTime(trunc_cs(curDiag->End.GetMS()) - parVal)); break; - case PARCLASS_KARAOKE: - karaoke = true; - mult = 10; + case PARCLASS_KARAOKE: { + int start = curDiag->Start.GetMS() / 10 + lineData->oldK + parVal; + int value = (instance.ConvertTime(start * 10) - lineData->newStart) / 10 - lineData->newK; + lineData->oldK += parVal; + lineData->newK += value; + curParam->Set(value); break; + } default: return; } - - // Parameter value - int parVal = curParam->Get() * mult; - - // Karaoke preprocess - int curKarPos = 0; - if (karaoke) { - if (name == _T("\\k")) { - curKarPos = lineData->k; - lineData->k += parVal/10; - } - else if (name == _T("\\K") || name == _T("\\kf")) { - curKarPos = lineData->kf; - lineData->kf += parVal/10; - } - else if (name == _T("\\ko")) { - curKarPos = lineData->ko; - lineData->ko += parVal/10; - } - else throw wxString::Format(_T("Unknown karaoke tag! '%s'"), name.c_str()); - curKarPos *= 10; - parVal += curKarPos; - } - - // Start time - if (start) { - int newStart = instance.Input->GetTimeAtFrame(instance.Output->GetFrameAtTime(curDiag->Start.GetMS())); - int absTime = curDiag->Start.GetMS() + parVal; - value = instance.Input->GetTimeAtFrame(instance.Output->GetFrameAtTime(absTime)) - newStart; - - // An end time of 0 is actually the end time of the line, so ensure nonzero is never converted to 0 - // Needed in the start case as well as the end one due to \t, whose end time needs the start time - // behavior - if (value == 0 && parVal != 0) value = 1; - } - - // End time - else { - int newEnd = instance.Input->GetTimeAtFrame(instance.Output->GetFrameAtTime(curDiag->End.GetMS())); - int absTime = curDiag->End.GetMS() - parVal; - value = newEnd - instance.Input->GetTimeAtFrame(instance.Output->GetFrameAtTime(absTime)); - if (value == 0 && parVal != 0) value = 1; - } - - // Karaoke postprocess - if (karaoke) { - int post = instance.Input->GetTimeAtFrame(instance.Output->GetFrameAtTime(curDiag->Start.GetMS() + curKarPos)); - int start = instance.Input->GetTimeAtFrame(instance.Output->GetFrameAtTime(curDiag->Start.GetMS())); - curKarPos = post-start; - value -= curKarPos; - } - - curParam->Set(value/mult); } - - -/// @brief Transform framerate -/// @param subs -/// void AssTransformFramerateFilter::TransformFrameRate(AssFile *subs) { - int n=0; - - // Run through - using std::list; - AssEntry *curEntry; - AssDialogue *curDialogue; + if (!Input->IsLoaded() || !Output->IsLoaded() || Input == Output || *Input == *Output) return; for (entryIter cur=subs->Line.begin();cur!=subs->Line.end();cur++) { - curEntry = *cur; - // why the christ was this ever done to begin with? - // yes, let's framerate compensate the start timestamp and then use the changed value to - // compensate it AGAIN 20 lines down? I DO NOT GET IT - // -Fluff - //curEntry->Start.SetMS(Input->GetTimeAtFrame(Output->GetFrameAtTime(curEntry->GetStartMS(),true),true)); - curDialogue = dynamic_cast(curEntry); + AssDialogue *curDialogue = dynamic_cast(*cur); - // Update dialogue entries if (curDialogue) { - // Line data LineData data; data.line = curDialogue; - data.k = 0; - data.kf = 0; - data.ko = 0; + data.newK = 0; + data.oldK = 0; + data.newStart = trunc_cs(ConvertTime(curDialogue->Start.GetMS())); + data.newEnd = trunc_cs(ConvertTime(curDialogue->End.GetMS())); // Process stuff curDialogue->ParseASSTags(); curDialogue->ProcessParameters(TransformTimeTags,&data); - curDialogue->Start.SetMS(Input->GetTimeAtFrame(Output->GetFrameAtTime(curDialogue->Start.GetMS(),true),true)); - curDialogue->End.SetMS(Input->GetTimeAtFrame(Output->GetFrameAtTime(curDialogue->End.GetMS(),false),false)); + curDialogue->Start.SetMS(data.newStart); + curDialogue->End.SetMS(data.newEnd); curDialogue->UpdateText(); curDialogue->ClearBlocks(); - n++; } } } +int AssTransformFramerateFilter::ConvertTime(int time) { + int frame = Output->GetFrameAtTime(time, false); + int frameStart = Output->GetTimeAtFrame(frame, false, true); + int frameEnd = Output->GetTimeAtFrame(frame + 1, false, true); + int frameDur = frameEnd - frameStart; + double dist = double(time - frameStart) / frameDur; + int newStart = Input->GetTimeAtFrame(frame, false, true); + int newEnd = Input->GetTimeAtFrame(frame + 1, false, true); + int newDur = newEnd - newStart; + + return newStart + newDur * dist; +} -/// DOCME AssTransformFramerateFilter AssTransformFramerateFilter::instance; - - diff --git a/aegisub/src/export_framerate.h b/aegisub/src/export_framerate.h index 4f9010e1f..2524b6528 100644 --- a/aegisub/src/export_framerate.h +++ b/aegisub/src/export_framerate.h @@ -34,110 +34,61 @@ /// @ingroup export /// - - - -/////////// -// Headers -#ifndef AGI_PRE -#include -#include -#include -#include -#include -#include -#include -#endif - #include "ass_export_filter.h" #include "vfr.h" - -////////////// -// Prototypes -class AssOverrideParameter; class AssDialogue; - - +class AssOverrideParameter; +class wxCheckBox; +class wxRadioButton; +class wxTextCtrl; /// DOCME /// @class AssTransformFramerateFilter /// @brief DOCME -/// -/// DOCME class AssTransformFramerateFilter : public AssExportFilter { -private: - - /// DOCME + /// The singleton instance of this filter static AssTransformFramerateFilter instance; - /// DOCME + // Yes, these are backwards + FrameRate *Input; /// Destination frame rate + FrameRate *Output; /// Source frame rate - /// DOCME - FrameRate *Input,*Output; - - /// DOCME - - /// DOCME FrameRate t1,t2; + wxTextCtrl *InputFramerate; /// Input frame rate text box + wxTextCtrl *OutputFramerate; /// Output frame rate text box - /// DOCME - wxTextCtrl *InputFramerate; + wxRadioButton *RadioOutputCFR; /// CFR radio control + wxRadioButton *RadioOutputVFR; /// VFR radio control - /// DOCME - wxTextCtrl *OutputFramerate; - - /// DOCME - wxRadioButton *RadioOutputCFR; - - /// DOCME - wxRadioButton *RadioOutputVFR; - - /// DOCME - wxCheckBox *Reverse; + wxCheckBox *Reverse; /// Switch input and output + /// Constructor AssTransformFramerateFilter(); + + /// @brief Apply the transformation to a file + /// @param subs File to process void TransformFrameRate(AssFile *subs); - static void TransformTimeTags(wxString name,int n,AssOverrideParameter *curParam,void *_curDiag); + /// @brief Transform a single tag + /// @param name Name of the tag + /// @param curParam Current parameter being processed + /// @param userdata LineData passed + static void TransformTimeTags(wxString name,int,AssOverrideParameter *curParam,void *userdata); + /// Initialize the singleton instance void Init(); + /// @brief Convert a time from the input frame rate to the output frame rate + /// @param time Time in ms to convert + /// @return Time in ms + /// + /// This preserves two things: + /// 1. The frame number + /// 2. The relative distance between the beginning of the frame which time + /// is in and the beginning of the next frame + int ConvertTime(int time); public: void ProcessSubs(AssFile *subs, wxWindow *export_dialog); wxWindow *GetConfigDialogWindow(wxWindow *parent); void LoadSettings(bool IsDefault); }; - - - -/// DOCME -/// @class LineData -/// @brief DOCME -/// -/// DOCME -class LineData { -public: - - /// DOCME - AssDialogue *line; - - /// DOCME - int k; - - /// DOCME - int kf; - - /// DOCME - int ko; -}; - - -/////// -// IDs -enum { - - /// DOCME - Get_Input_From_Video = 2000 -}; - - diff --git a/aegisub/src/vfr.cpp b/aegisub/src/vfr.cpp index ba33d28f7..68b0e7f74 100644 --- a/aegisub/src/vfr.cpp +++ b/aegisub/src/vfr.cpp @@ -34,9 +34,6 @@ /// @ingroup video_input /// - -/////////// -// Headers #include "config.h" #ifndef AGI_PRE @@ -53,53 +50,36 @@ /// @brief V2 Clear function -/// void FrameRate::Clear () { Frame.clear(); } - - /// @brief V2 Add frame /// @param ms -/// void FrameRate::AddFrame(int ms) { Frame.push_back(ms); } - - /// @brief V2 Get Average -/// void FrameRate::CalcAverage() { - if (Frame.size() <= 1) throw _("No timecodes to average"); AverageFrameRate = double(Frame.back()) / (Frame.size()-1); } - - -/// @brief Constructor FrameRate ////////////////////// -/// +/// @brief Constructor FrameRate::FrameRate() { Unload(); } - - /// @brief Destructor -/// FrameRate::~FrameRate() { Unload(); } - - /// @brief Loads VFR file /// @param filename -/// void FrameRate::Load(wxString filename) { using namespace std; @@ -241,11 +221,8 @@ void FrameRate::Load(wxString filename) { config::mru->Add("Timecodes", STD_STR(filename)); } - - /// @brief Save /// @param filename -/// void FrameRate::Save(wxString filename) { TextFileWriter file(filename,_T("ASCII")); file.WriteLineToFile(_T("# timecode format v2")); @@ -254,10 +231,7 @@ void FrameRate::Save(wxString filename) { } } - - /// @brief Unload -/// void FrameRate::Unload () { FrameRateType = NONE; AverageFrameRate = 0; @@ -268,11 +242,8 @@ void FrameRate::Unload () { vfrFile = _T(""); } - - /// @brief Sets to CFR /// @param fps -/// void FrameRate::SetCFR(double fps) { Unload(); loaded = true; @@ -280,13 +251,9 @@ void FrameRate::SetCFR(double fps) { AverageFrameRate = fps; } - - /// @brief Sets to VFR /// @param newTimes -/// void FrameRate::SetVFR(std::vector newTimes) { - // Prepare Unload(); loaded = true; @@ -299,22 +266,16 @@ void FrameRate::SetVFR(std::vector newTimes) { last_frame = (int)newTimes.size(); } - - /// @brief Gets frame number at time /// @param ms /// @param useceil /// @return -/// -int FrameRate::PFrameAtTime(int ms,bool useceil) { +int FrameRate::PFrameAtTime(int ms,bool useceil) const { // Check if it's loaded if (!loaded) return -1; - // Normalize miliseconds - ms = MAX(ms,0); - // Get for constant frame rate - if (FrameRateType == CFR || Frame.size() == 0) { + if (FrameRateType == CFR || Frame.size() == 0 || ms < 0) { double value = double(ms) * AverageFrameRate / 1000.0; if (useceil) return (int)ceil(value); else return (int)floor(value); @@ -366,13 +327,10 @@ int FrameRate::PFrameAtTime(int ms,bool useceil) { return -1; } - - /// @brief Gets time at frame /// @param frame /// @return -/// -int FrameRate::PTimeAtFrame(int frame) { +int FrameRate::PTimeAtFrame(int frame) const { // Not loaded if (!loaded) return -1; @@ -397,26 +355,20 @@ int FrameRate::PTimeAtFrame(int frame) { return -1; } - - /// @brief otherwise for start frames returns the adjusted time for end frames when start=false Get correct frame at time /// @param ms /// @param start /// @return -/// -int FrameRate::GetFrameAtTime(int ms,bool start) { +int FrameRate::GetFrameAtTime(int ms,bool start) const { return PFrameAtTime(ms,start); } - - /// @brief compensates and returns an end time when start=false Get correct time at frame -/// @param frame -/// @param start -/// @param exact +/// @param frame Frame number +/// @param start Adjust for start time +/// @param exact Don't do awful things to avoid rounding errors /// @return -/// -int FrameRate::GetTimeAtFrame(int frame,bool start,bool exact) { +int FrameRate::GetTimeAtFrame(int frame,bool start,bool exact) const { int finalTime; // Exact, for display @@ -439,121 +391,21 @@ int FrameRate::GetTimeAtFrame(int frame,bool start,bool exact) { return finalTime; } - - /// @brief Get the current list of frames/times /// @return -/// -std::vector FrameRate::GetFrameTimeList() { +std::vector FrameRate::GetFrameTimeList() const { return Frame; } - - -/// @brief e.g., in a mix of 24fps and 30fps, returns 120fps Calculate the common FPS for evil stuff -/// @return -/// -double FrameRate::GetCommonFPS() { - // Variables - int curDist; - int lastDist = 0; - int sectionStart = 0; - double curFps; - - // List of likely frame rates - std::vector frameRates; - frameRates.push_back(15.0 / 1.001); - frameRates.push_back(15); - frameRates.push_back(24.0 / 1.001); - frameRates.push_back(24); - frameRates.push_back(30.0 / 1.001); - frameRates.push_back(30); - frameRates.push_back(120.0 / 1.001); - frameRates.push_back(120); - - // List of rates found - std::vector found; - - // Find the relative fps of each area - for (unsigned int i=1;i= frameRates[j]) { - curFps = frameRates[j]; - break; - } - } - - // See if it's on list - bool onList = false; - for (unsigned int j=0;j 1) { - // Extract last two values - v1 = found.back(); - found.pop_back(); - v2 = found.back(); - found.pop_back(); - - // Divide them - v2 = v1/v2; - - // Find what it takes to make it an integer - for (minInt = 1;minInt<20;minInt++) { - tempd = v2 * minInt; - tempi1 = (int)(tempd-0.001); - tempi2 = (int)(tempd+0.001); - if (tempi1 != tempi2) break; - } - if (minInt != 20) v1 = v1*minInt; - - // See if it's close enough to one of the likely rates - for (unsigned int j=0;j= frameRates[j]) { - v1 = frameRates[j]; - break; - } - } - - // Re-insert obtained result - found.push_back(v1); - } - - return found.back(); +bool FrameRate::operator==(FrameRate const& rgt) { + if (FrameRateType != rgt.FrameRateType) return false; + if (FrameRateType == NONE) return true; + if (FrameRateType == CFR) return AverageFrameRate == rgt.AverageFrameRate; + return Frame == rgt.Frame; } - - /// DOCME FrameRate VFR_Output; /// DOCME FrameRate VFR_Input; - - diff --git a/aegisub/src/vfr.h b/aegisub/src/vfr.h index f1137bd02..5b4a09757 100644 --- a/aegisub/src/vfr.h +++ b/aegisub/src/vfr.h @@ -42,9 +42,6 @@ #pragma once -/////////// -// Headers - #ifndef AGI_PRE #include #include @@ -53,19 +50,10 @@ #include #endif -#include "include/aegisub/aegisub.h" - - /// DOCME enum ASS_FrameRateType { - - /// DOCME NONE, - - /// DOCME CFR, - - /// DOCME VFR }; @@ -97,8 +85,8 @@ private: void Clear(); void CalcAverage(); - int PFrameAtTime(int ms,bool useCeil=false); - int PTimeAtFrame(int frame); + int PFrameAtTime(int ms,bool useCeil=false) const; + int PTimeAtFrame(int frame) const; /// DOCME @@ -122,37 +110,33 @@ public: void Save(wxString file); void Unload(); - int GetFrameAtTime(int ms,bool start=true); - int GetTimeAtFrame(int frame,bool start=true,bool exact=false); + int GetFrameAtTime(int ms,bool start=true) const; + int GetTimeAtFrame(int frame,bool start=true,bool exact=false) const; /// @brief DOCME /// @return /// - double GetAverage() { return AverageFrameRate; }; + double GetAverage() const { return AverageFrameRate; }; /// @brief DOCME /// @return /// - bool IsLoaded() { return loaded; }; + bool IsLoaded() const { return loaded; }; /// @brief DOCME /// @return /// - ASS_FrameRateType GetFrameRateType() { return FrameRateType; }; + ASS_FrameRateType GetFrameRateType() const { return FrameRateType; }; /// @brief DOCME /// - wxString GetFilename() { return vfrFile; }; + wxString GetFilename() const { return vfrFile; }; - std::vector GetFrameTimeList(); - double GetCommonFPS(); + std::vector GetFrameTimeList() const; + + bool operator==(FrameRate const& rgt); }; - -/////////// -// Globals extern FrameRate VFR_Output; extern FrameRate VFR_Input; - - diff --git a/aegisub/src/video_provider_avs.cpp b/aegisub/src/video_provider_avs.cpp index 1c600297d..e385efeda 100644 --- a/aegisub/src/video_provider_avs.cpp +++ b/aegisub/src/video_provider_avs.cpp @@ -255,7 +255,6 @@ PClip AvisynthVideoProvider::OpenVideo(wxString _filename, bool mpeg2dec3_priori // Read keyframes and timecodes from MKV file isVfr = false; FrameRate temp; - double overFps = 0; bool mkvOpen = MatroskaWrapper::wrapper.IsOpen(); KeyFrames.Clear(); if (extension == _T(".mkv") || mkvOpen) { @@ -273,7 +272,6 @@ PClip AvisynthVideoProvider::OpenVideo(wxString _filename, bool mpeg2dec3_priori MatroskaWrapper::wrapper.SetToTimecodes(temp); isVfr = temp.GetFrameRateType() == VFR; if (isVfr) { - overFps = temp.GetCommonFPS(); MatroskaWrapper::wrapper.SetToTimecodes(VFR_Input); MatroskaWrapper::wrapper.SetToTimecodes(VFR_Output); trueFrameRate = temp;