From 812318c50c3d70de04a8cc81bede62f403dd2e73 Mon Sep 17 00:00:00 2001 From: Nymiro Date: Mon, 20 Oct 2025 12:25:22 +1300 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20ItemInfoBox=20=E5=92=8C=20?= =?UTF-8?q?PrimaryItemBox=20=E7=B1=BB=EF=BC=8C=E6=9B=B4=E6=96=B0=20HEICFil?= =?UTF-8?q?e=20=E7=B1=BB=E4=BB=A5=E6=94=AF=E6=8C=81=E6=8F=90=E5=8F=96?= =?UTF-8?q?=E4=B8=BB=E5=9B=BE=E5=83=8F=20ID=20=E5=92=8C=E5=88=97=E5=87=BA?= =?UTF-8?q?=E5=8F=AF=E7=94=A8=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .DS_Store | Bin 8196 -> 8196 bytes __pycache__/heic_file.cpython-314.pyc | Bin 5657 -> 6947 bytes __pycache__/heic_types.cpython-314.pyc | Bin 3925 -> 7609 bytes __pycache__/parser.cpython-314.pyc | Bin 2891 -> 2969 bytes heic_file.py | 20 ++++++++++ heic_types.py | 52 ++++++++++++++++++++++++- main.py | 35 +++++++---------- parser.py | 4 +- 8 files changed, 89 insertions(+), 22 deletions(-) diff --git a/.DS_Store b/.DS_Store index 2edecdfa0e9034416f99a12270e362e9d818d0bc..c8f860b6742574c236a6be75c66a4878933081a5 100644 GIT binary patch delta 156 zcmZp1XmQw}CJ^^_G6Mqx3xgg*IzuKyNp8N2OHxjL5>Si-h)-S#I_`)npMqDuAj2>? zIX|}msE2{UF@a&SgP_#pc>*p>UfE#gKLH+ATdQNTS10oeYO>DcIG1<@$da8LCCJB^ OFnO+^S17TaA271ASg9?o`4IJ*?KVZp8yZ*y!|ZwmnZWJYQkBvlcNOr7y~BH6_niE IB-q3U0FZVlng9R* diff --git a/__pycache__/heic_file.cpython-314.pyc b/__pycache__/heic_file.cpython-314.pyc index 64efcce60b333268663d91686144cb194c487705..9a25696439f08455909a9a3c831ecf751ac44b8e 100644 GIT binary patch delta 2506 zcmah~Z){Ul6u+;1uYK+MZ)>{_*t)lkb?XLY!uYc>3@}-?%*;|rAqG~u^_9KYy)O4X zkj-RU{GgZ+*m4tvPfCo5Xowg-7(W?F{9-hLWXgED7!wn}4i+4K^PJmulc?b(?|1J# z=l(h8ch5QZ`i_tG`8s`GH-Yaf`eb6Sf6-Sb%r3U=lchn2=@@jHi)R{Rqa-B{c?W%_ zuaSgF6G^!uB<1e5;W9!@e@bW}@v6o5x)eK=Kw|llRGZAw=^;~{JX*YHRbgjH&leZ( zzFhM*%*c{?a)y{fikP-h4^xDWWSZ;|Qc{Ez08Bpe0I2N>Su61!q z6_8Wy8 z)AW3~JEfVEY`^$=179HIEmvMEs2M|>Q4Mr27E=@oc8W?nCu{*^^JKYBx$Ir;NL&&x z`ftxgYkpNuX+U^H5N~#WVZYT=>eE`#KXYSfKJ4#VO8~+1BrS*m zGH`_rJH}bc5sKIkoFXI#*IG|bDmog%KlX|vvQ2zOha2$^uFTCocQiQ-0@t7PvPNH6 za0Bo4zF{E7MWQ4nL4Y|}#JTsBB_hw3h!l^+ot6j}pwca{6Wxk{4pDT?lE){sLXN6B z-44UJoyJgwL|JyYJOhn`izuLYQ%vTM5ee4<-nl~D)4i58{UBLZ;a)la%VevNlaESCco=5dBgc~O7?uuPJK z{o(0lO>*sAa)UWOl>^XYn>lncOLa{@*Q>N4J8cShspv&h$rsP-xrEZ6)p3+lO_fe+ zx~fc-iiV;=u}F*nL!37W#JXuYQJT?mae)(Opm{MMYYr#~o>R?Cd3B~d&#E6hHq<`= zah~`OE$5((JG1N$UY)%eMRs2f%?&^XObMqqr`gkjhwR>*OokyPcz_AOpg8lw zCW9Dd|HmLE87Iw1jLb9~xscTg*|CCJ5-FOlAQMV{4!G!@5fx>p0^IVzJP4*+PE|Ll zrWz1A7?PHR<7u9x<#4pzcm!wGu!0-BZVEeLopBTZs>U;P9$WGZJoME3E_?6!+IKF= ziRJK?_eU2;AA~!X!kw3n0p|-ZT?=1xE+l^PG_Bc5?G9Rd$0pyh30v81cSEcNHl@1} z@R6bo2rdLPoyq{1N$iQc*}=<6D{H8#7rO8CR@DiIc`53{i3b2iz`-79Eop4p2K6&B(A>dm0B&W;6+OkUw8UK2%o-SEvAn{ob288&46ru?Pj}J% zAcj0Rfz5O4YMOmo-2`5IUtJVVaN``DMyoEb>AB2g5sk`BO%%;yrkKwgs#!J=cBf|$ zGI&n8UX7vvEiC(v@5a?&uaIWn1*hAb|0sIA2G-+reuwz<@N-AvUUoLLuQ84SEQRzC z0!9zTw4~z*6YR54*BpN>Fw+m0$|63H6ajyuAK>Xb5OeD`c+^+%WnYitld;Mlj`bQS zYpYn>9($Zp^Wz%7)V99eC)KSIfOW+!h5jS}Jh%C-@c?avJuPu8YpC#SvEi_Cm^wg2 z;RzvDcp|Ik3Mw@cI3ZIW@hG%vH+0#lDR^8fN*B8kj-KVgi$>ohrQ+=p@q{3R)(9fs?{9yS BA432D delta 1447 zcmah}&u<$=6n^9VkzG5s<8%}MNaAb~NaGN>Q5#YM6*PvJq6!lk;YKJB9DAJ@Id(L= zK?xU9gajv$HBtq~sHYSBEy3&3#@0*#o?|bjf z-ktwuCh}Y)90Yvsw4Q99hT4$?`>=g+zU_>r24D<|MjVRfT!3}}c;OO&d6*m1sQHz78m*5I3>Tzfo#U8{yuSYWI4;fQ3MltA_CGdj9 z5PIzu57|$nz39`m5Ki}@10ikvF~PwFIh=qvh$Y!);@7}S=BkOi=n+4};^LMv8j?DQ zS?(3Ll{kxvufnO7IYtzceckc@xUH;SIJa+2oXadh^IumbSdO3X_-{nk62J71D6pKQ zkBB8TuY`G6e5t0y_iAkVy%m@fV>heEhG3^$hm zN+7Ps2b93FjyV{P+*}7RAq_2V$B^d<2qIzAzjG)AlO8%uAzn#amy*Q$=^RJ^s1oMyGf?L)gS!Ct4l z`#p8f*;=bteVser4sRB)2nzc2we>yEaWjFZRpGMOjZHD@@F2DtNH5Aj{6KcrR~oGf zC%dcI#_EY7Yk^%A?^v~|LE0GXrtKQVp*Ede&o}W<*{ipjE>9tgo%WkGJ}omyu6*Ns zolnR^)v{N1HJnb?Y}s;`2Bz6%@$SIsRm_KMtQ@WuGK{~5i!3J&5_=<>?2E2cSxCAxvWA6BNGvAj z2c9LuCZMwMw+U9no#bp)Ha|oAdbeNbzH)*PzU~~t>F3zQodEtkj_D85Nzn~BmiOS# zNL0Jnc_yMIjsc-#2Q}+2K#-S0y2(WNkXT5~h*m0r?f*q;cu)3KVzb<=HXQC|-SU>Z z)7)4)Bf1Sp5!{2W8)`S-xVF`*?lhd2`39O4uselttT4tNfq4Yh5u}e`@`(;?_6RN= P!Ih4>$e48kgwoSLk7FJC diff --git a/__pycache__/heic_types.cpython-314.pyc b/__pycache__/heic_types.cpython-314.pyc index 1186953cee2cd0b9f1627171cc98375193955dba..bb18c79edccc0ca0a3c972999a98939060746fd6 100644 GIT binary patch delta 3195 zcma)8T}&L;6}~gOvpc)6{KN~(&nzDEvuN$Gi47?f%#RJRj6x7B$tHG7M#Jt9kF@M= z?~L1kWG$5{g-BM4Jr8cHNTyPyvVCZ%cqr~md8(Y3mMy_cM^)vAzT~B{11+ig(sO2( zun;@RmFBy5&b@Q)nR~zU-MiEDps(Uwh0jZ%gfsuX(NJ}#qK^5=o%Z1?HRKG5x&tK2 zcd^(_h|XCy57@lZY?q^Gby?Oju$Grt-BG56gnhru{EuOBMv{HN_>GXGgUPMG;iyK_ ztg{X9wll~C*MH-DfvL;6XgRo~EeNd$7#3|?|CIZaBlrjU9eDf91u))Azl&`i+v8!L zwi&`U8O3y_P)X{+v1b7wjOvz^kzbGfa%^Q_ zEwJJLWBr5rjk3+2huM+8jD9luux~5&_EzP!E&iGnU)WwB`WAdkV*uk}&$BpVx+N)* zj!TkF9*pIPH&lREE7T%Erm!2~Ja!=GzIeY0V*8tO@N?F|H63^v?mF{?AX;J2DdWDQ zzUwNCl!?rgu%^#q4TG$SF`W!DC&|YMw17QHK3Qe_%h8xEJ6zh!>Gu{C?CuFKA8qi4-(?3etOw-fi%uO|!5M?o`YPy)773E}7lz%9zNqHt| zlTV9ymzVsvexOs!uqp&!!j$pAcfSaG5NPK+D=9)2%=PsfuP+NE!T1 zsrXK$Jt8IjdkqMmkM5!WIwg}0vF?Iqa@I;BS zJ#Q28u7ePPyjDxlr-Q^;|3qdMyBD(=<MC`a;bPn~-MXWE)YDx&RKCHr3zh&^`b3#P0Xb2=pT+#Q%3EDA*Wvke80Bg5 z+FuFrkR~|2KsU>1Hw>jH1Ev#HQ0b+;(EbdMpmCFvwYaL94C$pR8pap6oBC`=kLgn1 zO{J+4W=xktX_{)|aAMep3(0~%+;)?X-+*+WO3Km))0I%->4ai(8qh046EK7hB4ECU z-L|$~u|C8=B#>iEf+_4HF)LNnTa-F!W?ok`dJU`^Ds@P-ik&D5f%(mbV|+ZuVw^zx zGr%JGXHDb1>ATY_U90|&gSp_EwiE8#uDQ4|xl=Q^WxBVFdrG*bnHY%wi{mEly@4gF1xe4LTf?j$O|2-i5;P{ zAoS*i-i_{UVYncS=7rIRp>1Ju*|Xbxw$R*}Z|+?!XICwzr_`MV~PKao0Hbzp=X_qJqzv4K`eEgqc^1^Rp40ht7kd$5|=HcB8 zo{Zm4ui}p*U9@HZNUuQf-mSa0R^C|c`nW%v>)$xFbFP0oFtG81oxr6f=ZEEol*Rkh zH!>FIF1a{7o3S@>5CYzVN*T+BfU>S5%Cg*Lpe&*lg!K*Fp!nL(3%ztkUYa8%P7716^js~geF@N573w*1rVVKB0$C#DQy;EJH{+)Rb&X{ zy<`ItepW@sAT|ex?Wf66q%`?Ery8U7WI3+Qj5?byab0H;GXlyL832hYCc8jQrXo`i z-)!=JUNI2`AhVbSNT>lp5idx>V)9Ggb&1+QrY2XBC`d#MM2Le32@t^tB6L861&ELY z5mG?n7H@ofa!z7#adK)-PJBGjB3+P_G>|A}0ul}m5DR2MqOy~J@EOX2n3}9$2i;;x oN-Rz-Vg^aFLR2`)N^$uDnT$YOtUNhYR*~x)gA$`)kpNHt05`@+)Bpeg diff --git a/__pycache__/parser.cpython-314.pyc b/__pycache__/parser.cpython-314.pyc index 06faf086fdbed4d376529ad2c5866e2078ea146c..9e537d5938aabcff26452ad71bb726833f095435 100644 GIT binary patch delta 407 zcmX>tHd9<(n~#@^0SM%ZzGgh&U|@I*;=lkKl<~Q2qPo3}2UiJ~2X_g#JVOv$EN2jV zELRXmEVn*$36B9o5O*$P2``Z50n&UyoZ<{YT;dEl0ww%GjLHm}yb~vBJ2Kzm_bf@x z^~q08EXmBzbIPx{#TQVNnVVQt2@(hKxj_uiytI4}hb1#7Klv6*L1sxVke!*AHu36b zR#Bjngf^dL{LR8BG5I9hel~ufK#{=YKK6P>^~ro3&io=E0ZkYov^jy}AtR&MWG5~c zi6S15Fv!0}wjhEVNN5Ta$xmLuWnB;SJVOyDh)@O*Y9K-bM1ag%$xtK)VuK97#bJ}1 zpHiBWYFFeA int | None: + if not self._pitm_box: + print("Warning: 'pitm' box not found. Cannot determine primary item.") + return None + return self._pitm_box.item_id + + def list_items(self): + if not self._iinf_box: + print("Warning: 'iinf' box not found. Cannot list items.") + return + + print("Available items in HEIC file:") + for entry in self._iinf_box.entries: + print(f" - {entry}") def _detect_vendor(self): """ diff --git a/heic_types.py b/heic_types.py index cdfd8be..f0a8609 100644 --- a/heic_types.py +++ b/heic_types.py @@ -44,4 +44,54 @@ class ItemLocationBox(Box): if size == 2: return struct.unpack('>H', data[pos:pos+2])[0] if size == 4: return struct.unpack('>I', data[pos:pos+4])[0] if size == 8: return struct.unpack('>Q', data[pos:pos+8])[0] - return 0 \ No newline at end of file + return 0 + +class ItemInfoEntry: + """A data class for one item described in an 'iinf' box.""" + def __init__(self, item_id, item_type, item_name): + self.item_id = item_id + self.type = item_type + self.name = item_name + + def __repr__(self): + return f"" + +class ItemInfoBox(Box): + """ + A specialized class for the 'iinf' box. + Parses its own data to build a list of all available items. + """ + def __init__(self, size: int, box_type: str, offset: int, raw_data: bytes): + super().__init__(size, box_type, offset, raw_data) + + self.entries: list[ItemInfoEntry] = [] + self._parse_entries() + + def _parse_entries(self): + # 'iinf' is a FullBox, so the first 4 bytes are version/flags + item_count = struct.unpack('>H', self.raw_data[4:6])[0] + + # The children are 'infe' boxes, which are parsed by the generic parser + for infe_box in self.children: + if infe_box.type == 'infe': + # 'infe' is a FullBox (version, flags) + # version = infe_box.raw_data[0] + item_id = struct.unpack('>H', infe_box.raw_data[4:6])[0] + item_type = infe_box.raw_data[8:12].decode('ascii').strip('\x00') + item_name_bytes = infe_box.raw_data[12:] + item_name = item_name_bytes.decode('utf-8', errors='ignore').strip('\x00') + self.entries.append(ItemInfoEntry(item_id, item_type, item_name)) + +class PrimaryItemBox(Box): + """ + A specialized class for the 'pitm' box. + """ + def __init__(self, size: int, box_type: str, offset: int, raw_data: bytes): + super().__init__(size, box_type, offset, raw_data) + + self.item_id: int = 0 + self._parse_item_id() + + def _parse_item_id(self): + # 'pitm' is a FullBox (version, flags) + self.item_id = struct.unpack('>H', self.raw_data[4:6])[0] \ No newline at end of file diff --git a/main.py b/main.py index 8c73282..fc30ae2 100644 --- a/main.py +++ b/main.py @@ -1,28 +1,23 @@ +# In main.py from heic_file import HEICFile -def main(): - print("--- Processing Samsung HEIC file ---") - samsung_heic = HEICFile('samsung.heic') - samsung_video_data = samsung_heic.get_motion_photo_data() +def analyze_file(filename: str): + print(f"--- Analyzing {filename} ---") + heic_file = HEICFile(filename) + + # List all available items in the file + heic_file.list_items() + + # Get the ID of the primary image + primary_id = heic_file.get_primary_item_id() + if primary_id: + print(f"\nThe primary item ID is: {primary_id}") - if samsung_video_data: - output_filename = 'samsung_motion_photo.mp4' - with open(output_filename, 'wb') as f: - f.write(samsung_video_data) - print(f"Successfully extracted motion photo to '{output_filename}' ({len(samsung_video_data)} bytes)") - print("\n" + "="*40 + "\n") - print("--- Processing Apple HEIC file ---") - apple_heic = HEICFile('apple.heic') - apple_video_data = apple_heic.get_motion_photo_data() - - if apple_video_data: - # This block should not be reached for Apple files - print("Extracted embedded video from Apple file (this is unexpected).") - else: - print("No embedded motion photo found in Apple file, as expected.") - +def main(): + analyze_file('apple.heic') + analyze_file('samsung.heic') if __name__ == "__main__": main() \ No newline at end of file diff --git a/parser.py b/parser.py index 26a0c76..a003bd4 100644 --- a/parser.py +++ b/parser.py @@ -3,11 +3,13 @@ from typing import List, BinaryIO from io import BytesIO from base import Box # Import the base Box class -from heic_types import ItemLocationBox +from heic_types import ItemLocationBox, PrimaryItemBox, ItemInfoBox # The factory map remains here BOX_TYPE_MAP = { 'iloc': ItemLocationBox, + 'pitm': PrimaryItemBox, + 'iinf': ItemInfoBox, } CONTAINER_BOXES = {'meta', 'moov', 'trak', 'iprp', 'ipco', 'dinf', 'fiinf', 'ipro'}