Introduction
The Windows operating system has supported importing photos & videos from digital cameras and memory cards for a long time through various APIs, for example Windows Image Acquisition (WIA) and Windows Portable Devices (WPD).
Although useful, those programming interfaces are either antique or relatively low-level and cumbersome to use, and in general not well suited to the asynchronous programming model of modern Windows applications built on top of the Windows Runtime.
With Windows 10, Microsoft introduced the Windows Photo Import API, a new, fully asynchronous application programming interface that is part of the Universal Windows Platform.
The API can be consumed from Windows Store applications written in JavaScript, C# and C++/CX, as well as from classic Win32/COM applications written in C++, with the help of the Windows Runtime C++ Template Library (WRL), a modern successor to the respected Active Template Library that many developers known and love, or using the MIT-licensed Modern CPP framework from Kenny Kerr (references below).
This article describes the Windows Photo Import API and illustrates its use with code snippets written in the C# programming language, for a hypothetical Windows Store application. Displaying information on the screen (XAML markup) and gathering input from the user are topics beyond the scope of this article, which mainly focuses on consuming the API.
There is no downloadable code associated with this article, but you can download a fully working sample, part of the Windows SDK, hosted on GitHub (see the Developer Links section below).
At the end of the article, the use of the API from standard C++ and Win32 is briefly discussed.
TL;DR
As a teaser, the statement below enumerates all recognized devices (FindAllSourcesAsync), arbitrarily picks the first one ([0]) and connects to it (CreateSessionAsync). It then finds and selects all importable items on the device (FindItemsAsync), then finally imports everything to the user's picture library (ImportItemsAsync), all in one line of code: the API is said to be fluent, which enables concise notation.
Note that this statement will crash (dereferencing the first element of an empty list) if there are no devices connected: this is not real code like you would write in an application, but it still does all that was said above.
You must declare the picturesLibrary and removableStorage capabilities in your application manifest for the API to accept to work for you, then add a "using Windows.Media.Import;" statement at the top of your source file(s) to make the PhotoImport* types available. That's it.
var result = await (await (await PhotoImportManager
.FindAllSourcesAsync())[0]
.CreateImportSession()
.FindItemsAsync(PhotoImportContentTypeFilter.ImagesAndVideos, PhotoImportItemSelectionMode.SelectAll))
.ImportItemsAsync();
In a real program, you probably want to split that statement into different parts: check that there is indeed some device(s) to import from, let the user pick one, and perhaps specify a few options such as where to save the files. Then maybe add one more call to the chain to delete imported files from the device at the end, as well as hook up some progress indicator and Cancel button.
Still, the essence is there: you can add photo and video import to your app today and with minimal effort.
Developer Links
Official documentation: Windows.Media.Import Namespace (MSDN)
Official Windows SDK Sample: MediaImport Sample (Microsoft Universal Samples on GitHub)
MSDN Tutorial related to the Windows SDK sample: Import media from a device.
Background
The Windows Photo Import API was designed from scratch for Windows 10 to support modern, modeless workflows where an application initiates a transfer operation that runs asynchronously, entirely in the background, and thus does not block while the operation runs.
The background operations support progress notifications, events and cancelation, so the application remains in full control.
For classic (Win32/COM) applications, the Photo Import API runs as an in-process COM object instance. Its instantiation and consumption is facilitated by the use of the WRL template library or the Modern CPP framework.
For Windows Store applications, the Photo Import API runs out-of-process, hosted in the Runtime Broker, a per-user system process providing services to Windows Store applications executing on top of the Windows Runtime.
The API is projected into C++/CX, a version of C++ with some Microsoft-specific extensions that help consume Windows Runtime types, as well as to the C# and JavaScript languages for Windows Store applications.
Its use feels natural and native in those various languages, for example the underlying IVectorView<T> is projected as an IReadOnlyList<T> in C#, so C# programmers will consume it just like they would consume any other .NET API, simply by referencing it through a using
directive at the top of their source file(s).
The import operations running in the background continue to execute during Windows Store application suspensions and terminations induced by the Process Lifetime Manager (PLM), which can be suspend, terminate and subsequently restart apps as it sees fit. Windows Store application gets suspended when minimized to the taskbar, for example, or hidden from view, and can be terminated without further notice if the system needs to reclaim some memory.
The app is still shown as running on the Windows taskbar of course, and the PLM resumes, respectively restarts the application when the user interacts with it again, for example by clicking the taskbar icon to restore it, or using Alt+Tab to switch to it.
Most of the time, the users are unaware of the suspensions and terminations, but applications must deal with these events and reconnect to resources whenever they get reactivated. The Photo Import API includes a mechanism where a restarted application can reconnect to ongoing operations and reattach its progress and completion handlers. Note that classic Win32/COM applications are not subjected to PLM suspensions or terminations in any way.
The API itself is implemented as a WRL-based C++ component that is part of the Windows Runtime surface, the ABI, or Application Binary Interface, and is exposed to all 64 bit and 32 bit Windows Runtime and Win32/COM clients on all Windows 10 variants, from IoT to Phone to Desktop to XBox to Hololens, that is, it is part of the Universal Windows Platform, or more precisely it's a component of OneCore UAP.
There are, however, places where importing photos through an USB host connection does not make sense, and other cases where the underlying stacks and drivers are not present, for example the N and KN Windows variants available in some geographies are lacking components related to Digital Rights Management (DRM) and multimedia playing, which unfortunately extends to portable devices.
To allow you application to load everywhere, as Universal Windows Apps should, the API is present in all those places, but is not necessarily functional. The Photo Import API exposes a mechanism that let your application figure out at runtime if the API is functional on the particular platform variant your application happens to execute at that moment.
Note that in the case of the N and KN SKUs, the end-users can manually download and install the Media Feature Pack for Windows 10 to restore the full media & DRM functionality of the platform, so your application not being able to use the Photo Import API on a given machine at a given moment does not mean it will not work on the exact same machine a few moments later: applications must query to find out if the Photo Import API is supported each time they start.
The Windows 10 Photo Import API defining features:
- Windows Runtime asynchronous programming model with true background operations.
- Very robust file operations: your application can crash and restart without affecting the transfers.
- Import photos & videos from PTP/MTP and MSC devices (virtually all digital cameras and smart-phones).
- Import photos & videos from removable devices such as USB sticks and memory cards in USB readers.
- Support for camera RAW files and sidecar files (.WAV audio annotations, .XMP metadata, ...).
- Support for segmented video files (e.g. GoPro videos split in multiple 2 GB segments).
- Built-in de-duplication mechanism automatically preselects newly added content (1 million items history).
- Supports up to 64 concurrent import sessions from as many distinct devices.
- Carefully optimized for best transfer performance on USB 2.0 & USB 3.x devices and beyond.
- Built-in support for future protocols (such as PTP/IP), as they are added to the Windows platform.
General workflow
The photo import workflow with the Windows Photo Import API is split into several simple phases: source device selection, content enumeration, content transfer and, optionally, source content deletion.
Let's look at each of them in detail:
1. Select a source to import from
First, comes the device discovery, where the API enumerates and returns the list of available data sources to the application. USB-connected smart phones and digital cameras are on that list, as well as memory cards containing photos and videos plugged into USB card readers connected to the computer. Most card readers (USB 2.0, USB 3.x and PCIe card readers) should be supported. The API itself is not limited to USB-connected data sources and is ready to accommodate future connection mechanisms, such as PTP/IP for over-the-air wireless transfers.
Note: the RTM version of Windows 10 had a few compatibility issues with some devices and also a few bugs. Notably, a firmware issue affected many Nikon and Fuji digital camera models whose devices were always seen as empty, despite containing many photos. Also, the upgrade process left many users without access to their Documents folder, which includes their Pictures Library, due to a migration bug and as such were unable to import images using any application. Finally, some older-generation SATA-attached card readers were not recognized as valid import sources due to driver bugs incorrectly reporting the nature of their devices, and the Import API itself had a couple of bugs on its own. While importing photos & videos worked fine for most people, a small number of users were hit by one or more of the above issues and had a negative experience importing in Windows 10. Fortunately, most of these matters have been resolved since.
Back to our subject: the application selects a source device to import from and creates an import session with the device in question. In the AutoPlay scenario, the device selection phase is implicit as the system passes the plug-and-play device ID of the source to import from directly to the application, as part of its activation: the application thus creates an import session directly, using the system-supplied Plug'n'Play ID of the device.
Applications can compose with the DeviceWatcher class to get notified of devices being plugged and unplugged from the system, using the DeviceClass.PortableStorageDevice enumeration, or compose with AutoPlay to get notified of device's insertions using the WPD\ImageSource, StorageOnArrival, ShowPicturesOnArrival and MixedContentOnArrival AutoPlay events (the details of AutoPlay are beyond the scope of this article).
Application can also register themselves to handle the ms-photos:import Uri protocol (details at the end of this article) or their own similar protocol, to allow other applications to launch them and automatically trigger the import workflow from a given device: for example the Phone Companion app in Windows 10 directly launches the import workflow of the Windows Photos app on a specific device through this exact mechanism.
Once an import session has been created, you can start interacting with the device and the next step is to find some content to import.
2. Find and select content to import
Within the import session created in the previous phase, the application initiates a content enumeration operation which finds all importable items on the device. The application can tell the API to filter on the content type: photos, videos, or both.
The application can also tell how the initial item selection should be made, between not selecting any of the importable content, selecting only new content using the built-in de-duplication mechanism, or select everything.
After the enumeration phase completes, the API returns a list of matching items found on the device, with their selection flag set appropriately.
The application can optionally specify the import destination, the default being the current user's photo library default save location. The application can also set a few other options, some of which are described later in this article.
Finally, the application can allow the user to manually select the items to import by displaying thumbnails with checkboxes in some list view. The related XAML data binding and underlying view model are beyond the scope of this article: see the MediaImport SDK sample linked above for a simple thumbnail view implementation, that you can use as a starting point.
The API exposes methods that map directly to the usual Select All / Select None buttons typically found in selection user interfaces, as well as a Select New method that automatically selects new content based on the de-duplication mechanism, so users can return to the default initial selection if they want.
It should be noted that, in the case of Windows Store applications, the list elements are stored in a different process and that accessing the element's properties is relatively costly, as it incurs a local RPC call across process boundaries for each property retrieved. Is is OK to retrieve the item's properties on-demand, for example in a list view that retrieves the properties as items gets displayed page by page, but it is probably not OK to walk the entire list upfront and retrieve each item's properties at once in a tight loop. If you want to select or deselect all items at once, or return to the default selection, the API has functions for that, that runs instantly on the server side. For Win32 applications, the Photo Import component runs in-process and no extra cost is incurred.
The destination can be a local folder or a network share. Note that, by default, the files are likely to be written to the C: drive, where the user's Documents folder and Pictures Library usually resides. There is nothing wrong with that, but be aware that a lot of processes in Windows are "interested" in the arrival of new pictures and videos, in particular the system indexer and property caching system, as well as the thumbnail cache. Those services also write to the C: drive and compete with the import operation. If your application requires maximum performance, it is wise to write the imported files to a different drive.
3. Transfer content from the device to the host
Once the content to import has been selected, the next phase is logically the content transfer operation, where the API imports all selected items in batch and in the background. The application can optionally direct the API to ignore sidecars, siblings, or both, and only import the main items. See below for more details about the Photo Import API file handling.
Progress is reported during the transfer operation: the number of bytes imported so far, the number of items imported so far, the total number of bytes to import, the total number of items to import as well as progress, as a double-precision floating-point value in the range [0..1] inclusive. You can multiply this value by 100 if you want to display progress as a percentage: var percent = (int)(progress * 100 + 0.5);
The client can also subscribe to an "ItemImported" event to be notified in real time, as soon as an item has been successfully imported.
It is important to note that no significant processing should be done in the progress handler or the "ItemImported" event handler. However, setting the value of a progress indicator or computing/displaying the import statistics is of course perfectly fine.
The "ItemImported" event can be used to display the name of the files as they land on the end-user's system, or to queue those items for later processing, perhaps by adding them to a list that gets processed in parallel by the application. The key point is to always return as quickly as possible from the event handlers.
I recommend against flashing thumbnails of the imported files in your user interface, as extracting thumbnails from the destination files will disrupt the data flow from the device and to your hard drive. If you really want to do this, try to limit the number of thumbnails displayed by keeping a given thumbnail displayed for at least a few seconds before considering to replace it. You will get the nice visuals you are after, the user will have the time to see the image, and you will minimize the slowdown by limiting the churn.
Keep in mind the application should not compete with the background import operations: attempting to process the files as they are imported (and, say, save the result back to disk) will disrupt the steady data flow from the device to the host by creating contentions on the disk, and this may cause performance degradations that might not be significant when importing from slow devices, but which will definitely hurt when importing from faster peripherals, where the files lands on the host much quicker than they can be processed: you will get 10-15 photos per second from an Apple iPhone 6S device over USB 2.0, and sometimes much more from a fast memory card in a fast USB 3.0 card reader, so the less you do during import, the better.
You can take advantage of the asynchronous nature of the API to keep your application fully responsive during long transfers, but do not assume it's OK to perform heavy processing implying a lot of disk I/O, because your drive is pretty busy already, or at least it will be when you import from fast sources.
Events are fired asynchronously from the Photos Import API back into client applications, so if the application takes too long to process progress and "ItemImported" notifications, the import background task will not slow down but subsequent events will be dropped as the API takes care of not having more than one outstanding event of each kind per client: if an "ItemImported" event notification should be sent but the previous one is still being processed by the client, the API will simply drop the event and continue without waiting for the previous call to the event handler to return.
Applications running on top of the Windows Runtime can easily be suspended, and even terminated in some circumstances. The end-user cannot tell but when an application is not visible (say, it was minimized to the taskbar) the application is frozen in a "suspended" state to conserve energy.
After a while, the Windows Runtime's Process Lifecycle Manager can even decide to kill the application entirely and remove it from memory to reclaim resources.
When the end-user restores or switches back to the application, it is unfrozen and the app resume where it was, as if nothing happened, or a new process is started automatically and the application must take special care to restore its state and return to the point it was before being suspended, then killed.
The details of Windows Runtime application states and lifecycle are beyond the scope of this article, but the takeaway is that events and notifications from the API to the app can and will be missed.
You can use the events to notify the user but if you queue or flag files for processing - which is good as it might give whatever processor a head start - you must still plan to walk the list of imported files at the end and ensure you caught everything.
Obviously, no processing occurs when the app is suspended and even less so when it's terminated and completely gone: special care must be taken to be able to reconnect to pending background operations after a suspend/terminate/restart sequence (more on this later).
The initial (RTM 10240) version of the Photo Import API in Windows 10 reports progress and checks for cancelation only on file boundaries. This is not a problem for typical real-world operations, where the most common workload consists in importing many photos and a few videos from a smart phone, but when importing a single large file, say a long video, the progress jumps from 0% to 100% only after that single file has been imported, which may be slightly inconvenient as the user will not see your progress indicator move while that large file gets imported.
Similarly, if the import batch gets canceled by the user, the file currently being imported still goes to completion before the actual cancelation occurs.
The Photo Import API that ships as part of the November 2015 update (version 1511) addresses those concerns and provides fine-grained progress and cancelation, without requiring any change to existing clients: no recompilation or redeployment required and apps simply get finer updates of the progress.ImportProgress floating point value that is part of the progress reports, including intermediate updates while large files are imported.
The transfer performance of the Photo Import API has been carefully tuned to take maximum advantage of your device's bandwidth, and it will scale well with your hardware and bus speeds.
"Lies, damned lies and benchmarks" is as true as ever but I still give you a few data points:
This API has been seen importing a multi-gigabyte video file from a fast USB 3.0 key (ExtremePro CZ88) to an SSD hard drive (840PRO) at a sustained, real-world rate of 230 MB/s on a decent PC.
When importing typical smartphone shots from a very fast USB 3.0 source representative of tomorrow's devices, connected to a very modern SSD-equipped Z440 development workstation, the API has been seen importing more than 130 files per second over a 12,600 files payload, which completed in about 95 seconds.
In another test, more synthetic, designed to measure the overhead of the API and underlying stack, including the file-system, I/O subsystem and driver overhead but without being bound by SSD flash memory speed, a hundred Nikon D810 files, weighting approximately 40 MB each, were imported from a ramdisk to another ramdisk at a rate of 2.4-2.5 GB/s - that's 4 GB worth of files imported in about 1.65 second.
Keep in mind we are talking end-to-end scenarios where disk files are copied from a drive to another drive, not low-level "up to" figures from a hardware manufacturer. In the ramdisk test, the Photo Import API beats XCOPY by approximately 10% and performs within the same percent on SSD tests.
The Photo Import API is optimized to transfer large multimedia files and tries to stream relatively large blocks of data smoothly. The maximum speed is achieved when importing from a mass-storage device (USB 3.x or PCIe card reader) and writing to an SSD, preferably not C: to reduce contentions. The version of the API that will be part of Windows 10 "Redstone" mid-2016 will be slightly faster, still, and a bug - fixed deeper in the platform - will light-up some older (SATA-based) card readers that reportedly did not work with this API in the original and November 2015 versions of Windows 10.
As always, YMMV greatly depending on your conditions, test rig, file sizes, and a sheer number of other things but, for sure, the speed limit of your photo and video transfers will likely be somewhere else than in the Windows 10 Photo Import API: if you are looking for a fast import solution that scales well with future hardware advances, this is it!
The Photo Import API in Windows 10 version 1511 runs up to 3x faster(!) than Windows Live Photo Gallery 2012, or than the old built-in "Import Pictures and Videos" wizard, when importing from an iPhone 6S, and even more than that when dealing with USB 3.0 devices or the latest USB 3.1 with USB Type-C peripherals :-)
♦ Windows 10 "RS1" (Summer 2016)
The Photo Import API on Windows 10 "RS1" is a bit faster still. It has been shown sustaining a solid disk write efficiency average of 92-93% using the Windows Performance Analyzer, that is, pretty much perfect considering the measurements were made writing to the C:\ drive of a live retail system, without disabling any service or process, and the total disk time was pretty much 100% during the test, that is, the computer's SSD would not write any quicker. As a side note, the Windows Performance Analyzer is a fantastic tool, if you are inclined towards super-detailed low and very low-level performance analysis.
4. Delete imported content from the device (optional)
The application can direct the API to remove the imported content from the device in the optional content deletion operation. The API will only ever delete the files that were successfully imported due to the "chained" nature of its fluent interface, so there is no risk to inadvertently delete a file from the user's device, unless it was first successfully imported to the host during the current import session. The deletion operation also reports progress and is cancelable.
Using the Photo Import API from a Windows Store app
Since the Photo Import API accesses the user's photo library and the removable device(s) attached to their computer, the first task to accomplish to enable access to the Photo Import API is to declare the "removableStorage" and "picturesLibrary" capabilities in the application manifest:
<Capabilities>
<uap:Capability Name="removableStorage" />
<uap:Capability Name="picturesLibrary" />
...
</Capabilities>
Failure to do so will result in Access Denied exceptions thrown when attempting to access the API, and those declared capabilities will also direct Windows to ask for specific user consent when the application is first installed.
Since the Photo Import API is part of the Universal Windows Platform, it is available on a wide variety of platforms, all of them in fact, however not all platforms includes the underlying device drivers and MTP stack, so the first thing a Universal application should do is ask if the API is supported on the platform the app is currently running on:
using Windows.Media.Import;
...
if (await PhotoImportManager.IsSupportedAsync())
{
}
If not supported, the application should typically refrain from displaying the user-interface related to the import experience, for example an Application Bar "Import" button or some menu item can be hidden or disabled.
Scenario 1: User-triggered import workflow
The import workflow is said to be user-triggered when started explicitly, for example by pressing or tapping some "Import" button in the application's user-interface.
The application should enumerate the connected devices and present them in a list, so the user can pick the one s-he wants to import from. Where only one device is found, the application should still display the device selection list to provide a consistent user experience and avoid jumping on a device that might not be the right one, say for example when the user has an external USB-attached HDD connected and his-her camera is off.
Enumerating the available devices is done in a straightforward manner: assuming that sourceListBox
as a list control, you can bind the result of the static FindAllSourcesAsync method directly:
sourceListBox.ItemsSource = await PhotoImportManager.FindAllSourcesAsync();
You typically bind the properties of the PhotoImportSource using XAML to display the device names in a list and let the user pick one. See the MSDN sample for more details.
Import sources have many properties, such as display name, manufacturer, model, serial number, type, battery level, media list with free/used space for each medium and more.
You can display as few or as many details as your application requires, for example the following item template markup simply shows the display name and description:
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0" Text="{Binding DisplayName}" />
<TextBlock Grid.Column="1" Grid.Row="0" Margin="10,0,0,0" Text="{Binding Description}" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
Thumbnail properties, such as PhotoImportSource.Thumbnail, are unfortunately not bindable at the time of this writing, due to a gap in the XAML platform. The SDK sample demonstrate a technique to work around this limitation (see the item's list view).
Scenario 2: AutoPlay
AutoPlay (and Uri protocol activation) requires less interaction with the user. The shell starts your application, when associated with the device, and directly passes the device ID as a property of the DeviceActivatedEventArgs that you receive on activation. All there is to do is use the device ID to create the session.
Assuming the args
variable is of type DeviceActivatedEventArgs, a PhotoImportSource is created by calling the static FromIdAsync() method of the PhotoImportSource runtimeclass itself, as follow:
var source = await PhotoImportSource.FromIdAsync(args.DeviceInformationId);
You can also create an import source from a StorageFolder, for example some devices are mounted in Explorer as external drives and you can easily create an import source from the root folder of those drives using PhotoImportSource.FromFolderAsync()
.
Checking the device's lock state
Unlike digital cameras and bare memory cards, smartphones can be locked and most of them do not allow access to their content until the user unlocks their phone, and sometime also explicitly allow the host computer to access the pictures and videos by tapping "allow" in response to an additional prompt on the device.
The PhotoImportSource class has an IsLocked() method that you should call to find out if the device is locked.
Unfortunately, at the time of this writing at least, the method only works for Windows Phone devices. Fortunately, it returns a tri-state value in the form of a reference to a boolean, which can thus be null, true or false, and where null means "don't know."
When the value is false (device not locked) just move-on down the workflow. When the value is true, you probably want to prompt the user to unlock his-her device and then click some "retry" button that you provide.
You could consider polling the lock state at some reasonable pace - say once every 2 seconds, which gives you a one-second average lag - and auto-continue as soon as the device is found to be unlocked.
The last case - where the result is null - will likely be the most common if the respective smartphone's market shares are any indications. What you should do when you get a null reference out of IsLocked() is try to probe the storage media and get their capacity. Locked devices either hide their storage or report them as zero size, so if you get any error retrieving the media characteristics, or the reported total capacity is zero, you can safely assume the device is locked and prompt the user accordingly.
The PhotoImportSource::StorageMedia property returns a list of PhotoImportStorageMedium objects which describe the device's storage(s) in detail. Again, getting an empty collection, or getting a collection of media whose total capacity (PhotoImportStorageMedium::CapacityInBytes) is zero, or catching any error while probing the storage medium should be interpreted as a "device locked" situation.
Creating an import session
Once a device has been selected, either manually or implicitly, and you are satisfied that the device is accessible (see above), your app can establish a connection to the device by creating an import session, of type PhotoImportSession:
var session = source.CreateImportSession();
A session can be configured in various ways, for example, you can set the destination folder, a prefix for the destination files, and how to create subfolders, if desired.
In the below snippet, folder
is a StorageFolder that you obtained for example from a folder picker.
session.DestinationFolder = folder;
session.AppendSessionDateToDestinationFolder = true;
session.DestinationFileNamePrefix = "Holidays in Switzerland - ";
session.SubfolderCreationMode = PhotoImportSubfolderCreationMode.DoNotCreateSubfolders;
The API can create different types of subfolders for you on the destination:
Say the import root folder is "C:\Users\<alias>\Pictures".
Adding the session date by setting session.AppendSessionDateToDestinationFolder to true will create something like "C:\Users\<your_alias>\Pictures\YYYY-MM-DD nnn" automatically, where the current date is used and where nnn is a sequential number starting at 001.
The API can flatten all files (either to the import root, or to the session date folder created within the root), or it can recreate the folder hierarchy of the source, when the source is a mass-storage device, by setting session.SubfolderCreationMode to PhotoImportSubfolderCreationMode.KeepOriginalFolderStructure.
Finally, the API can also create date-based sub-folders in the form YYYY-MM using either the item's creation/modification date, whichever is older, or the EXIF date-taken as date source.
The two folder creation modes are orthogonal and can be combined: setting the AppendSessionDateToDestinationFolder property to true and setting the SubfolderCreationMode property to PhotoImportSubfolderCreationMode.CreateSubfoldersFromFileDate will result in folders in the form "C:\Users\<alias>\Pictures\YYYY-MM-DD nnn\YYYY-MM" where the last level is based on file dates.
♦ Windows 10 "RS1" (Summer 2016)
A new session property has been added to control the folder name format. For example, to set the format to YYYY-MM-DD, set session.SubfolderDateFormat to PhotoImportSubfolderDateFormat.YearMonthDay.
session.SubfolderDateFormat = PhotoImportSubfolderDateFormat.YearMonthDay;
Another property has been added to the session to control the future behavior of items that have been deselected by the user - provided that you expose the user interface to do so. By default, items that were deselected are still considered as new the next time around, as they were effectively never imported.
This forced users to deselect the same items over and over when they never wanted to import some of their items. The "RS1" version of the API adds a new property to the session to control this behavior: setting session.RememberDeselectedItems to true will cause the API to remember those deselected items, so they stay deselected in future sessions. The end-user can always manually-re-select any item (you'd just set the item's IsSelected property back to true).
session.RememberDeselectedItems = true;
This new property is only available on Windows 10 "RS1" and only when you compile your application against the RS1 SDK. On TH2 or TH1 the property was simply not where, and if you run your TH1/TH2 application on RS1, it still has no knowledge of the existence of the new RS1-and-later-only properties.
Note that the EXIF mode is noticeably slower, as each file must be opened on-the-fly and their EXIF metadata parsed to figure out into which folder they should land, so I'd not recommend using EXIF dates as the default.
If you elect to expose those options to your users, let them pick between "By session" or "By date" first, and when they pick "By date", let them additionally pick between "Use file dates (fast)" or "Extract EXIF dates (slow)" so they can make an informed decision.
EXIF date extraction does not work for videos and causes much extra burden for photos, in particular for raw files where a raw codec must be loaded and initialized for each file, so it should really not be the default: keep it as a power user option!
You can play with most options using the Windows SDK's MediaImport sample linked above to get a feel regarding how things work.
Enumerating the import source's content
The next step is to enumerate the device's content. To that effect, your app calls FindItemsAsync(), passing a couple of parameters to control what is returned and what gets selected by default:
var foundItems = await session.FindItemsAsync(PhotoImportContentTypeFilter.ImagesAndVideos,
PhotoImportItemSelectionMode.SelectAll);
You can easily enumerate only photos (or only videos) and preselect all items, only new items, or no items.
If you want to display progress and allow cancelation, you must create a cancelation token and a progress handler, and map the result to a Task.
Note that we do not know in advance how many items will be found, as such the progress should be indeterminate and the progress callback simply provides the number of items found so far, as a non-negative integer.
var cts = new CancellationTokenSource();
...
var progress = new Progress<uint>((result) =>
{
page.NotifyUser(String.Format("Found {0} Files", result.ToString()), NotifyType.StatusMessage);
});
var foundItems = await session.FindItemsAsync(typeFilter, selectionMode).AsTask(cts.Token, progress);
Selecting content to import
Once FindItemsAsync() completes, you are left with a PhotoImportFindItemsResult instance, which exposes several methods and properties and contains a list of found items. Note that the default item selection has been specified in the previous step. The preferred default selection mode is "SelectNew", which marks new content for transfer.
You can expose the methods and properties of the PhotoImportFindItemsResult through the user interface or preference options, depending on the level of interactivity and sophistication you want to provide.
foundItems.SelectNone();
foundItems.SelectAll();
await foundItems.SelectNewAsync();
foundItems.SetImportMode = PhotoImportImportMode.ImportEverything;
♦ Windows 10 "RS1" (Summer 2016 Anniversary Update)
The "RS1" version of the Photo Import API adds one more method, letting you efficiently select a date range without having to walk the item list yourself.
foundItems.AddItemsInDateRangeToSelection(start, duration);
The new method selects an interval said to be "semi-open to the right", i.e. [start, start+duration), that is, the start is included, but the end is not, avoiding inadvertent overlaps. For example, say - for the sake of discussion - that you want to add a range covering the whole of January 1. You would set the start to Jan 1 @ 00:00 and the duration to 24:00:00.000 and you will select the entire day but not an image that was taken exactly on Jan 2 @ 00:00, as you would if the end of the interval was included, as Jan 1 @ 00:00 + 24h is exactly Jan 2 @ 00:00.
Note that this call is cumulative: it does not clear whatever is already selected but adds to the current selection, thus the method name. If you want to select exactly one range, call SelectNone() first.
You can easily propose presets such as "Last weekend", "Last week", "Last month" etc.backwards, or implement a slider that the user can drag backward, say go back 30 days with the slider dragged all the way. You can set 'start' to the current date/time and use a negative TimeSpan as duration if you want: to select today, you can use tomorrow @ 00:00 and a duration of -24:00 hours.
This new method is only available on Windows 10 "RS1" and only when you compile your application against the RS1 SDK. On TH2 or TH1 the function was simply not where, and if you run your TH1/TH2 application on RS1, it still has no knowledge of the existence of the new RS1-and-later-only methods.
Each PhotoImportItem on the list of found items has its own IsSelected
boolean property that can be individually set. Typically, this would be done through the mean of a list view, where the end-user would check/uncheck items to mark them for transfer or not.
Assuming at least one item was found on the device, the following snippet will mark the first item in the list for import:
foundItems.FoundItems[0].IsSelected = true;
The PhotoImportFindItemsResult class exposes several counters, such as the number of items found (with a breakdown of photos, video, sidecars, and siblings), and the number of selected items, which is dynamic and varies as the selection methods are called, or as the IsSelected
property of each item is set.
var count = foundItems.TotalCount;
var selected = foundItems.SelectedTotalCount;
var selectedPhotos = foundItems.SelectedPhotosCount;
var selectedPhotosSize = foundItems.SelectedPhotosSizeInBytes;
The PhotoImportFindItemsResult also has a SelectionChanged event your application can subscribe to, to get notified of selection changes as they occur. See the SDK sample for details.
Importing selected content
Importing content is just as straightforward: call ImportItemsAsync() on the found items:
var importedItems = await foundItems.ImportItemsAsync();
A more sophisticated version with cancelation token, per-item events and progress reporting:
var cts = new CancellationTokenSource();
...
var progress = new Progress<PhotoImportProgress>((result) =>
{
progressBar.Value = result.ImportProgress;
});
foundItems.ItemImported += async (s, a) =>
{
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
page.NotifyUser(String.Format("Imported: {0}", a.ImportedItem.Name),
NotifyType.StatusMessage);
});
};
var importedItems = await foundItems.ImportItemsAsync().AsTask(cts.Token, progress);
Deleting imported content from the source device
Deleting flows naturally from the previous step: call DeleteImportedItemsFromSourceAsync() on the PhotoImportImportedItems instance returned by the ImportItemsAsync() task:
var deleteResult = await importedItems.DeleteImportedItemsFromSourceAsync();
...or, with progress and cancelation:
var cts = new CancellationTokenSource();
...
var progress = new Progress<double>((result) =>
{
progressBar.Value = result;
});
var deleteResult = await importedItems.DeleteImportedItemsFromSourceAsync()
.AsTask(cts.Token, progress);
Closing sessions (IClosable/IDisposable)
The type PhotoImportSession is closable/disposable, and your application must call Close() on it (or Dispose(), depending on the language you use) whenever it is done with it.
Failure to do so will cause the API to hold resources for a long time after your application has exited.
A good moment to dispose of the session is when you end the import workflow, after each asynchronous operations have completed, say when you display a summary at the end with a [Close] button.
Putting the call that disposes of the session in the handler of the Close button of that summary would be a good choice. Calling Close/Dispose multiple times has no adverse effect (the function is said to be idempotent).
session.Dispose();
You do not need to keep a reference to the session that you created, as all result classes have a Session property that let you access it whenever you need it.
If you call Close()/Dispose() while any asynchronous operation is still running, the call will fail with ERROR_BUSY. This behavior is justified on MSDN: "the caller must wait for all outstanding asynchronous operations to complete before calling Close".
Currently, Windows Store apps cannot distinguish between suspensions and terminations induced by the user clicking the red [X] at the top-right of the application window, so there is no practical way to decide to terminate the background running tasks or not.
The right thing to do is to let the tasks run and implement the terminate/restart scenario properly, so the app reconnects to any (still) running operation(s) when restarted, see the next section for details.
Supporting Terminate/Restart (PLM termination)
The Photo Import API implements a particular mechanism that allows restarted applications to reconnect to ongoing asynchronous tasks, that continued to run (and possibly completed) while the apps were away.
The concept is pretty simple: when the application starts, it check if some background import operations are in progress by retrieving a list of operations from a static method:
IReadOnlyList<PhotoImportOperation> ops = PhotoImportManager.GetPendingOperations();
There should be at most one entry in the list. The API support multiple concurrent sessions for advanced uses so if your application handles more than one import session at once you may find more than one operation on the list, however handling multiple sessions is beyond the scope of this article.
If the list is not empty then retrieve the last entry and switch on the import stage (op
is an element of the above list and is of type PhotoImportOperation)
switch (op.Stage)
{
case PhotoImportStage.FindingItems:
foundItems = await op.ContinueFindingItemsAsync.AsTask(cts.Token,
findAllItemsProgress);
break;
case PhotoImportStage.ImportingItems:
importedItems = await op.ContinueImportingItemsAsync.AsTask(cts.Token,
importSelectedItemsProgress);
break;
case PhotoImportStage.DeletingImportedItemsFromSource:
deletedItems = await op.ContinueDeletingImportedItemsFromSourceAsync.AsTask(
cts.Token, progress);
break;
case PhotoImportStage.NotStarted:
session = op.Session;
break;
}
For each case, the application should navigate to the page or dialog corresponding to the phase in question and resume as if nothing happened.
The API keeps track of multiple processes and will return the right pending operations to the right process, assuming several Windows Store applications are using the API concurrently.
The MediaImport Windows SDK sample contains a fully working implementation of the terminate/restart mechanism: you may want to refer to it for details.
Using the Photo Import API from Win32 in C++
This section is meant to be an introduction to help you get started with the consumption of the Windows Photo Import API from plain Win32 C++ applications.
Using the Photo Import API from straight C++ / Win32 is relatively easy if you are already familiar with consuming COM objects, the main difference being the activation of the component, read on...
Activating a Windows Runtime component is slightly different from activating a COM component: instead of using CoCreateInstance() and a CLSID, you use GetActivationFactory() with a string representing the canonical name of the component, then you call ActivateInstance(), and then you finally QI for the interface you want.
Beyond activation, everything is identical, as Windows Runtime components really are COM components. In fact, the original name of the Windows Runtime was "Modern COM", and you can still find many traces of ATL all over the new WRL template library.
Here is how to proceed (I may have omitted a detail or two, but the important points are covered):
#include <Windows.h>
#include <WRL.h>
#include <wrl\wrappers\corewrappers.h>
#include <Windows.Media.Import.h> // This header is part of the Windows SDK (Win10 10240 Universal)
using namespace Microsoft::WRL;
using namespace Microsoft::WRL::Wrappers;
using namespace Windows::Foundation;
using namespace ABI::Windows::Foundation::Collections;
using namespace ABI::Windows::Media::Import;
ComPtr<IInspectable> inspectable;
ComPtr<IActivationFactory> factory;
ComPtr<IPhotoImportSourceStatics> photoImportSourceStatics;
ComPtr<IPhotoImportManagerStatics> photoImportManagerStatics;
RoInitializeWrapper initialize(RO_INIT_MULTITHREADED);
HRESULT hr = GetActivationFactory(
HStringReference(RuntimeClass_Windows_Media_Import_PhotoImportManager).Get(),
factory.GetAddressOf());
hr = factory->ActivateInstance(inspectable.GetAddressOf());
hr = inspectable.As<IPhotoImportManagerStatics>(&photoImportManagerStatics);
You can create instances of the PhotoImportSource runtime class in a similar manner:
hr = GetActivationFactory(
HStringReference(RuntimeClass_Windows_Media_Import_PhotoImportSource).Get(),
factory.ReleaseAndGetAddressOf());
hr = factory->ActivateInstance(inspectable.ReleaseAndGetAddressOf());
hr = inspectable.As<IPhotoImportSourceStatics>(&photoImportSourceStatics);
We can now get rid of the factory and of the temporary inspectable:
factory.Reset();
inspectable.Reset();
As a side note, you may find the new way more verbose than the old CoCreateInstance(), but splitting that call in three is in fact more efficient in general, as the registry is hit only once for a given class in GetActivationFactory(), and the factory is then cached. This makes ActivateInstance() very fast, and as such the new way beats the old way hands down when creating a large number of instances of objects of the same class.
Unfortunately - if you are a COM developer - there is no way to register a Windows Runtime component and make it available for your own or general consumption at this time.
♦ Windows 10 "RS1" (Summer 2016 Anniversary Update)
The "RS1" version of the API introduces two additional interfaces to support the new method and the two properties that were added. You need to explicitly query for them as interfaces are not inherited in WinRT, that is, you cannot cast the interface pointers.
ComPtr<IPhotoImportSession2> session2;
session.As<IPhotoImportSession2>(&session2);
ComPtr<IPhotoImportFindItemsResult2> foundItems2;
foundItems.As<IPhotoImportFindItemsResult2(&foundItems2);
For the above to work, you need to make sure the following Windows SDK folders are on your Include path. This should be automatic if you selected a Windows 10 target platform version in Visual Studio.
Below are the needed SDK paths for the RS1 14393 version, aka "1607", aka Anniversary Update:
C:\Program Files (x86)\Windows Kits\10\Include\10.0.14393.0\winrt
C:\Program Files (x86)\Windows Kits\10\Include\10.0.14393.0\shared
C:\Program Files (x86)\Windows Kits\10\Include\10.0.14393.0\um
You also need to link with a static library (pick x86 or x64, depending on your build flavor):
C:\Program Files (x86)\Windows Kits\10\Lib\10.0.14393.0\um\x64\runtimeobject.lib
C:\Program Files (x86)\Windows Kits\10\Lib\10.0.14393.0\um\x86\runtimeobject.lib
Everything else is just plain, good, old familiar COM, and you will find many helpers in WRL. For example, you can now call photoImportManagerStatics->FindAllSourcesAsync(...); etc., and start using this brand new Windows 10 WinRT API from classic Win32!
Last, but not least, you will have to deal with WinRT async yourself, this is not that difficult, but you will notice that asynchronous methods return derivatives of IAsyncOperation and IAsyncAction, which in turn derives from IAsyncInfo: lots of fun ahead and the links below will soon prove very useful.
See also:
How to: Activate and Use a Windows Runtime Component Using WRL
How to: Complete Asynchronous Operations Using WRL
How to: Handle Events Using WRL
This should be enough to get you started! If you express a keen interest in the comments (and I find the time), I may expand the Win32 snippets a bit to help jumpstart your Win32 project. Keep in mind that using asynchronous WinRT APIs from classic COM/Win32 is already an intermediate-to-advanced task, clearly more challenging and verbose than consuming the same API from, say, C# in Windows Store applications!
Alternative to WRL for native C++11 development on the Windows Runtime ABI
You may also want to consider the Modern CPP framework from Kenny Kerr (MIT license), which exposes all Windows Runtime types in a standard C++ way, including the Windows.Media.Import namespace and all the types described in this article: See http://moderncpp.com for more information.
You can find the latest version of Kenny's framework on GitHub: https://github.com/kennykerr/modern
Update: Kenny now works for Microsoft! His Modern CPP framework is not likely to be maintained going forward, but he was probably hired for a reason ;-)
ObjectiveC?
Apparently, there is even an ObjectiveC wrapper for the Photo Import API (and most of the Windows Runtime). Not sure how to use them but if you have an app written in that language that you are porting to Windows, this may help.
Points of Interest
Sidecars, siblings and video segments handling
This section describes the various cases that exist in practice concerning the files that might be present on a digital camera or video recorder (JPEG files, Camera Raw files, Sidecars…) and the design choices made in the Photo Import API to handle them.
Term
| Definition
|
JPEG
| Represents the ubiquitous .JPG files created by most digital cameras and picture-taking devices such as smartphones.
|
RAW
| Represents the manufacturer-specific Camera Raw format produced by some digital cameras and some smart phones. Raw formats include Nikon .NEF, Canon .CR2 and .CRW, Leica, Panasonic and Nokia .DNG, Pentax .PEF, Olympus .ORF and several others. Threshold ships with a built-in raw codec (formerly known as the Microsoft Camera Codec pack) and supports many raw formats natively.
|
Sibling
| For photos, a Sibling is the RAW file associated with a matching JPEG file produced by digital cameras set to RAW+JPEG shooting mode, where the camera creates two distinct files for every picture taken. The Photo Import API arbitrarily considers the JPEG file to be the “main” item of the pair, and the RAW to be the sibling.
For video, a Sibling is a low-resolution version of the main video, e.g. GoPro .mp4 + .lrv pairs. The Photo Import API arbitrarily considers the .MP4 to be the “main” item of the pair, and the LRV to be the sibling.
There can be at most one sibling attached to a given item.
|
Video Segment
| A Video Segment is an artifact of some limitation in video recorders that split long video files into multiple segments. The reasons might be file format limitations, file size limitations on the storage media file system (e.g. 2GB) or some inherited arbitrary convention, such as the 20 minutes standard duration of a 35mm movie reel. The naming of the main (“first”) segment and the subsequent (“continuation”) segments depends on the video recorder. For the popular GoPro action cameras, the naming is as follow: if GOPR0052.MP4 is the main file, then GP010052.MP4, GP020052.MP4 etc. are the continuations, a scheme allowing for up to 99 continuations. The correlation can be made on the full path + video type + last four digits, then look for digits in the first four characters to decide which one is the main file. The last continuation is also (typically) smaller than the main file, and the timestamps may also reflect the chronological sequence. Video segments can have siblings and sidecars.
|
Sidecar
| A Sidecar is a file that is not a JPEG nor a RAW, nor an LRV in the video case, that shares the same base-name as the main file it refers to, but with a different file extension. Sidecars can contain additional metadata (.XMP), a thumbnail (.THM), an audio annotation (.WAV) or possibly any other additional data, such as for example a GPS track alongside a video file, in the form of a text file containing NMEA sentences. As an example, a JPEG file named DSC_0001.JPG might have a DSC_0001.WAV audio annotation sidecar. The correlation is done on the path + base name. The timestamps of sidecars may be similar or identical to the timestamp of the main files, however, in case of .XMP sidecars (metadata) and possibly others, it is conceivable that the sidecar has been edited, say to rate the image, at a later point in time. There may be more than one sidecar for any given item, for example, a thumbnail, an audio annotation, and an XMP metadata sidecar, all attached to the same main file. The Photo Import API also supports "double extensions", such as "Video.mp4" with a "Video.mp4.thm" sidecar, as well as all Windows Phone "rich capture" sidecars.
|
Several situations may be encountered:
-
A digital camera can create JPEG files only, with or without sidecar(s).
-
A digital camera can create JPEG or RAW files, with or without sidecar(s).
-
A digital camera can create RAW files only, with or without sidecar(s).
-
A digital camera can create both RAW and JPEG versions of each image, with or without sidecar(s). Since the shooting mode can be changed by the user on the fly, it is conceivable and even likely to see standalone JPEGs, independent RAWs and RAW+JPEG pairs all together in the same folder, on the same memory card.
-
A video recorder can create video files with or without sidecars, and the video files might be split into multiple segments. The segment themselves can have their own siblings, e.g. an associated .LRV file in the case of GoPro action cameras and their own sidecars (e.g. .THM file, GPS track…)
The Photo Import API supports all of the above scenarios:
The primary cases are handled as one might expect: standalone JPEG files can be imported, as well as standalone RAW files. RAW+JPEG files are detected and automatically aggregated, then imported (and optionally deleted) as a single item. The API differentiate RAW+JPEG from the regular RAW + sidecar(s) or JPEG + sidecar(s) cases: the JPEG is arbitrarily considered to be the main item, and the associated RAW is considered to be its sibling. Siblings are identified separately from sidecars at the API surface level to make the application task easier (no need to match the file extensions to infer what is what).
Sidecars such as .TMH, .WAV or .XMP are recognized and attached to the main item, or, in the case of segmented videos, to the appropriate video segment. The main items and their siblings and sidecars are considered to be one item and are selected, imported and optionally deleted at once. This matches the user’s perception of having taken one photo, or one video, despite the fact that said photo or video might have been stored in multiple versions (JPEG + RAW) or multiple .MP4 video file segments.
The API user has the option to import everything, to ignore any sidecars, to ignore siblings, or to ignore both the sidecars and the siblings.
The mechanisms implemented in the Photo Import API mirrors the natural behavior of the devices and the perception of the users: when set to RAW+JPEG mode, every image taken counts as one on the device despite producing two distinct files. The same goes for the so-called "living images" or "live photos" on modern smartphones, where a one or two seconds video is taken alongside your snaps and sits next to the JPEG still pictures.
In all logic, the Photo Import API also consider such a pair as 1 image and will report it, import it and delete it as such. Likewise, a long video split into 20 segments will still be reported as a single video on the device, as well as in the Photo Import API. A simpler approach based on files rather than items would count two images for each JPEG + RAW pairs and report twice the number of shots, and one distinct video for each video segment found, i.e. report 20 videos when only one was taken.
♦ Windows 10 "RS1" (Summer 2016)
Apple Live Images were introduced in iOS in late 2015, after the Photo Import API was released. When taking Live Images with iPhone devices, the API categorizes them as "videos", as the live part is a .MOV, and the JPEG is considered to be a sidecar. This is not a big issue as both files are imported by default, but this behavior has been corrected in the Windows 10 "RS1" (summer 2016) version of the Photo Import API and all iPhone photos, live or not, are reported as "photos" as they should.
Success-on-failure with partial results
One of the untold stories about the Windows Runtime is its attempt to make app developers' life easier by not requiring them to handle errors they can do nothing about.
Consider the following: your background import task is running fine, and right in the middle of the transfer, the user pulls the device out (this is known as a surprise removal in driver lingo). The connection has been severed, the file currently being transferred of course will be aborted. What the application should do?
In the old days, a typical application would display an infamous Abort, Retry, Ignore message box, likely for every item remaining in the batch.
More recently, the API would fail. The language projection (or wrapper) would translate that failure to an exception. In the race to market and with a little help from Murphy's law, the app developer would not have tested for that particular exception during development and the app would just crash.
Don't pretend it would not happen to your software, unhandled software exceptions caught many people, sometimes with catastrophic and spectacular consequences, and will continue to do so.
Today, very modern APIs oriented towards app developers, like the one described here, just return "success" in an event like this. The app would just display the typical import summary showing the work done so far as if nothing happened. Trust me: this is much better.
If the app is interested (most should probably not be) you can inspect the HasSucceeded
boolean property present in the result classes of the three long-running asynchronous operations (find, transfer and delete).
If HasSucceeded
was set to false, say during the import phase, then something went wrong, and you can compare the number of selected items with the number of imported items in the result if you want.
If you see that some items were not imported, you may display an additional message in the summary, like "Something went wrong, not all of your items were imported", possibly with the number of missing files.
There is really not much more you can do, so don't.
If the user unplugged the device, s-he already knows that your app will not complete the task. If the disk ran full, s-he knows it too, as the shell has already displayed a noisy toast saying so, and suggested to make some space using Disk Cleanup.
Consider this again: the disk ran full and the transfer aborted, say at 80% for the sake of discussion. Why would you cancel the entire import workflow and not allow the user (or the app) to delete whatever was already imported from the device? Just go ahead and continue as if nothing happened. If the device was unplugged, the delete task would not fail: it will just do nothing and return S_OK
with HasSucceeded
set to false, and again you should not care.
Bottom line: No more cryptic or undocumented HRESULTs, and no more unhandled software exceptions.
Once you get past the initial shock and think about it for a while, the new way is better: do not bother users with things they already know, or things they cannot do anything about. Makes sense?
Programmer errors, such as forgetting to declare the proper app capabilities, or somehow managing to pass a wrong enum value or other mishaps are of course all reported as usual and will crash your app, hopefully only during development, if you do not catch the exceptions that the language projections will throw for you.
As a side note, if you are interested in learning how your application behaves in the field (you definitely should!) instrument it with some "runtime intelligence" framework such as Microsoft Application Insights or similar, and send yourself telemetry digests reporting the failures and successes and what users do with your app, what options they pick, what is actually used and what is not. You then use this data to improve the user experience, for example by selecting better defaults, or adding "soft landing" helpers (e.g. toasts) that guide the user through the discovery of the hidden gems of your app that no one found yet.
Built-in de-duplication mechanism
The Photo Import API implements a simple de-duplication mechanism based on keys computed from the original case-sensitive file names, file sizes and file dates (oldest of created and modified).
Any sibling or video segment(s) are included in the computation of that key, which is created using the CRC-64 algorithm.
When enumerating content from a device, the API offers a mode which automatically pre-selects new items found on the device or media since the last import session from that device or media. The key of each item is computed on-the-fly and compared against a table of known keys stored in a per-user location on the end user's computer.
If the key exists in the table, that exact item was imported before on that machine and by that user and is not marked again for import (the IsSelected
property of the item is automatically set to false). If the key was not found in the table of known keys, the item is considered to be new and is automatically pre-selected for import (its IsSelected
property of the item is set to true).
When successfully importing an item, its key is added to the table of known keys, so the item will not be selected again by default the next time the same device or media is presented.
The table stores up to approximately one million keys so the de-duplication will work virtually forever in most consumer scenarios, and should be pretty reliable: with that many keys, the collision probability is in the order of one in twenty million, so the chances that the de-duplication mechanism "misses", i.e. considers a new file as already imported, are very, very low from a Windows consumer perspective, and even seen from a professional photographers taking thousands of pictures per week.
See this very interesting article from Jeff Preshing on hash collision probabilities for more information: http://preshing.com/20110504/hash-collision-probabilities/
Note that the dedupe mechanism does not attempt to compare the file's content, so it is not entirely failsafe in the case a file is changed on the device but it's size and dates stays exactly identical. Unfortunately, there is no real solution to that problem: talking to a device through the PTP protocol over USB is like talking through a straw, and hashing the entire file, or even part of it, before downloading it is out of question.
The key used internally by the de-dupe mechanism is available to you as item.ItemKey, a 64-bit unsigned integer and you can use it as part of your own (better?) de-dupe mechanism if you want.
The database is in C:\Users\<alias>\AppData\Local\Microsoft\Windows\PhotoImport\History.dat but no one should ever need to access this file directly as the API has a SelectAll
selection mode, a SelectAll() method, and the per-item's IsSelected
property, that allows re-importing any or all items any number of times without hassles, so there should never be a need to delete the PhotoImport folder and the history files it contains: just add some "Import All" or "Select All" button to your user interface and your users will thank you for letting them start over if they need to.
In any case, never, ever delete individual files from that folder, as you will likely leave the database in a corrupt state. If you have to, remove or rename the folder at next reboot (e.g. using SysInternals tools), or logoff/logon and delete the folder immediately. The deduplication database may be held open for as long as the Photo Import API component is kept loaded in the Runtime Broker, running on behalf of the current user!
Do not kill RuntimeBroker.exe, and always logoff or reboot after removing the folder content.
In Windows 10 "RS3" (fall 2017) the Runtime Broker will be per-application, but the Import API will be kept in the (deprecated) shared Runtime Broker.
For Windows 10 "RS4" (2018) the API will likely be re-hosted in its own shared process host, as the shared Runtime Broker will be retired.
URI Activation Protocol
Your application can register and handle a custom URI protocol if you want to enable other applications to launch your import experience programmatically. For example, the Microsoft Photos app bundled with Windows 10 implements the ms-photos:import protocol (full syntax: ms-photos:import?device=<percentEncodedDeviceId>) and will start its Import workflow automatically when launched that way:
await Launcher::LaunchUriAsync(ref new Uri("ms-photos:import?device=" +
Uri::EscapeComponent(deviceId)));
The above statement in written in C++/CX for Visual C++ 2015 Update 1 or later. Note the 'await' keyword. Here is an equivalent version in C#:
await Windows.System.Launcher.LaunchUriAsync(new Uri("import:from?device=" +
Uri.EscapeDataString(deviceId)));
The device ID is the Plug-and-Play ID of the device to import from, typically passed to your app by AutoPlay.
You can implement a similar mechanism in your Universal application just be adding a few lines of code and some markup in your application manifest: see How to handle Uri activation on MSDN for more information.
Traps and Pitfalls
To test the water with Windows Store app development, and eat my own dogfood, I created a small app (free, available in the Windows Store) based on this article. Here is what I learned.
The session.DestinationFolder property
The session.DestinationFolder property is a bit tricky to deal with. First, any folder that you set must be part of the user's picture library as Store apps cannot see the entire file-system of the machines they run on. If you want the user to be able to pick his-her own arbitrary destination folder, you must do so in a way that also adds said folder to the Picture Library.
Adding the folder to the Picture Library is the only way the Import API can write to it, but also the only way Store apps, including the built-in Photos app, can read your pictures afterward. Fortunately, the Windows Runtime includes what is necessary, and it is not difficult to use:
var pictureLibrary = await StorageLibrary.GetLibraryAsync(KnownLibraryId.Pictures);
if (pictureLibrary != null)
{
var folder = await pictureLibrary.RequestAddFolderAsync();
if (folder != null)
{
session.DestinationFolder = folder;
}
}
Note that what you read back from the same property is the actual destination folder, including any session folder that may get appended by the API, depending on the other options you set, as such, you cannot rely on the session to store the folder for you: if you want to remember the user's choice, and you probably do, store the .Path property of the folder picked by the user, not the one you get by reading the session.DestinationFolder
property.
If you store the custom folder's path to your app settings, be carful when you read it back: the folder may no longer exist, for example, the user might have deleted or renamed it.
You need to check for errors when creating a StorageFolder
back form the saved path string, and fall back to the Picture Library's default if needed: use StorageFolder.GetFolderFromPathAsync()
and trap any exception, then use the .SaveFolder
property of the StorageLibrary object you get back from StorageLibrary.GetLibraryAsync(KnownLibraryId.Pictures)
to get a safe place to write the content. Warn the user appropriately if you had to reset the destination folder for any reason, and erase or update the stalled path in your settings.
Using the DeviceWatcher
You can use a DeviceWatcher if you app presents a list of available devices to the user. The device watcher let you monitor device arrivals and departures, and your list can be updated dynamically to reflect what's available, without the user always having to press a refresh button or key.
Creating and setting up a device watcher is not very difficult: declare one as a member of, say, your Page:
DeviceWatcher watcher;
Then create/initialize it in the page's constructor:
watcher = DeviceInformation.CreateWatcher(DeviceClass.PortableStorageDevice);
watcher.Added += Watcher_Added;
watcher.Removed += Watcher_Removed;
watcher.EnumerationCompleted += Watcher_EnumerationCompleted;
watcher.Start();
The event handlers will now be invoked automatically: first, the Added
handler will be called once per device already present, then the EnumerationCompleted
handler will be called once, and, after that, the appropriate handler, Added
or Removed
, will be called as devices come and go.
I used a member variable, int deviceCount = 0;
to keep track of the number of connected devices. This way I could easily display an "empty" indicator to the user, inviting him-her to plug a compatible device, and also hide the message at the appropriate time.
private async void Watcher_Added(DeviceWatcher sender, DeviceInformation args)
{
++devicesCount;
if (devicesCount > 0)
{
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
this.Empty.Visibility = Visibility.Collapsed;
});
}
if (watching)
{
await RefreshDeviceListAsync(deviceAdded: true);
}
}
Similarly, you track device removals and maintain the device count, message, and refresh the list as appropriate.
private async void Watcher_Removed(DeviceWatcher sender, DeviceInformationUpdate args)
{
if (devicesCount > 0)
{
--devicesCount;
}
if (devicesCount == 0)
{
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
this.Empty.Opacity = 0;
this.Empty.Visibility = Visibility.Visible;
this.EmptyFadeInStoryboard.Begin();
});
}
if (watching)
{
await RefreshDeviceListAsync(deviceAdded: false);
}
}
Since you do not want your list to be updated for each device already connected, you can maintain some state to track what's going on. For example, you can use a bool
watching = false;
member variable that you turn to true in the EnumerationCompleted
event handler, so you know that, from now on, events relates to user actions:
private async void Watcher_EnumerationCompleted(DeviceWatcher sender, object args)
{
watching = true;
if (devicesCount == 0)
{
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
this.Empty.Visibility = Visibility.Visible;
});
}
}
When a device is added or removed, you can call some RefreshDeviceListAsync(bool deviceAdded)
method of yours, which can do something similar to the following:
if (watching)
{
IReadOnlyList<PhotoImportSource> sources = await PhotoImportManager.FindAllSourcesAsync();
if (sources != null)
{
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
SourceListBox.ItemsSource = sources;
});
}
}
One thing you will likely notice early is the DeviceWatcher sometimes triggers device arrival notification before the underlying WPD API had a chance to fully register the new device, and, as such, the above code snippet does not work very reliably.
To make it more robust, I compared the device count tracked using the watcher with the device count seen in the sources list, and used a wait-and-retry scheme in a loop, using await Task.Delay(100);
to wait 0.1 second between retries to give the system a chance to stabilize.
Note that some devices, such as for example the Lexar USB 3.0 Dual-Slot Reader, will trigger two device arrival events, one for each slot, but the Import API will only report the populated slots - the one(s) actually containing a medium, so you will observe discrepancies between the two counters. If you plug a Lexar dual reader with, say, both slots empty, the deviceCount
tracked by your code will be 2, but sources.Count
will be 0 as the slots are empty, or 1 if only one card was inserted in the reader.
You need to handle those cases and not loop forever waiting for devices that will never arrive (so use a max retry count, and give up after a total time of 1 second). Also, a device like the Lexar will trigger AutoPlay events when plugged and unplugged, but not when a memory card gets plugged or unplugged from the reader's own slots. As such, you must still provide a Refresh button and/or hot key (F5?) of some kind, so the user can manually reload the device list.
Finally, your device-watching business will compete against AutoPlay if your app is registered as the handler for device arrivals for a given device type: you will get both an Added
event from the DeviceWatcher, and an OnActivated
event on your app with ActivationKind.Device
and an DeviceActivatedEventArgs
. You must carefully manage state in your app to handle this and make sure the app behaves appropriately!
Also consider the case where your app is doing something already, like importing files, and another device gets plugged and causes the OnActivated
event to fire: if your app is already busy you probably want to ignore subsequent events until it's done doing what it's doing, unless you want to go an extra mile and support multiple concurrent import sessions, something the Import API will happily let you do, and that you probably want in a professional-grade media import solution.
Lots of fun with state management, and even more fun testing the whole thing :-)
History
2015-09-21 - Published.
2015-10-07 - Fixed some typos, added a few paragraphs. Minor readability edits and some formatting.
2016-01-24 - Added the "Checking the device's lock state" section, plus small updates and edits.
2016-02-09 - Extended the "Using the Photo Import API from Win32 in C++" section.
2016-04-10 - Added information about the Windows 10 "RS1" version of the API (Summer 2016).
2016-12-28 - Added link to MSDN tutorial related to the Windows SDK Sample for the Photo Import API.
2017-06-04 - Added the Traps and Pitfall section, plus small updates and edits.