Bug 8377 [ENH] Show HTML5 video/ audio for media files in OPAC and staff client

This enhancement uses information from MARC field 856 to generate the appropriate HTML5 code to embed am media player for the file(s) in a tab in the OPAC and staff client detail view. This patch supports the HTML5 <audio> and <video> element. Additionally it gives basic support for the <track> element. This element is not supported very well by recent browsers. Please consider the patch working when you get working video or audio.

Rebased to Master 22.11.2012

Signed-off-by: Chris Cormack <chrisc@catalyst.net.nz>
Signed-off-by: Jonathan Druart <jonathan.druart@biblibre.com>
Signed-off-by: Jared Camins-Esakov <jcamins@cpbibliography.com>
This commit is contained in:
Mirko Tietgen 2012-11-22 13:22:22 +01:00 committed by Jared Camins-Esakov
parent 37bd0c8d7e
commit 33aa6ec2fd
8 changed files with 298 additions and 1 deletions

226
C4/HTML5Media.pm Normal file
View file

@ -0,0 +1,226 @@
package C4::HTML5Media;
# Copyright 2012 Mirko Tietgen
#
# This file is part of Koha.
#
# Koha is free software; you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation; either version 2 of the License, or (at your option) any later
# version.
#
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with Koha; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
use strict;
use warnings;
use C4::Context;
use MARC::Field;
=head1 HTML5Media
C4::HTML5Media
=head1 Description
This module gets the relevant data from field 856 (MARC21/UNIMARC) to create a HTML5 audio or video element containing the file(s) catalogued in 856.
=cut
=head2 gethtml5media
Get all relevant data from field 856. Takes $template and $record in the subroutine call, sets appropriate params.
=cut
sub gethtml5media {
my $self = shift;
my $template = shift;
my $record = shift;
my @HTML5Media_sets = ();
my @HTML5Media_fields = $record->field(856);
my $HTML5MediaParent;
my $HTML5MediaWidth;
my @HTML5MediaExtensions = split( /\|/, C4::Context->preference("HTML5MediaExtensions") );
my $marcflavour = C4::Context->preference("marcflavour");
foreach my $HTML5Media_field (@HTML5Media_fields) {
my %HTML5Media;
# protocol
if ( $HTML5Media_field->indicator(1) eq '1' ) {
$HTML5Media{protocol} = 'ftp';
}
elsif ( $HTML5Media_field->indicator(1) eq '4' ) {
$HTML5Media{protocol} = 'http';
}
elsif ( $HTML5Media_field->indicator(1) eq '7' ) {
if ($marcflavour eq 'MARC21' || $marcflavour eq 'NORMARC') {
$HTML5Media{protocol} = $HTML5Media_field->subfield('2');
}
elsif ($marcflavour eq 'UNIMARC') {
$HTML5Media{protocol} = $HTML5Media_field->subfield('y');
}
}
else {
$HTML5Media{protocol} = 'http';
}
# user
if ( $HTML5Media_field->subfield('l') ) {
$HTML5Media{username} = $HTML5Media_field->subfield('l'); # yes, that is arbitrary if h and l are not the same. originally i flipped a coin in that case.
}
elsif ( $HTML5Media_field->subfield('h') ) {
$HTML5Media{username} = $HTML5Media_field->subfield('h');
}
# user/pass
if ( $HTML5Media{username} && $HTML5Media_field->subfield('k') ) {
$HTML5Media{loginblock} = $HTML5Media{username} . ':' . $HTML5Media_field->subfield('k') . '@';
}
elsif ( $HTML5Media{username} ) {
$HTML5Media{loginblock} = $HTML5Media{username} . '@';
}
else {
$HTML5Media{loginblock} = '';
}
# port
if ( $HTML5Media_field->subfield('p') ) {
$HTML5Media{portblock} = ':' . $HTML5Media_field->subfield('k');
}
else {
$HTML5Media{portblock} = '';
}
# src
if ( $HTML5Media_field->subfield('u') ) {
$HTML5Media{srcblock} = $HTML5Media_field->subfield('u');
}
elsif ( $HTML5Media_field->subfield('a') && $HTML5Media_field->subfield('d') && $HTML5Media_field->subfield('f') ) {
$HTML5Media{host} = $HTML5Media_field->subfield('a');
$HTML5Media{host} =~ s/(^\/|\/$)//g;
$HTML5Media{path} = $HTML5Media_field->subfield('d');
$HTML5Media{path} =~ s/(^\/|\/$)//g;
$HTML5Media{file} = $HTML5Media_field->subfield('f');
$HTML5Media{srcblock} = $HTML5Media{protocol} . '://' . $HTML5Media{loginblock} . $HTML5Media{host} . $HTML5Media{portblock} . '/' . $HTML5Media{path} . '/' . $HTML5Media{file};
}
else {
next; # no file to play
}
# extension
$HTML5Media{extension} = ($HTML5Media{srcblock} =~ m/([^.]+)$/)[0];
if ( !grep /$HTML5Media{extension}/, @HTML5MediaExtensions ) {
next; # not a specified media file
}
# mime
if ( $HTML5Media_field->subfield('c') ) {
$HTML5Media{codecs} = $HTML5Media_field->subfield('c');
}
### from subfield q…
if ( $HTML5Media_field->subfield('q') ) {
$HTML5Media{mime} = $HTML5Media_field->subfield('q');
}
### …or from file extension and codecs…
elsif ( $HTML5Media{codecs} ) {
if ( $HTML5Media{codecs} =~ /theora.*vorbis/ ) {
$HTML5Media{mime} = 'video/ogg';
}
elsif ( $HTML5Media{codecs} =~ /vp8.*vorbis/ ) {
$HTML5Media{mime} = 'video/webm';
}
elsif ( ($HTML5Media{codecs} =~ /^vorbis$/) && ($HTML5Media{extension} eq 'ogg') ) {
$HTML5Media{mime} = 'audio/ogg';
}
elsif ( ($HTML5Media{codecs} =~ /^vorbis$/) && ($HTML5Media{extension} eq 'webm') ) {
$HTML5Media{mime} = 'audio/webm';
}
}
### …or just from file extension
else {
if ( $HTML5Media{extension} eq 'ogv' ) {
$HTML5Media{mime} = 'video/ogg';
$HTML5Media{codecs} = 'theora,vorbis';
}
if ( $HTML5Media{extension} eq 'oga' ) {
$HTML5Media{mime} = 'audio/ogg';
$HTML5Media{codecs} = 'vorbis';
}
elsif ( $HTML5Media{extension} eq 'spx' ) {
$HTML5Media{mime} = 'audio/ogg';
$HTML5Media{codecs} = 'speex';
}
elsif ( $HTML5Media{extension} eq 'opus' ) {
$HTML5Media{mime} = 'audio/ogg';
$HTML5Media{codecs} = 'opus';
}
elsif ( $HTML5Media{extension} eq 'vtt' ) {
$HTML5Media{mime} = 'text/vtt';
}
}
# codecs
if ( $HTML5Media{codecs} ) {
$HTML5Media{codecblock} = '; codecs="' . $HTML5Media{codecs} . '"';
}
else {
$HTML5Media{codecblock} = '';
}
# type
if ( $HTML5Media{mime} ) {
$HTML5Media{typeblock} = ' type=\'' . $HTML5Media{mime} . $HTML5Media{codecblock} . '\'';
}
else {
$HTML5Media{typeblock} = '';
}
# element
if ( $HTML5Media{mime} =~ /audio/ ) {
$HTML5Media{type} = 'audio';
}
elsif ( $HTML5Media{mime} =~ /video/ ) {
$HTML5Media{type} = 'video';
}
elsif ( $HTML5Media{mime} =~ /text/ ) {
$HTML5Media{type} = 'track';
}
# push
if ( $HTML5Media{srcblock} && $HTML5Media{type} ) {
push (@HTML5Media_sets, \%HTML5Media);
}
}
# parent element
for my $i ( 0 .. $#HTML5Media_sets ) {
if ( ($HTML5Media_sets[$i]{mime}) && ($HTML5Media_sets[$i]{mime} =~ /audio/) ) {
if ( $HTML5MediaParent ne 'video' ) {
$HTML5MediaParent = 'audio';
$HTML5MediaWidth = '';
}
}
elsif ( ($HTML5Media_sets[$i]{mime}) && ($HTML5Media_sets[$i]{mime} =~ /video/) ) {
$HTML5MediaParent = 'video';
$HTML5MediaWidth = ' width="480"';
}
}
# child element
for my $j ( 0 .. $#HTML5Media_sets ) {
if ( ($HTML5Media_sets[$j]{type}) && ( ($HTML5Media_sets[$j]{type} eq 'video') || ($HTML5Media_sets[$j]{type} eq 'audio') ) ) {
if ( $HTML5Media_sets[$j]{type} eq $HTML5MediaParent ) {
$HTML5Media_sets[$j]{child} = 'source';
}
}
else {
$HTML5Media_sets[$j]{child} = $HTML5Media_sets[$j]{type};
}
}
# template parameters
if ( (scalar(@HTML5Media_sets) > 0) && ($HTML5MediaParent) ) {
$template->param(
HTML5MediaEnabled => 1,
HTML5MediaSets => \@HTML5Media_sets,
HTML5MediaParent => $HTML5MediaParent,
HTML5MediaWidth => $HTML5MediaWidth);
}
return $template;
}
1;

View file

@ -40,6 +40,7 @@ use C4::VirtualShelves;
use C4::XSLT;
use C4::Images;
use Koha::DateUtils;
use C4::HTML5Media;
# use Smart::Comments;
@ -379,6 +380,12 @@ if ( C4::Context->preference("LocalCoverImages") == 1 ) {
$template->{VARS}->{localimages} = \@images;
}
# HTML5 Media
if ( (C4::Context->preference("HTML5MediaEnabled") eq 'staff') || (C4::Context->preference("HTML5MediaEnabled") eq 'both') ) {
$template = C4::HTML5Media->gethtml5media($template,$record);
}
# Get OPAC URL
if (C4::Context->preference('OPACBaseURL')){
$template->param( OpacUrl => C4::Context->preference('OPACBaseURL') );

View file

@ -400,3 +400,5 @@ INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES('
INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES('OpacSeparateHoldings', '0', 'Separate current branch holdings from other holdings (OPAC)', NULL, 'YesNo');
INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES('OpacSeparateHoldingsBranch', 'homebranch', 'Branch used to separate holdings (OPAC)', 'homebranch|holdingbranch', 'Choice');
INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES('RenewalSendNotice','0', NULL, '', 'YesNo');
INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES('HTML5MediaEnabled','not','Show a tab with a HTML5 media player for files catalogued in field 856','not|opac|staff|both','Choice');
INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES('HTML5MediaExtensions','webm|ogg|ogv|oga|vtt','Media file extensions','','free');

View file

@ -6309,6 +6309,14 @@ if ( C4::Context->preference("Version") < TransformToNum($DBversion) ) {
SetVersion($DBversion);
}
$DBversion = 'XXX';
if ( C4::Context->preference("Version") < TransformToNum($DBversion) ) {
$dbh->do("INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES('HTML5MediaEnabled','not','Show a HTML5 media player in a tab on opac-detail.pl for media files catalogued in field 856.','not|opac|staff|both','Choice');");
$dbh->do("INSERT INTO systempreferences (variable,value,explanation,options,type) VALUES('HTML5MediaExtensions','webm|ogg|ogv|oga|vtt','Media file extensions','','free');");
print "Upgrade to $DBversion done (Add HTML5MediaEnabled and HTML5MediaExtensions sysprefs)\n";
SetVersion ($DBversion);
}
=head1 FUNCTIONS
=head2 TableExists($table)

View file

@ -286,3 +286,17 @@ Enhanced Content:
yes: Allow
no: "Don't allow"
- multiple images to be attached to each bibliographic record.
HTML5 Media:
-
- Show a tab with a HTML5 media player for files catalogued in field 856
- pref: HTML5MediaEnabled
choices:
not: "not at all."
opac: "in the OPAC."
staff: "in the staff client."
both: "in OPAC and staff client."
-
- Media file extensions
- pref: HTML5MediaExtensions
class: multi
- (separated with |).

View file

@ -272,7 +272,8 @@ function verify_images() {
[% IF ( subscriptionsnumber ) %]<li><a href="#subscriptions">Subscriptions</a></li>[% END %]
[% IF ( FRBRizeEditions ) %][% IF ( XISBNS ) %]<li><a href="#editions">Editions</a></li>[% END %][% END %]
[% IF ( LocalCoverImages ) %][% IF ( localimages || CAN_user_tools_upload_local_cover_images ) %]<li><a href="#images">Images</a></li>[% END %][% END %]
</ul>
[% IF ( HTML5MediaEnabled ) %][% IF ( HTML5MediaSets ) %]<li><a href="#html5media">Play media</a></li>[% END %][% END %]
</ul>
[% BLOCK items_table %]
<table>
@ -587,6 +588,20 @@ function verify_images() {
</div>
[% END %]
[% IF ( HTML5MediaEnabled ) %]
<div id="html5media">
<p>
<[% HTML5MediaParent %][% HTML5MediaWidth %] controls preload=none>
[% FOREACH HTML5MediaSet IN HTML5MediaSets %]
<[% HTML5MediaSet.child %] src="[% HTML5MediaSet.srcblock %]"[% HTML5MediaSet.typeblock %] />
[% END %]
[[% HTML5MediaParent %] tag not supported by your browser.]
</[% HTML5MediaParent %]>
</p>
</div>
[% END %]
</div><!-- /bibliodetails -->
<div class="yui-g" id="export" style="margin-top: 1em;">

View file

@ -16,6 +16,7 @@
<link rel="stylesheet" type="text/css" href="/opac-tmpl/prog/en/css/jquery.rating.css" />[% END %]
[% IF ( OpacHighlightedWords ) %]<script type="text/javascript" src="[% themelang %]/lib/jquery/plugins/jquery.highlight-3.js"></script>[% END %]
<script type="text/JavaScript" language="JavaScript">
//<![CDATA[
@ -718,6 +719,11 @@ YAHOO.util.Event.onContentReady("furtherm", function () {
[% IF ( OPACLocalCoverImages && localimages.size ) %]
<li id="tab_images"><a href="#images">Images</a></li>
[% END %]
[% IF ( HTML5MediaEnabled ) %][% IF ( HTML5MediaSets ) %]
<li id="tab_html5media"><a href="#html5media">Play media</a></li>
[% END %][% END %]
</ul>
[% IF ( serialcollection ) %]
@ -1053,6 +1059,19 @@ YAHOO.util.Event.onContentReady("furtherm", function () {
</table>
</div>[% END %][% END %]
[% IF ( HTML5MediaEnabled ) %]
<div id="html5media">
<p>
<[% HTML5MediaParent %][% HTML5MediaWidth %] controls preload=none>
[% FOREACH HTML5MediaSet IN HTML5MediaSets %]
<[% HTML5MediaSet.child %] src="[% HTML5MediaSet.srcblock %]"[% HTML5MediaSet.typeblock %] />
[% END %]
[[% HTML5MediaParent %] tag not supported by your browser.]
</[% HTML5MediaParent %]>
</p>
</div>
[% END %]
[% IF ( OPACLocalCoverImages && localimages.size ) %]
<div id="images">
<p>Click on an image to view it in the image viewer</p>

View file

@ -49,6 +49,7 @@ use MARC::Field;
use List::MoreUtils qw/any none/;
use C4::Images;
use Koha::DateUtils;
use C4::HTML5Media;
BEGIN {
if (C4::Context->preference('BakerTaylorEnabled')) {
@ -776,6 +777,11 @@ if (C4::Context->preference("OPACLocalCoverImages")){
$template->param(OPACLocalCoverImages => 1);
}
# HTML5 Media
if ( (C4::Context->preference("HTML5MediaEnabled") eq 'opac') || (C4::Context->preference("HTML5MediaEnabled") eq 'both') ) {
$template = C4::HTML5Media->gethtml5media($template,$record);
}
my $syndetics_elements;
if ( C4::Context->preference("SyndeticsEnabled") ) {