slidegen.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702
  1. #!/usr/bin/env python3
  2. """
  3. Copyright © 2022 Noah Vogt <noah@noahvogt.com>
  4. This program is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program. If not, see <http://www.gnu.org/licenses/>.
  14. """
  15. from os import name, path
  16. from abc import ABC, abstractmethod
  17. import sys
  18. import re
  19. from termcolor import colored
  20. import colorama
  21. from wand.image import Image
  22. from wand.color import Color
  23. from wand.display import display
  24. from wand.drawing import Drawing
  25. from wand.font import Font
  26. from wand.exceptions import BlobError
  27. IMAGE_FORMAT = "jpeg"
  28. FILE_EXTENSION = "jpg"
  29. FILE_NAMEING = "folie"
  30. WIDTH = 1920
  31. HEIGHT = 1080
  32. BG_COLOR = "white"
  33. FG_COLOR = "#6298a4"
  34. TITLE_COLOR = "#d8d5c4"
  35. MAX_TITLE_FONT_SIZE = 70
  36. MIN_TITLE_FONT_SIZE = 20
  37. TITLE_FONT_SIZE_STEP = 10
  38. TITLE_HEIGHT = 160
  39. TITLEBAR_Y = 65
  40. INFODISPLAY_FONT_SIZE = 25
  41. INFODISPLAY_ITEM_WIDTH = 20
  42. PLAYER_WIDTH = 560
  43. PLAYER_HEIGHT = 315
  44. BOLD_FONT_PATH = (
  45. "/usr/share/fonts/TTF/century-gothic/CenturyGothicBold.ttf"
  46. if name == "posix"
  47. else "winPATH"
  48. )
  49. FONT_PATH = (
  50. "/usr/share/fonts/TTF/century-gothic/CenturyGothic.ttf"
  51. if name == "posix"
  52. else "winPATH"
  53. )
  54. FONT = "Century-Gothic"
  55. BOLD_FONT = "Century-Gothic-Bold"
  56. TRIANGLE_WIDTH = 80
  57. TRIANGLE_HEIGTH = 160
  58. METADATA_FONT_SIZE = 36
  59. METADATA_X = 70
  60. METADATA_VALUE_CHAR_LIMIT = 100
  61. BOOK_Y = 260
  62. ATTRIBUTIONS_Y = 930
  63. TEXT_COLOR = "black"
  64. STRUCTURE_ELEMENT_X = 80
  65. STRUCTURE_ELEMENT_Y = 400
  66. STRUCTURE_ELEMENT_PER_LINE_CHAR_LIMIT = 85
  67. STRUCTURE_ELEMENT_MAX_LINES = 8
  68. TEXT_CANVAS_X = 160
  69. TEXT_CANVAS_Y = 400
  70. TEXT_CANVAS_WIDTH = 1600
  71. TEXT_CANVAS_HEIGHT = 600
  72. STRUCTURE_X = 1650
  73. STRUCTURE_Y = 1000
  74. MAX_CANVAS_FONT_SIZE = 55
  75. MIN_CANVAS_FONT_SIZE = 35
  76. CANVAS_FONT_SIZE_STEP = 5
  77. INTERLINE_SPACING = 30
  78. ARROW_HEIGHT = 50
  79. ARROW_COLOR = "black"
  80. ARROW_X = 1725
  81. ARROW_Y = 900
  82. METADATA_STRINGS = ("title", "book", "text", "melody", "structure")
  83. def error_msg(msg: str):
  84. print(colored("[*] Error: {}".format(msg), "red"))
  85. sys.exit(1)
  86. def log(message: str):
  87. print(colored("[*] {}".format(message), "green"))
  88. def get_empty_image() -> Image:
  89. img = Image(width=1, height=1, background=Color("white"))
  90. return img.clone()
  91. def structure_as_list(structure: str) -> list:
  92. return structure.replace(" ", "").split(",")
  93. def get_unique_structure_elements(structure: list) -> list:
  94. return list(dict.fromkeys(structure))
  95. def get_songtext_by_structure(content: list, structure: str) -> str:
  96. found_desired_structure: bool = False
  97. output_str: str = ""
  98. for line in content:
  99. stripped_line: str = line.strip()
  100. line_length: int = len(line)
  101. if line_length > STRUCTURE_ELEMENT_PER_LINE_CHAR_LIMIT:
  102. if line[-1] == "\n":
  103. line = line[:-1]
  104. error_msg(
  105. "line is configured to a character limit of "
  106. + str(STRUCTURE_ELEMENT_PER_LINE_CHAR_LIMIT)
  107. + " but has {} characters: \n{}".format(line_length, line)
  108. )
  109. if found_desired_structure:
  110. if stripped_line.startswith("[") and stripped_line.endswith("]"):
  111. break
  112. output_str += stripped_line + "\n"
  113. if (
  114. stripped_line.startswith("[")
  115. and stripped_line.endswith("]")
  116. and structure in stripped_line
  117. ):
  118. found_desired_structure: bool = True
  119. return output_str[:-1]
  120. class SongTemplate(ABC):
  121. @abstractmethod
  122. def get_template(self, title: str) -> Image:
  123. pass
  124. class StartSlide(ABC):
  125. @abstractmethod
  126. def get_slide(
  127. self,
  128. template_img: Image,
  129. book: str,
  130. text_author: str,
  131. melody_author: str,
  132. ):
  133. pass
  134. class SongSlide(ABC):
  135. @abstractmethod
  136. def get_slide(
  137. self,
  138. template_img: Image,
  139. slide_text: str,
  140. song_structure: list,
  141. index: int,
  142. useArrow: bool,
  143. ):
  144. pass
  145. class ClassicSongSlide(SongSlide):
  146. def get_slide(
  147. self,
  148. template_img: Image,
  149. slide_text: str,
  150. song_structure: list,
  151. index: int,
  152. useArrow: bool,
  153. ):
  154. canvas_img, font_size = self.get_text_canvas(slide_text)
  155. verse_or_chorus = song_structure[index]
  156. bg_img = template_img.clone()
  157. if "R" not in verse_or_chorus:
  158. bg_img.composite(
  159. self.get_index(verse_or_chorus, font_size),
  160. top=STRUCTURE_ELEMENT_Y,
  161. left=STRUCTURE_ELEMENT_X,
  162. )
  163. bg_img.composite(canvas_img, top=TEXT_CANVAS_Y, left=TEXT_CANVAS_X)
  164. if useArrow:
  165. bg_img.composite(self.get_arrow(), top=ARROW_Y, left=ARROW_X)
  166. bg_img.composite(
  167. self.get_structure_info_display(song_structure, index),
  168. top=STRUCTURE_Y,
  169. left=STRUCTURE_X,
  170. )
  171. return bg_img.clone()
  172. def get_arrow(self) -> Image:
  173. with Drawing() as draw:
  174. draw.stroke_width = 1
  175. draw.stroke_color = Color(ARROW_COLOR)
  176. draw.fill_color = Color(ARROW_COLOR)
  177. arrow_width = ARROW_HEIGHT * 3 // 2
  178. draw.path_start()
  179. draw.path_move(to=(0, ARROW_HEIGHT / 2 - ARROW_HEIGHT / 10))
  180. draw.path_line(to=(0, ARROW_HEIGHT / 2 + ARROW_HEIGHT / 10))
  181. draw.path_line(
  182. to=(arrow_width / 3 * 2, ARROW_HEIGHT / 2 + ARROW_HEIGHT / 10)
  183. )
  184. draw.path_line(to=(arrow_width / 3 * 2, ARROW_HEIGHT))
  185. draw.path_line(to=(arrow_width, ARROW_HEIGHT / 2))
  186. draw.path_line(to=(arrow_width / 3 * 2, 0))
  187. draw.path_line(
  188. to=(arrow_width / 3 * 2, ARROW_HEIGHT / 2 - ARROW_HEIGHT / 10)
  189. )
  190. draw.path_close()
  191. draw.path_finish()
  192. with Image(
  193. width=arrow_width,
  194. height=ARROW_HEIGHT,
  195. background=Color(BG_COLOR),
  196. ) as image:
  197. draw(image)
  198. return image.clone()
  199. def get_text_canvas(self, slide_text: str) -> tuple:
  200. font_size = MAX_CANVAS_FONT_SIZE
  201. while font_size >= MIN_CANVAS_FONT_SIZE:
  202. with Drawing() as draw:
  203. draw.fill_color = Color(TEXT_COLOR)
  204. draw.text_interline_spacing = INTERLINE_SPACING
  205. draw.font_size = font_size
  206. draw.font = FONT
  207. draw.text(0, font_size, slide_text)
  208. with Image(
  209. width=WIDTH, height=HEIGHT, background=Color(BG_COLOR)
  210. ) as img:
  211. draw(img)
  212. img.trim()
  213. if (
  214. img.width > TEXT_CANVAS_WIDTH
  215. or img.height > TEXT_CANVAS_HEIGHT
  216. ):
  217. font_size -= CANVAS_FONT_SIZE_STEP
  218. else:
  219. return img.clone(), font_size
  220. return get_empty_image(), 0
  221. def get_structure_info_display(self, structure: list, index: int) -> Image:
  222. with Drawing() as draw:
  223. draw.fill_color = Color(TEXT_COLOR)
  224. draw.font_size = INFODISPLAY_FONT_SIZE
  225. draw.font = FONT
  226. for current_index, item in enumerate(structure):
  227. if current_index == index:
  228. draw.font = BOLD_FONT
  229. draw.text(
  230. current_index * INFODISPLAY_ITEM_WIDTH,
  231. INFODISPLAY_FONT_SIZE,
  232. item,
  233. )
  234. draw.font = FONT
  235. else:
  236. draw.text(
  237. current_index * INFODISPLAY_ITEM_WIDTH,
  238. INFODISPLAY_FONT_SIZE,
  239. item,
  240. )
  241. with Image(
  242. width=WIDTH, height=HEIGHT, background=Color(BG_COLOR)
  243. ) as img:
  244. draw(img)
  245. img.trim()
  246. return img.clone()
  247. def get_index(self, verse: str, font_size: int) -> Image:
  248. with Image(
  249. width=WIDTH, height=HEIGHT, background=Color(BG_COLOR)
  250. ) as img:
  251. img.caption(
  252. verse + ".",
  253. font=Font(FONT_PATH, size=font_size, color=Color(TEXT_COLOR)),
  254. )
  255. img.trim()
  256. return img.clone()
  257. class ClassicStartSlide(StartSlide):
  258. def get_slide(
  259. self,
  260. template_img: Image,
  261. book: str,
  262. text_author: str,
  263. melody_author: str,
  264. ):
  265. start_img = template_img.clone()
  266. start_img.composite(
  267. self.get_attributions(text_author, melody_author),
  268. left=METADATA_X,
  269. top=ATTRIBUTIONS_Y,
  270. )
  271. start_img.composite(self.get_book(book), left=METADATA_X, top=BOOK_Y)
  272. return start_img.clone()
  273. def get_metadata(self, text: str) -> Image:
  274. with Image(
  275. width=WIDTH, height=HEIGHT, background=Color(BG_COLOR)
  276. ) as img:
  277. img.caption(
  278. text,
  279. font=Font(
  280. FONT_PATH, size=METADATA_FONT_SIZE, color=Color(TEXT_COLOR)
  281. ),
  282. )
  283. img.trim()
  284. return img.clone()
  285. def get_attributions(self, text_author: str, melody_author: str) -> Image:
  286. if text_author == melody_author:
  287. return self.get_metadata("Text & Melodie: " + text_author)
  288. return self.get_metadata(
  289. "Text: " + text_author + "\nMelodie: " + melody_author
  290. )
  291. def get_book(self, book: str) -> Image:
  292. return self.get_metadata(book)
  293. class ClassicSongTemplate(SongTemplate):
  294. def __init__(self):
  295. self.song_template = ""
  296. def get_base_image(self) -> Image:
  297. with Image(
  298. width=WIDTH, height=HEIGHT, background=Color(BG_COLOR)
  299. ) as img:
  300. return img.clone()
  301. def get_titlebar_rectangle(self, text: str) -> Image:
  302. font_size = MAX_TITLE_FONT_SIZE
  303. while font_size >= MIN_TITLE_FONT_SIZE:
  304. with Image(
  305. width=WIDTH, height=TITLE_HEIGHT, background=Color(FG_COLOR)
  306. ) as img:
  307. img.caption(
  308. text,
  309. font=Font(
  310. BOLD_FONT_PATH, size=font_size, color=Color(TITLE_COLOR)
  311. ),
  312. )
  313. img.trim()
  314. img.border(color=Color(FG_COLOR), width=30, height=0)
  315. trimmed_img_width = img.width
  316. trimmed_img_height = img.height
  317. concat_height = int((TITLE_HEIGHT - trimmed_img_height) / 2)
  318. correction_heigt = (
  319. TRIANGLE_HEIGTH - trimmed_img_height - (2 * concat_height)
  320. )
  321. concatenated_img = Image(
  322. width=trimmed_img_width,
  323. height=concat_height,
  324. background=Color(FG_COLOR),
  325. )
  326. concatenated_img.sequence.append(img)
  327. concatenated_img.sequence.append(
  328. Image(
  329. width=trimmed_img_width,
  330. height=concat_height + correction_heigt,
  331. background=Color(FG_COLOR),
  332. )
  333. )
  334. concatenated_img.concat(stacked=True)
  335. if concatenated_img.width > (
  336. WIDTH - PLAYER_WIDTH - TRIANGLE_WIDTH
  337. ):
  338. font_size -= TITLE_FONT_SIZE_STEP
  339. continue
  340. return concatenated_img.clone()
  341. return get_empty_image()
  342. def get_template(self, title: str) -> Image:
  343. titlebar_rectangle = self.get_titlebar_rectangle(title)
  344. titlebar_rectangle.sequence.append(self.get_titlebar_triangle())
  345. titlebar_rectangle.concat(stacked=False)
  346. base_img = self.get_base_image()
  347. base_img.composite(titlebar_rectangle, top=TITLEBAR_Y)
  348. self.song_template = base_img.clone()
  349. return base_img.clone()
  350. def get_titlebar_triangle(self) -> Image:
  351. with Drawing() as draw:
  352. draw.fill_color = Color(FG_COLOR)
  353. draw.path_start()
  354. draw.path_move(to=(TRIANGLE_WIDTH, 0))
  355. draw.path_line(to=(0, 0))
  356. draw.path_line(to=(0, TRIANGLE_HEIGTH))
  357. draw.path_close()
  358. draw.path_finish()
  359. with Image(
  360. width=TRIANGLE_WIDTH,
  361. height=TRIANGLE_HEIGTH,
  362. background=Color(BG_COLOR),
  363. ) as img:
  364. draw(img)
  365. return img.clone()
  366. def display(self):
  367. display(self.song_template)
  368. class Slidegen:
  369. def __init__(self, song_template_form, start_slide_form, song_slide_form):
  370. self.metadata: dict = {"": ""}
  371. self.songtext: dict = {"": ""}
  372. self.song_file_path: str = ""
  373. self.song_file_content: list = []
  374. self.output_dir: str = ""
  375. self.chosen_structure: list | str = ""
  376. self.generated_slides: list = []
  377. self.song_template_form = song_template_form
  378. self.start_slide_form = start_slide_form
  379. self.song_slide_form = song_slide_form
  380. self.parse_argv()
  381. def execute(self):
  382. self.parse_file()
  383. self.calculate_desired_structures()
  384. self.generate_slides()
  385. def generate_slides(self):
  386. song_template = self.song_template_form()
  387. log("generating template...")
  388. template_img = song_template.get_template(self.metadata["title"])
  389. first_slide = self.start_slide_form()
  390. log("generating start slide...")
  391. start_slide_img = first_slide.get_slide(
  392. template_img,
  393. self.metadata["book"],
  394. self.metadata["text"],
  395. self.metadata["melody"],
  396. )
  397. start_slide_img.format = IMAGE_FORMAT
  398. try:
  399. start_slide_img.save(
  400. filename=path.join(
  401. self.output_dir, FILE_NAMEING + "1." + FILE_EXTENSION
  402. )
  403. )
  404. except BlobError:
  405. error_msg("could not write start slide to target directory")
  406. log("generating song slides...")
  407. # unique_structures: list = list(set(self.chosen_structure))
  408. # count number of slides to be generated
  409. slide_count: int = 0
  410. for structure in self.chosen_structure:
  411. line_count: int = len(self.songtext[structure].splitlines())
  412. if line_count > STRUCTURE_ELEMENT_MAX_LINES:
  413. slide_count += line_count // STRUCTURE_ELEMENT_MAX_LINES + 1
  414. else:
  415. slide_count += 1
  416. current_slide_index: int = 0
  417. for index, structure in enumerate(self.chosen_structure):
  418. structure_element_splitted: list = self.songtext[
  419. structure
  420. ].splitlines()
  421. line_count = len(structure_element_splitted)
  422. use_line_ranges_per_index = []
  423. use_lines_per_index = []
  424. if line_count <= STRUCTURE_ELEMENT_MAX_LINES:
  425. inner_slide_count = 1
  426. else:
  427. inner_slide_count: int = (
  428. line_count // STRUCTURE_ELEMENT_MAX_LINES + 1
  429. )
  430. use_lines_per_index = [
  431. line_count // inner_slide_count
  432. ] * inner_slide_count
  433. for inner_slide in range(inner_slide_count):
  434. if sum(use_lines_per_index) == line_count:
  435. break
  436. use_lines_per_index[inner_slide] = (
  437. use_lines_per_index[inner_slide] + 1
  438. )
  439. for inner_slide in range(inner_slide_count):
  440. use_line_ranges_per_index.append(
  441. sum(use_lines_per_index[:inner_slide])
  442. )
  443. for inner_slide in range(inner_slide_count):
  444. current_slide_index += 1
  445. log(
  446. "generating song slide [{} / {}]...".format(
  447. current_slide_index, slide_count
  448. )
  449. )
  450. if inner_slide_count == 1:
  451. structure_element_value: str = self.songtext[structure]
  452. else:
  453. splitted_wanted_range: list = structure_element_splitted[
  454. use_line_ranges_per_index[
  455. inner_slide
  456. ] : use_line_ranges_per_index[inner_slide]
  457. + use_lines_per_index[inner_slide]
  458. ]
  459. structure_element_value: str = ""
  460. for element in splitted_wanted_range:
  461. structure_element_value += element + "\n"
  462. structure_element_value = structure_element_value[:-1]
  463. song_slide = self.song_slide_form()
  464. song_slide_img = song_slide.get_slide(
  465. template_img,
  466. structure_element_value,
  467. self.chosen_structure,
  468. index,
  469. bool(
  470. inner_slide_count != 1
  471. and inner_slide != inner_slide_count - 1
  472. ),
  473. )
  474. song_slide_img.format = IMAGE_FORMAT
  475. try:
  476. song_slide_img.save(
  477. filename=path.join(
  478. self.output_dir,
  479. FILE_NAMEING
  480. + str(current_slide_index + 1)
  481. + "."
  482. + FILE_EXTENSION,
  483. )
  484. )
  485. except BlobError:
  486. error_msg("could not write slide to target directory")
  487. def parse_file(self):
  488. self.parse_metadata()
  489. self.parse_songtext()
  490. def parse_metadata(self):
  491. metadata_dict = dict.fromkeys(METADATA_STRINGS)
  492. try:
  493. with open(self.song_file_path, mode="r", encoding="utf8") as opener:
  494. content = opener.readlines()
  495. except IOError:
  496. error_msg(
  497. "could not read the the song input file: '{}'".format(
  498. self.song_file_path
  499. )
  500. )
  501. valid_metadata_strings = list(METADATA_STRINGS)
  502. for line_nr, line in enumerate(content):
  503. if len(valid_metadata_strings) == 0:
  504. content = content[line_nr:]
  505. break
  506. if not re.match(
  507. r"^(?!structure)\S+: .+|^structure: ([0-9]+|R)(,([0-9]+|R))+$",
  508. line,
  509. ):
  510. if line[-1] == "\n":
  511. line = line[:-1]
  512. missing_metadata_strs = ""
  513. for metadata_str in valid_metadata_strings:
  514. missing_metadata_strs += ", " + metadata_str
  515. missing_metadata_strs = missing_metadata_strs[2:]
  516. error_msg(
  517. "invalid metadata syntax on line {}:\n{}\nThe ".format(
  518. line_nr + 1, line
  519. )
  520. + "following metadata strings are still missing: {}".format(
  521. missing_metadata_strs
  522. )
  523. )
  524. metadata_str = line[: line.index(":")]
  525. if metadata_str in valid_metadata_strings:
  526. metadata_dict[metadata_str] = line[line.index(": ") + 2 : -1]
  527. valid_metadata_strings.remove(metadata_str)
  528. continue
  529. error_msg("invalid metadata string '{}'".format(metadata_str))
  530. self.metadata = metadata_dict
  531. self.song_file_content = content
  532. def parse_songtext(self):
  533. unique_structures = get_unique_structure_elements(
  534. structure_as_list(self.metadata["structure"])
  535. )
  536. output_dict = dict.fromkeys(unique_structures)
  537. for structure in unique_structures:
  538. output_dict[structure] = get_songtext_by_structure(
  539. self.song_file_content, structure
  540. )
  541. self.songtext = output_dict
  542. def calculate_desired_structures(self):
  543. full_structure_str = str(self.metadata["structure"])
  544. full_structure_list = structure_as_list(full_structure_str)
  545. if len(self.chosen_structure) == 0:
  546. self.chosen_structure = structure_as_list(full_structure_str)
  547. log("chosen structure: {}".format(str(self.chosen_structure)))
  548. return
  549. if not "-" in self.chosen_structure:
  550. self.chosen_structure = structure_as_list(
  551. str(self.chosen_structure)
  552. )
  553. log("chosen structure: {}".format(str(self.chosen_structure)))
  554. return
  555. dash_index = str(self.chosen_structure).find("-")
  556. start_verse = str(self.chosen_structure[:dash_index]).strip()
  557. end_verse = str(self.chosen_structure[dash_index + 1 :]).strip()
  558. try:
  559. if int(start_verse) >= int(end_verse):
  560. error_msg("{} < {} must be true".format(start_verse, end_verse))
  561. if start_verse not in full_structure_str:
  562. error_msg("structure {} unknown".format(start_verse))
  563. if end_verse not in full_structure_str:
  564. error_msg("structure {} unknown".format(end_verse))
  565. except (ValueError, IndexError):
  566. error_msg("please choose a valid integer for the song structure")
  567. start_index = full_structure_list.index(start_verse)
  568. if start_index != 0:
  569. if (
  570. full_structure_list[0] == "R"
  571. and full_structure_list[start_index - 1] == "R"
  572. ):
  573. start_index -= 1
  574. end_index = full_structure_list.index(end_verse)
  575. if end_index != len(full_structure_list) - 1:
  576. if (
  577. full_structure_list[-1] == "R"
  578. and full_structure_list[end_index + 1] == "R"
  579. ):
  580. end_index += 1
  581. self.chosen_structure = full_structure_list[start_index : end_index + 1]
  582. log("chosen structure: {}".format(str(self.chosen_structure)))
  583. def parse_argv(self):
  584. try:
  585. self.song_file_path = sys.argv[1]
  586. self.output_dir = sys.argv[2]
  587. except IndexError:
  588. error_msg("incorrect amount of arguments provided, exiting...")
  589. try:
  590. self.chosen_structure = sys.argv[3]
  591. if self.chosen_structure.strip() == "":
  592. self.chosen_structure = ""
  593. except IndexError:
  594. self.chosen_structure = ""
  595. log("parsing {}...".format(self.song_file_path))
  596. def main():
  597. colorama.init()
  598. slidegen = Slidegen(
  599. ClassicSongTemplate, ClassicStartSlide, ClassicSongSlide
  600. )
  601. slidegen.execute()
  602. if __name__ == "__main__":
  603. main()