diff --git a/Makefile b/Makefile index f172148..4e7f179 100644 --- a/Makefile +++ b/Makefile @@ -34,12 +34,18 @@ install: .$(PYTHON)/bin/pip install -e . --ignore-installed cp ./bin/subaligner_1pass .$(PYTHON)/bin/subaligner_1pass cp ./bin/subaligner_2pass .$(PYTHON)/bin/subaligner_2pass + cp ./bin/subaligner_batch .$(PYTHON)/bin/subaligner_batch + cp ./bin/subaligner_convert .$(PYTHON)/bin/subaligner_convert + cp ./bin/subaligner_train .$(PYTHON)/bin/subaligner_train + cp ./bin/subaligner_tune .$(PYTHON)/bin/subaligner_tune cp ./bin/subaligner .$(PYTHON)/bin/subaligner uninstall: rm -f .$(PYTHON)/bin/subaligner rm -f .$(PYTHON)/bin/subaligner_1pass rm -f .$(PYTHON)/bin/subaligner_2pass + rm -f .$(PYTHON)/bin/subaligner_batch + rm -f .$(PYTHON)/bin/subaligner_convert rm -f .$(PYTHON)/bin/subaligner_train rm -f .$(PYTHON)/bin/subaligner_tune @@ -57,7 +63,7 @@ test: cat requirements.txt | xargs -L 1 .$(PYTHON)/bin/pip install; \ cat requirements-dev.txt | xargs -L 1 .$(PYTHON)/bin/pip install PYTHONPATH=. .$(PYTHON)/bin/python -m unittest discover - -.$(PYTHON)/bin/pycodestyle subaligner tests examples misc bin/subaligner bin/subaligner_1pass bin/subaligner_2pass bin/subaligner_train bin/subaligner_tune setup.py --ignore=E203,E501,W503 --exclude="subaligner/lib" + -.$(PYTHON)/bin/pycodestyle subaligner tests examples misc bin/subaligner bin/subaligner_1pass bin/subaligner_2pass bin/subaligner_batch bin/subaligner_convert bin/subaligner_train bin/subaligner_tune setup.py --ignore=E203,E501,W503 --exclude="subaligner/lib" test-all: ## run tests on every Python version with tox .$(PYTHON)/bin/tox @@ -79,17 +85,18 @@ pydoc: clean-doc ## generate pydoc HTML documentation based on docstrings cat requirements.txt | xargs -L 1 .$(PYTHON)/bin/pip install; \ .$(PYTHON)/bin/python -m pydoc -w subaligner; mv subaligner.html docs/index.html .$(PYTHON)/bin/python -m pydoc -w subaligner.embedder; mv subaligner.embedder.html docs + .$(PYTHON)/bin/python -m pydoc -w subaligner.exception; mv subaligner.exception.html docs .$(PYTHON)/bin/python -m pydoc -w subaligner.hparam_tuner; mv subaligner.hparam_tuner.html docs .$(PYTHON)/bin/python -m pydoc -w subaligner.hyperparameters; mv subaligner.hyperparameters.html docs + .$(PYTHON)/bin/python -m pydoc -w subaligner.logger; mv subaligner.logger.html docs .$(PYTHON)/bin/python -m pydoc -w subaligner.media_helper; mv subaligner.media_helper.html docs .$(PYTHON)/bin/python -m pydoc -w subaligner.network; mv subaligner.network.html docs .$(PYTHON)/bin/python -m pydoc -w subaligner.predictor; mv subaligner.predictor.html docs .$(PYTHON)/bin/python -m pydoc -w subaligner.singleton; mv subaligner.singleton.html docs + .$(PYTHON)/bin/python -m pydoc -w subaligner.subtitle; mv subaligner.subtitle.html docs .$(PYTHON)/bin/python -m pydoc -w subaligner.trainer; mv subaligner.trainer.html docs + .$(PYTHON)/bin/python -m pydoc -w subaligner.translator; mv subaligner.translator.html docs .$(PYTHON)/bin/python -m pydoc -w subaligner.utils; mv subaligner.utils.html docs - .$(PYTHON)/bin/python -m pydoc -w subaligner.subtitle; mv subaligner.subtitle.html docs - .$(PYTHON)/bin/python -m pydoc -w subaligner.logger; mv subaligner.logger.html docs - .$(PYTHON)/bin/python -m pydoc -w subaligner.exception; mv subaligner.exception.html docs .$(PYTHON)/bin/python -m pydoc -w subaligner._version; mv subaligner._version.html docs $(BROWSER) docs/index.html diff --git a/README.md b/README.md index 966e8d0..f2ba8b0 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,12 @@ $ subaligner -m single -v video.mp4 -s subtitle.srt -t src,tgt $ subaligner -m dual -v video.mp4 -s subtitle.srt -t src,tgt ``` ``` +# Run batch alignment + +$ subaligner_batch -m single -vd /videos -sd /subtitles -od /aligned_subtitles +$ subaligner_batch -m dual -vd /videos -sd /subtitles -od /aligned_subtitles +``` +``` # Run alignments with pipx $ pipx run subaligner -m single -v video.mp4 -s subtitle.srt @@ -116,7 +122,7 @@ $ docker run -it baxtree/subaligner subaligner_1pass -v https://example.com/vide $ docker run -it baxtree/subaligner subaligner_2pass -v https://example.com/video.mp4 -s https://example.com/subtitle.srt -o subtitle_aligned.srt ``` The aligned subtitle will be saved at `subtitle_aligned.srt`. For details on CLI, run `subaligner_1pass -h`, `subaligner_2pass -h` or `subaligner -h`. -Additional utilities can be used after consulting `subaligner_convert -h`, `subaligner_train -h` and `subaligner_tune -h`. +Additional utilities can be used after consulting `subaligner_batch -h`, `subaligner_convert -h`, `subaligner_train -h` and `subaligner_tune -h`. ![](figures/screencast.gif) ## Supported Formats diff --git a/bin/subaligner_batch b/bin/subaligner_batch new file mode 120000 index 0000000..6039b36 --- /dev/null +++ b/bin/subaligner_batch @@ -0,0 +1 @@ +../subaligner/subaligner_batch/__main__.py \ No newline at end of file diff --git a/setup.py b/setup.py index 3f57c21..18814d6 100644 --- a/setup.py +++ b/setup.py @@ -40,6 +40,7 @@ "subaligner.lib", "subaligner.subaligner_1pass", "subaligner.subaligner_2pass", + "subaligner.subaligner_batch", "subaligner.subaligner_convert", "subaligner.subaligner_train", "subaligner.subaligner_tune", @@ -59,6 +60,7 @@ "bin/subaligner", "bin/subaligner_1pass", "bin/subaligner_2pass", + "bin/subaligner_batch", "bin/subaligner_convert", "bin/subaligner_train", "bin/subaligner_tune", @@ -68,6 +70,7 @@ "subaligner=subaligner.__main__:main", "subaligner_1pass=subaligner.subaligner_1pass.__main__:main", "subaligner_2pass=subaligner.subaligner_2pass.__main__:main", + "subaligner_batch=subaligner.subaligner_batch.__main__:main", "subaligner_convert=subaligner.subaligner_convert.__main__:main", "subaligner_train=subaligner.subaligner_train.__main__:main", "subaligner_tune=subaligner.subaligner_tune.__main__:main", diff --git a/site/source/installation.rst b/site/source/installation.rst index 3017aeb..b6fbfab 100644 --- a/site/source/installation.rst +++ b/site/source/installation.rst @@ -54,6 +54,8 @@ to create a virtual environment and set up all the dependencies: (.venv) $ subaligner --help (.venv) $ subaligner_1pass --help (.venv) $ subaligner_2pass --help + (.venv) $ subaligner_batch --help + (.venv) $ subaligner_convert --help (.venv) $ subaligner_train --help (.venv) $ subaligner_tune --help diff --git a/site/source/usage.rst b/site/source/usage.rst index f412d23..f241739 100644 --- a/site/source/usage.rst +++ b/site/source/usage.rst @@ -36,6 +36,11 @@ Make sure you have got the virtual environment activated upfront. (.venv) $ subaligner -m single -v video.mp4 -s subtitle.srt -t src,tgt (.venv) $ subaligner -m dual -v video.mp4 -s subtitle.srt -t src,tgt +**Run batch alignment**:: + + (.venv) $ subaligner_batch -m single -vd /videos -sd /subtitles -od /aligned_subtitles + (.venv) $ subaligner_batch -m dual -vd /videos -sd /subtitles -od /aligned_subtitles + **Run alignments with the docker image**:: $ docker pull baxtree/subaligner diff --git a/subaligner/__main__.py b/subaligner/__main__.py index 14da6ec..7c56637 100755 --- a/subaligner/__main__.py +++ b/subaligner/__main__.py @@ -140,18 +140,23 @@ def main(): sys.exit(0) if FLAGS.mode == "": print("--mode was not passed in") + parser.print_usage() sys.exit(21) if FLAGS.video_path == "": print("--video_path was not passed in") + parser.print_usage() sys.exit(21) if FLAGS.subtitle_path == "": print("--subtitle_path was not passed in") + parser.print_usage() sys.exit(21) if FLAGS.subtitle_path.lower().startswith("http") and FLAGS.output == "": print("--output was not passed in for alignment on a remote subtitle file") + parser.print_usage() sys.exit(21) if FLAGS.subtitle_path.lower().startswith("teletext:") and FLAGS.output == "": print("--output was not passed in for alignment on embedded subtitles") + parser.print_usage() sys.exit(21) local_video_path = FLAGS.video_path @@ -194,6 +199,7 @@ def main(): Utils.extract_matroska_subtitle(local_video_path, int(params["stream_index"]), local_subtitle_path) else: print("Embedded subtitle selector cannot be empty") + parser.print_usage() sys.exit(21) predictor = Predictor() diff --git a/subaligner/subaligner_1pass/__main__.py b/subaligner/subaligner_1pass/__main__.py index 12882f4..9fbec7c 100755 --- a/subaligner/subaligner_1pass/__main__.py +++ b/subaligner/subaligner_1pass/__main__.py @@ -103,15 +103,19 @@ def main(): sys.exit(0) if FLAGS.video_path == "": print("--video_path was not passed in") + parser.print_usage() sys.exit(21) if FLAGS.subtitle_path == "": print("--subtitle_path was not passed in") + parser.print_usage() sys.exit(21) if FLAGS.subtitle_path.lower().startswith("http") and FLAGS.output == "": print("--output was not passed in for alignment on a remote subtitle file") + parser.print_usage() sys.exit(21) if FLAGS.subtitle_path.lower().startswith("teletext:") and FLAGS.output == "": print("--output was not passed in for alignment on embedded subtitles") + parser.print_usage() sys.exit(21) local_video_path = FLAGS.video_path @@ -151,6 +155,7 @@ def main(): Utils.extract_matroska_subtitle(local_video_path, int(params["stream_index"]), local_subtitle_path) else: print("Embedded subtitle selector cannot be empty") + parser.print_usage() sys.exit(21) predictor = Predictor() diff --git a/subaligner/subaligner_2pass/__main__.py b/subaligner/subaligner_2pass/__main__.py index 90ce1a9..500011f 100755 --- a/subaligner/subaligner_2pass/__main__.py +++ b/subaligner/subaligner_2pass/__main__.py @@ -130,15 +130,19 @@ def main(): sys.exit(0) if FLAGS.video_path == "": print("--video_path was not passed in") + parser.print_usage() sys.exit(21) if FLAGS.subtitle_path == "": print("--subtitle_path was not passed in") + parser.print_usage() sys.exit(21) if FLAGS.subtitle_path.lower().startswith("http") and FLAGS.output == "": print("--output was not passed in for alignment on a remote subtitle file") + parser.print_usage() sys.exit(21) if FLAGS.subtitle_path.lower().startswith("teletext:") and FLAGS.output == "": print("--output was not passed in for alignment on embedded subtitles") + parser.print_usage() sys.exit(21) local_video_path = FLAGS.video_path @@ -181,6 +185,7 @@ def main(): Utils.extract_matroska_subtitle(local_video_path, int(params["stream_index"]), local_subtitle_path) else: print("Embedded subtitle selector cannot be empty") + parser.print_usage() sys.exit(21) predictor = Predictor() diff --git a/subaligner/subaligner_batch/__init__.py b/subaligner/subaligner_batch/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/subaligner/subaligner_batch/__main__.py b/subaligner/subaligner_batch/__main__.py new file mode 100755 index 0000000..c8da2c6 --- /dev/null +++ b/subaligner/subaligner_batch/__main__.py @@ -0,0 +1,267 @@ +#!/usr/bin/env python +""" +usage: subaligner_batch [-h] [-m {single,dual}] [-vd VIDEO_DIRECTORY] [-sd SUBTITLE_DIRECTORY] [-l MAX_LOGLOSS] [-so] + [-sil {afr,amh,ara,arg,asm,aze,ben,bos,bul,cat,ces,cmn,cym,dan,deu,ell,eng,epo,est,eus,fas,fin,fra,gla,gle,glg,grc,grn,guj,heb,hin,hrv,hun,hye,ina,ind,isl,ita,jbo,jpn,kal,kan,kat,kir,kor,kur,lat,lav,lfn,lit,mal,mar,mkd,mlt,msa,mya,nah,nep,nld,nor,ori,orm,pan,pap,pol,por,ron,rus,sin,slk,slv,spa,sqi,srp,swa,swe,tam,tat,tel,tha,tsn,tur,ukr,urd,vie,yue,zho}] + [-fos] [-tod TRAINING_OUTPUT_DIRECTORY] [-od OUTPUT_DIRECTORY] [-t TRANSLATE] [-lgs] [-d] [-q] [-ver] + +Batch align multiple subtitle files and audiovisual files (v0.1.4) + +Subtitle files and their companion audiovisual files need to be stored in two separate directories. +Each file pair needs to share the same base filename, the part before the extension. + +optional arguments: + -h, --help show this help message and exit + -vd VIDEO_DIRECTORY, --video_directory VIDEO_DIRECTORY + Path to the video directory + -sd SUBTITLE_DIRECTORY, --subtitle_directory SUBTITLE_DIRECTORY + Path to the subtitle directory + -l MAX_LOGLOSS, --max_logloss MAX_LOGLOSS + Max global log loss for alignment + -so, --stretch_off Switch off stretch on non-English speech and subtitles) + -sil {afr,amh,ara,arg,asm,aze,ben,bos,bul,cat,ces,cmn,cym,dan,deu,ell,eng,epo,est,eus,fas,fin,fra,gla,gle,glg,grc,grn,guj,heb,hin,hrv,hun,hye,ina,ind,isl,ita,jbo,jpn,kal,kan,kat,kir,kor,kur,lat,lav,lfn,lit,mal,mar,mkd,mlt,msa,mya,nah,nep,nld,nor,ori,orm,pan,pap,pol,por,ron,rus,sin,slk,slv,spa,sqi,srp,swa,swe,tam,tat,tel,tha,tsn,tur,ukr,urd,vie,yue,zho}, --stretch_in_language {afr,amh,ara,arg,asm,aze,ben,bos,bul,cat,ces,cmn,cym,dan,deu,ell,eng,epo,est,eus,fas,fin,fra,gla,gle,glg,grc,grn,guj,heb,hin,hrv,hun,hye,ina,ind,isl,ita,jbo,jpn,kal,kan,kat,kir,kor,kur,lat,lav,lfn,lit,mal,mar,mkd,mlt,msa,mya,nah,nep,nld,nor,ori,orm,pan,pap,pol,por,ron,rus,sin,slk,slv,spa,sqi,srp,swa,swe,tam,tat,tel,tha,tsn,tur,ukr,urd,vie,yue,zho} + Stretch the subtitle with the supported ISO 639-3 language code [https://en.wikipedia.org/wiki/List_of_ISO_639-3_codes]. + NB: This will be ignored if either -so or --stretch_off is present + -fos, --exit_segfail Exit on any segment alignment failures + -tod TRAINING_OUTPUT_DIRECTORY, --training_output_directory TRAINING_OUTPUT_DIRECTORY + Path to the output directory containing training results + -od OUTPUT_DIRECTORY, --output_directory OUTPUT_DIRECTORY + Path to the output subtitle directory + -t TRANSLATE, --translate TRANSLATE + Source and target ISO 639-3 language codes separated by a comma (e.g., eng,zho) + -lgs, --languages Print out language codes used for stretch and translation + -d, --debug Print out debugging information + -q, --quiet Switch off logging information + -ver, --version show program's version number and exit + +required arguments: + -m {single,dual}, --mode {single,dual} + Alignment mode: either single or dual +""" + +import argparse +import sys +import traceback +import os + + +def main(): + if sys.version_info.major != 3: + print("Cannot find Python 3") + sys.exit(20) + try: + import subaligner + except ModuleNotFoundError: + print("Subaligner is not installed") + sys.exit(20) + + from subaligner._version import __version__ + parser = argparse.ArgumentParser(description="""Batch align multiple subtitle files and audiovisual files (v%s)\n +Subtitle files and their companion audiovisual files need to be stored in two separate directories. +Each file pair needs to share the same base filename, the part before the extension.""" % __version__, formatter_class=argparse.RawTextHelpFormatter) + required_args = parser.add_argument_group("required arguments") + required_args.add_argument( + "-m", + "--mode", + type=str, + default="", + choices=["single", "dual"], + help="Alignment mode: either single or dual", + ) + parser.add_argument( + "-vd", + "--video_directory", + type=str, + default="", + help="Path to the video directory", + ) + parser.add_argument( + "-sd", + "--subtitle_directory", + type=str, + default="", + help="Path to the subtitle directory", + ) + parser.add_argument( + "-l", + "--max_logloss", + type=float, + default=float("inf"), + help="Max global log loss for alignment", + ) + parser.add_argument( + "-so", + "--stretch_off", + action="store_true", + help="Switch off stretch on non-English speech and subtitles)", + ) + from subaligner.utils import Utils + parser.add_argument( + "-sil", + "--stretch_in_language", + type=str, + choices=Utils.get_stretch_language_codes(), + default="eng", + help="Stretch the subtitle with the supported ISO 639-3 language code [https://en.wikipedia.org/wiki/List_of_ISO_639-3_codes].\nNB: This will be ignored if either -so or --stretch_off is present", + ) + parser.add_argument( + "-fos", + "--exit_segfail", + action="store_true", + help="Exit on any segment alignment failures", + ) + parser.add_argument( + "-tod", + "--training_output_directory", + type=str, + default=os.path.abspath(os.path.dirname(subaligner.__file__)), + help="Path to the output directory containing training results", + ) + parser.add_argument( + "-od", + "--output_directory", + type=str, + default="", + help="Path to the output subtitle directory", + ) + parser.add_argument( + "-t", + "--translate", + type=str, + help="Source and target ISO 639-3 language codes separated by a comma (e.g., eng,zho)", + ) + parser.add_argument("-lgs", "--languages", action="store_true", + help="Print out language codes used for stretch and translation") + parser.add_argument("-d", "--debug", action="store_true", + help="Print out debugging information") + parser.add_argument("-q", "--quiet", action="store_true", + help="Switch off logging information") + parser.add_argument("-ver", "--version", action="version", version=__version__) + FLAGS, unparsed = parser.parse_known_args() + + if FLAGS.languages: + print("\n".join(Utils.get_language_table())) + sys.exit(0) + if FLAGS.mode == "": + print("--mode was not passed in") + parser.print_usage() + sys.exit(21) + if FLAGS.video_directory == "": + print("--video_directory was not passed in") + parser.print_usage() + sys.exit(21) + if FLAGS.subtitle_directory == "": + print("--subtitle_directory was not passed in") + parser.print_usage() + sys.exit(21) + if FLAGS.output_directory == "": + print("--output_directory was not passed in") + parser.print_usage() + sys.exit(21) + if os.path.abspath(FLAGS.subtitle_directory) == os.path.abspath(FLAGS.output_directory): + print("The output directory cannot be set to the same as the input subtitle directory") + parser.print_usage() + sys.exit(21) + + video_file_paths = [os.path.abspath(os.path.join(path, p)) for path, _, files in + os.walk(FLAGS.video_directory) for p in files if not p.startswith(".")] + subtitle_file_paths = [os.path.abspath(os.path.join(path, p)) for path, _, files in + os.walk(FLAGS.subtitle_directory) for p in files if not p.startswith(".")] + if len(video_file_paths) != len(subtitle_file_paths): + print("The numbers of input videos and subtitles do not match") + parser.print_usage() + sys.exit(21) + + output_dir = os.path.abspath(FLAGS.output_directory) + os.makedirs(output_dir, exist_ok=True) + video_file_paths = sorted(video_file_paths, key=lambda x: os.path.splitext(os.path.basename(x))[0]) + subtitle_file_paths = sorted(subtitle_file_paths, key=lambda x: os.path.splitext(os.path.basename(x))[0]) + exit_segfail = FLAGS.exit_segfail + stretch = not FLAGS.stretch_off + stretch_in_lang = FLAGS.stretch_in_language + + from subaligner.logger import Logger + Logger.VERBOSE = FLAGS.debug + Logger.QUIET = FLAGS.quiet + from subaligner.predictor import Predictor + from subaligner.translator import Translator + from subaligner.subtitle import Subtitle + from subaligner.exception import UnsupportedFormatException + from subaligner.exception import TerminalException + + predictor = Predictor() + failures = [] + for index in range(len(video_file_paths)): + local_video_path = video_file_paths[index] + local_subtitle_path = subtitle_file_paths[index] + try: + if FLAGS.mode == "single": + aligned_subs, audio_file_path, voice_probabilities, frame_rate = predictor.predict_single_pass( + video_file_path=local_video_path, + subtitle_file_path=local_subtitle_path, + weights_dir=os.path.join(FLAGS.training_output_directory, "models/training/weights") + ) + else: + aligned_subs, subs, voice_probabilities, frame_rate = predictor.predict_dual_pass( + video_file_path=local_video_path, + subtitle_file_path=local_subtitle_path, + weights_dir=os.path.join(FLAGS.training_output_directory, "models/training/weights"), + stretch=stretch, + stretch_in_lang=stretch_in_lang, + exit_segfail=exit_segfail, + ) + + parent_dir = os.path.dirname(local_subtitle_path.replace(os.path.abspath(FLAGS.subtitle_directory), output_dir)) + os.makedirs(parent_dir, exist_ok=True) + aligned_subtitle_path = os.path.abspath(os.path.join(parent_dir, + ".".join(os.path.basename(local_subtitle_path).rsplit(".", 1)).replace(".stl", ".srt"))) + + if FLAGS.translate is not None: + source, target = FLAGS.translate.split(",") + translator = Translator(source, target) + aligned_subs = translator.translate(aligned_subs) + Subtitle.export_subtitle(local_subtitle_path, aligned_subs, aligned_subtitle_path, frame_rate, "utf-8") + else: + Subtitle.export_subtitle(local_subtitle_path, aligned_subs, aligned_subtitle_path, frame_rate) + + log_loss = predictor.get_log_loss(voice_probabilities, aligned_subs) + if log_loss is None or log_loss > FLAGS.max_logloss: + print( + "Alignment failed with a too high loss value: {} for {} and {}".format(log_loss, local_video_path, local_subtitle_path) + ) + failures.append((local_video_path, local_subtitle_path)) + continue + + print("Aligned subtitle saved to: {}".format(aligned_subtitle_path)) + except UnsupportedFormatException as e: + print( + "{}\n{}".format(str(e), "".join(traceback.format_stack()) if FLAGS.debug else "") + ) + traceback.print_tb(e.__traceback__) + failures.append((local_video_path, local_subtitle_path)) + continue + except TerminalException as e: + print( + "{}\n{}".format(str(e), "".join(traceback.format_stack()) if FLAGS.debug else "") + ) + traceback.print_tb(e.__traceback__) + failures.append((local_video_path, local_subtitle_path)) + continue + except Exception as e: + print( + "{}\n{}".format(str(e), "".join(traceback.format_stack()) if FLAGS.debug else "") + ) + traceback.print_tb(e.__traceback__) + failures.append((local_video_path, local_subtitle_path)) + continue + else: + continue + + if len(failures) > 0: + print("WARNING: The following video and subtitle failed to align with each other:") + for failure in failures: + video_file_path, subtitle_file_path = failure + print("\t{} {}".format(video_file_path, subtitle_file_path)) + + +if __name__ == "__main__": + main() diff --git a/subaligner/subaligner_convert/__main__.py b/subaligner/subaligner_convert/__main__.py index 0955bf2..b0dd741 100755 --- a/subaligner/subaligner_convert/__main__.py +++ b/subaligner/subaligner_convert/__main__.py @@ -86,12 +86,16 @@ def main(): sys.exit(0) if FLAGS.input_subtitle_path == "": print("--input_subtitle_path was not passed in") + parser.print_usage() sys.exit(21) if FLAGS.output_subtitle_path == "": print("--output_subtitle_path was not passed in") + parser.print_usage() sys.exit(21) if FLAGS.output_subtitle_path.endswith(".sub") and FLAGS.frame_rate is None: print("--frame_rate was not passed in for conversion to MicroDVD") + parser.print_usage() + sys.exit(21) local_subtitle_path = FLAGS.input_subtitle_path diff --git a/subaligner/subaligner_train/__main__.py b/subaligner/subaligner_train/__main__.py index 87880e6..7b25043 100755 --- a/subaligner/subaligner_train/__main__.py +++ b/subaligner/subaligner_train/__main__.py @@ -209,9 +209,11 @@ def main(): if FLAGS.training_output_directory == "": print("--training_output_directory was not passed in") + parser.print_usage() sys.exit(21) if FLAGS.sound_effect_end_marker is not None and FLAGS.sound_effect_start_marker is None: print("--sound_effect_start_marker was not passed in when --sound_effect_end_marker was in use") + parser.print_usage() sys.exit(21) verbose = FLAGS.debug @@ -243,12 +245,18 @@ def main(): if not FLAGS.resume: if FLAGS.video_directory == "": print("--video_directory was not passed in") + parser.print_usage() sys.exit(21) if FLAGS.subtitle_directory == "": print("--subtitle_directory was not passed in") + parser.print_usage() sys.exit(21) - video_file_paths = [os.path.abspath(os.path.join(FLAGS.video_directory, p)) for p in os.listdir(FLAGS.video_directory) if not p.startswith(".")] - subtitle_file_paths = [os.path.abspath(os.path.join(FLAGS.subtitle_directory, p)) for p in os.listdir(FLAGS.subtitle_directory) if not p.startswith(".")] + video_file_paths = [os.path.abspath(os.path.join(path, p)) for path, _, files in + os.walk(FLAGS.video_directory) for p in files if not p.startswith(".")] + video_file_paths = sorted(video_file_paths, key=lambda x: os.path.splitext(os.path.basename(x))[0]) + subtitle_file_paths = [os.path.abspath(os.path.join(path, p)) for path, _, files in + os.walk(FLAGS.subtitle_directory) for p in files if not p.startswith(".")] + subtitle_file_paths = sorted(subtitle_file_paths, key=lambda x: os.path.splitext(os.path.basename(x))[0]) if FLAGS.use_training_dump: print("Use data dump from previous training and passed-in video and subtitle directories will be ignored") video_file_paths = subtitle_file_paths = None diff --git a/subaligner/subaligner_tune/__main__.py b/subaligner/subaligner_tune/__main__.py index 7a2d3b4..ee124d5 100755 --- a/subaligner/subaligner_tune/__main__.py +++ b/subaligner/subaligner_tune/__main__.py @@ -104,12 +104,15 @@ def main(): if FLAGS.video_directory == "": print("--video_directory was not passed in") + parser.print_usage() sys.exit(21) if FLAGS.subtitle_directory == "": print("--subtitle_directory was not passed in") + parser.print_usage() sys.exit(21) if FLAGS.training_output_directory == "": print("--training_output_directory was not passed in") + parser.print_usage() sys.exit(21) verbose = FLAGS.debug diff --git a/tests/integration/feature/subaligner.feature b/tests/integration/feature/subaligner.feature index e764790..024d7cb 100644 --- a/tests/integration/feature/subaligner.feature +++ b/tests/integration/feature/subaligner.feature @@ -208,6 +208,18 @@ Feature: Subaligner CLI | subaligner_1pass | | "test.srt" | eng,fra | "test_aligned.srt" | | subaligner_2pass | | "test.srt" | eng,deu | "test_aligned.srt" | + @batch + Scenario Outline: Test batch alignment + Given I have an audiovisual file directory "av" + And I have a subtitle file directory "sub" + And I want to save the alignment output in directory + When I run the subaligner_batch on them with stage + Then a new subtitle file is generated in the above output directory + Examples: + | output_dir | mode | subtitle-out | + | "aligned_single" | single | "test.srt" | + | "aligned_dual" | dual | "test.srt" | + @exception Scenario Outline: Test errors out on unsupported subtitle input Given I have a video file "test.mp4" diff --git a/tests/integration/feature/subaligner_train.feature b/tests/integration/feature/subaligner_train.feature index 8408703..b3cf6ad 100644 --- a/tests/integration/feature/subaligner_train.feature +++ b/tests/integration/feature/subaligner_train.feature @@ -6,7 +6,7 @@ Feature: Subaligner CLI Scenario: Test training on the LSTM network Given I have an audiovisual file directory "av" And I have a subtitle file directory "sub" - And I want to save the output in directory "output" + And I want to save the training output in directory "output" When I run the subaligner_train against them with the following options """ -bs 10 -do 0.5 -e 2 -p 1 -fhs 10 -bhs 5,2 -lr 0.01 -nt lstm -vs 0.3 -o adam @@ -19,7 +19,7 @@ Feature: Subaligner CLI Scenario: Test training on the Bidirectional LSTM network Given I have an audiovisual file directory "av" And I have a subtitle file directory "sub" - And I want to save the output in directory "output" + And I want to save the training output in directory "output" When I run the subaligner_train against them with the following options """ -bs 10 -do 0.5 -e 3 -p 1 -fhs 10 -bhs 5,2 -lr 0.01 -nt bi_lstm -vs 0.3 -o adam @@ -32,7 +32,7 @@ Feature: Subaligner CLI Scenario: Test training on the Conv1D network Given I have an audiovisual file directory "av" And I have a subtitle file directory "sub" - And I want to save the output in directory "output" + And I want to save the training output in directory "output" When I run the subaligner_train against them with the following options """ -bs 10 -do 0.5 -e 2 -p 1 -fhs 10 -bhs 5,2 -lr 0.01 -nt conv_1d -vs 0.3 -o adam @@ -45,7 +45,7 @@ Feature: Subaligner CLI Scenario: Test ignoring sound effects during on training Given I have an audiovisual file directory "av" And I have a subtitle file directory "sub" - And I want to save the output in directory "output" + And I want to save the training output in directory "output" When I run the subaligner_train against them with the following options """ -e 2 -nt lstm --sound_effect_start_marker "(" --sound_effect_end_marker ")" @@ -58,7 +58,7 @@ Feature: Subaligner CLI Scenario: Test erroring on sound_effect_end_marker used alone Given I have an audiovisual file directory "av" And I have a subtitle file directory "sub" - And I want to save the output in directory "output" + And I want to save the training output in directory "output" When I run the subaligner_train against them with the following options """ -e 2 -nt lstm --sound_effect_end_marker ")" @@ -69,7 +69,7 @@ Feature: Subaligner CLI Scenario: Test hyperparameter tuning on the LSTM network Given I have an audiovisual file directory "av" And I have a subtitle file directory "sub" - And I want to save the output in directory "output" + And I want to save the training output in directory "output" When I run the subaligner_tune against them with the following flags | epoch_per_trail | trails | network_type | | 1 | 2 | lstm | @@ -79,7 +79,7 @@ Feature: Subaligner CLI Scenario: Test hyperparameter tuning on the Bidirectional LSTM network Given I have an audiovisual file directory "av" And I have a subtitle file directory "sub" - And I want to save the output in directory "output" + And I want to save the training output in directory "output" When I run the subaligner_tune against them with the following flags | epoch_per_trail | trails | network_type | | 2 | 1 | bi_lstm | @@ -89,7 +89,7 @@ Feature: Subaligner CLI Scenario: Test hyperparameter tuning on the Conv1D network Given I have an audiovisual file directory "av" And I have a subtitle file directory "sub" - And I want to save the output in directory "output" + And I want to save the training output in directory "output" When I run the subaligner_tune against them with the following flags | epoch_per_trail | trails | network_type | | 1 | 2 | conv_1d | diff --git a/tests/integration/radish/step.py b/tests/integration/radish/step.py index 0323987..d8df79a 100644 --- a/tests/integration/radish/step.py +++ b/tests/integration/radish/step.py @@ -241,7 +241,7 @@ def subtitle_dir(step, sub_dir): step.context.sub_dir = os.path.join(PWD, "..", "..", "subaligner", "resource", sub_dir) -@given('I want to save the output in directory "{output_dir:S}"') +@given('I want to save the training output in directory "{output_dir:S}"') def output_dir(step, output_dir): step.context.training_output = os.path.join(step.context.temp_dir, output_dir) @@ -336,12 +336,36 @@ def run_subtitle_converter_with_translation(step, language_pair, output_subtitle step.context.exit_code = process.wait(timeout=WAIT_TIMEOUT_IN_SECONDS) -@before.each_scenario(on_tags="train or hyperparameter-tuning") +@given('I want to save the alignment output in directory "{aligned_dir:S}"') +def aligned_dir(step, aligned_dir): + step.context.aligning_output = os.path.join(step.context.temp_dir, aligned_dir) + + +@when('I run the subaligner_batch on them with {mode:S} stage') +def run_subaligner_batch(step, mode): + process = subprocess.Popen([ + os.path.join(PWD, "..", "..", "..", "bin", "subaligner_batch"), + "-m", mode, + "-vd", step.context.av_dir, + "-sd", step.context.sub_dir, + "-od", step.context.aligning_output, + "-q"], shell=False) + step.context.exit_code = process.wait(timeout=WAIT_TIMEOUT_IN_SECONDS) + + +@then('a new subtitle file "{file_name:S}" is generated in the above output directory') +def expect_result_in_aligning_output(step, file_name): + output_file_path = os.path.join(step.context.aligning_output, file_name) + assert step.context.exit_code == 0 + assert os.path.isfile(output_file_path) is True + + +@before.each_scenario(on_tags="train or hyperparameter-tuning or batch") def create_training_output_dir(scenario): scenario.context.temp_dir = tempfile.mkdtemp() -@after.each_scenario(on_tags="train or hyperparameter-tuning") +@after.each_scenario(on_tags="train or hyperparameter-tuning or batch") def remove_training_output_dir(scenario): if os.path.isdir(scenario.context.temp_dir): shutil.rmtree(scenario.context.temp_dir) diff --git a/tests/subaligner/resource/sub/test.srt b/tests/subaligner/resource/sub/test.srt index 6ed4b14..6eeafb8 100644 --- a/tests/subaligner/resource/sub/test.srt +++ b/tests/subaligner/resource/sub/test.srt @@ -56,849 +56,4 @@ that luck is rarely a lightning strike, 15 00:00:59,942 --> 00:01:01,696 -isolated and dramatic. - -16 -00:01:02,249 --> 00:01:04,635 -It's much more like the wind, - -17 -00:01:04,659 --> 00:01:06,225 -blowing constantly. - -18 -00:01:06,249 --> 00:01:07,818 -Sometimes it's calm, - -19 -00:01:07,842 --> 00:01:09,943 -and sometimes it blows in gusts, - -20 -00:01:09,967 --> 00:01:14,192 -and sometimes it comes from directions that you didn't even imagine. - -21 -00:01:14,216 --> 00:01:16,689 -So how do you catch the winds of luck? - -22 -00:01:17,616 --> 00:01:20,195 -It's easy, but it's not obvious. - -23 -00:01:20,665 --> 00:01:23,615 -So I'm going to share three things with you - -24 -00:01:23,639 --> 00:01:28,823 -that you can do to build a sail to capture the winds of luck. - -25 -00:01:29,651 --> 00:01:31,406 -The first thing you want to do - -26 -00:01:31,430 --> 00:01:33,935 -is to change your relationship with yourself. - -27 -00:01:33,959 --> 00:01:38,071 -Be willing to take small risks that get you out of your comfort zone. - -28 -00:01:38,095 --> 00:01:41,491 -Now, when we're children, we do this all the time. - -29 -00:01:42,459 --> 00:01:45,698 -We have to do this if we're going to learn how to walk or talk - -30 -00:01:45,722 --> 00:01:46,880 -or ride a bike - -31 -00:01:46,904 --> 00:01:48,848 -or even quantum mechanics. Right? - -32 -00:01:48,872 --> 00:01:51,796 -We need to go from someone one week who doesn't ride a bike - -33 -00:01:51,820 --> 00:01:53,603 -to, next week, someone who does. - -34 -00:01:53,627 --> 00:01:56,181 -And this requires us to get out of our comfort zone - -35 -00:01:56,205 --> 00:01:58,008 -and take some risks. - -36 -00:01:58,032 --> 00:02:00,151 -The problem is, as we get older, - -37 -00:02:00,175 --> 00:02:02,056 -we rarely do this. - -38 -00:02:02,080 --> 00:02:04,327 -We sort of lock down the sense of who we are - -39 -00:02:04,351 --> 00:02:06,149 -and don't stretch anymore. - -40 -00:02:06,173 --> 00:02:07,771 -Now, with my students, - -41 -00:02:07,795 --> 00:02:10,651 -I spend a lot of time giving them encouragement - -42 -00:02:10,675 --> 00:02:13,183 -to get out of their comfort zone and take some risks. - -43 -00:02:13,787 --> 00:02:14,952 -How do I do this? - -44 -00:02:14,976 --> 00:02:18,848 -Well, I start out by having them fill out a risk-o-meter. - -45 -00:02:18,872 --> 00:02:21,652 -Now, it's basically a fun thing we developed in our class - -46 -00:02:21,676 --> 00:02:24,706 -where they map out what risks they're willing to take. - -47 -00:02:24,730 --> 00:02:27,634 -And it becomes clear very quickly to them - -48 -00:02:27,658 --> 00:02:29,734 -that risk-taking is not binary. - -49 -00:02:30,242 --> 00:02:33,893 -There are intellectual risks and physical risks and financial risks - -50 -00:02:33,917 --> 00:02:37,905 -and emotional risks and social risks and ethical risks and political risks. - -51 -00:02:37,929 --> 00:02:41,714 -And once they do this, they compare their risk profiles with others, - -52 -00:02:41,738 --> 00:02:45,040 -and they quickly realize that they're all really different. - -53 -00:02:46,006 --> 00:02:48,562 -I then encourage them to stretch, - -54 -00:02:48,586 --> 00:02:51,475 -to take some risks that get them out of their comfort zone. - -55 -00:02:51,499 --> 00:02:54,854 -For example, I might ask them to do an intellectual risk - -56 -00:02:54,878 --> 00:02:57,942 -and try to tackle a problem they haven't tried before; - -57 -00:02:57,966 --> 00:03:02,465 -or a social risk, talking to someone sitting next to them on the train; - -58 -00:03:02,489 --> 00:03:04,220 -or an emotional risk, - -59 -00:03:04,244 --> 00:03:07,185 -maybe telling someone they really care about how they feel. - -60 -00:03:08,306 --> 00:03:10,751 -I do this myself all the time. - -61 -00:03:11,286 --> 00:03:13,691 -About a dozen years ago, I was on an airplane, - -62 -00:03:13,715 --> 00:03:16,476 -early, early morning flight on my way to Ecuador. - -63 -00:03:16,500 --> 00:03:22,222 -And normally, I would just put on my headphones - -64 -00:03:22,246 --> 00:03:24,177 -and go to sleep, wake up, do some work, - -65 -00:03:24,201 --> 00:03:25,960 -but I decided to take a little risk, - -66 -00:03:25,984 --> 00:03:28,842 -and I started a conversation with the man sitting next to me. - -67 -00:03:28,866 --> 00:03:32,073 -I introduced myself, and I learned that he was a publisher. - -68 -00:03:33,695 --> 00:03:34,856 -Interesting. - -69 -00:03:34,880 --> 00:03:37,128 -We ended up having a fascinating conversation. - -70 -00:03:37,152 --> 00:03:39,945 -I learned all about the future of the publishing industry. - -71 -00:03:39,969 --> 00:03:43,160 -So about three quarters of the way through the flight, - -72 -00:03:43,184 --> 00:03:44,965 -I decided to take another risk, - -73 -00:03:44,989 --> 00:03:48,949 -and I opened up my laptop and I shared with him a book proposal - -74 -00:03:48,973 --> 00:03:52,949 -I put together for something I was doing in my class. - -75 -00:03:52,973 --> 00:03:54,697 -And he was very polite, he read it, - -76 -00:03:54,721 --> 00:03:57,483 -and he said, "You know what, Tina, this isn't right for us, - -77 -00:03:57,507 --> 00:03:59,174 -but thank you so much for sharing." - -78 -00:03:59,198 --> 00:04:01,231 -It's OK. That risk didn't work out. - -79 -00:04:01,745 --> 00:04:03,021 -I shut my laptop. - -80 -00:04:03,045 --> 00:04:06,046 -At the end of the flight, we exchanged contact information. - -81 -00:04:07,594 --> 00:04:10,006 -A couple of months later, I reached out to him, - -82 -00:04:10,030 --> 00:04:12,672 -and I said, "Mark, would you like to come to my class? - -83 -00:04:12,696 --> 00:04:15,450 -I'm doing a project on reinventing the book, - -84 -00:04:15,474 --> 00:04:16,783 -the future of publishing." - -85 -00:04:16,807 --> 00:04:18,665 -And he said, "Great. I'd love to come." - -86 -00:04:18,689 --> 00:04:21,024 -So he came to my class. We had a great experience. - -87 -00:04:21,048 --> 00:04:23,147 -A few months later, I wrote to him again. - -88 -00:04:23,171 --> 00:04:25,837 -This time, I sent him a bunch of video clips - -89 -00:04:25,861 --> 00:04:28,099 -from another project my students had done. - -90 -00:04:28,948 --> 00:04:31,514 -He was so intrigued - -91 -00:04:31,538 --> 00:04:33,720 -by one of the projects the students had done, - -92 -00:04:33,744 --> 00:04:35,718 -he thought there might be a book in it, - -93 -00:04:35,742 --> 00:04:37,885 -and he wanted to meet those students. - -94 -00:04:38,224 --> 00:04:40,407 -I have to tell you, I was a little bit hurt. - -95 -00:04:41,456 --> 00:04:44,481 -I mean, he wanted to do a book with my students and not with me, - -96 -00:04:44,505 --> 00:04:45,656 -but OK, it's all right. - -97 -00:04:45,680 --> 00:04:49,187 -So I invited him to come down, and he and his colleagues came to Stanford - -98 -00:04:49,211 --> 00:04:52,655 -and met with the students, and afterwards, we had lunch together. - -99 -00:04:53,487 --> 00:04:55,283 -And one of his editors said to me, - -100 -00:04:55,307 --> 00:04:58,108 -"Hey, have you ever considered writing a book?" - -101 -00:04:58,918 --> 00:05:00,735 -I said, "Funny you should ask." - -102 -00:05:00,759 --> 00:05:03,854 -And I pulled out the exact same proposal - -103 -00:05:03,878 --> 00:05:07,242 -that I had showed his boss a year earlier. - -104 -00:05:07,266 --> 00:05:09,686 -Within two weeks, I had a contract, - -105 -00:05:09,710 --> 00:05:13,591 -and within two years, the book had sold over a million copies around the world. - -106 -00:05:16,225 --> 00:05:18,257 -Now, you might say, - -107 -00:05:18,281 --> 00:05:19,630 -"Oh, you're so lucky." - -108 -00:05:19,654 --> 00:05:20,925 -But of course I was lucky, - -109 -00:05:20,949 --> 00:05:25,273 -but that luck resulted from a series of small risks I took, - -110 -00:05:25,297 --> 00:05:27,567 -starting with saying hello. - -111 -00:05:27,591 --> 00:05:29,980 -And anyone can do this, - -112 -00:05:30,004 --> 00:05:31,767 -no matter where you are in your life, - -113 -00:05:31,791 --> 00:05:33,651 -no matter where you are in the world -- - -114 -00:05:33,675 --> 00:05:36,961 -even if you think you're the most unlucky person, - -115 -00:05:36,985 --> 00:05:41,274 -you can do this by taking little risks that get you out of your comfort zone. - -116 -00:05:41,298 --> 00:05:43,627 -You start building a sail to capture luck. - -117 -00:05:44,605 --> 00:05:46,348 -The second thing you want to do - -118 -00:05:46,372 --> 00:05:49,081 -is to change your relationship with other people. - -119 -00:05:49,105 --> 00:05:54,471 -You need to understand that everyone who helps you on your journey - -120 -00:05:54,495 --> 00:05:58,376 -is playing a huge role in getting you to your goals. - -121 -00:05:58,400 --> 00:06:01,011 -And if you don't show appreciation, - -122 -00:06:01,035 --> 00:06:03,320 -not only are you not closing the loop, - -123 -00:06:03,344 --> 00:06:05,082 -but you're missing an opportunity. - -124 -00:06:06,392 --> 00:06:08,424 -When someone does something for you, - -125 -00:06:08,448 --> 00:06:09,710 -they're taking that time - -126 -00:06:09,734 --> 00:06:12,934 -that they could be spending on themselves or someone else, - -127 -00:06:12,958 --> 00:06:15,439 -and you need to acknowledge what they're doing. - -128 -00:06:16,492 --> 00:06:20,761 -Now, I run three fellowship programs at Stanford, - -129 -00:06:20,785 --> 00:06:22,882 -and they are very competitive to get into, - -130 -00:06:22,906 --> 00:06:26,147 -and when I send out the letters to those students who don't get in, - -131 -00:06:26,171 --> 00:06:29,172 -I always know there are going to be people who are disappointed. - -132 -00:06:29,196 --> 00:06:32,376 -Some of the people who are disappointed send me notes, complaining. - -133 -00:06:32,400 --> 00:06:34,003 -Some of them send notes - -134 -00:06:34,027 --> 00:06:37,475 -saying what could I do to make myself more successful next time around? - -135 -00:06:37,499 --> 00:06:38,810 -And every once in a while, - -136 -00:06:38,834 --> 00:06:42,173 -someone sends me a note thanking me for the opportunity. - -137 -00:06:42,880 --> 00:06:44,596 -This happened about seven years ago. - -138 -00:06:44,620 --> 00:06:47,349 -A young man named Brian sent me a beautiful note saying, - -139 -00:06:47,373 --> 00:06:50,628 -"I know I've been rejected from this program twice, - -140 -00:06:50,652 --> 00:06:52,752 -but I want to thank you for the opportunity. - -141 -00:06:52,776 --> 00:06:55,423 -I learned so much through the process of applying." - -142 -00:06:56,205 --> 00:07:00,080 -I was so taken by the graciousness of his message - -143 -00:07:00,104 --> 00:07:02,190 -that I invited him to come and meet me. - -144 -00:07:02,214 --> 00:07:05,001 -And we spent some time chatting and cooked up an idea - -145 -00:07:05,025 --> 00:07:07,048 -for an independent study project together. - -146 -00:07:08,072 --> 00:07:10,041 -He was on the football team at Stanford, - -147 -00:07:10,065 --> 00:07:13,994 -and he decided to do a project on looking at leadership in that context. - -148 -00:07:14,366 --> 00:07:17,985 -We got to know each other incredibly well through that quarter, - -149 -00:07:18,009 --> 00:07:20,414 -and he took the project that he started working on - -150 -00:07:20,438 --> 00:07:21,748 -in the independent study - -151 -00:07:21,772 --> 00:07:26,252 -and turned it, ultimately, into a company called Play for Tomorrow, - -152 -00:07:26,276 --> 00:07:29,247 -where he teaches kids from disadvantaged backgrounds - -153 -00:07:29,271 --> 00:07:32,077 -how to, essentially, craft the lives they dream to live. - -154 -00:07:33,149 --> 00:07:35,998 -Now, the important thing about this story - -155 -00:07:36,022 --> 00:07:39,142 -is that we both ended up catching the winds of luck - -156 -00:07:39,166 --> 00:07:41,378 -as a result of his thank-you note. - -157 -00:07:41,402 --> 00:07:44,917 -But it was the winds that we didn't expect in the first place. - -158 -00:07:46,145 --> 00:07:48,194 -Over the course of the last couple of years, - -159 -00:07:48,218 --> 00:07:51,226 -I've come up with some tactics for my own life - -160 -00:07:51,250 --> 00:07:53,527 -to help me really foster appreciation. - -161 -00:07:54,107 --> 00:07:57,655 -My favorite is that at the end of every single day, - -162 -00:07:57,679 --> 00:08:01,425 -I look at my calendar and I review all the people I met with, - -163 -00:08:01,449 --> 00:08:04,505 -and I send thank-you notes to every single person. - -164 -00:08:04,529 --> 00:08:06,703 -It only takes a few minutes, - -165 -00:08:06,727 --> 00:08:08,506 -but at the end of every day, - -166 -00:08:08,530 --> 00:08:11,799 -I feel incredibly grateful and appreciative, - -167 -00:08:11,823 --> 00:08:14,343 -and I promise you it has increased my luck. - -168 -00:08:15,207 --> 00:08:18,806 -So first, you need to take some risks and get out of your comfort zone. - -169 -00:08:18,830 --> 00:08:21,296 -Second, you need to show appreciation. - -170 -00:08:21,320 --> 00:08:25,216 -And third, you want to change your relationship with ideas. - -171 -00:08:26,210 --> 00:08:29,518 -Most people look at new ideas that come there way and they judge them. - -172 -00:08:29,542 --> 00:08:32,298 -"That's a great idea" or "That's a terrible idea." - -173 -00:08:32,322 --> 00:08:35,306 -But it's actually much more nuanced. - -174 -00:08:36,031 --> 00:08:37,878 -Ideas are neither good or bad. - -175 -00:08:37,902 --> 00:08:42,708 -And in fact, the seeds of terrible ideas are often something truly remarkable. - -176 -00:08:44,039 --> 00:08:47,238 -One of my favorite exercises in my classes on creativity - -177 -00:08:47,262 --> 00:08:51,324 -is to help students foster an attitude of looking at terrible ideas - -178 -00:08:51,348 --> 00:08:54,503 -through the lens of possibilities. - -179 -00:08:55,157 --> 00:08:57,294 -So I give them a challenge: - -180 -00:08:57,318 --> 00:09:00,543 -to create an idea for a brand new restaurant. - -181 -00:09:01,080 --> 00:09:03,994 -They have to come up with the best ideas for a new restaurant - -182 -00:09:04,018 --> 00:09:06,587 -and the worst ideas for a new restaurant. - -183 -00:09:06,611 --> 00:09:10,070 -So the best ideas are things like a restaurant on a mountaintop - -184 -00:09:10,094 --> 00:09:11,629 -with a beautiful sunset, - -185 -00:09:11,653 --> 00:09:14,549 -or a restaurant on a boat with a gorgeous view. - -186 -00:09:14,573 --> 00:09:19,359 -And the terrible ideas are things like a restaurant in a garbage dump, - -187 -00:09:19,383 --> 00:09:23,627 -or a restaurant with terrible service that's really dirty, - -188 -00:09:23,651 --> 00:09:27,073 -or a restaurant that serves cockroach sushi. - -189 -00:09:28,445 --> 00:09:30,565 -So they hand all the ideas to me, - -190 -00:09:30,589 --> 00:09:32,832 -I read the great ideas out loud, - -191 -00:09:32,856 --> 00:09:34,921 -and then I rip them up and throw them away. - -192 -00:09:36,413 --> 00:09:39,555 -I then take the horrible ideas and redistribute them. - -193 -00:09:40,159 --> 00:09:44,722 -Each team now has an idea that another team thought was horrible, - -194 -00:09:44,746 --> 00:09:47,563 -and their challenge is to turn it into something brilliant. - -195 -00:09:48,984 --> 00:09:50,179 -Here's what happens. - -196 -00:09:50,904 --> 00:09:55,812 -Within about 10 seconds, someone says, "This is a fabulous idea." - -197 -00:09:55,836 --> 00:10:00,478 -And they have about three minutes before they pitch the idea to the class. - -198 -00:10:00,502 --> 00:10:03,789 -So the restaurant in the garbage dump? What does that turn into? - -199 -00:10:03,813 --> 00:10:07,973 -Well, they collect all the extra food from Michelin star restaurants - -200 -00:10:07,997 --> 00:10:10,301 -that was going to get thrown out, - -201 -00:10:10,325 --> 00:10:12,905 -and they have another restaurant at a much lower price, - -202 -00:10:12,929 --> 00:10:14,080 -with all the leftovers. - -203 -00:10:14,104 --> 00:10:15,261 -Pretty cool? - -204 -00:10:15,285 --> 00:10:18,233 -Or the restaurant that's dirty with terrible service? - -205 -00:10:18,689 --> 00:10:22,408 -Well, that turns into a restaurant that's a training ground - -206 -00:10:22,432 --> 00:10:26,527 -for future restauranteurs to figure out how to avoid all the pitfalls. - -207 -00:10:27,361 --> 00:10:29,298 -And the restaurant with cockroach sushi? - -208 -00:10:29,322 --> 00:10:31,452 -It turns into a sushi bar - -209 -00:10:31,476 --> 00:10:34,559 -with all sorts of really interesting and exotic ingredients. - -210 -00:10:35,922 --> 00:10:39,010 -If you look around at the companies, - -211 -00:10:39,034 --> 00:10:42,081 -the ventures that are really innovative around you, - -212 -00:10:42,105 --> 00:10:45,731 -the ones that we now take for granted that have changed our life, - -213 -00:10:45,755 --> 00:10:47,025 -well, you know what? - -214 -00:10:47,049 --> 00:10:49,930 -They all started out as crazy ideas. - -215 -00:10:49,954 --> 00:10:52,683 -They started ideas that when they pitched to other people, - -216 -00:10:52,707 --> 00:10:55,834 -most people said, "That's crazy, it will never work." - -217 -00:10:58,095 --> 00:11:03,863 -So, yes, sometimes people were born into terrible circumstances, - -218 -00:11:03,887 --> 00:11:06,172 -and sometimes, luck is a lightning bolt - -219 -00:11:06,196 --> 00:11:09,156 -that hits us with something wonderful or something terrible. - -220 -00:11:10,108 --> 00:11:14,060 -But the winds of luck are always there, - -221 -00:11:14,084 --> 00:11:17,139 -and if you're willing to take some risks, - -222 -00:11:17,163 --> 00:11:20,671 -if you're willing to really go out and show appreciation - -223 -00:11:20,695 --> 00:11:25,116 -and willing to really look at ideas, even if they're crazy, - -224 -00:11:25,140 --> 00:11:26,909 -through the lens of possibilities, - -225 -00:11:26,933 --> 00:11:31,335 -(SOUND EFFECT) - -226 -00:11:31,692 --> 00:11:32,843 -(SOUND EFFECT) - +isolated and dramatic. \ No newline at end of file