提交
This commit is contained in:
parent
3d58c7d497
commit
3525df1e95
@ -38,7 +38,7 @@ public partial class MainTreePanel : Tree
|
|||||||
SetColumnTitlesVisible(true);
|
SetColumnTitlesVisible(true);
|
||||||
|
|
||||||
SetColumnTitle(0, "文件名");
|
SetColumnTitle(0, "文件名");
|
||||||
SetColumnCustomMinimumWidth(0, 500);
|
SetColumnCustomMinimumWidth(0, 800);
|
||||||
SetColumnExpand(0, true);
|
SetColumnExpand(0, true);
|
||||||
|
|
||||||
MultiSelected += OnMultiSelected;
|
MultiSelected += OnMultiSelected;
|
||||||
|
|||||||
@ -5,5 +5,5 @@ namespace Learn.Config;
|
|||||||
[ConfigItem("APP")]
|
[ConfigItem("APP")]
|
||||||
public class AppConfig : IConfigItem
|
public class AppConfig : IConfigItem
|
||||||
{
|
{
|
||||||
public string ScanPath = "";
|
public string ScanPath { get; set; } = "";
|
||||||
}
|
}
|
||||||
1
Config/AppConfig.cs.uid
Normal file
1
Config/AppConfig.cs.uid
Normal file
@ -0,0 +1 @@
|
|||||||
|
uid://bakkudpxylhe8
|
||||||
@ -1,9 +0,0 @@
|
|||||||
using Learn.Utils;
|
|
||||||
|
|
||||||
namespace Learn.Config;
|
|
||||||
|
|
||||||
[ConfigItem("Proxy")]
|
|
||||||
public class ProxyConfig : IConfigItem
|
|
||||||
{
|
|
||||||
public string HttpProxy = "";
|
|
||||||
}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
uid://k75xslv0u3pm
|
|
||||||
58
Config/RawParserConfig.cs
Normal file
58
Config/RawParserConfig.cs
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Learn.Utils;
|
||||||
|
|
||||||
|
namespace Learn.Config;
|
||||||
|
|
||||||
|
public class MatchGroupsRules
|
||||||
|
{
|
||||||
|
public List<string> Full { get; set; }
|
||||||
|
public List<string> Partial { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class MatchTypeExtra
|
||||||
|
{
|
||||||
|
public List<string> IfDirNameIs { get; set; }
|
||||||
|
public List<string> IfFileExtensionIs { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MatchTypeSubtitle
|
||||||
|
{
|
||||||
|
public List<string> IfFileExtensionIs { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MatchTypeEpisode
|
||||||
|
{
|
||||||
|
public List<string> IfFileExtensionIs { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MatchTypeRules
|
||||||
|
{
|
||||||
|
public MatchTypeExtra Extra { get; set; }
|
||||||
|
public MatchTypeSubtitle Subtitle { get; set; }
|
||||||
|
public MatchTypeEpisode Episode { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MatchSeasonRules
|
||||||
|
{
|
||||||
|
public List<string> Regexes { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FilterTokenRules
|
||||||
|
{
|
||||||
|
public List<string> Regexes { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[ConfigItem("RawParser")]
|
||||||
|
public class RawParserConfig : IConfigItem
|
||||||
|
{
|
||||||
|
public string SplitRegex { get; set; } = "";
|
||||||
|
|
||||||
|
public MatchTypeRules TypeMatchRules { get; set; }
|
||||||
|
|
||||||
|
public MatchGroupsRules GroupsMatchRules { get; set; }
|
||||||
|
|
||||||
|
public MatchSeasonRules SeasonMatchRules { get; set; }
|
||||||
|
|
||||||
|
public FilterTokenRules TokenFilterRules { get; set; }
|
||||||
|
}
|
||||||
1
Config/RawParserConfig.cs.uid
Normal file
1
Config/RawParserConfig.cs.uid
Normal file
@ -0,0 +1 @@
|
|||||||
|
uid://ccldbt2fp1mxn
|
||||||
@ -1,9 +0,0 @@
|
|||||||
using Learn.Utils;
|
|
||||||
|
|
||||||
namespace Learn.Config;
|
|
||||||
|
|
||||||
[ConfigItem("TMDB")]
|
|
||||||
public class TMDBConfig : IConfigItem
|
|
||||||
{
|
|
||||||
public string ApiKey = "";
|
|
||||||
}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
uid://v07s3j12o0lw
|
|
||||||
10
Config/TMDBParserConfig.cs
Normal file
10
Config/TMDBParserConfig.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
using Learn.Utils;
|
||||||
|
|
||||||
|
namespace Learn.Config;
|
||||||
|
|
||||||
|
[ConfigItem("TMDBParser")]
|
||||||
|
public class TMDBParserConfig : IConfigItem
|
||||||
|
{
|
||||||
|
public string HttpProxy { get; set; } = "";
|
||||||
|
public string ApiKey { get; set; } = "";
|
||||||
|
}
|
||||||
1
Config/TMDBParserConfig.cs.uid
Normal file
1
Config/TMDBParserConfig.cs.uid
Normal file
@ -0,0 +1 @@
|
|||||||
|
uid://dp5xqvvuiclbg
|
||||||
@ -1,4 +1,5 @@
|
|||||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003APath_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F94e5a9ba04d74002870fbeeec71ff78cca738_003F85_003Fce11ac0c_003FPath_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ARawInfoPanel_005FScriptMethods_002Egenerated_002Ecs_002Fl_003AC_0021_003FUsers_003Flianzefeng_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003F9fdf5fe7bab299c9c5ee4ca6784782ae9851b9b_003FRawInfoPanel_005FScriptMethods_002Egenerated_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ARawInfoPanel_005FScriptMethods_002Egenerated_002Ecs_002Fl_003AC_0021_003FUsers_003Flianzefeng_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FSourcesCache_003F9fdf5fe7bab299c9c5ee4ca6784782ae9851b9b_003FRawInfoPanel_005FScriptMethods_002Egenerated_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AThrowHelper_002Ecs_002Fl_003AC_0021_003FUsers_003Flianzefeng_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa03380083db34a2faee436e29e06a72ae8e910_003Fa8_003Fec982e59_003FThrowHelper_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AThrowHelper_002Ecs_002Fl_003AC_0021_003FUsers_003Flianzefeng_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa03380083db34a2faee436e29e06a72ae8e910_003Fa8_003Fec982e59_003FThrowHelper_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ATreeItem_002Ecs_002Fl_003AC_0021_003FUsers_003Flianzefeng_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F1c378f459c054fecaf4484a0fa6d44c055a800_003F1d_003Fbc7bd422_003FTreeItem_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ATreeItem_002Ecs_002Fl_003AC_0021_003FUsers_003Flianzefeng_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F1c378f459c054fecaf4484a0fa6d44c055a800_003F1d_003Fbc7bd422_003FTreeItem_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
|||||||
9
Main.cs
9
Main.cs
@ -21,6 +21,7 @@ public partial class Main : Node
|
|||||||
[Export] private Button _saveButton;
|
[Export] private Button _saveButton;
|
||||||
[Export] private Button _resetButton;
|
[Export] private Button _resetButton;
|
||||||
[Export] private Button _loadButton;
|
[Export] private Button _loadButton;
|
||||||
|
[Export] private Button _reloadConfigButton;
|
||||||
|
|
||||||
[ExportGroup("编辑面板")]
|
[ExportGroup("编辑面板")]
|
||||||
[Export] private NodeInfoEditPanel _nodeInfoEditPanel;
|
[Export] private NodeInfoEditPanel _nodeInfoEditPanel;
|
||||||
@ -70,6 +71,7 @@ public partial class Main : Node
|
|||||||
_saveButton.Pressed += DoSave;
|
_saveButton.Pressed += DoSave;
|
||||||
_resetButton.Pressed += DoReset;
|
_resetButton.Pressed += DoReset;
|
||||||
_loadButton.Pressed += LoadData;
|
_loadButton.Pressed += LoadData;
|
||||||
|
_reloadConfigButton.Pressed += ReloadConfig;
|
||||||
_doParseButton.Text = "开始解析";
|
_doParseButton.Text = "开始解析";
|
||||||
|
|
||||||
_configs = new Configs();
|
_configs = new Configs();
|
||||||
@ -82,6 +84,11 @@ public partial class Main : Node
|
|||||||
_refreshPanels = true;
|
_refreshPanels = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ReloadConfig()
|
||||||
|
{
|
||||||
|
_configs.Load(true);
|
||||||
|
}
|
||||||
|
|
||||||
private void LoadData()
|
private void LoadData()
|
||||||
{
|
{
|
||||||
if (File.Exists(DataPath))
|
if (File.Exists(DataPath))
|
||||||
@ -146,7 +153,7 @@ public partial class Main : Node
|
|||||||
var originName = _doParseButton.Text;
|
var originName = _doParseButton.Text;
|
||||||
_doParseButton.Disabled = true;
|
_doParseButton.Disabled = true;
|
||||||
|
|
||||||
ItemParser parser = new RawParser();
|
ItemParser parser = new RawParser(_configs);
|
||||||
|
|
||||||
if (_mainTreePanel.Query(root, out var node))
|
if (_mainTreePanel.Query(root, out var node))
|
||||||
{
|
{
|
||||||
|
|||||||
@ -17,7 +17,7 @@ grow_vertical = 2
|
|||||||
[node name="FileDirDialog" type="FileDialog" parent="."]
|
[node name="FileDirDialog" type="FileDialog" parent="."]
|
||||||
script = ExtResource("1_d2g23")
|
script = ExtResource("1_d2g23")
|
||||||
|
|
||||||
[node name="Main" type="Node" parent="." node_paths=PackedStringArray("_dirSelector", "_openDirButton", "_doParseButton", "_saveButton", "_resetButton", "_loadButton", "_nodeInfoEditPanel", "_addKeyButton", "_removeKeyButton", "_inspectorPanel", "_mainTreePanel", "_columnIndexText", "_columnWidthText", "_columnText", "_addColumnButton", "_removeColumnButton", "_clearColumnButton", "_expandAllButton", "_foldAllButton")]
|
[node name="Main" type="Node" parent="." node_paths=PackedStringArray("_dirSelector", "_openDirButton", "_doParseButton", "_saveButton", "_resetButton", "_loadButton", "_reloadConfigButton", "_nodeInfoEditPanel", "_addKeyButton", "_removeKeyButton", "_inspectorPanel", "_mainTreePanel", "_columnIndexText", "_columnWidthText", "_columnText", "_addColumnButton", "_removeColumnButton", "_clearColumnButton", "_expandAllButton", "_foldAllButton")]
|
||||||
script = ExtResource("2_0727o")
|
script = ExtResource("2_0727o")
|
||||||
_dirSelector = NodePath("../FileDirDialog")
|
_dirSelector = NodePath("../FileDirDialog")
|
||||||
_openDirButton = NodePath("../MarginContainer/HSplitContainer/ScrollContainer/VBoxContainer2/FoldableContainer4/VBoxContainer2/TabContainer/文件夹操作/ScanDir")
|
_openDirButton = NodePath("../MarginContainer/HSplitContainer/ScrollContainer/VBoxContainer2/FoldableContainer4/VBoxContainer2/TabContainer/文件夹操作/ScanDir")
|
||||||
@ -25,6 +25,7 @@ _doParseButton = NodePath("../MarginContainer/HSplitContainer/ScrollContainer/VB
|
|||||||
_saveButton = NodePath("../MarginContainer/HSplitContainer/ScrollContainer/VBoxContainer2/FoldableContainer4/VBoxContainer2/TabContainer/保存操作/Save")
|
_saveButton = NodePath("../MarginContainer/HSplitContainer/ScrollContainer/VBoxContainer2/FoldableContainer4/VBoxContainer2/TabContainer/保存操作/Save")
|
||||||
_resetButton = NodePath("../MarginContainer/HSplitContainer/ScrollContainer/VBoxContainer2/FoldableContainer4/VBoxContainer2/TabContainer/保存操作/Reset")
|
_resetButton = NodePath("../MarginContainer/HSplitContainer/ScrollContainer/VBoxContainer2/FoldableContainer4/VBoxContainer2/TabContainer/保存操作/Reset")
|
||||||
_loadButton = NodePath("../MarginContainer/HSplitContainer/ScrollContainer/VBoxContainer2/FoldableContainer4/VBoxContainer2/TabContainer/保存操作/Load")
|
_loadButton = NodePath("../MarginContainer/HSplitContainer/ScrollContainer/VBoxContainer2/FoldableContainer4/VBoxContainer2/TabContainer/保存操作/Load")
|
||||||
|
_reloadConfigButton = NodePath("../MarginContainer/HSplitContainer/ScrollContainer/VBoxContainer2/FoldableContainer4/VBoxContainer2/TabContainer/保存操作/ReloadConfig")
|
||||||
_nodeInfoEditPanel = NodePath("../MarginContainer/HSplitContainer/ScrollContainer/VBoxContainer2/FoldableContainer/VBoxContainer/NodeEditPanel")
|
_nodeInfoEditPanel = NodePath("../MarginContainer/HSplitContainer/ScrollContainer/VBoxContainer2/FoldableContainer/VBoxContainer/NodeEditPanel")
|
||||||
_addKeyButton = NodePath("../MarginContainer/HSplitContainer/ScrollContainer/VBoxContainer2/FoldableContainer/VBoxContainer/HBoxContainer/AddKey")
|
_addKeyButton = NodePath("../MarginContainer/HSplitContainer/ScrollContainer/VBoxContainer2/FoldableContainer/VBoxContainer/HBoxContainer/AddKey")
|
||||||
_removeKeyButton = NodePath("../MarginContainer/HSplitContainer/ScrollContainer/VBoxContainer2/FoldableContainer/VBoxContainer/HBoxContainer/RemoveKey")
|
_removeKeyButton = NodePath("../MarginContainer/HSplitContainer/ScrollContainer/VBoxContainer2/FoldableContainer/VBoxContainer/HBoxContainer/RemoveKey")
|
||||||
@ -85,9 +86,10 @@ layout_mode = 2
|
|||||||
[node name="TabContainer" type="TabContainer" parent="MarginContainer/HSplitContainer/ScrollContainer/VBoxContainer2/FoldableContainer4/VBoxContainer2"]
|
[node name="TabContainer" type="TabContainer" parent="MarginContainer/HSplitContainer/ScrollContainer/VBoxContainer2/FoldableContainer4/VBoxContainer2"]
|
||||||
custom_minimum_size = Vector2(0, 200)
|
custom_minimum_size = Vector2(0, 200)
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
current_tab = 0
|
current_tab = 1
|
||||||
|
|
||||||
[node name="文件夹操作" type="VBoxContainer" parent="MarginContainer/HSplitContainer/ScrollContainer/VBoxContainer2/FoldableContainer4/VBoxContainer2/TabContainer"]
|
[node name="文件夹操作" type="VBoxContainer" parent="MarginContainer/HSplitContainer/ScrollContainer/VBoxContainer2/FoldableContainer4/VBoxContainer2/TabContainer"]
|
||||||
|
visible = false
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
metadata/_tab_index = 0
|
metadata/_tab_index = 0
|
||||||
|
|
||||||
@ -102,7 +104,6 @@ size_flags_vertical = 4
|
|||||||
text = "开始解析"
|
text = "开始解析"
|
||||||
|
|
||||||
[node name="保存操作" type="VBoxContainer" parent="MarginContainer/HSplitContainer/ScrollContainer/VBoxContainer2/FoldableContainer4/VBoxContainer2/TabContainer"]
|
[node name="保存操作" type="VBoxContainer" parent="MarginContainer/HSplitContainer/ScrollContainer/VBoxContainer2/FoldableContainer4/VBoxContainer2/TabContainer"]
|
||||||
visible = false
|
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
metadata/_tab_index = 1
|
metadata/_tab_index = 1
|
||||||
|
|
||||||
@ -118,6 +119,10 @@ text = "加载树表"
|
|||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
text = "重置树表"
|
text = "重置树表"
|
||||||
|
|
||||||
|
[node name="ReloadConfig" type="Button" parent="MarginContainer/HSplitContainer/ScrollContainer/VBoxContainer2/FoldableContainer4/VBoxContainer2/TabContainer/保存操作"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "重载配置"
|
||||||
|
|
||||||
[node name="树表操作" type="VBoxContainer" parent="MarginContainer/HSplitContainer/ScrollContainer/VBoxContainer2/FoldableContainer4/VBoxContainer2/TabContainer"]
|
[node name="树表操作" type="VBoxContainer" parent="MarginContainer/HSplitContainer/ScrollContainer/VBoxContainer2/FoldableContainer4/VBoxContainer2/TabContainer"]
|
||||||
visible = false
|
visible = false
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace Learn.Models;
|
namespace Learn.Models;
|
||||||
|
|
||||||
@ -28,47 +29,45 @@ public class TreeNode(Item item)
|
|||||||
|
|
||||||
public bool TryGetValue(string key, out string value, out bool isInherited)
|
public bool TryGetValue(string key, out string value, out bool isInherited)
|
||||||
{
|
{
|
||||||
|
value = null;
|
||||||
|
isInherited = false;
|
||||||
var curr = this;
|
var curr = this;
|
||||||
|
|
||||||
|
bool valueExists = false;
|
||||||
while (curr.Info != null)
|
while (curr.Info != null)
|
||||||
{
|
{
|
||||||
if (curr.Info.Info.TryGetValue(key, out value))
|
if (curr.Info.Info.TryGetValue(key, out var currValue))
|
||||||
{
|
{
|
||||||
isInherited = curr != this;
|
value = currValue;
|
||||||
return true;
|
valueExists = true;
|
||||||
|
isInherited = (curr != this);
|
||||||
}
|
}
|
||||||
curr = curr.Parent;
|
curr = curr.Parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
value = null;
|
return valueExists;
|
||||||
isInherited = false;
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<KeyValueInfo> GetKeyValueInfos()
|
public IEnumerable<KeyValueInfo> GetKeyValueInfos()
|
||||||
{
|
{
|
||||||
var result = new List<KeyValueInfo>();
|
var dict = new Dictionary<string, KeyValueInfo>();
|
||||||
var keys = new HashSet<string>();
|
|
||||||
var curr = this;
|
var curr = this;
|
||||||
|
|
||||||
while (curr.Info != null)
|
while (curr.Info != null)
|
||||||
{
|
{
|
||||||
foreach (var kv in curr.Info.Info)
|
foreach (var kv in curr.Info.Info)
|
||||||
{
|
{
|
||||||
if(!keys.Add(kv.Key)) continue;
|
var keyValueInfo = new KeyValueInfo
|
||||||
var keyValueInfo = new KeyValueInfo();
|
|
||||||
|
|
||||||
if (curr != this)
|
|
||||||
{
|
{
|
||||||
keyValueInfo.IsInherited = true;
|
IsInherited = curr != this,
|
||||||
}
|
Key = kv.Key,
|
||||||
|
Value = kv.Value
|
||||||
|
};
|
||||||
|
|
||||||
keyValueInfo.Key = kv.Key;
|
dict[kv.Key] = keyValueInfo;
|
||||||
keyValueInfo.Value = kv.Value;
|
|
||||||
result.Add(keyValueInfo);
|
|
||||||
}
|
}
|
||||||
curr = curr.Parent;
|
curr = curr.Parent;
|
||||||
}
|
}
|
||||||
return result;
|
return dict.Values.ToList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Learn.Models;
|
using Learn.Models;
|
||||||
@ -6,6 +7,14 @@ namespace Learn.Parsers;
|
|||||||
|
|
||||||
public static class ItemFields
|
public static class ItemFields
|
||||||
{
|
{
|
||||||
|
public enum ItemType
|
||||||
|
{
|
||||||
|
Unknown,
|
||||||
|
Extra,
|
||||||
|
Subtitle,
|
||||||
|
Episode
|
||||||
|
}
|
||||||
|
|
||||||
#region 基础信息
|
#region 基础信息
|
||||||
|
|
||||||
public static string MainKey_Path => "Path";
|
public static string MainKey_Path => "Path";
|
||||||
@ -25,36 +34,27 @@ public static class ItemFields
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region 普通字段
|
||||||
|
|
||||||
public static string Key_Group => "Group";
|
public static string Key_Group => "Group";
|
||||||
public static string Key_Title => "Title";
|
public static string Key_Title => "Title";
|
||||||
public static string Key_RawTitle => "RawTitle";
|
public static string Key_RawTitle => "RawTitle";
|
||||||
public static string Key_Season => "Season";
|
public static string Key_Season => "Season";
|
||||||
public static string Key_Year => "Year";
|
public static string Key_Type => "Type";
|
||||||
|
|
||||||
|
public static string Key_SubtitleLanguage => "SubLang";
|
||||||
|
|
||||||
public static void SetGroupIfNotExist(this Item item, string group)
|
public static ItemType Type(this Item item)
|
||||||
{
|
{
|
||||||
item.Info.TryAdd(Key_Group, group);
|
if (item.Info.TryGetValue(Key_Type, out var typeName))
|
||||||
|
{
|
||||||
|
if(Enum.TryParse(typeName, out ItemType type))
|
||||||
|
{
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ItemType.Unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SetTitleIfNotExist(this Item item, string title)
|
#endregion
|
||||||
{
|
|
||||||
item.Info.TryAdd(Key_Title, title);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void SetRawTitleIfNotExist(this Item item, string rawTitle)
|
|
||||||
{
|
|
||||||
item.Info.TryAdd(Key_RawTitle, rawTitle);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void SetSeasonIfNotExist(this Item item, string season)
|
|
||||||
{
|
|
||||||
item.Info.TryAdd(Key_Season, season);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void SetYear(this Item item, int year)
|
|
||||||
{
|
|
||||||
item.Info[Key_Year] = year.ToString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -12,7 +12,6 @@ namespace Learn.Parsers;
|
|||||||
/// "集数"
|
/// "集数"
|
||||||
/// "类型":"额外内容", "字幕", "剧集"
|
/// "类型":"额外内容", "字幕", "剧集"
|
||||||
/// "字幕语言"
|
/// "字幕语言"
|
||||||
/// "年份"
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface ItemParser
|
public interface ItemParser
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,135 +1,321 @@
|
|||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Learn.Config;
|
||||||
using Learn.Models;
|
using Learn.Models;
|
||||||
|
using Learn.Utils;
|
||||||
|
|
||||||
namespace Learn.Parsers;
|
namespace Learn.Parsers;
|
||||||
|
|
||||||
public class RawParser : ItemParser
|
public class RawParser(Configs configs) : ItemParser
|
||||||
{
|
{
|
||||||
private string ParseSeasonFromCN(string season)
|
private RawParserConfig config => configs.Get<RawParserConfig>();
|
||||||
{
|
|
||||||
if (int.TryParse(season, out _)) return season;
|
|
||||||
|
|
||||||
switch (season)
|
private List<string> FilterParts(List<string> parts)
|
||||||
{
|
{
|
||||||
case "零": return "0";
|
var result = parts.ToList();
|
||||||
case "一": return "1";
|
foreach (var regex in config.TokenFilterRules.Regexes)
|
||||||
case "二": return "2";
|
{
|
||||||
case "三": return "3";
|
result.RemoveAll(part => Regex.Match(part.Trim(), regex).Success);
|
||||||
case "四": return "4";
|
|
||||||
case "五": return "5";
|
|
||||||
case "六": return "6";
|
|
||||||
case "七": return "7";
|
|
||||||
case "八": return "8";
|
|
||||||
case "九": return "9";
|
|
||||||
}
|
}
|
||||||
return season;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<string> GetParts(Item item)
|
||||||
private (string, string) SplitTitleAndSeason(string rawTitle)
|
|
||||||
{
|
{
|
||||||
var match1 = Regex.Match(rawTitle, @"第(.+)季");
|
var matches = Regex.Matches(item.Name(), config.SplitRegex).Select(match => match.Value)
|
||||||
if (match1.Success)
|
.Select(match => match.Trim())
|
||||||
{
|
.Where(match => !string.IsNullOrEmpty(match))
|
||||||
var seasonStr = match1.Groups[0].Value.Trim();
|
.ToList();
|
||||||
var season = ParseSeasonFromCN(match1.Groups[1].Value.Trim());
|
return FilterParts(matches);
|
||||||
var title = rawTitle.Replace(seasonStr, "").Trim();
|
|
||||||
return (title, season);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var match2 = Regex.Match(rawTitle, @"[Ss]eason *(\d+)");
|
private bool TryNormalizeSeason(string seasonPart, out string season)
|
||||||
if (match2.Success)
|
|
||||||
{
|
|
||||||
var seasonStr = match1.Groups[0].Value.Trim();
|
|
||||||
var season = int.Parse(match1.Groups[1].Value.Trim()).ToString();
|
|
||||||
var title = rawTitle.Replace(seasonStr, "").Trim();
|
|
||||||
return (title, season);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (rawTitle, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SimplifyMatches(List<string> matches)
|
|
||||||
{
|
|
||||||
matches.RemoveAll(match => string.IsNullOrEmpty(match.Trim()));
|
|
||||||
matches.RemoveAll(match => Regex.Match(match.Trim(), @"\d+[Pp]$").Success);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<string> GetParts(string name)
|
|
||||||
{
|
|
||||||
var matches = Regex.Matches(name, @"[^\[\]_【】]+").Select(match => match.Value).ToList();
|
|
||||||
SimplifyMatches(matches);
|
|
||||||
return matches;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool TryParseRawTitle(Item item, out string rawTitle)
|
|
||||||
{
|
|
||||||
rawTitle = null;
|
|
||||||
var name = item.Name();
|
|
||||||
var matches = GetParts(name);
|
|
||||||
|
|
||||||
if (matches.Count == 0) return false;
|
|
||||||
|
|
||||||
if (matches.Count == 1)
|
|
||||||
{
|
|
||||||
rawTitle = matches[0];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
rawTitle = matches[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
(rawTitle, _) = SplitTitleAndSeason(rawTitle);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool TryParseSeason(Item item, out string season)
|
|
||||||
{
|
{
|
||||||
season = null;
|
season = null;
|
||||||
var name = item.Name();
|
if (int.TryParse(seasonPart, out var seasonInt))
|
||||||
var matches = GetParts(name);
|
|
||||||
|
|
||||||
if (matches.Count == 0) return false;
|
|
||||||
|
|
||||||
if (matches.Count == 1)
|
|
||||||
{
|
{
|
||||||
(_, season) = SplitTitleAndSeason(matches[0]);
|
season = seasonInt.ToString();
|
||||||
if (!string.IsNullOrEmpty(season)) return true;
|
return true;
|
||||||
}
|
}
|
||||||
else
|
switch (seasonPart)
|
||||||
{
|
{
|
||||||
(_, season) = SplitTitleAndSeason(matches[1]);
|
case "零":
|
||||||
if (!string.IsNullOrEmpty(season)) return true;
|
season = "0";
|
||||||
|
return true;
|
||||||
|
case "一":
|
||||||
|
season = "1";
|
||||||
|
return true;
|
||||||
|
case "二":
|
||||||
|
season = "2";
|
||||||
|
return true;
|
||||||
|
case "三":
|
||||||
|
season = "3";
|
||||||
|
return true;
|
||||||
|
case "四":
|
||||||
|
season = "4";
|
||||||
|
return true;
|
||||||
|
case "五":
|
||||||
|
season = "5";
|
||||||
|
return true;
|
||||||
|
case "六":
|
||||||
|
season = "6";
|
||||||
|
return true;
|
||||||
|
case "七":
|
||||||
|
season = "7";
|
||||||
|
return true;
|
||||||
|
case "八":
|
||||||
|
season = "8";
|
||||||
|
return true;
|
||||||
|
case "九":
|
||||||
|
season = "9";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryMatchSeason(string token, out string seasonPart, out string matchPart)
|
||||||
|
{
|
||||||
|
seasonPart = null;
|
||||||
|
matchPart = null;
|
||||||
|
|
||||||
|
var regexes = config.SeasonMatchRules?.Regexes;
|
||||||
|
if (regexes == null) return false;
|
||||||
|
|
||||||
|
token = token.Trim();
|
||||||
|
|
||||||
|
foreach (var regex in regexes)
|
||||||
|
{
|
||||||
|
var match = Regex.Match(token, regex);
|
||||||
|
if (!match.Success) continue;
|
||||||
|
matchPart = match.Value;
|
||||||
|
return TryNormalizeSeason(match.Groups[1].Value, out seasonPart);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryParseSeason(TreeNode node, out string season, out MatchInfo matchInfo)
|
||||||
|
{
|
||||||
|
season = null;
|
||||||
|
matchInfo = null;
|
||||||
|
|
||||||
|
var parts = GetParts(node.Info);
|
||||||
|
for (int i = 0; i < parts.Count; i++)
|
||||||
|
{
|
||||||
|
var part = parts[i];
|
||||||
|
var tokens = part.Split("-");
|
||||||
|
foreach (var token in tokens)
|
||||||
|
{
|
||||||
|
if (!TryMatchSeason(token, out season, out var content)) continue;
|
||||||
|
matchInfo = new MatchInfo
|
||||||
|
{
|
||||||
|
content = content,
|
||||||
|
partIndex = i
|
||||||
|
};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryParseGroup(Item item, out string group)
|
private bool IsFullMatch(string item, List<string> sequence)
|
||||||
|
{
|
||||||
|
if(sequence == null) return false;
|
||||||
|
for (int i = 0; i < sequence.Count; i++)
|
||||||
|
{
|
||||||
|
if (item.Equals(sequence[i], StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsPartialMatch(string item, List<string> sequence)
|
||||||
|
{
|
||||||
|
if(sequence == null) return false;
|
||||||
|
for (int i = 0; i < sequence.Count; i++)
|
||||||
|
{
|
||||||
|
if (item.Contains(sequence[i], StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryParseGroup(TreeNode node, out string group, out MatchInfo matchInfo)
|
||||||
{
|
{
|
||||||
group = null;
|
group = null;
|
||||||
var name = item.Name();
|
matchInfo = null;
|
||||||
var matches = GetParts(name);
|
|
||||||
|
|
||||||
if (matches.Count <= 1) return false;
|
var parts = GetParts(node.Info);
|
||||||
group = matches[0];
|
for (int i = 0; i < parts.Count; i++)
|
||||||
|
{
|
||||||
|
if (IsFullMatch(parts[i], config.GroupsMatchRules?.Full))
|
||||||
|
{
|
||||||
|
group = parts[i];
|
||||||
|
matchInfo = new MatchInfo
|
||||||
|
{
|
||||||
|
content = parts[i],
|
||||||
|
partIndex = i
|
||||||
|
};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < parts.Count; i++)
|
||||||
|
{
|
||||||
|
if (IsPartialMatch(parts[i], config.GroupsMatchRules?.Partial))
|
||||||
|
{
|
||||||
|
group = parts[i];
|
||||||
|
matchInfo = new MatchInfo
|
||||||
|
{
|
||||||
|
content = parts[i],
|
||||||
|
partIndex = i
|
||||||
|
};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ItemFields.ItemType ParseItemType(TreeNode node)
|
||||||
|
{
|
||||||
|
// 1. 判断是否属于Extras
|
||||||
|
foreach (var extraMatchName in config.TypeMatchRules.Extra.IfDirNameIs)
|
||||||
|
{
|
||||||
|
if (node.Info.Name().Equals(extraMatchName, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return ItemFields.ItemType.Extra;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.Info.IsFolder())
|
||||||
|
{
|
||||||
|
return ItemFields.ItemType.Unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
var infoExt = Path.GetExtension(node.Info.Name());
|
||||||
|
if (string.IsNullOrEmpty(infoExt)) return ItemFields.ItemType.Extra;
|
||||||
|
foreach (var ext in config.TypeMatchRules.Extra.IfFileExtensionIs)
|
||||||
|
{
|
||||||
|
if (infoExt.Equals(ext, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return ItemFields.ItemType.Extra;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 判断是不是字幕
|
||||||
|
foreach (var ext in config.TypeMatchRules.Subtitle.IfFileExtensionIs)
|
||||||
|
{
|
||||||
|
if (infoExt.Equals(ext, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return ItemFields.ItemType.Subtitle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 判断是不是剧集
|
||||||
|
foreach (var ext in config.TypeMatchRules.Episode.IfFileExtensionIs)
|
||||||
|
{
|
||||||
|
if (infoExt.Equals(ext, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return ItemFields.ItemType.Episode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 啥都不是,不知道
|
||||||
|
return ItemFields.ItemType.Unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryParseType(TreeNode node, out string type, out MatchInfo matchInfo)
|
||||||
|
{
|
||||||
|
matchInfo = null;
|
||||||
|
type = null;
|
||||||
|
var typeEnum = ParseItemType(node);
|
||||||
|
if (typeEnum == ItemFields.ItemType.Unknown) return false;
|
||||||
|
type = typeEnum.ToString();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private delegate bool FieldParser(Item item, out string result);
|
private bool TryParseSubtitleLanguage(TreeNode node, out string language, out MatchInfo matchInfo)
|
||||||
|
{
|
||||||
|
language = null;
|
||||||
|
matchInfo = null;
|
||||||
|
if (node.Info.Type() != ItemFields.ItemType.Subtitle)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var name = node.Info.Name();
|
||||||
|
var parts = name.Split(".");
|
||||||
|
if (parts.Length < 3) return false;
|
||||||
|
|
||||||
|
language = parts[^2];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryParseEpisode(TreeNode node, out string episode, out MatchInfo matchInfo)
|
||||||
|
{
|
||||||
|
episode = null;
|
||||||
|
matchInfo = null;
|
||||||
|
if (node.Info.Type() != ItemFields.ItemType.Episode)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var parts = GetParts(node.Info);
|
||||||
|
for (int i = 0; i < parts.Count; i++)
|
||||||
|
{
|
||||||
|
var part = parts[i];
|
||||||
|
var tokens = part.Split("-");
|
||||||
|
foreach (var token in tokens)
|
||||||
|
{
|
||||||
|
var match = Regex.Match(token.Trim(), @"^\d{1,2}$");
|
||||||
|
if (match.Success)
|
||||||
|
{
|
||||||
|
matchInfo = new MatchInfo
|
||||||
|
{
|
||||||
|
content = content,
|
||||||
|
partIndex = i
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// private bool TryParseRawTitle(TreeNode node, out string rawTitle, out MatchInfo matchInfo)
|
||||||
|
// {
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
class MatchInfo
|
||||||
|
{
|
||||||
|
public string content;
|
||||||
|
public int partIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
private delegate bool FieldParser(TreeNode node, out string result, out MatchInfo matchInfo);
|
||||||
|
|
||||||
private bool TryParseField(TreeNode node, FieldParser fieldParser, out string result)
|
private bool TryParseField(TreeNode node, FieldParser fieldParser, out string result)
|
||||||
{
|
{
|
||||||
result = null;
|
result = null;
|
||||||
if (node.Info == null) return false;
|
if (node.Info == null) return false;
|
||||||
if (!fieldParser(node.Info, out var fieldValue)) return false;
|
|
||||||
|
if (!fieldParser(node, out var fieldValue, out _)) return false;
|
||||||
var parsed = new List<string>();
|
var parsed = new List<string>();
|
||||||
foreach (var child in node.Children)
|
foreach (var child in node.Children)
|
||||||
{
|
{
|
||||||
if (fieldParser(child.Info, out var childFieldValue))
|
if (fieldParser(child, out var childFieldValue, out _))
|
||||||
{
|
{
|
||||||
parsed.Add(childFieldValue);
|
parsed.Add(childFieldValue);
|
||||||
}
|
}
|
||||||
@ -162,6 +348,7 @@ public class RawParser : ItemParser
|
|||||||
while (queue.Count > 0)
|
while (queue.Count > 0)
|
||||||
{
|
{
|
||||||
var current = queue.Dequeue();
|
var current = queue.Dequeue();
|
||||||
|
if(current.Info.Info.ContainsKey(fieldName)) continue;
|
||||||
if (TryParseField(current, fieldParser, out var fieldValue))
|
if (TryParseField(current, fieldParser, out var fieldValue))
|
||||||
{
|
{
|
||||||
current.Info.Info.TryAdd(fieldName, fieldValue);
|
current.Info.Info.TryAdd(fieldName, fieldValue);
|
||||||
@ -178,8 +365,10 @@ public class RawParser : ItemParser
|
|||||||
|
|
||||||
public async Task Parse(TreeNode node)
|
public async Task Parse(TreeNode node)
|
||||||
{
|
{
|
||||||
DoParse(node, TryParseRawTitle, ItemFields.Key_RawTitle);
|
|
||||||
DoParse(node, TryParseSeason, ItemFields.Key_Season);
|
DoParse(node, TryParseSeason, ItemFields.Key_Season);
|
||||||
DoParse(node, TryParseGroup, ItemFields.Key_Group);
|
DoParse(node, TryParseGroup, ItemFields.Key_Group);
|
||||||
|
DoParse(node, TryParseType, ItemFields.Key_Type);
|
||||||
|
DoParse(node, TryParseSubtitleLanguage, ItemFields.Key_SubtitleLanguage);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -14,6 +14,8 @@ namespace Learn.Parsers;
|
|||||||
|
|
||||||
public class TMDBParser(Configs configs) : ItemParser
|
public class TMDBParser(Configs configs) : ItemParser
|
||||||
{
|
{
|
||||||
|
private TMDBParserConfig config => configs.Get<TMDBParserConfig>();
|
||||||
|
|
||||||
private readonly Dictionary<string, SearchTv> _cache = new();
|
private readonly Dictionary<string, SearchTv> _cache = new();
|
||||||
|
|
||||||
private TMDbClient _client;
|
private TMDbClient _client;
|
||||||
@ -37,8 +39,8 @@ public class TMDBParser(Configs configs) : ItemParser
|
|||||||
{
|
{
|
||||||
if (_client != null) return _client;
|
if (_client != null) return _client;
|
||||||
|
|
||||||
var apiKey = configs.Get<TMDBConfig>().ApiKey;
|
var apiKey = config.ApiKey;
|
||||||
var proxy = configs.Get<ProxyConfig>().HttpProxy;
|
var proxy = config.HttpProxy;
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(proxy))
|
if (string.IsNullOrEmpty(proxy))
|
||||||
{
|
{
|
||||||
|
|||||||
@ -17,8 +17,8 @@ config/icon="res://icon.svg"
|
|||||||
|
|
||||||
[display]
|
[display]
|
||||||
|
|
||||||
window/size/viewport_width=1920
|
window/size/viewport_width=1600
|
||||||
window/size/viewport_height=1080
|
window/size/viewport_height=900
|
||||||
|
|
||||||
[dotnet]
|
[dotnet]
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user