/*
Copyright (C) 2022 Lari Varjonen <[email protected]>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
version 2 as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "main_window.h"
#include "ui_main_window.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
show();
setWindowTitle(app_name);
fe = new FramesExtractor();
draw_pixmap = new DrawPixmap();
image_label = new QLabel();
image_label->setAlignment(Qt::AlignTop | Qt::AlignLeft);
image_label->setStyleSheet("background-color:black;");
ui->scroll_area->setWidget(image_label);
setAcceptDrops(true);
}
MainWindow::~MainWindow()
{
delete fe;
delete draw_pixmap;
delete ui;
delete image_label;
}
void MainWindow::dragEnterEvent(QDragEnterEvent *event)
{
if (event->mimeData()->hasUrls())
{
event->acceptProposedAction();
}
}
void MainWindow::dropEvent(QDropEvent *e)
{
const QUrl url = e->mimeData()->urls().at(0);
QString new_video_path = url.toLocalFile();
if (fe->set_video(new_video_path))
{
InfoView iv(InfoView::window_type::ok,
"Invalid video file.",
"File is not a valid video file.",
this);
iv.exec();
return;
}
current_video_path = url.toLocalFile();
update_video_info();
on_generate_image_button_clicked();
}
void MainWindow::on_select_video_button_clicked()
{
QString new_video_path = QFileDialog::getOpenFileName(this, tr("Select a video file"), QDir::currentPath());
if (new_video_path.isEmpty())
{
return;
}
if (fe->set_video(new_video_path))
{
InfoView iv(InfoView::window_type::ok,
"Invalid video file.",
"File is not a valid video file.",
this);
iv.exec();
return;
}
current_video_path = new_video_path;
update_video_info();
}
void MainWindow::on_generate_image_button_clicked()
{
if (current_video_path.isEmpty())
{
return;
}
count_spin = ui->count_spin->value();
draw_pixmap->images.clear();
draw_pixmap->time_stamps.clear();
draw_pixmap->width_spin = ui->width_spin->value();
draw_pixmap->divider_spin = ui->divider_spin->value();
draw_pixmap->starting_height = 200;
on_updatetext_button_clicked();
int s_h = ui->start_h_spin->value();
int s_m = ui->start_m_spin->value();
int s_s = ui->start_s_spin->value();
start_time = (s_h * 60 * 60) + (s_m * 60) + s_s;
int e_h = ui->end_h_spin->value();
int e_m = ui->end_m_spin->value();
int e_s = ui->end_s_spin->value();
end_time = (e_h * 60 * 60) + (e_m * 60) + e_s;
double start_frame = start_time * current_video_fps;
double end_frame = end_time * current_video_fps;
if (start_frame > end_frame)
{
start_frame = end_frame;
}
if (start_frame > video_frame_count - current_video_fps)
{
start_frame = video_frame_count;
}
if (end_frame > video_frame_count - current_video_fps)
{
end_frame = video_frame_count;
}
get_pictures_from_video(start_frame, end_frame);
draw_pixmap->draw_pixmap(app_name);
image_label->setPixmap(draw_pixmap->generated_pixmap);
}
void MainWindow::on_updatetext_button_clicked()
{
if (current_video_path.isEmpty())
{
return;
}
draw_pixmap->file_text = ui->file_text->text();
draw_pixmap->video_text = ui->video_text->text();
draw_pixmap->audio_text = ui->audio_text->text();
draw_pixmap->size_text = ui->size_text->text();
draw_pixmap->draw_pixmap(app_name);
image_label->setPixmap(draw_pixmap->generated_pixmap);
}
void MainWindow::on_save_image_button_clicked()
{
if (draw_pixmap->generated_pixmap.isNull())
{
return;
}
QString new_file_path = QFileDialog::getSaveFileName(this,
tr("Save .png File"),
QDir::currentPath(),
tr("Images (*.png)"));
if (new_file_path.isEmpty())
{
return;
}
QFile file(new_file_path);
if(file.exists())
{
InfoView iv(InfoView::window_type::qa,
"File already exists.",
"File already exists. Do you want to overwrite it?",
this);
iv.exec();
if (iv.result_answer)
{
return;
}
}
draw_pixmap->generated_pixmap.save(new_file_path, "PNG");
}
void MainWindow::update_video_info()
{
QMap<QString, QString> map_ss = get_video_info(current_video_path);
video_frame_count = fe->get_video_frame_count();
current_video_fps = fe->get_video_fps();
QString duration = map_ss.value("duration-info");
QStringList strl = duration.split(":");
int duration_h = strl.at(0).toInt();
int duration_m = strl.at(1).toInt();
int duration_s = int(strl.at(2).toDouble());
ui->end_h_spin->setValue(duration_h);
ui->end_m_spin->setValue(duration_m);
ui->end_s_spin->setValue(duration_s);
QString file_text = map_ss.value("file-name");
QString video_text = map_ss.value("video-info");
QString audio_text = map_ss.value("audio-info");
QString bytes = map_ss.value("size-in-bytes");
QString in_MiB = map_ss.value("size-in-MiB");
QString size_text = tr("%1 bytes (%2), duration: %3")
.arg(bytes, in_MiB, duration);
ui->file_text->setText(file_text);
ui->video_text->setText(video_text);
ui->audio_text->setText(audio_text);
ui->size_text->setText(size_text);
ui->file_text->setCursorPosition(0);
ui->video_text->setCursorPosition(0);
ui->audio_text->setCursorPosition(0);
ui->size_text->setCursorPosition(0);
QFileInfo file_info(current_video_path);
QString file_name = file_info.fileName();
QString dir_name = file_info.absoluteDir().dirName();
setWindowTitle(tr("%1 @ %2 - %3").arg(file_name, dir_name, app_name));
}
QMap<QString, QString> MainWindow::get_video_info(QString video_path)
{
QProcess qp;
qp.setProgram(FFMPEG_EXE);
QStringList qp_args;
qp_args << "-i" << video_path;
qp.setArguments(qp_args);
qp.start();
qp.waitForFinished(-1);
QByteArray ba_result = qp.readAllStandardError();
QString s_result = QString(ba_result);
QMap<QString, QString> result_map;
s_result.replace('\n', " ");
while (s_result.contains(" "))
{
s_result.replace(" ", " ");
}
// Get name.
QString file_name = video_path.split("/").last();
result_map.insert("file-name", file_name);
QRegularExpression re;
QRegularExpressionMatch match;
// Get video.
re.setPattern(": Video: (.*?) Metadata:");
match = re.match(s_result);
if (match.hasMatch())
{
result_map.insert("video-info", match.captured(1));
}
else
{
result_map.insert("video-info", "<unknown video>");
}
// Get audio.
re.setPattern(": Audio: (.*?) Metadata:");
match = re.match(s_result);
if (match.hasMatch())
{
result_map.insert("audio-info", match.captured(1));
}
else
{
result_map.insert("audio-info", "<unknown audio>");
}
// Get duration.
re.setPattern("Duration: (.*?), ");
match = re.match(s_result);
if (match.hasMatch())
{
result_map.insert("duration-info", match.captured(1));
}
else
{
result_map.insert("duration-info", "<unknown duration>");
}
// Get bitrate.
re.setPattern(", bitrate: (.*? .*?) ");
match = re.match(s_result);
if (match.hasMatch())
{
result_map.insert("bitrate", match.captured(1));
}
else
{
result_map.insert("bitrate", "<unknown bitrate>");
}
// Get size in bytes and MiB.
qp.setProgram(STAT_EXE);
qp_args.clear();
qp_args << "--printf=\"%s\"" << video_path;
qp.setArguments(qp_args);
qp.start();
qp.waitForFinished(-1);
ba_result = qp.readAllStandardOutput();
s_result = QString(ba_result);
if (s_result.size() > 0)
{
s_result.replace("\"", "");
result_map.insert("size-in-bytes", s_result);
int bytes = s_result.toInt();
int size_MiB = ceil(bytes / 1024.0 / 1024.0);
s_result = QString::number(size_MiB) + "MiB";
result_map.insert("size-in-MiB", s_result);
}
else
{
result_map.insert("size-in-bytes", "<unknown byte size>");
result_map.insert("size-in-MiB", "<unknown MiB size>");
}
return result_map;
}
QString MainWindow::get_random_string(int size)
{
QString chars("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
QString return_str;
for(int i = 0; i < size; ++i)
{
int index = QRandomGenerator::global()->bounded(chars.size());
QChar c = chars.at(index);
return_str.append(c);
}
return return_str;
}
void MainWindow::get_pictures_from_video(double start_frame,
double end_frame)
{
QString random_name = get_random_string(8);
QString out_dir_path = tr("/tmp/%1/").arg(random_name);
while (QDir(out_dir_path).exists())
{
random_name = get_random_string(8);
out_dir_path = tr("/tmp/%1/").arg(random_name);
}
bool mkpath_result = QDir().mkpath(out_dir_path);
if (!mkpath_result)
{
return;
}
// Generate all pictures to temp dir.
Processing proc_ui(this);
fe->set(current_video_path,
out_dir_path,
count_spin,
start_frame,
end_frame,
current_video_fps);
connect(fe, &FramesExtractor::set_max, &proc_ui, &Processing::set_max);
connect(fe, &FramesExtractor::set_value, &proc_ui, &Processing::set_value);
connect(fe, &FramesExtractor::process_done, &proc_ui, &Processing::on_cancel_button_clicked);
fe->start();
proc_ui.exec();
fe->stop_work();
fe->wait();
// Read all pictures from temp dir.
QDir dir(out_dir_path);
dir.setSorting(QDir::NoSort);
dir.setFilter(QDir::AllEntries | QDir::NoDotAndDotDot);
QStringList imgs = dir.entryList(QStringList() << "*.jpg", QDir::Files);
if (imgs.isEmpty())
{
if (!QDir(out_dir_path).removeRecursively())
{
qCritical() << "Unable to remove temp directory.";
}
return;
}
QCollator coll;
coll.setNumericMode(true);
std::sort(imgs.begin(), imgs.end(), coll);
for (qsizetype i = 0; i < imgs.size(); i++)
{
QString file_name = imgs.at(i);
// Get image.
QUrl url = QUrl(tr("file://") + out_dir_path + file_name);
QImage image(url.path());
QImage img_scaled(image.scaledToWidth(
draw_pixmap->width_spin/draw_pixmap->divider_spin,
Qt::FastTransformation
));
draw_pixmap->images.push_back(img_scaled);
// Get image timestamp.
QRegularExpression re;
QRegularExpressionMatch match;
re.setPattern("\\[(.*)\\]");
match = re.match(file_name);
if (match.hasMatch())
{
draw_pixmap->time_stamps.push_back(match.captured(1));
}
}
// Fixing the last timestamp
// because the video fps value is just not accurate enough.
int e_h = ui->end_h_spin->value();
int e_m = ui->end_m_spin->value();
int e_s = ui->end_s_spin->value();
QString se_h = QString::number(e_h).rightJustified(2, '0');
QString se_m = QString::number(e_m).rightJustified(2, '0');
QString se_s = QString::number(e_s).rightJustified(2, '0');
QString new_last_timestamp = QString("%1:%2:%3").arg(se_h, se_m, se_s);
draw_pixmap->time_stamps.last() = new_last_timestamp;
if (!QDir(out_dir_path).removeRecursively())
{
qCritical() << "Unable to remove temp directory.";
}
}
void MainWindow::on_action_About_triggered()
{
AboutWindow aw(app_name, app_version, this);
aw.exec();
}
void MainWindow::on_action_Exit_triggered()
{
QApplication::quit();
}
void MainWindow::on_action_hide_signature_changed()
{
draw_pixmap->hide_signature = ui->action_hide_signature->isChecked();
on_updatetext_button_clicked();
}
void MainWindow::on_action_hide_timestamp_changed()
{
draw_pixmap->hide_timestamp = ui->action_hide_timestamp->isChecked();
draw_pixmap->draw_pixmap(app_name);
image_label->setPixmap(draw_pixmap->generated_pixmap);
}
/* Copyright (C) 2022 Lari Varjonen <[email protected]> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "main_window.h" #include "ui_main_window.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); show(); setWindowTitle(app_name); fe = new FramesExtractor(); draw_pixmap = new DrawPixmap(); image_label = new QLabel(); image_label->setAlignment(Qt::AlignTop | Qt::AlignLeft); image_label->setStyleSheet("background-color:black;"); ui->scroll_area->setWidget(image_label); setAcceptDrops(true); } MainWindow::~MainWindow() { delete fe; delete draw_pixmap; delete ui; delete image_label; } void MainWindow::dragEnterEvent(QDragEnterEvent *event) { if (event->mimeData()->hasUrls()) { event->acceptProposedAction(); } } void MainWindow::dropEvent(QDropEvent *e) { const QUrl url = e->mimeData()->urls().at(0); QString new_video_path = url.toLocalFile(); if (fe->set_video(new_video_path)) { InfoView iv(InfoView::window_type::ok, "Invalid video file.", "File is not a valid video file.", this); iv.exec(); return; } current_video_path = url.toLocalFile(); update_video_info(); on_generate_image_button_clicked(); } void MainWindow::on_select_video_button_clicked() { QString new_video_path = QFileDialog::getOpenFileName(this, tr("Select a video file"), QDir::currentPath()); if (new_video_path.isEmpty()) { return; } if (fe->set_video(new_video_path)) { InfoView iv(InfoView::window_type::ok, "Invalid video file.", "File is not a valid video file.", this); iv.exec(); return; } current_video_path = new_video_path; update_video_info(); } void MainWindow::on_generate_image_button_clicked() { if (current_video_path.isEmpty()) { return; } count_spin = ui->count_spin->value(); draw_pixmap->images.clear(); draw_pixmap->time_stamps.clear(); draw_pixmap->width_spin = ui->width_spin->value(); draw_pixmap->divider_spin = ui->divider_spin->value(); draw_pixmap->starting_height = 200; on_updatetext_button_clicked(); int s_h = ui->start_h_spin->value(); int s_m = ui->start_m_spin->value(); int s_s = ui->start_s_spin->value(); start_time = (s_h * 60 * 60) + (s_m * 60) + s_s; int e_h = ui->end_h_spin->value(); int e_m = ui->end_m_spin->value(); int e_s = ui->end_s_spin->value(); end_time = (e_h * 60 * 60) + (e_m * 60) + e_s; double start_frame = start_time * current_video_fps; double end_frame = end_time * current_video_fps; if (start_frame > end_frame) { start_frame = end_frame; } if (start_frame > video_frame_count - current_video_fps) { start_frame = video_frame_count; } if (end_frame > video_frame_count - current_video_fps) { end_frame = video_frame_count; } get_pictures_from_video(start_frame, end_frame); draw_pixmap->draw_pixmap(app_name); image_label->setPixmap(draw_pixmap->generated_pixmap); } void MainWindow::on_updatetext_button_clicked() { if (current_video_path.isEmpty()) { return; } draw_pixmap->file_text = ui->file_text->text(); draw_pixmap->video_text = ui->video_text->text(); draw_pixmap->audio_text = ui->audio_text->text(); draw_pixmap->size_text = ui->size_text->text(); draw_pixmap->draw_pixmap(app_name); image_label->setPixmap(draw_pixmap->generated_pixmap); } void MainWindow::on_save_image_button_clicked() { if (draw_pixmap->generated_pixmap.isNull()) { return; } QString new_file_path = QFileDialog::getSaveFileName(this, tr("Save .png File"), QDir::currentPath(), tr("Images (*.png)")); if (new_file_path.isEmpty()) { return; } QFile file(new_file_path); if(file.exists()) { InfoView iv(InfoView::window_type::qa, "File already exists.", "File already exists. Do you want to overwrite it?", this); iv.exec(); if (iv.result_answer) { return; } } draw_pixmap->generated_pixmap.save(new_file_path, "PNG"); } void MainWindow::update_video_info() { QMap<QString, QString> map_ss = get_video_info(current_video_path); video_frame_count = fe->get_video_frame_count(); current_video_fps = fe->get_video_fps(); QString duration = map_ss.value("duration-info"); QStringList strl = duration.split(":"); int duration_h = strl.at(0).toInt(); int duration_m = strl.at(1).toInt(); int duration_s = int(strl.at(2).toDouble()); ui->end_h_spin->setValue(duration_h); ui->end_m_spin->setValue(duration_m); ui->end_s_spin->setValue(duration_s); QString file_text = map_ss.value("file-name"); QString video_text = map_ss.value("video-info"); QString audio_text = map_ss.value("audio-info"); QString bytes = map_ss.value("size-in-bytes"); QString in_MiB = map_ss.value("size-in-MiB"); QString size_text = tr("%1 bytes (%2), duration: %3") .arg(bytes, in_MiB, duration); ui->file_text->setText(file_text); ui->video_text->setText(video_text); ui->audio_text->setText(audio_text); ui->size_text->setText(size_text); ui->file_text->setCursorPosition(0); ui->video_text->setCursorPosition(0); ui->audio_text->setCursorPosition(0); ui->size_text->setCursorPosition(0); QFileInfo file_info(current_video_path); QString file_name = file_info.fileName(); QString dir_name = file_info.absoluteDir().dirName(); setWindowTitle(tr("%1 @ %2 - %3").arg(file_name, dir_name, app_name)); } QMap<QString, QString> MainWindow::get_video_info(QString video_path) { QProcess qp; qp.setProgram(FFMPEG_EXE); QStringList qp_args; qp_args << "-i" << video_path; qp.setArguments(qp_args); qp.start(); qp.waitForFinished(-1); QByteArray ba_result = qp.readAllStandardError(); QString s_result = QString(ba_result); QMap<QString, QString> result_map; s_result.replace('\n', " "); while (s_result.contains(" ")) { s_result.replace(" ", " "); } // Get name. QString file_name = video_path.split("/").last(); result_map.insert("file-name", file_name); QRegularExpression re; QRegularExpressionMatch match; // Get video. re.setPattern(": Video: (.*?) Metadata:"); match = re.match(s_result); if (match.hasMatch()) { result_map.insert("video-info", match.captured(1)); } else { result_map.insert("video-info", "<unknown video>"); } // Get audio. re.setPattern(": Audio: (.*?) Metadata:"); match = re.match(s_result); if (match.hasMatch()) { result_map.insert("audio-info", match.captured(1)); } else { result_map.insert("audio-info", "<unknown audio>"); } // Get duration. re.setPattern("Duration: (.*?), "); match = re.match(s_result); if (match.hasMatch()) { result_map.insert("duration-info", match.captured(1)); } else { result_map.insert("duration-info", "<unknown duration>"); } // Get bitrate. re.setPattern(", bitrate: (.*? .*?) "); match = re.match(s_result); if (match.hasMatch()) { result_map.insert("bitrate", match.captured(1)); } else { result_map.insert("bitrate", "<unknown bitrate>"); } // Get size in bytes and MiB. qp.setProgram(STAT_EXE); qp_args.clear(); qp_args << "--printf=\"%s\"" << video_path; qp.setArguments(qp_args); qp.start(); qp.waitForFinished(-1); ba_result = qp.readAllStandardOutput(); s_result = QString(ba_result); if (s_result.size() > 0) { s_result.replace("\"", ""); result_map.insert("size-in-bytes", s_result); int bytes = s_result.toInt(); int size_MiB = ceil(bytes / 1024.0 / 1024.0); s_result = QString::number(size_MiB) + "MiB"; result_map.insert("size-in-MiB", s_result); } else { result_map.insert("size-in-bytes", "<unknown byte size>"); result_map.insert("size-in-MiB", "<unknown MiB size>"); } return result_map; } QString MainWindow::get_random_string(int size) { QString chars("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); QString return_str; for(int i = 0; i < size; ++i) { int index = QRandomGenerator::global()->bounded(chars.size()); QChar c = chars.at(index); return_str.append(c); } return return_str; } void MainWindow::get_pictures_from_video(double start_frame, double end_frame) { QString random_name = get_random_string(8); QString out_dir_path = tr("/tmp/%1/").arg(random_name); while (QDir(out_dir_path).exists()) { random_name = get_random_string(8); out_dir_path = tr("/tmp/%1/").arg(random_name); } bool mkpath_result = QDir().mkpath(out_dir_path); if (!mkpath_result) { return; } // Generate all pictures to temp dir. Processing proc_ui(this); fe->set(current_video_path, out_dir_path, count_spin, start_frame, end_frame, current_video_fps); connect(fe, &FramesExtractor::set_max, &proc_ui, &Processing::set_max); connect(fe, &FramesExtractor::set_value, &proc_ui, &Processing::set_value); connect(fe, &FramesExtractor::process_done, &proc_ui, &Processing::on_cancel_button_clicked); fe->start(); proc_ui.exec(); fe->stop_work(); fe->wait(); // Read all pictures from temp dir. QDir dir(out_dir_path); dir.setSorting(QDir::NoSort); dir.setFilter(QDir::AllEntries | QDir::NoDotAndDotDot); QStringList imgs = dir.entryList(QStringList() << "*.jpg", QDir::Files); if (imgs.isEmpty()) { if (!QDir(out_dir_path).removeRecursively()) { qCritical() << "Unable to remove temp directory."; } return; } QCollator coll; coll.setNumericMode(true); std::sort(imgs.begin(), imgs.end(), coll); for (qsizetype i = 0; i < imgs.size(); i++) { QString file_name = imgs.at(i); // Get image. QUrl url = QUrl(tr("file://") + out_dir_path + file_name); QImage image(url.path()); QImage img_scaled(image.scaledToWidth( draw_pixmap->width_spin/draw_pixmap->divider_spin, Qt::FastTransformation )); draw_pixmap->images.push_back(img_scaled); // Get image timestamp. QRegularExpression re; QRegularExpressionMatch match; re.setPattern("\\[(.*)\\]"); match = re.match(file_name); if (match.hasMatch()) { draw_pixmap->time_stamps.push_back(match.captured(1)); } } // Fixing the last timestamp // because the video fps value is just not accurate enough. int e_h = ui->end_h_spin->value(); int e_m = ui->end_m_spin->value(); int e_s = ui->end_s_spin->value(); QString se_h = QString::number(e_h).rightJustified(2, '0'); QString se_m = QString::number(e_m).rightJustified(2, '0'); QString se_s = QString::number(e_s).rightJustified(2, '0'); QString new_last_timestamp = QString("%1:%2:%3").arg(se_h, se_m, se_s); draw_pixmap->time_stamps.last() = new_last_timestamp; if (!QDir(out_dir_path).removeRecursively()) { qCritical() << "Unable to remove temp directory."; } } void MainWindow::on_action_About_triggered() { AboutWindow aw(app_name, app_version, this); aw.exec(); } void MainWindow::on_action_Exit_triggered() { QApplication::quit(); } void MainWindow::on_action_hide_signature_changed() { draw_pixmap->hide_signature = ui->action_hide_signature->isChecked(); on_updatetext_button_clicked(); } void MainWindow::on_action_hide_timestamp_changed() { draw_pixmap->hide_timestamp = ui->action_hide_timestamp->isChecked(); draw_pixmap->draw_pixmap(app_name); image_label->setPixmap(draw_pixmap->generated_pixmap); }
/*
    Copyright (C) 2022 Lari Varjonen <[email protected]>

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    version 2 as published by the Free Software Foundation.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

#include "main_window.h"
#include "ui_main_window.h"


MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    show();
    setWindowTitle(app_name);

    fe = new FramesExtractor();
    draw_pixmap = new DrawPixmap();
    image_label = new QLabel();

    image_label->setAlignment(Qt::AlignTop | Qt::AlignLeft);
    image_label->setStyleSheet("background-color:black;");

    ui->scroll_area->setWidget(image_label);
    setAcceptDrops(true);
}

MainWindow::~MainWindow()
{
    delete fe;
    delete draw_pixmap;
    delete ui;
    delete image_label;
}

void MainWindow::dragEnterEvent(QDragEnterEvent *event)
{
    if (event->mimeData()->hasUrls())
    {
        event->acceptProposedAction();
    }
}

void MainWindow::dropEvent(QDropEvent *e)
{
    const QUrl url = e->mimeData()->urls().at(0);
    QString new_video_path = url.toLocalFile();


    if (fe->set_video(new_video_path))
    {
        InfoView iv(InfoView::window_type::ok,
                    "Invalid video file.",
                    "File is not a valid video file.",
                    this);
        iv.exec();
        return;
    }

    current_video_path = url.toLocalFile();
    update_video_info();
    on_generate_image_button_clicked();
}

void MainWindow::on_select_video_button_clicked()
{
    QString new_video_path = QFileDialog::getOpenFileName(this, tr("Select a video file"), QDir::currentPath());

    if (new_video_path.isEmpty())
    {
        return;
    }


    if (fe->set_video(new_video_path))
    {
        InfoView iv(InfoView::window_type::ok,
                    "Invalid video file.",
                    "File is not a valid video file.",
                    this);
        iv.exec();
        return;
    }

    current_video_path = new_video_path;
    update_video_info();
}

void MainWindow::on_generate_image_button_clicked()
{
    if (current_video_path.isEmpty())
    {
        return;
    }

    count_spin = ui->count_spin->value();

    draw_pixmap->images.clear();
    draw_pixmap->time_stamps.clear();

    draw_pixmap->width_spin = ui->width_spin->value();
    draw_pixmap->divider_spin = ui->divider_spin->value();
    draw_pixmap->starting_height = 200;

    on_updatetext_button_clicked();

    int s_h = ui->start_h_spin->value();
    int s_m = ui->start_m_spin->value();
    int s_s = ui->start_s_spin->value();
    start_time = (s_h * 60 * 60) + (s_m * 60) + s_s;

    int e_h = ui->end_h_spin->value();
    int e_m = ui->end_m_spin->value();
    int e_s = ui->end_s_spin->value();
    end_time = (e_h * 60 * 60) + (e_m * 60) + e_s;

    double start_frame = start_time * current_video_fps;
    double end_frame = end_time * current_video_fps;

    if (start_frame > end_frame)
    {
        start_frame = end_frame;
    }
    if (start_frame > video_frame_count - current_video_fps)
    {
        start_frame = video_frame_count;
    }
    if (end_frame > video_frame_count - current_video_fps)
    {
        end_frame = video_frame_count;
    }

    get_pictures_from_video(start_frame, end_frame);

    draw_pixmap->draw_pixmap(app_name);
    image_label->setPixmap(draw_pixmap->generated_pixmap);
}

void MainWindow::on_updatetext_button_clicked()
{
    if (current_video_path.isEmpty())
    {
        return;
    }

    draw_pixmap->file_text = ui->file_text->text();
    draw_pixmap->video_text = ui->video_text->text();
    draw_pixmap->audio_text = ui->audio_text->text();
    draw_pixmap->size_text = ui->size_text->text();

    draw_pixmap->draw_pixmap(app_name);
    image_label->setPixmap(draw_pixmap->generated_pixmap);
}

void MainWindow::on_save_image_button_clicked()
{
    if (draw_pixmap->generated_pixmap.isNull())
    {
        return;
    }
    QString new_file_path = QFileDialog::getSaveFileName(this,
                                                    tr("Save .png File"),
                                                    QDir::currentPath(),
                                                    tr("Images (*.png)"));
    if (new_file_path.isEmpty())
    {
        return;
    }
    QFile file(new_file_path);
    if(file.exists())
    {
        InfoView iv(InfoView::window_type::qa,
                    "File already exists.",
                    "File already exists. Do you want to overwrite it?",
                    this);
        iv.exec();
        if (iv.result_answer)
        {
            return;
        }
    }
    draw_pixmap->generated_pixmap.save(new_file_path, "PNG");
}

void MainWindow::update_video_info()
{
    QMap<QString, QString> map_ss = get_video_info(current_video_path);

    video_frame_count = fe->get_video_frame_count();
    current_video_fps = fe->get_video_fps();

    QString duration = map_ss.value("duration-info");
    QStringList strl = duration.split(":");

    int duration_h = strl.at(0).toInt();
    int duration_m = strl.at(1).toInt();
    int duration_s = int(strl.at(2).toDouble());

    ui->end_h_spin->setValue(duration_h);
    ui->end_m_spin->setValue(duration_m);
    ui->end_s_spin->setValue(duration_s);

    QString file_text = map_ss.value("file-name");
    QString video_text = map_ss.value("video-info");
    QString audio_text = map_ss.value("audio-info");

    QString bytes = map_ss.value("size-in-bytes");
    QString in_MiB = map_ss.value("size-in-MiB");

    QString size_text = tr("%1 bytes (%2), duration: %3")
            .arg(bytes, in_MiB, duration);

    ui->file_text->setText(file_text);
    ui->video_text->setText(video_text);
    ui->audio_text->setText(audio_text);
    ui->size_text->setText(size_text);

    ui->file_text->setCursorPosition(0);
    ui->video_text->setCursorPosition(0);
    ui->audio_text->setCursorPosition(0);
    ui->size_text->setCursorPosition(0);

    QFileInfo file_info(current_video_path);
    QString file_name = file_info.fileName();
    QString dir_name = file_info.absoluteDir().dirName();

    setWindowTitle(tr("%1 @ %2 - %3").arg(file_name, dir_name, app_name));
}

QMap<QString, QString> MainWindow::get_video_info(QString video_path)
{
    QProcess qp;
    qp.setProgram(FFMPEG_EXE);

    QStringList qp_args;
    qp_args << "-i" << video_path;

    qp.setArguments(qp_args);
    qp.start();
    qp.waitForFinished(-1);

    QByteArray ba_result = qp.readAllStandardError();
    QString s_result = QString(ba_result);
    QMap<QString, QString> result_map;

    s_result.replace('\n', " ");
    while (s_result.contains("  "))
    {
        s_result.replace("  ", " ");
    }

    // Get name.
    QString file_name = video_path.split("/").last();
    result_map.insert("file-name", file_name);

    QRegularExpression re;
    QRegularExpressionMatch match;

    // Get video.
    re.setPattern(": Video: (.*?) Metadata:");
    match = re.match(s_result);
    if (match.hasMatch())
    {
        result_map.insert("video-info", match.captured(1));
    }
    else
    {
        result_map.insert("video-info", "<unknown video>");
    }

    // Get audio.
    re.setPattern(": Audio: (.*?) Metadata:");
    match = re.match(s_result);
    if (match.hasMatch())
    {
        result_map.insert("audio-info", match.captured(1));
    }
    else
    {
        result_map.insert("audio-info", "<unknown audio>");
    }

    // Get duration.
    re.setPattern("Duration: (.*?), ");
    match = re.match(s_result);
    if (match.hasMatch())
    {
        result_map.insert("duration-info", match.captured(1));
    }
    else
    {
        result_map.insert("duration-info", "<unknown duration>");
    }

    // Get bitrate.
    re.setPattern(", bitrate: (.*? .*?) ");
    match = re.match(s_result);
    if (match.hasMatch())
    {
        result_map.insert("bitrate", match.captured(1));
    }
    else
    {
        result_map.insert("bitrate", "<unknown bitrate>");
    }


    // Get size in bytes and MiB.
    qp.setProgram(STAT_EXE);
    qp_args.clear();
    qp_args << "--printf=\"%s\"" << video_path;

    qp.setArguments(qp_args);
    qp.start();
    qp.waitForFinished(-1);

    ba_result = qp.readAllStandardOutput();
    s_result = QString(ba_result);

    if (s_result.size() > 0)
    {
        s_result.replace("\"", "");
        result_map.insert("size-in-bytes", s_result);

        int bytes = s_result.toInt();
        int size_MiB = ceil(bytes / 1024.0 / 1024.0);
        s_result = QString::number(size_MiB) + "MiB";

        result_map.insert("size-in-MiB", s_result);
    }
    else
    {
        result_map.insert("size-in-bytes", "<unknown byte size>");
        result_map.insert("size-in-MiB", "<unknown MiB size>");
    }

    return result_map;
}

QString MainWindow::get_random_string(int size)
{
   QString chars("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
   QString return_str;
   for(int i = 0; i < size; ++i)
   {
       int index = QRandomGenerator::global()->bounded(chars.size());
       QChar c = chars.at(index);
       return_str.append(c);
   }
   return return_str;
}

void MainWindow::get_pictures_from_video(double start_frame,
                                         double end_frame)
{
    QString random_name = get_random_string(8);
    QString out_dir_path = tr("/tmp/%1/").arg(random_name);

    while (QDir(out_dir_path).exists())
    {
        random_name = get_random_string(8);
        out_dir_path = tr("/tmp/%1/").arg(random_name);
    }
    bool mkpath_result = QDir().mkpath(out_dir_path);
    if (!mkpath_result)
    {
        return;
    }

    // Generate all pictures to temp dir.
    Processing proc_ui(this);
    fe->set(current_video_path,
           out_dir_path,
           count_spin,
           start_frame,
           end_frame,
           current_video_fps);

    connect(fe, &FramesExtractor::set_max, &proc_ui, &Processing::set_max);
    connect(fe, &FramesExtractor::set_value, &proc_ui, &Processing::set_value);
    connect(fe, &FramesExtractor::process_done, &proc_ui, &Processing::on_cancel_button_clicked);

    fe->start();
    proc_ui.exec();
    fe->stop_work();
    fe->wait();


    // Read all pictures from temp dir.
    QDir dir(out_dir_path);
    dir.setSorting(QDir::NoSort);
    dir.setFilter(QDir::AllEntries | QDir::NoDotAndDotDot);
    QStringList imgs = dir.entryList(QStringList() << "*.jpg", QDir::Files);

    if (imgs.isEmpty())
    {
        if (!QDir(out_dir_path).removeRecursively())
        {
            qCritical() << "Unable to remove temp directory.";
        }
        return;
    }

    QCollator coll;
    coll.setNumericMode(true);
    std::sort(imgs.begin(), imgs.end(), coll);


    for (qsizetype i = 0; i < imgs.size(); i++)
    {
        QString file_name = imgs.at(i);

        // Get image.
        QUrl url = QUrl(tr("file://") + out_dir_path + file_name);
        QImage image(url.path());
        QImage img_scaled(image.scaledToWidth(
                              draw_pixmap->width_spin/draw_pixmap->divider_spin,
                              Qt::FastTransformation
                              ));
        draw_pixmap->images.push_back(img_scaled);

        // Get image timestamp.
        QRegularExpression re;
        QRegularExpressionMatch match;

        re.setPattern("\\[(.*)\\]");
        match = re.match(file_name);
        if (match.hasMatch())
        {
            draw_pixmap->time_stamps.push_back(match.captured(1));
        }
    }

    // Fixing the last timestamp
    // because the video fps value is just not accurate enough.
    int e_h = ui->end_h_spin->value();
    int e_m = ui->end_m_spin->value();
    int e_s = ui->end_s_spin->value();

    QString se_h = QString::number(e_h).rightJustified(2, '0');
    QString se_m = QString::number(e_m).rightJustified(2, '0');
    QString se_s = QString::number(e_s).rightJustified(2, '0');

    QString new_last_timestamp = QString("%1:%2:%3").arg(se_h, se_m, se_s);
    draw_pixmap->time_stamps.last() = new_last_timestamp;


    if (!QDir(out_dir_path).removeRecursively())
    {
        qCritical() << "Unable to remove temp directory.";
    }
}

void MainWindow::on_action_About_triggered()
{
    AboutWindow aw(app_name, app_version, this);
    aw.exec();
}

void MainWindow::on_action_Exit_triggered()
{
    QApplication::quit();
}

void MainWindow::on_action_hide_signature_changed()
{
    draw_pixmap->hide_signature = ui->action_hide_signature->isChecked();
    on_updatetext_button_clicked();
}

void MainWindow::on_action_hide_timestamp_changed()
{
    draw_pixmap->hide_timestamp = ui->action_hide_timestamp->isChecked();

    draw_pixmap->draw_pixmap(app_name);
    image_label->setPixmap(draw_pixmap->generated_pixmap);
}