Skip to content

Instantly share code, notes, and snippets.

@mistic100
Created February 7, 2015 09:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mistic100/07fa39b547f7e9f9a9f0 to your computer and use it in GitHub Desktop.
Save mistic100/07fa39b547f7e9f9a9f0 to your computer and use it in GitHub Desktop.
[PHP] A one-file videos files indexer connected to TheMovieDatabase (Windows only)
<?php
/**
* My Movie Database - A Windows Movie Indexer
*
* @author: Damien "Mistic" Sorel - http://strangeplanet.fr
* @license: GNU General Public License
* @since: 25/05/2013
*
* @version: 1.1.0
* @date: 25/07/2013
*
* @dependencies:
* jQuery - http://jquery.com
* jQuery UI - http://jqueryui.com
* jQuery DataTables - http://datatables.net
* jQuery UI MultiSelect - http://erichynds.com/blog/jquery-ui-multiselect-widget
* jQuery rateIt - http://rateit.codeplex.com
* TMDb-PHP-API - https://github.com/glamorous/TMDb-PHP-API
* Visitor.ttf - http://dafont.com/fr/visitor.font
* Edit Undo Line.ttf - http://www.dafont.com/fr/edit-undo-line.font
*
* images from:
* http://famfamfam.com
* http://loadinggif.com
* http://themoviedb.org
*
* The software will create the files movies.db, movies.log and the directory movies_cache.
*/
header('Content-type: text/html; charset=utf-8');
define('BASENAME', basename(__FILE__, '.php'));
define('CACHE_PATH', BASENAME.'_cache/');
define('CACHE_TIME', 3600*24);
$all_extensions = array('3gp','avi','flv','m4v','mkv','mov','mp4','mpeg','mpg','ogg','rm','wmv');
//-----------------------------------------------------
// EXTERNAL CONTENT
//-----------------------------------------------------
$externals = array(
// 'jquery.js' => 'http://code.jquery.com/jquery-1.9.1.min.js',
// 'jquery_ui.js' => 'http://code.jquery.com/ui/1.10.2/jquery-ui.min.js',
// 'jquery_ui.css' => 'http://code.jquery.com/ui/1.10.2/themes/smoothness/jquery-ui.min.css',
// 'datatables.js' => 'http://ajax.aspnetcdn.com/ajax/jquery.dataTables/1.9.4/jquery.dataTables.min.js',
// 'datatables.css' => 'http://ajax.aspnetcdn.com/ajax/jquery.dataTables/1.9.4/css/jquery.dataTables_themeroller.css',
'multiselect.js' => 'https://raw.github.com/ehynds/jquery-ui-multiselect-widget/master/src/jquery.multiselect.min.js',
'multiselect.css' => 'https://raw.github.com/ehynds/jquery-ui-multiselect-widget/master/jquery.multiselect.css',
'rateit.js' => 'https://raw.github.com/evadnoob/jquery.rateit/master/jquery.rateit.min.js',
'rateit.css' => 'https://raw.github.com/evadnoob/jquery.rateit/master/rateit.css',
'rateit_star.gif' => 'https://raw.github.com/evadnoob/jquery.rateit/master/star.gif',
'rateit_delete.gif' =>'https://raw.github.com/evadnoob/jquery.rateit/master/delete.gif',
'loader.gif' => 'http://loadinggif.com/images/image-selection/10.gif',
'star-on.png' => 'http://d3a8mw37cqal2z.cloudfront.net/images/star-on.png',
'star-off.png' => 'http://d3a8mw37cqal2z.cloudfront.net/images/star-off.png',
'star-half.png' => 'http://d3a8mw37cqal2z.cloudfront.net/images/star-half.png',
'tag.png' => 'http://d3a8mw37cqal2z.cloudfront.net/images/tag.png',
'no-profile.png' => 'http://d3a8mw37cqal2z.cloudfront.net/images/no-profile-w45.jpg',
'film.png' => 'http://www.famfamfam.com/lab/icons/silk/icons/film.png',
'film_delete.png' => 'http://www.famfamfam.com/lab/icons/silk/icons/film_delete.png',
'information.png' => 'http://www.famfamfam.com/lab/icons/silk/icons/information.png',
'help.png' => 'http://www.famfamfam.com/lab/icons/silk/icons/help.png',
'date_add.png' => 'http://www.famfamfam.com/lab/icons/silk/icons/date_add.png',
'cross.png' => 'http://www.famfamfam.com/lab/icons/silk/icons/cross.png',
'visitor.ttf' => 'https://raw.github.com/endroid/EndroidMobile/master/assets/font/visitor.ttf',
'edunline.ttf' => 'https://raw.github.com/rmzi/cyoa/master/edunline.ttf',
'tmdb.php' => 'https://raw.github.com/glamorous/TMDb-PHP-API/master/TMDb.php',
);
// dynamically load a content
if (isset($_GET['cdn']))
{
if (!array_key_exists($_GET['cdn'], $externals)) exit;
if (!file_exists(CACHE_PATH.$_GET['cdn']))
{
@mkdir(CACHE_PATH);
if (!download_file($externals[ $_GET['cdn'] ], CACHE_PATH.$_GET['cdn'])) exit;
}
header('Location: '.CACHE_PATH.$_GET['cdn']);
exit;
}
// download all dependencies at first launch
if (!file_exists(CACHE_PATH))
{
@mkdir(CACHE_PATH);
foreach ($externals as $file => $source)
{
download_file($source, CACHE_PATH.$file);
}
}
//-----------------------------------------------------
// INIT DATABASE
//-----------------------------------------------------
$init = !file_exists(BASENAME.'.db');
$db = new SQLite3(BASENAME.'.db');
$db->busyTimeout(1000);
$db->exec('PRAGMA encoding = "UTF-8";');
// create tables
if ($init)
{
$create_movies_table_query = '
CREATE TABLE IF NOT EXISTS `movies` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`filename` TEXT NOT NULL UNIQUE,
`name` TEXT NOT NULL,
`added_date` DATETIME NOT NULL,
`filesize` INTEGER NOT NULL,
`language` TEXT NOT NULL DEFAULT "default",
`resolution` TEXT NOT NULL DEFAULT "default",
`own` INTEGER NOT NULL DEFAULT 0,
`dead` INTEGER NOT NULL DEFAULT 0,
`rate` REAL NOT NULL DEFAULT 0,
`tmdb_id` INTEGER DEFAULT NULL,
`last_view` DATETIME DEFAULT NULL
);';
$db->exec($create_movies_table_query);
$create_config_table_query = '
CREATE TABLE IF NOT EXISTS `config` (
`param` TEXT NOT NULL UNIQUE,
`value` TEXT
);';
$db->exec($create_config_table_query);
}
// add default config
if ($init)
{
$default_config = array(
'allowed_extensions' => 'avi,mkv',
'main_path' => 'C:\\Users\\',
'check_period' => 48,
'last_check' => 0,
'first_launch' => 1,
'tmdb_api_key' => null,
'error_is_windows' => 1,
'error_path' => 1,
);
foreach ($default_config as $param => $value)
{
conf_update_param($param, $value);
}
}
// request for database update
if (isset($_GET['update_db']))
{
$new_columns = array(
);
$new_conf = array(
);
foreach ($new_columns as $table => $columns)
{
foreach ($columns as $col)
{
@$db->exec('ALTER TABLE `'.$table.'` ADD COLUMN '.$col.';');
}
}
foreach ($new_conf as $param => $value)
{
conf_update_param($param, $value, false);
}
}
// request for database reset
if (isset($_GET['reset_db']))
{
$db->exec('DELETE FROM `movies`;');
$db->exec('DELETE FROM `sqlite_sequence` WHERE name="movies";');
$db->exec('VACUUM;');
}
//-----------------------------------------------------
// LOAD CONFIGURATION
//-----------------------------------------------------
$conf = array();
// get conf from database
$result = $db->query('SELECT * FROM `config`;');
while ($row = $result->fetchArray(SQLITE3_ASSOC))
{
$conf[ $row['param'] ] = $row['value'];
}
$conf['is_windows'] = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN';
$conf['must_check'] = time()-$conf['last_check'] > $conf['check_period']*3600
|| isset($_GET['force_check'])
|| isset($_GET['reset_db']);
// submit config form
if (isset($_POST['save_config']))
{
if (count(array_full_diff($_POST['allowed_extensions'], explode(',', $conf['allowed_extensions']))))
{
$conf['must_check'] = true;
}
if (file_exists($_POST['main_path']))
{
if ($_POST['main_path'] != $conf['main_path'])
{
$conf['must_check'] = true;
}
conf_update_param('main_path', $_POST['main_path']);
}
else
{
$error_path = $_POST['main_path'];
$conf['must_check'] = false;
}
if ($conf['first_launch'])
{
conf_update_param('first_launch', 0);
}
conf_update_param('allowed_extensions', implode(',', $_POST['allowed_extensions']));
conf_update_param('check_period', $_POST['check_period']);
conf_update_param('tmdb_api_key', $_POST['tmdb_api_key']);
}
else if (!file_exists($conf['main_path']))
{
$error_path = $conf['main_path'];
$conf['must_check'] = false;
}
$conf['allowed_extensions'] = explode(',', $conf['allowed_extensions']);
//-----------------------------------------------------
// HIDE ERROR
//-----------------------------------------------------
if (isset($_POST['action']) && $_POST['action'] == 'hide_error')
{
conf_update_param('error_'.$_POST['error'], 0);
exit_json('ok');
}
//-----------------------------------------------------
// RATING
//-----------------------------------------------------
if (isset($_POST['action']) && $_POST['action'] == 'rating')
{
if ($db->exec('UPDATE `movies` SET rate = '.$_POST['rate'].' WHERE id = '.$_POST['movie_id'].';'))
{
exit_json('ok', $_POST['rate']);
}
else
{
exit_json('error', 'Unknown error.');
}
}
//-----------------------------------------------------
// VIEW DATE
//-----------------------------------------------------
if (isset($_POST['action']) && $_POST['action'] == 'view_date')
{
if ($db->exec('UPDATE `movies` SET last_view = "'.format_date($_POST['date'], 'd/m/Y', 'Y-m-d H:i:s').'" WHERE id = '.$_POST['movie_id'].';'))
{
exit_json('ok', $_POST['date']);
}
else
{
exit_json('error', 'Unknown error.');
}
}
//-----------------------------------------------------
// MOVIE INFO
//-----------------------------------------------------
if (isset($_POST['action']) && $_POST['action'] == 'tmdb')
{
// download TMDB-API class
if (!file_exists(CACHE_PATH.'tmdb.php'))
{
if (!download_file($externals['tmdb.php'], CACHE_PATH.'tmdb.php'))
{
exit_json('error', 'Unable to download TMDb.php from GitHub servers.');
}
}
// load TMDb
include_once(CACHE_PATH.'tmdb.php');
class _TMDb extends TMDb
{
/*
* this is an implementation of the TMDb class with a basic cache
* you can use the cached version on any get* method by prepending method name with an underscore
* cached getters only accept one argument
*/
function _initConfig()
{
if (($cache = $this->_getCache('config')) !== false)
{
$this->setConfig($cache);
}
else
{
$data = $this->getConfig();
$this->_setCache('config', $data);
}
}
function __call($method, $query)
{
if (strpos($method,'_')!==0) return $this->{$method};
$method = substr($method, 1);
$query = $query[0];
if (($data = $this->_getCache($method.'_'.$query)) !== false)
{
return $data;
}
$data = $this->{$method}($query);
$this->_setCache($method.$query, $data);
return $data;
}
private function _getCache($id)
{
$file = CACHE_PATH.$id.'.json';
if (file_exists($file))
{
if (time()-filemtime($file)<CACHE_TIME)
{
return json_decode(file_get_contents($file), true);
}
unlink($file);
}
return false;
}
private function _setCache($id, $data)
{
$file = CACHE_PATH.$id.'.json';
file_put_contents($file, json_encode($data));
}
}
$tmdb = new _TMDb($conf['tmdb_api_key'], 'en');
$tmdb->_initConfig();
// get movie data
$result = $db->query('SELECT id, name, tmdb_id FROM `movies` WHERE id='.$_POST['movie_id'].';');
$movie = $result->fetchArray(SQLITE3_ASSOC);
if (count($movie)==0)
{
exit_json('error', 'Unknown movie ID.');
}
// define tmdb id if provided
if (!empty($_POST['tmdb_id']))
{
$db->exec('UPDATE `movies` SET tmdb_id='.$_POST['tmdb_id'].' WHERE id='.$movie['id'].';');
$movie['tmdb_id'] = $_POST['tmdb_id'];
}
// reset tmdb id if asked
if (isset($_POST['reset']) && $_POST['reset']=='true')
{
$db->exec('UPDATE `movies` SET tmdb_id=NULL WHERE id='.$movie['id'].';');
$movie['tmdb_id'] = null;
}
// load data from imdb
if (!empty($movie['tmdb_id']))
{
try {
// fetch movie info and cast
$result = $tmdb->_getMovie($movie['tmdb_id']);
$result['cast'] = $tmdb->_getMovieCast($movie['tmdb_id']);
my_log('TMDb FETCH: '.$movie['name'].' (#'.$movie['tmdb_id'].')');
}
catch (TMDbException $e)
{
exit_json('error', $e->getMessage());
}
if (isset($result['status_code']))
{
exit_json('error', $result['status_message']);
}
$data = array(
'id' => $movie['id'],
'tmdb_id' => $result['id'],
'imdb_id' => $result['imdb_id'],
'title' => $result['title'],
'date' => format_date($result['release_date'], 'Y-m-d', 'Y'),
'poster' => $tmdb->getImageUrl($result['poster_path'], TMDb::IMAGE_POSTER, 'w342'),
'poster_full' => $tmdb->getImageUrl($result['poster_path'], TMDb::IMAGE_POSTER, 'w500'),
'overview' => $result['overview'],
'vote' => $result['vote_average'],
'genres' => $result['genres'],
'runtime' => format_runtime($result['runtime']),
);
// limit to 4 actors
foreach (array_slice($result['cast']['cast'], 0, 4) as $cast)
{
$data['cast'][] = array(
'id' => $cast['id'],
'name' => $cast['name'],
'character' => $cast['character'],
'photo' => $tmdb->getImageUrl($cast['profile_path'], TMDb::IMAGE_PROFILE, 'w45'),
);
}
exit_json('card', $data);
}
// search movie on tmdb
else
{
try {
// fetch movies list based on filename or special search
$search = !empty($_POST['search']) ? $_POST['search'] : $movie['name'];
$result = $tmdb->_searchMovie($search);
my_log('TMDb SEARCH: '.$search.' ('.$result['total_results'].' results)');
}
catch (TMDbException $e)
{
exit_json('error', $e->getMessage());
}
if (isset($result['status_code']))
{
exit_json('error', $result['status_message']);
}
$data = array();
if ($result['total_results']>0)
{
// limit to 8 movies
$result['results'] = array_slice($result['results'], 0, 8);
foreach ($result['results'] as $entry)
{
$data[] = array(
'id' => $movie['id'],
'tmdb_id' => $entry['id'],
'title' => $entry['title'],
'date' => format_date($entry['release_date'], 'Y-m-d', 'Y'),
'poster' => $tmdb->getImageUrl($entry['poster_path'], TMDb::IMAGE_POSTER, 'w154'),
);
}
}
exit_json('list', $data);
}
}
//-----------------------------------------------------
// FILESYSTEM MOVIES
//-----------------------------------------------------
if ($conf['must_check'] && $conf['is_windows'])
{
$fs_movies = array();
// data from dir command
$data = shell_exec('dir '.$conf['main_path'].' /-C /A:-D');
$data = explode("\n", iconv('CP850', 'UTF-8', $data));
$data = array_slice($data, 5, -2);
foreach ($data as $entry)
{
$ext = get_extension($entry);
if (!in_array($ext, $conf['allowed_extensions'])) continue;
$date = trim(substr($entry, 0, 17));
$date = str_replace(' ', ' ', $date);
$filesize = trim(substr($entry, 17, 18));
$filename = trim(substr($entry, 35));
$filename = preg_replace('#\x04#', 'OWN', $filename);
$name = get_filename_wo_extension($filename);
$name = preg_replace(
array(
'#MULTi#', '#720p#', '#1080p#', '#VOSTFR#', '#OWN#', // tags
'#^[A-Z]\. #', '#( +)$#', // trailing chars and spaces
),
null,
$name);
$fs_movies[ $filename ] = array(
'filename' => $filename,
'name' => $name,
'added_date' => format_date($date.':00', 'd/m/Y H:i:s', 'Y-m-d H:i:s'),
'filesize' => $filesize/1024,
'language' => strpos($filename,'MULTi')!==false ? 'multi' : ( strpos($filename,'VOSTFR')!==false ? 'vostfr' : 'default' ),
'resolution' => strpos($filename,'1080p')!==false ? '1080p' : ( strpos($filename,'720p')!==false ? '720p' : 'default' ),
'own' => strpos($filename,'OWN')!==false,
);
}
}
//-----------------------------------------------------
// DATABASE MOVIES
//-----------------------------------------------------
$db_movies = array();
// asked to clean the database (remove deads)
if (isset($_GET['delete_dead']))
{
$db->exec('DELETE FROM `movies` WHERE dead=1;');
$old_movies = array();
}
// get registered movies
$result = $db->query('SELECT * FROM `movies` ORDER BY filename;');
while ($row = $result->fetchArray(SQLITE3_ASSOC))
{
$db_movies[ $row['filename'] ] = $row;
}
$result->finalize();
if ($conf['must_check'] && $conf['is_windows'])
{
// add or update movies
$to_update = array_merge_recursive_distinct($db_movies, $fs_movies);
if (count($to_update))
{
$stmt = $db->prepare('
INSERT OR REPLACE INTO `movies`(
filename,
name,
added_date,
filesize,
language,
resolution,
own,
rate,
tmdb_id,
last_view
)
VALUES(
:filename,
:name,
:added_date,
:filesize,
:language,
:resolution,
:own,
:rate,
:tmdb_id,
:last_view
)
;');
foreach ($to_update as $movie)
{
$stmt->bindValue(':filename', $movie['filename'], SQLITE3_TEXT);
$stmt->bindValue(':name', $movie['name'], SQLITE3_TEXT);
$stmt->bindValue(':added_date', $movie['added_date'], SQLITE3_TEXT);
$stmt->bindValue(':filesize', $movie['filesize'], SQLITE3_INTEGER);
$stmt->bindValue(':language', $movie['language'], SQLITE3_TEXT);
$stmt->bindValue(':resolution', $movie['resolution'], SQLITE3_TEXT);
$stmt->bindValue(':own', $movie['own'], SQLITE3_INTEGER);
$stmt->bindValue(':rate', @$movie['rate'], SQLITE3_INTEGER);
$stmt->bindValue(':tmdb_id', @$movie['tmdb_id'], SQLITE3_INTEGER);
$stmt->bindValue(':last_view', @$movie['last_view'], SQLITE3_TEXT);
$stmt->execute();
}
$stmt->close();
}
// movies registered but deleted
$old_movies = array_diff(array_keys($db_movies), array_keys($fs_movies));
if (count($old_movies))
{
$stmt = $db->prepare('UPDATE `movies` SET dead = 1 WHERE filename = :filename;');
foreach ($old_movies as $file)
{
$stmt->bindValue(':filename', $db_movies[$file]['filename'], SQLITE3_TEXT);
$stmt->execute();
}
$stmt->close();
}
// rebuild list of movies
$result = $db->query('SELECT * FROM `movies` ORDER BY filename;');
while ($row = $result->fetchArray(SQLITE3_ASSOC))
{
$db_movies[ $row['filename'] ] = $row;
}
$result->finalize();
// update config
$conf['must_check'] = false;
conf_update_param('last_check', time());
}
// count dead movies
if (isset($old_movies))
{
$dead_movies = count($old_movies);
}
else
{
$result = $db->query('SELECT COUNT(*) FROM `movies` WHERE dead=1;');
list($dead_movies) = $result->fetchArray(SQLITE3_NUM);
}
//-----------------------------------------------------
// FUNCTIONS
//-----------------------------------------------------
function conf_update_param($param, $value, $replace=true)
{
global $db, $conf;
$conf[$param] = $value;
$db->exec('INSERT OR '.($replace?'REPLACE':'IGNORE').' INTO `config` VALUES("'.$param.'", "'.$value.'");');
}
function get_filename_wo_extension($filename)
{
$pos = strrpos($filename, '.');
return ($pos===false) ? $filename : substr($filename, 0, $pos);
}
function get_extension($filename)
{
return strtolower(substr( strrchr($filename, '.'), 1, strlen ($filename) ));
}
function array_full_diff($left, $right)
{
return array_diff(array_merge($left, $right), array_intersect($left, $right));
}
function array_merge_recursive_distinct($array1, $array2)
{
$merged = $array1;
foreach ($array2 as $key => &$value)
{
if (is_array($value) && isset($merged[$key]) && is_array($merged[$key]))
$merged[$key] = array_merge_recursive_distinct($merged[$key], $value);
else
$merged[$key] = $value;
}
return $merged;
}
function format_bytes($bytes, $precision=2)
{
$units = array('o', 'Kio', 'Mio', 'Gio', 'Tio');
$bytes = max($bytes, 0);
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
$pow = min($pow, count($units) - 1);
$bytes /= (1 << (10 * $pow));
return round($bytes, $precision) . ' ' . $units[$pow];
}
function format_date($value, $type_in, $type_out, $default=null)
{
if (empty($value)) return $default;
$date = date_create_from_format($type_in, $value);
return date_format($date, $type_out);
}
function format_runtime($value)
{
$value = round($value);
if ($value<=60) return $value.'min';
$hours = floor($value/60);
$minutes = $value-$hours*60;
return $hours.'h '.$minutes.'min';
}
function filter_res_lang($str)
{
return $str=='default' ? null : $str;
}
function download_file($url, $dest)
{
my_log('INFO: Download '.$url);
if (!function_exists('curl_init'))
{
my_log('ERROR: cURL no available. Unable to download '.$url);
return false;
}
$ch = curl_init();
curl_setopt_array($ch, array(
CURLOPT_URL => $url,
CURLOPT_HEADER => false,
CURLOPT_TIMEOUT => 3,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_SSL_VERIFYHOST => 0,
CURLOPT_SSL_VERIFYPEER => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 1,
CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 6.1; rv:22.0) Gecko/20100101 Firefox/22.0',
));
$result = curl_exec($ch);
curl_close($ch);
if ($result === false)
{
my_log('ERROR: Unable to download '.$url);
return false;
}
file_put_contents($dest, $result);
return true;
}
function my_log($str)
{
file_put_contents(BASENAME.'.log', '['.date('d-M-Y H:i:s').'] '.$str."\r\n", FILE_APPEND);
}
function exit_json($stat, $result=null)
{
global $db;
$db->close();
if ($stat=='error' && is_string($result))
{
my_log('ERROR: '.$result);
}
header('HTTP/1.1 200 OK');
echo json_encode(compact('stat', 'result'));
exit;
}
$db->close();
?>
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My Movie Database</title>
<link rel="shortcut icon" href="?cdn=film.png">
<link rel="stylesheet" href="http://code.jquery.com/ui/1.10.2/themes/smoothness/jquery-ui.min.css">
<link rel="stylesheet" href="http://ajax.aspnetcdn.com/ajax/jquery.dataTables/1.9.4/css/jquery.dataTables_themeroller.css">
<link rel="stylesheet" href="?cdn=multiselect.css">
<link rel="stylesheet" href="?cdn=rateit.css">
<style>
@viewport { width:device-width; zoom:1; }
@font-face { font-family:"Visitor"; src:url('?cdn=visitor.ttf'); }
@font-face { font-family:"Edit Undo Line"; src:url('?cdn=edunline.ttf'); }
/* BEGIN CSS RESET
http://meyerweb.com/eric/tools/css/reset
v2.0 | 20110126 | License: none (public domain) */
html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li,
fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video
{margin:0;padding:0;border:0;font-size:100%;vertical-align:baseline;}
article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section {display:block;}
body {line-height:1.1;}
blockquote, q {quotes:none;}
blockquote:before, blockquote:after, q:before, q:after {content:'';content:none;}
table {border-collapse:collapse;border-spacing:0;}
/* END CSS RESET */
body { font-family:"Corbel","Lucida Grande","Verdana",sans-serif; color:#222; font-size:13px; overflow-y:scroll; }
ul { margin:0.5em 0; padding-left:30px; }
dl dt { font-weight:bold; font-size:1.1em; margin:5px 0 2px 0; }
dl dd { padding:0 0 5px 10px; }
dl dt:not(:first-of-type) { border-top:1px dashed #ccc; padding-top:5px; }
a.no-style, a.no-style:hover { color:#222; text-decoration:none; }
a:not(.no-style) { color:#247EBF; text-decoration:none; border-bottom:1px dotted transparent; }
a:not(.no-style):hover { color:#EB9C39; border-bottom-color:#EB9C39; text-shadow:1px 1px 0 #ddd; }
.pointer { cursor:pointer; }
.hide { display:none; }
.ui-my-button { display:inline-block; width:16px; height:16px; padding: 2px; }
.ui-widget-overlay { background:#000; opacity:0.8; }
.ui-dialog { box-shadow: 5px 5px 8px rgba(0,0,0,0.8); }
.icon-delete { display:inline-block; height:16px; width:16px; vertical-align:-3px; }
.icon-delete:hover { background-position:0 -16px; }
div.rateit div.rateit-range, div.rateit div.rateit-hover, div.rateit div.rateit-selected, div.rateit div.rateit-preset, div.rateit div.rateit-preset-rtl { background-image: url('?cdn=rateit_star.gif'); }
.icon-delete, div.rateit div.rateit-reset { background-image: url('?cdn=rateit_delete.gif'); }
.loader img { width:31px; height:31px; display:block; position:absolute; top:50%; left:50%; margin:-15px; }
a.hide-error { display:block; font-size:0.8em; float:right; cursor:pointer; }
#the_overlay { position:fixed; top:0; left:0; width:100%; height:100%; background:#eee; z-index:1000; }
#the_overlay div { position:absolute; width:180px; height:40px; top:50%; left:50%; margin:-25px 0 0 -100px; padding:5px 10px; font-size:35px; font-weight:bold; text-align:center; }
#the_page, #config_form, #the_overlay { background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAIAAADZSiLoAAAAH0lEQVQImSXHMQEAMAwCMOrfK0jIjuVL2gLBzyHJtgd7wBdU3Vt/7AAAAABJRU5ErkJggg==); }
#the_header { padding:6px 15px; border-bottom:1px solid #cdcdcd; height:38px; background:linear-gradient(to bottom, #555 0%,#333 100%); }
#the_header h1 { color:#fff; font-size:34px; font-family:"Edit Undo Line"; text-shadow:1px 1px 0 #000; }
#the_menu { position:absolute; top:0px; right:0px; margin:0; }
#the_menu li { list-style:none; display:inline-block; background:linear-gradient(to bottom, #ccc 0%,#aaa 100%); height:20px; border-radius:4px; padding:5px 10px; margin:10px 10px 0px 0px; font-size:18px; cursor:pointer; }
#the_menu li:hover { background:linear-gradient(to bottom, #aaa 0%,#ccc 100%); text-shadow:1px 1px 0 #fff; }
#config_form p { margin-bottom:5px; }
#config_form label { display:inline-block; min-width:140px; text-align:right; font-weight:bold; }
#the_page { padding:20px; }
#the_footer { background:#EAEAEA; border-top:1px solid #cdcdcd; padding:10px; clear:both; }
#resetAll { float:right; cursor:pointer; margin-top:2px; font-weight:bold; }
#resetAll img { vertical-align:bottom; }
#list { table-layout:fixed; word-wrap:break-word; }
#list tr.dead td { background:linear-gradient(to bottom, #ffe4dd 0%,#ffaaaa 100%); }
#list tfoot { background:url('http://code.jquery.com/ui/1.10.2/themes/smoothness/images/ui-bg_glass_75_dadada_1x400.png') repeat-x scroll center center; }
#list tfoot input { width:80%; }
#list tfoot .search_input, #list tfoot select option:first-child { color:#999; }
#list tfoot select.search_input option:not(:first-child) { color:#222; }
#list tr td.right { text-align:right; }
#list tr td.x720p { color:green; }
#list tr td.x1080p { color:red; }
#list tr td.xvostfr { color:blue; }
#list tr td.xmulti { color:gold; }
#list tr td.visitor { font-family:"Visitor",monospace; font-size:16px; line-height:12px; }
#tmdb { padding:0.5em; }
#tmdb h4 { margin:10px 0 5px 0; }
#tmdb .list .item { display:inline-block; vertical-align:top; width:170px; padding:5px; margin:5px; border:1px dotted #aaa; border-radius:5px; cursor:pointer; z-index:1000; }
#tmdb .list .item:hover { background:#FCFBF4; border:1px solid #FCEFA1; z-index:2000; box-shadow:0px 0px 10px 000; }
#tmdb .list .title h2 { color:#222; }
#tmdb .list .title h3 { font-size:0.8em; color:#666; font-style:italic; }
#tmdb .list img { display:block; margin:3px auto; }
#tmdb .card { position:relative; }
#tmdb .card .collumn { float:left; }
#tmdb .card .poster-roller { position:absolute; display:none; left:210px; top:0; width:565px; height:410px; z-index:1000; background-size:cover; border-radius:8px; }
#tmdb .card .poster { position:relative; width:200px; margin-bottom:5px; cursor:pointer; }
#tmdb .card .poster img { display:block; width:200px; min-height:280px; border-radius:5px; background:#ccc; }
#tmdb .card .poster .tile { background:url(data:image/gif;base64,R0lGODlhAgACAIABAHOf4QAAACH5BAEAAAEALAAAAAACAAIAAAICRF4AOw==); position:absolute; display:none; left:0; top:0; width:100%; height:140px; z-index:1000; }
#tmdb .card .content { margin:0 5px 0 210px; }
#tmdb .card .title { padding-bottom:5px; margin-bottom:5px; border-bottom:1px solid #ccc; }
#tmdb .card .title h2 { color:#222; font-size:1.5em; vertical-align:middle; display:inline-block; }
#tmdb .card .title h3 { font-size:0.9em; color:#666; font-style:italic; vertical-align:middle; display:inline-block; }
#tmdb .card .content p { text-align:justify; }
#tmdb .card .genres .item { display:inline-block; position:relative; background:#8A9738 url('?cdn=tag.png') no-repeat 3px center; padding:3px 8px 5px 22px; border-radius:5px; margin:0 3px 2px 3px; color:#fff; }
#tmdb .card .genres .item:hover { border:none; text-shadow:none; top:-4px; box-shadow:1px 1px 0px #444; }
#tmdb .card .cast .item { display:inline-block; width:210px; margin-bottom:4px; }
#tmdb .card .cast .item img { float:left; border-radius:5px; margin-right:4px; width:45px; min-height:55px; background:url('?cdn=no-profile.png') center center; }
#tmdb .card .remove { float:right; background:#9B2525 url('?cdn=film_delete.png') no-repeat 3px center; padding:3px 8px 5px 22px; border-radius:5px; color:#fff; font-size:0.8em; opacity:0.6; }
#tmdb .card .remove:hover { opacity:1; }
@media screen and (max-width:780px) {
#the_header { height:19px; }
#the_header h1 { font-size:17px; letter-spacing:2px; }
#the_menu { display:none; }
#the_page { padding:5px; }
}
</style>
</head>
<body>
<div id="the_overlay"><div class="ui-state-default ui-corner-all">Loading...</div></div>
<div id="tmdb"></div>
<div id="date" title="Last view date">
<input type="text" tabindex="-1">
<button class="ui-button ui-widget ui-state-default ui-corner-all ui-button-text-only">
<span class="ui-button-text" style="padding:0.4em;">
<span class="ui-icon ui-icon-calendar" style="float:left;"></span>
Today</span>
</button>
</div>
<div id="the_header">
<h1>My Movie Database (<?php echo count($db_movies); ?>)</h1>
<ul id="the_menu">
<?php if ($conf['is_windows']) : ?><li><a href="?force_check" class="no-style">Update now</a></li><?php endif; ?>
<?php if ($dead_movies) : ?><li><a href="?delete_dead" class="no-style">Delete dead (<?php echo $dead_movies; ?>)</a></li><?php endif; ?>
<li class="open_config">Configuration</li>
<li class="open_about">About</li>
</ul>
</div> <!-- the_header -->
<div id="config_form" title="Configuration">
<form method="post" action="">
<p>
<label for="main_path">Path :</label>
<input type="text" name="main_path" id="main_path" value="<?php echo $conf['main_path']; ?>">
</p>
<p>
<label for="tmdb_api_key">TMDb API key :</label>
<input type="text" name="tmdb_api_key" id="tmdb_api_key" value="<?php echo $conf['tmdb_api_key']; ?>" size="32" maxlength="32">
<a href="http://docs.themoviedb.apiary.io/" target="_blank" class="ui-state-default ui-corner-all ui-my-button"><span class="ui-icon ui-icon-extlink"></span></a>
</p>
<p>
<label for="allowed_extensions">File types :</label>
<select id="allowed_extensions" name="allowed_extensions[]" multiple="multiple" size="5">
<?php
foreach ($all_extensions as $ext)
{
echo '
<option value="'.$ext.'" '.(in_array($ext, $conf['allowed_extensions'])?'selected':null).'>'.$ext.'</option>';
}
?>
</select>
</p>
<p>
<label for="check_period">Auto-update :</label>
<input type="text" name="check_period" id="check_period" value="<?php echo $conf['check_period']; ?>" size="2" maxlength="3">
(hours)
</p>
<input type="hidden" name="save_config" value="1">
</form>
</div> <!-- config_form -->
<div id="the_page">
<table id="list">
<thead><tr>
<th></th>
<th>Name</th>
<th></th>
<th>Note</th>
<th>Last view</th>
<th>HD</th>
<th>Language</th>
<th>Filesize</th>
<th>Format</th>
<th>Add date</th>
</tr></thead> <!-- table header -->
<tbody>
<?php
foreach ($db_movies as $movie)
{
echo '
<tr '.($movie['dead']?'class="dead"':null).'>
<td>
<img class="tmdb pointer" data-id="'.$movie['id'].'" data-tmdb-id="'.$movie['tmdb_id'].'"
src="?cdn='.(empty($movie['tmdb_id'])?'help':'information').'.png">
</td>
<td>'.$movie['name'].'</td>
<td>'.$movie['filename'].'</td>
<td><div class="rate" data-rateit-value="'.$movie['rate'].'" data-id="'.$movie['id'].'"></div></td>
<td>
<span>'.format_date($movie['last_view'], 'Y-m-d H:i:s', 'd/m/Y', 'never').'</span>
<img class="set-date pointer" data-id="'.$movie['id'].'" src="?cdn=date_add.png">
</td>
<td class="x'.$movie['resolution'].'">'.filter_res_lang($movie['resolution']).'</td>
<td class="x'.$movie['language'].'">'.filter_res_lang($movie['language']).'</td>
<td>'.($movie['filesize']*1024).'</td>
<td>'.strtoupper(get_extension($movie['filename'])).'</td>
<td>'.format_date($movie['added_date'], 'Y-m-d H:i:s', 'd/m/Y').'</td>
</tr>';
}
?>
</tbody> <!-- table body -->
<tfoot><tr>
<td></td>
<td>
<div class="icon-delete reset"></div>
<input type="text" placeholder="Name">
</td>
<td></td>
<td><div class="rate-search"></div></td>
<td>
<select class="search_input">
<option value="">all</option>
<option value="[0-9]*/[0-9]*/[0-9]*">seen</option>
<option value="never">not seen</option>
</select>
</td>
<td>
<select class="search_input">
<option value="">all</option>
<option value="^$">SD</option>
<option value="720p">720p</option>
<option value="1080p">1080p</option>
</select>
</td>
<td>
<select class="search_input">
<option value="">all</option>
<option value="^$">VF</option>
<option value="vostfr">VOSTFR</option>
<option value="multi">MULTI</option>
</select>
</td>
<td></td>
<td>
<select class="search_input">
<option value="">all</option>
<?php
foreach ($conf['allowed_extensions'] as $ext)
{
echo '
<option value="'.strtoupper($ext).'">'.strtoupper($ext).'</option>';
}
?>
</select>
</td>
<td></td>
</tr></tfoot> <!-- table filters -->
</table>
</div> <!-- the_page -->
<div id="the_footer">
Copyright &copy; 2013 <a href="http://strangeplanet.fr">Damien "Mistic" Sorel</a>
</div> <!-- the_footer -->
<div id="the_about" title="About">
<dl>
<dt>Usage</dt>
<dd>
<b>Synchronization :</b><br>
Synchronization is performed at startup every X hours (configurable in the configuration form), you can force synchronization at any moment by clicking on <i>Update now</i>.<br>
<br>
Once synchronized you can use the program even if your movies directory is not available, but you will get an warning at each startup and you will not be able to perform a new synchronization until the directory is available again.<br>
<br>
When deleting movies files, the corresponding movies remain in the software (with a red background), you can clean the database by clicking on <i>Delete dead</i>.
<dd>
<b>Movies info :</b><br>
In order to be able to display info from <a href="http://themoviedb.org" target="_blank">The Movie Database</a> you must create an account and get an API key, once registered, enter your API key in the configuration form.
</dd>
<dd>
<b>Local storage :</b><br>
Display configuration like current page and search filters are saved in the local storage of your browser, you can reset it by clicking on <img src="?cdn=cross.png">.
</dd>
<dd>
<b>Smartphones :</b><br>
The number of visible columns reduces when the the window shrinks, thus only the <i>Name</i> column is visible on smartphones.
</dd>
<dt>Filenames</dt>
<dd>
Files must respect a strict naming convention in order to be compatible with this software. The pattern is :
<pre style="font-family:monospace;">([A-Z]{1}.)? <b>.+</b> (VOSTFR|MULTi)? (720p|1080p)?</pre>
<ul>
<li>The optional leading letter is used to have a natural sort of files, it must be the first letter of the word following &laquo;The&raquo;, &laquo;Le&raquo; or &laquo;L'&raquo; if present.</li>
<li>The tags &laquo;VOSTFR&raquo;, &laquo;MULTi&raquo;, &laquo;720p&raquo; and &laquo;1080p&raquo; are optional and the order is not important.</li>
</ul>
<b>Examples :</b>
<ul>
<li>Cosmopolis.avi</li>
<li>Imaginaerum 720p VOSTFR.mp4</li>
<li>H. The Hobbit - An Unexpected Journey MULTi 1080p.mkv</li>
</ul>
</dd>
<dt>Dependencies</dt>
<dd>
This software uses the following librairies :
<ul>
<li><a href="http://jquery.com" target="_blank">jQuery</a></li>
<li><a href="http://jqueryui.com" target="_blank">jQuery UI</a></li>
<li><a href="http://datatables.net" target="_blank">jQuery DataTables</a></li>
<li><a href="http://erichynds.com/blog/jquery-ui-multiselect-widget" target="_blank">jQuery UI MultiSelect</a></li>
<li><a href="http://rateit.codeplex.com" target="_blank">jQuery rateIt</a></li>
<li><a href="https://github.com/glamorous/TMDb-PHP-API" target="_blank">TMDb-PHP-API</a></li>
</ul>
Images from :
<ul>
<li><a href="http://famfamfam.com" target="_blank">famfamfam</a></li>
<li><a href="http://loadinggif.com" target="_blank">LoadingGIF</a></li>
<li><a href="http://themoviedb.org" target="_blank">The Movie Database</a></li>
</ul>
Fonts :
<ul>
<li><a href="http://dafont.com/visitor.font" target="_blank">Visitor</a></li>
<li><a href="http://dafont.com/edit-undo-line.font" target="_blank">Edit Undo Line</a></li>
</ul>
<br>
Movies info provided by <a href="http://themoviedb.org" target="_blank">The Movie Database</a>.
</dd>
</dl>
</div> <!-- the_about -->
<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<script src="http://code.jquery.com/ui/1.10.2/jquery-ui.min.js"></script>
<script src="http://ajax.aspnetcdn.com/ajax/jquery.dataTables/1.9.4/jquery.dataTables.min.js"></script>
<script src="?cdn=multiselect.js"></script>
<script src="?cdn=rateit.js"></script>
<script>
var sConfigName = '<?php echo BASENAME; ?>_config',
sAjaxURL = '<?php echo basename(__FILE__); ?>';
//-----------------------------------------------------
// FUNCTIONS
//-----------------------------------------------------
function naturalSort (a, b) {
var re = /(^-?[0-9]+(\.?[0-9]*)[df]?e?[0-9]?$|^0x[0-9a-f]+$|[0-9]+)/gi,
sre = /(^[ ]*|[ ]*$)/g,
dre = /(^([\w ]+,?[\w ]+)?[\w ]+,?[\w ]+\d+:\d+(:\d+)?[\w ]?|^\d{1,4}[\/\-]\d{1,4}[\/\-]\d{1,4}|^\w+, \w+ \d+, \d{4})/,
hre = /^0x[0-9a-f]+$/i,
ore = /^0/,
i = function(s) { return naturalSort.insensitive && (''+s).toLowerCase() || ''+s },
// convert all to strings strip whitespace
x = i(a).replace(sre, '') || '',
y = i(b).replace(sre, '') || '',
// chunk/tokenize
xN = x.replace(re, '\0$1\0').replace(/\0$/,'').replace(/^\0/,'').split('\0'),
yN = y.replace(re, '\0$1\0').replace(/\0$/,'').replace(/^\0/,'').split('\0'),
// numeric, hex or date detection
xD = parseInt(x.match(hre)) || (xN.length != 1 && x.match(dre) && Date.parse(x)),
yD = parseInt(y.match(hre)) || xD && y.match(dre) && Date.parse(y) || null,
oFxNcL, oFyNcL;
// first try and sort Hex codes or Dates
if (yD)
if ( xD < yD ) return -1;
else if ( xD > yD ) return 1;
// natural sorting through split numeric strings and default strings
for(var cLoc=0, numS=Math.max(xN.length, yN.length); cLoc < numS; cLoc++) {
// find floats not starting with '0', string or 0 if not defined (Clint Priest)
oFxNcL = !(xN[cLoc] || '').match(ore) && parseFloat(xN[cLoc]) || xN[cLoc] || 0;
oFyNcL = !(yN[cLoc] || '').match(ore) && parseFloat(yN[cLoc]) || yN[cLoc] || 0;
// handle numeric vs string comparison - number < string - (Kyle Adams)
if (isNaN(oFxNcL) !== isNaN(oFyNcL)) { return (isNaN(oFxNcL)) ? 1 : -1; }
// rely on string comparison if different types - i.e. '02' < 2 != '02' < '2'
else if (typeof oFxNcL !== typeof oFyNcL) {
oFxNcL += '';
oFyNcL += '';
}
if (oFxNcL < oFyNcL) return -1;
if (oFxNcL > oFyNcL) return 1;
}
return 0;
}
function formatBytes(bytes, precision) {
precision = precision || 2;
bytes = Math.max(bytes, 0);
var units = new Array("o", "Kio", "Mio", "Gio", "Tio"),
pow = Math.floor((bytes ? Math.log(bytes) : 0) / Math.log(1024));
pow = Math.min(pow, units.length - 1);
bytes /= (1 << (10 * pow));
return bytes.toFixed(precision) + " " + units[pow];
}
function error(sError, sErrorID) {
if (sErrorID) {
sError+= '<br><a class="hide-error" data-id="'+ sErrorID +'">Don\'t show again</a>';
}
sError = '<div title="Error"><span class="ui-icon ui-icon-alert" style="float:left;margin-right:0.3em;"></span>'+ sError +'</div>';
$(sError).dialog({
modal: true,
draggable: false,
dialogClass: 'ui-state-error',
show: 'shake',
hide: 'fade',
minHeight: 0,
close: function() { $(this).dialog('destroy'); },
}).on('click', '.hide-error', function() {
$.ajax({
method: 'POST',
url: sAjaxURL,
data: {
action: 'hide_error',
error: sErrorID,
},
});
$(this).parent('div').dialog('destroy');
});
}
function setRate(fRate, $parent) {
$parent.attr('data-rateit-value', fRate);
$.ajax({
method: 'POST',
url: sAjaxURL,
dataType: 'json',
data: {
action: 'rating',
rate: fRate,
movie_id: $parent.attr('data-id'),
},
success: function(oData) {
if (oData.stat == 'error') {
error(oData.result);
}
},
error: function() {
error('Unexpected error.');
},
});
}
naturalSort.insensitive = true;
var fAsc = function(a, b) { return a<b ? -1 : (a>b ? 1 : 0); }
var fDesc = function(a, b) { return a<b ? 1 : (a>b ? -1 : 0); }
$.extend($.fn.dataTableExt.oSort, {
'natural-asc': function(a, b) { return naturalSort(a,b); },
'natural-desc': function(a, b) { return naturalSort(a,b) * -1; },
'date-uk-pre': function(a) {
var ukDatea = a.split('/');
return ukDatea[2] + ukDatea[1] + ukDatea[0];
},
'date-uk-asc': fAsc, 'date-uk-desc': fDesc,
'720-1080-pre': function(a) {
return a=='1080p' ? 2 : (a=='720p' ? 1 : 0);
},
'720-1080-asc': fAsc, '720-1080-desc': fDesc,
'rateit-pre': function(a) {
return $('<div>'+a+'</div>').children('.rate').attr('data-rateit-value');
},
'rateit-asc': fAsc, 'rateit-desc': fDesc,
});
$.extend($.fn.dataTableExt.ofnSearch, {
'rateit': function(a) {
return $('<div>'+a+'</div>').children('.rate').attr('data-rateit-value');
},
});
//-----------------------------------------------------
// ABOUT DIALOG
//-----------------------------------------------------
var $about_dialog = $('#the_about').dialog({
autoOpen: false,
resizable: false,
width: 600,
maxHeight: 630,
show: 'clip',
hide: 'clip',
buttons: [{
text: 'Close',
click: function() { $(this).dialog('close'); },
}],
});
$('.open_about').on('click', function() {
$about_dialog.dialog('open');
});
//-----------------------------------------------------
// CONFIG DIALOG
//-----------------------------------------------------
var $config_dialog = $('#config_form').dialog({
autoOpen: false,
resizable: false,
modal: true,
width: 520,
show: 'clip',
hide: 'clip',
close: function() { $(this).find('form')[0].reset(); },
buttons: [{
text: 'Cancel',
click: function() { $(this).dialog('close'); },
}, {
text: 'Save',
click: function() { $(this).find('form').submit(); },
}],
});
$('.open_config').on('click', function() {
$config_dialog.dialog('open');
});
$('#allowed_extensions').multiselect({
selectedList: 5,
header: false,
click: function(e, ui) {
if ($('#allowed_extensions').multiselect('getChecked').length == 0) {
$('#allowed_extensions').multiselect('close');
error('You must select at least one file type.');
if (e.currentTarget.nodeName.toUpperCase() == 'INPUT') {
$(e.currentTarget).trigger('click');
}
}
},
});
//-----------------------------------------------------
// DATE DIALOG
//-----------------------------------------------------
var $date_dialog = $('#date').dialog({
autoOpen: false,
resizable: false,
modal: true,
width: 320,
show: 'clip',
hide: 'clip',
buttons: [{
text: 'Cancel',
click: function() { $(this).dialog('close'); },
}, {
text: 'Save',
click: function() {
var $this = $(this);
$.ajax({
method: 'POST',
url: sAjaxURL,
dataType: 'json',
data: {
action: 'view_date',
movie_id: $this.data('id'),
date: $this.children('input').val(),
},
success: function(oData) {
if (oData.stat == 'error') {
error(oData.result);
}
else {
var sDate = oData.result=='' ? 'never' : oData.result;
$('.set-date[data-id="'+ $this.data('id') +'"]').prev('span').html(sDate);
$this.dialog('close');
}
},
error: function() {
error('Unexpected error.');
},
});
},
}],
});
$date_dialog.children('input').datepicker({
showAnim: 'slideDown',
dateFormat: 'dd/mm/yy',
});
$date_dialog.children('button').on('click', function() {
$date_dialog.children('input').datepicker('setDate', new Date());
});
$('.set-date').on('click', function() {
var sDate = $(this).prev('span').html();
$date_dialog.data('id', $(this).attr('data-id'));
$date_dialog.children('input').val(date=='never' ? null : sDate);
$date_dialog.dialog('open');
});
//-----------------------------------------------------
// DETAILS DIALOG
//-----------------------------------------------------
var $tmdb_dialog = $('#tmdb').dialog({
autoOpen: false,
show: 'clip',
hide: 'clip',
title: 'Movie details',
width: 800,
height: 530,
resizable: false,
buttons: [{
text: 'Close',
click: function() { $(this).dialog('close'); },
}],
}).on('reset', function() {
$(this)
.addClass('loader')
.html('<img src="?cdn=loader.gif">');
});
$('.tmdb').on('click', function(e, sSearch, bReset) {
$tmdb_dialog.trigger('reset').dialog('open');
var iId = $(this).attr('data-id');
sSearch = sSearch || null;
bReset = bReset || false;
$.ajax({
method: 'POST',
url: sAjaxURL,
dataType: 'json',
data: {
action: 'tmdb',
movie_id: iId,
tmdb_id: $(this).attr('data-tmdb-id'),
search: sSearch,
reset: bReset,
},
success: function(oData) {
var sHtml = '';
// error
if (oData.stat == 'error') {
$tmdb_dialog.dialog('close');
error(oData.result);
}
// movies list
else if (oData.stat == 'list') {
if (oData.result.length == 0) {
sHtml =
'<div class="search">'+
'<h4>No result !</h4>';
}
else {
sHtml =
'<div class="list">'+
'<h4>'+ oData.result.length +' result(s), please choose the right movie :</h4>';
for (var i=0; i<oData.result.length; i++) {
var oMovie = oData.result[i];
sHtml+=
'<div class="item" data-id="'+ iId +'" data-tmdb-id="'+ oMovie.tmdb_id +'">'+
'<div class="title">'+
'<h2><a href="http://www.themoviedb.org/movie/'+ oMovie.tmdb_id +'" target="_blank">'+ oMovie.title +'</a></h2>';
if (oMovie.date) sHtml+=
'<h3>('+ oMovie.date +')</h3>';
sHtml+=
'</div>'+
'<img src="'+ oMovie.poster +'">'+
'</div>';
}
sHtml+=
'</div>'+
'<div class="search">'+
'<h4>Don\'t find what you are looking for ?</h4>';
}
sHtml+=
'Try another name : '+
'<input type="text" size="30" class="tmdb-search-input"> '+
'<input type="submit" value="Search" class="tmdb-search" data-id="'+ iId +'">'+
'</div>';
$tmdb_dialog.removeClass('loader').html(sHtml);
}
// movie card
else if (oData.stat == 'card') {
var oMovie = oData.result,
fVote = Math.round(oMovie.vote*2)/2;
sHtml =
'<div class="card">'+
'<div class="poster-roller" style="background-image:url(\''+ oMovie.poster_full +'\');"></div>'+
'<div class="collumn">'+
'<div class="poster">'+
'<div class="tile"></div>'+
'<img src="'+ oMovie.poster +'">'+
'</div>'+
'<i>Runtime: '+ oMovie.runtime +'</i><br>'+
'<a href="http://www.themoviedb.org/movie/'+ oMovie.tmdb_id +'" target="_blank">themoviedb.org</a> - '+
'<a href="http://www.imdb.com/title/'+ oMovie.imdb_id +'" target="_blank">imdb.com</a>'+
'</div>'+
'<div class="content">'+
'<div class="title">'+
'<h2>'+ oMovie.title +'</h2> '+
'<h3>('+ oMovie.date +')</h3> '+
'</div>'+
'<div class="remove pointer" data-id="'+ iId +'">This is not the right movie ?</div>'+
'<div class="tmdb-rate">';
for (var i=1; i<=Math.floor(fVote); i++) sHtml+= '<img src="?cdn=star-on.png">';
if (fVote==Math.floor(fVote)+1) sHtml+= '<img src="?cdn=star-on.png">';
else if (fVote==Math.floor(fVote)+0.5) sHtml+= '<img src="?cdn=star-half.png">';
else if (fVote==Math.floor(fVote)) sHtml+= '<img src="?cdn=star-off.png">';
for (var i=Math.ceil(fVote+0.1)+1; i<=10; i++) sHtml+= '<img src="?cdn=star-off.png">';
sHtml+= ' '+ oMovie.fVote +'/10'+
'</div>'+
'<h4>Overview</h4>'+
'<p>'+ oMovie.overview +'</p>'+
'<h4>Genres</h4>'+
'<div class="genres">';
for (var i=0; i<oMovie.genres.length; i++) {
var oGenre = oMovie.genres[i];
sHtml+=
'<a class="item" href="http://www.themoviedb.org/genre/'+ oGenre.id +'" target="_blank">'+ oGenre.name +'</a>';
}
sHtml+=
'</div>'+
'<h4>Cast</h4>'+
'<div class="cast">';
for (var i=0; i<oMovie.cast.length; i++) {
var oCast = oMovie.cast[i];
sHtml+=
'<div class="item">'+
'<img src="'+ oCast.photo +'">'+
'<a href="http://www.themoviedb.org/person/'+ oCast.id +'" target="_blank">'+ oCast.name +'</a><br>';
if (oCast.character) sHtml+=
'<i>as '+ oCast.character +'</i>';
sHtml+=
'</div>';
}
sHtml+=
'</div>'+
'</div>'+
'</div>';
$tmdb_dialog.removeClass('loader').html(sHtml);
var $block = $('#tmdb .card');
// poster scroll on mouseover
// can delegate mouse events to document
$('#tmdb .card .poster').on({
mouseenter: function() {
$('.poster .tile', $block).fadeIn('fast');
$('.poster-roller', $block).fadeIn('fast');
$('.content', $block).animate({opacity:0});
},
mouseleave: function() {
$('.poster .tile', $block).stop().fadeOut('fast');
$('.poster-roller', $block).stop().fadeOut('fast');
$('.content', $block).stop().animate({opacity:1});
},
mousemove: function(e) {
var iPosterHeight = $('img', this).height(),
iFullPosterHeight = iPosterHeight*$('.poster-roller', $block).width()/$('img', this).width();
var y = e.pageY-$(this).offset().top-70;
y = Math.max(0, Math.min(iPosterHeight-140, y));
$('.poster .tile', $block).css('top', y);
var y2 = Math.round(y*iFullPosterHeight/iPosterHeight);
y2 = Math.max(0, Math.min(iFullPosterHeight-$('.poster-roller', $block).height(), y2));
$('.poster-roller', $block).css('background-position', 'center -'+ y2 +'px');
}
});
}
},
error: function() {
$tmdb_dialog.dialog('close');
error('Unexpected error.');
},
});
});
// prevent click propagation from links to main container
$(document).on('click', '#tmdb .list .item a', function(e) {
e.stopPropagation();
return true;
});
// change tmdb_id and reload ajax block when choosing a movie
$(document).on('click', '#tmdb .list .item', function() {
var $object = $('.tmdb[data-id="'+ $(this).attr('data-id') +'"]');
$object.attr('data-tmdb-id', $(this).attr('data-tmdb-id'));
$object.attr('src', '?cdn=information.png');
$object.trigger('click', [ null, false ]);
});
// reload the ajax block with a new search string
$(document).on('click', '#tmdb .search .tmdb-search', function() {
var $object = $('.tmdb[data-id="'+ $(this).attr('data-id') +'"]');
$object.trigger('click', [ $(this).prev('.tmdb-search-input').val(), false ]);
});
// dissociate a movie and reload the ajax block
$(document).on('click', '#tmdb .card .remove', function() {
var $object = $('.tmdb[data-id="'+ $(this).attr('data-id') +'"]');
$object.attr('data-tmdb-id', null);
$object.attr('src', '?cdn=help.png');
$object.trigger('click', [ null, true ]);
});
//-----------------------------------------------------
// MISC
//-----------------------------------------------------
// personal button using ui states
$('.ui-my-button').on({
mouseenter: function() { $(this).addClass('ui-state-error'); },
mouseleave: function() { $(this).removeClass('ui-state-error'); },
});
// rate stars for each movie
$('.rate').rateit({
step: 0.5,
}).on({
rated: function() { setRate($(this).rateit('value'), $(this)); },
reset: function() { setRate(0, $(this)); },
});
// ability to close dialogs by clicking on the overlay
$(document).on('click', '.ui-widget-overlay', function() {
$(this).next('.ui-dialog').children('.ui-dialog-content').dialog('close');
});
//-----------------------------------------------------
// DATATABLE
//-----------------------------------------------------
var oTable = $('#list').dataTable({
bJQueryUI: true,
sDom: '<"H"lr<"#resetAll">>t<"F"ip>',
aaSorting: [ [1,'asc'] ],
sPaginationType: 'full_numbers',
aLengthMenu: [ [10, 25, 50, 75, 100, -1], [10, 25, 50, 75, 100, 'All'] ],
iDisplayLength: 25,
bAutoWidth: false,
aoColumns: [
{ bSortable: false, sWidth: '20px', <?php if (empty($conf['tmdb_api_key'])) echo 'sWidth: \'0px\', sClass: \'hide\','; ?> },
{ iDataSort: 2, sType: 'natural', sWidth: '100%', }, // col 1 is sorted by col 2
{ sWidth: '0px', sClass: 'hide', },
{ sType: 'rateit', sWidth: '110px', },
{ sType: 'date-uk', sWidth: '90px', sClass: 'right', },
{ sType: '720-1080', sWidth: '50px', sClass: 'visitor', },
{ sWidth: '80px', sClass: 'visitor', },
{ sType: 'numeric', sWidth: '80px', mRender: function(data,type) {
return type=='display' ? formatBytes(data) : data; },
},
{ sWidth: '60px', sClass: 'visitor', },
{ sType: 'date-uk', sWidth: '80px', },
],
fnInitComplete: function(oSettings, json) {
$('#the_overlay').fadeOut(function() { $(this).remove(); });
},
bStateSave: true,
fnStateSave: function(oSettings, oData) {
window.localStorage.setItem(sConfigName, JSON.stringify(oData));
},
fnStateLoad: function(oSettings, oData) {
return JSON.parse(window.localStorage.getItem(sConfigName));
},
}).css('width','');
// responsive
$(window).on('resize ready', function() {
var iWidth = $(window).width();
oTable.fnSetColumnVis(0, iWidth > 500);
oTable.fnSetColumnVis(3, iWidth > 620);
oTable.fnSetColumnVis(4, iWidth > 720);
oTable.fnSetColumnVis(5, iWidth > 770);
oTable.fnSetColumnVis(6, iWidth > 860);
oTable.fnSetColumnVis(7, iWidth > 950);
oTable.fnSetColumnVis(8, iWidth > 1010);
oTable.fnSetColumnVis(9, iWidth > 1100);
});
// search inputs
$('tfoot input').on('keyup', function() {
oTable.fnFilter($(this).val(), $(this).parent('td').index());
});
$('tfoot .reset').on('click', function() {
$(this).next('input').val('').trigger('keyup');
});
$('tfoot select').on('change', function() {
oTable.fnFilter($(this).val(), $(this).parent('td').index(), true);
if ($(this).val() == '') {
$(this).addClass('search_input');
}
else {
$(this).removeClass('search_input');
}
});
$('tfoot .rate-search').rateit({
step: 0.5,
}).on({
rated: function() { oTable.fnFilter($(this).rateit('value'), $(this).parents('td').index()); },
reset: function() { oTable.fnFilter('', $(this).parents('td').index()); },
});
// reset all
$('#resetAll').html('<img src="?cdn=cross.png" alt="X"> Reset display')
.on('click', function() {
window.localStorage.removeItem(sConfigName);
window.location.reload();
});
// populate filters
var oConfig = JSON.parse(window.localStorage.getItem(sConfigName));
if (oConfig && oConfig.aoSearchCols != undefined) {
jQuery.each(oConfig.aoSearchCols, function(idx, oRule) {
if (oRule.sSearch != undefined) {
var $input = $('tfoot tr td').eq(idx).children('input');
if ($input.length) {
$input.val(oRule.sSearch);
return true;
}
var $select = $('tfoot tr td').eq(idx).children('select');
if ($select.length) {
$select.val(oRule.sSearch).removeClass('search_input');
return true;
}
var $raty = $('tfoot tr td').eq(idx).children('div.rate-search');
if ($raty.length) {
$raty.rateit('value', oRule.sSearch);
return true;
}
}
});
}
<?php
$conf['is_windows'] = false;
if (isset($error_path) && $conf['error_path'])
{
echo 'error("Path <b>'.$error_path.'</b> doesn\'t exist.", "path");';
}
if (!$conf['is_windows'] && $conf['error_is_windows'])
{
echo 'error("This application can only run on Windows.", "is_windows");';
}
if (!function_exists('curl_init'))
{
echo 'error("CURL extension not available. Please activate it.");';
}
if ($conf['first_launch'])
{
echo '$config_dialog.dialog("open");';
}
?>
</script>
</body>
</html>
<?php
// +-----------------------------------------------------------------------+
// | This program is free software; you can redistribute it and/or modify |
// | it under the terms of the GNU General Public License as published by |
// | the Free Software Foundation |
// | |
// | This program is distributed in the hope that it will be useful, but |
// | WITHOUT ANY WARRANTY; without even the implied warranty of |
// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
// | General Public License for more details. |
// | |
// | You should have received a copy of the GNU General Public License |
// | along with this program; if not, write to the Free Software |
// | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, |
// | USA. |
// +-----------------------------------------------------------------------+
?>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment