mirror of
https://github.com/kuhyx/scripts.git
synced 2026-07-04 13:23:07 +02:00
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:
parent
4016cf8a34
commit
3e336d4958
@ -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'
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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=()
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
241
scripts/lib/common.sh
Normal file
241
scripts/lib/common.sh
Normal 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
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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"
|
||||
|
||||
238
scripts/utils/convert_video.sh
Normal file
238
scripts/utils/convert_video.sh
Normal 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 "$@"
|
||||
@ -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 "$@"
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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 "$@"
|
||||
|
||||
@ -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 "$@"
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user