diff --git a/src/videoag/api/Backend.tsx b/src/videoag/api/Backend.tsx
index 0bd9429518f3ec4fece2ab2425bc955a420c6d81..34a1d843fc5e65c476a2404bd02f527cce6b8808 100644
--- a/src/videoag/api/Backend.tsx
+++ b/src/videoag/api/Backend.tsx
@@ -24,6 +24,8 @@ import type {
     GetSorterFilesRequest,
     GetSorterFilesResponse,
     RunSourceFileSorterResponse,
+    GetLectureMediaProcessOverviewResponse,
+    RunLectureMediaProcessSchedulerResponse,
     GetConfigurationResponse,
     GetNewConfigurationResponse,
     CreateConfiguredObjectRequest,
@@ -305,6 +307,26 @@ export class BackendImpl {
         );
     }
 
+    /// GET /lecture/{lecture_id}/media_process_overview
+    getLectureMediaProcessOverview(
+        lecture_id: int,
+    ): Promise<GetLectureMediaProcessOverviewResponse> {
+        return this.processResponse<GetLectureMediaProcessOverviewResponse>(
+            this.fetch(`${this.baseUrl()}/lecture/${lecture_id}/media_process_overview`),
+        );
+    }
+
+    /// GET /lecture/{lecture_id}/media_process_overview
+    runLectureMediaProcessScheduler(
+        lecture_id: int,
+    ): Promise<RunLectureMediaProcessSchedulerResponse> {
+        return this.processResponse<RunLectureMediaProcessSchedulerResponse>(
+            this.fetch(`${this.baseUrl()}/lecture/${lecture_id}/run_media_process_scheduler`, {
+                method: "POST",
+            }),
+        );
+    }
+
     // GET /object_management/{object_type}/{object_id}/configuration
     getOMConfiguration(object_type: string, object_id: int): Promise<GetConfigurationResponse> {
         return this.processResponse<GetConfigurationResponse>(
diff --git a/src/videoag/api/types.ts b/src/videoag/api/types.ts
index 8e7b4489fa9614a93f0b4653b1b52228aeddbcbb..8e0011256cd19968638b817ebeaa71c6679fc371 100644
--- a/src/videoag/api/types.ts
+++ b/src/videoag/api/types.ts
@@ -116,6 +116,18 @@ export interface publish_medium {
     include_in_player: boolean;
 }
 
+export interface medium_file {
+    file_path: string;
+    id: int;
+    input_data_sha256: string;
+    lecture_id: int;
+    medium_metadata_id?: int;
+    process_sha256: string;
+    process_target_id: string;
+    producer_job_id?: int;
+    to_be_replaced: boolean;
+}
+
 export type medium_metadata_type = "image" | "plain_video" | "plain_audio" | "thumbnail";
 
 export interface medium_metadata {
@@ -443,6 +455,27 @@ export interface RunSourceFileSorterResponse {
     scheduled_new: boolean;
 }
 
+/// GET /lecture/{lecture_id}/media_process_overview
+// No Request Interface
+export interface GetLectureMediaProcessOverviewResponse {
+    is_automatic_media_process_scheduler_enabled: boolean;
+    job_context: { [key: string]: job };
+    medium_files: { [key: string]: medium_file };
+    medium_metadata: { [key: string]: medium_metadata };
+    process: any; // TODO media_process type
+    process_sha256: string;
+    publish_media: { [key: string]: publish_medium };
+    sorter_files: { [key: string]: sorter_file };
+    status: string;
+}
+
+/// POST /lecture/{lecture_id}/run_media_process_scheduler
+// No Request Interface
+export interface RunLectureMediaProcessSchedulerResponse {
+    scheduled_new: boolean;
+    job_id: int;
+}
+
 /// GET /object_management/{object_type}/{object_id}/configuration
 // Request Interface
 export interface GetConfigurationRequest {
diff --git a/src/videoag/course/Lecture.tsx b/src/videoag/course/Lecture.tsx
index fe9e5ab5fac48c2020c6f98be309160dbdd21a39..c6a56a3dc3d91d4c2d6836e6975b3c88b01e1dac 100644
--- a/src/videoag/course/Lecture.tsx
+++ b/src/videoag/course/Lecture.tsx
@@ -14,7 +14,11 @@ import { StylizedText } from "@/videoag/miscellaneous/StylizedText";
 import { useEditMode } from "@/videoag/object_management/EditModeProvider";
 import { useLanguage } from "@/videoag/localization/LanguageProvider";
 
-import { PublishMediumDownloadButton, PublishMediumList } from "./Medium";
+import {
+    PublishMediumDownloadButton,
+    PublishMediumList,
+    ModalMediaProcessOverview,
+} from "./Medium";
 import { LectureLiveLabel } from "./LiveLabel";
 
 export function urlForLecture(course_id: string, lecture_id: string | number) {
@@ -211,7 +215,13 @@ export function LectureListItem({
                 <ul className="list-unstyled col-md-4 col-12 pe-0 row align-content-start">
                     <li className="col-xl-7 col-12 p-0">
                         {editMode ? (
-                            <PublishMediumList publish_media={lecture.publish_media!} />
+                            <>
+                                <PublishMediumList publish_media={lecture.publish_media!} />
+                                <ModalMediaProcessOverview
+                                    lectureId={lecture.id}
+                                    className="mt-2"
+                                />
+                            </>
                         ) : (
                             <PublishMediumDownloadButton
                                 publish_media={lecture.publish_media ?? []}
diff --git a/src/videoag/course/Medium.tsx b/src/videoag/course/Medium.tsx
index c2a0ae84d31bdcc7e7aa08299904e84fe4f59923..552da6010e5f2b840d325293061de0f87f799bb6 100644
--- a/src/videoag/course/Medium.tsx
+++ b/src/videoag/course/Medium.tsx
@@ -1,8 +1,19 @@
-import { Dropdown } from "react-bootstrap";
+import { useEffect, useState } from "react";
+import { Dropdown, Modal } from "react-bootstrap";
 
-import { publish_medium } from "@/videoag/api/types";
+import { int, publish_medium, GetLectureMediaProcessOverviewResponse } from "@/videoag/api/types";
+import { useApi } from "@/videoag/api/ApiProvider";
+import { showError, ErrorComponent } from "@/videoag/error/ErrorDisplay";
+import { JobStatusCard } from "@/videoag/job/Job";
 import { useLanguage } from "@/videoag/localization/LanguageProvider";
-import { filesizeToHuman } from "@/videoag/miscellaneous/Formatting";
+import {
+    filesizeToHuman,
+    timestampToString,
+    datetimeToString,
+} from "@/videoag/miscellaneous/Formatting";
+import { useMutexCall } from "@/videoag/miscellaneous/PromiseHelpers";
+import { ReloadBoundary } from "@/videoag/miscellaneous/ReloadBoundary";
+import { Spinner, showInfoToast, TooltipButton } from "@/videoag/miscellaneous/Util";
 import { useEditMode } from "@/videoag/object_management/EditModeProvider";
 import { OMDelete, EmbeddedOMFieldComponent } from "@/videoag/object_management/OMConfigComponent";
 
@@ -12,6 +23,10 @@ export function getMediumQualityName(medium: publish_medium): string | undefined
             return `${medium.medium_metadata.vertical_resolution}p`;
         case "plain_audio":
             return `${medium.medium_metadata.audio_sample_rate} Hz`;
+        case "thumbnail":
+            return `Thumbnail`;
+        case "image":
+            return `${medium.medium_metadata.horizontal_resolution}x${medium.medium_metadata.vertical_resolution}`;
         default:
             return undefined;
     }
@@ -174,3 +189,480 @@ export function PublishMediumDownloadButton({
         </span>
     );
 }
+
+function MediumFileListItem({
+    context,
+    mediumFileId,
+}: {
+    context: GetLectureMediaProcessOverviewResponse;
+    mediumFileId: int;
+}) {
+    const [showJobStatus, setShowJobStatus] = useState<boolean>(false);
+
+    const file = context.medium_files[mediumFileId.toString()]!;
+    const metadata = file.medium_metadata_id
+        ? context.medium_metadata[file.medium_metadata_id.toString()]
+        : undefined;
+    const publishMedium = metadata?.publish_medium_id
+        ? context.publish_media[metadata.publish_medium_id.toString()]
+        : undefined;
+    const hasMetadata = metadata !== undefined;
+
+    useEffect(() => {
+        if (!hasMetadata) {
+            setShowJobStatus(true);
+        }
+    }, [hasMetadata]);
+
+    let metadataComp = undefined;
+    if (metadata !== undefined) {
+        const typeRows = [];
+        if (["plain_video", "plain_audio"].includes(metadata.type)) {
+            typeRows.push(
+                <tr>
+                    <td>Duration</td>
+                    <td>
+                        {timestampToString(metadata.duration_sec!)} ({metadata.duration_sec!} s)
+                    </td>
+                </tr>,
+            );
+            typeRows.push(
+                <tr>
+                    <td>Audio Channel Count</td>
+                    <td>{metadata.audio_channel_count!}</td>
+                </tr>,
+            );
+            typeRows.push(
+                <tr>
+                    <td>Audio Sample Rate</td>
+                    <td>{metadata.audio_sample_rate!}</td>
+                </tr>,
+            );
+        }
+        if (["plain_video"].includes(metadata.type)) {
+            typeRows.push(
+                <tr>
+                    <td>Video Framerate</td>
+                    <td>
+                        {metadata.video_frame_rate_numerator! /
+                            metadata.video_frame_rate_denominator!}
+                    </td>
+                </tr>,
+            );
+        }
+        if (["image", "thumbnail", "plain_video"].includes(metadata.type)) {
+            typeRows.push(
+                <tr>
+                    <td>Resolution</td>
+                    <td>
+                        {metadata.horizontal_resolution!}x{metadata.vertical_resolution!}
+                    </td>
+                </tr>,
+            );
+        }
+        metadataComp = (
+            <>
+                <h5>Medium Metadata {metadata.id}</h5>
+                <table className="table">
+                    <thead>
+                        <tr>
+                            <th scope="col" style={{ width: "30%" }} />
+                            <th scope="col" style={{ width: "70%" }} />
+                        </tr>
+                    </thead>
+                    <tbody>
+                        <tr>
+                            <td>File Size</td>
+                            <td>
+                                {filesizeToHuman(metadata.file_size)} ({metadata.file_size} B)
+                            </td>
+                        </tr>
+                        <tr>
+                            <td>Type</td>
+                            <td>{metadata.type}</td>
+                        </tr>
+                        {typeRows}
+                    </tbody>
+                </table>
+            </>
+        );
+    }
+
+    let publishMediumComp = undefined;
+    if (publishMedium !== undefined) {
+        publishMediumComp = (
+            <>
+                <h5>Publish Medium {publishMedium.id}</h5>
+                <table className="table">
+                    <thead>
+                        <tr>
+                            <th scope="col" style={{ width: "30%" }} />
+                            <th scope="col" style={{ width: "70%" }} />
+                        </tr>
+                    </thead>
+                    <tbody>
+                        <tr>
+                            <td>
+                                Title{" "}
+                                <TooltipButton iconClassName="bi-pencil">
+                                    This field can be edited (with a double click)
+                                </TooltipButton>
+                            </td>
+                            <td>
+                                <EmbeddedOMFieldComponent
+                                    object_type="publish_medium"
+                                    object_id={publishMedium.id}
+                                    field_id="title"
+                                    field_type="string"
+                                    initialValue={publishMedium.title}
+                                />
+                            </td>
+                        </tr>
+                        <tr>
+                            <td>
+                                Visible{" "}
+                                <TooltipButton iconClassName="bi-pencil">
+                                    This field can be edited
+                                </TooltipButton>
+                            </td>
+                            <td>
+                                <EmbeddedOMFieldComponent
+                                    object_type="publish_medium"
+                                    object_id={publishMedium.id}
+                                    field_id="visible"
+                                    field_type="boolean"
+                                    initialValue={publishMedium.visible}
+                                />
+                            </td>
+                        </tr>
+                        <tr>
+                            <td>
+                                Include in Player?
+                                <TooltipButton>
+                                    Indicates whether this publish medium is included in the player.
+                                    This purely depends on the medium type (e.g. a thumbnail
+                                    won&apos;t be included in the player).
+                                </TooltipButton>
+                            </td>
+                            <td>{publishMedium.include_in_player ? "Yes" : "No"}</td>
+                        </tr>
+                    </tbody>
+                </table>
+            </>
+        );
+    }
+
+    return (
+        <div className={"card mb-3 " + (file.to_be_replaced ? "bg-warning-subtle" : "")}>
+            <div id={`medium_file_${file.id}`} className="card-body">
+                <h5>Medium File {file.id}</h5>
+                <table className="table">
+                    <thead>
+                        <tr>
+                            <th scope="col" style={{ width: "30%" }} />
+                            <th scope="col" style={{ width: "70%" }} />
+                        </tr>
+                    </thead>
+                    <tbody>
+                        <tr>
+                            <td>File Path</td>
+                            <td>{file.file_path}</td>
+                        </tr>
+                        <tr>
+                            <td>Input Data SHA256</td>
+                            <td>{file.input_data_sha256}</td>
+                        </tr>
+                        <tr>
+                            <td>Process SHA256</td>
+                            <td>{file.process_sha256}</td>
+                        </tr>
+                        <tr>
+                            <td>Process Target ID</td>
+                            <td>{file.process_target_id}</td>
+                        </tr>
+                        <tr>
+                            <td>Producer Job</td>
+                            <td>
+                                {file.producer_job_id &&
+                                    (showJobStatus ? (
+                                        <JobStatusCard jobId={file.producer_job_id} />
+                                    ) : (
+                                        <>
+                                            <span>{file.producer_job_id}</span>
+                                            <button
+                                                className="btn btn-secondary ms-2"
+                                                onClick={(e) => setShowJobStatus(true)}
+                                            >
+                                                Show Status
+                                            </button>
+                                        </>
+                                    ))}
+                            </td>
+                        </tr>
+                        <tr>
+                            <td>
+                                To Be Replaced?
+                                <TooltipButton>
+                                    Indicates whether this medium file is outdated due to various
+                                    reasons. Usually because the process changed or the input
+                                    dependencies changed.
+                                </TooltipButton>
+                            </td>
+                            <td>{file.to_be_replaced ? "Yes" : "No"}</td>
+                        </tr>
+                    </tbody>
+                </table>
+                {metadataComp}
+                {publishMediumComp}
+            </div>
+        </div>
+    );
+}
+
+function SorterFileListItem({
+    context,
+    sorterFileId,
+}: {
+    context: GetLectureMediaProcessOverviewResponse;
+    sorterFileId: int;
+}) {
+    const file = context.sorter_files[sorterFileId.toString()]!;
+
+    return (
+        <div className="card mb-3">
+            <div className="card-body">
+                <h5>Sorter File {file.id}</h5>
+                <table className="table">
+                    <thead>
+                        <tr>
+                            <th scope="col" style={{ width: "30%" }} />
+                            <th scope="col" style={{ width: "70%" }} />
+                        </tr>
+                    </thead>
+                    <tbody>
+                        <tr>
+                            <td>File Path</td>
+                            <td>{file.file_path}</td>
+                        </tr>
+                        <tr>
+                            <td>File Modification Time</td>
+                            <td>{datetimeToString(file.file_modification_time)}</td>
+                        </tr>
+                        <tr>
+                            <td>SHA256</td>
+                            <td>{file.sha256}</td>
+                        </tr>
+                        <tr>
+                            <td>Tag</td>
+                            <td>{file.tag}</td>
+                        </tr>
+                        <tr>
+                            <td>
+                                Designated Medium File ID
+                                <TooltipButton>
+                                    Shows which medium file was created for this source file. That
+                                    medium file and this source file reference the same file (have
+                                    the same file path).
+                                    <br />
+                                    Note that this medium file can change if the process changes,
+                                    etc.
+                                </TooltipButton>
+                            </td>
+                            <td>
+                                {file.designated_medium_file_id && (
+                                    <a href={`#medium_file_${file.designated_medium_file_id}`}>
+                                        {file.designated_medium_file_id}
+                                    </a>
+                                )}
+                            </td>
+                        </tr>
+                    </tbody>
+                </table>
+            </div>
+        </div>
+    );
+}
+
+export function MediaProcessOverview({ lectureId }: { lectureId: int }) {
+    const api = useApi();
+    const [overviewData, setOverviewData] = useState<
+        GetLectureMediaProcessOverviewResponse | undefined
+    >(undefined);
+    const [error, setError] = useState<any>(undefined);
+
+    const reloadData = useMutexCall(() =>
+        api
+            .getLectureMediaProcessOverview(lectureId)
+            .then(setOverviewData)
+            .catch((e) => {
+                console.log("Error while loading media process overview", e);
+                setError(e);
+            }),
+    );
+
+    useEffect(() => {
+        reloadData();
+    }, [lectureId, reloadData]);
+
+    const [doingSchedulerRequest, setDoingSchedulerRequest] = useState<boolean>(false);
+    const [schedulerJobId, setSchedulerJobId] = useState<int | undefined>(undefined);
+
+    const runScheduler = () => {
+        setDoingSchedulerRequest(true);
+        api.runLectureMediaProcessScheduler(lectureId)
+            .then((res) => {
+                setSchedulerJobId(res.job_id);
+                console.log(res.scheduled_new);
+                if (!res.scheduled_new) {
+                    showInfoToast("Scheduler was already running");
+                }
+            })
+            .catch((e) => {
+                showError(e, "Error while trying to run media process scheduler");
+            })
+            .finally(() => {
+                setDoingSchedulerRequest(false);
+            });
+    };
+
+    if (error !== undefined) {
+        return (
+            <ReloadBoundary reloadFunc={reloadData}>
+                <ErrorComponent
+                    error={error}
+                    objectName="Media Process Overview"
+                    showButtons={false}
+                />
+            </ReloadBoundary>
+        );
+    }
+
+    if (overviewData === undefined) {
+        return <Spinner visible={true} />;
+    }
+
+    return (
+        <ul className="list-unstyled">
+            <table className="table">
+                <thead>
+                    <tr>
+                        <th scope="col" style={{ width: "30%" }} />
+                        <th scope="col" style={{ width: "70%" }} />
+                    </tr>
+                </thead>
+                <tbody>
+                    <tr>
+                        <td>Current Media Process</td>
+                        <td>
+                            <textarea
+                                value={JSON.stringify(overviewData.process, null, "  ")}
+                                disabled={true}
+                                className="w-100"
+                            />
+                        </td>
+                    </tr>
+                    <tr>
+                        <td>Current Media Process SHA256</td>
+                        <td>{overviewData.process_sha256}</td>
+                    </tr>
+                    <tr>
+                        <td>Status</td>
+                        <td className="white-space-pre-wrap">{overviewData.status}</td>
+                    </tr>
+                    <tr>
+                        <td>
+                            Is automatic Media Process Scheduler enabled?{" "}
+                            <TooltipButton>
+                                This shows whether the media process scheduler will automatically
+                                run when any changes occur that might require processing media
+                                files. This can be changed in the lecture config or, if the lecture
+                                has specified &apos;Inherit&apos;, in the course config.
+                            </TooltipButton>
+                        </td>
+                        <td>
+                            {overviewData.is_automatic_media_process_scheduler_enabled
+                                ? "Yes"
+                                : "No"}
+                        </td>
+                    </tr>
+                </tbody>
+            </table>
+            <div className="mb-4 d-flex align-items-center">
+                <button
+                    onClick={runScheduler}
+                    disabled={doingSchedulerRequest}
+                    className="btn btn-secondary"
+                >
+                    Run Media Process Scheduler Manually
+                </button>
+                <TooltipButton>
+                    If the automatic media process scheduler is disabled, you can run it manually
+                    here (You will need to run it multiple times until the process is finished;
+                    after every finished job).
+                    <br />
+                    <br />
+                    If a job failed the automatic process scheduler will also not run again
+                    automatically (to prevent infinite loops) and you can run it manually. It will
+                    automatically detect the failed job and schedule a new one which might or might
+                    not fail again.
+                    <br />
+                    <br />
+                    In all other cases you do not need to run it manually.
+                </TooltipButton>
+                <Spinner visible={doingSchedulerRequest} />
+                {schedulerJobId && (
+                    <span className="m-1">
+                        <JobStatusCard jobId={schedulerJobId} />
+                    </span>
+                )}
+            </div>
+            <li>
+                <ul className="list-unstyled">
+                    {Object.values(overviewData.sorter_files).map((file) => (
+                        <li key={file.id}>
+                            <SorterFileListItem context={overviewData} sorterFileId={file.id} />
+                        </li>
+                    ))}
+                </ul>
+            </li>
+            <li>
+                <ul className="list-unstyled">
+                    {Object.values(overviewData.medium_files).map((file) => (
+                        <li key={file.id}>
+                            <MediumFileListItem context={overviewData} mediumFileId={file.id} />
+                        </li>
+                    ))}
+                </ul>
+            </li>
+        </ul>
+    );
+}
+
+export function ModalMediaProcessOverview({
+    lectureId,
+    className,
+}: {
+    lectureId: int;
+    className: string;
+}) {
+    const [showModal, setShowModal] = useState(false);
+    return (
+        <>
+            <button
+                type="button"
+                className={"btn btn-primary " + (className ?? "")}
+                onClick={() => setShowModal(true)}
+            >
+                <span className="bi bi-cpu" />
+            </button>
+            <Modal show={showModal} size="xl" onHide={() => setShowModal(false)}>
+                <Modal.Header closeButton>
+                    <Modal.Title>Media Process Overview</Modal.Title>
+                </Modal.Header>
+                <Modal.Body>
+                    {showModal && <MediaProcessOverview lectureId={lectureId} />}
+                </Modal.Body>
+            </Modal>
+        </>
+    );
+}