Skip to content

Commit

Permalink
wip: download ffmpeg dep
Browse files Browse the repository at this point in the history
  • Loading branch information
chenfan0 committed Aug 15, 2024
1 parent 00efcd7 commit 147632b
Show file tree
Hide file tree
Showing 18 changed files with 983 additions and 40 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"crypto-js": "^4.2.0",
"dayjs": "^1.11.11",
"debug": "^4.3.5",
"download": "^8.0.0",
"electron-updater": "^6.1.7",
"fluent-ffmpeg": "^2.1.3",
"i18next": "^23.11.5",
Expand All @@ -62,6 +63,7 @@
"@electron-toolkit/eslint-config-prettier": "^2.0.0",
"@electron-toolkit/eslint-config-ts": "^1.0.1",
"@electron-toolkit/tsconfig": "^1.0.1",
"@types/download": "^8.0.5",
"@types/fluent-ffmpeg": "^2.1.24",
"@types/node": "^18.19.9",
"@types/react": "^18.2.48",
Expand Down
708 changes: 703 additions & 5 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion src/code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ export const CRAWLER_ERROR_CODE = {
export const FFMPEG_ERROR_CODE = {
USER_KILL_PROCESS: 100,
CURRENT_LINE_ERROR: 101,
TIME_OUT: 102
TIME_OUT: 102,
MISS_DEP: 103
}

export const SUCCESS_CODE = 200
Expand Down Expand Up @@ -63,6 +64,9 @@ export const errorCodeToI18nMessage = (code: number, prefix: string) => {
case FFMPEG_ERROR_CODE.TIME_OUT:
message += 'time_out'
break
case FFMPEG_ERROR_CODE.MISS_DEP:
message += 'miss_dep'
break
default:
message += 'unknown_error'
break
Expand Down
1 change: 1 addition & 0 deletions src/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export const STOP_STREAM_RECORD = 'STOP_STREAM_RECORD'
export const STREAM_RECORD_END = 'STREAM_RECORD_END'

export const FFMPEG_PROGRESS_INFO = 'FFMPEG_PROGRESS_INFO'
export const DOWNLOAD_DEP_PROGRESS_INFO = 'DOWNLOAD_DEP_PROGRESS_INFO'

export const SHOW_NOTIFICATION = 'SHOW_NOTIFICATION'
export const SHOW_UPDATE_DIALOG = 'SHOW_UPDATE_DIALOG'
Expand Down
110 changes: 102 additions & 8 deletions src/main/ffmpeg/index.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,112 @@
import module from 'module'
import os from 'node:os'
import path from 'node:path'
import fsp from 'node:fs/promises'

import type Ffmpeg from 'fluent-ffmpeg'

import debug from 'debug'

import download from 'download'

const log = debug('fideo-ffmpeg')

const require = module.createRequire(import.meta.url)
const isMac = os.platform() === 'darwin'
const ffmpeg = require('fluent-ffmpeg') as typeof Ffmpeg

const isDev = import.meta.env.MODE === 'development'
const ffmpegPath = isDev
? path.join(__dirname, '../../resources/ffmpeg', 'ffmpeg')
: isMac
? path.join(process.resourcesPath, 'ffmpeg')
: path.join(process.resourcesPath, 'ffmpeg.exe')
ffmpeg.setFfmpegPath(ffmpegPath)
export const isMac = os.platform() === 'darwin'

const ffmpegMacUrl = 'https://gitlab.com/chenfan0/ffmpeg-resource/-/raw/main/ffmpeg-mac.zip'
const ffmpegWinUrl = 'https://gitlab.com/chenfan0/ffmpeg-resource/-/raw/main/ffmpeg-win.zip'

export async function checkFfmpegExist(dirname: string) {
const ffmpegPath = isMac
? path.resolve(dirname, 'ffmpeg-mac/ffmpeg')
: path.resolve(dirname, 'ffmpeg-win/ffmpeg.exe')
return fsp
.access(ffmpegPath, fsp.constants.F_OK)
.then(() => true)
.catch(() => false)
}

export async function checkFfprobeExist(dirname: string) {
const ffprobePath = isMac
? path.resolve(dirname, 'ffmpeg-mac/ffprobe')
: path.resolve(dirname, 'ffmpeg-win/ffprobe.exe')
return fsp
.access(ffprobePath, fsp.constants.F_OK)
.then(() => true)
.catch(() => false)
}

export const downloadDepProgressInfo: IDownloadDepProgressInfo = {
downloading: false,
progress: 0
}

export async function makeSureDependenciesExist(
dirname: string,
isFfmpegExist: boolean,
isFFprobeExist: boolean
) {
if (isFfmpegExist && isFFprobeExist) {
const ffmpegPath = isMac
? path.resolve(dirname, 'ffmpeg-mac/ffmpeg')
: path.resolve(dirname, 'ffmpeg-win/ffmpeg.exe')
const ffprobePath = isMac
? path.resolve(dirname, 'ffmpeg-mac/ffprobe')
: path.resolve(dirname, 'ffmpeg-win/ffprobe.exe')
ffmpeg.setFfmpegPath(ffmpegPath)
ffmpeg.setFfprobePath(ffprobePath)
return true
}

let _resolve: (value: unknown) => void, _reject: (reason?: any) => void

Check failure on line 64 in src/main/ffmpeg/index.ts

View workflow job for this annotation

GitHub Actions / build (windows-latest)

'_reject' is declared but its value is never read.
const p = new Promise((resolve, reject) => {
_resolve = resolve
_reject = reject
})

if (isMac) {
if (!isFfmpegExist) {
downloadDepProgressInfo.downloading = true
download(ffmpegMacUrl, dirname, { extract: true })
.on('downloadProgress', ({ percent }) => {
downloadDepProgressInfo.progress = percent
log(`ffmpeg download progress: ${percent}`)
})
.then(() => {
downloadDepProgressInfo.downloading = false
downloadDepProgressInfo.progress = 0
_resolve(true)
})
.catch(() => {
// _reject()
})
}
} else {
downloadDepProgressInfo.downloading = true
download(ffmpegWinUrl, dirname, { extract: true })
.on('downloadProgress', ({ percent }) => {
downloadDepProgressInfo.progress = percent
log(`ffmpeg download progress: ${percent}`)
})
.then(() => {
downloadDepProgressInfo.downloading = false
downloadDepProgressInfo.progress = 0
_resolve(true)
})
.catch(() => {})
}

return p
}

// const isDev = import.meta.env.MODE === 'development'
// const ffmpegPath = isDev
// ? path.join(__dirname, '../../resources/ffmpeg', 'ffmpeg')
// : isMac
// ? path.join(process.resourcesPath, 'ffmpeg')
// : path.join(process.resourcesPath, 'ffmpeg.exe')
// ffmpeg.setFfmpegPath(ffmpegPath)
export default ffmpeg
27 changes: 14 additions & 13 deletions src/main/ffmpeg/record.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import debug from 'debug'
import dayjs from 'dayjs'

import ffmpeg from '.'

import { SUCCESS_CODE, FFMPEG_ERROR_CODE } from '../../code'

import { RECORD_DUMMY_PROCESS } from '../../const'
Expand Down Expand Up @@ -105,6 +106,19 @@ async function convert(sourcePath: string, convertToMP4 = true) {
}

export async function recordStream(streamConfig: IStreamConfig, cb?: (code: number) => void) {
let _resolve!: (
value:
| {
code: number
}
| PromiseLike<{
code: number
}>
) => void
const p: Promise<{ code: number }> = new Promise((resolve) => {
_resolve = resolve
})

const { liveUrls, line, directory, filename, proxy, cookie, title, segmentTime, convertToMP4 } =
streamConfig

Expand All @@ -120,19 +134,6 @@ export async function recordStream(streamConfig: IStreamConfig, cb?: (code: numb
fs.mkdirSync(baseOutput)
}

let _resolve!: (
value:
| {
code: number
}
| PromiseLike<{
code: number
}>
) => void
const p: Promise<{ code: number }> = new Promise((resolve) => {
_resolve = resolve
})

if (recordStreamFfmpegProcessMap[title] !== RECORD_DUMMY_PROCESS) {
_resolve({
code: FFMPEG_ERROR_CODE.USER_KILL_PROCESS
Expand Down
68 changes: 59 additions & 9 deletions src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import pkg from '../../package.json'

import {
CLOSE_WINDOW,
DOWNLOAD_DEP_PROGRESS_INFO,
FFMPEG_PROGRESS_INFO,
FORCE_CLOSE_WINDOW,
GET_LIVE_URLS,
Expand All @@ -31,6 +32,13 @@ import {
resetRecordStreamFfmpeg,
setRecordStreamFfmpegProcessMap
} from './ffmpeg/record'
import ffmpeg, {
isMac,
makeSureDependenciesExist,
downloadDepProgressInfo,
checkFfmpegExist,
checkFfprobeExist
} from './ffmpeg'

async function checkUpdate() {
try {
Expand All @@ -46,14 +54,15 @@ async function checkUpdate() {
}
}

let timer: NodeJS.Timeout | undefined
const startTimerWhenFirstFfmpegProcessStart = () => {
if (timer === undefined) {
timer = setInterval(() => {
let ffmpegProcessTimer: NodeJS.Timeout | undefined
const startFfmpegProcessTimerWhenFirstFfmpegProcessStart = () => {
if (ffmpegProcessTimer === undefined) {
ffmpegProcessTimer = setInterval(() => {
win?.webContents.send(FFMPEG_PROGRESS_INFO, recordStreamFfmpegProgressInfo)
}, 1000)
}
}

const isAllFfmpegProcessEnd = () =>
Object.keys(recordStreamFfmpegProcessMap).every(
(key) =>
Expand All @@ -70,11 +79,26 @@ export const clearTimerWhenAllFfmpegProcessEnd = () => {
if (isAllFfmpegProcessEnd()) {
win?.webContents.send(FFMPEG_PROGRESS_INFO, recordStreamFfmpegProgressInfo)

clearInterval(timer)
timer = undefined
clearInterval(ffmpegProcessTimer)
ffmpegProcessTimer = undefined
}
}

let downloadDepTimer: NodeJS.Timeout | undefined
const startDownloadDepTimerWhenFirstDownloadDepStart = () => {
if (downloadDepTimer === undefined) {
downloadDepTimer = setInterval(() => {
win?.webContents.send(DOWNLOAD_DEP_PROGRESS_INFO, downloadDepProgressInfo)
}, 1000)
}
}

const stopDownloadDepTimerWhenAllDownloadDepEnd = () => {
win?.webContents.send(DOWNLOAD_DEP_PROGRESS_INFO, downloadDepProgressInfo)
clearInterval(downloadDepTimer)
downloadDepTimer = undefined
}

let win: BrowserWindow | null
function createWindow(): void {
// Create the browser window.
Expand All @@ -85,7 +109,6 @@ function createWindow(): void {
title: 'Fideo',
autoHideMenuBar: true,
frame: process.platform === 'darwin',
// ...(process.platform === 'linux' ? { icon } : {}),
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
sandbox: false,
Expand Down Expand Up @@ -128,9 +151,36 @@ function showNotification(title: string, body: string) {
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(() => {
app.whenReady().then(async () => {
// Set app user model id for windows
electronApp.setAppUserModelId('com.electron')
const userDataPath = app.getPath('userData')

const [isFFmpegExist, isFfprobeExist] = await Promise.all([
checkFfmpegExist(userDataPath),
checkFfprobeExist(userDataPath)
])

console.log(app.getPath('userData'))

makeSureDependenciesExist(app.getPath('userData'), isFFmpegExist, isFfprobeExist)
.then(() => {
const ffmpegPath = isMac
? join(userDataPath, 'ffmpeg-mac/ffmpeg')
: join(userDataPath, 'ffmpeg-win/ffmpeg.exe')
const ffprobePath = isMac
? join(userDataPath, 'ffmpeg-mac/ffprobe')
: join(userDataPath, 'ffmpeg-win/ffprobe.exe')
ffmpeg.setFfmpegPath(ffmpegPath)
ffmpeg.setFfprobePath(ffprobePath)

stopDownloadDepTimerWhenAllDownloadDepEnd()
})
.catch(() => {})

if (!isFFmpegExist || !isFfprobeExist) {
startDownloadDepTimerWhenFirstDownloadDepStart()
}

// Default open or close DevTools by F12 in development
// and ignore CommandOrControl + R in production.
Expand Down Expand Up @@ -184,7 +234,7 @@ app.whenReady().then(() => {
clearTimerWhenAllFfmpegProcessEnd()
})

startTimerWhenFirstFfmpegProcessStart()
startFfmpegProcessTimerWhenFirstFfmpegProcessStart()

return {
code: recordStreamCode
Expand Down
1 change: 1 addition & 0 deletions src/preload/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ declare global {

onStreamRecordEnd: (callback: (title: string, code: number) => void) => void
onFFmpegProgressInfo: (callback: (info: IFfmpegProgressInfo) => void) => void
onDownloadDepProgressInfo: (callback: (info: IDownloadDepProgressInfo) => void) => void
onUserCloseWindow: (callback: () => void) => void
onAppUpdate: (callback: () => void) => void
}
Expand Down
8 changes: 8 additions & 0 deletions src/preload/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { contextBridge, ipcRenderer } from 'electron'
import { electronAPI } from '@electron-toolkit/preload'
import {
CLOSE_WINDOW,
DOWNLOAD_DEP_PROGRESS_INFO,
FFMPEG_PROGRESS_INFO,
FORCE_CLOSE_WINDOW,
MAXIMIZE_RESTORE_WINDOW,
Expand Down Expand Up @@ -44,6 +45,13 @@ const api = {
callback(info)
})
},

onDownloadDepProgressInfo: (callback: (info: IDownloadDepProgressInfo) => void) => {
ipcRenderer.on(DOWNLOAD_DEP_PROGRESS_INFO, (_, info) => {
callback(info)
})
},

onUserCloseWindow: (callback: () => void) => {
ipcRenderer.on(USER_CLOSE_WINDOW, () => {
callback()
Expand Down
Loading

0 comments on commit 147632b

Please sign in to comment.