diff --git a/Cargo.toml b/Cargo.toml index d7a1426..b62fe90 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,8 @@ edition = "2021" [dependencies] rfd = "0.11" -whisper-rs = "0.8" -hound = "3" -tokio = { version = "1", features = ["rt-multi-thread"] } \ No newline at end of file +tokio = { version = "1", features = ["rt-multi-thread"] } +eframe = "0.22" +egui = "0.22" +font-kit = "0.11" +whisper_cli = "0.1" \ No newline at end of file diff --git a/src/font.rs b/src/font.rs new file mode 100644 index 0000000..9f47acb --- /dev/null +++ b/src/font.rs @@ -0,0 +1,22 @@ +use font_kit::source::SystemSource; + +pub fn load_fonts(ctx: &egui::Context) { + let sys = SystemSource::new(); + let font_name = format!("SimHei"); + let font = sys.select_family_by_name(&font_name).unwrap().fonts()[0] + .load() + .unwrap() + .copy_font_data() + .unwrap() + .to_vec(); + let mut font_defs = egui::FontDefinitions::default(); + font_defs + .font_data + .insert(font_name.to_string(), egui::FontData::from_owned(font)); + font_defs + .families + .get_mut(&egui::FontFamily::Proportional) + .unwrap() + .insert(0, font_name); + ctx.set_fonts(font_defs); +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 916c513..3c59258 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,109 +1,24 @@ -use std::{env, fs, io, thread}; -use std::io::Error; -use std::io::ErrorKind; -use std::path::PathBuf; -use std::process::Command; -use std::sync::{Arc, Mutex}; -use tokio::runtime::Runtime; +use eframe::NativeOptions; +use egui::Vec2; +use crate::ui::Conv; -#[derive(Debug, Clone, Default)] -struct Files { - audio: Option, - image: Option, - subtitle: Option, -} +mod ui; +mod font; +mod utils; -#[derive(Debug, Clone)] -struct Conv { - rt: Arc, - files: Arc>, +fn main() { + run(); } -impl Conv { - pub fn new() -> Self { - let rt = tokio::runtime::Builder::new_multi_thread() - .enable_all() - .build() - .unwrap(); - Self { - rt: Arc::new(rt), - files: Arc::new(Mutex::new(Files::default())), - } - } - - fn open_audio(&self) { - let mut audio = self.files.clone(); - // thread::spawn(move || { - if let Some(path) = rfd::FileDialog::new() - .pick_file() { - audio.lock().unwrap().audio = Some(path); - } - // }); - } - - fn open_image(&self) { - let mut image = self.files.clone(); - thread::spawn(move || { - if let Some(path) = rfd::FileDialog::new() - .pick_file() { - image.lock().unwrap().image = Some(path); - } - }); - } - - fn open_subtitle(&self) { - let mut subtitle = self.files.clone(); - thread::spawn(move || { - if let Some(path) = rfd::FileDialog::new() - .pick_file() { - subtitle.lock().unwrap().subtitle = Some(path); - } - }); - } -} - -fn main() -> io::Result<()> { - let current = env::current_dir()?; - let mut con = Conv::new(); - con.rt.spawn(async move { - println!("OK"); - }); - - con.open_audio(); - - let output = current.join("output.mp4"); - if output.exists() { - fs::remove_file(output).unwrap_or(()); - } - let ffmpeg = current.join("ffmpeg"); - let mut cmd = Command::new(&ffmpeg); - - if let Some(ref image) = con.files.lock().unwrap().image { - cmd - .arg("-loop") - .arg("1") - .arg("-i") - .arg(image); - } - - if let Some(ref audio) = con.files.lock().unwrap().audio { - cmd - .arg("-i") - .arg(audio); - } else { - return Err(Error::from(ErrorKind::Other)); - } - - cmd - .arg("-shortest") - .arg("output.mp4"); - - let exit_status = cmd - .spawn()? - .wait()?; - - return match exit_status.success() { - true => Ok(()), - false => Err(Error::from(ErrorKind::Unsupported)), +fn run() { + let option = NativeOptions { + icon_data: None, + initial_window_size: Some(Vec2::new(400.0, 300.0)), + follow_system_theme: true, + centered: true, + resizable: false, + ..NativeOptions::default() }; + eframe::run_native("Conv", option, Box::new(|cc| Box::new(Conv::new(cc)))) + .unwrap(); } diff --git a/src/ui.rs b/src/ui.rs new file mode 100644 index 0000000..7ae92fe --- /dev/null +++ b/src/ui.rs @@ -0,0 +1,131 @@ +use std::path::PathBuf; +use std::sync::{Arc, Mutex}; +use std::thread; +use eframe::{CreationContext, Frame}; +use egui::{Context, FontId}; +use egui::FontFamily::Proportional; +use egui::TextStyle::*; +use tokio::runtime::Runtime; +use crate::font::load_fonts; + +#[derive(Debug, Clone)] +pub struct Conv { + rt: Arc, + files: Arc>, +} + +#[derive(Debug, Clone, Default)] +struct Files { + audio: Option, + image: Option, + subtitle: Option, +} + +impl Conv { + pub fn new(cc: &CreationContext) -> Self { + load_fonts(&cc.egui_ctx); + let mut style = (*cc.egui_ctx.style()).clone(); + style.text_styles = [ + (Heading, FontId::new(30.0, Proportional)), + (Name("Heading2".into()), FontId::new(25.0, Proportional)), + (Name("Context".into()), FontId::new(23.0, Proportional)), + (Body, FontId::new(18.0, Proportional)), + (Monospace, FontId::new(14.0, Proportional)), + (Button, FontId::new(14.0, Proportional)), + (Small, FontId::new(10.0, Proportional)), + ] + .into(); + cc.egui_ctx.set_style(style); + + let rt = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .unwrap(); + Self { + rt: Arc::new(rt), + files: Default::default(), + } + } + + fn open_audio(files: Arc>) { + thread::spawn(move || { + if let Some(path) = rfd::FileDialog::new() + .add_filter("Audio File", &["mp3", "wav"]) + .pick_file() { + files.lock().unwrap().audio = Some(path); + } + }); + } + + fn open_image(files: Arc>) { + thread::spawn(move || { + if let Some(path) = rfd::FileDialog::new() + .add_filter("Image File", &["jpg", "png"]) + .pick_file() { + files.lock().unwrap().image = Some(path); + } + }); + } + + fn open_subtitle(files: Arc>) { + thread::spawn(move || { + if let Some(path) = rfd::FileDialog::new() + .add_filter("Subtitle File", &["srt", "lrc", "vtt"]) + .pick_file() { + files.lock().unwrap().subtitle = Some(path); + } + }); + } +} + +impl eframe::App for Conv { + fn update(&mut self, ctx: &Context, _: &mut Frame) { + ctx.request_repaint(); + + egui::CentralPanel::default().show(ctx, |ui| { + ui.vertical_centered(|ui| ui.heading("Conv")); + + ui.separator(); + + ui.horizontal(|ui| { + ui.label("选择音频"); + if ui.button("打开").clicked() { + Conv::open_audio(self.files.clone()); + } + }); + ui.label(format!("音频: {}", if let Some(ref p) = self.files.lock().unwrap().audio { + p.to_str().unwrap() + } else { + "None" + })); + + ui.horizontal(|ui| { + ui.label("选择背景图片"); + if ui.button("打开").clicked() { + Conv::open_image(self.files.clone()); + } + }); + ui.label(format!("背景图片: {}", if let Some(ref p) = self.files.lock().unwrap().image { + p.to_str().unwrap() + } else { + "None" + })); + + ui.horizontal(|ui| { + ui.label("选择字幕"); + if ui.button("打开").clicked() { + Conv::open_subtitle(self.files.clone()); + } + }); + ui.label(format!("字幕: {}", if let Some(ref p) = self.files.lock().unwrap().subtitle { + p.to_str().unwrap() + } else { + "None" + })); + + ui.separator(); + + + }); + } +} \ No newline at end of file diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..ef20dfd --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,45 @@ +use std::{env, fs::File, io::{BufRead, BufReader, Write}}; +use std::path::{Path, PathBuf}; + +fn is_number(s: &str) -> bool { + s.chars().all(|c| c.is_digit(10)) +} + +// 将srt时间格式转换为lrc时间格式 +fn format_time(time: &str) -> String { + let msec = &time[9..12]; + let sec = &time[6..8]; + let min = &time[3..5]; + + format!("[{}:{}.{:.2}]", min, sec, msec) +} + +fn srt2lrc(path: &str) -> Result<(), Box> { + let output_filename = Path::new(path).with_extension("lrc"); + + let file = File::open(path)?; + let reader = BufReader::new(file); + let lines = reader.lines().filter_map(|l| l.ok()); + + let lrc_content = lines.fold(String::new(), |lrc, line| { + if is_number(&line) { + // 行号 + lrc + } else if line.is_empty() { + // 空行 + lrc + } else { + // 时间和文本内容 + let time = line.split_whitespace().nth(0).unwrap(); + let text = line.splitn(2, char::is_whitespace).nth(1).unwrap(); + + let lrc_time = format_time(time); + format!("{}{}\n", lrc, lrc_time + " " + text) + } + }); + + let mut output_file = File::create(output_filename)?; + output_file.write_all(lrc_content.as_bytes())?; + + Ok(()) +}