From: Călin-Andrei Burloiu Date: Thu, 16 Feb 2012 17:49:51 +0000 (+0200) Subject: upload facility now works in single CIS mode; some simple command-line video moderati... X-Git-Url: http://p2p-next.cs.pub.ro/gitweb/?p=living-lab-site.git;a=commitdiff_plain;h=961611537619595dc7eed2104faafcd0c9cdc57b upload facility now works in single CIS mode; some simple command-line video moderation tools for admins has been implemented --- diff --git a/application/config/p2p-tube.php b/application/config/p2p-tube.php index 20ba114..96491f5 100644 --- a/application/config/p2p-tube.php +++ b/application/config/p2p-tube.php @@ -90,7 +90,12 @@ $config['default_torrent_ext'] = 'tstream'; | IDs must be numeric and must preferably start from 1. | */ -$config['categories'] = array(1 => 'movies', 2 => 'tech-talks', 3 => 'events', 4 => 'karaoke'); +$config['categories'] = array( + 1 => 'movies', + 2 => 'tech-talks', + 3 => 'events', + 4 => 'karaoke' +); /* |-------------------------------------------------------------------------- @@ -156,3 +161,14 @@ $config['available_languages_list'] = array( | */ $config['noreply_email'] = 'no-reply@p2p-next.cs.pub.ro'; + +/* +|-------------------------------------------------------------------------- +| Require Moderation +|-------------------------------------------------------------------------- +| +| Set whether or not a new uploaded video must be aproved by an +| administrator. +| +*/ +$config['require_moderation'] = TRUE; \ No newline at end of file diff --git a/application/controllers/admin_cli.php b/application/controllers/admin_cli.php index 604c07f..f0ef13b 100644 --- a/application/controllers/admin_cli.php +++ b/application/controllers/admin_cli.php @@ -19,7 +19,8 @@ class Admin_cli extends CI_Controller { } public function index() - { + { + echo "P2P-Tube".PHP_EOL; } /** @@ -39,6 +40,38 @@ class Admin_cli extends CI_Controller { else echo "No users were deleted.".PHP_EOL; } + + public function print_unactivated_videos() + { + $this->load->model('videos_model'); + + $videos = $this->videos_model->get_unactivated_videos(); + + if ($videos) + { + foreach($videos as $video) + { + echo $video['video_id']. ' ' + . site_url("watch/". $video['video_id'] + . "/". $video['name']). PHP_EOL; + } + } + } + + public function activate_video($video_id = NULL) + { + $this->load->model('videos_model'); + $video_id = intval($video_id); + + if ($video_id == 0) + { + echo 'usage: admin_cli activate_video $video_id'.PHP_EOL; + return; + } + + if (!$this->videos_model->activate_video(intval($video_id))) + echo 'error: an error occured while activating the video'.PHP_EOL; + } } /* End of file admin_cli.php */ diff --git a/application/controllers/catalog.php b/application/controllers/catalog.php index 31bdc51..d9aa933 100644 --- a/application/controllers/catalog.php +++ b/application/controllers/catalog.php @@ -17,6 +17,18 @@ class Catalog extends CI_Controller { public function index() { + $user_id = $this->session->userdata('user_id'); + if ($user_id) + { + if (intval($user_id) & USER_ROLE_ADMIN) + $allow_unactivated = TRUE; + else + $allow_unactivated = FALSE; + } + else + $allow_unactivated = FALSE; + + // ** // ** LOADING MODEL // ** @@ -26,7 +38,8 @@ class Catalog extends CI_Controller { { // Videos $vs_data['videos'] = $this->videos_model->get_videos_summary( - $id, NULL, 0, $this->config->item('videos_per_row')); + $id, NULL, 0, $this->config->item('videos_per_row'), + $allow_unactivated); // Category $vs_data['category_name'] = $name; @@ -78,6 +91,17 @@ class Catalog extends CI_Controller { public function category($category_name, $ordering = 'hottest', $offset = 0) { + $user_id = $this->session->userdata('user_id'); + if ($user_id) + { + if (intval($user_id) & USER_ROLE_ADMIN) + $allow_unactivated = TRUE; + else + $allow_unactivated = FALSE; + } + else + $allow_unactivated = FALSE; + // ** // ** LOADING MODEL // ** @@ -88,7 +112,8 @@ class Catalog extends CI_Controller { $this->load->model('videos_model'); $vs_data['videos'] = $this->videos_model->get_videos_summary( $category_data['category_id'], NULL, intval($offset), - $this->config->item('videos_per_page'), $ordering); + $this->config->item('videos_per_page'), $ordering, + $allow_unactivated); $vs_data['ordering'] = $ordering; @@ -98,7 +123,7 @@ class Catalog extends CI_Controller { "catalog/category/$category_name/$ordering/"); $pg_config['uri_segment'] = 5; $pg_config['total_rows'] = $this->videos_model->get_videos_count( - $category_data['category_id']); + $category_data['category_id'], NULL, $allow_unactivated); $pg_config['per_page'] = $this->config->item('videos_per_page'); $this->pagination->initialize($pg_config); $vs_data['pagination'] = $this->pagination->create_links(); diff --git a/application/controllers/user.php b/application/controllers/user.php index fad3ddf..169d5d2 100644 --- a/application/controllers/user.php +++ b/application/controllers/user.php @@ -26,7 +26,7 @@ class User extends CI_Controller { public function test($user_id = 1) { - echo extension_loaded('gd') ? 'gd' : 'nu'; +// echo extension_loaded('gd') ? 'gd' : 'nu'; } // DEBUG @@ -179,6 +179,7 @@ class User extends CI_Controller { $this->session->unset_userdata('user_id'); $this->session->unset_userdata('username'); $this->session->unset_userdata('auth_src'); + $this->session->unset_userdata('roles'); $this->session->unset_userdata('time_zone'); header('Location: '. site_url(urldecode_segments($redirect))); @@ -348,6 +349,17 @@ class User extends CI_Controller { { // TODO handle user not found + $user_id = $this->session->userdata('user_id'); + if ($user_id) + { + if (intval($user_id) & USER_ROLE_ADMIN) + $allow_unactivated = TRUE; + else + $allow_unactivated = FALSE; + } + else + $allow_unactivated = FALSE; + $this->load->config('localization'); $this->load->helper('date'); $this->lang->load('date'); @@ -370,15 +382,16 @@ class User extends CI_Controller { // User's videos $this->load->model('videos_model'); $vs_data['videos'] = $this->videos_model->get_videos_summary( - NULL, $username, intval($videos_offset), - $this->config->item('videos_per_page')); + NULL, $username, intval($videos_offset), + $this->config->item('videos_per_page'), 'hottest', + $allow_unactivated); // Pagination $this->load->library('pagination'); $pg_config['base_url'] = site_url("user/profile/$username/"); $pg_config['uri_segment'] = 4; $pg_config['total_rows'] = $this->videos_model->get_videos_count( - NULL, $username); + NULL, $username, $allow_unactivated); $pg_config['per_page'] = $this->config->item('videos_per_page'); $this->pagination->initialize($pg_config); $vs_data['pagination'] = $this->pagination->create_links(); @@ -687,6 +700,7 @@ class User extends CI_Controller { 'user_id'=> $user['id'], 'username'=> $user['username'], 'auth_src'=> $user['auth_src'], + 'roles'=> $user['roles'], 'time_zone'=> $user['time_zone'] )); $this->import = (isset($user['import']) ? $user['import'] : FALSE); diff --git a/application/controllers/video.php b/application/controllers/video.php index 6a5c19e..283851d 100644 --- a/application/controllers/video.php +++ b/application/controllers/video.php @@ -10,6 +10,7 @@ class Video extends CI_Controller { protected $uploaded_file; + protected $av_info; public function __construct() { @@ -23,12 +24,14 @@ class Video extends CI_Controller { //phpinfo(); } - public function test($fn) + public function test() { - $fn = "data/upload/$fn"; - $this->load->helper('video'); + $this->load->model('videos_model'); + + $videos = $this->videos_model->get_videos_summary(1, NULL, 0, 10, + 'alphabetically', TRUE); - var_dump(get_av_info($fn)); + var_dump($videos); } /** @@ -44,17 +47,67 @@ class Video extends CI_Controller { // ** // ** LOADING MODEL // ** - // Retrieve video information. $this->load->model('videos_model'); - $this->videos_model->inc_views($id); + + $data['user_id'] = $this->session->userdata('user_id'); + if ($data['user_id'] === FALSE) + $data['user_id'] = ''; + else + $data['user_id'] = intval($data['user_id']); + $user_roles = intval($this->session->userdata('roles')); +// echo USER_ROLE_ADMIN . ' / '; +// var_dump($user_roles); +// var_dump($user_roles | USER_ROLE_ADMIN); +// die(); + + // Retrieve video information. $data['video'] = $this->videos_model->get_video($id, $name); + if ($data['video'] === FALSE) + { + $this->load->helper('message'); + show_error_msg_page($this, + $this->lang->line('video_msg_no_video')); + return; + } + + // Video is being processed by CIS. + if ($data['video']['activation_code'] + && !$data['video']['content_ingested']) + { + $this->load->helper('message'); + show_error_msg_page($this, + $this->lang->line('video_msg_video_not_ready')); + return; + } + + // Unlogged in user can't see unactivated videos. + if (empty($data['user_id'])) + $allow_unactivated = FALSE; + else + { + if (($user_roles & USER_ROLE_ADMIN) == 0 + && $data['user_id'] != $data['video']['user_id']) + $allow_unactivated = FALSE; + else + $allow_unactivated = TRUE; + } + + // Video is not activated; can be seen by owner and admin. + if ($data['video']['activation_code'] && !$allow_unactivated) + { + $this->load->helper('message'); + show_error_msg_page($this, + $this->lang->line('video_msg_video_unactivated')); + return; + } + $categories = $this->config->item('categories'); $data['video']['category_name'] = $categories[ $data['video']['category_id'] ]; $data['plugin_type'] = ($plugin === NULL ? 'auto' : $plugin); - $data['user_id'] = $this->session->userdata('user_id'); - if ($data['user_id'] === FALSE) - $data['user_id'] = ''; + + // Increment the number of views for the video. + $this->videos_model->inc_views($id); // Display page. $params = array( 'title' => $data['video']['title'] . ' – ' @@ -161,7 +214,8 @@ class Video extends CI_Controller { $this->input->post('video-description'), $this->input->post('video-tags'), $this->av_info['duration'], - $prepared_formats['db_formats'], $category_id, $user_id); + $prepared_formats['db_formats'], $category_id, $user_id, + $this->uploaded_file); // Send a content ingestion request to // CIS (Content Ingestion Server). @@ -176,6 +230,18 @@ class Video extends CI_Controller { } } + public function cis_completion($activation_code) + { + $this->load->model('videos_model'); + + if ($this->config->item('require_moderation')) + $this->videos_model->set_content_ingested($activation_code); + else + $this->videos_model->activate_video($activation_code); + +// log_message('info', "cis_completion $activation_code"); + } + /** * Increments (dis)likes count for video with the specified id and returns to * the client as plain text the number if likes. diff --git a/application/exceptions/Send_email_exception.php b/application/exceptions/Send_email_exception.php index 17b3009..33856cd 100644 --- a/application/exceptions/Send_email_exception.php +++ b/application/exceptions/Send_email_exception.php @@ -2,5 +2,4 @@ class Send_email_exception extends Exception { - } \ No newline at end of file diff --git a/application/language/english/video_lang.php b/application/language/english/video_lang.php index 92b93df..77c0225 100644 --- a/application/language/english/video_lang.php +++ b/application/language/english/video_lang.php @@ -23,7 +23,11 @@ $lang['video_category'] = 'Category'; $lang['video_tags'] = 'Tags'; $lang['video_tags_hint'] = 'comma separated tags'; $lang['video_submit_upload'] = 'Upload'; + $lang['video_msg_video_uploaded'] = 'Your video has been uploaded and is now being processed.'; +$lang['video_msg_no_video'] = 'This video does not exist.'; +$lang['video_msg_video_not_ready'] = 'This video is being processed and is not available yet.'; +$lang['video_msg_video_unactivated'] = 'This video requires moderation and must be approved by an administrator.'; /* End of file video_lang.php */ diff --git a/application/models/videos_model.php b/application/models/videos_model.php index f927561..87b0d3b 100644 --- a/application/models/videos_model.php +++ b/application/models/videos_model.php @@ -26,11 +26,12 @@ class Videos_model extends CI_Model { * * @param int $category_id DB category ID; pass NULL for all * categories - * @param mixed $user an user_id (as int) or an username + * @param mixed $user an user_id (as int) or an username * (as string); pass NULL for all users - * @param int $offset - * @param int $count - * @param string $ordering control videos ording by these + * @param int $offset + * @param int $count number of videos to retrieve; if set to TRUE this + * method will retrieve the number of videos that satisfy condition + * @param string $ordering control videos ording by these * possibilities: * - * @return array a list of videos, each one being an assoc array with: + * @param bool $unactivated whether to retrieve or not ingested unactivated + * videos; typically only administrators should see this kind of assets + * @return array a list of videos, each one being an assoc array with: * */ public function get_videos_summary($category_id, $user, $offset, $count, - $ordering = 'hottest') + $ordering = 'hottest', $unactivated = FALSE) { $this->load->helper('text'); - // Ordering - switch ($ordering) + $order_statement = ""; + if ($count !== TRUE) { - case 'hottest': - $order_statement = "ORDER BY date DESC, score DESC, RAND()"; - break; - case 'newest': - $order_statement = "ORDER BY date DESC"; - break; - case 'alphabetically': - $order_statement = "ORDER BY title"; - break; - - default: - $order_statement = ""; + // Ordering + switch ($ordering) + { + case 'hottest': + $order_statement = "ORDER BY date DESC, score DESC, RAND()"; + break; + case 'newest': + $order_statement = "ORDER BY date DESC"; + break; + case 'alphabetically': + $order_statement = "ORDER BY title"; + break; + + default: + $order_statement = ""; + } } + // Show unactivated videos. + $cond_unactivated = ($unactivated + ? '(a.activation_code IS NULL OR a.activation_code IS NOT NULL + AND a.content_ingested = 1)' + : 'a.activation_code IS NULL'); + // Category filtering if ($category_id === NULL) $cond_category = "1"; @@ -90,17 +103,31 @@ class Videos_model extends CI_Model { $cond_user = "u.username = '$user'"; } - $query = $this->db->query( - "SELECT v.id, name, title, duration, user_id, u.username, views, + if ($count === TRUE) + $fields = "COUNT(*) count"; + else + $fields = "v.id, name, title, duration, user_id, u.username, views, thumbs_count, default_thumb, - (views + likes - dislikes) AS score - FROM `videos` v, `users` u + (views + likes - dislikes) AS score, + a.activation_code, a.content_ingested"; + + $query = $this->db->query( + "SELECT $fields + FROM `videos` v + LEFT JOIN `videos_unactivated` a ON (id = a.video_id), + `users` u WHERE v.user_id = u.id AND $cond_category AND $cond_user + AND $cond_unactivated $order_statement LIMIT $offset, $count"); if ($query->num_rows() > 0) + { + if ($count === TRUE) + return $query->row()->count; + $videos = $query->result_array(); + } else return array(); @@ -128,36 +155,14 @@ class Videos_model extends CI_Model { * NULL parameters count videos from all categories and / or all users. * * @param int $category_id - * @param mixed $user an user_id (as int) or an username (as string) - * @return int number of videos or FALSE if an error occured + * @param mixed $user an user_id (as int) or an username (as string) + * @return int number of videos or FALSE if an error occured */ - public function get_videos_count($category_id = NULL, $user = NULL) + public function get_videos_count($category_id = NULL, $user = NULL, + $unactivated = FALSE) { - if ($category_id === NULL) - $cond_category = "1"; - else - $cond_category = "category_id = $category_id"; - - if ($user === NULL) - $cond_user = "1"; - else - { - if (is_int($user)) - $cond_user = "v.user_id = $user"; - else if(is_string($user)) - $cond_user = "u.username = '$user'"; - } - - $query = $this->db->query( - "SELECT COUNT(*) count - FROM `videos` v, `users` u - WHERE v.user_id = u.id AND $cond_category AND $cond_user"); - - if ($query->num_rows() > 0) - return $query->row()->count; - - // Error - return FALSE; + return $this->get_videos_summary($category_id, $user, 0, TRUE, NULL, + $unactivated); } /** @@ -191,9 +196,12 @@ class Videos_model extends CI_Model { $this->load->helper('video'); $this->load->helper('text'); - $query = $this->db->query("SELECT v.*, u.username - FROM `videos` v, `users` u - WHERE v.user_id = u.id AND v.id = $id"); + $query = $this->db->query("SELECT v.*, u.username, + a.activation_code, a.content_ingested + FROM `videos` v + LEFT JOIN `videos_unactivated` a ON (v.id = a.video_id), + `users` u + WHERE v.user_id = u.id AND v.id = $id"); $video = array(); if ($query->num_rows() > 0) @@ -204,8 +212,7 @@ class Videos_model extends CI_Model { } else { - $video['err'] = 'INVALID_ID'; - return $video; + return FALSE; } // Convert JSON encoded string to arrays. @@ -260,10 +267,11 @@ class Videos_model extends CI_Model { * column from `videos` table * @param int $category_id * @param int $user_id + * @param string $uploaded_file the raw video file uploaded by the user * @return mixed returns an activation code on success or FALSE otherwise */ public function add_video($name, $title, $description, $tags, $duration, - $formats, $category_id, $user_id) + $formats, $category_id, $user_id, $uploaded_file) { // Tags. $json_tags = array(); @@ -299,12 +307,69 @@ class Videos_model extends CI_Model { $activation_code = Videos_model::gen_activation_code(); $query = $this->db->query("INSERT INTO `videos_unactivated` - (video_id, activation_code) - VALUES ($video_id, '$activation_code')"); + (video_id, activation_code, uploaded_file) + VALUES ($video_id, '$activation_code', '$uploaded_file')"); return $activation_code; } + public function set_content_ingested($activation_code) + { + return $this->db->query("UPDATE `videos_unactivated` + SET content_ingested = 1 + WHERE activation_code = '$activation_code'"); + } + + /** + * Activates a video by deleting its entry from `videos_unactivated`. + * + * @param mixed $code_or_id use type string for activation_code or type + * int for video_id + * @return boolean TRUE on success, FALSE otherwise + */ + public function activate_video($code_or_id) + { + if (is_string($code_or_id)) + $query = $this->db->query("SELECT uploaded_file from `videos_unactivated` + WHERE activation_code = '$code_or_id'"); + else if (is_int($code_or_id)) + $query = $this->db->query("SELECT uploaded_file from `videos_unactivated` + WHERE video_id = '$code_or_id'"); + else + return FALSE; + + if ($query->num_rows() > 0) + $uploaded_file = $query->row()->uploaded_file; + else + return FALSE; + + if (is_string($code_or_id)) + $query = $this->db->query("DELETE FROM `videos_unactivated` + WHERE activation_code = '$code_or_id'"); + else if (is_int($code_or_id)) + $query = $this->db->query("DELETE FROM `videos_unactivated` + WHERE video_id = '$code_or_id'"); + else + return FALSE; + + if (!$query) + return $query; + + return unlink("data/upload/$uploaded_file"); + } + + public function get_unactivated_videos() + { + $query = $this->db->query("SELECT a.video_id, v.name, a.content_ingested + FROM `videos_unactivated` a, `videos` v + WHERE a.video_id = v.id AND a.content_ingested = 1"); + + if ($query->num_rows() > 0) + return $query->result_array(); + else + return FALSE; + } + /** * Retrieves comments for a video. * diff --git a/cis/api/base.py b/cis/api/base.py index 11c922a..9e4ea5e 100644 --- a/cis/api/base.py +++ b/cis/api/base.py @@ -253,9 +253,9 @@ class BaseThumbExtractor: output_file_name = os.path.join(self.dest_path, self.name) \ + '_t' + ("%02d" % index) + '.jpg' - if os.path.exists(output_file_name): - raise cis_exceptions.FileAlreadyExistsException( \ - 'file "%s" already exists' % output_file_name) + #if os.path.exists(output_file_name): + #raise cis_exceptions.FileAlreadyExistsException( \ + #'file "%s" already exists' % output_file_name) return output_file_name diff --git a/cis/cis.py b/cis/cis.py index 3ae47cf..d096a9e 100755 --- a/cis/cis.py +++ b/cis/cis.py @@ -151,11 +151,16 @@ class CIWorker(threading.Thread): for f in files: os.unlink(os.path.join(path, f)) - def notify_completion(self): + def notify_completion(self, code): logger.log_msg('#%s: notifying web server about the job completion...'\ % self.job_id) - f = urllib.urlopen(config.WS_COMPLETION) + if config.WS_COMPLETION[len(config.WS_COMPLETION) - 1] == '/': + url = config.WS_COMPLETION + code + else: + url = config.WS_COMPLETION + '/' + code + + f = urllib.urlopen(url) f.read() def run(self): @@ -232,7 +237,7 @@ class CIWorker(threading.Thread): # * NOTIFY WEB SERVER ABOUT CONTENT INGESTION COMPLETION # TODO in the future web server should also be notified about errors try: - self.notify_completion() + self.notify_completion(job['code']) except Exception as e: logger.log_msg( '#%s: error while notifying web server about the job completion: %s' \ @@ -246,6 +251,8 @@ class CIWorker(threading.Thread): # * JOB FINISHED Server.queue.task_done() Server.load -= job['weight'] + logger.log_msg('#%s: finished' \ + % job['code'], logger.LOG_LEVEL_INFO) class Server: diff --git a/cis/config.py b/cis/config.py index ec35ca0..adb5ed0 100644 --- a/cis/config.py +++ b/cis/config.py @@ -33,8 +33,8 @@ WS_THUMBS_PATH = 'devel/data/thumbs' BT_TRACKER = 'http://p2p-next-10.grid.pub.ro:6969/announce' # Web server's URL for content ingestion completion. P2P-Tube uses # http:///video/cis_completion . -WS_COMPLETION = 'http://p2p-next-03.grid.pub.ro:8081/cis_completion' -#WS_COMPLETION = 'http://koala.cs.pub.ro/video/cis_completion' +#WS_COMPLETION = 'http://p2p-next-03.grid.pub.ro:8081/cis_completion' +WS_COMPLETION = 'http://p2p-next.cs.pub.ro/devel/video/cis_completion' # === CIS PATHS === diff --git a/cis/extract_summary_thumbs.py b/cis/extract_summary_thumbs.py new file mode 100755 index 0000000..f0a569c --- /dev/null +++ b/cis/extract_summary_thumbs.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python + +import sys + +from api import ffmpeg + +if len(sys.argv) != 4: + sys.stderr.write('usage: ' + sys.argv[0] + ' input_video_file dest_path thumbs_count\n') + exit(1) + +if __name__ == '__main__': + fn = sys.argv[1] + thumbs_count = int(sys.argv[3]) + + video_name = fn[0:fn.rindex('_')] + video_name = video_name[video_name.rindex('/')+1:] + + fte = ffmpeg.FFmpegThumbExtractor(input_file = fn, name = video_name) + fte.dest_path = sys.argv[2] + + fte.extract_summary_thumbs(thumbs_count) \ No newline at end of file