···
1
+
#!/usr/bin/env -S nix shell nixpkgs#parallel nixpkgs#ffmpeg nixpkgs#imagemagick --command bash
3
+
# Define temporary directory variables globally so trap can access them
7
+
# Function to clean up and exit
9
+
echo -e "\nInterrupted. Cleaning up temporary files..."
10
+
# Only remove if directories were actually created
11
+
[ -n "$FRAMES_IN" ] && [ -d "$FRAMES_IN" ] && rm -rf "$FRAMES_IN"
12
+
[ -n "$FRAMES_OUT" ] && [ -d "$FRAMES_OUT" ] && rm -rf "$FRAMES_OUT"
16
+
# Set up trap for Ctrl+C (SIGINT)
17
+
trap cleanup_and_exit INT
19
+
# Check if input and output parameters are provided
20
+
if [ $# -lt 2 ]; then
21
+
echo "Usage: $0 input_video output_video [num_threads]"
27
+
# Default to number of CPU cores if threads not specified
28
+
NUM_THREADS=${3:-$(nproc)}
30
+
# Determine and create temporary directories, preferring RAM disk
32
+
if [ -d "/dev/shm" ] && [ -w "/dev/shm" ]; then
33
+
TEMP_BASE="/dev/shm"
34
+
echo "Using RAM disk (/dev/shm) for temporary files."
35
+
elif [ -d "/tmp" ] && [ -w "/tmp" ] && mount | grep -q 'on /tmp type tmpfs'; then
37
+
echo "Using RAM disk (/tmp) for temporary files."
40
+
echo "RAM disk not available or not writable, using current directory for temporary files."
43
+
# Create unique temporary directory names using process ID
44
+
# Add a random suffix for extra safety in case PID wraps or script runs concurrently
45
+
RAND_SUFFIX=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 8)
46
+
FRAMES_IN="${TEMP_BASE}/inkify_frames_in_${$}_${RAND_SUFFIX}"
47
+
FRAMES_OUT="${TEMP_BASE}/inkify_frames_out_${$}_${RAND_SUFFIX}"
49
+
# Check if magick command exists
50
+
if ! command -v magick &>/dev/null; then
51
+
echo "Error: ImageMagick (magick command) not found. Please install it."
52
+
# Attempt cleanup before exiting
53
+
[ -d "$FRAMES_IN" ] && rm -rf "$FRAMES_IN"
54
+
[ -d "$FRAMES_OUT" ] && rm -rf "$FRAMES_OUT"
58
+
# Check if eink-2color.png exists
59
+
if [ ! -f "eink-2color.png" ]; then
60
+
echo "Error: Colormap file 'eink-2color.png' not found in the current directory."
61
+
# Attempt cleanup before exiting
62
+
[ -d "$FRAMES_IN" ] && rm -rf "$FRAMES_IN"
63
+
[ -d "$FRAMES_OUT" ] && rm -rf "$FRAMES_OUT"
67
+
# Create temporary directories only after checks pass
68
+
mkdir -p "$FRAMES_IN"
69
+
if [ $? -ne 0 ]; then echo "Error: Could not create temporary directory $FRAMES_IN"; exit 1; fi
70
+
mkdir -p "$FRAMES_OUT"
71
+
if [ $? -ne 0 ]; then echo "Error: Could not create temporary directory $FRAMES_OUT"; rm -rf "$FRAMES_IN"; exit 1; fi
74
+
# Extract frames from video and detect framerate
75
+
FRAMERATE=$(ffprobe -v error -select_streams v -of default=noprint_wrappers=1:nokey=1 -show_entries stream=r_frame_rate "$INPUT_VIDEO" 2>/dev/null | bc -l | awk '{printf "%.2f", $0}')
76
+
if [ -z "$FRAMERATE" ] || [ "$FRAMERATE" = "0.00" ]; then
77
+
echo "Error: Could not detect framerate for $INPUT_VIDEO."
78
+
cleanup_and_exit # Use cleanup function
80
+
echo "Detected framerate: $FRAMERATE fps"
82
+
# Extract frames from video (use -threads for faster extraction)
83
+
echo "Extracting frames to $FRAMES_IN..."
84
+
if ! ffmpeg -threads "$NUM_THREADS" -i "$INPUT_VIDEO" "$FRAMES_IN/frame_%04d.png" >/dev/null 2>&1; then
85
+
echo "Error: ffmpeg failed to extract frames from $INPUT_VIDEO."
86
+
cleanup_and_exit # Use cleanup function
89
+
# Check if frames were extracted
90
+
if ! ls "$FRAMES_IN"/frame_*.png > /dev/null 2>&1; then
91
+
echo "Error: No frames were extracted to $FRAMES_IN. Check input video and permissions."
96
+
# Process frames in parallel using GNU Parallel
97
+
if command -v parallel &>/dev/null; then
98
+
echo "Processing frames in parallel with $NUM_THREADS threads..."
99
+
# Need to export FRAMES_OUT for parallel to see it in subshells
101
+
# Use parallel's built-in path manipulation {/} for basename
102
+
if ! find "$FRAMES_IN" -name "frame_*.png" | parallel --progress -j "$NUM_THREADS" \
103
+
"magick {} -dither FloydSteinberg -define dither:diffusion-amount=100% -remap eink-2color.png \"$FRAMES_OUT/{/}\""; then
104
+
echo "Error: Parallel processing with magick failed."
108
+
echo "GNU Parallel not found. Processing frames sequentially..."
109
+
# Process each frame with the dithering effect
111
+
for frame in "$FRAMES_IN"/frame_*.png; do
112
+
# Check if the file exists before processing
113
+
if [ -f "$frame" ]; then
114
+
# Construct output path correctly using basename
115
+
output_frame="$FRAMES_OUT/$(basename "$frame")"
116
+
if ! magick "$frame" -dither FloydSteinberg -define dither:diffusion-amount=100% -remap eink-2color.png "$output_frame"; then
117
+
echo "Error: magick failed to process $frame."
118
+
# Decide if you want to stop on first error or continue
119
+
cleanup_and_exit # Stop on first error
120
+
# continue # Or uncomment to continue processing other frames
122
+
# echo "Processed: $frame" # Can be too verbose
123
+
processed_count=$((processed_count + 1))
126
+
echo "Processed $processed_count frames sequentially."
127
+
if [ $processed_count -eq 0 ]; then
128
+
echo "Error: No frames found to process sequentially in $FRAMES_IN"
133
+
# Check if output frames exist before attempting recombination
134
+
if ! ls "$FRAMES_OUT"/frame_*.png > /dev/null 2>&1; then
135
+
echo "Error: No processed frames found in $FRAMES_OUT. Image processing likely failed."
140
+
# Recombine frames into video with detected framerate (use -threads for faster encoding)
141
+
echo "Recombining frames from $FRAMES_OUT..."
142
+
# Allow ffmpeg to prompt for overwrite by removing output redirection
143
+
if ! ffmpeg -framerate "$FRAMERATE" -threads "$NUM_THREADS" -i "$FRAMES_OUT/frame_%04d.png" -c:v libx264 -preset faster -pix_fmt yuv420p "$OUTPUT_VIDEO"; then
144
+
echo "Error: ffmpeg failed to recombine frames into $OUTPUT_VIDEO (or user cancelled overwrite)."
145
+
cleanup_and_exit # Use cleanup function
148
+
# Use the cleanup function for consistency
149
+
cleanup_and_exit() {
150
+
echo -e "\nCleaning up temporary files..."
151
+
# Only remove if directories were actually created
152
+
[ -n "$FRAMES_IN" ] && [ -d "$FRAMES_IN" ] && rm -rf "$FRAMES_IN"
153
+
[ -n "$FRAMES_OUT" ] && [ -d "$FRAMES_OUT" ] && rm -rf "$FRAMES_OUT"
155
+
# Call cleanup explicitly at the end (trap handles interruptions)
157
+
# Reset exit code to 0 for successful completion