diff --git a/core/mkv_wrap.cpp b/core/mkv_wrap.cpp index 048d6a9dc..5e7da5e68 100644 --- a/core/mkv_wrap.cpp +++ b/core/mkv_wrap.cpp @@ -41,6 +41,11 @@ #include "dialog_progress.h" +//////////// +// Instance +MatroskaWrapper MatroskaWrapper::wrapper; + + /////////// // Defines #define CACHESIZE 65536 @@ -120,6 +125,7 @@ bool operator < (MkvFrame &t1, MkvFrame &t2) { void MatroskaWrapper::Parse() { // Clear keyframes and timecodes keyFrames.Clear(); + bytePos.Clear(); timecodes.clear(); std::list frames; @@ -157,7 +163,7 @@ void MatroskaWrapper::Parse() { while (mkv_ReadFrame(file,0,&rt,&startTime,&endTime,&filePos,&frameSize,&frameFlags) == 0) { // Read value double curTime = double(startTime) / 1000000.0; - frames.push_back(MkvFrame((frameFlags & FRAME_KF) != 0,curTime)); + frames.push_back(MkvFrame((frameFlags & FRAME_KF) != 0,curTime,filePos)); frameN++; // Cancelled? @@ -179,11 +185,12 @@ void MatroskaWrapper::Parse() { // Process timecodes and keyframes frames.sort(); - MkvFrame curFrame(false,0); + MkvFrame curFrame(false,0,0); int i = 0; for (std::list::iterator cur=frames.begin();cur!=frames.end();cur++) { curFrame = *cur; if (curFrame.isKey) keyFrames.Add(i); + bytePos.Add(curFrame.filePos); timecodes.push_back(curFrame.time); i++; } diff --git a/core/mkv_wrap.h b/core/mkv_wrap.h index 1033ab0a4..f2350058a 100644 --- a/core/mkv_wrap.h +++ b/core/mkv_wrap.h @@ -62,10 +62,12 @@ class MkvFrame { public: double time; bool isKey; + __int64 filePos; - MkvFrame(bool keyframe,double timecode) { + MkvFrame(bool keyframe,double timecode,__int64 _filePos) { isKey = keyframe; time = timecode; + filePos = _filePos; } }; @@ -80,6 +82,7 @@ private: MkvStdIO *input; wxArrayInt keyFrames; std::vector timecodes; + wxArrayInt bytePos; void Parse(); @@ -87,8 +90,14 @@ public: MatroskaWrapper(); ~MatroskaWrapper(); + bool IsOpen() { return file != NULL; } void Open(wxString filename); void Close(); + void SetToTimecodes(FrameRate &target); + wxArrayInt GetBytePositions() { return bytePos; } + unsigned int GetFrameCount() { return timecodes.size(); } wxArrayInt GetKeyFrames(); + + static MatroskaWrapper wrapper; }; diff --git a/core/video_display.cpp b/core/video_display.cpp index 82095a135..f5d88f649 100644 --- a/core/video_display.cpp +++ b/core/video_display.cpp @@ -140,53 +140,47 @@ void VideoDisplay::UpdateSize() { // Sets video filename void VideoDisplay::SetVideo(const wxString &filename) { // Unload video - if (filename.IsEmpty()) { - delete provider; - provider = NULL; - if (VFR_Output.GetFrameRateType() == VFR) VFR_Output.Unload(); - VFR_Input.Unload(); - - videoName = _T(""); - - frame_n = 0; - - Reset(); - } + delete provider; + provider = NULL; + if (VFR_Output.GetFrameRateType() == VFR) VFR_Output.Unload(); + VFR_Input.Unload(); + videoName = _T(""); + loaded = false; + frame_n = 0; + Reset(); // Load video - else { - SetVideo(_T("")); - + if (!filename.IsEmpty()) { try { grid->CommitChanges(true); // Choose a provider - bool usedDirectshow = false; provider = VideoProvider::GetProvider(filename,GetTempWorkFile()); provider->SetZoom(zoomValue); provider->SetDAR(GetARFromType(arType)); - - // Set keyframes + + // Read extra data from file + bool mkvOpen = MatroskaWrapper::wrapper.IsOpen(); wxString ext = filename.Right(4).Lower(); - if (ext == _T(".avi")) KeyFrames = VFWWrapper::GetKeyFrames(filename); - else if (ext == _T(".mkv")) { + KeyFrames.Clear(); + if (ext == _T(".mkv") || mkvOpen) { // Parse mkv - MatroskaWrapper mkvwrap; - mkvwrap.Open(filename); + if (!mkvOpen) MatroskaWrapper::wrapper.Open(filename); // Get keyframes - KeyFrames = mkvwrap.GetKeyFrames(); + KeyFrames = MatroskaWrapper::wrapper.GetKeyFrames(); // Ask to override timecodes int override = wxYES; if (VFR_Output.GetFrameRateType() == VFR) override = wxMessageBox(_T("You already have timecodes loaded. Replace them with the timecodes from the Matroska file?"),_T("Replace timecodes?"),wxYES_NO | wxICON_QUESTION); - if (override == wxYES) mkvwrap.SetToTimecodes(VFR_Output); + if (override == wxYES) MatroskaWrapper::wrapper.SetToTimecodes(VFR_Output); // Close mkv - mkvwrap.Close(); + MatroskaWrapper::wrapper.Close(); } - else KeyFrames.Clear(); + else if (ext == _T(".avi")) KeyFrames = VFWWrapper::GetKeyFrames(filename); + // Update size UpdateSize(); //Gather video parameters @@ -214,7 +208,6 @@ void VideoDisplay::SetVideo(const wxString &filename) { } loaded = provider != NULL; - } ////////// diff --git a/core/video_provider.cpp b/core/video_provider.cpp index fc87d536c..6dc7cff2d 100644 --- a/core/video_provider.cpp +++ b/core/video_provider.cpp @@ -64,31 +64,43 @@ VideoProvider *VideoProvider::GetProvider(wxString video,wxString subtitles) { // See if it's OK to use LAVC #ifdef USE_LAVC if (preffered == _T("ffmpeg")) { + // Load + bool success = false; + wxString error; try { provider = new LAVCVideoProvider(video,subtitles); + success = true; + } + + // Catch error + catch (wchar_t *err) { + error = err; } catch (...) { + error = _T("Unhandled exception."); + } + + if (!success) { // Delete old provider delete provider; // Try to fallback to avisynth if (avisynthAvailable) { - wxMessageBox(_T("Failed loading FFmpeg decoder for video, falling back to Avisynth."),_T("FFmpeg error.")); + wxMessageBox(_T("Failed loading FFmpeg decoder for video, falling back to Avisynth.\nError message: ") + error,_T("FFmpeg error.")); provider = NULL; } // Out of options, rethrow - else throw; + else throw error.c_str(); } } #endif // Use avisynth provider #ifdef __WINDOWS__ - bool usedDirectshow = false; if (!provider) { try { - provider = new AvisynthVideoProvider(video,subtitles,usedDirectshow); + provider = new AvisynthVideoProvider(video,subtitles); } catch (...) { delete provider; diff --git a/core/video_provider_avs.cpp b/core/video_provider_avs.cpp index b3ab75c09..f88e1cdc7 100644 --- a/core/video_provider_avs.cpp +++ b/core/video_provider_avs.cpp @@ -43,7 +43,7 @@ #ifdef __WINDOWS__ -AvisynthVideoProvider::AvisynthVideoProvider(wxString _filename, wxString _subfilename, bool &usedDirectshow) { +AvisynthVideoProvider::AvisynthVideoProvider(wxString _filename, wxString _subfilename) { bool mpeg2dec3_priority = true; RGB32Video = NULL; SubtitledVideo = NULL; @@ -57,7 +57,7 @@ AvisynthVideoProvider::AvisynthVideoProvider(wxString _filename, wxString _subfi LoadVSFilter(); - RGB32Video = OpenVideo(_filename,usedDirectshow,mpeg2dec3_priority); + RGB32Video = OpenVideo(_filename,mpeg2dec3_priority); dar = GetSourceWidth()/(double)GetSourceHeight(); @@ -110,11 +110,11 @@ void AvisynthVideoProvider::SetZoom(double _zoom) { GetFrame(last_fnum,true); } -PClip AvisynthVideoProvider::OpenVideo(wxString _filename, bool &usedDirectshow, bool mpeg2dec3_priority) { +PClip AvisynthVideoProvider::OpenVideo(wxString _filename, bool mpeg2dec3_priority) { wxMutexLocker lock(AviSynthMutex); AVSValue script; - usedDirectshow = false; + bool usedDirectshow = false; wxString extension = _filename.Right(4); extension.LowerCase(); @@ -161,6 +161,9 @@ PClip AvisynthVideoProvider::OpenVideo(wxString _filename, bool &usedDirectshow, // Convert to RGB32 script = env->Invoke("ConvertToRGB32", script); + // Directshow + if (usedDirectshow) wxMessageBox(_T("Warning! The file is being opened using Avisynth's DirectShowSource, which has unreliable seeking. Frame numbers might not match the real number. PROCEED AT YOUR OWN RISK!"),_T("DirectShowSource warning"),wxICON_EXCLAMATION); + // Cache return (env->Invoke("Cache", script)).AsClip(); } diff --git a/core/video_provider_avs.h b/core/video_provider_avs.h index 0612be2e6..4aba5a3a8 100644 --- a/core/video_provider_avs.h +++ b/core/video_provider_avs.h @@ -76,14 +76,14 @@ private: PClip SubtitledVideo; PClip ResizedVideo; - PClip OpenVideo(wxString _filename, bool &usedDirectshow, bool mpeg2dec3_priority = true); + PClip OpenVideo(wxString _filename, bool mpeg2dec3_priority = true); PClip ApplySubtitles(wxString _filename, PClip videosource); PClip ApplyDARZoom(double _zoom, double _dar, PClip videosource); wxBitmap GetFrame(int n, bool force); void LoadVSFilter(); public: - AvisynthVideoProvider(wxString _filename, wxString _subfilename, bool &usedDirectshow); + AvisynthVideoProvider(wxString _filename, wxString _subfilename); ~AvisynthVideoProvider(); void RefreshSubtitles(); diff --git a/core/video_provider_lavc.cpp b/core/video_provider_lavc.cpp index 9dcf95a93..cbe959615 100644 --- a/core/video_provider_lavc.cpp +++ b/core/video_provider_lavc.cpp @@ -41,6 +41,7 @@ #include "video_provider_lavc.h" #include "utils.h" #include "vfr.h" +#include "mkv_wrap.h" /////////////// @@ -121,7 +122,18 @@ void LAVCVideoProvider::LoadVideo(wxString filename) { if (result < 0) throw _T("Failed to open video decoder"); // Check length - if (stream->duration <= 0) throw _T("Returned invalid stream length"); + isVFR = false; + length = stream->duration; + if (length <= 0) { + if (strcmp(formatContext->iformat->name,"matroska") == 0) { + throw _T("FFmpeg fails at seeking Matroska. If you have any idea on how to fix it, Aegisub is open source."); + MatroskaWrapper::wrapper.Open(filename); + length = MatroskaWrapper::wrapper.GetFrameCount(); + bytePos = MatroskaWrapper::wrapper.GetBytePositions(); + isVFR = true; + } + if (length <= 0) throw _T("Returned invalid stream length"); + } // Set size dar = double(GetSourceWidth()) / GetSourceHeight(); @@ -283,26 +295,47 @@ wxBitmap LAVCVideoProvider::GetFrame(int n) { // Following frame, just get it if (n == frameNumber+1) { GetNextFrame(); + //wxLogMessage(wxString::Format(_T("%i"),lastDecodeTime)); } // Needs to seek else { - // Get seek position - //__int64 half = __int64(AV_TIME_BASE) * stream->r_frame_rate.den / stream->r_frame_rate.num / 2; - //__int64 seekTo = __int64(n) * AV_TIME_BASE * stream->r_frame_rate.den / stream->r_frame_rate.num + stream->start_time; - //if (seekTo > half) seekTo -= half; - //else seekTo = 0; - //__int64 finalPos = av_rescale(seekTo,stream->time_base.den,AV_TIME_BASE * __int64(stream->time_base.num)); + // Prepare seek + __int64 seekTo; + int result; + + // Get time to seek to + if (isVFR) { + //__int64 base = AV_TIME_BASE; + //__int64 time = VFR_Output.GetTimeAtFrame(n,true) * base / 1000000; + //seekTo = av_rescale(time,stream->time_base.den,AV_TIME_BASE * __int64(stream->time_base.num)); + //seekTo = __int64(n) * 1000 * stream->r_frame_rate.den / stream->r_frame_rate.num; + seekTo = bytePos[n]; + + result = av_seek_frame(formatContext,vidStream,seekTo,AVSEEK_FLAG_BACKWARD | AVSEEK_FLAG_BYTE); + } + + // Constant frame rate + else { + seekTo = n; + result = av_seek_frame(formatContext,vidStream,seekTo,AVSEEK_FLAG_BACKWARD); + } // Seek to keyframe - int result = av_seek_frame(formatContext,vidStream,n,AVSEEK_FLAG_BACKWARD); - avcodec_flush_buffers(codecContext); + if (result == 0) { + avcodec_flush_buffers(codecContext); - // Seek until final frame - bool ok = true; - do { - ok = GetNextFrame(); - } while (lastDecodeTime <= n && ok); + // Seek until final frame + bool ok = true; + do { + ok = GetNextFrame(); + } while (lastDecodeTime <= n && ok); + } + + // Failed seeking + else { + GetNextFrame(); + } } // Bitmap @@ -336,7 +369,7 @@ int LAVCVideoProvider::GetPosition() { //////////////////////// // Get number of frames int LAVCVideoProvider::GetFrameCount() { - return stream->duration; + return length; } diff --git a/core/video_provider_lavc.h b/core/video_provider_lavc.h index 977ed5043..e78c6387e 100644 --- a/core/video_provider_lavc.h +++ b/core/video_provider_lavc.h @@ -70,8 +70,12 @@ private: int display_w; int display_h; + wxArrayInt bytePos; + + bool isVFR; __int64 lastDecodeTime; int frameNumber; + int length; wxBitmap curFrame; bool validFrame;