/*** Olive - Non-Linear Video Editor Copyright (C) 2022 Olive Team 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 . ***/ #include "viewer.h" #include "config/config.h" #include "core.h" #include "node/traverser.h" namespace olive { const QString ViewerOutput::kVideoParamsInput = QStringLiteral("video_param_in"); const QString ViewerOutput::kAudioParamsInput = QStringLiteral("audio_param_in"); const QString ViewerOutput::kSubtitleParamsInput = QStringLiteral("subtitle_param_in"); const QString ViewerOutput::kTextureInput = QStringLiteral("tex_in"); const QString ViewerOutput::kSamplesInput = QStringLiteral("samples_in"); #define super Node ViewerOutput::ViewerOutput(bool create_buffer_inputs, bool create_default_streams) : last_length_(0), video_length_(0), audio_length_(0), autocache_input_video_(false), autocache_input_audio_(false), waveform_requests_enabled_(false) { AddInput(kVideoParamsInput, NodeValue::kVideoParams, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable | kInputFlagArray | kInputFlagHidden)); AddInput(kAudioParamsInput, NodeValue::kAudioParams, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable | kInputFlagArray | kInputFlagHidden)); AddInput(kSubtitleParamsInput, NodeValue::kSubtitleParams, InputFlags(kInputFlagNotConnectable | kInputFlagNotKeyframable | kInputFlagArray | kInputFlagHidden)); if (create_buffer_inputs) { AddInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); AddInput(kSamplesInput, NodeValue::kSamples, InputFlags(kInputFlagNotKeyframable)); } if (create_default_streams) { AddStream(Track::kVideo, QVariant()); AddStream(Track::kAudio, QVariant()); set_default_parameters(); } SetFlags(kDontShowInParamView); workarea_ = new TimelineWorkArea(this); markers_ = new TimelineMarkerList(this); } QString ViewerOutput::Name() const { return tr("Viewer"); } QString ViewerOutput::id() const { return QStringLiteral("org.olivevideoeditor.Olive.vieweroutput"); } QVector ViewerOutput::Category() const { return {kCategoryOutput}; } QString ViewerOutput::Description() const { return tr("Interface between a Viewer panel and the node system."); } QString ViewerOutput::duration() const { rational using_timebase; Timecode::Display using_display = Core::instance()->GetTimecodeDisplay(); // Get first enabled streams VideoParams video = GetFirstEnabledVideoStream(); AudioParams audio = GetFirstEnabledAudioStream(); SubtitleParams sub = GetFirstEnabledSubtitleStream(); if (video.is_valid() && video.video_type() != VideoParams::kVideoTypeStill) { // Prioritize video using_timebase = video.frame_rate_as_time_base(); } else if (audio.is_valid()) { // Use audio as a backup // If we're showing in a timecode, we prefer showing audio in seconds instead if (using_display == Timecode::kTimecodeDropFrame || using_display == Timecode::kTimecodeNonDropFrame) { using_display = Timecode::kTimecodeSeconds; } using_timebase = audio.sample_rate_as_time_base(); } else if (sub.is_valid()) { using_timebase = OLIVE_CONFIG("DefaultSequenceFrameRate").value(); } if (using_timebase.isNull()) { // No timebase, return null return QString(); } else { // Return time transformed to timecode return Timecode::time_to_timecode(GetLength(), using_timebase, using_display); } } QString ViewerOutput::rate() const { VideoParams video_stream; if (HasEnabledVideoStreams() && (video_stream = GetFirstEnabledVideoStream()).video_type() != VideoParams::kVideoTypeStill) { // This is a video editor, prioritize video streams return tr("%1 FPS").arg(video_stream.frame_rate().toDouble()); } else if (HasEnabledAudioStreams()) { // No video streams, return audio AudioParams audio_stream = GetFirstEnabledAudioStream(); return tr("%1 Hz").arg(audio_stream.sample_rate()); } return QString(); } bool ViewerOutput::HasEnabledVideoStreams() const { return GetFirstEnabledVideoStream().is_valid(); } bool ViewerOutput::HasEnabledAudioStreams() const { return GetFirstEnabledAudioStream().is_valid(); } bool ViewerOutput::HasEnabledSubtitleStreams() const { return GetFirstEnabledSubtitleStream().is_valid(); } VideoParams ViewerOutput::GetFirstEnabledVideoStream() const { int sz = GetVideoStreamCount(); for (int i=0; i(), static_cast(OLIVE_CONFIG("OfflinePixelFormat").toInt()), VideoParams::kInternalChannelCount, OLIVE_CONFIG("DefaultSequencePixelAspect").value(), OLIVE_CONFIG("DefaultSequenceInterlacing").value(), VideoParams::generate_auto_divider(width, height) )); SetAudioParams(AudioParams( OLIVE_CONFIG("DefaultSequenceAudioFrequency").toInt(), OLIVE_CONFIG("DefaultSequenceAudioLayout").toULongLong(), AudioParams::kInternalFormat )); } void ViewerOutput::InvalidateCache(const TimeRange& range, const QString& from, int element, InvalidateCacheOptions options) { Q_UNUSED(element) if (Node *connected = GetConnectedOutput(from, element)) { if (from == kTextureInput) { //connected->thumbnail_cache()->Request(range.Intersected(max_range), PlaybackCache::kPreviewsOnly); if (autocache_input_video_) { TimeRange max_range = InputTimeAdjustment(from, element, TimeRange(0, GetVideoLength()), false); connected->video_frame_cache()->Request(range.Intersected(max_range)); } } else if (from == kSamplesInput) { TimeRange max_range = InputTimeAdjustment(from, element, TimeRange(0, GetAudioLength()), false); if (waveform_requests_enabled_) { connected->waveform_cache()->Request(range.Intersected(max_range)); } if (autocache_input_audio_) { connected->audio_playback_cache()->Request(range.Intersected(max_range)); } } } VerifyLength(); super::InvalidateCache(range, from, element, options); } QVector ViewerOutput::GetEnabledStreamsAsReferences() const { QVector refs; { int vp_sz = GetVideoStreamCount(); for (int i=0; iwaveform_cache(), &AudioWaveformCache::Validated, this, &ViewerOutput::ConnectedWaveformChanged); } super::InputConnectedEvent(input, element, output); } void ViewerOutput::InputDisconnectedEvent(const QString &input, int element, Node *output) { if (input == kTextureInput) { emit TextureInputChanged(); } else if (input == kSamplesInput) { disconnect(output->waveform_cache(), &AudioWaveformCache::Validated, this, &ViewerOutput::ConnectedWaveformChanged); } super::InputDisconnectedEvent(input, element, output); } rational ViewerOutput::VerifyLengthInternal(Track::Type type) const { NodeTraverser traverser; switch (type) { case Track::kVideo: if (IsInputConnected(kTextureInput)) { NodeValueTable t = traverser.GenerateTable(GetConnectedOutput(kTextureInput), TimeRange(0, 0)); rational r = t.Get(NodeValue::kRational, QStringLiteral("length")).toRational(); if (!r.isNaN()) { return r; } } break; case Track::kAudio: if (IsInputConnected(kSamplesInput)) { NodeValueTable t = traverser.GenerateTable(GetConnectedOutput(kSamplesInput), TimeRange(0, 0)); rational r = t.Get(NodeValue::kRational, QStringLiteral("length")).toRational(); if (!r.isNaN()) { return r; } } break; case Track::kNone: case Track::kSubtitle: case Track::kCount: break; } return 0; } Node *ViewerOutput::GetConnectedTextureOutput() { return GetConnectedOutput(kTextureInput); } Node::ValueHint ViewerOutput::GetConnectedTextureValueHint() { return GetValueHintForInput(kTextureInput); } Node *ViewerOutput::GetConnectedSampleOutput() { return GetConnectedOutput(kSamplesInput); } Node::ValueHint ViewerOutput::GetConnectedSampleValueHint() { return GetValueHintForInput(kSamplesInput); } void ViewerOutput::SetWaveformEnabled(bool e) { if ((waveform_requests_enabled_ = e)) { if (Node *connected = this->GetConnectedSampleOutput()) { TimeRange max_range = InputTimeAdjustment(kSamplesInput, -1, TimeRange(0, GetAudioLength()), false); TimeRangeList invalid = connected->waveform_cache()->GetInvalidatedRanges(max_range); for (const TimeRange &r : invalid) { connected->waveform_cache()->Request(r); } } } } void ViewerOutput::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { if (HasInputWithID(kTextureInput)) { NodeValue repush = value[kTextureInput]; repush.set_tag(Track::Reference(Track::kVideo, 0).ToString()); table->Push(repush); } if (HasInputWithID(kSamplesInput)) { NodeValue repush = value[kSamplesInput]; repush.set_tag(Track::Reference(Track::kAudio, 0).ToString()); table->Push(value[kSamplesInput]); } } void ViewerOutput::InputValueChangedEvent(const QString &input, int element) { if (element == 0) { if (input == kVideoParamsInput) { VideoParams new_video_params = GetVideoParams(); bool size_changed = cached_video_params_.width() != new_video_params.width() || cached_video_params_.height() != new_video_params.height(); bool frame_rate_changed = cached_video_params_.frame_rate() != new_video_params.frame_rate(); bool pixel_aspect_changed = cached_video_params_.pixel_aspect_ratio() != new_video_params.pixel_aspect_ratio(); bool interlacing_changed = cached_video_params_.interlacing() != new_video_params.interlacing(); if (size_changed) { emit SizeChanged(new_video_params.width(), new_video_params.height()); } if (pixel_aspect_changed) { emit PixelAspectChanged(new_video_params.pixel_aspect_ratio()); } if (interlacing_changed) { emit InterlacingChanged(new_video_params.interlacing()); } if (frame_rate_changed) { emit FrameRateChanged(new_video_params.frame_rate()); } emit VideoParamsChanged(); cached_video_params_ = new_video_params; } else if (input == kAudioParamsInput) { AudioParams new_audio_params = GetAudioParams(); bool sample_rate_changed = new_audio_params.sample_rate() != cached_audio_params_.sample_rate(); if (sample_rate_changed) { emit SampleRateChanged(new_audio_params.sample_rate()); } emit AudioParamsChanged(); cached_audio_params_ = new_audio_params; } } super::InputValueChangedEvent(input, element); } void ViewerOutput::set_parameters_from_footage(const QVector footage) { foreach (ViewerOutput* f, footage) { QVector video_streams = f->GetEnabledVideoStreams(); QVector audio_streams = f->GetEnabledAudioStreams(); for (int i=0; i 0) { // Ignore still images past stream 0 continue; } using_timebase = GetVideoParams().time_base(); } else { using_timebase = s.frame_rate_as_time_base(); found_video_params = true; } SetVideoParams(VideoParams(s.width(), s.height(), using_timebase, static_cast(OLIVE_CONFIG("OfflinePixelFormat").toInt()), VideoParams::kInternalChannelCount, s.pixel_aspect_ratio(), s.interlacing(), VideoParams::generate_auto_divider(s.width(), s.height()))); if (found_video_params) { break; } } if (!audio_streams.isEmpty()) { const AudioParams& s = audio_streams.first(); SetAudioParams(AudioParams(s.sample_rate(), s.channel_layout(), AudioParams::kInternalFormat)); } } } 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; if (type == Track::kVideo) { id = kVideoParamsInput; } else if (type == Track::kAudio) { id = kAudioParamsInput; } else if (type == Track::kSubtitle) { id = kSubtitleParamsInput; } else { return -1; } // Add another video/audio param to the array for this stream int index = (index_in == -1) ? InputArraySize(id) : index_in; if (index >= InputArraySize(id)) { InputArrayResize(id, index+1); } SetStandardValue(id, value, index); return index; } QVector ViewerOutput::GetEnabledVideoStreams() const { QVector streams; int vp_sz = GetVideoStreamCount(); for (int i=0; i ViewerOutput::GetEnabledAudioStreams() const { QVector streams; int ap_sz = GetAudioStreamCount(); for (int i=0; i