Skip to content

Commit

Permalink
Enhance video conversion with format selection and improved progress …
Browse files Browse the repository at this point in the history
…tracking
  • Loading branch information
yeongpin committed Feb 14, 2025
1 parent f12cb33 commit efc8e41
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 48 deletions.
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
# Changelog

## [1.0.2] - 2025-02-13
### Added
- GitHub button
- Open external link
- Completion dialog
- Fix some bugs
- Fixed the output format
- Fixed the output path
- Fixed the progress bar
- Fixed the resolution
- Fixed the quality
- Fixed the speed
- Fixed the theme

## [1.0.1] - 2025-02-13
### Added
- Completion dialog
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "video-converter",
"version": "1.0.1",
"version": "1.0.2",
"description": "A versatile video format converter using Electron and FFmpeg",
"main": "src/main/main.js",
"author": "yeongpin",
Expand Down
8 changes: 5 additions & 3 deletions public/js/converter.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,14 +144,15 @@ document.addEventListener('DOMContentLoaded', async () => {

const options = {
inputPath: selectedFile.path,
outputPath: currentOutputPath, // use selected output directory
outputPath: currentOutputPath,
options: {
encoder: encoderSelect.value,
isLossless: document.querySelector('input[name="conversionMode"]:checked').value === 'lossless',
preset: speedSelect.value,
crf: qualitySelect.value,
resolution: resolutionSelect.value,
hdrMode: hdrSelect.value
hdrMode: hdrSelect.value,
format: formatSelect.value
}
};

Expand Down Expand Up @@ -246,7 +247,8 @@ async function convertVideo() {
preset: speedSelect.value,
crf: qualitySelect.value,
resolution: resolutionSelect.value,
hdrMode: hdrSelect.value
hdrMode: hdrSelect.value,
format: formatSelect.value
}
};

Expand Down
160 changes: 118 additions & 42 deletions src/main/ffmpeg.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,22 @@ const ffmpeg = require('fluent-ffmpeg');
const path = require('path');
const { BrowserWindow, app } = require('electron');
const fs = require('fs');
const isDev = require('electron-is-dev');

// get ffmpeg and ffprobe path
function getBinaryPath(binaryName) {
if (process.env.NODE_ENV === 'development') {
// development use global installed ffmpeg
if (isDev) {
// development environment
const devPath = path.join(__dirname, '../../external/ffmpeg/bin', `${binaryName}${process.platform === 'win32' ? '.exe' : ''}`);
if (fs.existsSync(devPath)) {
return devPath;
}
// fallback to global ffmpeg if not found in development path
return binaryName;
} else {
// production use bundled ffmpeg
// production environment
const resourcesPath = process.resourcesPath;
const platform = process.platform;
const extension = platform === 'win32' ? '.exe' : '';
const extension = process.platform === 'win32' ? '.exe' : '';
return path.join(resourcesPath, 'external', 'ffmpeg', 'bin', `${binaryName}${extension}`);
}
}
Expand All @@ -21,6 +26,9 @@ function getBinaryPath(binaryName) {
const ffmpegPath = getBinaryPath('ffmpeg');
const ffprobePath = getBinaryPath('ffprobe');

console.log('FFmpeg Path:', ffmpegPath);
console.log('FFprobe Path:', ffprobePath);

ffmpeg.setFfmpegPath(ffmpegPath);
ffmpeg.setFfprobePath(ffprobePath);

Expand Down Expand Up @@ -111,25 +119,61 @@ async function convertVideo(inputPath, outputPath, options = {}) {
const mainWindow = BrowserWindow.getFocusedWindow();
let startTime;

// get input file name (without extension) and extension
const inputFileName = path.basename(inputPath, path.extname(inputPath));
const outputExt = path.extname(inputPath);
// build new output file name
const outputFileName = `${inputFileName}-converted${outputExt}`;
// ensure output path exists and create full output path
fs.mkdirSync(outputPath, { recursive: true });
const finalOutputPath = path.join(outputPath, outputFileName);

const {
encoder = 'h264',
preset = 'medium',
crf = '23',
isLossless = false,
audioBitrate = '320k',
resolution = 'original',
hdrMode = 'keep'
hdrMode = 'keep',
format = 'mp4' // 添加format选项,默认为mp4
} = options;

// get input file name (without extension)
const inputFileName = path.basename(inputPath, path.extname(inputPath));

// 根据选择的格式设置输出扩展名
let outputExt;
switch (format.toLowerCase()) {
case 'mp4':
outputExt = '.mp4';
break;
case 'mkv':
outputExt = '.mkv';
break;
case 'mov':
outputExt = '.mov';
break;
case 'webm':
outputExt = '.webm';
break;
case 'avi':
outputExt = '.avi';
break;
case 'flv':
outputExt = '.flv';
break;
case 'wmv':
outputExt = '.wmv';
break;
case 'm4v':
outputExt = '.m4v';
break;
case 'ts':
outputExt = '.ts';
break;
default:
outputExt = '.mp4'; // 默认使用mp4
}

// build new output file name with correct extension
const outputFileName = `${inputFileName}-converted${outputExt}`;

// ensure output path exists and create full output path
fs.mkdirSync(outputPath, { recursive: true });
const finalOutputPath = path.join(outputPath, outputFileName);

try {
// show current resolution
mainWindow.webContents.send('video-info', {
Expand All @@ -148,12 +192,20 @@ async function convertVideo(inputPath, outputPath, options = {}) {
throw new Error(`Unsupported encoder: ${encoder}`);
}

// 根据输出格式设置编码器
let videoCodec = encoderConfig.codec;
if (format === 'webm') {
videoCodec = 'libvpx-vp9';
} else if (format === 'wmv') {
videoCodec = 'wmv2';
}

const baseOptions = [
'-c:v', encoderConfig.codec,
'-c:v', videoCodec,
'-preset', preset,
'-crf', crf,
'-movflags', '+faststart',
'-c:a', 'aac',
'-c:a', format === 'webm' ? 'libvorbis' : 'aac',
'-b:a', audioBitrate
];

Expand Down Expand Up @@ -193,30 +245,48 @@ async function convertVideo(inputPath, outputPath, options = {}) {
});
})
.on('progress', (progress) => {
if (progress.percent) {
const currentTime = Date.now();
const elapsedTime = (currentTime - startTime) / 1000;
const percentComplete = progress.percent;
const estimatedTotalTime = (elapsedTime / percentComplete) * 100;
const remainingTime = estimatedTotalTime - elapsedTime;
const eta = new Date(remainingTime * 1000).toISOString().substr(11, 8);

mainWindow.webContents.send('conversion-progress', {
percent: Math.round(percentComplete),
speed: progress.currentFps ? `${progress.currentFps} fps` : 'N/A',
time: progress.timemark,
size: progress.targetSize ? `${Math.round(progress.targetSize / 1024 / 1024)}MB` : 'N/A',
eta: eta,
status: 'Converting...'
});
const currentTime = Date.now();
const elapsedTime = (currentTime - startTime) / 1000; // 转换为秒

// 计算预计剩余时间(ETA)
const percent = progress.percent || 0;
let eta = 'calculating...';
if (percent > 0) {
const totalTime = (elapsedTime * 100) / percent;
const remainingTime = totalTime - elapsedTime;
eta = formatTime(remainingTime);
}

// 格式化当前处理时间
const processedTime = formatTime(elapsedTime);

// 计算文件大小
const size = progress.targetSize
? `${(progress.targetSize / 1024 / 1024).toFixed(2)}MB`
: 'calculating...';

mainWindow.webContents.send('conversion-progress', {
percent: Math.round(percent),
speed: progress.currentFps ? `${progress.currentFps} fps` : 'N/A',
time: processedTime,
size: size,
eta: eta,
status: 'Converting...'
});
})
.on('end', () => {
const endTime = Date.now();
const totalTime = formatTime((endTime - startTime) / 1000);

// 获取输出文件大小
const stats = fs.statSync(finalOutputPath);
const finalSize = `${(stats.size / 1024 / 1024).toFixed(2)}MB`;

mainWindow.webContents.send('conversion-progress', {
percent: 100,
speed: 'Done',
time: videoInfo.duration,
size: `${Math.round(videoInfo.size / 1024 / 1024)}MB`,
time: totalTime,
size: finalSize,
eta: '00:00:00',
status: 'Completed!',
outputPath: finalOutputPath
Expand All @@ -225,20 +295,26 @@ async function convertVideo(inputPath, outputPath, options = {}) {
})
.on('error', (err) => {
console.error('FFmpeg error:', err);
mainWindow.webContents.send('conversion-error', err.message);
reject(err);
});

command.run();

})
.run();
} catch (error) {
console.error('Conversion setup error:', error);
mainWindow.webContents.send('conversion-error', error.message);
console.error('Conversion error:', error);
reject(error);
}
});
}

// 时间格式化函数
function formatTime(seconds) {
seconds = Math.round(seconds);
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const remainingSeconds = seconds % 60;

return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(remainingSeconds).padStart(2, '0')}`;
}

module.exports = {
convertVideo,
getVideoInfo,
Expand Down

0 comments on commit efc8e41

Please sign in to comment.