implemented multithreaded importing #85
This commit is contained in:
@@ -29,5 +29,6 @@
|
||||
<file>slip-disabled.png</file>
|
||||
<file>slide.png</file>
|
||||
<file>slide-disabled.png</file>
|
||||
<file>throbber.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
+76
-12
@@ -1,7 +1,3 @@
|
||||
//
|
||||
// TODO: Fold this file into a multithreaded "file processor"
|
||||
//
|
||||
|
||||
#include "previewgenerator.h"
|
||||
|
||||
#include "media.h"
|
||||
@@ -10,6 +6,9 @@
|
||||
#include <QPixmap>
|
||||
#include <QDebug>
|
||||
#include <QtMath>
|
||||
#include <QTreeWidgetItem>
|
||||
|
||||
#define WAVEFORM_RESOLUTION 64
|
||||
|
||||
extern "C" {
|
||||
#include <libavformat/avformat.h>
|
||||
@@ -18,16 +17,46 @@ extern "C" {
|
||||
#include <libswresample/swresample.h>
|
||||
}
|
||||
|
||||
PreviewGenerator::PreviewGenerator(QObject* parent) : QThread(parent) {
|
||||
media = NULL;
|
||||
fmt_ctx = NULL;
|
||||
PreviewGenerator::PreviewGenerator(QTreeWidgetItem* i, Media* m) : QThread(0), fmt_ctx(NULL), item(i), media(m) {
|
||||
connect(this, SIGNAL(finished()), this, SLOT(deleteLater()));
|
||||
}
|
||||
|
||||
#define WAVEFORM_RESOLUTION 64
|
||||
void PreviewGenerator::parse_media() {
|
||||
// detect video/audio streams in file
|
||||
for (int i=0;i<(int)fmt_ctx->nb_streams;i++) {
|
||||
// Find the decoder for the video stream
|
||||
if (avcodec_find_decoder(fmt_ctx->streams[i]->codecpar->codec_id) == NULL) {
|
||||
qDebug() << "[ERROR] Unsupported codec in stream %d.\n";
|
||||
} else {
|
||||
MediaStream* ms = new MediaStream();
|
||||
ms->preview_done = false;
|
||||
ms->file_index = i;
|
||||
if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
|
||||
bool infinite_length = (fmt_ctx->streams[i]->avg_frame_rate.den == 0);
|
||||
ms->video_width = fmt_ctx->streams[i]->codecpar->width;
|
||||
ms->video_height = fmt_ctx->streams[i]->codecpar->height;
|
||||
ms->infinite_length = infinite_length;
|
||||
media->video_tracks.append(ms);
|
||||
} else if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
|
||||
ms->audio_channels = fmt_ctx->streams[i]->codecpar->channels;
|
||||
media->audio_tracks.append(ms);
|
||||
}
|
||||
}
|
||||
}
|
||||
media->length = fmt_ctx->duration;
|
||||
|
||||
void PreviewGenerator::run() {
|
||||
Q_ASSERT(media != NULL);
|
||||
media->ready = true;
|
||||
|
||||
if (media->video_tracks.size() == 0) {
|
||||
emit set_icon(ICON_TYPE_AUDIO);
|
||||
} else {
|
||||
emit set_icon(ICON_TYPE_VIDEO);
|
||||
}
|
||||
|
||||
item->setText(1, QString::number(media->length));
|
||||
}
|
||||
|
||||
void PreviewGenerator::generate_waveform() {
|
||||
SwsContext* sws_ctx;
|
||||
SwrContext* swr_ctx;
|
||||
AVFrame* temp_frame = av_frame_alloc();
|
||||
@@ -99,7 +128,7 @@ void PreviewGenerator::run() {
|
||||
|
||||
s->video_preview = QImage(data, dstW, dstH, linesize[0], QImage::Format_RGB888);
|
||||
|
||||
// delete [] data;
|
||||
// delete [] data;
|
||||
|
||||
s->preview_done = true;
|
||||
|
||||
@@ -183,6 +212,41 @@ void PreviewGenerator::run() {
|
||||
}
|
||||
}
|
||||
delete [] codec_ctx;
|
||||
}
|
||||
|
||||
avformat_close_input(&fmt_ctx);
|
||||
void PreviewGenerator::run() {
|
||||
Q_ASSERT(media != NULL);
|
||||
Q_ASSERT(item != NULL);
|
||||
|
||||
QByteArray ba = media->url.toLatin1();
|
||||
char* filename = new char[ba.size()+1];
|
||||
strcpy(filename, ba.data());
|
||||
|
||||
bool error = false;
|
||||
int errCode = avformat_open_input(&fmt_ctx, filename, NULL, NULL);
|
||||
if(errCode != 0) {
|
||||
char err[1024];
|
||||
av_strerror(errCode, err, 1024);
|
||||
item->setToolTip(0, "Could not open file - " + QString(err));
|
||||
error = true;
|
||||
} else {
|
||||
errCode = avformat_find_stream_info(fmt_ctx, NULL);
|
||||
if (errCode < 0) {
|
||||
char err[1024];
|
||||
av_strerror(errCode, err, 1024);
|
||||
item->setToolTip(0, "Could not find stream information - " + QString(err));
|
||||
error = true;
|
||||
} else {
|
||||
av_dump_format(fmt_ctx, 0, filename, 0);
|
||||
parse_media();
|
||||
generate_waveform();
|
||||
}
|
||||
avformat_close_input(&fmt_ctx);
|
||||
}
|
||||
if (error) {
|
||||
emit set_icon(ICON_TYPE_ERROR);
|
||||
} else {
|
||||
item->setToolTip(0, QString());
|
||||
}
|
||||
delete [] filename;
|
||||
}
|
||||
|
||||
+13
-1
@@ -3,15 +3,27 @@
|
||||
|
||||
#include <QThread>
|
||||
|
||||
#define ICON_TYPE_VIDEO 0
|
||||
#define ICON_TYPE_AUDIO 1
|
||||
#define ICON_TYPE_ERROR 2
|
||||
|
||||
struct Media;
|
||||
struct MediaStream;
|
||||
struct AVFormatContext;
|
||||
class QTreeWidgetItem;
|
||||
|
||||
class PreviewGenerator : public QThread
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
PreviewGenerator(QObject* parent = 0);
|
||||
PreviewGenerator(QTreeWidgetItem*, Media*);
|
||||
void run();
|
||||
signals:
|
||||
void set_icon(int);
|
||||
private:
|
||||
void parse_media();
|
||||
void generate_waveform();
|
||||
QTreeWidgetItem* item;
|
||||
Media* media;
|
||||
AVFormatContext* fmt_ctx;
|
||||
};
|
||||
|
||||
+2
-1
@@ -27,6 +27,7 @@
|
||||
#include <QStandardPaths>
|
||||
#include <QTimer>
|
||||
#include <QCloseEvent>
|
||||
#include <QMovie>
|
||||
|
||||
#define OLIVE_FILE_FILTER "Olive Project (*.ove)"
|
||||
|
||||
@@ -58,7 +59,7 @@ MainWindow::MainWindow(QWidget *parent) :
|
||||
darkPalette.setColor(QPalette::WindowText, Qt::white);
|
||||
darkPalette.setColor(QPalette::Base, QColor(25,25,25));
|
||||
darkPalette.setColor(QPalette::AlternateBase, QColor(53,53,53));
|
||||
darkPalette.setColor(QPalette::ToolTipBase, Qt::white);
|
||||
darkPalette.setColor(QPalette::ToolTipBase, QColor(25,25,25));
|
||||
darkPalette.setColor(QPalette::ToolTipText, Qt::white);
|
||||
darkPalette.setColor(QPalette::Text, Qt::white);
|
||||
darkPalette.setColor(QPalette::Button, QColor(53,53,53));
|
||||
|
||||
+38
-62
@@ -111,77 +111,22 @@ void Project::new_sequence(Sequence *s, bool open, QTreeWidgetItem* parent) {
|
||||
}
|
||||
|
||||
QTreeWidgetItem* Project::import_file(QString file) {
|
||||
QByteArray ba = file.toLatin1();
|
||||
char* filename = new char[ba.size()+1];
|
||||
strcpy(filename, ba.data());
|
||||
|
||||
QTreeWidgetItem* item = new_item();
|
||||
Media* m = new Media();
|
||||
m->url = file;
|
||||
m->name = file.mid(file.lastIndexOf('/')+1);
|
||||
item->setText(0, m->name);
|
||||
|
||||
AVFormatContext* pFormatCtx = NULL;
|
||||
int errCode = avformat_open_input(&pFormatCtx, filename, NULL, NULL);
|
||||
if(errCode != 0) {
|
||||
char err[1024];
|
||||
av_strerror(errCode, err, 1024);
|
||||
qDebug() << "[ERROR] Could not open" << filename << "-" << err;
|
||||
} else {
|
||||
errCode = avformat_find_stream_info(pFormatCtx, NULL);
|
||||
if (errCode < 0) {
|
||||
char err[1024];
|
||||
av_strerror(errCode, err, 1024);
|
||||
fprintf(stderr, "[ERROR] Could not find stream information. %s\n", err);
|
||||
} else {
|
||||
av_dump_format(pFormatCtx, 0, filename, 0);
|
||||
|
||||
// detect video/audio streams in file
|
||||
for (int i=0;i<(int)pFormatCtx->nb_streams;i++) {
|
||||
// Find the decoder for the video stream
|
||||
if (avcodec_find_decoder(pFormatCtx->streams[i]->codecpar->codec_id) == NULL) {
|
||||
qDebug() << "[ERROR] Unsupported codec in stream %d.\n";
|
||||
} else {
|
||||
MediaStream* ms = new MediaStream();
|
||||
ms->preview_done = false;
|
||||
ms->file_index = i;
|
||||
if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
|
||||
bool infinite_length = (pFormatCtx->streams[i]->avg_frame_rate.den == 0);
|
||||
ms->video_width = pFormatCtx->streams[i]->codecpar->width;
|
||||
ms->video_height = pFormatCtx->streams[i]->codecpar->height;
|
||||
ms->infinite_length = infinite_length;
|
||||
m->video_tracks.append(ms);
|
||||
} else if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
|
||||
ms->audio_channels = pFormatCtx->streams[i]->codecpar->channels;
|
||||
m->audio_tracks.append(ms);
|
||||
}
|
||||
}
|
||||
}
|
||||
m->length = pFormatCtx->duration;
|
||||
|
||||
if (m->video_tracks.size() == 0) {
|
||||
item->setIcon(0, QIcon(":/icons/audiosource.png"));
|
||||
} else {
|
||||
item->setIcon(0, QIcon(":/icons/videosource.png"));
|
||||
}
|
||||
item->setText(1, QString::number(m->length));
|
||||
|
||||
m->ready = true;
|
||||
|
||||
// generate waveform/thumbnail in another thread
|
||||
PreviewGenerator* pg = new PreviewGenerator();
|
||||
pg->fmt_ctx = pFormatCtx; // cleaned up in PG
|
||||
pg->media = m;
|
||||
connect(pg, SIGNAL(finished()), pg, SLOT(deleteLater()));
|
||||
pg->start(QThread::LowPriority);
|
||||
}
|
||||
}
|
||||
|
||||
set_media_of_tree(item, m);
|
||||
|
||||
project_changed = true;
|
||||
// set up throbber animation
|
||||
MediaThrobber* throbber = new MediaThrobber(item);
|
||||
|
||||
// generate waveform/thumbnail in another thread
|
||||
PreviewGenerator* pg = new PreviewGenerator(item, m);
|
||||
connect(pg, SIGNAL(set_icon(int)), throbber, SLOT(stop(int)));
|
||||
pg->start(QThread::LowPriority);
|
||||
|
||||
delete [] filename;
|
||||
return item;
|
||||
}
|
||||
|
||||
@@ -377,6 +322,7 @@ void Project::process_file_list(QStringList& files) {
|
||||
|
||||
ta->add_media(import_file(file));
|
||||
imported = true;
|
||||
project_changed = true;
|
||||
}
|
||||
if (imported) {
|
||||
undo_stack.push(ta);
|
||||
@@ -935,3 +881,33 @@ void Project::add_recent_project(QString url) {
|
||||
}
|
||||
save_recent_projects();
|
||||
}
|
||||
|
||||
#define THROBBER_LIMIT 20
|
||||
#define THROBBER_SIZE 50
|
||||
|
||||
MediaThrobber::MediaThrobber(QTreeWidgetItem* i) : pixmap(":/icons/throbber.png"), item(i), animation(0) {
|
||||
// set up throbber
|
||||
animation_update();
|
||||
animator.setInterval(20);
|
||||
connect(&animator, SIGNAL(timeout()), this, SLOT(animation_update()));
|
||||
animator.start();
|
||||
}
|
||||
|
||||
void MediaThrobber::animation_update() {
|
||||
if (animation == THROBBER_LIMIT) {
|
||||
animation = 0;
|
||||
}
|
||||
item->setIcon(0, QIcon(pixmap.copy(THROBBER_SIZE*animation, 0, THROBBER_SIZE, THROBBER_SIZE)));
|
||||
animation++;
|
||||
}
|
||||
|
||||
void MediaThrobber::stop(int icon_type) {
|
||||
animator.stop();
|
||||
switch (icon_type) {
|
||||
case ICON_TYPE_VIDEO: item->setIcon(0, QIcon(":/icons/videosource.png")); break;
|
||||
case ICON_TYPE_AUDIO: item->setIcon(0, QIcon(":/icons/audiosource.png")); break;
|
||||
case ICON_TYPE_ERROR: item->setIcon(0, QIcon::fromTheme("dialog-error")); break;
|
||||
}
|
||||
panel_project->source_table->viewport()->update();
|
||||
deleteLater();
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <QDockWidget>
|
||||
#include <QVector>
|
||||
#include <QTimer>
|
||||
|
||||
struct Media;
|
||||
struct Sequence;
|
||||
@@ -84,4 +85,19 @@ private slots:
|
||||
void clear_recent_projects();
|
||||
};
|
||||
|
||||
class MediaThrobber : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
MediaThrobber(QTreeWidgetItem*);
|
||||
public slots:
|
||||
void stop(int);
|
||||
private slots:
|
||||
void animation_update();
|
||||
private:
|
||||
QPixmap pixmap;
|
||||
int animation;
|
||||
QTreeWidgetItem* item;
|
||||
QTimer animator;
|
||||
};
|
||||
|
||||
#endif // PROJECT_H
|
||||
|
||||
@@ -35,11 +35,5 @@ bool Effect::is_enabled() {
|
||||
Effect* Effect::copy(Clip*) {return NULL;}
|
||||
void Effect::load(QXmlStreamReader*) {}
|
||||
void Effect::save(QXmlStreamWriter*) {}
|
||||
/*void Effect::import_values(float* val, int count) {
|
||||
qDebug() << "[ERROR] import_values MUST be overridden";
|
||||
}
|
||||
void Effect::export_values(float* val, int* count) {
|
||||
qDebug() << "[ERROR] export_values MUST be overridden";
|
||||
}*/
|
||||
void Effect::process_gl(int*, int*) {}
|
||||
void Effect::process_audio(uint8_t*, int) {}
|
||||
|
||||
Reference in New Issue
Block a user