// MDC 0.1 - (Motion Dependent Clip) // Copyright 2004 Carl Ritson // 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., 675 Mass Ave, Cambridge, MA 02139, USA, or visit // http://www.gnu.org/copyleft/gpl.html . #include #include #include #include "avisynth.h" #include "info.h" #include "scene-change.h" #define MAX_CLIPS 3 #define MAX_SEARCH_FRAMES 20 enum { DF_MASK = 0xff00, DF_CMASK = 0x00ff, DF_INIT = 0x0000, DF_DIFF_SET = 0x0100, DF_CLIP_SET = 0x0200, DF_IS_SC = 0x0400, DF_SC_SET = 0x0800 }; // Structures struct mclip { PClip clip; float t_lower; float t_upper; }; struct mdecision { int flags; float diff; float true_diff; float avg_diff; int map_to; }; class MDC : public GenericVideoFilter { public: MDC(PClip _child, PClip c1, float t1_lower, float t1_upper, PClip c2, float t2_lower, float t2_upper, PClip c3, float t3_lower, float t3_upper, int _defclip, int _pre_frames, int _post_frames, bool _sc_combined_search, float _sc_threshold, bool _scf_fix, float _scf_threshold, int _sc_algorithm, int _sc_search, bool _debug, IScriptEnvironment *env); PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment *env); private: void CalculateDiff(int n, IScriptEnvironment *env); void SetDiff(int n, float diff, float true_diff); void SetDiff(int n, float diff); float GetDiff(int n, IScriptEnvironment *env); float GetTrueDiff(int n, IScriptEnvironment *env); bool IsSC(int n, IScriptEnvironment *env); PVideoFrame OutputDebugInfo(PVideoFrame &frame, int n, IScriptEnvironment *env); void MakeDecision(int n, IScriptEnvironment *env); VideoInfo original_vi; mclip clips[3]; mdecision *decision; int defclip, pre_frames, post_frames; int sc_algorithm, sc_search; bool debug, sc_combined_search, scf_fix; float sc_threshold, scf_threshold; }; // Methods MDC::MDC(PClip _child, PClip c1, float t1_lower, float t1_upper, PClip c2, float t2_lower, float t2_upper, PClip c3, float t3_lower, float t3_upper, int _defclip, int _pre_frames, int _post_frames, bool _sc_combined_search, float _sc_threshold, bool _scf_fix, float _scf_threshold, int _sc_algorithm, int _sc_search, bool _debug, IScriptEnvironment *env) : GenericVideoFilter(_child), defclip(_defclip), pre_frames(_pre_frames), post_frames(_post_frames), sc_combined_search(_sc_combined_search), sc_threshold(_sc_threshold), scf_fix(_scf_fix), scf_threshold(_scf_threshold), sc_algorithm(_sc_algorithm), sc_search(_sc_search), debug(_debug) { VideoInfo target_vi; int i; for(i = 0; i < MAX_CLIPS; ++i) clips[i].clip = 0; // there must be at least one clip clips[0].clip = c1; clips[0].t_lower = t1_lower; clips[0].t_upper = t1_upper; if(c2 != 0) { clips[1].clip = c2; clips[1].t_lower = t2_lower; clips[1].t_upper = t2_upper; } if(c3 != 0) { clips[2].clip = c3; clips[2].t_lower = t3_lower; clips[2].t_upper = t3_upper; } target_vi = c1->GetVideoInfo(); for(i = 0; i < MAX_CLIPS; ++i) { VideoInfo cvi = clips[i].clip->GetVideoInfo(); if(cvi.height != target_vi.height) env->ThrowError("MDC: All output sources must have the same height!"); if(cvi.width != target_vi.width) env->ThrowError("MDC: All output sources must have the same width!"); if(!cvi.IsSameColorspace(target_vi)) env->ThrowError("MDC: All output sources must have the same colourspace!"); if(cvi.num_frames != target_vi.num_frames) env->ThrowError("MDC: All output sources must have the same number of frames!"); } defclip -= 1; if(defclip < 0 || defclip >= MAX_CLIPS) env->ThrowError("MDC: Invalid default clip!"); if(clips[defclip].clip == 0) env->ThrowError("MDC: Invalid default clip!"); if(vi.num_frames != target_vi.num_frames) env->ThrowError("MDC: determination source must have the same number of frames other clips!"); if(!vi.IsYV12()) env->ThrowError("MDC: requires YV12 determination source"); original_vi = vi; vi.height = target_vi.height; vi.width = target_vi.width; vi.pixel_type = target_vi.pixel_type; vi.image_type = target_vi.image_type; if(pre_frames < 0 || post_frames < 0) env->ThrowError("MDC: pre_frames and post_frames must be >= 0"); if((pre_frames + post_frames) > MAX_SEARCH_FRAMES) env->ThrowError("MDC: pre_frames + post_frames must be <= 20"); if(sc_search > MAX_SEARCH_FRAMES) env->ThrowError("MDC: sc_search, must be <= 20"); if(sc_algorithm < 0 || sc_algorithm > 2) env->ThrowError("MDC: sc_algorithm must be >= 0 and <= 2"); decision = new mdecision[vi.num_frames]; for(i = 0; i < vi.num_frames; ++i) { decision[i].flags = DF_INIT; decision[i].diff = 0.0; decision[i].true_diff = 0.0; decision[i].avg_diff = 0.0; decision[i].map_to = i; } } void MDC::CalculateDiff(int n, IScriptEnvironment *env) { if(n == 0) { SetDiff(n,0.0,0.0); return; } bool ISSE = !!(env->GetCPUFlags() & CPUF_INTEGER_SSE); PVideoFrame src1 = child->GetFrame(n-1,env); PVideoFrame src2 = child->GetFrame(n,env); float diff = 0.0; int plane = PLANAR_Y; for(;;) { const BYTE* srcp1 = src1->GetReadPtr(plane); const BYTE* srcp2 = src2->GetReadPtr(plane); int h = src1->GetHeight(plane); int w = src1->GetRowSize(plane); int pitch1 = src1->GetPitch(plane); int pitch2 = src2->GetPitch(plane); int b; w=(w/16)*16; if(ISSE) b = isse_scenechange_16(srcp1, srcp2, h, w, pitch1, pitch2); else b = C_scenechange_16(srcp1, srcp2, h, w, pitch1, pitch2); if(plane == PLANAR_Y) diff += (float)(b != 0 ? b : 1) / (float)(h * w) * 0.5f; else diff += (float)(b != 0 ? b : 1) / (float)(h * w) * 0.25f; if(plane == PLANAR_Y) plane = PLANAR_U; else if(plane == PLANAR_U) plane = PLANAR_V; else break; // leave loop } SetDiff(n,diff,diff); } void MDC::SetDiff(int n, float diff, float true_diff) { if(n < 0 || n >= vi.num_frames) return; decision[n].flags |= DF_DIFF_SET; decision[n].diff = diff; decision[n].true_diff = diff; } void MDC::SetDiff(int n, float diff) { if(n < 0 || n >= vi.num_frames) return; decision[n].flags |= DF_DIFF_SET; decision[n].diff = diff; } float MDC::GetDiff(int n, IScriptEnvironment *env) { if(n < 0 || n >= vi.num_frames) return 0.0; if(!(decision[n].flags & DF_DIFF_SET)) CalculateDiff(n,env); return decision[n].diff; } float MDC::GetTrueDiff(int n, IScriptEnvironment *env) { if(n < 0 || n >= vi.num_frames) return 0.0; if(!(decision[n].flags & DF_DIFF_SET)) CalculateDiff(n,env); return decision[n].true_diff; } bool MDC::IsSC(int n, IScriptEnvironment *env) { if(n == 0) decision[n].flags |= DF_IS_SC | DF_SC_SET; if(n < 0 || n >= vi.num_frames) return false; if(!(decision[n].flags & DF_SC_SET)) { if(sc_algorithm == 0) { if(GetTrueDiff(n,env) > sc_threshold) decision[n].flags |= DF_IS_SC; } else if(sc_algorithm == 1 || sc_algorithm == 2) { double no_elem = (sc_search/2) + (sc_algorithm==1?1:0); double sum, diff = GetTrueDiff(n,env); int i; for(i = 1, sum = 0.0; i < sc_search/2; ++i) { sum += GetTrueDiff(n-i,env); sum += GetTrueDiff(n+i,env); } if(sc_algorithm == 1) sum += diff; double mean = sum / no_elem; for(i = 1, sum = 0.0; i < sc_search/2; ++i) { float diff1 = GetTrueDiff(n-i,env); float diff2 = GetTrueDiff(n+i,env); sum += (diff1 - mean) * (diff1 - mean); sum += (diff2 - mean) * (diff2 - mean); } if(sc_algorithm == 1) sum += (diff - mean) * (diff - mean); double sd = sqrt(sum / no_elem); if(diff > (mean + (sd * (double)sc_threshold))) decision[n].flags |= DF_IS_SC; } decision[n].flags |= DF_SC_SET; } return (decision[n].flags & DF_IS_SC) == DF_IS_SC; } PVideoFrame MDC::OutputDebugInfo(PVideoFrame &frame, int n, IScriptEnvironment *env) { char buffer[80]; env->MakeWritable(&frame); _snprintf(buffer,sizeof(buffer), "% 5d, diff: %.2f, true_diff: %.2f, avg_diff: %.2f", n, decision[n].diff, decision[n].true_diff, decision[n].avg_diff); DrawString(frame,0,0,buffer); _snprintf(buffer,sizeof(buffer), "% 5d, clip: %d, sc: %s,%s", decision[n].map_to, (decision[n].flags & DF_CMASK)+1, (decision[n].flags & DF_SC_SET) ? "true" : "false", (decision[n].flags & DF_IS_SC) ? "true" : "false"); DrawString(frame,0,1,buffer); return frame; } void MDC::MakeDecision(int n, IScriptEnvironment *env) { float diff_sum = 0.0; int backward = pre_frames, forward = post_frames; int b_searched = 0, f_searched = 0; int i, j, frames = 0; if(IsSC(n,env)) { SetDiff(n,0.0); if(scf_fix) { if(GetDiff(n+1,env) < scf_threshold && (n+1) < vi.num_frames) { decision[n].map_to = n+1; SetDiff(n+1,0.0); } } backward = 0; } diff_sum += GetDiff(n,env); frames++; for(j = 0; j < 2; ++j) { for(i = b_searched+1; i < backward; ++i) { if((n-i) < 0 || IsSC(n-i,env)) { b_searched++; break; } diff_sum += GetDiff(n-i,env); frames++; } for(i = f_searched+1; i < forward; ++i) { if((n+i) >= vi.num_frames || IsSC(n+i,env)) { f_searched++; break; } diff_sum += GetDiff(n+i,env); frames++; } if(!sc_combined_search) break; if(f_searched == post_frames && b_searched == pre_frames) break; backward += (f_searched - post_frames); forward += (b_searched - pre_frames); } float avg_diff = diff_sum / ((float)frames); int clip = -1; for(i = 0; i < MAX_CLIPS && clip == -1; ++i) { if(clips[i].clip != 0 && avg_diff >= clips[i].t_lower && avg_diff < clips[i].t_upper) clip = i; } if(clip == -1) clip = defclip; decision[n].flags = ((decision[n].flags | DF_CLIP_SET) & DF_MASK) | (clip & DF_CMASK); decision[n].avg_diff = avg_diff; } PVideoFrame __stdcall MDC::GetFrame(int n, IScriptEnvironment *env) { if(!(decision[n].flags & DF_CLIP_SET)) MakeDecision(n,env); PVideoFrame frame = clips[decision[n].flags & DF_CMASK].clip->GetFrame(decision[n].map_to,env); if(debug) frame = OutputDebugInfo(frame,n,env); return frame; } // Factory Methods AVSValue __cdecl Create_MDC3(AVSValue args, void*, IScriptEnvironment *env) { return new MDC( args[0].AsClip(), args[1].AsClip(),args[2].AsFloat(0.0),args[3].AsFloat(0.0), args[4].AsClip(),args[5].AsFloat(0.0),args[6].AsFloat(0.0), args[7].AsClip(),args[8].AsFloat(0.0),args[9].AsFloat(0.0), args[10].AsInt(1), args[11].AsInt(2),args[12].AsInt(2), args[13].AsBool(true), args[14].AsFloat(args[17].AsInt(1) == 0 ? 20.0 : 1.5), args[15].AsBool(true),args[16].AsFloat(4.0), args[17].AsInt(1),args[18].AsInt(10), args[19].AsBool(false), env ); } // Entry Point extern "C" __declspec(dllexport) const char* __stdcall AvisynthPluginInit2(IScriptEnvironment* env) { env->AddFunction("MDC", "c" "[c1]c[t1_lower]f[t1_upper]f" "[c2]c[t2_lower]f[t2_upper]f" "[c3]c[t3_lower]f[t3_upper]f" "[default]i" "[pre_frames]i[post_frames]i" "[sc_combined_search]b[sc_threshold]f" "[scf_fix]b[scf_threshold]f" "[sc_algorithm]i[sc_search]i" "[debug]b", Create_MDC3, 0); return "Motion Dependent Clip Filter"; }