diff --git a/.gitignore b/.gitignore
index add57be..f400df3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,4 +2,5 @@ bin/
obj/
/packages/
riderModule.iml
-/_ReSharper.Caches/
\ No newline at end of file
+/_ReSharper.Caches/
+.idea/
\ No newline at end of file
diff --git a/.idea/.idea.VkAudioDownloader/.idea/.gitignore b/.idea/.idea.VkAudioDownloader/.idea/.gitignore
deleted file mode 100644
index d67a4ef..0000000
--- a/.idea/.idea.VkAudioDownloader/.idea/.gitignore
+++ /dev/null
@@ -1,13 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
-# Rider ignored files
-/contentModel.xml
-/.idea.VkAudioDownloader.iml
-/modules.xml
-/projectSettingsUpdater.xml
-# Editor-based HTTP Client requests
-/httpRequests/
-# Datasource local storage ignored files
-/dataSources/
-/dataSources.local.xml
diff --git a/.idea/.idea.VkAudioDownloader/.idea/encodings.xml b/.idea/.idea.VkAudioDownloader/.idea/encodings.xml
deleted file mode 100644
index 97626ba..0000000
--- a/.idea/.idea.VkAudioDownloader/.idea/encodings.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/.idea.VkAudioDownloader/.idea/indexLayout.xml b/.idea/.idea.VkAudioDownloader/.idea/indexLayout.xml
deleted file mode 100644
index f5a863a..0000000
--- a/.idea/.idea.VkAudioDownloader/.idea/indexLayout.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/.idea.VkAudioDownloader/.idea/misc.xml b/.idea/.idea.VkAudioDownloader/.idea/misc.xml
deleted file mode 100644
index f98d778..0000000
--- a/.idea/.idea.VkAudioDownloader/.idea/misc.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/.idea.VkAudioDownloader/.idea/vcs.xml b/.idea/.idea.VkAudioDownloader/.idea/vcs.xml
deleted file mode 100644
index cfebfd3..0000000
--- a/.idea/.idea.VkAudioDownloader/.idea/vcs.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/DTLib b/DTLib
index f6d045a..c701637 160000
--- a/DTLib
+++ b/DTLib
@@ -1 +1 @@
-Subproject commit f6d045ae2d97691d67bfa82a26e9c100bb3215a1
+Subproject commit c7016371a53efa266453e0ab4aacaae4fe260e9f
diff --git a/VkAudioDownloader.CLI/Program.cs b/VkAudioDownloader.CLI/Program.cs
index 74b8de9..c04bde1 100644
--- a/VkAudioDownloader.CLI/Program.cs
+++ b/VkAudioDownloader.CLI/Program.cs
@@ -1,11 +1,30 @@
-using DTLib.Dtsod;
+using System;
+using System.Linq;
+using System.Net.Http;
+using DTLib.Dtsod;
+using DTLib.Filesystem;
using VkAudioDownloader;
+using DTLib.Logging.New;
+using VkAudioDownloader.VkM3U8;
-var client = new VkClient(VkClientConfig.FromDtsod(new DtsodV23(File.ReadAllText("config.dtsod"))));
+
+if(!File.Exists("config.dtsod"))
+ File.Copy("config.dtsod.default","config.dtsod");
+
+var logger = new CompositeLogger(new DefaultLogFormat(true),
+ new ConsoleLogger(),
+ new FileLogger("logs", "VkAudioDownloaer"));
+var client = new VkClient(
+ VkClientConfig.FromDtsod(new DtsodV23(File.ReadAllText("config.dtsod"))),
+ logger);
+logger.Log("main", LogSeverity.Info, "initializing api...");
+logger.DebugLogEnabled = true;
client.Connect();
-Console.WriteLine(client.Api.Token);
-var audios = client.FindAudio("моя оборона");
-foreach (var a in audios)
-{
- Console.WriteLine(a.Title);
-}
+var audio = client.FindAudio("гражданская оборона", 1).First();
+Console.WriteLine($"{audio.Title} -- {audio.Artist} [{TimeSpan.FromSeconds(audio.Duration)}]");
+var Http = new HttpClient();
+var m3u8 = await Http.GetStringAsync(audio.Url);
+Console.WriteLine("downloaded m3u8 playlist:\n" + m3u8);
+var parser = new M3U8Parser();
+var HLSPlaylist = parser.Parse(audio.Url, m3u8);
+Console.WriteLine(HLSPlaylist);
diff --git a/VkAudioDownloader.CLI/VkAudioDownloader.CLI.csproj b/VkAudioDownloader.CLI/VkAudioDownloader.CLI.csproj
index 30f41bc..3a34438 100644
--- a/VkAudioDownloader.CLI/VkAudioDownloader.CLI.csproj
+++ b/VkAudioDownloader.CLI/VkAudioDownloader.CLI.csproj
@@ -4,7 +4,7 @@
Exe
net6.0
10
- enable
+ disable
enable
diff --git a/VkAudioDownloader.sln b/VkAudioDownloader.sln
index f47fb9a..61ecc0d 100644
--- a/VkAudioDownloader.sln
+++ b/VkAudioDownloader.sln
@@ -14,6 +14,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VkNet.Generators", "vknet\V
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VkNet.AudioBypassService", "VkNet.AudioBypass\VkNet.AudioBypassService\VkNet.AudioBypassService.csproj", "{5650A6DD-602D-49FF-A0B0-DB59BD63EACE}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sulution_items", "sulution_items", "{2CBCAE99-53A3-4ADE-A08B-5755EC471878}"
+ ProjectSection(SolutionItems) = preProject
+ .gitignore = .gitignore
+ .gitmodules = .gitmodules
+ EndProjectSection
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DTLib.Logging", "DTLib\DTLib.Logging\DTLib.Logging.csproj", "{A087B535-371A-4A7E-883E-B5B290567E9A}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -48,5 +56,9 @@ Global
{5650A6DD-602D-49FF-A0B0-DB59BD63EACE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5650A6DD-602D-49FF-A0B0-DB59BD63EACE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5650A6DD-602D-49FF-A0B0-DB59BD63EACE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A087B535-371A-4A7E-883E-B5B290567E9A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A087B535-371A-4A7E-883E-B5B290567E9A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A087B535-371A-4A7E-883E-B5B290567E9A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A087B535-371A-4A7E-883E-B5B290567E9A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
diff --git a/VkAudioDownloader/VkAudioDownloader.csproj b/VkAudioDownloader/VkAudioDownloader.csproj
index c8414fa..a6281c0 100644
--- a/VkAudioDownloader/VkAudioDownloader.csproj
+++ b/VkAudioDownloader/VkAudioDownloader.csproj
@@ -3,12 +3,14 @@
net6.0
10
- enable
+ disable
enable
+ true
+
diff --git a/VkAudioDownloader/VkClient.cs b/VkAudioDownloader/VkClient.cs
index 24e5c6b..0326e61 100644
--- a/VkAudioDownloader/VkClient.cs
+++ b/VkAudioDownloader/VkClient.cs
@@ -1,6 +1,6 @@
-global using DTLib;
-global using DTLib.Extensions;
-using DTLib.Logging;
+using System;
+using System.IO;
+using System.Net.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using VkNet;
@@ -9,8 +9,10 @@ using VkNet.Model;
using VkNet.Model.RequestParams;
using VkNet.Utils;
using VkNet.Model.Attachments;
-using VkNet.AudioBypassService;
using VkNet.AudioBypassService.Extensions;
+using DTLib.Logging.DependencyInjection;
+using DTLib.Logging.New;
+using ILogger = DTLib.Logging.New.ILogger;
namespace VkAudioDownloader;
@@ -20,15 +22,16 @@ public class VkClient : IDisposable
{
public VkApi Api;
public VkClientConfig Config;
-
- public VkClient(VkClientConfig conf)
+ private ILogger _logger;
+
+ public VkClient(VkClientConfig conf, ILogger logger)
{
Config = conf;
- var services = new ServiceCollection();
- //services.AddSingleton();
- services.AddAudioBypass();
+ _logger = logger;
+ var services = new ServiceCollection()
+ .Add(new LoggerService(logger))
+ .AddAudioBypass();
Api = new VkApi(services);
-
}
public void Connect()
@@ -40,12 +43,12 @@ public class VkClient : IDisposable
};
if (Config.Token is not null)
{
- Console.WriteLine("authorizing by token");
+ _logger.Log(nameof(VkClient),LogSeverity.Info,"authorizing by token");
authParams.AccessToken = Config.Token;
}
else
{
- Console.WriteLine("authorizing by login and password");
+ _logger.Log(nameof(VkClient),LogSeverity.Info,"authorizing by login and password");
authParams.Login = Config.Login;
authParams.Password = Config.Password;
}
diff --git a/VkAudioDownloader/VkM3U8/AudioDecryptor.cs b/VkAudioDownloader/VkM3U8/AudioDecryptor.cs
new file mode 100644
index 0000000..f115d28
--- /dev/null
+++ b/VkAudioDownloader/VkM3U8/AudioDecryptor.cs
@@ -0,0 +1,35 @@
+using System;
+using System.IO;
+using System.Security.Cryptography;
+
+namespace VkAudioDownloader.VkM3U8;
+
+public class AudioDecryptor : IDisposable
+{
+ private Aes Aes;
+ private ICryptoTransform Decryptor;
+
+ AudioDecryptor()
+ {
+ Aes=Aes.Create();
+ Aes.KeySize = 128;
+ Aes.Mode = CipherMode.CBC;
+ Aes.IV = new byte[4] { 0, 0, 0, 0 };
+ Aes.Padding = PaddingMode.Zeros;
+ Decryptor = Aes.CreateDecryptor();
+ }
+
+ public Stream Decrypt(Stream fragment)
+ => new CryptoStream(fragment, Decryptor, CryptoStreamMode.Read);
+
+ private bool _disposed;
+ public void Dispose()
+ {
+ if(_disposed) return;
+ Aes.Dispose();
+ Decryptor.Dispose();
+ _disposed = true;
+ }
+
+ ~AudioDecryptor() => Dispose();
+}
\ No newline at end of file
diff --git a/VkAudioDownloader/VkM3U8/HLSFragment.cs b/VkAudioDownloader/VkM3U8/HLSFragment.cs
new file mode 100644
index 0000000..342435c
--- /dev/null
+++ b/VkAudioDownloader/VkM3U8/HLSFragment.cs
@@ -0,0 +1,9 @@
+namespace VkAudioDownloader.VkM3U8;
+
+public record struct HLSFragment
+{
+ public string Name;
+ // public int Duration;
+ public bool Encrypted;
+ public string? EncryptionKeyUrl;
+}
\ No newline at end of file
diff --git a/VkAudioDownloader/VkM3U8/HLSPlaylist.cs b/VkAudioDownloader/VkM3U8/HLSPlaylist.cs
new file mode 100644
index 0000000..aeeca9d
--- /dev/null
+++ b/VkAudioDownloader/VkM3U8/HLSPlaylist.cs
@@ -0,0 +1,25 @@
+namespace VkAudioDownloader.VkM3U8;
+
+public class HLSPlaylist
+{
+
+ public HLSFragment[] Fragments { get; internal set; }
+
+ /// content duration in seconds
+ public int Duration { get; internal set; }
+
+ /// url before index.m3u8
+ public string BaseUrl { get; internal set; }
+
+ internal HLSPlaylist(HLSFragment[] fragments, int duration, string baseUrl)
+ {
+ Fragments = fragments;
+ Duration = duration;
+ BaseUrl = baseUrl;
+ }
+
+ public override string ToString() =>
+ $"BaseUrl: {BaseUrl}\n" +
+ $"Duration: {Duration}\n" +
+ $"Fragments: HLSFragment[{Fragments.Length}]";
+}
\ No newline at end of file
diff --git a/VkAudioDownloader/VkM3U8/M3U8Parser.cs b/VkAudioDownloader/VkM3U8/M3U8Parser.cs
new file mode 100644
index 0000000..e9a0a31
--- /dev/null
+++ b/VkAudioDownloader/VkM3U8/M3U8Parser.cs
@@ -0,0 +1,93 @@
+using System;
+using System.Collections.Generic;
+
+namespace VkAudioDownloader.VkM3U8;
+
+public class M3U8Parser
+{
+ private string _m3u8="";
+ private int _pos;
+ private List _fragments = new();
+ private int _playlistDuration = 0;
+ private HLSFragment _currentFragment = default;
+
+ // parses m3u8 playlist and resets state
+ public HLSPlaylist Parse(Uri m3u8Url, string m3u8Content)
+ {
+ _m3u8 = m3u8Content;
+ var line = NextLine();
+ while (!line.IsEmpty)
+ {
+ if (line.Contains('#'))
+ ParseHashTag(line);
+ else
+ {
+ _currentFragment.Name = line.ToString();
+ _fragments.Add(_currentFragment);
+ _currentFragment = default;
+ }
+
+ line = NextLine();
+ }
+
+ string urlStr = m3u8Url.ToString();
+ var rezult = new HLSPlaylist(
+ _fragments.ToArray(),
+ _playlistDuration,
+ urlStr.Remove(urlStr.LastIndexOf('/')+1));
+ Clear();
+ return rezult;
+ }
+
+ ReadOnlySpan NextLine()
+ {
+ int pos = _pos;
+ int index = _m3u8.IndexOf('\n', pos);
+ if (index == -1)
+ index = _m3u8.Length - _pos;
+ if (index == 0)
+ return ReadOnlySpan.Empty;
+ _pos = index+1;
+ if (_m3u8[index - 1] == '\r')
+ index--; // skip /r
+ var line = _m3u8.AsSpan(pos, index - pos);
+ return line;
+ }
+
+ private void ParseHashTag(ReadOnlySpan line)
+ {
+ if(line.StartsWith("EXT-X-TARGETDURATION:"))
+ _playlistDuration=Int32.Parse(line.After(':'));
+ else if (line.StartsWith("#EXT-X-KEY:METHOD="))
+ {
+ var method = line.After("#EXT-X-KEY:METHOD=");
+
+ if (method.ToString() == "NONE")
+ {
+ _currentFragment.Encrypted = false;
+ return;
+ }
+
+ var alg = method.Before(',');
+ if (alg.ToString() != "AES-128")
+ throw new Exception($"unknown encryption algorythm: {method}");
+
+ var keyUrl=method.After("URI=\"").Before('\"');
+ if (!keyUrl.StartsWith("http"))
+ throw new Exception($"key uri is not url: {keyUrl}");
+
+ // AES-128 which AudioDecryptor can decrypt
+ _currentFragment.Encrypted = true;
+ _currentFragment.EncryptionKeyUrl = keyUrl.ToString();
+ }
+ }
+
+ private void Clear()
+ {
+ _m3u8 = "";
+ _pos=0;
+ _fragments.Clear();
+ _currentFragment = default;
+ _playlistDuration = 0;
+ }
+}
\ No newline at end of file
diff --git a/VkAudioDownloader/VkM3U8/SpanHelper.cs b/VkAudioDownloader/VkM3U8/SpanHelper.cs
new file mode 100644
index 0000000..648f33e
--- /dev/null
+++ b/VkAudioDownloader/VkM3U8/SpanHelper.cs
@@ -0,0 +1,39 @@
+using System;
+
+namespace VkAudioDownloader.VkM3U8;
+
+internal static class SpanHelper
+{
+ public static ReadOnlySpan After(this ReadOnlySpan span, char c)
+ {
+ var index = span.IndexOf(c);
+ if (index == -1)
+ throw new Exception($"char <{c}> not found in span <{span}>");
+ return span.Slice(index+1);
+ }
+
+ public static ReadOnlySpan After(this ReadOnlySpan span, ReadOnlySpan s)
+ {
+ var index = span.IndexOf(s);
+ if (index == -1)
+ throw new Exception($"span <{s}> not found in span <{span}>");
+ return span.Slice(index+s.Length);
+ }
+
+
+ public static ReadOnlySpan Before(this ReadOnlySpan span, char c)
+ {
+ var index = span.IndexOf(c);
+ if (index == -1)
+ throw new Exception($"char <{c}> not found in span <{span}>");
+ return span.Slice(0,index);
+ }
+
+ public static ReadOnlySpan Before(this ReadOnlySpan span, ReadOnlySpan s)
+ {
+ var index = span.IndexOf(s);
+ if (index == -1)
+ throw new Exception($"span <{s}> not found in span <{span}>");
+ return span.Slice(0,index);
+ }
+}
\ No newline at end of file