Home video organizer: phase 1
This Python command scans a folder of videos recursively. It groups clips by filming date, excludes clips shorter than 5 seconds from the output, makes a LosslessCut .llc project for every clip longer than 3 minutes, and writes one chronologically sorted JSON manifest per day. Each manifest has an editable title string and a videos array. Source videos are never deleted or modified. A clip exactly 5 seconds long is included; a clip exactly 180 seconds long remains a video entry.
Requirements
- Python 3.9 or newer
- FFmpeg's
ffprobeon your PATH - LosslessCut to review/edit the generated
.llcprojects - On Windows, if IANA timezones are unavailable:
py -m pip install tzdata
Run
python home_video_phase1.py "/path/to/all-videos" "/path/to/day-output" --timezone America/New_York
On Windows:
py home_video_phase1.py "D:\Home Videos" "D:\Video Days" --timezone America/New_York
For one date, add --date 2024-07-11. The output directory must be outside the input directory. If the video has neither a recognized embedded recording date nor a date in its filename, the script skips it with a message. Use --fallback mtime if you want such files assigned by filesystem modification time (which can change when copying files).
Date and ordering
The command uses the file's embedded QuickTime com.apple.quicktime.creationdate or creation_time when available, then a YYYYMMDD[_HHMMSS] or YYYY-MM-DD[_HH-MM-SS] style filename. Date-only filenames are placed at midnight, with the path used to break ties. Embedded UTC dates are converted to --timezone before grouping and sorting. Choose the timezone where the videos were filmed. If the cameras have wrong clocks, correct their metadata or names before running the command.
Output
For example, 2024-07-11.json contains a title and an array of absolute paths:
{
"title": "",
"videos": [
"/path/to/day-output/projects/2024-07-11/long-3f7a4c5d12.llc",
"/path/to/all-videos/shorter.mp4"
]
}
The generated .llc project contains the absolute source path and one selected segment covering the whole video. Review long videos in LosslessCut to mark cuts. Set title when you have chosen a rendered-video title; rerunning the command preserves that string while updating videos. Existing array-format manifests are upgraded on the next run with an empty title. Files that fail probing or date detection are reported and cause a nonzero exit code; already successful days are still written.
Review titles
Install the lightweight Python VLC binding into the Python environment used for the tool:
py -m pip install -r requirements-review.txt
Open a daily manifest in the Tkinter reviewer:
py review_videos.py "D:\Video Days\2024-07-11.json"
The reviewer uses the portable VLC installation at C:\PortableApps\VLCPortable3 by default. Pass --vlc-dir or set VLC_DIR if it is elsewhere. VLC and Python must have matching architectures (both 64-bit, for example). It plays the source video for each entry, including the mediaFileName referenced by .llc projects; it does not apply LosslessCut edits. Use j/l to seek back/forward 10 seconds, Left/Right to change videos, and Space to pause or resume. Playback automatically starts the next manifest entry when a video ends. The title saves automatically to the manifest. Click the title box to edit; click elsewhere in the reviewer to return keyboard control to playback. Legacy array manifests are accepted and converted to the object format when saved.
Render titled daily videos
After filling in a manifest title, render all titled daily manifests in out/:
py render_daily_videos.py
The script saves one MP4 per manifest under out\rendered by default. Pass a different manifest folder as the first argument (for example, py render_daily_videos.py "D:\Video Days") and choose another destination with --output-dir. Untitled manifests are skipped. Existing output files are preserved unless --overwrite is supplied.
Videos are processed in manifest order. Raw video paths use the full clip; .llc entries use their mediaFileName and the time ranges in cutSegments (a project with no ranges uses the full clip). Before rendering, the script probes every source, uses the highest frame rate it finds, and chooses the largest source resolution by pixel area (ties go to the wider source). It accounts for 90-degree rotation metadata. Other clips are scaled and padded to fit that output canvas. The renderer crossfades video and audio over 1.5 seconds and fades the video and audio in and out over 1.5 seconds. The trimmed manifest title starts fading in at 3 seconds, stays fully visible for 5 seconds, then fades out; each title fade lasts 0.75 seconds. At the bottom right, it shows the manifest date (formatted like December 25, 2025) above the title, with both lines left aligned and no text box. The title uses C:\Windows\Fonts\MVBOLI.TTF (MV Boli). Timing, font, fallback frame rate (30 fps if the sources report none), and encoder settings are constants near the top of render_daily_videos.py.
FFmpeg and ffprobe must be available on PATH; FFmpeg must include the drawtext filter. Rendering tries NVIDIA's h264_nvenc encoder first and falls back to libx264 if NVENC is unavailable or fails. NVENC accelerates video encoding; the crossfade, scaling, and title filters run through FFmpeg's CPU filters.
An SVG can be used as a designed title template, but this FFmpeg build has no SVG decoder, so the SVG would first need to be rasterized to an image for each manifest. For the current two-line date/title overlay, drawtext keeps the workflow simpler. An SVG becomes useful if the title needs more elaborate graphics or decorative layout.
Compatibility note: .llc files use LosslessCut's version 2 project structure (mediaFileName, cutSegments). Check one generated project in your installed LosslessCut version before working through a large collection.