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