下载音频时,可能不是mp3 可能是m4a格式,暂时就遇到这2种
ffmpeg命令优化
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
<Project>
|
<Project>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<_PublishTargetUrl>E:\code\dysync\bin\Release\net6.0\publish\</_PublishTargetUrl>
|
<_PublishTargetUrl>E:\code\dysync\bin\Release\net6.0\publish\</_PublishTargetUrl>
|
||||||
<History>True|2026-01-03T13:38:51.4307034Z||;True|2026-01-02T19:09:16.9668906+08:00||;False|2026-01-02T19:09:11.7496369+08:00||;True|2026-01-02T15:42:08.2215697+08:00||;True|2026-01-02T09:46:56.1654861+08:00||;True|2026-01-02T09:35:28.0211225+08:00||;True|2026-01-01T23:07:01.9022045+08:00||;False|2026-01-01T23:06:56.0537216+08:00||;True|2026-01-01T22:16:10.2974067+08:00||;True|2026-01-01T22:16:06.2123787+08:00||;False|2026-01-01T22:15:33.3626979+08:00||;True|2026-01-01T22:01:30.7161900+08:00||;True|2026-01-01T21:10:19.3664263+08:00||;True|2026-01-01T21:09:38.6071080+08:00||;True|2025-12-30T11:45:54.5543034+08:00||;True|2025-12-30T09:19:08.3178124+08:00||;True|2025-12-29T18:57:29.7032246+08:00||;True|2025-12-27T14:52:24.4780776+08:00||;False|2025-12-27T14:52:19.5635794+08:00||;True|2025-12-27T14:48:01.6252748+08:00||;False|2025-12-27T14:47:55.7976192+08:00||;True|2025-12-27T14:38:23.9723838+08:00||;True|2025-12-27T13:00:29.8583858+08:00||;True|2025-12-26T22:18:42.4015637+08:00||;True|2025-12-26T22:10:58.5274572+08:00||;True|2025-12-26T22:06:26.3129600+08:00||;True|2025-12-26T22:03:55.6718618+08:00||;False|2025-12-26T22:03:48.3809954+08:00||;True|2025-12-26T22:02:33.1840390+08:00||;True|2025-12-26T22:01:16.1660100+08:00||;True|2025-12-25T00:50:26.1116465+08:00||;True|2025-12-25T00:48:27.3087708+08:00||;True|2025-12-25T00:47:38.4835720+08:00||;</History>
|
<History>True|2026-01-07T16:16:24.4451490Z||;True|2026-01-08T00:14:20.7430793+08:00||;True|2026-01-08T00:07:46.7228194+08:00||;True|2026-01-07T23:52:13.3290082+08:00||;True|2026-01-07T23:49:28.4838650+08:00||;True|2026-01-07T23:47:45.1936189+08:00||;True|2026-01-07T23:35:09.7818611+08:00||;True|2026-01-07T23:20:43.7462863+08:00||;True|2026-01-07T23:04:39.5140429+08:00||;False|2026-01-07T23:04:36.5367611+08:00||;True|2026-01-07T22:53:14.6013449+08:00||;True|2026-01-07T22:49:59.9549006+08:00||;True|2026-01-07T22:36:40.0254856+08:00||;False|2026-01-07T22:36:28.8275903+08:00||;True|2026-01-07T21:59:51.4355171+08:00||;True|2026-01-03T21:38:51.4307034+08:00||;True|2026-01-02T19:09:16.9668906+08:00||;False|2026-01-02T19:09:11.7496369+08:00||;True|2026-01-02T15:42:08.2215697+08:00||;True|2026-01-02T09:46:56.1654861+08:00||;True|2026-01-02T09:35:28.0211225+08:00||;True|2026-01-01T23:07:01.9022045+08:00||;False|2026-01-01T23:06:56.0537216+08:00||;True|2026-01-01T22:16:10.2974067+08:00||;True|2026-01-01T22:16:06.2123787+08:00||;False|2026-01-01T22:15:33.3626979+08:00||;True|2026-01-01T22:01:30.7161900+08:00||;True|2026-01-01T21:10:19.3664263+08:00||;True|2026-01-01T21:09:38.6071080+08:00||;True|2025-12-30T11:45:54.5543034+08:00||;True|2025-12-30T09:19:08.3178124+08:00||;True|2025-12-29T18:57:29.7032246+08:00||;True|2025-12-27T14:52:24.4780776+08:00||;False|2025-12-27T14:52:19.5635794+08:00||;True|2025-12-27T14:48:01.6252748+08:00||;False|2025-12-27T14:47:55.7976192+08:00||;True|2025-12-27T14:38:23.9723838+08:00||;True|2025-12-27T13:00:29.8583858+08:00||;True|2025-12-26T22:18:42.4015637+08:00||;True|2025-12-26T22:10:58.5274572+08:00||;True|2025-12-26T22:06:26.3129600+08:00||;True|2025-12-26T22:03:55.6718618+08:00||;False|2025-12-26T22:03:48.3809954+08:00||;True|2025-12-26T22:02:33.1840390+08:00||;True|2025-12-26T22:01:16.1660100+08:00||;True|2025-12-25T00:50:26.1116465+08:00||;True|2025-12-25T00:48:27.3087708+08:00||;True|2025-12-25T00:47:38.4835720+08:00||;</History>
|
||||||
<LastFailureDetails />
|
<LastFailureDetails />
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
</Project>
|
</Project>
|
||||||
@@ -5,7 +5,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
|
|||||||
<Project>
|
<Project>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<_PublishTargetUrl>E:\code\dysync\bin\Release\net6.0\publish\</_PublishTargetUrl>
|
<_PublishTargetUrl>E:\code\dysync\bin\Release\net6.0\publish\</_PublishTargetUrl>
|
||||||
<History>True|2026-01-03T13:42:41.3864639Z||;False|2026-01-03T21:42:26.7041325+08:00||;True|2026-01-02T09:32:11.1396877+08:00||;True|2026-01-01T23:30:38.0205783+08:00||;True|2026-01-01T23:29:35.2877898+08:00||;False|2026-01-01T23:27:39.5930556+08:00||;False|2026-01-01T23:24:52.0996222+08:00||;False|2026-01-01T23:24:09.0444840+08:00||;False|2026-01-01T23:24:06.1610789+08:00||;False|2026-01-01T23:24:03.3178410+08:00||;False|2026-01-01T23:22:56.2224992+08:00||;False|2026-01-01T23:22:47.8246988+08:00||;False|2026-01-01T23:22:43.2890820+08:00||;True|2026-01-01T23:21:03.6027351+08:00||;True|2026-01-01T23:07:57.5938094+08:00||;True|2025-12-29T18:55:45.8262096+08:00||;True|2025-12-28T00:21:04.1648410+08:00||;True|2025-12-28T00:18:49.2137559+08:00||;True|2025-12-28T00:14:18.1456892+08:00||;True|2025-12-28T00:11:06.6645248+08:00||;False|2025-12-28T00:10:49.4906020+08:00||;True|2025-12-27T23:51:57.6426264+08:00||;True|2025-12-27T23:40:08.4783210+08:00||;True|2025-12-27T14:59:32.0605406+08:00||;False|2025-12-27T14:59:21.2076690+08:00||;True|2025-12-27T12:58:06.9934830+08:00||;True|2025-12-27T12:51:45.3402320+08:00||;True|2025-12-27T12:45:49.9795040+08:00||;True|2025-12-27T12:42:13.9395619+08:00||;True|2025-12-27T11:32:35.8851241+08:00||;True|2025-12-27T11:32:11.1199261+08:00||;True|2025-12-27T11:31:34.3939619+08:00||;True|2025-12-26T22:39:54.4903900+08:00||;False|2025-12-26T22:39:38.9956198+08:00||;True|2025-12-26T13:29:16.3817938+08:00||;True|2025-12-26T13:13:27.1756564+08:00||;True|2025-12-26T13:12:27.5681845+08:00||;True|2025-12-25T21:53:39.9851108+08:00||;True|2025-12-25T21:14:06.5238213+08:00||;True|2025-12-25T14:05:40.5153425+08:00||;True|2025-12-25T00:52:42.0632072+08:00||;False|2025-12-25T00:52:36.5033522+08:00||;True|2025-12-24T20:47:03.4244585+08:00||;True|2025-12-24T20:38:49.1905853+08:00||;True|2025-12-24T20:38:18.7336590+08:00||;True|2025-12-24T20:33:27.1126773+08:00||;True|2025-12-24T20:32:29.2799273+08:00||;True|2025-12-23T20:55:43.6793723+08:00||;True|2025-12-23T20:54:55.9960612+08:00||;True|2025-12-21T13:23:11.0272294+08:00||;True|2025-12-21T13:22:56.8339811+08:00||;False|2025-12-21T13:22:50.2395530+08:00||;True|2025-12-21T13:11:36.6129339+08:00||;True|2025-12-21T12:15:18.4086793+08:00||;True|2025-12-21T12:15:11.4448190+08:00||;True|2025-12-20T14:05:36.6224332+08:00||;False|2025-12-20T14:05:28.6359828+08:00||;True|2025-12-20T13:58:30.1641630+08:00||;True|2025-12-20T13:55:16.1759645+08:00||;True|2025-12-20T12:16:22.5224516+08:00||;True|2025-12-19T09:50:16.9517676+08:00||;True|2025-12-19T09:49:47.0871734+08:00||;False|2025-12-19T09:49:36.7638757+08:00||;True|2025-12-19T09:35:13.2489248+08:00||;True|2025-12-14T19:09:59.9031104+08:00||;True|2025-12-13T21:21:10.0022707+08:00||;True|2025-12-11T22:07:16.6229994+08:00||;True|2025-12-11T22:05:56.7363206+08:00||;True|2025-12-11T22:01:07.5862406+08:00||;True|2025-12-11T22:00:43.5796594+08:00||;True|2025-12-08T21:20:37.8369271+08:00||;False|2025-12-08T21:20:21.1646252+08:00||;True|2025-12-08T20:51:50.6845619+08:00||;True|2025-12-08T16:43:45.2425324+08:00||;False|2025-12-08T16:42:23.0308163+08:00||;True|2025-12-08T16:41:52.4103868+08:00||;True|2025-12-08T16:41:49.8972006+08:00||;False|2025-12-08T16:41:39.5343442+08:00||;True|2025-12-08T12:40:19.2430740+08:00||;True|2025-12-08T12:02:30.6005542+08:00||;True|2025-12-08T11:34:29.6673185+08:00||;False|2025-12-08T11:30:56.2857042+08:00||;True|2025-12-08T10:52:43.7427594+08:00||;True|2025-12-08T07:05:03.2845531+08:00||;True|2025-12-07T23:02:10.0037815+08:00||;True|2025-12-07T22:48:25.7859648+08:00||;True|2025-12-07T20:49:13.6683281+08:00||;True|2025-12-07T08:47:30.1236366+08:00||;True|2025-12-06T13:14:32.4410202+08:00||;True|2025-12-06T13:07:29.7182102+08:00||;True|2025-12-06T13:05:46.2855366+08:00||;False|2025-12-06T13:05:40.3550904+08:00||;True|2025-12-06T13:03:47.8773880+08:00||;True|2025-12-06T13:03:28.9521030+08:00||;True|2025-12-05T23:11:51.5026151+08:00||;True|2025-12-05T12:46:26.1415079+08:00||;False|2025-12-05T12:46:19.1274332+08:00||;True|2025-12-05T09:04:36.7111717+08:00||;True|2025-12-05T08:22:51.7343317+08:00||;True|2025-12-05T08:20:32.0383739+08:00||;</History>
|
<History>True|2026-01-07T16:19:20.8948701Z||;True|2026-01-07T21:59:04.8702022+08:00||;True|2026-01-03T21:42:41.3864639+08:00||;False|2026-01-03T21:42:26.7041325+08:00||;True|2026-01-02T09:32:11.1396877+08:00||;True|2026-01-01T23:30:38.0205783+08:00||;True|2026-01-01T23:29:35.2877898+08:00||;False|2026-01-01T23:27:39.5930556+08:00||;False|2026-01-01T23:24:52.0996222+08:00||;False|2026-01-01T23:24:09.0444840+08:00||;False|2026-01-01T23:24:06.1610789+08:00||;False|2026-01-01T23:24:03.3178410+08:00||;False|2026-01-01T23:22:56.2224992+08:00||;False|2026-01-01T23:22:47.8246988+08:00||;False|2026-01-01T23:22:43.2890820+08:00||;True|2026-01-01T23:21:03.6027351+08:00||;True|2026-01-01T23:07:57.5938094+08:00||;True|2025-12-29T18:55:45.8262096+08:00||;True|2025-12-28T00:21:04.1648410+08:00||;True|2025-12-28T00:18:49.2137559+08:00||;True|2025-12-28T00:14:18.1456892+08:00||;True|2025-12-28T00:11:06.6645248+08:00||;False|2025-12-28T00:10:49.4906020+08:00||;True|2025-12-27T23:51:57.6426264+08:00||;True|2025-12-27T23:40:08.4783210+08:00||;True|2025-12-27T14:59:32.0605406+08:00||;False|2025-12-27T14:59:21.2076690+08:00||;True|2025-12-27T12:58:06.9934830+08:00||;True|2025-12-27T12:51:45.3402320+08:00||;True|2025-12-27T12:45:49.9795040+08:00||;True|2025-12-27T12:42:13.9395619+08:00||;True|2025-12-27T11:32:35.8851241+08:00||;True|2025-12-27T11:32:11.1199261+08:00||;True|2025-12-27T11:31:34.3939619+08:00||;True|2025-12-26T22:39:54.4903900+08:00||;False|2025-12-26T22:39:38.9956198+08:00||;True|2025-12-26T13:29:16.3817938+08:00||;True|2025-12-26T13:13:27.1756564+08:00||;True|2025-12-26T13:12:27.5681845+08:00||;True|2025-12-25T21:53:39.9851108+08:00||;True|2025-12-25T21:14:06.5238213+08:00||;True|2025-12-25T14:05:40.5153425+08:00||;True|2025-12-25T00:52:42.0632072+08:00||;False|2025-12-25T00:52:36.5033522+08:00||;True|2025-12-24T20:47:03.4244585+08:00||;True|2025-12-24T20:38:49.1905853+08:00||;True|2025-12-24T20:38:18.7336590+08:00||;True|2025-12-24T20:33:27.1126773+08:00||;True|2025-12-24T20:32:29.2799273+08:00||;True|2025-12-23T20:55:43.6793723+08:00||;True|2025-12-23T20:54:55.9960612+08:00||;True|2025-12-21T13:23:11.0272294+08:00||;True|2025-12-21T13:22:56.8339811+08:00||;False|2025-12-21T13:22:50.2395530+08:00||;True|2025-12-21T13:11:36.6129339+08:00||;True|2025-12-21T12:15:18.4086793+08:00||;True|2025-12-21T12:15:11.4448190+08:00||;True|2025-12-20T14:05:36.6224332+08:00||;False|2025-12-20T14:05:28.6359828+08:00||;True|2025-12-20T13:58:30.1641630+08:00||;True|2025-12-20T13:55:16.1759645+08:00||;True|2025-12-20T12:16:22.5224516+08:00||;True|2025-12-19T09:50:16.9517676+08:00||;True|2025-12-19T09:49:47.0871734+08:00||;False|2025-12-19T09:49:36.7638757+08:00||;True|2025-12-19T09:35:13.2489248+08:00||;True|2025-12-14T19:09:59.9031104+08:00||;True|2025-12-13T21:21:10.0022707+08:00||;True|2025-12-11T22:07:16.6229994+08:00||;True|2025-12-11T22:05:56.7363206+08:00||;True|2025-12-11T22:01:07.5862406+08:00||;True|2025-12-11T22:00:43.5796594+08:00||;True|2025-12-08T21:20:37.8369271+08:00||;False|2025-12-08T21:20:21.1646252+08:00||;True|2025-12-08T20:51:50.6845619+08:00||;True|2025-12-08T16:43:45.2425324+08:00||;False|2025-12-08T16:42:23.0308163+08:00||;True|2025-12-08T16:41:52.4103868+08:00||;True|2025-12-08T16:41:49.8972006+08:00||;False|2025-12-08T16:41:39.5343442+08:00||;True|2025-12-08T12:40:19.2430740+08:00||;True|2025-12-08T12:02:30.6005542+08:00||;True|2025-12-08T11:34:29.6673185+08:00||;False|2025-12-08T11:30:56.2857042+08:00||;True|2025-12-08T10:52:43.7427594+08:00||;True|2025-12-08T07:05:03.2845531+08:00||;True|2025-12-07T23:02:10.0037815+08:00||;True|2025-12-07T22:48:25.7859648+08:00||;True|2025-12-07T20:49:13.6683281+08:00||;True|2025-12-07T08:47:30.1236366+08:00||;True|2025-12-06T13:14:32.4410202+08:00||;True|2025-12-06T13:07:29.7182102+08:00||;True|2025-12-06T13:05:46.2855366+08:00||;False|2025-12-06T13:05:40.3550904+08:00||;True|2025-12-06T13:03:47.8773880+08:00||;True|2025-12-06T13:03:28.9521030+08:00||;True|2025-12-05T23:11:51.5026151+08:00||;True|2025-12-05T12:46:26.1415079+08:00||;False|2025-12-05T12:46:19.1274332+08:00||;True|2025-12-05T09:04:36.7111717+08:00||;</History>
|
||||||
<LastFailureDetails />
|
<LastFailureDetails />
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"deploy": "fn", //fn,docker
|
"deploy": "docker", //fn,docker
|
||||||
"dbconn": "",
|
"dbconn": "",
|
||||||
"tagName": "beta_1.9.6", //arm,beta
|
"tagName": "beta_1.9.7", //arm,beta
|
||||||
"dbtype": "Sqlite",
|
"dbtype": "Sqlite",
|
||||||
"fnVersion": "1.0.0"
|
"fnVersion": "1.0.0"
|
||||||
}
|
}
|
||||||
@@ -446,10 +446,11 @@ namespace dy.net.job
|
|||||||
foreach (var item in data.AwemeList)
|
foreach (var item in data.AwemeList)
|
||||||
{
|
{
|
||||||
//if (!item.Desc.Contains("人生苦短"))
|
//if (!item.Desc.Contains("人生苦短"))
|
||||||
////if (!item.Desc.Contains("如果你也喜欢"))
|
//if (item.AwemeId!= "7202236830337551616")
|
||||||
//{
|
//{
|
||||||
// continue;
|
// continue;
|
||||||
//}
|
//}
|
||||||
|
|
||||||
//判断视频是否是强制删除且不再下载的视频
|
//判断视频是否是强制删除且不再下载的视频
|
||||||
var deleteVideo = await douyinCommonService.ExistDeleteVideo(item.AwemeId);
|
var deleteVideo = await douyinCommonService.ExistDeleteVideo(item.AwemeId);
|
||||||
if (deleteVideo)
|
if (deleteVideo)
|
||||||
@@ -754,7 +755,8 @@ namespace dy.net.job
|
|||||||
await Task.Delay(_random.Next(1, 4) * 1000);
|
await Task.Delay(_random.Next(1, 4) * 1000);
|
||||||
|
|
||||||
// 下载视频
|
// 下载视频
|
||||||
if (!await douyinHttpClientService.DownloadAsync(videoUrl, savePath, cookie.Cookies))
|
var (Success, ActualSavePath) = await douyinHttpClientService.DownloadAsync(videoUrl, savePath, cookie.Cookies);
|
||||||
|
if (!Success)
|
||||||
{
|
{
|
||||||
Log.Error($"{VideoType}-{item?.Author?.Nickname ?? ""}-视频[{DouyinFileNameHelper.SanitizeLinuxFileName(item.Desc, item.AwemeId)}]下载失败!!!");
|
Log.Error($"{VideoType}-{item?.Author?.Nickname ?? ""}-视频[{DouyinFileNameHelper.SanitizeLinuxFileName(item.Desc, item.AwemeId)}]下载失败!!!");
|
||||||
|
|
||||||
@@ -812,8 +814,8 @@ namespace dy.net.job
|
|||||||
dynamicSavePaths.Add(dynamicSavePath);
|
dynamicSavePaths.Add(dynamicSavePath);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
var (Success, ActualSavePath) = await douyinHttpClientService.DownloadAsync(dynamicUrl, dynamicSavePath, cookie.Cookies);
|
||||||
if (!await douyinHttpClientService.DownloadAsync(dynamicUrl, dynamicSavePath, cookie.Cookies))
|
if (!Success)
|
||||||
{
|
{
|
||||||
Log.Error($"{VideoType}-{item?.Author?.Nickname ?? ""}-视频[{DouyinFileNameHelper.SanitizeLinuxFileName(item.Desc, item.AwemeId)}]-00{i},下载失败!!!");
|
Log.Error($"{VideoType}-{item?.Author?.Nickname ?? ""}-视频[{DouyinFileNameHelper.SanitizeLinuxFileName(item.Desc, item.AwemeId)}]-00{i},下载失败!!!");
|
||||||
}
|
}
|
||||||
@@ -891,8 +893,16 @@ namespace dy.net.job
|
|||||||
if (otherUrls.Count > 0)
|
if (otherUrls.Count > 0)
|
||||||
{
|
{
|
||||||
Log.Debug($"{VideoType}-额外找到{otherUrls.Count}个视频地址,即将再次开始下载...");
|
Log.Debug($"{VideoType}-额外找到{otherUrls.Count}个视频地址,即将再次开始下载...");
|
||||||
var result2 = await douyinHttpClientService.DownloadAsync(videoUrl, savePath, cookie.Cookies, otherUrls);
|
var (Success, ActualSavePath) = await douyinHttpClientService.DownloadAsync(videoUrl, savePath, cookie.Cookies, otherUrls);
|
||||||
Log.Debug($"{VideoType}-尝试多个地址后,下载成功,{savePath}");
|
if(Success)
|
||||||
|
{
|
||||||
|
Log.Debug($"{VideoType}-尝试多个地址后,下载成功,{savePath}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.Error($"{VideoType}-尝试多个地址后,下载失败,{item.Desc}");
|
||||||
|
return (flowControl: false, value: null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -963,7 +973,6 @@ namespace dy.net.job
|
|||||||
|
|
||||||
// 获取音乐URL
|
// 获取音乐URL
|
||||||
var mp3Url = item.Music?.PlayUrl?.UrlList?.FirstOrDefault();
|
var mp3Url = item.Music?.PlayUrl?.UrlList?.FirstOrDefault();
|
||||||
|
|
||||||
var firstImage = item.Images.FirstOrDefault();
|
var firstImage = item.Images.FirstOrDefault();
|
||||||
int height = firstImage.Height;
|
int height = firstImage.Height;
|
||||||
int width = firstImage.Width;
|
int width = firstImage.Width;
|
||||||
@@ -1244,7 +1253,7 @@ namespace dy.net.job
|
|||||||
// 如果封面文件不存在,则下载
|
// 如果封面文件不存在,则下载
|
||||||
if (!File.Exists(coverSavePath))
|
if (!File.Exists(coverSavePath))
|
||||||
{
|
{
|
||||||
var downRes = await douyinHttpClientService.DownloadAsync(coverUrl, coverSavePath, cookie.Cookies);
|
await douyinHttpClientService.DownloadAsync(coverUrl, coverSavePath, cookie.Cookies);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -313,7 +313,7 @@ namespace dy.net.service
|
|||||||
/// <param name="maxRetryCount">最大重试次数(默认3次)</param>
|
/// <param name="maxRetryCount">最大重试次数(默认3次)</param>
|
||||||
/// <param name="initialRetryDelay">初始重试延迟(默认1秒,指数退避)</param>
|
/// <param name="initialRetryDelay">初始重试延迟(默认1秒,指数退避)</param>
|
||||||
/// <returns>是否下载成功</returns>
|
/// <returns>是否下载成功</returns>
|
||||||
public async Task<bool> DownloadAsync(
|
public async Task<(bool Success, string ActualSavePath)> DownloadAsync(
|
||||||
string videoUrl,
|
string videoUrl,
|
||||||
string savePath,
|
string savePath,
|
||||||
string cookie,
|
string cookie,
|
||||||
@@ -362,21 +362,21 @@ namespace dy.net.service
|
|||||||
{
|
{
|
||||||
// 等待期间被取消,直接返回失败
|
// 等待期间被取消,直接返回失败
|
||||||
Serilog.Log.Information($"重试等待被取消:{videoUrl}");
|
Serilog.Log.Information($"重试等待被取消:{videoUrl}");
|
||||||
return false;
|
return (false, savePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
// 主动取消,不重试
|
// 主动取消,不重试
|
||||||
Serilog.Log.Information($"下载被取消:{videoUrl}");
|
Serilog.Log.Information($"下载被取消:{videoUrl}");
|
||||||
return false;
|
return (false, savePath);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// 不可重试的异常(如参数错误、权限不足等),直接失败
|
// 不可重试的异常(如参数错误、权限不足等),直接失败
|
||||||
Serilog.Log.Error(ex, $"下载失败(不可重试):{videoUrl}");
|
Serilog.Log.Error(ex, $"下载失败(不可重试):{videoUrl}");
|
||||||
CleanupIncompleteFile(savePath); // 清理不完整文件
|
CleanupIncompleteFile(savePath); // 清理不完整文件
|
||||||
return false;
|
return (false, savePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -489,12 +489,12 @@ namespace dy.net.service
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 单次下载尝试(核心下载逻辑)
|
/// 单次下载尝试(核心下载逻辑)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task<bool> TryDownloadOnceAsync(
|
private async Task<(bool Success, string ActualSavePath)> TryDownloadOnceAsync(
|
||||||
string videoUrl,
|
string videoUrl,
|
||||||
string savePath,
|
string savePath,
|
||||||
string cookie,
|
string cookie,
|
||||||
CancellationToken cancellationToken,
|
CancellationToken cancellationToken,
|
||||||
TimeSpan streamTimeout)
|
TimeSpan streamTimeout)
|
||||||
{
|
{
|
||||||
DateTime lastStreamActivity = DateTime.UtcNow; // 流活动时间戳
|
DateTime lastStreamActivity = DateTime.UtcNow; // 流活动时间戳
|
||||||
|
|
||||||
@@ -507,6 +507,11 @@ namespace dy.net.service
|
|||||||
|
|
||||||
// 清理已存在的文件(避免占用)
|
// 清理已存在的文件(避免占用)
|
||||||
CleanupIncompleteFile(savePath);
|
CleanupIncompleteFile(savePath);
|
||||||
|
//传入的文件后缀名
|
||||||
|
string ext = Path.GetExtension(savePath);
|
||||||
|
string actualSavePath = savePath;
|
||||||
|
string detectedExtension = string.Empty;
|
||||||
|
|
||||||
|
|
||||||
using (var httpClient = _clientFactory.CreateClient("dy_download"))
|
using (var httpClient = _clientFactory.CreateClient("dy_download"))
|
||||||
{
|
{
|
||||||
@@ -525,46 +530,90 @@ namespace dy.net.service
|
|||||||
|
|
||||||
long? totalBytes = response.Content.Headers.ContentLength;
|
long? totalBytes = response.Content.Headers.ContentLength;
|
||||||
|
|
||||||
// 读取流并写入文件
|
// 读取响应流
|
||||||
using (var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false))
|
using (var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false))
|
||||||
using (var fileStream = new FileStream(
|
|
||||||
savePath,
|
|
||||||
FileMode.CreateNew,
|
|
||||||
FileAccess.Write,
|
|
||||||
FileShare.None,
|
|
||||||
bufferSize: 8192,
|
|
||||||
options: FileOptions.Asynchronous | FileOptions.SequentialScan))
|
|
||||||
{
|
{
|
||||||
byte[] buffer = new byte[8192];
|
|
||||||
int bytesRead;
|
|
||||||
long totalRead = 0;
|
|
||||||
|
|
||||||
while ((bytesRead = await responseStream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) > 0)
|
//if (ext == ".mp3")
|
||||||
|
//{
|
||||||
|
// detectedExtension = DouyinFileUtils.GetFileExtensionByMagicNumber(responseStream);
|
||||||
|
//}
|
||||||
|
|
||||||
|
if (ext == ".mp3")
|
||||||
{
|
{
|
||||||
// 检查流读取超时
|
var contentType = response.Content.Headers.ContentType?.MediaType;
|
||||||
if (DateTime.UtcNow - lastStreamActivity > streamTimeout)
|
if (!string.IsNullOrEmpty(contentType))
|
||||||
{
|
{
|
||||||
throw new TimeoutException($"流读取超时({streamTimeout.TotalSeconds}秒无数据)");
|
if (contentType.Contains("audio/mp4") || contentType.Contains("audio/m4a"))
|
||||||
}
|
{
|
||||||
|
detectedExtension = "m4a";
|
||||||
await fileStream.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false);
|
}
|
||||||
totalRead += bytesRead;
|
else if (contentType.Contains("audio/mpeg") || contentType.Contains("audio/mp3"))
|
||||||
lastStreamActivity = DateTime.UtcNow;
|
{
|
||||||
|
detectedExtension = "mp3";
|
||||||
// 进度计算(可选)
|
}
|
||||||
if (totalBytes.HasValue)
|
else if (contentType.Contains("video/mp4"))
|
||||||
{
|
{
|
||||||
double progress = (double)totalRead / totalBytes.Value * 100;
|
detectedExtension = "mp4";
|
||||||
// 进度上报逻辑:OnProgressChanged(progress);
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await fileStream.FlushAsync(cancellationToken).ConfigureAwait(false); // 确保数据写入磁盘
|
if (!string.IsNullOrEmpty(detectedExtension))
|
||||||
|
{
|
||||||
|
string extension = Path.GetExtension(actualSavePath);
|
||||||
|
if (string.IsNullOrEmpty(extension))
|
||||||
|
{
|
||||||
|
actualSavePath += detectedExtension;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
actualSavePath = Path.ChangeExtension(actualSavePath, detectedExtension.Trim('.'));
|
||||||
|
savePath = actualSavePath;
|
||||||
|
}
|
||||||
|
CleanupIncompleteFile(actualSavePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var fileStream = new FileStream(
|
||||||
|
actualSavePath, // 使用修正后的保存路径
|
||||||
|
FileMode.CreateNew,
|
||||||
|
FileAccess.Write,
|
||||||
|
FileShare.None,
|
||||||
|
bufferSize: 8192,
|
||||||
|
options: FileOptions.Asynchronous | FileOptions.SequentialScan))
|
||||||
|
{
|
||||||
|
byte[] buffer = new byte[8192];
|
||||||
|
int bytesRead;
|
||||||
|
long totalRead = 0;
|
||||||
|
|
||||||
|
while ((bytesRead = await responseStream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) > 0)
|
||||||
|
{
|
||||||
|
// 检查流读取超时
|
||||||
|
if (DateTime.UtcNow - lastStreamActivity > streamTimeout)
|
||||||
|
{
|
||||||
|
throw new TimeoutException($"流读取超时({streamTimeout.TotalSeconds}秒无数据)");
|
||||||
|
}
|
||||||
|
|
||||||
|
await fileStream.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false);
|
||||||
|
totalRead += bytesRead;
|
||||||
|
lastStreamActivity = DateTime.UtcNow;
|
||||||
|
|
||||||
|
// 进度计算(可选)
|
||||||
|
if (totalBytes.HasValue)
|
||||||
|
{
|
||||||
|
double progress = (double)totalRead / totalBytes.Value * 100;
|
||||||
|
// 进度上报逻辑:OnProgressChanged(progress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await fileStream.FlushAsync(cancellationToken).ConfigureAwait(false); // 确保数据写入磁盘
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
// 返回成功状态和实际的保存路径
|
||||||
|
return (Success: true, ActualSavePath: actualSavePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -711,4 +760,6 @@ namespace dy.net.service
|
|||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -206,7 +206,7 @@ namespace dy.net.service
|
|||||||
|
|
||||||
// 2. 下载音频
|
// 2. 下载音频
|
||||||
string[] rawAudios = Array.Empty<string>();
|
string[] rawAudios = Array.Empty<string>();
|
||||||
if (mergeImg2Viedo&& request.AudioUrls != null && request.AudioUrls.Count>0)
|
if (mergeImg2Viedo && request.AudioUrls != null && request.AudioUrls.Count > 0)
|
||||||
{
|
{
|
||||||
var (audios, audioError) = await DownloadMediaAsync(
|
var (audios, audioError) = await DownloadMediaAsync(
|
||||||
request.AudioUrls, Path.Combine(tempDir, "raw-audios"), "audio_", "mp3", cookie);
|
request.AudioUrls, Path.Combine(tempDir, "raw-audios"), "audio_", "mp3", cookie);
|
||||||
@@ -219,7 +219,8 @@ namespace dy.net.service
|
|||||||
// 保存下载的音频(如果需要)
|
// 保存下载的音频(如果需要)
|
||||||
if (downMp3)
|
if (downMp3)
|
||||||
{
|
{
|
||||||
await SaveDownloadedFilesAsync(rawAudios, fileNamefolder, "mp3", Path.GetFileNameWithoutExtension(outputVideoPath));
|
var ext= Path.GetExtension(rawAudios[0]);
|
||||||
|
await SaveDownloadedFilesAsync(rawAudios, fileNamefolder, ext, Path.GetFileNameWithoutExtension(outputVideoPath));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -407,8 +408,15 @@ namespace dy.net.service
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await douyinHttpClientService.DownloadAsync(url, savePath, cookie);
|
var (Success, ActualSavePath) = await douyinHttpClientService.DownloadAsync(url, savePath, cookie);
|
||||||
successPaths.Add(savePath);
|
if(Success)
|
||||||
|
{
|
||||||
|
successPaths.Add(ActualSavePath);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Serilog.Log.Error($"下载失败:{url} → {savePath}");
|
||||||
|
}
|
||||||
//Console.WriteLine($"下载成功:{url} → {savePath}");
|
//Console.WriteLine($"下载成功:{url} → {savePath}");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
@@ -157,5 +157,64 @@
|
|||||||
{
|
{
|
||||||
return HasDirectoryReadPermission(directoryPath) && HasDirectoryWritePermission(directoryPath);
|
return HasDirectoryReadPermission(directoryPath) && HasDirectoryWritePermission(directoryPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 通过文件头魔数(前几个字节)识别真实文件类型
|
||||||
|
/// </summary>
|
||||||
|
public static string GetFileExtensionByMagicNumber(Stream stream)
|
||||||
|
{
|
||||||
|
// 读取前16个字节(足够识别常见格式)
|
||||||
|
byte[] magicBytes = new byte[16];
|
||||||
|
int readBytes = stream.Read(magicBytes, 0, magicBytes.Length);
|
||||||
|
//stream.Position = 0; // 读完重置指针
|
||||||
|
|
||||||
|
if (readBytes < 4) return ""; // 字节数不足,兜底
|
||||||
|
|
||||||
|
// 魔数匹配规则(核心:区分MP4视频和M4A音频)
|
||||||
|
// MP4/M4A 基础魔数:0x00 0x00 0x00 ?? 0x66 0x74 0x79 0x70 (ftyp)
|
||||||
|
if (magicBytes[4] == 0x66 && magicBytes[5] == 0x74 && magicBytes[6] == 0x79 && magicBytes[7] == 0x70)
|
||||||
|
{
|
||||||
|
// 读取ftyp后的字符,区分MP4/M4A
|
||||||
|
if (readBytes >= 12)
|
||||||
|
{
|
||||||
|
// MP4 特征:ftypmp42 / ftypisom / ftypmp41
|
||||||
|
string ftyp = System.Text.Encoding.ASCII.GetString(magicBytes, 8, 4);
|
||||||
|
if (ftyp == "mp42" || ftyp == "isom" || ftyp == "mp41")
|
||||||
|
{
|
||||||
|
// 进一步判断是否包含视频轨道(简化版:M4A只有音频,MP4可能含视频)
|
||||||
|
// 这里做简化区分:如果是纯音频则为m4a,否则为mp4
|
||||||
|
// (进阶版可解析MP4盒子,新手先按此规则)
|
||||||
|
return "m4a"; // 你原链接的真实类型
|
||||||
|
// 若要严格区分视频MP4,可扩展:return "mp4";
|
||||||
|
}
|
||||||
|
// M4A 特征:ftypM4A / ftypM4B / ftypM4P
|
||||||
|
else if (ftyp == "M4A " || ftyp == "M4B " || ftyp == "M4P ")
|
||||||
|
{
|
||||||
|
return "m4a";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 兜底:MP4/M4A统一先按Content-Type辅助,无则按mp4
|
||||||
|
return "mp4";
|
||||||
|
}
|
||||||
|
// MP3 魔数:0xFF 0xFB 或 0x49 0x44 0x33 (ID3)
|
||||||
|
else if ((magicBytes[0] == 0xFF && magicBytes[1] == 0xFB) ||
|
||||||
|
(magicBytes[0] == 0x49 && magicBytes[1] == 0x44 && magicBytes[2] == 0x33))
|
||||||
|
{
|
||||||
|
return "mp3";
|
||||||
|
}
|
||||||
|
// 其他常见格式(可扩展)
|
||||||
|
else if (magicBytes[0] == 0xFF && magicBytes[1] == 0xD8) // JPG
|
||||||
|
{
|
||||||
|
return "jpg";
|
||||||
|
}
|
||||||
|
else if (magicBytes[0] == 0x89 && magicBytes[1] == 0x50 && magicBytes[2] == 0x4E && magicBytes[3] == 0x47) // PNG
|
||||||
|
{
|
||||||
|
return "png";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return ""; // 未知格式
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+45
-40
@@ -33,8 +33,8 @@ namespace dy.net.utils
|
|||||||
_ffmpegExecutablePath = "ffmpeg";
|
_ffmpegExecutablePath = "ffmpeg";
|
||||||
_ffprobeExecutablePath = "ffprobe";
|
_ffprobeExecutablePath = "ffprobe";
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Process _ffmpegProcess;
|
private Process _ffmpegProcess;
|
||||||
private CancellationTokenSource _cancellationTokenSource;
|
private CancellationTokenSource _cancellationTokenSource;
|
||||||
@@ -140,16 +140,18 @@ namespace dy.net.utils
|
|||||||
Serilog.Log.Error($"获取音频时长失败,将使用图片总时长 ({audioDurationSeconds:F2}s) 作为视频时长: {ex.Message}");
|
Serilog.Log.Error($"获取音频时长失败,将使用图片总时长 ({audioDurationSeconds:F2}s) 作为视频时长: {ex.Message}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 计算图片序列需要循环的次数
|
// 计算图片序列需要循环的次数
|
||||||
int loopCount = 1;
|
int loopCount = 1;
|
||||||
double singleLoopDuration = imageList.Count * ImageDisplayDurationSeconds;
|
int imageCount = imageList.Count; // 要循环的图片总数(对应loop的size参数)
|
||||||
|
double singleLoopDuration = imageCount * ImageDisplayDurationSeconds;
|
||||||
|
|
||||||
if (audioDurationSeconds > singleLoopDuration)
|
if (audioDurationSeconds > singleLoopDuration)
|
||||||
{
|
{
|
||||||
loopCount = (int)Math.Ceiling(audioDurationSeconds / singleLoopDuration);
|
loopCount = (int)Math.Ceiling(audioDurationSeconds / singleLoopDuration);
|
||||||
//Console.WriteLine($"图片序列将循环 {loopCount} 次以匹配音频时长");
|
//Console.WriteLine($"图片序列将循环 {loopCount} 次以匹配音频时长");
|
||||||
}
|
}
|
||||||
|
// H264编码要求宽高必须为偶数 20260103
|
||||||
//H264编码要求宽高必须为偶数 20260103
|
|
||||||
if (VideoWidth % 2 != 0)
|
if (VideoWidth % 2 != 0)
|
||||||
{
|
{
|
||||||
VideoWidth++;
|
VideoWidth++;
|
||||||
@@ -158,50 +160,53 @@ namespace dy.net.utils
|
|||||||
{
|
{
|
||||||
VideoHeight++;
|
VideoHeight++;
|
||||||
}
|
}
|
||||||
|
|
||||||
var arguments = new List<string>
|
var arguments = new List<string>
|
||||||
{
|
{
|
||||||
"-y", // 覆盖输出文件
|
"-y", // 覆盖输出文件
|
||||||
|
|
||||||
// 图片序列输入:恢复你原有的 -r,移除可能冲突的参数
|
// 图片序列输入:保留原有逻辑,修复帧率含义(-r 是输出帧率,输入用-framerate更准确)
|
||||||
"-f", "image2",
|
"-f", "image2",
|
||||||
"-r", imageFps.ToString(CultureInfo.InvariantCulture), // 保留你原本的帧率参数
|
"-framerate", imageFps.ToString(CultureInfo.InvariantCulture), // 输入帧率(图片播放速度)
|
||||||
"-start_number", "0", // 仅增加:避免序列编号问题(不影响原有逻辑)
|
"-start_number", "0", // 序列起始编号
|
||||||
"-i", imageSequencePattern, // 关键修复:去掉引号(原代码的引号是核心失败原因)
|
"-i", imageSequencePattern, // 图片序列模板(无需引号,后续用ProcessStartInfo传参更安全)
|
||||||
|
|
||||||
// 音频输入:同样去掉引号
|
// 音频输入
|
||||||
"-i", audioFilePath,
|
"-i", audioFilePath,
|
||||||
|
|
||||||
// 滤镜修复:恢复简单写法,避免复杂参数(适配旧版 FFmpeg)
|
// 滤镜修复:补充size参数(核心!解决Number of frames to loop is not set报错)
|
||||||
"-filter_complex", $"[0:v]loop={loopCount - 1}[v]", // 去掉额外参数,和你原逻辑一致
|
// loop=循环次数:size=要循环的帧数;loop=-1表示无限循环(配合-shortest更简洁)
|
||||||
|
"-filter_complex", $"[0:v]loop=loop={loopCount - 1}:size={imageCount}[v]",
|
||||||
|
|
||||||
// 流映射:去掉引号,恢复简单写法
|
// 流映射
|
||||||
"-map", "[v]",
|
"-map", "[v]",
|
||||||
"-map", "1:a",
|
"-map", "1:a",
|
||||||
|
|
||||||
// 视频编码:保留你的变量,只增加兼容性参数
|
// 视频编码:保留你的变量,确保兼容性
|
||||||
"-c:v", VideoCodec, // 保留你原本的编码变量(之前能跑通,说明该编码支持)
|
"-c:v", VideoCodec,
|
||||||
"-preset", VideoPreset, // 保留原变量
|
"-preset", VideoPreset,
|
||||||
"-crf", $"{VideoCrf}", // 保留原变量
|
"-crf", $"{VideoCrf}",
|
||||||
"-s", $"{VideoWidth}x{VideoHeight}",
|
"-s", $"{VideoWidth}x{VideoHeight}",
|
||||||
"-pix_fmt", "yuv420p", // 仅增加:兼容性关键(不影响原有逻辑)
|
"-pix_fmt", "yuv420p", // 兼容所有播放器
|
||||||
"-profile:v", "main", // 仅增加:适配多数播放器(不冲突)
|
"-profile:v", "main",
|
||||||
|
|
||||||
// 音频编码:保留你的变量,修复可能的无效值
|
// 音频编码:标准化参数,避免无效值
|
||||||
"-c:a", AudioCodec, // 保留原变量
|
"-c:a", AudioCodec,
|
||||||
"-b:a", $"{AudioBitrate}", // 保留原变量
|
"-b:a", $"{AudioBitrate}",
|
||||||
"-ac", "2", // 仅增加:避免单声道兼容问题
|
"-ac", "2", // 立体声
|
||||||
"-ar", "44100", // 仅增加:标准化采样率
|
"-ar", "44100", // 标准采样率
|
||||||
|
|
||||||
// 封装优化:仅增加关键参数,不影响原有逻辑
|
// 封装优化
|
||||||
"-f", "mp4", // 明确格式(之前可能自动识别,现在显式声明更稳定)
|
"-f", "mp4",
|
||||||
"-movflags", "+faststart", // 仅增加:解决部分播放器无法播放
|
"-movflags", "+faststart",
|
||||||
|
|
||||||
// 保留你原有的同步参数
|
// 同步参数:确保视频时长匹配音频
|
||||||
"-shortest",
|
"-shortest",
|
||||||
|
|
||||||
|
// 输出路径
|
||||||
|
outputVideoPath
|
||||||
|
};
|
||||||
|
|
||||||
// 输出路径:去掉引号(核心修复点)
|
|
||||||
outputVideoPath
|
|
||||||
};
|
|
||||||
|
|
||||||
string args = string.Join(" ", arguments);
|
string args = string.Join(" ", arguments);
|
||||||
//Console.WriteLine($"执行FFmpeg命令: {_ffmpegExecutablePath} {args}");
|
//Console.WriteLine($"执行FFmpeg命令: {_ffmpegExecutablePath} {args}");
|
||||||
@@ -285,7 +290,7 @@ namespace dy.net.utils
|
|||||||
|
|
||||||
if (_ffmpegProcess.ExitCode != 0)
|
if (_ffmpegProcess.ExitCode != 0)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"FFmpeg执行失败,退出码: {_ffmpegProcess.ExitCode}。请查看控制台输出获取详细错误信息。");
|
throw new InvalidOperationException($"FFmpeg执行失败,退出码: {_ffmpegProcess.ExitCode}。请查看控制台输出获取详细错误信息。执行命令:ffmpeg {arguments}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
|
|||||||
Reference in New Issue
Block a user