ILIAS  trunk Revision v12.0_alpha-1227-g7ff6d300864
MediaObjectManager.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
21namespace ILIAS\MediaObjects;
22
31
33{
34 protected \ilLogger $logger;
38
39 public function __construct(
40 protected InternalDataService $data,
42 protected InternalDomainService $domain,
43 protected \ilMobStakeholder $stakeholder
44 ) {
45 $this->repo = $repo->mediaObject();
46 $this->image_converters = new Images(true);
47 $this->output_options = new ImageOutputOptions();
48 $this->logger = \ilLoggerFactory::getLogger('mob');
49 }
50
51 public function create(
52 int $id,
53 string $title,
54 int $from_mob_id = 0
55 ): void {
56 $this->repo->create(
57 $id,
58 $title,
59 $this->stakeholder,
60 $from_mob_id
61 );
62 }
63
64 public function addLocalDirectory(int $mob_id, string $dir): void
65 {
66 $this->repo->addLocalDirectory($mob_id, $dir);
67 }
68
69 public function addFileFromLegacyUpload(int $mob_id, string $tmp_name, string $target_path = ""): void
70 {
71 $this->repo->addFileFromLegacyUpload($mob_id, $tmp_name, $target_path);
72 }
73
74 public function addFileFromUpload(
75 int $mob_id,
76 UploadResult $result,
77 string $path = "/"
78 ): void {
79 $this->repo->addFileFromUpload($mob_id, $result, $path);
80 }
81
82 public function addFileFromLocal(int $mob_id, string $tmp_name, string $path): void
83 {
84 $this->repo->addFileFromLocal($mob_id, $tmp_name, $path);
85 }
86
87 public function removeLocation(
88 int $mob_id,
89 string $location
90 ): void {
91 $this->repo->removeLocation($mob_id, $location);
92 }
93
94 public function getLocationStream(
95 int $mob_id,
96 string $location
97 ): ZIPStream {
98 return $this->repo->getLocationStream($mob_id, $location);
99 }
100
101 public function getLocationContent(
102 int $mob_id,
103 string $location
104 ): string {
105 return $this->repo->getLocationContent($mob_id, $location);
106 }
107
108 public function addStream(
109 int $mob_id,
110 string $location,
111 FileStream $stream
112 ): void {
113 $this->repo->addStream($mob_id, $location, $stream);
114 }
115
116 public function getLocalSrc(int $mob_id, string $location): string
117 {
118 $src = $this->repo->getLocalSrc(
119 $mob_id,
121 );
122 if ($src === "") { // fallback: old source
123 $path_to_file = \ilObjMediaObject::_getURL($mob_id) . "/" . $location;
124 try {
125 $src = \ilWACSignedPath::signFile($path_to_file);
126 } catch (\Exception $e) {
127 }
128 }
129 return $src;
130 }
131
132 public function hasLocalFile(int $mob_id, string $location): bool
133 {
134 return $this->repo->hasLocalFile($mob_id, $location);
135 }
136
137 public function getContainerResource(
138 int $mob_id
139 ): ?StorableResource {
140 return $this->repo->getContainerResource($mob_id);
141 }
142
143 public function getContainerResourceId(
144 int $mob_id
146 return $this->repo->getContainerResourceId($mob_id);
147 }
148
149 public function getFilesOfPath(
150 int $mob_id,
151 string $dir_path
152 ): array {
153 return $this->repo->getFilesOfPath($mob_id, $dir_path);
154 }
155
156 public function getInfoOfEntry(
157 int $mob_id,
158 string $path
159 ): array {
160 return $this->repo->getInfoOfEntry(
161 $mob_id,
162 $path
163 );
164 }
165
166 public function deliverEntry(
167 int $mob_id,
168 string $path
169 ): void {
170 $this->repo->deliverEntry($mob_id, $path);
171 }
172
173
174 public function generatePreview(
175 int $mob_id,
176 string $location,
177 bool $local,
178 string $format,
179 int $sec = 1,
180 string $target_location = "mob_vpreview.png"
181 ): void {
182
183 $is_image = is_int(strpos($format, "image/"));
184 $is_video = in_array($format, ["video/mp4", "video/webm"]);
185
186 if ($local) {
187 if ($is_image) {
189 $image_quality = 60;
190
191 // the zip stream is not seekable, which is needed by Imagick
192 // so we create a seekable stream first
193 $tempStream = fopen('php://temp', 'w+');
194 stream_copy_to_stream($this->repo->getLocationStream($mob_id, $location)->detach(), $tempStream);
195 rewind($tempStream);
196 $stream = new Stream($tempStream);
197
198 $converter = $this->image_converters->resizeToFixedSize(
199 $stream,
200 $width,
201 $height,
202 true,
203 $this->output_options
204 ->withQuality($image_quality)
205 ->withFormat(ImageOutputOptions::FORMAT_PNG)
206 );
207 $this->repo->addStream(
208 $mob_id,
209 $target_location,
210 $converter->getStream()
211 );
212 fclose($tempStream);
213 }
214 if ($is_video) {
215 $zip_uri = $this->repo->getContainerPath($mob_id);
217 $zip_uri,
218 $location,
219 $sec
220 );
221 $png_res = fopen('php://memory', 'r+');
222 fwrite($png_res, $image_str);
223 rewind($png_res);
224 $png_stream = new Stream($png_res);
225 $this->repo->addStream(
226 $mob_id,
227 $target_location,
228 $png_stream
229 );
230 }
231 }
232 }
233
234 public function resizeImage(
235 int $mob_id,
236 string $location,
237 string $format,
238 int $width,
239 int $height,
240 bool $a_constrain_prop = false
241 ): string {
242
243 if (!is_int(strpos($format, "image/"))) {
244 return "";
245 }
246
247 $file_path = pathinfo($location);
248 $new_location = substr($file_path["basename"], 0, strlen($file_path["basename"]) -
249 strlen($file_path["extension"]) - 1) . "_" .
250 $width . "_" .
251 $height . "." . $file_path["extension"];
252
253
254 $image_quality = 60;
255
256 // the zip stream is not seekable, which is needed by Imagick
257 // so we create a seekable stream first
258 $tempStream = fopen('php://temp', 'w+');
259 stream_copy_to_stream($this->repo->getLocationStream($mob_id, $location)->detach(), $tempStream);
260 rewind($tempStream);
261 $stream = new Stream($tempStream);
262
263 $converter = $this->image_converters->resizeToFixedSize(
264 $stream,
265 $width,
266 $height,
267 false,
268 $this->output_options
269 //->withQuality($image_quality)
270 //->withFormat(ImageOutputOptions::FORMAT_PNG)
271 );
272 $this->repo->addStream(
273 $mob_id,
274 $new_location,
275 $converter->getStream()
276 );
277 fclose($tempStream);
278 return $new_location;
279 }
280
281 public function addPreviewFromUrl(
282 int $mob_id,
283 string $url,
284 string $target_location
285 ): void {
286 $log = $this->logger;
287 try {
288 $log->debug('Trying to fetch thumbnail from URL: {thumbnail_url}', [
289 'thumbnail_url' => $url,
290 ]);
291 $curl = new \ilCurlConnection($url);
292 $curl->init(true);
293 $curl->setOpt(CURLOPT_RETURNTRANSFER, true);
294 $curl->setOpt(CURLOPT_VERBOSE, true);
295 $curl->setOpt(CURLOPT_FOLLOWLOCATION, true);
296 $curl->setOpt(CURLOPT_TIMEOUT_MS, 5000);
297 $curl->setOpt(CURLOPT_TIMEOUT, 5);
298 $curl->setOpt(CURLOPT_FAILONERROR, true);
299 $curl->setOpt(CURLOPT_SSL_VERIFYPEER, 1);
300 $curl->setOpt(CURLOPT_SSL_VERIFYHOST, 2);
301
302 $str = $curl->exec();
303 $info = $curl->getInfo();
304
305 $log->debug('cURL Info: {info}', [
306 'info' => print_r($info, true)
307 ]);
308
309 $status = $info['http_code'] ?? '';
310 if ((int) $status === 200) {
311 $log->debug('Successfully fetched preview file from URL: Received {bytes} bytes', [
312 'bytes' => (string) strlen($str),
313 ]);
314 } else {
315 $log->error('Could not fetch thumbnail from YouTube: {thumbnail_url}', [
316 'thumbnail_url' => $url,
317 ]);
318 }
319
320 $res = fopen('php://memory', 'r+');
321 fwrite($res, $str);
322 rewind($res);
323 $stream = new Stream($res);
324 $this->repo->addStream(
325 $mob_id,
326 $target_location,
327 $stream
328 );
329 } catch (\Exception $e) {
330 $log->error('Could not fetch thumbnail from Url: {message}', [
331 'message' => $e->getMessage(),
332 ]);
333 $log->error($e->getTraceAsString());
334 }
335 }
336
337 public function getSrtFiles(int $mob_id, bool $vtt_only = false): array
338 {
339 $srt_files = [];
340 $valid_suffixes = $vtt_only
341 ? ["vtt"]
342 : ["srt", "vtt"];
343 foreach ($this->getFilesOfPath($mob_id, "/srt") as $i) {
344 $name = explode(".", $i["basename"]);
345 if (in_array($name[1], $valid_suffixes) && substr($name[0], 0, 9) == "subtitle_") {
346 $srt_files[] = [
347 "file" => $i["basename"],
348 "full_path" => $i["path"],
349 "src" => $this->getLocalSrc($mob_id, $i["path"]),
350 "language" => substr($name[0], 9, 2)
351 ];
352 }
353 }
354 return $srt_files;
355 }
356
357 public function generateMissingVTT(int $mob_id): void
358 {
359 $names = array_map(static function (array $i) {
360 return $i["file"];
361 }, $this->getSrtFiles($mob_id));
362 $missing_vtt = [];
363 foreach ($names as $name) {
364 if (str_ends_with($name, ".srt")) {
365 $vtt = str_replace(".srt", ".vtt", $name);
366 if (!in_array($vtt, $names) && !in_array($vtt, $missing_vtt)) {
367 $missing_vtt[] = $vtt;
368 }
369 }
370 }
371 foreach ($missing_vtt as $vtt_name) {
372 $srt_name = str_replace(".vtt", ".srt", $vtt_name);
373 $srt_content = stream_get_contents($this->repo->getLocationStream($mob_id, "srt/" . $srt_name)->detach());
374 $vtt_content = $this->srtToVtt($srt_content);
375 $this->repo->addString($mob_id, "/srt/" . $vtt_name, $vtt_content);
376 }
377 }
378
379 public function srtToVtt(string $srt_text): string
380 {
381 // Remove UTF-8 BOM if present
382 $srt_text = preg_replace('/^\xEF\xBB\xBF/', '', $srt_text);
383
384 // Normalise line-endings and split cues
385 $srt_text = preg_replace('~\r\n?~', "\n", $srt_text);
386 $blocks = preg_split("/\n{2,}/", trim($srt_text));
387
388 $vttLines = ['WEBVTT', '']; // header + blank line
389
390 foreach ($blocks as $block) {
391 $lines = explode("\n", $block);
392
393 if (count($lines) < 2) {
394 continue; // malformed cue
395 }
396
397 /* cue number? allow BOM or spaces either side */
398 if (preg_match('/^\s*\d+\s*$/u', $lines[0])) {
399 array_shift($lines); // drop it
400 }
401
402 /* now $lines[0] *is* the time-code line → , → . */
403 $lines[0] = preg_replace(
404 '/(\d{2}:\d{2}:\d{2}),(\d{3})/',
405 '$1.$2',
406 $lines[0]
407 );
408
409 $vttLines = array_merge($vttLines, $lines, ['']);
410 }
411
412 return implode("\n", $vttLines);
413 }
414
415 public function getLastChangeTimestamp(int $mob_id): int
416 {
417 return $this->repo->getLastChangeTimestamp($mob_id);
418 }
419
420 public function updateLastChange(int $mob_id): void
421 {
422 $this->repo->updateLastChangeTimestamp($mob_id, time());
423 }
424}
$id
plugin.php for ilComponentBuildPluginInfoObjectiveTest::testAddPlugins
Definition: plugin.php:23
$location
Definition: buildRTE.php:22
create(int $id, string $title, int $from_mob_id=0)
addFileFromUpload(int $mob_id, UploadResult $result, string $path="/")
getLocalSrc(int $mob_id, string $location)
addFileFromLegacyUpload(int $mob_id, string $tmp_name, string $target_path="")
hasLocalFile(int $mob_id, string $location)
addLocalDirectory(int $mob_id, string $dir)
getInfoOfEntry(int $mob_id, string $path)
deliverEntry(int $mob_id, string $path)
resizeImage(int $mob_id, string $location, string $format, int $width, int $height, bool $a_constrain_prop=false)
getLocationContent(int $mob_id, string $location)
addFileFromLocal(int $mob_id, string $tmp_name, string $path)
getLocationStream(int $mob_id, string $location)
generatePreview(int $mob_id, string $location, bool $local, string $format, int $sec=1, string $target_location="mob_vpreview.png")
addPreviewFromUrl(int $mob_id, string $url, string $target_location)
__construct(protected InternalDataService $data, InternalRepoService $repo, protected InternalDomainService $domain, protected \ilMobStakeholder $stakeholder)
addStream(int $mob_id, string $location, FileStream $stream)
removeLocation(int $mob_id, string $location)
getFilesOfPath(int $mob_id, string $dir_path)
getSrtFiles(int $mob_id, bool $vtt_only=false)
static extractPNGFromVideoInZip(string $zip, string $path, int $sec=1)
static getLogger(string $a_component_id)
Get component logger.
static _getURL(int $a_mob_id)
get directory for files of media object
static signFile(string $path_to_file)
return['delivery_method'=> 'php',]
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
$info
Definition: entry_point.php:21
The base interface for all filesystem streams.
Definition: FileStream.php:32
$log
Definition: ltiresult.php:34
$path
Definition: ltiservices.php:30
$res
Definition: ltiservices.php:69
if(!file_exists('../ilias.ini.php'))
$url
Definition: shib_logout.php:70