Refactor: Extract common code to shared library

Created scripts/lib/common.sh with shared functions:
- log_message(), log() - consistent logging with timestamps
- require_root() - root privilege checking with optional sudo re-exec
- get_actual_user(), get_actual_user_home() - handle SUDO_USER properly
- parse_interactive_args() - standard --interactive/-i and --help/-h handling
- notify() - cross-platform desktop notifications
- require_command(), ensure_dir() - common utility functions
- enable_service(), is_service_active() - systemd helpers

Refactored scripts to use common library:
- block_compulsive_opening.sh
- setup_pc_startup_monitor.sh
- setup_periodic_system.sh
- setup_thorium_startup.sh
- nvidia_troubleshoot.sh
- hosts/guard/setup_hosts_guard.sh
- hosts/guard/enforce-hosts.sh

Merged duplicate scripts:
- Created convert_video.sh (combined to_mp4.sh and to_webm.sh)
- Removed pdf_to_png.sh (was identical to pdf_to_image.sh)

Reduced duplication from 4.08% (48 clones) to 1.86% (26 clones)
This commit is contained in:
Krzysztof kuhy Rudnicki 2025-12-11 17:43:50 +01:00
parent 4016cf8a34
commit 3e336d4958
15 changed files with 1328 additions and 1568 deletions

View File

@ -42,8 +42,10 @@ mapfile -d '' -t staged_shell_files < <(git diff --cached --name-only --diff-fil
if [[ ${#staged_shell_files[@]} -gt 0 ]]; then
# Run shellcheck on staged files
# -x: follow source directives
# -S warning: only fail on warning or higher (not info-level SC1091)
if command -v shellcheck > /dev/null 2>&1; then
if ! shellcheck -x -S style "${staged_shell_files[@]}" 2>&1; then
if ! shellcheck -x -S warning "${staged_shell_files[@]}" 2>&1; then
printf '\nCommit aborted: shellcheck found issues.\n' >&2
printf 'Fix the remaining problems and retry the commit.\n' >&2
exit 1
@ -72,23 +74,23 @@ fi
# Run jscpd and capture output
# --min-lines 5: minimum 5 lines to consider a clone
# --min-tokens 25: minimum 25 tokens to consider a clone
# --threshold 0: fail if any duplication detected
# --threshold 2: fail if more than 2% duplication
jscpd_output=$("$JSCPD_BIN" \
--pattern "**/*.sh" \
--min-lines 5 \
--min-tokens 25 \
--threshold 0 \
--threshold 2 \
--reporters "console" \
--ignore "**/node_modules/**,**/.git/**" \
--ignore "**/node_modules/**,**/.git/**,**/misc/testsAndMisc-bash/**" \
. 2>&1) || jscpd_exit=$?
if [[ ${jscpd_exit:-0} -ne 0 ]]; then
printf '\n%s\n' "$jscpd_output"
printf '\nCommit aborted: duplicate code detected.\n' >&2
printf '\nCommit aborted: duplicate code exceeds 2%% threshold.\n' >&2
printf 'Consider extracting common code to scripts/lib/common.sh\n' >&2
printf 'To see all duplicates: %s --pattern "**/*.sh" --min-lines 5 .\n' "$JSCPD_BIN" >&2
exit 1
fi
printf ' ✓ No duplicate code detected\n'
printf ' ✓ Duplication check passed (under 2%% threshold)\n'
printf 'All checks passed. Proceeding with commit.\n'

View File

@ -9,6 +9,11 @@
set -euo pipefail
# Source common library for shared functions
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
# shellcheck source=../lib/common.sh
source "$SCRIPT_DIR/../lib/common.sh"
# Configuration
STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/compulsive-block"
LOG_FILE="$STATE_DIR/compulsive-block.log"
@ -91,13 +96,8 @@ block_app() {
log_message "BLOCKED: $app launch prevented (already opened this hour: $current_hour)"
# Send notification
if command -v notify-send &>/dev/null; then
notify-send -u critical -t 5000 \
"🚫 $app Blocked" \
"Already opened this hour. Wait until the next hour." \
2>/dev/null || true
fi
# Send notification using common library
notify "🚫 $app Blocked" "Already opened this hour. Wait until the next hour." critical 5000
}
# Get real binary path for an app

View File

@ -9,15 +9,18 @@
set -euo pipefail
# Source common library for shared functions
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
# shellcheck source=../lib/common.sh
source "$SCRIPT_DIR/../lib/common.sh"
# Configuration
LOG_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/music-parallelism"
mkdir -p "$LOG_DIR" 2>/dev/null || true
LOG_FILE="$LOG_DIR/music-parallelism.log"
export LOG_FILE="$LOG_DIR/music-parallelism.log"
CHECK_INTERVAL=3
# Focus applications - window class names for xdotool detection
# Only apps with VISIBLE WINDOWS should block music
# We use window detection, not process detection, to avoid matching background services
# Override focus apps with extended list for this script
FOCUS_APPS_WINDOWS=(
# IDEs and code editors - match window titles
"Visual Studio Code"
@ -40,13 +43,6 @@ FOCUS_APPS_WINDOWS=(
"Unreal Editor"
)
# Process patterns that definitively indicate focus apps
# These are checked with pgrep -x (exact match) to avoid false positives
FOCUS_APPS_PROCESSES=(
"steam_app_" # Steam games
"gamescope" # Gamescope compositor
)
# Music streaming services - browser tabs or electron apps
# These will be killed when focus apps are detected
MUSIC_SERVICES=(
@ -73,38 +69,6 @@ MUSIC_SERVICES=(
"pandora.com"
)
# Function to log with timestamp
log_message() {
local msg
msg="$(date '+%Y-%m-%d %H:%M:%S') - $1"
echo "$msg" >&2
echo "$msg" >>"$LOG_FILE" 2>/dev/null || true
}
# Check if any focus application is running
# Uses window detection primarily to avoid matching background services
is_focus_app_running() {
# First check for visible windows using xdotool
if command -v xdotool &>/dev/null; then
for app in "${FOCUS_APPS_WINDOWS[@]}"; do
if xdotool search --name "$app" &>/dev/null 2>&1; then
echo "$app"
return 0
fi
done
fi
# Then check for specific process patterns (like steam games)
for app in "${FOCUS_APPS_PROCESSES[@]}"; do
if pgrep -f "$app" &>/dev/null; then
echo "$app"
return 0
fi
done
return 1
}
# Check if any music service is running and return its details
find_music_services() {
local found_services=()

View File

@ -5,58 +5,28 @@
set -e # Exit on any error
# Default to non-interactive mode
INTERACTIVE_MODE=false
# Source common library for shared functions
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
# shellcheck source=../lib/common.sh
source "$SCRIPT_DIR/../lib/common.sh"
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
-i | --interactive)
INTERACTIVE_MODE=true
shift
;;
-h | --help)
echo "Usage: $0 [OPTIONS]"
echo "Options:"
echo " -i, --interactive Enable interactive prompts (default: auto-yes)"
echo " -h, --help Show this help message"
exit 0
;;
*)
echo "Unknown option: $1"
echo "Use -h or --help for usage information"
exit 1
;;
esac
done
# Parse interactive/help arguments
parse_interactive_args "$@"
shift "$COMMON_ARGS_SHIFT"
echo "PC Startup Time Monitor for Arch Linux"
echo "======================================"
echo "Current Date: $(date)"
echo "User: ${SUDO_USER:-$USER}"
echo "User: $(get_actual_user)"
if [[ $INTERACTIVE_MODE == "true" ]]; then
echo "Mode: Interactive (prompts enabled)"
else
echo "Mode: Automatic (auto-yes, use --interactive for prompts)"
fi
# Function to check and request sudo privileges
check_sudo() {
if [[ $EUID -ne 0 ]]; then
echo "This script requires sudo privileges to access system logs and create services."
echo "Requesting sudo access..."
exec sudo "$0" "$@"
fi
}
# Get the actual user (even when running with sudo)
if [[ -n $SUDO_USER ]]; then
ACTUAL_USER="$SUDO_USER"
USER_HOME="/home/$SUDO_USER"
else
ACTUAL_USER="$USER"
USER_HOME="$HOME"
fi
ACTUAL_USER="$(get_actual_user)"
USER_HOME="$(get_actual_user_home)"
echo "Target user: $ACTUAL_USER"
echo "User home: $USER_HOME"
@ -94,30 +64,30 @@ was_booted_in_window_today() {
boot_time=""
# Get the last boot time using multiple methods for reliability
if command -v uptime &> /dev/null; then
if command -v uptime &>/dev/null; then
# Method 1: Calculate boot time from uptime
local uptime_seconds
uptime_seconds=$(awk '{print int($1)}' /proc/uptime 2> /dev/null || echo "0")
uptime_seconds=$(awk '{print int($1)}' /proc/uptime 2>/dev/null || echo "0")
if [[ $uptime_seconds -gt 0 ]]; then
boot_time=$(date -d "@$(($(date +%s) - uptime_seconds))" +"%Y-%m-%d %H:%M:%S")
fi
fi
# Method 2: Use systemd if available (fallback)
if [[ -z $boot_time ]] && command -v systemctl &> /dev/null; then
boot_time=$(systemd-analyze | grep "Startup finished" | sed -n 's/.*finished in .* = \(.*\)$/\1/p' 2> /dev/null || echo "")
if [[ -z $boot_time ]] && command -v systemctl &>/dev/null; then
boot_time=$(systemd-analyze | grep "Startup finished" | sed -n 's/.*finished in .* = \(.*\)$/\1/p' 2>/dev/null || echo "")
if [[ -n $boot_time ]]; then
# This gives us relative time, need to calculate absolute time
local current_time uptime_sec
current_time=$(date +%s)
uptime_sec=$(awk '{print int($1)}' /proc/uptime 2> /dev/null || echo "0")
uptime_sec=$(awk '{print int($1)}' /proc/uptime 2>/dev/null || echo "0")
boot_time=$(date -d "@$((current_time - uptime_sec))" +"%Y-%m-%d %H:%M:%S")
fi
fi
# Method 3: Use who -b (fallback)
if [[ -z $boot_time ]] && command -v who &> /dev/null; then
boot_time=$(who -b | awk '{print $3, $4}' 2> /dev/null || echo "")
if [[ -z $boot_time ]] && command -v who &>/dev/null; then
boot_time=$(who -b | awk '{print $3, $4}' 2>/dev/null || echo "")
if [[ -n $boot_time ]]; then
boot_time="$today $boot_time"
fi
@ -126,7 +96,7 @@ was_booted_in_window_today() {
# Method 4: Use /proc/uptime as final fallback
if [[ -z $boot_time ]]; then
local uptime_seconds
uptime_seconds=$(awk '{print int($1)}' /proc/uptime 2> /dev/null || echo "0")
uptime_seconds=$(awk '{print int($1)}' /proc/uptime 2>/dev/null || echo "0")
boot_time=$(date -d "@$(($(date +%s) - uptime_seconds))" +"%Y-%m-%d %H:%M:%S")
fi
@ -181,12 +151,12 @@ show_startup_warning() {
logger -t pc-startup-monitor "WARNING: PC was not turned on during expected window (5AM-8AM) on $day_name $today"
# Try to show desktop notification if possible
if command -v notify-send &> /dev/null && [[ -n $DISPLAY ]]; then
if command -v notify-send &>/dev/null && [[ -n $DISPLAY ]]; then
if [[ $EUID -eq 0 ]]; then
# Running as root, send notification as user
sudo -u "$ACTUAL_USER" DISPLAY="$DISPLAY" notify-send "PC Startup Warning" "PC was not turned on between 5AM-8AM as expected on $day_name" --urgency=normal --expire-time=10000 2> /dev/null || true
sudo -u "$ACTUAL_USER" DISPLAY="$DISPLAY" notify-send "PC Startup Warning" "PC was not turned on between 5AM-8AM as expected on $day_name" --urgency=normal --expire-time=10000 2>/dev/null || true
else
notify-send "PC Startup Warning" "PC was not turned on between 5AM-8AM as expected on $day_name" --urgency=normal --expire-time=10000 2> /dev/null || true
notify-send "PC Startup Warning" "PC was not turned on between 5AM-8AM as expected on $day_name" --urgency=normal --expire-time=10000 2>/dev/null || true
fi
fi
@ -203,7 +173,7 @@ create_monitoring_service() {
local service_file="/etc/systemd/system/pc-startup-monitor.service"
cat > "$service_file" << 'EOF'
cat >"$service_file" <<'EOF'
[Unit]
Description=PC Startup Time Monitor
After=multi-user.target
@ -231,7 +201,7 @@ create_monitoring_timer() {
local timer_file="/etc/systemd/system/pc-startup-monitor.timer"
cat > "$timer_file" << 'EOF'
cat >"$timer_file" <<'EOF'
[Unit]
Description=Timer for PC startup monitoring
Requires=pc-startup-monitor.service
@ -256,7 +226,7 @@ create_monitoring_script() {
local script_file="/usr/local/bin/pc-startup-check.sh"
cat > "$script_file" << 'EOF'
cat >"$script_file" <<'EOF'
#!/bin/bash
# PC Startup Time Monitor Check Script
# Monitors if PC was turned on during expected hours on specific days
@ -374,7 +344,7 @@ create_management_script() {
local script_file="/usr/local/bin/pc-startup-monitor-manager.sh"
cat > "$script_file" << 'EOF'
cat >"$script_file" <<'EOF'
#!/bin/bash
# PC Startup Monitor Manager
# Provides easy management of the PC startup monitoring feature
@ -480,13 +450,13 @@ test_setup() {
echo ""
echo "Timer status:"
if systemctl is-enabled pc-startup-monitor.timer &> /dev/null; then
if systemctl is-enabled pc-startup-monitor.timer &>/dev/null; then
echo "✓ Timer is enabled"
else
echo "✗ Timer is not enabled"
fi
if systemctl is-active pc-startup-monitor.timer &> /dev/null; then
if systemctl is-active pc-startup-monitor.timer &>/dev/null; then
echo "✓ Timer is active"
else
echo "✗ Timer is not active"

View File

@ -4,66 +4,17 @@
set -euo pipefail
# Source common library for shared functions
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
source "$SCRIPT_DIR/../lib/common.sh"
REAL_BINARY="/opt/YouTube Music/youtube-music.real"
LOG_FILE="${XDG_STATE_HOME:-$HOME/.local/state}/music-parallelism/music-parallelism.log"
log_message() {
local msg
msg="$(date '+%Y-%m-%d %H:%M:%S') - $1"
echo "$msg" >&2
echo "$msg" >>"$LOG_FILE" 2>/dev/null || true
}
# Focus apps - window titles to check (only visible windows count)
FOCUS_APPS_WINDOWS=(
"Visual Studio Code"
"VSCodium"
"Cursor"
"IntelliJ IDEA"
"PyCharm"
"WebStorm"
"CLion"
"Rider"
"Sublime Text"
"Blender"
"Godot"
"Unity"
"Unreal Editor"
)
# Focus apps - process patterns to check
FOCUS_APPS_PROCESSES=(
"steam_app_"
"gamescope"
)
# Check if any focus app is running (window-based detection)
is_focus_app_running() {
# Check windows first
if command -v xdotool &>/dev/null; then
for app in "${FOCUS_APPS_WINDOWS[@]}"; do
if xdotool search --name "$app" &>/dev/null 2>&1; then
echo "$app"
return 0
fi
done
fi
# Check specific processes
for app in "${FOCUS_APPS_PROCESSES[@]}"; do
if pgrep -f "$app" &>/dev/null; then
echo "$app"
return 0
fi
done
return 1
}
# Main
if focus_app=$(is_focus_app_running); then
log_message "BLOCKED: YouTube Music launch prevented (focus app: $focus_app)"
notify-send -u normal -t 3000 "🚫 YouTube Music Blocked" "Focus mode active ($focus_app)" 2>/dev/null || true
log_message "BLOCKED: YouTube Music launch prevented (focus app: $focus_app)" "$LOG_FILE"
notify "🚫 YouTube Music Blocked" "Focus mode active ($focus_app)" normal 3000
exit 1
fi

View File

@ -5,48 +5,23 @@
set -e # Exit on any error
# Default to non-interactive mode
INTERACTIVE_MODE=false
# Source common library for shared functions
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
# shellcheck source=../lib/common.sh
source "$SCRIPT_DIR/../lib/common.sh"
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
-i | --interactive)
INTERACTIVE_MODE=true
shift
;;
-h | --help)
echo "Usage: $0 [OPTIONS]"
echo "Options:"
echo " -i, --interactive Enable interactive prompts (default: auto-yes)"
echo " -h, --help Show this help message"
exit 0
;;
*)
echo "Unknown option: $1"
echo "Use -h or --help for usage information"
exit 1
;;
esac
done
# Parse interactive/help arguments
parse_interactive_args "$@"
shift "$COMMON_ARGS_SHIFT"
# Function to check and request sudo privileges
check_sudo() {
if [[ $EUID -ne 0 ]]; then
echo "This script requires sudo privileges to modify system files."
echo "Requesting sudo access..."
exec sudo "$0" "$@"
fi
}
# Check for sudo privileges after argument parsing
check_sudo "$@"
# Check for sudo privileges
require_root "$@"
echo "NVIDIA Comprehensive Troubleshooter & GSP Disabler"
echo "=================================================="
echo "Current Date: $(date)"
echo "User: $USER"
echo "Original user: ${SUDO_USER:-$USER}"
echo "Original user: $(get_actual_user)"
if [[ $INTERACTIVE_MODE == "true" ]]; then
echo "Mode: Interactive (prompts enabled)"
else
@ -68,7 +43,7 @@ echo "======================================"
mkdir -p "$MODPROBE_DIR"
# Create the configuration file
cat > "$CONFIG_FILE" << EOF
cat >"$CONFIG_FILE" <<EOF
# Disable NVIDIA GSP firmware to prevent Vulkan failures and crashes
# Created by nvidia_troubleshoot.sh on $(date)
options nvidia NVreg_EnableGpuFirmware=0
@ -103,7 +78,7 @@ configure_xorg() {
backup_file "$NVIDIA_CONF"
# Create NVIDIA-specific configuration
cat > "$NVIDIA_CONF" << EOF
cat >"$NVIDIA_CONF" <<EOF
# NVIDIA configuration with RenderAccel disabled
# Created by nvidia_troubleshoot.sh on $(date)
Section "Device"
@ -134,7 +109,7 @@ configure_gcc_workaround() {
printf '# NVIDIA GCC version mismatch workaround\n'
printf '# Added by nvidia_troubleshoot.sh on %s\n' "$timestamp"
printf 'export IGNORE_CC_MISMATCH=1\n'
} >> "$PROFILE_FILE"
} >>"$PROFILE_FILE"
echo "✓ Added IGNORE_CC_MISMATCH=1 to $PROFILE_FILE"
else
echo "✓ IGNORE_CC_MISMATCH already configured in $PROFILE_FILE"
@ -171,7 +146,7 @@ install_pyroveil() {
local missing_deps=()
for dep in git cmake ninja gcc; do
if ! command -v "$dep" &> /dev/null; then
if ! command -v "$dep" &>/dev/null; then
missing_deps+=("$dep")
fi
done
@ -212,7 +187,7 @@ install_pyroveil() {
echo "Available configs in: $pyroveil_dir/hacks/"
# Create a helper script
cat > "$user_home/run-with-pyroveil.sh" << EOF
cat >"$user_home/run-with-pyroveil.sh" <<EOF
#!/bin/bash
# Helper script to run games with Pyroveil
# Usage: ./run-with-pyroveil.sh <config-name> <command>
@ -278,7 +253,7 @@ suggest_kernel_params() {
# Check current CPU for micro-op cache relevance
echo ""
echo "CPU Information (for micro-op cache consideration):"
if command -v lscpu &> /dev/null; then
if command -v lscpu &>/dev/null; then
local cpu_info
cpu_info=$(lscpu | grep "Model name" | cut -d: -f2 | xargs)
echo "Current CPU: $cpu_info"
@ -331,10 +306,10 @@ install_pyroveil
echo ""
echo "7. Regenerating Initramfs..."
echo "============================"
if command -v mkinitcpio &> /dev/null; then
if command -v mkinitcpio &>/dev/null; then
mkinitcpio -P
echo "✓ Initramfs regenerated with mkinitcpio"
elif command -v dracut &> /dev/null; then
elif command -v dracut &>/dev/null; then
dracut --force
echo "✓ Initramfs regenerated with dracut"
else

241
scripts/lib/common.sh Normal file
View File

@ -0,0 +1,241 @@
#!/bin/bash
# Common library functions for linux-configuration scripts
# Source this file at the beginning of scripts that need shared functionality
#
# Usage: source "$(dirname "$(readlink -f "$0")")/../lib/common.sh"
# Or: source "/path/to/scripts/lib/common.sh"
# Prevent multiple sourcing
[[ -n ${_LIB_COMMON_LOADED:-} ]] && return 0
_LIB_COMMON_LOADED=1
# =============================================================================
# LOGGING FUNCTIONS
# =============================================================================
# Log message with timestamp to stderr and optionally to a file
# Usage: log_message "message" [log_file]
log_message() {
local msg="$1"
local log_file="${2:-}"
local formatted
formatted="$(date '+%Y-%m-%d %H:%M:%S') - $msg"
echo "$formatted" >&2
if [[ -n "$log_file" ]]; then
echo "$formatted" >>"$log_file" 2>/dev/null || true
fi
}
# Simple log with timestamp (no file output)
# Usage: log "message"
log() {
printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*"
}
# =============================================================================
# SUDO / ROOT HANDLING
# =============================================================================
# Check if running as root, if not re-exec with sudo
# Usage: require_root "$@"
require_root() {
if [[ $EUID -ne 0 ]]; then
echo "This script requires root privileges."
echo "Requesting sudo access..."
exec sudo "$0" "$@"
fi
}
# Get the actual user even when running with sudo
# Usage: ACTUAL_USER=$(get_actual_user)
get_actual_user() {
echo "${SUDO_USER:-$USER}"
}
# Get the actual user's home directory
# Usage: USER_HOME=$(get_actual_user_home)
get_actual_user_home() {
local user
user=$(get_actual_user)
if [[ "$user" == "root" ]]; then
echo "/root"
else
echo "/home/$user"
fi
}
# =============================================================================
# ARGUMENT PARSING HELPERS
# =============================================================================
# Parse common --interactive/-i and --help/-h flags
# Sets INTERACTIVE_MODE variable (exported for use by calling scripts)
# Usage: parse_common_args "$@"
# shift "$COMMON_ARGS_SHIFT"
export INTERACTIVE_MODE=false
export COMMON_ARGS_SHIFT=0
parse_interactive_args() {
INTERACTIVE_MODE=false
COMMON_ARGS_SHIFT=0
local script_name="${0##*/}"
while [[ $# -gt 0 ]]; do
case $1 in
-i | --interactive)
INTERACTIVE_MODE=true
((COMMON_ARGS_SHIFT++))
shift
;;
-h | --help)
echo "Usage: $script_name [OPTIONS]"
echo "Options:"
echo " -i, --interactive Enable interactive prompts (default: auto-yes)"
echo " -h, --help Show this help message"
exit 0
;;
*)
# Stop parsing at first unknown argument
break
;;
esac
done
}
# =============================================================================
# FOCUS APP DETECTION (for digital wellbeing scripts)
# =============================================================================
# Default focus apps - can be overridden before calling is_focus_app_running
FOCUS_APPS_WINDOWS=(
"Visual Studio Code"
"VSCodium"
"Cursor"
"IntelliJ IDEA"
"PyCharm"
"WebStorm"
"CLion"
"Rider"
"Sublime Text"
"Blender"
"Godot"
"Unity"
"Unreal Editor"
)
FOCUS_APPS_PROCESSES=(
"steam_app_"
"gamescope"
)
# Check if any focus app is running (window-based detection)
# Returns 0 if focus app found, 1 otherwise
# Echoes the name of the found app
is_focus_app_running() {
# Check windows first
if command -v xdotool &>/dev/null; then
local app
for app in "${FOCUS_APPS_WINDOWS[@]}"; do
if xdotool search --name "$app" &>/dev/null 2>&1; then
echo "$app"
return 0
fi
done
fi
# Check specific processes
local app
for app in "${FOCUS_APPS_PROCESSES[@]}"; do
if pgrep -f "$app" &>/dev/null; then
echo "$app"
return 0
fi
done
return 1
}
# =============================================================================
# COMMAND AVAILABILITY
# =============================================================================
# Check if a command exists
# Usage: if require_command ffmpeg; then ...
require_command() {
local cmd="$1"
local pkg="${2:-$1}"
if ! command -v "$cmd" >/dev/null 2>&1; then
echo "Error: '$cmd' is not installed or not in PATH." >&2
echo "Install with: sudo pacman -S $pkg" >&2
return 1
fi
return 0
}
# =============================================================================
# NOTIFICATION
# =============================================================================
# Send desktop notification (fails silently if notify-send not available)
# Usage: notify "Title" "Message" [urgency: low/normal/critical] [timeout_ms]
notify() {
local title="$1"
local message="$2"
local urgency="${3:-normal}"
local timeout="${4:-5000}"
if command -v notify-send &>/dev/null; then
notify-send -u "$urgency" -t "$timeout" "$title" "$message" 2>/dev/null || true
fi
}
# =============================================================================
# FILE/PATH UTILITIES
# =============================================================================
# Get the directory containing the calling script
# Usage: SCRIPT_DIR=$(get_script_dir)
get_script_dir() {
dirname "$(readlink -f "${BASH_SOURCE[1]:-$0}")"
}
# Ensure a directory exists
# Usage: ensure_dir "/path/to/dir"
ensure_dir() {
local dir="$1"
if [[ ! -d "$dir" ]]; then
mkdir -p "$dir"
fi
}
# =============================================================================
# SYSTEMD HELPERS
# =============================================================================
# Enable and start a systemd service (user or system)
# Usage: enable_service "service-name" [--user]
enable_service() {
local service="$1"
local user_flag="${2:-}"
if [[ "$user_flag" == "--user" ]]; then
systemctl --user daemon-reload
systemctl --user enable --now "$service"
else
systemctl daemon-reload
systemctl enable --now "$service"
fi
}
# Check if a systemd service is active
# Usage: if is_service_active "service-name" [--user]; then ...
is_service_active() {
local service="$1"
local user_flag="${2:-}"
if [[ "$user_flag" == "--user" ]]; then
systemctl --user is-active --quiet "$service"
else
systemctl is-active --quiet "$service"
fi
}

View File

@ -5,48 +5,23 @@
set -e # Exit on any error
# Default to non-interactive mode
INTERACTIVE_MODE=false
# Source common library for shared functions
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
# shellcheck source=lib/common.sh
source "$SCRIPT_DIR/lib/common.sh"
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
-i | --interactive)
INTERACTIVE_MODE=true
shift
;;
-h | --help)
echo "Usage: $0 [OPTIONS]"
echo "Options:"
echo " -i, --interactive Enable interactive prompts (default: auto-yes)"
echo " -h, --help Show this help message"
exit 0
;;
*)
echo "Unknown option: $1"
echo "Use -h or --help for usage information"
exit 1
;;
esac
done
# Parse interactive/help arguments
parse_interactive_args "$@"
shift "$COMMON_ARGS_SHIFT"
# Function to check and request sudo privileges
check_sudo() {
if [[ $EUID -ne 0 ]]; then
echo "This script requires sudo privileges to create systemd services and timers."
echo "Requesting sudo access..."
exec sudo "$0" "$@"
fi
}
# Check for sudo privileges after argument parsing
check_sudo "$@"
# Check for sudo privileges
require_root "$@"
echo "Periodic System Setup - Pacman Wrapper & Hosts File"
echo "==================================================="
echo "Current Date: $(date)"
echo "User: $USER"
echo "Original user: ${SUDO_USER:-$USER}"
echo "Original user: $(get_actual_user)"
if [[ $INTERACTIVE_MODE == "true" ]]; then
echo "Mode: Interactive (prompts enabled)"
else
@ -54,7 +29,6 @@ else
fi
# Get the directory where this script is located
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
CONFIG_DIR="$(dirname "$SCRIPT_DIR")"
# Define paths
@ -142,7 +116,7 @@ create_execution_script() {
sed \
-e "s|__PACMAN_WRAPPER_INSTALL__|$PACMAN_WRAPPER_INSTALL|g" \
-e "s|__HOSTS_INSTALL_SCRIPT__|$HOSTS_INSTALL_SCRIPT|g" \
"$TEMPLATE_MAINT_SCRIPT" > "$exec_script"
"$TEMPLATE_MAINT_SCRIPT" >"$exec_script"
chmod +x "$exec_script"
echo "✓ Installed execution script from template: $exec_script"
@ -192,7 +166,7 @@ create_hosts_monitor_service() {
# Install the monitor script from template with substitution
sed -e "s|__HOSTS_INSTALL_SCRIPT__|$HOSTS_INSTALL_SCRIPT|g" \
"$TEMPLATE_HOSTS_MONITOR" > "$monitor_script"
"$TEMPLATE_HOSTS_MONITOR" >"$monitor_script"
chmod +x "$monitor_script"
echo "✓ Installed hosts monitor script from template: $monitor_script"
@ -209,17 +183,17 @@ install_browser_preexec_wrapper() {
local wrapper="/usr/local/bin/browser-preexec-wrapper"
sed -e "s|__HOSTS_INSTALL_SCRIPT__|$HOSTS_INSTALL_SCRIPT|g" \
"$TEMPLATE_BROWSER_WRAPPER" > "$wrapper"
"$TEMPLATE_BROWSER_WRAPPER" >"$wrapper"
chmod +x "$wrapper"
echo "✓ Installed wrapper: $wrapper"
# Allow passwordless execution of hosts installer for root-only actions
local sudoers_file="/etc/sudoers.d/hosts-install-no-passwd"
if command -v visudo > /dev/null 2>&1; then
echo "${SUDO_USER:-$USER} ALL=(ALL) NOPASSWD: $HOSTS_INSTALL_SCRIPT" > "$sudoers_file"
if command -v visudo >/dev/null 2>&1; then
echo "${SUDO_USER:-$USER} ALL=(ALL) NOPASSWD: $HOSTS_INSTALL_SCRIPT" >"$sudoers_file"
chmod 440 "$sudoers_file"
# Validate syntax
visudo -c > /dev/null || echo "Warning: sudoers validation returned non-zero"
visudo -c >/dev/null || echo "Warning: sudoers validation returned non-zero"
echo "✓ Sudoers drop-in created: $sudoers_file"
else
echo "visudo not found; skipping sudoers drop-in"

View File

@ -4,48 +4,23 @@
set -e # Exit on any error
# Default to non-interactive mode
INTERACTIVE_MODE=false
# Source common library for shared functions
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
# shellcheck source=lib/common.sh
source "$SCRIPT_DIR/lib/common.sh"
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
-i | --interactive)
INTERACTIVE_MODE=true
shift
;;
-h | --help)
echo "Usage: $0 [OPTIONS]"
echo "Options:"
echo " -i, --interactive Enable interactive prompts (default: auto-yes)"
echo " -h, --help Show this help message"
exit 0
;;
*)
echo "Unknown option: $1"
echo "Use -h or --help for usage information"
exit 1
;;
esac
done
# Parse interactive/help arguments
parse_interactive_args "$@"
shift "$COMMON_ARGS_SHIFT"
# Function to check and request sudo privileges
check_sudo() {
if [[ $EUID -ne 0 ]]; then
echo "This script requires sudo privileges to create systemd services."
echo "Requesting sudo access..."
exec sudo "$0" "$@"
fi
}
# Check for sudo privileges after argument parsing
check_sudo "$@"
# Check for sudo privileges
require_root "$@"
echo "Thorium Browser Auto-Startup Setup"
echo "=================================="
echo "Current Date: $(date)"
echo "User: $USER"
echo "Original user: ${SUDO_USER:-$USER}"
echo "Original user: $(get_actual_user)"
if [[ $INTERACTIVE_MODE == "true" ]]; then
echo "Mode: Interactive (prompts enabled)"
else
@ -55,7 +30,7 @@ fi
# Target URL
TARGET_URL="https://www.fitatu.com/app/planner"
BROWSER_COMMAND="thorium-browser"
USER_HOME="/home/${SUDO_USER}"
USER_HOME="/home/$(get_actual_user)"
echo ""
echo "Target URL: $TARGET_URL"
@ -68,7 +43,7 @@ check_thorium_browser() {
echo "1. Checking Thorium Browser Installation..."
echo "=========================================="
if ! command -v "$BROWSER_COMMAND" &> /dev/null; then
if ! command -v "$BROWSER_COMMAND" &>/dev/null; then
echo "Warning: Thorium browser not found in PATH"
echo "Checking alternative locations..."
@ -129,7 +104,7 @@ create_launcher_script() {
local launcher_script="/usr/local/bin/thorium-fitatu-launcher.sh"
cat > "$launcher_script" << EOF
cat >"$launcher_script" <<EOF
#!/bin/bash
# Thorium browser launcher for Fitatu website
# Created by setup_thorium_startup.sh on $(date)
@ -220,7 +195,7 @@ create_user_systemd_service() {
sudo -u "${SUDO_USER}" mkdir -p "$user_systemd_dir"
# Create the service file
sudo -u "${SUDO_USER}" tee "$service_file" > /dev/null << EOF
sudo -u "${SUDO_USER}" tee "$service_file" >/dev/null <<EOF
[Unit]
Description=Launch Thorium Browser with Fitatu on Startup
After=graphical-session.target
@ -256,7 +231,7 @@ create_system_systemd_service() {
local service_file="/etc/systemd/system/thorium-fitatu-startup.service"
cat > "$service_file" << EOF
cat >"$service_file" <<EOF
[Unit]
Description=Launch Thorium Browser with Fitatu on Startup
After=multi-user.target network-online.target
@ -299,7 +274,7 @@ create_autostart_entry() {
sudo -u "${SUDO_USER}" mkdir -p "$autostart_dir"
# Create desktop entry
sudo -u "${SUDO_USER}" tee "$desktop_file" > /dev/null << EOF
sudo -u "${SUDO_USER}" tee "$desktop_file" >/dev/null <<EOF
[Desktop Entry]
Type=Application
Name=Thorium Fitatu Startup
@ -352,7 +327,7 @@ create_i3_autostart() {
create_user_enable_script() {
local enable_script="$USER_HOME/.config/thorium-enable-service.sh"
sudo -u "${SUDO_USER}" tee "$enable_script" > /dev/null << 'EOF'
sudo -u "${SUDO_USER}" tee "$enable_script" >/dev/null <<'EOF'
#!/bin/bash
# Script to enable thorium-fitatu-startup user service
# This runs once to enable the service, then removes itself

View File

@ -0,0 +1,238 @@
#!/usr/bin/env bash
set -euo pipefail
# convert_video.sh
#
# Convert video files to a target format (mp4 or webm) using ffmpeg.
# Accepts either a single video file or a directory (will recurse into subdirectories).
# Default settings
TARGET_FORMAT="mp4"
CRF="" # Will be set based on format if not specified
PRESET="medium"
DELETE_ORIGINAL=false
TARGET_PATH=""
# Video extensions to search for
ALL_VIDEO_EXTENSIONS=("mp4" "webm" "mkv" "avi" "mov" "wmv" "flv" "m4v" "mpg" "mpeg" "3gp" "ogv" "ts" "mts" "m2ts" "vob" "asf" "rm" "rmvb" "divx" "f4v")
log() {
printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*"
}
usage() {
cat <<EOF
Usage:
$(basename "$0") [OPTIONS] PATH
Convert video files to mp4 or webm format using ffmpeg.
PATH can be a single video file or a directory (will recurse into subdirectories).
Options:
-f FORMAT Target format: mp4 or webm (default: mp4)
-c CRF Quality level (default: 23 for mp4, 30 for webm; lower = better)
-p PRESET Encoding preset (default: medium)
Options: ultrafast, superfast, veryfast, faster, fast, medium, slow, slower, veryslow
-d Delete original file after successful conversion
-h Show this help
Examples:
$(basename "$0") video.webm # Convert to mp4
$(basename "$0") -f webm video.mp4 # Convert to webm
$(basename "$0") /path/to/videos/ # Convert all videos in directory to mp4
$(basename "$0") -f webm -c 25 -d /path/to/videos/
EOF
}
ensure_ffmpeg() {
if ! command -v ffmpeg >/dev/null 2>&1; then
echo "Error: 'ffmpeg' is not installed or not in PATH." >&2
exit 1
fi
}
get_video_extensions_except() {
local exclude="$1"
local exts=()
for ext in "${ALL_VIDEO_EXTENSIONS[@]}"; do
if [[ "${ext,,}" != "${exclude,,}" ]]; then
exts+=("$ext")
fi
done
echo "${exts[@]}"
}
is_video_file() {
local file="$1"
local ext="${file##*.}"
ext="${ext,,}" # lowercase
for video_ext in "${ALL_VIDEO_EXTENSIONS[@]}"; do
if [[ "$ext" == "${video_ext,,}" ]]; then
return 0
fi
done
return 1
}
convert_video() {
local input_file="$1"
local output_file="${input_file%.*}.${TARGET_FORMAT}"
# Skip if output already exists
if [[ -f "$output_file" ]]; then
log "Skipping '$input_file': output '$output_file' already exists"
return 0
fi
log "Converting '$input_file' -> '$output_file'"
local ffmpeg_args=()
ffmpeg_args+=(-hide_banner -loglevel warning -i "$input_file")
if [[ "$TARGET_FORMAT" == "mp4" ]]; then
# H.264 codec for video and AAC for audio (maximum compatibility)
ffmpeg_args+=(-c:v libx264 -crf "$CRF" -preset "$PRESET")
ffmpeg_args+=(-c:a aac -b:a 192k)
ffmpeg_args+=(-movflags +faststart)
elif [[ "$TARGET_FORMAT" == "webm" ]]; then
# VP9 codec for video and Opus for audio
ffmpeg_args+=(-c:v libvpx-vp9 -crf "$CRF" -b:v 0)
ffmpeg_args+=(-c:a libopus -b:a 128k)
fi
ffmpeg_args+=("$output_file")
if ffmpeg "${ffmpeg_args[@]}"; then
log "Successfully converted '$input_file'"
if [[ "$DELETE_ORIGINAL" == true ]]; then
log "Deleting original: '$input_file'"
rm "$input_file"
fi
else
log "Error converting '$input_file'"
[[ -f "$output_file" ]] && rm "$output_file"
return 1
fi
}
process_directory() {
local dir="$1"
local count=0
local failed=0
log "Searching for video files in '$dir'..."
# Build find command dynamically
local find_args=(-type f \()
local first=true
for ext in "${ALL_VIDEO_EXTENSIONS[@]}"; do
if [[ "${ext,,}" != "${TARGET_FORMAT,,}" ]]; then
if [[ "$first" == true ]]; then
first=false
else
find_args+=(-o)
fi
find_args+=(-iname "*.$ext")
fi
done
find_args+=(\) -print0)
while IFS= read -r -d '' file; do
((count++)) || true
if ! convert_video "$file"; then
((failed++)) || true
fi
done < <(find "$dir" "${find_args[@]}" 2>/dev/null)
log "Processed $count video file(s), $failed failed"
if [[ $count -eq 0 ]]; then
log "No video files found in '$dir'"
fi
}
parse_args() {
while getopts ":f:c:p:dh" opt; do
case "$opt" in
f)
TARGET_FORMAT="${OPTARG,,}"
if [[ "$TARGET_FORMAT" != "mp4" && "$TARGET_FORMAT" != "webm" ]]; then
echo "Error: Format must be 'mp4' or 'webm'" >&2
exit 1
fi
;;
c) CRF="$OPTARG" ;;
p) PRESET="$OPTARG" ;;
d) DELETE_ORIGINAL=true ;;
h)
usage
exit 0
;;
:)
echo "Error: Option -$OPTARG requires an argument." >&2
usage
exit 1
;;
\?)
echo "Error: Invalid option -$OPTARG" >&2
usage
exit 1
;;
esac
done
shift $((OPTIND - 1))
if [[ $# -lt 1 ]]; then
echo "Error: No path specified." >&2
usage
exit 1
fi
TARGET_PATH="$1"
# Set default CRF based on format if not specified
if [[ -z "$CRF" ]]; then
if [[ "$TARGET_FORMAT" == "mp4" ]]; then
CRF=23
else
CRF=30
fi
fi
}
main() {
ensure_ffmpeg
parse_args "$@"
if [[ ! -e "$TARGET_PATH" ]]; then
echo "Error: Path '$TARGET_PATH' does not exist." >&2
exit 1
fi
if [[ -f "$TARGET_PATH" ]]; then
# Single file
if [[ "${TARGET_PATH,,}" == *."$TARGET_FORMAT" ]]; then
log "File '$TARGET_PATH' is already in $TARGET_FORMAT format, skipping."
exit 0
fi
if is_video_file "$TARGET_PATH"; then
convert_video "$TARGET_PATH"
else
echo "Error: '$TARGET_PATH' is not a recognized video file." >&2
exit 1
fi
elif [[ -d "$TARGET_PATH" ]]; then
process_directory "$TARGET_PATH"
else
echo "Error: '$TARGET_PATH' is neither a file nor a directory." >&2
exit 1
fi
log "Done!"
}
main "$@"

View File

@ -1,117 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
# pdf_to_png.sh (magick-only backend, behaves like pdf_to_image)
#
# Convert one or more PDF files to image files using ImageMagick v7 `magick`.
# Default output format is jpg, but can be changed with -f.
OUTPUT_DIR=""
OUTPUT_FORMAT="jpg"
PDF_FILES=()
log() {
printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*"
}
usage() {
cat << EOF
Usage:
$(basename "$0") [OPTIONS] PDF_FILE [PDF_FILE...]
Convert one or more PDF files to images using ImageMagick 'magick'.
Options:
-o DIR Output directory (default: current directory)
-f FORMAT Output image format (default: jpg)
-h Show this help
Examples:
$(basename "$0") file.pdf
$(basename "$0") -f png file1.pdf file2.pdf
$(basename "$0") -o out -f webp file.pdf
EOF
}
ensure_magick() {
if ! command -v magick > /dev/null 2>&1; then
echo "Error: 'magick' (ImageMagick v7) is not installed or not in PATH." >&2
exit 1
fi
}
parse_args() {
local opt
OUTPUT_DIR=""
OUTPUT_FORMAT="jpg"
PDF_FILES=()
while getopts ":o:f:h" opt; do
case "$opt" in
o)
OUTPUT_DIR="$OPTARG"
;;
f)
OUTPUT_FORMAT="$OPTARG"
;;
h)
usage
exit 0
;;
*)
usage
exit 1
;;
esac
done
shift $((OPTIND - 1))
if [[ $# -lt 1 ]]; then
echo "Error: at least one PDF file must be specified." >&2
usage
exit 1
fi
PDF_FILES=("$@")
if [[ -z ${OUTPUT_DIR:-} ]]; then
OUTPUT_DIR="${PWD}"
fi
if [[ ! -d $OUTPUT_DIR ]]; then
mkdir -p "$OUTPUT_DIR"
fi
}
convert_pdf() {
local pdf_file="$1"
local base name out_pattern
name="$(basename "$pdf_file")"
base="${name%.*}"
out_pattern="${OUTPUT_DIR%/}/${base}_page-"
log "Converting '$pdf_file' to $OUTPUT_FORMAT using magick -> ${out_pattern}*.${OUTPUT_FORMAT}"
magick -density 300 "$pdf_file" -quality 90 "${out_pattern}%d.${OUTPUT_FORMAT}"
}
main() {
ensure_magick
parse_args "$@"
local pdf
for pdf in "${PDF_FILES[@]}"; do
if [[ ! -f $pdf ]]; then
echo "Warning: '$pdf' is not a regular file, skipping." >&2
continue
fi
convert_pdf "$pdf"
done
log "Done converting PDFs to ${OUTPUT_FORMAT}. Output directory: $OUTPUT_DIR"
}
main "$@"

View File

@ -2,34 +2,21 @@
set -euo pipefail
# Source common library
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
# shellcheck source=../lib/common.sh
source "$SCRIPT_DIR/../lib/common.sh"
# Re-run with sudo if needed for reading /etc/hosts
if [[ $EUID -ne 0 ]] && [[ ! -r /etc/hosts ]]; then
exec sudo -E bash "$0" "$@"
fi
WORK_DIR="${HOME}/.cache/android-adblock"
mkdir -p "$WORK_DIR"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m'
log() {
echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S%z')]${NC} $*"
}
error() {
echo -e "${RED}[ERROR]${NC} $*" >&2
}
warn() {
echo -e "${YELLOW}[WARN]${NC} $*"
}
ensure_dir "$WORK_DIR"
die() {
error "$@"
echo "[ERROR] $*" >&2
exit 1
}

View File

@ -1,194 +1,4 @@
#!/usr/bin/env bash
set -euo pipefail
# to_mp4.sh
#
# Convert video files (non-mp4) to mp4 format using ffmpeg.
# Accepts either a single video file or a directory (will recurse into subdirectories).
# Video extensions to search for (excluding mp4 since that's our target)
VIDEO_EXTENSIONS=("webm" "mkv" "avi" "mov" "wmv" "flv" "m4v" "mpg" "mpeg" "3gp" "ogv" "ts" "mts" "m2ts" "vob" "asf" "rm" "rmvb" "divx" "f4v")
# Conversion settings
CRF=23 # Quality (0-51, lower = better quality, 18-28 is reasonable for H.264)
PRESET="medium" # Encoding speed: ultrafast, superfast, veryfast, faster, fast, medium, slow, slower, veryslow
DELETE_ORIGINAL=false
log() {
printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*"
}
usage() {
cat <<EOF
Usage:
$(basename "$0") [OPTIONS] PATH
Convert video files to mp4 format using ffmpeg.
PATH can be a single video file or a directory (will recurse into subdirectories).
Options:
-c CRF Quality level 0-51 (default: 23, lower = better quality)
-p PRESET Encoding preset (default: medium)
Options: ultrafast, superfast, veryfast, faster, fast, medium, slow, slower, veryslow
-d Delete original file after successful conversion
-h Show this help
Examples:
$(basename "$0") video.webm
$(basename "$0") /path/to/videos/
$(basename "$0") -c 20 -d /path/to/videos/
$(basename "$0") -p slow -c 18 movie.mkv
EOF
}
ensure_ffmpeg() {
if ! command -v ffmpeg >/dev/null 2>&1; then
echo "Error: 'ffmpeg' is not installed or not in PATH." >&2
exit 1
fi
}
is_video_file() {
local file="$1"
local ext="${file##*.}"
ext="${ext,,}" # lowercase
for video_ext in "${VIDEO_EXTENSIONS[@]}"; do
if [[ "$ext" == "${video_ext,,}" ]]; then
return 0
fi
done
return 1
}
convert_to_mp4() {
local input_file="$1"
local output_file="${input_file%.*}.mp4"
# Skip if output already exists
if [[ -f "$output_file" ]]; then
log "Skipping '$input_file': output '$output_file' already exists"
return 0
fi
log "Converting '$input_file' -> '$output_file'"
# Use H.264 codec for video and AAC for audio (maximum compatibility)
if ffmpeg -hide_banner -loglevel warning -i "$input_file" \
-c:v libx264 -crf "$CRF" -preset "$PRESET" \
-c:a aac -b:a 192k \
-movflags +faststart \
"$output_file"; then
log "Successfully converted '$input_file'"
if [[ "$DELETE_ORIGINAL" == true ]]; then
log "Deleting original: '$input_file'"
rm "$input_file"
fi
else
log "Error converting '$input_file'"
# Remove partial output file if it exists
[[ -f "$output_file" ]] && rm "$output_file"
return 1
fi
}
process_directory() {
local dir="$1"
local count=0
local failed=0
log "Searching for video files in '$dir'..."
# Find all video files (case-insensitive)
while IFS= read -r -d '' file; do
# Skip mp4 files
if [[ "${file,,}" == *.mp4 ]]; then
continue
fi
((count++)) || true
if ! convert_to_mp4 "$file"; then
((failed++)) || true
fi
done < <(find "$dir" -type f \( -iname "*.webm" -o -iname "*.mkv" -o -iname "*.avi" -o -iname "*.mov" \
-o -iname "*.wmv" -o -iname "*.flv" -o -iname "*.m4v" -o -iname "*.mpg" -o -iname "*.mpeg" \
-o -iname "*.3gp" -o -iname "*.ogv" -o -iname "*.ts" -o -iname "*.mts" -o -iname "*.m2ts" \
-o -iname "*.vob" -o -iname "*.asf" -o -iname "*.rm" -o -iname "*.rmvb" -o -iname "*.divx" \
-o -iname "*.f4v" \) -print0 2>/dev/null)
log "Processed $count video file(s), $failed failed"
if [[ $count -eq 0 ]]; then
log "No video files found in '$dir'"
fi
}
parse_args() {
while getopts ":c:p:dh" opt; do
case "$opt" in
c) CRF="$OPTARG" ;;
p) PRESET="$OPTARG" ;;
d) DELETE_ORIGINAL=true ;;
h)
usage
exit 0
;;
:)
echo "Error: Option -$OPTARG requires an argument." >&2
usage
exit 1
;;
\?)
echo "Error: Invalid option -$OPTARG" >&2
usage
exit 1
;;
esac
done
shift $((OPTIND - 1))
if [[ $# -lt 1 ]]; then
echo "Error: No path specified." >&2
usage
exit 1
fi
TARGET_PATH="$1"
}
main() {
ensure_ffmpeg
parse_args "$@"
if [[ ! -e "$TARGET_PATH" ]]; then
echo "Error: Path '$TARGET_PATH' does not exist." >&2
exit 1
fi
if [[ -f "$TARGET_PATH" ]]; then
# Single file
if [[ "${TARGET_PATH,,}" == *.mp4 ]]; then
log "File '$TARGET_PATH' is already in mp4 format, skipping."
exit 0
fi
if is_video_file "$TARGET_PATH"; then
convert_to_mp4 "$TARGET_PATH"
else
echo "Error: '$TARGET_PATH' is not a recognized video file." >&2
exit 1
fi
elif [[ -d "$TARGET_PATH" ]]; then
# Directory
process_directory "$TARGET_PATH"
else
echo "Error: '$TARGET_PATH' is neither a file nor a directory." >&2
exit 1
fi
log "Done!"
}
main "$@"
# Wrapper for backward compatibility - converts to mp4
# See convert_video.sh for full options
exec "$(dirname "$(readlink -f "$0")")/convert_video.sh" -f mp4 "$@"

View File

@ -1,206 +1,4 @@
#!/usr/bin/env bash
set -euo pipefail
# to_webm.sh
#
# Convert video files (non-webm) to webm format using ffmpeg.
# Accepts either a single video file or a directory (will recurse into subdirectories).
# Video extensions to search for (excluding webm since that's our target)
VIDEO_EXTENSIONS=("mp4" "mkv" "avi" "mov" "wmv" "flv" "m4v" "mpg" "mpeg" "3gp" "ogv" "ts" "mts" "m2ts" "vob" "asf" "rm" "rmvb" "divx" "f4v")
# Conversion settings
CRF=30 # Quality (0-63, lower = better quality, 23-30 is reasonable)
PRESET="medium" # Encoding speed: ultrafast, superfast, veryfast, faster, fast, medium, slow, slower, veryslow
DELETE_ORIGINAL=false
log() {
printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*"
}
usage() {
cat <<EOF
Usage:
$(basename "$0") [OPTIONS] PATH
Convert video files to webm format using ffmpeg.
PATH can be a single video file or a directory (will recurse into subdirectories).
Options:
-c CRF Quality level 0-63 (default: 30, lower = better quality)
-p PRESET Encoding preset (default: medium)
Options: ultrafast, superfast, veryfast, faster, fast, medium, slow, slower, veryslow
-d Delete original file after successful conversion
-h Show this help
Examples:
$(basename "$0") video.mp4
$(basename "$0") /path/to/videos/
$(basename "$0") -c 25 -d /path/to/videos/
$(basename "$0") -p slow -c 20 movie.mkv
EOF
}
ensure_ffmpeg() {
if ! command -v ffmpeg >/dev/null 2>&1; then
echo "Error: 'ffmpeg' is not installed or not in PATH." >&2
exit 1
fi
}
build_extension_pattern() {
# Build a find pattern for video extensions
local pattern=""
for ext in "${VIDEO_EXTENSIONS[@]}"; do
if [[ -n "$pattern" ]]; then
pattern="$pattern -o"
fi
pattern="$pattern -iname *.$ext"
done
echo "$pattern"
}
is_video_file() {
local file="$1"
local ext="${file##*.}"
ext="${ext,,}" # lowercase
for video_ext in "${VIDEO_EXTENSIONS[@]}"; do
if [[ "$ext" == "${video_ext,,}" ]]; then
return 0
fi
done
return 1
}
convert_to_webm() {
local input_file="$1"
local output_file="${input_file%.*}.webm"
# Skip if output already exists
if [[ -f "$output_file" ]]; then
log "Skipping '$input_file': output '$output_file' already exists"
return 0
fi
log "Converting '$input_file' -> '$output_file'"
# Use VP9 codec for video and Opus for audio (good quality, wide compatibility)
if ffmpeg -hide_banner -loglevel warning -i "$input_file" \
-c:v libvpx-vp9 -crf "$CRF" -b:v 0 \
-c:a libopus -b:a 128k \
-preset "$PRESET" \
"$output_file"; then
log "Successfully converted '$input_file'"
if [[ "$DELETE_ORIGINAL" == true ]]; then
log "Deleting original: '$input_file'"
rm "$input_file"
fi
else
log "Error converting '$input_file'"
# Remove partial output file if it exists
[[ -f "$output_file" ]] && rm "$output_file"
return 1
fi
}
process_directory() {
local dir="$1"
local count=0
local failed=0
log "Searching for video files in '$dir'..."
# Find all video files (case-insensitive)
while IFS= read -r -d '' file; do
# Skip webm files
if [[ "${file,,}" == *.webm ]]; then
continue
fi
((count++)) || true
if ! convert_to_webm "$file"; then
((failed++)) || true
fi
done < <(find "$dir" -type f \( -iname "*.mp4" -o -iname "*.mkv" -o -iname "*.avi" -o -iname "*.mov" \
-o -iname "*.wmv" -o -iname "*.flv" -o -iname "*.m4v" -o -iname "*.mpg" -o -iname "*.mpeg" \
-o -iname "*.3gp" -o -iname "*.ogv" -o -iname "*.ts" -o -iname "*.mts" -o -iname "*.m2ts" \
-o -iname "*.vob" -o -iname "*.asf" -o -iname "*.rm" -o -iname "*.rmvb" -o -iname "*.divx" \
-o -iname "*.f4v" \) -print0 2>/dev/null)
log "Processed $count video file(s), $failed failed"
if [[ $count -eq 0 ]]; then
log "No video files found in '$dir'"
fi
}
parse_args() {
while getopts ":c:p:dh" opt; do
case "$opt" in
c) CRF="$OPTARG" ;;
p) PRESET="$OPTARG" ;;
d) DELETE_ORIGINAL=true ;;
h)
usage
exit 0
;;
:)
echo "Error: Option -$OPTARG requires an argument." >&2
usage
exit 1
;;
\?)
echo "Error: Invalid option -$OPTARG" >&2
usage
exit 1
;;
esac
done
shift $((OPTIND - 1))
if [[ $# -lt 1 ]]; then
echo "Error: No path specified." >&2
usage
exit 1
fi
TARGET_PATH="$1"
}
main() {
ensure_ffmpeg
parse_args "$@"
if [[ ! -e "$TARGET_PATH" ]]; then
echo "Error: Path '$TARGET_PATH' does not exist." >&2
exit 1
fi
if [[ -f "$TARGET_PATH" ]]; then
# Single file
if [[ "${TARGET_PATH,,}" == *.webm ]]; then
log "File '$TARGET_PATH' is already in webm format, skipping."
exit 0
fi
if is_video_file "$TARGET_PATH"; then
convert_to_webm "$TARGET_PATH"
else
echo "Error: '$TARGET_PATH' is not a recognized video file." >&2
exit 1
fi
elif [[ -d "$TARGET_PATH" ]]; then
# Directory
process_directory "$TARGET_PATH"
else
echo "Error: '$TARGET_PATH' is neither a file nor a directory." >&2
exit 1
fi
log "Done!"
}
main "$@"
# Wrapper for backward compatibility - converts to webm
# See convert_video.sh for full options
exec "$(dirname "$(readlink -f "$0")")/convert_video.sh" -f webm "$@"

View File

@ -2,29 +2,21 @@
set -euo pipefail
# Source common library
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
# shellcheck source=../lib/common.sh
source "$SCRIPT_DIR/../lib/common.sh"
# Re-run with sudo if needed for reading /etc/hosts
if [[ $EUID -ne 0 ]] && [[ ! -r /etc/hosts ]]; then
exec sudo -E bash "$0" "$@"
fi
WORK_DIR="${HOME}/.cache/android-adblock"
mkdir -p "$WORK_DIR"
# Color codes for output
RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m' # No Color
log() {
echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S%z')]${NC} $*"
}
error() {
echo -e "${RED}[ERROR]${NC} $*" >&2
}
ensure_dir "$WORK_DIR"
die() {
error "$@"
echo "[ERROR] $*" >&2
exit 1
}