feat: add CLI with TUI, self-update, install script, and terminal section on landing page

- Full-screen Bubble Tea TUI with cream background fill using PadLine/FillBlankLines
- Self-update command (--update) pulling from GitHub releases
- install.sh for curl one-liner installation
- Terminal Lovers section on web landing page with install command and CLI features
- All 7 format categories, glob/directory batch support, auto-download ffmpeg
This commit is contained in:
noah
2026-03-09 22:53:10 +01:00
parent b9aac61ab8
commit 04a1f33cb1
23 changed files with 4064 additions and 0 deletions
+236
View File
@@ -0,0 +1,236 @@
package detect
// FileCategory represents the type of file
type FileCategory string
const (
CategoryImage FileCategory = "image"
CategoryDocument FileCategory = "document"
CategoryAudio FileCategory = "audio"
CategoryVideo FileCategory = "video"
CategoryData FileCategory = "data"
CategoryFont FileCategory = "font"
CategorySpreadsheet FileCategory = "spreadsheet"
CategoryUnknown FileCategory = "unknown"
)
// extensionMap maps file extensions to categories
var extensionMap = map[string]FileCategory{
// Images
"png": CategoryImage, "jpg": CategoryImage, "jpeg": CategoryImage,
"webp": CategoryImage, "gif": CategoryImage, "bmp": CategoryImage,
"tiff": CategoryImage, "tif": CategoryImage, "avif": CategoryImage,
"svg": CategoryImage, "ico": CategoryImage,
"heic": CategoryImage, "heif": CategoryImage,
"psd": CategoryImage,
// Documents
"pdf": CategoryDocument, "docx": CategoryDocument, "txt": CategoryDocument,
"md": CategoryDocument, "html": CategoryDocument, "htm": CategoryDocument,
"rtf": CategoryDocument, "epub": CategoryDocument, "pptx": CategoryDocument,
// Audio
"mp3": CategoryAudio, "wav": CategoryAudio, "flac": CategoryAudio,
"ogg": CategoryAudio, "aac": CategoryAudio, "m4a": CategoryAudio,
"wma": CategoryAudio, "opus": CategoryAudio,
// Video
"mp4": CategoryVideo, "webm": CategoryVideo, "avi": CategoryVideo,
"mov": CategoryVideo, "mkv": CategoryVideo, "flv": CategoryVideo,
"wmv": CategoryVideo, "m4v": CategoryVideo,
// Data
"csv": CategoryData, "json": CategoryData, "xml": CategoryData,
"yaml": CategoryData, "yml": CategoryData, "tsv": CategoryData,
"toml": CategoryData,
"ini": CategoryData, "env": CategoryData, "properties": CategoryData,
"ndjson": CategoryData, "jsonl": CategoryData, "sql": CategoryData,
// Spreadsheets
"xlsx": CategorySpreadsheet, "xls": CategorySpreadsheet, "ods": CategorySpreadsheet,
// Fonts
"ttf": CategoryFont, "otf": CategoryFont, "woff": CategoryFont, "woff2": CategoryFont,
}
// conversionMap maps each extension to its available target formats
// This matches the web app's conversion map exactly.
var conversionMap = map[string][]string{
// ─── Images ──────────────────────────────────────────────
"png": {"jpg", "webp", "gif", "bmp", "avif", "tiff", "ico"},
"jpg": {"png", "webp", "gif", "bmp", "avif", "tiff", "ico"},
"jpeg": {"png", "webp", "gif", "bmp", "avif", "tiff", "ico"},
"webp": {"png", "jpg", "gif", "bmp", "avif", "tiff", "ico"},
"gif": {"png", "jpg", "webp", "bmp", "avif", "tiff"},
"bmp": {"png", "jpg", "webp", "gif", "avif", "tiff"},
"tiff": {"png", "jpg", "webp", "gif", "bmp", "avif"},
"tif": {"png", "jpg", "webp", "gif", "bmp", "avif"},
"avif": {"png", "jpg", "webp", "gif", "bmp", "tiff"},
"svg": {"png", "jpg", "webp", "gif", "bmp", "avif", "tiff"},
"ico": {"png", "jpg", "webp", "gif", "bmp"},
"heic": {"png", "jpg", "webp", "gif", "bmp", "avif", "tiff"},
"heif": {"png", "jpg", "webp", "gif", "bmp", "avif", "tiff"},
"psd": {"png", "jpg", "webp", "gif", "bmp", "avif", "tiff", "ico"},
// ─── Documents ───────────────────────────────────────────
"pdf": {"txt", "html", "md", "docx", "epub"},
"docx": {"pdf", "html", "txt", "md", "epub"},
"md": {"html", "pdf", "txt", "docx", "epub", "pptx"},
"html": {"pdf", "txt", "md", "docx", "epub", "pptx"},
"htm": {"pdf", "txt", "md", "docx", "epub", "pptx"},
"txt": {"pdf", "html", "md", "docx", "epub", "pptx"},
"rtf": {"txt", "html", "md", "pdf", "docx"},
"epub": {"txt", "html", "md", "pdf"},
"pptx": {"txt", "html", "pdf", "md"},
// ─── Audio ───────────────────────────────────────────────
"mp3": {"wav", "ogg", "aac", "flac", "m4a", "opus"},
"wav": {"mp3", "ogg", "aac", "flac", "m4a", "opus"},
"flac": {"mp3", "wav", "ogg", "aac", "m4a", "opus"},
"ogg": {"mp3", "wav", "aac", "flac", "m4a", "opus"},
"aac": {"mp3", "wav", "ogg", "flac", "m4a", "opus"},
"m4a": {"mp3", "wav", "ogg", "flac", "aac", "opus"},
"wma": {"mp3", "wav", "ogg", "flac", "aac", "m4a"},
"opus": {"mp3", "wav", "ogg", "flac", "aac", "m4a"},
// ─── Video ───────────────────────────────────────────────
"mp4": {"webm", "avi", "mov", "mkv", "gif", "mp3", "wav", "ogg", "aac", "flac"},
"webm": {"mp4", "avi", "mov", "mkv", "gif", "mp3", "wav", "ogg", "aac", "flac"},
"avi": {"mp4", "webm", "mov", "mkv", "gif", "mp3", "wav", "ogg", "aac", "flac"},
"mov": {"mp4", "webm", "avi", "mkv", "gif", "mp3", "wav", "ogg", "aac", "flac"},
"mkv": {"mp4", "webm", "avi", "mov", "gif", "mp3", "wav", "ogg", "aac", "flac"},
"flv": {"mp4", "webm", "avi", "mov", "mkv", "gif", "mp3", "wav", "ogg", "aac", "flac"},
"wmv": {"mp4", "webm", "avi", "mov", "mkv", "gif", "mp3", "wav", "ogg", "aac", "flac"},
"m4v": {"mp4", "webm", "avi", "mov", "mkv", "gif", "mp3", "wav", "ogg", "aac", "flac"},
// ─── Data ────────────────────────────────────────────────
"csv": {"json", "xml", "yaml", "tsv", "toml", "xlsx", "ini", "env", "properties", "ndjson", "sql"},
"json": {"csv", "xml", "yaml", "tsv", "toml", "xlsx", "ini", "env", "properties", "ndjson", "sql"},
"xml": {"json", "csv", "yaml", "tsv", "toml", "xlsx"},
"yaml": {"json", "csv", "xml", "tsv", "toml", "xlsx", "ini", "env", "properties", "ndjson", "sql"},
"yml": {"json", "csv", "xml", "tsv", "toml", "xlsx", "ini", "env", "properties", "ndjson", "sql"},
"tsv": {"csv", "json", "xml", "yaml", "toml", "xlsx", "ndjson", "sql"},
"toml": {"json", "csv", "xml", "yaml", "tsv", "xlsx"},
"ini": {"json", "yaml", "toml", "env", "properties", "xml", "csv"},
"env": {"json", "yaml", "toml", "ini", "properties", "csv"},
"properties": {"json", "yaml", "toml", "ini", "env", "csv"},
"ndjson": {"json", "csv", "tsv", "yaml", "xml", "xlsx", "sql"},
"jsonl": {"json", "csv", "tsv", "yaml", "xml", "xlsx", "sql"},
"sql": {"json", "csv", "tsv", "yaml", "xlsx"},
// ─── Spreadsheets ────────────────────────────────────────
"xlsx": {"csv", "json", "tsv", "xml", "yaml", "toml", "ods", "html", "txt", "ndjson", "sql"},
"xls": {"xlsx", "csv", "json", "tsv", "xml", "yaml", "toml", "ods", "html", "txt", "ndjson", "sql"},
"ods": {"xlsx", "csv", "json", "tsv", "xml", "yaml", "toml", "html", "txt", "ndjson", "sql"},
// ─── Fonts ───────────────────────────────────────────────
"ttf": {"otf", "woff", "woff2"},
"otf": {"ttf", "woff", "woff2"},
"woff": {"ttf", "otf", "woff2"},
"woff2": {"ttf", "otf", "woff"},
}
// defaultTargets maps extensions to their preferred default target.
// Matches web app defaults exactly.
var defaultTargets = map[string]string{
// Images -> WebP (modern, smaller)
"png": "webp", "jpg": "webp", "jpeg": "webp", "gif": "webp",
"bmp": "png", "tiff": "png", "tif": "png", "avif": "png", "svg": "png", "ico": "png",
"heic": "jpg", "heif": "jpg", "psd": "png",
// Documents -> PDF (except PDF -> DOCX)
"docx": "pdf", "md": "html", "html": "pdf", "htm": "pdf", "txt": "pdf",
"pdf": "docx", "rtf": "docx", "epub": "html", "pptx": "pdf",
// Audio -> MP3
"wav": "mp3", "flac": "mp3", "ogg": "mp3", "aac": "mp3", "m4a": "mp3",
"wma": "mp3", "opus": "mp3", "mp3": "wav",
// Video -> MP4
"avi": "mp4", "mov": "mp4", "mkv": "mp4", "flv": "mp4", "wmv": "mp4",
"m4v": "mp4", "mp4": "webm", "webm": "mp4",
// Data -> JSON
"csv": "json", "xml": "json", "yaml": "json", "yml": "json", "tsv": "csv",
"json": "csv", "toml": "json",
"ini": "json", "env": "json", "properties": "json",
"ndjson": "json", "jsonl": "json", "sql": "json",
// Spreadsheets -> CSV
"xlsx": "csv", "xls": "csv", "ods": "csv",
// Fonts -> WOFF2 (modern web standard)
"ttf": "woff2", "otf": "woff2", "woff": "woff2", "woff2": "ttf",
}
// DetectCategory returns the category for a given file extension
func DetectCategory(ext string) FileCategory {
if cat, ok := extensionMap[ext]; ok {
return cat
}
return CategoryUnknown
}
// GetAvailableFormats returns the conversion targets for a given extension
func GetAvailableFormats(ext string) []string {
if fmts, ok := conversionMap[ext]; ok {
return fmts
}
return nil
}
// GetDefaultTarget returns the preferred default target format for an extension.
func GetDefaultTarget(ext string) string {
if def, ok := defaultTargets[ext]; ok {
return def
}
fmts := GetAvailableFormats(ext)
if len(fmts) > 0 {
return fmts[0]
}
return ""
}
// IsSupported returns true if the extension is known
func IsSupported(ext string) bool {
_, ok := extensionMap[ext]
return ok
}
// CategoryLabel returns a human-readable label
func CategoryLabel(cat FileCategory) string {
switch cat {
case CategoryImage:
return "Image"
case CategoryDocument:
return "Document"
case CategoryAudio:
return "Audio"
case CategoryVideo:
return "Video"
case CategoryData:
return "Data"
case CategoryFont:
return "Font"
case CategorySpreadsheet:
return "Spreadsheet"
default:
return "Unknown"
}
}
// CategoryIcon returns a unicode icon for the category
func CategoryIcon(cat FileCategory) string {
switch cat {
case CategoryImage:
return "\U0001f5bc" // framed picture
case CategoryDocument:
return "\U0001f4c4" // page facing up
case CategoryAudio:
return "\U0001f3b5" // musical note
case CategoryVideo:
return "\U0001f3ac" // clapper board
case CategoryData:
return "\U0001f4ca" // bar chart
case CategoryFont:
return "\U0001f524" // input latin letters
case CategorySpreadsheet:
return "\U0001f4cb" // clipboard
default:
return "\U0001f4c1" // file folder
}
}