Projects
Extra
get_iplayer
Sign Up
Log In
Username
Password
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
Expand all
Collapse all
Changes of Revision 10
View file
get_iplayer.changes
Changed
@@ -1,4 +1,47 @@ ------------------------------------------------------------------- +Mon Feb 7 18:56:44 UTC 2022 - Andrei Dziahel <develop7@develop7.info> + +- get_iplayer v3.28 + + * Web PVR: Fixed wrapping of long lines in programme info page + * Fixed bug that caused some episodes to be skipped when using + `--pid-recursive` with certain CBeebies/CBBC programmes + * Added support for "cloudfront" CDN. You can now use + `--exclude-supplier="cloudfront"` if necessary. + * The `modes` and `modesizes` programme info fields are now shown + in an abbreviated form. Individual streams are no longer listed, + only available quality levels. + * The "vbidi" CDN is now excluded by default. It is inaccessible + to get_iplayer and generates useless warnings derived from 403 responses + to requests for HLS master playlists. + +- get_iplayer v3.27 + + * Removed deprecated `--tag-utf8` option + * For a programme downloaded multiple times, history search now returns + the most recent entry with an extant media file instead of the + oldest entry. + * Web PVR + + Programme info pages and help page now open in new window/tab + + Play* links now open in new window/tab + + Play* links are no longer displayed for deleted files + + Play Direct links should now work in Firefox, Chrome, Edge + (but not Safari) With Remote Streaming type = Auto + + Play Direct links are now only displayed where MP4/M4A files + (or MP3 files from obsolete versions of get_iplayer) are available + + Removed Quicktime, AVI streaming formats + + Added MPEG-TS, Matroska, AAC, Vorbis streaming formats + * Windows: Fixed incorrect documentation link in installer finish screen + * macOS: get_iplayer should work on Apple Silicon systems via Rosetta 2, + but this has not been verified. The developers do not have access + to an Apple Silicon system. + * get_iplayer 3.27 or higher is required with Mojolicious 9.0+. Earlier + releases of Mojolicious will continue to work with get_iplayer 3.27 + or higher. + +- added download_sources source service + +------------------------------------------------------------------- Sun Nov 29 10:36:39 UTC 2020 - Luigi Baldoni <aloisio@gmx.com> - Update to version 3.26 (see
View file
get_iplayer.spec
Changed
@@ -6,7 +6,7 @@ # Name: get_iplayer -Version: 3.26 +Version: 3.28 Release: 0 Summary: Downloads H.264 BBC IPlayer TV, Radio, and Podcast Programs License: GPL-3.0-only
View file
_service
Added
@@ -0,0 +1,1 @@ +<services><service name="download_files" mode="disabled" /></services>
View file
get_iplayer-3.28.tar.gz/.github
Added
+(directory)
View file
get_iplayer-3.28.tar.gz/.github/ISSUE_TEMPLATE
Added
+(directory)
View file
get_iplayer-3.28.tar.gz/.github/ISSUE_TEMPLATE/bug_report.md
Added
@@ -0,0 +1,35 @@ +--- +name: Bug report +about: Create a bug report +title: '' +labels: '' +assignees: '' + +--- +### *Remove this line and all the text below before submitting your bug report* + +#### Read first + +- Do not request help with using get_iplayer. No user support will be provided. +- Do not request new features. Feature requests will not be accepted. +- All bug reports will automatically be closed and locked upon receipt. +- If your report identifies a reproducible bug in get_iplayer, it will be re-opened until a fix is released. +- You will receive no communication from the developers, so provide all the information required. + +#### What you need to provide + +- A clear and concise description of the bug. +- The **complete get_iplayer command line** used. +- The PID or URL of the programme you attempted to download, if applicable. **Only provide one programme**. +- A complete verbose log. Add logs as attachments. Do not paste logs into your bug report. Create a verbose log [e.g., log.txt] with: + + get_iplayer [your options here] --verbose > log.txt 2>&1 + +- Screenshots, if the bug appears to be in the Web PVR Manager user interface. +- OS and version [e.g. Windows 10 2004, macOS 10.15.5, Ubuntu 20.04.1] +- Browser and version [e.g. Chrome 83.0.4103, Edge 83.0.478.56, Firefox 78.0.1, Safari 13.1.1] +- get_iplayer version [e.g. 3.26, 3.26.0-MSWin32, 3.26.1-darwin] - use `get_iplayer -V` + +#### Documentation changes + +If you would like to contribute documentation changes, submit a bug report with your suggested changes and provide the full URL for the wiki page you wish to change. Direct editing of the wiki has been restricted to project developers due to attempted link hijacking by spambots.
View file
get_iplayer-3.28.tar.gz/.github/ISSUE_TEMPLATE/config.yml
Added
@@ -0,0 +1,1 @@ +blank_issues_enabled: false
View file
get_iplayer-3.28.tar.gz/.github/workflows
Added
+(directory)
View file
get_iplayer-3.28.tar.gz/.github/workflows/auto-close-lock-issue-action.yml
Added
@@ -0,0 +1,29 @@ +on: + issues: + types: + - opened +jobs: + auto-close-lock-issue-action_job: + runs-on: ubuntu-latest + name: auto-close-lock-issue-action_job + steps: + - name: auto-close-lock-issue-action_close-step + id: auto-close-lock-issue-action_close-step + uses: maxkomarychev/octions/octions/issues/update@master + with: + token: ${{ secrets.GITHUB_TOKEN }} + issue_number: ${{ github.event.issue.number }} + state: closed + - name: auto-close-lock-issue-action_lock-step + id: auto-close-lock-issue-action_lock-step + uses: maxkomarychev/octions/octions/issues/lock@master + with: + token: ${{ secrets.GITHUB_TOKEN }} + issue_number: ${{ github.event.issue.number }} + - name: auto-close-lock-issue-action_label-step + id: auto-close-lock-issue-action_label-step + uses: maxkomarychev/octions/octions/issues/add-labels@master + with: + token: ${{ secrets.GITHUB_TOKEN }} + issue_number: ${{ github.event.issue.number }} + labels: invalid
View file
get_iplayer-3.26.tar.gz/Makefile -> get_iplayer-3.28.tar.gz/Makefile
Changed
@@ -2,14 +2,13 @@ @echo No need to make anything. ifdef VERSION -next_ver := $(shell echo $(VERSION) + 0.01 | bc) release: @git update-index --refresh --unmerged @if git diff-index --name-only HEAD | grep ^ ; then \ echo Uncommitted changes in above files; exit 1; fi @git checkout master - @sed -i.bak -e 's/^\(my $$version = \).*/\1$(VERSION);/' -e 's/^\(my $$version_text\) = .*/\1;/' get_iplayer - @sed -i.bak -e 's/^\(my $$VERSION = \).*/\1$(VERSION);/' -e 's/^\(my $$VERSION_TEXT\) = .*/\1;/' get_iplayer.cgi + @sed -i.bak -e 's/^\(my $$version = \).*/\1$(VERSION);/' get_iplayer + @sed -i.bak -e 's/^\(my $$VERSION = \).*/\1$(VERSION);/' get_iplayer.cgi @rm -f get_iplayer.bak get_iplayer.cgi.bak @./get_iplayer --nocopyright --manpage get_iplayer.1 @git diff --exit-code get_iplayer.1 > /dev/null || \ @@ -18,13 +17,6 @@ @git log --format='%aN' | sort -u > CONTRIBUTORS; git add CONTRIBUTORS @git commit -m "Release $(VERSION)" get_iplayer get_iplayer.cgi get_iplayer.1 CONTRIBUTORS @git tag v$(VERSION) - @git checkout contribute - @git merge master - @sed -i.bak -e 's/^\(my $$version_text\);/\1 = "$(next_ver)-dev";/' get_iplayer - @sed -i.bak -e 's/^\(my $$VERSION_TEXT\);/\1 = "$(next_ver)-dev";/' get_iplayer.cgi - @rm -f get_iplayer.bak get_iplayer.cgi.bak - @git commit -m "bump dev version" get_iplayer get_iplayer.cgi - @git checkout master tarball: @git update-index --refresh --unmerged
View file
get_iplayer-3.26.tar.gz/README.md -> get_iplayer-3.28.tar.gz/README.md
Changed
@@ -18,8 +18,8 @@ **NOTE:** -- **get_iplayer can only search for programmes that were scheduled for broadcast on BBC linear services within the previous 30 days, even if some are available for more than 30 days on the iPlayer/Sounds sites. BBC Three programmes, red button programmes, iPlayer box sets, iPlayer exclusives, and BBC podcasts are not searchable. Old programmes that are still available after 30 days must be located on the iPlayer/Sounds sites and downloaded directly via PID or URL, but such use is not supported.**. -- **get_iplayer does not support downloading news/sport videos, other embedded media, archive sites, special collections, educational material, programme clips or any content other than whole episodes of programmes broadcast on BBC linear services within the previous 30 days, plus episodes of BBC Three programmes posted within the same period. It may be possible to download other content such as red button programmes or iPlayer box sets directly via PID or URL, but such use is not supported. get_iplayer DOES NOT support live recording from BBC channels.** +- **get_iplayer can only search for programmes that were scheduled for broadcast on BBC linear services within the previous 30 days, even if some are available for more than 30 days on the iPlayer/Sounds sites. BBC Three programmes, red button programmes, iPlayer box sets, web-only content, and BBC podcasts are not searchable. Old programmes that are still available after 30 days must be located on the iPlayer/Sounds sites and downloaded directly via PID or URL.** +- **get_iplayer does not support downloading news/sport videos, other embedded media, archive sites, special collections, educational material, programme clips or any content other than whole episodes of programmes broadcast on BBC linear services within the previous 30 days, plus episodes of BBC Three programmes posted within the same period. It is often possible to download other content such as red button programmes or iPlayer box sets directly via PID or URL. get_iplayer DOES NOT support live recording from BBC channels.** ## Documentation
View file
get_iplayer-3.26.tar.gz/get_iplayer -> get_iplayer-3.28.tar.gz/get_iplayer
Changed
@@ -1,6 +1,6 @@ #!/usr/bin/env perl # -# get_iplayer - Lists and records BBC iPlayer TV and radio programmes +# get_iplayer - Lists and records BBC iPlayer TV and BBC Sounds radio programmes # # Copyright (C) 2008-2010 Phil Lewis # @@ -24,7 +24,7 @@ # # package main; -my $version = 3.26; +my $version = 3.28; my $version_text; $version_text = sprintf("v%.2f", $version) unless $version_text; # @@ -85,11 +85,11 @@ attempts => [ 1, "attempts=n", 'Recording', '--attempts <number>', "Number of attempts to make or resume a failed connection. --attempts is applied per-stream, per-mode. Many modes have two or more streams available."], audioonly => [ 1, "audioonly|audio-only!", 'Recording', '--audio-only', "Only download audio stream for TV programme. 'hls' recording modes are not supported and ignored. Produces .m4a file. Implies --force."], downloadabortonfail => [ 1, "downloadabortonfail|download-abortonfail!", 'Recording', '--download-abortonfail', "Exit immediately if stream for any recording mode fails to download. Use to avoid repeated failed download attempts if connection is dropped or access is blocked."], - excludesupplier => [ 1, "excludecdn|exclude-cdn|excludesupplier|exclude-supplier=s", 'Recording', '--exclude-supplier <supplier>,<supplier>,...', "Comma-separated list of media stream suppliers (CDNs) to skip. Possible values: akamai,limelight,bidi. Synonym: --exclude-cdn."], + excludesupplier => [ 1, "excludecdn|exclude-cdn|excludesupplier|exclude-supplier=s", 'Recording', '--exclude-supplier <supplier>,<supplier>,...', "Comma-separated list of media stream suppliers (CDNs) to skip. Possible values: akamai,limelight,bidi,cloudfront. Synonym: --exclude-cdn."], force => [ 1, "force|force-download!", 'Recording', '--force', "Ignore programme history (unsets --hide option also)."], fps25 => [ 1, "fps25!", 'Recording', '--fps25', "Use only 25fps streams for TV programmes (HD video not available)."], get => [ 2, "get|record|g!", 'Recording', '--get, -g', "Start recording matching programmes. Search terms required."], - includesupplier => [ 1, "includecdn|include-cdn|includesupplier|include-supplier=s", 'Recording', '--include-supplier <supplier>,<supplier>,...', "Comma-separated list of media stream suppliers (CDNs) to use if not included by default or if previously excluded by --exclude-supplier. Possible values: akamai,limelight,bidi. Synonym: --include-cdn."], + includesupplier => [ 1, "includecdn|include-cdn|includesupplier|include-supplier=s", 'Recording', '--include-supplier <supplier>,<supplier>,...', "Comma-separated list of media stream suppliers (CDNs) to use if not included by default or if previously excluded by --exclude-supplier. Possible values: akamai,limelight,bidi,cloudfront. Synonym: --include-cdn."], hash => [ 1, "hash!", 'Recording', '--hash', "Show recording progress as hashes"], logprogress => [ 1, "log-progress|logprogress!", 'Recording', '--log-progress', "Force HLS/DASH download progress display to be captured when screen output is redirected to file. Progress display is normally omitted unless writing to terminal."], markdownloaded => [ 1, "markdownloaded|mark-downloaded!", 'Recording', '--mark-downloaded', "Mark programmes in search results or specified with --pid/--url as downloaded by inserting records in download history."], @@ -1867,7 +1867,7 @@ $OPTS{SendTE} = 0; @LWP::Protocol::http::EXTRA_SOCK_OPTS = %OPTS; - my ($ua, $url, $retries, $succeedmsg, $failmsg, $mustproxy, $fail_content, $ok404) = @_; + my ($ua, $url, $retries, $succeedmsg, $failmsg, $mustproxy, $fail_content, $ok4xx) = @_; $failmsg ||= "Failed to download URL"; $fail_content ||= ''; my $res; @@ -1887,25 +1887,22 @@ # Disable proxy unless mustproxy is flagged main::proxy_disable($ua) if $opt->{partialproxy} && ! $mustproxy; my $i; + my $is4xx; for ($i = 1; $i <= $retries; $i++) { logger "\nINFO: Downloading URL ($i/$retries): $url\n" if $opt->{verbose}; + $is4xx = 0; $res = $ua->request( HTTP::Request->new( GET => $url ) ); if ( ! $res->is_success ) { - if ( $i < $retries ) { - if ( $opt->{verbose} ) { - logger "\nWARNING: $failmsg ($i/$retries): $url\n"; - logger "WARNING: Response: ${\$res->code()} ${\$res->message()}\n"; - } - } else { - if ( $opt->{verbose} || ! ( $res->code() == 404 && $ok404 ) ) { - logger "\nERROR: $failmsg ($i/$retries): $url\n"; - logger "ERROR: Response: ${\$res->code()} ${\$res->message()}\n"; - if ( $res->code() == 403 ) { - logger "ERROR: Access to this resource was blocked by the BBC\n"; - } - logger "ERROR: Ignore this error if programme download is successful\n"; + $is4xx = $res->code() == 403 || $res->code() == 404; + if ( $opt->{verbose} || ! ( $is4xx && $ok4xx ) ) { + logger "\nWARNING: $failmsg ($i/$retries): $url\n"; + logger "WARNING: Response: ${\$res->code()} ${\$res->message()}\n"; + if ( $res->code() == 403 ) { + logger "WARNING: Access to this resource was blocked by the BBC\n"; } + logger "WARNING: Ignore this warning if programme download is successful\n"; } + last if $is4xx; } else { logger $succeedmsg; last; @@ -1914,7 +1911,7 @@ # Re-enable proxy unless mustproxy is flagged main::proxy_enable($ua) if $opt->{partialproxy} && ! $mustproxy; # Return empty string if we failed and content not required - if ( $i > $retries ) { + if ( ( $is4xx && ! $ok4xx ) || $i > $retries ) { if ( wantarray ) { return ($fail_content, $res); } else { @@ -2570,8 +2567,8 @@ 'This applies even if the base option name already begins with "no-", e.g., --no-no-tag or --no-no-artwork', ); push @man, - '.TH GET_IPLAYER "1" "June 2020" "Phil Lewis" "get_iplayer Manual"', - '.SH NAME', 'get_iplayer - Stream Recording tool and PVR for BBC iPlayer', + '.TH GET_IPLAYER "1" "December 2021" "Phil Lewis" "get_iplayer Manual"', + '.SH NAME', 'get_iplayer - Stream Recording tool and PVR for BBC iPlayer and BBC Sounds', '.SH SYNOPSIS', '\fBget_iplayer\fR [<options>] [<regex|index> ...]', '.PP', @@ -2583,7 +2580,7 @@ '.PP', '\fBget_iplayer\fR \fB--refresh\fR [\fB--type\fR=<type> <options>]', '.SH DESCRIPTION', - '\fBget_iplayer\fR lists, searches and records BBC iPlayer TV and radio programmes.', + '\fBget_iplayer\fR lists, searches and records BBC iPlayer TV and BBC Sounds radio programmes.', '.PP', '\fBget_iplayer\fR has two modes: recording a complete programme for later playback, and as a Personal Video Recorder (PVR), subscribing to', 'search terms and recording programmes automatically.', @@ -3160,10 +3157,12 @@ # Create new history entry if ( defined $hist->{ $record_entries->{pid} } ) { main::logger "WARNING: duplicate pid $record_entries->{pid} in history\n" if $opt->{debug}; - # Append filename and modes - $hist->{ $record_entries->{pid} }->{mode} .= ','.$record_entries->{mode} if defined $record_entries->{mode}; - $hist->{ $record_entries->{pid} }->{filename} .= ','.$record_entries->{filename} if defined $record_entries->{filename}; - main::logger "DEBUG: Loaded and merged '$record_entries->{pid}' = '$record_entries->{name} - $record_entries->{episode}' from history\n" if $opt->{debug}; + # Replace filename and mode with latest entry if file exists + if ( -f $record_entries->{filename} ) { + $hist->{ $record_entries->{pid} }->{mode} = $record_entries->{mode}; + $hist->{ $record_entries->{pid} }->{filename} = $record_entries->{filename}; + main::logger "DEBUG: Loaded and replaced '$record_entries->{pid}' = '$record_entries->{name} - $record_entries->{episode}' from history\n" if $opt->{debug}; + } } else { # workaround empty names #$record_entries->{name} = 'pid:'.$record_entries->{pid} if ! $record_entries->{name}; @@ -3575,8 +3574,15 @@ for my $key ( sort keys %{$data{$_}} ) { main::logger sprintf "%-16s ", $_.':'; if ( ref$data{$_}->{$key} ne 'HASH' ) { - main::logger "$key: $data{$_}->{$key}"; - main::logger " [estimated sizes only]" if $_ eq "modesizes"; + if ( $_ eq "modes" || $_ eq "modesizes" ) { + my %seen = (); + my @vals = sort Programme::cmp_modes grep { not $seen{$_}++ } map { $_ =~ s/([a-z])\d+/$1/; $_; } split(",", $data{$_}->{$key}); + my $val = join(",", @vals); + main::logger "$key: $val"; + main::logger " [estimated sizes]" if $_ eq "modesizes"; + } else { + main::logger "$key: $data{$_}->{$key}"; + } # This is the same as 'modes' list #} else { # main::logger "$key: ".(join ',', sort keys %{ $data{$_}->{$key} } ); @@ -4046,7 +4052,22 @@ $filename->{generic} = main::encode_fs(File::Spec->catfile($prog->{dir}, "$prog->{fileprefix}.xml")); $template->{generic} = '<?xml version="1.0" encoding="UTF-8" ?>'."\n"; $template->{generic} .= '<program_meta_data xmlns="http://linuxcentre.net/xmlstuff/get_iplayer" revision="1">'."\n"; - $template->{generic} .= "\t<$_>[$_]</$_>\n" for ( sort keys %{$prog} ); + my $version = $prog->{version} || 'unknown'; + for my $key ( sort keys %{$prog} ) { + my $subst = "[$key]"; + my $value = $prog->{$key}; + if ( ref$value eq 'HASH' ) { + if ( ref$value->{$version} ne 'HASH' ) { + # abbreviate mode lists + if ( $key eq "modes" || $key eq "modesizes" ) { + my %seen = (); + my @vals = sort Programme::cmp_modes grep { not $seen{$_}++ } map { $_ =~ s/([a-z])\d+/$1/; $_; } split(",", $value->{$version}); + $subst = join(",", @vals); + } + } + } + $template->{generic} .= "\t<$key>$subst</$key>\n"; + } $template->{generic} .= "</program_meta_data>\n"; # JSON template for all info (ignored) $filename->{json} = main::encode_fs(File::Spec->catfile($prog->{dir}, "$prog->{fileprefix}.json")); @@ -4091,12 +4112,19 @@ $self->{duration} = $self->{durations}->{$version} if $self->{durations}->{$version}; $self->{runtime} = int($self->{duration} / 60); my $jom = {}; - for my $key ( keys %{$self} ) { + for my $key ( sort keys %{$self} ) { my $value = $self->{$key}; # Get version specific value if this key is a hash if ( ref$value eq 'HASH' ) { if ( ref$value->{$version} ne 'HASH' ) { - $value = $value->{$version}; + # abbreviate mode lists + if ( $key eq "modes" || $key eq "modesizes" ) { + my %seen = (); + my @vals = sort Programme::cmp_modes grep { not $seen{$_}++ } map { $_ =~ s/([a-z])\d+/$1/; $_; } split(",", $value->{$version}); + $value = join(",", @vals); + } else { + $value = $value->{$version}; + } } else { next; } @@ -5592,7 +5620,7 @@ last unless $html; my $dom = XML::LibXML->load_html(string => $html, recover => 1, suppress_errors => 1); unless ( $channel ) { - $channel = $dom->findvalue('//a[contains(@class,"br-masthead__masterbrand")]'); + $channel = $dom->findvalue('//*[contains(@class,"br-masthead__masterbrand")]'); } unless ( $title ) { $title = $dom->findvalue('//div[contains(@class,"br-masthead__title")]/a'); @@ -5638,11 +5666,12 @@ $channel = undef; my @urls = ( "https://www.bbc.co.uk/iplayer/episodes/$parent_pid" ); for my $url ( @urls ) { + my $curr_url = $url; $curr_page = 1; $max_page = 1; $last_page = undef; { do { - my $html = main::request_url_retry($ua, $url, 3, '', ''); + my $html = main::request_url_retry($ua, $curr_url, 3, '', ''); last unless $html; my $dom = XML::LibXML->load_html(string => $html, recover => 1, suppress_errors => 1); if ( ! $check_series_nav ) { @@ -5691,7 +5720,10 @@ $last_page =~ s/(^\s+|\s+$)//g; $max_page = $last_page if $last_page > $max_page; $curr_page++; - $url = "https://www.bbc.co.uk/iplayer/episodes/$parent_pid?page=$curr_page"; + $curr_url = $url; + $curr_url .= "&" if $curr_url =~ /\?/; + $curr_url .= "?" unless $curr_url =~ /\?/; + $curr_url = "${curr_url}page=$curr_page"; } while ( $curr_page <= $max_page ) }; } } @@ -5746,7 +5778,7 @@ return $eps; } -# Intelligently split name and episode from title string for BBC iPlayer metadata +# Intelligently split name and episode from title string for BBC iPlayer/BBC Sounds metadata sub split_title { my $title = shift; my ( $name, $episode ); @@ -5837,7 +5869,7 @@ my $mattribs = shift; my $cattribs = shift; - main::logger "New BBC iPlayer Stream Found:\n"; + main::logger "New BBC iPlayer/BBC Sounds Stream Found:\n"; main::logger "MEDIA-ELEMENT:\n"; # list media attribs @@ -5949,7 +5981,7 @@ } } $conn->{href} = $variant_url; - my $data = main::request_url_retry( $ua, $conn->{href}, 3, undef, undef, 1 ); + my $data = main::request_url_retry( $ua, $conn->{href}, 3, undef, undef, 1, undef, 1 ); if ( ! $data ) { main::logger "WARNING: No HLS playlist returned ($conn->{href})\n" if $opt->{verbose}; return; @@ -6075,7 +6107,7 @@ } } $conn->{href} = $manifest_url; - my $xml = main::request_url_retry( $ua, $conn->{href}, 3, undef, undef, 1 ); + my $xml = main::request_url_retry( $ua, $conn->{href}, 3, undef, undef, 1, undef, 1 ); if ( ! $xml ) { main::logger "WARNING: No DASH manifest returned ($conn->{href})\n" if $opt->{verbose}; return; @@ -6344,6 +6376,7 @@ main::logger "INFO: Getting stream data for version: '$version'\n" if $opt->{verbose}; # filter CDN suppliers my @exclude_supplier = split(/,/, $opt->{excludesupplier}); + push @exclude_supplier, 'vbidi'; if ( $opt->{includesupplier} ) { @exclude_supplier = grep { $opt->{includesupplier} !~ /\b$_\b/ } @exclude_supplier; } @@ -6352,7 +6385,7 @@ } my $exclude_regex = '^ROGUEVALUE$'; if ( @exclude_supplier ) { - $exclude_regex = '('.(join('|', @exclude_supplier)).')'; + $exclude_regex = '_('.(join('|', @exclude_supplier)).')'; } # retrieve stream data @@ -6567,6 +6600,25 @@ } } + # keep max audio bitrate if more than one for given video resolution + my %seen = (); + my @modes = sort Programme::cmp_modes grep { not $seen{$_}++ } map { $_ =~ s/\d+$//; $_; } keys %$data; + for my $m ( @modes ) { + my $n = 0; + my $xbr; + while ( my $strm = $data->{$m . ++$n} ) { + if ( $strm->{audio_bitrate} > $xbr ) { + $xbr = $strm->{audio_bitrate}; + } + } + $n = 0; + while ( my $strm = $data->{$m . ++$n} ) { + if ( $strm->{audio_bitrate} < $xbr ) { + delete $data->{$m . $n}; + } + } + } + # Report modes found if ( $opt->{verbose} ) { main::logger sprintf("INFO: Found mode %10s: %s\n", $_, $data->{$_}->{type}) for sort Programme::cmp_modes keys %{ $data }; @@ -6780,7 +6832,7 @@ } } my @other_opts; - if ( ! $opt->{ffmpegobsolete} && $opt->{notag} ) { + if ( ! $opt->{ffmpegobsolete} ) { push @other_opts, ( '-movflags', 'faststart' ); } @@ -7200,13 +7252,26 @@ } } }; - - my $delay = Mojo::IOLoop->delay; + my $promise; + my $delay; + my $use_delay; + eval { + $promise = Mojo::Promise->new; + }; + if ( $@ ) { + main::logger "DEBUG: Mojo::Promise not available, using Mojo::IOLoop->delay\n" if $opt->{verbose}; + $delay = Mojo::IOLoop->delay; + $use_delay = 1; + } + my $count = 0; my $fetch; $fetch = sub { return unless my $url = shift @$urls; return if ( $rc_mojo && $opt->{refreshabortonerror} ); - my $end = $delay->begin; + my $end; + if ( $use_delay ) { + $end = $delay->begin; + } $attempts{$url}++; main::logger "\nINFO: Downloading $channels->{$url} schedule page ($attempts{$url}/$retries): $url\n" if $opt->{verbose}; $ua->get($url => sub { @@ -7215,12 +7280,20 @@ $get_callback->($ua, $tx); $fetch->(); } - $end->(); + if ( $use_delay ) { + $end->(); + } else { + $promise->resolve if --$count == 0; + } }); + $count++; }; - $fetch->() for 1 .. $max_conn; - $delay->wait; + if ( $use_delay ) { + $delay->wait; + } else { + $promise->wait; + } undef $ua; return 1 if $rc_mojo; } @@ -8222,8 +8295,8 @@ my $rate_percent; my $eta_str; my $prog_cdn; - ($prog_cdn = "$streamdata{type} ak ll bi") =~ s/^.*akamai(?=.*\b(ak)\b).*$|^.*limelight(?=.*\b(ll)\b).*$|^.*bidi(?=.*\b(bi)\b).*$/$1$2$3/; - $prog_cdn = "un" unless $prog_cdn =~ /^(ak|ll|bi)$/; + ($prog_cdn = "$streamdata{type} ak ll bi cf") =~ s/^.*akamai(?=.*\b(ak)\b).*$|^.*limelight(?=.*\b(ll)\b).*$|^.*bidi(?=.*\b(bi)\b).*$|^.*cloudfront(?=.*\b(cf)\b).*$/$1$2$3$4/; + $prog_cdn = "un" unless $prog_cdn =~ /^(ak|ll|bi|cf)$/; # LWP download callback my $callback = sub { @@ -8681,6 +8754,22 @@ my ( $self, $ua, $url, $prog, $version, %streamdata ) = @_; my $rc; + my $media_file = main::encode_fs(File::Spec->catfile($prog->{dir}, "$prog->{fileprefix}.dash.ts")); + my $media_type = $prog->{type} eq "tv" ? "video" : "audio"; + if ( $opt->{overwrite} ) { + unlink $media_file; + } else { + if ( -f $media_file ) { + main::logger "INFO: Using existing DASH $media_type file: $media_file\n"; + main::logger "INFO: Use --overwrite to re-download\n"; + if ( $prog->{type} eq "tv" && ! $opt->{audioonly} ) { + return $prog->postproc(undef, $media_file, $ua); + } else { + return $prog->postproc($media_file, undef, $ua); + } + } + } + # audio files my $audio_prefix = $prog->{fileprefix}; my $audio_tmp = main::encode_fs(File::Spec->catfile($prog->{dir}, "${audio_prefix}.audio.m4a")); @@ -9326,7 +9415,6 @@ tag_podcast_radio => [ 1, "tagpodcastradio|tag-podcast-radio!", 'Tagging', '--tag-podcast-radio', "Tag only downloaded radio programmes as iTunes podcasts (incompatible with Music/Podcasts/TV apps on macOS 10.15 and higher)"], tag_podcast_tv => [ 1, "tagpodcasttv|tag-podcast-tv!", 'Tagging', '--tag-podcast-tv', "Tag only downloaded tv programmes as iTunes podcasts (incompatible with Music/Podcasts/TV apps on macOS 10.15 and higher)"], tag_tracklist => [ 1, "tagtracklist|tag-tracklist!", 'Tagging', '--tag-tracklist', "Add track list of music played in programme (if available) to lyrics field."], - tag_utf8 => [ 1, "tagutf8|tag-utf8!", 'Ignored', '--tag-utf8', "Use UTF-8 encoding for non-ASCII characters in AtomicParsley parameter values (Linux/Unix/macOS only). Use only if auto-detect fails."], }; }
View file
get_iplayer-3.26.tar.gz/get_iplayer.1 -> get_iplayer-3.28.tar.gz/get_iplayer.1
Changed
@@ -1,6 +1,6 @@ -.TH GET_IPLAYER "1" "June 2020" "Phil Lewis" "get_iplayer Manual" +.TH GET_IPLAYER "1" "December 2021" "Phil Lewis" "get_iplayer Manual" .SH NAME -get_iplayer \- Stream Recording tool and PVR for BBC iPlayer +get_iplayer \- Stream Recording tool and PVR for BBC iPlayer and BBC Sounds .SH SYNOPSIS \fBget_iplayer\fR [<options>] [<regex|index> ...] .PP @@ -12,7 +12,7 @@ .PP \fBget_iplayer\fR \fB\-\-refresh\fR [\fB\-\-type\fR=<type> <options>] .SH DESCRIPTION -\fBget_iplayer\fR lists, searches and records BBC iPlayer TV and radio programmes. +\fBget_iplayer\fR lists, searches and records BBC iPlayer TV and BBC Sounds radio programmes. .PP \fBget_iplayer\fR has two modes: recording a complete programme for later playback, and as a Personal Video Recorder (PVR), subscribing to search terms and recording programmes automatically. @@ -174,7 +174,7 @@ Exit immediately if stream for any recording mode fails to download. Use to avoid repeated failed download attempts if connection is dropped or access is blocked. .TP \fB\-\-exclude\-supplier <supplier>,<supplier>,... -Comma\-separated list of media stream suppliers (CDNs) to skip. Possible values: akamai,limelight,bidi. Synonym: \-\-exclude\-cdn. +Comma\-separated list of media stream suppliers (CDNs) to skip. Possible values: akamai,limelight,bidi,cloudfront. Synonym: \-\-exclude\-cdn. .TP \fB\-\-force Ignore programme history (unsets \-\-hide option also). @@ -189,7 +189,7 @@ Show recording progress as hashes .TP \fB\-\-include\-supplier <supplier>,<supplier>,... -Comma\-separated list of media stream suppliers (CDNs) to use if not included by default or if previously excluded by \-\-exclude\-supplier. Possible values: akamai,limelight,bidi. Synonym: \-\-include\-cdn. +Comma\-separated list of media stream suppliers (CDNs) to use if not included by default or if previously excluded by \-\-exclude\-supplier. Possible values: akamai,limelight,bidi,cloudfront. Synonym: \-\-include\-cdn. .TP \fB\-\-log\-progress Force HLS/DASH download progress display to be captured when screen output is redirected to file. Progress display is normally omitted unless writing to terminal. @@ -583,7 +583,7 @@ .PP This manual page was originally written by Jonathan Wiltshire <jmw@debian.org> for the Debian project (but may be used by others). .SH COPYRIGHT NOTICE -get_iplayer v3.26, Copyright (C) 2008\-2010 Phil Lewis +get_iplayer v3.28, Copyright (C) 2008\-2010 Phil Lewis This program comes with ABSOLUTELY NO WARRANTY; for details use \-\-warranty. This is free software, and you are welcome to redistribute it under certain conditions; use \-\-conditions for details.
View file
get_iplayer-3.26.tar.gz/get_iplayer.cgi -> get_iplayer-3.28.tar.gz/get_iplayer.cgi
Changed
@@ -24,7 +24,7 @@ # License: GPLv3 (see LICENSE.txt) # -my $VERSION = 3.26; +my $VERSION = 3.28; my $VERSION_TEXT; $VERSION_TEXT = sprintf("v%.2f", $VERSION) unless $VERSION_TEXT; @@ -347,7 +347,7 @@ print $se "$data{_method}: $request{URL}\n"; # Is this the CGI or some other file request? - if ( $request{URL} =~ /^\/?(iplayer|recordings_delete|playlist.*|genplaylist.*|opml|)\/?$/ ) { + if ( $request{URL} =~ /^\/?(recordings_delete|playlist.+|genplaylist.+|)\/?$/ ) { # remove any vars that might affect the CGI #%ENV = (); @ARGV = (); @@ -480,57 +480,52 @@ my $action = $cgi->param( 'ACTION' ) || $request_uri; # Strip the leading '/' to get the action $action =~ s|^\/||g; - # rewrite short-form backwards compatible URIs - # e.g. http://server/stream?args -> http://server/get_iplayer.cgi?ACTION=stream&args - # Stream from get_iplayer STDOUT (optionally transcoding if required) - if ( $action eq 'direct' ) { + # Stream from file (optionally transcoding if required) + if ( $action eq 'direct' || $action eq 'playdirect' ) { binmode $fh, ':raw'; # get filename first my $progtype = $cgi->param( 'PROGTYPES' ); my $pid = $cgi->param( 'PID' ); - # If the modes list f set to nothing - #my $mode = $opt->{MODES}->{current} || $opt->{MODES}->{default}; my $mode = $cgi->param( 'MODES' ); my $filename = get_direct_filename( $pid, $mode, $progtype ); - # Use OUTTYPE for transcoding if required - get output ext - # $cgi->param('STREAMTYPE') || $cgi->param('OUTTYPE') || 'flv' if $action eq 'playlistdirect'; my $ext = lc( $cgi->param('STREAMTYPE') || $cgi->param( 'OUTTYPE' ) ); - # Remove fileprefix - $ext =~ s/^.*\.//g; # get file source ext my $src_ext = $filename; $src_ext =~ s/^.*\.//g; # Stream mime types my %mimetypes = ( - wav => 'audio/x-wav', + aac => 'audio/aac', + adts => 'audio/aac', flac => 'audio/x-flac', - aac => 'audio/mpeg', - m4a => 'audio/mpeg', + m4a => 'audio/mp4', mp3 => 'audio/mpeg', - rm => 'audio/x-pn-realaudio', + oga => 'audio/vorbis', + wav => 'audio/x-wav', + asf => 'video/x-ms-asf', + avi => 'video/avi', + flv => 'video/x-flv', + matroska => 'video/x-matroska', + mkv => 'video/x-matroska', mov => 'video/quicktime', mp4 => 'video/mp4', - avi => 'video/x-flv', - flv => 'video/x-flv', - asf => 'video/x-ms-asf', + mpegts => 'video/MP2T', + rm => 'audio/x-pn-realaudio', + ts => 'video/MP2T', ); - # default recipies - # Disable transcoding if none is specified as OUTTYPE/STREAMTYPE + # default recipes my $notranscode = 0; + # Disable transcoding if none is specified as OUTTYPE/STREAMTYPE + # Or if streaming MP4 via play direct if ( $ext =~ /none/i ) { - print $se "INFO: Transcoding disabled (OUTTYPE=none)\n"; - $ext = $src_ext; - $notranscode = 1; - - # cannot stream mp4/avi so transcode to flv - # Add types here which you want re-muxed into flv - #if ( $src_ext =~ m{^(mp4|avi|mov|mp3|aac)$} && ! $ext ) { - } elsif ( $src_ext =~ m{^(mp4|m4a|aac|avi|mov)$} && ! $ext ) { + print $se "INFO: Transcoding disabled (OUTTYPE=$ext)\n"; + $ext = $src_ext; + $notranscode = 1; + # Else known types re-mux into flv unless play direct + } elsif ( $action ne 'playdirect' && ! $ext && $src_ext =~ m{^(m4a|mp4|mp3|aac|avi|mkv|mov|ts)$} ) { $ext = 'flv'; - - # Else Default to no transcoding + # Else default to no transcoding } elsif ( ! $ext ) { $ext = $src_ext; } @@ -555,34 +550,25 @@ } # Get a playlist for a specified 'PROGTYPES' - } elsif ( $action eq 'playlist' || $action eq 'playlistdirect' || $action eq 'playlistfiles' ) { + } elsif ( $action eq 'playlistdirect' || $action eq 'playlistfiles' ) { # Output headers my $headers = $cgi->header( -type => 'audio/x-mpegurl' ); + # To save file + #my $headers = $cgi->header( -type => 'audio/x-mpegurl', -attachment => 'get_iplayer.m3u' ); # Send the headers to the browser print $se "\r\nHEADERS:\n$headers\n"; #if $opt_cmdline->{debug}; print $fh $headers; # determine output type - my $outtype = $cgi->param('OUTTYPE') || 'flv'; - $outtype = $cgi->param('STREAMTYPE') || $cgi->param('OUTTYPE') || 'flv' if $action eq 'playlistdirect'; + my $outtype = $cgi->param('OUTTYPE'); + $outtype = $cgi->param('STREAMTYPE') || $cgi->param('OUTTYPE') if $action eq 'playlistdirect'; # ( host, outtype, modes, progtype, bitrate, search, searchfields, action ) print $fh create_playlist_m3u_single( $request_host, $outtype, $opt->{MODES}->{current}, $opt->{PROGTYPES}->{current} , $cgi->param('BITRATE') || '', $opt->{SEARCH}->{current}, $opt->{SEARCHFIELDS}->{current} || 'name', $opt->{VERSIONLIST}->{current}, $action ); - # Get a playlist for a specified 'PROGTYPES' - } elsif ( $action eq 'opml' ) { - # Output headers - my $headers = $cgi->header( -type => 'text/xml' ); - - # Send the headers to the browser - print $se "\r\nHEADERS:\n$headers\n"; #if $opt_cmdline->{debug}; - print $fh $headers; - # ( host, outtype, modes, type, bitrate ) - print $fh get_opml( $request_host, $cgi->param('OUTTYPE') || 'flv', $opt->{MODES}->{current}, $opt->{PROGTYPES}->{current} , $cgi->param('BITRATE') || '', $opt->{SEARCH}->{current}, $cgi->param('LIST') || '' ); - # Get a playlist for a selected progs in form - } elsif ( $action eq 'genplaylist' || $action eq 'genplaylistdirect' || $action eq 'genplaylistfile' ) { + } elsif ( $action eq 'genplaylistdirect' || $action eq 'genplaylistfile' ) { # Output headers my $headers = $cgi->header( -type => 'audio/x-mpegurl' ); # To save file @@ -593,7 +579,7 @@ print $fh $headers; # determine output type - my $outtype = $cgi->param('OUTTYPE') || 'flv'; + my $outtype = $cgi->param('OUTTYPE'); $outtype = $cgi->param('STREAMTYPE') || $cgi->param('OUTTYPE') if $action eq 'genplaylistdirect'; # ( host, outtype, modes, bitrate, action ) @@ -802,72 +788,41 @@ sub build_ffmpeg_args { my ( $filename, $mimetype, $ext, $abitrate, $vsize, $vfr, $src_ext ) = ( @_ ); + my @cmd; + my @cmd_vopts; my @cmd_aopts; - my $src_mimetype = $mimetype; - # mime type override for audio->flv conversion - if ( lc( $src_ext ) =~ m{^(aac|m4a|mp3)$} ) { - $src_mimetype = 'audio/mpeg'; - } - if ( $abitrate =~ m{^\d+$} ) { - if ( lc( $ext ) eq 'flv' ) { - push @cmd_aopts, ( '-ar', '44100', '-ab', "${abitrate}k" ); - } else { - push @cmd_aopts, ( '-ab', "${abitrate}k" ); - } - } else { - if ( lc( $ext ) eq 'flv' ) { - push @cmd_aopts, ( '-ar', '44100' ); - } - # cannot copy code if for example we have an aac stream output as WAV (e.g. squeezebox flashaac) - #push @cmd_aopts, ( '-acodec', 'copy' ); + push @cmd_aopts, ( '-ab', "${abitrate}k" ); + } + if ( lc( $ext ) eq 'flv' ) { + push @cmd_aopts, ( '-ar', '44100' ); } - - my @cmd; # If conversion is necessary # Video - if ( $src_mimetype =~ m{^video} ) { - my @cmd_vopts; - + if ( $mimetype =~ m{^video} && $filename !~ m{\.(aac|m4a|mp3)$} ) { # Apply video size push @cmd_vopts, ( '-s', "${vsize}" ) if $vsize =~ m{^\d+x\d+$}; - # Apply video framerate - caveat - bitrate defaults to 200k if only vfr is set - push @cmd_vopts, ( '-r', $vfr ) if $vfr =~ m{^\d$}; - - # -sameq is bad - ## Apply sameq if framerate only and no bitrate - #push @cmd_vopts, '-sameq' if $vfr =~ m{^\d$} && $vsize !~ m{^\d+x\d+$}; - + push @cmd_vopts, ( '-r', $vfr ) if $vfr =~ m{^\d+$}; # Add in the codec if we are transcoding and not remuxing the stream if ( @cmd_vopts ) { push @cmd_vopts, ( '-vcodec', 'libx264' ); } else { push @cmd_vopts, ( '-vcodec', 'copy' ); } - - @cmd = ( - $opt_cmdline->{ffmpeg}, - #'-f', $src_ext, # not required? - '-i', $filename, - @cmd_aopts, - @cmd_vopts, - '-f', $ext, - '-', - ); # Audio } else { - @cmd = ( - $opt_cmdline->{ffmpeg}, - #'-f', $src_ext, # not required? - '-i', $filename, - '-vn', - @cmd_aopts, - '-ac', 2, - '-f', $ext, - '-', - ); + push @cmd_vopts, ( '-vn' ); } + @cmd = ( + $opt_cmdline->{ffmpeg}, + '-i', $filename, + @cmd_vopts, + @cmd_aopts, + '-ac', 2, + '-f', $ext, + '-', + ); print $se "DEBUG: Command args: ".(join ' ', @cmd)."\n"; return @cmd; } @@ -880,14 +835,8 @@ $outtype =~ s/^.*\.//g; my $searchterm = $search; - # this is already a wildcard default regex... - if ( $search eq '.*' ) { - $searchterm = '.*'; - # if it's a URL then bypass regex stuff - } elsif ( $search =~ m{^http} ) { - $searchterm = $search; # make search term regex friendly - } else { + if ( $searchterm ne '.*' && $searchterm !~ m{^http} ) { $searchterm =~ s|([\/\.\?\+\-\*\^\(\)\[\]\{\}])|\\$1|g; } @@ -899,12 +848,9 @@ '--nocopyright', '--expiry=999999999', '--webrequest', - get_iplayer_webrequest_args( 'nopurge=1', "type=$type", 'listformat=ENTRY|<pid>|<name>|<episode>|<desc>|<filename>|<mode>', "fields=$searchfields", "search=$searchterm", "versionlist=$versionlist" ), + get_iplayer_webrequest_args( 'history=1', 'skipdeleted=1', 'nopurge=1', "type=$type", 'listformat=ENTRY|<pid>|<name>|<episode>|<desc>|<filename>|<mode>', "fields=$searchfields", "search=$searchterm", "versionlist=$versionlist" ), ); - # Only add history search if the request is of this type or is a PlayFile from localfiles type - if ( ( $request eq 'playlistfiles' || $request eq 'playlistdirect' ) && ! ( $search =~ m{^/} && $searchfields eq 'pid' ) ) { - push @cmd, '--history', '--skipdeleted'; - } + my @out = get_cmd_output( @cmd ); push @playlist, "#EXTM3U\n"; @@ -923,27 +869,15 @@ # playlist with direct streaming for files through webserver if ( $request eq 'playlistdirect' ) { next if ! ( $pid && $type && $mode ); - $url = build_url_direct( $request_host, $type, $pid, $mode, basename( $filename ), $opt->{STREAMTYPE}->{current}, $opt->{HISTORY}->{current}, $opt->{BITRATE}->{current}, $opt->{VSIZE}->{current}, $opt->{VFR}->{current}, $opt->{VERSIONLIST}->{current} ); - - # If pid is actually a filename then use it cos this is a local file type programme - } elsif ( $request eq 'playlistfiles' && $pid =~ m{^/} ) { - next if ! $pid; - $url = search_absolute_path( $pid ) if $pid; + $url = build_url_direct( $request_host, $type, $pid, $mode, $outtype, $opt->{STREAMTYPE}->{current}, $opt->{HISTORY}->{current}, $opt->{BITRATE}->{current}, $opt->{VSIZE}->{current}, $opt->{VFR}->{current}, $opt->{VERSIONLIST}->{current} ); # playlist with local files } elsif ( $request eq 'playlistfiles' ) { next if ! $filename; $url = search_absolute_path( $filename ); - # playlist of proxied urls for streaming online prog via web server - } else { - next if ! ( $type && $pid ); - my $suffix = "${pid}.${outtype}"; - $url = build_url_stream( $request_host, $type, $pid, $mode || $modes, $suffix, $opt->{STREAMTYPE}->{current}, $opt->{BITRATE}->{current}, $opt->{VSIZE}->{current}, $opt->{VFR}->{current}, $opt->{VERSIONLIST}->{current} ); } - # Format required, e.g. - ##EXTINF:-1,BBC Radio - BBC Radio One (High Quality Stream) push @playlist, "#EXTINF:-1,$type - $channel - $name - $episode - $desc"; push @playlist, "$url\n"; @@ -961,11 +895,6 @@ my @record = ( $cgi->param( 'PROGSELECT' ) ); - # If a URL was specified by the User (assume auto mode list is OK): - if ( $opt->{URL}->{current} =~ m{^https?://} ) { - push @record, "$opt->{PROGTYPES}->{current}|$opt->{URL}->{current}|$opt->{URL}->{current}|-"; - } - # Create m3u from all selected 'TYPE|PID|NAME|EPISODE|MODE|CHANNEL' entries in the PVR for (@record) { my $url; @@ -979,35 +908,15 @@ # playlist with local files } elsif ( $request eq 'genplaylistfile' ) { - # If pid is actually a filename then use it cos this is a local file type programme - if ( $pid =~ m{^/} ) { - my $filename = search_absolute_path( $pid ); - $url = $filename if $filename; - } else { - # Lookup filename (add it if defined - even if relative) - # check for -f $filename if you want to exclude files that cannot be found - my $filename = get_direct_filename( $pid, $mode, $type ); - $url = $filename if $filename; - } - - # Uncomment this to make all playlists local for localfiles types - # If pid is actually a filename then use it cos this is a local file type programme - #} elsif ( $pid =~ m{^/} ) { - # my $filename = search_absolute_path( $pid ); - # $url = $filename if $filename; - - # playlist of proxied urls for streaming online prog via web server - } else { - my $suffix = "${pid}.${outtype}"; - $url = build_url_stream( $request_host, $type, $pid, $mode || $opt->{MODES}->{current}, $suffix, $opt->{STREAMTYPE}->{current}, $opt->{BITRATE}->{current}, $opt->{VSIZE}->{current}, $opt->{VFR}->{current}, $opt->{VERSIONLIST}->{current} ); + # Lookup filename (add it if defined - even if relative) + # check for -f $filename if you want to exclude files that cannot be found + my $filename = get_direct_filename( $pid, $mode, $type ); + $url = $filename if -f $filename; } # Skip empty urls next if ! $url; - # Format required, e.g. - ##EXTINF:-1,BBC Radio - BBC Radio One (High Quality Stream) - #http://localhost:1935/stream?PID=liveradio:bbc_radio_one&MODES=flashaac&OUTTYPE=bbc_radio_one.wav push @playlist, "#EXTINF:-1,$type - $channel - $name - $episode"; push @playlist, "$url\n"; @@ -1018,137 +927,17 @@ -sub get_opml { - my ( $request_host, $outtype, $modes, $type, $bitrate, $search, $list ) = ( @_ ); - my @playlist; - $outtype =~ s/^.*\.//g; - - #<?xml version="1.0" encoding="UTF-8"?> - #<opml version="1.1"> - # <head> - # <title>Grateful Dead - 1995-07-09-Chicago, IL</title> - # </head> - # <body> - # <outline URL="http://www.archive.org/.../gd1995-07-09d1t01_vbr.mp3" bitrate="200" source="Soundboard" text="Touch Of Grey" type="audio" /> - # <outline URL="http://www.archive.org/.../gd1995-07-09d1t02_vbr.mp3" bitrate="203" source="Soundboard" text="Little Red Rooster" type="audio" /> - # <outline URL="http://www.archive.org/.../gd1995-07-09d1t03_vbr.mp3" bitrate="194" source="Soundboard" text="Lazy River Road" type="audio" /> - # </body> - #</opml> - - print $se "INFO: Getting playlist for type '$type' using modes '$modes', bitrate '$bitrate', search='$search' and list '$list'\n"; - - # Header - push @playlist, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<opml version=\"1.1\">"; - - # Programmes - if (! $list) { - - # Header - push @playlist, "\t<head>\n\t\t\n\t</head>"; - push @playlist, "\t<body>"; - - # Extract and rewrite into playlist format - my @out = get_cmd_output( - $opt_cmdline->{getiplayer}, - '--encoding-locale=UTF-8', - '--encoding-console-out=UTF-8', - '--nocopyright', - '--expiry=999999999', - '--webrequest', - get_iplayer_webrequest_args( 'nopurge=1', "type=$type", 'listformat=<pid>|<name>|<episode>|<desc>', "search=$search" ), - ); - for ( grep !/^(Added:|Matches|$)/, @out ) { - chomp(); - # Strip unprinatble chars - s/(.)/(ord($1) > 127) ? "" : $1/egs; - my ($pid, $name, $episode, $desc) = (split /\|/)[0,1,2,3]; - next if ! ( $pid && $name ); - push @playlist, "\t\t<outline URL=\"".encode_entities( build_url_stream( $request_host, $type, $pid, $modes, $outtype ) )."\" bitrate=\"${bitrate}\" source=\"get_iplayer\" title=\"".encode_entities("$name - $episode - $desc")."\" text=\"".encode_entities("$name - $episode - $desc")."\" type=\"audio\" />"; - } - - # Top-level Menu - } elsif ( lc($list) eq 'menu' ) { - my %menu = ( - 'BBC iPlayer Radio Listen Again'=> "${request_host}?ACTION=opml&PROGTYPES=radio&LIST=channel", - ); - - # Header - push @playlist, "\t<head title=\"GetIplayer\">\n\t\t\n\t</head>"; - push @playlist, "\t<body>"; - for my $item ( sort keys %menu ) { - my $item_url = $menu{ $item }; - #http://localhost:1935/opml?PROGTYPES=<type>SEARCH=bbc+radio+1&MODES=${modes}&OUTTYPE=a.wav - push @playlist, "\t\t<outline URL=\"".encode_entities( $item_url )."\" text=\"".encode_entities( "$item" )."\" />"; - } - - # Channels/Names etc - } elsif ($list) { - - # Header - push @playlist, "\t<head>\n\t\t\n\t</head>"; - push @playlist, "\t<body>"; - - # Extract and rewrite into playlist format - my @out = get_cmd_output( - $opt_cmdline->{getiplayer}, - '--encoding-locale=UTF-8', - '--encoding-console-out=UTF-8', - '--nocopyright', - '--expiry=999999999', - '--webrequest', - get_iplayer_webrequest_args( 'nopurge=1', "type=$type", "list=$list", "channel=$search" ), - ); - for ( grep !/^(Added:|Matches|$)/, @out ) { - my $suffix; - chomp(); - # Strip unprinatble chars - s/(.)/(ord($1) > 127) ? "" : $1/egs; - next if ! m{^.+\(\d+\)$}; - my $item = $_; - s/\s*\(\d+\)$//g; - my $itemregex = '^'.$_.'$'; - # URL encode it - $itemregex =~ s/([^A-Za-z0-9])/sprintf("%%%02X", ord($1))/seg; - # Stateful addition of search terms - $suffix = '&LIST=name' if $list eq 'channel'; - # Format required, e.g. - #http://localhost:1935/opml?PROGTYPES=<type>SEARCH=bbc+radio+1&MODES=${modes}&OUTTYPE=a.wav - push @playlist, "\t\t<outline URL=\"".encode_entities("${request_host}?ACTION=opml&PROGTYPES=${type}&SEARCH=${itemregex}${suffix}&MODES=${modes}&OUTTYPE=a.wav")."\" text=\"".encode_entities("$item")."\" title=\"".encode_entities("$item")."\" type=\"playlist\" />"; - } - - } - - # Footer - push @playlist, "\t</body>\n</opml>"; - - return join ("\n", @playlist); -} - - - ### Playlist URL builders sub build_url_direct { - my ( $request_host, $progtypes, $pid, $modes, $outtype, $streamtype, $history, $bitrate, $vsize, $vfr, $versionlist ) = ( @_ ); + my ( $request_host, $progtypes, $pid, $modes, $outtype, $streamtype, $history, $bitrate, $vsize, $vfr, $versionlist, $action ) = ( @_ ); # Sanity check #print $se "DEBUG: building direct playback request using: PROGTYPES=${progtypes} PID=${pid} MODES=${modes} OUTTYPE=${outtype}\n"; # CGI::escape $_ = CGI::escape($_) for ( $progtypes, $pid, $modes, $outtype, $streamtype, $history, $bitrate, $vsize ); #print $se "DEBUG: building direct playback request using: PROGTYPES=${progtypes} PID=${pid} MODES=${modes} OUTTYPE=${outtype} BITRATE=${bitrate} VSIZE=${vsize} VFR=${vfr}\n"; # Build URL - return "${request_host}?ACTION=direct&PROGTYPES=${progtypes}&PID=${pid}&MODES=${modes}&HISTORY=${history}&OUTTYPE=${outtype}&STREAMTYPE=${streamtype}&BITRATE=${bitrate}&VSIZE=${vsize}&VFR=${vfr}&VERSIONLIST=${versionlist}"; -} - - -# "${request_host}?ACTION=stream&PROGTYPES=${type}&PID=${pid}&MODES=${modes}&OUTTYPE=${suffix}"; -sub build_url_stream { - my ( $request_host, $progtypes, $pid, $modes, $outtype, $streamtype, $bitrate, $vsize, $vfr, $versionlist ) = ( @_ ); - # Sanity check - #print $se "DEBUG: building stream playback request using: PROGTYPES=${progtypes} PID=${pid} MODES=${modes} OUTTYPE=${outtype}\n"; - # CGI::escape - $_ = CGI::escape($_) for ( $progtypes, $pid, $modes, $outtype, $streamtype, $bitrate, $vsize, $vfr ); - #print $se "DEBUG: building stream playback request using: PROGTYPES=${progtypes} PID=${pid} MODES=${modes} OUTTYPE=${outtype}\n"; - # Build URL - return "${request_host}?ACTION=stream&PROGTYPES=${progtypes}&PID=${pid}&MODES=${modes}&OUTTYPE=${outtype}&STREAMTYPE=${streamtype}&BITRATE=${bitrate}&VSIZE=${vsize}&VFR=${vfr}&VERSIONLIST=${versionlist}"; + $action ||= 'direct'; + return "${request_host}?ACTION=$action&PROGTYPES=${progtypes}&PID=${pid}&MODES=${modes}&HISTORY=${history}&OUTTYPE=${outtype}&STREAMTYPE=${streamtype}&BITRATE=${bitrate}&VSIZE=${vsize}&VFR=${vfr}&VERSIONLIST=${versionlist}"; } @@ -1967,7 +1756,7 @@ } # Show thumb if one exists $prog{$pid}->{thumbnail} ||= DEFAULT_THUMBNAIL; - print $fh img( { -class=>'action', -src=>$prog{$pid}->{thumbnail} } ) if $prog{$pid}->{thumbnail}; + print $fh img( { -height=>216, -class=>'action', -src=>$prog{$pid}->{thumbnail} } ) if $prog{$pid}->{thumbnail}; # Set optional output dir for pvr queue if set my $outdir; $outdir = '&OUTPUT='.CGI::escape("$opt->{OUTPUT}->{current}") if $opt->{OUTPUT}->{current}; @@ -1996,34 +1785,15 @@ # If the PID is a filename then filename is still searched using PID and TYPE sub get_direct_filename { my ( $pid, $mode, $type ) = ( @_ ); - my $out; - my @html; - my %prog; - my $pidisfile; my $history = 1; print $se "DEBUG: Looking up filename for MODE=$mode TYPE=$type PID=$pid\n"; - # set this flag if required and unset history if pid is a file - if ( -f $pid ) { - print $se "DEBUG: PID is a valid filename\n"; - $pidisfile = 1; - $history = 0; - } - - # Skip if not defined or, if pid is a file and no type defined - if ( $pidisfile && ! $type ) { - print $se "ERROR: Cannot lookup filename for PID which is a filename if type is not set\n"; - return ''; - } - if ( ( ! $pidisfile ) && ! ( $pid && $mode && $type ) ) { + if ( ! ( $pid && $mode && $type ) ) { print $se "ERROR: Cannot lookup filename unless PID, MODE and TYPE are set\n"; return ''; } - # make the pid regex friendly - $pid =~ s|([\/\.\?\+\-\*\^\(\)\[\]\{\}])|\\$1|g; - # Get the 'filename' entry from --history --info for this pid my @cmd = ( $opt_cmdline->{getiplayer}, @@ -2041,11 +1811,7 @@ # Extract the filename my $match = ( grep /^filename:/, @cmdout )[0]; my $filename; - if ( $pidisfile ) { - $filename = $1 if $match =~ m{^filename: (\/.+?)\|<filename>\|<mode>\s*$}; - } else { - $filename = $1 if $match =~ m{^filename: .+?\|\s*(.+?)\|$mode\s*$}; - } + $filename = $1 if $match =~ m{^filename: .+?\|\s*(.+?)\|$mode\s*$}; if ( $filename && $opt_cmdline->{encodinglocalefs} !~ /UTF-?8/i ) { $filename = encode($opt_cmdline->{encodinglocalefs}, $filename, sub { '' }); } @@ -2686,39 +2452,34 @@ } # Format of PROGSELECT: TYPE|PID|NAME|EPISODE|MODE|CHANNEL - push @row, td( {-class=>$search_class}, - checkbox( - -class => $search_class, - -name => 'PROGSELECT', - -label => '', - -value => "$prog{$pid}->{type}|$pid|$prog{$pid}->{name}|$prog{$pid}->{episode}|$prog{$pid}->{mode}|$prog{$pid}->{channel}", - -checked => 0, - -override => 1, - ) - ); - # Record and stream links + if ( $opt->{HISTORY}->{current} && ! -f $prog{$pid}->{filename} ) { + push @row, td( {-class=>$search_class} ); + } else { + push @row, td( {-class=>$search_class}, + checkbox( + -class => $search_class, + -name => 'PROGSELECT', + -label => '', + -value => "$prog{$pid}->{type}|$pid|$prog{$pid}->{name}|$prog{$pid}->{episode}|$prog{$pid}->{mode}|$prog{$pid}->{channel}", + -checked => 0, + -override => 1, + ) + ); + } + # Record links my $links; - # 'Play' - # Search mode with filename as pid - if ( $pid =~ m{^/} ) { - if ( -f $pid ) { - # Play - $links .= a( { -class=>$search_class, -title=>"Play from file on web server", -href=>build_url_playlist( '', 'playlist', 'pid', $pid, $opt->{MODES}->{current} || $default_modes, $prog{$pid}->{type}, basename( $pid ) , $opt->{STREAMTYPE}->{current}, $opt->{BITRATE}->{current}, $opt->{VSIZE}->{current}, $opt->{VFR}->{current}, $opt->{VERSIONLIST}->{current} ) }, 'Play' ).'<br />'; - # PlayFile - $links .= a( { -id=>'nowrap', -class=>$search_class, -title=>"Play from local file", -href=>build_url_playlist( '', 'playlistfiles', 'pid', $pid, $prog{$pid}->{mode}, $prog{$pid}->{type}, undef, undef ) }, 'Play File' ).'<br />'; - # PlayDirect - $links .= a( { -id=>'nowrap', -class=>$search_class, -title=>"Stream file into browser", -href=>build_url_direct( '', $prog{$pid}->{type}, $pid, $prog{$pid}->{mode}, $opt->{STREAMTYPE}->{current}, $opt->{STREAMTYPE}->{current}, $opt->{HISTORY}->{current}, $opt->{BITRATE}->{current}, $opt->{VSIZE}->{current}, $opt->{VFR}->{current}, $opt->{VERSIONLIST}->{current} ) }, 'Play Direct' ).'<br />'; - } # History mode - } elsif ( $opt->{HISTORY}->{current} ) { - if ( $opt->{HIDEDELETED}->{current} || -f $prog{$pid}->{filename} ) { + if ( $opt->{HISTORY}->{current} ) { + if ( -f $prog{$pid}->{filename} ) { # Play (Play Remote) - $links .= a( { -id=>'nowrap', -class=>$search_class, -title=>"Play from file on web server", -href=>build_url_playlist( '', 'playlistdirect', 'pid', $pid, $prog{$pid}->{mode}, $prog{$pid}->{type}, 'flv', 'flv', $opt->{BITRATE}->{current}, $opt->{VSIZE}->{current}, $opt->{VFR}->{current}, $opt->{VERSIONLIST}->{current} ) }, 'Play' ).'<br />'; + $links .= a( { -id=>'nowrap', -target=>'_blank', -class=>$search_class, -title=>"Stream from file on web server", -href=>build_url_playlist( '', 'playlistdirect', 'pid', $pid, $prog{$pid}->{mode}, $prog{$pid}->{type}, $opt->{STREAMTYPE}->{current}, $opt->{STREAMTYPE}->{current}, $opt->{BITRATE}->{current}, $opt->{VSIZE}->{current}, $opt->{VFR}->{current}, $opt->{VERSIONLIST}->{current} ) }, 'Play' ).'<br />'; # PlayFile - $links .= a( { -id=>'nowrap', -class=>$search_class, -title=>"Play from local file", -href=>build_url_playlist( '', 'playlistfiles', 'pid', $pid, $prog{$pid}->{mode}, $prog{$pid}->{type}, undef ) }, 'Play File' ).'<br />'; + $links .= a( { -id=>'nowrap', -target=>'_blank', -class=>$search_class, -title=>"Play from local file", -href=>build_url_playlist( '', 'playlistfiles', 'pid', $pid, $prog{$pid}->{mode}, $prog{$pid}->{type}, undef ) }, 'Play File' ).'<br />'; # PlayDirect - depends on browser support - $links .= a( { -id=>'nowrap', -class=>$search_class, -title=>"Stream file into browser", -href=>build_url_direct( '', $prog{$pid}->{type}, $pid, $prog{$pid}->{mode}, $opt->{STREAMTYPE}->{current}, $opt->{STREAMTYPE}->{current}, $opt->{HISTORY}->{current}, $opt->{BITRATE}->{current}, $opt->{VSIZE}->{current}, $opt->{VFR}->{current}, $opt->{VERSIONLIST}->{current} ) }, 'Play Direct' ).'<br />'; + if ( $prog{$pid}->{filename} =~ m{\.(m4a|mp4|mp3)$} ) { + $links .= a( { -id=>'nowrap', -target=>'_blank', -class=>$search_class, -title=>"Stream file into browser", -href=>build_url_direct( '', $prog{$pid}->{type}, $pid, $prog{$pid}->{mode}, $opt->{STREAMTYPE}->{current}, $opt->{STREAMTYPE}->{current}, $opt->{HISTORY}->{current}, $opt->{BITRATE}->{current}, $opt->{VSIZE}->{current}, $opt->{VFR}->{current}, $opt->{VERSIONLIST}->{current}, 'playdirect' ) }, 'Play Direct' ).'<br />'; + } } # Search mode } else { @@ -2746,9 +2507,9 @@ if ( $prog{$pid}->{$_} !~ m{^https?://} ) { $prog{$pid}->{$_} = DEFAULT_THUMBNAIL; } - push @row, td( {-class=>$search_class}, a( { -title=>"Open original web URL", -class=>$search_class, -href=>$prog{$pid}->{web}, -target => "_new" }, img( { -class=>$search_class, -height=>40, -src=>$prog{$pid}->{$_} } ) ) ); + push @row, td( {-class=>$search_class}, a( { -title=>"Open original web URL", -class=>$search_class, -href=>$prog{$pid}->{web}, -target => "_blank" }, img( { -class=>$search_class, -height=>40, -src=>$prog{$pid}->{$_} } ) ) ); } elsif ( /^web$/ ) { - push @row, td( {-class=>$search_class}, a( { -title=>"Open original web URL", -class=>$search_class, -href=>$prog{$pid}->{$_}, -target => "_new" }, 'Open URL' ) ); + push @row, td( {-class=>$search_class}, a( { -title=>"Open original web URL", -class=>$search_class, -href=>$prog{$pid}->{$_}, -target => "_blank" }, 'Open URL' ) ); # Calculate the seconds difference between epoch_now and epoch_datestring and convert back into array_time } elsif ( /^timeadded$/ ) { my @t = gmtime( $time - $prog{$pid}->{$_} ); @@ -3044,7 +2805,8 @@ my ( $page, $pagesize, $count, $trailsize ) = ( @_ ); # How many pages - my $pages = int( $count / $pagesize ) + 1; + my $pages = int( $count / $pagesize ); + $pages++ if $count % $pagesize; # If we request a page that is too high $page = $pages if $page > $pages; # Calc first and last programme numbers @@ -3254,7 +3016,7 @@ li( { -class=>$class->{recordings} }, a( { -class=>'nav', -title=>'History search page', -onClick => "BackupFormVars(formheader); formheader.NEXTPAGE.value='search_history'; formheader.submit(); RestoreFormVars(formheader);" }, 'Recordings' ) ). li( { -class=>$class->{pvrlist} }, a( { -class=>'nav', -title=>'List all saved PVR searches', -onClick => "BackupFormVars(formheader); formheader.NEXTPAGE.value='pvr_list'; formheader.submit(); RestoreFormVars(formheader);" }, 'PVR List' ) ). li( { -class=>$class->{pvrrun} }, a( { -class=>'nav', -title=>'Run the PVR now - wait for the PVR to complete', -onClick => "BackupFormVars(formheader); formheader.NEXTPAGE.value='pvr_run'; formheader.target='_newtab_pvrrun'; formheader.submit(); RestoreFormVars(formheader); formheader.target='';" }, 'Run PVR' ) ). - li( { -class=>'nav_tab' }, a( { -class=>'nav', -title=>'Show help and instructions', -href => "https://github.com/get-iplayer/get_iplayer/wiki/webpvr", -target => "_new" }, 'Help' ) ) + li( { -class=>'nav_tab' }, a( { -class=>'nav', -title=>'Show help and instructions', -href => "https://github.com/get-iplayer/get_iplayer/wiki/webpvr", -target => "_newtab_help" }, 'Help' ) ) ), ); print $fh hidden( -name => 'AUTOPVRRUN', -value => $opt->{AUTOPVRRUN}->{current}, -override => 1 ); @@ -3613,10 +3375,10 @@ save => 1, }; - my %vsize_labels = ( ''=>'Native', '1280x720'=>'1280x720', '832x468'=>'832x468', '640x360'=>'640x360', '512x288'=>'512x288', '480x272'=>'480x272', '320x176'=>'320x176', '176x96'=>'176x96' ); + my %vsize_labels = ( ''=>'Native', '1280x720'=>'1280x720', '960x540'=>'960x540', '832x468'=>'832x468', '704x396'=>'704x396', '640x360'=>'640x360', '512x288'=>'512x288', '448x252'=>'448x252', '384x216'=>'384x216', '256x144'=>'256x144', '192x108'=>'192x108' ); $opt->{VSIZE} = { title => 'Remote Streaming Video Size', # Title - tooltip => "Video size '<width>x<height>' to transcode remotely played files - leave blank for native size", # Tooltip + tooltip => "Video size '<width>x<height>' to transcode remotely played files - specify 'Native' for native size", # Tooltip webvar => 'VSIZE', # webvar type => 'popup', # type label => , \%vsize_labels, # labels @@ -3644,16 +3406,15 @@ default => '', save => 1, }; - - my %streamtype_labels = ( ''=>'Auto', 'none'=>'Disable Transcoding', 'flv'=>'Flash Video (flv)', 'mov'=>'Quicktime (mov)', 'asf'=>'Advanced Streaming Format (asf)', 'avi'=>'AVI', 'mp3'=>'MP3 (Audio Only)', 'aac'=>'AAC (Audio Only)', 'wav'=>'WAV (Audio Only)', 'flac'=>'FLAC (Audio Only)' ); + my %streamtype_labels = ( ''=>'Auto', 'none'=>'Disable Transcoding', 'flv'=>'Flash Video (H.264/MP3)', 'mpegts'=>'MPEG Transport Stream (H.264/MP2)', 'matroska'=>'Matroska (H.264/Vorbis)', 'asf'=>'Advanced Systems Format (H.264/WMA)', 'mp3'=>'MP3 (Audio Only)', 'adts'=>'AAC (Audio Only)', 'oga'=>'Vorbis (Audio Only)', 'wav'=>'WAV (Audio Only)', 'flac'=>'FLAC (Audio Only)' ); $opt->{STREAMTYPE} = { title => "Remote Streaming type", # Title - tooltip => "Force the output to be this type when using 'Play Remote' for 'PlayDirect' streaming(e.g. flv, mov). Specify 'none' to disable transcoding/remuxing. Leave blank for auto-detection", # Tooltip + tooltip => "Force the output to be this type when using 'Play' streaming. Specify 'Native' to disable transcoding/remuxing.", # Tooltip webvar => 'STREAMTYPE', # webvar type => 'popup', # type label => , \%streamtype_labels, # labels default => '', # default - value => [ '', 'none', 'flv', 'mov', 'asf', 'avi', 'mp3', 'aac', 'wav', 'flac' ], # values + value => [ '', 'none', 'flv', 'mpegts', 'matroska', 'asf', 'mp3', 'adts', 'oga', 'wav', 'flac' ], # values onChange=> "form1.submit();", save => 1, }; @@ -4164,6 +3925,10 @@ padding: 4px 8px; } + table.info > tbody > tr > td { + word-break: break-all + } + table.searchhead { width: 100%; }
Locations
Projects
Search
Status Monitor
Help
Open Build Service
OBS Manuals
API Documentation
OBS Portal
Reporting a Bug
Contact
Mailing List
Forums
Chat (IRC)
Twitter
Open Build Service (OBS)
is an
openSUSE project
.