From 9036f967fc64d7ae7c76800ea12a06b4748d917c Mon Sep 17 00:00:00 2001 From: Tyrone CT Date: Fri, 10 Oct 2025 17:54:53 +0800 Subject: [PATCH] =?UTF-8?q?OEM=E5=88=9D=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FATrace.Com/Class1.cs | 7 - FATrace.Com/ConfigHelper.cs | 9 + FATrace.Com/NVRCom.cs | 24 + FATrace.HKNetLib/Common/NVRCamera.cs | 30 + FATrace.HKNetLib/FATrace.HKNetLib.csproj | 4 + FATrace.HKNetLib/Wrapper/HkCamera.cs | 262 +++++++- FATrace.Model/OEMRawUse.cs | 69 ++ FATrace.Model/VideoAction.cs | 6 - FATrace.OEMApp/App.config | 7 +- FATrace.OEMApp/FATrace.OEMApp.csproj | 4 + FATrace.OEMApp/FSqlContext.cs | 7 +- FATrace.OEMApp/MainApp.Designer.cs | 625 +++++++++++++++++- FATrace.OEMApp/MainApp.cs | 387 +++++++++-- FATrace.OEMApp/MainApp.resx | 222 +++---- .../Model/Jellyfin/JellyfinModels.cs | 65 ++ FATrace.OEMApp/Services/JellyfinClient.cs | 245 +++++++ 16 files changed, 1736 insertions(+), 237 deletions(-) delete mode 100644 FATrace.Com/Class1.cs create mode 100644 FATrace.Com/NVRCom.cs create mode 100644 FATrace.HKNetLib/Common/NVRCamera.cs create mode 100644 FATrace.Model/OEMRawUse.cs create mode 100644 FATrace.OEMApp/Model/Jellyfin/JellyfinModels.cs create mode 100644 FATrace.OEMApp/Services/JellyfinClient.cs diff --git a/FATrace.Com/Class1.cs b/FATrace.Com/Class1.cs deleted file mode 100644 index dc6b742..0000000 --- a/FATrace.Com/Class1.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace FATrace.Com -{ - public class Class1 - { - - } -} diff --git a/FATrace.Com/ConfigHelper.cs b/FATrace.Com/ConfigHelper.cs index 6372b2d..fb1c0d7 100644 --- a/FATrace.Com/ConfigHelper.cs +++ b/FATrace.Com/ConfigHelper.cs @@ -45,6 +45,15 @@ namespace FATrace.Com } } + /// + /// 根据Key取Value值 + /// + /// + public static string GetValue(string key) + { + return ConfigurationManager.AppSettings[key].ToString().Trim(); + } + /// /// 根据 key 读取字符串配置,不存在时返回默认值。 /// diff --git a/FATrace.Com/NVRCom.cs b/FATrace.Com/NVRCom.cs new file mode 100644 index 0000000..325aacd --- /dev/null +++ b/FATrace.Com/NVRCom.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FATrace.Com +{ + /// + /// NVR 公共的类 + /// + public class NVRCom + { + /// + /// 获取视频名称 + /// + /// + /// + public static string GetVideoName(string Path,string Code) + { + return $"{Path}\\{DateTime.Now.ToString("yyyy-MM-dd HHmmss")} {Code}.mp4"; + } + } +} diff --git a/FATrace.HKNetLib/Common/NVRCamera.cs b/FATrace.HKNetLib/Common/NVRCamera.cs new file mode 100644 index 0000000..e775f5b --- /dev/null +++ b/FATrace.HKNetLib/Common/NVRCamera.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FATrace.HKNetLib.Common +{ + /// + /// NVR Camera 模型 + /// + public class NVRCamera + { + /// + /// 通道号 + /// + public int ChNo { get; set; } + + /// + /// 是否在线 + /// + public byte IsOnline { get; set; } + + /// + /// IP设备ID + /// + public ushort IPID { get; set; } + + } +} diff --git a/FATrace.HKNetLib/FATrace.HKNetLib.csproj b/FATrace.HKNetLib/FATrace.HKNetLib.csproj index 8826f6b..7acbb83 100644 --- a/FATrace.HKNetLib/FATrace.HKNetLib.csproj +++ b/FATrace.HKNetLib/FATrace.HKNetLib.csproj @@ -192,4 +192,8 @@ + + + + diff --git a/FATrace.HKNetLib/Wrapper/HkCamera.cs b/FATrace.HKNetLib/Wrapper/HkCamera.cs index 76b3c57..a3c9215 100644 --- a/FATrace.HKNetLib/Wrapper/HkCamera.cs +++ b/FATrace.HKNetLib/Wrapper/HkCamera.cs @@ -1,13 +1,11 @@ -using FATrace.HKNetLib.Common; +using FATrace.HKNetLib.Common; using FATrace.HKNetLib.Hardware; -using System; -using System.Collections.Generic; -using System.Linq; +using FATrace.Model; using System.Reflection; using System.Runtime.InteropServices; -using System.Text; -using System.Threading.Tasks; using static FATrace.HKNetLib.Hardware.CHCNetSDK; +using System.Threading; +using System.Threading.Tasks; namespace FATrace.HKNetLib.Wrapper { @@ -93,8 +91,73 @@ namespace FATrace.HKNetLib.Wrapper } } + #region NVR信息 + + /// + /// NVR IP + /// + public string NVR_IP { get; set; } + + /// + /// NVR Port + /// + public ushort NVR_Port { get; set; } + + /// + /// NVR UserName + /// + public string NVR_UserName { get; set; } + + /// + /// NVR Pw + /// + public string NVR_Pw { get; set; } + + /// + /// NVR VideoSavePath 保存的地址信息 + /// + public string NVRVideoSavePath { get; set; } + + #endregion + + + #region 公共信息 + + /// + /// 事件发布的消息 + /// + public event EventHandler VideoActionEventHandler; + + /// + /// 下发视频进度过程的Handler + /// + public event EventHandler NVRLoadVideoProcessEventHandler; + + /// + /// 下发视频完成的Handler + /// + public event EventHandler NVRLoadVideoCompleteEventHandler; + + /// + /// 当前NVR登录信息 + /// + public CameraLoginInfo NVRLoginInfo { get; set; } = new CameraLoginInfo(); + + /// + /// NVR登录状态 + /// + public bool NVRLoginState { get; set; } + + #endregion + + #region NVR开发 + /// + /// NVRCamera 集合数据 + /// + public List ListNVRCamera { get; set; } = new List(); + /// /// NVR设备信息 DVR_DEVICEINFO_V30 /// @@ -105,7 +168,14 @@ namespace FATrace.HKNetLib.Wrapper /// public CHCNetSDK.NET_DVR_IPPARACFG_V40 DVR_IPPARACFG_V40 { get; set; } + /// + /// 模拟通道个数 + /// private uint dwAChanTotalNum { get; set; } = 0; + + /// + /// 数字通道个数 + /// private uint dwDChanTotalNum { get; set; } = 0; public NET_DVR_GET_STREAM_UNION DVR_GET_STREAM_UNION { get; set; } @@ -153,6 +223,7 @@ namespace FATrace.HKNetLib.Wrapper { LastMsgErr = GetLastError(); //MessageBox.Show(str1); + NVRLoginState = false; return false; } else @@ -161,6 +232,12 @@ namespace FATrace.HKNetLib.Wrapper //MessageBox.Show("Login Success!"); //btnLogin.Text = "Logout"; + NVRLoginInfo.IP = DVRIPAddress; + NVRLoginInfo.Port = (ushort)DVRPortNumber; + NVRLoginInfo.Password = DVRPassword; + NVRLoginInfo.UserName = DVRUserName; + NVRLoginState = true; + dwAChanTotalNum = (uint)DVRDeviceInfoV30.byChanNum; dwDChanTotalNum = (uint)DVRDeviceInfoV30.byIPChanNum + 256 * (uint)DVRDeviceInfoV30.byHighDChanNum; @@ -236,6 +313,14 @@ namespace FATrace.HKNetLib.Wrapper Marshal.StructureToPtr(DVR_GET_STREAM_UNION, ptrChanInfo, false); DVR_IPCHANINFO = (CHCNetSDK.NET_DVR_IPCHANINFO)Marshal.PtrToStructure(ptrChanInfo, typeof(CHCNetSDK.NET_DVR_IPCHANINFO)); + ListNVRCamera.Add(new NVRCamera() + { + //ChNo = DVR_IPCHANINFO.byChannel, + ChNo = (i + 1), + IPID = DVR_IPCHANINFO.byIPID, + IsOnline = DVR_IPCHANINFO.byEnable + }); + //列出IP通道 //ListIPChannel(i + 1, DVR_IPCHANINFO.byEnable, DVR_IPCHANINFO.byIPID); Marshal.FreeHGlobal(ptrChanInfo); @@ -250,20 +335,19 @@ namespace FATrace.HKNetLib.Wrapper } /// - /// 根据时间获取DVR统计信息 + /// 根据时间获取DVR 视频数据信息 /// public (bool Result, string Msg) Sdk_NET_DVR_GetFileByTime_V40(DateTime startTime, DateTime endTime, string SaveVideFilePath) { if (m_lDownHandle >= 0) { - //MessageBox.Show("Downloading, please stop firstly!");//正在下载,请先停止下载 return (Result: false, Msg: "正在下载,请先停止下载"); } NET_DVR_PLAYCOND struDownPara = new NET_DVR_PLAYCOND(); - struDownPara.dwChannel = (uint)iChannelNum[(int)iSelIndex]; //通道号 Channel number + struDownPara.dwChannel = (uint)iChannelNum[(int)iSelIndex]; - //设置下载的开始时间 Set the starting time + // 设置下载的开始时间 struDownPara.struStartTime.dwYear = startTime.Year; struDownPara.struStartTime.dwMonth = startTime.Month; struDownPara.struStartTime.dwDay = startTime.Day; @@ -271,7 +355,7 @@ namespace FATrace.HKNetLib.Wrapper struDownPara.struStartTime.dwMinute = startTime.Minute; struDownPara.struStartTime.dwSecond = startTime.Second; - //设置下载的结束时间 Set the stopping time + // 设置下载的结束时间 struDownPara.struStopTime.dwYear = endTime.Year; struDownPara.struStopTime.dwMonth = endTime.Month; struDownPara.struStopTime.dwDay = endTime.Day; @@ -279,10 +363,7 @@ namespace FATrace.HKNetLib.Wrapper struDownPara.struStopTime.dwMinute = endTime.Minute; struDownPara.struStopTime.dwSecond = endTime.Second; - //string sVideoFileName; //录像文件保存路径和文件名 the path and file name to save - //sVideoFileName = "D:\\Downtest_Channel" + struDownPara.dwChannel + ".mp4"; - - //按时间下载 Download by time + // 按时间下载 m_lDownHandle = CHCNetSDK.NET_DVR_GetFileByTime_V40(m_lUserID, SaveVideFilePath, ref struDownPara); if (m_lDownHandle < 0) { @@ -291,30 +372,153 @@ namespace FATrace.HKNetLib.Wrapper } uint iOutValue = 0; - //该接口指定了当前要下载的录像文件,调用成功后,还需要调用NET_DVR_PlayBackControl_V40接口的开始播放控制命令(NET_DVR_PLAYSTART)才能实现下载。 - //V5.0.3.2或以后版本,通过该接口保存录像,保存的录像文件数据超过文件最大限制字节数(默认为1024MB),SDK会自动切片,即新建文件进行保存,文件名命名规则为“在接口传入的文件名基础上增加数字标识(例如:*_1.mp4、*_2.mp4)”。 - //可以调用NET_DVR_GetSDKLocalCfg、NET_DVR_SetSDKLocalCfg(配置类型:NET_DVR_LOCAL_CFG_TYPE_GENERAL)获取和设置切片模式和文件最大限制字节数。 + // 该接口指定了当前要下载的录像文件,调用成功后,还需要调用 NET_DVR_PlayBackControl_V40 接口的开始播放控制命令(NET_DVR_PLAYSTART)才能实现下载。 if (!CHCNetSDK.NET_DVR_PlayBackControl_V40(m_lDownHandle, CHCNetSDK.NET_DVR_PLAYSTART, IntPtr.Zero, 0, IntPtr.Zero, ref iOutValue)) { LastMsgErr = GetLastError(); + try { CHCNetSDK.NET_DVR_StopGetFile(m_lDownHandle); } catch { } + m_lDownHandle = -1; return (Result: false, Msg: $"[NET_DVR_PlayBackControl_V40] 执行下载控制失败:{LastMsgErr}"); } - return (Result: true, Msg: "下载成功!"); - } - - /// - /// 获取下载的进度 - /// - /// - /// - public int Sdk_NET_DVR_GetDownloadPos(Int32 downHandle) - { - return CHCNetSDK.NET_DVR_GetDownloadPos(downHandle); + StartDownloadProgressMonitor(); + return (Result: true, Msg: "下载OK!"); } #endregion + + #region 跟踪下载 + + private CancellationTokenSource _downloadCts; + private const int _downloadPollIntervalMs = 500; + + /// + /// 启动下载进度轮询,定期发布 NVRLoadVideoProcessEventHandler 事件 + /// + public void StartDownloadProgressMonitor() + { + // 先确保没有旧的轮询任务 + StopDownloadProgressMonitor(); + + _downloadCts = new CancellationTokenSource(); + var token = _downloadCts.Token; + + Task.Run(async () => + { + try + { + while (!token.IsCancellationRequested && m_lDownHandle >= 0) + { + int progress = -1; + try + { + progress = CHCNetSDK.NET_DVR_GetDownloadPos(m_lDownHandle); + } + catch + { + progress = -1; + } + + if (progress >= 0) + { + short percent = (short)Math.Min(100, progress); + try + { + NVRLoadVideoProcessEventHandler?.Invoke(this, percent); + } + catch + { + // 事件订阅者异常不应中断轮询 + } + + if (progress >= 100) + { + // 完成:停止下载,退出轮询 + Sdk_NET_DVR_StopGetFile(); + + NVRLoadVideoCompleteEventHandler?.Invoke(this, "下载完成"); + break; + } + } + else + { + // 发生错误,尝试停止下载并退出 + Sdk_NET_DVR_StopGetFile(); + break; + } + + try { await Task.Delay(_downloadPollIntervalMs, token); } catch { /* ignore */ } + } + } + catch + { + // 轮询线程异常吞掉,避免影响上层逻辑 + } + }, token); + } + + + /// + /// 停止下载进度轮询 + /// + private void StopDownloadProgressMonitor() + { + try + { + if (_downloadCts != null) + { + if (!_downloadCts.IsCancellationRequested) + { + _downloadCts.Cancel(); + } + _downloadCts.Dispose(); + _downloadCts = null; + } + } + catch + { + // 忽略取消中的异常 + } + } + + + /// + /// 停止当前下载并清理资源 + /// + /// + public (bool Result, string Msg) Sdk_NET_DVR_StopGetFile() + { + try + { + // 停止轮询 + StopDownloadProgressMonitor(); + + if (m_lDownHandle >= 0) + { + bool stopOk = CHCNetSDK.NET_DVR_StopGetFile(m_lDownHandle); + m_lDownHandle = -1; + if (!stopOk) + { + LastMsgErr = GetLastError(); + return (false, $"[NET_DVR_StopGetFile] 停止下载失败:{LastMsgErr}"); + } + } + return (true, "停止下载成功"); + } + catch (Exception) + { + return (false, "停止下载异常"); + } + } + + + + #endregion + + + + public bool IsOnline() { try diff --git a/FATrace.Model/OEMRawUse.cs b/FATrace.Model/OEMRawUse.cs new file mode 100644 index 0000000..3fa0e92 --- /dev/null +++ b/FATrace.Model/OEMRawUse.cs @@ -0,0 +1,69 @@ +using FreeSql.DataAnnotations; + +namespace FATrace.Model +{ + /// + /// OEM原料使用信息 + /// + [Table(Name = "OEMRawUse")] + public class OEMRawUse + { + /// + /// 主键 + /// + [Column(IsPrimary = true, IsIdentity = true)] + public long Id { get; set; } + + /// + /// 内袋二维码 + /// + [Column(Name = "InBagCode", IsNullable = false, StringLength = 100)] + public string? InBagCode { get; set; } + + /// + /// 原料名称 + /// + [Column(Name = "InBagCode", IsNullable = false, StringLength = 100)] + public string? RawName{ get; set; } + + /// + /// 视频链接 + /// + [Column(Name = "VideoUrl", IsNullable = false, StringLength = 100)] + public string? VideoUrl{ get; set; } + + /// + /// 用户信息 + /// + [Column(Name = "User", IsNullable = false, StringLength = 100)] + public string? User { get; set; } + + /// + /// URl状态 + /// + [Column(Name = "UrlState", IsNullable = false)] + public bool UrlState { get; set; } + + ///// + ///// 视频信息 + ///// + //[Column(Name = "User", IsNullable = false, StringLength = 100)] + //public VideoAction? VideoAction { get; set; } + + /// + /// 创建时间 + /// + [Column(ServerTime = DateTimeKind.Local, CanUpdate = true)] + public DateTime CreateTime { get; set; } + + /// + /// ///////////////////////////////////////////导航属性 LIN 一对一/////////////////////////////////////////////////////// + /// + public long VideoActionId { get; set; } // 外键字段,必要 + /// + /// 视频信息 + /// + public VideoAction? VideoAction { get; set; } + + } +} diff --git a/FATrace.Model/VideoAction.cs b/FATrace.Model/VideoAction.cs index 4ec2560..115514a 100644 --- a/FATrace.Model/VideoAction.cs +++ b/FATrace.Model/VideoAction.cs @@ -26,12 +26,6 @@ namespace FATrace.Model [Column(Name = "User", IsNullable = false, StringLength = 100)] public string? User { get; set; } - ///// - ///// 分类信息-CAN/LIN - ///// - //[Column(Name = "CANLINInfo", IsNullable = false, MapType = typeof(string))] - //public CANLIN CANLINInfo { get; set; } - /// /// 视频文件路径 /// diff --git a/FATrace.OEMApp/App.config b/FATrace.OEMApp/App.config index 63e7c72..794f590 100644 --- a/FATrace.OEMApp/App.config +++ b/FATrace.OEMApp/App.config @@ -3,11 +3,16 @@ - + + + + + + diff --git a/FATrace.OEMApp/FATrace.OEMApp.csproj b/FATrace.OEMApp/FATrace.OEMApp.csproj index cf6fb4b..c4912f5 100644 --- a/FATrace.OEMApp/FATrace.OEMApp.csproj +++ b/FATrace.OEMApp/FATrace.OEMApp.csproj @@ -415,9 +415,13 @@ + + + + diff --git a/FATrace.OEMApp/FSqlContext.cs b/FATrace.OEMApp/FSqlContext.cs index d4fd078..14e556a 100644 --- a/FATrace.OEMApp/FSqlContext.cs +++ b/FATrace.OEMApp/FSqlContext.cs @@ -1,9 +1,4 @@ using FATrace.Com; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace FATrace.OEMApp { @@ -16,7 +11,7 @@ namespace FATrace.OEMApp { return new FreeSql.FreeSqlBuilder() .UseConnectionString(FreeSql.DataType.SqlServer, connectionString) - .UseAutoSyncStructure(false) + .UseAutoSyncStructure(true) .Build(); } catch (Exception ex) diff --git a/FATrace.OEMApp/MainApp.Designer.cs b/FATrace.OEMApp/MainApp.Designer.cs index 1d55d72..78f59df 100644 --- a/FATrace.OEMApp/MainApp.Designer.cs +++ b/FATrace.OEMApp/MainApp.Designer.cs @@ -30,20 +30,79 @@ { components = new System.ComponentModel.Container(); System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainApp)); + statusStrip1 = new StatusStrip(); imageList1 = new ImageList(components); materialTabControl1 = new ReaLTaiizor.Controls.MaterialTabControl(); tabPage1 = new TabPage(); + materialCard4 = new ReaLTaiizor.Controls.MaterialCard(); + label18 = new Label(); + txtIOOutputTime = new TextBox(); + label17 = new Label(); + txtIOInputTime = new TextBox(); + label16 = new Label(); + txtIOInBagCode = new TextBox(); + txtRemainBoxCount = new TextBox(); + label15 = new Label(); + txtOutBoxCount = new TextBox(); + label14 = new Label(); + txtInBoxCount = new TextBox(); + label13 = new Label(); + label12 = new Label(); + label9 = new Label(); + txtIORawName = new TextBox(); + label10 = new Label(); + txtIOOutBagCode = new TextBox(); + label11 = new Label(); + materialCard3 = new ReaLTaiizor.Controls.MaterialCard(); + label8 = new Label(); + materialCard2 = new ReaLTaiizor.Controls.MaterialCard(); + btnTestAction = new Button(); + label7 = new Label(); + txtRURawName = new TextBox(); + label6 = new Label(); + txtRUInBagCode = new TextBox(); + label5 = new Label(); tabPage2 = new TabPage(); + btnStopHistoryPlay = new Button(); + dataGridView1 = new DataGridView(); + materialCard1 = new ReaLTaiizor.Controls.MaterialCard(); + label4 = new Label(); + txtSearchCode = new TextBox(); + btnHistoryVideoSearch = new Button(); + label3 = new Label(); + PdtHistorySearchEnd = new ReaLTaiizor.Controls.PoisonDateTime(); + label2 = new Label(); + label1 = new Label(); + PdtHistorySearchStart = new ReaLTaiizor.Controls.PoisonDateTime(); + btnPlayHistoryVideo = new Button(); + videoView1 = new LibVLCSharp.WinForms.VideoView(); tabPage3 = new TabPage(); + metroProgressBar1 = new ReaLTaiizor.Controls.MetroProgressBar(); + DownloadProgressBar = new ProgressBar(); btnStopLoadVideo = new Button(); btnLoadVideo = new Button(); btnNVRLogin = new Button(); - DownTimer = new System.Windows.Forms.Timer(components); - DownloadProgressBar = new ProgressBar(); + DownloadProgressBarMain = new ProgressBar(); materialTabControl1.SuspendLayout(); + tabPage1.SuspendLayout(); + materialCard4.SuspendLayout(); + materialCard3.SuspendLayout(); + materialCard2.SuspendLayout(); + tabPage2.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)dataGridView1).BeginInit(); + materialCard1.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)videoView1).BeginInit(); tabPage3.SuspendLayout(); SuspendLayout(); // + // statusStrip1 + // + statusStrip1.Location = new Point(3, 1005); + statusStrip1.Name = "statusStrip1"; + statusStrip1.Size = new Size(1914, 22); + statusStrip1.TabIndex = 1; + statusStrip1.Text = "statusStrip1"; + // // imageList1 // imageList1.ColorDepth = ColorDepth.Depth32Bit; @@ -76,6 +135,9 @@ // // tabPage1 // + tabPage1.Controls.Add(materialCard4); + tabPage1.Controls.Add(materialCard3); + tabPage1.Controls.Add(materialCard2); tabPage1.Font = new Font("Microsoft YaHei UI", 14.25F, FontStyle.Bold, GraphicsUnit.Point, 134); tabPage1.ImageKey = "set2.png"; tabPage1.Location = new Point(4, 26); @@ -86,8 +148,323 @@ tabPage1.Text = "主界面"; tabPage1.UseVisualStyleBackColor = true; // + // materialCard4 + // + materialCard4.BackColor = Color.FromArgb(255, 255, 255); + materialCard4.Controls.Add(label18); + materialCard4.Controls.Add(txtIOOutputTime); + materialCard4.Controls.Add(label17); + materialCard4.Controls.Add(txtIOInputTime); + materialCard4.Controls.Add(label16); + materialCard4.Controls.Add(txtIOInBagCode); + materialCard4.Controls.Add(txtRemainBoxCount); + materialCard4.Controls.Add(label15); + materialCard4.Controls.Add(txtOutBoxCount); + materialCard4.Controls.Add(label14); + materialCard4.Controls.Add(txtInBoxCount); + materialCard4.Controls.Add(label13); + materialCard4.Controls.Add(label12); + materialCard4.Controls.Add(label9); + materialCard4.Controls.Add(txtIORawName); + materialCard4.Controls.Add(label10); + materialCard4.Controls.Add(txtIOOutBagCode); + materialCard4.Controls.Add(label11); + materialCard4.Depth = 0; + materialCard4.ForeColor = Color.FromArgb(222, 0, 0, 0); + materialCard4.Location = new Point(14, 373); + materialCard4.Margin = new Padding(14); + materialCard4.MouseState = ReaLTaiizor.Helper.MaterialDrawHelper.MaterialMouseState.HOVER; + materialCard4.Name = "materialCard4"; + materialCard4.Padding = new Padding(14); + materialCard4.Size = new Size(864, 535); + materialCard4.TabIndex = 5; + // + // label18 + // + label18.AutoSize = true; + label18.ForeColor = Color.DimGray; + label18.Location = new Point(41, 260); + label18.Name = "label18"; + label18.Size = new Size(88, 26); + label18.TabIndex = 17; + label18.Text = "出库时间"; + // + // txtIOOutputTime + // + txtIOOutputTime.Location = new Point(154, 257); + txtIOOutputTime.Name = "txtIOOutputTime"; + txtIOOutputTime.Size = new Size(446, 32); + txtIOOutputTime.TabIndex = 16; + // + // label17 + // + label17.AutoSize = true; + label17.ForeColor = Color.DimGray; + label17.Location = new Point(41, 211); + label17.Name = "label17"; + label17.Size = new Size(88, 26); + label17.TabIndex = 15; + label17.Text = "入库时间"; + // + // txtIOInputTime + // + txtIOInputTime.Location = new Point(154, 208); + txtIOInputTime.Name = "txtIOInputTime"; + txtIOInputTime.Size = new Size(446, 32); + txtIOInputTime.TabIndex = 14; + // + // label16 + // + label16.AutoSize = true; + label16.ForeColor = Color.DimGray; + label16.Location = new Point(41, 109); + label16.Name = "label16"; + label16.Size = new Size(107, 26); + label16.TabIndex = 13; + label16.Text = "内袋二维码"; + // + // txtIOInBagCode + // + txtIOInBagCode.Location = new Point(154, 106); + txtIOInBagCode.Name = "txtIOInBagCode"; + txtIOInBagCode.Size = new Size(446, 32); + txtIOInBagCode.TabIndex = 12; + // + // txtRemainBoxCount + // + txtRemainBoxCount.BackColor = SystemColors.ButtonHighlight; + txtRemainBoxCount.BorderStyle = BorderStyle.None; + txtRemainBoxCount.Font = new Font("Microsoft YaHei UI", 21.75F, FontStyle.Bold, GraphicsUnit.Point, 134); + txtRemainBoxCount.Location = new Point(507, 417); + txtRemainBoxCount.Name = "txtRemainBoxCount"; + txtRemainBoxCount.ReadOnly = true; + txtRemainBoxCount.Size = new Size(100, 37); + txtRemainBoxCount.TabIndex = 11; + txtRemainBoxCount.Text = "100"; + txtRemainBoxCount.TextAlign = HorizontalAlignment.Center; + // + // label15 + // + label15.AutoSize = true; + label15.Font = new Font("Microsoft YaHei UI", 15.75F, FontStyle.Bold, GraphicsUnit.Point, 134); + label15.ForeColor = Color.DimGray; + label15.Location = new Point(496, 475); + label15.Name = "label15"; + label15.Size = new Size(117, 28); + label15.TabIndex = 10; + label15.Text = "剩余总箱数"; + // + // txtOutBoxCount + // + txtOutBoxCount.BackColor = SystemColors.ButtonHighlight; + txtOutBoxCount.BorderStyle = BorderStyle.None; + txtOutBoxCount.Font = new Font("Microsoft YaHei UI", 21.75F, FontStyle.Bold, GraphicsUnit.Point, 134); + txtOutBoxCount.Location = new Point(317, 417); + txtOutBoxCount.Name = "txtOutBoxCount"; + txtOutBoxCount.ReadOnly = true; + txtOutBoxCount.Size = new Size(100, 37); + txtOutBoxCount.TabIndex = 9; + txtOutBoxCount.Text = "100"; + txtOutBoxCount.TextAlign = HorizontalAlignment.Center; + // + // label14 + // + label14.AutoSize = true; + label14.Font = new Font("Microsoft YaHei UI", 15.75F, FontStyle.Bold, GraphicsUnit.Point, 134); + label14.ForeColor = Color.DimGray; + label14.Location = new Point(306, 475); + label14.Name = "label14"; + label14.Size = new Size(117, 28); + label14.TabIndex = 8; + label14.Text = "出库总箱数"; + // + // txtInBoxCount + // + txtInBoxCount.BackColor = SystemColors.ButtonHighlight; + txtInBoxCount.BorderStyle = BorderStyle.None; + txtInBoxCount.Font = new Font("Microsoft YaHei UI", 21.75F, FontStyle.Bold, GraphicsUnit.Point, 134); + txtInBoxCount.Location = new Point(103, 417); + txtInBoxCount.Name = "txtInBoxCount"; + txtInBoxCount.ReadOnly = true; + txtInBoxCount.Size = new Size(100, 37); + txtInBoxCount.TabIndex = 7; + txtInBoxCount.Text = "100"; + txtInBoxCount.TextAlign = HorizontalAlignment.Center; + // + // label13 + // + label13.AutoSize = true; + label13.Font = new Font("Microsoft YaHei UI", 15.75F, FontStyle.Bold, GraphicsUnit.Point, 134); + label13.ForeColor = Color.DimGray; + label13.Location = new Point(92, 475); + label13.Name = "label13"; + label13.Size = new Size(117, 28); + label13.TabIndex = 6; + label13.Text = "入库总箱数"; + // + // label12 + // + label12.AutoSize = true; + label12.Font = new Font("Microsoft YaHei UI", 15.75F, FontStyle.Bold, GraphicsUnit.Point, 134); + label12.ForeColor = Color.FromArgb(64, 64, 64); + label12.Location = new Point(10, 319); + label12.Name = "label12"; + label12.Size = new Size(159, 28); + label12.TabIndex = 5; + label12.Text = "成品出入库统计"; + // + // label9 + // + label9.AutoSize = true; + label9.ForeColor = Color.DimGray; + label9.Location = new Point(41, 160); + label9.Name = "label9"; + label9.Size = new Size(88, 26); + label9.TabIndex = 4; + label9.Text = "原料名称"; + // + // txtIORawName + // + txtIORawName.Location = new Point(154, 157); + txtIORawName.Name = "txtIORawName"; + txtIORawName.Size = new Size(446, 32); + txtIORawName.TabIndex = 3; + // + // label10 + // + label10.AutoSize = true; + label10.ForeColor = Color.DimGray; + label10.Location = new Point(41, 61); + label10.Name = "label10"; + label10.Size = new Size(107, 26); + label10.TabIndex = 2; + label10.Text = "外箱二维码"; + // + // txtIOOutBagCode + // + txtIOOutBagCode.Location = new Point(154, 58); + txtIOOutBagCode.Name = "txtIOOutBagCode"; + txtIOOutBagCode.Size = new Size(446, 32); + txtIOOutBagCode.TabIndex = 1; + // + // label11 + // + label11.AutoSize = true; + label11.Font = new Font("Microsoft YaHei UI", 15.75F, FontStyle.Bold, GraphicsUnit.Point, 134); + label11.ForeColor = Color.FromArgb(64, 64, 64); + label11.Location = new Point(10, 9); + label11.Name = "label11"; + label11.Size = new Size(159, 28); + label11.TabIndex = 0; + label11.Text = "成品出入库信息"; + // + // materialCard3 + // + materialCard3.BackColor = Color.FromArgb(255, 255, 255); + materialCard3.Controls.Add(label8); + materialCard3.Depth = 0; + materialCard3.ForeColor = Color.FromArgb(222, 0, 0, 0); + materialCard3.Location = new Point(897, 8); + materialCard3.Margin = new Padding(14); + materialCard3.MouseState = ReaLTaiizor.Helper.MaterialDrawHelper.MaterialMouseState.HOVER; + materialCard3.Name = "materialCard3"; + materialCard3.Padding = new Padding(14); + materialCard3.Size = new Size(1000, 900); + materialCard3.TabIndex = 1; + // + // label8 + // + label8.AutoSize = true; + label8.Font = new Font("Microsoft YaHei UI", 15.75F, FontStyle.Bold, GraphicsUnit.Point, 134); + label8.ForeColor = Color.FromArgb(64, 64, 64); + label8.Location = new Point(12, 9); + label8.Name = "label8"; + label8.Size = new Size(180, 28); + label8.TabIndex = 5; + label8.Text = "原料使用历史信息"; + // + // materialCard2 + // + materialCard2.BackColor = Color.FromArgb(255, 255, 255); + materialCard2.Controls.Add(DownloadProgressBarMain); + materialCard2.Controls.Add(btnTestAction); + materialCard2.Controls.Add(label7); + materialCard2.Controls.Add(txtRURawName); + materialCard2.Controls.Add(label6); + materialCard2.Controls.Add(txtRUInBagCode); + materialCard2.Controls.Add(label5); + materialCard2.Depth = 0; + materialCard2.ForeColor = Color.FromArgb(222, 0, 0, 0); + materialCard2.Location = new Point(14, 9); + materialCard2.Margin = new Padding(14); + materialCard2.MouseState = ReaLTaiizor.Helper.MaterialDrawHelper.MaterialMouseState.HOVER; + materialCard2.Name = "materialCard2"; + materialCard2.Padding = new Padding(14); + materialCard2.Size = new Size(864, 348); + materialCard2.TabIndex = 0; + // + // btnTestAction + // + btnTestAction.Location = new Point(41, 223); + btnTestAction.Name = "btnTestAction"; + btnTestAction.Size = new Size(142, 44); + btnTestAction.TabIndex = 5; + btnTestAction.Text = "结束操作"; + btnTestAction.UseVisualStyleBackColor = true; + btnTestAction.Click += btnTestAction_Click; + // + // label7 + // + label7.AutoSize = true; + label7.ForeColor = Color.DimGray; + label7.Location = new Point(41, 126); + label7.Name = "label7"; + label7.Size = new Size(88, 26); + label7.TabIndex = 4; + label7.Text = "原料名称"; + // + // txtRURawName + // + txtRURawName.Location = new Point(154, 123); + txtRURawName.Name = "txtRURawName"; + txtRURawName.Size = new Size(446, 32); + txtRURawName.TabIndex = 3; + // + // label6 + // + label6.AutoSize = true; + label6.ForeColor = Color.DimGray; + label6.Location = new Point(41, 68); + label6.Name = "label6"; + label6.Size = new Size(107, 26); + label6.TabIndex = 2; + label6.Text = "内袋二维码"; + // + // txtRUInBagCode + // + txtRUInBagCode.Location = new Point(154, 65); + txtRUInBagCode.Name = "txtRUInBagCode"; + txtRUInBagCode.Size = new Size(446, 32); + txtRUInBagCode.TabIndex = 1; + // + // label5 + // + label5.AutoSize = true; + label5.Font = new Font("Microsoft YaHei UI", 15.75F, FontStyle.Bold, GraphicsUnit.Point, 134); + label5.ForeColor = Color.FromArgb(64, 64, 64); + label5.Location = new Point(10, 9); + label5.Name = "label5"; + label5.Size = new Size(138, 28); + label5.TabIndex = 0; + label5.Text = "原料使用信息"; + // // tabPage2 // + tabPage2.Controls.Add(btnStopHistoryPlay); + tabPage2.Controls.Add(dataGridView1); + tabPage2.Controls.Add(materialCard1); + tabPage2.Controls.Add(btnPlayHistoryVideo); + tabPage2.Controls.Add(videoView1); tabPage2.Font = new Font("Microsoft YaHei UI", 14.25F, FontStyle.Bold, GraphicsUnit.Point, 134); tabPage2.ImageKey = "Load.png"; tabPage2.Location = new Point(4, 26); @@ -98,8 +475,145 @@ tabPage2.Text = "录像历史数据"; tabPage2.UseVisualStyleBackColor = true; // + // btnStopHistoryPlay + // + btnStopHistoryPlay.Location = new Point(628, 14); + btnStopHistoryPlay.Name = "btnStopHistoryPlay"; + btnStopHistoryPlay.Size = new Size(116, 45); + btnStopHistoryPlay.TabIndex = 12; + btnStopHistoryPlay.Text = "停止播放"; + btnStopHistoryPlay.UseVisualStyleBackColor = true; + btnStopHistoryPlay.Click += btnStopHistoryPlay_Click; + // + // dataGridView1 + // + dataGridView1.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize; + dataGridView1.Location = new Point(17, 328); + dataGridView1.Name = "dataGridView1"; + dataGridView1.Size = new Size(483, 584); + dataGridView1.TabIndex = 11; + // + // materialCard1 + // + materialCard1.BackColor = Color.FromArgb(255, 255, 255); + materialCard1.Controls.Add(label4); + materialCard1.Controls.Add(txtSearchCode); + materialCard1.Controls.Add(btnHistoryVideoSearch); + materialCard1.Controls.Add(label3); + materialCard1.Controls.Add(PdtHistorySearchEnd); + materialCard1.Controls.Add(label2); + materialCard1.Controls.Add(label1); + materialCard1.Controls.Add(PdtHistorySearchStart); + materialCard1.Depth = 0; + materialCard1.ForeColor = Color.FromArgb(222, 0, 0, 0); + materialCard1.Location = new Point(17, 24); + materialCard1.Margin = new Padding(14); + materialCard1.MouseState = ReaLTaiizor.Helper.MaterialDrawHelper.MaterialMouseState.HOVER; + materialCard1.Name = "materialCard1"; + materialCard1.Padding = new Padding(14); + materialCard1.Size = new Size(483, 287); + materialCard1.TabIndex = 10; + // + // label4 + // + label4.AutoSize = true; + label4.ForeColor = Color.Gray; + label4.Location = new Point(196, 34); + label4.Name = "label4"; + label4.Size = new Size(88, 26); + label4.TabIndex = 7; + label4.Text = "条码信息"; + // + // txtSearchCode + // + txtSearchCode.Location = new Point(17, 63); + txtSearchCode.Name = "txtSearchCode"; + txtSearchCode.Size = new Size(449, 32); + txtSearchCode.TabIndex = 6; + // + // btnHistoryVideoSearch + // + btnHistoryVideoSearch.ForeColor = Color.DimGray; + btnHistoryVideoSearch.Location = new Point(125, 214); + btnHistoryVideoSearch.Name = "btnHistoryVideoSearch"; + btnHistoryVideoSearch.Size = new Size(210, 56); + btnHistoryVideoSearch.TabIndex = 5; + btnHistoryVideoSearch.Text = "历史记录视频搜索"; + btnHistoryVideoSearch.UseVisualStyleBackColor = true; + btnHistoryVideoSearch.Click += btnHistoryVideoSearch_Click; + // + // label3 + // + label3.AutoSize = true; + label3.ForeColor = Color.Gray; + label3.Location = new Point(288, 117); + label3.Name = "label3"; + label3.Size = new Size(88, 26); + label3.TabIndex = 4; + label3.Text = "结束时间"; + // + // PdtHistorySearchEnd + // + PdtHistorySearchEnd.FontSize = ReaLTaiizor.Extension.Poison.PoisonDateTimeSize.Medium; + PdtHistorySearchEnd.Location = new Point(268, 155); + PdtHistorySearchEnd.MinimumSize = new Size(0, 29); + PdtHistorySearchEnd.Name = "PdtHistorySearchEnd"; + PdtHistorySearchEnd.Size = new Size(131, 32); + PdtHistorySearchEnd.TabIndex = 3; + // + // label2 + // + label2.AutoSize = true; + label2.ForeColor = Color.Gray; + label2.Location = new Point(75, 116); + label2.Name = "label2"; + label2.Size = new Size(88, 26); + label2.TabIndex = 2; + label2.Text = "开始时间"; + // + // label1 + // + label1.AutoSize = true; + label1.Font = new Font("Microsoft YaHei UI", 15.75F, FontStyle.Bold, GraphicsUnit.Point, 134); + label1.ForeColor = Color.Gray; + label1.Location = new Point(6, 7); + label1.Name = "label1"; + label1.Size = new Size(96, 28); + label1.TabIndex = 1; + label1.Text = "搜索条件"; + // + // PdtHistorySearchStart + // + PdtHistorySearchStart.FontSize = ReaLTaiizor.Extension.Poison.PoisonDateTimeSize.Medium; + PdtHistorySearchStart.Location = new Point(55, 154); + PdtHistorySearchStart.MinimumSize = new Size(0, 29); + PdtHistorySearchStart.Name = "PdtHistorySearchStart"; + PdtHistorySearchStart.Size = new Size(131, 32); + PdtHistorySearchStart.TabIndex = 0; + // + // btnPlayHistoryVideo + // + btnPlayHistoryVideo.Location = new Point(506, 14); + btnPlayHistoryVideo.Name = "btnPlayHistoryVideo"; + btnPlayHistoryVideo.Size = new Size(116, 45); + btnPlayHistoryVideo.TabIndex = 8; + btnPlayHistoryVideo.Text = "button1"; + btnPlayHistoryVideo.UseVisualStyleBackColor = true; + btnPlayHistoryVideo.Click += btnPlayHistoryVideo_Click; + // + // videoView1 + // + videoView1.BackColor = Color.Black; + videoView1.Location = new Point(506, 65); + videoView1.MediaPlayer = null; + videoView1.Name = "videoView1"; + videoView1.Size = new Size(1394, 847); + videoView1.TabIndex = 7; + videoView1.Text = "videoView1"; + // // tabPage3 // + tabPage3.Controls.Add(metroProgressBar1); tabPage3.Controls.Add(DownloadProgressBar); tabPage3.Controls.Add(btnStopLoadVideo); tabPage3.Controls.Add(btnLoadVideo); @@ -112,6 +626,36 @@ tabPage3.Text = "相机设置"; tabPage3.UseVisualStyleBackColor = true; // + // metroProgressBar1 + // + metroProgressBar1.BackgroundColor = Color.FromArgb(238, 238, 238); + metroProgressBar1.BorderColor = Color.FromArgb(238, 238, 238); + metroProgressBar1.DisabledBackColor = Color.FromArgb(238, 238, 238); + metroProgressBar1.DisabledBorderColor = Color.FromArgb(238, 238, 238); + metroProgressBar1.DisabledProgressColor = Color.FromArgb(120, 65, 177, 225); + metroProgressBar1.IsDerivedStyle = true; + metroProgressBar1.Location = new Point(252, 181); + metroProgressBar1.Maximum = 100; + metroProgressBar1.Minimum = 0; + metroProgressBar1.Name = "metroProgressBar1"; + metroProgressBar1.Orientation = ReaLTaiizor.Enum.Metro.ProgressOrientation.Horizontal; + metroProgressBar1.ProgressColor = Color.FromArgb(65, 177, 225); + metroProgressBar1.Size = new Size(125, 23); + metroProgressBar1.Style = ReaLTaiizor.Enum.Metro.Style.Light; + metroProgressBar1.StyleManager = null; + metroProgressBar1.TabIndex = 4; + metroProgressBar1.Text = "metroProgressBar1"; + metroProgressBar1.ThemeAuthor = "Taiizor"; + metroProgressBar1.ThemeName = "MetroLight"; + metroProgressBar1.Value = 0; + // + // DownloadProgressBar + // + DownloadProgressBar.Location = new Point(247, 124); + DownloadProgressBar.Name = "DownloadProgressBar"; + DownloadProgressBar.Size = new Size(130, 23); + DownloadProgressBar.TabIndex = 3; + // // btnStopLoadVideo // btnStopLoadVideo.Location = new Point(409, 54); @@ -142,22 +686,19 @@ btnNVRLogin.UseVisualStyleBackColor = true; btnNVRLogin.Click += btnNVRLogin_Click; // - // DownTimer + // DownloadProgressBarMain // - DownTimer.Tick += DownTimer_Tick; - // - // DownloadProgressBar - // - DownloadProgressBar.Location = new Point(247, 124); - DownloadProgressBar.Name = "DownloadProgressBar"; - DownloadProgressBar.Size = new Size(130, 23); - DownloadProgressBar.TabIndex = 3; + DownloadProgressBarMain.Location = new Point(1, 324); + DownloadProgressBarMain.Name = "DownloadProgressBarMain"; + DownloadProgressBarMain.Size = new Size(862, 23); + DownloadProgressBarMain.TabIndex = 6; // // MainApp // AutoScaleDimensions = new SizeF(8F, 17F); AutoScaleMode = AutoScaleMode.Font; ClientSize = new Size(1920, 1030); + Controls.Add(statusStrip1); Controls.Add(materialTabControl1); DrawerTabControl = materialTabControl1; Font = new Font("Microsoft YaHei UI", 9F, FontStyle.Bold, GraphicsUnit.Point, 134); @@ -168,12 +709,30 @@ WindowState = FormWindowState.Maximized; Load += MainApp_Load; materialTabControl1.ResumeLayout(false); + tabPage1.ResumeLayout(false); + materialCard4.ResumeLayout(false); + materialCard4.PerformLayout(); + materialCard3.ResumeLayout(false); + materialCard3.PerformLayout(); + materialCard2.ResumeLayout(false); + materialCard2.PerformLayout(); + tabPage2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)dataGridView1).EndInit(); + materialCard1.ResumeLayout(false); + materialCard1.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)videoView1).EndInit(); tabPage3.ResumeLayout(false); ResumeLayout(false); + PerformLayout(); } #endregion + + private StatusStrip statusStrip1; + private ImageList imageList1; + + private ReaLTaiizor.Controls.MaterialTabControl materialTabControl1; private TabPage tabPage1; private TabPage tabPage2; @@ -181,7 +740,49 @@ private Button btnNVRLogin; private Button btnLoadVideo; private Button btnStopLoadVideo; - private System.Windows.Forms.Timer DownTimer; private ProgressBar DownloadProgressBar; + private ReaLTaiizor.Controls.MetroProgressBar metroProgressBar1; + private LibVLCSharp.WinForms.VideoView videoView1; + private Button btnPlayHistoryVideo; + private ReaLTaiizor.Controls.MaterialCard materialCard1; + private ReaLTaiizor.Controls.PoisonDateTime PdtHistorySearchStart; + private Label label1; + private Button btnHistoryVideoSearch; + private Label label3; + private ReaLTaiizor.Controls.PoisonDateTime PdtHistorySearchEnd; + private Label label2; + private Label label4; + private TextBox txtSearchCode; + private DataGridView dataGridView1; + private Button btnStopHistoryPlay; + private ReaLTaiizor.Controls.MaterialCard materialCard2; + private Label label5; + private Label label6; + private TextBox txtRUInBagCode; + private Label label7; + private TextBox txtRURawName; + private ReaLTaiizor.Controls.MaterialCard materialCard3; + private Label label8; + private ReaLTaiizor.Controls.MaterialCard materialCard4; + private Label label9; + private TextBox txtIORawName; + private Label label10; + private TextBox txtIOOutBagCode; + private Label label11; + private Label label12; + private Label label13; + private TextBox txtInBoxCount; + private TextBox txtOutBoxCount; + private Label label14; + private TextBox txtRemainBoxCount; + private Label label15; + private Label label16; + private TextBox txtIOInBagCode; + private Label label18; + private TextBox txtIOOutputTime; + private Label label17; + private TextBox txtIOInputTime; + private Button btnTestAction; + private ProgressBar DownloadProgressBarMain; } } \ No newline at end of file diff --git a/FATrace.OEMApp/MainApp.cs b/FATrace.OEMApp/MainApp.cs index 40e3d6f..df5aa45 100644 --- a/FATrace.OEMApp/MainApp.cs +++ b/FATrace.OEMApp/MainApp.cs @@ -1,24 +1,32 @@ +using FATrace.Com; using FATrace.HKNetLib.Hardware; using FATrace.HKNetLib.Wrapper; +using FATrace.Model; +using LibVLCSharp.Shared; using ReaLTaiizor.Forms; -using System; -using System.Runtime.InteropServices; +using System.ComponentModel; namespace FATrace.OEMApp { public partial class MainApp : MaterialForm { - /// /// 海康Camera 客户端 /// public HkCamera HkCameraClient { get; set; } - /// - /// 通道集合 - /// - [MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 96, ArraySubType = UnmanagedType.U4)] - private int[] iChannelNum; + // 历史表列头中文映射 + private readonly Dictionary _historyHeaderMap = new Dictionary + { + { nameof(VideoAction.Id), "编号" }, + { nameof(VideoAction.Code), "条码" }, + { nameof(VideoAction.User), "用户" }, + { nameof(VideoAction.VideoFilePath), "视频路径" }, + { nameof(VideoAction.VideoName), "视频名称" }, + { nameof(VideoAction.StartTime), "开始时间" }, + { nameof(VideoAction.EndTime), "结束时间" }, + { nameof(VideoAction.CreateTime), "创建时间" } + }; public MainApp() { @@ -31,8 +39,20 @@ namespace FATrace.OEMApp HkCameraClient = new HkCamera(); //保存SDK日志 CHCNetSDK.NET_DVR_SetLogToFile(3, "C:\\SdkLog\\", true); - iChannelNum = new int[96]; + //读取配置 + //HkCameraClient.NVR_IP = ConfigHelper.GetValue("NVRIP"); + //HkCameraClient.NVR_Port = ConfigHelper.GetValue("NVRPort"); + //HkCameraClient.NVR_UserName = ConfigHelper.GetValue("NVRUserName"); + + HkCameraClient.NVRVideoSavePath = ConfigHelper.GetValue("NVRVideoSavePath"); + + + + InitMediaPlayer(); + InitHistoryGridBinding(); + + //materialListView1.DataBindings try { var systemName = Program.SystemName; @@ -56,24 +76,43 @@ namespace FATrace.OEMApp } } + /// + /// 当前登录用户 + /// + public string CurUserName { get; set; } + + #region 视频处理变量 + + /// + /// 当前播放的视频 + /// + public string CurrentVideoPath { get; set; } + + #endregion + + + #region 内包信息 + + /// + /// 内包条码 + /// + public string CurInBagCode { get; set; } + + /// + /// 内包条码 原料名称 + /// + public string CurInBagRawName { get; set; } + + #endregion + private void btnNVRLogin_Click(object sender, EventArgs e) { - //var Result = HkCameraClient.Login(new CameraLoginInfo() - //{ - // IP = "192.168.1.168", - // Port = 8000, - // UserName = "admin", - // Password = "glico@2025", - //}); + HkCameraClient.NVR_IP = ConfigHelper.GetValue("NVRIP"); + HkCameraClient.NVR_Port = ushort.Parse(ConfigHelper.GetValue("NVRPort")); + HkCameraClient.NVR_UserName = ConfigHelper.GetValue("NVRUserName"); + HkCameraClient.NVR_Pw = ConfigHelper.GetValue("NVRPw"); - //if (Result) - //{ - // MessageBox.Show("登录成功"); - // return; - //} - //MessageBox.Show("登录失败"); - - var result = HkCameraClient.Sdk_NET_DVR_Login_V30("192.168.1.168", 8000, "admin", "glico@2025"); + var result = HkCameraClient.Sdk_NET_DVR_Login_V30(HkCameraClient.NVR_IP, HkCameraClient.NVR_Port, HkCameraClient.NVR_UserName, HkCameraClient.NVR_Pw); if (result) { MessageBox.Show("登录成功"); @@ -83,24 +122,35 @@ namespace FATrace.OEMApp return; } + private void btnLoadVideo_Click(object sender, EventArgs e) { + CurrentVideoPath = NVRCom.GetVideoName(HkCameraClient.NVRVideoSavePath, "CODE"); - var Result = HkCameraClient.Sdk_NET_DVR_GetFileByTime_V40(DateTime.Now.AddMinutes(-5), DateTime.Now, $"D:\\Downtest_Channel{DateTime.Now.ToString("yyyy-MM-dd HHmmss")}.mp4"); + var Result = HkCameraClient.Sdk_NET_DVR_GetFileByTime_V40(DateTime.Now.AddMinutes(-5), DateTime.Now, CurrentVideoPath); if (Result.Result) { - DownTimer.Interval = 2000; - DownTimer.Enabled = true; + HkCameraClient.NVRLoadVideoProcessEventHandler -= HkCameraClient_NVRLoadVideoProcessEventHandler; + HkCameraClient.NVRLoadVideoProcessEventHandler += HkCameraClient_NVRLoadVideoProcessEventHandler; + // 进度轮询已在库方法内部自动启动,这里无需再次调用 + HkCameraClient.StartDownloadProgressMonitor(); } else { MessageBox.Show($"[Sdk_NET_DVR_GetFileByTime_V40] 执行失败:{Result.Msg}"); } - //btnStopDownload.Enabled = true; } + private void HkCameraClient_NVRLoadVideoProcessEventHandler(object? sender, short value) + { + this.BeginInvoke(new Action(() => + { + DownloadProgressBarMain.Value = value; + })); + } + private void btnDownloadName_Click(object sender, EventArgs e) { //if (m_lDownHandle >= 0) @@ -151,59 +201,266 @@ namespace FATrace.OEMApp private void btnStopLoadVideo_Click(object sender, EventArgs e) { - //if (m_lDownHandle < 0) - //{ - // return; - //} - - //if (!CHCNetSDK.NET_DVR_StopGetFile(m_lDownHandle)) - //{ - // iLastErr = CHCNetSDK.NET_DVR_GetLastError(); - // str = "NET_DVR_StopGetFile failed, error code= " + iLastErr; //下载控制失败,输出错误号 - // MessageBox.Show(str); - // return; - //} - - //timerDownload.Stop(); - - //MessageBox.Show("The downloading has been stopped succesfully!"); - //m_lDownHandle = -1; - //DownloadProgressBar.Value = 0; - //btnStopDownload.Enabled = true; + var Result = HkCameraClient.Sdk_NET_DVR_StopGetFile(); + if (Result.Result) + { + MessageBox.Show($"[暂停成功] :{Result.Msg}"); + } + else + { + MessageBox.Show($"[暂停失败] :{Result.Msg}"); + } } - private void DownTimer_Tick(object sender, EventArgs e) + #region 视频播放 + + private LibVLC _libVLC; + private MediaPlayer _mediaPlayer; + + /// + /// 初始化MediaPlay + /// + private void InitMediaPlayer() { - DownloadProgressBar.Maximum = 100; - DownloadProgressBar.Minimum = 0; + _libVLC = new LibVLC(); + _mediaPlayer = new MediaPlayer(_libVLC); + videoView1.MediaPlayer = _mediaPlayer; - int iPos = 0; + } - //获取下载进度 - iPos = HkCameraClient.Sdk_NET_DVR_GetDownloadPos(HkCameraClient.m_lDownHandle); - if ((iPos > DownloadProgressBar.Minimum) && (iPos < DownloadProgressBar.Maximum)) + #endregion + + /// + /// 测试手动播放 + /// + /// + /// + private void btnPlayHistoryVideo_Click(object sender, EventArgs e) + { + string path = "D:\\Downtest_Channel2025-09-12 102454.mp4"; + _mediaPlayer.Play(new Media(_libVLC, path)); + } + + /// + /// 停止播放当前的视频文件 + /// + /// + /// + private void btnStopHistoryPlay_Click(object sender, EventArgs e) + { + _mediaPlayer.Stop(); + } + + /// + /// 清理播放资源 + /// + private void CleanMediaPlay() + { + _mediaPlayer.Stop(); + _mediaPlayer.Dispose(); + _libVLC.Dispose(); + } + + + #region 历史数据查询 + + + + /// + /// 采用 BindingList + BindingSource,使数据变化自动触发 UI 刷新 + /// + private BindingSource historyBindingSource { get; set; } + private BindingList historyVideoBindingList { get; set; } + + + /// + /// 初始化历史记录表格的数据绑定 + /// + private void InitHistoryGridBinding() + { + historyBindingSource = new BindingSource(); + historyVideoBindingList = new BindingList(); + historyBindingSource.DataSource = historyVideoBindingList; + // 绑定到 WinForms 原生 DataGridView + dataGridView1.AutoGenerateColumns = true; + dataGridView1.DataSource = historyBindingSource; + // 绑定完成后统一调整列头与可见性 + dataGridView1.DataBindingComplete -= DataGridView1_DataBindingComplete; + dataGridView1.DataBindingComplete += DataGridView1_DataBindingComplete; + // 双击行播放对应视频 + dataGridView1.CellDoubleClick -= DataGridView1_CellDoubleClick; + dataGridView1.CellDoubleClick += DataGridView1_CellDoubleClick; + // 若此时已存在列,立即调整一次 + ConfigureHistoryGridColumns(); + } + + + private void btnHistoryVideoSearch_Click(object sender, EventArgs e) + { + var query = FSqlContext.FDb.Select(); + if (!string.IsNullOrEmpty(txtSearchCode.Text.Trim())) { - DownloadProgressBar.Value = iPos; + query = query.Where(a => a.Code!.Contains(txtSearchCode.Text.Trim())); } + query.Where(a => a.CreateTime >= PdtHistorySearchStart.Value && a.CreateTime <= PdtHistorySearchEnd.Value); - if (iPos == 100) //下载完成 + // 拉取结果并刷新绑定列表 + var resultList = query.ToList(); + // 暂停变更通知,批量更新提高效率 + historyVideoBindingList.RaiseListChangedEvents = false; + historyVideoBindingList.Clear(); + foreach (var item in resultList) { - DownloadProgressBar.Value = iPos; - if (!CHCNetSDK.NET_DVR_StopGetFile(HkCameraClient.m_lDownHandle)) + historyVideoBindingList.Add(item); + } + historyVideoBindingList.RaiseListChangedEvents = true; + // 通知 UI 刷新 + historyVideoBindingList.ResetBindings(); + } + + // 与历史记录表格列配置相关的成员(WinForms DataGridView) + private void DataGridView1_DataBindingComplete(object? sender, DataGridViewBindingCompleteEventArgs e) + { + try + { + ConfigureHistoryGridColumns(); + } + catch (Exception ex) + { + // 仅记录,不中断界面 + System.Diagnostics.Debug.WriteLine($"[DataBindingComplete] 列配置失败: {ex.Message}"); + } + } + + /// + /// 双击行,播放该行对应的视频文件 + /// + private void DataGridView1_CellDoubleClick(object? sender, DataGridViewCellEventArgs e) + { + if (e.RowIndex < 0) return; // 双击列头等 + try + { + var row = dataGridView1.Rows[e.RowIndex]; + if (row?.DataBoundItem is VideoAction va) { - MessageBox.Show($"[NET_DVR_StopGetFile] 执行失败:{HkCameraClient.GetLastError()}"); - return; + var path = va.VideoFilePath; + if (string.IsNullOrWhiteSpace(path)) + { + MessageBox.Show("该记录没有可播放的视频路径。", "播放提示", MessageBoxButtons.OK, MessageBoxIcon.Information); + return; + } + if (!File.Exists(path)) + { + MessageBox.Show($"未找到视频文件:\n{path}", "播放失败", MessageBoxButtons.OK, MessageBoxIcon.Warning); + return; + } + + _mediaPlayer?.Stop(); + _mediaPlayer?.Play(new LibVLCSharp.Shared.Media(_libVLC, path)); + } + } + catch (Exception ex) + { + MessageBox.Show($"播放失败: {ex.Message}", "播放失败", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + + /// + /// 按照 _historyHeaderMap 配置列头、隐藏不需要的列,并做常用格式化 + /// + private void ConfigureHistoryGridColumns() + { + if (dataGridView1 == null || dataGridView1.Columns.Count == 0) return; + + foreach (DataGridViewColumn col in dataGridView1.Columns) + { + var propName = col.DataPropertyName; + + // 设置列头中文 + if (!string.IsNullOrWhiteSpace(propName) && _historyHeaderMap.TryGetValue(propName, out var headerText)) + { + col.HeaderText = headerText; + } + + // 隐藏 Id 列 + if (propName == nameof(VideoAction.Id)) + { + col.Visible = false; + continue; + } + + //// 常用列宽与格式 + //if (propName is nameof(VideoAction.Code)) + //{ + // col.AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells; + //} + //else if (propName is nameof(VideoAction.VideoFilePath)) + //{ + // col.AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill; + //} + + // 时间列格式化 + if (propName is nameof(VideoAction.StartTime) or nameof(VideoAction.EndTime) or nameof(VideoAction.CreateTime)) + { + col.DefaultCellStyle.Format = "yyyy-MM-dd HH:mm:ss"; + col.AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells; } - HkCameraClient.m_lDownHandle = -1; - DownTimer.Stop(); } - if (iPos == 200) //网络异常,下载失败 + // 选中整行,易于操作 + dataGridView1.SelectionMode = DataGridViewSelectionMode.FullRowSelect; + dataGridView1.MultiSelect = false; + dataGridView1.ReadOnly = true; + dataGridView1.AllowUserToAddRows = false; + } + + + #endregion + + + /// + /// 操作结束 + /// + /// + /// + private void btnTestAction_Click(object sender, EventArgs e) + { + var Result = HkCameraClient.Sdk_NET_DVR_GetFileByTime_V40(DateTime.Now.AddMinutes(-5), DateTime.Now, NVRCom.GetVideoName(HkCameraClient.NVRVideoSavePath, "cODE")); + if (Result.Result) { - MessageBox.Show("The downloading is abnormal for the abnormal network!"); - DownTimer.Stop(); + HkCameraClient.NVRLoadVideoProcessEventHandler -= HkCameraClient_NVRLoadVideoProcessEventHandler; + HkCameraClient.NVRLoadVideoProcessEventHandler += HkCameraClient_NVRLoadVideoProcessEventHandler; + + HkCameraClient.NVRLoadVideoCompleteEventHandler -= HkCameraClient_NVRLoadVideoCompleteEventHandler; + HkCameraClient.NVRLoadVideoCompleteEventHandler += HkCameraClient_NVRLoadVideoCompleteEventHandler; + + // 进度轮询已在库方法内部自动启动,这里无需再次调用 } + else + { + MessageBox.Show($"[Sdk_NET_DVR_GetFileByTime_V40] 执行失败:{Result.Msg}"); + } + } + + /// + /// Handles the event triggered when the NVR (Network Video Recorder) completes loading a video. + /// Video下载完成 + /// + /// The source of the event. This can be if the event is not raised by a specific object. + /// A string containing information about the completed video load operation. + private void HkCameraClient_NVRLoadVideoCompleteEventHandler(object? sender, string e) + { + //先保存当前的信息 + var returnValues = FSqlContext.FDb.Insert(new OEMRawUse() + { + InBagCode=CurInBagCode, + RawName=CurInBagRawName, + User= CurUserName, + UrlState=false, + VideoUrl="",//暂时无法获取URL,因为服务器还没有扫描到文件信息 + }).ExecuteInserted(); + } } } diff --git a/FATrace.OEMApp/MainApp.resx b/FATrace.OEMApp/MainApp.resx index 57a94aa..f3c7b73 100644 --- a/FATrace.OEMApp/MainApp.resx +++ b/FATrace.OEMApp/MainApp.resx @@ -117,6 +117,9 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + 134, 17 + 17, 17 @@ -124,8 +127,8 @@ AAEAAAD/////AQAAAAAAAAAMAgAAAEZTeXN0ZW0uV2luZG93cy5Gb3JtcywgQ3VsdHVyZT1uZXV0cmFs LCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5BQEAAAAmU3lzdGVtLldpbmRvd3MuRm9ybXMu - SW1hZ2VMaXN0U3RyZWFtZXIBAAAABERhdGEHAgIAAAAJAwAAAA8DAAAAfCEAAAJNU0Z0AUkBTAIBAQgB - AAF4AQABeAEAARABAAEQAQAE/wEhAQAI/wFCAU0BNgcAATYDAAEoAwABQAMAATADAAEBAQABIAYAATD/ + SW1hZ2VMaXN0U3RyZWFtZXIBAAAABERhdGEHAgIAAAAJAwAAAA8DAAAAaiEAAAJNU0Z0AUkBTAIBAQgB + AAEwAQEBMAEBARABAAEQAQAE/wEhAQAI/wFCAU0BNgcAATYDAAEoAwABQAMAATADAAEBAQABIAYAATD/ AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AP8A/wD/AFIAAdsBlgESAf8B2wGWARIB/wQAAxIBGANJAYgB 2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wQAAdsB lgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8UAAHbAZYB @@ -159,120 +162,117 @@ +QH/AgAC/wP1Af8CAAL/AgAC/wIAAv8CAAL/AgAC/wIAAv8CxwH2Af8D9QH/AgAC/wIAAfwB/wJoAe4B /wQAAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B 2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/xQAAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsB - lgESAf8B2wGWARIB/wHbAZYBEgH/AlEBUAGfBAABkgF2AUQB8wHbAZYBEgH/AdsBlgESAf8B2wGWARIB - /wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wQAAdsBlgESAf8B2wGWARIB/ywAAdsBlgESAf8B2wGWARIB - /wNGAX8CAgFMAf8CAgHoAf8CAAHwAf8CiQH0Af8D9QH/AgAB9QH/AgAB9QH/A/UB/wIAAfUB/wIAAfUB - /wP1Af8CAAH1Af8CAAHwAf8CBgHrAf8CYgHqAf8DKgFAAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsB - lgESAf8DSQGIBAADBgEIAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/1AAAdsB - lgESAf8B2wGWARIB/ywAAdsBlgESAf8B2wGWARIB/wNGAX8CAgFAAf8CIAHdAf8CCgHgAf8CAwHhAf8D - 9QH/AgMB4QH/AgMB4QH/A/UB/wIDAeEB/wP1Af8D9QH/AgMB4QH/AiAB5AH/AiAB3QH/AlsB5AH/BAAD - CQEMAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/DAADDAEQAdsBlgESAf8B2wGWARIB/wFpAWMBWgHfFAAB - 2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8DKAE8BAADUgGgAdsB - lgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/BAAB2wGWARIB/wHbAZYB - EgH/KAADHQEoAdsBlgESAf8B2wGWARIB/wNGAX8CAgE0Af8COAHUAf8COAHYAf8COAHbAf8CAwHRAf8D - 9QH/AgMB0QH/A/UB/wIDAdEB/wP1Af8CDQHSAf8COAHbAf8COAHYAf8COAHUAf8CVAHeAf8IAAHbAZYB - EgH/AdsBlgESAf8UAAHbAZYBEgH/AdsBlgESAf8UAAHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYB - EgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8EAAHbAZYBEgH/AdsBlgESAf8B2wGWARIB - /wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/CAAB2wGWARIB/wHbAZYBEgH/DAAB2wGWARIB - /wHbAZYBEgH/AdsBlgESAf8MAAHbAZYBEgH/AdsBlgESAf8EAANGAX8CAgEpAf8CUQHNAf8CTwHUAf8C - TwHVAf8CTwHVAf8D9QH/Ao0B4QH/Ak8B1QH/A/UB/wP1Af8CTwHVAf8CTwHVAf8CTwHUAf8CUQHNAf8C - TAHXAf8EAAHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/DAAB2wGWARIB/wHbAZYBEgH/AdsB - lgESAf8B2wGWARIB/xAAAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYB - EgH/AdsBlgESAf8B2wGWARIB/wQAAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB - /wHbAZYBEgH/AdsBlgESAf8IAAHbAZYBEgH/AdsBlgESAf8CXwFcAcgIAAHbAZYBEgH/AdsBlgESAf8B - 2wGWARIB/wgAAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/BAADRgF/AgEBHgH/AmgBzAH/AmgB0QH/AmgB - 0gH/AmgB0gH/AmgB0gH/A/UB/wJoAdIB/wP1Af8CaAHSAf8CaAHSAf8CaAHSAf8CaAHRAf8CaAHMAf8C - QwHKAf8EAAHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8EAAHbAZYBEgH/AdsB - lgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8DUgGgDAAB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B - 2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/BAAB2wGWARIB/wHbAZYBEgH/AdsB - lgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wwAAdsBlgESAf8MAAHbAZYBEgH/AdsB - lgESAf8B2wGWARIB/wwAAdsBlgESAf8IAANGAX8CAQEUAf8CgAHOAf8CgAHRAf8CgAHSAf8CgAHSAf8C - gAHSAf8D9QH/A/UB/wKAAdIB/wKAAdIB/wKAAdIB/wKAAdIB/wKAAdEB/wKAAc4B/wI5AboB/wQAAdsB - lgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB - /wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/xAAAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B + lgESAf8B2wGWARIB/wHbAZYBEgH/AlEBUAGfBAACbwFgAfMB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B + 2wGWARIB/wHbAZYBEgH/AdsBlgESAf8EAAHbAZYBEgH/AdsBlgESAf8sAAHbAZYBEgH/AdsBlgESAf8D + RgF/AgIBTAH/AgIB6AH/AgAB8AH/AokB9AH/A/UB/wIAAfUB/wIAAfUB/wP1Af8CAAH1Af8CAAH1Af8D + 9QH/AgAB9QH/AgAB8AH/AgYB6wH/AmIB6gH/AyoBQAHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYB + EgH/A0kBiAQAAwYBCAHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf9QAAHbAZYB + EgH/AdsBlgESAf8sAAHbAZYBEgH/AdsBlgESAf8DRgF/AgIBQAH/AiAB3QH/AgoB4AH/AgMB4QH/A/UB + /wIDAeEB/wIDAeEB/wP1Af8CAwHhAf8D9QH/A/UB/wIDAeEB/wIgAeQB/wIgAd0B/wJbAeQB/wQAAwkB + DAHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wwAAwwBEAHbAZYBEgH/AdsBlgESAf8CYwFdAd8UAAHbAZYB + EgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wMoATwEAANSAaAB2wGWARIB + /wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8EAAHbAZYBEgH/AdsBlgESAf8o + AAMdASgB2wGWARIB/wHbAZYBEgH/A0YBfwICATQB/wI4AdQB/wI4AdgB/wI4AdsB/wIDAdEB/wP1Af8C + AwHRAf8D9QH/AgMB0QH/A/UB/wINAdIB/wI4AdsB/wI4AdgB/wI4AdQB/wJUAd4B/wgAAdsBlgESAf8B + 2wGWARIB/xQAAdsBlgESAf8B2wGWARIB/xQAAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B 2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wQAAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsB - lgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8cAAHbAZYBEgH/AdsBlgESAf8B2wGWARIB/xgAAyoB - PwIEAU8B/wKWAb8B/wKXAdYB/wKXAdYB/wKXAdYB/wKXAdYB/wKXAdYB/wP1Af8ClwHWAf8ClwHWAf8C - lwHWAf8ClwHWAf8ClwHWAf8ClwHTAf8CCgF3Af8IAAMyAU8EAANaAb8B2wGWARIB/wHbAZYBEgH/AdsB - lgESAf8DWgG/HAAB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B + lgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8IAAHbAZYBEgH/AdsBlgESAf8MAAHbAZYBEgH/AdsB + lgESAf8B2wGWARIB/wwAAdsBlgESAf8B2wGWARIB/wQAA0YBfwICASkB/wJRAc0B/wJPAdQB/wJPAdUB + /wJPAdUB/wP1Af8CjQHhAf8CTwHVAf8D9QH/A/UB/wJPAdUB/wJPAdUB/wJPAdQB/wJRAc0B/wJMAdcB + /wQAAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8MAAHbAZYBEgH/AdsBlgESAf8B2wGWARIB + /wHbAZYBEgH/EAAB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B 2wGWARIB/wHbAZYBEgH/BAAB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsB - lgESAf8B2wGWARIB/xwAAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/HAACCAFoAf8CVwFpAf8CrgHGAf8C - rwHeAf8CrwHeAf8CrwHeAf8CrwHeAf8CrwHeAf8CrwHeAf8CrwHeAf8CrwHeAf8CrwHeAf8CrwHeAf8C - CAF1Af8CCgFuAf8UAAHbAZYBEgH/AdsBlgESAf8B2wGWARIB/yAAA1gBuAHbAZYBEgH/AdsBlgESAf8B - 2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/BAAB2wGWARIB/wHbAZYBEgH/AdsB - lgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/xwAAdsBlgESAf8B2wGWARIB/wHbAZYB - EgH/HAADIQEvAggBZgH/AggBawH/AggBbgH/AggBbgH/AggBbgH/AggBbgH/AggBbgH/AggBbgH/AggB - bgH/AggBbgH/AggBbgH/AggBagH/AhABfgH/GAAB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8kAAJaAVgB - twHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8MAAHbAZYBEgH/AdsBlgESAf8B - 2wGWARIB/wHbAZYBEgH/AdsBlgESAf8DSQGHHAAB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8kAAMqAT8D - RgF/A0YBfwNGAX8DRgF/A0YBfwNGAX8DRgF/A0YBfwNGAX9oAAJiAV0B3AHbAZYBEgH/AdsBlgESAf8D - PQFoJAAB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8MAAHbAZYBEgH/AdsBlgESAf8B2wGWARIB/xgAAwwB - EAJHAUYBgAMtAUQUAAM6AWADOgFgbAAB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYB - EgH/HAAB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB - /wHbAZYBEgH/GAAB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/CAACWwFZAcAB - 2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/EAACWwFZAcAB2wGWARIB/wHbAZYB - EgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8D - KgFAGAAB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8IAAHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYB - EgH/AdsBlgESAf8YAAHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB - /wHbAZYBEgH/AdsBlgESAf8BqQF9ATUB+BAAAaUBhQE3AfcB2wGWARIB/wwAAzMBUAHbAZYBEgH/BAAB - 2wGWARIB/xAAAdsBlgESAf8QAAJbAVkBwCAAAkcBRgGAAyoBQBQAAdsBlgESAf8B2wGWARIB/wHbAZYB - EgH/AdsBlgESAf8MAAHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8IAAHbAZYB - EgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B - 2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wQAAdsB - lgESAf8UAAHbAZYBEgH/BAAB2wGWARIB/xAAAdsBlgESAf8QAAJbAVkBwCAAAkcBRgGAAyoBQBAAAdsB - lgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/xAAAdsBlgESAf8B2wGWARIB/wHbAZYB - EgH/AdsBlgESAf8IAAHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8QAAMSARgB - 2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/BAAB2wGWARIB/xQAAdsBlgESAf8E - AAHbAZYBEgH/EAAB2wGWARIB/xAAAlsBWQHACAAB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB - /wHbAZYBEgH/BAACRwFGAYADKgFAEAAB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/xQAAz0B - aAHbAZYBEgH/AdsBlgESAf8EAAHbAZYBEgH/AzoBYAHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYB - EgH/GAADGAEgAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8IAAHbAZYBEgH/EAAB2wGWARIB - /wQAAdsBlgESAf8QAAHbAZYBEgH/EAACWwFZAcAgAAJHAUYBgAMqAUAQAAHbAZYBEgH/AdsBlgESAf8B - 2wGWARIB/ygAAdsBlgESAf8IAAHbAZYBEgH/AdsBlgESAf8B2wGWARIB/xwAAdsBlgESAf8B2wGWARIB - /wHbAZYBEgH/DAABdAFsAVcB5wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8E - AAHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8UAAJbAVkBwAgAAdsBlgESAf8B - 2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AkcBRgGAAyoBQBAAAdsBlgESAf8B - kwGAAUEB9ygAAdsBlgESAf8B2wGWARIB/wgAAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/HAAB2wGWARIB - /wHbAZYBEgH/AdsBlgESAf9QAAJbAVkBwCAAAkcBRgGAAyoBQBAAAdsBlgESAf8oAAHbAZYBEgH/AdsB - lgESAf8B2wGWARIB/wgAAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/HAAB2wGWARIB/wHbAZYBEgH/AdsB - lgESAf8MAAJBAUABcAHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8EAAHbAZYB - EgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8UAAJbAVkBwAgAAlsBWQHAAlsBWQHAAlsB - WQHADAACRwFGAYADKgFAGAAB2wGWARIB/wHbAZYBEgH/GAAB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B - 2wGWARIB/wQAAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8cAAHbAZYBEgH/AdsBlgESAf8B - 2wGWARIB/wHbAZYBEgH/CAAB2wGWARIB/xAAAdsBlgESAf8EAAHbAZYBEgH/DAADKgFAAdsBlgESAf8Q - AAJbAVkBwAgAAzoBYAM6AWADOgFgDAACRwFGAYADKgFAFAAB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B - 2wGWARIB/xAAAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8DDAEQAwYBCAHbAZYBEgH/AdsB - lgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8UAAHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYB - EgH/AdsBlgESAf8EAAHbAZYBEgH/FAAB2wGWARIB/wQAAdsBlgESAf8QAAHbAZYBEgH/EAACWwFZAcAg - AAJHAUYBgAMqAUAUAAHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8IAAHbAZYB - EgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8IAAHbAZYBEgH/AdsBlgESAf8B2wGWARIB + lgESAf8B2wGWARIB/wgAAdsBlgESAf8B2wGWARIB/wJfAVwByAgAAdsBlgESAf8B2wGWARIB/wHbAZYB + EgH/CAAB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8EAANGAX8CAQEeAf8CaAHMAf8CaAHRAf8CaAHSAf8C + aAHSAf8CaAHSAf8D9QH/AmgB0gH/A/UB/wJoAdIB/wJoAdIB/wJoAdIB/wJoAdEB/wJoAcwB/wJDAcoB + /wQAAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wQAAdsBlgESAf8B2wGWARIB + /wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wNSAaAMAAHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYB + EgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8EAAHbAZYBEgH/AdsBlgESAf8B2wGWARIB + /wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/DAAB2wGWARIB/wwAAdsBlgESAf8B2wGWARIB + /wHbAZYBEgH/DAAB2wGWARIB/wgAA0YBfwIBARQB/wKAAc4B/wKAAdEB/wKAAdIB/wKAAdIB/wKAAdIB + /wP1Af8D9QH/AoAB0gH/AoAB0gH/AoAB0gH/AoAB0gH/AoAB0QH/AoABzgH/AjkBugH/BAAB2wGWARIB /wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsB - lgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wQAAdsBlgESAf8UAAHbAZYBEgH/BAAB - 2wGWARIB/xAAAdsBlgESAf8QAAJbAVkBwAJpAV0B6AMYASADGAEgAxgBIAMYASADGAEgAxgBIAMYASAB - 2wGWARIB/wMqAUAYAAHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/A1YBsAgAAdsBlgESAf8B - 2wGWARIB/wHbAZYBEgH/GAAB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsB - lgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8QAAHbAZYBEgH/AdsBlgESAf8QAAHbAZYBEgH/BAAB - 2wGWARIB/xAAAdsBlgESAf8UAAHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B - 2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/yAAAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsB - lgESAf8B2wGWARIB/ygAAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYB - EgH/AdsBlgESAf8B2wGWARIB/xgAAdsBlgESAf8B2wGWARIB/wMqAUAB2wGWARIB/wHbAZYBEgH/CAAB - 2wGWARIB/wHbAZYBEgH/AWQBYQFbAdgCWwFZAcAB2wGWARIB/wHbAZYBEgH/YAAB2wGWARIB/wHbAZYB - EgH/AdsBlgESAf8B2wGWARIB/yQAAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/DAAB2wGWARIB/wHbAZYB - EgH/AdsBlgESAf8YAAJHAUYBgAFpAWMBWgHfAlgBVgGzFAABaQFjAVoB3wFpAWMBWgHfAwYBCKAAAxgB - IAwAAlEBUAGfVAABQgFNAT4HAAE+AwABKAMAAUADAAEwAwABAQEAAQEFAAGAAQEWAAP/gQAB/wHyAQAB - gAH4AQ8BwAEBAf8BwAEAAYAB4AEDAYABAAH/AewBAAGAAcMB4wGAAQAB+AHMAQABgAHPAfkCAAH4AcAB - AAGAAY8B+QIAAdABQwEAAYABnwH8AgABgAEPAoABnwH8AgABAgEHAv8BnwH8AgABhwEPAoABnwH4AgAB - zwGfAQABgAHOATkCAAGHAQ8BAAGAAcYBMQIAAYIBBwEAAYAB7gE7AgABgAEPAQABgAH+AT8CAAHQAX8B - AAGAAf4BPwGAAQAB+AH/AQABgAH+AT8BgAEBAfgB/wGBAcAB/gE/AeABBwL/Af4BHwHxAccB4wHnA/8B - BwHwAQ8BwQGBAeABAwHxAYMB8AEHAZwBvQHvAfMB4QHBAYABAAG+Ab0B7wHzAcEB4QGDAcABvgG9AewB - EwHDAeIBBwHgAd4BvQHvAfMBxwH+AccB8QHAAYMB7AEDAc8B/AHHAfEC/wHvAfMB3wH4AccB8QHAAYMB - 7AFzAfMB8AGHAfAB3gG5AewBcwHhAeABAwHgAb4BvQHvAfMB4AHBAYABAAG+Ab0B4AEDAfABYwHwAQcB - ngG9AfABBwH4AT8B8AEPAcEBgQL/Af4BHwHxAccC4wT/Af0B3wL/Cw== + lgESAf8B2wGWARIB/wHbAZYBEgH/EAAB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYB + EgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/BAAB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB + /wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/xwAAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/GAADKgE/AgQB + TwH/ApYBvwH/ApcB1gH/ApcB1gH/ApcB1gH/ApcB1gH/ApcB1gH/A/UB/wKXAdYB/wKXAdYB/wKXAdYB + /wKXAdYB/wKXAdYB/wKXAdMB/wIKAXcB/wgAAzIBTwQAA1oBvwHbAZYBEgH/AdsBlgESAf8B2wGWARIB + /wNaAb8cAAHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYB + EgH/AdsBlgESAf8EAAHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB + /wHbAZYBEgH/HAAB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8cAAIIAWgB/wJXAWkB/wKuAcYB/wKvAd4B + /wKvAd4B/wKvAd4B/wKvAd4B/wKvAd4B/wKvAd4B/wKvAd4B/wKvAd4B/wKvAd4B/wKvAd4B/wIIAXUB + /wIKAW4B/xQAAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/IAADWAG4AdsBlgESAf8B2wGWARIB/wHbAZYB + EgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8EAAHbAZYBEgH/AdsBlgESAf8B2wGWARIB + /wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/HAAB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8c + AAMhAS8CCAFmAf8CCAFrAf8CCAFuAf8CCAFuAf8CCAFuAf8CCAFuAf8CCAFuAf8CCAFuAf8CCAFuAf8C + CAFuAf8CCAFuAf8CCAFqAf8CEAF+Af8YAAHbAZYBEgH/AdsBlgESAf8B2wGWARIB/yQAAloBWAG3AdsB + lgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wwAAdsBlgESAf8B2wGWARIB/wHbAZYB + EgH/AdsBlgESAf8B2wGWARIB/wNJAYccAAHbAZYBEgH/AdsBlgESAf8B2wGWARIB/yQAAyoBPwNGAX8D + RgF/A0YBfwNGAX8DRgF/A0YBfwNGAX8DRgF/A0YBf2gAAmIBXQHcAdsBlgESAf8B2wGWARIB/wM9AWgk + AAHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wwAAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/GAADDAEQAkcB + RgGAAy0BRBQAAzoBYAM6AWBsAAHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8c + AAHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsB + lgESAf8YAAHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8IAAJbAVkBwAHbAZYB + EgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8QAAJbAVkBwAHbAZYBEgH/AdsBlgESAf8B + 2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wMqAUAY + AAHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wgAAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B + 2wGWARIB/xgAAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsB + lgESAf8B2wGWARIB/wJ8AVMB+BAAAXkBbwFRAfcB2wGWARIB/wwAAzMBUAHbAZYBEgH/BAAB2wGWARIB + /xAAAdsBlgESAf8QAAJbAVkBwCAAAkcBRgGAAyoBQBQAAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsB + lgESAf8MAAHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8IAAHbAZYBEgH/AdsB + lgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB + /wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wQAAdsBlgESAf8U + AAHbAZYBEgH/BAAB2wGWARIB/xAAAdsBlgESAf8QAAJbAVkBwCAAAkcBRgGAAyoBQBAAAdsBlgESAf8B + 2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/xAAAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsB + lgESAf8IAAHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8QAAMSARgB2wGWARIB + /wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/BAAB2wGWARIB/xQAAdsBlgESAf8EAAHbAZYB + EgH/EAAB2wGWARIB/xAAAlsBWQHACAAB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYB + EgH/BAACRwFGAYADKgFAEAAB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/xQAAz0BaAHbAZYB + EgH/AdsBlgESAf8EAAHbAZYBEgH/AzoBYAHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/GAAD + GAEgAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8IAAHbAZYBEgH/EAAB2wGWARIB/wQAAdsB + lgESAf8QAAHbAZYBEgH/EAACWwFZAcAgAAJHAUYBgAMqAUAQAAHbAZYBEgH/AdsBlgESAf8B2wGWARIB + /ygAAdsBlgESAf8IAAHbAZYBEgH/AdsBlgESAf8B2wGWARIB/xwAAdsBlgESAf8B2wGWARIB/wHbAZYB + EgH/DAACZQFcAecB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/BAAB2wGWARIB + /wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/FAACWwFZAcAIAAHbAZYBEgH/AdsBlgESAf8B + 2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wJHAUYBgAMqAUAQAAHbAZYBEgH/Am8BUQH3KAAB + 2wGWARIB/wHbAZYBEgH/CAAB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8cAAHbAZYBEgH/AdsBlgESAf8B + 2wGWARIB/1AAAlsBWQHAIAACRwFGAYADKgFAEAAB2wGWARIB/ygAAdsBlgESAf8B2wGWARIB/wHbAZYB + EgH/CAAB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8cAAHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wwAAkEB + QAFwAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wQAAdsBlgESAf8B2wGWARIB + /wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/xQAAlsBWQHACAACWwFZAcACWwFZAcACWwFZAcAMAAJHAUYB + gAMqAUAYAAHbAZYBEgH/AdsBlgESAf8YAAHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/BAAB + 2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/xwAAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsB + lgESAf8IAAHbAZYBEgH/EAAB2wGWARIB/wQAAdsBlgESAf8MAAMqAUAB2wGWARIB/xAAAlsBWQHACAAD + OgFgAzoBYAM6AWAMAAJHAUYBgAMqAUAUAAHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/EAAB + 2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wMMARADBgEIAdsBlgESAf8B2wGWARIB/wHbAZYB + EgH/AdsBlgESAf8B2wGWARIB/xQAAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB + /wQAAdsBlgESAf8UAAHbAZYBEgH/BAAB2wGWARIB/xAAAdsBlgESAf8QAAJbAVkBwCAAAkcBRgGAAyoB + QBQAAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wgAAdsBlgESAf8B2wGWARIB + /wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wgAAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B + 2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYB + EgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/BAAB2wGWARIB/xQAAdsBlgESAf8EAAHbAZYBEgH/EAAB + 2wGWARIB/xAAAlsBWQHAAmkBYAHoAxgBIAMYASADGAEgAxgBIAMYASADGAEgAxgBIAHbAZYBEgH/AyoB + QBgAAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8DVgGwCAAB2wGWARIB/wHbAZYBEgH/AdsB + lgESAf8YAAHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYB + EgH/AdsBlgESAf8B2wGWARIB/xAAAdsBlgESAf8B2wGWARIB/xAAAdsBlgESAf8EAAHbAZYBEgH/EAAB + 2wGWARIB/xQAAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsB + lgESAf8B2wGWARIB/wHbAZYBEgH/IAAB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYB + EgH/KAAB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B2wGWARIB + /wHbAZYBEgH/GAAB2wGWARIB/wHbAZYBEgH/AyoBQAHbAZYBEgH/AdsBlgESAf8IAAHbAZYBEgH/AdsB + lgESAf8CXwFbAdgCWwFZAcAB2wGWARIB/wHbAZYBEgH/YAAB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8B + 2wGWARIB/yQAAdsBlgESAf8B2wGWARIB/wHbAZYBEgH/DAAB2wGWARIB/wHbAZYBEgH/AdsBlgESAf8Y + AAJHAUYBgAJjAV0B3wJYAVYBsxQAAmMBXQHfAmMBXQHfAwYBCKAAAxgBIAwAAlEBUAGfVAABQgFNAT4H + AAE+AwABKAMAAUADAAEwAwABAQEAAQEFAAGAAQEWAAP/gQAB/wHyAQABgAH4AQ8BwAEBAf8BwAEAAYAB + 4AEDAYABAAH/AewBAAGAAcMB4wGAAQAB+AHMAQABgAHPAfkCAAH4AcABAAGAAY8B+QIAAdABQwEAAYAB + nwH8AgABgAEPAoABnwH8AgABAgEHAv8BnwH8AgABhwEPAoABnwH4AgABzwGfAQABgAHOATkCAAGHAQ8B + AAGAAcYBMQIAAYIBBwEAAYAB7gE7AgABgAEPAQABgAH+AT8CAAHQAX8BAAGAAf4BPwGAAQAB+AH/AQAB + gAH+AT8BgAEBAfgB/wGBAcAB/gE/AeABBwL/Af4BHwHxAccB4wHnA/8BBwHwAQ8BwQGBAeABAwHxAYMB + 8AEHAZwBvQHvAfMB4QHBAYABAAG+Ab0B7wHzAcEB4QGDAcABvgG9AewBEwHDAeIBBwHgAd4BvQHvAfMB + xwH+AccB8QHAAYMB7AEDAc8B/AHHAfEC/wHvAfMB3wH4AccB8QHAAYMB7AFzAfMB8AGHAfAB3gG5AewB + cwHhAeABAwHgAb4BvQHvAfMB4AHBAYABAAG+Ab0B4AEDAfABYwHwAQcBngG9AfABBwH4AT8B8AEPAcEB + gQL/Af4BHwHxAccC4wT/Af0B3wL/Cw== - - 134, 17 - diff --git a/FATrace.OEMApp/Model/Jellyfin/JellyfinModels.cs b/FATrace.OEMApp/Model/Jellyfin/JellyfinModels.cs new file mode 100644 index 0000000..72d6a19 --- /dev/null +++ b/FATrace.OEMApp/Model/Jellyfin/JellyfinModels.cs @@ -0,0 +1,65 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace FATrace.OEMApp.Model.Jellyfin +{ + /// + /// Jellyfin /Items 接口响应模型 + /// + public sealed class JellyfinItemsResponse + { + /// + /// 返回的项目集合 + /// + [JsonPropertyName("Items")] + public List Items { get; set; } = new(); + + /// + /// 记录总数 + /// + [JsonPropertyName("TotalRecordCount")] + public int TotalRecordCount { get; set; } + + /// + /// 起始索引 + /// + [JsonPropertyName("StartIndex")] + public int StartIndex { get; set; } + } + + /// + /// Jellyfin 媒体条目最小字段模型 + /// + public sealed class JellyfinItem + { + /// + /// 名称 + /// + [JsonPropertyName("Name")] + public string? Name { get; set; } + + /// + /// 服务器 ID + /// + [JsonPropertyName("ServerId")] + public string? ServerId { get; set; } + + /// + /// 项目 ID + /// + [JsonPropertyName("Id")] + public string? Id { get; set; } + + /// + /// 频道 ID(可能为 null) + /// + [JsonPropertyName("ChannelId")] + public string? ChannelId { get; set; } + + /// + /// 类型,如 Movie、Series + /// + [JsonPropertyName("Type")] + public string? Type { get; set; } + } +} diff --git a/FATrace.OEMApp/Services/JellyfinClient.cs b/FATrace.OEMApp/Services/JellyfinClient.cs new file mode 100644 index 0000000..880a882 --- /dev/null +++ b/FATrace.OEMApp/Services/JellyfinClient.cs @@ -0,0 +1,245 @@ +using System; +using System.Diagnostics; +using System.Net; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using FATrace.OEMApp.Model.Jellyfin; +using NLog; +using RestSharp; + +namespace FATrace.OEMApp.Services +{ + /// + /// Jellyfin 客户端配置选项 + /// + public sealed class JellyfinClientOptions + { + /// + /// Jellyfin 基础地址,例如:http://192.168.0.30:8096 + /// + public required string BaseUrl { get; init; } + + /// + /// API 密钥(建议使用 Jellyfin 后台创建的密钥)。 + /// + public required string ApiKey { get; init; } + + /// + /// 超时时间,毫秒。默认 10000 毫秒。 + /// + public int TimeoutMs { get; init; } = 10000; + + /// + /// 是否将 api_key 作为查询参数传递。默认 true。 + /// 若为 false,则通过请求头 X-Emby-Token 传递。 + /// + public bool UseQueryApiKey { get; init; } = true; + + /// + /// 自定义 User-Agent。若为空将使用默认。 + /// + public string? UserAgent { get; init; } + } + + /// + /// Jellyfin HTTP 异常 + /// + public sealed class JellyfinHttpException : Exception + { + public HttpStatusCode StatusCode { get; } + public string? ResponseContent { get; } + + public JellyfinHttpException(string message, HttpStatusCode statusCode, string? content, Exception? inner = null) + : base(message, inner) + { + StatusCode = statusCode; + ResponseContent = content; + } + } + + /// + /// Jellyfin API 客户端封装(基于 RestSharp) + /// + public sealed class JellyfinClient : IJellyfinClient, IDisposable + { + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + + private readonly RestClient _client; + private readonly JellyfinClientOptions _options; + private readonly JsonSerializerOptions _jsonOptions = new() + { + PropertyNameCaseInsensitive = true + }; + + public JellyfinClient(JellyfinClientOptions options) + { + _options = options ?? throw new ArgumentNullException(nameof(options)); + if (string.IsNullOrWhiteSpace(_options.BaseUrl)) + throw new ArgumentException("BaseUrl 不能为空", nameof(options)); + if (string.IsNullOrWhiteSpace(_options.ApiKey)) + throw new ArgumentException("ApiKey 不能为空", nameof(options)); + + if (!Uri.TryCreate(_options.BaseUrl, UriKind.Absolute, out var baseUri)) + { + throw new ArgumentException($"无效的 BaseUrl: {_options.BaseUrl}", nameof(options)); + } + + var restOptions = new RestClientOptions(baseUri) + { + MaxTimeout = _options.TimeoutMs, + ThrowOnAnyError = false, + // UseUnsafeSerializer = false // 保持默认 System.Text.Json + }; + + _client = new RestClient(restOptions); + + // 默认头 + _client.AddDefaultHeader("Accept", "application/json"); + _client.AddDefaultHeader("Accept-Charset", "utf-8"); + if (!string.IsNullOrWhiteSpace(_options.UserAgent)) + { + _client.AddDefaultHeader("User-Agent", _options.UserAgent); + } + + // 通过 Header 传递 Token(若未使用 Query) + if (!_options.UseQueryApiKey) + { + _client.AddDefaultHeader("X-Emby-Token", _options.ApiKey); + } + } + + /// + /// 获取 Jellyfin Items 列表。 + /// + /// 父级目录/库 ID + /// 排序字段(默认 DateCreated) + /// 排序方向(Ascending/Descending,默认 Descending) + /// 限制返回数量,默认 10 + /// 包含的类型(例如 Movie, Series),默认 Movie + /// 取消令牌 + public async Task GetItemsAsync( + string parentId, + string sortBy = "DateCreated", + string sortOrder = "Descending", + int? limit = 10, + string includeItemTypes = "Movie", + CancellationToken cancellationToken = default) + { + if (string.IsNullOrWhiteSpace(parentId)) + throw new ArgumentException("parentId 不能为空", nameof(parentId)); + + var request = new RestRequest("Items", Method.Get); + request.AddQueryParameter("ParentId", parentId); + if (!string.IsNullOrWhiteSpace(sortBy)) request.AddQueryParameter("SortBy", sortBy); + if (!string.IsNullOrWhiteSpace(sortOrder)) request.AddQueryParameter("SortOrder", sortOrder); + if (limit.HasValue && limit.Value > 0) request.AddQueryParameter("Limit", limit.Value.ToString()); + if (!string.IsNullOrWhiteSpace(includeItemTypes)) request.AddQueryParameter("IncludeItemTypes", includeItemTypes); + + if (_options.UseQueryApiKey) + { + request.AddQueryParameter("api_key", _options.ApiKey); + } + + return await ExecuteAndDeserializeAsync(request, cancellationToken).ConfigureAwait(false); + } + + /// + /// 仅返回 Items 列表的便捷方法。 + /// + public async Task> GetItemsOnlyAsync( + string parentId, + string sortBy = "DateCreated", + string sortOrder = "Descending", + int? limit = 10, + string includeItemTypes = "Movie", + CancellationToken cancellationToken = default) + { + var resp = await GetItemsAsync(parentId, sortBy, sortOrder, limit, includeItemTypes, cancellationToken).ConfigureAwait(false); + return resp.Items ?? new System.Collections.Generic.List(); + } + + private async Task ExecuteAndDeserializeAsync(RestRequest request, CancellationToken ct) + { + var sw = Stopwatch.StartNew(); + try + { + Logger.Info("Jellyfin 请求: {Method} {Resource}", request.Method, request.Resource); + + var response = await _client.ExecuteAsync(request, ct).ConfigureAwait(false); + sw.Stop(); + + if (!response.IsSuccessful) + { + Logger.Warn("Jellyfin 响应失败: Status={StatusCode}, Elapsed={Elapsed}ms, ContentLength={Length}", + (int)response.StatusCode, sw.ElapsedMilliseconds, response.Content?.Length ?? 0); + + throw new JellyfinHttpException( + $"请求失败,HTTP {(int)response.StatusCode} {response.StatusDescription}", + response.StatusCode, + response.Content, + response.ErrorException); + } + + if (string.IsNullOrWhiteSpace(response.Content)) + { + throw new JellyfinHttpException("响应内容为空", response.StatusCode, null); + } + + var result = JsonSerializer.Deserialize(response.Content, _jsonOptions); + if (result == null) + { + throw new JellyfinHttpException("响应反序列化失败,返回结果为 null", response.StatusCode, response.Content); + } + + Logger.Debug("Jellyfin 调用成功: {Method} {Resource}, Elapsed={Elapsed}ms", request.Method, request.Resource, sw.ElapsedMilliseconds); + return result; + } + catch (OperationCanceledException) + { + sw.Stop(); + Logger.Warn("Jellyfin 请求被取消: {Resource}, Elapsed={Elapsed}ms", request.Resource, sw.ElapsedMilliseconds); + throw; + } + catch (JellyfinHttpException) + { + // 已包含上下文,直接抛出 + throw; + } + catch (Exception ex) + { + sw.Stop(); + Logger.Error(ex, "Jellyfin 请求异常: {Resource}, Elapsed={Elapsed}ms", request.Resource, sw.ElapsedMilliseconds); + throw new JellyfinHttpException("请求执行异常", HttpStatusCode.InternalServerError, null, ex); + } + } + + public void Dispose() + { + // RestClient 在新版本基于 HttpClient,通常无需显式释放。 + // 如需自定义 HttpMessageHandler,可在此释放。 + } + } + + /// + /// Jellyfin 客户端接口,便于后续依赖注入与单元测试。 + /// + public interface IJellyfinClient + { + Task GetItemsAsync( + string parentId, + string sortBy = "DateCreated", + string sortOrder = "Descending", + int? limit = 10, + string includeItemTypes = "Movie", + CancellationToken cancellationToken = default); + + Task> GetItemsOnlyAsync( + string parentId, + string sortBy = "DateCreated", + string sortOrder = "Descending", + int? limit = 10, + string includeItemTypes = "Movie", + CancellationToken cancellationToken = default); + } +}