mirror of
https://github.com/brunoherbelin/vimix.git
synced 2025-12-29 11:09:59 +01:00
2 new callbacks (PlayFastForward, PlaySpeed), modified Seek callback to take target time in seconds (instead of ratio of duration). Integrating this in Input Mapping GUI and Session saving.
356 lines
11 KiB
C++
356 lines
11 KiB
C++
/*
|
|
* This file is part of vimix - video live mixer
|
|
*
|
|
* **Copyright** (C) 2019-2022 Bruno Herbelin <bruno.herbelin@gmail.com>
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program 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 General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
**/
|
|
|
|
#include "BaseToolkit.h"
|
|
|
|
#include <chrono>
|
|
#include <ctime>
|
|
#include <sstream>
|
|
#include <list>
|
|
#include <iomanip>
|
|
#include <algorithm>
|
|
#include <climits>
|
|
#include <map>
|
|
|
|
#include <locale>
|
|
#include <unicode/ustream.h>
|
|
#include <unicode/translit.h>
|
|
|
|
uint64_t BaseToolkit::uniqueId()
|
|
{
|
|
auto duration = std::chrono::high_resolution_clock::now().time_since_epoch();
|
|
// 64-bit int 18446744073709551615UL
|
|
return std::chrono::duration_cast<std::chrono::nanoseconds>(duration).count() % 1000000000000000000UL;
|
|
}
|
|
|
|
|
|
|
|
std::string BaseToolkit::uniqueName(const std::string &basename, std::list<std::string> existingnames)
|
|
{
|
|
std::string tentativename = basename;
|
|
int count = 1;
|
|
int max = 100;
|
|
|
|
// while tentativename can be found in the list of existingnames
|
|
while ( std::find( existingnames.begin(), existingnames.end(), tentativename ) != existingnames.end() )
|
|
{
|
|
for( auto it = existingnames.cbegin(); it != existingnames.cend(); ++it) {
|
|
if ( it->find(tentativename) != std::string::npos)
|
|
++count;
|
|
}
|
|
|
|
if (count > 1) {
|
|
// check if the name is ending by "_i", with i being a number
|
|
int i = 0;
|
|
size_t delim = tentativename.find_last_of('_');
|
|
if (delim != std::string::npos && BaseToolkit::is_a_number(tentativename.substr(++delim), &i) )
|
|
// keep same ending "_i" and increment i
|
|
tentativename = tentativename.substr(0, delim) + std::to_string(++i);
|
|
else
|
|
// add the "_i" ending
|
|
tentativename = basename + "_" + std::to_string( count );
|
|
}
|
|
else
|
|
tentativename += "_";
|
|
|
|
if ( --max < 0 ) // for safety only, should never be needed
|
|
break;
|
|
}
|
|
|
|
return tentativename;
|
|
}
|
|
|
|
// Using ICU transliteration :
|
|
// https://unicode-org.github.io/icu/userguide/transforms/general/#icu-transliterators
|
|
|
|
std::string BaseToolkit::transliterate(const std::string &input)
|
|
{
|
|
// because icu::Transliterator is slow, we keep a dictionary of already
|
|
// transliterated texts to be faster during repeated calls (update of user interface)
|
|
static std::map<std::string, std::string> dictionary_;
|
|
std::map<std::string, std::string>::const_iterator existingentry = dictionary_.find(input);
|
|
|
|
if (existingentry == dictionary_.cend()) {
|
|
|
|
auto ucs = icu::UnicodeString::fromUTF8(input);
|
|
|
|
UErrorCode status = U_ZERO_ERROR;
|
|
icu::Transliterator *firstTrans = icu::Transliterator::createInstance(
|
|
"any-NFKD ; [:Nonspacing Mark:] Remove; NFKC; Latin", UTRANS_FORWARD, status);
|
|
firstTrans->transliterate(ucs);
|
|
delete firstTrans;
|
|
|
|
icu::Transliterator *secondTrans = icu::Transliterator::createInstance(
|
|
"any-NFKD ; [:Nonspacing Mark:] Remove; [@!#$*%~] Remove; NFKC", UTRANS_FORWARD, status);
|
|
secondTrans->transliterate(ucs);
|
|
delete secondTrans;
|
|
|
|
std::ostringstream output;
|
|
output << ucs;
|
|
|
|
// remember for future
|
|
dictionary_[input] = output.str();
|
|
}
|
|
|
|
// return remembered transliterated text
|
|
return dictionary_[input];
|
|
}
|
|
|
|
|
|
std::string BaseToolkit::unspace(const std::string &input, char delim)
|
|
{
|
|
std::string output = input;
|
|
std::replace_if( output.begin(), output.end(), ::isspace, delim);
|
|
return output;
|
|
}
|
|
|
|
std::string BaseToolkit::byte_to_string(long b)
|
|
{
|
|
double numbytes = static_cast<double>(b);
|
|
std::ostringstream oss;
|
|
|
|
std::list<std::string> list = {" Bytes", " KB", " MB", " GB", " TB"};
|
|
std::list<std::string>::iterator i = list.begin();
|
|
|
|
while(numbytes >= 1024.0 && i != list.end())
|
|
{
|
|
++i;
|
|
numbytes /= 1024.0;
|
|
}
|
|
oss << std::fixed << std::setprecision(2) << numbytes;
|
|
if (i != list.end()) oss << *i;
|
|
return oss.str();
|
|
}
|
|
|
|
std::string BaseToolkit::bits_to_string(long b)
|
|
{
|
|
double numbytes = static_cast<double>(b);
|
|
std::ostringstream oss;
|
|
|
|
std::list<std::string> list = {" bit", " Kbit", " Mbit", " Gbit", " Tbit"};
|
|
std::list<std::string>::iterator i = list.begin();
|
|
|
|
while(numbytes >= 1000.0 && i != list.end())
|
|
{
|
|
++i;
|
|
numbytes /= 1000.0;
|
|
}
|
|
oss << std::fixed << std::setprecision(2) << numbytes;
|
|
if (i != list.end()) oss << *i;
|
|
return oss.str();
|
|
}
|
|
|
|
|
|
std::string BaseToolkit::truncated(const std::string& str, int N)
|
|
{
|
|
std::string trunc = str;
|
|
int l = str.size();
|
|
if ( l > N ) {
|
|
trunc = std::string("...") + str.substr( l - N + 3 );
|
|
}
|
|
return trunc;
|
|
}
|
|
|
|
std::list<std::string> BaseToolkit::splitted(const std::string& str, char delim)
|
|
{
|
|
std::list<std::string> strings;
|
|
size_t start = 0;
|
|
size_t end = 0;
|
|
while ((start = str.find_first_not_of(delim, end)) != std::string::npos) {
|
|
end = str.find(delim, start);
|
|
size_t delta = start > 0 ? 1 : 0;
|
|
strings.push_back(str.substr( start -delta, end - start + delta));
|
|
}
|
|
return strings;
|
|
}
|
|
|
|
|
|
std::string BaseToolkit::joinned(std::list<std::string> strlist, char separator)
|
|
{
|
|
std::string str;
|
|
for (auto it = strlist.cbegin(); it != strlist.cend(); ++it) {
|
|
if (it != strlist.cbegin())
|
|
str += separator;
|
|
str += (*it);
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
bool BaseToolkit::is_a_number(const std::string& str, int *val)
|
|
{
|
|
bool isanumber = false;
|
|
|
|
try {
|
|
*val = std::stoi(str);
|
|
isanumber = true;
|
|
}
|
|
catch (const std::invalid_argument&) {
|
|
// avoids crash
|
|
}
|
|
catch (const std::out_of_range&) {
|
|
// avoids crash
|
|
}
|
|
|
|
return isanumber;
|
|
}
|
|
|
|
bool BaseToolkit::is_a_value(const std::string& str, float *val)
|
|
{
|
|
bool isavalue = false;
|
|
|
|
try {
|
|
*val = std::stof(str);
|
|
isavalue = true;
|
|
}
|
|
catch (const std::invalid_argument&) {
|
|
// avoids crash
|
|
}
|
|
catch (const std::out_of_range&) {
|
|
// avoids crash
|
|
}
|
|
|
|
return isavalue;
|
|
}
|
|
|
|
std::string BaseToolkit::common_prefix( const std::list<std::string> & allStrings )
|
|
{
|
|
if (allStrings.empty())
|
|
return std::string();
|
|
|
|
const std::string &s0 = allStrings.front();
|
|
auto _end = s0.cend();
|
|
for (auto it=std::next(allStrings.cbegin()); it != allStrings.cend(); ++it)
|
|
{
|
|
auto _loc = std::mismatch(s0.cbegin(), s0.cend(), it->cbegin(), it->cend());
|
|
if (std::distance(_loc.first, _end) > 0)
|
|
_end = _loc.first;
|
|
}
|
|
|
|
return std::string(s0.cbegin(), _end);
|
|
}
|
|
|
|
|
|
std::string BaseToolkit::common_suffix(const std::list<std::string> & allStrings)
|
|
{
|
|
if (allStrings.empty())
|
|
return std::string();
|
|
|
|
const std::string &s0 = allStrings.front();
|
|
auto r_end = s0.crend();
|
|
for (auto it=std::next(allStrings.cbegin()); it != allStrings.cend(); ++it)
|
|
{
|
|
auto r_loc = std::mismatch(s0.crbegin(), s0.crend(), it->crbegin(), it->crend());
|
|
if (std::distance(r_loc.first, r_end) > 0)
|
|
r_end = r_loc.first;
|
|
}
|
|
|
|
std::string suffix = std::string(s0.crbegin(), r_end);
|
|
std::reverse(suffix.begin(), suffix.end());
|
|
return suffix;
|
|
}
|
|
|
|
|
|
std::string BaseToolkit::common_pattern(const std::list<std::string> &allStrings)
|
|
{
|
|
if (allStrings.empty())
|
|
return std::string();
|
|
|
|
// find common prefix and suffix
|
|
const std::string &s0 = allStrings.front();
|
|
auto _end = s0.cend();
|
|
auto r_end = s0.crend();
|
|
for (auto it=std::next(allStrings.cbegin()); it != allStrings.cend(); ++it)
|
|
{
|
|
auto _loc = std::mismatch(s0.cbegin(), s0.cend(), it->cbegin(), it->cend());
|
|
if (std::distance(_loc.first, _end) > 0)
|
|
_end = _loc.first;
|
|
|
|
auto r_loc = std::mismatch(s0.crbegin(), s0.crend(), it->crbegin(), it->crend());
|
|
if (std::distance(r_loc.first, r_end) > 0)
|
|
r_end = r_loc.first;
|
|
}
|
|
|
|
std::string suffix = std::string(s0.crbegin(), r_end);
|
|
std::reverse(suffix.begin(), suffix.end());
|
|
|
|
return std::string(s0.cbegin(), _end) + "*" + suffix;
|
|
}
|
|
|
|
std::string BaseToolkit::common_numbered_pattern(const std::list<std::string> &allStrings, int *min, int *max)
|
|
{
|
|
if (allStrings.empty())
|
|
return std::string();
|
|
|
|
// find common prefix and suffix
|
|
const std::string &s0 = allStrings.front();
|
|
auto _end = s0.cend();
|
|
auto r_end = s0.crend();
|
|
for (auto it=std::next(allStrings.cbegin()); it != allStrings.cend(); ++it)
|
|
{
|
|
auto _loc = std::mismatch(s0.cbegin(), s0.cend(), it->cbegin(), it->cend());
|
|
if (std::distance(_loc.first, _end) > 0)
|
|
_end = _loc.first;
|
|
|
|
auto r_loc = std::mismatch(s0.crbegin(), s0.crend(), it->crbegin(), it->crend());
|
|
if (std::distance(r_loc.first, r_end) > 0)
|
|
r_end = r_loc.first;
|
|
}
|
|
|
|
// range of middle string, after prefix and before suffix
|
|
size_t pos_prefix = std::distance(s0.cbegin(), _end);
|
|
size_t pos_suffix = s0.size() - pos_prefix - std::distance(s0.crbegin(), r_end);
|
|
|
|
int n = -1;
|
|
*max = 0;
|
|
*min = INT_MAX;
|
|
// loop over all strings to verify there are numbers between prefix and suffix
|
|
for (auto it = allStrings.cbegin(); it != allStrings.cend(); ++it)
|
|
{
|
|
// get middle string, after prefix and before suffix
|
|
std::string s = it->substr(pos_prefix, pos_suffix);
|
|
// is this central string ONLY made of digits?
|
|
if (s.end() == std::find_if(s.begin(), s.end(), [](unsigned char c)->bool { return !isdigit(c); })) {
|
|
// yes, validate
|
|
*max = std::max(*max, std::atoi(s.c_str()) );
|
|
*min = std::min(*min, std::atoi(s.c_str()) );
|
|
if (n < 0)
|
|
n = s.size();
|
|
else if ( n != (int) s.size() ) {
|
|
n = 0;
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
n = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( n < 1 )
|
|
return std::string();
|
|
|
|
std::string suffix = std::string(s0.crbegin(), r_end);
|
|
std::reverse(suffix.begin(), suffix.end());
|
|
std::string pattern = std::string(s0.cbegin(), _end);
|
|
pattern += "%0" + std::to_string(n) + "d";
|
|
pattern += suffix;
|
|
return pattern;
|
|
}
|