From 0443cc652758caec6a3a24d21a0db0bcb3c2e21e Mon Sep 17 00:00:00 2001 From: timerix Date: Tue, 6 Dec 2022 02:56:41 +0600 Subject: [PATCH] ffmpeg wrapper --- DTLib | 2 +- VkAudioDownloader.CLI/Program.cs | 68 ++++++------ VkAudioDownloader.CLI/config.dtsod.default | 5 +- VkAudioDownloader.sln.DotSettings | 2 + VkAudioDownloader/AudioHelper.cs | 12 ++ VkAudioDownloader/FFMPegHelper.cs | 104 ++++++++++++++++++ VkAudioDownloader/VkAudioDownloader.csproj | 4 + VkAudioDownloader/VkClient.cs | 70 +++++++++--- VkAudioDownloader/VkClientConfig.cs | 33 +++++- VkAudioDownloader/VkM3U8/AudioAesDecryptor.cs | 3 +- VkAudioDownloader/VkM3U8/HLSPlaylist.cs | 22 +--- VkAudioDownloader/VkM3U8/HttpHelper.cs | 10 +- VkAudioDownloader/VkM3U8/M3U8Parser.cs | 1 + VkAudioDownloader/VkM3U8/SpanHelper.cs | 39 ------- 14 files changed, 252 insertions(+), 123 deletions(-) create mode 100644 VkAudioDownloader.sln.DotSettings create mode 100644 VkAudioDownloader/AudioHelper.cs create mode 100644 VkAudioDownloader/FFMPegHelper.cs delete mode 100644 VkAudioDownloader/VkM3U8/SpanHelper.cs diff --git a/DTLib b/DTLib index eec2ec6..3ebb5be 160000 --- a/DTLib +++ b/DTLib @@ -1 +1 @@ -Subproject commit eec2ec60bee84dc9b307c2e0a2803e399fca02ca +Subproject commit 3ebb5be5819fa46d36705a5752aecd885c406a8b diff --git a/VkAudioDownloader.CLI/Program.cs b/VkAudioDownloader.CLI/Program.cs index e36a71f..ea6d44f 100644 --- a/VkAudioDownloader.CLI/Program.cs +++ b/VkAudioDownloader.CLI/Program.cs @@ -6,45 +6,47 @@ using VkAudioDownloader; using DTLib.Logging.New; using VkAudioDownloader.VkM3U8; - if(!File.Exists("config.dtsod")) - File.Copy("config.dtsod.default","config.dtsod"); +{ + File.Copy("config.dtsod.default", "config.dtsod"); + throw new Exception("No config detected, default created. Edit it!"); +} + +var config = VkClientConfig.FromDtsod(new DtsodV23(File.ReadAllText("config.dtsod"))); var logger = new CompositeLogger(new DefaultLogFormat(true), new ConsoleLogger(), new FileLogger("logs", "VkAudioDownloaer")); +var _logger = new LoggerContext(logger, "main"); + +try +{ #if DEBUG -logger.DebugLogEnabled = true; + AudioAesDecryptor.TestAes(); #endif -AudioAesDecryptor.TestAes(); - -var client = new VkClient( - VkClientConfig.FromDtsod(new DtsodV23(File.ReadAllText("config.dtsod"))), - logger); -logger.Log("main", LogSeverity.Debug, "initializing api..."); -client.Connect(); + + var client = new VkClient(config, logger); + _logger.LogDebug("initializing api..."); + await client.ConnectAsync(); + // getting audio from vk -var http = new HttpHelper(); -var audio = client.FindAudio("гражданская оборона", 1).First(); -Console.WriteLine($"{audio.Title} -- {audio.Artist} [{TimeSpan.FromSeconds(audio.Duration)}]"); -var m3u8 = await http.GetStringAsync(audio.Url); -Console.WriteLine("downloaded m3u8 playlist\n"); -// parsing index.m3u8 -var parser = new M3U8Parser(); -var playlist = parser.Parse(audio.Url, m3u8); -Console.WriteLine(playlist); -// downloading parts -var frag = playlist.Fragments[3]; -var kurl =frag.EncryptionKeyUrl ?? throw new NullReferenceException(); -await http.DownloadAsync(kurl, "key.pub"); -if(Directory.Exists("playlist")) - Directory.Delete("playlist",true); -Directory.CreateDirectory("playlist"); -await http.DownloadAsync(playlist, "playlist"); + var audios = client.FindAudio("сталинский костюм").ToArray(); -// var decryptor = new AudioAesDecryptor(); -// string key = "cca42800074d7aeb"; -// using var encryptedFile = File.Open("encrypted.ts", FileMode.Open, FileAccess.ReadWrite); -// using var cryptoStream = decryptor.DecryptStream(encryptedFile, key); -// using var decryptedFile = File.Open("out.ts", FileMode.Create); -// await cryptoStream.CopyToAsync(decryptedFile); + for (var i = 0; i < audios.Length; i++) + { + var a = audios[i]; + Console.WriteLine($"[{i}] {a.AudioToString()}"); + } + + Console.Write("choose audio: "); + int ain = Convert.ToInt32(Console.ReadLine()); + var audio = audios[ain]; + Console.WriteLine($"selected \"{audio.Title}\" -- {audio.Artist} [{TimeSpan.FromSeconds(audio.Duration)}]"); + // downloading parts + string downloadedFile = await client.DownloadAudioAsync(audio, "downloads"); + _logger.LogInfo($"audio {audio.AudioToString()} downloaded to {downloadedFile}"); +} +catch (Exception ex) +{ + _logger.LogException(ex); +} diff --git a/VkAudioDownloader.CLI/config.dtsod.default b/VkAudioDownloader.CLI/config.dtsod.default index e5867dc..b20ffed 100644 --- a/VkAudioDownloader.CLI/config.dtsod.default +++ b/VkAudioDownloader.CLI/config.dtsod.default @@ -1,3 +1,4 @@ app_id: 0ul; -login: " "; -password: " "; +login: ""; +password: ""; +ffmpeg_dir: ""; diff --git a/VkAudioDownloader.sln.DotSettings b/VkAudioDownloader.sln.DotSettings new file mode 100644 index 0000000..06b71d5 --- /dev/null +++ b/VkAudioDownloader.sln.DotSettings @@ -0,0 +1,2 @@ + + False \ No newline at end of file diff --git a/VkAudioDownloader/AudioHelper.cs b/VkAudioDownloader/AudioHelper.cs new file mode 100644 index 0000000..2b89020 --- /dev/null +++ b/VkAudioDownloader/AudioHelper.cs @@ -0,0 +1,12 @@ +using System; +using System.Runtime.CompilerServices; +using VkNet.Model.Attachments; + +namespace VkAudioDownloader; + +public static class AudioHelper +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string AudioToString(this Audio a) + => $"\"{a.Title}\" -- {a.Artist} ({TimeSpan.FromSeconds(a.Duration)})"; +} \ No newline at end of file diff --git a/VkAudioDownloader/FFMPegHelper.cs b/VkAudioDownloader/FFMPegHelper.cs new file mode 100644 index 0000000..e8b5420 --- /dev/null +++ b/VkAudioDownloader/FFMPegHelper.cs @@ -0,0 +1,104 @@ +using System; +using CliWrap; +using DTLib.Filesystem; +using DTLib.Extensions; +using DTLib.Logging.New; + +namespace VkAudioDownloader; + +public class FFMPegHelper +{ + private LoggerContext _logger; + private readonly string ffmpeg; + public FFMPegHelper(ILogger logger, string ffmpegDir) + { + _logger = new LoggerContext(logger, nameof(FFMPegHelper)); + ffmpeg=Path.Concat(ffmpegDir,"ffmpeg"); + } + + /// creates fragments list for ffmppeg concat + /// there file list.txt will be created + /// audio files in fragmentsDir (with or without dir in path) + /// + public string CreateFragmentList(string fragmentsDir, string[] fragments) + { + string listFile = Path.Concat(fragmentsDir, "list.txt"); + using var playlistFile = File.OpenWrite(listFile); + for (var i = 0; i < fragments.Length; i++) + { + var clearFileName = fragments[i].AsSpan().AfterLast(Path.Sep); + playlistFile.Write($"file '{clearFileName}'\n".ToBytes(StringConverter.UTF8)); + } + + return listFile; + } + + + /// converts ts files in directory to opus + /// directory with ts fragment files + /// paths to created opus files + public Task ToOpus(string localDir) => + ToOpus(Directory.GetFiles(localDir, "*.ts")); + + /// + /// converts ts files in to opus + /// + /// ts fragment files + /// paths to created opus files + public async Task ToOpus(string[] fragments) + { + string[] output = new string[fragments.Length]; + var tasks = new Task[fragments.Length]; + + for (var i = 0; i < fragments.Length; i++) + { + string tsFile = fragments[i]; + string opusFile = tsFile.Replace(".ts",".opus"); + _logger.LogDebug($"{tsFile} -> {opusFile}"); + var command = Cli.Wrap(ffmpeg).WithArguments(new[] + { + "-i", tsFile, // input + "-loglevel", "warning", "-hide_banner", "-nostats", // print only warnings and errors + "-map", "0:0", // select first audio track (sometimes there are blank buggy second thack) + "-filter:a", "asetpts=PTS-STARTPTS", // fixes pts + "-c", "libopus", "-b:a", "96k", // encoding params + opusFile // output + }) + // ffmpeg prints all log to stderr, because in stdout it ptints encoded file + .WithStandardErrorPipe(PipeTarget.ToDelegate( + msg => _logger.LogWarn(msg))); + + tasks[i] = command.ExecuteAsync(); + output[i] = opusFile; + } + + await Task.WhenAll(tasks); + return output; + } + + public async Task Concat(string outfile, string fragmentListFile, string codec="libopus") + { + _logger.LogDebug($"{fragmentListFile} -> {outfile}"); + var command = Cli.Wrap(ffmpeg).WithArguments(new[] + { + "-f", "concat", // mode + "-i", fragmentListFile, // input list + "-loglevel", "warning", "-hide_banner", "-nostats", // print only warnings and errors + "-filter:a", "asetpts=PTS-STARTPTS", // fixes pts + "-c", codec, "-b:a", "96k", // encoding params + outfile, "-y" // output override + }) + // ffmpeg prints all log to stderr, because in stdout it ptints encoded file + .WithStandardErrorPipe(PipeTarget.ToDelegate( + msg => _logger.LogWarn(msg))) + .WithValidation(CommandResultValidation.None); + + var rezult =await command.ExecuteAsync(); + // log time + if (rezult.ExitCode != 0) + { + _logger.LogError($"command failed with code {rezult.ExitCode}"); + throw new Exception($"command: {command} failed"); + } + } +} \ No newline at end of file diff --git a/VkAudioDownloader/VkAudioDownloader.csproj b/VkAudioDownloader/VkAudioDownloader.csproj index a6281c0..d89851f 100644 --- a/VkAudioDownloader/VkAudioDownloader.csproj +++ b/VkAudioDownloader/VkAudioDownloader.csproj @@ -17,4 +17,8 @@ + + + + diff --git a/VkAudioDownloader/VkClient.cs b/VkAudioDownloader/VkClient.cs index 0326e61..d2c8105 100644 --- a/VkAudioDownloader/VkClient.cs +++ b/VkAudioDownloader/VkClient.cs @@ -1,6 +1,4 @@ using System; -using System.IO; -using System.Net.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using VkNet; @@ -12,7 +10,8 @@ using VkNet.Model.Attachments; using VkNet.AudioBypassService.Extensions; using DTLib.Logging.DependencyInjection; using DTLib.Logging.New; -using ILogger = DTLib.Logging.New.ILogger; +using DTLib.Filesystem; +using VkAudioDownloader.VkM3U8; namespace VkAudioDownloader; @@ -22,19 +21,23 @@ public class VkClient : IDisposable { public VkApi Api; public VkClientConfig Config; - private ILogger _logger; - - public VkClient(VkClientConfig conf, ILogger logger) + private DTLib.Logging.New.ILogger _logger; + private HttpHelper _http; + private FFMPegHelper _ffmpeg; + + public VkClient(VkClientConfig conf, DTLib.Logging.New.ILogger logger) { Config = conf; _logger = logger; + _http = new HttpHelper(); + _ffmpeg = new FFMPegHelper(logger,conf.FFMPegDir); var services = new ServiceCollection() .Add(new LoggerService(logger)) .AddAudioBypass(); Api = new VkApi(services); } - public void Connect() + public async Task ConnectAsync(int attempts=5) { var authParams = new ApiAuthParams { @@ -52,7 +55,19 @@ public class VkClient : IDisposable authParams.Login = Config.Login; authParams.Password = Config.Password; } - Api.Authorize(authParams); + + for (int authAttempt = 0; authAttempt < attempts; authAttempt++) + { + try + { + await Api.AuthorizeAsync(authParams); + break; + } + catch (Exception aex) + { + _logger.LogException(nameof(VkClient),aex); + } + } } public VkCollection