summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorshoshin <shoshin@cicadas.surf>2022-08-28 20:41:06 -0500
committershoshin <shoshin@cicadas.surf>2022-08-28 20:41:06 -0500
commitde7cf9ff4791bd2ba471bf8d35b4995d972506d1 (patch)
treeaa24bc56e9c472ed8b9f8a96e21541b95252f6c4
Add: new repo for refactored bard-bivoumacs
-rw-r--r--bard-bivoumacs.org745
1 files changed, 745 insertions, 0 deletions
diff --git a/bard-bivoumacs.org b/bard-bivoumacs.org
new file mode 100644
index 0000000..0ed4204
--- /dev/null
+++ b/bard-bivoumacs.org
@@ -0,0 +1,745 @@
+* Bard Bivou(m)acs
+** Notes
+ - =M-x html-html5-template= maybe?
+ - [[file:~/new-emacs/snippets/html-mode/audio::# -*- mode: snippet -*-][audio tag snippet]]
+ - [[file:~/new-emacs/straight/repos/emms/emms-info-opusinfo.el::(defun emms-info-opusinfo (track)][emms-info-opusinfo]]
+*** full emms integration path
+realizing a lot of the heavy lifting might be doable by
+leveraging the emms playlist buffer / functionality.
+
+#+name: track-list
+#+begin_src emacs-lisp :var dir=""
+ (let ((buf (emms-playlist-new))
+ (buf2 (get-buffer-create "*foo*"))
+ (html))
+ (emms-playlist-set-playlist-buffer buf)
+ (with-current-buffer buf
+ (emms-playlist-clear)
+ (emms-add-directory dir)
+ (emms-walk-tracks
+ (let* ((track (emms-playlist-track-at (point)))
+ (file (file-name-nondirectory (cdr (assoc 'name (cdr track)))))
+ (title (cdr (assoc 'info-title (cdr track))))
+ (number (cdr (assoc 'info-tracknumber (cdr track)))))
+ (with-current-buffer buf2
+ (insert "<li>") (newline)
+ (insert (concat "<button id='track-" number "' class='control' type='button'><img class='playlist-play' src='play.svg'></button>" number ". " title)) (newline)
+ (insert (concat "<audio>\n <source src='" file "' type='audio/ogg; codecs=opus'>\n</audio>"))
+ (goto-char (point-max))
+ (newline)
+ (insert "</li>")
+ (newline)))))
+ (with-current-buffer buf2
+ (html-mode)
+ (indent-region (point-min) (point-max))
+ (setq html (buffer-string)))
+ (kill-buffer "*foo*")
+ html)
+#+end_src
+
+#+RESULTS:
+#+begin_example
+<li>
+ <button id='track-1' class='control' type='button'><img class='playlist-play' src='play.svg'></button>1. akane
+ <audio>
+ <source src='casiopeia - basement days - 01 akane.opus' type='audio/ogg; codecs=opus'>
+ </audio>
+</li>
+<li>
+ <button id='track-2' class='control' type='button'><img class='playlist-play' src='play.svg'></button>2. merculite missle
+ <audio>
+ <source src='casiopeia - basement days - 02 merculite missle.opus' type='audio/ogg; codecs=opus'>
+ </audio>
+</li>
+... and so on
+#+end_example
+
+** Structure
+*** version 1
+ - [X] move html tags out of code as much as posible and put them here
+ - [ ] break up templates to fill them in easier with functions
+ - [X] add m3u or pls file link
+ - [ ] torrent?
+
+This defines two "halves" dividing the page. I'm envisioning a horizontal
+alignment on browser and vertical on mobile. It will display the cover art,
+artist and album title, the tracklist, and whatever player controls I come
+up with. This will be used as a format string in emacs-lisp code where we
+will fill in the %s characters with the desired values.
+#+name: structure
+#+begin_export html
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <title>%s</title>
+ <link href="../style.css" rel="stylesheet" type="text/css" />
+ <link rel="stylesheet" media="screen" href="https://fontlibrary.org/face/gnu-unifont" type="text/css"/>
+ </head>
+ <body>
+ <div id="app">
+ <div class='a-half'>
+ <div class='controls'>
+ <button id='rewind' class='control' type='button'><img class='control-img' src='../rewind.svg'></button>
+ <button id='main-play' class='control' type='button'><img class='control-img' src='../play-button.svg'></button>
+ <button id='forward' class='control' type='button'><img class='control-img' src='../ff.svg'></button>
+ </div>
+ <div class='track-list'>
+ %s
+ </div>
+ </div>
+ <div class='b-half'>
+ <div class='album-title'>
+ <h1>%s</h1>
+ </div>
+ <div class='cover-art'>
+ <img class='album-cover' src='%s' alt='album cover art' />
+ </div>
+ </div>
+ </div>
+ <div id='footer'>
+ <div>stream this <a href="basement days.m3u">playlist</a> in your favorite music player</div>
+ <div id='license'>
+ <div>
+ <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/"><img alt="Creative Commons License" style="border-width:0" src="https://i.creativecommons.org/l/by-sa/4.0/88x31.png" /></a>
+ </div>
+ <div>
+ Content licensed <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/">CC BY-SA 4.0</a>.
+ </div>
+ </div>
+ </div>
+ <script src="../player.js"></script>
+ </body>
+</html>
+#+end_export
+
+I imagine with a package, you might have a custom variable that
+could be used as a template file, or perhaps it could be a literal
+template file given in the directory or as an argument. Or... I
+have found [[https://github.com/Kinneyzhang/pp-html]] which is an elisp
+library for generating HTML directly. However, in the effort to Keep
+It Simple, Silly, plain HTML with %s's is fine for me.
+
+Anyhow, this dynamic variable will allow the functions defined below
+to use it assuming its assigned a proper format string form.
+
+#+name: set-template
+#+begin_src emacs-lisp :var templ=structure
+ (setq bard-bivoumacs-template templ)
+#+end_src
+
+#+RESULTS: set-template
+#+begin_example
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <title>%s</title>
+ <link href="../style.css" rel="stylesheet" type="text/css" />
+ <link rel="stylesheet" media="screen" href="https://fontlibrary.org/face/gnu-unifont" type="text/css"/>
+ </head>
+ <body>
+ <div id="app">
+ <div class='a-half'>
+ <div class='controls'>
+ <button id='rewind' class='control' type='button'><img class='control-img' src='../rewind.svg'></button>
+ <button id='main-play' class='control' type='button'><img class='control-img' src='../play-button.svg'></button>
+ <button id='forward' class='control' type='button'><img class='control-img' src='../ff.svg'></button>
+ </div>
+ <div class='track-list'>
+ %s
+ </div>
+ </div>
+ <div class='b-half'>
+ <div class='album-title'>
+ <h1>%s</h1>
+ </div>
+ <div class='cover-art'>
+ <img class='album-cover' src='%s' alt='album cover art' />
+ </div>
+ </div>
+ </div>
+ <div id='footer'>
+ <div>stream this <a href="basement days.m3u">playlist</a> in your favorite music player</div>
+ <div id='license'>
+ <div>
+ <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/"><img alt="Creative Commons License" style="border-width:0" src="https://i.creativecommons.org/l/by-sa/4.0/88x31.png" /></a>
+ </div>
+ <div>
+ Content licensed <a rel="license" href="http://creativecommons.iorg/licenses/by-sa/4.0/">CC BY-SA 4.0</a>.
+ </div>
+ </div>
+ </div>
+ <script src="../player.js"></script>
+ </body>
+</html>
+#+end_example
+
+*** version 2
+
+#+name: page-title
+#+begin_src html
+ casiopeia basement days
+#+end_src
+
+#+name: album-title
+#+begin_src emacs-lisp
+ "Basement Days"
+#+end_src
+
+#+name: html-index
+#+begin_src html :noweb yes :tangle index.html
+ <!doctype html>
+ <html lang="en">
+ <<head>>
+ <body>
+ <<bard-bivoumacs>>
+ </body>
+ </html>
+#+end_src
+
+#+name: head
+#+begin_src html :noweb yes
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <title><<page-title>></title>
+ <link href="style.css" rel="stylesheet" type="text/css" />
+#+end_src
+
+#+name: bard-bivoumacs
+#+begin_src html :noweb yes
+ <div id="app">
+ <<a-half>>
+ <<b-half>>
+ </div>
+ <<footer>>
+ <script src="player.js"></script>
+#+end_src
+
+#+name: a-half
+#+begin_src html :noweb yes
+ <div class='a-half'>
+ <div class='controls'>
+ <button id='rewind' class='control' type='button'><img class='control-img' src='rewind.svg'></button>
+ <button id='main-play' class='control' type='button'><img class='control-img' src='play.svg'></button>
+ <button id='forward' class='control' type='button'><img class='control-img' src='ff.svg'></button>
+ </div>
+ <div class='track-list'>
+ <ul>
+ <<track-list("~/Music/casiopeia/basement days/")>>
+ </ul>
+ </div>
+ </div>
+#+end_src
+
+#+name: b-half
+#+begin_src html :noweb yes
+ <div class='b-half'>
+ <div class='album-title'>
+ <h1><<album-title>></h1>
+ </div>
+ <div class='cover-art'>
+ <img class='album-cover' src='cover.jpg' alt='album cover art' />
+ </div>
+ </div>
+#+end_src
+
+#+name: footer
+#+begin_src html
+ <div id='footer'>
+ <div>stream this <a href="playlist.m3u">playlist</a> in your favorite music player</div>
+ <div id='license'>
+ <div>
+ <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/"><img alt="Creative Commons License" style="border-width:0" src="https://i.creativecommons.org/l/by-sa/4.0/88x31.png" /></a>
+ </div>
+ <div>
+ Content licensed <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/">CC BY-SA 4.0</a>.
+ </div>
+ </div>
+ </div>
+#+end_src
+
+** Style
+
+ - [X] you can't style audio controls, so i need to build my own elements
+ for controlling with JS
+ - [X] pick a font or fonts
+ - [X] deal with view sizes
+ - [X] button images (swap play/pause etc)
+
+*** Stylesheet
+#+name: stylesheet
+#+begin_src css :tangle style.css
+ @font-face {
+ font-family: 'UnifontMedium';
+ src: url('unifont.woff2');
+ font-weight: normal;
+ font-style: normal;
+ }
+
+ body {
+ background: lightblue;
+ font-family: 'UnifontMedium';
+ font-weight: normal;
+ font-style: normal;
+ display: flex;
+ flex-direction: column;
+ }
+
+ ul {
+ list-style: none;
+ padding-left: 0;
+ }
+
+ ul li::before {
+ content: "\200B";
+ }
+
+ #app {
+ display: flex;
+ align-items: center;
+ justify-content: space-around;
+ margin: auto;
+ width: 975px;
+ height: 800px;
+ }
+
+ #footer {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: space-around;
+ margin: auto;
+ width: 975px;
+ }
+
+ #license {
+ margin-top: 10px;
+ font-size: x-small;
+ display: flex;
+ align-items: center;
+ justify-content: space-evenly;
+ width: 50%;
+ }
+
+ .a-half {
+ height: 100%;
+ width: 50%;
+ display: flex;
+ flex-direction: column;
+ }
+
+ .controls {
+ height: 15%;
+ width: 100%;
+ display: flex;
+ justify-content: space-evenly;
+ }
+
+ .control {
+ border: 0;
+ background: transparent;
+ }
+
+ .track-list {
+ height: 85%;
+ }
+
+ .playlist-play {
+ height: 24px;
+ }
+
+ .b-half {
+ height: 100%;
+ width: 50%;
+ }
+
+ .album-title {
+ height: 20%;
+ }
+
+ .cover-art {
+ height: 80%;
+ }
+
+ .album-cover {
+ width: 100%;
+ }
+
+ @media (max-width: 500px) {
+ h1 {
+ font-size: medium;
+ }
+
+ #app {
+ display: flex;
+ align-items: center;
+ justify-content: space-around;
+ margin: auto;
+ flex-direction: column-reverse;
+ width: 100%;
+ height: 100%;
+ }
+
+ #footer {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: space-around;
+ margin: auto;
+ width: 100%;
+ }
+
+ #license {
+ margin-top: 10px;
+ font-size: x-small;
+ display: flex;
+ align-items: center;
+ justify-content: space-evenly;
+ width: 100%;
+ }
+
+ .a-half {
+ height: 50%;
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: flex-start;
+ }
+
+ .controls {
+ height: 10%;
+ }
+
+ .control-img {
+ height: 32px;
+ }
+
+ .track-list {
+ height: 90%;
+ }
+
+ .playlist-play {
+ height: 16px;
+ }
+
+ .b-half {
+ height: 50%;
+ width: 100%;
+ display: flex;
+ flex-direction: column-reverse;
+ align-items: center;
+ }
+
+ .album-title {
+ height: 15%;
+ }
+
+ .cover-art {
+ height: 85%;
+ }
+
+ .album-cover {
+ height: 100%;
+ width: 100%;
+ }
+ }
+#+end_src
+
+*** COMMENT Images
+
+#+name: play-svg
+#+begin_src xml :tangle play.svg
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="48.0px" height="48.0px"
+ version="1.1" baseProfile="full"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:ev="http://www.w3.org/2001/xml-events" >
+ <path
+ d="M 5,5 V 45 L 45,25 Z"
+ stroke="black"
+ fill="transparent" />
+</svg>
+#+end_src
+
+ [[file:play-button.svg]]
+
+#+name: pause-svg
+#+begin_src xml :tangle pause.svg
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="48.0px" height="48.0px"
+ version="1.1" baseProfile="full"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:ev="http://www.w3.org/2001/xml-events" >
+ <rect x="1" y="1" width="20" height="46" stroke="black" fill="transparent" />
+ <rect x="27" y="1" width="20" height="46" stroke="black" fill="transparent" />
+</svg>
+#+end_src
+
+*** Fonts
+
+#+name: russisch-sans
+#+begin_export html
+<link rel="stylesheet" media="screen" href="https://fontlibrary.org/face/russisch-sans" type="text/css"/>
+#+end_export
+
+#+name: unifont
+#+begin_src html
+<link rel="stylesheet" media="screen" href="https://fontlibrary.org/face/gnu-unifont" type="text/css"/>
+#+end_src
+
+#+name: set-font
+#+begin_src emacs-lisp :var font=unifont
+(setq bard-bivoumacs-font font)
+#+end_src
+
+** Generator Code
+
+The idea here is to read a directory that is prepared for release (see below)
+and generate a nice web frontend to play and display an album.
+
+ - [X] read a directory for audio files
+ - [X] parse audio metadata
+ - [X] produce <audio> tag for each track
+ - [X] wrap it all up in a page including metadata
+ - [ ] support more audio formats
+ - [X] hook up to JS code for running the "playlist"
+ - [ ] generate playlist file (use emms and modify path to be relative)
+
+#+begin_src emacs-lisp
+ (defun select-audio-files (dir audio-type)
+ "Returns a list of files from a directory DIR with given AUDIO-TYPE."
+ (seq-filter
+ (lambda (fn) (equal audio-type (file-name-extension fn)))
+ (directory-files dir)))
+
+ (defun make-track (file)
+ "Makes a track structure usable by EMMS info functions."
+ (list '*track* (cons 'type 'file) (cons 'name file)))
+
+ (defun process-dir-to-tracks (dir)
+ "Processes audio files from DIR into track structures usable with EMMS functions"
+ ;; TODO fix opus / info function hardcode
+ (let ((tracks (mapcar #'make-track (select-audio-files "." "opus"))))
+ (mapcar #'emms-info-opusinfo tracks)
+ tracks))
+
+ (defun encode-track-list-element (track)
+ "Creates an HTML list element with TRACK"
+ (let ((file (cdr (assoc 'name (cdr track))))
+ (title (cdr (assoc 'info-title (cdr track))))
+ (number (cdr (assoc 'info-tracknumber (cdr track)))))
+ (insert "<li>") (newline)
+ (insert (concat "<button id='track-" number "' class='control' type='button'><img class='playlist-play' src='play.svg'></button>" number ". " title)) (newline)
+ (insert (concat "<audio>\n <source src='" file "' type='audio/ogg; codecs=opus'>\n</audio>"))
+ (goto-char (point-max))
+ (newline)
+ (insert "</li>")
+ (newline)))
+
+ (defun tracklist-html (tracks)
+ (with-temp-buffer
+ (html-mode)
+ (insert "<ul>") (newline)
+ (dolist (track tracks)
+ (encode-track-list-element track))
+ (insert "</ul>")
+ (buffer-string)))
+
+ (defun generate-album-html (dir)
+ (interactive "DDirectory: ")
+ (let* ((tracks (process-dir-to-tracks dir))
+ (cover (concat (file-name-as-directory dir) "cover.png"))
+ (artist (cdr (assoc 'info-artist (cdr (cadr tracks)))))
+ (album (cdr (assoc 'info-album (cdr (cadr tracks)))))
+ (title (concat artist " - " album)))
+ (with-temp-file "basement-days.html"
+ (html-mode)
+ (insert
+ ;; title(page) | tracklist | title | cover
+ (format bard-bivoumacs-template title (tracklist-html tracks) title cover))
+ (indent-region (point-min) (point-max)))))
+#+end_src
+
+#+RESULTS:
+: generate-album-html
+
+*** org block version
+
+#+name: get-audio-files
+#+begin_src emacs-lisp :var audio-type="opus" :var dir="~/Music/casiopeia/basement days" :exports none
+ (let ((default-directory dir))
+ (seq-filter
+ (lambda (fn) (equal audio-type (file-name-extension fn)))
+ (mapcar #'expand-file-name (directory-files dir))))
+#+end_src
+
+#+RESULTS: get-audio-files
+| /home/shoshin/Music/casiopeia/basement days/1980 S-6.opus | /home/shoshin/Music/casiopeia/basement days/akane.opus | /home/shoshin/Music/casiopeia/basement days/bebop.opus | /home/shoshin/Music/casiopeia/basement days/bikini island.opus | /home/shoshin/Music/casiopeia/basement days/calypso drone i.opus | /home/shoshin/Music/casiopeia/basement days/calypso drone ii.opus | /home/shoshin/Music/casiopeia/basement days/calypso drone iii.opus | /home/shoshin/Music/casiopeia/basement days/canon.opus | /home/shoshin/Music/casiopeia/basement days/flirt.opus | /home/shoshin/Music/casiopeia/basement days/hardware.opus | /home/shoshin/Music/casiopeia/basement days/io fanfare.opus | /home/shoshin/Music/casiopeia/basement days/jacquelyne sofia kilmer.opus | /home/shoshin/Music/casiopeia/basement days/merculite missle.opus | /home/shoshin/Music/casiopeia/basement days/ripley.opus | /home/shoshin/Music/casiopeia/basement days/taxi medley.opus | /home/shoshin/Music/casiopeia/basement days/teseo.opus | /home/shoshin/Music/casiopeia/basement days/the slow train.opus |
+
+#+name: convert-to-emms-tracks
+#+begin_src emacs-lisp :noweb yes
+ (let ((tracks
+ (mapcar (lambda (file)
+ (list '*track* (cons 'type 'file) (cons 'name file)))
+ '<<get-audio-files()>>)))
+ (mapcar #'emms-info-opusinfo tracks)
+ tracks)
+#+end_src
+
+#+RESULTS: convert-to-emms-tracks
+| *track* | (type . file) | (name . /home/shoshin/Music/casiopeia/basement days/1980 S-6.opus) | (info-playing-time . 199) | (info-playing-time-min . 3) | (info-playing-time-sec . 19) | (info-file . /home/shoshin/Music/casiopeia/basement days/1980 S-6.opus) | (info-album . basement days) | (info-artist . casiopeia) | (info-date . 2000) | (info-encoder . Lavc58.35.100 libopus) | (info-genre . new wave) | (info-title . 1980 S-6) | (info-tracknumber . 4) | (info-tracktotal . 17) |
+| *track* | (type . file) | (name . /home/shoshin/Music/casiopeia/basement days/akane.opus) | (info-playing-time . 413) | (info-playing-time-min . 6) | (info-playing-time-sec . 53) | (info-file . /home/shoshin/Music/casiopeia/basement days/akane.opus) | (info-album . basement days) | (info-artist . casiopeia) | (info-date . 2000) | (info-encoder . Lavc58.35.100 libopus) | (info-genre . new wave) | (info-title . akane) | (info-tracknumber . 1) | (info-tracktotal . 17) |
+| *track* | (type . file) | (name . /home/shoshin/Music/casiopeia/basement days/bebop.opus) | (info-playing-time . 97) | (info-playing-time-min . 1) | (info-playing-time-sec . 37) | (info-file . /home/shoshin/Music/casiopeia/basement days/bebop.opus) | (info-album . basement days) | (info-artist . casiopeia) | (info-date . 2000) | (info-encoder . Lavc58.35.100 libopus) | (info-genre . new wave) | (info-title . bebop) | (info-tracknumber . 14) | (info-tracktotal . 17) |
+| *track* | (type . file) | (name . /home/shoshin/Music/casiopeia/basement days/bikini island.opus) | (info-playing-time . 240) | (info-playing-time-min . 4) | (info-playing-time-sec . 0) | (info-file . /home/shoshin/Music/casiopeia/basement days/bikini island.opus) | (info-album . basement days) | (info-artist . casiopeia) | (info-date . 2000) | (info-encoder . Lavc58.35.100 libopus) | (info-genre . new wave) | (info-title . bikini island) | (info-tracknumber . 12) | (info-tracktotal . 17) |
+| *track* | (type . file) | (name . /home/shoshin/Music/casiopeia/basement days/calypso drone i.opus) | (info-playing-time . 52) | (info-playing-time-min . 0) | (info-playing-time-sec . 52) | (info-file . /home/shoshin/Music/casiopeia/basement days/calypso drone i.opus) | (info-album . basement days) | (info-artist . casiopeia) | (info-date . 2000) | (info-encoder . Lavc58.35.100 libopus) | (info-genre . new wave) | (info-title . calypso drone i) | (info-tracknumber . 5) | (info-tracktotal . 17) |
+| *track* | (type . file) | (name . /home/shoshin/Music/casiopeia/basement days/calypso drone ii.opus) | (info-playing-time . 194) | (info-playing-time-min . 3) | (info-playing-time-sec . 14) | (info-file . /home/shoshin/Music/casiopeia/basement days/calypso drone ii.opus) | (info-album . basement days) | (info-artist . casiopeia) | (info-date . 2000) | (info-encoder . Lavc58.35.100 libopus) | (info-genre . new wave) | (info-title . calypso drone ii) | (info-tracknumber . 6) | (info-tracktotal . 17) |
+| *track* | (type . file) | (name . /home/shoshin/Music/casiopeia/basement days/calypso drone iii.opus) | (info-playing-time . 72) | (info-playing-time-min . 1) | (info-playing-time-sec . 12) | (info-file . /home/shoshin/Music/casiopeia/basement days/calypso drone iii.opus) | (info-album . basement days) | (info-artist . casiopeia) | (info-date . 2000) | (info-encoder . Lavc58.35.100 libopus) | (info-title . calypso drone iii) | (info-tracknumber . 7) | (info-tracktotal . 17) | |
+| *track* | (type . file) | (name . /home/shoshin/Music/casiopeia/basement days/canon.opus) | (info-playing-time . 441) | (info-playing-time-min . 7) | (info-playing-time-sec . 21) | (info-file . /home/shoshin/Music/casiopeia/basement days/canon.opus) | (info-album . basement days) | (info-artist . casiopeia) | (info-date . 2000) | (info-encoder . Lavc58.35.100 libopus) | (info-genre . new wave) | (info-title . canon) | (info-tracknumber . 17) | (info-tracktotal . 17) |
+| *track* | (type . file) | (name . /home/shoshin/Music/casiopeia/basement days/flirt.opus) | (info-playing-time . 233) | (info-playing-time-min . 3) | (info-playing-time-sec . 53) | (info-file . /home/shoshin/Music/casiopeia/basement days/flirt.opus) | (info-album . basement days) | (info-artist . casiopeia) | (info-date . 2000) | (info-encoder . Lavc58.35.100 libopus) | (info-genre . new wave) | (info-title . flirt) | (info-tracknumber . 15) | (info-tracktotal . 17) |
+| *track* | (type . file) | (name . /home/shoshin/Music/casiopeia/basement days/hardware.opus) | (info-playing-time . 90) | (info-playing-time-min . 1) | (info-playing-time-sec . 30) | (info-file . /home/shoshin/Music/casiopeia/basement days/hardware.opus) | (info-album . basement days) | (info-artist . casiopeia) | (info-date . 2000) | (info-encoder . Lavc58.35.100 libopus) | (info-genre . new wave) | (info-title . hardware) | (info-tracknumber . 16) | (info-tracktotal . 17) |
+| *track* | (type . file) | (name . /home/shoshin/Music/casiopeia/basement days/io fanfare.opus) | (info-playing-time . 129) | (info-playing-time-min . 2) | (info-playing-time-sec . 9) | (info-file . /home/shoshin/Music/casiopeia/basement days/io fanfare.opus) | (info-album . basement days) | (info-artist . casiopeia) | (info-date . 2000) | (info-encoder . Lavc58.35.100 libopus) | (info-genre . new wave) | (info-title . io fanfare) | (info-tracknumber . 9) | (info-tracktotal . 17) |
+| *track* | (type . file) | (name . /home/shoshin/Music/casiopeia/basement days/jacquelyne sofia kilmer.opus) | (info-playing-time . 466) | (info-playing-time-min . 7) | (info-playing-time-sec . 46) | (info-file . /home/shoshin/Music/casiopeia/basement days/jacquelyne sofia kilmer.opus) | (info-album . basement days) | (info-artist . casiopeia) | (info-date . 2000) | (info-encoder . Lavc58.35.100 libopus) | (info-genre . new wave) | (info-title . jacquelyne sofia kilmer) | (info-tracknumber . 8) | (info-tracktotal . 17) |
+| *track* | (type . file) | (name . /home/shoshin/Music/casiopeia/basement days/merculite missle.opus) | (info-playing-time . 280) | (info-playing-time-min . 4) | (info-playing-time-sec . 40) | (info-file . /home/shoshin/Music/casiopeia/basement days/merculite missle.opus) | (info-album . basement days) | (info-artist . casiopeia) | (info-date . 2000) | (info-encoder . Lavc58.35.100 libopus) | (info-genre . new wave) | (info-title . merculite missle) | (info-tracknumber . 2) | (info-tracktotal . 17) |
+| *track* | (type . file) | (name . /home/shoshin/Music/casiopeia/basement days/ripley.opus) | (info-playing-time . 183) | (info-playing-time-min . 3) | (info-playing-time-sec . 3) | (info-file . /home/shoshin/Music/casiopeia/basement days/ripley.opus) | (info-album . basement days) | (info-artist . casiopeia) | (info-date . 2000) | (info-encoder . Lavc58.35.100 libopus) | (info-genre . new wave) | (info-title . ripley) | (info-tracknumber . 3) | (info-tracktotal . 17) |
+| *track* | (type . file) | (name . /home/shoshin/Music/casiopeia/basement days/taxi medley.opus) | (info-playing-time . 327) | (info-playing-time-min . 5) | (info-playing-time-sec . 27) | (info-file . /home/shoshin/Music/casiopeia/basement days/taxi medley.opus) | (info-album . basement days) | (info-artist . casiopeia) | (info-date . 2000) | (info-encoder . Lavc58.35.100 libopus) | (info-genre . new wave) | (info-title . taxi medley) | (info-tracknumber . 10) | (info-tracktotal . 17) |
+| *track* | (type . file) | (name . /home/shoshin/Music/casiopeia/basement days/teseo.opus) | (info-playing-time . 198) | (info-playing-time-min . 3) | (info-playing-time-sec . 18) | (info-file . /home/shoshin/Music/casiopeia/basement days/teseo.opus) | (info-album . basement days) | (info-artist . casiopeia) | (info-date . 2000) | (info-encoder . Lavc58.35.100 libopus) | (info-genre . new wave) | (info-title . teseo) | (info-tracknumber . 13) | (info-tracktotal . 17) |
+| *track* | (type . file) | (name . /home/shoshin/Music/casiopeia/basement days/the slow train.opus) | (info-playing-time . 299) | (info-playing-time-min . 4) | (info-playing-time-sec . 59) | (info-file . /home/shoshin/Music/casiopeia/basement days/the slow train.opus) | (info-album . basement days) | (info-artist . casiopeia) | (info-date . 2000) | (info-encoder . Lavc58.35.100 libopus) | (info-genre . new wave) | (info-title . the slow train) | (info-tracknumber . 11) | (info-tracktotal . 17) |
+
+#+name: track-to-html-list-item
+#+begin_src emacs-lisp :var tracks=convert-to-emms-tracks()
+ (let ((file (cdr (assoc 'name (cdr (car tracks)))))
+ (title (cdr (assoc 'info-title (cdr (car tracks)))))
+ (number (cdr (assoc 'info-tracknumber (cdr (car tracks))))))
+ (with-temp-buffer
+ (insert "<li>") (newline)
+ (insert (concat "<button id='track-" number "' class='control' type='button'><img class='playlist-play' src='play.svg'></button>" number ". " title)) (newline)
+ (insert (concat "<audio>\n <source src='" file "' type='audio/ogg; codecs=opus'>\n</audio>"))
+ (goto-char (point-max))
+ (newline)
+ (insert "</li>")
+ (newline)
+ (buffer-string)))
+#+end_src
+
+#+RESULTS: track-to-html-list-item
+: <li>
+: <button id='track-4' class='control' type='button'><img class='playlist-play' src='play.svg'></button>4. 1980 S-6
+: <audio>
+: <source src='/home/shoshin/Music/casiopeia/basement days/1980 S-6.opus' type='audio/ogg; codecs=opus'>
+: </audio>
+: </li>
+
+** Playlist JS
+
+ - [X] register each audio element in a JS array
+ - [X] keep track of the current track
+ - [X] respond to track end event by starting next track
+ - [X] "main" controls for player
+ - [X] individual play buttons for each track to start from there
+ - [ ] ? shuffle ?
+ - [X] set current track time to 0 when seeking fd/bk
+ - [ ] Display time passed / total time of current track
+
+#+name: playlist-js
+#+begin_src javascript :tangle player.js
+ // @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL v3.0
+ var tracks = document.querySelectorAll("audio");
+ var trackIndex = 0
+ var currentTrack = tracks[trackIndex];
+ var mainPlay = document.getElementById("main-play");
+ var rewind = document.getElementById("rewind");
+ var forward = document.getElementById("forward");
+ var isPlaying = false;
+ var image = { play: "play.svg", pause: "pause.svg" }
+
+ function play() {
+ currentTrack.play();
+ mainPlay.firstChild.src = image.pause;
+ currentButton().src = image.pause;
+ isPlaying = true;
+ }
+
+ function pause() {
+ currentTrack.pause();
+ mainPlay.firstChild.src = image.play;
+ currentButton().src = image.play;
+ isPlaying = false;
+ }
+
+ function currentButton() {
+ return document.getElementById(`track-${trackIndex + 1}`).firstChild;
+ }
+
+ mainPlay.onclick = () => {
+ isPlaying ? pause() : play();
+ }
+
+ rewind.onclick = () => {
+ currentTrack.currentTime = 0;
+
+ if (trackIndex === 0) {
+ return;
+ } else {
+ pause();
+ trackIndex = trackIndex - 1;
+ currentTrack = tracks[trackIndex];
+ play();
+ }
+ }
+
+ forward.onclick = () => {
+ pause();
+ currentTrack.currentTime = 0;
+
+ if (trackIndex === tracks.length) {
+ trackIndex = 0;
+ currentTrack = tracks[0];
+ play();
+ } else {
+ trackIndex = trackIndex + 1;
+ currentTrack = tracks[trackIndex];
+ play();
+ }
+ }
+
+ tracks.forEach((track, index) => {
+ btn = document.getElementById(`track-${index + 1}`)
+ btn.onclick = () => {
+ pause();
+ trackIndex = index;
+ currentTrack = tracks[trackIndex];
+ play();
+ }
+
+ track.addEventListener("ended", () => {
+ currentButton().src = image.play;
+ trackIndex = trackIndex + 1;
+ currentTrack = tracks[trackIndex];
+ play();
+ })
+ });
+ // @license-end
+#+end_src
+
+** Content License
+
+I've decided to use the [[https://creativecommons.org/licenses/by-sa/4.0/][CC BY-SA 4.0 license]]
+which is the Creative Commons Attribution-ShareAlike that is similar
+to the AGPL. The content can be used for any purpose, as long as
+attribution is given to the author, and derivative works use a
+compatible license.
+
+#+name: license-html
+#+begin_src html
+<a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/"><img alt="Creative Commons License" style="border-width:0" src="https://i.creativecommons.org/l/by-sa/4.0/88x31.png" /></a><br />This work is licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/">Creative Commons Attribution-ShareAlike 4.0 International License</a>.
+#+end_src
+