Implement a YUV4MPEG video provider. (YUV4MPEG is an uncompressed video format originally created for use by mjpegtools but is also commonly used by a lot of Unix video software; see http://wiki.multimedia.cx/index.php?title=YUV4MPEG2 or http://manpages.ubuntu.com/manpages/karmic/en/man5/yuv4mpeg.5.html (man 5 yuv4mpeg, if you have mjpegtools installed) for a description of the format.)
Currently lacks a few features (no RFF flag parsing is done, interlacing is not supported and the colorspace conversion to RGB32 could stand considerable improvement) but at least now Aegisub is capable of reading video without FFMS2 or Avisynth. Updates #920. Originally committed to SVN as r3168.
This commit is contained in:
parent
ff0787d29c
commit
2b2a7f4212
6 changed files with 616 additions and 0 deletions
|
@ -1539,6 +1539,14 @@
|
|||
RelativePath="..\..\src\video_provider_manager.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\..\src\video_provider_yuv4mpeg.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\..\src\video_provider_yuv4mpeg.h"
|
||||
>
|
||||
</File>
|
||||
</Filter>
|
||||
<Filter
|
||||
Name="Spellchecking"
|
||||
|
|
|
@ -331,6 +331,7 @@ aegisub_2_2_SOURCES = \
|
|||
video_provider_cache.cpp \
|
||||
video_provider_dummy.cpp \
|
||||
video_provider_manager.cpp \
|
||||
video_provider_yuv4mpeg.cpp \
|
||||
video_slider.cpp \
|
||||
visual_feature.cpp \
|
||||
visual_tool.cpp \
|
||||
|
|
|
@ -1318,6 +1318,8 @@ bool FrameMain::LoadList(wxArrayString list) {
|
|||
videoList.Add(_T("asf"));
|
||||
videoList.Add(_T("mov"));
|
||||
videoList.Add(_T("rm"));
|
||||
videoList.Add(_T("y4m"));
|
||||
videoList.Add(_T("yuv"));
|
||||
|
||||
// Subtitle formats
|
||||
wxArrayString subsList;
|
||||
|
|
|
@ -53,6 +53,7 @@
|
|||
#endif
|
||||
#include "video_provider_dummy.h"
|
||||
#include "video_provider_cache.h"
|
||||
#include "video_provider_yuv4mpeg.h"
|
||||
|
||||
|
||||
////////////////
|
||||
|
@ -67,6 +68,19 @@ VideoProvider *VideoProviderFactoryManager::GetProvider(wxString video,double fp
|
|||
#endif
|
||||
}
|
||||
|
||||
try {
|
||||
VideoProvider *y4m_provider = new YUV4MPEGVideoProvider(video.c_str(), fps);
|
||||
if (y4m_provider)
|
||||
y4m_provider = new VideoProviderCache(y4m_provider);
|
||||
return y4m_provider;
|
||||
}
|
||||
catch (wxString temp) {
|
||||
wxLogDebug(_T("YUV4MPEG provider creation failed with reason: %s; trying other providers"), temp.c_str());
|
||||
}
|
||||
catch (...) {
|
||||
wxLogDebug(_T("YUV4MPEG provider creation failed for unknown reasons, trying other providers"));
|
||||
}
|
||||
|
||||
// List of providers
|
||||
wxArrayString list = GetFactoryList(Options.AsText(_T("Video provider")));
|
||||
|
||||
|
|
454
aegisub/src/video_provider_yuv4mpeg.cpp
Normal file
454
aegisub/src/video_provider_yuv4mpeg.cpp
Normal file
|
@ -0,0 +1,454 @@
|
|||
// 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
|
||||
//
|
||||
|
||||
|
||||
#include "video_provider_yuv4mpeg.h"
|
||||
|
||||
// All of this cstdio bogus is because of one reason and one reason only:
|
||||
// MICROSOFT'S IMPLEMENTATION OF STD::FSTREAM DOES NOT SUPPORT FILES LARGER THAN 2 GB.
|
||||
// (yes, really)
|
||||
// With cstdio it's at least possible to work around the problem...
|
||||
#ifdef _MSC_VER
|
||||
#define fseeko _fseeki64
|
||||
#define ftello _ftelli64
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
YUV4MPEGVideoProvider::YUV4MPEGVideoProvider(Aegisub::String filename, double fps) {
|
||||
sf = NULL;
|
||||
w = 0;
|
||||
h = 0;
|
||||
cur_fn = -1;
|
||||
inited = false;
|
||||
pixfmt = Y4M_PIXFMT_NONE;
|
||||
imode = Y4M_ILACE_NOTSET;
|
||||
num_frames = -1;
|
||||
fps_rat.num = -1;
|
||||
fps_rat.den = 1;
|
||||
seek_table.clear();
|
||||
|
||||
errmsg = _T("YUV4MPEG video provider: ");
|
||||
|
||||
try {
|
||||
LoadVideo(filename);
|
||||
}
|
||||
catch (wxString temp) {
|
||||
Close();
|
||||
errmsg.Append(temp);
|
||||
throw errmsg;
|
||||
}
|
||||
catch (...) {
|
||||
Close();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
YUV4MPEGVideoProvider::~YUV4MPEGVideoProvider() {
|
||||
Close();
|
||||
}
|
||||
|
||||
|
||||
void YUV4MPEGVideoProvider::LoadVideo(const Aegisub::String _filename) {
|
||||
Close();
|
||||
|
||||
wxString filename = wxFileName(wxString(_filename.c_str(), wxConvFile)).GetShortPath();
|
||||
|
||||
#ifdef WIN32
|
||||
sf = _wfopen(filename.wc_str(), _T("rb"));
|
||||
#else
|
||||
sf = fopen(filename.mb_str(wxConvUTF8), "rb");
|
||||
#endif
|
||||
|
||||
if (sf == NULL)
|
||||
throw wxString::Format(_T("Failed to open file"));
|
||||
|
||||
CheckFileFormat();
|
||||
|
||||
ParseFileHeader(ReadHeader(0, false));
|
||||
|
||||
if (w <= 0 || h <= 0)
|
||||
throw wxString(_T("Invalid resolution"));
|
||||
if (fps_rat.num <= 0 || fps_rat.den <= 0) {
|
||||
fps_rat.num = 25;
|
||||
fps_rat.den = 1;
|
||||
wxLogDebug(_T("YUV4MPEG video provider: framerate info unavailable, assuming 25fps"));
|
||||
}
|
||||
if (pixfmt == Y4M_PIXFMT_NONE)
|
||||
pixfmt = Y4M_PIXFMT_420JPEG;
|
||||
if (imode == Y4M_ILACE_NOTSET)
|
||||
imode = Y4M_ILACE_UNKNOWN;
|
||||
|
||||
switch (pixfmt) {
|
||||
case Y4M_PIXFMT_420JPEG:
|
||||
case Y4M_PIXFMT_420MPEG2:
|
||||
case Y4M_PIXFMT_420PALDV:
|
||||
frame_sz = (w * h * 3) / 2; break;
|
||||
case Y4M_PIXFMT_422:
|
||||
frame_sz = (w * h * 2); break;
|
||||
// TODO: add support for more pixel formats
|
||||
default:
|
||||
throw wxString(_T("Unsupported colorspace"));
|
||||
}
|
||||
|
||||
num_frames = IndexFile();
|
||||
if (num_frames <= 0 || seek_table.empty())
|
||||
throw wxString(_T("Unable to determine file length"));
|
||||
cur_fn = 0;
|
||||
|
||||
fseeko(sf, 0, SEEK_SET);
|
||||
}
|
||||
|
||||
|
||||
void YUV4MPEGVideoProvider::Close() {
|
||||
seek_table.clear();
|
||||
if (sf)
|
||||
fclose(sf);
|
||||
sf = NULL;
|
||||
}
|
||||
|
||||
|
||||
// verify that the file is actually a YUV4MPEG file
|
||||
void YUV4MPEGVideoProvider::CheckFileFormat() {
|
||||
char buf[10];
|
||||
if (fread(buf, 10, 1, sf) != 1)
|
||||
throw wxString(_T("CheckFileFormat: Failed reading header"));
|
||||
if (strncmp("YUV4MPEG2 ", buf, 10))
|
||||
throw wxString(_T("CheckFileFormat: File is not a YUV4MPEG file (bad magic)"));
|
||||
|
||||
fseeko(sf, 0, SEEK_SET);
|
||||
}
|
||||
|
||||
|
||||
// read a frame or file header and return a list of its parameters
|
||||
std::vector<wxString> YUV4MPEGVideoProvider::ReadHeader(int64_t startpos, bool reset_pos) {
|
||||
int64_t oldpos = ftello(sf);
|
||||
std::vector<wxString> tags;
|
||||
wxString curtag = _T("");
|
||||
int bytesread = 0;
|
||||
int buf;
|
||||
|
||||
if (fseeko(sf, startpos, SEEK_SET))
|
||||
throw wxString::Format(_T("YUV4MPEG video provider: ReadHeader: failed seeking to position %d"), startpos);
|
||||
|
||||
// read header until terminating newline (0x0A) is found
|
||||
while ((buf = fgetc(sf)) != 0x0A) {
|
||||
if (ferror(sf))
|
||||
throw wxString(_T("ReadHeader: Failed to read from file"));
|
||||
if (feof(sf)) {
|
||||
// you know, this is one of the places where it would be really nice
|
||||
// to be able to throw an exception object that tells the caller that EOF was reached
|
||||
wxLogDebug(_T("YUV4MPEG video provider: ReadHeader: Reached EOF, returning"));
|
||||
break;
|
||||
}
|
||||
|
||||
// some basic low-effort sanity checking
|
||||
if (buf == 0x00)
|
||||
throw wxString(_T("ReadHeader: Malformed header (unexpected NUL)"));
|
||||
if (++bytesread >= YUV4MPEG_HEADER_MAXLEN)
|
||||
throw wxString(_T("ReadHeader: Malformed header (no terminating newline found)"));
|
||||
|
||||
// found a new tag
|
||||
if (buf == 0x20) {
|
||||
tags.push_back(curtag);
|
||||
curtag.Clear();
|
||||
}
|
||||
else
|
||||
curtag.Append(static_cast<wxChar>(buf));
|
||||
}
|
||||
// if only one tag with no trailing space was found (possible in the
|
||||
// FRAME header case), make sure we get it
|
||||
if (!curtag.IsEmpty()) {
|
||||
tags.push_back(curtag);
|
||||
curtag.Clear();
|
||||
}
|
||||
|
||||
if (reset_pos)
|
||||
fseeko(sf, oldpos, SEEK_SET);
|
||||
|
||||
return tags;
|
||||
}
|
||||
|
||||
|
||||
// parse a file header and set file properties
|
||||
void YUV4MPEGVideoProvider::ParseFileHeader(const std::vector<wxString>& tags) {
|
||||
if (tags.size() <= 1)
|
||||
throw wxString(_T("ParseFileHeader: contentless header"));
|
||||
if (tags.front().Cmp(_T("YUV4MPEG2")))
|
||||
throw wxString(_T("ParseFileHeader: malformed header (bad magic)"));
|
||||
|
||||
// temporary stuff
|
||||
int t_w = -1;
|
||||
int t_h = -1;
|
||||
int t_fps_num = -1;
|
||||
int t_fps_den = -1;
|
||||
Y4M_InterlacingMode t_imode = Y4M_ILACE_NOTSET;
|
||||
Y4M_PixelFormat t_pixfmt = Y4M_PIXFMT_NONE;
|
||||
|
||||
for (unsigned i = 1; i < tags.size(); i++) {
|
||||
wxString tag = _T("");
|
||||
long tmp_long1 = 0;
|
||||
long tmp_long2 = 0;
|
||||
|
||||
if (tags.at(i).StartsWith(_T("W"), &tag)) {
|
||||
if (!tag.ToLong(&tmp_long1))
|
||||
throw wxString(_T("ParseFileHeader: invalid width"));
|
||||
t_w = (int)tmp_long1;
|
||||
}
|
||||
else if (tags.at(i).StartsWith(_T("H"), &tag)) {
|
||||
if (!tag.ToLong(&tmp_long1))
|
||||
throw wxString(_T("ParseFileHeader: invalid height"));
|
||||
t_h = (int)tmp_long1;
|
||||
}
|
||||
else if (tags.at(i).StartsWith(_T("F"), &tag)) {
|
||||
if (!(tag.BeforeFirst(':')).ToLong(&tmp_long1) && tag.AfterFirst(':').ToLong(&tmp_long2))
|
||||
throw wxString(_T("ParseFileHeader: invalid framerate"));
|
||||
t_fps_num = (int)tmp_long1;
|
||||
t_fps_den = (int)tmp_long2;
|
||||
}
|
||||
else if (tags.at(i).StartsWith(_T("C"), &tag)) {
|
||||
// technically this should probably be case sensitive,
|
||||
// but being liberal in what you accept doesn't hurt
|
||||
if (!tag.CmpNoCase(_T("420jpeg"))) t_pixfmt = Y4M_PIXFMT_420JPEG;
|
||||
else if (!tag.CmpNoCase(_T("420"))) t_pixfmt = Y4M_PIXFMT_420JPEG; // is this really correct?
|
||||
else if (!tag.CmpNoCase(_T("420mpeg2"))) t_pixfmt = Y4M_PIXFMT_420MPEG2;
|
||||
else if (!tag.CmpNoCase(_T("420paldv"))) t_pixfmt = Y4M_PIXFMT_420PALDV;
|
||||
else if (!tag.CmpNoCase(_T("411"))) t_pixfmt = Y4M_PIXFMT_411;
|
||||
else if (!tag.CmpNoCase(_T("422"))) t_pixfmt = Y4M_PIXFMT_422;
|
||||
else if (!tag.CmpNoCase(_T("444"))) t_pixfmt = Y4M_PIXFMT_444;
|
||||
else if (!tag.CmpNoCase(_T("444alpha"))) t_pixfmt = Y4M_PIXFMT_444ALPHA;
|
||||
else if (!tag.CmpNoCase(_T("mono"))) t_pixfmt = Y4M_PIXFMT_MONO;
|
||||
else
|
||||
throw wxString(_T("ParseFileHeader: invalid or unknown colorspace"));
|
||||
}
|
||||
else if (tags.at(i).StartsWith(_T("I"), &tag)) {
|
||||
if (!tag.CmpNoCase(_T("p"))) t_imode = Y4M_ILACE_PROGRESSIVE;
|
||||
else if (!tag.CmpNoCase(_T("t"))) t_imode = Y4M_ILACE_TFF;
|
||||
else if (!tag.CmpNoCase(_T("b"))) t_imode = Y4M_ILACE_BFF;
|
||||
else if (!tag.CmpNoCase(_T("m"))) t_imode = Y4M_ILACE_MIXED;
|
||||
else if (!tag.CmpNoCase(_T("?"))) t_imode = Y4M_ILACE_UNKNOWN;
|
||||
else
|
||||
throw wxString(_T("ParseFileHeader: invalid or unknown interlacing mode"));
|
||||
}
|
||||
else
|
||||
wxLogDebug(_T("ParseFileHeader: unparsed tag: %s"), tags.at(i).c_str());
|
||||
}
|
||||
|
||||
// The point of all this is to allow multiple YUV4MPEG2 headers in a single file
|
||||
// (can happen if you concat several files) as long as they have identical
|
||||
// header flags. The spec doesn't explicitly say you have to allow this,
|
||||
// but the "reference implementation" (mjpegtools) does, so I'm doing it too.
|
||||
if (inited) {
|
||||
if (t_w > 0 && t_w != w)
|
||||
throw wxString(_T("ParseFileHeader: illegal width change"));
|
||||
if (t_h > 0 && t_h != h)
|
||||
throw wxString(_T("ParseFileHeader: illegal height change"));
|
||||
if ((t_fps_num > 0 && t_fps_den > 0) && (t_fps_num != fps_rat.num || t_fps_den != fps_rat.den))
|
||||
throw wxString(_T("ParseFileHeader: illegal framerate change"));
|
||||
if (t_pixfmt != Y4M_PIXFMT_NONE && t_pixfmt != pixfmt)
|
||||
throw wxString(_T("ParseFileHeader: illegal colorspace change"));
|
||||
if (t_imode != Y4M_ILACE_NOTSET && t_imode != imode)
|
||||
throw wxString(_T("ParseFileHeader: illegal interlacing mode change"));
|
||||
}
|
||||
else {
|
||||
w = t_w;
|
||||
h = t_h;
|
||||
fps_rat.num = t_fps_num;
|
||||
fps_rat.den = t_fps_den;
|
||||
pixfmt = t_pixfmt != Y4M_PIXFMT_NONE ? t_pixfmt : Y4M_PIXFMT_420JPEG;
|
||||
imode = t_imode != Y4M_ILACE_NOTSET ? t_imode : Y4M_ILACE_UNKNOWN;
|
||||
inited = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// parse a frame header (currently unused)
|
||||
YUV4MPEGVideoProvider::Y4M_FrameFlags YUV4MPEGVideoProvider::ParseFrameHeader(const std::vector<wxString>& tags) {
|
||||
if (tags.front().Cmp(_("FRAME")))
|
||||
throw wxString(_T("ParseFrameHeader: malformed frame header (bad magic)"));
|
||||
|
||||
// TODO: implement parsing of rff flags etc
|
||||
|
||||
return Y4M_FFLAG_NONE;
|
||||
}
|
||||
|
||||
|
||||
// index the file, i.e. find all frames and their flags
|
||||
int YUV4MPEGVideoProvider::IndexFile() {
|
||||
int framecount = 0;
|
||||
int64_t curpos = ftello(sf);
|
||||
|
||||
// the ParseFileHeader() call in LoadVideo() will already have read
|
||||
// the file header for us and set the seek position correctly
|
||||
while (true) {
|
||||
curpos = ftello(sf); // update position
|
||||
// continue reading headers until no more are found
|
||||
std::vector<wxString> tags = ReadHeader(curpos, false);
|
||||
curpos = ftello(sf);
|
||||
|
||||
if (tags.empty())
|
||||
break; // no more headers
|
||||
|
||||
Y4M_FrameFlags flags = Y4M_FFLAG_NOTSET;
|
||||
if (!tags.front().Cmp(_T("YUV4MPEG2"))) {
|
||||
ParseFileHeader(tags);
|
||||
continue;
|
||||
}
|
||||
else if (!tags.front().Cmp(_T("FRAME")))
|
||||
flags = ParseFrameHeader(tags);
|
||||
|
||||
if (flags == Y4M_FFLAG_NONE) {
|
||||
framecount++;
|
||||
seek_table.push_back(curpos);
|
||||
// seek to next frame header start position
|
||||
if (fseeko(sf, frame_sz, SEEK_CUR))
|
||||
throw wxString::Format(_T("IndexFile: failed seeking to position %d"), curpos + frame_sz);
|
||||
}
|
||||
else {
|
||||
// TODO: implement this
|
||||
}
|
||||
}
|
||||
|
||||
return framecount;
|
||||
}
|
||||
|
||||
|
||||
const AegiVideoFrame YUV4MPEGVideoProvider::GetFrame(int n, int desired_fmts) {
|
||||
// don't try to seek to insane places
|
||||
if (n < 0)
|
||||
n = 0;
|
||||
if (n >= num_frames)
|
||||
n = num_frames-1;
|
||||
// set position
|
||||
cur_fn = n;
|
||||
|
||||
VideoFrameFormat src_fmt, dst_fmt;
|
||||
switch (pixfmt) {
|
||||
case Y4M_PIXFMT_420JPEG:
|
||||
case Y4M_PIXFMT_420MPEG2:
|
||||
case Y4M_PIXFMT_420PALDV:
|
||||
src_fmt = FORMAT_YV12; break;
|
||||
case Y4M_PIXFMT_422:
|
||||
src_fmt = FORMAT_YUY2; break;
|
||||
// TODO: add support for more pixel formats
|
||||
default:
|
||||
throw wxString(_T("YUV4MPEG video provider: GetFrame: Unsupported source colorspace"));
|
||||
}
|
||||
|
||||
// TODO: fix this terrible piece of crap and implement colorspace conversions
|
||||
// (write a function to select best output format)
|
||||
if ((desired_fmts & FORMAT_YV12) && src_fmt == FORMAT_YV12)
|
||||
dst_fmt = FORMAT_YV12;
|
||||
else if ((desired_fmts & FORMAT_YUY2) && src_fmt == FORMAT_YUY2)
|
||||
dst_fmt = FORMAT_YUY2;
|
||||
else if ((desired_fmts & FORMAT_RGB32) && src_fmt == FORMAT_YV12)
|
||||
dst_fmt = FORMAT_RGB32;
|
||||
else
|
||||
throw wxString(_T("YUV4MPEG video provider: GetFrame: Upstream video provider requested unknown or unsupported color format"));
|
||||
|
||||
int uv_width, uv_height;
|
||||
// TODO: ugh, fix this
|
||||
switch (src_fmt) {
|
||||
case FORMAT_YV12:
|
||||
uv_width = w / 2; uv_height = h / 2; break;
|
||||
case FORMAT_YUY2:
|
||||
uv_width = w / 2; uv_height = h; break;
|
||||
default:
|
||||
throw wxString(_T("YUV4MPEG video provider: GetFrame: sanity check failed"));
|
||||
}
|
||||
|
||||
AegiVideoFrame tmp_frame;
|
||||
|
||||
tmp_frame.format = src_fmt;
|
||||
tmp_frame.w = w;
|
||||
tmp_frame.h = h;
|
||||
tmp_frame.invertChannels = false;
|
||||
tmp_frame.pitch[0] = w;
|
||||
for (int i=1;i<=2;i++) tmp_frame.pitch[i] = uv_width;
|
||||
tmp_frame.Allocate();
|
||||
|
||||
fseeko(sf, seek_table[n], SEEK_SET);
|
||||
size_t ret;
|
||||
ret = fread(tmp_frame.data[0], w * h, 1, sf);
|
||||
if (ret != 1 || feof(sf) || ferror(sf))
|
||||
throw wxString(_T("YUV4MPEG video provider: GetFrame: failed to read luma plane"));
|
||||
for (int i = 1; i <= 2; i++) {
|
||||
ret = fread(tmp_frame.data[i], uv_width * uv_height, 1, sf);
|
||||
if (ret != 1 || feof(sf) || ferror(sf))
|
||||
throw wxString(_T("YUV4MPEG video provider: GetFrame: failed to read chroma planes"));
|
||||
}
|
||||
|
||||
AegiVideoFrame dst_frame;
|
||||
dst_frame.format = dst_fmt;
|
||||
dst_frame.w = w;
|
||||
dst_frame.h = h;
|
||||
if (dst_fmt == FORMAT_RGB32) {
|
||||
dst_frame.invertChannels = true;
|
||||
dst_frame.pitch[0] = w * 4;
|
||||
dst_frame.ConvertFrom(tmp_frame);
|
||||
}
|
||||
else
|
||||
dst_frame.CopyFrom(tmp_frame);
|
||||
|
||||
tmp_frame.Clear();
|
||||
|
||||
return dst_frame;
|
||||
}
|
||||
|
||||
|
||||
|
||||
///////////////
|
||||
// Utility functions
|
||||
int YUV4MPEGVideoProvider::GetWidth() {
|
||||
return w;
|
||||
}
|
||||
|
||||
int YUV4MPEGVideoProvider::GetHeight() {
|
||||
return h;
|
||||
}
|
||||
|
||||
int YUV4MPEGVideoProvider::GetFrameCount() {
|
||||
return num_frames;
|
||||
}
|
||||
|
||||
int YUV4MPEGVideoProvider::GetPosition() {
|
||||
return cur_fn;
|
||||
}
|
||||
|
||||
double YUV4MPEGVideoProvider::GetFPS() {
|
||||
return double(fps_rat.num) / double(fps_rat.den);
|
||||
}
|
||||
|
137
aegisub/src/video_provider_yuv4mpeg.h
Normal file
137
aegisub/src/video_provider_yuv4mpeg.h
Normal file
|
@ -0,0 +1,137 @@
|
|||
// 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 "include/aegisub/video_provider.h"
|
||||
#include <wx/wxprec.h>
|
||||
#include <stdio.h>
|
||||
#include <vector>
|
||||
|
||||
// ffmpeg uses 80, so I'm p sure this isn't too small
|
||||
#define YUV4MPEG_HEADER_MAXLEN 128
|
||||
|
||||
|
||||
class YUV4MPEGVideoProvider : public VideoProvider {
|
||||
private:
|
||||
enum Y4M_PixelFormat {
|
||||
Y4M_PIXFMT_NONE = -1,
|
||||
Y4M_PIXFMT_420JPEG,
|
||||
Y4M_PIXFMT_420MPEG2,
|
||||
Y4M_PIXFMT_420PALDV,
|
||||
Y4M_PIXFMT_411,
|
||||
Y4M_PIXFMT_422,
|
||||
Y4M_PIXFMT_444,
|
||||
Y4M_PIXFMT_444ALPHA,
|
||||
Y4M_PIXFMT_MONO,
|
||||
};
|
||||
|
||||
enum Y4M_InterlacingMode {
|
||||
Y4M_ILACE_NOTSET = -1, // not to be confused with Y4M_ILACE_UNKNOWN
|
||||
Y4M_ILACE_PROGRESSIVE,
|
||||
Y4M_ILACE_TFF,
|
||||
Y4M_ILACE_BFF,
|
||||
Y4M_ILACE_MIXED,
|
||||
Y4M_ILACE_UNKNOWN,
|
||||
};
|
||||
|
||||
// this is currently unused :(
|
||||
enum Y4M_FrameFlags {
|
||||
Y4M_FFLAG_NOTSET = -1,
|
||||
Y4M_FFLAG_NONE = 0x0000,
|
||||
// repeat field/frame flags
|
||||
Y4M_FFLAG_R_TFF = 0x0001, // TFF
|
||||
Y4M_FFLAG_R_TFF_R = 0x0002, // TFF and repeat
|
||||
Y4M_FFLAG_R_BFF = 0x0004, // BFF
|
||||
Y4M_FFLAG_R_BFF_R = 0x0008, // BFF and repeat
|
||||
Y4M_FFLAG_R_P = 0x0010, // progressive
|
||||
Y4M_FFLAG_R_P_R = 0x0020, // progressive and repeat once
|
||||
Y4M_FFLAG_R_P_RR = 0x0040, // progressive and repeat twice
|
||||
// temporal sampling flags
|
||||
Y4M_FFLAG_T_P = 0x0080, // progressive (fields sampled at the same time)
|
||||
Y4M_FFLAG_T_I = 0x0100, // interlaced (fields sampled at different times)
|
||||
// spatial sampling flags
|
||||
Y4M_FFLAG_C_P = 0x0200, // progressive (whole frame subsampled)
|
||||
Y4M_FFLAG_C_I = 0x0400, // interlaced (fields subsampled independently)
|
||||
Y4M_FFLAG_C_UNKNOWN = 0x0800, // unknown (only allowed for non-4:2:0 sampling)
|
||||
};
|
||||
|
||||
FILE *sf; // source file
|
||||
bool inited;
|
||||
int w, h; // width/height
|
||||
int num_frames; // length of file in frames
|
||||
int frame_sz; // size of each frame in bytes
|
||||
Y4M_PixelFormat pixfmt; // colorspace/pixel format
|
||||
Y4M_InterlacingMode imode; // interlacing mode
|
||||
struct {
|
||||
int num;
|
||||
int den;
|
||||
} fps_rat; // framerate
|
||||
|
||||
std::vector<int64_t> seek_table; // the position in the file of each frame, in bytes
|
||||
int cur_fn; // current frame number
|
||||
|
||||
wxString errmsg;
|
||||
|
||||
void LoadVideo(const Aegisub::String filename);
|
||||
void Close();
|
||||
|
||||
void CheckFileFormat();
|
||||
void ParseFileHeader(const std::vector<wxString>& tags);
|
||||
Y4M_FrameFlags ParseFrameHeader(const std::vector<wxString>& tags);
|
||||
std::vector<wxString> ReadHeader(int64_t startpos, bool reset_pos=false);
|
||||
int IndexFile();
|
||||
|
||||
public:
|
||||
YUV4MPEGVideoProvider(Aegisub::String filename, double fps);
|
||||
~YUV4MPEGVideoProvider();
|
||||
|
||||
const AegiVideoFrame GetFrame(int n, int formatType);
|
||||
int GetPosition();
|
||||
int GetFrameCount();
|
||||
|
||||
int GetWidth();
|
||||
int GetHeight();
|
||||
double GetFPS();
|
||||
bool AreKeyFramesLoaded() { return false; }
|
||||
wxArrayInt GetKeyFrames() { return wxArrayInt(); };
|
||||
bool IsVFR() { return false; };
|
||||
FrameRate GetTrueFrameRate() { return FrameRate(); };
|
||||
Aegisub::String GetDecoderName() { return L"YUV4MPEG"; }
|
||||
bool IsNativelyByFrames() { return true; }
|
||||
int GetDesiredCacheSize() { return 8; }
|
||||
};
|
||||
|
Loading…
Reference in a new issue