Merge branch 'master' into nodeview-redux
This commit is contained in:
@@ -162,6 +162,28 @@ void AudioVisualWaveform::OverwriteSums(const AudioVisualWaveform &sums, const r
|
|||||||
length_ = qMax(length_, dest + length);
|
length_ = qMax(length_, dest + length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AudioVisualWaveform::OverwriteSilence(const rational &start, const rational &length)
|
||||||
|
{
|
||||||
|
for (auto it=mipmapped_data_.begin(); it!=mipmapped_data_.end(); it++) {
|
||||||
|
rational rate = it->first;
|
||||||
|
|
||||||
|
Sample& our_arr = it->second;
|
||||||
|
|
||||||
|
double rate_dbl = rate.toDouble();
|
||||||
|
|
||||||
|
// Get our destination sample
|
||||||
|
int our_start_index = time_to_samples(start, rate_dbl);
|
||||||
|
int our_length_index = time_to_samples(length, rate_dbl);
|
||||||
|
int our_end_index = our_start_index + our_length_index;
|
||||||
|
|
||||||
|
if (our_arr.size() < our_end_index) {
|
||||||
|
our_arr.resize(our_end_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(reinterpret_cast<char*>(our_arr.data()) + our_start_index, 0, our_length_index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void AudioVisualWaveform::Shift(const rational &from, const rational &to)
|
void AudioVisualWaveform::Shift(const rational &from, const rational &to)
|
||||||
{
|
{
|
||||||
for (auto it=mipmapped_data_.begin(); it!=mipmapped_data_.end(); it++) {
|
for (auto it=mipmapped_data_.begin(); it!=mipmapped_data_.end(); it++) {
|
||||||
@@ -184,9 +206,7 @@ void AudioVisualWaveform::Shift(const rational &from, const rational &to)
|
|||||||
// Shifting backwards <-
|
// Shifting backwards <-
|
||||||
int copy_sz = data.size() - from_index;
|
int copy_sz = data.size() - from_index;
|
||||||
|
|
||||||
for (int i=0; i<copy_sz; i++) {
|
memcpy(&data.data()[to_index], &data.data()[from_index], copy_sz * sizeof(SamplePerChannel));
|
||||||
data.replace(to_index + i, data.at(from_index + i));
|
|
||||||
}
|
|
||||||
|
|
||||||
data.resize(data.size() - (from_index - to_index));
|
data.resize(data.size() - (from_index - to_index));
|
||||||
} else {
|
} else {
|
||||||
@@ -199,9 +219,10 @@ void AudioVisualWaveform::Shift(const rational &from, const rational &to)
|
|||||||
|
|
||||||
int copy_sz = old_sz - from_index;
|
int copy_sz = old_sz - from_index;
|
||||||
|
|
||||||
for (int i=0; i<copy_sz; i++) {
|
// Copy to a temporary buffer first to prevent overwriting bytes we need to copy
|
||||||
data.replace(data.size() - i - 1, data.at(old_sz - i - 1));
|
QByteArray temp(copy_sz * sizeof(SamplePerChannel), Qt::Uninitialized);
|
||||||
}
|
memcpy(temp.data(), &data.data()[from_index], temp.size());
|
||||||
|
memcpy(&data.data()[to_index], temp.data(), temp.size());
|
||||||
|
|
||||||
memset(reinterpret_cast<char*>(&data[from_index]), 0, distance * sizeof(SamplePerChannel));
|
memset(reinterpret_cast<char*>(&data[from_index]), 0, distance * sizeof(SamplePerChannel));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,6 +88,8 @@ public:
|
|||||||
*/
|
*/
|
||||||
void OverwriteSums(const AudioVisualWaveform& sums, const rational& dest, const rational& offset = 0, const rational &length = 0);
|
void OverwriteSums(const AudioVisualWaveform& sums, const rational& dest, const rational& offset = 0, const rational &length = 0);
|
||||||
|
|
||||||
|
void OverwriteSilence(const rational &start, const rational &length);
|
||||||
|
|
||||||
void Shift(const rational& from, const rational& to);
|
void Shift(const rational& from, const rational& to);
|
||||||
|
|
||||||
Sample GetSummaryFromTime(const rational& start, const rational& length) const;
|
Sample GetSummaryFromTime(const rational& start, const rational& length) const;
|
||||||
|
|||||||
@@ -148,10 +148,14 @@ bool FFmpegDecoder::OpenInternal()
|
|||||||
|
|
||||||
FramePtr FFmpegDecoder::RetrieveVideoInternal(const rational &timecode, const RetrieveVideoParams ¶ms)
|
FramePtr FFmpegDecoder::RetrieveVideoInternal(const rational &timecode, const RetrieveVideoParams ¶ms)
|
||||||
{
|
{
|
||||||
|
if (!InitScaler(params)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
AVStream* s = instance_.avstream();
|
AVStream* s = instance_.avstream();
|
||||||
|
|
||||||
// Retrieve frame
|
// Retrieve frame
|
||||||
FFmpegFramePool::ElementPtr return_frame = RetrieveFrame(timecode, params);
|
FFmpegFramePool::ElementPtr return_frame = RetrieveFrame(timecode);
|
||||||
|
|
||||||
// We found the frame, we'll return a copy
|
// We found the frame, we'll return a copy
|
||||||
if (return_frame) {
|
if (return_frame) {
|
||||||
@@ -161,7 +165,7 @@ FramePtr FFmpegDecoder::RetrieveVideoInternal(const rational &timecode, const Re
|
|||||||
native_pix_fmt_,
|
native_pix_fmt_,
|
||||||
native_channel_count_,
|
native_channel_count_,
|
||||||
av_guess_sample_aspect_ratio(instance_.fmt_ctx(), s, nullptr), // May be incorrect,
|
av_guess_sample_aspect_ratio(instance_.fmt_ctx(), s, nullptr), // May be incorrect,
|
||||||
VideoParams::kInterlaceNone, // May be incorrect
|
VideoParams::kInterlaceNone,
|
||||||
filter_params_.divider));
|
filter_params_.divider));
|
||||||
copy->set_timestamp(timecode);
|
copy->set_timestamp(timecode);
|
||||||
copy->allocate();
|
copy->allocate();
|
||||||
@@ -182,13 +186,9 @@ void FFmpegDecoder::CloseInternal()
|
|||||||
instance_.Close();
|
instance_.Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
int FFmpegDecoder::GetFilteredFrame(AVPacket* packet, AVFrame* output_frame, const RetrieveVideoParams& params)
|
int FFmpegDecoder::GetFilteredFrame(AVPacket* packet, AVFrame* output_frame)
|
||||||
{
|
{
|
||||||
// Ensure scaler is correct for these parameters
|
// Ensure scaler is correct for these parameters
|
||||||
if (!InitScaler(params)) {
|
|
||||||
return AVERROR(EINVAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
AVFrame* working_frame = av_frame_alloc();
|
AVFrame* working_frame = av_frame_alloc();
|
||||||
@@ -642,15 +642,18 @@ void FFmpegDecoder::CacheFrameToDisk(AVFrame *f)
|
|||||||
|
|
||||||
void FFmpegDecoder::ClearFrameCache()
|
void FFmpegDecoder::ClearFrameCache()
|
||||||
{
|
{
|
||||||
cached_frames_.clear();
|
if (!cached_frames_.isEmpty()) {
|
||||||
cache_at_eof_ = false;
|
cached_frames_.clear();
|
||||||
cache_at_zero_ = false;
|
cache_at_eof_ = false;
|
||||||
|
cache_at_zero_ = false;
|
||||||
|
|
||||||
// Filter graph may rely on "continuous" video frames, so we free the scaler here
|
// Filter graph may rely on "continuous" video frames, so we free the scaler here
|
||||||
FreeScaler();
|
FreeScaler();
|
||||||
|
InitScaler(filter_params_);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FFmpegFramePool::ElementPtr FFmpegDecoder::RetrieveFrame(const rational& time, const RetrieveVideoParams ¶ms)
|
FFmpegFramePool::ElementPtr FFmpegDecoder::RetrieveFrame(const rational& time)
|
||||||
{
|
{
|
||||||
int64_t target_ts = GetTimeInTimebaseUnits(time, instance_.avstream()->time_base, instance_.avstream()->start_time);
|
int64_t target_ts = GetTimeInTimebaseUnits(time, instance_.avstream()->time_base, instance_.avstream()->start_time);
|
||||||
|
|
||||||
@@ -658,7 +661,7 @@ FFmpegFramePool::ElementPtr FFmpegDecoder::RetrieveFrame(const rational& time, c
|
|||||||
int64_t seek_ts = target_ts;
|
int64_t seek_ts = target_ts;
|
||||||
bool still_seeking = false;
|
bool still_seeking = false;
|
||||||
|
|
||||||
if (params.src_interlacing != VideoParams::kInterlaceNone) {
|
if (filter_params_.src_interlacing != VideoParams::kInterlaceNone) {
|
||||||
// If we are de-interlacing, the timebase is doubled because we get one frame per field, so we
|
// If we are de-interlacing, the timebase is doubled because we get one frame per field, so we
|
||||||
// double the target timestamp too
|
// double the target timestamp too
|
||||||
target_ts *= 2;
|
target_ts *= 2;
|
||||||
@@ -696,7 +699,7 @@ FFmpegFramePool::ElementPtr FFmpegDecoder::RetrieveFrame(const rational& time, c
|
|||||||
|
|
||||||
// Pull from the decoder
|
// Pull from the decoder
|
||||||
av_frame_unref(working_frame);
|
av_frame_unref(working_frame);
|
||||||
ret = GetFilteredFrame(pkt, working_frame, params);
|
ret = GetFilteredFrame(pkt, working_frame);
|
||||||
|
|
||||||
// Handle any errors that aren't EOF (EOF is handled later on)
|
// Handle any errors that aren't EOF (EOF is handled later on)
|
||||||
if (ret < 0 && ret != AVERROR_EOF) {
|
if (ret < 0 && ret != AVERROR_EOF) {
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ private:
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
int GetFilteredFrame(AVPacket *packet, AVFrame *frame, const RetrieveVideoParams ¶ms);
|
int GetFilteredFrame(AVPacket *packet, AVFrame *frame);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Handle an FFmpeg error code
|
* @brief Handle an FFmpeg error code
|
||||||
@@ -138,7 +138,7 @@ private:
|
|||||||
|
|
||||||
void ClearFrameCache();
|
void ClearFrameCache();
|
||||||
|
|
||||||
FFmpegFramePool::ElementPtr RetrieveFrame(const rational &time, const RetrieveVideoParams ¶ms);
|
FFmpegFramePool::ElementPtr RetrieveFrame(const rational &time);
|
||||||
|
|
||||||
void RemoveFirstFrame();
|
void RemoveFirstFrame();
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ namespace olive {
|
|||||||
|
|
||||||
class ExportSubtitlesTab : public QWidget
|
class ExportSubtitlesTab : public QWidget
|
||||||
{
|
{
|
||||||
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
ExportSubtitlesTab(QWidget *parent = nullptr);
|
ExportSubtitlesTab(QWidget *parent = nullptr);
|
||||||
|
|
||||||
|
|||||||
@@ -47,14 +47,14 @@ ShaderCode MathNodeBase::GetShaderCodeInternal(const QString &shader_id, const Q
|
|||||||
const QString& mat_in = (type_a == NodeValue::kTexture) ? param_b_in : param_a_in;
|
const QString& mat_in = (type_a == NodeValue::kTexture) ? param_b_in : param_a_in;
|
||||||
|
|
||||||
// No-op frag shader (can we return QString() instead?)
|
// No-op frag shader (can we return QString() instead?)
|
||||||
operation = QStringLiteral("texture(%1, ove_texcoord)").arg(tex_in);
|
operation = QStringLiteral("texture2D(%1, ove_texcoord)").arg(tex_in);
|
||||||
|
|
||||||
vert = QStringLiteral("uniform mat4 %1;\n"
|
vert = QStringLiteral("uniform mat4 %1;\n"
|
||||||
"\n"
|
"\n"
|
||||||
"in vec4 a_position;\n"
|
"attribute vec4 a_position;\n"
|
||||||
"in vec2 a_texcoord;\n"
|
"attribute vec2 a_texcoord;\n"
|
||||||
"\n"
|
"\n"
|
||||||
"out vec2 ove_texcoord;\n"
|
"varying vec2 ove_texcoord;\n"
|
||||||
"\n"
|
"\n"
|
||||||
"void main() {\n"
|
"void main() {\n"
|
||||||
" gl_Position = %1 * a_position;\n"
|
" gl_Position = %1 * a_position;\n"
|
||||||
@@ -96,12 +96,10 @@ ShaderCode MathNodeBase::GetShaderCodeInternal(const QString &shader_id, const Q
|
|||||||
frag = QStringLiteral("uniform %1 %3;\n"
|
frag = QStringLiteral("uniform %1 %3;\n"
|
||||||
"uniform %2 %4;\n"
|
"uniform %2 %4;\n"
|
||||||
"\n"
|
"\n"
|
||||||
"in vec2 ove_texcoord;\n"
|
"varying vec2 ove_texcoord;\n"
|
||||||
"\n"
|
|
||||||
"out vec4 fragColor;\n"
|
|
||||||
"\n"
|
"\n"
|
||||||
"void main(void) {\n"
|
"void main(void) {\n"
|
||||||
" fragColor = %5;\n"
|
" gl_FragColor = %5;\n"
|
||||||
"}\n").arg(GetShaderUniformType(type_a),
|
"}\n").arg(GetShaderUniformType(type_a),
|
||||||
GetShaderUniformType(type_b),
|
GetShaderUniformType(type_b),
|
||||||
param_a_in,
|
param_a_in,
|
||||||
@@ -128,7 +126,7 @@ QString MathNodeBase::GetShaderUniformType(const olive::NodeValue::Type &type)
|
|||||||
QString MathNodeBase::GetShaderVariableCall(const QString &input_id, const NodeValue::Type &type, const QString& coord_op)
|
QString MathNodeBase::GetShaderVariableCall(const QString &input_id, const NodeValue::Type &type, const QString& coord_op)
|
||||||
{
|
{
|
||||||
if (type == NodeValue::kTexture) {
|
if (type == NodeValue::kTexture) {
|
||||||
return QStringLiteral("texture(%1, ove_texcoord%2)").arg(input_id, coord_op);
|
return QStringLiteral("texture2D(%1, ove_texcoord%2)").arg(input_id, coord_op);
|
||||||
}
|
}
|
||||||
|
|
||||||
return input_id;
|
return input_id;
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ void AudioPlaybackCache::SetParameters(const AudioParams ¶ms)
|
|||||||
}
|
}
|
||||||
|
|
||||||
params_ = params;
|
params_ = params;
|
||||||
|
visual_.set_channel_count(params_.channel_count());
|
||||||
|
|
||||||
// Restart empty file so there's always "something" to play
|
// Restart empty file so there's always "something" to play
|
||||||
ClearPlaylist();
|
ClearPlaylist();
|
||||||
@@ -55,7 +56,7 @@ void AudioPlaybackCache::SetParameters(const AudioParams ¶ms)
|
|||||||
emit ParametersChanged();
|
emit ParametersChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioPlaybackCache::WritePCM(const TimeRange &range, SampleBufferPtr samples, const qint64 &job_time)
|
void AudioPlaybackCache::WritePCM(const TimeRange &range, SampleBufferPtr samples, const AudioVisualWaveform *waveform, const qint64 &job_time)
|
||||||
{
|
{
|
||||||
QList<TimeRange> valid_ranges = GetValidRanges(range, job_time);
|
QList<TimeRange> valid_ranges = GetValidRanges(range, job_time);
|
||||||
if (valid_ranges.isEmpty()) {
|
if (valid_ranges.isEmpty()) {
|
||||||
@@ -83,6 +84,7 @@ void AudioPlaybackCache::WritePCM(const TimeRange &range, SampleBufferPtr sample
|
|||||||
foreach (const TimeRange& r, valid_ranges) {
|
foreach (const TimeRange& r, valid_ranges) {
|
||||||
rational this_segment_in = 0;
|
rational this_segment_in = 0;
|
||||||
|
|
||||||
|
// Write PCM to playlist
|
||||||
for (auto it=playlist_.begin(); it!=playlist_.end(); it++) {
|
for (auto it=playlist_.begin(); it!=playlist_.end(); it++) {
|
||||||
rational this_segment_out = this_segment_in + params_.bytes_to_time((*it).size());
|
rational this_segment_out = this_segment_in + params_.bytes_to_time((*it).size());
|
||||||
|
|
||||||
@@ -138,6 +140,13 @@ void AudioPlaybackCache::WritePCM(const TimeRange &range, SampleBufferPtr sample
|
|||||||
// Each segment is contiguous, so this out will be the next segment's in
|
// Each segment is contiguous, so this out will be the next segment's in
|
||||||
this_segment_in = this_segment_out;
|
this_segment_in = this_segment_out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write visual
|
||||||
|
if (waveform) {
|
||||||
|
visual_.OverwriteSums(*waveform, r.in(), r.in() - range.in(), r.length());
|
||||||
|
} else {
|
||||||
|
visual_.OverwriteSilence(r.in(), r.length());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (const TimeRange& v, ranges_we_validated) {
|
foreach (const TimeRange& v, ranges_we_validated) {
|
||||||
@@ -149,7 +158,7 @@ void AudioPlaybackCache::WriteSilence(const TimeRange &range, qint64 job_time)
|
|||||||
{
|
{
|
||||||
// WritePCM will automatically fill non-existent bytes with silence, so we just have to send
|
// WritePCM will automatically fill non-existent bytes with silence, so we just have to send
|
||||||
// it an empty sample buffer
|
// it an empty sample buffer
|
||||||
WritePCM(range, nullptr, job_time);
|
WritePCM(range, nullptr, nullptr, job_time);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioPlaybackCache::ShiftEvent(const rational &from_in_time, const rational &to_in_time)
|
void AudioPlaybackCache::ShiftEvent(const rational &from_in_time, const rational &to_in_time)
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
#ifndef AUDIOPLAYBACKCACHE_H
|
#ifndef AUDIOPLAYBACKCACHE_H
|
||||||
#define AUDIOPLAYBACKCACHE_H
|
#define AUDIOPLAYBACKCACHE_H
|
||||||
|
|
||||||
|
#include "audio/audiovisualwaveform.h"
|
||||||
#include "common/timerange.h"
|
#include "common/timerange.h"
|
||||||
#include "codec/samplebuffer.h"
|
#include "codec/samplebuffer.h"
|
||||||
#include "render/playbackcache.h"
|
#include "render/playbackcache.h"
|
||||||
@@ -65,7 +66,7 @@ public:
|
|||||||
|
|
||||||
void SetParameters(const AudioParams& params);
|
void SetParameters(const AudioParams& params);
|
||||||
|
|
||||||
void WritePCM(const TimeRange &range, SampleBufferPtr samples, const qint64& job_time);
|
void WritePCM(const TimeRange &range, SampleBufferPtr samples, const AudioVisualWaveform *waveform, const qint64& job_time);
|
||||||
|
|
||||||
void WriteSilence(const TimeRange &range, qint64 job_time);
|
void WriteSilence(const TimeRange &range, qint64 job_time);
|
||||||
|
|
||||||
@@ -181,6 +182,11 @@ public:
|
|||||||
*/
|
*/
|
||||||
PlaybackDevice* CreatePlaybackDevice(QObject *parent = nullptr) const;
|
PlaybackDevice* CreatePlaybackDevice(QObject *parent = nullptr) const;
|
||||||
|
|
||||||
|
const AudioVisualWaveform &visual() const
|
||||||
|
{
|
||||||
|
return visual_;
|
||||||
|
}
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void ParametersChanged();
|
void ParametersChanged();
|
||||||
|
|
||||||
@@ -212,6 +218,8 @@ private:
|
|||||||
|
|
||||||
AudioParams params_;
|
AudioParams params_;
|
||||||
|
|
||||||
|
AudioVisualWaveform visual_;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,9 +39,12 @@ namespace olive {
|
|||||||
|
|
||||||
QMutex FrameHashCache::currently_saving_frames_mutex_;
|
QMutex FrameHashCache::currently_saving_frames_mutex_;
|
||||||
QMap<QByteArray, FramePtr> FrameHashCache::currently_saving_frames_;
|
QMap<QByteArray, FramePtr> FrameHashCache::currently_saving_frames_;
|
||||||
|
const QString FrameHashCache::kCacheFormatExtension = QStringLiteral(".exr");
|
||||||
|
|
||||||
|
#define super PlaybackCache
|
||||||
|
|
||||||
FrameHashCache::FrameHashCache(QObject *parent) :
|
FrameHashCache::FrameHashCache(QObject *parent) :
|
||||||
PlaybackCache(parent)
|
super(parent)
|
||||||
{
|
{
|
||||||
if (DiskManager::instance()) {
|
if (DiskManager::instance()) {
|
||||||
connect(DiskManager::instance(), &DiskManager::DeletedFrame, this, &FrameHashCache::HashDeleted);
|
connect(DiskManager::instance(), &DiskManager::DeletedFrame, this, &FrameHashCache::HashDeleted);
|
||||||
@@ -419,11 +422,9 @@ QString FrameHashCache::CachePathName(const QByteArray& hash) const
|
|||||||
|
|
||||||
QString FrameHashCache::CachePathName(const QString &cache_path, const QByteArray &hash)
|
QString FrameHashCache::CachePathName(const QString &cache_path, const QByteArray &hash)
|
||||||
{
|
{
|
||||||
QString ext = GetFormatExtension();
|
|
||||||
|
|
||||||
QDir cache_dir(QDir(cache_path).filePath(QString(hash.left(1).toHex())));
|
QDir cache_dir(QDir(cache_path).filePath(QString(hash.left(1).toHex())));
|
||||||
|
|
||||||
QString filename = QStringLiteral("%1%2").arg(QString(hash.mid(1).toHex()), ext);
|
QString filename = QStringLiteral("%1%2").arg(QString(hash.mid(1).toHex()), kCacheFormatExtension);
|
||||||
|
|
||||||
// Register that in some way this hash has been accessed
|
// Register that in some way this hash has been accessed
|
||||||
QMetaObject::invokeMethod(DiskManager::instance(),
|
QMetaObject::invokeMethod(DiskManager::instance(),
|
||||||
|
|||||||
@@ -70,8 +70,6 @@ public:
|
|||||||
FramePtr LoadCacheFrame(const QByteArray& hash) const;
|
FramePtr LoadCacheFrame(const QByteArray& hash) const;
|
||||||
static FramePtr LoadCacheFrame(const QString& fn);
|
static FramePtr LoadCacheFrame(const QString& fn);
|
||||||
|
|
||||||
static QString GetFormatExtension();
|
|
||||||
|
|
||||||
static QVector<rational> GetFrameListFromTimeRange(TimeRangeList range_list, const rational& timebase);
|
static QVector<rational> GetFrameListFromTimeRange(TimeRangeList range_list, const rational& timebase);
|
||||||
QVector<rational> GetFrameListFromTimeRange(const TimeRangeList &range);
|
QVector<rational> GetFrameListFromTimeRange(const TimeRangeList &range);
|
||||||
QVector<rational> GetInvalidatedFrames();
|
QVector<rational> GetInvalidatedFrames();
|
||||||
@@ -94,6 +92,7 @@ private:
|
|||||||
|
|
||||||
static QMutex currently_saving_frames_mutex_;
|
static QMutex currently_saving_frames_mutex_;
|
||||||
static QMap<QByteArray, FramePtr> currently_saving_frames_;
|
static QMap<QByteArray, FramePtr> currently_saving_frames_;
|
||||||
|
static const QString kCacheFormatExtension;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void HashDeleted(const QString &s, const QByteArray& hash);
|
void HashDeleted(const QString &s, const QByteArray& hash);
|
||||||
|
|||||||
@@ -171,8 +171,11 @@ void PreviewAutoCacher::AudioRendered()
|
|||||||
if (watcher->HasResult()) {
|
if (watcher->HasResult()) {
|
||||||
const TimeRange &range = audio_tasks_.value(watcher);
|
const TimeRange &range = audio_tasks_.value(watcher);
|
||||||
|
|
||||||
|
AudioVisualWaveform waveform = watcher->GetTicket()->property("waveform").value<AudioVisualWaveform>();
|
||||||
|
|
||||||
viewer_node_->audio_playback_cache()->WritePCM(range,
|
viewer_node_->audio_playback_cache()->WritePCM(range,
|
||||||
watcher->Get().value<SampleBufferPtr>(),
|
watcher->Get().value<SampleBufferPtr>(),
|
||||||
|
&waveform,
|
||||||
watcher->GetTicket()->GetJobTime());
|
watcher->GetTicket()->GetJobTime());
|
||||||
|
|
||||||
bool pcm_is_usable = true;
|
bool pcm_is_usable = true;
|
||||||
@@ -506,7 +509,7 @@ void PreviewAutoCacher::TryRender()
|
|||||||
|
|
||||||
if (!invalidated_audio_.isEmpty()) {
|
if (!invalidated_audio_.isEmpty()) {
|
||||||
foreach (const TimeRange& range, invalidated_audio_) {
|
foreach (const TimeRange& range, invalidated_audio_) {
|
||||||
std::list<TimeRange> chunks = range.Split(2);
|
std::list<TimeRange> chunks = range.Split(30);
|
||||||
|
|
||||||
foreach (const TimeRange& r, chunks) {
|
foreach (const TimeRange& r, chunks) {
|
||||||
RenderTicketWatcher* watcher = new RenderTicketWatcher();
|
RenderTicketWatcher* watcher = new RenderTicketWatcher();
|
||||||
|
|||||||
@@ -173,7 +173,16 @@ void RenderProcessor::Run()
|
|||||||
table = GenerateTable(texture_output.node(), texture_output.output(), time);
|
table = GenerateTable(texture_output.node(), texture_output.output(), time);
|
||||||
}
|
}
|
||||||
|
|
||||||
ticket_->Finish(table.Get(NodeValue::kSamples));
|
QVariant sample_variant = table.Get(NodeValue::kSamples);
|
||||||
|
SampleBufferPtr samples = sample_variant.value<SampleBufferPtr>();
|
||||||
|
if (samples && 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
ticket_->Finish(sample_variant);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case RenderManager::kTypeVideoDownload:
|
case RenderManager::kTypeVideoDownload:
|
||||||
|
|||||||
+993
-209
File diff suppressed because it is too large
Load Diff
+3034
-2090
File diff suppressed because it is too large
Load Diff
+2483
-1771
File diff suppressed because it is too large
Load Diff
+1968
-933
File diff suppressed because it is too large
Load Diff
+7
-8
@@ -266,8 +266,7 @@ Length: %4</source>
|
|||||||
<message>
|
<message>
|
||||||
<location filename="../tool/tool.h" line="108"/>
|
<location filename="../tool/tool.h" line="108"/>
|
||||||
<source>Bars</source>
|
<source>Bars</source>
|
||||||
<translatorcomment>彩条</translatorcomment>
|
<translation type="unfinished">彩条</translation>
|
||||||
<translation type="unfinished"></translation>
|
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../tool/tool.h" line="110"/>
|
<location filename="../tool/tool.h" line="110"/>
|
||||||
@@ -282,8 +281,7 @@ Length: %4</source>
|
|||||||
<message>
|
<message>
|
||||||
<location filename="../tool/tool.h" line="114"/>
|
<location filename="../tool/tool.h" line="114"/>
|
||||||
<source>Tone</source>
|
<source>Tone</source>
|
||||||
<translatorcomment>音调</translatorcomment>
|
<translation type="unfinished">音调</translation>
|
||||||
<translation type="unfinished"></translation>
|
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../tool/tool.h" line="119"/>
|
<location filename="../tool/tool.h" line="119"/>
|
||||||
@@ -1425,7 +1423,8 @@ Make sure a sequence is loaded and it has a connected Viewer node.</source>
|
|||||||
<message>
|
<message>
|
||||||
<location filename="../dialog/export/exportvideotab.cpp" line="57"/>
|
<location filename="../dialog/export/exportvideotab.cpp" line="57"/>
|
||||||
<source>Basic</source>
|
<source>Basic</source>
|
||||||
<translation type="unfinished"></translation>
|
<translatorcomment>Would "基本" / "基本的" be more appropriate?</translatorcomment>
|
||||||
|
<translation type="unfinished">基本设置</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../dialog/export/exportvideotab.cpp" line="61"/>
|
<location filename="../dialog/export/exportvideotab.cpp" line="61"/>
|
||||||
@@ -2032,7 +2031,7 @@ Make sure a sequence is loaded and it has a connected Viewer node.</source>
|
|||||||
<message>
|
<message>
|
||||||
<location filename="../window/mainwindow/mainmenu.cpp" line="664"/>
|
<location filename="../window/mainwindow/mainmenu.cpp" line="664"/>
|
||||||
<source>Cache Entire Sequence</source>
|
<source>Cache Entire Sequence</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished">缓存整个序列</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../window/mainwindow/mainmenu.cpp" line="665"/>
|
<location filename="../window/mainwindow/mainmenu.cpp" line="665"/>
|
||||||
@@ -2112,7 +2111,7 @@ Make sure a sequence is loaded and it has a connected Viewer node.</source>
|
|||||||
<message>
|
<message>
|
||||||
<location filename="../window/mainwindow/mainmenu.cpp" line="685"/>
|
<location filename="../window/mainwindow/mainmenu.cpp" line="685"/>
|
||||||
<source>Enable Snapping</source>
|
<source>Enable Snapping</source>
|
||||||
<translation type="unfinished">开启边缘吸合/自动对齐</translation>
|
<translation type="unfinished">启用吸附</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="../window/mainwindow/mainmenu.cpp" line="686"/>
|
<location filename="../window/mainwindow/mainmenu.cpp" line="686"/>
|
||||||
@@ -4369,7 +4368,7 @@ What would you like to do with these clips?</source>
|
|||||||
<message>
|
<message>
|
||||||
<location filename="../widget/toolbar/toolbar.cpp" line="108"/>
|
<location filename="../widget/toolbar/toolbar.cpp" line="108"/>
|
||||||
<source>Toggle Snapping</source>
|
<source>Toggle Snapping</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished">切换吸附</translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
|
|||||||
@@ -1370,6 +1370,8 @@ public:
|
|||||||
|
|
||||||
Track* track = timeline_->GetTrackAt(track_index_);
|
Track* track = timeline_->GetTrackAt(track_index_);
|
||||||
|
|
||||||
|
track->BeginOperation();
|
||||||
|
|
||||||
bool append = (in_ >= track->track_length());
|
bool append = (in_ >= track->track_length());
|
||||||
|
|
||||||
// Check if the placement location is past the end of the timeline
|
// Check if the placement location is past the end of the timeline
|
||||||
@@ -1409,6 +1411,12 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
track->EndOperation();
|
||||||
|
|
||||||
|
if (ripple_remove_command_) {
|
||||||
|
track->Node::InvalidateCache(TimeRange(insert_->in(), insert_->out()), Track::kBlockInput);
|
||||||
|
}
|
||||||
|
|
||||||
for (int i=0; i<position_commands_.size(); i++) {
|
for (int i=0; i<position_commands_.size(); i++) {
|
||||||
position_commands_.at(i)->redo();
|
position_commands_.at(i)->redo();
|
||||||
}
|
}
|
||||||
@@ -1422,7 +1430,10 @@ public:
|
|||||||
|
|
||||||
Track* t = timeline_->GetTrackAt(track_index_);
|
Track* t = timeline_->GetTrackAt(track_index_);
|
||||||
|
|
||||||
|
TimeRange insert_range(insert_->in(), insert_->out());
|
||||||
|
|
||||||
// Firstly, remove our insert
|
// Firstly, remove our insert
|
||||||
|
t->BeginOperation();
|
||||||
t->RippleRemoveBlock(insert_);
|
t->RippleRemoveBlock(insert_);
|
||||||
|
|
||||||
if (ripple_remove_command_) {
|
if (ripple_remove_command_) {
|
||||||
@@ -1432,6 +1443,11 @@ public:
|
|||||||
t->RippleRemoveBlock(gap_);
|
t->RippleRemoveBlock(gap_);
|
||||||
gap_->setParent(&memory_manager_);
|
gap_->setParent(&memory_manager_);
|
||||||
}
|
}
|
||||||
|
t->EndOperation();
|
||||||
|
|
||||||
|
if (ripple_remove_command_) {
|
||||||
|
t->Node::InvalidateCache(insert_range, Track::kBlockInput);
|
||||||
|
}
|
||||||
|
|
||||||
// Remove tracks if we added them
|
// Remove tracks if we added them
|
||||||
for (int i=add_track_commands_.size()-1; i>=0; i--) {
|
for (int i=add_track_commands_.size()-1; i>=0; i--) {
|
||||||
|
|||||||
@@ -87,7 +87,11 @@ void ImportTool::DragEnter(TimelineViewMouseEvent *event)
|
|||||||
PrepGhosts(drag_start_.GetFrame() - parent()->SceneToTime(import_pre_buffer_),
|
PrepGhosts(drag_start_.GetFrame() - parent()->SceneToTime(import_pre_buffer_),
|
||||||
drag_start_.GetTrack().index());
|
drag_start_.GetTrack().index());
|
||||||
|
|
||||||
event->accept();
|
if (parent()->HasGhosts() || !parent()->GetConnectedNode()) {
|
||||||
|
event->accept();
|
||||||
|
} else {
|
||||||
|
event->ignore();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// FIXME: Implement dropping from file
|
// FIXME: Implement dropping from file
|
||||||
event->ignore();
|
event->ignore();
|
||||||
|
|||||||
@@ -60,8 +60,7 @@ void AudioWaveformView::SetViewer(AudioPlaybackCache *playback)
|
|||||||
pool_.clear();
|
pool_.clear();
|
||||||
pool_.waitForDone();
|
pool_.waitForDone();
|
||||||
|
|
||||||
disconnect(playback_, &AudioPlaybackCache::Validated, this, &AudioWaveformView::RenderRange);
|
disconnect(playback_, &AudioPlaybackCache::Validated, this, static_cast<void(AudioWaveformView::*)()>(&AudioWaveformView::update));
|
||||||
//disconnect(playback_, &AudioPlaybackCache::ParametersChanged, this, &AudioWaveformView::RenderRange);
|
|
||||||
|
|
||||||
SetTimebase(0);
|
SetTimebase(0);
|
||||||
}
|
}
|
||||||
@@ -69,14 +68,9 @@ void AudioWaveformView::SetViewer(AudioPlaybackCache *playback)
|
|||||||
playback_ = playback;
|
playback_ = playback;
|
||||||
|
|
||||||
if (playback_) {
|
if (playback_) {
|
||||||
connect(playback_, &AudioPlaybackCache::Validated, this, &AudioWaveformView::RenderRange);
|
connect(playback_, &AudioPlaybackCache::Validated, this, static_cast<void(AudioWaveformView::*)()>(&AudioWaveformView::update));
|
||||||
//connect(playback_, &AudioPlaybackCache::ParametersChanged, this, &AudioWaveformView::RenderRange);
|
|
||||||
|
|
||||||
SetTimebase(playback_->GetParameters().sample_rate_as_time_base());
|
SetTimebase(playback_->GetParameters().sample_rate_as_time_base());
|
||||||
|
|
||||||
waveform_.set_channel_count(playback_->GetParameters().channel_count());
|
|
||||||
|
|
||||||
RenderRange(TimeRange(0, playback_->GetLength()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,7 +95,7 @@ void AudioWaveformView::paintEvent(QPaintEvent *event)
|
|||||||
|
|
||||||
// Draw waveform
|
// Draw waveform
|
||||||
p.setPen(QColor(64, 255, 160)); // FIXME: Hardcoded color
|
p.setPen(QColor(64, 255, 160)); // FIXME: Hardcoded color
|
||||||
AudioVisualWaveform::DrawWaveform(&p, rect(), GetScale(), waveform_, SceneToTime(GetScroll()));
|
AudioVisualWaveform::DrawWaveform(&p, rect(), GetScale(), playback_->visual(), SceneToTime(GetScroll()));
|
||||||
|
|
||||||
// Draw playhead
|
// Draw playhead
|
||||||
p.setPen(PLAYHEAD_COLOR);
|
p.setPen(PLAYHEAD_COLOR);
|
||||||
@@ -110,42 +104,4 @@ void AudioWaveformView::paintEvent(QPaintEvent *event)
|
|||||||
p.drawLine(playhead_x, 0, playhead_x, height());
|
p.drawLine(playhead_x, 0, playhead_x, height());
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioWaveformView::RenderRange(TimeRange range)
|
|
||||||
{
|
|
||||||
// Limit range to length
|
|
||||||
range = TimeRange(qMax(rational(0), range.in()), qMin(playback_->GetLength(), range.out()));
|
|
||||||
|
|
||||||
// Floor to second increments
|
|
||||||
int64_t start = qFloor(range.in().toDouble());
|
|
||||||
int64_t end = qCeil(range.out().toDouble());
|
|
||||||
|
|
||||||
for (; start!=end; start++) {
|
|
||||||
TimeRange this_range(start, start+1);
|
|
||||||
|
|
||||||
QFutureWatcher<AudioVisualWaveform>* watcher = new QFutureWatcher<AudioVisualWaveform>();
|
|
||||||
connect(watcher, &QFutureWatcher<AudioVisualWaveform>::finished, this, &AudioWaveformView::BackgroundFinished);
|
|
||||||
|
|
||||||
jobs_.insert(this_range, watcher);
|
|
||||||
|
|
||||||
watcher->setFuture(QtConcurrent::run(&pool_, GenerateWaveform, playback_->CreatePlaybackDevice(), playback_->GetParameters(), this_range));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AudioWaveformView::BackgroundFinished()
|
|
||||||
{
|
|
||||||
QFutureWatcher<AudioVisualWaveform>* watcher = static_cast<QFutureWatcher<AudioVisualWaveform>*>(sender());
|
|
||||||
|
|
||||||
for (auto it=jobs_.begin(); it!=jobs_.end(); it++) {
|
|
||||||
if (it.value() == watcher) {
|
|
||||||
AudioVisualWaveform rendered = watcher->result();
|
|
||||||
waveform_.OverwriteSums(rendered, it.key().in());
|
|
||||||
jobs_.erase(it);
|
|
||||||
update();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
delete watcher;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,6 @@
|
|||||||
#include <QtConcurrent/QtConcurrent>
|
#include <QtConcurrent/QtConcurrent>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
#include "audio/audiovisualwaveform.h"
|
|
||||||
#include "render/audioparams.h"
|
#include "render/audioparams.h"
|
||||||
#include "render/audioplaybackcache.h"
|
#include "render/audioplaybackcache.h"
|
||||||
#include "widget/timeruler/seekablewidget.h"
|
#include "widget/timeruler/seekablewidget.h"
|
||||||
@@ -37,32 +36,16 @@ class AudioWaveformView : public SeekableWidget
|
|||||||
public:
|
public:
|
||||||
AudioWaveformView(QWidget* parent = nullptr);
|
AudioWaveformView(QWidget* parent = nullptr);
|
||||||
|
|
||||||
//void SetData(const QString& file, const AudioRenderingParams& params);
|
|
||||||
|
|
||||||
void SetViewer(AudioPlaybackCache *playback);
|
void SetViewer(AudioPlaybackCache *playback);
|
||||||
|
|
||||||
const AudioVisualWaveform* waveform() const
|
|
||||||
{
|
|
||||||
return &waveform_;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void paintEvent(QPaintEvent* event) override;
|
virtual void paintEvent(QPaintEvent* event) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void RenderRange(TimeRange range);
|
|
||||||
|
|
||||||
QThreadPool pool_;
|
QThreadPool pool_;
|
||||||
|
|
||||||
AudioPlaybackCache *playback_;
|
AudioPlaybackCache *playback_;
|
||||||
|
|
||||||
AudioVisualWaveform waveform_;
|
|
||||||
|
|
||||||
QHash<TimeRange, QFutureWatcher<AudioVisualWaveform>*> jobs_;
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void BackgroundFinished();
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -455,7 +455,7 @@ void ViewerWidget::StartAudioOutput()
|
|||||||
AudioManager::instance()->StartOutput(audio_cache,
|
AudioManager::instance()->StartOutput(audio_cache,
|
||||||
audio_cache->GetParameters().time_to_bytes(GetTime()),
|
audio_cache->GetParameters().time_to_bytes(GetTime()),
|
||||||
playback_speed_);
|
playback_speed_);
|
||||||
emit AudioManager::instance()->OutputWaveformStarted(waveform_view_->waveform(),
|
emit AudioManager::instance()->OutputWaveformStarted(&audio_cache->visual(),
|
||||||
GetTime(), playback_speed_);
|
GetTime(), playback_speed_);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -639,8 +639,7 @@ void MainMenu::Retranslate()
|
|||||||
|
|
||||||
// Edit menu
|
// Edit menu
|
||||||
edit_menu_->setTitle(tr("&Edit"));
|
edit_menu_->setTitle(tr("&Edit"));
|
||||||
//edit_undo_item_->setText(tr("&Undo")); FIXME: Does Qt translate these automatically?
|
Core::instance()->undo_stack()->UpdateActions(); // Update undo and redo
|
||||||
//edit_redo_item_->setText(tr("Redo"));
|
|
||||||
edit_delete2_item_->setText(tr("Delete (alt)"));
|
edit_delete2_item_->setText(tr("Delete (alt)"));
|
||||||
edit_insert_item_->setText(tr("Insert"));
|
edit_insert_item_->setText(tr("Insert"));
|
||||||
edit_overwrite_item_->setText(tr("Overwrite"));
|
edit_overwrite_item_->setText(tr("Overwrite"));
|
||||||
|
|||||||
Reference in New Issue
Block a user