提交
This commit is contained in:
parent
3d58c7d497
commit
3525df1e95
@ -38,7 +38,7 @@ public partial class MainTreePanel : Tree
|
||||
SetColumnTitlesVisible(true);
|
||||
|
||||
SetColumnTitle(0, "文件名");
|
||||
SetColumnCustomMinimumWidth(0, 500);
|
||||
SetColumnCustomMinimumWidth(0, 800);
|
||||
SetColumnExpand(0, true);
|
||||
|
||||
MultiSelected += OnMultiSelected;
|
||||
|
||||
@ -5,5 +5,5 @@ namespace Learn.Config;
|
||||
[ConfigItem("APP")]
|
||||
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">
|
||||
<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_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>
|
||||
|
||||
9
Main.cs
9
Main.cs
@ -21,6 +21,7 @@ public partial class Main : Node
|
||||
[Export] private Button _saveButton;
|
||||
[Export] private Button _resetButton;
|
||||
[Export] private Button _loadButton;
|
||||
[Export] private Button _reloadConfigButton;
|
||||
|
||||
[ExportGroup("编辑面板")]
|
||||
[Export] private NodeInfoEditPanel _nodeInfoEditPanel;
|
||||
@ -70,6 +71,7 @@ public partial class Main : Node
|
||||
_saveButton.Pressed += DoSave;
|
||||
_resetButton.Pressed += DoReset;
|
||||
_loadButton.Pressed += LoadData;
|
||||
_reloadConfigButton.Pressed += ReloadConfig;
|
||||
_doParseButton.Text = "开始解析";
|
||||
|
||||
_configs = new Configs();
|
||||
@ -82,6 +84,11 @@ public partial class Main : Node
|
||||
_refreshPanels = true;
|
||||
}
|
||||
|
||||
private void ReloadConfig()
|
||||
{
|
||||
_configs.Load(true);
|
||||
}
|
||||
|
||||
private void LoadData()
|
||||
{
|
||||
if (File.Exists(DataPath))
|
||||
@ -146,7 +153,7 @@ public partial class Main : Node
|
||||
var originName = _doParseButton.Text;
|
||||
_doParseButton.Disabled = true;
|
||||
|
||||
ItemParser parser = new RawParser();
|
||||
ItemParser parser = new RawParser(_configs);
|
||||
|
||||
if (_mainTreePanel.Query(root, out var node))
|
||||
{
|
||||
|
||||
@ -17,7 +17,7 @@ grow_vertical = 2
|
||||
[node name="FileDirDialog" type="FileDialog" parent="."]
|
||||
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")
|
||||
_dirSelector = NodePath("../FileDirDialog")
|
||||
_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")
|
||||
_resetButton = NodePath("../MarginContainer/HSplitContainer/ScrollContainer/VBoxContainer2/FoldableContainer4/VBoxContainer2/TabContainer/保存操作/Reset")
|
||||
_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")
|
||||
_addKeyButton = NodePath("../MarginContainer/HSplitContainer/ScrollContainer/VBoxContainer2/FoldableContainer/VBoxContainer/HBoxContainer/AddKey")
|
||||
_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"]
|
||||
custom_minimum_size = Vector2(0, 200)
|
||||
layout_mode = 2
|
||||
current_tab = 0
|
||||
current_tab = 1
|
||||
|
||||
[node name="文件夹操作" type="VBoxContainer" parent="MarginContainer/HSplitContainer/ScrollContainer/VBoxContainer2/FoldableContainer4/VBoxContainer2/TabContainer"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
metadata/_tab_index = 0
|
||||
|
||||
@ -102,7 +104,6 @@ size_flags_vertical = 4
|
||||
text = "开始解析"
|
||||
|
||||
[node name="保存操作" type="VBoxContainer" parent="MarginContainer/HSplitContainer/ScrollContainer/VBoxContainer2/FoldableContainer4/VBoxContainer2/TabContainer"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
metadata/_tab_index = 1
|
||||
|
||||
@ -118,6 +119,10 @@ text = "加载树表"
|
||||
layout_mode = 2
|
||||
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"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Learn.Models;
|
||||
|
||||
@ -28,47 +29,45 @@ public class TreeNode(Item item)
|
||||
|
||||
public bool TryGetValue(string key, out string value, out bool isInherited)
|
||||
{
|
||||
value = null;
|
||||
isInherited = false;
|
||||
var curr = this;
|
||||
|
||||
|
||||
bool valueExists = false;
|
||||
while (curr.Info != null)
|
||||
{
|
||||
if (curr.Info.Info.TryGetValue(key, out value))
|
||||
if (curr.Info.Info.TryGetValue(key, out var currValue))
|
||||
{
|
||||
isInherited = curr != this;
|
||||
return true;
|
||||
value = currValue;
|
||||
valueExists = true;
|
||||
isInherited = (curr != this);
|
||||
}
|
||||
curr = curr.Parent;
|
||||
}
|
||||
|
||||
value = null;
|
||||
isInherited = false;
|
||||
return false;
|
||||
|
||||
return valueExists;
|
||||
}
|
||||
|
||||
public IEnumerable<KeyValueInfo> GetKeyValueInfos()
|
||||
{
|
||||
var result = new List<KeyValueInfo>();
|
||||
var keys = new HashSet<string>();
|
||||
var dict = new Dictionary<string, KeyValueInfo>();
|
||||
var curr = this;
|
||||
|
||||
while (curr.Info != null)
|
||||
{
|
||||
foreach (var kv in curr.Info.Info)
|
||||
{
|
||||
if(!keys.Add(kv.Key)) continue;
|
||||
var keyValueInfo = new KeyValueInfo();
|
||||
|
||||
if (curr != this)
|
||||
var keyValueInfo = new KeyValueInfo
|
||||
{
|
||||
keyValueInfo.IsInherited = true;
|
||||
}
|
||||
|
||||
keyValueInfo.Key = kv.Key;
|
||||
keyValueInfo.Value = kv.Value;
|
||||
result.Add(keyValueInfo);
|
||||
IsInherited = curr != this,
|
||||
Key = kv.Key,
|
||||
Value = kv.Value
|
||||
};
|
||||
|
||||
dict[kv.Key] = keyValueInfo;
|
||||
}
|
||||
curr = curr.Parent;
|
||||
}
|
||||
return result;
|
||||
return dict.Values.ToList();
|
||||
}
|
||||
}
|
||||
@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Learn.Models;
|
||||
@ -6,6 +7,14 @@ namespace Learn.Parsers;
|
||||
|
||||
public static class ItemFields
|
||||
{
|
||||
public enum ItemType
|
||||
{
|
||||
Unknown,
|
||||
Extra,
|
||||
Subtitle,
|
||||
Episode
|
||||
}
|
||||
|
||||
#region 基础信息
|
||||
|
||||
public static string MainKey_Path => "Path";
|
||||
@ -25,36 +34,27 @@ public static class ItemFields
|
||||
|
||||
#endregion
|
||||
|
||||
#region 普通字段
|
||||
|
||||
public static string Key_Group => "Group";
|
||||
public static string Key_Title => "Title";
|
||||
public static string Key_RawTitle => "RawTitle";
|
||||
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 ItemType Type(this Item item)
|
||||
{
|
||||
if (item.Info.TryGetValue(Key_Type, out var typeName))
|
||||
{
|
||||
if(Enum.TryParse(typeName, out ItemType type))
|
||||
{
|
||||
return type;
|
||||
}
|
||||
}
|
||||
return ItemType.Unknown;
|
||||
}
|
||||
|
||||
public static void SetGroupIfNotExist(this Item item, string group)
|
||||
{
|
||||
item.Info.TryAdd(Key_Group, group);
|
||||
}
|
||||
|
||||
public static void SetTitleIfNotExist(this Item item, string title)
|
||||
{
|
||||
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();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@ -12,7 +12,6 @@ namespace Learn.Parsers;
|
||||
/// "集数"
|
||||
/// "类型":"额外内容", "字幕", "剧集"
|
||||
/// "字幕语言"
|
||||
/// "年份"
|
||||
/// </summary>
|
||||
public interface ItemParser
|
||||
{
|
||||
|
||||
@ -1,135 +1,321 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Learn.Config;
|
||||
using Learn.Models;
|
||||
using Learn.Utils;
|
||||
|
||||
namespace Learn.Parsers;
|
||||
|
||||
public class RawParser : ItemParser
|
||||
public class RawParser(Configs configs) : ItemParser
|
||||
{
|
||||
private string ParseSeasonFromCN(string season)
|
||||
{
|
||||
if (int.TryParse(season, out _)) return season;
|
||||
|
||||
switch (season)
|
||||
{
|
||||
case "零": return "0";
|
||||
case "一": return "1";
|
||||
case "二": return "2";
|
||||
case "三": return "3";
|
||||
case "四": return "4";
|
||||
case "五": return "5";
|
||||
case "六": return "6";
|
||||
case "七": return "7";
|
||||
case "八": return "8";
|
||||
case "九": return "9";
|
||||
}
|
||||
return season;
|
||||
}
|
||||
|
||||
|
||||
private (string, string) SplitTitleAndSeason(string rawTitle)
|
||||
{
|
||||
var match1 = Regex.Match(rawTitle, @"第(.+)季");
|
||||
if (match1.Success)
|
||||
{
|
||||
var seasonStr = match1.Groups[0].Value.Trim();
|
||||
var season = ParseSeasonFromCN(match1.Groups[1].Value.Trim());
|
||||
var title = rawTitle.Replace(seasonStr, "").Trim();
|
||||
return (title, season);
|
||||
}
|
||||
|
||||
var match2 = Regex.Match(rawTitle, @"[Ss]eason *(\d+)");
|
||||
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);
|
||||
}
|
||||
private RawParserConfig config => configs.Get<RawParserConfig>();
|
||||
|
||||
return (rawTitle, null);
|
||||
private List<string> FilterParts(List<string> parts)
|
||||
{
|
||||
var result = parts.ToList();
|
||||
foreach (var regex in config.TokenFilterRules.Regexes)
|
||||
{
|
||||
result.RemoveAll(part => Regex.Match(part.Trim(), regex).Success);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void SimplifyMatches(List<string> matches)
|
||||
private List<string> GetParts(Item item)
|
||||
{
|
||||
matches.RemoveAll(match => string.IsNullOrEmpty(match.Trim()));
|
||||
matches.RemoveAll(match => Regex.Match(match.Trim(), @"\d+[Pp]$").Success);
|
||||
var matches = Regex.Matches(item.Name(), config.SplitRegex).Select(match => match.Value)
|
||||
.Select(match => match.Trim())
|
||||
.Where(match => !string.IsNullOrEmpty(match))
|
||||
.ToList();
|
||||
return FilterParts(matches);
|
||||
}
|
||||
|
||||
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)
|
||||
private bool TryNormalizeSeason(string seasonPart, out string season)
|
||||
{
|
||||
season = null;
|
||||
var name = item.Name();
|
||||
var matches = GetParts(name);
|
||||
|
||||
if (matches.Count == 0) return false;
|
||||
|
||||
if (matches.Count == 1)
|
||||
if (int.TryParse(seasonPart, out var seasonInt))
|
||||
{
|
||||
(_, season) = SplitTitleAndSeason(matches[0]);
|
||||
if (!string.IsNullOrEmpty(season)) return true;
|
||||
season = seasonInt.ToString();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
switch (seasonPart)
|
||||
{
|
||||
(_, season) = SplitTitleAndSeason(matches[1]);
|
||||
if (!string.IsNullOrEmpty(season)) return true;
|
||||
case "零":
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
var name = item.Name();
|
||||
var matches = GetParts(name);
|
||||
matchInfo = null;
|
||||
|
||||
var parts = GetParts(node.Info);
|
||||
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;
|
||||
}
|
||||
|
||||
if (matches.Count <= 1) return false;
|
||||
group = matches[0];
|
||||
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;
|
||||
}
|
||||
|
||||
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 delegate bool FieldParser(Item item, out string result);
|
||||
//
|
||||
// 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)
|
||||
{
|
||||
result = null;
|
||||
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>();
|
||||
foreach (var child in node.Children)
|
||||
{
|
||||
if (fieldParser(child.Info, out var childFieldValue))
|
||||
if (fieldParser(child, out var childFieldValue, out _))
|
||||
{
|
||||
parsed.Add(childFieldValue);
|
||||
}
|
||||
@ -162,6 +348,7 @@ public class RawParser : ItemParser
|
||||
while (queue.Count > 0)
|
||||
{
|
||||
var current = queue.Dequeue();
|
||||
if(current.Info.Info.ContainsKey(fieldName)) continue;
|
||||
if (TryParseField(current, fieldParser, out var fieldValue))
|
||||
{
|
||||
current.Info.Info.TryAdd(fieldName, fieldValue);
|
||||
@ -178,8 +365,10 @@ public class RawParser : ItemParser
|
||||
|
||||
public async Task Parse(TreeNode node)
|
||||
{
|
||||
DoParse(node, TryParseRawTitle, ItemFields.Key_RawTitle);
|
||||
DoParse(node, TryParseSeason, ItemFields.Key_Season);
|
||||
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
|
||||
{
|
||||
private TMDBParserConfig config => configs.Get<TMDBParserConfig>();
|
||||
|
||||
private readonly Dictionary<string, SearchTv> _cache = new();
|
||||
|
||||
private TMDbClient _client;
|
||||
@ -37,8 +39,8 @@ public class TMDBParser(Configs configs) : ItemParser
|
||||
{
|
||||
if (_client != null) return _client;
|
||||
|
||||
var apiKey = configs.Get<TMDBConfig>().ApiKey;
|
||||
var proxy = configs.Get<ProxyConfig>().HttpProxy;
|
||||
var apiKey = config.ApiKey;
|
||||
var proxy = config.HttpProxy;
|
||||
|
||||
if (string.IsNullOrEmpty(proxy))
|
||||
{
|
||||
@ -48,7 +50,7 @@ public class TMDBParser(Configs configs) : ItemParser
|
||||
{
|
||||
_client = new TMDbClient(apiKey , proxy: new WebProxy(proxy));
|
||||
}
|
||||
|
||||
|
||||
return _client;
|
||||
}
|
||||
|
||||
|
||||
@ -17,8 +17,8 @@ config/icon="res://icon.svg"
|
||||
|
||||
[display]
|
||||
|
||||
window/size/viewport_width=1920
|
||||
window/size/viewport_height=1080
|
||||
window/size/viewport_width=1600
|
||||
window/size/viewport_height=900
|
||||
|
||||
[dotnet]
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user