PVideoFrame AvisynthVideoSource::GetFrame(int n, IScriptEnvironment *Env) { n = FFMIN(FFMAX(n,0), VI.num_frames - 1); char ErrorMsg[1024]; FFMS_ErrorInfo E; E.Buffer = ErrorMsg; E.BufferSize = sizeof(ErrorMsg); PVideoFrame Dst = Env->NewVideoFrame(VI); if (RFFMode > 0) { const FFMS_Frame *Frame = FFMS_GetFrame(V, FFMIN(FieldList[n].Top, FieldList[n].Bottom), &E); if (Frame == NULL) Env->ThrowError("FFVideoSource: %s", E.Buffer); if (FieldList[n].Top == FieldList[n].Bottom) { OutputFrame(Frame, Dst, Env); } else { int FirstField = FFMIN(FieldList[n].Top, FieldList[n].Bottom) == FieldList[n].Bottom; OutputField(Frame, Dst, FirstField, Env); Frame = FFMS_GetFrame(V, FFMAX(FieldList[n].Top, FieldList[n].Bottom), &E); if (Frame == NULL) Env->ThrowError("FFVideoSource: %s", E.Buffer); OutputField(Frame, Dst, !FirstField, Env); } } else { const FFMS_Frame *Frame; if (FPSNum > 0 && FPSDen > 0) { Frame = FFMS_GetFrameByTime(V, FFMS_GetVideoProperties(V)->FirstTime + (double)(n * (int64_t)FPSDen) / FPSNum, &E); } else { Frame = FFMS_GetFrame(V, n, &E); FFMS_Track *T = FFMS_GetTrackFromVideo(V); const FFMS_TrackTimeBase *TB = FFMS_GetTimeBase(T); Env->SetVar(Env->Sprintf("%s%s", this->VarPrefix, "FFVFR_TIME"), static_cast<int>(FFMS_GetFrameInfo(T, n)->PTS * static_cast<double>(TB->Num) / TB->Den)); } if (Frame == NULL) Env->ThrowError("FFVideoSource: %s", E.Buffer); Env->SetVar(Env->Sprintf("%s%s", this->VarPrefix, "FFPICT_TYPE"), static_cast<int>(Frame->PictType)); OutputFrame(Frame, Dst, Env); } return Dst; }
const VSFrameRef *VS_CC VSVideoSource::GetFrame(int n, int activationReason, void **instanceData, void **, VSFrameContext *frameCtx, VSCore *core, const VSAPI *vsapi) { VSVideoSource *vs = static_cast<VSVideoSource *>(*instanceData); if (activationReason == arInitial) { char ErrorMsg[1024]; FFMS_ErrorInfo E; E.Buffer = ErrorMsg; E.BufferSize = sizeof(ErrorMsg); std::string buf = "Source: "; int OutputIndex = vs->OutputAlpha ? vsapi->getOutputIndex(frameCtx) : 0; VSFrameRef *Dst = vsapi->newVideoFrame(vs->VI[OutputIndex].format, vs->VI[OutputIndex].width, vs->VI[OutputIndex].height, nullptr, core); VSMap *Props = vsapi->getFramePropsRW(Dst); const FFMS_Frame *Frame; if (vs->FPSNum > 0 && vs->FPSDen > 0) { double currentTime = FFMS_GetVideoProperties(vs->V)->FirstTime + (double)(n * (int64_t)vs->FPSDen) / vs->FPSNum; Frame = FFMS_GetFrameByTime(vs->V, currentTime, &E); vsapi->propSetInt(Props, "_DurationNum", vs->FPSDen, paReplace); vsapi->propSetInt(Props, "_DurationDen", vs->FPSNum, paReplace); vsapi->propSetFloat(Props, "_AbsoluteTime", currentTime, paReplace); } else { Frame = FFMS_GetFrame(vs->V, n, &E); FFMS_Track *T = FFMS_GetTrackFromVideo(vs->V); const FFMS_TrackTimeBase *TB = FFMS_GetTimeBase(T); int64_t num; if (n + 1 < vs->VI[0].numFrames) num = FFMS_GetFrameInfo(T, n + 1)->PTS - FFMS_GetFrameInfo(T, n)->PTS; else if (n > 0) // simply use the second to last frame's duration for the last one, should be good enough num = FFMS_GetFrameInfo(T, n)->PTS - FFMS_GetFrameInfo(T, n - 1)->PTS; else // just make it one timebase if it's a single frame clip num = 1; int64_t DurNum = TB->Num * num; int64_t DurDen = TB->Den * 1000; muldivRational(&DurNum, &DurDen, 1, 1); vsapi->propSetInt(Props, "_DurationNum", DurNum, paReplace); vsapi->propSetInt(Props, "_DurationDen", DurDen, paReplace); vsapi->propSetFloat(Props, "_AbsoluteTime", ((static_cast<double>(TB->Num) / 1000) * FFMS_GetFrameInfo(T, n)->PTS) / TB->Den, paReplace); } if (Frame == nullptr) { buf += E.Buffer; vsapi->setFilterError(buf.c_str(), frameCtx); return nullptr; } // Set AR variables if (vs->SARNum > 0 && vs->SARDen > 0) { vsapi->propSetInt(Props, "_SARNum", vs->SARNum, paReplace); vsapi->propSetInt(Props, "_SARDen", vs->SARDen, paReplace); } vsapi->propSetInt(Props, "_Matrix", Frame->ColorSpace, paReplace); vsapi->propSetInt(Props, "_Primaries", Frame->ColorPrimaries, paReplace); vsapi->propSetInt(Props, "_Transfer", Frame->TransferCharateristics, paReplace); if (Frame->ChromaLocation > 0) vsapi->propSetInt(Props, "_ChromaLocation", Frame->ChromaLocation - 1, paReplace); if (Frame->ColorRange == FFMS_CR_MPEG) vsapi->propSetInt(Props, "_ColorRange", 1, paReplace); else if (Frame->ColorRange == FFMS_CR_JPEG) vsapi->propSetInt(Props, "_ColorRange", 0, paReplace); vsapi->propSetData(Props, "_PictType", &Frame->PictType, 1, paReplace); // Set field information int FieldBased = 0; if (Frame->InterlacedFrame) FieldBased = (Frame->TopFieldFirst ? 2 : 1); vsapi->propSetInt(Props, "_FieldBased", FieldBased, paReplace); if (OutputIndex == 0) OutputFrame(Frame, Dst, vsapi); else OutputAlphaFrame(Frame, vs->VI[0].format->numPlanes, Dst, vsapi); return Dst; } return nullptr; }
/// @brief Opens video /// @param filename The filename to open void FFmpegSourceVideoProvider::LoadVideo(wxString filename) { wxString FileNameShort = wxFileName(filename).GetShortPath(); FFMS_Indexer *Indexer = FFMS_CreateIndexer(FileNameShort.utf8_str(), &ErrInfo); if (Indexer == NULL) { throw agi::FileNotFoundError(ErrInfo.Buffer); } std::map<int,wxString> TrackList = GetTracksOfType(Indexer, FFMS_TYPE_VIDEO); if (TrackList.size() <= 0) throw VideoNotSupported("no video tracks found"); // initialize the track number to an invalid value so we can detect later on // whether the user actually had to choose a track or not int TrackNumber = -1; if (TrackList.size() > 1) { TrackNumber = AskForTrackSelection(TrackList, FFMS_TYPE_VIDEO); // if it's still -1 here, user pressed cancel if (TrackNumber == -1) throw agi::UserCancelException("video loading cancelled by user"); } // generate a name for the cache file wxString CacheName = GetCacheFilename(filename); // try to read index FFMS_Index *Index = NULL; Index = FFMS_ReadIndex(CacheName.utf8_str(), &ErrInfo); bool IndexIsValid = false; if (Index != NULL) { if (FFMS_IndexBelongsToFile(Index, FileNameShort.utf8_str(), &ErrInfo)) { FFMS_DestroyIndex(Index); Index = NULL; } else IndexIsValid = true; } // time to examine the index and check if the track we want is indexed // technically this isn't really needed since all video tracks should always be indexed, // but a bit of sanity checking never hurt anyone if (IndexIsValid && TrackNumber >= 0) { FFMS_Track *TempTrackData = FFMS_GetTrackFromIndex(Index, TrackNumber); if (FFMS_GetNumFrames(TempTrackData) <= 0) { IndexIsValid = false; FFMS_DestroyIndex(Index); Index = NULL; } } // moment of truth if (!IndexIsValid) { int TrackMask = OPT_GET("Provider/FFmpegSource/Index All Tracks")->GetBool() ? FFMS_TRACKMASK_ALL : FFMS_TRACKMASK_NONE; try { // ignore audio decoding errors here, we don't care right now Index = DoIndexing(Indexer, CacheName, TrackMask, FFMS_IEH_IGNORE); } catch (wxString err) { throw VideoOpenError(STD_STR(err)); } } // update access time of index file so it won't get cleaned away wxFileName(CacheName).Touch(); // we have now read the index and may proceed with cleaning the index cache if (!CleanCache()) { //do something? } // track number still not set? if (TrackNumber < 0) { // just grab the first track TrackNumber = FFMS_GetFirstIndexedTrackOfType(Index, FFMS_TYPE_VIDEO, &ErrInfo); if (TrackNumber < 0) { FFMS_DestroyIndex(Index); Index = NULL; throw VideoNotSupported(std::string("Couldn't find any video tracks: ") + ErrInfo.Buffer); } } // set thread count int Threads = OPT_GET("Provider/Video/FFmpegSource/Decoding Threads")->GetInt(); // set seekmode // TODO: give this its own option? int SeekMode; if (OPT_GET("Provider/Video/FFmpegSource/Unsafe Seeking")->GetBool()) SeekMode = FFMS_SEEK_UNSAFE; else SeekMode = FFMS_SEEK_NORMAL; VideoSource = FFMS_CreateVideoSource(FileNameShort.utf8_str(), TrackNumber, Index, Threads, SeekMode, &ErrInfo); FFMS_DestroyIndex(Index); Index = NULL; if (VideoSource == NULL) { throw VideoOpenError(std::string("Failed to open video track: ") + ErrInfo.Buffer); } // load video properties VideoInfo = FFMS_GetVideoProperties(VideoSource); const FFMS_Frame *TempFrame = FFMS_GetFrame(VideoSource, 0, &ErrInfo); if (TempFrame == NULL) { throw VideoOpenError(std::string("Failed to decode first frame: ") + ErrInfo.Buffer); } Width = TempFrame->EncodedWidth; Height = TempFrame->EncodedHeight; if (FFMS_SetOutputFormatV(VideoSource, 1LL << FFMS_GetPixFmt("bgra"), Width, Height, FFMS_RESIZER_BICUBIC, &ErrInfo)) { throw VideoOpenError(std::string("Failed to set output format: ") + ErrInfo.Buffer); } // get frame info data FFMS_Track *FrameData = FFMS_GetTrackFromVideo(VideoSource); if (FrameData == NULL) throw VideoOpenError("failed to get frame data"); const FFMS_TrackTimeBase *TimeBase = FFMS_GetTimeBase(FrameData); if (TimeBase == NULL) throw VideoOpenError("failed to get track time base"); const FFMS_FrameInfo *CurFrameData; // build list of keyframes and timecodes std::vector<int> TimecodesVector; for (int CurFrameNum = 0; CurFrameNum < VideoInfo->NumFrames; CurFrameNum++) { CurFrameData = FFMS_GetFrameInfo(FrameData, CurFrameNum); if (CurFrameData == NULL) { throw VideoOpenError(STD_STR(wxString::Format(L"Couldn't get info about frame %d", CurFrameNum))); } // keyframe? if (CurFrameData->KeyFrame) KeyFramesList.push_back(CurFrameNum); // calculate timestamp and add to timecodes vector int Timestamp = (int)((CurFrameData->PTS * TimeBase->Num) / TimeBase->Den); TimecodesVector.push_back(Timestamp); } Timecodes = agi::vfr::Framerate(TimecodesVector); FrameNumber = 0; }
/// @brief Opens video /// @param filename The filename to open void FFmpegSourceVideoProvider::LoadVideo(wxString filename) { wxString FileNameShort = wxFileName(filename).GetShortPath(); FFMS_Indexer *Indexer = FFMS_CreateIndexer(FileNameShort.utf8_str(), &ErrInfo); if (!Indexer) throw agi::FileNotFoundError(ErrInfo.Buffer); std::map<int,wxString> TrackList = GetTracksOfType(Indexer, FFMS_TYPE_VIDEO); if (TrackList.size() <= 0) throw VideoNotSupported("no video tracks found"); // initialize the track number to an invalid value so we can detect later on // whether the user actually had to choose a track or not int TrackNumber = -1; if (TrackList.size() > 1) { TrackNumber = AskForTrackSelection(TrackList, FFMS_TYPE_VIDEO); // if it's still -1 here, user pressed cancel if (TrackNumber == -1) throw agi::UserCancelException("video loading cancelled by user"); } // generate a name for the cache file wxString CacheName = GetCacheFilename(filename); // try to read index agi::scoped_holder<FFMS_Index*, void (FFMS_CC*)(FFMS_Index*)> Index(FFMS_ReadIndex(CacheName.utf8_str(), &ErrInfo), FFMS_DestroyIndex); if (Index && FFMS_IndexBelongsToFile(Index, FileNameShort.utf8_str(), &ErrInfo)) Index = NULL; // time to examine the index and check if the track we want is indexed // technically this isn't really needed since all video tracks should always be indexed, // but a bit of sanity checking never hurt anyone if (Index && TrackNumber >= 0) { FFMS_Track *TempTrackData = FFMS_GetTrackFromIndex(Index, TrackNumber); if (FFMS_GetNumFrames(TempTrackData) <= 0) Index = NULL; } // moment of truth if (!Index) { int TrackMask = FFMS_TRACKMASK_NONE; if (OPT_GET("Provider/FFmpegSource/Index All Tracks")->GetBool() || OPT_GET("Video/Open Audio")->GetBool()) TrackMask = FFMS_TRACKMASK_ALL; Index = DoIndexing(Indexer, CacheName, TrackMask, GetErrorHandlingMode()); } else { FFMS_CancelIndexing(Indexer); } // update access time of index file so it won't get cleaned away wxFileName(CacheName).Touch(); // we have now read the index and may proceed with cleaning the index cache CleanCache(); // track number still not set? if (TrackNumber < 0) { // just grab the first track TrackNumber = FFMS_GetFirstIndexedTrackOfType(Index, FFMS_TYPE_VIDEO, &ErrInfo); if (TrackNumber < 0) throw VideoNotSupported(std::string("Couldn't find any video tracks: ") + ErrInfo.Buffer); } // set thread count int Threads = OPT_GET("Provider/Video/FFmpegSource/Decoding Threads")->GetInt(); if (FFMS_GetVersion() < ((2 << 24) | (17 << 16) | (2 << 8) | 1) && FFMS_GetSourceType(Index) == FFMS_SOURCE_LAVF) Threads = 1; // set seekmode // TODO: give this its own option? int SeekMode; if (OPT_GET("Provider/Video/FFmpegSource/Unsafe Seeking")->GetBool()) SeekMode = FFMS_SEEK_UNSAFE; else SeekMode = FFMS_SEEK_NORMAL; VideoSource = FFMS_CreateVideoSource(FileNameShort.utf8_str(), TrackNumber, Index, Threads, SeekMode, &ErrInfo); if (!VideoSource) throw VideoOpenError(std::string("Failed to open video track: ") + ErrInfo.Buffer); // load video properties VideoInfo = FFMS_GetVideoProperties(VideoSource); const FFMS_Frame *TempFrame = FFMS_GetFrame(VideoSource, 0, &ErrInfo); if (!TempFrame) throw VideoOpenError(std::string("Failed to decode first frame: ") + ErrInfo.Buffer); Width = TempFrame->EncodedWidth; Height = TempFrame->EncodedHeight; if (VideoInfo->SARDen > 0 && VideoInfo->SARNum > 0) DAR = double(Width) * VideoInfo->SARNum / ((double)Height * VideoInfo->SARDen); else DAR = double(Width) / Height; // Assuming TV for unspecified wxString ColorRange = TempFrame->ColorRange == FFMS_CR_JPEG ? "PC" : "TV"; int CS = TempFrame->ColorSpace; #if FFMS_VERSION >= ((2 << 24) | (17 << 16) | (1 << 8) | 0) if (CS != FFMS_CS_RGB && CS != FFMS_CS_BT470BG && OPT_GET("Video/Force BT.601")->GetBool()) { if (FFMS_SetInputFormatV(VideoSource, FFMS_CS_BT470BG, TempFrame->ColorRange, FFMS_GetPixFmt(""), &ErrInfo)) throw VideoOpenError(std::string("Failed to set input format: ") + ErrInfo.Buffer); CS = FFMS_CS_BT470BG; } #endif switch (CS) { case FFMS_CS_RGB: ColorSpace = "None"; break; case FFMS_CS_BT709: ColorSpace = wxString::Format("%s.709", ColorRange); break; case FFMS_CS_UNSPECIFIED: ColorSpace = wxString::Format("%s.%s", ColorRange, Width > 1024 || Height >= 600 ? "709" : "601"); break; case FFMS_CS_FCC: ColorSpace = wxString::Format("%s.FCC", ColorRange); break; case FFMS_CS_BT470BG: case FFMS_CS_SMPTE170M: ColorSpace = wxString::Format("%s.601", ColorRange); break; case FFMS_CS_SMPTE240M: ColorSpace = wxString::Format("%s.240M", ColorRange); break; default: throw VideoOpenError("Unknown video color space"); break; } const int TargetFormat[] = { FFMS_GetPixFmt("bgra"), -1 }; if (FFMS_SetOutputFormatV2(VideoSource, TargetFormat, Width, Height, FFMS_RESIZER_BICUBIC, &ErrInfo)) { throw VideoOpenError(std::string("Failed to set output format: ") + ErrInfo.Buffer); } // get frame info data FFMS_Track *FrameData = FFMS_GetTrackFromVideo(VideoSource); if (FrameData == NULL) throw VideoOpenError("failed to get frame data"); const FFMS_TrackTimeBase *TimeBase = FFMS_GetTimeBase(FrameData); if (TimeBase == NULL) throw VideoOpenError("failed to get track time base"); const FFMS_FrameInfo *CurFrameData; // build list of keyframes and timecodes std::vector<int> TimecodesVector; for (int CurFrameNum = 0; CurFrameNum < VideoInfo->NumFrames; CurFrameNum++) { CurFrameData = FFMS_GetFrameInfo(FrameData, CurFrameNum); if (CurFrameData == NULL) { throw VideoOpenError(STD_STR(wxString::Format("Couldn't get info about frame %d", CurFrameNum))); } // keyframe? if (CurFrameData->KeyFrame) KeyFramesList.push_back(CurFrameNum); // calculate timestamp and add to timecodes vector int Timestamp = (int)((CurFrameData->PTS * TimeBase->Num) / TimeBase->Den); TimecodesVector.push_back(Timestamp); } if (TimecodesVector.size() < 2) Timecodes = 25.0; else Timecodes = agi::vfr::Framerate(TimecodesVector); FrameNumber = 0; }