Skip to main content

agy_bridge/content/
media.rs

1//! Multimodal content types for chat input, mirroring the Python SDK's content
2//! primitives.
3//!
4//! The Python SDK accepts `Content = str | Image | Document | Audio | Video |
5//! list[ContentPrimitive]` as chat input. This module provides strongly-typed
6//! Rust equivalents with serialization support and ergonomic `From` impls.
7
8use serde::{Deserialize, Serialize};
9
10// =============================================================================
11// Media structs
12// =============================================================================
13
14/// Image content attachment primitive.
15///
16/// Binary image data with MIME type, mirroring `google.antigravity.types.Image`.
17#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
18pub struct Image {
19    /// Raw image bytes (e.g. PNG, JPEG).
20    pub data: Vec<u8>,
21    /// MIME type of the image (e.g. `"image/png"`).
22    pub mime_type: String,
23    /// Optional text description of the image.
24    #[serde(default)]
25    pub description: Option<String>,
26}
27
28/// Document content attachment primitive.
29///
30/// Binary document data with MIME type, mirroring `google.antigravity.types.Document`.
31#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
32pub struct Document {
33    /// Raw document bytes (e.g. PDF, JSON).
34    pub data: Vec<u8>,
35    /// MIME type of the document (e.g. `"application/pdf"`).
36    pub mime_type: String,
37    /// Optional text description of the document.
38    #[serde(default)]
39    pub description: Option<String>,
40}
41
42/// Audio content attachment primitive.
43///
44/// Binary audio data with MIME type, mirroring `google.antigravity.types.Audio`.
45#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
46pub struct Audio {
47    /// Raw audio bytes (e.g. WAV, MP3).
48    pub data: Vec<u8>,
49    /// MIME type of the audio (e.g. `"audio/wav"`).
50    pub mime_type: String,
51    /// Optional text description of the audio.
52    #[serde(default)]
53    pub description: Option<String>,
54}
55
56/// Video content attachment primitive.
57///
58/// Binary video data with MIME type, mirroring `google.antigravity.types.Video`.
59#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
60pub struct Video {
61    /// Raw video bytes (e.g. MP4, `WebM`).
62    pub data: Vec<u8>,
63    /// MIME type of the video (e.g. `"video/mp4"`).
64    pub mime_type: String,
65    /// Optional text description of the video.
66    #[serde(default)]
67    pub description: Option<String>,
68}
69
70// =============================================================================
71// MediaContent trait — shared interface for all media attachment types
72// =============================================================================
73
74/// Common interface for binary media attachment types ([`Image`], [`Document`],
75/// [`Audio`], [`Video`]).
76///
77/// Introduced to reduce boilerplate in serialization helpers that previously
78/// had to destructure each media struct individually.
79pub trait MediaContent {
80    /// The Python SDK type name used in the wire-format `"type"` field
81    /// (e.g. `"Image"`, `"Audio"`).
82    const TYPE_NAME: &'static str;
83
84    /// Raw binary payload.
85    fn data(&self) -> &[u8];
86    /// MIME type string.
87    fn mime_type(&self) -> &str;
88    /// Optional human-readable description.
89    fn description(&self) -> Option<&str>;
90}
91
92impl MediaContent for Image {
93    const TYPE_NAME: &'static str = "Image";
94    fn data(&self) -> &[u8] {
95        &self.data
96    }
97    fn mime_type(&self) -> &str {
98        &self.mime_type
99    }
100    fn description(&self) -> Option<&str> {
101        self.description.as_deref()
102    }
103}
104
105impl MediaContent for Document {
106    const TYPE_NAME: &'static str = "Document";
107    fn data(&self) -> &[u8] {
108        &self.data
109    }
110    fn mime_type(&self) -> &str {
111        &self.mime_type
112    }
113    fn description(&self) -> Option<&str> {
114        self.description.as_deref()
115    }
116}
117
118impl MediaContent for Audio {
119    const TYPE_NAME: &'static str = "Audio";
120    fn data(&self) -> &[u8] {
121        &self.data
122    }
123    fn mime_type(&self) -> &str {
124        &self.mime_type
125    }
126    fn description(&self) -> Option<&str> {
127        self.description.as_deref()
128    }
129}
130
131impl MediaContent for Video {
132    const TYPE_NAME: &'static str = "Video";
133    fn data(&self) -> &[u8] {
134        &self.data
135    }
136    fn mime_type(&self) -> &str {
137        &self.mime_type
138    }
139    fn description(&self) -> Option<&str> {
140        self.description.as_deref()
141    }
142}
143
144// =============================================================================
145// MIME type constants
146// =============================================================================
147
148/// Common image MIME types.
149pub mod mime {
150    /// MIME type for PNG images.
151    pub const IMAGE_PNG: &str = "image/png";
152    /// MIME type for JPEG images.
153    pub const IMAGE_JPEG: &str = "image/jpeg";
154    /// MIME type for GIF images.
155    pub const IMAGE_GIF: &str = "image/gif";
156    /// MIME type for WebP images.
157    pub const IMAGE_WEBP: &str = "image/webp";
158
159    /// MIME type for PDF documents.
160    pub const APPLICATION_PDF: &str = "application/pdf";
161    /// MIME type for plain text documents.
162    pub const TEXT_PLAIN: &str = "text/plain";
163    /// MIME type for JSON documents.
164    pub const APPLICATION_JSON: &str = "application/json";
165
166    /// MIME type for MP3 audio.
167    pub const AUDIO_MPEG: &str = "audio/mpeg";
168    /// MIME type for WAV audio.
169    pub const AUDIO_WAV: &str = "audio/wav";
170    /// MIME type for OGG audio.
171    pub const AUDIO_OGG: &str = "audio/ogg";
172    /// MIME type for FLAC audio.
173    pub const AUDIO_FLAC: &str = "audio/flac";
174
175    /// MIME type for MP4 video.
176    pub const VIDEO_MP4: &str = "video/mp4";
177    /// MIME type for `WebM` video.
178    pub const VIDEO_WEBM: &str = "video/webm";
179
180    /// Infer a MIME type from a file extension.
181    ///
182    /// Returns `None` if the extension is unrecognized.
183    #[must_use]
184    pub fn from_extension(ext: &str) -> Option<&'static str> {
185        match ext.to_ascii_lowercase().as_str() {
186            "png" => Some(IMAGE_PNG),
187            "jpg" | "jpeg" => Some(IMAGE_JPEG),
188            "gif" => Some(IMAGE_GIF),
189            "webp" => Some(IMAGE_WEBP),
190            "pdf" => Some(APPLICATION_PDF),
191            "txt" => Some(TEXT_PLAIN),
192            "json" => Some(APPLICATION_JSON),
193            "mp3" => Some(AUDIO_MPEG),
194            "wav" => Some(AUDIO_WAV),
195            "ogg" => Some(AUDIO_OGG),
196            "flac" => Some(AUDIO_FLAC),
197            "mp4" => Some(VIDEO_MP4),
198            "webm" => Some(VIDEO_WEBM),
199            _ => None,
200        }
201    }
202}
203
204// =============================================================================
205// Media convenience constructors
206// =============================================================================
207
208impl Image {
209    /// Creates a new [`Image`] with the given data and MIME type.
210    ///
211    /// # Examples
212    ///
213    /// ```
214    /// # use agy_bridge::content::Image;
215    /// let img = Image::new(vec![0x89, 0x50], "image/png");
216    /// assert_eq!(img.mime_type, "image/png");
217    /// assert_eq!(img.data, vec![0x89, 0x50]);
218    /// assert!(img.description.is_none());
219    /// ```
220    pub fn new(data: Vec<u8>, mime_type: impl Into<String>) -> Self {
221        Self {
222            data,
223            mime_type: mime_type.into(),
224            description: None,
225        }
226    }
227
228    /// Creates a new [`Image`] with MIME type `image/png`.
229    ///
230    /// # Examples
231    ///
232    /// ```
233    /// # use agy_bridge::content::Image;
234    /// let img = Image::png(vec![1, 2, 3]);
235    /// assert_eq!(img.mime_type, "image/png");
236    /// assert_eq!(img.data, vec![1, 2, 3]);
237    /// ```
238    #[must_use]
239    pub fn png(data: Vec<u8>) -> Self {
240        Self::new(data, mime::IMAGE_PNG)
241    }
242
243    /// Creates a new [`Image`] with MIME type `image/jpeg`.
244    ///
245    /// # Examples
246    ///
247    /// ```
248    /// # use agy_bridge::content::Image;
249    /// let img = Image::jpeg(vec![0xFF, 0xD8]);
250    /// assert_eq!(img.mime_type, "image/jpeg");
251    /// ```
252    #[must_use]
253    pub fn jpeg(data: Vec<u8>) -> Self {
254        Self::new(data, mime::IMAGE_JPEG)
255    }
256
257    /// Creates a new [`Image`] with MIME type `image/webp`.
258    #[must_use]
259    pub fn webp(data: Vec<u8>) -> Self {
260        Self::new(data, mime::IMAGE_WEBP)
261    }
262
263    /// Creates a new [`Image`] with MIME type `image/gif`.
264    #[must_use]
265    pub fn gif(data: Vec<u8>) -> Self {
266        Self::new(data, mime::IMAGE_GIF)
267    }
268
269    /// Sets a description on this image, consuming and returning `self`.
270    #[must_use]
271    pub fn with_description(mut self, description: impl Into<String>) -> Self {
272        self.description = Some(description.into());
273        self
274    }
275
276    /// Load an image from a file path, inferring the MIME type from the extension.
277    ///
278    /// # Errors
279    ///
280    /// Returns `std::io::Error` if the file cannot be read or the extension
281    /// is unrecognized.
282    pub fn from_file(path: impl AsRef<std::path::Path>) -> std::io::Result<Self> {
283        let path = path.as_ref();
284        let ext = path.extension().and_then(|e| e.to_str()).ok_or_else(|| {
285            std::io::Error::new(std::io::ErrorKind::InvalidInput, "missing file extension")
286        })?;
287        let mime_type = mime::from_extension(ext).ok_or_else(|| {
288            std::io::Error::new(
289                std::io::ErrorKind::InvalidInput,
290                format!("unrecognized image extension: {ext}"),
291            )
292        })?;
293        if !mime_type.starts_with("image/") {
294            return Err(std::io::Error::new(
295                std::io::ErrorKind::InvalidInput,
296                format!("MIME type '{mime_type}' is not an image type"),
297            ));
298        }
299        let data = std::fs::read(path)?;
300        Ok(Self::new(data, mime_type))
301    }
302}
303
304impl Document {
305    /// Creates a new [`Document`] with the given data and MIME type.
306    ///
307    /// # Examples
308    ///
309    /// ```
310    /// # use agy_bridge::content::Document;
311    /// let doc = Document::new(b"%PDF".to_vec(), "application/pdf");
312    /// assert_eq!(doc.mime_type, "application/pdf");
313    /// assert!(doc.description.is_none());
314    /// ```
315    pub fn new(data: Vec<u8>, mime_type: impl Into<String>) -> Self {
316        Self {
317            data,
318            mime_type: mime_type.into(),
319            description: None,
320        }
321    }
322
323    /// Creates a new [`Document`] with MIME type `application/pdf`.
324    ///
325    /// # Examples
326    ///
327    /// ```
328    /// # use agy_bridge::content::Document;
329    /// let doc = Document::pdf(b"%PDF-1.4".to_vec());
330    /// assert_eq!(doc.mime_type, "application/pdf");
331    /// ```
332    #[must_use]
333    pub fn pdf(data: Vec<u8>) -> Self {
334        Self::new(data, mime::APPLICATION_PDF)
335    }
336
337    /// Creates a new [`Document`] with MIME type `text/plain`.
338    #[must_use]
339    pub fn plain_text(data: Vec<u8>) -> Self {
340        Self::new(data, mime::TEXT_PLAIN)
341    }
342
343    /// Creates a new [`Document`] with MIME type `application/json`.
344    #[must_use]
345    pub fn json(data: Vec<u8>) -> Self {
346        Self::new(data, mime::APPLICATION_JSON)
347    }
348
349    /// Sets a description on this document, consuming and returning `self`.
350    #[must_use]
351    pub fn with_description(mut self, description: impl Into<String>) -> Self {
352        self.description = Some(description.into());
353        self
354    }
355
356    /// Load a document from a file path, inferring the MIME type from the extension.
357    ///
358    /// # Errors
359    ///
360    /// Returns `std::io::Error` if the file cannot be read or the extension
361    /// is unrecognized.
362    pub fn from_file(path: impl AsRef<std::path::Path>) -> std::io::Result<Self> {
363        let path = path.as_ref();
364        let ext = path.extension().and_then(|e| e.to_str()).ok_or_else(|| {
365            std::io::Error::new(std::io::ErrorKind::InvalidInput, "missing file extension")
366        })?;
367        let mime_type = mime::from_extension(ext).ok_or_else(|| {
368            std::io::Error::new(
369                std::io::ErrorKind::InvalidInput,
370                format!("unrecognized document extension: {ext}"),
371            )
372        })?;
373        if !mime_type.starts_with("application/") && !mime_type.starts_with("text/") {
374            return Err(std::io::Error::new(
375                std::io::ErrorKind::InvalidInput,
376                format!("MIME type '{mime_type}' is not a document type"),
377            ));
378        }
379        let data = std::fs::read(path)?;
380        Ok(Self::new(data, mime_type))
381    }
382}
383
384impl Audio {
385    /// Creates a new [`Audio`] with the given data and MIME type.
386    ///
387    /// # Examples
388    ///
389    /// ```
390    /// # use agy_bridge::content::Audio;
391    /// let audio = Audio::new(vec![0xFF, 0xFB], "audio/mpeg");
392    /// assert_eq!(audio.mime_type, "audio/mpeg");
393    /// assert!(audio.description.is_none());
394    /// ```
395    pub fn new(data: Vec<u8>, mime_type: impl Into<String>) -> Self {
396        Self {
397            data,
398            mime_type: mime_type.into(),
399            description: None,
400        }
401    }
402
403    /// Creates a new [`Audio`] with MIME type `audio/mpeg` (MP3).
404    ///
405    /// # Examples
406    ///
407    /// ```
408    /// # use agy_bridge::content::Audio;
409    /// let audio = Audio::mp3(vec![0xFF, 0xFB]);
410    /// assert_eq!(audio.mime_type, "audio/mpeg");
411    /// ```
412    #[must_use]
413    pub fn mp3(data: Vec<u8>) -> Self {
414        Self::new(data, mime::AUDIO_MPEG)
415    }
416
417    /// Creates a new [`Audio`] with MIME type `audio/wav`.
418    ///
419    /// # Examples
420    ///
421    /// ```
422    /// # use agy_bridge::content::Audio;
423    /// let audio = Audio::wav(vec![0x52, 0x49, 0x46, 0x46]);
424    /// assert_eq!(audio.mime_type, "audio/wav");
425    /// ```
426    #[must_use]
427    pub fn wav(data: Vec<u8>) -> Self {
428        Self::new(data, mime::AUDIO_WAV)
429    }
430
431    /// Creates a new [`Audio`] with MIME type `audio/ogg`.
432    #[must_use]
433    pub fn ogg(data: Vec<u8>) -> Self {
434        Self::new(data, mime::AUDIO_OGG)
435    }
436
437    /// Creates a new [`Audio`] with MIME type `audio/flac`.
438    #[must_use]
439    pub fn flac(data: Vec<u8>) -> Self {
440        Self::new(data, mime::AUDIO_FLAC)
441    }
442
443    /// Sets a description on this audio, consuming and returning `self`.
444    #[must_use]
445    pub fn with_description(mut self, description: impl Into<String>) -> Self {
446        self.description = Some(description.into());
447        self
448    }
449
450    /// Load audio from a file path, inferring the MIME type from the extension.
451    ///
452    /// # Errors
453    ///
454    /// Returns `std::io::Error` if the file cannot be read or the extension
455    /// is unrecognized.
456    pub fn from_file(path: impl AsRef<std::path::Path>) -> std::io::Result<Self> {
457        let path = path.as_ref();
458        let ext = path.extension().and_then(|e| e.to_str()).ok_or_else(|| {
459            std::io::Error::new(std::io::ErrorKind::InvalidInput, "missing file extension")
460        })?;
461        let mime_type = mime::from_extension(ext).ok_or_else(|| {
462            std::io::Error::new(
463                std::io::ErrorKind::InvalidInput,
464                format!("unrecognized audio extension: {ext}"),
465            )
466        })?;
467        if !mime_type.starts_with("audio/") {
468            return Err(std::io::Error::new(
469                std::io::ErrorKind::InvalidInput,
470                format!("MIME type '{mime_type}' is not an audio type"),
471            ));
472        }
473        let data = std::fs::read(path)?;
474        Ok(Self::new(data, mime_type))
475    }
476}
477
478impl Video {
479    /// Creates a new [`Video`] with the given data and MIME type.
480    ///
481    /// # Examples
482    ///
483    /// ```
484    /// # use agy_bridge::content::Video;
485    /// let video = Video::new(vec![0x00, 0x00], "video/mp4");
486    /// assert_eq!(video.mime_type, "video/mp4");
487    /// assert!(video.description.is_none());
488    /// ```
489    pub fn new(data: Vec<u8>, mime_type: impl Into<String>) -> Self {
490        Self {
491            data,
492            mime_type: mime_type.into(),
493            description: None,
494        }
495    }
496
497    /// Creates a new [`Video`] with MIME type `video/mp4`.
498    ///
499    /// # Examples
500    ///
501    /// ```
502    /// # use agy_bridge::content::Video;
503    /// let video = Video::mp4(vec![0x00, 0x00, 0x00, 0x1C]);
504    /// assert_eq!(video.mime_type, "video/mp4");
505    /// ```
506    #[must_use]
507    pub fn mp4(data: Vec<u8>) -> Self {
508        Self::new(data, mime::VIDEO_MP4)
509    }
510
511    /// Creates a new [`Video`] with MIME type `video/webm`.
512    #[must_use]
513    pub fn webm(data: Vec<u8>) -> Self {
514        Self::new(data, mime::VIDEO_WEBM)
515    }
516
517    /// Sets a description on this video, consuming and returning `self`.
518    #[must_use]
519    pub fn with_description(mut self, description: impl Into<String>) -> Self {
520        self.description = Some(description.into());
521        self
522    }
523
524    /// Load video from a file path, inferring the MIME type from the extension.
525    ///
526    /// # Errors
527    ///
528    /// Returns `std::io::Error` if the file cannot be read or the extension
529    /// is unrecognized.
530    pub fn from_file(path: impl AsRef<std::path::Path>) -> std::io::Result<Self> {
531        let path = path.as_ref();
532        let ext = path.extension().and_then(|e| e.to_str()).ok_or_else(|| {
533            std::io::Error::new(std::io::ErrorKind::InvalidInput, "missing file extension")
534        })?;
535        let mime_type = mime::from_extension(ext).ok_or_else(|| {
536            std::io::Error::new(
537                std::io::ErrorKind::InvalidInput,
538                format!("unrecognized video extension: {ext}"),
539            )
540        })?;
541        if !mime_type.starts_with("video/") {
542            return Err(std::io::Error::new(
543                std::io::ErrorKind::InvalidInput,
544                format!("MIME type '{mime_type}' is not a video type"),
545            ));
546        }
547        let data = std::fs::read(path)?;
548        Ok(Self::new(data, mime_type))
549    }
550}
551
552#[cfg(test)]
553mod tests {
554    use super::*;
555
556    #[test]
557    fn image_struct_serde_roundtrip() {
558        let img = Image {
559            data: vec![10, 20, 30],
560            mime_type: "image/bmp".to_string(),
561            description: Some("bitmap".to_string()),
562        };
563        let json = serde_json::to_string(&img).unwrap();
564        let parsed: Image = serde_json::from_str(&json).unwrap();
565        assert_eq!(parsed, img);
566    }
567
568    #[test]
569    fn document_struct_serde_roundtrip() {
570        let doc = Document {
571            data: b"{}".to_vec(),
572            mime_type: "application/json".to_string(),
573            description: None,
574        };
575        let json = serde_json::to_string(&doc).unwrap();
576        let parsed: Document = serde_json::from_str(&json).unwrap();
577        assert_eq!(parsed, doc);
578    }
579
580    #[test]
581    fn audio_struct_serde_roundtrip() {
582        let audio = Audio {
583            data: vec![0xAA, 0xBB],
584            mime_type: "audio/wav".to_string(),
585            description: Some("beep".to_string()),
586        };
587        let json = serde_json::to_string(&audio).unwrap();
588        let parsed: Audio = serde_json::from_str(&json).unwrap();
589        assert_eq!(parsed, audio);
590    }
591
592    #[test]
593    fn video_struct_serde_roundtrip() {
594        let video = Video {
595            data: vec![0xCC, 0xDD, 0xEE],
596            mime_type: "video/webm".to_string(),
597            description: None,
598        };
599        let json = serde_json::to_string(&video).unwrap();
600        let parsed: Video = serde_json::from_str(&json).unwrap();
601        assert_eq!(parsed, video);
602    }
603
604    #[test]
605    fn image_description_defaults_to_none() {
606        let json = r#"{"data":[1,2,3],"mime_type":"image/png"}"#;
607        let img: Image = serde_json::from_str(json).unwrap();
608        assert!(img.description.is_none());
609    }
610
611    #[test]
612    fn image_new_creates_correct_image() {
613        let img = Image::new(vec![10, 20], "image/webp");
614        assert_eq!(img.data, vec![10, 20]);
615        assert_eq!(img.mime_type, "image/webp");
616        assert!(img.description.is_none());
617    }
618
619    #[test]
620    fn image_png_creates_correct_image() {
621        let img = Image::png(vec![1, 2, 3]);
622        assert_eq!(img.data, vec![1, 2, 3]);
623        assert_eq!(img.mime_type, "image/png");
624        assert!(img.description.is_none());
625    }
626
627    #[test]
628    fn image_jpeg_creates_correct_image() {
629        let img = Image::jpeg(vec![0xFF, 0xD8]);
630        assert_eq!(img.data, vec![0xFF, 0xD8]);
631        assert_eq!(img.mime_type, "image/jpeg");
632        assert!(img.description.is_none());
633    }
634
635    #[test]
636    fn document_new_creates_correct_document() {
637        let doc = Document::new(b"data".to_vec(), "text/plain");
638        assert_eq!(doc.data, b"data".to_vec());
639        assert_eq!(doc.mime_type, "text/plain");
640        assert!(doc.description.is_none());
641    }
642
643    #[test]
644    fn document_pdf_creates_correct_document() {
645        let doc = Document::pdf(b"%PDF-1.4".to_vec());
646        assert_eq!(doc.data, b"%PDF-1.4".to_vec());
647        assert_eq!(doc.mime_type, "application/pdf");
648        assert!(doc.description.is_none());
649    }
650
651    #[test]
652    fn audio_new_creates_correct_audio() {
653        let audio = Audio::new(vec![0xAA], "audio/ogg");
654        assert_eq!(audio.data, vec![0xAA]);
655        assert_eq!(audio.mime_type, "audio/ogg");
656        assert!(audio.description.is_none());
657    }
658
659    #[test]
660    fn audio_mp3_creates_correct_audio() {
661        let audio = Audio::mp3(vec![0xFF, 0xFB]);
662        assert_eq!(audio.data, vec![0xFF, 0xFB]);
663        assert_eq!(audio.mime_type, "audio/mpeg");
664        assert!(audio.description.is_none());
665    }
666
667    #[test]
668    fn audio_wav_creates_correct_audio() {
669        let audio = Audio::wav(vec![0x52, 0x49, 0x46, 0x46]);
670        assert_eq!(audio.data, vec![0x52, 0x49, 0x46, 0x46]);
671        assert_eq!(audio.mime_type, "audio/wav");
672        assert!(audio.description.is_none());
673    }
674
675    #[test]
676    fn video_new_creates_correct_video() {
677        let video = Video::new(vec![0x00], "video/webm");
678        assert_eq!(video.data, vec![0x00]);
679        assert_eq!(video.mime_type, "video/webm");
680        assert!(video.description.is_none());
681    }
682
683    #[test]
684    fn video_mp4_creates_correct_video() {
685        let video = Video::mp4(vec![0x00, 0x00, 0x00, 0x1C]);
686        assert_eq!(video.data, vec![0x00, 0x00, 0x00, 0x1C]);
687        assert_eq!(video.mime_type, "video/mp4");
688        assert!(video.description.is_none());
689    }
690
691    #[test]
692    fn image_new_accepts_string_type() {
693        let img = Image::new(vec![1], String::from("image/gif"));
694        assert_eq!(img.mime_type, "image/gif");
695    }
696}