uploading works, but AV info is not automatically detected and video activation featu...
authorCălin-Andrei Burloiu <calin.burloiu@gmail.com>
Tue, 14 Feb 2012 14:58:37 +0000 (16:58 +0200)
committerCălin-Andrei Burloiu <calin.burloiu@gmail.com>
Tue, 14 Feb 2012 14:58:37 +0000 (16:58 +0200)
14 files changed:
application/config/content_ingestion.php [new file with mode: 0644]
application/config/p2p-tube.php
application/controllers/video.php
application/helpers/video_helper.php
application/language/english/video_lang.php
application/models/videos_model.php
application/views/header.php
application/views/video/upload_view.php
cis/api/base.py
cis/api/ffmpeg.py
cis/config.py
cis/job_test.json
js/jquery.ui.nsvideo.js
scripts/sync_cis.sh [new file with mode: 0755]

diff --git a/application/config/content_ingestion.php b/application/config/content_ingestion.php
new file mode 100644 (file)
index 0000000..a21310f
--- /dev/null
@@ -0,0 +1,98 @@
+<?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed');
+
+/**
+ * Content ingestion configuration; CIS interoperability configuration. 
+ * 
+ * Content ingestion is provided by a CIS (Content Ingestion Server).
+ * Communication is provided through web services directly or in a transparent
+ * manner through a CIS-LB (CIS - Load Balancer).
+ */
+
+/*
+|--------------------------------------------------------------------------
+| CIS / CIS-LB URL
+|--------------------------------------------------------------------------
+|
+| CIS web service URL (ended with '/'). For example:
+| 
+|      http://cis.org:31500/
+|
+*/
+$config['cis_url'] = 'http://p2p-next-03.grid.pub.ro:31500/';
+
+/*
+|--------------------------------------------------------------------------
+| Video Formats
+|--------------------------------------------------------------------------
+|
+| Formats available for the ingested video. You need to provide an array
+| of dictionaries as in the following example:
+| 
+|      $config['formats'] = array(
+|              array(
+|                      'container'=>'ogg',
+|                      'extension'=>'ogv',
+|                      'audio_codec'=>'vorbis',
+|                      'audio_bit_rate'=>'128k',
+|                      'audio_sampling_rate'=>44100,
+|                      'audio_channels'=>2,
+|                      'video_codec'=>'theora',
+|                      'video_bit_rate'=>'768k',
+|                      'video_frame_rate'=>25,
+|                      'video_height'=>600
+|              )
+|      );
+|
+| For a list of available containers and audio / video codecs see CIS
+| documentation. The video_height parameter is a desired height resolution.
+| It may be lower if the uploaded video has a lower resolution.
+|
+*/
+// 
+$config['formats'] = array(
+       array(
+               'container'=>'ogg',
+               'extension'=>'ogv',
+               'audio_codec'=>'vorbis',
+               'audio_bit_rate'=>'128k',
+               'audio_sampling_rate'=>44100,
+               'audio_channels'=>2,
+               'video_codec'=>'theora',
+               'video_bit_rate'=>'768k',
+               'video_frame_rate'=>25,
+               'video_height'=>600
+       ),
+       array(
+               'container'=>'ogg',
+               'extension'=>'ogv',
+               'audio_codec'=>'vorbis',
+               'audio_bit_rate'=>'192k',
+               'audio_sampling_rate'=>44100,
+               'audio_channels'=>2,
+               'video_codec'=>'theora',
+               'video_bit_rate'=>'1536k',
+               'video_frame_rate'=>25,
+               'video_height'=>1080
+       )
+);
+
+/*
+|--------------------------------------------------------------------------
+| Thumnail images count
+|--------------------------------------------------------------------------
+|
+| Number of thumbnail images for a video asset.
+|
+*/
+$config['thumbs_count'] = 4;
+
+/*
+|--------------------------------------------------------------------------
+| Eliminate Duplicate Resolutions
+|--------------------------------------------------------------------------
+|
+| Eliminate consecutive formats with the same resolution after processing
+| them.
+|
+*/
+$config['elim_dupl_res'] = TRUE;
\ No newline at end of file
index bcbe0f5..20ba114 100644 (file)
@@ -1,6 +1,6 @@
 <?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed');
 
-// P2P-Tube specific configuration files
+// P2P-Tube specific configuration file
 
 /*
 |--------------------------------------------------------------------------
index eb89005..366b92f 100644 (file)
@@ -9,6 +9,8 @@
  */
 class Video extends CI_Controller {
 
+       protected $uploaded_file;
+       
        public function __construct()
        {
                parent::__construct();
@@ -18,22 +20,12 @@ class Video extends CI_Controller {
        
        public function index()
        {
+               //phpinfo();
        }
        
        public function test()
        {
-               $r = new HttpRequest('http://example.com/form.php', HttpRequest::METH_POST);
-               $r->setOptions(array('cookies' => array('lang' => 'de')));
-               $r->addPostFields(array('user' => 'mike', 'pass' => 's3c|r3t'));
-               $r->addPostFile('image', 'profile.jpg', 'image/jpeg');
-               try
-               {
-                       echo $r->send()->getBody();
-               }
-               catch (HttpException $ex) 
-               {
-                       echo $ex;
-               }
+               var_dump($this->session->userdata('user_id'));
        }
        
        /**
@@ -104,13 +96,22 @@ class Video extends CI_Controller {
                
        public function upload()
        {
+               $user_id = $this->session->userdata('user_id');
+               
+               // Action not possible if an user is not logged in.
+               if (!$user_id)
+               {
+                       $this->load->helper('message');
+                       show_error_msg_page($this, 
+                               $this->lang->line('ui_msg_login_restriction'));
+                       return;
+               }
+               
                $this->load->library('form_validation');
 
                $this->form_validation->set_error_delimiters('<span class="error">',
                                '</span>');
                
-               // TODO check if user is logged in
-
                if ($this->form_validation->run('upload') === FALSE)
                {
                        $params = array('title' =>
@@ -140,18 +141,37 @@ class Video extends CI_Controller {
                {
                        $this->load->model('videos_model');
                        $this->load->helper('video');
+                       $this->config->load('content_ingestion');
                        
-                       $file_name = './data/upload/'. $_FILES['video-upload-file']['name'];
+                       $file_name = $this->uploaded_file;
                        $av_info = get_av_info($file_name);
+                       $name = urlencode(str_replace(' ', '-',
+                                       $this->input->post('video-title')));
+                       $category_id = $this->input->post('video-category');
+                       
+                       // Prepare formats
+                       $formats = $this->config->item('formats');
+                       $prepared_formats = prepare_formats($formats, $av_info,
+                                       $this->config->item('elim_dupl_res'));
                        
-                       // TODO category_id, user_id
-//                     $this->videos_model->add_video(
-//                                     $this->input->post('video-title'),
-//                                     $this->input->post('video-description'),
-//                                     $this->input->post('video-tags'),
-//                                     $av_info, 0, 1);
+                       // Add video to DB.
+                       $activation_code = $this->videos_model->add_video($name,
+                                       $this->input->post('video-title'),
+                                       $this->input->post('video-description'),
+                                       $this->input->post('video-tags'),
+                                       $av_info['duration'],
+                                       $prepared_formats['db_formats'], $category_id, $user_id);
                        
-                       // TODO call CIS
+                       // Send a content ingestion request to
+                       // CIS (Content Ingestion Server).
+                       $this->_send_content_ingestion($activation_code,
+                                       $file_name,
+                                       $name, $av_info['size'],
+                                       $prepared_formats['transcode_configs']);
+                       
+                       $this->load->helper('message');
+                       show_info_msg_page($this, 
+                               $this->lang->line('video_msg_video_uploaded'));
                }
        }
        
@@ -251,6 +271,50 @@ class Video extends CI_Controller {
                        return $output;
        }
        
+       /**
+        * Request content_ingest to the CIS in order to start the content
+        * ingestion process.
+        * 
+        * @param string $activation_code
+        * @param string $raw_video_fn uploaded video file name
+        * @param string $name
+        * @param int $raw_video_size uploaded video file size in bytes
+        * @param array $transcode_configs dictionary which must be included in
+        * the JSON data that needs to be sent to CIS
+        * @return mixed return the HTTP content (body) on success and FALSE
+        * otherwise
+        */
+       protected function _send_content_ingestion($activation_code, $raw_video_fn,
+                       $name, $raw_video_size, $transcode_configs)
+       {
+               $this->config->load('content_ingestion');
+               
+               $url = $this->config->item('cis_url') . 'ingest_content';
+               $data = array(
+                       'code'=>$activation_code,
+                       'raw_video'=>$raw_video_fn,
+                       'name'=>$name,
+                       'weight'=>$raw_video_size,
+                       'transcode_configs'=>$transcode_configs,
+                       'thumbs'=>$this->config->item('thumbs_count')
+               );
+               $json_data = json_encode($data);
+               
+               // Send request to CIS.
+               $r = new HttpRequest($url, HttpRequest::METH_POST);
+               $r->setBody($json_data);
+               try
+               {
+                       $response = $r->send()->getBody();
+               }
+               catch (HttpException $ex) 
+               {
+                       return FALSE;
+               }
+               
+               return $response;
+       }
+       
        public function _is_user_loggedin($param)
        {
                if (! $this->session->userdata('user_id'))
@@ -297,6 +361,8 @@ class Video extends CI_Controller {
 
                        if ($this->upload->do_upload('video-upload-file'))
                        {
+                               $this->uploaded_file = $this->upload->data();
+                               $this->uploaded_file = $this->uploaded_file['file_name'];
                                return TRUE;
                        }
                        else
index b845898..67ede1a 100644 (file)
@@ -117,8 +117,77 @@ function get_av_info($file_name)
 {
        // TODO use ffprobe to return width, height, DAR, duration and size of a video
        
-       return array('width'=> 800, 'height'=> 600, 'dar'=> '16:9',
-                       'duration'=> '00:10', 'size'=> 1101693);
+       return array('width'=> 1440, 'height'=> 1080, 'dar'=> '16:9',
+                       'duration'=> '00:10', 'size'=> 5568748);
+}
+
+/**
+ * Return a dictionary with formats compliant for DB and CIS and computes
+ * resolutions such that an uploaded video will not be converted to a higher
+ * resolution.
+ * 
+ * @param type $formats formats as in content_ingestion config file
+ * @param type $av_info structure as returned by get_av_info function from this
+ * helper
+ * @param type $elim_dupl_res eliminate consecutive formats with the same
+ * resolution
+ * @return array a dictionary with DB format at key 'db_formats' and CIS format
+ * at key 'transcode_configs' 
+ */
+function prepare_formats($formats, $av_info, $elim_dupl_res=FALSE)
+{
+       $transcode_configs = array();
+       $db_formats = array();
+       
+       for ($i = 0; $i < count($formats); $i++)
+       {
+               $transcode_configs[$i]['container'] = $formats[$i]['container'];
+               $transcode_configs[$i]['extension'] = $formats[$i]['extension'];
+               $db_formats[$i]['ext'] = $formats[$i]['extension'];
+               $transcode_configs[$i]['a_codec'] = $formats[$i]['audio_codec'];
+               $transcode_configs[$i]['a_bitrate'] = $formats[$i]['audio_bit_rate'];
+               $transcode_configs[$i]['a_samplingrate'] = $formats[$i]['audio_sampling_rate'];
+               $transcode_configs[$i]['a_channels'] = $formats[$i]['audio_channels'];
+               $transcode_configs[$i]['v_codec'] = $formats[$i]['video_codec'];
+               $transcode_configs[$i]['v_bitrate'] = $formats[$i]['video_bit_rate'];
+               $transcode_configs[$i]['v_framerate'] = $formats[$i]['video_frame_rate'];
+               $transcode_configs[$i]['v_dar'] = $av_info['dar'];
+               $db_formats[$i]['dar'] = $av_info['dar'];
+
+               $sar = $av_info['width'] / $av_info['height'];
+               
+               if ($av_info['height'] < $formats[$i]['video_height'])
+               {
+                       $width = $av_info['width'];
+                       $height = $av_info['height'];
+               }
+               else
+               {
+                       $height = $formats[$i]['video_height'];
+                       $width = round($sar * $height);
+               }
+               
+               $transcode_configs[$i]['v_resolution'] = "${width}x${height}";
+               $db_formats[$i]['res'] = "${width}x${height}";
+       }
+       
+       // Eliminate formats with duplicate resolutions.
+       if ($elim_dupl_res)
+       {
+               for ($i = 1; $i < count($transcode_configs); $i++)
+               {
+                       if ($transcode_configs[$i]['v_resolution']
+                                       === $transcode_configs[$i - 1]['v_resolution'])
+                       {
+                               unset($transcode_configs[$i - 1]);
+                               unset($db_formats[$i - 1]);
+                               $i--;
+                       }
+               }
+       }
+       
+       return array('transcode_configs'=>$transcode_configs,
+               'db_formats'=>$db_formats);
 }
 
 /* End of file video_helper.php */
index 459a5df..92b93df 100644 (file)
@@ -19,6 +19,7 @@ $lang['video_submit_post_comment'] = 'Post';
 $lang['video_upload_file'] = 'Video File';
 $lang['video_title'] = 'Title';
 $lang['video_description'] = 'Description';
+$lang['video_category'] = 'Category';
 $lang['video_tags'] = 'Tags';
 $lang['video_tags_hint'] = 'comma separated tags';
 $lang['video_submit_upload'] = 'Upload';
index b03cd16..f927561 100644 (file)
@@ -248,28 +248,23 @@ class Videos_model extends CI_Model {
                return $video;
        }
        
-       public function compute_video_name($title)
-       {
-               $name = str_replace(' ', '-', $title);
-               
-               return urlencode($name);
-       }
-       
        /**
         * Adds a new uploaded video to the DB.
         * 
-        * @param type $title
-        * @param type $description
-        * @param type $tags comma separated tags
-        * @param type $av_info a dictionary of video properties containing keys
-        * width, height, dar (display aspect ratio), duration and size; is can be
-        * returned with function get_av_info from video helper
+        * @param string $name
+        * @param string $title
+        * @param string $description
+        * @param string $tags comma separated tags
+        * @param string $duration video duration formatted [HH:]mm:ss
+        * @param array $formats a dictionary corresponding to `formats` JSON
+        * column from `videos` table
+        * @param int $category_id
+        * @param int $user_id
+        * @return mixed returns an activation code on success or FALSE otherwise
         */
-       public function add_video($title, $description, $tags, $av_info,
-                       $category_id, $user_id)
+       public function add_video($name, $title, $description, $tags, $duration,
+                       $formats, $category_id, $user_id)
        {
-               $name = $this->compute_video_name($title);
-               
                // Tags.
                $json_tags = array();
                $tok = strtok($tags, ',');
@@ -282,15 +277,13 @@ class Videos_model extends CI_Model {
                $json_tags = json_encode($json_tags);
                
                // TODO formats
-               $json_formats = '[{"res":"1280x720","ext":"ogv","dar":"16:9"},'
-                               . '{"res":"1067x600","ext":"ogv","dar":"16:9"}]';
-               
+               $json_formats = json_encode($formats);
                
                $query = $this->db->query("INSERT INTO `videos`
                                (name, title, description, duration, formats, category_id,
                                                user_id, tags, date)
-                               VALUES ('$name', '$title', '$description', '"
-                                               . $av_info['duration']. "', '$json_formats', $category_id,
+                               VALUES ('$name', '$title', '$description', '$duration',
+                                               '$json_formats', $category_id,
                                                $user_id, '$json_tags', utc_timestamp())");
                if ($query === FALSE)
                        return FALSE;
@@ -308,6 +301,8 @@ class Videos_model extends CI_Model {
                $query = $this->db->query("INSERT INTO `videos_unactivated`
                                (video_id, activation_code)
                                VALUES ($video_id, '$activation_code')");
+               
+               return $activation_code;
        }
        
        /**
index 475239f..c1867c2 100644 (file)
@@ -87,7 +87,7 @@
        <?php echo form_open('catalog/search', array('id'=>'quick-search')); ?>
                <label for="search-category"><?php echo $this->lang->line('ui_search_in') ?></label> <?php 
                        echo form_dropdown('search-category', $categories, 
-                               $search_category_name, 'id=search-category') ?>:
+                               $search_category_name, 'id="search-category"') ?>:
                <input type="text" id="search" name="search" value="<?php echo htmlentities($search_query) ?>" />
                <input type="submit" id="button-quick-search" value="<?php echo $this->lang->line('ui_search') ?>" />
                <a href="#" id="button-js-quick-search" style="display:none">
index 51a3db1..e60e144 100644 (file)
@@ -1,3 +1,11 @@
+<?php
+       // Categories
+       foreach ($this->config->item('categories') as $id => $name)
+       {
+               $categories[$id] = $this->lang->line("ui_categ_$name");
+       }
+?>
+
 <?php echo form_open_multipart("video/upload") ?>
 <table class="form">
        <tr>
        </tr>
        <tr><td></td><td><?php echo form_error('video-description') ?></td></tr>
        
+       <tr>
+               <th><?php echo $this->lang->line('video_category') ?> <span class="required">*</span> : </th>
+               <td><?php echo form_dropdown('video-category', $categories, 
+                               // TODO set_value not working
+                               set_value('video-category', '1')) ?></td>
+       </tr>
+       <tr><td></td><td></td></tr>
+       
        <tr>
                <th><?php echo $this->lang->line('video_tags') ?> <span class="required">*</span> : </th>
                <td><input type="text" name="video-tags" value="<?php echo set_value('video-tags') ?>" size="16" /> (<?php echo $this->lang->line('video_tags_hint') ?>)</td>
index b4e18a8..11c922a 100644 (file)
@@ -61,7 +61,7 @@ class BaseTranscoder:
 
         self.name = name
 
-    def transcode(self, container, a_codec=None, v_codec=None,
+    def transcode(self, container, extension=None, a_codec=None, v_codec=None,
             a_bitrate=None, a_samplingrate=None, a_channels=None,
             v_bitrate=None, v_framerate=None, v_resolution=None, v_dar=None):
         """
@@ -117,16 +117,19 @@ class BaseTranscoder:
             self.output_file += '_'
             self.output_file += v_resolution[(v_resolution.rindex('x')+1):]
             self.output_file += 'p'
-        ext = self.tr_extension(container, (v_codec is not None))
+        if extension == None:
+            ext = self.tr_extension(container, (v_codec is not None))
+        else:
+            ext = extension
         if ext is not None:
             self.output_file += '.' + ext
 
-        return self._transcode(self.tr_container(container),
+        return self._transcode(self.tr_container(container), ext,
                 self.tr_a_codec(a_codec), self.tr_v_codec(v_codec),
                 a_bitrate, a_samplingrate, a_channels,
                 v_bitrate, v_framerate, v_resolution, v_dar)
 
-    def _transcode(self, container, a_codec=None, v_codec=None,
+    def _transcode(self, container, extension=None, a_codec=None, v_codec=None,
             a_bitrate=None, a_samplingrate=None, a_channels=None,
             v_bitrate=None, v_framerate=None, v_resolution=None, v_dar=None):
         """
index 1974497..8e49f1a 100644 (file)
@@ -40,7 +40,7 @@ class FFmpegTranscoder(base.BaseTranscoder):
         "vp8": "libvpx"
     }
 
-    def _transcode(self, container, a_codec=None, v_codec=None,
+    def _transcode(self, container, extension=None, a_codec=None, v_codec=None,
             a_bitrate=None, a_samplingrate=None, a_channels=None,
             v_bitrate=None, v_framerate=None, v_resolution=None, v_dar=None):
 
index f19604c..ec35ca0 100644 (file)
@@ -9,7 +9,11 @@ from api import ftp
 
 # === GENERAL CONFIGURATIONS ===
 LOG_LEVEL = logger.LOG_LEVEL_DEBUG
+# Security features are experimental, incomplete and may not work.
 SECURITY = False
+# CIS periodically scans TORRENTS_PATH for new torrents at
+# START_DOWNLOADS_INTERVAL seconds. Note that this is a backup measure, because
+# CIS normally finds out about new torrents from start_torrents messages.
 START_DOWNLOADS_INTERVAL = 24 * 3600.0 # Once a day
 
 
@@ -27,16 +31,23 @@ WS_THUMBS_PATH = 'devel/data/thumbs'
 #BT_TRACKER = "http://p2p-next-10.grid.pub.ro:6969/announce"
 # BitTorrent tracker URL.
 BT_TRACKER = 'http://p2p-next-10.grid.pub.ro:6969/announce'
-# Web server's URL for content ingestion completion.
+# Web server's URL for content ingestion completion. P2P-Tube uses
+# http://<site>/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'
 
 
 # === CIS PATHS ===
-RAW_VIDEOS_PATH = 'tmp/raw'
-THUMBS_PATH = 'tmp/thumbs'
-MEDIA_PATH = '/home/p2p/export/p2p-tube/media'
-# In a distributed file system for multi-CIS.
+# RAW_VIDEOS_PATH, THUMBS_PATH and MEDIA_PATH need not to be in distributed
+# file system.
+# Temporary directory for uploaded videos transfered from the web server.
+RAW_VIDEOS_PATH = '/home/p2p/tmp/raw'
+# Temporary directory for image thumbnails.
+THUMBS_PATH = '/home/p2p/tmp/thumbs'
+# Temporary directory for converted videos.
+MEDIA_PATH = '/home/p2p/media'
+# TORRENTS_PATH contains torrents files shared by all CIS machines and needs to
+# be placed in a distributed file system.
 TORRENTS_PATH = '/home/p2p/export/p2p-tube/torrents'
 
 
@@ -53,7 +64,9 @@ FILE_TRANSFERER_CLASS = ftp.FTPFileTransferer
 
 # === EXTERNAL PROGRAMS BINARY FILES ===
 # Set this values to None if you want default values provided by the API
-# class to be used.
+# class to be used. It may be useful to complete this parameters if you
+# compiled the third-party binaries from sources and you don't have
+# administrative privileges to install them.
 # Binary of a prgram which retrives audio/video information, like duration.
 AVINFO_BIN = None
 # Binary of a prgram which transcodes an audio/video file.
index f148510..4f1d8c6 100644 (file)
@@ -1,16 +1,32 @@
 {
     "code": "33",
-    "raw_video": "test.ogv",
+    "raw_video": "test.mts",
     "name": "test",
-    "weight": 3,
+    "weight": 5568748,
     "transcode_configs": [
         {
-            "container": "webm",
+            "container": "ogg",
+            "extension": "ogv",
             "a_codec": "vorbis",
             "a_bitrate": "128k",
-            "v_codec": "vp8",
-            "v_bitrate": "480k",
-            "v_resolution": "640x480"
+            "a_samplingrate": 44100,
+            "a_channels": 2,
+            "v_codec": "theora",
+            "v_bitrate": "768k",
+            "v_framerate": 25,
+            "v_resolution": "800x600"
+        },
+        {
+            "container": "ogg",
+            "extension": "ogv",
+            "a_codec": "vorbis",
+            "a_bitrate": "192k",
+            "a_samplingrate": 44100,
+            "a_channels": 2,
+            "v_codec": "theora",
+            "v_bitrate": "1536k",
+            "v_framerate": 25,
+            "v_resolution": "1440x1080"
         }
     ],
     "thumbs": 4
index fe65927..2d87c07 100644 (file)
@@ -28,7 +28,7 @@ $.widget( "ui.nsvideo", {
        },
        
        _create: function() {
-               var widget = this;
+               widget = this;
                
                widget.element
                        .addClass( "ui-widget ui-widget-content ui-corner-all" );
@@ -382,6 +382,8 @@ $.widget( "ui.nsvideo", {
        },
        
        _setWidgetWidth: function() {
+        var widget = this;
+        
                if (widget.$video.width() < 640)
                {
                        widget.element.css('width',
@@ -704,6 +706,8 @@ $.widget( "ui.nsvideo", {
                },
                
                refreshState: function() {
+            var widget = this;
+            
                        var err = "";
                        var normal = "";
                        var network = "";
diff --git a/scripts/sync_cis.sh b/scripts/sync_cis.sh
new file mode 100755 (executable)
index 0000000..b87ed43
--- /dev/null
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+rsync -avz ../cis p2p@p2p-next-01.grid.pub.ro:export/p2p-tube/