various: allow setting footage aspect ratios

We had support for detecting aspect ratios and respecting them in the
render, but this allows people to not only see the aspect ratio in use,
but also override it with their own.
This commit is contained in:
itsmattkc
2020-07-25 00:58:01 +10:00
parent dca4c6d960
commit fb05594b50
14 changed files with 327 additions and 92 deletions
+7 -13
View File
@@ -114,8 +114,6 @@ bool FFmpegDecoder::Open()
native_pix_fmt_ = GetNativePixelFormat(ideal_pix_fmt_);
Q_ASSERT(native_pix_fmt_ != PixelFormat::PIX_FMT_INVALID);
aspect_ratio_ = our_instance->sample_aspect_ratio();
}
time_base_ = our_instance->stream()->time_base;
@@ -465,6 +463,7 @@ bool FFmpegDecoder::Probe(Footage *f, const QAtomicInt* cancelled)
bool image_is_still = false;
ImageStream::Interlacing interlacing = ImageStream::kInterlaceNone;
rational pixel_aspect_ratio;
{
// Read at least two frames to get more information about this video stream
@@ -484,6 +483,10 @@ bool FFmpegDecoder::Probe(Footage *f, const QAtomicInt* cancelled)
interlacing = ImageStream::kInterlacedBottomFirst;
}
}
pixel_aspect_ratio = av_guess_sample_aspect_ratio(instance.fmt_ctx(),
instance.stream(),
frame);
}
// Read second frame
@@ -527,6 +530,7 @@ bool FFmpegDecoder::Probe(Footage *f, const QAtomicInt* cancelled)
image_stream->set_height(avstream->codecpar->height);
image_stream->set_format(GetNativePixelFormat(FFmpegCommon::GetCompatiblePixelFormat(static_cast<AVPixelFormat>(avstream->codecpar->format))));
image_stream->set_interlacing(interlacing);
image_stream->set_pixel_aspect_ratio(pixel_aspect_ratio);
str = image_stream;
@@ -825,7 +829,7 @@ FramePtr FFmpegDecoder::BuffersToNativeFrame(int divider, int width, int height,
native_pix_fmt_,
divider));
copy->set_timestamp(Timecode::timestamp_to_time(ts, time_base_));
copy->set_sample_aspect_ratio(aspect_ratio_);
copy->set_sample_aspect_ratio(std::static_pointer_cast<ImageStream>(stream())->pixel_aspect_ratio());
copy->allocate();
// Convert frame to RGB/A for the rest of the pipeline
@@ -1262,16 +1266,6 @@ void FFmpegDecoderInstance::TruncateCacheRangeTo(const qint64 &t)
}
}
rational FFmpegDecoderInstance::sample_aspect_ratio() const
{
return av_guess_sample_aspect_ratio(fmt_ctx_, avstream_, nullptr);
}
AVStream *FFmpegDecoderInstance::stream() const
{
return avstream_;
}
FFmpegDecoderInstance::FFmpegDecoderInstance(const char *filename, int stream_index) :
fmt_ctx_(nullptr),
opts_(nullptr),
+9 -3
View File
@@ -64,8 +64,15 @@ public:
void RemoveFramesBefore(const qint64& t);
void TruncateCacheRangeTo(const qint64& t);
rational sample_aspect_ratio() const;
AVStream* stream() const;
AVFormatContext* fmt_ctx() const
{
return fmt_ctx_;
}
AVStream* stream() const
{
return avstream_;
}
void ClearFrameCache();
@@ -191,7 +198,6 @@ private:
PixelFormat::Format native_pix_fmt_;
rational time_base_;
rational aspect_ratio_;
int64_t start_time_;
static QHash< Stream*, QList<FFmpegDecoderInstance*> > instance_map_;
+5 -1
View File
@@ -113,7 +113,11 @@ const rational &Frame::sample_aspect_ratio() const
void Frame::set_sample_aspect_ratio(const rational &aspect_ratio)
{
sample_aspect_ratio_ = aspect_ratio;
if (aspect_ratio.isNull()) {
sample_aspect_ratio_ = 1;
} else {
sample_aspect_ratio_ = aspect_ratio;
}
}
const rational &Frame::timestamp() const
+3
View File
@@ -119,6 +119,9 @@ bool OIIODecoder::Probe(Footage *f, const QAtomicInt *cancelled)
image_stream->set_height(in->spec().height);
image_stream->set_format(GetFormatFromOIIOBasetype(in->spec()));
// FIXME: Haven't looked, does OIIO report pixel aspect ratio somewhere?
image_stream->set_pixel_aspect_ratio(1);
// Images will always have just one stream
image_stream->set_index(0);
+2
View File
@@ -36,6 +36,8 @@ set(OLIVE_SOURCES
common/qtutils.h
common/qtutils.cpp
common/range.h
common/ratiodialog.h
common/ratiodialog.cpp
common/rational.h
common/rational.cpp
common/threadedobject.h
+90
View File
@@ -0,0 +1,90 @@
/***
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 "ratiodialog.h"
#include <QCoreApplication>
#include <QMessageBox>
OLIVE_NAMESPACE_ENTER
double GetFloatRatioFromUser(QWidget* parent,
const QString& title,
bool* ok_in)
{
QString s;
forever {
bool ok;
s = QInputDialog::getText(parent,
title,
QCoreApplication::translate("RatioDialog", "Enter custom ratio (e.g. \"4:3\", \"16/9\", etc.):"),
QLineEdit::Normal,
s,
&ok);
if (!ok) {
// User cancelled dialog, do nothing
if (ok_in) {
*ok_in = false;
}
return qSNaN();
}
QStringList ratio_components = s.split(QRegExp(QStringLiteral(":|;|\\/")));
if (ratio_components.size() == 1) {
bool float_ok;
double flt = ratio_components.at(0).toDouble(&float_ok);
if (float_ok && flt > 0) {
if (ok_in) {
*ok_in = true;
}
return flt;
}
} else if (ratio_components.size() == 2) {
bool numer_ok, denom_ok;
double num = ratio_components.at(0).toDouble(&numer_ok);
double den = ratio_components.at(1).toDouble(&denom_ok);
if (numer_ok
&& denom_ok
&& num > 0) {
// Exit loop and set this ratio
if (ok_in) {
*ok_in = true;
}
return num / den;
}
}
QMessageBox::warning(parent,
QCoreApplication::translate("RatioDialog", "Invalid custom ratio"),
QCoreApplication::translate("RatioDialog", "Failed to parse \"%1\" into an aspect ratio. Please format a "
"rational fraction with a ':' or a '/' separator.").arg(s),
QMessageBox::Ok);
}
}
OLIVE_NAMESPACE_EXIT
+36
View File
@@ -0,0 +1,36 @@
/***
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 RATIODIALOG_H
#define RATIODIALOG_H
#include <QInputDialog>
#include "common/rational.h"
OLIVE_NAMESPACE_ENTER
double GetFloatRatioFromUser(QWidget* parent,
const QString& title,
bool* ok_in);
OLIVE_NAMESPACE_EXIT
#endif // RATIODIALOG_H
@@ -22,11 +22,13 @@
#include <QGridLayout>
#include <QGroupBox>
#include <QInputDialog>
#include <QLabel>
#include <QMessageBox>
#include <OpenColorIO/OpenColorIO.h>
namespace OCIO = OCIO_NAMESPACE::v1;
#include "common/ratiodialog.h"
#include "project/item/footage/footage.h"
#include "project/project.h"
#include "undo/undostack.h"
@@ -41,6 +43,37 @@ VideoStreamProperties::VideoStreamProperties(ImageStreamPtr stream) :
int row = 0;
video_layout->addWidget(new QLabel(tr("Pixel Aspect:")), row, 0);
pixel_aspect_combo_ = new QComboBox();
video_layout->addWidget(pixel_aspect_combo_, row, 1);
AddPixelAspectRatio(tr("Square Pixels"), rational(1));
AddPixelAspectRatio(tr("NTSC Standard"), rational(8, 9));
AddPixelAspectRatio(tr("NTSC Widescreen"), rational(32, 27));
AddPixelAspectRatio(tr("PAL Standard"), rational(16, 15));
AddPixelAspectRatio(tr("PAL Widescreen"), rational(64, 45));
AddPixelAspectRatio(tr("HD Anamorphic 1080"), rational(4, 3));
// Always add custom item last, much of the logic relies on this. Set this to the current AR so
// that if none of the above are ==, it will eventually select this item
AddPixelAspectRatio(QString(), rational());
UpdateCustomItem(stream->pixel_aspect_ratio());
// Determine which index to select on startup
for (int i=0; i<pixel_aspect_combo_->count(); i++) {
if (pixel_aspect_combo_->itemData(i).value<rational>() == stream->pixel_aspect_ratio()) {
pixel_aspect_combo_->setCurrentIndex(i);
break;
}
}
// Pick up index signal to query for custom aspect ratio if requested
connect(pixel_aspect_combo_, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
this, &VideoStreamProperties::PixelAspectComboBoxChanged);
row++;
video_layout->addWidget(new QLabel(tr("Interlacing:")), row, 0);
video_interlace_combo_ = new QComboBox();
@@ -119,12 +152,15 @@ void VideoStreamProperties::Accept(QUndoCommand *parent)
}
if (video_premultiply_alpha_->isChecked() != stream_->premultiplied_alpha()
|| set_colorspace != stream_->colorspace(false)) {
|| set_colorspace != stream_->colorspace(false)
|| static_cast<ImageStream::Interlacing>(video_interlace_combo_->currentIndex()) != stream_->interlacing()
|| pixel_aspect_combo_->currentData().value<rational>() != stream_->pixel_aspect_ratio()) {
new VideoStreamChangeCommand(stream_,
video_premultiply_alpha_->isChecked(),
set_colorspace,
static_cast<ImageStream::Interlacing>(video_interlace_combo_->currentIndex()),
pixel_aspect_combo_->currentData().value<rational>(),
parent);
}
@@ -163,16 +199,54 @@ bool VideoStreamProperties::IsImageSequence(ImageStream *stream)
return (stream->type() == Stream::kVideo && static_cast<VideoStream*>(stream)->is_image_sequence());
}
void VideoStreamProperties::AddPixelAspectRatio(const QString &name, const rational &ratio)
{
pixel_aspect_combo_->addItem(GetPixelAspectRatioItemText(name, ratio),
QVariant::fromValue(ratio));
}
QString VideoStreamProperties::GetPixelAspectRatioItemText(const QString &name, const rational &ratio)
{
return tr("%1 (%2)").arg(name, QString::number(ratio.toDouble(), 'f', 4));
}
void VideoStreamProperties::UpdateCustomItem(const rational &ratio)
{
const int custom_index = pixel_aspect_combo_->count() - 1;
pixel_aspect_combo_->setItemText(custom_index,
GetPixelAspectRatioItemText(tr("Custom"), ratio));
pixel_aspect_combo_->setItemData(custom_index,
QVariant::fromValue(ratio));
}
void VideoStreamProperties::PixelAspectComboBoxChanged(int index)
{
// Detect if custom was selected, in which case query what the new AR should be
if (index == pixel_aspect_combo_->count() - 1) {
// Query for custom pixel aspect ratio
bool ok;
double custom_ratio = GetFloatRatioFromUser(this, tr("Set Custom Pixel Aspect Ratio"), &ok);
if (ok) {
UpdateCustomItem(rational::fromDouble(custom_ratio));
}
}
}
VideoStreamProperties::VideoStreamChangeCommand::VideoStreamChangeCommand(ImageStreamPtr stream,
bool premultiplied,
QString colorspace,
ImageStream::Interlacing interlacing,
const rational &pixel_ar,
QUndoCommand *parent) :
UndoCommand(parent),
stream_(stream),
new_premultiplied_(premultiplied),
new_colorspace_(colorspace),
new_interlacing_(interlacing)
new_interlacing_(interlacing),
new_pixel_ar_(pixel_ar)
{
}
@@ -186,10 +260,12 @@ void VideoStreamProperties::VideoStreamChangeCommand::redo_internal()
old_premultiplied_ = stream_->premultiplied_alpha();
old_colorspace_ = stream_->colorspace(false);
old_interlacing_ = stream_->interlacing();
old_pixel_ar_ = stream_->pixel_aspect_ratio();
stream_->set_premultiplied_alpha(new_premultiplied_);
stream_->set_colorspace(new_colorspace_);
stream_->set_interlacing(new_interlacing_);
stream_->set_pixel_aspect_ratio(new_pixel_ar_);
}
void VideoStreamProperties::VideoStreamChangeCommand::undo_internal()
@@ -197,6 +273,7 @@ void VideoStreamProperties::VideoStreamChangeCommand::undo_internal()
stream_->set_premultiplied_alpha(old_premultiplied_);
stream_->set_colorspace(old_colorspace_);
stream_->set_interlacing(old_interlacing_);
stream_->set_pixel_aspect_ratio(old_pixel_ar_);
}
VideoStreamProperties::ImageSequenceChangeCommand::ImageSequenceChangeCommand(VideoStreamPtr video_stream, int64_t start_index, int64_t duration, QUndoCommand *parent) :
@@ -43,6 +43,12 @@ public:
private:
static bool IsImageSequence(ImageStream* stream);
void AddPixelAspectRatio(const QString& name, const rational& ratio);
static QString GetPixelAspectRatioItemText(const QString& name, const rational& ratio);
void UpdateCustomItem(const rational& ratio);
/**
* @brief Attached video stream
*/
@@ -73,12 +79,18 @@ private:
*/
IntegerSlider* imgseq_end_time_;
/**
* @brief Sets the pixel aspect ratio of the stream
*/
QComboBox* pixel_aspect_combo_;
class VideoStreamChangeCommand : public UndoCommand {
public:
VideoStreamChangeCommand(ImageStreamPtr stream,
bool premultiplied,
QString colorspace,
ImageStream::Interlacing interlacing,
const rational& pixel_ar,
QUndoCommand* parent = nullptr);
virtual Project* GetRelevantProject() const override;
@@ -93,10 +105,12 @@ private:
bool new_premultiplied_;
QString new_colorspace_;
ImageStream::Interlacing new_interlacing_;
rational new_pixel_ar_;
bool old_premultiplied_;
QString old_colorspace_;
ImageStream::Interlacing old_interlacing_;
rational old_pixel_ar_;
};
@@ -123,6 +137,10 @@ private:
int64_t old_duration_;
};
private slots:
void PixelAspectComboBoxChanged(int index);
};
OLIVE_NAMESPACE_EXIT
+47 -34
View File
@@ -242,38 +242,53 @@ QIcon Footage::icon()
QString Footage::duration()
{
if (streams_.isEmpty()) {
return QString();
// Find longest stream duration
StreamPtr longest_stream = nullptr;
rational longest;
foreach (StreamPtr stream, streams_) {
if (stream->type() == Stream::kVideo || stream->type() == Stream::kAudio) {
rational this_stream_dur = Timecode::timestamp_to_time(stream->duration(),
stream->timebase());
if (this_stream_dur > longest) {
longest_stream = stream;
longest = this_stream_dur;
}
}
}
if (streams_.first()->type() == Stream::kVideo) {
VideoStreamPtr video_stream = std::static_pointer_cast<VideoStream>(streams_.first());
if (longest_stream) {
if (longest_stream->type() == Stream::kVideo) {
VideoStreamPtr video_stream = std::static_pointer_cast<VideoStream>(longest_stream);
int64_t duration = video_stream->duration();
rational frame_rate_timebase = video_stream->frame_rate().flipped();
int64_t duration = video_stream->duration();
rational frame_rate_timebase = video_stream->frame_rate().flipped();
if (video_stream->timebase() != frame_rate_timebase) {
// Convert from timebase to frame rate
rational duration_time = Timecode::timestamp_to_time(duration, video_stream->timebase());
duration = Timecode::time_to_timestamp(duration_time, frame_rate_timebase);
if (video_stream->timebase() != frame_rate_timebase) {
// Convert from timebase to frame rate
rational duration_time = Timecode::timestamp_to_time(duration, video_stream->timebase());
duration = Timecode::time_to_timestamp(duration_time, frame_rate_timebase);
}
return Timecode::timestamp_to_timecode(duration,
frame_rate_timebase,
Core::instance()->GetTimecodeDisplay());
} else if (longest_stream->type() == Stream::kAudio) {
AudioStreamPtr audio_stream = std::static_pointer_cast<AudioStream>(longest_stream);
// If we're showing in a timecode, we prefer showing audio in seconds instead
Timecode::Display display = Core::instance()->GetTimecodeDisplay();
if (display == Timecode::kTimecodeDropFrame
|| display == Timecode::kTimecodeNonDropFrame) {
display = Timecode::kTimecodeSeconds;
}
return Timecode::timestamp_to_timecode(longest_stream->duration(),
longest_stream->timebase(),
display);
}
return Timecode::timestamp_to_timecode(duration,
frame_rate_timebase,
Core::instance()->GetTimecodeDisplay());
} else if (streams_.first()->type() == Stream::kAudio) {
AudioStreamPtr audio_stream = std::static_pointer_cast<AudioStream>(streams_.first());
// If we're showing in a timecode, we prefer showing audio in seconds instead
Timecode::Display display = Core::instance()->GetTimecodeDisplay();
if (display == Timecode::kTimecodeDropFrame
|| display == Timecode::kTimecodeNonDropFrame) {
display = Timecode::kTimecodeSeconds;
}
return Timecode::timestamp_to_timecode(streams_.first()->duration(),
streams_.first()->timebase(),
display);
}
return QString();
@@ -285,15 +300,13 @@ QString Footage::rate()
return QString();
}
if (streams_.first()->type() == Stream::kVideo) {
// Return the timebase as a frame rate
VideoStreamPtr video_stream = std::static_pointer_cast<VideoStream>(streams_.first());
if (HasStreamsOfType(Stream::kVideo)) {
// 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());
} else if (streams_.first()->type() == Stream::kAudio) {
// Return the sample rate
} else if (HasStreamsOfType(Stream::kAudio)) {
// No video streams, return audio
AudioStreamPtr audio_stream = std::static_pointer_cast<AudioStream>(streams_.first());
return QCoreApplication::translate("Footage", "%1 Hz").arg(audio_stream->sample_rate());
}
+2 -1
View File
@@ -29,7 +29,8 @@ OLIVE_NAMESPACE_ENTER
ImageStream::ImageStream() :
premultiplied_alpha_(false),
interlacing_(kInterlaceNone)
interlacing_(kInterlaceNone),
pixel_aspect_ratio_(1)
{
set_type(kImage);
}
+19
View File
@@ -93,6 +93,23 @@ public:
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;
@@ -109,6 +126,8 @@ private:
PixelFormat::Format format_;
rational pixel_aspect_ratio_;
private slots:
void ColorConfigChanged();
+1 -1
View File
@@ -149,7 +149,7 @@ QVariant OpenGLProxy::FrameToValue(FramePtr frame, StreamPtr stream, const Video
VideoParams frame_params = frame->video_params();
// Check frame aspect ratio
if (frame->sample_aspect_ratio() != 1 && frame->sample_aspect_ratio() != 0) {
if (frame->sample_aspect_ratio() != 1) {
int new_width = frame_params.width();
int new_height = frame_params.height();
+9 -37
View File
@@ -32,6 +32,7 @@
#include "audio/audiomanager.h"
#include "common/power.h"
#include "common/ratiodialog.h"
#include "common/timecodefunctions.h"
#include "config/config.h"
#include "project/item/sequence/sequence.h"
@@ -397,6 +398,8 @@ FramePtr DecodeCachedImage(const QString &fn, const rational& time)
if (frame) {
frame->set_timestamp(time);
} else {
qWarning() << "Tried to load cached frame from file but it was null";
}
return frame;
@@ -743,45 +746,14 @@ void ViewerWidget::ContextMenuSetSafeMargins()
void ViewerWidget::ContextMenuSetCustomSafeMargins()
{
QString s;
bool ok;
forever {
bool ok;
double new_ratio = GetFloatRatioFromUser(this,
tr("Safe Margins"),
&ok);
s = QInputDialog::getText(this,
tr("Safe Margins"),
tr("Enter custom ratio (e.g. \"4:3\", \"16/9\", etc.):"),
QLineEdit::Normal,
s,
&ok);
if (!ok) {
// User cancelled dialog, do nothing
return;
}
QStringList ratio_components = s.split(QRegExp(QStringLiteral(":|;|\\/")));
if (ratio_components.size() == 2) {
bool numer_ok, denom_ok;
double num = ratio_components.at(0).toDouble(&numer_ok);
double den = ratio_components.at(1).toDouble(&denom_ok);
if (numer_ok
&& denom_ok
&& num > 0) {
// Exit loop and set this ratio
context_menu_widget_->SetSafeMargins(ViewerSafeMarginInfo(true, num / den));
return;
}
}
QMessageBox::warning(this,
tr("Invalid custom ratio"),
tr("Failed to parse \"%1\" into an aspect ratio. Please format a "
"rational fraction with a ':' or a '/' separator.").arg(s),
QMessageBox::Ok);
if (ok) {
context_menu_widget_->SetSafeMargins(ViewerSafeMarginInfo(true, new_ratio));
}
}