Skip to content
Switch branches/tags
Go to file

Latest commit


Git stats


Failed to load latest commit information.
Latest commit message
Commit time

Pushtape Cassette

Pushtape Cassette is a lightweight framework for building better music web applications. Make a cassette of your music and render a complete music web app in seconds, featuring a persistent music player.




This project creates a static music webapp that can be integrated with any number of backend technologies: flat files, Wordpress/Drupal, JS Frameworks, Python, and Ruby. The key element that powers this idea is the cassette.json file, a portable discography format. This file acts like a single comprehensive endpoint - and from this endpoint, javascript is used to create a single-page-application using various micro-libraries.


  • Clone this repo to your web server
  • Check the base URL tag in index.html
  • Add new music folders inside the releases directory and add markdown files inside the pages directory.
  • Run python or update cassette.json manually
  • Visit index.html in your browser

Base URL

If you run the app from a subdirectory from document root, in index.html change the base tag to:

  <base href="/subdirectory/" />

or alternatively load all assets using absolute paths.


  • Build cassette.json by manually editing the file or run the build script.
$ cd pushtape-cassette
$ python

The build script will automatically generate a cassette.json based on the files in the working directory. It is intended to be run locally on the command line, but if your server is configured to run python scripts you can try running it from the browser or setup a crontab. Notes:

  • This script recursively searches through files and folders in the working directory and generates cassette.json as well as any necessary tracklist.jspf files.
  • The .jspf files are stored in _data and the music directories are left untouched.
  • You can use subfolders to organize releases by artist, i.e. releases/artist-name/release-name
  • If no artwork file is found, assets/images/default-artwork.jpg will be used.

Clean URLS

If you want to remove the hash # from the URL routes and use History API instead, in index.html set app.settings.cleanURLs to true. Note that running the app with History API enabled from document root is encouraged as it takes care of all relative link issues.

Flat File Example

Note: you can skip steps 3 and 4 if you use the build script.

  1. Add static pages by creating markdown files in the /pages directory, each file should have the .md extension.
  2. Add your music in the /releases directory. Place each release in its own directory, i.e. /releases/release-title or /releases/artist/release-title
  • Add liner notes by including a markdown file
  • Add artwork by including an artwork.jpg file (400x400 size is recommended)
  • Add MP3s or gather a list of remote MP3 URLs (128kbps - 320kbps CBR MP3 recommended)
  1. Create a tracklist.jspf file for each release, which contains the order of the tracklist, metadata, and file locations (local or remote)
  2. Build cassette.json with the URLs for your new pages and music.
  3. Visit your new static site.

Theme modifications

  • The CSS is organized following SMACSS principles. You can find theme related files in /assets/css.
  • The Bootstrap grid is included. Note that in index.html, the #content container div has Bootstrap's .container-fluid class applied. This means you can use .row and .col-* classes for any markup inside of that div.
  • For full sized page background, look for the comments inside assets/css/theme/theme.css.



Property Type Description
lastBuild timestamp A way to track when the file was last built or modified.
pages object Contains key:value pairs for static pages on your site. The key defines the first level JS router path, i.e. 'about'. The value contains the URL location for a markdown document. The URL can be relative or absolute. If your server is returning documents using JSON/JSONP, set "format" : "json". If you need to include an external link and bypass the JS router, set "type" : "external".
releases object Contains key:value pairs defining the music releases available. A key defines the JS router path and should be all lower case with no spaces, i.e. album-title or artist/album-title. The fully generated path ends up being release/album-title or release/artist/album-title. The corresponding value defines the properties for this release. At a minimum you should specify the URL for artwork.jpg and (relative or absolute, optionally can specify format as json). The playlist property needs to be a path to a valid JSPF playlist file, which specifies the track order and location of mp3 files, and any other metadata.

App settings

Property Type Description
app.settings.cassettePath string By default, application.js will load the local cassette.json path. You can override the path to cassette.json by setting this global variable before loading application.js.
app.settings.homePage string This value specifies what page should load by default. The path must be registered in the JS router.
app.settings.cleanURLs boolean If false, hash # urls are used. If true, the History API will handle clean URLs.

Known issues:

  • The /releases path is a reserved JS route used to list all the available releases, and is the default homepage.
  • In some instances local environments will not be able to load remote assets because of cross-origin request limitations. You may need to host those assets locally, otherwise running the web app on a web server should resolve any issues. If you further encounter problems, see the note about JSONP below.


  • Because this project aims to present a static UX, you will likely run into limitations if you want more dynamic functionality. You can always try mixing dynamic assets into markdown, or for the more technically advanced you can try modifying application.js to suit your needs.

Example cassette.json:

  "lastBuild": {},
  "pages": {
     "releases" : {},
     "about" : {"location" : "pages/"},
     "shows" : {"location" : "pages/"},
     "external-link" : {"title": "Soundcloud", "location" : "", "type" : "external"}
  "releases": {
    "example-release": {
      "title" : "Cosmic Voyage",
      "playlist" : "releases/example-release/tracklist.jspf",
      "artwork" : "releases/example-release/artwork.jpg",
      "notes" : "releases/example-release/"  
    "example-release-two": {
      "title" : "Bird Life",
      "playlist" : "releases/example-release-two/tracklist.jspf",
      "artwork" : "releases/example-release-two/artwork.jpg",
      "notes" : "releases/example-release-two/"  

Default Routes

URL Path Description
/ If no path is entered, the default homepage is loaded.
/[page-title] This parses and displays the markdown for a page as defined in cassette.json.
/releases A list of all releases with artwork and name, hyperlinked to the individual release page.
Displays all the information for a single release: artwork, playable tracklist, and notes.


Problem Steps
Blank page or missing CSS/JS Double check your base url in index.html. If you have trouble figuring out the right path, sometimes the server path can be inferred using Chrome inspector.
Cross-origin request problems (remote content not loading) When dealing with remote cross-origin requests valid JSONP must be returned and requests need to be formatted correctly. 1. You need to pass ?callback=? in the URL, i.e. 2: The response from the server must be JSONP, not just regular JSON. In particular, cross-origin issues may arise when remotely loading cassette.json, jspf,, and Alternatively you can just load all assets locally to avoid having to setup a JSONP workaround.
Site is not getting indexed by search engines Other than checking your robots.txt and other best practices, this is a known issue with frameworks that use Javascript to render page content. The easiest solution is to use a service like to cache and serve rendered HTML pages. I recommend installing the token via Apache. Here's a gist for what your .htaccess might look like (you'll need to change the TOKEN_VALUE and for your site).

Why did I make this?

A lot of music sites are fairly static but have tricky frontend requirements. The best music UX allows for an uninterrupted music listening experience while performing other tasks such as reading liner notes, browsing other music, etc. Usually this means AJAXifying a traditional CMS/static site or building a complete solution from scratch using JS. This quickly becomes a headache to build and maintain, especially in the long term. By creating a decoupled frontend framework, it allows for better separation of concerns and lowers the long-term effort required to build and maintain a site. Additionally, by leveraging JSPF and cassette.json, a portable discography format, data portability is not an afterthought - it is built into the application from the beginning.

Why JS micro-libraries? Why not Angular, Ember, etc?

I chose micro-libraries because the requirements for rendering a static music application are typically fairly modest, and I wanted to avoid reliance on a third party Single-Page-Application (SPA) framework. Additionally, because I used micro-libraries, it makes it easier to pick and choose what you want. For instance if you don't like the templating system, routing, or two-way binding libraries I chose, you can replace them with your preferred JS library/framework.



A lightweight framework for building static music apps. Make a cassette.json of your music and render a complete music site in seconds, featuring a persistent music player.





No releases published


No packages published