From bbcd7adc5849af71f034f53805819d6dc8cb1ff5 Mon Sep 17 00:00:00 2001
From: tws101 <tws101@users.noreply.github.com>
Date: Sat, 4 Jan 2025 10:20:13 -0600
Subject: [PATCH] Add files via upload

---
 .../1.0.0/index.ts                            |  91 ++++
 .../1.0.0/index.ts                            | 218 ++++++++
 .../1.0.0/index.ts                            | 476 ++++++++++++++++++
 .../0.7.0/index.ts                            | 217 ++++++++
 .../1.0.0/index.ts                            | 301 +++++++++++
 .../1.0.0/index.ts                            | 138 +++++
 .../1.0.0/index.ts                            |  69 +++
 .../1.0.0/index.ts                            | 112 +++++
 .../1.0.0/index.ts                            |  76 +++
 9 files changed, 1698 insertions(+)
 create mode 100644 FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandAudioRemoveCommentary/1.0.0/index.ts
 create mode 100644 FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandAudioRemoveDuplicates/1.0.0/index.ts
 create mode 100644 FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandAudioRemoveUnwantedLanuguages/1.0.0/index.ts
 create mode 100644 FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandStandardizeAllAudio/0.7.0/index.ts
 create mode 100644 FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandSubtitlesAddtoMKV/1.0.0/index.ts
 create mode 100644 FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandSubtitlesExtractSubs/1.0.0/index.ts
 create mode 100644 FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandSubtitlesRemoveCommentary/1.0.0/index.ts
 create mode 100644 FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandSubtitlesRemovebyLanguage/1.0.0/index.ts
 create mode 100644 FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandSubtitlesRemovebyProperty/1.0.0/index.ts

diff --git a/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandAudioRemoveCommentary/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandAudioRemoveCommentary/1.0.0/index.ts
new file mode 100644
index 000000000..f28bf3e6b
--- /dev/null
+++ b/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandAudioRemoveCommentary/1.0.0/index.ts
@@ -0,0 +1,91 @@
+import {
+  IpluginDetails,
+  IpluginInputArgs,
+  IpluginOutputArgs,
+} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces';
+import { Istreams } from '../../../../FlowHelpers/1.0.0/interfaces/synced/IFileObject';
+
+/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */
+const details = () :IpluginDetails => ({
+  name: 'Audio Remove Commentary',
+  description: 'Checks all Audio streams for commentary and removes them',
+  style: {
+    borderColor: '#6efefc',
+  },
+  tags: 'audio',
+  isStartPlugin: false,
+  pType: '',
+  requiresVersion: '2.11.01',
+  sidebarPosition: -1,
+  icon: '',
+  inputs: [
+  ],
+  outputs: [
+    {
+      number: 1,
+      tooltip: 'Go to Next Plugin',
+    },
+  ],
+});
+
+const noCommentary = (stream :Istreams) => {
+  if (!stream.tags || !stream.tags.title) {
+    return true;
+  } if (
+    stream.tags.title.toLowerCase().includes('commentary')
+    || stream.tags.title.toLowerCase().includes('description')
+    || stream.tags.title.toLowerCase().includes('sdh')
+  ) {
+    return false;
+  }
+  return true;
+};
+
+const findNumberOfAudioStream = (args :IpluginInputArgs) => {
+  if (args.inputFileObj.ffProbeData.streams) {
+    const number = args.inputFileObj.ffProbeData.streams.filter(
+      (stream :Istreams) => stream.codec_type === 'audio',
+    ).length;
+    return number;
+  }
+  return 0;
+};
+
+const removeCommentary = (args :IpluginInputArgs) => {
+  const numberOfAudioStreams = Number(findNumberOfAudioStream(args));
+  let audioStreamsRemoved = 0;
+  args.variables.ffmpegCommand.streams.forEach((stream) => {
+    if (stream.codec_type !== 'audio') {
+      return;
+    }
+    if (noCommentary(stream)) {
+      return;
+    }
+    args.jobLog(`Removing Stream ${stream.index} Commentray Detected`);
+    // eslint-disable-next-line no-param-reassign
+    stream.removed = true;
+    audioStreamsRemoved += 1;
+  });
+  if (audioStreamsRemoved === numberOfAudioStreams) {
+    throw new Error('All audio streams would be removed.');
+  }
+};
+
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+const plugin = (args:IpluginInputArgs):IpluginOutputArgs => {
+  const lib = require('../../../../../methods/lib')();
+  // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign
+  args.inputs = lib.loadDefaultValues(args.inputs, details);
+
+  removeCommentary(args);
+
+  return {
+    outputFileObj: args.inputFileObj,
+    outputNumber: 1,
+    variables: args.variables,
+  };
+};
+export {
+  details,
+  plugin,
+};
diff --git a/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandAudioRemoveDuplicates/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandAudioRemoveDuplicates/1.0.0/index.ts
new file mode 100644
index 000000000..a3d596b05
--- /dev/null
+++ b/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandAudioRemoveDuplicates/1.0.0/index.ts
@@ -0,0 +1,218 @@
+import {
+  IpluginDetails,
+  IpluginInputArgs,
+  IpluginOutputArgs,
+} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces';
+import { Istreams } from '../../../../FlowHelpers/1.0.0/interfaces/synced/IFileObject';
+
+/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */
+const details = () :IpluginDetails => ({
+  name: 'Audio Remove Duplicate Streams',
+  description: 'Remove Duplicate Audio Streams of each Language.',
+  style: {
+    borderColor: '#6efefc',
+  },
+  tags: 'audio',
+  isStartPlugin: false,
+  pType: '',
+  requiresVersion: '2.11.01',
+  sidebarPosition: -1,
+  icon: '',
+  inputs: [
+  ],
+  outputs: [
+    {
+      number: 1,
+      tooltip: 'Continue to next plugin',
+    },
+  ],
+});
+
+const findNumberOfAudioStream = (args :IpluginInputArgs) => {
+  if (args.inputFileObj.ffProbeData.streams) {
+    const number = args.inputFileObj.ffProbeData.streams.filter(
+      (stream :Istreams) => stream.codec_type === 'audio',
+    ).length;
+    return number;
+  }
+  return 0;
+};
+
+const getHighest = (first: Istreams, second: Istreams) => {
+  // @ts-expect-error channels
+  if (first?.channels > second?.channels) {
+    return first;
+  }
+  return second;
+};
+
+const noCommentary = (stream :Istreams) => {
+  if (!stream.tags || !stream.tags.title) {
+    return true;
+  } if (
+    stream.tags.title.toLowerCase().includes('commentary')
+    || stream.tags.title.toLowerCase().includes('description')
+    || stream.tags.title.toLowerCase().includes('sdh')
+  ) {
+    return false;
+  }
+  return true;
+};
+
+const undstreams = (args :IpluginInputArgs) => {
+  if (args.inputFileObj.ffProbeData.streams) {
+    const ustreams = args.inputFileObj.ffProbeData.streams.filter((stream :Istreams) => {
+      if (
+        stream.codec_type === 'audio'
+        && noCommentary(stream)
+        && (!stream.tags
+        || !stream.tags.language
+        || stream.tags.language.toLowerCase().includes('und'))
+      ) {
+        return true;
+      } return false;
+    });
+    return ustreams;
+  }
+  throw new Error('Error finding undefined streams');
+};
+
+const langStreams = (args :IpluginInputArgs, lang :string) => {
+  if (args.inputFileObj.ffProbeData.streams) {
+    const lStreams = args.inputFileObj.ffProbeData.streams.filter((stream :Istreams) => {
+      if (
+        stream.codec_type === 'audio'
+        && stream.tags?.language?.toLowerCase().includes(lang)
+        && noCommentary(stream)
+      ) {
+        return true;
+      } return false;
+    });
+    return lStreams;
+  }
+  throw new Error('Error finding duplicate streams');
+};
+
+const removeDuplicates = (args :IpluginInputArgs) => {
+  const numberOfAudioStreams = Number(findNumberOfAudioStream(args));
+  let hasDUPS = false;
+  let duplicates: Array<string> = [];
+  let audioStreamsRemoved = 0;
+
+  if (numberOfAudioStreams >= 2 && args.inputFileObj.ffProbeData.streams) {
+    const tag: Array<string> = [];
+    const audioStreams = args.inputFileObj.ffProbeData.streams.filter((stream :Istreams) => {
+      if (stream.codec_type === 'audio') {
+        return true;
+      } return false;
+    });
+
+    audioStreams.forEach((stream :Istreams) => {
+      let lang = '';
+      if (stream.tags !== undefined) {
+        if (stream.tags.language !== undefined) {
+          lang = stream.tags.language.toLowerCase();
+        } else {
+          lang = 'und';
+        }
+      } else {
+        lang = 'und';
+      }
+      tag.push(lang);
+    });
+
+    duplicates = tag.filter((item, index) => tag.indexOf(item) !== index);
+    if (duplicates.length >= 1) {
+      hasDUPS = true;
+    }
+  }
+
+  if (hasDUPS) {
+    const highestDUPS: Array<Istreams> = [];
+    const undhighestDUP: Array<Istreams> = [];
+    let undefIsDUP = false;
+    if (duplicates.includes('und')) {
+      undefIsDUP = true;
+    }
+
+    if (undefIsDUP) {
+      const findundID = (element :string) => element === 'und';
+      const iD = duplicates.findIndex(findundID);
+      duplicates.splice(iD, 1);
+      const undStreams = undstreams(args);
+      const streamwithhighestChannelCount = undStreams.reduce(getHighest);
+      undhighestDUP.push(streamwithhighestChannelCount);
+    }
+
+    duplicates.forEach((dup) => {
+      const streamWithTag = langStreams(args,
+        dup);
+      const streamwithhighestChannelCount = streamWithTag.reduce(getHighest);
+      highestDUPS.push(streamwithhighestChannelCount);
+    });
+
+    args.variables.ffmpegCommand.streams.forEach((stream) => {
+      if (stream.codec_type !== 'audio') {
+        return;
+      }
+      if (!undefIsDUP) {
+        if (stream.tags === undefined
+          || stream.tags.language === undefined
+          || stream.tags.language.toLowerCase().includes('und')
+        ) {
+          return;
+        }
+      }
+      if (stream.tags === undefined
+        || stream.tags.language === undefined
+        || stream.tags.language.toLowerCase().includes('und')
+      ) {
+        if (stream.index === undhighestDUP[0].index) {
+          return;
+        }
+        args.jobLog(`Removing Stream ${stream.index} Duplicate Detected`);
+        // eslint-disable-next-line no-param-reassign
+        stream.removed = true;
+        audioStreamsRemoved += 1;
+      }
+      if (stream.tags && stream.tags.language && !(duplicates.includes(stream.tags?.language?.toLowerCase()))) {
+        return;
+      }
+      let chosenStream = false;
+      highestDUPS.forEach((element :Istreams) => {
+        if (element.index === stream.index) {
+          chosenStream = true;
+        }
+      });
+      if (chosenStream) {
+        return;
+      }
+      args.jobLog(`Removing Stream ${stream.index} Duplicate Detected`);
+      // eslint-disable-next-line no-param-reassign
+      stream.removed = true;
+      audioStreamsRemoved += 1;
+    });
+  }
+  if (audioStreamsRemoved === numberOfAudioStreams) {
+    throw new Error('All audio streams would be removed.');
+  }
+};
+
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+const plugin = (args:IpluginInputArgs):IpluginOutputArgs => {
+  const lib = require('../../../../../methods/lib')();
+  // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign
+  args.inputs = lib.loadDefaultValues(args.inputs, details);
+
+  removeDuplicates(args);
+
+  return {
+    outputFileObj: args.inputFileObj,
+    outputNumber: 1,
+    variables: args.variables,
+  };
+};
+export {
+  details,
+  plugin,
+};
diff --git a/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandAudioRemoveUnwantedLanuguages/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandAudioRemoveUnwantedLanuguages/1.0.0/index.ts
new file mode 100644
index 000000000..275a40863
--- /dev/null
+++ b/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandAudioRemoveUnwantedLanuguages/1.0.0/index.ts
@@ -0,0 +1,476 @@
+import { Istreams } from '../../../../FlowHelpers/1.0.0/interfaces/synced/IFileObject';
+import {
+  IpluginDetails,
+  IpluginInputArgs,
+  IpluginOutputArgs,
+} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces';
+
+/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */
+const details = () :IpluginDetails => ({
+  name: 'Audio Remove Unwanted Languages',
+  description: 'Audio Remove Unwanted Languages, Define the languages you want to keep.'
+  + ' All others will be removed. If there is only one audio stream plugin will be skipped.',
+  style: {
+    borderColor: '#6efefc',
+  },
+  tags: 'audio',
+  isStartPlugin: false,
+  pType: '',
+  requiresVersion: '2.11.01',
+  sidebarPosition: -1,
+  icon: '',
+  inputs: [
+    {
+      label: 'Keep Languages',
+      name: 'langTags',
+      type: 'string',
+      defaultValue: 'eng',
+      inputUI: {
+        type: 'text',
+      },
+      tooltip:
+        'Choose the languages you want to keep.  Three letter format.'
+        + 'Seperate additional tags with commas eng,jpn,kor  ',
+    },
+    {
+      label: 'Keep Undefined',
+      name: 'keepUndefined',
+      type: 'boolean',
+      defaultValue: 'false',
+      inputUI: {
+        type: 'switch',
+      },
+      tooltip: 'Keeps the Undefined Audio Streams',
+    },
+    {
+      label: 'Keep Native, Requires API keys to check this if enabled.',
+      name: 'keepNative',
+      type: 'boolean',
+      defaultValue: 'false',
+      inputUI: {
+        type: 'switch',
+      },
+      tooltip: 'Toggle whether to enable setting keep native. ',
+    },
+    {
+      label: 'Priority, Check Radarr or Sonarr First',
+      name: 'priority',
+      type: 'string',
+      defaultValue: 'Radarr',
+      inputUI: {
+        type: 'dropdown',
+        options: [
+          'Radarr',
+          'Sonarr',
+        ],
+        displayConditions: {
+          logic: 'AND',
+          sets: [
+            {
+              logic: 'AND',
+              inputs: [
+                {
+                  name: 'keepNative',
+                  value: 'true',
+                  condition: '===',
+                },
+              ],
+            },
+          ],
+        },
+      },
+      tooltip: 'Specify the audio bitrate for newly added channels',
+    },
+    {
+      label: 'TMDB api key, It is recomended to add this under Tools Global Variables as api_key',
+      name: 'api_key',
+      type: 'string',
+      defaultValue: '{{{args.userVariables.global.api_key}}}',
+      inputUI: {
+        type: 'text',
+        displayConditions: {
+          logic: 'AND',
+          sets: [
+            {
+              logic: 'AND',
+              inputs: [
+                {
+                  name: 'keepNative',
+                  value: 'true',
+                  condition: '===',
+                },
+              ],
+            },
+          ],
+        },
+      },
+      tooltip: 'Input your TMDB api (v3) key here. (https://www.themoviedb.org/), or use api_key as a global variable.',
+    },
+    {
+      label: 'Radarr api key, It is recomended to add this under Tools Global Variables as radarr_api_key',
+      name: 'radarr_api_key',
+      type: 'string',
+      defaultValue: '{{{args.userVariables.global.radarr_api_key}}}',
+      inputUI: {
+        type: 'text',
+        displayConditions: {
+          logic: 'AND',
+          sets: [
+            {
+              logic: 'AND',
+              inputs: [
+                {
+                  name: 'keepNative',
+                  value: 'true',
+                  condition: '===',
+                },
+              ],
+            },
+          ],
+        },
+      },
+      tooltip: 'Input your Radarr api key here, or use radarr_api_key as a global variable.',
+    },
+    {
+      label: 'Radarr url, It is recomended to add this under Tools Global Variables as radarr_url',
+      name: 'radarr_url',
+      type: 'string',
+      defaultValue: '{{{args.userVariables.global.radarr_url}}}',
+      inputUI: {
+        type: 'text',
+        displayConditions: {
+          logic: 'AND',
+          sets: [
+            {
+              logic: 'AND',
+              inputs: [
+                {
+                  name: 'keepNative',
+                  value: 'true',
+                  condition: '===',
+                },
+              ],
+            },
+          ],
+        },
+      },
+      tooltip:
+        'Input your Radarr url here. (With the http://), do include the port,'
+        + 'or use radarr_url as a global variable.',
+    },
+    {
+      label: 'Sonarr api key, It is recomended to add this under Tools Global Variables as sonarr_api_key',
+      name: 'sonarr_api_key',
+      type: 'string',
+      defaultValue: '{{{args.userVariables.global.sonarr_api_key}}}',
+      inputUI: {
+        type: 'text',
+        displayConditions: {
+          logic: 'AND',
+          sets: [
+            {
+              logic: 'AND',
+              inputs: [
+                {
+                  name: 'keepNative',
+                  value: 'true',
+                  condition: '===',
+                },
+              ],
+            },
+          ],
+        },
+      },
+      tooltip: 'Input your Sonarr api key here, or use sonarr_api_key as a global variable.',
+    },
+    {
+      label: 'Sonarr url, It is recomended to add this under Tools Global Variables as sonarr_url',
+      name: 'sonarr_url',
+      type: 'string',
+      defaultValue: '{{{args.userVariables.global.sonarr_url}}}',
+      inputUI: {
+        type: 'text',
+        displayConditions: {
+          logic: 'AND',
+          sets: [
+            {
+              logic: 'AND',
+              inputs: [
+                {
+                  name: 'keepNative',
+                  value: 'true',
+                  condition: '===',
+                },
+              ],
+            },
+          ],
+        },
+      },
+      tooltip:
+        'Input your Sonarr url here. (With the http://), do include the port,'
+        + 'or use sonarr_url as a global variable.',
+    },
+  ],
+  outputs: [
+    {
+      number: 1,
+      tooltip: 'Continue to next plugin',
+    },
+  ],
+});
+
+const tmdbApi = async (filename :string, api_key :string, args :IpluginInputArgs) => {
+  let fileName = '';
+  // If filename begins with tt, it's already an imdb id
+  if (filename) {
+    if (filename.slice(0, 2) === 'tt') {
+      fileName = filename;
+    } else {
+      const idRegex = /(tt\d{7,8})/;
+      const fileMatch = filename.match(idRegex);
+      // eslint-disable-next-line prefer-destructuring
+      if (fileMatch) fileName = fileMatch[1];
+    }
+  }
+  if (fileName) {
+    const result = await args.deps.axios
+      .get(
+        `https://api.themoviedb.org/3/find/${fileName}?api_key=`
+        + `${api_key}&language=en-US&external_source=imdb_id`,
+      )
+      // eslint-disable-next-line @typescript-eslint/no-explicit-any
+      .then((resp :any) => (resp.data.movie_results.length > 0
+        ? resp.data.movie_results[0]
+        : resp.data.tv_results[0]));
+    if (!result) {
+      return null;
+    }
+    return result;
+  }
+  return null;
+};
+interface Body {
+  movie: {
+    imdbId: string,
+  }
+  series: {
+    imdbId: string,
+  }
+}
+const parseArrResponse = (body :Body, arr :string) => {
+  switch (arr) {
+    case 'radarr':
+      return body.movie;
+    case 'sonarr':
+      return body.series;
+    default:
+      throw new Error('This should never happen');
+  }
+};
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+const tmdbFetchResult = async (args :IpluginInputArgs, languages :any) => {
+  const priority = String(args.inputs.priority);
+  const api_key = String(args.inputs.api_key);
+  const radarr_api_key = String(args.inputs.radarr_api_key);
+  const radarr_url = String(args.inputs.radarr_url);
+  const sonarr_api_key = String(args.inputs.sonarr_api_key);
+  const sonarr_url = String(args.inputs.sonarr_url);
+  interface tmdbResult {
+    original_language: string,
+  }
+  let tmdbResult :null|tmdbResult = null;
+  let prio = ['radarr', 'sonarr'];
+  if (priority === 'sonarr') {
+    prio = ['sonarr', 'radarr'];
+  }
+  interface arrResult {
+    imdbId: string,
+    originalLanguage?: {
+      name?: string,
+    }
+  }
+  let radarrResult :null|arrResult = null;
+  let sonarrResult :null|arrResult = null;
+  if (args.inputFileObj.meta?.FileName) {
+    const fileNameEncoded = encodeURIComponent(args.inputFileObj.meta.FileName);
+    // eslint-disable-next-line no-restricted-syntax
+    for (const arr of prio) {
+      let imdbId = '';
+      switch (arr) {
+        case 'radarr':
+          if (tmdbResult) break;
+          if (radarr_api_key) {
+            // eslint-disable-next-line no-await-in-loop
+            radarrResult = await parseArrResponse(
+              // eslint-disable-next-line no-await-in-loop
+              await args.deps.axios
+                .get(
+                  `${radarr_url}/api/v3/parse?apikey=${radarr_api_key}&title=${fileNameEncoded}`,
+                ).then((resp :unknown) => {
+                  if (resp && typeof resp === 'object' && 'data' in resp && resp.data) {
+                    return resp.data;
+                  }
+                  return null;
+                }),
+              'radarr',
+            );
+            if (radarrResult) {
+              imdbId = radarrResult.imdbId;
+              args.jobLog(`Grabbed ID (${imdbId}) from Radarr `);
+              tmdbResult = { original_language: languages.getAlpha2Code(radarrResult.originalLanguage?.name, 'en') };
+            } else {
+              imdbId = fileNameEncoded;
+            }
+          }
+          break;
+        case 'sonarr':
+          if (tmdbResult) break;
+          if (sonarr_api_key) {
+            // eslint-disable-next-line no-await-in-loop
+            sonarrResult = await parseArrResponse(
+              // eslint-disable-next-line no-await-in-loop
+              await args.deps.axios.get(
+                `${sonarr_url}/api/v3/parse?apikey=${sonarr_api_key}&title=${fileNameEncoded}`,
+              ).then((resp :unknown) => {
+                if (resp && typeof resp === 'object' && 'data' in resp && resp.data) {
+                  return resp.data;
+                }
+                return null;
+              }),
+              'sonarr',
+            );
+            if (sonarrResult) {
+              imdbId = sonarrResult.imdbId;
+              args.jobLog(`Grabbed ID (${imdbId}) from Sonarr `);
+            } else {
+              imdbId = fileNameEncoded;
+            }
+            // eslint-disable-next-line no-await-in-loop
+            tmdbResult = await tmdbApi(imdbId, api_key, args);
+          }
+          break;
+        default:
+          throw new Error('This should never happen');
+      }
+    }
+    if (tmdbResult) {
+      return tmdbResult;
+    }
+    args.jobLog('Couldn\'t find the IMDB id of this file. I do not know what the native language is.');
+  }
+  return null;
+};
+
+const findNumberOfAudioStream = (args :IpluginInputArgs) => {
+  if (args.inputFileObj.ffProbeData.streams) {
+    const number = args.inputFileObj.ffProbeData.streams.filter(
+      (stream :Istreams) => stream.codec_type === 'audio',
+    ).length;
+    return number;
+  }
+  return 0;
+};
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+const refineLangTags = (languages :any, langTags :string[]) => {
+  const master = langTags;
+  langTags.forEach((element) => {
+    const lang = languages.alpha3BToAlpha2(element);
+    master.push(lang);
+  });
+  return master;
+};
+
+const removeUnwanted = (
+  args :IpluginInputArgs,
+  // eslint-disable-next-line @typescript-eslint/no-explicit-any
+  languages :any,
+  numberOfAudioStreams :number,
+  nativeLang :string,
+  nativeLangBool :boolean,
+) => {
+  const langTagsUnTrimmed = String(args.inputs.langTags).toLowerCase().split(',');
+  const langTags: Array<string> = [];
+  langTagsUnTrimmed.forEach((element) => {
+    const trimedElement = element.trim();
+    langTags.push(trimedElement);
+  });
+  const keepUndefined = Boolean(args.inputs.keepUndefined);
+
+  if (numberOfAudioStreams >= 2) {
+    const langTagsMaster = refineLangTags(languages, langTags);
+    let audioStreamsRemoved = 0;
+    args.variables.ffmpegCommand.streams.forEach((stream) => {
+      if (stream.codec_type !== 'audio') {
+        return;
+      }
+      if (keepUndefined) {
+        if ((!stream.tags || !stream.tags.language || stream.tags.language.toLowerCase().includes('und'))) {
+          return;
+        }
+      }
+      if (stream.tags && stream.tags.language && langTagsMaster.includes(stream.tags.language.toLowerCase())) {
+        return;
+      }
+      if (nativeLangBool) {
+        if (stream.tags?.language?.toLowerCase().includes(nativeLang)) {
+          return;
+        }
+      }
+      args.jobLog(`Removing Stream ${stream.index} is unwanted`);
+      // eslint-disable-next-line no-param-reassign
+      stream.removed = true;
+      audioStreamsRemoved += 1;
+    });
+    if (audioStreamsRemoved === numberOfAudioStreams) {
+      throw new Error('All audio streams would be removed.');
+    }
+  }
+};
+
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+const plugin = async (args: IpluginInputArgs): Promise<IpluginOutputArgs> => {
+  const lib = require('../../../../../methods/lib')();
+  // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign
+  args.inputs = lib.loadDefaultValues(args.inputs, details);
+  const dependencies = ['@cospired/i18n-iso-languages'];
+  await args.installClassicPluginDeps(dependencies);
+  // eslint-disable-next-line import/no-unresolved
+  const languages = require('@cospired/i18n-iso-languages');
+  const keepNative = Boolean(args.inputs.keepNative);
+  let nativeLang = '';
+  let nativeLangBool = false;
+  const numberOfAudioStreams = Number(findNumberOfAudioStream(args));
+
+  if (numberOfAudioStreams === 1) {
+    return {
+      outputFileObj: args.inputFileObj,
+      outputNumber: 1,
+      variables: args.variables,
+    };
+  }
+
+  if (keepNative) {
+    if (args.inputFileObj.ffProbeData.streams) {
+      const tmdbResult = await tmdbFetchResult(args, languages);
+      if (tmdbResult) {
+        const langsTemp :string = tmdbResult.original_language === 'cn' ? 'zh' : tmdbResult.original_language;
+        const originalLang :string = (languages.alpha2ToAlpha3B(langsTemp));
+        nativeLang = originalLang;
+        nativeLangBool = true;
+        args.jobLog(`Found ${langsTemp} using code ${nativeLang}`);
+      }
+    }
+  }
+
+  removeUnwanted(args, languages, numberOfAudioStreams, nativeLang, nativeLangBool);
+
+  return {
+    outputFileObj: args.inputFileObj,
+    outputNumber: 1,
+    variables: args.variables,
+  };
+};
+export {
+  details,
+  plugin,
+};
diff --git a/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandStandardizeAllAudio/0.7.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandStandardizeAllAudio/0.7.0/index.ts
new file mode 100644
index 000000000..808b02002
--- /dev/null
+++ b/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandStandardizeAllAudio/0.7.0/index.ts
@@ -0,0 +1,217 @@
+import { getFfType } from '../../../../FlowHelpers/1.0.0/fileUtils';
+import {
+  IpluginDetails,
+  IpluginInputArgs,
+  IpluginOutputArgs,
+} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces';
+
+/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */
+const details = () :IpluginDetails => ({
+  name: 'Standardize All Audio',
+  description: 'Standardize All Audio to the same codec. Command will be sent for all audio streams.'
+  + ' It is recomended to use Check All Audio Codecs or other verifiers before sending this command.'
+  + ' The following configurations are unsupported by FFmpeg.'
+  + ' FFmpeg does NOT support 1 channel truehd.'
+  + ' FFmpeg does NOT support 6 channel dca or libmp3lame.'
+  + ' FFmpeg does NOT support 8 channel dca, libmp3lame, truehd, ac3, or eac3.',
+  style: {
+    borderColor: '#6efefc',
+  },
+  tags: 'audio',
+  isStartPlugin: false,
+  pType: '',
+  requiresVersion: '2.11.01',
+  sidebarPosition: -1,
+  icon: '',
+  inputs: [
+    {
+      label: 'Audio Encoder',
+      name: 'audioEncoder',
+      type: 'string',
+      defaultValue: 'ac3',
+      inputUI: {
+        type: 'dropdown',
+        options: [
+          'aac',
+          'ac3',
+          'eac3',
+          'dca',
+          'flac',
+          'libopus',
+          'mp2',
+          'libmp3lame',
+          'truehd',
+        ],
+      },
+      tooltip: 'Enter the desired audio code',
+    },
+    {
+      label: 'Channels',
+      name: 'channels',
+      type: 'number',
+      defaultValue: '6',
+      inputUI: {
+        type: 'dropdown',
+        options: [
+          '1',
+          '2',
+          '6',
+          '8',
+        ],
+      },
+      tooltip: 'Enter the desired number of channel, certain channel counts are not supported with certain codec.',
+    },
+    {
+      label: 'Enable Bitrate',
+      name: 'enableBitrate',
+      type: 'boolean',
+      defaultValue: 'false',
+      inputUI: {
+        type: 'switch',
+      },
+      tooltip: 'Toggle whether to enable setting audio bitrate',
+    },
+    {
+      label: 'Bitrate',
+      name: 'bitrate',
+      type: 'string',
+      defaultValue: '300k',
+      inputUI: {
+        type: 'text',
+        displayConditions: {
+          logic: 'AND',
+          sets: [
+            {
+              logic: 'AND',
+              inputs: [
+                {
+                  name: 'enableBitrate',
+                  value: 'true',
+                  condition: '===',
+                },
+              ],
+            },
+          ],
+        },
+      },
+      tooltip: 'Specify the audio bitrate for newly added channels',
+    },
+    {
+      label: 'Enable Samplerate',
+      name: 'enableSamplerate',
+      type: 'boolean',
+      defaultValue: 'false',
+      inputUI: {
+        type: 'switch',
+      },
+      tooltip: 'Toggle whether to enable setting audio samplerate',
+    },
+    {
+      label: 'Samplerate',
+      name: 'samplerate',
+      type: 'string',
+      defaultValue: '48k',
+      inputUI: {
+        type: 'text',
+        displayConditions: {
+          logic: 'AND',
+          sets: [
+            {
+              logic: 'AND',
+              inputs: [
+                {
+                  name: 'enableSamplerate',
+                  value: 'true',
+                  condition: '===',
+                },
+              ],
+            },
+          ],
+        },
+      },
+      tooltip: 'Specify the audio bitrate for newly added channels',
+    },
+  ],
+  outputs: [
+    {
+      number: 1,
+      tooltip: 'Continue to next plugin',
+    },
+  ],
+});
+
+const checkAbort = (audioCodec :string, channelCount :number) => {
+  // channel count 1 not supported
+  if ((['truehd'].includes(audioCodec)) && channelCount === 1) {
+    throw new Error(
+      `Selected ${audioCodec} does not support the channel count of ${channelCount}. Reconfigure the Plugin`,
+    );
+  }
+  // channel count 6 not supported
+  if ((['dca', 'libmp3lame'].includes(audioCodec)) && channelCount === 6) {
+    throw new Error(
+      `Selected ${audioCodec} does not support the channel count of ${channelCount}. Reconfigure the Plugin`,
+    );
+  }
+  // channel count 8 not supported
+  if ((['dca', 'libmp3lame', 'truehd', 'ac3', 'eac3'].includes(audioCodec)) && channelCount === 8) {
+    throw new Error(
+      `Selected ${audioCodec} does not support the channel count of ${channelCount}. Reconfigure the Plugin`,
+    );
+  }
+};
+
+const transcodeStreams = (args :IpluginInputArgs) => {
+  const enableBitrate = Boolean(args.inputs.enableBitrate);
+  const bitrate = String(args.inputs.bitrate);
+  const enableSamplerate = Boolean(args.inputs.enableSamplerate);
+  const samplerate = String(args.inputs.samplerate);
+  const audioEncoder = String(args.inputs.audioEncoder);
+  const wantedChannelCount = Number(args.inputs.channels);
+
+  checkAbort(audioEncoder, wantedChannelCount);
+
+  args.variables.ffmpegCommand.streams.forEach((stream) => {
+    if (stream.codec_type !== 'audio') {
+      return;
+    }
+    let targetChannels = wantedChannelCount;
+    if (stream.channels && stream.channels < wantedChannelCount) {
+      targetChannels = Number(stream.channels);
+    }
+    args.jobLog(`Processing Stream ${stream.index} Standardizing`);
+    stream.outputArgs.push('-c:{outputIndex}', audioEncoder);
+    stream.outputArgs.push('-ac:{outputIndex}', `${targetChannels}`);
+    if (enableBitrate) {
+      const ffType = getFfType(stream.codec_type);
+      stream.outputArgs.push(`-b:${ffType}:{outputTypeIndex}`, `${bitrate}`);
+    }
+    if (enableSamplerate) {
+      stream.outputArgs.push('-ar:{outputIndex}', `${samplerate}`);
+    }
+    if (['dca', 'truehd', 'flac'].includes(audioEncoder)) {
+      stream.outputArgs.push('-strict', '-2');
+    }
+  });
+  // eslint-disable-next-line no-param-reassign
+  args.variables.ffmpegCommand.shouldProcess = true;
+};
+
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+const plugin = (args:IpluginInputArgs):IpluginOutputArgs => {
+  const lib = require('../../../../../methods/lib')();
+  // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign
+  args.inputs = lib.loadDefaultValues(args.inputs, details);
+
+  transcodeStreams(args);
+
+  return {
+    outputFileObj: args.inputFileObj,
+    outputNumber: 1,
+    variables: args.variables,
+  };
+};
+export {
+  details,
+  plugin,
+};
diff --git a/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandSubtitlesAddtoMKV/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandSubtitlesAddtoMKV/1.0.0/index.ts
new file mode 100644
index 000000000..595b43955
--- /dev/null
+++ b/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandSubtitlesAddtoMKV/1.0.0/index.ts
@@ -0,0 +1,301 @@
+import { getFileName, getFileAbosluteDir } from '../../../../FlowHelpers/1.0.0/fileUtils';
+import { Istreams } from '../../../../FlowHelpers/1.0.0/interfaces/synced/IFileObject';
+import {
+  IpluginDetails,
+  IpluginInputArgs,
+  IpluginOutputArgs,
+} from '../../../../FlowHelpers/1.1.0/interfaces/interfaces';
+
+/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */
+const details = () :IpluginDetails => ({
+  name: 'Subtitles Add to MKV',
+  description:
+    'Add Subtitles in SRT to MKV,'
+    + ' You must you the Begin/Exectute Command made for Multi Input.',
+  style: {
+    borderColor: '#6efefc',
+  },
+  tags: 'subtitle',
+  isStartPlugin: false,
+  pType: '',
+  requiresVersion: '2.11.01',
+  sidebarPosition: -1,
+  icon: '',
+  inputs: [
+    {
+      label: 'ONE Language tag to Add to MKV',
+      name: 'langTag',
+      type: 'string',
+      defaultValue: 'eng',
+      inputUI: {
+        type: 'text',
+      },
+      tooltip: 'Choose ONE three letter language tag to insert into the mkv.',
+    },
+    {
+      label: 'Include Forced Subs',
+      name: 'include_forced',
+      type: 'boolean',
+      defaultValue: 'false',
+      inputUI: {
+        type: 'switch',
+      },
+      tooltip:
+        'Forced subtitles will also be added, required naming is source.eng.forced.srt,'
+        + 'this example assumes chosen tag is eng.',
+    },
+    {
+      label: 'Include SDH Subs',
+      name: 'include_sdh',
+      type: 'boolean',
+      defaultValue: 'false',
+      inputUI: {
+        type: 'switch',
+      },
+      tooltip:
+        'Sdh subtitles will also be added, required naming is source.eng.sdh.srt,'
+        + 'this example assumes chosen tag is eng.',
+    },
+    {
+      label: 'Include CC Subs',
+      name: 'include_cc',
+      type: 'boolean',
+      defaultValue: 'false',
+      inputUI: {
+        type: 'switch',
+      },
+      tooltip:
+        'Sdh subtitles will also be added, required naming is source.eng.cc.srt,'
+        + 'this example assumes chosen tag is eng.',
+    },
+  ],
+  outputs: [
+    {
+      number: 1,
+      tooltip: 'Continue to next plugin',
+    },
+  ],
+});
+
+// eslint-disable-next-line @typescript-eslint/ban-types
+const loopOverStreamsOfType = (args :IpluginInputArgs, type :string, method :Function) => {
+  if (args.inputFileObj.ffProbeData.streams) {
+    for (let i = 0; i < args.inputFileObj.ffProbeData.streams.length; i++) {
+      if (args.inputFileObj.ffProbeData.streams[i].codec_type.toLowerCase() === type) {
+        method(args.inputFileObj.ffProbeData.streams[i]);
+      }
+    }
+  }
+};
+
+const buildSubtitleConfiguration = (args :IpluginInputArgs) => {
+  // eslint-disable-next-line import/no-unresolved
+  const languages = require('@cospired/i18n-iso-languages');
+  const fs = require('fs');
+  const processLanguage = String(args.inputs.langTag).trim();
+  const processLanguage2 :string = languages.alpha3BToAlpha2(processLanguage);
+  const boolGetForced = Boolean(args.inputs.include_forced);
+  const boolGetSdh = Boolean(args.inputs.include_sdh);
+  const boolGetCc = Boolean(args.inputs.include_cc);
+  const subtitleSettings = {
+    processFile: <boolean> false,
+    subInput: [] as string[],
+    subOutput: [] as string[],
+  };
+
+  let embeddedSubs = 0;
+  let boolHaveMain = false;
+  let boolHaveForced = false;
+  let boolHaveSdh = false;
+  let boolHaveCc = false;
+  // Loop through the streams
+  const subProcess = (stream :Istreams) => {
+    // eslint-disable-next-line no-plusplus
+    embeddedSubs++;
+    let lang = '';
+    let title = '';
+    let codec = '';
+    if (stream.tags !== undefined) {
+      if (stream.tags.language !== undefined) {
+        lang = stream.tags.language.toLowerCase();
+      }
+      if (stream.tags.title !== undefined) {
+        title = stream.tags.title.toLowerCase();
+      }
+    }
+    if (stream.codec_name !== undefined) {
+      codec = stream.codec_name.toLowerCase();
+    }
+
+    // Ignore these codecs
+    if (codec !== 'subrip' && codec !== 'ass' && codec !== 'mov_text' && codec !== 'ssa') {
+      return;
+    }
+
+    // Ignore languages we dont want
+    if (processLanguage !== lang && processLanguage2 !== lang) {
+      return;
+    }
+
+    // Check these titles and determine if we already have what we want
+    if (processLanguage === lang || processLanguage2 === lang) {
+      if (stream.disposition.forced || (title.includes('forced'))) {
+        boolHaveForced = true;
+      } else if (stream.disposition.sdh || (title.includes('sdh'))) {
+        boolHaveSdh = true;
+      } else if (stream.disposition.cc || (title.includes('cc'))) {
+        boolHaveCc = true;
+      } else {
+        boolHaveMain = true;
+      }
+    }
+  };
+
+  loopOverStreamsOfType(args, 'subtitle', subProcess);
+
+  // Check if all Good or Determine if we are missing a sub we want in the MKV
+  let boolCheckForSrt = false;
+  if (!boolHaveMain) {
+    boolCheckForSrt = true;
+  }
+  if (boolGetForced) {
+    if (!boolHaveForced) {
+      boolCheckForSrt = true;
+    }
+  }
+  if (boolGetSdh) {
+    if (!boolHaveSdh) {
+      boolCheckForSrt = true;
+    }
+  }
+  if (boolGetCc) {
+    if (!boolHaveCc) {
+      boolCheckForSrt = true;
+    }
+  }
+  if (!boolCheckForSrt) {
+    return subtitleSettings;
+  }
+
+  // Setup Variable to Check Disk for the files
+  const dispostionMain = '';
+  const dispostionForced = '.forced';
+  const dispostionSdh = '.sdh';
+  const dispostionCc = '.cc';
+
+  const buildSrtFile = (lang :string, disposition :string) => {
+    const fileName = getFileName(args.originalLibraryFile._id);
+    const orignalFolder = getFileAbosluteDir(args.originalLibraryFile._id);
+    const tempsrtFile = [orignalFolder, '/', fileName];
+    tempsrtFile.push(`.${lang}${disposition}.srt`);
+    const srtFile = tempsrtFile.join('');
+    return srtFile;
+  };
+
+  const mainSubFile = buildSrtFile(processLanguage, dispostionMain);
+  const mainAltSubFile = buildSrtFile(processLanguage2, dispostionMain);
+  const forcedSubFile = buildSrtFile(processLanguage, dispostionForced);
+  const forcedAltSubFile = buildSrtFile(processLanguage2, dispostionForced);
+  const sdhSubFile = buildSrtFile(processLanguage, dispostionSdh);
+  const sdhAltSubFile = buildSrtFile(processLanguage2, dispostionSdh);
+  const ccSubFile = buildSrtFile(processLanguage, dispostionCc);
+  const ccAltSubFile = buildSrtFile(processLanguage2, dispostionCc);
+
+  // Check for the SRT files names we want
+  const findSrtFile = (subFile :string, altSubFile :string) => {
+    if (fs.existsSync(`${subFile}`)) {
+      return subFile;
+    }
+    if (fs.existsSync(`${altSubFile}`)) {
+      return altSubFile;
+    }
+    return null;
+  };
+
+  const mainChosenSubsFile = findSrtFile(mainSubFile, mainAltSubFile);
+  const forcedChosenSubsFile = findSrtFile(forcedSubFile, forcedAltSubFile);
+  const sdhChosenSubsFile = findSrtFile(sdhSubFile, sdhAltSubFile);
+  const ccChosenSubsFile = findSrtFile(ccSubFile, ccAltSubFile);
+
+  // Add Subs to MKV
+  let subIndex = 1;
+  const titlepMain = 'default';
+  const titleForced = 'forced';
+  const titleSdh = 'sdh';
+  const titleCc = 'cc';
+
+  const transcode = (chosenSubsFile :string, title :string) => {
+    let disposition = title;
+    if (disposition === 'sdh' || disposition === 'cc') {
+      disposition = 'hearing_impaired';
+    }
+    const mInput = ['-sub_charenc', 'UTF-8', '-f', 'srt', '-i', chosenSubsFile];
+    mInput.forEach((element) => {
+      subtitleSettings.subInput.push(element);
+    });
+    const output = [
+      '-map', `${subIndex}:s`, `-codec:s:${embeddedSubs}`, 'srt', `-metadata:s:s:${embeddedSubs}`,
+      `language=${processLanguage}`, `-metadata:s:s:${embeddedSubs}`,
+      `title=${title}`, `-disposition:s:${embeddedSubs}`, disposition];
+    output.forEach((element) => {
+      subtitleSettings.subOutput.push(element);
+    });
+    // eslint-disable-next-line no-plusplus
+    embeddedSubs++;
+    // eslint-disable-next-line no-plusplus
+    subIndex++;
+  };
+
+  if (mainChosenSubsFile != null && !boolHaveMain) {
+    transcode(mainChosenSubsFile, titlepMain);
+    args.jobLog(`Adding ${args.inputs.langTag} SRT to MKV`);
+    subtitleSettings.processFile = true;
+  }
+  if (forcedChosenSubsFile != null && boolGetForced && !boolHaveForced) {
+    transcode(forcedChosenSubsFile, titleForced);
+    args.jobLog(`Adding ${args.inputs.langTag} SRT FORCED to MKV`);
+    subtitleSettings.processFile = true;
+  }
+  if (sdhChosenSubsFile != null && boolGetSdh && !boolHaveSdh) {
+    transcode(sdhChosenSubsFile, titleSdh);
+    args.jobLog(`Adding ${args.inputs.langTag} SRT SDH to MKV`);
+    subtitleSettings.processFile = true;
+  }
+  if (ccChosenSubsFile != null && boolGetCc && !boolHaveCc) {
+    transcode(ccChosenSubsFile, titleCc);
+    args.jobLog(`Adding ${args.inputs.langTag} SRT CC to MKV`);
+    subtitleSettings.processFile = true;
+  }
+
+  return subtitleSettings;
+};
+
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+const plugin = async (args: IpluginInputArgs): Promise<IpluginOutputArgs> => {
+  const lib = require('../../../../../methods/lib')();
+  // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign
+  args.inputs = lib.loadDefaultValues(args.inputs, details);
+  const dependencies = ['@cospired/i18n-iso-languages'];
+  await args.installClassicPluginDeps(dependencies);
+
+  const subtitleSettings = buildSubtitleConfiguration(args);
+
+  if (subtitleSettings.processFile) {
+    subtitleSettings.subInput.forEach((element) => {
+      args.variables.ffmpegCommand.multiInputArguments.push(element);
+    });
+    subtitleSettings.subOutput.forEach((element) => {
+      args.variables.ffmpegCommand.overallOuputArguments.push(element);
+    });
+  }
+
+  return {
+    outputFileObj: args.inputFileObj,
+    outputNumber: 1,
+    variables: args.variables,
+  };
+};
+export {
+  details,
+  plugin,
+};
diff --git a/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandSubtitlesExtractSubs/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandSubtitlesExtractSubs/1.0.0/index.ts
new file mode 100644
index 000000000..8731d3104
--- /dev/null
+++ b/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandSubtitlesExtractSubs/1.0.0/index.ts
@@ -0,0 +1,138 @@
+import { getFileName, getFileAbosluteDir } from '../../../../FlowHelpers/1.0.0/fileUtils';
+import {
+  IpluginDetails,
+  IpluginInputArgs,
+  IpluginOutputArgs,
+} from '../../../../FlowHelpers/1.1.0/interfaces/interfaces';
+
+/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */
+const details = () :IpluginDetails => ({
+  name: 'Subtitles Extract Subs',
+  description:
+    'Extract Subtitles to SRT,'
+    + ' You must use the Begin/Exectute Command made for Multi Output.'
+    + 'This outputs the subs to the input folder',
+  style: {
+    borderColor: '#6efefc',
+  },
+  tags: 'subtitle',
+  isStartPlugin: false,
+  pType: '',
+  requiresVersion: '2.11.01',
+  sidebarPosition: -1,
+  icon: '',
+  inputs: [
+    {
+      label: 'Overwrite existing SRT Files',
+      name: 'overwrite',
+      type: 'boolean',
+      defaultValue: 'false',
+      inputUI: {
+        type: 'switch',
+      },
+      tooltip: 'Overwrite with the extracted SRT files',
+    },
+  ],
+  outputs: [
+    {
+      number: 1,
+      tooltip: 'Continue to next plugin',
+    },
+  ],
+});
+
+const buildSubtitleConfiguration = (args :IpluginInputArgs) => {
+  const overwrite = Boolean(args.inputs.overwrite);
+  const fs = require('fs');
+  let subIdx = -1;
+  const subtitleSettings = {
+    processFile: <boolean> false,
+    subOutput: [] as string[],
+  };
+
+  args.variables.ffmpegCommand.streams.forEach((stream) => {
+    if (stream.codec_type !== 'subtitle') {
+      return;
+    }
+    subIdx += 1;
+    if (stream.removed) {
+      return;
+    }
+    let lang = '';
+    let title = '';
+    let strDisposition = '';
+    let boolTextSubs = false;
+    let codec = '';
+    if (stream.tags?.language !== undefined) {
+      lang = stream.tags.language.toLowerCase();
+    }
+    if (stream.tags?.title !== undefined) {
+      title = stream.tags.title.toLowerCase();
+    }
+    if (stream.codec_name !== undefined) {
+      codec = stream.codec_name.toLowerCase();
+    }
+    if (stream.disposition.forced || (title.includes('forced'))) {
+      strDisposition = '.forced';
+    } else if (stream.disposition.sdh || (title.includes('sdh'))) {
+      strDisposition = '.sdh';
+    } else if (stream.disposition.cc || (title.includes('cc'))) {
+      strDisposition = '.cc';
+    } else if (stream.disposition.commentary || stream.disposition.description
+      || (title.includes('commentary')) || (title.includes('description'))) {
+      strDisposition = '.commentary';
+    } else if (stream.disposition.lyrics
+      || (title.includes('signs')) || (title.includes('songs'))) {
+      strDisposition = '.signsandsongs';
+    }
+    if (codec === 'ass' || codec === 'mov_text' || codec === 'ssa' || codec === 'subrip') {
+      boolTextSubs = true;
+    }
+    if (!boolTextSubs) {
+      return;
+    }
+    // Build subtitle file names.
+    const fileName = getFileName(args.originalLibraryFile._id);
+    const orignalFolder = getFileAbosluteDir(args.originalLibraryFile._id);
+    const tempsubsFile = [orignalFolder, '/', fileName];
+    if (lang === '') {
+      tempsubsFile.push(`.und${strDisposition}.srt`);
+    } else {
+      tempsubsFile.push(`.${lang}${strDisposition}.srt`);
+    }
+    const subsFile = tempsubsFile.join('');
+    // Send Commands.
+    if (fs.existsSync(subsFile) && !overwrite) {
+      return;
+    }
+    args.jobLog(`Extracting Subtitles at index ${stream.index}`);
+    subtitleSettings.processFile = true;
+    subtitleSettings.subOutput.push('-map', `0:s:${subIdx}`, subsFile);
+  });
+  return subtitleSettings;
+};
+
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+const plugin = (args:IpluginInputArgs):IpluginOutputArgs => {
+  const lib = require('../../../../../methods/lib')();
+  // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign
+  args.inputs = lib.loadDefaultValues(args.inputs, details);
+
+  const subtitleSettings = buildSubtitleConfiguration(args);
+
+  if (subtitleSettings.processFile) {
+    subtitleSettings.subOutput.forEach((element) => {
+      args.variables.ffmpegCommand.multiOutputArguments.push(element);
+    });
+  }
+
+  return {
+    outputFileObj: args.inputFileObj,
+    outputNumber: 1,
+    variables: args.variables,
+  };
+};
+export {
+  details,
+  plugin,
+};
diff --git a/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandSubtitlesRemoveCommentary/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandSubtitlesRemoveCommentary/1.0.0/index.ts
new file mode 100644
index 000000000..5006d83d5
--- /dev/null
+++ b/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandSubtitlesRemoveCommentary/1.0.0/index.ts
@@ -0,0 +1,69 @@
+import {
+  IpluginDetails,
+  IpluginInputArgs,
+  IpluginOutputArgs,
+} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces';
+
+/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */
+const details = () :IpluginDetails => ({
+  name: 'Subtitles Remove Commentary',
+  description: 'Remove Commentary',
+  style: {
+    borderColor: '#6efefc',
+  },
+  tags: 'subtitle',
+  isStartPlugin: false,
+  pType: '',
+  requiresVersion: '2.11.01',
+  sidebarPosition: -1,
+  icon: '',
+  inputs: [
+  ],
+  outputs: [
+    {
+      number: 1,
+      tooltip: 'Continue to next plugin',
+    },
+  ],
+});
+
+const removeCommentary = (args :IpluginInputArgs) => {
+  args.variables.ffmpegCommand.streams.forEach((stream) => {
+    if (stream.codec_type !== 'subtitle') {
+      return;
+    }
+    let title = '';
+    if (stream.tags !== undefined) {
+      if (stream.tags.title !== undefined) {
+        title = stream.tags.title.toLowerCase();
+      }
+    }
+    if (stream.disposition.commentary
+      || stream.disposition.description
+      || (title.includes('commentary'))
+      || (title.includes('description'))) {
+      args.jobLog(`Removing Subtitles at index ${stream.index} Commentary Detected`);
+      // eslint-disable-next-line no-param-reassign
+      stream.removed = true;
+    }
+  });
+};
+
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+const plugin = (args:IpluginInputArgs):IpluginOutputArgs => {
+  const lib = require('../../../../../methods/lib')();
+  // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign
+  args.inputs = lib.loadDefaultValues(args.inputs, details);
+
+  removeCommentary(args);
+
+  return {
+    outputFileObj: args.inputFileObj,
+    outputNumber: 1,
+    variables: args.variables,
+  };
+};
+export {
+  details,
+  plugin,
+};
diff --git a/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandSubtitlesRemovebyLanguage/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandSubtitlesRemovebyLanguage/1.0.0/index.ts
new file mode 100644
index 000000000..437c9df06
--- /dev/null
+++ b/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandSubtitlesRemovebyLanguage/1.0.0/index.ts
@@ -0,0 +1,112 @@
+import {
+  IpluginDetails,
+  IpluginInputArgs,
+  IpluginOutputArgs,
+} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces';
+
+/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */
+const details = () :IpluginDetails => ({
+  name: 'Subtitles Remove by Language',
+  description: 'Remove by Language',
+  style: {
+    borderColor: '#6efefc',
+  },
+  tags: 'subtitle',
+  isStartPlugin: false,
+  pType: '',
+  requiresVersion: '2.11.01',
+  sidebarPosition: -1,
+  icon: '',
+  inputs: [
+    {
+      label: 'Keep Languages',
+      name: 'langTags',
+      type: 'string',
+      defaultValue: 'eng',
+      inputUI: {
+        type: 'text',
+      },
+      tooltip:
+      'Choose the subtitles languages you want to keep. Three letter format.'
+      + 'Seperate additional tags with commas eng,jpn,kor ',
+    },
+    {
+      label: 'Keep Undefined',
+      name: 'keepUndefined',
+      type: 'boolean',
+      defaultValue: 'false',
+      inputUI: {
+        type: 'switch',
+      },
+      tooltip: 'Keeps the Undefined Subtitles  Streams',
+    },
+  ],
+  outputs: [
+    {
+      number: 1,
+      tooltip: 'Continue to next plugin',
+    },
+  ],
+});
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+const refineLangTags = (languages :any, langTags :string[]) => {
+  const master = langTags;
+  langTags.forEach((element :string) => {
+    const lang = languages.alpha3BToAlpha2(element);
+    master.push(lang);
+  });
+  return master;
+};
+
+const removeNyLanguage = (args :IpluginInputArgs) => {
+  // eslint-disable-next-line import/no-unresolved
+  const languages = require('@cospired/i18n-iso-languages');
+  const keepUndefined = Boolean(args.inputs.keepUndefined);
+  const langTagsUnTrimmed = String(args.inputs.langTags).toLowerCase().split(',');
+  const langTags: Array<string> = [];
+  langTagsUnTrimmed.forEach((element) => {
+    const trimedElement = element.trim();
+    langTags.push(trimedElement);
+  });
+
+  const langTagsMaster = refineLangTags(languages, langTags);
+
+  args.variables.ffmpegCommand.streams.forEach((stream) => {
+    if (stream.codec_type !== 'subtitle') {
+      return;
+    }
+    if (keepUndefined) {
+      if ((!stream.tags || !stream.tags.language || stream.tags.language.toLowerCase().includes('und'))) {
+        return;
+      }
+    }
+    if (stream.tags && stream.tags.language && langTagsMaster.includes(stream.tags.language.toLowerCase())) {
+      return;
+    }
+    args.jobLog(`Removing Subtitles at index ${stream.index} Unwanted Language`);
+    // eslint-disable-next-line no-param-reassign
+    stream.removed = true;
+  });
+};
+
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+const plugin = async (args: IpluginInputArgs): Promise<IpluginOutputArgs> => {
+  const lib = require('../../../../../methods/lib')();
+  // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign
+  args.inputs = lib.loadDefaultValues(args.inputs, details);
+  const dependencies = ['@cospired/i18n-iso-languages'];
+  await args.installClassicPluginDeps(dependencies);
+
+  removeNyLanguage(args);
+
+  return {
+    outputFileObj: args.inputFileObj,
+    outputNumber: 1,
+    variables: args.variables,
+  };
+};
+export {
+  details,
+  plugin,
+};
diff --git a/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandSubtitlesRemovebyProperty/1.0.0/index.ts b/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandSubtitlesRemovebyProperty/1.0.0/index.ts
new file mode 100644
index 000000000..f4c214f8a
--- /dev/null
+++ b/FlowPluginsTs/CommunityFlowPlugins/ffmpegCommand/ffmpegCommandSubtitlesRemovebyProperty/1.0.0/index.ts
@@ -0,0 +1,76 @@
+import {
+  IpluginDetails,
+  IpluginInputArgs,
+  IpluginOutputArgs,
+} from '../../../../FlowHelpers/1.0.0/interfaces/interfaces';
+
+/* eslint no-plusplus: ["error", { "allowForLoopAfterthoughts": true }] */
+const details = () :IpluginDetails => ({
+  name: 'Subtitles Remove by Title',
+  description: 'Remove by Title',
+  style: {
+    borderColor: '#6efefc',
+  },
+  tags: 'subtitle',
+  isStartPlugin: false,
+  pType: '',
+  requiresVersion: '2.11.01',
+  sidebarPosition: -1,
+  icon: '',
+  inputs: [
+    {
+      label: 'Title to Remove',
+      name: 'valuesToRemove',
+      type: 'string',
+      defaultValue: 'forced',
+      inputUI: {
+        type: 'text',
+      },
+      tooltip: '\n Choose one Title to Remove  ',
+    },
+  ],
+  outputs: [
+    {
+      number: 1,
+      tooltip: 'Continue to next plugin',
+    },
+  ],
+});
+
+const removeByTitle = (args :IpluginInputArgs) => {
+  const valuesToRemove = String(args.inputs.valuesToRemove).toLowerCase().trim();
+  args.variables.ffmpegCommand.streams.forEach((stream) => {
+    if (stream.codec_type !== 'subtitle') {
+      return;
+    }
+    let title = '';
+    if (stream.tags !== undefined) {
+      if (stream.tags.title !== undefined) {
+        title = stream.tags.title.toLowerCase();
+      }
+    }
+    if (title.includes(valuesToRemove)) {
+      // eslint-disable-next-line no-param-reassign
+      stream.removed = true;
+    }
+  });
+};
+
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+const plugin = (args:IpluginInputArgs):IpluginOutputArgs => {
+  const lib = require('../../../../../methods/lib')();
+  // eslint-disable-next-line @typescript-eslint/no-unused-vars,no-param-reassign
+  args.inputs = lib.loadDefaultValues(args.inputs, details);
+
+  removeByTitle(args);
+
+  return {
+    outputFileObj: args.inputFileObj,
+    outputNumber: 1,
+    variables: args.variables,
+  };
+};
+export {
+  details,
+  plugin,
+};