From 05b5a5125476858b891331c4478fe394b443f11a Mon Sep 17 00:00:00 2001 From: Danila Kamaev Date: Mon, 1 Jun 2026 18:08:19 +0400 Subject: [PATCH] feat: support ps4 fPKG thumbnails --- copyparty/__main__.py | 6 +++--- copyparty/mtag.py | 44 +++++++++++++++++++++++++++++++++++++++++++ copyparty/up2k.py | 2 +- 3 files changed, 48 insertions(+), 4 deletions(-) diff --git a/copyparty/__main__.py b/copyparty/__main__.py index 60bd185e..41741dea 100644 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -1777,14 +1777,14 @@ def add_thumbnail(ap): # https://github.com/libvips/libvips # https://stackoverflow.com/a/47612661 # ffmpeg -hide_banner -demuxers | awk '/^ D /{print$2}' | while IFS= read -r x; do ffmpeg -hide_banner -h demuxer=$x; done | grep -E '^Demuxer |extensions:' - ap2.add_argument("--th-r-pil", metavar="T,T", type=u, default="avif,avifs,blp,bmp,cbz,dcx,dds,dib,emf,eps,epub,fits,flc,fli,fpx,gif,heic,heics,heif,heifs,icns,ico,im,j2p,j2k,jp2,jpeg,jpg,jpx,jxl,pbm,pcx,pgm,png,pnm,ppm,psd,qoi,sgi,spi,tga,tif,tiff,webp,wmf,xbm,xpm", help="image formats to decode using pillow") + ap2.add_argument("--th-r-pil", metavar="T,T", type=u, default="avif,avifs,blp,bmp,cbz,dcx,dds,dib,emf,eps,epub,fits,flc,fli,fpx,gif,heic,heics,heif,heifs,icns,ico,im,j2p,j2k,jp2,jpeg,jpg,jpx,jxl,pbm,pcx,pgm,pkg,png,pnm,ppm,psd,qoi,sgi,spi,tga,tif,tiff,webp,wmf,xbm,xpm", help="image formats to decode using pillow") ap2.add_argument("--th-r-vips", metavar="T,T", type=u, default="3fr,arw,avif,cr2,cr3,crw,dcr,dng,erf,exr,fit,fits,fts,gif,hdr,heic,heics,heif,heifs,jp2,jpeg,jpg,jpx,jxl,k25,kdc,mdc,mef,mrw,nef,nii,nrw,orf,pfm,pgm,png,ppm,raf,raw,rw2,sr2,srf,srw,svg,tif,tiff,webp,x3f", help="image formats to decode using pyvips") ap2.add_argument("--th-r-raw", metavar="T,T", type=u, default="3fr,arw,cr2,cr3,crw,dcr,dng,erf,k25,kdc,mdc,mef,mos,mrw,nef,nrw,orf,pef,raf,raw,rw2,sr2,srf,srw,x3f", help="image formats to decode using rawpy (if available) or libraw's dcraw_emu") - ap2.add_argument("--th-r-ffi", metavar="T,T", type=u, default="apng,avif,avifs,bmp,cbz,dds,dib,epub,fit,fits,fts,gif,hdr,heic,heics,heif,heifs,icns,ico,jp2,jpeg,jpg,jpx,jxl,pbm,pcx,pfm,pgm,png,pnm,ppm,psd,qoi,sgi,tga,tif,tiff,webp,xbm,xpm", help="image formats to decode using ffmpeg") + ap2.add_argument("--th-r-ffi", metavar="T,T", type=u, default="apng,avif,avifs,bmp,cbz,dds,dib,epub,fit,fits,fts,gif,hdr,heic,heics,heif,heifs,icns,ico,jp2,jpeg,jpg,jpx,jxl,pbm,pcx,pfm,pgm,pkg,png,pnm,ppm,psd,qoi,sgi,tga,tif,tiff,webp,xbm,xpm", help="image formats to decode using ffmpeg") ap2.add_argument("--th-r-ffv", metavar="T,T", type=u, default="3gp,asf,av1,avc,avi,flv,h264,h265,hevc,m4v,mjpeg,mjpg,mkv,mov,mp4,mpeg,mpeg2,mpegts,mpg,mpg2,mts,nut,ogm,ogv,rm,ts,vob,webm,wmv", help="video formats to decode using ffmpeg") ap2.add_argument("--th-r-ffa", metavar="T,T", type=u, default="aac,ac3,aif,aiff,alac,alaw,amr,apac,ape,au,bcstm,bfstm,brstm,bonk,dfpwm,dts,flac,gsm,ilbc,it,itgz,itxz,itz,m4a,m4b,m4r,mdgz,mdxz,mdz,mka,mo3,mod,mp2,mp3,mpc,mptm,mt2,mulaw,oga,ogg,okt,opus,ra,s3m,s3gz,s3xz,s3z,tak,tta,ulaw,wav,wma,wv,xm,xmgz,xmxz,xmz,xpk", help="audio formats to decode using ffmpeg") ap2.add_argument("--th-spec-cnv", metavar="T", type=u, default="it,itgz,itxz,itz,mdgz,mdxz,mdz,mo3,mod,s3m,s3gz,s3xz,s3z,xm,xmgz,xmxz,xmz,xpk", help="audio formats which provoke https://trac.ffmpeg.org/ticket/10797 (huge ram usage for s3xmodit spectrograms)") - ap2.add_argument("--au-unpk", metavar="E=F.C", type=u, default="mdz=mod.zip, mdgz=mod.gz, mdxz=mod.xz, s3z=s3m.zip, s3gz=s3m.gz, s3xz=s3m.xz, xmz=xm.zip, xmgz=xm.gz, xmxz=xm.xz, itz=it.zip, itgz=it.gz, itxz=it.xz, cbz=jpg.cbz, epub=jpg.epub", help="audio/image formats to decompress before passing to ffmpeg") + ap2.add_argument("--au-unpk", metavar="E=F.C", type=u, default="mdz=mod.zip, mdgz=mod.gz, mdxz=mod.xz, s3z=s3m.zip, s3gz=s3m.gz, s3xz=s3m.xz, xmz=xm.zip, xmgz=xm.gz, xmxz=xm.xz, itz=it.zip, itgz=it.gz, itxz=it.xz, cbz=jpg.cbz, epub=jpg.epub, pkg=png.pkg", help="audio/image formats to decompress before passing to ffmpeg") def add_transcoding(ap): diff --git a/copyparty/mtag.py b/copyparty/mtag.py index d9290783..bd05fc14 100644 --- a/copyparty/mtag.py +++ b/copyparty/mtag.py @@ -2,10 +2,12 @@ from __future__ import print_function, unicode_literals import argparse +from io import BytesIO import json import os import re import shutil +import struct import subprocess as sp import sys import tempfile @@ -192,6 +194,8 @@ def au_unpk( elif pk == "epub": fi = get_cover_from_epub(log, abspath) assert fi # !rm + elif pk == "pkg": + fi = get_cover_from_pkg(abspath) else: raise Exception("unknown compression %s" % (pk,)) @@ -472,6 +476,46 @@ def _get_cover_from_epub2( return None +def get_cover_from_pkg(abspath: str) -> IO[bytes]: + FPKG_MAGIC = 0x7F434E54 + ENTRY_ID_ICON0_PNG = 0x1200 + ENTRY_ID_PIC0_PNG = 0x1220 + # limits for sanity checks + ENTRY_COUNT_LIMIT = 255 + FILE_SIZE_LIMIT = 10_000_000 + + with open(abspath, 'rb') as f: + magic, entry_count, entry_table_position = _read_struct(f, '>I12xI4xI') + if magic != FPKG_MAGIC: + raise Exception("pkg file: not a playstation 4 fPKG: %s" % abspath) + if entry_count > ENTRY_COUNT_LIMIT: + raise Exception("pkg file: suspicious; too many entries: %s" % abspath) + + cover_offset = cover_length = None + f.seek(entry_table_position) + for _ in range(entry_count): + entry_id, offset, length = _read_struct(f, '>I12xII8x') + if entry_id not in {ENTRY_ID_ICON0_PNG, ENTRY_ID_PIC0_PNG}: + continue + cover_offset, cover_length = offset, length + # prefer icon0.png + if entry_id == ENTRY_ID_ICON0_PNG: + break + + if cover_length is None: + raise Exception("pkg file: no cover image found: %s" % abspath) + if cover_length > FILE_SIZE_LIMIT: + raise Exception("pkg file: suspicious; cover image too large: %s" % abspath) + + f.seek(cover_offset) + return BytesIO(f.read(cover_length)) + + +def _read_struct(file: IO[bytes], fmt: str) -> tuple[int, ...]: + size = struct.calcsize(fmt) + return struct.unpack(fmt, file.read(size)) + + class MTag(object): def __init__(self, log_func: "RootLogger", args: argparse.Namespace) -> None: self.log_func = log_func diff --git a/copyparty/up2k.py b/copyparty/up2k.py index 957cbc72..76e02801 100644 --- a/copyparty/up2k.py +++ b/copyparty/up2k.py @@ -102,7 +102,7 @@ ICV_EXTS = set(zsg.split(",")) zsg = "3gp,asf,av1,avc,avi,flv,m4v,mjpeg,mjpg,mkv,mov,mp4,mpeg,mpeg2,mpegts,mpg,mpg2,mts,nut,ogm,ogv,rm,vob,webm,wmv" VCV_EXTS = set(zsg.split(",")) -zsg = "aif,aiff,alac,ape,flac,m4a,m4b,m4r,mka,mp3,oga,ogg,opus,tak,tta,wav,wma,wv,cbz,epub" +zsg = "aif,aiff,alac,ape,flac,m4a,m4b,m4r,mka,mp3,oga,ogg,opus,tak,tta,wav,wma,wv,cbz,epub,pkg" ACV_EXTS = set(zsg.split(",")) zsg = "nohash noidx xdev xvol"