now waveforms and thumbnails are cached

This commit is contained in:
itsmattkc
2018-10-22 18:10:51 +11:00
parent bbdc17a984
commit 6558062099
12 changed files with 310 additions and 49 deletions
+30
View File
@@ -0,0 +1,30 @@
#include "loaddialog.h"
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QLabel>
#include <QProgressBar>
#include <QPushButton>
LoadDialog::LoadDialog(QWidget *parent) : QDialog(parent) {
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
QVBoxLayout* layout = new QVBoxLayout();
setLayout(layout);
layout->addWidget(new QLabel("Loading ''..."));
bar = new QProgressBar();
bar->setValue(50);
layout->addWidget(bar);
QPushButton* cancel_button = new QPushButton("Cancel");
connect(cancel_button, SIGNAL(clicked(bool)), this, SLOT(reject()));
QHBoxLayout* hboxLayout = new QHBoxLayout();
hboxLayout->addStretch();
hboxLayout->addWidget(cancel_button);
hboxLayout->addStretch();
layout->addLayout(hboxLayout);
}
+16
View File
@@ -0,0 +1,16 @@
#ifndef LOADDIALOG_H
#define LOADDIALOG_H
#include <QDialog>
class QProgressBar;
class LoadDialog : public QDialog
{
public:
LoadDialog(QWidget* parent = 0);
private:
QProgressBar* bar;
};
#endif // LOADDIALOG_H
+73
View File
@@ -0,0 +1,73 @@
#include "crc32.h"
#include <QFile>
Crc32::Crc32()
{
quint32 crc;
// initialize CRC table
for (int i = 0; i < 256; i++)
{
crc = i;
for (int j = 0; j < 8; j++)
crc = crc & 1 ? (crc >> 1) ^ 0xEDB88320UL : crc >> 1;
crc_table[i] = crc;
}
}
quint32 Crc32::calculateFromFile(QString filename)
{
quint32 crc;
QFile file;
char buffer[16000];
int len, i;
crc = 0xFFFFFFFFUL;
file.setFileName(filename);
if (file.open(QIODevice::ReadOnly))
{
while (!file.atEnd())
{
len = file.read(buffer, 16000);
for (i = 0; i < len; i++)
crc = crc_table[(crc ^ buffer[i]) & 0xFF] ^ (crc >> 8);
}
file.close();
}
return crc ^ 0xFFFFFFFFUL;
}
void Crc32::initInstance(int i)
{
instances[i] = 0xFFFFFFFFUL;
}
void Crc32::pushData(int i, char *data, int len)
{
quint32 crc = instances[i];
if (crc)
{
for (int j = 0; j < len; j++)
crc = crc_table[(crc ^ data[j]) & 0xFF] ^ (crc >> 8);
instances[i] = crc;
}
}
quint32 Crc32::releaseInstance(int i)
{
quint32 crc32 = instances[i];
if (crc32) {
instances.remove(i);
return crc32 ^ 0xFFFFFFFFUL;
}
else {
return 0;
}
}
+32
View File
@@ -0,0 +1,32 @@
#ifndef CRC32_H
#define CRC32_H
/*
* +-------------------------------------------------------------+
* | Taken from github.com/nusov/qt-crc32 used under MIT license |
* | |
* | Copyright (c) Alexander Nusov 2015 |
* +-------------------------------------------------------------+
*/
#include <QtCore>
#include <QString>
#include <QMap>
class Crc32
{
private:
quint32 crc_table[256];
QMap<int, quint32> instances;
public:
Crc32();
quint32 calculateFromFile(QString filename);
void initInstance(int i);
void pushData(int i, char *data, int len);
quint32 releaseInstance(int i);
};
#endif // CRC32_H
+1 -1
View File
@@ -37,7 +37,7 @@ struct MediaStream {
// preview thumbnail/waveform
bool preview_done;
QImage video_preview; // TODO change to QPixmap
QVector<qint8> audio_preview;
QVector<char> audio_preview;
};
struct Media {
+111 -8
View File
@@ -4,12 +4,18 @@
#include "panels/viewer.h"
#include "panels/project.h"
#include "io/config.h"
#include "io/crc32.h"
#include <QPainter>
#include <QPixmap>
#include <QDebug>
#include <QtMath>
#include <QTreeWidgetItem>
#include <QSemaphore>
#include <QCryptographicHash>
#include <QFile>
#include <QStandardPaths>
#include <QDir>
#define WAVEFORM_RESOLUTION 64
@@ -20,6 +26,8 @@ extern "C" {
#include <libswresample/swresample.h>
}
QSemaphore sem(4); // only 4 preview generators can run at one time
PreviewGenerator::PreviewGenerator(QTreeWidgetItem* i, Media* m, bool r) :
QThread(0),
fmt_ctx(NULL),
@@ -30,6 +38,12 @@ PreviewGenerator::PreviewGenerator(QTreeWidgetItem* i, Media* m, bool r) :
replace(r),
cancelled(false)
{
data_path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/previews";
QDir data_dir(data_path);
if (!data_dir.exists()) {
data_dir.mkpath(".");
}
connect(this, SIGNAL(finished()), this, SLOT(deleteLater()));
}
@@ -38,7 +52,7 @@ void PreviewGenerator::parse_media() {
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";
qDebug() << "[ERROR] Unsupported codec in stream" << i << "of file" << media->name;
} else {
MediaStream* ms = media->get_stream_from_file_index(fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO, i);
bool append = false;
@@ -95,6 +109,61 @@ void PreviewGenerator::parse_media() {
}
}
bool PreviewGenerator::retrieve_preview(const QString& hash) {
// returns true if generate_waveform must be run, false if we got all previews from cached files
if (retrieve_duration) {
return true;
}
bool found = true;
for (int i=0;i<media->video_tracks.size();i++) {
MediaStream* ms = media->video_tracks.at(i);
QString thumb_path = get_thumbnail_path(hash, ms);
QFile f(thumb_path);
if (f.exists()) {
qDebug() << "loaded thumb" << ms->file_index << "from" << thumb_path;
ms->video_preview.load(thumb_path);
ms->preview_done = true;
} else {
found = false;
break;
}
}
for (int i=0;i<media->audio_tracks.size();i++) {
MediaStream* ms = media->audio_tracks.at(i);
QString waveform_path = get_waveform_path(hash, ms);
QFile f(waveform_path);
if (f.exists()) {
qDebug() << "loaded wave" << ms->file_index << "from" << waveform_path;
f.open(QFile::ReadOnly);
QByteArray data = f.readAll();
ms->audio_preview.resize(data.size());
for (int j=0;j<data.size();j++) {
// faster way?
ms->audio_preview[j] = data.at(j);
}
ms->preview_done = true;
f.close();
} else {
found = false;
break;
}
}
if (!found) {
for (int i=0;i<media->video_tracks.size();i++) {
MediaStream* ms = media->video_tracks.at(i);
ms->preview_done = false;
}
for (int i=0;i<media->audio_tracks.size();i++) {
MediaStream* ms = media->audio_tracks.at(i);
ms->audio_preview.clear();
ms->preview_done = false;
}
}
return !found;
}
void PreviewGenerator::finalize_media() {
media->ready = true;
@@ -124,7 +193,6 @@ void PreviewGenerator::generate_waveform() {
codec_ctx[i] = avcodec_alloc_context3(codec);
avcodec_parameters_to_context(codec_ctx[i], fmt_ctx->streams[i]->codecpar);
avcodec_open2(codec_ctx[i], codec, NULL);
if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && codec_ctx[i]->channel_layout == 0) {
codec_ctx[i]->channel_layout = av_get_default_channel_layout(fmt_ctx->streams[i]->codecpar->channels);
}
@@ -300,16 +368,24 @@ void PreviewGenerator::generate_waveform() {
finalize_media();
}
delete [] media_lengths;
delete [] codec_ctx;
delete [] codec_ctx;
}
void PreviewGenerator::run() {
QString PreviewGenerator::get_thumbnail_path(const QString& hash, MediaStream* ms) {
return data_path + "/" + hash + "t" + QString::number(ms->file_index);
}
QString PreviewGenerator::get_waveform_path(const QString& hash, MediaStream* ms) {
return data_path + "/" + hash + "w" + QString::number(ms->file_index);
}
void PreviewGenerator::run() {
Q_ASSERT(media != NULL);
Q_ASSERT(item != NULL);
Q_ASSERT(item != NULL);
QByteArray ba = media->url.toLatin1();
char* filename = new char[ba.size()+1];
strcpy(filename, ba.data());
strcpy(filename, ba.data());
QString errorStr;
bool error = false;
@@ -328,8 +404,35 @@ void PreviewGenerator::run() {
error = true;
} else {
av_dump_format(fmt_ctx, 0, filename, 0);
parse_media();
generate_waveform();
parse_media();
sem.acquire();
// see if we already have data for this
QFileInfo file_info(media->url);
QString cache_file = media->url + QString::number(file_info.lastModified().toMSecsSinceEpoch());
QString hash = QCryptographicHash::hash(cache_file.toLatin1(), QCryptographicHash::Md5).toHex();
if (retrieve_preview(hash)) {
generate_waveform();
// save preview to file
for (int i=0;i<media->video_tracks.size();i++) {
MediaStream* ms = media->video_tracks.at(i);
if (ms->video_preview.save(get_thumbnail_path(hash, ms), "PNG")) {
qDebug() << "saved" << ms->file_index << "thumb to" << get_thumbnail_path(hash, ms);
}
}
for (int i=0;i<media->audio_tracks.size();i++) {
MediaStream* ms = media->audio_tracks.at(i);
QFile f(get_waveform_path(hash, ms));
f.open(QFile::WriteOnly);
f.write(ms->audio_preview.constData(), ms->audio_preview.size());
f.close();
qDebug() << "saved" << ms->file_index << "waveform to" << get_waveform_path(hash, ms);
}
}
sem.release();
}
avformat_close_input(&fmt_ctx);
}
+4
View File
@@ -23,6 +23,7 @@ signals:
void set_icon(int, bool);
private:
void parse_media();
bool retrieve_preview(const QString &hash);
void generate_waveform();
void finalize_media();
QTreeWidgetItem* item;
@@ -32,6 +33,9 @@ private:
bool contains_still_image;
bool replace;
bool cancelled;
QString data_path;
QString get_thumbnail_path(const QString &hash, MediaStream* ms);
QString get_waveform_path(const QString& hash, MediaStream* ms);
};
#endif // PREVIEWGENERATOR_H
+1 -2
View File
@@ -6,8 +6,7 @@ extern "C" {
#include <libavfilter/avfilter.h>
}
int main(int argc, char *argv[])
{
int main(int argc, char *argv[]) {
// init ffmpeg subsystem
av_register_all();
avfilter_register_all();
+6 -2
View File
@@ -84,7 +84,9 @@ SOURCES += \
dialogs/speeddialog.cpp \
dialogs/mediapropertiesdialog.cpp \
effects/video/waveeffect.cpp \
effects/video/temperatureeffect.cpp
effects/video/temperatureeffect.cpp \
io/crc32.cpp \
dialogs/loaddialog.cpp
HEADERS += \
mainwindow.h \
@@ -147,7 +149,9 @@ HEADERS += \
dialogs/speeddialog.h \
dialogs/mediapropertiesdialog.h \
effects/video/waveeffect.h \
effects/video/temperatureeffect.h
effects/video/temperatureeffect.h \
io/crc32.h \
dialogs/loaddialog.h
FORMS += \
mainwindow.ui \
+4
View File
@@ -20,6 +20,7 @@
#include "panels/effectcontrols.h"
#include "dialogs/newsequencedialog.h"
#include "dialogs/mediapropertiesdialog.h"
#include "dialogs/loaddialog.h"
#include <QFileDialog>
#include <QString>
@@ -1001,6 +1002,9 @@ bool Project::load_worker(QFile& f, QXmlStreamReader& stream, int type) {
void Project::load_project() {
new_project();
/*LoadDialog ld;
ld.exec();*/
QFile file(project_url);
if (!file.open(QIODevice::ReadOnly)) {
qDebug() << "[ERROR] Could not open file";
-2
View File
@@ -318,8 +318,6 @@ void Timeline::delete_selection(QVector<Selection>& selections, bool ripple_dele
}
}
qDebug() << "going to ripple at" << ripple_point << "by" << ripple_length;
if (can_ripple) ca->append(new RippleCommand(sequence, ripple_point, -ripple_length));
}
+32 -34
View File
@@ -1933,40 +1933,6 @@ void TimelineWidget::paintEvent(QPaintEvent*) {
if (actual_clip_rect.bottom() > height()) actual_clip_rect.setBottom(height());
p.fillRect(actual_clip_rect, (clip->enabled) ? QColor(clip->color_r, clip->color_g, clip->color_b) : QColor(96, 96, 96));
// draw top and tail triangles
if (clip->media_type == MEDIA_TYPE_FOOTAGE && !static_cast<Media*>(clip->media)->get_stream_from_file_index(clip->track < 0, clip->media_stream)->infinite_length) {
int triangle_size = TRACK_MIN_HEIGHT >> 2;
p.setPen(Qt::NoPen);
p.setBrush(QColor(80, 80, 80));
if (clip->clip_in == 0
&& clip_rect.x() + triangle_size > 0
&& clip_rect.y() + triangle_size > 0
&& clip_rect.x() < width()
&& clip_rect.y() < height()) {
const QPoint points[3] = {
QPoint(clip_rect.x(), clip_rect.y()),
QPoint(clip_rect.x() + triangle_size, clip_rect.y()),
QPoint(clip_rect.x(), clip_rect.y() + triangle_size)
};
p.drawPolygon(points, 3);
text_rect.setLeft(text_rect.left() + (triangle_size >> 2));
}
if (clip->timeline_out - clip->timeline_in + clip->clip_in == clip->getMaximumLength()
&& clip_rect.right() - triangle_size < width()
&& clip_rect.y() + triangle_size > 0
&& clip_rect.right() > 0
&& clip_rect.y() < height()) {
const QPoint points[3] = {
QPoint(clip_rect.right(), clip_rect.y()),
QPoint(clip_rect.right() - triangle_size, clip_rect.y()),
QPoint(clip_rect.right(), clip_rect.y() + triangle_size)
};
p.drawPolygon(points, 3);
text_rect.setRight(text_rect.right() - (triangle_size >> 2));
}
p.setBrush(Qt::NoBrush);
}
int thumb_x = clip_rect.x() + 1;
if (clip->media_type == MEDIA_TYPE_FOOTAGE) {
@@ -1977,6 +1943,38 @@ void TimelineWidget::paintEvent(QPaintEvent*) {
if (ms == NULL) {
draw_checkerboard = true;
} else if (ms->preview_done) {
// draw top and tail triangles
int triangle_size = TRACK_MIN_HEIGHT >> 2;
p.setPen(Qt::NoPen);
p.setBrush(QColor(80, 80, 80));
if (clip->clip_in == 0
&& clip_rect.x() + triangle_size > 0
&& clip_rect.y() + triangle_size > 0
&& clip_rect.x() < width()
&& clip_rect.y() < height()) {
const QPoint points[3] = {
QPoint(clip_rect.x(), clip_rect.y()),
QPoint(clip_rect.x() + triangle_size, clip_rect.y()),
QPoint(clip_rect.x(), clip_rect.y() + triangle_size)
};
p.drawPolygon(points, 3);
text_rect.setLeft(text_rect.left() + (triangle_size >> 2));
}
if (clip->timeline_out - clip->timeline_in + clip->clip_in == clip->getMaximumLength()
&& clip_rect.right() - triangle_size < width()
&& clip_rect.y() + triangle_size > 0
&& clip_rect.right() > 0
&& clip_rect.y() < height()) {
const QPoint points[3] = {
QPoint(clip_rect.right(), clip_rect.y()),
QPoint(clip_rect.right() - triangle_size, clip_rect.y()),
QPoint(clip_rect.right(), clip_rect.y() + triangle_size)
};
p.drawPolygon(points, 3);
text_rect.setRight(text_rect.right() - (triangle_size >> 2));
}
p.setBrush(Qt::NoBrush);
// draw thumbnail/waveform
long media_length = clip->getMaximumLength();