';
jNode.closest(".audio-stream-box").append( mdbPlayerAndToolkit );
jNode.hide();
}
if( playerUrl ) {
// toolkit output
waitForKeyElements(".mdb-player-audiostream:not(.mdb-processed-toolkit)", function( jNode ) {
logVar( "titleText toolkit", titleText );
getToolkit( playerUrl, "playerUrl", "detail page", jNode, "after", titleText, "link", 1, "", "auto" );
jNode.addClass("mdb-processed-toolkit");
});
}
}
// embed player
waitForKeyElements(".request-summary img.artwork", function( jNode ) {
var playerUrl = jNode.closest("a").attr("href"),
heading = $(".MuiGrid-root h1"),
titleText = normalizeTitleForSearch( heading.text() );
logVar( "playerUrl (in artwork as given)", playerUrl );
// Remove dots from hearthis slugs (So marking as integrated works)
// https://trackid.net/audiostreams/subfader-house
// > https://hearthis.at/subfader/h.o.u.s.e./ >> https://hearthis.at/subfader/house/
var playerUrl_domain = getDomain_fromUrlStr( playerUrl );
logVar( "playerUrl_domain", playerUrl_domain );
if( playerUrl_domain == "hearthis.at" ) {
playerUrl = playerUrl.removeDotsFromUrlSlug();
logVar( "playerUrl (after removeDotsFromUrlSlug() for hearthis.at)", playerUrl );
// change URL in artwork as well
jNode.closest("a").attr("href", playerUrl);
}
if( url != "" ) {
funcTidPlayers( jNode, playerUrl, titleText );
}
});
/*
* Compare page creation date to MixesDB last edit date
* only on positive usage results
*/
waitForKeyElements(".mdb-mixesdbLink.lastEdit", function( jNode ) {
var pageCreationTimestamp = $(".audio-stream-box > div > div > .MuiBox-root:nth-of-type(5) div + div p.MuiTypography-body1").text()
.trim()
// d/M/yyyy
// 1/3/2025, 9:54:50 AM
.replace(/ (AM|PM)$/i, "" )
.replace(/(\d+)\/(\d+)\/(\d+), (\d+:\d+:\d+)$/, "$3-$1-$2T$4Z" )
// m+d.M.yyyy
// 21.1.2025, 13:05:04
.replace(/(\d+)\.(\d+)\.(\d+), (\d+:\d+:\d+)$/, "$3-$2-$1T$4Z" ) // 18.1.2025, 10:43:21
// pad M-d
// 2025-1-3T9:54:50Z
.replace(/(\d{4})-(\d)-/, "$1-0$2-" )
.replace(/(\d{4})-(\d{2})-(\d)T/, "$1-$2-0$3T" )
;
var lastEditTimestamp = jNode.attr("data-lastedittimestamp"); // 2025-01-28T20:26:13Z
pageCreated_vs_lastEdit( pageCreationTimestamp, lastEditTimestamp );
});
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Tables
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*
* Tracklist table
* via table .mdb-tid-table
*/
// waitForKeyElements
waitForKeyElements(".mdb-tid-table:not('.tlEditor-processed')", function( jNode ) {
logFunc( "funcTidTracklist" );
var tlWrapper = jNode;
tlWrapper.addClass("tlEditor-processed");
// hide banner
$(".MuiAlert-root.MuiAlert-standardInfo").hide();
$(".MuiGrid-container.request-summary").css("margin-top", "0");
var heading = $(".MuiGrid-container .MuiGrid-grid-xs-12 p.MuiTypography-body1").first(),
mixTitle = heading.text(),
totalDur = $("p.MuiTypography-body1:contains('Duration')").closest("div").next(".MuiGrid-item").text(),
totalDur_Sec = durToSec(totalDur);
log(mixTitle);
logVar( "totalDur", totalDur);
// iterate
var tl = "",
li = $("tr", tlWrapper),
i = 1;
logVar("li.length", li.length);
li.each(function () {
var thisTrack = "";
var thisTitle = $(".title", this).text().replace(/\s*\n\s*/g, ' ').trim();
logVar( "title before replacing", thisTitle );
var artist = $(".artist", this).text()
.replace(/\s*\n\s*/g, ' ') // https://trackid.net/audiostreams/nature-one-2024-opening-gayphoriastage
.replace(/([A-Z0-9]),([A-Z0-9])/i, "$1, $2") // https://trackid.net/audiostreams/calvo-at-nature-one-2o17-we-call-it-home
.removeDuplicateNames()
.fixTidArtistnames()
,
title = thisTitle
.replace(")[", ") [") // normalize ")[" in title for futther treatment (removal) https://trackid.net/audiostreams/subfader-subfreaquence-house-tech-house-20100208
.replace(/\s*-\s*(?:feat(?:\.|uring)?)\s+(.+?)(?=$|\s*\()/i, ' (featuring $1)') // Scared Of My Heart - featuring E.R. Thorpe (Andre Lodemann Remix) https://trackid.net/audiostreams/balance-selections-215-james-harcourt#google_vignette
.replace(/ \(\d+ - Remaster\)$/, "") // Foo (Nutt Mix - Remastered 2021)
.replace(/^(.+) [\(\[](.+) - (?:\d+ )?Remaster(?:ed|ing)?(?: \d+)?[\)\]]/g, "$1 ($2)") // All Night (I Can Do It Right) (2016 - Remaster) https://trackid.net/audiostreams/subfader-the-ghetto-funk-show-20090216-mix-2 | Run before below stuff
.replace(/\s*[\(\[][^\(\[\)]*(Digital\s+Remaster|Mastering)[^\)\]]*[\)\]]/gi, "") // Title (2002 Digital Remaster / 24-Bit Mastering) https://trackid.net/audiostreams/dr-packer-live-in-ibiza-august-2025-hard-rock-hotel
.replace(/^(.+) [\(\[]Re-?master(ed|ing|is[ée])?( En)?(?:\s'?\d{2,4})?[\)\]]/gi, "$1") // (Remasterisé En 2002), also [Remaster] https://trackid.net/audiostreams/xmix3-1994-richie-hawtin-john-acquaviva-enter-the-digital-reality
.replace(/(.+) - (.+ (?:Remix|Mix|Version))/g, "$1 ($2)")
.replace(/^\((.+)\)$/g, "$1") // avoid "[000] Inland [Systemscan]" https://trackid.net/audiostreams/shed-josey-rebelle-sven-von-thulen-txl-berlin-recordings-chapter-7-arte-concert
.replace(/^(.+)-\d{4,5}$/g, "$1") // numbers as suffix, e.g. "Track Title-24070" https://trackid.net/audiostreams/alex-kvitta-sonderspur-pod-011281213
.replace(/^(.+) - (.+)$/g, "$1 ($2)") // "Track Title - Some Version" https://trackid.net/audiostreams/dj-hell-mayday-1999-soundtropolis
.replace(/(.+) \((\d+ )?Remaster(ed|ing)?( \d+)?\)$/g, "$1") // "Track Title - (Remaster)" etc
.replace(/(.+) \((\d+ )?([A-Za-z]+ )?(\s*Re-?master(ed|ing|;)?)(\s*(Mix|Version|Edition))?\)$/gi, "$1") // "Track Title - (2013 Japan Remaster; Remastered)"
.replace(/\s+\(Mixed\)/i, "") // remove " (Mixed)" https://trackid.net/audiostreams/balance-selections-234-sinca
.replace(/\((.+);Mixed\)/i, "($1)") // remove "(Versionx;Mixed)" https://trackid.net/audiostreams/itps064-iron-curtis
.replace(/\s*\((?=Original)[^()]*?(?:\([^()]*\)[^()]*)*\)/gi, "") // (Original Mix (Digital Only)) and variants https://trackid.net/audiostreams/sirarthur-chris-liebing-umek-gayle-san-live-u60311-19991105-1of9
;
let label = "";
let $label = $(".label", this);
if ($label.length && $label.text().trim() !== "") {
logVar( "label before fixing", $label.text() );
label = $label.text()
.replace(/\s*\n\s*/g, " ")
.replace("Records (Distribution)", "Records")
.replace(/[\[\]]/g, "")
.fixTidLabelnames()
.removeMajorLabels()
.removeArtistLabels(artist)
;
logVar( "label after fixing", label );
}
var startTime = $(".startTime", this).text(),
startTime_Sec = durToSec(startTime),
endTime = $(".endTime", this).text(),
endTime_Sec = durToSec(endTime),
previousTrack = $(".MuiDataGrid-row:eq(" + (i - 2) + ")"), // eq starts at 0
endTimePrevious = $(".MuiDataGrid-cell[data-field='endTime']", previousTrack).text(),
endTimePrevious_Sec = durToSec(endTimePrevious),
nextTrack = $(".MuiDataGrid-row:eq(" + (i) + ")"), // eq starts at 0
startTimeNext = $(".MuiDataGrid-cell[data-field='startTime']", nextTrack).text(),
startTimeNext_Sec = durToSec(startTimeNext);
artist = stripCountryCodes( artist );
title = removePointlessVersions( title );
title = removeDuplicateBracketedText( title );
//logVar( "artist", artist );
//logVar( "title", title );
// remove label when its actually the artist repeated
if (label == artist) {
label = "";
}
// dur
if (startTime !== "") {
// first track is gap?
if (i == 1) {
// start tl with gap when first dur is larger than 120(?)
if (startTime_Sec > 120) {
tl += '[0:00:00] ?\n...\n';
}
}
thisTrack += '[' + startTime + '] ';
}
// catch title (feat. artist2) and add to artist
// should actually be catched by TLE (issue#525)
var match = title.match(/\s*\(feat\. [^)]+\)/i);
if (match) {
var featPart = match[0].trim(); // e.g., "(feat. Foo)"
title = title.replace(match[0], '').trim();
artist += ' ' + featPart;
}
// artist - title
if (artist && title !== "") {
var artist_title = artist + ' - ' + title;
// last checks
// if found track is title of Mix CD
// https://trackid.net/audiostreams/groove-podcast-481-bossy-doll-bina
if (/\(Continuous DJ Mix\)\s*$/i.test(title)) {
artist_title = "?";
label = "";
}
thisTrack += artist_title;
}
// fixes on "Artist - Title"
thisTrack = thisTrack.removeDuplicatedVersionArtist();
// label
if (label !== "") {
thisTrack += ' [' + label + ']';
}
tl += thisTrack;
//logVar( "thisTrack", thisTrack );
// gaps
// add "..." row if gap is too laarge
if( !$(this).is(':last-child') ) {
// not last track
var gapSec = startTimeNext_Sec - endTime_Sec;
//log( "-------------------------------" );
//log( "> startTime: " + startTime );
//log( "> startTime_Sec: " + startTime_Sec );
//log( "> endTime: " + endTime );
//log( "> next startTime: " + startTimeNext );
//log( "> gapSec: " + gapSec );
// TID end times sometimes before start time
// https://trackid.net/audiostreams/b5096745-56ad-4d4d-af61-8d16e32e0521
// don't create next "[dur] ?" tracks then
if( endTime_Sec < startTime_Sec ) {
log( "Negative gapSec!" );
tl += "\n...";
} else {
if( gapSec > 30 ) {
tl += "\n[" + endTime + "] ?";
if (gapSec > 180) {
tl += "\n...";
}
}
}
tl += "\n";
} else {
// last track
//log( "> last track" );
//log( "> lastTrack_gap: " + lastTrack_gap );
var lastTrack_gap = totalDur_Sec - endTime_Sec;
//log( "> lastTrack_gap: " + lastTrack_gap );
// is the last track close to end or possible gap?
if (lastTrack_gap > 70) {
tl += "\n[" + endTime + "] ?";
}
if (lastTrack_gap > 240) {
tl += "\n...";
}
}
i++;
});
// API
tl = tl.trim();
log("tl before API:\n" + tl);
if (tl !== "") {
var res = apiTracklist( tl, "trackidNet" ),
tlApi = res.text;
log( 'tlApi ("trackidNet"):\n' + tlApi );
if( tlApi ) {
var tl_arr = make_tlArr( tlApi ),
tl_arr_fixedCues = tidMarkFalseCues( addCueDiffs( tl_arr ) ),
tl_arr_noDupes = removeAdjacentDuplicateTracks( tl_arr_fixedCues ),
tl_fixedCues = arr_toTlText( tl_arr_noDupes );
log( "tl_fixedCues:\n" + tl_fixedCues );
var res_fixedCues = apiTracklist( tl_fixedCues, "trackidNet" ),
tlApi_fixedCues = res_fixedCues.text;
if( tlApi_fixedCues ) {
tlWrapper.before( ta );
$("#mixesdb-TLbox").addClass("mixesdb-TLbox")
.val( tlApi_fixedCues )
.attr( "data-tlcandidate", tlApi );
fixTLbox( res.feedback );
}
if( tlApi.split("\n").length != tlApi_fixedCues.split("\n").length ) {
var info_cuesRemoved = '
Possibly false "?" tracks have been removed due to short cue differences.';
info_cuesRemoved += ' ';
//info_cuesRemoved += ' Max gap: minutes';
info_cuesRemoved += '
';
$("#tlEditor-feedback-topInfo").prepend( info_cuesRemoved );
}
// fix CSS
$("#tlEditor").parent().parent().css("display","block");
}
} else {
log("tl empty");
}
outputTidGenresTextarea();
});
function outputTidGenresTextarea() {
if (urlPath_noParams(1) != "audiostreams") return;
var styleRenameMap = {
"Drum n Bass": "Drum & Bass"
},
tidStyles = [];
var stylesRow = $("p.MuiTypography-body1").filter(function() {
return $(this).text().trim() == "Styles";
}).first().closest(".MuiBox-root");
if (!stylesRow.length) return;
$("a.link", stylesRow).each(function() {
var styleName = $(this).text().trim().replace(/,\s*$/, "");
if (styleName) {
tidStyles.push(styleName);
}
});
$("#mixesdb-TIDstylesWrapper").remove();
if (tidStyles.length < 1) return;
if (!$("#tlEditor").length || !$(".mdb-tid-table").length) return;
var tidStylesOutput = [];
$.each(tidStyles, function(i, styleName) {
var outputStyleName = styleRenameMap[styleName] || styleName;
tidStylesOutput.push("[[Category:" + outputStyleName + "]]");
});
var output = tidStylesOutput.join("\n");
$("#tlEditor").after('
Style suggestions
Please double-check by skipping through the mix manually.
');
$("#mixesdb-TIDstyles").val(output);
}
function toggleTracklistTextareaCueFormat() {
var ta = $("textarea.mixesdb-TLbox");
if (!ta.length) return;
function selectTracklistTextarea() {
ta.focus().select();
}
function detectCueFormatFromTextarea(text) {
var m = String(text || "").match(/^\s*\[\s*([0-9\?:]+)\s*\]/m);
if (!m) return "MM";
var cue = String(m[1] || "");
if (cue.indexOf(":") >= 0) return "HMM";
return "MM";
}
var currentFormat = ta.attr("data-mdb-cue-format") || detectCueFormatFromTextarea(ta.val());
var nextFormat = currentFormat === "MM" ? "HMM" : "MM";
if (currentFormat === "MM") {
ta.attr("data-mdb-cue-original", ta.val() || "");
}
if (nextFormat === "MM") {
var originalTracklist = ta.attr("data-mdb-cue-original");
if (typeof originalTracklist !== "undefined") {
ta.val(originalTracklist);
ta.attr("data-mdb-cue-format", nextFormat);
var buttonLabel_mm = "Switch cue format (mmm > h:m)";
$("#switchCueFormat").text(buttonLabel_mm);
selectTracklistTextarea();
return;
}
}
function padTrackIdMinutesCue(cue) {
var s = String(cue || "").trim();
if (!/^[0-9\?]{2}$/.test(s)) return s;
return "0" + s;
}
var lines = String(ta.val() || "").split("\n");
var convertedLines = lines.map(function (line) {
return line.replace(/^\s*\[\s*([0-9\?:]+)\s*\]/, function (m, cue) {
var switchedCue = toggleCue_MM_HMM(cue);
// TrackId exports mmm-style cues. Keep left-padding when converting
// from h:mm back to minutes-only so 0:59 becomes 059.
if (nextFormat === "MM") {
switchedCue = padTrackIdMinutesCue(switchedCue);
}
return "[" + switchedCue + "]";
});
});
ta.val(convertedLines.join("\n"));
ta.attr("data-mdb-cue-format", nextFormat);
var buttonLabel = nextFormat === "HMM" ? "Switch cue format (h:m > mmm)" : "Switch cue format (mmm > h:m)";
$("#switchCueFormat").text(buttonLabel);
selectTracklistTextarea();
}
if( visitDomain == "trackid.net" ) {
waitForKeyElements("ul#tlEditor-feedback-topInfo", function(jNode) {
if (!$("#switchCueFormat").length) {
jNode.prepend('');
}
});
}
$(document).on("click", "#switchCueFormat", function(e) {
e.preventDefault();
toggleTracklistTextareaCueFormat();
});
// toggleTlCandidate
waitForKeyElements("#toggleTlCandidate", function( jNode ) {
jNode.click(function() {
logFunc( "toggleTlCandidate" );
var ta = $("textarea.mixesdb-TLbox"),
ta_rows = ta.attr("rows"),
tl_orig = ta.val(),
tl_candidate = ta.attr("data-tlcandidate");
logVar( "tl_orig", tl_orig );
logVar( "tl_candidate", tl_candidate );
if( tl_candidate ) {
var tl_candidate_rows = tl_candidate.split("\n").length;
ta.val( tl_candidate )
.attr( "data-tlcandidate", tl_orig );
//$("#select_tidminGap_wrapper").show();
if( ta_rows < tl_candidate_rows ) {
ta.attr( "rows", tl_candidate_rows );
}
}
});
});
/*
* Fix ugly grid layout to proper tables
*/
var skipReplacingTables = false;
if( urlPath_noParams(1) == "submiturl" ) {
skipReplacingTables = true;
}
// waitForKeyElements
if( !skipReplacingTables ) {
waitForKeyElements(".MuiDataGrid-virtualScrollerRenderZone:not(.processed)", function( jNode ) {
jNode.addClass("processed");
setTimeout(function () {
funcTidTables( jNode.closest(".MuiDataGrid-main") );
}, timeoutDelay);
});
$(".MuiDataGrid-virtualScrollerRenderZone .MuiDataGrid-cell:not(.processed)").on("change", function() {
jNode.addClass("processed");
setTimeout(function () {
funcTidTables( $(this).closest(".MuiDataGrid-main") );
}, timeoutDelay);
});
}
// funcTidTables
function funcTidTables(jNode) {
logFunc( "funcTidTables" );
$(".mdb-tid-table").remove();
var audiostreams = [],
heading = $(".MuiGrid-grid-xs-12 p.MuiTypography-body1"),
grid = $(".data-grid", jNode).add(".MuiDataGrid-root");
if (grid.length == 1 && grid.is(":visible")) {
var tableClass = heading.text()
.replace(/[<>]+/g,"") // sane https://trackid.net/audiostreams/mr-c-at-mw-at-club-o-dance-theatre-den-haag-nl-25-february-2000
.replace(/ /g, "")
,
path = location.pathname.replace(/^\//, "");
grid.before('
');
var tbody = $(".mdb-tid-table tbody");
$(".MuiDataGrid-columnHeader", grid).each(function () {
var text = $(this).text().replace(/ /g, "").replace("CreatedOn", "Created on").replace("RequestedOn", "Requested on").replace("RequestedBy", "Requested by"),
textId = $(this).attr("data-field");
if (textId == "#") textId = "Index";
if (textId) {
tbody.append('
' + text + '
');
}
});
tbody.append('
MixesDB integration
');
$(".MuiDataGrid-row").each(function () {
//log("get urls" + $(this).html());
var rowId = $(this).attr("data-id"),
listItemLink = $(".MuiDataGrid-cell--textLeft[data-colindex=2] a.white-link", this);
if (typeof listItemLink.attr("href") !== "undefined") {
var rowUrl = listItemLink.attr("href").replace(/^\//, ""),
urlSplit = rowUrl.split("/"),
urlType = urlSplit[0].replace(/s$/, ""), // musictrack or audiostream
urlValue = urlSplit[1];
}
if (urlValue) {
switch (urlType) {
case "audiostream":
audiostreams.push(urlValue);
break;
}
}
// each gridd cell
tbody.append('