Skip to content

Commit 8844e94

Browse files
authored
Add files via upload
1 parent 621aa19 commit 8844e94

1 file changed

Lines changed: 266 additions & 0 deletions

File tree

VFRToCFR.cpp

Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
#include "VapourSynth.h"
2+
#include "VSHelper.h"
3+
#include <vector>
4+
#include <deque>
5+
#include <fstream>
6+
#include <string>
7+
#define EPSILON 0.0005
8+
9+
struct timestamp {
10+
unsigned int num;
11+
double start;
12+
double end;
13+
};
14+
15+
struct VFRToCFRData {
16+
VSNodeRef *node;
17+
VSVideoInfo vi;
18+
bool drop;
19+
std::vector<unsigned int> frameMap;
20+
};
21+
22+
static void VS_CC VFRToCFRInit(VSMap *in, VSMap *out, void **instanceData, VSNode *node, VSCore *core, const VSAPI *vsapi) {
23+
VFRToCFRData *d = static_cast<VFRToCFRData *>(*instanceData);
24+
vsapi->setVideoInfo(&d->vi, 1, node);
25+
}
26+
27+
static const VSFrameRef *VS_CC VFRToCFRGetFrame(int n, int activationReason, void **instanceData, void **frameData, VSFrameContext *frameCtx, VSCore *core, const VSAPI *vsapi) {
28+
VFRToCFRData *d = static_cast<VFRToCFRData *>(* instanceData);
29+
30+
if (activationReason == arInitial) {
31+
vsapi->requestFrameFilter(d->frameMap[n], d->node, frameCtx);
32+
}
33+
else if (activationReason == arAllFramesReady) {
34+
const VSFrameRef *src = vsapi->getFrameFilter(d->frameMap[n], d->node, frameCtx);
35+
VSFrameRef *dst = vsapi->copyFrame(src, core);
36+
VSMap *m = vsapi->getFramePropsRW(dst);
37+
vsapi->freeFrame(src);
38+
vsapi->propSetInt(m, "_DurationNum", d->vi.fpsDen, paReplace);
39+
vsapi->propSetInt(m, "_DurationDen", d->vi.fpsNum, paReplace);
40+
return dst;
41+
}
42+
43+
return nullptr;
44+
}
45+
46+
static void VS_CC VFRToCFRFree(void *instanceData, VSCore *core, const VSAPI *vsapi) {
47+
VFRToCFRData *d = static_cast<VFRToCFRData *>(instanceData);
48+
vsapi->freeNode(d->node);
49+
delete d;
50+
}
51+
52+
static void VS_CC VFRToCFRCreate(const VSMap *in, VSMap *out, void *userData, VSCore *core, const VSAPI *vsapi) {
53+
VFRToCFRData d{ vsapi->propGetNode(in, "clip", 0, 0), *vsapi->getVideoInfo(d.node) };
54+
int err;
55+
56+
d.vi.fpsNum = vsapi->propGetInt(in, "fpsnum", 0, &err);
57+
d.vi.fpsDen = vsapi->propGetInt(in, "fpsden", 0, &err);
58+
if (d.vi.fpsNum <= 0 || d.vi.fpsDen <= 0) {
59+
vsapi->setError(out, "Both fpsnum and fpsden need to be greater than 0.");
60+
vsapi->freeNode(d.node);
61+
return;
62+
}
63+
64+
d.drop = !!vsapi->propGetInt(in, "drop", 0, &err);
65+
if (err)
66+
d.drop = true;
67+
68+
std::string timecodes{ vsapi->propGetData(in, "timecodes", 0, &err) };
69+
70+
unsigned int count{ 0 };
71+
std::deque<timestamp> inTimes;
72+
std::ifstream file(timecodes);
73+
if (!file) {
74+
vsapi->setError(out, "Failed to open the timecodes file.");
75+
vsapi->freeNode(d.node);
76+
return;
77+
}
78+
std::string line;
79+
std::getline(file, line);
80+
while (file) {
81+
double i = 0;
82+
file >> i;
83+
timestamp time;
84+
time.start = i / 1000.0;
85+
time.num = count;
86+
count++;
87+
inTimes.push_back(time);
88+
}
89+
90+
//If last line is NULL, remove the last timestamp.
91+
if (inTimes.size() > 1 && inTimes.back().start <= 0)
92+
inTimes.pop_back();
93+
94+
for (unsigned int i = 0; i < inTimes.size(); i++) {
95+
if (i != inTimes.size() - 1) {
96+
inTimes[i].end = inTimes[i + 1].start;
97+
}
98+
}
99+
inTimes.back().end = inTimes.back().start + (inTimes.back().start / (inTimes.size() - 1));
100+
101+
unsigned int frames = (inTimes.back().start * d.vi.fpsNum / d.vi.fpsDen);
102+
103+
double check = frames / static_cast<double>(d.vi.fpsNum) * d.vi.fpsDen;
104+
while (check < inTimes.back().end && abs(check - inTimes.back().end) > static_cast<double>(EPSILON)) {
105+
frames++;
106+
check = frames / static_cast<double>(d.vi.fpsNum) * d.vi.fpsDen;
107+
}
108+
109+
d.frameMap.resize(frames);
110+
bool drop = false;
111+
std::deque<timestamp> choices;
112+
for (unsigned int i = 0; i < frames; i++) {
113+
double starttime = i * d.vi.fpsDen / static_cast<double>(d.vi.fpsNum);
114+
double endtime = (i + 1) * d.vi.fpsDen / static_cast<double>(d.vi.fpsNum);
115+
116+
//Remove frames that are eariler than the last frame chosen.
117+
while (!choices.empty() && d.frameMap[i - 1] > choices.front().num) {
118+
choices.pop_front();
119+
}
120+
121+
//Remove frames than can't be a choice for this frame.
122+
while (!choices.empty() && (starttime > choices.front().end || abs(starttime - choices.front().end) < static_cast<double>(EPSILON))) {
123+
choices.pop_front();
124+
}
125+
126+
//Add possible choices.
127+
while (!inTimes.empty() && endtime > inTimes.front().start && abs(endtime - inTimes.front().start) > static_cast<double>(EPSILON)) {
128+
129+
//This if statement can only be false when the framerate from the timecodes
130+
//file is really low (unrealistic).
131+
if (starttime < inTimes.front().end && abs(starttime - inTimes.front().end) > static_cast<double>(EPSILON)) {
132+
choices.push_back(inTimes.front());
133+
}
134+
inTimes.pop_front();
135+
}
136+
137+
//Should only occur with timestamps not starting at 0.
138+
if (choices.size() == 0) {
139+
if (inTimes.front().num == 0) {
140+
d.frameMap[i] = 0;
141+
}
142+
else {
143+
vsapi->setError(out, "No valid matches found. Framerate might be too high.");
144+
vsapi->freeNode(d.node);
145+
return;
146+
}
147+
}
148+
else {
149+
std::deque<timestamp> choicesTemp{ choices };
150+
151+
//Choose the VFR frame(s) with the longest duration.
152+
if (choicesTemp.size() > 1) {
153+
double longest = 0;
154+
for (unsigned int j = 0; j < choicesTemp.size(); j++) {
155+
double start;
156+
if (starttime > choicesTemp[j].start) {
157+
start = starttime;
158+
}
159+
else {
160+
start = choicesTemp[j].start;
161+
}
162+
double end;
163+
if (endtime < choicesTemp[j].end) {
164+
end = endtime;
165+
}
166+
else {
167+
end = choicesTemp[j].end;
168+
}
169+
if (longest < (end - start)) {
170+
longest = end - start;
171+
}
172+
}
173+
for (unsigned int j = 0; j < choicesTemp.size(); j++) {
174+
double start;
175+
if (starttime > choicesTemp[j].start) {
176+
start = starttime;
177+
}
178+
else {
179+
start = choicesTemp[j].start;
180+
}
181+
double end;
182+
if (endtime < choicesTemp[j].end) {
183+
end = endtime;
184+
}
185+
else {
186+
end = choicesTemp[j].end;
187+
}
188+
if (abs(longest - (end - start)) > static_cast<double>(EPSILON)) {
189+
choicesTemp.erase(choicesTemp.begin() + j);
190+
j--;
191+
}
192+
}
193+
}
194+
195+
//Choose the VFR frame(s) that is closest to the center of the CFR frame.
196+
if (choicesTemp.size() > 1) {
197+
double mid = ((endtime - starttime) / 2) + starttime;
198+
double shortest = endtime - starttime;
199+
bool found = false;
200+
timestamp hasMid;
201+
for (unsigned int j = 0; j < choicesTemp.size(); j++) {
202+
if (abs(mid - choicesTemp[j].start) < shortest) {
203+
shortest = abs(mid - choicesTemp[j].start);
204+
}
205+
if (abs(mid - choicesTemp[j].end) < shortest) {
206+
shortest = abs(mid - choicesTemp[j].end);
207+
}
208+
if (mid > choicesTemp[j].start && abs(mid - choicesTemp[j].start) > static_cast<double>(EPSILON) && mid < choicesTemp[j].end && abs(mid - choicesTemp[j].end) > static_cast<double>(EPSILON)) {
209+
hasMid = choicesTemp[j];
210+
found = true;
211+
break;
212+
}
213+
}
214+
if (found) {
215+
choicesTemp.clear();
216+
choicesTemp.push_back(hasMid);
217+
}
218+
else {
219+
for (unsigned int j = 0; j < choicesTemp.size(); j++) {
220+
if (abs(shortest - abs(mid - choicesTemp[j].start)) > static_cast<double>(EPSILON) && abs(shortest - abs(mid - choicesTemp[j].end)) > static_cast<double>(EPSILON)) {
221+
choicesTemp.erase(choicesTemp.begin() + j);
222+
j--;
223+
}
224+
}
225+
}
226+
}
227+
228+
//By now, there should be no more than two matches left.
229+
230+
//Choose the frame that hasn't been used for the CFR video yet.
231+
if (choicesTemp.size() > 1 && i != 0) {
232+
if (d.frameMap[i - 1] == choicesTemp.front().num) {
233+
choicesTemp.pop_front();
234+
}
235+
else if (d.frameMap[i - 1] == choicesTemp.back().num) {
236+
choicesTemp.pop_back();
237+
}
238+
}
239+
240+
//If none of them have been used, choose the first one.
241+
d.frameMap[i] = choicesTemp.front().num;
242+
243+
//Check if frames were dropped.
244+
if (i != 0 && choicesTemp.front().num - d.frameMap[i - 1] > 1) {
245+
drop = true;
246+
}
247+
}
248+
}
249+
250+
if (!d.drop && drop) {
251+
vsapi->setError(out, "Frames were dropped. Use drop = true if you want to allow frame drops.");
252+
vsapi->freeNode(d.node);
253+
return;
254+
}
255+
256+
d.vi.numFrames = frames;
257+
258+
VFRToCFRData* data = new VFRToCFRData{ d };
259+
260+
vsapi->createFilter(in, out, "Invert", VFRToCFRInit, VFRToCFRGetFrame, VFRToCFRFree, fmParallel, 0, data, core);
261+
}
262+
263+
VS_EXTERNAL_API(void) VapourSynthPluginInit(VSConfigPlugin configFunc, VSRegisterFunction registerFunc, VSPlugin *plugin) {
264+
configFunc("blaze.plugin.vfrtocfr", "vfrtocfr", "VFR to CFR Video Converter", VAPOURSYNTH_API_VERSION, 1, plugin);
265+
registerFunc("VFRToCFR", "clip:clip;timecodes:data;fpsnum:int;fpsden:int;drop:int:opt;", VFRToCFRCreate, nullptr, plugin);
266+
}

0 commit comments

Comments
 (0)