forked from mia/Aegisub
- Reworked the SMPTE timecode handling with Plorkyeran's help. It does now handle dropframe timecodes as well; the ms->SMPTE handling has been tested and seems reasonably correct, while the reverse conversion remains untested and unused. The Adobe Encore export filter will now use dropframe timecodes properly (previously it would play pretend with wallclock hours/minutes/seconds and incorrect frame numbers).
- Changed the SubtitleFormat::AskForFPS dialog box; removed the "PAL/NTSC only" choice and added a "show SMPTE dropframe" parameter instead. Also added 50fps as a choice. - While I was at it, reworked the TranStation export filter so it actually looks ahead to see if the next line will overlap with the current, and if so, move the end time of the current line backwards one frame, which fixes #767 Originally committed to SVN as r2920.
This commit is contained in:
parent
7215f354b9
commit
ffa5a2021d
8 changed files with 255 additions and 110 deletions
|
@ -39,6 +39,7 @@
|
|||
#include "config.h"
|
||||
|
||||
#include <wx/regex.h>
|
||||
#include <math.h>
|
||||
#include <fstream>
|
||||
#include <algorithm>
|
||||
#include "ass_time.h"
|
||||
|
@ -288,9 +289,18 @@ int AssTime::GetTimeCentiseconds() { return (time % 1000)/10; }
|
|||
|
||||
|
||||
|
||||
FractionalTime::FractionalTime (wxString separator, double numerator, double denominator) {
|
||||
///////
|
||||
// Constructor
|
||||
FractionalTime::FractionalTime (wxString separator, int numerator, int denominator, bool dropframe) {
|
||||
drop = dropframe;
|
||||
if (drop) {
|
||||
// no dropframe for any other framerates
|
||||
num = 30000;
|
||||
den = 1001;
|
||||
} else {
|
||||
num = numerator;
|
||||
den = denominator;
|
||||
}
|
||||
sep = separator;
|
||||
|
||||
// fractions < 1 are not welcome here
|
||||
|
@ -300,20 +310,23 @@ FractionalTime::FractionalTime (wxString separator, double numerator, double den
|
|||
throw _T("FractionalTime: no separator specified");
|
||||
}
|
||||
|
||||
///////
|
||||
// Destructor
|
||||
FractionalTime::~FractionalTime () {
|
||||
sep.Clear();
|
||||
}
|
||||
|
||||
int64_t FractionalTime::ToMillisecs (wxString _text) {
|
||||
///////
|
||||
// SMPTE text string to milliseconds conversion
|
||||
int FractionalTime::ToMillisecs (wxString _text) {
|
||||
wxString text = _text;
|
||||
wxString re_str = _T("");
|
||||
wxString sep_e = _T("\\") + sep; // escape this just in case it may be a reserved regex character
|
||||
text.Trim(false);
|
||||
text.Trim(true);
|
||||
long h=0,m=0,s=0,ms=0,f=0;
|
||||
|
||||
// hour minute second fraction
|
||||
re_str << _T("(\\d+)") << sep_e << _T("(\\d+)") << sep_e << _T("(\\d+)") << sep_e << _T("(\\d+)");
|
||||
re_str << _T("(\\d+)") << sep << _T("(\\d+)") << sep << _T("(\\d+)") << sep << _T("(\\d+)");
|
||||
|
||||
wxRegEx re(re_str, wxRE_ADVANCED);
|
||||
if (!re.IsValid())
|
||||
|
@ -325,28 +338,102 @@ int64_t FractionalTime::ToMillisecs (wxString _text) {
|
|||
re.GetMatch(text,2).ToLong(&m);
|
||||
re.GetMatch(text,3).ToLong(&s);
|
||||
re.GetMatch(text,4).ToLong(&f);
|
||||
// FIXME: find out how to do this in a sane way
|
||||
//if ((double)f >= ((double)num/(double)den) // overflow?
|
||||
// f = (num/den - 1);
|
||||
ms = long((1000.0 / (num/den)) * (double)f);
|
||||
|
||||
return (int64_t)((h * 3600000) + (m * 60000) + (s * 1000) + ms);
|
||||
int msecs_f = 0;
|
||||
int fn = 0;
|
||||
// dropframe? do silly things
|
||||
if (drop) {
|
||||
fn += h * frames_per_period * 6;
|
||||
fn += (m % 10) * frames_per_period;
|
||||
|
||||
if (m > 0) {
|
||||
fn += 1800;
|
||||
m--;
|
||||
|
||||
fn += m * 1798; // two timestamps dropped per minute after the first
|
||||
fn += s * 30 + f - 2;
|
||||
}
|
||||
else { // minute is evenly divisible by 10, keep first two timestamps
|
||||
fn += s * 30;
|
||||
fn += f;
|
||||
}
|
||||
|
||||
msecs_f = (fn * num) / den;
|
||||
}
|
||||
// no dropframe, may or may not sync with wallclock time
|
||||
// (see comment in FromMillisecs for an explanation of why it's done like this)
|
||||
else {
|
||||
int fps_approx = floor((double(num)/double(den))+0.5);
|
||||
fn += h * 3600 * fps_approx;
|
||||
fn += m * 60 * fps_approx;
|
||||
fn += s * fps_approx;
|
||||
fn += f;
|
||||
|
||||
msecs_f = (fn * num) / den;
|
||||
}
|
||||
|
||||
return msecs_f;
|
||||
}
|
||||
|
||||
///////
|
||||
// SMPTE text string to AssTime conversion
|
||||
AssTime FractionalTime::ToAssTime (wxString _text) {
|
||||
AssTime time;
|
||||
time.SetMS((int)ToMillisecs(_text));
|
||||
return time;
|
||||
}
|
||||
|
||||
///////
|
||||
// AssTime to SMPTE text string conversion
|
||||
wxString FractionalTime::FromAssTime(AssTime time) {
|
||||
return FromMillisecs((int64_t)time.GetMS());
|
||||
return FromMillisecs(time.GetMS());
|
||||
}
|
||||
|
||||
///////
|
||||
// Milliseconds to SMPTE text string conversion
|
||||
wxString FractionalTime::FromMillisecs(int64_t msec) {
|
||||
int h = msec / 3600000;
|
||||
int m = (msec % 3600000)/60000;
|
||||
int s = (msec % 60000)/1000;
|
||||
int f = int((msec % 1000) * ((num/den) / 1000.0));
|
||||
int h=0, m=0, s=0, f=0; // hours, minutes, seconds, fractions
|
||||
int fn = (msec*(int64_t)num) / (1000*den); // frame number
|
||||
|
||||
// dropframe?
|
||||
if (drop) {
|
||||
fn += 2 * (fn / (30 * 60)) - 2 * (fn / (30 * 60 * 10));
|
||||
h = fn / (30 * 60 * 60);
|
||||
m = (fn / (30 * 60)) % 60;
|
||||
s = (fn / 30) % 60;
|
||||
f = fn % 30;
|
||||
}
|
||||
// no dropframe; h/m/s may or may not sync to wallclock time
|
||||
else {
|
||||
/*
|
||||
This is truly the dumbest shit. What we're trying to ensure here
|
||||
is that non-integer framerates are desynced from the wallclock
|
||||
time by a correct amount of time. For example, in the
|
||||
NTSC-without-dropframe case, 3600*num/den would be 107892
|
||||
(when truncated to int), which is quite a good approximation of
|
||||
how a long an hour is when counted in 30000/1001 frames per second.
|
||||
Unfortunately, that's not what we want, since frame numbers will
|
||||
still range from 00 to 29, meaning that we're really getting _30_
|
||||
frames per second and not 29.97 and the full hour will be off by
|
||||
almost 4 seconds (108000 frames versus 107892).
|
||||
|
||||
DEATH TO SMPTE
|
||||
*/
|
||||
int fps_approx = floor((double(num)/double(den))+0.5);
|
||||
int frames_per_h = 3600*fps_approx;
|
||||
int frames_per_m = 60*fps_approx;
|
||||
int frames_per_s = fps_approx;
|
||||
while (fn >= frames_per_h) {
|
||||
h++; fn -= frames_per_h;
|
||||
}
|
||||
while (fn >= frames_per_m) {
|
||||
m++; fn -= frames_per_m;
|
||||
}
|
||||
while (fn >= frames_per_s) {
|
||||
s++; fn -= frames_per_s;
|
||||
}
|
||||
f = fn;
|
||||
}
|
||||
|
||||
return wxString::Format(_T("%02i") + sep + _T("%02i") + sep + _T("%02i") + sep + _T("%02i"),h,m,s,f);
|
||||
}
|
||||
|
|
|
@ -78,21 +78,28 @@ bool operator <= (AssTime &t1, AssTime &t2);
|
|||
bool operator >= (AssTime &t1, AssTime &t2);
|
||||
|
||||
|
||||
|
||||
/////////////////////////////
|
||||
// Class for that annoying SMPTE format timecodes stuff
|
||||
class FractionalTime {
|
||||
private:
|
||||
int64_t time; // milliseconds, like in AssTime
|
||||
double num, den; // numerator/denominator
|
||||
int time; // milliseconds, like in AssTime
|
||||
int num, den; // numerator/denominator
|
||||
bool drop; // EVIL
|
||||
wxString sep; // separator; someone might have separators of more than one character :V
|
||||
|
||||
// A period is roughly 10 minutes and is used for the dropframe stuff;
|
||||
// SMPTE dropframe timecodes drops 18 timestamps per 18000, hence the number 17982.
|
||||
static const int frames_per_period = 17982;
|
||||
|
||||
public:
|
||||
// dumb assumption? I give no fuck
|
||||
FractionalTime(wxString separator, double numerator=30.0, double denominator=1.0);
|
||||
// NOTE: separator can be a regex! at least if you only plan on doing SMPTE->somethingelse.
|
||||
FractionalTime(wxString separator, int numerator=30, int denominator=1, bool dropframe=false);
|
||||
~FractionalTime();
|
||||
|
||||
AssTime ToAssTime(wxString fractime);
|
||||
int64_t ToMillisecs(wxString fractime);
|
||||
int ToMillisecs(wxString fractime);
|
||||
|
||||
wxString FromAssTime(AssTime time);
|
||||
wxString FromMillisecs(int64_t msec);
|
||||
|
|
|
@ -279,11 +279,13 @@ wxString SubtitleFormat::GetWildcards(int mode) {
|
|||
|
||||
/////////////////////////////////
|
||||
// Ask the user to enter the FPS
|
||||
double SubtitleFormat::AskForFPS(bool palNtscOnly) {
|
||||
SubtitleFormat::FPSRational SubtitleFormat::AskForFPS(bool showSMPTE) {
|
||||
wxArrayString choices;
|
||||
FPSRational fps_rat;
|
||||
fps_rat.smpte_dropframe = false; // ensure it's false by default
|
||||
|
||||
// Video FPS
|
||||
bool vidLoaded = !palNtscOnly && VFR_Output.IsLoaded();
|
||||
bool vidLoaded = VFR_Output.IsLoaded();
|
||||
if (vidLoaded) {
|
||||
wxString vidFPS;
|
||||
if (VFR_Output.GetFrameRateType() == VFR) vidFPS = _T("VFR");
|
||||
|
@ -292,49 +294,73 @@ double SubtitleFormat::AskForFPS(bool palNtscOnly) {
|
|||
}
|
||||
|
||||
// Standard FPS values
|
||||
if (!palNtscOnly) {
|
||||
choices.Add(_("15.000 FPS"));
|
||||
choices.Add(_("23.976 FPS (Decimated NTSC)"));
|
||||
choices.Add(_("24.000 FPS (FILM)"));
|
||||
}
|
||||
choices.Add(_("25.000 FPS (PAL)"));
|
||||
choices.Add(_("29.970 FPS (NTSC)"));
|
||||
if (!palNtscOnly) {
|
||||
if (showSMPTE)
|
||||
choices.Add(_("29.970 FPS (NTSC with SMPTE dropframe)"));
|
||||
choices.Add(_("30.000 FPS"));
|
||||
choices.Add(_("50.000 FPS (PAL x2)"));
|
||||
choices.Add(_("59.940 FPS (NTSC x2)"));
|
||||
choices.Add(_("60.000 FPS"));
|
||||
choices.Add(_("119.880 FPS (NTSC x4)"));
|
||||
choices.Add(_("120.000 FPS"));
|
||||
}
|
||||
|
||||
// Ask
|
||||
int choice = wxGetSingleChoiceIndex(_("Please choose the appropriate FPS for the subtitles:"),_("FPS"),choices);
|
||||
if (choice == -1) return 0.0;
|
||||
if (choice == -1) {
|
||||
fps_rat.num = 0;
|
||||
fps_rat.den = 0;
|
||||
|
||||
// PAL/NTSC choice
|
||||
if (palNtscOnly) {
|
||||
if (choice == 0) return 25.0;
|
||||
else return 30.0 / 1.001;
|
||||
return fps_rat;
|
||||
}
|
||||
|
||||
// Get FPS from choice
|
||||
if (vidLoaded) choice--;
|
||||
// dropframe was displayed, that means all choices >4 are bumped up by 1
|
||||
if (showSMPTE) {
|
||||
switch (choice) {
|
||||
case -1: return -1.0; break; // VIDEO
|
||||
case 0: return 15.0; break;
|
||||
case 1: return 24.0 / 1.001; break;
|
||||
case 2: return 24.0; break;
|
||||
case 3: return 25.0; break;
|
||||
case 4: return 30.0 / 1.001; break;
|
||||
case 5: return 30.0; break;
|
||||
case 6: return 60.0 / 1.001; break;
|
||||
case 7: return 60.0; break;
|
||||
case 8: return 120.0 / 1.001; break;
|
||||
case 9: return 120.0; break;
|
||||
case -1: fps_rat.num = -1; fps_rat.den = 1; break; // VIDEO
|
||||
case 0: fps_rat.num = 15; fps_rat.den = 1; break;
|
||||
case 1: fps_rat.num = 24000; fps_rat.den = 1001; break;
|
||||
case 2: fps_rat.num = 24; fps_rat.den = 1; break;
|
||||
case 3: fps_rat.num = 25; fps_rat.den = 1; break;
|
||||
case 4: fps_rat.num = 30000; fps_rat.den = 1001; break;
|
||||
case 5: fps_rat.num = 30000; fps_rat.den = 1001; fps_rat.smpte_dropframe = true; break;
|
||||
case 6: fps_rat.num = 30; fps_rat.den = 1; break;
|
||||
case 7: fps_rat.num = 50; fps_rat.den = 1; break;
|
||||
case 8: fps_rat.num = 60000; fps_rat.den = 1001; break;
|
||||
case 9: fps_rat.num = 60; fps_rat.den = 1; break;
|
||||
case 10: fps_rat.num = 120000; fps_rat.den = 1001; break;
|
||||
case 11: fps_rat.num = 120; fps_rat.den = 1; break;
|
||||
}
|
||||
return fps_rat;
|
||||
} else {
|
||||
// dropframe wasn't displayed
|
||||
switch (choice) {
|
||||
case -1: fps_rat.num = -1; fps_rat.den = 1; break; // VIDEO
|
||||
case 0: fps_rat.num = 15; fps_rat.den = 1; break;
|
||||
case 1: fps_rat.num = 24000; fps_rat.den = 1001; break;
|
||||
case 2: fps_rat.num = 24; fps_rat.den = 1; break;
|
||||
case 3: fps_rat.num = 25; fps_rat.den = 1; break;
|
||||
case 4: fps_rat.num = 30000; fps_rat.den = 1001; break;
|
||||
case 5: fps_rat.num = 30; fps_rat.den = 1; break;
|
||||
case 6: fps_rat.num = 50; fps_rat.den = 1; break;
|
||||
case 7: fps_rat.num = 60000; fps_rat.den = 1001; break;
|
||||
case 8: fps_rat.num = 60; fps_rat.den = 1; break;
|
||||
case 9: fps_rat.num = 120000; fps_rat.den = 1001; break;
|
||||
case 10: fps_rat.num = 120; fps_rat.den = 1; break;
|
||||
}
|
||||
return fps_rat;
|
||||
}
|
||||
|
||||
// fubar
|
||||
return 0.0;
|
||||
fps_rat.num = 0;
|
||||
fps_rat.den = 0;
|
||||
|
||||
return fps_rat;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -65,6 +65,12 @@ private:
|
|||
static bool loaded;
|
||||
|
||||
protected:
|
||||
struct FPSRational {
|
||||
int num;
|
||||
int den;
|
||||
bool smpte_dropframe;
|
||||
};
|
||||
|
||||
std::list<AssEntry*> *Line;
|
||||
|
||||
void CreateCopy();
|
||||
|
@ -81,7 +87,7 @@ protected:
|
|||
void LoadDefault(bool defline=true);
|
||||
AssFile *GetAssFile() { return assFile; }
|
||||
int AddLine(wxString data,wxString group,int lasttime,int &version,wxString *outgroup=NULL);
|
||||
double AskForFPS(bool palNtscOnly=false);
|
||||
FPSRational AskForFPS(bool showSMPTE=false);
|
||||
|
||||
virtual wxString GetName()=0;
|
||||
virtual wxArrayString GetReadWildcards();
|
||||
|
|
|
@ -70,8 +70,8 @@ bool EncoreSubtitleFormat::CanWriteFile(wxString filename) {
|
|||
// Write file
|
||||
void EncoreSubtitleFormat::WriteFile(wxString _filename,wxString encoding) {
|
||||
// Get FPS
|
||||
double fps = AskForFPS(true);
|
||||
if (fps <= 0.0) return;
|
||||
FPSRational fps_rat = AskForFPS(true);
|
||||
if (fps_rat.num <= 0 || fps_rat.den <= 0) return;
|
||||
|
||||
// Open file
|
||||
TextFileWriter file(_filename,encoding);
|
||||
|
@ -88,14 +88,14 @@ void EncoreSubtitleFormat::WriteFile(wxString _filename,wxString encoding) {
|
|||
using std::list;
|
||||
int i = 0;
|
||||
|
||||
// Encore wants ; instead of : if we're dealing with NTSC
|
||||
FractionalTime fp(fps > 26.0 ? _T(";") : _T(":"), fps);
|
||||
// Encore wants ; instead of : if we're dealing with NTSC dropframe stuff
|
||||
FractionalTime ft(fps_rat.smpte_dropframe ? _T(";") : _T(":"), fps_rat.num, fps_rat.den, fps_rat.smpte_dropframe);
|
||||
|
||||
for (list<AssEntry*>::iterator cur=Line->begin();cur!=Line->end();cur++) {
|
||||
AssDialogue *current = AssEntry::GetAsDialogue(*cur);
|
||||
if (current && !current->Comment) {
|
||||
// Time stamps
|
||||
wxString timeStamps = wxString::Format(_T("%i "),++i) + fp.FromAssTime(current->Start) + _T(" ") + fp.FromAssTime(current->End);
|
||||
wxString timeStamps = wxString::Format(_T("%i "),++i) + ft.FromAssTime(current->Start) + _T(" ") + ft.FromAssTime(current->End);
|
||||
|
||||
// Write
|
||||
file.WriteLineToFile(timeStamps + current->Text);
|
||||
|
|
|
@ -110,6 +110,7 @@ void MicroDVDSubtitleFormat::ReadFile(wxString filename,wxString forceEncoding)
|
|||
|
||||
// Loop
|
||||
bool isFirst = true;
|
||||
FPSRational fps_rat;
|
||||
double fps = 0.0;
|
||||
while (file.HasMoreLines()) {
|
||||
wxString line = file.ReadLineFromFile();
|
||||
|
@ -133,9 +134,9 @@ void MicroDVDSubtitleFormat::ReadFile(wxString filename,wxString forceEncoding)
|
|||
|
||||
// If it wasn't an fps line, ask the user for it
|
||||
if (fps <= 0.0) {
|
||||
fps = AskForFPS();
|
||||
if (fps == 0.0) return;
|
||||
else if (fps > 0.0) cfr.SetCFR(fps);
|
||||
fps_rat = AskForFPS();
|
||||
if (fps_rat.num == 0) return;
|
||||
else if (fps_rat.num > 0) cfr.SetCFR(double(fps_rat.num)/double(fps_rat.den));
|
||||
else rate = &VFR_Output;
|
||||
}
|
||||
else {
|
||||
|
@ -172,9 +173,10 @@ void MicroDVDSubtitleFormat::WriteFile(wxString filename,wxString encoding) {
|
|||
// Set FPS
|
||||
FrameRate cfr;
|
||||
FrameRate *rate = 𝔠
|
||||
double fps = AskForFPS();
|
||||
if (fps == 0.0) return;
|
||||
else if (fps > 0.0) cfr.SetCFR(fps);
|
||||
FPSRational fps_rat = AskForFPS();
|
||||
if (fps_rat.num == 0 || fps_rat.den == 0) return;
|
||||
double fps = double(fps_rat.num) / double(fps_rat.den);
|
||||
if (fps > 0.0) cfr.SetCFR(fps);
|
||||
else rate = &VFR_Output;
|
||||
|
||||
// Convert file
|
||||
|
|
|
@ -74,8 +74,8 @@ bool TranStationSubtitleFormat::CanWriteFile(wxString filename) {
|
|||
// Write file
|
||||
void TranStationSubtitleFormat::WriteFile(wxString _filename,wxString encoding) {
|
||||
// Get FPS
|
||||
double fps = AskForFPS(true);
|
||||
if (fps <= 0.0) return;
|
||||
FPSRational fps_rat = AskForFPS(true);
|
||||
if (fps_rat.num <= 0 || fps_rat.den <= 0) return;
|
||||
|
||||
// Open file
|
||||
TextFileWriter file(_filename,encoding);
|
||||
|
@ -89,9 +89,31 @@ void TranStationSubtitleFormat::WriteFile(wxString _filename,wxString encoding)
|
|||
|
||||
// Write lines
|
||||
using std::list;
|
||||
AssDialogue *current = NULL;
|
||||
AssDialogue *next = NULL;
|
||||
for (list<AssEntry*>::iterator cur=Line->begin();cur!=Line->end();cur++) {
|
||||
AssDialogue *current = AssEntry::GetAsDialogue(*cur);
|
||||
if (next)
|
||||
current = next;
|
||||
next = AssEntry::GetAsDialogue(*cur);
|
||||
|
||||
if (current && !current->Comment) {
|
||||
// Write text
|
||||
file.WriteLineToFile(ConvertLine(current,&fps_rat,(next && !next->Comment) ? next->Start.GetMS() : -1));
|
||||
file.WriteLineToFile(_T(""));
|
||||
}
|
||||
}
|
||||
// flush last line
|
||||
if (next && !next->Comment)
|
||||
file.WriteLineToFile(ConvertLine(next,&fps_rat,-1));
|
||||
|
||||
// Every file must end with this line
|
||||
file.WriteLineToFile(_T("SUB["));
|
||||
|
||||
// Clean up
|
||||
ClearCopy();
|
||||
}
|
||||
|
||||
wxString TranStationSubtitleFormat::ConvertLine(AssDialogue *current, FPSRational *fps_rat, int nextl_start) {
|
||||
// Get line data
|
||||
AssStyle *style = GetAssFile()->GetStyle(current->Style);
|
||||
int valign = 0;
|
||||
|
@ -112,13 +134,15 @@ void TranStationSubtitleFormat::WriteFile(wxString _filename,wxString encoding)
|
|||
// Write header
|
||||
AssTime start = current->Start;
|
||||
AssTime end = current->End;
|
||||
// Subtract half a frame duration from end time, since it is inclusive
|
||||
// and we otherwise run the risk of having two lines overlap in a
|
||||
// frame, when they should run right into each other.
|
||||
end.SetMS(end.GetMS() - (int)(500.0/fps));
|
||||
FractionalTime ft(_T(":"),fps);
|
||||
wxString header = wxString::Format(_T("SUB[%i%s%s "),valign,halign,type) + ft.FromAssTime(start) + _T(">") + ft.FromAssTime(end) + _T("]");
|
||||
file.WriteLineToFile(header);
|
||||
|
||||
// Subtract one frame if the end time of the current line is equal to the
|
||||
// start of next one, since the end timestamp is inclusive and the lines
|
||||
// would overlap if left as is.
|
||||
if (nextl_start > 0 && end.GetMS() == nextl_start)
|
||||
end.SetMS(end.GetMS() - ((1000*fps_rat->den)/fps_rat->num));
|
||||
|
||||
FractionalTime ft(_T(":"), fps_rat->num, fps_rat->den, fps_rat->smpte_dropframe);
|
||||
wxString header = wxString::Format(_T("SUB[%i%s%s "),valign,halign,type) + ft.FromAssTime(start) + _T(">") + ft.FromAssTime(end) + _T("]\r\n");
|
||||
|
||||
// Process text
|
||||
wxString lineEnd = _T("\r\n");
|
||||
|
@ -128,15 +152,5 @@ void TranStationSubtitleFormat::WriteFile(wxString _filename,wxString encoding)
|
|||
current->Text.Replace(_T("\\N"),lineEnd,true);
|
||||
while (current->Text.Replace(lineEnd+lineEnd,lineEnd,true));
|
||||
|
||||
// Write text
|
||||
file.WriteLineToFile(current->Text);
|
||||
file.WriteLineToFile(_T(""));
|
||||
}
|
||||
}
|
||||
|
||||
// Every file must end with this line
|
||||
file.WriteLineToFile(_T("SUB["));
|
||||
|
||||
// Clean up
|
||||
ClearCopy();
|
||||
return header + current->Text;
|
||||
}
|
|
@ -45,6 +45,9 @@
|
|||
//////////////////////
|
||||
// TranStation writer
|
||||
class TranStationSubtitleFormat : public SubtitleFormat {
|
||||
private:
|
||||
wxString ConvertLine(AssDialogue *line, FPSRational *fps_rat, int nextl_start);
|
||||
|
||||
public:
|
||||
wxString GetName();
|
||||
wxArrayString GetWriteWildcards();
|
||||
|
|
Loading…
Reference in a new issue