From 366e2f7c82704ac186fe5a0014a701b39c9cb7b2 Mon Sep 17 00:00:00 2001 From: Nymiro Date: Mon, 20 Oct 2025 14:30:51 +1300 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=BC=A9=E7=95=A5=E5=9B=BE?= =?UTF-8?q?=E6=8F=90=E5=8F=96=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .DS_Store | Bin 10244 -> 10244 bytes __pycache__/heic_file.cpython-314.pyc | Bin 11802 -> 13806 bytes __pycache__/heic_types.cpython-314.pyc | Bin 18236 -> 18689 bytes heic_file.py | 52 ++++++++++++++++++++++++- heic_types.py | 21 ++++++++-- main.py | 17 ++++++++ 6 files changed, 85 insertions(+), 5 deletions(-) diff --git a/.DS_Store b/.DS_Store index 17999031001667e3ba292f8478ad13da3ed63a92..2f175593ae3a45061c018df5be943a121c26edbe 100644 GIT binary patch delta 286 zcmZn(XbIS$B2drxSsl`qIJ z3{K9^Edc6aVEnNFM6m+RE@Mb#$YUtWNk_;s?W&x|AcrE2X8E#5lMf2Wt1$FDf;a-n zAY^NpE-YY}d_YiYvYem`)1+81vrdqQ)$F0s&&QMd1vTL;*~zyA`4~4$mKBoR%p?@V a#F`_yW8$O9jUwWc3q*Jzyv>aw&B6e}6i#&j delta 286 zcmZn(XbIS$B2dp@cVRjM0|N_#9z!}qCPPVXzKcszPJR+ljDvxpLF8f3aYt176uj~U z8HT~h`MCu^Jq!%a7eFK{(CjjXRE9i;vYd2;EVHS6DT5q}G@9j)ZcjcaAg|K!=r+U= zNCqKW!yNCxF!_L>)MPn97p9IQU}l{l53BA^e*Qa?`vo;wkFh3Ax(#H>PQE3`$GBj! itdQhpCZQlE*4+Or{g)>F7`~?;P~g%+Z%bR`<1hlHP>M2xz_M;iF_ICcg;h-$SlS}Zpk=q01-pzg z4GCr%&@&ak##q8uwxG;r1{Y#T%zkXik~Wrrorxx+@NeRr(_hQZxoxq?N&EJEJkR?+ z=Q+3cTxtL7BWZ;+Go8Zi$oTUf6Z}zHEdr0xUePbmLcIyhN)^>BG3Vfz#mUSnF3Uo3 z*=BVyrPBly;@`5~mWUNkDIBI0Q?_Z1zip{#7e8i;a84T_nd41}fALrHYy)?ODGPNF zAgeAHMY1)hC8tDa1{UIG06d83GVy~pAJZf`JWCr?NgmTI=%@i&#Xo-mI&c5Sl zG3O{ajB-`Qdp1%iT0Y)(spDM7veFPz8p0Wd6-IT*c+R-Y=tGR&Z&_k?FEh0vrglE} zK4S{frUxIGFU zRzD?Ss?l$nZsxn&ElF`C8tT&z0Y>aa??{54Uj z7C=*GNwsimnxvA9h@?RuYt-mgF?00Qwqw+^ZB#)Am0Y%8nB+k^UoOKUq^atped2Oy zNLrHT>lax>+PL0rT4_QvXC@c4h>DXfmhuQ$GJ;sG%NiKLNz>ORr`Jy3jo!JvHZ~p| zySTnEvwmSZdi5^(oElrZFy~u4djpn5zdgD3)wSsCMD&_p_vi4*^yrOKs|#0R?A7_< z)%owC*2y}$wQ&9F`RMHUlZ7+Uk=v{Dv(Ymn(eWRkmh0*3(!w&XXP~dkWp(!IV07Wa z`j>u^%&XnYagM%z&gnW1IX83+a;(+HK?3&iXlS=`R)fLdt!QLfH(L)I+ylLK=yG$= z?%*73pVQ@l{j9Uk%ASBuoC`LY3@zS0O|EWtJ#29&#C5p`VYhq0W!FQS)kWwYhn2PU z;7OJ*$5%5%L`1H)3e$aYeoQ~!p@Vg}Yz|K>4RpGaQX9NEv4RJ;DuG@N?6G>V+=LIC zc?@11zZCapb6yX-1~@|SaaQ)YgTwpr7`&zXiS}1Q;U>NwCTcdBpf?*<8ev<)YRrY+ zs@4G;mhI^t=TrODFREIRx;f!j2y&;TW z_p$Yy$&Sk%%lS2-{F=E>mhz3^3?qRk%Ki0zeX!>Jp!{H%K152W^CqR2rOWDFA@#1g z(j|4>eEE{PaWwOhEHBRR@s!S>{8*T_#`3F6zCN_1(vH#(C7HpjnmO-WZ*X60ulIZ%j()GGRi4EjvL+J9S+YNoa2F}{dN7Fg>1)#QHN8RZ>K6)h mQm>b7P^q{t$Ct>zbb delta 391 zcmaEtJu8M!n~#@^0SIO{e9fqp*vMzZ!gy}7Bg;{~Akh;3FoqI=ATebIP4UfYtfvG_ zSy(MUG60E%A`6q3O0EdGs%Qx27byZwNM;0?0|m@L8i+YQuV9#bO3-?8f|%;$STV85 zbHzj^e-=|@Sv;s(IG8egm*lQ-+YWpxwncSt_JvqVHkWp~*Bx4JK9*~p^h*$?AHc!59 ztizZ$na9M83m8HlK*98#XR?op4(m4tc2<$e?I!;i_f2jyjpVOqWEA<#03yNR1^}rg BY!Uzf diff --git a/__pycache__/heic_types.cpython-314.pyc b/__pycache__/heic_types.cpython-314.pyc index 50a5690ba51d8aabb808da89d4f790016d6ae671..42b7d4739f788472a056aa1a9c5e09c079c80930 100644 GIT binary patch delta 1490 zcmZuxZ%kWN6u-Cad$gtWv9d9t1lobnKTud%1qQT0Sy35-6@B%Cfy~i4qC$DaWiz@B zV`OapK~9t!$5!2lG;zxa4$&-Ze%J>xHIS8z;Rl;V6aBQ!f5gP7_dHU8gqytIJ?Grt zJ?FgBzTdTX=)efo+x2N$!qyYRe;oGPCiTW#8k@4^x;gqbbmyO9IBO~3hZsW|f}Da9 zIs~(>EJ)Zhrj8VRrsPNQBm)21vY_4);3Mdc!Z}MLKZ526{A?+uBVb~8&Sq|b2J3Zt z9u&5}X_VdR=g%W43dihu{1}>JaL#U_V_0JqQVV~o7) zfccV~sg&|hC>>-a^Jq+Nb{rFp!|%K7bet7FnLCXEgcWEr?r|vuT$Yq`AU`w4{?zE?Fca|C>>w61nTH07ln`BWA zeyFS3jy&x~o~Y?iBv~96$PzWMzrM(&#rOths)K}d&!#2&7qz=s@3_*5?=&Hyw7^xC z#|C%mT}q>v3caaCN_|TaOlr(5FGg726M9kCCam6DkD@-AEsq4Q7XM#`6GN|?_ngfLR*{{%IO_AJ;A$bs9ML3Rd0->J4KC4=CpyNYm zLdZgR9bpOKafBxsSOqBaX79Mn%w?SvJk z^pb>NjP%6?(^5ue#PELQ*@Mx-k6wspSYs}DtTm20`*cF|XGyO^!tg1~M9`V^4WI2`fQ5HQ}J!FxR r6+;#SIkN%N%PN)1tYvl0OTAq$zZi5&Kf)Wnm{3Md!{I*(+ho#zlY4S| delta 1071 zcmY*YZAg<*6ux&K@8&)$KXPjGT{GSG>NG#nm2ORKTJ~ku){jssjRs-*PQnT*|CFVa z^N-y23`^Pdux@04^hHXZt9Cw1>+)i-RlE$?YhS&}%X<4`(Mhgt^L5K|>O1m!Q zdT8bX%-am$w)nUUblU@cmU1pg-5`9nWaA+4q^37vF_c;#;y#ere&c>p)57&pQa>CP zQn_C0_ClXv!M(Ic_=BM1@OhY7X*EAjm-eiKOtDY}8L4s2%f56`u zXxZJkw?Q0+%Y`G#yCiJIH7R&-+#rHT(Iu$H6Xbe!TI(ftz`cNn=5>W;vS zqXdscUm)yT-Hh=I_Fx~^R1@!*ki?Bq<``@*w&1bo`@&H1m*rBqX%-QM0PosNt<;RX@>*HZ zPK4?mFkhMh>F%OSVIQrJM@Edi?z4wNUw-5~CiMr_zux94DMUBG{&K^%v{@*LYsO58!S@|J^j{`vqF zPdfka64jIw0qMF7#RPE!Aq*?m9Z%FRd80QClnruMt0wi?q4 tuple[int, int] | None: if not (self._iprp_box and self._iprp_box.ipma and self._iprp_box.ipco): return None @@ -152,4 +156,48 @@ class HEICFile: with open(self.filepath, 'rb') as f: f.seek(offset) return f.read() - return None \ No newline at end of file + return None + + def get_thumbnail_data(self) -> bytes | None: + """ + 查找并提取缩略图图像数据 (如果存在)。 + 它会查找一个 'iref' 盒子,其中包含 'thmb' (thumbnail) 引用。 + """ + print("Attempting to extract thumbnail data...") + primary_id = self.get_primary_item_id() + if not primary_id: + print("Error: Could not determine primary item ID.") + return None + + if not self._iref_box: + print("Info: No 'iref' box found, cannot search for thumbnail.") + return None + + # 检查 'thmb' (thumbnail) 引用类型是否存在 + if 'thmb' not in self._iref_box.references: + print("Info: No 'thmb' references found in 'iref' box.") + return None + + # 检查主图像是否有与之关联的缩略图 + if primary_id not in self._iref_box.references['thmb']: + print(f"Info: Primary item ID {primary_id} has no 'thmb' reference.") + return None + + thumbnail_ids = self._iref_box.references['thmb'][primary_id] + if not thumbnail_ids: + print(f"Info: Primary item ID {primary_id} has 'thmb' reference, but no target IDs.") + return None + + # 获取第一个缩略图 ID + thumbnail_id = thumbnail_ids[0] + print(f"Found thumbnail reference: Primary ID {primary_id} -> Thumbnail ID {thumbnail_id}") + + # 使用现有的 get_item_data 来提取它 + thumbnail_data = self.get_item_data(thumbnail_id) + + if thumbnail_data: + print(f"Successfully extracted thumbnail data (Item ID {thumbnail_id}).") + return thumbnail_data + else: + print(f"Error: Failed to get data for thumbnail item ID {thumbnail_id}.") + return None \ No newline at end of file diff --git a/heic_types.py b/heic_types.py index b1b4fc0..aad7d35 100644 --- a/heic_types.py +++ b/heic_types.py @@ -206,8 +206,10 @@ class ItemReferenceEntry: class ItemReferenceBox(Box): def __init__(self, size: int, box_type: str, offset: int, raw_data: bytes): super().__init__(size, box_type, offset, raw_data) - self.references: dict[int, list[int]] = {} + # 结构: {'thmb': {from_id: [to_id, ...]}, 'dimg': {from_id: [to_id, ...]}} + self.references: dict[str, dict[int, list[int]]] = {} self._parse_references() + def _parse_references(self): stream = self.raw_data version_flags = struct.unpack('>I', stream[:4])[0] @@ -215,17 +217,29 @@ class ItemReferenceBox(Box): pos = 4 item_id_size = 4 if version == 1 else 2 while pos < len(stream): - if pos + 8 > len(stream): break + if pos + 8 > len(stream): break # 需要空间来读取 size 和 type ref_box_size = struct.unpack('>I', stream[pos:pos+4])[0] + ref_box_type = stream[pos+4:pos+8].decode('ascii', errors='ignore') + if pos + ref_box_size > len(stream): break + + # 为这种引用类型创建字典 (如果它还不存在) + if ref_box_type not in self.references: + self.references[ref_box_type] = {} + if item_id_size == 4: + if pos + 12 > len(stream): break from_item_id = struct.unpack('>I', stream[pos+8:pos+12])[0] ref_count_pos = pos + 12 else: + if pos + 10 > len(stream): break from_item_id = struct.unpack('>H', stream[pos+8:pos+10])[0] ref_count_pos = pos + 10 + + if ref_count_pos + 2 > len(stream): break reference_count = struct.unpack('>H', stream[ref_count_pos:ref_count_pos+2])[0] to_ids_pos = ref_count_pos + 2 + to_item_ids = [] for _ in range(reference_count): if to_ids_pos + item_id_size > len(stream): break @@ -235,5 +249,6 @@ class ItemReferenceBox(Box): to_id = struct.unpack('>H', stream[to_ids_pos:to_ids_pos+2])[0] to_item_ids.append(to_id) to_ids_pos += item_id_size - self.references[from_item_id] = to_item_ids + + self.references[ref_box_type][from_item_id] = to_item_ids pos += ref_box_size \ No newline at end of file diff --git a/main.py b/main.py index 658f173..14d40ca 100644 --- a/main.py +++ b/main.py @@ -40,6 +40,23 @@ def analyze_and_reconstruct(filename: str): else: print("Failed to reconstruct image.") + # 3. 尝试提取并保存缩略图 + thumbnail_data = heic_file.get_thumbnail_data() + if thumbnail_data: + try: + # 缩略图数据通常是 JPEG 或 HEIC 格式 + # 我们先假设它是 JPEG 并以此后缀保存 + thumb_filename = f"{base_filename}_thumbnail.jpg" + with open(thumb_filename, "wb") as f_thumb: + f_thumb.write(thumbnail_data) + print(f"Successfully saved thumbnail data to: {thumb_filename}") + print(f" (Note: This file might be a JPEG or a small HEIC. Try opening it.)") + except Exception as e: + print(f"Could not save thumbnail file: {e}") + else: + print("No thumbnail data found or extracted.") + + print("\n" + "="*40 + "\n") def main():