A modern static site generator for musicians and music labels, written in JavaScript/TypeScript.
Inspired by Faircamp, this tool helps you create beautiful, fast static websites to showcase your music without the need for databases or complex hosting.
- 🎵 Audio-first: Automatically reads metadata from your audio files
- 📦 Zero database: Pure static HTML generation
- 🎨 Customizable: Template-based theming system
- 🚀 Fast: Static sites that load instantly
- 📱 Responsive: Mobile-friendly out of the box
- 🔊 Built-in player: Modern HTML5 audio player
- 💿 Multi-format: Support for MP3, FLAC, OGG, WAV, and more
- 🏷️ Flexible metadata: YAML-based configuration files
- 📡 RSS/Atom feeds: Automatic feed generation for releases
- 🎙️ Podcast support: Generate podcast RSS feeds
- 📦 Embed widgets: Embeddable HTML widgets for releases
- 🎶 M3U playlists: Automatic playlist generation with frontend download links
- 🎨 Procedural covers: Auto-generate cover art if missing
- 🔐 Unlock codes: Decentralized download protection via GunDB
- 🏢 Label mode: Multi-artist catalog support
- 🖼️ Custom backgrounds: Header and page background image support
- 🔗 Remote audio files: Use external URLs for audio files instead of local files
- 🌐 Community Directory: Auto-register your site to a public directory of Tunecamp sites
- 🎧 Community Player: Centralized player to discover and listen to music from all Tunecamp sites
- 🖥️ Server Mode: Personal streaming server with REST API and web interface
- 🎤 Lyrics Support: Display and manage lyrics for tracks
- 💬 Comments System: Decentralized comments on tracks via GunDB
- 📊 Library Statistics: Track listening habits, play history, top tracks/artists
- 🌊 Waveform Generation: Visual waveform data for audio files
- 📁 File Browser: Admin file system browser for library management
- 🔍 Search: Full-text search across tracks, albums, and artists
- 👥 User Accounts: Decentralized user authentication for comments and profiles
- 🎯 Type Safety: Gleam integration for critical utility functions
- Node.js 18+
- Gleam (for development/building from source)
npm install -g tunecamp
# or
yarn global add tunecampFor development or building from source:
# Install dependencies
npm install
# Build the project (includes Gleam compilation)
npm run gleam:build
npm run build- Create your catalog structure:
my-music/
├── catalog.yaml
├── artist.yaml
└── releases/
└── my-first-album/
├── release.yaml
├── cover.jpg
└── tracks/
├── 01-track-one.mp3
├── 02-track-two.mp3
└── track.yaml (optional)
- Configure your catalog:
# catalog.yaml
title: "My Music Catalog"
description: "Independent music releases"
url: "https://mymusic.com"# artist.yaml
name: "Artist Name"
bio: "Artist biography goes here"
links:
- bandcamp: "https://artistname.bandcamp.com"
- spotify: "https://open.spotify.com/artist/..."
donationLinks:
- platform: "PayPal"
url: "https://paypal.me/artistname"
description: "Support the artist"
- platform: "Ko-fi"
url: "https://ko-fi.com/artistname"
description: "Buy me a coffee"# releases/my-first-album/release.yaml
title: "My First Album"
date: "2024-01-15"
description: "An amazing debut album"
download: free # Options: free, paycurtain, codes, none
price: 10.00
paypalLink: "https://paypal.me/artistname/10"
stripeLink: "https://buy.stripe.com/..."
bandcampLink: "https://artistname.bandcamp.com/album/..."
streamingLinks: # Optional links to listen on streaming platforms
- platform: "Spotify"
url: "https://open.spotify.com/track/..."
- platform: "Apple Music"
url: "https://music.apple.com/album/..."
license: "cc-by" # Options: copyright, cc-by, cc-by-sa, cc-by-nc, cc-by-nc-sa, cc-by-nc-nd, cc-by-nd, public-domain
unlisted: false # Set to true to hide from index but keep accessible via direct link- Generate your site:
tunecamp build ./my-music --output ./public- Deploy:
Upload the public folder to any static hosting service (Netlify, Vercel, GitHub Pages, etc.)
The basePath configuration is essential for correct asset loading when your site is deployed.
For deployments at the root of a domain (e.g., mymusic.com):
# catalog.yaml
basePath: "" # or omit the fieldFor deployments in a subdirectory (e.g., GitHub Pages at username.github.io/my-music):
# catalog.yaml
basePath: "/my-music"GitHub Pages (Project Site)
basePath: "/repository-name"Netlify/Vercel (Custom Domain)
basePath: ""Netlify/Vercel (Subdirectory)
basePath: "/subfolder"You can also override the basePath at build time:
tunecamp build ./my-music --output ./public --basePath /my-musicGlobal catalog configuration:
title: "Catalog Title" # Required
description: "Catalog description" # Optional
url: "https://yoursite.com" # Optional
basePath: "" # Optional: Base path for deployment (empty for root, "/repo-name" for subdirectory)
theme: "default" # Optional: Theme name (default: "default")
language: "en" # Optional: Language code (default: "en")
headerImage: "header.png" # Optional: Image to replace title in header (Bandcamp-style)
backgroundImage: "background.png" # Optional: Background image for entire page (local file or URL)
customFont: "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" # Optional: Custom font URL (Google Fonts, etc.) or local file path
customCSS: "custom.css" # Optional: Custom CSS file path (relative to input directory) or external URL
labelMode: false # Optional: Set to true for multi-artist label catalogs (default: false)
podcast: # Optional: Podcast feed configuration
enabled: true
title: "My Podcast"
description: "Podcast description"
author: "Artist Name"
email: "email@example.com"
category: "Music"
image: "podcast-cover.jpg"
explicit: falseImportant: The basePath option is crucial when deploying to subdirectories (e.g., GitHub Pages). If your site will be at username.github.io/my-music/, set basePath: "/my-music".
Artist information:
name: "Artist Name" # Required
bio: "Biography text" # Optional
photo: "artist.jpg" # Optional: Artist photo image path
links: # Optional: Social/website links
- website: "https://..."
- bandcamp: "https://..."
- spotify: "https://..."
- instagram: "https://..."
donationLinks: # Optional: Support/donation links
- platform: "PayPal"
url: "https://paypal.me/artistname"
description: "Support the artist" # Optional
- platform: "Ko-fi"
url: "https://ko-fi.com/artistname"
description: "Buy me a coffee" # Optional
slug: "artist-name" # Optional: URL slug (for label mode only)Individual release configuration:
title: "Album Title" # Required
date: "2024-01-15" # Required: Release date (YYYY-MM-DD format)
description: "Album description" # Optional
cover: "cover.jpg" # Optional: Cover art image (auto-detected if missing, procedural cover generated if not found)
download: "free" # Optional: Download mode - "free", "paycurtain", "codes", or "none" (default: "free")
price: 10.00 # Optional: Suggested price for paycurtain mode
paypalLink: "https://paypal.me/artistname/10" # Optional: PayPal donation/payment link (for paycurtain mode)
stripeLink: "https://buy.stripe.com/..." # Optional: Stripe payment link (for paycurtain mode)
bandcampLink: "https://artistname.bandcamp.com/album/..." # Optional: Bandcamp purchase link (for paycurtain mode)
streamingLinks: # Optional: Links to listen on streaming platforms
- platform: "Spotify" # Platform name (displayed as-is)
url: "https://open.spotify.com/track/..." # Platform URL
- platform: "Apple Music"
url: "https://music.apple.com/album/..."
- platform: "YouTube Music"
url: "https://music.youtube.com/watch?v=..."
license: "cc-by" # Optional: License type - "copyright", "cc-by", "cc-by-sa", "cc-by-nc", "cc-by-nc-sa", "cc-by-nc-nd", "cc-by-nd", or "public-domain"
genres: # Optional: List of genres
- "Electronic"
- "Ambient"
credits: # Optional: List of credits
- role: "Producer" # Credit role (e.g., "Producer", "Vocalist", "Engineer")
name: "Producer Name" # Person's name
unlisted: false # Optional: Set to true to hide from index/feeds but keep accessible via direct link (default: false)
artistSlug: "artist-name" # Optional: For label mode - associate release with an artist by slug
unlockCodes: # Optional: Configuration for unlock codes (required if download: "codes")
enabled: true # Required: Enable unlock codes
namespace: tunecamp # Optional: GunDB namespace (default: "tunecamp")
publicKey: "abc123..." # Optional but recommended: Public key from gundb-keypair.json (required for private space)
peers: # Optional: Custom GunDB peers
- "https://your-relay.com/gun"Optional track-level metadata overrides:
tracks: # Optional: Override track metadata
- file: "01-track.mp3" # Required: Track filename
title: "Custom Title" # Optional: Custom track title (overrides metadata from audio file)
description: "Track notes" # Optional: Track description or notes# Build a catalog
tunecamp build <input-dir> --output <output-dir>
# Build with custom base path (overrides catalog.yaml)
tunecamp build <input-dir> --output <output-dir> --basePath /my-music
# Serve locally
tunecamp serve <output-dir> --port 3000
# Initialize a new catalog
tunecamp init <directory>download: freeAll tracks available for immediate download.
download: paycurtain
price: 10.00
paypalLink: "https://paypal.me/artistname/10"
stripeLink: "https://buy.stripe.com/..."Pay-what-you-want with suggested price. Users can download for free, but are encouraged to support the artist.
codes mode instead.
download: codes
unlockCodes:
enabled: true
namespace: tunecamp # Optional, default: tunecampProtect downloads with unlock codes validated via GunDB (decentralized, no backend required). See Unlock Codes Guide for details.
generate-codes.ts) must be run locally on your machine where you have access to the Tunecamp source code. If you deploy only the static HTML output (e.g., to Vercel, Netlify, GitHub Pages), you won't be able to generate new codes from the deployed site - it's just static HTML.
Workflow:
- Run
tunecamp buildlocally - Generate codes locally:
npx ts-node src/tools/generate-codes.ts <release-slug> --count 20 - Deploy the static
public/folder to your hosting - Distribute the generated codes to your customers
Generate codes using:
npx ts-node src/tools/generate-codes.ts <release-slug> --count 20Tunecamp automatically tracks and displays download counts for your releases using a public GunDB space. This works out of the box with no configuration required:
- Real-time counter: Download counts update in real-time across all visitors
- Decentralized: No server required - data is stored on public GunDB peers
- Anonymous: No user tracking, just simple counters
- Visible to all: Download counts are shown on each release page
The download counter increments when users click "Download All" or individual track download buttons.
Tunecamp includes an automatic community registry powered by GunDB. When someone visits your Tunecamp site, it gets automatically registered in a decentralized directory of Tunecamp sites.
Features:
- Automatic registration: No sign-up needed - your site is discovered when visited
- Decentralized: Data stored on public GunDB peers, no central server
- Real-time: New sites appear instantly in the community directory
- Privacy-respecting: Only public info is shared (URL, title, artist name)
- Network Discovery: In Server Mode, browse and play tracks from other TuneCamp instances
- Cross-Site Playback: Stream tracks from other TuneCamp sites directly in your player
How it works:
- Build and deploy your Tunecamp site
- When a visitor loads your site, it registers automatically
- Your site appears in the Tunecamp Community directory
- Discover other independent artists using Tunecamp!
Server Mode Network Features:
- Browse all registered TuneCamp sites in the community
- Discover and play tracks shared by other instances
- Add network tracks to your queue
- View site information and covers from other instances
You can disable auto-registration by removing the community-registry.js script from your build output.
- MP3
- FLAC
- OGG Vorbis
- WAV
- M4A/AAC
- OPUS
Tunecamp includes a single, highly customizable theme:
The default theme is a modern, Faircamp-inspired design with:
- Dark/light mode toggle
- Responsive two-column layout
- Integrated header with background image support
- Modern navigation bar
- Prominently positioned audio player (after release metadata, before track list)
- Customizable colors via CSS variables
You can customize the default theme using:
- Background images: Set
backgroundImageincatalog.yamlfor a custom page background - Header images: Set
headerImageincatalog.yamlfor a custom header image - Custom CSS: Add
customCSSto override styles or change colors - Custom fonts: Add
customFontfor typography customization
The theme uses CSS variables for easy customization:
--bg-color: Main background color--text-color: Primary text color--accent: Accent color (buttons, links)--accent-hover: Hover state for accent- And more (see
templates/default/assets/style.css)
Create your own theme by adding a folder in the templates/ directory:
templates/my-theme/
├── layout.hbs
├── index.hbs
├── release.hbs
└── assets/
├── style.css
└── player.js
For detailed information about themes, see Theme Showcase.
When you build a catalog, Tunecamp automatically generates:
- HTML pages:
index.htmland release pages - RSS/Atom feeds:
feed.xml(RSS 2.0) andatom.xml - Podcast feed:
podcast.xml(if enabled in config) - M3U playlists:
playlist.m3ufor each release andcatalog.m3ufor the entire catalog - Embed widgets:
embed.html,embed-code.txt, andembed-compact.txtfor each release - Procedural covers: Auto-generated SVG covers if release has no cover art
Each release gets embeddable HTML widgets that you can use on other websites:
- Full embed: Complete widget with cover, info, and audio player
- Compact embed: Smaller inline widget
- Iframe embed: Standalone embed page for iframe embedding
Access embed codes at: releases/<release-slug>/embed-code.txt
Share & Embed Section: Each release page includes a "Share & Embed" section at the top with:
- Catalog link: Quick navigation back to the main catalog
- RSS/Atom feeds: Direct links to
feed.xml(RSS 2.0) andatom.xmlfor feed readers - Copy link: One-click button to copy the release URL to clipboard
- Embed code: Modal viewer with tabs for full and compact embed codes, with copy functionality
- All embed codes are accessible directly from the page without needing to navigate to text files
Automatic feed generation for:
- RSS 2.0 feed at
feed.xml - Atom feed at
atom.xml - Podcast RSS feed at
podcast.xml(if enabled)
All feeds include proper metadata, cover images, and track information.
Playlists are generated for:
- Each individual release:
releases/<release-slug>/playlist.m3u - Entire catalog:
catalog.m3u
Playlists include track metadata (duration, artist, title) and can be opened in any music player.
Check the /examples directory for complete catalog examples:
- artist-free: Simple artist catalog with free downloads
- artist-paycurtain: Artist with pay-what-you-want model
- label: Multi-artist label catalog
Contributions are welcome! Please feel free to submit a Pull Request.
MIT License - see LICENSE file for details
Inspired by Faircamp by Simon Repp.
Enable label mode to create catalogs with multiple artists:
# catalog.yaml
labelMode: trueCreate artists/ directory with artist YAML files. Each release can be associated with an artist using artistSlug in release.yaml.
Hide releases from the main index and feeds while keeping them accessible via direct link:
# release.yaml
unlisted: trueUseful for:
- Work-in-progress releases
- Exclusive content
- Testing releases before public launch
If a release has no cover art, Tunecamp automatically generates a procedural SVG cover based on the release title and artist name. The cover uses deterministic algorithms (no AI) to create unique, consistent artwork.
Replace the text title in the header with a custom image, similar to Bandcamp:
# catalog.yaml
headerImage: "header.png" # Path relative to catalog directory or URLThe header image will be displayed prominently at the top of all pages in the header section. This image appears in the header area with the navigation bar.
Set a custom background image for the entire page:
# catalog.yaml
backgroundImage: "background.png" # Path relative to catalog directory or URLThe background image will cover the entire page with a semi-transparent overlay to maintain text readability. This is separate from the header image - the header image appears in the header section, while the background image covers the entire page body.
Add custom fonts from Google Fonts or other sources:
# catalog.yaml
customFont: "https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"The font will be loaded before the theme CSS, allowing you to use it in your custom CSS.
Add custom CSS to override or extend theme styles. You can use either a local file or an external URL:
# catalog.yaml
# Local file (copied to assets/ during build)
customCSS: "custom.css"
# Or external URL (CDN, etc.)
customCSS: "https://cdn.jsdelivr.net/npm/water.css@2/out/water.css"Local CSS files are copied to the assets/ directory during build. The custom CSS is loaded after the theme CSS, so your styles will override the default theme.
Example custom.css:
/* Apply custom font to body */
body {
font-family: 'Inter', sans-serif;
}
/* Custom header image styling */
.header-image {
max-height: 300px;
border-radius: 8px;
}