vosk model selection in GUI

This commit is contained in:
Priler
2026-01-05 03:38:04 +05:00
parent 36acacf259
commit cab53abcbe
14 changed files with 323 additions and 40 deletions

View File

@@ -7,7 +7,7 @@ repository.workspace = true
edition.workspace = true
[dependencies]
jarvis-core = { path = "../jarvis-core" }
jarvis-core = { path = "../jarvis-core", features = ["intent"] }
once_cell.workspace = true
log.workspace = true
simple-log = "2.4"

View File

@@ -64,7 +64,7 @@ pub fn init_dirs() -> Result<(), String> {
*/
pub const DEFAULT_AUDIO_TYPE: AudioType = AudioType::Kira;
pub const DEFAULT_RECORDER_TYPE: RecorderType = RecorderType::PvRecorder;
pub const DEFAULT_WAKE_WORD_ENGINE: WakeWordEngine = WakeWordEngine::Rustpotter;
pub const DEFAULT_WAKE_WORD_ENGINE: WakeWordEngine = WakeWordEngine::Vosk;
pub const DEFAULT_INTENT_RECOGNITION_ENGINE: IntentRecognitionEngine = IntentRecognitionEngine::IntentClassifier;
pub const DEFAULT_SPEECH_TO_TEXT_ENGINE: SpeechToTextEngine = SpeechToTextEngine::Vosk;
@@ -121,19 +121,20 @@ pub const RUSTPOTTER_DEFAULT_CONFIG: Lazy<RustpotterConfig> = Lazy::new(|| {
});
// PICOVOICE
pub const COMMANDS_PATH: &str = "commands/";
pub const KEYWORDS_PATH: &str = "picovoice/keywords/";
pub const COMMANDS_PATH: &str = "resources/commands/";
pub const KEYWORDS_PATH: &str = "resources/picovoice/keywords/";
pub const DEFAULT_KEYWORD: &str = "jarvis_windows.ppn";
pub const DEFAULT_SENSITIVITY: f32 = 1.0;
// VOSK
// pub const VOSK_MODEL_PATH: &str = const_concat!(PUBLIC_PATH, "/vosk/model_small");
pub const VOSK_MODELS_PATH: &str = "resources/vosk";
pub const VOSK_MODEL_PATH: &str = "resources/vosk/model_small";
pub const VOSK_FETCH_PHRASE: &str = "джарвис";
pub const VOSK_MODEL_PATH: &str = "vosk/model_small";
pub const VOSK_MIN_RATIO: f64 = 70.0;
// IRE (intents recognition)
pub const INTENT_CLASSIFIER_MIN_CONFIDENCE: f64 = 0.5;
pub const INTENT_CLASSIFIER_MIN_CONFIDENCE: f64 = 0.75;
// ETC
pub const CMD_RATIO_THRESHOLD: f64 = 65f64;

View File

@@ -14,6 +14,8 @@ pub struct Settings {
pub intent_recognition_engine: IntentRecognitionEngine,
pub speech_to_text_engine: SpeechToTextEngine,
pub vosk_model: String,
pub api_keys: ApiKeys,
}
@@ -27,6 +29,8 @@ impl Default for Settings {
intent_recognition_engine: config::DEFAULT_INTENT_RECOGNITION_ENGINE,
speech_to_text_engine: config::DEFAULT_SPEECH_TO_TEXT_ENGINE,
vosk_model: String::from(""), // auto detect first available
api_keys: ApiKeys {
picovoice: String::from(""),
openai: String::from(""),

View File

@@ -23,9 +23,17 @@ pub mod stt;
#[cfg(feature = "intent")]
pub mod intent;
pub mod vosk_models;
// shared statics
pub static APP_DIR: Lazy<PathBuf> = Lazy::new(|| std::env::current_dir().unwrap());
pub static SOUND_DIR: Lazy<PathBuf> = Lazy::new(|| APP_DIR.clone().join("sound"));
// pub static APP_DIR: Lazy<PathBuf> = Lazy::new(|| std::env::current_dir().unwrap());
pub static APP_DIR: Lazy<PathBuf> = Lazy::new(|| {
std::env::current_exe()
.ok()
.and_then(|p| p.parent().map(|p| p.to_path_buf()))
.unwrap_or_else(|| std::env::current_dir().unwrap())
});
pub static SOUND_DIR: Lazy<PathBuf> = Lazy::new(|| APP_DIR.clone().join("resources/sound"));
pub static APP_DIRS: OnceCell<AppDirs> = OnceCell::new();
pub static APP_CONFIG_DIR: OnceCell<PathBuf> = OnceCell::new();
pub static APP_LOG_DIR: OnceCell<PathBuf> = OnceCell::new();

View File

@@ -6,6 +6,9 @@ use once_cell::sync::OnceCell;
use crate::config::structs::SpeechToTextEngine;
use crate::vosk_models;
// use vosk_models::{scan_vosk_models, get_model_path, VoskModelInfo};
static STT_TYPE: OnceCell<SpeechToTextEngine> = OnceCell::new();
pub fn init() -> Result<(), ()> {
@@ -30,6 +33,7 @@ pub fn init() -> Result<(), ()> {
Ok(())
}
pub fn recognize(data: &[i16], partial: bool) -> Option<String> {
match STT_TYPE.get().unwrap() {
SpeechToTextEngine::Vosk => vosk::recognize(data, partial),

View File

@@ -3,18 +3,26 @@ use vosk::{DecodingState, Model, Recognizer};
use std::sync::Mutex;
use crate::config::VOSK_MODEL_PATH;
// use crate::config::VOSK_MODEL_PATH;
use crate::config;
use crate::stt::vosk_models;
use crate::DB;
static MODEL: OnceCell<Model> = OnceCell::new();
static RECOGNIZER: OnceCell<Mutex<Recognizer>> = OnceCell::new();
pub fn init_vosk() {
pub fn init_vosk() -> Result<(), String> {
if RECOGNIZER.get().is_some() {
return;
return Ok(());
} // already initialized
let model = Model::new(VOSK_MODEL_PATH).unwrap();
let mut recognizer = Recognizer::new(&model, 16000.0).unwrap();
let model_path = get_configured_model_path()?;
let model = Model::new(model_path.to_str().unwrap())
.ok_or_else(|| format!("Failed to load Vosk model from: {}", model_path.display()))?;
let mut recognizer = Recognizer::new(&model, 16000.0)
.ok_or("Failed to create Vosk recognizer")?;
recognizer.set_max_alternatives(10);
recognizer.set_words(true);
@@ -22,6 +30,8 @@ pub fn init_vosk() {
MODEL.set(model);
RECOGNIZER.set(Mutex::new(recognizer));
Ok(())
}
pub fn recognize(data: &[i16], include_partial: bool) -> Option<String> {
@@ -73,6 +83,34 @@ pub fn recognize(data: &[i16], include_partial: bool) -> Option<String> {
}
}
fn get_configured_model_path() -> Result<std::path::PathBuf, String> {
// try to get from settings
if let Some(db) = DB.get() {
let settings = db.read();
if !settings.vosk_model.is_empty() {
if let Some(path) = vosk_models::get_model_path(&settings.vosk_model) {
return Ok(path);
}
warn!("Configured Vosk model '{}' not found, falling back to auto-detect", settings.vosk_model);
}
}
// auto-detect: use first available model
let available = vosk_models::scan_vosk_models();
if let Some(first) = available.first() {
info!("Auto-detected Vosk model: {}", first.name);
return Ok(first.path.clone());
}
// fallback to legacy path
let legacy_path = std::path::Path::new(config::VOSK_MODEL_PATH);
if legacy_path.exists() {
return Ok(legacy_path.to_path_buf());
}
Err("No Vosk models found".into())
}
// pub fn stereo_to_mono(input_data: &[i16]) -> Vec<i16> {
// let mut result = Vec::with_capacity(input_data.len() / 2);
// result.extend(

View File

@@ -0,0 +1,113 @@
use std::fs;
use std::path::{Path, PathBuf};
use crate::{APP_DIR, config};
#[derive(Debug, Clone)]
pub struct VoskModelInfo {
pub name: String, // folder name: "vosk-model-small-ru-0.22"
pub path: PathBuf, // full path
pub language: String, // extracted from name: "ru"
pub size: String, // "small", "large", etc.
}
// Scan for available Vosk models
pub fn scan_vosk_models() -> Vec<VoskModelInfo> {
let models_dir = {
APP_DIR.join(config::VOSK_MODELS_PATH)
};
let mut models = Vec::new();
info!("TESTTTTTTTTTTTTT: {}", models_dir.display());
if !models_dir.exists() {
warn!("Vosk models directory not found: {}", models_dir.display());
return models;
}
let entries = match fs::read_dir(models_dir) {
Ok(e) => e,
Err(e) => {
warn!("Failed to read vosk models directory: {}", e);
return models;
}
};
for entry in entries {
let entry = match entry {
Ok(e) => e,
Err(_) => continue,
};
let path = entry.path();
// must be a directory
if !path.is_dir() {
continue;
}
// check if it looks like a vosk model (has am/conf/graph folders or similar)
if !is_vosk_model(&path) {
continue;
}
let name = path.file_name()
.and_then(|n| n.to_str())
.unwrap_or("")
.to_string();
let (language, size) = parse_model_name(&name);
models.push(VoskModelInfo {
name,
path,
language,
size,
});
}
models.sort_by(|a, b| a.name.cmp(&b.name));
models
}
// Check if directory looks like a Vosk model
fn is_vosk_model(path: &Path) -> bool {
// vosk models typically have these subdirectories
path.join("am").exists() ||
path.join("conf").exists() ||
path.join("graph").exists() ||
path.join("ivector").exists()
}
// Extract language and size from model name
// e.g., "vosk-model-small-ru-0.22" -> ("ru", "small")
fn parse_model_name(name: &str) -> (String, String) {
let parts: Vec<&str> = name.split('-').collect();
let mut language = String::from("unknown");
let mut size = String::from("unknown");
// look for common size indicators
for part in &parts {
match *part {
"small" | "big" | "large" | "lgraph" => size = part.to_string(),
// language codes are usually 2 letters
s if s.len() == 2 && s.chars().all(|c| c.is_alphabetic()) => {
language = s.to_string();
}
_ => {}
}
}
(language, size)
}
// Get model path by name
pub fn get_model_path(model_name: &str) -> Option<PathBuf> {
let path = Path::new(config::VOSK_MODELS_PATH).join(model_name);
if path.exists() && path.is_dir() {
Some(path)
} else {
None
}
}

View File

@@ -61,6 +61,9 @@ fn main() {
tauri_commands::get_peak_ram_usage,
tauri_commands::get_cpu_temp,
tauri_commands::get_cpu_usage,
// vosk
tauri_commands::list_vosk_models,
])
.run(tauri::generate_context!())
.expect("error while running tauri application");

View File

@@ -22,3 +22,7 @@ pub use fs::*;
// import SYS commands
mod sys;
pub use sys::*;
// import STT commands
mod stt;
pub use stt::*;

View File

@@ -10,6 +10,7 @@ pub fn db_read(state: tauri::State<'_, AppState>, key: &str) -> String {
"assistant_voice" => settings.voice.clone(),
"selected_wake_word_engine" => format!("{:?}", settings.wake_word_engine),
"selected_intent_recognition_engine" => format!("{:?}", settings.intent_recognition_engine),
"selected_vosk_model" => settings.vosk_model.clone(),
"speech_to_text_engine" => format!("{:?}", settings.speech_to_text_engine),
"api_key__picovoice" => settings.api_keys.picovoice.clone(),
"api_key__openai" => settings.api_keys.openai.clone(),
@@ -49,6 +50,9 @@ pub fn db_write(state: tauri::State<'_, AppState>, key: &str, val: &str) -> bool
_ => return false,
}
}
"selected_vosk_model" => {
settings.vosk_model = val.to_string();
}
"api_key__picovoice" => {
settings.api_keys.picovoice = val.to_string();
}

View File

@@ -0,0 +1,21 @@
use jarvis_core::{vosk_models, DB};
use serde::Serialize;
#[derive(Serialize)]
pub struct VoskModel {
pub name: String,
pub language: String,
pub size: String,
}
#[tauri::command]
pub fn list_vosk_models() -> Vec<VoskModel> {
vosk_models::scan_vosk_models()
.into_iter()
.map(|m| VoskModel {
name: m.name,
language: m.language,
size: m.size,
})
.collect()
}

View File

@@ -39,6 +39,7 @@
}
let availableMicrophones: MicrophoneOption[] = []
let availableVoskModels: { label: string; value: string }[] = []
let settingsSaved = false
let saveButtonDisabled = false
@@ -47,6 +48,7 @@
let selectedMicrophone = ""
let selectedWakeWordEngine = ""
let selectedIntentRecognitionEngine = ""
let selectedVoskModel = ""
let apiKeyPicovoice = ""
let apiKeyOpenai = ""
@@ -73,6 +75,7 @@
invoke("db_write", { key: "selected_microphone", val: selectedMicrophone }),
invoke("db_write", { key: "selected_wake_word_engine", val: selectedWakeWordEngine }),
invoke("db_write", { key: "selected_intent_recognition_engine", val: selectedIntentRecognitionEngine }),
invoke("db_write", { key: "selected_vosk_model", val: selectedVoskModel }),
invoke("db_write", { key: "api_key__picovoice", val: apiKeyPicovoice }),
invoke("db_write", { key: "api_key__openai", val: apiKeyOpenai })
])
@@ -107,11 +110,19 @@
value: String(idx)
}))
// load vosk models
const voskModels = await invoke<{ name: string; language: string; size: string }[]>("list_vosk_models")
availableVoskModels = voskModels.map(m => ({
label: `${m.name} (${m.language}, ${m.size})`,
value: m.name
}))
// load settings from db
const [mic, wakeWord, intentReco, pico, openai] = await Promise.all([
const [mic, wakeWord, intentReco, voskModel, pico, openai] = await Promise.all([
invoke<string>("db_read", { key: "selected_microphone" }),
invoke<string>("db_read", { key: "selected_wake_word_engine" }),
invoke<string>("db_read", { key: "selected_intent_recognition_engine" }),
invoke<string>("db_read", { key: "selected_vosk_model" }),
invoke<string>("db_read", { key: "api_key__picovoice" }),
invoke<string>("db_read", { key: "api_key__openai" })
])
@@ -119,6 +130,7 @@
selectedMicrophone = mic
selectedWakeWordEngine = wakeWord
selectedIntentRecognitionEngine = intentReco
selectedVoskModel = voskModel
apiKeyPicovoice = pico
apiKeyOpenai = openai
} catch (err) {
@@ -232,6 +244,29 @@
</Alert>
{/if}
<Space h="xl" />
{#key availableVoskModels}
<NativeSelect
data={[
{ label: "Авто-определение", value: "" },
...availableVoskModels
]}
label="Модель распознавания речи (Vosk)"
description="Выберите модель Vosk для распознавания речи."
variant="filled"
bind:value={selectedVoskModel}
/>
{/key}
{#if availableVoskModels.length === 0}
<Space h="sm" />
<Alert title="Модели не найдены" color="orange" variant="outline">
<Text size="sm" color="gray">
Поместите модели Vosk в папку resources/vosk
</Text>
</Alert>
{/if}
<Space h="xl" />
<NativeSelect
data={[

View File

@@ -0,0 +1,40 @@
// vite.config.ts
import { defineConfig } from "file:///D:/Rust/jarvis-app/frontend/node_modules/vite/dist/node/index.js";
import { svelte } from "file:///D:/Rust/jarvis-app/frontend/node_modules/@sveltejs/vite-plugin-svelte/src/index.js";
import sveltePreprocess from "file:///D:/Rust/jarvis-app/frontend/node_modules/svelte-preprocess/dist/index.js";
import tsconfigPaths from "file:///D:/Rust/jarvis-app/frontend/node_modules/vite-tsconfig-paths/dist/index.mjs";
import routify from "file:///D:/Rust/jarvis-app/frontend/node_modules/@roxi/routify/lib/extra/vite-plugin/vite-plugin.js";
var vite_config_default = defineConfig({
plugins: [
svelte({
preprocess: [
sveltePreprocess({
typescript: true
})
],
onwarn: (warning, handler) => {
const { code, frame } = warning;
if (code === "css-unused-selector")
return;
handler(warning);
}
}),
routify(),
tsconfigPaths()
],
clearScreen: false,
server: {
port: 1420,
strictPort: true
},
envPrefix: ["VITE_", "TAURI_"],
build: {
target: process.env.TAURI_PLATFORM == "windows" ? "chrome105" : "safari13",
minify: !process.env.TAURI_DEBUG ? "esbuild" : false,
sourcemap: !!process.env.TAURI_DEBUG
}
});
export {
vite_config_default as default
};
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCJEOlxcXFxSdXN0XFxcXGphcnZpcy1hcHBcXFxcZnJvbnRlbmRcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZmlsZW5hbWUgPSBcIkQ6XFxcXFJ1c3RcXFxcamFydmlzLWFwcFxcXFxmcm9udGVuZFxcXFx2aXRlLmNvbmZpZy50c1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9pbXBvcnRfbWV0YV91cmwgPSBcImZpbGU6Ly8vRDovUnVzdC9qYXJ2aXMtYXBwL2Zyb250ZW5kL3ZpdGUuY29uZmlnLnRzXCI7aW1wb3J0IHsgZGVmaW5lQ29uZmlnIH0gZnJvbSBcInZpdGVcIjtcclxuaW1wb3J0IHsgc3ZlbHRlIH0gZnJvbSBcIkBzdmVsdGVqcy92aXRlLXBsdWdpbi1zdmVsdGVcIjtcclxuaW1wb3J0IHN2ZWx0ZVByZXByb2Nlc3MgZnJvbSBcInN2ZWx0ZS1wcmVwcm9jZXNzXCI7XHJcbmltcG9ydCB0c2NvbmZpZ1BhdGhzIGZyb20gJ3ZpdGUtdHNjb25maWctcGF0aHMnXHJcbmltcG9ydCByb3V0aWZ5IGZyb20gJ0Byb3hpL3JvdXRpZnkvdml0ZS1wbHVnaW4nXHJcblxyXG5leHBvcnQgZGVmYXVsdCBkZWZpbmVDb25maWcoe1xyXG4gIHBsdWdpbnM6IFtcclxuICAgIHN2ZWx0ZSh7XHJcbiAgICAgIHByZXByb2Nlc3M6IFtcclxuICAgICAgICBzdmVsdGVQcmVwcm9jZXNzKHtcclxuICAgICAgICAgIHR5cGVzY3JpcHQ6IHRydWUsXHJcbiAgICAgICAgfSksXHJcbiAgICAgIF0sXHJcbiAgICAgIG9ud2FybjogKHdhcm5pbmcsIGhhbmRsZXIpID0+IHtcclxuICAgICAgICBjb25zdCB7IGNvZGUsIGZyYW1lIH0gPSB3YXJuaW5nO1xyXG4gICAgICAgIGlmIChjb2RlID09PSBcImNzcy11bnVzZWQtc2VsZWN0b3JcIilcclxuICAgICAgICAgICAgcmV0dXJuO1xyXG5cclxuICAgICAgICBoYW5kbGVyKHdhcm5pbmcpO1xyXG4gICAgICB9LFxyXG4gICAgfSksXHJcbiAgICByb3V0aWZ5KCksXHJcbiAgICB0c2NvbmZpZ1BhdGhzKClcclxuICBdLFxyXG5cclxuICBjbGVhclNjcmVlbjogZmFsc2UsXHJcbiAgc2VydmVyOiB7XHJcbiAgICBwb3J0OiAxNDIwLFxyXG4gICAgc3RyaWN0UG9ydDogdHJ1ZSxcclxuICB9LFxyXG4gIGVudlByZWZpeDogW1wiVklURV9cIiwgXCJUQVVSSV9cIl0sXHJcbiAgYnVpbGQ6IHtcclxuICAgIHRhcmdldDogcHJvY2Vzcy5lbnYuVEFVUklfUExBVEZPUk0gPT0gXCJ3aW5kb3dzXCIgPyBcImNocm9tZTEwNVwiIDogXCJzYWZhcmkxM1wiLFxyXG4gICAgbWluaWZ5OiAhcHJvY2Vzcy5lbnYuVEFVUklfREVCVUcgPyBcImVzYnVpbGRcIiA6IGZhbHNlLFxyXG4gICAgc291cmNlbWFwOiAhIXByb2Nlc3MuZW52LlRBVVJJX0RFQlVHLFxyXG4gIH0sXHJcbn0pOyJdLAogICJtYXBwaW5ncyI6ICI7QUFBMlEsU0FBUyxvQkFBb0I7QUFDeFMsU0FBUyxjQUFjO0FBQ3ZCLE9BQU8sc0JBQXNCO0FBQzdCLE9BQU8sbUJBQW1CO0FBQzFCLE9BQU8sYUFBYTtBQUVwQixJQUFPLHNCQUFRLGFBQWE7QUFBQSxFQUMxQixTQUFTO0FBQUEsSUFDUCxPQUFPO0FBQUEsTUFDTCxZQUFZO0FBQUEsUUFDVixpQkFBaUI7QUFBQSxVQUNmLFlBQVk7QUFBQSxRQUNkLENBQUM7QUFBQSxNQUNIO0FBQUEsTUFDQSxRQUFRLENBQUMsU0FBUyxZQUFZO0FBQzVCLGNBQU0sRUFBRSxNQUFNLE1BQU0sSUFBSTtBQUN4QixZQUFJLFNBQVM7QUFDVDtBQUVKLGdCQUFRLE9BQU87QUFBQSxNQUNqQjtBQUFBLElBQ0YsQ0FBQztBQUFBLElBQ0QsUUFBUTtBQUFBLElBQ1IsY0FBYztBQUFBLEVBQ2hCO0FBQUEsRUFFQSxhQUFhO0FBQUEsRUFDYixRQUFRO0FBQUEsSUFDTixNQUFNO0FBQUEsSUFDTixZQUFZO0FBQUEsRUFDZDtBQUFBLEVBQ0EsV0FBVyxDQUFDLFNBQVMsUUFBUTtBQUFBLEVBQzdCLE9BQU87QUFBQSxJQUNMLFFBQVEsUUFBUSxJQUFJLGtCQUFrQixZQUFZLGNBQWM7QUFBQSxJQUNoRSxRQUFRLENBQUMsUUFBUSxJQUFJLGNBQWMsWUFBWTtBQUFBLElBQy9DLFdBQVcsQ0FBQyxDQUFDLFFBQVEsSUFBSTtBQUFBLEVBQzNCO0FBQ0YsQ0FBQzsiLAogICJuYW1lcyI6IFtdCn0K

View File

@@ -1,27 +1,26 @@
# Simple python script used to
# copy some libraries to the "target" directory
# after Rust build
# Note that Rust build should be run via "cargo make <cmd>" command
# in order to automate all the compile process
import os
from pathlib import Path
import shutil
# some config vars
# format: (source, destination_name)
SOURCE = (
"resources/commands/",
"resources/vosk/",
"resources/lib/",
"resources/keywords/",
"resources/rustpotter/",
"resources/sound/",
"lib/windows/amd64/libgcc_s_seh-1.dll",
"lib/windows/amd64/libstdc++-6.dll",
"lib/windows/amd64/libvosk.dll",
"lib/windows/amd64/libvosk.lib",
"lib/windows/amd64/libwinpthread-1.dll"
("resources/commands/", "resources/commands/"),
("resources/vosk/", "resources/vosk/"),
("resources/lib/", "lib/"),
("resources/keywords/", "resources/keywords/"),
("resources/rustpotter/", "resources/rustpotter/"),
("resources/sound/", "resources/sound/"),
("lib/windows/amd64/libgcc_s_seh-1.dll", None),
("lib/windows/amd64/libstdc++-6.dll", None),
("lib/windows/amd64/libvosk.dll", None),
("lib/windows/amd64/libvosk.lib", None),
("lib/windows/amd64/libwinpthread-1.dll", None)
)
TARGET_DIRS = (
@@ -39,27 +38,36 @@ for tdir in TARGET_DIRS:
continue
# copy lib files
for src in SOURCE:
if os.path.isdir(ABS_PATH + src):
for entry in SOURCE:
if isinstance(entry, tuple):
src, dest_name = entry
else:
src, dest_name = entry, None
src_path = ABS_PATH + src
if os.path.isdir(src_path):
# copy the whole directory
full_target_dir_path = os.path.join(tdir, src)
target_name = dest_name if dest_name else os.path.basename(src.rstrip('/'))
full_target_dir_path = os.path.join(tdir, target_name)
if os.path.isdir(full_target_dir_path):
print("[-] Directory already exists, skipping: ", src)
print("[-] Directory already exists, skipping: ", src, "->", target_name)
else:
shutil.copytree(ABS_PATH + src, os.path.join(tdir, src))
shutil.copytree(src_path, full_target_dir_path)
print("[+] Directory copied: ", src, "->", target_name)
print("[+] Directory copied: ", src)
elif os.path.isfile(ABS_PATH + src):
elif os.path.isfile(src_path):
# copy file
full_target_file_path = os.path.join(tdir, src)
target_name = dest_name if dest_name else os.path.basename(src)
full_target_file_path = os.path.join(tdir, target_name)
if os.path.isfile(full_target_file_path):
print("[-] File already exists, skipping: ", src)
print("[-] File already exists, skipping: ", src, "->", target_name)
else:
shutil.copy(ABS_PATH + src, tdir)
print("[+] File copied: ", src)
shutil.copy(src_path, full_target_file_path)
print("[+] File copied: ", src, "->", target_name)
else:
print("[?] Unknown entity to copy: ", src)
print("Post compile build done.")