Skip to main content

Copyright Detection

Overview

The Copyright Detection module helps streamers identify whether songs are safe or blocked for streaming. It maintains per-account and global safe/blocked song lists, supports bulk importing tracks from Spotify playlists, provides a real-time song status checker, and includes a community voting system where users vote on a song's copyright status. Admins can review vote candidates and approve them into the global blocked list. This feature is available on Pro plan and above.

Architecture

Backend

  • GraphQL (apps/api/src/graphql/copyright.rs) -- Queries for safe songs, blocked songs, playlist syncs, song status checks, and vote candidates. Mutations for adding/deleting safe/blocked songs, importing playlists, managing playlist syncs, casting votes, and admin approval/dismissal of vote candidates.
  • Database (apps/api/src/db/copyright.rs) -- PostgreSQL operations for safe/blocked song lists, playlist sync records, song status checks (checks by spotify_track_id, ISRC, or title/artist fuzzy match), vote casting, and vote candidate aggregation.

Song Identification

Songs are identified by multiple keys (checked in priority order):

  1. Spotify Track ID -- Exact match on spotify_track_id
  2. ISRC -- International Standard Recording Code match
  3. Title + Artist -- Fuzzy text match on song_name and artist

Frontend

  • Copyright detection dashboard with safe/blocked song lists.
  • Song status checker with real-time lookup.
  • Spotify playlist import wizard.
  • Community voting interface.
  • Admin review panel for vote candidates.

API

GraphQL Queries

QueryPermissionDescription
safeSongscopyright:readList all safe songs for the account (includes global entries)
blockedSongscopyright:readList all blocked songs for the account (includes global entries)
playlistSyncscopyright:readList playlist sync records for the account
checkSong(spotifyTrackId?, isrc?, title?, artist?)copyright:readCheck copyright status of a song. Returns SongStatusResult (SAFE/BLOCKED/REPORTED/UNKNOWN).
voteCandidatescopyright:readList songs with enough community votes/reports for admin review.
adminReports(status?, reportType?, limit?, offset?)copyright:editAdmin: paginated list of reports with optional status/type filters
curatedPlaylistTracks(playlistId)copyright:editAdmin: tracks for a curated playlist with song info
publicSafeSongs(search?, limit?, offset?)Feature gate onlyPublic: paginated global safe songs
publicBlockedSongs(search?, limit?, offset?)Feature gate onlyPublic: paginated global blocked songs
publicReports(limit?, offset?)Feature gate onlyPublic: pending copyright reports
publicSystemPlaylistsFeature gate onlyPublic: system-curated playlists
publicSongStatus(spotifyTrackId?, isrc?, title?, artist?)Feature gate onlyPublic: global copyright status for a song

GraphQL Mutations

MutationPermissionDescription
addSafeSong(input: AddSafeSongInput)copyright:editAdd a song to the account's safe list
deleteSafeSong(id: UUID)copyright:deleteDelete a safe song (own account's only)
addBlockedSong(input: AddBlockedSongInput)copyright:editAdd a song to the account's blocked list
deleteBlockedSong(id: UUID)copyright:deleteDelete a blocked song (own account's only)
importPlaylist(spotifyPlaylistId)copyright:editImport tracks from a Spotify playlist as safe songs. Creates a playlist sync record.
deletePlaylistSync(id: UUID)copyright:deleteDelete a playlist sync and its associated safe songs
createCopyrightReport(input)copyright:reportReport a song as copyright-blocked
createSafeRecommendation(input)copyright:recommendRecommend a song as safe
confirmCopyrightReport(id)copyright:voteCast a confirmation vote on an existing copyright report
approveReport(id, addToPlaylistId?)copyright:editApprove a report: copyright reports add to the global blocked list, recommendation reports add to the global safe list. Optionally add to a curated playlist.
dismissReport(id)copyright:editDismiss a report without action
previewPlaylist(spotifyPlaylistId)copyright:editPreview a Spotify playlist's tracks with copyright status (10-minute Redis cache)
importSelectedSongs(input: ImportSelectedSongsInput!)copyright:editImport selected tracks from a previously previewed playlist. Returns ImportResult.
createCuratedPlaylist(genre, spotifyPlaylistName)copyright:editAdmin: create a system-curated playlist
updateCuratedPlaylist(id, genre?, spotifyPlaylistName?)copyright:editAdmin: update a curated playlist
deleteCuratedPlaylist(id)copyright:editAdmin: delete a curated playlist
addToCuratedPlaylist(playlistId, safeSongId)copyright:editAdmin: add a safe song to a curated playlist
removeFromCuratedPlaylist(playlistId, safeSongId)copyright:editAdmin: remove a safe song from a curated playlist
syncCuratedPlaylist(id)copyright:editAdmin: sync a curated playlist with Spotify (returns SyncResult)

REST Endpoints

All paths live under /v1/copyright. Bodies are snake_case.

MethodPathPermissionDescription
GET/v1/copyright/checkcopyright:readCheck a song's status by spotify_track_id, isrc, or title+artist
GET/v1/copyright/safe-songscopyright:readList safe songs (account + globals)
POST/v1/copyright/safe-songscopyright:editAdd a safe song
DELETE/v1/copyright/safe-songs/{id}copyright:deleteDelete an account safe song
GET/v1/copyright/blocked-songscopyright:readList blocked songs (account + globals)
POST/v1/copyright/blocked-songscopyright:editAdd a blocked song
DELETE/v1/copyright/blocked-songs/{id}copyright:deleteDelete an account blocked song
POST/v1/copyright/import-playlistcopyright:editImport a Spotify playlist as safe songs
GET/v1/copyright/playlist-syncscopyright:readList playlist sync records
DELETE/v1/copyright/playlist-syncs/{id}copyright:deleteDelete a playlist sync (removes imported safe songs)
POST/v1/copyright/votecopyright:voteCast a vote on a song (confirm/recommend)
GET/v1/copyright/vote-candidatescopyright:readList candidates ready for admin review
POST/v1/copyright/vote-candidates/{track_id}/approvecopyright:editApprove a candidate into the global blocked list
POST/v1/copyright/vote-candidates/{track_id}/dismisscopyright:editDismiss a candidate without action

Input Types

AddSafeSongInput:

FieldTypeDescription
songNameStringSong title
artistStringArtist name
spotifyTrackIdString?Spotify track ID
isrcString?International Standard Recording Code
sourceStringSource of the safe listing (e.g., "manual", "playlist_import")
sourceRefString?Reference to the source (e.g., playlist ID)

AddBlockedSongInput:

FieldTypeDescription
songNameStringSong title
artistStringArtist name
spotifyTrackIdString?Spotify track ID
isrcString?ISRC code
sourceStringSource of the blocked listing

Permissions

PermissionDescription
copyright:readView safe/blocked song lists and check song status
copyright:editAdd safe/blocked songs, import playlists
copyright:deleteDelete safe/blocked songs, delete playlist syncs
copyright:reportSubmit copyright reports
copyright:recommendSubmit safe-song recommendations
copyright:voteConfirm existing copyright reports

Database

TableDatabaseDescription
copyright_safe_songsPostgreSQLid, account_id (null for global), song_name, artist, spotify_track_id, isrc, source, source_ref, verified, created_at
copyright_blocked_songsPostgreSQLid, account_id (null for global), song_name, artist, spotify_track_id, isrc, source, auto_learned, created_at
copyright_playlist_syncsPostgreSQLid, account_id, spotify_playlist_id, spotify_playlist_name, auto_sync, last_synced_at, song_count, created_at
copyright_votesPostgreSQLid, user_id, spotify_track_id, song_name, artist, vote_type ("copyright" or "safe"), created_at

Song Status Check Logic

The check_song_status function checks in order:

  1. Is the song in the blocked list (account-specific or global)?
  2. Is the song in the safe list (account-specific or global)?
  3. If neither, return Unknown.

Matching uses spotify_track_id first, then ISRC, then title+artist fuzzy match.

Data Flow

Manual Song Management

  1. User adds a song to their safe or blocked list via the dashboard.
  2. When checking a song during a stream, checkSong is called.
  3. The system checks blocked list first, then safe list, and returns the status.

Playlist Import

  1. User provides a Spotify playlist ID.
  2. System fetches playlist tracks from the Spotify API.
  3. Tracks are bulk-inserted into copyright_safe_songs with source = "playlist_import".
  4. A copyright_playlist_syncs record is created for tracking.

Community Voting

  1. Users submit reports (createCopyrightReport) or safe recommendations (createSafeRecommendation) for a song, and can confirm existing reports (confirmCopyrightReport).
  2. Votes are aggregated. When enough accumulate, the song appears in voteCandidates.
  3. Admins review candidates and either approveReport (adds to global blocked list) or dismissReport.

Plan Restriction

Copyright detection is only available on the Pro plan and above (copyright_detection_allowed in PlanLimits).

Key Files

PathDescription
apps/api/src/graphql/copyright.rsGraphQL queries and mutations
apps/api/src/db/copyright.rsDatabase operations for safe/blocked songs, playlists, votes