diff --git a/dialogs/loaddialog.cpp b/dialogs/loaddialog.cpp new file mode 100644 index 000000000..47a641810 --- /dev/null +++ b/dialogs/loaddialog.cpp @@ -0,0 +1,30 @@ +#include "loaddialog.h" + +#include +#include +#include +#include +#include + +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); +} diff --git a/dialogs/loaddialog.h b/dialogs/loaddialog.h new file mode 100644 index 000000000..7f6b0a77f --- /dev/null +++ b/dialogs/loaddialog.h @@ -0,0 +1,16 @@ +#ifndef LOADDIALOG_H +#define LOADDIALOG_H + +#include + +class QProgressBar; + +class LoadDialog : public QDialog +{ +public: + LoadDialog(QWidget* parent = 0); +private: + QProgressBar* bar; +}; + +#endif // LOADDIALOG_H diff --git a/io/crc32.cpp b/io/crc32.cpp new file mode 100644 index 000000000..063a74780 --- /dev/null +++ b/io/crc32.cpp @@ -0,0 +1,73 @@ +#include "crc32.h" + +#include + +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; + } +} diff --git a/io/crc32.h b/io/crc32.h new file mode 100644 index 000000000..0814238b3 --- /dev/null +++ b/io/crc32.h @@ -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 +#include +#include + +class Crc32 +{ +private: + quint32 crc_table[256]; + QMap 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 diff --git a/io/media.h b/io/media.h index 3e16410a7..39a937507 100644 --- a/io/media.h +++ b/io/media.h @@ -37,7 +37,7 @@ struct MediaStream { // preview thumbnail/waveform bool preview_done; QImage video_preview; // TODO change to QPixmap - QVector audio_preview; + QVector audio_preview; }; struct Media { diff --git a/io/previewgenerator.cpp b/io/previewgenerator.cpp index 0443e567c..ad1c6628d 100644 --- a/io/previewgenerator.cpp +++ b/io/previewgenerator.cpp @@ -4,12 +4,18 @@ #include "panels/viewer.h" #include "panels/project.h" #include "io/config.h" +#include "io/crc32.h" #include #include #include #include #include +#include +#include +#include +#include +#include #define WAVEFORM_RESOLUTION 64 @@ -20,6 +26,8 @@ extern "C" { #include } +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;ivideo_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;iaudio_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;jaudio_preview[j] = data.at(j); + } + ms->preview_done = true; + f.close(); + } else { + found = false; + break; + } + } + if (!found) { + for (int i=0;ivideo_tracks.size();i++) { + MediaStream* ms = media->video_tracks.at(i); + ms->preview_done = false; + } + for (int i=0;iaudio_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;ivideo_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;iaudio_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); } diff --git a/io/previewgenerator.h b/io/previewgenerator.h index f0439a828..864ae6975 100644 --- a/io/previewgenerator.h +++ b/io/previewgenerator.h @@ -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 diff --git a/main.cpp b/main.cpp index 2615101ab..60800c5db 100644 --- a/main.cpp +++ b/main.cpp @@ -6,8 +6,7 @@ extern "C" { #include } -int main(int argc, char *argv[]) -{ +int main(int argc, char *argv[]) { // init ffmpeg subsystem av_register_all(); avfilter_register_all(); diff --git a/olive.pro b/olive.pro index ae60fa782..42bc6fefb 100644 --- a/olive.pro +++ b/olive.pro @@ -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 \ diff --git a/panels/project.cpp b/panels/project.cpp index eb15cdb1b..104c01e01 100644 --- a/panels/project.cpp +++ b/panels/project.cpp @@ -20,6 +20,7 @@ #include "panels/effectcontrols.h" #include "dialogs/newsequencedialog.h" #include "dialogs/mediapropertiesdialog.h" +#include "dialogs/loaddialog.h" #include #include @@ -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"; diff --git a/panels/timeline.cpp b/panels/timeline.cpp index 2818ee73f..8448528b2 100644 --- a/panels/timeline.cpp +++ b/panels/timeline.cpp @@ -318,8 +318,6 @@ void Timeline::delete_selection(QVector& 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)); } diff --git a/ui/timelinewidget.cpp b/ui/timelinewidget.cpp index 67b33a47b..42ad2d45c 100644 --- a/ui/timelinewidget.cpp +++ b/ui/timelinewidget.cpp @@ -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(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();