diff --git a/LanguageCodes.json b/LanguageCodes.json new file mode 100644 index 0000000..635a236 --- /dev/null +++ b/LanguageCodes.json @@ -0,0 +1,942 @@ +{ + "aa": { + "639-1": "aa", + "639-2": "aar", + "name": "Afar" + }, + "ab": { + "639-1": "ab", + "639-2": "abk", + "name": "Abkhaz" + }, + "ae": { + "639-1": "ae", + "639-2": "ave", + "name": "Avestan" + }, + "af": { + "639-1": "af", + "639-2": "afr", + "name": "Afrikaans" + }, + "ak": { + "639-1": "ak", + "639-2": "aka", + "name": "Akan" + }, + "am": { + "639-1": "am", + "639-2": "amh", + "name": "Amharic" + }, + "an": { + "639-1": "an", + "639-2": "arg", + "name": "Aragonese" + }, + "ar": { + "639-1": "ar", + "639-2": "ara", + "name": "Arabic" + }, + "as": { + "639-1": "as", + "639-2": "asm", + "name": "Assamese" + }, + "av": { + "639-1": "av", + "639-2": "ava", + "name": "Avaric" + }, + "ay": { + "639-1": "ay", + "639-2": "aym", + "name": "Aymara" + }, + "az": { + "639-1": "az", + "639-2": "aze", + "name": "Azerbaijani" + }, + "ba": { + "639-1": "ba", + "639-2": "bak", + "name": "Bashkir" + }, + "be": { + "639-1": "be", + "639-2": "bel", + "name": "Belarusian" + }, + "bg": { + "639-1": "bg", + "639-2": "bul", + "name": "Bulgarian" + }, + "bh": { + "639-1": "bh", + "639-2": "bih", + "name": "Bihari" + }, + "bi": { + "639-1": "bi", + "639-2": "bis", + "name": "Bislama" + }, + "bm": { + "639-1": "bm", + "639-2": "bam", + "name": "Bambara" + }, + "bn": { + "639-1": "bn", + "639-2": "ben", + "name": "Bengali, Bangla" + }, + "bo": { + "639-1": "bo", + "639-2": "bod", + "639-2/B": "tib", + "name": "Tibetan Standard, Tibetan, Central" + }, + "br": { + "639-1": "br", + "639-2": "bre", + "name": "Breton" + }, + "bs": { + "639-1": "bs", + "639-2": "bos", + "name": "Bosnian" + }, + "ca": { + "639-1": "ca", + "639-2": "cat", + "name": "Catalan" + }, + "ce": { + "639-1": "ce", + "639-2": "che", + "name": "Chechen" + }, + "ch": { + "639-1": "ch", + "639-2": "cha", + "name": "Chamorro" + }, + "co": { + "639-1": "co", + "639-2": "cos", + "name": "Corsican" + }, + "cr": { + "639-1": "cr", + "639-2": "cre", + "name": "Cree" + }, + "cs": { + "639-1": "cs", + "639-2": "ces", + "639-2/B": "cze", + "name": "Czech" + }, + "cu": { + "639-1": "cu", + "639-2": "chu", + "name": "Old Church Slavonic, Church Slavonic, Old Bulgarian" + }, + "cv": { + "639-1": "cv", + "639-2": "chv", + "name": "Chuvash" + }, + "cy": { + "639-1": "cy", + "639-2": "cym", + "639-2/B": "wel", + "name": "Welsh" + }, + "da": { + "639-1": "da", + "639-2": "dan", + "name": "Danish" + }, + "de": { + "639-1": "de", + "639-2": "deu", + "639-2/B": "ger", + "name": "German" + }, + "dv": { + "639-1": "dv", + "639-2": "div", + "name": "Divehi, Dhivehi, Maldivian" + }, + "dz": { + "639-1": "dz", + "639-2": "dzo", + "name": "Dzongkha" + }, + "ee": { + "639-1": "ee", + "639-2": "ewe", + "name": "Ewe" + }, + "el": { + "639-1": "el", + "639-2": "ell", + "639-2/B": "gre", + "name": "Greek (modern)" + }, + "en": { + "639-1": "en", + "639-2": "eng", + "name": "English" + }, + "eo": { + "639-1": "eo", + "639-2": "epo", + "name": "Esperanto" + }, + "es": { + "639-1": "es", + "639-2": "spa", + "name": "Spanish" + }, + "et": { + "639-1": "et", + "639-2": "est", + "name": "Estonian" + }, + "eu": { + "639-1": "eu", + "639-2": "eus", + "639-2/B": "baq", + "name": "Basque" + }, + "fa": { + "639-1": "fa", + "639-2": "fas", + "639-2/B": "per", + "name": "Persian (Farsi)" + }, + "ff": { + "639-1": "ff", + "639-2": "ful", + "name": "Fula, Fulah, Pulaar, Pular" + }, + "fi": { + "639-1": "fi", + "639-2": "fin", + "name": "Finnish" + }, + "fj": { + "639-1": "fj", + "639-2": "fij", + "name": "Fijian" + }, + "fo": { + "639-1": "fo", + "639-2": "fao", + "name": "Faroese" + }, + "fr": { + "639-1": "fr", + "639-2": "fra", + "639-2/B": "fre", + "name": "French" + }, + "fy": { + "639-1": "fy", + "639-2": "fry", + "name": "Western Frisian" + }, + "ga": { + "639-1": "ga", + "639-2": "gle", + "name": "Irish" + }, + "gd": { + "639-1": "gd", + "639-2": "gla", + "name": "Scottish Gaelic, Gaelic" + }, + "gl": { + "639-1": "gl", + "639-2": "glg", + "name": "Galician" + }, + "gn": { + "639-1": "gn", + "639-2": "grn", + "name": "Guaraní" + }, + "gu": { + "639-1": "gu", + "639-2": "guj", + "name": "Gujarati" + }, + "gv": { + "639-1": "gv", + "639-2": "glv", + "name": "Manx" + }, + "ha": { + "639-1": "ha", + "639-2": "hau", + "name": "Hausa" + }, + "he": { + "639-1": "he", + "639-2": "heb", + "name": "Hebrew (modern)" + }, + "hi": { + "639-1": "hi", + "639-2": "hin", + "name": "Hindi" + }, + "ho": { + "639-1": "ho", + "639-2": "hmo", + "name": "Hiri Motu" + }, + "hr": { + "639-1": "hr", + "639-2": "hrv", + "name": "Croatian" + }, + "ht": { + "639-1": "ht", + "639-2": "hat", + "name": "Haitian, Haitian Creole" + }, + "hu": { + "639-1": "hu", + "639-2": "hun", + "name": "Hungarian" + }, + "hy": { + "639-1": "hy", + "639-2": "hye", + "639-2/B": "arm", + "name": "Armenian" + }, + "hz": { + "639-1": "hz", + "639-2": "her", + "name": "Herero" + }, + "ia": { + "639-1": "ia", + "639-2": "ina", + "name": "Interlingua" + }, + "id": { + "639-1": "id", + "639-2": "ind", + "name": "Indonesian" + }, + "ie": { + "639-1": "ie", + "639-2": "ile", + "name": "Interlingue" + }, + "ig": { + "639-1": "ig", + "639-2": "ibo", + "name": "Igbo" + }, + "ii": { + "639-1": "ii", + "639-2": "iii", + "name": "Nuosu" + }, + "ik": { + "639-1": "ik", + "639-2": "ipk", + "name": "Inupiaq" + }, + "io": { + "639-1": "io", + "639-2": "ido", + "name": "Ido" + }, + "is": { + "639-1": "is", + "639-2": "isl", + "639-2/B": "ice", + "name": "Icelandic" + }, + "it": { + "639-1": "it", + "639-2": "ita", + "name": "Italian" + }, + "iu": { + "639-1": "iu", + "639-2": "iku", + "name": "Inuktitut" + }, + "ja": { + "639-1": "ja", + "639-2": "jpn", + "name": "Japanese" + }, + "jv": { + "639-1": "jv", + "639-2": "jav", + "name": "Javanese" + }, + "ka": { + "639-1": "ka", + "639-2": "kat", + "639-2/B": "geo", + "name": "Georgian" + }, + "kg": { + "639-1": "kg", + "639-2": "kon", + "name": "Kongo" + }, + "ki": { + "639-1": "ki", + "639-2": "kik", + "name": "Kikuyu, Gikuyu" + }, + "kj": { + "639-1": "kj", + "639-2": "kua", + "name": "Kwanyama, Kuanyama" + }, + "kk": { + "639-1": "kk", + "639-2": "kaz", + "name": "Kazakh" + }, + "kl": { + "639-1": "kl", + "639-2": "kal", + "name": "Kalaallisut, Greenlandic" + }, + "km": { + "639-1": "km", + "639-2": "khm", + "name": "Khmer" + }, + "kn": { + "639-1": "kn", + "639-2": "kan", + "name": "Kannada" + }, + "ko": { + "639-1": "ko", + "639-2": "kor", + "name": "Korean" + }, + "kr": { + "639-1": "kr", + "639-2": "kau", + "name": "Kanuri" + }, + "ks": { + "639-1": "ks", + "639-2": "kas", + "name": "Kashmiri" + }, + "ku": { + "639-1": "ku", + "639-2": "kur", + "name": "Kurdish" + }, + "kv": { + "639-1": "kv", + "639-2": "kom", + "name": "Komi" + }, + "kw": { + "639-1": "kw", + "639-2": "cor", + "name": "Cornish" + }, + "ky": { + "639-1": "ky", + "639-2": "kir", + "name": "Kyrgyz" + }, + "la": { + "639-1": "la", + "639-2": "lat", + "name": "Latin" + }, + "lb": { + "639-1": "lb", + "639-2": "ltz", + "name": "Luxembourgish, Letzeburgesch" + }, + "lg": { + "639-1": "lg", + "639-2": "lug", + "name": "Ganda" + }, + "li": { + "639-1": "li", + "639-2": "lim", + "name": "Limburgish, Limburgan, Limburger" + }, + "ln": { + "639-1": "ln", + "639-2": "lin", + "name": "Lingala" + }, + "lo": { + "639-1": "lo", + "639-2": "lao", + "name": "Lao" + }, + "lt": { + "639-1": "lt", + "639-2": "lit", + "name": "Lithuanian" + }, + "lu": { + "639-1": "lu", + "639-2": "lub", + "name": "Luba-Katanga" + }, + "lv": { + "639-1": "lv", + "639-2": "lav", + "name": "Latvian" + }, + "mg": { + "639-1": "mg", + "639-2": "mlg", + "name": "Malagasy" + }, + "mh": { + "639-1": "mh", + "639-2": "mah", + "name": "Marshallese" + }, + "mi": { + "639-1": "mi", + "639-2": "mri", + "639-2/B": "mao", + "name": "Māori" + }, + "mk": { + "639-1": "mk", + "639-2": "mkd", + "639-2/B": "mac", + "name": "Macedonian" + }, + "ml": { + "639-1": "ml", + "639-2": "mal", + "name": "Malayalam" + }, + "mn": { + "639-1": "mn", + "639-2": "mon", + "name": "Mongolian" + }, + "mr": { + "639-1": "mr", + "639-2": "mar", + "name": "Marathi (Marāṭhī)" + }, + "ms": { + "639-1": "ms", + "639-2": "msa", + "639-2/B": "may", + "name": "Malay" + }, + "mt": { + "639-1": "mt", + "639-2": "mlt", + "name": "Maltese" + }, + "my": { + "639-1": "my", + "639-2": "mya", + "639-2/B": "bur", + "name": "Burmese" + }, + "na": { + "639-1": "na", + "639-2": "nau", + "name": "Nauruan" + }, + "nb": { + "639-1": "nb", + "639-2": "nob", + "name": "Norwegian Bokmål" + }, + "nd": { + "639-1": "nd", + "639-2": "nde", + "name": "Northern Ndebele" + }, + "ne": { + "639-1": "ne", + "639-2": "nep", + "name": "Nepali" + }, + "ng": { + "639-1": "ng", + "639-2": "ndo", + "name": "Ndonga" + }, + "nl": { + "639-1": "nl", + "639-2": "nld", + "639-2/B": "dut", + "name": "Dutch" + }, + "nn": { + "639-1": "nn", + "639-2": "nno", + "name": "Norwegian Nynorsk" + }, + "no": { + "639-1": "no", + "639-2": "nor", + "name": "Norwegian" + }, + "nr": { + "639-1": "nr", + "639-2": "nbl", + "name": "Southern Ndebele" + }, + "nv": { + "639-1": "nv", + "639-2": "nav", + "name": "Navajo, Navaho" + }, + "ny": { + "639-1": "ny", + "639-2": "nya", + "name": "Chichewa, Chewa, Nyanja" + }, + "oc": { + "639-1": "oc", + "639-2": "oci", + "name": "Occitan" + }, + "oj": { + "639-1": "oj", + "639-2": "oji", + "name": "Ojibwe, Ojibwa" + }, + "om": { + "639-1": "om", + "639-2": "orm", + "name": "Oromo" + }, + "or": { + "639-1": "or", + "639-2": "ori", + "name": "Oriya" + }, + "os": { + "639-1": "os", + "639-2": "oss", + "name": "Ossetian, Ossetic" + }, + "pa": { + "639-1": "pa", + "639-2": "pan", + "name": "(Eastern) Punjabi" + }, + "pi": { + "639-1": "pi", + "639-2": "pli", + "name": "Pāli" + }, + "pl": { + "639-1": "pl", + "639-2": "pol", + "name": "Polish" + }, + "ps": { + "639-1": "ps", + "639-2": "pus", + "name": "Pashto, Pushto" + }, + "pt": { + "639-1": "pt", + "639-2": "por", + "name": "Portuguese" + }, + "qu": { + "639-1": "qu", + "639-2": "que", + "name": "Quechua" + }, + "rm": { + "639-1": "rm", + "639-2": "roh", + "name": "Romansh" + }, + "rn": { + "639-1": "rn", + "639-2": "run", + "name": "Kirundi" + }, + "ro": { + "639-1": "ro", + "639-2": "ron", + "639-2/B": "rum", + "name": "Romanian" + }, + "ru": { + "639-1": "ru", + "639-2": "rus", + "name": "Russian" + }, + "rw": { + "639-1": "rw", + "639-2": "kin", + "name": "Kinyarwanda" + }, + "sa": { + "639-1": "sa", + "639-2": "san", + "name": "Sanskrit (Saṁskṛta)" + }, + "sc": { + "639-1": "sc", + "639-2": "srd", + "name": "Sardinian" + }, + "sd": { + "639-1": "sd", + "639-2": "snd", + "name": "Sindhi" + }, + "se": { + "639-1": "se", + "639-2": "sme", + "name": "Northern Sami" + }, + "sg": { + "639-1": "sg", + "639-2": "sag", + "name": "Sango" + }, + "si": { + "639-1": "si", + "639-2": "sin", + "name": "Sinhalese, Sinhala" + }, + "sk": { + "639-1": "sk", + "639-2": "slk", + "639-2/B": "slo", + "name": "Slovak" + }, + "sl": { + "639-1": "sl", + "639-2": "slv", + "name": "Slovene" + }, + "sm": { + "639-1": "sm", + "639-2": "smo", + "name": "Samoan" + }, + "sn": { + "639-1": "sn", + "639-2": "sna", + "name": "Shona" + }, + "so": { + "639-1": "so", + "639-2": "som", + "name": "Somali" + }, + "sq": { + "639-1": "sq", + "639-2": "sqi", + "639-2/B": "alb", + "name": "Albanian" + }, + "sr": { + "639-1": "sr", + "639-2": "srp", + "name": "Serbian" + }, + "ss": { + "639-1": "ss", + "639-2": "ssw", + "name": "Swati" + }, + "st": { + "639-1": "st", + "639-2": "sot", + "name": "Southern Sotho" + }, + "su": { + "639-1": "su", + "639-2": "sun", + "name": "Sundanese" + }, + "sv": { + "639-1": "sv", + "639-2": "swe", + "name": "Swedish" + }, + "sw": { + "639-1": "sw", + "639-2": "swa", + "name": "Swahili" + }, + "ta": { + "639-1": "ta", + "639-2": "tam", + "name": "Tamil" + }, + "te": { + "639-1": "te", + "639-2": "tel", + "name": "Telugu" + }, + "tg": { + "639-1": "tg", + "639-2": "tgk", + "name": "Tajik" + }, + "th": { + "639-1": "th", + "639-2": "tha", + "name": "Thai" + }, + "ti": { + "639-1": "ti", + "639-2": "tir", + "name": "Tigrinya" + }, + "tk": { + "639-1": "tk", + "639-2": "tuk", + "name": "Turkmen" + }, + "tl": { + "639-1": "tl", + "639-2": "tgl", + "name": "Tagalog" + }, + "tn": { + "639-1": "tn", + "639-2": "tsn", + "name": "Tswana" + }, + "to": { + "639-1": "to", + "639-2": "ton", + "name": "Tonga (Tonga Islands)" + }, + "tr": { + "639-1": "tr", + "639-2": "tur", + "name": "Turkish" + }, + "ts": { + "639-1": "ts", + "639-2": "tso", + "name": "Tsonga" + }, + "tt": { + "639-1": "tt", + "639-2": "tat", + "name": "Tatar" + }, + "tw": { + "639-1": "tw", + "639-2": "twi", + "name": "Twi" + }, + "ty": { + "639-1": "ty", + "639-2": "tah", + "name": "Tahitian" + }, + "ug": { + "639-1": "ug", + "639-2": "uig", + "name": "Uyghur" + }, + "uk": { + "639-1": "uk", + "639-2": "ukr", + "name": "Ukrainian" + }, + "ur": { + "639-1": "ur", + "639-2": "urd", + "name": "Urdu" + }, + "uz": { + "639-1": "uz", + "639-2": "uzb", + "name": "Uzbek" + }, + "ve": { + "639-1": "ve", + "639-2": "ven", + "name": "Venda" + }, + "vi": { + "639-1": "vi", + "639-2": "vie", + "name": "Vietnamese" + }, + "vo": { + "639-1": "vo", + "639-2": "vol", + "name": "Volapük" + }, + "wa": { + "639-1": "wa", + "639-2": "wln", + "name": "Walloon" + }, + "wo": { + "639-1": "wo", + "639-2": "wol", + "name": "Wolof" + }, + "xh": { + "639-1": "xh", + "639-2": "xho", + "name": "Xhosa" + }, + "yi": { + "639-1": "yi", + "639-2": "yid", + "name": "Yiddish" + }, + "yo": { + "639-1": "yo", + "639-2": "yor", + "name": "Yoruba" + }, + "za": { + "639-1": "za", + "639-2": "zha", + "name": "Zhuang, Chuang" + }, + "zh": { + "639-1": "zh", + "639-2": "zho", + "639-2/B": "chi", + "name": "Chinese" + }, + "zu": { + "639-1": "zu", + "639-2": "zul", + "name": "Zulu" + } +} \ No newline at end of file diff --git a/README.md b/README.md index a22da9a..51ad443 100644 --- a/README.md +++ b/README.md @@ -8,26 +8,20 @@ Only 1 instance of the script will be running and if other downloads complete du Script will do the following: 1. Unpack or copy to Temporary Processing folder defined in config.json (copy from config-sample.json) -2. If torrent label is TV show as defined in JSON Label - - Start [Subliminal](https://github.com/Diaoul/subliminal) to see if there are missing subtitles we can download - - Will strip all srt subtitle languages not in JSON WantedLanguages - - Extract all SRT subtitles in JSON WantedLanguages - - Rename all srt files from alpha3 to alpha2 codes as defined in JSON LanguageCodes - - File.3.en.srt - - File.en.srt - - File.nl.srt +2. If torrent label is TV show as defined in config.json Label + - Will strip all srt subtitle languages not in config.json WantedLanguages from MKV + - Extract all SRT subtitles in config.json WantedLanguages from MKV + - Will try to download missing subs from OpenSubtitles.com + - Renames all srt files from ISO 639-2 (3 letter codes) to ISO 639-1 (2 letter codes) as defined in LanguageCodes.json - Clean up the srt subtitles using [Subtitle Edit](https://github.com/SubtitleEdit/subtitleedit) - Remove Hearing Impaired. - Fix Common errors. - Start Medusa Import -3. If torrent label is Movie as defined in JSON Label - - Start [Subliminal](https://github.com/Diaoul/subliminal) to see if there are missing subtitles we can download - - Will strip all srt subtitle languages not in JSON WantedLanguages - - Extract all SRT subtitles in JSON WantedLanguages - - Rename all srt files from alpha3 to alpha2 codes as defined in JSON LanguageCodes - - File.3.en.srt - - File.en.srt - - File.nl.srt +3. If torrent label is Movie as defined in config.json Label + - Will strip all srt subtitle languages not in config.json WantedLanguages from MKV + - Extract all SRT subtitles in config.json WantedLanguages from MKV + - Will try to download missing subs from OpenSubtitles.com + - Renames all srt files from ISO 639-2 (3 letter codes) to ISO 639-1 (2 letter codes) as defined in LanguageCodes.json - Clean up the srt subtitles using [Subtitle Edit](https://github.com/SubtitleEdit/subtitleedit) - Remove Hearing Impaired. - Fix Common errors. @@ -55,7 +49,7 @@ powershell "C:\Scripts\TorrentScript\TorrentScript.ps1" -DownloadPath '%R' -Down Other important setting is that for each torrent a new Folder needs to be created. ![qBittorrent Folder settings page](https://i.imgur.com/Uq6bOBP.png) -Within Radarr you need to set up Remote Path Mapping to an emtpy folder, otherwise Radarr will pick up to downloads directly from qbittorent +Within Radarr you need to set up Remote Path Mapping to an empty folder, otherwise Radarr will pick up to downloads directly from qbittorent Example: - qBittorent root download path = C:\Torrent\Downloads\ @@ -69,5 +63,4 @@ The following external tools need to be available and the path defined in the `c - [WinRar](https://www.rarlab.com/download.htm) - [MKVMerge](https://mkvtoolnix.download/) - [MKVExtract](https://mkvtoolnix.download/) - - [Subtitle Edit](https://github.com/SubtitleEdit/subtitleedit) - - [Subliminal](https://github.com/Diaoul/subliminal) \ No newline at end of file + - [Subtitle Edit](https://github.com/SubtitleEdit/subtitleedit) \ No newline at end of file diff --git a/TorrentScript.ps1 b/TorrentScript.ps1 index 879f93a..8b13074 100644 --- a/TorrentScript.ps1 +++ b/TorrentScript.ps1 @@ -1,21 +1,26 @@ <# .SYNOPSIS -This script performs various tasks related to torrent downloads, including unraring, post-processing for movies and TV shows and any other QBittorrent download, and sending notifications. - + This script performs various tasks related to torrent downloads, + including unraring, post-processing for movies and TV shows + and any other QBittorrent download, and sending notifications. .DESCRIPTION -This script is designed to handle the post-processing of torrent downloads. It supports unraring, renaming, and organizing files based on certain criteria. Additionally, it can communicate with Medusa and Radarr for further post-processing. - + This script is designed to handle the post-processing of torrent downloads. + It supports unraring, renaming, and organizing files based on certain criteria. + Additionally, it can communicate with Medusa and Radarr for further post-processing. .PARAMETER DownloadPath -Specifies the path of the downloaded files. - + Specifies the path of the downloaded torrent. If not provided, the script prompts for input. .PARAMETER DownloadLabel -Specifies the label associated with the downloaded files. - + Specifies the label for the downloaded torrent. If not provided, the script prompts for input. .PARAMETER TorrentHash -Specifies the torrent hash, required for Radarr. - + Specifies the torrent hash, required for Radarr. .PARAMETER NoCleanUp -Indicates whether to skip the cleanup process in the Temp Process folder. + Indicates whether to skip the cleanup process in the Temp Process folder. +.EXAMPLE + TorrentScript.ps1 -DownloadPath "C:\Downloads\MyTorrent" -DownloadLabel "TV" -TorrentHash "1234567890" + Processes a TV show torrent with the specified path, label, and torrent hash. +.EXAMPLE + TorrentScript.ps1 + Prompts for user input for download path and label to process a torrent. #> [CmdletBinding()] @@ -45,33 +50,43 @@ ForEach ($import in @($Functions)) { } } -Write-Host 'Loading Powershell Modules, this might take a while' -ForegroundColor DarkYellow -# Load required modules -$modules = @("WriteAscii", "Send-MailKitMessage") -foreach ($module in $modules) { - Load-Module -ModuleName $module -} -Write-Host 'All checks done' -ForegroundColor DarkYellow - -#* Start of script -Clear-Host -$ScriptTitle = "Torrent Script" +# User Variables try { - Write-Ascii $ScriptTitle -ForegroundColor DarkYellow + $configPath = Join-Path $PSScriptRoot 'config.json' + $Config = Get-Content $configPath -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop } catch { - Write-Host $ScriptTitle -ForegroundColor DarkYellow + Write-Host 'Exception:' $_.Exception.Message -ForegroundColor Red + Write-Host 'Invalid config.json file' -ForegroundColor Red + exit 1 } -# User Variables +# Reading Language Code lookup try { - $configPath = Join-Path $PSScriptRoot 'config.json' - $Config = Get-Content $configPath -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop + $LanguageCodesPath = Join-Path $PSScriptRoot 'LanguageCodes.json' + $LanguageCodes = Get-Content $LanguageCodesPath -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop + # Create a hashtable for language code lookup + $LanguageCodeLookup = @{} + foreach ($langCodeKey in $LanguageCodes.PSObject.Properties.Name) { + $langCode = $LanguageCodes.$langCodeKey + $639_2 = $langCode.'639-2' + $639_2_B = $langCode.'639-2/B' + $639_1 = $langCode.'639-1' + + if ($639_2) { + $LanguageCodeLookup[$639_2] = $639_1 + } + + if ($639_2_B) { + $LanguageCodeLookup[$639_2_B] = $639_1 + } + } } catch { Write-Host 'Exception:' $_.Exception.Message -ForegroundColor Red - Write-Host 'Invalid config.json file' -ForegroundColor Red + Write-Host 'Invalid LanguageCodes.json' -ForegroundColor Red exit 1 } + # Log Date format $LogFileDateFormat = Get-Date -Format $Config.DateFormat @@ -94,8 +109,6 @@ $WinRarPath = $Config.Tools.WinRarPath $MKVMergePath = $Config.Tools.MKVMergePath $MKVExtractPath = $Config.Tools.MKVExtractPath $SubtitleEditPath = $Config.Tools.SubtitleEditPath -$SubliminalPath = $Config.Tools.SubliminalPath -$MailSendPath = $Config.Tools.MailSendPath # Import qBittorrent Settings $qBittorrentHost = $Config.qBittorrent.Host @@ -103,7 +116,6 @@ $qBittorrentPort = $Config.qBittorrent.Port $qBittorrentUser = $Config.qBittorrent.User $qBittorrentPassword = $Config.qBittorrent.Password - # Import Medusa Settings $MedusaHost = $Config.Medusa.Host $MedusaPort = $Config.Medusa.Port @@ -127,21 +139,43 @@ $SMTPport = $Config.Mail.SMTPport $SMTPuser = $Config.Mail.SMTPuser $SMTPpass = $Config.Mail.SMTPpass -# OpenSubtitle User and Pass for Subliminal +# OpenSubtitle User and Pass for OpenSubtitle.com $OpenSubUser = $Config.OpenSub.User $OpenSubPass = $Config.OpenSub.Password -$omdbAPI = $Config.OpenSub.omdbAPI +$OpenSubAPI = $Config.OpenSub.API +$OpenSubHearing_impaired = $Config.OpenSub.hearing_impaired +$OpenSubForeign_parts_only = $Config.OpenSub.foreign_parts_only +$OpenSubMachine_translated = $Config.OpenSub.machine_translated +$OpenSubAI_translated = $Config.OpenSub.ai_translated # Language codes of subtitles to keep $WantedLanguages = $Config.WantedLanguages $SubtitleNamesToRemove = $Config.SubtitleNamesToRemove # Test additional programs -$Tools = @($WinRarPath, $MKVMergePath, $MKVExtractPath, $SubtitleEditPath, $SubliminalPath, $MailSendPath) +$Tools = @($WinRarPath, $MKVMergePath, $MKVExtractPath, $SubtitleEditPath) foreach ($Tool in $Tools) { Test-Variable-Path -Path $Tool } +Write-Host 'Loading Powershell Modules, this might take a while' -ForegroundColor DarkYellow +# Load required modules +$modules = @("WriteAscii", "Send-MailKitMessage") +foreach ($module in $modules) { + Install-PSModule -ModuleName $module +} +Write-Host 'All checks done' -ForegroundColor DarkYellow + +#* Start of script +Clear-Host +$ScriptTitle = "Torrent Script" + +try { + Write-Ascii $ScriptTitle -ForegroundColor DarkYellow +} catch { + Write-Host $ScriptTitle -ForegroundColor DarkYellow +} + #* Get input if no parameters defined # Build the download Location, this is the Download Root Path added with the Download name if (-not $PSBoundParameters.ContainsKey('DownloadPath')) { @@ -156,11 +190,10 @@ $DownloadName = Split-Path -Path $DownloadPath -Leaf # Download Label if (-not $PSBoundParameters.ContainsKey('DownloadLabel')) { # Handle no Download Path given as parameter - $QBcategories = Get-QBittorrentCategories -qBittorrentUrl $($qBittorrentHost + ":" + $qBittorrentPort) -username $qBittorrentUser -password $qBittorrentPassword + $QBcategories = Get-QBittorrentCategories -qBittorrentUrl $qBittorrentHost -qBittorrentPort $qBittorrentPort -username $qBittorrentUser -password $qBittorrentPassword if ($QBcategories) { - $Categories = $($QBcategories.PSObject.Properties.Value.name) - $Categories += "[Enter your own]" - $DownloadLabel = Select-MenuOption -MenuOptions $Categories -MenuQuestion "Torrent Label" + $QBcategories += "[Enter your own]" + $DownloadLabel = Select-MenuOption -MenuOptions $QBcategories -MenuQuestion "Torrent Label" if ($DownloadLabel -eq "[Enter your own]") { $DownloadLabel = Get-Input -Message 'Download Label' } @@ -212,7 +245,7 @@ If (!(Test-Path -LiteralPath $LogArchivePath)) { $ScriptMutex = New-Mutex -MutexName 'DownloadScript' # Start Stopwatch -$StopWatch = [system.diagnostics.stopwatch]::startNew() +$ScriptTimer = [system.diagnostics.stopwatch]::startNew() # Check paths from Parameters If (!(Test-Path -LiteralPath $DownloadPath)) { @@ -256,7 +289,13 @@ if ($RarFile) { $TotalSize = (Get-ChildItem -LiteralPath $DownloadPath -Recurse | Measure-Object -Property Length -Sum).Sum $UnRarStopWatch = [system.diagnostics.stopwatch]::startNew() foreach ($Rar in $RarFilePaths) { - Start-UnRar -UnRarSourcePath $Rar -UnRarTargetPath $ProcessPathFull + $UnrarParams = @{ + UnRarSourcePath = $Rar + UnRarTargetPath = $ProcessPathFull + DownloadLabel = $DownloadLabel + DownloadName = $DownloadName + } + Start-UnRar @UnrarParams } # Stop the Stopwatch $UnRarStopWatch.Stop() @@ -264,10 +303,25 @@ if ($RarFile) { Write-HTMLLog -Column1 'Throughput:' -Column2 "$(Format-Size -SizeInBytes ($TotalSize/$UnRarStopWatch.Elapsed.TotalSeconds))/s" } elseif (-not $RarFile -and $SingleFile) { Write-HTMLLog -Column1 '*** Single File ***' -Header - Start-RoboCopy -Source $DownloadRootPath -Destination $ProcessPathFull -File $DownloadName + $FileCopyParams = @{ + source = $DownloadRootPath + Destination = $ProcessPathFull + File = $DownloadName + DownloadLabel = $DownloadLabel + DownloadName = $DownloadName + } + Start-FileCopy @FileCopyParams + } elseif (-not $RarFile -and $Folder) { Write-HTMLLog -Column1 '*** Folder ***' -Header - Start-RoboCopy -Source $DownloadPath -Destination $ProcessPathFull -File '*.*' + $FileCopyParams = @{ + source = $DownloadPath + Destination = $ProcessPathFull + File = '*.*' + DownloadLabel = $DownloadLabel + DownloadName = $DownloadName + } + Start-FileCopy @FileCopyParams } @@ -275,7 +329,7 @@ if ($RarFile) { if ($DownloadLabel -eq $TVLabel -or $DownloadLabel -eq $MovieLabel) { $mp4Files = @(Get-ChildItem -LiteralPath $ProcessPathFull -Recurse -Filter '*.mp4' | Where-Object { $_.DirectoryName -notlike "*\Sample" }) if ($mp4Files.Count -gt 0) { - Start-MP4ToMKVRemux -VideoFileObjects $mp4Files + Start-MP4ToMKVRemux -Source $ProcessPathFull -MKVMergePath $MKVMergePath } $mkvFiles = @(Get-ChildItem -LiteralPath $ProcessPathFull -Recurse -Filter '*.mkv' | Where-Object { $_.DirectoryName -notlike "*\Sample" }) @@ -284,26 +338,61 @@ if ($DownloadLabel -eq $TVLabel -or $DownloadLabel -eq $MovieLabel) { } else { $VideoContainer = $false } + if ($VideoContainer) { - # Download any missing subs - # Remove Subliminal for now as the OpenSubtitles API is closed - # Start-Subliminal -Source $ProcessPathFull - # Remove unwanted subtitle languages and extract wanted subtitles and rename - Start-MKVSubtitleStrip $ProcessPathFull - + $MKVToolnixImportParams = @{ + Source = $ProcessPathFull + MKVMergePath = $MKVMergePath + MKVExtractPath = $MKVExtractPath + WantedLanguages = $WantedLanguages + SubtitleNamesToRemove = $SubtitleNamesToRemove + LanguageCodeLookup = $LanguageCodeLookup + } + Start-MKVSubtitleStrip @MKVToolnixImportParams + + # Download missing subtitles using OpenSubtitles.com API + $OpenSubLanguages = @() + foreach ($language in $WantedLanguages) { + $OpenSubLanguages += $LanguageCodeLookup[$language] + } + $OpenSubParams = @{ + Source = $ProcessPathFull + OpenSubUser = $OpenSubUser + OpenSubPass = $OpenSubPass + OpenSubAPI = $OpenSubAPI + OpenSubHearing_impaired = $OpenSubHearing_impaired + OpenSubForeign_parts_only = $OpenSubForeign_parts_only + OpenSubMachine_translated = $OpenSubMachine_translated + OpenSubAI_translated = $OpenSubAI_translated + WantedLanguages = $OpenSubLanguages + } + if ($DownloadLabel -eq $TVLabel) { + $OpenSubParams.Add("Type", "episode") + } elseif ($DownloadLabel -eq $MovieLabel) { + $OpenSubParams.Add("Type", "movie") + } + Start-OpenSubtitlesDownload @OpenSubParams + # Clean up Subs - Start-SubEdit -File '*.srt' -Source $ProcessPathFull + $SubtitleEditParams = @{ + Source = $ProcessPathFull + Files = '*.srt' + SubtitleEditPath = $SubtitleEditPath + DownloadLabel = $DownloadLabel + DownloadName = $DownloadName + } + Start-SubtitleEdit @SubtitleEditParams Write-HTMLLog -Column1 '*** MKV Files ***' -Header foreach ($Mkv in $mkvFiles) { - Write-HTMLLog -Column1 ' ' -Column2 $Mkv.name + Write-HTMLLog -Column2 $Mkv.name } $SrtFiles = Get-ChildItem -LiteralPath $ProcessPathFull -Recurse -Filter '*.srt' if ($SrtFiles.Count -gt 0) { Write-HTMLLog -Column1 '*** Subtitle Files ***' -Header foreach ($Srt in $SrtFiles) { - Write-HTMLLog -Column1 ' ' -Column2 $srt.name + Write-HTMLLog -Column2 $srt.name } } } else { @@ -321,10 +410,19 @@ if ($DownloadLabel -eq $TVLabel -or $DownloadLabel -eq $MovieLabel) { # Get the correct remote Medusa file path, if script is not running on local machine to Medusa # Remove the common prefix and append the MedusaRemotePath $MedusaPathFull = Join-Path $MedusaRemotePath ($ProcessPathFull.Substring($prefixLength)) + # Call Medusa to Post Process - Import-Medusa -Source $MedusaPathFull - CleanProcessPath -Path $ProcessPathFull -NoCleanUp $NoCleanUp - Stop-Script -ExitReason "$DownloadLabel - $DownloadName" + $MedusaImportParams = @{ + Source = $MedusaPathFull + MedusaApiKey = $MedusaApiKey + MedusaHost = $MedusaHost + MedusaPort = $MedusaPort + MedusaTimeOutMinutes = $MedusaTimeOutMinutes + DownloadLabel = $DownloadLabel + DownloadName = $DownloadName + } + Import-Medusa @MedusaImportParams + } elseif ($DownloadLabel -eq $MovieLabel) { # Get the correct remote Radarr file path, if script is not running on local machine to Radarr # Get the common prefix length between the paths @@ -333,11 +431,36 @@ if ($DownloadLabel -eq $TVLabel -or $DownloadLabel -eq $MovieLabel) { $RadarrPathFull = Join-Path $RadarrRemotePath ($ProcessPathFull.Substring($prefixLength)) # Call Radarr to Post Process - Import-Radarr -Source $RadarrPathFull - CleanProcessPath -Path $ProcessPathFull -NoCleanUp $NoCleanUp - Stop-Script -ExitReason "$DownloadLabel - $DownloadName" + $RadarrImportParams = @{ + Source = $RadarrPathFull + RadarrApiKey = $RadarrApiKey + RadarrHost = $RadarrHost + RadarrPort = $RadarrPort + RadarrTimeOutMinutes = $RadarrTimeOutMinutes + TorrentHash = $TorrentHash + DownloadLabel = $DownloadLabel + DownloadName = $DownloadName + } + Import-Radarr @RadarrImportParams } + # Cleanup the Process Path folders + if ($NoCleanUp) { + Write-HTMLLog -Column1 'Cleanup' -Column2 'NoCleanUp switch was given at command line, leaving files:' + Write-HTMLLog -Column2 "$ProcessPathFull" + } else { + try { + If (Test-Path -LiteralPath $ProcessPathFull) { + Remove-Item -Force -Recurse -LiteralPath $ProcessPathFull + } + } catch { + Write-HTMLLog -Column1 'Exception:' -Column2 $_.Exception.Message -ColorBg 'Error' + Write-HTMLLog -Column1 'Result:' -Column2 'Failed' -ColorBg 'Error' + } + } + + # Ending Script for Movie and TV downloads + Stop-Script -ExitReason "$DownloadLabel - $DownloadName" } # Reached the end of script Write-HTMLLog -Column1 '*** Post Process General Download ***' -Header @@ -345,4 +468,4 @@ $Files = Get-ChildItem -LiteralPath $ProcessPathFull -Recurse -Filter '*.*' foreach ($File in $Files) { Write-HTMLLog -Column1 'File:' -Column2 $File.name } -Stop-Script -ExitReason "$DownloadLabel - $DownloadName" +Stop-Script -ExitReason "$DownloadLabel - $DownloadName" \ No newline at end of file diff --git a/config-sample.json b/config-sample.json index cd25f52..d5652c6 100644 --- a/config-sample.json +++ b/config-sample.json @@ -11,8 +11,7 @@ "WinRarPath": "C:\\Program Files\\WinRAR\\rar.exe", "MKVMergePath": "C:\\Program Files\\MKVToolNix\\mkvmerge.exe", "MKVExtractPath": "C:\\Program Files\\MKVToolNix\\mkvextract.exe", - "SubtitleEditPath": "C:\\GitHub\\SubtitleEdit\\SubtitleEdit.exe", - "SubliminalPath": "C:\\GitHub\\subliminal\\Scripts\\subliminal.exe" + "SubtitleEditPath": "C:\\GitHub\\SubtitleEdit\\SubtitleEdit.exe" }, "Mail": { "To": "user@mail.com", @@ -26,7 +25,11 @@ "OpenSub": { "User": "user", "Password": "Password123", - "omdbAPI": "2028C39D" + "API": "2028C39D", + "hearing_impaired" : "exclude", + "foreign_parts_only" : "exclude", + "machine_translated" : "exclude", + "ai_translated" : "include" }, "WantedLanguages": [ "eng", @@ -35,16 +38,6 @@ "SubtitleNamesToRemove": [ "Forced" ], - "LanguageCodes": [ - { - "alpha3": "eng", - "alpha2": "en" - }, - { - "alpha3": "dut", - "alpha2": "nl" - } - ], "qBittorrent": { "Host": "http://localhost", "Port": "8080", diff --git a/functions/CleanProcessPath.ps1 b/functions/CleanProcessPath.ps1 deleted file mode 100644 index bd0dcbb..0000000 --- a/functions/CleanProcessPath.ps1 +++ /dev/null @@ -1,42 +0,0 @@ -function CleanProcessPath { - [CmdletBinding()] - param ( - [Parameter( - Mandatory = $true - )] - [string]$Path, - - [Parameter( - Mandatory = $false - )] - [bool]$NoCleanUp - ) - - # Make sure needed functions are available otherwise try to load them. - $commands = 'Write-HTMLLog' - foreach ($commandName in $commands) { - if (!($command = Get-Command $commandName -ErrorAction SilentlyContinue)) { - Try { - . $PSScriptRoot\$commandName.ps1 - Write-Host "$commandName Function loaded." -ForegroundColor Green - } Catch { - Write-Error -Message "Failed to import $commandName function: $_" - exit 1 - } - } - } - # Start - - if ($NoCleanUp) { - Write-HTMLLog -Column1 'Cleanup' -Column2 'NoCleanUp switch was given at command line, leaving files' - } else { - try { - If (Test-Path -LiteralPath $ProcessPathFull) { - Remove-Item -Force -Recurse -LiteralPath $ProcessPathFull - } - } catch { - Write-HTMLLog -Column1 'Exception:' -Column2 $_.Exception.Message -ColorBg 'Error' - Write-HTMLLog -Column1 'Result:' -Column2 'Failed' -ColorBg 'Error' - } - } -} \ No newline at end of file diff --git a/functions/Format-Size.ps1 b/functions/Format-Size.ps1 index bcae90e..4634f63 100644 --- a/functions/Format-Size.ps1 +++ b/functions/Format-Size.ps1 @@ -1,49 +1,64 @@ -function Format-Size() { - <# - .SYNOPSIS - Takes bytes and converts it to KB.MB,GB,TB,PB - - .DESCRIPTION - Takes bytes and converts it to KB.MB,GB,TB,PB - - .PARAMETER SizeInBytes - Input bytes - - .EXAMPLE - Format-Size -SizeInBytes 864132 - 843,88 KB - - Format-Size -SizeInBytes 8641320 - 8,24 MB - - .NOTES - General notes - #> +<# +.SYNOPSIS + Formats a size in bytes into a human-readable format. +.DESCRIPTION + This function takes a size in bytes as input and converts it into a human-readable format, + displaying the size in terabytes (TB), gigabytes (GB), megabytes (MB), kilobytes (KB), + or bytes based on the magnitude of the input. +.PARAMETER SizeInBytes + Specifies the size in bytes that needs to be formatted. +.INPUTS + Accepts a double-precision floating-point number representing the size in bytes. +.OUTPUTS + Returns a formatted string representing the size in TB, GB, MB, KB, or bytes. +.EXAMPLE + Format-Size -SizeInBytes 150000000000 + # Output: "139.81 GB" + # Description: Formats 150,000,000,000 bytes into gigabytes. +.EXAMPLE + 5000000 | Format-Size + # Output: "4.77 MB" + # Description: Pipes 5,000,000 bytes to the function and formats the size into megabytes. +#> + +function Format-Size { [CmdletBinding()] - param( + param ( [Parameter( Mandatory = $true, ValueFromPipeline = $true )] [double]$SizeInBytes ) - switch ([math]::Max($SizeInBytes, 0)) { + + switch ($SizeInBytes) { { $_ -ge 1PB } { - '{0:N2} PB' -f ($SizeInBytes / 1PB); break + # Convert to PB + '{0:N2} PB' -f ($SizeInBytes / 1PB) + break } { $_ -ge 1TB } { - '{0:N2} TB' -f ($SizeInBytes / 1TB); break + # Convert to TB + '{0:N2} TB' -f ($SizeInBytes / 1TB) + break } { $_ -ge 1GB } { - '{0:N2} GB' -f ($SizeInBytes / 1GB); break + # Convert to GB + '{0:N2} GB' -f ($SizeInBytes / 1GB) + break } { $_ -ge 1MB } { - '{0:N2} MB' -f ($SizeInBytes / 1MB); break + # Convert to MB + '{0:N2} MB' -f ($SizeInBytes / 1MB) + break } { $_ -ge 1KB } { - '{0:N2} KB' -f ($SizeInBytes / 1KB); break + # Convert to KB + '{0:N2} KB' -f ($SizeInBytes / 1KB) + break } default { + # Display in bytes if less than 1KB "$SizeInBytes Bytes" } } diff --git a/functions/Get-Input.ps1 b/functions/Get-Input.ps1 index 8847d55..bca6f65 100644 --- a/functions/Get-Input.ps1 +++ b/functions/Get-Input.ps1 @@ -1,45 +1,50 @@ -Function Get-Input { - <# - .SYNOPSIS - Get input from user - - .DESCRIPTION - Stop the script and get input from user and answer will be returned - - .PARAMETER Message - The question to show to the user - - .PARAMETER Required - If provided will force an answer to be given - - .EXAMPLE - $UserName = Get-Input -Message "What is your name" -Required - $UserAge = Get-Input -Message "What is your age" - - .NOTES - General notes - #> +<# +.SYNOPSIS + Gets user input with optional validation. +.DESCRIPTION + This function prompts the user for input and validates it based on the optional 'Required' switch. +.PARAMETER Message + Specifies the message displayed to the user as a prompt for input. +.PARAMETER Required + Indicates whether the input is required. If used, the function continues to prompt until valid input is provided. +.INPUTS + None. This function does not accept piped input. +.OUTPUTS + System.String. The user-provided input. +.EXAMPLE + Get-Input -Message "Enter your name" -Required + Prompts the user to enter their name, and the input is required. +.EXAMPLE + Get-Input -Message "Enter your age" + Prompts the user to enter their age, and the input is optional. +#> +function Get-Input { [CmdletBinding()] param( - [Parameter( - Mandatory = $true - )] + [Parameter(Mandatory = $true)] [string]$Message, - [Parameter( - Mandatory = $false - )] + [Parameter(Mandatory = $false)] [switch]$Required - ) - if ($Required) { - While ( ($Null -eq $Variable) -or ($Variable -eq '') ) { - $Variable = Read-Host -Prompt "$Message" - $Variable = $Variable.Trim() - } - } else { - $Variable = Read-Host -Prompt "$Message" - $Variable = $Variable.Trim() + + # Variable to store user input + $UserInput = $null + + # Loop until a valid input is provided (if Required switch is used) + while ($Required -and ($null -eq $UserInput -or $UserInput -eq '')) { + Write-Host "Required | " -ForegroundColor DarkRed -NoNewline + $UserInput = Read-Host -Prompt $Message + $UserInput = $UserInput.Trim() } - Return $Variable + + # If Required switch is not used, get input without validation + if (-not $Required) { + Write-Host "Optional | " -ForegroundColor DarkCyan -NoNewline + $UserInput = Read-Host -Prompt "$Message" + $UserInput = $UserInput.Trim() + } + + # Return the user input + return $UserInput } \ No newline at end of file diff --git a/functions/Get-QBittorrentCategories.ps1 b/functions/Get-QBittorrentCategories.ps1 index 14440a5..5278d60 100644 --- a/functions/Get-QBittorrentCategories.ps1 +++ b/functions/Get-QBittorrentCategories.ps1 @@ -1,35 +1,43 @@ -function Get-QBittorrentCategories { - <# - .SYNOPSIS - Retrieves categories from qBittorrent API. - - .DESCRIPTION - This function retrieves the categories available in qBittorrent using its API. - - .PARAMETER qBittorrentUrl - The URL of the qBittorrent WebUI. - - .PARAMETER username - The username for authentication in the qBittorrent WebUI. - - .PARAMETER password - The password for authentication in the qBittorrent WebUI. - - .EXAMPLE - $categories = Get-QBittorrentCategories -qBittorrentUrl "http://localhost:8080" -username "admin" -password "password" - Retrieves categories from qBittorrent using specified credentials. - - .NOTES +<# +.SYNOPSIS + Retrieves qBittorrent categories using REST API. +.DESCRIPTION + This function connects to a qBittorrent server via REST API, authenticates using provided + credentials, and retrieves the list of categories. +.PARAMETER qBittorrentUrl + Specifies the URL of the qBittorrent server. +.PARAMETER qBittorrentPort + Specifies the port number of the qBittorrent server. +.PARAMETER username + Specifies the username for authenticating to the qBittorrent server. +.PARAMETER password + Specifies the password for authenticating to the qBittorrent server. +.OUTPUTS + Returns an array of qBittorrent categories. +.EXAMPLE + Get-QBittorrentCategories -qBittorrentUrl "http://localhost" -qBittorrentPort 8080 -username "admin" -password "admin123" + # Retrieves and returns the list of qBittorrent categories. +.NOTES This function uses qBittorrent's API v2. - #> +#> +function Get-QBittorrentCategories { + [CmdletBinding()] param ( + [Parameter(Mandatory = $true)] [string]$qBittorrentUrl, + + [Parameter(Mandatory = $true)] + [int]$qBittorrentPort, + + [Parameter(Mandatory = $true)] [string]$username, + + [Parameter(Mandatory = $true)] [string]$password ) # Construct the base URI for API calls - $baseUri = "$qBittorrentUrl/api/v2" + $baseUri = "$qBittorrentUrl`:$qBittorrentPort/api/v2" try { # Create a session to persist the authentication @@ -44,12 +52,13 @@ function Get-QBittorrentCategories { # Get categories $categoriesResponse = Invoke-RestMethod -Uri "$baseUri/torrents/categories" -WebSession $session -ErrorAction Stop - + # Log out from qBittorrent + Invoke-RestMethod -Uri "$baseUri/auth/logout" -Method Post -WebSession $session -ErrorAction Stop | Out-Null + # Check if categories are retrieved if ($null -ne $categoriesResponse) { - # Log out from qBittorrent - Invoke-RestMethod -Uri "$baseUri/auth/logout" -Method Post -WebSession $session -ErrorAction Stop | Out-Null - return $categoriesResponse + $Categories = $($categoriesResponse.PSObject.Properties.Value.name) + return $Categories } else { Write-Host "Categories not found or empty response." } @@ -61,6 +70,5 @@ function Get-QBittorrentCategories { Write-Host "The requested item was not found. Please check the URL or endpoint." } catch { Write-Host "An unexpected error occurred: $_" - # Additional handling for specific errors can be added here } } \ No newline at end of file diff --git a/functions/Import-Medusa.ps1 b/functions/Import-Medusa.ps1 index b96d728..b3e2dde 100644 --- a/functions/Import-Medusa.ps1 +++ b/functions/Import-Medusa.ps1 @@ -1,44 +1,71 @@ +<# +.SYNOPSIS + Imports items into Medusa for post-processing. +.DESCRIPTION + This function initiates the import process in Medusa for post-processing. It takes various parameters + such as source directory, Medusa API key, host information, timeout settings, and download details. +.PARAMETER Source + Specifies the source directory for the items to be imported. +.PARAMETER MedusaApiKey + Specifies the API key for authentication with the Medusa server. +.PARAMETER MedusaHost + Specifies the host address of the Medusa server. +.PARAMETER MedusaPort + Specifies the port number for communication with the Medusa server. +.PARAMETER MedusaTimeOutMinutes + Specifies the timeout duration (in minutes) for the Medusa import operation. +.PARAMETER DownloadLabel + Specifies the label for the download being imported. +.PARAMETER DownloadName + Specifies the name of the download being imported. +.OUTPUTS + None +.EXAMPLE + Import-Medusa -Source "C:\Downloads\Show" -MedusaApiKey "123456" -MedusaHost "medusa.example.com" + -MedusaPort 8081 -MedusaTimeOutMinutes 30 -DownloadLabel "TV" -DownloadName "Show" + Initiates the Medusa import for a episode named "Show" in the "C:\Downloads" directory. +#> function Import-Medusa { - <# - .SYNOPSIS - Start import in Medusa - - .DESCRIPTION - Start import to Medusa with timeout and error handeling - - .PARAMETER Source - Path to episode to import - - .EXAMPLE - Import-Medusa -Source 'C:\Temp\Episode' - - .NOTES - General notes - #> [CmdletBinding()] param ( - [Parameter( - Mandatory = $true - )] - [string]$Source + [Parameter(Mandatory = $true)] + [string]$Source, + + [Parameter(Mandatory = $true)] + [string]$MedusaApiKey, + + [Parameter(Mandatory = $true)] + [string]$MedusaHost, + + [Parameter(Mandatory = $true)] + [int]$MedusaPort, + + [Parameter(Mandatory = $true)] + [int]$MedusaTimeOutMinutes, + + [Parameter(Mandatory = $true)] + [string]$DownloadLabel, + + [Parameter(Mandatory = $true)] + [string]$DownloadName ) # Make sure needed functions are available otherwise try to load them. - $commands = 'Write-HTMLLog', 'Stop-Script' - foreach ($commandName in $commands) { - if (!($command = Get-Command $commandName -ErrorAction SilentlyContinue)) { - Try { - . $PSScriptRoot\$commandName.ps1 - Write-Host "$commandName Function loaded." -ForegroundColor Green - } Catch { - Write-Error -Message "Failed to import $commandName function: $_" + $functionsToLoad = @('Write-HTMLLog', 'Stop-Script') + foreach ($functionName in $functionsToLoad) { + if (-not (Get-Command $functionName -ErrorAction SilentlyContinue)) { + try { + . "$PSScriptRoot\$functionName.ps1" + Write-Host "$functionName function loaded." -ForegroundColor Green + } catch { + Write-Error "Failed to import $functionName function: $_" exit 1 } } } - # Start + # Start Medusa import. $body = @{ 'proc_dir' = $Source 'resource' = '' @@ -54,7 +81,10 @@ function Import-Medusa { $headers = @{ 'X-Api-Key' = $MedusaApiKey } + Write-HTMLLog -Column1 '*** Medusa Import ***' -Header + + # Doing the API call to Medusa try { $response = Invoke-RestMethod -Uri "http://$MedusaHost`:$MedusaPort/api/v2/postprocess" -Method Post -Body $Body -Headers $headers } catch { @@ -62,9 +92,12 @@ function Import-Medusa { Write-HTMLLog -Column1 'Result:' -Column2 'Failed' -ColorBg 'Error' Stop-Script -ExitReason "Medusa Error: $DownloadLabel - $DownloadName" } + if ($response.status -eq 'success') { $timeout = New-TimeSpan -Minutes $MedusaTimeOutMinutes $endTime = (Get-Date).Add($timeout) + + # Check progress of Import Job in Medusa and wait till success or Time Out do { try { $status = Invoke-RestMethod -Uri "http://$MedusaHost`:$MedusaPort/api/v2/postprocess/$($response.queueItem.identifier)" -Method Get -Headers $headers @@ -76,17 +109,22 @@ function Import-Medusa { Start-Sleep 1 } until ($status.success -or ((Get-Date) -gt $endTime)) + if ($status.success) { + # Find if there were any errors posted back by Medusa $ValuesToFind = 'Processing failed', 'aborting post-processing', 'Unable to figure out what folder to process' $MatchPattern = ($ValuesToFind | ForEach-Object { [regex]::Escape($_) }) -join '|' + if ($status.output -match $MatchPattern) { $ValuesToFind = 'Retrieving episode object for', 'Current quality', 'New quality', 'Old size', 'New size', 'Processing failed', 'aborting post-processing', 'Unable to figure out what folder to process' $MatchPattern = ($ValuesToFind | ForEach-Object { [regex]::Escape($_) }) -join '|' + foreach ($line in $status.output ) { if ($line -match $MatchPattern) { Write-HTMLLog -Column1 'Medusa:' -Column2 $line -ColorBg 'Error' } } + Write-HTMLLog -Column1 'Result:' -Column2 'Failed' -ColorBg 'Error' Stop-Script -ExitReason "Medusa Error: $DownloadLabel - $DownloadName" } else { diff --git a/functions/Import-Radarr.ps1 b/functions/Import-Radarr.ps1 index 05c8b24..fac4dee 100644 --- a/functions/Import-Radarr.ps1 +++ b/functions/Import-Radarr.ps1 @@ -1,42 +1,73 @@ +<# +.SYNOPSIS + Imports downloaded movies into Radarr and monitors the import progress. +.DESCRIPTION + This function imports downloaded movies into Radarr and monitors the import progress. It uses Radarr API + to initiate the import process and checks the status until completion or timeout. +.PARAMETER Source + Specifies the path of the downloaded movie. +.PARAMETER RadarrApiKey + Specifies the API key for Radarr. +.PARAMETER RadarrHost + Specifies the host (URL or IP) where Radarr is running. +.PARAMETER RadarrPort + Specifies the port on which Radarr is listening. +.PARAMETER RadarrTimeOutMinutes + Specifies the timeout duration (in minutes) for the Radarr import operation. +.PARAMETER TorrentHash + Specifies the unique identifier (hash) of the downloaded torrent. +.PARAMETER DownloadLabel + Specifies a label for the downloaded movie. +.PARAMETER DownloadName + Specifies the name of the downloaded movie. +.OUTPUTS + None +.EXAMPLE + Import-Radarr -Source "C:\Downloads\Movie1" -RadarrApiKey "yourApiKey" -RadarrHost "localhost" -RadarrPort 7878 + -RadarrTimeOutMinutes 30 -TorrentHash "abc123" -DownloadLabel "Action" -DownloadName "Movie1" + Initiates Radarr import for the specified movie and monitors the progress. +#> function Import-Radarr { - <# - .SYNOPSIS - Start Radarr movie import - - .DESCRIPTION - STart the Radarr movie import with error handling - - .PARAMETER Source - Path to movie to import - - .EXAMPLE - Import-Radarr -Source 'C:\Temp\Movie' - - .NOTES - General notes - #> [CmdletBinding()] param ( - [Parameter( - Mandatory = $true - )] - [string]$Source + [Parameter(Mandatory = $true)] + [string]$Source, + + [Parameter(Mandatory = $true)] + [string]$RadarrApiKey, + + [Parameter(Mandatory = $true)] + [string]$RadarrHost, + + [Parameter(Mandatory = $true)] + [int]$RadarrPort, + + [Parameter(Mandatory = $true)] + [int]$RadarrTimeOutMinutes, + + [Parameter(Mandatory = $false)] + [string]$TorrentHash, + + [Parameter(Mandatory = $true)] + [string]$DownloadLabel, + + [Parameter(Mandatory = $true)] + [string]$DownloadName ) # Make sure needed functions are available otherwise try to load them. - $commands = 'Write-HTMLLog', 'Stop-Script' - foreach ($commandName in $commands) { - if (!($command = Get-Command $commandName -ErrorAction SilentlyContinue)) { - Try { - . $PSScriptRoot\$commandName.ps1 - Write-Host "$commandName Function loaded." -ForegroundColor Green - } Catch { - Write-Error -Message "Failed to import $commandName function: $_" + $functionsToLoad = @('Write-HTMLLog', 'Stop-Script') + foreach ($functionName in $functionsToLoad) { + if (-not (Get-Command $functionName -ErrorAction SilentlyContinue)) { + try { + . "$PSScriptRoot\$functionName.ps1" + Write-Host "$functionName function loaded." -ForegroundColor Green + } catch { + Write-Error "Failed to import $functionName function: $_" exit 1 } } } - # Start $body = @{ 'name' = 'DownloadedMoviesScan' diff --git a/functions/Load-Module.ps1 b/functions/Install-PSModule.ps1 similarity index 75% rename from functions/Load-Module.ps1 rename to functions/Install-PSModule.ps1 index dcd2e55..3dd8d31 100644 --- a/functions/Load-Module.ps1 +++ b/functions/Install-PSModule.ps1 @@ -1,24 +1,29 @@ <# .SYNOPSIS - Loads a PowerShell module and ensures it is installed. - + Installs and imports a PowerShell module, checking if it's already loaded. .DESCRIPTION - This function checks if a specified PowerShell module is already loaded. If not, it checks if the module is installed and installs it if necessary. - + This function installs a PowerShell module specified by the $ModuleName parameter. + It checks if the module is already loaded and active. If not, it verifies if the + module is installed. If not installed, it checks for the availability of NuGet as + the Package Provider and installs it if necessary. Finally, it installs and loads + the specified module. .PARAMETER ModuleName - Specifies the name of the module to load. - -.NOTES - File Name : Load-Module.ps1 - Prerequisite : PowerShell V5 - -.LINK - https://docs.microsoft.com/en-us/powershell/ + Specifies the name of the PowerShell module to be installed and imported. +.INPUTS + String - You can pipeline the name of the module as a string to this function. +.OUTPUTS + None - This function does not return any objects. +.EXAMPLE + Install-PSModule -ModuleName "ExampleModule" + This example installs and imports the "ExampleModule" PowerShell module. #> - -function Load-Module { +function Install-PSModule { [CmdletBinding()] - param ([Parameter(Mandatory = $true)] + param ( + [Parameter( + Mandatory = $true, + ValueFromPipeline = $true + )] [string]$ModuleName ) @@ -53,7 +58,6 @@ function Load-Module { # Check if the module is available for installation if (Find-Module -Name $ModuleName -ErrorAction SilentlyContinue) { - Write-Host " We need to install $ModuleName PowerShell Modules first, this might take a while" -ForegroundColor DarkYellow Write-Host " Installing $ModuleName PowerShell Modules" -ForegroundColor DarkYellow -NoNewline # Install the module diff --git a/functions/New-Mutex.ps1 b/functions/New-Mutex.ps1 index 8d4e541..ca67efd 100644 --- a/functions/New-Mutex.ps1 +++ b/functions/New-Mutex.ps1 @@ -1,58 +1,62 @@ -function New-Mutex { - <# - .SYNOPSIS - Create a Mutex - .DESCRIPTION - This function attempts to get a lock to a mutex with a given name. If a lock - cannot be obtained this function waits until it can. - - Using mutexes, multiple scripts/processes can coordinate exclusive access - to a particular work product. One script can create the mutex then go about - doing whatever work is needed, then release the mutex at the end. All other - scripts will wait until the mutex is released before they too perform work - that only one at a time should be doing. +<# +.SYNOPSIS + Creates or opens a named mutex to control access to a shared resource. +.DESCRIPTION + The New-Mutex function creates or opens a named mutex, providing a simple mechanism + for interprocess synchronization. It allows a script or function to acquire and release + a lock on a specified mutex, ensuring exclusive access to a shared resource. +.PARAMETER MutexName + Specifies the name of the mutex. This parameter is mandatory and must be a non-null, + non-empty string. +.OUTPUTS + Returns a PSObject containing information about the created or opened mutex. + The PSObject includes the MutexName and the Mutex object itself. +.EXAMPLE + PS C:\> $myMutex = New-Mutex -MutexName "MyMutex" + PS C:\> # Perform actions requiring exclusive access to the shared resource + PS C:\> Remove-Mutex -MutexObject $myMutex - This function outputs a PSObject with the following NoteProperties: + This example creates a new mutex named "MyMutex," acquires the lock, performs + actions requiring exclusive access, releases the lock, and continues with + other script or function logic. +#> - Name - Mutex - - Use this object in a followup call to Remove-Mutex once done. - .PARAMETER MutexName - The name of the mutex to create. - .INPUTS - None. You cannot pipe objects to this function. - .OUTPUTS - PSObject - #Requires -Version 2.0 - #> +function New-Mutex { [CmdletBinding()] [OutputType( [PSObject] )] Param( - [Parameter( - Mandatory = $true - )] + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$MutexName ) + # Variables for tracking mutex status $MutexWasCreated = $false - $Mutex = $Null + $Mutex = $null + Write-Host "Waiting to acquire lock [$MutexName]..." -ForegroundColor DarkGray + + # Attempt to open an existing mutex or create a new one [void][System.Reflection.Assembly]::LoadWithPartialName('System.Threading') try { $Mutex = [System.Threading.Mutex]::OpenExisting($MutexName) } catch { $Mutex = New-Object System.Threading.Mutex($true, $MutexName, [ref]$MutexWasCreated) } + + # Acquire the lock if the mutex was created successfully try { if (!$MutexWasCreated) { $Mutex.WaitOne() | Out-Null } } catch { + # Handle any errors during mutex acquisition } + Write-Host "Lock [$MutexName] acquired. Executing..." -ForegroundColor DarkGray + + # Output a PSObject with mutex information Write-Output ([PSCustomObject]@{ Name = $MutexName; Mutex = $Mutex }) -} # New-Mutex \ No newline at end of file +} \ No newline at end of file diff --git a/functions/Remove-Mutex.ps1 b/functions/Remove-Mutex.ps1 index 26a272f..cf96dba 100644 --- a/functions/Remove-Mutex.ps1 +++ b/functions/Remove-Mutex.ps1 @@ -1,23 +1,31 @@ +<# +.SYNOPSIS + Releases the lock held by the specified mutex object. +.DESCRIPTION + The Remove-Mutex function releases the lock held by the specified mutex object. + It is intended for use with mutex objects created by the New-Mutex function, + allowing the release of the lock and enabling other processes or scripts to + acquire the mutex. +.PARAMETER MutexObject + Specifies the mutex object to release. This parameter is mandatory and must be + a valid PSObject containing mutex information, typically obtained from the + New-Mutex function. +.OUTPUTS + None. The function releases the lock on the specified mutex. +.EXAMPLE + PS C:\> $myMutex = New-Mutex -MutexName "MyMutex" + PS C:\> # Perform actions requiring exclusive access to the shared resource + PS C:\> Remove-Mutex -MutexObject $myMutex + + This example creates a new mutex named "MyMutex," acquires the lock, performs + actions requiring exclusive access, and then releases the lock using the + Remove-Mutex function. +#> + function Remove-Mutex { - <# - .SYNOPSIS - Removes a previously created Mutex - .DESCRIPTION - This function attempts to release a lock on a mutex created by an earlier call - to New-Mutex. - .PARAMETER MutexObject - The PSObject object as output by the New-Mutex function. - .INPUTS - None. You cannot pipe objects to this function. - .OUTPUTS - None. - #Requires -Version 2.0 - #> [CmdletBinding()] Param( - [Parameter( - Mandatory = $true - )] + [Parameter(Mandatory = $true)] [ValidateNotNull()] [PSObject]$MutexObject ) @@ -25,7 +33,10 @@ function Remove-Mutex { # $MutexObject | fl * | Out-String | Write-Host Write-Host "Releasing lock [$($MutexObject.Name)]..." -ForegroundColor DarkGray try { + # Release the Mutex [void]$MutexObject.Mutex.ReleaseMutex() + Write-Host "Lock released: $($MutexObject.Name)" -ForegroundColor DarkGray } catch { + Write-Warning "Failed to release lock: $($MutexObject.Name). $_" } -} # Remove-Mutex \ No newline at end of file +} \ No newline at end of file diff --git a/functions/Select-MenuOption.ps1 b/functions/Select-MenuOption.ps1 index b5abdfc..640a754 100644 --- a/functions/Select-MenuOption.ps1 +++ b/functions/Select-MenuOption.ps1 @@ -1,25 +1,28 @@ -function Select-MenuOption { - <# - .SYNOPSIS - This function creates a menu with options provided in the $MenuOptions parameter and prompts the user to select an option. The selected option is returned as output. - - .DESCRIPTION - The Select-MenuOption function is used to create a menu with options provided as an array in the $MenuOptions parameter. The function prompts the user to select an option by displaying the options with their corresponding index numbers. The user must enter the index number of the option they wish to select. The function checks if the entered number is within the range of the available options and returns the selected option. - - .PARAMETER MenuOptions +<# +.SYNOPSIS + Presents a menu of options to the user and allows them to select one. +.DESCRIPTION + The Select-MenuOption function is used to create a menu with options provided + as an array in the $MenuOptions parameter. + The function prompts the user to select an option by displaying the options + with their corresponding index numbers. + The user must enter the index number of the option they wish to select. + The function checks if the entered number is within the range of the available options and returns the selected option. +.PARAMETER MenuOptions An array of options to be presented in the menu. The options must be of the same data type. - - .PARAMETER MenuQuestion +.PARAMETER MenuQuestion A string representing the question to be asked when prompting the user for input. - - .EXAMPLE +.OUTPUTS + The selected menu option. +.EXAMPLE $Options = @("Option 1","Option 2","Option 3") $Question = "an option" $SelectedOption = Select-MenuOption -MenuOptions $Options -MenuQuestion $Question - This example creates a menu with three options "Option 1", "Option 2", and "Option 3". The user is prompted to select an option by displaying the options with their index numbers. The function returns the selected option. - - .NOTES - #> + This example creates a menu with three options "Option 1", "Option 2", and "Option 3". + The user is prompted to select an option by displaying the options with their index numbers. + The function returns the selected option. +#> +function Select-MenuOption { param ( [Parameter(Mandatory = $true)] [Object]$MenuOptions, @@ -27,32 +30,42 @@ function Select-MenuOption { [Parameter(Mandatory = $true)] [string]$MenuQuestion ) + + # Check if there is only one option, return it directly if ($MenuOptions.Count -eq 1) { Return $MenuOptions - } else { - Write-Host "`nSelect the correct $MenuQuestion" -ForegroundColor DarkCyan - $menu = @{} - $maxWidth = [math]::Ceiling([math]::Log10($MenuOptions.Count + 1)) - for ($i = 1; $i -le $MenuOptions.count; $i++) { - $indexDisplay = "$i.".PadRight($maxWidth + 2) - Write-Host "$indexDisplay" -ForegroundColor Magenta -NoNewline - Write-Host "$($MenuOptions[$i - 1])" -ForegroundColor White - $menu.Add($i, ($MenuOptions[$i - 1])) - } - do { - try { - $numOk = $true - [int]$ans = Read-Host "Enter $MenuQuestion number to select" - if ($ans -lt 1 -or $ans -gt $MenuOptions.Count) { - $numOK = $false - Write-Host 'Not a valid selection' -ForegroundColor DarkRed - } - } catch { + } + + Write-Host "`nSelect the correct $MenuQuestion" -ForegroundColor DarkCyan + + $menu = @{} + $maxWidth = [math]::Ceiling([math]::Log10($MenuOptions.Count + 1)) + + # Display menu options with index numbers + for ($i = 1; $i -le $MenuOptions.count; $i++) { + $indexDisplay = "$i.".PadRight($maxWidth + 2) + Write-Host "$indexDisplay" -ForegroundColor Magenta -NoNewline + Write-Host "$($MenuOptions[$i - 1])" -ForegroundColor White + $menu.Add($i, ($MenuOptions[$i - 1])) + } + + # Prompt the user for input and validate the selection + do { + try { + $numOk = $true + [int]$ans = Read-Host "Enter $MenuQuestion number to select" + if ($ans -lt 1 -or $ans -gt $MenuOptions.Count) { $numOK = $false - Write-Host 'Please enter a number' -ForegroundColor DarkRed + Write-Host 'Not a valid selection' -ForegroundColor DarkRed } - } # end do - until (($ans -ge 1 -and $ans -le $MenuOptions.Count) -and $numOK) - Return $MenuOptions[$ans - 1] - } + } catch { + $numOK = $false + Write-Host 'Please enter a number' -ForegroundColor DarkRed + } + } # end do + until (($ans -ge 1 -and $ans -le $MenuOptions.Count) -and $numOK) + + # Return the selected option + Return $MenuOptions[$ans - 1] + } \ No newline at end of file diff --git a/functions/Send-HtmlMail.ps1 b/functions/Send-HtmlMail.ps1 index 320b6de..e733076 100644 --- a/functions/Send-HtmlMail.ps1 +++ b/functions/Send-HtmlMail.ps1 @@ -1,40 +1,34 @@ <# .SYNOPSIS Sends an HTML email using the specified SMTP server and credentials. - .DESCRIPTION - This function sends an HTML email using the specified SMTP server, port, credentials, - recipient details, and email content. - + This function sends an HTML email using the Send-MailKitMessage cmdlet. It requires the + SMTP server details, sender's and recipient's email addresses, subject, and HTML body. .PARAMETER SMTPServer - The SMTP server address. - + Specifies the address of the SMTP server for sending the email. .PARAMETER SMTPServerPort - The SMTP server port. - + Specifies the port number to be used when connecting to the SMTP server. .PARAMETER SmtpUser - The username for SMTP authentication. - + Specifies the username for authenticating with the SMTP server. .PARAMETER SmtpPassword - The password for SMTP authentication. - + Specifies the password for authenticating with the SMTP server. .PARAMETER To - The email address of the recipient(s). Multiple recipients should be separated by commas. - + Specifies the email address of the recipient. .PARAMETER From - The sender's email address. - + Specifies the email address of the sender. .PARAMETER Subject - The subject of the email. - + Specifies the subject of the email. .PARAMETER HTMLBody - The HTML content of the email body. - + Specifies the HTML content of the email body. +.OUTPUTS + None. The function does not return any objects. .EXAMPLE - Send-HtmlMail -SMTPServer 'smtp.example.com' -SMTPServerPort 587 -SmtpUser 'user@example.com' - -SmtpPassword 'password' -To 'recipient@example.com' -From 'sender@example.com' - -Subject 'Test Email' -HTMLBody '

This is a test email.

' + Send-HtmlMail -SMTPServer "smtp.example.com" -SMTPServerPort 587 -SmtpUser "user@example.com" + -SmtpPassword "P@ssw0rd" -To "recipient@example.com" -From "sender@example.com" + -Subject "Test Email" -HTMLBody "

This is a test email.

" + Sends a test email with HTML content. #> + function Send-HtmlMail { [CmdletBinding()] param( @@ -78,14 +72,6 @@ function Send-HtmlMail { "HTMLBody" = $HTMLBody } - - - if (-not (Get-Module -Name Send-MailKitMessage -ListAvailable)) { - Install-Module -Name Send-MailKitMessage -AllowClobber -Force -Confirm:$false -Scope CurrentUser - } else { - Import-Module -Name Send-MailKitMessage - } - # Send the email try { Send-MailKitMessage @Parameters diff --git a/functions/Send-Mail.ps1 b/functions/Send-Mail.ps1 deleted file mode 100644 index 4f369de..0000000 --- a/functions/Send-Mail.ps1 +++ /dev/null @@ -1,106 +0,0 @@ -function Send-Mail { - <# - .SYNOPSIS - Send mail - - .DESCRIPTION - Send email with file contents as body - - .PARAMETER SMTPserver - SMTP server - - .PARAMETER SMTPport - SMTP Port - - .PARAMETER MailTo - Mail to - - .PARAMETER MailFrom - Mail From - - .PARAMETER MailFromName - Mail From Name - - .PARAMETER MailSubject - Mail Subject - - .PARAMETER MailBody - Path to html file as mail body - - .PARAMETER SMTPuser - SMTP User - - .PARAMETER SMTPpass - SMTP Password - - .EXAMPLE - Send-Mail -SMTPserver 'mail.domain.com' -SMTPPort '25' -MailTo 'recipient@mail.com' -MailFrom 'sender@mail.com' -MailFromName 'Sender Name' -MailSubject 'Mail Subject' -MailBody 'C:\Temp\log.html' -SMTPuser 'user' -SMTPpass 'p@ssw0rd' - - .NOTES - General notes - #> - [CmdletBinding()] - param ( - [Parameter( - Mandatory = $true - )] - [string]$SMTPserver, - - [Parameter( - Mandatory = $true - )] - [string]$SMTPport, - - [Parameter( - Mandatory = $true - )] - [string]$MailTo, - - [Parameter( - Mandatory = $true - )] - [string]$MailFrom, - - [Parameter( - Mandatory = $true - )] - [string]$MailFromName, - - [Parameter( - Mandatory = $true - )] - [string]$MailSubject, - - [Parameter( - Mandatory = $true - )] - [string]$MailBody, - - [Parameter( - Mandatory = $true - )] - [string]$SMTPuser, - - [Parameter( - Mandatory = $true - )] - [string]$SMTPpass - ) - - $StartInfo = New-Object System.Diagnostics.ProcessStartInfo - $StartInfo.FileName = $MailSendPath - $StartInfo.RedirectStandardError = $true - $StartInfo.RedirectStandardOutput = $true - $StartInfo.UseShellExecute = $false - $StartInfo.Arguments = @("-smtp $SMTPserver", "-port $SMTPport", "-domain $SMTPserver", "-t $MailTo", "-f $MailFrom", "-fname `"$MailFromName`"", "-sub `"$MailSubject`"", 'body', "-file `"$MailBody`"", "-mime-type `"text/html`"", '-ssl', "auth -user $SMTPuser -pass $SMTPpass") - $Process = New-Object System.Diagnostics.Process - $Process.StartInfo = $StartInfo - $Process.Start() | Out-Null - # $stdout = $Process.StandardOutput.ReadToEnd() - # $stderr = $Process.StandardError.ReadToEnd() - # Write-Host "stdout: $stdout" - # Write-Host "stderr: $stderr" - $Process.WaitForExit() - # Write-Host "exit code: " + $p.ExitCode - # return $stdout -} \ No newline at end of file diff --git a/functions/Start-FileCopy.ps1 b/functions/Start-FileCopy.ps1 new file mode 100644 index 0000000..9785a01 --- /dev/null +++ b/functions/Start-FileCopy.ps1 @@ -0,0 +1,197 @@ +<# +.SYNOPSIS + Copies files using RoboCopy or falls back to PowerShell Copy if RoboCopy is not available. +.DESCRIPTION + This function copies files from a source to a destination using RoboCopy or PowerShell Copy. +.PARAMETER Source + Specifies the source path of the files to be copied. +.PARAMETER Destination + Specifies the destination path where the files will be copied. +.PARAMETER File + Specifies the filter for files to be copied. Use '*.*' for all files. +.PARAMETER DownloadLabel + Specifies the label for the download operation (for logging purposes). +.PARAMETER DownloadName + Specifies the name of the download operation (for logging purposes). +.OUTPUTS + None +.EXAMPLE + Start-FileCopy -Source "C:\Source" -Destination "D:\Destination" -File '*.*' -DownloadLabel "Label" -DownloadName "Download" + Copies all files from C:\Source to D:\Destination and logs the results. +.EXAMPLE + Start-FileCopy -Source "C:\Source" -Destination "D:\Destination" -File 'example.txt' -DownloadLabel "Label" -DownloadName "Download" + Copies a single file named 'example.txt' from C:\Source to D:\Destination and logs the results. +#> + + +function Start-FileCopy { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [string]$Source, + + [Parameter(Mandatory = $true)] + [string]$Destination, + + [Parameter(Mandatory = $true)] + [string]$File, + + [Parameter(Mandatory = $true)] + [string]$DownloadLabel, + + [Parameter(Mandatory = $true)] + [string]$DownloadName + ) + + # Make sure needed functions are available otherwise try to load them. + $functionsToLoad = @('Write-HTMLLog', 'Stop-Script', 'Format-Size') + foreach ($functionName in $functionsToLoad) { + if (-not (Get-Command $functionName -ErrorAction SilentlyContinue)) { + try { + . "$PSScriptRoot\$functionName.ps1" + Write-Host "$functionName function loaded." -ForegroundColor Green + } catch { + Write-Error "Failed to import $functionName function: $_" + exit 1 + } + } + } + + if (Test-Path "$env:SystemRoot\system32\Robocopy.exe") { + + # Set RoboCopy options based on the file parameter. + $options = @('/R:1', '/W:1', '/J', '/NP', '/NJH', '/NFL', '/NDL', '/MT8') + if ($File -eq '*.*') { + $options += '/E' + } + + $cmdArgs = @("`"$Source`"", "`"$Destination`"", "`"$File`"", $options) + + # Execute RoboCopy command + Write-HTMLLog -Column1 'Starting:' -Column2 'RoboCopy files' + try { + $Output = robocopy @cmdArgs + } catch { + Write-Host 'Exception:' $_.Exception.Message -ForegroundColor Red + Write-Host 'RoboCopy not found' -ForegroundColor Red + exit 1 + } + + # Parse RoboCopy output + foreach ($line in $Output) { + switch -Regex ($line) { + # Dir metrics + '^\s+Dirs\s:\s*' { + # Example: Dirs : 35 0 0 0 0 0 + $dirs = $_.Replace('Dirs :', '').Trim() + # Now remove the white space between the values.' + $dirs = $dirs -split '\s+' + + # Assign the appropriate column to values. + $TotalDirs = $dirs[0] + $CopiedDirs = $dirs[1] + $FailedDirs = $dirs[4] + } + # File metrics + '^\s+Files\s:\s[^*]' { + # Example: Files : 8318 0 8318 0 0 0 + $files = $_.Replace('Files :', '').Trim() + # Now remove the white space between the values.' + $files = $files -split '\s+' + + # Assign the appropriate column to values. + $TotalFiles = $files[0] + $CopiedFiles = $files[1] + $FailedFiles = $files[4] + } + # Byte metrics + '^\s+Bytes\s:\s*' { + # Example: Bytes : 1.607 g 0 1.607 g 0 0 0 + $bytes = $_.Replace('Bytes :', '').Trim() + # Now remove the white space between the values.' + $bytes = $bytes -split '\s+' + + # The raw text from the log file contains a k,m,or g after the non zero numbers. + # This will be used as a multiplier to determine the size in kb. + $counter = 0 + $tempByteArray = 0, 0, 0, 0, 0, 0 + $tempByteArrayCounter = 0 + foreach ($column in $bytes) { + if ($column -eq 'k') { + $tempByteArray[$tempByteArrayCounter - 1] = '{0:N2}' -f ([single]($bytes[$counter - 1]) * 1024) + $counter += 1 + } elseif ($column -eq 'm') { + $tempByteArray[$tempByteArrayCounter - 1] = '{0:N2}' -f ([single]($bytes[$counter - 1]) * 1048576) + $counter += 1 + } elseif ($column -eq 'g') { + $tempByteArray[$tempByteArrayCounter - 1] = '{0:N2}' -f ([single]($bytes[$counter - 1]) * 1073741824) + $counter += 1 + } else { + $tempByteArray[$tempByteArrayCounter] = $column + $counter += 1 + $tempByteArrayCounter += 1 + } + } + # Assign the appropriate column to values. + $TotalSize = Format-Size -SizeInBytes ([double]::Parse($tempByteArray[0])) + $CopiedSize = Format-Size -SizeInBytes ([double]::Parse($tempByteArray[1])) + $FailedSize = Format-Size -SizeInBytes ([double]::Parse($tempByteArray[4])) + # array columns 2,3, and 5 are available, but not being used currently. + } + # Speed metrics + '^\s+Speed\s:.*sec.$' { + # Example: Speed : 120.816 Bytes/min. + $speed = $_.Replace('Speed :', '').Trim() + $speed = $speed.Replace('Bytes/sec.', '').Trim() + # Remove any dots in the number + $speed = $speed.Replace('.', '').Trim() + # Assign the appropriate column to values. + $speed = Format-Size -SizeInBytes $speed + } + } + } + + # Log results + if ($FailedDirs -gt 0 -or $FailedFiles -gt 0) { + Write-HTMLLog -Column1 'Dirs' -Column2 "$TotalDirs Total" -ColorBg 'Error' + Write-HTMLLog -Column1 'Dirs' -Column2 "$FailedDirs Failed" -ColorBg 'Error' + Write-HTMLLog -Column1 'Files:' -Column2 "$TotalFiles Total" -ColorBg 'Error' + Write-HTMLLog -Column1 'Files:' -Column2 "$FailedFiles Failed" -ColorBg 'Error' + Write-HTMLLog -Column1 'Size:' -Column2 "$TotalSize Total" -ColorBg 'Error' + Write-HTMLLog -Column1 'Size:' -Column2 "$FailedSize Failed" -ColorBg 'Error' + Write-HTMLLog -Column1 'Result:' -Column2 'Failed' -ColorBg 'Error' + Stop-Script -ExitReason "Copy Error: $DownloadLabel - $DownloadName" + } else { + Write-HTMLLog -Column1 'Dirs:' -Column2 "$CopiedDirs Copied" + Write-HTMLLog -Column1 'Files:' -Column2 "$CopiedFiles Copied" + Write-HTMLLog -Column1 'Size:' -Column2 "$CopiedSize" + Write-HTMLLog -Column1 'Throughput:' -Column2 "$Speed/s" + Write-HTMLLog -Column1 'Result:' -Column2 'Successful' -ColorBg 'Success' + } + } else { + # RoboCopy not available, fallback to PowerShell copy + Write-HTMLLog -Column1 'Starting:' -Column2 'Copy files using PowerShell Copy' + try { + # Start the Stopwatch + $copyResultsStopWatch = [system.diagnostics.stopwatch]::startNew() + $copyResults = Copy-Item -Path $Source -Destination $Destination -Filter $File -Recurse -PassThru -ErrorAction Stop + # Stop the Stopwatch + $copyResultsStopWatch.Stop() + + # Log results for Copy-Item + $totalFiles = ($copyResults | Where-Object { -not $_.PSIsContainer }).Count + $totalDirs = ($copyResults | Where-Object { $_.PSIsContainer }).Count + $totalSize = ($copyResults | Measure-Object Length -Sum).Sum + + Write-HTMLLog -Column1 'Dirs:' -Column2 $totalDirs + Write-HTMLLog -Column1 'Files:' -Column2 "$totalFiles Copied" + Write-HTMLLog -Column1 'Size:' -Column2 (Format-Size -SizeInBytes $totalSize) + Write-HTMLLog -Column1 'Throughput:' -Column2 "$(Format-Size -SizeInBytes ($TotalSize/$copyResultsStopWatch.Elapsed.TotalSeconds))/s" + Write-HTMLLog -Column1 'Result:' -Column2 'Successful' -ColorBg 'Success' + } catch { + # Log error for Copy-Item + Write-HTMLLog -Column1 'Result:' -Column2 'Failed' -ColorBg 'Error' + Stop-Script -ExitReason "Copy Error: $DownloadLabel - $DownloadName" + } + } +} \ No newline at end of file diff --git a/functions/Start-MKVSubtitleStrip.ps1 b/functions/Start-MKVSubtitleStrip.ps1 index 6a3b7e7..6ee2e90 100644 --- a/functions/Start-MKVSubtitleStrip.ps1 +++ b/functions/Start-MKVSubtitleStrip.ps1 @@ -1,111 +1,133 @@ +<# +.SYNOPSIS + Strips unwanted subtitles from MKV files and extracts desired SRT subtitles. +.DESCRIPTION + This function extracts SRT subtitles from MKV files based on specified criteria, + and removes unwanted subtitles. It also remuxes the MKV file to exclude undesired + subtitle tracks. +.PARAMETER Source + Specifies the path to the directory containing MKV files. +.PARAMETER MKVMergePath + Specifies the path to the MKVMerge executable. +.PARAMETER MKVExtractPath + Specifies the path to the MKVExtract executable. +.PARAMETER WantedLanguages + Specifies an array of language codes for the desired subtitles. + The function extracts subtitles for the specified languages. + Example: @("eng", "dut") +.PARAMETER SubtitleNamesToRemove + Specifies an array of subtitle names to be removed. + Subtitles with matching names will be excluded from the extraction. + Example: @("Forced") +.PARAMETER LanguageCodeLookup + Specifies an Hashtable of language code mappings. + Each item in the array should be a hashtable with 'alpha3' and 'alpha2' keys + representing the three-letter and two-letter language codes, respectively. + Example: @(@{alpha3="eng"; alpha2="en"}, @{alpha3="dut"; alpha2="nl"}) +.OUTPUTS + None +.EXAMPLE + Start-MKVSubtitleStrip -Source "C:\Path\To\MKVFiles" -MKVMergePath "C:\Program Files\MKVToolNix\mkvmerge.exe" + -MKVExtractPath "C:\Program Files\MKVToolNix\mkvextract.exe" -WantedLanguages @("eng", "dut") + -SubtitleNamesToRemove @("Forced") -LanguageCodes @{"en"=@{"639-1"="en";"639-2"="eng";"name"="English"}; "nl"=@{"639-1"="nl";"639-2"="nld";"639-2/B"="dut";"name"="Dutch"}} +#> + function Start-MKVSubtitleStrip { - <# - .SYNOPSIS - Extract wanted SRT subtitles from MKVs in root folder and remux MKVs to strip out unwanted subtitle languages - .DESCRIPTION - Searches for all MKV files in root folder and extracts the SRT that are defined in $WantedLanguages - Remux any MKV that has subtitles of unwanted languages or Track_name in $SubtitleNamesToRemove. - Rename srt subtitle files based on $LanguageCodes - .PARAMETER Source - Defines the root folder to start the search for MKV files - .EXAMPLE - Start-MKVSubtitleStrip 'C:\Temp\Source' - .OUTPUTS - SRT files of the desired languages - file.en.srt - file.2.en.srt - file.nl.srt - file.de.srt - MKV files without the unwanted subtitles - file.mkv - #> [CmdletBinding()] param( - [Parameter( - Mandatory = $true - )] - $Source + [Parameter(Mandatory = $true)] + [string]$Source, + + [Parameter(Mandatory = $true)] + [string]$MKVMergePath, + + [Parameter(Mandatory = $true)] + [string]$MKVExtractPath, + + [Parameter(Mandatory = $true)] + [array]$WantedLanguages, + + [Parameter(Mandatory = $true)] + [array]$SubtitleNamesToRemove, + + [Parameter(Mandatory = $true)] + [hashtable]$LanguageCodeLookup ) # Make sure needed functions are available otherwise try to load them. - $commands = 'Write-HTMLLog' - foreach ($commandName in $commands) { - if (!($command = Get-Command $commandName -ErrorAction SilentlyContinue)) { - Try { - . $PSScriptRoot\$commandName.ps1 - Write-Host "$commandName Function loaded." -ForegroundColor Green - } Catch { - Write-Error -Message "Failed to import $commandName function: $_" + $functionsToLoad = @('Write-HTMLLog') + foreach ($functionName in $functionsToLoad) { + if (-not (Get-Command $functionName -ErrorAction SilentlyContinue)) { + try { + . "$PSScriptRoot\$functionName.ps1" + Write-Host "$functionName function loaded." -ForegroundColor Green + } catch { + Write-Error "Failed to import $functionName function: $_" exit 1 } } } - # Start - - $episodes = @() + + # Initialize variables + $MkvFiles = @() $SubsExtracted = $false $TotalSubsToExtract = 0 $TotalSubsToRemove = 0 - Write-HTMLLog -Column1 '*** Extract srt files from Video Container ***' -Header + Write-HTMLLog -Column1 '*** Extract srt files from MKV ***' -Header + Get-ChildItem -LiteralPath $Source -Recurse -Filter '*.mkv' | Where-Object { $_.DirectoryName -notlike "*\Sample" } | ForEach-Object { - Get-ChildItem -LiteralPath $_.FullName | ForEach-Object { - $fileName = $_.BaseName - $filePath = $_.FullName - $fileRoot = $_.Directory + $MkvFileInfo = $_ - # Start the json export with MKVMerge on the available tracks - $StartInfo = New-Object System.Diagnostics.ProcessStartInfo - $StartInfo.FileName = $MKVMergePath - $StartInfo.RedirectStandardError = $true - $StartInfo.RedirectStandardOutput = $true - $StartInfo.UseShellExecute = $false - $StartInfo.Arguments = @('-J', "`"$filePath`"") - $Process = New-Object System.Diagnostics.Process - $Process.StartInfo = $StartInfo - $Process.Start() | Out-Null - $stdout = $Process.StandardOutput.ReadToEnd() - # $stderr = $Process.StandardError.ReadToEnd() - # Write-Host "stdout: $stdout" - # Write-Host "stderr: $stderr" - $Process.WaitForExit() - if ($Process.ExitCode -eq 2) { - Write-HTMLLog -Column1 'Exit Code:' -Column2 $($Process.ExitCode) -ColorBg 'Error' - Write-HTMLLog -Column1 'mkvmerge:' -Column2 $stdout -ColorBg 'Error' - Write-HTMLLog -Column1 'Result:' -Column2 'Failed' -ColorBg 'Error' - } elseif ($Process.ExitCode -eq 1) { + # Start the json export with MKVMerge on the available tracks + $StartInfo = New-Object System.Diagnostics.ProcessStartInfo + $StartInfo.FileName = $MKVMergePath + $StartInfo.RedirectStandardError = $true + $StartInfo.RedirectStandardOutput = $true + $StartInfo.UseShellExecute = $false + $StartInfo.Arguments = @('-J', "`"$($MkvFileInfo.FullName)`"") + $Process = New-Object System.Diagnostics.Process + $Process.StartInfo = $StartInfo + $Process.Start() | Out-Null + $stdout = $Process.StandardOutput.ReadToEnd() + $Process.WaitForExit() + + switch ($process.ExitCode) { + 0 { + $MkvFileMetadata = $stdout | ConvertFrom-Json + } + 1 { Write-HTMLLog -Column1 'Exit Code:' -Column2 $($Process.ExitCode) -ColorBg 'Error' Write-HTMLLog -Column1 'mkvmerge:' -Column2 $stdout -ColorBg 'Error' Write-HTMLLog -Column1 'Result:' -Column2 'Warning' -ColorBg 'Error' - } elseif ($Process.ExitCode -eq 0) { - $fileMetadata = $stdout | ConvertFrom-Json - } else { + } + default { Write-HTMLLog -Column1 'Exit Code:' -Column2 $($Process.ExitCode) -ColorBg 'Error' Write-HTMLLog -Column1 'mkvmerge:' -Column2 $stdout -ColorBg 'Error' Write-HTMLLog -Column1 'Result:' -Column2 'Warning' -ColorBg 'Error' } + } - $file = @{ - FileName = $fileName - FilePath = $filePath - FileRoot = $fileRoot - FileTracks = $fileMetadata.tracks - FileAttachments = $fileMetadata.attachments - } - - $episodes += New-Object PSObject -Property $file + $file = @{ + FileName = $MkvFileInfo.BaseName + FilePath = $MkvFileInfo.FullName + FileRoot = $MkvFileInfo.Directory + FileTracks = $MkvFileMetadata.tracks + FileAttachments = $MkvFileMetadata.attachments } + + $MkvFiles += New-Object PSObject -Property $file } # Extract wanted SRT subtitles - $episodes | ForEach-Object { - $episode = $_ + $MkvFiles | ForEach-Object { + $MkvFile = $_ $SubIDsToExtract = @() $SubIDsToRemove = @() $SubsToExtract = @() $SubNamesToKeep = @() - $episode.FileTracks | ForEach-Object { + $MkvFile.FileTracks | ForEach-Object { $FileTrack = $_ if ($FileTrack.id) { # Check if subtitle is srt @@ -119,35 +141,18 @@ function Start-MKVSubtitleStrip { elseif ($FileTrack.properties.language -in $WantedLanguages) { # Handle multiple subtitles of same language, if exist skip duplicates of same language - if ("$($episode.FileName).$($FileTrack.properties.language).srt" -notin $SubNamesToKeep) { + if ("$($MkvFile.FileName).$($FileTrack.properties.language).srt" -notin $SubNamesToKeep) { $prefix = "$($FileTrack.properties.language)" # Add Subtitle name and ID to be extracted - $SubsToExtract += "`"$($FileTrack.id):$($episode.FileRoot)\$($episode.FileName).$($prefix).srt`"" + $SubsToExtract += "`"$($FileTrack.id):$($MkvFile.FileRoot)\$($MkvFile.FileName).$($prefix).srt`"" # Keep track of subtitle file names that will be extracted to handle possible duplicates - $SubNamesToKeep += "$($episode.FileName).$($prefix).srt" + $SubNamesToKeep += "$($MkvFile.FileName).$($prefix).srt" # Add subtitle ID to for MKV remux $SubIDsToExtract += $FileTrack.id - } - - # Handle multiple subtitles of same language, if exist append ID to file - # if ("$($episode.FileName).$($FileTrack.properties.language).srt" -in $SubNamesToKeep) { - # $prefix = "$($FileTrack.id).$($FileTrack.properties.language)" - # } else { - # $prefix = "$($FileTrack.properties.language)" - # } - - # # Add Subtitle name and ID to be extracted - # $SubsToExtract += "`"$($FileTrack.id):$($episode.FileRoot)\$($episode.FileName).$($prefix).srt`"" - - # # Keep track of subtitle file names that will be extracted to handle possible duplicates - # $SubNamesToKeep += "$($episode.FileName).$($prefix).srt" - - # # Add subtitle ID to for MKV remux - # $SubIDsToExtract += $FileTrack.id - + } } else { $SubIDsToRemove += $FileTrack.id } @@ -155,7 +160,7 @@ function Start-MKVSubtitleStrip { } } - # Count all subtitles to keep and remove of logging + # Count all subtitles to keep and remove for logging $TotalSubsToExtract = $TotalSubsToExtract + $SubIDsToExtract.count $TotalSubsToRemove = $TotalSubsToRemove + $SubIDsToRemove.count @@ -166,67 +171,62 @@ function Start-MKVSubtitleStrip { $StartInfo.RedirectStandardError = $true $StartInfo.RedirectStandardOutput = $true $StartInfo.UseShellExecute = $false - $StartInfo.Arguments = @("`"$($episode.FilePath)`"", 'tracks', "$SubsToExtract") + $StartInfo.Arguments = @("`"$($MkvFile.FilePath)`"", 'tracks', "$SubsToExtract") $Process = New-Object System.Diagnostics.Process $Process.StartInfo = $StartInfo $Process.Start() | Out-Null $stdout = $Process.StandardOutput.ReadToEnd() - # $stderr = $Process.StandardError.ReadToEnd() - # Write-Host "stdout: $stdout" - # Write-Host "stderr: $stderr" $Process.WaitForExit() - if ($Process.ExitCode -eq 2) { - Write-HTMLLog -Column1 'Exit Code:' -Column2 $($Process.ExitCode) -ColorBg 'Error' - Write-HTMLLog -Column1 'mkvextract:' -Column2 $stdout -ColorBg 'Error' - Write-HTMLLog -Column1 'Result:' -Column2 'Failed' -ColorBg 'Error' - } elseif ($Process.ExitCode -eq 1) { - Write-HTMLLog -Column1 'Exit Code:' -Column2 $($Process.ExitCode) -ColorBg 'Error' - Write-HTMLLog -Column1 'mkvextract:' -Column2 $stdout -ColorBg 'Error' - Write-HTMLLog -Column1 'Result:' -Column2 'Warning' -ColorBg 'Error' - } elseif ($Process.ExitCode -eq 0) { - $SubsExtracted = $true - # Write-HTMLLog -Column1 "Extracted:" -Column2 "$($SubsToExtract.count) Subtitles" - } else { - Write-HTMLLog -Column1 'Exit Code:' -Column2 $($Process.ExitCode) -ColorBg 'Error' - Write-HTMLLog -Column1 'mkvextract:' -Column2 $stdout -ColorBg 'Error' - Write-HTMLLog -Column1 'Result:' -Column2 'Unknown' -ColorBg 'Error' + + switch ($process.ExitCode) { + 0 { + $SubsExtracted = $true + } + 1 { + Write-HTMLLog -Column1 'Exit Code:' -Column2 $($Process.ExitCode) -ColorBg 'Error' + Write-HTMLLog -Column1 'mkvextract:' -Column2 $stdout -ColorBg 'Error' + Write-HTMLLog -Column1 'Result:' -Column2 'Warning' -ColorBg 'Error' + } + default { + Write-HTMLLog -Column1 'Exit Code:' -Column2 $process.ExitCode -ColorBg 'Error' + Write-HTMLLog -Column1 'mkvmerge:' -Column2 $stdout -ColorBg 'Error' + Write-HTMLLog -Column1 'Result:' -Column2 'Failed' -ColorBg 'Error' + } } } + + # Remux and strip out all unwanted subtitle languages if ($SubIDsToRemove.Count -gt 0) { - $TmpFileName = $Episode.FileName + '.tmp' - $TmpMkvPath = Join-Path $episode.FileRoot $TmpFileName + $TmpFileName = $MkvFile.FileName + '.tmp' + $TmpMkvPath = Join-Path $MkvFile.FileRoot $TmpFileName $StartInfo = New-Object System.Diagnostics.ProcessStartInfo $StartInfo.FileName = $MKVMergePath $StartInfo.RedirectStandardError = $true $StartInfo.RedirectStandardOutput = $true $StartInfo.UseShellExecute = $false - $StartInfo.Arguments = @("-o `"$TmpMkvPath`"", "-s !$($SubIDsToRemove -join ',')", "`"$($episode.FilePath)`"") + $StartInfo.Arguments = @("-o `"$TmpMkvPath`"", "-s !$($SubIDsToRemove -join ',')", "`"$($MkvFile.FilePath)`"") $Process = New-Object System.Diagnostics.Process $Process.StartInfo = $StartInfo $Process.Start() | Out-Null $stdout = $Process.StandardOutput.ReadToEnd() - # $stderr = $Process.StandardError.ReadToEnd() - # Write-Host "stdout: $stdout" - # Write-Host "stderr: $stderr" $Process.WaitForExit() - if ($Process.ExitCode -eq 2) { - Write-HTMLLog -Column1 'Exit Code:' -Column2 $($Process.ExitCode) -ColorBg 'Error' - Write-HTMLLog -Column1 'mkvmerge:' -Column2 $stdout -ColorBg 'Error' - Write-HTMLLog -Column1 'Result:' -Column2 'Failed' -ColorBg 'Error' - } elseif ($Process.ExitCode -eq 1) { - Write-HTMLLog -Column1 'Exit Code:' -Column2 $($Process.ExitCode) -ColorBg 'Error' - Write-HTMLLog -Column1 'mkvmerge:' -Column2 $stdout -ColorBg 'Error' - Write-HTMLLog -Column1 'Result:' -Column2 'Warning' -ColorBg 'Error' - } elseif ($Process.ExitCode -eq 0) { - # Overwrite original mkv after successful remux - Move-Item -LiteralPath $TmpMkvPath -Destination $($episode.FilePath) -Force - # Write-HTMLLog -Column1 "Removed:" -Column2 "$($SubIDsToRemove.Count) unwanted subtitle languages" - } else { - Write-HTMLLog -Column1 'Exit Code:' -Column2 $($Process.ExitCode) -ColorBg 'Error' - Write-HTMLLog -Column1 'mkvmerge:' -Column2 $stdout -ColorBg 'Error' - Write-HTMLLog -Column1 'Result:' -Column2 'Warning' -ColorBg 'Error' + + switch ($process.ExitCode) { + 0 { + Move-Item -LiteralPath $TmpMkvPath -Destination $($MkvFile.FilePath) -Force + } + 1 { + Write-HTMLLog -Column1 'Exit Code:' -Column2 $($Process.ExitCode) -ColorBg 'Error' + Write-HTMLLog -Column1 'mkvmerge:' -Column2 $stdout -ColorBg 'Error' + Write-HTMLLog -Column1 'Result:' -Column2 'Warning' -ColorBg 'Error' + } + default { + Write-HTMLLog -Column1 'Exit Code:' -Column2 $($Process.ExitCode) -ColorBg 'Error' + Write-HTMLLog -Column1 'mkvmerge:' -Column2 $stdout -ColorBg 'Error' + Write-HTMLLog -Column1 'Result:' -Column2 'Failed' -ColorBg 'Error' + } } } } @@ -234,18 +234,22 @@ function Start-MKVSubtitleStrip { # Rename extracted subs to correct 2 county code based on $LanguageCodes if ($SubsExtracted) { $SrtFiles = Get-ChildItem -LiteralPath $Source -Recurse -Filter '*.srt' + foreach ($srt in $SrtFiles) { - $FileDirectory = $srt.Directory - $FilePath = $srt.FullName - $FileName = $srt.Name - foreach ($LanguageCode in $Config.LanguageCodes) { - $FileNameNew = $FileName.Replace(".$($LanguageCode.alpha3).", ".$($LanguageCode.alpha2).") - $ReplacementWasMade = $FileName -cne $FileNameNew - if ($ReplacementWasMade) { - $Destination = Join-Path -Path $FileDirectory -ChildPath $FileNameNew - Move-Item -LiteralPath $FilePath -Destination $Destination -Force - break - } + $SrtDirectory = $srt.Directory + $SrtPath = $srt.FullName + $SrtName = $srt.Name + + # Extract the language code from the file name + $languageCode = $srt -replace '.*\.([a-zA-Z]+)\.srt$', '$1' + + # Check if the language code exists in the lookup table + if ($LanguageCodeLookup.ContainsKey($languageCode)) { + $SrtNameNew = $SrtName -replace "\.$languageCode\.srt$", ".$($LanguageCodeLookup[$languageCode]).srt" + $Destination = Join-Path -Path $SrtDirectory -ChildPath $SrtNameNew + Move-Item -LiteralPath $SrtPath -Destination $Destination -Force + } else { + Write-HTMLLog -Column2 "Language code $languageCode not found in the 3 letter lookup table." -ColorBg 'Error' } } if ($TotalSubsToExtract -gt 0) { diff --git a/functions/Start-MP4ToMKVRemux.ps1 b/functions/Start-MP4ToMKVRemux.ps1 index 468b0bf..903cef7 100644 --- a/functions/Start-MP4ToMKVRemux.ps1 +++ b/functions/Start-MP4ToMKVRemux.ps1 @@ -1,38 +1,39 @@ +<# +.SYNOPSIS + Remuxes MP4 files to MKV using MKVMerge. +.DESCRIPTION + This function remuxes MP4 files to MKV format using MKVMerge. + It deletes the original MP4 file after successful remux and + overwrites the original MKV file. +.PARAMETER Source + Specifies the path to the directory containing MP4 files to remux. +.PARAMETER MKVMergePath + Specifies the path to the MKVMerge executable. +.OUTPUTS + Outputs the remuxed MKV files to the specified directory. +.EXAMPLE + Start-MP4ToMKVRemux -Source "C:\Videos" -MKVMergePath "C:\Program Files\MKVToolNix\mkvmerge.exe" + # Remuxes all MP4 files in the "C:\Videos" directory using MKVMerge. +#> function Start-MP4ToMKVRemux { - <# - .SYNOPSIS - Converts MP4 video files to MKV format using mkvmerge. - - .DESCRIPTION - This function remuxes MP4 video files to MKV format by extracting available tracks using mkvmerge. It deletes the original MP4 file upon successful conversion and saves the resulting MKV file. - - .PARAMETER Source - Specifies the MP4 video file(s) to be converted to MKV format. - - .EXAMPLE - Start-MP4ToMKVRemux -VideoFileObjects "C:\Videos\example.mp4" - Remuxes the specified MP4 file to MKV format. - - .OUTPUTS - This function does not return any specific output. It performs the conversion process and logs the result in an HTML log file. - #> [CmdletBinding()] param( - [Parameter( - Mandatory = $true - )] - $VideoFileObjects + [Parameter(Mandatory = $true)] + [string]$Source, + + [Parameter(Mandatory = $true)] + [string]$MKVMergePath ) # Make sure needed functions are available otherwise try to load them. - $commands = 'Write-HTMLLog' - foreach ($commandName in $commands) { - if (!($command = Get-Command $commandName -ErrorAction SilentlyContinue)) { - Try { - . $PSScriptRoot\$commandName.ps1 - Write-Host "$commandName Function loaded." -ForegroundColor Green - } Catch { - Write-Error -Message "Failed to import $commandName function: $_" + $functionsToLoad = @('Write-HTMLLog') + foreach ($functionName in $functionsToLoad) { + if (-not (Get-Command $functionName -ErrorAction SilentlyContinue)) { + try { + . "$PSScriptRoot\$functionName.ps1" + Write-Host "$functionName function loaded." -ForegroundColor Green + } catch { + Write-Error "Failed to import $functionName function: $_" exit 1 } } @@ -40,50 +41,47 @@ function Start-MP4ToMKVRemux { # Start Write-HTMLLog -Column1 '*** Remux MP4 to MKV ***' -Header - $VideoFileObjects | ForEach-Object { - Get-ChildItem -LiteralPath $_.FullName | ForEach-Object { - $fileName = $_.BaseName - $filePath = $_.FullName - $fileRoot = $_.Directory - # Start the json export with MKVMerge on the available tracks - $TmpFileName = $fileName + '.tmp' - $TmpMkvPath = Join-Path $fileRoot $TmpFileName - $StartInfo = New-Object System.Diagnostics.ProcessStartInfo - $StartInfo.FileName = $MKVMergePath - $StartInfo.RedirectStandardError = $true - $StartInfo.RedirectStandardOutput = $true - $StartInfo.UseShellExecute = $false - $StartInfo.Arguments = @("-o `"$TmpMkvPath`"", "`"$filePath`"") - $Process = New-Object System.Diagnostics.Process - $Process.StartInfo = $StartInfo - $Process.Start() | Out-Null - $stdout = $Process.StandardOutput.ReadToEnd() - # $stderr = $Process.StandardError.ReadToEnd() - # Write-Host "stdout: $stdout" - # Write-Host "stderr: $stderr" - $Process.WaitForExit() - if ($Process.ExitCode -eq 2) { - Write-HTMLLog -Column1 'Exit Code:' -Column2 $($Process.ExitCode) -ColorBg 'Error' - Write-HTMLLog -Column1 'mkvmerge:' -Column2 $stdout -ColorBg 'Error' - Write-HTMLLog -Column1 'Result:' -Column2 'Failed' -ColorBg 'Error' - } elseif ($Process.ExitCode -eq 1) { - Write-HTMLLog -Column1 'Exit Code:' -Column2 $($Process.ExitCode) -ColorBg 'Error' - Write-HTMLLog -Column1 'mkvmerge:' -Column2 $stdout -ColorBg 'Error' - Write-HTMLLog -Column1 'Result:' -Column2 'Warning' -ColorBg 'Error' - } elseif ($Process.ExitCode -eq 0) { + Get-ChildItem -LiteralPath $Source -Recurse -Filter '*.mp4' | Where-Object { $_.DirectoryName -notlike "*\Sample" } | ForEach-Object { + $fileName = $_.BaseName + $filePath = $_.FullName + $fileRoot = $_.Directory + + # Start MKVMerge to remux MP4 to MKV + $TmpFileName = $fileName + '.tmp' + $TmpMkvPath = Join-Path $fileRoot $TmpFileName + $StartInfo = New-Object System.Diagnostics.ProcessStartInfo + $StartInfo.FileName = $MKVMergePath + $StartInfo.RedirectStandardError = $true + $StartInfo.RedirectStandardOutput = $true + $StartInfo.UseShellExecute = $false + $StartInfo.Arguments = @("-o `"$TmpMkvPath`"", "`"$filePath`"") + $Process = New-Object System.Diagnostics.Process + $Process.StartInfo = $StartInfo + $Process.Start() | Out-Null + $stdout = $Process.StandardOutput.ReadToEnd() + $Process.WaitForExit() + + switch ($process.ExitCode) { + 0 { # Delete original MP4 Remove-Item -Force -LiteralPath $filePath + # Overwrite original mkv after successful remux - Rename-Item -LiteralPath $TmpMkvPath -NewName $($fileName + ".mkv") - # Write-HTMLLog -Column1 "Removed:" -Column2 "$($SubIDsToRemove.Count) unwanted subtitle languages" - } else { - Write-HTMLLog -Column1 'Exit Code:' -Column2 $($Process.ExitCode) -ColorBg 'Error' + Rename-Item -LiteralPath $tmpMkvPath -NewName "$fileName.mkv" + Write-HTMLLog -Column1 'Remuxed:' -Column2 "$fileName.mp4" + } + 1 { + Write-HTMLLog -Column1 'Exit Code:' -Column2 $process.ExitCode -ColorBg 'Warning' + Write-HTMLLog -Column1 'mkvmerge:' -Column2 $stdout -ColorBg 'Warning' + Write-HTMLLog -Column1 'Result:' -Column2 'Warning' -ColorBg 'Warning' + } + default { + Write-HTMLLog -Column1 'Exit Code:' -Column2 $process.ExitCode -ColorBg 'Error' Write-HTMLLog -Column1 'mkvmerge:' -Column2 $stdout -ColorBg 'Error' - Write-HTMLLog -Column1 'Result:' -Column2 'Warning' -ColorBg 'Error' + Write-HTMLLog -Column1 'Result:' -Column2 'Failed' -ColorBg 'Error' } - - } } + Write-HTMLLog -Column1 'Result:' -Column2 'Successful' -ColorBg 'Success' } diff --git a/functions/Start-OpenSubtitlesDownload.ps1 b/functions/Start-OpenSubtitlesDownload.ps1 new file mode 100644 index 0000000..95929c4 --- /dev/null +++ b/functions/Start-OpenSubtitlesDownload.ps1 @@ -0,0 +1,390 @@ +<# +.SYNOPSIS + Downloads missing subtitles from OpenSubtitle.com. +.DESCRIPTION + This function searches for and downloads missing subtitles for specified video files + from OpenSubtitle.com. It supports downloading subtitles in multiple languages. +.PARAMETER Source + The source directory containing the video files for which subtitles are to be downloaded. +.PARAMETER OpenSubUser + The username for accessing the OpenSubtitle API. +.PARAMETER OpenSubPass + The password for accessing the OpenSubtitle API. +.PARAMETER OpenSubAPI + The API key for accessing the OpenSubtitle API. +.PARAMETER OpenSubHearing_impaired + Indicates whether to include subtitles for the hearing impaired (include/exclude/only). +.PARAMETER OpenSubForeign_parts_only + Indicates whether to include subtitles for foreign parts only (include/exclude/only). +.PARAMETER OpenSubMachine_translated + Indicates whether to include machine-translated subtitles (include/exclude/only). +.PARAMETER OpenSubAI_translated + Indicates whether to include AI-translated subtitles (include/exclude/only). +.PARAMETER WantedLanguages + An array of language codes specifying the desired subtitle languages. +.PARAMETER Type + The type of the video files (e.g., movie, episode). + +.OUTPUTS + This function outputs a log of downloaded and failed subtitles along with language-specific counts + and the remaining downloads for the day. + +.EXAMPLE + Start-OpenSubtitlesDownload -Source "C:\Videos" -OpenSubUser "username" -OpenSubPass "password" + -OpenSubAPI "APIKey" -OpenSubHearing_impaired "exclude" -OpenSubForeign_parts_only "exclude" + -OpenSubMachine_translated "exclude" -OpenSubAI_translated "exclude" -WantedLanguages @("en", "fr") + -Type "movie" + Searches for and downloads missing subtitles for video files in the "C:\Videos" directory, + excluding hearing impaired and foreign parts only subtitles, and including English and Spanish subtitles. +#> + +function Start-OpenSubtitlesDownload { + param( + [Parameter(Mandatory = $true)] + [string]$Source, + + [Parameter(Mandatory = $true)] + [string]$OpenSubUser, + + [Parameter(Mandatory = $true)] + [string]$OpenSubPass, + + [Parameter(Mandatory = $true)] + [string]$OpenSubAPI, + + [Parameter(Mandatory = $true)] + [ValidateSet("include", "exclude", "only")] + [string]$OpenSubHearing_impaired, + + [Parameter(Mandatory = $true)] + [ValidateSet("include", "exclude", "only")] + [string]$OpenSubForeign_parts_only, + + [Parameter(Mandatory = $true)] + [ValidateSet("include", "exclude", "only")] + [string]$OpenSubMachine_translated, + + [Parameter(Mandatory = $true)] + [ValidateSet("include", "exclude", "only")] + [string]$OpenSubAI_translated, + + [Parameter(Mandatory = $true)] + [array]$WantedLanguages, + + [Parameter(Mandatory = $true)] + [ValidateSet("episode", "movie")] + [string]$Type + ) + + # Make sure needed functions are available otherwise try to load them. + $functionsToLoad = @('Write-HTMLLog') + foreach ($functionName in $functionsToLoad) { + if (-not (Get-Command $functionName -ErrorAction SilentlyContinue)) { + try { + . "$PSScriptRoot\$functionName.ps1" + Write-Host "$functionName function loaded." -ForegroundColor Green + } catch { + Write-Error "Failed to import $functionName function: $_" + exit 1 + } + } + } + + # Function to connect to the OpenSubtitle API + function Connect-OpenSubtitleAPI { + param( + [string]$username, + [string]$password, + [string]$APIKey + ) + + # Set headers + $headers = @{ + "Content-Type" = "application/json" + "User-Agent" = "Torrentscript" + "Accept" = "application/json" + "Api-Key" = $APIKey + } + + # Set body + $body = @{ + username = $username + password = $password + } | ConvertTo-Json + + try { + # Make requests + $response = Invoke-RestMethod -Uri 'https://api.opensubtitles.com/api/v1/login' -Method POST -Headers $headers -ContentType 'application/json' -Body $body + # Check for successful login + if ($response.status -eq 200) { + return $response.token + } else { + Write-HTMLLog -Column1 'OpenSubs:' -Column2 "Login failed:" -ColorBg 'Error' + Write-HTMLLog -Column2 "$($response.status)" -ColorBg 'Error' + return $null + } + } catch { + Write-HTMLLog -Column1 'OpenSubs:' -Column2 "Error occurred while logging in:" -ColorBg 'Error' + Write-HTMLLog -Column2 "$($_.Exception.Message)" -ColorBg 'Error' + return $null + } + } + + # Function to disconnect from the OpenSubtitle API + function Disconnect-OpenSubtitleAPI { + param( + [string]$APIKey, + [string]$token + ) + + # Set headers + $headers = @{ + "User-Agent" = "Torrentscript" + "Accept" = "application/json" + "Api-Key" = $APIKey + "Authorization" = "Bearer $token" + } + + try { + # Make request + $response = Invoke-RestMethod -Uri 'https://api.opensubtitles.com/api/v1/logout' -Method DELETE -Headers $headers + + # Check for successful logout + if ($response.status -eq 200) { + Write-Host "Logout successful: $($response.message)" + } else { + Write-Host "Logout failed: $($response.status)" + } + } catch { + Write-Host "Error occurred while logging out: $_" + } + } + + # Function to search for subtitles + function Search-Subtitles { + param( + [string]$type, + [string]$query, + [string]$languages, + [string]$moviehash, + [string]$APIKey, + [string]$hearing_impaired, + [string]$foreign_parts_only, + [string]$machine_translated, + [string]$ai_translated + ) + + # Set headers + $headers = @{ + "User-Agent" = "Torrentscript" + "Api-Key" = $APIKey + } + + # Build the URI with query parameters + $uri = "https://api.opensubtitles.com/api/v1/subtitles?type=$type&query=$query&languages=$languages&moviehash=$moviehash&hearing_impaired=$hearing_impaired&foreign_parts_only=$foreign_parts_only&machine_translated=$machine_translated&ai_translated=$ai_translated" + + try { + # Make request + $response = Invoke-RestMethod -Uri $uri -Method GET -Headers $headers + + # Check for successful response + if ($response) { + # Initialize a hashtable to store subtitle IDs for each language + $subtitleInfo = @{ + VideoFileName = $query + SubtitleIds = @{} + } + + # Loop through the data to find the IDs for each language + foreach ($subtitle in $response.data) { + $language = $subtitle.attributes.language + + # Check if the language key exists in the hashtable + if (-not $subtitleInfo.SubtitleIds.ContainsKey($language)) { + $subtitleInfo.SubtitleIds[$language] = $subtitle.attributes.files[0].file_id + } + } + + # Return the hashtable containing subtitle IDs for each language + return $subtitleInfo + } else { + # No subtitles found for $query + return $null + } + } catch { + Write-HTMLLog -Column1 'OpenSubs:' -Column2 "Error occurred while searching for subtitles:" -ColorBg 'Error' + Write-HTMLLog -Column2 "$($_.Exception.Message)" -ColorBg 'Error' + return $null + } + } + + # Function to download all subtitles missing + function Save-AllSubtitles { + param( + [hashtable]$subtitleInfo, + [string]$APIKey, + [string]$token, + [string]$baseDirectory + ) + + # Initialize counters + $downloadedCount = 0 + $failedCount = 0 + $languageCounts = @{} + + # Set headers + $headers = @{ + "User-Agent" = "Torrentscript" + "Content-Type" = "application/json" + "Accept" = "application/json" + "Api-Key" = $APIKey + "Authorization" = "Bearer $token" + } + + # Iterate through each language in the subtitle info + foreach ($language in $subtitleInfo.SubtitleIds.Keys) { + $subtitleId = $subtitleInfo.SubtitleIds[$language] + $videoFileName = $subtitleInfo.VideoFileName + $subtitleFileName = "$baseDirectory\$videoFileName.$language.srt" + + # Check if the subtitle file already exists + if (-not (Test-Path $subtitleFileName)) { + # Build the body for the request + $body = @{ + file_id = $subtitleId + } | ConvertTo-Json + + try { + # Make request to download the subtitle + $response = Invoke-RestMethod -Uri 'https://api.opensubtitles.com/api/v1/download' -Method POST -Headers $headers -ContentType 'application/json' -Body $body + + # Check if the link is present in the response + if ($response.link) { + # Download the subtitle file + Invoke-WebRequest -Uri $response.link -OutFile $subtitleFileName + + # Increment downloaded count + $downloadedCount++ + + # Increment language-specific count + if (-not $languageCounts.ContainsKey($language)) { + $languageCounts[$language] = 1 + } else { + $languageCounts[$language]++ + } + } else { + Write-HTMLLog -Column1 'OpenSubs:' -Column2 "Failed to download subtitle for language: $language" -ColorBg 'Error' + # Increment failed count + $failedCount++ + } + } catch { + Write-HTMLLog -Column1 'OpenSubs:' -Column2 "Error occurred while downloading subtitle for language $($language):" -ColorBg 'Error' + Write-HTMLLog -Column2 "$($_.Exception.Message)" -ColorBg 'Error' + # Increment failed count + $failedCount++ + } + } + } + + # Return the counts and language-specific counts + return @{ + Downloaded = $downloadedCount + Failed = $failedCount + LanguageCounts = $languageCounts + RemainingDownloads = $response.remaining + } + } + + + # Function to get video hash + function Get-VideoHash([string]$path) { + $dataLength = 65536 + + function LongSum([UInt64]$a, [UInt64]$b) { + [UInt64](([Decimal]$a + $b) % ([Decimal]([UInt64]::MaxValue) + 1)) + } + + function StreamHash([IO.Stream]$stream) { + $hashLength = 8 + [UInt64]$lhash = 0 + [byte[]]$buffer = New-Object byte[] $hashLength + $i = 0 + while ( ($i -lt ($dataLength / $hashLength)) -and ($stream.Read($buffer, 0, $hashLength) -gt 0) ) { + $i++ + $lhash = LongSum $lhash ([BitConverter]::ToUInt64($buffer, 0)) + } + $lhash + } + + try { + $stream = [IO.File]::OpenRead($path) + [UInt64]$lhash = $stream.Length + $lhash = LongSum $lhash (StreamHash $stream) + $stream.Position = [Math]::Max(0L, $stream.Length - $dataLength) + $lhash = LongSum $lhash (StreamHash $stream) + $hash = "{0:X}" -f $lhash + if ($hash.Length -lt 16) { + $hash = ("0" * (16 - $hash.Length)) + $hash + } + $hash + } finally { + $stream.Close() + } + } + + #* Start the search and download of the subtitles + try { + Write-HTMLLog -Column1 '*** Download missing subs from OpenSubtitle.com ***' -Header + $token = Connect-OpenSubtitleAPI -username $OpenSubUser -password $OpenSubPass -APIKey $OpenSubAPI + + if ($token) { + $videoFiles = @(Get-ChildItem -LiteralPath $ProcessPathFull -Recurse -Filter '*.mkv' | Where-Object { $_.DirectoryName -notlike "*\Sample" }) + + foreach ($videoFile in $videoFiles) { + $videoHash = Get-VideoHash $videoFile.FullName + + $languageString = $WantedLanguages -join ',' + + $queryParams = @{ + query = $videoFile.BaseName + APIKey = $OpenSubAPI + type = $Type + moviehash = $videoHash + hearing_impaired = $OpenSubHearing_impaired + foreign_parts_only = $OpenSubForeign_parts_only + machine_translated = $OpenSubMachine_translated + ai_translated = $OpenSubAI_translated + languages = $languageString + } + $subtitleInfo = Search-Subtitles @queryParams + + # Call Save-AllSubtitles and capture the counts + $subtitlesCounts = Save-AllSubtitles -subtitleInfo $subtitleInfo -APIKey $OpenSubAPI -token $token -baseDirectory $videoFile.DirectoryName + } + + if ($($subtitlesCounts.Downloaded) -gt 0) { + # Log language-specific counts + foreach ($language in $subtitlesCounts.LanguageCounts.Keys) { + Write-HTMLLog -Column1 "Downloaded:" -Column2 "$($subtitlesCounts.LanguageCounts[$language]) in $($language.ToUpper())" + } + # Log the counts + Write-HTMLLog -Column1 "Downloaded:" -Column2 "$($subtitlesCounts.Downloaded) Total" + Write-HTMLLog -Column2 "Remaining downloads today: $($subtitlesCounts.RemainingDownloads)" + + if ($($subtitlesCounts.Failed) -gt 0) { + Write-HTMLLog -Column1 "Failed:" -Column2 "$($subtitlesCounts.Failed) failed to download" -ColorBg 'Error' + Write-HTMLLog -Column1 'Result:' -Column2 'Failed' -ColorBg 'Error' + } else { + Write-HTMLLog -Column1 'Result:' -Column2 'Successful' -ColorBg 'Success' + } + } else { + Write-HTMLLog -Column1 'Result:' -Column2 'No downloads found or needed' -ColorBg 'Success' + } + + Disconnect-OpenSubtitleAPI -APIKey $OpenSubAPI -token $token + } + } catch { + Write-HTMLLog -Column1 'OpenSubs:' -Column2 "Error occurred:" -ColorBg 'Error' + Write-HTMLLog -Column2 "$($_.Exception.Message)" -ColorBg 'Error' + } +} \ No newline at end of file diff --git a/functions/Start-RoboCopy.ps1 b/functions/Start-RoboCopy.ps1 deleted file mode 100644 index 5125f12..0000000 --- a/functions/Start-RoboCopy.ps1 +++ /dev/null @@ -1,162 +0,0 @@ -function Start-RoboCopy { - <# - .SYNOPSIS - RoboCopy wrapper - .DESCRIPTION - Wrapper for RoboCopy since it it way faster to copy that way. - This Function relies on Write-HTMLLog Function and Stop-Script Function - .PARAMETER Source - Source path - .PARAMETER Destination - Destination path - .PARAMETER File - File patern to copy *.* or name.ext - This allows for folder or single filecopy - .EXAMPLE - Start-RoboCopy -Source 'C:\Temp\Source' -Destination 'C:\Temp\Destination' -File '*.*' - Start-RoboCopy -Source 'C:\Temp\Source' -Destination 'C:\Temp\Destination' -File 'file.ext' - #> - [CmdletBinding()] - param( - [Parameter( - Mandatory = $true - )] - [string]$Source, - - [Parameter( - Mandatory = $true - )] - [string]$Destination, - - [Parameter( - Mandatory = $true - )] - [string]$File - ) - - # Make sure needed functions are available otherwise try to load them. - $commands = 'Write-HTMLLog', 'Stop-Script', 'Format-Size' - foreach ($commandName in $commands) { - if (!($command = Get-Command $commandName -ErrorAction SilentlyContinue)) { - Try { - . $PSScriptRoot\$commandName.ps1 - Write-Host "$commandName Function loaded." -ForegroundColor Green - } Catch { - Write-Error -Message "Failed to import $commandName function: $_" - exit 1 - } - } - } - - # Start - if ($File -ne '*.*') { - $options = @('/R:1', '/W:1', '/J', '/NP', '/NP', '/NJH', '/NFL', '/NDL', '/MT8') - } elseif ($File -eq '*.*') { - $options = @('/R:1', '/W:1', '/E', '/J', '/NP', '/NJH', '/NFL', '/NDL', '/MT8') - } - - $cmdArgs = @("`"$Source`"", "`"$Destination`"", "`"$File`"", $options) - - # executing unrar command - Write-HTMLLog -Column1 'Starting:' -Column2 'Copy files' - try { - # executing Robocopy command - $Output = robocopy @cmdArgs - } catch { - Write-Host 'Exception:' $_.Exception.Message -ForegroundColor Red - Write-Host 'RoboCopy not found' -ForegroundColor Red - exit 1 - } - - - foreach ($line in $Output) { - switch -Regex ($line) { - # Dir metrics - '^\s+Dirs\s:\s*' { - # Example: Dirs : 35 0 0 0 0 0 - $dirs = $_.Replace('Dirs :', '').Trim() - # Now remove the white space between the values.' - $dirs = $dirs -split '\s+' - - # Assign the appropriate column to values. - $TotalDirs = $dirs[0] - $CopiedDirs = $dirs[1] - $FailedDirs = $dirs[4] - } - # File metrics - '^\s+Files\s:\s[^*]' { - # Example: Files : 8318 0 8318 0 0 0 - $files = $_.Replace('Files :', '').Trim() - # Now remove the white space between the values.' - $files = $files -split '\s+' - - # Assign the appropriate column to values. - $TotalFiles = $files[0] - $CopiedFiles = $files[1] - $FailedFiles = $files[4] - } - # Byte metrics - '^\s+Bytes\s:\s*' { - # Example: Bytes : 1.607 g 0 1.607 g 0 0 0 - $bytes = $_.Replace('Bytes :', '').Trim() - # Now remove the white space between the values.' - $bytes = $bytes -split '\s+' - - # The raw text from the log file contains a k,m,or g after the non zero numbers. - # This will be used as a multiplier to determine the size in kb. - $counter = 0 - $tempByteArray = 0, 0, 0, 0, 0, 0 - $tempByteArrayCounter = 0 - foreach ($column in $bytes) { - if ($column -eq 'k') { - $tempByteArray[$tempByteArrayCounter - 1] = '{0:N2}' -f ([single]($bytes[$counter - 1]) * 1024) - $counter += 1 - } elseif ($column -eq 'm') { - $tempByteArray[$tempByteArrayCounter - 1] = '{0:N2}' -f ([single]($bytes[$counter - 1]) * 1048576) - $counter += 1 - } elseif ($column -eq 'g') { - $tempByteArray[$tempByteArrayCounter - 1] = '{0:N2}' -f ([single]($bytes[$counter - 1]) * 1073741824) - $counter += 1 - } else { - $tempByteArray[$tempByteArrayCounter] = $column - $counter += 1 - $tempByteArrayCounter += 1 - } - } - # Assign the appropriate column to values. - $TotalSize = Format-Size -SizeInBytes ([double]::Parse($tempByteArray[0])) - $CopiedSize = Format-Size -SizeInBytes ([double]::Parse($tempByteArray[1])) - $FailedSize = Format-Size -SizeInBytes ([double]::Parse($tempByteArray[4])) - # array columns 2,3, and 5 are available, but not being used currently. - } - # Speed metrics - '^\s+Speed\s:.*sec.$' { - # Example: Speed : 120.816 Bytes/min. - $speed = $_.Replace('Speed :', '').Trim() - $speed = $speed.Replace('Bytes/sec.', '').Trim() - # Remove any dots in the number - $speed = $speed.Replace('.', '').Trim() - # Assign the appropriate column to values. - $speed = Format-Size -SizeInBytes $speed - } - } - } - - - if ($FailedDirs -gt 0 -or $FailedFiles -gt 0) { - Write-HTMLLog -Column1 'Dirs' -Column2 "$TotalDirs Total" -ColorBg 'Error' - Write-HTMLLog -Column1 'Dirs' -Column2 "$FailedDirs Failed" -ColorBg 'Error' - Write-HTMLLog -Column1 'Files:' -Column2 "$TotalFiles Total" -ColorBg 'Error' - Write-HTMLLog -Column1 'Files:' -Column2 "$FailedFiles Failed" -ColorBg 'Error' - Write-HTMLLog -Column1 'Size:' -Column2 "$TotalSize Total" -ColorBg 'Error' - Write-HTMLLog -Column1 'Size:' -Column2 "$FailedSize Failed" -ColorBg 'Error' - Write-HTMLLog -Column1 'Result:' -Column2 'Failed' -ColorBg 'Error' - Stop-Script -ExitReason "Copy Error: $DownloadLabel - $DownloadName" - } else { - Write-HTMLLog -Column1 'Dirs:' -Column2 "$CopiedDirs Copied" - Write-HTMLLog -Column1 'Files:' -Column2 "$CopiedFiles Copied" - Write-HTMLLog -Column1 'Size:' -Column2 "$CopiedSize" - Write-HTMLLog -Column1 'Throughput:' -Column2 "$Speed/s" - Write-HTMLLog -Column1 'Result:' -Column2 'Successful' -ColorBg 'Success' - } -} \ No newline at end of file diff --git a/functions/Start-SubEdit.ps1 b/functions/Start-SubEdit.ps1 deleted file mode 100644 index 44075e3..0000000 --- a/functions/Start-SubEdit.ps1 +++ /dev/null @@ -1,70 +0,0 @@ -function Start-SubEdit { - <# - .SYNOPSIS - Start Subtitle Edit - - .DESCRIPTION - Start Subtitle Edit to clean up subtitles - - .PARAMETER Source - Path to process and clean subtitles - - .PARAMETER Files - The files to process, this will be *.srt typically - - .EXAMPLE - Start-SubEdit -File '*.srt' -Source 'C:\Temp\Episode' - - .NOTES - General notes - #> - [CmdletBinding()] - param ( - [Parameter( - Mandatory = $true - )] - [string]$Source, - - [Parameter( - Mandatory = $true - )] - [string]$Files - ) - - # Make sure needed functions are available otherwise try to load them. - $commands = 'Write-HTMLLog' - foreach ($commandName in $commands) { - if (!($command = Get-Command $commandName -ErrorAction SilentlyContinue)) { - Try { - . $PSScriptRoot\$commandName.ps1 - Write-Host "$commandName Function loaded." -ForegroundColor Green - } Catch { - Write-Error -Message "Failed to import $commandName function: $_" - exit 1 - } - } - } - # Start - - Write-HTMLLog -Column1 '*** Clean up Subtitles ***' -Header - $StartInfo = New-Object System.Diagnostics.ProcessStartInfo - $StartInfo.FileName = $SubtitleEditPath - $StartInfo.RedirectStandardError = $true - $StartInfo.RedirectStandardOutput = $true - $StartInfo.UseShellExecute = $false - $StartInfo.Arguments = @('/convert', "$Files", 'subrip', "/inputfolder`:`"$Source`"", '/overwrite', '/MergeSameTexts', '/fixcommonerrors', '/removetextforhi', '/fixcommonerrors', '/fixcommonerrors') - $Process = New-Object System.Diagnostics.Process - $Process.StartInfo = $StartInfo - $Process.Start() | Out-Null - $stdout = $Process.StandardOutput.ReadToEnd() - $stderr = $Process.StandardError.ReadToEnd() - $Process.WaitForExit() - if ($Process.ExitCode -gt 1) { - Write-HTMLLog -Column1 'Exit Code:' -Column2 $($Process.ExitCode) -ColorBg 'Error' - Write-HTMLLog -Column1 'Error:' -Column2 $stderr -ColorBg 'Error' - Write-HTMLLog -Column1 'Result:' -Column2 'Failed' -ColorBg 'Error' - Stop-Script -ExitReason "SubEdit Error: $DownloadLabel - $DownloadName" - } else { - Write-HTMLLog -Column1 'Result:' -Column2 'Successful' -ColorBg 'Success' - } -} \ No newline at end of file diff --git a/functions/Start-Subliminal.ps1 b/functions/Start-Subliminal.ps1 deleted file mode 100644 index 42190d6..0000000 --- a/functions/Start-Subliminal.ps1 +++ /dev/null @@ -1,87 +0,0 @@ -function Start-Subliminal { - <# - .SYNOPSIS - Start Subliminal to download subs - - .DESCRIPTION - Start Subliminal to download needed subtitles - - .PARAMETER Source - Path to files that need subtitles - - .EXAMPLE - Start-Subliminal -Source 'C:\Temp\Episode' - - .NOTES - General notes - #> - [CmdletBinding()] - param ( - [Parameter( - Mandatory = $true - )] - [string]$Source - ) - - # Make sure needed functions are available otherwise try to load them. - $commands = 'Write-HTMLLog' - foreach ($commandName in $commands) { - if (!($command = Get-Command $commandName -ErrorAction SilentlyContinue)) { - Try { - . $PSScriptRoot\$commandName.ps1 - Write-Host "$commandName Function loaded." -ForegroundColor Green - } Catch { - Write-Error -Message "Failed to import $commandName function: $_" - exit 1 - } - } - } - # Start - - Write-HTMLLog -Column1 '*** Download missing Subtitles ***' -Header - $StartInfo = New-Object System.Diagnostics.ProcessStartInfo - $StartInfo.FileName = $SubliminalPath - $StartInfo.RedirectStandardError = $true - $StartInfo.RedirectStandardOutput = $true - $StartInfo.UseShellExecute = $false - $StartInfo.Arguments = @('--opensubtitles', $OpenSubUser, $OpenSubPass, "--omdb $omdbAPI", 'download', '-r omdb', '-p opensubtitles', '-l eng', '-l nld', "`"$Source`"") - $Process = New-Object System.Diagnostics.Process - $Process.StartInfo = $StartInfo - $Process.Start() | Out-Null - $stdout = $Process.StandardOutput.ReadToEnd() - $stderr = $Process.StandardError.ReadToEnd() - $Process.WaitForExit() - # Write-Host $stdout - # Write-Host $stderr - if ($stdout -match '(\d+)(?=\s*video collected)') { - $VideoCollected = $Matches.0 - } - if ($stdout -match '(\d+)(?=\s*video ignored)') { - $VideoIgnored = $Matches.0 - } - if ($stdout -match '(\d+)(?=\s*error)') { - $VideoError = $Matches.0 - } - if ($stdout -match '(\d+)(?=\s*subtitle)') { - $SubsDownloaded = $Matches.0 - } - if ($stdout -match 'Some providers have been discarded due to unexpected errors') { - $SubliminalExitCode = 1 - } - if ($SubliminalExitCode -gt 0) { - Write-HTMLLog -Column1 'Exit Code:' -Column2 $($Process.ExitCode) -ColorBg 'Error' - Write-HTMLLog -Column1 'Error:' -Column2 $stderr -ColorBg 'Error' - Write-HTMLLog -Column1 'Result:' -Column2 'Failed' -ColorBg 'Error' - } else { - if ($SubsDownloaded -gt 0) { - # Write-HTMLLog -Column1 "Downloaded:" -Column2 "$SubsDownloaded Subtitles" - Write-HTMLLog -Column1 'Collected:' -Column2 "$VideoCollected Videos" - Write-HTMLLog -Column1 'Ignored:' -Column2 "$VideoIgnored Videos" - Write-HTMLLog -Column1 'Error:' -Column2 "$VideoError Videos" - Write-HTMLLog -Column1 'Downloaded:' -Column2 "$SubsDownloaded Subtitles" - Write-HTMLLog -Column1 'Result:' -Column2 'Successful' -ColorBg 'Success' - } else { - Write-HTMLLog -Column1 'Result:' -Column2 'No subs downloaded with Subliminal' - } - } -} \ No newline at end of file diff --git a/functions/Start-SubtitleEdit.ps1 b/functions/Start-SubtitleEdit.ps1 new file mode 100644 index 0000000..bc6672c --- /dev/null +++ b/functions/Start-SubtitleEdit.ps1 @@ -0,0 +1,114 @@ +<# +.SYNOPSIS + Starts Subtitle Edit to clean up subtitles. +.DESCRIPTION + This function initiates Subtitle Edit, a free and open-source subtitle editor, + to clean up subtitles for specified files. +.PARAMETER Source + Specifies the source directory of the subtitle files. +.PARAMETER Files + Specifies the file or wildcard pattern for subtitle files to process. +.PARAMETER SubtitleEditPath + Specifies the path to the Subtitle Edit executable. +.PARAMETER DownloadLabel + Specifies the label associated with the download. +.PARAMETER DownloadName + Specifies the name of the downloaded content. +.OUTPUTS +None. +.EXAMPLE + Start-SubtitleEdit -Source "C:\Subtitles" -Files "*.srt" -SubtitleEditPath "C:\SubtitleEdit.exe" -DownloadLabel "TV" -DownloadName "Episode1" + # Initiates Subtitle Edit to clean up subtitles for TV Episode1. +.EXAMPLE + Start-SubtitleEdit -Source "C:\Movies" -Files "*.sub" -SubtitleEditPath "C:\SubtitleEdit.exe" -DownloadLabel "Movie" -DownloadName "FilmA" + # Initiates Subtitle Edit to clean up subtitles for Movie FilmA. +#> +function Start-SubtitleEdit { + + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [string]$Source, + + [Parameter(Mandatory = $true)] + [string]$Files, + + [Parameter(Mandatory = $true)] + [string]$SubtitleEditPath, + + [Parameter(Mandatory = $true)] + [string]$DownloadLabel, + + [Parameter(Mandatory = $true)] + [string]$DownloadName + ) + + # Make sure needed functions are available otherwise try to load them. + $functionsToLoad = @('Write-HTMLLog') + foreach ($functionName in $functionsToLoad) { + if (-not (Get-Command $functionName -ErrorAction SilentlyContinue)) { + try { + . "$PSScriptRoot\$functionName.ps1" + Write-Host "$functionName function loaded." -ForegroundColor Green + } catch { + Write-Error "Failed to import $functionName function: $_" + exit 1 + } + } + } + # Start + + Write-HTMLLog -Column1 '*** Clean up Subtitles ***' -Header + $StartInfo = New-Object System.Diagnostics.ProcessStartInfo + $StartInfo.FileName = $SubtitleEditPath + $StartInfo.RedirectStandardError = $true + $StartInfo.RedirectStandardOutput = $true + $StartInfo.UseShellExecute = $false + $StartInfo.Arguments = @('/convert', "$Files", 'subrip', "/inputfolder`:`"$Source`"", '/overwrite', '/MergeSameTexts', '/fixcommonerrors', '/removetextforhi', '/fixcommonerrors', '/fixcommonerrors') + $Process = New-Object System.Diagnostics.Process + $Process.StartInfo = $StartInfo + $Process.Start() | Out-Null + $stdout = $Process.StandardOutput.ReadToEnd() + # $stderr = $Process.StandardError.ReadToEnd() + $Process.WaitForExit() + + # Regular expressions to extract information + $versionRegex = "Subtitle Edit (\d+\.\d+\.\d+)" + $filesConvertedRegex = "(\d+) file\(s\) converted" + $timeTakenRegex = "(\d+:\d+:\d+\.\d+)" + + # Extract information using regular expressions + $matchResult = $stdout -match $versionRegex + if ($matchResult) { + $version = $matches[1] + } + + $matchResult = $stdout -match $filesConvertedRegex + if ($matchResult) { + $filesConverted = $matches[1] + } + + $matchResult = $stdout -match $timeTakenRegex + if ($matchResult) { + $timeTaken = $matches[1] + # Convert the string to a TimeSpan object + $timeSpan = [TimeSpan]::Parse($timeTaken) + # Format the TimeSpan object as a string in a user-friendly way + $userFriendlyTime = $timeSpan.ToString("hh\:mm\:ss") + } + + switch ($process.ExitCode) { + 0 { + Write-HTMLLog -Column1 'SubtitleEdit:' -Column2 "V $version" + Write-HTMLLog -Column1 'Subtitles:' -Column2 "$filesConverted Converted" + Write-HTMLLog -Column1 'Time Taken:' -Column2 $userFriendlyTime + Write-HTMLLog -Column1 'Result:' -Column2 'Successful' -ColorBg 'Success' + } + default { + Write-HTMLLog -Column1 'Exit Code:' -Column2 $($Process.ExitCode) -ColorBg 'Error' + Write-HTMLLog -Column1 'Error:' -Column2 $stdout -ColorBg 'Error' + Write-HTMLLog -Column1 'Result:' -Column2 'Failed' -ColorBg 'Error' + Stop-Script -ExitReason "SubEdit Error: $DownloadLabel - $DownloadName" + } + } +} \ No newline at end of file diff --git a/functions/Start-UnRar.ps1 b/functions/Start-UnRar.ps1 index 3fdb22e..3e9b445 100644 --- a/functions/Start-UnRar.ps1 +++ b/functions/Start-UnRar.ps1 @@ -1,38 +1,48 @@ +<# +.SYNOPSIS + Start-UnRar function extracts RAR files using WinRAR. +.DESCRIPTION + This function extracts RAR files specified by the UnRarSourcePath to the UnRarTargetPath. + It logs the extraction process, handles errors, and stops the script if extraction fails. +.PARAMETER UnRarSourcePath + Specifies the path of the RAR file to be extracted. +.PARAMETER UnRarTargetPath + Specifies the target path where the contents of the RAR file will be extracted. +.PARAMETER DownloadLabel + Specifies the label associated with the download. +.PARAMETER DownloadName + Specifies the name of the download. +.OUTPUTS + Outputs the result of the extraction process. +.EXAMPLE + Start-UnRar -UnRarSourcePath "C:\Downloads\File.rar" -UnRarTargetPath "C:\Extracted" -DownloadLabel "TV" -DownloadName "Show1" + # Extracts File.rar to C:\Extracted and logs the process. +#> function Start-UnRar { - <# - .SYNOPSIS - Unrar file - .DESCRIPTION - Takes rar file and unrar them to target - .PARAMETER UnRarSourcePath - Path of rar file to extract - .PARAMETER UnRarTargetPath - Destination folder path - .EXAMPLE - Start-UnRar -UnRarSourcePath 'C:\Temp\Source\file.rar' -UnRarTargetPath 'C:\Temp\Destination' - #> [CmdletBinding()] Param( - [Parameter( - Mandatory = $true - )] + [Parameter(Mandatory = $true)] [string]$UnRarSourcePath, - [Parameter( - Mandatory = $true - )] - [string]$UnRarTargetPath + [Parameter(Mandatory = $true)] + [string]$UnRarTargetPath, + + [Parameter(Mandatory = $true)] + [string]$DownloadLabel, + + [Parameter(Mandatory = $true)] + [string]$DownloadName ) # Make sure needed functions are available otherwise try to load them. - $commands = 'Write-HTMLLog', 'Stop-Script' - foreach ($commandName in $commands) { - if (!($command = Get-Command $commandName -ErrorAction SilentlyContinue)) { - Try { - . $PSScriptRoot\$commandName.ps1 - Write-Host "$commandName Function loaded." -ForegroundColor Green - } Catch { - Write-Error -Message "Failed to import $commandName function: $_" + $functionsToLoad = @('Write-HTMLLog', 'Stop-Script') + foreach ($functionName in $functionsToLoad) { + if (-not (Get-Command $functionName -ErrorAction SilentlyContinue)) { + try { + . "$PSScriptRoot\$functionName.ps1" + Write-Host "$functionName function loaded." -ForegroundColor Green + } catch { + Write-Error "Failed to import $functionName function: $_" exit 1 } } @@ -51,10 +61,9 @@ function Start-UnRar { $Process = New-Object System.Diagnostics.Process $Process.StartInfo = $StartInfo $Process.Start() | Out-Null - # $stdout = $Process.StandardOutput.ReadToEnd() + $stdout = $Process.StandardOutput.ReadToEnd() $stderr = $Process.StandardError.ReadToEnd() - # Write-Host "stdout: $stdout" - # Write-Host "stderr: $stderr" + $Process.WaitForExit() if ($Process.ExitCode -gt 0) { Write-HTMLLog -Column1 'Exit Code:' -Column2 $($Process.ExitCode) -ColorBg 'Error' diff --git a/functions/Stop-Script.ps1 b/functions/Stop-Script.ps1 index 8c8d8dc..9b42319 100644 --- a/functions/Stop-Script.ps1 +++ b/functions/Stop-Script.ps1 @@ -1,17 +1,18 @@ +<# +.SYNOPSIS + Stop-Script function stops the script execution, logs the execution time, and sends an email notification. +.DESCRIPTION + This function is designed to stop script execution, record the execution time, and send an email notification. +.PARAMETER ExitReason + Mandatory parameter specifying the reason for stopping the script. +.EXAMPLE + Stop-Script -ExitReason "Script completed successfully" + Stops the script, logs execution time, and sends an email with the specified exit reason. +.NOTES + This functions relies heavily on variables that have been set in the main script, + they are not passed as parameters to this functions +#> function Stop-Script { - <# - .SYNOPSIS - Stops the script, removes Mutex and send log file. - - .DESCRIPTION - Stops the script and removes the Mutex. - - .PARAMETER ExitReason - Reason for exit. - - .EXAMPLE - Stop-Script -ExitReason "Script completed successfully." - #> [CmdletBinding()] Param( [Parameter(Mandatory = $true)] @@ -19,14 +20,14 @@ function Stop-Script { ) # Make sure needed functions are available otherwise try to load them. - $commands = 'Write-HTMLLog', 'Send-HtmlMail', 'Remove-Mutex' - foreach ($commandName in $commands) { - if (!($command = Get-Command $commandName -ErrorAction SilentlyContinue)) { - Try { - . $PSScriptRoot\$commandName.ps1 - Write-Host "$commandName Function loaded." -ForegroundColor Green - } Catch { - Write-Error -Message "Failed to import $commandName function: $_" + $functionsToLoad = @('Write-HTMLLog', 'Send-HtmlMail', 'Remove-Mutex') + foreach ($functionName in $functionsToLoad) { + if (-not (Get-Command $functionName -ErrorAction SilentlyContinue)) { + try { + . "$PSScriptRoot\$functionName.ps1" + Write-Host "$functionName function loaded." -ForegroundColor Green + } catch { + Write-Error "Failed to import $functionName function: $_" exit 1 } } @@ -34,10 +35,10 @@ function Stop-Script { # Start # Stop the Stopwatch - $StopWatch.Stop() + $ScriptTimer.Stop() - Write-HTMLLog -Column1 '*** Script Exection time ***' -Header - Write-HTMLLog -Column1 'Time Taken:' -Column2 $($StopWatch.Elapsed.ToString('mm\:ss')) + Write-HTMLLog -Column1 '*** Script Execution time ***' -Header + Write-HTMLLog -Column1 'Time Taken:' -Column2 $($ScriptTimer.Elapsed.ToString('mm\:ss')) Format-Table Write-Log -LogFile $LogFilePath diff --git a/functions/Test-Variable-Path.ps1 b/functions/Test-Variable-Path.ps1 index e89cdc0..72aae61 100644 --- a/functions/Test-Variable-Path.ps1 +++ b/functions/Test-Variable-Path.ps1 @@ -1,29 +1,36 @@ -function Test-Variable-Path { - <# - .SYNOPSIS - Test path to variables - - .DESCRIPTION - Test path to - - .PARAMETER Path - File path to test if exist - - .EXAMPLE +<# +.SYNOPSIS + Test path to variables. +.DESCRIPTION + This function tests the existence of a specified file path. +.PARAMETER Path + Specifies the file path to be tested for existence. +.INPUTS + Accepts a string representing the file path to be tested. +.EXAMPLE Test-Variable-Path -Path 'c:\Windows\notepad.exe' - - .NOTES - General notes - #> + # Checks if the specified file path exists. +#> +function Test-Variable-Path { [CmdletBinding()] param ( - [Parameter(Mandatory = $true)] + [Parameter( + Mandatory = $false, + ValueFromPipeline = $true + )] [string]$Path ) + + if ([string]::IsNullOrWhiteSpace($Path)) { + Write-Host "Path cannot be empty or null." -ForegroundColor Red + Write-Host 'Will now exit!' -ForegroundColor Red + Exit 1 + } + if (!(Test-Path -LiteralPath $Path)) { Write-Host "Cannot find: $Path" -ForegroundColor Red Write-Host "As defined in config" -ForegroundColor Red Write-Host 'Will now exit!' -ForegroundColor Red Exit 1 } -} \ No newline at end of file +} diff --git a/functions/Write-HTMLLog.ps1 b/functions/Write-HTMLLog.ps1 index 981dd84..1b14c56 100644 --- a/functions/Write-HTMLLog.ps1 +++ b/functions/Write-HTMLLog.ps1 @@ -1,26 +1,20 @@ -function Format-Table { - <# - .SYNOPSIS +<# +.SYNOPSIS Starts and stops Log file - - .DESCRIPTION - Will either initiate the Log Variable in memory and open the HTML Table or clos the table - - .PARAMETER Start +.DESCRIPTION + Will either initiate the Log Variable in memory and open the HTML Table or closes the table +.PARAMETER Start If defined indicated to open the HTML Table, without it the HTML table will be closed - - .EXAMPLE +.EXAMPLE Format-Table -Start Format-Table - - .NOTES +.NOTES General notes - #> +#> +function Format-Table { [CmdletBinding()] param ( - [Parameter( - Mandatory = $false - )] + [Parameter(Mandatory = $false)] [switch]$Start ) @@ -38,61 +32,44 @@ function Format-Table { } } -Function Write-HTMLLog { - <# - .SYNOPSIS +<# +.SYNOPSIS Add line to in memory log - - .DESCRIPTION - Adds a line to the in memory log file and based on the parameters will do formating - - .PARAMETER Column1 - Text to be put in first column, mandatory - - .PARAMETER Column2 +.DESCRIPTION + Adds a line to the in memory log file and based on the parameters will do formatting +.PARAMETER Column1 + Text to be put in first column, not mandatory +.PARAMETER Column2 Text to be put in the second column, not mandatory - - .PARAMETER Header +.PARAMETER Header Define that the Text from the parameter Column1 should be treated as new Header in the log table. If switch is defined Column2 is ignored - - .PARAMETER ColorBg +.PARAMETER ColorBg Background color of Table Cell, this is a switch indicating a Success or Error. If not defined the standard color will be used. - Success will get Green Table Cell color Error will get Red Table Cell Color - - .EXAMPLE +.EXAMPLE Write-HTMLLog -Column1 '*** Header of the table ***' -Header Write-HTMLLog -Column1 'Column1 Text' -Column2 'Column2 Text' Write-HTMLLog -Column1 'Exit Code:' -Column2 'Failed to do X' -ColorBg 'Error' Write-HTMLLog -Column1 'Result:' -Column2 'Successful' -ColorBg 'Success' - - .NOTES - General notes - #> +#> +Function Write-HTMLLog { [CmdletBinding()] Param( - [Parameter( - Mandatory = $true - )] + [Parameter(Mandatory = $false)] [string]$Column1, - [Parameter( - Mandatory = $false - )] + [Parameter(Mandatory = $false)] [string]$Column2, - [Parameter( - Mandatory = $false - )] + [Parameter(Mandatory = $false)] [switch]$Header, - [Parameter( - Mandatory = $false - )] + [Parameter(Mandatory = $false)] [ValidateSet( - 'Success', 'Error' + 'Success', + 'Error' )] [string]$ColorBg ) @@ -115,32 +92,28 @@ Function Write-HTMLLog { $global:Log += '' } } - Write-Output "$Column1 $Column2" + if ($Column1 -eq "") { + <# Action to perform if the condition is true #> + } + Write-Host $(($Column1, $Column2 | Where-Object { $_ }) -join ' ') + # Write-Host "$Column1 $Column2" } -function Write-Log { - <# - .SYNOPSIS +<# +.SYNOPSIS Write log to disk - - .DESCRIPTION +.DESCRIPTION Takes the Global Variable that hold the log in memory and writes it to disk - - .PARAMETER LogFile +.PARAMETER LogFile Log File including the Path to write - - .EXAMPLE +.EXAMPLE Write-Log -LogFile 'C:\Temp\logfile.html' - - .NOTES - General notes - #> +#> +function Write-Log { [CmdletBinding()] param ( - [Parameter( - Mandatory = $true - )] + [Parameter(Mandatory = $true)] [string]$LogFile ) Set-Content -LiteralPath $LogFile -Value $global:Log