text: //  Copyright (c) 2012 Fredrik Mellbin
  //
  //  Permission is hereby granted, free of charge, to any person obtaining a copy
  //  of this software and associated documentation files (the "Software"), to deal
  //  in the Software without restriction, including without limitation the rights
  //  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  //  copies of the Software, and to permit persons to whom the Software is
  //  furnished to do so, subject to the following conditions:
  //
  //  The above copyright notice and this permission notice shall be included in
  //  all copies or substantial portions of the Software.
  //
  //  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  //  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  //  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  //  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  //  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  //  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  //  THE SOFTWARE.
  
  #include "ffms.h"
  #include "vapoursource.h"
  
  #include <cstdio>
  #include <cstring>
  #include <string>
  
  // assume windows is the only OS with a case insensitive filesystem
  #ifndef _WIN32
  #define _stricmp strcmp
  #endif
  
  static void VS_CC CreateIndex(const VSMap *in, VSMap *out, void *, VSCore *, const VSAPI *vsapi)  {
  	FFMS_Init(0,  1);
  
  	char ErrorMsg[1024];
  	FFMS_ErrorInfo E;
  	E.Buffer = ErrorMsg;
  	E.BufferSize = sizeof(ErrorMsg);
  	int err;
  
  	const char *Source = vsapi->propGetData(in, "source", 0, 0);
  	const char *CacheFile = vsapi->propGetData(in, "cachefile", 0, &err);
  	int IndexMask = (int)vsapi->propGetInt(in, "indexmask", 0, &err);
  	if (err)
  		IndexMask = -1;
  	int DumpMask = (int)vsapi->propGetInt(in, "dumpmask", 0, &err);
  	const char *AudioFile = vsapi->propGetData(in, "audiofile", 0, &err);
  	if (err)
  		AudioFile = "%sourcefile%.%trackzn%.w64";
  	int ErrorHandling = (int)vsapi->propGetInt(in, "errorhandling", 0, &err);
  	if (err)
  		ErrorHandling = FFMS_IEH_IGNORE;
  	bool OverWrite = !!vsapi->propGetInt(in, "overwrite", 0, &err);
  	const char *DemuxerStr = vsapi->propGetData(in, "demuxer", 0, &err);
  
  	std::string DefaultCache(Source);
  	if (!CacheFile || !strcmp(CacheFile, "")) {
  		DefaultCache.append(".ffindex");
  		CacheFile = DefaultCache.c_str();
  	}
  
  	if (!AudioFile || !strcmp(AudioFile, ""))
  		return vsapi->setError(out, "Index: Specifying an empty audio filename is not allowed");
  
  	int Demuxer = FFMS_SOURCE_DEFAULT;
  	if (DemuxerStr) {
  		if (!strcmp(DemuxerStr, "lavf"))
  			Demuxer = FFMS_SOURCE_LAVF;
  		else if (!strcmp(DemuxerStr, "matroska"))
  			Demuxer = FFMS_SOURCE_MATROSKA;
  		else if (!strcmp(DemuxerStr, "haalimpeg"))
  			Demuxer = FFMS_SOURCE_HAALIMPEG;
  		else if (!strcmp(DemuxerStr, "haaliogg"))
  			Demuxer = FFMS_SOURCE_HAALIOGG;
  		else
  			return vsapi->setError(out, "Index: Invalid demuxer requested");
  	}
  
  	FFMS_Index *Index = FFMS_ReadIndex(CacheFile, &E);
  	if (OverWrite || !Index || (Index && FFMS_IndexBelongsToFile(Index, Source, 0) != FFMS_ERROR_SUCCESS)) {
  		FFMS_Indexer *Indexer = FFMS_CreateIndexerWithDemuxer(Source, Demuxer, &E);
  		if (!Indexer) {
  			FFMS_DestroyIndex(Index);
  			return vsapi->setError(out, (std::string("Index: ") + E.Buffer).c_str());
  		}
  		if (!(Index = FFMS_DoIndexing(Indexer, IndexMask, DumpMask, FFMS_DefaultAudioFilename, (void *)AudioFile, ErrorHandling, NULL, NULL, &E)))
  			return vsapi->setError(out, (std::string("Index: ") + E.Buffer).c_str());
  		if (FFMS_WriteIndex(CacheFile, Index, &E)) {
  			FFMS_DestroyIndex(Index);
  			return vsapi->setError(out, (std::string("Index: ") + E.Buffer).c_str());
  		}
  		FFMS_DestroyIndex(Index);
  		if (!OverWrite)
  			vsapi->propSetData(out, "result", "Index generated", 0, 0);
  		else
  			vsapi->propSetData(out, "result", "Index generated (forced overwrite)", 0, 0);
  	} else {
  		FFMS_DestroyIndex(Index);
  		vsapi->propSetData(out, "result", "Valid index already exists", 0, 0);
  	}
  }
  
  static void VS_CC CreateSource(const VSMap *in, VSMap *out, void *, VSCore *core, const VSAPI *vsapi)  {
  	FFMS_Init(0,  1);
  
  	char ErrorMsg[1024];
  	FFMS_ErrorInfo E;
  	E.Buffer = ErrorMsg;
  	E.BufferSize = sizeof(ErrorMsg);
  	int err;
  
  	const char *Source = vsapi->propGetData(in, "source", 0, 0);
  	int Track = (int)vsapi->propGetInt(in, "track", 0, &err);
  	if (err)
  		Track = -1;
  	bool Cache = !!vsapi->propGetInt(in, "cache", 0, &err);
  	if (err)
  		Cache = true;
  	const char *CacheFile = vsapi->propGetData(in, "cachefile", 0, &err);
  	int FPSNum = (int)vsapi->propGetInt(in, "fpsnum", 0, &err);
  	if (err)
  		FPSNum = -1;
  	int FPSDen = (int)vsapi->propGetInt(in, "fpsden", 0, &err);
  	if (err)
  		FPSDen = 1;
  	int Threads = (int)vsapi->propGetInt(in, "threads", 0, &err);
  	const char *Timecodes = vsapi->propGetData(in, "timecodes", 0, &err);
  	int SeekMode = (int)vsapi->propGetInt(in, "seekmode", 0, &err);
  	if (err)
  		SeekMode = FFMS_SEEK_NORMAL;
  	int RFFMode = (int)vsapi->propGetInt(in, "rffmode", 0, &err);
  	int Width = (int)vsapi->propGetInt(in, "width", 0, &err);
  	int Height = (int)vsapi->propGetInt(in, "height", 0, &err);
  	const char *Resizer = vsapi->propGetData(in, "resizer", 0, &err);
  	if (err)
  		Resizer = "BICUBIC";
  	int Format = (int)vsapi->propGetInt(in, "format", 0, &err);
  
  	if (FPSDen < 1)
  		return vsapi->setError(out, "Source: FPS denominator needs to be 1 or higher");
  	if (Track <= -2)
  		return vsapi->setError(out, "Source: No video track selected");
  	if (SeekMode < -1 || SeekMode > 3)
  		return vsapi->setError(out, "Source: Invalid seekmode selected");
  	if (RFFMode < 0 || RFFMode > 2)
  		return vsapi->setError(out, "Source: Invalid RFF mode selected");
  	if (RFFMode > 0 && FPSNum > 0)
  		return vsapi->setError(out, "Source: RFF modes may not be combined with CFR conversion");
  	if (Timecodes && !_stricmp(Source, Timecodes))
  		return vsapi->setError(out, "Source: Timecodes will overwrite the source");
  
  	FFMS_Index *Index = NULL;
  	std::string DefaultCache;
  	if (Cache) {
  		if (CacheFile && *CacheFile) {
  			if (!_stricmp(Source, CacheFile))
  				return vsapi->setError(out, "Source: Cache will overwrite the source");
  			Index = FFMS_ReadIndex(CacheFile, &E);
  		} else {
  			DefaultCache = Source;
  			DefaultCache += ".ffindex";
  			CacheFile = DefaultCache.c_str();
  			Index = FFMS_ReadIndex(CacheFile, &E);
  			// Reindex if the index doesn't match the file and its name wasn't
  			// explicitly given
  			if (Index && FFMS_IndexBelongsToFile(Index, Source, 0) != FFMS_ERROR_SUCCESS) {
  				FFMS_DestroyIndex(Index);
  				Index = 0;
  			}
  		}
  	}
  
  	if (!Index) {
  		if (!(Index = FFMS_MakeIndex(Source, 0, 0, NULL, NULL, true, NULL, NULL, &E)))
  			return vsapi->setError(out, (std::string("Index: ") + E.Buffer).c_str());
  
  		if (Cache)
  			if (FFMS_WriteIndex(CacheFile, Index, &E)) {
  				FFMS_DestroyIndex(Index);
  				return vsapi->setError(out, (std::string("Index: ") + E.Buffer).c_str());
  			}
  	}
  
  	if (Track == -1)
  		Track = FFMS_GetFirstIndexedTrackOfType(Index, FFMS_TYPE_VIDEO, &E);
  	if (Track < 0) {
  		FFMS_DestroyIndex(Index);
  		return vsapi->setError(out, "Source: No video track found");
  	}
  
  	if (Timecodes && strcmp(Timecodes, "")) {
  		if (FFMS_WriteTimecodes(FFMS_GetTrackFromIndex(Index, Track), Timecodes, &E)) {
  			FFMS_DestroyIndex(Index);
  			return vsapi->setError(out, (std::string("Index: ") + E.Buffer).c_str());
  		}
  	}
  
  	VSVideoSource *vs;
  	try {
  		vs = new VSVideoSource(Source, Track, Index, FPSNum, FPSDen, Threads, SeekMode, RFFMode, Width, Height, Resizer, Format, vsapi, core);
  	} catch (std::exception const& e) {
  		FFMS_DestroyIndex(Index);
  		return vsapi->setError(out, e.what());
  	}
  
  	vsapi->createFilter(in, out, "Source", VSVideoSource::Init, VSVideoSource::GetFrame, VSVideoSource::Free, fmSerial, 0,vs, core);
  
  	FFMS_DestroyIndex(Index);
  }
  
  static void VS_CC GetLogLevel(const VSMap *, VSMap *out, void *, VSCore *, const VSAPI *vsapi) {
  	vsapi->propSetInt(out, "level", FFMS_GetLogLevel(), 0);
  }
  
  static void VS_CC SetLogLevel(const VSMap *in, VSMap *out, void *, VSCore *, const VSAPI *vsapi) {
  	FFMS_SetLogLevel((int)vsapi->propGetInt(in, "level", 0, 0));
  	vsapi->propSetInt(out, "level", FFMS_GetLogLevel(), 0);
  }
  
  static void VS_CC GetVersion(const VSMap *, VSMap *out, void *, VSCore *, const VSAPI *vsapi) {
  	int Version = FFMS_GetVersion();
  	char buf[100];
  	sprintf(buf, "%d.%d.%d.%d", Version >> 24, (Version & 0xFF0000) >> 16, (Version & 0xFF00) >> 8, Version & 0xFF);
  	vsapi->propSetData(out, "version", buf, 0, 0);
  }
  
  VS_EXTERNAL_API(void) VapourSynthPluginInit(VSConfigPlugin configFunc, VSRegisterFunction registerFunc, VSPlugin *plugin) {
  	configFunc("com.vapoursynth.ffms2", "ffms2", "FFmpegSource 2 for VapourSynth", VAPOURSYNTH_API_VERSION, 1, plugin);
  	registerFunc("Index", "source:data;cachefile:data:opt;indexmask:int:opt;dumpmask:int:opt;audiofile:data:opt;errorhandling:int:opt;overwrite:int:opt;demuxer:data:opt;", CreateIndex, NULL, plugin);
  	registerFunc("Source", "source:data;track:int:opt;cache:int:opt;cachefile:data:opt;fpsnum:int:opt;fpsden:int:opt;threads:int:opt;timecodes:data:opt;seekmode:int:opt;width:int:opt;height:int:opt;resizer:data:opt;format:int:opt;", CreateSource, NULL, plugin);
  	registerFunc("GetLogLevel", "", GetLogLevel, NULL, plugin);
  	registerFunc("SetLogLevel", "level:int;", SetLogLevel, NULL, plugin);
  	registerFunc("Version", "", GetVersion, NULL, plugin);
  }
  