Analyzing 12 years of music tracking history

Since 2009, I’ve been tracking what songs I listen on using the then popular Winamp and nowadays Spotify. I was always wondering what questions I could answer from this data, such as:

  • How does my activity look like throughout the years?
  • Which days are the most active?
  • How has my music taste evolved over this period?
  • I know that I had a lot of obsessions with various songs. Can I identify them? And how long did they last?
  • Are there patterns of music genres that I listen to through the year?

Collecting data

Fortunately, there are many free tools for getting data so there was no need to spend time on writing code and learning how their API works. The tool I used was and I got back a 145Mb file containing around 200k scrobbles. If an average song is 3 minutes long, this is more than 416 days of listening time!

After cleaning up the file and using the musicbrainz‘s API to get each artists genre information (which took quite a while as I had to limit my requests to 1/sec and there were over 6k unique artists) I was ready to analyse the data using pandas and seaborn. After constructing a dataframe (see below) with all the information I needed (around 42Mb) I was ready to analyse my listening history!

{“mbid”:”cc0b7089-c08d-4c10-b6b0-873582c17fd6″,”#text”:”System of a Down”}{‘mbid’: ’03bfb2be-bb2e-3252-ae04-ed15e2c333c6′, ‘#text’: ‘Toxicity’}Chop Suey!2021-11-20 14:57:45[rock, nu metal, metal, experimental rock]
{“mbid”:”f59c5520-5f46-4d2c-b2c4-822eabf53419″,”#text”:”Linkin Park”}{‘mbid’: ”, ‘#text’: ‘Hybrid Theory (Bonus Edition)’}Papercut2021-11-20 14:51:48[rock, rapcore, rap rock, rap metal, pop rock]

Yearly activity

The best way to visualise this is by creating a heatmap per year. This was a good start to familiarize myself with pandas and seaborn and I was able to do it with the code snippet below. First, I grouped my data per year, added a new day column and finally use the heatmap function of the seaborn library.

def generate_yearly_play_heatmaps(df):
    start_year = df.iloc[-1]['date'].year
    end_year = df.iloc[0]['date'].year
    for year in range(start_year, end_year + 1):
        begin = datetime.datetime(year, 1, 1)
        end = datetime.datetime(year, 12, 31)
        days = pd.DataFrame({'plays':0,
                             'date':pd.date_range(begin, end, freq='D')})
        month_names = pd.date_range(begin, end, freq='M').strftime("%B").tolist()
        # Get yearly data
        yearly_data = df[(df['date'] > begin) & (df['date'] < end)]
        yearly_data = pd.merge(yearly_data, days, how='outer', left_on='date', right_on='date')
        yearly_data['date'] = pd.to_datetime(yearly_data['date']).dt.normalize()
        yearly_data = yearly_data.value_counts(['date']).reset_index(name="plays")

        # Since we injected some new data, replace them with 0
        yearly_data.loc[yearly_data['plays'] == 1, 'plays'] = 0

        # Generate extra day/month columns
        yearly_data = yearly_data.sort_values('date')
        yearly_data['month'] = yearly_data['date'].dt.month_name()
        yearly_data['date'] = yearly_data['date'].dt.strftime('%d')
        # Create monthly dfs for easier plotting
        month = yearly_data.reset_index().pivot(index='month', columns='date', values='plays').reindex(month_names)
        fig, ax = plt.subplots(figsize=(31,12)) 
        sns.heatmap(month, ax=ax, cmap=sns.color_palette("crest", as_cmap=True), annot=True, fmt='g', square=True)
        plt.title('Number of songs listened each day in ' + str(year))
        fig.savefig('heatmaps/plays_in_' + str(year) + '.png') 

After running it, I was pleased to get some nice heatmaps:

Now that I got the hang of it, I was ready to tackle some more interesting questions!

Which days are the most active

This time, it makes sense to breakdown the analysis not only per year but also per month as some interesting patterns could be observed. The code used can be seen below:

def generate_daily_activity(df):
    start_year = df.iloc[-1]['date'].year
    end_year = df.iloc[0]['date'].year
    day_names = pd.date_range(datetime.datetime(2009, 1, 5), datetime.datetime(2009, 1, 12), freq='D').strftime("%A").tolist()
    month_names = pd.date_range(datetime.datetime(2009, 1, 5), datetime.datetime(2009, 12, 31), freq='M').strftime("%B").tolist()
    yearly_data = df
    yearly_data['day'] = yearly_data['date'].dt.day_name()
    yearly_data['year'] = yearly_data['date'].dt.year
    yearly_data['month'] = yearly_data['date'].dt.month_name()
    monthly_data = yearly_data
    yearly_data = yearly_data.groupby(['day', 'year'], as_index=False).count()
    yd = pd.DataFrame()
    for year in range(start_year + 1, end_year + 1):
        yd = pd.concat([yd, yearly_data.loc[yearly_data['year'] == year].set_index('day').reindex(day_names).reset_index()])
    sns.catplot(data=yd, x='year', hue='day', y='date', kind='bar', height=5, aspect=1.7)
    plt.ylabel('Number of songs listened per day')

    # Get monthly data
    monthly_data = monthly_data.groupby(['month', 'year'], as_index=False).count()
    yd = pd.DataFrame()
    for year in range(start_year + 1, end_year + 1):
        yd = pd.concat([yd, monthly_data.loc[monthly_data['year'] == year].set_index('month').reindex(month_names).reset_index()])
    sns.catplot(data=yd, x='month', hue='year', y='date', kind='bar', height=7, aspect=2.7)
    plt.ylabel('Number of songs listened per month')

A few key things that I noticed from those graphs are:

  • During the period 2009 to 2011 my listening patterns during the week were about the same
  • There was a big drop during 2012 to 2016
  • In 2017, 2018 and 2019 there is a clear difference between Weekdays and Saturday/Sunday
  • 2020 and 2021 this difference has shrinked

All the above patterns can be explained quite easily: During 2009-2011 I was in the University and from 2012 to 2016 I was mostly listening to radio (no data tracking) that’s why there is a huge drop in the data tracked. In 2016, I started working thus, I was mostly listening to music during work hours (hence the drop on the weekends) and of course early 2020 there was Covid-19 so, the distinction between days became almost non-existent!

So far, everything looks as expected but what I wanted to find out was more meaningful patterns: if and how my music taste changed over the years.

The evolution of my music taste

The way I chose to approach this question was simple: Find the top 5 music genres and see how this trend evolves. Each artist has multiple genres thus, I only pick the first one that I got back from musicbrainz in order to keep things simple.

def music_taste(df):
    start_year = df.iloc[-1]['date'].year
    end_year = df.iloc[0]['date'].year

    yearly_data = df
    yearly_data['genres'] = yearly_data['genres'].apply(lambda d: d if isinstance(d, list) else [])
    yearly_data['year'] = yearly_data['date'].dt.year
    yd = pd.DataFrame()
    for year in range(start_year + 1, end_year + 1):
        # Get 5 most popular genres
        tmp_df = pd.DataFrame(yearly_data.loc[yearly_data['year'] == year]['genres'].apply(pd.Series).iloc[:,0].value_counts(normalize = True).rename('norm'))
        tmp_df['year'] = year
        tmp_df['norm'] = tmp_df['norm'].apply(lambda x : x * 100)
        yd = pd.concat([yd, tmp_df[:5]])

    pivot_df = yd.pivot(index='year', columns='index', values='norm').sort_index(ascending=False)

    ax = pivot_df.plot.barh(stacked=True, figsize=(30,15))
    handles, labels = ax.get_legend_handles_labels()
    counter = 0
    for i, p in enumerate(ax.patches, 0):
        if (i % len(pivot_df) == 0) & (i != 0):
            counter += 1
        left, bottom, width, height = p.get_bbox().bounds
        label = labels[counter]
        if width > 0:
            ax.annotate((f'{label}: {"%.1f" % width }%'), xy=(left+width/2, bottom+height/2), ha='center', va='center')
    plt.legend(loc='center left', bbox_to_anchor=(1.0, 0.5))
    plt.xlabel('Percentage of listening history')
    plt.title('Top 5 music genres per year')
    plt.savefig('barplots/yearly_genres.png', dpi=320)

After running the above code, I got some interesting results! Looking at the genre trends someone can easily spot a major constant throughout the years (rock) which eventually took a huge portion of my listening time.

Another thing that I noticed was that as the years go by I started listening to EDM less and new genres slowly took place such as synthwave, synthpop and more heavy rock styles. It’s interesting to focus on synthwave as it was a genre that came out of nowhere and took a large portion of my listening time. The Google trend of this genre shows that it started gaining in popularity around mid 2016 which is exactly when I started listening.

Also, some trends emerged and grew over time, such as r&b but eventually disappeared into oblivion! After my music taste analysis was done it was time to move into something more complicated.

Music obsessions

Before measuring this, I had to define what a music obsession is. There are many times where I find myself listening to a song on repeat for days. But how many days before it’s categorized as an obsession? I couldn’t find enough data online, so I came up with this arbitrary rule: “Listening a song for longer than 5 days and at least 5 times per day classifies for an obsession”. So, my next step was to dig into my data and find all the possible songs that fit in the above rule.

def music_obsessions(df):
    # Drop irrelevant columns
    obsessions = df.drop(['genres', 'album', 'artist'], 1)
    obsessions['date'] = df['date']

    # Do an early filtering
    # Keep songs that appear more than OBSESSIONS_PLAYS_STREAK * OBSESSIONS_DAYS_STREAK times overall
    obsessions = obsessions[obsessions.groupby(['name'])['name'].transform('size') > (OBSESSIONS_PLAYS_STREAK * OBSESSIONS_DAYS_STREAK)].reset_index(drop=True)

    # Keep songs that appear more than OBSESSIONS_PLAYS_STREAK times per day
    obsessions = obsessions.groupby(['date', 'name']).size().to_frame('size')
    obsessions = obsessions[obsessions['size'] > OBSESSIONS_PLAYS_STREAK].reset_index()

    # Get unique songs
    unique_songs = obsessions['name'].unique()

    # Create final df
    day = pd.Timedelta('1d')
    final_df = pd.DataFrame()
    for song in unique_songs:
        tmp_df = obsessions[obsessions['name'] == song]['date']
        if tmp_df.shape[0] < OBSESSIONS_DAYS_STREAK:

        # Find consecutive dates
        cons_dates = ((tmp_df - tmp_df.shift(-1)).abs() == day) | (tmp_df.diff() == day)

        # Group consecutive dates and filter based on concurent days
        filtered = tmp_df[cons_dates]
        breaks = filtered.diff() != day
        groups = breaks.cumsum()
        for _, g in filtered.groupby(groups):
            if(g.shape[0] >= OBSESSIONS_DAYS_STREAK):
                days = int((g.iloc[-1] - g.iloc[0]) / day) + 1
                a_df = pd.DataFrame([[song, g.iloc[0], g.iloc[-1], '%.20s (%dd)' % (song , days)]], columns=['Task', 'Start', 'Finish', 'Labels'])
                final_df = pd.concat([final_df, a_df])

    final_df.reset_index(drop=True, inplace=True)

    # Generate gantt chart
    fig = px.timeline(final_df, x_start="Start", x_end="Finish", y="Task", color="Task", text = "Labels")
    fig.update_yaxes(autorange="reversed", visible=False)
    fig.write_image("barplots/music_obsessions.png", width=10*300, height=2*300)

I decided that a Gantt chart would be the best way to visualise this and running the above code results in the timeline below. Most of my music obsessions last for 5 days with only 2 exceptions.

Someone might have a different definition of what an obsession is so you can play around with OBSESSIONS_DAYS_STREAK and OBSESSIONS_PLAYS_STREAK and generate the timeline that suits you.

Now that I’ve found my obsessions, I could finally move to the last question which I find to be the most interesting one.

Patterns of music

When I started this analysis there was one question that I was aiming to answer: What are my music patterns throughout the years and if there is any consistency? Initially, I thought on doing this analysis on music genres but I decided that it would be more interesting to do this with the actual artists. I would first have to split my data into years and months and then remove the artists that I’ve listened to 1 or 2 times as those were probably into a random playlist. Then, I would have to find which artists keep appearing throughout the years/months and if there is a pattern. For a pattern to occur, I kept only artists that appear more than 3 times through the years, have with more than 10 plays per month and span across 3 or more years.

def music_patterns(df):
    start_year = df.iloc[-1]['date'].year
    end_year = df.iloc[0]['date'].year
    yearly_data = df.drop(['genres', 'album', 'name'], 1)
    yearly_data['artist'] = yearly_data['artist'].apply(lambda d: d['#text'] if isinstance(d, dict) else dict())
    yearly_data['year'] = yearly_data['date'].dt.year
    yearly_data['month'] = yearly_data['date'].dt.month
    yearly_data = yearly_data.dropna()

    yd = pd.DataFrame()
    for year in range(start_year + 1, end_year + 1):
        tmp_df = yearly_data[yearly_data['year'] == year]
        tmp_df['count'] = tmp_df.groupby(['month', 'artist'])['artist'].transform('count')
        tmp_df = tmp_df.drop_duplicates()
        tmp_df['year'] = year
        yd = pd.concat([yd, tmp_df])
    # Drop artists with less than 10 plays per month
    yd = yd[yd['count'] >= 10]
    yd.reset_index(inplace=True, drop=True)
    # Drop artists that appear less than 3 times. (A pattern should be more than 3)
    yd = yd[yd.groupby(['artist'])['artist'].transform('size') > 3]
    # Also drop artists that don't appear in more than 3 different years
    tmp_df = yd.groupby(['artist'], as_index= False)['year'].nunique()
    tmp_df = tmp_df[tmp_df['year'] < 3]
    for artist in tmp_df['artist']:
        yd = yd[~(yd['artist'] == artist)]
    ax = sns.scatterplot(x='date', y='artist', hue='artist', legend=False, data=yd)
    # yd.plot(x='date', y='artist', kind='scatter')
    plt.savefig('barplots/music_patterns.png', dpi=320)

The plot genreated is a bit messy since it contains more than 100 artists but with a closer look (you can click on the image to enlarge it) one can see some interesting patterns. One example is that I tend to listen to Eminem during the period of September – November.

There are many more interesting questions that could be answered but I decided to stop my analysis here as this could take on for weeks! Overall it was a fun project were I learned a bit about pandas, plotting with python and my music taste!

Creating a Spotify playlist from a radio show

Recently, I discovered a very interesting radio show hosted by Giannis Petridis on ert. Giannis is hosting one of the longest surviving radio shows in Greece and has one of the biggest record collections in the world. In his show he mostly suggests new artists and albums that are not promoted by mainstream media so it’s a great way for discovering new music. After listening a few of his shows I discovered some hidden gems so I started to think of a way to extract all these data without having to listen to all of his shows.

Data collection

Fortunately a lot of the shows are available on demand so first, I had to download everything locally. Ert’s website hosts the shows starting from 2017 up until today. After fiddling around the website I found that you can easily access the .mp3 files by a specific URL. The URLs follow a rather simple naming scheme which is {year}{month}{day} and some constant text. In order to store them locally I wrote this simple python script.


import requests

url = "{}{}{}-apo-tis-4-stis-5.mp3"

# Iterate through available dates
for year in range(2017, 2021):
    for month in range(1, 13):
        for day in range(1, 32):
            month_s = str(month)
            day_s = str(day)
            if month < 10:
                month_s = "0" + month_s
            if day < 10:
                day_s = "0" + day_s
            url_to_dl = url.format(str(year), month_s, day_s)
            response = requests.get(url_to_dl)
            total = response.headers.get('content-length')
            if total is None:
                print("File not found")
                with open("./{}{}{}.mp3".format(str(year), month_s, day_s), 'wb') as f:
                    for data in response.iter_content(chunk_size=2048):

After running the script for a while I ended up with 662 .mp3 files (~40Gb) that I had to somehow analyze and export the metadata.

Analyzing Data

The first thing that came to mind was to use a service like Shazam. Shazam is a music identification service where by submitting a short sample of audio it can identify which song that is. An interesting article about how audio identification works can be found here:

Unfortunately Shazam does not offer an API but there are other services that we can use. I had a look at AcoustID, an open source audio identification service which comes with an open library of fingerprints but the drawback is that it matches only full length songs which means that I could not use it in the context of a radio show.

In the end I ended up with AudD, a paid service which provides a very simple API in order to identify audio files. The first approach that I used in order to identify all the songs was to split each radio show into 10sec segments and scan one every 10 segments in order to reduce the requests but also make sure that I wasn’t skipping any song. For splitting the tracks I used pydub.

The python script for doing this can be seen below.


import requests
import os
from pydub import AudioSegment

data = {
    'api_token': 'XXXXXXXXXXXXXXX',
    'return': 'apple_music,spotify',

# Iterate through files
directory = os.fsencode('.')

for file in os.listdir(directory):
    filename = os.fsdecode(file)
    filename_out = os.path.splitext(filename)[0] + '.json'
    if filename.endswith('.mp3'):
        print('Splitting: ' + filename)
        radio_show = AudioSegment.from_mp3(filename)
        # Split audio in 10 sec chucks and only use 1 every 60 sec.
        with open(filename_out, 'a+') as out_f:
            for i, chunk in enumerate(radio_show[::10000]):
                if not (i % 10):
                chunk.export('temp.mp3', format='mp3')
                result ='', data=data, files={'file' : open('temp.mp3', 'rb')})

The total amount of requests needed is around 166K which based on the service’s pricing would require $840. I tried to further reduce the requests by using 1 segment every 3 min given the assumption that each track is more than 3 min long. This drops the requests to ~55K but still, I needed something better.

Speech Segmentation

Ideally I’d want to split every raw file to segments of speech and music. This falls under the field of Speech Segmentation and as with everything nowadays there is a pre-trained python module called inaSpeechSegmenter that allows us to detect speech, music and speaker gender. After setting it up, the first thing was to try and segment a single radio show and check if the results are correct.

Running the code snippet below,

from inaSpeechSegmenter import Segmenter, seg2csv
media = './media/musanmix.mp3'
seg = Segmenter()
segmentation = seg(media)

yields the first results:

[('noEnergy', 0.0, 0.6), ('music', 0.6, 64.28), ('male', 64.28, 66.7), ('noEnergy', 66.7, 67.34), ('male', 67.34, 68.72), ('noEnergy', 68.72, 69.24), ('male', 69.24, 75.34), ('noEnergy', 75.34, 76.3), ('male', 76.3, 78.38), ('noEnergy', 78.38, 78.76), ('noise', 78.76, 79.08), ('noEnergy', 79.08, 79.52), ('male', 79.52, 84.5), ('noEnergy', 84.5, 86.06), ('male', 86.06, 88.08), ('noEnergy', 88.08, 88.7), ('male', 88.7, 90.9), ('noEnergy', 90.9, 91.60000000000001), ('male', 91.60000000000001, 95.06), ('noEnergy', 95.06, 96.06), ('music', 96.06, 128.9), ('male', 128.9, 144.04), ('noEnergy', 144.04, 144.68), ('male', 144.68, 155.4), ('noEnergy', 155.4, 156.58), ('male', 156.58, 158.34), ('noEnergy', 158.34, 158.96), ('male', 158.96, 160.38), ('noEnergy', 160.38, 160.88), ('male', 160.88, 162.86), ('noEnergy', 162.86, 164.22), ('male', 164.22, 166.34), ('noEnergy', 166.34, 167.48), ('male', 167.48, 179.72), ('noEnergy', 179.72, 180.74), ('male', 180.74, 182.42000000000002), ('noEnergy', 182.42000000000002, 184.0), ('male', 184.0, 198.4), ('noEnergy', 198.4, 198.84), ('male', 198.84, 212.34), ('noEnergy', 212.34, 212.72), ('male', 212.72, 274.08), ('music', 274.08, 279.26), ('noise', 279.26, 287.88), ('music', 287.88, 289.78000000000003), ('noise', 289.78000000000003, 292.38), ('music', 292.38, 298.5), ('male', 298.5, 309.74), ('noEnergy', 309.74, 310.32), ('male', 310.32, 362.06), ('noEnergy', 362.06, 362.7), ('male', 362.7, 364.0), ('noEnergy', 364.0, 364.66), ('male', 364.66, 406.46000000000004), ('noEnergy', 406.46000000000004, 406.96000000000004), ('male', 406.96000000000004, 494.76), ('noEnergy', 494.76, 495.22), ('male', 495.22, 503.76), ('noEnergy', 503.76, 504.24), ('male', 504.24, 553.1800000000001), ('noEnergy', 553.1800000000001, 554.16), ('male', 554.16, 563.6), ('female', 563.6, 568.66), ('male', 568.66, 581.78), ('noEnergy', 581.78, 582.22), ('male', 582.22, 601.22), ('noEnergy', 601.22, 601.6800000000001), ('male', 601.6800000000001, 606.44), ('noEnergy', 606.44, 607.14), ('male', 607.14, 607.66), ('noEnergy', 607.66, 608.0600000000001), ('male', 608.0600000000001, 643.32), ('noEnergy', 643.32, 643.72), ('male', 643.72, 647.1800000000001), ('noEnergy', 647.1800000000001, 647.76), ('male', 647.76, 665.88), ('noEnergy', 665.88, 666.3000000000001), ('male', 666.3000000000001, 694.38), ('noEnergy', 694.38, 694.78), ('male', 694.78, 712.02), ('noEnergy', 712.02, 712.46), ('male', 712.46, 760.72), ('noEnergy', 760.72, 762.3000000000001), ('noise', 762.3000000000001, 772.66), ('music', 772.66, 877.4200000000001), ('noise', 877.4200000000001, 881.9200000000001), ('noEnergy', 881.9200000000001, 882.52), ('male', 882.52, 916.12), ('noEnergy', 916.12, 916.62), ('male', 916.62, 928.74), ('noEnergy', 928.74, 929.3000000000001), ('male', 929.3000000000001, 940.4200000000001), ('noEnergy', 940.4200000000001, 941.12), ('male', 941.12, 943.38), ('noEnergy', 943.38, 943.84), ('male', 943.84, 963.46), ('music', 963.46, 1185.58), ('noEnergy', 1185.58, 1187.22), ('music', 1187.22, 1202.66), ('male', 1202.66, 1215.08), ('music', 1215.08, 1221.34), ('male', 1221.34, 1254.8600000000001), ('music', 1254.8600000000001, 1258.84), ('male', 1258.84, 1296.72), ('music', 1296.72, 1432.38), ('male', 1432.38, 1467.04), ('noEnergy', 1467.04, 1467.58), ('male', 1467.58, 1473.68), ('noEnergy', 1473.68, 1474.76), ('male', 1474.76, 1480.68), ('music', 1480.68, 1490.84), ('male', 1490.84, 1493.14), ('music', 1493.14, 1762.32), ('male', 1762.32, 1781.02), ('noEnergy', 1781.02, 1781.72), ('male', 1781.72, 1817.4), ('noEnergy', 1817.4, 1818.26), ('male', 1818.26, 1826.16), ('noEnergy', 1826.16, 1827.1000000000001), ('male', 1827.1000000000001, 1828.02), ('noEnergy', 1828.02, 1828.48), ('male', 1828.48, 1831.06), ('noEnergy', 1831.06, 1831.6000000000001), ('music', 1831.6000000000001, 1835.74), ('male', 1835.74, 1838.78), ('music', 1838.78, 1841.28), ('male', 1841.28, 1847.22), ('music', 1847.22, 2053.06), ('male', 2053.06, 2058.02), ('music', 2058.02, 2059.38), ('male', 2059.38, 2090.3), ('noEnergy', 2090.3, 2090.7), ('male', 2090.7, 2098.68), ('noEnergy', 2098.68, 2099.92), ('male', 2099.92, 2105.68), ('noEnergy', 2105.68, 2106.76), ('male', 2106.76, 2110.16), ('noEnergy', 2110.16, 2111.0), ('male', 2111.0, 2115.2200000000003), ('noEnergy', 2115.2200000000003, 2116.44), ('male', 2116.44, 2127.06), ('music', 2127.06, 2130.8), ('male', 2130.8, 2132.44), ('music', 2132.44, 2309.18), ('male', 2309.18, 2346.04), ('noEnergy', 2346.04, 2346.44), ('male', 2346.44, 2352.9), ('noEnergy', 2352.9, 2354.38), ('male', 2354.38, 2383.58), ('music', 2383.58, 2474.7200000000003), ('male', 2474.7200000000003, 2513.5), ('noEnergy', 2513.5, 2514.2200000000003), ('male', 2514.2200000000003, 2538.7000000000003), ('noEnergy', 2538.7000000000003, 2541.9), ('male', 2541.9, 2546.0), ('music', 2546.0, 2547.7200000000003), ('female', 2547.7200000000003, 2548.64), ('noEnergy', 2548.64, 2550.12), ('music', 2550.12, 2618.9), ('noEnergy', 2618.9, 2619.78), ('music', 2619.78, 2666.78), ('noEnergy', 2666.78, 2667.66), ('music', 2667.66, 2762.86), ('noEnergy', 2762.86, 2764.2400000000002), ('music', 2764.2400000000002, 2778.4), ('noEnergy', 2778.4, 2779.62), ('music', 2779.62, 2804.2400000000002), ('noEnergy', 2804.2400000000002, 2806.64)]

Opening the file, I was happy to verify that the music segments matched exactly. Putting everything together I created a script that will take a 10s sample from each segment and send it for identification to AudD.


import requests
import os
import sys
from inaSpeechSegmenter import Segmenter, seg2csv
from pydub import AudioSegment

data = {
    'api_token': 'XXXXXXXXXXXXXXX',
    'return': 'spotify',

if (len(sys.argv)) < 3:
    print('Usage: ' + sys.argv[0] + ' <input.mp3> <output.json>')

filename = sys.argv[1]
filename_out = sys.argv[2]

# Segment audio
seg = Segmenter()
segmentation = seg(filename)

# Get music timestamps and keep only
# the ones that are >10s
music_timestamps = [m for m in segmentation if ((m[0] == 'music') and (m[2] - m [1] > 10))]

total_requests = 0

# Iterate timestamps and send for identification
radio_show = AudioSegment.from_mp3(filename)

with open(filename_out, 'a+') as out_f:
    for m in music_timestamps:
        start_ms = int(m[1] * 1000)
        chunk = radio_show[start_ms:start_ms + 10000]
        chunk.export('temp.mp3', format='mp3')
        result ='', data=data, files={'file' : open('temp.mp3', 'rb')})
        total_requests += 1


Spotify playlist

After I got the json response with the songs the next step was to create a Spotify playlist. The first step was to register a new app on Spotify for developers

and then I used spotipy in order to access the API and add the tracks to my playlist:


import spotipy
from spotipy.oauth2 import SpotifyOAuth

sp = spotipy.Spotify(auth_manager=SpotifyOAuth(client_id="XXXX",
                                               scope="playlist-modify-private playlist-modify",				

playlist_url = ''
username = 'mpekatsoula'

track_ids = ['TRACK_01', 'TRACK_O2']
results = sp.user_playlist_add_tracks(username, playlist_url, track_ids)

After putting everything together and by making sure that I don’t add any duplicate songs, it was time to run the script. Running it on a single 60min radio show takes around 2min on a NVIDIA GTX 1060 which is quite reasonable.

The gereated Spotify playlist

And in case you are still wondering, I managed to drop my total requests to AudD to ~10K!