Merge branch 'master' into hashremoval
This commit is contained in:
@@ -199,14 +199,14 @@ DecoderPtr Decoder::CreateFromID(const QString &id)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int64_t Decoder::GetTimeInTimebaseUnits(const rational &time, const rational &timebase, int64_t start_time, VideoParams::Interlacing interlacing)
|
||||
int64_t Decoder::GetTimeInTimebaseUnits(const rational &time, const rational &timebase, int64_t start_time)
|
||||
{
|
||||
int64_t t = Timecode::time_to_timestamp(time, timebase);
|
||||
t += start_time;
|
||||
return t;
|
||||
}
|
||||
|
||||
rational Decoder::GetTimestampInTimeUnits(int64_t time, const rational &timebase, int64_t start_time, VideoParams::Interlacing interlacing)
|
||||
rational Decoder::GetTimestampInTimeUnits(int64_t time, const rational &timebase, int64_t start_time)
|
||||
{
|
||||
time -= start_time;
|
||||
return Timecode::timestamp_to_time(time, timebase);
|
||||
|
||||
+3
-7
@@ -150,13 +150,9 @@ public:
|
||||
RetrieveVideoParams()
|
||||
{
|
||||
divider = 1;
|
||||
src_interlacing = VideoParams::kInterlaceNone;
|
||||
dst_interlacing = VideoParams::kInterlaceNone;
|
||||
}
|
||||
|
||||
int divider;
|
||||
VideoParams::Interlacing src_interlacing;
|
||||
VideoParams::Interlacing dst_interlacing;
|
||||
|
||||
void reset()
|
||||
{
|
||||
@@ -165,7 +161,7 @@ public:
|
||||
|
||||
bool operator==(const RetrieveVideoParams& rhs) const
|
||||
{
|
||||
return divider == rhs.divider && src_interlacing == rhs.src_interlacing && dst_interlacing == rhs.dst_interlacing;
|
||||
return divider == rhs.divider;
|
||||
}
|
||||
|
||||
bool operator!=(const RetrieveVideoParams& rhs) const
|
||||
@@ -295,8 +291,8 @@ protected:
|
||||
return stream_;
|
||||
}
|
||||
|
||||
static int64_t GetTimeInTimebaseUnits(const rational& time, const rational& timebase, int64_t start_time, VideoParams::Interlacing interlacing);
|
||||
static rational GetTimestampInTimeUnits(int64_t time, const rational& timebase, int64_t start_time, VideoParams::Interlacing interlacing);
|
||||
static int64_t GetTimeInTimebaseUnits(const rational& time, const rational& timebase, int64_t start_time);
|
||||
static rational GetTimestampInTimeUnits(int64_t time, const rational& timebase, int64_t start_time);
|
||||
|
||||
signals:
|
||||
/**
|
||||
|
||||
@@ -271,7 +271,6 @@ FootageDescription FFmpegDecoder::Probe(const QString &filename, const QAtomicIn
|
||||
frame);
|
||||
|
||||
compatible_pix_fmt = FFmpegUtils::GetCompatiblePixelFormat(static_cast<AVPixelFormat>(avstream->codecpar->format));
|
||||
qDebug() << "GOT IT FROM FRAME" << compatible_pix_fmt;
|
||||
}
|
||||
|
||||
// Read second frame
|
||||
@@ -654,7 +653,7 @@ void FFmpegDecoder::ClearFrameCache()
|
||||
|
||||
AVFramePtr FFmpegDecoder::RetrieveFrame(const rational& time, const QAtomicInt *cancelled)
|
||||
{
|
||||
int64_t target_ts = GetTimeInTimebaseUnits(time, instance_.avstream()->time_base, instance_.avstream()->start_time, filter_params_.src_interlacing);
|
||||
int64_t target_ts = GetTimeInTimebaseUnits(time, instance_.avstream()->time_base, instance_.avstream()->start_time);
|
||||
|
||||
const int64_t min_seek = -instance_.avstream()->start_time;
|
||||
int64_t seek_ts = target_ts;
|
||||
@@ -843,19 +842,6 @@ bool FFmpegDecoder::InitScaler(AVFrame *input, const RetrieveVideoParams& params
|
||||
// Link filters as necessary
|
||||
AVFilterContext *last_filter = buffersrc_ctx_;
|
||||
|
||||
// Add interlacing filter if necessary
|
||||
if (filter_params_.src_interlacing != VideoParams::kInterlaceNone) {
|
||||
// Footage is interlaced, our renderer works in progressive so we'll need to de-interlace
|
||||
AVFilterContext* interlace_filter;
|
||||
|
||||
snprintf(filter_args, kFilterArgSz, "mode=1:parity=%s",
|
||||
GetInterlacingModeInFFmpeg(filter_params_.src_interlacing));
|
||||
avfilter_graph_create_filter(&interlace_filter, avfilter_get_by_name("yadif"), "yadif", filter_args, nullptr, filter_graph_);
|
||||
|
||||
avfilter_link(last_filter, 0, interlace_filter, 0);
|
||||
last_filter = interlace_filter;
|
||||
}
|
||||
|
||||
// Add scale filter if necessary
|
||||
int dst_width, dst_height;
|
||||
if (filter_params_.divider > 1) {
|
||||
@@ -864,10 +850,9 @@ bool FFmpegDecoder::InitScaler(AVFrame *input, const RetrieveVideoParams& params
|
||||
dst_width = VideoParams::GetScaledDimension(src_width, filter_params_.divider);
|
||||
dst_height = VideoParams::GetScaledDimension(src_height, filter_params_.divider);
|
||||
|
||||
snprintf(filter_args, kFilterArgSz, "w=%d:h=%d:flags=fast_bilinear:interl=%d",
|
||||
snprintf(filter_args, kFilterArgSz, "w=%d:h=%d:flags=fast_bilinear:interl=-1",
|
||||
dst_width,
|
||||
dst_height,
|
||||
params.dst_interlacing != VideoParams::kInterlaceNone);
|
||||
dst_height);
|
||||
|
||||
avfilter_graph_create_filter(&scale_filter, avfilter_get_by_name("scale"), "scale", filter_args, nullptr, filter_graph_);
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
|
||||
#include "oiiodecoder.h"
|
||||
|
||||
#include <OpenImageIO/imagebufalgo.h>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
@@ -36,8 +35,7 @@ namespace olive {
|
||||
QStringList OIIODecoder::supported_formats_;
|
||||
|
||||
OIIODecoder::OIIODecoder() :
|
||||
image_(nullptr),
|
||||
buffer_(nullptr)
|
||||
image_(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -75,17 +73,11 @@ FootageDescription OIIODecoder::Probe(const QString &filename, const QAtomicInt*
|
||||
bool stream_enabled = true;
|
||||
|
||||
for (int i=0; in->seek_subimage(i, 0); i++) {
|
||||
VideoParams video_params;
|
||||
|
||||
OIIO::ImageSpec spec = in->spec();
|
||||
|
||||
VideoParams video_params = GetVideoParamsFromImageSpec(spec);
|
||||
|
||||
video_params.set_stream_index(i);
|
||||
video_params.set_width(spec.width);
|
||||
video_params.set_height(spec.height);
|
||||
video_params.set_format(OIIOUtils::GetFormatFromOIIOBasetype(static_cast<OIIO::TypeDesc::BASETYPE>(spec.format.basetype)));
|
||||
video_params.set_channel_count(spec.nchannels);
|
||||
video_params.set_pixel_aspect_ratio(OIIOUtils::GetPixelAspectRatioFromOIIO(spec));
|
||||
video_params.set_video_type(VideoParams::kVideoTypeStill);
|
||||
|
||||
if (i > 1) {
|
||||
// This is a multilayer image and this image might have an offset
|
||||
@@ -128,31 +120,40 @@ bool OIIODecoder::RetrieveVideoInternal(TexturePtr destination, const rational &
|
||||
Q_UNUSED(timecode)
|
||||
Q_UNUSED(cancelled)
|
||||
|
||||
VideoParams vp(buffer_->spec().width,
|
||||
buffer_->spec().height,
|
||||
pix_fmt_,
|
||||
channel_count_,
|
||||
OIIOUtils::GetPixelAspectRatioFromOIIO(buffer_->spec()),
|
||||
VideoParams::kInterlaceNone, // FIXME: Does OIIO deinterlace for us?
|
||||
params.divider);
|
||||
VideoParams vp = GetVideoParamsFromImageSpec(image_->spec());
|
||||
vp.set_divider(params.divider);
|
||||
|
||||
if (params.divider == 1) {
|
||||
if (!buffer_.is_allocated() || last_params_ != params) {
|
||||
last_params_ = params;
|
||||
|
||||
destination->Upload(buffer_->localpixels(), buffer_->scanline_stride() / vp.GetBytesPerPixel());
|
||||
buffer_.destroy();
|
||||
buffer_.set_video_params(vp);
|
||||
buffer_.allocate();
|
||||
|
||||
} else {
|
||||
if (params.divider == 1) {
|
||||
// Just upload straight to the buffer
|
||||
image_->read_image(oiio_pix_fmt_, buffer_.data(), OIIO::AutoStride, buffer_.linesize_bytes());
|
||||
} else {
|
||||
OIIO::ImageBuf buf(image_->spec());
|
||||
image_->read_image(image_->spec().format, buf.localpixels(), buf.pixel_stride(), buf.scanline_stride(), buf.z_stride());
|
||||
|
||||
// Will need to resize the image
|
||||
OIIO::ImageBuf dst(OIIO::ImageSpec(vp.effective_width(), vp.effective_height(), buffer_->spec().nchannels, buffer_->spec().format));
|
||||
// Roughly downsample image for divider (for some reason OIIO::ImageBufAlgo::resample failed here)
|
||||
int px_sz = vp.GetBytesPerPixel();
|
||||
for (int dst_y=0; dst_y<buffer_.height(); dst_y++) {
|
||||
int src_y = dst_y * buf.spec().height / buffer_.height();
|
||||
|
||||
if (!OIIO::ImageBufAlgo::resample(dst, *buffer_)) {
|
||||
qWarning() << "OIIO resize failed";
|
||||
for (int dst_x=0; dst_x<buffer_.width(); dst_x++) {
|
||||
int src_x = dst_x * buf.spec().width / buffer_.width();
|
||||
memcpy(buffer_.data() + buffer_.linesize_bytes() * dst_y + px_sz * dst_x,
|
||||
static_cast<uint8_t*>(buf.localpixels()) + buf.scanline_stride() * src_y + px_sz * src_x,
|
||||
px_sz);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
destination->Upload(dst.localpixels(), dst.scanline_stride() / vp.GetBytesPerPixel());
|
||||
|
||||
}
|
||||
|
||||
destination->Upload(buffer_.data(), buffer_.linesize_pixels());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -201,9 +202,6 @@ bool OIIODecoder::OpenImageHandler(const QString &fn, int subimage)
|
||||
// Check if we can work with this pixel format
|
||||
const OIIO::ImageSpec& spec = image_->spec();
|
||||
|
||||
// Store channel count
|
||||
channel_count_ = spec.nchannels;
|
||||
|
||||
// We use RGBA frames because that tends to be the native format of GPUs
|
||||
pix_fmt_ = OIIOUtils::GetFormatFromOIIOBasetype(static_cast<OIIO::TypeDesc::BASETYPE>(spec.format.basetype));
|
||||
|
||||
@@ -212,18 +210,13 @@ bool OIIODecoder::OpenImageHandler(const QString &fn, int subimage)
|
||||
return false;
|
||||
}
|
||||
|
||||
OIIO::TypeDesc::BASETYPE type = OIIOUtils::GetOIIOBaseTypeFromFormat(pix_fmt_);
|
||||
oiio_pix_fmt_ = OIIOUtils::GetOIIOBaseTypeFromFormat(pix_fmt_);
|
||||
|
||||
if (type == OIIO::TypeDesc::UNKNOWN) {
|
||||
if (oiio_pix_fmt_ == OIIO::TypeDesc::UNKNOWN) {
|
||||
qCritical() << "Failed to determine appropriate OIIO basetype from native format";
|
||||
return false;
|
||||
}
|
||||
|
||||
buffer_ = new OIIO::ImageBuf(OIIO::ImageSpec(spec.width, spec.height, spec.nchannels, type),
|
||||
OIIO::InitializePixels::No);
|
||||
|
||||
image_->read_image(type, buffer_->localpixels());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -234,10 +227,21 @@ void OIIODecoder::CloseImageHandle()
|
||||
image_ = nullptr;
|
||||
}
|
||||
|
||||
if (buffer_) {
|
||||
delete buffer_;
|
||||
buffer_ = nullptr;
|
||||
}
|
||||
buffer_.destroy();
|
||||
}
|
||||
|
||||
VideoParams OIIODecoder::GetVideoParamsFromImageSpec(const OIIO::ImageSpec &spec)
|
||||
{
|
||||
VideoParams video_params;
|
||||
|
||||
video_params.set_width(spec.width);
|
||||
video_params.set_height(spec.height);
|
||||
video_params.set_format(OIIOUtils::GetFormatFromOIIOBasetype(static_cast<OIIO::TypeDesc::BASETYPE>(spec.format.basetype)));
|
||||
video_params.set_channel_count(spec.nchannels);
|
||||
video_params.set_pixel_aspect_ratio(OIIOUtils::GetPixelAspectRatioFromOIIO(spec));
|
||||
video_params.set_video_type(VideoParams::kVideoTypeStill);
|
||||
|
||||
return video_params;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -56,11 +56,13 @@ private:
|
||||
|
||||
void CloseImageHandle();
|
||||
|
||||
static VideoParams GetVideoParamsFromImageSpec(const OIIO::ImageSpec &spec);
|
||||
|
||||
VideoParams::Format pix_fmt_;
|
||||
OIIO::TypeDesc::BASETYPE oiio_pix_fmt_;
|
||||
|
||||
int channel_count_;
|
||||
|
||||
OIIO::ImageBuf* buffer_;
|
||||
Frame buffer_;
|
||||
RetrieveVideoParams last_params_;
|
||||
|
||||
static QStringList supported_formats_;
|
||||
|
||||
|
||||
@@ -191,6 +191,13 @@ void SampleBuffer::transform_volume_for_sample_on_channel(int sample_index, int
|
||||
data_[channel][sample_index] *= volume;
|
||||
}
|
||||
|
||||
void SampleBuffer::clamp()
|
||||
{
|
||||
for (int i=0; i<channel_count(); i++) {
|
||||
clamp_channel(i);
|
||||
}
|
||||
}
|
||||
|
||||
void SampleBuffer::silence()
|
||||
{
|
||||
silence(0, sample_count_per_channel_);
|
||||
@@ -223,4 +230,34 @@ void SampleBuffer::set(int channel, const float *data, int sample_offset, int sa
|
||||
memcpy(&data_[channel].data()[sample_offset], data, sizeof(float) * sample_length);
|
||||
}
|
||||
|
||||
void SampleBuffer::clamp_channel(int channel)
|
||||
{
|
||||
const float min = -1.0f;
|
||||
const float max = 1.0f;
|
||||
|
||||
float *cdat = data_[channel].data();
|
||||
int unopt_start = 0;
|
||||
|
||||
#if defined(Q_PROCESSOR_X86) || defined(Q_PROCESSOR_ARM)
|
||||
__m128 min_sse = _mm_load1_ps(&min);
|
||||
__m128 max_sse = _mm_load1_ps(&max);
|
||||
|
||||
unopt_start = (sample_count_per_channel_ / 4) * 4;
|
||||
for (int j=0; j<unopt_start; j+=4) {
|
||||
float *here = cdat + j;
|
||||
__m128 samples = _mm_loadu_ps(here);
|
||||
|
||||
samples = _mm_max_ps(samples, min_sse);
|
||||
samples = _mm_min_ps(samples, max_sse);
|
||||
|
||||
_mm_storeu_ps(here, samples);
|
||||
}
|
||||
#endif
|
||||
|
||||
for (int sample=unopt_start; sample<sample_count(); sample++) {
|
||||
float &s = data(channel)[sample];
|
||||
s = std::clamp(s, min, max);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -71,6 +71,8 @@ public:
|
||||
return r;
|
||||
}
|
||||
|
||||
int channel_count() const { return data_.size(); }
|
||||
|
||||
bool is_allocated() const;
|
||||
void allocate();
|
||||
void destroy();
|
||||
@@ -82,6 +84,8 @@ public:
|
||||
void transform_volume_for_sample(int sample_index, float volume);
|
||||
void transform_volume_for_sample_on_channel(int sample_index, int channel, float volume);
|
||||
|
||||
void clamp();
|
||||
|
||||
void silence();
|
||||
void silence(int start_sample, int end_sample);
|
||||
void silence_bytes(int start_byte, int end_byte);
|
||||
@@ -93,6 +97,8 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
void clamp_channel(int channel);
|
||||
|
||||
AudioParams audio_params_;
|
||||
|
||||
int sample_count_per_channel_;
|
||||
|
||||
@@ -304,14 +304,24 @@ int64_t Timecode::time_to_timestamp(const double &time, const rational &timebase
|
||||
return 0;
|
||||
}
|
||||
|
||||
const double eps = 0.000000000001;
|
||||
|
||||
switch (floor) {
|
||||
case kRound:
|
||||
default:
|
||||
return qRound64(d);
|
||||
case kFloor:
|
||||
return qFloor(d);
|
||||
if (d > qCeil(d)-eps) {
|
||||
return qCeil(d);
|
||||
} else {
|
||||
return qFloor(d);
|
||||
}
|
||||
case kCeil:
|
||||
return qCeil(d);
|
||||
if (d < qFloor(d)+eps) {
|
||||
return qFloor(d);
|
||||
} else {
|
||||
return qCeil(d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -102,6 +102,7 @@ void Config::SetDefaults()
|
||||
SetEntryInternal(QStringLiteral("StopPlaybackOnLastFrame"), NodeValue::kBoolean, false);
|
||||
SetEntryInternal(QStringLiteral("UseLegacyColorInInputTab"), NodeValue::kBoolean, false);
|
||||
SetEntryInternal(QStringLiteral("ReassocLinToNonLin"), NodeValue::kBoolean, false);
|
||||
SetEntryInternal(QStringLiteral("PreviewNonFloatDontAskAgain"), NodeValue::kBoolean, false);
|
||||
|
||||
SetEntryInternal(QStringLiteral("AutoCacheDelay"), NodeValue::kInt, 1000);
|
||||
|
||||
|
||||
+9
-5
@@ -390,10 +390,12 @@ void Core::DialogProjectPropertiesShow()
|
||||
|
||||
void Core::DialogExportShow()
|
||||
{
|
||||
ViewerOutput* viewer = GetSequenceToExport();
|
||||
ViewerOutput* viewer;
|
||||
rational time;
|
||||
|
||||
if (viewer) {
|
||||
if (GetSequenceToExport(&viewer, &time)) {
|
||||
ExportDialog* ed = new ExportDialog(viewer, main_window_);
|
||||
ed->SetTime(time);
|
||||
connect(ed, &ExportDialog::finished, ed, &ExportDialog::deleteLater);
|
||||
ed->open();
|
||||
}
|
||||
@@ -857,7 +859,7 @@ void Core::SaveProjectInternal(Project* project, const QString& override_filenam
|
||||
psm->deleteLater();
|
||||
}
|
||||
|
||||
ViewerOutput* Core::GetSequenceToExport()
|
||||
bool Core::GetSequenceToExport(ViewerOutput **viewer, rational *time)
|
||||
{
|
||||
// First try the most recently focused time based window
|
||||
TimeBasedPanel* time_panel = PanelManager::instance()->MostRecentlyFocused<TimeBasedPanel>();
|
||||
@@ -875,7 +877,9 @@ ViewerOutput* Core::GetSequenceToExport()
|
||||
tr("This Sequence is empty. There is nothing to export."),
|
||||
QMessageBox::Ok);
|
||||
} else {
|
||||
return time_panel->GetConnectedViewer();
|
||||
*viewer = time_panel->GetConnectedViewer();
|
||||
*time = time_panel->GetTime();
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
QMessageBox::critical(main_window_,
|
||||
@@ -884,7 +888,7 @@ ViewerOutput* Core::GetSequenceToExport()
|
||||
QMessageBox::Ok);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
QString Core::GetAutoRecoveryIndexFilename()
|
||||
|
||||
+1
-1
@@ -545,7 +545,7 @@ private:
|
||||
/**
|
||||
* @brief Retrieves the currently most active sequence for exporting
|
||||
*/
|
||||
ViewerOutput *GetSequenceToExport();
|
||||
bool GetSequenceToExport(ViewerOutput **viewer, rational *time);
|
||||
|
||||
static QString GetAutoRecoveryIndexFilename();
|
||||
|
||||
|
||||
@@ -526,7 +526,7 @@ ExportParams ExportDialog::GenerateParams() const
|
||||
if (ExportCodec::IsCodecAStillImage(video_tab_->GetSelectedCodec()) && !video_tab_->IsImageSequenceSet()) {
|
||||
// Exporting as image without exporting image sequence, only export one frame
|
||||
rational export_time = video_tab_->GetStillImageTime();
|
||||
params.set_custom_range(TimeRange(export_time, export_time));
|
||||
params.set_custom_range(TimeRange(export_time, export_time + GetSelectedTimebase()));
|
||||
} else if (range_combobox_->currentIndex() == kRangeInToOut) {
|
||||
// Assume if this combobox is enabled, workarea is enabled - a check that we make in this dialog's constructor
|
||||
params.set_custom_range(viewer_node_->GetTimelinePoints()->workarea()->range());
|
||||
|
||||
@@ -46,6 +46,14 @@ public:
|
||||
|
||||
rational GetSelectedTimebase() const;
|
||||
|
||||
void SetTime(const rational &time)
|
||||
{
|
||||
preview_viewer_->SetAudioScrubbingEnabled(false);
|
||||
preview_viewer_->SetTime(time);
|
||||
video_tab_->SetTime(time);
|
||||
preview_viewer_->SetAudioScrubbingEnabled(true);
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void closeEvent(QCloseEvent *e) override;
|
||||
|
||||
|
||||
@@ -106,6 +106,29 @@ void SequenceDialog::accept()
|
||||
return;
|
||||
}
|
||||
|
||||
if (!VideoParams::FormatIsFloat(parameter_tab_->GetSelectedPreviewFormat())
|
||||
&& !OLIVE_CONFIG("PreviewNonFloatDontAskAgain").toBool()) {
|
||||
QMessageBox b(this);
|
||||
QCheckBox *dont_show_again_ = new QCheckBox(tr("Don't ask me again"));
|
||||
|
||||
b.setIcon(QMessageBox::Warning);
|
||||
b.setWindowTitle(tr("Low Quality Preview"));
|
||||
b.setText(tr("The preview resolution has been set to a non-float format. This may cause banding and clipping artifacts in the preview.\n\n"
|
||||
"Do you wish to continue?"));
|
||||
b.setCheckBox(dont_show_again_);
|
||||
|
||||
b.addButton(QMessageBox::Yes);
|
||||
b.addButton(QMessageBox::No);
|
||||
|
||||
if (b.exec() == QMessageBox::No) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (dont_show_again_->isChecked()) {
|
||||
OLIVE_CONFIG("PreviewNonFloatDontAskAgain") = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Generate video and audio parameter structs from data
|
||||
VideoParams video_params = VideoParams(parameter_tab_->GetSelectedVideoWidth(),
|
||||
parameter_tab_->GetSelectedVideoHeight(),
|
||||
|
||||
@@ -71,7 +71,7 @@ SequenceDialogParameterTab::SequenceDialogParameterTab(Sequence* sequence, QWidg
|
||||
preview_layout->addWidget(preview_resolution_label_, row, 2);
|
||||
row++;
|
||||
preview_layout->addWidget(new QLabel(tr("Quality:")), row, 0);
|
||||
preview_format_field_ = new PixelFormatComboBox(true);
|
||||
preview_format_field_ = new PixelFormatComboBox(false);
|
||||
preview_layout->addWidget(preview_format_field_, row, 1, 1, 2);
|
||||
row++;
|
||||
preview_layout->addWidget(new QLabel(tr("Auto-Cache:")), row, 0);
|
||||
|
||||
@@ -990,6 +990,8 @@ public:
|
||||
InputFlags GetInputFlags(const QString& input) const;
|
||||
void SetInputFlags(const QString &input, const InputFlags &f);
|
||||
|
||||
virtual void LoadFinishedEvent(){}
|
||||
|
||||
static void SetValueAtTime(const NodeInput &input, const rational &time, const QVariant &value, int track, MultiUndoCommand *command, bool insert_on_all_tracks_if_no_key);
|
||||
|
||||
static std::list<Node*> FindPath(Node *from, Node *to, int path_index = 0);
|
||||
|
||||
@@ -465,6 +465,11 @@ void ViewerOutput::set_parameters_from_footage(const QVector<ViewerOutput *> foo
|
||||
}
|
||||
|
||||
int ViewerOutput::AddStream(Track::Type type, const QVariant& value)
|
||||
{
|
||||
return SetStream(type, value, -1);
|
||||
}
|
||||
|
||||
int ViewerOutput::SetStream(Track::Type type, const QVariant &value, int index_in)
|
||||
{
|
||||
QString id;
|
||||
|
||||
@@ -479,8 +484,11 @@ int ViewerOutput::AddStream(Track::Type type, const QVariant& value)
|
||||
}
|
||||
|
||||
// Add another video/audio param to the array for this stream
|
||||
int index = InputArraySize(id);
|
||||
InputArrayAppend(id);
|
||||
int index = (index_in == -1) ? InputArraySize(id) : index_in;
|
||||
|
||||
if (index >= InputArraySize(id)) {
|
||||
InputArrayResize(id, index+1);
|
||||
}
|
||||
|
||||
SetStandardValue(id, value, index);
|
||||
|
||||
|
||||
@@ -211,6 +211,7 @@ protected:
|
||||
virtual void InputValueChangedEvent(const QString& input, int element) override;
|
||||
|
||||
int AddStream(Track::Type type, const QVariant &value);
|
||||
int SetStream(Track::Type type, const QVariant &value, int index);
|
||||
|
||||
private:
|
||||
rational last_length_;
|
||||
|
||||
@@ -43,6 +43,8 @@ const QString Footage::kLoopModeInput = QStringLiteral("loop_in");
|
||||
|
||||
Footage::Footage(const QString &filename) :
|
||||
ViewerOutput(false, false),
|
||||
timestamp_(0),
|
||||
valid_(false),
|
||||
cancelled_(nullptr)
|
||||
{
|
||||
SetCacheTextures(true);
|
||||
@@ -53,7 +55,9 @@ Footage::Footage(const QString &filename) :
|
||||
|
||||
Clear();
|
||||
|
||||
set_filename(filename);
|
||||
if (!filename.isEmpty()) {
|
||||
set_filename(filename);
|
||||
}
|
||||
|
||||
QTimer *check_timer = new QTimer(this);
|
||||
check_timer->setInterval(5000);
|
||||
@@ -76,59 +80,7 @@ void Footage::InputValueChangedEvent(const QString &input, int element)
|
||||
// Reset internal stream cache
|
||||
Clear();
|
||||
|
||||
// Determine if file still exists
|
||||
QFileInfo info(filename());
|
||||
|
||||
if (info.exists()) {
|
||||
// Grab timestamp
|
||||
set_timestamp(info.lastModified().toMSecsSinceEpoch());
|
||||
|
||||
// Determine if we've already cached the metadata of this file
|
||||
QString meta_cache_file = QDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)).filePath(FileFunctions::GetUniqueFileIdentifier(filename()));
|
||||
|
||||
FootageDescription footage_info;
|
||||
|
||||
// Try to load footage info from cache
|
||||
if (!QFileInfo::exists(meta_cache_file) || !footage_info.Load(meta_cache_file)) {
|
||||
|
||||
// Probe and create cache
|
||||
QVector<DecoderPtr> decoder_list = Decoder::ReceiveListOfAllDecoders();
|
||||
|
||||
foreach (DecoderPtr decoder, decoder_list) {
|
||||
footage_info = decoder->Probe(filename(), cancelled_);
|
||||
|
||||
if (footage_info.IsValid()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!footage_info.Save(meta_cache_file)) {
|
||||
qWarning() << "Failed to save stream cache, footage will have to be re-probed";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (footage_info.IsValid()) {
|
||||
decoder_ = footage_info.decoder();
|
||||
|
||||
for (int i=0; i<footage_info.GetVideoStreams().size(); i++) {
|
||||
AddStream(Track::kVideo, QVariant::fromValue(footage_info.GetVideoStreams().at(i)));
|
||||
}
|
||||
|
||||
for (int i=0; i<footage_info.GetAudioStreams().size(); i++) {
|
||||
AddStream(Track::kAudio, QVariant::fromValue(footage_info.GetAudioStreams().at(i)));
|
||||
}
|
||||
|
||||
for (int i=0; i<footage_info.GetSubtitleStreams().size(); i++) {
|
||||
AddStream(Track::kSubtitle, QVariant::fromValue(footage_info.GetSubtitleStreams().at(i)));
|
||||
}
|
||||
|
||||
SetValid();
|
||||
}
|
||||
|
||||
} else {
|
||||
set_timestamp(0);
|
||||
}
|
||||
Reprobe();
|
||||
} else {
|
||||
super::InputValueChangedEvent(input, element);
|
||||
}
|
||||
@@ -419,6 +371,13 @@ rational Footage::AdjustTimeByLoopMode(rational time, Footage::LoopMode loop_mod
|
||||
return time;
|
||||
}
|
||||
|
||||
void Footage::LoadFinishedEvent()
|
||||
{
|
||||
if (!filename().isEmpty()) {
|
||||
Reprobe();
|
||||
}
|
||||
}
|
||||
|
||||
qint64 Footage::creation_time() const
|
||||
{
|
||||
QFileInfo info(filename());
|
||||
@@ -482,6 +441,98 @@ void Footage::UpdateTooltip()
|
||||
}
|
||||
}
|
||||
|
||||
void Footage::Reprobe()
|
||||
{
|
||||
// Determine if file still exists
|
||||
QString filename = this->filename();
|
||||
|
||||
// In case of failure to load file, set timestamp to a value that will always be invalid so we
|
||||
// continuously reprobe
|
||||
set_timestamp(0);
|
||||
|
||||
if (!filename.isEmpty()) {
|
||||
QFileInfo info(filename);
|
||||
|
||||
if (info.exists()) {
|
||||
// Grab timestamp
|
||||
set_timestamp(info.lastModified().toMSecsSinceEpoch());
|
||||
|
||||
// Determine if we've already cached the metadata of this file
|
||||
QString meta_cache_file = QDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)).filePath(FileFunctions::GetUniqueFileIdentifier(filename));
|
||||
|
||||
FootageDescription footage_info;
|
||||
|
||||
// Try to load footage info from cache
|
||||
if (!QFileInfo::exists(meta_cache_file) || !footage_info.Load(meta_cache_file)) {
|
||||
|
||||
// Probe and create cache
|
||||
QVector<DecoderPtr> decoder_list = Decoder::ReceiveListOfAllDecoders();
|
||||
|
||||
foreach (DecoderPtr decoder, decoder_list) {
|
||||
footage_info = decoder->Probe(filename, cancelled_);
|
||||
|
||||
if (footage_info.IsValid()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!footage_info.Save(meta_cache_file)) {
|
||||
qWarning() << "Failed to save stream cache, footage will have to be re-probed";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (footage_info.IsValid()) {
|
||||
decoder_ = footage_info.decoder();
|
||||
|
||||
InputArrayResize(kVideoParamsInput, footage_info.GetVideoStreams().size());
|
||||
for (int i=0; i<footage_info.GetVideoStreams().size(); i++) {
|
||||
VideoParams video_stream = footage_info.GetVideoStreams().at(i);
|
||||
|
||||
if (i < InputArraySize(kVideoParamsInput)) {
|
||||
VideoParams existing = this->GetVideoParams(i);
|
||||
if (existing.is_valid()) {
|
||||
video_stream = MergeVideoStream(video_stream, existing);
|
||||
}
|
||||
}
|
||||
|
||||
SetStream(Track::kVideo, QVariant::fromValue(video_stream), i);
|
||||
}
|
||||
|
||||
InputArrayResize(kAudioParamsInput, footage_info.GetAudioStreams().size());
|
||||
for (int i=0; i<footage_info.GetAudioStreams().size(); i++) {
|
||||
SetStream(Track::kAudio, QVariant::fromValue(footage_info.GetAudioStreams().at(i)), i);
|
||||
}
|
||||
|
||||
InputArrayResize(kSubtitleParamsInput, footage_info.GetSubtitleStreams().size());
|
||||
for (int i=0; i<footage_info.GetSubtitleStreams().size(); i++) {
|
||||
SetStream(Track::kSubtitle, QVariant::fromValue(footage_info.GetSubtitleStreams().at(i)), i);
|
||||
}
|
||||
|
||||
SetValid();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VideoParams Footage::MergeVideoStream(const VideoParams &base, const VideoParams &over)
|
||||
{
|
||||
VideoParams merged = base;
|
||||
|
||||
merged.set_pixel_aspect_ratio(over.pixel_aspect_ratio());
|
||||
merged.set_interlacing(over.interlacing());
|
||||
merged.set_colorspace(over.colorspace());
|
||||
merged.set_premultiplied_alpha(over.premultiplied_alpha());
|
||||
if (merged.video_type() == VideoParams::kVideoTypeImageSequence && over.video_type() == VideoParams::kVideoTypeImageSequence) {
|
||||
merged.set_start_time(over.start_time());
|
||||
merged.set_duration(over.duration());
|
||||
merged.set_frame_rate(over.frame_rate());
|
||||
}
|
||||
|
||||
return merged;
|
||||
}
|
||||
|
||||
void Footage::CheckFootage()
|
||||
{
|
||||
// Don't check files if not the active window
|
||||
@@ -495,7 +546,7 @@ void Footage::CheckFootage()
|
||||
|
||||
if (current_file_timestamp != timestamp()) {
|
||||
// File has changed!
|
||||
set_timestamp(current_file_timestamp);
|
||||
Reprobe();
|
||||
InvalidateAll(kFilenameInput);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,6 +185,8 @@ public:
|
||||
|
||||
static rational AdjustTimeByLoopMode(rational time, LoopMode loop_mode, const rational& length, VideoParams::Type type, const rational &timebase);
|
||||
|
||||
virtual void LoadFinishedEvent() override;
|
||||
|
||||
virtual qint64 creation_time() const override;
|
||||
virtual qint64 mod_time() const override;
|
||||
|
||||
@@ -215,6 +217,10 @@ private:
|
||||
*/
|
||||
void UpdateTooltip();
|
||||
|
||||
void Reprobe();
|
||||
|
||||
VideoParams MergeVideoStream(const VideoParams &base, const VideoParams &over);
|
||||
|
||||
/**
|
||||
* @brief Internal timestamp object
|
||||
*/
|
||||
|
||||
@@ -243,6 +243,8 @@ void ProjectSerializer210528::LoadNode(Node *node, XMLNodeData &xml_node_data, Q
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
node->LoadFinishedEvent();
|
||||
}
|
||||
|
||||
void ProjectSerializer210528::LoadInput(Node *node, QXmlStreamReader *reader, XMLNodeData &xml_node_data) const
|
||||
|
||||
@@ -240,6 +240,8 @@ void ProjectSerializer210907::LoadNode(Node *node, XMLNodeData &xml_node_data, Q
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
node->LoadFinishedEvent();
|
||||
}
|
||||
|
||||
void ProjectSerializer210907::LoadInput(Node *node, QXmlStreamReader *reader, XMLNodeData &xml_node_data) const
|
||||
|
||||
@@ -290,6 +290,8 @@ void ProjectSerializer211228::LoadNode(Node *node, XMLNodeData &xml_node_data, Q
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
node->LoadFinishedEvent();
|
||||
}
|
||||
|
||||
void ProjectSerializer211228::LoadInput(Node *node, QXmlStreamReader *reader, XMLNodeData &xml_node_data) const
|
||||
|
||||
@@ -548,6 +548,8 @@ void ProjectSerializer220403::LoadNode(Node *node, XMLNodeData &xml_node_data, Q
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
node->LoadFinishedEvent();
|
||||
}
|
||||
|
||||
void ProjectSerializer220403::SaveNode(Node *node, QXmlStreamWriter *writer) const
|
||||
|
||||
@@ -783,12 +783,14 @@ void PreviewAutoCacher::SetViewerNode(ViewerOutput *viewer_node)
|
||||
if (!video_tasks_.isEmpty()) {
|
||||
// Cancel any video tasks and wait for them to finish
|
||||
CancelVideoTasks(true);
|
||||
video_tasks_.clear();
|
||||
}
|
||||
|
||||
// Handle audio rendering tasks
|
||||
if (!audio_tasks_.isEmpty()) {
|
||||
// Cancel any audio tasks and wait for them to finish
|
||||
CancelAudioTasks(true);
|
||||
audio_tasks_.clear();
|
||||
}
|
||||
|
||||
// Clear any single frame render that might be queued
|
||||
|
||||
@@ -215,11 +215,15 @@ void RenderProcessor::Run()
|
||||
ResolveJobs(sample_val, time);
|
||||
|
||||
SampleBuffer samples = sample_val.toSamples();
|
||||
if (samples.is_allocated() && ticket_->property("enablewaveforms").toBool()) {
|
||||
AudioVisualWaveform vis;
|
||||
vis.set_channel_count(samples.audio_params().channel_count());
|
||||
vis.OverwriteSamples(samples, samples.audio_params().sample_rate());
|
||||
ticket_->setProperty("waveform", QVariant::fromValue(vis));
|
||||
if (samples.is_allocated()) {
|
||||
samples.clamp();
|
||||
|
||||
if (ticket_->property("enablewaveforms").toBool()) {
|
||||
AudioVisualWaveform vis;
|
||||
vis.set_channel_count(samples.audio_params().channel_count());
|
||||
vis.OverwriteSamples(samples, samples.audio_params().sample_rate());
|
||||
ticket_->setProperty("waveform", QVariant::fromValue(vis));
|
||||
}
|
||||
}
|
||||
|
||||
if (HeardCancel()) {
|
||||
@@ -404,30 +408,30 @@ void RenderProcessor::ProcessVideoFootage(TexturePtr destination, const FootageJ
|
||||
|
||||
DecoderPtr decoder = nullptr;
|
||||
|
||||
if (stream_data.video_type() == VideoParams::kVideoTypeVideo) {
|
||||
switch (stream_data.video_type()) {
|
||||
case VideoParams::kVideoTypeVideo:
|
||||
case VideoParams::kVideoTypeStill:
|
||||
decoder = ResolveDecoderFromInput(decoder_id, default_codec_stream);
|
||||
} else {
|
||||
break;
|
||||
case VideoParams::kVideoTypeImageSequence:
|
||||
{
|
||||
// Since image sequences involve multiple files, we don't engage the decoder cache
|
||||
decoder = Decoder::CreateFromID(decoder_id);
|
||||
|
||||
QString frame_filename;
|
||||
|
||||
if (stream_data.video_type() == VideoParams::kVideoTypeImageSequence) {
|
||||
int64_t frame_number = stream_data.get_time_in_timebase_units(input_time);
|
||||
frame_filename = Decoder::TransformImageSequenceFileName(stream.filename(), frame_number);
|
||||
} else {
|
||||
frame_filename = stream.filename();
|
||||
}
|
||||
int64_t frame_number = stream_data.get_time_in_timebase_units(input_time);
|
||||
frame_filename = Decoder::TransformImageSequenceFileName(stream.filename(), frame_number);
|
||||
|
||||
// Decoder will close automatically since it's a stream_ptr
|
||||
decoder->Open(Decoder::CodecStream(frame_filename, stream_data.stream_index()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (decoder) {
|
||||
Decoder::RetrieveVideoParams p;
|
||||
p.divider = stream.video_params().divider();
|
||||
p.src_interlacing = stream_data.interlacing();
|
||||
p.dst_interlacing = GetCacheVideoParams().interlacing();
|
||||
|
||||
if (!IsCancelled()) {
|
||||
|
||||
|
||||
@@ -83,7 +83,10 @@ void NodeParamViewItem::Retranslate()
|
||||
|
||||
void NodeParamViewItem::RecreateBody()
|
||||
{
|
||||
delete body_;
|
||||
if (body_) {
|
||||
body_->setParent(nullptr);
|
||||
body_->deleteLater();
|
||||
}
|
||||
|
||||
body_ = new NodeParamViewItemBody(node_, create_checkboxes_);
|
||||
connect(body_, &NodeParamViewItemBody::RequestSelectNode, this, &NodeParamViewItem::RequestSelectNode);
|
||||
|
||||
@@ -68,7 +68,8 @@ ViewerWidget::ViewerWidget(QWidget *parent) :
|
||||
prequeuing_video_(false),
|
||||
prequeuing_audio_(0),
|
||||
record_armed_(false),
|
||||
recording_(false)
|
||||
recording_(false),
|
||||
enable_audio_scrubbing_(true)
|
||||
{
|
||||
// Set up main layout
|
||||
QVBoxLayout* layout = new QVBoxLayout(this);
|
||||
@@ -834,7 +835,7 @@ void ViewerWidget::PauseInternal()
|
||||
|
||||
void ViewerWidget::PushScrubbedAudio()
|
||||
{
|
||||
if (!IsPlaying() && GetConnectedNode() && OLIVE_CONFIG("AudioScrubbing").toBool()) {
|
||||
if (!IsPlaying() && GetConnectedNode() && OLIVE_CONFIG("AudioScrubbing").toBool() && enable_audio_scrubbing_) {
|
||||
// Get audio src device from renderer
|
||||
const AudioParams& params = GetConnectedNode()->audio_playback_cache()->GetParameters();
|
||||
|
||||
|
||||
@@ -89,6 +89,11 @@ public:
|
||||
|
||||
void StartCapture(TimelineWidget *source, const TimeRange &time, const Track::Reference &track);
|
||||
|
||||
void SetAudioScrubbingEnabled(bool e)
|
||||
{
|
||||
enable_audio_scrubbing_ = e;
|
||||
}
|
||||
|
||||
public slots:
|
||||
void Play(bool in_to_out_only);
|
||||
|
||||
@@ -276,6 +281,8 @@ private:
|
||||
|
||||
qint64 queue_starved_start_;
|
||||
|
||||
bool enable_audio_scrubbing_;
|
||||
|
||||
private slots:
|
||||
void PlaybackTimerUpdate();
|
||||
|
||||
|
||||
@@ -105,4 +105,24 @@ OLIVE_ADD_TEST(TimeRangeListFrameIteratorSize)
|
||||
OLIVE_TEST_END;
|
||||
}
|
||||
|
||||
OLIVE_ADD_TEST(TimeRangeListFrameIteratorSize2)
|
||||
{
|
||||
const rational timebase(1001, 30000);
|
||||
|
||||
TimeRangeList ranges;
|
||||
|
||||
ranges.insert(TimeRange(rational(247247, 30000), rational(31031, 3750))); // 1
|
||||
|
||||
TimeRange tr(rational(247247, 30000), rational(31031, 3750));
|
||||
|
||||
TimeRangeListFrameIterator iterator(ranges, timebase);
|
||||
|
||||
QVector<rational> vec = iterator.ToVector();
|
||||
|
||||
OLIVE_ASSERT_EQUAL(vec.size(), 1);
|
||||
OLIVE_ASSERT_EQUAL(iterator.size(), vec.size());
|
||||
|
||||
OLIVE_TEST_END;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user