Skip to content

Instantly share code, notes, and snippets.

@fuddl
Last active June 12, 2026 21:50
Show Gist options
  • Select an option

  • Save fuddl/e17aa687df6ac1c7cbee5650ccfbc889 to your computer and use it in GitHub Desktop.

Select an option

Save fuddl/e17aa687df6ac1c7cbee5650ccfbc889 to your computer and use it in GitHub Desktop.
YTMusic2listenbrainz.py

Submit Your YouTube Music Watch History to Listenbrainz

This script allows you to submit your YouTube Music watch history to Listenbrainz.

Prerequisites

  1. Python 3.x installed on your computer.
  2. An active Listenbrainz account.
  3. Your YouTube Music watch history file from Google Takeout.

Instructions

Step 1: Download Your YouTube Watch History

  • Visit Google Takeout.
  • From the list of data to include, deselect all except for YouTube and YouTube Music.
  • Within the YouTube and YouTube Music section, select only history and then watch history.
  • Click Next step and follow the prompts to download your watch history, which will be provided as a watch-history.json file.

Step 2: Prepare Your Environment

  1. Download YTMusic2listenbrainz.py script from this repository.
  2. Place your watch-history.json file in the same directory as the YTMusic2listenbrainz.py script.
  3. Enter your listenbrainz_token in YTMusic2listenbrainz.py.

Step 3: Run the Script

Open a terminal or command prompt and navigate to the directory containing the script and your watch history file. Run the script with the following command:

python3 YTMusic2listenbrainz.py

Step 4: Verify Your Submission

After running the script, log in to your Listenbrainz account to verify that your watch history has been submitted successfully.

import requests
import json
import re
from datetime import datetime
import time
# your token here
listenbrainz_token = '<enter yout token here>'
# the earliest timestamp you want to submit from.
min_timestamp = 963792000
def batch(iterable, n=1):
l = len(iterable)
for ndx in range(0, l, n):
yield iterable[ndx:min(ndx + n, l)]
with open('watch-history.json', 'r', encoding='utf-8') as file:
json_data = json.load(file)
def submit_to_listenbrainz(data, token):
headers = {
'Authorization': f'Token {token}',
'Content-Type': 'application/json'
}
listenbrainz_url = 'https://api.listenbrainz.org/1/submit-listens'
listens = []
for entry in data:
timestamp = entry['time'].replace('Z', '+00:00')
listened_at = int(datetime.fromisoformat(timestamp).timestamp())
if (listened_at < min_timestamp):
continue
if (entry["header"] == "YouTube"):
continue
if 'subtitles' not in entry:
continue
if entry['subtitles'][0]['name'] == ' - Topic':
continue
track_metadata = {
'artist_name': re.sub(r'\s-\sTopic$', '', entry['subtitles'][0]['name']),
'track_name': re.sub(r'^Watched\s', '', entry['title']),
'additional_info': {
'music_service': 'music.youtube.com',
'origin_url': entry['titleUrl'],
'submission_client': 'https://gist.github.com/fuddl/e17aa687df6ac1c7cbee5650ccfbc889',
}
}
listens.append({
'listened_at': listened_at,
'track_metadata': track_metadata
})
responses = []
for listen_batch in batch(listens, 1000):
payload = {
'listen_type': 'import',
'payload': listen_batch
}
response = requests.post(listenbrainz_url, headers=headers, data=json.dumps(payload))
time.sleep(int(response.headers['X-RateLimit-Reset-In']))
print(response)
responses.append(response)
return responses
response = submit_to_listenbrainz(json_data, listenbrainz_token)
print(response)
@Lycoris9

Copy link
Copy Markdown

Does this still work? I'm getting this error below :(
Screenshot 2024-12-10 001340

@fuddl

fuddl commented Jan 15, 2025

Copy link
Copy Markdown
Author

sorry for the delayed response. It seems as if your python is using the wrong encoding. please try it with the latest version and let me know if it worked.

@SomewhatAwake

Copy link
Copy Markdown

Heya, new issues cropped up. Here's the traceback.

File <Path To The Script>\YTMusic2listenbrainz.py", line 29, in submit_to_listenbrainz
    listened_at = int(datetime.fromisoformat(entry['time']).timestamp())
ValueError: Invalid isoformat string: '2025-04-02T03:44:12.747Z'

@fuddl

fuddl commented Apr 13, 2025

Copy link
Copy Markdown
Author

@SomewhatAwake Interesting, please try again with the updated version and let me know if it worked

@lampeight

Copy link
Copy Markdown

Hi there, I'm not sure if this is a newer change by Google but the watch-history is downloaded as a .html, not a json (unless I am missing something?) so the script fails. Are there any plans to adapt this at all?

I've tried but had no luck with this alternative tool.

@SomewhatAwake

Copy link
Copy Markdown

Hi @lampeight,

In google takeout, there's a seperate setting to export it as a JSON file. By default it is html.

@lampeight

Copy link
Copy Markdown

Ah, my mistake! Changed and the tool worked perfectly. Thanks!

@fuddl

fuddl commented Sep 26, 2025

Copy link
Copy Markdown
Author

Maybe the readme should mention that

@krisbalintona

Copy link
Copy Markdown

Yeah, I think the detail about exporting to JSON rather than the default HTML should be noted in the Google Takeout line.

@brend-an

Copy link
Copy Markdown

The JSON file often leads to incomplete or truncated listening histories. Conversely, the HTML file contains a complete history of listening data. There is another file called MyActivity.html that seems to contain the same data including YouTube watch history that could also be used as source data. It doesn't seem to have the same truncation issues.

I'm not sure what to do with this information to other than to alert developers.

@elliaster

Copy link
Copy Markdown

Hello, I was trying to upload my youtube music library as well using the json file. I put the token and everything but when I run the python3 YTMusic2listenbrainz.py command, I just get "[]" and nothing seems to have uploaded to listenbrainz. Is there a fix for this or did I do something wrong? Thank you!

image

@BlakeAlvarez

BlakeAlvarez commented May 27, 2026

Copy link
Copy Markdown

Hello, I stumbled upon this post and noticed that it worked great, albeit for the little data that the watch-history.json made. I downloaded the watch-history.html instead, which contains the entire watch history. I created a script and thought you all would like to know. I took heavy inspo from you, thanks a ton for your hard inital work @fuddl!

@brend-an @elliaster

My script can be found here: https://github.com/BlakeAlvarez/YTMusic-ListenBrainz-Importer.git as ytmusic-listenbrainz-importer.py

Please feel free to critique or comment on any changes needed to be done, I would love feedback or tips!

@twistios

Copy link
Copy Markdown

"Watched" is localized, so the removal will not work for languages other than english, I guess it's based on the account language.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment