diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..00dc79c --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +# http://editorconfig.org/ + +# top-most EditorConfig file +root = true + +# all files +[*] +indent_style = space +indent_size = 4 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 8f1c07b..1003e6c 100644 --- a/.gitignore +++ b/.gitignore @@ -261,5 +261,5 @@ paket-files/ __pycache__/ *.pyc -BililiveRecorder.WPF/BuildInfo.cs +**/BuildInfo.cs BililiveRecorder.WPF/Nlog.config diff --git a/BililiveRecorder.Core/BililiveAPI.cs b/BililiveRecorder.Core/BililiveAPI.cs index f64bef4..12f87a0 100644 --- a/BililiveRecorder.Core/BililiveAPI.cs +++ b/BililiveRecorder.Core/BililiveAPI.cs @@ -1,7 +1,6 @@ using Newtonsoft.Json.Linq; using NLog; using System; -using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; @@ -134,11 +133,11 @@ namespace BililiveRecorder.Core var withoutTxy = all.Where(x => !x.Contains("txy.")).ToArray(); if (withoutTxy.Length > 0) { - return withoutTxy[random.Next(0, withoutTxy.Length - 1)]; + return withoutTxy[random.Next(withoutTxy.Length)]; } else if (attempt_left <= 0) { - return all[random.Next(0, all.Length - 1)]; + return all[random.Next(all.Length)]; } } else @@ -152,15 +151,11 @@ namespace BililiveRecorder.Core // 随机选择一个 url if ((await HttpGetJsonAsync(url))?["data"]?["durl"] is JArray array) { - List urls = new List(); - for (int i = 0; i < array.Count; i++) - { - urls.Add(array[i]?["url"]?.ToObject()); - } + var urls = array.Select(t => t?["url"]?.ToObject()); var distinct = urls.Distinct().ToArray(); if (distinct.Length > 0) { - return distinct[random.Next(0, distinct.Count() - 1)]; + return distinct[random.Next(distinct.Length)]; } } throw new Exception("没有直播播放地址"); diff --git a/BililiveRecorder.Core/BililiveRecorder.Core.csproj b/BililiveRecorder.Core/BililiveRecorder.Core.csproj index a5f8668..01fb70d 100644 --- a/BililiveRecorder.Core/BililiveRecorder.Core.csproj +++ b/BililiveRecorder.Core/BililiveRecorder.Core.csproj @@ -16,6 +16,9 @@ none false + + + @@ -24,4 +27,8 @@ + + cd $(SolutionDir) +powershell -ExecutionPolicy Bypass -File .\CI\patch_buildinfo.ps1 Core + \ No newline at end of file diff --git a/BililiveRecorder.Core/Config/ConfigV1.cs b/BililiveRecorder.Core/Config/ConfigV1.cs index f3938d5..8be1397 100644 --- a/BililiveRecorder.Core/Config/ConfigV1.cs +++ b/BililiveRecorder.Core/Config/ConfigV1.cs @@ -86,12 +86,6 @@ namespace BililiveRecorder.Core.Config [JsonProperty("timing_watchdog_timeout")] public uint TimingWatchdogTimeout { get => _timingWatchdogTimeout; set => SetField(ref _timingWatchdogTimeout, value); } - /// - /// 最大直播数据落后时间 毫秒 - /// - [JsonProperty("timing_watchdog_behind")] - public uint TimingWatchdogBehind { get => _timingWatchdogBehind; set => SetField(ref _timingWatchdogBehind, value); } - /// /// 请求 API 时使用的 Cookie /// @@ -123,7 +117,6 @@ namespace BililiveRecorder.Core.Config private string _workDirectory; private uint _timingWatchdogTimeout = 10 * 1000; - private uint _timingWatchdogBehind = 10 * 1000; private uint _timingStreamRetry = 6 * 1000; private uint _timingStreamConnect = 3 * 1000; private uint _timingDanmakuRetry = 2 * 1000; diff --git a/BililiveRecorder.Core/IRecordedRoom.cs b/BililiveRecorder.Core/IRecordedRoom.cs index 7b0e5ec..d89d0a8 100644 --- a/BililiveRecorder.Core/IRecordedRoom.cs +++ b/BililiveRecorder.Core/IRecordedRoom.cs @@ -17,7 +17,7 @@ namespace BililiveRecorder.Core bool IsRecording { get; } double DownloadSpeedPersentage { get; } - double DownloadSpeedKiBps { get; } + double DownloadSpeedMegaBitps { get; } DateTime LastUpdateDateTime { get; } void Clip(); diff --git a/BililiveRecorder.Core/RecordedRoom.cs b/BililiveRecorder.Core/RecordedRoom.cs index bdd3d22..e85f2ce 100644 --- a/BililiveRecorder.Core/RecordedRoom.cs +++ b/BililiveRecorder.Core/RecordedRoom.cs @@ -82,7 +82,7 @@ namespace BililiveRecorder.Core public CancellationTokenSource cancellationTokenSource = null; private double _DownloadSpeedPersentage = 0; - private double _DownloadSpeedKiBps = 0; + private double _DownloadSpeedMegaBitps = 0; private long _lastUpdateSize = 0; private int _lastUpdateTimestamp = 0; public DateTime LastUpdateDateTime { get; private set; } = DateTime.Now; @@ -91,10 +91,10 @@ namespace BililiveRecorder.Core get { return _DownloadSpeedPersentage; } private set { if (value != _DownloadSpeedPersentage) { _DownloadSpeedPersentage = value; TriggerPropertyChanged(nameof(DownloadSpeedPersentage)); } } } - public double DownloadSpeedKiBps + public double DownloadSpeedMegaBitps { - get { return _DownloadSpeedKiBps; } - private set { if (value != _DownloadSpeedKiBps) { _DownloadSpeedKiBps = value; TriggerPropertyChanged(nameof(DownloadSpeedKiBps)); } } + get { return _DownloadSpeedMegaBitps; } + private set { if (value != _DownloadSpeedMegaBitps) { _DownloadSpeedMegaBitps = value; TriggerPropertyChanged(nameof(DownloadSpeedMegaBitps)); } } } public RecordedRoom(ConfigV1 config, @@ -192,7 +192,7 @@ namespace BililiveRecorder.Core cancellationTokenSource.Cancel(); if (!(StreamDownloadTask?.Wait(TimeSpan.FromSeconds(2)) ?? true)) { - logger.Log(RoomId, LogLevel.Warn, "尝试强制关闭连接,请检查网络连接是否稳定"); + logger.Log(RoomId, LogLevel.Warn, "停止录制超时,尝试强制关闭连接,请检查网络连接是否稳定"); _stream?.Close(); _stream?.Dispose(); @@ -283,7 +283,7 @@ namespace BililiveRecorder.Core }, { "version", - "TEST" + BuildInfo.Version + " " + BuildInfo.HeadShaShort }, { "roomid", @@ -351,8 +351,8 @@ namespace BililiveRecorder.Core } logger.Log(RoomId, LogLevel.Info, - (token.IsCancellationRequested ? "用户操作" : "直播已结束") + ",停止录制。" - + (_retry ? "将重试启动。" : "")); + (token.IsCancellationRequested ? "本地操作结束当前录制。" : "服务器关闭直播流,可能是直播已结束。") + + (_retry ? "将重试启动。" : "")); if (_retry) { StreamMonitor.Check(TriggerType.HttpApiRecheck, (int)_config.TimingStreamRetry); @@ -383,7 +383,7 @@ namespace BililiveRecorder.Core _response = null; _lastUpdateTimestamp = 0; - DownloadSpeedKiBps = 0d; + DownloadSpeedMegaBitps = 0d; DownloadSpeedPersentage = 0d; TriggerPropertyChanged(nameof(IsRecording)); } @@ -394,7 +394,7 @@ namespace BililiveRecorder.Core _lastUpdateSize += bytesRead; if (passedSeconds > 1.5) { - DownloadSpeedKiBps = _lastUpdateSize / passedSeconds / 1024; // KiB per sec + DownloadSpeedMegaBitps = _lastUpdateSize / passedSeconds * 8d / 1_000_000d; // mega bit per second DownloadSpeedPersentage = (DownloadSpeedPersentage / 2) + ((Processor.TotalMaxTimestamp - _lastUpdateTimestamp) / passedSeconds / 1000 / 2); // ((RecordedTime/1000) / RealTime)% _lastUpdateTimestamp = Processor.TotalMaxTimestamp; _lastUpdateSize = 0; diff --git a/BililiveRecorder.Core/Recorder.cs b/BililiveRecorder.Core/Recorder.cs index f495d23..4a41302 100644 --- a/BililiveRecorder.Core/Recorder.cs +++ b/BililiveRecorder.Core/Recorder.cs @@ -213,17 +213,7 @@ namespace BililiveRecorder.Core { if (DateTime.Now - room.LastUpdateDateTime > TimeSpan.FromMilliseconds(Config.TimingWatchdogTimeout)) { - logger.Warn("服务器停止提供 [{roomid}] 直播间的直播数据,通常是录制时网络不稳定导致,将会断开重连", room.RoomId); - room.StopRecord(); - room.StartRecord(); - } - else if (room.Processor != null && - ((DateTime.Now - room.Processor.StartDateTime).TotalMilliseconds - > - (room.Processor.TotalMaxTimestamp + Config.TimingWatchdogBehind)) - ) - { - logger.Warn("直播间 [{roomid}] 的下载速度达不到录制标准,将断开重连。请检查网络是否稳定", room.RoomId); + logger.Warn("服务器未断开连接但停止提供 [{roomid}] 直播间的直播数据,通常是录制侧网络不稳定导致,将会断开重连", room.RoomId); room.StopRecord(); room.StartRecord(); } diff --git a/BililiveRecorder.FlvProcessor/BililiveRecorder.FlvProcessor.csproj b/BililiveRecorder.FlvProcessor/BililiveRecorder.FlvProcessor.csproj index e475f58..48b2a6d 100644 --- a/BililiveRecorder.FlvProcessor/BililiveRecorder.FlvProcessor.csproj +++ b/BililiveRecorder.FlvProcessor/BililiveRecorder.FlvProcessor.csproj @@ -16,8 +16,15 @@ none false + + + + + cd $(SolutionDir) +powershell -ExecutionPolicy Bypass -File .\CI\patch_buildinfo.ps1 FlvProcessor + \ No newline at end of file diff --git a/BililiveRecorder.FlvProcessor/FlvStreamProcessor.cs b/BililiveRecorder.FlvProcessor/FlvStreamProcessor.cs index 46a7c1e..c141695 100644 --- a/BililiveRecorder.FlvProcessor/FlvStreamProcessor.cs +++ b/BililiveRecorder.FlvProcessor/FlvStreamProcessor.cs @@ -249,7 +249,7 @@ namespace BililiveRecorder.FlvProcessor { if (_hasOffset) { - tag.SetTimeStamp(Math.Max(0, tag.TimeStamp - _baseTimeStamp)); // 修复时间戳 + tag.SetTimeStamp(tag.TimeStamp - _baseTimeStamp); TotalMaxTimestamp = Math.Max(TotalMaxTimestamp, tag.TimeStamp); } else diff --git a/BililiveRecorder.FlvProcessor/FlvTag.cs b/BililiveRecorder.FlvProcessor/FlvTag.cs index 99e3691..015d8d7 100644 --- a/BililiveRecorder.FlvProcessor/FlvTag.cs +++ b/BililiveRecorder.FlvProcessor/FlvTag.cs @@ -77,8 +77,7 @@ namespace BililiveRecorder.FlvProcessor var size = BitConverter.GetBytes(useDataSize ? Data.Length : TagSize).ToBE(); Buffer.BlockCopy(size, 1, tag, 1, 3); - byte[] timing = new byte[4]; - Buffer.BlockCopy(BitConverter.GetBytes(Math.Max(0, TimeStamp - offset)).ToBE(), 0, timing, 0, timing.Length); + byte[] timing = BitConverter.GetBytes(TimeStamp - offset).ToBE(); Buffer.BlockCopy(timing, 1, tag, 4, 3); Buffer.BlockCopy(timing, 0, tag, 7, 1); diff --git a/BililiveRecorder.WPF/BililiveRecorder.WPF.csproj b/BililiveRecorder.WPF/BililiveRecorder.WPF.csproj index 5eaf67d..d69ca3c 100644 --- a/BililiveRecorder.WPF/BililiveRecorder.WPF.csproj +++ b/BililiveRecorder.WPF/BililiveRecorder.WPF.csproj @@ -196,7 +196,6 @@ ResXFileCodeGenerator Resources.Designer.cs - @@ -225,7 +224,7 @@ cd $(SolutionDir) -PowerShell -ExecutionPolicy Bypass -File .\CI\patch_buildinfo.ps1 +powershell -ExecutionPolicy Bypass -File .\CI\patch_buildinfo.ps1 WPF copy /y .\BililiveRecorder.WPF\Nlog.$(ConfigurationName).config .\BililiveRecorder.WPF\NLog.config diff --git a/BililiveRecorder.WPF/MainWindow.xaml b/BililiveRecorder.WPF/MainWindow.xaml index d138a56..ec319b8 100644 --- a/BililiveRecorder.WPF/MainWindow.xaml +++ b/BililiveRecorder.WPF/MainWindow.xaml @@ -95,7 +95,7 @@ - + @@ -156,8 +156,8 @@ 处理中剪辑数量: - 录制下载速度: - + 实时下载速度: + 录制速度比: diff --git a/BililiveRecorder.WPF/MainWindow.xaml.cs b/BililiveRecorder.WPF/MainWindow.xaml.cs index 8cad8fb..78ca829 100644 --- a/BililiveRecorder.WPF/MainWindow.xaml.cs +++ b/BililiveRecorder.WPF/MainWindow.xaml.cs @@ -39,7 +39,10 @@ namespace BililiveRecorder.WPF "问题反馈邮箱: rec@danmuji.org", "QQ群: 689636812", "", - "功能调整:删除直播间按钮调整了位置,从软件界面右侧移动到了列表右键菜单" + "删除直播间按钮在列表右键菜单里", + "", + "录制速度比 在 100% 左右说明跟上了主播直播的速度", + "小于 100% 说明录播电脑的下载带宽不够,跟不上录制直播" }; public static void AddLog(string message) => _AddLog?.Invoke(message); diff --git a/BililiveRecorder.WPF/SettingsWindow.xaml b/BililiveRecorder.WPF/SettingsWindow.xaml index 3a74a0b..05cdd80 100644 --- a/BililiveRecorder.WPF/SettingsWindow.xaml +++ b/BililiveRecorder.WPF/SettingsWindow.xaml @@ -59,7 +59,7 @@ - 录制重连间隔: + 录制重试间隔: @@ -130,7 +130,7 @@ 毫秒 - 录制超时时间: + 接收数据超时: @@ -154,31 +154,6 @@ 毫秒 - 录制最长落后: - - - - - - - - - - - - - 注: - 录制比主播直播落后一定时间后自动断开重连。 - 实际使用中如果发生了因为跟不上主播直播 - 的情况,说明录制电脑的网速不够。 - - - - - 毫秒 - 开播检查间隔: diff --git a/BililiveRecorder.WPF/BuildInfo.txt b/BuildInfo.txt similarity index 85% rename from BililiveRecorder.WPF/BuildInfo.txt rename to BuildInfo.txt index d0e504f..71c021a 100644 --- a/BililiveRecorder.WPF/BuildInfo.txt +++ b/BuildInfo.txt @@ -1,4 +1,4 @@ -namespace BililiveRecorder.WPF +namespace BililiveRecorder.[PROJECT_NAME] { internal class BuildInfo { diff --git a/CI/patch_buildinfo.ps1 b/CI/patch_buildinfo.ps1 index a5c7443..6475d5b 100644 --- a/CI/patch_buildinfo.ps1 +++ b/CI/patch_buildinfo.ps1 @@ -4,4 +4,6 @@ $isAppveyor = if ($env:APPVEYOR -eq $null) { "false" } else { $env:APPVEYOR } $buildversion = if ($env:APPVEYOR_BUILD_VERSION -eq $null) { "本地编译" } else { $env:APPVEYOR_BUILD_VERSION } $githash = git rev-parse --verify HEAD -(Get-Content .\BililiveRecorder.WPF\BuildInfo.txt).Replace('[APPVEYOR]', $isAppveyor.ToLower()).Replace('[VERSION]', $buildversion).Replace('[GIT_HASH]', $githash).Replace('[GIT_HASH_S]', $githash.Substring(0, 8)) | Set-Content .\BililiveRecorder.WPF\BuildInfo.cs +(Get-Content .\BuildInfo.txt).Replace('[PROJECT_NAME]', $args).Replace('[APPVEYOR]', $isAppveyor.ToLower()).Replace('[VERSION]', $buildversion).Replace('[GIT_HASH]', $githash).Replace('[GIT_HASH_S]', $githash.Substring(0, 8)) | Set-Content ".\BililiveRecorder.$args\BuildInfo.cs" + +Write-Output "BuildInfo for $args patched" diff --git a/README.md b/README.md index 4b18e9b..243527b 100644 --- a/README.md +++ b/README.md @@ -19,19 +19,16 @@ See [rec.danmuji.org](https://rec.danmuji.org) (in Chinese) ## Feature - Easy to use -- Fix timestamp automatically +- Reset timestamp to start from 0 +- Writes duration info automatically when recoding session ends. - Start recording when stream starts - Record multiple stream at same time -- Have a "Clip" Feature (just like [Twitch's](https://help.twitch.tv/customer/portal/articles/2442508-how-to-use-clips)) -- Pure C#, no native dependents +- Pure C#, no native dependency like ffmpeg - Open source! ## Develop & Getting Started -You'll need: - -- Visual Studio 2017 with .NET Core -- PowerShell +**Visual Studio 2017 with .NET Core** and **PowerShell** is required. Visual Studio 2019 *could* work but is not tested. Some file are generated by [PreComplie Script](./CI/patch_buildinfo.ps1). Build project to clear errors shown by Visual Studio. @@ -50,6 +47,6 @@ You can start poking around from... ## Reference & Acknowledgements -- [coreyauger/flv-streamer-2-file](https://github.com/coreyauger/flv-streamer-2-file): An awesome library. -- [zyzsdy/biliroku](https://github.com/zyzsdy/biliroku): First BiliBili stream record tool. -- [Video File Format Specification Version 10.pdf](https://wwwimages2.adobe.com/content/dam/acom/en/devnet/flv/video_file_format_spec_v10.pdf) +- [Adobe Flash Video File Format Specification 10.1.2.01.pdf](https://www.adobe.com/content/dam/acom/en/devnet/flv/video_file_format_spec_v10_1.pdf) +- [coreyauger/flv-streamer-2-file](https://github.com/coreyauger/flv-streamer-2-file) +- [zyzsdy/biliroku](https://github.com/zyzsdy/biliroku) - (probably) first BiliBili stream recording tool. diff --git a/README_CN.md b/README_CN.md index ae13fb9..00d65ca 100644 --- a/README_CN.md +++ b/README_CN.md @@ -13,11 +13,11 @@ ## 功能 - 使用简单 -- 自动修复视频时间戳 +- 使录出的文件时间戳从 0 开始 +- 录制结束后自动写入总时长信息 - 可以主播开播后自动录制 - 可以同时录制多个直播间 -- 可以录制“即时回放剪辑”(类似 [Twitch 的 Clip](https://help.twitch.tv/customer/portal/articles/2442508-how-to-use-clips)) -- 纯 C# 实现,无 Native 依赖 +- 纯 C# 实现,无 ffmpeg 等 native 依赖 - 开源! ## 入门 & 开发 @@ -34,7 +34,7 @@ BililiveRecorder.WPF | .NET Framework 4.6.2 BililiveRecorder.Core | .NET Standard 2.0 BililiveRecorder.FlvProcessor | .NET Standard 2.0 -BililiveRecorder.Server | .NET Core 2.0 | 预留坑,将来填 +BililiveRecorder.Server | .NET Core 2.0 | 预留坑,将来填(咕咕咕) 如果你想研究这个项目的源代码,或修改功能的话: @@ -42,8 +42,14 @@ BililiveRecorder.Server | .NET Core 2.0 | 预留坑,将来填 - 录制逻辑建议从 `BililiveRecorder.Core/Recorder.cs` 开始看 - FLV数据处理建议从 `BililiveRecorder.FlvProcessor/FlvStreamProcessor.cs` 开始看 +## Server 版说明 + +本项目核心逻辑均与 WPF 界面分离,使用 .NET Standard 2.0 而不是 .NET Framework,可以较轻松地改出可在 Linux 上运行的 .NET Core 版本,但因为本人没时间等原因一直没有做。 + +如果有有能人士有在 Linux 上运行本项目的需求的话可以自行 fork 修改(但我大概不会 merge 回来) + ## 参考资料 & 鸣谢 -- [coreyauger/flv-streamer-2-file](https://github.com/coreyauger/flv-streamer-2-file): 在FLV处理方面参考了很多 -- [zyzsdy/biliroku](https://github.com/zyzsdy/biliroku): 第一个B站直播录播工具 -- [Video File Format Specification Version 10.pdf](https://wwwimages2.adobe.com/content/dam/acom/en/devnet/flv/video_file_format_spec_v10.pdf): FLV视频文件格式规范 +- [Adobe Flash Video File Format Specification 10.1.2.01.pdf](https://www.adobe.com/content/dam/acom/en/devnet/flv/video_file_format_spec_v10_1.pdf) +- [coreyauger/flv-streamer-2-file](https://github.com/coreyauger/flv-streamer-2-file) +- [zyzsdy/biliroku](https://github.com/zyzsdy/biliroku): (大概是)第一个B站直播录播工具 diff --git a/VERSION b/VERSION index f662c7e..8d2c87f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.1.14 \ No newline at end of file +1.1.15 \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml index 5deb5b6..237b210 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -34,10 +34,10 @@ install: before_build: - nuget restore -Verbosity quiet - - msbuild /nologo /v:q /t:Clean + - msbuild /nologo /v:m /t:Clean build_script: - - ps: msbuild /nologo /v:q /p:Configuration="$env:CONFIGURATION" /p:SquirrelBuildTarget="$env:DEPLOY_SITE_GIT\BililiveRecorder" /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" + - ps: msbuild /nologo /v:m /p:Configuration="$env:CONFIGURATION" /p:SquirrelBuildTarget="$env:DEPLOY_SITE_GIT\BililiveRecorder" /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" for: -