An automated command-line tool that recursively discovers and downloads photos from Microsoft OneDrive to local storage, safely removing each photo from cloud storage after verified download completion. Built with Python and Microsoft Graph API.
- Overview
- Features
- Architecture
- Tech Stack
- Prerequisites
- Azure App Registration Setup
- Installation
- Configuration
- Usage
- Project Structure
- Testing & Verification
- Troubleshooting
- License
OneDrive Photo Importer automates the migration and backup of photos stored in Microsoft OneDrive personal accounts. The agent navigates the entire OneDrive folder hierarchy using Microsoft Graph API v1.0, streams original photo files to a local directory via direct blob storage URLs, and deletes cloud copies only after verifying local file integrity and size.
Designed for resilience during large media migrations, the application includes automatic silent token refreshing, rate-limit backoff (HTTP 429 retry handling), persistent progress tracking, and interactive device flow authentication.
- Recursive Folder Discovery: Automatically traverses all nested subdirectories starting from the OneDrive root (
/me/drive/root/children). - Original Quality Preservation: Bypasses Graph API image transcoding by downloading directly from pre-signed Azure Blob URLs (
@microsoft.graph.downloadUrl). - Safe Cloud Deletion: Issues Graph API
DELETErequests (/me/drive/items/{id}) only after local file download and non-zero byte size validation complete successfully. - Date & Range Filtering: Filters photos by EXIF metadata (
photo.takenDateTime), filename timestamp patterns (YYYYMMDD_HHMMSS), or item creation timestamps. - Resume & Progress Tracking: Maintains state in
.onedrive_progress.jsonto resume interrupted imports without re-downloading completed items. - Dry-Run Mode: Evaluates matching files and calculates total download payload size without performing network writes or cloud deletions.
- Rate-Limit & Transient Retry Handling: Automatically intercepts HTTP 429 response status codes and network timeouts, adhering to server-requested
Retry-Afterintervals. - Broad Format Support: Detects standard raster formats, raw camera files, and Apple/Samsung image extensions (e.g., HEIC, HEIF, DNG, CR2, NEF, ARW).
The following diagram illustrates the token acquisition, recursive file discovery, streaming download, and deletion lifecycle executed by onedrive_photo_importer.py.
graph TD
subgraph Local Environment
Agent["OneDrive Photo Importer Agent<br/>(onedrive_photo_importer.py)"]
Cache[".onedrive_token_cache.json"]
Progress[".onedrive_progress.json"]
Logs["onedrive_importer.log"]
Disk["Local Storage<br/>(./onedrive_photos)"]
end
subgraph Microsoft Identity & Graph Services
AzureAuth["Microsoft Identity Platform<br/>(OAuth2 Device Flow)"]
GraphAPI["Microsoft Graph API v1.0<br/>(graph.microsoft.com)"]
BlobStore["Azure Blob Storage<br/>(Download URLs)"]
end
Agent -->|1. Authenticate / Refresh Token| Cache
Cache -->|Token Request| AzureAuth
AzureAuth -->|Access Token| Agent
Agent -->|2. Walk Folders & Filter Photos| GraphAPI
Agent -->|3. Query Download Metadata| GraphAPI
GraphAPI -->|Return @microsoft.graph.downloadUrl| Agent
Agent -->|4. Stream Binary Data| BlobStore
BlobStore -->|Save File| Disk
Agent -->|5. Verify Non-zero Bytes| Disk
Agent -->|6. Delete Cloud Item| GraphAPI
Agent -->|7. Persist State & Logs| Progress
Agent -->|Write Log Entries| Logs
- Python:
3.8+
msal(>=1.28.0): Microsoft Authentication Library for Python (OAuth2 Device Code Flow and Token Cache Management).requests(>=2.31.0): HTTP library for Microsoft Graph REST API calls and binary payload streaming.tqdm(>=4.66.0): Terminal progress indicator for media processing loops.
- Microsoft Graph API:
v1.0(https://graph.microsoft.com/v1.0)
- Python: Version
3.8or higher installed locally. - Microsoft Account: Personal Microsoft account (Outlook.com, Hotmail, Live) with access to OneDrive.
- Azure Application Registration: Azure Client ID configured with delegated
Files.ReadWritepermissions.
Before running the application, register a public client application in the Azure Portal to enable Microsoft Graph API access.
- Sign in to the Azure Portal.
- Navigate to App registrations and select New registration.
- Configure registration settings:
- Name:
OneDrive Photo Importer(or preferred name). - Supported account types:
Personal Microsoft accounts only. - Redirect URI: Leave blank.
- Name:
- Select Register.
- Copy the Application (client) ID displayed on the Overview page.
- Configure authentication settings:
- Go to Authentication > Add a platform > Mobile and desktop applications.
- Select the redirect URI:
https://login.microsoftonline.com/common/oauth2/nativeclient. - Select Configure.
- Under Advanced settings, set Allow public client flows to Yes.
- Save changes.
- Configure API permissions:
- Go to API permissions > Add a permission > Microsoft Graph.
- Select Delegated permissions.
- Search for and select
Files.ReadWrite. - Select Add permissions.
# 1. Clone the repository
git clone https://github.com/Omiiii04/OneDrive-Agent.git
cd OneDrive-Agent
# 2. Create a virtual environment
python -m venv venv
# 3. Activate the virtual environment
# Windows (PowerShell):
.\venv\Scripts\Activate.ps1
# Linux / macOS:
source venv/bin/activate
# 4. Install dependencies
pip install -r requirements.txtOpen onedrive_photo_importer.py and update the configuration variables located near the top of the file:
# ═══════════════════════════════════════════
# CONFIGURATION ← Edit these two lines
# ═══════════════════════════════════════════
CLIENT_ID = "YOUR_CLIENT_ID_HERE" # Application (client) ID from Azure Portal
DOWNLOAD_FOLDER = "./onedrive_photos" # Local directory path for saved photos
# ═══════════════════════════════════════════| File | Type | Description |
|---|---|---|
onedrive_photo_importer.py |
Source | Main application script and entry point. |
requirements.txt |
Dependency Manifest | Required Python packages and version specifications. |
.onedrive_token_cache.json |
Generated | Encrypted MSAL token cache generated after authentication. |
.onedrive_progress.json |
Generated | Progress tracker containing lists of processed item IDs (done_ids) and failure details. |
onedrive_importer.log |
Generated | Plain-text execution log storing timestamped runtime events. |
On initial execution, onedrive_photo_importer.py triggers an MSAL Device Code authentication flow. The console displays a URL and user code:
--------------------------------------------------------------
To sign in, use a web browser to open the page https://www.microsoft.com/link and enter the code XXXXXXXX to authenticate.
--------------------------------------------------------------
Navigate to https://www.microsoft.com/link, input the code, and sign in. Tokens are saved to .onedrive_token_cache.json and silently refreshed on subsequent executions.
python onedrive_photo_importer.py [FLAGS]| Flag | Argument | Default | Description |
|---|---|---|---|
--folder |
PATH |
./onedrive_photos |
Destination directory for downloaded photos. |
--dry-run |
Flag | False |
Scans and outputs photo listings without downloading or deleting files. |
--no-delete |
Flag | False |
Downloads photos to local storage while preserving cloud copies on OneDrive. |
--resume |
Flag | False |
Loads.onedrive_progress.json and skips previously completed photo IDs. |
--limit |
INT |
None |
Restricts execution to process a maximum ofN matching photos. |
--date |
YYYY-MM-DD |
None |
Filters photos created or taken on an exact specified date. |
--date-from |
YYYY-MM-DD |
None |
Filters photos created or taken on or after the specified start date. |
--date-to |
YYYY-MM-DD |
None |
Filters photos created or taken on or before the specified end date. |
Scan drive contents and display total count and size without modifying storage:
python onedrive_photo_importer.py --dry-runDownload a single photo without deleting from OneDrive:
python onedrive_photo_importer.py --limit 1 --no-deleteDownload and delete photos within a specific calendar year:
python onedrive_photo_importer.py --date-from 2024-01-01 --date-to 2024-12-31Resume a cancelled or interrupted execution using saved progress:
python onedrive_photo_importer.py --resumeImage items are identified via Graph API facets (image, photo) or matched against the following extension list:
| Category | File Extensions |
|---|---|
| Standard Images | .jpg, .jpeg, .png, .gif, .bmp, .webp |
| Mobile Formats | .heic, .heif |
| Uncompressed / High Quality | .tiff, .tif |
| RAW Camera Formats | .raw, .dng, .cr2, .nef, .arw, .orf, .rw2 |
OneDrive-Agent/
├── .gitignore # Git exclusion configuration
├── Architecture.png # Existing architecture diagram asset
├── LICENSE # Apache License 2.0 legal text
├── README.md # Project documentation
├── image/ # Documentation image resources
│ └── README/
│ └── 1784627234745.png
├── onedrive_photo_importer.py # Primary executable application script
└── requirements.txt # Python dependency specifications
Static syntax analysis and compilation verification can be executed locally:
# Verify Python syntax and compile bytecode
python -m py_compile onedrive_photo_importer.pyNote: Execution testing of Graph API network operations requires an active Azure Application Client ID and user authentication.
Symptom: Script exits with status code 1 displaying CLIENT_ID is not set!.
Cause: CLIENT_ID in onedrive_photo_importer.py retains the default placeholder value ("YOUR_CLIENT_ID_HERE").
Resolution: Replace "YOUR_CLIENT_ID_HERE" with your Azure Application (client) ID.
Symptom: Console outputs Rate-limited — waiting X s before retry....
Cause: Microsoft Graph API threshold exceeded.
Resolution: The script automatically waits for the duration specified in the HTTP Retry-After header before retrying requests. No manual intervention is required.
Symptom: RuntimeError: Authentication failed: ... or RuntimeError: Could not start device flow.
Cause: Invalid Client ID, missing public client flow configuration in Azure Portal, or network connectivity issues.
Resolution: Confirm Allow public client flows is set to Yes under Authentication > Advanced settings in the Azure Portal.
This project is licensed under the Apache License 2.0. See the LICENSE file for details.