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 "ui_main_window.h"
MainWindow::MainWindow(QWidget *parent)
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);
MainWindow::~MainWindow()
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,
"File is not a valid video file.",
current_video_path = url.toLocalFile();
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())
if (fe->set_video(new_video_path))
InfoView iv(InfoView::window_type::ok,
"File is not a valid video file.",
current_video_path = new_video_path;
void MainWindow::on_generate_image_button_clicked()
if (current_video_path.isEmpty())
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)
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())
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())
QString new_file_path = QFileDialog::getSaveFileName(this,
if (new_file_path.isEmpty())
QFile file(new_file_path);
InfoView iv(InfoView::window_type::qa,
"File already exists. Do you want to overwrite it?",
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)
qp.setProgram(FFMPEG_EXE);
qp_args << "-i" << video_path;
qp.setArguments(qp_args);
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(" ", " ");
QString file_name = video_path.split("/").last();
result_map.insert("file-name", file_name);
QRegularExpressionMatch match;
re.setPattern(": Video: (.*?) Metadata:");
match = re.match(s_result);
result_map.insert("video-info", match.captured(1));
result_map.insert("video-info", "<unknown video>");
re.setPattern(": Audio: (.*?) Metadata:");
match = re.match(s_result);
result_map.insert("audio-info", match.captured(1));
result_map.insert("audio-info", "<unknown audio>");
re.setPattern("Duration: (.*?), ");
match = re.match(s_result);
result_map.insert("duration-info", match.captured(1));
result_map.insert("duration-info", "<unknown duration>");
re.setPattern(", bitrate: (.*? .*?) ");
match = re.match(s_result);
result_map.insert("bitrate", match.captured(1));
result_map.insert("bitrate", "<unknown bitrate>");
// Get size in bytes and MiB.
qp_args << "--printf=\"%s\"" << video_path;
qp.setArguments(qp_args);
ba_result = qp.readAllStandardOutput();
s_result = QString(ba_result);
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);
result_map.insert("size-in-bytes", "<unknown byte size>");
result_map.insert("size-in-MiB", "<unknown MiB size>");
QString MainWindow::get_random_string(int size)
QString chars("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
for(int i = 0; i < size; ++i)
int index = QRandomGenerator::global()->bounded(chars.size());
QChar c = chars.at(index);
void MainWindow::get_pictures_from_video(double start_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);
// Generate all pictures to temp dir.
Processing proc_ui(this);
fe->set(current_video_path,
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);
// Read all pictures from temp dir.
dir.setSorting(QDir::NoSort);
dir.setFilter(QDir::AllEntries | QDir::NoDotAndDotDot);
QStringList imgs = dir.entryList(QStringList() << "*.jpg", QDir::Files);
if (!QDir(out_dir_path).removeRecursively())
qCritical() << "Unable to remove temp directory.";
coll.setNumericMode(true);
std::sort(imgs.begin(), imgs.end(), coll);
for (qsizetype i = 0; i < imgs.size(); i++)
QString file_name = imgs.at(i);
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,
draw_pixmap->images.push_back(img_scaled);
QRegularExpressionMatch match;
re.setPattern("\\[(.*)\\]");
match = re.match(file_name);
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);
void MainWindow::on_action_Exit_triggered()
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);
}