From 1006de4a0d187761a07e48e5e8a31fe9e5217757 Mon Sep 17 00:00:00 2001 From: jianzhichu Date: Thu, 8 Jan 2026 00:23:08 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=8B=E8=BD=BD=E9=9F=B3=E9=A2=91=E6=97=B6?= =?UTF-8?q?=EF=BC=8C=E5=8F=AF=E8=83=BD=E4=B8=8D=E6=98=AFmp3=20=E5=8F=AF?= =?UTF-8?q?=E8=83=BD=E6=98=AFm4a=E6=A0=BC=E5=BC=8F=EF=BC=8C=E6=9A=82?= =?UTF-8?q?=E6=97=B6=E5=B0=B1=E9=81=87=E5=88=B0=E8=BF=992=E7=A7=8D=20ffmpe?= =?UTF-8?q?g=E5=91=BD=E4=BB=A4=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Properties/PublishProfiles/docker.pubxml.user | 2 +- Properties/PublishProfiles/fn.pubxml.user | 2 +- appsettings.json | 4 +- job/DouyinBasicSyncJob.cs | 25 ++-- service/DouyinHttpClientService.cs | 127 ++++++++++++------ service/DouyinMergeVideoService.cs | 16 ++- utils/DouyinFileUtils.cs | 59 ++++++++ utils/FFmpegHelper.cs | 85 ++++++------ 8 files changed, 226 insertions(+), 94 deletions(-) diff --git a/Properties/PublishProfiles/docker.pubxml.user b/Properties/PublishProfiles/docker.pubxml.user index bce62b9..8e42124 100644 --- a/Properties/PublishProfiles/docker.pubxml.user +++ b/Properties/PublishProfiles/docker.pubxml.user @@ -3,7 +3,7 @@ <_PublishTargetUrl>E:\code\dysync\bin\Release\net6.0\publish\ - 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||; + 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||; \ No newline at end of file diff --git a/Properties/PublishProfiles/fn.pubxml.user b/Properties/PublishProfiles/fn.pubxml.user index 6b3931f..4e851ab 100644 --- a/Properties/PublishProfiles/fn.pubxml.user +++ b/Properties/PublishProfiles/fn.pubxml.user @@ -5,7 +5,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121. <_PublishTargetUrl>E:\code\dysync\bin\Release\net6.0\publish\ - 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||; + 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||; diff --git a/appsettings.json b/appsettings.json index d156dd5..d9a721d 100644 --- a/appsettings.json +++ b/appsettings.json @@ -1,7 +1,7 @@ { - "deploy": "fn", //fn,docker + "deploy": "docker", //fn,docker "dbconn": "", - "tagName": "beta_1.9.6", //arm,beta + "tagName": "beta_1.9.7", //arm,beta "dbtype": "Sqlite", "fnVersion": "1.0.0" } \ No newline at end of file diff --git a/job/DouyinBasicSyncJob.cs b/job/DouyinBasicSyncJob.cs index 9ba00c9..d4b3058 100644 --- a/job/DouyinBasicSyncJob.cs +++ b/job/DouyinBasicSyncJob.cs @@ -446,10 +446,11 @@ namespace dy.net.job foreach (var item in data.AwemeList) { //if (!item.Desc.Contains("人生苦短")) - ////if (!item.Desc.Contains("如果你也喜欢")) + //if (item.AwemeId!= "7202236830337551616") //{ // continue; //} + //判断视频是否是强制删除且不再下载的视频 var deleteVideo = await douyinCommonService.ExistDeleteVideo(item.AwemeId); if (deleteVideo) @@ -754,7 +755,8 @@ namespace dy.net.job 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)}]下载失败!!!"); @@ -812,8 +814,8 @@ namespace dy.net.job dynamicSavePaths.Add(dynamicSavePath); continue; } - - if (!await douyinHttpClientService.DownloadAsync(dynamicUrl, dynamicSavePath, cookie.Cookies)) + var (Success, ActualSavePath) = await douyinHttpClientService.DownloadAsync(dynamicUrl, dynamicSavePath, cookie.Cookies); + if (!Success) { 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) { Log.Debug($"{VideoType}-额外找到{otherUrls.Count}个视频地址,即将再次开始下载..."); - var result2 = await douyinHttpClientService.DownloadAsync(videoUrl, savePath, cookie.Cookies, otherUrls); - Log.Debug($"{VideoType}-尝试多个地址后,下载成功,{savePath}"); + var (Success, ActualSavePath) = await douyinHttpClientService.DownloadAsync(videoUrl, savePath, cookie.Cookies, otherUrls); + if(Success) + { + Log.Debug($"{VideoType}-尝试多个地址后,下载成功,{savePath}"); + } + else + { + Log.Error($"{VideoType}-尝试多个地址后,下载失败,{item.Desc}"); + return (flowControl: false, value: null); + } } else { @@ -963,7 +973,6 @@ namespace dy.net.job // 获取音乐URL var mp3Url = item.Music?.PlayUrl?.UrlList?.FirstOrDefault(); - var firstImage = item.Images.FirstOrDefault(); int height = firstImage.Height; int width = firstImage.Width; @@ -1244,7 +1253,7 @@ namespace dy.net.job // 如果封面文件不存在,则下载 if (!File.Exists(coverSavePath)) { - var downRes = await douyinHttpClientService.DownloadAsync(coverUrl, coverSavePath, cookie.Cookies); + await douyinHttpClientService.DownloadAsync(coverUrl, coverSavePath, cookie.Cookies); } } diff --git a/service/DouyinHttpClientService.cs b/service/DouyinHttpClientService.cs index 7937a92..f1ce61d 100644 --- a/service/DouyinHttpClientService.cs +++ b/service/DouyinHttpClientService.cs @@ -313,7 +313,7 @@ namespace dy.net.service /// 最大重试次数(默认3次) /// 初始重试延迟(默认1秒,指数退避) /// 是否下载成功 - public async Task DownloadAsync( + public async Task<(bool Success, string ActualSavePath)> DownloadAsync( string videoUrl, string savePath, string cookie, @@ -362,21 +362,21 @@ namespace dy.net.service { // 等待期间被取消,直接返回失败 Serilog.Log.Information($"重试等待被取消:{videoUrl}"); - return false; + return (false, savePath); } } catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) { // 主动取消,不重试 Serilog.Log.Information($"下载被取消:{videoUrl}"); - return false; + return (false, savePath); } catch (Exception ex) { // 不可重试的异常(如参数错误、权限不足等),直接失败 Serilog.Log.Error(ex, $"下载失败(不可重试):{videoUrl}"); CleanupIncompleteFile(savePath); // 清理不完整文件 - return false; + return (false, savePath); } } } @@ -489,12 +489,12 @@ namespace dy.net.service /// /// 单次下载尝试(核心下载逻辑) /// - private async Task TryDownloadOnceAsync( - string videoUrl, - string savePath, - string cookie, - CancellationToken cancellationToken, - TimeSpan streamTimeout) + private async Task<(bool Success, string ActualSavePath)> TryDownloadOnceAsync( + string videoUrl, + string savePath, + string cookie, + CancellationToken cancellationToken, + TimeSpan streamTimeout) { DateTime lastStreamActivity = DateTime.UtcNow; // 流活动时间戳 @@ -507,6 +507,11 @@ namespace dy.net.service // 清理已存在的文件(避免占用) CleanupIncompleteFile(savePath); + //传入的文件后缀名 + string ext = Path.GetExtension(savePath); + string actualSavePath = savePath; + string detectedExtension = string.Empty; + using (var httpClient = _clientFactory.CreateClient("dy_download")) { @@ -525,46 +530,90 @@ namespace dy.net.service long? totalBytes = response.Content.Headers.ContentLength; - // 读取流并写入文件 + // 读取响应流 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") { - // 检查流读取超时 - if (DateTime.UtcNow - lastStreamActivity > streamTimeout) + var contentType = response.Content.Headers.ContentType?.MediaType; + if (!string.IsNullOrEmpty(contentType)) { - 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); + if (contentType.Contains("audio/mp4") || contentType.Contains("audio/m4a")) + { + detectedExtension = "m4a"; + } + else if (contentType.Contains("audio/mpeg") || contentType.Contains("audio/mp3")) + { + detectedExtension = "mp3"; + } + else if (contentType.Contains("video/mp4")) + { + detectedExtension = "mp4"; + } } } - 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 } + + } diff --git a/service/DouyinMergeVideoService.cs b/service/DouyinMergeVideoService.cs index 5450cc0..92fccb3 100644 --- a/service/DouyinMergeVideoService.cs +++ b/service/DouyinMergeVideoService.cs @@ -206,7 +206,7 @@ namespace dy.net.service // 2. 下载音频 string[] rawAudios = Array.Empty(); - if (mergeImg2Viedo&& request.AudioUrls != null && request.AudioUrls.Count>0) + if (mergeImg2Viedo && request.AudioUrls != null && request.AudioUrls.Count > 0) { var (audios, audioError) = await DownloadMediaAsync( request.AudioUrls, Path.Combine(tempDir, "raw-audios"), "audio_", "mp3", cookie); @@ -219,7 +219,8 @@ namespace dy.net.service // 保存下载的音频(如果需要) 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 { - await douyinHttpClientService.DownloadAsync(url, savePath, cookie); - successPaths.Add(savePath); + var (Success, ActualSavePath) = await douyinHttpClientService.DownloadAsync(url, savePath, cookie); + if(Success) + { + successPaths.Add(ActualSavePath); + } + else + { + Serilog.Log.Error($"下载失败:{url} → {savePath}"); + } //Console.WriteLine($"下载成功:{url} → {savePath}"); } catch (Exception ex) diff --git a/utils/DouyinFileUtils.cs b/utils/DouyinFileUtils.cs index bcc58a4..d2936dd 100644 --- a/utils/DouyinFileUtils.cs +++ b/utils/DouyinFileUtils.cs @@ -157,5 +157,64 @@ { return HasDirectoryReadPermission(directoryPath) && HasDirectoryWritePermission(directoryPath); } + + /// + /// 通过文件头魔数(前几个字节)识别真实文件类型 + /// + 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 ""; // 未知格式 + } + } } } diff --git a/utils/FFmpegHelper.cs b/utils/FFmpegHelper.cs index d343d45..4fe253b 100644 --- a/utils/FFmpegHelper.cs +++ b/utils/FFmpegHelper.cs @@ -33,8 +33,8 @@ namespace dy.net.utils _ffmpegExecutablePath = "ffmpeg"; _ffprobeExecutablePath = "ffprobe"; #endif - } -} + } + } private Process _ffmpegProcess; private CancellationTokenSource _cancellationTokenSource; @@ -140,16 +140,18 @@ namespace dy.net.utils Serilog.Log.Error($"获取音频时长失败,将使用图片总时长 ({audioDurationSeconds:F2}s) 作为视频时长: {ex.Message}"); } + // 计算图片序列需要循环的次数 int loopCount = 1; - double singleLoopDuration = imageList.Count * ImageDisplayDurationSeconds; + int imageCount = imageList.Count; // 要循环的图片总数(对应loop的size参数) + double singleLoopDuration = imageCount * ImageDisplayDurationSeconds; + if (audioDurationSeconds > singleLoopDuration) { loopCount = (int)Math.Ceiling(audioDurationSeconds / singleLoopDuration); //Console.WriteLine($"图片序列将循环 {loopCount} 次以匹配音频时长"); } - - //H264编码要求宽高必须为偶数 20260103 + // H264编码要求宽高必须为偶数 20260103 if (VideoWidth % 2 != 0) { VideoWidth++; @@ -158,50 +160,53 @@ namespace dy.net.utils { VideoHeight++; } + var arguments = new List - { - "-y", // 覆盖输出文件 + { + "-y", // 覆盖输出文件 - // 图片序列输入:恢复你原有的 -r,移除可能冲突的参数 - "-f", "image2", - "-r", imageFps.ToString(CultureInfo.InvariantCulture), // 保留你原本的帧率参数 - "-start_number", "0", // 仅增加:避免序列编号问题(不影响原有逻辑) - "-i", imageSequencePattern, // 关键修复:去掉引号(原代码的引号是核心失败原因) + // 图片序列输入:保留原有逻辑,修复帧率含义(-r 是输出帧率,输入用-framerate更准确) + "-f", "image2", + "-framerate", imageFps.ToString(CultureInfo.InvariantCulture), // 输入帧率(图片播放速度) + "-start_number", "0", // 序列起始编号 + "-i", imageSequencePattern, // 图片序列模板(无需引号,后续用ProcessStartInfo传参更安全) - // 音频输入:同样去掉引号 - "-i", audioFilePath, + // 音频输入 + "-i", audioFilePath, - // 滤镜修复:恢复简单写法,避免复杂参数(适配旧版 FFmpeg) - "-filter_complex", $"[0:v]loop={loopCount - 1}[v]", // 去掉额外参数,和你原逻辑一致 + // 滤镜修复:补充size参数(核心!解决Number of frames to loop is not set报错) + // loop=循环次数:size=要循环的帧数;loop=-1表示无限循环(配合-shortest更简洁) + "-filter_complex", $"[0:v]loop=loop={loopCount - 1}:size={imageCount}[v]", - // 流映射:去掉引号,恢复简单写法 - "-map", "[v]", - "-map", "1:a", + // 流映射 + "-map", "[v]", + "-map", "1:a", - // 视频编码:保留你的变量,只增加兼容性参数 - "-c:v", VideoCodec, // 保留你原本的编码变量(之前能跑通,说明该编码支持) - "-preset", VideoPreset, // 保留原变量 - "-crf", $"{VideoCrf}", // 保留原变量 - "-s", $"{VideoWidth}x{VideoHeight}", - "-pix_fmt", "yuv420p", // 仅增加:兼容性关键(不影响原有逻辑) - "-profile:v", "main", // 仅增加:适配多数播放器(不冲突) + // 视频编码:保留你的变量,确保兼容性 + "-c:v", VideoCodec, + "-preset", VideoPreset, + "-crf", $"{VideoCrf}", + "-s", $"{VideoWidth}x{VideoHeight}", + "-pix_fmt", "yuv420p", // 兼容所有播放器 + "-profile:v", "main", - // 音频编码:保留你的变量,修复可能的无效值 - "-c:a", AudioCodec, // 保留原变量 - "-b:a", $"{AudioBitrate}", // 保留原变量 - "-ac", "2", // 仅增加:避免单声道兼容问题 - "-ar", "44100", // 仅增加:标准化采样率 + // 音频编码:标准化参数,避免无效值 + "-c:a", AudioCodec, + "-b:a", $"{AudioBitrate}", + "-ac", "2", // 立体声 + "-ar", "44100", // 标准采样率 - // 封装优化:仅增加关键参数,不影响原有逻辑 - "-f", "mp4", // 明确格式(之前可能自动识别,现在显式声明更稳定) - "-movflags", "+faststart", // 仅增加:解决部分播放器无法播放 + // 封装优化 + "-f", "mp4", + "-movflags", "+faststart", - // 保留你原有的同步参数 - "-shortest", + // 同步参数:确保视频时长匹配音频 + "-shortest", + + // 输出路径 + outputVideoPath + }; - // 输出路径:去掉引号(核心修复点) - outputVideoPath - }; string args = string.Join(" ", arguments); //Console.WriteLine($"执行FFmpeg命令: {_ffmpegExecutablePath} {args}"); @@ -285,7 +290,7 @@ namespace dy.net.utils if (_ffmpegProcess.ExitCode != 0) { - throw new InvalidOperationException($"FFmpeg执行失败,退出码: {_ffmpegProcess.ExitCode}。请查看控制台输出获取详细错误信息。"); + throw new InvalidOperationException($"FFmpeg执行失败,退出码: {_ffmpegProcess.ExitCode}。请查看控制台输出获取详细错误信息。执行命令:ffmpeg {arguments}"); } } finally