LaunchArgumentParser
This commit is contained in:
parent
d650dc19e3
commit
24c1868e6b
44
VkAudioDownloader.CLI/LaunchArgument.cs
Normal file
44
VkAudioDownloader.CLI/LaunchArgument.cs
Normal file
@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using DTLib.Extensions;
|
||||
|
||||
namespace VkAudioDownloader.CLI;
|
||||
|
||||
public class LaunchArgument
|
||||
{
|
||||
public string[] Aliases;
|
||||
public string Description;
|
||||
public string? ParamName;
|
||||
public Action? Handler;
|
||||
public Action<string>? HandlerWithArg;
|
||||
|
||||
private LaunchArgument(string[] aliases, string description)
|
||||
{
|
||||
Aliases = aliases;
|
||||
Description = description;
|
||||
}
|
||||
|
||||
public LaunchArgument(string[] aliases, string description, Action handler)
|
||||
: this(aliases, description) => Handler = handler;
|
||||
|
||||
public LaunchArgument(string[] aliases, string description, Action<string> handler, string paramName)
|
||||
: this(aliases, description)
|
||||
{
|
||||
HandlerWithArg = handler;
|
||||
ParamName = paramName;
|
||||
}
|
||||
|
||||
public StringBuilder AppendHelpInfo(StringBuilder b)
|
||||
{
|
||||
b.Append(Aliases[0]);
|
||||
for (int i = 1; i < Aliases.Length; i++)
|
||||
b.Append(", ").Append(Aliases[i]);
|
||||
if (!String.IsNullOrEmpty(ParamName))
|
||||
b.Append(" [").Append(ParamName).Append(']');
|
||||
b.Append(" - ").Append(Description);
|
||||
return b;
|
||||
}
|
||||
|
||||
public override string ToString() =>
|
||||
$"{{{{{Aliases.MergeToString(", ")}}}, Handler: {Handler is null}, HandlerWithArg: {HandlerWithArg is null}}}";
|
||||
}
|
||||
114
VkAudioDownloader.CLI/LaunchArgumentParser.cs
Normal file
114
VkAudioDownloader.CLI/LaunchArgumentParser.cs
Normal file
@ -0,0 +1,114 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace VkAudioDownloader.CLI;
|
||||
|
||||
public class LaunchArgumentParser
|
||||
{
|
||||
private Dictionary<string, LaunchArgument> argDict = new();
|
||||
private List<LaunchArgument> argList = new();
|
||||
public bool ExitIfNoArgs = true;
|
||||
|
||||
public class ExitAfterHelpException : Exception
|
||||
{
|
||||
internal ExitAfterHelpException() : base("your program can use this exception to exit after displaying help message")
|
||||
{ }
|
||||
}
|
||||
|
||||
public string CreateHelpMessage()
|
||||
{
|
||||
StringBuilder b = new();
|
||||
foreach (var arg in argList)
|
||||
arg.AppendHelpInfo(b).Append('\n');
|
||||
b.Remove(b.Length-1, 1);
|
||||
return b.ToString();
|
||||
}
|
||||
public string CreateHelpArgMessage(string argAlias)
|
||||
{
|
||||
StringBuilder b = new();
|
||||
var arg = Get(argAlias);
|
||||
arg.AppendHelpInfo(b);
|
||||
return b.ToString();
|
||||
}
|
||||
private void HelpHandler()
|
||||
{
|
||||
Console.WriteLine(CreateHelpMessage());
|
||||
throw new ExitAfterHelpException();
|
||||
}
|
||||
|
||||
private void HelpArgHandler(string argAlias)
|
||||
{
|
||||
Console.WriteLine(CreateHelpArgMessage(argAlias));
|
||||
throw new ExitAfterHelpException();
|
||||
}
|
||||
|
||||
|
||||
public LaunchArgumentParser()
|
||||
{
|
||||
var help = new LaunchArgument(new[] { "h", "help" },
|
||||
"shows help message", HelpHandler);
|
||||
Add(help);
|
||||
var helpArg = new LaunchArgument( new[]{ "ha", "helparg" },
|
||||
"shows help message for particular argument",
|
||||
HelpArgHandler, "argAlias");
|
||||
Add(helpArg);
|
||||
}
|
||||
|
||||
public LaunchArgumentParser(ICollection<LaunchArgument> arguments) : this()
|
||||
{
|
||||
foreach (var arg in arguments)
|
||||
Add(arg);
|
||||
}
|
||||
public LaunchArgumentParser(params LaunchArgument[] arguments) : this()
|
||||
{
|
||||
for (var i = 0; i < arguments.Length; i++)
|
||||
Add(arguments[i]);
|
||||
}
|
||||
|
||||
public void Add(LaunchArgument arg)
|
||||
{
|
||||
argList.Add(arg);
|
||||
for(int a=0; a<arg.Aliases.Length; a++)
|
||||
argDict.Add(arg.Aliases[a], arg);
|
||||
}
|
||||
|
||||
public LaunchArgument Get(string argAlias)
|
||||
{
|
||||
// different argument providing patterns
|
||||
if (!argDict.TryGetValue(argAlias, out var arg) && // arg
|
||||
!(argAlias.StartsWith("--") && argDict.TryGetValue(argAlias.Substring(2), out arg)) && // --arg
|
||||
!(argAlias.StartsWith('-') && argDict.TryGetValue(argAlias.Substring(1), out arg)) && // -arg
|
||||
!(argAlias.StartsWith('/') && argDict.TryGetValue(argAlias.Substring(1), out arg))) // /arg
|
||||
throw new Exception($"invalid argument: {argAlias}\n{CreateHelpMessage()}");
|
||||
|
||||
return arg;
|
||||
}
|
||||
|
||||
/// <param name="args">program launch args</param>
|
||||
/// <exception cref="Exception">argument {args[i]} should have a parameter after it</exception>
|
||||
/// <exception cref="NullReferenceException">argument hasn't got any handlers</exception>
|
||||
/// <exception cref="ExitAfterHelpException">happens after help message is displayed</exception>
|
||||
public void ParseAndHandle(string[] args)
|
||||
{
|
||||
if (args.Length == 0 && ExitIfNoArgs)
|
||||
{
|
||||
HelpHandler();
|
||||
}
|
||||
|
||||
for (int i = 0; i < args.Length; i++)
|
||||
{
|
||||
LaunchArgument arg = Get(args[i]);
|
||||
|
||||
if (arg.HandlerWithArg is not null)
|
||||
{
|
||||
if (i+1 >= args.Length)
|
||||
throw new Exception($"argument <{args[i]}> should have a parameter after it");
|
||||
arg.HandlerWithArg(args[++i]);
|
||||
}
|
||||
else if (arg.Handler is not null)
|
||||
arg.Handler();
|
||||
else throw new NullReferenceException($"argument <{args[i]}> hasn't got any handlers");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -5,10 +5,13 @@ using System.IO;
|
||||
using DTLib.Extensions;
|
||||
using VkAudioDownloader;
|
||||
using DTLib.Logging.New;
|
||||
using VkAudioDownloader.CLI;
|
||||
|
||||
Console.InputEncoding = StringConverter.UTF8;
|
||||
Console.OutputEncoding = StringConverter.UTF8;
|
||||
|
||||
LaunchArgumentParser argParser = new LaunchArgumentParser();
|
||||
|
||||
if(!File.Exists("config.dtsod"))
|
||||
{
|
||||
File.Copy("config.dtsod.default", "config.dtsod");
|
||||
@ -19,38 +22,48 @@ var config = VkClientConfig.FromDtsod(new DtsodV23(File.ReadAllText("config.dtso
|
||||
|
||||
var logger = new CompositeLogger(new DefaultLogFormat(true),
|
||||
new ConsoleLogger(),
|
||||
new FileLogger("logs", "VkAudioDownloaer"));
|
||||
var _logger = new ContextLogger(logger, "Main");
|
||||
_logger.LogDebug("DEBUG LOG ENABLED");
|
||||
new FileLogger("logs", "VkAudioDownloaer"),
|
||||
new FileLogger("logs", "VkAudioDownloaer_debug") { DebugLogEnabled = true});
|
||||
var mainLoggerContext = new ContextLogger(logger, "Main");
|
||||
mainLoggerContext.LogDebug("DEBUG LOG ENABLED");
|
||||
|
||||
try
|
||||
{
|
||||
#if DEBUG
|
||||
// checking correctness of my aes-128 decryptor on current platform
|
||||
VkAudioDownloader.Helpers.AudioAesDecryptor.TestAes();
|
||||
#endif
|
||||
|
||||
_logger.LogInfo("initializing api...");
|
||||
mainLoggerContext.LogInfo("initializing api...");
|
||||
var client = new VkClient(config, logger);
|
||||
await client.ConnectAsync();
|
||||
|
||||
// getting audio from vk
|
||||
var audios = client.FindAudio("сталинский костюм").ToArray();
|
||||
argParser.Add(new LaunchArgument(new []{"s", "search"}, "search audio on vk.com", SearchAudio, "query"));
|
||||
argParser.ParseAndHandle(args);
|
||||
|
||||
void SearchAudio(string query)
|
||||
{
|
||||
var audios = client.FindAudio(query).ToArray();
|
||||
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}");
|
||||
Console.WriteLine($"selected {audio.AudioToString()}");
|
||||
|
||||
string downloadedFile = client.DownloadAudioAsync(audio, "downloads").GetAwaiter().GetResult();
|
||||
mainLoggerContext.LogInfo($"audio {audio.AudioToString()} downloaded to {downloadedFile}");
|
||||
}
|
||||
}
|
||||
catch (LaunchArgumentParser.ExitAfterHelpException)
|
||||
{
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex);
|
||||
mainLoggerContext.LogError(ex);
|
||||
}
|
||||
Console.ResetColor();
|
||||
|
||||
@ -61,8 +61,7 @@ public class FFMPegHelper
|
||||
opusFile // output
|
||||
})
|
||||
// ffmpeg prints all log to stderr, because in stdout it ptints encoded file
|
||||
.WithStandardErrorPipe(PipeTarget.ToDelegate(
|
||||
msg => _logger.LogWarn(msg)));
|
||||
.WithStandardErrorPipe(PipeTarget.ToDelegate(StdErrHandle));
|
||||
|
||||
tasks[i] = command.ExecuteAsync();
|
||||
output[i] = opusFile;
|
||||
@ -72,6 +71,13 @@ public class FFMPegHelper
|
||||
return output;
|
||||
}
|
||||
|
||||
protected void StdErrHandle(string msg)
|
||||
{
|
||||
if(msg.EndsWith("start time for stream 1 is not set in estimate_timings_from_pts"))
|
||||
_logger.LogDebug(msg);
|
||||
else _logger.LogWarn(msg);
|
||||
}
|
||||
|
||||
public async Task Concat(string outfile, string fragmentListFile, string codec="libopus")
|
||||
{
|
||||
_logger.LogDebug($"{fragmentListFile} -> {outfile}");
|
||||
|
||||
@ -24,6 +24,6 @@
|
||||
<PackageReference Include="VkNet.AudioBypassService" Version="1.7.4" />
|
||||
<PackageReference Include="VkNet" Version="1.72.0" />
|
||||
<PackageReference Include="DTLib.Dtsod" Version="1.0.2" />
|
||||
<PackageReference Include="DTLib.Logging" Version="1.0.5" />
|
||||
<PackageReference Include="DTLib.Logging" Version="1.0.6" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@ -24,15 +24,15 @@ public class VkClient : IDisposable
|
||||
public VkApi Api;
|
||||
public VkClientConfig Config;
|
||||
private DTLib.Logging.New.ContextLogger _logger;
|
||||
private HttpHelper _http;
|
||||
private FFMPegHelper _ffmpeg;
|
||||
public HttpHelper Http;
|
||||
public FFMPegHelper Ffmpeg;
|
||||
|
||||
public VkClient(VkClientConfig conf, DTLib.Logging.New.ILogger logger)
|
||||
{
|
||||
Config = conf;
|
||||
_logger = new ContextLogger(logger, nameof(VkClient));
|
||||
_http = new HttpHelper();
|
||||
_ffmpeg = new FFMPegHelper(logger,conf.FFMPegDir);
|
||||
Http = new HttpHelper();
|
||||
Ffmpeg = new FFMPegHelper(logger,conf.FfmpegDir);
|
||||
var services = new ServiceCollection()
|
||||
.Add(new LoggerService<VkApi>(logger))
|
||||
.AddAudioBypass();
|
||||
@ -44,7 +44,7 @@ public class VkClient : IDisposable
|
||||
var authParams = new ApiAuthParams
|
||||
{
|
||||
ApplicationId = Config.AppId,
|
||||
Settings = Settings.Audio
|
||||
Settings = Settings.Audio,
|
||||
};
|
||||
if (Config.Token is not null)
|
||||
{
|
||||
@ -79,10 +79,11 @@ public class VkClient : IDisposable
|
||||
Count = maxRezults,
|
||||
});
|
||||
|
||||
|
||||
///<returns>file name</returns>
|
||||
public Task<string> DownloadAudioAsync(Audio audio, string localDir) =>
|
||||
DownloadAudioAsync(audio, localDir,TimeSpan.FromHours(1));
|
||||
|
||||
///<returns>file name</returns>
|
||||
public async Task<string> DownloadAudioAsync(Audio audio, string localDir, TimeSpan durationLimit)
|
||||
{
|
||||
if (!audio.Url.ToString().StartsWith("http"))
|
||||
@ -93,16 +94,16 @@ public class VkClient : IDisposable
|
||||
if(File.Exists(outFile))
|
||||
_logger.LogWarn( $"file {outFile} already exists");
|
||||
|
||||
string m3u8 = await _http.GetStringAsync(audio.Url);
|
||||
string m3u8 = await Http.GetStringAsync(audio.Url);
|
||||
var parser = new M3U8Parser();
|
||||
var hls = parser.Parse(audio.Url, m3u8);
|
||||
if (hls.Duration > durationLimit.TotalSeconds)
|
||||
throw new Exception($"duration limit <{durationLimit}> exceeded by track <{audio}> - <{hls.Duration}>");
|
||||
|
||||
await _http.DownloadAsync(hls, fragmentDir);
|
||||
string[] opusFragments = await _ffmpeg.ToOpus(fragmentDir);
|
||||
string listFile = _ffmpeg.CreateFragmentList(fragmentDir, opusFragments);
|
||||
await _ffmpeg.Concat(outFile, listFile);
|
||||
await Http.DownloadAsync(hls, fragmentDir);
|
||||
string[] opusFragments = await Ffmpeg.ToOpus(fragmentDir);
|
||||
string listFile = Ffmpeg.CreateFragmentList(fragmentDir, opusFragments);
|
||||
await Ffmpeg.Concat(outFile, listFile);
|
||||
Directory.Delete(fragmentDir);
|
||||
|
||||
return outFile;
|
||||
@ -113,7 +114,7 @@ public class VkClient : IDisposable
|
||||
{
|
||||
if (_disposed) return;
|
||||
Api.Dispose();
|
||||
_http.Dispose();
|
||||
Http.Dispose();
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@ namespace VkAudioDownloader;
|
||||
public class VkClientConfig
|
||||
{
|
||||
/// directory where ffmpeg and ffprobe binaries are stored
|
||||
public string FFMPegDir;
|
||||
public string FfmpegDir;
|
||||
/// vk app id from https://vk.com/apps?act=manage
|
||||
public ulong AppId;
|
||||
/// account password
|
||||
@ -16,27 +16,37 @@ public class VkClientConfig
|
||||
public string? Token;
|
||||
|
||||
|
||||
public VkClientConfig(string ffmPegDir, ulong appId, string? token)
|
||||
public VkClientConfig(string ffmpegDir, ulong appId, string? token)
|
||||
{
|
||||
FFMPegDir = ffmPegDir;
|
||||
FfmpegDir = ffmpegDir;
|
||||
AppId = appId;
|
||||
Token = token;
|
||||
}
|
||||
|
||||
public VkClientConfig(string ffmPegDir, ulong appId, string? password, string? login)
|
||||
public VkClientConfig(string ffmpegDir, ulong appId, string? password, string? login)
|
||||
{
|
||||
FFMPegDir = ffmPegDir;
|
||||
FfmpegDir = ffmpegDir;
|
||||
AppId = appId;
|
||||
Password = password;
|
||||
Login = login;
|
||||
}
|
||||
|
||||
private VkClientConfig(string ffmPegDir, ulong appId)
|
||||
private VkClientConfig(string ffmpegDir, ulong appId)
|
||||
{
|
||||
FFMPegDir = ffmPegDir;
|
||||
FfmpegDir = ffmpegDir;
|
||||
AppId = appId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// {
|
||||
/// ffmpeg_dir: "";
|
||||
/// app_id: 0ul;
|
||||
/// token: "";
|
||||
/// #or
|
||||
/// login: "";
|
||||
/// password: "";
|
||||
/// };
|
||||
/// </summary>
|
||||
public static VkClientConfig FromDtsod(DtsodV23 dtsod)
|
||||
{
|
||||
var config = new VkClientConfig(dtsod["ffmpeg_dir"], dtsod["app_id"]);
|
||||
@ -56,7 +66,7 @@ public class VkClientConfig
|
||||
{ "password", Password ?? null },
|
||||
{ "login", Login ?? null },
|
||||
{ "token", Token ?? null },
|
||||
{ "ffmpeg_dir", FFMPegDir}
|
||||
{ "ffmpeg_dir", FfmpegDir}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user