From e46f263d8e9026d39b193bd7cce4f70cf66d2817 Mon Sep 17 00:00:00 2001 From: Leugi <> Date: Fri, 1 Jan 2021 12:15:41 +0000 Subject: [PATCH] 71_YAMAHA_MC: added mute with params 0 and 1, added GetFuncStatus to regular update Status git-svn-id: https://svn.fhem.de/fhem/trunk@23451 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/FHEM/71_YAMAHA_MC.pm | 161 ++++++++++++++++++++++++++++++++------ 1 file changed, 138 insertions(+), 23 deletions(-) diff --git a/fhem/FHEM/71_YAMAHA_MC.pm b/fhem/FHEM/71_YAMAHA_MC.pm index ccfb3060b..cb8a6916a 100644 --- a/fhem/FHEM/71_YAMAHA_MC.pm +++ b/fhem/FHEM/71_YAMAHA_MC.pm @@ -43,8 +43,19 @@ use Encode qw(decode encode); #use UPnP::ControlPoint; #use Net::UPnP::ControlPoint; -use Net::UPnP::AV::MediaRenderer; +#use lib from fhem instead UPnP::ControlPoint : +#my $gPath = ''; +#BEGIN { +# $gPath = substr($0, 0, rindex($0, '/')); +#} +#if (lc(substr($0, -7)) eq 'fhem.pl') { +# $gPath = $attr{global}{modpath}.'/FHEM'; +#} +#use lib ($gPath.'/lib', $gPath.'/FHEM/lib', './FHEM/lib', './lib', './FHEM', './', '/usr/local/FHEM/share/fhem/FHEM/lib'); +# +#use UPnP::ControlPoint; use Net::UPnP::ControlPoint; +use Net::UPnP::AV::MediaRenderer; use Net::UPnP::Device; use Net::UPnP::Service; use Net::UPnP::AV::MediaServer; @@ -147,9 +158,9 @@ my %YAMAHA_MC_setCmdsWithArgs = ( "volumeStraight" => "/v1/main/setVolume?volume=", "volumeUp:noArg" => "/v1/main/setVolume?volume=", "volumeDown:noArg" => "/v1/main/setVolume?volume=", - "mute:toggle,true,false" => "/v1/main/setMute?enable=", - "setSpeakerA:toggle,true,false" => "/v1/main/setSpeakerA?enable=", - "setSpeakerB:toggle,true,false" => "/v1/main/setSpeakerB?enable=", + "mute:toggle,true,false,0,1" => "/v1/main/setMute?enable=", + "setSpeakerA:toggle,true,false" => "/v1/system/setSpeakerA?enable=", + "setSpeakerB:toggle,true,false" => "/v1/system/setSpeakerB?enable=", "setToneBass:slider,-10,1,10" => "/v1/main/setEqualizer?low=", "setToneMid:slider,-10,1,10" => "/v1/main/setEqualizer?mid=", "setToneHigh:slider,-10,1,10" => "/v1/main/setEqualizer?high=", @@ -215,7 +226,7 @@ sub YAMAHA_MC_Initialize($) { $hash->{ReadFn} = "YAMAHA_MC_Read"; # modules attributes - $hash->{AttrList} = "do_not_notify:0,1 " . "disable:1,0 " . "disabledForIntervals " . "request-timeout:1,2,3,4,5,10 " . "model " . "standard_volume:15 " . "ttsvolume " . "volumeSteps:3 " . "pathToFavoriteServer " . "FavoriteServerChannel " . "FavoriteNetRadioChannel " . "autoplay_disabled:true,false " . "autoReadReg:4_reqStatus " . "actCycle:off " . "DLNAsearch:on,off " . "DLNAServer " . "powerCmdDelay " . "menuLayerDelay " . "homebridgeMapping " . "eventProcessing:0,1 " . $readingFnAttributes; + $hash->{AttrList} = "do_not_notify:0,1 " . "disable:1,0 " . "disabledForIntervals " . "request-timeout:1,2,3,4,5,10 " . "model " . "standard_volume:15 " . "ttsvolume " . "volumeSteps:3 " . "pathToFavoriteServer " . "FavoriteServerChannel " . "FavoriteNetRadioChannel " . "autoplay_disabled:true,false " . "autoReadReg:4_reqStatus " . "actCycle:off " . "DLNAsearch:on,off " . "DLNAServer " . "powerCmdDelay " . "menuLayerDelay " . "homebridgeMapping " . "eventProcessing:0,1 " . "ignoredIPs " . "usedonlyIPs " . $readingFnAttributes; } # ------------------------------------------------------------------------------ @@ -464,16 +475,18 @@ sub YAMAHA_MC_setupControlpoint { my ($hash) = @_; my $error; my $cp; - my @usedonlyIPs = split( /,/, AttrVal( $hash->{NAME}, 'usedonlyIPs', '' ) ); - my @ignoredIPs = split( /,/, AttrVal( $hash->{NAME}, 'ignoredIPs', '' ) ); + #my @usedonlyIPs = split( /,/, AttrVal( $hash->{NAME}, 'usedonlyIPs', '' ) ); + #my @ignoredIPs = split( /,/, AttrVal( $hash->{NAME}, 'ignoredIPs', '' ) ); do { eval { - #$cp = UPnP::ControlPoint->new(SearchPort => 0, SubscriptionPort => 0, MaxWait => 30, UsedOnlyIP => \@usedonlyIPs, IgnoreIP => \@ignoredIPs, LogLevel => AttrVal($hash->{NAME}, 'verbose', 0)); + Log3 $hash->{NAME}, 1, "$hash->{TYPE}: $hash->{NAME} - try to setupControlpoint"; + #$cp = UPnP::ControlPoint->new(SearchPort => 0, SubscriptionPort => 0, MaxWait => 30, UsedOnlyIP => \@usedonlyIPs, IgnoreIP => \@ignoredIPs, LogLevel => AttrVal($hash->{NAME}, 'verbose', 0)); $cp = Net::UPnP::ControlPoint->new(); $hash->{helper}{controlpoint} = $cp; }; $error = $@; + Log3 $hash->{NAME}, 1, "$hash->{TYPE}: $hash->{NAME} YAMAHA_MC_setupControlpoint end result:$error"; } while ($error); return undef; @@ -491,6 +504,7 @@ sub YAMAHA_MC_setupMediaRenderer { do { eval { + Log3 $hash->{NAME}, 1, "$hash->{TYPE}: $hash->{NAME} - try to setupMediaRenderer"; $MediaRenderer = Net::UPnP::AV::MediaRenderer->new(); $hash->{helper}{MediaRenderer} = $MediaRenderer; }; @@ -683,6 +697,11 @@ sub YAMAHA_MC_GetStatus($;$) { # get distribution info Log3 $name, 4, "$type: $name YAMAHA_MC_GetStatus fetching getLocationInfo now"; YAMAHA_MC_getDistributionInfo( $hash, $priority ); + + #Function Status auch wichtig für Speaker_a und Speaker_b Status + Log3 $name, 4, "$type: $name YAMAHA_MC_GetStatus fetching getFuncStatus now"; + YAMAHA_MC_httpRequestQueue( $hash, "getFuncStatus", "", { options => { at_first => 0, priority => $priority, unless_in_queue => 1 } } ); # call fn that will do the http request + } else { @@ -937,6 +956,7 @@ sub YAMAHA_MC_DiscoverRenderer($) { my $HOST = $hash->{HOST}; my $DLNAsearch = AttrVal( $hash->{NAME}, "DLNAsearch", "off" ); + my $MediaRendererFound = 0; Log3 $name, 4, "$name YAMAHA_MC_DiscoverRenderer DLNAsearch is turned " . $DLNAsearch . "\n"; if ( $DLNAsearch eq "on" ) { @@ -947,14 +967,53 @@ sub YAMAHA_MC_DiscoverRenderer($) { #my $MediaRendererDLNA = Net::UPnP::AV::MediaRenderer->new(); my $MediaRendererDLNA = $hash->{helper}{MediaRenderer}; + # wenn die Helper nicht gesetzt dann hier nochnals starten + if ( !defined( $ControlPointDLNA ) ) { + Log3 $name, 1, "$name - Controlpoint not yet defined, starting YAMAHA_MC_setupControlpoint "; + YAMAHA_MC_setupControlpoint($hash); + } + + if ( !defined( $MediaRendererDLNA ) ) { + Log3 $name, 1, "$name - MediaRendererDLNA not yet defined, starting YAMAHA_MC_setupMediaRenderer "; + YAMAHA_MC_setupMediaRenderer($hash); + } + + # wenn die Helper immer noch nicht gesetzt dann hier abbrechen + if ( !defined( $hash->{helper}{controlpoint} ) ) { + Log3 $name, 1, "$name - Controlpoint still not defined, exiting"; + return undef; + } + else { + Log3 $name, 1, "$name - ControlPointDLNA defined found, now "; + $ControlPointDLNA = $hash->{helper}{controlpoint}; + } + + if ( !defined( $hash->{helper}{MediaRenderer} ) ) { + Log3 $name, 1, "$name - MediaRendererDLNA still not defined, exiting "; + return undef; + } + else { + Log3 $name, 1, "$name - MediaRendererDLNA found, now "; + $MediaRendererDLNA = $hash->{helper}{MediaRenderer}; + } + + Log3 $name, 4, "$name YAMAHA_MC_DiscoverRenderer start search for own dlna Renderer"; + # search via root device my @dev_list = (); my $retry_cnt = 0; - while ( @dev_list <= 0 || $retry_cnt > 5 ) { - @dev_list = $ControlPointDLNA->search( st => 'upnp:rootdevice', mx => 3 ); + while ( @dev_list <= 0 and $retry_cnt < 10 ) { + Log3 $name, 4, "$name YAMAHA_MC_DiscoverRenderer ControlPointDLNA Retrycount=$retry_cnt"; + eval { + @dev_list = $ControlPointDLNA->search( st => 'upnp:rootdevice', mx => 5 ); + }; + if($@) { + Log3 $hash, 3, "$name, 4, $name YAMAHA_MC_DiscoverRenderer ControlPointDLNA failed with error $@"; + } $retry_cnt++; } + # Network Name als DLNA Renderer verwenden if ( !defined( $hash->{network_name} ) ) { @@ -966,7 +1025,10 @@ sub YAMAHA_MC_DiscoverRenderer($) { unless ( defined( $hash->{network_name} ) ) { $hash->{network_name} = 'No Network name available' } + # go throuh device list + Log3 $name, 4, "$name YAMAHA_MC_DiscoverRenderer Networkname is defined = $hash->{network_name}, query Device List now .."; my $devNum = 0; + foreach my $dev (@dev_list) { my $device_type = $dev->getdevicetype(); if ( $device_type ne 'urn:schemas-upnp-org:device:MediaRenderer:1' ) { @@ -980,6 +1042,7 @@ sub YAMAHA_MC_DiscoverRenderer($) { $MediaRendererDLNA->setdevice($dev); $MediaRendererDLNA->stop(); + $MediaRendererFound = 1; Log3 $name, 4, "$name YAMAHA_MC_DiscoverRenderer Saving MediaRendererDLNA in helper "; $hash->{helper}{MediaRendererDLNA} = $MediaRendererDLNA; @@ -1008,6 +1071,13 @@ sub YAMAHA_MC_DiscoverRenderer($) { if ($@) { Log3 $hash, 3, "YAMAHA_MC_DiscoverRenderer: Discovery failed with: $@"; } + + if ($MediaRendererFound) { + Log3 $hash, 3, "YAMAHA_MC_DiscoverRenderer: Correct MediaRenderer found"; + } else { + Log3 $hash, 3, "YAMAHA_MC_DiscoverRenderer: MediaRenderer not found"; + } + return undef; } @@ -1041,8 +1111,13 @@ sub YAMAHA_MC_DiscoverMediaServer($) { while ( @dev_list <= 0 ) { Log3 $hash, 3, "$name Searching for MediaServer.. @dev_list"; - my $obj = Net::UPnP::ControlPoint->new(); - @dev_list = $obj->search( st => 'urn:schemas-upnp-org:device:MediaServer:1', mx => 5 ); + eval { + my $obj = Net::UPnP::ControlPoint->new(); + @dev_list = $obj->search( st => 'urn:schemas-upnp-org:device:MediaServer:1', mx => 5 ); + }; + if($@) { + Log3 $hash, 3, "$name, 4, $name YAMAHA_MC_DiscoverMediaServer ControlPointDLNA search failed with error $@"; + } $retry_cnt++; if ( $retry_cnt >= 3 ) { Log3 $hash, 3, "$name [!] No media found. Releasing semaphore, exiting."; @@ -1053,13 +1128,25 @@ sub YAMAHA_MC_DiscoverMediaServer($) { my $devNum = 0; my $dev; + my $device_type; + my $friendlyname; foreach $dev (@dev_list) { - my $device_type = $dev->getdevicetype(); + eval { + $device_type = $dev->getdevicetype(); + }; + if($@) { + Log3 $hash, 3, "$name, 4, $name YAMAHA_MC_DiscoverMediaServer getdevicetype failed with error $@"; + } if ( $device_type ne 'urn:schemas-upnp-org:device:MediaServer:1' ) { next; } $devNum++; - my $friendlyname = $dev->getfriendlyname(); + eval { + $friendlyname = $dev->getfriendlyname(); + }; + if($@) { + Log3 $hash, 3, "$name, 4, $name YAMAHA_MC_DiscoverMediaServer getfriendlyname failed with error $@"; + } Log3 $hash, 3, "$name found [$devNum] : device name: [" . $friendlyname . "] "; if ( $friendlyname ne $miniDLNAname ) { Log3 $hash, 3, "$name skipping this device."; @@ -1070,8 +1157,13 @@ sub YAMAHA_MC_DiscoverMediaServer($) { } Log3 $hash, 3, "Init MediaServer now"; - my $MediaServer = Net::UPnP::AV::MediaServer->new(); - $MediaServer->setdevice($dev); + eval { + my $MediaServer = Net::UPnP::AV::MediaServer->new(); + $MediaServer->setdevice($dev); + }; + if($@) { + Log3 $hash, 3, "$name, 4, $name YAMAHA_MC_DiscoverMediaServer MediaServer setdevice failed with error $@"; + } Log3 $name, 4, "$name Saving MediaServer in helper "; $hash->{helper}{MediaServerDLNA} = $dev; readingsSingleUpdate( $hash, 'MediaServer', $friendlyname, 1 ); @@ -1616,9 +1708,9 @@ sub YAMAHA_MC_UpdateLists($;$) { "volumeStraight" => "/v1/main/setVolume?volume=", "volumeUp:noArg" => "/v1/main/setVolume?volume=", "volumeDown:noArg" => "/v1/main/setVolume?volume=", - "mute:toggle,true,false" => "/v1/main/setMute?enable=", - "setSpeakerA:toggle,true,false" => "/v1/main/setSpeakerA?enable=", - "setSpeakerB:toggle,true,false" => "/v1/main/setSpeakerB?enable=", + "mute:toggle,true,false,0,1" => "/v1/main/setMute?enable=", + "setSpeakerA:toggle,true,false" => "/v1/system/setSpeakerA?enable=", + "setSpeakerB:toggle,true,false" => "/v1/system/setSpeakerB?enable=", "setToneBass:slider,-10,1,10" => "/v1/main/setEqualizer?low=", "setToneMid:slider,-10,1,10" => "/v1/main/setEqualizer?mid=", "setToneHigh:slider,-10,1,10" => "/v1/main/setEqualizer?high=", @@ -1779,7 +1871,7 @@ sub YAMAHA_MC_Set($$@) { my $deviceList_comma = join( ",", @deviceList ); $cmd = "?" unless defined $cmd; - my $usage = "Unknown argument $cmd, choose one of " . "on:noArg " . "off:noArg " . "power:on,standby,toggle " . "toggle:noArg " . "setAutoPowerStandby:true,false " . "volume:slider,0,1,100 " . "volumeStraight " . "volumeUp:noArg " . "volumeDown:noArg " . "mute:toggle,true,false " . "setSpeakerA:toggle,true,false " . "setSpeakerB:toggle,true,false " . "setToneBass:slider,-10,1,10 " . "setToneMid:slider,-10,1,10 " . "setToneHigh:slider,-10,1,10 " . ( exists( $hash->{helper}{INPUTS} ) ? "input:" . $inputs_comma . " " : "input " ) . ( exists( $hash->{helper}{INPUTS} ) ? "prepareInputChange:" . $inputs_comma . " " : "prepareInputChange " ) . "getStatus:noArg " . "getFeatures:noArg " . "getFuncStatus:noArg " . "selectMenu " . ( exists( $hash->{helper}{MENUITEMS} ) ? "selectMenuItem:" . $menuitems_comma . " " : "selectMenuItem " ) . "selectPlayMenu " . ( exists( $hash->{helper}{MENUITEMS} ) ? "selectPlayMenuItem:" . $menuitems_comma . " " : "" ) . "getPlayInfo:noArg " . "playback:play,stop,pause,play_pause,previous,next,fast_reverse_start,fast_reverse_end,fast_forward_start,fast_forward_end " . "getMenu:noArg " . "getMenuItems:noArg " . "returnMenu:noArg " . "getDeviceInfo:noArg " . "getSoundProgramList:noArg " . ( exists( $hash->{helper}{SOUNDPROGRAMS} ) ? "setSoundProgramList:" . $soundprograms_comma . " " : "" ) . "setFmTunerPreset:slider,0,1,20 " . "setDabTunerPreset:slider,0,1,20 " . "setNetRadioPreset " . "TurnFavNetRadioChannelOn:1,2,3,4,5,6,7,8 " . "TurnFavServerChannelOn:noArg " . "navigateListMenu " . "NetRadioNextFavChannel:noArg " . "NetRadioPrevFavChannel:noArg " . "sleep:uzsuSelectRadio,0,30,60,90,120 " . "getNetworkStatus:noArg " . "getLocationInfo:noArg " . "getDistributionInfo:noArg " . "getBluetoothInfo:noArg " . "enableBluetooth:true,false " . "setGroupName " . "mcLinkTo:multiple," . $deviceList_comma . " " . "speakfile " . "mcUnLink:multiple," . ReadingsVal( $hash->{NAME}, "linkedClients", "" ) . " " . "setServerInfo " . "setClientInfo " . "startDistribution " . "isNewFirmwareAvailable:noArg " . "statusRequest:noArg "; + my $usage = "Unknown argument $cmd, choose one of " . "on:noArg " . "off:noArg " . "power:on,standby,toggle " . "toggle:noArg " . "setAutoPowerStandby:true,false " . "volume:slider,0,1,100 " . "volumeStraight " . "volumeUp:noArg " . "volumeDown:noArg " . "mute:toggle,true,false,0,1 " . "setSpeakerA:toggle,true,false " . "setSpeakerB:toggle,true,false " . "setToneBass:slider,-10,1,10 " . "setToneMid:slider,-10,1,10 " . "setToneHigh:slider,-10,1,10 " . ( exists( $hash->{helper}{INPUTS} ) ? "input:" . $inputs_comma . " " : "input " ) . ( exists( $hash->{helper}{INPUTS} ) ? "prepareInputChange:" . $inputs_comma . " " : "prepareInputChange " ) . "getStatus:noArg " . "getFeatures:noArg " . "getFuncStatus:noArg " . "selectMenu " . ( exists( $hash->{helper}{MENUITEMS} ) ? "selectMenuItem:" . $menuitems_comma . " " : "selectMenuItem " ) . "selectPlayMenu " . ( exists( $hash->{helper}{MENUITEMS} ) ? "selectPlayMenuItem:" . $menuitems_comma . " " : "" ) . "getPlayInfo:noArg " . "playback:play,stop,pause,play_pause,previous,next,fast_reverse_start,fast_reverse_end,fast_forward_start,fast_forward_end " . "getMenu:noArg " . "getMenuItems:noArg " . "returnMenu:noArg " . "getDeviceInfo:noArg " . "getSoundProgramList:noArg " . ( exists( $hash->{helper}{SOUNDPROGRAMS} ) ? "setSoundProgramList:" . $soundprograms_comma . " " : "" ) . "setFmTunerPreset:slider,0,1,20 " . "setDabTunerPreset:slider,0,1,20 " . "setNetRadioPreset " . "TurnFavNetRadioChannelOn:1,2,3,4,5,6,7,8 " . "TurnFavServerChannelOn:noArg " . "navigateListMenu " . "NetRadioNextFavChannel:noArg " . "NetRadioPrevFavChannel:noArg " . "sleep:uzsuSelectRadio,0,30,60,90,120 " . "getNetworkStatus:noArg " . "getLocationInfo:noArg " . "getDistributionInfo:noArg " . "getBluetoothInfo:noArg " . "enableBluetooth:true,false " . "setGroupName " . "mcLinkTo:multiple," . $deviceList_comma . " " . "speakfile " . "mcUnLink:multiple," . ReadingsVal( $hash->{NAME}, "linkedClients", "" ) . " " . "setServerInfo " . "setClientInfo " . "startDistribution " . "isNewFirmwareAvailable:noArg " . "statusRequest:noArg "; # delay in Seks for next request after turning on device my $powerCmdDelay = AttrVal( $hash->{NAME}, "powerCmdDelay", 3 ); @@ -1867,14 +1959,28 @@ sub YAMAHA_MC_Set($$@) { Log3 $name, 4, "$name : YAMAHA_MC_Set mute to $a[2]"; if ( ( defined( $a[2] ) ) && ( ( lc( $a[2] ) eq "true" ) || ( lc( $a[2] ) eq "false" ) ) ) { YAMAHA_MC_httpRequestQueue( $hash, "mute", lc( $a[2] ) ); # call fn that will do the http request + } + elsif ( ( defined( $a[2] ) ) && ( lc( $a[2] ) eq "0" ) ) { + Log3 $name, 4, "$name : YAMAHA_MC_Set mute 0"; + YAMAHA_MC_httpRequestQueue( $hash, "mute", "false" ) ; # call fn that will do the http request + } + elsif ( ( defined( $a[2] ) ) && ( lc( $a[2] ) eq "1" ) ) { + Log3 $name, 4, "$name : YAMAHA_MC_Set mute 1"; + YAMAHA_MC_httpRequestQueue( $hash, "mute", "true" ) ; # call fn that will do the http request } elsif ( ( defined( $a[2] ) ) && ( lc( $a[2] ) eq "toggle" ) ) { Log3 $name, 4, "$name : YAMAHA_MC_Set mute toggle"; if ( ReadingsVal( $name, "mute", "false" ) eq "false" ) { YAMAHA_MC_httpRequestQueue( $hash, "mute", "true", { options => { can_fail => 1 } } ); # call fn that will do the http request } - else { + elsif ( ReadingsVal( $name, "mute", "false" ) eq "true" ) { YAMAHA_MC_httpRequestQueue( $hash, "mute", "false", { options => { can_fail => 1 } } ); # call fn that will do the http request + } + elsif ( ReadingsVal( $name, "mute", "false" ) eq "0" ) { + YAMAHA_MC_httpRequestQueue( $hash, "mute", "1", { options => { can_fail => 1 } } ); # call fn that will do the http request + } + elsif ( ReadingsVal( $name, "mute", "false" ) eq "1" ) { + YAMAHA_MC_httpRequestQueue( $hash, "mute", "0", { options => { can_fail => 1 } } ); # call fn that will do the http request } } else { @@ -1923,7 +2029,7 @@ sub YAMAHA_MC_Set($$@) { YAMAHA_MC_httpRequestQueue( $hash, $cmd, ( $a[2] ) ); # call fn that will do the http request } else { - return "invalid parameter $a[2] for set mute"; + return "invalid parameter $a[2] for set $cmd"; } } elsif ( $cmd eq "setSoundProgramList" ) { @@ -3040,6 +3146,10 @@ sub YAMAHA_MC_udpEventParse($$) { $volume = YAMAHA_MC_volume_abs2rel( $hash, $volume ); readingsBulkUpdateIfChanged( $hash, "volume", $volume, 1 ); } + if ( my $volume = $main->{"input"} ) { + Log3 $name, 4, "$type: $name YAMAHA_MC_udpEventParse got new input \n"; + readingsBulkUpdateIfChanged( $hash, "input", $main->{"input"}, 1 ); + } } if ( my $tuner = $event->{"tuner"} ) { @@ -4046,7 +4156,7 @@ sub YAMAHA_MC_httpRequestParse($$$) { } elsif ( ( $cmd eq "mute" ) ) { - if ( $arg eq "true" ) { + if ( ( $arg eq "true" ) || ( $arg eq "1" ) ) { readingsSingleUpdate( $hash, "mute", "true", 1 ); } else { @@ -4897,7 +5007,12 @@ sub YAMAHA_URI_Escape($) { will work with basic functions. Use "cpan install JSON" or operating system's package manager to install JSON Modul. Depending on your os the required package is named: libjson-perl or perl-JSON. + Futher needed packages :
+
  • sudo apt-get install libjson-perl
  • +
  • sudo apt-get install libmp3-info-perl
  • +
  • sudo apt-get install -y libnet-upnp-perl
  • +
  • perl -MCPAN -e 'install MP3::Info'