footage: merge video and image streams and improve image sequence import process
Image streams were initially separated from video streams, but they're now joined with a parameter defining if they're a still image, image sequence, or regular video. The image sequence import process has also improved so that if images from the same sequence are imported too, they'll either be ignored or the user won't be asked again for those if they should be an image sequence (fixes #1193). Also shifts decoder "probe" process to return an item, useful if the decoder returns a non-footage item.
This commit is contained in:
@@ -21,8 +21,6 @@ set(OLIVE_SOURCES
|
||||
project/item/footage/audiostream.cpp
|
||||
project/item/footage/footage.h
|
||||
project/item/footage/footage.cpp
|
||||
project/item/footage/imagestream.h
|
||||
project/item/footage/imagestream.cpp
|
||||
project/item/footage/stream.h
|
||||
project/item/footage/stream.cpp
|
||||
project/item/footage/videostream.h
|
||||
|
||||
@@ -96,4 +96,9 @@ void AudioStream::append_conformed_version(const AudioParams ¶ms)
|
||||
emit ConformAppended(params);
|
||||
}
|
||||
|
||||
QIcon AudioStream::icon() const
|
||||
{
|
||||
return icon::Audio;
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
@@ -53,6 +53,8 @@ public:
|
||||
bool has_conformed_version(const AudioParams& params);
|
||||
void append_conformed_version(const AudioParams& params);
|
||||
|
||||
virtual QIcon icon() const override;
|
||||
|
||||
signals:
|
||||
void ConformAppended(OLIVE_NAMESPACE::AudioParams params);
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ Footage::~Footage()
|
||||
|
||||
void Footage::Load(QXmlStreamReader *reader, XMLNodeData &xml_node_data, const QAtomicInt* cancelled)
|
||||
{
|
||||
/*
|
||||
QXmlStreamAttributes attributes = reader->attributes();
|
||||
|
||||
foreach (const QXmlStreamAttribute& attr, attributes) {
|
||||
@@ -110,6 +111,7 @@ void Footage::Load(QXmlStreamReader *reader, XMLNodeData &xml_node_data, const Q
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
void Footage::Save(QXmlStreamWriter *writer) const
|
||||
@@ -128,25 +130,18 @@ void Footage::Save(QXmlStreamWriter *writer) const
|
||||
writer->writeEndElement(); // footage
|
||||
}
|
||||
|
||||
const Footage::Status& Footage::status() const
|
||||
{
|
||||
return status_;
|
||||
}
|
||||
|
||||
void Footage::set_status(const Footage::Status &status)
|
||||
{
|
||||
status_ = status;
|
||||
|
||||
UpdateTooltip();
|
||||
}
|
||||
|
||||
void Footage::Clear()
|
||||
{
|
||||
// Clear all streams
|
||||
ClearStreams();
|
||||
|
||||
// Reset ready state
|
||||
set_status(kUnprobed);
|
||||
valid_ = false;
|
||||
}
|
||||
|
||||
void Footage::SetValid()
|
||||
{
|
||||
valid_ = true;
|
||||
}
|
||||
|
||||
const QString &Footage::filename() const
|
||||
@@ -210,34 +205,21 @@ void Footage::set_decoder(const QString &id)
|
||||
|
||||
QIcon Footage::icon()
|
||||
{
|
||||
switch (status_) {
|
||||
case kUnprobed:
|
||||
case kUnindexed:
|
||||
// FIXME Set a waiting icon
|
||||
return QIcon();
|
||||
case kReady:
|
||||
if (HasStreamsOfType(Stream::kVideo)) {
|
||||
if (valid_ && !streams_.isEmpty()) {
|
||||
StreamPtr first_stream = streams_.first();
|
||||
|
||||
// Prioritize the video icon
|
||||
return icon::Video;
|
||||
|
||||
} else if (HasStreamsOfType(Stream::kAudio)) {
|
||||
|
||||
// Otherwise assume it's audio only
|
||||
if (first_stream->type() == Stream::kVideo) {
|
||||
if (std::static_pointer_cast<VideoStream>(first_stream)->video_type() == VideoStream::kVideoTypeStill) {
|
||||
return icon::Image;
|
||||
} else {
|
||||
return icon::Video;
|
||||
}
|
||||
} else if (first_stream->type() == Stream::kAudio) {
|
||||
return icon::Audio;
|
||||
|
||||
} else if (HasStreamsOfType(Stream::kImage)) {
|
||||
|
||||
// Otherwise assume it's an image
|
||||
return icon::Image;
|
||||
|
||||
}
|
||||
/* fall-through */
|
||||
case kInvalid:
|
||||
return icon::Error;
|
||||
}
|
||||
|
||||
return QIcon();
|
||||
return icon::Error;
|
||||
}
|
||||
|
||||
QString Footage::duration()
|
||||
@@ -248,7 +230,7 @@ QString Footage::duration()
|
||||
rational longest;
|
||||
|
||||
foreach (StreamPtr stream, streams_) {
|
||||
if (stream->type() == Stream::kVideo || stream->type() == Stream::kAudio) {
|
||||
if (stream->enabled() && (stream->type() == Stream::kVideo || stream->type() == Stream::kAudio)) {
|
||||
rational this_stream_dur = Timecode::timestamp_to_time(stream->duration(),
|
||||
stream->timebase());
|
||||
|
||||
@@ -300,7 +282,8 @@ QString Footage::rate()
|
||||
return QString();
|
||||
}
|
||||
|
||||
if (HasStreamsOfType(Stream::kVideo)) {
|
||||
if (HasStreamsOfType(Stream::kVideo)
|
||||
&& std::static_pointer_cast<VideoStream>(get_first_stream_of_type(Stream::kVideo))->video_type() != VideoStream::kVideoTypeStill) {
|
||||
// This is a video editor, prioritize video streams
|
||||
VideoStreamPtr video_stream = std::static_pointer_cast<VideoStream>(get_first_stream_of_type(Stream::kVideo));
|
||||
return QCoreApplication::translate("Footage", "%1 FPS").arg(video_stream->frame_rate().toDouble());
|
||||
@@ -331,10 +314,6 @@ quint64 Footage::get_enabled_stream_flags() const
|
||||
|
||||
void Footage::ClearStreams()
|
||||
{
|
||||
if (streams_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Delete all streams
|
||||
streams_.clear();
|
||||
}
|
||||
@@ -343,7 +322,7 @@ bool Footage::HasStreamsOfType(const Stream::Type &type) const
|
||||
{
|
||||
// Return true if any streams are video streams
|
||||
foreach (StreamPtr stream, streams_) {
|
||||
if (stream->type() == type) {
|
||||
if (stream->enabled() && stream->type() == type) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -354,7 +333,7 @@ bool Footage::HasStreamsOfType(const Stream::Type &type) const
|
||||
StreamPtr Footage::get_first_stream_of_type(const Stream::Type &type) const
|
||||
{
|
||||
foreach (StreamPtr stream, streams_) {
|
||||
if (stream->type() == type) {
|
||||
if (stream->enabled() && stream->type() == type) {
|
||||
return stream;
|
||||
}
|
||||
}
|
||||
@@ -364,62 +343,21 @@ StreamPtr Footage::get_first_stream_of_type(const Stream::Type &type) const
|
||||
|
||||
void Footage::UpdateTooltip()
|
||||
{
|
||||
switch (status_) {
|
||||
case kUnprobed:
|
||||
set_tooltip(QCoreApplication::translate("Footage", "Waiting for probe"));
|
||||
break;
|
||||
case kUnindexed:
|
||||
set_tooltip(QCoreApplication::translate("Footage", "Waiting for index"));
|
||||
break;
|
||||
case kReady:
|
||||
{
|
||||
if (valid_) {
|
||||
QString tip = QCoreApplication::translate("Footage", "Filename: %1").arg(filename());
|
||||
|
||||
if (!streams_.isEmpty()) {
|
||||
tip.append("\n");
|
||||
|
||||
for (int i=0;i<streams_.size();i++) {
|
||||
|
||||
StreamPtr s = streams_.at(i);
|
||||
|
||||
switch (s->type()) {
|
||||
case Stream::kVideo:
|
||||
case Stream::kImage:
|
||||
{
|
||||
ImageStreamPtr vs = std::static_pointer_cast<ImageStream>(s);
|
||||
|
||||
tip.append(
|
||||
QCoreApplication::translate("Footage",
|
||||
"\nVideo %1: %2x%3").arg(QString::number(i),
|
||||
QString::number(vs->width()),
|
||||
QString::number(vs->height()))
|
||||
);
|
||||
break;
|
||||
}
|
||||
case Stream::kAudio:
|
||||
{
|
||||
AudioStreamPtr as = std::static_pointer_cast<AudioStream>(s);
|
||||
|
||||
tip.append(
|
||||
QCoreApplication::translate("Footage",
|
||||
"\nAudio %1: %2 channels %3 Hz").arg(QString::number(i),
|
||||
QString::number(as->channels()),
|
||||
QString::number(as->sample_rate()))
|
||||
);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
foreach (StreamPtr s, streams_) {
|
||||
if (s->enabled()) {
|
||||
tip.append("\n");
|
||||
tip.append(s->description());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
set_tooltip(tip);
|
||||
}
|
||||
break;
|
||||
case kInvalid:
|
||||
set_tooltip(QCoreApplication::translate("Footage", "An error occurred probing this footage"));
|
||||
break;
|
||||
} else {
|
||||
set_tooltip(QCoreApplication::translate("Footage", "This footage is not valid for use"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
#include "common/rational.h"
|
||||
#include "project/item/item.h"
|
||||
#include "project/item/footage/audiostream.h"
|
||||
#include "project/item/footage/imagestream.h"
|
||||
#include "project/item/footage/videostream.h"
|
||||
#include "timeline/timelinepoints.h"
|
||||
|
||||
@@ -43,13 +42,6 @@ OLIVE_NAMESPACE_ENTER
|
||||
class Footage : public Item, public TimelinePoints
|
||||
{
|
||||
public:
|
||||
enum Status {
|
||||
kUnprobed,
|
||||
kUnindexed,
|
||||
kReady,
|
||||
kInvalid
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Footage Constructor
|
||||
*/
|
||||
@@ -72,26 +64,6 @@ public:
|
||||
*/
|
||||
virtual void Save(QXmlStreamWriter *writer) const override;
|
||||
|
||||
/**
|
||||
* @brief Check the ready state of this Footage object
|
||||
*
|
||||
* @return
|
||||
*
|
||||
* If the Footage has been successfully probed, this will return TRUE.
|
||||
*/
|
||||
const Status& status() const;
|
||||
|
||||
/**
|
||||
* @brief Set ready state
|
||||
*
|
||||
* This should only be set by olive::ProbeMedia. Sets the Footage's current status to a member of enum
|
||||
* Footage::Status.
|
||||
*
|
||||
* This function also runs UpdateIcon() and UpdateTooltip(). If you need to override the tooltip (e.g. for an error
|
||||
* message), you must run set_tooltip() *after* running set_status();
|
||||
*/
|
||||
void set_status(const Status& status);
|
||||
|
||||
/**
|
||||
* @brief Reset Footage state ready for running through Probe() again
|
||||
*
|
||||
@@ -104,6 +76,16 @@ public:
|
||||
*/
|
||||
void Clear();
|
||||
|
||||
bool IsValid() const
|
||||
{
|
||||
return valid_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets this footage to valid and ready to use
|
||||
*/
|
||||
void SetValid();
|
||||
|
||||
/**
|
||||
* @brief Return the current filename of this Footage object
|
||||
*/
|
||||
@@ -254,16 +236,13 @@ private:
|
||||
*/
|
||||
QList<StreamPtr> streams_;
|
||||
|
||||
/**
|
||||
* @brief Internal ready setting
|
||||
*/
|
||||
Status status_;
|
||||
|
||||
/**
|
||||
* @brief Internal attached decoder ID
|
||||
*/
|
||||
QString decoder_;
|
||||
|
||||
bool valid_;
|
||||
|
||||
};
|
||||
|
||||
using FootagePtr = std::shared_ptr<Footage>;
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2019 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#include "imagestream.h"
|
||||
|
||||
#include "common/xmlutils.h"
|
||||
#include "footage.h"
|
||||
#include "project/project.h"
|
||||
#include "render/colormanager.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
ImageStream::ImageStream() :
|
||||
premultiplied_alpha_(false),
|
||||
interlacing_(VideoParams::kInterlaceNone),
|
||||
pixel_aspect_ratio_(1)
|
||||
{
|
||||
set_type(kImage);
|
||||
}
|
||||
|
||||
void ImageStream::FootageSetEvent(Footage *f)
|
||||
{
|
||||
// For some reason this connection fails if we don't explicitly specify DirectConnection
|
||||
connect(f->project()->color_manager(),
|
||||
&ColorManager::ConfigChanged,
|
||||
this,
|
||||
&ImageStream::ColorConfigChanged,
|
||||
Qt::DirectConnection);
|
||||
|
||||
connect(f->project()->color_manager(),
|
||||
&ColorManager::DefaultInputColorSpaceChanged,
|
||||
this,
|
||||
&ImageStream::DefaultColorSpaceChanged,
|
||||
Qt::DirectConnection);
|
||||
}
|
||||
|
||||
void ImageStream::LoadCustomParameters(QXmlStreamReader *reader)
|
||||
{
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (reader->name() == QStringLiteral("colorspace")) {
|
||||
set_colorspace(reader->readElementText());
|
||||
} else {
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ImageStream::SaveCustomParameters(QXmlStreamWriter *writer) const
|
||||
{
|
||||
writer->writeTextElement("colorspace", colorspace_);
|
||||
}
|
||||
|
||||
QString ImageStream::description() const
|
||||
{
|
||||
return QCoreApplication::translate("Stream", "%1: Image - %2x%3").arg(QString::number(index()),
|
||||
QString::number(width()),
|
||||
QString::number(height()));
|
||||
}
|
||||
|
||||
bool ImageStream::premultiplied_alpha() const
|
||||
{
|
||||
return premultiplied_alpha_;
|
||||
}
|
||||
|
||||
void ImageStream::set_premultiplied_alpha(bool e)
|
||||
{
|
||||
premultiplied_alpha_ = e;
|
||||
|
||||
emit ParametersChanged();
|
||||
}
|
||||
|
||||
const QString &ImageStream::colorspace(bool default_if_empty) const
|
||||
{
|
||||
if (colorspace_.isEmpty() && default_if_empty) {
|
||||
return footage()->project()->color_manager()->GetDefaultInputColorSpace();
|
||||
} else {
|
||||
return colorspace_;
|
||||
}
|
||||
}
|
||||
|
||||
void ImageStream::set_colorspace(const QString &color)
|
||||
{
|
||||
colorspace_ = color;
|
||||
|
||||
emit ParametersChanged();
|
||||
}
|
||||
|
||||
QString ImageStream::get_colorspace_match_string() const
|
||||
{
|
||||
return QStringLiteral("%1:%2").arg(footage()->project()->color_manager()->GetConfigFilename(),
|
||||
colorspace());
|
||||
}
|
||||
|
||||
void ImageStream::ColorConfigChanged()
|
||||
{
|
||||
ColorManager* color_manager = footage()->project()->color_manager();
|
||||
|
||||
// Check if this colorspace is in the new config
|
||||
if (!colorspace_.isEmpty()) {
|
||||
QStringList colorspaces = color_manager->ListAvailableColorspaces();
|
||||
if (!colorspaces.contains(colorspace_)) {
|
||||
// Set to empty if not
|
||||
colorspace_.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// Either way, the color calculation has likely changed so we signal here
|
||||
emit ParametersChanged();
|
||||
}
|
||||
|
||||
void ImageStream::DefaultColorSpaceChanged()
|
||||
{
|
||||
// If no colorspace is set, this stream uses the default color space and it's just changed
|
||||
if (colorspace_.isEmpty()) {
|
||||
emit ParametersChanged();
|
||||
}
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
@@ -1,137 +0,0 @@
|
||||
/***
|
||||
|
||||
Olive - Non-Linear Video Editor
|
||||
Copyright (C) 2019 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
***/
|
||||
|
||||
#ifndef IMAGESTREAM_H
|
||||
#define IMAGESTREAM_H
|
||||
|
||||
#include "render/pixelformat.h"
|
||||
#include "render/videoparams.h"
|
||||
#include "stream.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
/**
|
||||
* @brief A Stream derivative containing video-specific information
|
||||
*/
|
||||
class ImageStream : public Stream
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ImageStream();
|
||||
|
||||
virtual QString description() const override;
|
||||
|
||||
const int& width() const
|
||||
{
|
||||
return width_;
|
||||
}
|
||||
|
||||
void set_width(const int& width)
|
||||
{
|
||||
width_ = width;
|
||||
}
|
||||
|
||||
const int& height() const
|
||||
{
|
||||
return height_;
|
||||
}
|
||||
|
||||
void set_height(const int& height)
|
||||
{
|
||||
height_ = height;
|
||||
}
|
||||
|
||||
const PixelFormat::Format& format() const
|
||||
{
|
||||
return format_;
|
||||
}
|
||||
|
||||
void set_format(const PixelFormat::Format& format)
|
||||
{
|
||||
format_ = format;
|
||||
}
|
||||
|
||||
bool premultiplied_alpha() const;
|
||||
void set_premultiplied_alpha(bool e);
|
||||
|
||||
const QString& colorspace(bool default_if_empty = true) const;
|
||||
void set_colorspace(const QString& color);
|
||||
|
||||
QString get_colorspace_match_string() const;
|
||||
|
||||
VideoParams::Interlacing interlacing() const
|
||||
{
|
||||
return interlacing_;
|
||||
}
|
||||
|
||||
void set_interlacing(VideoParams::Interlacing i)
|
||||
{
|
||||
interlacing_ = i;
|
||||
|
||||
emit ParametersChanged();
|
||||
}
|
||||
|
||||
const rational& pixel_aspect_ratio() const
|
||||
{
|
||||
return pixel_aspect_ratio_;
|
||||
}
|
||||
|
||||
void set_pixel_aspect_ratio(const rational& r)
|
||||
{
|
||||
// Auto-correct null aspect ratio to 1:1
|
||||
if (r.isNull()) {
|
||||
pixel_aspect_ratio_ = 1;
|
||||
} else {
|
||||
pixel_aspect_ratio_ = r;
|
||||
}
|
||||
|
||||
emit ParametersChanged();
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void FootageSetEvent(Footage*) override;
|
||||
|
||||
virtual void LoadCustomParameters(QXmlStreamReader *reader) override;
|
||||
|
||||
virtual void SaveCustomParameters(QXmlStreamWriter* writer) const override;
|
||||
|
||||
private:
|
||||
int width_;
|
||||
int height_;
|
||||
bool premultiplied_alpha_;
|
||||
QString colorspace_;
|
||||
VideoParams::Interlacing interlacing_;
|
||||
|
||||
PixelFormat::Format format_;
|
||||
|
||||
rational pixel_aspect_ratio_;
|
||||
|
||||
private slots:
|
||||
void ColorConfigChanged();
|
||||
|
||||
void DefaultColorSpaceChanged();
|
||||
|
||||
};
|
||||
|
||||
using ImageStreamPtr = std::shared_ptr<ImageStream>;
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
#endif // IMAGESTREAM_H
|
||||
@@ -78,7 +78,6 @@ Footage *Stream::footage() const
|
||||
void Stream::set_footage(Footage *f)
|
||||
{
|
||||
footage_ = f;
|
||||
FootageSetEvent(footage_);
|
||||
}
|
||||
|
||||
const rational &Stream::timebase() const
|
||||
@@ -123,19 +122,8 @@ void Stream::set_enabled(bool e)
|
||||
enabled_ = e;
|
||||
}
|
||||
|
||||
QIcon Stream::IconFromType(const Stream::Type &type)
|
||||
QIcon Stream::icon() const
|
||||
{
|
||||
switch (type) {
|
||||
case Stream::kVideo:
|
||||
return icon::Video;
|
||||
case Stream::kImage:
|
||||
return icon::Image;
|
||||
case Stream::kAudio:
|
||||
return icon::Audio;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return QIcon();
|
||||
}
|
||||
|
||||
@@ -144,10 +132,6 @@ QMutex *Stream::proxy_access_lock()
|
||||
return &proxy_access_lock_;
|
||||
}
|
||||
|
||||
void Stream::FootageSetEvent(Footage*)
|
||||
{
|
||||
}
|
||||
|
||||
void Stream::LoadCustomParameters(QXmlStreamReader* reader)
|
||||
{
|
||||
reader->skipCurrentElement();
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include <QXmlStreamWriter>
|
||||
|
||||
#include "common/rational.h"
|
||||
#include "ui/icons/icons.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
@@ -52,8 +53,7 @@ public:
|
||||
kAudio,
|
||||
kData,
|
||||
kSubtitle,
|
||||
kAttachment,
|
||||
kImage = 100
|
||||
kAttachment
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -90,13 +90,11 @@ public:
|
||||
bool enabled() const;
|
||||
void set_enabled(bool e);
|
||||
|
||||
static QIcon IconFromType(const Type& type);
|
||||
virtual QIcon icon() const;
|
||||
|
||||
QMutex* proxy_access_lock();
|
||||
|
||||
protected:
|
||||
virtual void FootageSetEvent(Footage*);
|
||||
|
||||
virtual void LoadCustomParameters(QXmlStreamReader *reader);
|
||||
|
||||
virtual void SaveCustomParameters(QXmlStreamWriter* writer) const;
|
||||
|
||||
@@ -23,21 +23,35 @@
|
||||
#include <QFile>
|
||||
|
||||
#include "common/timecodefunctions.h"
|
||||
#include "common/xmlutils.h"
|
||||
#include "footage.h"
|
||||
#include "project/project.h"
|
||||
#include "render/colormanager.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
VideoStream::VideoStream() :
|
||||
premultiplied_alpha_(false),
|
||||
interlacing_(VideoParams::kInterlaceNone),
|
||||
video_type_(VideoStream::kVideoTypeVideo),
|
||||
pixel_aspect_ratio_(1),
|
||||
start_time_(0),
|
||||
is_image_sequence_(false)
|
||||
{
|
||||
set_type(kVideo);
|
||||
set_type(Stream::kVideo);
|
||||
}
|
||||
|
||||
QString VideoStream::description() const
|
||||
{
|
||||
return QCoreApplication::translate("Stream", "%1: Video - %2x%3").arg(QString::number(index()),
|
||||
QString::number(width()),
|
||||
QString::number(height()));
|
||||
if (video_type_ == VideoStream::kVideoTypeStill) {
|
||||
return QCoreApplication::translate("Stream", "%1: Image - %2x%3").arg(QString::number(index()),
|
||||
QString::number(width()),
|
||||
QString::number(height()));
|
||||
} else {
|
||||
return QCoreApplication::translate("Stream", "%1: Video - %2x%3").arg(QString::number(index()),
|
||||
QString::number(width()),
|
||||
QString::number(height()));
|
||||
}
|
||||
}
|
||||
|
||||
const rational &VideoStream::frame_rate() const
|
||||
@@ -76,125 +90,88 @@ int64_t VideoStream::get_time_in_timebase_units(const rational &time) const
|
||||
return Timecode::time_to_timestamp(time, timebase()) + start_time();
|
||||
}
|
||||
|
||||
/*
|
||||
int64_t VideoStream::get_closest_timestamp_in_frame_index(const rational &time)
|
||||
QIcon VideoStream::icon() const
|
||||
{
|
||||
// Get rough approximation of what the timestamp would be in this timebase
|
||||
int64_t target_ts = Timecode::time_to_timestamp(time, timebase());
|
||||
|
||||
// Find closest actual timebase in the file
|
||||
return get_closest_timestamp_in_frame_index(target_ts);
|
||||
if (video_type_ == kVideoTypeStill) {
|
||||
return icon::Image;
|
||||
} else {
|
||||
return icon::Video;
|
||||
}
|
||||
}
|
||||
|
||||
int64_t VideoStream::get_closest_timestamp_in_frame_index(int64_t timestamp)
|
||||
void VideoStream::LoadCustomParameters(QXmlStreamReader *reader)
|
||||
{
|
||||
QMutexLocker locker(proxy_access_lock());
|
||||
|
||||
if (!frame_index_.isEmpty()) {
|
||||
if (timestamp <= frame_index_.first()) {
|
||||
return frame_index_.first();
|
||||
} else if (timestamp >= frame_index_.last()) {
|
||||
return frame_index_.last();
|
||||
while (XMLReadNextStartElement(reader)) {
|
||||
if (reader->name() == QStringLiteral("colorspace")) {
|
||||
set_colorspace(reader->readElementText());
|
||||
} else {
|
||||
// Use index to find closest frame in file
|
||||
for (int i=1;i<frame_index_.size();i++) {
|
||||
int64_t this_ts = frame_index_.at(i);
|
||||
reader->skipCurrentElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this_ts == timestamp) {
|
||||
return timestamp;
|
||||
} else if (this_ts > timestamp) {
|
||||
return frame_index_.at(i - 1);
|
||||
}
|
||||
}
|
||||
void VideoStream::SaveCustomParameters(QXmlStreamWriter *writer) const
|
||||
{
|
||||
writer->writeTextElement("colorspace", colorspace_);
|
||||
}
|
||||
|
||||
bool VideoStream::premultiplied_alpha() const
|
||||
{
|
||||
return premultiplied_alpha_;
|
||||
}
|
||||
|
||||
void VideoStream::set_premultiplied_alpha(bool e)
|
||||
{
|
||||
premultiplied_alpha_ = e;
|
||||
|
||||
emit ParametersChanged();
|
||||
}
|
||||
|
||||
const QString &VideoStream::colorspace(bool default_if_empty) const
|
||||
{
|
||||
if (colorspace_.isEmpty() && default_if_empty) {
|
||||
return footage()->project()->color_manager()->GetDefaultInputColorSpace();
|
||||
} else {
|
||||
return colorspace_;
|
||||
}
|
||||
}
|
||||
|
||||
void VideoStream::set_colorspace(const QString &color)
|
||||
{
|
||||
colorspace_ = color;
|
||||
|
||||
emit ParametersChanged();
|
||||
}
|
||||
|
||||
QString VideoStream::get_colorspace_match_string() const
|
||||
{
|
||||
return QStringLiteral("%1:%2").arg(footage()->project()->color_manager()->GetConfigFilename(),
|
||||
colorspace());
|
||||
}
|
||||
|
||||
void VideoStream::ColorConfigChanged()
|
||||
{
|
||||
ColorManager* color_manager = footage()->project()->color_manager();
|
||||
|
||||
// Check if this colorspace is in the new config
|
||||
if (!colorspace_.isEmpty()) {
|
||||
QStringList colorspaces = color_manager->ListAvailableColorspaces();
|
||||
if (!colorspaces.contains(colorspace_)) {
|
||||
// Set to empty if not
|
||||
colorspace_.clear();
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
// Either way, the color calculation has likely changed so we signal here
|
||||
emit ParametersChanged();
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
void VideoStream::clear_frame_index()
|
||||
void VideoStream::DefaultColorSpaceChanged()
|
||||
{
|
||||
{
|
||||
QMutexLocker locker(&index_access_lock_);
|
||||
|
||||
frame_index_.clear();
|
||||
// If no colorspace is set, this stream uses the default color space and it's just changed
|
||||
if (colorspace_.isEmpty()) {
|
||||
emit ParametersChanged();
|
||||
}
|
||||
|
||||
emit IndexChanged();
|
||||
}
|
||||
|
||||
void VideoStream::append_frame_index(const int64_t &ts)
|
||||
{
|
||||
{
|
||||
QMutexLocker locker(&index_access_lock_);
|
||||
|
||||
frame_index_.append(ts);
|
||||
}
|
||||
|
||||
emit IndexChanged();
|
||||
}
|
||||
|
||||
bool VideoStream::is_frame_index_ready()
|
||||
{
|
||||
QMutexLocker locker(&index_access_lock_);
|
||||
|
||||
return !frame_index_.isEmpty() && frame_index_.last() == VideoStream::kEndTimestamp;
|
||||
}
|
||||
|
||||
int64_t VideoStream::last_frame_index_timestamp()
|
||||
{
|
||||
QMutexLocker locker(&index_access_lock_);
|
||||
|
||||
return frame_index_.last();
|
||||
}
|
||||
|
||||
bool VideoStream::load_frame_index(const QString &s)
|
||||
{
|
||||
// Load index from file
|
||||
QFile index_file(s);
|
||||
|
||||
if (index_file.exists() && index_file.open(QFile::ReadOnly)) {
|
||||
{
|
||||
QMutexLocker locker(&index_access_lock_);
|
||||
|
||||
// Resize based on filesize
|
||||
frame_index_.resize(static_cast<size_t>(index_file.size()) / sizeof(int64_t));
|
||||
|
||||
// Read frame index into vector
|
||||
index_file.read(reinterpret_cast<char*>(frame_index_.data()),
|
||||
index_file.size());
|
||||
}
|
||||
|
||||
index_file.close();
|
||||
|
||||
emit IndexChanged();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool VideoStream::save_frame_index(const QString &s)
|
||||
{
|
||||
QFile index_file(s);
|
||||
|
||||
if (index_file.open(QFile::WriteOnly)) {
|
||||
// Write index in binary
|
||||
QMutexLocker locker(&index_access_lock_);
|
||||
|
||||
index_file.write(reinterpret_cast<const char*>(frame_index_.constData()),
|
||||
frame_index_.size() * static_cast<int>(sizeof(int64_t)));
|
||||
|
||||
index_file.close();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
@@ -21,18 +21,106 @@
|
||||
#ifndef VIDEOSTREAM_H
|
||||
#define VIDEOSTREAM_H
|
||||
|
||||
#include "imagestream.h"
|
||||
#include "render/pixelformat.h"
|
||||
#include "render/videoparams.h"
|
||||
#include "stream.h"
|
||||
|
||||
OLIVE_NAMESPACE_ENTER
|
||||
|
||||
class VideoStream : public ImageStream
|
||||
/**
|
||||
* @brief A Stream derivative containing video-specific information
|
||||
*/
|
||||
class VideoStream : public Stream
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
VideoStream();
|
||||
|
||||
enum VideoType {
|
||||
kVideoTypeVideo,
|
||||
kVideoTypeStill,
|
||||
kVideoTypeImageSequence
|
||||
};
|
||||
|
||||
virtual QString description() const override;
|
||||
|
||||
VideoType video_type() const
|
||||
{
|
||||
return video_type_;
|
||||
}
|
||||
|
||||
void set_video_type(VideoType t)
|
||||
{
|
||||
video_type_ = t;
|
||||
}
|
||||
|
||||
const int& width() const
|
||||
{
|
||||
return width_;
|
||||
}
|
||||
|
||||
void set_width(const int& width)
|
||||
{
|
||||
width_ = width;
|
||||
}
|
||||
|
||||
const int& height() const
|
||||
{
|
||||
return height_;
|
||||
}
|
||||
|
||||
void set_height(const int& height)
|
||||
{
|
||||
height_ = height;
|
||||
}
|
||||
|
||||
const PixelFormat::Format& format() const
|
||||
{
|
||||
return format_;
|
||||
}
|
||||
|
||||
void set_format(const PixelFormat::Format& format)
|
||||
{
|
||||
format_ = format;
|
||||
}
|
||||
|
||||
bool premultiplied_alpha() const;
|
||||
void set_premultiplied_alpha(bool e);
|
||||
|
||||
const QString& colorspace(bool default_if_empty = true) const;
|
||||
void set_colorspace(const QString& color);
|
||||
|
||||
QString get_colorspace_match_string() const;
|
||||
|
||||
VideoParams::Interlacing interlacing() const
|
||||
{
|
||||
return interlacing_;
|
||||
}
|
||||
|
||||
void set_interlacing(VideoParams::Interlacing i)
|
||||
{
|
||||
interlacing_ = i;
|
||||
|
||||
emit ParametersChanged();
|
||||
}
|
||||
|
||||
const rational& pixel_aspect_ratio() const
|
||||
{
|
||||
return pixel_aspect_ratio_;
|
||||
}
|
||||
|
||||
void set_pixel_aspect_ratio(const rational& r)
|
||||
{
|
||||
// Auto-correct null aspect ratio to 1:1
|
||||
if (r.isNull()) {
|
||||
pixel_aspect_ratio_ = 1;
|
||||
} else {
|
||||
pixel_aspect_ratio_ = r;
|
||||
}
|
||||
|
||||
emit ParametersChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get this video stream's frame rate
|
||||
*
|
||||
@@ -49,23 +137,32 @@ public:
|
||||
|
||||
int64_t get_time_in_timebase_units(const rational& time) const;
|
||||
|
||||
/*
|
||||
int64_t get_closest_timestamp_in_frame_index(const rational& time);
|
||||
int64_t get_closest_timestamp_in_frame_index(int64_t timestamp);
|
||||
virtual QIcon icon() const override;
|
||||
|
||||
void clear_frame_index();
|
||||
void append_frame_index(const int64_t& ts);
|
||||
bool is_frame_index_ready();
|
||||
int64_t last_frame_index_timestamp();
|
||||
public slots:
|
||||
void ColorConfigChanged();
|
||||
|
||||
bool load_frame_index(const QString& s);
|
||||
bool save_frame_index(const QString& s);
|
||||
*/
|
||||
void DefaultColorSpaceChanged();
|
||||
|
||||
protected:
|
||||
virtual void LoadCustomParameters(QXmlStreamReader *reader) override;
|
||||
|
||||
virtual void SaveCustomParameters(QXmlStreamWriter* writer) const override;
|
||||
|
||||
private:
|
||||
rational frame_rate_;
|
||||
int width_;
|
||||
int height_;
|
||||
bool premultiplied_alpha_;
|
||||
QString colorspace_;
|
||||
VideoParams::Interlacing interlacing_;
|
||||
|
||||
//QVector<int64_t> frame_index_;
|
||||
VideoType video_type_;
|
||||
|
||||
PixelFormat::Format format_;
|
||||
|
||||
rational pixel_aspect_ratio_;
|
||||
|
||||
rational frame_rate_;
|
||||
|
||||
int64_t start_time_;
|
||||
|
||||
|
||||
@@ -278,33 +278,29 @@ void Sequence::set_parameters_from_footage(const QList<Footage *> footage)
|
||||
VideoStream* vs = static_cast<VideoStream*>(s.get());
|
||||
|
||||
// If this is a video stream, use these parameters
|
||||
if (!found_video_params && !vs->frame_rate().isNull()) {
|
||||
if (!found_video_params) {
|
||||
rational using_timebase;
|
||||
|
||||
if (vs->video_type() == VideoStream::kVideoTypeStill) {
|
||||
// If this is a still image, we'll use it's resolution but won't set
|
||||
// `found_video_params` in case something with a frame rate comes along which we'll
|
||||
// prioritize
|
||||
using_timebase = video_params().time_base();
|
||||
} else {
|
||||
using_timebase = vs->frame_rate().flipped();
|
||||
found_video_params = true;
|
||||
}
|
||||
|
||||
set_video_params(VideoParams(vs->width(),
|
||||
vs->height(),
|
||||
vs->frame_rate().flipped(),
|
||||
using_timebase,
|
||||
static_cast<PixelFormat::Format>(Config::Current()["DefaultSequencePreviewFormat"].toInt()),
|
||||
vs->pixel_aspect_ratio(),
|
||||
vs->interlacing(),
|
||||
VideoParams::generate_auto_divider(vs->width(), vs->height())));
|
||||
found_video_params = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Stream::kImage:
|
||||
if (!found_video_params) {
|
||||
// If this is an image stream, we'll use it's resolution but won't set `found_video_params` in case
|
||||
// something with a frame rate comes along which we'll prioritize
|
||||
ImageStream* is = static_cast<ImageStream*>(s.get());
|
||||
|
||||
set_video_params(VideoParams(is->width(),
|
||||
is->height(),
|
||||
video_params().time_base(),
|
||||
static_cast<PixelFormat::Format>(Config::Current()["DefaultSequencePreviewFormat"].toInt()),
|
||||
is->pixel_aspect_ratio(),
|
||||
is->interlacing(),
|
||||
VideoParams::generate_auto_divider(is->width(), is->height())));
|
||||
}
|
||||
break;
|
||||
case Stream::kAudio:
|
||||
if (!found_audio_params) {
|
||||
AudioStream* as = static_cast<AudioStream*>(s.get());
|
||||
|
||||
@@ -36,6 +36,11 @@ Project::Project() :
|
||||
autorecovery_saved_(true)
|
||||
{
|
||||
root_.set_project(this);
|
||||
|
||||
connect(&color_manager_, &ColorManager::ConfigChanged,
|
||||
this, &Project::ColorConfigChanged);
|
||||
connect(&color_manager_, &ColorManager::DefaultInputColorSpaceChanged,
|
||||
this, &Project::DefaultColorSpaceChanged);
|
||||
}
|
||||
|
||||
void Project::Load(QXmlStreamReader *reader, MainWindowLayoutInfo* layout, const QAtomicInt* cancelled)
|
||||
@@ -210,4 +215,30 @@ const QString &Project::cache_path(bool default_if_empty) const
|
||||
return cache_path_;
|
||||
}
|
||||
|
||||
void Project::ColorConfigChanged()
|
||||
{
|
||||
QList<ItemPtr> footage = this->get_items_of_type(Item::kFootage);
|
||||
|
||||
foreach (ItemPtr item, footage) {
|
||||
foreach (StreamPtr s, std::static_pointer_cast<Footage>(item)->streams()) {
|
||||
if (s->type() == Stream::kVideo) {
|
||||
std::static_pointer_cast<VideoStream>(s)->ColorConfigChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Project::DefaultColorSpaceChanged()
|
||||
{
|
||||
QList<ItemPtr> footage = this->get_items_of_type(Item::kFootage);
|
||||
|
||||
foreach (ItemPtr item, footage) {
|
||||
foreach (StreamPtr s, std::static_pointer_cast<Footage>(item)->streams()) {
|
||||
if (s->type() == Stream::kVideo) {
|
||||
std::static_pointer_cast<VideoStream>(s)->DefaultColorSpaceChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OLIVE_NAMESPACE_EXIT
|
||||
|
||||
@@ -96,6 +96,11 @@ private:
|
||||
|
||||
QString cache_path_;
|
||||
|
||||
private slots:
|
||||
void ColorConfigChanged();
|
||||
|
||||
void DefaultColorSpaceChanged();
|
||||
|
||||
};
|
||||
|
||||
using ProjectPtr = std::shared_ptr<Project>;
|
||||
|
||||
Reference in New Issue
Block a user