Skip to content

Commit 3ceab4b

Browse files
committed
v1.3.9
1 parent 51f9a32 commit 3ceab4b

File tree

10 files changed

+154
-116
lines changed

10 files changed

+154
-116
lines changed

Configuration/GeneralOptions.cs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ public class GeneralOptions : EditableOptionsBase
88
{
99
public override string EditorTitle => "全局设置";
1010

11-
[DisplayName("启用 MediaInfo")]
11+
[DisplayName("启用 MediaInfoKeeper 插件")]
1212
[Description("启用后优先从 JSON 恢复,提取后再写入 JSON。")]
1313
public bool PersistMediaInfoEnabled { get; set; } = true;
1414

@@ -20,13 +20,9 @@ public class GeneralOptions : EditableOptionsBase
2020
[Description("开启后阻止 Emby 自带的 ffprobe 运行,仅插件内部允许调用。")]
2121
public bool DisableSystemFfprobe { get; set; } = true;
2222

23-
[DisplayName("禁用 Emby 系统元数据刷新")]
24-
[Description("开启后阻止 Emby 默认的 CanRefresh 通过,仅插件内部允许调用。")]
25-
public bool DisableSystemMetadataRefresh { get; set; } = true;
26-
27-
[DisplayName("显示 MetadataProvidersGuard 日志")]
28-
[Description("开启后记录 CanRefresh 拦截/放行日志,默认关闭。")]
29-
public bool EnableMetadataProvidersGuardLog { get; set; } = false;
23+
[DisplayName("启用剧集元数据变动监控")]
24+
[Description("开启后将监控媒体元数据刷新过程,当剧集触发封面刷新时延迟恢复媒体信息,避免 .strm 文件刷新后出现媒体信息丢失的问题。")]
25+
public bool EnableMetadataProvidersWatcher { get; set; } = true;
3026

3127
[DisplayName("MediaInfo JSON 存储根目录")]
3228
[Description("为空时,JSON 保存到媒体文件同目录。填写后会用这个值,拼接媒体路径存储Json。")]

Configuration/RecentTaskOptions.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,5 +35,19 @@ public class RecentTaskOptions : EditableOptionsBase
3535
[EditMultilSelect]
3636
[SelectItemsSource(nameof(RefreshMetadataModeOptions))]
3737
public string RefreshMetadataMode { get; set; } = "Fill";
38+
39+
[Browsable(false)]
40+
public List<EditorSelectOption> RefreshImageModeOptions { get; set; } = new List<EditorSelectOption>
41+
{
42+
new EditorSelectOption { Value = "Fill", Name = "补全缺失" },
43+
new EditorSelectOption { Value = "Replace", Name = "全部替换" }
44+
};
45+
46+
[DisplayName("图片刷新模式")]
47+
[Description("单选,选择“补全缺失”或“全部替换”图片。")]
48+
[EditMultilSelect]
49+
[SelectItemsSource(nameof(RefreshImageModeOptions))]
50+
public string RefreshImageMode { get; set; } = "Fill";
51+
3852
}
3953
}

MediaInfoKeeper.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
<Description>MediaInfoKeeper Emby plugin</Description>
55
<PackageTags>emby;plugin;pms;media;server;</PackageTags>
66
<BaseOutputPath>Build\bin\</BaseOutputPath>
7-
<AssemblyVersion>1.3.8.0</AssemblyVersion>
8-
<FileVersion>1.3.8.0</FileVersion>
9-
<Version>1.3.8</Version>
7+
<AssemblyVersion>1.3.9.0</AssemblyVersion>
8+
<FileVersion>1.3.9.0</FileVersion>
9+
<Version>1.3.9</Version>
1010
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
1111
</PropertyGroup>
1212
<ItemGroup>

Plugin.cs

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,7 @@ public Plugin(
7777
FileSystem = fileSystem;
7878

7979
FfprobeGuard.Initialize(this.logger, this.Options.General.DisableSystemFfprobe);
80-
MetadataProvidersGuard.Initialize(this.logger,
81-
this.Options.General.DisableSystemMetadataRefresh,
82-
this.Options.General.EnableMetadataProvidersGuardLog);
80+
MetadataProvidersWatcher.Initialize(this.logger, this.Options.General.EnableMetadataProvidersWatcher);
8381

8482
this.currentPersistMediaInfo = this.Options.General.PersistMediaInfoEnabled;
8583

@@ -162,10 +160,10 @@ protected override void OnOptionsSaved(PluginConfiguration options)
162160
this.logger.Info($"DeleteMediaInfoJsonOnRemove 设置为 {options.General.DeleteMediaInfoJsonOnRemove}");
163161
this.logger.Info($"CatchupLibraries 设置为 {(string.IsNullOrEmpty(options.LibraryScope.CatchupLibraries) ? "EMPTY" : options.LibraryScope.CatchupLibraries)}");
164162
this.logger.Info($"ScheduledTaskLibraries 设置为 {(string.IsNullOrEmpty(options.LibraryScope.ScheduledTaskLibraries) ? "EMPTY" : options.LibraryScope.ScheduledTaskLibraries)}");
163+
this.logger.Info($"EnableMetadataProvidersWatcher 设置为 {options.General.EnableMetadataProvidersWatcher}");
165164

166165
FfprobeGuard.Configure(options.General.DisableSystemFfprobe);
167-
MetadataProvidersGuard.Configure(options.General.DisableSystemMetadataRefresh,
168-
options.General.EnableMetadataProvidersGuardLog);
166+
MetadataProvidersWatcher.Configure(options.General.EnableMetadataProvidersWatcher);
169167
}
170168

171169
private string NormalizeScopedLibraries(string raw)
@@ -258,28 +256,28 @@ private async void OnItemAdded(object sender, ItemChangeEventArgs e)
258256
{
259257
// 恢复失败时先触发媒体信息提取,再写入 JSON。
260258
this.logger.Info("恢复失败,开始提取 MediaInfo");
261-
// 构建用于媒体信息提取的刷新参数与库选项。
262-
var refreshOptions = MediaInfoService.GetMediaInfoRefreshOptions();
263-
var collectionFolders = (BaseItem[])this.libraryManager.GetCollectionFolders(e.Item);
264-
var libraryOptions = this.libraryManager.GetLibraryOptions(e.Item);
265-
var dummyLibraryOptions = LibraryService.CopyLibraryOptions(libraryOptions);
266259

267260
// 触发一次刷新以提取 MediaInfo。
268261
e.Item.DateLastRefreshed = new DateTimeOffset();
269262
using (FfprobeGuard.Allow())
270-
using (MetadataProvidersGuard.Allow())
271263
{
264+
this.logger.Info("恢复失败,是初次入库,进行下载元数据");
265+
// 构建用于媒体信息提取的刷新参数与库选项。
266+
var metadataRefreshOptions = new MetadataRefreshOptions(new DirectoryService(this.logger, this.fileSystem))
267+
{
268+
EnableRemoteContentProbe = true,
269+
MetadataRefreshMode = MetadataRefreshMode.FullRefresh,
270+
ImageRefreshMode = MetadataRefreshMode.FullRefresh,
271+
ReplaceAllMetadata = true,
272+
ReplaceAllImages = false
273+
};
274+
275+
var itemCollectionFolders = (BaseItem[])this.libraryManager.GetCollectionFolders(e.Item);
276+
var itemLibraryOptions = this.libraryManager.GetLibraryOptions(e.Item);
277+
e.Item.DateLastRefreshed = new DateTimeOffset();
272278
await this.providerManager
273-
.RefreshSingleItem(e.Item, refreshOptions, collectionFolders, dummyLibraryOptions, CancellationToken.None)
274-
.ConfigureAwait(false);
275-
// 父级item也尝试刷新
276-
var parentPath = e.Item.ContainingFolderPath;
277-
var parentFolder = this.libraryManager.FindByPath(parentPath, true) as Folder;
278-
logger.Info($"尝试刷新父级item: {parentFolder}");
279-
await this.providerManager
280-
.RefreshSingleItem(parentFolder, refreshOptions, collectionFolders, libraryOptions, CancellationToken.None)
279+
.RefreshSingleItem(e.Item, metadataRefreshOptions, itemCollectionFolders, itemLibraryOptions, CancellationToken.None)
281280
.ConfigureAwait(false);
282-
283281
}
284282
// 提取完成后写入 JSON。
285283
this.logger.Info("MediaInfo 提取完成,写入 JSON");
@@ -350,6 +348,7 @@ await this.providerManager
350348
this.logger.Info("已有 MediaInfo,覆盖写入 JSON");
351349
_ = MediaInfoService.SerializeMediaInfo(e.Item.InternalId, directoryService, true, "OnItemAdded Overwrite");
352350
}
351+
353352
}
354353
catch (Exception ex)
355354
{

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ MediaInfoKeeper 的目标是把媒体信息保存为 JSON,在需要时快速
6262
- 条目移除时删除 JSON:删除条目时移除对应 JSON。
6363
- 禁用 Emby 系统 ffprobe:仅插件内部允许调用。
6464
- 禁用 Emby 系统元数据刷新:仅插件内部允许调用。
65-
- 显示 MetadataProvidersGuard 日志:记录 CanRefresh 拦截/放行日志,默认关闭。
6665
- 追更媒体库:用于入库触发与删除 JSON 逻辑;留空表示全部。支持多选。
6766
- 计划任务媒体库:用于计划任务范围;留空表示全部。支持多选。
6867
- 最近入库条目数量:用于“提取媒体信息(最近入库)”计划任务,默认 100。

ScheduledTask/ExtractMediaInfoTask.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,6 @@ private async Task ProcessItemAsync(BaseItem item, string source, CancellationTo
130130
}
131131

132132
using (FfprobeGuard.Allow())
133-
using (MetadataProvidersGuard.Allow())
134133
{
135134
var filePath = item.Path;
136135
if (string.IsNullOrEmpty(filePath))

ScheduledTask/ExtractRecentMediaInfoTask.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,6 @@ private async Task ProcessItemAsync(BaseItem item, string source, CancellationTo
133133
}
134134

135135
using (FfprobeGuard.Allow())
136-
using (MetadataProvidersGuard.Allow())
137136
{
138137
var filePath = item.Path;
139138
if (string.IsNullOrEmpty(filePath))

ScheduledTask/RefreshRecentMetadataTask.cs

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,9 @@ public async Task Execute(CancellationToken cancellationToken, IProgress<double>
4646
return;
4747
}
4848

49-
var replace = ShouldReplace();
50-
this.logger.Info($"计划任务条目数 {total},覆盖模式={replace}");
49+
var replaceMetadata = ShouldReplaceMetadata();
50+
var replaceImages = ShouldReplaceImages();
51+
this.logger.Info($"计划任务条目数{total},元数据覆盖{replaceMetadata},图片覆盖{replaceImages}");
5152

5253
var current = 0;
5354
foreach (var item in items)
@@ -63,16 +64,13 @@ public async Task Execute(CancellationToken cancellationToken, IProgress<double>
6364

6465
try
6566
{
66-
var options = BuildRefreshOptions(replace);
67+
var options = BuildRefreshOptions(replaceMetadata, replaceImages);
6768
var collectionFolders = (BaseItem[])this.libraryManager.GetCollectionFolders(item);
6869
var libraryOptions = this.libraryManager.GetLibraryOptions(item);
6970

70-
using (MetadataProvidersGuard.Allow())
71-
{
72-
await Plugin.ProviderManager
73-
.RefreshSingleItem(item, options, collectionFolders, libraryOptions, cancellationToken)
74-
.ConfigureAwait(false);
75-
}
71+
await Plugin.ProviderManager
72+
.RefreshSingleItem(item, options, collectionFolders, libraryOptions, cancellationToken)
73+
.ConfigureAwait(false);
7674
// 刷新完元数据要重新从json恢复媒体信息,
7775
// 非strm会重新 ffprobe,但是没有allow所以会拦截,
7876
// strm会丢失信息,所以重新恢复
@@ -127,22 +125,38 @@ private List<BaseItem> FetchRecentItems()
127125
return items;
128126
}
129127

130-
private MetadataRefreshOptions BuildRefreshOptions(bool replace)
128+
private MetadataRefreshOptions BuildRefreshOptions(bool replaceMetadata, bool replaceImages)
131129
{
132130
var directoryService = new DirectoryService(this.logger, Plugin.FileSystem);
133131
return new MetadataRefreshOptions(directoryService)
134132
{
135133
EnableRemoteContentProbe = true,
136-
MetadataRefreshMode = replace ? MetadataRefreshMode.FullRefresh : MetadataRefreshMode.Default,
137-
ImageRefreshMode = replace ? MetadataRefreshMode.FullRefresh : MetadataRefreshMode.Default,
138-
ReplaceAllMetadata = replace,
139-
ReplaceAllImages = replace
134+
MetadataRefreshMode = MetadataRefreshMode.FullRefresh,
135+
ImageRefreshMode = MetadataRefreshMode.FullRefresh,
136+
ReplaceAllMetadata = replaceMetadata,
137+
ReplaceAllImages = replaceImages
140138
};
141139
}
142140

143-
private bool ShouldReplace()
141+
private bool ShouldReplaceMetadata()
144142
{
145143
var mode = Plugin.Instance.Options.RecentTasks.RefreshMetadataMode ?? string.Empty;
144+
return HasReplaceFlag(mode);
145+
}
146+
147+
private bool ShouldReplaceImages()
148+
{
149+
var mode = Plugin.Instance.Options.RecentTasks.RefreshImageMode;
150+
if (string.IsNullOrWhiteSpace(mode))
151+
{
152+
mode = Plugin.Instance.Options.RecentTasks.RefreshMetadataMode ?? string.Empty;
153+
}
154+
155+
return HasReplaceFlag(mode);
156+
}
157+
158+
private static bool HasReplaceFlag(string mode)
159+
{
146160
var tokens = mode.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
147161
return tokens.Any(v => v.Equals("Replace", StringComparison.OrdinalIgnoreCase));
148162
}

Services/FfprobeGuard.cs

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,14 @@ public static void Initialize(ILogger pluginLogger, bool disableSystemFfprobe)
3535
var mediaEncoding = Assembly.Load("Emby.Server.MediaEncoding");
3636
if (mediaEncoding == null)
3737
{
38-
logger.Warn("ffprobe guard init skipped: Emby.Server.MediaEncoding not found");
38+
logger.Warn("ffprobe guard 初始化跳过:未找到 Emby.Server.MediaEncoding");
3939
return;
4040
}
4141

4242
var mediaProbeManager = mediaEncoding.GetType("Emby.Server.MediaEncoding.Probing.MediaProbeManager");
4343
if (mediaProbeManager == null)
4444
{
45-
logger.Warn("ffprobe guard init skipped: MediaProbeManager type not found");
45+
logger.Warn("ffprobe guard 初始化跳过:未找到 MediaProbeManager 类型");
4646
return;
4747
}
4848

@@ -59,11 +59,11 @@ public static void Initialize(ILogger pluginLogger, bool disableSystemFfprobe)
5959

6060
if (runFfProcess == null || emptyResult == null)
6161
{
62-
logger.Warn("ffprobe guard init failed: target method not found or unsupported return type");
62+
logger.Warn("ffprobe guard 初始化失败:目标方法未找到或返回类型不支持");
6363
return;
6464
}
6565

66-
logger.Info($"ffprobe guard target: {runFfProcess.DeclaringType?.FullName}.{runFfProcess.Name}({string.Join(",", runFfProcess.GetParameters().Select(p => p.ParameterType.Name))}) -> {runFfProcess.ReturnType?.FullName}");
66+
logger.Info($"ffprobe guard 目标方法: {runFfProcess.DeclaringType?.FullName}.{runFfProcess.Name}({string.Join(",", runFfProcess.GetParameters().Select(p => p.ParameterType.Name))}) -> {runFfProcess.ReturnType?.FullName}");
6767

6868
harmony = new Harmony("mediainfokeeper.ffprobe");
6969

@@ -75,31 +75,31 @@ public static void Initialize(ILogger pluginLogger, bool disableSystemFfprobe)
7575
}
7676
catch (Exception patchEx)
7777
{
78-
logger.Error("ffprobe guard patch failed");
78+
logger.Error("ffprobe guard patch 失败");
7979
logger.Error(patchEx.Message);
8080
logger.Error(patchEx.ToString());
8181
harmony = null;
8282
isEnabled = false;
8383
return;
8484
}
8585

86-
logger.Info("ffprobe guard installed");
86+
logger.Info("ffprobe guard 已安装");
8787
}
8888
catch (Exception e)
8989
{
90-
logger.Error("ffprobe guard init failed");
90+
logger.Error("ffprobe guard 初始化失败");
9191
logger.Error(e.Message);
9292
logger.Error(e.ToString());
9393
harmony = null;
9494
isEnabled = false;
95-
logger.Warn("ffprobe guard disabled due to initialization failure; ffprobe will not be intercepted.");
95+
logger.Warn("ffprobe guard 初始化失败已禁用,ffprobe 不再拦截");
9696
}
9797
}
9898

9999
public static void Configure(bool disableSystemFfprobe)
100100
{
101101
isEnabled = disableSystemFfprobe;
102-
logger?.Info("ffprobe guard " + (isEnabled ? "enabled" : "disabled"));
102+
logger?.Info("ffprobe guard " + (isEnabled ? "已启用" : "已禁用"));
103103
}
104104

105105
/// <summary>
@@ -153,7 +153,7 @@ private static void RunFfProcessPostfix(ref object __result)
153153
var message = lines[lines.Length - 1].Trim();
154154
if (!string.IsNullOrEmpty(message))
155155
{
156-
logger.Error("ffprobe error: " + message);
156+
logger.Error("ffprobe 错误: " + message);
157157
}
158158
}
159159
}
@@ -179,7 +179,7 @@ private static object CreateEmptyResult(Type returnType)
179179
{
180180
try { standardOutput?.SetValue(payload, "{}"); }
181181
catch { /* best-effort stub */ }
182-
try { standardError?.SetValue(payload, "ffprobe suppressed by MediaInfoKeeper"); }
182+
try { standardError?.SetValue(payload, "ffprobe 已被 MediaInfoKeeper 拦截"); }
183183
catch { /* best-effort stub */ }
184184
}
185185
}
@@ -265,7 +265,7 @@ private static void LogCandidates(Type type, string methodName)
265265
.Select(m =>
266266
$"{m.Name}({string.Join(", ", m.GetParameters().Select(p => p.ParameterType.Name))}) -> {m.ReturnType?.Name}");
267267

268-
logger?.Info($"{type?.FullName}.{methodName} candidates: {string.Join("; ", candidates ?? Enumerable.Empty<string>())}");
268+
logger?.Info($"{type?.FullName}.{methodName} 候选方法: {string.Join("; ", candidates ?? Enumerable.Empty<string>())}");
269269
}
270270
catch (Exception e)
271271
{
@@ -283,7 +283,7 @@ private static void LogPropertyCandidates(Type type, string propertyName)
283283
.Where(p => p.Name == propertyName)
284284
.Select(p => $"{p.PropertyType?.Name} {p.Name}");
285285

286-
logger?.Info($"{type?.FullName}.{propertyName} property candidates: {string.Join("; ", candidates ?? Enumerable.Empty<string>())}");
286+
logger?.Info($"{type?.FullName}.{propertyName} 候选属性: {string.Join("; ", candidates ?? Enumerable.Empty<string>())}");
287287
}
288288
catch (Exception e)
289289
{

0 commit comments

Comments
 (0)