diff options
author | shoshin <shoshin@cicadas.surf> | 2022-08-28 20:41:06 -0500 |
---|---|---|
committer | shoshin <shoshin@cicadas.surf> | 2022-08-28 20:41:06 -0500 |
commit | de7cf9ff4791bd2ba471bf8d35b4995d972506d1 (patch) | |
tree | aa24bc56e9c472ed8b9f8a96e21541b95252f6c4 |
Add: new repo for refactored bard-bivoumacs
-rw-r--r-- | bard-bivoumacs.org | 745 |
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 + |