This commit is contained in:
rodrigo 2026-06-25 11:06:15 -06:00
parent 86d2e8fc9f
commit b68896754b
317 changed files with 10682 additions and 5204 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

View File

@ -1,77 +1,350 @@
import asyncio import asyncio
import websockets
import json
from pydub import AudioSegment from pydub import AudioSegment
import os import os
import threading
import http.server
import socketserver
import socket
import time
import subprocess
import urllib.request # Para enviar HTTP GET a la ESP32 (sin librerías extra)
# Configuración de tu Bocina IP
IP_BOCINA = "192.168.1.142"
PUERTO_WS = 81
async def _enviar_audio_ws(pcm_data): # ================= CONFIGURACIÓN =================
url = f"ws://{IP_BOCINA}:{PUERTO_WS}" IP_BOCINA = "192.168.15.135"
chunk_size = 1024 PUERTO_ESP32 = 80
PUERTO_HTTP = 8000
CARPETA_AUDIO = "public_audio"
if not os.path.exists(CARPETA_AUDIO):
os.makedirs(CARPETA_AUDIO)
# ================= AUTO-DETECTAR MI IP =================
def obtener_mi_ip():
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
s.connect((IP_BOCINA, 80))
IP = s.getsockname()[0]
except Exception:
IP = '127.0.0.1'
finally:
s.close()
return IP
MI_IP = obtener_mi_ip()
print(f"[RED] IP auto-detectada para el servidor de audio: {MI_IP}")
def iniciar_servidor_http():
class Handler(http.server.SimpleHTTPRequestHandler):
def __init__(self, *args, **kwargs):
super().__init__(*args, directory=CARPETA_AUDIO, **kwargs)
def log_message(self, format, *args):
print(f"[SERVIDOR HTTP] Petición de ESP32: {args[0]}")
def handle(self):
try:
super().handle()
except (BrokenPipeError, ConnectionResetError):
pass
def copyfile(self, source, outputfile):
try:
super().copyfile(source, outputfile)
except (BrokenPipeError, ConnectionResetError):
pass
class ReusableTCPServer(socketserver.TCPServer):
allow_reuse_address = True
try: try:
async with websockets.connect(url, ping_timeout=None) as websocket: with ReusableTCPServer(("", PUERTO_HTTP), Handler) as httpd:
# Esperamos el mensaje de bienvenida print(f"Servidor de Audio activo en http://{MI_IP}:{PUERTO_HTTP}")
await websocket.recv() httpd.serve_forever()
chunks_enviados = 0
for i in range(0, len(pcm_data), chunk_size):
chunk = pcm_data[i:i + chunk_size]
await websocket.send(chunk)
chunks_enviados += 1
# ⚡ EL TRUCO MAESTRO: CONTROL DE FLUJO
if chunks_enviados % 10 == 0:
try:
# Python se pausa hasta que el ESP32 mande el "ACK"
await asyncio.wait_for(websocket.recv(), timeout=1.0)
except asyncio.TimeoutError:
pass # Si hay lag en la red, forzamos a seguir para no cortar
else:
await asyncio.sleep(0.002) # Micro-pausa entre paquetes individuales
# Le damos 1 segundo al ESP32 para reproducir los últimos datos en su memoria
await asyncio.sleep(1.0)
await websocket.send(json.dumps({"cmd": "STOP"}))
return True
except Exception as e: except Exception as e:
print(f" Error conectando a la bocina IP {IP_BOCINA}: {e}") print(f"ERROR CRÍTICO AL LEVANTAR EL SERVIDOR: {e}")
# ================= ORDEN A LA ESP32 =================
# La ESP32 usa HTTP GET en /play?url=..., NO WebSocket.
# Antes usábamos WebSocket al puerto 81 → la ESP32 nunca recibió nada.
def _mandar_orden_reproduccion(nombre_archivo):
url_mp3 = f"http://{MI_IP}:{PUERTO_HTTP}/{nombre_archivo}"
# Construimos la URL exacta que espera el handlePlay() de la ESP32
url_orden = f"http://{IP_BOCINA}:{PUERTO_ESP32}/play?url={url_mp3}"
try:
print(f"Ordenando a ESP32 reproducir: {url_mp3}")
with urllib.request.urlopen(url_orden, timeout=5) as respuesta:
estado = respuesta.read().decode()
print(f"ESP32 respondió: {estado}")
return True
except Exception as e:
print(f" Error enviando orden a la bocina: {e}")
return False return False
# ================= CONTROL DE VOLUMEN =================
def _mandar_orden_volumen(nivel=18):
"""Establece volumen directamente (0-21)"""
url_orden = f"http://{IP_BOCINA}:{PUERTO_ESP32}/volume?level={nivel}"
try:
with urllib.request.urlopen(url_orden, timeout=5) as respuesta:
print(f"Volumen: {respuesta.read().decode()}")
return True
except Exception as e:
print(f"Error cambiando volumen: {e}")
return False
def subir_volumen():
"""Sube 1 nivel de volumen"""
url_orden = f"http://{IP_BOCINA}:{PUERTO_ESP32}/volume_up"
try:
with urllib.request.urlopen(url_orden, timeout=5) as respuesta:
print(f" {respuesta.read().decode()}")
return True
except Exception as e:
print(f"Error: {e}")
return False
def bajar_volumen():
"""Baja 1 nivel de volumen"""
url_orden = f"http://{IP_BOCINA}:{PUERTO_ESP32}/volume_down"
try:
with urllib.request.urlopen(url_orden, timeout=5) as respuesta:
print(f"{respuesta.read().decode()}")
return True
except Exception as e:
print(f"Error: {e}")
return False
# ================= LÓGICA PRINCIPAL =================
def enviar_archivos_bocina(lista_archivos): def enviar_archivos_bocina(lista_archivos):
"""
Recibe una lista de rutas de archivos MP3, los une en un solo track,
los convierte al formato del ESP32 y los transmite.
"""
if not lista_archivos: if not lista_archivos:
return return
try: try:
# 1. Unimos los audios
audio_final = AudioSegment.empty() audio_final = AudioSegment.empty()
for archivo in lista_archivos: for archivo in lista_archivos:
if os.path.exists(archivo): if os.path.exists(archivo):
segmento = AudioSegment.from_file(archivo) audio_final += AudioSegment.from_file(archivo)
audio_final += segmento
audio_mono = audio_final.split_to_mono()[0] # 2. WAV temporal
ruta_temp = os.path.join(CARPETA_AUDIO, "temp.wav")
audio_final.export(ruta_temp, format="wav")
# ⚡ 2. SUBIMOS LA CALIDAD (A 22050 Hz) # 3. Nombre único anti-caché
audio_final = audio_mono.set_frame_rate(22050).set_sample_width(2) timestamp = int(time.time())
nombre_salida = f"saludo_{timestamp}.mp3"
ruta_salida = os.path.join(CARPETA_AUDIO, nombre_salida)
# 3. Quitamos el clipping (saturación). # 4. MP3 ligero para reducir la presión sobre el buffer de Audio.h
# Como ya no estamos sumando canales, podemos dejarle más volumen. Le bajaremos solo 2 dB. # El fix completo del OOM va en el .ino (ver abajo)
audio_final = audio_final - 2 comando_ffmpeg = [
"/usr/bin/ffmpeg", "-y",
"-i", ruta_temp,
"-codec:a", "libmp3lame",
"-b:a", "48k",
"-ar", "24000",
"-ac", "1",
# FILTRO ACÚSTICO: Corta todo lo que no sea voz humana
"-af", "highpass=f=150, lowpass=f=7500",
"-map_metadata", "-1",
"-id3v2_version", "0",
"-write_xing", "0",
ruta_salida
]
subprocess.run(comando_ffmpeg, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
# 4. Obtenemos los datos puros # 5. Limpieza del temporal
pcm_data = audio_final.raw_data if os.path.exists(ruta_temp):
os.remove(ruta_temp)
# ⚡ 4. TRANSMITIMOS A LA BOCINA (¡Te faltaba esta parte!) # 6. Enviamos la orden en un hilo separado
print(f" Transmitiendo audio a {IP_BOCINA}...") # (evita conflicto con el runtime de InsightFace/ONNX)
asyncio.run(_enviar_audio_ws(pcm_data)) def _lanzar_orden():
time.sleep(0.5) # Aseguramos que el archivo ya está en disco
_mandar_orden_reproduccion(nombre_salida)
# Limpiamos MP3 viejos DESPUÉS de que la ESP32 confirmó
for archivo in os.listdir(CARPETA_AUDIO):
if archivo.startswith("saludo_") and archivo != nombre_salida:
try:
os.remove(os.path.join(CARPETA_AUDIO, archivo))
except Exception:
pass
hilo = threading.Thread(target=_lanzar_orden, daemon=True)
hilo.start()
except Exception as e: except Exception as e:
print(f"Error procesando el audio para la bocina: {e}") print(f"Error procesando el audio: {e}")
# ==============================================================================
# AUTO-ARRANQUE DEL SERVIDOR (Hilo en segundo plano)
# ==============================================================================
servidor_thread = threading.Thread(target=iniciar_servidor_http, daemon=True)
servidor_thread.start()
"""
import asyncio
from pydub import AudioSegment
import os
import threading
import http.server
import socketserver
import socket
import time
import subprocess
import urllib.request # Para enviar HTTP GET a la ESP32 (sin librerías extra)
# ================= CONFIGURACIÓN =================
IP_BOCINA = "192.168.1.77" # La IP de la ESP32 que se definio como estatica
PUERTO_ESP32 = 80 # La ESP32 escucha HTTP en el puerto 80
PUERTO_HTTP = 8000
CARPETA_AUDIO = "public_audio"
if not os.path.exists(CARPETA_AUDIO):
os.makedirs(CARPETA_AUDIO)
# ================= SERVIDOR HTTP =================
def obtener_mi_ip():
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
s.connect(('10.255.255.255', 1))
IP = s.getsockname()[0]
except Exception:
IP = '127.0.0.1'
finally:
s.close()
return IP
MI_IP = obtener_mi_ip()
def iniciar_servidor_http():
class Handler(http.server.SimpleHTTPRequestHandler):
def __init__(self, *args, **kwargs):
super().__init__(*args, directory=CARPETA_AUDIO, **kwargs)
def log_message(self, format, *args):
print(f"[SERVIDOR HTTP] Petición de ESP32: {args[0]}")
def handle(self):
try:
super().handle()
except (BrokenPipeError, ConnectionResetError):
pass
def copyfile(self, source, outputfile):
try:
super().copyfile(source, outputfile)
except (BrokenPipeError, ConnectionResetError):
pass
class ReusableTCPServer(socketserver.TCPServer):
allow_reuse_address = True
try:
with ReusableTCPServer(("", PUERTO_HTTP), Handler) as httpd:
print(f"Servidor de Audio activo en http://{MI_IP}:{PUERTO_HTTP}")
httpd.serve_forever()
except Exception as e:
print(f"ERROR CRÍTICO AL LEVANTAR EL SERVIDOR: {e}")
# ================= ORDEN A LA ESP32 =================
# La ESP32 usa HTTP GET en /play?url=..., NO WebSocket.
# Antes usábamos WebSocket al puerto 81 → la ESP32 nunca recibió nada.
def _mandar_orden_reproduccion(nombre_archivo):
url_mp3 = f"http://{MI_IP}:{PUERTO_HTTP}/{nombre_archivo}"
# Construimos la URL exacta que espera el handlePlay() de la ESP32
url_orden = f"http://{IP_BOCINA}:{PUERTO_ESP32}/play?url={url_mp3}"
try:
print(f"Ordenando a ESP32 reproducir: {url_mp3}")
with urllib.request.urlopen(url_orden, timeout=5) as respuesta:
estado = respuesta.read().decode()
print(f"ESP32 respondió: {estado}")
return True
except Exception as e:
print(f" Error enviando orden a la bocina: {e}")
return False
# ================= LÓGICA PRINCIPAL =================
def enviar_archivos_bocina(lista_archivos):
if not lista_archivos:
return
try:
# 1. Unimos los audios
audio_final = AudioSegment.empty()
for archivo in lista_archivos:
if os.path.exists(archivo):
audio_final += AudioSegment.from_file(archivo)
# 2. WAV temporal
ruta_temp = os.path.join(CARPETA_AUDIO, "temp.wav")
audio_final.export(ruta_temp, format="wav")
# 3. Nombre único anti-caché
timestamp = int(time.time())
nombre_salida = f"saludo_{timestamp}.mp3"
ruta_salida = os.path.join(CARPETA_AUDIO, nombre_salida)
# 4. MP3 ligero para reducir la presión sobre el buffer de Audio.h
# El fix completo del OOM va en el .ino (ver abajo)
comando_ffmpeg = [
"/usr/bin/ffmpeg", "-y",
"-i", ruta_temp,
"-codec:a", "libmp3lame",
"-b:a", "48k",
"-ar", "24000",
"-ac", "1",
# FILTRO ACÚSTICO: Corta todo lo que no sea voz humana
"-af", "highpass=f=150, lowpass=f=7500",
"-map_metadata", "-1",
"-id3v2_version", "0",
"-write_xing", "0",
ruta_salida
]
subprocess.run(comando_ffmpeg, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
# 5. Limpieza del temporal
if os.path.exists(ruta_temp):
os.remove(ruta_temp)
# 6. Enviamos la orden en un hilo separado
# (evita conflicto con el runtime de InsightFace/ONNX)
def _lanzar_orden():
time.sleep(0.5) # Aseguramos que el archivo ya está en disco
_mandar_orden_reproduccion(nombre_salida)
# Limpiamos MP3 viejos DESPUÉS de que la ESP32 confirmó
for archivo in os.listdir(CARPETA_AUDIO):
if archivo.startswith("saludo_") and archivo != nombre_salida:
try:
os.remove(os.path.join(CARPETA_AUDIO, archivo))
except Exception:
pass
hilo = threading.Thread(target=_lanzar_orden, daemon=True)
hilo.start()
except Exception as e:
print(f"Error procesando el audio: {e}")
# ==============================================================================
# AUTO-ARRANQUE DEL SERVIDOR (Hilo en segundo plano)
# ==============================================================================
servidor_thread = threading.Thread(target=iniciar_servidor_http, daemon=True)
servidor_thread.start()
"""

Binary file not shown.

225
bocina_esp32.ino Normal file
View File

@ -0,0 +1,225 @@
#include <Arduino.h>
#include <WiFi.h>
#include <WebServer.h>
#include "Audio.h"
const char* ssid = "SmartEstancia";
const char* password = "Smart12345";
IPAddress local_IP(192, 168, 1, 77);
IPAddress gateway(192, 168, 1, 254);
IPAddress subnet(255, 255, 255, 0);
IPAddress primaryDNS(8, 8, 8, 8);
IPAddress secondaryDNS(8, 8, 4, 4);
#define I2S_BCLK 26
#define I2S_LRC 25
#define I2S_DOUT 33
Audio audio;
WebServer server(80);
void handlePlay() {
if (server.hasArg("url")) {
String url = server.arg("url");
Serial.print("🎵 Python ordenó reproducir: ");
Serial.println(url);
audio.stopSong();
audio.connecttohost(url.c_str());
server.send(200, "text/plain", "Reproduciendo OK");
} else {
server.send(400, "text/plain", "Error: Falta la URL");
}
}
// --- NUEVO: Control de Volumen ---
void handleVolume() {
if (server.hasArg("level")) {
int nivel = server.arg("level").toInt();
if (nivel >= 0 && nivel <= 21) {
audio.setVolume(nivel);
Serial.print("🔊 Volumen cambiado a: ");
Serial.println(nivel);
server.send(200, "text/plain", "Volumen OK: " + String(nivel));
} else {
server.send(400, "text/plain", "Error: Nivel debe ser 0-21");
}
} else {
server.send(400, "text/plain", "Error: Falta parametro 'level'");
}
}
void handleVolumeUp() {
int actual = audio.getVolume();
if (actual < 21) audio.setVolume(actual + 1);
server.send(200, "text/plain", "Volumen: " + String(audio.getVolume()));
}
void handleVolumeDown() {
int actual = audio.getVolume();
if (actual > 0) audio.setVolume(actual - 1);
server.send(200, "text/plain", "Volumen: " + String(audio.getVolume()));
}
void setup() {
Serial.begin(115200);
Serial.println("\n--- INICIANDO SISTEMA DE AUDIO ESP32 ---");
if (!WiFi.config(local_IP, gateway, subnet, primaryDNS, secondaryDNS)) {
Serial.println("Fallo al configurar la IP Estatica");
}
WiFi.begin(ssid, password);
Serial.print("Conectando a WiFi");
int intento = 0;
while (WiFi.status() != WL_CONNECTED && intento < 20) {
delay(500);
Serial.print(".");
intento++;
}
if (WiFi.status() != WL_CONNECTED) {
Serial.println("\nFallo de conexion.");
while(true){ delay(1000); }
}
Serial.println("\nWiFi Conectado!");
Serial.print("IP FIJA: ");
Serial.println(WiFi.localIP());
audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT);
audio.setVolume(15); // Volumen inicial medio
server.on("/play", HTTP_GET, handlePlay);
server.on("/volume", HTTP_GET, handleVolume);
server.on("/volume_up", HTTP_GET, handleVolumeUp);
server.on("/volume_down", HTTP_GET, handleVolumeDown);
server.begin();
Serial.println("Listo para recibir audio...");
}
void loop() {
audio.loop();
server.handleClient();
if (Serial.available()) {
String url = Serial.readStringUntil('\n');
url.trim();
if (url.length() > 5 && url.startsWith("http")) {
audio.stopSong();
audio.connecttohost(url.c_str());
}
}
}
void audio_info(const char *info){ Serial.print("INFO AUDIO: "); Serial.println(info); }
void audio_id3data(const char *info){ Serial.print("METADATOS: "); Serial.println(info); }
void audio_eof_mp3(const char *info){ Serial.print("FIN DEL AUDIO: "); Serial.println(info); }
"""
#include <Arduino.h>
#include <WiFi.h>
#include <WebServer.h>
#include "Audio.h"
// =======================================================
// 1. CONFIGURACIÓN DE RED
// =======================================================
const char* ssid = "SmartEstancia";
const char* password = "Smart12345";
// --- CONFIGURACIÓN DE IP ESTÁTICA ---
IPAddress local_IP(192, 168, 1, 77); // La IP que le asiganamos a la bocina
IPAddress gateway(192, 168, 1, 254); // la IP del router
IPAddress subnet(255, 255, 255, 0); // Máscara de subred
IPAddress primaryDNS(8, 8, 8, 8); // DNS primario (Google)
IPAddress secondaryDNS(8, 8, 4, 4); // DNS secundario (Google)
// =======================================================
// 2. CONFIGURACIÓN DE PINES CORRECTOS
// =======================================================
#define I2S_BCLK 26
#define I2S_LRC 25
#define I2S_DOUT 33
Audio audio;
WebServer server(80);
void handlePlay() {
if (server.hasArg("url")) {
String url = server.arg("url");
Serial.print("🎵 Python ordenó reproducir: ");
Serial.println(url);
audio.stopSong();
audio.connecttohost(url.c_str());
server.send(200, "text/plain", "Reproduciendo OK");
} else {
server.send(400, "text/plain", "Error: Falta la URL");
}
}
void setup() {
Serial.begin(115200);
Serial.println("\n--- INICIANDO SISTEMA DE AUDIO ESP32 ---");
if (psramFound()) {
Serial.println("PSRAM encontrada.");
} else {
Serial.println(" Sin PSRAM. Usando RAM interna.");
}
// ⚡ APLICAR IP ESTÁTICA ANTES DE CONECTAR
if (!WiFi.config(local_IP, gateway, subnet, primaryDNS, secondaryDNS)) {
Serial.println(" Fallo al configurar la IP Estática");
}
WiFi.begin(ssid, password);
Serial.print("Conectando a WiFi");
int intento = 0;
while (WiFi.status() != WL_CONNECTED && intento < 20) {
delay(500);
Serial.print(".");
intento++;
}
if (WiFi.status() != WL_CONNECTED) {
Serial.println("\n Fallo de conexión.");
while(true){ delay(1000); }
}
Serial.println("\n WiFi Conectado de verdad!");
Serial.print("Tu IP es FIJA: ");
Serial.println(WiFi.localIP());
audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT);
audio.setVolume(18);
server.on("/play", HTTP_GET, handlePlay);
server.begin();
Serial.println("Listo para recibir audio...");
}
void loop() {
audio.loop();
server.handleClient();
if (Serial.available()) {
String url = Serial.readStringUntil('\n');
url.trim();
if (url.length() > 5 && url.startsWith("http")) {
audio.stopSong();
audio.connecttohost(url.c_str());
}
}
}
void audio_info(const char *info){ Serial.print("INFO AUDIO: "); Serial.println(info); }
void audio_id3data(const char *info){ Serial.print("METADATOS: "); Serial.println(info); }
void audio_eof_mp3(const char *info){ Serial.print("FIN DEL AUDIO: "); Serial.println(info); }
"""

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Some files were not shown because too many files have changed in this diff Show More