general file post processing and post processing support for FLV files
authorTony Cook <tony@develop-help.com>
Fri, 11 Dec 2009 05:14:49 +0000 (05:14 +0000)
committertony <tony@45cb6cf1-00bc-42d2-bb5a-07f51df49f94>
Fri, 11 Dec 2009 05:14:49 +0000 (05:14 +0000)
44 files changed:
MANIFEST
schema/bse.sql
site/cgi-bin/admin/admin.pl
site/cgi-bin/bse.cfg
site/cgi-bin/modules/Article.pm
site/cgi-bin/modules/ArticleFile.pm [deleted file]
site/cgi-bin/modules/ArticleFiles.pm [deleted file]
site/cgi-bin/modules/Articles.pm
site/cgi-bin/modules/BSE/DB/Mysql.pm
site/cgi-bin/modules/BSE/Edit/Article.pm
site/cgi-bin/modules/BSE/FileEditor.pm
site/cgi-bin/modules/BSE/FileHandler/Base.pm [new file with mode: 0644]
site/cgi-bin/modules/BSE/FileHandler/Default.pm [new file with mode: 0644]
site/cgi-bin/modules/BSE/FileHandler/FLV.pm [new file with mode: 0644]
site/cgi-bin/modules/BSE/Formatter.pm
site/cgi-bin/modules/BSE/ImageHandler/Flash.pm
site/cgi-bin/modules/BSE/Shop/Util.pm
site/cgi-bin/modules/BSE/StorageMgr/Files.pm
site/cgi-bin/modules/BSE/TB/ArticleFile.pm [new file with mode: 0644]
site/cgi-bin/modules/BSE/TB/ArticleFileMeta.pm [new file with mode: 0644]
site/cgi-bin/modules/BSE/TB/ArticleFileMetas.pm [new file with mode: 0644]
site/cgi-bin/modules/BSE/TB/ArticleFiles.pm [new file with mode: 0644]
site/cgi-bin/modules/BSE/TagFormats.pm
site/cgi-bin/modules/BSE/Thumb/Imager.pm
site/cgi-bin/modules/BSE/UI/AdminShop.pm
site/cgi-bin/modules/BSE/UI/Dispatch.pm
site/cgi-bin/modules/BSE/UI/Thumb.pm
site/cgi-bin/modules/BSE/UI/User.pm
site/cgi-bin/modules/BSE/UserReg.pm
site/cgi-bin/modules/BSE/Util/Thumb.pm
site/cgi-bin/modules/Generate/Article.pm
site/cgi-bin/user.pl
site/data/db/sql_statements.data
site/htdocs/css/style-main.css
site/htdocs/images/50.png [new file with mode: 0644]
site/htdocs/images/videoclose.png [new file with mode: 0644]
site/htdocs/js/bse.js [new file with mode: 0644]
site/htdocs/js/swfobject.js [new file with mode: 0644]
site/htdocs/swf/flvplayer.swf [new file with mode: 0644]
site/templates/admin/file_edit.tmpl
site/templates/base.tmpl
site/templates/inline/flv.tmpl [new file with mode: 0644]
site/templates/inline/flv_small.tmpl [new file with mode: 0644]
site/util/mysql.str

index 61bdbd7db9c4bc8eade38e361a3355db3f32feb0..ca3b680fa02a64a927132550533b7cb3d408ce29 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -46,8 +46,6 @@ site/cgi-bin/modules/AdminUtil.pm
 site/cgi-bin/modules/Apache/Session/DBIreal.pm
 site/cgi-bin/modules/Apache/Session/Store/DBIreal.pm
 site/cgi-bin/modules/Article.pm
-site/cgi-bin/modules/ArticleFile.pm
-site/cgi-bin/modules/ArticleFiles.pm
 site/cgi-bin/modules/Articles.pm
 site/cgi-bin/modules/BSE/API.pm
 site/cgi-bin/modules/BSE/Admin/StepParents.pm
@@ -80,6 +78,9 @@ site/cgi-bin/modules/BSE/EmailBlackEntry.pm
 site/cgi-bin/modules/BSE/EmailBlacklist.pm
 site/cgi-bin/modules/BSE/EmailRequest.pm
 site/cgi-bin/modules/BSE/EmailRequests.pm
+site/cgi-bin/modules/BSE/FileHandler/Base.pm
+site/cgi-bin/modules/BSE/FileHandler/Default.pm
+site/cgi-bin/modules/BSE/FileHandler/FLV.pm
 site/cgi-bin/modules/BSE/Formatter.pm
 site/cgi-bin/modules/BSE/Formatter/Article.pm
 site/cgi-bin/modules/BSE/Formatter/Subscription.pm
@@ -141,6 +142,10 @@ site/cgi-bin/modules/BSE/TB/AdminPerm.pm
 site/cgi-bin/modules/BSE/TB/AdminPerms.pm
 site/cgi-bin/modules/BSE/TB/AdminUser.pm
 site/cgi-bin/modules/BSE/TB/AdminUsers.pm
+site/cgi-bin/modules/BSE/TB/ArticleFile.pm
+site/cgi-bin/modules/BSE/TB/ArticleFileMeta.pm
+site/cgi-bin/modules/BSE/TB/ArticleFileMetas.pm
+site/cgi-bin/modules/BSE/TB/ArticleFiles.pm
 site/cgi-bin/modules/BSE/TB/BackgroundTask.pm
 site/cgi-bin/modules/BSE/TB/BackgroundTasks.pm
 site/cgi-bin/modules/BSE/TB/FileAccessLog.pm
@@ -339,6 +344,7 @@ site/htdocs/css/admin.css
 site/htdocs/css/admin.css_natural
 site/htdocs/css/style-main.css
 site/htdocs/favicon.ico
+site/htdocs/images/50.png
 site/htdocs/images/admin/busy.gif
 site/htdocs/images/admin/checked.gif
 site/htdocs/images/admin/error.gif
@@ -365,8 +371,10 @@ site/htdocs/images/titles/perl.gif
 site/htdocs/images/titles/the_shop.gif
 site/htdocs/images/titles/your_site.gif
 site/htdocs/images/trans_pixel.gif
+site/htdocs/images/videoclose.png
 # site/htdocs/js/admin_dragdrop.js
 site/htdocs/js/admin_prodopts.js
+site/htdocs/js/bse.js
 site/htdocs/js/builder.js
 site/htdocs/js/controls.js
 site/htdocs/js/date.js
@@ -377,6 +385,8 @@ site/htdocs/js/scriptaculous.js
 site/htdocs/js/scriptoverride.js
 site/htdocs/js/slider.js
 site/htdocs/js/sound.js
+site/htdocs/js/swfobject.js
+site/htdocs/swf/flvplayer.swf
 #  site/htdocs/shop created by the Makefile
 site/templates/1/shop_multicat.tmpl
 site/templates/1/sitemap.tmpl
@@ -548,6 +558,8 @@ site/templates/include/search_results.tmpl
 site/templates/include/usermenu.tmpl
 site/templates/index.tmpl
 site/templates/index2.tmpl
+site/templates/inline/flv.tmpl
+site/templates/inline/flv_small.tmpl
 site/templates/interest/askagain_base.tmpl
 site/templates/interest/confirm_base.tmpl
 site/templates/interest/error_base.tmpl
index 3faf1122a122b2d162ca42893c33dd03a5bb06c3..a907805ce014fc860de4426c3c3f682f6f874fec 100644 (file)
@@ -442,10 +442,28 @@ create table article_files (
 
   storage varchar(20) not null default 'local',
   src varchar(255) not null default '',
+  category varchar(20) not null default '',
+  file_handler varchar(20) not null default '',
 
   primary key (id)
 );
 
+drop table if exists bse_article_file_meta;
+create table bse_article_file_meta (
+  id integer not null auto_increment primary key,
+
+  -- refers to article_files
+  file_id integer not null,
+
+  -- name of this metadata
+  name varchar(20) not null,
+
+  content_type varchar(80) not null default 'text/plain',
+  value longblob not null,
+
+  unique file_name(file_id, name)
+);
+
 -- these are mailing list subscriptions
 drop table if exists subscription_types;
 create table subscription_types (
index 0a27f50c46522922e42cc017b452640d57c9fbd6..957ea26abc24014e2eb54d48a7bb7fd729dd4d6a 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/perl -w
 # -d:ptkdb
-BEGIN { $ENV{DISPLAY} = '192.168.32.51:0.0' }
+BEGIN { $ENV{DISPLAY} = '192.168.32.54:0.0' }
 use strict;
 use FindBin;
 use CGI::Carp 'fatalsToBrowser';
index 0fe093b4cce08e2a211635aa1a28b230bf8c3ae0..3f28dd35e8f6c6e2734e65b731904df65a3871af 100644 (file)
@@ -7,6 +7,7 @@ secureurl=$(url)
 [basic]
 randomdata = /dev/urandom
 access_control=0
+file_handlers=flv
 
 [paths]
 ; the following needs to be set to a path writable by the BSE processes
@@ -432,3 +433,6 @@ tellafriend_n=/cgi-bin/nuser.pl/tellafriend
 
 [thumb geometries]
 editor=scale(200x200)
+
+[file handlers]
+flv=BSE::FileHandler::FLV
index ffd8ee233f30c3690c4e63f4c98a6f4d17042f79..2970c1e81670259bddd4d8c6a603ccc7378c8d49 100644 (file)
@@ -145,8 +145,8 @@ sub section {
 sub files {
   my ($self) = @_;
 
-  require ArticleFiles;
-  return ArticleFiles->getBy(articleId=>$self->{id});
+  require BSE::TB::ArticleFiles;
+  return BSE::TB::ArticleFiles->getBy(articleId=>$self->{id});
 }
 
 sub parent {
diff --git a/site/cgi-bin/modules/ArticleFile.pm b/site/cgi-bin/modules/ArticleFile.pm
deleted file mode 100644 (file)
index 368eeaa..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-package ArticleFile;
-use strict;
-# represents a file associated with an article from the database
-use Squirrel::Row;
-use vars qw/@ISA/;
-@ISA = qw/Squirrel::Row/;
-use Carp 'confess';
-
-sub columns {
-  return qw/id articleId displayName filename sizeInBytes description 
-            contentType displayOrder forSale download whenUploaded
-            requireUser notes name hide_from_list storage src/;
-}
-
-sub remove {
-  my ($self, $cfg) = @_;
-
-  $cfg or confess "No \$cfg supplied to ",ref $self,"->remove()";
-
-  my $downloadPath = $cfg->entryErr('paths', 'downloads');
-  my $filename = $downloadPath . "/" . $self->{filename};
-  my $debug_del = $cfg->entryBool('debug', 'file_unlink', 0);
-  if ($debug_del) {
-    unlink $filename
-      or print STDERR "Error deleting $filename: $!\n";
-  }
-  else {
-    unlink $filename;
-  }
-  
-  $self->SUPER::remove();
-}
-
-sub article {
-  my $self = shift;
-  require Articles;
-
-  return Articles->getByPkey($self->{articleId});
-}
-
-1;
diff --git a/site/cgi-bin/modules/ArticleFiles.pm b/site/cgi-bin/modules/ArticleFiles.pm
deleted file mode 100644 (file)
index ebde288..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-package ArticleFiles;
-use strict;
-use Squirrel::Table;
-use vars qw(@ISA $VERSION);
-@ISA = qw(Squirrel::Table);
-use ArticleFile;
-
-sub rowClass {
-  return 'ArticleFile';
-}
-
-sub file_storages {
-  my $self = shift;
-  return map [ $_->{filename}, $_->{storage}, $_ ], $self->all;
-}
-
-1;
index 7cd87da54d2cb09d27721ba44033af64fff12a9a..2198a7495e01358890f428e6440e5a15f4107bde 100644 (file)
@@ -85,8 +85,8 @@ sub all_visible_kids {
 sub global_files {
   my ($self) = @_;
 
-  require ArticleFiles;
-  return ArticleFiles->getBy(articleId => -1);
+  require BSE::TB::ArticleFiles;
+  return BSE::TB::ArticleFiles->getBy(articleId => -1);
 }
 
 1;
index f7cfa2a50c067588c5bb71073e8e9e9e06148704..8ebb7462dad3424bea0764c5c3a807ff2714863b 100644 (file)
@@ -176,9 +176,9 @@ EOS
 
    ArticleFiles => 'select * from article_files',
    addArticleFile =>
-   'insert into article_files values (null,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)',
+   'insert into article_files values (null,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)',
    replaceArticleFile =>
-   'replace article_files values (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)',
+   'replace article_files values (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)',
    deleteArticleFile => 'delete from article_files where id = ?',
    getArticleFileByArticleId =>
    'select * from article_files where articleId = ? order by displayOrder desc',
index 7f7f90e38c75fc6c0675aca9fdb54b3e48b63e04..455acd14d7c4af6011cad2711c1eb3c4876178a9 100644 (file)
@@ -221,7 +221,7 @@ sub should_be_catalog {
     $parent = $articles->getByPkey($article->{id});
   }
 
-  my $shopid = $self->{cfg}->entryErr('articles', 'shop');
+  my $shopid = $self->cfg->entryErr('articles', 'shop');
 
   return $article->{parentid} && $parent &&
     ($article->{parentid} == $shopid || 
@@ -234,7 +234,7 @@ sub possible_parents {
   my %labels;
   my @values;
 
-  my $shopid = $self->{cfg}->entryErr('articles', 'shop');
+  my $shopid = $self->cfg->entryErr('articles', 'shop');
   my @parents = $articles->getBy('level', $article->{level}-1);
   @parents = grep { $_->{generator} eq 'Generate::Article' 
                      && $_->{id} != $shopid } @parents;
@@ -403,7 +403,7 @@ sub title_images {
   my ($self, $article) = @_;
 
   my @title_images;
-  my $imagedir = cfg_image_dir($self->{cfg});
+  my $imagedir = cfg_image_dir($self->cfg);
   if (opendir TITLE_IMAGES, "$imagedir/titles") {
     @title_images = sort 
       grep -f "$imagedir/titles/$_" && /\.(gif|jpeg|jpg|png)$/i,
@@ -438,7 +438,7 @@ sub template_dirs {
   my @dirs = $self->base_template_dirs;
   if (my $parentid = $article->{parentid}) {
     my $section = "children of $parentid";
-    if (my $dirs = $self->{cfg}->entry($section, 'template_dirs')) {
+    if (my $dirs = $self->cfg->entry($section, 'template_dirs')) {
       push @dirs, split /,/, $dirs;
     }
   }
@@ -2798,8 +2798,12 @@ sub req_thumb {
     my $imagedir = $cfg->entry('paths', 'images', $Constants::IMAGEDIR);
     
     my $error;
-    ($data, $type) = $thumb_obj->
-      thumb_data("$imagedir/$image->{image}", $geometry, \$error)
+    ($data, $type) = $thumb_obj->thumb_data
+      (
+       filename => "$imagedir/$image->{image}",
+       geometry => $geometry,
+       error => \$error
+      )
        or return 
          {
           type => 'text/plain',
@@ -3044,6 +3048,11 @@ my %file_fields =
     description => 'Identifier',
     maxlength => 80,
    },
+   category =>
+   {
+    description => "Category",
+    maxlength => 20,
+   },
   );
 
 sub fileadd {
@@ -3055,8 +3064,8 @@ sub fileadd {
 
   my %file;
   my $cgi = $req->cgi;
-  require ArticleFile;
-  my @cols = ArticleFile->columns;
+  require BSE::TB::ArticleFiles;
+  my @cols = BSE::TB::ArticleFile->columns;
   shift @cols;
   for my $col (@cols) {
     if (defined $cgi->param($col)) {
@@ -3075,6 +3084,7 @@ sub fileadd {
   $file{download}      = 0 + exists $file{download};
   $file{requireUser}   = 0 + exists $file{requireUser};
   $file{hide_from_list} = 0 + exists $file{hide_from_list};
+  $file{category}       ||= '';
 
   my $downloadPath = $self->{cfg}->entryVar('paths', 'downloads');
 
@@ -3164,9 +3174,10 @@ sub fileadd {
   $file{whenUploaded} = now_datetime();
   $file{storage}        = 'local';
   $file{src}            = '';
+  $file{file_handler}   = "";
 
-  require ArticleFiles;
-  my $fileobj = ArticleFiles->add(@file{@cols});
+  require BSE::TB::ArticleFiles;
+  my $fileobj = BSE::TB::ArticleFiles->add(@file{@cols});
 
   $req->flash("New file added");
 
@@ -3179,7 +3190,7 @@ sub fileadd {
     my $src;
     $storage = $self->_select_filestore($req, $file_manager, $storage, $fileobj);
     $src = $file_manager->store($filename, $storage, $fileobj);
-      
+
     if ($src) {
       $fileobj->{src} = $src;
       $fileobj->{storage} = $storage;
@@ -3190,6 +3201,9 @@ sub fileadd {
     $req->flash($@);
   }
 
+  $fileobj->set_handler($req->cfg);
+  $fileobj->save;
+
   use Util 'generate_article';
   generate_article($articles, $article) if $Constants::AUTO_GENERATE;
 
@@ -3284,7 +3298,7 @@ sub filesave {
                           "You don't have access to save file information for this article");
   my @files = $self->get_files($article);
 
-  my $download_path = $self->{cfg}->entryVar('paths', 'downloads');
+  my $download_path = BSE::TB::ArticleFiles->download_path($self->{cfg});
 
   my $cgi = $req->cgi;
   my %names;
@@ -3292,6 +3306,7 @@ sub filesave {
   my @old_files;
   my @new_files;
   my %store_anyway;
+  my @content_changed;
   for my $file (@files) {
     my $id = $file->{id};
     my $desc = $cgi->param("description_$id");
@@ -3303,6 +3318,8 @@ sub filesave {
     }
     my $notes = $cgi->param("notes_$id");
     defined $notes and $file->{notes} = $notes;
+    my $category = $cgi->param("category_$id");
+    defined $category and $file->{category} = $category;
     my $name = $cgi->param("name_$id");
     if (defined $name) {
       $file->{name} = $name;
@@ -3364,6 +3381,7 @@ sub filesave {
              $file->{sizeInBytes} = -s $full_name;
              $file->{whenUploaded} = now_datetime();
              $file->{displayName} = $display_name;
+             push @content_changed, $file;
            }
            else {
              $errors{"file_$id"} = $msg;
@@ -3432,6 +3450,12 @@ sub filesave {
     unlink "$download_path/$filename";
   }
 
+  # update file type metadatas
+  for my $file (@content_changed) {
+    $file->set_handler($self->{cfg});
+    $file->save;
+  }
+
   use Util 'generate_article';
   generate_article($articles, $article) if $Constants::AUTO_GENERATE;
 
@@ -3489,7 +3513,7 @@ sub req_save_file {
                               "You don't have access to save file information for this article");
   my @other_files = grep $_->{id} != $id, @files;
 
-  my $download_path = $self->{cfg}->entryVar('paths', 'downloads');
+  my $download_path = BSE::TB::ArticleFiles->download_path($self->{cfg});
 
   my %errors;
 
index 8c8e597f39c85ff73c67b0a7e9a08548b4a89159..c89ab312216f49b13b5ddbc5fbbe7d4a21316dad 100644 (file)
@@ -2,12 +2,12 @@ package BSE::FileEditor;
 use strict;
 use BSE::Util::Tags;
 use Constants qw($TMPLDIR $IMAGES_URI);
-use ArticleFiles;
+use BSE::TB::ArticleFiles;
 use Util qw/refresh_to/;
 
 =head1 NAME
 
-  BSE::FileEditor - maintains a list of images associated with an article
+  BSE::FileEditor - maintains a list of files associated with an article
 
 =head1 SYNOPSIS
 
@@ -142,7 +142,7 @@ sub fileadd {
 
   my %file;
   
-  my @cols = ArticleFile->columns;
+  my @cols = BSE::TB::ArticleFile->columns;
   shift @cols;
   for my $col (@cols) {
     if (defined $self->{cgi}->param($col)) {
@@ -222,7 +222,7 @@ sub fileadd {
   $file{displayOrder} = time;
   $file{whenUploaded} = now_datetime();
 
-  my $fileobj = ArticleFiles->add(@file{@cols});
+  my $fileobj = BSE::TB::ArticleFiles->add(@file{@cols});
 
   $self->_refresh_list($article);
 }
@@ -320,7 +320,7 @@ sub _getfiles {
   my ($self, $article) = @_;
 
   return sort { $b->{displayOrder} <=> $a->{displayOrder} }
-    ArticleFiles->getBy(articleId=>$article->{id});
+    BSE::TB::ArticleFiles->getBy(articleId=>$article->{id});
 }
 
 1;
diff --git a/site/cgi-bin/modules/BSE/FileHandler/Base.pm b/site/cgi-bin/modules/BSE/FileHandler/Base.pm
new file mode 100644 (file)
index 0000000..bc14175
--- /dev/null
@@ -0,0 +1,34 @@
+package BSE::FileHandler::Base;
+use strict;
+use Carp qw(confess);
+
+sub new {
+  my ($class, $cfg) = @_;
+
+  $cfg
+    or confess "Missing cfg option";
+
+  return bless
+    {
+     cfg => $cfg,
+    }, $class;
+}
+
+sub cfg {
+  $_[0]{cfg};
+}
+
+sub cfg_entry {
+  my ($self, $key, $def) = @_;
+
+  return $self->cfg->entry($self->section, $key, $def);
+}
+
+sub process_file {
+  my ($self, $file) = @_;
+
+  my $class = ref $self;
+  confess "$class hasn't implemented process-file";
+}
+
+1;
diff --git a/site/cgi-bin/modules/BSE/FileHandler/Default.pm b/site/cgi-bin/modules/BSE/FileHandler/Default.pm
new file mode 100644 (file)
index 0000000..eb4cbf6
--- /dev/null
@@ -0,0 +1,24 @@
+package BSE::FileHandler::Default;
+use strict;
+use base "BSE::FileHandler::Base";
+use DevHelp::HTML;
+
+sub process_file {
+  my ($self, $file) = @_;
+
+  # accept the file, but do nothing
+
+  1;
+}
+
+sub inline {
+  my ($self, $file) = @_;
+
+  my $url = $file->url;
+  my $eurl = escape_html($url);
+  my $class = $file->{download} ? "file_download" : "file_inline";
+  my $html = qq!<a class="$class" href="$eurl">! . escape_html($file->{displayName}) . '</a>';
+  return $html;
+}
+
+1;
diff --git a/site/cgi-bin/modules/BSE/FileHandler/FLV.pm b/site/cgi-bin/modules/BSE/FileHandler/FLV.pm
new file mode 100644 (file)
index 0000000..816c2a3
--- /dev/null
@@ -0,0 +1,208 @@
+package BSE::FileHandler::FLV;
+use strict;
+use base "BSE::FileHandler::Base";
+use BSE::Util::Tags qw(tag_hash);
+use DevHelp::HTML;
+
+sub process_file {
+  my ($self, $file) = @_;
+
+  my %info;
+  unless 
+    (eval {
+      local $SIG{__DIE__};
+      require FLV::Info;
+      my $reader = FLV::Info->new;
+      $reader->parse($file->full_filename($self->cfg));
+      %info = $reader->get_info;
+      1;
+    }) {
+    die "Cannot parse as FLV: $@\n";
+  }
+
+  $info{video_width} && $info{video_height} && $info{duration}
+    or die "Missing required metadata\n";
+
+  $file->add_meta(name => 'width', value => $info{video_width});
+  $file->add_meta(name => 'height', value => $info{video_height});
+  my $dur_secs = sprintf("%.0f", $info{duration}/1000);
+  $file->add_meta(name => 'duration', value => $dur_secs);
+  my $fmt_dur = sprintf("%02d:%02d:%02d", $dur_secs / 3600, ($dur_secs / 60) % 60,
+                       $dur_secs % 60);
+  $file->add_meta(name => "duration_formatted", value => $fmt_dur);
+  $file->add_meta(name => 'audio_type', value => $info{audio_type});
+
+  my $section = "flv handler";
+  if ($self->cfg_entry("ffmpeg", 1)) {
+    my $raw_frame = $self->cfg_entry("raw_frame", 1);
+    my $fmt = $self->cfg_entry("frame_fmt", "jpeg");
+    my $content_type = $self->cfg_entry("frame_content_type", "image/$fmt");
+    my $bin = $self->cfg_entry("ffmpeg_bin", "ffmpeg");
+    my $debug = $self->cfg_entry("debug", 0);
+    my @geo_names = split /,/, $self->cfg_entry("frame_thumbs");
+    my @cvt_options = split /,/, $self->cfg_entry("ffmpeg_options");
+    my $cmd = "$bin -i " . $file->full_filename($self->cfg) . " -vcodec ppm -ss 5 -vframes 1 @cvt_options -f image2 -";
+    my $redir = $debug ? "" : "2>/dev/null";
+    my $ppm_data = `$cmd $redir`;
+
+    $?
+      and die "Cannot extract placeholder image\n";
+
+    if ($raw_frame) {
+      my $image_data = '';
+      require Imager;
+      my $im = Imager->new;
+      $im->read(data => $ppm_data, type => "pnm")
+       or die "Cannot load placeholder image: ", $im->errstr, "\n";
+      
+      $im->write(data => \$image_data, type => $fmt, @cvt_options)
+      or die "Cannot produce final placeholder data: ", $im->errstr, "\n";
+      $file->add_meta(name => "ph_data",
+                     value => $image_data,
+                     content_type => $content_type);
+    }
+
+    if (@geo_names) {
+      my $thumbs = $self->_get_thumbs_class;
+      for my $geo_name (@geo_names) {
+       my $geometry = $self->cfg->entry('thumb geometries', $geo_name)
+         or die "Cannot find thumb geometry $geo_name\n";
+       my $thumb_data = '';
+       my $error;
+       my ($twidth, $theight, $type, $original) =
+         $thumbs->thumb_dimensions_sized
+         (
+          $geometry,
+          $info{video_width},
+          $info{video_height},
+          undef
+         );
+       ($thumb_data, $content_type) = $thumbs->thumb_data
+         (
+          data => $ppm_data,
+          geometry => $geometry,
+          error => \$error
+         )
+           or die "Cannot thumb captured frame; $error\n";
+       $file->add_meta(name => "ph_${geo_name}_width",
+                       value => $twidth);
+       $file->add_meta(name => "ph_${geo_name}_height",
+                       value => $theight);
+       $file->add_meta(name => "ph_${geo_name}_data",
+                       value => $thumb_data,
+                       content_type => $content_type);
+      }
+    }
+
+  }
+
+  return 1;
+}
+
+sub section {
+  return "flv handler";
+}
+
+sub _get_thumbs_class {
+  my ($self) = @_;
+
+  my $class = $self->cfg->entry('editor', 'thumbs_class')
+    or return;
+  
+  (my $filename = "$class.pm") =~ s!::!/!g;
+  eval { require $filename; };
+  if ($@) {
+    print STDERR "** Error loading thumbs_class $class ($filename): $@\n";
+    return;
+  }
+  my $obj;
+  eval { $obj = $class->new($self->{cfg}) };
+  if ($@) {
+    print STDERR "** Error creating thumbs objects $class: $@\n";
+    return;
+  }
+
+  return $obj;
+}
+
+sub inline {
+  my ($self, $file, $parms) = @_;
+
+  defined $parms or $parms = '';
+
+  require BSE::Template;
+  my %meta = map { $_->name => $_->value }
+    grep $_->content_type eq "text/plain", $file->metadata;
+
+  my $base_template = "inline/flv";
+  my $template = $base_template;
+  if ($parms =~ s/\btemplate=(\w+)//
+     || $parms =~ /^(\w+)$/) {
+    $template .= "_$1";
+  }
+
+  my %acts =
+    (
+     BSE::Util::Tags->static(undef, $self->cfg),
+     meta => [ \&tag_hash, \%meta ],
+     file => [ \&tag_hash, $file ],
+     src => scalar(escape_html($file->url)),
+    );
+
+  return BSE::Template->get_page($template, $self->cfg, \%acts, );
+}
+
+1;
+
+=item NAME
+
+BSE::FileHandler::FLV - file metadata generator for FLV files
+
+=item Configuration
+
+  [flv handler]
+  ffmpeg=1
+  raw_frame=1
+  frame_fmt=jpeg
+  ; following defaults to image/(frame_fmt)
+  frame_content_type=image/jpeg
+  ffmpeg_bin=ffmpeg
+  frame_thumbs=geoname1,geoname2
+
+=over
+
+=item *
+
+ffmpeg - if non-zero, which is the default, ffmpeg is used to extract
+a single frame for use as a placeholder image.
+
+=item *
+
+raw_frame - if non-zero, which is the default, the frame extracted
+with ffmpeg is added as metadata with name C<ph_data>.
+
+=item *
+
+frame_fmt - the image format of the C<ph_data> image.  Default: jpeg.
+
+=item *
+
+frame_content_type - the MIME content type of the C<ph_data> image,
+defaults to "image/" followed by the value of frame_fmt.
+
+=item *
+
+ffmpeg_bin - the name of the ffmpeg binary.  If ffmpeg is in the PATH
+this can be left as just C<ffmpeg>, otherwise set this to the full
+path to ffmpeg.
+
+=item *
+
+frame_thumbs - geometry names separated by commas for extra
+thumbnailed placeholder images, attached as C<ph_>I<geometry>C<_data>
+metadata to the file, with the width and height in
+C<ph_>I<geometry>C<_width> and C<ph_>I<geometry>C<_height>.
+
+=back
+
+=cut
index d010a1939b54ce9c716193fd0b1d21dec2ad56b0..a97134a58b69d8700b022ded22c1e0577ab4a400 100644 (file)
@@ -264,6 +264,19 @@ sub gfilelink {
   return $self->_file($file, $text, $type);
 }
 
+sub file {
+  my ($self, $fileid, $field, $type) = @_;
+
+  my ($file) = grep $_->{name} eq $fileid, @{$self->{files}}
+    or return "* unknown file $fileid *";
+
+  return $file->inline
+    (
+     field => $field,
+     cfg => $self->{gen}->cfg,
+    );
+}
+
 sub thumbimage {
   my ($self, $geo_id, $image_id) = @_;
 
@@ -313,6 +326,8 @@ sub replace {
       and return 1;
   $$rpart =~ s#filelink\[\s*(\w+)\s*\]# $self->filelink($1, undef, 'filelink') #ige
       and return 1;
+  $$rpart =~ s#file\[(\w+)(?:\|([\w.]*))?\]# $self->file($1, $2, 'file') #ige
+    and return 1;
   $$rpart =~ s#gpopimage\[([^\[\]]+)\]# $self->gpopimage($1) #ige
     and return 1;
   $$rpart =~ s#popimage\[([^\[\]]+)\]# $self->popimage($1) #ige
index 43b74769e4eb13450dc986256f41d50f8d8d2348..e69e5747c480458467a46cff8651d747bfc995e1 100644 (file)
@@ -117,8 +117,6 @@ sub _make_thumb_hash {
 sub thumb {
   my ($self, %opts) = @_;
 
-  $DB::single=1;
-
   my $geo_id = delete $opts{geo};
   defined $geo_id
     or confess "Missing geo parameter";
index c1bcdaa8c322f78be77155bda0e681030954d04b..d4023a337f48dc7ba3bf826e130b51c6d3c60144 100644 (file)
@@ -351,11 +351,11 @@ sub need_logon {
   my $user = get_siteuser($session, $cfg, $cgi);
 
   if (!$user && $reg_if_files) {
-    require ArticleFiles;
+    require BSE::TB::ArticleFiles;
     # scan to see if any of the products have files
     # requires a subscription or subscribes
     for my $prod (@$cart_prods) {
-      my @files = ArticleFiles->getBy(articleId=>$prod->{id});
+      my @files = BSE::TB::ArticleFiles->getBy(articleId=>$prod->{id});
       if (grep $_->{forSale}, @files) {
        return ("register before checkout", "shop/fileitems");
       }
index 3be76fa26d8cd8766e073a7f2a3dc7d2260edd69..632d6065801163b248b8bcf339d992582cca48c6 100644 (file)
@@ -24,8 +24,8 @@ sub type {
 }
 
 sub files {
-  require ArticleFiles;
-  return ArticleFiles->file_storages;
+  require BSE::TB::ArticleFiles;
+  return BSE::TB::ArticleFiles->file_storages;
 }
 
 sub metadata {
diff --git a/site/cgi-bin/modules/BSE/TB/ArticleFile.pm b/site/cgi-bin/modules/BSE/TB/ArticleFile.pm
new file mode 100644 (file)
index 0000000..4fff9ed
--- /dev/null
@@ -0,0 +1,174 @@
+package BSE::TB::ArticleFile;
+use strict;
+# represents a file associated with an article from the database
+use Squirrel::Row;
+use vars qw/@ISA/;
+@ISA = qw/Squirrel::Row/;
+use Carp 'confess';
+
+sub columns {
+  return qw/id articleId displayName filename sizeInBytes description 
+            contentType displayOrder forSale download whenUploaded
+            requireUser notes name hide_from_list storage src category
+            file_handler/;
+}
+
+sub full_filename {
+  my ($self, $cfg) = @_;
+
+  my $downloadPath = BSE::TB::ArticleFiles->download_path($cfg);
+  return $downloadPath . "/" . $self->{filename};
+}
+
+sub remove {
+  my ($self, $cfg) = @_;
+
+  $self->clear_metadata;
+
+  $cfg or confess "No \$cfg supplied to ",ref $self,"->remove()";
+
+  my $filename = $self->full_filename($cfg);
+  my $debug_del = $cfg->entryBool('debug', 'file_unlink', 0);
+  if ($debug_del) {
+    unlink $filename
+      or print STDERR "Error deleting $filename: $!\n";
+  }
+  else {
+    unlink $filename;
+  }
+  
+  $self->SUPER::remove();
+}
+
+sub article {
+  my $self = shift;
+  require Articles;
+
+  return Articles->getByPkey($self->{articleId});
+}
+
+sub url {
+  my ($file) = @_;
+
+  return "/cgi-bin/user.pl/download_file/$file->{id}";
+}
+
+sub handler {
+  my ($self, $cfg) = @_;
+
+  return BSE::TB::ArticleFiles->handler($self->file_handler, $cfg);
+}
+
+sub clear_metadata {
+  my ($self) = @_;
+
+  BSE::DB->run(bseClearArticleFileMetadata => $self->{id});
+}
+
+sub set_handler {
+  my ($self, $cfg) = @_;
+
+  my %errors; # save for later setting into the metadata
+
+  for my $handler_entry (BSE::TB::ArticleFiles->file_handlers($cfg)) {
+    my ($key, $handler) = @$handler_entry;
+    my $success = eval {
+      $self->clear_metadata;
+      $handler->process_file($self);
+      1;
+    };
+    if ($success) {
+      # set errors from handlers that failed
+      for my $key (keys %errors) {
+       $self->add_meta(name => "${key}_error", value => $errors{$key});
+      }
+      $self->set_file_handler($key);
+
+      return;
+    }
+    else {
+      $errors{$key} = $@;
+      chomp $errors{$key};
+    }
+  }
+
+  # we should never get here
+  $self->set_file_handler("");
+  print STDERR "** Ran off the end of ArticleFile->set_handler()\n";
+  return;
+}
+
+sub add_meta {
+  my ($self, %opts) = @_;
+
+  require BSE::TB::ArticleFileMetas;
+  return BSE::TB::ArticleFileMetas->make
+      (
+       file_id => $self->{id},
+       %opts,
+      );
+}
+
+sub metadata {
+  my ($self) = @_;
+
+  require BSE::TB::ArticleFileMetas;
+  return  BSE::TB::ArticleFileMetas->getBy
+    (
+     file_id => $self->id
+    );
+}
+
+sub meta_by_name {
+  my ($self, $name) = @_;
+
+  require BSE::TB::ArticleFileMetas;
+  my ($result) = BSE::TB::ArticleFileMetas->getBy
+    (
+     file_id => $self->id,
+     name => $name
+    )
+      or return;
+
+  return $result;
+}
+
+sub inline {
+  my ($file, %opts) = @_;
+
+  my $cfg = delete $opts{cfg}
+    or confess "Missing cfg parameter";
+
+  my $field = delete $opts{field};
+  defined $field or $field = "";
+
+  if ($field && exists $file->{$field}) {
+    return escape_html($file->{$field});
+  }
+  elsif ($field =~ /^meta\.(\w+)$/) {
+    my $name = $1;
+    my $meta = $file->meta_by_name($name)
+      or return "";
+    $meta->content_type eq "text/plain"
+      or return "* metadata $name isn't text *";
+
+    return escape_html($meta->value);
+  }
+  elsif ($field eq "link") {
+    my $url = "/cgi-bin/user.pl?download_file=1&file=$file->{id}";
+    my $eurl = escape_html($url);
+    if ($field eq 'url') {
+      return $eurl;
+    }
+    my $class = $file->{download} ? "file_download" : "file_inline";
+    my $html = qq!<a class="$class" href="$eurl">! . escape_html($file->{displayName}) . '</a>';
+    return $html;
+  }
+  else {
+    my $handler = $file->handler($cfg);
+    return $handler->inline($file, $field);
+  }
+
+}
+
+1;
diff --git a/site/cgi-bin/modules/BSE/TB/ArticleFileMeta.pm b/site/cgi-bin/modules/BSE/TB/ArticleFileMeta.pm
new file mode 100644 (file)
index 0000000..73f5974
--- /dev/null
@@ -0,0 +1,17 @@
+package BSE::TB::ArticleFileMeta;
+use strict;
+use base 'Squirrel::Row';
+
+sub table {
+  "bse_article_file_meta";
+}
+
+sub columns {
+  qw/id file_id name content_type value/;
+}
+
+sub defaults {
+  content_type => "text/plain",
+}
+
+1;
diff --git a/site/cgi-bin/modules/BSE/TB/ArticleFileMetas.pm b/site/cgi-bin/modules/BSE/TB/ArticleFileMetas.pm
new file mode 100644 (file)
index 0000000..a1c7910
--- /dev/null
@@ -0,0 +1,8 @@
+package BSE::TB::ArticleFileMetas;
+use strict;
+use base 'Squirrel::Table';
+use BSE::TB::ArticleFileMeta;
+
+sub rowClass { "BSE::TB::ArticleFileMeta" }
+
+1;
diff --git a/site/cgi-bin/modules/BSE/TB/ArticleFiles.pm b/site/cgi-bin/modules/BSE/TB/ArticleFiles.pm
new file mode 100644 (file)
index 0000000..42e967a
--- /dev/null
@@ -0,0 +1,64 @@
+package BSE::TB::ArticleFiles;
+use strict;
+use Squirrel::Table;
+use vars qw(@ISA $VERSION);
+@ISA = qw(Squirrel::Table);
+use BSE::TB::ArticleFile;
+use Carp qw(confess);
+
+sub rowClass {
+  return 'BSE::TB::ArticleFile';
+}
+
+sub file_storages {
+  my $self = shift;
+  return map [ $_->{filename}, $_->{storage}, $_ ], $self->all;
+}
+
+sub categories {
+  my ($class, $cfg) = @_;
+
+  my @cat_ids = split /,/, $cfg->entry("article file categories", "ids", "");
+  grep $_ eq "", @cat_ids
+    or unshift @cat_ids, "";
+
+  my @cats;
+  for my $id (@cat_ids) {
+    my $section = length $id ? "file category $id" : "file category empty";
+    my $def_name = length $id ? ucfirst $id : "(None)";
+    my $name = $cfg->entry($section, "name", $def_name);
+    push @cats, +{ id => $id, name => $name };
+  }
+
+  return @cats;
+}
+
+sub file_handlers {
+  my ($self, $cfg) = @_;
+
+  $cfg or confess "Missing cfg option";
+
+  my @handlers = split /[;,]/, $cfg->entry('basic', 'file_handlers');
+  push @handlers, "";
+
+  return map [ $_, $self->handler($_, $cfg) ], @handlers;
+}
+
+sub handler {
+  my ($self, $id, $cfg) = @_;
+
+  my $key = $id || "default";
+  my $class = $id ? $cfg->entry("file handlers", $id) : "BSE::FileHandler::Default";
+
+  (my $file = $class . ".pm") =~ s(::)(/)g;
+  require $file;
+  return $class->new($cfg);
+}
+
+sub download_path {
+  my ($class, $cfg) = @_;
+
+  return $cfg->entryVar('paths', 'downloads');
+}
+
+1;
index 314db0d2174716acf50fe69bbccb3c91bdb0927f..6f819bd348baf392b9fc633130a41fa507b8b04b 100644 (file)
@@ -28,19 +28,11 @@ sub _format_file {
 
   defined $field or $field = '';
 
-  if ($field && exists $file->{$field}) {
-    return escape_html($file->{$field});
-  }
-  else {
-    my $url = "/cgi-bin/user.pl?download_file=1&file=$file->{id}";
-    my $eurl = escape_html($url);
-    if ($field eq 'url') {
-      return $eurl;
-    }
-    my $class = $file->{download} ? "file_download" : "file_inline";
-    my $html = qq!<a class="$class" href="$eurl">! . escape_html($file->{displayName}) . '</a>';
-    return $html;
-  }
+  return $file->inline
+    (
+     cfg => $self->cfg,
+     field => $field
+    );
 }
 
 1;
index 348c07760b6b0064cb16b77c3800f44e814925b4..dc75056e7e89effa6e2347cb124d3abf8b62922e 100644 (file)
@@ -110,7 +110,14 @@ sub thumb_dimensions_sized {
 }
 
 sub thumb_data {
-  my ($self, $filename, $geometry, $error) = @_;
+  my ($self, %opts) = @_;
+
+  #$filename, $geometry, $error) = @_;
+
+  my $filename = delete $opts{filename};
+  my $geometry = delete $opts{geometry};
+  my $data = delete $opts{data};
+  my $error = delete $opts{error};
 
   my $geolist = $self->_parse_geometry($geometry, $error)
     or return;
@@ -122,8 +129,20 @@ sub thumb_data {
   }
   require Imager;
   my $src = Imager->new;
-  unless ($src->read(file => $filename)) {
-    $$error = "Cannot read image $filename: ".$src->errstr;
+  if ($filename) {
+    unless ($src->read(file => $filename)) {
+      $$error = "Cannot read image $filename: ".$src->errstr;
+      return;
+    }
+  }
+  elsif ($data) {
+    unless ($src->read(data => $data)) {
+      $$error = "Cannot read image (by data): ".$src->errstr;
+      return;
+    }
+  }
+  else {
+    $$error = "No image filename or data supplied";
     return;
   }
 
@@ -135,6 +154,11 @@ sub thumb_data {
      translate => 'errdiff',
      gifquant => 'gen',
     );
+  $write_opts{type} = $src->tags(name => 'i_format');
+  my @allowed_types = split /,/, $self->{cfg}->entry(CFG_SECTION, 'types', "jpeg,png,gif");
+  unless (grep $_ eq $write_opts{type}, @allowed_types) {
+    $write_opts{type} = $allowed_types[0];
+  }
 
   my $work = $src;
   for my $geo (@$geolist) {
@@ -144,7 +168,6 @@ sub thumb_data {
   if ($work->getchannels == 4 || $work->getchannels == 2) {
     $write_opts{type} ||= 'png';
   }
-  $write_opts{type} ||= $src->tags(name => 'i_format');
 
   my $type = $write_opts{type};
 
@@ -157,14 +180,14 @@ sub thumb_data {
     $work = $tmp;
   }
 
-  my $data;
+  my $outdata;
   $work = $work->to_rgb8;
-  unless ($work->write(data => \$data, %write_opts)) {
+  unless ($work->write(data => \$outdata, %write_opts)) {
     $$error = "cannot write image ".$work->errstr;
     return;
   }
 
-  return ( $data, "image/$type" );
+  return ( $outdata, "image/$type" );
 }
 
 sub _incs {
@@ -1463,6 +1486,7 @@ BSE::Thumb::Imager - thumbnail driver using Imager
  include=path1:path2:path3
  blib=/path/to/imager/src
  jpegquality=90
+ allowed_types=jpeg,png,gif
 
  [thumb geometries]
  geometryname1=operator1(arguments),operator2(arguments)
index e47923b0a4fa359aee8b9d35cd9891059c8b1e88..d079b9ccb41f2a7e7947ce9251604844a8dee86d 100644 (file)
@@ -316,8 +316,8 @@ sub product_form {
   }
   my @files;
   if ($product->{id}) {
-    require 'ArticleFiles.pm';
-    @files = ArticleFiles->getBy(articleId=>$product->{id});
+    require BSE::TB::ArticleFiles;
+    @files = BSE::TB::ArticleFiles->getBy(articleId=>$product->{id});
   }
   my $file_index;
 
index 543901b15839ed186d0cc2b60e084f62f56b01c8..fbfe7109244e8cf535561311c51bd6113f837025 100644 (file)
@@ -45,6 +45,13 @@ sub dispatch {
   unless ($action) {
     ($action, @extras) = $self->other_action($cgi);
   }
+  if (!$action && $ENV{PATH_INFO}) {
+    my @components = split '/', $ENV{PATH_INFO};
+    @components && !$components[0] and shift @components;
+    if (@components && $actions->{$components[0]}) {
+      ($action, @extras) = @components;
+    }
+  }
   $action ||= $self->default_action;
 
   $self->check_action($req, $action, \$result)
index 3ddece4358ec96f864301c9e3ab42a2d4a9c40e6..b6e17d7a3c28e5bb7ff029838ddb555b5a6ee86c 100644 (file)
@@ -81,7 +81,12 @@ sub dispatch {
     -e $filename 
       or return $class->error($req, "image file missing");
     
-    (my $data, $type) = $thumbs->thumb_data($filename, $geometry, \$error)
+    (my $data, $type) = $thumbs->thumb_data
+      (
+       filename => $filename,
+       geometry => $geometry,
+       error => \$error
+      )
       or return $class->error($req, $error);
     
     return
index 5b63d04cb340074718d5c1b9bacab685884128d1..d4dfbba085e0b13e937f60a176148c289ae034d9 100644 (file)
@@ -97,7 +97,7 @@ sub req_info {
       sub {
        require 'ArticleFiles.pm';
        @files = sort { $b->{displayOrder} <=> $a->{displayOrder} }
-         ArticleFiles->getBy(articleId=>$items[$item_index]{productId});
+         BSE::TB::ArticleFiles->getBy(articleId=>$items[$item_index]{productId});
       },
       'prodfile', 'prodfiles', \$file_index),
      ifFileAvail =>
index 76f00154aab2ddf87539e29ae5d4c5bde63ea0c6..b7f5309d9c40e06b654ac8c4347ddb71b6766de0 100644 (file)
@@ -44,6 +44,7 @@ my %actions =
    orderdetail => 'req_orderdetail',
    wishlist => 'req_wishlist',
    downufile => 'req_downufile',
+   file_metadata => "req_file_metadata",
   );
 
 sub actions { \%actions }
@@ -1073,9 +1074,9 @@ sub req_userpage {
      make_multidependent_iterator
      ([ \$item_index, \$order_index],
       sub {
-       require 'ArticleFiles.pm';
+       require BSE::TB::ArticleFiles;
        @files = sort { $b->{displayOrder} <=> $a->{displayOrder} }
-         ArticleFiles->getBy(articleId=>$items[$item_index]{productId});
+         BSE::TB::ArticleFiles->getBy(articleId=>$items[$item_index]{productId});
       },
       'prodfile', 'prodfiles', \$file_index),
      ifFileAvail =>
@@ -1222,8 +1223,8 @@ sub req_download {
   my ($item) = grep $_->{id} == $itemid,
   BSE::TB::OrderItems->getBy(orderId=>$order->{id})
     or return _refresh_userpage($cfg, $msgs->(notinorder=>"Not part of that order"));
-  require 'ArticleFiles.pm';
-  my @files = ArticleFiles->getBy(articleId=>$item->{productId})
+  require BSE::TB::ArticleFiles;
+  my @files = BSE::TB::ArticleFiles->getBy(articleId=>$item->{productId})
     or return _refresh_userpage($cfg, $msgs->(nofilesonline=>"No files in this line"));
   my $fileid = $cgi->param('file')
     or return _refresh_userpage($cfg, $msgs->(nofileid=>"No file id supplied"));
@@ -1268,7 +1269,7 @@ sub req_download {
 }
 
 sub req_download_file {
-  my ($self, $req) = @_;
+  my ($self, $req, $fileid) = @_;
 
   my $cfg = $req->cfg;
   my $cgi = $req->cgi;
@@ -1280,10 +1281,10 @@ sub req_download_file {
   if ($userid) {
     $user = SiteUsers->getBy(userId=>$userid);
   }
-  my $fileid = $cgi->param('file')
+  $fileid ||= $cgi->param('file')
     or return $self->req_show_logon($req, 
                         $msgs->('nofileid', "No file id supplied"));
-  require ArticleFiles;
+  require BSE::TB::ArticleFiles;
   my $file;
   my $article;
   my $article_id = $cgi->param('page');
@@ -1309,7 +1310,7 @@ sub req_download_file {
     ($file) = grep $_->{name} eq $fileid, $article->files;
   }
   else {
-    $file = ArticleFiles->getByPkey($fileid);
+    $file = BSE::TB::ArticleFiles->getByPkey($fileid);
   }
   $file
     or return $self->req_show_logon($req,
@@ -1386,6 +1387,45 @@ sub req_download_file {
   close FILE;
 }
 
+sub req_file_metadata {
+  my ($self, $req, $fileid, $metaname) = @_;
+
+  my $user = $req->siteuser;
+  my $cgi = $req->cgi;
+  $fileid ||= $cgi->param('file')
+    or return $self->req_show_logon($req, $req->text(nofileid => "No file id supplied"));
+  $metaname ||= $cgi->param('name')
+    or return $self->req_show_logon($req, $req->text(nometaname => "No metaname supplied"));
+  require BSE::TB::ArticleFiles;
+  my $file = BSE::TB::ArticleFiles->getByPkey($fileid)
+    or return $self->req_show_logon($req, $req->text(nosuchfile => "No such file"));
+  
+  if ($file->articleId != -1) {
+    # check the user has access
+    my $article = $file->article
+      or return $self->req_show_logon($req, $req->text(nofilearticle => "No article found for this file"));
+    if ($article->is_dynamic && !$req->siteuser_has_access($article)) {
+      if ($req->siteuser) {
+       return $self->req_userpage($req, $req->text(downloadnoacces => "You do not have access to this article"));
+      }
+      else {
+       return $self->req_show_logon($req, $req->text(needlogon => "You need to logon to download this file"));
+      }
+    }
+  }
+  my $meta = $file->meta_by_name($metaname)
+    or return $self->req_show_logon($req, $req->text(nosuchmeta => "There is no metadata by that name for this file"));
+
+  my %result =
+    (
+     type => $meta->content_type,
+     content => $meta->value,
+    );
+    
+  require BSE::Template;
+  BSE::Template->output_result($req, \%result);
+}
+
 sub req_show_lost_password {
   my ($self, $req, $message) = @_;
 
index 77ec4a41df7660c07f56b2ed57855ab82d2a77a9..374a7854da669695c738731ceaa3fca1c7e3839b 100644 (file)
@@ -50,7 +50,12 @@ sub generate_thumb {
 
     my $error;
     (my $data, $type) =
-      $thumbs->thumb_data($image_filename, $geometry, \$error)
+      $thumbs->thumb_data
+       (
+        filename => $image_filename,
+        geometry => $geometry,
+        error => \$error
+       )
        or return;
 
     if (open IMAGE, ">$cache_name") {
index f6b5b4ab6e3f8538dfd30b25f53f508c377fe6fb..7e19e0f107c7d3204bf3b0c8b1b1cffcf637c314 100644 (file)
@@ -8,7 +8,7 @@ use vars qw(@ISA);
 use Generate;
 use Util qw(generate_button);
 use BSE::Util::Tags qw(tag_article);
-use ArticleFiles;
+use BSE::TB::ArticleFiles;
 @ISA = qw/Generate/;
 use DevHelp::HTML;
 use BSE::Arrows;
@@ -340,7 +340,7 @@ sub baseActs {
   my $image_index = -1;
   my $had_image_tags = 0;
   my @all_files = sort { $b->{displayOrder} <=> $a->{displayOrder} }
-    ArticleFiles->getBy(articleId=>$article->{id});
+    BSE::TB::ArticleFiles->getBy(articleId=>$article->{id});
   my @files = grep !$_->{hide_from_list}, @all_files;
   
   my $blank = qq!<img src="$IMAGES_URI/trans_pixel.gif"  width="17" height="13" border="0" align="absbottom" alt="" />!;
index 393345e902199c07809a5b4c737acf61ee7f02c4..e8023de496a27cee228d57021ed64eb97f892264 100755 (executable)
@@ -1,6 +1,6 @@
 #!/usr/bin/perl -w
 # -d:ptkdb
-BEGIN { $ENV{DISPLAY} = '192.168.32.51:0.0'; }
+BEGIN { $ENV{DISPLAY} = '192.168.32.54:0.0'; }
 use strict;
 use FindBin;
 use lib "$FindBin::Bin/modules";
index cc3f28b694d664fb2880f2decfe990c65698f008..9c755b91dfaa5d951f3b31653beafcad8a7aaba9 100644 (file)
@@ -194,3 +194,9 @@ SQL
 
 name: BackgroundTasks
 sql_statement: select * from bse_background_tasks
+
+name: bseClearArticleFileMetadata
+sql_statement: <<SQL
+delete from bse_article_file_meta
+where file_id = ?
+SQL
index 2f202fa84ae7400dc53a91a92289fd5380414e51..0a6fb3af6f2ddffde4944c303f848f6f73faf5a3 100644 (file)
@@ -77,3 +77,34 @@ form td.submit { text-align: right; }
   float: left;
 }
 
+#bse_video_stage { 
+  position: fixed;
+  width: 100%;
+  height: 100%;
+  top: 0px;
+  background-image: url(/images/50.png);
+  display: none;
+}
+
+#bse_video_frame { 
+  position: absolute;
+  border: 1px solid #000;
+  left: 50%;
+  top: 20%;
+}
+
+#bse_video_close { 
+  display: block;
+  position: absolute;
+  right: 50%;
+  top: 20%;
+  margin-top: -20px;
+}
+
+#bse_video_close img { 
+  border: 0px;
+}
+
+.bse_video_popup img {
+  border: 1px solid #CCC;
+}
\ No newline at end of file
diff --git a/site/htdocs/images/50.png b/site/htdocs/images/50.png
new file mode 100644 (file)
index 0000000..693c3f6
Binary files /dev/null and b/site/htdocs/images/50.png differ
diff --git a/site/htdocs/images/videoclose.png b/site/htdocs/images/videoclose.png
new file mode 100644 (file)
index 0000000..6d1ddb5
Binary files /dev/null and b/site/htdocs/images/videoclose.png differ
diff --git a/site/htdocs/js/bse.js b/site/htdocs/js/bse.js
new file mode 100644 (file)
index 0000000..03d4698
--- /dev/null
@@ -0,0 +1,82 @@
+var bse_image_popup;
+function bse_popup_image (article_id, image_id, width, height, tag_id, image_url) {
+  var url = "/cgi-bin/image.pl?id=" + article_id + "&imid=" + image_id
+       + '&comment=jspopup';
+  var work_width = width;
+  var work_height = height;
+  var features = 'width=' + work_width + ',height=' + work_height
+    + ',resizable=yes';
+
+  // lose the old one if it exists
+  if (bse_image_popup != null && !bse_image_popup.closed) {
+    bse_image_popup.close();
+  }
+  var left = window.screenX + (window.outerWidth - work_width) / 2;
+  var top = window.screenY + (window.outerHeight - work_height) / 2;
+  features = features + ",top=" + top + ",left=" + left;
+
+  bse_image_popup = window.open(url, 'bse_image', features, 0);
+
+  return 0;
+}
+
+var bse_video_stage;
+var bse_video_frame;
+var bse_video_running = 0;
+
+function bse_show_video (fileid, width, height, src) {
+  if (bse_video_running) {
+    bse_close_video();
+    return false;
+  }
+  // check for our staging area
+  if (!bse_video_stage) {
+    bse_video_stage = document.getElementById("bse_video_stage");
+    bse_video_frame = document.getElementById("bse_video_frame");
+    bse_video_close = document.getElementById("bse_video_close");
+  }
+  if (!bse_video_stage) {
+    bse_video_stage = document.createElement("div");
+    bse_video_stage.id = "bse_video_stage";
+    bse_video_frame = document.createElement("div");
+    bse_video_frame.id = "bse_video_frame";
+    bse_video_close = document.createElement("a");
+    bse_video_close.href="#";
+    bse_video_close.onclick = bse_close_video;
+    bse_video_close.id = "bse_video_close";
+    bse_video_stage.appendChild(bse_video_frame);
+    bse_video_stage.appendChild(bse_video_close);
+    var img = document.createElement("img");
+    img.src = "/images/videoclose.png";
+    bse_video_close.appendChild(img);
+    document.body.appendChild(bse_video_stage);
+  }
+  bse_video_frame.style.width = width + "px";
+  bse_video_frame.style.height = height + "px";
+  var margin = (-width / 2);
+  bse_video_frame.style.marginLeft = margin + "px";
+  bse_video_close.style.marginRight = (margin - 20) + "px";
+
+
+  var so = new SWFObject("/swf/flvplayer.swf", "flvplayer", width, height, "8", "#000000");
+  so.addParam('allowFullScreen',true);
+  so.addVariable('aspect_ratio', width/height);
+  //so.addVariable('placeholder','/cgi-bin/user.pl/file_metadata/'+fileid+'/ph_data');
+  so.addVariable('video_path',src);
+  so.addVariable('autoplay');
+  so.write("bse_video_frame");
+
+  bse_video_stage.style.display="block";
+
+  bse_video_stage.onmousedown = "bse_close_video(); return false;";
+  bse_video_running = 1;
+
+  return false;
+}
+
+function bse_close_video() {
+  bse_video_frame.innerHTML = '';
+  bse_video_stage.style.display = "none";
+  bse_video_running = 0;
+  return false;
+}
\ No newline at end of file
diff --git a/site/htdocs/js/swfobject.js b/site/htdocs/js/swfobject.js
new file mode 100644 (file)
index 0000000..c11a56e
--- /dev/null
@@ -0,0 +1,8 @@
+/**
+ * SWFObject v1.5.1: Flash Player detection and embed - http://blog.deconcept.com/swfobject/
+ *
+ * SWFObject is (c) 2007 Geoff Stearns and is released under the MIT License:
+ * http://www.opensource.org/licenses/mit-license.php
+ *
+ */
+if(typeof deconcept=="undefined"){var deconcept={};}if(typeof deconcept.util=="undefined"){deconcept.util={};}if(typeof deconcept.SWFObjectUtil=="undefined"){deconcept.SWFObjectUtil={};}deconcept.SWFObject=function(_1,id,w,h,_5,c,_7,_8,_9,_a){if(!document.getElementById){return;}this.DETECT_KEY=_a?_a:"detectflash";this.skipDetect=deconcept.util.getRequestParameter(this.DETECT_KEY);this.params={};this.variables={};this.attributes=[];if(_1){this.setAttribute("swf",_1);}if(id){this.setAttribute("id",id);}if(w){this.setAttribute("width",w);}if(h){this.setAttribute("height",h);}if(_5){this.setAttribute("version",new deconcept.PlayerVersion(_5.toString().split(".")));}this.installedVer=deconcept.SWFObjectUtil.getPlayerVersion();if(!window.opera&&document.all&&this.installedVer.major>7){if(!deconcept.unloadSet){deconcept.SWFObjectUtil.prepUnload=function(){__flash_unloadHandler=function(){};__flash_savedUnloadHandler=function(){};window.attachEvent("onunload",deconcept.SWFObjectUtil.cleanupSWFs);};window.attachEvent("onbeforeunload",deconcept.SWFObjectUtil.prepUnload);deconcept.unloadSet=true;}}if(c){this.addParam("bgcolor",c);}var q=_7?_7:"high";this.addParam("quality",q);this.setAttribute("useExpressInstall",false);this.setAttribute("doExpressInstall",false);var _c=(_8)?_8:window.location;this.setAttribute("xiRedirectUrl",_c);this.setAttribute("redirectUrl","");if(_9){this.setAttribute("redirectUrl",_9);}};deconcept.SWFObject.prototype={useExpressInstall:function(_d){this.xiSWFPath=!_d?"expressinstall.swf":_d;this.setAttribute("useExpressInstall",true);},setAttribute:function(_e,_f){this.attributes[_e]=_f;},getAttribute:function(_10){return this.attributes[_10]||"";},addParam:function(_11,_12){this.params[_11]=_12;},getParams:function(){return this.params;},addVariable:function(_13,_14){this.variables[_13]=_14;},getVariable:function(_15){return this.variables[_15]||"";},getVariables:function(){return this.variables;},getVariablePairs:function(){var _16=[];var key;var _18=this.getVariables();for(key in _18){_16[_16.length]=key+"="+_18[key];}return _16;},getSWFHTML:function(){var _19="";if(navigator.plugins&&navigator.mimeTypes&&navigator.mimeTypes.length){if(this.getAttribute("doExpressInstall")){this.addVariable("MMplayerType","PlugIn");this.setAttribute("swf",this.xiSWFPath);}_19="<embed type=\"application/x-shockwave-flash\" src=\""+this.getAttribute("swf")+"\" width=\""+this.getAttribute("width")+"\" height=\""+this.getAttribute("height")+"\" style=\""+(this.getAttribute("style")||"")+"\"";_19+=" id=\""+this.getAttribute("id")+"\" name=\""+this.getAttribute("id")+"\" ";var _1a=this.getParams();for(var key in _1a){_19+=[key]+"=\""+_1a[key]+"\" ";}var _1c=this.getVariablePairs().join("&");if(_1c.length>0){_19+="flashvars=\""+_1c+"\"";}_19+="/>";}else{if(this.getAttribute("doExpressInstall")){this.addVariable("MMplayerType","ActiveX");this.setAttribute("swf",this.xiSWFPath);}_19="<object id=\""+this.getAttribute("id")+"\" classid=\"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000\" width=\""+this.getAttribute("width")+"\" height=\""+this.getAttribute("height")+"\" style=\""+(this.getAttribute("style")||"")+"\">";_19+="<param name=\"movie\" value=\""+this.getAttribute("swf")+"\" />";var _1d=this.getParams();for(var key in _1d){_19+="<param name=\""+key+"\" value=\""+_1d[key]+"\" />";}var _1f=this.getVariablePairs().join("&");if(_1f.length>0){_19+="<param name=\"flashvars\" value=\""+_1f+"\" />";}_19+="</object>";}return _19;},write:function(_20){if(this.getAttribute("useExpressInstall")){var _21=new deconcept.PlayerVersion([6,0,65]);if(this.installedVer.versionIsValid(_21)&&!this.installedVer.versionIsValid(this.getAttribute("version"))){this.setAttribute("doExpressInstall",true);this.addVariable("MMredirectURL",escape(this.getAttribute("xiRedirectUrl")));document.title=document.title.slice(0,47)+" - Flash Player Installation";this.addVariable("MMdoctitle",document.title);}}if(this.skipDetect||this.getAttribute("doExpressInstall")||this.installedVer.versionIsValid(this.getAttribute("version"))){var n=(typeof _20=="string")?document.getElementById(_20):_20;n.innerHTML=this.getSWFHTML();return true;}else{if(this.getAttribute("redirectUrl")!=""){document.location.replace(this.getAttribute("redirectUrl"));}}return false;}};deconcept.SWFObjectUtil.getPlayerVersion=function(){var _23=new deconcept.PlayerVersion([0,0,0]);if(navigator.plugins&&navigator.mimeTypes.length){var x=navigator.plugins["Shockwave Flash"];if(x&&x.description){_23=new deconcept.PlayerVersion(x.description.replace(/([a-zA-Z]|\s)+/,"").replace(/(\s+r|\s+b[0-9]+)/,".").split("."));}}else{if(navigator.userAgent&&navigator.userAgent.indexOf("Windows CE")>=0){var axo=1;var _26=3;while(axo){try{_26++;axo=new ActiveXObject("ShockwaveFlash.ShockwaveFlash."+_26);_23=new deconcept.PlayerVersion([_26,0,0]);}catch(e){axo=null;}}}else{try{var axo=new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7");}catch(e){try{var axo=new ActiveXObject("ShockwaveFlash.ShockwaveFlash.6");_23=new deconcept.PlayerVersion([6,0,21]);axo.AllowScriptAccess="always";}catch(e){if(_23.major==6){return _23;}}try{axo=new ActiveXObject("ShockwaveFlash.ShockwaveFlash");}catch(e){}}if(axo!=null){_23=new deconcept.PlayerVersion(axo.GetVariable("$version").split(" ")[1].split(","));}}}return _23;};deconcept.PlayerVersion=function(_29){this.major=_29[0]!=null?parseInt(_29[0]):0;this.minor=_29[1]!=null?parseInt(_29[1]):0;this.rev=_29[2]!=null?parseInt(_29[2]):0;};deconcept.PlayerVersion.prototype.versionIsValid=function(fv){if(this.major<fv.major){return false;}if(this.major>fv.major){return true;}if(this.minor<fv.minor){return false;}if(this.minor>fv.minor){return true;}if(this.rev<fv.rev){return false;}return true;};deconcept.util={getRequestParameter:function(_2b){var q=document.location.search||document.location.hash;if(_2b==null){return q;}if(q){var _2d=q.substring(1).split("&");for(var i=0;i<_2d.length;i++){if(_2d[i].substring(0,_2d[i].indexOf("="))==_2b){return _2d[i].substring((_2d[i].indexOf("=")+1));}}}return "";}};deconcept.SWFObjectUtil.cleanupSWFs=function(){var _2f=document.getElementsByTagName("OBJECT");for(var i=_2f.length-1;i>=0;i--){_2f[i].style.display="none";for(var x in _2f[i]){if(typeof _2f[i][x]=="function"){_2f[i][x]=function(){};}}}};if(!document.getElementById&&document.all){document.getElementById=function(id){return document.all[id];};}var getQueryParamValue=deconcept.util.getRequestParameter;var FlashObject=deconcept.SWFObject;var SWFObject=deconcept.SWFObject;
\ No newline at end of file
diff --git a/site/htdocs/swf/flvplayer.swf b/site/htdocs/swf/flvplayer.swf
new file mode 100644 (file)
index 0000000..f70322b
Binary files /dev/null and b/site/htdocs/swf/flvplayer.swf differ
index 8ef40e979e0412bc592424f642db0c75f9e4bc4f..8184dea3dd4db57f7af58b1e96bb74fb670b55d9 100644 (file)
@@ -17,7 +17,7 @@
 <input type="hidden" name="file_id" value="<:efile id:>" />
 <input type="hidden" name="_t" value="file" />
 <input type="hidden" name="save_file_flags" value="1" />
-        <table cellpadding="6" border="0" cellspacing="1">
+        <table class="editform editformsmall">
           <tr> 
             <th align="left">Replacement file:</th>
             <td> 
index aff4ff4894be2c7001c95e9bd9df59550bb85299..d0478982e19480ef47daa3bc5192d0eebb57bb00 100644 (file)
@@ -6,38 +6,9 @@
 <meta http-equiv="Expires" content="Thu, 01 Jan 1970 00:00:00 GMT">
 <link rel="stylesheet" href="/css/style-main.css">
 <:ajax includes:>
+<script src="/js/bse.js"></script>
+<script src="/js/swfobject.js"></script>
 </head>
-<script>
-var bse_image_popup;
-function bse_popup_image (article_id, image_id, width, height, tag_id, image_url) {
-  var url = "/cgi-bin/image.pl?id=" + article_id + "&imid=" + image_id
-       + '&comment=jspopup';
-  var work_width = width + <:cfg popimage extrawidth 0:>;
-  var work_height = height + <:cfg popimage extraheight 0:>;
-  var features = 'width=' + work_width + ',height=' + work_height 
-    + ',resizable=yes';
-
-  // lose the old one if it exists
-  if (<:cfg popimage popmiddle 0:>) {
-    if (bse_image_popup != null && !bse_image_popup.closed) {
-      bse_image_popup.close();
-    }
-    var left = window.screenX + (window.outerWidth - work_width) / 2;
-    var top = window.screenY + (window.outerHeight - work_height) / 2;
-    features = features + ",top=" + top + ",left=" + left;
-  }
-  else if (bse_image_popup != null && !bse_image_popup.closed) {
-    var left = bse_image_popup.screenX;
-    var top = bse_image_popup.screenY;
-    features = features + ",top=" + top + ",left=" + left;
-    bse_image_popup.close();
-  }
-
-  bse_image_popup = window.open(url, 'bse_image', features, 0);
-
-  return 0;
-}
-</script>
 <body bgcolor="#FFFFFF" text="#000000" link="#666666" vlink="#666666" alink="#FF7F00" leftmargin="0" topmargin="0" marginwidth="0" marginheight="0">
 <table width="100%" border="0" cellspacing="0" cellpadding="0" bgcolor="#FF7F00">
   <tr> 
diff --git a/site/templates/inline/flv.tmpl b/site/templates/inline/flv.tmpl
new file mode 100644 (file)
index 0000000..80a0183
--- /dev/null
@@ -0,0 +1,8 @@
+<script type="text/javascript" src="/js/swfobject.js"></script><div id="flashcontent">  This text is replaced by the Flash movie.</div><script type="text/javascript">  var so = new SWFObject("/swf/flvplayer.swf", "flvplayer", "<:meta width:>", "<:meta height:>", "8", "#000000");  so.addParam('allowFullScreen',true);  so.addVariable('aspect_ratio', <:meta width:>/<:meta height:>); so.addVariable('placeholder','/cgi-bin/user.pl/file_metadata/<:file id:>/ph_data');  so.addVariable('video_path','<:src:>'); so.addVariable("wmode", "opaque"); so.write("flashcontent");</script>
+<!--
+so.addVariable('placeholder','placeholder.jpg'); 
+-->
+
+<!-- object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0" type="application/x-shockwave-flash" height="<:meta height:>" width="<:meta width:>"><param name="movie" value="/swf/flvplayer.swf"><param name="video_path" value="/notarealvideo"><param name="quality" value="high"><param name="wmode" value="opaque"><embed src="/swf/flvplayer.swf" video_path="/notarealvideo" quality="high" wmode="opaque" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer" height="<:meta height:>" width="<:meta width:>"></object -->
+
+<!-- div id="flv<:file id:>"><a href="#" onclick="return false;"><img src="/cgi-bin/user.pl?file_metadata=1&amp;file=<:file id:>&amp;name=ph_data" width="<:meta width:>" height="<:meta height:>" /></a></div -->
\ No newline at end of file
diff --git a/site/templates/inline/flv_small.tmpl b/site/templates/inline/flv_small.tmpl
new file mode 100644 (file)
index 0000000..510ab84
--- /dev/null
@@ -0,0 +1 @@
+<div><a class="bse_video_popup" href="#" onclick="return bse_show_video(<:file id:>, <:meta width:>, <:meta height:>, '<:src:>');"><img src="/cgi-bin/user.pl/file_metadata/<:file id:>/ph_editor_data" width="<:meta ph_editor_width:>" height="<:meta ph_editor_height:>" /></a></div>
\ No newline at end of file
index 60536650ba5676e47ffe0ba2a50c5a40fb209f8e..770808f76818f8b489f192224a9fa9775d106795 100644 (file)
@@ -97,7 +97,17 @@ Column name;varchar(80);NO;;
 Column hide_from_list;int(11);NO;0;
 Column storage;varchar(20);NO;local;
 Column src;varchar(255);NO;;
+Column category;varchar(20);NO;;
+Column file_handler;varchar(20);NO;;
 Index PRIMARY;1;[id]
+Table bse_article_file_meta
+Column id;int(11);NO;NULL;auto_increment
+Column file_id;int(11);NO;NULL;
+Column name;varchar(20);NO;NULL;
+Column content_type;varchar(80);NO;text/plain;
+Column value;longblob;NO;NULL;
+Index PRIMARY;1;[id]
+Index file_name;1;[file_id;name]
 Table bse_article_groups
 Column article_id;int(11);NO;NULL;
 Column group_id;int(11);NO;NULL;