Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 113 additions & 1 deletion src/videopreview/videopreview.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
#include <QImageWriter>
#include <QImageReader>
#include <QDebug>
#include <QCryptographicHash>
#include <QFile>

#include <cmath>

Expand Down Expand Up @@ -76,6 +78,7 @@ VideoPreview::VideoPreview(QString mplayer_path, QWidget * parent) : QWidget(par
prop.aspect_ratio = 0;
prop.display_osd = true;
prop.extract_format = JPEG;
prop.hash_algorithm = HASH_MD5;

output_dir = "smplayer_preview";
full_output_dir = QDir::tempPath() +"/"+ output_dir;
Expand Down Expand Up @@ -247,6 +250,10 @@ bool VideoPreview::extractImages() {
}
}

// Store hash information for later use in saveImage
file_hash = i.file_hash;
hash_algorithm_name = i.hash_algorithm_name;

displayVideoInfo(i);

// Let's begin
Expand Down Expand Up @@ -299,6 +306,47 @@ bool VideoPreview::extractImages() {
return true;
}

QString VideoPreview::calculateHash(const QString & filename, HashAlgorithm algorithm) {
if (algorithm == HASH_NONE) {
return QString();
}

QFile file(filename);
if (!file.open(QIODevice::ReadOnly)) {
qDebug("VideoPreview::calculateHash: error opening file");
return QString();
}

QCryptographicHash::Algorithm hashAlg;
switch (algorithm) {
case HASH_SHA1:
hashAlg = QCryptographicHash::Sha1;
break;
case HASH_SHA256:
hashAlg = QCryptographicHash::Sha256;
break;
case HASH_MD5:
default:
hashAlg = QCryptographicHash::Md5;
break;
}

QCryptographicHash hash(hashAlg);

// Read file in chunks to avoid memory issues with large files
const qint64 chunkSize = 64 * 1024; // 64KB chunks
while (!file.atEnd()) {
QByteArray chunk = file.read(chunkSize);
if (chunk.isEmpty()) {
break;
}
hash.addData(chunk);
}

file.close();
return hash.result().toHex();
}

#if defined(Q_OS_UNIX) && !defined(NO_SMPLAYER_SUPPORT)
bool VideoPreview::isOptionAvailableinMPV(const QString & option) {
static QStringList option_list;
Expand Down Expand Up @@ -518,7 +566,12 @@ void VideoPreview::displayVideoInfo(const VideoInfo & i) {
QString audio_bitrate = (i.audio_bitrate==0) ? no_info : tr("%1 kbps").arg(i.audio_bitrate/1000);
QString audio_rate = (i.audio_rate==0) ? no_info : tr("%1 Hz").arg(i.audio_rate);

title->setText("<h2 " FONT_STYLE ">" + i.filename + "</h2>");
// Add hash information to filename if available
QString titleText = i.filename;
if (!i.file_hash.isEmpty() && !i.hash_algorithm_name.isEmpty()) {
titleText += QString(" (%1: %2)").arg(i.hash_algorithm_name).arg(i.file_hash);
}
title->setText("<h2 " FONT_STYLE ">" + titleText + "</h2>");

int count = 1;

Expand Down Expand Up @@ -579,6 +632,26 @@ VideoInfo VideoPreview::getInfo(const QString & mplayer_path, const QString & fi
if (fi.exists()) {
i.filename = fi.fileName();
i.size = fi.size();
// Calculate hash of the video file using the selected algorithm
i.file_hash = calculateHash(filename, prop.hash_algorithm);
switch (prop.hash_algorithm) {
case HASH_MD5:
i.hash_algorithm_name = "MD5";
break;
case HASH_SHA1:
i.hash_algorithm_name = "SHA1";
break;
case HASH_SHA256:
i.hash_algorithm_name = "SHA256";
break;
case HASH_NONE:
default:
i.hash_algorithm_name = "";
break;
}
if (!i.file_hash.isEmpty()) {
qDebug("VideoPreview::getInfo: %s hash: '%s'", i.hash_algorithm_name.toUtf8().constData(), i.file_hash.toUtf8().constData());
}
}

QRegExp rx("^ID_(.*)=(.*)");
Expand Down Expand Up @@ -721,6 +794,41 @@ void VideoPreview::saveImage() {
image = image.scaledToWidth(prop.max_width, Qt::SmoothTransformation);
qDebug("VideoPreview::saveImage: image scaled to : %d %d", image.size().width(), image.size().height());
}

// Add hash information to the image
if (!file_hash.isEmpty() && !hash_algorithm_name.isEmpty()) {
QPainter painter(&image);
QFont font;
font.setPointSize(10);
font.setBold(true);
painter.setFont(font);

QString hash_text = QString("%1: %2").arg(hash_algorithm_name).arg(file_hash);

// Calculate text dimensions
QFontMetrics fm(font);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
int text_width = fm.horizontalAdvance(hash_text);
#else
int text_width = fm.width(hash_text);
#endif
int text_height = fm.height();

// Position at bottom left (changed from bottom right)
int x = 10;
int y = image.height() - text_height - 5;

// Draw background rectangle
QRect bg_rect(x - 5, y - 2, text_width + 10, text_height + 4);
painter.fillRect(bg_rect, QBrush(QColor(255, 255, 255, 200)));

// Draw text
painter.setPen(Qt::black);
painter.drawText(x, y + text_height - 5, hash_text);

qDebug("VideoPreview::saveImage: added %s hash to image: %s", hash_algorithm_name.toUtf8().constData(), file_hash.toUtf8().constData());
}

if (!image.save(filename)) {
// Failed!!!
qDebug("VideoPreview::saveImage: error saving '%s'", filename.toUtf8().constData());
Expand All @@ -744,6 +852,7 @@ bool VideoPreview::showConfigDialog(QWidget * parent) {
d.setDisplayOSD( displayOSD() );
d.setAspectRatio( aspectRatio() );
d.setFormat( extractFormat() );
d.setHashAlgorithm( hashAlgorithm() );
d.setSaveLastDirectory( save_last_directory );

if (d.exec() == QDialog::Accepted) {
Expand All @@ -756,6 +865,7 @@ bool VideoPreview::showConfigDialog(QWidget * parent) {
setDisplayOSD( d.displayOSD() );
setAspectRatio( d.aspectRatio() );
setExtractFormat(d.format() );
setHashAlgorithm(d.hashAlgorithm() );
save_last_directory = d.saveLastDirectory();

return true;
Expand All @@ -775,6 +885,7 @@ void VideoPreview::saveSettings() {
set->setValue("max_width", maxWidth());
set->setValue("osd", displayOSD());
set->setValue("format", extractFormat());
set->setValue("hash_algorithm", hashAlgorithm());
set->setValue("save_last_directory", save_last_directory);

if (save_last_directory) {
Expand All @@ -800,6 +911,7 @@ void VideoPreview::loadSettings() {
setMaxWidth( set->value("max_width", maxWidth()).toInt() );
setDisplayOSD( set->value("osd", displayOSD()).toBool() );
setExtractFormat( (ExtractFormat) set->value("format", extractFormat()).toInt() );
setHashAlgorithm( (HashAlgorithm) set->value("hash_algorithm", hashAlgorithm()).toInt() );
save_last_directory = set->value("save_last_directory", save_last_directory).toBool();
last_directory = set->value("last_directory", last_directory).toString();

Expand Down
13 changes: 12 additions & 1 deletion src/videopreview/videopreview.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class VideoInfo
public:
VideoInfo() { filename.clear(); width = 0; height = 0; length = 0;
size = 0; fps = 0; aspect = 0; video_bitrate = 0;
audio_bitrate = 0; audio_rate = 0; };
audio_bitrate = 0; audio_rate = 0; file_hash.clear(); hash_algorithm_name.clear(); };
~VideoInfo() {};

QString filename;
Expand All @@ -51,6 +51,8 @@ class VideoInfo
int audio_rate;
QString video_format;
QString audio_format;
QString file_hash;
QString hash_algorithm_name;
};

class VideoPreview : public QWidget
Expand All @@ -59,6 +61,7 @@ class VideoPreview : public QWidget

public:
enum ExtractFormat { JPEG = 1, PNG = 2 };
enum HashAlgorithm { HASH_NONE = 0, HASH_MD5 = 1, HASH_SHA1 = 2, HASH_SHA256 = 3 };

VideoPreview(QString mplayer_path, QWidget * parent = 0);
~VideoPreview();
Expand Down Expand Up @@ -95,6 +98,9 @@ class VideoPreview : public QWidget
void setExtractFormat( ExtractFormat format ) { prop.extract_format = format; };
ExtractFormat extractFormat() { return prop.extract_format; };

void setHashAlgorithm( HashAlgorithm algorithm ) { prop.hash_algorithm = algorithm; };
HashAlgorithm hashAlgorithm() { return prop.hash_algorithm; };

bool createThumbnails();

bool showConfigDialog(QWidget * parent);
Expand Down Expand Up @@ -126,6 +132,7 @@ protected slots:
QString framePicture();
void saveSettings();
void loadSettings();
QString calculateHash(const QString & filename, HashAlgorithm algorithm);

#if defined(Q_OS_UNIX) && !defined(NO_SMPLAYER_SUPPORT)
bool isOptionAvailableinMPV(const QString & option);
Expand Down Expand Up @@ -160,12 +167,16 @@ protected slots:
double aspect_ratio;
bool display_osd;
ExtractFormat extract_format;
HashAlgorithm hash_algorithm;
} prop;

struct {
int thumbnail_width;
} run;

QString file_hash;
QString hash_algorithm_name;

QString last_directory;
bool save_last_directory;
QString error_message;
Expand Down
21 changes: 21 additions & 0 deletions src/videopreview/videopreviewconfigdialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ VideoPreviewConfigDialog::VideoPreviewConfigDialog( QWidget* parent, Qt::WindowF
format_combo->addItem("jpg", VideoPreview::JPEG);
}

hash_combo->addItem(tr("None"), VideoPreview::HASH_NONE);
hash_combo->addItem("MD5", VideoPreview::HASH_MD5);
hash_combo->addItem("SHA1", VideoPreview::HASH_SHA1);
hash_combo->addItem("SHA256", VideoPreview::HASH_SHA256);

filename_edit->setWhatsThis( tr("The preview will be created for the video you specify here.") );
dvd_device_edit->setWhatsThis( tr("Enter here the DVD device or a folder with a DVD image.") );
columns_spin->setWhatsThis( tr("The thumbnails will be arranged on a table.") +" "+ tr("This option specifies the number of columns of the table.") );
Expand All @@ -55,6 +60,7 @@ VideoPreviewConfigDialog::VideoPreviewConfigDialog( QWidget* parent, Qt::WindowF
max_width_spin->setWhatsThis( tr("This option specifies the maximum width in pixels that the generated preview image will have.") );
format_combo->setWhatsThis( tr("Some frames will be extracted from the video in order to create the preview. Here you can choose "
"the image format for the extracted frames. PNG may give better quality.") );
hash_combo->setWhatsThis( tr("Select the checksum algorithm to calculate the file hash. The hash will be displayed on the preview and the saved thumbnail.") );

layout()->setSizeConstraint(QLayout::SetFixedSize);
}
Expand Down Expand Up @@ -140,6 +146,21 @@ VideoPreview::ExtractFormat VideoPreviewConfigDialog::format() {
return (VideoPreview::ExtractFormat) format_combo->itemData(idx).toInt();
}

void VideoPreviewConfigDialog::setHashAlgorithm(VideoPreview::HashAlgorithm algorithm) {
int idx = hash_combo->findData(algorithm);
if (idx < 0) {
// Fallback to MD5 if the algorithm is not found (matches class default)
idx = hash_combo->findData(VideoPreview::HASH_MD5);
if (idx < 0) idx = 0; // Ultimate fallback to first item
}
hash_combo->setCurrentIndex(idx);
}

VideoPreview::HashAlgorithm VideoPreviewConfigDialog::hashAlgorithm() {
int idx = hash_combo->currentIndex();
return (VideoPreview::HashAlgorithm) hash_combo->itemData(idx).toInt();
}

void VideoPreviewConfigDialog::setSaveLastDirectory(bool b) {
save_last_directory_check->setChecked(b);
}
Expand Down
3 changes: 3 additions & 0 deletions src/videopreview/videopreviewconfigdialog.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ class VideoPreviewConfigDialog : public QDialog, public Ui::VideoPreviewConfigDi
void setFormat(VideoPreview::ExtractFormat format);
VideoPreview::ExtractFormat format();

void setHashAlgorithm(VideoPreview::HashAlgorithm algorithm);
VideoPreview::HashAlgorithm hashAlgorithm();

void setSaveLastDirectory(bool b);
bool saveLastDirectory();

Expand Down
31 changes: 31 additions & 0 deletions src/videopreview/videopreviewconfigdialog.ui
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,36 @@
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout">
<item>
<widget class="QLabel" name="label_8">
<property name="text">
<string>&amp;Checksum algorithm</string>
</property>
<property name="buddy">
<cstring>hash_combo</cstring>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="hash_combo"/>
</item>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>71</width>
<height>23</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line">
<property name="sizePolicy">
Expand Down Expand Up @@ -334,6 +364,7 @@
<tabstop>initial_step_spin</tabstop>
<tabstop>max_width_spin</tabstop>
<tabstop>format_combo</tabstop>
<tabstop>hash_combo</tabstop>
<tabstop>button_box</tabstop>
</tabstops>
<resources/>
Expand Down