Upload 8 files
Browse files- .gitattributes +3 -0
- discord_voice.node +3 -0
- gpu_encoder_helper.exe +3 -0
- index.js +581 -0
- manifest.json +12 -0
- mediapipe.dll +3 -0
- package.json +3 -0
- selfie_segmentation.tflite +3 -0
- selfie_segmentation_landscape.tflite +3 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
discord_voice.node filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
gpu_encoder_helper.exe filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
mediapipe.dll filter=lfs diff=lfs merge=lfs -text
|
discord_voice.node
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:24f60cc3a6418aa280d0485f96c620388c8c4223da34e50beae7cf4fb4445aef
|
| 3 |
+
size 15471544
|
gpu_encoder_helper.exe
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e631259dea71b6b54e4ee6bcbc687e47ebacc6ff3afbc3e46612e9a1758c2e64
|
| 3 |
+
size 755128
|
index.js
ADDED
|
@@ -0,0 +1,581 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* eslint-disable no-console */
|
| 2 |
+
// eslint-disable-next-line import/no-unresolved, import/extensions
|
| 3 |
+
const VoiceEngine = require('./discord_voice.node');
|
| 4 |
+
const fs = require('fs');
|
| 5 |
+
const os = require('os');
|
| 6 |
+
const process = require('process');
|
| 7 |
+
const path = require('path');
|
| 8 |
+
|
| 9 |
+
const isElectronRenderer =
|
| 10 |
+
typeof window !== 'undefined' && window != null && window.DiscordNative && window.DiscordNative.isRenderer;
|
| 11 |
+
|
| 12 |
+
const appSettings = isElectronRenderer ? window.DiscordNative.settings : global.appSettings;
|
| 13 |
+
const features = isElectronRenderer ? window.DiscordNative.features : global.features;
|
| 14 |
+
const mainArgv = isElectronRenderer ? window.DiscordNative.processUtils.getMainArgvSync() : [];
|
| 15 |
+
|
| 16 |
+
let dataDirectory;
|
| 17 |
+
if (isElectronRenderer) {
|
| 18 |
+
try {
|
| 19 |
+
dataDirectory =
|
| 20 |
+
isElectronRenderer && window.DiscordNative.fileManager.getModuleDataPathSync
|
| 21 |
+
? path.join(window.DiscordNative.fileManager.getModuleDataPathSync(), 'discord_voice')
|
| 22 |
+
: null;
|
| 23 |
+
} catch (e) {
|
| 24 |
+
console.error('Failed to get data directory: ', e);
|
| 25 |
+
}
|
| 26 |
+
if (dataDirectory != null) {
|
| 27 |
+
try {
|
| 28 |
+
fs.mkdirSync(dataDirectory, {recursive: true});
|
| 29 |
+
} catch (e) {
|
| 30 |
+
console.warn("Couldn't create voice data directory ", dataDirectory, ':', e);
|
| 31 |
+
}
|
| 32 |
+
}
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
// Init logging
|
| 36 |
+
const isFileManagerAvailable = window?.DiscordNative?.fileManager;
|
| 37 |
+
const isLogDirAvailable = isFileManagerAvailable?.getAndCreateLogDirectorySync;
|
| 38 |
+
let logDirectory;
|
| 39 |
+
if (isLogDirAvailable) {
|
| 40 |
+
logDirectory = window.DiscordNative.fileManager.getAndCreateLogDirectorySync();
|
| 41 |
+
// TODO If/when we move away from utilizing webRTC logging in voice:
|
| 42 |
+
// This module uses a different approach to the log-level, particularly an integer value rather than a string.
|
| 43 |
+
// We should eventually try to align on the string approach (and querying it from our common settings) used by other modules.
|
| 44 |
+
// logLevel = window.DiscordNative.fileManager.logLevelSync();
|
| 45 |
+
} else {
|
| 46 |
+
console.warn('Unable to find log directory');
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
const defaultAudioSubsystem = process.platform === 'win32' ? 'experimental' : 'standard';
|
| 50 |
+
const audioSubsystem = appSettings
|
| 51 |
+
? appSettings.getSync('audioSubsystem', defaultAudioSubsystem)
|
| 52 |
+
: defaultAudioSubsystem;
|
| 53 |
+
const offloadAdmControls = appSettings ? appSettings.getSync('offloadAdmControls', false) : false;
|
| 54 |
+
const debugLogging = appSettings ? appSettings.getSync('debugLogging', true) : true;
|
| 55 |
+
const asyncVideoInputDeviceInit = appSettings ? appSettings.getSync('asyncVideoInputDeviceInit', false) : false;
|
| 56 |
+
const asyncClipsSourceDeinit = appSettings ? appSettings.getSync('asyncClipsSourceDeinit', false) : false;
|
| 57 |
+
|
| 58 |
+
function versionGreaterThanOrEqual(v1, v2) {
|
| 59 |
+
const v1parts = v1.split('.').map(Number);
|
| 60 |
+
const v2parts = v2.split('.').map(Number);
|
| 61 |
+
|
| 62 |
+
for (let i = 0; i < Math.max(v1parts.length, v2parts.length); i++) {
|
| 63 |
+
const num1 = i < v1parts.length ? v1parts[i] : 0;
|
| 64 |
+
const num2 = i < v2parts.length ? v2parts[i] : 0;
|
| 65 |
+
if (num1 > num2) return true;
|
| 66 |
+
if (num1 < num2) return false;
|
| 67 |
+
}
|
| 68 |
+
return true;
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
function parseArguments(args) {
|
| 72 |
+
const parsed = {
|
| 73 |
+
'log-level': -1,
|
| 74 |
+
};
|
| 75 |
+
|
| 76 |
+
const descriptions = {
|
| 77 |
+
'log-level': 'Logging level.',
|
| 78 |
+
'use-fake-video-capture': 'Use fake video capture device.',
|
| 79 |
+
'use-file-for-fake-video-capture': 'Use local file for fake video capture.',
|
| 80 |
+
'use-fake-audio-capture': 'Use fake audio capture device.',
|
| 81 |
+
'use-files-for-fake-audio-capture': 'Use local files for fake audio capture.',
|
| 82 |
+
};
|
| 83 |
+
|
| 84 |
+
for (let i = 0; i < args.length; i++) {
|
| 85 |
+
const parts = args[i].split('=');
|
| 86 |
+
const arg = parts[0];
|
| 87 |
+
const inlineValue = parts.slice(1).join('='); // Join the rest back together in case there are '=' in the value
|
| 88 |
+
|
| 89 |
+
function getValue() {
|
| 90 |
+
if (inlineValue !== undefined) {
|
| 91 |
+
return inlineValue;
|
| 92 |
+
}
|
| 93 |
+
return args[++i];
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
switch (arg) {
|
| 97 |
+
case '-h':
|
| 98 |
+
case '--help':
|
| 99 |
+
console.log('Help requested:');
|
| 100 |
+
for (const [key, value] of Object.entries(descriptions)) {
|
| 101 |
+
console.log(`--${key}: ${value}`);
|
| 102 |
+
}
|
| 103 |
+
process.exit(0);
|
| 104 |
+
break;
|
| 105 |
+
case '--log-level':
|
| 106 |
+
parsed['log-level'] = parseInt(getValue(), 10);
|
| 107 |
+
break;
|
| 108 |
+
case '--use-fake-video-capture':
|
| 109 |
+
parsed['use-fake-video-capture'] = true;
|
| 110 |
+
break;
|
| 111 |
+
case '--use-file-for-fake-video-capture':
|
| 112 |
+
parsed['use-file-for-fake-video-capture'] = getValue();
|
| 113 |
+
break;
|
| 114 |
+
case '--use-fake-audio-capture':
|
| 115 |
+
parsed['use-fake-audio-capture'] = true;
|
| 116 |
+
break;
|
| 117 |
+
case '--use-files-for-fake-audio-capture':
|
| 118 |
+
parsed['use-files-for-fake-audio-capture'] = getValue();
|
| 119 |
+
break;
|
| 120 |
+
}
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
return parsed;
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
const argv = parseArguments(mainArgv.slice(1));
|
| 127 |
+
const logLevel = argv['log-level'] === -1 ? (debugLogging ? 2 : -1) : argv['log-level'];
|
| 128 |
+
const useFakeVideoCapture = argv['use-fake-video-capture'];
|
| 129 |
+
const useFileForFakeVideoCapture = argv['use-file-for-fake-video-capture'];
|
| 130 |
+
const useFakeAudioCapture = argv['use-fake-audio-capture'];
|
| 131 |
+
const useFilesForFakeAudioCapture = argv['use-files-for-fake-audio-capture'];
|
| 132 |
+
|
| 133 |
+
features.declareSupported('voice_panning');
|
| 134 |
+
features.declareSupported('voice_multiple_connections');
|
| 135 |
+
features.declareSupported('media_devices');
|
| 136 |
+
features.declareSupported('media_video');
|
| 137 |
+
features.declareSupported('debug_logging');
|
| 138 |
+
features.declareSupported('set_audio_device_by_id');
|
| 139 |
+
features.declareSupported('set_video_device_by_id');
|
| 140 |
+
features.declareSupported('loopback');
|
| 141 |
+
features.declareSupported('experiment_config');
|
| 142 |
+
features.declareSupported('remote_locus_network_control');
|
| 143 |
+
//features.declareSupported('connection_replay');
|
| 144 |
+
features.declareSupported('simulcast');
|
| 145 |
+
features.declareSupported('simulcast_bugfix');
|
| 146 |
+
features.declareSupported('direct_video');
|
| 147 |
+
features.declareSupported('electron_video');
|
| 148 |
+
features.declareSupported('fixed_keyframe_interval');
|
| 149 |
+
features.declareSupported('first_frame_callback');
|
| 150 |
+
features.declareSupported('remote_user_multi_stream');
|
| 151 |
+
features.declareSupported('go_live_hardware');
|
| 152 |
+
features.declareSupported('bandwidth_estimation_experiments');
|
| 153 |
+
features.declareSupported('mls_pairwise_fingerprints');
|
| 154 |
+
features.declareSupported('soundshare');
|
| 155 |
+
features.declareSupported('screen_soundshare');
|
| 156 |
+
features.declareSupported('offload_adm_controls');
|
| 157 |
+
features.declareSupported('audio_codec_red');
|
| 158 |
+
features.declareSupported('sidechain_compression');
|
| 159 |
+
features.declareSupported('async_video_input_device_init');
|
| 160 |
+
features.declareSupported('async_clips_source_deinit');
|
| 161 |
+
features.declareSupported('port_aware_latency_testing');
|
| 162 |
+
|
| 163 |
+
if (process.platform === 'darwin') {
|
| 164 |
+
features.declareSupported('screen_capture_kit');
|
| 165 |
+
if (versionGreaterThanOrEqual(os.release(), '23.0.0')) {
|
| 166 |
+
features.declareSupported('native_screenshare_picker');
|
| 167 |
+
}
|
| 168 |
+
}
|
| 169 |
+
|
| 170 |
+
if (process.platform === 'linux') {
|
| 171 |
+
// from WebRTC DesktopCapturer::IsRunningUnderWayland()
|
| 172 |
+
const sessionType = process.env.XDG_SESSION_TYPE;
|
| 173 |
+
const isUnderWayland = sessionType?.startsWith('wayland') && process.env.WAYLAND_DISPLAY != null;
|
| 174 |
+
|
| 175 |
+
const currentDesktop = process.env.XDG_CURRENT_DESKTOP;
|
| 176 |
+
// we only want to enable the gamescope capturer if we're running in a non-nested gamescope session
|
| 177 |
+
const isUnderGamescope =
|
| 178 |
+
!isUnderWayland && currentDesktop?.includes('gamescope') && process.env.GAMESCOPE_WAYLAND_DISPLAY != null;
|
| 179 |
+
const isVaapiEnabled = VoiceEngine.isVaapiEnabled();
|
| 180 |
+
|
| 181 |
+
if (isUnderWayland) {
|
| 182 |
+
features.declareSupported('native_screenshare_picker');
|
| 183 |
+
}
|
| 184 |
+
if (isVaapiEnabled) {
|
| 185 |
+
features.declareSupported('vaapi');
|
| 186 |
+
}
|
| 187 |
+
if (isUnderGamescope && isVaapiEnabled) {
|
| 188 |
+
// ensure we have access to the pipewire socket
|
| 189 |
+
const runtimeDir = process.env.PIPEWIRE_RUNTIME_DIR || process.env.XDG_RUNTIME_DIR || process.env.USERPROFILE;
|
| 190 |
+
if (runtimeDir) {
|
| 191 |
+
const socketName = runtimeDir + '/' + (process.env.PIPEWIRE_REMOTE || 'pipewire-0');
|
| 192 |
+
const sstat = fs.statSync(socketName, {throwIfNoEntry: false});
|
| 193 |
+
if (sstat && sstat.isSocket()) {
|
| 194 |
+
features.declareSupported('gamescope_capture');
|
| 195 |
+
}
|
| 196 |
+
}
|
| 197 |
+
}
|
| 198 |
+
}
|
| 199 |
+
|
| 200 |
+
if (
|
| 201 |
+
process.platform === 'win32'
|
| 202 |
+
|| (process.platform === 'darwin' && versionGreaterThanOrEqual(os.release(), '16.0.0'))
|
| 203 |
+
) {
|
| 204 |
+
features.declareSupported('mediapipe');
|
| 205 |
+
features.declareSupported('mediapipe_animated');
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
+
if (process.platform === 'win32' || process.platform === 'darwin' || process.platform === 'linux') {
|
| 209 |
+
features.declareSupported('image_quality_measurement');
|
| 210 |
+
}
|
| 211 |
+
|
| 212 |
+
if (process.platform === 'win32') {
|
| 213 |
+
features.declareSupported('voice_legacy_subsystem');
|
| 214 |
+
features.declareSupported('wumpus_video');
|
| 215 |
+
features.declareSupported('hybrid_video');
|
| 216 |
+
features.declareSupported('elevated_hook');
|
| 217 |
+
features.declareSupported('soundshare_loopback');
|
| 218 |
+
features.declareSupported('screen_previews');
|
| 219 |
+
features.declareSupported('window_previews');
|
| 220 |
+
features.declareSupported('audio_debug_state');
|
| 221 |
+
features.declareSupported('video_effects');
|
| 222 |
+
features.declareSupported('voice_experimental_subsystem');
|
| 223 |
+
features.declareSupported('voice_automatic_subsystem');
|
| 224 |
+
features.declareSupported('voice_subsystem_deferred_switch');
|
| 225 |
+
features.declareSupported('voice_bypass_system_audio_input_processing');
|
| 226 |
+
features.declareSupported('clips');
|
| 227 |
+
}
|
| 228 |
+
|
| 229 |
+
function bindConnectionInstance(instance) {
|
| 230 |
+
return {
|
| 231 |
+
destroy: () => instance.destroy(),
|
| 232 |
+
|
| 233 |
+
setTransportOptions: (options) => instance.setTransportOptions(options),
|
| 234 |
+
setSelfMute: (mute) => instance.setSelfMute(mute),
|
| 235 |
+
setSelfDeafen: (deaf) => instance.setSelfDeafen(deaf),
|
| 236 |
+
|
| 237 |
+
mergeUsers: (users) => instance.mergeUsers(users),
|
| 238 |
+
destroyUser: (userId) => instance.destroyUser(userId),
|
| 239 |
+
|
| 240 |
+
prepareSecureFramesTransition: (transitionId, version, callback) =>
|
| 241 |
+
instance.prepareSecureFramesTransition(transitionId, version, callback),
|
| 242 |
+
prepareSecureFramesEpoch: (epoch, version, groupId) => instance.prepareSecureFramesEpoch(epoch, version, groupId),
|
| 243 |
+
executeSecureFramesTransition: (transitionId) => instance.executeSecureFramesTransition(transitionId),
|
| 244 |
+
|
| 245 |
+
updateMLSExternalSender: (externalSenderPackage) => instance.updateMLSExternalSender(externalSenderPackage),
|
| 246 |
+
getMLSKeyPackage: (callback) => instance.getMLSKeyPackage(callback),
|
| 247 |
+
processMLSProposals: (message, callback) => instance.processMLSProposals(message, callback),
|
| 248 |
+
prepareMLSCommitTransition: (transitionId, commit, callback) =>
|
| 249 |
+
instance.prepareMLSCommitTransition(transitionId, commit, callback),
|
| 250 |
+
processMLSWelcome: (transitionId, welcome, callback) => instance.processMLSWelcome(transitionId, welcome, callback),
|
| 251 |
+
getMLSPairwiseFingerprint: (version, userId, callback) =>
|
| 252 |
+
instance.getMLSPairwiseFingerprint(version, userId, callback),
|
| 253 |
+
setOnMLSFailureCallback: (callback) => instance.setOnMLSFailureCallback(callback),
|
| 254 |
+
setSecureFramesStateUpdateCallback: (callback) => instance.setSecureFramesStateUpdateCallback(callback),
|
| 255 |
+
|
| 256 |
+
setLocalVolume: (userId, volume) => instance.setLocalVolume(userId, volume),
|
| 257 |
+
setLocalMute: (userId, mute) => instance.setLocalMute(userId, mute),
|
| 258 |
+
fastUdpReconnect: () => instance.fastUdpReconnect(),
|
| 259 |
+
setLocalPan: (userId, left, right) => instance.setLocalPan(userId, left, right),
|
| 260 |
+
setDisableLocalVideo: (userId, disabled) => instance.setDisableLocalVideo(userId, disabled),
|
| 261 |
+
|
| 262 |
+
setMinimumOutputDelay: (delay) => instance.setMinimumOutputDelay(delay),
|
| 263 |
+
getEncryptionModes: (callback) => instance.getEncryptionModes(callback),
|
| 264 |
+
configureConnectionRetries: (baseDelay, maxDelay, maxAttempts) =>
|
| 265 |
+
instance.configureConnectionRetries(baseDelay, maxDelay, maxAttempts),
|
| 266 |
+
setOnSpeakingCallback: (callback) => instance.setOnSpeakingCallback(callback),
|
| 267 |
+
setOnNativeMuteToggleCallback: (callback) => instance.setOnNativeMuteToggleCallback?.(callback),
|
| 268 |
+
setOnNativeMuteChangedCallback: (callback) => instance.setOnNativeMuteChangedCallback?.(callback),
|
| 269 |
+
setOnSpeakingWhileMutedCallback: (callback) => instance.setOnSpeakingWhileMutedCallback(callback),
|
| 270 |
+
setPingInterval: (interval) => instance.setPingInterval(interval),
|
| 271 |
+
setPingCallback: (callback) => instance.setPingCallback(callback),
|
| 272 |
+
setPingTimeoutCallback: (callback) => instance.setPingTimeoutCallback(callback),
|
| 273 |
+
setRemoteUserSpeakingStatus: (userId, speaking) => instance.setRemoteUserSpeakingStatus(userId, speaking),
|
| 274 |
+
setRemoteUserCanHavePriority: (userId, canHavePriority) =>
|
| 275 |
+
instance.setRemoteUserCanHavePriority(userId, canHavePriority),
|
| 276 |
+
|
| 277 |
+
setOnVideoCallback: (callback) => instance.setOnVideoCallback(callback),
|
| 278 |
+
setOnFirstFrameCallback: (callback) => instance.setOnFirstFrameCallback(callback),
|
| 279 |
+
setOnFirstFrameDeliveredStatsCallback: (callback) => instance.setOnFirstFrameDeliveredStatsCallback(callback),
|
| 280 |
+
setOnFirstFrameEncryptedStatsCallback: (callback) => instance.setOnFirstFrameEncryptedStatsCallback(callback),
|
| 281 |
+
setVideoBroadcast: (broadcasting) => instance.setVideoBroadcast(broadcasting),
|
| 282 |
+
setDesktopSource: (id, videoHook, type) => instance.setDesktopSource(id, videoHook, type),
|
| 283 |
+
setDesktopSourceWithOptions: (options) => instance.setDesktopSourceWithOptions(options),
|
| 284 |
+
setGoLiveDevices: (options) => instance.setGoLiveDevices(options),
|
| 285 |
+
clearGoLiveDevices: () => instance.clearGoLiveDevices(),
|
| 286 |
+
clearDesktopSource: () => instance.clearDesktopSource(),
|
| 287 |
+
setDesktopSourceStatusCallback: (callback) => instance.setDesktopSourceStatusCallback(callback),
|
| 288 |
+
setOnDesktopSourceEnded: (callback) => instance.setOnDesktopSourceEnded(callback),
|
| 289 |
+
setOnSoundshare: (callback) => instance.setOnSoundshare(callback),
|
| 290 |
+
setOnSoundshareEnded: (callback) => instance.setOnSoundshareEnded(callback),
|
| 291 |
+
setOnSoundshareFailed: (callback) => instance.setOnSoundshareFailed(callback),
|
| 292 |
+
setPTTActive: (active, priority, muteOverride) => instance.setPTTActive(active, priority, muteOverride),
|
| 293 |
+
getStats: (callback) => instance.getStats(callback),
|
| 294 |
+
getFilteredStats: (filter, callback) => instance.getFilteredStats(filter, callback),
|
| 295 |
+
startReplay: () => instance.startReplay(),
|
| 296 |
+
setClipRecordUser: (userId, dataType, shouldRecord) => instance.setClipRecordUser(userId, dataType, shouldRecord),
|
| 297 |
+
setRtcLogMarker: (marker) => instance.setRtcLogMarker(marker),
|
| 298 |
+
startSamplesLocalPlayback: (samplesId, options, channels, callback) =>
|
| 299 |
+
instance.startSamplesLocalPlayback(samplesId, options, channels, callback),
|
| 300 |
+
stopSamplesLocalPlayback: (sourceId) => instance.stopSamplesLocalPlayback(sourceId),
|
| 301 |
+
stopAllSamplesLocalPlayback: () => instance.stopAllSamplesLocalPlayback(),
|
| 302 |
+
setOnVideoEncoderFallbackCallback: (codecName) => instance.setOnVideoEncoderFallbackCallback(codecName),
|
| 303 |
+
setOnRtcpMessageCallback: (callback) => instance.setOnRtcpMessageCallback?.(callback),
|
| 304 |
+
presentDesktopSourcePicker: (style) => instance.presentDesktopSourcePicker(style),
|
| 305 |
+
};
|
| 306 |
+
}
|
| 307 |
+
|
| 308 |
+
VoiceEngine.createTransport = VoiceEngine._createTransport;
|
| 309 |
+
|
| 310 |
+
if (isElectronRenderer) {
|
| 311 |
+
VoiceEngine.setImageDataAllocator((width, height) => new window.ImageData(width, height));
|
| 312 |
+
}
|
| 313 |
+
|
| 314 |
+
VoiceEngine.createVoiceConnectionWithOptions = function (userId, connectionOptions, onConnectCallback) {
|
| 315 |
+
const instance = new VoiceEngine.VoiceConnection(userId, connectionOptions, onConnectCallback);
|
| 316 |
+
return bindConnectionInstance(instance);
|
| 317 |
+
};
|
| 318 |
+
VoiceEngine.createOwnStreamConnectionWithOptions = VoiceEngine.createVoiceConnectionWithOptions;
|
| 319 |
+
|
| 320 |
+
// TODO(dyc): |audioEngineId| is vestigial and does not actually get used.
|
| 321 |
+
// "default" was (we deleted audio engine IDs with the removal of android's
|
| 322 |
+
// separate gameAudio engine) hardcoded within nativelib. update the API to
|
| 323 |
+
// reflect this.
|
| 324 |
+
VoiceEngine.createReplayConnection = function (audioEngineId, callback, replayLog) {
|
| 325 |
+
if (replayLog == null) {
|
| 326 |
+
return null;
|
| 327 |
+
}
|
| 328 |
+
|
| 329 |
+
return bindConnectionInstance(new VoiceEngine.VoiceReplayConnection(replayLog, audioEngineId, callback));
|
| 330 |
+
};
|
| 331 |
+
|
| 332 |
+
const setAudioSubsystemInternal = function (subsystem, forceRestart) {
|
| 333 |
+
if (appSettings == null) {
|
| 334 |
+
log('warn', 'Unable to access app settings.');
|
| 335 |
+
return;
|
| 336 |
+
}
|
| 337 |
+
|
| 338 |
+
appSettings.set('audioSubsystem', subsystem);
|
| 339 |
+
|
| 340 |
+
if (isElectronRenderer) {
|
| 341 |
+
if (forceRestart) {
|
| 342 |
+
// DANGER: any unconditional call to setAudioSubsytem will bootloop if we don't
|
| 343 |
+
// debounce noop changes.
|
| 344 |
+
if (subsystem === audioSubsystem) {
|
| 345 |
+
return;
|
| 346 |
+
}
|
| 347 |
+
window.DiscordNative.app.relaunch();
|
| 348 |
+
} else {
|
| 349 |
+
console.log(`Deferring audio subsystem switch to ${subsystem} until next restart.`);
|
| 350 |
+
}
|
| 351 |
+
}
|
| 352 |
+
};
|
| 353 |
+
|
| 354 |
+
VoiceEngine.setAudioSubsystem = function (subsystem) {
|
| 355 |
+
setAudioSubsystemInternal(subsystem, true);
|
| 356 |
+
};
|
| 357 |
+
|
| 358 |
+
VoiceEngine.queueAudioSubsystem = function (subsystem) {
|
| 359 |
+
setAudioSubsystemInternal(subsystem, false);
|
| 360 |
+
};
|
| 361 |
+
|
| 362 |
+
VoiceEngine.setOffloadAdmControls = function (doOffload) {
|
| 363 |
+
appSettings.set('offloadAdmControls', doOffload);
|
| 364 |
+
};
|
| 365 |
+
|
| 366 |
+
VoiceEngine.setAsyncVideoInputDeviceInitSetting = function (enable) {
|
| 367 |
+
appSettings.set('asyncVideoInputDeviceInit', enable);
|
| 368 |
+
};
|
| 369 |
+
|
| 370 |
+
VoiceEngine.setAsyncClipsSourceDeinitSetting = function (enable) {
|
| 371 |
+
appSettings.set('asyncClipsSourceDeinit', enable);
|
| 372 |
+
};
|
| 373 |
+
|
| 374 |
+
VoiceEngine.setDebugLogging = function (enable) {
|
| 375 |
+
if (appSettings == null) {
|
| 376 |
+
log('warn', 'Unable to access app settings.');
|
| 377 |
+
return;
|
| 378 |
+
}
|
| 379 |
+
|
| 380 |
+
if (debugLogging === enable) {
|
| 381 |
+
return;
|
| 382 |
+
}
|
| 383 |
+
|
| 384 |
+
appSettings.set('debugLogging', enable);
|
| 385 |
+
|
| 386 |
+
if (isElectronRenderer) {
|
| 387 |
+
window.DiscordNative.app.relaunch();
|
| 388 |
+
}
|
| 389 |
+
};
|
| 390 |
+
|
| 391 |
+
VoiceEngine.getDebugLogging = function () {
|
| 392 |
+
return debugLogging;
|
| 393 |
+
};
|
| 394 |
+
|
| 395 |
+
const videoStreams = {};
|
| 396 |
+
const directVideoStreams = {};
|
| 397 |
+
|
| 398 |
+
const ensureCanvasContext = function (sinkId) {
|
| 399 |
+
let canvas = document.getElementById(sinkId);
|
| 400 |
+
if (canvas == null) {
|
| 401 |
+
for (const popout of window.popouts.values()) {
|
| 402 |
+
const element = popout.document != null && popout.document.getElementById(sinkId);
|
| 403 |
+
if (element != null) {
|
| 404 |
+
canvas = element;
|
| 405 |
+
break;
|
| 406 |
+
}
|
| 407 |
+
}
|
| 408 |
+
|
| 409 |
+
if (canvas == null) {
|
| 410 |
+
return null;
|
| 411 |
+
}
|
| 412 |
+
}
|
| 413 |
+
|
| 414 |
+
const context = canvas.getContext('2d');
|
| 415 |
+
if (context == null) {
|
| 416 |
+
log('info', `Failed to initialize context for sinkId ${sinkId}`);
|
| 417 |
+
return null;
|
| 418 |
+
}
|
| 419 |
+
|
| 420 |
+
return context;
|
| 421 |
+
};
|
| 422 |
+
|
| 423 |
+
let activeSinksChangeCallback;
|
| 424 |
+
VoiceEngine.setActiveSinksChangeCallback = function (callback) {
|
| 425 |
+
activeSinksChangeCallback = callback;
|
| 426 |
+
};
|
| 427 |
+
|
| 428 |
+
function notifyActiveSinksChange(streamId) {
|
| 429 |
+
if (activeSinksChangeCallback == null) {
|
| 430 |
+
return;
|
| 431 |
+
}
|
| 432 |
+
const sinks = videoStreams[streamId];
|
| 433 |
+
const hasVideoStreamSink = sinks != null && sinks.size > 0;
|
| 434 |
+
const hasDirectVideoStreamSink = directVideoStreams[streamId] != null;
|
| 435 |
+
|
| 436 |
+
activeSinksChangeCallback(streamId, hasVideoStreamSink || hasDirectVideoStreamSink);
|
| 437 |
+
}
|
| 438 |
+
|
| 439 |
+
// [adill] NB: with context isolation it has become extremely costly (both memory & performance) to provide the image
|
| 440 |
+
// data directly to clients at any reasonably fast interval so we've replaced setVideoOutputSink with a direct canvas
|
| 441 |
+
// renderer via addVideoOutputSink
|
| 442 |
+
const setVideoOutputSink = VoiceEngine.setVideoOutputSink;
|
| 443 |
+
const clearVideoOutputSink = (streamId) => {
|
| 444 |
+
// [adill] NB: if you don't pass a frame callback setVideoOutputSink clears the sink
|
| 445 |
+
setVideoOutputSink(streamId);
|
| 446 |
+
};
|
| 447 |
+
const signalVideoOutputSinkReady = VoiceEngine.signalVideoOutputSinkReady;
|
| 448 |
+
delete VoiceEngine.setVideoOutputSink;
|
| 449 |
+
delete VoiceEngine.signalVideoOutputSinkReady;
|
| 450 |
+
|
| 451 |
+
function addVideoOutputSinkInternal(sinkId, streamId, frameCallback) {
|
| 452 |
+
let sinks = videoStreams[streamId];
|
| 453 |
+
if (sinks == null) {
|
| 454 |
+
sinks = videoStreams[streamId] = new Map();
|
| 455 |
+
}
|
| 456 |
+
|
| 457 |
+
// notifyActiveSinksChange relies on videoStreams having the correct state
|
| 458 |
+
const needsToSubscribeToFrames = sinks.size === 0;
|
| 459 |
+
sinks.set(sinkId, frameCallback);
|
| 460 |
+
|
| 461 |
+
if (needsToSubscribeToFrames) {
|
| 462 |
+
log('info', `Subscribing to frames for streamId ${streamId}`);
|
| 463 |
+
const onFrame = (imageData) => {
|
| 464 |
+
const sinks = videoStreams[streamId];
|
| 465 |
+
if (sinks != null) {
|
| 466 |
+
for (const callback of sinks.values()) {
|
| 467 |
+
if (callback != null) {
|
| 468 |
+
callback(imageData);
|
| 469 |
+
}
|
| 470 |
+
}
|
| 471 |
+
}
|
| 472 |
+
signalVideoOutputSinkReady(streamId);
|
| 473 |
+
};
|
| 474 |
+
setVideoOutputSink(streamId, onFrame, true);
|
| 475 |
+
notifyActiveSinksChange(streamId);
|
| 476 |
+
}
|
| 477 |
+
}
|
| 478 |
+
|
| 479 |
+
VoiceEngine.addVideoOutputSink = function (sinkId, streamId, frameCallback) {
|
| 480 |
+
let canvasContext = null;
|
| 481 |
+
addVideoOutputSinkInternal(sinkId, streamId, (imageData) => {
|
| 482 |
+
if (canvasContext == null) {
|
| 483 |
+
canvasContext = ensureCanvasContext(sinkId);
|
| 484 |
+
if (canvasContext == null) {
|
| 485 |
+
return;
|
| 486 |
+
}
|
| 487 |
+
}
|
| 488 |
+
if (frameCallback != null) {
|
| 489 |
+
frameCallback(imageData.width, imageData.height);
|
| 490 |
+
}
|
| 491 |
+
// [adill] NB: Electron 9+ on macOS would show massive leaks in the the GPU helper process when a non-Discord
|
| 492 |
+
// window completely occludes the Discord window. Adding this tiny readback ameliorates the issue. We tried WebGL
|
| 493 |
+
// rendering which did not exhibit the issue, however, the context limit of 16 was too small to be a real
|
| 494 |
+
// alternative.
|
| 495 |
+
canvasContext.getImageData(0, 0, 1, 1);
|
| 496 |
+
canvasContext.putImageData(imageData, 0, 0);
|
| 497 |
+
});
|
| 498 |
+
};
|
| 499 |
+
|
| 500 |
+
VoiceEngine.removeVideoOutputSink = function (sinkId, streamId) {
|
| 501 |
+
const sinks = videoStreams[streamId];
|
| 502 |
+
if (sinks != null) {
|
| 503 |
+
sinks.delete(sinkId);
|
| 504 |
+
if (sinks.size === 0) {
|
| 505 |
+
delete videoStreams[streamId];
|
| 506 |
+
log('info', `Unsubscribing from frames for streamId ${streamId}`);
|
| 507 |
+
clearVideoOutputSink(streamId);
|
| 508 |
+
notifyActiveSinksChange(streamId);
|
| 509 |
+
}
|
| 510 |
+
}
|
| 511 |
+
};
|
| 512 |
+
|
| 513 |
+
// We wrap the direct video calls so we can keep track of all active
|
| 514 |
+
// video output sinks
|
| 515 |
+
const addDirectVideoOutputSink_ = VoiceEngine.addDirectVideoOutputSink;
|
| 516 |
+
const removeDirectVideoOutputSink_ = VoiceEngine.removeDirectVideoOutputSink;
|
| 517 |
+
VoiceEngine.addDirectVideoOutputSink = function (streamId) {
|
| 518 |
+
log('info', `Subscribing to direct frames for streamId ${streamId}`);
|
| 519 |
+
addDirectVideoOutputSink_(streamId);
|
| 520 |
+
directVideoStreams[streamId] = true;
|
| 521 |
+
notifyActiveSinksChange(streamId);
|
| 522 |
+
};
|
| 523 |
+
VoiceEngine.removeDirectVideoOutputSink = function (streamId) {
|
| 524 |
+
log('info', `Unsubscribing from direct frames for streamId ${streamId}`);
|
| 525 |
+
removeDirectVideoOutputSink_(streamId);
|
| 526 |
+
delete directVideoStreams[streamId];
|
| 527 |
+
notifyActiveSinksChange(streamId);
|
| 528 |
+
};
|
| 529 |
+
|
| 530 |
+
let sinkId = 0;
|
| 531 |
+
VoiceEngine.getNextVideoOutputFrame = function (streamId) {
|
| 532 |
+
const nextVideoFrameSinkId = `getNextVideoFrame_${++sinkId}`;
|
| 533 |
+
|
| 534 |
+
return new Promise((resolve, reject) => {
|
| 535 |
+
setTimeout(() => {
|
| 536 |
+
VoiceEngine.removeVideoOutputSink(nextVideoFrameSinkId, streamId);
|
| 537 |
+
reject(new Error('getNextVideoOutputFrame timeout'));
|
| 538 |
+
}, 5000);
|
| 539 |
+
|
| 540 |
+
addVideoOutputSinkInternal(nextVideoFrameSinkId, streamId, (imageData) => {
|
| 541 |
+
VoiceEngine.removeVideoOutputSink(nextVideoFrameSinkId, streamId);
|
| 542 |
+
resolve({
|
| 543 |
+
width: imageData.width,
|
| 544 |
+
height: imageData.height,
|
| 545 |
+
data: new Uint8ClampedArray(imageData.data.buffer),
|
| 546 |
+
});
|
| 547 |
+
});
|
| 548 |
+
});
|
| 549 |
+
};
|
| 550 |
+
|
| 551 |
+
function log(level, message) {
|
| 552 |
+
const consoleLogFn = (() => {
|
| 553 |
+
if (!['trace', 'debug', 'info', 'warn', 'error', 'log'].includes(level)) {
|
| 554 |
+
return console.info;
|
| 555 |
+
}
|
| 556 |
+
return console[level];
|
| 557 |
+
})();
|
| 558 |
+
consoleLogFn(message);
|
| 559 |
+
|
| 560 |
+
// Note: this currently races with the VoiceEngine initialization,
|
| 561 |
+
// not all logs may get logged here early in the process
|
| 562 |
+
VoiceEngine.consoleLog(level, message);
|
| 563 |
+
}
|
| 564 |
+
|
| 565 |
+
console.log(`Initializing voice engine with audio subsystem: ${audioSubsystem}`);
|
| 566 |
+
VoiceEngine.platform = process.platform;
|
| 567 |
+
VoiceEngine.initialize({
|
| 568 |
+
audioSubsystem,
|
| 569 |
+
logLevel,
|
| 570 |
+
dataDirectory,
|
| 571 |
+
logDirectory,
|
| 572 |
+
useFakeVideoCapture,
|
| 573 |
+
useFileForFakeVideoCapture,
|
| 574 |
+
useFakeAudioCapture,
|
| 575 |
+
useFilesForFakeAudioCapture,
|
| 576 |
+
offloadAdmControls,
|
| 577 |
+
asyncVideoInputDeviceInit,
|
| 578 |
+
asyncClipsSourceDeinit,
|
| 579 |
+
});
|
| 580 |
+
|
| 581 |
+
module.exports = VoiceEngine;
|
manifest.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"files": [
|
| 3 |
+
"discord_voice.node",
|
| 4 |
+
"gpu_encoder_helper.exe",
|
| 5 |
+
"selfie_segmentation.tflite",
|
| 6 |
+
"selfie_segmentation_landscape.tflite",
|
| 7 |
+
"mediapipe.dll",
|
| 8 |
+
"package.json",
|
| 9 |
+
"index.js",
|
| 10 |
+
"manifest.json"
|
| 11 |
+
]
|
| 12 |
+
}
|
mediapipe.dll
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:b0eb7c4d2e4a211796ead7c0a78666940d4571c6c78cfa1eb9990a035ec107b3
|
| 3 |
+
size 8667576
|
package.json
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"private": "true"
|
| 3 |
+
}
|
selfie_segmentation.tflite
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:9ee168ec7c8f2a16c56fe8e1cfbc514974cbbb7e434051b455635f1bd1462f5c
|
| 3 |
+
size 249505
|
selfie_segmentation_landscape.tflite
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a77d03f4659b9f6b6c1f5106947bf40e99d7655094b6527f214ea7d451106edd
|
| 3 |
+
size 250145
|