Skip to content

Bug: Fix audio file with cover image treated as video file - #91

Merged
omeryusufyagci merged 10 commits into
omeryusufyagci:90-bug-audio-file-treated-as-a-video-filefrom
Rushi1109:90-fix-audio-file-with-cover-image-treated-as-video-file
Jan 15, 2025
Merged

omeryusufyagci merged 10 commits into
omeryusufyagci:90-bug-audio-file-treated-as-a-video-filefrom
Rushi1109:90-fix-audio-file-with-cover-image-treated-as-video-file

Conversation

@Rushi1109

Copy link
Copy Markdown

fixes the bug raised in #90

The ffprobe tool is used to check if any stream contains codec_name as mjpeg.

If mjpeg is present, the media is treated as Audio, other media is treated as Video

@omeryusufyagci

Copy link
Copy Markdown
Owner

Hey @Rushi1109, thanks for the PR. Frankly, I wasn't familiar with the full output set of ffprobe, but from what I see they provide disposition.attached_pic as a field in itself. Could you please get the json output which should give you a list of streams, with each stream providing you this field. From what I briefly checked, that should literally be what we're looking for.

Thanks for checking!

@omeryusufyagci omeryusufyagci added bug Something isn't working core Media processing component, the core C++ binary. iteration Requires further iteration for approval labels Dec 24, 2024
@Rushi1109

Copy link
Copy Markdown
Author

Hey @omeryusufyagci,

When looking at the raw data of the media I see DISPOSITION:attached_pic = 1 for stream index 2 which is the video stream.

But, When I tried to get the information about disposition.attached_pic from ffprobe tool, I am not able to get it's value. I tried extracting from json but still it's not showing attached_pic for that file. Do you have any idea about why it's getting filtered out, by ffmpeg?

@Rushi1109

Copy link
Copy Markdown
Author

Moreover @omeryusufyagci,

I have researched a bit and got to know that If the video stream contains a static image, the average frame rate for that stream will be 0/0. My current implementation is using the avg_frame_rate from video stream and decides the media type.

Do you see any issue with this approach?

@omeryusufyagci

Copy link
Copy Markdown
Owner

Hey @omeryusufyagci,

When looking at the raw data of the media I see DISPOSITION:attached_pic = 1 for stream index 2 which is the video stream.

But, When I tried to get the information about disposition.attached_pic from ffprobe tool, I am not able to get it's value. I tried extracting from json but still it's not showing attached_pic for that file. Do you have any idea about why it's getting filtered out, by ffmpeg?

Please make sure you include disposition after the -show_entries flag, e.g. -show_entries stream=index,<other_stuff>,disposition.

Could you try like that, and if there's still an issue please provide the command you used and the output you received.

I have researched a bit and got to know that If the video stream contains a static image, the average frame rate for that stream will be 0/0. My current implementation is using the avg_frame_rate from video stream and decides the media type.

Do you see any issue with this approach?

Thanks for figuring this out! Good to know that'd be a valid fix. The disposition.attached_image would be a more robust solution, as it explicitly identifies cover images and aligns better with the intent of filtering out such streams.

Could you please verify this and let me know if it resolves the issue? Thank you!

@Rushi1109

Copy link
Copy Markdown
Author

Please make sure you include disposition after the -show_entries flag, e.g. -show_entries stream=index,<other_stuff>,disposition

Yes I included disposition. I am using the same command of ffprobe but instead of avg_frame_rate, I added disposition and removed that awk command. But, I am not seeing any output for disposition.

Can you check it on your side? Do you see disposition in output?

@omeryusufyagci

omeryusufyagci commented Dec 29, 2024

Copy link
Copy Markdown
Owner

Please make sure you include disposition after the -show_entries flag, e.g. -show_entries stream=index,<other_stuff>,disposition

Yes I included disposition. I am using the same command of ffprobe but instead of avg_frame_rate, I added disposition and removed that awk command. But, I am not seeing any output for disposition.

Can you check it on your side? Do you see disposition in output?

HI @Rushi1109, you're right. I was able to reproduce the same behavior. It depends on what's bundled with the metadata, and we won't be able to count on the existence of that field. That means we'll have to check for this ourselves.

You're also correct, that a cover image is reported as a video stream that has 0/0 avg frame rate. It may also be reported as 'Cover' in a comment field, but that again is dependent on the metadata, whereas the 0/0 avg frame rate seems to be always available.

In this case, I propose to get the same ffprobe output as json (fyi, we're already using nlohmann/json in the project), parse the avg frame rate into a vector, and check if a given file has only 1 video stream and that video stream has 0/0 avg frame rate. Meaning, as soon as we iterate through a second video stream, we should conclude our check to avoid wasting time there.

Could you update this PR to implement this? Thanks

@Rushi1109

Copy link
Copy Markdown
Author

HI @Rushi1109, you're right. I was able to reproduce the same behavior. It depends on what's bundled with the metadata, and we won't be able to count on the existence of that field. That means we'll have to check for this ourselves.

You're also correct, that a cover image is reported as a video stream that has 0/0 avg frame rate. It may also be reported as 'Cover' in a comment field, but that again is dependent on the metadata, whereas the 0/0 avg frame rate seems to be always available.

In this case, I propose to get the same ffprobe output as json (fyi, we're already using nlohmann/json in the project), parse the avg frame rate into a vector, and check if a given file has only 1 video stream and that video stream has 0/0 avg frame rate. Meaning, as soon as we iterate through a second video stream, we should conclude our check to avoid wasting time there.

Could you update this PR to implement this? Thanks

Sure. I'll look into that today.

@Rushi1109

Copy link
Copy Markdown
Author

Hello @omeryusufyagci,

I have made required changes. I have tested it manually and it is working as expected.

Can you test it on your side and let me know if any change is required?

@Rushi1109 Rushi1109 changed the title check if the stream contains mjpeg as codec_name, then treat media as audio Bug: Fix audio file with cover image treated as video file Dec 31, 2024

@omeryusufyagci omeryusufyagci left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the changes @Rushi1109 !

Here's some comments for you:

About the parseFrameRate() addition:
The MediaProcessor::Utils NS is normally meant for generic utility. In this case, the frame rate helper is intrinsically coupled with getMediaType(). So, we should move this under the Engine class as a private member function. Also, for expressiveness, let's rename it to hasZeroFrameRate().

Regarding the implementation of this helper, I believe we can make it simpler. We want to know if an otherwise audio file has a video stream that has exactly "0/0" for the avg frame rate. So, we can simply and do a full string comparison, instead of parsing each char in the field. Pass a const ref of the stream object, and it can be as simple as a 1-line return statement that checks if it matches the expected case. We should however, make sure to cover the cases where the field is missing or invalid.

Updates on getMediaType():
Good start on this with the json parsing, but we can also simplify this similar to above. We should also encapsulate the logic where we're determining if we have a valid video stream. Otherwise, it makes it needlessly difficult understand what this function does at a glance.

Here's roughly what I have in mind:

for (const auto& stream : streams) {
    if (hasValidVideoStream(stream)) {
        return MediaType::Video;
    }
    if (hasValidAudioStream(stream)) {
        hasAudioStream = true; // define this flag false before the loop
    }
}

// if we haven't returned for Video and have found a valid audio stream
if (hasAudioStream) {
    return MediaType::Audio;
}

// otherwise
return MediaType::Unsupported;
}

So please encapsulate both checks (hasValid...) into individual functions. I realize the one for audio will likely be a 1-line return statement, but I'd still prefer to have it for the sake of consistency.
Within the hasValidVideoStream(), you'd call hasZeroFrameRate() with a const ref of the json stream for the avg frame rate field.
The other 2 helpers should also be called with const refs of the stream and all 3 new functions should be const themselves too.

This approach should make it easier to understand what getMediaType() does, and in general simplify the deduction logic to see if we have a cover image. Could you please update this PR accordingly?

Please let me know if you have any questions along the way. Many thanks!

@Rushi1109
Rushi1109 changed the base branch from main to 90-bug-audio-file-treated-as-a-video-file January 2, 2025 15:43
@Rushi1109

Copy link
Copy Markdown
Author

Updated the PR.

@omeryusufyagci omeryusufyagci left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @Rushi1109, thanks for the updates. Definitely moving in the right direction! Could you please take a look and update the PR? Thanks!

Comment thread MediaProcessor/src/Engine.h Outdated
Comment thread MediaProcessor/src/Engine.cpp Outdated
Comment thread MediaProcessor/src/Engine.cpp
Comment thread MediaProcessor/src/Engine.h Outdated
Comment on lines +56 to +63
/**
* @brief Checks if the provided stream is valid video stream
*
* @param stream json data of stream
*
* @return True if the stream is valid. False otherwise.
*/
bool hasValidVideoStream(const nlohmann::json& stream) const;

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does valid mean here? We should describe what exactly that means here

Comment thread MediaProcessor/src/Engine.cpp Outdated
Comment thread MediaProcessor/src/Engine.cpp Outdated
Comment thread MediaProcessor/src/Engine.cpp Outdated
Comment on lines +75 to +92
nlohmann::json streamData = nlohmann::json::parse(*output);

bool containsAudioStream;
for (const auto& stream : streamData["streams"]) {
if (hasValidVideoStream(stream)) {
return MediaType::Video;
}
if (hasValidAudioStream(stream)) {
containsAudioStream = true;
}
}

if (containsAudioStream) {
return MediaType::Audio;
} else {
throw std::runtime_error("Unsupported media type detected.");
}

return MediaType::Unsupported;
}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the helpers are called hasValid...Stream it could be nicer to pass the entire stream and handle the iterations within the helpers. In doing so, I feel there won't be a need for the audio helper flag containsAudioStream.
You can check if we have a valid video stream and return Video, otherwise check for audio and return Audio, otherwise return Unsupported.

This would make getMediaType very easy to reason about.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the helpers are called hasValid...Stream it could be nicer to pass the entire stream and handle the iterations within the helpers. In doing so, I feel there won't be a need for the audio helper flag containsAudioStream.
You can check if we have a valid video stream and return Video, otherwise check for audio and return Audio, otherwise return Unsupported.

Done. I have made the changes. Can you please review it?

Comment thread MediaProcessor/src/Engine.cpp Outdated
Comment on lines +89 to +94
if (stream["codec_type"] != "video") {
continue;
}
if(!stream.contains("avg_frame_rate")) {
continue;
}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be better to check for the inverse condition and return false, instead of continuing for the expected case. The earlier implementation in getMediaType was like that already.

Comment on lines +54 to +79
/**
* @brief Checks if the streams contains valid video stream and not static media (i.e. cover image)
*
* @param streamData json data of all the streams
*
* @return True if the stream is valid video stream. False otherwise.
*/
bool hasValidVideoStream(const nlohmann::json& streamData) const;

/**
* @brief Checks if the streams contains valid audio stream
*
* @param streamData json data of all the streams
*
* @return True if the stream is valid. False otherwise.
*/
bool hasValidAudioStream(const nlohmann::json& streamData) const;

/**
* @brief Checks if the stream's avg_frame_rate is 0/0
*
* @param frameRate the avg_frame_rate field from stream
*
* @return True if avg_frame_rate equals to 0/0. False otherwise.
*/
bool hasZeroFrameRate(const std::string& frameRate) const;

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So far we have not included [at]params for function parameters in the project. Let's keep it that way, and eventually we'll likely do a review of our commenting practices in the public APIs. Anyway, for private member functions, it's not necessary to provide docs in the header file.

Also, my previous remark on what valid stands for, for the 2 helpers remain open. I would add a line in the description, i.e. after the brief to define what valid means in our context.

@omeryusufyagci
omeryusufyagci merged commit 2d6f448 into omeryusufyagci:90-bug-audio-file-treated-as-a-video-file Jan 15, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working core Media processing component, the core C++ binary. iteration Requires further iteration for approval

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants