playlist export via Perl

General discussions.

Moderator: moderators

playlist export via Perl

Postby Jägs » Mon Jun 19, 2017 5:22 pm

Greetings:

Thought I'd share my Perl playlist exporter for Subsonic, in case anyone might find it useful. It's been a while since I wrote it, but I think I relied heavily upon the Java version posted in these discussion. It's also a bit hacky, as I hadn't programmed in Perl for quite some time, so there's likely a bunch of stuff that can be cleaned up, but here you go:

Code: Select all
#!/usr/bin/perl

use HTTP::Request::Common qw(POST);
use LWP::UserAgent;
use HTML::Strip;
use Class::Struct;
use Switch;

# create the structure that will be used to store the playlist information
struct ( playlist => {
      playlistID => '$', # DB ID of the playlist
      playlistName => '$', # name of the playlist
      playlistSongs => '@', # array of songs in the playlist
      }
   );

# create the structure that will be used to store the song information
# this will be used to generate the M3U format
struct ( song => {
      songPath => '$', # path of the song
      songArtist => '$', # artist
      songTitle => '$', # title
      songDuration => '$', # duration in seconds
      }
   );

# this will be an array containing instances of the structure we created above
my @playlists;


if ( $#ARGV + 1 != 4 ) {

   print "Usage:  playlistExport.pl <URL to db.view> <user:password base64 encoded> <format (bak or  m3u)> <playlist path>\n\n";
   exit;
}

else {
   my $baseURL = $ARGV[0];
   my $playlistQuery = "select ID, NAME from PLAYLIST;";
   my $songQuery = "SELECT PATH, ARTIST, TITLE, DURATION_SECONDS FROM MEDIA_FILE INNER JOIN PLAYLIST_FILE ON (MEDIA_FILE.ID = PLAYLIST_FILE.MEDIA_FILE_ID) WHERE PLAYLIST_ID= XX ORDER BY PLAYLIST_FILE.ID;";
   my $authEncoded = $ARGV[1];
   my $playlistFormat = $ARGV[2];
   my $playlistPath = $ARGV[3];

   # if the user did not add the trailing slash, let's add it
   if ( $playlistPath !~ /\/$/ ) {
      $playlistPath .= "/";
   }
   
   my $userAgent = LWP::UserAgent->new();
   
   # first, get the playlists
   my $request = POST $baseURL,
      Content_Type => form-data,
      Content => [ 'query' => $playlistQuery ],
      Authorization => 'Basic ' . $authEncoded;
   
   my $playlistHTML  = $userAgent->request($request)->as_string();

   print "playlistHTML = $playlistHTML\n";
   
   # now, parse through the raw playlist HTML code and store the playlist names and IDs in an array
   
   my $currentIndex = -1;
   
   for (split /^/, $playlistHTML) {
   
      if ( $_ =~ 'ruleTableCell' ) {
   
         # set up the tag stripper
         my $stripper = HTML::Strip->new();
      
         # strip the HTML tags/attributes
         my $thisData = $stripper->parse($_);
   
         # remove any leading/trailing whitespace
         $thisData =~ s/^\s+|\s+$//g;
   
         # if the remaining data is entirely numeric (the ID), set the the array index to it
         if ( $thisData =~ /^\d+$/ && $currentIndex == -1 ) {
            $currentIndex = $thisData;
         }
         
         # otherwise, it should be the playlist name, so push it to the array and reset the index
         else {
            my $thisPlaylist = playlist->new ( playlistID => $currentIndex, playlistName => $thisData );
            push (@playlists,  $thisPlaylist);
            $currentIndex = -1;
         }
      }
   }
   
   # now, for each of the playlists, grab the list of songs
   for ( $indexer = 0; $indexer <= $#playlists; $indexer++ ) {
      
      # set up the query to get the songs from the playlist
      my $thisQuery = $songQuery;
      my $thisID = $playlists[$indexer]->playlistID;
      $thisQuery =~ s/XX/$thisID/;

      # now, get the songs for the given playlist
      my $request = POST $baseURL,
         Content_Type => form-data,
         Content => [ 'query' => $thisQuery ],
         Authorization => 'Basic ' . $authEncoded;
   
      my $songsHTML  = $userAgent->request($request)->as_string();

      # this will be used to keep track of the current field, so we can populate the song array with the proper information
      my $currentField = 0;
      my $thisSong;

      # just like the playlists, we need to parse through the $songsHTML to get the paths and add them to the playlist's array
      for (split /^/, $songsHTML ) {
   
         if ( $_ =~ 'ruleTableCell' ) {

            # set up the tag stripper
            my $stripper = HTML::Strip->new();
      
            # strip the HTML tags/attributes
            my $thisData = $stripper->parse($_);

            # remove any leading/trailing whitespace
            $thisData =~ s/^\s+|\s+$//g;

            switch ( $currentField ) {
      
               case 0   { $thisSong = song->new (songPath => $thisData); $currentField++; }
               case 1   { $thisSong->songArtist($thisData); $currentField++; }
               case 2   { $thisSong->songTitle($thisData); $currentField++; }
               case 3   { $thisSong->songDuration($thisData); push (@{$playlists[$indexer]->playlistSongs}, $thisSong); $currentField = 0; }
            }
         }
      }
   
      # now, output the playlists to the the format and path specified by the user

      # first, check to see if the path exists
      if ( -d $playlistPath ) {

         # the path exists, so let's create the file handler
         open (THISPLAYLIST, ">" . $playlistPath . $playlists[$indexer]->playlistName . "." . $playlistFormat ) or die "ERROR:  could not open the playlist file (" . $playlists[$indexer]->playlistName . ") for writing!\n";

         print "processing playlist:  " . $playlistPath . $playlists[$indexer]->playlistName . "." . $playlistFormat . "\n";

         # if the user has specified M3U as the format, print out the header
         if ( $playlistFormat eq "m3u" ) {
            print THISPLAYLIST "#EXTM3U\n";
         }
         
         # now, loop through all of the songs in the playlist, and print them to the file in the specified format
         foreach ( @{$playlists[$indexer]->playlistSongs} ) {
            
            switch ($playlistFormat) {

               case "m3u" { print THISPLAYLIST "#EXTINF:" . $_->songDuration . ", " . $_->songArtist . " - " . $_->songTitle . "\n" . $_->songPath . "\n"; }
               case "bak" { print THISPLAYLIST $_->songPath . "\n";}
            }
         }

         # close the file
         close (THISPLAYLIST);
      }

      else {
         print "ERROR:  the specified path ($playlistPath) does not exist!\n";
         exit;
      }
   }
}


Here's the usage:

Code: Select all
./playlistExport.pl (URL to Subsonic's db.view) (encoded Auth ID) (playlist format) (export path)


I use this in cron to run nightly to create weekly backups, so here's my cron script

Code: Select all
FOLDERID=`date +\%w`; /path/playlistExport.pl http://localhost/music/db.view XXXXXXXXXXXXXXXX m3u /path/$FOLDERID; touch /path/$FOLDERID
Last edited by Jägs on Tue Oct 03, 2017 5:17 pm, edited 1 time in total.
Jägs
 
Posts: 82
Joined: Wed Apr 06, 2011 9:52 pm

Re: playlist export via Perl

Postby MikeLegit » Thu Sep 14, 2017 7:44 pm

Can you explain how to obtain the (encoded Auth ID)? So far I have found this string in subsonic.script, however using enc:2159373669417521 gives me the empty username error below.

[/share/CACHEDEV1_DATA/.qpkg/QSubSonic] # cat db/subsonic.script |grep mike
INSERT INTO USER VALUES('mike','enc:2159373669417521',169449424182,243543408,0,FALSE,'myemail@gmail.com')

Code: Select all
# ./playlistExport.pl http://192.168.1.5:4040/db.view enc:2159373669417521 m3u /root/3/playlist
Unquoted string "form" may clash with future reserved word at ./playlistExport.pl line 54.
Unquoted string "playlist" may clash with future reserved word at ./playlistExport.pl line 86.
Unquoted string "form" may clash with future reserved word at ./playlistExport.pl line 103.
Unquoted string "song" may clash with future reserved word at ./playlistExport.pl line 129.
Argument "data" isn't numeric in subtraction (-) at ./playlistExport.pl line 53.
Argument "form" isn't numeric in subtraction (-) at ./playlistExport.pl line 53.
playlistHTML = HTTP/1.1 401 Empty Username
Connection: close
Server: Jetty(6.1.x)
WWW-Authenticate: Basic realm="Subsonic"
Content-Length: 1389
Content-Type: text/html; charset=iso-8859-1
Client-Date: Thu, 14 Sep 2017 19:53:06 GMT
Client-Peer: 192.168.1.5:4040
Client-Response-Num: 1
Title: Error 401

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"/>
<title>Error 401 </title>
</head>
<body><h2>HTTP ERROR: 401</h2><pre>Empty Username</pre>
<p>RequestURI=/db.view</p><p><i><small><a href="http://jetty.mortbay.org/">Powered by Jetty://</a></small></i></p><br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>
<br/>

</body>
</html>



-ML
This forum is blocking my ISP with their aggressive filtering, so I can only login via my phone. After all my contributions (and more than 2X the recommend donation), the site admin is unwilling to help. You can find me at SmallNetBuilder Forums.
MikeLegit
 
Posts: 35
Joined: Mon Apr 01, 2013 2:18 pm

Re: playlist export via Perl

Postby Jägs » Tue Oct 03, 2017 5:16 pm

Sorry, just saw your post. I was revisiting because I had to reinstall my server and needed to grab the script.

For the AuthID, just take your username and password, with a colon in between them, and run it through Base64 encoding. You can use this website, if you want:

https://www.base64encode.org/

So, if my username is "user," and my password is "password," I would plug in "user:password" into the site above, which would generate:

dXNlcjpwYXNzd29yZA==

Then you should be good to go. I've updated the script above to be more clear on this.
Jägs
 
Posts: 82
Joined: Wed Apr 06, 2011 9:52 pm


Return to General

Who is online

Users browsing this forum: No registered users and 2 guests