La màquina del temps en un script
Acoloreix.sh és una eina autònoma per a Linux que tradueix la Intel·ligència Artificial en un procés d'un sol clic. Sense dependre de memòria RAM massiva i evitant els errors de continuïtat, transforma vídeos històrics al color del segle XXI.
🛠️ Arquitectura Nadiua
Extreu fotogrames i àudio en qualitat RAW (WAV) evitant desincronitzacions de temps (errors PTS/DTS) i processa desenes de milers d'imatges mitjançant un flux continu de Python que esquiva els límits de memòria de Linux.
🧠 IA de DeOldify
Detecta automàticament la teva targeta gràfica. Si tens la potència necessària (8GB+ VRAM), desplega el model de vídeo avançat. En cas contrari, aplica el model artístic lleuger per garantir que funcioni a qualsevol ordinador.
🔄 Resiliència i Deflicker
Pots aturar el procés quan vulguis; el sistema de punts de control reprendrà la feina sense repetir el que ja està pintat. El muntatge final inclou filtres temporals per eliminar el parpelleig de la IA.
L'Script Definitiu
Guarda aquest codi com a acoloreix.sh, atorga-li permisos d'execució (chmod +x acoloreix.sh) i obre les portes al color.
#!/bin/bash
# ==========================================
# 0. MENÚ D'AJUDA I DESINSTAL·LACIÓ
# ==========================================
if [[ "$1" == "--help" || "$1" == "-h" ]]; then
echo "=========================================================="
echo " ACOLOREIX.SH - Restaurador i acoloridor de vídeo amb IA"
echo "=========================================================="
echo "Ús: $0 [ruta_al_video.mp4]"
echo ""
echo "Opcions:"
echo " -h, --help Mostra aquest menú d'ajuda."
echo " -u, --uninstall Desinstal·la l'entorn de la IA i allibera espai."
echo ""
echo "Intel·ligència Artificial Adaptativa:"
echo " L'script escaneja el teu maquinari. Si detecta una gràfica"
echo " NVIDIA amb >= 8GB de VRAM, instal·larà el model específic"
echo " per a vídeo. En cas contrari, usarà el model artístic lleuger."
exit 0
fi
if [[ "$1" == "--uninstall" || "$1" == "-u" ]]; then
echo "⚠️ Esborrant entorn d'IA..."
rm -rf "$HOME/.deoldify_local"
echo "Fet."
exit 0
fi
# ==========================================
# 0.1 CONTROL D'INTERRUPCIONS I FUNCIONS
# ==========================================
trap 'echo -e "\n[!] Procés interromput. Feina guardada a la carpeta temporal."; exit 1' INT TERM
comprovar_espai() {
local disponible_kb=$(df -k "$1" | awk 'NR==2 {print $4}')
local disponible_mb=$((disponible_kb / 1024))
if [ "$disponible_mb" -lt "$2" ]; then
echo "🚨 POC ESPAI DETECTAT! ($disponible_mb MB lliures, es preveuen $2 MB)"; read -p "Vols continuar igualment? (s/N): " r
[[ ! "$r" =~ ^[Ss] ]] && exit 1
fi
}
reorganitzar_carpetes() {
python3 -c "
import sys, os, shutil
dir_path = sys.argv[1]
if os.path.exists(dir_path):
files = [f for f in os.listdir(dir_path) if f.endswith('.png') and f.startswith('frame_')]
for f in files:
try:
num = int(''.join(filter(str.isdigit, f)))
sub = f'{num // 500:03d}'
os.makedirs(os.path.join(dir_path, sub), exist_ok=True)
shutil.move(os.path.join(dir_path, f), os.path.join(dir_path, sub, f))
except: pass
" "$1"
}
# ==========================================
# 1. DEPENDÈNCIES I ACTUALITZACIÓ
# ==========================================
echo "--- [1/7] Comprovant eines del sistema ---"
sudo apt-get update -qq
for p in zenity ffmpeg git python3-venv python3-pip wget gawk sed bc libnotify-bin; do
if ! dpkg-query -W -f='${Status}' "$p" 2>/dev/null | grep -q "ok installed"; then
echo ">> Instal·lant paquet que falta o està trencat: $p..."
sudo apt-get install -y "$p" || { echo "Error instal·lant $p. Revisa la teva connexió."; exit 1; }
fi
done
# ==========================================
# 2. SELECCIÓ I DETECCIÓ DE FEINA ANTERIOR
# ==========================================
echo -e "\n--- [2/7] Selecció del fitxer ---"
INTERACTIU=0
if [ -z "$1" ]; then
FITXER_ENTRADA=$(zenity --file-selection --title="Selecciona el vídeo" 2>/dev/null)
[ -z "$FITXER_ENTRADA" ] && exit 0
INTERACTIU=1
else
FITXER_ENTRADA="$1"
fi
[ ! -f "$FITXER_ENTRADA" ] && { echo "Error: Fitxer no trobat."; exit 1; }
DIR_BASE=$(dirname "$FITXER_ENTRADA")
NOM_BASE=$(basename "$FITXER_ENTRADA")
NOM_SENSE_EXT="${NOM_BASE%.*}"
CARPETA_TEMP="$DIR_BASE/temp_${NOM_SENSE_EXT}"
CARPETA_IN="$CARPETA_TEMP/frames_originals"
CARPETA_OUT="$CARPETA_TEMP/frames_color"
FITXER_SORTIDA="$DIR_BASE/${NOM_SENSE_EXT}-color.mp4"
if [ -d "$CARPETA_TEMP" ]; then
echo ">> Detectada feina anterior per a aquest vídeo."
if [ "$INTERACTIU" -eq 1 ]; then
if ! zenity --question --text="S'ha detectat una feina a mitges per:\n\n<b>$NOM_BASE</b>\n\nVols reprendre-la des d'on es va aturar?" --title="Reprendre?" 2>/dev/null; then
rm -rf "$CARPETA_TEMP"
fi
else
read -p "Vols reprendre la feina anterior per '$NOM_BASE'? (S/n): " resp
[[ "$resp" =~ ^[Nn] ]] && rm -rf "$CARPETA_TEMP"
fi
fi
mkdir -p "$CARPETA_IN" "$CARPETA_OUT"
# ==========================================
# 3. PREPARACIÓ D'IA I ESCÀNER DE MAQUINARI
# ==========================================
echo -e "\n--- [3/7] Verificant maquinari i motor d'IA ---"
USAR_VIDEO_MODEL=0
if command -v nvidia-smi &> /dev/null; then
VRAM_MB=$(nvidia-smi --query-gpu=memory.total --format=csv,noheader,nounits | head -n 1 | awk '{print int($1)}')
echo ">> Detectada GPU NVIDIA amb ${VRAM_MB} MB de VRAM."
if [ "$VRAM_MB" -ge 7500 ]; then
echo ">> Maquinari òptim! S'utilitzarà el model avançat per a Vídeo de DeOldify."
USAR_VIDEO_MODEL=1
else
echo ">> VRAM insuficient per al model de Vídeo (Recomanat: 8GB+). Es farà servir el model Artístic."
fi
else
echo ">> No s'ha detectat GPU NVIDIA. Es farà servir el model Artístic (més lleuger)."
fi
DIR_IA="$HOME/.deoldify_local"
if [ -d "$DIR_IA" ] && [ -z "$(ls -A "$DIR_IA" 2>/dev/null)" ]; then
echo ">> S'ha detectat una carpeta buida d'una descàrrega fallida anterior. Es netejarà ara..."
rm -rf "$DIR_IA"
fi
if [ ! -d "$DIR_IA" ]; then
echo ">> Descarregant codi de la Intel·ligència Artificial..."
git clone https://github.com/jantic/DeOldify.git "$DIR_IA" || {
echo -e "\n[!] ERROR CRÍTIC: No s'ha pogut descarregar DeOldify des de GitHub."
exit 1
}
fi
cd "$DIR_IA" || { echo "Error: No es pot accedir a $DIR_IA"; exit 1; }
[ ! -d "venv" ] && python3 -m venv venv
source venv/bin/activate
if ! python3 -c "import torch, yaml, requests, fastai" &>/dev/null; then
echo ">> Instal·lant o reparant llibreries de Python..."
pip install --upgrade pip setuptools wheel
pip install torch torchvision torchaudio tqdm PyYAML pandas matplotlib scipy Pillow requests
sed -i 's/==.*//g' requirements.txt
pip install -r requirements.txt
fi
if [ "$USAR_VIDEO_MODEL" -eq 1 ]; then
if [ ! -f "models/ColorizeVideo_gen.pth" ]; then
echo ">> Descarregant el model d'IA per a Vídeo..."
mkdir -p models
wget -q --show-progress https://data.deepai.org/deoldify/ColorizeVideo_gen.pth -O models/ColorizeVideo_gen.pth || exit 1
fi
else
if [ ! -f "models/ColorizeArtistic_gen.pth" ]; then
echo ">> Descarregant el model d'IA Artístic..."
mkdir -p models
wget -q --show-progress https://data.deepai.org/deoldify/ColorizeArtistic_gen.pth -O models/ColorizeArtistic_gen.pth || exit 1
fi
fi
cat << 'EOF' > processar_deoldify.py
import sys, os, torch, warnings
from tqdm import tqdm
from deoldify import device
from deoldify.device_id import DeviceId
warnings.filterwarnings("ignore")
_original_load = torch.load
torch.load = lambda *args, **kwargs: _original_load(*args, **{**kwargs, "weights_only": False}) if "weights_only" not in kwargs else _original_load(*args, **kwargs)
from deoldify.visualize import *
device.set(device=DeviceId.GPU0 if torch.cuda.is_available() else DeviceId.CPU)
input_dir, output_dir, checkpoint_file = sys.argv[1], sys.argv[2], sys.argv[3]
arxius = []
for root, _, files in os.walk(input_dir):
for f in files:
if f.endswith(".png"):
arxius.append(os.path.join(root, f))
arxius = sorted(arxius)
if len(arxius) == 0:
print("No hi ha frames pendents a processar.")
sys.exit(0)
# __INIT_COLORIZER__
for in_path in tqdm(arxius, desc="Pintant fotogrames", unit="frame"):
filename = os.path.basename(in_path)
try:
num = int("".join(filter(str.isdigit, filename)))
sub = f"{(num // 500):03d}"
except ValueError:
sub = "000"
out_path = os.path.join(output_dir, sub, filename)
os.makedirs(os.path.dirname(out_path), exist_ok=True)
colorizer.get_transformed_image(in_path, render_factor=21, watermarked=False).save(out_path)
os.remove(in_path)
with open(checkpoint_file, "w") as f: f.write("fet")
EOF
if [ "$USAR_VIDEO_MODEL" -eq 1 ]; then
sed -i 's/# __INIT_COLORIZER__/colorizer = get_video_colorizer().vis/g' processar_deoldify.py
else
sed -i 's/# __INIT_COLORIZER__/colorizer = get_image_colorizer(artistic=True)/g' processar_deoldify.py
fi
deactivate
# ==========================================
# 4. EXTRACCIÓ I CONTROL DE CONTINUÏTAT
# ==========================================
FPS=$(ffprobe -v error -select_streams v:0 -show_entries stream=r_frame_rate -of default=noprint_wrappers=1:nokey=1 "$FITXER_ENTRADA" | bc -l)
DURADA=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$FITXER_ENTRADA")
TOTAL_FRAMES=$(echo "$DURADA * $FPS" | bc | awk '{print int($1+0.5)}')
echo -e "\n--- [4/7] Extracció d'àudio i fotogrames ---"
if [ -f "$CARPETA_TEMP/audio.aac" ]; then
echo ">> Detectada extracció directa d'àudio antiga (.aac). S'esborrarà per forçar la nova extracció neta (.wav)..."
rm -f "$CARPETA_TEMP/audio.aac"
fi
[ ! -f "$CARPETA_TEMP/audio.wav" ] && ffmpeg -y -i "$FITXER_ENTRADA" -vn -c:a pcm_s16le "$CARPETA_TEMP/audio.wav" -loglevel error
reorganitzar_carpetes "$CARPETA_IN"
reorganitzar_carpetes "$CARPETA_OUT"
FRAMES_TOTALS_EXTRETS=$(find "$CARPETA_IN" "$CARPETA_OUT" -type f -name "*.png" 2>/dev/null | wc -l)
if [ -f "$CARPETA_TEMP/.frames_extrets" ] && [ "$FRAMES_TOTALS_EXTRETS" -eq 0 ]; then
echo ">> [AUTO-REPARACIÓ] Constava com a extret però no hi ha frames. Reiniciant extracció..."
rm -f "$CARPETA_TEMP/.frames_extrets"
fi
if [ -f "$CARPETA_TEMP/.frames_extrets" ]; then
echo ">> L'extracció de fotogrames ja estava completada."
else
MAX=$(find "$CARPETA_TEMP" -type f -name "frame_*.png" 2>/dev/null | sed -n 's/.*frame_\([0-9]*\)\.png/\1/p' | sort -n | tail -1)
MAX=$((10#${MAX:-0}))
if [ "$MAX" -gt 0 ] && [ "$MAX" -lt "$TOTAL_FRAMES" ]; then
SEGON_INICI=$(awk -v frames="$MAX" -v fps="$FPS" 'BEGIN { printf "%.3f", frames/fps }')
echo ">> Reprenent extracció des del fotograma $((MAX+1))..."
ffmpeg -y -ss "$SEGON_INICI" -i "$FITXER_ENTRADA" -start_number $((MAX+1)) "$CARPETA_IN/frame_%06d.png" -hide_banner -stats || exit 1
elif [ "$MAX" -eq 0 ]; then
comprovar_espai "$DIR_BASE" "$(echo "$TOTAL_FRAMES * 2.5 + 100" | bc | awk '{print int($1+0.5)}')" "Processament de vídeo"
echo ">> Iniciant extracció des de zero..."
ffmpeg -y -i "$FITXER_ENTRADA" "$CARPETA_IN/frame_%06d.png" -hide_banner -stats || exit 1
fi
reorganitzar_carpetes "$CARPETA_IN"
touch "$CARPETA_TEMP/.frames_extrets"
fi
# ==========================================
# 5. ACOLORIMENT INTEL·LIGENT (Omitible)
# ==========================================
echo -e "\n--- [5/7] Acoloriment amb IA ---"
FRAMES_PENDENTS=$(find "$CARPETA_IN" -type f -name "*.png" 2>/dev/null | wc -l)
FRAMES_FETS=$(find "$CARPETA_OUT" -type f -name "*.png" 2>/dev/null | wc -l)
if [ -f "$CARPETA_TEMP/.color_completat" ] && [ "$FRAMES_FETS" -eq 0 ]; then
echo ">> [AUTO-REPARACIÓ] Constava com acolorida però no hi ha frames de color. Reiniciant IA..."
rm -f "$CARPETA_TEMP/.color_completat"
fi
if [ -f "$CARPETA_TEMP/.color_completat" ]; then
echo ">> El procés d'acoloriment ja s'havia completat prèviament. Saltant pas."
else
if [ "$FRAMES_PENDENTS" -eq 0 ] && [ "$FRAMES_FETS" -gt 0 ] && [ -f "$CARPETA_TEMP/.frames_extrets" ]; then
echo ">> Tots els $FRAMES_FETS fotogrames ja estan pintats!"
touch "$CARPETA_TEMP/.color_completat"
else
if [ "$FRAMES_FETS" -gt 0 ]; then
echo ">> Reprenent acoloriment: $FRAMES_FETS fets, $FRAMES_PENDENTS pendents."
else
echo ">> Iniciant acoloriment de $FRAMES_PENDENTS fotogrames..."
fi
cd "$DIR_IA" && ./venv/bin/python3 processar_deoldify.py "$CARPETA_IN" "$CARPETA_OUT" "$CARPETA_TEMP/.color_completat"
[ $? -ne 0 ] && { echo "[!] Error en la IA."; exit 1; }
fi
fi
# ==========================================
# 6. MUNTATGE FINAL AMB CANONADA MILLORADA
# ==========================================
echo -e "\n--- [6/7] Muntatge final i control de continuïtat ---"
if [ "$FRAMES_FETS" -eq 0 ]; then
echo "[!] Error Crític: No s'ha trobat cap fotograma per muntar."
exit 1
fi
FILTRE="-vf deflicker=m=am:s=5,eq=saturation=1.2:contrast=1.05,hqdn3d=0:0:10:15"
echo ">> Inicant enviament continu d'imatges a FFmpeg (Bypass de memòria RAM i filtres de corrupció)..."
# Aquí utilitzem exactament l'arquitectura anterior (pipe:0) però canviant l'xargs cat
# per un script pur de Python que assegura que el flux no es talli mai i salta els frames de 0 bytes.
set -o pipefail
if [ -f "$CARPETA_TEMP/audio.wav" ]; then
find "$CARPETA_OUT" -type f -name "*.png" -print0 | sort -z -V | python3 -c '
import sys, os
for f in sys.stdin.read().split("\0"):
if f and os.path.getsize(f) > 0:
with open(f, "rb") as img:
sys.stdout.buffer.write(img.read())
' | ffmpeg -y -framerate "$FPS" -f image2pipe -i pipe:0 -i "$CARPETA_TEMP/audio.wav" $FILTRE -c:v libx264 -pix_fmt yuv420p -c:a aac -b:a 192k -shortest "$FITXER_SORTIDA" -stats
else
find "$CARPETA_OUT" -type f -name "*.png" -print0 | sort -z -V | python3 -c '
import sys, os
for f in sys.stdin.read().split("\0"):
if f and os.path.getsize(f) > 0:
with open(f, "rb") as img:
sys.stdout.buffer.write(img.read())
' | ffmpeg -y -framerate "$FPS" -f image2pipe -i pipe:0 $FILTRE -c:v libx264 -pix_fmt yuv420p "$FITXER_SORTIDA" -stats
fi
ESTAT=$?
set +o pipefail
# ==========================================
# 7. NETEJA
# ==========================================
if [ $ESTAT -eq 0 ]; then
echo -e "\n--- [7/7] Procés finalitzat amb èxit ---"
if [ "$INTERACTIU" -eq 1 ]; then
zenity --question --text="Vídeo completat!\n\nVols esborrar els fitxers temporals per alliberar espai?" --title="Neteja" 2>/dev/null && rm -rf "$CARPETA_TEMP"
else
read -p "Vols esborrar els fitxers temporals? (S/n): " neteja
[[ -z "$neteja" || "$neteja" =~ ^[Ss] ]] && rm -rf "$CARPETA_TEMP"
fi
echo "Vídeo desat a: $FITXER_SORTIDA"
notify-send "Fet!" "Vídeo restaurat correctament."
else
echo "Error durant el muntatge final."
exit 1
fi