Skip to content

Commit

Permalink
feat: Add Kodi NFO <hdrtype>
Browse files Browse the repository at this point in the history
Kodi's `<hdrtype>` tag is now supported. We load the HDR type via
mediainfolib's `HDR_Format_Compatibility`.  Tested for HDR10 files.
  • Loading branch information
bugwelle committed Nov 10, 2024
1 parent 9d1c66f commit e97b474
Show file tree
Hide file tree
Showing 22 changed files with 693 additions and 431 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
### Added

- Renamer: Placeholder `<tmdbId>` is now also available for TV shows and episodes (#1814)
- NFO: `<hdrtype>` is now supported (#1810)

## 2.12.0 - 2024-10-13

Expand Down
2 changes: 2 additions & 0 deletions MediaElch.pro
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,7 @@ SOURCES += \
src/ui/small_widgets/SlidingStackedWidget.cpp \
src/ui/small_widgets/SpinBoxDelegate.cpp \
src/ui/small_widgets/StereoModeComboBox.cpp \
src/ui/small_widgets/HdrTypeComboBox.cpp \
src/ui/small_widgets/TagCloud.cpp \
src/ui/small_widgets/TvShowTreeView.cpp \
src/ui/small_widgets/WebImageLabel.cpp \
Expand Down Expand Up @@ -882,6 +883,7 @@ HEADERS += Version.h \
src/ui/small_widgets/SlidingStackedWidget.h \
src/ui/small_widgets/SpinBoxDelegate.h \
src/ui/small_widgets/StereoModeComboBox.h \
src/ui/small_widgets/HdrTypeComboBox.h \
src/ui/small_widgets/TagCloud.h \
src/ui/small_widgets/TvShowTreeView.h \
src/ui/small_widgets/WebImageLabel.h \
Expand Down
20 changes: 0 additions & 20 deletions src/globals/Helper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -485,26 +485,6 @@ QIcon iconForLabel(ColorLabel label)
return QIcon(pixmap);
}

QMap<QString, QString> stereoModes()
{
QMap<QString, QString> modes;
modes.insert("left_right", "side by side (left eye first)");
modes.insert("bottom_top", "top-bottom (right eye first)");
modes.insert("bottom_top", "top-bottom (left eye first)");
modes.insert("checkerboard_rl", "checkboard (right eye first)");
modes.insert("checkerboard_lr", "checkboard (left eye first)");
modes.insert("row_interleaved_rl", "row interleaved (right eye first)");
modes.insert("row_interleaved_lr", "row interleaved (left eye first)");
modes.insert("col_interleaved_rl", "column interleaved (right eye first)");
modes.insert("col_interleaved_lr", "column interleaved (left eye first)");
modes.insert("anaglyph_cyan_red", "anaglyph (cyan/red)");
modes.insert("right_left", "side by side (right eye first)");
modes.insert("anaglyph_green_magenta", "anaglyph (green/magenta)");
modes.insert("block_lr", "both eyes laced in one block (left eye first)");
modes.insert("block_rl", "both eyes laced in one block (right eye first)");
return modes;
}

QString matchResolution(int width, int height, const QString& scanType)
{
QString res;
Expand Down
1 change: 0 additions & 1 deletion src/globals/Helper.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ qreal similarity(const QString& s1, const QString& s2);
QMap<ColorLabel, QString> labels();
QColor colorForLabel(ColorLabel label, QString theme);
QIcon iconForLabel(ColorLabel label);
QMap<QString, QString> stereoModes();
QString matchResolution(int width, int height, const QString& scanType);

/// \brief Take the given URL and make an HTML link tag.
Expand Down
27 changes: 23 additions & 4 deletions src/media/MediaInfoFile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,18 +111,37 @@ QString MediaInfoFile::scanType(int streamIndex) const
if (getVideo(streamIndex, "CodecID") == "V_MPEGH/ISO/HEVC") {
return "progressive";
}
QString scanType = getVideo(streamIndex, "ScanType");
const QString scanType = getVideo(streamIndex, "ScanType");
if (scanType == "MBAFF") {
return "interlaced";
}
return scanType.toLower();
}

QString MediaInfoFile::hdrType(int streamIndex) const
{
// See https://en.wikipedia.org/wiki/High-dynamic-range_television for values.
// We map them to https://kodi.wiki/view/NFO_files/Movies -> `<hdrtype>`
const QString scanType = getVideo(streamIndex, "HDR_Format_Compatibility").toLower();

if (scanType.contains("hdr10")) {
return "hdr10";
}
if (scanType.contains("dolby")) {
return "dolbyvision";
}
if (scanType.contains("hlg")) {
return "hlg";
}

return scanType.toLower();
}

QString MediaInfoFile::stereoFormat(int streamIndex) const
{
QString multiView = getVideo(streamIndex, "MultiView_Layout").toLower();
if (helper::stereoModes().values().contains(multiView)) {
return helper::stereoModes().key(multiView);
const QString multiView = getVideo(streamIndex, "MultiView_Layout").toLower();
if (StreamDetails::stereoModes().values().contains(multiView)) {
return StreamDetails::stereoModes().key(multiView);
}
return "";
}
Expand Down
41 changes: 22 additions & 19 deletions src/media/MediaInfoFile.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#pragma once

#include "media/Path.h"

#include <QString>
#include <chrono>
#include <memory>
Expand Down Expand Up @@ -52,36 +54,37 @@ class MediaInfoFile

static bool hasMediaInfo();

bool isReady() const;
ELCH_NODISCARD bool isReady() const;

int subtitleCount() const;
int videoStreamCount() const;
int audioStreamCount() const;
ELCH_NODISCARD int subtitleCount() const;
ELCH_NODISCARD int videoStreamCount() const;
ELCH_NODISCARD int audioStreamCount() const;

std::chrono::milliseconds duration(int streamIndex) const;
std::size_t videoWidth(int streamIndex) const;
std::size_t videoHeight(int streamIndex) const;
double aspectRatio(int streamIndex) const;
QString codec(int streamIndex) const;
QString mpegVersion(int streamIndex) const;
QString scanType(int streamIndex) const;
QString stereoFormat(int streamIndex) const;
QString format(int streamIndex) const;
ELCH_NODISCARD QString codec(int streamIndex) const;
ELCH_NODISCARD QString mpegVersion(int streamIndex) const;
ELCH_NODISCARD QString scanType(int streamIndex) const;
ELCH_NODISCARD QString stereoFormat(int streamIndex) const;
ELCH_NODISCARD QString format(int streamIndex) const;
ELCH_NODISCARD QString hdrType(int streamIndex) const;

QString audioLanguage(int streamIndex) const;
QString audioCodec(int streamIndex) const;
QString audioChannels(int streamIndex) const;
ELCH_NODISCARD QString audioLanguage(int streamIndex) const;
ELCH_NODISCARD QString audioCodec(int streamIndex) const;
ELCH_NODISCARD QString audioChannels(int streamIndex) const;

QString subtitleLang(int streamIndex) const;
ELCH_NODISCARD QString subtitleLang(int streamIndex) const;

private:
QString parseVideoFormat(QString format, QString version) const;
ELCH_NODISCARD QString parseVideoFormat(QString format, QString version) const;

QString getGeneral(int streamIndex, const char* parameter) const;
QString getVideo(int streamIndex, const char* parameter) const;
QString getAudio(int streamIndex, const char* parameter) const;
QStringList getAudio(int streamIndex, QStringList parameters) const;
QString getText(int streamIndex, const char* parameter) const;
ELCH_NODISCARD QString getGeneral(int streamIndex, const char* parameter) const;
ELCH_NODISCARD QString getVideo(int streamIndex, const char* parameter) const;
ELCH_NODISCARD QString getAudio(int streamIndex, const char* parameter) const;
ELCH_NODISCARD QStringList getAudio(int streamIndex, QStringList parameters) const;
ELCH_NODISCARD QString getText(int streamIndex, const char* parameter) const;

private:
// We don't want the MediaInfoLib include here so we avoid it by
Expand Down
121 changes: 114 additions & 7 deletions src/media/StreamDetails.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@ StreamDetails::StreamDetails(QObject* parent, mediaelch::FileList files) :
QString StreamDetails::detailToString(VideoDetails details)
{
switch (details) {
case VideoDetails::Unknown: return "undefined";
case VideoDetails::Codec: return "codec";
case VideoDetails::Aspect: return "aspect";
case VideoDetails::Width: return "width";
case VideoDetails::Height: return "height";
case VideoDetails::DurationInSeconds: return "durationinseconds";
case VideoDetails::ScanType: return "scantype";
case VideoDetails::StereoMode: return "stereomode";
case VideoDetails::HdrType: return "hdrtype";
}
qCWarning(generic) << "Undefined video detail: no string representation";
return "undefined";
Expand All @@ -36,9 +38,10 @@ QString StreamDetails::detailToString(VideoDetails details)
QString StreamDetails::detailToString(AudioDetails details)
{
switch (details) {
case StreamDetails::AudioDetails::Codec: return "codec";
case StreamDetails::AudioDetails::Language: return "language";
case StreamDetails::AudioDetails::Channels: return "channels";
case AudioDetails::Unknown: return "undefined";
case AudioDetails::Codec: return "codec";
case AudioDetails::Language: return "language";
case AudioDetails::Channels: return "channels";
}
qCWarning(generic) << "Undefined audio detail: no string representation";
return "undefined";
Expand All @@ -47,15 +50,58 @@ QString StreamDetails::detailToString(AudioDetails details)
QString StreamDetails::detailToString(SubtitleDetails details)
{
switch (details) {
case StreamDetails::SubtitleDetails::Language: return "language";
case SubtitleDetails::Unknown: return "undefined";
case SubtitleDetails::Language: return "language";
}
qCWarning(generic) << "Undefined subtitle detail: no string representation";
return "undefined";
}

/**
* \brief Clears all information
*/
StreamDetails::VideoDetails StreamDetails::stringToVideoDetail(QString detail)
{
static const QMap<QString, StreamDetails::VideoDetails> map = {
{"codec", StreamDetails::VideoDetails::Codec},
{"aspect", StreamDetails::VideoDetails::Aspect},
{"width", StreamDetails::VideoDetails::Width},
{"height", StreamDetails::VideoDetails::Height},
{"durationinseconds", StreamDetails::VideoDetails::DurationInSeconds},
{"scantype", StreamDetails::VideoDetails::ScanType},
{"stereomode", StreamDetails::VideoDetails::StereoMode},
{"hdrtype", StreamDetails::VideoDetails::HdrType},
};
if (map.contains(detail)) {
return map[detail];
}

return StreamDetails::VideoDetails::Unknown;
}

StreamDetails::AudioDetails StreamDetails::stringToAudioDetail(QString detail)
{
static const QMap<QString, StreamDetails::AudioDetails> map = {
{"codec", StreamDetails::AudioDetails::Codec},
{"language", StreamDetails::AudioDetails::Language},
{"channels", StreamDetails::AudioDetails::Channels},
};
if (map.contains(detail)) {
return map[detail];
}

return StreamDetails::AudioDetails::Unknown;
}

StreamDetails::SubtitleDetails StreamDetails::stringToSubtitleDetail(QString detail)
{
static const QMap<QString, StreamDetails::SubtitleDetails> map = {
{"language", StreamDetails::SubtitleDetails::Language},
};
if (map.contains(detail)) {
return map[detail];
}

return StreamDetails::SubtitleDetails::Unknown;
}

void StreamDetails::clear()
{
m_videoDetails.clear();
Expand Down Expand Up @@ -168,6 +214,7 @@ bool StreamDetails::loadWithLibrary()
setVideoDetail(VideoDetails::Height, QString::number(mi.videoHeight(0)));
setVideoDetail(VideoDetails::ScanType, mi.scanType(0));
setVideoDetail(VideoDetails::StereoMode, mi.stereoFormat(0));
setVideoDetail(VideoDetails::HdrType, mi.hdrType(0));
}

const int audioCount = mi.audioStreamCount();
Expand Down Expand Up @@ -341,3 +388,63 @@ QString StreamDetails::videoCodec() const
{
return m_videoDetails.value(VideoDetails::Codec);
}

QVector<StreamDetails::VideoDetails> StreamDetails::allVideoDetailsAsList()
{
return {
StreamDetails::VideoDetails::Codec,
StreamDetails::VideoDetails::Aspect,
StreamDetails::VideoDetails::Width,
StreamDetails::VideoDetails::Height,
StreamDetails::VideoDetails::DurationInSeconds,
StreamDetails::VideoDetails::ScanType,
StreamDetails::VideoDetails::StereoMode,
StreamDetails::VideoDetails::HdrType,
};
}

QVector<StreamDetails::AudioDetails> StreamDetails::allAudioDetailsAsList()
{
return {
StreamDetails::AudioDetails::Codec,
StreamDetails::AudioDetails::Language,
StreamDetails::AudioDetails::Channels,
};
}

QVector<StreamDetails::SubtitleDetails> StreamDetails::allSubtitleDetailsAsList()
{
return {
StreamDetails::SubtitleDetails::Language,
};
}

QMap<QString, QString> StreamDetails::stereoModes()
{
QMap<QString, QString> modes;
modes.insert("left_right", "side by side (left eye first)");
modes.insert("bottom_top", "top-bottom (right eye first)");
modes.insert("bottom_top", "top-bottom (left eye first)");
modes.insert("checkerboard_rl", "checkboard (right eye first)");
modes.insert("checkerboard_lr", "checkboard (left eye first)");
modes.insert("row_interleaved_rl", "row interleaved (right eye first)");
modes.insert("row_interleaved_lr", "row interleaved (left eye first)");
modes.insert("col_interleaved_rl", "column interleaved (right eye first)");
modes.insert("col_interleaved_lr", "column interleaved (left eye first)");
modes.insert("anaglyph_cyan_red", "anaglyph (cyan/red)");
modes.insert("right_left", "side by side (right eye first)");
modes.insert("anaglyph_green_magenta", "anaglyph (green/magenta)");
modes.insert("block_lr", "both eyes laced in one block (left eye first)");
modes.insert("block_rl", "both eyes laced in one block (right eye first)");
return modes;
}

QVector<QString> StreamDetails::hdrTypes()
{
QVector<QString> types{
"hdr10",
"dolbyvision",
"hlg",
};
return types;
}
27 changes: 21 additions & 6 deletions src/media/StreamDetails.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once

#include "media/Path.h"
#include "utils/Meta.h"

#include <QMap>
#include <QObject>
Expand All @@ -22,22 +23,36 @@ class StreamDetails : public QObject
Width,
Height,
ScanType,
StereoMode
StereoMode,
HdrType, // added in v2.12.1
Unknown, // added in v2.12.1
};
enum class AudioDetails
{
Language,
Codec,
Channels
Channels,
Unknown, // added in v2.12.1
};
enum class SubtitleDetails
{
Language
Language,
Unknown, // added in v2.12.1
};

static QString detailToString(VideoDetails details);
static QString detailToString(AudioDetails details);
static QString detailToString(SubtitleDetails details);
ELCH_NODISCARD static QVector<VideoDetails> allVideoDetailsAsList();
ELCH_NODISCARD static QVector<AudioDetails> allAudioDetailsAsList();
ELCH_NODISCARD static QVector<SubtitleDetails> allSubtitleDetailsAsList();

ELCH_NODISCARD static QString detailToString(VideoDetails details);
ELCH_NODISCARD static QString detailToString(AudioDetails details);
ELCH_NODISCARD static QString detailToString(SubtitleDetails details);
ELCH_NODISCARD static VideoDetails stringToVideoDetail(QString detail);
ELCH_NODISCARD static AudioDetails stringToAudioDetail(QString detail);
ELCH_NODISCARD static SubtitleDetails stringToSubtitleDetail(QString detail);

ELCH_NODISCARD static QMap<QString, QString> stereoModes();
ELCH_NODISCARD static QVector<QString> hdrTypes();

/// \brief Loads stream details from the file. Returns true if successful.
ELCH_NODISCARD bool loadStreamDetails();
Expand Down
Loading

0 comments on commit e97b474

Please sign in to comment.