Skip to content

Commit

Permalink
feat: refactored cli script and made CLI colors nicer
Browse files Browse the repository at this point in the history
  • Loading branch information
tmgulland committed Oct 20, 2024
1 parent 30adcce commit 4cdead1
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 91 deletions.
72 changes: 60 additions & 12 deletions src/cli/process.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
#!/usr/bin/env bun

import { isSuccessfulResult, processVideos } from '../video'
import { isFFmpegInstalled, isSuccessfulResult, processVideos } from '../video'
import { name } from '../../package.json'
import { parseArgs } from 'util'
import { print } from '../log'
import { logo, print } from '../log'
import { logCO2eReport } from '../sustainability'
import { access, mkdir } from 'fs/promises'
import { basename } from 'path'
import { fileExists, getFilesInDirectory, isDirectory } from '../fs'

const { values } = parseArgs({
options: {
Expand All @@ -28,18 +31,55 @@ const input = values.src
const outputFolder = values.output
const baseDir = values.baseDir

processVideos({ input, outputFolder, baseDir, loglevel: 'quiet' })
.then(async (videos) => {
const FILE_TYPES = ['.mp4', '.mov']
const main = async () => {
try {
print.log({
message: logo.split('\n'),
color: 'lime green'
})

await access(input)

const isDir = await isDirectory(input)
let files: string[] = []

if (isDir) {
files = await getFilesInDirectory(input, FILE_TYPES)
} else {
const existingFile = await fileExists(input, basename(input), FILE_TYPES)
if (existingFile) {
files.push(existingFile)
} else {
print.error({
message: [`Error: The file "${input}" is not a supported video format (.mp4 or .mov).`]
})
process.exit(1)
}
}

const hasFFmpeg = await isFFmpegInstalled()

if (!hasFFmpeg) {
print.error({ message: ['Error: ffmpeg is not installed.'] })
process.exit(1)
}

await mkdir(outputFolder, { recursive: true })

const videos = await processVideos({
files,
outputFolder,
baseDir,
loglevel: 'quiet'
})
if (videos.success.length > 0) {
print.log({
message: [`> Generated ${videos.success.length} videos`],
message: [`Processed ${videos.success.length} videos`],
color: 'lime green'
})
print.log({
message: videos.success
.filter(isSuccessfulResult)
.map((video) => `> ${video.manifest?.id}`),
indent: 2
message: videos.success.filter(isSuccessfulResult).map((video) => `∟ ${video.manifest?.id}`)
})

logCO2eReport(videos.success)
Expand All @@ -56,8 +96,16 @@ processVideos({ input, outputFolder, baseDir, loglevel: 'quiet' })
color: 'orange'
})
}
})
.catch((error) => {
} catch (error) {
print.error({
message: [`Error`]
})

console.error(error)
process.exit(1)
})
}
}

if (import.meta.main) {
main()
}
4 changes: 3 additions & 1 deletion src/fs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ export const getFilesInDirectory = async (
if (!extensions || extensions.length === 0) {
return files
}
return files.filter((file) => extensions.includes(extname(file).toLowerCase()))
return files
.filter((file) => extensions.includes(extname(file).toLowerCase()))
.map((file) => join(directory, file))
}

export const isDirectory = async (path: string): Promise<boolean> => {
Expand Down
10 changes: 9 additions & 1 deletion src/log.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ type LogOptions = {
color?: string
}

export const log = ({ message, indent = 0, color = 'grey' }: LogOptions) => {
export const log = ({ message, indent = 2, color = 'grey' }: LogOptions) => {
const indentation = ' '.repeat(indent)
const colorCode = getColorCode(color)

Expand Down Expand Up @@ -38,3 +38,11 @@ export const print = {
error,
log
}

export const logo = `
\\ | /
— F I G U R E —
/ | \\
`
14 changes: 6 additions & 8 deletions src/sustainability.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { co2 } from '@tgwf/co2'
import { isNumber } from '@figureland/kit/ts/guards'
import type { VideoManifest } from './schema'
import type { VideoProcessingSuccessResult } from './video'
import { print } from './log'
import { isNumber } from '@figureland/kit/ts/guards'

const swd = new co2({ model: 'swd' })
const co2e = (bytes: number) => Number(swd.perByte(bytes))
Expand Down Expand Up @@ -87,17 +87,15 @@ export const logCO2eReport = (videos: VideoProcessingSuccessResult[]) => {

print.log({
message: [
`Saved ${formatBytes(deltaSize)} of data, or an estimated ${formatCO2e(deltaCO2e)} in emissions`
`:) Saved ${formatBytes(deltaSize)} of data, or an estimated ${formatCO2e(deltaCO2e)} in emissions`
],
color: 'lime green',
indent: 0
color: 'lime green'
})
print.log({
message: [
`For an example website with 1000 visitors per month`,
`this could save ${formatCO2e(calculation)}/yr`
`You could save ${formatCO2e(calculation)}/yr`,
`based on a website with 1000 visitors/month`
],
color: 'lime green',
indent: 0
color: 'forest green'
})
}
83 changes: 14 additions & 69 deletions src/video.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { $ } from 'bun'
import { mkdir, access } from 'fs/promises'
import { mkdir } from 'fs/promises'
import { parse, join, basename, relative } from 'path'
import type { VideoManifest } from './schema'
import { generateManifest } from './manifest'
import { getFileHash } from './hash'
import { getVideoManifest } from './api'
import { fileExists, fileSize, getFilesInDirectory, isDirectory } from './fs'
import { fileExists, fileSize } from './fs'
import { print } from './log'

export const getVideoFPS = async (inputFile: string) => {
Expand Down Expand Up @@ -196,14 +196,12 @@ export const processVideo = async ({
const endTime = performance.now()
const elapsedTime = ((endTime - startTime) / 1000).toFixed(2)

print.log({ message: [`Finished processing ${filename} (${elapsedTime}s)`] })
print.log({ message: [`${manifestPath}`], indent: 2 })
print.log({ message: [`Optimised ${filename} (${elapsedTime}s)`] })
for (const source of manifest.sources) {
const sizeInMB = (source.size / (1024 * 1024)).toFixed(2)
const percentReduction = ((1 - source.size / metadata.size) * 100).toFixed(0)
print.log({
message: [`${source.type} (${sizeInMB}mb, ${percentReduction}% smaller)`],
indent: 4
message: [`∟ ${source.type} (${sizeInMB}mb, ${percentReduction}% smaller)`]
})
}
return {
Expand All @@ -227,88 +225,35 @@ export const isFFmpegInstalled = async (): Promise<boolean> => {
}

export const processVideos = async ({
input,
files,
outputFolder,
baseDir = '/',
loglevel = 'info'
}: {
input: string
files: string[]
outputFolder: string
baseDir?: string
loglevel?: FFMpegLogLevel
}) => {
try {
await access(input)
} catch (error) {
print.error({ message: [`Error: The input "${input}" does not exist or is not accessible.`] })
process.exit(1)
}

const hasFFmpeg = await isFFmpegInstalled()

if (!hasFFmpeg) {
print.error({ message: ['Error: ffmpeg is not installed.'] })
process.exit(1)
}

await mkdir(outputFolder, { recursive: true })

const results: VideoProcessingResult[] = []

const isDir = await isDirectory(input)

if (isDir) {
const videoFiles = await getFilesInDirectory(input, ['.mp4', '.mov'])

if (videoFiles.length === 0) {
print.error({
message: [
`No video files found in "${input}". Please make sure the directory contains .mp4 or .mov files.`
]
})
process.exit(0)
}

for (const f of videoFiles) {
const file = join(input, f)
const result = await processVideo({
outputFolder,
file,
baseDir,
overwrite: true,
loglevel
})
if (result) {
results.push(result)
}
}

print.log({ message: ['All videos processed.'] })
} else {
const singleFileExists = await fileExists(input, basename(input), ['.mp4', '.mov'])
if (!singleFileExists) {
print.error({
message: [`Error: The file "${input}" is not a supported video format (.mp4 or .mov).`]
})
process.exit(1)
}
print.log({
message: [`Processing ${files.length} videos`]
})

for (const file of files) {
const result = await processVideo({
outputFolder,
file: input,
baseDir: '/converted',
file,
baseDir,
overwrite: true,
loglevel: 'info'
loglevel
})

if (result) {
results.push(result)
}

print.log({
message: ['Video processed.']
})
}

return collectResults(results)
}

Expand Down

0 comments on commit 4cdead1

Please # to comment.