sandreas / m4b-tool

m4b-tool is a command line utility to merge, split and chapterize audiobook files such as mp3, ogg, flac, m4a or m4b
MIT License
1.18k stars 76 forks source link

[Solution] Fade in/out effect for MP3s merged into an M4B #225

Open devnoname120 opened 1 year ago

devnoname120 commented 1 year ago

I spent quite a bit of time and attempts to figure out how to add a fade in/out effect between MP3s merged into an M4B. I share my solution here for future visitors. Note that this solution could easily be natively integrated in m4b-tool but my schedule is very busy and unfortunately I don't have the bandwidth to do a pull request.

My requirements:

My solution:

find . -iname '*.mp3' -print0 | xargs -0 -I{} -P 8 ffmpeg -i {} -f lavfi -i anullsrc -max_muxing_queue_size 9999 -map_metadata 0 -strict experimental -movflags +faststart -vn -y -ab 196k -ar 44100 -ac 2 -acodec libfdk_aac -filter_complex '[0]afade=t=in:d=1:curve=tri[a]; [1]atrim=0:0.7[t]; [a][t]acrossfade=d=0.7:o=1:c1=tri:c2=nofade' -f mp4 {}.m4b

m4b-tool merge -vvv --debug --no-conversion --include-extensions=m4b --output-file="merged.m4b" .

Note: For the conversion step I directly use a FFmpeg command (ffmpeg -i {} -f lavfi -i […]) instead of m4b-tool for two reasons: 1) m4b-tool silently ignores the --ffmpeg-param for the Fraunhofer FDK AAC (libfdk_aac) codec (!) because m4b-tool directly runs ffmpeg instead of using the Ffmpeg.php executable abstraction.

Explanations: The interesting parts are the following options in the first line. They add a fade-in + fade-out effect losslessly without an extra re-encoding step thanks to a filtergraph:

-f lavfi -i anullsrc
-filter_complex '[0]afade=t=in:d=1:curve=tri[a]; [1]atrim=0:0.7[t]; [a][t]acrossfade=d=0.7:o=1:c1=tri:c2=nofade'

Detailed break down for the curious:

sandreas commented 1 year ago

Phew, thank you for this huge and detailed investigation.

I (personally) do not have ANY use case for this - fading in does indeed modify the audio in a way I never would like to have it. Furthermore I don't think this is really an issue... more like a detailed guide to achieve something.

The --ffmpeg-param thing was a quick and dirty approach to provide some extended feature, but it was a really, REALLY bad idea. It causes more issues than it solves in my opinion.

What I should have done instead was to provide a small plugin api to modify commands before they are getting executed. Example:

// my-plugin.php
m4btool_register_command_plugin(function(array $command, CommandContext $context) {
    if(in_array("ffmpeg", $command, true)) {
        return $command;
    }
    // modify command as you wish
    // ....
   // then return it
   return $command
});

And then running

m4b-tool merge --command-plugin="my-plugin.php" ....

What do you think? Would this be better for your use case?

devnoname120 commented 1 year ago

Hmm I think that a plugin API would still have a learning curve and wouldn't be very convenient for one-off solutions. Just like with the --ffmpeg-param you would need to understand which commands m4b-tools runs and in which order. You'd additionally have to figure out how you should patch the array making sure that you only apply the changes at the right steps of the process.

A plugin API could definitely be useful if you plan on welcoming plugin contributions. But then it would require substantial effort to maintain these plugins considering that they would patch the command (not necessarily nicely in nice and future-proof ways).

In my case the hardest was to figure out where/how the ffmpeg commands were built, that --ffmpeg-param didn't behave the way I assumed it would, and finally deciding that it would just be less effort to add an echo right before the ffmpeg commands get executed so that I can just grab the commands and modify them manually.

I think that a great starting point would be to print the ffmpeg commands that m4b-tool runs (maybe by default to make them easier to discover). People who want custom behaviors could just use --dry-run, modify the ffmpeg commands, and manually run them. If they want to contribute the feature back to m4b-tool they can add a new option and do a PR.

What do you think?

sandreas commented 1 year ago

I think that a great starting point would be to print the ffmpeg commands that m4b-tool runs (maybe by default to make them easier to discover). People who want custom behaviors could just use --dry-run, modify the ffmpeg commands, and manually run them. If they want to contribute the feature back to m4b-tool they can add a new option and do a PR.

Oh that is easy. Just use --debug. Maybe it would be nice to have ONLY the commands printed, so an option with --command-logfile or something may be the solution for this.

devnoname120 commented 1 year ago

@sandreas Does it also work with FDK AAC? iirc the command was built differently but I'm not sure if the debug log works anyway or not.