Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion electron/converters/audio-converter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,12 +136,13 @@ export async function convertAudio(options: AudioConvertOptions): Promise<Conver
}
}

// Execute FFmpeg conversion
// Execute FFmpeg conversion (audioOnly ensures video sources are stripped to audio)
const result = await executeFFmpeg({
inputPath: sourcePath,
outputPath,
format: targetFormat,
quality,
audioOnly: true,
onProgress,
metadata: options.metadata,
})
Expand Down
6 changes: 4 additions & 2 deletions electron/converters/utils/ffmpeg-wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export interface FFmpegConvertOptions {
outputPath: string
format: string
quality: number // 1-100
audioOnly?: boolean // Extract audio only
onProgress?: (percent: number) => void
metadata?: Record<string, string> // Custom metadata
}
Expand Down Expand Up @@ -252,8 +253,9 @@ export async function executeFFmpeg(options: FFmpegConvertOptions): Promise<FFmp
const mediaInfo = await probeMediaFile(options.inputPath)
const totalDuration = mediaInfo?.duration || 0

// Determine if this is a video or audio conversion
const isVideo = !!(mediaInfo?.videoCodec)
// Determine if this is a video or audio conversion.
// audioOnly forces audio-only args even when source has a video stream.
const isVideo = !!(mediaInfo?.videoCodec) && !options.audioOnly

const args = buildFFmpegArgs({
inputPath: options.inputPath,
Expand Down
21 changes: 21 additions & 0 deletions electron/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,27 @@ ipcMain.handle('convert-files', async (_event, payload: ConvertPayload) => {
currentOperation: 'Finalizing...',
startTime,
})
} else if (category === 'video' && isAudioFormat(file.targetFormat)) {
// Video → audio extraction (e.g. MP4 → MP3)
result = await convertAudio({
sourcePath: file.sourcePath,
outputDir: outputDir,
targetFormat: file.targetFormat,
quality,
overwriteBehavior,
onProgress: (percent) => {
const elapsed = (Date.now() - startTime) / 1000
const eta = percent > 0 ? Math.round((elapsed / percent) * (100 - percent)) : 0
mainWindow?.webContents.send('conversion-progress', {
fileId: file.fileId,
progress: percent,
status: 'converting',
currentOperation: 'Extracting audio...',
eta,
startTime,
})
},
})
} else if (category === 'video' && isVideoFormat(file.sourceExt)) {
// Video conversion with progress tracking
result = await convertVideo({
Expand Down
16 changes: 8 additions & 8 deletions src/config/formats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,49 +177,49 @@ export const FORMAT_MAP: Record<string, FormatInfo> = {
mp4: {
category: 'video',
label: 'MP4',
targets: ['mkv', 'avi', 'mov', 'webm', 'gif'],
targets: ['mkv', 'avi', 'mov', 'webm', 'gif', 'mp3', 'aac', 'wav', 'ogg', 'flac', 'm4a'],
description: 'MPEG-4 Part 14 - most widely supported video format'
},
mkv: {
category: 'video',
label: 'MKV',
targets: ['mp4', 'avi', 'mov', 'webm'],
targets: ['mp4', 'avi', 'mov', 'webm', 'mp3', 'aac', 'wav', 'ogg', 'flac', 'm4a'],
description: 'Matroska Video - open container format, supports multiple tracks'
},
avi: {
category: 'video',
label: 'AVI',
targets: ['mp4', 'mkv', 'mov', 'webm'],
targets: ['mp4', 'mkv', 'mov', 'webm', 'mp3', 'aac', 'wav', 'ogg', 'flac', 'm4a'],
description: 'Audio Video Interleave - legacy Windows format'
},
mov: {
category: 'video',
label: 'MOV',
targets: ['mp4', 'mkv', 'avi', 'webm'],
targets: ['mp4', 'mkv', 'avi', 'webm', 'mp3', 'aac', 'wav', 'ogg', 'flac', 'm4a'],
description: 'QuickTime Movie - Apple video format'
},
webm: {
category: 'video',
label: 'WebM',
targets: ['mp4', 'mkv', 'avi', 'mov'],
targets: ['mp4', 'mkv', 'avi', 'mov', 'mp3', 'aac', 'wav', 'ogg', 'flac', 'm4a'],
description: 'Web Media - open format optimized for web streaming'
},
'3gp': {
category: 'video',
label: '3GP',
targets: ['mp4', 'mkv', 'avi'],
targets: ['mp4', 'mkv', 'avi', 'mp3', 'aac', 'wav'],
description: '3rd Generation Partnership Project - mobile video format'
},
flv: {
category: 'video',
label: 'FLV',
targets: ['mp4', 'mkv', 'avi', 'webm'],
targets: ['mp4', 'mkv', 'avi', 'webm', 'mp3', 'aac', 'wav', 'ogg', 'flac', 'm4a'],
description: 'Flash Video - legacy web video format'
},
wmv: {
category: 'video',
label: 'WMV',
targets: ['mp4', 'mkv', 'avi', 'webm'],
targets: ['mp4', 'mkv', 'avi', 'webm', 'mp3', 'aac', 'wav', 'ogg', 'flac', 'm4a'],
description: 'Windows Media Video - Microsoft streaming format'
},

Expand Down