Files
oak-editor/app/node/block/clip/clip.h
T
Mike-Solar c203ce1dbf fix: black preview when footage connects directly to a clip
ClipBlock::kBufferIn is declared as NodeValue::kNone with no value
hint, so when a footage node is connected straight to a clip's buffer
input (no effect node in between), the traverser has no type to look
up in the footage's value table and falls back to its last entry. A
footage pushes its video texture first and its audio samples last, so
a video clip ended up fed with audio samples, produced no texture and
the preview went silently black. With any node in between, the table
only carries the passthrough texture, which is why the bug only showed
on direct connections.

Make Node::GetValueHintForInput virtual and override it in ClipBlock
to prefer the value type matching the clip's track (kTexture on video
tracks, kSamples on audio tracks).

Regression tests cover the exact application render path
(PreviewAutoCacher -> RenderManager -> RenderWorkerPool ->
oak-render-worker) for both the direct and indirect cases, the hint
following the track type, and viewer display widgets. The direct-case
test reproduces the black frame without the fix and passes with it.
Full suite: 584 passed.
2026-07-16 01:06:24 +08:00

277 lines
6.1 KiB
C++

/***
Olive - Non-Linear Video Editor
Copyright (C) 2022 Olive Team
Modifications Copyright (C) 2025 mikesolar
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/
#ifndef CLIPBLOCK_H
#define CLIPBLOCK_H
#include "audio/audiovisualwaveform.h"
#include "codec/decoder.h"
#include "node/block/block.h"
#include "node/input/multicam/multicamnode.h"
#include "node/output/track/track.h"
namespace olive
{
class ViewerOutput;
/**
* @brief Node that represents a block of Media
*/
class ClipBlock : public Block {
Q_OBJECT
public:
ClipBlock();
NODE_DEFAULT_FUNCTIONS(ClipBlock)
virtual QString Name() const override;
virtual QString id() const override;
virtual QString Description() const override;
virtual void set_length_and_media_out(const rational &length) override;
virtual void set_length_and_media_in(const rational &length) override;
Track::Type GetTrackType() const
{
if (track()) {
return track()->type();
} else {
return Track::kNone;
}
}
virtual Node::ValueHint
GetValueHintForInput(const QString &input, int element = -1) const override;
rational media_in() const;
void set_media_in(const rational &media_in);
bool IsAutocaching() const
{
return GetStandardValue(kAutoCacheInput).toBool();
}
void SetAutocache(bool e);
void DiscardCache();
virtual void InvalidateCache(const TimeRange &range, const QString &from,
int element,
InvalidateCacheOptions options) override;
virtual TimeRange InputTimeAdjustment(const QString &input, int element,
const TimeRange &input_time,
bool clamp) const override;
virtual TimeRange
OutputTimeAdjustment(const QString &input, int element,
const TimeRange &input_time) const override;
virtual void Value(const NodeValueRow &value, const NodeGlobals &globals,
NodeValueTable *table) const override;
virtual void Retranslate() override;
void
RequestInvalidatedFromConnected(bool force_all = false,
const TimeRange &intersect = TimeRange());
double speed() const
{
return GetStandardValue(kSpeedInput).toDouble();
}
bool reverse() const
{
return GetStandardValue(kReverseInput).toBool();
}
void set_reverse(bool e)
{
SetStandardValue(kReverseInput, e);
}
bool maintain_audio_pitch() const
{
return GetStandardValue(kMaintainAudioPitchInput).toBool();
}
void set_maintain_audio_pitch(bool e)
{
SetStandardValue(kMaintainAudioPitchInput, e);
}
TransitionBlock *in_transition()
{
return in_transition_;
}
void set_in_transition(TransitionBlock *t)
{
in_transition_ = t;
}
TransitionBlock *out_transition()
{
return out_transition_;
}
void set_out_transition(TransitionBlock *t)
{
out_transition_ = t;
}
const QVector<Block *> &block_links() const
{
return block_links_;
}
FrameHashCache *connected_video_cache() const
{
if (Node *n = GetConnectedOutput(kBufferIn)) {
return n->video_frame_cache();
} else {
return nullptr;
}
}
AudioPlaybackCache *connected_audio_cache() const
{
if (Node *n = GetConnectedOutput(kBufferIn)) {
return n->audio_playback_cache();
} else {
return nullptr;
}
}
FrameHashCache *thumbnails()
{
if (Node *n = GetConnectedOutput(kBufferIn)) {
return n->thumbnail_cache();
} else {
return nullptr;
}
}
AudioWaveformCache *waveform()
{
if (Node *n = GetConnectedOutput(kBufferIn)) {
return n->waveform_cache();
} else {
return nullptr;
}
}
void AddCachePassthroughFrom(ClipBlock *other);
ViewerOutput *connected_viewer() const
{
return connected_viewer_;
}
virtual TimeRange GetVideoCacheRange() const override
{
return TimeRange(0, length());
}
virtual TimeRange GetAudioCacheRange() const override
{
return TimeRange(0, length());
}
virtual void ConnectedToPreviewEvent() override;
TimeRange media_range() const;
/**
* @brief Get currently set loop mode
*/
LoopMode loop_mode() const
{
return static_cast<LoopMode>(GetStandardValue(kLoopModeInput).toInt());
}
void set_loop_mode(LoopMode l)
{
SetStandardValue(kLoopModeInput, int(l));
}
MultiCamNode *FindMulticam();
static const QString kBufferIn;
static const QString kMediaInInput;
static const QString kSpeedInput;
static const QString kReverseInput;
static const QString kMaintainAudioPitchInput;
static const QString kLoopModeInput;
static const QString kAutoCacheInput;
protected:
virtual void LinkChangeEvent() override;
virtual void InputConnectedEvent(const QString &input, int element,
Node *output) override;
virtual void InputDisconnectedEvent(const QString &input, int element,
Node *output) override;
virtual void InputValueChangedEvent(const QString &input,
int element) override;
private:
enum SequenceToMediaTimeFlag {
kSTMNone = 0x0,
kSTMIgnoreReverse = 0x1,
kSTMIgnoreSpeed = 0x2,
kSTMIgnoreLoop = 0x4
};
rational SequenceToMediaTime(const rational &sequence_time,
uint64_t flags = kSTMNone) const;
rational MediaToSequenceTime(const rational &media_time) const;
void RequestRangeFromConnected(const TimeRange &range);
void RequestRangeForCache(PlaybackCache *cache, const TimeRange &max_range,
const TimeRange &range, bool invalidate,
bool request);
void RequestInvalidatedForCache(PlaybackCache *cache,
const TimeRange &max_range);
bool GetAdjustedThumbnailRange(TimeRange *r) const;
QVector<Block *> block_links_;
TransitionBlock *in_transition_;
TransitionBlock *out_transition_;
ViewerOutput *connected_viewer_;
private:
rational last_media_in_;
};
}
#endif // TIMELINEBLOCK_H