- ✔️ JS client show status on last slide\r
- ✔️ Shortcut to edit config in systray menu\r
- Export all slides on presentation init\r
+- Abstract pres name in class & underscore `presentation` instance\r
+- Check assertions\r
+- Use `__debug__` flag\r
+- Make constant for static dir\r
+- Comment sys.coinitflags\r
+- Die if error in run_ws or run_http
\ No newline at end of file
[Main]
-logging = debug
+
+# Logging level
+# Options: debug, info, warning, error, critical
+# (see https://docs.python.org/3/howto/logging.html#when-to-use-logging)
+logging = info
+
+# Cache location
+# Directory to store slide preview images for each presentation
cache = C:\Windows\Temp\ppt-cache
+
+# Cache format
+# Can be set to any image filter set in the registry, but most commonly JPG or PNG
+# (see https://docs.microsoft.com/en-us/office/vba/api/powerpoint.slide.export)
cache_format = JPG
+
+# Cache timeout
+# Time after which slides should be exported again to reflect any changes made in the
+# PowerPoint editor. Set to 0 to never consider the cache stale (not recommended). Value
+# is in seconds.
cache_timeout = 300
+
+# Cache initialisation
+# Whether to export all slides when a slideshow is started. Can be set to any falsy or
+# truthy value.
+cache_init = True
+
+# Black-white behaviour
+# Sets the behaviour when returning from black or white mode. Options: "both", "literal"
+# In PowerPoint's presenter view, if the slideshow is in black or white screen mode,
+# attempting to switch to the other mode (white/black) will result in the slideshow
+# returning to a normal (visible) state, rather than white/black. This behaviour is
+# also the default for ppt-control, and is invoked by setting "blackwhite" to "both"
+# (when the slideshow is in black or white mode, requesting either black or white mode
+# will return the slideshow to normal). The more intuitive setting is "literal", whereby
+# when the slideshow is in black or white mode, pressing the button corresponding to the
+# mode that the slideshow is already in will return the slideshow to normal mode, and
+# pressing the other button will put the slideshow into that mode explicitly.
blackwhite = both
+# Refresh interval
+# Describes the number of seconds between backend updates, which consist of:
+# - synchronisation of PowerPoint's events with the internal state.
+# - updating the interface (systray icon) status
+# - check for PowerPoint files that have been opened in protected view
+# You could set a shorter value here, but these updates don't have to be instant so
+# the default value of 2 seconds should work fine.
+refresh = 2
+
+# Disable protecte view
+# If set to a truthy value, ppt-control will attempt to disable protected view on files
+# which have been opened in protected view. Obviously this introduces some security risk,
+# so if you are uncomfortable with this, leave it off and you will have to disable
+# protected view for each file manually to be able to control it will ppt-control.
+disable_protected = True
+
[HTTP]
+
+# HTTP interface
+# The interface to listen for HTTP requests on. Change this if you want to restrict the
+# HTTP frontend to a specific network. A blank value means all interfaces.
interface =
+
+# HTTP port
+# The port to listen for HTTP requests on. The default value of 80 is the standard
+# port for HTTP, so when set to this value you don't need to include the port number
+# in the URL when navigating to the frontend. For all other values you will need to
+# specify the port number in the web browser (except 443, but that's a bad idea).
port = 80
[WebSocket]
+# WebSocket interface
+# The interface to listen for WebSocket requests on. Change this if you want to restrict
+# the WebSocket interface to a specific network. A value of 0.0.0.0 means all interfaces.
interface = 0.0.0.0
+
+
+# WebSocket port
+# The port to listen for WebSocket requests on. This needs to match the port specified in
+# whatever frontend you are using (e.g. in the JS code for the HTTP frontend, or in the
+# script settings for the OBS frontend).
port = 5678
\r
defaults = {\r
'Main': {\r
- # Logging level\r
- # Options: debug, info, warning, error, critical\r
- # (see https://docs.python.org/3/howto/logging.html#when-to-use-logging)\r
- 'logging': 'debug',\r
-\r
- # Cache location\r
- # Directory to store slide preview images for each presentation\r
+ 'logging': 'info',\r
'cache': r'''C:\Windows\Temp\ppt-cache''',\r
-\r
- # Cache format\r
- # Can be set to any image filter set in the registry, but most commonly JPG or PNG\r
- # (see https://docs.microsoft.com/en-us/office/vba/api/powerpoint.slide.export)\r
'cache_format': 'JPG',\r
-\r
- # Cache timeout\r
- # Time after which slides should be exported again to reflect any changes made in the\r
- # PowerPoint editor. Value is in seconds.\r
'cache_timeout': 5*60,\r
-\r
- # Cache initialisation\r
- # Whether to export all slides when a slideshow is started. Can be set to any falsy or\r
- # truthy value.\r
'cache_init': True,\r
-\r
- # Black-white behaviour\r
- # Sets the behaviour when returning from black or white mode. Options: "both", "literal"\r
- # In PowerPoint's presenter view, if the slideshow is in black or white screen mode,\r
- # attempting to switch to the other mode (white/black) will result in the slideshow\r
- # returning to a normal (visible) state, rather than white/black. This behaviour is\r
- # also the default for ppt-control, and is set by setting the value of "blackwhite"\r
- # to "both" (since when the slideshow is in black or white mode, requesting either\r
- # of black or white mode will return the slideshow to normal). The more intuitive\r
- # settings is "literal", whereby when the slideshow is in black or white mode, \r
- # pressing the button corresponding to the mode that the slideshow is already in will\r
- # return the slideshow to normal mode, and pressing the other button will put the \r
- # slideshow into that mode.\r
'blackwhite': 'both',\r
-\r
- # Refresh interval\r
- # Describes the number of seconds between backend updates, which consist of:\r
- # - synchronisation of PowerPoint's events with the internal state. \r
- # - updating the interface (systray icon) status\r
- # - check for PowerPoint files that have been opened in protected view\r
- # You could set a shorter value here, but these updates don't have to be instant so\r
- # the default value of 2 seconds should work fine.\r
'refresh': 2,\r
-\r
- # Disable protecte view\r
- # If set to a truthy value, ppt-control will attempt to disable protected view on files\r
- # which have been opened in protected view. Obviously this introduces some security risk,\r
- # so if you are uncomfortable with this, leave it off and you will have to disable \r
- # protected view for each file manually to be able to control it will ppt-control.\r
'disable_protected': True\r
},\r
'HTTP': {\r
- # HTTP interface\r
- # The interface to listen for HTTP requests on. Change this if you want to restrict the\r
- # HTTP frontend to a specific network. A blank value means all interfaces.\r
'interface': '',\r
-\r
- # HTTP port\r
- # The port to listen for HTTP requests on. The default value of 80 is the standard \r
- # port for HTTP, so when set to this value you don't need to include the port number\r
- # in the URL when navigating to the frontend. For all other values you will need to \r
- # specify the port number in the web browser (except 443, but that's a bad idea).\r
'port': 80\r
},\r
'WebSocket': {\r
- # WebSocket interface\r
- # The interface to listen for WebSocket requests on. Change this if you want to restrict\r
- # the WebSocket interface to a specific network. A value of 0.0.0.0 means all interfaces.\r
'interface': '0.0.0.0',\r
-\r
- # WebSocket port\r
- # The port to listen for WebSocket requests on. This needs to match the port specified in\r
- # whatever frontend you are using (e.g. in the JS code for the HTTP frontend, or in the\r
- # script settings for the OBS frontend).\r
'port': 5678\r
}\r
}\r
after installation.\r
"""\r
prefs = loadconf([])\r
- prefs.write(file, space_around_delimiters=True)
\ No newline at end of file
+ prefs.write(file, space_around_delimiters=True)\r
def export(self, slide):\r
"""\r
Export a relatively low-resolution image of a slide using PowerPoint's built-in export \r
- function. The cache destination is set in the config.\r
+ function. The cache destination is set in the config. The slide is not exported if it has \r
+ a non-stale cached file.\r
"""\r
destination = config.prefs["Main"]["cache"] + "\\" + self.presentation.Name + "\\" + str(slide) + "." + config.prefs["Main"]["cache_format"].lower()\r
logger.debug("Exporting slide {} of {}".format(slide, self.presentation.Name))\r
os.makedirs(os.path.dirname(destination), exist_ok=True)\r
- if not os.path.exists(destination) or time.time() - os.path.getmtime(destination) > config.prefs.getint("Main", "cache_timeout"):\r
+ if not os.path.exists(destination) or (config.prefs.getint("Main", "cache_timeout") > 0 and \r
+ time.time() - os.path.getmtime(destination) > config.prefs.getint("Main", "cache_timeout")):\r
if slide <= self.slide_total():\r
attempts = 0\r
while attempts < 3:\r
if config.prefs["Main"]["logging"] == "debug":\r
log_level = logging.DEBUG\r
elif config.prefs["Main"]["logging"] == "info":\r
- log_level = logging.CRITICAL\r
+ log_level = logging.INFO\r
else:\r
log_level = logging.WARNING\r
- log_level = logging.DEBUG\r
\r
log_formatter = logging.Formatter("%(asctime)s [%(threadName)-12.12s] [%(levelname)-7.7s] %(message)s")\r
logger = logging.getLogger("ppt-control")\r
icon = MyIcon(PKG_NAME)\r
icon.icon = Image.open(os.path.dirname(os.path.realpath(__file__)) + r'''\static\icons\ppt.ico''')\r
icon.title = "{} {}".format(PKG_NAME, PKG_VERSION)\r
- #refresh_menu(icon)\r
icon.visible = True\r
icon.run(setup=start_server)\r
\r
<button id="white">White</button>\r
</p>\r
\r
- <input type="checkbox" checked="true" id="show_current">Show current slide</input>\r
- <input type="checkbox" checked="true" id="show_next">Show next slide</input>\r
- <input type="checkbox" checked="true" id="shortcuts">Keyboard shortcuts</input>\r
+ \r
<p class="presentation_text">\r
Presentation: <select id="presentation"></select>\r
- <span class="status_text">Disconnected</span>\r
+ <span class="status_container">\r
+ <span class="status_text">Disconnected</span>\r
+ <button class="start">Start</button>\r
+ <button class="stop">Stop</button>\r
+ </span>\r
</p>\r
- <button class="start">Start</button>\r
- <button class="stop">Stop</button>\r
+ <input type="checkbox" checked="true" id="show_current">Show current slide</input>\r
+ <input type="checkbox" checked="true" id="show_next">Show next slide</input>\r
+ <input type="checkbox" checked="true" id="shortcuts">Keyboard shortcuts</input>\r
</div>\r
</div>\r
\r
-var DEFAULT_TITLE = "ppt-control"
+const DEFAULT_TITLE = "ppt-control";
+const LABEL_STOPPED = "Slideshow stopped";
+const LABEL_RUNNING = "Slideshow running";
+const LABEL_DISCONNECTED = "Disconnected";
+const LABEL_FINAL_PREFIX = "Final slide ‐ ";
+const LABEL_END_PREFIX = "End of slideshow ‐ ";
var preloaded = false;
var preload = [];
next.click()
}
+window.addEventListener('resize', function(event) {set_control_width()}, true);
+
function sync_current() {
if (show_current.checked) {
var d = new Date;
if (Object.keys(presentationData).length > 0) {
currentPresentationData = presentationData[getPresentationName()];
- console.log("Current presentation is " + getPresentationName());
presentation_text.style.display = "block";
- status_text.textContent = "Slideshow running";
+ status_text.innerHTML = LABEL_RUNNING;
startBtn.style.display = "none";
stopBtn.style.display = "block";
if (show_current.checked) {
}
}
if (currentPresentationData.slide_current == currentPresentationData.slide_total) {
- status_text.textContent = "Slideshow running (last slide)";
+ status_text.innerHTML = LABEL_FINAL_PREFIX + LABEL_RUNNING;
}
if (currentPresentationData.slide_current == currentPresentationData.slide_total + 1) {
- status_text.textContent = "End of slideshow";
+ status_text.innerHTML = LABEL_END_PREFIX + LABEL_RUNNING;
next_img.src = "/black.jpg";
} else {
next_img.src = "/cache/" + currentPresentationData.name + "/" + (currentPresentationData.slide_current + 1) + ".jpg?t=" + d.getTime();
if (currentPresentationData.slide_current == 0) {
current_img.src = "/black.jpg";
next_img.src = "/black.jpg";
- status_text.textContent = "Slideshow not running";
+ status_text.innerHTML = LABEL_STOPPED;
startBtn.style.display = "block";
stopBtn.style.display = "none";
}
document.title = DEFAULT_TITLE;
current_img.src = "/black.jpg";
next_img.src = "/black.jpg";
- status_text.textContent = "Disconnected";
+ status_text.innerHTML = LABEL_DISCONNECTED;
total.textContent = "?";
current.value = "";
presentation_text.style.display = "none";
function receive_message(event) {
data = JSON.parse(event.data);
- console.log("Received data: " + data);
if (Object.keys(presentationData).includes(data.name)) {
// Existing presentation
presentationData[data.name] = {"name": data.name, "pres_open": data.pres_open, "slideshow": data.slideshow, "visible": data.visible, "slide_current": data.slide_current, "slide_total": data.slide_total, "option": presentationData[data.name].option};
}
function saveSettings() {
- console.log("Saving settings")
settingsString = JSON.stringify({showcurrent: show_current.checked, shownext: show_next.checked, enable_shortcuts: shortcuts.checked});
setCookie(COOKIENAME, settingsString, COOKIEEXP);
}
display: none;
}
-.status_text {
+.start, .stop {
+ float: right;
+ margin: 0 0 0 10px;
+}
+
+.status_container {
float: right;
+}
+
+.status_text {
+ vertical-align: middle;
}
\ No newline at end of file
packages=setuptools.find_packages(),
python_requires='>=3.6', # as of v0.0.1, OBS only supports use of Python 3.6 for scripts. Otherwise the package works fine on > 3.6.
install_requires=['pywin32', 'websockets', 'pystray'], # https://packaging.python.org/en/latest/requirements.html
- data_files=[(ppt_control.__configdir__, ['ppt-control.ini'])],
+ data_files=[(ppt_control.CONFIG_DIR, [ppt_control.CONFIG_FILE])],
+ entry_points={'gui_scripts': ['ppt-control = ppt_control.ppt_control:start_interface']},
package_data={"": ["static/*", "static/icons/*"]}
#include_package_data=True
)