president/Music.pl

626 lines
20 KiB
Perl
Executable File

#!/usr/bin/perl
use Mojolicious::Lite -signatures;
use Mojolicious::Static;
use Mojo::Log;
use Mojo::File;
use Mojo::JSON qw(decode_json encode_json);
use Encode qw(encode decode);
use List::Util qw(shuffle);
use Data::Dumper;
use Data::UUID;
use Mojo::Util qw/term_escape html_unescape url_unescape url_escape/;
use File::Find;
use File::Slurp;
plugin 'RenderFile';
use LWP::UserAgent;
use Mojolicious::Sessions;
use URI::Encode qw(uri_encode uri_decode);
use MIME::Base64;
use strict;
my $com = 'echo $HOME';
my $cwd = `$com`;
chomp $cwd;
require "./subroutines.pl";
my $device = &subs::device_setter();
my $config_file = read_file('./config.json');
my $config = decode_json $config_file;
my $environment = $config->{'environment'};
my @folders = @{$config->{'folders'}};
our $logfile = &subs::home($config->{'logfile'});
`touch $logfile` if not -e $logfile;
our $log = Mojo::Log->new(path => $logfile);
my (@results);
my $files = [];
my $thumb_files = [];
my $permissive = 0;
sub process_file() {
my $name = $File::Find::name;
if ($name =~ m/mp3$|m4a$|wav$|flac$|mp4$|mov$|webm$|weba$|wmv$/i) {
$name =~ s/^\.\///g;
push @{$files}, $name;
}
if ($permissive == 1) {
if ($name =~ m/mp3\.enc$|m4a\.enc$|wav\.enc$|flac\.enc$|mp4\.enc$|mov\.enc$|webm\.enc$|weba\.enc$|wmv\.enc$/i) {
$name =~ s/^\.\///g;
push @{$files}, $name;
}
}
}
sub process_thumbfile() {
my $name = $File::Find::name;
if ($name =~ m/jpg$|png$/i) {
$name =~ s/^\.\///g;
push @{$thumb_files}, $name;
}
}
get '/manager/music' => sub ($c) {
my $search = $c->param('search') || &subs::setting_grabber({ app => 'music', 'setting' => 'search' });
my $window_maker = $c->param('window_maker');
my $unlock = &subs::setting_grabber({ app => 'music', setting => 'combo_unlock' });
my $port = $c->req->url->base->port;
my $misses = eval { return decode_json $c->param('misses') } || {};
if (!$misses->{'video'}) {
$misses->{'video'} = &subs::random_string_creator(25,"Aa");
}
$files = undef;
my $settings = &subs::settings_grabber({ app => 'music' });
$settings->{'artist'} = eval { return decode_json $settings->{'artist'} } || [];
$settings->{'album'} = eval { return decode_json $settings->{'album'} } || [];
if ($settings->{'library'} ne 'local' && $c->param('remoted') ne 'yes') {
my $rm = &subs::db_query('select * from remote_machines where uuid=? and connection=?', $settings->{'library'}, 'active')->hashes->[0];
if ($rm->{'uuid'}) {
$c->param('remote_uuid' => $rm->{'uuid'});
$c->param('subprocess' => 'yes');
my $result = &Manager::remote_relay_request($c);
if ($result =~ /music/gi) {
$c->render(text => $result);
return;
}
}
else { &subs::setting_setter({ app => 'music', setting => 'library', value => 'local' }); $settings->{'library'} = 'local'; }
}
my $misc_settings = &Manager::misc_setting_list();
if (my $cache = &subs::cache_get({ app => 'music', context => 'content' })) {
if ($cache->{'contents'}) {
if ($c->param('window_maker')) {
my $website = &Manager::window_maker({ app => 'music', title => 'music', contents => $cache->{'contents'} }, &subs::rightNow());
$c->render(text => $website);
}
else {
$c->render(text => $cache->{'contents'});
}
return;
}
}
if ($unlock) {
if (secure_compare(&subs::note_decrypter($c->session('suds'), $unlock), &subs::note_decrypter($c->session('suds'), &subs::setting_grabber({ app => 'music', setting => 'combo_unlock' })) ) ) {
$permissive = 1;
}
}
my $crate = &music_search({ settings => $settings, c => $c, search => $search, now_playing => {}, port => $port, misc_settings => $misc_settings });
my $timestamp = &subs::rightNow();
my $jmao = &subs::setting_grabber({ app => 'music', setting => 'mao' });
my $player = &subs::cache_get({ app => 'music', context => 'player' });
my $resizes = eval { return decode_json $settings->{'resizes'} } || {};
my $contents;
if ($c->param('window_maker')) {
$contents = $c->render_to_string(
crate => $crate,
interface_colour => &subs::random_colour_grabber(),
template => 'apps/music/music',
window_maker => 'yes',
folders => $misc_settings,
device => &subs::device_setter(),
mao => eval { return decode_json $jmao } || {},
jmao => $jmao,
search => $search,
resizes => $resizes,
misses => $misses,
);
my $website = &Manager::window_maker({ app => 'music', title => 'music', contents => $contents }, $timestamp);
$c->render(text => $website);
}
else {
$contents = $c->render_to_string(
crate => $crate,
interface_colour => &subs::random_colour_grabber(),
template => 'apps/music/music',
layout => 'music',
window_maker => 'no',
folders => $misc_settings,
device => &subs::device_setter(),
mao => eval { return decode_json $jmao } || {},
jmao => $jmao,
search => $search,
resizes => $resizes,
misses => $misses
);
$c->render(text => $contents);
}
&subs::cache_set({ app => 'music', context => 'content' }, { contents => $contents });
};
get '/play' => sub($c) {
my $mao = eval { return decode_json &subs::setting_grabber({ app => 'music', setting => 'mao' }) } || {};
my $track = &subs::terminal_name(uri_decode $c->param('track'));
my $data;
&Manager::rock_and_roll($c);
if ($mao->{'srv'} eq 'on') {
Mojo::IOLoop->subprocess->run_p(sub {
if ($device eq 'computer') {
my $t = term_escape $track;
`vlc $track`;
}
elsif ($device eq 'mobile') {
`play $track`;
}
});
$c->render(text => 'play');
}
};
any '/music/receiver' => sub($c) {
my $file = $c->param('file');
my $remote_address = $c->tx->remote_address;
my $domain = $c->param('domain');
my $remote_uuid = $c->param('remote_uuid');
my $toggle = $c->param('toggle');
my $seek = $c->param('seek');
my $volume = $c->param('volume');
my $music_data = &subs::decrypter($c->session('suds'), decode_base64 $c->param('music_data'));
my $piped = &subs::home('~/.president/pipe');
my $timestamp = $c->param('timestamp');
my $latency = $c->param('latency');
my $queuing = $c->param('queuing');
my $server_time = &subs::rightNow();
my $pipe = $piped;
my @file = split /\./, $file;
my $ext = $file[-1];
if ($ext eq 'enc') {
$ext = $file[-2];
}
$pipe = $pipe . '.' . $ext;
my $ws_watcher = &subs::home('~/.president/ws_watcher');
my $pre_md = eval { return decode_json read_file($ws_watcher) } || {};
$log->info(Dumper $pre_md);
$log->info(Dumper $music_data);
write_file($ws_watcher, $music_data) unless $queuing == 1;
my $md = eval { return decode_json $music_data } || {};
if ($toggle eq 'off') {
`pkill play`;
`shred -u $piped*`;
`shred -u $ws_watcher`;
}
elsif ($toggle eq 'seek') {
`pkill play`;
$seek = $seek + ((&subs::rightNow() - $server_time) / 1000) + $latency;
threads->create(sub() {
`play $pipe trim $seek 60000000000`;
});
}
else {
my $rm = &subs::db_query('select * from remote_machines where (ip=? or fqdn=?) and connection=?', $remote_address,$domain,'active')->hashes->[0];
if ($rm->{'uuid'}) {
if ($md->{'file'} ne $pre_md->{'file'} || $queuing == 1) {
my $signatorial = &subs::signatorial_designer();
$rm = &remote_useragent_maker({ ip => $rm->{'ip'}, signatorial => $signatorial, rm => $rm });
my $url = $rm->{'manager'} . '/file_open?&file=' . uri_encode $file;
$rm->{'ua'} = $rm->{'ua'}->max_response_size(0);
my $res = $rm->{'ua'}->get($url);# => { Accept => '*/*' } => 'Content!');
my $data = $res->res->content->asset->slurp;
unless (-e $pipe) {
`touch $pipe`;
}
write_file($pipe, $data);
if ($queuing != 1) {
`pkill play`;
$seek = $seek + ((&subs::rightNow() - $server_time) / 1000) + $latency;
threads->create(sub() {
`play $pipe trim $seek 60000000000`;
});
}
}
else {
`pkill play`;
$seek = $seek + ((&subs::rightNow() - $server_time) / 1000) + $latency;
threads->create(sub() {
`play $pipe trim $seek 60000000000`;
});
}
}
}
$c->render(json => { timestamp => $timestamp, 'seek' => $seek, file => $file, toggle => $toggle, volume => $volume, type => 'transmitter' });
};
sub music_folder_grabber() {
my ($db,$database,$sql) = &subs::database_grabber();
my @folders = ();
my $settings = &Manager::misc_setting_list();
foreach my $s ( keys %{$settings->{$device}} ) {
if ($s =~ /_location$/gi) {
my $status = &subs::setting_grabber({ app => 'music_folder_toggle', setting => $s, device => $device });
push @folders, $settings->{$device}->{$s} if $status eq 'on';
}
}
return @folders;
}
sub music_search($data) {
my $search = $data->{'search'};
my $now_playing = $data->{'now_playing'};
my $settings = $data->{'settings'};
my $c = $data->{'c'};
my $misc_settings = $data->{'misc_settings'};
my $port = $data->{'port'};
my $misses = $data->{'misses'};
$files = [];
unless ($settings->{'uuid'}) {
$settings = &subs::settings_grabber({ app => 'music' });
$settings->{'artist'} = eval { return decode_json $settings->{'artist'} } || [];
$settings->{'album'} = eval { return decode_json $settings->{'album'} } || [];
}
$permissive = 1 if $settings->{'combo_unlock'};
my @folders;
if (scalar @{$settings->{'album'}} > 0) {
push @folders, map { $_->{'path'} } @{$settings->{'album'}};
}
elsif (scalar @{$settings->{'artist'}} > 0) {
push @folders, map { $_->{'path'} } @{$settings->{'artist'}};
}
else {
@folders = &music_folder_grabber();
}
foreach my $folder (@folders) {
$folder = &subs::home($folder);
if (-d $folder) {
find(\&process_file,$folder);
}
}
my @local_files = @{$files};
if ($search) {
@local_files = grep { lc $_ =~ /($search)/gi } @{$files};
}
my $crate = &song_maker({ c => $c, port => $port, files => \@local_files, settings => $settings, now_playing => $now_playing });
$permissive = 0;
return $crate;
}
sub song_maker($data) {
my $files = $data->{'files'};
my $settings = $data->{'settings'};
my $now_playing = $data->{'now_playing'};
my $c = $data->{'c'};
my $misc_settings = $data->{'misc_settings'};
my $port = $data->{'port'};
my $config = $data->{'config'};
my @local_files;
my $background_colour = &subs::setting_grabber({ app => 'misc', setting => 'manager_background_colour'});
my $computer_name = &subs::setting_grabber({ app => 'me', setting => 'computer_name' });
my $file_informations = {};
my ($cache_artists,$cache_albums);
if ($data->{'settings'}-{'all_albums'} eq 'yes') {
$cache_albums = &subs::cache_get({ app => 'music', context => 'albums' }) || {};
}
if ($data->{'settings'}->{'all_artists'} eq 'yes') {
$cache_artists = &subs::cache_get({ app => 'music', context => 'artists' }) || {};
}
if ($data->{'same_order'} != 1) {
if ($settings->{'shuffle'} eq 'on') {
@local_files = shuffle(@{$files});
}
else {
@local_files = sort @{$files};
}
}
else {
@local_files = @{$files};
}
my @lf;
my $track_number = 0;
my $max_files = &subs::setting_grabber({ app => 'misc', setting => 'max_files' } ) || 30;
my $tree = &subs::inventory_grabber();
my $artists = {};
my $albums = {};
my $songs = [];
my $pushed_now_playing = 0;
my $current_track;
splice @local_files, $max_files;
if ($now_playing->{'file'}) {
my @is = grep { $_ eq $now_playing->{'file'} } @local_files;
unshift @local_files, $now_playing->{'file'} unless grep { $_ eq $now_playing->{'file'} } @local_files;
}
for (my $n = 0; $n <= $max_files; $n++) {
my $f = $local_files[$n];
unless (-e $f) {
next;
}
$track_number = $track_number + 1;
my @t = split '/', $f;
my @temp_path = @t;
my $t = $t[-1];
my $track_number = 0;
$t =~ s/(\.mp3$|\.m4a$|\.wav$|\.flac$)//gi;
my $extension = $1;
my ($info,$type,@thumbs);
if ($extension) {
$type = 'audio';
}
else {
$type = 'video';
}
my $filename = $f;
my $full_title = $t;
$tree->{lc $t} = $t[-3];
$tree->{lc $t} .= "\n" . $t[-2] if $t[-2];
$tree->{lc $t} .="\n" . $tree->{lc $t};
pop @temp_path;
my $album_folder = join '/', @temp_path;
if ($type eq 'audio') {
$thumb_files = [];
find(\&process_thumbfile,$album_folder);
@thumbs = @{$thumb_files};
}
my $app;
if ($filename =~ /\.enc$/gi) {
$app = $t[-2];
}
my $formatted_name = &subs::format_name(join '.', $full_title);
pop @temp_path;
my $artist_folder = join '/', @temp_path;
$f = {
filename => $filename,
type => $type,
track => $t,
extension => $extension,
artist => $t[-3],
artist_folder => $artist_folder,
album => $t[-2],
app => &subs::unformat_name($t[-2]),
album_folder => $album_folder,
thumbs => \@thumbs,
info => $info,
song => $t[-1],
full_title => $full_title,
formatted_name => $formatted_name,
track_number => $track_number,
file_uuid => &subs::random_string_creator(21),
description => $tree->{lc $t},
computer_name => $computer_name,
colour => $background_colour
};
if ($filename && $filename !~ /\.enc$/gi) {
if (!$file_informations->{$artist_folder}) {
$file_informations->{$artist_folder} = &subs::cache_get({ app => 'music', context => 'file_information', subcontext => $artist_folder }) || {};
}
my $file_information = $file_informations->{$artist_folder};
if ($file_information->{$filename}) {
$f->{'info'} = $file_information->{$filename};
}
else {
$f = &subs::file_media_information($f, &subs::terminal_name($filename));
$file_information->{$filename} = $f->{'info'};
}
foreach my $i ( qw/artist album track genre title date composer publisher lyrics lyrics-eng/ ) {
if ($i =~ /lyrics/ && $f->{'info'}->{'tags'}->{$i}) {
$f->{'lyrics'} = $f->{'info'}->{'tags'}->{$i};
$f->{'lyrics'} =~ s/\\\n|\\\r/<br>/gi;
}
else {
$f->{$i} = $f->{'info'}->{'tags'}->{$i} if $f->{'info'}->{'tags'}->{$i};
}
}
}
if ($now_playing->{'file'} eq $filename) {
$current_track = $n;
$f->{'playing'} = 'yes';
}
else {
}
$artists->{$t[-3]}->{'full_title'} => $t[-3];
$artists->{$t[-3]}->{'path'} = $artist_folder;
push @{$artists->{$t[-3]}->{'tracks'}}, $f;
$albums->{$t[-2]}->{'full_title'} => $t[-3];
$albums->{$t[-2]}->{'path'} = $album_folder;
push @{$albums->{$t[-3]}->{'tracks'}}, $f;
push @{$songs}, $f;
if ($data->{'settings'}->{'all_artists'} eq 'yes') {
$cache_artists->{lc $f->{'artist'}} = { path => $artist_folder, artist => $f->{'artist'} };
}
if ($data->{'settings'}->{'all_albums'} eq 'yes') {
$cache_albums->{lc $f->{'album'}} = { path => $album_folder, album => $f->{'album'} };
}
}
foreach my $finfo ( keys %{$file_informations} ) {
&subs::cache_set({ app => 'music', context => 'file_information', subcontext => $finfo, warranty => '-6M' }, $file_informations->{$finfo});
}
if ($data->{'settings'}->{'all_artists'} eq 'yes') {
&subs::cache_set({ app => 'music', context => 'artists' }, $cache_artists);
$artists = $cache_artists;
}
if ($data->{'settings'}->{'all_albums'} eq 'yes') {
&subs::cache_set({ app => 'music', context => 'albums' }, $cache_albums);
$albums = $cache_albums;
}
my $crate = {
config => $config,
remote_uuid => $c->param('remote_uuid'),
current_track => $current_track,
artist => $settings->{'artist'},
album => $settings->{'album'},
artists => $artists,
albums => $albums,
songs => $songs,
settings => $settings,
};
return $crate;
}
post '/music/search' => sub ($c) {
my $title = 'music';
my $search = lc $c->param('search');
my $unlock = $c->param('unlock');
my $window_maker = $c->param('window_maker');
my $new_settings = eval { return decode_json $c->param('new_settings') } || {};
my $resizes = $c->param('resizes');
my $misses = eval { return decode_json $c->param('misses') } || {};
if (!$misses->{'video'}) {
$misses->{'video'} = &subs::random_string_creator(25,"Aa");
}
foreach my $ns ( keys %{$new_settings} ) {
&subs::setting_setter({ app => 'music', setting => $ns, value => $new_settings->{$ns} });
}
my $port = $c->req->url->base->port;
my $list = eval { return decode_json $c->param('list') } || [];
my $now_playing = eval { return decode_json $c->param('now_playing') } || {};
my $same_order = $c->param('same_order');
&subs::cache_delete({ app => 'music', context => 'content' });
my $artist = $c->param('artist');
my $album = $c->param('album');
my $misc_settings = &Manager::misc_setting_list();
if ($unlock) {
if (secure_compare(&subs::note_decrypter($c->session('suds'), $unlock), &subs::note_decrypter($c->session('suds'), &subs::setting_grabber({ app => 'gallery', setting => 'combo_unlock' })) ) ) {
$permissive = 1;
}
}
&subs::setting_setter({ app => 'music', setting => 'resizes', value => $resizes });
&subs::setting_setter({ app => 'music', setting => 'album', value => $album });
&subs::setting_setter({ app => 'music', setting => 'artist', value => $artist });
&subs::setting_setter({ app => 'music', setting => 'search', value => $search });
my $settings = &subs::settings_grabber({ app => 'music' });
if ($settings->{'library'} ne 'local' && $c->param('remoted') ne 'yes') {
$c->param('resizes' => undef);
my $rm = &subs::db_query('select * from remote_machines where uuid=? and connection=?', $settings->{'library'}, 'active')->hashes->[0];
if ($rm->{'uuid'}) {
$c->param('remote_uuid' => $rm->{'uuid'});
$c->param('subprocess' => 'yes');
my $result = &Manager::remote_relay_request($c);
if ($result =~ /music/gi) {
my $dom = Mojo::DOM->new($result);
$c->render(text => $result);
return;
}
}
else { &subs::setting_setter({ app => 'music', setting => 'library', value => 'local' }); $settings->{'library'} = 'local'; }
}
$settings->{'artist'} = eval { return decode_json $settings->{'artist'} } || [];
$settings->{'album'} = eval { return decode_json $settings->{'album'} } || [];
my $crate;
if (scalar @{$list} > 0) {
my $permissive = 1 if $settings->{'combo_unlock'};
$crate = &song_maker({ c => $c, config => $config, port => $port, files => $list, settings => $settings, now_playing => $now_playing, same_order => $same_order, misc_settings => $misc_settings });
}
else {
$crate = &music_search({ c => $c, search => $search, now_playing => $now_playing, settings => $settings, port => $port, misc_settings => $misc_settings });
}
my @interface_color= qw/yellow green blue orange black fuschia pink navy purple brown/;
my $random_number = rand(0..8);
my $timestamp = &subs::rightNow();
my $jmao = &subs::setting_grabber({ app => 'music', setting => 'mao' });
$artist = eval { return decode_json $artist } || [];
$album = eval { return decode_json $album } || [];
$resizes = eval { return decode_json $resizes } || {};
my $contents;
if ($c->param('window_maker')) {
$contents = $c->render_to_string(
crate => $crate,
interface_colour => $interface_color[$random_number],
template => 'apps/music/music',
window_maker => 'yes',
folders => $misc_settings,
device => &subs::device_setter(),
mao => eval { return decode_json $jmao } || {},
jmao => $jmao,
search => $search,
resizes => $resizes,
misses => $misses
);
my $website = &Manager::window_maker({ app => 'music', title => $title, contents => $contents }, $timestamp);
$c->render(json => { artist => $artist, album => $album, search => $search, html => $website });
}
else {
$contents = $c->render_to_string(
template => 'apps/music/files',
crate => $crate,
search => $search,
);
$c->render(text => $contents);
}
&subs::cache_set({ app => 'music', context => 'content' }, { contents => $contents });
};
post '/music/folder_toggle' => sub($c) {
my $timestamp = $c->param('timestamp');
my $location = $c->param('location');
my $status = $c->param('status');
&subs::setting_setter({ app => 'music_folder_toggle', setting => $location, value => $status, timestamp => $timestamp });
my $setting = &subs::setting_grabber({ app => 'music_folder_toggle', setting => $location, device => $device });
$c->render(text => $setting);
};
post '/music/audio_output_select' => sub ($c) {
my $output = $c->param('output');
my $state = $c->param('state');
my $mao = eval { return decode_json &subs::setting_grabber({ app => 'music', setting => 'mao' }) } || {};
$mao->{$output} = $state;
my $jmao = encode_json $mao;
&subs::setting_setter({ app => 'music', setting => 'mao', value => $jmao });
$c->render(json => $mao);
};
get '/music/configuration' => sub($c) {
my $returner = {};
my $settings = &subs::settings_grabber({ app => 'music' });
my $jmao = $settings->{'mao'};
$returner->{'html'} = $c->render_to_string(
template => 'apps/music/configuration',
folders => &Manager::misc_setting_list(),
device => &subs::device_setter(),
mao => eval { return decode_json $jmao } || {},
jmao => $jmao,
settings => $settings,
crate => { remote_uuid => $c->param('remote_uuid') }
);
$c->render(json => $returner);
};
1;