1use num_enum::{FromPrimitive, IntoPrimitive};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, FromPrimitive, IntoPrimitive)]
15#[repr(u16)]
16pub enum OperationCode {
17 GetDeviceInfo = 0x1001,
19 OpenSession = 0x1002,
21 CloseSession = 0x1003,
23 GetStorageIds = 0x1004,
25 GetStorageInfo = 0x1005,
27 GetNumObjects = 0x1006,
29 GetObjectHandles = 0x1007,
31 GetObjectInfo = 0x1008,
33 GetObject = 0x1009,
35 GetThumb = 0x100A,
37 DeleteObject = 0x100B,
39 SendObjectInfo = 0x100C,
41 SendObject = 0x100D,
43 InitiateCapture = 0x100E,
45 FormatStore = 0x100F,
47 ResetDevice = 0x1010,
49 SelfTest = 0x1011,
51 SetObjectProtection = 0x1012,
53 PowerDown = 0x1013,
55 GetDevicePropDesc = 0x1014,
57 GetDevicePropValue = 0x1015,
59 SetDevicePropValue = 0x1016,
61 ResetDevicePropValue = 0x1017,
63 TerminateOpenCapture = 0x1018,
65 MoveObject = 0x1019,
67 CopyObject = 0x101A,
69 GetPartialObject = 0x101B,
71 InitiateOpenCapture = 0x101C,
73 GetObjectPropsSupported = 0x9801,
75 GetObjectPropDesc = 0x9802,
77 GetObjectPropValue = 0x9803,
79 SetObjectPropValue = 0x9804,
81 GetObjectPropList = 0x9805,
83 SetObjectPropList = 0x9806,
85 GetObjectReferences = 0x9810,
87 SetObjectReferences = 0x9811,
89 GetPartialObject64 = 0x95C1,
92 #[num_enum(catch_all)]
94 Unknown(u16),
95}
96
97#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, FromPrimitive, IntoPrimitive)]
101#[repr(u16)]
102pub enum ResponseCode {
103 Ok = 0x2001,
105 GeneralError = 0x2002,
107 SessionNotOpen = 0x2003,
109 InvalidTransactionId = 0x2004,
111 OperationNotSupported = 0x2005,
113 ParameterNotSupported = 0x2006,
115 IncompleteTransfer = 0x2007,
117 InvalidStorageId = 0x2008,
119 InvalidObjectHandle = 0x2009,
121 DevicePropNotSupported = 0x200A,
123 InvalidObjectFormatCode = 0x200B,
125 StoreFull = 0x200C,
127 ObjectWriteProtected = 0x200D,
129 StoreReadOnly = 0x200E,
131 AccessDenied = 0x200F,
133 NoThumbnailPresent = 0x2010,
135 DeviceBusy = 0x2019,
137 InvalidParentObject = 0x201A,
139 InvalidDevicePropFormat = 0x201B,
141 InvalidDevicePropValue = 0x201C,
143 InvalidParameter = 0x201D,
145 SessionAlreadyOpen = 0x201E,
147 TransactionCancelled = 0x201F,
149 ObjectTooLarge = 0xA809,
151 #[num_enum(catch_all)]
153 Unknown(u16),
154}
155
156#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, FromPrimitive, IntoPrimitive)]
160#[repr(u16)]
161pub enum EventCode {
162 CancelTransaction = 0x4001,
164 ObjectAdded = 0x4002,
166 ObjectRemoved = 0x4003,
168 StoreAdded = 0x4004,
170 StoreRemoved = 0x4005,
172 DevicePropChanged = 0x4006,
174 ObjectInfoChanged = 0x4007,
176 DeviceInfoChanged = 0x4008,
178 StorageInfoChanged = 0x400C,
180 CaptureComplete = 0x400D,
182 #[num_enum(catch_all)]
184 Unknown(u16),
185}
186
187#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, FromPrimitive, IntoPrimitive)]
191#[repr(u16)]
192pub enum ObjectFormatCode {
193 Undefined = 0x3000,
195 Association = 0x3001,
197 Script = 0x3002,
199 Executable = 0x3003,
201 Text = 0x3004,
203 Html = 0x3005,
205 Dpof = 0x3006,
207 Aiff = 0x3007,
209 Wav = 0x3008,
211 Mp3 = 0x3009,
213 Avi = 0x300A,
215 Mpeg = 0x300B,
217 Asf = 0x300C,
219 Jpeg = 0x3801,
221 Tiff = 0x3804,
223 Gif = 0x3807,
225 Bmp = 0x3808,
227 Pict = 0x380A,
229 Png = 0x380B,
231 WmaAudio = 0xB901,
233 OggAudio = 0xB902,
235 AacAudio = 0xB903,
237 FlacAudio = 0xB906,
239 WmvVideo = 0xB981,
241 Mp4Container = 0xB982,
243 M4aAudio = 0xB984,
245 #[num_enum(catch_all)]
247 Unknown(u16),
248}
249
250impl ObjectFormatCode {
251 #[must_use]
255 pub fn from_extension(ext: &str) -> Self {
256 match ext.to_lowercase().as_str() {
257 "txt" => ObjectFormatCode::Text,
259 "html" | "htm" => ObjectFormatCode::Html,
260 "dpof" => ObjectFormatCode::Dpof,
261
262 "aiff" | "aif" => ObjectFormatCode::Aiff,
264 "wav" => ObjectFormatCode::Wav,
265 "mp3" => ObjectFormatCode::Mp3,
266 "wma" => ObjectFormatCode::WmaAudio,
267 "ogg" | "oga" => ObjectFormatCode::OggAudio,
268 "aac" => ObjectFormatCode::AacAudio,
269 "flac" => ObjectFormatCode::FlacAudio,
270 "m4a" => ObjectFormatCode::M4aAudio,
271
272 "avi" => ObjectFormatCode::Avi,
274 "mpg" | "mpeg" => ObjectFormatCode::Mpeg,
275 "asf" => ObjectFormatCode::Asf,
276 "wmv" => ObjectFormatCode::WmvVideo,
277 "mp4" | "m4v" => ObjectFormatCode::Mp4Container,
278
279 "jpg" | "jpeg" => ObjectFormatCode::Jpeg,
281 "tif" | "tiff" => ObjectFormatCode::Tiff,
282 "gif" => ObjectFormatCode::Gif,
283 "bmp" => ObjectFormatCode::Bmp,
284 "pict" | "pct" => ObjectFormatCode::Pict,
285 "png" => ObjectFormatCode::Png,
286
287 "exe" | "dll" | "bin" => ObjectFormatCode::Executable,
289 "sh" | "bat" | "cmd" | "ps1" => ObjectFormatCode::Script,
290
291 _ => ObjectFormatCode::Undefined,
292 }
293 }
294
295 #[must_use]
297 pub fn is_audio(&self) -> bool {
298 matches!(
299 self,
300 ObjectFormatCode::Aiff
301 | ObjectFormatCode::Wav
302 | ObjectFormatCode::Mp3
303 | ObjectFormatCode::WmaAudio
304 | ObjectFormatCode::OggAudio
305 | ObjectFormatCode::AacAudio
306 | ObjectFormatCode::FlacAudio
307 | ObjectFormatCode::M4aAudio
308 )
309 }
310
311 #[must_use]
313 pub fn is_video(&self) -> bool {
314 matches!(
315 self,
316 ObjectFormatCode::Avi
317 | ObjectFormatCode::Mpeg
318 | ObjectFormatCode::Asf
319 | ObjectFormatCode::WmvVideo
320 | ObjectFormatCode::Mp4Container
321 )
322 }
323
324 #[must_use]
326 pub fn is_image(&self) -> bool {
327 matches!(
328 self,
329 ObjectFormatCode::Jpeg
330 | ObjectFormatCode::Tiff
331 | ObjectFormatCode::Gif
332 | ObjectFormatCode::Bmp
333 | ObjectFormatCode::Pict
334 | ObjectFormatCode::Png
335 )
336 }
337}
338
339#[allow(clippy::derivable_impls)]
341impl Default for ObjectFormatCode {
342 fn default() -> Self {
343 ObjectFormatCode::Undefined
344 }
345}
346
347#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, FromPrimitive, IntoPrimitive)]
351#[repr(u16)]
352pub enum ObjectPropertyCode {
353 StorageId = 0xDC01,
355 ObjectFormat = 0xDC02,
357 ProtectionStatus = 0xDC03,
359 ObjectSize = 0xDC04,
361 ObjectFileName = 0xDC07,
363 DateCreated = 0xDC08,
365 DateModified = 0xDC09,
367 ParentObject = 0xDC0B,
369 Name = 0xDC44,
371 #[num_enum(catch_all)]
373 Unknown(u16),
374}
375
376#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, FromPrimitive, IntoPrimitive)]
380#[repr(u16)]
381pub enum PropertyDataType {
382 Undefined = 0x0000,
384 Int8 = 0x0001,
386 Uint8 = 0x0002,
388 Int16 = 0x0003,
390 Uint16 = 0x0004,
392 Int32 = 0x0005,
394 Uint32 = 0x0006,
396 Int64 = 0x0007,
398 Uint64 = 0x0008,
400 Int128 = 0x0009,
402 Uint128 = 0x000A,
404 #[num_enum(catch_all)]
406 Unknown(u16),
407 String = 0xFFFF,
409}
410
411impl PropertyDataType {
412 #[must_use]
417 pub fn byte_size(&self) -> Option<usize> {
418 match self {
419 PropertyDataType::Int8 | PropertyDataType::Uint8 => Some(1),
420 PropertyDataType::Int16 | PropertyDataType::Uint16 => Some(2),
421 PropertyDataType::Int32 | PropertyDataType::Uint32 => Some(4),
422 PropertyDataType::Int64 | PropertyDataType::Uint64 => Some(8),
423 PropertyDataType::Int128 | PropertyDataType::Uint128 => Some(16),
424 PropertyDataType::String
425 | PropertyDataType::Undefined
426 | PropertyDataType::Unknown(_) => None,
427 }
428 }
429}
430
431#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, FromPrimitive, IntoPrimitive)]
441#[repr(u16)]
442pub enum DevicePropertyCode {
443 Undefined = 0x5000,
445 BatteryLevel = 0x5001,
447 FunctionalMode = 0x5002,
449 ImageSize = 0x5003,
451 CompressionSetting = 0x5004,
453 WhiteBalance = 0x5005,
455 RgbGain = 0x5006,
457 FNumber = 0x5007,
459 FocalLength = 0x5008,
461 FocusDistance = 0x5009,
463 FocusMode = 0x500A,
465 ExposureMeteringMode = 0x500B,
467 FlashMode = 0x500C,
469 ExposureTime = 0x500D,
471 ExposureProgramMode = 0x500E,
473 ExposureIndex = 0x500F,
475 ExposureBiasCompensation = 0x5010,
477 DateTime = 0x5011,
479 CaptureDelay = 0x5012,
481 StillCaptureMode = 0x5013,
483 Contrast = 0x5014,
485 Sharpness = 0x5015,
487 DigitalZoom = 0x5016,
489 EffectMode = 0x5017,
491 BurstNumber = 0x5018,
493 BurstInterval = 0x5019,
495 TimelapseNumber = 0x501A,
497 TimelapseInterval = 0x501B,
499 FocusMeteringMode = 0x501C,
501 UploadUrl = 0x501D,
503 Artist = 0x501E,
505 CopyrightInfo = 0x501F,
507 #[num_enum(catch_all)]
509 Unknown(u16),
510}
511
512#[cfg(test)]
513mod tests {
514 use super::*;
515
516 #[test]
517 fn operation_code_standard_ptp_and_mtp_codes() {
518 let cases: [(u16, OperationCode); 13] = [
521 (0x100F, OperationCode::FormatStore),
522 (0x1010, OperationCode::ResetDevice),
523 (0x1011, OperationCode::SelfTest),
524 (0x1012, OperationCode::SetObjectProtection),
525 (0x1013, OperationCode::PowerDown),
526 (0x1018, OperationCode::TerminateOpenCapture),
527 (0x101C, OperationCode::InitiateOpenCapture),
528 (0x9801, OperationCode::GetObjectPropsSupported),
529 (0x9802, OperationCode::GetObjectPropDesc),
530 (0x9805, OperationCode::GetObjectPropList),
531 (0x9806, OperationCode::SetObjectPropList),
532 (0x9810, OperationCode::GetObjectReferences),
533 (0x9811, OperationCode::SetObjectReferences),
534 ];
535 for (raw, expected) in cases {
536 assert_eq!(OperationCode::from(raw), expected);
537 assert_eq!(u16::from(expected), raw);
538 }
539 }
540
541 #[test]
542 fn from_extension_detection() {
543 assert_eq!(
545 ObjectFormatCode::from_extension("mp3"),
546 ObjectFormatCode::Mp3
547 );
548 assert_eq!(
549 ObjectFormatCode::from_extension("flac"),
550 ObjectFormatCode::FlacAudio
551 );
552 assert_eq!(
553 ObjectFormatCode::from_extension("aif"),
554 ObjectFormatCode::Aiff
555 ); assert_eq!(
559 ObjectFormatCode::from_extension("mp4"),
560 ObjectFormatCode::Mp4Container
561 );
562 assert_eq!(
563 ObjectFormatCode::from_extension("avi"),
564 ObjectFormatCode::Avi
565 );
566 assert_eq!(
567 ObjectFormatCode::from_extension("mpg"),
568 ObjectFormatCode::Mpeg
569 ); assert_eq!(
573 ObjectFormatCode::from_extension("jpg"),
574 ObjectFormatCode::Jpeg
575 );
576 assert_eq!(
577 ObjectFormatCode::from_extension("png"),
578 ObjectFormatCode::Png
579 );
580 assert_eq!(
581 ObjectFormatCode::from_extension("tif"),
582 ObjectFormatCode::Tiff
583 ); assert_eq!(
587 ObjectFormatCode::from_extension("txt"),
588 ObjectFormatCode::Text
589 );
590 assert_eq!(
591 ObjectFormatCode::from_extension("htm"),
592 ObjectFormatCode::Html
593 ); assert_eq!(
597 ObjectFormatCode::from_extension("exe"),
598 ObjectFormatCode::Executable
599 );
600 assert_eq!(
601 ObjectFormatCode::from_extension("sh"),
602 ObjectFormatCode::Script
603 );
604
605 assert_eq!(
607 ObjectFormatCode::from_extension("MP3"),
608 ObjectFormatCode::Mp3
609 );
610
611 assert_eq!(
613 ObjectFormatCode::from_extension("xyz"),
614 ObjectFormatCode::Undefined
615 );
616 assert_eq!(
617 ObjectFormatCode::from_extension(""),
618 ObjectFormatCode::Undefined
619 );
620 }
621
622 #[test]
625 fn is_audio() {
626 assert!(ObjectFormatCode::Mp3.is_audio());
627 assert!(ObjectFormatCode::FlacAudio.is_audio());
628 assert!(!ObjectFormatCode::Jpeg.is_audio());
629 assert!(!ObjectFormatCode::Mp4Container.is_audio());
630 }
631
632 #[test]
633 fn is_video() {
634 assert!(ObjectFormatCode::Mp4Container.is_video());
635 assert!(ObjectFormatCode::Avi.is_video());
636 assert!(!ObjectFormatCode::Mp3.is_video());
637 assert!(!ObjectFormatCode::Jpeg.is_video());
638 }
639
640 #[test]
641 fn is_image() {
642 assert!(ObjectFormatCode::Jpeg.is_image());
643 assert!(ObjectFormatCode::Png.is_image());
644 assert!(!ObjectFormatCode::Mp3.is_image());
645 assert!(!ObjectFormatCode::Mp4Container.is_image());
646 }
647
648 #[test]
649 fn format_categories_are_mutually_exclusive() {
650 let all_formats = [
651 ObjectFormatCode::Undefined,
652 ObjectFormatCode::Association,
653 ObjectFormatCode::Script,
654 ObjectFormatCode::Executable,
655 ObjectFormatCode::Text,
656 ObjectFormatCode::Html,
657 ObjectFormatCode::Dpof,
658 ObjectFormatCode::Aiff,
659 ObjectFormatCode::Wav,
660 ObjectFormatCode::Mp3,
661 ObjectFormatCode::Avi,
662 ObjectFormatCode::Mpeg,
663 ObjectFormatCode::Asf,
664 ObjectFormatCode::Jpeg,
665 ObjectFormatCode::Tiff,
666 ObjectFormatCode::Gif,
667 ObjectFormatCode::Bmp,
668 ObjectFormatCode::Pict,
669 ObjectFormatCode::Png,
670 ObjectFormatCode::WmaAudio,
671 ObjectFormatCode::OggAudio,
672 ObjectFormatCode::AacAudio,
673 ObjectFormatCode::FlacAudio,
674 ObjectFormatCode::WmvVideo,
675 ObjectFormatCode::Mp4Container,
676 ObjectFormatCode::M4aAudio,
677 ];
678
679 for format in all_formats {
680 let categories = [format.is_audio(), format.is_video(), format.is_image()];
681 let true_count = categories.iter().filter(|&&b| b).count();
682 assert!(
683 true_count <= 1,
684 "{:?} belongs to multiple categories",
685 format
686 );
687 }
688 }
689
690 #[test]
693 fn property_data_type_byte_size() {
694 assert_eq!(PropertyDataType::Int8.byte_size(), Some(1));
696 assert_eq!(PropertyDataType::Uint8.byte_size(), Some(1));
697 assert_eq!(PropertyDataType::Int16.byte_size(), Some(2));
698 assert_eq!(PropertyDataType::Uint16.byte_size(), Some(2));
699 assert_eq!(PropertyDataType::Int32.byte_size(), Some(4));
700 assert_eq!(PropertyDataType::Uint32.byte_size(), Some(4));
701 assert_eq!(PropertyDataType::Int64.byte_size(), Some(8));
702 assert_eq!(PropertyDataType::Uint64.byte_size(), Some(8));
703 assert_eq!(PropertyDataType::Int128.byte_size(), Some(16));
704 assert_eq!(PropertyDataType::Uint128.byte_size(), Some(16));
705
706 assert_eq!(PropertyDataType::String.byte_size(), None);
708 assert_eq!(PropertyDataType::Undefined.byte_size(), None);
709 assert_eq!(PropertyDataType::Unknown(0x1234).byte_size(), None);
710 }
711}