New upstream version 18.0.1+dfsg1

This commit is contained in:
Sebastian Ramacher 2017-04-19 21:54:15 +02:00
parent 6efda2859e
commit f2cf6cce50
1337 changed files with 41178 additions and 84670 deletions

View file

@ -5,20 +5,53 @@ if(APPLE)
include_directories(${COCOA})
endif()
if(WIN32 OR APPLE)
set(frontend-tools_HEADERS
auto-scene-switcher.hpp
)
set(frontend-tools_SOURCES
auto-scene-switcher.cpp
)
set(frontend-tools_UI
forms/auto-scene-switcher.ui
)
endif()
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/frontend-tools-config.h.in"
"${CMAKE_BINARY_DIR}/config/frontend-tools-config.h")
set(frontend-tools_HEADERS
auto-scene-switcher.hpp
${frontend-tools_HEADERS}
"${CMAKE_BINARY_DIR}/config/frontend-tools-config.h"
output-timer.hpp
tool-helpers.hpp
)
set(frontend-tools_SOURCES
${frontend-tools_SOURCES}
frontend-tools.c
auto-scene-switcher.cpp
output-timer.cpp
)
set(frontend-tools_UI
forms/auto-scene-switcher.ui
${frontend-tools_UI}
forms/output-timer.ui
)
if(WIN32)
set(frontend-tools_PLATFORM_SOURCES
auto-scene-switcher-win.cpp)
if(BUILD_CAPTIONS)
set(frontend-tools_PLATFORM_SOURCES
${frontend-tools_PLATFORM_SOURCES}
captions.cpp
captions-stream.cpp)
set(frontend-tools_PLATFORM_HEADERS
captions.hpp
captions-stream.hpp)
set(frontend-tools_PLATFORM_UI
forms/captions.ui)
endif()
elseif(APPLE)
set(frontend-tools_PLATFORM_SOURCES
auto-scene-switcher-osx.mm)
@ -29,13 +62,16 @@ elseif(APPLE)
${COCOA})
endif()
qt5_wrap_ui(frontend-tools_UI_HEADERS ${frontend-tools_UI})
qt5_wrap_ui(frontend-tools_UI_HEADERS
${frontend-tools_UI}
${frontend-tools_PLATFORM_UI})
add_library(frontend-tools MODULE
${frontend-tools_HEADERS}
${frontend-tools_SOURCES}
${frontend-tools_PLATFORM_SOURCES}
${frontend-tools_UI_HEADERS}
${frontend-tools_PLATFORM_SOURCES}
${frontend-tools_PLATFORM_HEADERS}
)
target_link_libraries(frontend-tools
${frontend-tools_PLATFORM_LIBS}

View file

@ -3,8 +3,10 @@
#include <obs.hpp>
#include <util/util.hpp>
#include <QMainWindow>
#include <QMessageBox>
#include <QAction>
#include "auto-scene-switcher.hpp"
#include "tool-helpers.hpp"
#include <condition_variable>
#include <chrono>
@ -81,37 +83,6 @@ static inline QString MakeSwitchName(const QString &scene,
return QStringLiteral("[") + scene + QStringLiteral("]: ") + window;
}
static inline string GetWeakSourceName(obs_weak_source_t *weak_source)
{
string name;
obs_source_t *source = obs_weak_source_get_source(weak_source);
if (source) {
name = obs_source_get_name(source);
obs_source_release(source);
}
return name;
}
static inline OBSWeakSource GetWeakSourceByName(const char *name)
{
OBSWeakSource weak;
obs_source_t *source = obs_get_source_by_name(name);
if (source) {
weak = obs_source_get_weak_source(source);
obs_weak_source_release(weak);
obs_source_release(source);
}
return weak;
}
static inline OBSWeakSource GetWeakSourceByQString(const QString &name)
{
return GetWeakSourceByName(name.toUtf8().constData());
}
SceneSwitcher::SceneSwitcher(QWidget *parent)
: QDialog(parent),
ui(new Ui_SceneSwitcher)
@ -231,13 +202,19 @@ void SceneSwitcher::on_add_clicked()
int idx = FindByData(windowName);
if (idx == -1) {
QListWidgetItem *item = new QListWidgetItem(text,
ui->switches);
item->setData(Qt::UserRole, v);
lock_guard<mutex> lock(switcher->m);
switcher->switches.emplace_back(source,
windowName.toUtf8().constData());
try {
lock_guard<mutex> lock(switcher->m);
switcher->switches.emplace_back(source,
windowName.toUtf8().constData());
QListWidgetItem *item = new QListWidgetItem(text,
ui->switches);
item->setData(Qt::UserRole, v);
} catch (const regex_error &) {
QMessageBox::warning(this,
obs_module_text("InvalidRegex.Title"),
obs_module_text("InvalidRegex.Text"));
}
} else {
QListWidgetItem *item = ui->switches->item(idx);
item->setText(text);

View file

@ -0,0 +1,418 @@
#include "captions-stream.hpp"
#include <mmreg.h>
#include <util/windows/CoTaskMemPtr.hpp>
#include <util/threading.h>
#include <util/base.h>
using namespace std;
#if 0
#define debugfunc(format, ...) blog(LOG_DEBUG, "[Captions] %s(" format ")", \
__FUNCTION__, ##__VA_ARGS__)
#else
#define debugfunc(format, ...)
#endif
CaptionStream::CaptionStream(DWORD samplerate_) :
samplerate(samplerate_),
event(CreateEvent(nullptr, false, false, nullptr))
{
buf_info.ulMsMinNotification = 50;
buf_info.ulMsBufferSize = 500;
buf_info.ulMsEventBias = 0;
format.wFormatTag = WAVE_FORMAT_PCM;
format.nChannels = 1;
format.nSamplesPerSec = 16000;
format.nAvgBytesPerSec = format.nSamplesPerSec * sizeof(uint16_t);
format.nBlockAlign = 2;
format.wBitsPerSample = 16;
format.cbSize = sizeof(format);
resampler.Reset(&format);
}
void CaptionStream::Stop()
{
{
lock_guard<mutex> lock(m);
circlebuf_free(buf);
}
cv.notify_one();
}
void CaptionStream::PushAudio(const struct audio_data *data, bool muted)
{
uint8_t *output[MAX_AV_PLANES] = {};
uint32_t frames = data->frames;
uint64_t ts_offset;
bool ready = false;
audio_resampler_resample(resampler, output, &frames, &ts_offset,
data->data, data->frames);
if (output[0]) {
if (muted)
memset(output[0], 0, frames * sizeof(int16_t));
lock_guard<mutex> lock(m);
circlebuf_push_back(buf, output[0], frames * sizeof(int16_t));
write_pos += frames * sizeof(int16_t);
if (wait_size && buf->size >= wait_size)
ready = true;
}
if (ready)
cv.notify_one();
}
// IUnknown methods
STDMETHODIMP CaptionStream::QueryInterface(REFIID riid, void **ppv)
{
if (riid == IID_IUnknown) {
AddRef();
*ppv = this;
} else if (riid == IID_IStream) {
AddRef();
*ppv = (IStream*)this;
} else if (riid == IID_ISpStreamFormat) {
AddRef();
*ppv = (ISpStreamFormat*)this;
} else if (riid == IID_ISpAudio) {
AddRef();
*ppv = (ISpAudio*)this;
} else {
*ppv = nullptr;
return E_NOINTERFACE;
}
return NOERROR;
}
STDMETHODIMP_(ULONG) CaptionStream::AddRef()
{
return (ULONG)os_atomic_inc_long(&refs);
}
STDMETHODIMP_(ULONG) CaptionStream::Release()
{
ULONG new_refs = (ULONG)os_atomic_dec_long(&refs);
if (!new_refs)
delete this;
return new_refs;
}
// ISequentialStream methods
STDMETHODIMP CaptionStream::Read(void *data, ULONG bytes, ULONG *read_bytes)
{
HRESULT hr = S_OK;
size_t cur_size;
debugfunc("data, %lu, read_bytes", bytes);
if (!data)
return STG_E_INVALIDPOINTER;
{
lock_guard<mutex> lock1(m);
wait_size = bytes;
cur_size = buf->size;
}
unique_lock<mutex> lock(m);
if (bytes > cur_size)
cv.wait(lock);
if (bytes > (ULONG)buf->size) {
bytes = (ULONG)buf->size;
hr = S_FALSE;
}
if (bytes)
circlebuf_pop_front(buf, data, bytes);
if (read_bytes)
*read_bytes = bytes;
wait_size = 0;
pos.QuadPart += bytes;
return hr;
}
STDMETHODIMP CaptionStream::Write(const void *, ULONG bytes,
ULONG*)
{
debugfunc("data, %lu, written_bytes", bytes);
UNUSED_PARAMETER(bytes);
return STG_E_INVALIDFUNCTION;
}
// IStream methods
STDMETHODIMP CaptionStream::Seek(LARGE_INTEGER move, DWORD origin,
ULARGE_INTEGER *new_pos)
{
debugfunc("%lld, %lx, new_pos", move, origin);
UNUSED_PARAMETER(move);
UNUSED_PARAMETER(origin);
if (!new_pos)
return E_POINTER;
if (origin != SEEK_CUR || move.QuadPart != 0)
return E_NOTIMPL;
*new_pos = pos;
return S_OK;
}
STDMETHODIMP CaptionStream::SetSize(ULARGE_INTEGER new_size)
{
debugfunc("%llu", new_size);
UNUSED_PARAMETER(new_size);
return STG_E_INVALIDFUNCTION;
}
STDMETHODIMP CaptionStream::CopyTo(IStream *stream, ULARGE_INTEGER bytes,
ULARGE_INTEGER *read_bytes,
ULARGE_INTEGER *written_bytes)
{
HRESULT hr;
debugfunc("stream, %llu, read_bytes, written_bytes", bytes);
if (!stream)
return STG_E_INVALIDPOINTER;
ULONG written = 0;
if (bytes.QuadPart > (ULONGLONG)buf->size)
bytes.QuadPart = (ULONGLONG)buf->size;
lock_guard<mutex> lock(m);
temp_buf.resize((size_t)bytes.QuadPart);
circlebuf_peek_front(buf, &temp_buf[0], (size_t)bytes.QuadPart);
hr = stream->Write(temp_buf.data(), (ULONG)bytes.QuadPart, &written);
if (read_bytes)
*read_bytes = bytes;
if (written_bytes)
written_bytes->QuadPart = written;
return hr;
}
STDMETHODIMP CaptionStream::Commit(DWORD commit_flags)
{
debugfunc("%lx", commit_flags);
UNUSED_PARAMETER(commit_flags);
/* TODO? */
return S_OK;
}
STDMETHODIMP CaptionStream::Revert(void)
{
debugfunc("");
return S_OK;
}
STDMETHODIMP CaptionStream::LockRegion(ULARGE_INTEGER offset,
ULARGE_INTEGER size, DWORD type)
{
debugfunc("%llu, %llu, %ld", offset, size, type);
UNUSED_PARAMETER(offset);
UNUSED_PARAMETER(size);
UNUSED_PARAMETER(type);
/* TODO? */
return STG_E_INVALIDFUNCTION;
}
STDMETHODIMP CaptionStream::UnlockRegion(ULARGE_INTEGER offset,
ULARGE_INTEGER size, DWORD type)
{
debugfunc("%llu, %llu, %ld", offset, size, type);
UNUSED_PARAMETER(offset);
UNUSED_PARAMETER(size);
UNUSED_PARAMETER(type);
/* TODO? */
return STG_E_INVALIDFUNCTION;
}
static const wchar_t *stat_name = L"Caption stream";
STDMETHODIMP CaptionStream::Stat(STATSTG *stg, DWORD flag)
{
debugfunc("stg, %lu", flag);
if (!stg)
return E_POINTER;
lock_guard<mutex> lock(m);
*stg = {};
stg->type = STGTY_STREAM;
stg->cbSize.QuadPart = (ULONGLONG)buf->size;
if (flag == STATFLAG_DEFAULT) {
stg->pwcsName = (wchar_t*)CoTaskMemAlloc(sizeof(stat_name));
memcpy(stg->pwcsName, stat_name, sizeof(stat_name));
}
return S_OK;
}
STDMETHODIMP CaptionStream::Clone(IStream **stream)
{
debugfunc("stream");
*stream = nullptr;
return E_NOTIMPL;
}
// ISpStreamFormat methods
STDMETHODIMP CaptionStream::GetFormat(GUID *guid,
WAVEFORMATEX **co_mem_wfex_out)
{
debugfunc("guid, co_mem_wfex_out");
if (!guid || !co_mem_wfex_out)
return E_POINTER;
if (format.wFormatTag == 0) {
*co_mem_wfex_out = nullptr;
return S_OK;
}
void *wfex = CoTaskMemAlloc(sizeof(format));
memcpy(wfex, &format, sizeof(format));
*co_mem_wfex_out = (WAVEFORMATEX*)wfex;
return S_OK;
}
// ISpAudio methods
STDMETHODIMP CaptionStream::SetState(SPAUDIOSTATE state_, ULONGLONG)
{
debugfunc("%lu, reserved", state_);
state = state_;
return S_OK;
}
STDMETHODIMP CaptionStream::SetFormat(REFGUID guid_ref,
const WAVEFORMATEX *wfex)
{
debugfunc("guid, wfex");
if (!wfex)
return E_INVALIDARG;
if (guid_ref == SPDFID_WaveFormatEx) {
lock_guard<mutex> lock(m);
memcpy(&format, wfex, sizeof(format));
resampler.Reset(wfex);
/* 50 msec */
DWORD size = format.nSamplesPerSec / 20;
DWORD byte_size = size * format.nBlockAlign;
circlebuf_reserve(buf, (size_t)byte_size);
}
return S_OK;
}
STDMETHODIMP CaptionStream::GetStatus(SPAUDIOSTATUS *status)
{
debugfunc("status");
if (!status)
return E_POINTER;
/* TODO? */
lock_guard<mutex> lock(m);
*status = {};
status->cbNonBlockingIO = (ULONG)buf->size;
status->State = state;
status->CurSeekPos = pos.QuadPart;
status->CurDevicePos = write_pos;
return S_OK;
}
STDMETHODIMP CaptionStream::SetBufferInfo(const SPAUDIOBUFFERINFO *buf_info_)
{
debugfunc("buf_info");
/* TODO */
buf_info = *buf_info_;
return S_OK;
}
STDMETHODIMP CaptionStream::GetBufferInfo(SPAUDIOBUFFERINFO *buf_info_)
{
debugfunc("buf_info");
if (!buf_info_)
return E_POINTER;
*buf_info_ = buf_info;
return S_OK;
}
STDMETHODIMP CaptionStream::GetDefaultFormat(GUID *format,
WAVEFORMATEX **co_mem_wfex_out)
{
debugfunc("format, co_mem_wfex_out");
if (!format || !co_mem_wfex_out)
return E_POINTER;
void *wfex = CoTaskMemAlloc(sizeof(format));
memcpy(wfex, &format, sizeof(format));
*format = SPDFID_WaveFormatEx;
*co_mem_wfex_out = (WAVEFORMATEX*)wfex;
return S_OK;
}
STDMETHODIMP_(HANDLE) CaptionStream::EventHandle(void)
{
debugfunc("");
return event;
}
STDMETHODIMP CaptionStream::GetVolumeLevel(ULONG *level)
{
debugfunc("level");
if (!level)
return E_POINTER;
*level = vol;
return S_OK;
}
STDMETHODIMP CaptionStream::SetVolumeLevel(ULONG level)
{
debugfunc("%lu", level);
vol = level;
return S_OK;
}
STDMETHODIMP CaptionStream::GetBufferNotifySize(ULONG *size)
{
debugfunc("size");
if (!size)
return E_POINTER;
*size = notify_size;
return S_OK;
}
STDMETHODIMP CaptionStream::SetBufferNotifySize(ULONG size)
{
debugfunc("%lu", size);
notify_size = size;
return S_OK;
}

View file

@ -0,0 +1,119 @@
#include <windows.h>
#include <sapi.h>
#include <condition_variable>
#include <mutex>
#include <vector>
#include <obs.h>
#include <media-io/audio-resampler.h>
#include <util/circlebuf.h>
#include <util/windows/WinHandle.hpp>
#include <fstream>
class CircleBuf {
circlebuf buf = {};
public:
inline ~CircleBuf() {circlebuf_free(&buf);}
inline operator circlebuf*() {return &buf;}
inline circlebuf *operator->() {return &buf;}
};
class Resampler {
audio_resampler_t *resampler = nullptr;
public:
inline void Reset(const WAVEFORMATEX *wfex)
{
const struct audio_output_info *aoi =
audio_output_get_info(obs_get_audio());
struct resample_info src;
src.samples_per_sec = aoi->samples_per_sec;
src.format = aoi->format;
src.speakers = aoi->speakers;
struct resample_info dst;
dst.samples_per_sec = uint32_t(wfex->nSamplesPerSec);
dst.format = AUDIO_FORMAT_16BIT;
dst.speakers = (enum speaker_layout)wfex->nChannels;
if (resampler)
audio_resampler_destroy(resampler);
resampler = audio_resampler_create(&dst, &src);
}
inline ~Resampler() {audio_resampler_destroy(resampler);}
inline operator audio_resampler_t*() {return resampler;}
};
class CaptionStream : public ISpAudio {
volatile long refs = 1;
SPAUDIOBUFFERINFO buf_info = {};
ULONG notify_size = 0;
SPAUDIOSTATE state;
WinHandle event;
ULONG vol = 0;
std::condition_variable cv;
std::mutex m;
std::vector<int16_t> temp_buf;
WAVEFORMATEX format = {};
Resampler resampler;
CircleBuf buf;
ULONG wait_size = 0;
DWORD samplerate = 0;
ULARGE_INTEGER pos = {};
ULONGLONG write_pos = 0;
public:
CaptionStream(DWORD samplerate);
void Stop();
void PushAudio(const struct audio_data *audio_data, bool muted);
// IUnknown methods
STDMETHODIMP QueryInterface(REFIID riid, void **ppv) override;
STDMETHODIMP_(ULONG) AddRef() override;
STDMETHODIMP_(ULONG) Release() override;
// ISequentialStream methods
STDMETHODIMP Read(void *data, ULONG bytes, ULONG *read_bytes) override;
STDMETHODIMP Write(const void *data, ULONG bytes, ULONG *written_bytes)
override;
// IStream methods
STDMETHODIMP Seek(LARGE_INTEGER move, DWORD origin,
ULARGE_INTEGER *new_pos) override;
STDMETHODIMP SetSize(ULARGE_INTEGER new_size) override;
STDMETHODIMP CopyTo(IStream *stream, ULARGE_INTEGER bytes,
ULARGE_INTEGER *read_bytes,
ULARGE_INTEGER *written_bytes) override;
STDMETHODIMP Commit(DWORD commit_flags) override;
STDMETHODIMP Revert(void) override;
STDMETHODIMP LockRegion(ULARGE_INTEGER offset, ULARGE_INTEGER size,
DWORD type) override;
STDMETHODIMP UnlockRegion(ULARGE_INTEGER offset, ULARGE_INTEGER size,
DWORD type) override;
STDMETHODIMP Stat(STATSTG *stg, DWORD flags) override;
STDMETHODIMP Clone(IStream **stream) override;
// ISpStreamFormat methods
STDMETHODIMP GetFormat(GUID *guid, WAVEFORMATEX **co_mem_wfex_out)
override;
// ISpAudio methods
STDMETHODIMP SetState(SPAUDIOSTATE state, ULONGLONG reserved) override;
STDMETHODIMP SetFormat(REFGUID guid_ref, const WAVEFORMATEX *wfex)
override;
STDMETHODIMP GetStatus(SPAUDIOSTATUS *status) override;
STDMETHODIMP SetBufferInfo(const SPAUDIOBUFFERINFO *buf_info) override;
STDMETHODIMP GetBufferInfo(SPAUDIOBUFFERINFO *buf_info) override;
STDMETHODIMP GetDefaultFormat(GUID *format,
WAVEFORMATEX **co_mem_wfex_out) override;
STDMETHODIMP_(HANDLE) EventHandle(void) override;
STDMETHODIMP GetVolumeLevel(ULONG *level) override;
STDMETHODIMP SetVolumeLevel(ULONG level) override;
STDMETHODIMP GetBufferNotifySize(ULONG *size) override;
STDMETHODIMP SetBufferNotifySize(ULONG size) override;
};

View file

@ -0,0 +1,533 @@
#include <obs-frontend-api.h>
#include "captions-stream.hpp"
#include "captions.hpp"
#include "tool-helpers.hpp"
#include <sphelper.h>
#include <util/dstr.hpp>
#include <util/platform.h>
#include <util/windows/HRError.hpp>
#include <util/windows/ComPtr.hpp>
#include <util/windows/CoTaskMemPtr.hpp>
#include <util/threading.h>
#include <obs-module.h>
#include <string>
#include <thread>
#include <mutex>
#define do_log(type, format, ...) blog(type, "[Captions] " format, \
##__VA_ARGS__)
#define error(format, ...) do_log(LOG_ERROR, format, ##__VA_ARGS__)
#define debug(format, ...) do_log(LOG_DEBUG, format, ##__VA_ARGS__)
using namespace std;
struct obs_captions {
thread th;
recursive_mutex m;
WinHandle stop_event;
string source_name;
OBSWeakSource source;
LANGID lang_id;
void main_thread();
void start();
void stop();
inline obs_captions() :
stop_event(CreateEvent(nullptr, false, false, nullptr)),
lang_id(GetUserDefaultUILanguage())
{
}
inline ~obs_captions() {stop();}
};
static obs_captions *captions = nullptr;
/* ------------------------------------------------------------------------- */
struct locale_info {
DStr name;
LANGID id;
inline locale_info() {}
inline locale_info(const locale_info &) = delete;
inline locale_info(locale_info &&li)
: name(std::move(li.name)),
id(li.id)
{}
};
static void get_valid_locale_names(vector<locale_info> &names);
static bool valid_lang(LANGID id);
/* ------------------------------------------------------------------------- */
CaptionsDialog::CaptionsDialog(QWidget *parent) :
QDialog(parent),
ui(new Ui_CaptionsDialog)
{
ui->setupUi(this);
lock_guard<recursive_mutex> lock(captions->m);
auto cb = [this] (obs_source_t *source)
{
uint32_t caps = obs_source_get_output_flags(source);
QString name = obs_source_get_name(source);
if (caps & OBS_SOURCE_AUDIO)
ui->source->addItem(name);
OBSWeakSource weak = OBSGetWeakRef(source);
if (weak == captions->source)
ui->source->setCurrentText(name);
return true;
};
using cb_t = decltype(cb);
ui->source->blockSignals(true);
ui->source->addItem(QStringLiteral(""));
ui->source->setCurrentIndex(0);
obs_enum_sources([] (void *data, obs_source_t *source) {
return (*static_cast<cb_t*>(data))(source);}, &cb);
ui->source->blockSignals(false);
ui->enable->blockSignals(true);
ui->enable->setChecked(captions->th.joinable());
ui->enable->blockSignals(false);
vector<locale_info> locales;
get_valid_locale_names(locales);
bool set_language = false;
ui->language->blockSignals(true);
for (int idx = 0; idx < (int)locales.size(); idx++) {
locale_info &locale = locales[idx];
ui->language->addItem(locale.name->array, (int)locale.id);
if (locale.id == captions->lang_id) {
ui->language->setCurrentIndex(idx);
set_language = true;
}
}
if (!set_language && locales.size())
ui->language->setCurrentIndex(0);
ui->language->blockSignals(false);
if (!locales.size()) {
ui->source->setEnabled(false);
ui->enable->setEnabled(false);
ui->language->setEnabled(false);
} else if (!set_language) {
bool started = captions->th.joinable();
if (started)
captions->stop();
captions->m.lock();
captions->lang_id = locales[0].id;
captions->m.unlock();
if (started)
captions->start();
}
}
void CaptionsDialog::on_source_currentIndexChanged(int)
{
bool started = captions->th.joinable();
if (started)
captions->stop();
captions->m.lock();
captions->source_name = ui->source->currentText().toUtf8().constData();
captions->source = GetWeakSourceByName(captions->source_name.c_str());
captions->m.unlock();
if (started)
captions->start();
}
void CaptionsDialog::on_enable_clicked(bool checked)
{
if (checked)
captions->start();
else
captions->stop();
}
void CaptionsDialog::on_language_currentIndexChanged(int)
{
bool started = captions->th.joinable();
if (started)
captions->stop();
captions->m.lock();
captions->lang_id = (LANGID)ui->language->currentData().toInt();
captions->m.unlock();
if (started)
captions->start();
}
/* ------------------------------------------------------------------------- */
void obs_captions::main_thread()
try {
ComPtr<CaptionStream> audio;
ComPtr<ISpObjectToken> token;
ComPtr<ISpRecoGrammar> grammar;
ComPtr<ISpRecognizer> recognizer;
ComPtr<ISpRecoContext> context;
HRESULT hr;
auto cb = [&] (const struct audio_data *audio_data,
bool muted)
{
audio->PushAudio(audio_data, muted);
};
using cb_t = decltype(cb);
auto pre_cb = [] (void *param, obs_source_t*,
const struct audio_data *audio_data, bool muted)
{
return (*static_cast<cb_t*>(param))(audio_data, muted);
};
os_set_thread_name(__FUNCTION__);
CoInitialize(nullptr);
wchar_t lang_str[32];
_snwprintf(lang_str, 31, L"language=%x", (int)captions->lang_id);
hr = SpFindBestToken(SPCAT_RECOGNIZERS, lang_str, nullptr, &token);
if (FAILED(hr))
throw HRError("SpFindBestToken failed", hr);
hr = CoCreateInstance(CLSID_SpInprocRecognizer, nullptr, CLSCTX_ALL,
__uuidof(ISpRecognizer), (void**)&recognizer);
if (FAILED(hr))
throw HRError("CoCreateInstance for recognizer failed", hr);
hr = recognizer->SetRecognizer(token);
if (FAILED(hr))
throw HRError("SetRecognizer failed", hr);
hr = recognizer->SetRecoState(SPRST_INACTIVE);
if (FAILED(hr))
throw HRError("SetRecoState(SPRST_INACTIVE) failed", hr);
hr = recognizer->CreateRecoContext(&context);
if (FAILED(hr))
throw HRError("CreateRecoContext failed", hr);
ULONGLONG interest = SPFEI(SPEI_RECOGNITION) |
SPFEI(SPEI_END_SR_STREAM);
hr = context->SetInterest(interest, interest);
if (FAILED(hr))
throw HRError("SetInterest failed", hr);
HANDLE notify;
hr = context->SetNotifyWin32Event();
if (FAILED(hr))
throw HRError("SetNotifyWin32Event", hr);
notify = context->GetNotifyEventHandle();
if (notify == INVALID_HANDLE_VALUE)
throw HRError("GetNotifyEventHandle failed", E_NOINTERFACE);
size_t sample_rate = audio_output_get_sample_rate(obs_get_audio());
audio = new CaptionStream((DWORD)sample_rate);
audio->Release();
hr = recognizer->SetInput(audio, false);
if (FAILED(hr))
throw HRError("SetInput failed", hr);
hr = context->CreateGrammar(1, &grammar);
if (FAILED(hr))
throw HRError("CreateGrammar failed", hr);
hr = grammar->LoadDictation(nullptr, SPLO_STATIC);
if (FAILED(hr))
throw HRError("LoadDictation failed", hr);
hr = grammar->SetDictationState(SPRS_ACTIVE);
if (FAILED(hr))
throw HRError("SetDictationState failed", hr);
hr = recognizer->SetRecoState(SPRST_ACTIVE);
if (FAILED(hr))
throw HRError("SetRecoState(SPRST_ACTIVE) failed", hr);
HANDLE events[] = {notify, stop_event};
{
captions->source = GetWeakSourceByName(
captions->source_name.c_str());
OBSSource strong = OBSGetStrongRef(source);
if (strong)
obs_source_add_audio_capture_callback(strong,
pre_cb, &cb);
}
for (;;) {
DWORD ret = WaitForMultipleObjects(2, events, false, INFINITE);
if (ret != WAIT_OBJECT_0)
break;
CSpEvent event;
bool exit = false;
while (event.GetFrom(context) == S_OK) {
if (event.eEventId == SPEI_RECOGNITION) {
ISpRecoResult *result = event.RecoResult();
CoTaskMemPtr<wchar_t> text;
hr = result->GetText((ULONG)-1, (ULONG)-1,
true, &text, nullptr);
if (FAILED(hr))
continue;
char text_utf8[512];
os_wcs_to_utf8(text, 0, text_utf8, 512);
obs_output_t *output =
obs_frontend_get_streaming_output();
if (output)
obs_output_output_caption_text1(output,
text_utf8);
debug("\"%s\"", text_utf8);
obs_output_release(output);
} else if (event.eEventId == SPEI_END_SR_STREAM) {
exit = true;
break;
}
}
if (exit)
break;
}
{
OBSSource strong = OBSGetStrongRef(source);
if (strong)
obs_source_remove_audio_capture_callback(strong,
pre_cb, &cb);
}
audio->Stop();
CoUninitialize();
} catch (HRError err) {
error("%s failed: %s (%lX)", __FUNCTION__, err.str, err.hr);
CoUninitialize();
captions->th.detach();
}
void obs_captions::start()
{
if (!captions->th.joinable()) {
ResetEvent(captions->stop_event);
if (valid_lang(captions->lang_id))
captions->th = thread([] () {captions->main_thread();});
}
}
void obs_captions::stop()
{
if (!captions->th.joinable())
return;
SetEvent(captions->stop_event);
captions->th.join();
}
static bool get_locale_name(LANGID id, char *out)
{
wchar_t name[256];
int size = GetLocaleInfoW(id, LOCALE_SENGLISHLANGUAGENAME, name, 256);
if (size <= 0)
return false;
os_wcs_to_utf8(name, 0, out, 256);
return true;
}
static bool valid_lang(LANGID id)
{
ComPtr<ISpObjectToken> token;
wchar_t lang_str[32];
HRESULT hr;
_snwprintf(lang_str, 31, L"language=%x", (int)id);
hr = SpFindBestToken(SPCAT_RECOGNIZERS, lang_str, nullptr, &token);
return SUCCEEDED(hr);
}
static void get_valid_locale_names(vector<locale_info> &locales)
{
locale_info cur;
char locale_name[256];
static const LANGID default_locales[] = {
0x0409,
0x0401,
0x0402,
0x0403,
0x0404,
0x0405,
0x0406,
0x0407,
0x0408,
0x040a,
0x040b,
0x040c,
0x040d,
0x040e,
0x040f,
0x0410,
0x0411,
0x0412,
0x0413,
0x0414,
0x0415,
0x0416,
0x0417,
0x0418,
0x0419,
0x041a,
0
};
/* ---------------------------------- */
LANGID def_id = GetUserDefaultUILanguage();
LANGID id = def_id;
if (valid_lang(id) && get_locale_name(id, locale_name)) {
dstr_copy(cur.name, obs_module_text(
"Captions.CurrentSystemLanguage"));
dstr_replace(cur.name, "%1", locale_name);
cur.id = id;
locales.push_back(std::move(cur));
}
/* ---------------------------------- */
const LANGID *locale = default_locales;
while (*locale) {
id = *locale;
if (id != def_id &&
valid_lang(id) &&
get_locale_name(id, locale_name)) {
dstr_copy(cur.name, locale_name);
cur.id = id;
locales.push_back(std::move(cur));
}
locale++;
}
}
/* ------------------------------------------------------------------------- */
extern "C" void FreeCaptions()
{
delete captions;
captions = nullptr;
}
static void obs_event(enum obs_frontend_event event, void *)
{
if (event == OBS_FRONTEND_EVENT_EXIT)
FreeCaptions();
}
static void save_caption_data(obs_data_t *save_data, bool saving, void*)
{
if (saving) {
lock_guard<recursive_mutex> lock(captions->m);
obs_data_t *obj = obs_data_create();
obs_data_set_string(obj, "source",
captions->source_name.c_str());
obs_data_set_bool(obj, "enabled", captions->th.joinable());
obs_data_set_int(obj, "lang_id", captions->lang_id);
obs_data_set_obj(save_data, "captions", obj);
obs_data_release(obj);
} else {
captions->stop();
captions->m.lock();
obs_data_t *obj = obs_data_get_obj(save_data, "captions");
if (!obj)
obj = obs_data_create();
obs_data_set_default_int(obj, "lang_id",
GetUserDefaultUILanguage());
bool enabled = obs_data_get_bool(obj, "enabled");
captions->source_name = obs_data_get_string(obj, "source");
captions->lang_id = (int)obs_data_get_int(obj, "lang_id");
captions->source = GetWeakSourceByName(
captions->source_name.c_str());
obs_data_release(obj);
captions->m.unlock();
if (enabled)
captions->start();
}
}
extern "C" void InitCaptions()
{
QAction *action = (QAction*)obs_frontend_add_tools_menu_qaction(
obs_module_text("Captions"));
captions = new obs_captions;
auto cb = [] ()
{
obs_frontend_push_ui_translation(obs_module_get_string);
QWidget *window =
(QWidget*)obs_frontend_get_main_window();
CaptionsDialog dialog(window);
dialog.exec();
obs_frontend_pop_ui_translation();
};
obs_frontend_add_save_callback(save_caption_data, nullptr);
obs_frontend_add_event_callback(obs_event, nullptr);
action->connect(action, &QAction::triggered, cb);
}

View file

@ -0,0 +1,20 @@
#pragma once
#include <QDialog>
#include <memory>
#include "ui_captions.h"
class CaptionsDialog : public QDialog {
Q_OBJECT
std::unique_ptr<Ui_CaptionsDialog> ui;
public:
CaptionsDialog(QWidget *parent);
public slots:
void on_source_currentIndexChanged(int idx);
void on_enable_clicked(bool checked);
void on_language_currentIndexChanged(int idx);
};

View file

@ -4,8 +4,22 @@ SceneSwitcher.OnNoMatch.DontSwitch="No canviar"
SceneSwitcher.OnNoMatch.SwitchTo="Canvia a:"
SceneSwitcher.CheckInterval="Comprova el títol de la finestra activa cada:"
SceneSwitcher.ActiveOrNotActive="El canviador de escena està:"
InvalidRegex.Title="Expressió regular no vàlida"
InvalidRegex.Text="La expressió regular que heu introduït no es vàlida."
Active="Actiu"
Inactive="Inactiu"
Start="Inicia"
Stop="Atura"
Captions="Subtítols (Experimental)"
Captions.AudioSource="Font d'àudio"
Captions.CurrentSystemLanguage="Idioma actual del sistema (%1)"
OutputTimer="Temporitzador de sortida"
OutputTimer.Stream="Atura la transmissió després de:"
OutputTimer.Record="Atura la gravació després de:"
OutputTimer.Stream.StoppingIn="La transmissió s'aturarà en:"
OutputTimer.Record.StoppingIn="La gravació s'aturarà en:"
OutputTimer.Stream.EnableEverytime="Activa el temporitzador en cada transmissió"
OutputTimer.Record.EnableEverytime="Activa el temporitzador en cada enregistrament"

View file

@ -4,8 +4,22 @@ SceneSwitcher.OnNoMatch.DontSwitch="Nepřepínat"
SceneSwitcher.OnNoMatch.SwitchTo="Přepnout na:"
SceneSwitcher.CheckInterval="Kontrolovat titulek aktivního okna každých:"
SceneSwitcher.ActiveOrNotActive="Přepínač scén je:"
InvalidRegex.Title="Chybný regulární výraz"
InvalidRegex.Text="Zadaný regulární výraz je chybný."
Active="Aktivní"
Inactive="Neaktivní"
Start="Spustit"
Stop="Zastavit"
Captions="Titulky (experiment.)"
Captions.AudioSource="Zdroj zvuku"
Captions.CurrentSystemLanguage="Aktuální systémový jazyk (%1)"
OutputTimer="Časovač"
OutputTimer.Stream="Přestat vysílat po:"
OutputTimer.Record="Přestat nahrávat po:"
OutputTimer.Stream.StoppingIn="Vysílání se zastaví za:"
OutputTimer.Record.StoppingIn="Nahrávání se zastaví za:"
OutputTimer.Stream.EnableEverytime="Pokaždé povolit časovač vysílání"
OutputTimer.Record.EnableEverytime="Pokaždé povolit časovač nahrávání"

View file

@ -1,11 +1,25 @@
SceneSwitcher="Automatisk sceneskifter"
SceneSwitcher.OnNoMatch="Når intet vindue svarer til:"
SceneSwitcher="Automatisk sceneomskifter"
SceneSwitcher.OnNoMatch="Når intet vindue matcher:"
SceneSwitcher.OnNoMatch.DontSwitch="Skift ikke"
SceneSwitcher.OnNoMatch.SwitchTo="Skift til:"
SceneSwitcher.CheckInterval="Kontroller aktivt vinduestitel hvert:"
SceneSwitcher.ActiveOrNotActive="Sceneskifter er:"
SceneSwitcher.ActiveOrNotActive="Sceneomskifter er:"
InvalidRegex.Title="Ugyldigt regulært udtryk"
InvalidRegex.Text="Det af dig angivne regulære udtryk er ugyldigt."
Active="Aktiv"
Inactive="Inaktiv"
Start="Start"
Stop="Stop"
Captions="Undertekster (eksperimentel)"
Captions.AudioSource="Lydkilde"
Captions.CurrentSystemLanguage="Aktuelt systemsprog (%1)"
OutputTimer="Output-timer"
OutputTimer.Stream="Stands streaming efter:"
OutputTimer.Record="Stands optagelse efter:"
OutputTimer.Stream.StoppingIn="Streaming standser om:"
OutputTimer.Record.StoppingIn="Streaming standser om:"
OutputTimer.Stream.EnableEverytime="Aktivér streaming-timer hver gang"
OutputTimer.Record.EnableEverytime="Aktivér optage-timer hver gang"

View file

@ -4,8 +4,22 @@ SceneSwitcher.OnNoMatch.DontSwitch="Nicht wechseln"
SceneSwitcher.OnNoMatch.SwitchTo="Wechseln zu:"
SceneSwitcher.CheckInterval="Titel des aktiven Fensters überprüfen alle:"
SceneSwitcher.ActiveOrNotActive="Szenenwechsler ist:"
InvalidRegex.Title="Ungültiger regulärer Ausdruck"
InvalidRegex.Text="Der reguläre Ausdruck, den Sie eingegeben haben, ist ungültig."
Active="Aktiv"
Inactive="Inaktiv"
Start="Start"
Stop="Stop"
Captions="Untertitel (experimentell)"
Captions.AudioSource="Audioquelle"
Captions.CurrentSystemLanguage="Aktuelle Systemsprache (%1)"
OutputTimer="Ausgabetimer"
OutputTimer.Stream="Stoppe Stream nach:"
OutputTimer.Record="Stoppe Aufnahme nach:"
OutputTimer.Stream.StoppingIn="Stream stoppt in:"
OutputTimer.Record.StoppingIn="Aufnahme stoppt in:"
OutputTimer.Stream.EnableEverytime="Streaming-Timer jedes Mal aktivieren"
OutputTimer.Record.EnableEverytime="Aufnahme-Timer jedes Mal aktivieren"

View file

@ -4,7 +4,21 @@ SceneSwitcher.OnNoMatch.DontSwitch="Don't switch"
SceneSwitcher.OnNoMatch.SwitchTo="Switch to:"
SceneSwitcher.CheckInterval="Check active window title every:"
SceneSwitcher.ActiveOrNotActive="Scene Switcher is:"
InvalidRegex.Title="Invalid Regular Expression"
InvalidRegex.Text="The regular expression that you entered is invalid."
Active="Active"
Inactive="Inactive"
Start="Start"
Stop="Stop"
Captions="Captions (Experimental)"
Captions.AudioSource="Audio source"
Captions.CurrentSystemLanguage="Current System Language (%1)"
OutputTimer="Output Timer"
OutputTimer.Stream="Stop streaming after:"
OutputTimer.Record="Stop recording after:"
OutputTimer.Stream.StoppingIn="Streaming stopping in:"
OutputTimer.Record.StoppingIn="Recording stopping in:"
OutputTimer.Stream.EnableEverytime="Enable streaming timer every time"
OutputTimer.Record.EnableEverytime="Enable recording timer every time"

View file

@ -4,8 +4,22 @@ SceneSwitcher.OnNoMatch.DontSwitch="No cambiar"
SceneSwitcher.OnNoMatch.SwitchTo="Cambiar a:"
SceneSwitcher.CheckInterval="Comprobar el título de la ventana activa cada:"
SceneSwitcher.ActiveOrNotActive="El cambiador de escena está:"
InvalidRegex.Title="Expresión regular no válida"
InvalidRegex.Text="La expresión regular que ha introducido no es válida."
Active="Activo"
Inactive="Inactivo"
Start="Iniciar"
Stop="Detener"
Captions="Subtítulos (Experimental)"
Captions.AudioSource="Fuente de audio"
Captions.CurrentSystemLanguage="Idioma actual del sistema (%1)"
OutputTimer="Temporizador de salida"
OutputTimer.Stream="Detener la transmisión después de:"
OutputTimer.Record="Detener la grabación después de:"
OutputTimer.Stream.StoppingIn="Finalizando transmisión en:"
OutputTimer.Record.StoppingIn="Finalizando grabación en:"
OutputTimer.Stream.EnableEverytime="Activar temporizador en cada transmisión"
OutputTimer.Record.EnableEverytime="Activar temporizador en cada grabación"

View file

@ -0,0 +1,21 @@
SceneSwitcher="Automaatne stseeni vahetaja"
SceneSwitcher.OnNoMatch="Kui üksi aken ei sobi:"
SceneSwitcher.OnNoMatch.DontSwitch="Ära vaheta"
SceneSwitcher.OnNoMatch.SwitchTo="Lülitu ümber:"
SceneSwitcher.CheckInterval="Kontrollige aktiivse akna pealkiri iga:"
SceneSwitcher.ActiveOrNotActive="Stseen vahetaja on:"
Active="Aktiivne"
Inactive="Inaktiivne"
Start="Alusta"
Stop="Lõpeta"
Captions="Subtiitrid (eksperimentaalne)"
Captions.AudioSource="Heli allikas"
Captions.CurrentSystemLanguage="Praegune süsteemi keel (%1)"
OutputTimer="Väljundi taimer"
OutputTimer.Stream="Lõpeta voogedastus pärast:"
OutputTimer.Record="Lõpeta voogedastus pärast:"
OutputTimer.Stream.StoppingIn="Voogedastus lõppeb:"
OutputTimer.Record.StoppingIn="Salvestamine lõppeb:"

View file

@ -4,8 +4,22 @@ SceneSwitcher.OnNoMatch.DontSwitch="Ez aldatu"
SceneSwitcher.OnNoMatch.SwitchTo="Aldatu hona:"
SceneSwitcher.CheckInterval="Leiho aktiboaren titulua egiaztatzeko maiztasuna:"
SceneSwitcher.ActiveOrNotActive="Eszena aldatzailea dago:"
InvalidRegex.Title="Adierazpen erregular baliogabea"
InvalidRegex.Text="Sartu duzun adierazpen erregularra baliogabea da."
Active="Aktiboa"
Inactive="Inaktiboa"
Start="Hasi"
Stop="Gelditu"
Captions="Epigrafeak (esperimentala)"
Captions.AudioSource="Audio-iturburua"
Captions.CurrentSystemLanguage="Sistemaren hizkuntza (%1)"
OutputTimer="Irteera tenporizadorea"
OutputTimer.Stream="Gelditu transmisioa hau pasata:"
OutputTimer.Record="Gelditu grabazioa hau pasata:"
OutputTimer.Stream.StoppingIn="Transmisioa geldituko da: hau barru:"
OutputTimer.Record.StoppingIn="Grabazioa geldituko da hau barru:"
OutputTimer.Stream.EnableEverytime="Gaitu transmisio tenporizadorea aldiro"
OutputTimer.Record.EnableEverytime="Gaitu grabazio tenporizadorea aldiro"

View file

@ -4,8 +4,22 @@ SceneSwitcher.OnNoMatch.DontSwitch="Älä vaihda"
SceneSwitcher.OnNoMatch.SwitchTo="Vaihda:"
SceneSwitcher.CheckInterval="Tarkista aktiivisen ikkunan otsikko:"
SceneSwitcher.ActiveOrNotActive="Skenen vaihtaja:"
InvalidRegex.Title="Invalid Regular Expression"
InvalidRegex.Text="The regular expression that you entered is invalid."
Active="Aktiivinen"
Inactive="Ei käytössä"
Start="Käynnistä"
Stop="Pysäytä"
Captions="Kuvatekstit (Experimental)"
Captions.AudioSource="Äänilähde"
Captions.CurrentSystemLanguage="Järjestelmän kieli (%1)"
OutputTimer="Ulostulo-ajastin"
OutputTimer.Stream="Pysäyttää lähetyksen:"
OutputTimer.Record="Pysäyttää tallennuksen:"
OutputTimer.Stream.StoppingIn="Lähetys pysäytetään:"
OutputTimer.Record.StoppingIn="Tallennus pysäytetään:"
OutputTimer.Stream.EnableEverytime="Ota lähetysajastin käyttöön aina"
OutputTimer.Record.EnableEverytime="Ota tallennusajastin käyttöön aina"

View file

@ -4,8 +4,22 @@ SceneSwitcher.OnNoMatch.DontSwitch="Ne rien faire"
SceneSwitcher.OnNoMatch.SwitchTo="Basculer vers :"
SceneSwitcher.CheckInterval="Détecter le titre de la fenêtre active toutes les :"
SceneSwitcher.ActiveOrNotActive="Etat du sélecteur automatique :"
InvalidRegex.Title="Expression invalide"
InvalidRegex.Text="L'expression régulière saisie est invalide."
Active="Actif"
Inactive="Inactif"
Start="Démarrer"
Stop="Arrêter"
Captions="Sous-titres (expérimental)"
Captions.AudioSource="Source audio"
Captions.CurrentSystemLanguage="Langue du système (%1)"
OutputTimer="Minuterie des sorties"
OutputTimer.Stream="Arrêter le streaming dans :"
OutputTimer.Record="Arrêter l'enregistrement dans :"
OutputTimer.Stream.StoppingIn="Arrêt du streaming dans :"
OutputTimer.Record.StoppingIn="Arrêt de l'enregistrement dans :"
OutputTimer.Stream.EnableEverytime="Activer le minuteur automatiquement à chaque stream"
OutputTimer.Record.EnableEverytime="Activer le minuteur automatiquement à chaque enregistrement"

View file

@ -4,8 +4,19 @@ SceneSwitcher.OnNoMatch.DontSwitch="Ne menjaj"
SceneSwitcher.OnNoMatch.SwitchTo="Promeni na:"
SceneSwitcher.CheckInterval="Proveravaj naziv aktivnog prozora svakih:"
SceneSwitcher.ActiveOrNotActive="Menjač scena je:"
InvalidRegex.Title="Neispravan regularni izraz"
InvalidRegex.Text="Regularni izraz koji ste uneli nije ispravan."
Active="Aktivan"
Inactive="Neaktivan"
Start="Pokreni"
Stop="Zaustavi"
OutputTimer="Tempomat snimanja i emitovanja"
OutputTimer.Stream="Zaustavi emitovanje nakon:"
OutputTimer.Record="Zaustavi snimanje nakon:"
OutputTimer.Stream.StoppingIn="Prekidanje emitovanja za:"
OutputTimer.Record.StoppingIn="Prekidanje snimanja za:"
OutputTimer.Stream.EnableEverytime="Omogući štopovanje emitovanja svaki put"
OutputTimer.Record.EnableEverytime="Omogući štopovanje snimanja svaki put"

View file

@ -4,8 +4,22 @@ SceneSwitcher.OnNoMatch.DontSwitch="Ne váltson"
SceneSwitcher.OnNoMatch.SwitchTo="Váltson:"
SceneSwitcher.CheckInterval="Aktív ablak ellenőrzése ennyi időközönként:"
SceneSwitcher.ActiveOrNotActive="Jelenet váltó:"
InvalidRegex.Title="Érvénytelen kifejezés"
InvalidRegex.Text="A megadott kifejezés érvénytelen."
Active="Aktív"
Inactive="Inaktiv"
Start="Start"
Stop="Stop"
Captions="Feliratok (Kísérleti)"
Captions.AudioSource="Audio forrás"
Captions.CurrentSystemLanguage="Rendszer aktuális nyelve (%1)"
OutputTimer="Kimeneti időzítő"
OutputTimer.Stream="Stream leállítása:"
OutputTimer.Record="Felvétel leállítása:"
OutputTimer.Stream.StoppingIn="A stream leáll:"
OutputTimer.Record.StoppingIn="Felvétel leáll:"
OutputTimer.Stream.EnableEverytime="Stream időzítő indítása minden alkalommal"
OutputTimer.Record.EnableEverytime="Felvétel időzítő indítása minden alkalommal"

View file

@ -0,0 +1,25 @@
SceneSwitcher="Cambia scena automatico"
SceneSwitcher.OnNoMatch="Quando nessuna scena coincide a:"
SceneSwitcher.OnNoMatch.DontSwitch="Non passare"
SceneSwitcher.OnNoMatch.SwitchTo="Passa a:"
SceneSwitcher.CheckInterval="Controlla il titolo della finestra attiva ogni:"
SceneSwitcher.ActiveOrNotActive="Lo scene switcher è:"
InvalidRegex.Title="Espressione regolare non valida"
InvalidRegex.Text="L'espressione regolare che hai inserito non è valido."
Active="Attivo"
Inactive="Inattivo"
Start="Inizio"
Stop="Stop"
Captions="Sottotitoli (Sperimentale)"
Captions.AudioSource="Fonte audio"
Captions.CurrentSystemLanguage="Lingua del sistema in uso (%1)"
OutputTimer="Timer Output"
OutputTimer.Stream="Termina diretta dopo:"
OutputTimer.Record="Termina registrazione dopo:"
OutputTimer.Stream.StoppingIn="La diretta terminerà in:"
OutputTimer.Record.StoppingIn="La registrazione terminerà in:"
OutputTimer.Stream.EnableEverytime="Abilita il timer per lo streaming ogni volta"
OutputTimer.Record.EnableEverytime="Abilita il timer per la registrazione ogni volta"

View file

@ -4,8 +4,22 @@ SceneSwitcher.OnNoMatch.DontSwitch="切り替えない"
SceneSwitcher.OnNoMatch.SwitchTo="切り替える:"
SceneSwitcher.CheckInterval="アクティブウィンドウタイトルを確認する間隔:"
SceneSwitcher.ActiveOrNotActive="シーンスイッチャーは:"
InvalidRegex.Title="無効な正規表現"
InvalidRegex.Text="入力した正規表現は有効ではありません。"
Active="アクティブ"
Inactive="非アクティブ"
Start="開始"
Stop="停止"
Captions="見出し (実験的)"
Captions.AudioSource="音声ソース"
Captions.CurrentSystemLanguage="現在のシステム言語 (%1)"
OutputTimer="出力タイマー"
OutputTimer.Stream="配信停止の時間設定:"
OutputTimer.Record="録画停止の時間設定:"
OutputTimer.Stream.StoppingIn="配信停止まで:"
OutputTimer.Record.StoppingIn="録画停止まで:"
OutputTimer.Stream.EnableEverytime="毎回配信タイマーを有効にする"
OutputTimer.Record.EnableEverytime="毎回録画タイマーを有効にする"

View file

@ -4,8 +4,22 @@ SceneSwitcher.OnNoMatch.DontSwitch="전환하지 않음"
SceneSwitcher.OnNoMatch.SwitchTo="여기로 전환:"
SceneSwitcher.CheckInterval="활성화된 윈도우 제목을 확인:"
SceneSwitcher.ActiveOrNotActive="장면 전환기:"
InvalidRegex.Title="유효하지 않은 정규 표현"
InvalidRegex.Text="입력한 정규 표현이 유효하지 않습니다."
Active="활성화"
Inactive="비활성화"
Start="시작"
Stop="중단"
Captions="자막 (실험적 기능)"
Captions.AudioSource="오디오 소스"
Captions.CurrentSystemLanguage="현재 시스템 언어 (%1)"
OutputTimer="출력 시간 설정"
OutputTimer.Stream="이 시간 이후 방송 중단:"
OutputTimer.Record="이 시간 이후 녹화 중단:"
OutputTimer.Stream.StoppingIn="방송 중지까지 남은 시간:"
OutputTimer.Record.StoppingIn="녹화 중지까지 남은 시간:"
OutputTimer.Stream.EnableEverytime="매번 방송 시간 기록기 활성화"
OutputTimer.Record.EnableEverytime="매번 녹화 시간 기록기 활성화"

View file

@ -0,0 +1,11 @@
Active="Aktif"
Inactive="Tidak Aktif"
Start="Mula"
Stop="Berhenti"
OutputTimer.Stream="Berhenti 'streaming' selepas:"
OutputTimer.Record="Berhenti merakam selepas:"
OutputTimer.Stream.StoppingIn="'Streaming' dihentikan dalam:"
OutputTimer.Record.StoppingIn="Rakaman dihentikan dalam:"

View file

@ -0,0 +1,25 @@
SceneSwitcher="Automatisk Scene Skifter"
SceneSwitcher.OnNoMatch="Når ingen vindu passer overens:"
SceneSwitcher.OnNoMatch.DontSwitch="Ikke bytt"
SceneSwitcher.OnNoMatch.SwitchTo="Bytt til:"
SceneSwitcher.CheckInterval="Sjekk aktivt vindu hver:"
SceneSwitcher.ActiveOrNotActive="Sceneskifter er:"
InvalidRegex.Title="Ugyldig regulært utrykk"
InvalidRegex.Text="Det regulære utrykket du har angitt er ugyldig."
Active="Aktiv"
Inactive="Inaktiv"
Start="Start"
Stop="Stopp"
Captions="Bildetekster (eksperimentell)"
Captions.AudioSource="Lyd kilde"
Captions.CurrentSystemLanguage="Någjeldende System Språk"
OutputTimer="Stoppeklokke"
OutputTimer.Stream="Stopp streaming etter:"
OutputTimer.Record="Stopp opptak etter:"
OutputTimer.Stream.StoppingIn="Streaming stopper om:"
OutputTimer.Record.StoppingIn="Opptak stopper om:"
OutputTimer.Stream.EnableEverytime="Aktiver streaming timer hver gang"
OutputTimer.Record.EnableEverytime="Aktiver opptaks timer hver gang"

View file

@ -4,8 +4,22 @@ SceneSwitcher.OnNoMatch.DontSwitch="Wissel niet"
SceneSwitcher.OnNoMatch.SwitchTo="Wissel naar:"
SceneSwitcher.CheckInterval="Controleer actieve venstertitel elke:"
SceneSwitcher.ActiveOrNotActive="Scènewisselaar is:"
InvalidRegex.Title="Ongeldige Reguliere Expressie"
InvalidRegex.Text="De ingevoerde reguliere expressie is ongeldig."
Active="Actief"
Inactive="Inactief"
Start="Start"
Stop="Stop"
Captions="Ondertiteling (Experimenteel)"
Captions.AudioSource="Audiobron"
Captions.CurrentSystemLanguage="Huidige Systeemtaal (%1)"
OutputTimer="Uitvoertimer"
OutputTimer.Stream="Stop met streamen na:"
OutputTimer.Record="Stop met opnemen na:"
OutputTimer.Stream.StoppingIn="Stream stopt over:"
OutputTimer.Record.StoppingIn="Opname stopt over:"
OutputTimer.Stream.EnableEverytime="Schakel streaming timer elke keer in"
OutputTimer.Record.EnableEverytime="Schakel opnametimer elke keer in"

View file

@ -4,8 +4,22 @@ SceneSwitcher.OnNoMatch.DontSwitch="Nie przełączaj"
SceneSwitcher.OnNoMatch.SwitchTo="Przełącz na:"
SceneSwitcher.CheckInterval="Sprawdź tytuł aktywnego okna co:"
SceneSwitcher.ActiveOrNotActive="Przełączanie scen jest:"
InvalidRegex.Title="Nieprawidłowe wyrażenie regularne"
InvalidRegex.Text="Podane wyrażenie regularne jest nieprawidłowe."
Active="Aktywne"
Inactive="Nieaktywne"
Start="Start"
Stop="Stop"
Captions="Podpisy (eksperymentalne)"
Captions.AudioSource="Źródła dźwięku"
Captions.CurrentSystemLanguage="Obecny język systemu (%1)"
OutputTimer="Wyłącznik czasowy"
OutputTimer.Stream="Zatrzymaj stream po:"
OutputTimer.Record="Zatrzymaj nagrywanie po:"
OutputTimer.Stream.StoppingIn="Zatrzymanie streamu za:"
OutputTimer.Record.StoppingIn="Zatrzymanie nagrywania za:"
OutputTimer.Stream.EnableEverytime="Włącz timer streamu za każdym razem"
OutputTimer.Record.EnableEverytime="Włącz timer nagrywania za każdym razem"

View file

@ -4,8 +4,22 @@ SceneSwitcher.OnNoMatch.DontSwitch="Não alternar"
SceneSwitcher.OnNoMatch.SwitchTo="Alternar para:"
SceneSwitcher.CheckInterval="Checar o título da janela ativa a cada:"
SceneSwitcher.ActiveOrNotActive="O alternador de cenas está:"
InvalidRegex.Title="Expressão Regular inválida"
InvalidRegex.Text="A expressão regular que você inseriu é inválida."
Active="Ligado"
Inactive="Desligado"
Start="Iniciar"
Stop="Parar"
Captions="Legendas (Experimental)"
Captions.AudioSource="Fonte de Áudio"
Captions.CurrentSystemLanguage="Idioma Atual do Sistema (%1)"
OutputTimer="Temporizador de saída"
OutputTimer.Stream="Parar a transmissão após:"
OutputTimer.Record="Parar a gravação após:"
OutputTimer.Stream.StoppingIn="A transmissão irá parar em:"
OutputTimer.Record.StoppingIn="A gravação irá parar em:"
OutputTimer.Stream.EnableEverytime="Ativar o timer streaming o tempo todo"
OutputTimer.Record.EnableEverytime="Ativar o timer de gravação o tempo todo"

View file

@ -0,0 +1,20 @@
SceneSwitcher="Troca automática de cenas"
SceneSwitcher.OnNoMatch="Quando nenhuma janela corresponder:"
SceneSwitcher.OnNoMatch.DontSwitch="Não trocar"
SceneSwitcher.OnNoMatch.SwitchTo="Trocar para:"
SceneSwitcher.CheckInterval="Verificar o nome da janela ativa a cada:"
SceneSwitcher.ActiveOrNotActive="A troca de cenas está:"
InvalidRegex.Title="Expressão regular inválida"
InvalidRegex.Text="A expressão regular que introduziu é inválida."
Active="Ativa"
Inactive="Inativa"
Start="Iniciar"
Stop="Parar"
OutputTimer="Temporizador de saída"
OutputTimer.Stream="Para a transmissão após:"
OutputTimer.Record="Parar a gravação após:"
OutputTimer.Stream.StoppingIn="A transmissão irá parar em:"
OutputTimer.Record.StoppingIn="A gravação irá parar em:"

View file

@ -0,0 +1,13 @@
SceneSwitcher="Schimbator automat de scenă"
SceneSwitcher.OnNoMatch="Cand nici o fereastra nu se potriveste:"
SceneSwitcher.OnNoMatch.SwitchTo="Schimbă la:"
Active="Activ"
Inactive="Inactiv"
Start="Pornire"
Stop="Oprire"
Captions.AudioSource="Sursa audio"
Captions.CurrentSystemLanguage="Limba curentă a sistemului (%1)"
OutputTimer.Record="Opriți inregistrarea dupa:"

View file

@ -4,8 +4,22 @@ SceneSwitcher.OnNoMatch.DontSwitch="Не переключать"
SceneSwitcher.OnNoMatch.SwitchTo="Переключать на:"
SceneSwitcher.CheckInterval="Проверять имя активного окна каждые:"
SceneSwitcher.ActiveOrNotActive="Переключатель сцен:"
InvalidRegex.Title="Недопустимое регулярное выражение"
InvalidRegex.Text="Регулярное выражение, которое вы ввели, содержит ошибки."
Active="Активен"
Inactive="Неактивен"
Start="Запустить"
Stop="Остановить"
Captions="Субтитры (экспериментально)"
Captions.AudioSource="Источник звука"
Captions.CurrentSystemLanguage="Текущий язык системы (%1)"
OutputTimer="Таймер записи и стрима"
OutputTimer.Stream="Завершить стрим через:"
OutputTimer.Record="Завершить запись через:"
OutputTimer.Stream.StoppingIn="Стрим будет завершён через:"
OutputTimer.Record.StoppingIn="Запись будет завершена через:"
OutputTimer.Stream.EnableEverytime="Включать таймер стрима каждый раз"
OutputTimer.Record.EnableEverytime="Включать таймер записи каждый раз"

View file

@ -0,0 +1,10 @@
SceneSwitcher.CheckInterval="Kontrolovať aktívne okno každých:"
Active="Aktívny"
Inactive="Neaktivní"
Start="Spustiť"
Stop="Zastaviť"
OutputTimer.Stream="Zastaviť stream po:"
OutputTimer.Record="Zastaviť nahrávanie po:"

View file

@ -4,8 +4,19 @@ SceneSwitcher.OnNoMatch.DontSwitch="Ne menjaj"
SceneSwitcher.OnNoMatch.SwitchTo="Promeni na:"
SceneSwitcher.CheckInterval="Proveravaj naziv aktivnog prozora svakih:"
SceneSwitcher.ActiveOrNotActive="Menjač scena je:"
InvalidRegex.Title="Neispravan regularni izraz"
InvalidRegex.Text="Regularni izraz koji ste uneli nije ispravan."
Active="Aktivan"
Inactive="Neaktivan"
Start="Pokreni"
Stop="Zaustavi"
OutputTimer="Tempomat snimanja i emitovanja"
OutputTimer.Stream="Zaustavi emitovanje nakon:"
OutputTimer.Record="Zaustavi snimanje nakon:"
OutputTimer.Stream.StoppingIn="Prekidanje emitovanja za:"
OutputTimer.Record.StoppingIn="Prekidanje snimanja za:"
OutputTimer.Stream.EnableEverytime="Omogući štopovanje emitovanja svaki put"
OutputTimer.Record.EnableEverytime="Omogući štopovanje snimanja svaki put"

View file

@ -4,8 +4,19 @@ SceneSwitcher.OnNoMatch.DontSwitch="Не мењај"
SceneSwitcher.OnNoMatch.SwitchTo="Промени на:"
SceneSwitcher.CheckInterval="Проверавај назив активног прозора сваких:"
SceneSwitcher.ActiveOrNotActive="Мењач сцена је:"
InvalidRegex.Title="Неисправан регуларни израз"
InvalidRegex.Text="Регуларни израз који сте унели није исправан."
Active="Активан"
Inactive="Неактиван"
Start="Покрени"
Stop="Заустави"
OutputTimer="Темпомат снимања и емитовања"
OutputTimer.Stream="Заустави емитовање након:"
OutputTimer.Record="Заустави снимање након:"
OutputTimer.Stream.StoppingIn="Прекидање емитовања за:"
OutputTimer.Record.StoppingIn="Прекидање снимања за:"
OutputTimer.Stream.EnableEverytime="Омогући штоповање емитовање сваки пут"
OutputTimer.Record.EnableEverytime="Омогући штоповање снимања сваки пут"

View file

@ -4,8 +4,22 @@ SceneSwitcher.OnNoMatch.DontSwitch="Byt inte"
SceneSwitcher.OnNoMatch.SwitchTo="Byt till:"
SceneSwitcher.CheckInterval="Kontrollera aktiv fönstertitel varje:"
SceneSwitcher.ActiveOrNotActive="Scenbytaren är:"
InvalidRegex.Title="Ogiltigt reguljärt uttryck"
InvalidRegex.Text="Det reguljära uttrycket du angav är ogiltigt."
Active="Aktiv"
Inactive="Inaktiv"
Start="Starta"
Stop="Stoppa"
Captions="Undertexter (experimentell)"
Captions.AudioSource="Ljudkälla"
Captions.CurrentSystemLanguage="Aktuellt systemspråk (%1)"
OutputTimer="Utdatatimer"
OutputTimer.Stream="Sluta streama efter:"
OutputTimer.Record="Stoppa inspelningen efter:"
OutputTimer.Stream.StoppingIn="Streamen stoppas om:"
OutputTimer.Record.StoppingIn="Inspelningen stoppas om:"
OutputTimer.Stream.EnableEverytime="Aktivera strömtimer varje gång"
OutputTimer.Record.EnableEverytime="Aktivera inspelningstimer varje gång"

View file

@ -0,0 +1,25 @@
SceneSwitcher="Otomatik Sahne Değiştirici"
SceneSwitcher.OnNoMatch="Hiçbir pencere ile eşleşmez ise:"
SceneSwitcher.OnNoMatch.DontSwitch="Geçiş yapma"
SceneSwitcher.OnNoMatch.SwitchTo="Şuna geç:"
SceneSwitcher.CheckInterval="Etkin pencere başlığını kontrol et:"
SceneSwitcher.ActiveOrNotActive="Sahne Değiştirici:"
InvalidRegex.Title="Geçersiz Kurallı İfade"
InvalidRegex.Text="Girdiğiniz kurallı ifade geçersiz."
Active="Etkin"
Inactive="Devre Dışı"
Start="Başlat"
Stop="Durdur"
Captions="Altyazı (Deneysel)"
Captions.AudioSource="Ses kaynağı"
Captions.CurrentSystemLanguage="Geçerli Sistem Dili (%1)"
OutputTimer=ıkış Zamanlayıcısı"
OutputTimer.Stream="Şuradan sonra yayını durdur:"
OutputTimer.Record="Şuradan sonra kaydı durdur:"
OutputTimer.Stream.StoppingIn="Yayın durduruluyor:"
OutputTimer.Record.StoppingIn="Kayıt durduruluyor:"
OutputTimer.Stream.EnableEverytime="Her zaman yayın zamanlayıcıyı etkinleştir"
OutputTimer.Record.EnableEverytime="Her zaman kayıt zamanlayıcıyı etkinleştir"

View file

@ -4,8 +4,22 @@ SceneSwitcher.OnNoMatch.DontSwitch="Нічого не робити"
SceneSwitcher.OnNoMatch.SwitchTo="Перейти до:"
SceneSwitcher.CheckInterval="Перевіряти заголовок активного вікна кожні:"
SceneSwitcher.ActiveOrNotActive="Автоматичний перехід між Сценами:"
InvalidRegex.Title="Неприпустимий регулярний вираз"
InvalidRegex.Text="Ви ввели неприпустимий регулярний вираз (шаблон)."
Active="Активний"
Inactive="Неактивний"
Start="Запустити"
Stop="Зупинити"
Captions="Субтитри (експериментально)"
Captions.AudioSource="Джерело Аудіо"
Captions.CurrentSystemLanguage="Поточна мова Системи (%1)"
OutputTimer="Таймер для Виводу"
OutputTimer.Stream="Закінчити трансляцію за:"
OutputTimer.Record="Зупинити запис за:"
OutputTimer.Stream.StoppingIn="Трансляція припиниться за:"
OutputTimer.Record.StoppingIn="Запис зупиниться за:"
OutputTimer.Stream.EnableEverytime="Щоразу запускається трансляція - вмикати Таймер для Виводу"
OutputTimer.Record.EnableEverytime="Щоразу починається запис - вмикати Таймер для Виводу"

View file

@ -0,0 +1,17 @@
SceneSwitcher="Tự động chuyển cảnh"
SceneSwitcher.OnNoMatch="Khi cửa sổ không khớp:"
SceneSwitcher.OnNoMatch.DontSwitch="Không chuyển"
SceneSwitcher.OnNoMatch.SwitchTo="Chuyển sang:"
SceneSwitcher.CheckInterval="Kiểm tra tiêu đề cửa sổ mỗi:"
SceneSwitcher.ActiveOrNotActive="Chuyển cảnh đang:"
Active="Đang hoạt động"
Inactive="Không hoạt động"
Start="Bắt đầu"
Stop="Dừng"
OutputTimer.Stream="Dừng stream sau:"
OutputTimer.Record="Dừng ghi video sau:"
OutputTimer.Stream.StoppingIn="Stream sẽ dừng trong:"
OutputTimer.Record.StoppingIn="Quay video sẽ dừng trong:"

View file

@ -4,8 +4,22 @@ SceneSwitcher.OnNoMatch.DontSwitch="不切换"
SceneSwitcher.OnNoMatch.SwitchTo="切换到:"
SceneSwitcher.CheckInterval="检查活动窗口的标题,每:"
SceneSwitcher.ActiveOrNotActive="场景切换器是:"
InvalidRegex.Title="无效的正则表达式"
InvalidRegex.Text="您输入的正则表达式是无效的。"
Active="已激活"
Inactive="未激活"
Start="开始"
Stop="停止"
Captions="标题(实验)"
Captions.AudioSource="音频源"
Captions.CurrentSystemLanguage="当前系统语言 (%1)"
OutputTimer="输出计时器"
OutputTimer.Stream="停止流处理后:"
OutputTimer.Record="停止录制后:"
OutputTimer.Stream.StoppingIn="串流停止在:"
OutputTimer.Record.StoppingIn="录制停止在:"
OutputTimer.Stream.EnableEverytime="每次启用流计时器"
OutputTimer.Record.EnableEverytime="每次启用录制计时器"

View file

@ -4,8 +4,22 @@ SceneSwitcher.OnNoMatch.DontSwitch="不切換"
SceneSwitcher.OnNoMatch.SwitchTo="切換到︰"
SceneSwitcher.CheckInterval="檢查使用中視窗標題的頻率︰"
SceneSwitcher.ActiveOrNotActive="場景切換器︰"
InvalidRegex.Title="無效的正規表達式"
InvalidRegex.Text="您輸入的正則運算式不正確"
Active="啟動中"
Inactive="未啟動"
Start="開始"
Stop="停止"
Captions="標題 (實驗)"
Captions.AudioSource="音訊源"
Captions.CurrentSystemLanguage="目前系統語言 (%1)"
OutputTimer="輸出計時器"
OutputTimer.Stream="在下面時間後停止串流:"
OutputTimer.Record="在下面時間後停止錄影:"
OutputTimer.Stream.StoppingIn="串流將在下面時間內停止"
OutputTimer.Record.StoppingIn="錄影將在下面時間內停止"
OutputTimer.Stream.EnableEverytime="每次都啟動串流計時器"
OutputTimer.Record.EnableEverytime="每次都啟動錄影計時器"

View file

@ -0,0 +1,132 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CaptionsDialog</class>
<widget class="QDialog" name="CaptionsDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>519</width>
<height>140</height>
</rect>
</property>
<property name="windowTitle">
<string>Captions</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QFormLayout" name="formLayout">
<property name="labelAlignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<item row="1" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Captions.AudioSource</string>
</property>
<property name="buddy">
<cstring>source</cstring>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="source">
<property name="insertPolicy">
<enum>QComboBox::InsertAlphabetically</enum>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="enable">
<property name="text">
<string>Enable</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Basic.Settings.General.Language</string>
</property>
<property name="buddy">
<cstring>language</cstring>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="language"/>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="accept">
<property name="text">
<string>OK</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>accept</sender>
<signal>clicked()</signal>
<receiver>CaptionsDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>268</x>
<y>331</y>
</hint>
<hint type="destinationlabel">
<x>229</x>
<y>-11</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View file

@ -0,0 +1,208 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>OutputTimer</class>
<widget class="QDialog" name="OutputTimer">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>600</width>
<height>200</height>
</rect>
</property>
<property name="windowTitle">
<string>OutputTimer</string>
</property>
<layout class="QGridLayout" name="timerLayout">
<item row="0" column="1">
<widget class="QLabel" name="streamingLabel">
<property name="text">
<string>OutputTimer.Stream</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QSpinBox" name="streamingTimerHours">
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>24</number>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QLabel" name="hoursLabel">
<property name="text">
<string>Hours</string>
</property>
</widget>
</item>
<item row="0" column="4">
<widget class="QSpinBox" name="streamingTimerMinutes">
<property name="maximum">
<number>59</number>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item row="0" column="5">
<widget class="QLabel" name="minutesLabel">
<property name="text">
<string>Minutes</string>
</property>
</widget>
</item>
<item row="0" column="6">
<widget class="QSpinBox" name="streamingTimerSeconds">
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>59</number>
</property>
<property name="value">
<number>30</number>
</property>
</widget>
</item>
<item row="0" column="7">
<widget class="QLabel" name="secondsLabel">
<property name="text">
<string>Seconds</string>
</property>
</widget>
</item>
<item row="0" column="8">
<widget class="QPushButton" name="outputTimerStream">
<property name="text">
<string>Start</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="streamStoppingIn">
<property name="text">
<string>OutputTimer.Stream.StoppingIn</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QLabel" name="streamTime">
<property name="text">
<string>00:00:00</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="recordingLabel">
<property name="text">
<string>OutputTimer.Record</string>
</property>
</widget>
</item>
<item row="3" column="2">
<widget class="QSpinBox" name="recordingTimerHours">
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>24</number>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item row="3" column="3">
<widget class="QLabel" name="hoursLabel_2">
<property name="text">
<string>Hours</string>
</property>
</widget>
</item>
<item row="3" column="4">
<widget class="QSpinBox" name="recordingTimerMinutes">
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>59</number>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item row="3" column="5">
<widget class="QLabel" name="minutesLabel_2">
<property name="text">
<string>Minutes</string>
</property>
</widget>
</item>
<item row="3" column="6">
<widget class="QSpinBox" name="recordingTimerSeconds">
<property name="minimum">
<number>0</number>
</property>
<property name="maximum">
<number>59</number>
</property>
<property name="value">
<number>30</number>
</property>
</widget>
</item>
<item row="3" column="7">
<widget class="QLabel" name="secondsLabel_2">
<property name="text">
<string>Seconds</string>
</property>
</widget>
</item>
<item row="3" column="8">
<widget class="QPushButton" name="outputTimerRecord">
<property name="text">
<string>Start</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLabel" name="recordStoppingIn">
<property name="text">
<string>OutputTimer.Record.StoppingIn</string>
</property>
</widget>
</item>
<item row="5" column="2">
<widget class="QLabel" name="recordTime">
<property name="text">
<string>00:00:00</string>
</property>
</widget>
</item>
<item row="1" column="1" colspan="3">
<widget class="QCheckBox" name="autoStartStreamTimer">
<property name="text">
<string>OutputTimer.Stream.EnableEverytime</string>
</property>
</widget>
</item>
<item row="4" column="1" colspan="3">
<widget class="QCheckBox" name="autoStartRecordTimer">
<property name="text">
<string>OutputTimer.Record.EnableEverytime</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources />
<connections />
</ui>

View file

@ -0,0 +1,3 @@
#pragma once
#define BUILD_CAPTIONS @BUILD_CAPTIONS@

View file

@ -1,18 +1,41 @@
#include <obs-module.h>
#include "frontend-tools-config.h"
OBS_DECLARE_MODULE()
OBS_MODULE_USE_DEFAULT_LOCALE("frontend-tools", "en-US")
#if defined(_WIN32) || defined(__APPLE__)
void InitSceneSwitcher();
void FreeSceneSwitcher();
#endif
#if defined(_WIN32) && BUILD_CAPTIONS
void InitCaptions();
void FreeCaptions();
#endif
void InitOutputTimer();
void FreeOutputTimer();
bool obs_module_load(void)
{
#if defined(_WIN32) || defined(__APPLE__)
InitSceneSwitcher();
#endif
#if defined(_WIN32) && BUILD_CAPTIONS
InitCaptions();
#endif
InitOutputTimer();
return true;
}
void obs_module_unload(void)
{
#if defined(_WIN32) || defined(__APPLE__)
FreeSceneSwitcher();
#endif
#if defined(_WIN32) && BUILD_CAPTIONS
FreeCaptions();
#endif
FreeOutputTimer();
}

View file

@ -0,0 +1,312 @@
#include <obs-frontend-api.h>
#include <obs-module.h>
#include <obs.hpp>
#include <util/util.hpp>
#include <QAction>
#include <QMainWindow>
#include <QTimer>
#include <QObject>
#include "output-timer.hpp"
using namespace std;
OutputTimer *ot;
OutputTimer::OutputTimer(QWidget *parent)
: QDialog(parent),
ui(new Ui_OutputTimer)
{
ui->setupUi(this);
QObject::connect(ui->outputTimerStream, SIGNAL(clicked()), this,
SLOT(StreamingTimerButton()));
QObject::connect(ui->outputTimerRecord, SIGNAL(clicked()), this,
SLOT(RecordingTimerButton()));
streamingTimer = new QTimer(this);
streamingTimerDisplay = new QTimer(this);
recordingTimer = new QTimer(this);
recordingTimerDisplay = new QTimer(this);
}
void OutputTimer::closeEvent(QCloseEvent*)
{
obs_frontend_save();
}
void OutputTimer::StreamingTimerButton()
{
if (!obs_frontend_streaming_active()) {
obs_frontend_streaming_start();
} else if (streamingAlreadyActive) {
StreamTimerStart();
streamingAlreadyActive = false;
} else if (obs_frontend_streaming_active()) {
obs_frontend_streaming_stop();
}
}
void OutputTimer::RecordingTimerButton()
{
if (!obs_frontend_recording_active()) {
obs_frontend_recording_start();
} else if (recordingAlreadyActive) {
RecordTimerStart();
recordingAlreadyActive = false;
} else if (obs_frontend_recording_active()) {
obs_frontend_recording_stop();
}
}
void OutputTimer::StreamTimerStart()
{
if (!isVisible() && ui->autoStartStreamTimer->isChecked() == false) {
streamingAlreadyActive = true;
return;
}
int hours = ui->streamingTimerHours->value();
int minutes = ui->streamingTimerMinutes->value();
int seconds = ui->streamingTimerSeconds->value();
int total = (((hours * 3600) +
(minutes * 60)) +
seconds) * 1000;
if (total == 0)
total = 1000;
streamingTimer->setInterval(total);
streamingTimer->setSingleShot(true);
QObject::connect(streamingTimer, SIGNAL(timeout()),
SLOT(EventStopStreaming()));
QObject::connect(streamingTimerDisplay, SIGNAL(timeout()), this,
SLOT(UpdateStreamTimerDisplay()));
streamingTimer->start();
streamingTimerDisplay->start(1000);
ui->outputTimerStream->setText(obs_module_text("Stop"));
UpdateStreamTimerDisplay();
}
void OutputTimer::RecordTimerStart()
{
if (!isVisible() && ui->autoStartRecordTimer->isChecked() == false) {
recordingAlreadyActive = true;
return;
}
int hours = ui->recordingTimerHours->value();
int minutes = ui->recordingTimerMinutes->value();
int seconds = ui->recordingTimerSeconds->value();
int total = (((hours * 3600) +
(minutes * 60)) +
seconds) * 1000;
if (total == 0)
total = 1000;
recordingTimer->setInterval(total);
recordingTimer->setSingleShot(true);
QObject::connect(recordingTimer, SIGNAL(timeout()),
SLOT(EventStopRecording()));
QObject::connect(recordingTimerDisplay, SIGNAL(timeout()), this,
SLOT(UpdateRecordTimerDisplay()));
recordingTimer->start();
recordingTimerDisplay->start(1000);
ui->outputTimerRecord->setText(obs_module_text("Stop"));
UpdateRecordTimerDisplay();
}
void OutputTimer::StreamTimerStop()
{
streamingAlreadyActive = false;
if (!isVisible() && streamingTimer->isActive() == false)
return;
if (streamingTimer->isActive())
streamingTimer->stop();
ui->outputTimerStream->setText(obs_module_text("Start"));
if (streamingTimerDisplay->isActive())
streamingTimerDisplay->stop();
ui->streamTime->setText("00:00:00");
}
void OutputTimer::RecordTimerStop()
{
recordingAlreadyActive = false;
if (!isVisible() && recordingTimer->isActive() == false)
return;
if (recordingTimer->isActive())
recordingTimer->stop();
ui->outputTimerRecord->setText(obs_module_text("Start"));
if (recordingTimerDisplay->isActive())
recordingTimerDisplay->stop();
ui->recordTime->setText("00:00:00");
}
void OutputTimer::UpdateStreamTimerDisplay()
{
int remainingTime = streamingTimer->remainingTime() / 1000;
int seconds = remainingTime % 60;
int minutes = (remainingTime % 3600) / 60;
int hours = remainingTime / 3600;
QString text;
text.sprintf("%02d:%02d:%02d", hours, minutes, seconds);
ui->streamTime->setText(text);
}
void OutputTimer::UpdateRecordTimerDisplay()
{
int remainingTime = recordingTimer->remainingTime() / 1000;
int seconds = remainingTime % 60;
int minutes = (remainingTime % 3600) / 60;
int hours = remainingTime / 3600;
QString text;
text.sprintf("%02d:%02d:%02d", hours, minutes, seconds);
ui->recordTime->setText(text);
}
void OutputTimer::ShowHideDialog()
{
if (!isVisible()) {
setVisible(true);
QTimer::singleShot(250, this, SLOT(show()));
} else {
setVisible(false);
QTimer::singleShot(250, this, SLOT(hide()));
}
}
void OutputTimer::EventStopStreaming()
{
obs_frontend_streaming_stop();
}
void OutputTimer::EventStopRecording()
{
obs_frontend_recording_stop();
}
static void SaveOutputTimer(obs_data_t *save_data, bool saving, void *)
{
if (saving) {
obs_data_t *obj = obs_data_create();
obs_data_set_int(obj, "streamTimerHours",
ot->ui->streamingTimerHours->value());
obs_data_set_int(obj, "streamTimerMinutes",
ot->ui->streamingTimerMinutes->value());
obs_data_set_int(obj, "streamTimerSeconds",
ot->ui->streamingTimerSeconds->value());
obs_data_set_int(obj, "recordTimerHours",
ot->ui->recordingTimerHours->value());
obs_data_set_int(obj, "recordTimerMinutes",
ot->ui->recordingTimerMinutes->value());
obs_data_set_int(obj, "recordTimerSeconds",
ot->ui->recordingTimerSeconds->value());
obs_data_set_bool(obj, "autoStartStreamTimer",
ot->ui->autoStartStreamTimer->isChecked());
obs_data_set_bool(obj, "autoStartRecordTimer",
ot->ui->autoStartRecordTimer->isChecked());
obs_data_set_obj(save_data, "output-timer", obj);
obs_data_release(obj);
} else {
obs_data_t *obj = obs_data_get_obj(save_data,
"output-timer");
if (!obj)
obj = obs_data_create();
ot->ui->streamingTimerHours->setValue(
obs_data_get_int(obj, "streamTimerHours"));
ot->ui->streamingTimerMinutes->setValue(
obs_data_get_int(obj, "streamTimerMinutes"));
ot->ui->streamingTimerSeconds->setValue(
obs_data_get_int(obj, "streamTimerSeconds"));
ot->ui->recordingTimerHours->setValue(
obs_data_get_int(obj, "recordTimerHours"));
ot->ui->recordingTimerMinutes->setValue(
obs_data_get_int(obj, "recordTimerMinutes"));
ot->ui->recordingTimerSeconds->setValue(
obs_data_get_int(obj, "recordTimerSeconds"));
ot->ui->autoStartStreamTimer->setChecked(
obs_data_get_bool(obj, "autoStartStreamTimer"));
ot->ui->autoStartRecordTimer->setChecked(
obs_data_get_bool(obj, "autoStartRecordTimer"));
obs_data_release(obj);
}
}
extern "C" void FreeOutputTimer()
{
}
static void OBSEvent(enum obs_frontend_event event, void *)
{
if (event == OBS_FRONTEND_EVENT_EXIT) {
obs_frontend_save();
FreeOutputTimer();
} else if (event == OBS_FRONTEND_EVENT_STREAMING_STARTED) {
ot->StreamTimerStart();
} else if (event == OBS_FRONTEND_EVENT_STREAMING_STOPPING) {
ot->StreamTimerStop();
} else if (event == OBS_FRONTEND_EVENT_RECORDING_STARTED) {
ot->RecordTimerStart();
} else if (event == OBS_FRONTEND_EVENT_RECORDING_STOPPING) {
ot->RecordTimerStop();
}
}
extern "C" void InitOutputTimer()
{
QAction *action = (QAction*)obs_frontend_add_tools_menu_qaction(
obs_module_text("OutputTimer"));
obs_frontend_push_ui_translation(obs_module_get_string);
QMainWindow *window = (QMainWindow*)obs_frontend_get_main_window();
ot = new OutputTimer(window);
auto cb = [] ()
{
ot->ShowHideDialog();
};
obs_frontend_pop_ui_translation();
obs_frontend_add_save_callback(SaveOutputTimer, nullptr);
obs_frontend_add_event_callback(OBSEvent, nullptr);
action->connect(action, &QAction::triggered, cb);
}

View file

@ -0,0 +1,40 @@
#pragma once
#include <QDialog>
#include <memory>
#include "ui_output-timer.h"
class QCloseEvent;
class OutputTimer : public QDialog {
Q_OBJECT
public:
std::unique_ptr<Ui_OutputTimer> ui;
OutputTimer(QWidget *parent);
void closeEvent(QCloseEvent *event) override;
public slots:
void StreamingTimerButton();
void RecordingTimerButton();
void StreamTimerStart();
void RecordTimerStart();
void StreamTimerStop();
void RecordTimerStop();
void UpdateStreamTimerDisplay();
void UpdateRecordTimerDisplay();
void ShowHideDialog();
void EventStopStreaming();
void EventStopRecording();
private:
bool streamingAlreadyActive = false;
bool recordingAlreadyActive = false;
QTimer *streamingTimer;
QTimer *recordingTimer;
QTimer *streamingTimerDisplay;
QTimer *recordingTimerDisplay;
};

View file

@ -0,0 +1,36 @@
#pragma once
#include <obs.hpp>
#include <string>
#include <QString>
static inline OBSWeakSource GetWeakSourceByName(const char *name)
{
OBSWeakSource weak;
obs_source_t *source = obs_get_source_by_name(name);
if (source) {
weak = obs_source_get_weak_source(source);
obs_weak_source_release(weak);
obs_source_release(source);
}
return weak;
}
static inline OBSWeakSource GetWeakSourceByQString(const QString &name)
{
return GetWeakSourceByName(name.toUtf8().constData());
}
static inline std::string GetWeakSourceName(obs_weak_source_t *weak_source)
{
std::string name;
obs_source_t *source = obs_weak_source_get_source(weak_source);
if (source) {
name = obs_source_get_name(source);
obs_source_release(source);
}
return name;
}