Convenient ways of opening external subtitles

Play video with external subtiles by:
* selecting and opening both video+subs from file chooser/manager
* dropping subtitles file on player window
* opening subtiles from file chooser/manager while video plays
* send their file path/uri to player via WebSocket message
This commit is contained in:
Rafał Dzięgiel
2021-04-07 13:52:51 +02:00
parent 28d8986072
commit b15b94fc90
7 changed files with 92 additions and 86 deletions

View File

@@ -12,10 +12,7 @@ class ClapperApp extends AppBase
{ {
super._init(); super._init();
this.set_flags( this.flags |= Gio.ApplicationFlags.HANDLES_OPEN;
this.get_flags()
| Gio.ApplicationFlags.HANDLES_OPEN
);
} }
vfunc_startup() vfunc_startup()
@@ -39,26 +36,7 @@ class ClapperApp extends AppBase
{ {
super.vfunc_open(files, hint); super.vfunc_open(files, hint);
const { player } = this.active_window.get_child(); this._openFiles(files);
if(!this.doneFirstActivate)
player._preparePlaylist(files);
else
player.set_playlist(files);
this.activate(); this.activate();
} }
_onWindowShow(window)
{
super._onWindowShow(window);
const { player } = this.active_window.get_child();
const success = player.playlistWidget.nextTrack();
if(!success)
debug('playlist is empty');
player.widget.grab_focus();
}
}); });

View File

@@ -59,6 +59,17 @@ class ClapperAppBase extends Gtk.Application
); );
} }
_openFiles(files)
{
const [playlist, subs] = Misc.parsePlaylistFiles(files);
const { player } = this.active_window.get_child();
if(playlist && playlist.length)
player.set_playlist(playlist);
if(subs)
player.set_subtitles(subs);
}
_onFirstActivate() _onFirstActivate()
{ {
const gtkSettings = Gtk.Settings.get_default(); const gtkSettings = Gtk.Settings.get_default();
@@ -71,19 +82,9 @@ class ClapperAppBase extends Gtk.Application
this._onIconThemeChanged(gtkSettings); this._onIconThemeChanged(gtkSettings);
gtkSettings.connect('notify::gtk-theme-name', this._onThemeChanged.bind(this)); gtkSettings.connect('notify::gtk-theme-name', this._onThemeChanged.bind(this));
gtkSettings.connect('notify::gtk-icon-theme-name', this._onIconThemeChanged.bind(this)); gtkSettings.connect('notify::gtk-icon-theme-name', this._onIconThemeChanged.bind(this));
this.windowShowSignal = this.active_window.connect(
'show', this._onWindowShow.bind(this)
);
this.doneFirstActivate = true; this.doneFirstActivate = true;
} }
_onWindowShow(window)
{
window.disconnect(this.windowShowSignal);
this.windowShowSignal = null;
}
_onThemeChanged(gtkSettings) _onThemeChanged(gtkSettings)
{ {
const theme = gtkSettings.gtk_theme_name; const theme = gtkSettings.gtk_theme_name;

View File

@@ -24,10 +24,7 @@ class ClapperFileChooser extends Gtk.FileChooserNative
filter.add_mime_type('video/*'); filter.add_mime_type('video/*');
filter.add_mime_type('audio/*'); filter.add_mime_type('audio/*');
filter.add_mime_type('application/claps'); filter.add_mime_type('application/claps');
this.subsMimes = [ Misc.subsMimes.forEach(mime => filter.add_mime_type(mime));
'application/x-subrip',
];
this.subsMimes.forEach(mime => filter.add_mime_type(mime));
this.add_filter(filter); this.add_filter(filter);
this.responseSignal = this.connect('response', this._onResponse.bind(this)); this.responseSignal = this.connect('response', this._onResponse.bind(this));
@@ -46,36 +43,26 @@ class ClapperFileChooser extends Gtk.FileChooserNative
if(response === Gtk.ResponseType.ACCEPT) { if(response === Gtk.ResponseType.ACCEPT) {
const files = this.get_files(); const files = this.get_files();
const playlist = []; const filesArray = [];
let index = 0; let index = 0;
let file; let file;
let subs;
while((file = files.get_item(index))) { while((file = files.get_item(index))) {
const filename = file.get_basename(); filesArray.push(file);
const [type, isUncertain] = Gio.content_type_guess(filename, null);
if(this.subsMimes.includes(type)) {
subs = file;
files.remove(index);
continue;
}
playlist.push(file);
index++; index++;
} }
const { player } = this.get_transient_for().get_child(); const { application } = this.transient_for;
const isHandlesOpen = Boolean(
application.flags & Gio.ApplicationFlags.HANDLES_OPEN
);
if(playlist.length) /* Remote app does not handle open */
player.set_playlist(playlist); if(isHandlesOpen)
application.open(filesArray, "");
/* add subs to single selected video else
or to already playing file */ application._openFiles(filesArray);
if(subs && !files.get_item(1))
player.set_subtitles(subs);
} }
this.unref(); this.unref();

View File

@@ -5,6 +5,9 @@ const { debug } = Debug;
var appName = 'Clapper'; var appName = 'Clapper';
var appId = 'com.github.rafostar.Clapper'; var appId = 'com.github.rafostar.Clapper';
var subsMimes = [
'application/x-subrip',
];
var clapperPath = null; var clapperPath = null;
var clapperVersion = null; var clapperVersion = null;
@@ -96,6 +99,33 @@ function getFormattedTime(time, showHours)
return parsed + `${minutes}:${seconds}`; return parsed + `${minutes}:${seconds}`;
} }
function parsePlaylistFiles(filesArray)
{
let index = filesArray.length;
let subs = null;
while(index--) {
const file = filesArray[index];
const filename = (file.get_basename)
? file.get_basename()
: file.substring(file.lastIndexOf('/') + 1);
const [type, isUncertain] = Gio.content_type_guess(filename, null);
if(subsMimes.includes(type)) {
subs = file;
filesArray.splice(index, 1);
}
}
/* We only support single video
* with external subtitles */
if(subs && filesArray.length > 1)
subs = null;
return [filesArray, subs];
}
function encodeHTML(text) function encodeHTML(text)
{ {
return text.replace(/&/g, '&') return text.replace(/&/g, '&')

View File

@@ -141,24 +141,14 @@ class ClapperPlayer extends PlayerBase
this.set_playlist(playlist); this.set_playlist(playlist);
} }
_preparePlaylist(playlist) set_playlist(playlist)
{ {
this.playlistWidget.removeAll(); this.playlistWidget.removeAll();
for(let source of playlist) { for(let source of playlist) {
const uri = (source.get_uri != null) const uri = this._getSourceUri(source);
? source.get_uri()
: Gst.uri_is_valid(source)
? source
: Gst.filename_to_uri(source);
this.playlistWidget.addItem(uri); this.playlistWidget.addItem(uri);
} }
}
set_playlist(playlist)
{
this._preparePlaylist(playlist);
const firstTrack = this.playlistWidget.get_row_at_index(0); const firstTrack = this.playlistWidget.get_row_at_index(0);
if(!firstTrack) return; if(!firstTrack) return;
@@ -168,9 +158,7 @@ class ClapperPlayer extends PlayerBase
set_subtitles(source) set_subtitles(source)
{ {
const uri = (source.get_uri) const uri = this._getSourceUri(source);
? source.get_uri()
: source;
this.set_subtitle_uri(uri); this.set_subtitle_uri(uri);
this.set_subtitle_track_enabled(true); this.set_subtitle_track_enabled(true);
@@ -292,6 +280,7 @@ class ClapperPlayer extends PlayerBase
case 'play': case 'play':
case 'pause': case 'pause':
case 'set_playlist': case 'set_playlist':
case 'set_subtitles':
this[action](value); this[action](value);
break; break;
case 'toggle_maximized': case 'toggle_maximized':
@@ -315,6 +304,15 @@ class ClapperPlayer extends PlayerBase
} }
} }
_getSourceUri(source)
{
return (source.get_uri != null)
? source.get_uri()
: Gst.uri_is_valid(source)
? source
: Gst.filename_to_uri(source);
}
_performCloseCleanup(window) _performCloseCleanup(window)
{ {
window.disconnect(this.closeRequestSignal); window.disconnect(this.closeRequestSignal);

View File

@@ -23,17 +23,27 @@ class ClapperPlayerRemote extends GObject.Object
const uris = []; const uris = [];
/* We can not send GioFiles via WebSocket */ /* We can not send GioFiles via WebSocket */
for(let source of playlist) { for(let source of playlist)
const uri = (source.get_uri != null) uris.push(this._getSourceUri(source));
? source.get_uri()
: source;
uris.push(uri);
}
this.webclient.sendMessage({ this.webclient.sendMessage({
action: 'set_playlist', action: 'set_playlist',
value: uris value: uris
}); });
} }
set_subtitles(source)
{
this.webclient.sendMessage({
action: 'set_subtitles',
value: this._getSourceUri(source)
});
}
_getSourceUri(source)
{
return (source.get_uri != null)
? source.get_uri()
: source;
}
}); });

View File

@@ -1,4 +1,4 @@
const { Gdk, GLib, GObject, Gst, GstClapper, Gtk } = imports.gi; const { Gdk, Gio, GLib, GObject, Gst, GstClapper, Gtk } = imports.gi;
const { Controls } = imports.src.controls; const { Controls } = imports.src.controls;
const Debug = imports.src.debug; const Debug = imports.src.debug;
const Dialogs = imports.src.dialogs; const Dialogs = imports.src.dialogs;
@@ -907,15 +907,17 @@ class ClapperWidget extends Gtk.Grid
_onDataDrop(dropTarget, value, x, y) _onDataDrop(dropTarget, value, x, y)
{ {
const playlist = value.split(/\r?\n/).filter(uri => { const files = value.split(/\r?\n/).filter(uri => {
return Gst.uri_is_valid(uri); return Gst.uri_is_valid(uri);
}); });
if(!playlist.length) if(!files.length)
return false; return false;
this.player.set_playlist(playlist); for(let index in files)
this.root.application.activate(); files[index] = Gio.File.new_for_uri(files[index]);
this.root.application.open(files, "");
return true; return true;
} }