commands

package
v0.0.16 Latest Latest
Warning

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

Go to latest
Published: Apr 3, 2026 License: BSD-3-Clause Imports: 27 Imported by: 0

Documentation

Overview

Package commands implements the CLI commands and orchestration logic for the shrink application.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type ArchiveEstimateResult added in v0.0.6

type ArchiveEstimateResult struct {
	FutureSize       int64
	ProcessingTime   int
	HasProcessable   bool
	TotalArchiveSize int64
	IsBroken         bool
	IsTimeout        bool
}

ArchiveEstimateResult holds the result of archive size estimation

type ArchiveProcessor

type ArchiveProcessor struct {
	BaseProcessor
	// contains filtered or unexported fields
}

ArchiveProcessor handles archive file processing

func NewArchiveProcessor

func NewArchiveProcessor(ffmpeg *ffmpeg.FFmpegProcessor, cfg *models.ProcessorConfig) *ArchiveProcessor

func (*ArchiveProcessor) CanProcess

func (p *ArchiveProcessor) CanProcess(m *models.ShrinkMedia) bool

func (*ArchiveProcessor) EstimateSize

func (*ArchiveProcessor) EstimateSizeForArchive

func (p *ArchiveProcessor) EstimateSizeForArchive(m *models.ShrinkMedia, cfg *models.ProcessorConfig) ArchiveEstimateResult

EstimateSizeForArchive estimates size using compressed size and inspects archive contents

func (*ArchiveProcessor) ExtractAndProcess

func (p *ArchiveProcessor) ExtractAndProcess(ctx context.Context, m *models.ShrinkMedia, cfg *models.ProcessorConfig,
	imageProc *ImageProcessor, ffmpegProc *ffmpeg.FFmpegProcessor, registry models.ProcessorRegistry, depth int,
) models.ProcessResult

ExtractAndProcess extracts archive contents and processes media recursively

func (*ArchiveProcessor) Process

type AudioProcessor

type AudioProcessor struct {
	BaseProcessor
	// contains filtered or unexported fields
}

AudioProcessor handles audio file processing

func NewAudioProcessor

func NewAudioProcessor(ffmpeg *ffmpeg.FFmpegProcessor) *AudioProcessor

func (*AudioProcessor) CanProcess

func (p *AudioProcessor) CanProcess(m *models.ShrinkMedia) bool

func (*AudioProcessor) EstimateSize

func (*AudioProcessor) Process

type BaseProcessor

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

BaseProcessor provides common functionality for all processors

func (*BaseProcessor) Category

func (b *BaseProcessor) Category() string

Category returns the media type for this processor

func (*BaseProcessor) RequiredTool added in v0.0.4

func (b *BaseProcessor) RequiredTool() string

RequiredTool returns the name of the external tool required by this processor

type BitrateFlags added in v0.0.4

type BitrateFlags struct {
	SourceAudioBitrate string `` /* 129-byte string literal not displayed */
	SourceVideoBitrate string `` /* 130-byte string literal not displayed */
	TargetAudioBitrate string `default:"128kbps" help:"Target audio bitrate" env:"SHRINK_TARGET_AUDIO_BITRATE"`
	TargetVideoBitrate string `default:"800kbps" help:"Target video bitrate" env:"SHRINK_TARGET_VIDEO_BITRATE"`
}

type Config

type Config struct {
	CoreFlags        `embed:""`
	PathFilterFlags  `embed:"" group:"PathFilter"`
	MediaFilterFlags `embed:"" group:"MediaFilter"`
	TimeFilterFlags  `embed:"" group:"Time"`
	DeletedFlags     `embed:"" group:"Deleted"`
	SavingsFlags     `embed:"" group:"Savings"`
	BitrateFlags     `embed:"" group:"Bitrate"`
	TranscodingFlags `embed:"" group:"Transcoding"`
	VideoFlags       `embed:"" group:"Video"`
	ImageFlags       `embed:"" group:"Image"`
	TextFlags        `embed:"" group:"Text"`
	ParallelFlags    `embed:"" group:"Parallel"`
	MemoryFlags      `embed:"" group:"Memory"`
	TimeoutFlags     `embed:"" group:"Timeout"`
	ScheduleFlags    `embed:"" group:"Schedule"`

	ContinueFrom string `help:"Skip media until specific file path is seen" env:"SHRINK_CONTINUE_FROM"`
	Move         string `help:"Directory to move successful files" env:"SHRINK_MOVE"`
	MoveBroken   string `help:"Directory to move unsuccessful files" env:"SHRINK_MOVE_BROKEN"`
	ForceShrink  bool   `help:"Force reprocessing of already shrinked files" env:"SHRINK_FORCE_SHRINK"`
}

Config contains all command-line flags and configuration for the shrink application

func (*Config) ApplyProfile

func (c *Config) ApplyProfile()

func (*Config) BuildProcessorConfig added in v0.0.4

func (c *Config) BuildProcessorConfig() *models.ProcessorConfig

type CoreFlags added in v0.0.4

type CoreFlags struct {
	Profile   string `` /* 151-byte string literal not displayed */
	Verbose   int    `short:"v" type:"counter" help:"Enable verbose logging (-v for info, -vv for debug)" env:"SHRINK_VERBOSE"`
	Simulate  bool   `help:"Dry run; don't actually do anything" env:"SHRINK_SIMULATE"`
	NoConfirm bool   `short:"y" help:"Don't ask for confirmation" env:"SHRINK_NO_CONFIRM"`
}

type DeletedFlags added in v0.0.4

type DeletedFlags struct {
	// HideDeleted defaults to true: most users don't want to see deleted files in listings
	HideDeleted bool `default:"true" help:"Exclude deleted files" env:"SHRINK_HIDE_DELETED"`
	OnlyDeleted bool `help:"Include only deleted files" env:"SHRINK_ONLY_DELETED"`
	// Valid defaults to true: prefer processing files with valid metadata
	Valid   bool `default:"true" help:"Attempt to process files with valid metadata" env:"SHRINK_VALID"`
	Invalid bool `help:"Attempt to process files with invalid metadata" env:"SHRINK_INVALID"`
}

type Engine added in v0.0.4

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

Engine coordinates the media analysis and processing lifecycle.

func NewEngine added in v0.0.4

func NewEngine(ui UI, cfg *models.ProcessorConfig, engCfg EngineConfig, sqlDBs []*sql.DB, registry *MediaRegistry, metrics *ShrinkMetrics) *Engine

NewEngine creates a new Engine instance.

type EngineConfig added in v0.0.4

type EngineConfig struct {
	VideoThreads    int
	Video4KThreads  int
	AudioThreads    int
	ImageThreads    int
	TextThreads     int
	AnalysisThreads int
	Timeout         TimeoutFlags
	Move            string
	ActiveTime      []*utils.TimeRange
}

EngineConfig contains concurrency and timeout settings for the engine.

type ImageFlags added in v0.0.4

type ImageFlags struct {
	TargetImageSize string  `default:"30KiB" help:"Target image size" env:"SHRINK_TARGET_IMAGE_SIZE"`
	MaxImageHeight  int     `default:"2400" help:"Maximum image height" env:"SHRINK_MAX_IMAGE_HEIGHT"`
	MaxImageWidth   int     `default:"2400" help:"Maximum image width" env:"SHRINK_MAX_IMAGE_WIDTH"`
	MaxWidthBuffer  float64 `default:"0.05" help:"Buffer percentage for width upscaling" env:"SHRINK_MAX_WIDTH_BUFFER"`
	MaxHeightBuffer float64 `default:"0.05" help:"Buffer percentage for height upscaling" env:"SHRINK_MAX_HEIGHT_BUFFER"`
}

type ImageProcessor

type ImageProcessor struct {
	BaseProcessor
}

ImageProcessor handles image file processing

func NewImageProcessor

func NewImageProcessor() *ImageProcessor

func (*ImageProcessor) CanProcess

func (p *ImageProcessor) CanProcess(m *models.ShrinkMedia) bool

func (*ImageProcessor) EstimateSize

func (*ImageProcessor) Process

type InstalledTools

type InstalledTools struct {
	FFmpeg      bool
	ImageMagick bool
	Calibre     bool
	Unar        bool
}

InstalledTools tracks which external tools are available

func (InstalledTools) IsAvailable added in v0.0.4

func (it InstalledTools) IsAvailable(toolName string) bool

IsAvailable returns true if the named tool is installed

type MediaFilterFlags added in v0.0.4

type MediaFilterFlags struct {
	VideoOnly  bool `help:"Only video files" env:"SHRINK_VIDEO_ONLY"`
	AudioOnly  bool `help:"Only audio files" env:"SHRINK_AUDIO_ONLY"`
	ImageOnly  bool `help:"Only image files" env:"SHRINK_IMAGE_ONLY"`
	TextOnly   bool `help:"Only text/ebook files" env:"SHRINK_TEXT_ONLY"`
	NoArchives bool `help:"Skip processing archive files" env:"SHRINK_NO_ARCHIVES"`
}

type MediaRegistry

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

MediaRegistry manages all media processors

func NewProcessorRegistry

func NewProcessorRegistry(ffmpeg *ffmpeg.FFmpegProcessor, cfg *models.ProcessorConfig, videoOnly, audioOnly, imageOnly, textOnly, noArchives bool) *MediaRegistry

NewProcessorRegistry creates a new registry with available processors based on flags

func (*MediaRegistry) Cleanup added in v0.0.6

func (r *MediaRegistry) Cleanup()

Cleanup releases resources held by processors

func (*MediaRegistry) GetProcessor

GetProcessor returns the appropriate processor for a media item

type MediaTypeStats

type MediaTypeStats struct {
	Total          int
	Processed      int
	Success        int
	Failed         int
	Skipped        int
	Running        int
	TotalSize      int64
	FutureSize     int64
	TotalTime      float64 // processing time in seconds
	TotalDuration  int64   // total media duration in seconds (for speed ratio)
	QueuedTimeEst  float64 // estimated time for queued items in seconds
	RunningTimeEst float64 // estimated time for running items in seconds
}

MediaTypeStats tracks processing statistics for a specific media type

func (*MediaTypeStats) RemainingTimeEst added in v0.0.9

func (s *MediaTypeStats) RemainingTimeEst() float64

RemainingTimeEst returns total estimated remaining time (queued + running)

func (*MediaTypeStats) SpaceSaved

func (s *MediaTypeStats) SpaceSaved() int64

SpaceSaved returns bytes saved

func (*MediaTypeStats) SpeedRatio

func (s *MediaTypeStats) SpeedRatio() float64

SpeedRatio returns the processing speed ratio (e.g., 2.5x realtime)

type MemoryFlags added in v0.0.6

type MemoryFlags struct {
	MemoryLimit    string `` /* 130-byte string literal not displayed */
	MemorySwapMax  string `` /* 132-byte string literal not displayed */
	UseJournald    bool   `help:"Use journald-compatible mode for systemd-run" env:"SHRINK_USE_JOURNALD"`
	DisableSystemd bool   `help:"Disable systemd-run wrapper even if available" env:"SHRINK_DISABLE_SYSTEMD"`
}

type ParallelFlags added in v0.0.4

type ParallelFlags struct {
	VideoThreads    int `default:"2" help:"Maximum concurrent video transcodes" env:"SHRINK_VIDEO_THREADS"`
	Video4KThreads  int `` /* 134-byte string literal not displayed */
	AudioThreads    int `default:"4" help:"Maximum concurrent audio transcodes" env:"SHRINK_AUDIO_THREADS"`
	ImageThreads    int `default:"8" help:"Maximum concurrent image conversions" env:"SHRINK_IMAGE_THREADS"`
	TextThreads     int `default:"2" help:"Maximum concurrent text conversions" env:"SHRINK_TEXT_THREADS"`
	AnalysisThreads int `default:"0" help:"Maximum concurrent analysis workers (0: CPU count * 4)" env:"SHRINK_ANALYSIS_THREADS"`
}

type PathFilterFlags added in v0.0.4

type PathFilterFlags struct {
	Include []string `short:"s" help:"Include paths matching pattern" env:"SHRINK_INCLUDE"`
	Exclude []string `short:"E" help:"Exclude paths matching pattern" env:"SHRINK_EXCLUDE"`
	Search  []string `help:"Search terms" env:"SHRINK_SEARCH"`
}

type ProgressLogHandler

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

ProgressLogHandler is a custom slog.Handler that coordinates logs with the progress bar. It clears the progress bar before writing a log message.

func NewProgressLogHandler

func NewProgressLogHandler(handler slog.Handler, metrics *ShrinkMetrics) *ProgressLogHandler

func (*ProgressLogHandler) Enabled

func (h *ProgressLogHandler) Enabled(ctx context.Context, level slog.Level) bool

func (*ProgressLogHandler) Handle

func (h *ProgressLogHandler) Handle(ctx context.Context, r slog.Record) error

func (*ProgressLogHandler) WithAttrs

func (h *ProgressLogHandler) WithAttrs(attrs []slog.Attr) slog.Handler

func (*ProgressLogHandler) WithGroup

func (h *ProgressLogHandler) WithGroup(name string) slog.Handler

type RunningFile added in v0.0.4

type RunningFile struct {
	MediaType     string
	Path          string
	EstimatedTime int // estimated processing time in seconds
}

RunningFile tracks a file currently being processed

type SavingsFlags added in v0.0.4

type SavingsFlags struct {
	MinSavingsVideo string `default:"5%" help:"Minimum savings for video (percentage or bytes)" env:"SHRINK_MIN_SAVINGS_VIDEO"`
	MinSavingsAudio string `default:"10%" help:"Minimum savings for audio (percentage or bytes)" env:"SHRINK_MIN_SAVINGS_AUDIO"`
	MinSavingsImage string `default:"15%" help:"Minimum savings for images (percentage or bytes)" env:"SHRINK_MIN_SAVINGS_IMAGE"`
}

type ScheduleFlags added in v0.0.16

type ScheduleFlags struct {
	ActiveTime []string `` /* 177-byte string literal not displayed */
}

type ShrinkCmd

type ShrinkCmd struct {
	Databases []string `arg:"" required:"" help:"SQLite database files or directories to scan"`
	Config    `embed:""`
	// contains filtered or unexported fields
}

ShrinkCmd is the main command for shrinking media files

func (*ShrinkCmd) ApplyContinueFrom added in v0.0.4

func (c *ShrinkCmd) ApplyContinueFrom(media []models.ShrinkMedia) []models.ShrinkMedia

func (*ShrinkCmd) Confirm added in v0.0.4

func (c *ShrinkCmd) Confirm() bool

func (*ShrinkCmd) MoveTo added in v0.0.4

func (c *ShrinkCmd) MoveTo(path string)

func (*ShrinkCmd) MoveToBroken added in v0.0.4

func (c *ShrinkCmd) MoveToBroken(path string, partFiles []string)

func (*ShrinkCmd) MoveToSkipped added in v0.0.7

func (c *ShrinkCmd) MoveToSkipped(path string, partFiles []string)

MoveToSkipped moves files that were skipped (no savings, already optimized, etc.)

func (*ShrinkCmd) PrintSummary added in v0.0.4

func (c *ShrinkCmd) PrintSummary(media []models.ShrinkMedia)

func (*ShrinkCmd) PrintUnknownExtensions added in v0.0.4

func (c *ShrinkCmd) PrintUnknownExtensions()

func (*ShrinkCmd) Run

func (c *ShrinkCmd) Run(ctx *kong.Context) error

func (*ShrinkCmd) SortByEfficiency added in v0.0.4

func (c *ShrinkCmd) SortByEfficiency(media []models.ShrinkMedia)

type ShrinkMetrics

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

ShrinkMetrics aggregates statistics across all media types

func NewShrinkMetrics

func NewShrinkMetrics() *ShrinkMetrics

NewShrinkMetrics creates a new metrics tracker

func (*ShrinkMetrics) ClearProgress

func (m *ShrinkMetrics) ClearProgress()

ClearProgress erases the currently printed progress block from the screen

func (*ShrinkMetrics) IsTTY

func (m *ShrinkMetrics) IsTTY() bool

IsTTY returns whether the output is a TTY

func (*ShrinkMetrics) LogSummary

func (m *ShrinkMetrics) LogSummary()

LogSummary logs the final metrics summary

func (*ShrinkMetrics) PrintProgress

func (m *ShrinkMetrics) PrintProgress()

PrintProgress prints the current progress with summary table Errors are printed normally via slog and will temporarily overwrite progress Progress is reprinted on next update cycle

func (*ShrinkMetrics) RecordFailure

func (m *ShrinkMetrics) RecordFailure(mediaType string, processingTime float64)

RecordFailure records a failed processing

func (*ShrinkMetrics) RecordQueuedTime added in v0.0.9

func (m *ShrinkMetrics) RecordQueuedTime(mediaType string, estimatedTime int)

RecordQueuedTime adds estimated processing time for a queued item

func (*ShrinkMetrics) RecordRunning added in v0.0.4

func (m *ShrinkMetrics) RecordRunning(mediaType string, path string, estimatedTime int)

RecordRunning records that a media item is starting to be processed

func (*ShrinkMetrics) RecordSkipped

func (m *ShrinkMetrics) RecordSkipped(mediaType string)

RecordSkipped records a skipped media item

func (*ShrinkMetrics) RecordStarted

func (m *ShrinkMetrics) RecordStarted(mediaType string, path string)

RecordStarted records that a media item is being processed

func (*ShrinkMetrics) RecordStopped added in v0.0.4

func (m *ShrinkMetrics) RecordStopped(mediaType string, path string)

RecordStopped records that a media item has finished processing

func (*ShrinkMetrics) RecordSuccess

func (m *ShrinkMetrics) RecordSuccess(mediaType string, size, futureSize int64, processingTime float64, duration int64)

RecordSuccess records a successful processing

func (*ShrinkMetrics) SetCancelled added in v0.0.16

func (m *ShrinkMetrics) SetCancelled(cancelled bool)

SetCancelled sets the cancellation flag

type TextFlags added in v0.0.4

type TextFlags struct {
	SkipOCR  bool `help:"Skip OCR for PDFs that already contain text" env:"SHRINK_SKIP_OCR"`
	ForceOCR bool `help:"Force OCR even on PDFs with text" env:"SHRINK_FORCE_OCR"`
	RedoOCR  bool `help:"Re-do OCR on PDFs that already have OCR" env:"SHRINK_REDO_OCR"`
	NoOCR    bool `help:"Skip OCR entirely" env:"SHRINK_NO_OCR"`
}

type TextProcessor

type TextProcessor struct {
	BaseProcessor
	// contains filtered or unexported fields
}

TextProcessor handles text/ebook file processing

func NewTextProcessor

func NewTextProcessor() *TextProcessor

func (*TextProcessor) CanProcess

func (p *TextProcessor) CanProcess(m *models.ShrinkMedia) bool

func (*TextProcessor) Cleanup added in v0.0.6

func (p *TextProcessor) Cleanup()

Cleanup releases resources held by the TextProcessor

func (*TextProcessor) EstimateSize

func (*TextProcessor) Process

type TimeFilterFlags added in v0.0.4

type TimeFilterFlags struct {
	CreatedAfter   string `help:"Created after date" env:"SHRINK_CREATED_AFTER"`
	ModifiedAfter  string `help:"Modified after date" env:"SHRINK_MODIFIED_AFTER"`
	ModifiedBefore string `help:"Modified before date" env:"SHRINK_MODIFIED_BEFORE"`
}

type TimeoutFlags added in v0.0.4

type TimeoutFlags struct {
	VideoTimeout          string  `default:"90m" help:"Video timeout when duration is unknown" env:"SHRINK_VIDEO_TIMEOUT"`
	AudioTimeout          string  `default:"10m" help:"Audio timeout when duration is unknown" env:"SHRINK_AUDIO_TIMEOUT"`
	ImageTimeout          string  `default:"10m" help:"Image timeout" env:"SHRINK_IMAGE_TIMEOUT"`
	TextTimeout           string  `default:"20m" help:"Text timeout" env:"SHRINK_TEXT_TIMEOUT"`
	ArchiveAnalyzeTimeout string  `default:"5m" help:"Archive analysis (lsar) timeout" env:"SHRINK_ARCHIVE_ANALYZE_TIMEOUT"`
	ArchiveGlobTimeout    string  `default:"2m" help:"Archive glob operations timeout" env:"SHRINK_ARCHIVE_GLOB_TIMEOUT"`
	ArchiveExtractTimeout string  `default:"30m" help:"Archive extraction (unar) timeout" env:"SHRINK_ARCHIVE_EXTRACT_TIMEOUT"`
	VideoTimeoutMult      float64 `default:"3.0" help:"Video timeout multiplier (timeout = duration * multiplier)" env:"SHRINK_VIDEO_TIMEOUT_MULT"`
	AudioTimeoutMult      float64 `default:"0.5" help:"Audio timeout multiplier (timeout = duration * multiplier)" env:"SHRINK_AUDIO_TIMEOUT_MULT"`
	SplitLongerThan       string  `default:"37mins" help:"Split audio longer than this duration" env:"SHRINK_SPLIT_LONGER_THAN"`
	MinSplitSegment       string  `default:"90s" help:"Minimum split segment duration" env:"SHRINK_MIN_SPLIT_SEGMENT"`
	DeleteUnplayable      bool    `help:"Delete unplayable files" env:"SHRINK_DELETE_UNPLAYABLE"`
	AlwaysSplit           bool    `help:"Always split audio on silence" env:"SHRINK_ALWAYS_SPLIT"`
}

type TranscodingFlags added in v0.0.4

type TranscodingFlags struct {
	TranscodingVideoRate float64 `default:"1.8" help:"Ratio of duration eg. 4x realtime speed" env:"SHRINK_TRANSCODING_VIDEO_RATE"`
	TranscodingAudioRate float64 `default:"150" help:"Ratio of duration eg. 100x realtime speed" env:"SHRINK_TRANSCODING_AUDIO_RATE"`
	TranscodingImageTime float64 `default:"1.5" help:"Seconds to process an image" env:"SHRINK_TRANSCODING_IMAGE_TIME"`
	VerboseFFmpeg        bool    `help:"Enable verbose FFmpeg logging" env:"SHRINK_VERBOSE_FFMPEG"`
}

type UI added in v0.0.4

type UI interface {
	Confirm() bool
	MoveTo(path string)
	MoveToBroken(path string, partFiles []string)
	MoveToSkipped(path string, partFiles []string)
}

UI interface for user interaction and file operations. It abstracts CLI-specific actions from the core engine.

type UnknownExt added in v0.0.6

type UnknownExt struct {
	Count int
	Size  int64
}

UnknownExt tracks the count and total size of files with an unknown extension

type VideoFlags added in v0.0.4

type VideoFlags struct {
	Preset          string `help:"SVT-AV1 preset (0-13, lower is slower/better) (default depends on profile)" env:"SHRINK_PRESET"`
	CRF             string `help:"CRF value for SVT-AV1 (0-63, lower is better) (default depends on profile)" env:"SHRINK_CRF"`
	MaxVideoHeight  int    `default:"960" help:"Maximum video height" env:"SHRINK_MAX_VIDEO_HEIGHT"`
	MaxVideoWidth   int    `default:"1440" help:"Maximum video width" env:"SHRINK_MAX_VIDEO_WIDTH"`
	Keyframes       bool   `help:"Extract keyframes only" env:"SHRINK_KEYFRAMES"`
	NoPreserveVideo bool   `help:"Don't preserve video when audio-only" env:"SHRINK_NO_PRESERVE_VIDEO"`
	IncludeTimecode bool   `help:"Include timecode streams in output" env:"SHRINK_INCLUDE_TIMECODE"`
	// DeleteLarger defaults to true: keep the smaller file (usually the transcoded output)
	DeleteLarger bool `default:"true" help:"Delete larger of transcode or original files" env:"SHRINK_DELETE_LARGER"`
}

type VideoProcessor

type VideoProcessor struct {
	BaseProcessor
	// contains filtered or unexported fields
}

VideoProcessor handles video file processing

func NewVideoProcessor

func NewVideoProcessor(ffmpeg *ffmpeg.FFmpegProcessor) *VideoProcessor

func (*VideoProcessor) CanProcess

func (p *VideoProcessor) CanProcess(m *models.ShrinkMedia) bool

func (*VideoProcessor) EstimateSize

func (*VideoProcessor) Process

Jump to

Keyboard shortcuts

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