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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vlcsync
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Summary: Utility for synchronize multiple instances of VLC. Supports seek, play and pause.
5
5
  Home-page: https://github.com/mrkeuz/vlcsync/
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "vlcsync"
3
- version = "0.3.0"
3
+ version = "0.3.1"
4
4
  description = "Utility for synchronize multiple instances of VLC. Supports seek, play and pause. "
5
5
  authors = ["mrkeuz <mrkeuz@users.noreply.github.com>"]
6
6
  license = "MIT"
@@ -15,7 +15,7 @@ entry_points = \
15
15
 
16
16
  setup_kwargs = {
17
17
  'name': 'vlcsync',
18
- 'version': '0.3.0',
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![vlcsync](./docs/vlcsync.gif)\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.0"
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
- print(f"\nVlc state change detected from ({vlc_id})")
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
- is_change = not cur_state.same(prev_state)
82
+ full_same, playlist_same = cur_state.same(prev_state)
83
83
 
84
84
  # Return cur_state for reduce further socket communications
85
- return is_change, cur_state
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, source: Vlc):
217
+ def sync_all(self, state: State, source_vlc: Vlc):
218
218
  logger.debug(">" * 60)
219
- logger.debug(f"Detect change to {state} from {source.vlc_id}")
220
- logger.debug(f" old --> {source.prev_state} ")
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(source.prev_state.vid_start_at - state.vid_start_at)}")
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, source)
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
- return (self.same_play_state(other) and
55
- self.same_playlist_item(other) and
56
- (
57
- self.play_in_same_pos(other) or
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