File Coverage

blib/lib/WebService/Freesound.pm
Criterion Covered Total %
statement 167 188 88.8
branch 38 56 67.8
condition 17 30 56.6
subroutine 23 24 95.8
pod 15 15 100.0
total 260 313 83.0


line stmt bran cond sub pod time code
1             package WebService::Freesound;
2 2     2   145676 use strict;
  2         5  
  2         105  
3 2     2   13 use warnings;
  2         5  
  2         94  
4              
5 2     2   84 use 5.008;
  2         15  
6              
7 2     2   1437 use LWP::Simple;
  2         96582  
  2         27  
8 2     2   1217 use LWP::UserAgent;
  2         7  
  2         131  
9 2     2   19 use Carp;
  2         5  
  2         245  
10              
11 2     2   1199 use JSON qw(decode_json);
  2         16707  
  2         24  
12              
13             # Freesound.org urls.
14             #
15             our %urls = (
16             'base' => 'https://www.freesound.org/apiv2',
17             'code' => '/oauth2/authorize/',
18             'access_token' => '/oauth2/access_token/',
19             'search' => '/search/text/?',
20             'download' => '/sounds/_ID_/download/',
21             'me' => '/me/',
22             );
23              
24             =pod
25              
26             =head1 NAME
27              
28             WebService::Freesound - Perl wrapper around Freesound OAuth2 API!
29              
30             =head1 VERSION
31              
32             Version 0.01
33              
34             =cut
35              
36             our $VERSION = '0.01';
37              
38             =head1 SYNOPSIS
39              
40             #!/usr/bin/perl
41             use WebService::Freesound;
42            
43             my %args = (
44             client_id => '03bbed9a541baf763526',
45             client_secret => 'abcde1234598765fedcba78629091899aef32101',
46             session_file => '/var/www/myapp/freesoundrc',
47             );
48              
49             my $freesound = WebService::Freesound->new (%args);
50              
51             # Freesound 3-Step OAuth process:
52             #
53             # Step 1.
54             # Get authorisation URL
55             #
56             my $authorization_url = $freesound->get_authorization_url();
57              
58             # Step 2.
59             # Redirect the user to authorization url, or paste into browser.
60             # Or pop up an
70              
71             # Step 3.
72             # A 'code' will be made available from the authorization_url from Freesound.
73             # Use it here to get access_token, refresh_token and expiry times. These are
74             # stored internally to the object and on disk. In theory, you should not need
75             # to see them, but are accessible via their respective accessors.
76             #
77             my $rc = $freesound->get_new_oauth_tokens ($code));
78             if ($rc) {
79             print $freesound->error;
80             }
81              
82             # At any time you can call check_authority to see if you are still authorised,
83             # and this will return 1 or undef/$freesound->error. It will refresh the tokens
84             # if refresh_if_expired is set.
85             #
86             my $rc = $freesound->check_authority (refresh_if_expired => 1/undef);
87             if ($rc) {
88             print $freesound->error;
89             }
90              
91             # All done with OAuth2 now it ..should.. just work forever (or until you revoke
92             # the authorization at L, when
93             # logged in, or set refresh_if_expired to undef).
94             #
95             # Get Freesound data, see L
96             # Returns a L or undef and $freesound->error.
97             #
98             my $rc = $freesound->check_authority (refresh_if_expired => 1);
99             if ($rc) {
100             my $response = $freesound->query ("query..." or "filter..." etc) # no query question mark required.
101             }
102              
103             # Download the sample from a Freesound id into the specified directory with a
104             # progress update in counter_file - for web apps get javascript to fire an Ajax call
105             # to read this file until 100% complete. Returns the path to the sample or undef
106             # and $freesound->error.
107             #
108             my $rc = $freesound->check_authority (refresh_if_expired => 1);
109             if ($rc) {
110             my $file = $freesound->download ($id, '/var/www/myapp/downloads/', $counter_file);
111             }
112              
113             =head1 DESCRIPTION
114              
115             This module provides a Perl wrapper around the L RESTful API.
116              
117             Freesound is a collaborative database of Creative Commons Licensed sounds. It allows
118             you to browse, download and share sounds. This Perl wrapper at present allows you
119             'read-only' access to Freesound, ie browse and download. Upcoming versions could provide
120             upload, describe and edit your own sounds (though I expect it might just be easier to use
121             their website for this).
122              
123             The complete Freesound API is documented at L
124              
125             In order to use this Perl module you will need get an account at Freesound
126             (L) and then to register your application with them
127             at L. Your application will then be given a client ID and
128             a client secret which you will need to use to get OAuth2 authorisation.
129              
130             The OAuth2 Dance is described at Freesound, L
131             and officially at L. It is a three step process as
132             suggested above.
133              
134             This module should look after the authorisation once done, ie when the expiry time arrives
135             it can automatically refresh the tokens. The auth tokens are therefore kept as a file specified by
136             "I", which should be read-only by you/www-data only.
137              
138             When downloading a sound sample from Freesound a progress meter is available in "I"
139             which is useful in web contexts as a progress bar. Format of the file is :
140              
141             ::
142             # for example "10943051:12578220:87", ie 87% of 12578220 bytes written.
143              
144             This is optional.
145              
146             Also the download will download the sample file as its name and type suffix (some Freesound names have
147             suffixes, some don't), so something like "/var/www/myapp/downloads/Pretty tune on piano.wav",
148             ".../violin tremolo G5.aif" etc.
149              
150             The query method allows you to put any text string into its parameter so that you have the full
151             capabilities of Freesound search, filter, sort etc, as described here :
152             L
153              
154             If used as part of a web app, then the process could be :
155              
156             =over 4
157              
158             =item * Check for I. If none then put up an iframe with the src set to output
159             of C<$freesound->get_authorization_url();>
160              
161             =item * User clicks Authorise with a callback run (set in Freesound API credentials :
162             L (ie http://localhost/cgi-bin/mayapp/do_auth.cgi)
163             which calls C<$freesound->get_oauth_tokens ($code))> - the code will be a parameter in
164             the CGI (ie C<$q->param ('code')>).
165              
166             =item * Text search box on main webpage can then be used as inputs to C<$freesound->query> -
167             the output formatted into HTML (from XML or json) as you
168             wish. With Freesound you get a picture of the waveform and a low quality sound preview so you can engineer
169             your website to show the waveform and have start/stop/pause buttons. Best not to replicate the entire
170             Freesound website, as this might contravene their terms and conditions.
171              
172             =item * A Freesound sample will have an id. This can be used in C<$freesound->download ($id, $dir, $counter_file)>.
173              
174             =item * Show download progress bar by continually polling the contents
175             of I (with an Ajax call) and drawing a CSS bar. Actually downloads to your server, not the
176             web-browser users Downloads directory.
177              
178             =back
179              
180             =head2 METHODS
181              
182             =over 4
183              
184             =item new ( I, I and I )
185              
186             Creates a new Freesound object for authorisation, queries and downloads.
187             I, I and I are required.
188              
189             =cut
190              
191             sub new {
192 1     1 1 305 my ( $class, %args ) = @_;
193 1         4 my $self = bless {}, $class;
194              
195             $self->{client_id} = $args{client_id}
196 1   33     13 || croak('client_id is required');
197             $self->{client_secret} = $args{client_secret}
198 1   33     6 || croak('client_secret is required');
199             $self->{session_file} = $args{session_file}
200 1   33     7 || croak('session_file is required');
201              
202             # State.
203             #
204 1         13 $self->{ua} = LWP::UserAgent->new();
205 1         456 $self->{error} = "";
206 1         3 $self->{access_token} = "";
207 1         3 $self->{refresh_token} = "";
208 1         4 $self->{expires_in} = "";
209              
210 1         5 return $self;
211             }
212              
213             =item client_id
214              
215             Accessor for the Client ID that was provided when you registered your
216             application with Freesound.org.
217              
218             =cut
219              
220             sub client_id {
221 7     7 1 1072 my ( $self, $client_id ) = @_;
222              
223 7 50       24 if ( defined $client_id ) {
224 0         0 $self->{client_id} = $client_id;
225             }
226              
227 7         37 return $self->{client_id};
228             }
229              
230             =item client_secret
231              
232             Accessor for the client secret that was provided when you registered your
233             application with Freesound.org.
234              
235             =cut
236              
237             sub client_secret {
238 6     6 1 11 my ( $self, $client_secret ) = @_;
239              
240 6 50       21 if ( defined $client_secret ) {
241 0         0 $self->{client_secret} = $client_secret;
242             }
243              
244 6         35 return $self->{client_secret};
245             }
246              
247             =item session_file
248              
249             Accessor for the session file that stores the authorisation codes.
250              
251             =cut
252              
253             sub session_file {
254 14     14 1 69 my ( $self, $session_file ) = @_;
255              
256 14 50       85 if ( defined $session_file ) {
257 0         0 $self->{session_file} = $session_file;
258             }
259              
260 14         994 return $self->{session_file};
261             }
262              
263             =item ua
264              
265             Accessor for the User Agent.
266              
267             =cut
268              
269             sub ua {
270 26     26 1 62 my ( $self, $ua ) = @_;
271              
272 26 50       88 if ( defined $ua ) {
273 0         0 $self->{ua} = $ua;
274             }
275              
276 26         176 return $self->{ua};
277             }
278              
279             =item error
280              
281             Accessor for the error messages that may occur.
282              
283             =cut
284              
285             sub error {
286 40     40 1 202 my ( $self, $error ) = @_;
287              
288 40 100       150 if ( defined $error ) {
289 32         82 $self->{error} = $error;
290             }
291              
292 40         149 return $self->{error};
293             }
294              
295             =item access_token
296              
297             Accessor for the OAuth2 access_token.
298              
299             =cut
300              
301             sub access_token {
302 14     14 1 25 my ( $self, $access_token ) = @_;
303              
304 14 50       62 if ( defined $access_token ) {
305 0         0 $self->{access_token} = $access_token;
306             }
307              
308 14         62 return $self->{access_token};
309             }
310              
311             =item refresh_token
312              
313             Accessor for the OAuth2 refresh_token.
314              
315             =cut
316              
317             sub refresh_token {
318 3     3 1 34 my ( $self, $refresh_token ) = @_;
319              
320 3 50       15 if ( defined $refresh_token ) {
321 0         0 $self->{refresh_token} = $refresh_token;
322             }
323              
324 3         18 return $self->{refresh_token};
325             }
326              
327             =item expires_in
328              
329             Accessor for the OAuth2 expiry time.
330              
331             =cut
332              
333             sub expires_in {
334 3     3 1 10 my ( $self, $expires_in ) = @_;
335              
336 3 50       29 if ( defined $expires_in ) {
337 0         0 $self->{expires_in} = $expires_in;
338             }
339              
340 3         20 return $self->{expires_in};
341             }
342              
343             =item get_authorization_url
344              
345             This returns the URL to start with when no auth is offered or accepted. Use it in an
346             iframe if using this in a CGI environment (ie send to L).
347              
348             =cut
349              
350             sub get_authorization_url {
351 1     1 1 3 my $self = shift;
352             my $auth_url
353             = $urls{'base'}
354 1         10 . $urls{'code'}
355             . '?client_id='
356             . $self->client_id
357             . '&response_type=code'
358             . '&state=xyz';
359 1         7 return $auth_url;
360             }
361              
362             =item get_new_oauth_tokens ( I )
363              
364             Takes the resultant 'code' displayed when the user authorises this app on Freesound.org and
365             then sets the internal OAuth tokens, along with expiry time. This method seriailises
366             this in the session_file for later use. This is Step 3 in the process as described in
367             L.
368             Returns 1 if succesful, undef and $freesound->error if not.
369              
370             =cut
371              
372             sub get_new_oauth_tokens {
373              
374 3     3 1 7 my $self = shift;
375 3   33     15 my $code = shift || croak "Need the code from Freesound authorisation";
376 3         5 my $rc = 1;
377 3         10 $self->error("");
378              
379 3         11 my $url = $urls{'base'} . $urls{'access_token'};
380 3         10 my %form = (
381             client_id => $self->client_id,
382             client_secret => $self->client_secret,
383             grant_type => 'authorization_code',
384             code => $code,
385             );
386 3         14 $rc = $self->_post_request( $url, \%form );
387              
388 3         29 return $rc;
389             }
390              
391             =item check_authority
392              
393             Checks the session file exists, has a current token. If no session file, then
394             returns URI to get the initial code from. If session file exists and and has not
395             expired then it checks with Freesound.org for existing authority. If the tokens
396             need refreshing and refresh_if_expired is set, it attempts a refresh. If that's
397             successful, then updates the session file with new oauth tokens. Return error if
398             the refresh didn't work (or refreshable but not asked to) - maybe because the
399             authority has been revoked. See L
400             when logged into Freesound.org. Return error if there is no authorisation at
401             Freesound.org.
402              
403             =cut
404              
405             sub check_authority {
406              
407 4     4 1 2006578 my $self = shift;
408 4         51 my %args = (
409             refresh_if_expired => undef,
410             @_
411             );
412 4         30 $self->error("");
413 4         15 my $rc = 1;
414              
415             # Check file exists.
416             #
417 4 100       22 if ( -s $self->session_file ) {
418              
419             # Read the session file and get its json auth tokens.
420             #
421 3 50       60 open my $fh, '<', $self->session_file
422             or croak
423             "Cannot read provided session_file for reading, though it does exist : $!";
424 3         22 my $oauth_tokens_string = "";
425 3         114 while ( my $line = <$fh> ) {
426 3         33 $oauth_tokens_string .= $line;
427             }
428 3         292 close $fh;
429              
430 3         9 my $oauth_tokens;
431 3         12 eval { $oauth_tokens = decode_json($oauth_tokens_string); };
  3         85  
432              
433 3 50       20 if ($oauth_tokens) {
434              
435             # Load them into the object for use when getting
436             # anything from Freesound. Encapsulate.
437             #
438 3         47 foreach ( keys %$oauth_tokens ) {
439 15         93 $self->{$_} = $oauth_tokens->{$_};
440             }
441              
442             # Check expiry by file timestamp, expires_in and now.
443             #
444 3         20 my $timestamp = ( stat( $self->session_file ) )[9];
445 3         15 my $expired;
446             $expired++
447 3 100       25 if ( ( $timestamp + $oauth_tokens->{'expires_in'} ) < time );
448              
449 3 100 100     47 if ( defined $args{'refresh_if_expired'} && $expired ) {
    100          
450              
451             # Expired, refresh tokens.
452             #
453 1         4 my $url = $urls{'base'} . $urls{'access_token'};
454             my %form = (
455             client_id => $self->client_id,
456             client_secret => $self->client_secret,
457             grant_type => 'refresh_token',
458 1         4 refresh_token => $oauth_tokens->{'refresh_token'},
459             );
460              
461             # $freesound->error will be filled in if an error.
462             #
463 1 50       13 unless ( $self->_post_request( $url, \%form ) ) {
464 0         0 $rc = undef;
465             }
466              
467             }
468             elsif ($expired) {
469 1         9 $rc = undef;
470 1         14 $self->error("Authority has expired");
471             }
472             }
473             else {
474 0         0 $self->error(
475             "OAuth tokens from the specified session_file appears to be not JSON"
476             );
477 0         0 $rc = undef;
478             }
479             }
480             else {
481              
482             # No session file,
483             #
484 1         6 $self->error( 'Need to re-authorise with Freesound, '
485             . 'use get_authorization_url then get_oauth_tokens '
486             . 'with the returned code from Freesound' );
487 1         2 $rc = undef;
488             }
489              
490             # Finally, if we're all ok our end, check with Freesound.org.
491             #
492 4 100       28 if ($rc) {
493 2         12 my $url = $urls{'base'} . $urls{'me'};
494 2         7 my $auth_String = "Bearer " . $self->access_token;
495 2         7 $self->ua->default_header( 'Authorization' => "$auth_String" );
496 2         130 my $response = $self->ua->get($url);
497 2 50       206 unless ( $response->is_success ) {
498 0         0 $rc = undef;
499 0         0 $self->error(
500             "Not authorised with Freesound : " . $response->status_line );
501             }
502              
503             }
504 4         80 return $rc;
505             }
506              
507             =item query ( I )
508              
509             Does the querying of the Freesound database, see
510             L
511             Should just let any string go into the query like filter, tag, sort, geotag etc. Just a string.
512             Returns whatever Freesound returns in an L.
513              
514             =cut
515              
516             sub query {
517              
518 8     8 1 74 my $self = shift;
519 8   66     71 my $query = shift || croak "Need a Freesound query string";
520 7         34 $self->error("");
521              
522 7         32 my $auth_String = "Bearer " . $self->access_token;
523 7         38 $self->ua->default_header( 'Authorization' => "$auth_String" );
524 7         625 my $url = $urls{'base'} . $urls{'search'} . $query;
525 7         91 my $response = $self->ua->get($url);
526 7 100       1156 $self->error( $response->status_line ) unless $response->is_success;
527 7         67 return $response;
528             }
529              
530             =item download ( I, I, {I} )
531              
532             The I is unique to a sample on Freesound, use C<$freesound->query>. The I
533             is where the downloaded file should go, the actual sound file will be named after its name on Freesound
534             and will have the correct extension (wav, mp3, aif etc). The I is optional - it keeps
535             a running count of the download progress . In a web environment a Javascript Ajax call can read
536             this in real-time to give a progress bar. I probably needs to be named with a session id
537             of some sort. Returns the path of the file or undef (then see C<$freesound->error>).
538              
539             =cut
540              
541             sub download {
542 4     4 1 2730 my $self = shift;
543 4   66     46 my $id = shift || croak "download needs a Freesound id";
544 3   66     27 my $to = shift || croak "download needs a destination directory id";
545 2         5 my $counter_file = shift;
546 2         4 my $rc = 1;
547 2         13 $self->error("");
548              
549             # Download needs OAuth.
550             #
551 2         8 $self->ua->default_header(
552             'Authorization' => 'Bearer ' . $self->access_token );
553              
554             # Download url has an ID in the midle of it.
555             #
556 2         140 my $url = $urls{'base'} . $urls{'download'};
557 2         15 $url =~ s/_ID_/$id/;
558              
559             # Get the name of the sample (ie BASS01.wav) from Freesound.org.
560             #
561 2         9 my $filename
562             = $self->get_filename_from_id($id); # error to $freesound->error
563              
564 2         4 my $fqp;
565 2 100       76 if ($filename) {
566              
567 1 50       204 open my $download_fh, '>', "$to/$filename"
568             or croak "Cannot open $filename for writing : $!";
569              
570             # Use a callback sub to keep count of the number of bytes
571             # written to the sample file, in a counter file.
572             #
573 1         3 my $received_size = 0;
574             my $response = $self->ua->get(
575             $url,
576             ':read_size_hint' => 8192,
577             ':content_cb' => sub {
578              
579             # This is handily provided by User::Agent in the callback.
580             #
581 0     0   0 my ( $data, $response, $protocol ) = @_;
582              
583             # Write this chunk of data to the download file.
584             #
585 0         0 print $download_fh $data;
586              
587             # Ony actually update download progress if requested.
588             #
589 0 0       0 if ( defined $counter_file ) {
590              
591             # Calculate progress.
592             #
593 0         0 my $total_size = $response->header('Content-Length');
594 0         0 $received_size += length $data;
595 0         0 my $percentage_complete = sprintf( "%d",
596             ( $received_size / $total_size ) * 100 );
597              
598             # Progress bar info : "10943051:12578220:87",
599             # ie 87% of 12578220 bytes written.
600             #
601 0 0       0 open my $counter_fh, '>', $counter_file
602             or croak "Cannot open $counter_file for writing : $!";
603 0         0 print $counter_fh
604             "$received_size:$total_size:$percentage_complete";
605 0         0 close $counter_fh;
606             }
607             }
608 1         4 );
609 1         103 close $download_fh;
610 1         9 $fqp = $to . '/' . $filename;
611             }
612 2         15 return $fqp;
613             }
614              
615             =item get_filename_from_id ( I )
616              
617             Does a query to get two fields - name and type (wav, mp3, aif etc) from the Freesound I of the sample.
618             Returns undef and $freesound->error if can't find a name/type for this id.
619              
620             =cut
621              
622             sub get_filename_from_id {
623              
624 4     4 1 9 my $self = shift;
625 4         5 my $id = shift;
626 4         13 $self->error("");
627              
628             # Query Freesound for the filename from its id, return name and type
629             # in json.
630             #
631 4         20 my $response = $self->query("filter=id:$id&fields=name,type&format=json");
632              
633 4         7 my $filename;
634             my $type;
635 4 100       11 if ( $response->is_success ) {
636              
637 3         19 my $details;
638 3         5 eval { $details = decode_json( $response->decoded_content ); };
  3         12  
639              
640             # {
641             # 'count' => 1,
642             # 'results' => [
643             # {
644             # 'type' => 'wav',
645             # 'name' => 'bass 16b.wav'
646             # }
647             # ],
648             # 'previous' => undef,
649             # 'next' => undef
650             # };
651 3 100 66     351 if ( defined $details
      66        
652             && defined $details->{'results'}->[0]->{'name'}
653             && defined $details->{'results'}->[0]->{'type'} )
654             {
655              
656 2         6 my $name = $details->{'results'}->[0]->{'name'};
657 2         6 my $type = $details->{'results'}->[0]->{'type'};
658              
659             # Could be "bass 16b.mp3.wav" if user hasn't named it
660             # properly.
661             #
662 2         6 $filename = "$name.$type";
663              
664             # Override if extension is actually correct.
665             #
666 2 50       57 $filename = $name if $name =~ /\.$type\s*$/;
667              
668             }
669             else {
670 1         6 $self->error("Cannot find a name or type for Freesound id $id");
671             }
672              
673             }
674             else {
675 1         9 $self->error( $response->status_line );
676             }
677 4         27 return $filename;
678             }
679              
680             =back
681              
682             =head1 INTERNAL SUBROUTINES/METHODS
683              
684             Please don't use these as they may change on a whim.
685              
686             =over 4
687              
688             =item _post_request
689              
690             Updates the objects oauth tokens and session file from User Agent response. Returns
691             1 or undef and $freesound->error.
692              
693             =cut
694              
695             sub _post_request {
696              
697 4     4   7 my $self = shift;
698 4         6 my $url = shift;
699 4         5 my $post_args = shift;
700 4         6 my $rc = 1;
701 4         10 $self->error("");
702              
703 4         10 my $response = $self->ua->post( $url, $post_args );
704              
705 4 100       437 if ( $response->is_success ) {
706              
707 3         44 my $oauth_tokens_string = $response->decoded_content;
708              
709             # Extract the data into the object and save it too.
710             #
711             # "access_token", "token_type", "Bearer", "expires_in",
712             # "refresh_token", "scope"
713             #
714 3         477 my $oauth_tokens;
715 3         6 eval { $oauth_tokens = decode_json($oauth_tokens_string) };
  3         57  
716              
717             # todo - need to use JSON properly.
718              
719 3 100       13 if ($oauth_tokens) {
720              
721             # Save to session_file.
722             #
723 2 50       18 open my $counter_fh, '>', $self->session_file
724             or croak "Cannot open provided session_file for writing : $!";
725 2         18 print $counter_fh $oauth_tokens_string;
726 2         99 close $counter_fh;
727              
728             # Encapsulate.
729             #
730 2         16 foreach ( keys %$oauth_tokens ) {
731 10         31 $self->{$_} = $oauth_tokens->{$_};
732             }
733              
734             }
735             else {
736 1         6 $self->error("Response from user agent appears not to be JSON");
737 1         3 $rc = undef;
738             }
739              
740             }
741             else {
742 1         22 $self->error( $response->status_line );
743 1         4 $rc = undef;
744             }
745 4         42 return $rc;
746              
747             }
748              
749             =back
750              
751             =head1 AUTHOR
752              
753             Andy Cragg, C<< >>
754              
755             =head1 BUGS
756              
757             This is beta code and may contain bugs - please feel free to fix them and send patches.
758              
759             =head1 SUPPORT
760              
761             You can find documentation for this module with the perldoc command.
762              
763             perldoc WebService::Freesound
764              
765             You can also look for information at:
766              
767             =over 4
768              
769             =item * RT: CPAN's request tracker (report bugs here)
770              
771             L
772              
773             =item * AnnoCPAN: Annotated CPAN documentation
774              
775             L
776              
777             =item * CPAN Ratings
778              
779             L
780              
781             =item * Search CPAN
782              
783             L
784              
785             =back
786              
787             =head1 ACKNOWLEDGEMENTS
788              
789             I had a look at L by Mohan Prasad Gutta, L for some ideas.
790              
791             =head1 LICENSE AND COPYRIGHT
792              
793             Copyright 2016 Andy Cragg.
794              
795             This program is free software; you can redistribute it and/or modify it
796             under the terms of the the Artistic License (2.0). You may obtain a
797             copy of the full license at:
798              
799             L
800              
801             Any use, modification, and distribution of the Standard or Modified
802             Versions is governed by this Artistic License. By using, modifying or
803             distributing the Package, you accept this license. Do not use, modify,
804             or distribute the Package, if you do not accept this license.
805              
806             If your Modified Version has been derived from a Modified Version made
807             by someone other than you, you are nevertheless required to ensure that
808             your Modified Version complies with the requirements of this license.
809              
810             This license does not grant you the right to use any trademark, service
811             mark, tradename, or logo of the Copyright Holder.
812              
813             This license includes the non-exclusive, worldwide, free-of-charge
814             patent license to make, have made, use, offer to sell, sell, import and
815             otherwise transfer the Package with respect to any patent claims
816             licensable by the Copyright Holder that are necessarily infringed by the
817             Package. If you institute patent litigation (including a cross-claim or
818             counterclaim) against any party alleging that the Package constitutes
819             direct or contributory patent infringement, then this Artistic License
820             to you shall terminate on the date that such litigation is filed.
821              
822             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
823             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
824             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
825             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
826             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
827             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
828             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
829             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
830              
831              
832             =cut
833              
834             1; # End of WebService::Freesound
835