{"id":46274450,"url":"https://github.com/aconaway1/media_archiver","last_synced_at":"2026-03-04T04:03:06.460Z","repository":{"id":340037907,"uuid":"1164270136","full_name":"aconaway1/media_archiver","owner":"aconaway1","description":"AI-generated. Reviewed but no warranties. Very experimental.","archived":false,"fork":false,"pushed_at":"2026-02-22T22:28:46.000Z","size":19,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-23T02:58:21.383Z","etag":null,"topics":["ai","ai-generated","claude","claude-ai"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/aconaway1.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-22T22:00:41.000Z","updated_at":"2026-02-22T23:04:40.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/aconaway1/media_archiver","commit_stats":null,"previous_names":["aconaway1/media_archiver"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/aconaway1/media_archiver","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aconaway1%2Fmedia_archiver","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aconaway1%2Fmedia_archiver/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aconaway1%2Fmedia_archiver/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aconaway1%2Fmedia_archiver/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/aconaway1","download_url":"https://codeload.github.com/aconaway1/media_archiver/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aconaway1%2Fmedia_archiver/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30071670,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-04T03:25:38.285Z","status":"ssl_error","status_checked_at":"2026-03-04T03:25:05.086Z","response_time":59,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai","ai-generated","claude","claude-ai"],"created_at":"2026-03-04T04:03:05.724Z","updated_at":"2026-03-04T04:03:06.454Z","avatar_url":"https://github.com/aconaway1.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Media Archiver\n\n\u003e Built with [Claude Code](https://claude.ai/claude-code)\n\nA Python CLI tool for archiving and standardizing the naming of media files from various devices (GoPro, DJI drones, Tascam recorders, etc.).\n\n## Features\n\n- **Standardized Naming**: Renames media files to `YYYYMMDD-HHMMSS-\u003cdevice_type\u003e.ext` format with device identification\n- **Smart Timestamp Extraction**: Extracts creation timestamps from file metadata with intelligent fallback to file modification time\n- **Collision Handling**: Automatically handles naming conflicts with incrementing suffixes\n- **File Type Support**: Video (MP4, MOV), Audio (M4A, WAV, AAC), Images (JPG, PNG, RAW formats), and SRT files\n- **Safe Operations**: Copies files (preserves originals) rather than moving them\n- **Smart Skipping**: Skips files that already exist in the destination with informative messaging\n- **YAML Config File**: Set persistent defaults for destination, flags, and device tags in `config.yml`\n- **Smart Source Scanning**: Point at a volume/SD card root and the tool auto-detects media directories (DCIM/100GOPRO, DCIM/DJI_001, MUSIC/, etc.)\n- **Device Tagging**: Use `--device-tag` to identify source devices (e.g., `gopro-a`, `drone-mavic3`) in filenames\n- **Recent File Filtering**: By default, only archives files from today. Use `--recent N` to include the last N days, or `--recent 0` for all files\n- **Optional Raw Image Skipping**: Use `--skip-raw` flag to exclude raw image files if needed\n- **Optional SRT Skipping**: Use `--ignore-srt` flag to exclude DJI SRT subtitle/telemetry files if needed\n\n## Installation\n\n1. Clone or navigate to the media_archiver project directory\n2. Create a virtual environment and install dependencies:\n\n```bash\npython3 -m venv venv\nsource venv/bin/activate  # On Windows: venv\\Scripts\\activate\npip install -r requirements.txt\n```\n\nNo external tools needed! The archiver uses pure Python libraries for metadata extraction.\n\n## Configuration\n\nYou can set persistent defaults in a `config.yml` file in the project directory. Copy the example to get started:\n\n```bash\ncp config.yml.example config.yml\n```\n\nEdit `config.yml` with your preferred defaults:\n\n```yaml\n# Source and destination directories\nsource: /Volumes/Untitled\ndestination: ~/Library/CloudStorage/Dropbox/Video\n\n# File filtering\nskip_raw: false\nignore_srt: false\n\n# Device identification tag (appended to filenames)\ndevice_tag: mavic3\n\n# Only archive files from the last N days (0 = all files)\nrecent: 1\n\n# Overwrite destination files with different content\noverwrite: false\n\n# Enable debug logging\nverbose: false\n```\n\nCLI flags always override config file values. The config file is gitignored so your local paths stay private.\n\n## Usage\n\n```bash\npython main.py --source \u003csource_directory\u003e --destination \u003cdestination_directory\u003e [options]\n```\n\nIf `--source` and `--destination` are set in `config.yml`, you can run with no arguments:\n\n```bash\npython main.py\n```\n\n### Options\n\n| Flag | Description |\n|------|-------------|\n| `--source` | Source directory containing media files (required, via CLI or config) |\n| `--destination` | Destination directory for archived files (required, via CLI or config) |\n| `--recent N` | Only archive files from the last N days (default: 1, today only). Use `--recent 0` for all files |\n| `--device-tag TAG` | Freeform tag appended to filenames to identify the source device |\n| `--skip-raw` | Skip raw image files (.raw, .dng, .cr2, .nef, .arw, .gpr) |\n| `--overwrite` | Overwrite destination files if content differs (use with caution) |\n| `--ignore-srt` | Skip DJI SRT subtitle/telemetry files |\n| `--verbose` | Enable debug logging with timing diagnostics |\n\n### Examples\n\nArchive today's files from an SD card (default behavior):\n\n```bash\npython main.py --source /Volumes/GoPro --destination ~/Videos/Archive\n```\n\nArchive the last 3 days of footage with a device tag:\n\n```bash\npython main.py --source /Volumes/GoPro --destination ~/Videos/Archive --recent 3 --device-tag gopro-a\n```\n\nArchive all files (override the default recent filter):\n\n```bash\npython main.py --source /Volumes/GoPro --destination ~/Videos/Archive --recent 0\n```\n\nArchive DJI files without SRT telemetry, tagged by drone:\n\n```bash\npython main.py --source /Volumes/DJI_001/DCIM --destination ~/Videos/DJI --ignore-srt --device-tag mavic3\n```\n\nPoint at an SD card root and let the tool find media directories:\n\n```bash\npython main.py --source /Volumes/Untitled --destination ~/Videos/Archive\n```\n\nThe tool will scan for known structures and ask for confirmation:\n\n```\nDetected media sources:\n--------------------------------------------------\n  [gopro] /Volumes/Untitled/DCIM/100GOPRO (42 files)\n  [gopro] /Volumes/Untitled/DCIM/101GOPRO (18 files)\n\nTotal: 2 source directories\n\nProcess these sources? [y/N]\n```\n\nWith config.yml set up, archive with zero arguments:\n\n```bash\npython main.py\n```\n\nArchive files and skip raw images:\n\n```bash\npython main.py --source ./raw_media --destination ./archived_media --skip-raw\n```\n\n## How It Works\n\n### 1. Source Scanning\nWhen given a source path, the tool first checks if media files exist directly in that directory (backward compatible). If not, it scans for known device directory structures:\n- **DCIM/** subdirectories: 100GOPRO, 101GOPRO, DJI_001, 100MEDIA, etc.\n- **Audio folders**: MUSIC/, SOUND/, RECORD/ (Tascam-style recorders)\n\nEach detected subdirectory is treated as a separate source. If multiple sources are found, you'll be prompted to confirm before processing.\n\n### 2. File Discovery\nThe tool scans each source directory for supported media files (.mp4, .mov, .m4a, .wav, .aac, .jpg, .jpeg, .png, .raw, .dng, .cr2, .nef, .arw, .gpr, .srt).\n\n### 3. Timestamp Extraction\nFor each file, the tool attempts to extract the creation timestamp in this order:\n- **Video files (.mp4, .mov)**: Reads metadata using hachoir (pure Python) or OpenCV\n- **Audio files (.m4a, .wav, .aac)**: Reads date/year tags using mutagen library\n- **Image files (.jpg, .jpeg, .png, RAW)**: Reads EXIF DateTime, DateTimeOriginal, or DateTimeDigitized tags\n- **Fallback**: Uses the file's modification timestamp\n\n### 3b. Device Type Detection\nThe tool identifies the source device using multiple strategies:\n- **Filename patterns**: Detects GoPro files (GOPR*, GP*) and DJI files (DJI*)\n- **Metadata tags**: Searches video metadata for manufacturer info (DJI, GoPro, HERO)\n- **File type**: Audio-only files (M4A, WAV, AAC) default to 'audio'; image files default to 'image'; SRT files default to 'srt'\n- **Default**: Unidentified video files default to 'video'\n\nDevice types include: `video`, `drone`, `audio`, `image`, `srt`, or `unknown`\n\n### 4. Directory Organization\nFiles are organized by date in the destination directory:\n```\n\u003cDESTINATION\u003e/\n├── 2024/\n│   ├── 02/\n│   │   ├── 15/\n│   │   │   ├── 20240215-143045-video-gopro-a.mp4\n│   │   │   ├── 20240215-143045-drone-mavic3.mov\n│   │   │   ├── 20240215-143215-image.jpg\n│   │   │   └── 20240215-143245-image.png\n│   │   └── 16/\n│   │       └── 20240216-091530-audio.wav\n```\n\n### 5. Filename Generation\nFilenames are formatted as `YYYYMMDD-HHMMSS-\u003cdevice_type\u003e[-\u003cdevice_tag\u003e].\u003cextension\u003e`:\n- `YYYYMMDD`: Date (e.g., 20240215)\n- `HHMMSS`: Time (e.g., 143045)\n- `\u003cdevice_type\u003e`: Device type (e.g., video, drone, audio, image)\n- `\u003cdevice_tag\u003e`: Optional device identifier from `--device-tag`\n- Extension: Original file extension (e.g., .mp4)\n\nExample outputs:\n- `20240215-143045-video.mp4` (GoPro video, no tag)\n- `20240215-143045-video-gopro-a.mp4` (GoPro video with `--device-tag gopro-a`)\n- `20240215-143045-drone-mavic3.mov` (DJI drone with `--device-tag mavic3`)\n- `20240215-143215-image.jpg` (Photo from camera or drone)\n- `20240216-091530-audio.wav` (Tascam audio recording)\n\n### 6. Collision Handling\nIf a file with the same name already exists in the same date folder:\n- First collision: Changes to `YYYYMMDD-HHMMSS-\u003cdevice_type\u003e.1.ext`\n- Second collision: Changes to `YYYYMMDD-HHMMSS-\u003cdevice_type\u003e.2.ext`\n- And so on...\n\n### 7. File Copying\nFiles are copied to the destination directory while preserving their metadata.\n\n### 8. Duplicate File Detection\nWhen a destination file with the same name already exists:\n- The tool calculates SHA256 checksums of both files\n- If checksums match: File is skipped (already archived)\n- If checksums differ: File is skipped with a warning (use `--overwrite` flag to replace it)\n- The `--overwrite` flag allows replacing files with different content (use with caution)\n\n## Output\n\nThe tool provides informative logging:\n\n```\nINFO: Starting archiver: /source -\u003e /destination\nINFO: Skipped 128 file(s) older than 1 day(s)\nINFO: Found 5 media file(s) to process\nINFO: Copied: GOPR1234.MP4 -\u003e 20240222-143025-video-gopro-a.mp4\nINFO: Copied: DJI_0001.MOV -\u003e 20240222-143126-drone-mavic3.mov\nINFO: Copied: audio.wav -\u003e 20240222-143200-audio.wav\nINFO: Processing complete: 5 copied, 0 skipped/failed\n```\n\n## Requirements\n\n- Python 3.7+\n- mutagen library for audio metadata reading\n- hachoir library for video metadata parsing (pure Python)\n- opencv-python for video format validation\n- Pillow library for image EXIF metadata reading\n- pyyaml for config file support\n- Read and write permissions to source and destination directories\n\n## Error Handling\n\nThe tool gracefully handles:\n- Missing or invalid source/destination directories\n- Corrupted media files (falls back to file timestamps)\n- Permission errors\n- Files that can't be read or written\n- Invalid metadata formats\n\nProcessing continues even if individual files fail, with detailed error logging.\n\n## Supported Formats\n\n**Video Files:**\n- MP4 (.mp4) - GoPro, DJI, and general video\n- MOV (.mov) - GoPro and general video\n\n**Audio Files:**\n- WAV (.wav) - Tascam recorders and general audio\n- M4A (.m4a) - Tascam recorders and general audio\n- AAC (.aac) - General audio\n\n**Image Files:**\n- JPEG (.jpg, .jpeg) - Photos from any device\n- PNG (.png) - General images\n- RAW Formats (.raw, .dng, .cr2, .nef, .arw, .gpr) - Professional camera RAW files (can be skipped with `--skip-raw` flag)\n\n## Troubleshooting\n\n### Cannot extract timestamp\nThe tool will use the file's modification time as a fallback. Check file timestamps:\n```bash\n# macOS/Linux\nls -lh \u003cfile\u003e\n\n# Or use the tool's verbose output\n```\n\n### Permission denied errors\nEnsure you have read permissions on the source directory and write permissions on the destination directory.\n\n### Files not being copied\nCheck that:\n1. Files may be older than 1 day — try `--recent 0` to include all files\n2. Files have supported extensions (.mp4, .mov, .m4a, .wav, .aac, .jpg, .jpeg, .png, .raw, .dng, etc.)\n3. Destination directory exists and is writable\n4. Source directory contains readable files\n5. Raw files aren't being skipped due to `--skip-raw` flag\n6. Try with a single test file first to isolate the issue\n\n### Parser warnings (hachoir, OpenCV)\nYou may see warnings like:\n- `[warn] Skip parser 'MP4File': Unknown MOV file type`\n- `[\u003cNdsFile\u003e] Error when getting size of 'header': delete it`\n\nThese are **not our code deleting anything**. They come from third-party parsing libraries (hachoir and OpenCV) that attempt to extract metadata from video files. The \"delete it\" message refers to internal cleanup of temporary parse structures, not actual file deletion.\n\nThese warnings typically occur when:\n- Parsing DJI or proprietary video formats that differ from standard MP4/MOV specs\n- The file format has unexpected structure or tags\n- The library encounters something it doesn't recognize in the metadata\n\n**This is normal and safe** - the archiver continues to work, and files are archived successfully. When metadata extraction fails, the archiver falls back to using the file's modification time for the archive timestamp.\n\n### DJI files with incorrect timestamps\nSome DJI drone files may not have extractable creation metadata in the format the archiver expects. In these cases, the archiver will use the file's modification time from the SD card, which is typically the time the file was written (usually very close to when it was actually recorded).\n\nTo ensure accurate timestamps:\n- If your DJI files were recorded on a drone with correct system time, the modification time should be accurate\n- If timestamps are significantly off, check that your drone's clock was set correctly when recording\n- You can verify file timestamps using: `ls -lh \u003cfile\u003e` (macOS/Linux)\n\n## License\n\nMIT License\n\n## Development\n\nThis project is developed with [Claude Code](https://claude.ai/claude-code), Anthropic's CLI tool for AI-assisted software development.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faconaway1%2Fmedia_archiver","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faconaway1%2Fmedia_archiver","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faconaway1%2Fmedia_archiver/lists"}