@@ -41,4 +41,197 @@ impl JPEGReader {
41
41
42
42
Ok ( HashMap :: new ( ) )
43
43
}
44
+
45
+ pub fn write_iptc (
46
+ buffer : & Vec < u8 > ,
47
+ data : & HashMap < IPTCTag , String > ,
48
+ ) -> Result < Vec < u8 > , Box < dyn Error > > {
49
+ let mut new_buffer = Vec :: new ( ) ;
50
+
51
+ // Copy the initial JPEG marker (SOI)
52
+ if buffer. len ( ) < 2 || buffer[ 0 ] != 0xFF || buffer[ 1 ] != 0xD8 {
53
+ return Err ( "Not a valid JPEG file" . into ( ) ) ;
54
+ }
55
+ new_buffer. extend_from_slice ( & buffer[ 0 ..2 ] ) ;
56
+ let mut offset = 2 ;
57
+
58
+ // Convert IPTC data to binary format first
59
+ let iptc_data = Self :: convert_iptc_to_binary ( data) ?;
60
+ let mut found_app13 = false ;
61
+ let mut inserted_app13 = false ;
62
+
63
+ // Copy segments until we find SOS
64
+ while offset + 1 < buffer. len ( ) {
65
+ // Every JPEG segment must start with 0xFF
66
+ if buffer[ offset] != 0xFF {
67
+ offset += 1 ;
68
+ continue ;
69
+ }
70
+
71
+ let marker = buffer[ offset + 1 ] ;
72
+
73
+ // Skip empty markers
74
+ if marker == 0xFF {
75
+ offset += 1 ;
76
+ continue ;
77
+ }
78
+
79
+ // For markers without length field
80
+ if marker == 0x00 || marker == 0x01 || ( marker >= 0xD0 && marker <= 0xD7 ) {
81
+ new_buffer. extend_from_slice ( & buffer[ offset..offset + 2 ] ) ;
82
+ offset += 2 ;
83
+ continue ;
84
+ }
85
+
86
+ // End of image marker
87
+ if marker == 0xD9 {
88
+ new_buffer. extend_from_slice ( & buffer[ offset..offset + 2 ] ) ;
89
+ break ;
90
+ }
91
+
92
+ // Start of scan marker - copy the rest of the file
93
+ if marker == 0xDA {
94
+ // If we haven't inserted APP13 yet, do it now
95
+ if !found_app13 && !inserted_app13 {
96
+ // Write APP13 marker
97
+ new_buffer. extend_from_slice ( & [ 0xFF , 0xED ] ) ;
98
+ // Write length (including length bytes)
99
+ let total_length = ( iptc_data. len ( ) + 2 ) as u16 ;
100
+ new_buffer. push ( ( total_length >> 8 ) as u8 ) ;
101
+ new_buffer. push ( total_length as u8 ) ;
102
+ // Write IPTC data
103
+ new_buffer. extend_from_slice ( & iptc_data) ;
104
+ }
105
+
106
+ // Copy SOS marker and all remaining data
107
+ new_buffer. extend_from_slice ( & buffer[ offset..] ) ;
108
+ break ;
109
+ }
110
+
111
+ // Check if we can read the length
112
+ if offset + 3 >= buffer. len ( ) {
113
+ new_buffer. extend_from_slice ( & buffer[ offset..] ) ;
114
+ break ;
115
+ }
116
+
117
+ let length = buffer. read_u16be ( offset + 2 ) as usize ;
118
+
119
+ // Validate segment length
120
+ if length < 2 || offset + 2 + length > buffer. len ( ) {
121
+ new_buffer. extend_from_slice ( & buffer[ offset..] ) ;
122
+ break ;
123
+ }
124
+
125
+ // If this is APP13, replace it with our new data
126
+ if marker == 0xED {
127
+ found_app13 = true ;
128
+ // Write APP13 marker
129
+ new_buffer. extend_from_slice ( & [ 0xFF , 0xED ] ) ;
130
+ // Write length (including length bytes)
131
+ let total_length = ( iptc_data. len ( ) + 2 ) as u16 ;
132
+ new_buffer. push ( ( total_length >> 8 ) as u8 ) ;
133
+ new_buffer. push ( total_length as u8 ) ;
134
+ // Write IPTC data
135
+ new_buffer. extend_from_slice ( & iptc_data) ;
136
+ offset += 2 + length;
137
+ } else {
138
+ // If we haven't found APP13 and this is after APP0/APP1 but before other segments,
139
+ // insert our APP13 data here
140
+ if !found_app13 && !inserted_app13 && marker > 0xE1 {
141
+ // Write APP13 marker
142
+ new_buffer. extend_from_slice ( & [ 0xFF , 0xED ] ) ;
143
+ // Write length (including length bytes)
144
+ let total_length = ( iptc_data. len ( ) + 2 ) as u16 ;
145
+ new_buffer. push ( ( total_length >> 8 ) as u8 ) ;
146
+ new_buffer. push ( total_length as u8 ) ;
147
+ // Write IPTC data
148
+ new_buffer. extend_from_slice ( & iptc_data) ;
149
+ inserted_app13 = true ;
150
+ }
151
+
152
+ // Copy the marker and its data
153
+ new_buffer. extend_from_slice ( & buffer[ offset..offset + 2 + length] ) ;
154
+ offset += 2 + length;
155
+ }
156
+ }
157
+
158
+ Ok ( new_buffer)
159
+ }
160
+
161
+ fn convert_iptc_to_binary ( data : & HashMap < IPTCTag , String > ) -> Result < Vec < u8 > , Box < dyn Error > > {
162
+ let mut binary = Vec :: new ( ) ;
163
+
164
+ // Add Photoshop header
165
+ binary. extend_from_slice ( b"Photoshop 3.0\0 " ) ;
166
+ binary. push ( 0x00 ) ; // Pad to even length
167
+
168
+ // Add 8BIM marker and IPTC block
169
+ let mut iptc_block = Vec :: new ( ) ;
170
+
171
+ // Add IPTC data
172
+ for ( tag, value) in data {
173
+ if let Some ( ( record, dataset) ) = Self :: get_record_dataset ( tag) {
174
+ // Field delimiter
175
+ iptc_block. push ( 0x1C ) ;
176
+
177
+ // Record number and dataset number
178
+ iptc_block. push ( record) ;
179
+ iptc_block. push ( dataset) ;
180
+
181
+ // Value length (big endian)
182
+ let value_bytes = value. as_bytes ( ) ;
183
+ let value_len = value_bytes. len ( ) as u16 ;
184
+ iptc_block. push ( ( value_len >> 8 ) as u8 ) ;
185
+ iptc_block. push ( value_len as u8 ) ;
186
+
187
+ // Value
188
+ iptc_block. extend_from_slice ( value_bytes) ;
189
+ }
190
+ }
191
+
192
+ // Add 8BIM marker
193
+ binary. extend_from_slice ( b"8BIM" ) ;
194
+
195
+ // Resource ID for IPTC (0x0404)
196
+ binary. extend_from_slice ( & [ 0x04 , 0x04 ] ) ;
197
+
198
+ // Empty name (padded to even length)
199
+ binary. push ( 0x00 ) ;
200
+ binary. push ( 0x00 ) ;
201
+
202
+ // Block size (big endian)
203
+ let block_size = iptc_block. len ( ) as u32 ;
204
+ binary. extend_from_slice ( & [
205
+ ( block_size >> 24 ) as u8 ,
206
+ ( block_size >> 16 ) as u8 ,
207
+ ( block_size >> 8 ) as u8 ,
208
+ block_size as u8 ,
209
+ ] ) ;
210
+
211
+ // Add the IPTC block
212
+ binary. extend_from_slice ( & iptc_block) ;
213
+
214
+ // Pad to even length if needed
215
+ if binary. len ( ) % 2 != 0 {
216
+ binary. push ( 0x00 ) ;
217
+ }
218
+
219
+ Ok ( binary)
220
+ }
221
+
222
+ fn get_record_dataset ( tag : & IPTCTag ) -> Option < ( u8 , u8 ) > {
223
+ match tag {
224
+ IPTCTag :: City => Some ( ( 2 , 90 ) ) ,
225
+ IPTCTag :: Keywords => Some ( ( 2 , 25 ) ) ,
226
+ IPTCTag :: ByLine => Some ( ( 2 , 80 ) ) ,
227
+ IPTCTag :: Caption => Some ( ( 2 , 120 ) ) ,
228
+ IPTCTag :: CopyrightNotice => Some ( ( 2 , 116 ) ) ,
229
+ IPTCTag :: Credit => Some ( ( 2 , 110 ) ) ,
230
+ IPTCTag :: Headline => Some ( ( 2 , 105 ) ) ,
231
+ IPTCTag :: ObjectName => Some ( ( 2 , 5 ) ) ,
232
+ IPTCTag :: Source => Some ( ( 2 , 115 ) ) ,
233
+ IPTCTag :: Urgency => Some ( ( 2 , 10 ) ) ,
234
+ _ => None ,
235
+ }
236
+ }
44
237
}
0 commit comments