Copy disabled (too large)
Download .txt
Showing preview only (22,505K chars total). Download the full file to get everything.
Repository: aza547/wow-recorder
Branch: main
Commit: 6699af64174f
Files: 264
Total size: 247.4 MB
Directory structure:
gitextract_hwpkyjj1/
├── .erb/
│ ├── configs/
│ │ ├── .eslintrc
│ │ ├── webpack.config.base.ts
│ │ ├── webpack.config.eslint.ts
│ │ ├── webpack.config.main.dev.ts
│ │ ├── webpack.config.main.prod.ts
│ │ ├── webpack.config.preload.dev.ts
│ │ ├── webpack.config.renderer.dev.dll.ts
│ │ ├── webpack.config.renderer.dev.ts
│ │ ├── webpack.config.renderer.prod.ts
│ │ └── webpack.paths.ts
│ ├── mocks/
│ │ └── fileMock.js
│ └── scripts/
│ ├── .eslintrc
│ ├── check-build-exists.ts
│ ├── check-native-dep.js
│ ├── check-node-env.js
│ ├── check-port-in-use.js
│ ├── clean.js
│ ├── delete-source-maps.js
│ ├── electron-rebuild.js
│ ├── link-modules.ts
│ └── notarize.js
├── .eslintrc.js
├── .gitattributes
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ └── workflows/
│ └── node.js.yml
├── .gitignore
├── .vscode/
│ ├── extensions.json
│ ├── launch.json
│ ├── settings.json
│ └── tasks.json
├── CHANGELOG.md
├── LICENSE
├── README.md
├── assets/
│ ├── assets.d.ts
│ └── entitlements.mac.plist
├── docs/
│ ├── CONTRIBUTING.md
│ ├── Colors.md
│ ├── FilterAudio.md
│ ├── Localisation.md
│ ├── LocateLogDirectory.md
│ ├── SettingsReference.md
│ ├── design.excalidraw
│ └── example-obs-config/
│ ├── Output.json
│ └── Video.json
├── eslint.config.mjs
├── installer.nsh
├── package.json
├── postcss.config.js
├── release/
│ └── app/
│ └── package.json
├── src/
│ ├── __tests__/
│ │ ├── activitys/
│ │ │ ├── ArenaMatch.test.ts
│ │ │ ├── Battleground.test.ts
│ │ │ ├── ChallengeModeDungeon.test.ts
│ │ │ ├── RaidEncounter.test.ts
│ │ │ └── SoloShuffle.test.ts
│ │ ├── localisation/
│ │ │ └── Translate.test.ts
│ │ └── parser/
│ │ └── Basic.test.ts
│ ├── activitys/
│ │ ├── Activity.ts
│ │ ├── ArenaMatch.ts
│ │ ├── Battleground.ts
│ │ ├── ChallengeModeDungeon.ts
│ │ ├── Manual.ts
│ │ ├── RaidEncounter.ts
│ │ └── SoloShuffle.ts
│ ├── config/
│ │ ├── ConfigService.ts
│ │ └── configSchema.ts
│ ├── localisation/
│ │ ├── chineseSimplified.ts
│ │ ├── english.ts
│ │ ├── german.ts
│ │ ├── korean.ts
│ │ ├── phrases.ts
│ │ └── translations.ts
│ ├── main/
│ │ ├── AppUpdater.ts
│ │ ├── Combatant.ts
│ │ ├── Manager.ts
│ │ ├── Recorder.ts
│ │ ├── VideoProcessQueue.ts
│ │ ├── constants.ts
│ │ ├── keystone.ts
│ │ ├── main.ts
│ │ ├── menu.ts
│ │ ├── obsEnums.ts
│ │ ├── preload.ts
│ │ ├── types.ts
│ │ └── util.ts
│ ├── parsing/
│ │ ├── ClassicLogHandler.ts
│ │ ├── CombatLogWatcher.ts
│ │ ├── EraLogHandler.ts
│ │ ├── LogHandler.ts
│ │ ├── LogLine.ts
│ │ ├── RetailLogHandler.ts
│ │ └── logutils.ts
│ ├── renderer/
│ │ ├── App.css
│ │ ├── App.tsx
│ │ ├── AudioSourceControls.tsx
│ │ ├── BattlegroundInfo.tsx
│ │ ├── BulkTransferDialog.tsx
│ │ ├── CategoryPage.tsx
│ │ ├── ChatOverlayControls.tsx
│ │ ├── CloudSettings.tsx
│ │ ├── ConfirmChatNamePrompt.tsx
│ │ ├── CrashStatus.tsx
│ │ ├── DateRangePicker.tsx
│ │ ├── DeleteDialog.tsx
│ │ ├── DiscordButton.tsx
│ │ ├── FlavourSettings.tsx
│ │ ├── GeneralSettings.tsx
│ │ ├── KillVideoDialog.tsx
│ │ ├── KillVideoProgress.tsx
│ │ ├── KillVideoSourceTimeline.tsx
│ │ ├── Layout.tsx
│ │ ├── LocaleSettings.tsx
│ │ ├── LogButton.tsx
│ │ ├── ManualSettings.tsx
│ │ ├── MicrophoneStatus.tsx
│ │ ├── MultiPovPlaybackToggles.tsx
│ │ ├── PVESettings.tsx
│ │ ├── PVPSettings.tsx
│ │ ├── PatreonButton.tsx
│ │ ├── RaidComp.tsx
│ │ ├── RecorderPreview.tsx
│ │ ├── RendererTitleBar.tsx
│ │ ├── SavingStatus.tsx
│ │ ├── SceneEditor.tsx
│ │ ├── SearchBar.tsx
│ │ ├── SettingsPage.tsx
│ │ ├── SideMenu.tsx
│ │ ├── SnackBar.tsx
│ │ ├── StorageFilterToggle.tsx
│ │ ├── TagDialog.tsx
│ │ ├── TestButton.tsx
│ │ ├── VideoBaseControls.tsx
│ │ ├── VideoChat.tsx
│ │ ├── VideoCorrelator.ts
│ │ ├── VideoFilter.ts
│ │ ├── VideoMarkerToggles.tsx
│ │ ├── VideoPlayer.tsx
│ │ ├── VideoSourceControls.tsx
│ │ ├── VideoTag.ts
│ │ ├── WindowsSettings.tsx
│ │ ├── components/
│ │ │ ├── Badge/
│ │ │ │ └── Badge.tsx
│ │ │ ├── Button/
│ │ │ │ └── Button.tsx
│ │ │ ├── Command/
│ │ │ │ └── Command.tsx
│ │ │ ├── Dialog/
│ │ │ │ └── Dialog.tsx
│ │ │ ├── DrawingOverlay/
│ │ │ │ ├── DrawingOverlay.css
│ │ │ │ └── DrawingOverlay.tsx
│ │ │ ├── HoverCard/
│ │ │ │ └── HoverCard.tsx
│ │ │ ├── Input/
│ │ │ │ └── Input.tsx
│ │ │ ├── Label/
│ │ │ │ └── Label.tsx
│ │ │ ├── Menu/
│ │ │ │ ├── Item.tsx
│ │ │ │ ├── Menu.tsx
│ │ │ │ └── index.tsx
│ │ │ ├── MultiSelect/
│ │ │ │ └── MultiSelect.tsx
│ │ │ ├── Popover/
│ │ │ │ └── Popover.tsx
│ │ │ ├── Progress/
│ │ │ │ └── Progress.tsx
│ │ │ ├── ScrollArea/
│ │ │ │ └── ScrollArea.tsx
│ │ │ ├── Select/
│ │ │ │ └── Select.tsx
│ │ │ ├── Separator/
│ │ │ │ └── Separator.tsx
│ │ │ ├── Slider/
│ │ │ │ └── Slider.tsx
│ │ │ ├── StatusLight/
│ │ │ │ └── StatusLight.tsx
│ │ │ ├── Switch/
│ │ │ │ └── Switch.tsx
│ │ │ ├── Tables/
│ │ │ │ ├── Cells.tsx
│ │ │ │ ├── Headers.tsx
│ │ │ │ ├── Sorting.ts
│ │ │ │ ├── TableData.tsx
│ │ │ │ └── VideoSelectionTable.tsx
│ │ │ ├── Tabs/
│ │ │ │ └── Tabs.tsx
│ │ │ ├── TextArea/
│ │ │ │ └── textarea.tsx
│ │ │ ├── TextBanner/
│ │ │ │ └── TextBanner.tsx
│ │ │ ├── Toast/
│ │ │ │ ├── Toast.tsx
│ │ │ │ ├── Toaster.tsx
│ │ │ │ └── useToast.ts
│ │ │ ├── Toggle/
│ │ │ │ └── Toggle.tsx
│ │ │ ├── ToggleGroup/
│ │ │ │ └── ToggleGroup.tsx
│ │ │ ├── Tooltip/
│ │ │ │ └── Tooltip.tsx
│ │ │ ├── Viewpoints/
│ │ │ │ └── ViewpointSelection.tsx
│ │ │ └── utils/
│ │ │ └── index.ts
│ │ ├── containers/
│ │ │ ├── ApplicationStatusCard/
│ │ │ │ ├── ApplicationStatusCard.tsx
│ │ │ │ ├── CloudStatus.tsx
│ │ │ │ ├── CloudStatusCard.tsx
│ │ │ │ ├── ErrorReporter.tsx
│ │ │ │ ├── MicStatus.tsx
│ │ │ │ └── Status.tsx
│ │ │ └── UpdateNotifier/
│ │ │ └── UpdateNotifier.tsx
│ │ ├── images.ts
│ │ ├── index.ejs
│ │ ├── index.tsx
│ │ ├── preload.d.ts
│ │ ├── rendererutils.ts
│ │ ├── sounds.ts
│ │ └── useSettings.ts
│ ├── storage/
│ │ ├── CloudClient.ts
│ │ ├── DiskClient.ts
│ │ ├── DiskSizeMonitor.ts
│ │ └── StorageClient.ts
│ ├── types/
│ │ ├── KeyTypesUIOHook.ts
│ │ ├── VideoCategory.ts
│ │ └── api.ts
│ └── utils/
│ ├── AsyncQueue.ts
│ ├── AuthError.ts
│ ├── Poller.ts
│ ├── RetryableConfigError.ts
│ ├── TestConfigService.ts
│ ├── configUtils.ts
│ ├── testButtonData.ts
│ └── testButtonUtils.ts
├── tailwind.config.js
├── tests/
│ ├── README.md
│ ├── logs/
│ │ ├── classic/
│ │ │ ├── battleground.txt
│ │ │ ├── mop_challenge_mode.txt
│ │ │ ├── raid.txt
│ │ │ ├── rated_2v2.txt
│ │ │ ├── rated_2v2_double.txt
│ │ │ ├── rated_2v2_extra_units.txt
│ │ │ ├── rated_2v2_feign_death.txt
│ │ │ ├── rated_3v3.txt
│ │ │ ├── rated_3v3_force_stop.txt
│ │ │ └── rated_5v5.txt
│ │ ├── era/
│ │ │ └── raid.txt
│ │ └── retail/
│ │ ├── mythic_plus.txt
│ │ ├── mythic_plus_ditch_into_raid.txt
│ │ ├── mythic_plus_drop_go.txt
│ │ ├── mythic_plus_no_boss.txt
│ │ ├── mythic_plus_repair.txt
│ │ ├── raid_reset.txt
│ │ ├── raid_unknown_encounter.txt
│ │ ├── raid_wipe.txt
│ │ ├── rated_2v2.txt
│ │ ├── rated_2v2_afk_out.txt
│ │ ├── rated_3v3.txt
│ │ ├── rated_battleground.txt
│ │ ├── rated_solo_shuffle.txt
│ │ ├── skirmish.txt
│ │ ├── wargame_3v3.txt
│ │ └── zone_changes.txt
│ └── src/
│ ├── classic/
│ │ ├── battleground.py
│ │ ├── mop_challenge_mode.py
│ │ ├── raid.py
│ │ ├── rated_2v2.py
│ │ ├── rated_2v2_double.py
│ │ ├── rated_2v2_extra_units.py
│ │ ├── rated_2v2_feign_death.py
│ │ ├── rated_3v3.py
│ │ ├── rated_3v3_force_stop.py
│ │ └── rated_5v5.py
│ ├── era/
│ │ └── raid.py
│ ├── retail/
│ │ ├── mythic_plus.py
│ │ ├── mythic_plus_ditch_into_raid.py
│ │ ├── mythic_plus_drop_go.py
│ │ ├── mythic_plus_no_boss.py
│ │ ├── mythic_plus_repair.py
│ │ ├── raid_reset.py
│ │ ├── raid_unknown_encounter.py
│ │ ├── raid_wipe.py
│ │ ├── rated_2v2.py
│ │ ├── rated_2v2_afk_out.py
│ │ ├── rated_3v3.py
│ │ ├── rated_battleground.py
│ │ ├── rated_solo_shuffle.py
│ │ ├── skirmish.py
│ │ ├── wargame_3v3.py
│ │ └── zone_changes.py
│ └── test.py
└── tsconfig.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .erb/configs/.eslintrc
================================================
{
"rules": {
"no-console": "off",
"global-require": "off",
"import/no-dynamic-require": "off",
"no-underscore-dangle": "off"
}
}
================================================
FILE: .erb/configs/webpack.config.base.ts
================================================
/**
* Base webpack config used across other specific configs
*/
import webpack from 'webpack';
import TsconfigPathsPlugins from 'tsconfig-paths-webpack-plugin';
import webpackPaths from './webpack.paths';
import { dependencies as externals } from '../../release/app/package.json';
const configuration: webpack.Configuration = {
externals: [...Object.keys(externals || {})],
stats: 'errors-only',
module: {
rules: [
{
test: /\.[jt]sx?$/,
exclude: /node_modules/,
use: {
loader: 'ts-loader',
options: {
// Remove this line to enable type checking in webpack builds
transpileOnly: true,
compilerOptions: {
module: 'nodenext',
moduleResolution: 'nodenext',
},
},
},
},
],
},
output: {
path: webpackPaths.srcPath,
// https://github.com/webpack/webpack/issues/1114
library: { type: 'commonjs2' },
},
/**
* Determine the array of extensions that should be used to resolve modules.
*/
resolve: {
extensions: ['.js', '.jsx', '.json', '.ts', '.tsx'],
modules: [webpackPaths.srcPath, 'node_modules'],
// There is no need to add aliases here, the paths in tsconfig get mirrored
plugins: [new TsconfigPathsPlugins()],
fallback: {
'roughjs/bin/rough': require.resolve('roughjs/bin/rough'),
'roughjs/bin/generator': require.resolve('roughjs/bin/generator'),
'roughjs/bin/math': require.resolve('roughjs/bin/math')
}
},
plugins: [
new webpack.EnvironmentPlugin({
NODE_ENV: 'production',
}),
new webpack.DefinePlugin({
'process.env.FLUENTFFMPEG_COV': false,
}),
],
};
export default configuration;
================================================
FILE: .erb/configs/webpack.config.eslint.ts
================================================
/* eslint import/no-unresolved: off, import/no-self-import: off */
module.exports = require('./webpack.config.renderer.dev').default;
================================================
FILE: .erb/configs/webpack.config.main.dev.ts
================================================
/**
* Webpack config for development electron main process
*/
import path from 'path';
import webpack from 'webpack';
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
import { merge } from 'webpack-merge';
import checkNodeEnv from '../scripts/check-node-env';
import baseConfig from './webpack.config.base';
import webpackPaths from './webpack.paths';
// When an ESLint server is running, we can't set the NODE_ENV so we'll check if it's
// at the dev webpack config is not accidentally run in a production environment
if (process.env.NODE_ENV === 'production') {
checkNodeEnv('development');
}
const configuration: webpack.Configuration = {
devtool: 'inline-source-map',
mode: 'development',
target: 'electron-main',
entry: {
main: path.join(webpackPaths.srcMainPath, 'main.ts'),
preload: path.join(webpackPaths.srcMainPath, 'preload.ts'),
},
output: {
path: webpackPaths.dllPath,
filename: '[name].bundle.dev.js',
library: {
type: 'umd',
},
},
plugins: [
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
new BundleAnalyzerPlugin({
analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled',
analyzerPort: 8888,
}),
new webpack.DefinePlugin({
'process.type': '"browser"',
}),
],
/**
* Disables webpack processing of __dirname and __filename.
* If you run the bundle in node.js it falls back to these values of node.js.
* https://github.com/webpack/webpack/issues/2010
*/
node: {
__dirname: false,
__filename: false,
},
};
export default merge(baseConfig, configuration);
================================================
FILE: .erb/configs/webpack.config.main.prod.ts
================================================
/**
* Webpack config for production electron main process
*/
import path from 'path';
import webpack from 'webpack';
import { merge } from 'webpack-merge';
import TerserPlugin from 'terser-webpack-plugin';
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
import baseConfig from './webpack.config.base';
import webpackPaths from './webpack.paths';
import checkNodeEnv from '../scripts/check-node-env';
import deleteSourceMaps from '../scripts/delete-source-maps';
checkNodeEnv('production');
deleteSourceMaps();
const configuration: webpack.Configuration = {
devtool: 'source-map',
mode: 'production',
target: 'electron-main',
entry: {
main: path.join(webpackPaths.srcMainPath, 'main.ts'),
preload: path.join(webpackPaths.srcMainPath, 'preload.ts'),
},
output: {
path: webpackPaths.distMainPath,
filename: '[name].js',
library: {
type: 'umd',
},
},
optimization: {
minimizer: [
new TerserPlugin({
parallel: true,
}),
],
},
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled',
analyzerPort: 8888,
}),
/**
* Create global constants which can be configured at compile time.
*
* Useful for allowing different behaviour between development builds and
* release builds
*
* NODE_ENV should be production so that modules do not perform certain
* development checks
*/
new webpack.EnvironmentPlugin({
NODE_ENV: 'production',
DEBUG_PROD: false,
START_MINIMIZED: false,
}),
new webpack.DefinePlugin({
'process.type': '"browser"',
}),
],
/**
* Disables webpack processing of __dirname and __filename.
* If you run the bundle in node.js it falls back to these values of node.js.
* https://github.com/webpack/webpack/issues/2010
*/
node: {
__dirname: false,
__filename: false,
},
};
export default merge(baseConfig, configuration);
================================================
FILE: .erb/configs/webpack.config.preload.dev.ts
================================================
import path from 'path';
import webpack from 'webpack';
import { merge } from 'webpack-merge';
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
import baseConfig from './webpack.config.base';
import webpackPaths from './webpack.paths';
import checkNodeEnv from '../scripts/check-node-env';
// When an ESLint server is running, we can't set the NODE_ENV so we'll check if it's
// at the dev webpack config is not accidentally run in a production environment
if (process.env.NODE_ENV === 'production') {
checkNodeEnv('development');
}
const configuration: webpack.Configuration = {
devtool: 'inline-source-map',
mode: 'development',
target: 'electron-preload',
entry: path.join(webpackPaths.srcMainPath, 'preload.ts'),
output: {
path: webpackPaths.dllPath,
filename: 'preload.js',
library: {
type: 'umd',
},
},
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled',
}),
/**
* Create global constants which can be configured at compile time.
*
* Useful for allowing different behaviour between development builds and
* release builds
*
* NODE_ENV should be production so that modules do not perform certain
* development checks
*
* By default, use 'development' as NODE_ENV. This can be overriden with
* 'staging', for example, by changing the ENV variables in the npm scripts
*/
new webpack.EnvironmentPlugin({
NODE_ENV: 'development',
}),
new webpack.LoaderOptionsPlugin({
debug: true,
}),
],
/**
* Disables webpack processing of __dirname and __filename.
* If you run the bundle in node.js it falls back to these values of node.js.
* https://github.com/webpack/webpack/issues/2010
*/
node: {
__dirname: false,
__filename: false,
},
watch: true,
};
export default merge(baseConfig, configuration);
================================================
FILE: .erb/configs/webpack.config.renderer.dev.dll.ts
================================================
/**
* Builds the DLL for development electron renderer process
*/
import webpack from 'webpack';
import path from 'path';
import { merge } from 'webpack-merge';
import baseConfig from './webpack.config.base';
import webpackPaths from './webpack.paths';
import { dependencies } from '../../package.json';
import checkNodeEnv from '../scripts/check-node-env';
checkNodeEnv('development');
const dist = webpackPaths.dllPath;
const configuration: webpack.Configuration = {
context: webpackPaths.rootPath,
devtool: 'eval',
mode: 'development',
target: 'electron-renderer',
externals: ['fsevents', 'crypto-browserify'],
/**
* Use `module` from `webpack.config.renderer.dev.js`
*/
module: require('./webpack.config.renderer.dev').default.module,
entry: {
renderer: Object.keys(dependencies || {}),
},
output: {
path: dist,
filename: '[name].dev.dll.js',
library: {
name: 'renderer',
type: 'var',
},
},
plugins: [
new webpack.DllPlugin({
path: path.join(dist, '[name].json'),
name: '[name]',
}),
/**
* Create global constants which can be configured at compile time.
*
* Useful for allowing different behaviour between development builds and
* release builds
*
* NODE_ENV should be production so that modules do not perform certain
* development checks
*/
new webpack.EnvironmentPlugin({
NODE_ENV: 'development',
}),
new webpack.LoaderOptionsPlugin({
debug: true,
options: {
context: webpackPaths.srcPath,
output: {
path: webpackPaths.dllPath,
},
},
}),
],
};
export default merge(baseConfig, configuration);
================================================
FILE: .erb/configs/webpack.config.renderer.dev.ts
================================================
import 'webpack-dev-server';
import path from 'path';
import fs from 'fs';
import webpack from 'webpack';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import chalk from 'chalk';
import { merge } from 'webpack-merge';
import { execSync, spawn } from 'child_process';
import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin';
import baseConfig from './webpack.config.base';
import webpackPaths from './webpack.paths';
import checkNodeEnv from '../scripts/check-node-env';
// When an ESLint server is running, we can't set the NODE_ENV so we'll check if it's
// at the dev webpack config is not accidentally run in a production environment
if (process.env.NODE_ENV === 'production') {
checkNodeEnv('development');
}
const port = process.env.PORT || 1212;
const manifest = path.resolve(webpackPaths.dllPath, 'renderer.json');
const skipDLLs =
module.parent?.filename.includes('webpack.config.renderer.dev.dll') ||
module.parent?.filename.includes('webpack.config.eslint');
/**
* Warn if the DLL is not built
*/
if (
!skipDLLs &&
!(fs.existsSync(webpackPaths.dllPath) && fs.existsSync(manifest))
) {
console.log(
chalk.black.bgYellow.bold(
'The DLL files are missing. Sit back while we build them for you with "npm run build-dll"',
),
);
execSync('npm run postinstall');
}
const configuration: webpack.Configuration = {
devtool: 'inline-source-map',
mode: 'development',
target: ['web', 'electron-renderer'],
entry: [
`webpack-dev-server/client?http://localhost:${port}/dist`,
'webpack/hot/only-dev-server',
path.join(webpackPaths.srcRendererPath, 'index.tsx'),
],
output: {
path: webpackPaths.distRendererPath,
publicPath: '/',
filename: 'renderer.dev.js',
library: {
type: 'umd',
},
},
module: {
rules: [
{
test: /\.s?(c|a)ss$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: true,
sourceMap: true,
importLoaders: 1,
},
},
'sass-loader',
'postcss-loader',
],
include: /\.module\.s?(c|a)ss$/,
},
{
test: /\.s?css$/,
use: ['style-loader', 'css-loader', 'sass-loader', 'postcss-loader'],
exclude: /\.module\.s?(c|a)ss$/,
},
// Fonts
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource',
},
// Images
{
test: /\.(png|jpg|jpeg|gif)$/i,
type: 'asset/resource',
},
// Audio
{
test: /\.(mp3)$/i,
type: 'asset/resource',
},
// SVG
{
test: /\.svg$/,
use: [
{
loader: '@svgr/webpack',
options: {
prettier: false,
svgo: false,
svgoConfig: {
plugins: [{ removeViewBox: false }],
},
titleProp: true,
ref: true,
},
},
'file-loader',
],
},
],
},
plugins: [
...(skipDLLs
? []
: [
new webpack.DllReferencePlugin({
context: webpackPaths.dllPath,
manifest: require(manifest),
sourceType: 'var',
}),
]),
new webpack.NoEmitOnErrorsPlugin(),
/**
* Create global constants which can be configured at compile time.
*
* Useful for allowing different behaviour between development builds and
* release builds
*
* NODE_ENV should be production so that modules do not perform certain
* development checks
*
* By default, use 'development' as NODE_ENV. This can be overriden with
* 'staging', for example, by changing the ENV variables in the npm scripts
*/
new webpack.EnvironmentPlugin({
NODE_ENV: 'development',
}),
new webpack.LoaderOptionsPlugin({
debug: true,
}),
new ReactRefreshWebpackPlugin(),
new HtmlWebpackPlugin({
filename: path.join('index.html'),
template: path.join(webpackPaths.srcRendererPath, 'index.ejs'),
minify: {
collapseWhitespace: true,
removeAttributeQuotes: true,
removeComments: true,
},
isBrowser: false,
env: process.env.NODE_ENV,
isDevelopment: process.env.NODE_ENV !== 'production',
nodeModules: webpackPaths.appNodeModulesPath,
}),
],
node: {
__dirname: false,
__filename: false,
},
devServer: {
port,
compress: true,
hot: true,
headers: { 'Access-Control-Allow-Origin': '*' },
static: {
publicPath: '/',
},
historyApiFallback: {
verbose: true,
},
setupMiddlewares(middlewares) {
console.log('Starting preload.js builder...');
const preloadProcess = spawn('npm', ['run', 'start:preload'], {
shell: true,
stdio: 'inherit',
})
.on('close', (code: number) => process.exit(code!))
.on('error', (spawnError) => console.error(spawnError));
console.log('Starting Main Process...');
let args = ['run', 'start:main'];
if (process.env.MAIN_ARGS) {
args = args.concat(
['--', ...process.env.MAIN_ARGS.matchAll(/"[^"]+"|[^\s"]+/g)].flat(),
);
}
spawn('npm', args, {
shell: true,
stdio: 'inherit',
})
.on('close', (code: number) => {
preloadProcess.kill();
process.exit(code!);
})
.on('error', (spawnError) => console.error(spawnError));
return middlewares;
},
},
};
export default merge(baseConfig, configuration);
================================================
FILE: .erb/configs/webpack.config.renderer.prod.ts
================================================
/**
* Build config for electron renderer process
*/
import path from 'path';
import webpack from 'webpack';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
import CssMinimizerPlugin from 'css-minimizer-webpack-plugin';
import { merge } from 'webpack-merge';
import TerserPlugin from 'terser-webpack-plugin';
import baseConfig from './webpack.config.base';
import webpackPaths from './webpack.paths';
import checkNodeEnv from '../scripts/check-node-env';
import deleteSourceMaps from '../scripts/delete-source-maps';
checkNodeEnv('production');
deleteSourceMaps();
const configuration: webpack.Configuration = {
devtool: 'source-map',
mode: 'production',
target: ['web', 'electron-renderer'],
entry: [path.join(webpackPaths.srcRendererPath, 'index.tsx')],
output: {
path: webpackPaths.distRendererPath,
publicPath: './',
filename: 'renderer.js',
library: {
type: 'umd',
},
},
module: {
rules: [
{
test: /\.s?(a|c)ss$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
modules: true,
sourceMap: true,
importLoaders: 1,
},
},
'sass-loader',
'postcss-loader',
],
include: /\.module\.s?(c|a)ss$/,
},
{
test: /\.s?(a|c)ss$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'sass-loader',
'postcss-loader',
],
exclude: /\.module\.s?(c|a)ss$/,
},
// Fonts
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource',
},
// Images
{
test: /\.(png|jpg|jpeg|gif)$/i,
type: 'asset/resource',
},
// Audio
{
test: /\.(mp3)$/i,
type: 'asset/resource',
},
// SVG
{
test: /\.svg$/,
use: [
{
loader: '@svgr/webpack',
options: {
prettier: false,
svgo: false,
svgoConfig: {
plugins: [{ removeViewBox: false }],
},
titleProp: true,
ref: true,
},
},
'file-loader',
],
},
],
},
optimization: {
minimize: true,
minimizer: [new TerserPlugin(), new CssMinimizerPlugin()],
},
plugins: [
/**
* Create global constants which can be configured at compile time.
*
* Useful for allowing different behaviour between development builds and
* release builds
*
* NODE_ENV should be production so that modules do not perform certain
* development checks
*/
new webpack.EnvironmentPlugin({
NODE_ENV: 'production',
DEBUG_PROD: false,
}),
new MiniCssExtractPlugin({
filename: 'style.css',
}),
new BundleAnalyzerPlugin({
analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled',
analyzerPort: 8889,
}),
new HtmlWebpackPlugin({
filename: 'index.html',
template: path.join(webpackPaths.srcRendererPath, 'index.ejs'),
minify: {
collapseWhitespace: true,
removeAttributeQuotes: true,
removeComments: true,
},
isBrowser: false,
isDevelopment: false,
}),
new webpack.DefinePlugin({
'process.type': '"renderer"',
}),
],
};
export default merge(baseConfig, configuration);
================================================
FILE: .erb/configs/webpack.paths.ts
================================================
const path = require('path');
const rootPath = path.join(__dirname, '../..');
const erbPath = path.join(__dirname, '..');
const erbNodeModulesPath = path.join(erbPath, 'node_modules');
const dllPath = path.join(__dirname, '../dll');
const srcPath = path.join(rootPath, 'src');
const srcMainPath = path.join(srcPath, 'main');
const srcRendererPath = path.join(srcPath, 'renderer');
const releasePath = path.join(rootPath, 'release');
const appPath = path.join(releasePath, 'app');
const appPackagePath = path.join(appPath, 'package.json');
const appNodeModulesPath = path.join(appPath, 'node_modules');
const srcNodeModulesPath = path.join(srcPath, 'node_modules');
const distPath = path.join(appPath, 'dist');
const distMainPath = path.join(distPath, 'main');
const distRendererPath = path.join(distPath, 'renderer');
const buildPath = path.join(releasePath, 'build');
export default {
rootPath,
erbNodeModulesPath,
dllPath,
srcPath,
srcMainPath,
srcRendererPath,
releasePath,
appPath,
appPackagePath,
appNodeModulesPath,
srcNodeModulesPath,
distPath,
distMainPath,
distRendererPath,
buildPath,
};
================================================
FILE: .erb/mocks/fileMock.js
================================================
export default 'test-file-stub';
================================================
FILE: .erb/scripts/.eslintrc
================================================
{
"rules": {
"no-console": "off",
"global-require": "off",
"import/no-dynamic-require": "off",
"import/no-extraneous-dependencies": "off"
}
}
================================================
FILE: .erb/scripts/check-build-exists.ts
================================================
// Check if the renderer and main bundles are built
import path from 'path';
import chalk from 'chalk';
import fs from 'fs';
import webpackPaths from '../configs/webpack.paths';
const mainPath = path.join(webpackPaths.distMainPath, 'main.js');
const rendererPath = path.join(webpackPaths.distRendererPath, 'renderer.js');
if (!fs.existsSync(mainPath)) {
throw new Error(
chalk.whiteBright.bgRed.bold(
'The main process is not built yet. Build it by running "npm run build:main"',
),
);
}
if (!fs.existsSync(rendererPath)) {
throw new Error(
chalk.whiteBright.bgRed.bold(
'The renderer process is not built yet. Build it by running "npm run build:renderer"',
),
);
}
================================================
FILE: .erb/scripts/check-native-dep.js
================================================
import fs from 'fs';
import chalk from 'chalk';
import { execSync } from 'child_process';
import { dependencies } from '../../package.json';
if (dependencies) {
const dependenciesKeys = Object.keys(dependencies);
const nativeDeps = fs
.readdirSync('node_modules')
.filter((folder) => fs.existsSync(`node_modules/${folder}/binding.gyp`));
if (nativeDeps.length === 0) {
process.exit(0);
}
try {
// Find the reason for why the dependency is installed. If it is installed
// because of a devDependency then that is okay. Warn when it is installed
// because of a dependency
const { dependencies: dependenciesObject } = JSON.parse(
execSync(`npm ls ${nativeDeps.join(' ')} --json`).toString(),
);
const rootDependencies = Object.keys(dependenciesObject);
const filteredRootDependencies = rootDependencies.filter((rootDependency) =>
dependenciesKeys.includes(rootDependency),
);
if (filteredRootDependencies.length > 0) {
const plural = filteredRootDependencies.length > 1;
console.log(`
${chalk.whiteBright.bgYellow.bold(
'Webpack does not work with native dependencies.',
)}
${chalk.bold(filteredRootDependencies.join(', '))} ${
plural ? 'are native dependencies' : 'is a native dependency'
} and should be installed inside of the "./release/app" folder.
First, uninstall the packages from "./package.json":
${chalk.whiteBright.bgGreen.bold('npm uninstall your-package')}
${chalk.bold(
'Then, instead of installing the package to the root "./package.json":',
)}
${chalk.whiteBright.bgRed.bold('npm install your-package')}
${chalk.bold('Install the package to "./release/app/package.json"')}
${chalk.whiteBright.bgGreen.bold(
'cd ./release/app && npm install your-package',
)}
Read more about native dependencies at:
${chalk.bold(
'https://electron-react-boilerplate.js.org/docs/adding-dependencies/#module-structure',
)}
`);
process.exit(1);
}
} catch (e) {
console.log('Native dependencies could not be checked');
}
}
================================================
FILE: .erb/scripts/check-node-env.js
================================================
import chalk from 'chalk';
export default function checkNodeEnv(expectedEnv) {
if (!expectedEnv) {
throw new Error('"expectedEnv" not set');
}
if (process.env.NODE_ENV !== expectedEnv) {
console.log(
chalk.whiteBright.bgRed.bold(
`"process.env.NODE_ENV" must be "${expectedEnv}" to use this webpack config`,
),
);
process.exit(2);
}
}
================================================
FILE: .erb/scripts/check-port-in-use.js
================================================
import chalk from 'chalk';
import detectPort from 'detect-port';
const port = process.env.PORT || '1212';
detectPort(port, (_err, availablePort) => {
if (port !== String(availablePort)) {
throw new Error(
chalk.whiteBright.bgRed.bold(
`Port "${port}" on "localhost" is already in use. Please use another port. ex: PORT=4343 npm start`,
),
);
} else {
process.exit(0);
}
});
================================================
FILE: .erb/scripts/clean.js
================================================
import { rimrafSync } from 'rimraf';
import fs from 'fs';
import webpackPaths from '../configs/webpack.paths';
const foldersToRemove = [
webpackPaths.distPath,
webpackPaths.buildPath,
webpackPaths.dllPath,
];
foldersToRemove.forEach((folder) => {
if (fs.existsSync(folder)) rimrafSync(folder);
});
================================================
FILE: .erb/scripts/delete-source-maps.js
================================================
import fs from 'fs';
import path from 'path';
import { rimrafSync } from 'rimraf';
import webpackPaths from '../configs/webpack.paths';
export default function deleteSourceMaps() {
if (fs.existsSync(webpackPaths.distMainPath))
rimrafSync(path.join(webpackPaths.distMainPath, '*.js.map'), {
glob: true,
});
if (fs.existsSync(webpackPaths.distRendererPath))
rimrafSync(path.join(webpackPaths.distRendererPath, '*.js.map'), {
glob: true,
});
}
================================================
FILE: .erb/scripts/electron-rebuild.js
================================================
import { execSync } from 'child_process';
import fs from 'fs';
import { dependencies } from '../../release/app/package.json';
import webpackPaths from '../configs/webpack.paths';
if (
Object.keys(dependencies || {}).length > 0 &&
fs.existsSync(webpackPaths.appNodeModulesPath)
) {
const electronRebuildCmd =
'../../node_modules/.bin/electron-rebuild --force --types prod,dev,optional --module-dir . -v 38.1.2';
const cmd =
process.platform === 'win32'
? electronRebuildCmd.replace(/\//g, '\\')
: electronRebuildCmd;
execSync(cmd, {
cwd: webpackPaths.appPath,
stdio: 'inherit',
});
}
================================================
FILE: .erb/scripts/link-modules.ts
================================================
import fs from 'fs';
import webpackPaths from '../configs/webpack.paths';
const { srcNodeModulesPath, appNodeModulesPath, erbNodeModulesPath } =
webpackPaths;
if (fs.existsSync(appNodeModulesPath)) {
if (!fs.existsSync(srcNodeModulesPath)) {
fs.symlinkSync(appNodeModulesPath, srcNodeModulesPath, 'junction');
}
if (!fs.existsSync(erbNodeModulesPath)) {
fs.symlinkSync(appNodeModulesPath, erbNodeModulesPath, 'junction');
}
}
================================================
FILE: .erb/scripts/notarize.js
================================================
const { notarize } = require('@electron/notarize');
const { build } = require('../../package.json');
exports.default = async function notarizeMacos(context) {
const { electronPlatformName, appOutDir } = context;
if (electronPlatformName !== 'darwin') {
return;
}
if (process.env.CI !== 'true') {
console.warn('Skipping notarizing step. Packaging is not running in CI');
return;
}
if (
!(
'APPLE_ID' in process.env &&
'APPLE_ID_PASS' in process.env &&
'APPLE_TEAM_ID' in process.env
)
) {
console.warn(
'Skipping notarizing step. APPLE_ID, APPLE_ID_PASS, and APPLE_TEAM_ID env variables must be set',
);
return;
}
const appName = context.packager.appInfo.productFilename;
await notarize({
tool: 'notarytool',
appBundleId: build.appId,
appPath: `${appOutDir}/${appName}.app`,
appleId: process.env.APPLE_ID,
appleIdPassword: process.env.APPLE_ID_PASS,
teamId: process.env.APPLE_TEAM_ID,
});
};
================================================
FILE: .eslintrc.js
================================================
module.exports = {
extends: 'erb',
rules: {
// A temporary hack related to IDE not resolving correct package.json
'import/no-extraneous-dependencies': 'off',
'import/no-unresolved': 'error',
// Since React 17 and typescript 4.1 you can safely disable the rule
'react/react-in-jsx-scope': 'off',
'no-underscore-dangle': 'off',
'no-plusplus': 'off',
'no-console': 'off',
'prettier/prettier': [
'error',
{
endOfLine: 'auto', // stops prettier complaining about windows line-endings
},
],
'react/jsx-props-no-spreading': 'off',
},
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module',
project: './tsconfig.json',
tsconfigRootDir: __dirname,
createDefaultProgram: true,
},
settings: {
'import/resolver': {
// See https://github.com/benmosher/eslint-plugin-import/issues/1396#issuecomment-575727774 for line below
node: {},
webpack: {
config: require.resolve('./.erb/configs/webpack.config.eslint.ts'),
},
typescript: {},
},
'import/parsers': {
'@typescript-eslint/parser': ['.ts', '.tsx'],
},
},
};
================================================
FILE: .gitattributes
================================================
* text eol=lf
*.exe binary
*.png binary
*.jpg binary
*.jpeg binary
*.ico binary
*.icns binary
*.eot binary
*.otf binary
*.ttf binary
*.woff binary
*.woff2 binary
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Additional context**
Add any other context about the problem here.
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
================================================
FILE: .github/workflows/node.js.yml
================================================
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
name: Warcraft Recorder CI
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node JS
uses: actions/setup-node@v4
with:
node-version: '22.12.0'
cache: 'npm'
- name: Install deps
run: npm install
# https://github.com/aza547/wow-recorder/issues/737
# - name: Run unit tests
# run: npm test
================================================
FILE: .gitignore
================================================
# Logs
*.log
# Runtime data
pids
*.pid
*.seed
# Coverage directory used by tools like istanbul
coverage
.eslintcache
# Dependency directory
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
node_modules
# OSX
.DS_Store
release/app/dist
release/build
.erb/dll
.idea
npm-debug.log.*
*.css.d.ts
*.sass.d.ts
*.scss.d.ts
# OBS Stuff
src/main/osn-data/*
# Python crap
*cpython*
*__pycache__*
# Cloud storage dirs
upload
buffer
# Some stuff OBS window capture creates at runtime?
win-capture/
================================================
FILE: .vscode/extensions.json
================================================
{
"recommendations": ["dbaeumer.vscode-eslint", "EditorConfig.EditorConfig"]
}
================================================
FILE: .vscode/launch.json
================================================
{
"version": "0.2.0",
"configurations": [
{
"name": "Electron: Main",
"type": "node",
"request": "launch",
"protocol": "inspector",
"runtimeExecutable": "npm",
"runtimeArgs": [
"run start:main --inspect=5858 --remote-debugging-port=9223"
],
"preLaunchTask": "Start Webpack Dev"
},
{
"name": "Electron: Renderer",
"type": "chrome",
"request": "attach",
"port": 9223,
"webRoot": "${workspaceFolder}",
"timeout": 15000
}
],
"compounds": [
{
"name": "Electron: All",
"configurations": ["Electron: Main", "Electron: Renderer"]
}
]
}
================================================
FILE: .vscode/settings.json
================================================
{
"files.associations": {
".eslintrc": "jsonc",
".prettierrc": "jsonc",
".eslintignore": "ignore"
},
"eslint.validate": [
"javascript",
"javascriptreact",
"html",
"typescriptreact",
"typescript"
],
"javascript.validate.enable": false,
"javascript.format.enable": false,
"typescript.format.enable": false,
"search.exclude": {
".git": true,
".eslintcache": true,
".erb/dll": true,
"release/{build,app/dist}": true,
"node_modules": true,
"npm-debug.log.*": true,
"test/**/__snapshots__": true,
"package-lock.json": true,
"*.{css,sass,scss}.d.ts": true
}
}
================================================
FILE: .vscode/tasks.json
================================================
{
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"label": "Start Webpack Dev",
"script": "start:renderer",
"options": {
"cwd": "${workspaceFolder}"
},
"isBackground": true,
"problemMatcher": {
"owner": "custom",
"pattern": {
"regexp": "____________"
},
"background": {
"activeOnStart": true,
"beginsPattern": "Compiling\\.\\.\\.$",
"endsPattern": "(Compiled successfully|Failed to compile)\\.$"
}
}
}
]
}
================================================
FILE: CHANGELOG.md
================================================
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
### Changed
### Added
### Fixed
## [7.6.3] - 2026-04-13
### Fixed
- Fix the bulk download button.
## [7.6.2] - 2026-04-12
### Added
- [Issue 695](https://github.com/aza547/wow-recorder/issues/695) - Warn users when Advanced Combat Logging is disabled in WoW settings.
### Fixed
- Fix a bug introduced in the last release where deleting videos manually didn't actually delete the file.
- Update Algeth'ar Academy dungeon timer.
## [7.6.1] - 2026-04-10
### Changed
- Restyled the tag dialog box to handle multiline descriptions and be scrollable.
### Added
- [Issue 805](https://github.com/aza547/wow-recorder/issues/805) - Automatically generate YouTube compatible timeline for multiview kill videos.
### Fixed
- Fix the viewpoints counter being cut off if the player name is too long.
- Upload rendered kill videos if config specifies clip upload.
- Fix an issue where the default storage path could interacted poorly with OneDrive.
- Add some missing map IDs causing files to be named "Unknown Dungeon".
## [7.6.0] - 2026-03-14
### Added
- [Issue 777](https://github.com/aza547/wow-recorder/issues/777) - Update M+ timers for Midnight S1.
- Cloud videos can now be directly clipped.
- Adds the kill video creator feature.
## [7.5.2] - 2026-02-26
### Added
- [Issue 797](https://github.com/aza547/wow-recorder/issues/797) - Add the new devourer spec.
### Fixed
- Validate log path shows false by default when it's meant to show true.
- Avoid shipping a duplicate copy of ffmpeg which reduces app size by about 60MB.
## [7.5.1] - 2026-01-21
### Fixed
- Update the parsing of COMBATANT_INFO events which changed in Midnight prepatch.
## [7.5.0] - 2026-01-18
### Added
- A switch to disable strict log path validation.
- Manual recording start/stop buttons on the side menu as an alternative to hotkeys.
- [Issue 788](https://github.com/aza547/wow-recorder/issues/788) - Added the app version to the video metadata and an indicator in the frontend.
- [Issue 754](https://github.com/aza547/wow-recorder/issues/754) - Add a timer for the currently recording activity.
### Fixed
- [Issue 789](https://github.com/aza547/wow-recorder/issues/789) - Fixes a bug where manual recordings could be interrupted by combat log events.
- Fix a regression where the force stop button did nothing.
## [7.4.0] - 2026-01-08
### Added
- [Issue 769](https://github.com/aza547/wow-recorder/issues/769) - Users with delete permissions can now delete chat messages.
- [Issue 783](https://github.com/aza547/wow-recorder/issues/783) - Search for common WoW locations on initial install to auto-configure log paths.
- [Issue 783](https://github.com/aza547/wow-recorder/issues/783) - Default storage folder to a sensible location on first time installation.
### Fixed
- [Issue 733](https://github.com/aza547/wow-recorder/issues/733) - Fix row highlighting when sorted.
- [Issue 731](https://github.com/aza547/wow-recorder/issues/731) - Fix an issue where quickly switching between content types could fail to record.
- [Issue 782](https://github.com/aza547/wow-recorder/issues/782) - Make video buttons more friendly in the presence of cloud/disk storage.
## [7.3.0] - 2025-12-07
### Added
- [Issue 777](https://github.com/aza547/wow-recorder/issues/777) - Adds Midnight M+, raids and new arena.
- [Issue 769](https://github.com/aza547/wow-recorder/issues/769) - Enables chat for Clips and fixes for Manual categories.
## [7.2.1] - 2025-11-29
### Changed
- [Issue 771](https://github.com/aza547/wow-recorder/issues/771) - Record in MKV as an intermediate format for better error handle characteristics.
- [Issue 775](https://github.com/aza547/wow-recorder/issues/755) - Selected player is now sticky when changing between rows.
### Added
- [PR 768](https://github.com/aza547/wow-recorder/pull/768) - Added config for setting classic PTR.
- [Issue 776](https://github.com/aza547/wow-recorder/issues/776) - Patreon icon in bottom left.
### Fixed
- Improve frontend render performance on upload/download progress re-renders.
- Fix an issue where the temporary recording folder would only get cleared out on restarts of the app.
- [Issue 775](https://github.com/aza547/wow-recorder/issues/775) - Fixes a bug where phyiscally changing from an Nvidia to an AMD GPU or vice versa could cause the app to crash.
## [7.2.0] - 2025-11-12
### Changed
- Moved the viewpoints selection into an expandable drawer.
### Added
- [PR 764](https://github.com/aza547/wow-recorder/pull/764) - Adds the video chat feature for Pro users.
- Classic PTR "support". The app will accept Classic PTR paths. As usual, PTR support is best effort.
### Fixed
- Take the latest rust-ps executable.
- Make the debounce timer on inputting cloud login details longer so it's less annoying.
- Fix the style of the scrollbar on the filter selection dropdown.
- [Issue 765](https://github.com/aza547/wow-recorder/issues/765) - Allow number settings fields to be temporarily empty while changing them.
## [7.1.1] - 2025-10-31
### Fixed
- [Issue 760](https://github.com/aza547/wow-recorder/issues/760) - Guard against invalid monitor indexes.
- Add the `-movflags +faststart` ffmpeg flags to the final video cut to improve Cloud load speeds.
- Fix some Korean translations that were accidentally in Chinese.
- [Issue 759](https://github.com/aza547/wow-recorder/issues/759) - Fix issue where sleeping windows could trigger a "failed to force stop" error.
## [7.1.0] - 2025-10-26
### Added
- [Issue 719](https://github.com/aza547/wow-recorder/issues/719) - Add the ability to bulk upload/download videos.
### Fixed
- [Issue 748](https://github.com/aza547/wow-recorder/issues/748) - Fix an issue where the auto-installer would fail to close the application.
- [Issue 728](https://github.com/aza547/wow-recorder/issues/728) - Fix an issue where WCR could fail to load videos from disk if many thousands of videos are present.
- Fix an issue where WCR wouldn't launch for some users with modified PATH environment variables.
- Better adhere to Electron security best practices, videos are now served from disk in a more secure manner via a custom protocol.
## [7.0.3] - 2025-10-11
### Fixed
- Fixes some translations and phrases in the audio settings.
- [Issue 746](https://github.com/aza547/wow-recorder/issues/746) - Rewords the "Reset Game" button to "Auto-Fit Game".
- [Issue 745](https://github.com/aza547/wow-recorder/issues/745) - Fix PTT/Manual hotkeys being overly permissive.
- [Issue 747](https://github.com/aza547/wow-recorder/issues/747) - Better client logout handling on disabled/deleted guild.
## [7.0.2] - 2025-10-01
### Added
- Log electron GPU info on startup.
- Log error messages from OBS signals if available.
### Fixed
- [Issue 740](https://github.com/aza547/wow-recorder/issues/740) - Fix an issue where HDR displays would show a black screen on both the preview and recording.
- [Issue 743](https://github.com/aza547/wow-recorder/issues/743) - Fix a bug where the encoder could auto-default to an invalid selection if recording resolutions higher than 4000.
- [Issue 744](https://github.com/aza547/wow-recorder/issues/744) - Fix a bug where the game source would not move if the overlay source was snapped.
- Fix a bug where a previous clip could be re-used if OBS hit a recording error.
## [7.0.1] - 2025-09-27
### Fixed
- Fix a bug where opening the audio settings page could crash the app.
## [7.0.0] - 2025-09-27
### Changed
- [Issue 697](https://github.com/aza547/wow-recorder/issues/697) & [PR 702](https://github.com/aza547/wow-recorder/pull/702) - Remove the dependency on OSN, OBS bindings are now provided by [noobs](https://github.com/aza547/noobs).
- Buffer recordings are now entirely in memory, removing a timing window that could cause videos to be wrongly cut also reducing disk wear and hopefully user confusion.
- Rework the audio sources page to be easier to understand.
- The `jim_nvenc` and `jim_nvenc_av1` encoders have been replaced by their modern counterparts. Migration is automatic.
- Pro config is now separated from the base config and won't interrupt recording if it goes wrong (e.g. your guild is deleted).
### Added
- Add the ability to manually record via a hotkey configurable in the settings.
- Add the ability to drag and scale video sources on the scene, as well as various other options.
- Split out cloud config from regular config and add an appropriate status card. Cloud settings can be reconfigured while recording.
- Add the ability to force the video sources to SDR.
- Volume can now be configured on a per-source basis.
- [Issue 632](https://github.com/aza547/wow-recorder/issues/632) - Add AV1 support for AMD: there has been no testing of this, please share your experience in Discord!
- Add support for QSV (H264 and AV1) encoding.
- Add a button to refresh the list of guilds on demand.
- On first time setup, automatically set the default the encoder to a sensible default (i.e. pick a hardware encoder).
- Add a button to set the encoder to a sensible default (same as above, but on demand rather than on first time install).
### Fixed
- [Issue 677](https://github.com/aza547/wow-recorder/issues/677) - Resolve issues with select menus hidden behind scene preview.
- [Issue 640](https://github.com/aza547/wow-recorder/issues/640) - Remove a hack relying on moving the preview off-screen when not on display.
- Fix HOA timer.
- Hide the scene preview while select menus are open, so they do not overlap.
- Automatically handle the changing of an audio device ID, if the device is available with a different ID.
- OBS logs now live in the same folder as the WCR logs so should be more obvious.
- OBS logs now have correct timestamps, rather than starting at 00:00:00.
-
## [6.15.6] - 2025-08-28
### Fixed
- [Issue 725](https://github.com/aza547/wow-recorder/issues/725) - Remove the faders. This is to address crashes reported by several users. This function will come back using the libobs volume setting in 7.0.0 but setting source volume is for now unsupported. Stability is priority.
## [6.15.5] - 2025-08-25
### Fixed
- Fix Streets of Tazavesh timer again.
- [Issue 720](https://github.com/aza547/wow-recorder/issues/720) - Fix record challenge mode toggle.
- [Issue 716](https://github.com/aza547/wow-recorder/issues/716) - Don't allow "#" in storage path.
- [Issue 703](https://github.com/aza547/wow-recorder/issues/703) - Allow "`" as the PTT key.
- [Issue 639](https://github.com/aza547/wow-recorder/issues/639) - Fix key handling when drawing mode is enabled.
## [6.15.4] - 2025-08-16
### Fixed
- [Issue 717](https://github.com/aza547/wow-recorder/issues/717) - Update to OSN `0.25.47` to fix game capture mode.
- Fixed some key timers.
- Fixed a fader release log.
## [6.15.3] - 2025-08-10
### Added
- [PR 713](https://github.com/aza547/wow-recorder/pull/713) - Add switch to Enable/Disable recording of MoP Classic challenge modes.
### Fixed
- [Issue 708](https://github.com/aza547/wow-recorder/issues/708) - Fix an issue where force stopping classic arena would give the wrong result and category.
- Update timers for TWW season 3 Mythic+.
## [6.15.1] - 2025-08-01
### Fixed
- Add some missing MOP classic arenas.
## [6.15.0] - 2025-07-31
### Changed
- Track the region as part of the combatant now the combat log includes it.
### Added
- [PR 692](https://github.com/aza547/wow-recorder/pull/692/files) - Adds Configurable Push-To-Talk Release Delay.
- Add MOP challenge modes.
### Fixed
- Make wipes overrun a few seconds longerto avoid missing anything.
- [Issue 668](https://github.com/aza547/wow-recorder/issues/668) - Fixes the zoom in hotkey.
- Make the test raid button work even when "record current raid only" is selected.
- Fix an issue where rescaling would not trigger on changing the canvas size.
## [6.14.2] - 2025-06-29
### Changed
- Don't apply current retail encounter filtering to PTR.
- Update OSN to 0.25.34.
### Added
- [Issue 689](https://github.com/aza547/wow-recorder/issues/689) - Add TWW Season 3 new dungeon.
### Fixed
- Fix a bug where the video source wouldn't rescale correctly after a reconfigure sometimes.
## [6.14.1] - 2025-06-15
### Changed
- [Issue 660](https://github.com/aza547/wow-recorder/issues/660) - Re-enable labels on video progress / volume sliders.
### Fixed
- [Issue 650](https://github.com/aza547/wow-recorder/issues/650) - Fix an issue where outside players could appear in the Mythic+ roster.
- [Issue 687](https://github.com/aza547/wow-recorder/issues/687) - Fix an issue where the sorting of some columns was broken.
- [Issue 686](https://github.com/aza547/wow-recorder/issues/686) - Fix an issue where some keyboard interactions with the video selection table were broken when sorted.
- [Issue 684](https://github.com/aza547/wow-recorder/issues/684) - Fix an issue where Solo Shuffle videos could be grouped in the UI in error.
- [Issue 585](https://github.com/aza547/wow-recorder/issues/585) - Fix some missing translations.
## [6.14.0] - 2025-06-07
### Changed
- [Issue 519](https://github.com/aza547/wow-recorder/issues/519), [PR 676](https://github.com/aza547/wow-recorder/pull/676) - Use OBS's force stop functionality where we don't need a video file.
- Some style improvements to the video selection table.
### Added
- [Issue 678](https://github.com/aza547/wow-recorder/issues/678) - Add the ability to record/upload current the current raid tier encounters only.
- Added an indicator to show how many videos are queued for upload download in addition to those currently in progress.
### Fixed
- [Issue 594](https://github.com/aza547/wow-recorder/issues/594) - Move the tag button to a more sensible place.
## [6.13.3] - 2025-06-01
### Added
- Add a "hide empty categories" option.
- [Issue 673](https://github.com/aza547/wow-recorder/issues/673) - Improve the delete dialog to allow individual videos to be deleted.
- Add the ability to disable hardware acceleration in the app.
### Fixed
- Stop re-encoding the audio on cutting video.
- [Issue 659](https://github.com/aza547/wow-recorder/issues/650) - Fix a bug where M+ without a boss pull would not record.
- [Issue 671](https://github.com/aza547/wow-recorder/issues/671) - Fix a benign error popping up on installing visual C++ libs.
- [Issue 672](https://github.com/aza547/wow-recorder/issues/672) - Fix a JavaScript error that could appear on quitting.
## [6.13.2] - 2025-05-22
### Added
- Add audio source bars to the audio configuration panel.
### Fixed
- [Issue 663](https://github.com/aza547/wow-recorder/issues/663) - Fix a bug with ghost audio devices not being deselectable.
- Rescale video to fit scene on source callback from OBS rather than a timer.
- Fix a bug where the process audio slider did not apply correctly.
## [6.13.1] - 2025-05-16
### Changed
- Change the polling mechanism to be websocket based, client refreshes are quicker and more efficient on the server side.
- Add a small amount of overrun to raid wipes to alleviate any rounding of the duration cutting into the pull.
## [6.13.0] - 2025-05-11
### Added
- [Issue 610](https://github.com/aza547/wow-recorder/issues/610) - Adds audio source configuration on a per-app basis.
### Fixed
- Fix an issue where 0% wipes were shown as 100% wipes.
- Fix an issue where Gallywix would show as 0% wipe on Mythic if wiping before the shield is broken.
## [6.12.2] - 2025-05-08
### Changed
- [Issue 654](https://github.com/aza547/wow-recorder/issues/654) - Make the colors for the boss %age HP more natural.
### Fixed
- [Issue 651](https://github.com/aza547/wow-recorder/issues/651) - Fixed an issue where deleting wouldn't update the GUI.
## [6.12.1] - 2025-04-28
### Fixed
- Fix an issue where double clicking the storage filter would set undefined.
- [Issue 469](https://github.com/aza547/wow-recorder/issues/649) - Fix an issue where the storage filter could reset to the "no videos" page.
- Fix an issue where video would randomly resize after changing tab.
## [6.12.0] - 2025-04-27
### Added
- [Issue 616](https://github.com/aza547/wow-recorder/issues/616) - Add an upload toggle for Retail and Classic recordings.
- Add a Storage filter toggle, remove the search tags for cloud/disk and the override switch for bypassing cloud videos.
### Fixed
- [Issue 642](https://github.com/aza547/wow-recorder/issues/642) - Check daily for updates not just on startup.
- Fixed an issue where 0% wipes would not show the %.
- Fix an issue where the Log path validation would accept a folder of the same level (e.g Interface).
- [Issue 646](https://github.com/aza547/wow-recorder/issues/646) - Fix an issue with the table page resetting on interacting with the videos.
- Fix an issue where arrow keys would not have consistent behaviour when video progress or volume slider was focused.
- [Issue 643](https://github.com/aza547/wow-recorder/issues/643) - Add missing unlocked filter and fix some icons.
- [Issue 602](https://github.com/aza547/wow-recorder/issues/602) - Reset the video player size on a resize if it's bigger than the window height.
## [6.11.1] - 2025-04-21
### Fixed
- Fix an issue where the scene preview would be misplaced when using Windows scaling.
## [6.11.0] - 2025-04-20
### Changed
- Shortlinks are now permanent, so reflect that in the UI.
### Added
- Add a drawing mode to allow annotation of the video player with Excalidraw.
- [Issue 621](https://github.com/aza547/wow-recorder/issues/621) - Add a delete permission tier and update client to respect it.
### Fixed
- The date range filter now translates correctly.
- Allow key events to propogate while video progress slider is focused.
- [Issue 628](https://github.com/aza547/wow-recorder/issues/628) - Fixes a bug where downloading a video wasn't possible if cloud upload was disabled in the config.
- [Issue 609](https://github.com/aza547/wow-recorder/issues/609) - Fixes a bug where numeric fields in settings would accept text and display NaN.
- [Issue 622](https://github.com/aza547/wow-recorder/issues/622) - Fix a bug where it was not possible to deselect the default audio devices.
- [Issue 625](https://github.com/aza547/wow-recorder/issues/625) - Fix a bug where audio devices would get deselected if they were unrecognized.
- [Issue 638](https://github.com/aza547/wow-recorder/issues/638) - Fix a bug where zooming (CTRL SHIFT + / CTRL -) would mess with the scene preview.
## [6.10.0] - 2025-04-12
### Changed
- Restyle the video table, removing the expandable rows in preference for a more minimal design.
- Delete, tag, and protect/star/lock buttons now apply to the row and not the viewpoint.
- Add the name "Warcraft Recorder" and app icon to the title bar.
- Upgrade obs-studio-node to 0.25.17 (and OBS to 30.2.4).
### Added
- Added all combatants back to search filter.
- Show boss percentages on wipes.
- Catch render errors and display a refresh button for the user to recover.
- Add a date filter, and range picker.
### Fixed
- Bump packages, including electron, to pick up latest tzdata.
- [Issue 624](https://github.com/aza547/wow-recorder/issues/624) - Fixes an issue where manually deleting log files could cause recording to stop.
- [Issue 606](https://github.com/aza547/wow-recorder/issues/606) - Fixes an issue where bulk delete could cause the app to go blank.
- Add the cage of carnage arena.
## [6.9.3] - 2025-03-24
### Fixed
- Fix an issue introduced in 6.8.0 and then again in 6.9.2 where audio could be desynced on video seeking.
- Fix an issue where bulk deletes could fail due to exceeding the database rate limit.
- Fix an issue where AV1 encoded long videos (Mythic+) could fail to cut due to ffprobe outputting >1MB of stdout.
## [6.9.2] - 2025-03-21
### Fixed
- Fix an issue where clipping videos could fail when clipping from the start of a video.
- Fix an issue where MP4 files were technically not valid (despite still playing), preventing raw video embeds in Discord and flikering playback in VLC.
- Reduce the time to cut videos by reverting to copying the audio stream, and adding more frequent keyframes.
- [Issue 661](https://github.com/aza547/wow-recorder/issues/611) - Updates for some M+ timers that changed.
## [6.9.1] - 2025-03-16
### Fixed
- [Issue 605](https://github.com/aza547/wow-recorder/issues/605) - Fix an issue where CTRL + Shift modifiers weren't being releases properly sometimes.
- Fix some more M+ timers.
## [6.9.0] - 2025-03-16
### Changed
- Collapse the search bar dropdown on picking something.
### Added
- [Issue 380](https://github.com/aza547/wow-recorder/issues/380) - Add support for the Nvidia and AMD AV1 encoders.
### Fixed
- [Issue 608](https://github.com/aza547/wow-recorder/issues/608) - Fix some M+ timers.
## [6.8.2] - 2025-03-07
### Changed
- Clipping mode now assumes a more sensible clip size.
- Clipping mode labels now don't overlap so poorly.
- Show a more appropriate cloud icon if there isn't a cloud video to view.
- Stop CI builds (but still run the tests), code signing means the build needs to be local to the token.
### Added
- Add the 5760x1080 resolution.
- Fix a bunch of S2 Mythic+ dungeon timers.
- EV code signing!
### Fixed
- [Issue 588](https://github.com/aza547/wow-recorder/issues/588) - Fixes an issue where the client was prone to locking accounts on password change.
## [6.8.1] - 2025-02-23
### Added
- App updates are now automatic, but will still prompt the user before installing.
### Fixed
- Fix an issue introduced in 6.8.0 where audio could be desynced on video seeking.
- Do better validation of the retail PTR log path.
- Fix a bug where PTR recordings were cut short.
## [6.8.0] - 2025-02-21
### Added
- [Issue 593](https://github.com/aza547/wow-recorder/issues/593) - Allow use of arrow keys, enter, backspace to control search tags.
- Add the Wintergrasp zone ID.
- Add the ability to use ctrl / shift modifiers to select videos in the video table.
- [PR 597](https://github.com/aza547/wow-recorder/pull/597) - Add the ability to do multipov playback.
- [PR 598](https://github.com/aza547/wow-recorder/pull/598) - Resolve an issue where the start time of videos could vary by a few seconds due to keyframe snapping.
## [6.7.0] - 2025-02-02
### Changed
- Performance and style improvements for the video player.
### Added
- [Issue 590](https://github.com/aza547/wow-recorder/issues/590) - Add support for TWW S2 Mythic+ dungeons.
- [Issue 591](https://github.com/aza547/wow-recorder/issues/591) - Add a "clips upload" toggle in the settings.
- [Issue 583](https://github.com/aza547/wow-recorder/issues/583) - Add a PTR recording settings, supported on a best-effort basis.
### Fixed
- [Issue 586](https://github.com/aza547/wow-recorder/issues/586) - Fix a bug where we would spam logs with rescaling information.
- Fix a longstanding issue where the status tooltip could overlap the recording preview.
- Allow the default audio device to be selected, and default to it.
- Fix a bug with Game and Window capture modes not working for Beta or PTR clients.
## [6.6.0] - 2025-01-27
### Changed
- Revamp the search bar to provide a nicer experience.
### Added
- Add the ability to skip individual frames with the "." or the "," hot keys.
- Add some extra details to the video selection table showing if rows contain videos that are starred or tagged.
### Fixed
- [Issue 587](https://github.com/aza547/wow-recorder/issues/587) - Fixes issues with the video player covering title bar.
## [6.5.1] - 2025-01-05
### Fixed
- Fix some missing translations.
- Fix a bug with Window capture mode not correctly setting up the scene.
- [Issue 579](https://github.com/aza547/wow-recorder/issues/579) - Fix a bug where clipped videos were counting towards pull count.
## [6.5.0] - 2025-01-03
### Changed
- Use new API endpoint.
- Replace the guild text field with a dropdown.
### Added
- Simplified Chinese language support.
### Fixed
- Tidy up some borders in the video table.
- Let video category counters go higher than 999.
- [Issue 571](https://github.com/aza547/wow-recorder/issues/571) - Fix non-english client game/window capture detection.
## [6.4.1] - 2024-12-20
### Added
- German language support.
### Fixed
- Fix a bug where you couldn't configure a custom chat overlay.
- Add pagnation to video lists to avoid performance problems with a large number of videos.
## [6.4.0] - 2024-12-19
### Changed
- [Issue 559](https://github.com/aza547/wow-recorder/issues/559) - Don't group clips in the UI, it's confusing.
- Fix an issue where Challenger's Peril was being accounted for twice.
### Added
- [Issue 566](https://github.com/aza547/wow-recorder/issues/566) - Add localisation support.
- [Issue 566](https://github.com/aza547/wow-recorder/issues/566) - Add Korean translations.
### Fixed
- [Issue 572](https://github.com/aza547/wow-recorder/issues/572) - Fix upload bug.
- Stop upload/download trackers flickering on 0%.
## [6.3.0] - 2024-12-12
### Changed
- Improve responsiveness of viewpoint switching, and retain playing state.
- Update Electron from version 24.4.0 to 33.2.0, and other related packages.
### Added
- Add difficulty IDs for Classic Era Phase 6 raids.
## [6.2.1] - 2024-12-08
### Fixed
- Fix a bug where Classic Era was not recording.
- Fix a bug where the status card would not update after a game mode config change.
- Fix a bug where Challenger's Peril wasn't being accounted for in M+ dungeon timers.
- Fix a bug where bulk deleting videos wouldn't delete disk based videos.
- Fix a bug where bulk deleting videos when not a Pro user would crash the app.
## [6.2.0] - 2024-12-06
### Added
- [Issue 488](https://github.com/aza547/wow-recorder/issues/488) - Added a bulk delete option.
### Fixed
- [Issue 558](https://github.com/aza547/wow-recorder/issues/558) - Fix a bug where it was possible to set your storage path and buffer path to the same thing.
- [Issue 558](https://github.com/aza547/wow-recorder/issues/558) - Fix a bug where we would rescale the scene too often under some conditions.
## [6.1.0] - 2024-11-30
### Added
- Redesign the video selection panel to be more performant and useful.
- [Issue 538](https://github.com/aza547/wow-recorder/issues/538) - Pro users can now use a gif as their custom chat overlay.
### Fixed
- Fix an issue where the upload/download icons would flicker.
- Relax pull grouping timer as apparently Windows does a bad job of automatically keeping you in sync with an NTP server.
- [Issue 550](https://github.com/aza547/wow-recorder/issues/550) - Add the 90s Challenger's Peril correction to M+ chest calculation.
- Fix a bug where deleted videos were sometimes not correctly deleted.
- Fix an issue where the CMAA 2 setting in WoW could cause blurry video.
## [6.0.4] - 2024-10-27
### Fixed
- [Issue 544](https://github.com/aza547/wow-recorder/issues/544) - Fix the "cannot be closed on upgrade" bug.
- Fix some M+ timers in TWW S1.
- Add raid IDs for MC / BWL / ZG / Onyxia Era raids.
## [6.0.3] - 2024-10-12
### Changed
- Relax the timer we expect OBS to stop in from 30s to 60s.
### Added
- Add cloud / disk as search filters.
- [Issue 537](https://github.com/aza547/wow-recorder/issues/537) - Space bar can now be used to start/stop the video player.
- [Issue 522](https://github.com/aza547/wow-recorder/issues/522) - Adds chinese client support for Window and Game capture modes.
### Fixed
- [Issue 542](https://github.com/aza547/wow-recorder/issues/542) - Fix a bug with the Devour M+ affix not displaying an image.
- [Issue 539](https://github.com/aza547/wow-recorder/issues/539) - Fix a bug where Mythic+ result search terms were misrepresented by the search bar.
- [Issue 540](https://github.com/aza547/wow-recorder/issues/540) - Clips are now timestamped at the point of clipping and sorted accordingly, rather than inheriting from their parent video.
- [Issue 543](https://github.com/aza547/wow-recorder/issues/543) - Fix a bug where the raid encounter pull counter could be wrong.
- [Issue 457](https://github.com/aza547/wow-recorder/issues/457) - Stop keyboard media keys playing/pausing the video when the app is minimized.
- [Issue 533](https://github.com/aza547/wow-recorder/issues/533) - Fix a bug where Challenger's Peril wasn't included in the keystone uprade level calculation.
- [Issue 535](https://github.com/aza547/wow-recorder/issues/535) - Fix a bug where using multiple audio devices could cause audio to sound terrible.
- Fix an issue where the scene could end up wrongly scaled after multiple settings changes.
- A bug with the 3440x1200 resolution.
## [6.0.2] - 2024-09-21
### Added
- [Issue 526](https://github.com/aza547/wow-recorder/issues/526) - Added the 3360x1440 resolution option.
### Fixed
- [Issue 518](https://github.com/aza547/wow-recorder/issues/518) - Minor UI issues.
- [Issue 529](https://github.com/aza547/wow-recorder/issues/529) - Raid upload difficulty threshold is no longer ignored.
- [Issue 531](https://github.com/aza547/wow-recorder/issues/531) - Selecting a monitor and unplugging it would break the app.
- Improve OBS signal handling to be more robust to timeouts.
- Update TWW season 1 M+ timers.
## [6.0.1] - 2024-09-02
### Fixed
- [Issue 521](https://github.com/aza547/wow-recorder/issues/521) - Missing support for the new Deephaul Ravine battleground.
## [6.0.0] - 2024-08-27
### Changed
- [PR 517](https://github.com/aza547/wow-recorder/pull/517) - Major rework of the UI, big thanks to Stephix for contributing this.
- Upgrade OBS to 29 (and OSN to 0.24.43).
### Fixed
- [Issue 512](https://github.com/aza547/wow-recorder/issues/512) - Fix a bug where the manager would repeatedly retry configuration if the user got the password wrong.
- Fixed an issue where you could not download a video if the cloud upload setting was disabled.
- Fix a bug where downloading the same video twice in a row would fail.
- Improve simultaenous death handling when applying video timeline marks.
## [5.7.2] - 2024-08-04
### Fixed
- The cloud account name field is now labelled user / email.
- [Issue 510](https://github.com/aza547/wow-recorder/issues/510) - Refocus the main window if the users tries to launch the app again.
## [5.7.1] - 2024-08-04
### Fixed
- [Issue 504](https://github.com/aza547/wow-recorder/issues/504) - Reattempt to configure on network failures.
- Another attempt to fix an issue where the rust-ps binary was getting flagged by anti-virus.
## [5.7.0] - 2024-08-03
### Changed
- [Issue 508](https://github.com/aza547/wow-recorder/issues/508) - Shareable links now last up to 30 days.
### Added
- Adds TWW season 1 Mythic+ dungeon support.
### Fixed
- Handle TWW log timestamps which now include the year.
- Attempt to fix an issue where the rust-ps binary was getting flagged by anti-virus.
## [5.6.2] - 2024-07-27
### Fixed
- Fix a regression where PTR and Beta didn't work in 5.6.1.
- [Issue 506](https://github.com/aza547/wow-recorder/issues/506) - Improve disk delete behaviour.
- Fix a bug where window capture did not work on TWW pre-patch as Blizzard changed the window name.
- Stop writing the cloud password to logs.
## [5.6.1] - 2024-07-08
### Changed
- Make delete and delete all points of view into separate buttons for ease of use.
### Fixed
- Fix a major memory leak when using the AMD hardware encoder.
- Fix a slow memory leak caused by the process polling mechanism.
- Fix a bug where the upload rate limit field would show when not relevant.
- Fix a bug where we would sometimes fail to cut a video as the clean-up ran mid-cutting.
## [5.6.0] - 2024-06-15
### Added
- [Issue 485](https://github.com/aza547/wow-recorder/issues/485) - Added upload rate limit setting.
### Fixed
- Make the chat overlay scale slider step size smaller.
- Fix the scrollbar width on the scene editor.
## [5.5.0] - 2024-06-12
### Added
- [Issue 403](https://github.com/aza547/wow-recorder/issues/403) - Allow Pro users to use a custom chat overlay.
- Access control to allow users to read but not write. Accompanies website changes.
### Fixed
- Fix a bug where TWW beta wouldn't be accepted as a retail log path.
- [Issue 227](https://github.com/aza547/wow-recorder/issues/227) - Fix a bug in classic where arena games sometimes ended too early.
## [5.4.2] - 2024-06-03
### Changed
- Make shareable links readable, and website player enhancements.
### Fixed
- Fix a bug with the disk size monitor not cleaning up correctly.
## [5.4.1] - 2024-05-23
### Changed
- [Issue 502](https://github.com/aza547/wow-recorder/issues/502) - Bring back combatant search filters.
### Fixed
- More M+ timer fixes.
- Better detection of mage specs in classic.
- Ignore the desktop.ini file when running folder checks.
## [5.4.0] - 2024-05-11
### Fixed
- Fix a bug where we would re-use the same stream on a failed upload, instead of a new one.
- Fix a bug where deleting a cloud video would not trigger other clients to update.
- Make the cloud settings more responsive withb a debounce timer.
- [Issue 500](https://github.com/aza547/wow-recorder/issues/500) - Fix some M+ timers.
- [Issue 400](https://github.com/aza547/wow-recorder/issues/400) - Prevent setting a pre-existing storage or buffer path with videos in it to avoid accidental deletions.
- [Issue 499](https://github.com/aza547/wow-recorder/issues/499) - Add a button and hotkey to delete all points of view for an activity.
## [5.3.1] - 2024-05-08
### Fixed
- Improve retry handling in-case of cloud upload failure.
- Fix a bug where two copies of WR were showing in Windows.
- Allow classic beta to be accepted as a valid log path.
## [5.3.0] - 2024-05-05
### Added
- [Issue 498](https://github.com/aza547/wow-recorder/issues/498) - Allows more detailed configuration of what videos to upload.
- Use new API auth endpoint to validate we're authenticated with the cloud.
- Client-side work to match API improvements for scalability.
- Fix a server side bug where long-running uploads would fail after an hour.
### Fixed
- [Issue 497](https://github.com/aza547/wow-recorder/issues/497) - Fix a bug where the upload/download buttons would show up when config wasn't valid, and when the upload button could sometimes cause errors.
## [5.2.5] - 2024-05-02
### Fixed
- [Issue 494](https://github.com/aza547/wow-recorder/issues/494) - Fix a bug where some old videos don't upload correctly due to level/keystoneLevel confusion.
- [Issue 495](https://github.com/aza547/wow-recorder/issues/495) - Fix a bug where old videos didn't show the start time correctly.
## [5.2.4] - 2024-04-28
### Fixed
- [Issue 483](https://github.com/aza547/wow-recorder/issues/483) - Fix a bug where pull count for raid bosses reset over midnight.
- [Issue 490](https://github.com/aza547/wow-recorder/issues/490) - Fix a bug where durations over an hour would wrap.
- [Issue 489](https://github.com/aza547/wow-recorder/issues/489) - Fix a bug where videos larger than 5GB failed to upload.
## [5.2.3] - 2024-04-21
### Fixed
- Fix a bug showing M+ level with old metadata versions.
## [5.2.2] - 2024-04-21
### Changed
- Video button styling.
### Fixed
- Fix a status icon bug.
## [5.2.1] - 2024-04-21
### Fixed
- Various search bar fixes.
- Remove app updater code, it doesn't work without a certificate.
## [5.2.0] - 2024-04-20
### Changed
- Cloud size monitor now runs server side.
### Added
- Add a reconfiguring status and animation to the status icon.
### Fixed
- Max cloud storage size is now variable.
- Fix a bug where the wrong video would show on startup/category change with multipov.
## [5.1.3] - 2024-04-17
### Fixed
- Fix a deplete timer in DOTI.
- Fix a bug where we could try dereference an undefined video.
- Fix a bug where search text was retained when changing categories.
## [5.1.2] - 2024-04-16
### Fixed
- A bug where the show more button would show in error.
- A bug where the cloud size monitor would delete protected videos.
## [5.1.1] - 2024-04-14
### Fixed
- Fix a bug where hitting play/pause was slightly slow to respond.
## [5.1.0] - 2024-04-14
### Fixed
- Bring back auto-updater.
## [5.0.1] - 2024-04-14
### Fixed
- Fix the cloud size monitor so it deletes from the database.
- Fix to download button not working.
## [5.0.0] - 2024-04-13
### Changed
- Use D1 for storing cloud video state, instead of JSON files in R2.
### Fixed
- Fix to include new SOD difficulty for Sunken Temple.
## [4.1.4] - 2024-04-12
### Fixed
- Fix pull counter for cloud videos.
## [4.1.3] - 2024-04-07
### Fixed
- Improve the responsiveness of setting reconfigures by only validating what has changed.
- Make the video player resizing more responsive.
- Remove deaths from unique hashing as it seems they can vary.
- Cloud size monitor running in wrong direction & test for this.
- Significant frontend performance improvements.
## [4.1.2] - 2024-04-04
### Fixed
- Cloud size monitor running in wrong direction & add a test for this.
## [4.1.1] - 2024-04-02
### Fixed
- Fix size monitor to not stop at 1000 keys.
- Reset number of videos displayed on category change.
- Fix a leaky event listener on the video button download function.
- Improve some cloud logging.
- Reset the videos shown on changing category.
## [4.1.0] - 2024-04-01
### Changed
- Let badges go higher than 99.
### Added
- Classic era raid support.
## [4.0.9] - 2024-04-01
### Changed
- Change the test icon to be more intiuative.
- Order video POVs alphabetically.
- Fix a bug where we would forget the player size.
## [4.0.8] - 2024-03-31
### Fixed
- POV styling improvements.
- Cloud access bug with hardcoded bucket name.
- Make settings scrollbar wider.
- More cloud access logging.
## [4.0.7] - 2024-03-30
### Changed
- Make the POV selection group cloud and disk videos.
## [4.0.6] - 2024-03-30
### Fixed
- Fix ripple effect on video buttons when selecting sub-buttons.
- Fix button ripple effect radius.
## [4.0.5] - 2024-03-30
### Fixed
- Fix the UI button around the download spinner.
- Fix some margins around the video buttons.
## [4.0.4] - 2024-03-30
### Changed
- Add some borders to UI buttons.
### Fixed
- Fix a bug where special characters in character names could break some functionality.
## [4.0.3] - 2024-03-30
### Fixed
- Fix a bug where frontend resource URLs could expire and fail to load.
- Improve logging for cloud function.
- Fix video marker buttons not reacting correctly.
## [4.0.2] - 2024-03-29
### Fixed
- Fix a bug where deleteing a POV could cause a blank screen.
## [4.0.1] - 2024-03-29
### Fixed
- Fix a bug where uploads would buffer the entire file into memory.
- Fix a bug where the progress bars maths was wonky.
## [4.0.0] - 2024-03-29
### Changed
- Change the CQP values for recording, they were too high resulting in large video files.
### Added
- Add cloud storage support.
## [3.25.3] - 2024-03-05
### Fixed
- Fix a bug where we would fail to detect wow running due to fastlist missing dependencies.
## [3.25.2] - 2024-03-04
### Fixed
- [Issue 482](https://github.com/aza547/wow-recorder/issues/482) - Fix a bug where leaving a Mythic+ and re-entering would cut it wrongly.
- [Issue 317](https://github.com/aza547/wow-recorder/issues/317) - Remove dependency on tasklist.
- Bring back the avoid_negative_ts make_zero to the cut command.
- Make the selected video more obvious.
## [3.25.1] - 2024-02-26
### Fixed
- [Issue 226](https://github.com/aza547/wow-recorder/issues/226) - Fix a bug where classic arena could be tagged with the wrong category.
- [Issue 252](https://github.com/aza547/wow-recorder/issues/252) - Fix a bug where hunter's feign death would end a classic arena game early.
- [Issue 481](https://github.com/aza547/wow-recorder/issues/481) - Improve classic arena spec detection.
- Cut videos more accurately by dropping the no-negative-ts flag from the cut call to ffmpeg.
- Show durations in the UI including overrun.
- Fix the color of unidentified specs in the UI.
## [3.25.0] - 2024-02-13
### Changed
- Remove combatant specs and classes from filter queries, class and spec querys now only apply to the player.
- Restyle the video delete prompt as a dialog option.
### Added
- [Issue 421](https://github.com/aza547/wow-recorder/issues/421) - Tagging feature.
### Fixed
- [Issue 388](https://github.com/aza547/wow-recorder/issues/388) - Pause video playback on minimize to system tray.
- Prevent spellchecking on the search bar text field giving annoying squiggles.
- [Issue 434](https://github.com/aza547/wow-recorder/issues/434) - Fix abandoned/deplete marking on Mythic+ dungeons.
## [3.24.0] - 2024-02-10
### Changed
- [Issue 474](https://github.com/aza547/wow-recorder/issues/474) - Use CQP/CRF encoder modes rather than VBR.
- Removed support for ffmpeg_nvenc encoder, as jim_nvenc is always preferable.
### Added
- [Issue 475](https://github.com/aza547/wow-recorder/issues/475) - Make overrun times for raid and dungeons configurable.
### Fixed
- [Issue 478](https://github.com/aza547/wow-recorder/issues/478) - Fix an issue with config validation sometimes failing when it shouldn't.
## [3.23.2] - 2024-02-03
### Fixed
- [Issue 462](https://github.com/aza547/wow-recorder/issues/462) - Fix a bug where you could not delete the selected video.
- Fix a bug where we were using the day of the week instead of day of the month in clipped file names.
## [3.23.1] - 2024-01-27
### Changed
- Improvements to the python integration test infrastructure.
- Improvements to video player controls aesthetics and overlapping of sliders in clipping mode.
### Added
- [Issue 470](https://github.com/aza547/wow-recorder/issues/470) - Add the 5120x2160 resolution.
### Fixed
- [Issue 311](https://github.com/aza547/wow-recorder/issues/311) - Ensure there is enough disk space on application of storage config.
- [Issue 477](https://github.com/aza547/wow-recorder/issues/477) - Prevent mic showing as listening when WoW is closed.
- [Issue 476](https://github.com/aza547/wow-recorder/issues/476) - Fix some buttons getting shunted off the screen on small monitors.
- Fix a bug where month numbers in clipped file names were off by one.
## [3.23.0] - 2023-12-30
### Added
- [Issue 463](https://github.com/aza547/wow-recorder/issues/463) - Video clipping feature, includes a rework to the video player controls and timeline markers.
### Fixed
- [Issue 459](https://github.com/aza547/wow-recorder/issues/459) - Fix a bug where we didn't respect the content type settings.
## [3.22.1] - 2023-12-17
### Added
- Add the ability to drag to resize the video player without going fullscreen.
### Fixed
- Fix some issues with the status icon occasionally showing the wrong status.
## [3.22.0] - 2023-12-10
### Added
- Add badges to the video buttons to show how many videos in each category.
- [Issue 115](https://github.com/aza547/wow-recorder/issues/115) - Add option to suppress background microphone noise.
- [Issue 448](https://github.com/aza547/wow-recorder/issues/448) - Allow naked modifier key use as the push to talk hotkey.
### Changed
- Improve responsiveness on startup.
### Fixed
- [Issue 458](https://github.com/aza547/wow-recorder/issues/458) - Fixed a bug where videos could be cut way shorter than intended.
## [3.21.0] - 2023-12-04
### Changed
- Make the video button more concise.
- Don't include "Unknown Raid" in the video file name.
### Fixed
- [Issue 455](https://github.com/aza547/wow-recorder/issues/455) - Fix the first write of the combat log being ignored.
- [Issue 453](https://github.com/aza547/wow-recorder/issues/453) - Fix erroneously holding an audio device on app starting.
- [Issue 454](https://github.com/aza547/wow-recorder/issues/454) - Improve error handling around OBS including automatic recovery from OBS misbehaviour.
- [Issue 456](https://github.com/aza547/wow-recorder/issues/456) - Fix issue with search bar remembering query but not text.
## [3.20.2] - 2023-11-17
### Fixed
- Add missing endboss in Waycrest to avoid "undefined" showing on the video timeline.
- Make text for dungeon names a bit smaller as new dungeons have long names.
## [3.20.1] - 2023-11-14
### Fixed
- Fix some bugs with Dragonflight season 3 Mythic+ dungeons.
## [3.20.0] - 2023-11-14
### Added
- [Issue 451](https://github.com/aza547/wow-recorder/issues/451) - Add Dragonflight season 3 Mythic+ dungeons.
## [3.19.4] - 2023-11-07
### Fixed
- Fix a bug where deleting a video when the first video in a category is selected breaks things.
## [3.19.3] - 2023-11-07
### Fixed
- Fix a bug where deleting a video when the last video in a category is selected breaks things.
- [Issue 447](https://github.com/aza547/wow-recorder/issues/447) - Fix a bug where could end up in an error state after sleeping Windows/closing WoW.
- [Issue 401](https://github.com/aza547/wow-recorder/issues/401) - Fix a bug where the selected video delete button shows but doesn't work.
- Pre-emptively increase the buffer time to 15 mins in anticipation of combat log changes.
## [3.19.2] - 2023-10-23
### Fixed
- Improve rejected promise handling by removing blanket rejected promise handling.
- Revert some color format changes breaking compatiblity with some platforms/players.
## [3.19.1] - 2023-10-21
### Added
- Add a CTRL override to the delete video confirmation prompt.
- Add retail / classic queries to the search bar.
### Changed
- Improve the look of some settings while a recording is active.
### Fixed
- [Issue 446](https://github.com/aza547/wow-recorder/issues/446) - Fix to darkness in AMD encodings, and improvements for other encoders.
- [Issue 443](https://github.com/aza547/wow-recorder/issues/443) - Back out initial fix and handle F5 refresh properly.
- Fixed an issue with some encounters not working as search bar queries.
- Hide the "Unknown Raid" text to make app maintenance easier.
- Fixed issue where we tried to delete some non-existent files noisy ENOENT errors in logs.
- Improve the search bar behaviour for existing queries when new videos are recorded.
## [3.19.0] - 2023-10-03
### Added
- Add the 3840 x 1200 resolution.
- [Issue 125](https://github.com/aza547/wow-recorder/issues/125) - Push to talk for microphone recording.
- Window capture support.
### Fixed
- [Issue 443](https://github.com/aza547/wow-recorder/issues/443) - Fix to Ctrl + R breaking the app.
- Fix PTR log path validation.
## [3.18.1] - 2023-09-03
### Fixed
- [Issue 440](https://github.com/aza547/wow-recorder/issues/440) - Fix a bug preventing settings being configured for the first time.
## [3.18.0] - 2023-09-03
### Added
- [Issue 425](https://github.com/aza547/wow-recorder/issues/425) - Add death counter to the video button for raids and mythic+.
### Fixed
- [Issue 435](https://github.com/aza547/wow-recorder/issues/435) - Only allow the software encoder for resolutions over 4000 pixels.
- [Issue 437](https://github.com/aza547/wow-recorder/issues/437) - Replace some sync logic with async logic.
## [3.17.0] - 2023-08-05
### Added
- [Issue 415](https://github.com/aza547/wow-recorder/issues/415) - Add buttons for easily toggling video markers, and other video marker improvements.
### Fixed
- Fix a bug where the coloring of the video tracker bar was slightly wrong.
- Fix a bug where the app didn't remember the last selected category on starting up.
## [3.16.1] - 2023-07-28
### Fixed
- Fix a bug where if no matches found with a filter query then the app would forever show the no videos message.
## [3.16.0] - 2023-07-23
### Changed
- Revamp the UI to be more intuative.
## [3.15.2] - 2023-07-16
### Fixed
- [Issue 310](https://github.com/aza547/wow-recorder/issues/310) - React to Windows sleep events better to avoid problems on Windows waking up.
### Changed
- Start building and packaging with the latest version of NodeJS (20.4.0).
## [3.15.1] - 2023-07-12
### Added
- Add the augmentation spec for evokers.
## [3.15.0] - 2023-07-08
### Added
- [Issue 407](https://github.com/aza547/wow-recorder/issues/407) - Record unrecognised encounters. This will enable WR to record legacy, beta and ptr raid bosses.
- [Issue 428](https://github.com/aza547/wow-recorder/issues/428) - Add a daily pull counter to raid video buttons.
### Changed
- Improve home page aesthetics.
- [Issue 427](https://github.com/aza547/wow-recorder/issues/427) - Include the player name in the video file name.
### Fixed
- Fix category selection chip which would do nothing when used on the settings/scene editor pages.
- Default max storage to zero (unlimited) instead of 200GB.
## [3.14.2] - 2023-06-24
### Added
- Add encounters for Trial of the Crusader classic.
## [3.14.1] - 2023-06-21
### Fixed
- Revert the version of OBS studio node to 0.23.71 as the upgrade cause a bug preventing the chat overlay from showing.
## [3.14.0] - 2023-06-20
### Added
- [Issue 423](https://github.com/aza547/wow-recorder/issues/423) - Add affixes and some other UI improvements.
- [Issue 353](https://github.com/aza547/wow-recorder/issues/353) - Add overrun icon to the status indicator.
### Changed
- Bump the version of OBS studio node to 0.23.82.
## [3.13.1] - 2023-06-04
### Changed
- [Issue 416](https://github.com/aza547/wow-recorder/issues/416) - Automatically scale to canvas size.
- [Issue 420](https://github.com/aza547/wow-recorder/issues/420) - Convert to using thumbnails instead of fixed images.
### Fixed
- [Issue 417](https://github.com/aza547/wow-recorder/issues/417) - Fix a bug where sometimes recordings were missed.
- Split out the chat overlay settings from the game settings so it's more responsive.
- [Issue 418](https://github.com/aza547/wow-recorder/issues/418) - Improve the test button UX.
- [Issue 419](https://github.com/aza547/wow-recorder/issues/419) - Fix some misnamed boss encounters.
## [3.13.0] - 2023-05-21
### Added
- [Issue 216](https://github.com/aza547/wow-recorder/issues/216) - Add volume controls for audio sources.
### Changed
- [Issue 404](https://github.com/aza547/wow-recorder/issues/404) - Revamp settings so they can be configured live.
- [Issue 406](https://github.com/aza547/wow-recorder/issues/406) - Show death markers in all content types.
### Fixed
- [Issue 387](https://github.com/aza547/wow-recorder/issues/387) - Fix to Mythic+ video markers UX.
- [Issue 395](https://github.com/aza547/wow-recorder/issues/395) - Fix Dragonflight S2 dungeon timings for +2 and +3 chests.
- [Issue 405](https://github.com/aza547/wow-recorder/issues/405) - Guard against setting retail and classic log path to the same value.
- [Issue 410](https://github.com/aza547/wow-recorder/issues/410) - Fix preview dissapearing if recording while player is fullscreen.
- [Issue 393](https://github.com/aza547/wow-recorder/issues/393) - Improve some clipping issues with the preview.
## [3.12.0] - 2023-05-03
### Added
- [Issue 386](https://github.com/aza547/wow-recorder/issues/386) - Option to disable minimize to tray.
- [Issue 394](https://github.com/aza547/wow-recorder/issues/394) - Updates for Dragonflight season 2 content.
### Fixed
- [Issue 397](https://github.com/aza547/wow-recorder/issues/397) - Fix issue where scene preview may not position correctly.
## [3.11.0] - 2023-04-30
### Added
- [Issue 354](https://github.com/aza547/wow-recorder/issues/354) - Add a scene preview to the home page.
- [Issue 354](https://github.com/aza547/wow-recorder/issues/354) - Add a scene editor page.
- [Issue 46](https://github.com/aza547/wow-recorder/issues/46) - Add the ability to use a chat overlay.
### Changed
- Restyle some checkboxes in the settings as switches, for continuity.
### Fixed
- [Issue 384](https://github.com/aza547/wow-recorder/issues/384) - Fix Balakar Khan encounter ID.
- [Issue 391](https://github.com/aza547/wow-recorder/issues/391) - Bump electron version to ^24.0.0 to fix Mexico timezone bug.
## [3.10.4] - 2023-04-26
### Added
- [Issue 381](https://github.com/aza547/wow-recorder/issues/381) - Add "bookmarked" as a filter option.
### Fixed
- [Issue 381](https://github.com/aza547/wow-recorder/issues/381) - Fix a bug where the search bar query isn't cleared.
- [Issue 383](https://github.com/aza547/wow-recorder/issues/383) - Remove some options from the installer that didn't work.
- [Issue 382](https://github.com/aza547/wow-recorder/issues/383) - Fix a bug where toggling bookmark on a video could make it vanish from the UI.
## [3.10.3] - 2023-04-24
### Fixed
- [Issue 328](https://github.com/aza547/wow-recorder/issues/328) - Fix a bug where the installer didn't automatically install the Visual C++ Redistributable package from Microsoft.
## [3.10.2] - 2023-04-23
### Changed
- [Issue 370](https://github.com/aza547/wow-recorder/issues/370) - Show more than the most recent video on home page.
- [Issue 371](https://github.com/aza547/wow-recorder/issues/371) - Include Battleground names in the UI.
- [Issue 352](https://github.com/aza547/wow-recorder/issues/352) - Change the home page to be less fancy and more functional.
- [Issue 359](https://github.com/aza547/wow-recorder/issues/359) - Add lots of support querys to the filter bar.
- [Issue 366](https://github.com/aza547/wow-recorder/issues/366) - Delete button now has a confirmation prompt.
### Fixed
- [Issue 376](https://github.com/aza547/wow-recorder/issues/376) - Change seeking so clicking a video marker won't go to the start but to where the user clicked.
## [3.10.1] - 2023-04-11
### Fixed
- [Issue 373](https://github.com/aza547/wow-recorder/issues/373) - Fixed an issue where the Mythic+ UI didn't display correctly due to combatant bleed.
- [Issue 372](https://github.com/aza547/wow-recorder/issues/372) - Improve the help text next to the force stop button.
## [3.10.0] - 2023-04-10
### Added
- [Issue 358](https://github.com/aza547/wow-recorder/issues/358) - Option to set threshold for raid difficulty recordings.
- [Issue 177](https://github.com/aza547/wow-recorder/issues/177) - Allow picking a category when using the test button.
- [Issue 359](https://github.com/aza547/wow-recorder/issues/359) - Add a search bar for filtering videos.
### Changed
- [Issue 352](https://github.com/aza547/wow-recorder/issues/352) - Improvements to the home page aesthetics.
- [Issue 347](https://github.com/aza547/wow-recorder/issues/347) - Only overrun in raids on kills.
### Fixed
- [Issue 355](https://github.com/aza547/wow-recorder/issues/355) - Fixed an issue where swapping characters can confuse the parser.
- [Issue 355](https://github.com/aza547/wow-recorder/issues/355) - Fixed an issue where we were incorrectly marking abandoned M+ as completed.
- [Issue 356](https://github.com/aza547/wow-recorder/issues/356) - Fixed an issue where adding too many audio devices wasn't handled well.
- [Issue 365](https://github.com/aza547/wow-recorder/issues/365) - Fixed an issue where an unknown specID would crash the app.
- [Issue 367](https://github.com/aza547/wow-recorder/issues/367) - Fixed an issue where Mythic+ COMBATANT_INFO events weren't getting handled appropriately.
- [Issue 368](https://github.com/aza547/wow-recorder/issues/368) - Fixed an issue where comps don't display correctly in some retail content.
## [3.9.0] - 2023-04-07
### Added
- [Issue 334](https://github.com/aza547/wow-recorder/issues/334) - Add JKL, arrows and space hotkeys to video player for seeking.
### Changed
- [Issue 352](https://github.com/aza547/wow-recorder/issues/352) - Improvements to the home page aesthetics.
- Adjusted M+ markers to be based on segment duration.
- Adjusted M+ marker mouseover tips to be boss names instead of just 'Boss'.
### Fixed
- [NO-ISSUE] Fixed a bug where the app would sometimes crash on selecting a category with no videos.
- [Issue 362](https://github.com/aza547/wow-recorder/issues/362) - Include the M+ keystone level in the GUI.
## [3.8.0] - 2023-04-04
### Changed
- [Issue 352](https://github.com/aza547/wow-recorder/issues/352) - Totally redesign the user interface.
- [Issue 348](https://github.com/aza547/wow-recorder/issues/348) - Changed CSS for Seek Bar to make it taller.
### Fixed
- [Issue 350](https://github.com/aza547/wow-recorder/issues/350) - Fix a bug where videos could be cut wrongly after using the stop recording button.
- Corrected first boss name in Court of Stars.
- [Issue 144](https://github.com/aza547/wow-recorder/issues/144) - Hide markers for some categories/flavours.
## [3.7.0] - 2023-03-24
### Added
- [Issue 340](https://github.com/aza547/wow-recorder/issues/340) - Option to minimize on clicking quit button.
- [Issue 144](https://github.com/aza547/wow-recorder/issues/144) - Add video timeline markers for solo shuffle and mythic+.
- [NO-ISSUE](https://github.com/aza547/wow-recorder/issues/144) - Add 5120x1440 resolution.
### Changed
- [Issue 144](https://github.com/aza547/wow-recorder/issues/144) - Use the Video JS player for playback.
### Fixed
- [Issue 334](https://github.com/aza547/wow-recorder/issues/344) - Fix bitrate for AMD GPUs, signficantly improving video quality.
- [Issue 334](https://github.com/aza547/wow-recorder/issues/344) - Remove some unsupported encoders.
## [3.6.2] - 2023-03-12
### Changed
- [NO-ISSUE] Upgrade OSN from 0.23.59 to 0.23.71.
### Fixed
- [Issue 287](https://github.com/aza547/wow-recorder/issues/287) - Fix some ugly icons to look better.
- [Issue 325](https://github.com/aza547/wow-recorder/issues/325) - Fix the OBS process not closing correctly on quitting.
- [Issue 338](https://github.com/aza547/wow-recorder/issues/338) - Resolve a problem when upgrading the app wouldn't shutdown OBS.
## [3.6.1] - 2023-03-08
### Fixed
- [Issue 332](https://github.com/aza547/wow-recorder/issues/332) - Disable hardware accelerated rendering of the app.
- [Issue 336](https://github.com/aza547/wow-recorder/issues/336) - Fix a bug where we sometimes didn't refresh the GUI after a video was recorded.
- [Issue 337](https://github.com/aza547/wow-recorder/issues/337) - Fix a spammy log if there is an empty WoW log dir.
## [3.6.0] - 2023-03-04
### Added
- [Issue 329](https://github.com/aza547/wow-recorder/issues/329) - Option to hide cursor.
- [Issue 326](https://github.com/aza547/wow-recorder/issues/326) - Option to ignore M+ below a certain level.
### Fixed
- [Issue 323](https://github.com/aza547/wow-recorder/issues/329) - Fix bug where size monitor was deleting protected videos.
## [3.5.1] - 2023-02-07
### Fixed
- [Issue 321](https://github.com/aza547/wow-recorder/issues/321) - Fix so that bitrate settings are remembered after first recording.
## [3.5.0] - 2023-02-03
### Added
- [Issue 34](https://github.com/aza547/wow-recorder/issues/34) - Add unit test infrastructure.
- [Issue 312](https://github.com/aza547/wow-recorder/issues/312) - Add a bookmark icon for protected videos.
### Changed
- [Issue 272](https://github.com/aza547/wow-recorder/issues/272) - Revert request for elevated permissions preventing running on startup.
### Fixed
- [Issue 293](https://github.com/aza547/wow-recorder/issues/293) - Fix a backend bug where reconfiguring would leak audio device references.
- [Issue 303](https://github.com/aza547/wow-recorder/issues/303) - Fix error handling so that we don't get a blank screen if something goes wrong.
- [Issue 291](https://github.com/aza547/wow-recorder/issues/291) - Improve activity and recorder logic to prevent classic double stop issue.
- [Issue 314](https://github.com/aza547/wow-recorder/issues/314) - Make size monitor more async to avoid app lag on game ending.
## [3.4.0] - 2023-01-22
### Added
- [Issue 296](https://github.com/aza547/wow-recorder/issues/296) - Add Ulduar classic support.
- [Issue 300](https://github.com/aza547/wow-recorder/issues/300) - Add difficulty to raid file names.
### Fixed
- [Issue 285](https://github.com/aza547/wow-recorder/issues/285) - Fix bug that prevented retail recording of retail war games.
- [Issue 288](https://github.com/aza547/wow-recorder/issues/288) - Fix bug that prevented changing the FPS setting.
- [Issue 275](https://github.com/aza547/wow-recorder/issues/275) - Increase retail log timeout for better handling of M+.
- [Issue 251](https://github.com/aza547/wow-recorder/issues/251) - Fix text overflow clipping in video selection buttons.
- [Issue 293](https://github.com/aza547/wow-recorder/issues/293) - Fix to prevent adding too many audio devices in the settings.
- [Issue 294](https://github.com/aza547/wow-recorder/issues/294) - Remove a bunch of encoders that don't work with WR.
## [3.3.3] - 2023-01-14
### Fixed
- Fix a bug where we didn't respect the overrun.
- [Issue 279](https://github.com/aza547/wow-recorder/issues/279) - Improve signalling robustness.
- [Issue 276](https://github.com/aza547/wow-recorder/issues/276) - Reconfiguring settings doesn't result in a blank screen with game capture.
- [Issue 280](https://github.com/aza547/wow-recorder/issues/280) - Handle unplugged audio devices better in the config.
## [3.3.2] - 2023-01-08
### Fixed
- [Issue 273](https://github.com/aza547/wow-recorder/issues/273) - Fix a bug where raid resets could crash the app.
- [Issue 271](https://github.com/aza547/wow-recorder/issues/271) - Allow OBS more time to signal.
- [Issue 274](https://github.com/aza547/wow-recorder/issues/274) - Fix bug when sometimes an internal arena zone change would crash the app.
## [3.3.1] - 2023-01-02
### Fixed
- [NO-ISSUE]- Fix crass default resolution bug.
## [3.3.0] - 2023-01-01
### Added
- [Issue 245](https://github.com/aza547/wow-recorder/issues/245) - Ability to pick and chose any combination of audio devices to record.
### Changed
- Upgrade obs-studio-node to 0.23.59.
### Fixed
- [Issue 264](https://github.com/aza547/wow-recorder/issues/264) - Attempt to fix some permissions problems on Windows 11.
- [Issue 223](https://github.com/aza547/wow-recorder/issues/223) - Make resolutions hardcoded so there can be no weird disappearing of options.
- [Issue 256](https://github.com/aza547/wow-recorder/issues/256) - Fix bug where resolution was sometimes flipped.
- [Issue 57](https://github.com/aza547/wow-recorder/issues/57) - Fix a bug preventing windows from sleeping with WR open.
## [3.2.0] - 2022-12-23
### Added
- [Issue 247](https://github.com/aza547/wow-recorder/issues/247) - Better handling for Solo Shuffle.
### Fixed
- [Issue 257](https://github.com/aza547/wow-recorder/issues/257) - Improve right click menu responsiveness.
## [3.1.2] - 2022-12-17
### Added
- [Issue 246](https://github.com/aza547/wow-recorder/issues/246) - Config check that storage path and buffer path are different.
- [Issue 282](https://github.com/aza547/wow-recorder/issues/282) - Added config toggle switch to force input audio to mono.
### Changed
- Autoplay videos on selection.
- Display errors in a neater manner with suggestions on how to get help.
- Improve clipping of classic arenas to skip waiting room.
### Fixed
- Fix a bug where closing wow didn't stop the recorder if mid activity.
- Fix a problem when saving videos to NFS mounts.
- [Issue 187](https://github.com/aza547/wow-recorder/issues/187) - Add Dragonflight M+ timings.
## [3.1.1] - 2022-12-09
### Fixed
- [Issue 239](https://github.com/aza547/wow-recorder/issues/239) - Fix app crashing when WoW closes if both log paths (retail and classic) are not configured.
- [Issue 238](https://github.com/aza547/wow-recorder/issues/238) - Don't crash on unrecognised video category, just don't record.
- Fix to log watching to make the UI more responsive.
- Fix Nokhun Proving Grounds image and shorten name.
- Update video poster to look better.
## [3.1.0] - 2022-12-03
### Added
- [Issue 187](https://github.com/aza547/wow-recorder/issues/187) - Added the new M+ dungeons and arena for Dragonflight S1.
- [Issue 237](https://github.com/aza547/wow-recorder/issues/237) - Show specifically what config is wrong when config is invalid.
### Fixed
- [Issue 236](https://github.com/aza547/wow-recorder/issues/236) - Fix to ignore normal, heroic and m0 dungeon bosses, as well as unknown encounters.
## [3.0.4] - 2022-11-27
### Fixed
- [Issue 235](https://github.com/aza547/wow-recorder/issues/235) - Fix a bug where abandoned M+ runs caused the app to crash.
- [Issue 229](https://github.com/aza547/wow-recorder/issues/229) - Fix a bug where the test button didn't work without a retail log path configured.
- Fix a bug where closing WoW while in an activity could crash the app.
## [3.0.3] - 2022-11-24
### Fixed
- Fix a bug where overrun isn't working as intended. Broke this in 3.0.1.
## [3.0.2] - 2022-11-20
### Fixed
- Fix a bug where videos were sometimes cut to wrong sizes.
- Fix a bug where Evoker class color wasn't displayed in the UI.
## [3.0.1] - 2022-11-14
### Changed
- [Issue 228](https://github.com/aza547/wow-recorder/issues/228) - Clip activities better by not assuming buffer time is end of video.
### Fixed
- [Issue 224](https://github.com/aza547/wow-recorder/issues/224) - Make settings window taller to avoid clipping content settings.
- [Issue 230](https://github.com/aza547/wow-recorder/issues/230) - Fix a bug where recordings after saving settings were broken.
## [3.0.0] - 2022-11-12
### Added
- [Issue 50](https://github.com/aza547/wow-recorder/issues/50) - Classic arena and battleground support.
- Functionality to have a good estimate at if a battleground is a win or loss.
- Spec detection for all categories that lacked it.
- Initial Evoker class handling in preperation for Dragonflight.
### Changed
- [Issue 224](https://github.com/aza547/wow-recorder/issues/224) - Improve main window styling. Bbreaks backwards compatbility with previously recorded videos.
### Fixed
- [Issue 221](https://github.com/aza547/wow-recorder/issues/221) - Fix a bug where on some setups only a subsection of the game was recorded.
## [2.10.2] - 2022-11-04
### Fixed
- [Issue 220](https://github.com/aza547/wow-recorder/issues/220) - Revert audio sources muting to fix black screen recording bug.
## [2.10.1] - 2022-11-02
### Fixed
- [Issue 57](https://github.com/aza547/wow-recorder/issues/57) - Only enable audio sources when recording buffer to avoid Windows not being able to go into sleep mode.
- [Issue 218](https://github.com/aza547/wow-recorder/issues/218) - Fix solo shuffle on DF pre-patch.
## [2.10.0] - 2022-10-22
### Added
- [Issue 211](https://github.com/aza547/wow-recorder/issues/211) - Validate combat log paths to avoid mistakes.
- [Issue 2](https://github.com/aza547/wow-recorder/issues/2) - Add Game Capture mode.
- [Issue 207](https://github.com/aza547/wow-recorder/issues/207) - Suggest some bitrates in the settings help text.
### Fixed
- [Issue 168](https://github.com/aza547/wow-recorder/issues/168) - Fix player combatant not being saved properly when a recording is forcibly ended.
- [Issue 205](https://github.com/aza547/wow-recorder/issues/205) - Expose encoder in Advanced Settings.
- [Issue 206](https://github.com/aza547/wow-recorder/issues/206) - Bitrate label corrected say to Mbps.
- [Issue 208](https://github.com/aza547/wow-recorder/issues/208) - Fix to Warsong Gulch image.
- [Issue 213](https://github.com/aza547/wow-recorder/issues/213) - Fix the application occasionally crashing when WoW is closed.
## [2.9.0] - 2022-10-11
### Added
- [Issue 50](https://github.com/aza547/wow-recorder/issues/50) - Add WOTLK classic raid support for Naxx, EOE, OS and VOA.
- [Issue 191](https://github.com/aza547/wow-recorder/issues/191) - Recording FPS, output resolution, and video bit rate now adjustable in video settings.
- [Issue 187](https://github.com/aza547/wow-recorder/issues/187) - Added Vault of the Incarnates raid IDs.
- [Issue 192](https://github.com/aza547/wow-recorder/issues/192) - Accept Beta, PTR and classic processes as reason to move to ready state.
### Changed
- [Issue 194](https://github.com/aza547/wow-recorder/issues/194) - Change to variable bitrate recording. This will drastically reduce video sizes.
### Fixed
- [Issue 194](https://github.com/aza547/wow-recorder/issues/194) - Cut videos in a queue and don't block the recorder.
- [Issue 193](https://github.com/aza547/wow-recorder/issues/193) - Add some guards so we don't start recording in an invalid state.
- [Issue 199](https://github.com/aza547/wow-recorder/issues/199) - Fix a bug where clicking test button several times would do bad things.
## [2.8.2] - 2022-10-02
### Added
- [Issue 184](https://github.com/aza547/wow-recorder/issues/184) - Option to start-up to the system tray.
### Fixed
- [Issue 164](https://github.com/aza547/wow-recorder/issues/164) - Expose the settings help text in the UI.
- [Issue 178](https://github.com/aza547/wow-recorder/issues/178) - Fix bufferStoragePath defaulting to an empty string.
- [Issue 186](https://github.com/aza547/wow-recorder/issues/186) - Prevent running multiple copies of WR.
## [2.8.1] - 2022-09-27
### Fixed
- [Issue 175](https://github.com/aza547/wow-recorder/issues/175) - Fix test button on non en-GB locales.
- [Issue 176](https://github.com/aza547/wow-recorder/issues/176) - Fix app crashing when recording is force stopped.
- [Issue 177](https://github.com/aza547/wow-recorder/issues/177) - Let test run regardless of 2v2 config setting.
## [2.8.0] - 2022-09-26
### Added
- [Issue 81](https://github.com/aza547/wow-recorder/issues/81) - Better monitor selection in settings UI.
- [Issue 52](https://github.com/aza547/wow-recorder/issues/52) - Video files are now named more human friendly.
- [Issue 134](https://github.com/aza547/wow-recorder/issues/134) - Only handle UNIT_DIED when a recording activity is in progress.
- [Issue 142](https://github.com/aza547/wow-recorder/issues/142) - Make it possible to stop recording by clicking the 'rec' icon.
- [Issue 166](https://github.com/aza547/wow-recorder/issues/166) - Remember the selected category across application restarts.
- [Issue 150](https://github.com/aza547/wow-recorder/issues/50) - Add infrastructure for future classic support.
- [Issue 168](https://github.com/aza547/wow-recorder/issues/168) - Add a timeout feature that will end a recording after 2 minutes of combatlog inactivity.
### Changed
- [Issue 165](https://github.com/aza547/wow-recorder/issues/165) - Now loads videos asynchronously to improve application reponsiveness on start up with many videos.
- [Issue 164](https://github.com/aza547/wow-recorder/issues/164) - Entirely revamp the settings to be more responsive and modern. Will reset user settings.
### Fixed
- [Issue 124](https://github.com/aza547/wow-recorder/issues/234) - Make buffering dir configurable. This setting is optional and will sensibly default.
- [Issue 123](https://github.com/aza547/wow-recorder/issues/123) - More robust monitor selection.
- [Issue 128](https://github.com/aza547/wow-recorder/issues/128) - Guard against multiple recording buffer restarts.
- [Issue 130](https://github.com/aza547/wow-recorder/issues/130) - Fix invalid default audio input/output device.
- [Issue 139](https://github.com/aza547/wow-recorder/issues/139) - Give OBS longer to recover, but crash the app if it doesn't signal.
- [Issue 155](https://github.com/aza547/wow-recorder/issues/155) - Fix periodic lag spike every 1 second while using app.
- [Issue 167](https://github.com/aza547/wow-recorder/issues/167) - Fix Iron Docks M+ timer.
- [Issue 133](https://github.com/aza547/wow-recorder/pull/133) - Fix bug that audio device would sometimes record when set to none.
## [2.7.0] - 2022-09-19
### Added
- [Issue 47](https://github.com/aza547/wow-recorder/issues/48) - Add Mythic+ recording support.
- [Issue 74](https://github.com/aza547/wow-recorder/issues/74) - Added version check from github releases page.
- [Issue 99](https://github.com/aza547/wow-recorder/issues/99) - Remember video sound settings when changing videos.
- [Issue 107](https://github.com/aza547/wow-recorder/issues/107) - Add a config setting for minimum raid duration, to avoid saving boss resets.
- [Issue 17](https://github.com/aza547/wow-recorder/issues/17) - Allow the selection of input/output audio devices for recording in settings.
### Changed
- [Issue 66](https://github.com/aza547/wow-recorder/issues/66) - Store buffer recordings in a better location.
### Fixed
- [Issue 96](https://github.com/aza547/wow-recorder/issues/96) - Fixed windows resolution scaling resulting in OBS Resolutions not being set properly.
- [Issue 78](https://github.com/aza547/wow-recorder/issues/78) - Gracefully fail if a video can't be deleted, rather than giving an uncaught exception error.
- [Issue 75](https://github.com/aza547/wow-recorder/issues/75) - Fix to size monitor blocking saving of videos.
- [Issue 86](https://github.com/aza547/wow-recorder/issues/86) - Fix various event listener leaks.
- [Issue 112](https://github.com/aza547/wow-recorder/issues/112) - Crash the app if OBS gets into a bad state.
## [2.6.1] - 2022-09-05
### Fixed
- [Issue 70](https://github.com/aza547/wow-recorder/issues/70) - Double clicking test button no longer breaks the test.
- [Issue 77](https://github.com/aza547/wow-recorder/issues/77) - Don't expect hyphen in WoWCombatLog.txt.
- [Issue 82](https://github.com/aza547/wow-recorder/issues/82) - Don't fall over if a 5v5 wargame recording is made.
- Update various NPM packages to resolve various dependabot security issues.
## [2.6.0] - 2022-08-29
### Added
- [Issue 50](https://github.com/aza547/wow-recorder/issues/50) - Add some plumbing for future when we support classic.
- [Issue 2](https://github.com/aza547/wow-recorder/issues/2) - Add a monitor selection config option. Defaults to first monitor.
- [Issue 9](https://github.com/aza547/wow-recorder/issues/9) - Add a test button to the GUI.
### Changed
- Assert that OBS behaves as expected or crash the app, previously we would just continue and get into god knows what error states.
- No longer require the application to be restarted on a config change.
- Take OSN `0.22.10`, previously was on `0.10.10`.
### Fixed
- Rename window from "Arena Recorder" to "Warcraft Recorder".
- [Issue 23](https://github.com/aza547/wow-recorder/issues/23) - Fix clean-up buffer issue on app close.
- [Issue 64](https://github.com/aza547/wow-recorder/issues/64), [Issue 60](https://github.com/aza547/wow-recorder/issues/60) - Overhaul async logic causing problems.
- [Issue 54](https://github.com/aza547/wow-recorder/issues/54) - Fix to stop recording when leaving arena games with /afk.
- [Issue 23](https://github.com/aza547/wow-recorder/issues/23) - Fix bug where app would fail to start if there were no logs in the WoW logs directory.
- [Issue 69](https://github.com/aza547/wow-recorder/issues/69) - Fix cleanup buffer JS error.
## [2.5.2] - 2022-08-29
### Fixed
- Fix issue where ZONE_CHANGE can crash the app.
## [2.5.1] - 2022-08-29
### Fixed
- Fix ID for Sun King's Salvation encounter.
- Fix resolution hardcoded regression.
- Fix issue where raid encounters don't save the result correctly if quickly followed by a zone change.
## [2.5.0] - 2022-08-29
### Added
- [Issue 29](https://github.com/aza547/wow-recorder/issues/29) - Add all shadowlands raid encounters.
- [Issue 44](https://github.com/aza547/wow-recorder/issues/44) - Auto-stop recording if WoW is closed.
- [Issue 26](https://github.com/aza547/wow-recorder/issues/26) - Buffer recording to always capture the beginning of games/encounters.
- Add a button to open the application log path for debugging.
- Add a link to Discord in the application.
- Small improvements improvements to tests.
### Fixed
- Clean-up handling of images, it was really messy.
## [2.4.1] - 2022-08-20
### Fixed
- Fix BG recording that was regressed in 2.4.0.
- Fix Warsong Gulch zone ID.
## [2.4.0] - 2022-08-20
### Added
- Write more useful information to metadata files, including player name and spec. Thanks again to ericlytle for the contribution.
- [Issue 19](https://github.com/aza547/wow-recorder/issues/19) - Display spec and name on arena and raid videos.
- MMR hover text for arenas.
### Changed
- Remove hardcoded aspect ratio of application only appropriate for 1080p recordings.
### Fixed
- [Issue 40](https://github.com/aza547/wow-recorder/issues/40) - Fix to AMD AMF encoder.
- [Issue 42](https://github.com/aza547/wow-recorder/issues/42) - Fix to Deepwind Gorge button image.
- [Issue 42](https://github.com/aza547/wow-recorder/issues/42) - Fix issue where internal BG zone changes stop the recording.
## [2.3.0] - 2022-08-14
### Added
- Resources directory and better test scripts, although they still suck.
- [Issue 10](https://github.com/aza547/wow-recorder/issues/10) - Add logging infrastructure.
- [Issue 33](https://github.com/aza547/wow-recorder/issues/33) - Add tray icon and menu. Make minimizing now hide in system tray.
- [Issue 32](https://github.com/aza547/wow-recorder/issues/32) - Add setting to run on start-up.
- [Issue 6](https://github.com/aza547/wow-recorder/issues/6) - Battlegrounds is now a supported category.
### Changed
- Record at 60 FPS instead of 30.
### Fixed
- Clean-up of react UI code.
## [2.2.0] - 2022-08-07
### Added
- [Issue 28](https://github.com/aza547/wow-recorder/issues/28) - Add open file in system explorer option when right clicking videos.
- [Issue 28](https://github.com/aza547/wow-recorder/issues/28) - Add delete video option when right clicking videos.
- [Issue 27](https://github.com/aza547/wow-recorder/issues/27) - Add save video option when right clicking videos.
### Changed
- [Issue 37](https://github.com/aza547/wow-recorder/issues/37) - Remove bitrate cap, drastically increasing recording quality (and file size). Probably should make this configurable in the future.
### Fixed
- [Issue 22](https://github.com/aza547/wow-recorder/issues/22) - Make app less fragile to missing metadata files.
## [2.1.0] - 2022-08-05
### Added
- Add some color to outcome indicator.
### Changed
### Fixed
- [Issue 5](https://github.com/aza547/wow-recorder/issues/5) - Fix arena win/loss indicator. Thanks to ericlytle for the code contribution.
## [2.0.1] - 2022-07-31
### Fixed
- Fix minimize button.
- [Issue 21](https://github.com/aza547/wow-recorder/issues/21) - Handle people /afking out of content gracefully by stopping recording on ZONE_CHANGE for most categories.
## [2.0.0] - 2022-07-07
### Added
- Backdrops for SOFO raid bosses.
### Changed
- Use libobs for recording.
- Removal of the python code for screen recording.
- Removal of ffmpeg binary for screen capture.
- Refactor of most internal logic.
- Disable BG/Mythic+ modes in the GUI for now.
### Fixed
## [1.0.3] - 2022-07-03
### Added
- Better logs for GPU detection.
### Changed
- Move output.log to a fixed relative location.
### Fixed
- Fix for AMD hardware encoding.
## [1.0.2] - 2022-06-28
### Changed
- Rename python.log to ffmpeg.log.
- Use hardware encoding on NVIDIA or AMD GPUs.
- Change app icon so not using the electron default.
### Fixed
- Fix size monitor so it actually works.
## [1.0.1] - 2022-06-26
### Fixed
- Fixed up README and CHANGELOG.
- Remove some hardcoded paths.
- Fix bug that recorder doesn't start on a dir without logs in it.
- Create required directories in storage path if they don't exist.
- Stop/start recorder process on config change.
- Add some extremely basic console logs for python recorder controller.
## [1.0.0] - 2022-06-21
### Added
- Initial drop of project.
================================================
FILE: LICENSE
================================================
Warcraft Recorder
---------------------------------------------
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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 2 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, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
Electron React Boilerplate
---------------------------------------------
The MIT License (MIT)
Copyright (c) 2015-present Electron React Boilerplate
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Excalidraw
---------------------------------------------
MIT License
Copyright (c) 2020 Excalidraw
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# Warcraft Recorder



Warcraft Recorder is a desktop screen recorder. It watches the WoW combat log file for interesting events, records them, and presents a user interface in which the recordings can be viewed.
<img width="1920" height="1032" alt="image" src="https://github.com/user-attachments/assets/aea579e3-5a7f-477d-bea0-273556a3ef9b" />
# How to Use
1. Download and run the most recent [Warcraft Recorder installer](https://github.com/aza547/wow-recorder/releases/latest).
2. Launch the application and click the Settings button.
- Create a folder on your PC to store the recordings.
- Set the Storage Path to the folder you just created.
- Enable recording and set the location of your World of Warcraft logs folder.
- Modify any other settings as desired.
3. Click the Scene button and configure the OBS scene and recording settings.
- Select your desired output resolution.
- Add your speakers and/or microphone if you want to include audio.
- Recommend selecting a hardware encoder, if available.
- Modify any other settings as desired.
5. Install the required combat logging addon, enabling advanced combat logging when prompted.
- Retail: SimpleCombatLogger ([CurseForge](https://www.curseforge.com/wow/addons/simplecombatlogger), [Wago](https://addons.wago.io/addons/simplecombatlogger)).
- Classic: AutoCombatLogger ([CurseForge](https://www.curseforge.com/wow/addons/autocombatlogger), [Wago](https://addons.wago.io/addons/autocombatlogger)).
- Classic Era: AutoCombatLogger ([CurseForge](https://www.curseforge.com/wow/addons/autocombatlogger), [Wago](https://addons.wago.io/addons/autocombatlogger)).
# Supported Platforms
| OS | Support |
|---|---|
| Windows | Yes |
| Mac | No |
| Linux | No |
| Flavour | Support |
|---|---|
| Retail | Yes |
| MoP Classic | Yes |
| Classic Era | SoD Raids Only |
# Testing It Works
You can test that Warcraft Recorder works by clicking the test icon with World of Warcraft running after you have completed the above setup steps. This runs a short test of the recording function.
# Bug Reports & Suggestions
Please create an issue, I will get to it eventually. Bear in mind maintaining this is a hobby for me, so it may take me some time to comment. If you think you can improve something, feel free to submit a PR.
I've created a dedicated discord for this project, feel free to join [here](https://discord.gg/NPha7KdjVk).
# Contributing
If you're interested in getting involved please drop me a message on discord and I can give you access to our development channel. Also see [contributing](https://github.com/aza547/wow-recorder/blob/main/docs/CONTRIBUTING.md) docs.
# Mentions
The recording done by Warcraft Recorder is made possible by packaging up [OBS](https://obsproject.com/). We wouldn't stand a chance at providing something useful without it. Big thanks to the OBS developers.
The app is built with [Electron](https://www.electronjs.org/) and [React](https://react.dev/), using the boilerplate provided by the [ERB](https://electron-react-boilerplate.js.org/) project.
Drawing overlay created using [Excalidraw](https://github.com/excalidraw/excalidraw).
================================================
FILE: assets/assets.d.ts
================================================
type Styles = Record<string, string>;
declare module '*.svg' {
const content: string;
export default content;
}
declare module '*.png' {
const content: string;
export default content;
}
declare module '*.jpg' {
const content: string;
export default content;
}
declare module '*.mp3' {
const src: string;
export default src;
}
declare module '*.scss' {
const content: Styles;
export default content;
}
declare module '*.sass' {
const content: Styles;
export default content;
}
declare module '*.css' {
const content: Styles;
export default content;
}
================================================
FILE: assets/entitlements.mac.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.allow-jit</key>
<true/>
</dict>
</plist>
================================================
FILE: docs/CONTRIBUTING.md
================================================
# Contributing
The below steps describe development on Windows. The app is currently not supported on other operating systems.
## Architecture
Once I drew the structure of the application in Excalidraw. You can see that below. It's a rough overview of the key parts and may be a useful overview for any interested developers.

You can find the source in the `design.excalidraw` file in this directory.
## Start in Development Mode
Development mode benefits from the infrastructure offered by [electron-react-boilerplate](https://github.com/electron-react-boilerplate/electron-react-boilerplate). You can read more about it on their [docs](https://electron-react-boilerplate.js.org/). It allows for a very quick development cycle, access to chrome dev tools, and hot reloading of the app on saving new changes.
1. Install the latest version of Node.js version (latest at time of writing is 20.4.0) from [here](https://nodejs.org/en/).
1. Clone a copy of the [wow-recorder](https://github.com/aza547/wow-recorder) codebase.
1. Change into the checkout directory.
1. Run `npm install` on the command line to install required node packages.
1. Run `npm start` to launch the application in development mode.
## Building, Packaging and Releasing
> As of 3.3.1, we have CI builds for all commits to main.
> As of 6.8.2, only the tests run in the CI. Can't build in the CI now that we're signing the exe.
1. Build the electron application.
1. Update the version number in `./release/app/package.json` if appropriate.
1. Run `npm run package` to build the electron application.
1. Install the .exe and run the tests to make sure you've not broken something crass.
1. With WarcraftRecorder open, run: `python .\resources\test-scripts\all_tests.py`.
1. Manually check the app behaves as expected while this runs.
1. Recordings are created.
1. Appropriate metadata is created.
1. User experience has not degraded.
1. Share the application.
1. Update the CHANGELOG.md with the new version number and change details.
1. Commit and push all changes.
1. Tag a release on GitHub and attach the built files:
- `./release/build/WarcraftRecorder-Setup-X.Y.Z.exe` to enable installation.
- `./release/build/latest.yml` to allow the auto updater to function.
- `./release/build/WarcraftRecorder-Setup-6.8.0.exe.blockmap` to allow the auto updater to function.
## Tests
1. Run `npm test` to run the UTs.
1. These are `jest` based unit tests.
2. Note: This is a WIP - the UTs currently are not useful.
2. To run end-to-end tests (requires some hardcoded path updates):
* All tests: `python .\resources\test-scripts\all_tests.py`.
* Individual test: `python .\resources\test-scripts\retail_mythic_plus.py`.
## Debugging Mode
You can use VSCode's JavaScript Debug terminal to step through the code, add breakpoints, view variables and the other IDE features.
1. Go to file, new terminal.
1. Click the arrow next to the "+" icon.
1. Select "JavaScrtip Debug Terminal". See below image.
1. Run the application in development mode as per above instructions (i.e. `npm start`).
1. Enjoy the ability to use the IDE features.
<img src="https://i.imgur.com/zFIaGHa.png" width="200">
## Debugging in Production with Dev Tools
From [here](https://electron-react-boilerplate.js.org/docs/packaging).
`npx cross-env DEBUG_PROD=true npm run package`
## Building OSN
> Advice is not to build this and just get it from the folks at Streamlabs. I built it once, it was a total faff.
> If you really need to build it, you can probably find some useful notes in the history of this doc.
> This is hosted by streamlabs here (note version number):
> - https://s3-us-west-2.amazonaws.com/obsstudionodes3.streamlabs.com/osn-0.23.59-release-win64.tar.gz
The above is no longer true, I'm now rebuilding OSN to add force stop functionality. Follow the OSN build instructions and then do:
`tar -czvf osn-0.25.34wcr-release-win64.tar.gz -C build/distribute/ obs-studio-node` to get an archive ready for use.
Below are various additional OSN resources:
- [Example](https://github.com/Envek/obs-studio-node-example)
- [Community Docs](https://github.com/hrueger/obs-studio-node-docs)
- [Streamlabs Desktop](https://github.com/stream-labs/desktop)
- [AdvancedRecordingFactory API](https://github.com/stream-labs/obs-studio-node/pull/1128)
- [OSN Tests](https://github.com/stream-labs/obs-studio-node/tree/staging/tests/osn-tests/src)
## Microsoft Stamp of Approval
If we just build a .exe and release it Windows will warn it may be dangerous. Could resolve this buy purchasing a certificate from a CA, but it costs a fortune. Read more about it in this [issue](https://github.com/aza547/wow-recorder/issues/11).
1. Submit it for analysis [here](https://www.microsoft.com/en-us/wdsi/filesubmission) after releasing it to make that warning go away.
1. Select "Microsoft Defender Smartscreen" as the security product.
1. "Company name" - just put your own name.
1. "Do you have a Microsoft support case number?" - No.
1. Leave next few fields blank/unchanged.
1. Select & upload the .exe.
1. "What do you believe this file is?" - Incorrectly detected as malware/malicious
1. Detection name - "WarcraftRecorder.Setup.2.0.1.exe"
1. "Additional information" - whatever, I'm sure no one will read it.
1. This isn't instant but seems to get resolved within 24 hours, that seems good enough.
================================================
FILE: docs/Colors.md
================================================
Dark blue background: #1A233A (set on HTML in app.css)
Old lighter Blue background: #272e48
Extra dark blue? (bottom/top bars): #182035
Extra extra dark blue (used in settings boxes): #141b2d
Red: #bb4420
================================================
FILE: docs/FilterAudio.md
================================================
# How to Filter Recorded Audio
The below is a method to allow Warcraft Recorder to record the system audio while ignoring discord audio. Thanks to Maxi for submitting these instructions.
## Instructions
1. Download the latest version of [Voicemeeter Banana](https://vb-audio.com/Voicemeeter/banana.htm).
1. Install and reboot your computer.
1. Set up the input and output for Voicemeeter Banana in the Windows audio settings playback.
- Right click on 'Voicemeeter Input' and select 'Default Device'.
- Right click your desired speaker and select 'Default Communications Device'.
<img src="https://i.imgur.com/phCnBh1.png" width="300">
1. Switch to the Recordings tab
- Right click on 'Voicemeeter Output' and select 'Default Device'.
- Right click your desired microphone and select 'Default Communications Device'.
<img src="https://i.imgur.com/DJBUimL.png" width="300">
1. Open Voicemeeter Banana.
- Click on the menu button in the top right of the program.
- Set to automatically launch with Windows.
<img src="https://i.imgur.com/G5FSdaq.png" width="500">
1. Set your desired speaker output within Voicemeeter.
- Click A1 in the top right and select your speaker.
<img src="https://i.imgur.com/eEqM6P3.png" width="500">
1. Open Discord settings and go to Voice and Video tab.
- Choose 'Voice Meeter Aux Input' as your 'Output Device'.
<img src="https://i.imgur.com/JHU4yof.png" width="500">
1. Open Warcraft Recorder settings and set the audio device to VoiceMeeter.
<img src="https://i.imgur.com/rS39pGv.png" width="500">
1. Enjoy!
## Tips
- Rename your output columns so you don’t get confused on which is which. Just right click the area circled. VAIO is your nonfiltered audio and AUX is the filtered discord audio.
<img src="https://i.imgur.com/NpmVlix.png" width="500">
- You can have multiple audio outputs for different speaker/headset configs by selecting them in the A1 A2 A3 in the top right. Put your secondary setup on A2 and then whenever you want to swap from headset to speaker or whatever you have setup select them.
<img src="https://i.imgur.com/GswWyKr.png" width="500">
================================================
FILE: docs/Localisation.md
================================================
# How to Add A Language
1. Copy the file `src/localisation/english.ts` to another language e.g. `src/localisation/klingon.ts`.
2. Replace all the strings with the appropriate translation.
3. Add an entry in the `Languages` enum in `src/localisation/types.ts`. For example:
```
enum Language {
ENGLISH = 'English',
KOREAN = 'Korean',
...
KLINGON = 'Klingon',
}
```
4. Add an entry to the data variable in `src/localisation/translations.ts` to point to the new translations. For example:
```
import KlingonTranslations from './Klingon';
...
const data: LocalizationDataType = {
[Language.ENGLISH]: EnglishTranslations,
[Language.KOREAN]: KoreanTranslations,
...
[Language.KLINGON]: KlingonTranslations,
};
```
5. Optionally update the VideoFilter class to include search strings for the new language. These won't translate automatically. If you skip this the search bar won't be usable in the new language.
6. That's it. You should now be able to select the new language in the settings page.
# How To Add User-Facing Strings
I've just added localisation support to the client. All users facing text in the application should now be populated via the localisation infrastructure.
That means no hard coded english user facing strings, unless there is good reason for it. To add a new localised phrase through the you must:
1. Add it to the `Phrase` enum in `src/localisation/types.ts`. For example:
```
enum Phrase {
...
SettingsDisabledText
}
```
2. Add a translation to each language the client supports (see `src/localisation/english.ts` for example). This is well typed, so you should see warnings if you miss this. For example:
```
...
[Phrase.SettingsDisabledText]: 'Invalid retail log path.',
```
3. In the appropriate component where you want to display the phrase, render it using the `getLocalPhrase(language, phrase)` function. For example:
```
<TextBanner>
{getLocalePhrase(appState.language, Phrase.SettingsDisabledText)}
</TextBanner>
```
Obviously this doesn't apply to anything internal. That should all remain in english.
================================================
FILE: docs/LocateLogDirectory.md
================================================
# How to find your World of Warcraft combat log directory
It can be a bit confusing to find the right directory where your Warcraft combat logs are saved if you've never dealt with them before, so here's a quick step-by-step guide to how to find it for any flavour of Warcraft.
# Instructions
1. Open the Battle.net Launcher
2. Select the version of WoW you want to record
3. Click the gear-icon to the right of the "_Play_" button
4. Click on "_Show in Explorer_"<br/>

5. Double-click on the flavour of WoW you want to record. One of `_classic`, `_retail_`, `_ptr_`, or `_beta_`<br/>

6. Double-click the folder `Logs`<br/>

7. Right click the address bar in Explorer and copy the location<br/>

8. This location can be pasted into the folder selection box in Warcraft Recorder
# Tips
If you're using Windows 11, you can stop at step 6 and simply right click the `Logs` folder and select "_Copy as path_".
================================================
FILE: docs/SettingsReference.md
================================================
# Settings Reference
This is intended to be a quick reference to help understand the configuration we can apply to libobs via the obs-studio-node package. It's been collated using the following functions:
- OBS_settings_getListCategories
- OBS_settings_getSettings
## Categories
```
[
'General',
'Stream',
'Output',
'Audio',
'Video',
'Hotkeys',
'Advanced'
]
```
## General
```
{
data: [
{
nameSubCategory: 'Output',
parameters: [
{
name: 'WarnBeforeStartingStream',
type: 'OBS_PROPERTY_BOOL',
description: 'Show confirmation dialog when starting streams',
subType: '',
currentValue: false,
values: [],
visible: true,
enabled: true,
masked: false
},
{
name: 'WarnBeforeStoppingStream',
type: 'OBS_PROPERTY_BOOL',
description: 'Show confirmation dialog when stopping streams',
subType: '',
currentValue: false,
values: [],
visible: true,
enabled: true,
masked: false
},
{
name: 'RecordWhenStreaming',
type: 'OBS_PROPERTY_BOOL',
description: 'Automatically record when streaming',
subType: '',
currentValue: false,
values: [],
visible: true,
enabled: true,
masked: false
},
{
name: 'KeepRecordingWhenStreamStops',
type: 'OBS_PROPERTY_BOOL',
description: 'Keep recording when stream stops',
subType: '',
currentValue: false,
values: [],
visible: true,
enabled: true,
masked: false
},
{
name: 'ReplayBufferWhileStreaming',
type: 'OBS_PROPERTY_BOOL',
description: 'Automatically start replay buffer when streaming',
subType: '',
currentValue: false,
values: [],
visible: true,
enabled: true,
masked: false
},
{
name: 'KeepReplayBufferStreamStops',
type: 'OBS_PROPERTY_BOOL',
description: 'Keep replay buffer active when stream stops',
subType: '',
currentValue: false,
values: [],
visible: true,
enabled: true,
masked: false
}
]
},
{
nameSubCategory: 'Source Alignement Snapping',
parameters: [
{
name: 'SnappingEnabled',
type: 'OBS_PROPERTY_BOOL',
description: 'Enable',
subType: '',
currentValue: true,
values: [],
visible: true,
enabled: true,
masked: false
},
{
name: 'SnapDistance',
type: 'OBS_PROPERTY_DOUBLE',
description: 'Snap Sensitivity',
subType: '',
currentValue: 10,
minVal: 0,
maxVal: 100,
stepVal: 0.5,
values: [],
visible: true,
enabled: true,
masked: false
},
{
name: 'ScreenSnapping',
type: 'OBS_PROPERTY_BOOL',
description: 'Snap Sources to edge of screen',
subType: '',
currentValue: true,
values: [],
visible: true,
enabled: true,
masked: false
},
{
name: 'SourceSnapping',
type: 'OBS_PROPERTY_BOOL',
description: 'Snap Sources to other sources',
subType: '',
currentValue: true,
values: [],
visible: true,
enabled: true,
masked: false
},
{
name: 'CenterSnapping',
type: 'OBS_PROPERTY_BOOL',
description: 'Snap Sources to horizontal and vertical center',
subType: '',
currentValue: false,
values: [],
visible: true,
enabled: true,
masked: false
}
]
},
{
nameSubCategory: 'Projectors',
parameters: [
{
name: 'HideProjectorCursor',
type: 'OBS_PROPERTY_BOOL',
description: 'Hide cursor over projectors',
subType: '',
currentValue: false,
values: [],
visible: true,
enabled: true,
masked: false
},
{
name: 'ProjectorAlwaysOnTop',
type: 'OBS_PROPERTY_BOOL',
description: 'Make projectors always on top',
subType: '',
currentValue: false,
values: [],
visible: true,
enabled: true,
masked: false
},
{
name: 'SaveProjectors',
type: 'OBS_PROPERTY_BOOL',
description: 'Save projectors on exit',
subType: '',
currentValue: false,
values: [],
visible: true,
enabled: true,
masked: false
}
]
},
{
nameSubCategory: 'System Tray',
parameters: [
{
name: 'SysTrayEnabled',
type: 'OBS_PROPERTY_BOOL',
description: 'Enable',
subType: '',
currentValue: false,
values: [],
visible: true,
enabled: true,
masked: false
},
{
name: 'SysTrayWhenStarted',
type: 'OBS_PROPERTY_BOOL',
description: 'Minimize to system tray when started',
subType: '',
currentValue: false,
values: [],
visible: true,
enabled: true,
masked: false
},
{
name: 'SysTrayMinimizeToTray',
type: 'OBS_PROPERTY_BOOL',
description: 'Always minimize to system tray instead of task bar',
subType: '',
currentValue: false,
values: [],
visible: true,
enabled: true,
masked: false
}
]
}
],
type: 0
}
```
## Video
```
{
data: [
{
nameSubCategory: 'Untitled',
parameters: [
{
name: 'Base',
type: 'OBS_INPUT_RESOLUTION_LIST',
description: 'Base (Canvas) Resolution',
subType: 'OBS_COMBO_FORMAT_STRING',
currentValue: '1280x720',
values: [
{ '1920x1080': '1920x1080' },
{ '1280x720': '1280x720' },
{ '1080x1920': '1080x1920' }
],
visible: true,
enabled: false,
masked: false
},
{
name: 'Output',
type: 'OBS_INPUT_RESOLUTION_LIST',
description: 'Output (Scaled) Resolution',
subType: 'OBS_COMBO_FORMAT_STRING',
currentValue: '640x360',
values: [
{ '1280x720': '1280x720' },
{ '1024x576': '1024x576' },
{ '960x540': '960x540' },
{ '852x480': '852x480' },
{ '768x432': '768x432' },
{ '730x410': '730x410' },
{ '640x360': '640x360' },
{ '568x320': '568x320' },
{ '512x288': '512x288' },
{ '464x260': '464x260' },
{ '426x240': '426x240' }
],
visible: true,
enabled: false,
masked: false
},
{
name: 'ScaleType',
type: 'OBS_PROPERTY_LIST',
description: 'Downscale Filter',
subType: 'OBS_COMBO_FORMAT_STRING',
currentValue: 'bicubic',
values: [
{ 'Bilinear (Fastest, but blurry if scaling)': 'bilinear' },
{ 'Bicubic (Sharpened scaling, 16 samples)': 'bicubic' },
{ 'Lanczos (Sharpened scaling, 32 samples)': 'lanczos' }
],
visible: true,
enabled: false,
masked: false
},
{
name: 'FPSType',
type: 'OBS_PROPERTY_LIST',
description: 'FPS Type',
subType: 'OBS_COMBO_FORMAT_STRING',
currentValue: 'Common FPS Values',
values: [
{ 'Common FPS Values': 'Common FPS Values' },
{ 'Integer FPS Value': 'Integer FPS Value' },
{ 'Fractional FPS Value': 'Fractional FPS Value' }
],
visible: true,
enabled: false,
masked: false
},
{
name: 'FPSCommon',
type: 'OBS_PROPERTY_LIST',
description: 'Common FPS Values',
subType: 'OBS_COMBO_FORMAT_STRING',
currentValue: '60',
values: [
{ '10': '10' },
{ '20': '20' },
{ '24 NTSC': '24 NTSC' },
{ '29.97': '29.97' },
{ '30': '30' },
{ '48': '48' },
{ '59.94': '59.94' },
{ '60': '60' }
],
visible: true,
enabled: false,
masked: false
}
]
}
],
type: 0
}
```
## Output
```
{
data: [
{
nameSubCategory: 'Untitled',
parameters: [
{
name: 'Mode',
type: 'OBS_PROPERTY_LIST',
description: 'Output Mode',
subType: 'OBS_COMBO_FORMAT_STRING',
currentValue: 'Advanced',
values: [ { Simple: 'Simple' }, { Advanced: 'Advanced' } ],
visible: true,
enabled: false,
masked: false
}
]
},
{
nameSubCategory: 'Streaming',
parameters: [
{
name: 'TrackIndex',
type: 'OBS_PROPERTY_LIST',
description: 'Audio Track',
subType: 'OBS_COMBO_FORMAT_STRING',
currentValue: '1',
values: [
{ '1': '1' },
{ '2': '2' },
{ '3': '3' },
{ '4': '4' },
{ '5': '5' },
{ '6': '6' }
],
visible: true,
enabled: false,
masked: false
},
{
name: 'VodTrackEnabled',
type: 'OBS_PROPERTY_BOOL',
description: 'Twitch VOD',
subType: '',
currentValue: false,
values: [],
visible: true,
enabled: false,
masked: false
},
{
name: 'Encoder',
type: 'OBS_PROPERTY_LIST',
description: 'Encoder',
subType: 'OBS_COMBO_FORMAT_STRING',
currentValue: 'obs_x264',
values: [
{ 'Software (x264)': 'obs_x264' },
{ 'Hardware (NVENC)': 'ffmpeg_nvenc' },
{ 'Hardware (NVENC) (new)': 'jim_nvenc' }
],
visible: true,
enabled: false,
masked: false
},
{
name: 'ApplyServiceSettings',
type: 'OBS_PROPERTY_BOOL',
description: 'Enforce streaming service encoder settings',
subType: '',
currentValue: true,
values: [],
visible: true,
enabled: false,
masked: false
},
{
name: 'Rescale',
type: 'OBS_PROPERTY_BOOL',
description: 'Rescale Output',
subType: '',
currentValue: false,
values: [],
visible: true,
enabled: false,
masked: false
},
{
name: 'rate_control',
type: 'OBS_PROPERTY_LIST',
description: 'Rate Control',
subType: 'OBS_COMBO_FORMAT_STRING',
currentValue: 'CBR',
values: [
{ CBR: 'CBR' },
{ ABR: 'ABR' },
{ VBR: 'VBR' },
{ CRF: 'CRF' }
],
visible: true,
enabled: false,
masked: false
},
{
name: 'bitrate',
type: 'OBS_PROPERTY_INT',
description: 'Bitrate',
subType: 'OBS_COMBO_FORMAT_STRING',
currentValue: 2500,
minVal: 50,
maxVal: 10000000,
stepVal: 50,
values: [
{ CBR: 'CBR' },
{ ABR: 'ABR' },
{ VBR: 'VBR' },
{ CRF: 'CRF' }
],
visible: true,
enabled: false,
masked: false
},
{
name: 'use_bufsize',
type: 'OBS_PROPERTY_BOOL',
description: 'Use Custom Buffer Size',
subType: 'OBS_COMBO_FORMAT_STRING',
currentValue: false,
values: [
{ CBR: 'CBR' },
{ ABR: 'ABR' },
{ VBR: 'VBR' },
{ CRF: 'CRF' }
],
visible: true,
enabled: false,
masked: false
},
{
name: 'buffer_size',
type: 'OBS_PROPERTY_INT',
description: 'Buffer Size',
subType: 'OBS_COMBO_FORMAT_STRING',
currentValue: 2500,
minVal: 0,
maxVal: 10000000,
stepVal: 1,
values: [
{ CBR: 'CBR' },
{ ABR: 'ABR' },
{ VBR: 'VBR' },
{ CRF: 'CRF' }
],
visible: false,
enabled: false,
masked: false
},
{
name: 'crf',
type: 'OBS_PROPERTY_INT',
description: 'CRF',
subType: 'OBS_COMBO_FORMAT_STRING',
currentValue: 23,
minVal: 0,
maxVal: 51,
stepVal: 1,
values: [
{ CBR: 'CBR' },
{ ABR: 'ABR' },
{ VBR: 'VBR' },
{ CRF: 'CRF' }
],
visible: false,
enabled: false,
masked: false
},
{
name: 'keyint_sec',
type: 'OBS_PROPERTY_INT',
description: 'Keyframe Interval (seconds, 0=auto)',
subType: 'OBS_COMBO_FORMAT_STRING',
currentValue: 0,
minVal: 0,
maxVal: 20,
stepVal: 1,
values: [
{ CBR: 'CBR' },
{ ABR: 'ABR' },
{ VBR: 'VBR' },
{ CRF: 'CRF' }
],
visible: true,
enabled: false,
masked: false
},
{
name: 'preset',
type: 'OBS_PROPERTY_LIST',
description: 'CPU Usage Preset (higher = less CPU)',
subType: 'OBS_COMBO_FORMAT_STRING',
currentValue: 'veryfast',
values: [
{ ultrafast: 'ultrafast' },
{ superfast: 'superfast' },
{ veryfast: 'veryfast' },
{ faster: 'faster' },
{ fast: 'fast' },
{ medium: 'medium' },
{ slow: 'slow' },
{ slower: 'slower' },
{ veryslow: 'veryslow' },
{ placebo: 'placebo' }
],
visible: true,
enabled: false,
masked: false
},
{
name: 'profile',
type: 'OBS_PROPERTY_LIST',
description: 'Profile',
subType: 'OBS_COMBO_FORMAT_STRING',
currentValue: '',
values: [
{ '(None)': '' },
{ baseline: 'baseline' },
{ main: 'main' },
{ high: 'high' }
],
visible: true,
enabled: false,
masked: false
},
{
name: 'tune',
type: 'OBS_PROPERTY_LIST',
description: 'Tune',
subType: 'OBS_COMBO_FORMAT_STRING',
currentValue: '',
values: [
{ '(None)': '' },
{ film: 'film' },
{ animation: 'animation' },
{ grain: 'grain' },
{ stillimage: 'stillimage' },
{ psnr: 'psnr' },
{ ssim: 'ssim' },
{ fastdecode: 'fastdecode' },
{ zerolatency: 'zerolatency' }
],
visible: true,
enabled: false,
masked: false
},
{
name: 'x264opts',
type: 'OBS_PROPERTY_TEXT',
description: 'x264 Options (separated by space)',
subType: 'OBS_COMBO_FORMAT_STRING',
currentValue: '',
values: [
{ '(None)': '' },
{ film: 'film' },
{ animation: 'animation' },
{ grain: 'grain' },
{ stillimage: 'stillimage' },
{ psnr: 'psnr' },
{ ssim: 'ssim' },
{ fastdecode: 'fastdecode' },
{ zerolatency: 'zerolatency' }
],
visible: true,
enabled: false,
masked: false
},
{
name: 'repeat_headers',
type: 'OBS_PROPERTY_BOOL',
description: 'repeat_headers',
subType: 'OBS_COMBO_FORMAT_STRING',
currentValue: false,
values: [
{ '(None)': '' },
{ film: 'film' },
{ animation: 'animation' },
{ grain: 'grain' },
{ stillimage: 'stillimage' },
{ psnr: 'psnr' },
{ ssim: 'ssim' },
{ fastdecode: 'fastdecode' },
{ zerolatency: 'zerolatency' }
],
visible: false,
enabled: false,
masked: false
}
]
},
{
nameSubCategory: 'Recording',
parameters: [
{
name: 'RecType',
type: 'OBS_PROPERTY_LIST',
description: 'Type',
subType: 'OBS_COMBO_FORMAT_STRING',
currentValue: 'Standard',
values: [ { Standard: 'Standard' } ],
visible: true,
enabled: false,
masked: false
},
{
name: 'RecFilePath',
type: 'OBS_PROPERTY_PATH',
description: 'Recording Path',
subType: '',
currentValue: 'D:\\wow-recorder-files\\.temp',
values: [],
visible: true,
enabled: false,
masked: false
},
{
name: 'RecFileNameWithoutSpace',
type: 'OBS_PROPERTY_BOOL',
description: 'Generate File Name without Space',
subType: '',
currentValue: false,
values: [],
visible: true,
enabled: false,
masked: false
},
{
name: 'RecFormat',
type: 'OBS_PROPERTY_LIST',
description: 'Recording Format',
subType: 'OBS_COMBO_FORMAT_STRING',
currentValue: 'mp4',
values: [
{ mp4: 'mp4' },
{ flv: 'flv' },
{ mov: 'mov' },
{ mkv: 'mkv' },
{ ts: 'ts' },
{ m3u8: 'm3u8' }
],
visible: true,
enabled: false,
masked: false
},
{
name: 'RecTracks',
type: 'OBS_PROPERTY_BITMASK',
description: 'Audio Track',
subType: '',
currentValue: 63,
minVal: -200,
maxVal: 200,
stepVal: 1,
values: [],
visible: true,
enabled: true,
masked: false
},
{
name: 'RecEncoder',
type: 'OBS_PROPERTY_LIST',
description: 'Recording',
subType: 'OBS_COMBO_FORMAT_STRING',
currentValue: 'jim_nvenc',
values: [
{ 'Use stream encoder': 'none' },
{ 'Software (x264)': 'obs_x264' },
{ 'Hardware (NVENC)': 'ffmpeg_nvenc' },
{ 'Hardware (NVENC) (new)': 'jim_nvenc' }
],
visible: true,
enabled: false,
masked: false
},
{
name: 'RecRescale',
type: 'OBS_PROPERTY_BOOL',
description: 'Rescale Output',
subType: '',
currentValue: false,
values: [],
visible: false,
enabled: false,
masked: false
},
{
name: 'RecMuxerCustom',
type: 'OBS_PROPERTY_EDIT_TEXT',
description: 'Custom Muxer Settings',
subType: '',
currentValue: '',
values: [],
visible: true,
enabled: false,
masked: false
},
{
name: 'Recrate_control',
type: 'OBS_PROPERTY_LIST',
description: 'Rate Control',
subType: 'OBS_COMBO_FORMAT_STRING',
currentValue: 'VBR',
values: [
{ CBR: 'CBR' },
{ CQP: 'CQP' },
{ VBR: 'VBR' },
{ Lossless: 'lossless' }
],
visible: true,
enabled: false,
masked: false
},
{
name: 'Recbitrate',
type: 'OBS_PROPERTY_INT',
description: 'Bitrate',
subType: 'OBS_COMBO_FORMAT_STRING',
currentValue: 15360,
minVal: 50,
maxVal: 300000,
stepVal: 50,
values: [
{ CBR: 'CBR' },
{ CQP: 'CQP' },
{ VBR: 'VBR' },
{ Lossless: 'lossless' }
],
visible: true,
enabled: false,
masked: false
},
{
name: 'Recmax_bitrate',
type: 'OBS_PROPERTY_INT',
description: 'Max Bitrate',
subType: 'OBS_COMBO_FORMAT_STRING',
currentValue: 300000,
minVal: 50,
maxVal: 300000,
stepVal: 50,
values: [
{ CBR: 'CBR' },
{ CQP: 'CQP' },
{ VBR: 'VBR' },
{ Lossless: 'lossless' }
],
visible: true,
enabled: false,
masked: false
},
{
name: 'Reccqp',
type: 'OBS_PROPERTY_INT',
description: 'CQ Level',
subType: 'OBS_COMBO_FORMAT_STRING',
currentValue: 20,
minVal: 1,
maxVal: 30,
stepVal: 1,
values: [
{ CBR: 'CBR' },
{ CQP: 'CQP' },
{ VBR: 'VBR' },
{ Lossless: 'lossless' }
],
visible: false,
enabled: false,
masked: false
},
{
name: 'Reckeyint_sec',
type: 'OBS_PROPERTY_INT',
description: 'Keyframe Interval (seconds, 0=auto)',
subType: 'OBS_COMBO_FORMAT_STRING',
currentValue: 0,
minVal: 0,
maxVal: 10,
stepVal: 1,
values: [
{ CBR: 'CBR' },
{ CQP: 'CQP' },
{ VBR: 'VBR' },
{ Lossless: 'lossless' }
],
visible: true,
enabled: false,
masked: false
},
{
name: 'Recpreset',
type: 'OBS_PROPERTY_LIST',
description: 'Preset',
subType: 'OBS_COMBO_FORMAT_STRING',
currentValue: 'hq',
values: [
{ 'Max Quality': 'mq' },
{ Quality: 'hq' },
{ Performance: 'default' },
{ 'Max Performance': 'hp' },
{ 'Low-Latency': 'll' },
{ 'Low-Latency Quality': 'llhq' },
{ 'Low-Latency Performance': 'llhp' }
],
visible: true,
enabled: false,
masked: false
},
{
name: 'Recprofile',
type: 'OBS_PROPERTY_LIST',
description: 'Profile',
subType: 'OBS_COMBO_FORMAT_STRING',
currentValue: 'high',
values: [
{ high: 'high' },
{ main: 'main' },
{ baseline: 'baseline' }
],
visible: true,
enabled: false,
masked: false
},
{
name: 'Reclookahead',
type: 'OBS_PROPERTY_BOOL',
description: 'Look-ahead',
subType: 'OBS_COMBO_FORMAT_STRING',
currentValue: false,
values: [
{ high: 'high' },
{ main: 'main' },
{ baseline: 'baseline' }
],
visible: true,
enabled: false,
masked: false
},
{
name: 'Recrepeat_headers',
type: 'OBS_PROPERTY_BOOL',
description: 'repeat_headers',
subType: 'OBS_COMBO_FORMAT_STRING',
currentValue: false,
values: [
{ high: 'high' },
{ main: 'main' },
{ baseline: 'baseline' }
],
visible: false,
enabled: false,
masked: false
},
{
name: 'Recpsycho_aq',
type: 'OBS_PROPERTY_BOOL',
description: 'Psycho Visual Tuning',
subType: 'OBS_COMBO_FORMAT_STRING',
currentValue: true,
values: [
{ high: 'high' },
{ main: 'main' },
{ baseline: 'baseline' }
],
visible: true,
enabled: false,
masked: false
},
{
name: 'Recgpu',
type: 'OBS_PROPERTY_INT',
description: 'GPU',
subType: 'OBS_COMBO_FORMAT_STRING',
currentValue: 0,
minVal: 0,
maxVal: 8,
stepVal: 1,
values: [
{ high: 'high' },
{ main: 'main' },
{ baseline: 'baseline' }
],
visible: true,
enabled: false,
masked: false
},
{
name: 'Recbf',
type: 'OBS_PROPERTY_INT',
description: 'Max B-frames',
subType: 'OBS_COMBO_FORMAT_STRING',
currentValue: 2,
minVal: 0,
maxVal: 4,
stepVal: 1,
values: [
{ high: 'high' },
{ main: 'main' },
{ baseline: 'baseline' }
],
visible: true,
enabled: false,
masked: false
}
]
},
{
nameSubCategory: 'Audio - Track 1',
parameters: [
{
name: 'Track1Bitrate',
type: 'OBS_PROPERTY_LIST',
description: 'Audio Bitrate',
subType: 'OBS_COMBO_FORMAT_STRING',
currentValue: '160',
values: [
{ '64': '64' },
{ '96': '96' },
{ '128': '128' },
{ '160': '160' },
{ '192': '192' },
{ '224': '224' },
{ '256': '256' },
{ '288': '288' },
{ '320': '320' }
],
visible: true,
enabled: false,
masked: false
},
{
name: 'Track1Name',
type: 'OBS_PROPERTY_EDIT_TEXT',
description: 'Name',
subType: '',
currentValue: 'Mixed: all sources',
values: [],
visible: true,
enabled: false,
masked: false
}
]
},
{
nameSubCategory: 'Audio - Track 2',
parameters: [
{
name: 'Track2Bitrate',
type: 'OBS_PROPERTY_LIST',
description: 'Audio Bitrate',
subType: 'OBS_COMBO_FORMAT_STRING',
currentValue: '160',
values: [
{ '64': '64' },
{ '96': '96' },
{ '128': '128' },
{ '160': '160' },
{ '192': '192' },
{ '224': '224' },
{ '256': '256' },
{ '288': '288' },
{ '320': '320' }
],
visible: true,
enabled: false,
masked: false
},
{
name: 'Track2Name',
type: 'OBS_PROPERTY_EDIT_TEXT',
description: 'Name',
subType: '',
currentValue: 'Microphone (3- G533 Gaming Headset)',
values: [],
visible: true,
enabled: false,
masked: false
}
]
},
{
nameSubCategory: 'Audio - Track 3',
parameters: [
{
name: 'Track3Bitrate',
type: 'OBS_PROPERTY_LIST',
description: 'Audio Bitrate',
subType: 'OBS_COMBO_FORMAT_STRING',
currentValue: '160',
values: [
{ '64': '64' },
{ '96': '96' },
{ '128': '128' },
{ '160': '160' },
{ '192': '192' },
{ '224': '224' },
{ '256': '256' },
{ '288': '288' },
{ '320': '320' }
],
visible: true,
enabled: false,
masked: false
},
{
name: 'Track3Name',
type: 'OBS_PROPERTY_EDIT_TEXT',
description: 'Name',
subType: '',
currentValue: 'BenQ GW2480 (NVIDIA High Definition Audio)',
values: [],
visible: true,
enabled: false,
masked: false
}
]
},
{
nameSubCategory: 'Audio - Track 4',
parameters: [
{
name: 'Track4Bitrate',
type: 'OBS_PROPERTY_LIST',
description: 'Audio Bitrate',
subType: 'OBS_COMBO_FORMAT_STRING',
currentValue: '160',
values: [
{ '64': '64' },
{ '96': '96' },
{ '128': '128' },
{ '160': '160' },
{ '192': '192' },
{ '224': '224' },
{ '256': '256' },
{ '288': '288' },
{ '320': '320' }
],
visible: true,
enabled: false,
masked: false
},
{
name: 'Track4Name',
type: 'OBS_PROPERTY_EDIT_TEXT',
description: 'Name',
subType: '',
currentValue: 'Digital Audio (S/PDIF) (High Definition Audio Device)',
values: [],
visible: true,
enabled: false,
masked: false
}
]
},
{
nameSubCategory: 'Audio - Track 5',
parameters: [
{
name: 'Track5Bitrate',
type: 'OBS_PROPERTY_LIST',
description: 'Audio Bitrate',
subType: 'OBS_COMBO_FORMAT_STRING',
currentValue: '160',
values: [
{ '64': '64' },
{ '96': '96' },
{ '128': '128' },
{ '160': '160' },
{ '192': '192' },
{ '224': '224' },
{ '256': '256' },
{ '288': '288' },
{ '320': '320' }
],
visible: true,
enabled: false,
masked: false
},
{
name: 'Track5Name',
type: 'OBS_PROPERTY_EDIT_TEXT',
description: 'Name',
subType: '',
currentValue: 'Speakers (3- G533 Gaming Headset)',
values: [],
visible: true,
enabled: false,
masked: false
}
]
},
{
nameSubCategory: 'Audio - Track 6',
parameters: [
{
name: 'Track6Bitrate',
type: 'OBS_PROPERTY_LIST',
description: 'Audio Bitrate',
subType: 'OBS_COMBO_FORMAT_STRING',
currentValue: '160',
values: [
{ '64': '64' },
{ '96': '96' },
{ '128': '128' },
{ '160': '160' },
{ '192': '192' },
{ '224': '224' },
{ '256': '256' },
{ '288': '288' },
{ '320': '320' }
],
visible: true,
enabled: false,
masked: false
},
{
name: 'Track6Name',
type: 'OBS_PROPERTY_EDIT_TEXT',
description: 'Name',
subType: '',
currentValue: 'BenQ GW2480 (NVIDIA High Definition Audio)',
values: [],
visible: true,
enabled: false,
masked: false
}
]
},
{
nameSubCategory: 'Replay Buffer',
parameters: [
{
name: 'RecRB',
type: 'OBS_PROPERTY_BOOL',
description: 'Enable Replay Buffer',
subType: '',
currentValue: true,
values: [],
visible: true,
enabled: false,
masked: false
},
{
name: 'RecRBTime',
type: 'OBS_PROPERTY_INT',
description: 'Maximum Replay Time (Seconds)',
subType: '',
currentValue: 20,
minVal: 0,
maxVal: 21599,
stepVal: 0,
values: [],
visible: true,
enabled: false,
masked: false
}
]
}
],
type: 1
}
```
## Audio
```
{
data: [
{
nameSubCategory: 'Untitled',
parameters: [
{
name: 'SampleRate',
type: 'OBS_PROPERTY_LIST',
description: 'Sample Rate (requires a restart)',
subType: 'OBS_COMBO_FORMAT_INT',
currentValue: 44100,
minVal: -200,
maxVal: 200,
stepVal: 1,
values: [ { '44.1khz': 44100 }, { '48khz': 48000 } ],
visible: true,
enabled: true,
masked: false
},
{
name: 'ChannelSetup',
type: 'OBS_PROPERTY_LIST',
description: 'Channels (requires a restart)',
subType: 'OBS_COMBO_FORMAT_STRING',
currentValue: 'Stereo',
values: [
{ Mono: 'Mono' },
{ Stereo: 'Stereo' },
{ '2.1': '2.1' },
{ '4.0': '4.0' },
{ '4.1': '4.1' },
{ '5.1': '5.1' },
{ '7.1': '7.1' }
],
visible: true,
enabled: true,
masked: false
}
]
}
],
type: 0
}
```
## Advanced
```
data: [
{
nameSubCategory: 'General',
parameters: [
{
name: 'ProcessPriority',
type: 'OBS_PROPERTY_LIST',
description: 'Process Priority',
subType: 'OBS_COMBO_FORMAT_STRING',
currentValue: 'Normal',
values: [
{ High: 'High' },
{ 'Above Normal': 'AboveNormal' },
{ Normal: 'Normal' },
{ 'Below Normal': 'BelowNormal' },
{ Idle: 'Idle' }
],
visible: true,
enabled: true,
masked: false
}
]
},
{
nameSubCategory: 'Video',
parameters: [
{
name: 'ColorFormat',
type: 'OBS_PROPERTY_LIST',
description: 'Color Format',
subType: 'OBS_COMBO_FORMAT_STRING',
currentValue: 'NV12',
values: [
{ NV12: 'NV12' },
{ I420: 'I420' },
{ I444: 'I444' },
{ RGB: 'RGB' }
],
visible: true,
enabled: true,
masked: false
},
{
name: 'ColorSpace',
type: 'OBS_PROPERTY_LIST',
description: 'YUV Color Space',
subType: 'OBS_COMBO_FORMAT_STRING',
currentValue: '601',
values: [ { '601': '601' }, { '709': '709' } ],
visible: true,
enabled: true,
masked: false
},
{
name: 'ColorRange',
type: 'OBS_PROPERTY_LIST',
description: 'YUV Color Range',
subType: 'OBS_COMBO_FORMAT_STRING',
currentValue: 'Partial',
values: [ { Partial: 'Partial' }, { Full: 'Full' } ],
visible: true,
enabled: true,
masked: false
},
{
name: 'ForceGPUAsRenderDevice',
type: 'OBS_PROPERTY_BOOL',
description: 'Force GPU as render device',
subType: '',
currentValue: true,
values: [],
visible: true,
enabled: true,
masked: false
}
]
},
{
nameSubCategory: 'Audio',
parameters: [
{
name: 'MonitoringDeviceName',
type: 'OBS_PROPERTY_LIST',
description: 'Audio Monitoring Device',
subType: 'OBS_COMBO_FORMAT_STRING',
currentValue: 'Default',
values: [
{ Default: 'Default' },
{
'BenQ GW2480 (NVIDIA High Definition Audio)': 'BenQ GW2480 (NVIDIA High Definition Audio)'
},
{
'Digital Audio (S/PDIF) (High Definition Audio Device)': 'Digital Audio (S/PDIF) (High Definition Audio Device)'
},
{
'Speakers (3- G533 Gaming Headset)': 'Speakers (3- G533 Gaming Headset)'
},
{
'BenQ GW2480 (NVIDIA High Definition Audio)': 'BenQ GW2480 (NVIDIA High Definition Audio)'
}
],
visible: true,
enabled: true,
masked: false
},
{
name: 'DisableAudioDucking',
type: 'OBS_PROPERTY_BOOL',
description: 'Disable Windows audio ducking',
subType: '',
currentValue: false,
values: [],
visible: true,
enabled: true,
masked: false
}
]
},
{
nameSubCategory: 'Recording',
parameters: [
{
name: 'FilenameFormatting',
type: 'OBS_PROPERTY_EDIT_TEXT',
description: 'Filename Formatting',
subType: '',
currentValue: '%CCYY-%MM-%DD %hh-%mm-%ss',
values: [],
visible: true,
enabled: true,
masked: false
},
{
name: 'OverwriteIfExists',
type: 'OBS_PROPERTY_BOOL',
description: 'Overwrite if file exists',
subType: '',
currentValue: false,
values: [],
visible: true,
enabled: true,
masked: false
}
]
},
{
nameSubCategory: 'Replay Buffer',
parameters: [
{
name: 'RecRBPrefix',
type: 'OBS_PROPERTY_EDIT_TEXT',
description: 'Replay Buffer Filename Prefix',
subType: '',
currentValue: 'Replay',
values: [],
visible: true,
enabled: true,
masked: false
},
{
name: 'RecRBSuffix',
type: 'OBS_PROPERTY_EDIT_TEXT',
description: 'Replay Buffer Filename Suffix',
subType: '',
currentValue: '',
values: [],
visible: true,
enabled: true,
masked: false
}
]
},
{
nameSubCategory: 'Stream Delay',
parameters: [
{
name: 'DelayEnable',
type: 'OBS_PROPERTY_BOOL',
description: 'Enable',
subType: '',
currentValue: false,
values: [],
visible: true,
enabled: true,
masked: false
},
{
name: 'DelaySec',
type: 'OBS_PROPERTY_INT',
description: 'Duration (seconds)',
subType: '',
currentValue: 20,
minVal: 0,
maxVal: 1800,
stepVal: 0,
values: [],
visible: true,
enabled: true,
masked: false
},
{
name: 'DelayPreserve',
type: 'OBS_PROPERTY_BOOL',
description: 'Preserved cutoff point (increase delay) when reconnecting',
subType: '',
currentValue: true,
values: [],
visible: true,
enabled: true,
masked: false
}
]
},
{
nameSubCategory: 'Automatically Reconnect',
parameters: [
{
name: 'Reconnect',
type: 'OBS_PROPERTY_BOOL',
description: 'Enable',
subType: '',
currentValue: true,
values: [],
visible: true,
enabled: true,
masked: false
},
{
name: 'RetryDelay',
type: 'OBS_PROPERTY_INT',
description: 'Retry Delay (seconds)',
subType: '',
currentValue: 10,
minVal: 0,
maxVal: 30,
stepVal: 0,
values: [],
visible: true,
enabled: true,
masked: false
},
{
name: 'MaxRetries',
type: 'OBS_PROPERTY_INT',
description: 'Maximum Retries',
subType: '',
currentValue: 20,
minVal: 0,
maxVal: 10000,
stepVal: 0,
values: [],
visible: true,
enabled: true,
masked: false
}
]
},
{
nameSubCategory: 'Network',
parameters: [
{
name: 'BindIP',
type: 'OBS_PROPERTY_LIST',
description: 'Bind to IP',
subType: 'OBS_COMBO_FORMAT_STRING',
currentValue: 'default',
values: [
{ Default: 'default' },
{
'[Ethernet] 2a00:23c8:75a6:ab01:bf7a:f4cf:9148:d7dc': '2a00:23c8:75a6:ab01:bf7a:f4cf:9148:d7dc'
},
{
'[Ethernet] 2a00:23c8:75a6:ab01:6963:4a2b:896d:8e2a': '2a00:23c8:75a6:ab01:6963:4a2b:896d:8e2a'
},
{
'[Ethernet] fe80::65b8:61b9:a3e6:17c6': 'fe80::65b8:61b9:a3e6:17c6'
},
{ '[Ethernet] 192.168.1.108': '192.168.1.108' }
],
visible: true,
enabled: true,
masked: false
},
{
name: 'DynamicBitrate',
type: 'OBS_PROPERTY_BOOL',
description: 'Dynamically change bitrate when dropping frames while streaming',
subType: '',
currentValue: false,
values: [],
visible: true,
enabled: true,
masked: false
},
{
name: 'NewSocketLoopEnable',
type: 'OBS_PROPERTY_BOOL',
description: 'Enable new networking code',
subType: '',
currentValue: false,
values: [],
visible: true,
enabled: true,
masked: false
},
{
name: 'LowLatencyEnable',
type: 'OBS_PROPERTY_BOOL',
description: 'Low latency mode',
subType: '',
currentValue: false,
values: [],
visible: true,
enabled: true,
masked: false
}
]
},
{
nameSubCategory: 'Sources',
parameters: [
{
name: 'browserHWAccel',
type: 'OBS_PROPERTY_BOOL',
description: 'Enable Browser Source Hardware Acceleration (requires a restart)',
subType: '',
currentValue: true,
values: [],
visible: true,
enabled: true,
masked: false
}
]
},
{
nameSubCategory: 'Media Files',
parameters: [
{
name: 'fileCaching',
type: 'OBS_PROPERTY_BOOL',
description: 'Enable media file caching',
subType: '',
currentValue: true,
values: [],
visible: true,
enabled: true,
masked: false
}
]
}
],
type: 0
}
```
================================================
FILE: docs/design.excalidraw
================================================
{
"type": "excalidraw",
"version": 2,
"source": "https://excalidraw.com",
"elements": [
{
"id": "CbHsQOiiQNNJLMyXsIJF1",
"type": "diamond",
"x": 552,
"y": 243,
"width": 165,
"height": 157,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 2
},
"seed": 590750557,
"version": 46,
"versionNonce": 324355443,
"isDeleted": false,
"boundElements": [
{
"type": "text",
"id": "CcOngIv622Dcnb9HId_EO"
}
],
"updated": 1701528460326,
"link": null,
"locked": false
},
{
"id": "CcOngIv622Dcnb9HId_EO",
"type": "text",
"x": 613.5600128173828,
"y": 309.25,
"width": 42.379974365234375,
"height": 25,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"roundness": null,
"seed": 808346493,
"version": 10,
"versionNonce": 1805795133,
"isDeleted": false,
"boundElements": null,
"updated": 1701528460326,
"link": null,
"locked": false,
"text": "Main",
"fontSize": 20,
"fontFamily": 1,
"textAlign": "center",
"verticalAlign": "middle",
"baseline": 18,
"containerId": "CbHsQOiiQNNJLMyXsIJF1",
"originalText": "Main",
"lineHeight": 1.25
},
{
"id": "TfEyMabeUHphyWTL5BWG1",
"type": "rectangle",
"x": 833,
"y": 283,
"width": 840.0000000000001,
"height": 62,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 3
},
"seed": 1938783965,
"version": 178,
"versionNonce": 1175592723,
"isDeleted": false,
"boundElements": [
{
"type": "text",
"id": "fsPEHQbcMbE4sCgezkEEa"
},
{
"id": "WbV7-s4YvinNTzg5dEAd8",
"type": "arrow"
}
],
"updated": 1701528460326,
"link": null,
"locked": false
},
{
"id": "fsPEHQbcMbE4sCgezkEEa",
"type": "text",
"x": 1212.5500259399414,
"y": 301.5,
"width": 80.89994812011719,
"height": 25,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"roundness": null,
"seed": 1094090867,
"version": 161,
"versionNonce": 1928417181,
"isDeleted": false,
"boundElements": null,
"updated": 1701528460326,
"link": null,
"locked": false,
"text": "Manager",
"fontSize": 20,
"fontFamily": 1,
"textAlign": "center",
"verticalAlign": "middle",
"baseline": 18,
"containerId": "TfEyMabeUHphyWTL5BWG1",
"originalText": "Manager",
"lineHeight": 1.25
},
{
"id": "WbV7-s4YvinNTzg5dEAd8",
"type": "arrow",
"x": 716,
"y": 322,
"width": 109,
"height": 0.6917760933026216,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"roundness": {
"type": 2
},
"seed": 159277555,
"version": 122,
"versionNonce": 1799537843,
"isDeleted": false,
"boundElements": null,
"updated": 1701528460326,
"link": null,
"locked": fa
gitextract_hwpkyjj1/ ├── .erb/ │ ├── configs/ │ │ ├── .eslintrc │ │ ├── webpack.config.base.ts │ │ ├── webpack.config.eslint.ts │ │ ├── webpack.config.main.dev.ts │ │ ├── webpack.config.main.prod.ts │ │ ├── webpack.config.preload.dev.ts │ │ ├── webpack.config.renderer.dev.dll.ts │ │ ├── webpack.config.renderer.dev.ts │ │ ├── webpack.config.renderer.prod.ts │ │ └── webpack.paths.ts │ ├── mocks/ │ │ └── fileMock.js │ └── scripts/ │ ├── .eslintrc │ ├── check-build-exists.ts │ ├── check-native-dep.js │ ├── check-node-env.js │ ├── check-port-in-use.js │ ├── clean.js │ ├── delete-source-maps.js │ ├── electron-rebuild.js │ ├── link-modules.ts │ └── notarize.js ├── .eslintrc.js ├── .gitattributes ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ └── feature_request.md │ └── workflows/ │ └── node.js.yml ├── .gitignore ├── .vscode/ │ ├── extensions.json │ ├── launch.json │ ├── settings.json │ └── tasks.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── assets/ │ ├── assets.d.ts │ └── entitlements.mac.plist ├── docs/ │ ├── CONTRIBUTING.md │ ├── Colors.md │ ├── FilterAudio.md │ ├── Localisation.md │ ├── LocateLogDirectory.md │ ├── SettingsReference.md │ ├── design.excalidraw │ └── example-obs-config/ │ ├── Output.json │ └── Video.json ├── eslint.config.mjs ├── installer.nsh ├── package.json ├── postcss.config.js ├── release/ │ └── app/ │ └── package.json ├── src/ │ ├── __tests__/ │ │ ├── activitys/ │ │ │ ├── ArenaMatch.test.ts │ │ │ ├── Battleground.test.ts │ │ │ ├── ChallengeModeDungeon.test.ts │ │ │ ├── RaidEncounter.test.ts │ │ │ └── SoloShuffle.test.ts │ │ ├── localisation/ │ │ │ └── Translate.test.ts │ │ └── parser/ │ │ └── Basic.test.ts │ ├── activitys/ │ │ ├── Activity.ts │ │ ├── ArenaMatch.ts │ │ ├── Battleground.ts │ │ ├── ChallengeModeDungeon.ts │ │ ├── Manual.ts │ │ ├── RaidEncounter.ts │ │ └── SoloShuffle.ts │ ├── config/ │ │ ├── ConfigService.ts │ │ └── configSchema.ts │ ├── localisation/ │ │ ├── chineseSimplified.ts │ │ ├── english.ts │ │ ├── german.ts │ │ ├── korean.ts │ │ ├── phrases.ts │ │ └── translations.ts │ ├── main/ │ │ ├── AppUpdater.ts │ │ ├── Combatant.ts │ │ ├── Manager.ts │ │ ├── Recorder.ts │ │ ├── VideoProcessQueue.ts │ │ ├── constants.ts │ │ ├── keystone.ts │ │ ├── main.ts │ │ ├── menu.ts │ │ ├── obsEnums.ts │ │ ├── preload.ts │ │ ├── types.ts │ │ └── util.ts │ ├── parsing/ │ │ ├── ClassicLogHandler.ts │ │ ├── CombatLogWatcher.ts │ │ ├── EraLogHandler.ts │ │ ├── LogHandler.ts │ │ ├── LogLine.ts │ │ ├── RetailLogHandler.ts │ │ └── logutils.ts │ ├── renderer/ │ │ ├── App.css │ │ ├── App.tsx │ │ ├── AudioSourceControls.tsx │ │ ├── BattlegroundInfo.tsx │ │ ├── BulkTransferDialog.tsx │ │ ├── CategoryPage.tsx │ │ ├── ChatOverlayControls.tsx │ │ ├── CloudSettings.tsx │ │ ├── ConfirmChatNamePrompt.tsx │ │ ├── CrashStatus.tsx │ │ ├── DateRangePicker.tsx │ │ ├── DeleteDialog.tsx │ │ ├── DiscordButton.tsx │ │ ├── FlavourSettings.tsx │ │ ├── GeneralSettings.tsx │ │ ├── KillVideoDialog.tsx │ │ ├── KillVideoProgress.tsx │ │ ├── KillVideoSourceTimeline.tsx │ │ ├── Layout.tsx │ │ ├── LocaleSettings.tsx │ │ ├── LogButton.tsx │ │ ├── ManualSettings.tsx │ │ ├── MicrophoneStatus.tsx │ │ ├── MultiPovPlaybackToggles.tsx │ │ ├── PVESettings.tsx │ │ ├── PVPSettings.tsx │ │ ├── PatreonButton.tsx │ │ ├── RaidComp.tsx │ │ ├── RecorderPreview.tsx │ │ ├── RendererTitleBar.tsx │ │ ├── SavingStatus.tsx │ │ ├── SceneEditor.tsx │ │ ├── SearchBar.tsx │ │ ├── SettingsPage.tsx │ │ ├── SideMenu.tsx │ │ ├── SnackBar.tsx │ │ ├── StorageFilterToggle.tsx │ │ ├── TagDialog.tsx │ │ ├── TestButton.tsx │ │ ├── VideoBaseControls.tsx │ │ ├── VideoChat.tsx │ │ ├── VideoCorrelator.ts │ │ ├── VideoFilter.ts │ │ ├── VideoMarkerToggles.tsx │ │ ├── VideoPlayer.tsx │ │ ├── VideoSourceControls.tsx │ │ ├── VideoTag.ts │ │ ├── WindowsSettings.tsx │ │ ├── components/ │ │ │ ├── Badge/ │ │ │ │ └── Badge.tsx │ │ │ ├── Button/ │ │ │ │ └── Button.tsx │ │ │ ├── Command/ │ │ │ │ └── Command.tsx │ │ │ ├── Dialog/ │ │ │ │ └── Dialog.tsx │ │ │ ├── DrawingOverlay/ │ │ │ │ ├── DrawingOverlay.css │ │ │ │ └── DrawingOverlay.tsx │ │ │ ├── HoverCard/ │ │ │ │ └── HoverCard.tsx │ │ │ ├── Input/ │ │ │ │ └── Input.tsx │ │ │ ├── Label/ │ │ │ │ └── Label.tsx │ │ │ ├── Menu/ │ │ │ │ ├── Item.tsx │ │ │ │ ├── Menu.tsx │ │ │ │ └── index.tsx │ │ │ ├── MultiSelect/ │ │ │ │ └── MultiSelect.tsx │ │ │ ├── Popover/ │ │ │ │ └── Popover.tsx │ │ │ ├── Progress/ │ │ │ │ └── Progress.tsx │ │ │ ├── ScrollArea/ │ │ │ │ └── ScrollArea.tsx │ │ │ ├── Select/ │ │ │ │ └── Select.tsx │ │ │ ├── Separator/ │ │ │ │ └── Separator.tsx │ │ │ ├── Slider/ │ │ │ │ └── Slider.tsx │ │ │ ├── StatusLight/ │ │ │ │ └── StatusLight.tsx │ │ │ ├── Switch/ │ │ │ │ └── Switch.tsx │ │ │ ├── Tables/ │ │ │ │ ├── Cells.tsx │ │ │ │ ├── Headers.tsx │ │ │ │ ├── Sorting.ts │ │ │ │ ├── TableData.tsx │ │ │ │ └── VideoSelectionTable.tsx │ │ │ ├── Tabs/ │ │ │ │ └── Tabs.tsx │ │ │ ├── TextArea/ │ │ │ │ └── textarea.tsx │ │ │ ├── TextBanner/ │ │ │ │ └── TextBanner.tsx │ │ │ ├── Toast/ │ │ │ │ ├── Toast.tsx │ │ │ │ ├── Toaster.tsx │ │ │ │ └── useToast.ts │ │ │ ├── Toggle/ │ │ │ │ └── Toggle.tsx │ │ │ ├── ToggleGroup/ │ │ │ │ └── ToggleGroup.tsx │ │ │ ├── Tooltip/ │ │ │ │ └── Tooltip.tsx │ │ │ ├── Viewpoints/ │ │ │ │ └── ViewpointSelection.tsx │ │ │ └── utils/ │ │ │ └── index.ts │ │ ├── containers/ │ │ │ ├── ApplicationStatusCard/ │ │ │ │ ├── ApplicationStatusCard.tsx │ │ │ │ ├── CloudStatus.tsx │ │ │ │ ├── CloudStatusCard.tsx │ │ │ │ ├── ErrorReporter.tsx │ │ │ │ ├── MicStatus.tsx │ │ │ │ └── Status.tsx │ │ │ └── UpdateNotifier/ │ │ │ └── UpdateNotifier.tsx │ │ ├── images.ts │ │ ├── index.ejs │ │ ├── index.tsx │ │ ├── preload.d.ts │ │ ├── rendererutils.ts │ │ ├── sounds.ts │ │ └── useSettings.ts │ ├── storage/ │ │ ├── CloudClient.ts │ │ ├── DiskClient.ts │ │ ├── DiskSizeMonitor.ts │ │ └── StorageClient.ts │ ├── types/ │ │ ├── KeyTypesUIOHook.ts │ │ ├── VideoCategory.ts │ │ └── api.ts │ └── utils/ │ ├── AsyncQueue.ts │ ├── AuthError.ts │ ├── Poller.ts │ ├── RetryableConfigError.ts │ ├── TestConfigService.ts │ ├── configUtils.ts │ ├── testButtonData.ts │ └── testButtonUtils.ts ├── tailwind.config.js ├── tests/ │ ├── README.md │ ├── logs/ │ │ ├── classic/ │ │ │ ├── battleground.txt │ │ │ ├── mop_challenge_mode.txt │ │ │ ├── raid.txt │ │ │ ├── rated_2v2.txt │ │ │ ├── rated_2v2_double.txt │ │ │ ├── rated_2v2_extra_units.txt │ │ │ ├── rated_2v2_feign_death.txt │ │ │ ├── rated_3v3.txt │ │ │ ├── rated_3v3_force_stop.txt │ │ │ └── rated_5v5.txt │ │ ├── era/ │ │ │ └── raid.txt │ │ └── retail/ │ │ ├── mythic_plus.txt │ │ ├── mythic_plus_ditch_into_raid.txt │ │ ├── mythic_plus_drop_go.txt │ │ ├── mythic_plus_no_boss.txt │ │ ├── mythic_plus_repair.txt │ │ ├── raid_reset.txt │ │ ├── raid_unknown_encounter.txt │ │ ├── raid_wipe.txt │ │ ├── rated_2v2.txt │ │ ├── rated_2v2_afk_out.txt │ │ ├── rated_3v3.txt │ │ ├── rated_battleground.txt │ │ ├── rated_solo_shuffle.txt │ │ ├── skirmish.txt │ │ ├── wargame_3v3.txt │ │ └── zone_changes.txt │ └── src/ │ ├── classic/ │ │ ├── battleground.py │ │ ├── mop_challenge_mode.py │ │ ├── raid.py │ │ ├── rated_2v2.py │ │ ├── rated_2v2_double.py │ │ ├── rated_2v2_extra_units.py │ │ ├── rated_2v2_feign_death.py │ │ ├── rated_3v3.py │ │ ├── rated_3v3_force_stop.py │ │ └── rated_5v5.py │ ├── era/ │ │ └── raid.py │ ├── retail/ │ │ ├── mythic_plus.py │ │ ├── mythic_plus_ditch_into_raid.py │ │ ├── mythic_plus_drop_go.py │ │ ├── mythic_plus_no_boss.py │ │ ├── mythic_plus_repair.py │ │ ├── raid_reset.py │ │ ├── raid_unknown_encounter.py │ │ ├── raid_wipe.py │ │ ├── rated_2v2.py │ │ ├── rated_2v2_afk_out.py │ │ ├── rated_3v3.py │ │ ├── rated_battleground.py │ │ ├── rated_solo_shuffle.py │ │ ├── skirmish.py │ │ ├── wargame_3v3.py │ │ └── zone_changes.py │ └── test.py └── tsconfig.json
SYMBOL INDEX (696 symbols across 121 files)
FILE: .erb/configs/webpack.config.renderer.dev.ts
method setupMiddlewares (line 189) | setupMiddlewares(middlewares) {
FILE: .erb/scripts/check-node-env.js
function checkNodeEnv (line 3) | function checkNodeEnv(expectedEnv) {
FILE: .erb/scripts/delete-source-maps.js
function deleteSourceMaps (line 6) | function deleteSourceMaps() {
FILE: assets/assets.d.ts
type Styles (line 1) | type Styles = Record<string, string>;
FILE: src/activitys/Activity.ts
method constructor (line 35) | constructor(startDate: Date, category: VideoCategory, flavour: Flavour) {
method zoneID (line 47) | get zoneID() {
method zoneID (line 51) | set zoneID(zoneID) {
method category (line 55) | get category() {
method category (line 59) | set category(category) {
method startDate (line 63) | get startDate() {
method startDate (line 67) | set startDate(date) {
method result (line 71) | get result() {
method result (line 75) | set result(result) {
method deaths (line 79) | get deaths() {
method playerGUID (line 83) | get playerGUID() {
method playerGUID (line 87) | set playerGUID(guid) {
method endDate (line 91) | get endDate() {
method endDate (line 95) | set endDate(date) {
method combatantMap (line 99) | get combatantMap() {
method combatantMap (line 103) | set combatantMap(cm) {
method flavour (line 107) | get flavour() {
method flavour (line 111) | set flavour(flavour) {
method overrun (line 115) | get overrun() {
method overrun (line 119) | set overrun(s) {
method duration (line 124) | get duration() {
method player (line 135) | get player() {
method end (line 149) | end(endDate: Date, result: boolean) {
method getCombatant (line 155) | getCombatant(GUID: string) {
method addCombatant (line 159) | addCombatant(combatant: Combatant) {
method addDeath (line 163) | addDeath(death: PlayerDeathType) {
method getUniqueHash (line 173) | getUniqueHash(): string {
FILE: src/activitys/ArenaMatch.ts
class ArenaMatch (line 13) | class ArenaMatch extends Activity {
method constructor (line 14) | constructor(
method zoneID (line 25) | get zoneID() {
method resultInfo (line 29) | get resultInfo() {
method zoneName (line 43) | get zoneName() {
method endArena (line 55) | endArena(endDate: Date, winningTeamID: number) {
method determineArenaMatchResult (line 60) | determineArenaMatchResult(winningTeamID: number): boolean {
method getMetadata (line 78) | getMetadata(): Metadata {
method getFileName (line 100) | getFileName() {
method addCombatant (line 132) | addCombatant(combatant: Combatant) {
FILE: src/activitys/Battleground.ts
class Battleground (line 12) | class Battleground extends Activity {
method constructor (line 13) | constructor(
method battlegroundName (line 24) | get battlegroundName(): string {
method estimateResult (line 50) | estimateResult() {
method getMetadata (line 63) | getMetadata(): Metadata {
method getFileName (line 80) | getFileName(): string {
FILE: src/activitys/ChallengeModeDungeon.ts
class ChallengeModeDungeon (line 19) | class ChallengeModeDungeon extends Activity {
method constructor (line 34) | constructor(
method endDate (line 58) | get endDate() {
method endDate (line 62) | set endDate(date) {
method CMDuration (line 66) | get CMDuration() {
method CMDuration (line 70) | set CMDuration(duration) {
method timings (line 74) | get timings() {
method timeline (line 78) | get timeline() {
method level (line 82) | get level() {
method mapID (line 86) | get mapID() {
method upgradeLevel (line 90) | get upgradeLevel(): number {
method currentSegment (line 116) | get currentSegment() {
method dungeonName (line 120) | get dungeonName(): string {
method resultInfo (line 141) | get resultInfo() {
method endChallengeMode (line 154) | endChallengeMode(endDate: Date, CMDuration: number, result: boolean) {
method addTimelineSegment (line 169) | addTimelineSegment(
method endCurrentTimelineSegment (line 180) | endCurrentTimelineSegment(date: Date) {
method removeLastTimelineSegment (line 186) | removeLastTimelineSegment() {
method getLastBossEncounter (line 190) | getLastBossEncounter(): ChallengeModeTimelineSegment | undefined {
method getMetadata (line 201) | getMetadata(): Metadata {
method getFileName (line 231) | getFileName(): string {
FILE: src/activitys/Manual.ts
class Manual (line 9) | class Manual extends Activity {
method constructor (line 10) | constructor(startDate: Date, flavour: Flavour) {
method getMetadata (line 17) | getMetadata(): Metadata {
method getFileName (line 31) | getFileName(): string {
FILE: src/activitys/RaidEncounter.ts
class RaidEncounter (line 14) | class RaidEncounter extends Activity {
method constructor (line 25) | constructor(
method difficultyID (line 39) | get difficultyID() {
method encounterID (line 43) | get encounterID() {
method encounterName (line 47) | get encounterName() {
method zoneID (line 51) | get zoneID(): number {
method raid (line 70) | get raid(): RaidInstanceType {
method resultInfo (line 93) | get resultInfo() {
method difficulty (line 107) | get difficulty() {
method getMetadata (line 122) | getMetadata(): Metadata {
method getFileName (line 151) | getFileName(): string {
method updateHp (line 181) | public updateHp(current: number, max: number): void {
FILE: src/activitys/SoloShuffle.ts
class SoloShuffle (line 24) | class SoloShuffle extends Activity {
method constructor (line 27) | constructor(startDate: Date, zoneID: number) {
method zoneID (line 34) | get zoneID() {
method currentRound (line 38) | get currentRound() {
method zoneName (line 42) | get zoneName() {
method roundsWon (line 54) | get roundsWon() {
method resultInfo (line 64) | get resultInfo() {
method playerGUID (line 70) | get playerGUID() {
method playerGUID (line 74) | set playerGUID(GUID) {
method player (line 78) | get player() {
method getCombatant (line 92) | getCombatant(GUID: string) {
method startRound (line 97) | startRound(startDate: Date) {
method endRound (line 113) | endRound(endDate: Date, winningTeamID: number) {
method addDeath (line 117) | addDeath(death: PlayerDeathType) {
method addCombatant (line 155) | addCombatant(combatant: Combatant) {
method endGame (line 159) | endGame(endDate: Date) {
method getTimelineSegments (line 169) | getTimelineSegments(): SoloShuffleTimelineSegment[] {
method getMetadata (line 196) | getMetadata(): Metadata {
method getFileName (line 222) | getFileName() {
FILE: src/config/ConfigService.ts
type IConfigService (line 11) | interface IConfigService extends EventEmitter {
class ConfigService (line 50) | class ConfigService
method getInstance (line 70) | static getInstance(): ConfigService {
method constructor (line 75) | private constructor() {
method has (line 150) | has(key: keyof ConfigurationSchema): boolean {
method get (line 154) | get<T>(key: keyof ConfigurationSchema): T {
method set (line 172) | set(key: keyof ConfigurationSchema, value: any): void {
method getPath (line 187) | getPath(key: keyof ConfigurationSchema): string {
method getNumber (line 197) | getNumber(key: keyof ConfigurationSchema): number {
method getString (line 201) | getString(key: keyof ConfigurationSchema): string {
method cleanupStore (line 210) | private cleanupStore(): void {
method configValueChanged (line 235) | private configValueChanged(key: string, value: any): boolean {
method logConfigChanged (line 246) | private static logConfigChanged(newConfig: { [key: string]: any }): vo...
FILE: src/config/configSchema.ts
type ConfigurationSchema (line 4) | type ConfigurationSchema = {
type ConfigurationSchemaKey (line 103) | type ConfigurationSchemaKey = keyof ConfigurationSchema;
FILE: src/localisation/chineseSimplified.ts
constant CHINESE_SIMPLIFIED (line 4) | const CHINESE_SIMPLIFIED: Translations = {
FILE: src/localisation/english.ts
constant ENGLISH (line 4) | const ENGLISH: Translations = {
FILE: src/localisation/german.ts
constant GERMAN (line 4) | const GERMAN: Translations = {
FILE: src/localisation/korean.ts
constant KOREAN (line 4) | const KOREAN: Translations = {
FILE: src/localisation/phrases.ts
type Phrase (line 1) | enum Phrase {
type Language (line 523) | enum Language {
type Translations (line 530) | type Translations = {
type LocalizationDataType (line 534) | type LocalizationDataType = {
FILE: src/main/AppUpdater.ts
class AppUpdater (line 13) | class AppUpdater {
method constructor (line 14) | constructor(window: BrowserWindow) {
method periodicallyCheckUpdate (line 36) | private periodicallyCheckUpdate() {
FILE: src/main/Combatant.ts
class Combatant (line 6) | class Combatant {
method constructor (line 26) | constructor(GUID: string, teamID?: number, specID?: number) {
method GUID (line 41) | get GUID() {
method GUID (line 48) | set GUID(value) {
method teamID (line 55) | get teamID() {
method teamID (line 62) | set teamID(value) {
method specID (line 69) | get specID() {
method specID (line 76) | set specID(value) {
method name (line 84) | get name() {
method name (line 92) | set name(value) {
method realm (line 100) | get realm() {
method realm (line 108) | set realm(value) {
method region (line 115) | get region() {
method region (line 122) | set region(value) {
method isFullyDefined (line 126) | isFullyDefined() {
method getRaw (line 137) | getRaw(): RawCombatant {
FILE: src/main/Manager.ts
class Manager (line 55) | class Manager {
method constructor (line 122) | constructor() {
method startup (line 138) | public async startup() {
method reconfigureBase (line 176) | public async reconfigureBase() {
method configureBase (line 213) | private async configureBase(startup: boolean) {
method forceStop (line 222) | public async forceStop() {
method test (line 238) | public test(category: VideoCategory, endTest: boolean) {
method setConfigValid (line 258) | private setConfigValid() {
method setConfigInvalid (line 267) | private setConfigInvalid(reason: string) {
method refreshStatus (line 277) | public refreshStatus() {
method checkAdvancedLogging (line 316) | public async checkAdvancedLogging() {
method pushAdvancedLoggingStatus (line 349) | public pushAdvancedLoggingStatus() {
method watchConfigWtfFiles (line 357) | private watchConfigWtfFiles() {
method refreshRecStatus (line 396) | private refreshRecStatus(
method refreshMicStatus (line 408) | private refreshMicStatus(status: MicStatus) {
method redrawPreview (line 415) | private redrawPreview() {
method onWowStarted (line 428) | private async onWowStarted() {
method onWowStopped (line 447) | private async onWowStopped() {
method applyBaseConfig (line 470) | private async applyBaseConfig(config: BaseConfig, startup: boolean) {
method configureObsVideo (line 524) | private configureObsVideo() {
method configureObsAudio (line 532) | private configureObsAudio() {
method configureObsOverlay (line 548) | private configureObsOverlay() {
method setupListeners (line 556) | private setupListeners() {
FILE: src/main/Recorder.ts
class Recorder (line 77) | class Recorder extends EventEmitter {
method getInstance (line 86) | public static getInstance() {
method constructor (line 209) | private constructor() {
method setupListeners (line 215) | private setupListeners() {
method startBuffer (line 408) | public async startBuffer() {
method startRecording (line 432) | public async startRecording(offset: number) {
method stop (line 456) | public async stop() {
method forceStop (line 480) | public async forceStop(timeout: boolean) {
method configureBase (line 503) | public async configureBase(config: BaseConfig, startup: boolean) {
method getEncoderSettings (line 559) | private static getEncoderSettings(encoder: string, quality: string) {
method configureVideoSources (line 599) | public configureVideoSources(config: ObsVideoConfig) {
method configureOwnOverlay (line 639) | private async configureOwnOverlay(config: ObsOverlayConfig) {
method configureDefaultOverlay (line 678) | private configureDefaultOverlay(config: ObsOverlayConfig) {
method configureAudioSources (line 714) | public configureAudioSources(config: ObsAudioConfig) {
method removeAudioSources (line 824) | public removeAudioSources() {
method clearFindWindowInterval (line 843) | public clearFindWindowInterval() {
method shutdownOBS (line 855) | public shutdownOBS() {
method getAvailableEncoders (line 871) | public getAvailableEncoders(): string[] {
method configurePreview (line 886) | public configurePreview(x: number, y: number, width: number, height: n...
method showPreview (line 894) | public showPreview() {
method hidePreview (line 902) | public hidePreview() {
method disablePreview (line 910) | public disablePreview() {
method cleanup (line 918) | public async cleanup(obsPath: string) {
method startObsBuffer (line 938) | private async startObsBuffer() {
method convertObsBuffer (line 965) | private async convertObsBuffer(offset: number) {
method stopObsRecording (line 987) | private async stopObsRecording() {
method forceStopOBS (line 1034) | private async forceStopOBS(timeout: boolean) {
method createRecordingDirs (line 1073) | private static async createRecordingDirs(obsPath: string) {
method initializeObs (line 1086) | public initializeObs() {
method handleSignal (line 1122) | private handleSignal(signal: Signal) {
method configureWindowCaptureSource (line 1169) | private configureWindowCaptureSource(config: ObsVideoConfig) {
method configureGameCaptureSource (line 1213) | private configureGameCaptureSource(config: ObsVideoConfig) {
method configureMonitorCaptureSource (line 1259) | private configureMonitorCaptureSource(config: ObsVideoConfig) {
method configureOverlayImageSource (line 1341) | public async configureOverlayImageSource(config: ObsOverlayConfig) {
method muteInputDevices (line 1369) | private muteInputDevices() {
method unmuteInputDevices (line 1384) | private unmuteInputDevices() {
method pushToTalkHandler (line 1396) | private pushToTalkHandler(
method getCqpFromQuality (line 1432) | private static getCqpFromQuality(encoder: string, quality: string) {
method windowMatch (line 1472) | private static windowMatch(item: { name: string; value: string | numbe...
method attachCaptureSource (line 1485) | public attachCaptureSource() {
method getDisplayInfo (line 1549) | public getDisplayInfo(): {
method getSourcePosition (line 1563) | public getSourcePosition(item: SceneItem) {
method setSourcePosition (line 1601) | public setSourcePosition(
method resetSourcePosition (line 1682) | public resetSourcePosition(src: string) {
method saveSourcePosition (line 1730) | private saveSourcePosition(
method getSensibleEncoderDefault (line 1764) | public getSensibleEncoderDefault() {
method useDefaultOverlayImage (line 1793) | private async useDefaultOverlayImage(config: ObsOverlayConfig) {
method getAndClearLastFile (line 1819) | public getAndClearLastFile() {
FILE: src/main/VideoProcessQueue.ts
class VideoProcessQueue (line 51) | class VideoProcessQueue {
method getInstance (line 60) | public static getInstance() {
method constructor (line 116) | private constructor() {
method createVideoQueue (line 123) | private createVideoQueue() {
method createUploadQueue (line 141) | private createUploadQueue() {
method createDownloadQueue (line 159) | private createDownloadQueue() {
method createKillVideoQueue (line 177) | private createKillVideoQueue() {
method processVideoQueueItem (line 256) | private async processVideoQueueItem(
method processUploadQueueItem (line 289) | private async processUploadQueueItem(
method processDownloadQueueItem (line 363) | private async processDownloadQueueItem(
method processKillVideoQueueItem (line 413) | private async processKillVideoQueueItem(
method onKillVideoProgress (line 483) | private onKillVideoProgress(progress: number) {
method errorProcessingVideo (line 493) | private static errorProcessingVideo(err: unknown) {
method errorUploadingVideo (line 500) | private static errorUploadingVideo(err: unknown) {
method errorDownloadingVideo (line 507) | private static errorDownloadingVideo(err: unknown) {
method errorKillVideo (line 514) | private static errorKillVideo(err: unknown) {
method startedProcessingVideo (line 521) | private startedProcessingVideo(item: VideoQueueItem) {
method finishProcessingVideo (line 530) | private async finishProcessingVideo(item: VideoQueueItem) {
method startedProcessingKillVideo (line 557) | private startedProcessingKillVideo(item: KillVideoQueueItem) {
method finishProcessingKillVideo (line 571) | private finishProcessingKillVideo(item: KillVideoQueueItem) {
method startedUploadingVideo (line 591) | private startedUploadingVideo(item: UploadQueueItem) {
method finishUploadingVideo (line 601) | private finishUploadingVideo(item: UploadQueueItem) {
method startedDownloadingVideo (line 615) | private startedDownloadingVideo(video: RendererVideo) {
method finishDownloadingVideo (line 626) | private async finishDownloadingVideo(video: RendererVideo) {
method videoQueueEmpty (line 644) | private async videoQueueEmpty() {
method uploadQueueEmpty (line 670) | private async uploadQueueEmpty() {
method downloadQueueEmpty (line 677) | private async downloadQueueEmpty() {
method sanitizeFilename (line 697) | private static sanitizeFilename(filename: string): string {
method getOutputVideoPath (line 706) | private static getOutputVideoPath(data: VideoQueueItem, outputDir: str...
method cutVideo (line 726) | private async cutVideo(
method ffmpegWrapper (line 777) | private static async ffmpegWrapper(
method prepareKillVideoPath (line 839) | private static prepareKillVideoPath(video: RendererVideo) {
method prepareKillVideoAudioMap (line 865) | private static prepareKillVideoAudioMap(item: KillVideoQueueItem) {
method prepareKillVideoComplexFilter (line 880) | private static prepareKillVideoComplexFilter(item: KillVideoQueueItem) {
FILE: src/main/constants.ts
type ICategoryRecordingSettings (line 45) | interface ICategoryRecordingSettings {
type CategoryRecordingSettingsBase (line 56) | type CategoryRecordingSettingsBase = {
type InstanceDifficultyPartyType (line 1145) | type InstanceDifficultyPartyType = 'party' | 'raid' | 'pvp';
type InstanceDifficultyIdType (line 1146) | type InstanceDifficultyIdType = 'lfr' | 'normal' | 'heroic' | 'mythic' |...
type InstanceDifficultyType (line 1147) | type InstanceDifficultyType = {
type InstanceDifficultyObjectType (line 1153) | type InstanceDifficultyObjectType = {
type WoWCharacterDamageType (line 1381) | type WoWCharacterDamageType = 'melee' | 'ranged';
type WoWCharacterRoleType (line 1382) | type WoWCharacterRoleType = 'tank' | 'healer' | 'damage';
type WoWCharacterClassType (line 1383) | type WoWCharacterClassType =
type SpecializationObjectType (line 1416) | type SpecializationObjectType = {
FILE: src/main/keystone.ts
type TimelineSegmentType (line 1) | enum TimelineSegmentType {
type RawChallengeModeTimelineSegment (line 6) | type RawChallengeModeTimelineSegment = {
class ChallengeModeTimelineSegment (line 15) | class ChallengeModeTimelineSegment {
method constructor (line 20) | constructor(
method length (line 31) | length(): number {
method getRaw (line 35) | getRaw(): RawChallengeModeTimelineSegment {
FILE: src/main/main.ts
method click (line 141) | click() {
method click (line 148) | click() {
FILE: src/main/menu.ts
class MenuBuilder (line 3) | class MenuBuilder {
method constructor (line 4) | constructor() {}
method buildMenu (line 6) | buildMenu(): Menu {
method buildDefaultTemplate (line 13) | private buildDefaultTemplate(): MenuItemConstructorOptions[] {
FILE: src/main/obsEnums.ts
type ERecordingState (line 1) | const enum ERecordingState {
type EOBSOutputSignal (line 6) | const enum EOBSOutputSignal {
type ESupportedEncoders (line 15) | enum ESupportedEncoders {
type QualityPresets (line 25) | enum QualityPresets {
type CaptureMode (line 32) | const enum CaptureMode {
FILE: src/main/preload.ts
type Channels (line 6) | type Channels =
method sendMessage (line 58) | sendMessage(channel: Channels, args: unknown[]) {
method sendSync (line 62) | sendSync(channel: Channels, args: unknown[]) {
method invoke (line 66) | invoke(channel: Channels, args: unknown[]) {
method on (line 70) | on(channel: Channels, func: (...args: unknown[]) => void) {
method once (line 78) | once(channel: Channels, func: (...args: unknown[]) => void) {
method removeAllListeners (line 82) | removeAllListeners(channel: Channels) {
method getDisplayInfo (line 86) | getDisplayInfo(): Promise<{
method configurePreview (line 97) | configurePreview(x: number, y: number, width: number, height: number) {
method showPreview (line 101) | showPreview() {
method hidePreview (line 105) | hidePreview() {
method disablePreview (line 109) | disablePreview() {
method getSourcePosition (line 113) | getSourcePosition(
method setSourcePosition (line 119) | setSourcePosition(
method resetSourcePosition (line 135) | resetSourcePosition(src: SceneItem) {
method audioSettingsOpen (line 139) | audioSettingsOpen(): Promise<void> {
method audioSettingsClosed (line 143) | audioSettingsClosed(): Promise<void> {
method createAudioSource (line 148) | createAudioSource(id: string, type: AudioSourceType): Promise<string> {
method getAudioSourceProperties (line 152) | getAudioSourceProperties(id: string): Promise<ObsProperty[]> {
method deleteAudioSource (line 156) | deleteAudioSource(id: string): void {
method setAudioSourceDevice (line 160) | setAudioSourceDevice(id: string, device: string): void {
method setAudioSourceWindow (line 164) | setAudioSourceWindow(id: string, window: string): void {
method setAudioSourceVolume (line 168) | setAudioSourceVolume(id: string, volume: number): void {
method setForceMono (line 172) | setForceMono(enabled: boolean) {
method setAudioSuppression (line 176) | setAudioSuppression(enabled: boolean) {
method reconfigureBase (line 180) | reconfigureBase() {
method reconfigureVideo (line 184) | reconfigureVideo() {
method reconfigureAudio (line 188) | reconfigureAudio() {
method reconfigureOverlay (line 192) | reconfigureOverlay() {
method reconfigureCloud (line 196) | reconfigureCloud() {
method getSensibleEncoderDefault (line 200) | getSensibleEncoderDefault(): Promise<string> {
method refreshCloudGuilds (line 204) | refreshCloudGuilds() {
method getOrCreateChatCorrelator (line 208) | getOrCreateChatCorrelator(video: RendererVideo): Promise<string> {
method getChatMessages (line 212) | getChatMessages(correlator: string): Promise<TChatMessageWithId[]> {
method postChatMessage (line 216) | postChatMessage(correlator: string, message: string) {
method deleteChatMessage (line 220) | deleteChatMessage(id: number) {
method toggleManualRecording (line 224) | toggleManualRecording() {
method forceStopRecording (line 228) | forceStopRecording() {
method createKillVideo (line 232) | createKillVideo(
method clipVideo (line 249) | clipVideo(video: RendererVideo, offset: number, duration: number) {
FILE: src/main/types.ts
type Flavour (line 12) | enum Flavour {
type RecStatus (line 20) | enum RecStatus {
type ActivityStatus (line 30) | type ActivityStatus = {
type MicStatus (line 35) | enum MicStatus {
type SaveStatus (line 44) | enum SaveStatus {
type KillVideoStatus (line 52) | type KillVideoStatus = {
type ErrorReport (line 61) | type ErrorReport = {
type UnitFlags (line 70) | enum UnitFlags {
type PlayerDeathType (line 105) | type PlayerDeathType = {
type VideoPlayerSettings (line 117) | type VideoPlayerSettings = {
type FileSortDirection (line 122) | enum FileSortDirection {
type FileFinderCallbackType (line 131) | type FileFinderCallbackType = (
type OurDisplayType (line 141) | type OurDisplayType = {
type NumberKeyToStringValueMapType (line 154) | type NumberKeyToStringValueMapType = {
type StringKeyToNumberValueMapType (line 158) | type StringKeyToNumberValueMapType = {
type RaidInstanceType (line 162) | type RaidInstanceType = {
type FileInfo (line 169) | type FileInfo = {
type VideoQueueItem (line 176) | type VideoQueueItem = {
type Metadata (line 191) | type Metadata = {
type CloudMetadata (line 231) | type CloudMetadata = Metadata & {
type CloudSignedMetadata (line 243) | type CloudSignedMetadata = CloudMetadata & {
type RawCombatant (line 252) | type RawCombatant = {
type RendererVideo (line 265) | type RendererVideo = Metadata & {
type SoloShuffleTimelineSegment (line 278) | type SoloShuffleTimelineSegment = {
type EDeviceType (line 285) | enum EDeviceType {
type IOBSDevice (line 291) | interface IOBSDevice {
type IDevice (line 296) | interface IDevice {
type AudioSourceType (line 302) | enum AudioSourceType {
type AudioSource (line 308) | type AudioSource = {
type Pages (line 320) | enum Pages {
type StorageFilter (line 329) | enum StorageFilter {
type AppState (line 338) | type AppState = {
type AdvancedLoggingStatus (line 356) | type AdvancedLoggingStatus = {
type CloudState (line 364) | type CloudState = {
type TPreviewPosition (line 371) | type TPreviewPosition = {
type DeviceType (line 378) | enum DeviceType {
type EncoderType (line 384) | enum EncoderType {
type Encoder (line 389) | type Encoder = {
type BaseConfig (line 395) | type BaseConfig = {
type ObsVideoConfig (line 416) | type ObsVideoConfig = {
type ObsOverlayConfig (line 426) | type ObsOverlayConfig = {
type ObsAudioConfig (line 437) | type ObsAudioConfig = {
type CloudConfig (line 447) | type CloudConfig = {
type DeathMarkers (line 455) | enum DeathMarkers {
type MarkerColors (line 461) | enum MarkerColors {
type VideoMarker (line 467) | type VideoMarker = {
type SliderMark (line 474) | type SliderMark = {
type CloudStatus (line 479) | type CloudStatus = {
type DiskStatus (line 492) | type DiskStatus = {
type CloudObject (line 497) | type CloudObject = {
type IBrowserWindow (line 503) | interface IBrowserWindow {
type UploadQueueItem (line 509) | type UploadQueueItem = {
type KillVideoQueueItem (line 513) | type KillVideoQueueItem = {
type KillVideoSegment (line 522) | type KillVideoSegment = {
type CreateMultiPartUploadResponseBody (line 528) | type CreateMultiPartUploadResponseBody = {
type CompleteMultiPartUploadRequestBody (line 532) | type CompleteMultiPartUploadRequestBody = {
type ISettingsSubCategory (line 537) | interface ISettingsSubCategory {
type TObsStringList (line 543) | type TObsStringList = { value: string }[];
type IObsFont (line 545) | interface IObsFont {
type TObsValue (line 553) | type TObsValue =
type TObsType (line 560) | type TObsType =
type IObsInput (line 579) | interface IObsInput<TValueType> {
type IObsListOption (line 591) | interface IObsListOption<TValue> {
type IObsListInput (line 596) | interface IObsListInput<TValue> extends IObsInput<TValue> {
type TObsFormData (line 600) | type TObsFormData = (
type ObsSourceCallbackInfo (line 605) | type ObsSourceCallbackInfo = {
type ObsVolmeterCallbackInfo (line 612) | type ObsVolmeterCallbackInfo = {
type SceneItem (line 619) | enum SceneItem {
type SceneInteraction (line 624) | enum SceneInteraction {
type BoxDimensions (line 630) | type BoxDimensions = {
type VideoSourceName (line 641) | enum VideoSourceName {
type AudioSourcePrefix (line 648) | enum AudioSourcePrefix {
type WowProcessEvent (line 654) | enum WowProcessEvent {
type SoundAlerts (line 659) | enum SoundAlerts {
FILE: src/parsing/ClassicLogHandler.ts
class ClassicLogHandler (line 23) | class ClassicLogHandler extends LogHandler {
method constructor (line 24) | constructor(logPath: string) {
method handleEncounterStartLine (line 41) | protected async handleEncounterStartLine(line: LogLine) {
method handleSpellAuraAppliedLine (line 52) | private handleSpellAuraAppliedLine(line: LogLine) {
method handleZoneChange (line 113) | private async handleZoneChange(line: LogLine) {
method handleUnitDiedLine (line 161) | protected handleUnitDiedLine(line: LogLine) {
method handleSpellCastSuccess (line 183) | private handleSpellCastSuccess(line: LogLine) {
method startArena (line 223) | private async startArena(startDate: Date, zoneID: number) {
method calculateArenaResult (line 244) | private static calculateArenaResult(arenaMatch: ArenaMatch) {
method endArena (line 258) | private async endArena(endDate: Date) {
method processClassicCombatant (line 273) | protected processClassicCombatant(
method processArenaDeath (line 346) | private processArenaDeath(deathDate: Date) {
method battlegroundStart (line 386) | private async battlegroundStart(line: LogLine) {
method battlegroundEnd (line 408) | private async battlegroundEnd(line: LogLine) {
method handleCombatantInfoLine (line 421) | private handleCombatantInfoLine(line: LogLine) {
method handleChallengeModeStartLine (line 457) | private async handleChallengeModeStartLine(line: LogLine) {
method handleChallengeModeEndLine (line 516) | private async handleChallengeModeEndLine(line: LogLine) {
FILE: src/parsing/CombatLogWatcher.ts
class CombatLogWatcher (line 25) | class CombatLogWatcher extends EventEmitter {
method constructor (line 70) | constructor(logDir: string, timeout: number) {
method watch (line 79) | public async watch() {
method unwatch (line 118) | public async unwatch() {
method getLogDirectoryState (line 128) | private async getLogDirectoryState() {
method process (line 146) | private async process(file: string) {
method parseFileChunk (line 177) | private async parseFileChunk(file: string, bytes: number, position: nu...
method handleLogLine (line 209) | public handleLogLine(line: string) {
method resetTimeout (line 220) | private resetTimeout() {
FILE: src/parsing/EraLogHandler.ts
class EraLogHandler (line 11) | class EraLogHandler extends LogHandler {
method constructor (line 12) | constructor(logPath: string) {
method handleEncounterStartLine (line 26) | protected async handleEncounterStartLine(line: LogLine) {
method handleCombatantInfoLine (line 37) | private handleCombatantInfoLine(line: LogLine): void {
method handleSpellAuraAppliedLine (line 73) | private handleSpellAuraAppliedLine(line: LogLine) {
method handleSpellCastSuccess (line 85) | private handleSpellCastSuccess(line: LogLine) {
method processEraCombatant (line 115) | protected processEraCombatant(
method handleUnitDiedLine (line 138) | protected handleUnitDiedLine(line: LogLine) {
FILE: src/parsing/LogHandler.ts
method constructor (line 65) | constructor(logPath: string, dataTimeout: number) {
method destroy (line 86) | public destroy() {
method handleEncounterStartLine (line 91) | protected async handleEncounterStartLine(line: LogLine, flavour: Flavour) {
method handleEncounterEndLine (line 132) | protected async handleEncounterEndLine(line: LogLine) {
method handleUnitDiedLine (line 166) | protected handleUnitDiedLine(line: LogLine): void {
method startActivity (line 213) | protected static async startActivity(activity: Activity) {
method endActivity (line 247) | protected static async endActivity() {
method dataTimeout (line 359) | protected async dataTimeout(ms: number) {
method forceEndActivity (line 371) | public static async forceEndActivity(timedelta = 0) {
method dropActivity (line 387) | public static dropActivity() {
method zoneChangeStop (line 392) | protected async zoneChangeStop(line: LogLine) {
method isArena (line 404) | protected isArena() {
method isBattleground (line 420) | protected isBattleground() {
method isMythicPlus (line 429) | protected isMythicPlus() {
method isManual (line 438) | protected isManual() {
method processCombatant (line 443) | protected processCombatant(
method handleSpellDamage (line 498) | protected handleSpellDamage(line: LogLine) {
method handleManualRecordingHotKey (line 536) | public static async handleManualRecordingHotKey() {
FILE: src/parsing/LogLine.ts
class LogLine (line 11) | class LogLine {
method constructor (line 31) | constructor(public original: string) {
method arg (line 44) | arg(index: number): any {
method date (line 56) | date(): Date {
method type (line 96) | type(): string {
method parseLogArg (line 106) | private parseLogArg(maxSplits?: number): void {
method addArg (line 193) | private addArg(value: any): void {
method toString (line 198) | toString(): string {
FILE: src/parsing/RetailLogHandler.ts
class RetailLogHandler (line 33) | class RetailLogHandler extends LogHandler {
method constructor (line 36) | constructor(logPath: string) {
method setIsPtr (line 56) | public setIsPtr() {
method handleArenaStartLine (line 60) | private async handleArenaStartLine(line: LogLine) {
method handleArenaEndLine (line 127) | private async handleArenaEndLine(line: LogLine) {
method handleChallengeModeStartLine (line 153) | private async handleChallengeModeStartLine(line: LogLine) {
method handleChallengeModeEndLine (line 227) | private async handleChallengeModeEndLine(line: LogLine) {
method handleEncounterStartLine (line 261) | protected async handleEncounterStartLine(line: LogLine) {
method handleEncounterEndLine (line 357) | protected async handleEncounterEndLine(line: LogLine) {
method handleZoneChange (line 407) | private async handleZoneChange(line: LogLine) {
method handleCombatantInfoLine (line 464) | private handleCombatantInfoLine(line: LogLine): void {
method handleSpellAuraAppliedLine (line 503) | private handleSpellAuraAppliedLine(line: LogLine) {
method handleSpellCastSuccess (line 522) | private handleSpellCastSuccess(line: LogLine) {
method getRelativeTimestampForTimelineSegment (line 570) | private getRelativeTimestampForTimelineSegment(eventDate: Date) {
method battlegroundStart (line 585) | private async battlegroundStart(line: LogLine) {
method battlegroundEnd (line 607) | private async battlegroundEnd(line: LogLine) {
FILE: src/renderer/App.tsx
function App (line 541) | function App() {
FILE: src/renderer/AudioSourceControls.tsx
type IProps (line 52) | interface IProps {
FILE: src/renderer/BattlegroundInfo.tsx
type IProps (line 5) | interface IProps {
FILE: src/renderer/BulkTransferDialog.tsx
type BulkTransferDialogProps (line 15) | type BulkTransferDialogProps = {
FILE: src/renderer/CategoryPage.tsx
type IProps (line 53) | interface IProps {
FILE: src/renderer/ChatOverlayControls.tsx
type IProps (line 17) | interface IProps {
FILE: src/renderer/CloudSettings.tsx
type IProps (line 44) | interface IProps {
FILE: src/renderer/ConfirmChatNamePrompt.tsx
type IProps (line 8) | interface IProps {
FILE: src/renderer/CrashStatus.tsx
type IProps (line 6) | interface IProps {
function CrashStatus (line 10) | function CrashStatus(props: IProps) {
FILE: src/renderer/DateRangePicker.tsx
type IProps (line 7) | interface IProps {
FILE: src/renderer/DeleteDialog.tsx
type DeleteDialogProps (line 31) | type DeleteDialogProps = {
FILE: src/renderer/DiscordButton.tsx
type IProps (line 11) | interface IProps {
function DiscordButton (line 15) | function DiscordButton(props: IProps) {
FILE: src/renderer/FlavourSettings.tsx
type IProps (line 15) | interface IProps {
FILE: src/renderer/GeneralSettings.tsx
type IProps (line 17) | interface IProps {
FILE: src/renderer/KillVideoDialog.tsx
type IProps (line 31) | interface IProps {
FILE: src/renderer/KillVideoProgress.tsx
type IProps (line 10) | interface IProps {
FILE: src/renderer/KillVideoSourceTimeline.tsx
type SourceTimelineProps (line 24) | interface SourceTimelineProps {
FILE: src/renderer/Layout.tsx
type IProps (line 15) | interface IProps {
FILE: src/renderer/LocaleSettings.tsx
type IProps (line 20) | interface IProps {
FILE: src/renderer/LogButton.tsx
type IProps (line 10) | interface IProps {
function LogsButton (line 14) | function LogsButton(props: IProps) {
FILE: src/renderer/ManualSettings.tsx
type IProps (line 26) | interface IProps {
FILE: src/renderer/MicrophoneStatus.tsx
type IProps (line 6) | interface IProps {
function MicrophoneStatus (line 10) | function MicrophoneStatus(props: IProps) {
FILE: src/renderer/MultiPovPlaybackToggles.tsx
type IProps (line 11) | interface IProps {
FILE: src/renderer/PVESettings.tsx
type IProps (line 27) | interface IProps {
FILE: src/renderer/PVPSettings.tsx
type IProps (line 10) | interface IProps {
FILE: src/renderer/PatreonButton.tsx
type IProps (line 11) | interface IProps {
function PatreonButton (line 15) | function PatreonButton(props: IProps) {
FILE: src/renderer/RaidComp.tsx
type IProps (line 8) | interface IProps {
type RoleCount (line 12) | type RoleCount = {
FILE: src/renderer/RecorderPreview.tsx
type Snap (line 21) | enum Snap {
FILE: src/renderer/RendererTitleBar.tsx
function RendererTitleBar (line 7) | function RendererTitleBar() {
FILE: src/renderer/SavingStatus.tsx
type IProps (line 5) | interface IProps {
function SavingStatus (line 9) | function SavingStatus(props: IProps) {
FILE: src/renderer/SceneEditor.tsx
type IProps (line 26) | interface IProps {
FILE: src/renderer/SearchBar.tsx
type IProps (line 39) | interface IProps {
FILE: src/renderer/SettingsPage.tsx
type IProps (line 23) | interface IProps {
FILE: src/renderer/SideMenu.tsx
type IProps (line 59) | interface IProps {
FILE: src/renderer/SnackBar.tsx
type IProps (line 7) | interface IProps {
function SnackBar (line 15) | function SnackBar(props: IProps) {
FILE: src/renderer/StorageFilterToggle.tsx
type IProps (line 15) | interface IProps {
FILE: src/renderer/TagDialog.tsx
type IProps (line 17) | interface IProps {
function TagDialog (line 25) | function TagDialog(props: IProps) {
FILE: src/renderer/TestButton.tsx
type IProps (line 20) | interface IProps {
FILE: src/renderer/VideoBaseControls.tsx
type IProps (line 45) | interface IProps {
FILE: src/renderer/VideoChat.tsx
type IProps (line 17) | interface IProps {
FILE: src/renderer/VideoCorrelator.ts
class VideoCorrelator (line 8) | class VideoCorrelator {
method correlate (line 14) | public static correlate(raw: RendererVideo[]) {
method correlateVideo (line 31) | private static correlateVideo(video: RendererVideo, videos: RendererVi...
method reverseChronologicalVideoSort (line 107) | private static reverseChronologicalVideoSort(
FILE: src/renderer/VideoFilter.ts
class VideoFilter (line 31) | class VideoFilter {
method constructor (line 57) | constructor(
method filter (line 79) | public filter() {
method getCategorySuggestions (line 103) | public static getCategorySuggestions(
method getVideoSuggestions (line 129) | private static getVideoSuggestions(video: RendererVideo, language: Lan...
method getGenericSuggestions (line 148) | private static getGenericSuggestions(
method pushCombatantTag (line 218) | private static pushCombatantTag(
method getDungeonSuggestions (line 246) | private static getDungeonSuggestions(
method getRaidSuggestions (line 305) | private static getRaidSuggestions(video: RendererVideo, language: Lang...
method getPvpSuggestions (line 354) | private static getPvpSuggestions(video: RendererVideo, language: Langu...
FILE: src/renderer/VideoMarkerToggles.tsx
type IProps (line 18) | interface IProps {
FILE: src/renderer/VideoPlayer.tsx
type IProps (line 66) | interface IProps {
type VideoPlayerRef (line 103) | interface VideoPlayerRef {
method seekAllPlayersTo (line 137) | seekAllPlayersTo(seconds: number) {
FILE: src/renderer/VideoSourceControls.tsx
type IProps (line 18) | interface IProps {
FILE: src/renderer/VideoTag.ts
class VideoTag (line 1) | class VideoTag {
method constructor (line 8) | constructor(grouping: number, label: string, icon: string, color: stri...
method decode (line 15) | public static decode(encoded: string) {
method encode (line 20) | public encode() {
method getAsTag (line 29) | public getAsTag() {
FILE: src/renderer/WindowsSettings.tsx
type IProps (line 12) | interface IProps {
FILE: src/renderer/components/Badge/Badge.tsx
type BadgeProps (line 26) | interface BadgeProps
function Badge (line 30) | function Badge({ className, variant, ...props }: BadgeProps) {
FILE: src/renderer/components/Button/Button.tsx
type ButtonProps (line 38) | interface ButtonProps
FILE: src/renderer/components/Command/Command.tsx
type CommandDialogProps (line 24) | type CommandDialogProps = DialogProps;
FILE: src/renderer/components/DrawingOverlay/DrawingOverlay.tsx
type DrawingOverlayProps (line 8) | interface DrawingOverlayProps {
FILE: src/renderer/components/Input/Input.tsx
type InputProps (line 6) | type InputProps = React.InputHTMLAttributes<HTMLInputElement>;
FILE: src/renderer/components/Menu/Item.tsx
type MenuItemProps (line 6) | type MenuItemProps = {
type MenuBadgeProps (line 43) | type MenuBadgeProps = {
FILE: src/renderer/components/Menu/Menu.tsx
type MenuProps (line 8) | type MenuProps = {
type MenuContextType (line 14) | interface MenuContextType {
FILE: src/renderer/components/MultiSelect/MultiSelect.tsx
type MultiSelectProps (line 36) | interface MultiSelectProps
FILE: src/renderer/components/ScrollArea/ScrollArea.tsx
type ScrollIndicatorProps (line 29) | type ScrollIndicatorProps = {
function handleScroll (line 68) | function handleScroll() {
function handleScroll (line 100) | function handleScroll() {
type ScrollAreaProps (line 124) | type ScrollAreaProps = {
FILE: src/renderer/components/StatusLight/StatusLight.tsx
type StatusLightVariant (line 29) | type StatusLightVariant =
type StatusLightProps (line 40) | interface StatusLightProps
FILE: src/renderer/components/Tables/VideoSelectionTable.tsx
type IProps (line 24) | interface IProps {
FILE: src/renderer/components/TextArea/textarea.tsx
function Textarea (line 5) | function Textarea({ className, ...props }: React.ComponentProps<'textare...
FILE: src/renderer/components/TextBanner/TextBanner.tsx
type TextBannerProps (line 7) | type TextBannerProps = {
FILE: src/renderer/components/Toast/Toast.tsx
type ToastProps (line 118) | type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>;
type ToastActionElement (line 120) | type ToastActionElement = React.ReactElement<typeof ToastAction>;
FILE: src/renderer/components/Toast/Toaster.tsx
function Toaster (line 11) | function Toaster() {
FILE: src/renderer/components/Toast/useToast.ts
constant TOAST_LIMIT (line 5) | const TOAST_LIMIT = 3;
constant TOAST_REMOVE_DELAY (line 6) | const TOAST_REMOVE_DELAY = 1000000;
type ToasterToast (line 8) | type ToasterToast = ToastProps & {
function genId (line 24) | function genId() {
type ActionType (line 29) | type ActionType = typeof actionTypes;
type Action (line 31) | type Action =
type State (line 49) | interface State {
function dispatch (line 59) | function dispatch(action: Action) {
type Toast (line 138) | type Toast = Omit<ToasterToast, 'id'>;
function toast (line 140) | function toast({ ...props }: Toast) {
function useToast (line 169) | function useToast() {
FILE: src/renderer/components/Tooltip/Tooltip.tsx
type TooltipProps (line 6) | interface TooltipProps
type TooltipProviderProps (line 60) | type TooltipProviderProps = TooltipPrimitive.TooltipProviderProps;
FILE: src/renderer/components/Viewpoints/ViewpointSelection.tsx
type IProps (line 19) | interface IProps {
function ViewpointSelection (line 26) | function ViewpointSelection(props: IProps) {
FILE: src/renderer/components/utils/index.ts
function cn (line 4) | function cn(...inputs: ClassValue[]) {
FILE: src/renderer/containers/ApplicationStatusCard/ApplicationStatusCard.tsx
type ApplicationStatusCardProps (line 17) | type ApplicationStatusCardProps = {
FILE: src/renderer/containers/ApplicationStatusCard/CloudStatus.tsx
type StatusProps (line 16) | type StatusProps = {
FILE: src/renderer/containers/ApplicationStatusCard/CloudStatusCard.tsx
type CloudStatusCardProps (line 6) | type CloudStatusCardProps = {
FILE: src/renderer/containers/ApplicationStatusCard/Status.tsx
type StatusInfo (line 27) | type StatusInfo = {
type StatusProps (line 33) | type StatusProps = {
FILE: src/renderer/containers/UpdateNotifier/UpdateNotifier.tsx
type IProps (line 11) | type IProps = {
function arePropsEqual (line 16) | function arePropsEqual(oldProps: IProps, newProps: IProps): boolean {
FILE: src/renderer/preload.d.ts
type Window (line 9) | interface Window {
FILE: src/storage/CloudClient.ts
type VideoMessages (line 27) | const enum VideoMessages {
type GuildMessages (line 37) | const enum GuildMessages {
class CloudClient (line 46) | class CloudClient implements StorageClient {
method getInstance (line 55) | public static getInstance() {
method constructor (line 185) | private constructor() {
method startHeartbeatTimer (line 202) | private startHeartbeatTimer() {
method startReconnectTimer (line 229) | private startReconnectTimer() {
method startBatchTimer (line 254) | private startBatchTimer() {
method ready (line 276) | public async ready() {
method fetchAffiliations (line 285) | public async fetchAffiliations(logoutOn401: boolean) {
method refreshStatus (line 306) | public async refreshStatus() {
method refreshVideos (line 333) | public async refreshVideos() {
method getVideos (line 342) | private async getVideos() {
method deleteVideos (line 378) | public async deleteVideos(videoNames: string[]) {
method protectVideos (line 396) | public async protectVideos(videoNames: string[], protect: boolean) {
method tagVideos (line 421) | public async tagVideos(videoNames: string[], tag: string) {
method reset (line 440) | private reset() {
method configure (line 460) | private async configure() {
method createAuthHeader (line 570) | private static createAuthHeader(user: string, pass: string) {
method postVideo (line 579) | public async postVideo(metadata: CloudMetadata) {
method getAsFile (line 610) | public async getAsFile(
method signPutUrl (line 660) | private async signPutUrl(key: string, bytes: number) {
method createMultiPartUpload (line 683) | private async createMultiPartUpload(
method completeMultiPartUpload (line 711) | private async completeMultiPartUpload(key: string, etags: string[]) {
method putFile (line 735) | public async putFile(
method pollInit (line 765) | public async pollInit() {
method startPolling (line 780) | public startPolling() {
method stopPolling (line 790) | public stopPolling() {
method connectPollingWebsocket (line 803) | private connectPollingWebsocket() {
method getUsage (line 852) | public async getUsage() {
method getStorageLimit (line 873) | public async getStorageLimit() {
method getUserAffiliations (line 894) | private async getUserAffiliations(
method runHousekeeping (line 920) | public async runHousekeeping() {
method getMtime (line 940) | private async getMtime(): Promise<number> {
method checkForUpdate (line 961) | private async checkForUpdate() {
method getContentType (line 1004) | private static getContentType(key: string) {
method doSinglePartUpload (line 1021) | private async doSinglePartUpload(
method doMultiPartUpload (line 1092) | private async doMultiPartUpload(
method getShareableLink (line 1235) | public async getShareableLink(videoName: string) {
method validateResponseStatus (line 1257) | private validateResponseStatus(status: number) {
method setupListeners (line 1265) | private setupListeners() {
method handleWebsocketMessage (line 1329) | private handleWebsocketMessage(data: RawData) {
method getOrCreateChatCorrelator (line 1429) | public async getOrCreateChatCorrelator(video: RendererVideo) {
method getChatMessages (line 1481) | public async getChatMessages(correlator: string) {
method postChatMessage (line 1508) | public async postChatMessage(correlator: string, message: string) {
method deleteChatMessage (line 1533) | public async deleteChatMessage(id: number) {
FILE: src/storage/DiskClient.ts
class DiskClient (line 23) | class DiskClient implements StorageClient {
method getInstance (line 32) | public static getInstance() {
method constructor (line 37) | private constructor() {
method ready (line 42) | public async ready() {
method refreshStatus (line 55) | public async refreshStatus() {
method refreshVideos (line 73) | public async refreshVideos() {
method getVideos (line 81) | private async getVideos() {
method deleteVideos (line 132) | public async deleteVideos(videoPaths: string[]) {
method tagVideos (line 136) | public async tagVideos(videoPaths: string[], tag: string) {
method protectVideos (line 140) | public async protectVideos(videoPaths: string[], protect: boolean) {
method protectVideoDisk (line 149) | private async protectVideoDisk(protect: boolean, videoPath: string) {
method tagVideoDisk (line 173) | private async tagVideoDisk(videoPath: string, tag: string) {
method deleteVideoDisk (line 199) | private async deleteVideoDisk(videoPath: string) {
method setupListeners (line 222) | private setupListeners() {
FILE: src/storage/DiskSizeMonitor.ts
class DiskSizeMonitor (line 19) | class DiskSizeMonitor {
method run (line 22) | async run() {
method usage (line 86) | public async usage() {
FILE: src/storage/StorageClient.ts
type StorageClient (line 1) | interface StorageClient {
FILE: src/types/KeyTypesUIOHook.ts
type PTTEventType (line 129) | enum PTTEventType {
type PTTKeyPressEvent (line 137) | interface PTTKeyPressEvent {
FILE: src/types/VideoCategory.ts
type VideoCategory (line 1) | enum VideoCategory {
FILE: src/types/api.ts
type TAffiliation (line 13) | type TAffiliation = z.infer<typeof Affiliation>;
type TChatMessageWithId (line 23) | type TChatMessageWithId = z.infer<typeof ChatMessageWithId>;
FILE: src/utils/AsyncQueue.ts
class AsyncQueue (line 1) | class AsyncQueue {
method constructor (line 6) | constructor(limit: number) {
method add (line 10) | public add(task: () => Promise<void>) {
method run (line 17) | private async run() {
FILE: src/utils/AuthError.ts
class AuthError (line 1) | class AuthError extends Error {
method constructor (line 2) | constructor(message: string) {
FILE: src/utils/Poller.ts
class Poller (line 12) | class Poller extends EventEmitter {
method getInstance (line 44) | static getInstance() {
method constructor (line 52) | private constructor() {
method isWowRunning (line 60) | public isWowRunning() {
method stop (line 67) | public stop() {
method start (line 80) | public start() {
FILE: src/utils/RetryableConfigError.ts
class RetryableConfigError (line 1) | class RetryableConfigError extends Error {
method constructor (line 7) | constructor(message: string, time: number) {
FILE: src/utils/TestConfigService.ts
class ConfigService (line 10) | class ConfigService
method has (line 14) | has(_key: keyof ConfigurationSchema): boolean {
method get (line 18) | get<T>(key: keyof ConfigurationSchema): T {
method set (line 25) | set(_key: keyof ConfigurationSchema, _value: any): void {
method getNumber (line 29) | getNumber(_key: keyof ConfigurationSchema): number {
method getString (line 33) | getString(_key: keyof ConfigurationSchema): string {
method getPath (line 37) | getPath(_key: keyof ConfigurationSchema): string {
FILE: tests/src/test.py
function replace_date (line 107) | def replace_date(line, flavour):
function get_latest_metadata (line 122) | def get_latest_metadata():
function check_output_file (line 133) | def check_output_file(expected, index):
function check_output (line 156) | def check_output(output):
function check_boss_count (line 174) | def check_boss_count(num):
function close_sleep_open (line 190) | def close_sleep_open(file, sec, path):
function get_test_log (line 198) | def get_test_log(flavour):
function get_sample_log_lines (line 216) | def get_sample_log_lines(file):
function get_event (line 222) | def get_event(line):
function maybe_sleep (line 227) | def maybe_sleep(line, test, log_file, log_path):
function find_test_by_name (line 241) | def find_test_by_name(flavour, test_name):
function run_test (line 253) | def run_test(flavour, test):
function run_retail (line 285) | def run_retail():
function run_classic (line 291) | def run_classic():
function run_era (line 297) | def run_era():
function run_ptr (line 302) | def run_ptr():
function run_all (line 307) | def run_all():
function run_single (line 315) | def run_single(flavour, test_name):
Copy disabled (too large)
Download .json
Condensed preview — 264 files, each showing path, character count, and a content snippet. Download the .json file for the full structured content (23,128K chars).
[
{
"path": ".erb/configs/.eslintrc",
"chars": 149,
"preview": "{\n \"rules\": {\n \"no-console\": \"off\",\n \"global-require\": \"off\",\n \"import/no-dynamic-require\": \"off\",\n \"no-und"
},
{
"path": ".erb/configs/webpack.config.base.ts",
"chars": 1759,
"preview": "/**\n * Base webpack config used across other specific configs\n */\n\nimport webpack from 'webpack';\nimport TsconfigPathsPl"
},
{
"path": ".erb/configs/webpack.config.eslint.ts",
"chars": 135,
"preview": "/* eslint import/no-unresolved: off, import/no-self-import: off */\n\nmodule.exports = require('./webpack.config.renderer."
},
{
"path": ".erb/configs/webpack.config.main.dev.ts",
"chars": 1655,
"preview": "/**\n * Webpack config for development electron main process\n */\n\nimport path from 'path';\nimport webpack from 'webpack';"
},
{
"path": ".erb/configs/webpack.config.main.prod.ts",
"chars": 2012,
"preview": "/**\n * Webpack config for production electron main process\n */\n\nimport path from 'path';\nimport webpack from 'webpack';\n"
},
{
"path": ".erb/configs/webpack.config.preload.dev.ts",
"chars": 1946,
"preview": "import path from 'path';\nimport webpack from 'webpack';\nimport { merge } from 'webpack-merge';\nimport { BundleAnalyzerPl"
},
{
"path": ".erb/configs/webpack.config.renderer.dev.dll.ts",
"chars": 1720,
"preview": "/**\n * Builds the DLL for development electron renderer process\n */\n\nimport webpack from 'webpack';\nimport path from 'pa"
},
{
"path": ".erb/configs/webpack.config.renderer.dev.ts",
"chars": 5698,
"preview": "import 'webpack-dev-server';\nimport path from 'path';\nimport fs from 'fs';\nimport webpack from 'webpack';\nimport HtmlWeb"
},
{
"path": ".erb/configs/webpack.config.renderer.prod.ts",
"chars": 3621,
"preview": "/**\n * Build config for electron renderer process\n */\n\nimport path from 'path';\nimport webpack from 'webpack';\nimport Ht"
},
{
"path": ".erb/configs/webpack.paths.ts",
"chars": 1136,
"preview": "const path = require('path');\n\nconst rootPath = path.join(__dirname, '../..');\n\nconst erbPath = path.join(__dirname, '.."
},
{
"path": ".erb/mocks/fileMock.js",
"chars": 33,
"preview": "export default 'test-file-stub';\n"
},
{
"path": ".erb/scripts/.eslintrc",
"chars": 162,
"preview": "{\n \"rules\": {\n \"no-console\": \"off\",\n \"global-require\": \"off\",\n \"import/no-dynamic-require\": \"off\",\n \"import"
},
{
"path": ".erb/scripts/check-build-exists.ts",
"chars": 705,
"preview": "// Check if the renderer and main bundles are built\nimport path from 'path';\nimport chalk from 'chalk';\nimport fs from '"
},
{
"path": ".erb/scripts/check-native-dep.js",
"chars": 2049,
"preview": "import fs from 'fs';\nimport chalk from 'chalk';\nimport { execSync } from 'child_process';\nimport { dependencies } from '"
},
{
"path": ".erb/scripts/check-node-env.js",
"chars": 381,
"preview": "import chalk from 'chalk';\n\nexport default function checkNodeEnv(expectedEnv) {\n if (!expectedEnv) {\n throw new Erro"
},
{
"path": ".erb/scripts/check-port-in-use.js",
"chars": 414,
"preview": "import chalk from 'chalk';\nimport detectPort from 'detect-port';\n\nconst port = process.env.PORT || '1212';\n\ndetectPort(p"
},
{
"path": ".erb/scripts/clean.js",
"chars": 308,
"preview": "import { rimrafSync } from 'rimraf';\nimport fs from 'fs';\nimport webpackPaths from '../configs/webpack.paths';\n\nconst fo"
},
{
"path": ".erb/scripts/delete-source-maps.js",
"chars": 474,
"preview": "import fs from 'fs';\nimport path from 'path';\nimport { rimrafSync } from 'rimraf';\nimport webpackPaths from '../configs/"
},
{
"path": ".erb/scripts/electron-rebuild.js",
"chars": 624,
"preview": "import { execSync } from 'child_process';\nimport fs from 'fs';\nimport { dependencies } from '../../release/app/package.j"
},
{
"path": ".erb/scripts/link-modules.ts",
"chars": 446,
"preview": "import fs from 'fs';\nimport webpackPaths from '../configs/webpack.paths';\n\nconst { srcNodeModulesPath, appNodeModulesPat"
},
{
"path": ".erb/scripts/notarize.js",
"chars": 998,
"preview": "const { notarize } = require('@electron/notarize');\nconst { build } = require('../../package.json');\n\nexports.default = "
},
{
"path": ".eslintrc.js",
"chars": 1162,
"preview": "module.exports = {\n extends: 'erb',\n rules: {\n // A temporary hack related to IDE not resolving correct package.jso"
},
{
"path": ".gitattributes",
"chars": 188,
"preview": "* text eol=lf\n*.exe binary\n*.png binary\n*.jpg binary\n*.jpeg binary\n*.ico binary\n*.icns binary\n*.eot "
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.md",
"chars": 535,
"preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Describe the b"
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.md",
"chars": 595,
"preview": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Is your fea"
},
{
"path": ".github/workflows/node.js.yml",
"chars": 761,
"preview": "# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tes"
},
{
"path": ".gitignore",
"chars": 539,
"preview": "# Logs\n*.log\n\n# Runtime data\npids\n*.pid\n*.seed\n\n# Coverage directory used by tools like istanbul\ncoverage\n.eslintcache\n\n"
},
{
"path": ".vscode/extensions.json",
"chars": 81,
"preview": "{\n \"recommendations\": [\"dbaeumer.vscode-eslint\", \"EditorConfig.EditorConfig\"]\n}\n"
},
{
"path": ".vscode/launch.json",
"chars": 666,
"preview": "{\n \"version\": \"0.2.0\",\n \"configurations\": [\n {\n \"name\": \"Electron: Main\",\n \"type\": \"node\",\n \"request"
},
{
"path": ".vscode/settings.json",
"chars": 644,
"preview": "{\n \"files.associations\": {\n \".eslintrc\": \"jsonc\",\n \".prettierrc\": \"jsonc\",\n \".eslintignore\": \"ignore\"\n },\n \n"
},
{
"path": ".vscode/tasks.json",
"chars": 556,
"preview": "{\n \"version\": \"2.0.0\",\n \"tasks\": [\n {\n \"type\": \"npm\",\n \"label\": \"Start Webpack Dev\",\n \"script\": \"sta"
},
{
"path": "CHANGELOG.md",
"chars": 79279,
"preview": "# Changelog\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changel"
},
{
"path": "LICENSE",
"chars": 20460,
"preview": "Warcraft Recorder\n---------------------------------------------\n\n GNU GENERAL PUBLIC LICENSE\n "
},
{
"path": "README.md",
"chars": 3454,
"preview": "# Warcraft Recorder\n\n![Version]"
},
{
"path": "assets/assets.d.ts",
"chars": 585,
"preview": "type Styles = Record<string, string>;\n\ndeclare module '*.svg' {\n const content: string;\n export default content;\n}\n\nde"
},
{
"path": "assets/entitlements.mac.plist",
"chars": 333,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "docs/CONTRIBUTING.md",
"chars": 5520,
"preview": "# Contributing\n\nThe below steps describe development on Windows. The app is currently not supported on other operating s"
},
{
"path": "docs/Colors.md",
"chars": 206,
"preview": "Dark blue background: #1A233A (set on HTML in app.css)\nOld lighter Blue background: #272e48\nExtra dark blue? (bottom/top"
},
{
"path": "docs/FilterAudio.md",
"chars": 2188,
"preview": "# How to Filter Recorded Audio\n\nThe below is a method to allow Warcraft Recorder to record the system audio while ignori"
},
{
"path": "docs/Localisation.md",
"chars": 2070,
"preview": "# How to Add A Language\n1. Copy the file `src/localisation/english.ts` to another language e.g. `src/localisation/klingo"
},
{
"path": "docs/LocateLogDirectory.md",
"chars": 1072,
"preview": "# How to find your World of Warcraft combat log directory\n\nIt can be a bit confusing to find the right directory where y"
},
{
"path": "docs/SettingsReference.md",
"chars": 42710,
"preview": "# Settings Reference\nThis is intended to be a quick reference to help understand the configuration we can apply to libob"
},
{
"path": "docs/design.excalidraw",
"chars": 40484,
"preview": "{\n \"type\": \"excalidraw\",\n \"version\": 2,\n \"source\": \"https://excalidraw.com\",\n \"elements\": [\n {\n \"id\": \"CbHsQ"
},
{
"path": "docs/example-obs-config/Output.json",
"chars": 25515,
"preview": "[\n {\n \"nameSubCategory\": \"Untitled\",\n \"parameters\": [\n {\n \"name\": \"Mode\",\n \"type\": \"OBS_PROPER"
},
{
"path": "docs/example-obs-config/Video.json",
"chars": 3413,
"preview": "Video\n[\n {\n \"nameSubCategory\": \"Untitled\",\n \"parameters\": [\n {\n \"name\": \"Base\",\n \"type\": \"OBS_"
},
{
"path": "eslint.config.mjs",
"chars": 1081,
"preview": "import globals from 'globals';\nimport pluginJs from '@eslint/js';\nimport tseslint from 'typescript-eslint';\nimport plugi"
},
{
"path": "installer.nsh",
"chars": 1016,
"preview": "!macro customInstall\n NSISdl::download https://aka.ms/vs/17/release/vc_redist.x64.exe \"$INSTDIR\\vc_redist.x64.exe\" \n "
},
{
"path": "package.json",
"chars": 8502,
"preview": "{\n \"name\": \"WarcraftRecorder\",\n \"description\": \"Warcraft Recorder\",\n \"keywords\": [\n \"world\",\n \"of\",\n \"warcra"
},
{
"path": "postcss.config.js",
"chars": 118,
"preview": "/* eslint global-require: off */\n\nmodule.exports = {\n plugins: [require('tailwindcss'), require('autoprefixer')],\n};\n"
},
{
"path": "release/app/package.json",
"chars": 559,
"preview": "{\n \"name\": \"WarcraftRecorder\",\n \"version\": \"7.6.3\",\n \"description\": \"Warcraft Recorder\",\n \"main\": \"./dist/main/main."
},
{
"path": "src/__tests__/activitys/ArenaMatch.test.ts",
"chars": 1485,
"preview": "import ArenaMatch from '../../activitys/ArenaMatch';\nimport { Flavour } from '../../main/types';\nimport { VideoCategory "
},
{
"path": "src/__tests__/activitys/Battleground.test.ts",
"chars": 1377,
"preview": "import { Flavour, PlayerDeathType } from '../../main/types';\nimport Battleground from '../../activitys/Battleground';\nim"
},
{
"path": "src/__tests__/activitys/ChallengeModeDungeon.test.ts",
"chars": 6894,
"preview": "jest.mock(\n 'config/ConfigService',\n () => ({\n __esModule: true,\n default: {\n getInstance: () => ({\n "
},
{
"path": "src/__tests__/activitys/RaidEncounter.test.ts",
"chars": 1871,
"preview": "import { specializationById } from '../../main/constants';\nimport RaidEncounter from '../../activitys/RaidEncounter';\nim"
},
{
"path": "src/__tests__/activitys/SoloShuffle.test.ts",
"chars": 2494,
"preview": "import { PlayerDeathType } from 'main/types';\nimport SoloShuffle from '../../activitys/SoloShuffle';\nimport Combatant fr"
},
{
"path": "src/__tests__/localisation/Translate.test.ts",
"chars": 1831,
"preview": "import { Flavour, Metadata } from '../../main/types';\nimport { convertKoreanVideoCategory } from '../../main/util';\nimpo"
},
{
"path": "src/__tests__/parser/Basic.test.ts",
"chars": 1935,
"preview": "import LogLine from '../../parsing/LogLine';\nimport CombatLogWatcher from '../../parsing/CombatLogWatcher';\n\ntest('Basic"
},
{
"path": "src/activitys/Activity.ts",
"chars": 3958,
"preview": "import crypto from 'crypto';\nimport ConfigService from 'config/ConfigService';\nimport { PlayerDeathType, Flavour, Metada"
},
{
"path": "src/activitys/ArenaMatch.ts",
"chars": 4379,
"preview": "import Combatant from 'main/Combatant';\nimport { getLocalePhrase } from '../localisation/translations';\nimport { Languag"
},
{
"path": "src/activitys/Battleground.ts",
"chars": 2805,
"preview": "import { Flavour, Metadata } from 'main/types';\nimport { Language, Phrase } from '../localisation/phrases';\nimport { get"
},
{
"path": "src/activitys/ChallengeModeDungeon.ts",
"chars": 5734,
"preview": "import Combatant from '../main/Combatant';\nimport { Language, Phrase } from '../localisation/phrases';\nimport { getLocal"
},
{
"path": "src/activitys/Manual.ts",
"chars": 840,
"preview": "import { Flavour, Metadata } from 'main/types';\nimport { VideoCategory } from '../types/VideoCategory';\nimport Activity "
},
{
"path": "src/activitys/RaidEncounter.ts",
"chars": 4827,
"preview": "import { Flavour, Metadata, RaidInstanceType } from 'main/types';\nimport Combatant from '../main/Combatant';\nimport { ge"
},
{
"path": "src/activitys/SoloShuffle.ts",
"chars": 6180,
"preview": "import Combatant from 'main/Combatant';\nimport { Language, Phrase } from '../localisation/phrases';\nimport { getLocalePh"
},
{
"path": "src/config/ConfigService.ts",
"chars": 7038,
"preview": "import ElectronStore from 'electron-store';\nimport { ipcMain } from 'electron';\nimport path from 'path';\nimport { EventE"
},
{
"path": "src/config/configSchema.ts",
"chars": 15180,
"preview": "import { Phrase } from 'localisation/phrases';\nimport { AudioSource, AudioSourceType } from 'main/types';\n\nexport type C"
},
{
"path": "src/localisation/chineseSimplified.ts",
"chars": 24641,
"preview": "import { Translations, Phrase } from './phrases';\n\n/* eslint-disable prettier/prettier */\nconst CHINESE_SIMPLIFIED: Tran"
},
{
"path": "src/localisation/english.ts",
"chars": 36395,
"preview": "import { Translations, Phrase } from './phrases';\n\n/* eslint-disable prettier/prettier */\nconst ENGLISH: Translations = "
},
{
"path": "src/localisation/german.ts",
"chars": 39422,
"preview": "import { Translations, Phrase } from './phrases';\n\n/* eslint-disable prettier/prettier */\nconst GERMAN: Translations = {"
},
{
"path": "src/localisation/korean.ts",
"chars": 26412,
"preview": "import { Translations, Phrase } from './phrases';\n\n/* eslint-disable prettier/prettier */\nconst KOREAN: Translations = {"
},
{
"path": "src/localisation/phrases.ts",
"chars": 12254,
"preview": "enum Phrase {\n NoVideosSaved,\n FirstTimeHere,\n SetupInstructions,\n ClipsDisplayedHere,\n NoClipsSaved,\n StoragePath"
},
{
"path": "src/localisation/translations.ts",
"chars": 1930,
"preview": "import { VideoCategory } from '../types/VideoCategory';\nimport { Language, LocalizationDataType, Phrase } from './phrase"
},
{
"path": "src/main/AppUpdater.ts",
"chars": 1557,
"preview": "import { BrowserWindow, ipcMain } from 'electron';\nimport { autoUpdater } from 'electron-updater';\n\nconst customConsoleL"
},
{
"path": "src/main/Combatant.ts",
"chars": 2747,
"preview": "import { RawCombatant } from './types';\n\n/**\n * Represents an arena combatant.\n */\nexport default class Combatant {\n pr"
},
{
"path": "src/main/Manager.ts",
"chars": 22486,
"preview": "import fs, { FSWatcher } from 'fs';\nimport { app, ipcMain, powerMonitor } from 'electron';\nimport { uIOhook, UiohookKeyb"
},
{
"path": "src/main/Recorder.ts",
"chars": 53772,
"preview": "import path from 'path';\nimport fs from 'fs';\nimport WaitQueue from 'wait-queue';\nimport {\n UiohookKeyboardEvent,\n Uio"
},
{
"path": "src/main/VideoProcessQueue.ts",
"chars": 28702,
"preview": "import path from 'path';\nimport { getBaseConfig, shouldUpload } from '../utils/configUtils';\nimport DiskSizeMonitor from"
},
{
"path": "src/main/constants.ts",
"chars": 44142,
"preview": "import { Phrase } from '../localisation/phrases';\nimport { VideoCategory } from '../types/VideoCategory';\nimport { Confi"
},
{
"path": "src/main/keystone.ts",
"chars": 1213,
"preview": "enum TimelineSegmentType {\n BossEncounter = 'Boss',\n Trash = 'Trash',\n}\n\ntype RawChallengeModeTimelineSegment = {\n se"
},
{
"path": "src/main/main.ts",
"chars": 13920,
"preview": "import path from 'path';\nimport {\n app,\n BrowserWindow,\n shell,\n ipcMain,\n dialog,\n Tray,\n Menu,\n clipboard,\n p"
},
{
"path": "src/main/menu.ts",
"chars": 1424,
"preview": "import { Menu, MenuItemConstructorOptions } from 'electron';\n\nexport default class MenuBuilder {\n constructor() {}\n\n b"
},
{
"path": "src/main/obsEnums.ts",
"chars": 702,
"preview": "export const enum ERecordingState {\n None = 'none',\n Recording = 'recording',\n}\n\nexport const enum EOBSOutputSignal {\n"
},
{
"path": "src/main/preload.ts",
"chars": 6460,
"preview": "import { contextBridge, ipcRenderer, IpcRendererEvent } from 'electron';\nimport { ObsProperty, SceneItemPosition, Source"
},
{
"path": "src/main/types.ts",
"chars": 15084,
"preview": "import { Size } from 'electron';\nimport { Language } from '../localisation/translations';\nimport { RawChallengeModeTimel"
},
{
"path": "src/main/util.ts",
"chars": 36568,
"preview": "import { URL } from 'url';\nimport path from 'path';\nimport fs, {\n createReadStream,\n existsSync,\n promises as fspromi"
},
{
"path": "src/parsing/ClassicLogHandler.ts",
"chars": 16542,
"preview": "import {\n classicArenas,\n classicBattlegrounds,\n classicUniqueSpecAuras,\n classicUniqueSpecSpells,\n mopChallengeMod"
},
{
"path": "src/parsing/CombatLogWatcher.ts",
"chars": 6241,
"preview": "import { EventEmitter } from 'stream';\nimport fs, { watch, FSWatcher } from 'fs';\nimport util from 'util';\nimport { File"
},
{
"path": "src/parsing/EraLogHandler.ts",
"chars": 4921,
"preview": "import LogHandler from './LogHandler';\nimport { Flavour } from '../main/types';\nimport { isUnitPlayer } from './logutils"
},
{
"path": "src/parsing/LogHandler.ts",
"chars": 17360,
"preview": "import VideoProcessQueue from '../main/VideoProcessQueue';\nimport Combatant from '../main/Combatant';\nimport CombatLogWa"
},
{
"path": "src/parsing/LogLine.ts",
"chars": 5297,
"preview": "/**\n * A just-in-time parsed line from the WoW combat log.\n *\n * The object is constructed from the original combat log "
},
{
"path": "src/parsing/RetailLogHandler.ts",
"chars": 20199,
"preview": "import Combatant from '../main/Combatant';\n\nimport {\n currentRetailEncounters,\n dungeonEncounters,\n dungeonsByMapId,\n"
},
{
"path": "src/parsing/logutils.ts",
"chars": 897,
"preview": "import { UnitFlags } from '../main/types';\n\nexport const hasFlag = (flags: number, flag: number) => {\n return (flags & "
},
{
"path": "src/renderer/App.css",
"chars": 26819,
"preview": "/*\n * @NOTE: Prepend a `~` to css file paths that are in your node_modules\n * See https://github.com/webpack-cont"
},
{
"path": "src/renderer/App.tsx",
"chars": 16288,
"preview": "import { MemoryRouter as Router, Routes, Route } from 'react-router-dom';\nimport { useEffect, useMemo, useRef, useState "
},
{
"path": "src/renderer/AudioSourceControls.tsx",
"chars": 22182,
"preview": "import { AppState, AudioSource, AudioSourceType } from 'main/types';\nimport { Dispatch, SetStateAction, useEffect, useRe"
},
{
"path": "src/renderer/BattlegroundInfo.tsx",
"chars": 679,
"preview": "import React from 'react';\nimport Box from '@mui/material/Box';\nimport { RendererVideo } from 'main/types';\n\ninterface I"
},
{
"path": "src/renderer/BulkTransferDialog.tsx",
"chars": 2478,
"preview": "import { AppState, RendererVideo } from 'main/types';\nimport { getLocalePhrase } from 'localisation/translations';\nimpor"
},
{
"path": "src/renderer/CategoryPage.tsx",
"chars": 21242,
"preview": "import * as React from 'react';\nimport { AppState, RendererVideo } from 'main/types';\nimport {\n Dispatch,\n MutableRefO"
},
{
"path": "src/renderer/ChatOverlayControls.tsx",
"chars": 8753,
"preview": "import { Dispatch, SetStateAction, useEffect, useRef, useState } from 'react';\nimport { configSchema, ConfigurationSchem"
},
{
"path": "src/renderer/CloudSettings.tsx",
"chars": 23843,
"preview": "import * as React from 'react';\nimport { configSchema, ConfigurationSchema } from 'config/configSchema';\nimport { AppSta"
},
{
"path": "src/renderer/ConfirmChatNamePrompt.tsx",
"chars": 1998,
"preview": "import { ConfigurationSchema } from 'config/configSchema';\nimport { Dispatch, SetStateAction } from 'react';\nimport { se"
},
{
"path": "src/renderer/CrashStatus.tsx",
"chars": 2518,
"preview": "import { ErrorReport, Crashes } from 'main/types';\nimport BugReportIcon from '@mui/icons-material/BugReport';\nimport { T"
},
{
"path": "src/renderer/DateRangePicker.tsx",
"chars": 3211,
"preview": "import { getLocalePhrase } from 'localisation/translations';\nimport { Phrase, Language } from 'localisation/phrases';\nim"
},
{
"path": "src/renderer/DeleteDialog.tsx",
"chars": 5732,
"preview": "import { AppState, RendererVideo } from 'main/types';\nimport { getLocalePhrase } from 'localisation/translations';\nimpor"
},
{
"path": "src/renderer/DiscordButton.tsx",
"chars": 1031,
"preview": "import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';\nimport { faDiscord } from '@fortawesome/free-brands-sv"
},
{
"path": "src/renderer/FlavourSettings.tsx",
"chars": 17653,
"preview": "import { ConfigurationSchema, configSchema } from 'config/configSchema';\nimport React, { Dispatch, SetStateAction } from"
},
{
"path": "src/renderer/GeneralSettings.tsx",
"chars": 8745,
"preview": "import * as React from 'react';\nimport { configSchema } from 'config/configSchema';\nimport { AppState, RecStatus } from "
},
{
"path": "src/renderer/KillVideoDialog.tsx",
"chars": 8442,
"preview": "import { KillVideoSegment, RendererVideo } from 'main/types';\nimport { getLocalePhrase } from 'localisation/translations"
},
{
"path": "src/renderer/KillVideoProgress.tsx",
"chars": 2061,
"preview": "import { KillVideoStatus } from 'main/types';\nimport Progress from './components/Progress/Progress';\nimport { cn } from "
},
{
"path": "src/renderer/KillVideoSourceTimeline.tsx",
"chars": 19764,
"preview": "import { KillVideoSegment } from 'main/types';\nimport {\n getPlayerClass,\n getPlayerName,\n getPlayerSpecID,\n getWoWCl"
},
{
"path": "src/renderer/Layout.tsx",
"chars": 2463,
"preview": "import * as React from 'react';\nimport {\n AdvancedLoggingStatus,\n Pages,\n RecStatus,\n AppState,\n RendererVideo,\n} f"
},
{
"path": "src/renderer/LocaleSettings.tsx",
"chars": 3964,
"preview": "import * as React from 'react';\nimport { configSchema, ConfigurationSchema } from 'config/configSchema';\nimport { Info }"
},
{
"path": "src/renderer/LogButton.tsx",
"chars": 878,
"preview": "import { FileText } from 'lucide-react';\nimport { AppState } from 'main/types';\nimport { getLocalePhrase } from 'localis"
},
{
"path": "src/renderer/ManualSettings.tsx",
"chars": 5944,
"preview": "import { configSchema, ConfigurationSchema } from 'config/configSchema';\nimport React, {\n Dispatch,\n SetStateAction,\n "
},
{
"path": "src/renderer/MicrophoneStatus.tsx",
"chars": 718,
"preview": "import { MicStatus } from 'main/types';\nimport MicIcon from '@mui/icons-material/Mic';\nimport MicOffIcon from '@mui/icon"
},
{
"path": "src/renderer/MultiPovPlaybackToggles.tsx",
"chars": 1986,
"preview": "import { AppState, RendererVideo } from 'main/types';\nimport {\n ToggleGroup,\n ToggleGroupItem,\n} from './components/To"
},
{
"path": "src/renderer/PVESettings.tsx",
"chars": 14660,
"preview": "import { configSchema, ConfigurationSchema } from 'config/configSchema';\nimport React from 'react';\nimport { Info } from"
},
{
"path": "src/renderer/PVPSettings.tsx",
"chars": 3734,
"preview": "import { ConfigurationSchema } from 'config/configSchema';\nimport React from 'react';\nimport { AppState } from 'main/typ"
},
{
"path": "src/renderer/PatreonButton.tsx",
"chars": 1042,
"preview": "import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';\nimport { faPatreon } from '@fortawesome/free-brands-sv"
},
{
"path": "src/renderer/RaidComp.tsx",
"chars": 2851,
"preview": "import { Box } from '@mui/material';\nimport React from 'react';\nimport { RawCombatant, RendererVideo } from 'main/types'"
},
{
"path": "src/renderer/RecorderPreview.tsx",
"chars": 13296,
"preview": "import { Box } from '@mui/material';\nimport React, { useCallback, useEffect, useRef, useState } from 'react';\nimport { s"
},
{
"path": "src/renderer/RendererTitleBar.tsx",
"chars": 1694,
"preview": "import { ComponentProps } from 'react';\nimport { cn } from './components/utils';\nimport icon from '../../assets/icon.png"
},
{
"path": "src/renderer/SavingStatus.tsx",
"chars": 604,
"preview": "import { IconButton } from '@mui/material';\nimport SaveAsIcon from '@mui/icons-material/SaveAs';\nimport { SaveStatus } f"
},
{
"path": "src/renderer/SceneEditor.tsx",
"chars": 6087,
"preview": "import { Box } from '@mui/material';\nimport React, { Dispatch, SetStateAction, useState } from 'react';\nimport { AppStat"
},
{
"path": "src/renderer/SearchBar.tsx",
"chars": 9691,
"preview": "import { AppState, RendererVideo } from 'main/types';\nimport { KeyboardEventHandler, useEffect, useRef, useState } from "
},
{
"path": "src/renderer/SettingsPage.tsx",
"chars": 6257,
"preview": "import React, { Dispatch, SetStateAction } from 'react';\nimport { AdvancedLoggingStatus, AppState, RecStatus } from 'mai"
},
{
"path": "src/renderer/SideMenu.tsx",
"chars": 10155,
"preview": "import {\n Clapperboard,\n Cog,\n Dice2,\n Dice3,\n Dice5,\n Goal,\n HardHat,\n MonitorCog,\n Play,\n Square,\n Sword,\n "
},
{
"path": "src/renderer/SnackBar.tsx",
"chars": 1255,
"preview": "import * as React from 'react';\nimport Snackbar from '@mui/material/Snackbar';\nimport IconButton from '@mui/material/Ico"
},
{
"path": "src/renderer/StorageFilterToggle.tsx",
"chars": 2363,
"preview": "import { AppState, RendererVideo, StorageFilter } from 'main/types';\nimport { Dispatch, SetStateAction } from 'react';\ni"
},
{
"path": "src/renderer/TagDialog.tsx",
"chars": 3706,
"preview": "import { RendererVideo } from 'main/types';\nimport { Dispatch, SetStateAction, useState } from 'react';\nimport { getLoca"
},
{
"path": "src/renderer/TestButton.tsx",
"chars": 2666,
"preview": "import React from 'react';\nimport { VideoCategory } from 'types/VideoCategory';\nimport { AppState, RecStatus } from 'mai"
},
{
"path": "src/renderer/VideoBaseControls.tsx",
"chars": 10653,
"preview": "import {\n Dispatch,\n FC,\n SetStateAction,\n useEffect,\n useRef,\n useState,\n} from 'react';\nimport { AppState, Encod"
},
{
"path": "src/renderer/VideoChat.tsx",
"chars": 8957,
"preview": "import { MessageSquare, SendHorizontal, Unplug, X } from 'lucide-react';\nimport { Textarea } from './components/TextArea"
},
{
"path": "src/renderer/VideoCorrelator.ts",
"chars": 4474,
"preview": "import { VideoCategory } from 'types/VideoCategory';\nimport { RendererVideo } from '../main/types';\nimport { areDatesWit"
},
{
"path": "src/renderer/VideoFilter.ts",
"chars": 11071,
"preview": "import {\n dungeonAffixesById,\n dungeonsByZoneId,\n instanceNamesByZoneId,\n specializationById,\n} from 'main/constants"
},
{
"path": "src/renderer/VideoMarkerToggles.tsx",
"chars": 4598,
"preview": "import { VideoCategory } from 'types/VideoCategory';\nimport { useEffect, useRef } from 'react';\nimport { ConfigurationSc"
},
{
"path": "src/renderer/VideoPlayer.tsx",
"chars": 40091,
"preview": "import {\n AppState,\n DeathMarkers,\n RendererVideo,\n SliderMark,\n StorageFilter,\n VideoMarker,\n VideoPlayerSetting"
},
{
"path": "src/renderer/VideoSourceControls.tsx",
"chars": 6376,
"preview": "import { useEffect, useRef, useState } from 'react';\nimport { AppState, OurDisplayType } from 'main/types';\nimport { con"
},
{
"path": "src/renderer/VideoTag.ts",
"chars": 783,
"preview": "export default class VideoTag {\n public grouping;\n public label;\n public icon;\n public color;\n private static seper"
},
{
"path": "src/renderer/WindowsSettings.tsx",
"chars": 4091,
"preview": "import * as React from 'react';\nimport { ConfigurationSchema } from 'config/configSchema';\nimport { AppState } from 'mai"
},
{
"path": "src/renderer/components/Badge/Badge.tsx",
"chars": 1152,
"preview": "import * as React from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from '"
},
{
"path": "src/renderer/components/Button/Button.tsx",
"chars": 1952,
"preview": "import * as React from 'react';\nimport { Slot } from '@radix-ui/react-slot';\nimport { cva, type VariantProps } from 'cla"
},
{
"path": "src/renderer/components/Command/Command.tsx",
"chars": 4884,
"preview": "import * as React from 'react';\nimport { type DialogProps } from '@radix-ui/react-dialog';\nimport { Command as CommandPr"
},
{
"path": "src/renderer/components/Dialog/Dialog.tsx",
"chars": 3910,
"preview": "import * as React from 'react';\nimport * as DialogPrimitive from '@radix-ui/react-dialog';\nimport { X } from 'lucide-rea"
},
{
"path": "src/renderer/components/DrawingOverlay/DrawingOverlay.css",
"chars": 401,
"preview": ".drawing-overlay-content {\n flex: 1;\n background-color: transparent;\n position: relative;\n pointer-events: a"
},
{
"path": "src/renderer/components/DrawingOverlay/DrawingOverlay.tsx",
"chars": 2119,
"preview": "import React from 'react';\nimport { Excalidraw } from '@excalidraw/excalidraw';\nimport '@excalidraw/excalidraw/index.css"
},
{
"path": "src/renderer/components/HoverCard/HoverCard.tsx",
"chars": 1366,
"preview": "import * as React from 'react';\nimport * as HoverCardPrimitive from '@radix-ui/react-hover-card';\n\nimport { cn } from '."
},
{
"path": "src/renderer/components/Input/Input.tsx",
"chars": 916,
"preview": "/* eslint-disable react/prop-types */\nimport * as React from 'react';\n\nimport { cn } from '../utils';\n\nexport type Input"
},
{
"path": "src/renderer/components/Label/Label.tsx",
"chars": 747,
"preview": "import * as React from 'react';\nimport * as LabelPrimitive from '@radix-ui/react-label';\nimport { cva, type VariantProps"
},
{
"path": "src/renderer/components/Menu/Item.tsx",
"chars": 1532,
"preview": "import { PropsWithChildren, ReactNode } from 'react';\nimport { useMenuContext } from './Menu';\nimport { cn } from '../ut"
},
{
"path": "src/renderer/components/Menu/Menu.tsx",
"chars": 1928,
"preview": "/* eslint-disable @typescript-eslint/no-explicit-any */\n// Disabled the above because the only other option here is to m"
},
{
"path": "src/renderer/components/Menu/index.tsx",
"chars": 162,
"preview": "import { Menu as Root, MenuLabel } from './Menu';\nimport Item from './Item';\n\nconst Menu = Object.assign(Root, { Label: "
},
{
"path": "src/renderer/components/MultiSelect/MultiSelect.tsx",
"chars": 7161,
"preview": "import * as React from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { CheckIcon, C"
},
{
"path": "src/renderer/components/Popover/Popover.tsx",
"chars": 1260,
"preview": "import * as React from 'react';\nimport * as PopoverPrimitive from '@radix-ui/react-popover';\n\nimport { cn } from '../uti"
},
{
"path": "src/renderer/components/Progress/Progress.tsx",
"chars": 780,
"preview": "import * as React from 'react';\nimport * as ProgressPrimitive from '@radix-ui/react-progress';\n\nimport { cn } from '../u"
},
{
"path": "src/renderer/components/ScrollArea/ScrollArea.tsx",
"chars": 5312,
"preview": "import * as React from 'react';\nimport * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';\n\nimport { ChevronDow"
},
{
"path": "src/renderer/components/Select/Select.tsx",
"chars": 5720,
"preview": "import * as React from 'react';\nimport * as SelectPrimitive from '@radix-ui/react-select';\nimport { ChevronDown, Chevron"
},
{
"path": "src/renderer/components/Separator/Separator.tsx",
"chars": 781,
"preview": "import * as React from 'react';\nimport * as SeparatorPrimitive from '@radix-ui/react-separator';\n\nimport { cn } from '.."
},
{
"path": "src/renderer/components/Slider/Slider.tsx",
"chars": 1703,
"preview": "import * as React from 'react';\nimport * as SliderPrimitive from '@radix-ui/react-slider';\n\nimport { cn } from '../utils"
},
{
"path": "src/renderer/components/StatusLight/StatusLight.tsx",
"chars": 1911,
"preview": "import React from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\n\nimport { cn } from '../ut"
},
{
"path": "src/renderer/components/Switch/Switch.tsx",
"chars": 1176,
"preview": "import * as React from 'react';\nimport * as SwitchPrimitives from '@radix-ui/react-switch';\n\nimport { cn } from '../util"
},
{
"path": "src/renderer/components/Tables/Cells.tsx",
"chars": 10999,
"preview": "import { CellContext } from '@tanstack/react-table';\nimport { CloudStatus, RendererVideo } from 'main/types';\nimport {\n "
},
{
"path": "src/renderer/components/Tables/Headers.tsx",
"chars": 3064,
"preview": "import { Checkbox } from '@mui/material';\nimport { HeaderContext } from '@tanstack/react-table';\nimport { getLocalePhras"
},
{
"path": "src/renderer/components/Tables/Sorting.ts",
"chars": 3710,
"preview": "import { Row } from '@tanstack/react-table';\nimport { getLocalePhrase } from 'localisation/translations';\nimport { Langu"
},
{
"path": "src/renderer/components/Tables/TableData.tsx",
"chars": 12675,
"preview": "import {\n ColumnDef,\n getCoreRowModel,\n getPaginationRowModel,\n getSortedRowModel,\n PaginationState,\n useReactTabl"
},
{
"path": "src/renderer/components/Tables/VideoSelectionTable.tsx",
"chars": 10303,
"preview": "import { AppState, RendererVideo } from 'main/types';\n\nimport { Cell, flexRender, Header, Row, Table } from '@tanstack/r"
},
{
"path": "src/renderer/components/Tabs/Tabs.tsx",
"chars": 2000,
"preview": "import * as React from 'react';\nimport * as TabsPrimitive from '@radix-ui/react-tabs';\n\nimport { cn } from '../utils';\n\n"
},
{
"path": "src/renderer/components/TextArea/textarea.tsx",
"chars": 788,
"preview": "import * as React from 'react';\n\nimport { cn } from '../utils';\n\nfunction Textarea({ className, ...props }: React.Compon"
},
{
"path": "src/renderer/components/TextBanner/TextBanner.tsx",
"chars": 915,
"preview": "import { ShieldAlert } from 'lucide-react';\nimport { PropsWithChildren } from 'react';\nimport { cva, type VariantProps }"
},
{
"path": "src/renderer/components/Toast/Toast.tsx",
"chars": 5037,
"preview": "import * as React from 'react';\nimport * as ToastPrimitives from '@radix-ui/react-toast';\nimport { cva, type VariantProp"
},
{
"path": "src/renderer/components/Toast/Toaster.tsx",
"chars": 768,
"preview": "import {\n Toast,\n ToastClose,\n ToastDescription,\n ToastProvider,\n ToastTitle,\n ToastViewport,\n} from './Toast';\nim"
},
{
"path": "src/renderer/components/Toast/useToast.ts",
"chars": 3941,
"preview": "import * as React from 'react';\n\nimport type { ToastActionElement, ToastProps } from './Toast';\n\nconst TOAST_LIMIT = 3;\n"
},
{
"path": "src/renderer/components/Toggle/Toggle.tsx",
"chars": 1504,
"preview": "import * as React from 'react';\nimport * as TogglePrimitive from '@radix-ui/react-toggle';\nimport { cva, type VariantPro"
},
{
"path": "src/renderer/components/ToggleGroup/ToggleGroup.tsx",
"chars": 2044,
"preview": "import * as React from 'react';\nimport * as ToggleGroupPrimitive from '@radix-ui/react-toggle-group';\nimport { cva, type"
},
{
"path": "src/renderer/components/Tooltip/Tooltip.tsx",
"chars": 2307,
"preview": "import * as React from 'react';\nimport * as TooltipPrimitive from '@radix-ui/react-tooltip';\n\nimport { cn } from '../uti"
},
{
"path": "src/renderer/components/Viewpoints/ViewpointSelection.tsx",
"chars": 8682,
"preview": "import { Box } from '@mui/material';\nimport { AppState, RawCombatant, RendererVideo } from 'main/types';\nimport { Ban, X"
},
{
"path": "src/renderer/components/utils/index.ts",
"chars": 169,
"preview": "import { clsx, type ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\nexport function cn(...inputs: C"
},
{
"path": "src/renderer/containers/ApplicationStatusCard/ApplicationStatusCard.tsx",
"chars": 2856,
"preview": "import {\n ActivityStatus,\n AdvancedLoggingStatus,\n AppState,\n ErrorReport,\n MicStatus,\n RecStatus,\n SaveStatus,\n}"
},
{
"path": "src/renderer/containers/ApplicationStatusCard/CloudStatus.tsx",
"chars": 6693,
"preview": "import { Phrase } from 'localisation/phrases';\nimport { getLocalePhrase } from 'localisation/translations';\nimport { Clo"
},
{
"path": "src/renderer/containers/ApplicationStatusCard/CloudStatusCard.tsx",
"chars": 1190,
"preview": "import { Dispatch, SetStateAction } from 'react';\nimport { AppState } from 'main/types';\n\nimport CloudStatus from './Clo"
},
{
"path": "src/renderer/containers/ApplicationStatusCard/ErrorReporter.tsx",
"chars": 1403,
"preview": "import { Phrase } from 'localisation/phrases';\nimport { getLocalePhrase } from 'localisation/translations';\nimport { Shi"
},
{
"path": "src/renderer/containers/ApplicationStatusCard/MicStatus.tsx",
"chars": 897,
"preview": "import { Phrase } from 'localisation/phrases';\nimport { getLocalePhrase } from 'localisation/translations';\nimport { Mic"
},
{
"path": "src/renderer/containers/ApplicationStatusCard/Status.tsx",
"chars": 13941,
"preview": "import { getLocalePhrase } from 'localisation/translations';\nimport { Phrase } from 'localisation/phrases';\nimport { Har"
},
{
"path": "src/renderer/containers/UpdateNotifier/UpdateNotifier.tsx",
"chars": 1144,
"preview": "import { Phrase } from 'localisation/phrases';\nimport { getLocalePhrase } from 'localisation/translations';\nimport { Arr"
},
{
"path": "src/renderer/images.ts",
"chars": 6761,
"preview": "import { WoWCharacterClassType } from 'main/constants';\n\nimport TankIcon from '../../assets/roles/tank.png';\nimport Heal"
},
{
"path": "src/renderer/index.ejs",
"chars": 218,
"preview": "<!DOCTYPE html>\n<html>\n <head>\n <meta charset=\"utf-8\" />\n <meta\n http-equiv=\"Content-Security-Policy\"\n "
},
{
"path": "src/renderer/index.tsx",
"chars": 184,
"preview": "import { createRoot } from 'react-dom/client';\nimport App from './App';\n\nconst container = document.getElementById('root"
},
{
"path": "src/renderer/preload.d.ts",
"chars": 3260,
"preview": "import ElectronStore from 'electron-store';\nimport { QualityPresets } from 'main/obsEnums';\nimport { Channels } from 'ma"
},
{
"path": "src/renderer/rendererutils.ts",
"chars": 29582,
"preview": "/**\n * Please keep this file FREE from filesystem/Node JS process related code as it\n * is used in both the backend and "
},
{
"path": "src/renderer/sounds.ts",
"chars": 637,
"preview": "import { SoundAlerts } from 'main/types';\n\nimport start from '../../assets/sounds/manual-recording-start.mp3';\nimport st"
},
{
"path": "src/renderer/useSettings.ts",
"chars": 7465,
"preview": "import { ConfigurationSchema } from 'config/configSchema';\nimport { AudioSource } from 'main/types';\nimport * as React f"
},
{
"path": "src/storage/CloudClient.ts",
"chars": 45025,
"preview": "import fs from 'fs';\nimport axios, { AxiosRequestConfig } from 'axios';\nimport {\n CloudMetadata,\n CloudStatus,\n Compl"
},
{
"path": "src/storage/DiskClient.ts",
"chars": 7625,
"preview": "import ConfigService from 'config/ConfigService';\nimport StorageClient from './StorageClient';\nimport {\n delayedDeleteV"
},
{
"path": "src/storage/DiskSizeMonitor.ts",
"chars": 2793,
"preview": "import { FileInfo, FileSortDirection } from '../main/types';\nimport ConfigService from '../config/ConfigService';\nimport"
},
{
"path": "src/storage/StorageClient.ts",
"chars": 344,
"preview": "interface StorageClient {\n ready(): Promise<boolean>;\n refreshStatus(): Promise<void>;\n refreshVideos(): Promise<void"
},
{
"path": "src/types/KeyTypesUIOHook.ts",
"chars": 1983,
"preview": "export const UiohookModifierKeyMap = {\n Ctrl: 29,\n CtrlRight: 3613,\n Shift: 42,\n ShiftRight: 54,\n Alt: 56,\n AltRig"
},
{
"path": "src/types/VideoCategory.ts",
"chars": 270,
"preview": "export enum VideoCategory {\n TwoVTwo = '2v2',\n ThreeVThree = '3v3',\n FiveVFive = '5v5',\n Skirmish = 'Skirmish',\n So"
},
{
"path": "src/types/api.ts",
"chars": 511,
"preview": "import { z } from 'zod';\n\nexport const Affiliation = z.object({\n id: z.number(),\n userName: z.string(),\n guildName: z"
},
{
"path": "src/utils/AsyncQueue.ts",
"chars": 819,
"preview": "export default class AsyncQueue {\n private queue: (() => Promise<void>)[] = [];\n private running = false;\n private li"
},
{
"path": "src/utils/AuthError.ts",
"chars": 135,
"preview": "export default class AuthError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'Aut"
}
]
// ... and 64 more files (download for full content)
About this extraction
This page contains the full source code of the aza547/wow-recorder GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 264 files (247.4 MB), approximately 5.6M tokens, and a symbol index with 696 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.