In a previous blog post, I have shown how to use ffplay as a more sophisticated video player than it is by default. In this blog post, I will show how to use it as an audio player.
Over the last few days, I have been creating videos for my [FFmpeg book](../../ffmpeg-book.html]. Recently, I found the original videos that I thought I had lost. I needed an audio player that displays metadata in more detail so I built on using FFplay. I have also integrated it with my online video downloader script so that it will play videos that I want to listen rather than watch.
If you press the W key, you can cycle through this window that my script creates and two native windows that ffplay usually displays. The only drawback is that it has lost its ability to fast-forward or go backwards. The screen does not act as a giant playback slider.
Code
This amovie filter in ffmpeg cannot handle files with apostrophe (') in the file name. If you were to directly type in the name, it will require five slashes to escape it. It seems almost impossible to do it in a script.
#!/bin/bash
set -u
sFont="/usr/share/fonts/truetype/freefont/FreeSans.ttf"
sFile="$*"
getSafeString() {
sText="$*"
sText="${sText//\'/’}"
MVE_SafeString="${sText//\:/ː}"
export MVE_SafeString
}
getNameValue() {
sSourceFile=$1
sName=$2
MVE_NameValue=""
sLine="$(grep -i $sName $sSourceFile)"
if [ ! -z "$sLine" ]; then
MVE_NameValue=${sLine##*=}
fi
export MVE_NameValue
}
if [ -f "$sFile" ]; then
if [ "${sFile//\'/}" != "$sFile" ]; then
notify-send "Audio FFplayer error" "File name contains illegal characters"
exit
fi
else
notify-send "Audio FFplayer error" "Inaccessible/non-existent file"
exit
fi
sTitle=""
sAlbum=""
sArtist=""
sYear=""
sCopyright=""
sComment=""
sContainer=""
sCodec=""
sChannels=""
sChannelLayout=""
sSampleRate=""
sBitRate=""
sTempFile1=$(mktemp)
sTempFile2=$(mktemp)
ffprobe -loglevel quiet -read_intervals %+60 \
-select_streams a:0 \
-show_entries "stream=duration : stream=codec_long_name :
stream=channel_layout : stream=channels :
stream=sample_rate : stream=bit_rate :
format=format_name : format=format_long_name " \
-print_format "default=nokey=0:noprint_wrappers=1" \
"$sFile" > "$sTempFile1"
ffprobe -loglevel quiet -read_intervals %+60 \
-show_entries "tags=title : tags=album: tags=artist : tags=year :
tags=date : tags=copyright : tags=comment " \
-print_format "default=nokey=0:noprint_wrappers=1" \
"$sFile" > "$sTempFile2"
cat "$sTempFile1"
cat "$sTempFile2"
getNameValue $sTempFile1 format_long_name
sContainer="$MVE_NameValue"
getNameValue $sTempFile1 codec_long_name
sCodec="$MVE_NameValue"
getNameValue $sTempFile1 sample_rate
sSampleRate="$MVE_NameValue"
getNameValue $sTempFile1 channel_layout
sChannelLayout="$MVE_NameValue"
getNameValue $sTempFile1 channels
sChannels="$MVE_NameValue"
getNameValue $sTempFile1 bit_rate
sBitRate="$MVE_NameValue"
getNameValue $sTempFile1 duration
sDuration="$MVE_NameValue"
getNameValue $sTempFile2 TAG:Title
sTitle="$MVE_NameValue"
getSafeString "$sTitle"
sTitle="$MVE_SafeString"
getNameValue $sTempFile2 TAG:Album
sAlbum="$MVE_NameValue"
getSafeString "$sAlbum"
sAlbum="$MVE_SafeString"
getNameValue $sTempFile2 TAG:Artist
sArtist="$MVE_NameValue"
getSafeString "$sArtist"
sArtist="$MVE_SafeString"
getNameValue $sTempFile2 TAG:Year
sYear="$MVE_NameValue"
if [ -z "$sYear" ]; then
getNameValue $sTempFile2 TAG:Date
sYear="$MVE_NameValue"
fi
getSafeString "$sYear"
sYear="$MVE_SafeString"
getNameValue $sTempFile2 TAG:Copyright
sCopyright="${MVE_NameValue}"
getSafeString "$sCopyright"
sCopyright="$MVE_SafeString"
sCopyright="${sCopyright//:/\\:}"
getNameValue $sTempFile2 TAG:Comment
sComment="${MVE_NameValue}"
getSafeString "$sComment"
sComment="$MVE_SafeString"
sComment="${sComment//:/\\:}"
rm $sTempFile1 $sTempFile2
if [ -z "$sChannels" ]; then
notify-send "Audio FFplayer error" "Unknown/non-existent audio"
exit
fi
sChartWLocationX=10
if [ $sChannels -gt 3 ]; then
sChartWLocationX=300
fi
if [ "$sDuration" = "N/A" ]; then
sTime=$(ffprobe -loglevel quiet -read_intervals %+60 \
-select_streams a:0 \
-show_entries "stream_tags=duration " \
-print_format "default=nokey=1:noprint_wrappers=1" \
"$sFile")
if [ "${#sTime}" -ge 6 ]; then
sTime=${sTime%.*}
sHours=${sTime%%:*}
sHours=${sHours#0}
sMinutes=${sTime#*:}
sMinutes=${sMinutes%:*}
sMinutes=${sMinutes#0}
sSeconds=${sTime##*:}
sSeconds=${sSeconds#0}
sDuration=$(echo ${sHours}*3600 + ${sMinutes}*60 + $sSeconds | bc)
else
notify-send "Audio FFplayer error" "Unable to determine duration of audio"
fi
else
sDuration=${sDuration%.*}
sHours="0"
sMinutes="0"
sSeconds="0"
if [ "$sDuration" -ge 3600 ]; then
sHours=$(echo $sHours / 3600 | bc)
fi
sRem=$(echo $sDuration - ${sHours}*3600 | bc)
if [ "$sRem" -ge 60 ]; then
sMinutes=$(echo $sRem / 60 | bc)
fi
sSeconds=$(echo $sDuration - ${sHours}*3600 - ${sMinutes}*60 | bc)
fi
if [ "$sHours" -lt 10 ]; then
sHours="0${sHours}"
fi
if [ "$sMinutes" -lt 10 ]; then
sMinutes="0${sMinutes}"
fi
if [ "$sSeconds" -lt 10 ]; then
sSeconds="0${sSeconds}"
fi
sTime="${sHours}h ${sMinutes}m ${sSeconds}s"
sBitRate=$(echo $sBitRate / 1000 | bc)
sPlayerTitle="$sTitle"
if [ -z "$sTitle" ]; then
sPlayerTitle=$(basename "$sFile")
sTitle="$sPlayerTitle"
fi
sFileName=$(basename "$sFile")
sFileName="${sFileName%.*}"
sImage="/tmp/${sFileName}.png"
ffmpeg -loglevel error -ss 1 -i "$sFile" -frames:v 1 -t 1 "$sImage"
if [ $? -eq 0 ]; then
ffplay -f lavfi \
-i "amovie='${sFile}',asplit=4[a1][a2][a3][out0];
amovie='${sFile}':s=dv[v];
[a1]showfreqs=s=250x100:mode=bar:cmode=separate:
colors=orange|red[chartf];
[a2]showvolume=w=250:h=50:p=0.6:dm=2:dmc=red[chartv];
[a3]showwaves=s=250x100:mode=cline:draw=full:
colors=yellow|orange:split_channels=1[chartw];
color=color=0xFFAAFF22:s=vga[c];
[v]scale=w=200:h=-2[art];
[c][art]overlay=x=W-w-20:y=H-h-20,
drawtext=x=round(W*t/${sDuration}):y=H-th-2:
fontfile='${sFont}':
fontsize=round(H/20):fontcolor=FF7777FF:
text='□',
drawtext=x=10:y=15:fontcolor=white:alpha=0.9:shadowx=1:shadowy=2:
fontsize=20:fontfile='${sFont}':
text='${sTitle}',
drawtext=x=300:y=60:fontcolor=white:alpha=0.9:shadowx=1:shadowy=2:
fontsize=15:fontfile='${sFont}':
text='Artist\: ${sArtist}',
drawtext=x=300:y=80:fontcolor=white:alpha=0.9:shadowx=1:shadowy=2:
fontsize=15:fontfile='${sFont}':
text='Album\: ${sAlbum}',
drawtext=x=300:y=100:fontcolor=white:alpha=0.9:shadowx=1:shadowy=2:
fontsize=15:fontfile='${sFont}':
text='Date\: ${sYear}',
drawtext=x=300:y=120:fontcolor=white:alpha=0.9:shadowx=1:shadowy=2:
fontsize=15:fontfile='${sFont}':
text='Copyright\: ${sCopyright}',
drawtext=x=300:y=140:fontcolor=white:alpha=0.9:shadowx=1:shadowy=2:
fontsize=15:fontfile='${sFont}':
text='Comment\: ${sComment}',
drawtext=x=300:y=160:fontcolor=white:alpha=0.9:shadowx=1:shadowy=2:
fontsize=15:fontfile='${sFont}':
text='Duration\: ${sTime}',
drawtext=x=300:y=180:fontcolor=white:alpha=0.9:shadowx=1:shadowy=2:
fontsize=15:fontfile='${sFont}':
text='Container\: ${sContainer}',
drawtext=x=300:y=200:fontcolor=white:alpha=0.9:shadowx=1:shadowy=2:
fontsize=15:fontfile='${sFont}':
text='Codec\: ${sCodec}',
drawtext=x=300:y=220:fontcolor=white:alpha=0.9:shadowx=1:shadowy=2:
fontsize=15:fontfile='${sFont}':
text='Channels\: ${sChannels} \(${sChannelLayout}\)',
drawtext=x=300:y=240:fontcolor=white:alpha=0.9:shadowx=1:shadowy=2:
fontsize=15:fontfile='${sFont}':
text='Sample rate\: ${sSampleRate}',
drawtext=x=300:y=260:fontcolor=white:alpha=0.9:shadowx=1:shadowy=2:
fontsize=15:fontfile='${sFont}':
text='Bitrate\: ${sBitRate} kbps'[bg];
[bg][chartf]overlay=x=10:y=40[b1];
[b1][chartv]overlay=x=10:y=170[b2];
[b2][chartw]overlay=x=${sChartWLocationX}:y=300[out1]" \
-autoexit -hide_banner \
-window_title "$sPlayerTitle « ffplayer »"
else
ffplay -f lavfi -loglevel error \
-i "amovie='${sFile}',asplit=4[a1][a2][a3][out0];
[a1]showfreqs=s=250x100:mode=bar:cmode=separate:
colors=orange|red[chartf];
[a2]showvolume=w=250:h=50:p=0.6:dm=2:dmc=red[chartv];
[a3]showwaves=s=250x100:mode=cline:draw=full:
colors=yellow|orange:split_channels=1[chartw];
color=color=0xFFAAFF22:s=vga,
drawtext=x=round(W*t/${sDuration}):y=H-th-2:
fontfile='${sFont}':
fontsize=round(H/20):fontcolor=FF7777FF:
text='□',
drawtext=x=10:y=15:fontcolor=white:alpha=0.9:shadowx=1:shadowy=2:
fontsize=20:fontfile='${sFont}':
text='${sTitle}',
drawtext=x=300:y=60:fontcolor=white:alpha=0.9:shadowx=1:shadowy=2:
fontsize=15:fontfile='${sFont}':
text='Artist\: ${sArtist}',
drawtext=x=300:y=80:fontcolor=white:alpha=0.9:shadowx=1:shadowy=2:
fontsize=15:fontfile='${sFont}':
text='Album\: ${sAlbum}',
drawtext=x=300:y=100:fontcolor=white:alpha=0.9:shadowx=1:shadowy=2:
fontsize=15:fontfile='${sFont}':
text='Date\: ${sYear}',
drawtext=x=300:y=120:fontcolor=white:alpha=0.9:shadowx=1:shadowy=2:
fontsize=15:fontfile='${sFont}':
text='Copyright\: ${sCopyright}',
drawtext=x=300:y=140:fontcolor=white:alpha=0.9:shadowx=1:shadowy=2:
fontsize=15:fontfile='${sFont}':
text='Comment\: ${sComment}',
drawtext=x=300:y=160:fontcolor=white:alpha=0.9:shadowx=1:shadowy=2:
fontsize=15:fontfile='${sFont}':
text='Duration\: ${sTime}',
drawtext=x=300:y=180:fontcolor=white:alpha=0.9:shadowx=1:shadowy=2:
fontsize=15:fontfile='${sFont}':
text='Container\: ${sContainer}',
drawtext=x=300:y=200:fontcolor=white:alpha=0.9:shadowx=1:shadowy=2:
fontsize=15:fontfile='${sFont}':
text='Codec\: ${sCodec}',
drawtext=x=300:y=220:fontcolor=white:alpha=0.9:shadowx=1:shadowy=2:
fontsize=15:fontfile='${sFont}':
text='Channels\: ${sChannels} \(${sChannelLayout}\)',
drawtext=x=300:y=240:fontcolor=white:alpha=0.9:shadowx=1:shadowy=2:
fontsize=15:fontfile='${sFont}':
text='Sample rate\: ${sSampleRate}',
drawtext=x=300:y=260:fontcolor=white:alpha=0.9:shadowx=1:shadowy=2:
fontsize=15:fontfile='${sFont}':
text='Bitrate\: ${sBitRate} kbps'[bg];
[bg][chartf]overlay=x=10:y=40[b1];
[b1][chartv]overlay=x=10:y=170[b2];
[b2][chartw]overlay=x=${sChartWLocationX}:y=300[out1]" \
-autoexit -hide_banner \
-window_title "$sPlayerTitle « ffplayer »"
fi
if [ -f "$sImage" ]; then
rm "$sImage"
fi
Save the above bash code as a text file named "audio-ffplayer.txt" preferably in your /opt directory and create a audio-ffplayer.desktop file (contents listed below) file in your /usr/share/applications so that it will show up as an available application in the "Open with" context menu when you right-click on audio files.
[Desktop Entry]
Name=Audio FFplayer
Comment=Play audio files using FFmpeg player
Exec=bash /opt/audio-ffplayer.txt %U
Terminal=false
Type=Application
MimeType=application/ogg;application/x-ogg;audio/aac;audio/flac;audio/midi;audio/mp3;audio/mp4;audio/mpeg;audio/ogg;audio/wav;audio/x-flac;audio/x-mp3;audio/x-mpeg;audio/x-ms-asx;audio/x-ms-wma;audio/x-vorbis+ogg;audio/x-wav;audio/x-wavpack;
Icon=/usr/share/icons/gnome/256x256/mimetypes/audio-x-generic.png
NoDisplay=true
Screenshot

Video demo
In this video, I encounter an online video my browser cannot display. I then copy the URL and run it with my video downloader script. The script downloads the audio portion of the video, converts it to MP3 (using ffmpeg of course), embeds the thumbnail and launches the MP3 with my audio player script.

Before playing this video, click on the Autoplay button to prevent the annoying autoloading of related videos.
Bonus feature
This player can play video files too. They will play downsized in the bottom-right corner where the thumbnail is displayed for audio files. There will be a delay though. Generating a thumbnail (if present) for audio files is quick as the files are small. But for a video files, which are larger, it may take a while.
Become an FFmpeg PRO by reading my book Quick Start Guide to FFmpeg.
- MORE INFO — http://www.vsubhash.in/ffmpeg-book.html
- BUY — https://books2read.com/ffmpeg (common link for all stores)
- FREE INTERNATIONAL SHIPPING -
https://www.apress.com/9781484287002
UPDATE: The code for the audio player was updated to include the option -read_intervals %+60 for the ffprobe commands. Otherwise, the script would start with a delay on large video files.
