second commit
This commit is contained in:
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# Xmake cache
|
||||||
|
.xmake/
|
||||||
|
build/
|
||||||
|
|
||||||
|
# MacOS Cache
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
|
||||||
|
.vscode
|
||||||
@@ -7,13 +7,28 @@
|
|||||||
<title>Document</title>
|
<title>Document</title>
|
||||||
<script
|
<script
|
||||||
src="https://cdn.jsdelivr.net/combine/npm/tone@14.7.58,npm/@magenta/music@1.23.1/es6/core.js,npm/focus-visible@5,npm/html-midi-player@1.5.0"></script>
|
src="https://cdn.jsdelivr.net/combine/npm/tone@14.7.58,npm/@magenta/music@1.23.1/es6/core.js,npm/focus-visible@5,npm/html-midi-player@1.5.0"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<midi-player src="indie.mid" sound-font
|
<midi-player src="indie.mid" sound-font
|
||||||
visualizer="#myVisualizer">
|
visualizer="#myVisualizer">
|
||||||
</midi-player>
|
</midi-player>
|
||||||
<midi-visualizer type="piano-roll" id="myVisualizer"></midi-visualizer>
|
<midi-visualizer type="piano-roll" id="myVisualizer"></midi-visualizer>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|
||||||
|
<!--
|
||||||
|
|
||||||
|
|
||||||
|
Prendre un fichier midi :
|
||||||
|
|
||||||
|
- Sélectionner un passage
|
||||||
|
- Sélectionner les instruments
|
||||||
|
- Générer les fichiers midi
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-->
|
||||||
|
|||||||
48
src/main.cpp
Normal file
48
src/main.cpp
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
#include "stx-execpipe.h"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
static const std::vector<std::string> links = {
|
||||||
|
"2gQ--SCEfmI"};
|
||||||
|
|
||||||
|
static const std::string YTDLP_BINARY = "/usr/bin/yt-dlp";
|
||||||
|
|
||||||
|
void DownloadFile()
|
||||||
|
{
|
||||||
|
stx::ExecPipe ep;
|
||||||
|
|
||||||
|
std::string link = "https://music.youtube.com/watch?v=" + links.front();
|
||||||
|
|
||||||
|
std::vector<std::string> args = {YTDLP_BINARY, link.c_str(), "--extract-audio", "--audio-format", "vorbis", "-o", "%(id)s.%(ext)s"};
|
||||||
|
|
||||||
|
ep.add_exec(&args);
|
||||||
|
ep.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
float GetDuration()
|
||||||
|
{
|
||||||
|
stx::ExecPipe ep;
|
||||||
|
|
||||||
|
std::string fileName = links.front() + ".ogg";
|
||||||
|
|
||||||
|
std::vector<std::string> args = {"/usr/bin/ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", fileName};
|
||||||
|
|
||||||
|
ep.add_exec(&args);
|
||||||
|
std::string output;
|
||||||
|
ep.set_output_string(&output);
|
||||||
|
ep.run();
|
||||||
|
|
||||||
|
return std::stof(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
|
||||||
|
float duration = GetDuration();
|
||||||
|
|
||||||
|
std::cout << "Durée : " << duration << std::endl;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
1698
src/stx-execpipe.cpp
Normal file
1698
src/stx-execpipe.cpp
Normal file
File diff suppressed because it is too large
Load Diff
367
src/stx-execpipe.h
Normal file
367
src/stx-execpipe.h
Normal file
@@ -0,0 +1,367 @@
|
|||||||
|
// -*- mode: c++; fill-column: 79 -*-
|
||||||
|
|
||||||
|
/*
|
||||||
|
* STX Execution Pipe Library v0.7.0
|
||||||
|
* Copyright (C) 2010 Timo Bingmann
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU Lesser General Public License as published by the
|
||||||
|
* Free Software Foundation; either version 2.1 of the License, or (at your
|
||||||
|
* option) any later version.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||||
|
* for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with this library; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _STX_EXECPIPE_H_
|
||||||
|
#define _STX_EXECPIPE_H_
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
/// STX - Some Template Extensions namespace
|
||||||
|
namespace stx {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract class used as an input stream source for an ExecPipe.
|
||||||
|
*
|
||||||
|
* Derived classes can be used in an ExecPipe to generate an input stream
|
||||||
|
* source. Data generated by this class is written to the first stage of the
|
||||||
|
* pipe.
|
||||||
|
*
|
||||||
|
* When data is needed by the pipe the function poll() is called. This pure
|
||||||
|
* virtual function must generate data and push it into a buffer using the
|
||||||
|
* write() function. The input stream is terminated when poll() returns false.
|
||||||
|
*/
|
||||||
|
class PipeSource
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
/// pointer to associated pipe. filled by ExecPipe::add_input_source()
|
||||||
|
class ExecPipeImpl* m_impl;
|
||||||
|
|
||||||
|
/// association to the pipe implementation for write access to m_impl.
|
||||||
|
friend class ExecPipeImpl;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// Constructor which clears m_impl.
|
||||||
|
PipeSource();
|
||||||
|
|
||||||
|
/// Poll the input source for new data. The input stream is closed when
|
||||||
|
/// this function returns false, otherwise it will be polled again.
|
||||||
|
virtual bool poll() = 0;
|
||||||
|
|
||||||
|
/// Write input data to the first stage via a buffer.
|
||||||
|
void write(const void* data, unsigned int datalen);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract class used as an output stream source for an ExecPipe.
|
||||||
|
*
|
||||||
|
* Derived classes can be attached to the end of an execution pipe. It will
|
||||||
|
* receive all data outputted by the final pipe stage.
|
||||||
|
*
|
||||||
|
* Data read from the final or preceding stage is passed to the class via
|
||||||
|
* process(). When the final stage closes the pipe, the function eof() is
|
||||||
|
* called.
|
||||||
|
*/
|
||||||
|
class PipeSink
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/// Pure virtual function which receives the output stream as read from the
|
||||||
|
/// final or preceding pipe stage.
|
||||||
|
virtual void process(const void* data, unsigned int datalen) = 0;
|
||||||
|
|
||||||
|
/// Pure virtual function called when the final or preceding pipe stage
|
||||||
|
/// finishes.
|
||||||
|
virtual void eof() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract class used as an intermediate pipe stage between executed
|
||||||
|
* processes.
|
||||||
|
*
|
||||||
|
* Derived classes can be inserted into an execution pipe between two
|
||||||
|
* externally executed processes. It will receive all data from the preceding
|
||||||
|
* pipe stage and after processing it may forward output to the next pipe
|
||||||
|
* stage.
|
||||||
|
*
|
||||||
|
* The class is derived from PipeSink and receives data from the preceding
|
||||||
|
* stage via the inherited functions process() and also the eof()
|
||||||
|
* signal. Usually process() will perform some action on the data and then
|
||||||
|
* forward the resulting data block to the next pipe stage via write().
|
||||||
|
*/
|
||||||
|
class PipeFunction : public PipeSink
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
/// pointer to associated pipe filled by ExecPipe::add_function()
|
||||||
|
class ExecPipeImpl* m_impl;
|
||||||
|
|
||||||
|
/// pipe stage identifier
|
||||||
|
unsigned int m_stageid;
|
||||||
|
|
||||||
|
/// association to the pipe implementation for write access to m_impl.
|
||||||
|
friend class ExecPipeImpl;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// Constructor which clears m_impl and m_stageid.
|
||||||
|
PipeFunction();
|
||||||
|
|
||||||
|
/// Write input data to the next pipe stage via a buffer.
|
||||||
|
void write(const void* data, unsigned int datalen);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Main library interface (reference counted pointer)
|
||||||
|
*
|
||||||
|
* The ExecPipe class is the main interface to the library. It is a reference
|
||||||
|
* counted pointer implementation, so you can easily copy and pass around
|
||||||
|
* without duplicating the inside object. See the \ref index "main page" for
|
||||||
|
* detailed information and examples.
|
||||||
|
*/
|
||||||
|
class ExecPipe
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
/// reference-counted pointer implementation
|
||||||
|
class ExecPipeImpl* m_impl;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// Construct a new uninitialize execution pipe.
|
||||||
|
ExecPipe();
|
||||||
|
|
||||||
|
/// Release reference to execution pipe.
|
||||||
|
~ExecPipe();
|
||||||
|
|
||||||
|
/// Copy-constructor creates a new reference to the _same_ pipe.
|
||||||
|
ExecPipe(const ExecPipe& ep);
|
||||||
|
|
||||||
|
/// Assignment operator creates a new reference to the right pipe.
|
||||||
|
ExecPipe& operator=(const ExecPipe& ep);
|
||||||
|
|
||||||
|
// *** Debugging Output ***
|
||||||
|
|
||||||
|
/// Enumeration for ascending debug levels.
|
||||||
|
enum DebugLevel
|
||||||
|
{
|
||||||
|
DL_ERROR=0, ///< error reporting is always active. shows failed syscalls.
|
||||||
|
DL_INFO=1, ///< info reports at important points during pipe run.
|
||||||
|
DL_DEBUG=2, ///< debug shows information about select() calls.
|
||||||
|
DL_TRACE=3 ///< trace lists lots of info about read() and write() calls.
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Change the current debug level. The default is DL_ERROR.
|
||||||
|
void set_debug_level(enum DebugLevel dl);
|
||||||
|
|
||||||
|
/// Change output function for debug messages. If set to NULL (the default)
|
||||||
|
/// the debug lines are printed to stdout.
|
||||||
|
void set_debug_output(void (*output)(const char *line));
|
||||||
|
|
||||||
|
// *** Input Selectors ***
|
||||||
|
|
||||||
|
///@{ \name Input Selectors
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assign an already opened file descriptor as input stream for the first
|
||||||
|
* exec stage.
|
||||||
|
*/
|
||||||
|
void set_input_fd(int fd);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assign a file as input stream source. This file will be opened read-only
|
||||||
|
* and read by the first exec stage.
|
||||||
|
*/
|
||||||
|
void set_input_file(const char* path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assign a std::string as input stream source. The contents of the string
|
||||||
|
* will be written to the first exec stage. The string object is not copied
|
||||||
|
* and must still exist when run() is called.
|
||||||
|
*/
|
||||||
|
void set_input_string(const std::string* input);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assign a PipeSource as input stream source. The object will be queried
|
||||||
|
* via the read() function for data which is then written to the first exec
|
||||||
|
* stage.
|
||||||
|
*/
|
||||||
|
void set_input_source(PipeSource* source);
|
||||||
|
|
||||||
|
///@}
|
||||||
|
|
||||||
|
// *** Output Selectors ***
|
||||||
|
|
||||||
|
///@{ \name Output Selectors
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assign an already opened file descriptor as output stream for the last
|
||||||
|
* exec stage.
|
||||||
|
*/
|
||||||
|
void set_output_fd(int fd);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assign a file as output stream destination. This file will be created or
|
||||||
|
* truncated write-only and written by the last exec stage.
|
||||||
|
*/
|
||||||
|
void set_output_file(const char* path, int mode = 0666);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assign a std::string as output stream destination. The output of the
|
||||||
|
* last exec stage will be stored as the contents of the string. The string
|
||||||
|
* object is not copied and must still exist when run() is called.
|
||||||
|
*/
|
||||||
|
void set_output_string(std::string* output);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assign a PipeSink as output stream destination. The object will receive
|
||||||
|
* data via the process() function and is informed via eof()
|
||||||
|
*/
|
||||||
|
void set_output_sink(PipeSink* sink);
|
||||||
|
|
||||||
|
///@}
|
||||||
|
|
||||||
|
// *** Pipe Stages ***
|
||||||
|
|
||||||
|
///@{ \name Add Pipe Stages
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the number of pipe stages added.
|
||||||
|
*/
|
||||||
|
unsigned int size() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an exec() stage to the pipe with given arguments. Note that argv[0]
|
||||||
|
* is set to prog.
|
||||||
|
*/
|
||||||
|
void add_exec(const char* prog);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an exec() stage to the pipe with given arguments. Note that argv[0]
|
||||||
|
* is set to prog.
|
||||||
|
*/
|
||||||
|
void add_exec(const char* prog, const char* arg1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an exec() stage to the pipe with given arguments. Note that argv[0]
|
||||||
|
* is set to prog.
|
||||||
|
*/
|
||||||
|
void add_exec(const char* prog, const char* arg1, const char* arg2);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an exec() stage to the pipe with given arguments. Note that argv[0]
|
||||||
|
* is set to prog.
|
||||||
|
*/
|
||||||
|
void add_exec(const char* prog, const char* arg1, const char* arg2, const char* arg3);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an exec() stage to the pipe with given arguments. The vector of
|
||||||
|
* arguments is not copied, so it must still exist when run() is
|
||||||
|
* called. Note that the program called is args[0].
|
||||||
|
*/
|
||||||
|
void add_exec(const std::vector<std::string>* args);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an execp() stage to the pipe with given arguments. The PATH variable
|
||||||
|
* is search for programs not containing a slash / character. Note that
|
||||||
|
* argv[0] is set to prog.
|
||||||
|
*/
|
||||||
|
void add_execp(const char* prog);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an execp() stage to the pipe with given arguments. The PATH variable
|
||||||
|
* is search for programs not containing a slash / character. Note that
|
||||||
|
* argv[0] is set to prog.
|
||||||
|
*/
|
||||||
|
void add_execp(const char* prog, const char* arg1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an execp() stage to the pipe with given arguments. The PATH variable
|
||||||
|
* is search for programs not containing a slash / character. Note that
|
||||||
|
* argv[0] is set to prog.
|
||||||
|
*/
|
||||||
|
void add_execp(const char* prog, const char* arg1, const char* arg2);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an execp() stage to the pipe with given arguments. The PATH variable
|
||||||
|
* is search for programs not containing a slash / character. Note that
|
||||||
|
* argv[0] is set to prog.
|
||||||
|
*/
|
||||||
|
void add_execp(const char* prog, const char* arg1, const char* arg2, const char* arg3);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an execp() stage to the pipe with given arguments. The PATH variable
|
||||||
|
* is search for programs not containing a slash / character. The vector of
|
||||||
|
* arguments is not copied, so it must still exist when run() is
|
||||||
|
* called. Note that the program called is args[0].
|
||||||
|
*/
|
||||||
|
void add_execp(const std::vector<std::string>* args);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an exece() stage to the pipe with the given arguments and
|
||||||
|
* environments. This is the most flexible exec() call. The vector of
|
||||||
|
* arguments and environment variables is not copied, so it must still
|
||||||
|
* exist when run() is called. The args[0] is _not_ override with path, so
|
||||||
|
* you can fake program name calls.
|
||||||
|
*/
|
||||||
|
void add_exece(const char* path,
|
||||||
|
const std::vector<std::string>* args,
|
||||||
|
const std::vector<std::string>* env);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a function stage to the pipe. This function object will be called in
|
||||||
|
* the parent process with data passing through the stage. See PipeFunction
|
||||||
|
* for more information.
|
||||||
|
*/
|
||||||
|
void add_function(PipeFunction* func);
|
||||||
|
|
||||||
|
///@}
|
||||||
|
|
||||||
|
// *** Run Pipe ***
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run the configured pipe sequence and wait for all children processes to
|
||||||
|
* complete. Returns a reference to *this for chaining.
|
||||||
|
*
|
||||||
|
* This function call should be wrapped into a try-catch block as it will
|
||||||
|
* throw() if a system call fails.
|
||||||
|
*/
|
||||||
|
ExecPipe& run();
|
||||||
|
|
||||||
|
// *** Inspection After Pipe Execution ***
|
||||||
|
|
||||||
|
///@{ \name Inspect Return Codes
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the return status of exec() stage's program run after pipe execution
|
||||||
|
* as indicated by wait().
|
||||||
|
*/
|
||||||
|
int get_return_status(unsigned int stageid) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the return code of exec() stage's program run after pipe execution,
|
||||||
|
* or -1 if the program terminated abnormally.
|
||||||
|
*/
|
||||||
|
int get_return_code(unsigned int stageid) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the signal of the abnormally terminated exec() stage's program run
|
||||||
|
* after pipe execution, or -1 if the program terminated normally.
|
||||||
|
*/
|
||||||
|
int get_return_signal(unsigned int stageid) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if the return code of all exec() stages were zero.
|
||||||
|
*/
|
||||||
|
bool all_return_codes_zero() const;
|
||||||
|
|
||||||
|
///@}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace stx
|
||||||
|
|
||||||
|
#endif // _STX_EXECPIPE_H_
|
||||||
78
xmake.lua
Normal file
78
xmake.lua
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
add_rules("mode.debug", "mode.release")
|
||||||
|
|
||||||
|
set_languages("c++11")
|
||||||
|
|
||||||
|
target("Songdle")
|
||||||
|
set_kind("binary")
|
||||||
|
add_files("src/*.cpp")
|
||||||
|
set_rundir(".")
|
||||||
|
|
||||||
|
--
|
||||||
|
-- If you want to known more usage about xmake, please see https://xmake.io
|
||||||
|
--
|
||||||
|
-- ## FAQ
|
||||||
|
--
|
||||||
|
-- You can enter the project directory firstly before building project.
|
||||||
|
--
|
||||||
|
-- $ cd projectdir
|
||||||
|
--
|
||||||
|
-- 1. How to build project?
|
||||||
|
--
|
||||||
|
-- $ xmake
|
||||||
|
--
|
||||||
|
-- 2. How to configure project?
|
||||||
|
--
|
||||||
|
-- $ xmake f -p [macosx|linux|iphoneos ..] -a [x86_64|i386|arm64 ..] -m [debug|release]
|
||||||
|
--
|
||||||
|
-- 3. Where is the build output directory?
|
||||||
|
--
|
||||||
|
-- The default output directory is `./build` and you can configure the output directory.
|
||||||
|
--
|
||||||
|
-- $ xmake f -o outputdir
|
||||||
|
-- $ xmake
|
||||||
|
--
|
||||||
|
-- 4. How to run and debug target after building project?
|
||||||
|
--
|
||||||
|
-- $ xmake run [targetname]
|
||||||
|
-- $ xmake run -d [targetname]
|
||||||
|
--
|
||||||
|
-- 5. How to install target to the system directory or other output directory?
|
||||||
|
--
|
||||||
|
-- $ xmake install
|
||||||
|
-- $ xmake install -o installdir
|
||||||
|
--
|
||||||
|
-- 6. Add some frequently-used compilation flags in xmake.lua
|
||||||
|
--
|
||||||
|
-- @code
|
||||||
|
-- -- add debug and release modes
|
||||||
|
-- add_rules("mode.debug", "mode.release")
|
||||||
|
--
|
||||||
|
-- -- add macro definition
|
||||||
|
-- add_defines("NDEBUG", "_GNU_SOURCE=1")
|
||||||
|
--
|
||||||
|
-- -- set warning all as error
|
||||||
|
-- set_warnings("all", "error")
|
||||||
|
--
|
||||||
|
-- -- set language: c99, c++11
|
||||||
|
-- set_languages("c99", "c++11")
|
||||||
|
--
|
||||||
|
-- -- set optimization: none, faster, fastest, smallest
|
||||||
|
-- set_optimize("fastest")
|
||||||
|
--
|
||||||
|
-- -- add include search directories
|
||||||
|
-- add_includedirs("/usr/include", "/usr/local/include")
|
||||||
|
--
|
||||||
|
-- -- add link libraries and search directories
|
||||||
|
-- add_links("tbox")
|
||||||
|
-- add_linkdirs("/usr/local/lib", "/usr/lib")
|
||||||
|
--
|
||||||
|
-- -- add system link libraries
|
||||||
|
-- add_syslinks("z", "pthread")
|
||||||
|
--
|
||||||
|
-- -- add compilation and link flags
|
||||||
|
-- add_cxflags("-stdnolib", "-fno-strict-aliasing")
|
||||||
|
-- add_ldflags("-L/usr/local/lib", "-lpthread", {force = true})
|
||||||
|
--
|
||||||
|
-- @endcode
|
||||||
|
--
|
||||||
|
|
||||||
Reference in New Issue
Block a user