Bash - calculate length of all mp3 files in one folder
Asked Answered
F

5

5

I have the following command which returns the filenames and lengths of mp3s files:

mp3info -p "%f: %m:%02s\n" *.mp3

How can I use this in a bash script to return the total length (sum) of all mp3 files in a given directory? I would like to have the following notation: mm:ss

Filippo answered 6/8, 2017 at 19:43 Comment(2)
Please edit your Q to show 3-5 lines of output from that command as well as the exact format you would like the final output (given that sample input) to look like. AND if you have any code, it is best to share that, as S.O. isn't really a code-writing service. We're happy to help you fix what's not working. Maybe you want to look at grymoire.com/Unix/awk.html for a tool that is designed for such problems. awk is available on all Unix/Linux platforms as part of the std install. Good luck.Antidisestablishmentarianism
Possibly related: #8933553Druid
F
5

I'd go for a three step approach:

  1. instead of printing filename.mp3: mm:ss\n, omit the file name and print the overall seconds
  2. Build arithmetic expression from result, giving you total seconds
  3. divide by 60, round down to get minutes, calculate remainder of seconds.

The first step is easy

mp3info -o '%S' 

will do the job. Now, we want things to give us a valid numerical expression of the form

time1+time2+....

so,

mp3info -o '%S + '

would seem wise.

Then, because the last thing mp3info prints will then be a +, let's add a zero:

"$(mp3info -o '%S + ') 0"

and use that string in an arithmetic expression:

total_seconds=$(( $(mp3info -o '%S + ' *.mp3) 0 ))

Now, get the full minutes:

full_minutes=$(( total_seconds / 60 ))

and the remaining seconds

seconds=$(( total_seconds % 60 ))

So the total script would look like

#!/bin/bash 
# This code is under GPLv2
# Author: Marcus Müller

total_seconds=$(( $(mp3info -o '%S + ' *.mp3) 0 ))

printf "%02d:%02d\n" $((total_seconds / 60)) $((total_seconds % 60))
Fulfil answered 6/8, 2017 at 19:58 Comment(8)
To print in mm:ss format, use printf, like this: printf "%02d:%02d\n" $((total_seconds / 60)) $((total_seconds % 60)).Remediable
@Remediable I see zero advantage of that. It's longer, harder to read, and less efficient. Ah you mean for the double digit notation, yeah, OK, point takenBeat
.. and it prints leading zeros.Remediable
Yeah my bad. I think the credit should go to you -would you mind editing my answer?Beat
total_seconds = breaks, you can't have spaces around = in the assignment.Wellappointed
Small typo -o instead of -p in the final versionFilippo
There are quite a number of significant problems with this example including but not limited to abuse of sub-shells and wacky concatenation. This all could be accomplished with piping mp3info into awk as the Unix gods intended. An example can be found at: https://mcmap.net/q/1945187/-bash-calculate-length-of-all-mp3-files-in-one-folderCarmelcarmela
I'll argue that these gods do not exist, if they exist they predate multimedia file formats and tools by 4 decades, and that they hold no power in the domain of humankind, where "getting the job done" beats "learning how to do it more elegantly by learning the awk language" every time ;)Beat
E
3

Base calculation in mp3info is error prone, as metadata is not real data. Using sox (swiss army tool for audio) you can get directly from mp3 file.

R=0
for a in *.mp3
do
  T="$(soxi -D $a 2>-)"
  echo $T
  [[ "$T" != "" ]] && R="$R + $T"
done
echo $R | bc

if you want hours

echo "($R) / 60" | bc

For long list of mp3 would be better to sum duration at each loop

Expunge answered 11/8, 2017 at 7:19 Comment(2)
This hanged and apparently altered a filename in the directory ("01 - ..." was changed to '-'. Maybe "2>-" should be "2>/dev/null".Thunder
"$a" needs quotes to properly handle filenames with whitespace. Better yet, call it a more descriptive name like "$TRACK" (variable-naming pet peeve of mine...).Thunder
C
2

A one-liner to rule them all:

mp3info -p '%S\n' *.mp3 | awk '{s+=$1} END {printf"%d:%02d:%02d\n",s/3600,s%3600/60,s%3600%60}'
Carmelcarmela answered 18/9, 2022 at 10:20 Comment(0)
D
0

This Bourne shell one-liner, when used inside a directory containing only music files, will output their total length in seconds with a very high degree of precision:

LENGTH=0; for file in *; do LENGTH="$LENGTH+$(ffprobe -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$file" 2>/dev/null)"; done; echo "$LENGTH" | bc

Modified to only output the length of .mp3 files (and thus avoid breaking on the innocuous .docx sitting within our music directory), it would look like this:

LENGTH=0; for file in *.mp3; do if [ -f "$file" ]; then LENGTH="$LENGTH+$(ffprobe -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$file" 2>/dev/null)"; fi; done; echo "$LENGTH" | bc

And if, for example, we wanted to output the total length of audio with only several different file extensions, we can do that as well by adding a second wildcard, still avoiding the dreadful, scary .docx:

LENGTH=0; for file in *.mp3 *.ogg; do if [ -f "$file" ]; then LENGTH="$LENGTH+$(ffprobe -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$file" 2>/dev/null)"; fi; done; echo "$LENGTH" | bc

Naturally, ffmpeg has to be installed to use either of these.

Druid answered 2/12, 2019 at 13:28 Comment(0)
T
0

Here's a variation of @albfan's kind answer with descriptive variable names and syntax highlighting. It works for an entire directory of directories (albums).

#!/bin/bash

TOTAL=0

for ALBUM in *
do
    if [ -d "${ALBUM}" ] ; then
        ALBUM_TIME=0
        echo ${ALBUM}

        # Make `cd` quiet
        cd "${ALBUM}" &>/dev/null
        for TRACK in *.mp3; do
            TRACK_TIME="$(soxi -D "${TRACK}" 2>/dev/null)"
            # noisy
            # echo "Track duration: $TRACK_TIME"
            [[ "$TRACK_TIME" != "" ]] && ALBUM_TIME="$ALBUM_TIME + $TRACK_TIME"
        done
        # Convert summation to a single number for efficiency
        ALBUM_TIME=`echo $ALBUM_TIME | bc`
        # This has to be two stmts?
        echo -n "Album time (min): "; echo 'scale=2;' ${ALBUM_TIME} / 60 | bc
        echo # line break
        TOTAL="$TOTAL + $ALBUM_TIME"
        cd ..
    fi

    # Evaluate the expression
    TOTAL=`echo $TOTAL | bc`

done

echo -n "Total time (hrs): "; echo 'scale=2;' ${TOTAL} / 3600 | bc
Thunder answered 21/2, 2020 at 21:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.