Limit import files

This commit is contained in:
2026-01-05 16:36:43 +08:00
parent 4bd453bab0
commit 6b64abb1fa
5 changed files with 205 additions and 7 deletions
+61 -2
View File
@@ -21,6 +21,7 @@
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/avutil.h>
#include <libavutil/frame.h>
}
@@ -43,6 +44,44 @@ namespace olive
QVariant Yuv2RgbShader;
QVariant DeinterlaceShader;
namespace {
constexpr int64_t kAnalyzeDurationUs = 5000000;
constexpr int64_t kProbeSizeBytes = 20000000;
void ApplyFormatOpenOptions(AVDictionary **opts)
{
av_dict_set_int(opts, "analyzeduration", kAnalyzeDurationUs, 0);
av_dict_set_int(opts, "probesize", kProbeSizeBytes, 0);
}
void TuneFormatContext(AVFormatContext *ctx)
{
if (!ctx) {
return;
}
ctx->probesize = kProbeSizeBytes;
ctx->max_analyze_duration = kAnalyzeDurationUs;
}
void DiscardSubtitleStreams(AVFormatContext *ctx)
{
if (!ctx) {
return;
}
for (unsigned int i = 0; i < ctx->nb_streams; i++) {
AVStream *stream = ctx->streams[i];
if (stream && stream->codecpar &&
stream->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) {
stream->discard = AVDISCARD_ALL;
}
}
}
} // namespace
FFmpegDecoder::FFmpegDecoder()
: sws_ctx_(nullptr)
, working_packet_(nullptr)
@@ -339,7 +378,12 @@ FootageDescription FFmpegDecoder::Probe(const QString &filename,
// Open file in a format context
AVFormatContext *fmt_ctx = nullptr;
error_code = avformat_open_input(&fmt_ctx, filename_c, nullptr, nullptr);
AVDictionary *format_opts = nullptr;
ApplyFormatOpenOptions(&format_opts);
error_code = avformat_open_input(&fmt_ctx, filename_c, nullptr, &format_opts);
av_dict_free(&format_opts);
TuneFormatContext(fmt_ctx);
DiscardSubtitleStreams(fmt_ctx);
// Handle format context error
if (error_code == 0) {
@@ -901,6 +945,7 @@ AVFramePtr FFmpegDecoder::RetrieveFrame(const rational &time,
int ret;
AVFramePtr return_frame = nullptr;
AVFramePtr filtered = nullptr;
bool retried_after_eof = false;
while (true) {
// Break out of loop if we've cancelled
@@ -948,6 +993,15 @@ AVFramePtr FFmpegDecoder::RetrieveFrame(const rational &time,
cache_at_eof_ = true;
if (cached_frames_.empty()) {
if (!retried_after_eof) {
retried_after_eof = true;
ClearFrameCache();
instance_.Seek(min_seek);
cache_at_zero_ = true;
still_seeking = true;
continue;
}
qCritical()
<< "Unexpected codec EOF - unable to retrieve frame";
} else {
@@ -1064,7 +1118,12 @@ FFmpegDecoder::Instance::Instance()
bool FFmpegDecoder::Instance::Open(const char *filename, int stream_index)
{
// Open file in a format context
int error_code = avformat_open_input(&fmt_ctx_, filename, nullptr, nullptr);
AVDictionary *format_opts = nullptr;
ApplyFormatOpenOptions(&format_opts);
int error_code = avformat_open_input(&fmt_ctx_, filename, nullptr, &format_opts);
av_dict_free(&format_opts);
TuneFormatContext(fmt_ctx_);
DiscardSubtitleStreams(fmt_ctx_);
// Handle format context error
if (error_code != 0) {
+117 -3
View File
@@ -74,6 +74,72 @@
#include "widget/menu/menushared.h"
#include "window/mainwindow/mainwindow.h"
namespace {
QStringList FootageVideoExtensions()
{
return QStringList{
QStringLiteral("mp4"), QStringLiteral("mov"), QStringLiteral("m4v"),
QStringLiteral("avi"), QStringLiteral("mpg"), QStringLiteral("mpeg"),
QStringLiteral("m2ts"), QStringLiteral("mts"), QStringLiteral("ts"),
QStringLiteral("webm"), QStringLiteral("wmv"), QStringLiteral("flv"),
QStringLiteral("3gp"), QStringLiteral("3g2"), QStringLiteral("mxf")
};
}
QStringList FootageAudioExtensions()
{
return QStringList{
QStringLiteral("wav"), QStringLiteral("mp3"), QStringLiteral("flac"),
QStringLiteral("aac"), QStringLiteral("ogg"), QStringLiteral("opus"),
QStringLiteral("m4a"), QStringLiteral("alac"), QStringLiteral("aif"),
QStringLiteral("aiff"), QStringLiteral("aifc"), QStringLiteral("wma")
};
}
QStringList FootageImageExtensions()
{
return QStringList{
QStringLiteral("png"), QStringLiteral("jpg"), QStringLiteral("jpeg"),
QStringLiteral("tif"), QStringLiteral("tiff"), QStringLiteral("bmp"),
QStringLiteral("gif"), QStringLiteral("exr"), QStringLiteral("dpx"),
QStringLiteral("webp")
};
}
QString BuildFootageFilterGroup(const QString &label,
const QStringList &extensions)
{
QStringList patterns;
patterns.reserve(extensions.size());
for (const QString &ext : extensions) {
patterns.append(QStringLiteral("*.%1").arg(ext));
}
return QStringLiteral("%1 (%2)")
.arg(label, patterns.join(QLatin1Char(' ')));
}
QString BuildFootageFileDialogFilter()
{
QStringList all = FootageVideoExtensions() + FootageAudioExtensions() +
FootageImageExtensions();
all.removeDuplicates();
QStringList groups;
groups << BuildFootageFilterGroup(QObject::tr("Common Media Files"), all);
groups << BuildFootageFilterGroup(QObject::tr("Video Files"),
FootageVideoExtensions());
groups << BuildFootageFilterGroup(QObject::tr("Audio Files"),
FootageAudioExtensions());
groups << BuildFootageFilterGroup(QObject::tr("Image Files"),
FootageImageExtensions());
return groups.join(QStringLiteral(";;"));
}
} // namespace
namespace olive
{
@@ -102,6 +168,29 @@ Core *Core::instance()
return instance_;
}
QString Core::FootageFileDialogFilter()
{
return BuildFootageFileDialogFilter();
}
QStringList Core::AllowedFootageExtensions()
{
QStringList all = FootageVideoExtensions() + FootageAudioExtensions() +
FootageImageExtensions();
all.removeDuplicates();
return all;
}
bool Core::IsFootageExtensionAllowed(const QString &path)
{
const QString ext = QFileInfo(path).suffix().toLower();
if (ext.isEmpty()) {
return false;
}
return AllowedFootageExtensions().contains(ext);
}
void Core::DeclareTypesForQt()
{
qRegisterMetaType<olive::core::rational>();
@@ -240,7 +329,31 @@ void Core::ImportFiles(const QStringList &urls, Folder *parent)
return;
}
ProjectImportTask *pim = new ProjectImportTask(parent, urls);
QStringList filtered_urls;
QStringList rejected_urls;
filtered_urls.reserve(urls.size());
for (const QString &url : urls) {
if (IsFootageExtensionAllowed(url)) {
filtered_urls.append(url);
} else {
rejected_urls.append(url);
}
}
if (!rejected_urls.isEmpty()) {
QMessageBox::warning(
main_window_, tr("Unsupported media"),
tr("Skipped %1 file(s) that are not allowed by the current media "
"type filter.")
.arg(rejected_urls.size()));
}
if (filtered_urls.isEmpty()) {
return;
}
ProjectImportTask *pim = new ProjectImportTask(parent, filtered_urls);
if (!pim->GetFileCount()) {
// No files to import
@@ -333,8 +446,9 @@ void Core::DialogAboutShow()
void Core::DialogImportShow()
{
// Open dialog for user to select files
QStringList files =
QFileDialog::getOpenFileNames(main_window_, tr("Import footage..."));
QStringList files = QFileDialog::getOpenFileNames(
main_window_, tr("Import footage..."), QString(),
FootageFileDialogFilter());
// Check if the user actually selected files to import
if (!files.isEmpty()) {
+4
View File
@@ -132,6 +132,10 @@ public:
*/
static Core *instance();
static QString FootageFileDialogFilter();
static QStringList AllowedFootageExtensions();
static bool IsFootageExtensionAllowed(const QString &path);
const CoreParams &core_params() const
{
return core_params_;
@@ -18,11 +18,14 @@
#include "footagerelinkdialog.h"
#include "core.h"
#include <QDialogButtonBox>
#include <QFileDialog>
#include <QFileInfo>
#include <QHeaderView>
#include <QLabel>
#include <QMessageBox>
#include <QPushButton>
#include <QScrollBar>
#include <QVBoxLayout>
@@ -104,7 +107,8 @@ void FootageRelinkDialog::BrowseForFootage()
QFileInfo info(f->filename());
QString new_fn = QFileDialog::getOpenFileName(
this, tr("Relink \"%1\"").arg(f->GetLabel()), info.absolutePath());
this, tr("Relink \"%1\"").arg(f->GetLabel()), info.absolutePath(),
Core::FootageFileDialogFilter());
// Originally, this function would attempt to filter to the exact filename of the missing file.
// However, this would break on Windows if the filename had any spaces in it. The reason is
@@ -117,6 +121,13 @@ void FootageRelinkDialog::BrowseForFootage()
// We received a new filename
if (!new_fn.isEmpty()) {
if (!Core::IsFootageExtensionAllowed(new_fn)) {
QMessageBox::warning(
this, tr("Unsupported media"),
tr("This file type is not allowed by the current media type "
"filter."));
return;
}
// Store original dir since we might be able to use this to find other files
QDir original_dir = info.dir();
QDir new_dir = QFileInfo(new_fn).dir();
+11 -1
View File
@@ -506,8 +506,18 @@ void ProjectExplorer::ReplaceSelectedFootage()
{
Footage *footage = static_cast<Footage *>(context_menu_items_.first());
QString file = QFileDialog::getOpenFileName(this, tr("Replace Footage"));
QString file = QFileDialog::getOpenFileName(
this, tr("Replace Footage"), QString(),
Core::FootageFileDialogFilter());
if (!file.isEmpty()) {
if (!Core::IsFootageExtensionAllowed(file)) {
QMessageBox::warning(
this, tr("Unsupported media"),
tr("This file type is not allowed by the current media type "
"filter."));
return;
}
auto p = new MultiUndoCommand();
// Change filename parameter