implemented logger and M3U8 parser
This commit is contained in:
parent
952e4f290c
commit
9f04c3e9b0
3
.gitignore
vendored
3
.gitignore
vendored
@ -2,4 +2,5 @@ bin/
|
||||
obj/
|
||||
/packages/
|
||||
riderModule.iml
|
||||
/_ReSharper.Caches/
|
||||
/_ReSharper.Caches/
|
||||
.idea/
|
||||
13
.idea/.idea.VkAudioDownloader/.idea/.gitignore
vendored
13
.idea/.idea.VkAudioDownloader/.idea/.gitignore
vendored
@ -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
|
||||
@ -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>
|
||||
@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="UserContentModel">
|
||||
<attachedFolders />
|
||||
<explicitIncludes />
|
||||
<explicitExcludes />
|
||||
</component>
|
||||
</project>
|
||||
@ -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>
|
||||
@ -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
2
DTLib
@ -1 +1 @@
|
||||
Subproject commit f6d045ae2d97691d67bfa82a26e9c100bb3215a1
|
||||
Subproject commit c7016371a53efa266453e0ab4aacaae4fe260e9f
|
||||
@ -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);
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<LangVersion>10</LangVersion>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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" />
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
35
VkAudioDownloader/VkM3U8/AudioDecryptor.cs
Normal file
35
VkAudioDownloader/VkM3U8/AudioDecryptor.cs
Normal 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();
|
||||
}
|
||||
9
VkAudioDownloader/VkM3U8/HLSFragment.cs
Normal file
9
VkAudioDownloader/VkM3U8/HLSFragment.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace VkAudioDownloader.VkM3U8;
|
||||
|
||||
public record struct HLSFragment
|
||||
{
|
||||
public string Name;
|
||||
// public int Duration;
|
||||
public bool Encrypted;
|
||||
public string? EncryptionKeyUrl;
|
||||
}
|
||||
25
VkAudioDownloader/VkM3U8/HLSPlaylist.cs
Normal file
25
VkAudioDownloader/VkM3U8/HLSPlaylist.cs
Normal 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}]";
|
||||
}
|
||||
93
VkAudioDownloader/VkM3U8/M3U8Parser.cs
Normal file
93
VkAudioDownloader/VkM3U8/M3U8Parser.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
39
VkAudioDownloader/VkM3U8/SpanHelper.cs
Normal file
39
VkAudioDownloader/VkM3U8/SpanHelper.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user