Skip to content

Feature/golang rewrite#17

Merged
exTerEX merged 19 commits into
mainfrom
feature/golang-rewrite
May 5, 2026
Merged

Feature/golang rewrite#17
exTerEX merged 19 commits into
mainfrom
feature/golang-rewrite

Conversation

@exTerEX
Copy link
Copy Markdown
Owner

@exTerEX exTerEX commented May 4, 2026

No description provided.

exTerEX added 18 commits March 31, 2026 17:03
- Create Go project structure under go/
- Port shared constants, utils, thumbnails, file browser, job store
- Set up gin web server with embedded static files
- Port organizer module (date_sorter, renamer, scanner)
- Add organizer API routes
- Add Go Makefile targets
- Single-pass filepath.WalkDir replaces 82+ Python rglob calls
- Generic JobStore with TTL-based cleanup replaces leaky Python dicts
- True goroutine parallelism throughout
…ses 4-7)

- Inspector: EXIF read/strip via go-exif, integrity check via imaging + ffprobe
- Resizer: fit/fill/stretch/pad modes using disintegration/imaging
- Converter: image conversion via imaging, video via ffmpeg subprocess
- Dupfinder: CPU-only perceptual hashing via goimagehash, video frame extraction
- All modules use goroutine workers with semaphore concurrency control
- Added shared utilities: IsImage, IsVideo, NormaliseExt
- Registered all routes in server.go (inspector, resizer, converter, dupfinder)
- Added corona10/goimagehash v1.1.0 dependency
- Shared: add GET /api/browse (in-page dir browser), POST /api/browse/native
- Shared: rename /api/system-info to /api/system_info, add ffmpeg encoders
- Shared: add GET /api/media for full-size media preview
- Organizer: rename /api/organizer/scan to /plan
- Organizer: execute accepts {job_id} in body instead of URL param
- Organizer: status returns unified 'plan' array, plan_count, conflicts, execution
- Organizer: PlanRename accepts start_seq parameter
Copilot AI review requested due to automatic review settings May 4, 2026 22:15
}

// Avoid overwriting
if _, err := os.Stat(dest); err == nil {
}

// Avoid overwriting
if _, err := os.Stat(dest); err == nil {

cmd = append(cmd, dest)

out, err2 := exec.Command(cmd[0], cmd[1:]...).CombinedOutput()
return nil
})
} else {
entries, err := os.ReadDir(folder)
Comment thread web/routes_converter.go
}

origSize := int64(0)
if info, err := os.Stat(source); err == nil {
Comment thread web/routes_shared.go
}

path = filepath.Clean(path)
info, err := os.Stat(path)
Comment thread web/routes_shared.go
return
}

entries, _ := os.ReadDir(path)
Comment thread web/routes_shared.go
}

filePath = filepath.Clean(filePath)
info, err := os.Stat(filePath)
Comment thread web/routes_shared.go
if contentType == "" {
contentType = "application/octet-stream"
}
c.File(filePath)
Comment thread web/routes_shared.go

// isDir returns true when path exists and is a directory.
func isDir(path string) bool {
info, err := os.Stat(path)
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR rewrites the project from a Python/Flask implementation to a Go implementation (Gin-based), embedding the web UI assets and re-implementing the converter/dupfinder/organizer backends in Go. It also migrates CI/docs tooling from Python/Sphinx to Go/Jekyll and removes the Python source + pytest suite.

Changes:

  • Introduces a Go web server (cmd/morphic, web/) serving embedded templates/static assets and exposing /api/* endpoints for shared, organizer, converter, and dupfinder features.
  • Adds Go implementations for shared utilities (jobs, scanning, thumbnails, native folder dialog) and module logic (converter, dupfinder, organizer).
  • Removes Python source code, tests, and Sphinx docs; updates Makefile/CI/CodeQL/Dependabot/docs to Go + Jekyll.

Reviewed changes

Copilot reviewed 105 out of 128 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
web/static/style.css UI styling additions (indeterminate progress, converter/organizer tweaks, stop button, interrupted notice).
web/server.go Gin router setup with embedded templates/static and no-cache middleware.
web/routes_shared.go Shared API endpoints: browse, native browse, thumbnails, system info, media serving.
web/routes_organizer.go Organizer API endpoints: plan/execute/status/cancel.
web/routes_dupfinder.go Dupfinder API endpoints: scan/status/results/cancel/delete.
web/routes_converter.go Converter API endpoints: scan/formats/convert/progress/poll/cancel/delete.
internal/shared/utils.go Go replacements for shared scanning + formatting utilities.
internal/shared/thumbnails.go Thumbnail generation (imaging + ffmpeg fallback) with in-memory cache.
internal/shared/jobs.go Generic job + job store with cancellation context and TTL cleanup.
internal/shared/file_browser.go OS-native folder dialog helpers (zenity/kdialog/osascript/powershell).
internal/shared/constants.go Shared constants (extensions, thresholds, version, excluded folders, aliases).
internal/organizer/scanner.go Organizer job planning/execution orchestration.
internal/organizer/renamer.go Organizer rename templating + rename execution.
internal/organizer/date_sorter.go Organizer EXIF/mtime date extraction + sort planning/execution.
internal/dupfinder/images.go Image perceptual hashing and duplicate grouping.
internal/dupfinder/videos.go Video probing + frame hashing and duplicate grouping.
internal/converter/scanner.go Folder scan inventory for convertible media.
internal/converter/constants.go Video container configs + conversion target tables.
internal/converter/converter_test.go Go unit tests for scanner + image conversion + tables.
cmd/morphic/main.go CLI entry-point: flags, starts server, optional browser open.
go.mod Introduces Go module and dependencies (gin, imaging, go-exif, uuid, etc.).
Makefile Replaces Python/uv targets with Go build/test/vet/run/tidy targets.
docs/index.md New Jekyll/GitHub Pages landing page + API summary.
docs/Gemfile Jekyll/GitHub Pages Ruby dependencies.
docs/_config.yml Jekyll site configuration.
docs/index.rst Removes Sphinx index.
docs/getting_started.rst Removes Sphinx getting started doc.
docs/development.rst Removes Sphinx development doc.
docs/configuration.rst Removes Sphinx configuration doc.
docs/conf.py Removes Sphinx configuration.
docs/cli.rst Removes Sphinx CLI doc.
docs/changelog.rst Removes Sphinx changelog.
docs/api/shared.rst Removes Sphinx API docs.
docs/api/frontend.rst Removes Sphinx API docs.
docs/api/dupfinder.rst Removes Sphinx API docs.
docs/api/converter.rst Removes Sphinx API docs.
.github/workflows/CI.yml Migrates CI to Go (go test, go vet, build) and coverage upload changes.
.github/workflows/codeql.yml Switches CodeQL analysis to Go + workflow scope updates.
.github/workflows/documentation.yml Migrates docs build/deploy to Jekyll (Ruby) and updates paths.
.github/workflows/release.yml Adds release workflow generating release notes and publishing releases.
.github/dependabot.yml Switches Dependabot ecosystem from pip to gomod.
.vscode/settings.json Switches IDE settings from Python pytest to Go test explorer.
.idea/misc.xml Removes Python-specific IDE settings.
.gitignore Replaces Python-focused ignores with Go/GoLand/VSCode ignores.
tests/conftest.py Removes pytest fixtures.
tests/shared/test_utils.py Removes Python shared utils tests.
tests/shared/test_thumbnails.py Removes Python thumbnail tests.
tests/shared/test_file_browser.py Removes Python file browser tests.
tests/shared/test_constants.py Removes Python constants tests.
tests/resizer/test_scanner.py Removes Python resizer tests.
tests/resizer/test_operations.py Removes Python resizer tests.
tests/organizer/test_renamer.py Removes Python organizer tests.
tests/organizer/test_date_sorter.py Removes Python organizer tests.
tests/inspector/test_scanner.py Removes Python inspector tests.
tests/inspector/test_integrity.py Removes Python inspector tests.
tests/inspector/test_exif.py Removes Python inspector tests.
src/morphic/init.py Removes Python package root.
src/morphic/shared/constants.py Removes Python shared constants.
src/morphic/shared/utils.py Removes Python shared utils.
src/morphic/shared/thumbnails.py Removes Python thumbnails.
src/morphic/shared/file_browser.py Removes Python native dialog code.
src/morphic/shared/init.py Removes Python shared exports.
src/morphic/converter/constants.py Removes Python converter constants.
src/morphic/converter/scanner.py Removes Python converter scanner.
src/morphic/converter/init.py Removes Python converter exports.
src/morphic/dupfinder/init.py Removes Python dupfinder exports.
src/morphic/organizer/date_sorter.py Removes Python organizer date sorter.
src/morphic/organizer/renamer.py Removes Python organizer renamer.
src/morphic/organizer/scanner.py Removes Python organizer scanner.
src/morphic/organizer/init.py Removes Python organizer exports.
src/morphic/resizer/operations.py Removes Python resizer ops.
src/morphic/resizer/scanner.py Removes Python resizer scanner.
src/morphic/resizer/init.py Removes Python resizer exports.
src/morphic/inspector/exif.py Removes Python inspector EXIF support.
src/morphic/inspector/integrity.py Removes Python inspector integrity checks.
src/morphic/inspector/scanner.py Removes Python inspector scanner.
src/morphic/inspector/init.py Removes Python inspector exports.
src/morphic/frontend/app.py Removes Flask app factory/CLI.
src/morphic/frontend/routes_shared.py Removes Flask shared routes.
src/morphic/frontend/routes_converter.py Removes Flask converter routes.
src/morphic/frontend/routes_dupfinder.py Removes Flask dupfinder routes.
src/morphic/frontend/routes_organizer.py Removes Flask organizer routes.
src/morphic/frontend/routes_inspector.py Removes Flask inspector routes.
src/morphic/frontend/routes_resizer.py Removes Flask resizer routes.
src/morphic/frontend/main.py Removes Python module entry-point.
src/morphic/frontend/init.py Removes Flask frontend exports.
Files not reviewed (1)
  • .idea/misc.xml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread web/routes_shared.go
Comment on lines +28 to +37
path := c.Query("path")
if path == "" {
home, _ := os.UserHomeDir()
path = home
}

path = filepath.Clean(path)
info, err := os.Stat(path)
if err != nil || !info.IsDir() {
c.JSON(http.StatusBadRequest, gin.H{"error": "Not a directory"})
Comment thread web/routes_shared.go
Comment on lines +77 to +83
// handleBrowseNative opens the OS-native folder picker dialog.
func handleBrowseNative(c *gin.Context) {
folder, available, err := shared.OpenNativeFolderDialog()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
Comment thread web/routes_shared.go
Comment on lines +189 to +194
contentType := mime.TypeByExtension(filepath.Ext(filePath))
if contentType == "" {
contentType = "application/octet-stream"
}
c.File(filePath)
}
Comment thread web/server.go
Comment on lines +23 to +33
// Serve static files from embedded FS
staticFS, _ := fs.Sub(webFS, "static")
r.StaticFS("/static", http.FS(staticFS))

// No-cache middleware (mirrors Python's @app.after_request)
r.Use(func(c *gin.Context) {
c.Header("Cache-Control", "no-cache, no-store, must-revalidate")
c.Header("Pragma", "no-cache")
c.Header("Expires", "0")
c.Next()
})
Comment on lines +116 to +131
switch job.Mode {
case "sort":
dest := job.Destination
if dest == "" {
dest = job.Folder
}
plan := PlanSort(paths, job.Template, dest)
job.mu.Lock()
job.SortPlan = plan
job.mu.Unlock()
case "rename":
plan := PlanRename(paths, job.Template, job.Operation, job.StartSeq)
job.mu.Lock()
job.RenamePlan = plan
job.mu.Unlock()
}
Comment thread web/routes_converter.go
Comment on lines +15 to +21
type conversionJob struct {
shared.Job
Total int `json:"total"`
Completed int `json:"completed"`
CurrentFile string `json:"current_file"`
Results []map[string]interface{} `json:"results"`
}
Comment thread web/routes_converter.go
Comment on lines +205 to +213
lastStr := c.Query("last")
last := -1
if lastStr != "" {
for i := 0; i < len(lastStr); i++ {
if lastStr[i] >= '0' && lastStr[i] <= '9' {
last = last*10 + int(lastStr[i]-'0')
}
}
}
Comment thread web/routes_converter.go
Comment on lines +291 to +300
func absPath(p string) (string, error) {
abs, err := os.Getwd()
if err != nil {
return p, err
}
if len(p) > 0 && p[0] == '/' {
return p, nil
}
return abs + "/" + p, nil
}
Comment on lines +18 to +28
var VideoContainers = []VideoContainerConfig{
{
Name: "MP4",
Codecs: []string{"h264", "h265", "av1"},
Extensions: []string{".mp4", ".m4a", ".m4p", ".m4b", ".m4r", ".m4v"},
},
{
Name: "Matroska",
Codecs: []string{"h264", "h265", "av1", "vp9"},
Extensions: []string{".mkv", ".mk3d", ".mka", ".mks"},
},
Comment on lines +101 to +121
for i := 0; i < numFrames; i++ {
ts := startTime + float64(i+1)*interval
frameFile := fmt.Sprintf("/tmp/morphic_frame_%d_%d.jpg", os.Getpid(), i)

cmd := exec.Command("ffmpeg", "-y",
"-ss", fmt.Sprintf("%.3f", ts),
"-i", path,
"-vframes", "1",
"-q:v", "2",
frameFile,
)
cmd.Stdout = nil
cmd.Stderr = nil

if err := cmd.Run(); err != nil {
continue
}

img, err := imaging.Open(frameFile)
os.Remove(frameFile)
if err != nil {
@exTerEX exTerEX merged commit 6bb0467 into main May 5, 2026
7 of 8 checks passed
@exTerEX exTerEX deleted the feature/golang-rewrite branch May 6, 2026 19:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants