--- /dev/null
+<?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
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
-// P2P-Tube specific configuration files
+// P2P-Tube specific configuration file
/*
|--------------------------------------------------------------------------
*/
class Video extends CI_Controller {
+ protected $uploaded_file;
+
public function __construct()
{
parent::__construct();
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'));
}
/**
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' =>
{
$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'));
}
}
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'))
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
{
// 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 */
$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';
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, ',');
$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;
$query = $this->db->query("INSERT INTO `videos_unactivated`
(video_id, activation_code)
VALUES ($video_id, '$activation_code')");
+
+ return $activation_code;
}
/**
<?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">
+<?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>
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):
"""
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):
"""
"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):
# === 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
#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'
# === 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.
{
"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
},
_create: function() {
- var widget = this;
+ widget = this;
widget.element
.addClass( "ui-widget ui-widget-content ui-corner-all" );
},
_setWidgetWidth: function() {
+ var widget = this;
+
if (widget.$video.width() < 640)
{
widget.element.css('width',
},
refreshState: function() {
+ var widget = this;
+
var err = "";
var normal = "";
var network = "";
--- /dev/null
+#!/bin/bash
+
+rsync -avz ../cis p2p@p2p-next-01.grid.pub.ro:export/p2p-tube/