| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | =head1 NAME | 
| 2 |  |  |  |  |  |  |  | 
| 3 |  |  |  |  |  |  | StreamFinder - Fetch actual raw streamable URLs from various radio-station, video & podcast websites. | 
| 4 |  |  |  |  |  |  |  | 
| 5 |  |  |  |  |  |  | =head1 INSTALLATION | 
| 6 |  |  |  |  |  |  |  | 
| 7 |  |  |  |  |  |  | To install this module, run the following commands: | 
| 8 |  |  |  |  |  |  |  | 
| 9 |  |  |  |  |  |  | perl Makefile.PL | 
| 10 |  |  |  |  |  |  |  | 
| 11 |  |  |  |  |  |  | make | 
| 12 |  |  |  |  |  |  |  | 
| 13 |  |  |  |  |  |  | make test | 
| 14 |  |  |  |  |  |  |  | 
| 15 |  |  |  |  |  |  | make install | 
| 16 |  |  |  |  |  |  |  | 
| 17 |  |  |  |  |  |  | =head1 AUTHOR | 
| 18 |  |  |  |  |  |  |  | 
| 19 |  |  |  |  |  |  | This module is Copyright (C) 2017-2023 by | 
| 20 |  |  |  |  |  |  |  | 
| 21 |  |  |  |  |  |  | Jim Turner, C<<  >> | 
| 22 |  |  |  |  |  |  |  | 
| 23 |  |  |  |  |  |  | Email: turnerjw784@yahoo.com | 
| 24 |  |  |  |  |  |  |  | 
| 25 |  |  |  |  |  |  | All rights reserved. | 
| 26 |  |  |  |  |  |  |  | 
| 27 |  |  |  |  |  |  | You may distribute this module under the terms of either the GNU General | 
| 28 |  |  |  |  |  |  | Public License or the Artistic License, as specified in the Perl README file. | 
| 29 |  |  |  |  |  |  |  | 
| 30 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 31 |  |  |  |  |  |  |  | 
| 32 |  |  |  |  |  |  | #!/usr/bin/perl | 
| 33 |  |  |  |  |  |  |  | 
| 34 |  |  |  |  |  |  | use strict; | 
| 35 |  |  |  |  |  |  |  | 
| 36 |  |  |  |  |  |  | use StreamFinder; | 
| 37 |  |  |  |  |  |  |  | 
| 38 |  |  |  |  |  |  | die "..usage:  $0 URL\n"  unless ($ARGV[0]); | 
| 39 |  |  |  |  |  |  |  | 
| 40 |  |  |  |  |  |  | my $station = new StreamFinder($ARGV[0]); | 
| 41 |  |  |  |  |  |  |  | 
| 42 |  |  |  |  |  |  | die "Invalid URL or no streams found!\n"  unless ($station); | 
| 43 |  |  |  |  |  |  |  | 
| 44 |  |  |  |  |  |  | my $firstStream = $station->get(); | 
| 45 |  |  |  |  |  |  |  | 
| 46 |  |  |  |  |  |  | print "First Stream URL=$firstStream\n"; | 
| 47 |  |  |  |  |  |  |  | 
| 48 |  |  |  |  |  |  | my $url = $station->getURL(); | 
| 49 |  |  |  |  |  |  |  | 
| 50 |  |  |  |  |  |  | print "Stream URL=$url\n"; | 
| 51 |  |  |  |  |  |  |  | 
| 52 |  |  |  |  |  |  | my $stationTitle = $station->getTitle(); | 
| 53 |  |  |  |  |  |  |  | 
| 54 |  |  |  |  |  |  | print "Title=$stationTitle\n"; | 
| 55 |  |  |  |  |  |  |  | 
| 56 |  |  |  |  |  |  | my $stationDescription = $station->getTitle('desc'); | 
| 57 |  |  |  |  |  |  |  | 
| 58 |  |  |  |  |  |  | print "Description=$stationDescription\n"; | 
| 59 |  |  |  |  |  |  |  | 
| 60 |  |  |  |  |  |  | my $stationID = $station->getID(); | 
| 61 |  |  |  |  |  |  |  | 
| 62 |  |  |  |  |  |  | print "Station ID=$stationID\n"; | 
| 63 |  |  |  |  |  |  |  | 
| 64 |  |  |  |  |  |  | my $artist = $station->{'artist'}; | 
| 65 |  |  |  |  |  |  |  | 
| 66 |  |  |  |  |  |  | print "Artist=$artist\n"  if ($artist); | 
| 67 |  |  |  |  |  |  |  | 
| 68 |  |  |  |  |  |  | my $genre = $station->{'genre'}; | 
| 69 |  |  |  |  |  |  |  | 
| 70 |  |  |  |  |  |  | print "Genre=$genre\n"  if ($genre); | 
| 71 |  |  |  |  |  |  |  | 
| 72 |  |  |  |  |  |  | my $icon_url = $station->getIconURL(); | 
| 73 |  |  |  |  |  |  |  | 
| 74 |  |  |  |  |  |  | if ($icon_url) {   #SAVE THE ICON TO A TEMP. FILE: | 
| 75 |  |  |  |  |  |  |  | 
| 76 |  |  |  |  |  |  | print "Icon URL=$icon_url=\n"; | 
| 77 |  |  |  |  |  |  |  | 
| 78 |  |  |  |  |  |  | my ($image_ext, $icon_image) = $station->getIconData(); | 
| 79 |  |  |  |  |  |  |  | 
| 80 |  |  |  |  |  |  | if ($icon_image && open IMGOUT, ">/tmp/${stationID}.$image_ext") { | 
| 81 |  |  |  |  |  |  |  | 
| 82 |  |  |  |  |  |  | binmode IMGOUT; | 
| 83 |  |  |  |  |  |  |  | 
| 84 |  |  |  |  |  |  | print IMGOUT $icon_image; | 
| 85 |  |  |  |  |  |  |  | 
| 86 |  |  |  |  |  |  | close IMGOUT; | 
| 87 |  |  |  |  |  |  |  | 
| 88 |  |  |  |  |  |  | print "...Icon image downloaded to (/tmp/${stationID}.$image_ext)\n"; | 
| 89 |  |  |  |  |  |  |  | 
| 90 |  |  |  |  |  |  | } | 
| 91 |  |  |  |  |  |  |  | 
| 92 |  |  |  |  |  |  | } | 
| 93 |  |  |  |  |  |  |  | 
| 94 |  |  |  |  |  |  | my $stream_count = $station->count(); | 
| 95 |  |  |  |  |  |  |  | 
| 96 |  |  |  |  |  |  | print "--Stream count=$stream_count=\n"; | 
| 97 |  |  |  |  |  |  |  | 
| 98 |  |  |  |  |  |  | my @streams = $station->get(); | 
| 99 |  |  |  |  |  |  |  | 
| 100 |  |  |  |  |  |  | foreach my $s (@streams) { | 
| 101 |  |  |  |  |  |  |  | 
| 102 |  |  |  |  |  |  | print "------ stream URL=$s=\n"; | 
| 103 |  |  |  |  |  |  |  | 
| 104 |  |  |  |  |  |  | } | 
| 105 |  |  |  |  |  |  |  | 
| 106 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 107 |  |  |  |  |  |  |  | 
| 108 |  |  |  |  |  |  | StreamFinder accepts a webpage URL for a valid radio station, video, or podcast | 
| 109 |  |  |  |  |  |  | / episode URL on supported websites and returns the actual stream URL(s), | 
| 110 |  |  |  |  |  |  | title, and cover art icon for that station / podcast / video.  The purpose is | 
| 111 |  |  |  |  |  |  | that one needs one of these URLs in order to have the option to stream the | 
| 112 |  |  |  |  |  |  | station / podcast / video in one's own choice of media player software rather | 
| 113 |  |  |  |  |  |  | than using their web browser and accepting flash, ads, javascript, cookies, | 
| 114 |  |  |  |  |  |  | trackers, web-bugs, and other crapware associated with that method of play. | 
| 115 |  |  |  |  |  |  | The author created and uses his own custom all-purpose media player called | 
| 116 |  |  |  |  |  |  | "Fauxdacious Media Player" (his custom forked version of the open-source | 
| 117 |  |  |  |  |  |  | "Audacious Audio Player).  "Fauxdacious" | 
| 118 |  |  |  |  |  |  | (L) incorporates this module via | 
| 119 |  |  |  |  |  |  | a Perl helper-script to decode and play streams, along with their titles / | 
| 120 |  |  |  |  |  |  | station names, and station / podcast / video icons, artists / channel names, | 
| 121 |  |  |  |  |  |  | genres, and descriptions! | 
| 122 |  |  |  |  |  |  |  | 
| 123 |  |  |  |  |  |  | Please NOTE:  StreamFinder is a module, NOT a standalone application.  It is | 
| 124 |  |  |  |  |  |  | designed to be used by other Perl applications.  To create your own very simple | 
| 125 |  |  |  |  |  |  | application just to fetch stream data manually, simply grab the code in the | 
| 126 |  |  |  |  |  |  | B section above, save it to an executable text file, ie. | 
| 127 |  |  |  |  |  |  | I, and run it from the command line with a supported streaming | 
| 128 |  |  |  |  |  |  | site URL as the argument.  You can then edit it to tailor it to your needs. | 
| 129 |  |  |  |  |  |  |  | 
| 130 |  |  |  |  |  |  | The currently-supported websites are: | 
| 131 |  |  |  |  |  |  | podcasts.apple.com podcasts (L), | 
| 132 |  |  |  |  |  |  | bitchute.com videos (L), | 
| 133 |  |  |  |  |  |  | blogger.com videos (L), | 
| 134 |  |  |  |  |  |  | brandnewtube.com and ugetube.com videos (L), | 
| 135 |  |  |  |  |  |  | brighteon.com videos (L), | 
| 136 |  |  |  |  |  |  | castbox.fm podcasts (L), | 
| 137 |  |  |  |  |  |  | goodpods.com podcasts (L), | 
| 138 |  |  |  |  |  |  | podcasts.google.com podcasts (L), | 
| 139 |  |  |  |  |  |  | iheartradio.com radio stations and podcasts (L), | 
| 140 |  |  |  |  |  |  | www.internetradio.com radio stations (L), | 
| 141 |  |  |  |  |  |  | www.linktv.org videos (L), | 
| 142 |  |  |  |  |  |  | onlineradiobox.com radio stations (L), | 
| 143 |  |  |  |  |  |  | odysee.com videos (L), | 
| 144 |  |  |  |  |  |  | podbean.com podcasts (L), | 
| 145 |  |  |  |  |  |  | podcastaddict.com podcasts (L), | 
| 146 |  |  |  |  |  |  | podchaser.com podcasts (L), | 
| 147 |  |  |  |  |  |  | radio.net radio stations (L), | 
| 148 |  |  |  |  |  |  | rcast.net radio stations (L), | 
| 149 |  |  |  |  |  |  | rumble.com videos (L), | 
| 150 |  |  |  |  |  |  | sermonaudio.com sermons: audio and video (L), | 
| 151 |  |  |  |  |  |  | soundcloud.com (non-paywalled) songs (L), | 
| 152 |  |  |  |  |  |  | spreaker.com podcasts (L), | 
| 153 |  |  |  |  |  |  | tunein.com (non-paywalled) radio stations and podcasts | 
| 154 |  |  |  |  |  |  | (L), vimeo.com videos (L), | 
| 155 |  |  |  |  |  |  | youtube.com, et. al and other sites that youtube-dl supports | 
| 156 |  |  |  |  |  |  | (L), | 
| 157 |  |  |  |  |  |  | zeno.fm radio stations and podcasts (L), | 
| 158 |  |  |  |  |  |  | and L - search any (other) webpage URL (not supported | 
| 159 |  |  |  |  |  |  | by any of the other submodules) for streams. | 
| 160 |  |  |  |  |  |  |  | 
| 161 |  |  |  |  |  |  | NOTE:  StreamFinder::Podcastaddict is now considered depreciated and may be | 
| 162 |  |  |  |  |  |  | removed in a later StreamFinder release as it now requires a specific valid | 
| 163 |  |  |  |  |  |  | episode page to fetch streams from, as Podcastaddict.com has javascripted up | 
| 164 |  |  |  |  |  |  | their podcast pages now to the point that it is no longer possible to obtain | 
| 165 |  |  |  |  |  |  | a playlist or first episode from them via our scripts. | 
| 166 |  |  |  |  |  |  |  | 
| 167 |  |  |  |  |  |  | NOTE:  StreamFinder::Reciva and StreamFinder::Radionomy have been removed, as | 
| 168 |  |  |  |  |  |  | those sites have now closed down. | 
| 169 |  |  |  |  |  |  |  | 
| 170 |  |  |  |  |  |  | NOTE:  For many sites, ie. Youtube, Vimeo, Apple, Spreaker, Castbox, Google, | 
| 171 |  |  |  |  |  |  | etc. the "station" object actually refers to a specific video or podcast, but | 
| 172 |  |  |  |  |  |  | functions the same way.  For some others, it may be a podcast episode. | 
| 173 |  |  |  |  |  |  |  | 
| 174 |  |  |  |  |  |  | Each site is supported by a separate subpackage (StreamFinder::I), | 
| 175 |  |  |  |  |  |  | which is determined and selected based on the URL argument passed to it when | 
| 176 |  |  |  |  |  |  | the StreamFinder object is created.  The methods are overloaded by the selected | 
| 177 |  |  |  |  |  |  | subpackage's methods.  An example would be B. | 
| 178 |  |  |  |  |  |  |  | 
| 179 |  |  |  |  |  |  | Please see the POD. documentation for each subpackage for important additional | 
| 180 |  |  |  |  |  |  | information on options and features specific to each site / subpackage! | 
| 181 |  |  |  |  |  |  |  | 
| 182 |  |  |  |  |  |  | One or more playable streams can be returned for each station / video / | 
| 183 |  |  |  |  |  |  | podcast, along with at least a "title" (station name / video or podcast episode | 
| 184 |  |  |  |  |  |  | title) and an icon image URL ("iconurl" - if found).  Additional information | 
| 185 |  |  |  |  |  |  | that MAY be fetched is a (larger?) banner image ("imageurl"), a (longer?) | 
| 186 |  |  |  |  |  |  | "description", an "artist" / author, a "genre", and / or a "year" (podcasts, | 
| 187 |  |  |  |  |  |  | videos, etc.), an AlbumArtist / channel URL, and possibly a second | 
| 188 |  |  |  |  |  |  | icon image for the channel (podcasts and videos).  Some sites also provide | 
| 189 |  |  |  |  |  |  | radio stations' FCC call letters ("fccid").  For icon and image URLs, | 
| 190 |  |  |  |  |  |  | functions exist (getIconData() and getImageData()) to fetch the actual binary | 
| 191 |  |  |  |  |  |  | data and mime type for downloading to local storage for use by your | 
| 192 |  |  |  |  |  |  | application or preferred media player.  NOTE:  StreamFinder::Anystream is not | 
| 193 |  |  |  |  |  |  | able to return much beyond the stream URLs it finds, but please see it's POD | 
| 194 |  |  |  |  |  |  | documentation for details on what it is able to return. | 
| 195 |  |  |  |  |  |  |  | 
| 196 |  |  |  |  |  |  | If you have another streaming site that is not supported, first, make sure | 
| 197 |  |  |  |  |  |  | you have B installed and see if B can | 
| 198 |  |  |  |  |  |  | successfully fetch any streams for it.  If not, then please file a feature | 
| 199 |  |  |  |  |  |  | request via email or the CPAN bug system, or (for faster service), provide a | 
| 200 |  |  |  |  |  |  | Perl patch module / program source that can extract some or all of the | 
| 201 |  |  |  |  |  |  | necessary information for streams on that site and I'll consider it!  The | 
| 202 |  |  |  |  |  |  | easiest way to do this is to take one of the existing submodules, copy it to | 
| 203 |  |  |  |  |  |  | "StreamFinder::I.pm", modify it (and the POD docs) to your | 
| 204 |  |  |  |  |  |  | specific site's needs, test it on several of their pages (see the "SYNOPSIS" | 
| 205 |  |  |  |  |  |  | code above), and send it to me (That's what I do when I want to add a | 
| 206 |  |  |  |  |  |  | new site)! | 
| 207 |  |  |  |  |  |  |  | 
| 208 |  |  |  |  |  |  | =head1 SUBROUTINES/METHODS | 
| 209 |  |  |  |  |  |  |  | 
| 210 |  |  |  |  |  |  | =over 4 | 
| 211 |  |  |  |  |  |  |  | 
| 212 |  |  |  |  |  |  | =item B(I [, I ]) | 
| 213 |  |  |  |  |  |  |  | 
| 214 |  |  |  |  |  |  | Accepts a URL and creates and returns a new station, video, or | 
| 215 |  |  |  |  |  |  | podcast object, or I if the URL is not a valid station or | 
| 216 |  |  |  |  |  |  | no streams are found. | 
| 217 |  |  |  |  |  |  |  | 
| 218 |  |  |  |  |  |  | NOTE:  Depending on the type of site being queried, the "station | 
| 219 |  |  |  |  |  |  | object" can be either a streaming station, a video, or a podcast, | 
| 220 |  |  |  |  |  |  | but works the same way (method calls, arguments, etc.). | 
| 221 |  |  |  |  |  |  |  | 
| 222 |  |  |  |  |  |  | NOTE:  A full URL must be specified here, but if using any of the | 
| 223 |  |  |  |  |  |  | subpackage modules directly instead, then either a full URL OR just | 
| 224 |  |  |  |  |  |  | the station / video / podcast's site ID may be used!  Reason being | 
| 225 |  |  |  |  |  |  | that this function parses the full URL to determine which subpackage | 
| 226 |  |  |  |  |  |  | (site) module to use. | 
| 227 |  |  |  |  |  |  |  | 
| 228 |  |  |  |  |  |  | I can vary depending on the type of site that is | 
| 229 |  |  |  |  |  |  | being queried.  One option common to all sites is I<-debug>, which | 
| 230 |  |  |  |  |  |  | turns on debugging output.  A numeric option can follow specifying | 
| 231 |  |  |  |  |  |  | the level (0, 1, or 2).  0 is none, 1 is basic, 2 is detailed. | 
| 232 |  |  |  |  |  |  | Default:  B<1> (if I<-debug> is specified).  Warning: 2 will dump a ton | 
| 233 |  |  |  |  |  |  | of output (mostly the HTML of the web page being parsed! | 
| 234 |  |  |  |  |  |  |  | 
| 235 |  |  |  |  |  |  | One specific option (I<-omit>, added as of v1.45) permits omitting | 
| 236 |  |  |  |  |  |  | specific submodules which are currently installed from being considered. | 
| 237 |  |  |  |  |  |  | For example, to NOT handle Youtube videos nor use the fallback | 
| 238 |  |  |  |  |  |  | "Anystream" module, specify:  I<-omit> => I<"Youtube,Anystream">, which | 
| 239 |  |  |  |  |  |  | will cause StreamFinder::Anystream and StreamFinder::Youtube to not be used | 
| 240 |  |  |  |  |  |  | for the stream search.  Default is for all installed submodules to be | 
| 241 |  |  |  |  |  |  | considered.  NOTE:  Omitting a module from being considered when seeking | 
| 242 |  |  |  |  |  |  | to match the correct module by site URL does NOT prevent that | 
| 243 |  |  |  |  |  |  | module from being invoked by a selected module for an embedded link, OR | 
| 244 |  |  |  |  |  |  | in the case of StreamFinder::Youtube being omitted, will still be invoked, | 
| 245 |  |  |  |  |  |  | if required or needed by a non-omitted module initially selected! | 
| 246 |  |  |  |  |  |  |  | 
| 247 |  |  |  |  |  |  | Another global option (applicable to all submodules) is the I<-secure> | 
| 248 |  |  |  |  |  |  | option who's argument can be either 0 or 1 (I or I).  If 1, | 
| 249 |  |  |  |  |  |  | then only secure ("https://") streams will be returned.  NOTE, it's | 
| 250 |  |  |  |  |  |  | possible that some sites may only contain insecure ("http://") streams, | 
| 251 |  |  |  |  |  |  | which won't return any streams if this option is specified.  Therefore, | 
| 252 |  |  |  |  |  |  | it may be necessary, if setting this option globally, to set it to | 
| 253 |  |  |  |  |  |  | zero in the config. files for those specific modules, if you determine | 
| 254 |  |  |  |  |  |  | that to be the case (I have not tested all sites for that).  Default: | 
| 255 |  |  |  |  |  |  | I<-secure> is 0 (false) - return all streams (http and https). | 
| 256 |  |  |  |  |  |  |  | 
| 257 |  |  |  |  |  |  | Any other options (including I<-debug>) will be passed to the submodule | 
| 258 |  |  |  |  |  |  | (if any) that handles the URL you pass in, but note, submodules accept | 
| 259 |  |  |  |  |  |  | different options and ignore ones they do not recognize.  Valid values | 
| 260 |  |  |  |  |  |  | for some options can also vary across different submodules.  A better | 
| 261 |  |  |  |  |  |  | way to change default options for one or more submodules is to set up | 
| 262 |  |  |  |  |  |  | submodule configuration files for the ones you wish to change. | 
| 263 |  |  |  |  |  |  |  | 
| 264 |  |  |  |  |  |  | Additional options: | 
| 265 |  |  |  |  |  |  |  | 
| 266 |  |  |  |  |  |  | I<-hls_bandwidth> => "I" | 
| 267 |  |  |  |  |  |  |  | 
| 268 |  |  |  |  |  |  | Limit HLS (m3u8) streams that contain a list of other HLS streams of varying | 
| 269 |  |  |  |  |  |  | BANDWIDTH values (in BITS per second) by selecting the highest bitrate stream | 
| 270 |  |  |  |  |  |  | at or below the specified limit when I<$stream>->I is called. | 
| 271 |  |  |  |  |  |  |  | 
| 272 |  |  |  |  |  |  | DEFAULT I<-none-> (no limiting by bitrate). | 
| 273 |  |  |  |  |  |  |  | 
| 274 |  |  |  |  |  |  | I<-log> => "I" | 
| 275 |  |  |  |  |  |  |  | 
| 276 |  |  |  |  |  |  | Specify path to a log file.  If a valid and writable file is specified, A line | 
| 277 |  |  |  |  |  |  | will be appended to this file every time one or more streams is successfully | 
| 278 |  |  |  |  |  |  | fetched for a url. | 
| 279 |  |  |  |  |  |  |  | 
| 280 |  |  |  |  |  |  | DEFAULT I<-none-> (no logging). | 
| 281 |  |  |  |  |  |  |  | 
| 282 |  |  |  |  |  |  | I<-logfmt> specifies a format string for lines written to the log file. | 
| 283 |  |  |  |  |  |  |  | 
| 284 |  |  |  |  |  |  | DEFAULT "I<[time] [url] - [site]: [title] ([total])>". | 
| 285 |  |  |  |  |  |  |  | 
| 286 |  |  |  |  |  |  | The valid field I<[variables]> are:  [stream]: The url of the first/best stream | 
| 287 |  |  |  |  |  |  | found.  [site]:  The site (submodule) name matching the webpage url. | 
| 288 |  |  |  |  |  |  | [url]:  The url searched for streams.  [time]: Perl timestamp when the line was | 
| 289 |  |  |  |  |  |  | logged.  [title], [artist], [album], [description], [year], [genre], [total], | 
| 290 |  |  |  |  |  |  | [albumartist]:  The corresponding field data returned (or "I<-na->", | 
| 291 |  |  |  |  |  |  | if no value). | 
| 292 |  |  |  |  |  |  |  | 
| 293 |  |  |  |  |  |  | =item $station->B(['playlist']) | 
| 294 |  |  |  |  |  |  |  | 
| 295 |  |  |  |  |  |  | Returns an array of strings representing all stream URLs found. | 
| 296 |  |  |  |  |  |  | If I<"playlist"> is specified, then an extended m3u playlist is returned | 
| 297 |  |  |  |  |  |  | instead of stream url(s).  NOTE:  For podcast sites, if an author / channel | 
| 298 |  |  |  |  |  |  | page url is given, rather than an individual podcast episode's url, get() | 
| 299 |  |  |  |  |  |  | returns the first (latest?) podcast episode found, and get("playlist") returns | 
| 300 |  |  |  |  |  |  | an extended m3u playlist containing the urls, titles, etc. for all the podcast | 
| 301 |  |  |  |  |  |  | episodes found on that page url from latest to oldest. | 
| 302 |  |  |  |  |  |  |  | 
| 303 |  |  |  |  |  |  | =item $station->B([I]) | 
| 304 |  |  |  |  |  |  |  | 
| 305 |  |  |  |  |  |  | Similar to B() except it only returns a single stream representing | 
| 306 |  |  |  |  |  |  | the first valid stream found. | 
| 307 |  |  |  |  |  |  |  | 
| 308 |  |  |  |  |  |  | Current options are:  I<"random">, I<"nopls">, and I<"noplaylists">. | 
| 309 |  |  |  |  |  |  | By default, the first ("best"?) stream is returned.  If I<"random"> is | 
| 310 |  |  |  |  |  |  | specified, then a random one is selected from the list of streams found. | 
| 311 |  |  |  |  |  |  | If I<"nopls"> is specified, and the stream to be returned is a ".pls" playlist, | 
| 312 |  |  |  |  |  |  | it is first fetched and the first entry (or a random entry if I<"random"> is | 
| 313 |  |  |  |  |  |  | specified) is returned.  This is needed by Fauxdacious Mediaplayer. | 
| 314 |  |  |  |  |  |  | If I<"noplaylists"> is specified, and the stream to be returned is a | 
| 315 |  |  |  |  |  |  | "playlist" (either .pls or .m3u? extension), it is first fetched and the first | 
| 316 |  |  |  |  |  |  | entry (or a random entry if I<"random"> is specified) in the playlist | 
| 317 |  |  |  |  |  |  | is returned. | 
| 318 |  |  |  |  |  |  |  | 
| 319 |  |  |  |  |  |  | =item $station->B() | 
| 320 |  |  |  |  |  |  |  | 
| 321 |  |  |  |  |  |  | Returns the number of streams found for the station. | 
| 322 |  |  |  |  |  |  |  | 
| 323 |  |  |  |  |  |  | =item $station->B(['fccid']) | 
| 324 |  |  |  |  |  |  |  | 
| 325 |  |  |  |  |  |  | Returns the station's site ID (default), or station's FCC | 
| 326 |  |  |  |  |  |  | call-letters ("fccid") for applicable sites and stations. | 
| 327 |  |  |  |  |  |  |  | 
| 328 |  |  |  |  |  |  | =item $station->B(['desc']) | 
| 329 |  |  |  |  |  |  |  | 
| 330 |  |  |  |  |  |  | Returns the station's title, (or long description, if "desc" specified). | 
| 331 |  |  |  |  |  |  |  | 
| 332 |  |  |  |  |  |  | NOTE:  Some sights do not support a separate long description field, | 
| 333 |  |  |  |  |  |  | so if none found, the standard title field will always be returned. | 
| 334 |  |  |  |  |  |  |  | 
| 335 |  |  |  |  |  |  | =item $station->B(['artist']) | 
| 336 |  |  |  |  |  |  |  | 
| 337 |  |  |  |  |  |  | Returns the URL for the station's "cover art" icon image, if any. | 
| 338 |  |  |  |  |  |  |  | 
| 339 |  |  |  |  |  |  | Some video and podcast sites will also provide a separate artist/channel | 
| 340 |  |  |  |  |  |  | icon.  If B<'artist'> is specified, this icon url is returned instead, | 
| 341 |  |  |  |  |  |  | if any. | 
| 342 |  |  |  |  |  |  |  | 
| 343 |  |  |  |  |  |  | =item $station->B(['artist']) | 
| 344 |  |  |  |  |  |  |  | 
| 345 |  |  |  |  |  |  | Returns a two-element array consisting of the extension (ie. "png", | 
| 346 |  |  |  |  |  |  | "gif", "jpeg", etc.) and the actual icon image (binary data), if any. | 
| 347 |  |  |  |  |  |  | This makes it easy to download the image to local storage for use by | 
| 348 |  |  |  |  |  |  | your preferred media player. | 
| 349 |  |  |  |  |  |  |  | 
| 350 |  |  |  |  |  |  | Some video and podcast sites will also provide a separate artist/channel | 
| 351 |  |  |  |  |  |  | icon.  If B<'artist'> is specified, this icon's data is returned instead, | 
| 352 |  |  |  |  |  |  | if any. | 
| 353 |  |  |  |  |  |  |  | 
| 354 |  |  |  |  |  |  | =item $station->B(['artist']) | 
| 355 |  |  |  |  |  |  |  | 
| 356 |  |  |  |  |  |  | Returns the URL for the station's "cover art" banner image, if any. | 
| 357 |  |  |  |  |  |  |  | 
| 358 |  |  |  |  |  |  | NOTE:  If no "banner image" (usually a larger image) is found, | 
| 359 |  |  |  |  |  |  | the "icon image" URL will be returned. | 
| 360 |  |  |  |  |  |  |  | 
| 361 |  |  |  |  |  |  | Some video and podcast sites will also provide a separate artist/channel | 
| 362 |  |  |  |  |  |  | image (usually larger).  If B<'artist'> is specified, this icon url is | 
| 363 |  |  |  |  |  |  | returned instead, if any. | 
| 364 |  |  |  |  |  |  |  | 
| 365 |  |  |  |  |  |  | =item $station->B(['artist']) | 
| 366 |  |  |  |  |  |  |  | 
| 367 |  |  |  |  |  |  | Returns a two-element array consisting of the extension (ie. "png", | 
| 368 |  |  |  |  |  |  | "gif", "jpeg", etc.) and the actual station's banner image | 
| 369 |  |  |  |  |  |  | (binary data).  This makes it easy to download the image to | 
| 370 |  |  |  |  |  |  | local storage for use by your preferred media player. | 
| 371 |  |  |  |  |  |  |  | 
| 372 |  |  |  |  |  |  | NOTE:  If no "banner image" (usually a larger image) is found, | 
| 373 |  |  |  |  |  |  | the "icon image" data, if any, will be returned. | 
| 374 |  |  |  |  |  |  |  | 
| 375 |  |  |  |  |  |  | Some video and podcast sites will also provide a separate artist/channel | 
| 376 |  |  |  |  |  |  | image (usually larger).  If B<'artist'> is specified, this icon's data is | 
| 377 |  |  |  |  |  |  | returned instead, if any. | 
| 378 |  |  |  |  |  |  |  | 
| 379 |  |  |  |  |  |  | =item $station->B() | 
| 380 |  |  |  |  |  |  |  | 
| 381 |  |  |  |  |  |  | Returns the station / podcast / video's type (I). | 
| 382 |  |  |  |  |  |  | (one of:  "Anystream", "Apple", "BitChute", "Blogger", "Youtube", etc. - | 
| 383 |  |  |  |  |  |  | depending on the sight that matched the URL). | 
| 384 |  |  |  |  |  |  |  | 
| 385 |  |  |  |  |  |  | Some video and podcast sites will also provide a separate artist/channel | 
| 386 |  |  |  |  |  |  | image (usually larger).  If B<'artist'> is specified, this icon url is | 
| 387 |  |  |  |  |  |  | returned instead, if any. | 
| 388 |  |  |  |  |  |  |  | 
| 389 |  |  |  |  |  |  | =back | 
| 390 |  |  |  |  |  |  |  | 
| 391 |  |  |  |  |  |  | =head1 CONFIGURATION FILES | 
| 392 |  |  |  |  |  |  |  | 
| 393 |  |  |  |  |  |  | The default root location directory for StreamFinder configuration files | 
| 394 |  |  |  |  |  |  | is "~/.config/StreamFinder".  To use an alternate location directory, | 
| 395 |  |  |  |  |  |  | specify it in the "I" environment variable, ie.: | 
| 396 |  |  |  |  |  |  | B<$ENV{STREAMFINDER} = "/etc/StreamFinder">. | 
| 397 |  |  |  |  |  |  |  | 
| 398 |  |  |  |  |  |  | =over 4 | 
| 399 |  |  |  |  |  |  |  | 
| 400 |  |  |  |  |  |  | =item ~/.config/StreamFinder/config | 
| 401 |  |  |  |  |  |  |  | 
| 402 |  |  |  |  |  |  | Optional text file for specifying various configuration options. | 
| 403 |  |  |  |  |  |  | Each option is specified on a separate line in the formats below: | 
| 404 |  |  |  |  |  |  | NOTE:  Do not follow the lines with a semicolon, comma, or any other | 
| 405 |  |  |  |  |  |  | separator.  Non-numeric I should be surrounded with quotes, either | 
| 406 |  |  |  |  |  |  | single or double.  Blank lines and lines beginning with a "#" sign as | 
| 407 |  |  |  |  |  |  | their first non-blank character are ignored as comments. | 
| 408 |  |  |  |  |  |  |  | 
| 409 |  |  |  |  |  |  | 'option' => 'value' [, ...] | 
| 410 |  |  |  |  |  |  |  | 
| 411 |  |  |  |  |  |  | 'option' => ['value1', 'value2', ...] [, ...] | 
| 412 |  |  |  |  |  |  |  | 
| 413 |  |  |  |  |  |  | 'option' => {'key1' => 'value1', 'key2' => 'value2', ...} [, ...] | 
| 414 |  |  |  |  |  |  |  | 
| 415 |  |  |  |  |  |  | and the options are loaded into a hash used by all sites | 
| 416 |  |  |  |  |  |  | (submodules) that support them.  Valid options include | 
| 417 |  |  |  |  |  |  | I<-debug> => [0|1|2] and most of the L options. | 
| 418 |  |  |  |  |  |  |  | 
| 419 |  |  |  |  |  |  | =item ~/.config/StreamFinder/I/config | 
| 420 |  |  |  |  |  |  |  | 
| 421 |  |  |  |  |  |  | Optional text file for specifying various configuration options | 
| 422 |  |  |  |  |  |  | for a specific site (submodule, ie. "Youtube" for | 
| 423 |  |  |  |  |  |  | StreamFinder::Youtube).  Each option is specified on a separate | 
| 424 |  |  |  |  |  |  | line in the formats below: | 
| 425 |  |  |  |  |  |  |  | 
| 426 |  |  |  |  |  |  | 'option' => 'value' [, ...] | 
| 427 |  |  |  |  |  |  |  | 
| 428 |  |  |  |  |  |  | 'option' => ['value1', 'value2', ...] [, ...] | 
| 429 |  |  |  |  |  |  |  | 
| 430 |  |  |  |  |  |  | 'option' => {'key1' => 'value1', 'key2' => 'value2', ...} [, ...] | 
| 431 |  |  |  |  |  |  |  | 
| 432 |  |  |  |  |  |  | and the options are loaded into a hash used only by the specific | 
| 433 |  |  |  |  |  |  | (submodule) specified.  Valid options include | 
| 434 |  |  |  |  |  |  | I<-debug> => [0|1|2] and most of the L options. | 
| 435 |  |  |  |  |  |  |  | 
| 436 |  |  |  |  |  |  | NOTE:  Options specified here override any specified in I<~/.config/StreamFinder/config>. | 
| 437 |  |  |  |  |  |  |  | 
| 438 |  |  |  |  |  |  | =back | 
| 439 |  |  |  |  |  |  |  | 
| 440 |  |  |  |  |  |  | NOTE:  Options specified in the options parameter list of the I | 
| 441 |  |  |  |  |  |  | function will override those corresponding options specified in these files. | 
| 442 |  |  |  |  |  |  |  | 
| 443 |  |  |  |  |  |  | =head1 DEPENDENCIES | 
| 444 |  |  |  |  |  |  |  | 
| 445 |  |  |  |  |  |  | L, L, L | 
| 446 |  |  |  |  |  |  |  | 
| 447 |  |  |  |  |  |  | =head1 RECCOMENDS | 
| 448 |  |  |  |  |  |  |  | 
| 449 |  |  |  |  |  |  | youtube-dl, or other compatable program such as yt-dlp, etc. | 
| 450 |  |  |  |  |  |  | (for Youtube, Bitchute, Blogger, Brighteon, Odysee, Vimeo) | 
| 451 |  |  |  |  |  |  | NOTE:  Required for Youtube, Odysee, and SoundCloud to work. | 
| 452 |  |  |  |  |  |  |  | 
| 453 |  |  |  |  |  |  | wget | 
| 454 |  |  |  |  |  |  |  | 
| 455 |  |  |  |  |  |  | =head1 BUGS | 
| 456 |  |  |  |  |  |  |  | 
| 457 |  |  |  |  |  |  | Please report any bugs or feature requests to C, or through | 
| 458 |  |  |  |  |  |  | the web interface at L. | 
| 459 |  |  |  |  |  |  | I will be notified, and then you'llautomatically be notified of progress on | 
| 460 |  |  |  |  |  |  | your bug as I make changes. | 
| 461 |  |  |  |  |  |  |  | 
| 462 |  |  |  |  |  |  | =head1 SUPPORT | 
| 463 |  |  |  |  |  |  |  | 
| 464 |  |  |  |  |  |  | You can find documentation for this module with the perldoc command. | 
| 465 |  |  |  |  |  |  |  | 
| 466 |  |  |  |  |  |  | perldoc StreamFinder | 
| 467 |  |  |  |  |  |  |  | 
| 468 |  |  |  |  |  |  | You can also look for information at: | 
| 469 |  |  |  |  |  |  |  | 
| 470 |  |  |  |  |  |  | =head1 SEE ALSO | 
| 471 |  |  |  |  |  |  |  | 
| 472 |  |  |  |  |  |  | Fauxdacious media player - (L) | 
| 473 |  |  |  |  |  |  |  | 
| 474 |  |  |  |  |  |  | =over 4 | 
| 475 |  |  |  |  |  |  |  | 
| 476 |  |  |  |  |  |  | =item * RT: CPAN's request tracker (report bugs here) | 
| 477 |  |  |  |  |  |  |  | 
| 478 |  |  |  |  |  |  | L | 
| 479 |  |  |  |  |  |  |  | 
| 480 |  |  |  |  |  |  | =item * CPAN Ratings | 
| 481 |  |  |  |  |  |  |  | 
| 482 |  |  |  |  |  |  | L | 
| 483 |  |  |  |  |  |  |  | 
| 484 |  |  |  |  |  |  | =item * Search CPAN | 
| 485 |  |  |  |  |  |  |  | 
| 486 |  |  |  |  |  |  | L | 
| 487 |  |  |  |  |  |  |  | 
| 488 |  |  |  |  |  |  | =back | 
| 489 |  |  |  |  |  |  |  | 
| 490 |  |  |  |  |  |  | =head1 LICENSE AND COPYRIGHT | 
| 491 |  |  |  |  |  |  |  | 
| 492 |  |  |  |  |  |  | Copyright 2017-2023 Jim Turner. | 
| 493 |  |  |  |  |  |  |  | 
| 494 |  |  |  |  |  |  | This program is free software; you can redistribute it and/or modify it | 
| 495 |  |  |  |  |  |  | under the terms of the the Artistic License (2.0). You may obtain a | 
| 496 |  |  |  |  |  |  | copy of the full license at: | 
| 497 |  |  |  |  |  |  |  | 
| 498 |  |  |  |  |  |  | L | 
| 499 |  |  |  |  |  |  |  | 
| 500 |  |  |  |  |  |  | Any use, modification, and distribution of the Standard or Modified | 
| 501 |  |  |  |  |  |  | Versions is governed by this Artistic License. By using, modifying or | 
| 502 |  |  |  |  |  |  | distributing the Package, you accept this license. Do not use, modify, | 
| 503 |  |  |  |  |  |  | or distribute the Package, if you do not accept this license. | 
| 504 |  |  |  |  |  |  |  | 
| 505 |  |  |  |  |  |  | If your Modified Version has been derived from a Modified Version made | 
| 506 |  |  |  |  |  |  | by someone other than you, you are nevertheless required to ensure that | 
| 507 |  |  |  |  |  |  | your Modified Version complies with the requirements of this license. | 
| 508 |  |  |  |  |  |  |  | 
| 509 |  |  |  |  |  |  | This license does not grant you the right to use any trademark, service | 
| 510 |  |  |  |  |  |  | mark, tradename, or logo of the Copyright Holder. | 
| 511 |  |  |  |  |  |  |  | 
| 512 |  |  |  |  |  |  | This license includes the non-exclusive, worldwide, free-of-charge | 
| 513 |  |  |  |  |  |  | patent license to make, have made, use, offer to sell, sell, import and | 
| 514 |  |  |  |  |  |  | otherwise transfer the Package with respect to any patent claims | 
| 515 |  |  |  |  |  |  | licensable by the Copyright Holder that are necessarily infringed by the | 
| 516 |  |  |  |  |  |  | Package. If you institute patent litigation (including a cross-claim or | 
| 517 |  |  |  |  |  |  | counterclaim) against any party alleging that the Package constitutes | 
| 518 |  |  |  |  |  |  | direct or contributory patent infringement, then this Artistic License | 
| 519 |  |  |  |  |  |  | to you shall terminate on the date that such litigation is filed. | 
| 520 |  |  |  |  |  |  |  | 
| 521 |  |  |  |  |  |  | Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER | 
| 522 |  |  |  |  |  |  | AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. | 
| 523 |  |  |  |  |  |  | THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR | 
| 524 |  |  |  |  |  |  | PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY | 
| 525 |  |  |  |  |  |  | YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR | 
| 526 |  |  |  |  |  |  | CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR | 
| 527 |  |  |  |  |  |  | CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, | 
| 528 |  |  |  |  |  |  | EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 
| 529 |  |  |  |  |  |  |  | 
| 530 |  |  |  |  |  |  | =cut | 
| 531 |  |  |  |  |  |  |  | 
| 532 |  |  |  |  |  |  | package StreamFinder; | 
| 533 |  |  |  |  |  |  |  | 
| 534 |  |  |  |  |  |  | require 5.001; | 
| 535 |  |  |  |  |  |  |  | 
| 536 | 1 |  |  | 1 |  | 66337 | use strict; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 30 |  | 
| 537 | 1 |  |  | 1 |  | 5 | use warnings; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 28 |  | 
| 538 | 1 |  |  | 1 |  | 5 | use vars qw(@ISA @EXPORT $VERSION); | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 1662 |  | 
| 539 |  |  |  |  |  |  |  | 
| 540 |  |  |  |  |  |  | our $VERSION = '2.17'; | 
| 541 |  |  |  |  |  |  | our $DEBUG = 0; | 
| 542 |  |  |  |  |  |  |  | 
| 543 |  |  |  |  |  |  | require Exporter; | 
| 544 |  |  |  |  |  |  |  | 
| 545 |  |  |  |  |  |  | @ISA = qw(Exporter); | 
| 546 |  |  |  |  |  |  | @EXPORT = qw(); | 
| 547 |  |  |  |  |  |  | my @supported_mods = (qw(Anystream Apple Bitchute Blogger BrandNewTube Brighteon Castbox Goodpods | 
| 548 |  |  |  |  |  |  | Google IHeartRadio InternetRadio Odysee OnlineRadiobox Podbean PodcastAddict Podchaser | 
| 549 |  |  |  |  |  |  | RadioNet Rcast Rumble SermonAudio SoundCloud	Spreaker	Tunein Vimeo Youtube LinkTV Zeno)); | 
| 550 |  |  |  |  |  |  |  | 
| 551 |  |  |  |  |  |  | my %useit; | 
| 552 |  |  |  |  |  |  |  | 
| 553 |  |  |  |  |  |  | foreach my $module (@supported_mods) | 
| 554 |  |  |  |  |  |  | { | 
| 555 |  |  |  |  |  |  | $useit{$module} = 1; | 
| 556 |  |  |  |  |  |  | } | 
| 557 |  |  |  |  |  |  |  | 
| 558 |  |  |  |  |  |  | sub new | 
| 559 |  |  |  |  |  |  | { | 
| 560 | 0 |  |  | 0 | 1 |  | my $class = shift; | 
| 561 | 0 |  |  |  |  |  | my $url = shift; | 
| 562 |  |  |  |  |  |  |  | 
| 563 | 0 |  |  |  |  |  | my $self = {}; | 
| 564 | 0 | 0 |  |  |  |  | return undef  unless ($url); | 
| 565 |  |  |  |  |  |  |  | 
| 566 | 0 |  |  |  |  |  | my $arg; | 
| 567 | 0 |  |  |  |  |  | my @args = (); | 
| 568 | 0 |  |  |  |  |  | while (@_) { | 
| 569 | 0 |  |  |  |  |  | $arg = shift(@_); | 
| 570 | 0 | 0 |  |  |  |  | if ($arg =~ /^\-?omit$/o) {   #ALLOW USER TO OMIT SPECIFIC INSTALLED SUBMODULE(S): | 
| 571 | 0 |  |  |  |  |  | my @omitModules = split(/\,\s*/, shift(@_)); | 
| 572 | 0 |  |  |  |  |  | foreach my $omit (@omitModules) | 
| 573 |  |  |  |  |  |  | { | 
| 574 | 0 |  |  |  |  |  | $useit{$omit} = 0; | 
| 575 |  |  |  |  |  |  | } | 
| 576 |  |  |  |  |  |  | } else { | 
| 577 | 0 |  |  |  |  |  | push @args, $arg; | 
| 578 |  |  |  |  |  |  | } | 
| 579 |  |  |  |  |  |  | } | 
| 580 |  |  |  |  |  |  |  | 
| 581 | 0 |  |  |  |  |  | my $haveit = 0; | 
| 582 | 0 | 0 |  |  |  |  | push @args, ('-debug', $DEBUG)  if ($DEBUG); | 
| 583 | 0 | 0 | 0 |  |  |  | if ($url =~ m#\b(?:podcasts?|music)\.apple\.com\/# && $useit{'Apple'}) { | 
|  |  | 0 | 0 |  |  |  |  | 
|  |  | 0 | 0 |  |  |  |  | 
|  |  | 0 | 0 |  |  |  |  | 
|  |  | 0 | 0 |  |  |  |  | 
|  |  | 0 | 0 |  |  |  |  | 
|  |  | 0 | 0 |  |  |  |  | 
|  |  | 0 | 0 |  |  |  |  | 
|  |  | 0 | 0 |  |  |  |  | 
|  |  | 0 | 0 |  |  |  |  | 
|  |  | 0 | 0 |  |  |  |  | 
|  |  | 0 | 0 |  |  |  |  | 
|  |  | 0 | 0 |  |  |  |  | 
|  |  | 0 | 0 |  |  |  |  | 
|  |  | 0 | 0 |  |  |  |  | 
|  |  | 0 | 0 |  |  |  |  | 
|  |  | 0 | 0 |  |  |  |  | 
|  |  | 0 | 0 |  |  |  |  | 
|  |  | 0 | 0 |  |  |  |  | 
|  |  | 0 | 0 |  |  |  |  | 
|  |  | 0 | 0 |  |  |  |  | 
|  |  | 0 | 0 |  |  |  |  | 
|  |  | 0 | 0 |  |  |  |  | 
|  |  | 0 | 0 |  |  |  |  | 
|  |  | 0 | 0 |  |  |  |  | 
|  |  | 0 |  |  |  |  |  | 
| 584 | 0 |  |  |  |  |  | eval { require 'StreamFinder/Apple.pm'; $haveit = 1; }; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 585 | 0 | 0 |  |  |  |  | return new StreamFinder::Apple($url, @args)  if ($haveit); | 
| 586 |  |  |  |  |  |  | } elsif ($url =~ m#\brumble\.com\/# && $useit{'Rumble'}) { | 
| 587 | 0 |  |  |  |  |  | eval { require 'StreamFinder/Rumble.pm'; $haveit = 1; }; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 588 | 0 | 0 |  |  |  |  | return new StreamFinder::Rumble($url, @args)  if ($haveit); | 
| 589 |  |  |  |  |  |  | } elsif ($url =~ m#\bpodcastaddict\.# && $useit{'PodcastAddict'}) { | 
| 590 | 0 |  |  |  |  |  | eval { require 'StreamFinder/PodcastAddict.pm'; $haveit = 1; }; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 591 | 0 | 0 |  |  |  |  | return new StreamFinder::PodcastAddict($url, @args)  if ($haveit); | 
| 592 |  |  |  |  |  |  | } elsif ($url =~ m#\b(?:brandnew|uge)tube\.# && $useit{'BrandNewTube'}) { #HANDLES brandnewtube & ugetube! | 
| 593 | 0 |  |  |  |  |  | eval { require 'StreamFinder/BrandNewTube.pm'; $haveit = 1; }; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 594 | 0 | 0 |  |  |  |  | return new StreamFinder::BrandNewTube($url, @args)  if ($haveit); | 
| 595 |  |  |  |  |  |  | } elsif ($url =~ m#\bbitchute\.# && $useit{'Bitchute'}) { | 
| 596 | 0 |  |  |  |  |  | eval { require 'StreamFinder/Bitchute.pm'; $haveit = 1; }; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 597 | 0 | 0 |  |  |  |  | return new StreamFinder::Bitchute($url, @args)  if ($haveit); | 
| 598 |  |  |  |  |  |  | } elsif ($url =~ m#\biheart(?:radio)?\.#i && $useit{'IHeartRadio'}) { | 
| 599 | 0 |  |  |  |  |  | eval { require 'StreamFinder/IHeartRadio.pm'; $haveit = 1; }; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 600 | 0 | 0 |  |  |  |  | return new StreamFinder::IHeartRadio($url, @args)  if ($haveit); | 
| 601 |  |  |  |  |  |  | } elsif ($url =~ m#\btunein\.# && $useit{'Tunein'}) {  #NOTE:ALSO USES youtube-dl! | 
| 602 | 0 |  |  |  |  |  | eval { require 'StreamFinder/Tunein.pm'; $haveit = 1; }; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 603 | 0 | 0 |  |  |  |  | return new StreamFinder::Tunein($url, @args)  if ($haveit); | 
| 604 |  |  |  |  |  |  | } elsif ($url =~ m#\bbrighteon\.com\/# && $useit{'Brighteon'}) {  #NOTE:ALSO USES youtube-dl! | 
| 605 | 0 |  |  |  |  |  | eval { require 'StreamFinder/Brighteon.pm'; $haveit = 1; }; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 606 | 0 | 0 |  |  |  |  | return new StreamFinder::Brighteon($url, @args)  if ($haveit); | 
| 607 |  |  |  |  |  |  | } elsif ($url =~ m#\bspreaker\.# && $useit{'Spreaker'}) { | 
| 608 | 0 |  |  |  |  |  | eval { require 'StreamFinder/Spreaker.pm'; $haveit = 1; }; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 609 | 0 | 0 |  |  |  |  | return new StreamFinder::Spreaker($url, @args)  if ($haveit); | 
| 610 |  |  |  |  |  |  | } elsif ($url =~ m#\bcastbox\.\w+\/# && $useit{'Castbox'}) { | 
| 611 | 0 |  |  |  |  |  | eval { require 'StreamFinder/Castbox.pm'; $haveit = 1; }; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 612 | 0 | 0 |  |  |  |  | return new StreamFinder::Castbox($url, @args)  if ($haveit); | 
| 613 |  |  |  |  |  |  | } elsif ($url =~ m#\b\.google\.\w+\/# && $useit{'Google'}) { | 
| 614 | 0 |  |  |  |  |  | eval { require 'StreamFinder/Google.pm'; $haveit = 1; }; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 615 | 0 | 0 |  |  |  |  | return new StreamFinder::Google($url, @args)  if ($haveit); | 
| 616 |  |  |  |  |  |  | } elsif ($url =~ m#\bradio\.net\/# && $useit{'RadioNet'}) { | 
| 617 | 0 |  |  |  |  |  | eval { require 'StreamFinder/RadioNet.pm'; $haveit = 1; }; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 618 | 0 | 0 |  |  |  |  | return new StreamFinder::RadioNet($url, @args)  if ($haveit); | 
| 619 |  |  |  |  |  |  | } elsif ($url =~ m#\bvimeo\.# && $useit{'Vimeo'}) {  #NOTE:ALSO USES youtube-dl! | 
| 620 | 0 |  |  |  |  |  | eval { require 'StreamFinder/Vimeo.pm'; $haveit = 1; }; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 621 | 0 | 0 |  |  |  |  | return new StreamFinder::Vimeo($url, @args)  if ($haveit); | 
| 622 |  |  |  |  |  |  | } elsif ($url =~ m#\bblogger\.# && $useit{'Blogger'}) { | 
| 623 | 0 |  |  |  |  |  | eval { require 'StreamFinder/Blogger.pm'; $haveit = 1; }; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 624 | 0 | 0 |  |  |  |  | return new StreamFinder::Blogger($url, @args)  if ($haveit); | 
| 625 |  |  |  |  |  |  | } elsif ($url =~ m#\bsermonaudio\.com\/# && $useit{'SermonAudio'}) { | 
| 626 | 0 |  |  |  |  |  | eval { require 'StreamFinder/SermonAudio.pm'; $haveit = 1; }; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 627 | 0 | 0 |  |  |  |  | return new StreamFinder::SermonAudio($url, @args)  if ($haveit); | 
| 628 |  |  |  |  |  |  | } elsif ($url =~ m#\bodysee\.com\/# && $useit{'Odysee'}) { | 
| 629 | 0 |  |  |  |  |  | eval { require 'StreamFinder/Odysee.pm'; $haveit = 1; }; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 630 | 0 | 0 |  |  |  |  | return new StreamFinder::Odysee($url, @args)  if ($haveit); | 
| 631 |  |  |  |  |  |  | } elsif ($url =~ m#\bpodbean\.com\/# && $useit{'Podbean'}) { | 
| 632 | 0 |  |  |  |  |  | eval { require 'StreamFinder/Podbean.pm'; $haveit = 1; }; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 633 | 0 | 0 |  |  |  |  | return new StreamFinder::Podbean($url, @args)  if ($haveit); | 
| 634 |  |  |  |  |  |  | } elsif ($url =~ m#\bonlineradiobox\.# && $useit{'OnlineRadiobox'}) { | 
| 635 | 0 |  |  |  |  |  | eval { require 'StreamFinder/OnlineRadiobox.pm'; $haveit = 1; }; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 636 | 0 | 0 |  |  |  |  | return new StreamFinder::OnlineRadiobox($url, @args)  if ($haveit); | 
| 637 |  |  |  |  |  |  | } elsif ($url =~ m#\binternet\-radio\.# && $useit{'InternetRadio'}) { | 
| 638 | 0 |  |  |  |  |  | eval { require 'StreamFinder/InternetRadio.pm'; $haveit = 1; }; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 639 | 0 | 0 |  |  |  |  | return new StreamFinder::InternetRadio($url, @args)  if ($haveit); | 
| 640 |  |  |  |  |  |  | } elsif ($url =~ m#\bsoundcloud\.# && $useit{'SoundCloud'}) { | 
| 641 | 0 |  |  |  |  |  | eval { require 'StreamFinder/SoundCloud.pm'; $haveit = 1; }; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 642 | 0 | 0 |  |  |  |  | return new StreamFinder::SoundCloud($url, @args)  if ($haveit); | 
| 643 |  |  |  |  |  |  | } elsif ($url =~ m#\bgoodpods\.# && $useit{'Goodpods'}) { | 
| 644 | 0 |  |  |  |  |  | eval { require 'StreamFinder/Goodpods.pm'; $haveit = 1; }; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 645 | 0 | 0 |  |  |  |  | return new StreamFinder::Goodpods($url, @args)  if ($haveit); | 
| 646 |  |  |  |  |  |  | } elsif ($url =~ m#\brcast\.# && $useit{'Rcast'}) { | 
| 647 | 0 |  |  |  |  |  | eval { require 'StreamFinder/Rcast.pm'; $haveit = 1; }; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 648 | 0 | 0 |  |  |  |  | return new StreamFinder::Rcast($url, @args)  if ($haveit); | 
| 649 |  |  |  |  |  |  | } elsif ($url =~ m#\bpodchaser\.# && $useit{'Podchaser'}) { | 
| 650 | 0 |  |  |  |  |  | eval { require 'StreamFinder/Podchaser.pm'; $haveit = 1; }; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 651 | 0 | 0 |  |  |  |  | return new StreamFinder::Podchaser($url, @args)  if ($haveit); | 
| 652 |  |  |  |  |  |  | } elsif ($url =~ m#\blinktv\.# && $useit{'LinkTV'}) { | 
| 653 | 0 |  |  |  |  |  | eval { require 'StreamFinder/LinkTV.pm'; $haveit = 1; }; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 654 | 0 | 0 |  |  |  |  | return new StreamFinder::LinkTV($url, @args)  if ($haveit); | 
| 655 |  |  |  |  |  |  | } elsif ($url =~ m#\bzeno\.# && $useit{'Zeno'}) { | 
| 656 | 0 |  |  |  |  |  | eval { require 'StreamFinder/Zeno.pm'; $haveit = 1; }; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 657 | 0 | 0 |  |  |  |  | return new StreamFinder::Zeno($url, @args)  if ($haveit); | 
| 658 |  |  |  |  |  |  | } elsif ($useit{'Youtube'}) {  #DEFAULT TO youtube-dl SINCE SO MANY URLS ARE HANDLED THERE NOW. | 
| 659 | 0 |  |  |  |  |  | eval { require 'StreamFinder/Youtube.pm'; $haveit = 1; }; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 660 | 0 | 0 |  |  |  |  | if ($haveit) { | 
| 661 | 0 |  |  |  |  |  | my $yt = new StreamFinder::Youtube($url, @args); | 
| 662 | 0 | 0 | 0 |  |  |  | return $yt  if (defined($yt) && $yt && $yt->count() > 0); | 
|  |  |  | 0 |  |  |  |  | 
| 663 |  |  |  |  |  |  | } | 
| 664 |  |  |  |  |  |  | } | 
| 665 | 0 | 0 |  |  |  |  | if ($useit{'Anystream'}) {  #SITE NOT SUPPORTED, TRY TO FIND ANY STREAM URLS WE CAN: | 
| 666 | 0 |  |  |  |  |  | $haveit = 0; | 
| 667 | 0 |  |  |  |  |  | eval { require 'StreamFinder/Anystream.pm'; $haveit = 1; }; | 
|  | 0 |  |  |  |  |  |  | 
|  | 0 |  |  |  |  |  |  | 
| 668 | 0 | 0 |  |  |  |  | return new StreamFinder::Anystream($url, @args)  if ($haveit); | 
| 669 |  |  |  |  |  |  | } | 
| 670 | 0 |  |  |  |  |  | return undef; | 
| 671 |  |  |  |  |  |  | } | 
| 672 |  |  |  |  |  |  |  | 
| 673 |  |  |  |  |  |  | 1 |