diff --git a/src/videopreview/videopreview.cpp b/src/videopreview/videopreview.cpp index b7dbee245..d6985e1aa 100644 --- a/src/videopreview/videopreview.cpp +++ b/src/videopreview/videopreview.cpp @@ -40,6 +40,8 @@ #include #include #include +#include +#include #include @@ -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; @@ -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 @@ -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; @@ -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("

" + i.filename + "

"); + // 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("

" + titleText + "

"); int count = 1; @@ -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_(.*)=(.*)"); @@ -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()); @@ -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) { @@ -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; @@ -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) { @@ -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(); diff --git a/src/videopreview/videopreview.h b/src/videopreview/videopreview.h index 4284e5faa..c3a423d43 100644 --- a/src/videopreview/videopreview.h +++ b/src/videopreview/videopreview.h @@ -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; @@ -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 @@ -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(); @@ -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); @@ -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); @@ -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; diff --git a/src/videopreview/videopreviewconfigdialog.cpp b/src/videopreview/videopreviewconfigdialog.cpp index fe7e461ff..263817abf 100644 --- a/src/videopreview/videopreviewconfigdialog.cpp +++ b/src/videopreview/videopreviewconfigdialog.cpp @@ -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.") ); @@ -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); } @@ -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); } diff --git a/src/videopreview/videopreviewconfigdialog.h b/src/videopreview/videopreviewconfigdialog.h index ea28f159f..27e5eab8a 100644 --- a/src/videopreview/videopreviewconfigdialog.h +++ b/src/videopreview/videopreviewconfigdialog.h @@ -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(); diff --git a/src/videopreview/videopreviewconfigdialog.ui b/src/videopreview/videopreviewconfigdialog.ui index 3225abc44..681eb70e7 100644 --- a/src/videopreview/videopreviewconfigdialog.ui +++ b/src/videopreview/videopreviewconfigdialog.ui @@ -272,6 +272,36 @@ + + + + + + &Checksum algorithm + + + hash_combo + + + + + + + + + + Qt::Horizontal + + + + 71 + 23 + + + + + + @@ -334,6 +364,7 @@ initial_step_spin max_width_spin format_combo + hash_combo button_box