diff --git a/OverLua/avisynth.cpp b/OverLua/avisynth.cpp index 196014270..84c850196 100644 --- a/OverLua/avisynth.cpp +++ b/OverLua/avisynth.cpp @@ -31,6 +31,7 @@ #include "avisynth.h" #include "overlua.h" +#include "vfr.h" // Lots of code lifted from the CSRI avisynth.cpp @@ -38,6 +39,7 @@ class OverLuaAvisynth : public GenericVideoFilter { private: OverLuaScript *script; double spf; // seconds per frame - for frame/timestamp conversion + VFRTranslator *vfr; public: OverLuaAvisynth(PClip _child, IScriptEnvironment *env, const char *file, const char *datastring, const char *vfrfile) @@ -55,6 +57,10 @@ public: try { script = new OverLuaScript(file, datastring); spf = (double)vi.fps_denominator / (double)vi.fps_numerator; + if (vfrfile) + vfr = GetVFRTranslator(vfrfile); + else + vfr = 0; } catch (const char *e) { env->ThrowError(e); @@ -66,6 +72,8 @@ public: ~OverLuaAvisynth() { + if (vfr) + delete vfr; delete script; } @@ -76,6 +84,8 @@ public: env->MakeWritable(&avsframe); double frametime = n * spf; + if (vfr) + frametime = vfr->TimeStampFromFrameNumber(n); ptrdiff_t stride = avsframe->GetPitch(); unsigned char *plane = avsframe->GetWritePtr(); diff --git a/OverLua/docs/overlua.txt b/OverLua/docs/overlua.txt index 26a72e53b..b1cd017ee 100644 --- a/OverLua/docs/overlua.txt +++ b/OverLua/docs/overlua.txt @@ -20,7 +20,6 @@ subtitles/karaoke. vfrfile is the path to a timecode file Matroska format 1 or 2. If supplied, it will be used to translate frame numbers to timestamps instead of relying on the frame rate provided by Avisynth. -VFR support is not implemented yet. API the Lua script must implement diff --git a/OverLua/vfr.cpp b/OverLua/vfr.cpp new file mode 100644 index 000000000..60f300db5 --- /dev/null +++ b/OverLua/vfr.cpp @@ -0,0 +1,184 @@ +/* + * VFR translation functions for OverLua + * + + Copyright 2007 Niels Martin Hansen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + Contact: + E-mail: + IRC: jfs in #aegisub on irc.rizon.net + + */ + +#include "vfr.h" +#include +#include +#include + + +// Work with seconds per frame (spf) here instead of fps since that's more natural for the translation we're doing + + +class TimecodesV1 : public VFRTranslator { +private: + // Used when sections run out + double default_spf; + double first_non_section_timestamp; + int first_non_section_frame; + + // Also generate sections for unspecified ones + // (use the default framerate then) + struct FrameRateSection { + double start_time; + double spf; + int start_frame; + int end_frame; + }; + std::vector sections; + +public: + virtual double TimeStampFromFrameNumber(int n) + { + // Find correct section + for (size_t i = 0; i < sections.size(); i++) { + FrameRateSection § = sections[i]; + if (n >= sect.start_frame && n <= sect.end_frame) { + return sect.start_time + (n - sect.start_frame) * sect.spf; + } + } + // Not in a section + if (n < 0) return 0.0; + return first_non_section_timestamp + (n - first_non_section_frame) * default_spf; + } + + TimecodesV1(FILE *vfrfile) + { + char buf[100]; + + default_spf = -1; + double cur_time = 0.0; + + FrameRateSection temp_section; + temp_section.start_time = 0.0; + temp_section.spf = -1; + temp_section.start_frame = 0; + temp_section.end_frame = 0; + + while (fgets(buf, 100, vfrfile)) { + // Comment? + if (buf[0] == '#') continue; + + if (strncmp(buf, "Assume ", 7) == 0 && default_spf < 0) { + char *num = buf+7; + default_spf = atof(num); + if (default_spf > 0) + default_spf = 1 / default_spf; + else + default_spf = -1; + temp_section.spf = default_spf; + continue; + } + + int start_frame, end_frame; + double fps; + if (scanf("%d,%d,%f", &start_frame, &end_frame, &fps) == 3) { + // Finish the current temp section + temp_section.end_frame = start_frame - 1; + if (temp_section.end_frame >= temp_section.start_frame) { + cur_time += (temp_section.end_frame - temp_section.start_frame + 1) * temp_section.spf; + sections.push_back(temp_section); + } + // Insert the section corresponding to this line + temp_section.spf = 1/fps; + temp_section.start_frame = start_frame; + temp_section.end_frame = end_frame; + temp_section.start_time = cur_time; + cur_time += (end_frame - start_frame + 1) / fps; + sections.push_back(temp_section); + // Begin new temp section + temp_section.spf = default_spf; + temp_section.start_frame = end_frame + 1; + temp_section.end_frame = end_frame; // yes, negative duration + temp_section.start_time = cur_time; + } + } + + first_non_section_timestamp = cur_time; + first_non_section_frame = temp_section.start_frame; + } + +}; + + +class TimecodesV2 : public VFRTranslator { +private: + // Main data + std::vector timestamps; + // For when data are exhausted (well, they shouldn't, then the vfr file is bad) + int last_known_frame; + double last_known_timestamp; + double assumed_spf; + +public: + virtual double TimeStampFromFrameNumber(int n) + { + if (n < (int)timestamps.size() && n >= 0) { + return timestamps[n]; + } + if (n < 0) return 0.0; + return last_known_timestamp + (n - last_known_frame) * assumed_spf; + } + + TimecodesV2(FILE *vfrfile) + { + char buf[50]; + + timestamps.reserve(8192); // should be enough for most cases + + while (fgets(buf, 50, vfrfile)) { + // Comment? + if (buf[0] == '#') continue; + // Otherwise assume it's a good timestamp + timestamps.push_back(atof(buf)); + } + + last_known_frame = (int)timestamps.size()-1; + last_known_timestamp = timestamps[last_known_frame]; + assumed_spf = 1/25.0; // yes, this is stupid - anyone got a better idea? + } + +}; + + +VFRTranslator *GetVFRTranslator(const char *vfrfile) +{ + char buf[32]; + buf[19] = 0; // In "# timecode format v1" the version number is character index 19 + FILE *f = fopen(vfrfile, "r"); + VFRTranslator *res = 0; + if (fgets(buf, 32, f) && buf[0] == '#') { + // So do some really shoddy parsing here, assume the file is good + if (buf[19] == '1') { + res = new TimecodesV1(f); + } else if (buf[19] == '2') { + res = new TimecodesV2(f); + } + } + fclose(f); + return res; +} + diff --git a/OverLua/vfr.h b/OverLua/vfr.h new file mode 100644 index 000000000..2f6d997a0 --- /dev/null +++ b/OverLua/vfr.h @@ -0,0 +1,37 @@ +/* + * VFR translation functions for OverLua + * + + Copyright 2007 Niels Martin Hansen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + Contact: + E-mail: + IRC: jfs in #aegisub on irc.rizon.net + + */ + +#ifndef VFR_H +#define VFR_H + +class VFRTranslator { +public: + virtual double TimeStampFromFrameNumber(int n) = 0; +}; + +VFRTranslator *GetVFRTranslator(const char *vfrfile); + +#endif