mirror of
https://github.com/jstrdev/ps5directo.git
synced 2026-10-28 14:01:16 +01:00
1.21.0: versiones para Linux y Mac
This commit is contained in:
1 parent
d5de6399e0
commit
be8a217875
13 files changed
+713
-78
No files matched your search
@@ -0,0 +1 @@
|
||||
blank_issues_enabled: true
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
name: Mac (versión en pruebas / testing)
|
||||
about: Cuéntanos cómo va PS5 Directo en tu Mac / Tell us how PS5 Directo works on your Mac
|
||||
title: "[Mac] "
|
||||
labels: mac
|
||||
---
|
||||
|
||||
**Mac y macOS / Mac and macOS version:** (por ejemplo / e.g. MacBook Air M2, macOS 15.1)
|
||||
|
||||
**¿Abre? / Does it open?** (sí / no / con avisos — yes / no / with warnings)
|
||||
|
||||
**¿Qué funciona y qué falla? / What works and what fails?**
|
||||
|
||||
|
||||
**Informe de fallo / Problem report:** en el programa pulsa «Crear informe de fallo» y arrastra aquí el .zip / in the program click "Create problem report" and drag the .zip here.
|
||||
|
||||
Si no llega a abrirse, adjunta si puedes / If it doesn't open, please attach if you can:
|
||||
`~/Library/Application Support/PS5 Directo/lanzador.log` y / and `~/.ps5directo/ps5directo.log`
|
||||
@@ -3,6 +3,8 @@
|
||||
# - PS5Directo-X.Y.Z-Setup.exe el instalador: doble clic, «Instalar» y listo. Es la descarga normal.
|
||||
# - PS5Directo-X.Y.Z-Windows.zip lo mismo sin instalar (con Python incluido). Tambien lo usa
|
||||
# «Actualizar» dentro del programa, que solo copia los archivos.
|
||||
# - PS5Directo-X.Y.Z-Linux.tar.gz Linux x86-64, con Python y 7-Zip incluidos (se abre con ps5directo.sh).
|
||||
# - PS5Directo-X.Y.Z-macOS.dmg Mac (Apple Silicon e Intel), en pruebas: la app con Python y 7-Zip.
|
||||
name: Publicar version
|
||||
|
||||
on:
|
||||
@@ -47,6 +49,35 @@ jobs:
|
||||
- name: Zip para Windows
|
||||
run: (cd dist && zip -qr "PS5Directo-${{ steps.v.outputs.ver }}-Windows.zip" PS5Directo)
|
||||
|
||||
# Linux (x86-64): la app con Python y 7-Zip incluidos. Se abre con ps5directo.sh y la primera
|
||||
# vez se añade sola al menu de aplicaciones.
|
||||
- name: Paquete para Linux
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -e
|
||||
L=build/linux/PS5Directo
|
||||
mkdir -p "$L"
|
||||
git archive HEAD | tar -x -C "$L"
|
||||
rm -rf "$L/.github" "$L/installer" "$L"/*.bat "$L"/*.vbs
|
||||
chmod +x "$L/ps5directo.sh"
|
||||
PBS=20250604; PYV=3.12.11
|
||||
curl -fsSL -o pyl.tgz "https://github.com/astral-sh/python-build-standalone/releases/download/$PBS/cpython-$PYV+$PBS-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz"
|
||||
tar -xzf pyl.tgz -C "$L"
|
||||
P="$L/python/lib/python3.12"
|
||||
rm -rf "$P/test" "$P/idlelib" "$P/ensurepip" "$P/lib2to3" "$P/pydoc_data" "$P/turtledemo" \
|
||||
"$P"/site-packages/pip* "$P/tkinter/test" "$L/python/share" "$L/python/include"
|
||||
find "$L/python" -name __pycache__ -prune -exec rm -rf {} +
|
||||
"$L/python/bin/python3" -c "import ssl, sqlite3, ftplib, tkinter, lzma, bz2, ctypes; print('python ok')"
|
||||
url=$(gh api repos/ip7z/7zip/releases/latest --jq '.assets[] | select(.name | test("linux-x64\\.tar\\.xz$")) | .browser_download_url' | head -1)
|
||||
mkdir -p "$L/bin" sz
|
||||
curl -fsSL -o sz.tar.xz "$url"
|
||||
tar -xJf sz.tar.xz -C sz
|
||||
cp sz/7zz "$L/bin/7zz"
|
||||
chmod +x "$L/bin/7zz"
|
||||
(cd build/linux && tar -czf "../../dist/PS5Directo-${{ steps.v.outputs.ver }}-Linux.tar.gz" PS5Directo)
|
||||
ls -la dist
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: dist
|
||||
@@ -73,6 +104,102 @@ jobs:
|
||||
if ($LASTEXITCODE -ne 0) { exit 1 }
|
||||
Get-ChildItem dist\*.exe, dist\*.zip | Format-Table Name, Length
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: setup
|
||||
path: dist/PS5Directo-*-Setup.exe
|
||||
retention-days: 3
|
||||
|
||||
# Mac: "PS5 Directo.app" con Python para Apple Silicon e Intel y 7-Zip, dentro de un .dmg.
|
||||
# El lanzador (installer/macos/launcher.sh) copia el programa a ~/Library/Application Support
|
||||
# para que «Actualizar» funcione. Firmada solo "ad hoc" (sin cuenta de desarrollador de Apple).
|
||||
mac:
|
||||
needs: build
|
||||
runs-on: macos-latest
|
||||
env:
|
||||
VER: ${{ needs.build.outputs.ver }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Montar la app
|
||||
run: |
|
||||
set -e
|
||||
A="build/PS5 Directo.app"; C="$A/Contents"; R="$C/Resources"
|
||||
mkdir -p "$C/MacOS" "$R/app" "$R/bin"
|
||||
git archive HEAD | tar -x -C "$R/app"
|
||||
rm -rf "$R/app/.github" "$R/app/installer" "$R/app"/*.bat "$R/app"/*.vbs "$R/app/ps5directo.sh"
|
||||
cp installer/macos/launcher.sh "$C/MacOS/PS5 Directo"
|
||||
chmod +x "$C/MacOS/PS5 Directo"
|
||||
sed "s/__VERSION__/$VER/g" installer/macos/Info.plist > "$C/Info.plist"
|
||||
plutil -lint "$C/Info.plist"
|
||||
# icono
|
||||
mkdir -p ps5.iconset
|
||||
for s in 16 32 128 256 512; do
|
||||
sips -z $s $s web/icon-512.png --out "ps5.iconset/icon_${s}x${s}.png" >/dev/null
|
||||
d=$((s*2)); sips -z $d $d web/icon-512.png --out "ps5.iconset/icon_${s}x${s}@2x.png" >/dev/null
|
||||
done
|
||||
iconutil -c icns ps5.iconset -o "$R/ps5directo.icns"
|
||||
# Python para cada procesador
|
||||
PBS=20250604; PYV=3.12.11
|
||||
for pair in aarch64:arm64 x86_64:x86_64; do
|
||||
src=${pair%%:*}; dst=${pair##*:}
|
||||
curl -fsSL -o py.tgz "https://github.com/astral-sh/python-build-standalone/releases/download/$PBS/cpython-$PYV+$PBS-$src-apple-darwin-install_only_stripped.tar.gz"
|
||||
rm -rf python && tar -xzf py.tgz
|
||||
P=python/lib/python3.12
|
||||
rm -rf "$P/test" "$P/idlelib" "$P/ensurepip" "$P/lib2to3" "$P/pydoc_data" "$P/turtledemo" \
|
||||
"$P"/site-packages/pip* "$P/tkinter/test" python/share python/include
|
||||
find python -name __pycache__ -prune -exec rm -rf {} +
|
||||
mv python "$R/python-$dst"
|
||||
done
|
||||
"$R/python-$(uname -m)/bin/python3" -c "import ssl, sqlite3, ftplib, lzma, bz2, ctypes; print('python ok')"
|
||||
# 7-Zip
|
||||
url=$(gh api repos/ip7z/7zip/releases/latest --jq '.assets[] | select(.name | test("mac\\.tar\\.xz$")) | .browser_download_url' | head -1)
|
||||
mkdir -p sz && curl -fsSL -o sz.tar.xz "$url" && tar -xJf sz.tar.xz -C sz
|
||||
cp sz/7zz "$R/bin/7zz" && chmod +x "$R/bin/7zz"
|
||||
"$R/bin/7zz" | head -2
|
||||
codesign --force --deep --sign - "$A"
|
||||
codesign --verify --deep --verbose=1 "$A"
|
||||
|
||||
- name: Prueba de arranque en un Mac
|
||||
run: |
|
||||
export HOME="$RUNNER_TEMP/casa"; mkdir -p "$HOME"
|
||||
"build/PS5 Directo.app/Contents/MacOS/PS5 Directo" --no-browser &
|
||||
ok=""
|
||||
for i in $(seq 1 30); do
|
||||
sleep 1
|
||||
if grep -q "PS5 Directo $VER" "$HOME/.ps5directo/ps5directo.log" 2>/dev/null; then ok=1; break; fi
|
||||
done
|
||||
cat "$HOME/.ps5directo/ps5directo.log" || true
|
||||
cat "$HOME/Library/Application Support/PS5 Directo/lanzador.log" || true
|
||||
pkill -f "PS5 Directo/app/app.py" || true
|
||||
test -n "$ok"
|
||||
|
||||
- name: Crear el .dmg
|
||||
run: |
|
||||
set -e
|
||||
mkdir -p dmg dist
|
||||
cp -R "build/PS5 Directo.app" dmg/
|
||||
ln -s /Applications dmg/Applications
|
||||
cp installer/macos/LEEME-Mac.txt "dmg/Si no abre - LEEME.txt"
|
||||
hdiutil create -volname "PS5 Directo" -srcfolder dmg -ov -format UDZO "dist/PS5Directo-$VER-macOS.dmg"
|
||||
ls -la dist
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: mac
|
||||
path: dist/PS5Directo-*-macOS.dmg
|
||||
retention-days: 3
|
||||
|
||||
release:
|
||||
needs: [build, setup, mac]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: dist
|
||||
merge-multiple: true
|
||||
|
||||
# Con una etiqueta crea la release. Pulsando "Run workflow" en Actions añade (o rehace) los archivos
|
||||
# de la release de la version que pone app.py, aunque ya exista.
|
||||
- uses: softprops/action-gh-release@v2
|
||||
@@ -81,3 +208,5 @@ jobs:
|
||||
files: |
|
||||
dist/PS5Directo-*-Setup.exe
|
||||
dist/PS5Directo-*-Windows.zip
|
||||
dist/PS5Directo-*-Linux.tar.gz
|
||||
dist/PS5Directo-*-macOS.dmg
|
||||
@@ -305,8 +305,38 @@ QUÉ DESCARGAR
|
||||
prefiera descomprimirlo en una carpeta. También lo usa "Actualizar".
|
||||
Para quitarlo: Configuración de Windows > Aplicaciones > PS5 Directo >
|
||||
Desinstalar. Tus ajustes se conservan por si vuelves a instalarlo.
|
||||
- PS5Directo-X.Y.Z-macOS.dmg: para Mac (Apple Silicon e Intel, macOS 11
|
||||
o posterior). EN PRUEBAS: todavía no se ha probado en un Mac de verdad.
|
||||
Ábrelo y arrastra PS5 Directo a Aplicaciones. La primera vez macOS la
|
||||
bloquea porque no está firmada por Apple: en macOS 15 o posterior,
|
||||
Ajustes del Sistema > Privacidad y seguridad > "Abrir igualmente"; en
|
||||
anteriores, clic derecho en la app > Abrir. Si algo falla, "Crear
|
||||
informe de fallo" y adjúntalo en github.com/jstrdev/ps5directo/issues.
|
||||
- PS5Directo-X.Y.Z-Linux.tar.gz: para Linux (x86-64, distribuciones
|
||||
recientes como Ubuntu 22.04 o posterior). Descomprímelo donde quieras
|
||||
y ábrelo una vez con ./ps5directo.sh en una terminal (o clic derecho
|
||||
> Ejecutar como programa). Esa vez se añade al menú de aplicaciones y
|
||||
desde entonces lo abres desde ahí. Trae Python y 7-Zip.
|
||||
Para la ventana sin pestañas usa Chrome, Chromium, Edge o Brave si los
|
||||
tienes (si no, se abre en tu navegador). Para los torrents hace falta
|
||||
aria2: sudo apt install aria2. En Linux no hay "Enviar a" ni el aviso
|
||||
del cortafuegos de Windows.
|
||||
|
||||
|
||||
NOVEDADES 1.21.0
|
||||
- Versión para Mac EN PRUEBAS (PS5Directo-1.21.0-macOS.dmg), para Apple
|
||||
Silicon e Intel, con Python y 7-Zip incluidos. Aún no se ha probado en
|
||||
un Mac de verdad: se agradece cualquier aviso en GitHub.
|
||||
- El informe de fallo incluye ahora el registro técnico del programa
|
||||
(sin el código de acceso de la sesión).
|
||||
- Arreglado: si cerrabas el programa en la Biblioteca, al abrirlo salía
|
||||
marcada «Transferencias» con la Biblioteca vacía debajo.
|
||||
- Versión para Linux (PS5Directo-1.21.0-Linux.tar.gz), con Python y 7-Zip
|
||||
incluidos. Se añade sola al menú de aplicaciones y se actualiza igual
|
||||
que en Windows.
|
||||
- Las descargas al PC van a tu carpeta de Descargas real (en Linux puede
|
||||
llamarse "Descargas").
|
||||
|
||||
NOVEDADES 1.20.4
|
||||
- Icono propio en la barra de tareas: la ventana ya no se mete dentro del
|
||||
icono de Edge o Chrome, y al anclarla se ancla PS5 Directo.
|
||||
@@ -314,6 +344,14 @@ NOVEDADES 1.20.4
|
||||
tenías en vez de abrir otra.
|
||||
- Si lo anclaste antes de esta versión, desáncla el icono viejo y ancla
|
||||
el nuevo (clic derecho en el icono de la barra > Anclar).
|
||||
- Capturas: salen también las hechas con la pantalla en HDR (.jxr), que se
|
||||
ven convertidas a JPG (al bajarlas al PC se guarda el original). Cada una
|
||||
dice de qué aplicación es y la fecha, los vídeos llevan la portada de la
|
||||
aplicación y ya no salen las copias temporales que hace la consola al
|
||||
compartir.
|
||||
- Arreglado: en «Archivos de la consola», calcular el tamaño de una
|
||||
carpeta, borrarla, copiarla, arreglar sus permisos y «Reemplazar» una
|
||||
carpeta que ya existía fallaban desde la 1.20.3.
|
||||
|
||||
NOVEDADES 1.20.3
|
||||
- Instalador para Windows (PS5Directo-1.20.3-Setup.exe): doble clic,
|
||||
|
||||
@@ -32,6 +32,8 @@ An all-in-one Windows tool for jailbroken PS5 consoles: it **downloads, converts
|
||||
## Requirements
|
||||
|
||||
- **PC:** Windows 10/11. Nothing else to install: the download includes everything it needs. Optional: [7-Zip](https://www.7-zip.org/), only if you want to extract on the PC instead of on the console.
|
||||
- **Mac (new, in testing — not tested on a real Mac yet, [feedback welcome](../../issues/new?template=mac.md)):** macOS 11 or later, Apple Silicon or Intel. Python and 7-Zip are included. Torrents need `aria2` (`brew install aria2`).
|
||||
- **Linux (new, x86-64):** any recent distro (glibc 2.35+, e.g. Ubuntu 22.04+). Python and 7-Zip are included. For the app window without tabs, Chrome, Chromium, Edge or Brave (otherwise it opens in your default browser). Torrents need `aria2` (`sudo apt install aria2`).
|
||||
- **Jailbroken PS5:**
|
||||
- An FTP server, for example [ftpsrv](https://github.com/ps5-payload-dev/ftpsrv) on port 2121.
|
||||
- [BFpilot](https://github.com/ItsBlurf/BFpilot) on port 5905.
|
||||
@@ -48,13 +50,17 @@ An all-in-one Windows tool for jailbroken PS5 consoles: it **downloads, converts
|
||||
>
|
||||
> Rather not install? Download `PS5Directo-X.Y.Z-Windows.zip` instead, extract it anywhere and run `Crear accesos directos.bat`. Both update themselves the same way.
|
||||
|
||||
**Mac (in testing):** download `PS5Directo-X.Y.Z-macOS.dmg`, open it and drag **PS5 Directo** into Applications. The app isn't signed by Apple, so the first time macOS blocks it: on macOS 15+ go to *System Settings → Privacy & Security → Open Anyway*; on older versions right-click the app → *Open*. **It hasn't been tested on a real Mac yet:** if something fails, click *Create problem report* in the program and attach it to an [issue](../../issues/new?template=mac.md). Thanks!
|
||||
|
||||
**Linux:** download `PS5Directo-X.Y.Z-Linux.tar.gz`, extract it anywhere (for example in your home folder) and open it once with `./ps5directo.sh` in a terminal (or right-click → *Run as a Program*). That first time it adds **PS5 Directo** to your applications menu, so from then on you open it from there. It updates itself like on Windows.
|
||||
|
||||
> **About BFpilot and permissions:** up to at least v0.4.4, BFpilot's archive extractor leaves files without the execute bit, so the PS5 refuses to launch them (CE-107750-0). This also happens when you extract from BFpilot's own web UI. PS5 Directo fixes it automatically after every extraction. For content you already have, use **Settings → Fix permissions**.
|
||||
|
||||
The full guide is in [README_EN.txt](README_EN.txt) (Spanish: [LEEME.txt](LEEME.txt)).
|
||||
|
||||
## Publishing a release
|
||||
|
||||
Bump `VERSION` in `app.py`, add the notes to `LEEME.txt` / `README_EN.txt`, then push a tag (`git tag v1.20.3 && git push --tags`). The GitHub Action builds the installer `PS5Directo-X.Y.Z-Setup.exe` and `PS5Directo-X.Y.Z-Windows.zip` (both with Python included) and creates the release. The installer script is `installer/PS5Directo.iss` (Inno Setup).
|
||||
Bump `VERSION` in `app.py`, add the notes to `LEEME.txt` / `README_EN.txt`, then push a tag (`git tag v1.20.3 && git push --tags`). The GitHub Action builds the installer `PS5Directo-X.Y.Z-Setup.exe`, `PS5Directo-X.Y.Z-Windows.zip`, `PS5Directo-X.Y.Z-Linux.tar.gz` and `PS5Directo-X.Y.Z-macOS.dmg` (all with Python included; the Mac app is built and smoke-tested on a GitHub macOS runner) and creates the release. The installer script is `installer/PS5Directo.iss` (Inno Setup).
|
||||
|
||||
## Disclaimer
|
||||
|
||||
@@ -88,4 +94,8 @@ Herramienta todo en uno para Windows y PS5 con jailbreak: **descarga, convierte
|
||||
|
||||
Si Windows dice «Windows protegió su PC», pulsa **Más información → Ejecutar de todos modos** (el instalador no está firmado, que cuesta dinero; es el aviso normal en programas pequeños y gratuitos). ¿Prefieres no instalar? Descarga el `.zip`, descomprímelo y ejecuta `Crear accesos directos.bat`. La guía completa está en [LEEME.txt](LEEME.txt).
|
||||
|
||||
**Mac (nuevo, en pruebas):** descarga `PS5Directo-X.Y.Z-macOS.dmg`, ábrelo y arrastra **PS5 Directo** a Aplicaciones. No está firmada por Apple, así que la primera vez macOS la bloquea: en macOS 15 o posterior ve a *Ajustes del Sistema → Privacidad y seguridad → Abrir igualmente*; en versiones anteriores, clic derecho en la app → *Abrir*. **Todavía no se ha podido probar en un Mac de verdad:** si algo falla, pulsa *Crear informe de fallo* en el programa y adjúntalo en un [aviso](../../issues/new?template=mac.md). ¡Gracias!
|
||||
|
||||
**Linux (nuevo):** descarga `PS5Directo-X.Y.Z-Linux.tar.gz`, descomprímelo donde quieras y ábrelo una vez con `./ps5directo.sh` en una terminal (o clic derecho → *Ejecutar como programa*). Esa primera vez se añade solo al menú de aplicaciones, y desde entonces lo abres desde ahí. Trae Python y 7-Zip; para los torrents hace falta `aria2` (`sudo apt install aria2`).
|
||||
|
||||
**Aviso:** proyecto independiente, sin relación con Sony ni con los autores de las herramientas citadas. No incluye contenido de terceros, claves ni archivos de Sony, ni enlaces a sitios de descargas. Úsalo solo con contenido que tengas derecho a usar (copias de seguridad propias y homebrew). No hace el jailbreak ni salta ninguna protección: solo copia archivos a una consola que ya has modificado tú. No se da ayuda para conseguir contenido sin licencia. Se ofrece tal cual, sin garantías, y lo que hagas con él es responsabilidad tuya.
|
||||
@@ -319,15 +319,50 @@ WHAT TO DOWNLOAD
|
||||
rather extract it to a folder. "Update" uses it too.
|
||||
To remove it: Windows Settings > Apps > PS5 Directo > Uninstall. Your
|
||||
settings are kept in case you install it again.
|
||||
- PS5Directo-X.Y.Z-macOS.dmg: for Mac (Apple Silicon and Intel, macOS 11
|
||||
or later). IN TESTING: not tested on a real Mac yet. Open it and drag
|
||||
PS5 Directo into Applications. The first time macOS blocks it because
|
||||
it isn't signed by Apple: on macOS 15 or later, System Settings >
|
||||
Privacy & Security > "Open Anyway"; on older ones, right-click the app
|
||||
> Open. If something fails, "Create problem report" and attach it at
|
||||
github.com/jstrdev/ps5directo/issues.
|
||||
- PS5Directo-X.Y.Z-Linux.tar.gz: for Linux (x86-64, recent distros such
|
||||
as Ubuntu 22.04 or later). Extract it anywhere and open it once with
|
||||
./ps5directo.sh in a terminal (or right-click > Run as a Program).
|
||||
That first time it adds itself to the applications menu, so from then
|
||||
on you open it from there. Python and 7-Zip are included.
|
||||
For the window without tabs it uses Chrome, Chromium, Edge or Brave if
|
||||
you have one (otherwise your browser). Torrents need aria2:
|
||||
sudo apt install aria2. Linux has no "Send to" and no Windows firewall
|
||||
prompt.
|
||||
|
||||
WHAT'S NEW
|
||||
----------
|
||||
1.21.0 - Mac version IN TESTING (PS5Directo-1.21.0-macOS.dmg), for Apple
|
||||
Silicon and Intel, with Python and 7-Zip included. Not tested on
|
||||
a real Mac yet: any report on GitHub is welcome.
|
||||
- The problem report now includes the program's technical log
|
||||
(without the session access code).
|
||||
- Fixed: if you closed the program on the Library tab, on reopening
|
||||
it showed "Transfers" selected with an empty Library below.
|
||||
- Linux version (PS5Directo-1.21.0-Linux.tar.gz), with Python and
|
||||
7-Zip included. It adds itself to the applications menu and
|
||||
updates itself like on Windows.
|
||||
- Downloads to the PC go to your real Downloads folder (on Linux it
|
||||
may have another name).
|
||||
|
||||
1.20.4 - Its own taskbar icon: the window no longer hides inside the Edge
|
||||
or Chrome icon, and pinning it pins PS5 Directo.
|
||||
- If it's already open and you open it again, it brings the existing
|
||||
window to the front instead of opening another one.
|
||||
- If you pinned it before this version, unpin the old icon and pin
|
||||
the new one (right-click the taskbar icon > Pin).
|
||||
- Captures: screenshots taken with HDR on (.jxr) now show up,
|
||||
previewed as JPG (downloading keeps the original). Each one shows
|
||||
the app and the date, videos show the app's cover, and the
|
||||
temporary copies the console makes when sharing are hidden.
|
||||
- Fixed: in "Console files", folder size, deleting, copying, fixing
|
||||
permissions and "Replace" on an existing folder failed since 1.20.3.
|
||||
|
||||
1.20.3 - Windows installer (PS5Directo-1.20.3-Setup.exe): double-click,
|
||||
"Install", done, everything included. The .zip is still there if
|
||||
|
||||
@@ -24,7 +24,7 @@ sys.path.insert(0, HERE)
|
||||
import engine as E # noqa: E402
|
||||
import features as F # noqa: E402
|
||||
|
||||
VERSION = "1.20.4"
|
||||
VERSION = "1.21.0"
|
||||
# Repositorio de GitHub donde se publican las versiones (usuario/nombre). Si lo rellenas,
|
||||
# todas las copias buscan actualizaciones ahi sin tener que configurar nada.
|
||||
DEFAULT_REPO = "jstrdev/ps5directo"
|
||||
@@ -1238,7 +1238,7 @@ def fm_extract(src, folder, password="", exists="keepboth"):
|
||||
def fm_download(paths):
|
||||
cfg = cfg_snapshot()
|
||||
paths = [fm_clean(x) for x in paths]
|
||||
root_local = os.path.join(os.path.expanduser("~"), "Downloads", "PS5 Directo")
|
||||
root_local = os.path.join(downloads_dir(), "PS5 Directo")
|
||||
|
||||
def walk(rpath):
|
||||
"""[(ruta remota, ruta relativa, tamano)] de un archivo o carpeta."""
|
||||
@@ -1535,8 +1535,7 @@ def fm_api(p, b):
|
||||
return {"path": fp, "name": os.path.basename(fp)}
|
||||
if p == "/api/report/open":
|
||||
os.makedirs(report_dir(), exist_ok=True)
|
||||
if os.name == "nt":
|
||||
os.startfile(report_dir())
|
||||
_open_quiet(report_dir())
|
||||
return {"dir": report_dir()}
|
||||
if p == "/api/lib/check":
|
||||
return launch_check(str(b.get("path") or ""), int(b.get("minutes") or 30))
|
||||
@@ -1597,14 +1596,16 @@ def fm_api(p, b):
|
||||
if p == "/api/bk/restore":
|
||||
if BK["busy"]:
|
||||
raise E.PipeError("Espera a que termine la copia en marcha")
|
||||
threading.Thread(target=backup_restore, args=(str(b.get("path") or ""), b.get("ids") or []),
|
||||
daemon=True).start()
|
||||
bpath = os.path.abspath(str(b.get("path") or ""))
|
||||
if not bpath.startswith(os.path.abspath(backup_dir(cfg_snapshot()))) or not os.path.isfile(bpath):
|
||||
raise E.PipeError("Esa copia ya no está en la carpeta de copias (¿la moviste o borraste?). "
|
||||
"Pulsa Actualizar en la lista de copias.")
|
||||
threading.Thread(target=backup_restore, args=(bpath, b.get("ids") or []), daemon=True).start()
|
||||
return {}
|
||||
if p == "/api/bk/open":
|
||||
d = backup_dir()
|
||||
os.makedirs(d, exist_ok=True)
|
||||
if os.name == "nt":
|
||||
os.startfile(d)
|
||||
_open_quiet(d)
|
||||
return {"dir": d}
|
||||
if p == "/api/gu/status":
|
||||
return gu_status()
|
||||
@@ -1660,9 +1661,9 @@ def fm_api(p, b):
|
||||
FM_CANCEL.set()
|
||||
return {}
|
||||
if p == "/api/fm/open_local":
|
||||
path = FMOP.get("local") or os.path.join(os.path.expanduser("~"), "Downloads", "PS5 Directo")
|
||||
if os.name == "nt" and os.path.isdir(path):
|
||||
os.startfile(path)
|
||||
path = FMOP.get("local") or os.path.join(downloads_dir(), "PS5 Directo")
|
||||
if os.path.isdir(path):
|
||||
_open_quiet(path)
|
||||
return {"path": path}
|
||||
if p == "/api/pick":
|
||||
return {"paths": pick_paths(b.get("kind") or "files", b.get("title") or "")}
|
||||
@@ -1707,14 +1708,49 @@ def fm_api(p, b):
|
||||
raise E.PipeError("no existe")
|
||||
|
||||
|
||||
_DL_DIR = []
|
||||
|
||||
|
||||
def downloads_dir():
|
||||
"""Carpeta de Descargas del usuario. En Linux puede llamarse "Descargas" (xdg-user-dir)."""
|
||||
if _DL_DIR:
|
||||
return _DL_DIR[0]
|
||||
d = os.path.join(os.path.expanduser("~"), "Downloads")
|
||||
if sys.platform.startswith("linux"):
|
||||
try:
|
||||
import subprocess
|
||||
r = subprocess.run(["xdg-user-dir", "DOWNLOAD"], capture_output=True, timeout=5)
|
||||
x = r.stdout.decode("utf-8", "replace").strip()
|
||||
if r.returncode == 0 and x and x != os.path.expanduser("~") and os.path.isdir(x):
|
||||
d = x
|
||||
except Exception:
|
||||
pass
|
||||
_DL_DIR.append(d)
|
||||
return d
|
||||
|
||||
|
||||
def _open_quiet(path):
|
||||
try:
|
||||
F.open_path(path)
|
||||
except Exception as e:
|
||||
print("No pude abrir %s: %s" % (path, e))
|
||||
|
||||
|
||||
def pick_paths(kind, title):
|
||||
"""Abre el dialogo de Windows para elegir archivos o una carpeta del PC."""
|
||||
"""Abre el dialogo del sistema para elegir archivos o una carpeta del PC."""
|
||||
import subprocess
|
||||
if os.name == "nt":
|
||||
try:
|
||||
return F.pick_windows(kind, title)
|
||||
except Exception as e:
|
||||
print("Selector de PowerShell:", e)
|
||||
else:
|
||||
try:
|
||||
r = F.pick_native(kind, title)
|
||||
if r is not None:
|
||||
return r
|
||||
except Exception as e:
|
||||
print("Selector del sistema:", e)
|
||||
exe = sys.executable
|
||||
if os.name == "nt" and exe.lower().endswith("pythonw.exe"):
|
||||
alt = exe[:-len("pythonw.exe")] + "python.exe"
|
||||
@@ -1848,6 +1884,7 @@ COVER_DIR = os.path.join(DATA_DIR, "covers")
|
||||
MEDIA_DIR = os.path.join(DATA_DIR, "media")
|
||||
MEDIA_ROOT = "/user/av_contents"
|
||||
MEDIA_TYPES = {".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".png": "image/png", ".webp": "image/webp",
|
||||
".jxr": "image/vnd.ms-photo", # capturas con la pantalla en HDR
|
||||
".mp4": "video/mp4", ".webm": "video/webm", ".mov": "video/quicktime"}
|
||||
|
||||
|
||||
@@ -1952,7 +1989,7 @@ def _job_name(j):
|
||||
|
||||
|
||||
def report_dir():
|
||||
return os.path.join(os.path.expanduser("~"), "Downloads", "PS5 Directo", "Informes")
|
||||
return os.path.join(downloads_dir(), "PS5 Directo", "Informes")
|
||||
|
||||
|
||||
def make_report(job_id=None):
|
||||
@@ -1997,6 +2034,13 @@ def make_report(job_id=None):
|
||||
z.writestr("actividad.json", json.dumps(read_history()[-60:], ensure_ascii=False, indent=1))
|
||||
with NOTIF_LOCK:
|
||||
z.writestr("avisos.json", json.dumps(NOTIFS[:60], ensure_ascii=False, indent=1))
|
||||
try: # registro tecnico del programa (sin el codigo de acceso de la sesion)
|
||||
with open(os.path.join(DATA_DIR, "ps5directo.log"), encoding="utf-8", errors="replace") as fh:
|
||||
lines = fh.readlines()[-400:]
|
||||
z.writestr("registro_del_programa.txt",
|
||||
"".join(re.sub(r"([#?&]t=)[\w-]+", r"\1(oculto)", ln) for ln in lines))
|
||||
except OSError:
|
||||
pass
|
||||
z.writestr("LEEME.txt", "Informe de PS5 Directo %s (%s)\n\n%s\n\nNo lleva contraseñas. Pásaselo a quien te "
|
||||
"ayude.\n" % (VERSION, info["date"], "\n".join(summary)))
|
||||
return fp
|
||||
@@ -2281,23 +2325,64 @@ def col_icon(path):
|
||||
|
||||
|
||||
def media_list():
|
||||
"""Capturas y videos de la consola (los mas nuevos primero)."""
|
||||
"""Capturas y videos de la consola (los mas nuevos primero). La PS5 los guarda en
|
||||
/user/av_contents/{photo,video}/NPXS40087/<Title ID>/<carpeta>/<fecha>.jpg|jxr|webm."""
|
||||
cfg = cfg_snapshot()
|
||||
out = []
|
||||
files = E.Ftp(cfg).call(lambda f: _ftp_walk(f, MEDIA_ROOT, [], threading.Event()))
|
||||
files = E.Ftp(cfg).call(lambda f: _ftp_walk_sizes(f, MEDIA_ROOT, [], threading.Event()))
|
||||
titles = lib_load().get("titles") or {}
|
||||
for p, sz in files:
|
||||
ext = posixpath.splitext(p)[1].lower()
|
||||
if ext not in MEDIA_TYPES or "/thumbnail" in p.lower():
|
||||
low = p.lower()
|
||||
# miniaturas y copias temporales que hace la consola al compartir
|
||||
if ext not in MEDIA_TYPES or "/thumbnail" in low or "/content_tmp/" in low:
|
||||
continue
|
||||
out.append({"path": p, "name": posixpath.basename(p), "size": sz,
|
||||
"video": MEDIA_TYPES[ext].startswith("video"),
|
||||
"folder": posixpath.basename(posixpath.dirname(p))})
|
||||
out.sort(key=lambda x: x["name"], reverse=True)
|
||||
name = posixpath.basename(p)
|
||||
tids = [c for c in p.split("/") if re.match(r"^[A-Z]{4}\d{5}$", c) and c != "NPXS40087"]
|
||||
tid = tids[-1] if tids else ""
|
||||
m = re.search(r"(20\d{2})(\d{2})(\d{2})_?(\d{2})(\d{2})(\d{2})?", name)
|
||||
when = "%s-%s-%sT%s:%s:%s" % (m.group(1), m.group(2), m.group(3), m.group(4), m.group(5),
|
||||
m.group(6) or "00") if m else ""
|
||||
out.append({"path": p, "name": name, "size": sz,
|
||||
"video": MEDIA_TYPES[ext].startswith("video"), "hdr": ext == ".jxr",
|
||||
"tid": tid, "app": (titles.get(tid) or {}).get("title") or "",
|
||||
"when": when, "folder": posixpath.basename(posixpath.dirname(p))})
|
||||
out.sort(key=lambda x: (x["when"] or "", x["name"]), reverse=True)
|
||||
return out[:3000]
|
||||
|
||||
|
||||
def media_cached(path):
|
||||
"""Copia local (cache) de una captura o video, para verla en el navegador."""
|
||||
def _jxr_to_jpg(src, dst):
|
||||
"""Vista previa de una captura HDR (.jxr): Windows trae el decodificador de JPEG XR
|
||||
(y en Mac se intenta con sips, la herramienta de imagenes del sistema)."""
|
||||
if sys.platform == "darwin":
|
||||
import subprocess
|
||||
try:
|
||||
r = subprocess.run(["sips", "-s", "format", "jpeg", src, "--out", dst], capture_output=True, timeout=60)
|
||||
return r.returncode == 0 and os.path.isfile(dst) and os.path.getsize(dst) > 0
|
||||
except Exception:
|
||||
return False
|
||||
if os.name != "nt":
|
||||
return False
|
||||
q = lambda t: "'" + t.replace("'", "''") + "'"
|
||||
script = (
|
||||
"Add-Type -AssemblyName PresentationCore;"
|
||||
"$s=[IO.File]::OpenRead(%s);"
|
||||
"try{ $d=[System.Windows.Media.Imaging.BitmapDecoder]::Create($s,"
|
||||
" [System.Windows.Media.Imaging.BitmapCreateOptions]::None,"
|
||||
" [System.Windows.Media.Imaging.BitmapCacheOption]::OnLoad) } finally { $s.Close() };"
|
||||
"$c=New-Object System.Windows.Media.Imaging.FormatConvertedBitmap($d.Frames[0],"
|
||||
" [System.Windows.Media.PixelFormats]::Bgr24, $null, 0);"
|
||||
"$e=New-Object System.Windows.Media.Imaging.JpegBitmapEncoder; $e.QualityLevel=90;"
|
||||
"$e.Frames.Add([System.Windows.Media.Imaging.BitmapFrame]::Create($c));"
|
||||
"$o=[IO.File]::Create(%s); try{ $e.Save($o) } finally { $o.Close() }"
|
||||
) % (q(src), q(dst))
|
||||
r = _powershell(script, timeout=60)
|
||||
return r.returncode == 0 and os.path.isfile(dst) and os.path.getsize(dst) > 0
|
||||
|
||||
|
||||
def media_cached(path, preview=False):
|
||||
"""Copia local (cache) de una captura o video, para verla en el navegador. Con preview,
|
||||
una captura HDR (.jxr, que los navegadores no abren) se da convertida a JPG."""
|
||||
import hashlib
|
||||
path = fm_clean(path)
|
||||
if not path.startswith(MEDIA_ROOT + "/"):
|
||||
@@ -2311,6 +2396,10 @@ def media_cached(path):
|
||||
with open(fp + ".part", "wb") as fo:
|
||||
E.Ftp(cfg_snapshot()).call(lambda f: f.retrbinary("RETR " + path, fo.write, 1024 * 1024))
|
||||
os.replace(fp + ".part", fp)
|
||||
if preview and ext == ".jxr":
|
||||
jpg = fp[:-4] + "_vista.jpg"
|
||||
if os.path.isfile(jpg) or _jxr_to_jpg(fp, jpg):
|
||||
return jpg, "image/jpeg"
|
||||
return fp, MEDIA_TYPES[ext]
|
||||
FMT_LABEL = {"plain": "carpeta normal", "ffpkg": "imagen .ffpkg", "exfat": "imagen .exfat", "ffpfs": "imagen .ffpfs",
|
||||
"ffpfsc": "imagen comprimida .ffpfsc", "fpkg": "paquete .fpkg", "lz4": "carpeta LZ4",
|
||||
@@ -2478,6 +2567,8 @@ def ensure_aria2(job, cfg):
|
||||
if w:
|
||||
return w
|
||||
if os.name != "nt":
|
||||
if sys.platform == "darwin":
|
||||
raise E.PipeError("Para bajar torrents hace falta aria2. Instálalo con Homebrew (brew.sh): brew install aria2")
|
||||
raise E.PipeError("Para bajar torrents hace falta aria2 (instálalo, por ejemplo: sudo apt install aria2)")
|
||||
with ARIA2_LOCK:
|
||||
if os.path.isfile(mine):
|
||||
@@ -2975,7 +3066,7 @@ def forge_job(job, srcs, cfg, cancel, src_path, created, pc_folder, up):
|
||||
|
||||
def pc_temp_root(cfg):
|
||||
d = (cfg.get("pc_temp_dir") or "").strip().strip('"')
|
||||
return d or os.path.join(os.path.expanduser("~"), "Downloads", "PS5 Directo (temporal)")
|
||||
return d or os.path.join(downloads_dir(), "PS5 Directo (temporal)")
|
||||
|
||||
|
||||
def _pcx_state(job):
|
||||
@@ -4665,7 +4756,7 @@ def backup_dir(cfg=None):
|
||||
return d or os.path.join(os.path.expanduser("~"), "Documents", "PS5 Directo", "Copias")
|
||||
|
||||
|
||||
def _ftp_walk(f, root, out, cancel, limit=None):
|
||||
def _ftp_walk_sizes(f, root, out, cancel, limit=None):
|
||||
"""[(ruta, tamano)] de todos los archivos bajo root (vacio si no existe)."""
|
||||
try:
|
||||
listing = E.ftp_listdir(f, root)
|
||||
@@ -4676,7 +4767,7 @@ def _ftp_walk(f, root, out, cancel, limit=None):
|
||||
raise E.Cancelled()
|
||||
p = root.rstrip("/") + "/" + n
|
||||
if v["type"] == "dir":
|
||||
_ftp_walk(f, p, out, cancel, limit)
|
||||
_ftp_walk_sizes(f, p, out, cancel, limit)
|
||||
elif v["type"] == "file" and (limit is None or (v.get("size") or 0) <= limit):
|
||||
out.append((p, v.get("size") or 0))
|
||||
return out
|
||||
@@ -4720,10 +4811,10 @@ def backup_run(sections=None, label="copia"):
|
||||
todo = [] # (seccion, ruta, tamano)
|
||||
if "saves" in sections:
|
||||
for r in _save_roots(f):
|
||||
todo += [("saves", p, s) for p, s in _ftp_walk(f, r, [], BK_CANCEL)]
|
||||
todo += [("saves", p, s) for p, s in _ftp_walk_sizes(f, r, [], BK_CANCEL)]
|
||||
if "config" in sections:
|
||||
for r in BK_CONFIG_DIRS:
|
||||
todo += [("config", p, s) for p, s in _ftp_walk(f, r, [], BK_CANCEL, BK_MAX_FILE)]
|
||||
todo += [("config", p, s) for p, s in _ftp_walk_sizes(f, r, [], BK_CANCEL, BK_MAX_FILE)]
|
||||
if "appdb" in sections:
|
||||
for p in BK_APPDB:
|
||||
try:
|
||||
@@ -5461,7 +5552,7 @@ def health_check():
|
||||
# Actualizaciones: desde un .zip o desde GitHub
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
UPDATE_EXTS = (".py", ".html", ".txt", ".bat", ".vbs", ".ico", ".js", ".json", ".png", ".css", ".md", ".svg")
|
||||
UPDATE_EXTS = (".py", ".html", ".txt", ".bat", ".vbs", ".sh", ".ico", ".js", ".json", ".png", ".css", ".md", ".svg")
|
||||
|
||||
|
||||
def _ver_tuple(v):
|
||||
@@ -5598,6 +5689,8 @@ def install_zip(data):
|
||||
with open(tmp, "wb") as fo:
|
||||
fo.write(z.read(n))
|
||||
os.replace(tmp, dst)
|
||||
if dst.endswith(".sh") and os.name != "nt":
|
||||
os.chmod(dst, 0o755)
|
||||
return new_ver
|
||||
|
||||
|
||||
@@ -5608,6 +5701,8 @@ def restart_soon():
|
||||
kw = {"cwd": HERE}
|
||||
if os.name == "nt":
|
||||
kw["creationflags"] = 0x00000008 | 0x00000200 # DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP
|
||||
else:
|
||||
kw["start_new_session"] = True
|
||||
def go():
|
||||
time.sleep(0.8)
|
||||
try:
|
||||
@@ -5748,7 +5843,7 @@ class Handler(BaseHTTPRequestHandler):
|
||||
if not token_ok((q.get("t") or [""])[0]):
|
||||
return self._send(403, {"ok": False})
|
||||
try:
|
||||
fp, ctype = media_cached((q.get("p") or [""])[0])
|
||||
fp, ctype = media_cached((q.get("p") or [""])[0], preview=bool((q.get("v") or [""])[0]))
|
||||
except Exception as e:
|
||||
return self._send(404, {"ok": False, "error": str(e)})
|
||||
size = os.path.getsize(fp)
|
||||
@@ -5826,7 +5921,8 @@ class Handler(BaseHTTPRequestHandler):
|
||||
"unread": sum(1 for n in NOTIFS if not n.get("read")), "settings": dict(SETTINGS), "jobs": jobs,
|
||||
"inbox": len(INBOX), "repo": SETTINGS.get("update_repo") or DEFAULT_REPO,
|
||||
"sched": {"start": SCHED["start"], "after": SCHED["after"], "countdown": SCHED["countdown"]},
|
||||
"lan": bool(LAN["srv"]), "remote": self.lan, "os": os.name}
|
||||
"lan": bool(LAN["srv"]), "remote": self.lan, "os": os.name,
|
||||
"platform": sys.platform}
|
||||
st["settings"].pop("ftp_pass", None)
|
||||
if self.lan:
|
||||
st["settings"].pop("lan_token", None)
|
||||
@@ -5885,8 +5981,7 @@ class Handler(BaseHTTPRequestHandler):
|
||||
if p == "/api/open_ext_folder":
|
||||
folder = os.path.join(HERE, "extension")
|
||||
try:
|
||||
if os.name == "nt":
|
||||
os.startfile(folder)
|
||||
F.open_path(folder)
|
||||
return self._send(200, {"ok": True, "path": folder})
|
||||
except Exception as e:
|
||||
return self._send(200, {"ok": False, "path": folder, "error": str(e)})
|
||||
@@ -6440,15 +6535,26 @@ def forward_to_running(ses, paths):
|
||||
|
||||
|
||||
def _find_app_browser():
|
||||
"""Edge (viene con Windows) o Chrome, para abrir la app en su propia ventana."""
|
||||
if os.name != "nt":
|
||||
return None
|
||||
"""Edge o Chrome (o Chromium/Brave), para abrir la app en su propia ventana sin pestañas."""
|
||||
import shutil
|
||||
cands = []
|
||||
for env in ("PROGRAMFILES(X86)", "PROGRAMFILES", "LOCALAPPDATA"):
|
||||
root = os.environ.get(env)
|
||||
if root:
|
||||
cands.append(os.path.join(root, "Microsoft", "Edge", "Application", "msedge.exe"))
|
||||
cands.append(os.path.join(root, "Google", "Chrome", "Application", "chrome.exe"))
|
||||
if os.name == "nt":
|
||||
for env in ("PROGRAMFILES(X86)", "PROGRAMFILES", "LOCALAPPDATA"):
|
||||
root = os.environ.get(env)
|
||||
if root:
|
||||
cands.append(os.path.join(root, "Microsoft", "Edge", "Application", "msedge.exe"))
|
||||
cands.append(os.path.join(root, "Google", "Chrome", "Application", "chrome.exe"))
|
||||
elif sys.platform == "darwin":
|
||||
for base in ("/Applications", os.path.expanduser("~/Applications")):
|
||||
for app, exe in (("Google Chrome", "Google Chrome"), ("Microsoft Edge", "Microsoft Edge"),
|
||||
("Brave Browser", "Brave Browser"), ("Chromium", "Chromium")):
|
||||
cands.append(os.path.join(base, app + ".app", "Contents", "MacOS", exe))
|
||||
else:
|
||||
for n in ("google-chrome", "google-chrome-stable", "chromium", "chromium-browser", "microsoft-edge",
|
||||
"microsoft-edge-stable", "brave-browser", "brave"):
|
||||
w = shutil.which(n)
|
||||
if w:
|
||||
cands.append(w)
|
||||
for c in cands:
|
||||
if os.path.isfile(c):
|
||||
return c
|
||||
@@ -6567,6 +6673,16 @@ def taskbar_watch():
|
||||
|
||||
def focus_window():
|
||||
"""Si la ventana ya esta abierta, la trae al frente (en vez de abrir otra). True si habia."""
|
||||
if os.name != "nt":
|
||||
import shutil
|
||||
import subprocess
|
||||
if sys.platform.startswith("linux") and shutil.which("wmctrl"):
|
||||
try:
|
||||
return subprocess.run(["wmctrl", "-x", "-a", "PS5Directo"], capture_output=True,
|
||||
timeout=5).returncode == 0
|
||||
except Exception:
|
||||
return False
|
||||
return False
|
||||
try:
|
||||
import ctypes
|
||||
u32 = ctypes.windll.user32
|
||||
@@ -6589,9 +6705,12 @@ def open_window(url):
|
||||
import subprocess
|
||||
profile = os.path.join(DATA_DIR, "ventana")
|
||||
try:
|
||||
return subprocess.Popen([exe, "--app=" + url, "--user-data-dir=" + profile,
|
||||
"--window-size=1150,900", "--no-first-run",
|
||||
"--no-default-browser-check"])
|
||||
args = [exe, "--app=" + url, "--user-data-dir=" + profile, "--window-size=1150,900",
|
||||
"--no-first-run", "--no-default-browser-check"]
|
||||
if os.name != "nt" and sys.platform != "darwin":
|
||||
args.append("--class=PS5Directo") # en Linux agrupa la ventana con su icono del menu
|
||||
return subprocess.Popen(args, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL, start_new_session=os.name != "nt")
|
||||
except Exception:
|
||||
pass
|
||||
webbrowser.open(url)
|
||||
@@ -6605,9 +6724,41 @@ def _powershell(script, timeout=30):
|
||||
creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0))
|
||||
|
||||
|
||||
def ensure_linux_menu():
|
||||
"""Linux: entrada "PS5 Directo" en el menu de aplicaciones (con su icono). Se rehace si el
|
||||
programa cambia de carpeta. Tambien acepta archivos soltados encima (Exec ... %F)."""
|
||||
launcher = os.path.join(HERE, "ps5directo.sh")
|
||||
if not os.path.isfile(launcher):
|
||||
return
|
||||
try:
|
||||
if not os.access(launcher, os.X_OK):
|
||||
os.chmod(launcher, 0o755)
|
||||
apps = os.path.join(os.environ.get("XDG_DATA_HOME") or os.path.expanduser("~/.local/share"), "applications")
|
||||
fp = os.path.join(apps, "ps5directo.desktop")
|
||||
q = lambda t: '"' + t.replace("\\", "\\\\").replace('"', '\\"') + '"'
|
||||
body = ("[Desktop Entry]\nType=Application\nName=PS5 Directo\n"
|
||||
"Comment=Descarga, convierte y envía a tu PS5\nComment[en]=Download, convert and send to your PS5\n"
|
||||
"Exec=%s %%F\nIcon=%s\nTerminal=false\nCategories=Network;FileTransfer;\n"
|
||||
"StartupWMClass=PS5Directo\n" % (q(launcher), os.path.join(HERE, "web", "icon-256.png")))
|
||||
try:
|
||||
with open(fp, encoding="utf-8") as f:
|
||||
if f.read() == body:
|
||||
return
|
||||
except OSError:
|
||||
pass
|
||||
os.makedirs(apps, exist_ok=True)
|
||||
with open(fp, "w", encoding="utf-8") as f:
|
||||
f.write(body)
|
||||
print("Entrada en el menu de aplicaciones:", fp)
|
||||
except Exception as e:
|
||||
print("No se pudo crear la entrada del menu:", e)
|
||||
|
||||
|
||||
def ensure_shortcuts():
|
||||
"""Acceso directo "PS5 Directo" en el escritorio y en "Enviar a", con el icono del programa.
|
||||
Se crea la primera vez; despues solo se corrige si apunta a otra carpeta (una copia vieja)."""
|
||||
if sys.platform.startswith("linux"):
|
||||
return ensure_linux_menu()
|
||||
if os.name != "nt":
|
||||
return
|
||||
try:
|
||||
|
||||
+99
-13
@@ -1211,13 +1211,41 @@ def diagnose(ps5_ip, cfg, speed=None, active=0):
|
||||
# Energia: no dormir mientras hay trabajo y apagar/suspender al acabar
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_AWAKE = {"proc": None}
|
||||
|
||||
|
||||
def keep_awake(on):
|
||||
if os.name != "nt":
|
||||
"""Que el PC no se suspenda mientras hay envios. En Linux/Mac se hace con un proceso
|
||||
(systemd-inhibit / caffeinate) que dura mientras haga falta."""
|
||||
if os.name == "nt":
|
||||
try:
|
||||
import ctypes
|
||||
ES_CONTINUOUS, ES_SYSTEM_REQUIRED = 0x80000000, 0x00000001
|
||||
ctypes.windll.kernel32.SetThreadExecutionState(ES_CONTINUOUS | (ES_SYSTEM_REQUIRED if on else 0))
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
p = _AWAKE["proc"]
|
||||
if not on:
|
||||
if p is not None:
|
||||
try:
|
||||
p.terminate()
|
||||
except Exception:
|
||||
pass
|
||||
_AWAKE["proc"] = None
|
||||
return True
|
||||
if p is not None and p.poll() is None:
|
||||
return True
|
||||
if sys.platform == "darwin":
|
||||
cmd = ["caffeinate", "-i", "-w", str(os.getpid())]
|
||||
elif shutil.which("systemd-inhibit"):
|
||||
cmd = ["systemd-inhibit", "--what=sleep:idle", "--who=PS5 Directo", "--why=Enviando a la PS5",
|
||||
"--mode=block", "sleep", "infinity"]
|
||||
else:
|
||||
return False
|
||||
try:
|
||||
import ctypes
|
||||
ES_CONTINUOUS, ES_SYSTEM_REQUIRED = 0x80000000, 0x00000001
|
||||
ctypes.windll.kernel32.SetThreadExecutionState(ES_CONTINUOUS | (ES_SYSTEM_REQUIRED if on else 0))
|
||||
_AWAKE["proc"] = subprocess.Popen(cmd, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
@@ -1225,15 +1253,73 @@ def keep_awake(on):
|
||||
|
||||
def power_action(kind):
|
||||
"""'sleep' (suspender) o 'shutdown' (apagar). Devuelve un texto de lo hecho."""
|
||||
if os.name != "nt":
|
||||
return "Solo funciona en Windows"
|
||||
if kind == "shutdown":
|
||||
subprocess.Popen(["shutdown", "/s", "/t", "0", "/c", "PS5 Directo: cola terminada"], creationflags=NO_WINDOW)
|
||||
return "Apagando el PC"
|
||||
if kind == "sleep":
|
||||
subprocess.Popen(["rundll32.exe", "powrprof.dll,SetSuspendState", "0,1,0"], creationflags=NO_WINDOW)
|
||||
return "Suspendiendo el PC"
|
||||
return ""
|
||||
if os.name == "nt":
|
||||
if kind == "shutdown":
|
||||
subprocess.Popen(["shutdown", "/s", "/t", "0", "/c", "PS5 Directo: cola terminada"], creationflags=NO_WINDOW)
|
||||
return "Apagando el PC"
|
||||
if kind == "sleep":
|
||||
subprocess.Popen(["rundll32.exe", "powrprof.dll,SetSuspendState", "0,1,0"], creationflags=NO_WINDOW)
|
||||
return "Suspendiendo el PC"
|
||||
return ""
|
||||
keep_awake(False)
|
||||
if sys.platform == "darwin":
|
||||
cmd = {"shutdown": ["osascript", "-e", 'tell application "System Events" to shut down'],
|
||||
"sleep": ["pmset", "sleepnow"]}.get(kind)
|
||||
else:
|
||||
cmd = {"shutdown": ["systemctl", "poweroff"], "sleep": ["systemctl", "suspend"]}.get(kind)
|
||||
if not cmd:
|
||||
return ""
|
||||
try:
|
||||
subprocess.Popen(cmd, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
except Exception as e:
|
||||
return "No pude: %s" % e
|
||||
return "Apagando el PC" if kind == "shutdown" else "Suspendiendo el PC"
|
||||
|
||||
|
||||
def open_path(path):
|
||||
"""Abre una carpeta o archivo del PC con el programa del sistema (Explorador, Finder...)."""
|
||||
if os.name == "nt":
|
||||
os.startfile(path)
|
||||
return
|
||||
cmd = ["open", path] if sys.platform == "darwin" else ["xdg-open", path]
|
||||
subprocess.Popen(cmd, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
|
||||
|
||||
def pick_native(kind, title):
|
||||
"""Selector de archivos/carpeta del sistema en Linux (zenity/kdialog) o Mac (osascript).
|
||||
Devuelve la lista elegida ([] si se cancela) o None si no hay ninguno disponible."""
|
||||
title = title or "PS5 Directo"
|
||||
if sys.platform == "darwin":
|
||||
if kind == "folder":
|
||||
script = 'POSIX path of (choose folder with prompt %s)' % _as_str(title)
|
||||
else:
|
||||
script = ('set l to choose file with prompt %s with multiple selections allowed\n'
|
||||
'set o to ""\nrepeat with f in l\nset o to o & POSIX path of f & linefeed\nend repeat\nreturn o'
|
||||
% _as_str(title))
|
||||
args = ["osascript"]
|
||||
for ln in script.split("\n"):
|
||||
args += ["-e", ln]
|
||||
p = subprocess.run(args, capture_output=True, timeout=3600)
|
||||
if p.returncode != 0:
|
||||
return []
|
||||
return [x.rstrip("/") or "/" for x in p.stdout.decode("utf-8", "replace").splitlines() if x.strip()]
|
||||
if shutil.which("zenity"):
|
||||
cmd = ["zenity", "--file-selection", "--title=" + title, "--separator=\n"]
|
||||
cmd += ["--directory"] if kind == "folder" else ["--multiple"]
|
||||
elif shutil.which("kdialog"):
|
||||
home = os.path.expanduser("~")
|
||||
cmd = (["kdialog", "--title", title, "--getexistingdirectory", home] if kind == "folder" else
|
||||
["kdialog", "--title", title, "--getopenfilename", home, "--multiple", "--separate-output"])
|
||||
else:
|
||||
return None
|
||||
p = subprocess.run(cmd, capture_output=True, timeout=3600)
|
||||
if p.returncode != 0:
|
||||
return []
|
||||
return [x for x in p.stdout.decode("utf-8", "replace").splitlines() if x.strip()]
|
||||
|
||||
|
||||
def _as_str(t):
|
||||
return '"' + str(t).replace("\\", "\\\\").replace('"', '\\"') + '"'
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleName</key><string>PS5 Directo</string>
|
||||
<key>CFBundleDisplayName</key><string>PS5 Directo</string>
|
||||
<key>CFBundleIdentifier</key><string>io.github.jstrdev.ps5directo</string>
|
||||
<key>CFBundleExecutable</key><string>PS5 Directo</string>
|
||||
<key>CFBundleIconFile</key><string>ps5directo</string>
|
||||
<key>CFBundlePackageType</key><string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key><string>__VERSION__</string>
|
||||
<key>CFBundleVersion</key><string>__VERSION__</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key><string>6.0</string>
|
||||
<key>LSMinimumSystemVersion</key><string>11.0</string>
|
||||
<key>NSHighResolutionCapable</key><true/>
|
||||
<key>LSApplicationCategoryType</key><string>public.app-category.utilities</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,64 @@
|
||||
PS5 DIRECTO PARA MAC (EN PRUEBAS)
|
||||
=================================
|
||||
|
||||
Esta versión todavía no se ha podido probar en un Mac de verdad. Si algo
|
||||
falla, en el programa pulsa "Crear informe de fallo" y adjúntalo en un
|
||||
aviso en GitHub: https://github.com/jstrdev/ps5directo/issues
|
||||
Con eso se arregla rápido. ¡Gracias por probarla!
|
||||
|
||||
INSTALAR
|
||||
1. Arrastra "PS5 Directo" a la carpeta Aplicaciones (Applications).
|
||||
2. Ábrelo desde Aplicaciones.
|
||||
|
||||
SI MACOS NO TE DEJA ABRIRLO
|
||||
La app no está firmada por Apple (eso cuesta 99 $ al año), así que la
|
||||
primera vez macOS la bloquea. Es normal en programas gratuitos.
|
||||
- macOS 15 (Sequoia) o posterior: intenta abrirla, cierra el aviso y ve a
|
||||
Ajustes del Sistema > Privacidad y seguridad. Abajo verás "Se ha
|
||||
bloqueado PS5 Directo": pulsa "Abrir igualmente".
|
||||
- macOS 14 o anterior: clic derecho (o Control + clic) sobre la app >
|
||||
Abrir > Abrir.
|
||||
- Si aun así no abre, en Terminal:
|
||||
xattr -dr com.apple.quarantine "/Applications/PS5 Directo.app"
|
||||
|
||||
NOTAS
|
||||
- Funciona en Mac con Apple Silicon (M1, M2...) e Intel, macOS 11 o
|
||||
posterior. Trae Python y 7-Zip.
|
||||
- Para la ventana sin pestañas usa Chrome, Edge o Brave si los tienes; si
|
||||
no, se abre en Safari.
|
||||
- Para los torrents hace falta aria2: instala Homebrew (brew.sh) y luego
|
||||
"brew install aria2".
|
||||
- Se actualiza solo desde el propio programa, como en Windows.
|
||||
|
||||
|
||||
PS5 DIRECTO FOR MAC (TESTING)
|
||||
=============================
|
||||
|
||||
This version hasn't been tested on a real Mac yet. If something fails,
|
||||
click "Create problem report" in the program and attach it to an issue
|
||||
on GitHub: https://github.com/jstrdev/ps5directo/issues
|
||||
That makes it quick to fix. Thanks for trying it!
|
||||
|
||||
INSTALL
|
||||
1. Drag "PS5 Directo" into the Applications folder.
|
||||
2. Open it from Applications.
|
||||
|
||||
IF MACOS WON'T OPEN IT
|
||||
The app isn't signed by Apple (that costs $99 a year), so macOS blocks it
|
||||
the first time. That's normal for free programs.
|
||||
- macOS 15 (Sequoia) or later: try to open it, close the warning and go
|
||||
to System Settings > Privacy & Security. Near the bottom you'll see
|
||||
"PS5 Directo was blocked": click "Open Anyway".
|
||||
- macOS 14 or earlier: right-click (or Control-click) the app > Open >
|
||||
Open.
|
||||
- If it still won't open, in Terminal:
|
||||
xattr -dr com.apple.quarantine "/Applications/PS5 Directo.app"
|
||||
|
||||
NOTES
|
||||
- Works on Apple Silicon (M1, M2...) and Intel Macs, macOS 11 or later.
|
||||
Python and 7-Zip are included.
|
||||
- For the window without tabs it uses Chrome, Edge or Brave if you have
|
||||
one; otherwise it opens in Safari.
|
||||
- Torrents need aria2: install Homebrew (brew.sh), then
|
||||
"brew install aria2".
|
||||
- It updates itself from the program, like on Windows.
|
||||
@@ -0,0 +1,45 @@
|
||||
#!/bin/bash
|
||||
# Lanzador de "PS5 Directo.app" (va en Contents/MacOS/PS5 Directo).
|
||||
# - Python incluido para Apple Silicon (python-arm64) e Intel (python-x86_64), en Contents/Resources.
|
||||
# - El programa se copia a ~/Library/Application Support/PS5 Directo/app y se ejecuta desde ahi,
|
||||
# para que «Actualizar» pueda escribir aunque la app este en /Applications. Si la app trae una
|
||||
# version mas nueva que la copiada, se vuelve a copiar.
|
||||
RES="$(cd "$(dirname "$0")/../Resources" && pwd)"
|
||||
SUP="$HOME/Library/Application Support/PS5 Directo"
|
||||
APP="$SUP/app"
|
||||
LOG="$SUP/lanzador.log"
|
||||
mkdir -p "$SUP"
|
||||
|
||||
case "$(uname -m)" in
|
||||
arm64) PY="$RES/python-arm64/bin/python3" ;;
|
||||
*) PY="$RES/python-x86_64/bin/python3" ;;
|
||||
esac
|
||||
[ -x "$PY" ] || PY="$RES/python-x86_64/bin/python3" # Rosetta, por si acaso
|
||||
|
||||
ver() { sed -n 's/^VERSION = "\([^"]*\)".*/\1/p' "$1" 2>/dev/null | head -1; }
|
||||
newer() { # 0 si $1 es mas nueva que $2
|
||||
[ -z "$2" ] && return 0
|
||||
[ "$1" = "$2" ] && return 1
|
||||
[ "$(printf '%s\n%s\n' "$1" "$2" | sort -t. -k1,1n -k2,2n -k3,3n | tail -1)" = "$1" ]
|
||||
}
|
||||
BUNDLED="$(ver "$RES/app/app.py")"
|
||||
COPIED="$(ver "$APP/app.py")"
|
||||
if [ ! -f "$APP/app.py" ] || newer "$BUNDLED" "$COPIED"; then
|
||||
echo "$(date) copio la version $BUNDLED (habia: ${COPIED:-ninguna})" >> "$LOG"
|
||||
rm -rf "$APP.nuevo"
|
||||
cp -R "$RES/app" "$APP.nuevo" && rm -rf "$APP" && mv "$APP.nuevo" "$APP"
|
||||
fi
|
||||
|
||||
export PATH="$RES/bin:$PATH" # 7-Zip incluido
|
||||
export PYTHONUNBUFFERED=1
|
||||
cd "$APP" || exit 1
|
||||
if [ ! -x "$PY" ]; then
|
||||
osascript -e 'display alert "PS5 Directo" message "No encuentro el Python incluido en la app. Vuelve a descargarla de github.com/jstrdev/ps5directo/releases"' >/dev/null 2>&1
|
||||
exit 1
|
||||
fi
|
||||
mkdir -p "$HOME/.ps5directo"
|
||||
# El programa sigue en segundo plano y el lanzador termina: asi cada clic en el icono vuelve a
|
||||
# entrar aqui y, si ya estaba abierto, solo trae su ventana (si no, macOS creeria que la app
|
||||
# sigue abierta y no haria nada).
|
||||
nohup "$PY" "$APP/app.py" "$@" >> "$HOME/.ps5directo/ps5directo.log" 2>&1 < /dev/null &
|
||||
exit 0
|
||||
@@ -0,0 +1,25 @@
|
||||
#!/bin/sh
|
||||
# Abre PS5 Directo en Linux (y en Mac, si lo usas desde la carpeta).
|
||||
# Usa el Python que viene incluido (carpeta python/) o, si no esta, el python3 del sistema.
|
||||
# Puedes pasarle archivos: ./ps5directo.sh juego.rar
|
||||
DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
cd "$DIR" || exit 1
|
||||
# 7-Zip incluido (carpeta bin/), para "Descomprimir en el PC"
|
||||
[ -d "$DIR/bin" ] && PATH="$DIR/bin:$PATH" && export PATH
|
||||
if [ -x "$DIR/python/bin/python3" ]; then
|
||||
PY="$DIR/python/bin/python3"
|
||||
elif command -v python3 >/dev/null 2>&1; then
|
||||
PY="python3"
|
||||
else
|
||||
MSG="No encuentro Python 3. Instálalo (por ejemplo: sudo apt install python3) o baja la versión de PS5 Directo para Linux, que ya lo trae."
|
||||
command -v zenity >/dev/null 2>&1 && zenity --error --title="PS5 Directo" --text="$MSG" 2>/dev/null
|
||||
echo "$MSG" >&2
|
||||
exit 1
|
||||
fi
|
||||
export PYTHONUNBUFFERED=1
|
||||
# Abierto desde el menu (sin terminal): el registro va al mismo archivo que en Windows
|
||||
if [ ! -t 1 ]; then
|
||||
mkdir -p "$HOME/.ps5directo"
|
||||
exec "$PY" "$DIR/app.py" "$@" >> "$HOME/.ps5directo/ps5directo.log" 2>&1
|
||||
fi
|
||||
exec "$PY" "$DIR/app.py" "$@"
|
||||
+36
-21
@@ -325,7 +325,9 @@ tr:hover .dots{opacity:1}
|
||||
.mdgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(190px,1fr));gap:10px;margin-top:10px}
|
||||
.mdt{position:relative;border:1px solid var(--line);border-radius:10px;overflow:hidden;background:var(--bg);cursor:pointer;aspect-ratio:16/9;display:grid;place-items:center}
|
||||
.mdt img{width:100%;height:100%;object-fit:cover;display:block}
|
||||
.mdt .vd{font-size:34px;color:#cfd8ea}
|
||||
.mdt .vd{font-size:34px;color:#fff;position:relative;text-shadow:0 2px 10px rgba(0,0,0,.8)}
|
||||
.mdt img.cv{position:absolute;inset:0;opacity:.5}
|
||||
.mdt .hb{position:absolute;top:7px;right:7px;font-size:10px;font-weight:700;padding:1px 6px;border-radius:6px;background:rgba(0,0,0,.65);color:#ffd36b;letter-spacing:.04em}
|
||||
.mdt .nm{position:absolute;left:0;right:0;bottom:0;padding:3px 7px;font-size:11px;background:linear-gradient(transparent,rgba(0,0,0,.75));color:#dfe6f2;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||
.mdt input{position:absolute;top:7px;left:7px;width:18px;height:18px}
|
||||
.mdt.on{outline:2px solid #5b9dff;outline-offset:-2px}
|
||||
@@ -438,6 +440,7 @@ header{background:rgba(14,17,22,.97)}.card{background:rgb(22,27,34)}}
|
||||
.su-row>div{flex:1;min-width:0}
|
||||
.su-legal{font-size:12.5px;color:var(--muted);border-left:3px solid var(--line);padding-left:10px}
|
||||
@media(max-width:600px){.lib-grid{grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:10px}.gact button{font-size:11.5px;padding:4px 5px}}
|
||||
body.notwin .winonly{display:none!important}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -479,7 +482,7 @@ header{background:rgba(14,17,22,.97)}.card{background:rgb(22,27,34)}}
|
||||
<h2>Nueva transferencia</h2>
|
||||
<label for="lines">Enlaces directos (uno por línea). Pega todas las partes juntas: .part1.rar, .part2.rar… o .001, .002…</label>
|
||||
<textarea id="lines" placeholder="https://servidor/archivo.part1.rar https://servidor/archivo.part2.rar También vale la ruta de un archivo o carpeta de tu PC (D:\Descargas\archivo.rar), un enlace ftp:// de tu NAS o un torrent (magnet o .torrent)"></textarea>
|
||||
<div class="drophint" id="drophint">Archivos de tu PC: suéltalos en esta página y los busco en tu PC, o arrástralos sobre el <b>icono de PS5 Directo</b>, o clic derecho → <b>Enviar a → PS5 Directo</b>.</div>
|
||||
<div class="drophint" id="drophint">Archivos de tu PC: suéltalos en esta página y los busco en tu PC, o arrástralos sobre el <b>icono de PS5 Directo</b><span class="winonly">, o clic derecho → <b>Enviar a → PS5 Directo</b></span>.</div>
|
||||
<div class="pickrow">
|
||||
<button class="small" id="btnPickFiles">Elegir archivos…</button>
|
||||
<button class="small" id="btnPickFolder">Elegir carpeta…</button>
|
||||
@@ -694,7 +697,7 @@ header{background:rgba(14,17,22,.97)}.card{background:rgb(22,27,34)}}
|
||||
<button class="small" id="mdSel">Seleccionar todo</button>
|
||||
<button class="small" id="mdDown" disabled>Bajar al PC</button>
|
||||
<button class="primary small" id="mdLoad">Actualizar</button></div>
|
||||
<p class="hint">Las capturas de pantalla y los vídeos guardados en la consola. Pulsa una para verla grande; marca las que quieras y «Bajar al PC» las guarda en Descargas\PS5 Directo.</p>
|
||||
<p class="hint">Las capturas de pantalla y los vídeos guardados en la consola. Pulsa una para verla grande; marca las que quieras y «Bajar al PC» las guarda en Descargas › PS5 Directo.</p>
|
||||
<div class="hint" id="mdSum"></div>
|
||||
<div class="mdgrid" id="mdGrid"></div>
|
||||
</section>
|
||||
@@ -780,6 +783,7 @@ header{background:rgba(14,17,22,.97)}.card{background:rgb(22,27,34)}}
|
||||
<div class="wl-c" style="--c:#ec6fb4"><b>💾 Copias de seguridad</b><span>Tus partidas y ajustes de la consola guardados en el PC por si algo falla.</span></div>
|
||||
<div class="wl-c" style="--c:#45c3dc"><b>📱 Móvil y navegador</b><span>Contrólalo desde el móvil con un QR y manda enlaces desde el navegador sin copiar y pegar.</span></div>
|
||||
</div>
|
||||
<p class="wl-note macbeta" hidden><b>🍎 Versión de Mac en pruebas:</b> todavía no se ha podido probar en un Mac de verdad. Si algo falla, pulsa «Crear informe de fallo» y adjúntalo en un aviso en GitHub (Issues): con eso se arregla rápido. ¡Gracias por probarla!</p>
|
||||
<p class="wl-note"><b>No es solo un programa para subir archivos:</b> une descarga, conversión, envío y gestión de la consola en uno. Tampoco instala ni cambia nada del sistema: solo mueve los archivos que tú eliges, y cada uno lo usa bajo su responsabilidad.</p>
|
||||
<div class="actions"><button class="small" id="wlTour">Ver la guía de uso</button><div class="spacer"></div><button class="primary" id="wlOk">Empezar</button></div>
|
||||
</div>
|
||||
@@ -932,8 +936,8 @@ header{background:rgba(14,17,22,.97)}.card{background:rgb(22,27,34)}}
|
||||
<p class="hint">Alternativa a BFpilot: 7-Zip descomprime en el PC sobre la marcha y la app manda los archivos ya sueltos, comprobando cada uno con su CRC. No escribe nada descomprimido en el PC y en la PS5 solo ocupa el contenido final (sin el RAR al lado). A cambio, por el Wi-Fi viaja todo sin comprimir (puede ser 3-4 veces más) y los enlaces se bajan antes al PC (hace falta sitio para el RAR).</p>
|
||||
<div class="row tri">
|
||||
<div><label>Por defecto</label><select id="s_extract_where"><option value="ps5">En la PS5 (BFpilot)</option><option value="pc">En el PC (7-Zip)</option></select></div>
|
||||
<div><label>Carpeta del PC para bajar los RAR</label><input id="s_pc_temp_dir" type="text" placeholder="Descargas\PS5 Directo (temporal)"></div>
|
||||
<div><label>Ruta de 7z.exe (vacío = buscarlo)</label><input id="s_sevenzip_path" type="text" placeholder="C:\Program Files\7-Zip\7z.exe"></div>
|
||||
<div><label>Carpeta del PC para bajar los RAR</label><input id="s_pc_temp_dir" type="text" placeholder="Descargas › PS5 Directo (temporal)"></div>
|
||||
<div><label>Ruta de 7-Zip (vacío = buscarlo)</label><input id="s_sevenzip_path" type="text" placeholder="C:\Program Files\7-Zip\7z.exe"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top:12px;border-top:1px solid var(--line);padding-top:10px">
|
||||
@@ -991,7 +995,7 @@ header{background:rgba(14,17,22,.97)}.card{background:rgb(22,27,34)}}
|
||||
|
||||
<div class="toast" id="toast"></div>
|
||||
<script>
|
||||
const I18N_EXACT={"Atrás (Alt+←)": "Back (Alt+←)", "Adelante (Alt+→)": "Forward (Alt+→)", "Subir un nivel (Retroceso)": "Up one level (Backspace)", "Haz clic en el hueco para escribir una ruta": "Click the empty space to type a path", "Actualizar (F5)": "Refresh (F5)", "Cortar": "Cut", "Mover a…": "Move to…", "Copiar a…": "Copy to…", "Más…": "More…", "Más acciones": "More actions", "Atajos: doble clic abre · clic derecho para el menú · Supr borra · F2 renombra · Ctrl+C / Ctrl+X / Ctrl+V · Ctrl+A selecciona todo · Retroceso sube": "Shortcuts: double-click opens · right-click for the menu · Del deletes · F2 renames · Ctrl+C / Ctrl+X / Ctrl+V · Ctrl+A selects all · Backspace goes up", "Mover aquí": "Move here", "Copiar aquí": "Copy here", "Nueva carpeta": "New folder", "Ya existe": "Already exists", "Reemplazar / combinar": "Replace / merge", "Reemplazar": "Replace", "Combinar": "Merge", "Conservar ambos": "Keep both", "Omitir": "Skip", "Reemplazar sobrescribe los archivos con el mismo nombre. Si es una carpeta, se combina: lo que ya tenía y no estás pegando se queda. Conservar ambos le pone (2) al nuevo.": "Replace overwrites files with the same name. Folders are merged: whatever they already had that you are not pasting stays. Keep both adds (2) to the new one.", "¿Qué hago con los que ya existen en la carpeta de destino?": "What should I do with the ones that already exist in the destination folder?", "Extraer": "Extract", "Extraer…": "Extract…", "Carpeta": "Folder", "Contraseña (solo si tiene)": "Password (only if it has one)", "La consola lo descomprime en una carpeta al lado del archivo (con BFpilot) y deja el comprimido como está. Nada pasa por el PC.": "The console extracts it into a folder next to the archive (with BFpilot) and leaves the archive as it is. Nothing goes through the PC.", "Notificaciones": "Notifications", "Marcar leídos": "Mark all read", "Borrar todo": "Clear all", "Descartar": "Dismiss", "Unidades": "Drives", "Accesos": "Places", "Recientes": "Recent", "Destinos recientes": "Recent destinations", "libres": "free", "Almacenamiento interno": "Internal storage", "Aplicaciones (homebrew)": "Apps (homebrew)", "Datos (/data)": "Data (/data)", "Paquetes (/data/pkg)": "Packages (/data/pkg)", "Restos de PS5 Directo": "PS5 Directo leftovers", "BFpilot no está cargado: el explorador va por FTP. Copiar pasa por el PC (más lento), y para descomprimir o ver el espacio libre hace falta BFpilot.": "BFpilot is not loaded: the browser works over FTP. Copying goes through the PC (slower), and extracting or showing free space needs BFpilot.", "desde": "from", "Pegar dentro": "Paste inside", "Pegar aquí": "Paste here", "Subir archivos aquí": "Upload files here", "Subir carpeta aquí": "Upload folder here", "Actualizar la carpeta": "Refresh folder", "Supr": "Del", "Copiar ruta de esta carpeta": "Copy this folder's path", "Abrir": "Open", "Descargar al PC": "Download to PC", "Copiar": "Copy", "Renombrar": "Rename", "Calcular tamaño": "Calculate size", "Permisos 777": "Permissions 777", "Copiar ruta": "Copy path", "Copiar rutas": "Copy paths", "Borrar": "Delete", "Ve a la carpeta de destino y pulsa \"Pegar aquí\" (o Ctrl+V)": "Go to the destination folder and click \"Paste here\" (or Ctrl+V)", "Ruta copiada": "Path copied", "El nombre no puede llevar / ni \\": "The name cannot contain / or \\", "No se puede pegar una carpeta dentro de sí misma": "You cannot paste a folder inside itself", "No hay carpetas dentro.": "No folders inside.", "Una carpeta no se puede meter dentro de sí misma.": "A folder cannot go inside itself.", "Ya está en esta carpeta.": "It is already in this folder.", "Otra unidad sin BFpilot: se copia pasando por el PC y luego se borra el original.": "Another drive without BFpilot: it is copied through the PC and then the original is deleted.", "BFpilot no está cargado: cárgalo en la PS5 para poder extraer.": "BFpilot is not loaded: load it on the PS5 to extract.", "Ya hay una carpeta con ese nombre: se usará uno nuevo terminado en (2).": "There is already a folder with that name: a new one ending in (2) will be used.", "Pon un solo nombre de carpeta, sin /": "Use a single folder name, without /", "No los encuentro en tu PC: usa \"Subir archivos aquí\"": "I cannot find them on your PC: use \"Upload files here\"", "Extrayendo": "Extracting", "ahora": "now", "Aún no hay avisos. Aquí aparecerán las transferencias terminadas, los errores, las desconexiones y las versiones nuevas.": "No notifications yet. Finished transfers, errors, disconnections and new versions will show up here.", "Novedades": "What's new", "Entendido": "Got it", "Se ha actualizado. Esto es lo nuevo:": "PS5 Directo has been updated. Here is what's new:", "Transferencias es la cola. En Archivos de la consola navegas por la PS5: ves tamaños, copias, mueves, renombras, borras, bajas al PC o subes archivos y carpetas. Espacio te enseña en un gráfico qué ocupa Line truncated
|
||||
const I18N_EXACT={"Atrás (Alt+←)": "Back (Alt+←)", "Adelante (Alt+→)": "Forward (Alt+→)", "Subir un nivel (Retroceso)": "Up one level (Backspace)", "Haz clic en el hueco para escribir una ruta": "Click the empty space to type a path", "Actualizar (F5)": "Refresh (F5)", "Cortar": "Cut", "Mover a…": "Move to…", "Copiar a…": "Copy to…", "Más…": "More…", "Más acciones": "More actions", "Atajos: doble clic abre · clic derecho para el menú · Supr borra · F2 renombra · Ctrl+C / Ctrl+X / Ctrl+V · Ctrl+A selecciona todo · Retroceso sube": "Shortcuts: double-click opens · right-click for the menu · Del deletes · F2 renames · Ctrl+C / Ctrl+X / Ctrl+V · Ctrl+A selects all · Backspace goes up", "Mover aquí": "Move here", "Copiar aquí": "Copy here", "Nueva carpeta": "New folder", "Ya existe": "Already exists", "Reemplazar / combinar": "Replace / merge", "Reemplazar": "Replace", "Combinar": "Merge", "Conservar ambos": "Keep both", "Omitir": "Skip", "Reemplazar sobrescribe los archivos con el mismo nombre. Si es una carpeta, se combina: lo que ya tenía y no estás pegando se queda. Conservar ambos le pone (2) al nuevo.": "Replace overwrites files with the same name. Folders are merged: whatever they already had that you are not pasting stays. Keep both adds (2) to the new one.", "¿Qué hago con los que ya existen en la carpeta de destino?": "What should I do with the ones that already exist in the destination folder?", "Extraer": "Extract", "Extraer…": "Extract…", "Carpeta": "Folder", "Contraseña (solo si tiene)": "Password (only if it has one)", "La consola lo descomprime en una carpeta al lado del archivo (con BFpilot) y deja el comprimido como está. Nada pasa por el PC.": "The console extracts it into a folder next to the archive (with BFpilot) and leaves the archive as it is. Nothing goes through the PC.", "Notificaciones": "Notifications", "Marcar leídos": "Mark all read", "Borrar todo": "Clear all", "Descartar": "Dismiss", "Unidades": "Drives", "Accesos": "Places", "Recientes": "Recent", "Destinos recientes": "Recent destinations", "libres": "free", "Almacenamiento interno": "Internal storage", "Aplicaciones (homebrew)": "Apps (homebrew)", "Datos (/data)": "Data (/data)", "Paquetes (/data/pkg)": "Packages (/data/pkg)", "Restos de PS5 Directo": "PS5 Directo leftovers", "BFpilot no está cargado: el explorador va por FTP. Copiar pasa por el PC (más lento), y para descomprimir o ver el espacio libre hace falta BFpilot.": "BFpilot is not loaded: the browser works over FTP. Copying goes through the PC (slower), and extracting or showing free space needs BFpilot.", "desde": "from", "Pegar dentro": "Paste inside", "Pegar aquí": "Paste here", "Subir archivos aquí": "Upload files here", "Subir carpeta aquí": "Upload folder here", "Actualizar la carpeta": "Refresh folder", "Supr": "Del", "Copiar ruta de esta carpeta": "Copy this folder's path", "Abrir": "Open", "Descargar al PC": "Download to PC", "Copiar": "Copy", "Renombrar": "Rename", "Calcular tamaño": "Calculate size", "Permisos 777": "Permissions 777", "Copiar ruta": "Copy path", "Copiar rutas": "Copy paths", "Borrar": "Delete", "Ve a la carpeta de destino y pulsa \"Pegar aquí\" (o Ctrl+V)": "Go to the destination folder and click \"Paste here\" (or Ctrl+V)", "Ruta copiada": "Path copied", "El nombre no puede llevar / ni \\": "The name cannot contain / or \\", "No se puede pegar una carpeta dentro de sí misma": "You cannot paste a folder inside itself", "No hay carpetas dentro.": "No folders inside.", "Una carpeta no se puede meter dentro de sí misma.": "A folder cannot go inside itself.", "Ya está en esta carpeta.": "It is already in this folder.", "Otra unidad sin BFpilot: se copia pasando por el PC y luego se borra el original.": "Another drive without BFpilot: it is copied through the PC and then the original is deleted.", "BFpilot no está cargado: cárgalo en la PS5 para poder extraer.": "BFpilot is not loaded: load it on the PS5 to extract.", "Ya hay una carpeta con ese nombre: se usará uno nuevo terminado en (2).": "There is already a folder with that name: a new one ending in (2) will be used.", "Pon un solo nombre de carpeta, sin /": "Use a single folder name, without /", "No los encuentro en tu PC: usa \"Subir archivos aquí\"": "I cannot find them on your PC: use \"Upload files here\"", "Extrayendo": "Extracting", "ahora": "now", "Aún no hay avisos. Aquí aparecerán las transferencias terminadas, los errores, las desconexiones y las versiones nuevas.": "No notifications yet. Finished transfers, errors, disconnections and new versions will show up here.", "Novedades": "What's new", "Entendido": "Got it", "Se ha actualizado. Esto es lo nuevo:": "PS5 Directo has been updated. Here is what's new:", "Transferencias es la cola. En Archivos de la consola navegas por la PS5: ves tamaños, copias, mueves, renombras, borras, bajas al PC o subes archivos y carpetas. Espacio te enseña en un gráfico qué ocupa Line truncated
|
||||
const I18N_RX=[["^Este ZIP usa cifrado AES y BFpilot todavia no sabe abrirlo \\(solo el cifrado ZIP clasico\\)\\. Los archivos ya estan en la PS5, en ([\\s\\S]*?)\\. Puedes bajarlos al PC, extraerlos con 7-Zip y subir la carpeta\\.$", "This ZIP uses AES encryption and BFpilot can't open it yet (only classic ZIP encryption). The files are already on the PS5, in {0}. You can download them to the PC, extract them with 7-Zip and upload the folder."], ["^Aviso: lo descomprimido puede ocupar hasta ~5 veces el RAR \\(~([\\s\\S]*?)\\) y quedan ([\\s\\S]*?) libres\\. Puede bastar, pero si el espacio baja del margen lo paro\\. Si puedes, libera espacio\\.$", "Warning: extracted content can take up to ~5 times the RAR (~{0}) and {1} is free. It may be enough, but if space drops below the margin I stop. Free up space if you can."], ["^Ojo: el contenido descomprimido puede ocupar hasta ~5 veces el RAR \\(~([\\s\\S]*?)\\) y no se si cabra\\. Si al descomprimir baja del margen, lo paro y los RAR se quedan en la PS5\\.$", "Heads up: extracted content can take up to ~5 times the RAR (~{0}) and it may not fit. If space drops below the margin while extracting, I stop and the RARs stay on the PS5."], ["^AVISO: no pude poner los permisos de ([\\s\\S]*?) \\(([\\s\\S]*?)\\)\\. Si no arranca, en FileZilla: clic derecho en la carpeta > Permisos > 777 > recurrir a subdirectorios\\.$", "WARNING: couldn't set permissions on {0} ({1}). If it doesn't launch, in FileZilla: right-click the folder > File permissions > 777 > recurse into subdirectories."], ["^No cabe en el PC: hay que bajar ([\\s\\S]*?) a ([\\s\\S]*?) y solo hay ([\\s\\S]*?) libres\\. Libera espacio, cambia la carpeta temporal en Ajustes o descomprime en la PS5\\.$", "Not enough space on the PC: {0} must be downloaded to {1} and only {2} is free. Free up space, change the temporary folder in Settings, or extract on the PS5."], ["^No cabe en la PS5: descomprimido ocupa ([\\s\\S]*?), faltan por mandar ([\\s\\S]*?), hay ([\\s\\S]*?) libres y dejo ([\\s\\S]*?) de margen\\. Libera espacio y pulsa Reintentar\\.$", "Not enough space on the PS5: extracted it takes {0}, {1} left to send, {2} free and I keep {3} as margin. Free up space and click Retry."], ["^No hay sitio en la PS5: faltan por enviar ([\\s\\S]*?), hay ([\\s\\S]*?) libres y dejo ([\\s\\S]*?) de margen \\(Ajustes\\)\\. Libera espacio y pulsa Reintentar\\.$", "Not enough space on the PS5: {0} left to send, {1} free and I keep {2} as margin (Settings). Free up space and click Retry."], ["^La IP de la PS5 no es valida \\(([\\s\\S]*?)\\): en Ajustes escribe solo los numeros, por ejemplo 192\\.168\\.1\\.50, sin ftp:\\/\\/ ni puerto$", "The PS5 IP is not valid ({0}): in Settings type just the numbers, for example 192.168.1.50, without ftp:// or a port"], ["^Parado para no llenar la PS5 \\(([\\s\\S]*?)\\): quedan ([\\s\\S]*?) libres y el margen es ([\\s\\S]*?)\\. Libera espacio y pulsa Reintentar; sigue donde iba\\.$", "Stopped so the PS5 doesn't fill up ({0}): {1} free and the margin is {2}. Free up space and click Retry; it continues where it left off."], ["^BFpilot no abre ZIP partidos \\(\\.z01, \\.z02\\.\\.\\.\\)\\. Uno los RAR\\/7z partidos, pero este formato no\\. Los archivos estan en ([\\s\\S]*?)\\.$", "BFpilot can't open split ZIPs (.z01, .z02...). I can join split RAR/7z, but not this format. The files are in {0}."], ["^No cabe en la PS5: faltan ([\\s\\S]*?) por enviar, hay ([\\s\\S]*?) libres y el margen es ([\\s\\S]*?)\\. Libera espacio y pulsa Reintentar\\.$", "It doesn't fit on the PS5: {0} left to send, {1} free and the margin is {2}. Free up space and click Retry."], ["^Ojo: ya tienes ([\\s\\S]*?) en ([\\s\\S]*?)\\. Dos copias con el mismo Title ID pueden liar a ShadowMountPlus \\(deja solo una\\)\\.([\\s\\S]*?)$", "Heads up: you already have {0} in {1}. Two copies with the same Title ID can confuse ShadowMountPlus (keep only one).{2}"], ["^Ojo: ya tienes ([\\s\\S]*?) en ([\\s\\S]*?)\\. Dos copias con el mismo Title ID pueden liar a ShadowMountPlus \\(deja solo una\\)\\.$", "Heads up: you already have {0} in {1}. Two copies with the same Title ID can confuse ShadowMountPlus (keep only one)."], ["^El comprimido tiene enlaces \\(([\\s\\S]*?)\\); eso solo lo sabe extraer BFpilot\\. Descomprimelo en la PS5\\.$", "The archive contains links ({0}); only BFpilot can extract those. Extract it on the PS5."], ["^No hay sitio en la PS5: quedan ([\\s\\S]*?) libres y el margen es ([\\s\\S]*?)\\. Libera espacio y pulsa Reintentar\\.$", "Not enough space on the PS5: {0} free and the margin is {1}. Free up space and click Retry."], ["^Ya existe ([\\s\\S]*?)\\. No lo piso: borralo o renombralo y pulsa Reintentar\\. Lo extraido esta en ([\\s\\S]*?)$", "{0} already exists. I won't overwrite it: delete or rename it and click Retry. What was extracted is in {1}"], ["^Recibido de la extensión: ([\\s\\S]*?)\\. Cuando estén todas las partes, pulsa \"Añadir a la cola\"\\.$", "Received from the extension: {0}. When all parts are iLine truncated
|
||||
Object.assign(I18N_EXACT,{"Windows tiene bloqueado Python en el firewall (pasa si se cerró su aviso la primera vez).": "Windows Firewall is blocking Python (this happens if its prompt was closed the first time).", "Además, tu red está marcada como «Pública».": "Also, your network is marked as \"Public\".", "¿Se queda en blanco? Prueba de conexión": "Stays blank? Connection test", "Escanea este código pequeño. Si en el móvil sale «El móvil llega a PS5 Directo», la red está bien. Si no carga, el móvil no llega al PC: mira que esté en la misma Wi-Fi (no la de invitados ni con datos) y el firewall de abajo.": "Scan this small code. If your phone shows \"El móvil llega a PS5 Directo\", the network is fine. If it doesn't load, the phone can't reach the PC: check it's on the same Wi-Fi (not the guest network or mobile data) and the firewall below."});Object.assign(I18N_EXACT,{"Contraseña, destino, dónde descomprimir y el formato. Si no sabes qué elegir, déjalo como está: funciona así.": "Password, destination, where to extract and the format. If you're not sure what to pick, leave it as it is: it works like that."});Object.assign(I18N_EXACT,{"Cómo dejarlo en la PS5": "How to leave it on the PS5", "Opcional. Si no sabes cuál elegir, deja Carpeta.": "Optional. If you're not sure, leave Folder.", "Carpeta": "Folder", "Por defecto · sin convertir": "Default · no conversion", "Imagen .ffpkg": ".ffpkg image", "Recomendada": "Recommended", "Imagen .ffpfsc": ".ffpfsc image", "Ocupa menos": "Smaller", "Experimental": "Experimental", "LZ4 + .ffpkg": "LZ4 + .ffpkg", "El contenido se descomprime y se deja como una carpeta normal. Es lo de siempre: no se convierte nada y no ocupa sitio en el PC.": "The content is extracted and left as a normal folder. The usual way: nothing is converted and nothing takes up room on the PC.", "Imagen .ffpkg: un único archivo que ShadowMountPlus monta a velocidad completa. Es el formato que recomienda ShadowMountPlus. Viaja más rápido por FTP que miles de archivos pequeños y no tiene el problema de permisos.": ".ffpkg image: a single file ShadowMountPlus mounts at full speed. It's the format ShadowMountPlus recommends. It travels faster over FTP than thousands of small files and has no permissions problem.", "Imagen comprimida .ffpfsc: un contenedor que guarda dentro una imagen .ffpkg comprimida. Ocupa un 30-60 % menos en la consola, pero se lee más despacio (150-250 MB/s) y algunos títulos pueden dar tirones.": "Compressed .ffpfsc image: a container holding a compressed .ffpkg image inside. 30-60% smaller on the console, but read more slowly (150-250 MB/s) and some titles may stutter.", "LZ4 (carpeta): empaqueta los datos para que ocupen menos y carguen antes. Solo sirve para títulos AMPR; si no lo es, se manda como carpeta normal. Experimental: con algunos títulos se cuelga al guardar partida.": "LZ4 (folder): packs the data so it takes less room and loads faster. Only for AMPR titles; otherwise it's sent as a normal folder. Experimental: some titles crash when saving.", "LZ4 dentro de una imagen .ffpkg. Solo títulos AMPR; si no lo es, se manda como imagen .ffpkg normal. Experimental: con algunos títulos se cuelga al guardar partida.": "LZ4 inside an .ffpkg image. AMPR titles only; otherwise it's sent as a plain .ffpkg image. Experimental: some titles crash when saving.", "Se convierte en tu PC antes de enviarlo: mientras tanto ocupa espacio en el disco del PC (más o menos lo que ocupa descomprimido). Al terminar se borra.": "It's converted on your PC before sending: meanwhile it takes up room on the PC's disk (about its extracted size). It's deleted when done.", "Libre ahora en {0}: {1}": "Free now in {0}: {1}", "Antes de convertir": "Before converting", "Los enlaces se descargan primero al PC.": "Links are downloaded to the PC first.", "La conversión necesita sitio libre en el disco del PC (más o menos lo que ocupa el contenido descomprimido).": "The conversion needs free room on the PC's disk (about the content's extracted size).", "Al terminar de enviarse, se borra todo del PC.": "Once it's sent, everything is deleted from the PC.", "No volver a mostrar": "Don't show again", "Mejor carpeta normal": "Use normal folder instead", "Entendido": "Got it", "Con este formato, PS5 Directo prepara el contenido": "With this format, PS5 Directo prepares the content", "en tu PC": "on your PC", "antes de enviarlo, con PS5 Dump Forge. Hasta ahora todo pasaba directo a la consola sin ocupar sitio en el PC; con este formato no:": "before sending it, with PS5 Dump Forge. Until now everything went straight to the console without taking room on the PC; with this format it doesn't:"});Object.assign(I18N_EXACT,{"Windows tiene tu red marcada como «Pública»: así bloquea que el móvil entre.": "Windows has your network marked as \"Public\": that blocks your phone from connecting.", "Falta el permiso del Firewall de Windows para que el móvil pueda entrar.": "Windows FiLine truncated
|
||||
Object.assign(I18N_EXACT,{"Sin convertir": "No conversion", "tal cual": "as is", "Por defecto": "Default", "Por defecto.": "Default.", "Recomendada.": "Recommended.", "Ocupa menos.": "Smaller.", "Experimental.": "Experimental.", "Imagen": "Image", "Imagen comprimida": "Compressed image", "LZ4 en imagen": "LZ4 in an image", "carpeta": "folder", "Opcional. Si no sabes cuál elegir, deja «Sin convertir».": "Optional. If you're not sure, leave \"No conversion\".", "Mejor sin convertir": "Don't convert", "Sin convertir (tal cual)": "No conversion (as is)", "Se deja tal como viene: si es un comprimido se descomprime, y si ya es una imagen (.ffpkg, .exfat, .ffpfsc) o viene empaquetado en LZ4, se envía sin tocar. No ocupa sitio en el PC.": "Left as it comes: archives are extracted, and if it's already an image (.ffpkg, .exfat, .ffpfsc) or LZ4-packed, it's sent untouched. Takes no room on the PC.", "Imagen .ffpkg (UFS2): ojo, no es un paquete .pkg/.fpkg de instalar; es una imagen de disco que ShadowMountPlus monta a velocidad completa. Viaja más rápido por FTP que miles de archivos pequeños y no tiene el problema de permisos.": ".ffpkg image (UFS2): note it isn't an installable .pkg/.fpkg package; it's a disk image ShadowMountPlus mounts at full speed. It travels faster over FTP than thousands of small files and has no permissions problem.", "Imagen comprimida .ffpfsc: un contenedor que guarda dentro una imagen .ffpkg comprimida. Ocupa un 30-60 % menos en la consola, pero se lee más despacio (150-250 MB/s) y algunos títulos pueden dar tirones.": "Compressed .ffpfsc image: a container holding a compressed .ffpkg image inside. 30-60% smaller on the console, but read more slowly (150-250 MB/s) and some titles may stutter.", "LZ4 (carpeta): empaqueta los datos para que ocupen menos y carguen antes. Solo sirve para títulos AMPR; si no lo es, se manda sin convertir. Con algunos títulos se cuelga al guardar partida.": "LZ4 (folder): packs the data so it takes less room and loads faster. Only for AMPR titles; otherwise it's sent without conversion. Some titles crash when saving.", "LZ4 dentro de una imagen .ffpkg. Solo títulos AMPR; si no lo es, se manda como imagen .ffpkg normal. Con algunos títulos se cuelga al guardar partida.": "LZ4 inside an .ffpkg image. AMPR titles only; otherwise it's sent as a plain .ffpkg image. Some titles crash when saving."});
|
||||
@@ -1033,10 +1037,12 @@ Object.assign(I18N_EXACT,{"Ver actualizaciones": "See updates", "Abre en el nave
|
||||
Object.assign(I18N_EXACT,{"Instalado en la consola": "Installed on the console", "instalado": "installed", "Instalado por la consola: se desinstala desde su menú, no desde aquí.": "Installed by the console: uninstall it from its menu, not from here.", "Se gestiona desde la consola": "Managed from the console"});
|
||||
Object.assign(I18N_EXACT,{"PS5 Game Updater no está bien colocado:": "PS5 Game Updater isn't in the right place:", "Colocarlo bien": "Put it in place", "PS5 Game Updater está abierto en la consola: busca e instala las actualizaciones oficiales de lo instalado.": "PS5 Game Updater is open on the console: it finds and installs official updates for what's installed.", "Tienes PS5 Game Updater en la consola. Ábrelo allí para poder usarlo desde aquí.": "You have PS5 Game Updater on the console. Open it there to use it from here.", "Abrir Game Updater": "Open Game Updater", "Colocado en /data/homebrew/PPSA99440": "Placed in /data/homebrew/PPSA99440", "Abre PS5 Game Updater en la consola y vuelve a probar.": "Open PS5 Game Updater on the console and try again."});
|
||||
I18N_RX.push(['^Está en ([\\s\\S]+?), pero tiene que estar en ([\\s\\S]+?) para que funcione\\.$','It is in {0}, but it has to be in {1} to work.'],['^La carpeta ([\\s\\S]+?) no tiene sce_sys: está incompleta\\.$','The folder {0} has no sce_sys: it is incomplete.']);
|
||||
Object.assign(I18N_EXACT,{"Copia de seguridad": "Backup", "Capturas": "Captures", "En la consola": "On the console", "En tu PC": "On your PC", "Abrir carpeta": "Open folder", "Hacer copia ahora": "Back up now", "Guarda en tu PC lo importante de la consola por si algo falla (un cuelgue, un reinicio o tener que volver a empezar): partidas guardadas, ajustes de ShadowMountPlus y de los payloads, y la base de datos de aplicaciones. Las partidas se guardan tal cual, cifradas: solo se pueden restaurar en esta misma consola y con el mismo usuario. Lo que hay en /data/homebrew no hace falta copiarlo: no se pierde.": "Saves what matters on the console to your PC in case something goes wrong (a crash, a reboot or having to start over): saved data, ShadowMountPlus and payload settings, and the app database. Saved data is stored as is, encrypted: it can only be restored to this same console and user. What's in /data/homebrew doesn't need backing up: it isn't lost.", "Partidas guardadas (PS5 y PS4)": "Saved data (PS5 and PS4)", "Ajustes de ShadowMountPlus y de los payloads": "ShadowMountPlus and payload settings", "Base de datos de aplicaciones (solo se copia, no se restaura)": "App database (backed up only, never restored)", "Hacer una copia sola cada día (si la consola está encendida)": "Back up automatically every day (if the console is on)", "Guardar las últimas": "Keep the last", "copias": "backups", "Cancelar": "Cancel", "Fecha": "Date", "Contenido": "Contents", "Tamaño": "Size", "Capturas y vídeos": "Captures and videos", "Seleccionar todo": "Select all", "Bajar al PC": "Download to PC", "Actualizar": "Refresh", "Las capturas de pantalla y los vídeos guardados en la consola. Pulsa una para verla grande; marca las que quieras y «Bajar al PC» las guarda en Descargas\\PS5 Directo.": "Screenshots and videos saved on the console. Click one to see it full size; tick the ones you want and \"Download to PC\" saves them to Downloads\\PS5 Directo.", "Añadir carpeta…": "Add folder…", "Volver a leer": "Read again", "Elige las carpetas de tu PC o de tu NAS donde guardas contenido para la consola (también valen rutas de red como \\\\NAS\\carpeta). Te marca lo que ya está en la consola y lo que no, y lo envías con un clic (con el formato y el destino elegidos en Transferencias).": "Choose the folders on your PC or NAS where you keep content for the console (network paths like \\\\NAS\\folder work too). It marks what's already on the console and what isn't, and you send it with one click (with the format and destination chosen in Transfers).", "Diagnóstico": "Diagnosis", "Cerrar": "Close", "Últimas líneas del registro": "Last log lines", "El informe es un .zip con el registro, la versión y los ajustes (sin contraseñas). Pásaselo a quien te ayude.": "The report is a .zip with the log, version and settings (no passwords). Send it to whoever is helping you.", "Copiar resumen": "Copy summary", "Guardar informe (.zip)": "Save report (.zip)", "Bienvenido a PS5 Directo": "Welcome to PS5 Directo", "Un gestor de archivos para tu PS5 que descarga, convierte y envía en un solo paso.": "A file manager for your PS5 that downloads, converts and sends in a single step.", "⚡ Descarga y envía a la vez": "⚡ Downloads and sends at once", "Pegas un enlace y va directo a la consola mientras se descarga, sin llenar el disco del PC.": "Paste a link and it goes straight to the console while it downloads, without filling your PC's disk.", "🔄 Convierte por el camino": "🔄 Converts on the way", "Carpeta, .exfat, LZ4, .ffpfsc… dentro del mismo trabajo, sin usar otro programa.": "Folder, .exfat, LZ4, .ffpfsc… within the same job, no other program needed.", "📦 Descomprime y coloca": "📦 Extracts and places", "RAR, 7z, ZIP y torrents, también por partes y con contraseña. Lo deja listo en /data/homebrew.": "RAR, 7z, ZIP and torrents, also split and password-protected. Leaves it ready in /data/homebrew.", "📚 Biblioteca": "📚 Library", "Lo que hay en la consola con portadas, versión y firmware, lo instalado de PS4 y PS5, y lo que tienes en el PC.": "What's on the console with covers, version and firmware, installed PS4 and PS5 content, and what you have on your PC.", "💾 Copias de seguridad": "💾 Backups", "Tus partidas y ajustes de la consola guardados en el PC por si algo falla.": "Your saved data and console settings kept on your PC in case something goes wrong.", "📱 Móvil y navegador": "📱 Phone and browser", "Contrólalo desde el móvil con un QR y manda enlaces desde el navegador sin copiar y pegar.": "Control it from your phone with a QR code and send links from your browser without copy and paste.", "No es solo un programa para subir archivos:": "It's not just an upload tool:", "une descarga, conversión, envío y gestión de la consola en uno. Tampoco instala ni cambia nada del sistema: solo mueve los archivos que tú eliges, y cada uno lo usa bajo su responsabilidad.": "it combines downloadinLine truncated
|
||||
Object.assign(I18N_EXACT,{"Copia de seguridad": "Backup", "Capturas": "Captures", "En la consola": "On the console", "En tu PC": "On your PC", "Abrir carpeta": "Open folder", "Hacer copia ahora": "Back up now", "Guarda en tu PC lo importante de la consola por si algo falla (un cuelgue, un reinicio o tener que volver a empezar): partidas guardadas, ajustes de ShadowMountPlus y de los payloads, y la base de datos de aplicaciones. Las partidas se guardan tal cual, cifradas: solo se pueden restaurar en esta misma consola y con el mismo usuario. Lo que hay en /data/homebrew no hace falta copiarlo: no se pierde.": "Saves what matters on the console to your PC in case something goes wrong (a crash, a reboot or having to start over): saved data, ShadowMountPlus and payload settings, and the app database. Saved data is stored as is, encrypted: it can only be restored to this same console and user. What's in /data/homebrew doesn't need backing up: it isn't lost.", "Partidas guardadas (PS5 y PS4)": "Saved data (PS5 and PS4)", "Ajustes de ShadowMountPlus y de los payloads": "ShadowMountPlus and payload settings", "Base de datos de aplicaciones (solo se copia, no se restaura)": "App database (backed up only, never restored)", "Hacer una copia sola cada día (si la consola está encendida)": "Back up automatically every day (if the console is on)", "Guardar las últimas": "Keep the last", "copias": "backups", "Cancelar": "Cancel", "Fecha": "Date", "Contenido": "Contents", "Tamaño": "Size", "Capturas y vídeos": "Captures and videos", "Seleccionar todo": "Select all", "Bajar al PC": "Download to PC", "Actualizar": "Refresh", "Las capturas de pantalla y los vídeos guardados en la consola. Pulsa una para verla grande; marca las que quieras y «Bajar al PC» las guarda en Descargas › PS5 Directo.": "Screenshots and videos saved on the console. Click one to see it full size; tick the ones you want and \"Download to PC\" saves them to Downloads › PS5 Directo.", "Añadir carpeta…": "Add folder…", "Volver a leer": "Read again", "Elige las carpetas de tu PC o de tu NAS donde guardas contenido para la consola (también valen rutas de red como \\\\NAS\\carpeta). Te marca lo que ya está en la consola y lo que no, y lo envías con un clic (con el formato y el destino elegidos en Transferencias).": "Choose the folders on your PC or NAS where you keep content for the console (network paths like \\\\NAS\\folder work too). It marks what's already on the console and what isn't, and you send it with one click (with the format and destination chosen in Transfers).", "Diagnóstico": "Diagnosis", "Cerrar": "Close", "Últimas líneas del registro": "Last log lines", "El informe es un .zip con el registro, la versión y los ajustes (sin contraseñas). Pásaselo a quien te ayude.": "The report is a .zip with the log, version and settings (no passwords). Send it to whoever is helping you.", "Copiar resumen": "Copy summary", "Guardar informe (.zip)": "Save report (.zip)", "Bienvenido a PS5 Directo": "Welcome to PS5 Directo", "Un gestor de archivos para tu PS5 que descarga, convierte y envía en un solo paso.": "A file manager for your PS5 that downloads, converts and sends in a single step.", "⚡ Descarga y envía a la vez": "⚡ Downloads and sends at once", "Pegas un enlace y va directo a la consola mientras se descarga, sin llenar el disco del PC.": "Paste a link and it goes straight to the console while it downloads, without filling your PC's disk.", "🔄 Convierte por el camino": "🔄 Converts on the way", "Carpeta, .exfat, LZ4, .ffpfsc… dentro del mismo trabajo, sin usar otro programa.": "Folder, .exfat, LZ4, .ffpfsc… within the same job, no other program needed.", "📦 Descomprime y coloca": "📦 Extracts and places", "RAR, 7z, ZIP y torrents, también por partes y con contraseña. Lo deja listo en /data/homebrew.": "RAR, 7z, ZIP and torrents, also split and password-protected. Leaves it ready in /data/homebrew.", "📚 Biblioteca": "📚 Library", "Lo que hay en la consola con portadas, versión y firmware, lo instalado de PS4 y PS5, y lo que tienes en el PC.": "What's on the console with covers, version and firmware, installed PS4 and PS5 content, and what you have on your PC.", "💾 Copias de seguridad": "💾 Backups", "Tus partidas y ajustes de la consola guardados en el PC por si algo falla.": "Your saved data and console settings kept on your PC in case something goes wrong.", "📱 Móvil y navegador": "📱 Phone and browser", "Contrólalo desde el móvil con un QR y manda enlaces desde el navegador sin copiar y pegar.": "Control it from your phone with a QR code and send links from your browser without copy and paste.", "No es solo un programa para subir archivos:": "It's not just an upload tool:", "une descarga, conversión, envío y gestión de la consola en uno. Tampoco instala ni cambia nada del sistema: solo mueve los archivos que tú eliges, y cada uno lo usa bajo su responsabilidad.": "it combines downLine truncated
|
||||
Object.assign(I18N_EXACT,{"Todo lo que tienes en la consola con su portada, versión y formato, también lo instalado de PS4 y PS5. Te avisa de lo que no arranca en tu firmware y de lo repetido. En «En tu PC» eliges tus carpetas del PC o del NAS y ves lo que falta en la consola para enviarlo con un clic.": "Everything on the console with cover, version and format, including installed PS4 and PS5 content. It flags what won't launch on your firmware and duplicates. In \"On your PC\" you pick your PC or NAS folders and see what's missing on the console to send it with one click.", "Guarda en el PC tus partidas y los ajustes de la consola por si algo falla, a mano o una vez al día, y restaura las partidas cuando lo necesites.": "Saves your saved data and console settings to the PC in case something goes wrong, manually or once a day, and restores saved data when you need it.", "Mira las capturas y los vídeos de la consola y bájalos al PC.": "Browse the console's screenshots and videos and download them to the PC.", "Si una transferencia da error, pulsa «Diagnóstico» en ella: te dice qué ha pasado y cómo arreglarlo, y guarda un informe para pasar a quien te ayude. En Ajustes, «Revisión completa» revisa la conexión.": "If a transfer fails, click \"Diagnosis\" on it: it tells you what happened and how to fix it, and saves a report to send to whoever helps you. In Settings, \"Full check\" checks the connection."});
|
||||
Object.assign(I18N_EXACT,{"Comprobar arranque": "Launch check", "Comprobar": "Check", "Si no abre o se cierra al arrancar: revisa la carpeta y busca registros y capturas": "If it won't open or closes on launch: checks the folder and looks for logs and captures", "Revisa la carpeta en la consola y busca registros y capturas de los últimos minutos. Truco: si al abrirla sale un error en la tele, pulsa el botón de captura del mando antes de cerrarlo; la captura aparecerá aquí.": "Checks the folder on the console and looks for logs and captures from the last few minutes. Tip: if an error shows on the TV when opening it, press the controller's capture button before closing it; the capture will show up here.", "Código de error que salió en la tele (si lo hubo)": "Error code shown on the TV (if any)", "Volver a comprobar": "Check again", "Revisando…": "Checking…", "No encuentro nada raro en la carpeta.": "Nothing odd found in the folder.", "Últimas capturas de la consola (por si salió el error en pantalla):": "Latest console captures (in case the error was on screen):", "Registro reciente": "Recent log", "No hay registros (.log) de los últimos 30 minutos en la consola. Si acabas de probar a abrirla, es que no ha dejado ninguno.": "No logs (.log) from the last 30 minutes on the console. If you just tried to open it, it didn't leave any.", "No conozco ese código. Guárdalo en el informe para que lo vea quien te ayude.": "I don't know that code. Save it in the report so whoever helps you can see it.", "Tiene eboot.bin (el ejecutable principal).": "Has eboot.bin (the main executable).", "eboot.bin no tiene permiso de ejecución.": "eboot.bin isn't executable.", "Pulsa «Permisos» en la tarjeta (pone 777).": "Click \"Permissions\" on the card (sets 777).", "Falta eboot.bin: la aplicación no puede arrancar.": "eboot.bin is missing: the app can't start.", "Vuelve a enviarla: está incompleta.": "Send it again: it's incomplete.", "Tiene sce_sys/param.json.": "Has sce_sys/param.json.", "Falta sce_sys/param.json: la consola no sabe qué es.": "sce_sys/param.json is missing: the console doesn't know what it is.", "Falta la portada (icon0.png). No impide arrancar.": "The cover (icon0.png) is missing. It doesn't stop it starting.", "Necesitas una versión con backport para tu firmware.": "You need a version with a backport for your firmware.", "Lleva backport (carpeta fakelib).": "It has a backport (fakelib folder).", "Está empaquetada en LZ4 pero falta ampr_assets.index.": "It's LZ4-packed but ampr_assets.index is missing.", "Vuelve a convertirla o envíala sin convertir.": "Convert it again or send it unconverted.", "LZ4: falta ampr_emu.index.": "LZ4: ampr_emu.index is missing.", "Si no arranca, vuelve a convertirla o envíala sin convertir.": "If it won't start, convert it again or send it unconverted.", "LZ4: no lleva perfil .runtime (usa los valores por defecto). Si se cierra al arrancar, es una causa habitual.": "LZ4: no .runtime profile (uses defaults). If it closes on launch, this is a common cause.", "Prueba a enviarla sin convertir, como carpeta o .exfat.": "Try sending it unconverted, as a folder or .exfat.", "Vuelve a enviarla.": "Send it again.", "LZ4: lleva el emulador AMPR en su fakelib.": "LZ4: the AMPR emulator is in its fakelib.", "LZ4: no encuentro el emulador AMPR (libSceAmpr) en fakelib.": "LZ4: can't find the AMPR emulator (libSceAmpr) in fakelib.", "Sin él los datos empaquetados no se pueden leer. Vuelve a convertirla.": "Without it the packed data can't be read. Convert it again.", "Si se cierra al arrancar, prueba a apartar esa carpeta.": "If it closes on launch, try moving that folder aside.", "Deja solo una.": "Keep only one.", "Si no aparece en el menú, prueba a enviarla como carpeta o .exfat.": "If it doesn't show in the menu, try sending it as a folder or .exfat."});
|
||||
I18N_RX.push(['^Pide firmware ([\\d.]+) y tu PS5 tiene ([\\d.]+)\\.$','Needs firmware {0} and your PS5 has {1}.'],['^Firmware: pide ([\\d.]+) y tu PS5 tiene ([\\d.]+)\\.$','Firmware: needs {0} and your PS5 has {1}.'],['^LZ4: tiene el índice de paquetes \\((\\d+) archivos de datos\\)\\.$','LZ4: has the pack index ({0} data files).']);
|
||||
Object.assign(I18N_EXACT,{"Esa copia ya no está en la carpeta de copias (¿la moviste o borraste?). Pulsa Actualizar en la lista de copias.": "That backup is no longer in the backups folder (did you move or delete it?). Click Refresh in the backups list."});
|
||||
Object.assign(I18N_EXACT,{"🍎 Versión de Mac en pruebas:": "🍎 Mac version in testing:", "todavía no se ha podido probar en un Mac de verdad. Si algo falla, pulsa «Crear informe de fallo» y adjúntalo en un aviso en GitHub (Issues): con eso se arregla rápido. ¡Gracias por probarla!": "it hasn't been tested on a real Mac yet. If something fails, click \"Create problem report\" and attach it to an issue on GitHub: that makes it quick to fix. Thanks for trying it!", "Para bajar torrents hace falta aria2. Instálalo con Homebrew (brew.sh): brew install aria2": "Torrents need aria2. Install it with Homebrew (brew.sh): brew install aria2"});
|
||||
// --- idioma: español (original) o inglés. Traduce el texto de la página y los mensajes del programa.
|
||||
const LANG=(()=>{try{const v=localStorage.getItem('ps5d_lang');if(v==='es'||v==='en')return v}catch(e){}
|
||||
return (navigator.language||'es').toLowerCase().startsWith('es')?'es':'en'})();
|
||||
@@ -1309,7 +1315,7 @@ async function checkConn(){try{paintConn(await api('/api/test',{}))}catch(e){}}
|
||||
let refreshFails=0, refreshing=false;
|
||||
async function refresh(){
|
||||
if(refreshing) return; refreshing=true;
|
||||
try{const s=await api('/api/state');refreshFails=0;state=s;paintBell(s.unread||0);if(s.version){$('ver').textContent='v'+s.version;window.titleBase='PS5 Directo v'+s.version;if(!window.titleTimer)document.title=window.titleBase}if(s.inbox>0)takeInbox();if(!$('dest').dataset.touched)$('dest').value=s.settings.dest||'/data/homebrew';if(!$('where').dataset.touched)$('where').value=s.settings.extract_where==='pc'?'pc':'ps5';if(!$('fmt').dataset.touched){const f=s.settings.pc_format;$('fmt').value=(f&&f!=='folder'&&($('where').value==='pc'||f==='plain'))?f:'';fmtNote()}render();renderSched();applyRemote();paintFw()}
|
||||
try{const s=await api('/api/state');refreshFails=0;state=s;paintBell(s.unread||0);if(s.os&&s.os!=='nt')document.body.classList.add('notwin');if(s.version){$('ver').textContent='v'+s.version;window.titleBase='PS5 Directo v'+s.version;if(!window.titleTimer)document.title=window.titleBase}if(s.inbox>0)takeInbox();if(!$('dest').dataset.touched)$('dest').value=s.settings.dest||'/data/homebrew';if(!$('where').dataset.touched)$('where').value=s.settings.extract_where==='pc'?'pc':'ps5';if(!$('fmt').dataset.touched){const f=s.settings.pc_format;$('fmt').value=(f&&f!=='folder'&&($('where').value==='pc'||f==='plain'))?f:'';fmtNote()}render();renderSched();applyRemote();paintFw()}
|
||||
catch(e){ if(++refreshFails>=5) $('jobs').innerHTML=`<div class="empty">No conecto con el programa. Cierra esta ventana y abre PS5 Directo otra vez. (${esc(e.message)})</div>`}
|
||||
finally{ refreshing=false }
|
||||
fmAfterUploads();
|
||||
@@ -1665,7 +1671,7 @@ function afterUpdate(v){
|
||||
|
||||
// ===================== bienvenida =====================
|
||||
let welcomeNext=null;
|
||||
function showWelcome(next){ welcomeNext=next||null; $('dlgWelcome').showModal(); }
|
||||
function showWelcome(next){ welcomeNext=next||null; document.querySelectorAll('.macbeta').forEach(e=>e.hidden=(state.platform!=='darwin')); $('dlgWelcome').showModal(); }
|
||||
function welcomeDone(){ $('dlgWelcome').close(); api('/api/settings',{welcome_seen:true}).catch(()=>{}); if(state.settings) state.settings.welcome_seen=true; const n=welcomeNext; welcomeNext=null; if(n) n(); }
|
||||
$('wlOk').onclick=welcomeDone;
|
||||
$('wlTour').onclick=()=>{ welcomeNext=null; welcomeDone(); startTour(); };
|
||||
@@ -1745,20 +1751,24 @@ async function mdLoad(){
|
||||
md.loaded=true; $('mdSum').textContent=tr('Leyendo la consola…'); $('mdGrid').innerHTML='';
|
||||
try{const r=await api('/api/media/list',{}); if(!r.ok) throw new Error(r.error); md.items=r.items; md.sel.clear(); mdPaint()}catch(e){$('mdSum').textContent=e.message}
|
||||
}
|
||||
function mdWhen(w){ try{ return new Date(w).toLocaleString(LANG==='en'?'en-GB':'es-ES',{dateStyle:'short',timeStyle:'short'}) }catch(e){ return w } }
|
||||
function mdPaint(){
|
||||
const n=md.items.length, v=md.items.filter(x=>x.video).length;
|
||||
$('mdSum').textContent=n?tr('{0} capturas y {1} vídeos').replace('{0}',n-v).replace('{1}',v)+' · '+human(md.items.reduce((a,x)=>a+(x.size||0),0)):tr('No hay capturas ni vídeos en la consola.');
|
||||
$('mdGrid').innerHTML=md.items.map((x,i)=>{const u=`/api/media/file?p=${encodeURIComponent(x.path)}&t=${encodeURIComponent(TOKEN)}`;
|
||||
return `<div class="mdt ${md.sel.has(i)?'on':''}" data-i="${i}" title="${esc(x.name)} · ${human(x.size)}">${x.video?'<span class="vd">▶</span>':`<img loading="lazy" src="${u}" alt="">`}<input type="checkbox" ${md.sel.has(i)?'checked':''}><span class="nm" data-noi18n>${esc(x.folder)} · ${esc(x.name)}</span></div>`}).join('');
|
||||
$('mdGrid').innerHTML=md.items.map((x,i)=>{const u=`/api/media/file?p=${encodeURIComponent(x.path)}&t=${encodeURIComponent(TOKEN)}&v=1`;
|
||||
const cov=x.tid?`<img class="cv" loading="lazy" src="/api/cover?p=${encodeURIComponent('/user/appmeta/'+x.tid)}&t=${encodeURIComponent(TOKEN)}" alt="" onerror="this.remove()">`:'';
|
||||
const pic=x.video?cov+'<span class="vd">▶</span>':`<img loading="lazy" src="${u}" alt="" onerror="this.replaceWith(document.createTextNode('🖼'))">`;
|
||||
const label=(x.app||x.tid||x.folder)+(x.when?' · '+mdWhen(x.when):'');
|
||||
return `<div class="mdt ${md.sel.has(i)?'on':''}" data-i="${i}" title="${esc(x.name)} · ${human(x.size)}">${pic}${x.hdr?'<span class="hb">HDR</span>':''}<input type="checkbox" ${md.sel.has(i)?'checked':''}><span class="nm" data-noi18n>${esc(label)}</span></div>`}).join('');
|
||||
$('mdDown').disabled=!md.sel.size; $('mdDown').textContent=md.sel.size?tr('Bajar al PC ({0})').replace('{0}',md.sel.size):tr('Bajar al PC');
|
||||
}
|
||||
$('mdGrid').addEventListener('click',e=>{const t=e.target.closest('.mdt'); if(!t) return; const i=+t.dataset.i;
|
||||
if(e.target.tagName==='INPUT'){ e.target.checked?md.sel.add(i):md.sel.delete(i); t.classList.toggle('on',e.target.checked); $('mdDown').disabled=!md.sel.size; $('mdDown').textContent=md.sel.size?tr('Bajar al PC ({0})').replace('{0}',md.sel.size):tr('Bajar al PC'); return }
|
||||
window.open(`/api/media/file?p=${encodeURIComponent(md.items[i].path)}&t=${encodeURIComponent(TOKEN)}`,'_blank','noopener');});
|
||||
window.open(`/api/media/file?p=${encodeURIComponent(md.items[i].path)}&t=${encodeURIComponent(TOKEN)}&v=1`,'_blank','noopener');});
|
||||
$('mdSel').onclick=()=>{ if(md.sel.size===md.items.length) md.sel.clear(); else md.items.forEach((_,i)=>md.sel.add(i)); mdPaint(); };
|
||||
$('mdLoad').onclick=mdLoad;
|
||||
$('mdDown').onclick=async()=>{ const paths=[...md.sel].map(i=>md.items[i].path);
|
||||
try{const r=await api('/api/fm/download',{paths}); if(!r.ok) throw new Error(r.error); toast(tr('Bajando {0} archivos a Descargas\\PS5 Directo. El progreso sale en «Archivos de la consola».').replace('{0}',paths.length))}catch(e){toast(e.message)} };
|
||||
try{const r=await api('/api/fm/download',{paths}); if(!r.ok) throw new Error(r.error); toast(tr('Bajando {0} archivos a Descargas › PS5 Directo. El progreso sale en «Archivos de la consola».').replace('{0}',paths.length))}catch(e){toast(e.message)} };
|
||||
|
||||
// ===================== biblioteca: en tu PC =====================
|
||||
let col={roots:[],items:null,poll:null};
|
||||
@@ -1818,7 +1828,7 @@ async function lcRun(){
|
||||
const ic={ok:'✔',warn:'!',bad:'✖',info:'i'};
|
||||
$('lcList').innerHTML=r.checks.map(c=>`<div class="lcrow ${c.level}"><i>${ic[c.level]}</i><div>${esc(tr(c.text))}${c.fix?`<div class="fx">${esc(tr(c.fix))}</div>`:''}</div></div>`).join('')||`<div class="hint">${esc(tr('No encuentro nada raro en la carpeta.'))}</div>`;
|
||||
const caps=r.captures||[];
|
||||
$('lcCaps').innerHTML=caps.length?`<div class="hint" style="width:100%">${esc(tr('Últimas capturas de la consola (por si salió el error en pantalla):'))}</div>`+caps.map(c=>{const u=`/api/media/file?p=${encodeURIComponent(c.path)}&t=${encodeURIComponent(TOKEN)}`;return `<a href="${u}" target="_blank" rel="noopener" title="${esc(c.name)}">${c.video?'▶':`<img src="${u}" alt="">`}</a>`}).join(''):'';
|
||||
$('lcCaps').innerHTML=caps.length?`<div class="hint" style="width:100%">${esc(tr('Últimas capturas de la consola (por si salió el error en pantalla):'))}</div>`+caps.map(c=>{const u=`/api/media/file?p=${encodeURIComponent(c.path)}&t=${encodeURIComponent(TOKEN)}&v=1`;return `<a href="${u}" target="_blank" rel="noopener" title="${esc(c.name)}">${c.video?'▶':`<img src="${u}" alt="">`}</a>`}).join(''):'';
|
||||
$('lcLogs').innerHTML=(r.logs||[]).length?r.logs.map(l=>`<details><summary>${esc(tr('Registro reciente'))}: <span class="mono" data-noi18n>${esc(l.path)}</span></summary><pre class="log" data-noi18n>${esc(l.tail)}</pre></details>`).join(''):`<div class="hint">${esc(tr('No hay registros (.log) de los últimos 30 minutos en la consola. Si acabas de probar a abrirla, es que no ha dejado ninguno.'))}</div>`;
|
||||
}
|
||||
$('lcClose').onclick=()=>$('dlgLaunch').close();
|
||||
@@ -1834,14 +1844,17 @@ let curTab='Transfers';
|
||||
function showTab(t){
|
||||
curTab=t;
|
||||
['Transfers','Library','Files','Space','Backup','Media','Activity'].forEach(x=>{$('tab'+x).hidden=(x!==t)});
|
||||
if(t==='Backup') bkEnter();
|
||||
if(t==='Media') mdEnter();
|
||||
if(t==='Library') libEnter();
|
||||
document.querySelectorAll('#tabs button').forEach(b=>b.classList.toggle('on',b.dataset.tab===t));
|
||||
try{localStorage.setItem('ps5d_tab',t)}catch(e){}
|
||||
if(t==='Files'&&!fm.loaded) fmGo(fm.path,true);
|
||||
if(t==='Activity') loadActivity();
|
||||
if(t==='Space') spEnter();
|
||||
// lo que carga cada pestaña: si algo falla, la pestaña ya está bien cambiada
|
||||
try{
|
||||
if(t==='Backup') bkEnter();
|
||||
if(t==='Media') mdEnter();
|
||||
if(t==='Library') libEnter();
|
||||
if(t==='Files'&&!fm.loaded) fmGo(fm.path,true);
|
||||
if(t==='Activity') loadActivity();
|
||||
if(t==='Space') spEnter();
|
||||
}catch(e){ console.error('Pestaña '+t+':', e) }
|
||||
}
|
||||
document.querySelectorAll('#tabs button').forEach(b=>b.onclick=()=>showTab(b.dataset.tab));
|
||||
|
||||
@@ -2414,7 +2427,9 @@ $('actBody').addEventListener('click',async e=>{const b=e.target.closest('button
|
||||
if(!confirm(tr('¿Empezar de nuevo desde cero? Se vuelve a descargar y enviar, como si pegaras el enlace otra vez.'))) return;
|
||||
try{await api('/api/history/redo',{id:b.dataset.redo}); toast(tr('Añadido a la cola')); showTab('Transfers'); refresh()}catch(err){toast(err.message)}});
|
||||
$('actClear').onclick=async()=>{if(!confirm(tr('¿Borrar todo el historial?')))return; await api('/api/history/clear',{}); loadActivity()};
|
||||
try{const t=localStorage.getItem('ps5d_tab'); if(t&&t!=='Transfers'&&$('tab'+t)) showTab(t)}catch(e){}
|
||||
// vuelve a la última pestaña cuando ya está todo el programa cargado (antes la Biblioteca fallaba al
|
||||
// abrir y se quedaba marcada «Transferencias» con el contenido de la Biblioteca)
|
||||
setTimeout(()=>{try{const t=localStorage.getItem('ps5d_tab'); if(t&&t!=='Transfers'&&$('tab'+t)) showTab(t)}catch(e){console.error(e)}},0);
|
||||
</script>
|
||||
<script>
|
||||
// ===================== 1.20: formato, programar, payloads, diagnóstico, móvil =====================
|
||||
|
||||
Reference in new issue
Block a user