# 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;