> acoloreix.sh_

Mata el gris. Reviu la història.

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.

acoloreix.sh (Última versió del servidor)
#!/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