now waveforms and thumbnails are cached
This commit is contained in:
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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
@@ -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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user