Splitting and Processing Large Files

Welcome back! In our previous lessons, we've explored basic transcribing techniques with Whisper, as well as calculating media duration using FFmpeg in Go. Today, we'll shift our focus to transcribing large files with Whisper and FFmpeg. Managing large audio or video files by splitting them into manageable pieces ensures that tasks like transcription can be performed efficiently and without errors. This lesson will empower you to handle these files smoothly, leveraging FFmpeg's capabilities from Go using the ffmpeg-go library.

Understanding Transcribing Large Files

Whisper has a file size limitation of 25 MB, which poses a challenge when attempting to transcribe large audio or video files. To work around this constraint, we need a method to divide these large files into smaller, manageable chunks that can be processed sequentially. Our strategy involves leveraging FFmpeg's capabilities to split the files into segments that fall within the permissible size limit. This will ensure compatibility with Whisper while maintaining the quality and integrity of the original content. By breaking down large files, we facilitate efficient transcription, allowing for smooth and accurate processing of each smaller segment.

Using FFmpeg-go to Get Media Duration

Let's revisit how we retrieve the media's length using FFmpeg in Go with the ffmpeg-go library. In the previous lesson, we implemented the GetAudioDuration function, which uses the ffmpeg-go library to access ffprobe functionality directly from Go code:

// GetAudioDuration returns the duration of an audio file in seconds
func GetAudioDuration(filePath string) (float64, error) {
    fileInfo, err := os.Stat(filePath)
    if err != nil {
        if os.IsNotExist(err) {
            return 0, fmt.Errorf("file does not exist: %s", filePath)
        }
        return 0, fmt.Errorf("error accessing file: %v", err)
    }

    if !fileInfo.Mode().IsRegular() {
        return 0, fmt.Errorf("%s is not a regular file", filePath)
    }

    probeJSON, err := ffmpeg_go.Probe(filePath)
    if err != nil {
        return 0, fmt.Errorf("ffprobe error: %v", err)
    }

    var result FFProbeResult
    if err := json.Unmarshal([]byte(probeJSON), &result); err != nil {
        return 0, fmt.Errorf("failed to parse ffprobe output: %v", err)
    }

    if result.Format.Duration == "" {
        return 0, fmt.Errorf("no duration information found in file: %s", filePath)
    }

    duration, err := strconv.ParseFloat(result.Format.Duration, 64)
    if err != nil {
        return 0, fmt.Errorf("failed to parse duration: %v", err)
    }

    return duration, nil
}

This function calls ffmpeg_go.Probe, which internally runs ffprobe and returns the output as JSON. We then parse the JSON to extract the duration of the media file. This approach allows us to programmatically determine the length of any audio or video file, which is essential for calculating how to split the file into appropriately sized chunks.

Using FFmpeg-go to Split Media Files into Chunks

To split a large media file into smaller chunks, we use the SplitIntoChunk function from internal/transcriber/transcriber.go. This function uses FFmpeg via the ffmpeg-go library to extract a specific chunk from the media file, given a start time and duration.

// SplitIntoChunk splits a media file into a chunk with specified start time and duration
func SplitIntoChunk(filePath string, startTime, duration float64, chunkNumber int) (string, error) {
    ext := filepath.Ext(filePath)
    chunkPath := fmt.Sprintf("/usercode/FILESYSTEM/whisperapp/resources/chunk_%d%s", chunkNumber, ext)

    err := ffmpeg_go.Input(filePath, ffmpeg_go.KwArgs{"ss": fmt.Sprintf("%.2f", startTime)}).
        Output(chunkPath,
            ffmpeg_go.KwArgs{
                "t": fmt.Sprintf("%.2f", duration),
                "c": "copy",
                "y": "",
            },
        ).
        OverWriteOutput().
        Silent(true).
        Run()

    if err != nil {
        return "", fmt.Errorf("encoding failed for chunk %d: %v", chunkNumber, err)
    }
    return chunkPath, nil
}

Code Explanation:

  1. Parameters:

    • filePath: Path to the original media file.
    • startTime: The start time (in seconds) for the chunk.
    • duration: The duration (in seconds) of the chunk.
    • chunkNumber: The index of the chunk (used for naming).
  2. Output Path:

    • The chunk is saved in the resources directory with a name like chunk_1.mp3.
  3. FFmpeg-go Command:

    • The command uses ffmpeg-go's fluent API to specify input, start time (ss), duration (t), and output file.
    • c: copy avoids re-encoding, and y overwrites output files.
    • .Silent(true) suppresses FFmpeg output.
  4. Error Handling:

    • If FFmpeg fails, an error is returned.
Example: Splitting a File into Chunks

To split a file into multiple chunks, you can use GetAudioDuration to determine the total duration, then call SplitIntoChunk in a loop, specifying the start time and duration for each chunk. For example, to split a file into 10-second chunks:

func main() {
    filePath := "/usercode/FILESYSTEM/whisperapp/resources/sample_audio.mp3"
    chunkDuration := 10.0 // seconds

    totalDuration, err := transcriber.GetAudioDuration(filePath)
    if err != nil {
        fmt.Printf("Error getting duration: %v\n", err)
        return
    }

    numChunks := int(math.Ceil(totalDuration / chunkDuration))
    for i := 0; i < numChunks; i++ {
        start := float64(i) * chunkDuration
        duration := chunkDuration
        if start+duration > totalDuration {
            duration = totalDuration - start
        }
        chunkPath, err := transcriber.SplitIntoChunk(filePath, start, duration, i+1)
        if err != nil {
            fmt.Printf("Error splitting chunk %d: %v\n", i+1, err)
            continue
        }
        fmt.Printf("Chunk %d created at: %s\n", i+1, chunkPath)
    }
}

This will create chunk files like chunk_1.mp3, chunk_2.mp3, etc., in the resources directory.

Checking Yourself: Executing the Media File Split

To test the splitting functionality, you can call the SplitIntoChunk function with a sample media file and a desired chunk size. For example:

func main() {
    filePath := "/usercode/FILESYSTEM/whisperapp/resources/sample_audio.mp3"
    chunkPath, err := transcriber.SplitIntoChunk(filePath, 0, 10, 1)
    if err != nil {
        fmt.Printf("Error splitting chunk: %v\n", err)
        return
    }
    fmt.Printf("Chunk created at: %s\n", chunkPath)
}

After running the code, you should see output similar to:

Chunk created at: /usercode/FILESYSTEM/whisperapp/resources/chunk_1.mp3

You can then check the resources directory for the new chunk file.

Lesson Summary

You have learned how to split large media files into smaller chunks using FFmpeg in Go, leveraging the ffmpeg-go library and the SplitIntoChunk function. This approach allows you to process large files efficiently and prepare them for transcription with Whisper, staying within file size limits and ensuring smooth, error-free operation.

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal