diff --git a/README.md b/README.md
index b504d0a..d1b97bf 100644
--- a/README.md
+++ b/README.md
@@ -183,7 +183,6 @@ Valid top-level keys and their expected values are:
    video. Relative to `upload_dir`.
    Details under the heading [Slides](#slides).
 
-
 ### Subtitles
 
 There are two top-level keys that deal with subtitles: `subtitles` and
@@ -265,13 +264,18 @@ These are the valid keys for a source object:
    only have a `true` value for one stream per presentation. If omitted on
    stream creation, this will defauilt to `false`.
 
+ * `enabled`: `bool`  
+   Whether this stream will be displayed in the player. At least one stream
+   must be enabled. If omitted on stream creation, this will deafult to `true`.
+
 A `sources` object would look like this:
 ```json
 {
   "asourcename": {
     "video": "some/path",
     "poster": "some/other/path",
-    "playAudio": someBool
+    "playAudio": someBool,
+    "enabled": somebool,
   },
   "anothersource": {...},
   ...
@@ -377,12 +381,14 @@ This is a job specification that has all keys and values:
         "main": {
             "video": "videos/myvideo.mp4",
             "poster": "aposter.jpg",
-            "playAudio": true
+            "playAudio": true,
+            "enabled": true
         },
         "second": {
             "video": "myothervideo.mp4",
             "poster": "anotherposter.jpg",
-            "playAudio": false
+            "playAudio": false,
+            "enabled": false
         }
     },
     "slides": {
@@ -580,3 +586,7 @@ A source definition is a JSON object with the following keys:
  * `playAudio`: `bool`  
    A boolean value denoting whether to this stream's audio track. This will
    only be set to `true` for one source in a given package.
+
+ * `enabled`: `bool`
+   Whether this stream will be displayed in the player. At least one stream
+   will be enabled.
diff --git a/pipeline/__init__.py b/pipeline/__init__.py
index a5b21e3..3d7b70e 100644
--- a/pipeline/__init__.py
+++ b/pipeline/__init__.py
@@ -1,6 +1,5 @@
 import logging
 import logging.handlers
-import multiprocessing as mp
 import os
 import shutil
 
diff --git a/pipeline/handlers/__init__.py b/pipeline/handlers/__init__.py
index 092579a..c28dfb9 100644
--- a/pipeline/handlers/__init__.py
+++ b/pipeline/handlers/__init__.py
@@ -1,7 +1,6 @@
 import multiprocessing as mp
 
 from .audio import AudioHandler
-from .handler import Handler
 from .metadata import MetadataHandler
 from .poster import PosterHandler
 from .slides import SlidesHandler
@@ -9,6 +8,7 @@ from .subtitles_whisper_hack import SubtitlesWhisperHandler
 from .subtitles_import import SubtitlesImportHandler
 from .thumbnail import ThumbnailHandler
 from .transcode import TranscodeHandler
+from .visibility import VisibilityHandler
 from ..ldap import Ldap
 from ..utils import get_section
 
@@ -20,6 +20,7 @@ allHandlers = [AudioHandler,
                SubtitlesWhisperHandler,
                ThumbnailHandler,
                TranscodeHandler,
+               VisibilityHandler,
                ]
 
 def init_handlers(collector, worker, config):
diff --git a/pipeline/handlers/activation.py b/pipeline/handlers/activation.py
deleted file mode 100644
index e69b083..0000000
--- a/pipeline/handlers/activation.py
+++ /dev/null
@@ -1,32 +0,0 @@
-from .handler import Handler
-from ..exceptions import ValidationException
-
-
-@Handler.register
-class VisibilityHandler(Handler):
-    """
-    This class handles visibility settings for streams.
-    """
-    @classmethod
-    def wants(cls, jobspec, existing_package):
-        """
-        Return True if this handler wants to process this jobspec.
-        Raises an exception if the job is wanted but doesn't pass validation.
-
-        In order for a job to be wanted, the field 'sources' must exist and
-        at least one of the source items must contain a 'enabled' field.
-
-        """
-        pass
-
-    @classmethod
-    def _validate(cls, jobspec, existing_package):
-        """
-        Return True if the job is valid for this handler.
-
-        A job is valid as long as at least one of the package's source items
-        """
-        pass
-
-    def _handle(self, jobspec, existing_package, tempdir):
-        pass
diff --git a/pipeline/handlers/handler.py b/pipeline/handlers/handler.py
index a6452ce..63a4737 100644
--- a/pipeline/handlers/handler.py
+++ b/pipeline/handlers/handler.py
@@ -1,4 +1,3 @@
-import logging
 
 from abc import ABCMeta, abstractmethod
 from pathlib import Path
diff --git a/pipeline/handlers/metadata.py b/pipeline/handlers/metadata.py
index f00e1fa..4e1839c 100644
--- a/pipeline/handlers/metadata.py
+++ b/pipeline/handlers/metadata.py
@@ -1,5 +1,4 @@
 from .handler import Handler
-from ..exceptions import ValidationException
 
 
 @Handler.register
diff --git a/pipeline/handlers/poster.py b/pipeline/handlers/poster.py
index 9bb4bf7..f4a740e 100644
--- a/pipeline/handlers/poster.py
+++ b/pipeline/handlers/poster.py
@@ -79,7 +79,7 @@ class PosterHandler(Handler):
         super()._validate(jobspec, existing_package)
         if uploaded:
             if 'upload_dir' not in jobspec:
-                raise ValidationException(f"upload_dir missing")
+                raise ValidationException("upload_dir missing")
         for name, poster in uploaded.items():
             if not path.isfile(path.join(jobspec['upload_dir'],
                                          poster)):
diff --git a/pipeline/handlers/slides.py b/pipeline/handlers/slides.py
index 6e10459..28490db 100644
--- a/pipeline/handlers/slides.py
+++ b/pipeline/handlers/slides.py
@@ -1,5 +1,4 @@
 from os import path, rename
-from shutil import rmtree
 
 import ffmpeg
 
diff --git a/pipeline/handlers/subtitles_import.py b/pipeline/handlers/subtitles_import.py
index 1d5da49..099cd46 100644
--- a/pipeline/handlers/subtitles_import.py
+++ b/pipeline/handlers/subtitles_import.py
@@ -1,4 +1,3 @@
-import logging
 
 from pathlib import Path
 
diff --git a/pipeline/handlers/subtitles_whisper_hack.py b/pipeline/handlers/subtitles_whisper_hack.py
index a9293f7..5aa1954 100644
--- a/pipeline/handlers/subtitles_whisper_hack.py
+++ b/pipeline/handlers/subtitles_whisper_hack.py
@@ -6,12 +6,10 @@ from multiprocessing import Process, Queue
 from pathlib import Path
 from queue import Empty
 from time import sleep
-from threading import Event
 
 from .handler import Handler
-from ..exceptions import ConfigurationException, ValidationException
+from ..exceptions import ValidationException
 
-import torch
 import whisper
 import whisper.utils
 
@@ -58,7 +56,7 @@ def _whisper_processor(inqueue,
             if language is None:
                 out_language = result['language']
                 logger.info(
-                    f"Detected language '%s' in %s.", out_language, inpath)
+                    "Detected language '%s' in %s.", out_language, inpath)
             else:
                 out_language = language
 
diff --git a/pipeline/handlers/subtitles_whisper_serial.py b/pipeline/handlers/subtitles_whisper_serial.py
index ffdcd29..f3c7d4f 100644
--- a/pipeline/handlers/subtitles_whisper_serial.py
+++ b/pipeline/handlers/subtitles_whisper_serial.py
@@ -5,7 +5,6 @@ from multiprocessing import Process, Queue
 from pathlib import Path
 from queue import Empty
 from time import sleep
-from threading import Event
 
 from .handler import Handler
 from ..exceptions import ConfigurationException, ValidationException
diff --git a/pipeline/handlers/transcode.py b/pipeline/handlers/transcode.py
index a97aba5..1aba1c0 100644
--- a/pipeline/handlers/transcode.py
+++ b/pipeline/handlers/transcode.py
@@ -1,7 +1,6 @@
 import logging
 
 from os import path, remove, rename
-from shutil import rmtree
 
 import ffmpeg
 
@@ -169,7 +168,7 @@ class TranscodeHandler(Handler):
         """
         super()._validate(jobspec, existing_package)
         if 'upload_dir' not in jobspec:
-            raise ValidationException(f"upload_dir missing")
+            raise ValidationException("upload_dir missing")
         for name, source in jobspec['sources'].items():
             if not path.isfile(path.join(jobspec['upload_dir'],
                                          source['video'])):
@@ -228,8 +227,10 @@ class TranscodeHandler(Handler):
                 else:
                     # Initialize a new source
                     # set playAudio to False by default so we don't conflict
-                    # with the audio handler
-                    sources[name] = {'playAudio': False}
+                    # with the audio handler, set enabled to True so we don't
+                    # conflict with the visibility handler
+                    sources[name] = {'playAudio': False,
+                                     'enabled': True}
 
                 # Save new source videos
                 sources[name]['video'] = variants
diff --git a/pipeline/handlers/visibility.py b/pipeline/handlers/visibility.py
new file mode 100644
index 0000000..ce1042b
--- /dev/null
+++ b/pipeline/handlers/visibility.py
@@ -0,0 +1,57 @@
+import copy
+from pathlib import Path
+
+from .handler import Handler
+from ..exceptions import ValidationException
+from ..package import Package
+
+@Handler.register
+class VisibilityHandler(Handler):
+    """
+    This class handles visibility settings for streams.
+    """
+    @classmethod
+    def wants(cls, jobspec: dict, existing_package: Package) -> bool | ValidationException:
+        """
+        Return True if this handler wants to process this jobspec.
+        Raises an exception if the job is wanted but doesn't pass validation.
+
+        In order for a job to be wanted, the field 'sources' must exist and
+        at least one of the source items must contain a 'enabled' field.
+
+        """
+        if 'sources' in jobspec and any('enabled' in source for source in jobspec['sources'].values()):
+            return cls._validate(jobspec, existing_package)
+        return False
+
+    @classmethod
+    def _validate(cls, jobspec: dict, existing_package: Package) -> bool | ValidationException:
+        """
+        Return True if the job is valid for this handler.
+
+        A job is valid as long as at least one of the package's source items
+        has its 'enabled' field set to True.
+        """
+        sources = copy.deepcopy(existing_package.get('sources', {}))
+        for name, source in jobspec['sources'].items():
+            if name not in sources:
+                sources[name] = {}
+            if 'enabled' in source:
+                sources[name]['enabled'] = source['enabled']
+        for source in sources.values():
+            if 'enabled' in source:
+                return True
+        raise ValidationException("No enabled sources") 
+
+    def _handle(self, jobspec: dict, existing_package: Package, tempdir: Path) -> callable:
+        """
+        """
+        def apply_func(package: Package) -> None:
+            sources = package.get('sources', {})
+            for name, source in jobspec['sources'].items():
+                if name not in sources:
+                    sources[name] = {}
+                if 'enabled' in source:
+                    sources[name]['enabled'] = source['enabled']
+
+        return apply_func
diff --git a/pipeline/ldap.py b/pipeline/ldap.py
index 364729d..ce55624 100644
--- a/pipeline/ldap.py
+++ b/pipeline/ldap.py
@@ -1,5 +1,4 @@
 from ldap3 import Connection, ObjectDef, Reader, Server
-from ldap3.core.exceptions import LDAPSocketSendError
 
 class Ldap:
     def __init__(self, conf):
diff --git a/pipeline/notifier.py b/pipeline/notifier.py
index cf906a1..36d7f07 100644
--- a/pipeline/notifier.py
+++ b/pipeline/notifier.py
@@ -1,5 +1,4 @@
 import json
-import logging
 
 from dataclasses import asdict, dataclass
 from pprint import pformat
diff --git a/pipeline/package.py b/pipeline/package.py
index 2697e79..cd2b4d9 100644
--- a/pipeline/package.py
+++ b/pipeline/package.py
@@ -2,7 +2,7 @@ import json
 
 from copy import deepcopy
 from os import mkdir, path, rename, remove
-from shutil import copy, copytree
+from shutil import copytree
 
 import ffmpeg
 
diff --git a/pipeline/preprocessors/cattura.py b/pipeline/preprocessors/cattura.py
index 88a736b..898b5a2 100644
--- a/pipeline/preprocessors/cattura.py
+++ b/pipeline/preprocessors/cattura.py
@@ -110,4 +110,4 @@ class CatturaProcessor(Preprocessor):
         for key in data.keys():
             if key.startswith('mediapackage:'):
                 return data[key]
-        raise KeyError(f"no 'mediapackage' key in job specification")
+        raise KeyError("no 'mediapackage' key in job specification")
diff --git a/pipeline/preprocessors/preprocessor.py b/pipeline/preprocessors/preprocessor.py
index 748f45a..24b93d7 100644
--- a/pipeline/preprocessors/preprocessor.py
+++ b/pipeline/preprocessors/preprocessor.py
@@ -1,5 +1,4 @@
 from abc import ABCMeta, abstractmethod
-from os import mkdir, path
 
 from ..queuethread import QueueThread
 
diff --git a/pipeline/utils.py b/pipeline/utils.py
index 454f12a..c34f771 100644
--- a/pipeline/utils.py
+++ b/pipeline/utils.py
@@ -1,4 +1,3 @@
-from os import chdir, path
 from types import FunctionType
 
 
@@ -23,6 +22,7 @@ canonical_jobspec = {
                                  'source': str}},
     'sources': {str: {'poster': str,
                       'playAudio': bool,
+                      'enabled': bool,
                       'video': str}},
     'slides': str,
     'notification_url': str,
@@ -49,6 +49,7 @@ canonical_manifest = {
     'subtitles': {str: str},
     'sources': {str: {'poster': str,
                       'playAudio': bool,
+                      'enabled': bool,
                       'video': {'720': str,
                                 '1080': str}}}
 }
diff --git a/pipeline/workthread.py b/pipeline/workthread.py
index dd88eb1..4efb351 100644
--- a/pipeline/workthread.py
+++ b/pipeline/workthread.py
@@ -1,4 +1,3 @@
-import logging
 import multiprocessing as mp
 
 from collections import deque
@@ -6,7 +5,7 @@ from collections.abc import Iterable
 from dataclasses import dataclass
 from pprint import pformat
 from threading import Event
-from time import sleep, strftime
+from time import sleep
 from typing import Callable
 
 from .queuethread import QueueThread
diff --git a/play-daemon.py b/play-daemon.py
index 5870874..350e7d5 100755
--- a/play-daemon.py
+++ b/play-daemon.py
@@ -5,7 +5,6 @@ import sys
 
 from configparser import ConfigParser
 from os import path
-from time import sleep
 
 from pipeline import Pipeline
 
diff --git a/test.py b/test.py
index dc972cf..ff95249 100755
--- a/test.py
+++ b/test.py
@@ -272,7 +272,7 @@ class PipelineTest(DaemonTest):
                     print("¤ Contents of invalid notification file ¤")
                     print(f.read())
                     print("¤ End invalid notification file contents ¤")
-                    self.fail(f"Invalid JSON in result file.")
+                    self.fail("Invalid JSON in result file.")
 
             # Validate that this is the correct resultfile
             self.assertEqual(jobid, result['jobid'])
@@ -300,7 +300,7 @@ class PipelineTest(DaemonTest):
         return final_result
 
     def init_job(self, pkgid=False, url=False, subs=False, thumb=False,
-                 source_count=0, poster_count=0):
+                 source_count=0, disabled_source_count=0, poster_count=0):
         jobspec = {}
 
         if url == True:
@@ -333,7 +333,7 @@ class PipelineTest(DaemonTest):
         if source_count:
             # poster_count determines the number of posters to include.
             # Any remaining posters will be generated in pipeline
-            self.add_sources(jobspec, source_count, poster_count)
+            self.add_sources(jobspec, source_count, poster_count, disabled_source_count)
 
         return jobspec
 
@@ -343,7 +343,8 @@ class PipelineTest(DaemonTest):
                               str(uuid.uuid4()))
             jobspec['upload_dir'] = uldir
             makedirs(uldir)
-        return uldir
+            return uldir
+        return jobspec['upload_dir']
 
     def add_subtitles(self, jobspec):
         uldir = self.ensure_uldir(jobspec)
@@ -356,16 +357,22 @@ class PipelineTest(DaemonTest):
             copyfile(subspath, path.join(uldir, subsfile))
         jobspec['subtitles'] = subspec
 
-    def add_sources(self, jobspec, count, poster_count=0):
+    def add_sources(self, jobspec, count, poster_count=0, disabled_count=0):
         uldir = self.ensure_uldir(jobspec)
-        jobspec['sources'] = {}
+        if 'sources' not in jobspec:
+            jobspec['sources'] = {}
         posters = 0
+        disabled = 0
         for i in range(count):
             videopath = next(self.videopaths)
             videofile = path.basename(videopath)
             copyfile(videopath, path.join(uldir, videofile))
             sourcedef = {'video': videofile,
-                         'playAudio': False}
+                         'playAudio': False,
+                         'enabled': True}
+            if disabled < disabled_count:
+                sourcedef['enabled'] = False
+                disabled += 1
             if i == 0:
                 sourcedef['playAudio'] = True
             if posters < poster_count:
@@ -457,7 +464,7 @@ class PipelineTest(DaemonTest):
 
     #@unittest.skip("This test is very slow")
     def test_transcoding(self):
-        jobspec = self.init_job(source_count=4, poster_count=2)
+        jobspec = self.init_job(source_count=3, poster_count=2, disabled_source_count=1)
 
         jobid = self.submit_default_job(jobspec)
         result = self.wait_for_result(jobid, ['AudioHandler',