Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
name: Build
on: [push, pull_request]
on:
push:
pull_request:
workflow_dispatch:

jobs:
windows32:
Expand Down
21 changes: 20 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
-->

# Syncplay
![GitHub Actions build status](https://github.com/Syncplay/syncplay/workflows/Build/badge.svg)
![GitHub Actions build status](https://github.com/lssb/syncplay/workflows/Build/badge.svg)

Solution to synchronize video playback across multiple instances of mpv, VLC, MPC-HC, MPC-BE and mplayer2 over the Internet.

Expand All @@ -32,6 +32,25 @@ https://syncplay.pl
## Download
https://syncplay.pl/download/

## This fork (lssb-strm)

This is a personal build of Syncplay 1.7.7 for watching **`.strm` libraries** (Emby / Jellyfin / NAS pointers) together. It does not replace upstream Syncplay; the official project remains at https://github.com/Syncplay/syncplay.

Related report: [Syncplay/syncplay#807](https://github.com/Syncplay/syncplay/issues/807).

### What changed

- **Shared playlist identity.** mpv treats `.strm` as a playlist and reports the inner HTTP URL as the current file. This build keeps the `.strm` filename as the room identity for 25 seconds after open, so two people can each play their own NAS URL while staying on the same playlist item.
- **Sidecar subtitles.** Syncplay starts mpv with a bundled `strm-sidecars.lua` (`--scripts-append`). Matching `.srt` / `.ass` files next to the `.strm` are loaded automatically. You do not need to change the mpv install.
- **Server.** No server change. A 1.7.7 server is fine.
- **About dialog.** Window title is `Syncplay v1.7.7 lssb-strm`. Help → About names **lssb** and **白小九日**.

### 本次修改(中文)

mpv 会把 `.strm` 展开成里面的 URL,Syncplay 会把它当成另一个文件,同名字幕也找不到。本 fork 做了两件事:房间里继续用 `.strm` 文件名(打开后 25 秒内完成绑定),启动 mpv 时带上内置脚本,从 `.strm` 所在目录加载同名字幕。服务端不用改。

Windows 64 位便携包请到本仓库 [Actions](https://github.com/lssb/syncplay/actions) 下载 `Syncplay_1.7.7_x64_Portable`(工作流名:Build,分支 `strm-identity`)。

## What does it do

Syncplay synchronises the position and play state of multiple media players so that the viewers can watch the same thing at the same time.
Expand Down
1 change: 1 addition & 0 deletions buildPy2exe.py
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,7 @@ def run(self):

resources = [
"syncplay/resources/syncplayintf.lua",
"syncplay/resources/strm-sidecars.lua",
"syncplay/resources/license.rtf",
"syncplay/resources/third-party-notices.txt"
]
Expand Down
6 changes: 5 additions & 1 deletion syncplay/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
version = '1.7.7'
revision = ' development'
revision = ' lssb-strm'
milestone = 'Yoitsu'
release_number = '120'
projectURL = 'https://syncplay.pl/'
custom_build_note = (
"本版本包含 .strm 播放列表身份与同名字幕补丁,由 lssb、白小九日 修改。<br />"
"This build adds .strm playlist identity and sidecar subtitle loading. Modified by lssb and 白小九日."
)
19 changes: 12 additions & 7 deletions syncplay/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
pass

from syncplay import utils, constants, version
from syncplay.strm import StrmIdentityTracker
from syncplay.constants import PRIVACY_SENDHASHED_MODE, PRIVACY_DONTSEND_MODE, \
PRIVACY_HIDDENFILENAME
from syncplay.messages import getMissingStrings, getMessage, isNoOSDMessage
Expand Down Expand Up @@ -101,6 +102,7 @@ def __init__(self, playerClass, ui, config):
self.lastRewindTime = None
self.lastUpdatedFileTime = None
self.lastAdvanceTime = None
self.strmIdentity = StrmIdentityTracker()
self.fileOpenBeforeChangingPlaylistIndex = None
self.waitingToLoadNewfile = False
self.waitingToLoadNewfileSince = None
Expand Down Expand Up @@ -562,18 +564,20 @@ def eofReportedByPlayer(self):
def updateFile(self, filename, duration, path):
self.lastUpdatedFileTime = time.time()
newPath = ""
if utils.isURL(path):
filename = path
if not path:
return
try:
size = os.path.getsize(path)
except:
filename, path, strmSize = self.strmIdentity.canonicalize(filename, path)
if strmSize is not None:
size = strmSize
else:
try:
path = path.decode('utf-8')
size = os.path.getsize(path)
except:
size = 0
try:
path = path.decode('utf-8')
size = os.path.getsize(path)
except:
size = 0
if not utils.isURL(path) and os.path.exists(path):
self.fileSwitch.notifyUserIfFileNotInMediaDirectory(filename, path)
filename, size = self.__executePrivacySettings(filename, size)
Expand Down Expand Up @@ -656,6 +660,7 @@ def openFile(self, filePath, resetPosition=False, fromUser=False):
self.playlist.loadPlaylistFromFile(filePath, resetPosition)
return

self.strmIdentity.begin_open(filePath)
self.playlist.openedFile()
self._player.openFile(filePath, resetPosition)
if resetPosition:
Expand Down
1 change: 1 addition & 0 deletions syncplay/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ def getValueForOS(constantDict):
MPV_SENDMESSAGE_COOLDOWN_TIME = 0.05
MPV_MAX_NEWFILE_COOLDOWN_TIME = 3
STREAM_ADDITIONAL_IGNORE_TIME = 10
STRM_RESOLVE_TIMEOUT = 25
MPV_LOCK_WAIT_TIME = 0.05
VLC_OPEN_MAX_WAIT_TIME = 20
VLC_MIN_PORT = 10000
Expand Down
3 changes: 3 additions & 0 deletions syncplay/players/iina.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ def _preparePlayer(self):
for key, value in constants.IINA_PROPERTIES.items():
self._setProperty(key, value)
self._listener.sendLine(["load-script", findResourcePath("syncplayintf.lua")])
sidecar = MpvPlayer.strmSidecarScriptPath()
if sidecar:
self._listener.sendLine(["load-script", sidecar])
super()._preparePlayer()

def _onFileUpdate(self):
Expand Down
2 changes: 2 additions & 0 deletions syncplay/players/memento.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from syncplay.players.mpv import MpvPlayer
from syncplay.utils import findResourcePath, playerPathExists
from syncplay.strm import add_strm_sidecar_script


class MementoPlayer(MpvPlayer):
Expand Down Expand Up @@ -52,6 +53,7 @@ def getIconPath(path):
def getStartupArgs(userArgs):
args = constants.MPV_ARGS
args["scripts"] = findResourcePath("syncplayintf.lua")
add_strm_sidecar_script(args)
if userArgs:
for argToAdd in userArgs:
if argToAdd.startswith("--"):
Expand Down
9 changes: 9 additions & 0 deletions syncplay/players/mpv.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from syncplay.utils import getRuntimeDir, isURL, findResourcePath
from syncplay.utils import isMacOS, isWindows, isASCII
from syncplay.utils import playerPathExists
from syncplay.strm import add_strm_sidecar_script
from syncplay.vendor.python_mpv_jsonipc.python_mpv_jsonipc import MPV

class MpvPlayer(BasePlayer):
Expand Down Expand Up @@ -53,10 +54,18 @@ def run(client, playerPath, filePath, args):
"Please use mpv >=0.28.0.")
return MpvPlayer(client, MpvPlayer.getExpandedPath(playerPath), filePath, args)

@staticmethod
def strmSidecarScriptPath():
sidecar = findResourcePath("strm-sidecars.lua")
if os.path.isfile(sidecar):
return sidecar
return None

@staticmethod
def getStartupArgs(userArgs):
args = constants.MPV_ARGS
args["script"] = findResourcePath("syncplayintf.lua")
add_strm_sidecar_script(args)
if userArgs:
for argToAdd in userArgs:
if argToAdd.startswith('--'):
Expand Down
98 changes: 98 additions & 0 deletions syncplay/resources/strm-sidecars.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
-- Load sidecar subtitles from the directory of a .strm playlist file.
-- mpv treats .strm as a playlist, so path/filename become the inner URL and
-- default sub-auto looks next to that URL instead of next to the .strm.
-- Syncplay starts mpv with --scripts-append pointing at this file.

local utils = require("mp.utils")
local msg = require("mp.msg")

local SUB_EXTS = {
ass = true, srt = true, ssa = true, sub = true, vtt = true,
idx = true, lrc = true, smi = true, sup = true,
}

local last_playlist_path = nil

local function basename(path)
if not path then
return nil
end
return path:match("[^/\\]+$")
end

local function dirname(path)
if not path then
return nil
end
return path:match("^(.*)[/\\]")
end

local function stem(name)
if not name then
return nil
end
return name:gsub("%.[^/.]+$", "")
end

local function extension(name)
if not name then
return nil
end
return name:lower():match("%.([^.]+)$")
end

local function is_matching_sidecar(strm_stem, filename)
local ext = extension(filename)
if not ext or not SUB_EXTS[ext] then
return false
end
local file_stem = stem(filename)
if file_stem == strm_stem then
return true
end
local prefix = strm_stem .. "."
return file_stem:sub(1, #prefix) == prefix
end

local function load_sidecars(playlist_path)
if not playlist_path or playlist_path == last_playlist_path then
return
end
if not playlist_path:lower():match("%.strm$") then
last_playlist_path = playlist_path
return
end
last_playlist_path = playlist_path

local dir = dirname(playlist_path)
local strm_stem = stem(basename(playlist_path))
if not dir or not strm_stem then
return
end

local files = utils.readdir(dir, "files")
if not files then
msg.warn("strm-sidecars: cannot read " .. dir)
return
end

local loaded = 0
for _, filename in ipairs(files) do
if is_matching_sidecar(strm_stem, filename) then
local full = utils.join_path(dir, filename)
mp.commandv("sub-add", full, "auto")
loaded = loaded + 1
msg.info("strm-sidecars: added " .. full)
end
end
if loaded == 0 then
msg.verbose("strm-sidecars: no sidecars next to " .. playlist_path)
end
end

mp.observe_property("playlist-path", "string", function(_, value)
load_sidecars(value)
end)
mp.register_event("start-file", function()
load_sidecars(mp.get_property("playlist-path"))
end)
89 changes: 89 additions & 0 deletions syncplay/strm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import os
from urllib.parse import urlparse

from syncplay import constants
from syncplay import utils


def is_strm_path(file_path):
if not file_path:
return False
path = str(file_path)
if "://" in path:
path = urlparse(path).path
return os.path.splitext(path)[1].lower() == ".strm"


def _canonical_filename(path):
if utils.isURL(path):
return path
return os.path.basename(path)


def add_strm_sidecar_script(args):
from syncplay.utils import findResourcePath
sidecar = findResourcePath("strm-sidecars.lua")
if os.path.isfile(sidecar):
args["scripts_append"] = sidecar
return args


class StrmIdentityTracker:
def __init__(self, timeout=None, time_fn=None):
self.timeout = constants.STRM_RESOLVE_TIMEOUT if timeout is None else timeout
self._time = time_fn
self.source = None
self.resolved_path = None
self.source_time = None

def _now(self):
if self._time is None:
import time
return time.time()
return self._time()

def clear(self):
self.source = None
self.resolved_path = None
self.source_time = None

def begin_open(self, file_path):
self.clear()
if is_strm_path(file_path):
self.source = file_path
self.source_time = self._now()

def _resolution_timed_out(self):
if self.source_time is None:
return True
return self._now() - self.source_time > self.timeout

def canonicalize(self, filename, path):
if not path:
return filename, path, None

if is_strm_path(path):
self.source = path
self.resolved_path = None
self.source_time = self._now()
return _canonical_filename(path), path, 0

if self.source:
if self.resolved_path is None:
if self._resolution_timed_out():
self.clear()
elif utils.isURL(path):
self.resolved_path = path
path = self.source
return _canonical_filename(path), path, 0
else:
self.clear()
elif path == self.resolved_path:
path = self.source
return _canonical_filename(path), path, 0
else:
self.clear()

if utils.isURL(path):
filename = path
return filename, path, None
Loading