implemented logger and M3U8 parser

This commit is contained in:
timerix 2022-11-18 01:59:26 +06:00
parent 952e4f290c
commit 9f04c3e9b0
17 changed files with 262 additions and 66 deletions

3
.gitignore vendored
View File

@ -2,4 +2,5 @@ bin/
obj/
/packages/
riderModule.iml
/_ReSharper.Caches/
/_ReSharper.Caches/
.idea/

View File

@ -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

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="PROJECT" charset="UTF-8" />
</component>
</project>

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="com.jetbrains.rider.android.RiderAndroidMiscFileCreationComponent">
<option name="ENSURE_MISC_FILE_EXISTS" value="true" />
</component>
</project>

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
<mapping directory="$PROJECT_DIR$/DTLib" vcs="Git" />
<mapping directory="$PROJECT_DIR$/VkNet.AudioBypass" vcs="Git" />
<mapping directory="$PROJECT_DIR$/vknet" vcs="Git" />
</component>
</project>

2
DTLib

@ -1 +1 @@
Subproject commit f6d045ae2d97691d67bfa82a26e9c100bb3215a1
Subproject commit c7016371a53efa266453e0ab4aacaae4fe260e9f

View File

@ -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);

View File

@ -4,7 +4,7 @@
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>10</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

View File

@ -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

View File

@ -3,12 +3,14 @@
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>10</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\DTLib\DTLib.Dtsod\DTLib.Dtsod.csproj" />
<ProjectReference Include="..\DTLib\DTLib.Logging\DTLib.Logging.csproj" />
<ProjectReference Include="..\DTLib\DTLib\DTLib.csproj" />
<ProjectReference Include="..\VkNet.AudioBypass\VkNet.AudioBypassService\VkNet.AudioBypassService.csproj" />
<ProjectReference Include="..\vknet\VkNet.Generators\VkNet.Generators.csproj" />

View File

@ -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<LoggerService>();
services.AddAudioBypass();
_logger = logger;
var services = new ServiceCollection()
.Add(new LoggerService<VkApi>(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;
}

View File

@ -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();
}

View File

@ -0,0 +1,9 @@
namespace VkAudioDownloader.VkM3U8;
public record struct HLSFragment
{
public string Name;
// public int Duration;
public bool Encrypted;
public string? EncryptionKeyUrl;
}

View File

@ -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}]";
}

View File

@ -0,0 +1,93 @@
using System;
using System.Collections.Generic;
namespace VkAudioDownloader.VkM3U8;
public class M3U8Parser
{
private string _m3u8="";
private int _pos;
private List<HLSFragment> _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<char> NextLine()
{
int pos = _pos;
int index = _m3u8.IndexOf('\n', pos);
if (index == -1)
index = _m3u8.Length - _pos;
if (index == 0)
return ReadOnlySpan<char>.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<char> 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;
}
}

View File

@ -0,0 +1,39 @@
using System;
namespace VkAudioDownloader.VkM3U8;
internal static class SpanHelper
{
public static ReadOnlySpan<char> After(this ReadOnlySpan<char> 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<char> After(this ReadOnlySpan<char> span, ReadOnlySpan<char> 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<char> Before(this ReadOnlySpan<char> 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<char> Before(this ReadOnlySpan<char> span, ReadOnlySpan<char> s)
{
var index = span.IndexOf(s);
if (index == -1)
throw new Exception($"span <{s}> not found in span <{span}>");
return span.Slice(0,index);
}
}