1 /*
2  * Copyright (c) 2017-2022 gnome-mpv
3  *
4  * This file is part of Celluloid.
5  *
6  * Celluloid is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Celluloid is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Celluloid.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include <string.h>
21 #include <glib/gstdio.h>
22 #include <glib/gi18n.h>
23 
24 #include "celluloid-option-parser.h"
25 #include "celluloid-player.h"
26 #include "celluloid-player-options.h"
27 #include "celluloid-marshal.h"
28 #include "celluloid-metadata-cache.h"
29 #include "celluloid-mpv.h"
30 #include "celluloid-def.h"
31 
32 #define get_private(player) \
33 	((CelluloidPlayerPrivate *)celluloid_player_get_instance_private(CELLULOID_PLAYER(player)))
34 
35 typedef struct _CelluloidPlayerPrivate CelluloidPlayerPrivate;
36 
37 enum
38 {
39 	PROP_0,
40 	PROP_PLAYLIST,
41 	PROP_METADATA,
42 	PROP_TRACK_LIST,
43 	PROP_DISC_LIST,
44 	PROP_EXTRA_OPTIONS,
45 	N_PROPERTIES
46 };
47 
48 struct _CelluloidPlayerPrivate
49 {
50 	CelluloidMpv parent;
51 	CelluloidMetadataCache *cache;
52 	GVolumeMonitor *monitor;
53 	GPtrArray *playlist;
54 	GPtrArray *metadata;
55 	GPtrArray *track_list;
56 	GPtrArray *disc_list;
57 	GHashTable *log_levels;
58 	gboolean loaded;
59 	gboolean new_file;
60 	gboolean init_vo_config;
61 	gchar *tmp_input_config;
62 	gchar *extra_options;
63 };
64 
65 static void
66 set_property(	GObject *object,
67 		guint property_id,
68 		const GValue *value,
69 		GParamSpec *pspec );
70 
71 static void
72 get_property(	GObject *object,
73 		guint property_id,
74 		GValue *value,
75 		GParamSpec *pspec );
76 
77 static void
78 dispose(GObject *object);
79 
80 static void
81 finalize(GObject *object);
82 
83 static void
84 autofit(CelluloidPlayer *player);
85 
86 static void
87 metadata_update(CelluloidPlayer *player, gint64 pos);
88 
89 static void
90 mpv_event_notify(CelluloidMpv *mpv, gint event_id, gpointer event_data);
91 
92 static void
93 mpv_log_message(	CelluloidMpv *mpv,
94 			mpv_log_level log_level,
95 			const gchar *prefix,
96 			const gchar *text );
97 
98 static void
99 mpv_property_changed(CelluloidMpv *mpv, const gchar *name, gpointer value);
100 
101 static void
102 observe_properties(CelluloidMpv *mpv);
103 
104 static void
105 apply_default_options(CelluloidMpv *mpv);
106 
107 static void
108 initialize(CelluloidMpv *mpv);
109 
110 static gint
111 apply_options_array_string(CelluloidMpv *mpv, const gchar *args);
112 
113 static void
114 apply_extra_options(CelluloidPlayer *player);
115 
116 static void
117 load_file(CelluloidMpv *mpv, const gchar *uri, gboolean append);
118 
119 static void
120 reset(CelluloidMpv *mpv);
121 
122 static void
123 load_input_conf(CelluloidPlayer *player, const gchar *input_conf);
124 
125 static void
126 load_config_file(CelluloidMpv *mpv);
127 
128 static void
129 load_input_config_file(CelluloidPlayer *player);
130 
131 static void
132 load_scripts(CelluloidPlayer *player);
133 
134 static CelluloidTrack *
135 parse_track_entry(mpv_node_list *node);
136 
137 static void
138 add_file_to_playlist(CelluloidPlayer *player, const gchar *uri);
139 
140 static void
141 load_from_playlist(CelluloidPlayer *player);
142 
143 static CelluloidPlaylistEntry *
144 parse_playlist_entry(mpv_node_list *node);
145 
146 static void
147 update_playlist(CelluloidPlayer *player);
148 
149 static void
150 update_metadata(CelluloidPlayer *player);
151 
152 static void
153 update_track_list(CelluloidPlayer *player);
154 
155 static void
156 cache_update_handler(	CelluloidMetadataCache *cache,
157 			const gchar *uri,
158 			gpointer data );
159 
160 static void
161 mount_added_handler(GVolumeMonitor *monitor, GMount *mount, gpointer data);
162 
163 static void
164 volume_removed_handler(GVolumeMonitor *monitor, GVolume *volume, gpointer data);
165 
166 static void
167 guess_content_handler(GMount *mount, GAsyncResult *res, gpointer data);
168 
G_DEFINE_TYPE_WITH_PRIVATE(CelluloidPlayer,celluloid_player,CELLULOID_TYPE_MPV)169 G_DEFINE_TYPE_WITH_PRIVATE(CelluloidPlayer, celluloid_player, CELLULOID_TYPE_MPV)
170 
171 static void
172 set_property(	GObject *object,
173 		guint property_id,
174 		const GValue *value,
175 		GParamSpec *pspec )
176 {
177 	CelluloidPlayerPrivate *priv = get_private(object);
178 
179 	switch(property_id)
180 	{
181 		case PROP_PLAYLIST:
182 		case PROP_METADATA:
183 		case PROP_TRACK_LIST:
184 		case PROP_DISC_LIST:
185 		g_critical("Attempted to set read-only property");
186 		break;
187 
188 		case PROP_EXTRA_OPTIONS:
189 		g_free(priv->extra_options);
190 		priv->extra_options = g_value_dup_string(value);
191 		break;
192 
193 		default:
194 		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
195 		break;
196 	}
197 }
198 
199 static void
get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)200 get_property(	GObject *object,
201 		guint property_id,
202 		GValue *value,
203 		GParamSpec *pspec )
204 {
205 	CelluloidPlayerPrivate *priv = get_private(object);
206 
207 	switch(property_id)
208 	{
209 		case PROP_PLAYLIST:
210 		g_value_set_pointer(value, priv->playlist);
211 		break;
212 
213 		case PROP_METADATA:
214 		g_value_set_pointer(value, priv->metadata);
215 		break;
216 
217 		case PROP_TRACK_LIST:
218 		g_value_set_pointer(value, priv->track_list);
219 		break;
220 
221 		case PROP_DISC_LIST:
222 		g_value_set_pointer(value, priv->disc_list);
223 		break;
224 
225 		case PROP_EXTRA_OPTIONS:
226 		g_value_set_string(value, priv->extra_options);
227 		break;
228 
229 		default:
230 		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
231 		break;
232 	}
233 }
234 
235 static void
dispose(GObject * object)236 dispose(GObject *object)
237 {
238 	CelluloidPlayerPrivate *priv = get_private(object);
239 
240 	g_clear_object(&priv->cache);
241 	g_clear_object(&priv->monitor);
242 
243 	G_OBJECT_CLASS(celluloid_player_parent_class)->dispose(object);
244 }
245 
246 static void
finalize(GObject * object)247 finalize(GObject *object)
248 {
249 	CelluloidPlayerPrivate *priv = get_private(object);
250 
251 	if(priv->tmp_input_config)
252 	{
253 		g_unlink(priv->tmp_input_config);
254 	}
255 
256 	g_free(priv->tmp_input_config);
257 	g_free(priv->extra_options);
258 	g_ptr_array_free(priv->playlist, TRUE);
259 	g_ptr_array_free(priv->metadata, TRUE);
260 	g_ptr_array_free(priv->track_list, TRUE);
261 	g_ptr_array_free(priv->disc_list, TRUE);
262 
263 	G_OBJECT_CLASS(celluloid_player_parent_class)->finalize(object);
264 }
265 
266 static void
autofit(CelluloidPlayer * player)267 autofit(CelluloidPlayer *player)
268 {
269 }
270 
271 static void
metadata_update(CelluloidPlayer * player,gint64 pos)272 metadata_update(CelluloidPlayer *player, gint64 pos)
273 {
274 }
275 
276 static void
mpv_event_notify(CelluloidMpv * mpv,gint event_id,gpointer event_data)277 mpv_event_notify(CelluloidMpv *mpv, gint event_id, gpointer event_data)
278 {
279 	CelluloidPlayerPrivate *priv = get_private(mpv);
280 
281 	if(event_id == MPV_EVENT_START_FILE)
282 	{
283 		gboolean vo_configured = FALSE;
284 
285 		celluloid_mpv_get_property(	mpv,
286 					"vo-configured",
287 					MPV_FORMAT_FLAG,
288 					&vo_configured );
289 
290 		/* If the vo is not configured yet, save the content of mpv's
291 		 * playlist. This will be loaded again when the vo is
292 		 * configured.
293 		 */
294 		if(!vo_configured)
295 		{
296 			update_playlist(CELLULOID_PLAYER(mpv));
297 		}
298 	}
299 	else if(event_id == MPV_EVENT_END_FILE)
300 	{
301 		if(priv->loaded)
302 		{
303 			priv->new_file = FALSE;
304 		}
305 	}
306 	else if(event_id == MPV_EVENT_IDLE)
307 	{
308 		priv->loaded = FALSE;
309 	}
310 	else if(event_id == MPV_EVENT_FILE_LOADED)
311 	{
312 		priv->loaded = TRUE;
313 	}
314 	else if(event_id == MPV_EVENT_VIDEO_RECONFIG)
315 	{
316 		if(priv->new_file)
317 		{
318 			g_signal_emit_by_name(CELLULOID_PLAYER(mpv), "autofit");
319 		}
320 	}
321 
322 	CELLULOID_MPV_CLASS(celluloid_player_parent_class)
323 		->mpv_event_notify(mpv, event_id, event_data);
324 }
325 
326 static void
mpv_log_message(CelluloidMpv * mpv,mpv_log_level log_level,const gchar * prefix,const gchar * text)327 mpv_log_message(	CelluloidMpv *mpv,
328 			mpv_log_level log_level,
329 			const gchar *prefix,
330 			const gchar *text )
331 {
332 	CelluloidPlayerPrivate *priv = get_private(mpv);
333 	gsize prefix_len = strlen(prefix);
334 	gboolean found = FALSE;
335 	const gchar *iter_prefix;
336 	gpointer iter_level;
337 	GHashTableIter iter;
338 
339 	g_hash_table_iter_init(&iter, priv->log_levels);
340 
341 	while(	!found &&
342 		g_hash_table_iter_next(	&iter,
343 					(gpointer *)&iter_prefix,
344 					&iter_level) )
345 	{
346 		gsize iter_prefix_len = strlen(iter_prefix);
347 		gint cmp = strncmp(	iter_prefix,
348 					prefix,
349 					(prefix_len < iter_prefix_len)?
350 					prefix_len:iter_prefix_len );
351 
352 		/* Allow both exact match and prefix match */
353 		if(cmp == 0
354 		&& (iter_prefix_len == prefix_len
355 		|| (iter_prefix_len < prefix_len
356 		&& prefix[iter_prefix_len] == '/')))
357 		{
358 			found = TRUE;
359 		}
360 	}
361 
362 	if(!found || (gint)log_level <= GPOINTER_TO_INT(iter_level))
363 	{
364 		gchar *buf = g_strdup(text);
365 		gsize len = strlen(buf);
366 
367 		if(len > 1)
368 		{
369 			/* g_message() automatically adds a newline
370 			 * character when using the default log handler,
371 			 * but log messages from mpv already come
372 			 * terminated with a newline character so we
373 			 * need to take it out.
374 			 */
375 			if(buf[len-1] == '\n')
376 			{
377 				buf[len-1] = '\0';
378 			}
379 
380 			g_message("[%s] %s", prefix, buf);
381 		}
382 
383 		g_free(buf);
384 	}
385 
386 	CELLULOID_MPV_CLASS(celluloid_player_parent_class)
387 		->mpv_log_message(mpv, log_level, prefix, text);
388 }
389 
390 static void
mpv_property_changed(CelluloidMpv * mpv,const gchar * name,gpointer value)391 mpv_property_changed(CelluloidMpv *mpv, const gchar *name, gpointer value)
392 {
393 	CelluloidPlayer *player = CELLULOID_PLAYER(mpv);
394 	CelluloidPlayerPrivate *priv = get_private(mpv);
395 
396 	if(g_strcmp0(name, "pause") == 0)
397 	{
398 		gboolean idle_active = FALSE;
399 		gboolean pause = value?*((int *)value):TRUE;
400 
401 		celluloid_mpv_get_property
402 			(mpv, "idle-active", MPV_FORMAT_FLAG, &idle_active);
403 
404 		if(idle_active && !pause && !priv->init_vo_config)
405 		{
406 			load_from_playlist(player);
407 		}
408 	}
409 	else if(g_strcmp0(name, "playlist") == 0)
410 	{
411 		gboolean idle_active = FALSE;
412 		gboolean was_empty = FALSE;
413 
414 		celluloid_mpv_get_property
415 			(mpv, "idle-active", MPV_FORMAT_FLAG, &idle_active);
416 
417 		was_empty =	priv->init_vo_config ||
418 				priv->playlist->len == 0;
419 
420 		if(!idle_active && !priv->init_vo_config)
421 		{
422 			update_playlist(player);
423 		}
424 
425 		/* Check if we're transitioning from empty playlist to non-empty
426 		 * playlist.
427 		 */
428 		if(was_empty && priv->playlist->len > 0)
429 		{
430 			celluloid_mpv_set_property_flag(mpv, "pause", FALSE);
431 		}
432 	}
433 	else if(g_strcmp0(name, "metadata") == 0)
434 	{
435 		update_metadata(player);
436 	}
437 	else if(g_strcmp0(name, "track-list") == 0)
438 	{
439 		update_track_list(player);
440 	}
441 	else if(g_strcmp0(name, "vo-configured") == 0)
442 	{
443 		if(priv->init_vo_config)
444 		{
445 			priv->init_vo_config = FALSE;
446 			load_scripts(player);
447 			load_from_playlist(player);
448 		}
449 	}
450 
451 	CELLULOID_MPV_CLASS(celluloid_player_parent_class)
452 		->mpv_property_changed(mpv, name, value);
453 }
454 
455 static void
observe_properties(CelluloidMpv * mpv)456 observe_properties(CelluloidMpv *mpv)
457 {
458 	celluloid_mpv_observe_property(mpv, 0, "aid", MPV_FORMAT_STRING);
459 	celluloid_mpv_observe_property(mpv, 0, "vid", MPV_FORMAT_STRING);
460 	celluloid_mpv_observe_property(mpv, 0, "sid", MPV_FORMAT_STRING);
461 	celluloid_mpv_observe_property(mpv, 0, "chapters", MPV_FORMAT_INT64);
462 	celluloid_mpv_observe_property(mpv, 0, "core-idle", MPV_FORMAT_FLAG);
463 	celluloid_mpv_observe_property(mpv, 0, "idle-active", MPV_FORMAT_FLAG);
464 	celluloid_mpv_observe_property(mpv, 0, "border", MPV_FORMAT_FLAG);
465 	celluloid_mpv_observe_property(mpv, 0, "fullscreen", MPV_FORMAT_FLAG);
466 	celluloid_mpv_observe_property(mpv, 0, "pause", MPV_FORMAT_FLAG);
467 	celluloid_mpv_observe_property(mpv, 0, "loop-file", MPV_FORMAT_STRING);
468 	celluloid_mpv_observe_property(mpv, 0, "loop-playlist", MPV_FORMAT_STRING);
469 	celluloid_mpv_observe_property(mpv, 0, "duration", MPV_FORMAT_DOUBLE);
470 	celluloid_mpv_observe_property(mpv, 0, "media-title", MPV_FORMAT_STRING);
471 	celluloid_mpv_observe_property(mpv, 0, "metadata", MPV_FORMAT_NODE);
472 	celluloid_mpv_observe_property(mpv, 0, "playlist", MPV_FORMAT_NODE);
473 	celluloid_mpv_observe_property(mpv, 0, "playlist-count", MPV_FORMAT_INT64);
474 	celluloid_mpv_observe_property(mpv, 0, "playlist-pos", MPV_FORMAT_INT64);
475 	celluloid_mpv_observe_property(mpv, 0, "speed", MPV_FORMAT_DOUBLE);
476 	celluloid_mpv_observe_property(mpv, 0, "track-list", MPV_FORMAT_NODE);
477 	celluloid_mpv_observe_property(mpv, 0, "vo-configured", MPV_FORMAT_FLAG);
478 	celluloid_mpv_observe_property(mpv, 0, "volume", MPV_FORMAT_DOUBLE);
479 	celluloid_mpv_observe_property(mpv, 0, "volume-max", MPV_FORMAT_DOUBLE);
480 	celluloid_mpv_observe_property(mpv, 0, "window-maximized", MPV_FORMAT_FLAG);
481 	celluloid_mpv_observe_property(mpv, 0, "window-scale", MPV_FORMAT_DOUBLE);
482 }
483 
484 static void
apply_default_options(CelluloidMpv * mpv)485 apply_default_options(CelluloidMpv *mpv)
486 {
487 	gchar *config_dir = get_config_dir_path();
488 	gchar *watch_dir = get_watch_dir_path();
489 	const gchar *screenshot_dir =	g_get_user_special_dir
490 					(G_USER_DIRECTORY_PICTURES);
491 
492 	const struct
493 	{
494 		const gchar *name;
495 		const gchar *value;
496 	}
497 	options[] = {	{"vo", "opengl,vdpau,vaapi,xv,x11,libmpv,"},
498 			{"osd-level", "1"},
499 			{"softvol", "yes"},
500 			{"force-window", "immediate"},
501 			{"input-default-bindings", "yes"},
502 			{"audio-client-name", ICON_NAME},
503 			{"title", "${media-title}"},
504 			{"autofit-larger", "75%"},
505 			{"window-scale", "1"},
506 			{"pause", "yes"},
507 			{"ytdl", "yes"},
508 			{"ytdl-raw-options", "yes-playlist="},
509 			{"load-scripts", "no"},
510 			{"osd-bar", "no"},
511 			{"input-cursor", "no"},
512 			{"cursor-autohide", "no"},
513 			{"softvol-max", "100"},
514 			{"config-dir", config_dir},
515 			{"watch-later-directory", watch_dir},
516 			{"screenshot-directory", screenshot_dir},
517 			{"screenshot-template", "%f-%P"},
518 			{NULL, NULL} };
519 
520 	for(gint i = 0; options[i].name; i++)
521 	{
522 		g_debug(	"Applying default option --%s=%s",
523 				options[i].name,
524 				options[i].value );
525 
526 		celluloid_mpv_set_option_string
527 			(mpv, options[i].name, options[i].value);
528 	}
529 
530 	g_free(config_dir);
531 	g_free(watch_dir);
532 }
533 
534 static void
initialize(CelluloidMpv * mpv)535 initialize(CelluloidMpv *mpv)
536 {
537 	CelluloidPlayer *player = CELLULOID_PLAYER(mpv);
538 
539 	apply_default_options(mpv);
540 	load_config_file(mpv);
541 	load_input_config_file(player);
542 	apply_extra_options(player);
543 	observe_properties(mpv);
544 
545 	CELLULOID_MPV_CLASS(celluloid_player_parent_class)->initialize(mpv);
546 }
547 
548 static gint
apply_options_array_string(CelluloidMpv * mpv,const gchar * args)549 apply_options_array_string(CelluloidMpv *mpv, const gchar *args)
550 {
551 	gint fail_count = 0;
552 
553 	while(args && *args)
554 	{
555 		gchar *key = NULL;
556 		gchar *value = NULL;
557 
558 		args = parse_option(args, &key, &value);
559 
560 		if(key && *key)
561 		{
562 			g_debug("Applying option: --%s=%s", key, value);
563 
564 			if(celluloid_mpv_set_option_string(mpv, key, value) < 0)
565 			{
566 				fail_count++;
567 
568 				g_warning(	"Failed to apply option: --%s=%s\n",
569 						key,
570 						value );
571 			}
572 		}
573 		else
574 		{
575 			g_warning("Failed to parse options");
576 
577 			args = NULL;
578 			fail_count++;
579 		}
580 
581 		g_free(key);
582 		g_free(value);
583 	}
584 
585 	return fail_count*(-1);
586 }
587 
588 static void
apply_extra_options(CelluloidPlayer * player)589 apply_extra_options(CelluloidPlayer *player)
590 {
591 	CelluloidPlayerPrivate *priv = get_private(player);
592 	CelluloidMpv *mpv = CELLULOID_MPV(player);
593 	gchar *extra_options = priv->extra_options;
594 
595 	g_debug("Applying extra mpv options: %s", extra_options);
596 
597 	/* Apply extra options */
598 	if(extra_options && apply_options_array_string(mpv, extra_options) < 0)
599 	{
600 		const gchar *msg = _("Failed to apply one or more MPV options.");
601 		g_signal_emit_by_name(mpv, "error", msg);
602 	}
603 }
604 
605 static void
load_file(CelluloidMpv * mpv,const gchar * uri,gboolean append)606 load_file(CelluloidMpv *mpv, const gchar *uri, gboolean append)
607 {
608 	CelluloidPlayer *player = CELLULOID_PLAYER(mpv);
609 	CelluloidPlayerPrivate *priv = get_private(mpv);
610 	gboolean ready = FALSE;
611 	gboolean idle_active = FALSE;
612 
613 	g_object_get(mpv, "ready", &ready, NULL);
614 
615 	celluloid_mpv_get_property
616 		(mpv, "idle-active", MPV_FORMAT_FLAG, &idle_active);
617 
618 	if(idle_active || !ready)
619 	{
620 		if(!append)
621 		{
622 			priv->new_file = TRUE;
623 			g_ptr_array_set_size(priv->playlist, 0);
624 		}
625 
626 		add_file_to_playlist(player, uri);
627 	}
628 	else
629 	{
630 		if(!append)
631 		{
632 			priv->new_file = TRUE;
633 			priv->loaded = FALSE;
634 		}
635 
636 		CELLULOID_MPV_CLASS(celluloid_player_parent_class)
637 			->load_file(mpv, uri, append);
638 	}
639 
640 	/* Playlist items added when mpv is idle doesn't get added directly to
641 	 * its internal playlist, so the property change signal won't be fired.
642 	 * We need to emit notify signal here manually to ensure that the
643 	 * playlist widget gets updated.
644 	 */
645 	if(idle_active)
646 	{
647 		g_object_notify(G_OBJECT(player), "playlist");
648 	}
649 }
650 
651 static void
reset(CelluloidMpv * mpv)652 reset(CelluloidMpv *mpv)
653 {
654 	gboolean idle_active = FALSE;
655 	gint64 playlist_pos = 0;
656 	gdouble volume = 0;
657 
658 	celluloid_mpv_get_property
659 		(mpv, "idle-active", MPV_FORMAT_FLAG, &idle_active);
660 	celluloid_mpv_get_property
661 		(mpv, "playlist-pos", MPV_FORMAT_INT64, &playlist_pos);
662 	celluloid_mpv_get_property
663 		(mpv, "volume", MPV_FORMAT_DOUBLE, &volume);
664 
665 	CELLULOID_MPV_CLASS(celluloid_player_parent_class)->reset(mpv);
666 
667 	load_scripts(CELLULOID_PLAYER(mpv));
668 
669 	if(!idle_active)
670 	{
671 		load_from_playlist(CELLULOID_PLAYER(mpv));
672 	}
673 
674 	if(playlist_pos > 0)
675 	{
676 		celluloid_mpv_set_property
677 			(mpv, "playlist-pos", MPV_FORMAT_INT64, &playlist_pos);
678 	}
679 
680 	celluloid_mpv_set_property(mpv, "volume", MPV_FORMAT_DOUBLE, &volume);
681 }
682 
683 static void
load_input_conf(CelluloidPlayer * player,const gchar * input_conf)684 load_input_conf(CelluloidPlayer *player, const gchar *input_conf)
685 {
686 	CelluloidPlayerPrivate *priv = get_private(player);
687 	const gchar *default_keybinds[] = DEFAULT_KEYBINDS;
688 	gchar *tmp_path;
689 	FILE *tmp_file;
690 	gint tmp_fd;
691 
692 	if(priv->tmp_input_config)
693 	{
694 		g_unlink(priv->tmp_input_config);
695 		g_free(priv->tmp_input_config);
696 	}
697 
698 	tmp_fd = g_file_open_tmp("."BIN_NAME"-XXXXXX", &tmp_path, NULL);
699 	tmp_file = fdopen(tmp_fd, "w");
700 	priv->tmp_input_config = tmp_path;
701 
702 	if(!tmp_file)
703 	{
704 		g_error("Failed to open temporary input config file");
705 	}
706 
707 	for(gint i = 0; default_keybinds[i]; i++)
708 	{
709 		const gsize len = strlen(default_keybinds[i]);
710 		gsize write_size = fwrite(default_keybinds[i], len, 1, tmp_file);
711 		gint rc = fputc('\n', tmp_file);
712 
713 		if(write_size != 1 || rc != '\n')
714 		{
715 			g_error(	"Error writing default keybindings to "
716 					"temporary input config file" );
717 		}
718 	}
719 
720 	g_debug("Using temporary input config file: %s", tmp_path);
721 
722 	celluloid_mpv_set_option_string
723 		(CELLULOID_MPV(player), "input-conf", tmp_path);
724 
725 	if(input_conf && strlen(input_conf) > 0)
726 	{
727 		const gsize buf_size = 65536;
728 		void *buf = g_malloc(buf_size);
729 		GFile *src_file = g_file_new_for_uri(input_conf);
730 		GFileInputStream *src_stream = g_file_read(src_file, NULL, NULL);
731 		gsize read_size = buf_size;
732 
733 		if(!src_stream)
734 		{
735 			g_warning(	"Cannot open input config file: %s",
736 					input_conf );
737 		}
738 
739 		while(src_stream && read_size == buf_size)
740 		{
741 			read_size =
742 				(gsize)
743 				g_input_stream_read
744 				(G_INPUT_STREAM(src_stream), buf, buf_size, NULL, NULL);
745 
746 			if(read_size != fwrite(buf, 1, read_size, tmp_file))
747 			{
748 				g_error(	"Error writing requested input "
749 						"config file to temporary "
750 						"input config file" );
751 			}
752 		}
753 
754 		g_info("Loaded input config file: %s", input_conf);
755 
756 		g_clear_object(&src_stream);
757 		g_object_unref(src_file);
758 		g_free(buf);
759 	}
760 
761 	fclose(tmp_file);
762 }
763 
764 static void
load_config_file(CelluloidMpv * mpv)765 load_config_file(CelluloidMpv *mpv)
766 {
767 	GSettings *settings = g_settings_new(CONFIG_ROOT);
768 
769 	if(g_settings_get_boolean(settings, "mpv-config-enable"))
770 	{
771 		gchar *mpv_conf =	g_settings_get_string
772 					(settings, "mpv-config-file");
773 
774 		GFile *file = g_file_new_for_uri(mpv_conf);
775 		gchar *path = g_file_get_path(file);
776 
777 		g_info("Loading config file: %s", path);
778 		celluloid_mpv_load_config_file(mpv, path);
779 
780 		g_free(path);
781 		g_object_unref(file);
782 		g_free(mpv_conf);
783 	}
784 
785 	g_object_unref(settings);
786 }
787 
788 static void
load_input_config_file(CelluloidPlayer * player)789 load_input_config_file(CelluloidPlayer *player)
790 {
791 	GSettings *settings = g_settings_new(CONFIG_ROOT);
792 	gchar *input_conf = NULL;
793 
794 	if(g_settings_get_boolean(settings, "mpv-input-config-enable"))
795 	{
796 		input_conf =	g_settings_get_string
797 				(settings, "mpv-input-config-file");
798 
799 		g_info("Loading input config file: %s", input_conf);
800 	}
801 
802 	load_input_conf(player, input_conf);
803 
804 	g_free(input_conf);
805 	g_object_unref(settings);
806 }
807 
808 static void
load_scripts(CelluloidPlayer * player)809 load_scripts(CelluloidPlayer *player)
810 {
811 	gchar *path = get_scripts_dir_path();
812 	GDir *dir = g_dir_open(path, 0, NULL);
813 
814 	if(dir)
815 	{
816 		const gchar *name;
817 
818 		do
819 		{
820 			gchar *full_path;
821 
822 			name = g_dir_read_name(dir);
823 			full_path = g_build_filename(path, name, NULL);
824 
825 			if(g_file_test(full_path, G_FILE_TEST_IS_REGULAR))
826 			{
827 				const gchar *cmd[]
828 					= {"load-script", full_path, NULL};
829 
830 				g_info("Loading script: %s", full_path);
831 				celluloid_mpv_command
832 					(CELLULOID_MPV(player), cmd);
833 			}
834 
835 			g_free(full_path);
836 		}
837 		while(name);
838 
839 		g_dir_close(dir);
840 	}
841 	else
842 	{
843 		g_warning("Failed to open scripts directory: %s", path);
844 	}
845 
846 	g_free(path);
847 }
848 
849 static CelluloidTrack *
parse_track_entry(mpv_node_list * node)850 parse_track_entry(mpv_node_list *node)
851 {
852 	CelluloidTrack *entry = celluloid_track_new();
853 
854 	for(gint i = 0; i < node->num; i++)
855 	{
856 		if(g_strcmp0(node->keys[i], "type") == 0)
857 		{
858 			const gchar *type = node->values[i].u.string;
859 
860 			if(g_strcmp0(type, "audio") == 0)
861 			{
862 				entry->type = TRACK_TYPE_AUDIO;
863 			}
864 			else if(g_strcmp0(type, "video") == 0)
865 			{
866 				entry->type = TRACK_TYPE_VIDEO;
867 			}
868 			else if(g_strcmp0(type, "sub") == 0)
869 			{
870 				entry->type = TRACK_TYPE_SUBTITLE;
871 			}
872 		}
873 		else if(g_strcmp0(node->keys[i], "title") == 0)
874 		{
875 			entry->title = g_strdup(node->values[i].u.string);
876 		}
877 		else if(g_strcmp0(node->keys[i], "lang") == 0)
878 		{
879 			entry->lang = g_strdup(node->values[i].u.string);
880 		}
881 		else if(g_strcmp0(node->keys[i], "id") == 0)
882 		{
883 			entry->id = node->values[i].u.int64;
884 		}
885 	}
886 
887 	return entry;
888 }
889 
890 static void
add_file_to_playlist(CelluloidPlayer * player,const gchar * uri)891 add_file_to_playlist(CelluloidPlayer *player, const gchar *uri)
892 {
893 	CelluloidPlaylistEntry *entry = celluloid_playlist_entry_new(uri, NULL);
894 
895 	g_ptr_array_add(get_private(player)->playlist, entry);
896 }
897 
load_from_playlist(CelluloidPlayer * player)898 static void load_from_playlist(CelluloidPlayer *player)
899 {
900 	CelluloidMpv *mpv = CELLULOID_MPV(player);
901 	GPtrArray *playlist = get_private(player)->playlist;
902 
903 	for(guint i = 0; playlist && i < playlist->len; i++)
904 	{
905 		CelluloidPlaylistEntry *entry = g_ptr_array_index(playlist, i);
906 
907 		/* Do not append on first iteration */
908 		CELLULOID_MPV_CLASS(celluloid_player_parent_class)
909 			->load_file(mpv, entry->filename, i != 0);
910 	}
911 }
912 
913 static CelluloidPlaylistEntry *
parse_playlist_entry(mpv_node_list * node)914 parse_playlist_entry(mpv_node_list *node)
915 {
916 	const gchar *filename = NULL;
917 	const gchar *title = NULL;
918 
919 	for(gint i = 0; i < node->num; i++)
920 	{
921 		if(g_strcmp0(node->keys[i], "filename") == 0)
922 		{
923 			filename = node->values[i].u.string;
924 		}
925 		else if(g_strcmp0(node->keys[i], "title") == 0)
926 		{
927 			title = node->values[i].u.string;
928 		}
929 	}
930 
931 	return celluloid_playlist_entry_new(filename, title);
932 }
933 
934 static void
update_playlist(CelluloidPlayer * player)935 update_playlist(CelluloidPlayer *player)
936 {
937 	CelluloidPlayerPrivate *priv;
938 	GSettings *settings;
939 	gboolean prefetch_metadata;
940 	const mpv_node_list *org_list;
941 	mpv_node playlist;
942 
943 	priv = get_private(player);
944 	settings = g_settings_new(CONFIG_ROOT);
945 	prefetch_metadata = g_settings_get_boolean(settings, "prefetch-metadata");
946 
947 	g_ptr_array_set_size(priv->playlist, 0);
948 
949 	celluloid_mpv_get_property
950 		(CELLULOID_MPV(player), "playlist", MPV_FORMAT_NODE, &playlist);
951 
952 	org_list = playlist.u.list;
953 
954 	if(playlist.format == MPV_FORMAT_NODE_ARRAY)
955 	{
956 		for(gint i = 0; i < org_list->num; i++)
957 		{
958 			CelluloidPlaylistEntry *entry;
959 
960 			entry = parse_playlist_entry(org_list->values[i].u.list);
961 
962 			if(!entry->title && prefetch_metadata)
963 			{
964 				CelluloidMetadataCacheEntry *cache_entry;
965 
966 				cache_entry =	celluloid_metadata_cache_lookup
967 						(priv->cache, entry->filename);
968 				entry->title =	g_strdup(cache_entry->title);
969 			}
970 
971 			g_ptr_array_add(priv->playlist, entry);
972 		}
973 
974 		mpv_free_node_contents(&playlist);
975 		g_object_notify(G_OBJECT(player), "playlist");
976 	}
977 
978 
979 	if(prefetch_metadata)
980 	{
981 		celluloid_metadata_cache_load_playlist
982 			(priv->cache, priv->playlist);
983 	}
984 
985 	g_object_unref(settings);
986 }
987 
988 static void
update_metadata(CelluloidPlayer * player)989 update_metadata(CelluloidPlayer *player)
990 {
991 	CelluloidPlayerPrivate *priv = get_private(player);
992 	mpv_node_list *org_list = NULL;
993 	mpv_node metadata;
994 
995 	g_ptr_array_set_size(priv->metadata, 0);
996 
997 	celluloid_mpv_get_property
998 		(CELLULOID_MPV(player), "metadata", MPV_FORMAT_NODE, &metadata);
999 
1000 	org_list = metadata.u.list;
1001 
1002 	if(metadata.format == MPV_FORMAT_NODE_MAP && org_list->num > 0)
1003 	{
1004 		for(gint i = 0; i < org_list->num; i++)
1005 		{
1006 			const gchar *key;
1007 			mpv_node value;
1008 
1009 			key = org_list->keys[i];
1010 			value = org_list->values[i];
1011 
1012 			if(value.format == MPV_FORMAT_STRING)
1013 			{
1014 				CelluloidMetadataEntry *entry;
1015 
1016 				entry =	celluloid_metadata_entry_new
1017 					(key, value.u.string);
1018 
1019 				g_ptr_array_add(priv->metadata, entry);
1020 			}
1021 			else
1022 			{
1023 				g_warning(	"Ignored metadata field %s "
1024 						"with unexpected format %d",
1025 						key,
1026 						value.format );
1027 			}
1028 		}
1029 
1030 		mpv_free_node_contents(&metadata);
1031 		g_object_notify(G_OBJECT(player), "metadata");
1032 	}
1033 }
1034 
1035 static void
update_track_list(CelluloidPlayer * player)1036 update_track_list(CelluloidPlayer *player)
1037 {
1038 	CelluloidPlayerPrivate *priv = get_private(player);
1039 	mpv_node_list *org_list = NULL;
1040 	mpv_node track_list;
1041 
1042 	g_ptr_array_set_size(priv->track_list, 0);
1043 	celluloid_mpv_get_property(	CELLULOID_MPV(player),
1044 					"track-list",
1045 					MPV_FORMAT_NODE,
1046 					&track_list );
1047 
1048 	org_list = track_list.u.list;
1049 
1050 	if(track_list.format == MPV_FORMAT_NODE_ARRAY)
1051 	{
1052 		for(gint i = 0; i < org_list->num; i++)
1053 		{
1054 			CelluloidTrack *entry =	parse_track_entry
1055 						(org_list->values[i].u.list);
1056 
1057 			g_ptr_array_add(priv->track_list, entry);
1058 		}
1059 
1060 		mpv_free_node_contents(&track_list);
1061 		g_object_notify(G_OBJECT(player), "track-list");
1062 	}
1063 }
1064 
1065 static void
cache_update_handler(CelluloidMetadataCache * cache,const gchar * uri,gpointer data)1066 cache_update_handler(	CelluloidMetadataCache *cache,
1067 			const gchar *uri,
1068 			gpointer data )
1069 {
1070 	CelluloidPlayerPrivate *priv = get_private(data);
1071 
1072 	for(guint i = 0; i < priv->playlist->len; i++)
1073 	{
1074 		CelluloidPlaylistEntry *entry =
1075 			g_ptr_array_index(priv->playlist, i);
1076 
1077 		if(g_strcmp0(entry->filename, uri) == 0)
1078 		{
1079 			CelluloidMetadataCacheEntry *cache_entry =
1080 				celluloid_metadata_cache_lookup(cache, uri);
1081 
1082 			g_free(entry->title);
1083 			entry->title = g_strdup(cache_entry->title);
1084 
1085 			g_signal_emit_by_name
1086 				(data, "metadata-update", (gint64)i);
1087 		}
1088 	}
1089 }
1090 
1091 static void
mount_added_handler(GVolumeMonitor * monitor,GMount * mount,gpointer data)1092 mount_added_handler(GVolumeMonitor *monitor, GMount *mount, gpointer data)
1093 {
1094 	GAsyncReadyCallback callback
1095 		= (GAsyncReadyCallback)guess_content_handler;
1096 	gchar *name = g_mount_get_name(mount);
1097 
1098 	g_debug("Mount %s added", name);
1099 	g_mount_guess_content_type(mount, FALSE, NULL, callback, data);
1100 
1101 	g_free(name);
1102 }
1103 
1104 static void
volume_removed_handler(GVolumeMonitor * monitor,GVolume * volume,gpointer data)1105 volume_removed_handler(GVolumeMonitor *monitor, GVolume *volume, gpointer data)
1106 {
1107 	GPtrArray *disc_list = get_private(data)->disc_list;
1108 	gboolean found = FALSE;
1109 	gchar *path = g_volume_get_identifier(volume, "unix-device");
1110 
1111 	for(guint i = 0; !found && path && i < disc_list->len; i++)
1112 	{
1113 		CelluloidDisc *disc = g_ptr_array_index(disc_list, i);
1114 
1115 		if(g_str_has_suffix(disc->uri, path))
1116 		{
1117 			g_ptr_array_remove_index(disc_list, i);
1118 
1119 			found = TRUE;
1120 		}
1121 	}
1122 
1123 	g_object_notify(G_OBJECT(data), "disc-list");
1124 }
1125 
1126 static void
guess_content_handler(GMount * mount,GAsyncResult * res,gpointer data)1127 guess_content_handler(GMount *mount, GAsyncResult *res, gpointer data)
1128 {
1129 	GError *error = NULL;
1130 	gchar **types = g_mount_guess_content_type_finish(mount, res, &error);
1131 
1132 	if(error)
1133 	{
1134 		gchar *name = g_mount_get_name(mount);
1135 
1136 		g_warning(	"Failed to guess content type for mount %s: %s",
1137 				name,
1138 				error->message );
1139 
1140 		g_free(name);
1141 	}
1142 	else
1143 	{
1144 		CelluloidPlayer *player = data;
1145 		const gchar *protocol = NULL;
1146 		GVolume *volume = g_mount_get_volume(mount);
1147 		gchar *path = 	volume ?
1148 				g_volume_get_identifier(volume, "unix-device") :
1149 				NULL;
1150 
1151 		for(gint i = 0; path && !protocol && types && types[i]; i++)
1152 		{
1153 			if(g_strcmp0(types[i], "x-content/video-blueray") == 0)
1154 			{
1155 				protocol = "bd";
1156 			}
1157 			else if(g_strcmp0(types[i], "x-content/video-dvd") == 0)
1158 			{
1159 				protocol = "dvd";
1160 			}
1161 			else if(g_strcmp0(types[i], "x-content/audio-cdda") == 0)
1162 			{
1163 				protocol = "cdda";
1164 			}
1165 		}
1166 
1167 		if(path && protocol)
1168 		{
1169 			CelluloidDisc *disc = celluloid_disc_new();
1170 
1171 			disc->uri = g_strdup_printf("%s:///%s", protocol, path);
1172 			disc->label = g_mount_get_name(mount);
1173 
1174 			g_ptr_array_add(get_private(player)->disc_list, disc);
1175 			g_object_notify(G_OBJECT(player), "disc-list");
1176 		}
1177 
1178 		g_free(path);
1179 		g_clear_object(&volume);
1180 	}
1181 
1182 	g_strfreev(types);
1183 }
1184 
1185 static void
celluloid_player_class_init(CelluloidPlayerClass * klass)1186 celluloid_player_class_init(CelluloidPlayerClass *klass)
1187 {
1188 	CelluloidMpvClass *mpv_class = CELLULOID_MPV_CLASS(klass);
1189 	GObjectClass *obj_class = G_OBJECT_CLASS(klass);
1190 	GParamSpec *pspec = NULL;
1191 
1192 	klass->autofit = autofit;
1193 	klass->metadata_update = metadata_update;
1194 	mpv_class->mpv_event_notify = mpv_event_notify;
1195 	mpv_class->mpv_log_message = mpv_log_message;
1196 	mpv_class->mpv_property_changed = mpv_property_changed;
1197 	mpv_class->initialize = initialize;
1198 	mpv_class->load_file = load_file;
1199 	mpv_class->reset = reset;
1200 	obj_class->set_property = set_property;
1201 	obj_class->get_property = get_property;
1202 	obj_class->dispose = dispose;
1203 	obj_class->finalize = finalize;
1204 
1205 	pspec = g_param_spec_pointer
1206 		(	"playlist",
1207 			"Playlist",
1208 			"The playlist",
1209 			G_PARAM_READABLE );
1210 	g_object_class_install_property(obj_class, PROP_PLAYLIST, pspec);
1211 
1212 	pspec = g_param_spec_pointer
1213 		(	"metadata",
1214 			"Metadata",
1215 			"The metadata tags of the current file",
1216 			G_PARAM_READABLE );
1217 	g_object_class_install_property(obj_class, PROP_METADATA, pspec);
1218 
1219 	pspec = g_param_spec_pointer
1220 		(	"track-list",
1221 			"Track list",
1222 			"Audio, video, and subtitle tracks of the current file",
1223 			G_PARAM_READABLE );
1224 	g_object_class_install_property(obj_class, PROP_TRACK_LIST, pspec);
1225 
1226 	pspec = g_param_spec_pointer
1227 		(	"disc-list",
1228 			"Disc list",
1229 			"List of mounted discs",
1230 			G_PARAM_READABLE );
1231 	g_object_class_install_property(obj_class, PROP_DISC_LIST, pspec);
1232 
1233 	pspec = g_param_spec_string
1234 		(	"extra-options",
1235 			"Extra options",
1236 			"Extra options to pass to mpv",
1237 			NULL,
1238 			G_PARAM_READWRITE );
1239 	g_object_class_install_property(obj_class, PROP_EXTRA_OPTIONS, pspec);
1240 
1241 	g_signal_new(	"autofit",
1242 			G_TYPE_FROM_CLASS(klass),
1243 			G_SIGNAL_RUN_FIRST,
1244 			0,
1245 			NULL,
1246 			NULL,
1247 			g_cclosure_marshal_VOID__VOID,
1248 			G_TYPE_NONE,
1249 			0 );
1250 	g_signal_new(	"metadata-update",
1251 			G_TYPE_FROM_CLASS(klass),
1252 			G_SIGNAL_RUN_FIRST,
1253 			0,
1254 			NULL,
1255 			NULL,
1256 			g_cclosure_gen_marshal_VOID__INT64,
1257 			G_TYPE_NONE,
1258 			1,
1259 			G_TYPE_INT64 );
1260 }
1261 
1262 static void
celluloid_player_init(CelluloidPlayer * player)1263 celluloid_player_init(CelluloidPlayer *player)
1264 {
1265 	CelluloidPlayerPrivate *priv = get_private(player);
1266 
1267 	priv->cache =		celluloid_metadata_cache_new();
1268 	priv->monitor =		g_volume_monitor_get();
1269 	priv->playlist =	g_ptr_array_new_with_free_func
1270 				((GDestroyNotify)celluloid_playlist_entry_free);
1271 	priv->metadata =	g_ptr_array_new_with_free_func
1272 				((GDestroyNotify)celluloid_metadata_entry_free);
1273 	priv->track_list =	g_ptr_array_new_with_free_func
1274 				((GDestroyNotify)celluloid_track_free);
1275 	priv->disc_list =	g_ptr_array_new_with_free_func
1276 				((GDestroyNotify)celluloid_disc_free);
1277 	priv->log_levels =	g_hash_table_new_full
1278 				(g_str_hash, g_str_equal, g_free, NULL);
1279 
1280 	priv->loaded = FALSE;
1281 	priv->new_file = TRUE;
1282 	priv->init_vo_config = TRUE;
1283 	priv->tmp_input_config = NULL;
1284 	priv->extra_options = NULL;
1285 
1286 	g_signal_connect(	priv->cache,
1287 				"update",
1288 				G_CALLBACK(cache_update_handler),
1289 				player );
1290 	g_signal_connect(	priv->monitor,
1291 				"mount-added",
1292 				G_CALLBACK(mount_added_handler),
1293 				player );
1294 
1295 	// We need to connect to volume-removed instead of mount-removed because
1296 	// we need to use the path of the volume to figure out which entry in
1297 	// disc_list to remove. However, by the time mount-removed fires, the
1298 	// mount would no longer be associated with its volume. This works fine
1299 	// since we only add mounts with associated volumes to disc_list.
1300 	g_signal_connect(	priv->monitor,
1301 				"volume-removed",
1302 				G_CALLBACK(volume_removed_handler),
1303 				player );
1304 
1305 	// Emit mount-added to fill disc_list using mounts that already exist.
1306 	GList *mount_list = g_volume_monitor_get_mounts(priv->monitor);
1307 
1308 	for(GList *cur = mount_list; cur; cur = g_list_next(cur))
1309 	{
1310 		g_signal_emit_by_name(priv->monitor, "mount-added", cur->data);
1311 	}
1312 
1313 	g_list_free_full(mount_list, g_object_unref);
1314 }
1315 
1316 CelluloidPlayer *
celluloid_player_new(gint64 wid)1317 celluloid_player_new(gint64 wid)
1318 {
1319 	return CELLULOID_PLAYER(g_object_new(	celluloid_player_get_type(),
1320 						"wid", wid,
1321 						NULL ));
1322 }
1323 
1324 void
celluloid_player_set_playlist_position(CelluloidPlayer * player,gint64 position)1325 celluloid_player_set_playlist_position(CelluloidPlayer *player, gint64 position)
1326 {
1327 	CelluloidMpv *mpv = CELLULOID_MPV(player);
1328 	gint64 playlist_pos = 0;
1329 
1330 	celluloid_mpv_get_property
1331 		(mpv, "playlist-pos", MPV_FORMAT_INT64, &playlist_pos);
1332 
1333 	if(position != playlist_pos)
1334 	{
1335 		celluloid_mpv_set_property
1336 			(mpv, "playlist-pos", MPV_FORMAT_INT64, &position);
1337 	}
1338 }
1339 
1340 void
celluloid_player_remove_playlist_entry(CelluloidPlayer * player,gint64 position)1341 celluloid_player_remove_playlist_entry(CelluloidPlayer *player, gint64 position)
1342 {
1343 	CelluloidMpv *mpv = CELLULOID_MPV(player);
1344 	gboolean idle_active =	celluloid_mpv_get_property_flag
1345 				(mpv, "idle-active");
1346 
1347 	if(!idle_active)
1348 	{
1349 		const gchar *cmd[] = {"playlist_remove", NULL, NULL};
1350 		gchar *index_str =	g_strdup_printf
1351 					("%" G_GINT64_FORMAT, position);
1352 
1353 		cmd[1] = index_str;
1354 
1355 		celluloid_mpv_command(CELLULOID_MPV(player), cmd);
1356 		g_free(index_str);
1357 	}
1358 }
1359 
1360 void
celluloid_player_move_playlist_entry(CelluloidPlayer * player,gint64 src,gint64 dst)1361 celluloid_player_move_playlist_entry(	CelluloidPlayer *player,
1362 					gint64 src,
1363 					gint64 dst )
1364 {
1365 	CelluloidMpv *mpv = CELLULOID_MPV(player);
1366 	gboolean idle_active =	celluloid_mpv_get_property_flag
1367 				(mpv, "idle-active");
1368 
1369 	if(idle_active)
1370 	{
1371 		GPtrArray *playlist;
1372 		CelluloidPlaylistEntry **entry;
1373 
1374 		playlist = get_private(player)->playlist;
1375 		entry = (CelluloidPlaylistEntry **)
1376 			&g_ptr_array_index(playlist, src);
1377 
1378 		g_ptr_array_insert(playlist, (gint)dst, *entry);
1379 
1380 		/* Prevent the entry from being freed */
1381 		*entry = NULL;
1382 		g_ptr_array_remove_index(playlist, (guint)((src > dst)?--src:src));
1383 	}
1384 	else
1385 	{
1386 		const gchar *cmd[] =	{"playlist_move", NULL, NULL, NULL};
1387 		gchar *src_str =	g_strdup_printf
1388 					("%" G_GINT64_FORMAT, (src > dst)?--src:src);
1389 		gchar *dst_str =	g_strdup_printf
1390 					("%" G_GINT64_FORMAT, dst);
1391 
1392 		cmd[1] = src_str;
1393 		cmd[2] = dst_str;
1394 
1395 		celluloid_mpv_command(CELLULOID_MPV(player), cmd);
1396 
1397 		g_free(src_str);
1398 		g_free(dst_str);
1399 	}
1400 }
1401 
1402 void
celluloid_player_set_log_level(CelluloidPlayer * player,const gchar * prefix,const gchar * level)1403 celluloid_player_set_log_level(	CelluloidPlayer *player,
1404 				const gchar *prefix,
1405 				const gchar *level )
1406 {
1407 	const struct
1408 	{
1409 		gchar *name;
1410 		mpv_log_level level;
1411 	}
1412 	level_map[] = {	{"no", MPV_LOG_LEVEL_NONE},
1413 			{"fatal", MPV_LOG_LEVEL_FATAL},
1414 			{"error", MPV_LOG_LEVEL_ERROR},
1415 			{"warn", MPV_LOG_LEVEL_WARN},
1416 			{"info", MPV_LOG_LEVEL_INFO},
1417 			{"v", MPV_LOG_LEVEL_V},
1418 			{"debug", MPV_LOG_LEVEL_DEBUG},
1419 			{"trace", MPV_LOG_LEVEL_TRACE},
1420 			{NULL, MPV_LOG_LEVEL_NONE} };
1421 
1422 	GHashTableIter iter;
1423 	gpointer iter_level = GINT_TO_POINTER(DEFAULT_LOG_LEVEL);
1424 	CelluloidPlayerPrivate *priv = get_private(player);
1425 	mpv_log_level max_level = DEFAULT_LOG_LEVEL;
1426 	gboolean found = FALSE;
1427 	gint i = 0;
1428 
1429 	do
1430 	{
1431 		found = (g_strcmp0(level, level_map[i].name) == 0);
1432 	}
1433 	while(!found && level_map[++i].name);
1434 
1435 	if(found && g_strcmp0(prefix, "all") != 0)
1436 	{
1437 		g_hash_table_replace(	priv->log_levels,
1438 					g_strdup(prefix),
1439 					GINT_TO_POINTER(level_map[i].level) );
1440 	}
1441 
1442 	max_level = level_map[i].level;
1443 
1444 	g_hash_table_iter_init(&iter, priv->log_levels);
1445 
1446 	while(g_hash_table_iter_next(&iter, NULL, &iter_level))
1447 	{
1448 		if(GPOINTER_TO_INT(iter_level) > (gint)max_level)
1449 		{
1450 			max_level = GPOINTER_TO_INT(iter_level);
1451 		}
1452 	}
1453 
1454 	for(i = 0; level_map[i].level != max_level; i++);
1455 
1456 	celluloid_mpv_request_log_messages
1457 		(CELLULOID_MPV(player), level_map[i].name);
1458 }
1459