Merge pull request #603 from Rafostar/devel

Merge devel branch
This commit is contained in:
Rafał Dzięgiel
2025-12-20 12:48:51 +01:00
committed by GitHub
14 changed files with 215 additions and 106 deletions

View File

@@ -89,7 +89,7 @@ drag_item_prepare_cb (GtkDragSource *drag_source, gdouble x, gdouble y, ClapperA
paintable = gtk_widget_paintable_new (list_widget);
drag_data = g_new0 (ClapperAppQueueListDragData, 1);
drag_data = g_new (ClapperAppQueueListDragData, 1);
drag_data->item = gst_object_ref (item);
drag_data->widget = g_object_ref_sink (pickup);
drag_data->paintable = gdk_paintable_get_current_image (paintable);
@@ -150,8 +150,12 @@ queue_drop_value_notify_cb (GtkDropTarget *drop_target,
{
const GValue *value = gtk_drop_target_get_value (drop_target);
if (value && !clapper_app_utils_value_for_item_is_valid (value))
gtk_drop_target_reject (drop_target);
if (value) {
if (!clapper_app_utils_value_for_item_is_valid (value))
gtk_drop_target_reject (drop_target);
} else {
self->list_target = NULL; // Reset when value is lost
}
}
static GdkDragAction
@@ -223,6 +227,7 @@ queue_drop_leave_cb (GtkDropTarget *drop_target, ClapperAppQueueList *self)
if (self->list_target) {
gtk_widget_set_margin_top (self->list_target, 0);
gtk_widget_set_margin_bottom (self->list_target, 0);
self->list_target = NULL;
}
}
@@ -230,55 +235,75 @@ static gboolean
queue_drop_cb (GtkDropTarget *drop_target, const GValue *value,
gdouble x, gdouble y, ClapperAppQueueList *self)
{
ClapperQueue *queue;
ClapperMediaItem *item;
GtkWidget *pickup;
ClapperPlayer *player = clapper_gtk_get_player_from_ancestor (GTK_WIDGET (self));
ClapperQueue *queue = clapper_player_get_queue (player);
ClapperMediaItem *item = NULL;
guint drop_index = 0;
gboolean success = FALSE;
if (G_UNLIKELY (self->list_target == NULL))
return FALSE;
if (self->list_target) {
GtkWidget *pickup = gtk_widget_get_first_child (self->list_target);
pickup = gtk_widget_get_first_child (self->list_target);
/* Reset margins on drop */
gtk_widget_set_margin_top (self->list_target, 0);
gtk_widget_set_margin_bottom (self->list_target, 0);
self->list_target = NULL;
/* Reset margins on drop */
gtk_widget_set_margin_top (self->list_target, 0);
gtk_widget_set_margin_bottom (self->list_target, 0);
self->list_target = NULL;
if (G_UNLIKELY (pickup == NULL) || !CLAPPER_APP_IS_MEDIA_ITEM_BOX (pickup))
return FALSE;
if (G_UNLIKELY (pickup == NULL) || !CLAPPER_APP_IS_MEDIA_ITEM_BOX (pickup))
return FALSE;
item = clapper_app_media_item_box_get_media_item (CLAPPER_APP_MEDIA_ITEM_BOX_CAST (pickup));
item = clapper_app_media_item_box_get_media_item (CLAPPER_APP_MEDIA_ITEM_BOX_CAST (pickup));
queue = CLAPPER_QUEUE (gst_object_get_parent (GST_OBJECT (item)));
if (!clapper_queue_find_item (queue, item, &drop_index))
return FALSE;
if (G_UNLIKELY (queue == NULL))
return FALSE;
if (!clapper_queue_find_item (queue, item, &drop_index)) {
gst_object_unref (queue);
return FALSE;
if (self->drop_after)
drop_index++;
} else {
drop_index = clapper_queue_get_n_items (queue); // Append
}
if (self->drop_after)
drop_index++;
/* Moving item with widget */
if (G_VALUE_HOLDS (value, GTK_TYPE_WIDGET)) {
ClapperAppQueueListDragData *drag_data;
drag_data = (ClapperAppQueueListDragData *) g_object_get_data (G_OBJECT (self), "drag-data");
/* Insert at different place */
if (item != drag_data->item) {
guint index = 0;
/* When user drops a widget and drag data is present, it means
* that it is an item drop within the same window */
if (drag_data) {
/* Insert at different place */
if (item != drag_data->item) {
guint index = 0;
if (clapper_queue_find_item (queue, drag_data->item, &index)) {
if (drop_index > index)
drop_index--;
if (clapper_queue_find_item (queue, drag_data->item, &index)) {
if (drop_index > index)
drop_index--;
clapper_queue_reposition_item (queue, drag_data->item, drop_index);
success = TRUE;
clapper_queue_reposition_item (queue, drag_data->item, drop_index);
success = TRUE;
}
}
} else { // Drop from another window
GtkWidget *drop_widget = GTK_WIDGET (g_value_get_object (value));
if (G_LIKELY (CLAPPER_APP_IS_MEDIA_ITEM_BOX (drop_widget))) {
ClapperMediaItem *drop_item;
ClapperQueue *src_queue;
drop_item = clapper_app_media_item_box_get_media_item (
CLAPPER_APP_MEDIA_ITEM_BOX_CAST (drop_widget));
if ((src_queue = CLAPPER_QUEUE (gst_object_get_parent (GST_OBJECT (drop_item))))) {
gst_object_ref (drop_item); // Ref so it survives queue removal
clapper_queue_remove_item (src_queue, drop_item);
clapper_queue_insert_item (queue, drop_item, drop_index);
success = TRUE;
gst_object_unref (drop_item); // Unref after placed in new queue
gst_object_unref (src_queue);
}
}
}
} else {
@@ -300,8 +325,6 @@ queue_drop_cb (GtkDropTarget *drop_target, const GValue *value,
}
}
gst_object_unref (queue);
return success;
}

View File

@@ -335,37 +335,36 @@ _resize_tick (GtkWidget *widget, GdkFrameClock *frame_clock,
return G_SOURCE_CONTINUE;
}
static gint
_get_gcd (gint width, gint height)
{
return (height > 0)
? _get_gcd (height, width % height)
: width;
}
static void
_calculate_win_resize (gint win_w, gint win_h,
gint vid_w, gint vid_h, gint *dest_w, gint *dest_h)
{
gdouble win_aspect = (gdouble) win_w / win_h;
gdouble vid_aspect = (gdouble) vid_w / vid_h;
/* Get the smallest integer ratio (e.g. 1920x1080 becomes 16x9) */
gint gcd = _get_gcd (vid_w, vid_h);
gint min_w = vid_w / gcd;
gint min_h = vid_h / gcd;
if (win_aspect < vid_aspect) {
while (!G_APPROX_VALUE (fmod (win_w, vid_aspect), 0, FLT_EPSILON))
win_w++;
/* Find the scale multiplier that best fits video */
gint scale_fit = MIN (win_w / min_w, win_h / min_h);
win_h = round ((gdouble) win_w / vid_aspect);
/* Calculate minimal allowed scale for video (-1 for ceil) */
gint scale_min = MAX (
(MIN_WINDOW_WIDTH + min_w - 1) / min_w,
(MIN_WINDOW_HEIGHT + min_h - 1) / min_h);
if (win_h < MIN_WINDOW_HEIGHT) {
_calculate_win_resize (G_MAXINT, MIN_WINDOW_HEIGHT, vid_w, vid_h, dest_w, dest_h);
return;
}
} else {
while (!G_APPROX_VALUE (fmod (win_h * vid_aspect, 1.0), 0, FLT_EPSILON))
win_h++;
/* Select best allowed scale multiplier */
gint scale = MAX (scale_fit, scale_min);
win_w = round ((gdouble) win_h * vid_aspect);
if (win_w < MIN_WINDOW_WIDTH) {
_calculate_win_resize (MIN_WINDOW_WIDTH, G_MAXINT, vid_w, vid_h, dest_w, dest_h);
return;
}
}
*dest_w = win_w;
*dest_h = win_h;
*dest_w = min_w * scale;
*dest_h = min_h * scale;
}
static void
@@ -1283,6 +1282,7 @@ clapper_app_window_constructed (GObject *object)
if ((proxy = clapper_enhancer_proxy_list_get_proxy_by_module (proxies, "clapper-mpris"))) {
clapper_enhancer_proxy_set_locally (proxy,
"app-id", CLAPPER_APP_ID,
"own-name", mpris_name,
"identity", CLAPPER_APP_NAME,
"desktop-entry", CLAPPER_APP_ID,

View File

@@ -91,6 +91,7 @@ clapperapp_sources = [
clapperapp_c_args = [
'-DG_LOG_DOMAIN="ClapperApp"',
'-DCLAPPER_APP_INTERNAL_COMPILATION',
'-DCLAPPER_DISABLE_DEPRECATION_WARNINGS',
'-DGST_USE_UNSTABLE_API',
]

View File

@@ -6,7 +6,7 @@
<property name="close-response">cancel</property>
<property name="default-response">add</property>
<property name="follows-content-size">false</property>
<property name="content-width">420</property>
<property name="content-width">460</property>
<property name="extra-child">
<object class="GtkListBox">
<property name="selection-mode">none</property>

View File

@@ -139,6 +139,7 @@ clappergtk_sources = [
clappergtk_c_args = [
'-DG_LOG_DOMAIN="ClapperGtk"',
'-DCLAPPER_GTK_COMPILATION',
'-DCLAPPER_DISABLE_DEPRECATION_WARNINGS',
'-DGST_USE_UNSTABLE_API',
]

View File

@@ -239,7 +239,6 @@ clapper_harvest_fill_from_cache (ClapperHarvest *self, ClapperEnhancerProxy *pro
g_free (filename);
if (!mapped_file) {
/* No error if cache disabled or version mismatch */
if (error) {
if (error->domain == G_FILE_ERROR && error->code == G_FILE_ERROR_NOENT)
GST_DEBUG_OBJECT (self, "No cached harvest found");
@@ -247,6 +246,9 @@ clapper_harvest_fill_from_cache (ClapperHarvest *self, ClapperEnhancerProxy *pro
GST_ERROR_OBJECT (self, "Could not use cached harvest, reason: %s", error->message);
g_error_free (error);
} else {
/* No error if cache disabled or version mismatch */
GST_DEBUG_OBJECT (self, "Import skipped");
}
return FALSE;

View File

@@ -28,7 +28,7 @@
G_BEGIN_DECLS
G_GNUC_INTERNAL
void clapper_media_item_update_from_tag_list (ClapperMediaItem *item, const GstTagList *tags, ClapperPlayer *player);
void clapper_media_item_update_from_tag_list (ClapperMediaItem *item, const GstTagList *tags, gboolean allow_overwrite, ClapperPlayer *player);
G_GNUC_INTERNAL
void clapper_media_item_update_from_discoverer_info (ClapperMediaItem *self, GstDiscovererInfo *info);

View File

@@ -66,7 +66,7 @@ typedef struct
{
ClapperMediaItem *item;
gboolean changed;
gboolean from_user;
gboolean allow_overwrite;
} ClapperMediaItemTagIterData;
enum
@@ -476,8 +476,8 @@ _tags_replace_func (const GstTagList *tags, const gchar *tag, ClapperMediaItemTa
break;
}
/* Users can only set non-existing tags */
if (data->from_user)
/* No overwrites allowed (e.g. user tags) */
if (!data->allow_overwrite)
break;
/* Check with tolerance for doubles */
@@ -522,7 +522,7 @@ _tags_replace_func (const GstTagList *tags, const gchar *tag, ClapperMediaItemTa
static gboolean
clapper_media_item_insert_tags_internal (ClapperMediaItem *self, const GstTagList *tags,
ClapperAppBus *app_bus, gboolean from_user, ClapperReactableItemUpdatedFlags *flags)
ClapperAppBus *app_bus, gboolean allow_overwrite, ClapperReactableItemUpdatedFlags *flags)
{
ClapperMediaItemTagIterData data;
gboolean title_changed = FALSE, cont_changed = FALSE;
@@ -531,7 +531,7 @@ clapper_media_item_insert_tags_internal (ClapperMediaItem *self, const GstTagLis
data.item = self;
data.changed = FALSE;
data.from_user = from_user;
data.allow_overwrite = allow_overwrite;
if (G_LIKELY (tags != self->tags))
gst_tag_list_foreach (tags, (GstTagForeachFunc) _tags_replace_func, &data);
@@ -540,12 +540,12 @@ clapper_media_item_insert_tags_internal (ClapperMediaItem *self, const GstTagLis
*flags |= CLAPPER_REACTABLE_ITEM_UPDATED_TAGS;
if ((title_changed = _refresh_tag_prop_unlocked (self, GST_TAG_TITLE,
(!from_user || self->title_is_parsed), &self->title))) {
(allow_overwrite || self->title_is_parsed), &self->title))) {
self->title_is_parsed = FALSE;
*flags |= CLAPPER_REACTABLE_ITEM_UPDATED_TITLE;
}
cont_changed = _refresh_tag_prop_unlocked (self, GST_TAG_CONTAINER_FORMAT,
!from_user, &self->container_format);
allow_overwrite, &self->container_format);
}
GST_OBJECT_UNLOCK (self);
@@ -618,7 +618,7 @@ clapper_media_item_populate_tags (ClapperMediaItem *self, const GstTagList *tags
if ((player = clapper_player_get_from_ancestor (GST_OBJECT_CAST (self))))
app_bus = player->app_bus;
changed = clapper_media_item_insert_tags_internal (self, tags, app_bus, TRUE, &flags);
changed = clapper_media_item_insert_tags_internal (self, tags, app_bus, FALSE, &flags);
if (changed && player) {
ClapperFeaturesManager *features_manager;
@@ -652,22 +652,18 @@ clapper_media_item_get_timeline (ClapperMediaItem *self)
void
clapper_media_item_update_from_tag_list (ClapperMediaItem *self, const GstTagList *tags,
ClapperPlayer *player)
gboolean allow_overwrite, ClapperPlayer *player)
{
GstTagScope scope = gst_tag_list_get_scope (tags);
ClapperReactableItemUpdatedFlags flags = 0;
gboolean changed = clapper_media_item_insert_tags_internal (self, tags, player->app_bus, allow_overwrite, &flags);
if (scope == GST_TAG_SCOPE_GLOBAL) {
ClapperReactableItemUpdatedFlags flags = 0;
gboolean changed = clapper_media_item_insert_tags_internal (self, tags, player->app_bus, FALSE, &flags);
if (changed) {
ClapperFeaturesManager *features_manager;
if (changed) {
ClapperFeaturesManager *features_manager;
if (player->reactables_manager)
clapper_reactables_manager_trigger_item_updated (player->reactables_manager, self, flags);
if ((features_manager = clapper_player_get_features_manager (player)))
clapper_features_manager_trigger_item_updated (features_manager, self);
}
if (player->reactables_manager)
clapper_reactables_manager_trigger_item_updated (player->reactables_manager, self, flags);
if ((features_manager = clapper_player_get_features_manager (player)))
clapper_features_manager_trigger_item_updated (features_manager, self);
}
}
@@ -693,7 +689,7 @@ clapper_media_item_update_from_discoverer_info (ClapperMediaItem *self, GstDisco
GstDiscovererContainerInfo *cinfo = (GstDiscovererContainerInfo *) sinfo;
if ((tags = gst_discoverer_container_info_get_tags (cinfo)))
changed |= clapper_media_item_insert_tags_internal (self, tags, player->app_bus, FALSE, &flags);
changed |= clapper_media_item_insert_tags_internal (self, tags, player->app_bus, TRUE, &flags);
}
gst_discoverer_stream_info_unref (sinfo);
}
@@ -783,8 +779,8 @@ clapper_media_item_update_from_parsed_playlist (ClapperMediaItem *self, GListSto
GST_OBJECT_LOCK (other_item);
if (other_item->tags)
clapper_media_item_update_from_tag_list (self, other_item->tags, player);
if (other_item->tags) // Tags are always GLOBAL here
clapper_media_item_update_from_tag_list (self, other_item->tags, TRUE, player);
/* Since its redirect now, we have to update title to describe new file instead of
* being a playlist title. If other item had parsed title, it also means that tags

View File

@@ -1038,7 +1038,9 @@ _handle_tag_msg (GstMessage *msg, ClapperPlayer *player)
}
player->pending_tags = gst_tag_list_ref (tags);
} else if (G_LIKELY (player->played_item != NULL)) {
clapper_media_item_update_from_tag_list (player->played_item, tags, player);
gboolean is_global = (gst_tag_list_get_scope (tags) == GST_TAG_SCOPE_GLOBAL);
if (is_global || player->stream_tags_allowed)
clapper_media_item_update_from_tag_list (player->played_item, tags, is_global, player);
}
gst_tag_list_unref (tags);
@@ -1116,10 +1118,28 @@ static inline void
_handle_stream_collection_msg (GstMessage *msg, ClapperPlayer *player)
{
GstStreamCollection *collection = NULL;
guint i, n_streams, n_video = 0, n_audio = 0, n_text = 0;
GST_INFO_OBJECT (player, "Stream collection");
gst_message_parse_stream_collection (msg, &collection);
n_streams = gst_stream_collection_get_size (collection);
for (i = 0; i < n_streams; ++i) {
GstStream *stream = gst_stream_collection_get_stream (collection, i);
GstStreamType stream_type = gst_stream_get_stream_type (stream);
if ((stream_type & GST_STREAM_TYPE_VIDEO) == GST_STREAM_TYPE_VIDEO)
n_video++;
else if ((stream_type & GST_STREAM_TYPE_AUDIO) == GST_STREAM_TYPE_AUDIO)
n_audio++;
else if ((stream_type & GST_STREAM_TYPE_TEXT) == GST_STREAM_TYPE_TEXT)
n_text++;
}
player->stream_tags_allowed = (n_video + n_audio + n_text == 1);
GST_DEBUG_OBJECT (player, "Stream tags allowed: %s", (player->stream_tags_allowed) ? "yes" : "no");
clapper_player_take_stream_collection (player, collection);
}
@@ -1214,9 +1234,10 @@ _handle_stream_start_msg (GstMessage *msg, ClapperPlayer *player)
clapper_player_playbin_update_current_decoders (player);
if (player->pending_tags) {
if (G_LIKELY (player->played_item != NULL))
clapper_media_item_update_from_tag_list (player->played_item, player->pending_tags, player);
if (G_LIKELY (player->played_item != NULL)) {
/* Pending tags come from "extractablesrc" and are always GLOBAL (preferred) */
clapper_media_item_update_from_tag_list (player->played_item, player->pending_tags, TRUE, player);
}
gst_clear_tag_list (&player->pending_tags);
}
if (player->pending_toc) {

View File

@@ -61,6 +61,7 @@ struct _ClapperPlayer
/* Pending tags/toc that arrive before stream start.
* To be applied to "played_item", thus no lock needed. */
gboolean stream_tags_allowed;
GstTagList *pending_tags;
GstToc *pending_toc;

View File

@@ -780,6 +780,7 @@ clapper_player_reset (ClapperPlayer *self, gboolean pending_dispose)
GST_OBJECT_UNLOCK (self);
self->stream_tags_allowed = FALSE;
gst_clear_tag_list (&self->pending_tags);
if (self->pending_toc) {

View File

@@ -134,6 +134,40 @@ clapper_playlist_type_find (GstTypeFind *tf, ClapperEnhancerProxy *proxy)
CLAPPER_PLAYLIST_MEDIA_TYPE, "enhancer", G_TYPE_STRING, module_name, NULL);
}
static gboolean
_is_claps_possible (const guint8 *data, gsize len)
{
gboolean possible = FALSE;
/* Linux file path */
if (len >= 2)
possible = (data[0] == '/' && g_ascii_isalnum (data[1]));
#ifdef G_OS_WIN32
/* Windows file path ("C:\..." or "D:/...") */
if (!possible && len >= 3) {
possible = (g_ascii_isalpha (data[0]) && data[1] == ':' && (data[2] == '\\' || data[2] == '/'));
/* Windows UNC path */
if (!possible)
possible = (data[0] == '\\' && data[1] == '\\' && g_ascii_isalnum (data[2]));
}
#endif
/* Check for URI (at least 3 characters before colon) */
if (!possible && len > 3) {
guint i = 0, end = MIN (len, 16);
while (i < end && g_ascii_isalpha (data[i]))
++i;
if (i >= 3 && i < end)
possible = (data[i] == ':');
}
return possible;
}
/* Finds text file of full file paths. Claps file might also use URIs,
* but in that case lets GStreamer built-in type finders find that as
* "text/uri-list" and we will handle it with this element too. */
@@ -141,26 +175,54 @@ static void
clapper_claps_type_find (GstTypeFind *tf, gpointer user_data G_GNUC_UNUSED)
{
const guint8 *data;
guint64 data_size = 16;
if ((data = gst_type_find_peek (tf, 0, 3))) {
gboolean possible;
if (!(data = gst_type_find_peek (tf, 0, data_size))) {
if ((data_size = gst_type_find_get_length (tf)) > 0)
data = gst_type_find_peek (tf, 0, data_size);
/* Linux file path */
possible = (data[0] == '/' && g_ascii_isalnum (data[1]));
if (!data)
return;
}
#ifdef G_OS_WIN32
/* Windows file path ("C:\..." or "D:/...") */
if (!possible)
possible = (g_ascii_isalpha (data[0]) && data[1] == ':' && (data[2] == '\\' || data[2] == '/'));
/* Continue parsing only if start looks like
* file path, otherwise reject data early */
if (_is_claps_possible (data, data_size)) {
guint probability = GST_TYPE_FIND_POSSIBLE;
data_size = 1024;
/* Windows UNC Path */
if (!possible)
possible = (data[0] == '\\' && data[1] == '\\' && g_ascii_isalnum (data[2]));
#endif
if (!(data = gst_type_find_peek (tf, 0, data_size)))
if ((data_size = gst_type_find_get_length (tf)) > 0)
data = gst_type_find_peek (tf, 0, data_size);
if (possible) {
GST_INFO ("Suggesting possible type: " CLAPPER_CLAPS_MEDIA_TYPE);
gst_type_find_suggest_empty_simple (tf, GST_TYPE_FIND_POSSIBLE, CLAPPER_CLAPS_MEDIA_TYPE);
if (data) {
const guint8 *line_start = data;
const guint8 *end = data + data_size;
guint pathlike = 0, total = 0;
while (line_start < end) {
const guint8 *newline = memchr (line_start, '\n', end - line_start);
gsize len = newline ? (newline - line_start) : (end - line_start);
if (len > 0) {
++total;
if (_is_claps_possible (line_start, len))
++pathlike;
}
if (!newline)
break;
line_start = newline + 1;
}
/* Multiple lines and most of them looks like a file path */
if (total > 1 && pathlike >= MAX (2, total * 3 / 4))
probability = GST_TYPE_FIND_LIKELY;
GST_INFO ("Suggesting %s type: " CLAPPER_CLAPS_MEDIA_TYPE,
(probability >= GST_TYPE_FIND_LIKELY) ? "likely" : "possible");
gst_type_find_suggest_empty_simple (tf, probability, CLAPPER_CLAPS_MEDIA_TYPE);
}
}
}

View File

@@ -171,6 +171,7 @@ clapper_sources = [
clapper_c_args = [
'-DG_LOG_DOMAIN="Clapper"',
'-DCLAPPER_COMPILATION',
'-DCLAPPER_DISABLE_DEPRECATION_WARNINGS',
'-DGST_USE_UNSTABLE_API',
'-DG_SETTINGS_ENABLE_BACKEND',
]

View File

@@ -41,7 +41,7 @@
#define @CLAPPER_API@_API _@CLAPPER_API@_VISIBILITY
#if !defined(@CLAPPER_API@_COMPILATION)
#if !defined(CLAPPER_DISABLE_DEPRECATION_WARNINGS)
#define @CLAPPER_API@_DEPRECATED G_DEPRECATED _@CLAPPER_API@_VISIBILITY
#define @CLAPPER_API@_DEPRECATED_FOR(f) G_DEPRECATED_FOR(f) _@CLAPPER_API@_VISIBILITY
#else