Last active
August 30, 2025 05:27
-
-
Save diazona/fa34f1d5163086f8236b to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| [[source]] | |
| url = "https://pypi.org/simple" | |
| verify_ssl = true | |
| name = "pypi" | |
| [packages] | |
| tvnamer = "*" | |
| [dev-packages] | |
| [requires] | |
| python_version = "3.8" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "_meta": { | |
| "hash": { | |
| "sha256": "f31b3f353e1644d3b7114eb04324dd6770fac831dedaf51b277bb7e7ec19e755" | |
| }, | |
| "pipfile-spec": 6, | |
| "requires": { | |
| "python_version": "3.8" | |
| }, | |
| "sources": [ | |
| { | |
| "name": "pypi", | |
| "url": "https://pypi.org/simple", | |
| "verify_ssl": true | |
| } | |
| ] | |
| }, | |
| "default": { | |
| "certifi": { | |
| "hashes": [ | |
| "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", | |
| "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5" | |
| ], | |
| "markers": "python_version >= '3.7'", | |
| "version": "==2025.8.3" | |
| }, | |
| "charset-normalizer": { | |
| "hashes": [ | |
| "sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91", | |
| "sha256:02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0", | |
| "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154", | |
| "sha256:07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601", | |
| "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884", | |
| "sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07", | |
| "sha256:0f2be7e0cf7754b9a30eb01f4295cc3d4358a479843b31f328afd210e2c7598c", | |
| "sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64", | |
| "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe", | |
| "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f", | |
| "sha256:16a8770207946ac75703458e2c743631c79c59c5890c80011d536248f8eaa432", | |
| "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc", | |
| "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa", | |
| "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9", | |
| "sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae", | |
| "sha256:1ef99f0456d3d46a50945c98de1774da86f8e992ab5c77865ea8b8195341fc19", | |
| "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d", | |
| "sha256:23b6b24d74478dc833444cbd927c338349d6ae852ba53a0d02a2de1fce45b96e", | |
| "sha256:252098c8c7a873e17dd696ed98bbe91dbacd571da4b87df3736768efa7a792e4", | |
| "sha256:257f26fed7d7ff59921b78244f3cd93ed2af1800ff048c33f624c87475819dd7", | |
| "sha256:2c322db9c8c89009a990ef07c3bcc9f011a3269bc06782f916cd3d9eed7c9312", | |
| "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92", | |
| "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31", | |
| "sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c", | |
| "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f", | |
| "sha256:34a7f768e3f985abdb42841e20e17b330ad3aaf4bb7e7aeeb73db2e70f077b99", | |
| "sha256:3653fad4fe3ed447a596ae8638b437f827234f01a8cd801842e43f3d0a6b281b", | |
| "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15", | |
| "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392", | |
| "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f", | |
| "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8", | |
| "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491", | |
| "sha256:4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0", | |
| "sha256:511729f456829ef86ac41ca78c63a5cb55240ed23b4b737faca0eb1abb1c41bc", | |
| "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0", | |
| "sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f", | |
| "sha256:5b413b0b1bfd94dbf4023ad6945889f374cd24e3f62de58d6bb102c4d9ae534a", | |
| "sha256:5d8d01eac18c423815ed4f4a2ec3b439d654e55ee4ad610e153cf02faf67ea40", | |
| "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927", | |
| "sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849", | |
| "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce", | |
| "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", | |
| "sha256:70bfc5f2c318afece2f5838ea5e4c3febada0be750fcf4775641052bbba14d05", | |
| "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c", | |
| "sha256:74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c", | |
| "sha256:78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a", | |
| "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc", | |
| "sha256:88ab34806dea0671532d3f82d82b85e8fc23d7b2dd12fa837978dad9bb392a34", | |
| "sha256:8999f965f922ae054125286faf9f11bc6932184b93011d138925a1773830bbe9", | |
| "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096", | |
| "sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14", | |
| "sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30", | |
| "sha256:a2d08ac246bb48479170408d6c19f6385fa743e7157d716e144cad849b2dd94b", | |
| "sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b", | |
| "sha256:b5e3b2d152e74e100a9e9573837aba24aab611d39428ded46f4e4022ea7d1942", | |
| "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db", | |
| "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5", | |
| "sha256:c60e092517a73c632ec38e290eba714e9627abe9d301c8c8a12ec32c314a2a4b", | |
| "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce", | |
| "sha256:c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669", | |
| "sha256:c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0", | |
| "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018", | |
| "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93", | |
| "sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe", | |
| "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049", | |
| "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", | |
| "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef", | |
| "sha256:d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2", | |
| "sha256:d22dbedd33326a4a5190dd4fe9e9e693ef12160c77382d9e87919bce54f3d4ca", | |
| "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16", | |
| "sha256:d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f", | |
| "sha256:d95bfb53c211b57198bb91c46dd5a2d8018b3af446583aab40074bf7988401cb", | |
| "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1", | |
| "sha256:ec557499516fc90fd374bf2e32349a2887a876fbf162c160e3c01b6849eaf557", | |
| "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37", | |
| "sha256:fb731e5deb0c7ef82d698b0f4c5bb724633ee2a489401594c5c88b02e6cb15f7", | |
| "sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72", | |
| "sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c", | |
| "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9" | |
| ], | |
| "markers": "python_version >= '3.7'", | |
| "version": "==3.4.3" | |
| }, | |
| "idna": { | |
| "hashes": [ | |
| "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", | |
| "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3" | |
| ], | |
| "markers": "python_version >= '3.6'", | |
| "version": "==3.10" | |
| }, | |
| "requests": { | |
| "hashes": [ | |
| "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", | |
| "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422" | |
| ], | |
| "markers": "python_version >= '3.8'", | |
| "version": "==2.32.4" | |
| }, | |
| "requests-cache": { | |
| "hashes": [ | |
| "sha256:813023269686045f8e01e2289cc1e7e9ae5ab22ddd1e2849a9093ab3ab7270eb", | |
| "sha256:81e13559baee64677a7d73b85498a5a8f0639e204517b5d05ff378e44a57831a" | |
| ], | |
| "version": "==0.5.2" | |
| }, | |
| "tvdb-api": { | |
| "hashes": [ | |
| "sha256:f63f6db99441bb202368d44aaabc956acc4202b18fc343a66bf724383ee1f563" | |
| ], | |
| "version": "==3.1.0" | |
| }, | |
| "tvnamer": { | |
| "hashes": [ | |
| "sha256:dc2ea8188df6ac56439343630466b874c57756dd0b2538dd8e7905048f425f04" | |
| ], | |
| "index": "pypi", | |
| "version": "==3.0.4" | |
| }, | |
| "urllib3": { | |
| "hashes": [ | |
| "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", | |
| "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9" | |
| ], | |
| "markers": "python_version >= '3.8'", | |
| "version": "==2.2.3" | |
| } | |
| }, | |
| "develop": {} | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "search_all_languages": false, | |
| "language": "en", | |
| "batch": true, | |
| "dry-run": false, | |
| "verbose": true, | |
| "episode_separator": "-", | |
| "episode_single": "e%02d", | |
| "multiep_format": "%(epname)s (%(episodemin)s-%(episodemax)s)", | |
| "filename_anime_with_episode": "%(seriesname)s - %(episode)s - %(episodename)s%(ext)s", | |
| "filename_anime_with_episode_without_crc": "%(seriesname)s - %(episode)s - %(episodename)s%(ext)s", | |
| "filename_anime_without_episode": "%(seriesname)s - %(episode)s%(ext)s", | |
| "filename_anime_without_episode_without_crc": "%(seriesname)s - %(episode)s%(ext)s", | |
| "filename_with_date_and_episode": "%(seriesname)s - %(year)04d-%(month)02d-%(day)02d - %(episodename)s%(ext)s", | |
| "filename_with_date_without_episode": "%(seriesname)s - %(year)04d-%(month)02d-%(day)02d%(ext)s", | |
| "filename_with_episode": "%(seriesname)s - s%(seasonnumber)02d%(episode)s - %(episodename)s%(ext)s", | |
| "filename_with_episode_no_season": "%(seriesname)s - s00%(episode)s - %(episodename)s%(ext)s", | |
| "filename_without_episode": "%(seriesname)s - %(seasonnumber)02d%(episode)s%(ext)s", | |
| "filename_without_episode_no_season": "%(seriesname)s - s00%(episode)s%(ext)s", | |
| "order": "dvd" | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/python3 | |
| '''A script that reads episode filenames from standard input, one per line, | |
| and prints out the corresponding filenames in a standard format on standard | |
| output, one per line. The tvnamer Python library is used to generate the | |
| new filenames, and TheTVDB is used to access episode titles. So this does | |
| basically the same thing as tvnamer, except that instead of actually | |
| renaming files, it just transforms filenames.''' | |
| import os.path, sys, json | |
| from tvdb_api import Tvdb | |
| from tvnamer.tvnamer_exceptions import BaseTvnamerException, DataRetrievalError | |
| from tvnamer.utils import Config, FileParser, split_extension | |
| # It's a little sketchy to reuse the tvnamer API key, but since I'm basically | |
| # doing the same thing tvnamer does anyway and I could almost as easily just | |
| # call it as a command-line program (which would 100% legitimately use this API | |
| # key), this seems reasonable for private use | |
| from tvnamer.main import TVNAMER_API_KEY | |
| def read_config(cfg_filename, dry=False, verbose=False): | |
| Config.update({ | |
| 'search_all_languages': False, | |
| 'language': 'en', | |
| 'batch': True, | |
| 'dry-run': dry, | |
| 'verbose': verbose, | |
| 'episode_separator': '-', | |
| 'episode_single': 'e%02d', | |
| 'multiep_format': '%(epname)s (%(episodemin)s-%(episodemax)s)', | |
| 'filename_anime_with_episode': '%(seriesname)s - %(episode)s - %(episodename)s%(ext)s', | |
| 'filename_anime_with_episode_without_crc': '%(seriesname)s - %(episode)s - %(episodename)s%(ext)s', | |
| 'filename_anime_without_episode': '%(seriesname)s - %(episode)s%(ext)s', | |
| 'filename_anime_without_episode_without_crc': '%(seriesname)s - %(episode)s%(ext)s', | |
| 'filename_with_date_and_episode': '%(seriesname)s - %(year)04d-%(month)02d-%(day)02d - %(episodename)s%(ext)s', | |
| 'filename_with_date_without_episode': '%(seriesname)s - %(year)04d-%(month)02d-%(day)02d%(ext)s', | |
| 'filename_with_episode': '%(seriesname)s - s%(seasonnumber)02d%(episode)s - %(episodename)s%(ext)s', | |
| 'filename_with_episode_no_season': '%(seriesname)s - s00%(episode)s - %(episodename)s%(ext)s', | |
| 'filename_without_episode': '%(seriesname)s - %(seasonnumber)02d%(episode)s%(ext)s', | |
| 'filename_without_episode_no_season': '%(seriesname)s - s00%(episode)s%(ext)s', | |
| 'order': 'dvd', | |
| 'tvdb_api_key': TVNAMER_API_KEY | |
| }) | |
| try: | |
| with open(cfg_filename) as f: | |
| Config.update(json.load(f)) | |
| except FileNotFoundError: | |
| pass | |
| def new_name(filename): | |
| ep = FileParser(filename).parse() | |
| ep.populateFromTvdb( | |
| Tvdb(interactive=False, search_all_languages=False, language=Config['language'], dvdorder=True, cache=True, apikey=Config['tvdb_api_key']), | |
| force_name=Config.get("force_name", None), | |
| series_id=Config.get("series_id", None), | |
| ) | |
| return os.path.join(os.path.dirname(filename), ep.generateFilename()) | |
| def main(): | |
| try: | |
| read_config(sys.argv[1], bool(sys.argv[2]), bool(sys.argv[3])) | |
| except Exception: | |
| sys.exit(1) | |
| try: | |
| for line in sys.stdin: | |
| try: | |
| print(new_name(line.rstrip('\n'))) | |
| except (BaseTvnamerException, OSError): | |
| if Config['skip_file_on_error'] or Config['skip_behaviour'] == 'exit': | |
| sys.exit(1) | |
| else: | |
| print(line) | |
| except KeyboardInterrupt: | |
| pass | |
| except Exception as e: | |
| print(e, file=sys.stderr) | |
| sys.exit(1) | |
| if __name__ == '__main__': | |
| main() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| SCRIPT="$(realpath $0)" | |
| COLOR_NC='\033[00m' # No Color | |
| COLOR_WHITE='\033[01;37m' | |
| COLOR_BLACK='\033[00;30m' | |
| COLOR_BLUE='\033[00;34m' | |
| COLOR_LIGHT_BLUE='\033[01;34m' | |
| COLOR_GREEN='\033[00;32m' | |
| COLOR_LIGHT_GREEN='\033[01;32m' | |
| COLOR_CYAN='\033[00;36m' | |
| COLOR_LIGHT_CYAN='\033[01;36m' | |
| COLOR_RED='\033[00;31m' | |
| COLOR_LIGHT_RED='\033[01;31m' | |
| COLOR_PURPLE='\033[00;35m' | |
| COLOR_LIGHT_PURPLE='\033[01;35m' | |
| COLOR_BROWN='\033[00;33m' | |
| COLOR_YELLOW='\033[01;33m' | |
| COLOR_GRAY='\033[00;30m' | |
| COLOR_LIGHT_GRAY='\033[00;37m' | |
| COLOR_BOLD=$(tput bold) # not really a "color" but close enough | |
| COLOR_RESET=$(tput sgr0) | |
| TERM_TITLE='\033]2;' | |
| END_TERM_TITLE='\007' | |
| RESET_TERM_TITLE='\e]0;\a' | |
| HOSTNAME="$(hostname)" | |
| NEWLINE=$'\n' | |
| RIP_ROOT_DIR="$HOME/Videos/rips" | |
| RIP_TVNAMER="$(dirname "$SCRIPT")/rip-tvnamer.py" | |
| RIP_TVNAMER_CONF="$(dirname "$SCRIPT")/rip-tvnamer.json" | |
| source_conf() { | |
| [ -f "$1" ] && source "$1" | |
| } | |
| source_conf ./rip.conf | |
| source_conf "$HOME/.config/rip.conf" | |
| source_conf "$(dirname "$0")/rip.conf" | |
| usage() { | |
| local usage_str | |
| case "$mode" in | |
| movie) | |
| usage_str="Usage in movie mode: $progname 'title (year)' [dvdtitlespec] or $progname title [year [dvdtitlespec]]" | |
| ;; | |
| tv) | |
| usage_str="Usage in tv mode: $progname seriestitle season dvdtitlespecs..." | |
| ;; | |
| *) | |
| usage_str="Usage: | |
| (tv) $progname seriestitle season dvdtitlespecs... | |
| (movie) $progname 'title (year)' [dvdtitlespec] | |
| (movie) $progname title year [dvdtitlespec]" | |
| ;; | |
| esac | |
| die "$usage_str" | |
| } | |
| stripcolors() { | |
| sed -r "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[mGK]//g;s/\\\\033.{1,6}m//g" | |
| } | |
| log() { | |
| local label="${1}" | |
| shift | |
| stripcolors <<< "[$label] $@" >> rip.log | |
| return 0 | |
| } | |
| die() { | |
| echo -e "${COLOR_RED}${COLOR_BOLD}$@${COLOR_RESET}" >&2 | |
| log "die" "$@" | |
| exit 1 | |
| } | |
| logecho() { | |
| echo -e "$@" | |
| log "echo" "$@" | |
| } | |
| logdebug() { | |
| [ -n "$debug" ] && log "debug" "$@" | |
| return 0 | |
| } | |
| notify_text() { | |
| shift | |
| curl --silent http://textbelt.com/text -d "number=$PHONE_NUMBER" -d "message=$* on $HOSTNAME" >>rip.log | |
| echo >>rip.log | |
| } | |
| notify_pushbullet() { | |
| local notification_title="$1" | |
| shift | |
| curl --silent --header "Access-Token: $PUSHBULLET_ACCESS_TOKEN" -X POST https://api.pushbullet.com/v2/pushes --header 'Content-Type: application/json' --data-binary "{\"type\": \"note\", \"title\": \"$notification_title\", \"body\": \"$* on $HOSTNAME\"}" >>rip.log | |
| echo >>rip.log | |
| } | |
| notify_kdialog() { | |
| local notification_title="$1" | |
| shift | |
| kdialog --passivepopup "$*" 4 --title "$notification_title" | |
| } | |
| mark_start() { | |
| logecho "${COLOR_YELLOW}Starting at $(date)${COLOR_RESET}" | |
| } | |
| mark_stop() { | |
| logecho "${COLOR_YELLOW}Ending at $(date)${COLOR_RESET}" | |
| } | |
| get_free_megabytes() { | |
| local dfdir="$1" | |
| until [ -f "$dfdir" -o -d "$dfdir" ]; do | |
| dfdir="$(dirname "$dfdir")" | |
| done | |
| df --block-size=1M "$dfdir" | |
| } | |
| parse_length() { | |
| local duration="$1" | |
| [[ "$duration" =~ ^([0-9]+)[:h]([0-5][0-9])[:m]([0-5][0-9]\.[0-9]+s?)?$ ]] | |
| hours="${BASH_REMATCH[1]}" | |
| minutes="${BASH_REMATCH[2]}" | |
| seconds="${BASH_REMATCH[3]}" | |
| } | |
| get_length() { | |
| local duration | |
| duration="$(ffprobe -show_entries format=duration -v error -of default=noprint_wrappers=1:nokey=1 -sexagesimal "$1")" | |
| parse_length "$duration" | |
| } | |
| format_length() { | |
| if [[ "$hours" -gt 0 ]]; then | |
| echo "${hours}h ${minutes}m" | |
| else | |
| echo "${minutes}m" | |
| fi | |
| } | |
| output_check() { | |
| local outdir="$(dirname "$outfile")" | |
| # ensure that output directory exists | |
| if [[ ! -d "$outdir" ]]; then | |
| if [ -z "$dry" ]; then | |
| logecho "Creating output directory $outdir" | |
| mkdir -p "$outdir" || die "Failed to create output directory $outdir" | |
| else | |
| logecho "Not creating output directory $outdir (dry run)" | |
| fi | |
| fi | |
| # print a warning if filesystem is running low on space | |
| local dfoutput="$(get_free_megabytes "$outdir")" | |
| local freespace="$(awk 'NR==2 {print $4}' <<<"$dfoutput")" | |
| local mountpoint="$(awk 'NR==2 {print $1}' <<<"$dfoutput")" | |
| if (( $freespace < 1500 )); then # allow 1.5G per disc | |
| die "insufficient free space on partition $mountpoint" | |
| elif (( $freespace < 3000 )); then | |
| logecho "${COLOR_RED}${COLOR_BOLD}WARNING: ${COLOR_RED}free space on partition $mountpoint is almost exhausted${COLOR_RESET}" | |
| elif (( $freespace < 6000 )); then | |
| logecho "${COLOR_YELLOW}${COLOR_BOLD}WARNING: ${COLOR_YELLOW}free space on partition $mountpoint is getting low${COLOR_RESET}" | |
| fi | |
| } | |
| set_terminal_title() { | |
| echo -n -e "${TERM_TITLE}$(stripcolors <<< "$*")${END_TERM_TITLE}" | |
| } | |
| reset_terminal_title() { | |
| # not sure if this really works... | |
| # http://superuser.com/questions/339862/restore-mac-os-x-terminal-title-after-closing-a-ssh-connection | |
| echo -n -e "${RESET_TERM_TITLE}" | |
| } | |
| # expects newline-separated numbers on stdin | |
| # not guaranteed to work for negative inputs | |
| # http://stackoverflow.com/questions/13708705/in-bash-how-to-convert-number-list-into-ranges-of-numbers | |
| list_to_ranges() { | |
| local num | |
| while read num || [ -n "$last" ]; do | |
| [ -n "$num" ] && let num="10#$num" | |
| if [ -z "$last" ]; then | |
| first="$num" | |
| last="$num" | |
| continue | |
| fi | |
| if [[ "$num" -eq "$(($last+1))" ]]; then | |
| let last++ | |
| else | |
| if [[ "$first" -eq "$last" ]]; then | |
| echo "$first" | |
| else | |
| echo "$first-$last" | |
| fi | |
| first="$num" | |
| last="$num" | |
| fi | |
| done | |
| } | |
| select_cpus() { | |
| seq "$(grep -c processor /proc/cpuinfo)" | shuf -n "$1" | tr '\n' ',' | head -c -1 | |
| } | |
| send_notifications() { | |
| for m in $(<<< "$notification_modes" tr ' ' '\n' | sort -u); do # outer non-quoting here is intentional | |
| notify_$m "Rip complete" "$notification_message" | |
| done | |
| } | |
| run_handbrake() { | |
| output_check | |
| local cmdline="" | |
| [ -n "$ncpus" ] && cmdline+="taskset -c '$(select_cpus $ncpus)' " | |
| [ -n "$HANDBRAKE_PRESET_FILE" ] && handbrake_opts="${handbrake_opts} --preset-import-file \"$HANDBRAKE_PRESET_FILE\"" | |
| [ -n "$HANDBRAKE_PRESET_NAME" ] && handbrake_opts="${handbrake_opts} --preset \"$HANDBRAKE_PRESET_NAME\"" | |
| cmdline+="HandBrakeCLI ${handbrake_opts} " | |
| if [ "$title" == "main" ]; then | |
| cmdline+="--main-feature " | |
| else | |
| cmdline+="--title '$title' " | |
| fi | |
| [ -n "$chapters" -a "$chapters" != "-" ] && cmdline+="--chapters ${chapters} " | |
| cmdline+="-i \"$input_source\" -o \"${outfile}\"" # hopefully there are no double quotes or backslashes in $input_source or $outfile | |
| log "command" "$cmdline" | |
| if [ -n "$dry" ]; then | |
| echo "$cmdline" | |
| logecho "Rip successful (dry run)" | |
| else | |
| eval "$cmdline" </dev/null &>>rip.log || die "Ripping ${display_output} failed" | |
| # Check length | |
| get_length "${outfile}" | |
| declare -i length_discrepancy expected_length | |
| if [[ -n "$expected_length_override" ]]; then | |
| expected_length="$expected_length_override" | |
| elif [[ -z "${imdb_id:-}" ]] || ! expected_length="$(get_length_omdb)"; then | |
| # TV episodes wind up in this branch | |
| logecho "Rip successful but unable to check length: generated length ${COLOR_CYAN}${COLOR_BOLD}$(format_length)${COLOR_RESET}" | |
| return | |
| fi | |
| length_discrepancy="$((hours * 60 + minutes - expected_length))" | |
| if (( length_discrepancy < -2 || length_discrepancy > 2 )); then | |
| local formatted_length formatted_expected_length | |
| formatted_length="$(format_length)" | |
| hours="$((expected_length / 60))" | |
| minutes="$((expected_length % 60))" | |
| formatted_expected_length="$(format_length)" | |
| die "Ripping ${display_output} failed: generated length ${COLOR_RED}${COLOR_BOLD}$formatted_length${COLOR_RESET} but expected ${COLOR_CYAN}${COLOR_BOLD}$formatted_expected_length${COLOR_RESET}" | |
| else | |
| logecho "Rip successful: generated length ${COLOR_CYAN}${COLOR_BOLD}$(format_length)${COLOR_RESET}" | |
| fi | |
| fi | |
| } | |
| search_year_omdb() { | |
| local name="$1" r | |
| r="$(curl --silent 'https://www.omdbapi.com/' -G --data-urlencode "s=$name" --data-urlencode "apikey=$OMDB_APIKEY" | jq -r --arg name "$name" -e '.Search[] | @sh "names+=(\(.Title)); years+=(\(.Year)); imdb_ids+=(\(.imdbID))"')" || die "Movie lookup failed (OMDB API)" | |
| eval "$r" | |
| } | |
| get_length_omdb() { | |
| local r | |
| curl --fail --silent 'https://www.omdbapi.com/' -G --data-urlencode "i=$imdb_id" --data-urlencode "apikey=$OMDB_APIKEY" | jq -r -e '.Runtime | match("^([0-9]+) min$") | .captures[0].string' || die "Movie runtime lookup failed (OMDB API)" | |
| } | |
| search_year_themoviedb() { | |
| die "TheMovieDB not enabled" | |
| } | |
| rip_movie() { | |
| local name="$1" year imdb_id | |
| shift | |
| if [[ "$name" =~ \([12][0-9]{3}\)$ ]]; then | |
| year="${name: -5:4}" | |
| name="${name%%*([[:space:]])\(????\)}" | |
| elif [[ "$1" =~ ^[12][0-9]{3}$ ]]; then | |
| year="$1" | |
| shift | |
| else | |
| # get year from IMDB | |
| declare -a names=() years=() imdb_ids=() | |
| search_year_omdb "$name" | |
| case "${#years[@]}" in | |
| 1) | |
| name="${names[0]}" | |
| year="${years[0]}" | |
| imdb_id="${imdb_ids[0]}" | |
| ;; | |
| 0) | |
| die "Movie title '$name' not recognized" ;; | |
| *) | |
| echo "Select:" | |
| local i | |
| declare -a choices=() | |
| for (( i=0; i < "${#names[@]}"; ++i )); do | |
| choices+=("${names[$i]} (${years[$i]})") | |
| done | |
| select choice in "${choices[@]}"; do | |
| if [[ -n "$choice" ]]; then | |
| name="${names[$REPLY-1]}" | |
| year="${years[$REPLY-1]}" | |
| imdb_id="${imdb_ids[$REPLY-1]}" | |
| break | |
| fi | |
| done ;; | |
| esac | |
| fi | |
| case "$#" in | |
| 0) | |
| title="main" | |
| ;; | |
| 1) | |
| # parses something of the form | |
| # title | |
| # title:chapter-chapter | |
| # :chapter-chapter | |
| logdebug "parsing title spec $1" | |
| if [[ "$1" =~ ^[0-9]*:[0-9]+(-[0-9]+)?$ ]]; then | |
| title="${1%:*}" | |
| chapters="${1#*:}" | |
| elif [[ "$1" =~ ^[0-9]+$ ]]; then | |
| title="$1" | |
| else | |
| die "Invalid title/chapter spec: '$1'" | |
| fi | |
| ;; | |
| *) | |
| usage | |
| ;; | |
| esac | |
| local movie_base_name="${name//:/} ($year)" # colon is a common character in movie titles that is not allowed in NTFS filenames | |
| [[ -n "$imdb_id" ]] && movie_base_name+=" {imdb-$imdb_id}" | |
| outfile="${RIP_ROOT_DIR}/movies/$movie_base_name/$movie_base_name.m4v" | |
| [[ -f "$outfile" ]] && die "Not ripping existing file $outfile" | |
| display_output="${COLOR_LIGHT_CYAN}$name ${COLOR_LIGHT_PURPLE}($year)" | |
| set_terminal_title "Ripping $display_output" | |
| if [ "$title" == "main" ]; then | |
| display_title="main title" | |
| else | |
| display_title="title ${title}" | |
| fi | |
| logecho "${COLOR_BOLD}Ripping ${display_output} ${COLOR_WHITE}from ${display_title} to ${outfile}...${COLOR_RESET}" | |
| run_handbrake | |
| notification_message="Finished ripping $name ($year)" | |
| } | |
| # parses something of the form | |
| # title | |
| # title:chapter-chapter | |
| # title[episode] | |
| # title:chapter-chapter[episode] | |
| # title[episode-episode] | |
| # title:chapter-chapter[episode-episode] | |
| # title-title | |
| # title-title/increment | |
| # title[episode]-title | |
| # title[episode]-title/increment | |
| # or a comma-separated sequence of the above | |
| parse_titlespecs_tv() { | |
| local title chapters first_episode last_episode first last step | |
| for a in ${@//,/ }; do # don't use double quotes here so that comma-separated specs get interpreted as different words after the replacement | |
| logdebug "title spec $a" | |
| if [[ "$a" =~ ^([0-9]+)(:([0-9]+(-[0-9]+)?))?(\[([0-9]+)(-([0-9]+))?\])?$ ]]; then | |
| title="${BASH_REMATCH[1]}" | |
| chapters="${BASH_REMATCH[3]}" | |
| first_episode="${BASH_REMATCH[6]}" | |
| last_episode="${BASH_REMATCH[8]}" | |
| (( 10#$title == 0 )) && die "Invalid title/chapter spec: '$a'" | |
| echo "$title" "${chapters:=-}" "$first_episode" "$last_episode" | |
| elif [[ "$a" =~ ^[0-9]+$ ]]; then | |
| (( $a == 0 )) && die "Invalid title/chapter spec: '$a'" | |
| echo "$a" | |
| elif [[ "$a" =~ ^([0-9]+)-([0-9]+)(/([0-9]+))?$ ]]; then | |
| first="${BASH_REMATCH[1]}" | |
| last="${BASH_REMATCH[2]}" | |
| step="${BASH_REMATCH[4]}" | |
| (( 10#$first == 0 || 10#$last < 10#$first || 10#${step:-1} == 0 )) && die "Invalid title/chapter spec: '$a'" | |
| # non-quoting of $step here is intentional; if it's empty, seq thinks it's only getting two arguments | |
| seq "$first" $step "$last" | |
| elif [[ "$a" =~ ^([0-9]+)\[([0-9]+)\]-([0-9]+)(/([0-9]+))?$ ]]; then | |
| first="${BASH_REMATCH[1]}" | |
| last="${BASH_REMATCH[3]}" | |
| step="${BASH_REMATCH[5]}" | |
| first_episode="${BASH_REMATCH[2]}" | |
| [ -z "$first_episode" ] && first_episode="$starting_episode" | |
| (( 10#$first == 0 || 10#$last < 10#$first || 10#${step:-1} == 0 )) && die "Invalid title/chapter spec: '$a'" | |
| # non-quoting of $step here is intentional; if it's empty, seq thinks it's only getting two arguments | |
| seq "$first" $step "$last" | while read n; do echo "$n" "-" $(((10#$n-10#${first})/$step+10#${first_episode})); done | |
| else | |
| die "Invalid title/chapter spec: '$a'" | |
| fi | |
| done | |
| } | |
| # Find a range of $1 consecutive episode numbers that are unused | |
| # and echo the beginning of the range | |
| find_free_episode_range() { | |
| local episodes last_episode_in_any_file=0 range_length="$1" outdir="${2:-$PWD}" range | |
| # Collect a newline-separated list of all the episode numbers | |
| # among all the m4v files in $outdir | |
| for f in "$outdir"/*.m4v; do | |
| # If there are no m4v files in the directory, immediately echo 1 and return | |
| # All episode numbers are free so there's no need for further checking | |
| if ! [[ -f "$f" ]]; then | |
| echo 1 | |
| return | |
| fi | |
| [[ "$(basename "$f")" =~ s[0-9]+e([0-9]+)(-e([0-9]+))?( - .+)?\.m4v$ ]] | |
| first_episode_in_file="${BASH_REMATCH[1]}" | |
| last_episode_in_file="${BASH_REMATCH[3]}" | |
| let first_episode_in_file="10#$first_episode_in_file" | |
| if [ -n "$last_episode_in_file" ]; then | |
| let last_episode_in_file="10#$last_episode_in_file" | |
| episodes="$episodes$NEWLINE$(seq "$first_episode_in_file" "$last_episode_in_file")" | |
| [ "$last_episode_in_any_file" -gt "$last_episode_in_file" ] || last_episode_in_any_file="$last_episode_in_file" | |
| else | |
| episodes="$episodes$NEWLINE$first_episode_in_file" | |
| [ "$last_episode_in_any_file" -gt "$first_episode_in_file" ] || last_episode_in_any_file="$first_episode_in_file" | |
| fi | |
| done | |
| # Then append to that list a newline-separated sequence | |
| # from 1 to the largest episode number | |
| episodes="$episodes$NEWLINE$(seq $last_episode_in_any_file)" | |
| # Sort and uniq-filter the resulting list to identify any numbers | |
| # which only occur once - those are the numbers for which no | |
| # episode yet exists | |
| for range in $(sort <<<"$episodes" | uniq -u | list_to_ranges); do | |
| # If only one free episode number is needed, return the first one | |
| if [[ "$range_length" -eq 1 ]] && [[ "$range" =~ [0-9]+ ]]; then | |
| echo "$range" | |
| return | |
| # If a range of free episode numbers is needed, check each range | |
| # to see if it's long enough and return the first one that is | |
| elif [[ "$range_length" -gt 1 ]] && [[ "$range" =~ [0-9]+-[0-9]+ ]]; then | |
| if (( $range + $range_length > 0 )); then | |
| echo "${range%%-*}" | |
| return | |
| fi | |
| fi | |
| done | |
| # If no suitable free episode numbers were found that way, return | |
| # one more than the largest existing episode number | |
| echo "$((10#$last_episode_in_any_file + 1))" | |
| } | |
| ripexists_tv() { | |
| local episoderangetwodigit seasontwodigit series_sanitized | |
| # $outfile gets used again in rip_tv() | |
| if [ -n "$last_episode" ]; then | |
| printf -v episoderangetwodigit 'e%02d-e%02d' $first_episode $last_episode | |
| else | |
| printf -v episoderangetwodigit 'e%02d' $first_episode | |
| fi | |
| series_sanitized="${series//:/}" | |
| if [[ "$season" =~ ^([Ss]pecials?|00?)$ ]]; then | |
| season="0" | |
| seasontwodigit="s00" | |
| outfile="${RIP_ROOT_DIR}/tv/$series_sanitized/Specials/$series_sanitized - ${seasontwodigit}${episoderangetwodigit}.m4v" | |
| else | |
| printf -v seasontwodigit 's%02d' $season | |
| outfile="${RIP_ROOT_DIR}/tv/$series_sanitized/Season $season/$series_sanitized - ${seasontwodigit}${episoderangetwodigit}.m4v" | |
| fi | |
| [ -n "$use_tvnamer" ] && tvnamer_transform_outfile | |
| [ -f "$outfile" ] | |
| } | |
| # Checks the requirements for the Python script that invokes tvnamer | |
| check_tvnamer_reqs() { | |
| python3 - 2>/dev/null <<-EOF | |
| import sys | |
| try: | |
| import json | |
| from tvdb_api import Tvdb | |
| from tvnamer.tvnamer_exceptions import BaseTvnamerException, DataRetrievalError | |
| from tvnamer.utils import Config, FileParser | |
| except ImportError: | |
| sys.exit(1) | |
| EOF | |
| } | |
| start_tvnamer() { | |
| [ -n "$use_tvnamer" ] || return | |
| [ -n "$tvnamer_started" ] && return | |
| # Very important to use -u for unbuffered output | |
| logdebug "Running tvnamer script" | |
| log "command" "$RIP_TVNAMER" "${tvnamer_cfgfile:-$RIP_TVNAMER_CONF}" "$dry" "$debug" | |
| coproc python3 -u "$RIP_TVNAMER" "${tvnamer_cfgfile:-$RIP_TVNAMER_CONF}" "$dry" "$debug" | |
| tvnamer_stdout=${COPROC[0]} | |
| tvnamer_stdin=${COPROC[1]} | |
| tvnamer_pid=$COPROC_PID | |
| tvnamer_started="1" | |
| logdebug "tvnamer script started" | |
| } | |
| tvnamer_transform_outfile() { | |
| [ -n "$use_tvnamer" ] || return | |
| [ -n "$tvnamer_started" ] || die "tvnamer script not started" | |
| echo "$outfile" >&"$tvnamer_stdin" | |
| # 30 seconds should be more than long enough to get a response from TVDB | |
| if ! read -t 30 -u "$tvnamer_stdout" tvdb_filename; then | |
| logecho "Error determining new filename from TVDB" | |
| return | |
| fi | |
| outfile="$tvdb_filename" | |
| } | |
| rip_tv() { | |
| (( $# < 3 )) && usage | |
| local starting_episode_option_set | |
| [ -n "$starting_episode" ] && starting_episode_option_set=1 | |
| series="$1" | |
| shift | |
| season="$1" | |
| shift | |
| [[ "$season" =~ ^([0-9]+|[Ss]pecials?)$ ]] || usage | |
| parsed_titlespecs="$(parse_titlespecs_tv "$@")" || exit 1 | |
| tempfile="$(mktemp)" | |
| [ -n "$use_tvnamer" ] && start_tvnamer | |
| trap "rm -f '$tempfile'; exit 1" SIGTERM SIGINT | |
| while read title chapters first_episode last_episode; do | |
| [ "$chapters" == "-" ] && chapters= | |
| if [ -z "$first_episode" ]; then | |
| # run this to get a dummy filename to get the right directory to put files in | |
| use_tvnamer="" ripexists_tv | |
| if [ -z "$starting_episode" ]; then | |
| starting_episode="$(find_free_episode_range "$(wc -l <<<"$parsed_titlespecs" | cut -d' ' -f 1)" "$(dirname "$outfile")")" | |
| fi | |
| first_episode="$starting_episode" | |
| # now run it again to get the correct output filename | |
| ripexists_tv | |
| else | |
| [ -n "$starting_episode_option_set" ] && logecho "Ignoring first episode number provided through option (-e $starting_episode); starting at episode $first_episode instead" | |
| starting_episode_option_set= | |
| if ripexists_tv; then | |
| logecho "Skipping existing episode $first_episode" | |
| continue | |
| fi | |
| fi | |
| if [ -n "$last_episode" ]; then | |
| display_output="${COLOR_LIGHT_CYAN}$series ${COLOR_LIGHT_RED}S${season}${COLOR_LIGHT_GREEN}E${first_episode}-${last_episode}" | |
| else | |
| display_output="${COLOR_LIGHT_CYAN}$series ${COLOR_LIGHT_RED}S${season}${COLOR_LIGHT_GREEN}E${first_episode}" | |
| last_episode="$first_episode" | |
| fi | |
| starting_episode="$first_episode" | |
| set_terminal_title "Ripping $display_output" | |
| logecho "${COLOR_BOLD}Ripping ${display_output} ${COLOR_WHITE}from title ${title} to ${outfile}...${COLOR_RESET}" | |
| run_handbrake | |
| seq "$first_episode" "$last_episode" >> "$tempfile" | |
| let ++starting_episode | |
| sleep "$delay" | |
| logdebug "Slept for $delay seconds" | |
| done <<< "$parsed_titlespecs" || exit 1 | |
| local plural | |
| [ "$(wc -l $tempfile | cut -d' ' -f 1)" -gt 1 ] && plural=1 | |
| ripped_episodes="$(list_to_ranges < "$tempfile" | paste -sd ",")" | |
| rm "$tempfile" | |
| trap SIGTERM SIGINT | |
| notification_message="Finished ripping $series season $season episode${plural:+s} $ripped_episodes" | |
| } | |
| scan_titles() { | |
| # TODO refactor to reduce duplication with run_handbrake | |
| local cmdline="" | |
| [ -n "$ncpus" ] && cmdline+="taskset -c '$(select_cpus $ncpus)' " | |
| handbrake_opts="--json" | |
| [ -n "$HANDBRAKE_PRESET_FILE" ] && handbrake_opts="${handbrake_opts} --preset-import-file \"$HANDBRAKE_PRESET_FILE\"" | |
| [ -n "$HANDBRAKE_PRESET_NAME" ] && handbrake_opts="${handbrake_opts} --preset \"$HANDBRAKE_PRESET_NAME\"" | |
| cmdline+="HandBrakeCLI ${handbrake_opts} --title 0 --min-duration 300 " | |
| cmdline+="-i '$input_source'" | |
| log "command" "$cmdline" | |
| if [ -n "$dry" ]; then | |
| echo "$cmdline" | |
| logecho "Scan successful (dry run)" | |
| else | |
| if ! \ | |
| eval "$cmdline" </dev/null 2>>rip.log \ | |
| | sed -n '/JSON Title Set:/,$ {s/^JSON Title Set://;p}' \ | |
| | jq -rc 'def zlpad: tostring | if (length < 2) then ((2 - length) * "0") + . else . end; def fmt: "\(.Hours):\(.Minutes | zlpad):\(.Seconds | zlpad)"; .TitleList[] | "Title: \(.Index)\n Playlist: \(.Playlist)\n Duration: \(.Duration | fmt)\n Chapters: \([.ChapterList[].Duration] | map(fmt))"' \ | |
| ; then | |
| die "Scan failed" | |
| fi | |
| logecho "Scan successful" | |
| fi | |
| } | |
| main() { | |
| shopt -s extglob | |
| dry="" | |
| debug="" | |
| ncpus="" | |
| input_source="/dev/sr0" | |
| delay="0" | |
| starting_episode="" | |
| starting_offset="" | |
| mode="" | |
| use_tvnamer="" | |
| notification_modes="" | |
| handbrake_opts="--multi-pass " | |
| expected_length_override="" | |
| # option processing | |
| processed_arguments="$(getopt --options "d:e:hi:j:t:nf:1" --longoptions "cpus:,device:,episode:,delay:,debug,dry-run,help,input-source:,text,pushbullet,kdialog,notification:,scan,start-at:,movie,tv,mode:,no-dvdnav,one-pass,tvnamer,expected-length" --name "$progname" -- "$@")" | |
| logdebug "got arguments $processed_arguments" | |
| eval set -- "$processed_arguments" | |
| while true; do | |
| case "$1" in | |
| -h|--help) | |
| usage | |
| ;; | |
| --mode) | |
| case "$2" in | |
| "movie") | |
| mode="movie" ;; | |
| "tv") | |
| mode="tv" ;; | |
| "scan") | |
| mode="scan" ;; | |
| esac | |
| shift 2 | |
| ;; | |
| --movie) | |
| mode="movie" | |
| shift | |
| ;; | |
| --tv) | |
| mode="tv" | |
| shift | |
| ;; | |
| --scan) | |
| mode="scan" | |
| shift | |
| ;; | |
| --text) | |
| notification_modes+=" text" | |
| shift | |
| ;; | |
| --pushbullet) | |
| notification_modes+=" pushbullet" | |
| shift | |
| ;; | |
| --kdialog) | |
| notification_modes+=" kdialog" | |
| shift | |
| ;; | |
| -f|--notification) | |
| notification_modes+=" ${2//,/ }" | |
| shift 2 | |
| ;; | |
| --no-dvdnav) | |
| handbrake_opts+="--no-dvdnav " | |
| shift | |
| ;; | |
| -1|--one-pass) | |
| handbrake_opts="${handbrake_opts/--two-pass/}" | |
| shift | |
| ;; | |
| -t|--delay) | |
| [[ "$2" =~ ^[0-9]+$ ]] && delay="$2" | |
| shift 2 | |
| ;; | |
| -d|--device) | |
| if [[ "$2" == /* ]]; then # (this is a shell glob match) | |
| input_source="$2" | |
| else | |
| input_source="/dev/$2" | |
| fi | |
| [ -r "$device" ] || die "unreadable input source $input_source" | |
| shift 2 | |
| ;; | |
| -i|--input-source) | |
| input_source="$2" | |
| [ -r "$input_source" ] || die "unreadable input source $input_source" | |
| shift 2 | |
| ;; | |
| -j|--cpus) | |
| [[ "$2" =~ ^[0-9]+$ ]] && ncpus="$2" | |
| shift 2 | |
| ;; | |
| -e|--episode) | |
| [[ "$2" =~ ^[0-9]+$ ]] && starting_episode="$2" | |
| shift 2 | |
| ;; | |
| --start-at) | |
| case "$2" in | |
| *s) handbrake_opts+="--start-at seconds:${2%s} " ;; | |
| *f) handbrake_opts+="--start-at frames:${2%f} " ;; | |
| *pts) handbrake_opts+="--start-at pts:${2%pts} " ;; | |
| seconds:*|frames:*|pts:*) handbrake_opts+="--start-at $2 " ;; | |
| *) die "invalid starting offset $2" ;; | |
| esac | |
| shift 2 | |
| ;; | |
| --tvnamer) | |
| check_tvnamer_reqs || die "tvnamer not available" | |
| use_tvnamer="1" | |
| shift | |
| ;; | |
| --expected-length) | |
| parse_length "$2" | |
| expected_length_override=$(( hours * 60 + minutes )) | |
| shift 2 | |
| ;; | |
| --debug) | |
| debug="1" | |
| shift | |
| ;; | |
| -n|--dry-run) | |
| dry="1" | |
| shift | |
| ;; | |
| --) | |
| shift | |
| break | |
| ;; | |
| *) | |
| die "Error processing arguments - remaining args $@" | |
| esac | |
| done | |
| # if a mode is not specified: | |
| # if we have at least 3 arguments (series, season, title specs), we're in TV mode | |
| # if we have only 1 or 2 arguments (name, optional title spec), we're in movie mode | |
| if [ -z "$mode" ]; then | |
| if (( $# < 3 )); then | |
| mode="movie" | |
| else | |
| mode="tv" | |
| fi | |
| fi | |
| mark_start | |
| case "$mode" in | |
| movie) | |
| rip_movie "$@" | |
| ;; | |
| tv) | |
| rip_tv "$@" | |
| ;; | |
| scan) | |
| scan_titles "$@" | |
| ;; | |
| *) | |
| die "invalid mode" | |
| ;; | |
| esac | |
| mark_stop | |
| reset_terminal_title | |
| send_notifications | |
| } | |
| getopt -T >/dev/null | |
| [ "$?" == 4 ] || die "requires GNU getopt" | |
| progname="$0" | |
| [ "$#" -gt 0 ] || die "no arguments" | |
| TIMEFORMAT="${COLOR_BOLD}Elapsed time: %0lE${COLOR_RESET}" | |
| time main "$@" | |
| exit 0 | |
| #kate: space-indent off; indent-mode normal; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment