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