How to Auto-Link lost Media in CapCut projects using Python

ave you ever opened a CapCut project and seen the dreaded “Media lost” or “File not found” message? It usually happens when you move or rename the original videos, images, or audio used in your project. Manually relinking each clip can take hours.

In this complete 2025 guide, you’ll learn how to automatically fix lost media in CapCut using a simple yet powerful Python script that finds, restores, and relinks missing media files. This method is safe, tested, and based on CapCut’s internal JSON structure.


🧠 Why Does CapCut Lose Media?

CapCut projects rely on absolute file paths (like C:\Videos\intro.mp4) or relative paths inside the project folder. If you:

  • Move your media files to another drive
  • Rename or delete them
  • Sync between different computers (Windows ↔ macOS)

CapCut can no longer find the original files. That’s when you see the “Media lost” error.

Also check out: How to run CapCut in Linux

Always backup your project before experimenting — you can also explore other helpful CapCut guides like How to Add Music in CapCut or How to Use Keyframe in CapCut while managing projects.


💡 How This Python Fix Works

Our Python solution automatically:

  1. Scans the CapCut project’s JSON files (like draft_content.json or draft_info.json).
  2. Finds all missing media paths that CapCut can’t locate.
  3. Searches your drives for matching filenames.
  4. Copies and relinks them into your project’s material/import/ folder.
  5. Optionally checks durations using FFmpeg (ffprobe) to ensure the best match for duplicate files.

This saves you from manually hunting for clips and guarantees that your project opens smoothly again.


⚠️ Before You Begin

✅ Always back up your CapCut project folder before running the script.
✅ Make sure you have Python 3.8+ and FFmpeg installed on your computer.
✅ Test with --dry-run mode before making actual changes.

To install FFmpeg on Windows:

choco install ffmpeg

On macOS:

brew install ffmpeg


🧩 Folder Example (for clarity)

CapCutProject/
├── draft_content.json
├── draft_info.json
├── material/
│   ├── import/
│   ├── video/
│   └── audio/

Your missing clips will be automatically restored into the material/import/ folder.


⚙️ The Improved Python Script (With Duration Matching)

Below is the enhanced version that uses ffprobe to compare media durations and choose the correct file among duplicates.

Save the following as capcut_auto_relink.py.

#!/usr/bin/env python3
"""
Auto-link lost media in CapCut projects with filename and duration matching.
Author: Muhammad Ali | Updated: 2025
"""

import argparse, json, os, shutil, subprocess, time
from pathlib import Path

FILE_EXTENSIONS = {'.mp4', '.mov', '.mkv', '.avi', '.webm', '.png', '.jpg', '.jpeg', '.gif', '.aac', '.mp3', '.wav', '.m4a', '.flac'}

def get_duration(file_path):
    """Return media duration in seconds using ffprobe (if available)."""
    try:
        cmd = [
            'ffprobe', '-v', 'error', '-show_entries', 'format=duration',
            '-of', 'default=noprint_wrappers=1:nokey=1', str(file_path)
        ]
        output = subprocess.check_output(cmd, stderr=subprocess.DEVNULL).decode().strip()
        return round(float(output), 1)
    except Exception:
        return None

def build_file_index(search_dirs):
    """Build an index of filename -> [paths]"""
    index = {}
    for d in search_dirs:
        for root, _, files in os.walk(d):
            for f in files:
                fn = f.lower()
                if any(fn.endswith(ext) for ext in FILE_EXTENSIONS):
                    full_path = Path(root) / f
                    index.setdefault(fn, []).append(full_path)
    return index

def find_best_match(filename, expected_duration, index):
    """Find the best match by filename and (if possible) duration."""
    matches = index.get(filename.lower(), [])
    if not matches:
        return None
    if expected_duration is None:
        return matches[0]
    best = None
    smallest_diff = float('inf')
    for path in matches:
        dur = get_duration(path)
        if dur is None:
            continue
        diff = abs(dur - expected_duration)
        if diff < smallest_diff:
            smallest_diff = diff
            best = path
    return best or matches[0]

def extract_durations(json_data):
    """Extract known durations from JSON (CapCut stores durations in seconds or ms)."""
    durations = {}
    def walk(o):
        if isinstance(o, dict):
            if 'duration' in o and isinstance(o['duration'], (int, float)):
                if 'path' in o and isinstance(o['path'], str):
                    durations[o['path']] = round(float(o['duration']) / 1000, 1)
            for v in o.values(): walk(v)
        elif isinstance(o, list):
            for v in o: walk(v)
    walk(json_data)
    return durations

def main(project_dir, search_dirs, dry_run=False):
    project_dir = Path(project_dir)
    json_file = next(project_dir.glob('draft_*.json'), None)
    if not json_file:
        print("❌ No project JSON found.")
        return
    data = json.loads(json_file.read_text(encoding='utf-8', errors='ignore'))
    backup = json_file.with_suffix(json_file.suffix + f'.bak.{int(time.time())}')
    shutil.copy2(json_file, backup)
    print(f"🧾 Backup created: {backup}")

    durations = extract_durations(data)
    index = build_file_index(search_dirs)
    import_dir = project_dir / 'material' / 'import'
    import_dir.mkdir(parents=True, exist_ok=True)

    fixed, missing = 0, 0
    def walk_json(obj):
        nonlocal fixed, missing
        if isinstance(obj, dict):
            for k, v in obj.items():
                if isinstance(v, str) and any(v.lower().endswith(ext) for ext in FILE_EXTENSIONS):
                    orig_path = Path(v)
                    if not orig_path.exists():
                        dur = durations.get(v)
                        best = find_best_match(orig_path.name, dur, index)
                        if best:
                            dest = import_dir / best.name
                            if not dry_run:
                                shutil.copy2(best, dest)
                            obj[k] = f"material/import/{best.name}"
                            fixed += 1
                            print(f"✅ Fixed: {orig_path} → {dest}")
                        else:
                            missing += 1
                            print(f"❌ Missing: {orig_path}")
                else:
                    walk_json(v)
        elif isinstance(obj, list):
            for i, v in enumerate(obj):
                if isinstance(v, (dict, list)):
                    walk_json(v)

    walk_json(data)
    if not dry_run:
        json_file.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding='utf-8')
    print(f"\n✅ {fixed} media files relinked. ❌ {missing} still missing.")

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description="Auto-link lost CapCut media by filename & duration")
    parser.add_argument('--project', required=True, help='CapCut project folder path')
    parser.add_argument('--search', nargs='+', required=True, help='Folders to search for missing media')
    parser.add_argument('--dry-run', action='store_true', help='Simulate actions without modifying files')
    args = parser.parse_args()
    main(args.project, args.search, args.dry_run)


🧪 How to Use the Script

  1. Run in dry mode first: python capcut_auto_relink.py --project "C:\CapCutProjects\MyVideo" --search "D:\Videos" --dry-run This scans and reports missing files without touching your project.
  2. If results look correct, run it for real: python capcut_auto_relink.py --project "C:\CapCutProjects\MyVideo" --search "D:\Videos"
  3. The script will:
    • Backup your JSON
    • Copy missing files into material/import/
    • Update all paths automatically
  4. Reopen CapCut — your project should load perfectly!

🧰 Advanced Tips

  • Avoid lost media in the future: always enable Copy Media to Project when importing.
  • Run this script after moving projects between drives or computers.
  • Use FFmpeg duration check to avoid wrong file replacements.
  • Cross-platform safe: works on Windows, macOS, and Linux.

You can also explore CapCut features related to your projects, like:


🧩 Common Questions

Q: Does this work for CapCut mobile?
A: Mobile project formats differ. You can export your mobile project to desktop and run the same script.

Q: Will this damage my project?
A: No — it makes a full backup before editing. Always verify with --dry-run first.

Q: Can I make it a GUI tool?
A: Yes! You can easily wrap it with PySimpleGUI or Tkinter to make a user-friendly relinker for creators.


📚 Verified References & Community Knowledge

  • CapCut community discussions confirming JSON-based project structures
  • Reverse-engineering notes of draft_content.json and material/import/ folder behavior
  • Developer-verified techniques using ffprobe for duration verification

✅ Final Summary

StepAction
1️⃣Back up your CapCut project
2️⃣Run the Python script in --dry-run mode
3️⃣Verify the matches and rerun without dry mode
4️⃣Open CapCut and confirm all clips are restored

With this improved Python tool, you can recover any CapCut project suffering from “Media lost” errors in minutes — no manual linking required.

Comments

2 responses to “How to Auto-Link lost Media in CapCut projects using Python”

  1. […] if Lip Sync works here.If it does, export your old project’s clips and rebuild them in a new […]

    Like

  2. […] Check out : How to Auto-Link lost Media in CapCut projects using Python […]

    Like

Leave a comment

Design a site like this with WordPress.com
Get started