@@ -23,7 +23,7 @@ impl<'a> BMByteSearchable for Bytes<'a> {
2323 fn value_at ( & self , index : usize ) -> u8 {
2424 self . bytes [ index]
2525 }
26- fn iter ( & self ) -> Iter < u8 > {
26+ fn iter ( & self ) -> Iter < ' _ , u8 > {
2727 self . bytes . iter ( )
2828 }
2929}
@@ -101,11 +101,79 @@ impl SmMotion {
101101 let bmb = BMByte :: from ( & indicator) . unwrap ( ) ;
102102 let bytes = Bytes :: new ( & self . mmap [ ..] ) ;
103103 // Using the first entry because it is quite unique
104- self . video_index = match bmb. find_first_in ( bytes) {
105- // Move index on the length of indicator
104+ let base_index = match bmb. find_first_in ( bytes) {
105+ // Move index on the length of indicator (right after the marker)
106106 Some ( index) => Some ( index + 16 ) ,
107107 None => None ,
108108 } ;
109+
110+ // On newer Samsung devices (e.g., SG22) there may be extra bytes after the marker
111+ // before the actual MP4 begins. The MP4 typically starts with an 'ftyp' box,
112+ // which appears 4 bytes after the start of the file/box (after the 32-bit size).
113+ // To make detection robust, scan forward from the marker for 'ftyp' and, if found,
114+ // shift the starting index back by 4 bytes to the true MP4 start.
115+ if let Some ( start_after_marker) = base_index {
116+ // Limit the scan window to avoid scanning the entire image. 64 KiB should be plenty.
117+ let scan_start = start_after_marker;
118+ let scan_end = ( scan_start + 65536 ) . min ( self . mmap . len ( ) ) ;
119+ let scan_slice = & self . mmap [ scan_start..scan_end] ;
120+
121+ // Search for 'ftyp' inside the scan window using the same Boyer-Moore engine
122+ let ftyp: Vec < u8 > = b"ftyp" . to_vec ( ) ;
123+ let bmb_ftyp = BMByte :: from ( & ftyp) . unwrap ( ) ;
124+ let bytes_ftyp = Bytes :: new ( scan_slice) ;
125+ let adjusted = match bmb_ftyp. find_first_in ( bytes_ftyp) {
126+ Some ( rel_pos) => {
127+ // Ensure we don't underflow when backing up 4 bytes for the size field
128+ if rel_pos >= 4 {
129+ Some ( scan_start + rel_pos - 4 )
130+ } else {
131+ Some ( scan_start)
132+ }
133+ }
134+ None => None ,
135+ } ;
136+ // If we didn't find 'ftyp' right after the marker (new HEIC layout),
137+ // try to locate the MP4 'ftyp' elsewhere in the file. Prefer the last
138+ // occurrence before the marker to avoid the HEIC's own 'ftyp' at the start.
139+ if let Some ( idx) = adjusted {
140+ self . video_index = Some ( idx) ;
141+ } else {
142+ // Define a small helper to test major brand after 'ftyp'
143+ let majors: [ & [ u8 ] ; 5 ] = [ b"isom" , b"mp42" , b"mp41" , b"iso4" , b"avc1" ] ;
144+ let mut search_pos = 0usize ;
145+ let mmap = & self . mmap ;
146+ let mut chosen: Option < usize > = None ;
147+ while let Some ( pos) = mmap[ search_pos..] . windows ( 4 ) . position ( |w| w == b"ftyp" ) {
148+ let abs_pos = search_pos + pos;
149+ // Check we have enough bytes to read major brand
150+ if abs_pos + 8 < mmap. len ( ) {
151+ let major = & mmap[ abs_pos + 4 ..abs_pos + 8 ] ;
152+ // Filter out HEIC/HEIF brands and keep MP4-like ones
153+ let is_mp4_brand = majors. iter ( ) . any ( |m| * m == major) ;
154+ if is_mp4_brand {
155+ // Only consider positions before the SEF footer marker to avoid false positives
156+ if abs_pos < start_after_marker {
157+ chosen = Some ( abs_pos) ;
158+ }
159+ }
160+ }
161+ // Advance search position
162+ search_pos = abs_pos + 4 ;
163+ if search_pos >= mmap. len ( ) { break ; }
164+ }
165+ if let Some ( ftyp_pos) = chosen {
166+ // Back up 4 bytes for size field if possible
167+ let start = if ftyp_pos >= 4 { ftyp_pos - 4 } else { ftyp_pos } ;
168+ self . video_index = Some ( start) ;
169+ } else {
170+ // Fallback: keep original behavior (start right after marker)
171+ self . video_index = Some ( start_after_marker) ;
172+ }
173+ }
174+ } else {
175+ self . video_index = None ;
176+ }
109177 Ok ( self . video_index )
110178 }
111179
0 commit comments