-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathgenerator.php
190 lines (170 loc) · 7.5 KB
/
generator.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
<?php
/**
* Create the thumbnails for the Video
* $opts:
* -input: The input file to be used.
* -name: The output name to be used. (default video name)
* -output: The output directory where the thumbnails and vtt file will be saved
* -timespan: The time span (in seconds) between each thumbnail (default, {$params['timespan']})
* -width: The max width of the thumbnail (default, {$params['thumbWidth']})
* -verbose: Verbose - don't coalesce the thumbnails into one image (boolean)
* -poster: Generate poster image from a random frame in the video (boolean)
* -delete: Delete any previous thumbnails that match before generating new images (boolean)
* -videotypes: Accepted video content types. Default: array('video/mp4') (Array)
*
*/
function createthumbnail($opts) {
$params = [
'library' => 'ffmpeg', // the ffmpeg command - full path if needs be
'input' => null, // input video file - specified as command line parameter
'output' => __DIR__, // The output directory
'timespan' => 10, // seconds between each thumbnail
'thumbWidth' => 120, // thumbnail width
'spriteWidth' => 10, // number of thumbnails per row in sprite sheet
'videotypes' => array('video/mp4')
];
// process input parameters
if (isset($opts['library'])) {
$params['library'] = $opts['library'];
}
$params['input'] = escapeshellarg($opts['input']);
if (isset($opts['output'])) {
$params['output'] = realpath($opts['output']);
}
if (isset($opts['timespan']) && (int)$opts['timespan']) {
$params['timespan'] = $opts['timespan'];
}
if (isset($opts['width']) && (int)$opts['width']) {
$params['thumbWidth'] = $opts['width'];
}
if (isset($opts['videotypes']) && is_array($opts['videotypes'])) {
$params['videotypes'] = $opts['videotypes'];
}
$commands = [
'details' => $params['library'] . ' -i %s 2>&1',
'poster' => $params['library'] . ' -ss %d -i %s -y -vframes 1 "%s/%s-poster.jpg" 2>&1',
'thumbs' => $params['library'] . ' -ss %0.04f -i %s -y -an -sn -vsync 0 -q:v 5 -threads 1 '
. '-vf scale=%d:-1,select="not(mod(n\\,%d))" "%s/thumbnails/%s-%%04d.jpg" 2>&1'
];
// sanity checks
if (!is_readable($opts['input'])) {
if (filter_var($opts['input'], FILTER_VALIDATE_URL)) {
if (checkurl($opts['input'], $params['videotypes']) === false) {
throw new ThumbnailWebVttException("Cannot read the url file '{$opts['input']}'");
}
}
else {
throw new ThumbnailWebVttException("Cannot read the input file '{$opts['input']}'");
}
}
if (!is_writable($params['output'])) {
throw new ThumbnailWebVttException("Cannot write to output directory '{$opts['output']}'");
}
if (!file_exists($params['output'] . '/thumbnails')) {
if (!mkdir($params['output'] . '/thumbnails')) {
throw new ThumbnailWebVttException("Could not create thumbnail output directory '{$params['output']}/thumbnails'");
}
}
$details = shell_exec(sprintf($commands['details'], $params['input']));
if ($details === null || !preg_match('/^(?:\s+)?'. $params['library'] .' version ([^\s,]*)/i', $details)) {
throw new ThumbnailWebVttException('Cannot find '. $params['library'] .' - try specifying the path in the $params variable');
}
// determine some values we need
$time = $fps = [];
preg_match('/Duration: ((\d+):(\d+):(\d+))\.\d+, start: ([^,]*)/is', $details, $time);
preg_match('/\b(\d+(?:\.\d+)?) fps\b/', $details, $fps);
$duration = ($time[2] * 3600) + ($time[3] * 60) + $time[4];
$start = $time[5];
$fps = $fps[1];
$name = (isset($opts['name']))? $opts['name'] : strtolower(substr(basename($opts['input']), 0, strrpos(basename($opts['input']), '.')));
// generate random poster if required
if (isset($opts['poster']) && $opts['poster'] === TRUE) {
shell_exec(sprintf($commands['poster'], rand(1, $duration - 1), $opts['input'], $params['output'], $name));
}
// generate all thumbnail images
$filter = function($current, $key, $iterator) use ($name) {
return (
$current->isFile()
&& preg_match("!{$name}-\\d{4}\\.jpg$!", $current->getFilename())
);
};
if (isset($opts['delete']) && $opts['delete'] === TRUE) {
$files = new CallbackFilterIterator(
new FilesystemIterator("{$params['output']}/thumbnails"), $filter
);
foreach ($files as $f) {
unlink($f);
}
}
shell_exec(sprintf($commands['thumbs'],
$start + .0001, $params['input'], $params['thumbWidth'],
$params['timespan'] * $fps, $params['output'], $name
));
$files = array_values(iterator_to_array(
new CallbackFilterIterator(
new FilesystemIterator("{$params['output']}/thumbnails"), $filter
)
));
if (!($total = count($files))) {
throw new ThumbnailWebVttException("Could not find any thumbnails matching '{$params['output']}/thumbnails/{$name}-\\d{4}.jpg'");
}
sort($files, SORT_NATURAL);
// create coalesce image if needs be
if (isset($opts['verbose']) && $opts['verbose'] === FALSE) {
$thumbsAcross = min($total, $params['spriteWidth']);
$sizes = getimagesize($files[0]);
$rows = ceil($total/$thumbsAcross);
$w = $sizes[0] * $thumbsAcross;
$h = $sizes[1] * $rows;
$coalesce = imagecreatetruecolor($w, $h);
}
// generate vtt file, merge thumbnails if needs be
$vtt = "WEBVTT\n\n";
for ($rx = $ry = $s = $f = 0; $f < $total; $f++) {
$t1 = sprintf('%02d:%02d:%02d.000', ($s / 3600), ($s / 60 % 60), $s % 60);
$s += $params['timespan'];
$t2 = sprintf('%02d:%02d:%02d.000', ($s / 3600), ($s / 60 % 60), $s % 60);
if (isset($opts['verbose']) && $opts['verbose'] !== FALSE) {
$vtt .= "{$t1} --> {$t2}\nthumbnails/" . basename($files[$f]);
} else {
if ($f && !($f % $thumbsAcross)) {
$rx = 0;
++$ry;
}
imagecopymerge($coalesce, imagecreatefromjpeg($files[$f]), $rx * $sizes[0], $ry * $sizes[1], 0, 0, $sizes[0], $sizes[1], 100);
$vtt .= sprintf("%s --> %s\n{$name}.jpg#xywh=%d,%d,%d,%d", $t1, $t2, $rx++ * $sizes[0], $ry * $sizes[1], $sizes[0], $sizes[1]);
}
$vtt .= "\n\n";
}
// tidy up
if (isset($opts['verbose']) && $opts['verbose'] === FALSE) {
imagejpeg($coalesce, "{$params['output']}/{$name}.jpg", 75);
for ($s = 0, $f = 0; $f < $total; $f++) {
unlink($files[$f]);
}
rmdir($params['output'] . '/thumbnails');
}
file_put_contents("{$params['output']}/{$name}.vtt", $vtt);
return $params['output'] . '/' . $name .'.vtt';
}
/**
* Check url & Content-Type (eg. 'video/mp4')
*
*/
function checkurl($url, $videotypes) {
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_NOBODY, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
$data = curl_exec($ch);
$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
curl_close($ch);
return ($data !== false && $httpcode === 200 && in_array($contentType, $videotypes));
}
/**
* ThumbnailWebVttException
*
*/
class ThumbnailWebVttException extends Exception {}