# This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # 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. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . ################### Localfiles class ################# package Programme::localfiles; use Env qw[@PATH]; #use MP3::Info; # better eval this module use Fcntl; use HTML::Entities; use strict; use URI; use Cwd 'abs_path'; use File::Basename; # Inherit from Programme class use base 'Programme'; # Class vars # Global options my $opt; # Constructor # Usage: $prog{$pid} = Programme->new( 'pid' => $pid, 'name' => $name, ); sub new { my $type = shift; my %params = @_; my $self = {}; for (keys %params) { $self->{$_} = $params{$_}; } # Ensure the subclass $opt var is pointing to the Superclass global optref $opt = $Programme::optref; bless $self, $type; } sub index_min { return 900001 } sub index_max { return 999999 } # Class cmdline Options sub opt_format { return { localfilesdirs => [ 0, "localfilesdirs=s", 'Config', '--localfilesdirs [,dir,]', "Directories/Folders to scan for new files"], outputlocalfiles => [ 1, "outputlocalfiles=s", 'Output', '--outputlocalfiles ', "Output directory for localfiles recordings"], }; } # Method to return optional list_entry format sub optional_list_entry_format { my $prog = shift; my @format; for ( qw/ channel categories / ) { push @format, $prog->{$_} if defined $prog->{$_}; } return ', '.join ', ', @format; } # cache expiry in seconds sub expiry { return ( 999999999999 ); # forever } # Returns the modes to try for this prog type sub modelist { return 'localfiles'; } # Recursive function to recursively find files in a directory sub find_matching_files { my $dir = shift; my $file_regex = shift || '.+'; $dir =~ s|\/+$||g; my @filelist; my ( $node, $lvl_counter, $list_length ); if ( opendir( DIR, $dir ) ) { my @lines = grep !/^(\.\.|\.)\/?$/, readdir( DIR ); closedir( DIR ); for ( @lines ) { $node = $dir.'/'.$_; if( -d $node && ! -l $node ) { push @filelist, find_matching_files( $node, $file_regex ); #main::logger "DEBUG: dir = $node\n"; } elsif ( $node =~ m{$file_regex}i ) { push @filelist, $node; #main::logger "DEBUG: file = $node\n"; } } } return @filelist; } # Usage: Programme::localfiles->get_links( $progref, 'localfiles' ); sub get_links { shift; # ignore obj ref my $progref = shift; my $prog_type = shift; # Check if we have MP3::Info eval "use MP3::Info"; if ($@) { main::logger "WARNING: Please install the MP3::Info perl module to use '$prog_type' plugin\n"; #main::logger "WARNING: Please download and run latest installer or install the MP3::Info perl module to use '$prog_type' plugin\n"; return 0; }; # error if no dirs specified if ( ! $opt->{localfilesdirs} ) { main::logger "ERROR: Please set --localfilesdirs to point to your collection if using the '$prog_type' plugin\n"; return 1; } main::logger "INFO: Getting Local Files Index from $opt->{localfilesdirs}\n"; # Get mp3 file list my @files; my $ext = 'mp3'; for my $scandir ( split /,/, $opt->{localfilesdirs} ) { main::logger "INFO: Scanning $scandir\n"; my @scan = find_matching_files( $scandir, '.+\.'.$ext.'$' ); main::logger "INFO: Got ".($#scan+1)." $ext files\n"; push @files, @scan; } for my $file ( @files ) { chomp( $file ); my $pid = abs_path( $file ); # Read MP3 tags into a hash for file my $info = get_mp3info( $file ); my $tag = get_mp3tag( $file, undef, undef, 1 ); # dump tags #main::logger "TAG: $_=$tag->{$_}\n" for keys %{ $tag }; # Clean data - removes unicode :-( #StringUtils::clean_utf8_and_whitespace( $tag->{$_} ) for keys %{ $tag }; # TAGS: YEAR,RIPPING TOOL,ARTIST,COMMENT,TITLE,ALBUM,RELEASE TYPE,GENRE,RIP DATE,TRACKNUM,TAGVERSION,SOURCE # INFO: SIZE,OFFSET,MS,STEREO,SECS,PADDING,LAME,MM,COPYRIGHT,SS,LAYER,MODE,FREQUENCY,VBR,TIME,FRAMES,BITRATE,VBR_SCALE,VERSION,FRAME_LENGTH # Skip if there is no track name if ( ! ( $tag->{'TITLE'} && $tag->{'ALBUM'} && $tag->{'ARTIST'} ) ) { ## Maybe guess the metadata from the filename here? #main::logger "WARNING: $pid has no title - skipping\n" if $opt->{verbose}; #next; main::logger "WARNING: $pid has no title - guessing\n" if $opt->{verbose}; if ( basename( $file ) =~ m{^(\d*?)[\w\-]*?(.+?)\.(\w+)$} ) { #$tag->{'TITLE'} = "$tag->{'TRACKNUM'}_$tag->{'ALBUM'}" if $tag->{'TRACKNUM'} && $tag->{'ALBUM'} && ! $tag->{'TITLE'}; $tag->{'TITLE'} = $tag->{'TITLE'} || $2; $tag->{'ALBUM'} = $tag->{'ALBUM'} || $2; $tag->{'ARTIST'} = $tag->{'ARTIST'} || $2; } } main::logger '.'; my @category; push @category, $tag->{'GENRE'}; my @description; for ( "($info->{'BITRATE'}kbps/$info->{'FREQUENCY'}kHz)", $tag->{'COMMENT'}, $tag->{'RIPPING TOOL'} ) { push @description, $_ if $_; } # Get thumbnail from header my $thumbnail; my $dir = dirname( $file ); # better to use opendir and regex search filename for ( qw/ front.jpg cover.jpg AlbumArtSmall.jpg / ) { $thumbnail = "file://${dir}/${_}" if -f "${dir}/${_}"; } # Skip if this pid is a duplicate if ( defined $progref->{$pid} ) { main::logger "WARNING: '$pid, $progref->{$pid}->{name} - $progref->{$pid}->{episode}, $progref->{$pid}->{channel}' already exists (this channel = $_)\n" if $opt->{verbose}; next; } # build data structure $progref->{$pid} = Programme::localfiles->new( 'pid' => $pid, 'name' => $tag->{'ALBUM'}, 'versions' => 'default', 'episode' => $tag->{'TITLE'}, 'desc' => join(', ', @description), 'available' => $tag->{'YEAR'} || $tag->{'RIP DATE'}, 'duration' => $info->{'SECS'}, 'thumbnail' => $thumbnail, 'channel' => $tag->{'ARTIST'}, 'categories' => join(',', @category), 'type' => $prog_type, 'web' => "file://${file}", ); # Sanitize element values for ( values %{ $progref->{$pid} } ) { s/[\n\r]/ /g; s/(^[\s_,]+|[\s_,]+$)//g; } } main::logger "\n"; return 0; } # Gets media streams data for this version pid # $media = file|undef sub get_stream_data { my ( $prog, $verpid, $media ) = @_; my $data = {}; $opt->{quiet} = 0 if $opt->{streaminfo}; $data->{localfiles}->{ext} = $prog->{web}; $data->{localfiles}->{ext} =~ s|^.*\.(\w+)$|$1|g; $data->{localfiles}->{streamer} = 'filestreamonly'; $data->{localfiles}->{streamurl} = $prog->{web}; $data->{localfiles}->{type} = 'Local File stream'; # Return a hash with media => url if '' is specified - otherwise just the specified url if ( ! $media ) { return $data; } else { # Make sure this hash exists before we pass it back... $data->{$media}->{exists} = 0 if not defined $data->{$media}; return $data->{$media}; } } sub download { my ( $prog, $ua, $mode, $version, $version_pid ) = ( @_ ); # if subsonly required then skip return 'skip' if $opt->{subsonly}; # Determine the correct filename and extension for this download my $filename_orig = $prog->{pid}; $filename_orig =~ s|^.+/(.+?)\.\w+$|$1|g; $prog->{ext} = $prog->{streams}->{$version}->{$mode}->{ext}; # Determine the correct filenames for this download return 'skip' if $prog->generate_filenames( $ua, " - $filename_orig" ); # Create dir for prog if not streaming-only if ( ( ! ( $opt->{stdout} && $opt->{nowrite} ) ) && ( ! $opt->{test} ) ) { $prog->create_dir(); } # Skip from here if we are only testing downloads return 1 if $opt->{test}; # Instantiate new streamer based on streamdata my $class = "Streamer::$prog->{streams}->{$version}->{$mode}->{streamer}"; my $stream = $class->new; return $stream->get( $ua, $prog->{pid}, $prog ); } 1;