-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: ClaytonTDM <[email protected]>
- Loading branch information
1 parent
6d802af
commit 5a7e2d6
Showing
2 changed files
with
70 additions
and
173 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,185 +1,83 @@ | ||
const fs = require("fs").promises; | ||
const path = require("path"); | ||
const { execFile } = require("child_process"); | ||
const util = require("util"); | ||
const execFilePromise = util.promisify(execFile); | ||
|
||
const gifsiclePath = "gifsicle"; | ||
const gifskiPath = "gifski"; | ||
const convertPath = "convert"; | ||
const identifyPath = "identify"; | ||
|
||
const textFileExtensions = [ | ||
".md", | ||
".json", | ||
".js", | ||
".css", | ||
".html", | ||
".htm", | ||
".htm", | ||
".txt", | ||
".asp" | ||
]; | ||
|
||
async function isAnimatedGif(filePath) { | ||
try { | ||
const { stdout } = await execFilePromise("identify", [ | ||
"-format", | ||
"%n\n", | ||
filePath, | ||
]); | ||
const frameCount = parseInt(stdout.trim(), 10); | ||
return frameCount > 1; | ||
} catch (error) { | ||
console.error(`Error checking if GIF is animated: ${error.message}`); | ||
return false; | ||
} | ||
try { | ||
const { stdout } = await execFilePromise(gifsiclePath, ["--info", filePath]); | ||
return stdout.includes("+ image #"); | ||
} catch (error) { | ||
console.error(`Error checking if GIF is animated: ${error.message}`); | ||
return false; | ||
} | ||
} | ||
|
||
async function getGifLoopCount(filePath) { | ||
try { | ||
const { stdout } = await execFilePromise(identifyPath, [ | ||
"-format", | ||
"%L", | ||
filePath, | ||
]); | ||
const loopCount = parseInt(stdout.trim(), 10); | ||
return isNaN(loopCount) ? 0 : loopCount; | ||
} catch (error) { | ||
console.error(`Error getting GIF loop count: ${error.message}`); | ||
return 0; | ||
} | ||
try { | ||
const { stdout } = await execFilePromise(gifsiclePath, ["--info", filePath]); | ||
const loopMatch = stdout.match(/loop forever|loop count (\d+)/); | ||
if (loopMatch) { | ||
if (loopMatch[0] === "loop forever") { | ||
return 0; // Infinite loop | ||
} else { | ||
return parseInt(loopMatch[1], 10); | ||
} | ||
} | ||
return -1; // No explicit loop information found | ||
} catch (error) { | ||
console.error(`Error getting GIF loop count: ${error.message}`); | ||
return -1; // Error case | ||
} | ||
} | ||
|
||
async function convertGifToWebp(inputPath, outputPath) { | ||
try { | ||
const animated = await isAnimatedGif(inputPath); | ||
if (animated) { | ||
const loopCount = await getGifLoopCount(inputPath); | ||
const loopArg = | ||
loopCount === 0 ? "--repeat=0" : `--repeat=${loopCount - 1}`; | ||
await execFilePromise(gifskiPath, [ | ||
"--quality=100", | ||
loopArg, | ||
"--output", | ||
outputPath, | ||
inputPath, | ||
]); | ||
} else { | ||
await execFilePromise(convertPath, [ | ||
inputPath, | ||
"-define", | ||
"webp:lossless=true", | ||
outputPath, | ||
]); | ||
} | ||
console.log(`Converted ${inputPath} to ${outputPath}`); | ||
|
||
// Delete the original GIF file | ||
await fs.unlink(inputPath); | ||
console.log(`Deleted original GIF: ${inputPath}`); | ||
|
||
return true; | ||
} catch (error) { | ||
console.error(`Error converting ${inputPath}: ${error.message}`); | ||
if (error.code === "ENOENT") { | ||
console.error( | ||
"The required executable was not found. Please make sure gifski and ImageMagick are installed and the paths are correct." | ||
); | ||
} | ||
return false; | ||
} | ||
} | ||
|
||
async function isTextFile(filePath) { | ||
const ext = path.extname(filePath).toLowerCase(); | ||
if (textFileExtensions.includes(ext)) { | ||
return true; | ||
} | ||
|
||
try { | ||
const buffer = await fs.readFile(filePath); | ||
const fileString = buffer.toString("utf8", 0, 1000); // Read first 1000 bytes | ||
return /^[\x20-\x7E\r\n]*$/.test(fileString); | ||
} catch (error) { | ||
console.error(`Error checking if file is text: ${error.message}`); | ||
return false; | ||
} | ||
} | ||
|
||
async function replaceInFile(filePath, replacements) { | ||
try { | ||
if (await isTextFile(filePath)) { | ||
let data = await fs.readFile(filePath, "utf8"); | ||
let changed = false; | ||
for (const { oldName, newName } of replacements) { | ||
const regex = new RegExp(oldName, "g"); | ||
const newData = data.replace(regex, newName); | ||
if (newData !== data) { | ||
data = newData; | ||
changed = true; | ||
} | ||
} | ||
if (changed) { | ||
await fs.writeFile(filePath, data, "utf8"); | ||
console.log(`Updated references in ${filePath}`); | ||
} | ||
} | ||
} catch (error) { | ||
console.error(`Error updating ${filePath}: ${error.message}`); | ||
} | ||
} | ||
|
||
async function processDirectory(directoryPath, allConvertedFiles = []) { | ||
try { | ||
const entries = await fs.readdir(directoryPath, { | ||
withFileTypes: true, | ||
}); | ||
const convertedFiles = []; | ||
|
||
for (const entry of entries) { | ||
const fullPath = path.join(directoryPath, entry.name); | ||
|
||
if (entry.isDirectory()) { | ||
await processDirectory(fullPath, allConvertedFiles); | ||
} else if (entry.isFile()) { | ||
if (path.extname(entry.name).toLowerCase() === ".gif") { | ||
const outputPath = path.join( | ||
path.dirname(fullPath), | ||
`${path.basename(fullPath, ".gif")}.webp` | ||
); | ||
const success = await convertGifToWebp( | ||
fullPath, | ||
outputPath | ||
); | ||
if (success) { | ||
convertedFiles.push({ | ||
oldName: entry.name, | ||
newName: path.basename(outputPath), | ||
}); | ||
} | ||
} else { | ||
await replaceInFile(fullPath, allConvertedFiles); | ||
} | ||
} | ||
} | ||
|
||
allConvertedFiles.push(...convertedFiles); | ||
|
||
return allConvertedFiles; | ||
} catch (error) { | ||
console.error( | ||
`Error processing directory ${directoryPath}: ${error.message}` | ||
); | ||
return allConvertedFiles; | ||
} | ||
} | ||
|
||
async function main() { | ||
const rootDirectory = "."; | ||
console.log(`Starting conversion process in ${rootDirectory}`); | ||
const convertedFiles = await processDirectory(rootDirectory); | ||
console.log("Conversion process completed"); | ||
console.log(`Total files converted: ${convertedFiles.length}`); | ||
} | ||
|
||
main().catch((error) => console.error("An error occurred:", error)); | ||
try { | ||
const animated = await isAnimatedGif(inputPath); | ||
if (animated) { | ||
const loopCount = await getGifLoopCount(inputPath); | ||
let loopArg; | ||
if (loopCount === 0) { | ||
loopArg = "--repeat=0"; // Infinite loop | ||
} else if (loopCount === -1) { | ||
loopArg = "--repeat=1"; // Play once (no loop) | ||
} else { | ||
loopArg = `--repeat=${loopCount}`; // Specific number of loops | ||
} | ||
|
||
await execFilePromise(gifskiPath, [ | ||
"--quality=100", | ||
loopArg, | ||
"--output", | ||
outputPath, | ||
inputPath, | ||
]); | ||
} else { | ||
// For non-animated GIFs, use ImageMagick's convert | ||
await execFilePromise(convertPath, [ | ||
inputPath, | ||
"-define", | ||
"webp:lossless=true", | ||
outputPath, | ||
]); | ||
} | ||
console.log(`Converted ${inputPath} to ${outputPath}`); | ||
|
||
// Delete the original GIF file | ||
await fs.unlink(inputPath); | ||
console.log(`Deleted original GIF: ${inputPath}`); | ||
|
||
return true; | ||
} catch (error) { | ||
console.error(`Error converting ${inputPath}: ${error.message}`); | ||
if (error.code === "ENOENT") { | ||
console.error( | ||
"The required executable was not found. Please make sure gifsicle, gifski, and ImageMagick are installed and the paths are correct." | ||
); | ||
} | ||
return false; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters