Generating reports for SoundExchange, BMI, etc

Forgive me if this has already been discussed or if there’s a solution in code that I haven’t explored yet. I thought I would ask first and then maybe be pointed in the proper direction.

I’m eager to automate, as much as possible, the quarterly reports to Sound Exchange and the PROs. Currently this is a manual process because the two reports generated by Libretime are logs of playout history and listener stats, but there is not a one-to-one correspondence between timestamps in the two reports. So I have to pull the history of a two-week period, guestimate the number of listeners by looking at the graph, and then crunch the data down to unique tracks with the number of plays and the number of total listeners.

My question: Is there a point in the playout process where the application knows the file id of the current song playing AND the number of listeners currently connected to the stream? If so, I would like to add a method that records file id, increments number of plays, and adds the current number of listeners to any stored value for that file id (or creates a new record if it’s the first time the file has been played during this two-week audit period). Then I could run a query that pulls file info and numbers of play / listeners, using this new table, and be done with my report. Ideally, I’d also like to be able to turn the ‘reporting period’ on and off, maybe via configuration, so that this method would only fire if reporting_period = 1.

What do you think?

There is currently no functionality or code written for this. It has been requested before see https://github.com/LibreTime/libretime/issues/283 & https://github.com/LibreTime/libretime/issues/196 but nobody has specified out what needs to be done to implement it.

It sounds like you are on the right track here, the number of listeners is pulled from Icecast by querying the Icecast server and I’m assuming writing this info to the database. I’m also interested in helping with this as I think it would be good for the number of listeners to certain tracks to be easily tracked.

Discussing it on the above issues might be good as well once we get into the specifics of code etc.

Thanks, Robb. I’ve commented on issue 283 with my ideas.

– Pat

I just added some more thoughts based upon my analysis on there tonight. I’ll be doing some of this work paid for by the grant we received so any feedback would be welcome.

Has there been any movement on the generation of a SoundExchange report?
How are others doing this?

Pulling my hair out at the moment, as if we cannot get this problem solved we might as well shut down as the penalties will kill us :slight_smile:

Eventually, I gave up and switched to Live365, which meant that my stream was only legal/accessible in the US, UK and Canada. But not having to deal with those reports and actually paying less for royalties than when I did it all myself was worth it.

Thank you for your reply.
For us this is not a possibility as this is a ministry with listeners in some countries with, hmm, let’s say dubious governments :slight_smile:

I’ve baked some stuff together which might help some. Is it complete, probably not. Can it be improved, most likely.

One bash script which reads every new entry in the Icecast log file.
One service ctl script which now let’s the batch file be a service.
One Awk Script that makes every entry uniq, adding up the number of listeners and the number of times it played.
#!/bin/bash
Service_name=“Your station Name”
Transmission_category=“B” # determined by SoundExchange in our case it is B
week=date +%U
#set -x
logFile=./IcecastLogs/playlist.log

# Function to process each line

process_line() {
line=“$1”

# Split the line by the '|' delimiter
IFS='|' read -ra parts <<< "$line"

# Extract date and time from the first part
datetime_part=${parts[0]}
stream_name=${parts[1]}
listeners=${parts[2]}
track_info=${parts[3]}

# Further split the datetime part
IFS=':' read -ra date_time <<< "$datetime_part"
date_part=${date_time[0]}
time_part=(${date_time[@]:1})

# Extract the day, month, year from the date part
IFS='/' read -ra date_parts <<< "$date_part"
day=${date_parts[0]}
month=${date_parts[1]}
year=${date_parts[2]}

# Extract hour, minute
hour=${time_part[0]}
minute=${time_part[1]}

# Split the track_info into show name, performer, and song title
track_info=`echo "${track_info// - /-}"`
IFS='-' read -ra track_parts <<< "$track_info"
show_name=${track_parts[0]}
performer=${track_parts[1]}
song_title=${track_parts[2]}

# Escape single quotes in the variables
performer=$(echo "$performer" | sed "s/'/''/g")
song_title=$(echo "$song_title" | sed "s/'/''/g")
album_name=$(docker exec libretime-postgres psql -U XXXXXX -q -t -c "select album_title from cc_files WHERE artist_name='$performer' and track_title='$song_title';" --pset footer=off | tail -2 | head -1 | cut -c2-) 2> /dev/null
check_label=$(docker exec libretime-postgres psql -U XXXXXX -q -t -c "select label from cc_files WHERE artist_name='$performer' and track_title='$song_title';" --pset footer=off | tail -2 | head -1 | cut -c2-) 2> /dev/null

#==============================================================================

if [ -z "$check_label" ]
then
    jsondata=$(curl -s -G \
    --url "https://api.discogs.com/database/search" \
    --data-urlencode "artist=$performer" \
    --data-urlencode "track=$song_title" \
    --data-urlencode "release_title=$album_name" \
    --data-urlencode "type=release" \
    --data-urlencode "format=CD" \
    -H "Authorization: Discogs token=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
    -H "User-Agent: EWCMI_Radio/1.0") 2> /dev/null
    label=`echo $jsondata | jq -r '.results[0].label[0]'`
fi
if [ -z "$check_label" ] && [ "$label" != "null" ]
then
    lab=$label
    perf=$performer
    song=$song_title
    album=$album_name
    label=$(echo "$label" | sed "s/'/''/g")
    performer=$(echo "$performer" | sed "s/'/''/g")
    song_title=$(echo "$song_title" | sed "s/'/''/g")
    album_name=$(echo "$album_name" | sed "s/'/''/g")
    docker exec libretime-postgres psql -U XXXXXX -t -c \"update cc_files set label='$label' WHERE artist_name='$performer' and track_title='$song_title' and album_title='$album_name';\"
else
label=$check_label
fi

#==============================================================================

# Get missing info: Publisher, icrs
# search on artist, recording(track title), 
# Print the extracted variables (or use them as needed in your script)
LogLine="$Service_name|$Transmission_category|$performer|$song_title|ISRC|$album_name|$label|$listeners|(aggregate Tuning Hours)|$show_name|Total Plays"
LogLine=`echo $LogLine | sed "s/''/'/g"`
LogLineTest=$(echo "$LogLine" | tr '[:upper:]' '[:lower:]' | grep -iE "jingle|promo|---|legal|need him")
if [ -z "$LogLineTest" ]
then
    echo "$LogLine" >> ./SoundExchange/$year-$month-$week.raw
fi

}

# Start monitoring the log file
tail -Fn0 “$logFile” | while read -r line; do
test=echo $line | grep offline
if [ -z “$test” ]
then
process_line “$line”
fi
done

The AWK Script to process the raw data log file

cat SoundExchangeFormat.sh

#!/bin/bash
#ls | grep -v SoundExchangeFormat.sh |cut -c10-11 | sort
Path=“/xxxxxxxxx/xxxx/SoundExchange”
declare -i Week
Year=date +%Y
#Month=date +%b
Week=date +%U

Find the first day of the specified week

first_day_of_week=$(date -d “$Year-01-01 +$((Week - 1)) weeks” +%Y-%m-%d)

Extract the month from that date

month=$(date -d “$first_day_of_week” +%B)
Week=$Week-1
Month=echo $month | cut -c1-3
#set -x
if [ -f “$Path/$Year”-“$Month”-“$Week”“SE.csv” ]
then
#ls “$Path/$Year”-“$Month”-“$Week”
“SE.csv”
exit
fi

Define the log file

logfile=$Path/$Year"-“$Month”-“$Week”.raw"

Define the log file

date=echo $1 | awk -F"." '{print $1}'
#set -x

Process the log file

awk -F ‘|’ ’
{
# Create a key using fields 3 (Performer), 4 (Album), 6 (Title), and 7 (Label)
key = $3 FS $4 FS $6 FS $7

# Sum the listeners for this key
listeners[key] += $8

# Count the number of plays for this key
plays[key]++

# Store the first occurrence of each unique entry
if (!(key in first_occurrence)) {
    first_occurrence[key] = $0
}

}
END {
# Reprint unique log entries with updated Total Plays and listener count
for (k in first_occurrence) {
# Update the Total Plays and Listener count in the original entry
split(first_occurrence[k], fields, FS)
fields[8] = listeners[k] # Update number of listeners
fields[11] = plays[k] # Update total plays

    # Print the updated entry
    print fields[1] FS fields[2] FS fields[3] FS fields[4] FS fields[5] FS fields[6] FS fields[7] FS fields[8] FS fields[9] FS fields[10] FS fields[11]
}

}
’ “$logfile” > “$Path/$Year”-“$Month”-“$Week”_“SE.csv”

#echo “Unique log file has been created with updated Total Plays and listener counts.”

The SoundExchangeformat.sh is executed each Monday morning from Cron
0 2 * * 1 /xxxxxxxxx/xxxx/SoundExchange/SoundExchangeFormat.sh > /dev/null 2>&1

Starting tracklog.sh as a service
/etc/systemd/system/tracklog.service

cat /etc/systemd/system/tracklog.service

[Unit]
Description=Tracklog Bash Script
After=network.target

[Service]
ExecStart=/home/container/libretime/tracklog.bash
Restart=always
RestartSec=5
User=root
WorkingDirectory=/home/container/libretime
StandardOutput=journal
StandardError=journal
#Environment=YOUR_ENV_VAR=value

[Install]
WantedBy=multi-user.target

Hopefully this might help some people, or instigate the creation of the log file from within Libretime.

Chris.