This page suggests replacing the video files with links to the original, which saves space. It has this gist in ruby, and I coded this gist in Python, which is also below. The iMovie library went from 300 GB to 5 GB because I skipped two projects I was still working on.
Like the ruby version:
- it goes through an iMovie 10 library and replaces the files in
Original Media
for which it can find a correspondence with links
- it requires you to import into the library, quit iMovie, and then run the script.
Unlike the ruby version:
- it uses symlinks to the original media instead of hard links (I confirmed that it works just as well)
- you can define the filetypes to replace (movie, audio, image)
- you can adapt the global variable
PROJECTS_TO_SKIP
to avoid replacing media
on some projects that you may be working on.
- you can skip projects that you're still working on
- it assumes that your iMovie library and originals folder are organized by the same event name, because in my case I had multiple
DSC001.MOV
and I use the event name to distinguish them
- if the event names are different, e.g. if you create two events titled "movie" then iMovie renames
the second to "movie 1", you can adapt the global variable
SHOW_NAME_CORRESPONDENCE
to map the name of the iMovie event to the name of
the folder with the original content.
import doctest
import glob
import os
import pathlib
import shutil
import sys
FILE_SUFFIXES_LOWERCASE = [".mp4", ".mts", ".mov", ".jpg", ".jpeg", ".png"]
PROJECTS_TO_SKIP = [] # e.g., ["project 1", "project 2"]
SHOW_NAME_CORRESPONDENCE = {} # e.g. {"movie": "movie 1"}
def skip(f):
"""Returns a boolean for whether to skip a file depending on suffix.
>>> skip("abc.mp4")
False
>>> skip("ABC.JPEG")
False
>>> skip("abc.plist")
True
>>> skip("00114.MTS")
False
"""
suffix = pathlib.Path(f).suffix.lower()
return suffix not in FILE_SUFFIXES_LOWERCASE
def get_show_and_name(f):
"""
>>> show, name = get_show_and_name("/Volumes/video/iMovie Library.imovielibrary/my great show/Original Media/00117.mts")
>>> "my great show" == show
True
>>> "00117.mts" == name
True
>>> show, name = get_show_and_name("/Volumes/video/path/to/originals/my great show/00117.mts")
>>> "my great show" == show
True
>>> "00117.mts" == name
True
"""
path = pathlib.Path(f)
name = path.name.lower()
dirname = str(path.parents[0])
imovie = "iMovie Library.imovielibrary" in dirname
parent_dir = str(path.parents[2 if imovie else 1])
show = dirname.replace(parent_dir, "")
if imovie:
assert show.endswith("/Original Media"), f
show = show.replace("/Original Media", "")
assert show.startswith("/")
show = show[1:].lower()
if show in SHOW_NAME_CORRESPONDENCE:
show = SHOW_NAME_CORRESPONDENCE[show]
return show, name
def build_originals_dict(originals):
"""Go through the original directory to build a dictionary of filenames to paths."""
originals_dic = dict()
for f in glob.glob(os.path.join(originals, "**", "*.*"), recursive=True):
if skip(f):
continue
show, name = get_show_and_name(f)
originals_dic[(show, name)] = f
return originals_dic
def replace_files_with_symlinks(library, originals):
"""Go through the iMovie library and find the replacements."""
originals_dic = build_originals_dict(originals)
# List files recursively
for f in glob.glob(os.path.join(library, "**", "*.*"), recursive=True):
if skip(f) or os.path.islink(f):
continue
show, name = get_show_and_name(f)
if (show, name) in originals_dic:
target = originals_dic[(show, name)]
print("Replacing %s with %s" % (f, target))
os.unlink(f)
os.symlink(target, f)
else:
print("No original found for %s" % f)
def main():
args = sys.argv
assert 3 == len(args), "You need to pass 3 arguments"
library = args[1]
originals = args[2]
replace_files_with_symlinks(library = library, originals = originals)
if "__main__" == __name__:
r = doctest.testmod()
assert 0 == r.failed, "Problem: doc-tests do not pass!"
main()