Python automation case study

MovieBot / StreamCinema Vote Bot

Python/TwitchIO bot for Twitch votes and OBS playback. It scans local movies, resolves ties, refreshes tokens, reconnects, and sends OBS the winner.

Project type
Twitch/OBS stream automation
Status
Public repo / local automation / showcase page
Runtime
Python 3.8+, TwitchIO, OBS WebSocket
Role
Solo builder: bot logic, reliability, setup docs, portfolio pages
Last updated
May 2026

Quick summary

MovieBot in 30 seconds.

Python automation, stream tooling, command logic, and integration proof.

What this proves

Python automation, Twitch/OBS integration, and tested command/state logic.

Core technical work

TwitchIO commands, vote state, ties, local scanning, OAuth refresh, reconnects, OBS handoff, and pytest.

Best path

Start with repo/tests, then scan architecture, OBS flow, and vote state. Movie Night and Library show viewer surfaces.

Known limits

Runs locally beside OBS; readiness depends on secrets, config, local media, health checks, logs, operator controls, and live tests.

Try this demo

Try this in 2 minutes.

The bot runs locally. This path reviews viewer pages, command flow, source, and tests.

  1. Search a movie.Filter by title, year, runtime, or rating.
  2. Copy a vote.Use Copy !vote for the exact chat command.
  3. Read command flow.Connect chat votes to OBS handoff.
  4. Review source/tests.Open the repo, CI workflow, and pytest file.
Movie Library page showing a searchable public-domain poster grid and copyable MovieBot vote command buttons
Movie Library: search, select a title, and copy an exact vote command.

Visual proof

The browser pages support a local bot.

The screenshot shows the viewer catalog, not the Python runtime.

What to notice

  • The catalog turns long titles into accurate !vote commands.
  • Movie Night gives context; source and tests prove the automation.
  • The bot still needs local Python, Twitch credentials, OBS config, and monitoring.

TL;DR

MovieBot in 15 seconds.

Problem
Streamers need chat voting without manually tracking votes, ties, and playback handoff.
Built
Python Twitch bot that scans local movies, accepts votes, resolves winners, and updates OBS.
Stack
Python, TwitchIO, OBS WebSocket, OAuth refresh, MoviePy/ffprobe, pytest, local filesystem.
Demo
Movie Night page and Movie Library show the viewer-facing surfaces.
Source
Inefy/twitch-movie-bot, including pytest tests and CI status.
Result
Chat commands, local lookup, vote state, ties, token refresh, reconnects, and OBS updates in one flow.
Current limitation
Local setup required; availability depends on Twitch, OBS, local media, and monitoring.

01 / Summary

Chat votes become OBS playback.

MovieBot is a Python/TwitchIO control layer: chat updates vote state, local files provide the winner, and OBS WebSocket updates playback.

Technical problem

Chat, vote state, local files, Twitch auth, and OBS playback must stay coordinated.

Constraints

The bot depends on Twitch OAuth/IRC, OBS WebSocket, local paths, and services that can disconnect.

Approach

I built commands, vote state, movie scanning, refresh/reconnect paths, and OBS handoff.

Tradeoff

Local folders keep setup simple but less portable than hosted storage and operator controls.

Next improvement

Next: operator dashboard, connection health, vote/queue state, OBS status, and stronger integration tests.

What it demonstrates: Async Python, Twitch auth, local scanning, input validation, stateful voting, OBS control, and recovery behavior.

Why this matters

It turns stream chores into automation.

Operational problem

Movie nights require voting, tallying, lookup, tie handling, and OBS changes while hosting.

Useful approach

The bot treats chat as input, keeps local state, and hands selected files to OBS with refresh/reconnect paths.

Employer signal

Shows integration work, async commands, reliability, API auth, file automation, and support pages.

Production readiness

Bot flow works; stream ops need controls.

Already solid

Repo covers commands, vote changes, ties, scanning, OBS handoff, token refresh, reconnects, docs, and pytest.

Showcase-only

Not a hosted service. Assumes local movies, OBS config, private secrets, and log monitoring.

Production work

For public use: dashboard, queue controls, integration tests, rate limits, logs, health checks, alerts, and recovery.

Security and ops

Tokens, OBS passwords, and media paths stay out of source. Production needs rotation, scoped permissions, moderation, and backups.

02 / What I built

Bot logic, OBS handoff, reliability, and support pages.

  • Implemented Twitch commands for votes, results, current movie, time, movie lists, and help.
  • Built movie scanning with extension filters, one-level subfolders, rescans, and local media validation.
  • Built vote-state logic for changed votes, duplicates, partial matches, ties, and fallback selection.
  • Connected OBS by verifying scenes/sources, updating media, switching scenes, and restarting playback.
  • Added OAuth refresh, IRC health checks, reconnects, startup validation, logs, pytest, and support pages.

External pieces: TwitchIO, Twitch OAuth/chat, OBS WebSocket, IMDb links, posters, and local public-domain files.

03 / Problem

Manual stream control does not scale.

Manual voting distracts the streamer: count votes, resolve titles, pick a winner, update OBS, and recover when services drop.

MovieBot turns chat into input and OBS into output. Viewers get predictable commands; the streamer gets repeatable handoff and logs.

04 / Viewer flow

Viewer flow stays in Twitch chat.

  1. Browse options Use Movie Library or !movies to see titles.
  2. Vote by title Use !vote <movie name>; exact or clear partial matches work.
  3. Check the room state !currentmovie, !time, and !results show state.
  4. Change a vote Changing a title replaces the previous vote.
  5. Watch the winner At poll end, OBS starts the winning local file.

05 / Streamer/operator flow

Operator sets config, then runs the loop.

  1. Prepare Twitch credentials Set client ID/secret, tokens, and channel in local env settings.
  2. Prepare OBS Enable OBS WebSocket and configure scene/source names.
  3. Point at local media Set the folder containing public-domain video files.
  4. Start the bot Run python bot.py. Startup validates settings, tokens, movies, OBS, and schedule.
  5. Monitor logs Console and bot.log show OBS errors, token issues, chat recovery, and fallbacks.

06 / Tech stack

Python around Twitch, OBS, and local media.

  • Python 3.8+
  • TwitchIO
  • OBS WebSocket
  • pytest
  • OAuth refresh
  • MoviePy / ffprobe

Python handles commands, background routines, chat, OBS playback, duration checks, and private env config.

07 / Twitch command flow

Commands are validated before they change state.

Viewer command behavior from the MovieBot source.
Command Behavior Reliability detail
!vote title Registers a vote for a matching movie during an active poll. Rejects invalid votes and updates previous votes instead of double-counting.
!results Shows current vote counts sorted by vote total and title. Handles inactive poll and no-vote states.
!currentmovie Shows the currently playing movie. Reports when no movie is active.
!time Shows total duration and remaining time for the current movie. Uses recorded start time and duration.
!movies Links to the public movie list when configured, otherwise lists the first available titles. Keeps chat short while supporting the library page.
!help Explains the available commands. Sends delayed lines with a compact fallback.

08 / OBS playback flow

OBS handoff updates the media source.

MovieBot system flow Chat commands update votes, pick a movie, and control OBS. Refresh, reconnect, and scan routines support the loop.
MovieBot chat-to-OBS architecture diagram Twitch chat commands enter a Python command parser, update vote state, resolve the poll, choose a scanned local movie, and send OBS WebSocket commands. Token refresh and reconnect routines support Twitch and OBS connections. VIEWER Chat !vote / !results TWITCHIO Parser Validate command STATE Votes One vote/user POLL Winner Pick winner OBS OBS control Set source / scene OUTPUT Stream Movie starts MEDIA Folder scan Playable files RELIABILITY Token / reconnect Twitch and OBS
Chat creates input, vote state selects a file, OBS receives playback commands, and recovery routines keep the loop alive.

Text fallback: TwitchIO routes chat to Python. The bot validates input, picks a local movie, updates OBS, and runs refresh/reconnect routines.

  1. Verify OBS connection Connect to OBS, check scene, and verify media source.
  2. Set the local file Normalize the selected path and send it to OBS.
  3. Refresh the source Hide, update, show, switch scene, and restart the media input.
  4. Retry before giving up Retry media loading; fall back to another movie when possible.
OBS handoff is a small command pipeline around a normalized local file path.
normalized_path = os.path.normpath(path)
settings = {"local_file": normalized_path}

set_settings_request = obs_requests.SetInputSettings(
    inputName=MEDIA_SOURCE_NAME,
    inputSettings=settings,
    overlay=True
)

set_response = await self.safe_obs_call(set_settings_request)
if set_response is not None and await self._set_program_scene(SCENE_NAME):
    if await self._restart_media_input(MEDIA_SOURCE_NAME):
        return True

Trimmed from public source; no local paths or credentials.

09 / Movie folder scanning

Local media is scanned and refreshed.

The bot scans configured movie folders for common video formats and one level of subdirectories, without following symlinks.

  • Missing directories log an error and return an empty list.
  • A 10-minute rescan refreshes the list and keeps the prior list if no files appear.
  • Duration uses ffprobe, then MoviePy, then a default.

10 / Vote changes and ties

The voting model handles real chat behavior.

Partial title matching

Exact matches win first; ambiguous partial matches are rejected.

Vote changes

Each viewer owns one vote. Changing it decrements the old title and increments the new one.

Tie and fallback handling

Ties are random and announced. No votes can fall back to a random movie.

Each viewer owns one vote; changes update user and aggregate state.
previous_vote_path = self.voter_data.get(user)
if previous_vote_path:
    if previous_vote_path == selected_movie_path:
        await ctx.send(f"@{user}, you already voted for '{selected_basename}'.")
        return
    if previous_vote_path in self.votes:
        self.votes[previous_vote_path] -= 1
        if self.votes[previous_vote_path] <= 0:
            del self.votes[previous_vote_path]

self.votes[selected_movie_path] = self.votes.get(selected_movie_path, 0) + 1
self.voter_data[user] = selected_movie_path

Trimmed from public source for vote-change logic.

11 / Token refresh and reconnect behavior

Long sessions need recovery paths.

Twitch OAuth

Token refresh

Validates access tokens, refreshes near expiry, saves updates, and refreshes process env.

IRC health

Chat reconnect

Checks IRC health, refreshes tokens, reconnects, and can hard reset TwitchIO after repeated failure.

OBS health

Safe OBS calls

OBS calls use connection checks, timeouts, failure marking, and reconnect attempts.

Playback state

Stale callback protection

A generation counter prevents old callbacks from ending a newer movie.

Failure fallback

Alternative selection

If OBS fails to load a file, the bot clears votes, excludes the failed path, and tries another movie.

Shutdown

Cleanup

Shutdown cancels callbacks/tasks, stops routines, disconnects OBS, and closes TwitchIO.

12 / Setup and security notes

Credentials stay local.

  • .env.example covers Twitch, movie directory, OBS, scene/source, and optional movie-list URL.
  • README warns not to commit .env, tokens, secrets, passwords, or logs.
  • Startup checks required Twitch settings and MOVIE_DIRECTORY.
  • OBS WebSocket and scene/source names must match config.
  • Committed credentials should be rotated before publishing clean history.

13 / Deployment and run notes

Runs locally beside OBS.

Hosted/run location

The Python bot runs on the operator machine beside the movie folder and OBS. Support pages live on GitHub Pages.

Environment/config

Local .env: Twitch credentials, channel, movie directory, OBS config, scene/source, and optional movie-list URL.

External APIs/services

Runtime uses TwitchIO/OAuth, OBS WebSocket, local movie files, and ffprobe/MoviePy. Support pages link to IMDb/posters.

Local development

Create a Python 3.8+ venv, install requirements, configure .env, run pytest, then python bot.py.

Secrets and operations

Never commit .env, tokens, secrets, passwords, logs, or media paths. Production adds rotation, scoped permissions, logs, health checks, and dashboard.

14 / Testing and QA notes

Tests target Twitch/OBS risks.

Public pytest checks cover config, scanning, token refresh, OBS calls, commands, scheduling, fallbacks, startup, and cleanup. GitHub Actions runs the suite.

Automated tests

tests/test_bot_logic.py covers scanning, durations, config, token helpers, OBS calls, commands, scheduling, fallbacks, startup, and cleanup.

Manual checklist

Run with local movies and OBS config, send key commands, then confirm OBS updates the media source.

Browser and device checks

QA Movie Night embeds, Library search/copy, links, mobile layout, focus, and blocked-embed fallbacks.

Edge cases

Covers missing folders, ambiguous titles, changed votes, no-vote polls, ties, missing duration tools, OBS timeouts, stale callbacks, and reconnects.

Known limitations

No dashboard, CI-backed Twitch/OBS integration, synthetic E2E stream test, metrics, or alerting yet.

15 / Screenshots

Browser surfaces around the bot.

16 / Next improvements

Next improvements

  • Add an operator dashboard for movie, votes, OBS health, token status, and playback timing.
  • Add dry-run mode for chat commands and OBS calls against mocks.
  • Add structured logs or metrics for long sessions.

Next step

Review source, then stream pages.

The repo shows the Python automation. Movie Night and Movie Library show the viewer surfaces.