Better file management in Shotcut

I would like to propose an enhancement to the project saving structure in Shortcut Video Editor. Currently, managing projects with multiple media assets can be cumbersome, especially when working with multiple memory cards or collaborating with a team.

Proposed Solution:

When saving a project, Shortcut Video Editor should create a structured folder system to keep all related assets organized. The project folder would contain four sub-folders:

Clips: Stores all video clips used in the project

Audio: Stores all audio files used in the project

Images and Animations: Stores images and animations used in the project

Proxies: Stores proxy files generated for the project

The main project file (.MLT) would be saved outside these folders, in the root of the project directory.

Benefits:

Easy collaboration

Send the entire project folder to team members, ensuring all assets are included

Efficient project management

Keep assets organized, making it easier to locate and manage files

Reduced errors

Minimize missing assets and broken links

Space management

Allow working with multiple memory cards without worrying about losing clips

Example Structure:

Project Name/
Clips/
Audio/
Images and Animations/
Proxies/
Project Name.MLT

To address concerns regarding complexity and storage for small projects, I propose that this advanced file management be implemented as an optional workflow. The standard “Save As” command would function as it does currently, saving only the project file (.MLT), which is ideal for quick edits, short slideshows, or simple projects that do not require external sharing.

For professional-grade or collaborative work, a new command, “Save As and Consolidate,” would trigger the creation of the structured project folder, automatically copying all active media (Clips, Audio, Images/Animations, and Proxies) into their respective subfolders. This mandatory consolidation eliminates the critical risk of a team member or mix engineer receiving a project with missing assets—such as a separately stored SFX folder—while still allowing for detailed internal organization by scene or day using Shotcut’s native playlist folders and bins.

2 Likes

So the “Save As” stays as it is, and we have a new “Save As and Consolidate,” to do what you said, I think it’s a good idea.

1 Like

This is already on the road map as “archive project.”

5 Likes

Oh… cool, thank you very much!

1 Like

Thanks so much

Glad to see this here, I’d also love this, in particular, to easily work cross-platform, as I use both a Mac laptop and a Windows desktop to work on the same projects, and those file paths aren’t the same for included assets, obviously.

1 Like

I just wrote up a simple script that should work on Unix-like systems (details in this post) that might be helpful for folks. Unfortunately someone else will have to do the work of getting it to work on Windows haha — our desktop is Linux-only and my Windows shell scripting knowledge is limited-to-nonexistent.

I have yet to generate proxies, so I do not know if my script will automatically capture them as well (my guess is that they will not be captured). If someone who uses them can explain if they show up in the MLT file (and how), I can see if I can modify the script to deal with them as well. All primary resources (images, audio, video) should be properly accounted for. I think the proxy is still working (and proxies are stored in the project directory under proxies, so they don’t need to be copied/linked anywhere), since I don’t modify the shotcut:hash field, but yeah…would like to get some confirmation.

1 Like

You might want to check out my Project Management Tool.

Here is a direct link to the project on Github: GitHub - Fillimerica/ShotcutProjectPackager: A utility for managing ShotCut Project included files

and there is a thread in the Resources section of the forum that can be used for questions, suggestions, or feedback.

Tom..

1 Like

They are if you manually move or copy files into the project’s folder BEFORE importing them into Shotcut.

Oh wow! I had started to store files in the project folder, but hadn’t fully tested it on both platforms, but it’s working properly, thanks for noting this info. Good stuff to know, and consider my suggestion unnecessary now!

1 Like

Technical Notes for Developers

This section is optional, for maintainers and contributors familiar with the Shotcut / MLT codebase.

Shotcut is a Qt-based C++ application using the MLT framework (mltframework/shotcut). The .mlt project is an XML document that references media via file paths. The proposed “Save As and Consolidate” feature can be implemented as a fairly self‑contained workflow around:

  • Enumerating active media sources in the current MLT project
  • Creating a new folder structure
  • Copying media into that structure (optionally with progress UI and error handling)
  • Updating MLT resource paths to point to the new locations
  • Saving the updated .mlt file in the root of the new project folder

1. Media Enumeration

Goal: Collect all active media items (video, audio, images, proxies) referenced by the current project.

In MLT, clip URIs usually appear as:

  • producer.resource properties
  • Playlist/track entries referencing those producers
  • Possibly filters or transitions referencing external files (e.g., images, LUTs)

Simple approach:

  1. Iterate over all producers in the current MLT profile / tractor:

    • For each producer, read the resource property.
    • Skip non-file schemes (http://, https://, ftp://, etc.) or special MLT producers.
    • Normalize and resolve to absolute paths.
  2. (Optional but safer) Scan for:

    • External image masks or overlays used by filters (lut3d, mask, etc.)
    • External subtitle or text templates, if any.

From a C++/Qt perspective, pseudocode might look like:

// PSEUDOCODE – illustrates general flow only

QSet<QString> mediaFiles;

for (auto *producer : project->allProducers()) {
    QString resource = QString::fromUtf8(mlt_producer_get_string(producer, "resource"));
    if (resource.isEmpty())
        continue;

    QUrl url(resource);
    if (!url.isLocalFile())
        continue; // skip non-local or non-file resources

    QString path = QFileInfo(url.toLocalFile()).absoluteFilePath();
    mediaFiles.insert(path);
}

This set powers the subsequent copy & relink steps.

2. Folder Structure Creation

When the user chooses “Save As and Consolidate…”:

  1. Ask for a new project root directory (e.g., via QFileDialog::getExistingDirectory or a custom dialog).

  2. Inside that directory, create:

    ProjectRoot/
      Clips/
      Audio/
      Images and Animations/
      Proxies/
      ProjectName.mlt
    
  3. You can map each discovered file to one of those folders based on:

    • Extension (e.g., .mp4, .mov, .mkv → Clips; .wav, .mp3 → Audio; .png, .jpg → Images)
    • Whether it’s known to be a proxy (more below).

Example folder mapping (simplified):

enum class MediaType { Clip, Audio, Image, Proxy };

MediaType classifyFile(const QString &path) {
    QString ext = QFileInfo(path).suffix().toLower();

    static const QSet<QString> videoExt = { "mp4", "mov", "mkv", "avi" };
    static const QSet<QString> audioExt = { "wav", "mp3", "flac", "aac", "ogg" };
    static const QSet<QString> imageExt = { "png", "jpg", "jpeg", "gif", "tiff", "bmp", "webp" };

    if (/* check if path matches Shotcut proxy pattern */) {
        return MediaType::Proxy;
    } else if (videoExt.contains(ext)) {
        return MediaType::Clip;
    } else if (audioExt.contains(ext)) {
        return MediaType::Audio;
    } else if (imageExt.contains(ext)) {
        return MediaType::Image;
    } else {
        // fallback: treat unknown media as Clip
        return MediaType::Clip;
    }
}

Destination path:

QString destinationFor(const QString &projectRoot, const QString &originalPath) {
    MediaType type = classifyFile(originalPath);
    QString subdir;
    switch (type) {
    case MediaType::Clip:  subdir = "Clips"; break;
    case MediaType::Audio: subdir = "Audio"; break;
    case MediaType::Image: subdir = "Images and Animations"; break;
    case MediaType::Proxy: subdir = "Proxies"; break;
    }

    QDir dir(projectRoot);
    dir.mkpath(subdir);
    return dir.filePath(subdir + "/" + QFileInfo(originalPath).fileName());
}

3. Copying Assets (Including Proxies)

Core logic:

  • For each file in mediaFiles, compute destPath = destinationFor(projectRoot, originalPath).
  • If destPath already exists and is identical (size + hash or mtime), skip or confirm with the user.
  • Copy via QFile::copy(originalPath, destPath) (optionally robust with temp files then rename).
  • Track mapping: originalPath → destPath.

Pseudocode:

QMap<QString, QString> pathMap; // oldPath -> newPath

for (const QString &src : mediaFiles) {
    QString dst = destinationFor(projectRoot, src);

    if (src == dst) {
        pathMap.insert(src, dst);
        continue;
    }

    if (!QFileInfo::exists(src)) {
        // log or show a warning about missing source
        continue;
    }

    QDir().mkpath(QFileInfo(dst).absolutePath());
    if (!QFile::copy(src, dst)) {
        // log error, maybe allow "Skip / Retry / Abort"
        continue;
    }

    pathMap.insert(src, dst);
}
Proxy handling

Shotcut already has a proxy system (with a proxy directory and naming pattern). For proxies:

  • Detect proxy files from the MLT or Shotcut’s internal proxy manager.
  • Prefer to copy the existing proxies if available, so the consolidated project opens quickly.
  • Store proxies under Proxies/ in the new project.

Option A (simple): Only handle proxies that appear as resource entries (i.e., whatever the current project is actually referencing).

Option B (more thorough): For each original clip in Clips/, check if a proxy exists in the global proxy directory using Shotcut’s existing naming convention, and copy it to Proxies/, updating any relevant MLT resource or proxy metadata.

Developers can hook into Shotcut’s existing proxy manager instead of reinventing detection logic.

4. Relinking / Path Rewriting in the MLT XML

Once all copies are completed and pathMap is filled, update the project’s resource references.

Two ways to do this:

  1. MLT-Level Rebinding (preferred in C++ code path):
    For each producer, see if its resource maps to a new path; if so, update the property, then serialize the modified MLT project.

  2. XML-Level Rewriting (as a fallback concept):
    Load the .mlt XML as text, update resource="..." attributes using pathMap, and save.

MLT-level example (pseudocode):

for (auto *producer : project->allProducers()) {
    QString resource = QString::fromUtf8(mlt_producer_get_string(producer, "resource"));
    if (resource.isEmpty())
        continue;

    QUrl url(resource);
    if (!url.isLocalFile())
        continue;

    QString oldPath = QFileInfo(url.toLocalFile()).absoluteFilePath();
    if (!pathMap.contains(oldPath))
        continue;

    QString newPath = pathMap.value(oldPath);
    QString newResource = QUrl::fromLocalFile(newPath).toString();

    mlt_producer_set(producer, "resource", newResource.toUtf8().constData());
}

After all updates:

// Save modified MLT to new project root:
QString newMltPath = projectRoot + "/" + projectName + ".mlt";
project->saveToFile(newMltPath); // assuming Shotcut has a wrapper method

5. Proxy Path Logic

If the project uses proxies, Shotcut likely stores:

  • Original path
  • Proxy path
  • A boolean flag like use_proxy or internal state

Consolidation options:

  • Keep proxy resolution logic intact and simply ensure that any resource paths currently pointing at proxies are updated to their new locations inside Proxies/.
  • Store proxy paths relative to the project root, so moving the whole folder works on other systems.

Typical behavior flow:

  1. When consolidating, if a producer is currently using a proxy, its resource might already be the proxy file.
  2. Copy that proxy file to Proxies/.
  3. Update resource to Proxies/... (relative to project root).
  4. Keep a producer property (or Shotcut metadata) that still knows the original high-res source path in case the user regenerates proxies or toggles proxy usage later.

A very simple proxy rewrite could be:

if (isProxyResource(resource)) {
    // oldProxyPath -> newProxyPath
    QString oldProxyPath = QFileInfo(url.toLocalFile()).absoluteFilePath();
    QString newProxyPath = pathMap.value(oldProxyPath, oldProxyPath);
    mlt_producer_set(producer, "resource",
                     QUrl::fromLocalFile(newProxyPath).toString().toUtf8().constData());
}

Where isProxyResource() uses Shotcut’s existing naming pattern or internal flags.

6. Relative vs Absolute Paths

To make the project portable:

  • After consolidation, all resource paths can be made relative to the .mlt file location.
  • This can be done after the copy step, just before saving, by replacing the absolute path with QDir(projectRoot).relativeFilePath(newPath) and then prefixing with file: if desired.

Example:

QString relative = QDir(projectRoot).relativeFilePath(newPath);
QString newResource = QUrl::fromLocalFile(relative).toString(); // or custom scheme
mlt_producer_set(producer, "resource", newResource.toUtf8().constData());

This ensures the entire project folder can be moved to a new drive or sent to another machine without breaking links, as long as the internal structure stays the same.

7. UI Placement and Workflow

Suggested UX:

  • In the main File menu:

    • Save As… (existing behavior – .mlt only)
    • Save As and Consolidate… (new behavior)
  • When selecting Save As and Consolidate…:

    1. Prompt: “Choose project folder and name.”
    2. Optional options:
      • Checkbox: “Copy & include proxies”
      • Checkbox: “Make all media paths relative to project folder”
    3. Show a progress dialog:
      • “Copying Clips (x/y)”
      • “Copying Audio (x/y)”
      • “Copying Images (x/y)”
      • “Copying Proxies (x/y)”
  • On completion:

    • Offer to open the newly consolidated project from its new location.
    • State: “All active media have been copied into this folder. You can now safely share or move it.”