Skip to content

Commit

Permalink
Add scene detection threshold
Browse files Browse the repository at this point in the history
  • Loading branch information
jojje committed Feb 3, 2019
1 parent 21f7987 commit 7a210cd
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 12 deletions.
30 changes: 27 additions & 3 deletions Cycle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
#include <memory>
#include "Cycle.h"

float sceneThreshold;

int cmpDescendingDiff(const void * a, const void * b);

Cycle::Cycle(int length, int creates) :
Expand Down Expand Up @@ -75,22 +77,44 @@ bool Cycle::includes(int frame) {
}

bool Cycle::isBadFrame(int frame) {
for (int i = 0; i < creates; i++) {
// Rules:
// 1. A scene frame is only considered if there is at least two frames in the cycle.
// 2. If there are two or more frames, and at least one in the cycle is above the threshold,
// then that frame will be regarded as the scene change, and all others will be judged
// depending on number of creates required for the cycle (ordered by diffs in descending order).
sortDiffsIfNeeded();
int sceneChanges = 0;
for (int i = 0; i < creates + sceneChanges && i < length; i++) {
if (creates < length && sceneChanges < 1 && sceneThreshold < sortedDiffs[i].diff) {
sceneChanges++;
continue;
}
if (getFrameWithLargestDiff(i) == frame) {
return true;
}
}
return false;
}

bool Cycle::isSceneChange(int frame) {
sortDiffsIfNeeded();
return creates < length // Ignore scene change logic unless there is an *option* to select frames.
&& sortedDiffs[0].frame == frame
&& sceneThreshold < sortedDiffs[0].diff;
}

int Cycle::getFrameWithLargestDiff(int offset) {
sortDiffsIfNeeded();
if (offset > length - 1) return -1;
return sortedDiffs[offset].frame;
}

void Cycle::sortDiffsIfNeeded() {
if (!sorted) {
memcpy(sortedDiffs.get(), diffs.get(), length * sizeof(CycleDiff));
qsort(sortedDiffs.get(), length, sizeof(CycleDiff), cmpDescendingDiff);
sorted = true;
}
if (offset > length - 1) return -1;
return sortedDiffs[offset].frame;
}

int cmpDescendingDiff(const void * a, const void * b) {
Expand Down
4 changes: 4 additions & 0 deletions Cycle.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@

#include <memory>

extern float sceneThreshold;

typedef struct {
int frame; // frame number
float diff; // frame diff to previous
Expand All @@ -33,6 +35,7 @@ typedef struct {

class Cycle {
bool sorted;
void sortDiffsIfNeeded();

public:
int creates; // number of frames to create in the cycle (n in m creation)
Expand All @@ -47,6 +50,7 @@ class Cycle {
int getFrameWithLargestDiff(int offset);
bool includes(int frame);
bool isBadFrame(int n);
bool isSceneChange(int n);
void reset();
void updateFrameMap();
};
3 changes: 3 additions & 0 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ Default: `1`
When scripting AviSynth, users are prevented from accessing individual frames, as selecting frames is the purview of the application leveraging AviSynth. Filter plugins however have greater flexibility and can pick and choose which frame to use and this *offset* option gives you some flexibility in selecting replacement frames that do not necessarily reside at the same frame number as the primary clip's. Of course, you could achieve the effect of fixed offset like this by using *Trim* and *Loop*, so this is just a shorthand to save you from having to stitch and slice clips in the script.
Default: `-1` (frame with number before current_frame)

* `scene`: Scene detection threshold.
Any frame difference ("YDifferenceFromPrevious") above this threshold will be regarded as a scene change. When a scene change frame is detected, the frame from the source clip will be used instead of the alt-clip. When a scene change frame is detected in a cycle, the frame with the next largest frame diff will be picked instead. In short, if a scene change is detected in a cycle, then the frame with the largest diff will be removed from skip tagging. There is _one_ exception to this rule, namely that the number of frames to "create" must be less than the "cycle" length. If both are equal, then scene detection is disabled, so as to ensure the specified number of frames to create per cycle is always followed. The exception is there to ensure audio does not get out of sync, as the number of frames inserted would otherwise be totally dependent on how many scene changes were detected in a given clip.

* `debug`: Display various internal metrics as an image overlay.
Default: `false`

Expand Down
15 changes: 11 additions & 4 deletions SmoothSkip.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <stdio.h>
#include "SmoothSkip.h"
#include "CycleCache.h"
#include "Cycle.h"
#include "FrameDiff.h"
#include "3rd-party/info.h"

Expand Down Expand Up @@ -75,11 +76,13 @@ PVideoFrame __stdcall SmoothSkip::GetFrame(int n, IScriptEnvironment* env) {
frame = info(env, frame, msg, 0, row++);
sprintf(msg, "FPS: %.3f (child: %.3f)", GetFps(this), GetFps(child));
frame = info(env, frame, msg, 0, row++);
sprintf(msg, "Scene: %.1f", sceneThreshold);
frame = info(env, frame, msg, 0, row++);
sprintf(msg, "Cycle frame diffs (child):");
frame = info(env, frame, msg, 0, row++);
for (int i = 0; i < cycle.length; i++) {
sprintf(msg, "%s %d (%.5f) ",
cycle.isBadFrame(cycle.diffs[i].frame) ? "*" : " ",
cycle.isSceneChange(cycle.diffs[i].frame) ? "S" : cycle.isBadFrame(cycle.diffs[i].frame) ? "*" : " ",
cycle.diffs[i].frame,
cycle.diffs[i].diff);
frame = info(env, frame, msg, 0, row++);
Expand Down Expand Up @@ -109,7 +112,7 @@ void SmoothSkip::updateCycle(IScriptEnvironment* env, int cn, VideoInfo cvi, Cyc

// Constructor
SmoothSkip::SmoothSkip(PClip _child, PClip _altclip, int cycleLen, int creates, int _offset,
bool _debug, IScriptEnvironment* env) :
double sceneThresh, bool _debug, IScriptEnvironment* env) :
GenericVideoFilter(_child), altclip(_altclip), offset(_offset), debug(_debug) {
VideoInfo avi = altclip->GetVideoInfo();
VideoInfo cvi = child->GetVideoInfo();
Expand All @@ -120,6 +123,9 @@ GenericVideoFilter(_child), altclip(_altclip), offset(_offset), debug(_debug) {
if (cycleLen > cvi.num_frames) raiseError(env, "Cycle can't be larger than the frames in source clip");
if (cycleLen > avi.num_frames) raiseError(env, "Cycle can't be larger than the frames in alt clip");
if (creates < 1 || creates > cycleLen) raiseError(env, "Create must be between 1 and the value of cycle (1 <= create <= cycle)");
if (sceneThresh < 0) raiseError(env, "Scene threshold must be >= 0.0");

sceneThreshold = static_cast<float>(sceneThresh); // Assign to static variable in the cycle header, so it can be used in the cycle logic

try {
cycles = new CycleCache(cycleLen, creates, cvi.num_frames);
Expand All @@ -146,7 +152,8 @@ AVSValue __cdecl Create_SmoothSkip(AVSValue args, void* user_data, IScriptEnviro
args[2].AsInt(4), // cycle
args[3].AsInt(1), // create
args[4].AsInt(0), // offset
args[5].AsBool(false), // debug
args[5].AsFloat(32), // offset
args[6].AsBool(false), // debug
env);
}

Expand Down Expand Up @@ -188,7 +195,7 @@ FrameMap SmoothSkip::getFrameMapping(IScriptEnvironment* env, int n) {

FrameMap map = cycle.frameMap[cycleOffset];
if (map.dstframe != n)
raiseError(env, "Frame counting is out of whack");
raiseError(env, "BUG! Frame counting is out of whack. Please report this to the author.");

return map;
}
Expand Down
10 changes: 5 additions & 5 deletions SmoothSkip.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@
#define VERSION "1.0.3"

class SmoothSkip : public GenericVideoFilter {
PClip altclip; // The super clip from MVTools2
bool debug; // debug arg
int offset; // frame offset used to get frame from the alternate clip.
PClip altclip; // The super clip from MVTools2
bool debug; // debug arg
int offset; // frame offset used to get frame from the alternate clip.

public:
CycleCache* cycles;
SmoothSkip(PClip _child, PClip _altclip, int cycleLen, int creates, int offset,
bool _debug, IScriptEnvironment* env);
double sceneThreshold, bool _debug, IScriptEnvironment* env);
~SmoothSkip();
PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment* env);
private:
Expand All @@ -46,7 +46,7 @@ class SmoothSkip : public GenericVideoFilter {
AVSValue __cdecl Create_SmoothSkip(AVSValue args, void* user_data, IScriptEnvironment* env);

extern "C" __declspec(dllexport) const char* __stdcall AvisynthPluginInit2(IScriptEnvironment* env) {
env->AddFunction("SmoothSkip", "cc[CYCLE]i[CREATE]i[OFFSET]i[DEBUG]b", Create_SmoothSkip, 0);
env->AddFunction("SmoothSkip", "cc[CYCLE]i[CREATE]i[OFFSET]f[SCENE]i[DEBUG]b", Create_SmoothSkip, 0);
return "'SmoothSkip' plugin v" VERSION ", author: tinjon[at]gmail.com";
}

Expand Down

0 comments on commit 7a210cd

Please sign in to comment.