2022-08-08 02:14:31 +02:00
|
|
|
// Copyright (c) 2022, arch1t3cht <arch1t3cht@gmail.com>
|
|
|
|
//
|
|
|
|
// Permission to use, copy, modify, and distribute this software for any
|
|
|
|
// purpose with or without fee is hereby granted, provided that the above
|
|
|
|
// copyright notice and this permission notice appear in all copies.
|
|
|
|
//
|
|
|
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
|
|
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
|
|
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
|
|
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
|
|
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
|
|
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
|
|
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
//
|
|
|
|
// Aegisub Project http://www.aegisub.org/
|
|
|
|
|
|
|
|
/// @file video_provider_bestsource.cpp
|
|
|
|
/// @brief BestSource-based video provider
|
|
|
|
/// @ingroup video_input bestsource
|
|
|
|
///
|
|
|
|
|
|
|
|
#ifdef WITH_BESTSOURCE
|
|
|
|
#include "include/aegisub/video_provider.h"
|
|
|
|
|
|
|
|
#include "videosource.h"
|
2022-08-09 04:01:02 +02:00
|
|
|
#include "audiosource.h"
|
2022-08-08 02:14:31 +02:00
|
|
|
#include "BSRational.h"
|
|
|
|
|
|
|
|
extern "C" {
|
|
|
|
#include <libavutil/frame.h>
|
|
|
|
#include <libavutil/pixfmt.h>
|
|
|
|
#include <libswscale/swscale.h>
|
|
|
|
}
|
|
|
|
|
2022-08-11 02:05:16 +02:00
|
|
|
#include "bestsource_common.h"
|
2022-08-08 02:14:31 +02:00
|
|
|
#include "options.h"
|
|
|
|
#include "compat.h"
|
|
|
|
#include "video_frame.h"
|
|
|
|
namespace agi { class BackgroundRunner; }
|
|
|
|
|
|
|
|
#include <libaegisub/fs.h>
|
|
|
|
#include <libaegisub/path.h>
|
|
|
|
#include <libaegisub/make_unique.h>
|
|
|
|
#include <libaegisub/background_runner.h>
|
|
|
|
#include <libaegisub/log.h>
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
/// @class BSVideoProvider
|
|
|
|
/// @brief Implements video loading through BestSource.
|
|
|
|
class BSVideoProvider final : public VideoProvider {
|
|
|
|
std::map<std::string, std::string> bsopts;
|
|
|
|
BestVideoSource bs;
|
|
|
|
VideoProperties properties;
|
|
|
|
|
|
|
|
std::vector<int> Keyframes;
|
|
|
|
agi::vfr::Framerate Timecodes;
|
2022-08-09 04:01:02 +02:00
|
|
|
std::string colorspace;
|
|
|
|
bool has_audio = false;
|
2022-08-08 02:14:31 +02:00
|
|
|
|
|
|
|
public:
|
|
|
|
BSVideoProvider(agi::fs::path const& filename, std::string const& colormatrix, agi::BackgroundRunner *br);
|
|
|
|
|
|
|
|
void GetFrame(int n, VideoFrame &out) override;
|
|
|
|
|
2022-08-09 04:01:02 +02:00
|
|
|
void SetColorSpace(std::string const& matrix) override { } // TODO Follow Aegisub's colorspace forcing?
|
2022-08-08 02:14:31 +02:00
|
|
|
|
|
|
|
int GetFrameCount() const override { return properties.NumFrames; };
|
|
|
|
|
|
|
|
int GetWidth() const override { return properties.Width; };
|
|
|
|
int GetHeight() const override { return properties.Height; };
|
|
|
|
double GetDAR() const override { return ((double) properties.Width * properties.SAR.Num) / (properties.Height * properties.SAR.Den); };
|
|
|
|
|
|
|
|
agi::vfr::Framerate GetFPS() const override { return Timecodes; };
|
2022-08-09 04:01:02 +02:00
|
|
|
std::string GetColorSpace() const override { return colorspace; };
|
|
|
|
std::string GetRealColorSpace() const override { return colorspace; };
|
2022-08-08 02:14:31 +02:00
|
|
|
std::vector<int> GetKeyFrames() const override { return Keyframes; };
|
|
|
|
std::string GetDecoderName() const override { return "BestSource"; };
|
|
|
|
bool WantsCaching() const override { return false; };
|
2022-08-09 04:01:02 +02:00
|
|
|
bool HasAudio() const override { return has_audio; };
|
2022-08-08 02:14:31 +02:00
|
|
|
};
|
|
|
|
|
2022-08-09 04:01:02 +02:00
|
|
|
// Match the logic from the ffms2 provider, but directly use libavutil's constants and don't abort when encountering an unknown color space
|
|
|
|
std::string colormatrix_description(const AVFrame *frame) {
|
|
|
|
// Assuming TV for unspecified
|
|
|
|
std::string str = frame->color_range == AVCOL_RANGE_JPEG ? "PC" : "TV";
|
|
|
|
LOG_D("bestsource") << frame->colorspace;
|
|
|
|
|
|
|
|
switch (frame->colorspace) {
|
|
|
|
case AVCOL_SPC_BT709:
|
|
|
|
return str + ".709";
|
|
|
|
case AVCOL_SPC_FCC:
|
|
|
|
return str + ".FCC";
|
|
|
|
case AVCOL_SPC_BT470BG:
|
|
|
|
case AVCOL_SPC_SMPTE170M:
|
|
|
|
return str + ".601";
|
|
|
|
case AVCOL_SPC_SMPTE240M:
|
|
|
|
return str + ".240M";
|
|
|
|
default:
|
|
|
|
return "None";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-08 02:14:31 +02:00
|
|
|
BSVideoProvider::BSVideoProvider(agi::fs::path const& filename, std::string const& colormatrix, agi::BackgroundRunner *br) try
|
|
|
|
: bsopts()
|
2022-08-11 02:05:16 +02:00
|
|
|
, bs(filename.string(), "", -1, false, OPT_GET("Provider/Video/BestSource/Threads")->GetInt(), GetBSCacheFile(filename), &bsopts)
|
2022-08-08 02:14:31 +02:00
|
|
|
{
|
2022-08-10 02:40:02 +02:00
|
|
|
bs.SetMaxCacheSize(OPT_GET("Provider/Video/BestSource/Max Cache Size")->GetInt() << 20);
|
|
|
|
bs.SetSeekPreRoll(OPT_GET("Provider/Video/BestSource/Seek Preroll")->GetInt());
|
2022-08-09 04:01:02 +02:00
|
|
|
try {
|
|
|
|
BestAudioSource dummysource(filename.string(), -1, 0, "");
|
|
|
|
has_audio = true;
|
|
|
|
} catch (AudioException const& err) {
|
|
|
|
has_audio = false;
|
|
|
|
}
|
2022-08-08 02:14:31 +02:00
|
|
|
|
|
|
|
properties = bs.GetVideoProperties();
|
|
|
|
|
|
|
|
if (properties.NumFrames == -1) {
|
|
|
|
LOG_D("bs") << "File not cached or varying samples, creating cache.";
|
|
|
|
br->Run([&](agi::ProgressSink *ps) {
|
|
|
|
ps->SetTitle(from_wx(_("Exacting")));
|
|
|
|
ps->SetMessage(from_wx(_("Creating cache... This can take a while!")));
|
|
|
|
ps->SetIndeterminate();
|
|
|
|
if (bs.GetExactDuration()) {
|
|
|
|
LOG_D("bs") << "File cached and has exact samples.";
|
|
|
|
}
|
|
|
|
});
|
|
|
|
properties = bs.GetVideoProperties();
|
|
|
|
}
|
|
|
|
|
|
|
|
br->Run([&](agi::ProgressSink *ps) {
|
|
|
|
ps->SetTitle(from_wx(_("Scanning")));
|
|
|
|
ps->SetMessage(from_wx(_("Finding Keyframes and Timecodes...")));
|
|
|
|
|
|
|
|
std::vector<int> TimecodesVector;
|
|
|
|
for (int n = 0; n < properties.NumFrames; n++) {
|
|
|
|
if (ps->IsCancelled()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
std::unique_ptr<BestVideoFrame> frame(bs.GetFrame(n));
|
|
|
|
if (frame == nullptr) {
|
|
|
|
throw VideoOpenError("Couldn't read frame!");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (frame->GetAVFrame()->key_frame) {
|
|
|
|
Keyframes.push_back(n);
|
|
|
|
}
|
|
|
|
|
2023-02-13 13:34:08 +01:00
|
|
|
TimecodesVector.push_back(1000 * frame->Pts * properties.TimeBase.Num / properties.TimeBase.Den);
|
2022-08-08 02:14:31 +02:00
|
|
|
ps->SetProgress(n, properties.NumFrames);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (TimecodesVector.size() < 2 || TimecodesVector.front() == TimecodesVector.back()) {
|
|
|
|
Timecodes = (double) properties.FPS.Num / properties.FPS.Den;
|
|
|
|
} else {
|
|
|
|
Timecodes = agi::vfr::Framerate(TimecodesVector);
|
|
|
|
}
|
|
|
|
});
|
2022-08-09 04:01:02 +02:00
|
|
|
|
2023-01-31 02:47:02 +01:00
|
|
|
BSCleanCache();
|
|
|
|
|
2022-08-09 04:01:02 +02:00
|
|
|
// Decode the first frame to get the color space
|
|
|
|
std::unique_ptr<BestVideoFrame> frame(bs.GetFrame(0));
|
|
|
|
colorspace = colormatrix_description(frame->GetAVFrame());
|
2022-08-08 02:14:31 +02:00
|
|
|
}
|
|
|
|
catch (VideoException const& err) {
|
|
|
|
throw VideoOpenError("Failed to create BestVideoSource");
|
|
|
|
}
|
|
|
|
|
|
|
|
void BSVideoProvider::GetFrame(int n, VideoFrame &out) {
|
|
|
|
std::unique_ptr<BestVideoFrame> bsframe(bs.GetFrame(n));
|
|
|
|
if (bsframe == nullptr) {
|
|
|
|
throw VideoDecodeError("Couldn't read frame!");
|
|
|
|
}
|
|
|
|
const AVFrame *frame = bsframe->GetAVFrame();
|
|
|
|
|
|
|
|
SwsContext *context = sws_getContext(
|
|
|
|
frame->width, frame->height, (AVPixelFormat) frame->format, // TODO figure out aegi's color space forcing.
|
|
|
|
frame->width, frame->height, AV_PIX_FMT_BGR0,
|
|
|
|
SWS_BICUBIC, nullptr, nullptr, nullptr);
|
|
|
|
|
|
|
|
if (context == nullptr) {
|
|
|
|
throw VideoDecodeError("Couldn't convert frame!");
|
|
|
|
}
|
|
|
|
|
2022-08-29 11:08:21 +02:00
|
|
|
int range = frame->color_range == AVCOL_RANGE_JPEG;
|
|
|
|
const int *coefficients = sws_getCoefficients(frame->colorspace == AVCOL_SPC_UNSPECIFIED ? AVCOL_SPC_BT709 : frame->colorspace);
|
|
|
|
|
|
|
|
sws_setColorspaceDetails(context,
|
|
|
|
coefficients, range,
|
|
|
|
coefficients, range,
|
|
|
|
0, 1 << 16, 1 << 16);
|
|
|
|
|
2022-08-08 03:32:33 +02:00
|
|
|
out.data.resize(frame->width * frame->height * 4);
|
|
|
|
uint8_t *data[1] = {&out.data[0]};
|
|
|
|
int stride[1] = {frame->width * 4};
|
|
|
|
sws_scale(context, frame->data, frame->linesize, 0, frame->height, data, stride);
|
2022-08-08 02:14:31 +02:00
|
|
|
|
2022-08-08 03:32:33 +02:00
|
|
|
out.width = frame->width;
|
|
|
|
out.height = frame->height;
|
|
|
|
out.pitch = stride[0];
|
2022-08-08 02:14:31 +02:00
|
|
|
out.flipped = false; // TODO figure out flipped
|
|
|
|
|
|
|
|
sws_freeContext(context);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
std::unique_ptr<VideoProvider> CreateBSVideoProvider(agi::fs::path const& path, std::string const& colormatrix, agi::BackgroundRunner *br) {
|
|
|
|
return agi::make_unique<BSVideoProvider>(path, colormatrix, br);
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif /* WITH_BESTSOURCE */
|