From ce2a54951733dd3ce9b1ab515bf683aa9c941cda Mon Sep 17 00:00:00 2001 From: Karl Blomster Date: Tue, 21 Jul 2009 20:24:25 +0000 Subject: [PATCH] Implemented a basic QuickTime video provider. Works in my simple tests on Windows, but I have no idea if it even compiles on Mac. I leave it up to verm to add detection for it to configure; it's disabled by default (#ifdef'd out unless you #define WITH_QUICKTIME). Originally committed to SVN as r3187. --- .../aegisub_vs2008/aegisub_vs2008.vcproj | 8 + aegisub/src/Makefile.am | 1 + aegisub/src/config/config_windows0.h | 5 + aegisub/src/setup.cpp | 4 + aegisub/src/video_provider_manager.cpp | 6 + aegisub/src/video_provider_quicktime.cpp | 335 ++++++++++++++++++ aegisub/src/video_provider_quicktime.h | 125 +++++++ 7 files changed, 484 insertions(+) create mode 100644 aegisub/src/video_provider_quicktime.cpp create mode 100644 aegisub/src/video_provider_quicktime.h diff --git a/aegisub/build/aegisub_vs2008/aegisub_vs2008.vcproj b/aegisub/build/aegisub_vs2008/aegisub_vs2008.vcproj index 5c023739e..730247409 100644 --- a/aegisub/build/aegisub_vs2008/aegisub_vs2008.vcproj +++ b/aegisub/build/aegisub_vs2008/aegisub_vs2008.vcproj @@ -1538,6 +1538,14 @@ + + + + timecodes = IndexFile(); + if (timecodes.size() == 0) + throw wxString(_T("QuickTime video provider: failed to index file")); + + // ask about vfr override etc + vfr_fps.SetVFR(timecodes); + int override_tc = wxYES; + if (VFR_Output.IsLoaded()) { + override_tc = wxMessageBox(_("You already have timecodes loaded. Would you like to replace them with timecodes from the video file?"), _("Replace timecodes?"), wxYES_NO | wxICON_QUESTION); + if (override_tc == wxYES) { + VFR_Input.SetVFR(timecodes); + VFR_Output.SetVFR(timecodes); + } + } else { // no timecodes loaded, go ahead and apply + VFR_Input.SetVFR(timecodes); + VFR_Output.SetVFR(timecodes); + } + + // set assumed "cfr" fps (dunno if this is actually used anywhere) + double len_s = (double)GetMovieDuration(movie) / (double)GetMovieTimeScale(movie); + assumed_fps = (double)num_frames / len_s; + + cur_fn = 0; +} + + +std::vector QuickTimeVideoProvider::IndexFile() { + TimeScale scale = GetMovieTimeScale(movie); + OSType v_type[1]; + v_type[0] = VisualMediaCharacteristic; + std::vector timecodes; + std::map timestamp_map; // TODO: just do a binary search instead + + int framecount = 1; + TimeValue cur_timeval = 0; + + // get the first frame + GetMovieNextInterestingTime(movie, nextTimeMediaSample + nextTimeEdgeOK, 1, v_type, cur_timeval, 0, &cur_timeval, NULL); + keyframes.push_back(0); // interesting assumption? + + // first, find timestamps and count frames + while (cur_timeval >= 0) { + qt_timestamps.push_back(cur_timeval); + timestamp_map.insert(std::pair(cur_timeval, framecount)); + timecodes.push_back((cur_timeval * 1000) / scale); + framecount++; + GetMovieNextInterestingTime(movie, nextTimeMediaSample, 1, v_type, cur_timeval, 0, &cur_timeval, NULL); + } + // GetMovieNextInterestingTime() returns -1 when there are no more interesting times, + // so we incremented framecount once too much + num_frames = --framecount; + + + // next, find keyframes + cur_timeval = 0; + while (cur_timeval >= 0) { + GetMovieNextInterestingTime(movie, nextTimeSyncSample, 1, v_type, cur_timeval, 0, &cur_timeval, NULL); + keyframes.push_back(timestamp_map[cur_timeval]); + } + + return timecodes; +} + + +const AegiVideoFrame QuickTimeVideoProvider::GetFrame(int n) { + if (n < 0) + n = 0; + if (n >= num_frames) + n = num_frames-1; + cur_fn = n; + + // seek + SetMovieTimeValue(movie, qt_timestamps[n]); + qt_err = GetMoviesError(); + QTCheckError(qt_err, wxString::Format(_T("QuickTime video provider: failed to seek to TimeValue %d"), qt_timestamps[n])); + + // render to offscreen buffer + qt_err = UpdateMovie(movie); + QTCheckError(qt_err, wxString(_T("QuickTime video provider: failed to render frame"))); + MoviesTask(movie, 0L); + qt_err = GetMoviesError(); + QTCheckError(qt_err, wxString(_T("QuickTime video provider: failed to render frame"))); + + // set up destination + AegiVideoFrame dst_frame; + dst_frame.format = FORMAT_RGB32; + dst_frame.w = w; + dst_frame.h = h; + dst_frame.invertChannels = true; + dst_frame.flipped = false; + dst_frame.pitch[0] = w * 4; // 4 bytes per sample + dst_frame.Allocate(); + + // copy data from offscreen buffer + Ptr src_ptr8 = GetPixBaseAddr(GetGWorldPixMap(gw)); + uint32_t *src_ptr = reinterpret_cast(src_ptr8); + uint32_t *dst_ptr = reinterpret_cast(dst_frame.data[0]); + for (int i=0; i<(w*h); i++) + // swap endian if needed; quickdraw always renders big-endian + *dst_ptr++ = Endian::BigToMachine(*src_ptr++); // fun with pointers! + + return dst_frame; +} + + + + +/////////////// +// Utility functions + +void QuickTimeVideoProvider::QTCheckError(OSErr err, wxString errmsg) { + if (err != noErr) + throw errmsg; + /* CheckError(err, errmsg.c_str()); // I wonder if this actually works on Mac, and if so, what it does */ +} + +int QuickTimeVideoProvider::GetWidth() { + return w; +} + +int QuickTimeVideoProvider::GetHeight() { + return h; +} + +int QuickTimeVideoProvider::GetFrameCount() { + return num_frames; +} + +int QuickTimeVideoProvider::GetPosition() { + return cur_fn; +} + +double QuickTimeVideoProvider::GetFPS() { + return assumed_fps; +} + +bool QuickTimeVideoProvider::AreKeyFramesLoaded() { + if (keyframes.GetCount() > 0) + return true; + else + return false; +} + +wxArrayInt QuickTimeVideoProvider::GetKeyFrames() { + return keyframes; +} + +FrameRate QuickTimeVideoProvider::GetTrueFrameRate() { + return vfr_fps; +} + + +#endif /* WITH_QUICKTIME */ diff --git a/aegisub/src/video_provider_quicktime.h b/aegisub/src/video_provider_quicktime.h new file mode 100644 index 000000000..87736ea31 --- /dev/null +++ b/aegisub/src/video_provider_quicktime.h @@ -0,0 +1,125 @@ +// Copyright (c) 2009, Karl Blomster +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the Aegisub Group nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// +// ----------------------------------------------------------------------------- +// +// AEGISUB +// +// Website: http://aegisub.cellosoft.com +// Contact: mailto:zeratul@cellosoft.com +// + + +#pragma once + +#include "config.h" +#include + +#ifdef WITH_QUICKTIME + +#ifdef _MSC_VER +// avoid conflicts between MSVC's stdint.h and QT's stdint.h +#define _STDINT_H +// get MSVC to shut up about a macro redefinition in QT's ConditionalMacros.h +#pragma warning(disable: 4004) +#endif + +#ifndef WIN32 +#define MacOffsetRect OffsetRect +#endif + + +#include "include/aegisub/video_provider.h" +extern "C" { +#ifdef WIN32 +#include +#include +#include +#include +#else +#include // not sure about this path, someone on mac needs to test it +#endif +} +#include +#include +#include +#include +#include "vfr.h" + + +class QuickTimeVideoProvider : public VideoProvider { +private: + Movie movie; // source object + GWorldPtr gw, gw_tmp; // render buffers + Handle in_dataref; // input data handle + + int w, h; // width/height + int num_frames; // length of file in frames + int cur_fn; // current frame number + FrameRate vfr_fps; // vfr framerate + double assumed_fps; // average framerate + wxArrayInt keyframes; // list of keyframes + std::vector qt_timestamps; // qt timestamps (used for seeking) + + OSErr qt_err; // quicktime error code + wxString errmsg; // aegisub error message + + bool CanOpen(const Handle& dataref, const OSType dataref_type); + void LoadVideo(const Aegisub::String filename); + std::vector IndexFile(); + void Close(); + + void QTCheckError(OSErr err, wxString errmsg); + +public: + QuickTimeVideoProvider(Aegisub::String filename); + ~QuickTimeVideoProvider(); + + const AegiVideoFrame GetFrame(int n); + int GetPosition(); + int GetFrameCount(); + + int GetWidth(); + int GetHeight(); + double GetFPS(); + bool IsVFR() { return true; }; + FrameRate GetTrueFrameRate(); + wxArrayInt GetKeyFrames(); + bool QuickTimeVideoProvider::AreKeyFramesLoaded(); + Aegisub::String GetDecoderName() { return L"QuickTime"; }; + bool IsNativelyByFrames() { return true; }; + int GetDesiredCacheSize() { return 8; }; +}; + + +class QuickTimeVideoProviderFactory : public VideoProviderFactory { +public: + VideoProvider *CreateProvider(Aegisub::String video) { return new QuickTimeVideoProvider(video); } +}; + + +#endif /* WITH_QUICKTIME */