vlcsync 0.3.0__tar.gz → 0.3.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {vlcsync-0.3.0 → vlcsync-0.3.1}/PKG-INFO +1 -1
- {vlcsync-0.3.0 → vlcsync-0.3.1}/pyproject.toml +1 -1
- {vlcsync-0.3.0 → vlcsync-0.3.1}/setup.py +1 -1
- {vlcsync-0.3.0 → vlcsync-0.3.1}/vlcsync/cli.py +3 -3
- {vlcsync-0.3.0 → vlcsync-0.3.1}/vlcsync/syncer.py +24 -9
- {vlcsync-0.3.0 → vlcsync-0.3.1}/vlcsync/vlc.py +13 -13
- {vlcsync-0.3.0 → vlcsync-0.3.1}/vlcsync/vlc_finder.py +2 -1
- {vlcsync-0.3.0 → vlcsync-0.3.1}/vlcsync/vlc_state.py +4 -8
- {vlcsync-0.3.0 → vlcsync-0.3.1}/LICENSE.md +0 -0
- {vlcsync-0.3.0 → vlcsync-0.3.1}/README.md +0 -0
- {vlcsync-0.3.0 → vlcsync-0.3.1}/vlcsync/__init__.py +0 -0
- {vlcsync-0.3.0 → vlcsync-0.3.1}/vlcsync/cli_utils.py +0 -0
- {vlcsync-0.3.0 → vlcsync-0.3.1}/vlcsync/main.py +0 -0
- {vlcsync-0.3.0 → vlcsync-0.3.1}/vlcsync/vlc_socket.py +0 -0
@@ -15,7 +15,7 @@ entry_points = \
|
|
15
15
|
|
16
16
|
setup_kwargs = {
|
17
17
|
'name': 'vlcsync',
|
18
|
-
'version': '0.3.
|
18
|
+
'version': '0.3.1',
|
19
19
|
'description': 'Utility for synchronize multiple instances of VLC. Supports seek, play and pause. ',
|
20
20
|
'long_description': 'VLC Sync\n========\n\nUtility for synchronize multiple instances of VLC. Supports seek, play and pause/stop, playlist and volume sync. \n \n\n#### Motivation\n\nStrongly inspired by F1 streams with extra driver tracking data streams. Did [not find](#alternatives) reasonable alternative for Linux for playing several videos synchronously. So decided to write my own solution.\n\n## Install\n\n```shell\npip3 install -U vlcsync\n```\n\nor \n\n- Download [binary release](https://github.com/mrkeuz/vlcsync/releases) for Windows 7/10 \n NOTE: On some systems there are false positive Antivirus warnings [issues](https://github.com/mrkeuz/vlcsync/issues/1).\n In this case use [alternative way](./docs/install.md#windows-detailed-instructions) to install. \n\n## Run\n\n`Vlc` players should open with `--rc-host 127.0.0.42` option OR configured properly from gui (see [how configure vlc](./docs/vlc_setup.md)) \n\n```shell\n\n# Run vlc players \n$ vlc --rc-host 127.0.0.42 SomeMedia1.mkv &\n$ vlc --rc-host 127.0.0.42 SomeMedia2.mkv &\n$ vlc --rc-host 127.0.0.42 SomeMedia3.mkv &\n\n# vlcsync will monitor and syncing all players\n$ vlcsync\n\n# Started from version 0.2.0\n\n# For control remote vlc instances, \n# remote port should be open and rc interface listen on 0.0.0.0\n$ vlcsync --rc-host 192.168.1.100:12345 --rc-host 192.168.1.50:54321\n\n# For disable local discovery (only remote instances)\n$ vlcsync --no-local-discovery --rc-host 192.168.1.100:12345\n\n# Started from version 0.3.0 (playlists sync)\n# Support volume sync for exotic cases\n$ vlcsync --volume-sync\n\n# For help and see all options\n$ vlcsync --help\n```\n\n## Awesome \n\nAwesome [use-case](./docs/awesome.md) ideas\n\n## Demo\n\n\n\n## Limitations \n\n- Frame-to-frame sync NOT provided. `vlc` does not have precise controlling via `rc` interface out of box. \n Difference between videos can be **up to ~0.5 seconds** in worst case. Especially when playing from network share, \n due buffering time and network latency.\n\n- Currently, tested on:\n - Linux (Ubuntu 20.04)\n - Windows 7 (32-bit)\n - Windows 10 (64-bit)\n\n## Alternatives\n\n- [vlc](https://www.videolan.org/vlc/index.ru.html) \n - There is a [netsync](https://wiki.videolan.org/Documentation:Modules/netsync/) but seem only master-slave (tried, but not working by some reason)\n - Open additional media. Seems feature broken in vlc 3 (also afaik limited only 2 streams) \n- [Syncplay](https://github.com/Syncplay/syncplay) - very promised, but little [complicated](https://github.com/Syncplay/syncplay/discussions/463) for sync different videos\n- [bino](https://bino3d.org/) - working, very strange controls, file dialog not working and only fullscreen\n- [gridplayer](https://github.com/vzhd1701/gridplayer) - low fps by some reason\n- [mpv](https://github.com/mpv-player/mpv) - with [mixing multiple videos](https://superuser.com/a/1325668/1272472) in one window. Unfortunately does not support multiple screens\n- [AVPlayer](http://www.awesomevideoplayer.com/) - only Win, macOS, up to 4 videos in free version\n\n## Contributing\n\nAny thoughts, ideas and contributions welcome!\n\nA special thanks to **KorDen32** for inspiration! <img src="./docs/F1.svg" alt="F1" width="45"/>\n\nEnjoy!\n',
|
21
21
|
'author': 'mrkeuz',
|
@@ -12,7 +12,7 @@ from vlcsync.vlc_finder import print_exc
|
|
12
12
|
from vlcsync.vlc_state import VlcId
|
13
13
|
|
14
14
|
# Also ref in project.toml
|
15
|
-
__version__ = "0.3.
|
15
|
+
__version__ = "0.3.1"
|
16
16
|
|
17
17
|
|
18
18
|
@click.command
|
@@ -43,7 +43,7 @@ __version__ = "0.3.0"
|
|
43
43
|
""")
|
44
44
|
def main(rc_host_list: Set[VlcId], no_local_discover, volume_sync):
|
45
45
|
"""Utility for synchronize multiple instances of VLC. Supports seek, play and pause."""
|
46
|
-
print("Vlcsync started...")
|
46
|
+
print("Vlcsync started...", flush=True)
|
47
47
|
|
48
48
|
app_config = AppConfig(rc_host_list, no_local_discover, volume_sync)
|
49
49
|
time.sleep(2) # Wait instances
|
@@ -57,4 +57,4 @@ def main(rc_host_list: Set[VlcId], no_local_discover, volume_sync):
|
|
57
57
|
sys.exit(0)
|
58
58
|
except Exception:
|
59
59
|
print_exc()
|
60
|
-
print("Exception detected. Restart sync...")
|
60
|
+
print("Exception detected. Restart sync...", flush=True)
|
@@ -3,11 +3,11 @@ from __future__ import annotations
|
|
3
3
|
from dataclasses import dataclass
|
4
4
|
import sys
|
5
5
|
import time
|
6
|
-
from typing import Set
|
6
|
+
from typing import Set, List
|
7
7
|
|
8
8
|
from loguru import logger
|
9
9
|
|
10
|
-
from vlcsync.vlc import VLC_IFACE_IP, VlcProcs
|
10
|
+
from vlcsync.vlc import VLC_IFACE_IP, VlcProcs, Vlc
|
11
11
|
from vlcsync.vlc_finder import LocalProcessFinderProvider, ExtraHostFinder
|
12
12
|
from vlcsync.vlc_socket import VlcConnectionError
|
13
13
|
|
@@ -30,21 +30,21 @@ class Syncer:
|
|
30
30
|
vlc_finders = set()
|
31
31
|
if not self.app_config.no_local_discovery:
|
32
32
|
vlc_finders.add(LocalProcessFinderProvider(VLC_IFACE_IP))
|
33
|
-
print(f" Discover instances on {VLC_IFACE_IP} iface...")
|
33
|
+
print(f" Discover instances on {VLC_IFACE_IP} iface...", flush=True)
|
34
34
|
else:
|
35
|
-
print(" Local discovery vlc instances DISABLED...")
|
35
|
+
print(" Local discovery vlc instances DISABLED...", flush=True)
|
36
36
|
|
37
37
|
if app_config.extra_rc_hosts:
|
38
38
|
vlc_finders.add(ExtraHostFinder(app_config.extra_rc_hosts))
|
39
39
|
for rc_host in app_config.extra_rc_hosts:
|
40
40
|
rc_host: VlcId
|
41
|
-
print(f" Manual host defined {rc_host.addr}:{rc_host.port}")
|
41
|
+
print(f" Manual host defined {rc_host.addr}:{rc_host.port}", flush=True)
|
42
42
|
else:
|
43
|
-
print(""" Manual vlc addresses ("--rc-host" args) NOT provided...""")
|
43
|
+
print(""" Manual vlc addresses ("--rc-host" args) NOT provided...""", flush=True)
|
44
44
|
|
45
45
|
if not vlc_finders:
|
46
46
|
print("\nTarget vlc instances not selected (nor autodiscover, nor manually). \n"
|
47
|
-
"""See: "vlcsync --help" for more info""")
|
47
|
+
"""See: "vlcsync --help" for more info""", flush=True)
|
48
48
|
sys.exit(1)
|
49
49
|
|
50
50
|
self.env = VlcProcs(vlc_finders)
|
@@ -69,11 +69,26 @@ class Syncer:
|
|
69
69
|
|
70
70
|
def sync_playstate(self):
|
71
71
|
for vlc_id, vlc in self.env.all_vlc.items():
|
72
|
-
is_changed, state = vlc.is_state_change()
|
72
|
+
is_changed, state, playlist_changed = vlc.is_state_change()
|
73
73
|
|
74
74
|
if is_changed:
|
75
|
-
|
75
|
+
# Workaround Save volumes.
|
76
|
+
# When playlist items changed ALSO happen volumes sync by some reason. But SHOULD NOT!
|
77
|
+
volumes: List[tuple[Vlc, int]] = []
|
78
|
+
if not self.app_config.volume_sync and playlist_changed:
|
79
|
+
volumes = [(vlc1, vlc1.volume()) for _, vlc1 in self.env.all_vlc.items()]
|
80
|
+
#
|
81
|
+
|
82
|
+
print(f"\nVlc state change detected from ({vlc_id})", flush=True)
|
76
83
|
self.env.sync_all(state, vlc)
|
84
|
+
|
85
|
+
# Restore volumes if needed
|
86
|
+
if volumes:
|
87
|
+
for vlc_next, volume in volumes:
|
88
|
+
vlc_next.set_volume(volume)
|
89
|
+
# Trying to fix endless resyncing hang
|
90
|
+
# Cannot find reproduce steps for that
|
91
|
+
time.sleep(0.5)
|
77
92
|
break
|
78
93
|
|
79
94
|
def sync_volume(self):
|
@@ -46,7 +46,7 @@ class Vlc:
|
|
46
46
|
def volume(self) -> Optional[int]:
|
47
47
|
vol = self.vlc_conn.cmd("volume")
|
48
48
|
if vol.strip() != '':
|
49
|
-
return int(vol)
|
49
|
+
return int(float(vol.replace(",",'.')))
|
50
50
|
else:
|
51
51
|
return None
|
52
52
|
|
@@ -79,10 +79,10 @@ class Vlc:
|
|
79
79
|
def is_state_change(self) -> (bool, State):
|
80
80
|
cur_state: State = self.cur_state()
|
81
81
|
prev_state: State = self.prev_state
|
82
|
-
|
82
|
+
full_same, playlist_same = cur_state.same(prev_state)
|
83
83
|
|
84
84
|
# Return cur_state for reduce further socket communications
|
85
|
-
return
|
85
|
+
return not full_same, cur_state, not playlist_same
|
86
86
|
|
87
87
|
def sync_to(self, new_state: State, source: Vlc):
|
88
88
|
|
@@ -195,7 +195,7 @@ class VlcProcs:
|
|
195
195
|
for vlc_id in vlc_candidates:
|
196
196
|
if vlc_id not in self._vlc_instances.keys():
|
197
197
|
if vlc := self.try_connect(vlc_id):
|
198
|
-
print(f"Found active instance {vlc_id}, with state {vlc.cur_state()}")
|
198
|
+
print(f"Found active instance {vlc_id}, with state {vlc.cur_state()}", flush=True)
|
199
199
|
self._vlc_instances[vlc_id] = vlc
|
200
200
|
|
201
201
|
logger.debug(f"Compute all_vlc (took {time.time() - start:.3f})...")
|
@@ -207,31 +207,31 @@ class VlcProcs:
|
|
207
207
|
return Vlc(vlc_id)
|
208
208
|
except Exception as e:
|
209
209
|
logger.opt(exception=True).debug("Cannot connect to {0}, cause: {1}", vlc_id, e)
|
210
|
-
print(f"Cannot connect to {vlc_id} socket, cause: {e}. Skipping. Enable debug for more info. See --help. ")
|
210
|
+
print(f"Cannot connect to {vlc_id} socket, cause: {e}. Skipping. Enable debug for more info. See --help. ", flush=True)
|
211
211
|
return None
|
212
212
|
|
213
213
|
@property
|
214
214
|
def all_vlc(self) -> dict[VlcId, Vlc]:
|
215
215
|
return self._vlc_instances.copy() # copy: for thread safe
|
216
216
|
|
217
|
-
def sync_all(self, state: State,
|
217
|
+
def sync_all(self, state: State, source_vlc: Vlc):
|
218
218
|
logger.debug(">" * 60)
|
219
|
-
logger.debug(f"Detect change to {state} from {
|
220
|
-
logger.debug(f" old --> {
|
219
|
+
logger.debug(f"Detect change to {state} from {source_vlc.vlc_id}")
|
220
|
+
logger.debug(f" old --> {source_vlc.prev_state} ")
|
221
221
|
logger.debug(f" new --> {state} ")
|
222
|
-
logger.debug(f" Time diff abs(old - new) {abs(
|
222
|
+
logger.debug(f" Time diff abs(old - new) {abs(source_vlc.prev_state.vid_start_at - state.vid_start_at)}")
|
223
223
|
logger.debug("<" * 60)
|
224
224
|
logger.debug("")
|
225
|
-
print(">>> Sync players...")
|
225
|
+
print(">>> Sync players...", flush=True)
|
226
226
|
|
227
227
|
for next_pid, next_vlc in self.all_vlc.items():
|
228
228
|
next_vlc: Vlc
|
229
|
-
print(f" Sync {next_pid} to {state}")
|
230
|
-
next_vlc.sync_to(state,
|
229
|
+
print(f" Sync {next_pid} to {state}", flush=True)
|
230
|
+
next_vlc.sync_to(state, source_vlc)
|
231
231
|
print()
|
232
232
|
|
233
233
|
def dereg(self, vlc_id: VlcId):
|
234
|
-
print(f"Detect vlc instance closed {vlc_id}")
|
234
|
+
print(f"Detect vlc instance closed {vlc_id}", flush=True)
|
235
235
|
if vlc_to_close := self._vlc_instances.pop(vlc_id, None):
|
236
236
|
vlc_to_close.close()
|
237
237
|
|
@@ -90,11 +90,12 @@ class LocalProcessFinderProvider(IVlcListFinder):
|
|
90
90
|
|
91
91
|
def print_exc():
|
92
92
|
print("-" * 60)
|
93
|
-
print("Exception in user code: ")
|
93
|
+
print("Exception in user code: ", flush=True)
|
94
94
|
print("-" * 60)
|
95
95
|
traceback.print_exc(file=sys.stdout)
|
96
96
|
print("-" * 60)
|
97
97
|
print()
|
98
|
+
sys.stdout.flush()
|
98
99
|
|
99
100
|
|
100
101
|
class ExtraHostFinder(IVlcListFinder):
|
@@ -51,14 +51,10 @@ class State:
|
|
51
51
|
vid_start_at: float = field(repr=False)
|
52
52
|
|
53
53
|
def same(self, other: State):
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
self.pause_is_same_pos(other) or
|
59
|
-
self.both_stopped(other)
|
60
|
-
)
|
61
|
-
)
|
54
|
+
playlist_same = self.same_playlist_item(other)
|
55
|
+
full_same = (self.same_play_state(other) and playlist_same and (
|
56
|
+
self.play_in_same_pos(other) or self.pause_is_same_pos(other) or self.both_stopped(other)))
|
57
|
+
return full_same, playlist_same
|
62
58
|
|
63
59
|
def same_play_state(self, other: State):
|
64
60
|
return self.play_state == other.play_state
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|