stash
stash copied to clipboard
[Feature] Support audio files/stories
Is your feature request related to a problem? Please describe. There isn't a native way to play audio files in Stash.
Describe the solution you'd like Adding support for audio files/stories. Metadata I would like to see:
name
author
Performer
url for source
website of source
date
language
cover front
cover back
Rating
Tags
Details
Additional context Added $40 bounty.
Related #11
$50 bounty added (txn #793013). Total bounty $90.
Please. Publish this update which is of paramount importance to stash!
I'd also love this. I have a huge audio library that I'd like to use here, and I have a couple additional thoughts on how it should be implemented. Edit: I've added some ideas since I posted this, and will probably add more as I learn more about how to use Stash in general and have time to think about the ideal way to implement audio.
Multiple Source/website URL entries. GonewildAudio, for example, has a reddit link and potentially several other sources. It would be good to be able to add all the links in a post, possibly by scraper eventually, so that if you need to go back and re-source the file or something, you can get it even if one of the sources is down or hidden.)
Grouping Audios. Sometimes audios come as parts of a whole, so it would be nice to tag them as such, similar to how Movies are groups of Scenes. I would propose calling it "Series", but the name doesn't matter much.
Markers. Same as in videos, it would be helpful to have these to jump back to your favorite parts.
Playback of video files as audio. There are some creators who post the original/best/only source to video platforms like pornhub. The "videos" are still audio content, usually with some cover art as the "video". It would be ideal to allow a toggle on a per-item or per-file basis that switches on/off video playback for a video file in an audio library, in the rare case in which the video track is anything other than stationary cover art, so that we don't waste bandwidth playing back video unnecessarily.
This would be extremely helpful in organizing audio collections from r/GoneWildAudio. Why not implement the simplest support for audio files to be played as Audios? The rest would be identical to scenes with some minor differences.
If anyone is interested I've made a temporary solution:
- Add audio extensions in the library settings.
- Create an "Audio" tag.
- Make and "Audio" directory to put the files in.
- Use Auto Tag to tag the files inside the folder.
- Use custom css and js to modify or replace the player (like below).
Here are my custom css and js: css:
/* Audio: Hide player. */
.VideoPlayer.audio .vjs-big-play-button, .VideoPlayer.audio .scrubber-wrapper, .VideoPlayer.audio .vjs-fullscreen-control,
.VideoPlayer.audio .vjs-control-bar {
display: none !important;
}
js:
// Replace player
function setAudioElement(bool){
const pl = document.querySelector(".VideoPlayer")
var elm = document.getElementById('VideoJsPlayer_html5_api');
if (bool){
const videoElement = elm
pl.classList.add("audio")
var audioElement = document.createElement('audio');
for (var i = 0; i < videoElement.attributes.length; i++) {
audioElement.setAttribute(videoElement.attributes[i].name, videoElement.attributes[i].value);
}
audioElement.src = videoElement.src
audioElement.controls = true
audioElement.autoplay = false
videoElement.parentNode.replaceChild(audioElement, videoElement);
audioElement.parentNode.insertBefore(document.querySelector(".vjs-poster"), audioElement);
}else{
const audioElement = elm
pl.classList.remove("audio")
var videoElement = document.createElement('video');
for (var i = 0; i < audioElement.attributes.length; i++) {
videoElement.setAttribute(audioElement.attributes[i].name, audioElement.attributes[i].value);
}
videoElement.src = audioElement.src
videoElement.controls = false
audioElement.parentNode.replaceChild(videoElement, audioElement);
videoElement.parentNode.insertBefore(videoElement, document.querySelector(".vjs-poster"));
}
}
// Add "Audio Only" switch button
function audioswitch(){
const elm = document.querySelector(".scene-toolbar-group")
const btng = document.createElement("div")
const btn = document.createElement("button")
const pl = document.querySelector(".VideoPlayer")
if (pl.classList.contains("audio")) {
btn.style.background = "rgba(138,155,168,.15)"
}
btn.classList.add("minimal", "btn", "btn-secondary")
btn.innerHTML= "Audio Only"
btn.id = "audioSwitch"
btn.onclick = function (){
const pl = document.querySelector(".VideoPlayer")
const bt = document.getElementById("audioSwitch")
if (pl.classList.contains("audio")){
setAudioElement(false);
bt.style.background = "transparent"
} else {
setAudioElement(true);
bt.style.background = "rgba(138,155,168,.15)"
}
}
btng.classList.add("btn-group")
btng.appendChild(btn)
elm.appendChild(btng)
}
// Find audio tag and replace player
function findAudioTag() {
const links = document.querySelectorAll('.tag-item a');
if(links){
links.forEach(link => {
const divText = link.querySelector('div').textContent.trim();
if (divText === "Audio") {
setAudioElement(true)
}
});}
}
// Check if page is a scene page
function shouldRunOnPage() {
const currentPath = window.location.pathname;
const itemsPagePattern = /^\/scenes\/\d+(.*)/;
return itemsPagePattern.test(currentPath);
}
// Check if elements are rendered every 100ms then run the scripts
if (shouldRunOnPage()){
function checkRendered() {
const targetElement2 = document.querySelector('.VideoPlayer');
const targetElement3 = document.querySelector('#VideoJsPlayer_html5_api')?.src
const targetElement4 = document.querySelector('.scene-toolbar-group')
if (targetElement2 && targetElement3 && targetElement4) {
findAudioTag()
audioswitch()
clearInterval(intervalId);
}
}
const intervalId = setInterval(checkRendered, 100);
}
Here are my custom css and js:
@d0t-d0t-d0t I don't know what this code is supposed to do, but it doesn't work with stash version 0.27. No video/audio player replacement has been observed on audio files with the Audio
tag. Can you help resolve this issue?
Here are my custom css and js:
@d0t-d0t-d0t I don't know what this code is supposed to do, but it doesn't work with stash version 0.27. No video/audio player replacement has been observed on audio files with the
Audio
tag. Can you help resolve this issue?
You should try refreshing the page, the code isn't perfect.
This is the updated code:
function setAudioElement(bool){
const pl = document.querySelector(".VideoPlayer")
var elm = document.getElementById('VideoJsPlayer_html5_api');
if (bool){
const videoElement = elm
pl.classList.add("audio")
var audioElement = document.createElement('audio');
for (var i = 0; i < videoElement.attributes.length; i++) {
audioElement.setAttribute(videoElement.attributes[i].name, videoElement.attributes[i].value);
}
audioElement.src = videoElement.src.replace(/(^.*\/scene\/\d+\/)(stream.*)\?/, "$1stream?")
audioElement.controls = true
audioElement.autoplay = false
videoElement.parentNode.replaceChild(audioElement, videoElement);
audioElement.parentNode.insertBefore(document.querySelector(".vjs-poster"), audioElement);
}else{
const audioElement = elm
pl.classList.remove("audio")
var videoElement = document.createElement('video');
for (var i = 0; i < audioElement.attributes.length; i++) {
videoElement.setAttribute(audioElement.attributes[i].name, audioElement.attributes[i].value);
}
videoElement.src = audioElement.src
videoElement.controls = false
audioElement.parentNode.replaceChild(videoElement, audioElement);
videoElement.parentNode.insertBefore(videoElement, document.querySelector(".vjs-poster"));
}
}
function audioswitch(){
const elm = document.querySelector(".scene-toolbar-group")
const btng = document.createElement("div")
const btn = document.createElement("button")
const pl = document.querySelector(".VideoPlayer")
if (pl.classList.contains("audio")) {
btn.style.background = "rgba(138,155,168,.15)"
}
btn.classList.add("minimal", "btn", "btn-secondary")
btn.innerHTML= "Audio Only"
btn.id = "audioSwitch"
btn.onclick = function (){
const pl = document.querySelector(".VideoPlayer")
const bt = document.getElementById("audioSwitch")
if (pl.classList.contains("audio")){
setAudioElement(false);
bt.style.background = "transparent"
} else {
setAudioElement(true);
bt.style.background = "rgba(138,155,168,.15)"
}
}
btng.classList.add("btn-group")
btng.appendChild(btn)
elm.appendChild(btng)
}
function findAudioTag() {
// Select all <a> elements within the <span>
const links = document.querySelectorAll('.tag-item a');
if(links){
// Iterate through each <a> element
links.forEach(link => {
// Get the text content of the <div> inside the <a>
const divText = link.querySelector('div').textContent.trim();
// Check if the text matches the target text
if (divText === "Audio") {
setAudioElement(true)
}
});}
}
function shouldRunOnPage() {
const currentPath = window.location.pathname;
const itemsPagePattern = /^\/scenes\/\d+(.*)/;
return itemsPagePattern.test(currentPath);
}
if (shouldRunOnPage()){
function checkRendered() {
const targetElement2 = document.querySelector('.VideoPlayer');
const targetElement3 = document.querySelector('#VideoJsPlayer_html5_api')?.src
const targetElement4 = document.querySelector('.scene-toolbar-group')
if (targetElement2 && targetElement3 && targetElement4) {
findAudioTag()
audioswitch()
// Stop polling
clearInterval(intervalId);
}
}
const intervalId = setInterval(checkRendered, 100);
}
Remember to use this custom css:
/* Audio: Remove big play button (leave only the button in controls). */
.VideoPlayer.audio .vjs-big-play-button, .VideoPlayer.audio .scrubber-wrapper, .VideoPlayer.audio .vjs-fullscreen-control,
.VideoPlayer.audio .vjs-control-bar {
display: none !important;
}
/* Audio: Make the controlbar visible by default */
.VideoPlayer.audio .vjs-control-bar {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
}
/* Make player height minimum to the controls height so when we hide video/poster area the controls are displayed correctly. */
/*
.VideoPlayer.audio .video-js {
height: fill !important;
}
.VideoPlayer.audio .video-wrapper {
height: 6rem !important;
}
*/
@d0t-d0t-d0t I updated the code, and refreshed the page several times, I even cleared the browser cache and restarted the docker container several times. CCSS and custom javascript are activated in the settings but the code changes absolutely nothing to the reader!
@miltuss You should see something like this:
There should also be an 'Audio Only' toggle button
@d0t-d0t-d0t Unfortunately your script only works for you
@miltuss That's strange, you could try to copy the script and paste it in the browser console to see if it works, i was thinking of making a plugin (i don't know if plugins can use js to modify preexisting elements) if i had the time.
I aslo converted it in a simple user script:
// ==UserScript==
// @name Stash Audio Switch
// @version 0.1
// @description Add an audio-only switch to video player and apply custom CSS
// @match https://example.com/scenes/*
// @run-at document-idle
// @grant GM_addStyle
// ==/UserScript==
(function() {
'use strict';
GM_addStyle(`
.VideoPlayer.audio .vjs-big-play-button,
.VideoPlayer.audio .scrubber-wrapper,
.VideoPlayer.audio .vjs-fullscreen-control,
.VideoPlayer.audio .vjs-control-bar {
display: none !important;
}
`);
function setAudioElement(bool) {
const pl = document.querySelector(".VideoPlayer");
var elm = document.getElementById('VideoJsPlayer_html5_api');
if (bool) {
const videoElement = elm;
pl.classList.add("audio");
var audioElement = document.createElement('audio');
for (var i = 0; i < videoElement.attributes.length; i++) {
audioElement.setAttribute(videoElement.attributes[i].name, videoElement.attributes[i].value);
}
audioElement.src = videoElement.src.replace(/(^.*\/scene\/\d+\/)(stream.*)\?/, "$1stream?");
audioElement.controls = true;
audioElement.autoplay = false;
videoElement.parentNode.replaceChild(audioElement, videoElement);
audioElement.parentNode.insertBefore(document.querySelector(".vjs-poster"), audioElement);
} else {
const audioElement = elm;
pl.classList.remove("audio");
var videoElement = document.createElement('video');
for (var i = 0; i < audioElement.attributes.length; i++) {
videoElement.setAttribute(audioElement.attributes[i].name, audioElement.attributes[i].value);
}
videoElement.src = audioElement.src;
videoElement.controls = false;
audioElement.parentNode.replaceChild(videoElement, audioElement);
videoElement.parentNode.insertBefore(videoElement, document.querySelector(".vjs-poster"));
}
}
function audioswitch() {
const elm = document.querySelector(".scene-toolbar-group");
const btng = document.createElement("div");
const btn = document.createElement("button");
const pl = document.querySelector(".VideoPlayer");
if (pl.classList.contains("audio")) {
btn.style.background = "rgba(138,155,168,.15)";
}
btn.classList.add("minimal", "btn", "btn-secondary");
btn.innerHTML = "Audio Only";
btn.id = "audioSwitch";
btn.onclick = function() {
const pl = document.querySelector(".VideoPlayer");
const bt = document.getElementById("audioSwitch");
if (pl.classList.contains("audio")) {
setAudioElement(false);
bt.style.background = "transparent";
} else {
setAudioElement(true);
bt.style.background = "rgba(138,155,168,.15)";
}
};
btng.classList.add("btn-group");
btng.appendChild(btn);
elm.appendChild(btng);
}
function findAudioTag() {
const links = document.querySelectorAll('.tag-item a');
if (links) {
links.forEach(link => {
const divText = link.querySelector('div').textContent.trim();
if (divText === "Audio") {
setAudioElement(true);
}
});
}
}
function checkRendered() {
const targetElement2 = document.querySelector('.VideoPlayer');
const targetElement3 = document.querySelector('#VideoJsPlayer_html5_api')?.src;
const targetElement4 = document.querySelector('.scene-toolbar-group');
if (targetElement2 && targetElement3 && targetElement4) {
findAudioTag();
audioswitch();
clearInterval(intervalId);
}
}
const intervalId = setInterval(checkRendered, 100);
})();
@d0t-d0t-d0t I figured out what's wrong with the code, when we navigate to stash to open an audio file with the Audio Tag, the script doesn't run like this:
The script necessarily needs a refresh of the reader page to run.
In addition, markers do not work with the browser's built-in reader. So the method consisting of using the Stash player with the HLS transcoder remains the best while waiting for real integration of audio support in Stash.
@miltuss
I figured out what's wrong with the code, when we navigate to stash to open an audio file with the Audio Tag
I forgot to mention that you have to replace the match with the url of your stash instance.
In addition, markers do not work with the browser's built-in reader.
The code can be modified to use the stash player but i haven't tested it with audio files.
@d0t-d0t-d0t
I forgot to mention that you have to replace the match with the url of your stash instance.
I had inserted the URL of my instance instead of the default URL of the userscript from the first test
The code can be modified to use the stash player but i haven't tested it with audio files.
The player must read the audio correctly provided the HLS codec is selected. I don't know if it is possible to code a script that can automatically select the HSL on stash's Video.JS player when playing an audio file. I think this would be the best DIY to do while waiting for the official release of this feature!
I have created the audio-transcodes plugin for stash. This plugin converts an audio file to a video and stash will play this video instead allowing the normal video player to work. Install the plugin and run tasks > audio-transcodes > Process All
By doing this, your audio files will not be recognized by stash as audio tracks when integrating the AudioStoris functionality into Stash.
HLS allows you to play audio files in Stash, you just need to find a way to exclude other formats by default when playing an audio file, so that playback starts immediately on HLS and not manually switch to HLS.
Here are the updated script/css and userscript that use the original player and automatically change the transcoder to hls: https://gist.github.com/d0t-d0t-d0t/d061b10f12587a2d4e50c392d319a2f5
This would be better suited for a gist. Issue comments are not a good place for versioned code and take up an immense amount of vertical space
I've made it in to a plugin: https://github.com/d0t-d0t-d0t/StashAudioPlayer