jonghwanhyeon / python-ffmpeg

A python binding for FFmpeg which provides sync and async APIs
MIT License
302 stars 53 forks source link

Difficulty adding multiple metadata fields to output file #65

Closed welpo closed 1 month ago

welpo commented 1 month ago

Hello!

First off: thanks for this binding!

I'm trying to add multiple metadata fields to an MP3 file. My goal is to recreate the following FFmpeg command:

ffmpeg -y -i input.mp3 -metadata title="Test Title" -metadata artist="Test Artist" -metadata album="Test Album" -metadata year="2024" -c copy output.mp3

However, I've encountered several issues while attempting to achieve this using the wrapper.

Code

import os
from ffmpeg import FFmpeg, FFmpegError

def add_metadata_to_mp3():
    input_file = os.path.expanduser("~/Desktop/test.mp3")
    output_file = os.path.expanduser("~/Desktop/test_metadata.mp3")

    metadata = {
        "title": "Test Title",
        "artist": "Test Artist",
        "album": "Test Album",
        "year": "2024"
    }

    ffmpeg = (
        FFmpeg()
        .option("y")  # Overwrite output file if it exists
        .input(input_file)
    )

    for key, value in metadata.items():
        ffmpeg = ffmpeg.option(f"metadata", f"{key}={value}")
    ffmpeg = ffmpeg.output(output_file, c="copy")

    try:
        ffmpeg.execute()
        print(f"Metadata added successfully. Output file: {output_file}")
    except FFmpegError as e:
        print(f"An FFmpeg error occurred: {e.message}")
        print(f"FFmpeg arguments: {e.arguments}")

if __name__ == "__main__":
    add_metadata_to_mp3()

Attempts and results

  1. Single metadata string:

    metadata_str = ":".join(f"{key}={value}" for key, value in metadata.items())
    ffmpeg.output(url=output_file, metadata=metadata_str, c="copy")

    Result: All metadata fields were concatenated into the "Title" field.

  2. Separate metadata options:

    for key, value in metadata.items():
       ffmpeg = ffmpeg.option("metadata", f"{key}={value}")
    ffmpeg = ffmpeg.output(url=output_file, c="copy")

    Result: Error - "Error opening input files: Invalid argument" FFmpeg arguments were ordered incorrectly, with metadata before input file:

    ['ffmpeg', '-y', '-metadata', 'title=Test Title', '-metadata', 'artist=Test Artist', '-metadata', 'album=Test Album', '-metadata', 'year=2024', '-i', '/Users/welpo/Desktop/test.mp3', '-c', 'copy', '/Users/welpo/Desktop/test_metadata.mp3']
  3. Output options dictionary:

    output_options = {"c": "copy"}
    for key, value in metadata.items():
       output_options[f"metadata:{key}"] = value
    ffmpeg = ffmpeg.output(output_file, **output_options)

    Result: Error - "Error opening output files: Invalid argument" FFmpeg arguments had incorrect metadata syntax:

    ['ffmpeg', '-y', '-i', '/Users/welpo/Desktop/test.mp3', '-c', 'copy', '-metadata:title', 'Test Title', '-metadata:artist', 'Test Artist', '-metadata:album', 'Test Album', '-metadata:year', '2024', '/Users/welpo/Desktop/test_metadata.mp3']
  4. Metadata as separate options:

    for key, value in metadata.items():
       ffmpeg = ffmpeg.option(f"metadata", f"{key}={value}")
    ffmpeg = ffmpeg.output(output_file, c="copy")

    Result: Error - "Error opening input files: Invalid argument" FFmpeg arguments still ordered incorrectly, with metadata before input file:

    ['ffmpeg', '-y', '-metadata', 'title=Test Title', '-metadata', 'artist=Test Artist', '-metadata', 'album=Test Album', '-metadata', 'year=2024', '-i', '/Users/welpo/Desktop/test.mp3', '-c', 'copy', '/Users/welpo/Desktop/test_metadata.mp3']

Is there a correct way to add multiple metadata fields to an output file using the wrapper? If so, could you provide an example of how to achieve this?

Thanks!

Additional information

welpo commented 1 month ago

I did it!

After a million attempts, Claude came up with the idea to use the metadata:g:n format for passing global metadata options. Here's the working code:

def add_metadata_to_mp3():
    input_file = os.path.expanduser("~/Desktop/test.mp3")
    output_file = os.path.expanduser("~/Desktop/test_metadata.mp3")

    metadata = {
        "metadata:g:0": "title=Test Title",
        "metadata:g:1": "artist=Test Artist",
        "metadata:g:2": "album=Test Album",
        "metadata:g:3": "year=2024"
    }

    ffmpeg = (
        FFmpeg()
        .option("y")
        .input(input_file)
        .output(output_file, c="copy", **metadata)
    )

    try:
        ffmpeg.execute()
        print(f"Metadata added successfully. Output file: {output_file}")
    except FFmpegError as e:
        print(f"An FFmpeg error occurred: {e.message}")
        print(f"FFmpeg arguments: {e.arguments}")

This defines the metadata as a list of tuples, where each tuple contains the metadata:g:n key and the full key=value string for each metadata item.

I searched around and found this comment, which may have been in the training data.