class = 'annodex'; $class->max_size = 4*(1 << 20); $class->mime_ext = Array( 'application/x-annodex'=>'anx', 'application/ogg'=>'ogg', 'application/x-cmml'=>'cmml', ); $class->create = '_class_annodex_create'; $class->update = '_class_annodex_update'; $class->form = '_class_annodex_form'; $class->destroy = '_class_annodex_destroy'; $class->access = 'create acidfree annodexs'; $class->resize = '_class_annodex_resize'; return $class; } /* Create a new annodex node */ function _class_annodex_create(&$node, &$file) { /* Maybe create an extra node? One node for direct viewing of the * annodex, one for editing, and maybe Cortado embedding the annodex. * The normal acidfree policy of having the node for embedding, and * then linking straight to the file for fullscreen wont work, as the * annodex is dynamically generated. */ $node->large = acidfree_add_file($node, $file->filepath, "{$node->nid}_large.ogg", $node->mime, TRUE); $node->small = $node->large; // For now there's no distinction between // high and low resolution of the video. } //function _class_annodex_resize(&$node) { // return( _class_video_resize($node) ); //} //function _class_annodex_update(&$node) { // return( _class_video_update($node) ); //} /* Define the HTML forms shown to users when creating an annodex */ //FIXME Maybe allow CMML upload? function _class_annodex_form($op, &$node, $name_prefix='') { switch ($op) { case 'new': $nailer = variable_get('acidfree_annodex_thumbnailer', 'none'); $output = ''; switch ($nailer) { case 'none': //$output .= form_textfield(t('Annodex width'), $name_prefix.'annodexx', 320, 10, 10); //$output .= form_textfield(t('Annodex height'), $name_prefix.'annodexy', 240, 10, 10); break; case 'user': $output .= form_file(t('Annodex thumbnail'), $name_prefix.'userthumb', 50, NULL, true); break; case 'mplayer': // possibly allow them to choose frame number? too complex? // too much decoding time -- no option to tell where to start decoding... break; } break; case 'preview': $output .= form_hidden('thumb', is_object($node->thumb)?$node->thumb->fid:$node->thumb); break; case 'update': $output .= form_hidden('thumb', is_object($node->thumb)?$node->thumb->fid:$node->thumb); break; } return $output; } function _class_annodex_destroy(&$node) { } /* function _class_annodex_mplayer_options() { $name = 'acidfree_path_to_mplayer'; $mplayer_path = variable_get($name, '/usr/bin/mplayer'); if (!is_executable($mplayer_path)) form_set_error($name, t('Path to mplayer is not correct — Annodex thumbnails will not be generated')); return form_textfield(t('Path to mplayer'), $name, $mplayer_path, 64, 64); } */ /* function _class_annodex_manipulation_options() { $options = array( 'none'=>t('No thumbnail — use Annodex image'), 'user'=>t('User uploaded thumbnails'), 'mplayer'=>t('Use mplayer to create thumbnails') ); $name = 'acidfree_annodex_thumbnailer'; $value = variable_get($name, 'none'); return form_radios(t('Annodex thumbnail options'), $name, $value, $options). acidfree_call("_class_annodex_{$value}_options"); } */ /* Display an annodex page */ function theme_acidfree_print_full_annodex(&$node) { if (!$node->large) return ''; if ($node->page) $output = acidfree_pager_creator($node); $output .= theme_annodex_control( &$node ); return $output; } function theme_acidfree_print_thumb_annodex(&$node, $parent=null) { } function _annodex_view_help($text, $link, $alt) { $output = "

".t('Problems viewing Annodexes?')."
\n"; $output .= l($text, $link, Array('title'=>$alt)); $output .= "

\n"; return $output; } //FIXME write body of function. Cortado applet based? /* Display the clip table, links to actual annodex etc. */ function theme_annodex_control(&$node) { global $user; $arg_name = "media"; /* Flag for determining whether to give option of editing clips */ $editing = can_edit( $node ); $mode = mode($arg_name); if( $mode=="add" && $editing ) { try { insert_clip( $_POST, $node ); } catch( Exception $e ) { drupal_set_message( $e->getMessage(), 'error' ); } $mode = "view"; } if( $mode=="delete" && $editing ) { delete_clip($node, $_GET['delete']); $mode = "view"; } switch( $mode ) { case "edit": $output .= view_header($node, $arg_name); $result = db_query("SELECT * FROM {acidfree_cmml_clips} WHERE `nid`={$node->nid} AND `id`=\"{$_GET['clip']}\""); $row = db_fetch_array( $result ); $output .= clip_table($node, $arg_name, $row['id'], $editing); $output .= "

"; $output .= clip_update_form( $node, $row, "Update clip" ); $output .= view_footer($node, $arg_name); $output .= $node->body; break; case "view": $output .= view_header($node, $arg_name); $output .= clip_table($node, $arg_name, NULL, $editing); $output .= "

"; if( $editing ) { $output .= clip_update_form( $node, null, "Add new clip" ); } $output .= view_footer($node, $arg_name); $output .= $node->body; break; case "media": $media = select_media( $arg_name ); output_media( $media, $node ); break; } return $output; } /* Returns false if the clip info isn't valid, otherwise inserts the clip * information into the database. */ function insert_clip( $attributes, $node ) { foreach( $attributes as $name=>$attribute ) { $attributes[$name] = db_escape_string( $attribute ); } /* Check clip info is valid */ if( $attributes['id']=="" || $attributes['start_seconds']=="" ) { throw new Exception("ID and start time are compulsory fields"); } if( strlen($attributes['id']) > 32 ) { throw new Exception("ID must be no longer than 32 characters"); } if( count( explode( " ", $attributes['id'] ) ) != 1 ) { throw new Exception("ID must be a single word without whitespace"); } if( strlen($attributes['desc']) > 512 ) { throw new Exception("Description must be no longer than 512 characters"); } if( $attributes['id'] ) { $start = 60*60*$attributes['start_hours'] + 60*$attributes['start_minutes'] + $attributes['start_seconds']; /* Check whether we need to create a new clip or just update an * old one. */ $occurances = db_result(db_query("SELECT COUNT(`id`) FROM {acidfree_cmml_clips} WHERE `id` = '{$attributes['id']}';")); if( $occurances == 0 ) { //FIXME filemanager has problems with this. //$img = add_clip_png( $node, $attributes['id'], $start ); $result = db_query("INSERT INTO {acidfree_cmml_clips} (`nid`, `a_href`, `a_text`, `img`, `desc`, `start`, `end`, `id`, `track`) VALUES('{$node->nid}', '{$attributes['a_href']}', '{$attributes['a_text']}', '$img', '{$attributes['desc']}', $start, ".(float)$attributes['end'].",'{$attributes['id']}', '{$attributes['track']}');"); } else { $result = db_query("UPDATE {acidfree_cmml_clips} SET `nid` = '{$node->nid}', `a_href` = '{$attributes['a_href']}', `desc` = '{$attributes['desc']}', `start` = '$start', `id` = '{$attributes['id']}' WHERE `id` = '{$attributes['id']}';"); } } return; } /* Table displaying clips of the annodex */ function clip_table( $node, $arg_name, $clip=NULL, $editable=FALSE ) { if( $clip ) { $and = "AND id='$clip'"; } $result = db_query("SELECT * FROM {acidfree_cmml_clips} WHERE nid={$node->nid} $and ORDER BY `start`;"); /* Output clip information in a themed table. */ $table_rows = array(); //$attributes = array( 'width'=>"70%" ); $header = array(); $header[] = array( 'data'=>'Clip ID', 'width'=>"10%"); $header[] = array( 'data'=>'Start', 'width'=>"10%"); $header[] = array( 'data'=>'Image', 'width'=>"10%"); $header[] = array( 'data'=>'Description', 'width'=>"60%"); $header[] = array( 'data'=>'Options', 'width'=>"10%"); while( $row = db_fetch_object($result) ) { $cells = array(); $cells[] = array('data'=>l($row->id, "node/{$node->nid}",NULL,"$arg_name=annodex&id={$row->id}")."
"); $cells[] = array('data'=>$row->start); if( $row->img ) { $cells[] = array('data'=>"nid}&$arg_name=annodex&id={$row->id}\">img}\">"); } else { $cells[] = array('data'=>NULL); } if( $row->desc ) { $cells[] = array('data'=>wordwrap($row->desc,40, '
', TRUE)); } else { $cells[] = array('data'=>NULL); } $options = ""; $options .= l("Play", "node/{$node->nid}",NULL,"media=annodex&id={$row->id}"); /* Only show edit and delete links to users with the permissions to use * them. */ if( $editable ) { $options .= "
"; $options .= l("Edit", "node/{$node->nid}",NULL,"clip={$row->id}"); $options .= "
"; $options .= l("Delete", "node/{$node->nid}",NULL,"delete={$row->id}"); } $cells[] = array('data'=>$options); /* Add cells */ $table_rows[] = $cells; } /* Add table if there's anything in it */ if( count($table_rows) != 0 ) { $output .= theme_table( $header, $table_rows, $attributes ); } else { $output .= "

No clips available

"; } return $output; } /* Use mplayer to rip a png to go with the clip */ function add_clip_png( $node, $id, $time) { $mplayer = variable_get('acidfree_path_to_mplayer', '/usr/bin/mplayer'); $tmpdir = acidfree_mktmpdir('mplayer'); $path = realpath(_acidfree_get_large_path( $node, true )); $command = "$mplayer -vo png:z=6 -vf scale -zoom -xy 120 -ao null -ss $time -frames 1 $path"; chdir($tmpdir); $mplayeroutput = shell_exec($command); if ($oldcwd) { chdir($oldcwd); } $mplayer_out = $tmpdir.DIRECTORY_SEPARATOR."00000002.png"; if (!file_exists($mplayer_out)) { drupal_set_message("file $mplayer_out does not exist", 'error'); } else if(!is_readable($mplayer_out)) { drupal_set_message("file $mplayer_out is not readable", 'error'); } else { $filename = "{$node->nid}_$id.png"; $png = acidfree_add_file($node, $mplayer_out, $filename, "image/png", true); } //FIXME This will only work if an absolute path is given for //filemanager. This is a hack to get around the mess that giving //absolute path to filemanager wreaks with the URLs. if( $png->working ) { $area = "working"; } else if ( $png->active ) { $area = "active"; } if( $path->dir ) { $dir = "1"; } else { $dir = "0"; } $path = "files/$area/$dir/{$png->filename}"; $bad_url = url($path,null,null,true); $base_url = explode( "/?q", $bad_url ); $path = "{$base_url[0]}/$path"; return $path; } /* General information about the annodex */ function headers( $node, $arg_name ) { $media_file = realpath( _acidfree_get_large_path( $node, true )); $anx = new AnnodexWriter(); $anx->import( $media_file, null, null, 0, -1 ); $header = array(); $header[] = array( 'data'=>'Title'); $header[] = array( 'data'=>'Duration'); $row = array(array('data'=>$node->title), array('data'=>$anx->get_duration())); $output .="
"; $output .=""; $output .= theme_table( $header, array($row) ); $output .=""; $output .="

"; $output .= l("Play Annodex", "node/{$node->nid}",NULL,"$arg_name=annodex")." "; $output .="

"; $anx->close(); return $output; } /* Direct links to cmml, ogg and annodex */ function footer($node, $arg_name) { $output .= "

Download... "; $output .= l("Annodex", "node/{$node->nid}",NULL,"$arg_name=annodex")." "; $output .= "|"; $output .= l("Ogg Theora", "node/{$node->nid}",NULL,"$arg_name=theora")." "; $output .= "|"; $output .= l("CMML", "node/{$node->nid}",NULL,"$arg_name=cmml")." "; $output .= "

"; $output .= "
"; return $output; } /* Form for creating or editing clips. */ function clip_update_form( $node, $attributes, $button_text ) { $header = array(); $header[] = array( 'data' => null ); $header[] = array( 'data' => null ); $table_rows = array(); $cells = array(); //if( $attributes['id'] ) { // $cells[] = "{$attributes['id']}".form_hidden("id",$attributes['id']); //} else { $cells[] = array('data'=>''); //} $cells[] = array('data'=>'ID'); $table_rows[] = $cells; $cells = array(); $cells[] = array('data'=>''); $cells[] = array('data'=>'Description'); $table_rows[] = $cells; $cells = array(); $cells[] = array('data'=>' '); $cells[] = array('data'=>'Start time'); $table_rows[] = $cells; $cells = array(); $cells[] = array('data'=>''); $cells[] = array('data'=>null); $table_rows[] = $cells; $output .= '
'; $output .= theme_table( $header, $table_rows ); $output .= '
'; return $output; } /* Work out which raw media to serve */ function select_media( $arg_name ) { if( isset($_GET[$arg_name])) { return $_GET[$arg_name]; } else { return false; } } /* Output raw cmml, annodex or ogg rather than the normal page. */ function output_media( $media, $node ) { switch ($media) { case "cmml": output_cmml($node); die; break; case "annodex": /* We need to call output() rather than * output_annodex() because of apps like the * firefox extension that want both of them on * the one link. */ output($node); die; break; case "theora": output_ogg($node); die; break; default: output($node); die; break; } } /* Work out what mode the page is being called in. */ function mode( $arg_name ) { if( $_GET['delete'] ) { return "delete"; } if( $_POST['id'] || $_POST['desc'] || $_POST['start_seconds'] || $_POST['start_minutes'] || $_POST['start_hours'] ) return "add"; if( $_GET['clip'] ) { return "edit"; } if( select_media( $arg_name ) ) { return "media"; } return "view"; } /* Check if the current user has edit permissions. */ function can_edit( $node ) { if (acidfree_access('update',$node)) { return TRUE; } else { return FALSE; } } function view_header($node, $arg_name) { return headers($node, $arg_name); } function view_footer($node, $arg_name) { return footer($node, $arg_name); } /* Remove a clip from the database */ function delete_clip($node, $id) { $query = "DELETE FROM {acidfree_cmml_clips} WHERE `nid`={$node->nid} AND `id`='$id';"; db_query( $query ); }