GSoC 2026: Development of A New Calibre Plugin

Contact information

Name: Md Waqib Sk
Matrix/nick: waqib2992
Mail: waqibsk.2006@gmail.com

Github: Waqibsk (Md Waqib Sk) · GitHub
LinkedIn: https://www.linkedin.com/in/md-waqib-sk-0619232a2/
Project Size : 175 Hours (medium)

Proposed project

My goal is to make a Calibre plugin for BookBrainz . The UI will contain 3 different tabs, each serving different purpose.

  • Update Metadata
  • Browse BookBrainz
  • Add Collection

File Structure :

calibbre/
├── images/
│	 └── logo.png
│	 	 
├── config.py
├── __init__.py
├── main.py
├── ui.py
└── plugin-import-name-calibbre.txt

UI Development

I will be using PyQt5 and Qt Designer to develop the UI . It will generate a *.ui file then i will convert it to a .*.py file using pyuic5 for further development .

Features :

( Note: I have tried to give sample functions where needed and mostly i have written the pseudo code . If any of them require further clarification , please let me know )

MetaData Update of a selected calibre book:

  • Get the book that the user selected (if no book is selected this option will be disabled)

Api Endpoints :

  • db.get_metadata()
  • gui.library_view
 
 selected_ids = self.gui.library_view.get_selected_ids()
 
  if not selected_ids:
     print("No books selected.")
  
  # disable the buttons (add a message suggesting the user to select a book first)
     return
  else:
  
  # Enable buttons 

 db = self.gui.current_db.new_api
 
 first_book_id = selected_ids[0] # we will only fetch metadata of one book
     
 self.mi = db.get_metadata(first_book_id)

  def search_book(self):
       """
       1. Search for the book by name and get 0-20 results.
       2. Populate the table.
       
       """     
       book_name = self.mi.title
       search_url = f"https://api.bookbrainz.org/1/search?q={book_name}&type=edition&size=10&from=0"

       response = requests.get(search_url)
       search_response_data = response.json()
       results = search_response_data.get("searchResult", [])
  
     # get required fields from search_response_data (bookBBID, bookTitle etc)
    
    for index,item in enumerate(results):

        bookBBID = item.get("bbid")
        bookTitle = item["defaultAlias"].get("name",book_name)
        row_position = self.tableWidget.rowCount()
    
        self.tableWidget.insertRow(row_position)
       
        self.tableWidget.setItem(row_position, 0, QTableWidgetItem(bookBBID))
        self.tableWidget.setItem(row_position, 1, QTableWidgetItem(bookTitle))
       
       # same for the others             

A list of 20 search (we can make it customizable by having a settings feature but i think its unnecessary ) result will appear In a tabular format with

  1. Book BBID

  2. Book name

  3. Sorted book name

  4. Book language

  5. Book author (possible after closing the ticket)

There will be different states also like

  • Initial state
  • Loading state
  • All results fetched

I will handle these states using stacked QtWidget and change the ui accordingly

Demo Screenshots :



  • Get selected book’s metadata :

    Api Endpoint : https://api.bookbrainz.org/1/edition/{selected_bbid}"

    Response Structure :

    Sample Function :

    def fetch_metadata():
        
    	# get the current selected bbid
    	selected_row = self.tableWidget.currentRow()
            if selected_row == -1:
                selected_row=0
        selected_bbid = self.tableWidget.item(selected_row,0).text()
        book_name =    self.tableWidget.item(selected_row,1).text()   
    
    	# get the data from api and show in ui 
    try:
              detail_url = f"https://api.bookbrainz.org/1/edition/{selected_bbid}"
              response = requests.get(detail_url)
              response.raise_for_status()
              edition_data = response.json()
          
    except Exception as e:
              QMessageBox.critical(self, "API Error", f"Failed to fetch details: {e}")
              return
    
          #  convert to Calibre metadata object and cache it 
          self.cached_metadata = self.create_metadata_object(edition_data, book_name)
    
    • Update metadata :
    
    from calibre.ebooks.metadata.book.base import Metadata
      
     def create_metadata_object(self, bb_data,book_name):
          
          # getting the details
    
          title = book_name 
          
          authors = []
          
          if 'authorCredits' in bb_data:
              for credit in bb_data['authorCredits']:
                  if 'name' in credit and isinstance(credit['name'], dict):
                       authors.append(credit['name'].get('name', 'Unknown'))
                  elif 'name' in credit:
                       authors.append(credit['name'])
          
          if not authors:
              authors = ["Unknown Author"]
    
          # metadata object
    
          mi = Metadata(title,authors)
          
         # set publisher and date and other details also to mi
    
          return mi
    
    
    def update_metadata():
        
        db = self.gui.current_db.new_api
        
        db.set_metadata(book_id, self.cached_metadata, set_title=True, set_authors=True)
       
        QMessageBox.information(self, "Success", "Book updated successfully!")
        
        # clear cache after update
        self.cached_metadata = None
  	

Browse BookBrainz Tab :

This will contain a search bar where users will search editions by name .
( Note : Then again 0-20 search result will appear with book Name and author name as the previous tab i initially thought of merging this to a single tab because it is a bit repitative but In my opinion keeping the update metadata separate will be clean as the 2 tabs serve different purposes even though they share the same table )

This will contain two features :

  • Add book to calibre
    We will be adding the books in calibre as empty books (will buy/read later) because we will only provide metadata not the actual readable book
 def get_selected_books_data():
 
 results=[] # list of tuple ( format <mi,format_map> )
 
  # fetch the selected books metadata by bbid from BB api 
  # create metadata object 
  # append result as <mi,{}> (as we are adding empty book the file path should be empty)
 self.books_data_to_add=results

def add_book_to_calibre():
 
 if not self.books_data_to_add:
        return

# use the db.add_books() endpoint with books_data_to_add

ids = self.gui.current_db.add_books(self.books_data_to_add, add_duplicates=True)
print(f"Successfully added {len(ids)} empty books.")

  • Download Book MetaData as Json file (single or multiple) :
	def download_book_metadata(self):

	 save_path = choose_save_file(self.gui, 'save-json', 'books.json', filters= [('JSON Files', ['json'])]) # choose where to save

     export_data = {} # array of selected books metadata

     if save_path:
         with open(save_path, 'w', encoding='utf-8') as json_file:
             json.dump(export_data, json_file, indent=4, ensure_ascii=False)

             print(f"Successfully exported {len(export_data)} books to                        {save_path}")

Add Collection To Calibre :

  • Search public Collection by BBID

  • Show Collection items in the ui

  • User may choose some book or entire collection to add in calibre

    ( I need some opinion regarding this )

  • Initially i thought of merging this feature with the browse tab because in both cases we are adding the books to calibre

  • But In future we may introduce Auth and private collections so i decided to make a separate tab to keep things clean

  • I think we should restrict the collection size to 50-100 because it does not make sense to show 500 books from the collection and show it in the UI

Note: The API required to implement this feature is not currently present but i think it can implemented in the timeline before i start working on this (thats why i saved this feature for last ) , I am sharing the details of what the API endpoint should look like .
( I want to know if the team will handle this otherwise i will allocate one more week dedicating to this )

Api Endpoint : /collection/{bbid} (with a type property )

Usage:

  • Returns the collection , along with a items array
  • if in the array item itself we get the metadata details for each item , it would be great . Because then while adding the editions in calibre i will not have to make separate API calls for getting metadata info for each selected item

Example on how it will be used in the function :

  • Fetch Collection By BBID :

    def fetch_collection():
    
    	search_result = requests.url(api)
    	# check if results array size is greater than 50 
    	if( len(search_result.items) > 50 ):
    		# show error message 
    	
    	# show the items in tabular format 	
    
    
  • Add Collection function :

    def add_collection():
    	# it will be almost similar to add_book_to_calibre function in Browse tab 
    	
    

Timeline

Phase 1: (Before Mid-Evals)

Expected outcome :

  • A working Update Metadata tab
  • Start working on Browse tab

Week 1 (May 25 - May 31):

  • Create & finalize Qt ui design for Metadata tab
  • Create & finalize Qt ui design for Browse tab

Week 2 (June 1 - June 7):

  • Write & test function for searching selected book in Bookbrainz
  • Show the books in the ui and handle different states (loading etc)

Week 3 (June 8 - June 14):

  • Implement & test Fetch Metadata from BB function and display it in the UI
  • Handle different states in the ui while fetching

Week 4-5 (June 15 - June 28):

  • Make and test the Update book Metadata function
  • (Work on update metadata tab will be finished )
  • Test entire update metadata feature and fix any existing bugs
  • Write documentation on how to use it

Week 6 (June 29 - July 5):

  • ( Work on Browse BB tab starts )
  • Implement & test search Edition ( by name ) in calibre function

Phase 2: (After Mid-Evals)

Expected outcome :

  • A Working installable calibre plugin with 3 different tabs/features

Week 7-8 (July 6 - July 19):

  • Write and test Download Metadata function
  • Write and test Add Book to Calibre function
  • Test entire Browse tab
  • Write documentation on how to use it
  • (Work on Browse tab will be finished )

Week 9 (July 20 - July 26):

  • (Start working on Collection tab )
  • Create & finalize Qt ui design for Collection tab
  • Implement & test function to fetch public collection( using bbid) from BB
  • Show the results in tabular format & handle different states

Week 10 (July 27 - Aug 2):

  • Write and test add collection to calibre function (it will be almost simillar to add book to calibre in browse tab)
  • Write docs for Collection tab and test the entire tab
  • (Work on Add Collections tab will be finished )

Week 11 (Aug 3 - Aug 9):

  • This is buffer time if any of the fixed deadlines are not met i will cover it here
  • Test the entire plugin

Week 12 (Aug 10 - Aug 16):

  • Wrap up and make video describing all the features of the plugin
  • Final Report

In all the functions i will implement proper error handling with QtMessageBox (eg: Network error , No books found etc )

Extended/Future Goals

( All the features mentioned below will require Authentication )

  1. Add Books To BookBrainz : Add calibre books directly to BookBrainz

    • I think This would be a good feature to grow the books in Book Brainz as users will just have to press one click to add the book ( the calibre books will be stored as editions in book brainz)
  2. Add Private Collection :

    • It will be an Extension of the Collection tab
  3. CritiqueBrainz integration ( future feature ) : Write reviews of a selected book directly from the plugin

    • In this case first we have to check if the selected book exists in BB or not , then get the corresponding BBID after that we can make the request

Community affinities

What type of music do you listen to?

I listen to mostly soft music , listing some of my favourite songs :

  1. Lego House : Release group “Lego House” by Ed Sheeran - MusicBrainz
  2. Perfect : Release group “Perfect” by Ed Sheeran - MusicBrainz
  3. Photograph : Release group “Photograph” by Ed Sheeran - MusicBrainz
  4. Circles : Release group “Circles” by Post Malone - MusicBrainz
  5. Gul : Release group “Gul” by Anuv Jain - MusicBrainz
  6. Alag Aasmaan : Release group “Alag Aasmaan” by Anuv Jain - MusicBrainz

What type of books do you read?

Some of my favourite books are :

  1. The Alchemist : The Alchemist (Work) – BookBrainz
  2. The Da Vinci Code : The Da Vinci Code (Edition) – BookBrainz
  3. Harry Potter : Harry Potter (Series) – BookBrainz

What aspects of MusicBrainz/ListenBrainz/BookBrainz/Picard interest you the most?

I always liked the classic vibe of Wikipedia and thats exactly what i get when i look at Metabrainz projects . I also like how detailed the databases are ( i mean i never thought a single book can have so much info before looking at bookbrainz).

Programming precedents

When did you first start programming?

I started programming in the first year of my college . And the initial motivation for programming was i can make games through it . Although my interest has changed over the time but that was how i started .

Have you contributed to other open source projects? If so, which projects and can we see some of your code?

Yes i have contributed to OpenStreetMap
You can check out the PRs here:

  1. Validation: non standard dashes in dash-sensitive tag values by Waqibsk · Pull Request #11316 · openstreetmap/iD · GitHub
  2. Feat: Click to switch features that are crossing ways by Waqibsk · Pull Request #11346 · openstreetmap/iD · GitHub
  3. fix: allow 0 as input value in layer tag by Waqibsk · Pull Request #11300 · openstreetmap/iD · GitHub

And some no-code (docs) contribution include :

  1. Next-cloudinary : add: guide for delivering remote images by Waqibsk · Pull Request #600 · cloudinary-community/next-cloudinary · GitHub
  2. LocalStack : [ Docs]: Normalize structure: Build & deploy Lambda container images (ECR) by Waqibsk · Pull Request #260 · localstack/localstack-docs · GitHub

You can also checkout some of my personal projects :

  1. GitHub - Waqibsk/Darklash: 2d fighting game
  2. GitHub - Waqibsk/delta: A Go script to delete your emails based on their subjects :)

Practical requirements

What computer(s) do you have available for working on your SoC project?
I have Asus Vivobook 14
Specifications:

  1. Processor: AMD Ryzen™ 5 7520U with Radeon™ Graphics × 8
  2. Os: Ubuntu 24.04.1 LTS
  3. Ram: 8.0 GB
  4. Disk Space: 512 GB

How much time do you have available per week, and how would you plan to use it?

I have no other commitments during the summer and can dedicate 20-25 hours a week.
I am also planning to close this ticket before the coding period .

References:

  1. GitHub - kovidgoyal/calibre: The official source code repository for the calibre ebook manager
  2. https://manual.calibre-ebook.com/creating_plugins.html
  3. API documentation for the database interface — calibre 8.16.2 documentation
  4. https://manual.calibre-ebook.com/plugins.html#plugins
  5. Swagger UI
2 Likes

Oh, this is something I would love. I have been trying to get more reading going, but it gets so weird with some eBooks and a lack of standardization for me to reference, with some things not going ISBN, so anything to support BBID and adding more to it I would love. (Plus incentivizes me to get my Calibre online setup working nice out of this)