mirror of
https://github.com/nicetry247/offlineacademy.git
synced 2026-07-26 11:58:44 +00:00
85 lines
2.6 KiB
TypeScript
Executable File
85 lines
2.6 KiB
TypeScript
Executable File
import { execSync } from 'child_process'
|
|
import { join, dirname, basename, extname } from 'path'
|
|
import { existsSync, mkdirSync } from 'fs'
|
|
|
|
/**
|
|
* Get video duration in seconds using ffprobe
|
|
*/
|
|
export function getVideoDuration(filePath: string): number | null {
|
|
try {
|
|
const output = execSync(
|
|
`ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "${filePath}"`,
|
|
{ encoding: 'utf-8', timeout: 30000 }
|
|
)
|
|
const duration = parseFloat(output.trim())
|
|
return isNaN(duration) ? null : Math.round(duration)
|
|
} catch (error) {
|
|
console.warn(`Failed to get duration for ${filePath}:`, error)
|
|
return null
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generate thumbnail from video at specified time (default 10% or 5 seconds)
|
|
*/
|
|
export function generateVideoThumbnail(
|
|
videoPath: string,
|
|
thumbnailDir: string,
|
|
timePercent: number = 0.1
|
|
): string | null {
|
|
try {
|
|
// Get video duration first to calculate timestamp
|
|
const duration = getVideoDuration(videoPath)
|
|
if (!duration) return null
|
|
|
|
const timestamp = Math.min(duration * timePercent, 5) // Max 5 seconds or 10%
|
|
const timeStr = formatTimeForFfmpeg(timestamp)
|
|
|
|
// Create thumbnail directory if it doesn't exist
|
|
if (!existsSync(thumbnailDir)) {
|
|
mkdirSync(thumbnailDir, { recursive: true })
|
|
}
|
|
|
|
const videoName = basename(videoPath, extname(videoPath))
|
|
const thumbnailName = `${videoName}-thumb.jpg`
|
|
const thumbnailPath = join(thumbnailDir, thumbnailName)
|
|
|
|
// Generate thumbnail using ffmpeg
|
|
execSync(
|
|
`ffmpeg -y -ss ${timeStr} -i "${videoPath}" -vframes 1 -q:v 2 -vf "scale=320:-1" "${thumbnailPath}"`,
|
|
{ stdio: 'ignore', timeout: 60000 }
|
|
)
|
|
|
|
if (existsSync(thumbnailPath)) {
|
|
return thumbnailName
|
|
}
|
|
return null
|
|
} catch (error) {
|
|
console.warn(`Failed to generate thumbnail for ${videoPath}:`, error)
|
|
return null
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Format seconds as HH:MM:SS for ffmpeg -ss parameter
|
|
*/
|
|
function formatTimeForFfmpeg(seconds: number): string {
|
|
const hrs = Math.floor(seconds / 3600)
|
|
const mins = Math.floor((seconds % 3600) / 60)
|
|
const secs = Math.floor(seconds % 60)
|
|
const ms = Math.round((seconds % 1) * 1000)
|
|
return `${hrs.toString().padStart(2, '0')}:${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}.${ms.toString().padStart(3, '0')}`
|
|
}
|
|
|
|
/**
|
|
* Check if ffmpeg/ffprobe are available
|
|
*/
|
|
export function checkVideoTools(): boolean {
|
|
try {
|
|
execSync('ffprobe -version', { stdio: 'ignore' })
|
|
execSync('ffmpeg -version', { stdio: 'ignore' })
|
|
return true
|
|
} catch {
|
|
return false
|
|
}
|
|
} |