Skip to content

Commit 7ef0e07

Browse files
committed
Merge branch 'main' of github.com:utmp/OpenConvert-desktop
2 parents b82cbbf + a645f96 commit 7ef0e07

8 files changed

Lines changed: 521 additions & 217 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
## Supported files
2121
- Image: png, jpg, gif, webp, jxl, bmp, avif, tiff
22+
- Document: pdf, epub, xps, cbz, mobi, fb2 and many more ...
2223
- ~~Video: mp4, mkv, avi, mov, gif, 3gp~~
2324
- ~~Audio: aac, mp3, mp4a, wav~~
2425
## 🛠 Tech stack

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "openconvert",
3-
"version": "1.0.0",
3+
"version": "1.2.0",
44
"description": "All in one file converter",
55
"main": "./out/main/index.js",
66
"author": "utmp",

src/main/index.js

Lines changed: 110 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11

2-
import { app, shell, BrowserWindow, ipcMain, dialog } from 'electron'
2+
import { app, shell, BrowserWindow, ipcMain, dialog,session,screen } from 'electron'
33
import { join } from 'path'
44
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
55
import icon from '../../resources/icon.png?asset'
66
import sharp from 'sharp'
7-
import { promises as fs } from 'fs'
7+
import fs from 'fs'
88
import path from 'path'
9-
9+
import { checkPluginInstalled, getSupportedFormats,documentConvert,isFormatSupported } from './initPlugin'
1010
function createWindow() {
11-
// Create the browser window.
11+
const primaryDisplay = screen.getPrimaryDisplay();
12+
const {width,height} = primaryDisplay.workAreaSize
1213
const mainWindow = new BrowserWindow({
13-
width: 900,
14-
height: 670,
14+
minWidth: width/2,
15+
minHeight: height/1.5,
16+
maxHeight: height,
17+
maxWidth: width,
1518
show: false,
1619
autoHideMenuBar: true,
1720
...(process.platform === 'linux' ? { icon } : {}),
@@ -48,7 +51,6 @@ app.whenReady().then(() => {
4851

4952
// Default open or close DevTools by F12 in development
5053
// and ignore CommandOrControl + R in production.
51-
// see https://github.com/alex8088/electron-toolkit/tree/master/packages/utils
5254
app.on('browser-window-created', (_, window) => {
5355
optimizer.watchWindowShortcuts(window)
5456
})
@@ -64,9 +66,7 @@ app.whenReady().then(() => {
6466
})
6567

6668
// Handle image processing with Sharp
67-
// In your main process
6869
ipcMain.handle('process:image', async (_, { filepath, name, options, savePath }) => {
69-
console.log(filepath)
7070
try {
7171
let pipeline = sharp(filepath);
7272

@@ -94,12 +94,10 @@ ipcMain.handle('process:image', async (_, { filepath, name, options, savePath })
9494
}else if (options.format === 'jxl') {
9595
pipeline = pipeline.jxl({ quality: options.quality });
9696
}
97-
9897
// control if output type is tile
9998
if(options.format === 'tile'){
10099
options.format = 'zip'
101100
}
102-
103101
// Generate output filename
104102
const parsedName = path.parse(name);
105103
const outputName = `${parsedName.name}.${options.format}`;
@@ -121,7 +119,7 @@ ipcMain.handle('process:image', async (_, { filepath, name, options, savePath })
121119
};
122120
}
123121
});
124-
122+
125123
createWindow()
126124

127125
app.on('activate', function () {
@@ -131,14 +129,109 @@ ipcMain.handle('process:image', async (_, { filepath, name, options, savePath })
131129
})
132130
})
133131

134-
// Quit when all windows are closed, except on macOS. There, it's common
135-
// for applications and their menu bar to stay active until the user quits
136-
// explicitly with Cmd + Q.
137132
app.on('window-all-closed', () => {
138133
if (process.platform !== 'darwin') {
139134
app.quit()
140135
}
141136
})
137+
// handle document conversion
138+
ipcMain.handle("document:convert", async (event, {filename,inputPath,outputPath, options}) => {
139+
console.log('parameters', inputPath,outputPath,filename);
140+
try {
141+
const isInstalled = await checkPluginInstalled();
142+
if (!isInstalled) {
143+
return {
144+
success: false,
145+
message: 'MuPDF is not installed. Please install it from Plugins page'
146+
};
147+
}
142148

143-
// In this file you can include the rest of your app's specific main process
144-
// code. You can also put them in separate files and require them here.
149+
// Validate input format
150+
const inputFormat = path.extname(inputPath).slice(1);
151+
if (!isFormatSupported(inputFormat, 'input')) {
152+
return {
153+
success: false,
154+
message: `Unsupported input format: ${inputFormat}`
155+
};
156+
}
157+
158+
// Validate output format
159+
const outputFormat = options?.format || getDefaultOutputFormat(inputFormat);
160+
if (!isFormatSupported(outputFormat, 'output')) {
161+
return {
162+
success: false,
163+
message: `Unsupported output format: ${outputFormat}`
164+
};
165+
}
166+
167+
// Convert document
168+
const result = await documentConvert(filename,inputPath,outputPath, {
169+
format: outputFormat,
170+
...options
171+
});
172+
173+
return result;
174+
} catch (error) {
175+
console.error('Document conversion error:', error);
176+
return {
177+
success: false,
178+
message: error.message
179+
};
180+
}
181+
});
182+
ipcMain.handle('document:get-formats',async()=>{
183+
return getSupportedFormats();
184+
})
185+
//check if plugin is installed
186+
ipcMain.handle('plugin:check-installed',async (event,pluginId)=>{
187+
const pluginPath = path.join(app.getAppPath(),'out','Plugins',`${pluginId}.exe`)
188+
return fs.existsSync(pluginPath)
189+
})
190+
191+
// plugin installation
192+
ipcMain.handle('plugin:install',async(event,{url,id})=>{
193+
const sess = session.defaultSession;
194+
return new Promise((resolve, reject) => {
195+
196+
197+
session.defaultSession.on('will-download', (event, item) => {
198+
const PLUGINS_DIR = path.join(app.getAppPath(),'out', 'Plugins')
199+
200+
// Create plugins directory if it doesn't exist
201+
if (!fs.existsSync(PLUGINS_DIR)) {
202+
fs.mkdirSync(PLUGINS_DIR, { recursive: true })
203+
}
204+
205+
const pluginPath = path.join(PLUGINS_DIR, `${id}.exe`)
206+
item.setSavePath(pluginPath)
207+
208+
item.on('updated', (event, state) => {
209+
if (state === 'interrupted') {
210+
reject(new Error('Download interrupted'))
211+
}
212+
})
213+
214+
item.once('done', (event, state) => {
215+
if (state === 'completed') {
216+
resolve(true)
217+
} else {
218+
reject(new Error(`Download failed with state: ${state}`))
219+
}
220+
})
221+
})
222+
sess.downloadURL(url)
223+
})
224+
})
225+
// delete plugin
226+
ipcMain.handle('plugin:uninstall', async (event,pluginId)=>{
227+
const pluginPath = path.join(app.getAppPath(),'out','Plugins',`${pluginId}.exe`)
228+
try{
229+
if (fs.existsSync(pluginPath)){
230+
await fs.promises.unlink(pluginPath)
231+
return true
232+
}
233+
}catch(error){
234+
console.error('Failed to delete plugin: ',error)
235+
throw error
236+
}
237+
})

src/main/initPlugin.js

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
const mupdfProps = {
2+
from: {
3+
text: [
4+
"pdf",
5+
"epub",
6+
"xps",
7+
"cbz",
8+
"mobi",
9+
"fb2",
10+
"svg",
11+
"png",
12+
"jpg",
13+
"bmp"
14+
],
15+
},
16+
to: {
17+
text: [
18+
"cbz",
19+
"png",
20+
"pnm",
21+
"pgm",
22+
"ppm",
23+
"pam",
24+
"pbm",
25+
"pkm",
26+
"pcl",
27+
"pclm",
28+
"ps",
29+
"pwg",
30+
"pdf",
31+
"svg",
32+
"html",
33+
"xhtml",
34+
"text",
35+
"stext"
36+
],
37+
}
38+
}
39+
import {execFile} from 'node:child_process';
40+
import path from 'path';
41+
import { join } from 'path';
42+
import fs from 'fs'
43+
const converterPath = join(__dirname, '../Plugins', 'mutool.exe');
44+
export async function documentConvert(filename,inputPath,outputPath, options = {}) {
45+
return new Promise((resolve, reject) => {
46+
const outputFormat = options.format;
47+
const args = [
48+
'convert',
49+
'-F',outputFormat,
50+
'-o',`${outputPath}\\${filename}.${outputFormat}`,
51+
inputPath
52+
];
53+
54+
execFile(converterPath, args, (error, stdout, stderr) => {
55+
if (error) {
56+
console.error('Conversion Error:', error);
57+
reject(new Error(`Conversion failed: ${error.message}`));
58+
return;
59+
}
60+
61+
if (stderr) {
62+
console.error('Conversion stderr:', stderr);
63+
reject(new Error(`Conversion error: ${stderr}`));
64+
return;
65+
}
66+
67+
// Check if output file was created
68+
if (!fs.existsSync(outputPath)) {
69+
reject(new Error('Output file was not created'));
70+
return;
71+
}
72+
73+
resolve({
74+
success: true,
75+
outputPath,
76+
message: 'File converted successfully',
77+
stdout
78+
});
79+
});
80+
});
81+
}
82+
export function getSupportedFormats() {
83+
return {
84+
input: mupdfProps.from.text,
85+
output: mupdfProps.to.text
86+
};
87+
}
88+
// Add validation function for supported formats
89+
export function isFormatSupported(format, type = 'input') {
90+
const formats = type === 'input' ? mupdfProps.from.text : mupdfProps.to.text;
91+
return formats.includes(format.toLowerCase());
92+
}
93+
94+
// Update checkPluginInstalled to be more robust
95+
export async function checkPluginInstalled() {
96+
try {
97+
const pluginPath = path.join('out', 'Plugins', 'mutool.exe');
98+
const exists = fs.existsSync(pluginPath);
99+
if (!exists) {
100+
return false;
101+
}
102+
await new Promise((resolve, reject) => {
103+
execFile(pluginPath, ['--version'], (error, stdout, stderr) => {
104+
if (error) {
105+
reject(error);
106+
} else {
107+
resolve(stdout);
108+
}
109+
});
110+
});
111+
112+
return true;
113+
} catch (error) {
114+
console.error('Plugin check failed:', error);
115+
return false;
116+
}
117+
}

src/preload/index.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,7 @@ if (process.contextIsolated) {
2222
} else {
2323
window.electron = electronAPI
2424
window.api = api
25-
}
25+
}
26+
contextBridge.exposeInMainWorld('electronAPI',{
27+
dwnPlugin: (url) => ipcRenderer.send('plugin:install',url)
28+
})

src/renderer/src/assets/github.svg

Lines changed: 1 addition & 0 deletions
Loading

0 commit comments

Comments
 (0)