engine

package
v0.1.12 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 9, 2026 License: GPL-3.0 Imports: 24 Imported by: 0

Documentation

Overview

Package engine contains the core processing logic for revoco. This file implements Phase 1: file discovery and JSON-to-media matching.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ApplyEXIFBatch

func ApplyEXIFBatch(
	ctx context.Context,
	destMap map[string]string,
	mediaJSON map[string]string,
	dryRun bool,
	logger *log.Logger,
	progress func(done, total int),
) (applied, dateFallback, skipped, errCount int, errs []error, fatalErr error)

ApplyEXIFBatch applies metadata to a batch of files using the persistent exiftool process. For files with JSON: uses JSON metadata. For files without JSON: falls back to filename date. The context allows cancellation during the batch processing loop. Returns an error if exiftool is not available (fatal error).

func ConvertMotionPhotos

func ConvertMotionPhotos(destMap map[string]string, dryRun bool, logger *log.Logger, progress func(done, total int)) (converted int, errs []error)

ConvertMotionPhotos finds .MP and .COVER files in destMap and converts them to .mp4 using ffmpeg (stream copy, no re-encode).

func FormatDate

func FormatDate(unix int64) string

FormatDate formats a Unix timestamp as a date-only string.

func FormatTimestamp

func FormatTimestamp(unix int64) string

FormatTimestamp formats a Unix timestamp as "YYYY:MM:DD HH:MM:SS" for EXIF.

func ParseUnixString

func ParseUnixString(s string) int64

ParseUnixString parses a string Unix timestamp, returning 0 on error.

Types

type AlbumsResult

type AlbumsResult struct {
	// MediaAlbum maps media path -> album name ("" = root / no album)
	MediaAlbum  map[string]string
	NamedAlbums []string
}

AlbumsResult holds Phase 2 output: which album (if any) each media file belongs to.

func AssignAlbums

func AssignAlbums(gfotoPath string, mediaFiles map[string]string) (*AlbumsResult, error)

AssignAlbums classifies Takeout subfolders and assigns album membership to each media file.

type AnalysisResult

type AnalysisResult struct {
	TotalMedia   int
	TotalJSON    int
	MatchRate    float64 // 0.0–1.0
	TotalBytes   int64
	Albums       []string
	EarliestDate time.Time
	LatestDate   time.Time
	MotionPhotos int
	Videos       int
}

AnalysisResult is returned by Analyze and shown on the pre-flight screen.

func Analyze

func Analyze(sourceDir string, progress func(done, total int)) (*AnalysisResult, error)

Analyze performs a quick (read-only) scan of a Takeout source directory and returns statistics for the pre-flight screen. It accepts an optional progress callback that is called with (done, total) file counts as the walk proceeds.

type ExifTool

type ExifTool struct {
	// contains filtered or unexported fields
}

ExifTool manages a persistent exiftool -stay_open process to avoid per-file process spawn overhead.

func NewExifTool

func NewExifTool(logger *log.Logger) (*ExifTool, error)

NewExifTool starts a persistent exiftool process. The logger is used to capture stderr output from exiftool.

func (*ExifTool) ApplyDateFromFilename

func (e *ExifTool) ApplyDateFromFilename(mediaPath string) (bool, error)

ApplyDateFromFilename extracts a date from the filename and applies it via exiftool.

func (*ExifTool) ApplyEXIFFromJSON

func (e *ExifTool) ApplyEXIFFromJSON(mediaPath, jsonPath string) error

ApplyEXIFFromJSON applies metadata from a Takeout JSON file to its media file.

func (*ExifTool) Close

func (e *ExifTool) Close() error

Close shuts down the persistent exiftool process.

func (*ExifTool) Execute

func (e *ExifTool) Execute(args []string) error

Execute sends a batch of arguments to exiftool and waits for completion. Arguments are one per line; exiftool reads until it sees "-execute".

type HashResult

type HashResult struct {
	// MediaHash maps media path -> hex MD5 hash
	MediaHash map[string]string
	// Unique is the deduplicated set of media paths to keep (album copies win)
	Unique []string
	// Duplicates is the count of removed duplicate files
	Duplicates int
}

HashResult holds Phase 3 deduplication output.

func DeduplicateFiles

func DeduplicateFiles(mediaFiles map[string]string, mediaAlbum map[string]string, progress func(done, total int)) (*HashResult, error)

DeduplicateFiles computes MD5 hashes for all media files and removes duplicates. Album files (mediaAlbum[path] != "") take priority over root files.

type IndexResult

type IndexResult struct {
	// MediaFiles maps media path -> its matched JSON path (empty if unmatched)
	MediaFiles map[string]string
	// OrphanJSONs are JSON files with no matching media
	OrphanJSONs []OrphanJSON
	// Stats
	TotalMedia   int
	TotalJSON    int
	TotalMatched int
	TotalOrphans int
}

IndexResult holds the output of Phase 1.

func IndexFiles

func IndexFiles(ctx context.Context, gfotoPath string, progress func(done, total int)) (*IndexResult, error)

IndexFiles walks the Google Photos subdirectory, classifies all files, and matches JSON metadata to their corresponding media files. The context allows cancellation during the matching phase.

type MissingReport

type MissingReport struct {
	Entries []metadata.MissingEntry
	Path    string
}

MissingReport is the output of Phase 8.

func GenerateReport

func GenerateReport(orphans []OrphanJSON, destDir string) (*MissingReport, error)

GenerateReport builds missing-files.json from orphan JSON entries and writes it to destDir.

type OrphanJSON

type OrphanJSON struct {
	Path         string
	Title        string
	URL          string
	PhotoTakenTS int64
	CreationTS   int64
	SourceFolder string
}

OrphanJSON is a JSON metadata file with no corresponding media file.

type PipelineConfig

type PipelineConfig struct {
	SourceDir  string
	DestDir    string
	SessionDir string // if set, logs go here instead of DestDir
	UseMove    bool
	DryRun     bool
}

PipelineConfig holds all options for a full processing run.

type PipelineResult

type PipelineResult struct {
	Stats   Stats
	Report  *MissingReport
	LogPath string
}

PipelineResult is the final output of a complete run.

func Run

func Run(cfg PipelineConfig, events chan<- ProgressEvent) (*PipelineResult, error)

Run executes the full 8-phase pipeline, emitting progress events on the provided channel. The channel is closed when the run completes. This is a convenience wrapper around RunWithContext using context.Background().

func RunWithContext

func RunWithContext(ctx context.Context, cfg PipelineConfig, events chan<- ProgressEvent) (*PipelineResult, error)

RunWithContext executes the full 8-phase pipeline with cancellation support. The channel is closed when the run completes or is cancelled.

type ProgressEvent

type ProgressEvent struct {
	Phase   int
	Label   string
	Done    int
	Total   int
	Message string // optional log message
}

ProgressEvent is emitted by the pipeline to report progress to the TUI/CLI.

type RecoverConfig

type RecoverConfig struct {
	// InputJSON is the path to missing-files.json produced by Phase 8.
	InputJSON string
	// OutputDir is where recovered files are placed.
	OutputDir string
	// SessionDir, if set, is where logs are written (instead of OutputDir).
	SessionDir string
	// CookieJar is the path to the Netscape cookie jar file.
	CookieJar string
	// Concurrency is the number of parallel downloads.
	Concurrency int
	// Delay is the minimum pause between requests (per worker) in seconds.
	Delay float64
	// MaxRetry is the maximum number of attempts per file.
	MaxRetry int
	// StartFrom is the 1-indexed entry to resume from.
	StartFrom int
	// DryRun reports what would be downloaded without doing it.
	DryRun bool
}

RecoverConfig holds all options for a recovery run.

type RecoverEvent

type RecoverEvent struct {
	Done    int
	Total   int
	Message string
	IsError bool
}

RecoverEvent is emitted during a recovery run.

type RecoverResult

type RecoverResult struct {
	Downloaded int
	Skipped    int
	Failed     int
	FailedPath string
}

RecoverResult is the final summary of a recovery run.

func RunRecover

func RunRecover(cfg RecoverConfig, events chan<- RecoverEvent) (*RecoverResult, error)

RunRecover executes the recovery pipeline. The events channel is closed when the run completes.

type Stats

type Stats struct {
	MediaFound        int
	JSONMatched       int
	JSONOrphans       int
	DuplicatesRemoved int
	FilesTransferred  int
	ConflictsResolved int
	MPConverted       int
	EXIFApplied       int
	DateFromFilename  int
	Errors            int
	Albums            int
}

Stats is the final summary of a processing run.

type TransferConfig

type TransferConfig struct {
	DestDir string
	UseMove bool
	DryRun  bool
}

TransferConfig holds options for Phase 4.

type TransferResult

type TransferResult struct {
	// DestMap maps source media path -> destination path
	DestMap           map[string]string
	FilesTransferred  int
	ConflictsResolved int
	Errors            int
	ErrorList         []error
}

TransferResult holds Phase 4 output.

func TransferFiles

func TransferFiles(
	ctx context.Context,
	unique []string,
	mediaAlbum map[string]string,
	mediaJSON map[string]string,
	mediaHash map[string]string,
	cfg TransferConfig,
	logger *log.Logger,
	progress func(done, total int),
) (*TransferResult, error)

TransferFiles copies (or moves) deduplicated media files to the destination, preserving album subdirectory structure and resolving name conflicts. The context allows cancellation during the transfer loop.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL