chore: baseline usable version

This commit is contained in:
dela
2026-06-10 11:17:51 +08:00
commit a2f82a86b6
932 changed files with 172551 additions and 0 deletions

View File

@@ -0,0 +1,932 @@
#ifndef ADMINMODELS_H
#define ADMINMODELS_H
#include <QString>
#include <QStringList>
#include <QJsonObject>
#include <QJsonArray>
#include <QDateTime>
#include <QMetaType>
#include "../../qEmbyCore_global.h"
struct QEMBYCORE_EXPORT SystemInfo {
Q_GADGET
public:
QString serverName;
QString version;
QString operatingSystem;
QString operatingSystemName;
QString architecture;
QString id;
QString localAddress;
QString wanAddress;
bool hasUpdateAvailable = false;
bool supportsLibraryMonitor = false;
bool canSelfRestart = false;
bool canSelfUpdate = false;
static SystemInfo fromJson(const QJsonObject& obj) {
SystemInfo info;
info.serverName = obj["ServerName"].toString();
info.version = obj["Version"].toString();
info.operatingSystem = obj["OperatingSystem"].toString();
info.operatingSystemName = obj["OperatingSystemDisplayName"].toString();
info.architecture = obj["SystemArchitecture"].toString();
info.id = obj["Id"].toString();
info.localAddress = obj["LocalAddress"].toString();
info.wanAddress = obj["WanAddress"].toString();
info.hasUpdateAvailable = obj["HasUpdateAvailable"].toBool();
info.supportsLibraryMonitor = obj["SupportsLibraryMonitor"].toBool();
info.canSelfRestart = obj["CanSelfRestart"].toBool();
info.canSelfUpdate = obj["CanSelfUpdate"].toBool();
return info;
}
};
Q_DECLARE_METATYPE(SystemInfo)
struct QEMBYCORE_EXPORT SessionInfo {
Q_GADGET
public:
QString id;
QString userId;
QString userName;
QString client;
QString deviceName;
QString deviceId;
QString applicationVersion;
QString remoteEndPoint;
QString lastActivityDate;
QString nowPlayingItemId;
QString nowPlayingItemName;
QString nowPlayingItemType;
QString nowPlayingSeriesName;
int nowPlayingParentIndexNumber = -1;
int nowPlayingIndexNumber = -1;
QString nowPlayingImageTag;
QString nowPlayingThumbTag;
QString nowPlayingBackdropTag;
QString nowPlayingSeriesId;
QString nowPlayingSeriesPrimaryTag;
QString parentThumbItemId;
QString parentThumbImageTag;
QString parentBackdropItemId;
QString parentBackdropImageTag;
qint64 nowPlayingRunTimeTicks = 0;
bool isPaused = false;
qint64 positionTicks = 0;
QString playMethod;
QString transcodingContainer;
QString transcodingVideoCodec;
QString transcodingAudioCodec;
int transcodingBitrate = 0;
double transcodingProgress = 0.0;
bool isNowPlaying() const { return !nowPlayingItemId.isEmpty(); }
QString seasonEpisodeLabel() const {
if (nowPlayingParentIndexNumber >= 0 && nowPlayingIndexNumber >= 0) {
return QString("S%1:E%2")
.arg(nowPlayingParentIndexNumber)
.arg(nowPlayingIndexNumber);
}
return {};
}
static QString formatTicks(qint64 ticks) {
qint64 totalSeconds = ticks / 10000000;
int hours = totalSeconds / 3600;
int minutes = (totalSeconds % 3600) / 60;
int seconds = totalSeconds % 60;
if (hours > 0)
return QString("%1:%2:%3").arg(hours).arg(minutes, 2, 10, QChar('0')).arg(seconds, 2, 10, QChar('0'));
return QString("%1:%2").arg(minutes).arg(seconds, 2, 10, QChar('0'));
}
double playbackProgress() const {
if (nowPlayingRunTimeTicks <= 0) return 0.0;
return static_cast<double>(positionTicks) / nowPlayingRunTimeTicks;
}
static SessionInfo fromJson(const QJsonObject& obj) {
SessionInfo session;
session.id = obj["Id"].toString();
session.userId = obj["UserId"].toString();
session.userName = obj["UserName"].toString();
session.client = obj["Client"].toString();
session.deviceName = obj["DeviceName"].toString();
session.deviceId = obj["DeviceId"].toString();
session.applicationVersion = obj["ApplicationVersion"].toString();
session.remoteEndPoint = obj["RemoteEndPoint"].toString();
session.lastActivityDate = obj["LastActivityDate"].toString();
if (obj.contains("NowPlayingItem")) {
QJsonObject np = obj["NowPlayingItem"].toObject();
session.nowPlayingItemId = np["Id"].toString();
session.nowPlayingItemName = np["Name"].toString();
session.nowPlayingItemType = np["Type"].toString();
session.nowPlayingSeriesName = np["SeriesName"].toString();
session.nowPlayingParentIndexNumber = np["ParentIndexNumber"].toInt(-1);
session.nowPlayingIndexNumber = np["IndexNumber"].toInt(-1);
session.nowPlayingRunTimeTicks = np["RunTimeTicks"].toVariant().toLongLong();
QJsonObject imgTags = np["ImageTags"].toObject();
session.nowPlayingImageTag = imgTags["Primary"].toString();
session.nowPlayingThumbTag = imgTags["Thumb"].toString();
QJsonArray bdTags = np["BackdropImageTags"].toArray();
if (!bdTags.isEmpty()) {
session.nowPlayingBackdropTag = bdTags.first().toString();
}
session.nowPlayingSeriesId = np["SeriesId"].toString();
session.nowPlayingSeriesPrimaryTag = np["SeriesPrimaryImageTag"].toString();
session.parentThumbItemId = np["ParentThumbItemId"].toString();
session.parentThumbImageTag = np["ParentThumbImageTag"].toString();
session.parentBackdropItemId = np["ParentBackdropItemId"].toString();
QJsonArray parentBdTags = np["ParentBackdropImageTags"].toArray();
if (!parentBdTags.isEmpty()) {
session.parentBackdropImageTag = parentBdTags.first().toString();
}
}
if (obj.contains("PlayState")) {
QJsonObject ps = obj["PlayState"].toObject();
session.isPaused = ps["IsPaused"].toBool();
session.positionTicks = ps["PositionTicks"].toVariant().toLongLong();
session.playMethod = ps["PlayMethod"].toString();
}
if (obj.contains("TranscodingInfo")) {
QJsonObject ti = obj["TranscodingInfo"].toObject();
session.transcodingContainer = ti["Container"].toString();
session.transcodingVideoCodec = ti["VideoCodec"].toString();
session.transcodingAudioCodec = ti["AudioCodec"].toString();
session.transcodingBitrate = ti["Bitrate"].toInt();
session.transcodingProgress = ti["CompletionPercentage"].toDouble();
}
return session;
}
};
Q_DECLARE_METATYPE(SessionInfo)
struct QEMBYCORE_EXPORT UserInfo {
Q_GADGET
public:
QString id;
QString name;
QString serverIdRaw;
bool isAdministrator = false;
bool isDisabled = false;
bool hasPassword = false;
QString lastLoginDate;
QString lastActivityDate;
QString primaryImageTag;
bool enableAllFolders = true;
static UserInfo fromJson(const QJsonObject& obj) {
UserInfo user;
user.id = obj["Id"].toString();
user.name = obj["Name"].toString();
user.serverIdRaw = obj["ServerId"].toString();
user.hasPassword = obj["HasPassword"].toBool();
user.lastLoginDate = obj["LastLoginDate"].toString();
user.lastActivityDate = obj["LastActivityDate"].toString();
user.primaryImageTag = obj["PrimaryImageTag"].toString();
if (obj.contains("Policy")) {
QJsonObject policy = obj["Policy"].toObject();
user.isAdministrator = policy["IsAdministrator"].toBool();
user.isDisabled = policy["IsDisabled"].toBool();
user.enableAllFolders = policy["EnableAllFolders"].toBool();
}
return user;
}
};
Q_DECLARE_METATYPE(UserInfo)
struct QEMBYCORE_EXPORT AdminMediaSubFolderInfo {
Q_GADGET
public:
QString id;
QString guid;
QString path;
bool isUserAccessConfigurable = true;
static AdminMediaSubFolderInfo fromJson(const QJsonObject& obj) {
AdminMediaSubFolderInfo folder;
folder.id = obj["Id"].toString();
folder.guid = obj["Guid"].toString();
folder.path = obj["Path"].toString();
folder.isUserAccessConfigurable =
obj["IsUserAccessConfigurable"].toBool(true);
return folder;
}
QString effectiveId() const {
return !guid.trimmed().isEmpty() ? guid.trimmed() : id.trimmed();
}
};
Q_DECLARE_METATYPE(AdminMediaSubFolderInfo)
struct QEMBYCORE_EXPORT AdminMediaFolderInfo {
Q_GADGET
public:
QString id;
QString name;
static AdminMediaFolderInfo fromJson(const QJsonObject& obj) {
AdminMediaFolderInfo folder;
folder.id = obj["Guid"].toString();
if (folder.id.isEmpty()) {
folder.id = obj["Id"].toString();
}
folder.name = obj["Name"].toString();
return folder;
}
};
Q_DECLARE_METATYPE(AdminMediaFolderInfo)
struct QEMBYCORE_EXPORT SelectableMediaFolderInfo {
Q_GADGET
public:
QString id;
QString name;
bool isUserAccessConfigurable = true;
QList<AdminMediaSubFolderInfo> subFolders;
static SelectableMediaFolderInfo fromJson(const QJsonObject& obj) {
SelectableMediaFolderInfo folder;
folder.id = obj["Guid"].toString();
if (folder.id.isEmpty()) {
folder.id = obj["Id"].toString();
}
folder.name = obj["Name"].toString();
folder.isUserAccessConfigurable =
obj["IsUserAccessConfigurable"].toBool(true);
const QJsonArray subFolderArray = obj["SubFolders"].toArray();
for (const auto& value : subFolderArray) {
folder.subFolders.append(
AdminMediaSubFolderInfo::fromJson(value.toObject()));
}
return folder;
}
};
Q_DECLARE_METATYPE(SelectableMediaFolderInfo)
struct QEMBYCORE_EXPORT AdminChannelInfo {
Q_GADGET
public:
QString id;
QString name;
static AdminChannelInfo fromJson(const QJsonObject& obj) {
AdminChannelInfo channel;
channel.id = obj["Guid"].toString();
if (channel.id.isEmpty()) {
channel.id = obj["Id"].toString();
}
channel.name = obj["Name"].toString();
return channel;
}
};
Q_DECLARE_METATYPE(AdminChannelInfo)
struct QEMBYCORE_EXPORT AdminDeviceInfo {
Q_GADGET
public:
QString id;
QString name;
QString appName;
static AdminDeviceInfo fromJson(const QJsonObject& obj) {
AdminDeviceInfo device;
device.id = obj["ReportedDeviceId"].toString();
if (device.id.isEmpty()) {
device.id = obj["Id"].toString();
}
device.name = obj["Name"].toString();
if (device.name.isEmpty()) {
device.name = obj["DeviceName"].toString();
}
device.appName = obj["AppName"].toString();
return device;
}
};
Q_DECLARE_METATYPE(AdminDeviceInfo)
struct QEMBYCORE_EXPORT AdminAuthProviderInfo {
Q_GADGET
public:
QString id;
QString name;
static AdminAuthProviderInfo fromJson(const QJsonObject& obj) {
AdminAuthProviderInfo provider;
provider.id = obj["Id"].toString();
provider.name = obj["Name"].toString();
return provider;
}
};
Q_DECLARE_METATYPE(AdminAuthProviderInfo)
struct QEMBYCORE_EXPORT AdminFeatureInfo {
Q_GADGET
public:
QString id;
QString name;
static AdminFeatureInfo fromJson(const QJsonObject& obj) {
AdminFeatureInfo feature;
feature.id = obj["Id"].toString();
feature.name = obj["Name"].toString();
return feature;
}
};
Q_DECLARE_METATYPE(AdminFeatureInfo)
struct QEMBYCORE_EXPORT AdminParentalRatingInfo {
Q_GADGET
public:
QString name;
int value = 0;
static AdminParentalRatingInfo fromJson(const QJsonObject& obj) {
AdminParentalRatingInfo rating;
rating.name = obj["Name"].toString();
rating.value = obj["Value"].toInt();
return rating;
}
};
Q_DECLARE_METATYPE(AdminParentalRatingInfo)
struct QEMBYCORE_EXPORT LocalizationCulture {
Q_GADGET
public:
QString name;
QString displayName;
QString twoLetterISOLanguageName;
QString threeLetterISOLanguageName;
QStringList threeLetterISOLanguageNames;
QStringList twoLetterISOLanguageNames;
static LocalizationCulture fromJson(const QJsonObject& obj) {
LocalizationCulture culture;
culture.name = obj["Name"].toString();
culture.displayName = obj["DisplayName"].toString();
culture.twoLetterISOLanguageName =
obj["TwoLetterISOLanguageName"].toString();
culture.threeLetterISOLanguageName =
obj["ThreeLetterISOLanguageName"].toString();
const QJsonArray threeLetterNames =
obj["ThreeLetterISOLanguageNames"].toArray();
for (const auto& value : threeLetterNames) {
const QString text = value.toString().trimmed();
if (!text.isEmpty() &&
!culture.threeLetterISOLanguageNames.contains(text)) {
culture.threeLetterISOLanguageNames.append(text);
}
}
const QJsonArray twoLetterNames =
obj["TwoLetterISOLanguageNames"].toArray();
for (const auto& value : twoLetterNames) {
const QString text = value.toString().trimmed();
if (!text.isEmpty() &&
!culture.twoLetterISOLanguageNames.contains(text)) {
culture.twoLetterISOLanguageNames.append(text);
}
}
return culture;
}
QString canonicalCode() const {
if (!twoLetterISOLanguageName.trimmed().isEmpty()) {
return twoLetterISOLanguageName.trimmed();
}
if (!twoLetterISOLanguageNames.isEmpty()) {
return twoLetterISOLanguageNames.first().trimmed();
}
if (!threeLetterISOLanguageName.trimmed().isEmpty()) {
return threeLetterISOLanguageName.trimmed();
}
if (!threeLetterISOLanguageNames.isEmpty()) {
return threeLetterISOLanguageNames.first().trimmed();
}
return {};
}
QString visibleName() const {
if (!displayName.trimmed().isEmpty()) {
return displayName.trimmed();
}
return name.trimmed();
}
QStringList allCodes() const {
QStringList codes;
auto appendUnique = [&codes](const QString& value) {
const QString trimmed = value.trimmed();
if (!trimmed.isEmpty() && !codes.contains(trimmed, Qt::CaseInsensitive)) {
codes.append(trimmed);
}
};
appendUnique(twoLetterISOLanguageName);
appendUnique(threeLetterISOLanguageName);
for (const QString& value : twoLetterISOLanguageNames) {
appendUnique(value);
}
for (const QString& value : threeLetterISOLanguageNames) {
appendUnique(value);
}
return codes;
}
};
Q_DECLARE_METATYPE(LocalizationCulture)
struct QEMBYCORE_EXPORT LocalizationCountry {
Q_GADGET
public:
QString name;
QString displayName;
QString englishName;
QString twoLetterISORegionName;
QString threeLetterISORegionName;
static LocalizationCountry fromJson(const QJsonObject& obj) {
LocalizationCountry country;
country.name = obj["Name"].toString();
country.displayName = obj["DisplayName"].toString();
country.englishName = obj["EnglishName"].toString();
country.twoLetterISORegionName =
obj["TwoLetterISORegionName"].toString();
country.threeLetterISORegionName =
obj["ThreeLetterISORegionName"].toString();
return country;
}
QString canonicalCode() const {
if (!twoLetterISORegionName.trimmed().isEmpty()) {
return twoLetterISORegionName.trimmed();
}
if (!name.trimmed().isEmpty()) {
return name.trimmed();
}
if (!threeLetterISORegionName.trimmed().isEmpty()) {
return threeLetterISORegionName.trimmed();
}
return {};
}
QString visibleName() const {
if (!displayName.trimmed().isEmpty()) {
return displayName.trimmed();
}
if (!englishName.trimmed().isEmpty()) {
return englishName.trimmed();
}
return name.trimmed();
}
QStringList allCodes() const {
QStringList codes;
auto appendUnique = [&codes](const QString& value) {
const QString trimmed = value.trimmed();
if (!trimmed.isEmpty() && !codes.contains(trimmed, Qt::CaseInsensitive)) {
codes.append(trimmed);
}
};
appendUnique(name);
appendUnique(twoLetterISORegionName);
appendUnique(threeLetterISORegionName);
return codes;
}
};
Q_DECLARE_METATYPE(LocalizationCountry)
struct QEMBYCORE_EXPORT VirtualFolder {
Q_GADGET
public:
QString id;
QString name;
QString itemId;
QString guid;
QString collectionType;
QStringList locations;
QString primaryImageItemId;
QJsonObject libraryOptions;
static VirtualFolder fromJson(const QJsonObject& obj) {
VirtualFolder folder;
folder.id = obj["Id"].toString();
folder.name = obj["Name"].toString();
folder.itemId = obj["ItemId"].toString();
folder.guid = obj["Guid"].toString();
folder.collectionType = obj["CollectionType"].toString();
folder.primaryImageItemId = obj["PrimaryImageItemId"].toString();
folder.libraryOptions = obj["LibraryOptions"].toObject();
if (folder.id.isEmpty()) {
folder.id = folder.itemId;
}
if (folder.itemId.isEmpty()) {
folder.itemId = folder.id;
}
if (obj.contains("Locations")) {
QJsonArray locs = obj["Locations"].toArray();
for (const auto& loc : locs) {
folder.locations.append(loc.toString());
}
}
return folder;
}
QString virtualFolderId() const {
return id.isEmpty() ? itemId : id;
}
QString collectionTypeDisplayName() const {
if (collectionType == "movies") return QObject::tr("Movies");
if (collectionType == "tvshows") return QObject::tr("TV Shows");
if (collectionType == "music") return QObject::tr("Music");
if (collectionType == "homevideos") return QObject::tr("Home Videos");
if (collectionType == "audiobooks" || collectionType == "books")
return QObject::tr("Books");
if (collectionType == "musicvideos") return QObject::tr("Music Videos");
if (collectionType == "boxsets") return QObject::tr("Collections");
if (collectionType == "playlists") return QObject::tr("Playlists");
if (collectionType.isEmpty()) return QObject::tr("Mixed");
return collectionType;
}
};
Q_DECLARE_METATYPE(VirtualFolder)
struct QEMBYCORE_EXPORT ItemImageInfo {
Q_GADGET
public:
QString imageType;
int imageIndex = -1;
QString path;
QString fileName;
int width = 0;
int height = 0;
qint64 size = 0;
static ItemImageInfo fromJson(const QJsonObject& obj) {
ItemImageInfo info;
info.imageType = obj.value(QStringLiteral("ImageType")).toString();
if (info.imageType.isEmpty()) {
info.imageType = obj.value(QStringLiteral("Type")).toString();
}
info.imageIndex = obj.value(QStringLiteral("ImageIndex")).toInt(-1);
info.path = obj.value(QStringLiteral("Path")).toString();
info.fileName = obj.value(QStringLiteral("Filename")).toString();
info.width = obj.value(QStringLiteral("Width")).toInt();
info.height = obj.value(QStringLiteral("Height")).toInt();
info.size = obj.value(QStringLiteral("Size")).toVariant().toLongLong();
return info;
}
};
Q_DECLARE_METATYPE(ItemImageInfo)
struct QEMBYCORE_EXPORT RemoteSearchResult {
Q_GADGET
public:
QString name;
int productionYear = 0;
int indexNumber = -1;
int indexNumberEnd = -1;
int parentIndexNumber = -1;
QString premiereDate;
QString imageUrl;
QString searchProviderName;
QString overview;
QString albumArtistName;
QStringList artistNames;
QJsonObject providerIds;
QJsonObject rawData;
QString providerIdSummary() const {
QStringList pairs;
const QStringList keys = providerIds.keys();
for (const QString& key : keys) {
const QString value = providerIds.value(key).toString().trimmed();
if (!key.trimmed().isEmpty() && !value.isEmpty()) {
pairs.append(QStringLiteral("%1=%2").arg(key.trimmed(), value));
}
}
return pairs.join(QStringLiteral(", "));
}
QString secondaryText() const {
QStringList parts;
if (productionYear > 0) {
parts.append(QString::number(productionYear));
}
if (parentIndexNumber >= 0 && indexNumber >= 0) {
parts.append(QStringLiteral("S%1E%2")
.arg(parentIndexNumber, 2, 10, QChar('0'))
.arg(indexNumber, 2, 10, QChar('0')));
} else if (indexNumber >= 0) {
parts.append(QStringLiteral("E%1").arg(indexNumber));
}
if (!searchProviderName.trimmed().isEmpty()) {
parts.append(searchProviderName.trimmed());
}
return parts.join(QStringLiteral(" | "));
}
QJsonObject toJson() const {
if (!rawData.isEmpty()) {
return rawData;
}
QJsonObject obj;
obj.insert(QStringLiteral("Name"), name);
obj.insert(QStringLiteral("ProductionYear"), productionYear);
obj.insert(QStringLiteral("IndexNumber"), indexNumber);
obj.insert(QStringLiteral("IndexNumberEnd"), indexNumberEnd);
obj.insert(QStringLiteral("ParentIndexNumber"), parentIndexNumber);
obj.insert(QStringLiteral("PremiereDate"), premiereDate);
obj.insert(QStringLiteral("ImageUrl"), imageUrl);
obj.insert(QStringLiteral("SearchProviderName"), searchProviderName);
obj.insert(QStringLiteral("Overview"), overview);
obj.insert(QStringLiteral("ProviderIds"), providerIds);
if (!albumArtistName.trimmed().isEmpty()) {
QJsonObject albumArtist;
albumArtist.insert(QStringLiteral("Name"), albumArtistName.trimmed());
obj.insert(QStringLiteral("AlbumArtist"), albumArtist);
}
if (!artistNames.isEmpty()) {
QJsonArray artists;
for (const QString& artistName : artistNames) {
if (artistName.trimmed().isEmpty()) {
continue;
}
QJsonObject artist;
artist.insert(QStringLiteral("Name"), artistName.trimmed());
artists.append(artist);
}
obj.insert(QStringLiteral("Artists"), artists);
}
return obj;
}
static RemoteSearchResult fromJson(const QJsonObject& obj) {
RemoteSearchResult result;
result.name = obj.value(QStringLiteral("Name")).toString();
result.productionYear =
obj.value(QStringLiteral("ProductionYear")).toInt();
result.indexNumber = obj.value(QStringLiteral("IndexNumber")).toInt(-1);
result.indexNumberEnd =
obj.value(QStringLiteral("IndexNumberEnd")).toInt(-1);
result.parentIndexNumber =
obj.value(QStringLiteral("ParentIndexNumber")).toInt(-1);
result.premiereDate = obj.value(QStringLiteral("PremiereDate")).toString();
result.imageUrl = obj.value(QStringLiteral("ImageUrl")).toString();
result.searchProviderName =
obj.value(QStringLiteral("SearchProviderName")).toString();
result.overview = obj.value(QStringLiteral("Overview")).toString();
result.providerIds = obj.value(QStringLiteral("ProviderIds")).toObject();
result.rawData = obj;
const QJsonValue albumArtistValue = obj.value(QStringLiteral("AlbumArtist"));
if (albumArtistValue.isObject()) {
result.albumArtistName =
albumArtistValue.toObject().value(QStringLiteral("Name")).toString();
} else if (albumArtistValue.isString()) {
result.albumArtistName = albumArtistValue.toString();
}
const QJsonArray artists = obj.value(QStringLiteral("Artists")).toArray();
for (const QJsonValue& artistValue : artists) {
if (artistValue.isObject()) {
const QString artistName =
artistValue.toObject().value(QStringLiteral("Name")).toString().trimmed();
if (!artistName.isEmpty()) {
result.artistNames.append(artistName);
}
} else if (artistValue.isString()) {
const QString artistName = artistValue.toString().trimmed();
if (!artistName.isEmpty()) {
result.artistNames.append(artistName);
}
}
}
return result;
}
};
Q_DECLARE_METATYPE(RemoteSearchResult)
struct QEMBYCORE_EXPORT ScheduledTaskInfo {
Q_GADGET
public:
QString id;
QString name;
QString description;
QString category;
QString state;
double currentProgressPercentage = 0.0;
QJsonArray triggers;
bool isHidden = false;
QString key;
QString lastExecutionResult;
QString lastStartTime;
QString lastEndTime;
bool isRunning() const { return state == "Running"; }
bool hasTriggers() const { return !triggers.isEmpty(); }
static ScheduledTaskInfo fromJson(const QJsonObject& obj) {
ScheduledTaskInfo task;
task.id = obj["Id"].toString();
task.name = obj["Name"].toString();
task.description = obj["Description"].toString();
task.category = obj["Category"].toString();
task.state = obj["State"].toString();
task.currentProgressPercentage = obj["CurrentProgressPercentage"].toDouble();
task.triggers = obj["Triggers"].toArray();
task.isHidden = obj["IsHidden"].toBool(false);
task.key = obj["Key"].toString();
if (obj.contains("LastExecutionResult")) {
QJsonObject result = obj["LastExecutionResult"].toObject();
task.lastExecutionResult = result["Status"].toString();
task.lastStartTime = result["StartTimeUtc"].toString();
task.lastEndTime = result["EndTimeUtc"].toString();
}
return task;
}
};
Q_DECLARE_METATYPE(ScheduledTaskInfo)
struct QEMBYCORE_EXPORT LogFileInfo {
Q_GADGET
public:
QString name;
QString dateCreated;
QString dateModified;
long long size = 0;
QString readableSize() const {
if (size < 1024) return QString("%1 B").arg(size);
if (size < 1024 * 1024) return QString("%1 KB").arg(size / 1024.0, 0, 'f', 1);
return QString("%1 MB").arg(size / (1024.0 * 1024.0), 0, 'f', 1);
}
static LogFileInfo fromJson(const QJsonObject& obj) {
LogFileInfo log;
log.name = obj["Name"].toString();
log.dateCreated = obj["DateCreated"].toString();
log.dateModified = obj["DateModified"].toString();
log.size = obj["Size"].toVariant().toLongLong();
return log;
}
};
Q_DECLARE_METATYPE(LogFileInfo)
struct QEMBYCORE_EXPORT ActivityLogEntry {
Q_GADGET
public:
qint64 id = 0;
QString name;
QString type;
QString overview;
QString shortOverview;
QString date;
QString userId;
QString severity;
QString itemId;
static ActivityLogEntry fromJson(const QJsonObject& obj) {
ActivityLogEntry entry;
entry.id = obj["Id"].toVariant().toLongLong();
entry.name = obj["Name"].toString();
entry.type = obj["Type"].toString();
entry.overview = obj["Overview"].toString();
entry.shortOverview = obj["ShortOverview"].toString();
entry.date = obj["Date"].toString();
entry.userId = obj["UserId"].toString();
entry.severity = obj["Severity"].toString();
entry.itemId = obj["ItemId"].toString();
return entry;
}
};
Q_DECLARE_METATYPE(ActivityLogEntry)
#endif

View File

@@ -0,0 +1,194 @@
#ifndef DANMAKUMODELS_H
#define DANMAKUMODELS_H
#include "../../qEmbyCore_global.h"
#include <QColor>
#include <QDateTime>
#include <QList>
#include <QString>
#include <QStringList>
#include <QVariantMap>
struct QEMBYCORE_EXPORT DanmakuServerDefinition {
QString id;
QString name;
QString provider = QStringLiteral("dandanplay");
QString baseUrl = QStringLiteral("https://api.dandanplay.net");
QString description;
QString appId;
QString appSecret;
QString contentScope;
bool builtIn = false;
bool enabled = true;
bool isValid() const {
return !id.trimmed().isEmpty() && !name.trimmed().isEmpty() &&
!baseUrl.trimmed().isEmpty();
}
QString displayName() const {
return name.trimmed().isEmpty() ? baseUrl.trimmed() : name.trimmed();
}
};
Q_DECLARE_METATYPE(DanmakuServerDefinition)
struct QEMBYCORE_EXPORT DanmakuProviderConfig {
QString provider = QStringLiteral("dandanplay");
QString baseUrl = QStringLiteral("https://api.dandanplay.net");
QString endpointId;
QString endpointName;
QString appId;
QString appSecret;
QString contentScope;
bool enabled = true;
bool withRelated = true;
int cacheHours = 24;
};
Q_DECLARE_METATYPE(DanmakuProviderConfig)
struct QEMBYCORE_EXPORT DanmakuMediaContext {
QString serverId;
QString mediaId;
QString mediaSourceId;
QString itemType;
QString title;
QString originalTitle;
QString seriesName;
int productionYear = 0;
int seasonNumber = -1;
int episodeNumber = -1;
qint64 durationMs = 0;
QString path;
QVariantMap providerIds;
QString cacheKey() const {
return mediaSourceId.isEmpty() ? mediaId
: QStringLiteral("%1:%2")
.arg(mediaId, mediaSourceId);
}
bool isEpisode() const {
return itemType.compare(QStringLiteral("Episode"),
Qt::CaseInsensitive) == 0;
}
QString displayTitle() const {
if (isEpisode() && !seriesName.isEmpty()) {
return QStringLiteral("%1 S%2E%3")
.arg(seriesName)
.arg(seasonNumber, 2, 10, QChar('0'))
.arg(episodeNumber, 2, 10, QChar('0'));
}
return title;
}
};
Q_DECLARE_METATYPE(DanmakuMediaContext)
struct QEMBYCORE_EXPORT DanmakuMatchCandidate {
QString provider;
QString cacheScope;
QString endpointId;
QString endpointName;
QString targetId;
QString title;
QString subtitle;
int seasonNumber = -1;
int episodeNumber = -1;
qint64 durationMs = 0;
double score = 0.0;
QString matchReason;
int commentCount = 0;
bool isValid() const {
return !provider.isEmpty() && !targetId.isEmpty();
}
QString displayText() const {
return subtitle.isEmpty() ? title
: QStringLiteral("%1 - %2")
.arg(subtitle, title);
}
};
Q_DECLARE_METATYPE(DanmakuMatchCandidate)
struct QEMBYCORE_EXPORT DanmakuMatchResult {
bool matched = false;
bool cacheHit = false;
bool manualOverride = false;
DanmakuMatchCandidate selected;
QList<DanmakuMatchCandidate> candidates;
bool needManualMatch() const {
return !matched && !candidates.isEmpty();
}
};
Q_DECLARE_METATYPE(DanmakuMatchResult)
struct QEMBYCORE_EXPORT DanmakuComment {
qint64 timeMs = 0;
int mode = 1;
QColor color = QColor(Qt::white);
int fontLevel = 25;
QString sender;
QString text;
QDateTime createdAt;
bool isValid() const {
return !text.trimmed().isEmpty() && timeMs >= 0;
}
};
Q_DECLARE_METATYPE(DanmakuComment)
struct QEMBYCORE_EXPORT DanmakuRenderOptions {
bool enabled = true;
double opacity = 0.72;
double fontScale = 1.0;
int fontWeight = 400;
double outlineSize = 3.0;
double shadowOffset = 1.0;
int areaPercent = 70;
int density = 100;
double speedScale = 0.5;
int offsetMs = 0;
bool hideScroll = false;
bool hideTop = false;
bool hideBottom = false;
bool dualSubtitle = true;
QStringList blockedKeywords;
};
Q_DECLARE_METATYPE(DanmakuRenderOptions)
struct QEMBYCORE_EXPORT DanmakuLoadResult {
bool success = false;
QString assFilePath;
int commentCount = 0;
QString provider;
QString sourceServerId;
QString sourceServerName;
QString sourceTitle;
bool needManualMatch = false;
QList<DanmakuComment> comments;
DanmakuMatchResult matchResult;
bool hasComments() const {
return commentCount > 0 && hasRenderableContent();
}
bool hasCommentPayload() const {
return !comments.isEmpty();
}
bool hasRenderableTrack() const {
return success && !assFilePath.isEmpty();
}
bool hasRenderableContent() const {
return hasRenderableTrack() || hasCommentPayload();
}
};
Q_DECLARE_METATYPE(DanmakuLoadResult)
#endif

View File

@@ -0,0 +1,277 @@
#include "mediaitem.h"
#include <QDateTime>
MediaPersonInfo MediaPersonInfo::fromJson(const QJsonObject& obj) {
MediaPersonInfo person;
person.id = obj["Id"].toString();
person.name = obj["Name"].toString();
person.role = obj["Role"].toString();
person.type = obj["Type"].toString();
if (obj.contains("PrimaryImageTag")) {
person.primaryImageTag = obj["PrimaryImageTag"].toString();
}
return person;
}
MediaStudioInfo MediaStudioInfo::fromJson(const QJsonObject& obj) {
MediaStudioInfo studio;
studio.name = obj["Name"].toString();
studio.id = obj["Id"].toVariant().toString();
return studio;
}
MediaExternalUrlInfo MediaExternalUrlInfo::fromJson(const QJsonObject& obj) {
MediaExternalUrlInfo urlInfo;
urlInfo.name = obj["Name"].toString();
urlInfo.url = obj["Url"].toString();
return urlInfo;
}
QPair<QString, QString> MediaImageInfo::bestPoster() const {
if (!primaryTag.isEmpty()) return {primaryTag, QStringLiteral("Primary")};
if (!thumbTag.isEmpty()) return {thumbTag, QStringLiteral("Thumb")};
if (!backdropTag.isEmpty()) return {backdropTag, QStringLiteral("Backdrop")};
if (!parentPrimaryTag.isEmpty()) return {parentPrimaryTag, QStringLiteral("Primary")};
if (!parentBackdropTag.isEmpty()) return {parentBackdropTag, QStringLiteral("Backdrop")};
return {{}, {}};
}
QPair<QString, QString> MediaImageInfo::bestThumb() const {
if (!thumbTag.isEmpty()) return {thumbTag, QStringLiteral("Thumb")};
if (!backdropTag.isEmpty()) return {backdropTag, QStringLiteral("Backdrop")};
if (!primaryTag.isEmpty()) return {primaryTag, QStringLiteral("Primary")};
if (!parentBackdropTag.isEmpty()) return {parentBackdropTag, QStringLiteral("Backdrop")};
if (!parentThumbTag.isEmpty()) return {parentThumbTag, QStringLiteral("Thumb")};
if (!parentPrimaryTag.isEmpty()) return {parentPrimaryTag, QStringLiteral("Primary")};
return {{}, {}};
}
QPair<QString, QString> MediaImageInfo::bestBackdrop() const {
if (!backdropTag.isEmpty()) return {backdropTag, QStringLiteral("Backdrop")};
if (!thumbTag.isEmpty()) return {thumbTag, QStringLiteral("Thumb")};
if (!primaryTag.isEmpty()) return {primaryTag, QStringLiteral("Primary")};
if (!parentBackdropTag.isEmpty()) return {parentBackdropTag, QStringLiteral("Backdrop")};
if (!parentThumbTag.isEmpty()) return {parentThumbTag, QStringLiteral("Thumb")};
return {{}, {}};
}
bool MediaImageInfo::isParentTag(const QString& tag) const {
if (tag.isEmpty()) return false;
return (tag == parentPrimaryTag || tag == parentBackdropTag || tag == parentThumbTag);
}
MediaImageInfo MediaImageInfo::fromJson(const QJsonObject& obj) {
MediaImageInfo info;
info.primaryImageAspectRatio = obj["PrimaryImageAspectRatio"].toDouble();
if (obj.contains("ImageTags")) {
QJsonObject tagsObj = obj["ImageTags"].toObject();
if (tagsObj.contains("Primary")) info.primaryTag = tagsObj["Primary"].toString();
if (tagsObj.contains("Thumb")) info.thumbTag = tagsObj["Thumb"].toString();
if (tagsObj.contains("Logo")) info.logoTag = tagsObj["Logo"].toString();
if (tagsObj.contains("Backdrop")) info.backdropTag = tagsObj["Backdrop"].toString();
}
if (obj.contains("PrimaryImageItemId")) {
info.primaryImageItemId = obj["PrimaryImageItemId"].toString();
}
if (info.primaryTag.isEmpty() && obj.contains("PrimaryImageTag")) {
info.primaryTag = obj["PrimaryImageTag"].toString();
}
if (obj.contains("BackdropImageTags")) {
QJsonArray backdropTags = obj["BackdropImageTags"].toArray();
if (!backdropTags.isEmpty()) {
info.backdropTag = backdropTags[0].toString();
}
}
if (obj.contains("SeriesPrimaryImageTag")) {
info.parentPrimaryTag = obj["SeriesPrimaryImageTag"].toString();
}
if (obj.contains("ParentThumbImageTag")) {
info.parentThumbTag = obj["ParentThumbImageTag"].toString();
}
if (obj.contains("ParentBackdropImageTags")) {
QJsonArray parentBackdrops = obj["ParentBackdropImageTags"].toArray();
if (!parentBackdrops.isEmpty()) {
info.parentBackdropTag = parentBackdrops[0].toString();
}
}
if (obj.contains("SeriesId")) {
info.parentImageItemId = obj["SeriesId"].toString();
}
return info;
}
MediaUserDataInfo MediaUserDataInfo::fromJson(const QJsonObject& obj) {
MediaUserDataInfo ud;
ud.isFavorite = obj["IsFavorite"].toBool();
ud.playedPercentage = obj["PlayedPercentage"].toDouble();
ud.playbackPositionTicks = obj["PlaybackPositionTicks"].toVariant().toLongLong();
ud.played = obj["Played"].toBool();
ud.playCount = obj["PlayCount"].toInt();
ud.lastPlayedDate = obj["LastPlayedDate"].toString();
return ud;
}
MediaItem MediaItem::fromJson(const QJsonObject& obj) {
MediaItem item;
item.id = obj["Id"].toString();
item.name = obj["Name"].toString();
item.originalTitle = obj["OriginalTitle"].toString();
item.sortName = obj["SortName"].toString();
item.type = obj["Type"].toString();
item.mediaType = obj["MediaType"].toString();
item.collectionType = obj["CollectionType"].toString();
item.collectionId = obj["CollectionId"].toString();
item.collectionName = obj["CollectionName"].toString();
item.playlistItemId = obj["PlaylistItemId"].toString();
item.seriesId = obj["SeriesId"].toString();
item.seriesName = obj["SeriesName"].toString();
item.parentIndexNumber = obj.contains("ParentIndexNumber") ? obj["ParentIndexNumber"].toInt(-1) : -1;
item.indexNumber = obj.contains("IndexNumber") ? obj["IndexNumber"].toInt(-1) : -1;
if (obj.contains("ProviderIds")) {
const QJsonObject providerIdsObj = obj["ProviderIds"].toObject();
for (auto it = providerIdsObj.constBegin(); it != providerIdsObj.constEnd(); ++it) {
item.providerIds.insert(it.key(), it.value().toVariant());
}
}
item.productionYear = obj["ProductionYear"].toInt();
item.overview = obj["Overview"].toString();
item.officialRating = obj["OfficialRating"].toString();
item.criticRating = obj["CriticRating"].toInt();
item.runTimeTicks = obj["RunTimeTicks"].toVariant().toLongLong();
item.communityRating = obj["CommunityRating"].toDouble();
item.canDownload = obj["CanDownload"].toBool(false);
if (obj.contains("PremiereDate")) {
item.premiereDate = obj["PremiereDate"].toString();
int tIdx = item.premiereDate.indexOf('T');
if (tIdx > 0) item.premiereDate = item.premiereDate.left(tIdx);
}
if (obj.contains("DateCreated")) {
QString rawDate = obj["DateCreated"].toString();
item.dateCreatedRaw = rawDate;
QDateTime dt = QDateTime::fromString(rawDate, Qt::ISODate);
if (dt.isValid()) {
item.dateCreated = dt.toLocalTime().toString("yyyy-MM-dd HH:mm");
} else {
item.dateCreated = rawDate;
int tIdx = item.dateCreated.indexOf('T');
if (tIdx > 0 && item.dateCreated.length() >= tIdx + 6) {
item.dateCreated = item.dateCreated.left(tIdx + 6).replace('T', ' ');
}
}
}
item.container = obj["Container"].toString();
item.path = obj["Path"].toString();
item.size = obj["Size"].toVariant().toLongLong();
item.bitrate = obj["Bitrate"].toVariant().toLongLong();
item.width = obj["Width"].toInt();
item.height = obj["Height"].toInt();
item.childCount = obj["ChildCount"].toInt();
item.recursiveItemCount = obj["RecursiveItemCount"].toInt();
if (obj.contains("UserData")) {
item.userData = MediaUserDataInfo::fromJson(obj["UserData"].toObject());
}
item.images = MediaImageInfo::fromJson(obj);
if (obj.contains("GenreItems")) {
QJsonArray arr = obj["GenreItems"].toArray();
for (const auto& val : arr) {
QString name = val.toObject()["Name"].toString();
if (!name.isEmpty()) item.genres.append(name);
}
}
if (item.genres.isEmpty() && obj.contains("Genres")) {
QJsonArray arr = obj["Genres"].toArray();
for (const auto& val : arr) item.genres.append(val.toString());
}
if (obj.contains("Taglines")) {
QJsonArray arr = obj["Taglines"].toArray();
for (const auto& val : arr) item.taglines.append(val.toString());
}
if (obj.contains("TagItems")) {
QJsonArray arr = obj["TagItems"].toArray();
for (const auto& val : arr) item.tags.append(val.toObject()["Name"].toString());
}
if (obj.contains("RemoteTrailers")) {
QJsonArray arr = obj["RemoteTrailers"].toArray();
for (const auto& val : arr) item.remoteTrailers.append(val.toObject()["Url"].toString());
}
if (obj.contains("People")) {
QJsonArray arr = obj["People"].toArray();
for (const auto& val : arr) {
item.people.append(MediaPersonInfo::fromJson(val.toObject()));
}
}
if (obj.contains("Studios")) {
QJsonArray arr = obj["Studios"].toArray();
for (const auto& val : arr) {
item.studios.append(MediaStudioInfo::fromJson(val.toObject()));
}
}
if (obj.contains("ExternalUrls")) {
QJsonArray arr = obj["ExternalUrls"].toArray();
for (const auto& val : arr) {
item.externalUrls.append(MediaExternalUrlInfo::fromJson(val.toObject()));
}
}
if (obj.contains("MediaSources")) {
QJsonArray arr = obj["MediaSources"].toArray();
for (const auto& val : arr) {
item.mediaSources.append(MediaSourceInfo::fromJson(val.toObject()));
}
}
return item;
}

View File

@@ -0,0 +1,249 @@
#ifndef MEDIAITEM_H
#define MEDIAITEM_H
#include <QString>
#include <QJsonObject>
#include <QJsonArray>
#include <QStringList>
#include <QList>
#include <QVariant>
#include <QMetaType>
#include <QObject>
#include "../../qEmbyCore_global.h"
#include "playbackinfo.h"
struct QEMBYCORE_EXPORT MediaPersonInfo {
Q_GADGET
Q_PROPERTY(QString id MEMBER id)
Q_PROPERTY(QString name MEMBER name)
Q_PROPERTY(QString role MEMBER role)
Q_PROPERTY(QString type MEMBER type)
Q_PROPERTY(QString primaryImageTag MEMBER primaryImageTag)
public:
QString id;
QString name;
QString role;
QString type;
QString primaryImageTag;
static MediaPersonInfo fromJson(const QJsonObject& obj);
};
Q_DECLARE_METATYPE(MediaPersonInfo)
struct QEMBYCORE_EXPORT MediaStudioInfo {
Q_GADGET
Q_PROPERTY(QString id MEMBER id)
Q_PROPERTY(QString name MEMBER name)
public:
QString id;
QString name;
static MediaStudioInfo fromJson(const QJsonObject& obj);
};
Q_DECLARE_METATYPE(MediaStudioInfo)
struct QEMBYCORE_EXPORT MediaExternalUrlInfo {
Q_GADGET
Q_PROPERTY(QString name MEMBER name)
Q_PROPERTY(QString url MEMBER url)
public:
QString name;
QString url;
static MediaExternalUrlInfo fromJson(const QJsonObject& obj);
};
Q_DECLARE_METATYPE(MediaExternalUrlInfo)
struct QEMBYCORE_EXPORT MediaImageInfo {
Q_GADGET
Q_PROPERTY(QString primaryTag MEMBER primaryTag)
Q_PROPERTY(QString thumbTag MEMBER thumbTag)
Q_PROPERTY(QString backdropTag MEMBER backdropTag)
Q_PROPERTY(QString logoTag MEMBER logoTag)
Q_PROPERTY(double primaryImageAspectRatio MEMBER primaryImageAspectRatio)
Q_PROPERTY(QString primaryImageItemId MEMBER primaryImageItemId)
public:
QString primaryTag;
QString thumbTag;
QString backdropTag;
QString logoTag;
double primaryImageAspectRatio = 0.0;
QString primaryImageItemId;
QString parentPrimaryTag;
QString parentBackdropTag;
QString parentThumbTag;
QString parentImageItemId;
QPair<QString, QString> bestPoster() const;
QPair<QString, QString> bestThumb() const;
QPair<QString, QString> bestBackdrop() const;
bool isParentTag(const QString& tag) const;
static MediaImageInfo fromJson(const QJsonObject& obj);
};
Q_DECLARE_METATYPE(MediaImageInfo)
struct QEMBYCORE_EXPORT MediaUserDataInfo {
Q_GADGET
Q_PROPERTY(bool isFavorite MEMBER isFavorite)
Q_PROPERTY(double playedPercentage MEMBER playedPercentage)
Q_PROPERTY(long long playbackPositionTicks MEMBER playbackPositionTicks)
Q_PROPERTY(bool played MEMBER played)
Q_PROPERTY(int playCount MEMBER playCount)
Q_PROPERTY(QString lastPlayedDate MEMBER lastPlayedDate)
public:
bool isFavorite = false;
double playedPercentage = 0.0;
long long playbackPositionTicks = 0;
bool played = false;
int playCount = 0;
QString lastPlayedDate;
static MediaUserDataInfo fromJson(const QJsonObject& obj);
};
Q_DECLARE_METATYPE(MediaUserDataInfo)
struct QEMBYCORE_EXPORT MediaItem {
Q_GADGET
Q_PROPERTY(QString id MEMBER id)
Q_PROPERTY(QString name MEMBER name)
Q_PROPERTY(QString originalTitle MEMBER originalTitle)
Q_PROPERTY(QString sortName MEMBER sortName)
Q_PROPERTY(QString type MEMBER type)
Q_PROPERTY(QString mediaType MEMBER mediaType)
Q_PROPERTY(QString collectionType MEMBER collectionType)
Q_PROPERTY(QString collectionId MEMBER collectionId)
Q_PROPERTY(QString collectionName MEMBER collectionName)
Q_PROPERTY(QString playlistId MEMBER playlistId)
Q_PROPERTY(QString playlistItemId MEMBER playlistItemId)
Q_PROPERTY(QString seriesId MEMBER seriesId)
Q_PROPERTY(QString seriesName MEMBER seriesName)
Q_PROPERTY(QVariantMap providerIds MEMBER providerIds)
Q_PROPERTY(int productionYear MEMBER productionYear)
Q_PROPERTY(QString overview MEMBER overview)
Q_PROPERTY(QString officialRating MEMBER officialRating)
Q_PROPERTY(QString premiereDate MEMBER premiereDate)
Q_PROPERTY(QString dateCreated MEMBER dateCreated)
Q_PROPERTY(QString dateCreatedRaw MEMBER dateCreatedRaw)
Q_PROPERTY(int criticRating MEMBER criticRating)
Q_PROPERTY(bool canDownload MEMBER canDownload)
Q_PROPERTY(QString container MEMBER container)
Q_PROPERTY(QString path MEMBER path)
Q_PROPERTY(long long size MEMBER size)
Q_PROPERTY(long long bitrate MEMBER bitrate)
Q_PROPERTY(int width MEMBER width)
Q_PROPERTY(int height MEMBER height)
Q_PROPERTY(int partCount MEMBER partCount)
Q_PROPERTY(int childCount MEMBER childCount)
Q_PROPERTY(int recursiveItemCount MEMBER recursiveItemCount)
Q_PROPERTY(int parentIndexNumber MEMBER parentIndexNumber)
Q_PROPERTY(int indexNumber MEMBER indexNumber)
public:
QString id;
QString name;
QString originalTitle;
QString sortName;
QString type;
QString mediaType;
QString collectionType;
QString collectionId;
QString collectionName;
QString playlistId;
QString playlistItemId;
QString seriesId;
QString seriesName;
QVariantMap providerIds;
int productionYear = 0;
QString overview;
QString officialRating;
QString premiereDate;
QString dateCreated;
QString dateCreatedRaw;
long long runTimeTicks = 0;
double communityRating = 0.0;
int criticRating = 0;
bool canDownload = false;
QString container;
QString path;
long long size = 0;
long long bitrate = 0;
int width = 0;
int height = 0;
int partCount = 1;
int childCount = 0;
int recursiveItemCount = 0;
int parentIndexNumber = -1;
int indexNumber = -1;
QStringList genres;
QStringList tags;
QStringList taglines;
QStringList remoteTrailers;
MediaImageInfo images;
MediaUserDataInfo userData;
QString resumeItemId;
MediaUserDataInfo resumeUserData;
bool hasResumeContext = false;
QList<MediaPersonInfo> people;
QList<MediaStudioInfo> studios;
QList<MediaExternalUrlInfo> externalUrls;
QList<MediaSourceInfo> mediaSources;
bool isFavorite() const { return userData.isFavorite; }
QString primaryImageTag() const { return images.primaryTag; }
QString backdropImageTag() const { return images.backdropTag; }
QString getPrimaryImageId() const {
return images.primaryImageItemId.isEmpty() ? id : images.primaryImageItemId;
}
static MediaItem fromJson(const QJsonObject& obj);
};
Q_DECLARE_METATYPE(MediaItem)
#endif

View File

@@ -0,0 +1,119 @@
#include "playbackinfo.h"
#include <QJsonArray>
#include <QStringList>
#include <QVariant>
namespace {
QDateTime parseOptionalDateTime(const QJsonObject& json, const QStringList& keys)
{
for (const QString& key : keys) {
const QString rawValue = json.value(key).toString().trimmed();
if (rawValue.isEmpty()) {
continue;
}
QDateTime parsed = QDateTime::fromString(rawValue, Qt::ISODateWithMs);
if (!parsed.isValid()) {
parsed = QDateTime::fromString(rawValue, Qt::ISODate);
}
if (parsed.isValid()) {
return parsed;
}
}
return {};
}
}
MediaStreamInfo MediaStreamInfo::fromJson(const QJsonObject& json) {
MediaStreamInfo info;
info.type = json["Type"].toString();
info.codec = json["Codec"].toString();
info.codecTag = json["CodecTag"].toString();
info.title = json["Title"].toString();
info.displayTitle = json["DisplayTitle"].toString();
info.profile = json["Profile"].toString();
info.level = json["Level"].toInt();
info.width = json["Width"].toInt();
info.height = json["Height"].toInt();
info.aspectRatio = json["AspectRatio"].toString();
info.isInterlaced = json["IsInterlaced"].toBool();
info.realFrameRate = json["RealFrameRate"].toDouble();
info.bitRate = json["BitRate"].toVariant().toLongLong();
info.bitDepth = json["BitDepth"].toInt();
info.pixelFormat = json["PixelFormat"].toString();
info.refFrames = json["RefFrames"].toInt();
info.language = json["Language"].toString();
info.channelLayout = json["ChannelLayout"].toString();
info.channels = json["Channels"].toInt();
info.sampleRate = json["SampleRate"].toInt();
info.isDefault = json["IsDefault"].toBool();
info.displayLanguage = json["DisplayLanguage"].toString();
info.isForced = json["IsForced"].toBool();
info.isHearingImpaired = json["IsHearingImpaired"].toBool();
info.index = json.contains("Index") ? json["Index"].toInt() : -1;
info.isExternal = json["IsExternal"].toBool();
info.deliveryMethod = json["DeliveryMethod"].toString();
info.deliveryUrl = json["DeliveryUrl"].toString();
info.isExternalUrl = json["IsExternalUrl"].toBool();
info.isTextSubtitleStream = json["IsTextSubtitleStream"].toBool();
info.supportsExternalStream = json["SupportsExternalStream"].toBool();
info.path = json["Path"].toString();
info.protocol = json["Protocol"].toString();
info.extendedVideoType = json["ExtendedVideoType"].toString();
info.extendedVideoSubType = json["ExtendedVideoSubType"].toString();
info.attachmentSize = json["AttachmentSize"].toVariant().toLongLong();
return info;
}
MediaSourceInfo MediaSourceInfo::fromJson(const QJsonObject& json) {
MediaSourceInfo info;
info.id = json["Id"].toString();
info.name = json["Name"].toString();
info.path = json["Path"].toString();
info.container = json["Container"].toString();
info.directStreamUrl = json["DirectStreamUrl"].toString();
if (json.contains("Size")) {
info.size = json["Size"].toVariant().toLongLong();
}
if (json.contains("RunTimeTicks")) {
info.runTimeTicks = json["RunTimeTicks"].toVariant().toLongLong();
}
info.dateCreated = parseOptionalDateTime(
json, {QStringLiteral("DateCreated"), QStringLiteral("CreatedAt")});
info.dateModified = parseOptionalDateTime(
json, {QStringLiteral("DateModified"), QStringLiteral("ModifiedAt"),
QStringLiteral("LastModified"), QStringLiteral("UpdatedAt")});
if (json.contains("MediaStreams")) {
QJsonArray streamsArr = json["MediaStreams"].toArray();
for (const auto& val : streamsArr) {
info.mediaStreams.append(MediaStreamInfo::fromJson(val.toObject()));
}
}
return info;
}
PlaybackInfo PlaybackInfo::fromJson(const QJsonObject& json) {
PlaybackInfo info;
info.playSessionId = json["PlaySessionId"].toString();
if (json.contains("MediaSources")) {
QJsonArray sourcesArr = json["MediaSources"].toArray();
for (const auto& val : sourcesArr) {
info.mediaSources.append(MediaSourceInfo::fromJson(val.toObject()));
}
}
return info;
}

View File

@@ -0,0 +1,140 @@
#ifndef PLAYBACKINFO_H
#define PLAYBACKINFO_H
#include "../../qEmbyCore_global.h"
#include <QDateTime>
#include <QString>
#include <QList>
#include <QJsonObject>
#include <QMetaType>
#include <QObject>
struct QEMBYCORE_EXPORT MediaStreamInfo {
Q_GADGET
Q_PROPERTY(QString type MEMBER type)
Q_PROPERTY(QString codec MEMBER codec)
Q_PROPERTY(QString codecTag MEMBER codecTag)
Q_PROPERTY(QString title MEMBER title)
Q_PROPERTY(QString displayTitle MEMBER displayTitle)
Q_PROPERTY(QString profile MEMBER profile)
Q_PROPERTY(int level MEMBER level)
Q_PROPERTY(int width MEMBER width)
Q_PROPERTY(int height MEMBER height)
Q_PROPERTY(QString aspectRatio MEMBER aspectRatio)
Q_PROPERTY(bool isInterlaced MEMBER isInterlaced)
Q_PROPERTY(float realFrameRate MEMBER realFrameRate)
Q_PROPERTY(long long bitRate MEMBER bitRate)
Q_PROPERTY(int bitDepth MEMBER bitDepth)
Q_PROPERTY(QString pixelFormat MEMBER pixelFormat)
Q_PROPERTY(int refFrames MEMBER refFrames)
Q_PROPERTY(QString language MEMBER language)
Q_PROPERTY(QString channelLayout MEMBER channelLayout)
Q_PROPERTY(int channels MEMBER channels)
Q_PROPERTY(int sampleRate MEMBER sampleRate)
Q_PROPERTY(bool isDefault MEMBER isDefault)
Q_PROPERTY(QString displayLanguage MEMBER displayLanguage)
Q_PROPERTY(bool isForced MEMBER isForced)
Q_PROPERTY(bool isHearingImpaired MEMBER isHearingImpaired)
Q_PROPERTY(int index MEMBER index)
Q_PROPERTY(bool isExternal MEMBER isExternal)
Q_PROPERTY(QString deliveryMethod MEMBER deliveryMethod)
Q_PROPERTY(QString deliveryUrl MEMBER deliveryUrl)
Q_PROPERTY(bool isExternalUrl MEMBER isExternalUrl)
Q_PROPERTY(bool isTextSubtitleStream MEMBER isTextSubtitleStream)
Q_PROPERTY(bool supportsExternalStream MEMBER supportsExternalStream)
Q_PROPERTY(QString path MEMBER path)
Q_PROPERTY(QString protocol MEMBER protocol)
Q_PROPERTY(QString extendedVideoType MEMBER extendedVideoType)
Q_PROPERTY(QString extendedVideoSubType MEMBER extendedVideoSubType)
Q_PROPERTY(long long attachmentSize MEMBER attachmentSize)
public:
QString type;
QString codec;
QString codecTag;
QString title;
QString displayTitle;
QString profile;
int level = 0;
int width = 0;
int height = 0;
QString aspectRatio;
bool isInterlaced = false;
float realFrameRate = 0.0f;
long long bitRate = 0;
int bitDepth = 0;
QString pixelFormat;
int refFrames = 0;
QString language;
QString channelLayout;
int channels = 0;
int sampleRate = 0;
bool isDefault = false;
QString displayLanguage;
bool isForced = false;
bool isHearingImpaired = false;
int index = -1;
bool isExternal = false;
QString deliveryMethod;
QString deliveryUrl;
bool isExternalUrl = false;
bool isTextSubtitleStream = false;
bool supportsExternalStream = false;
QString path;
QString protocol;
QString extendedVideoType;
QString extendedVideoSubType;
long long attachmentSize = 0;
static MediaStreamInfo fromJson(const QJsonObject& json);
};
Q_DECLARE_METATYPE(MediaStreamInfo)
struct QEMBYCORE_EXPORT MediaSourceInfo {
Q_GADGET
Q_PROPERTY(QString id MEMBER id)
Q_PROPERTY(QString name MEMBER name)
Q_PROPERTY(QString path MEMBER path)
Q_PROPERTY(QString container MEMBER container)
Q_PROPERTY(long long size MEMBER size)
Q_PROPERTY(QString directStreamUrl MEMBER directStreamUrl)
Q_PROPERTY(long long runTimeTicks MEMBER runTimeTicks)
Q_PROPERTY(QDateTime dateCreated MEMBER dateCreated)
Q_PROPERTY(QDateTime dateModified MEMBER dateModified)
public:
QString id;
QString name;
QString path;
QString container;
long long size = 0;
QString directStreamUrl;
long long runTimeTicks = 0;
QDateTime dateCreated;
QDateTime dateModified;
QList<MediaStreamInfo> mediaStreams;
static MediaSourceInfo fromJson(const QJsonObject& json);
};
Q_DECLARE_METATYPE(MediaSourceInfo)
struct QEMBYCORE_EXPORT PlaybackInfo {
Q_GADGET
Q_PROPERTY(QString playSessionId MEMBER playSessionId)
public:
QString playSessionId;
QList<MediaSourceInfo> mediaSources;
static PlaybackInfo fromJson(const QJsonObject& json);
};
Q_DECLARE_METATYPE(PlaybackInfo)
#endif

View File

@@ -0,0 +1,13 @@
#ifndef PLAYERLAUNCHCONTEXT_H
#define PLAYERLAUNCHCONTEXT_H
#include "mediaitem.h"
#include "playbackinfo.h"
struct PlayerLaunchContext {
MediaItem mediaItem;
MediaSourceInfo selectedSource;
};
Q_DECLARE_METATYPE(PlayerLaunchContext)
#endif

View File

@@ -0,0 +1,92 @@
#include "proxyconfig.h"
#include <QNetworkProxyFactory>
#include <QNetworkProxyQuery>
#include <QString>
QNetworkProxy ProxyConfig::toQNetworkProxy() const {
switch (mode) {
case None:
return QNetworkProxy(QNetworkProxy::NoProxy);
case System: {
const auto proxies =
QNetworkProxyFactory::systemProxyForQuery(QNetworkProxyQuery());
if (proxies.isEmpty()) {
return QNetworkProxy(QNetworkProxy::NoProxy);
}
return proxies.first();
}
case Custom: {
if (host.trimmed().isEmpty() || port == 0) {
return QNetworkProxy(QNetworkProxy::NoProxy);
}
const QNetworkProxy::ProxyType qtType =
(type == Socks5) ? QNetworkProxy::Socks5Proxy
: QNetworkProxy::HttpProxy;
return QNetworkProxy(qtType, host.trimmed(), port,
username, password);
}
}
return QNetworkProxy(QNetworkProxy::NoProxy);
}
QJsonObject ProxyConfig::toJson() const {
QJsonObject obj;
obj["mode"] = static_cast<int>(mode);
obj["type"] = static_cast<int>(type);
obj["host"] = host;
obj["port"] = static_cast<int>(port);
obj["username"] = username;
obj["password"] = password;
obj["bypassLocalhost"] = bypassLocalhost;
return obj;
}
ProxyConfig ProxyConfig::fromJson(const QJsonObject& obj) {
ProxyConfig cfg;
if (obj.isEmpty()) {
return cfg;
}
cfg.mode = static_cast<Mode>(obj.value("mode").toInt(0));
cfg.type = static_cast<Type>(obj.value("type").toInt(0));
cfg.host = obj.value("host").toString();
const int parsedPort = obj.value("port").toInt(0);
cfg.port = (parsedPort > 0 && parsedPort <= 65535)
? static_cast<quint16>(parsedPort)
: 0;
cfg.username = obj.value("username").toString();
cfg.password = obj.value("password").toString();
cfg.bypassLocalhost = obj.value("bypassLocalhost").toBool(true);
return cfg;
}
QString ProxyConfig::summary() const {
switch (mode) {
case None:
return QStringLiteral("none");
case System:
return QStringLiteral("system");
case Custom: {
const QString typeLabel =
(type == Socks5) ? QStringLiteral("socks5") : QStringLiteral("http");
const QString credLabel =
username.isEmpty() ? QString() : QStringLiteral(" auth=***");
return QStringLiteral("custom %1://%2:%3%4")
.arg(typeLabel,
host.trimmed(),
QString::number(port),
credLabel);
}
}
return QStringLiteral("unknown");
}
bool ProxyConfig::operator==(const ProxyConfig& other) const {
return mode == other.mode && type == other.type && host == other.host &&
port == other.port && username == other.username &&
password == other.password &&
bypassLocalhost == other.bypassLocalhost;
}

View File

@@ -0,0 +1,59 @@
#ifndef PROXYCONFIG_H
#define PROXYCONFIG_H
#include "../../qEmbyCore_global.h"
#include <QJsonObject>
#include <QNetworkProxy>
#include <QString>
struct QEMBYCORE_EXPORT ProxyConfig {
enum Mode {
None = 0,
System = 1,
Custom = 2,
};
enum Type {
Http = 0,
Socks5 = 1,
};
Mode mode = None;
Type type = Http;
QString host;
quint16 port = 0;
QString username;
QString password;
bool bypassLocalhost = true;
QNetworkProxy toQNetworkProxy() const;
QJsonObject toJson() const;
static ProxyConfig fromJson(const QJsonObject& obj);
bool isExplicit() const { return mode != None; }
QString summary() const;
bool operator==(const ProxyConfig& other) const;
bool operator!=(const ProxyConfig& other) const { return !(*this == other); }
};
#endif

View File

@@ -0,0 +1,35 @@
#ifndef SERVERPROFILE_H
#define SERVERPROFILE_H
#include "proxyconfig.h"
#include <QString>
#include <QUuid>
struct ServerProfile {
enum ServerType { Emby, Jellyfin };
QString id = QUuid::createUuid().toString();
QString name;
QString url;
ServerType type = Emby;
bool ignoreSslVerification = false;
QString userId;
QString userName;
QString accessToken;
QString deviceId;
bool isAdmin = false;
bool canDownloadMedia = false;
QString iconBase64;
bool useGlobalProxy = false;
ProxyConfig proxy;
bool isValid() const { return !accessToken.isEmpty(); }
};
#endif

View File

@@ -0,0 +1,370 @@
#include "configbundle.h"
#include "../../config/configstore.h"
#include <QCoreApplication>
#include <QDateTime>
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QJsonDocument>
#include <QJsonValue>
#include <QStandardPaths>
#include <QSysInfo>
#include <QVariant>
namespace
{
constexpr char kKSchemaVersion[] = "schemaVersion";
constexpr char kKAppVersion[] = "appVersion";
constexpr char kKDeviceName[] = "deviceName";
constexpr char kKOsName[] = "osName";
constexpr char kKOsPretty[] = "osPretty";
constexpr char kKExportedAt[] = "exportedAt";
constexpr char kKMetadata[] = "metadata";
constexpr char kKServers[] = "servers";
constexpr char kKConfigEntries[] = "configEntries";
QString serversJsonPath()
{
const QString dir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
return QDir(dir).filePath(QStringLiteral("servers.json"));
}
QString compactProductType(QString type)
{
type = type.trimmed().toLower();
if (type.isEmpty())
{
return QStringLiteral("OS");
}
if (type == QStringLiteral("ubuntu"))
return QStringLiteral("Ubuntu");
if (type == QStringLiteral("debian"))
return QStringLiteral("Debian");
if (type == QStringLiteral("arch"))
return QStringLiteral("Arch");
if (type == QStringLiteral("fedora"))
return QStringLiteral("Fedora");
if (type == QStringLiteral("centos"))
return QStringLiteral("CentOS");
if (type == QStringLiteral("opensuse"))
return QStringLiteral("openSUSE");
if (type == QStringLiteral("manjaro"))
return QStringLiteral("Manjaro");
if (type == QStringLiteral("rhel"))
return QStringLiteral("RHEL");
if (type == QStringLiteral("linux"))
return QStringLiteral("Linux");
type[0] = type.at(0).toUpper();
return type;
}
int windowsBuildNumber()
{
const QStringList parts = QSysInfo::kernelVersion().split(QLatin1Char('.'));
bool ok = false;
const int build = parts.size() >= 3 ? parts.at(2).toInt(&ok) : 0;
return ok ? build : 0;
}
QString readableOsName()
{
const QString type = QSysInfo::productType().toLower();
const QString version = QSysInfo::productVersion().trimmed();
if (type == QStringLiteral("windows"))
{
const int build = windowsBuildNumber();
if (build >= 22000)
return QStringLiteral("Win11");
if (build > 0)
return QStringLiteral("Win10");
return version.isEmpty() ? QStringLiteral("Windows")
: QStringLiteral("Windows%1").arg(version);
}
if (type == QStringLiteral("macos") || type == QStringLiteral("osx"))
return version.isEmpty() ? QStringLiteral("macOS")
: QStringLiteral("macOS%1").arg(version);
const QString name = compactProductType(type);
return version.isEmpty() ? name : name + version;
}
QJsonValue variantToJson(const QVariant &v)
{
if (!v.isValid() || v.isNull()) {
return QJsonValue::Null;
}
switch (static_cast<QMetaType::Type>(v.userType())) {
case QMetaType::Bool:
return v.toBool();
case QMetaType::Int:
case QMetaType::UInt:
case QMetaType::LongLong:
case QMetaType::ULongLong:
return v.toLongLong();
case QMetaType::Double:
case QMetaType::Float:
return v.toDouble();
case QMetaType::QStringList: {
QJsonArray arr;
for (const QString &s : v.toStringList()) {
arr.append(s);
}
return arr;
}
case QMetaType::QByteArray:
{
const QByteArray ba = v.toByteArray();
const QJsonDocument tryDoc = QJsonDocument::fromJson(ba);
if (!tryDoc.isNull()) {
if (tryDoc.isObject()) return tryDoc.object();
if (tryDoc.isArray()) return tryDoc.array();
}
return QString::fromUtf8(ba);
}
case QMetaType::QString:
default:
return v.toString();
}
}
QVariant jsonToVariant(const QJsonValue &v)
{
switch (v.type()) {
case QJsonValue::Bool:
return v.toBool();
case QJsonValue::Double: {
const double d = v.toDouble();
const qlonglong i = static_cast<qlonglong>(d);
if (static_cast<double>(i) == d) {
return QVariant::fromValue(i);
}
return d;
}
case QJsonValue::String:
return v.toString();
case QJsonValue::Array:
return QString::fromUtf8(
QJsonDocument(v.toArray()).toJson(QJsonDocument::Compact));
case QJsonValue::Object:
return QString::fromUtf8(
QJsonDocument(v.toObject()).toJson(QJsonDocument::Compact));
case QJsonValue::Null:
case QJsonValue::Undefined:
default:
return QVariant();
}
}
}
QJsonObject ConfigBundleMetadata::toJson() const
{
QJsonObject obj;
obj.insert(QString::fromLatin1(kKSchemaVersion), schemaVersion);
obj.insert(QString::fromLatin1(kKAppVersion), appVersion);
obj.insert(QString::fromLatin1(kKDeviceName), deviceName);
obj.insert(QString::fromLatin1(kKOsName), osName);
obj.insert(QString::fromLatin1(kKOsPretty), osPretty);
obj.insert(QString::fromLatin1(kKExportedAt), exportedAt);
return obj;
}
ConfigBundleMetadata ConfigBundleMetadata::fromJson(const QJsonObject &obj)
{
ConfigBundleMetadata m;
m.schemaVersion = obj.value(QString::fromLatin1(kKSchemaVersion)).toInt(1);
m.appVersion = obj.value(QString::fromLatin1(kKAppVersion)).toString();
m.deviceName = obj.value(QString::fromLatin1(kKDeviceName)).toString();
m.osName = obj.value(QString::fromLatin1(kKOsName)).toString();
m.osPretty = obj.value(QString::fromLatin1(kKOsPretty)).toString();
m.exportedAt = static_cast<qint64>(obj.value(QString::fromLatin1(kKExportedAt)).toDouble(0.0));
return m;
}
ConfigBundleMetadata ConfigBundleMetadata::current()
{
ConfigBundleMetadata m;
m.schemaVersion = 1;
m.appVersion = QCoreApplication::applicationVersion();
if (m.appVersion.isEmpty()) {
m.appVersion = QStringLiteral("dev");
}
m.deviceName = QSysInfo::machineHostName();
m.osName = readableOsName();
m.osPretty = QSysInfo::prettyProductName();
m.exportedAt = QDateTime::currentMSecsSinceEpoch();
return m;
}
const QStringList &ConfigBundle::includedNamespaces()
{
static const QStringList list = {
QStringLiteral("general/"),
QStringLiteral("network/"),
QStringLiteral("appearance/"),
QStringLiteral("library/"),
QStringLiteral("player/"),
QStringLiteral("danmaku/"),
QStringLiteral("ext_player/"),
QStringLiteral("search/"),
QStringLiteral("sort/"),
QStringLiteral("view/"),
QStringLiteral("server/"),
QStringLiteral("app/"),
};
return list;
}
bool ConfigBundle::shouldExcludeKey(const QString &key)
{
static const QStringList exactBlacklist = {
QStringLiteral("general/last_selected_server_id"),
QStringLiteral("app/last_selected_server_id"),
QStringLiteral("appearance/startup_window_state"),
QStringLiteral("ext_player/detected_list"),
QStringLiteral("player/last_subtitle_dir"),
};
if (exactBlacklist.contains(key)) {
return true;
}
static const QStringList prefixBlacklist = {
QStringLiteral("cache/"),
QStringLiteral("player/external_subtitle/"),
QStringLiteral("download/"),
};
for (const QString &prefix : prefixBlacklist) {
if (key.startsWith(prefix)) {
return true;
}
}
return false;
}
QJsonObject ConfigBundle::toJson() const
{
QJsonObject root;
root.insert(QString::fromLatin1(kKMetadata), metadata.toJson());
root.insert(QString::fromLatin1(kKServers), servers);
root.insert(QString::fromLatin1(kKConfigEntries), configEntries);
return root;
}
QByteArray ConfigBundle::serialize(bool indented) const
{
return QJsonDocument(toJson()).toJson(indented ? QJsonDocument::Indented
: QJsonDocument::Compact);
}
ConfigBundle ConfigBundle::fromJson(const QJsonObject &obj)
{
ConfigBundle b;
b.metadata = ConfigBundleMetadata::fromJson(
obj.value(QString::fromLatin1(kKMetadata)).toObject());
b.servers = obj.value(QString::fromLatin1(kKServers)).toArray();
b.configEntries = obj.value(QString::fromLatin1(kKConfigEntries)).toObject();
return b;
}
std::optional<ConfigBundle> ConfigBundle::deserialize(const QByteArray &bytes)
{
QJsonParseError err{};
const QJsonDocument doc = QJsonDocument::fromJson(bytes, &err);
if (err.error != QJsonParseError::NoError || !doc.isObject()) {
qWarning() << "[ConfigBundle] deserialize parse failed:" << err.errorString();
return std::nullopt;
}
return fromJson(doc.object());
}
ConfigBundle ConfigBundle::collectFromLocal()
{
ConfigBundle b;
b.metadata = ConfigBundleMetadata::current();
{
QFile f(serversJsonPath());
if (f.open(QIODevice::ReadOnly)) {
const QByteArray bytes = f.readAll();
f.close();
const QJsonDocument doc = QJsonDocument::fromJson(bytes);
if (doc.isArray()) {
b.servers = doc.array();
} else {
qWarning() << "[ConfigBundle] servers.json is not array, skipped";
}
} else {
qDebug() << "[ConfigBundle] servers.json not present at"
<< serversJsonPath();
}
}
ConfigStore *store = ConfigStore::instance();
if (store) {
const QStringList allKeys = store->allKeys();
const QStringList &nsList = includedNamespaces();
for (const QString &key : allKeys) {
bool inWhitelist = false;
for (const QString &ns : nsList) {
if (key.startsWith(ns)) {
inWhitelist = true;
break;
}
}
if (!inWhitelist) {
continue;
}
if (shouldExcludeKey(key)) {
continue;
}
const QVariant val = store->get<QVariant>(key);
b.configEntries.insert(key, variantToJson(val));
}
}
qDebug() << "[ConfigBundle] collectFromLocal"
<< "| servers:" << b.servers.size()
<< "| entries:" << b.configEntries.size()
<< "| host:" << b.metadata.deviceName
<< "| os:" << b.metadata.osPretty
<< "| app:" << b.metadata.appVersion;
return b;
}

View File

@@ -0,0 +1,80 @@
#ifndef CONFIGBUNDLE_H
#define CONFIGBUNDLE_H
#include "../../qEmbyCore_global.h"
#include <QByteArray>
#include <QJsonArray>
#include <QJsonObject>
#include <QString>
#include <QStringList>
#include <optional>
enum class MergeStrategy
{
Replace = 0,
Merge = 1,
LocalWins = 2,
};
struct QEMBYCORE_EXPORT ConfigBundleMetadata
{
int schemaVersion = 1;
QString appVersion;
QString deviceName;
QString osName;
QString osPretty;
qint64 exportedAt = 0;
QJsonObject toJson() const;
static ConfigBundleMetadata fromJson(const QJsonObject &obj);
static ConfigBundleMetadata current();
};
class QEMBYCORE_EXPORT ConfigBundle
{
public:
ConfigBundleMetadata metadata;
QJsonArray servers;
QJsonObject configEntries;
QJsonObject toJson() const;
QByteArray serialize(bool indented = false) const;
static ConfigBundle fromJson(const QJsonObject &obj);
static std::optional<ConfigBundle> deserialize(const QByteArray &bytes);
static ConfigBundle collectFromLocal();
static const QStringList &includedNamespaces();
static bool shouldExcludeKey(const QString &key);
};
#endif