Speeding up a Youtube demonstration video

6 February 2024

It’s not uncommon for coursework involving technology to be graded via the inclusion of a Youtube video, which the student (learner) uploads to show their work. Timestamps can be included in the description which makes it very easy for the person who is marking (grading) the work to see whereabouts in the video the different required features/parts of the specification are shown. By way of example, this is the approach taken by Harvard’s CS50W, where both a code submission and video upload is required for submission of coursework.

Often it is specified that a video cannot exceed a certain length. What if your video is too long though, and you already generated your timestamps? In this case you would want to do two things

  1. Speed up the video
  2. Update the timestamps that you have already generated

Speeding up the video

This can be done with the open source video editing software Shotcut

  • To install: brew install shotcut—this assumes MacOS, adjust as approprate for your OS and package manager
  • Launch shotcut and open your video—you can adjust the speed using Properties and then export the video after that

Updating the timestamps

Assuming you have timestamps in a file like this timestamps.md:

0:00 Models: Your application should have at least three models in addition to the User model: one for auction listings, one for bids, and one for comments made on auction listings
0:28 Create Listing: Users should be able to visit a page to create a new listing
1:30 Active Listings Page: The default route of your web application should let users view all of the currently active auction listings
1:51 Listing Page: Clicking on a listing should take users to a page specific to that listing

etc.

Something similar to the below (youtube_speedup.py) can be used to update the timestamps:

# Description:
#   A tool to update the timestamps of a video to reflect a given speed-up factor
# Usage:
#   cat timestamps.md | python youtube_speedup.py -s 1.5 -t 6:35

import sys
import argparse
import re


def validate_input(input_: str) -> None:
    len_acceptable = sum([1 for c in input_ if c.isdigit() or c == ":"])
    assert len(input_) == len_acceptable, f"{input_} is not a valid input (digits, colon)"


def round_int(real: float) -> int:
    return int(round(real, 0))


def timestamp_line_split(input_: str) -> tuple[int, int, str]:
    splits = re.split(r"\s+", input_.strip())
    time = splits[0]
    rest = " ".join(splits[1:])
    assert ":" in time, f"Invalid time format: {time} (should contain a colon e.g. MM:SS)"
    mins_s, secs_s = time.split(":")
    return (int(mins_s), int(secs_s), rest)


def speed_up_transform(mins: int, secs: int, speed_up: float) -> tuple[int, int]:
    length_in_secs = mins * 60 + secs
    new_length_in_secs = length_in_secs / speed_up
    new_mins = new_length_in_secs // 60
    new_secs = new_length_in_secs % 60
    return (round_int(new_mins), round_int(new_secs))


def parse_timestamps(input_: str, speed_up: float) -> list[str]:
    buffer = []

    # Firstly let's isolate the timestamps and the descriptions
    for timestamp_raw in input_.split("\n"):
        timestamp = timestamp_raw.strip()
        if timestamp:
            mins, secs, description = timestamp_line_split(timestamp)
            new_mins, new_secs = speed_up_transform(mins, secs, speed_up)
            new_timestamp = f"{new_mins}:{new_secs} {description}"
            buffer.append(new_timestamp)

    return "\n".join(buffer)


def main():
    # Create the parser
    parser = argparse.ArgumentParser("Update the timestamp timestamps of a video to reflect a given speed-up. Pipe in the timestamp text")

    # Add arguments
    parser.add_argument("-s", "--speedup", type=float, help="speed-up factor as a float e.g. 1.5")
    parser.add_argument("-t", "--time", type=str, help="Existing time e.g. 5:00")

    # Parse the arguments
    args = parser.parse_args()

    # Probably a better way to do this
    assert args.speedup, "A speed-up factor is required"
    assert args.time, "A time is required"

    # Calculate the new total time
    validate_input(args.time)
    mins, secs = args.time.split(":")
    mins, secs = int(mins), int(secs)
    new_mins, new_secs = speed_up_transform(mins, secs, args.speedup)

    # Print the updated timestamps
    input_ = sys.stdin.read()
    print(parse_timestamps(input_, args.speedup))

    # Print the new total time
    print(f"{mins}:{secs} -> {new_mins}:{new_secs}")


if __name__ == "__main__":
    main()