Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
45edb73
feat: Add GPG support to nix development environment
mjc Sep 10, 2025
c7e973c
refactor: improve video processing and VMAF handling
mjc Sep 10, 2025
5228325
feat: add shared SQLite performance configuration
mjc Sep 10, 2025
dd5090f
feat: enhance analyzer performance monitoring and telemetry
mjc Sep 10, 2025
842b204
refactor: simplify Radarr webhook video processing
mjc Sep 10, 2025
74c1b59
chore: add .expert/ to gitignore for expert tool support
mjc Sep 10, 2025
4d3b26f
fix: resolve MKV attachment handling in ab-av1 operations
mjc Sep 10, 2025
1986c02
Update AI instructions to mandate full test suite runs
mjc Sep 10, 2025
5ea7b98
Remove Media.create_video function and fix upsert_video
mjc Sep 10, 2025
533bc75
Fix dashboard LiveView initialization issues
mjc Sep 10, 2025
75f87f6
Enhance test fixtures and standardize video creation patterns
mjc Sep 10, 2025
689ac64
fix: Add max_audio_channels and atmos to Video changeset optional fields
mjc Sep 10, 2025
84fc3fa
feat: Implement intelligent sync preservation for unchanged files
mjc Sep 10, 2025
f51220b
fix: Update error handling test to match video_fixture return pattern
mjc Sep 10, 2025
7d5efbd
fix: Set video state to :analyzed in CRF search tests
mjc Sep 10, 2025
749338b
fix: restore Video schema required fields
mjc Sep 10, 2025
94a3996
fix: standardize fixture return patterns for test consistency
mjc Sep 10, 2025
a41e6e5
fix: apply tuple destructuring to core test files
mjc Sep 10, 2025
6bacfa4
fix: apply tuple destructuring to CRF search tests
mjc Sep 10, 2025
fe79bad
fix: apply tuple destructuring to integration tests
mjc Sep 10, 2025
3006c2c
fix: apply tuple destructuring to AB-AV1 and analyzer tests
mjc Sep 10, 2025
83824c8
fix: apply tuple destructuring to remaining test files
mjc Sep 10, 2025
c227426
fix: make duration optional for analyzed state transition
mjc Sep 10, 2025
9f6ceeb
fix: SQLite DISTINCT compatibility for failures live view
mjc Sep 10, 2025
79d54fd
fix: replace PostgreSQL CONCAT with SQLite concatenation operator
mjc Sep 11, 2025
f6088d0
fix: add missing castable fields to Video schema
mjc Sep 11, 2025
d4899cf
refactor: use VideoUpsert module in sync operations
mjc Sep 11, 2025
1c028b7
remove: delete debug module and update documentation
mjc Sep 11, 2025
786d6af
test: fix test failures and eliminate log noise
mjc Sep 11, 2025
326f5e4
fix: make analysis fields optional in Video schema
mjc Sep 11, 2025
8635ca3
fix: add comprehensive webhook validation for Sonarr/Radarr
mjc Sep 11, 2025
bcf4347
Fix comprehensive Dialyzer type errors and add complete type coverage
mjc Sep 11, 2025
657a0e2
refactor: resolve Credo complexity issues and complete Dialyzer types
mjc Sep 11, 2025
4089feb
enhance: add Dialyzer type checking to pre-commit hook
mjc Sep 11, 2025
43dcbba
fix: handle missing fd executable gracefully in ManualScanner
mjc Sep 12, 2025
fc3f1f7
feat: add dialyzer configuration and targeted ignores
mjc Sep 12, 2025
223f51c
fix: resolve dialyzer type errors in core modules
mjc Sep 12, 2025
679f77f
refactor: improve Broadway encoder error handling and dialyzer compat…
mjc Sep 12, 2025
b99cdf9
fix: resolve minor dialyzer issues in Mix tasks
mjc Sep 12, 2025
0863715
Fix media and analyzer modules
mjc Sep 12, 2025
0368200
Fix remaining dialyzer errors in ab_av1 and UI modules
mjc Sep 12, 2025
4d4898d
Temporarily disable dialyzer in pre-commit hook
mjc Sep 12, 2025
b92d84a
fix: add video to encoder state for ProgressParser compatibility
mjc Sep 12, 2025
998b5ff
test: remove meaningless tests from Broadway modules
mjc Sep 12, 2025
cdbd66f
refactor: reorganize tests with UnitCase and split unit/integration t…
mjc Sep 12, 2025
99640a2
fix: remove :state from @optional list in Video schema
mjc Sep 12, 2025
7f423a5
Fix reviewer feedback and standardize debug logging
mjc Sep 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions .githooks/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#!/bin/sh

# Save current staged changes
git stash push --keep-index --include-untracked -m "pre-commit-stash"

# Function to cleanup on exit
cleanup() {
EXIT_CODE=$?
# Only pop stash if we created one
if [ -n "$(git stash list | grep "pre-commit-stash")" ]; then
git stash pop
fi
exit $EXIT_CODE
}

# Set up cleanup on script exit
trap cleanup EXIT

echo "Running credo strict check..."
if ! mix credo --strict; then
echo "❌ Credo strict check failed. Please fix the issues and try again."
exit 1
fi

echo "Checking code formatting..."
if ! mix format --check-formatted; then
echo "❌ Code is not properly formatted. Run 'mix format' and try again."
exit 1
fi

# Check if mix format --migrate would make changes
echo "Checking for formatting..."
if ! mix format --migrate --check-formatted; then
echo "❌ Code needs formatting. Run 'mix format --migrate' and try again."
exit 1
fi

# TODO: Re-enable dialyzer once all ab-av1 static analysis limitations are resolved
# echo "Running Dialyzer type checking..."
# if ! mix dialyzer; then
# echo "❌ Dialyzer type checking failed. Please fix the type errors and try again."
# exit 1
# fi

echo "✅ All checks passed!"
exit 0
53 changes: 43 additions & 10 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ Reencodarr is an Elixir/Phoenix application for bulk video transcoding using the

## Core Architecture

### Database: SQLite with Advanced Concurrency
**Key Change**: Migrated from PostgreSQL to SQLite with WAL mode for better deployment simplicity while maintaining concurrency.

- **Configuration**: All SQLite optimizations consolidated in `config/config.exs` with WAL mode, 256MB cache, 512MB memory mapping
- **Concurrency**: WAL mode enables simultaneous read/write operations (analyzer + sync can run concurrently)
- **Migration**: Use `scripts/migrate_to_sqlite.exs` for PostgreSQL→SQLite data migration

### Broadway Pipeline System
Three Broadway pipelines handle video processing with fault tolerance and observability:

Expand All @@ -25,26 +32,51 @@ Key pattern: Each pipeline has a Producer that checks GenServer availability bef

### Essential Commands
```bash
# Setup (requires PostgreSQL)
mix setup # Full setup: deps, DB, assets
make docker-compose-up # Start PostgreSQL container
iex -S mix phx.server # Development with live reload
# Setup (no longer requires PostgreSQL)
mix setup # Full setup: deps, SQLite DB, assets

# Testing (ALWAYS run full test suite)
mix test # Uses manual sandbox mode - ALWAYS run complete suite, never individual tests

# Database
mix ecto.reset # Drop/create/migrate/seed
mix test # Uses manual sandbox mode
mix ecto.reset # Drop/create/migrate/seed (SQLite)

# Code Quality (automated via git hooks)
mix setup_precommit # Setup git hooks for credo + formatting
mix credo --strict # Strict code analysis
mix format # Code formatting

# Debugging
# Visit /broadway-dashboard for pipeline monitoring
```

### Key Dependencies
- **Required Binaries**: `ab-av1`, `ffmpeg`, `mediainfo`
- **Database**: PostgreSQL with pool_size: 50 for dev concurrency
- **Database**: SQLite with WAL mode and optimized pragma settings (see `config/config.exs`)
- **External APIs**: Sonarr/Radarr via `CarReq` with circuit breaker pattern

## Project-Specific Patterns

### Database Configuration Pattern
**Critical**: SQLite optimizations are centralized in `config/config.exs` and must not be overridden in environment configs:

```elixir
# Base config applies to all environments
config :reencodarr, Reencodarr.Repo,
pragma: [
journal_mode: "WAL", # Enable concurrent access
busy_timeout: 120_000, # 2-minute timeout for concurrent ops
cache_size: -256_000, # 256MB cache
mmap_size: 536_870_912 # 512MB memory mapping
]
```

### Git Hooks & Code Quality
Pre-commit hooks automatically enforce code quality:
- **Setup**: `mix setup_precommit` configures git to use `.githooks/pre-commit`
- **Checks**: Credo strict mode, format validation, format migration detection
- **Stash Safety**: Unstaged changes are safely stashed during checks

### Broadway Pipeline Development
When adding new pipelines:
1. Create producer that checks GenServer availability (`crf_search_available?()` pattern)
Expand All @@ -54,9 +86,9 @@ When adding new pipelines:

### Database Query Patterns
```elixir
# Array operations for codec filtering
fragment("EXISTS (SELECT 1 FROM unnest(?) elem WHERE LOWER(elem) LIKE LOWER(?))",
v.audio_codecs, "%opus%")
# SQLite array operations for codec filtering (uses JSON functions)
fragment("EXISTS (SELECT 1 FROM json_each(?) WHERE json_each.value = ?)",
v.video_codecs, "av1")

# State queries use enum states, not boolean flags
where: v.state not in [:encoded, :failed]
Expand Down Expand Up @@ -120,3 +152,4 @@ simple_vmaf: ~r/crf\s(?<crf>\d+(?:\.\d+)?)\sVMAF\s(?<score>\d+\.\d+)\s\((?<perce
- `lib/reencodarr/services/` - External API clients
- `lib/reencodarr/ab_av1/` - Command execution and parsing
- `test/support/` - Shared test utilities and fixtures
- `scripts/` - Database migration and configuration update utilities
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,4 @@ config/dev.overrides.exs

# Log files
/logs/
.expert/
52 changes: 51 additions & 1 deletion config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,33 @@ config :reencodarr,
generators: [timestamp_type: :utc_datetime],
env: config_env()

# Shared database configuration - SQLite performance tuning
config :reencodarr, Reencodarr.Repo,
# Use binary format for arrays and maps (more efficient than JSON strings)
array_type: :binary,
map_type: :binary,
# SQLite optimizations for concurrent operations across all environments
pragma: [
# Enable WAL mode for maximum concurrency
journal_mode: "WAL",
# WAL checkpoint settings for better write performance
wal_autocheckpoint: 1000,
# Use NORMAL sync mode with WAL for good performance/safety balance
synchronous: "NORMAL",
# Store temp tables in memory for better performance
temp_store: "MEMORY",
# Enable full mutex mode for better concurrency
locking_mode: "NORMAL",
# Allow reads during page writes
read_uncommitted: true,
# Increase busy timeout for concurrent operations (2 minutes)
busy_timeout: 120_000,
# Large cache size (256MB) for better performance
cache_size: -256_000,
# Large memory mapping (512MB) for better I/O performance
mmap_size: 536_870_912
]

config :reencodarr, :temp_dir, Path.join(System.tmp_dir!(), "ab-av1")

# Configure file exclude patterns for video filtering
Expand Down Expand Up @@ -68,7 +95,30 @@ config :tailwind,
# Configures Elixir's Logger
config :logger, :console,
format: "$time $metadata[$level] $message\n",
metadata: [:request_id]
metadata: [
:request_id,
:analyzer_progress,
:normalized,
:analyzer_files_count,
:queue_length,
:analyzing,
:encoding,
:crf_searching,
:throughput,
:percent,
:stats,
:vmaf_id,
:base_args,
:vmaf_params,
:result_args,
:input_count,
:path_count,
:path,
:state,
:exit_code,
:result,
:video_info
]

# Use Jason for JSON parsing in Phoenix
config :phoenix, :json_library, Jason
Expand Down
19 changes: 2 additions & 17 deletions config/dev.exs
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,8 @@ config :reencodarr, Reencodarr.Repo,
database: "priv/reencodarr_dev.db",
stacktrace: true,
show_sensitive_data_on_connection_error: true,
pool_size: 8,
# Enable JSONB support for arrays and maps (more efficient than JSON strings)
# Use JSONB storage format for arrays
array_type: :binary,
# Use JSONB storage format for maps
map_type: :binary,
# SQLite optimizations for concurrent operations
pragma: [
# Enable WAL mode for better concurrency
journal_mode: "WAL",
# Increase busy timeout to handle concurrent operations
busy_timeout: 30_000,
# Optimize for performance
synchronous: "NORMAL",
cache_size: -2000,
temp_store: "memory"
]
# Increase pool size for better concurrency with Broadway pipelines
pool_size: 20

# For development, we disable any cache and enable
# debugging and code reloading.
Expand Down
18 changes: 6 additions & 12 deletions config/runtime.exs
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,14 @@ if System.get_env("PHX_SERVER") do
end

if config_env() == :prod do
database_url =
System.get_env("DATABASE_URL") ||
raise """
environment variable DATABASE_URL is missing.
For example: ecto://USER:PASS@HOST/DATABASE
"""

maybe_ipv6 = if System.get_env("ECTO_IPV6") in ~w(true 1), do: [:inet6], else: []
database_path =
System.get_env("DATABASE_PATH") ||
"priv/reencodarr_prod.db"

config :reencodarr, Reencodarr.Repo,
# ssl: true,
url: database_url,
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "50"),
socket_options: maybe_ipv6
database: database_path,
# Production pool size - can be overridden by DATABASE_POOL_SIZE env var
pool_size: String.to_integer(System.get_env("DATABASE_POOL_SIZE") || "20")

# The secret key base is used to sign/encrypt cookies and other secrets.
# A default value is used in config/dev.exs and config/test.exs but you
Expand Down
17 changes: 3 additions & 14 deletions config/test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,10 @@ import Config
config :reencodarr, Reencodarr.Repo,
database: "priv/reencodarr_test#{System.get_env("MIX_TEST_PARTITION")}.db",
pool: Ecto.Adapters.SQL.Sandbox,
# Reduce concurrency for SQLite
# Use single connection for test sandbox
pool_size: 1,
# Enable JSONB support for arrays and maps
array_type: :binary,
map_type: :binary,
# SQLite-specific optimizations
# Increase timeout
timeout: 30_000,
# Enable WAL mode for better concurrency
pragma: [
{"journal_mode", "WAL"},
{"busy_timeout", "30000"},
{"temp_store", "memory"},
{"cache_size", "-64000"}
]
# Test-specific timeout
timeout: 30_000

# We don't run a server during test. If one is required,
# you can enable the server option below.
Expand Down
30 changes: 30 additions & 0 deletions dialyzer.ignore-warnings
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Only ignore specific ab-av1 reachability issues due to static analysis limitations
lib/reencodarr/ab_av1/helper.ex:*: Function open_port/2 has no local return
# Success path functions unreachable because dialyzer can't see that ab-av1 open_port succeeds
pattern <_port@1, _vmaf@1, _output_file@1, _context@1> can never match the type
can never match, because previous clauses completely cover the type
The pattern
_port, _vmaf, _output_file, _context
lib/reencodarr/encoder/broadway.ex:235:8:pattern_match_cov
Function handle_encoding_result/3 will never be called
Function handle_encoding_error/3 will never be called
Function handle_critical_encoding_failure/4 will never be called
Function handle_recoverable_encoding_failure/4 will never be called
Function handle_encoding_process/4 will never be called
Function process_port_messages/2 will never be called
Function notify_encoding_success/2 will never be called
lib/reencodarr/encoder/broadway.ex:275:8:unused_fun
pattern variable _code@1 can never match the type
variable_code
previous clauses completely cover the type
:exception | :port_error
lib/reencodarr/encoder/broadway.ex:608:9:pattern_match_cov
guard clause can never succeed
when _ :: false != false
lib/reencodarr/encoder/broadway.ex:672:79:guard_fail
# Additional specific patterns for remaining errors
lib/reencodarr/encoder/broadway.ex:275:8:unused_fun
lib/reencodarr/encoder/broadway.ex:608:9:pattern_match_cov
lib/reencodarr/encoder/broadway.ex:672:79:guard_fail
# Pattern match in notify_encoding_failure - integer codes unreachable due to success path limitation
code when is_integer(code)
13 changes: 13 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@
pkgs.fd
pkgs.curl
pkgs.docker-compose
pkgs.gnupg
pkgs.pinentry
pkgs.pinentry-curses
]
++ lib.optional pkgs.stdenv.isLinux pkgs.libnotify
++ lib.optional pkgs.stdenv.isLinux pkgs.inotify-tools
Expand All @@ -97,6 +100,16 @@
export DATABASE_URL="ecto://mjc@localhost:5432/reencodarr_dev"
export SECRET_KEY_BASE="WEWsPGIpK/OgJA2ZcwzsgZxWKSAp35IsqWPYsvSUmm5awBUGpvsVOcG2kkDteXR1"
export COMPOSE_BAKE=true

# GPG Configuration
export GPG_TTY=$(tty)
export PINENTRY_USER_DATA="USE_CURSES=1"

# Ensure GPG agent is using the right pinentry
echo "pinentry-program ${pkgs.pinentry-curses}/bin/pinentry-curses" >> ~/.gnupg/gpg-agent.conf 2>/dev/null || true

# Configure git to use nix-provided GPG
git config --global gpg.program "${pkgs.gnupg}/bin/gpg"
'';
};
}
Expand Down
8 changes: 4 additions & 4 deletions lib/mix/tasks/dump.ex
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
defmodule Mix.Tasks.Dump do
use Mix.Task

@shortdoc "Dump the current state of the application"

@moduledoc """
Dumps the current state of the application to a file.

Expand All @@ -11,6 +7,10 @@ defmodule Mix.Tasks.Dump do
mix dump
"""

use Mix.Task

@shortdoc "Dump the current state of the application"

alias Reencodarr.Repo

@doc "Run the dump task asynchronously for all schemas."
Expand Down
6 changes: 3 additions & 3 deletions lib/mix/tasks/reencodarr/failure_report.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
defmodule Mix.Tasks.Reencodarr.FailureReport do
use Mix.Task

@moduledoc """
Generates and displays a video processing failure report.

Expand All @@ -26,6 +24,8 @@ defmodule Mix.Tasks.Reencodarr.FailureReport do
mix reencodarr.failure_report --format json
"""

use Mix.Task

@shortdoc "Generates video processing failure report"

def run(args) do
Expand All @@ -52,7 +52,7 @@ defmodule Mix.Tasks.Reencodarr.FailureReport do
Reencodarr.FailureReporting.print_failure_report(report_opts)

_ ->
Mix.shell().error("Invalid format. Use 'console' or 'json'.")
Mix.Shell.IO.error("Invalid format. Use 'console' or 'json'.")
System.halt(1)
end
end
Expand Down
Loading