Fixed issues with IA utils, continued work on playlist mgmt UI

This commit is contained in:
rainbow napkin 2025-04-01 08:47:34 -04:00
parent 3da88aea2a
commit f4db10fbc3
7 changed files with 142 additions and 56 deletions

View file

@ -46,6 +46,7 @@ playlistMediaProperties.pre('save', async function (next){
}); });
//methods //methods
//Rehydrate to a full phat media object
playlistMediaProperties.methods.rehydrate = function(){ playlistMediaProperties.methods.rehydrate = function(){
//Return item as a full phat, standard media object //Return item as a full phat, standard media object
return new media( return new media(
@ -58,4 +59,14 @@ playlistMediaProperties.methods.rehydrate = function(){
); );
} }
//Dehydrate to minified flat network-friendly object
playlistMediaProperties.methods.dehydrate = function(){
return {
title: this.title,
url: this.url,
duration: this.duration,
uuid: this.uuid.toString()
};
}
module.exports = mediaSchema.discriminator('saved', playlistMediaProperties); module.exports = mediaSchema.discriminator('saved', playlistMediaProperties);

View file

@ -41,11 +41,7 @@ playlistSchema.methods.dehydrate = function(){
//Fill media array //Fill media array
for(let media of this.media){ for(let media of this.media){
mediaArray.push({ mediaArray.push(media.dehydrate());
title: media.title,
url: media.url,
duration: media.duration
});
} }
//return dehydrated playlist //return dehydrated playlist

View file

@ -16,11 +16,13 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
//Node Imports //Node Imports
const url = require("node:url"); const url = require("node:url");
const validator = require('validator');
//Local Imports //Local Imports
const regexUtils = require('../regexUtils'); const regexUtils = require('../regexUtils');
const media = require('../../app/channel/media/media');
module.exports.fetchMetadata = async function(link){ module.exports.fetchMetadata = async function(link, title){
//Parse link //Parse link
const parsedLink = new url.URL(link); const parsedLink = new url.URL(link);
//Split link path //Split link path
@ -31,6 +33,10 @@ module.exports.fetchMetadata = async function(link){
splitPath.splice(0,3) splitPath.splice(0,3)
//Join remaining link path back together to get requested file path within the given archive.org upload //Join remaining link path back together to get requested file path within the given archive.org upload
const requestedPath = decodeURIComponent(splitPath.join('/')); const requestedPath = decodeURIComponent(splitPath.join('/'));
//Create empty list to hold media objects
const mediaList = [];
//Create empty variable to hold return data object
let data;
//Create metadata link from itemID //Create metadata link from itemID
const metadataLink = `https://archive.org/metadata/${itemID}`; const metadataLink = `https://archive.org/metadata/${itemID}`;
@ -55,23 +61,48 @@ module.exports.fetchMetadata = async function(link){
//Filter out any in-compatible files //Filter out any in-compatible files
const compatibleFiles = rawMetadata.files.filter(compatibilityFilter); const compatibleFiles = rawMetadata.files.filter(compatibilityFilter);
//If we're requesting an empty path //If we're requesting an empty path
if(requestedPath == ''){ if(requestedPath == ''){
//Return item metadata and compatible files //Return item metadata and compatible files
return { data = {
files: compatibleFiles, files: compatibleFiles,
metadata: rawMetadata.metadata metadata: rawMetadata.metadata
} }
//Other wise //Other wise
}else{ }else{
//Return item metadata and matching compatible files //Return item metadata and matching compatible files
return { data = {
//Filter files out that don't match requested path and return remaining list //Filter files out that don't match requested path and return remaining list
files: compatibleFiles.filter(pathFilter), files: compatibleFiles.filter(pathFilter),
metadata: rawMetadata.metadata metadata: rawMetadata.metadata
} }
} }
//for every compatible and relevant file returned from IA
for(let file of data.files){
//Split file path by directories
const path = file.name.split('/');
//pull filename from path and escape in-case someone put something nasty in there
const name = validator.escape(validator.trim(path[path.length - 1]));
//Construct link from pulled info
const link = `https://archive.org/download/${data.metadata.identifier}/${file.name}`;
//if we where handed a null title
if(title == null || title == ''){
//Create new media object from file info substituting filename for title
mediaList.push(new media(name, name, link, link, 'ia', Number(file.length)));
}else{
//Create new media object from file info
mediaList.push(new media(title, name, link, link, 'ia', Number(file.length)));
}
}
//return media object list
return mediaList;
function compatibilityFilter(file){ function compatibilityFilter(file){
//return true for all files that match for web-safe formats //return true for all files that match for web-safe formats
return file.format == "h.264 IA" || file.format == "h.264" || file.format == "Ogg Video" || file.format.match("MPEG4"); return file.format == "h.264 IA" || file.format == "h.264" || file.format == "Ogg Video" || file.format.match("MPEG4");

View file

@ -19,41 +19,17 @@ const validator = require('validator');//No express here, so regular validator i
//local import //local import
const iaUtil = require('./internetArchiveUtils'); const iaUtil = require('./internetArchiveUtils');
const media = require('../../app/channel/media/media');
module.exports.yankMedia = async function(url, title){ module.exports.yankMedia = async function(url, title){
//Get pull type
const pullType = await this.getMediaType(url); const pullType = await this.getMediaType(url);
if(pullType == 'ia'){ //Check pull type
//Create empty list to hold media objects switch(pullType){
const mediaList = []; case "ia":
//Pull metadata from IA //return media object list from IA module
const mediaInfo = await iaUtil.fetchMetadata(url); return await iaUtil.fetchMetadata(url, title);
default:
//for every compatible and relevant file returned from IA
for(let file of mediaInfo.files){
//Split file path by directories
const path = file.name.split('/');
//pull filename from path
const name = path[path.length - 1];
//Construct link from pulled info
const link = `https://archive.org/download/${mediaInfo.metadata.identifier}/${file.name}`;
//if we where handed a null title
if(title == null || title == ''){
//Create new media object from file info substituting filename for title
mediaList.push(new media(name, name, link, link, 'ia', Number(file.length)));
}else{
//Create new media object from file info
mediaList.push(new media(title, name, link, link, 'ia', Number(file.length)));
}
}
//return media object list
return mediaList;
}else{
//return null to signify a bad url //return null to signify a bad url
return null; return null;
} }
@ -69,7 +45,6 @@ module.exports.getMediaType = async function(url){
return null; return null;
} }
//If we have link to a resource from archive.org //If we have link to a resource from archive.org
if(url.match(/^https\:\/\/archive.org\//g)){ if(url.match(/^https\:\/\/archive.org\//g)){
//return internet archive code //return internet archive code

View file

@ -164,10 +164,7 @@ div.dragging-queue-entry{
.queue-playlist-span{ .queue-playlist-span{
justify-content: space-between; justify-content: space-between;
} padding: 0 0.2em;
.queue-playlist-div{
padding: 0 0.15em;
margin: 0 0.15em; margin: 0 0.15em;
} }
@ -182,10 +179,27 @@ div.dragging-queue-entry{
user-select: none; user-select: none;
} }
.queue-playlist-title-span{
text-wrap: nowrap;
display: flex;
flex-direction: row;
}
.queue-playlist-count{ .queue-playlist-count{
font-size: 0.8em; font-size: 0.8em;
} }
.queue-playlist-media-container-div{
resize: vertical;
overflow: scroll;
height: 5em;
}
.queue-playlist-media-container-div p{
margin: 0;
font-size: 0.8em;
}
#queue-create-playlist-popup-div{ #queue-create-playlist-popup-div{
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View file

@ -546,7 +546,13 @@ div.archived p{
border-block: var(--accent1) solid 1px; border-block: var(--accent1) solid 1px;
} }
.not-first-queue-playlist-div{
.queue-playlist-media-container-div{
background-color: var(--bg1-alt0);
border-block: var(--accent1) solid 1px;
}
.queue-playlist-span.not-first{
border-top: var(--bg1-alt0) solid 1px; border-top: var(--bg1-alt0) solid 1px;
} }

View file

@ -1103,24 +1103,27 @@ class playlistManager{
//Set playlist div dataset //Set playlist div dataset
playlistDiv.dataset.name = playlist.name; playlistDiv.dataset.name = playlist.name;
//If this isn't our first rodeo
if(playlistIndex != 0){
//make note
playlistDiv.classList.add('not-first-queue-playlist-div');
}
//Create span to hold playlist entry line contents //Create span to hold playlist entry line contents
const playlistSpan = document.createElement('span'); const playlistSpan = document.createElement('span');
//Set classes //Set classes
playlistSpan.classList.add('queue-playlist-span'); playlistSpan.classList.add('queue-playlist-span');
//If this isn't our first rodeo
if(playlistIndex != 0){
//make note
playlistSpan.classList.add('not-first');
}
//pre-render and keep this so we can use it later
const mediaContainer = renderMedia();
//Append items to playlist entry line //Append items to playlist entry line
playlistSpan.appendChild(renderLabels()); playlistSpan.appendChild(renderLabels());
playlistSpan.appendChild(renderControls()); playlistSpan.appendChild(renderControls());
//Append items to playlist div //Append items to playlist div
playlistDiv.appendChild(playlistSpan); playlistDiv.appendChild(playlistSpan);
playlistDiv.appendChild(renderMedia()); playlistDiv.appendChild(mediaContainer);
//Append current playlist div to the channel playlists div //Append current playlist div to the channel playlists div
this.channelPlaylistDiv.appendChild(playlistDiv); this.channelPlaylistDiv.appendChild(playlistDiv);
@ -1132,6 +1135,16 @@ class playlistManager{
//Set it's class //Set it's class
playlistLabels.classList.add('queue-playlist-labels-span'); playlistLabels.classList.add('queue-playlist-labels-span');
//create playlist title span
const playlistTitleSpan = document.createElement('span');
//Set class
playlistTitleSpan.classList.add('queue-playlist-title-span', 'interactive');
//Create playlist title caret
const playlistTitleCaret = document.createElement('i');
//Set class
playlistTitleCaret.classList.add('bi-caret-right-fill');
//Create playlist title label //Create playlist title label
const playlistTitle = document.createElement('p'); const playlistTitle = document.createElement('p');
//Set it's class //Set it's class
@ -1139,6 +1152,10 @@ class playlistManager{
//Unescape Sanatized Enteties and safely inject as plaintext //Unescape Sanatized Enteties and safely inject as plaintext
playlistTitle.innerText = utils.unescapeEntities(playlist.name); playlistTitle.innerText = utils.unescapeEntities(playlist.name);
//Construct playlist title span
playlistTitleSpan.appendChild(playlistTitleCaret);
playlistTitleSpan.appendChild(playlistTitle);
//Create playlist count label //Create playlist count label
const playlistCount = document.createElement('p'); const playlistCount = document.createElement('p');
//Set it's class //Set it's class
@ -1147,11 +1164,33 @@ class playlistManager{
playlistCount.innerText = `Count: ${playlist.media.length}`; playlistCount.innerText = `Count: ${playlist.media.length}`;
//Append items to playlist labels span //Append items to playlist labels span
playlistLabels.appendChild(playlistTitle); playlistLabels.appendChild(playlistTitleSpan);
playlistLabels.appendChild(playlistCount); playlistLabels.appendChild(playlistCount);
//Define input listeners
playlistTitleSpan.addEventListener('click', toggleMedia.bind(this));
//return playlistLabels //return playlistLabels
return playlistLabels; return playlistLabels;
function toggleMedia(){
//If the div is hidden
if(mediaContainer.style.display == 'none'){
//Light up the button
playlistTitleSpan.classList.add('positive');
//Flip the caret
playlistTitleCaret.classList.replace('bi-caret-right-fill', 'bi-caret-down-fill');
//Show the div
mediaContainer.style.display = '';
}else{
//Unlight the button
playlistTitleSpan.classList.remove('positive');
//Flip the caret
playlistTitleCaret.classList.replace('bi-caret-down-fill', 'bi-caret-right-fill');
//Hide the div
mediaContainer.style.display = 'none';
}
}
} }
function renderControls(){ function renderControls(){
@ -1195,14 +1234,28 @@ class playlistManager{
//Create media container div //Create media container div
const mediaContainer = document.createElement('div'); const mediaContainer = document.createElement('div');
//Set classes //Set classes
mediaContainer.classList.add('queue-playlist-media-div'); mediaContainer.classList.add('queue-playlist-media-container-div');
//Auto-hide media container
mediaContainer.style.display = 'none';
for(let media of playlist.media){ for(let media of playlist.media){
//Create media div
const mediaDiv = document.createElement('div');
//Set class
mediaDiv.classList.add('queue-playlist-media-div');
//Create media title //Create media title
const mediaTitle = document.createElement('p'); const mediaTitle = document.createElement('p');
//Set class //Set class
mediaTitle.classList.add('queue-playlist-media-title'); mediaTitle.classList.add('queue-playlist-media-title');
//Inject text content
mediaTitle.innerText = utils.unescapeEntities(media.title);
//Append items to media div
mediaDiv.appendChild(mediaTitle);
//Append media div to media container
mediaContainer.appendChild(mediaDiv);
} }
//return media container //return media container