ONO::ToolBox::Audio

package ONO::ToolBox::Audio;
################################################################################
# COPYRIGHT / LICENSE #
################################################################################
#
# This file is part of the ONO Software Project.
#
# Copyright (C) 2000-2025 Jos KIRPS [ www.kirps.com | jos_AT_kirps_DOT_com ]
# and The Joopita Project [ www.joopita.org | contact_AT_joopita_DOT_com ]
#
# This file, as well as other parts of the ONO Software Project or related
# elements, are FREE SOFTWARE available under the ARTISTIC LICENSE 2.0.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# For the full license, see /ono/osr/license/LICENSE.txt, or write to
# jos_AT_kirps_DOT_com or contact_AT_joopita_DOT_com.
#
################################################################################
# END OF COPYRIGHT / LICENSE, HERE COMES THE CODE ... #
################################################################################


use strict;

use ONO::Lib::Basic;
use ONO::IO;

use ONO::Lib::Audio::Object;
use ONO::Lib::Audio::ToolBox;

use ONO::Lib::UI::MSG;
use ONO::Lib::UI::Page;

###############################################################################
# ONO
###############################################################################

sub player {

my (
$self,
$COVER,
$TITLE,
$ARTIST,
$INFO,
$DIR,
$tracks_ref,
$switches,
$script_url,
$vars_ref,
) = @_;

#: This is the build-in ONO audio player that will allow to select and
#: listen to different audio tracks. Note that this is great for music
#: albums and directories - if you'd like to play a single audio / MP3
#: file, then please check the audio player in ONO_Lib_Audio_Object.
#:
#: -C automatic cover generation using ffmpeg
#: -p page nav enabled (requires $script_url + $vars_ref)
#: -R random mode (requires $script_url + $vars_ref)

$TITLE = ONO::Lib::Basic->ucfirstletters_mkspaces($TITLE);
$ARTIST = ONO::Lib::Basic->ucfirstletters_mkspaces($ARTIST);

if (ONO::IO->image($COVER)) {
$COVER = qq~<img class="block radius5 auto" src="$COVER" style="width:100%;height:auto;max-width:320px" alt="$ARTIST - $COVER">~;
} else {
$COVER = "";
}

my ($TRACKS,$RANDOM_JS,$RANDOM_JS_NAMES,$total,$counter,%vars,$DEBUG);

my $max_per_page = 50;
my @tracks = @$tracks_ref;

if ($switches =~ /(p|R)/ && $vars_ref) {
%vars = %$vars_ref;

if ($vars{'player_random'}) {
$max_per_page = 1000;
my $num = @$tracks_ref;
$vars{'player_random'} = int(rand($num));
}

}

# track name cleaner - part 1/2 - build the test strings

my ($TrackCleaner,$TrackCleanerMode,$TrackCleanerFirst,@TrackCleanerData,$TrackCleanerUse);

foreach my $track (@tracks) {
if ($track !~ /^\./ && $track =~ /\.(mp3|m4a|wav|flac|ogg)$/i) {
if (!$TrackCleanerFirst) {
$TrackCleanerFirst = $track;
my @tp = split(/ /,$TrackCleanerFirst);
$TrackCleanerMode = " ";
if ($TrackCleanerFirst !~ / / && $TrackCleanerFirst =~ /_/) {
@tp = split(/_/,$TrackCleanerFirst);
$TrackCleanerMode = "_";
}
my $num = @tp;
for (my $i = 0; $i < $num; $i++) {
if ($tp[$i]) {
for (my $j = 0; $j < $i; $j++) {
$TrackCleanerData[$i] .= "$tp[$j]$TrackCleanerMode";
}
}
}
}
}
}

# track name cleaner - part 2/2 - find the best match

foreach my $TrackCleanerString (reverse @TrackCleanerData) {

my $discard;

if (!$TrackCleanerUse && $TrackCleanerString !~ /(\(|\))/) {
foreach my $track (@tracks) {
if ($track !~ /^\./ && $track =~ /\.(mp3|m4a|wav|flac|ogg)$/i) {
if ($track !~ /^$TrackCleanerString/) {
$discard++;
}
}
}
}

if (!$TrackCleanerUse && !$discard) {
$TrackCleanerUse = $TrackCleanerString;
}

}

# display tracks

foreach my $track (@tracks) {

if ($track !~ /^\./ && $track =~ /\.(mp3|m4a|wav|flac|ogg)$/i) {

$total++;

if ($total == 1 && !$COVER && $switches =~ /C/) {
ONO::Lib::Audio::ToolBox->extract_cover_image("$DIR/$track");
}

if ($vars{'player_random'}) {
$RANDOM_JS .= qq~"$DIR/$track",~;
my $TrackName = &track_name("",$track);
$RANDOM_JS_NAMES .= qq~"$TrackName",~;
}

if ($counter < $max_per_page && $total > ($vars{'player_page'}-1)*$max_per_page && (!$vars{'player_random'} || $total == $vars{'player_random'})) {

$counter++;

my $next = $counter+1;

my $TrackName = &track_name("",$track,$TrackCleanerUse);

my $SCRIPT = qq~document.getElementById('audio_player_$counter').onended = function() {
if (onojs_exists('audio_player_$next')) {
document.getElementById('audio_player_$next').play();
} else {
document.getElementById('audio_player_1').play();
}
}
~;

my $pswitches;

if ($vars{'player_random'}) {

# this is being blocked by most modern browsers now, we'll leave it in place anyway ($SCRIPT needs to be reset anyway):

$SCRIPT = qq~document.getElementById('audio_player_$counter').onloadeddata = function() {document.getElementById('audio_player_$counter').play()}~;
$pswitches = "a";
}

my $PLAYER = ONO::Lib::Audio::Object->player("audio_player_$counter","$DIR/$track","L$pswitches");

$TRACKS .= qq~<tr>
<td>
<div class="pad10_2 radius5" style="background-color:#111111">
<div id="audio_player_trackname_$counter" class="p2 cole"><span class="col9">$total)</span> $TrackName</div>
<div class="p2">$PLAYER</div>
</div>
<script>$SCRIPT</script>
</td>
</tr>
~;

}

}

}

if ($TRACKS) {

my $SCRIPT = ONO::Lib::Audio::Object->player_script("S");

my ($FR,$NAV);

if ($total > $max_per_page && $switches =~ /p/) {

$NAV .= qq~<div class="mt5">~;
$NAV .= ONO::Lib::UI::Page->nav(
1,
$vars{'player_page'},
int($total/$max_per_page)+1,
qq~$script_url&player_page=~,
);
$NAV .= qq~</div>~;

}

if ($switches =~ /p/) {
$FR .= qq~<a href="$script_url&player_random=1" class="button fr">random</a>~;
}

if ($RANDOM_JS) {
$RANDOM_JS =~ s~,$~~;
$RANDOM_JS_NAMES =~ s~,$~~;
$RANDOM_JS = qq~<script>
var ono_player_tracks = [$RANDOM_JS];
var ono_player_names = [$RANDOM_JS_NAMES];
document.getElementById('audio_player_1').onended = function() {
var rand = Math.floor(Math.random()*ono_player_tracks.length);
document.getElementById('audio_player_1').src=ono_player_tracks[rand];
onojs_sethtml('audio_player_trackname_1',ono_player_names[rand]);
document.getElementById('audio_player_1').play();
}
</script>
~;
}

return qq~$DEBUG$SCRIPT
<div class="box_black auto" style="background-color:#222222;max-width:1200px">
<div class="inline w100">
<div class="w20 fl w100_1000 fn_1000">
<div class="p10">$COVER</div>
</div>
<div class="w80 fl w100_1000 fn_1000">
<div class="p20 p5_1000" style="padding-top:10px">
$FR
<h1 class="m0" style="color:#eeeeee">$TITLE</h1>
<h2 style="color:#aaaaaa">$ARTIST</h2>
<div class="box_grey p5_1000">
<table class="wide_table">
$TRACKS
</table>
$NAV
$RANDOM_JS
</div>
</div>
</div>
</div>
</div>
~;

} else {

return "";

}

}

sub track_name {

#: This will clean up a track name and make it human-readable.

my $Track = $_[1];
my $TrackCleanerUse = $_[2];

if ($TrackCleanerUse && $TrackCleanerUse !~ /(\(|\))/) {
$Track =~ s~^$TrackCleanerUse~~;
}

$Track =~ s~\.(mp3|m4a|wav|flac|ogg)$~~;
$Track =~ s~_~ ~g;
$Track =~ s~-~ - ~g;

$Track = ONO::Lib::Basic->remove_bad_spaces($Track);

my ($Track2,$pcount);
foreach my $part (split(/ /,$Track)) {
$pcount++;
if ($pcount > 1 || $part =~ /[A-Za-z]/) {
$Track2 .= ucfirst "$part ";
}
}
$Track2 =~ s~^- ~~;

return $Track2;

}

###############################################################################
# end of script
###############################################################################

1;

__END__