ScriptCollection 3.5.107__py3-none-any.whl → 3.5.109__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -628,3 +628,29 @@ def CurrentUserHasElevatedPrivileges() -> int:
628
628
  return 1
629
629
  else:
630
630
  return 0
631
+
632
+
633
+ def Espoc() -> int:
634
+ GeneralUtilities.write_message_to_stdout("check...")
635
+ parser = argparse.ArgumentParser(description="Espoc (appreviation for 'exit started programs on close') is a tool to ensure the started processes of your program will also get terminated when the execution of your program is finished.")
636
+ parser.add_argument('-p', '--processid', required=True)
637
+ parser.add_argument('-f', '--file', required=True, help='Specifies the file where the process-ids of the started processes are stored (line by line). This file will be deleted when all started processes are terminated.')
638
+ args = parser.parse_args()
639
+ process_id = args.processid
640
+ process_list_file: str = args.file
641
+ if not os.path.isabs(process_list_file):
642
+ process_list_file = GeneralUtilities.resolve_relative_path(process_list_file, os.getcwd())
643
+ GeneralUtilities.assert_condition(GeneralUtilities.process_is_running_by_id(process_id), f"Process with id {process_id} is not running.")
644
+ while GeneralUtilities.process_is_running_by_id(process_id):
645
+ time.sleep(1)
646
+ GeneralUtilities.write_message_to_stdout(f"Process with id {process_id} is not running anymore. Start terminating remaining processes.")
647
+ if os.path.exists(process_list_file):
648
+ for line in GeneralUtilities.read_lines_from_file(process_list_file):
649
+ if GeneralUtilities.string_has_content(line):
650
+ current_process_id = int(line.strip())
651
+ GeneralUtilities.kill_process(current_process_id, True)
652
+ GeneralUtilities.ensure_file_does_not_exist(process_list_file)
653
+ GeneralUtilities.write_message_to_stdout("All started processes terminated.")
654
+ else:
655
+ GeneralUtilities.write_message_to_stdout(f"File '{process_list_file}' does not exist. No processes to terminate.")
656
+ return 0
@@ -33,7 +33,7 @@ from .ProgramRunnerBase import ProgramRunnerBase
33
33
  from .ProgramRunnerPopen import ProgramRunnerPopen
34
34
  from .ProgramRunnerEpew import ProgramRunnerEpew, CustomEpewArgument
35
35
 
36
- version = "3.5.107"
36
+ version = "3.5.109"
37
37
  __version__ = version
38
38
 
39
39
 
@@ -603,6 +603,12 @@ class ScriptCollectionCore:
603
603
  self.assert_is_git_repository(repository_folder)
604
604
  return self.run_program_argsasarray("git", ["rev-parse", "--verify", "HEAD"], repository_folder, throw_exception_if_exitcode_is_not_zero=False)[0] == 0
605
605
 
606
+ @GeneralUtilities.check_arguments
607
+ def run_git_command_in_repository_and_submodules(self, repository_folder: str, arguments: list[str]) -> None:
608
+ self.assert_is_git_repository(repository_folder)
609
+ self.run_program_argsasarray("git", arguments, repository_folder)
610
+ self.run_program_argsasarray("git", ["submodule", "foreach", "--recursive", "git"]+arguments, repository_folder)
611
+
606
612
  @GeneralUtilities.check_arguments
607
613
  def export_filemetadata(self, folder: str, target_file: str, encoding: str = "utf-8", filter_function=None) -> None:
608
614
  folder = GeneralUtilities.resolve_relative_path_from_current_working_directory(folder)
@@ -881,7 +887,7 @@ class ScriptCollectionCore:
881
887
 
882
888
  @GeneralUtilities.check_arguments
883
889
  def __create_thumbnails(self, filename: str, fps: str, folder: str, tempname_for_thumbnails: str) -> list[str]:
884
- argument = ['-i', filename, '-r', str(fps), '-vf', 'scale=-1:120', '-vcodec', 'png', f'{tempname_for_thumbnails}-%002d.png']
890
+ argument = ['-i', filename, '-r', fps, '-vf', 'scale=-1:120', '-vcodec', 'png', f'{tempname_for_thumbnails}-%002d.png']
885
891
  self.run_program_argsasarray("ffmpeg", argument, folder, throw_exception_if_exitcode_is_not_zero=True)
886
892
  files = GeneralUtilities.get_direct_files_of_folder(folder)
887
893
  result: list[str] = []
@@ -898,8 +904,17 @@ class ScriptCollectionCore:
898
904
  def __create_thumbnail(self, outputfilename: str, folder: str, length_in_seconds: float, tempname_for_thumbnails: str, amount_of_images: int) -> None:
899
905
  duration = timedelta(seconds=length_in_seconds)
900
906
  info = GeneralUtilities.timedelta_to_simple_string(duration)
901
- rows: int = 5
902
- columns: int = math.ceil(amount_of_images/rows)
907
+ next_square_number = GeneralUtilities.get_next_square_number(amount_of_images)
908
+ root = math.sqrt(next_square_number)
909
+ rows: int = root # 5
910
+ columns: int = root # math.ceil(amount_of_images/rows)
911
+ argument = ['-title', f'"{outputfilename} ({info})"', '-tile', f'{rows}x{columns}', f'{tempname_for_thumbnails}*.png', f'{outputfilename}.png']
912
+ self.run_program_argsasarray("montage", argument, folder, throw_exception_if_exitcode_is_not_zero=True)
913
+
914
+ @GeneralUtilities.check_arguments
915
+ def __create_thumbnail2(self, outputfilename: str, folder: str, length_in_seconds: float, rows: int, columns: int, tempname_for_thumbnails: str, amount_of_images: int) -> None:
916
+ duration = timedelta(seconds=length_in_seconds)
917
+ info = GeneralUtilities.timedelta_to_simple_string(duration)
903
918
  argument = ['-title', f'"{outputfilename} ({info})"', '-tile', f'{rows}x{columns}', f'{tempname_for_thumbnails}*.png', f'{outputfilename}.png']
904
919
  self.run_program_argsasarray("montage", argument, folder, throw_exception_if_exitcode_is_not_zero=True)
905
920
 
@@ -912,7 +927,7 @@ class ScriptCollectionCore:
912
927
  return math.ceil(x * d) / d
913
928
 
914
929
  @GeneralUtilities.check_arguments
915
- def generate_thumbnail(self, file: str, frames_per_second: str, tempname_for_thumbnails: str = None, hook=None) -> None:
930
+ def generate_thumbnail(self, file: str, frames_per_second: float, tempname_for_thumbnails: str = None, hook=None) -> None:
916
931
  if tempname_for_thumbnails is None:
917
932
  tempname_for_thumbnails = "t_"+str(uuid.uuid4())
918
933
 
@@ -923,16 +938,9 @@ class ScriptCollectionCore:
923
938
  preview_files: list[str] = []
924
939
  try:
925
940
  length_in_seconds = self.__calculate_lengh_in_seconds(filename, folder)
926
- if (frames_per_second.endswith("fps")):
927
- # frames per second, example: frames_per_second="20fps" => 20 frames per second
928
- frames_per_second = self.__roundup(float(frames_per_second[:-3]), 2)
929
- frames_per_second_as_string = str(frames_per_second)
930
- amounf_of_previewframes = int(math.floor(length_in_seconds*frames_per_second))
931
- else:
932
- # concrete amount of frame, examples: frames_per_second="16" => 16 frames for entire video
933
- amounf_of_previewframes = int(float(frames_per_second))
934
- # self.roundup((amounf_of_previewframes-2)/length_in_seconds, 2)
935
- frames_per_second_as_string = f"{amounf_of_previewframes-2}/{length_in_seconds}"
941
+ # frames per second, example: frames_per_second="20fps" => 20 frames per second
942
+ frames_per_second = self.__roundup(float(frames_per_second[:-3]), 2)
943
+ frames_per_second_as_string = str(frames_per_second)
936
944
  preview_files = self.__create_thumbnails(filename, frames_per_second_as_string, folder, tempname_for_thumbnails)
937
945
  if hook is not None:
938
946
  hook(file, preview_files)
@@ -942,6 +950,29 @@ class ScriptCollectionCore:
942
950
  for thumbnail_to_delete in preview_files:
943
951
  os.remove(thumbnail_to_delete)
944
952
 
953
+ @GeneralUtilities.check_arguments
954
+ def generate_thumbnail_by_amount_of_pictures(self, file: str, amount_of_columns: int, amount_of_rows: int, tempname_for_thumbnails: str = None, hook=None) -> None:
955
+ if tempname_for_thumbnails is None:
956
+ tempname_for_thumbnails = "t_"+str(uuid.uuid4())
957
+
958
+ file = GeneralUtilities.resolve_relative_path_from_current_working_directory(file)
959
+ filename = os.path.basename(file)
960
+ folder = os.path.dirname(file)
961
+ filename_without_extension = Path(file).stem
962
+ preview_files: list[str] = []
963
+ try:
964
+ length_in_seconds = self.__calculate_lengh_in_seconds(filename, folder)
965
+ amounf_of_previewframes = int(amount_of_columns*amount_of_rows)
966
+ frames_per_second_as_string = f"{amounf_of_previewframes-2}/{length_in_seconds}"
967
+ preview_files = self.__create_thumbnails(filename, frames_per_second_as_string, folder, tempname_for_thumbnails)
968
+ if hook is not None:
969
+ hook(file, preview_files)
970
+ actual_amounf_of_previewframes = len(preview_files)
971
+ self.__create_thumbnail2(filename_without_extension, folder, length_in_seconds, amount_of_rows, amount_of_columns, tempname_for_thumbnails, actual_amounf_of_previewframes)
972
+ finally:
973
+ for thumbnail_to_delete in preview_files:
974
+ os.remove(thumbnail_to_delete)
975
+
945
976
  @GeneralUtilities.check_arguments
946
977
  def extract_pdf_pages(self, file: str, from_page: int, to_page: int, outputfile: str) -> None:
947
978
  pdf_reader: PdfReader = PdfReader(file)
@@ -1025,8 +1025,9 @@ class TasksForCommonProjectStructure:
1025
1025
 
1026
1026
  if codeunit_has_testcases:
1027
1027
  source_testcoveragereport = os.path.join(other_folder_in_repository, "Artifacts", "TestCoverageReport")
1028
- target_testcoveragereport = os.path.join(target_folder, "TestCoverageReport")
1029
- shutil.copytree(source_testcoveragereport, target_testcoveragereport)
1028
+ if os.path.isdir(source_testcoveragereport): # check, because it is not a mandatory artifact. if the artifact is not available, the user gets already a warning.
1029
+ target_testcoveragereport = os.path.join(target_folder, "TestCoverageReport")
1030
+ shutil.copytree(source_testcoveragereport, target_testcoveragereport)
1030
1031
 
1031
1032
  @GeneralUtilities.check_arguments
1032
1033
  def __standardized_tasks_release_artifact(self, information: CreateReleaseInformationForProjectInCommonProjectFormat) -> None:
@@ -1560,9 +1561,9 @@ class TasksForCommonProjectStructure:
1560
1561
  combined_file = os.path.join(codeunit_folder, file)
1561
1562
  if not os.path.isfile(combined_file):
1562
1563
  raise ValueError(f'The mandatory file "{file}" does not exist in the codeunit-folder.')
1563
-
1564
- if os.path.isfile(os.path.join(codeunit_folder,"Other","requirements.txt")):
1565
- self.install_requirementstxt_for_codeunit(codeunit_folder,verbosity)
1564
+
1565
+ if os.path.isfile(os.path.join(codeunit_folder, "Other", "requirements.txt")):
1566
+ self.install_requirementstxt_for_codeunit(codeunit_folder, verbosity)
1566
1567
 
1567
1568
  # Check developer
1568
1569
  if self.validate_developers_of_repository:
@@ -2271,9 +2272,9 @@ class TasksForCommonProjectStructure:
2271
2272
  def update_dependencies_of_typical_python_repository_requirements(self, repository_folder: str, verbosity: int, cmd_args: list[str]) -> None:
2272
2273
  verbosity = self.get_verbosity_from_commandline_arguments(cmd_args, verbosity)
2273
2274
 
2274
- development_requirements_file = os.path.join(repository_folder, "Other","requirements.txt")
2275
+ development_requirements_file = os.path.join(repository_folder, "Other", "requirements.txt")
2275
2276
  if (os.path.isfile(development_requirements_file)):
2276
- self.__sc.update_dependencies_of_python_in_requirementstxt_file(development_requirements_file,[], verbosity)
2277
+ self.__sc.update_dependencies_of_python_in_requirementstxt_file(development_requirements_file, [], verbosity)
2277
2278
 
2278
2279
  @GeneralUtilities.check_arguments
2279
2280
  def update_dependencies_of_typical_python_codeunit(self, update_script_file: str, verbosity: int, cmd_args: list[str]) -> None:
@@ -2282,17 +2283,17 @@ class TasksForCommonProjectStructure:
2282
2283
  # TODO consider ignored_dependencies
2283
2284
  verbosity = self.get_verbosity_from_commandline_arguments(cmd_args, verbosity)
2284
2285
 
2285
- setup_cfg=os.path.join(codeunit_folder, "setup.cfg")
2286
+ setup_cfg = os.path.join(codeunit_folder, "setup.cfg")
2286
2287
  if (os.path.isfile(setup_cfg)):
2287
- self.__sc.update_dependencies_of_python_in_setupcfg_file(setup_cfg,ignored_dependencies, verbosity)
2288
+ self.__sc.update_dependencies_of_python_in_setupcfg_file(setup_cfg, ignored_dependencies, verbosity)
2288
2289
 
2289
- development_requirements_file = os.path.join(codeunit_folder, "requirements.txt")#required for codeunits which contain python-code which need third-party dependencies
2290
+ development_requirements_file = os.path.join(codeunit_folder, "requirements.txt") # required for codeunits which contain python-code which need third-party dependencies
2290
2291
  if (os.path.isfile(development_requirements_file)):
2291
- self.__sc.update_dependencies_of_python_in_requirementstxt_file(development_requirements_file,ignored_dependencies, verbosity)
2292
+ self.__sc.update_dependencies_of_python_in_requirementstxt_file(development_requirements_file, ignored_dependencies, verbosity)
2292
2293
 
2293
- development_requirements_file2 = os.path.join(codeunit_folder, "Other","requirements.txt")#required for codeunits which contain python-scripts which needs third-party dependencies
2294
+ development_requirements_file2 = os.path.join(codeunit_folder, "Other", "requirements.txt") # required for codeunits which contain python-scripts which needs third-party dependencies
2294
2295
  if (os.path.isfile(development_requirements_file2)):
2295
- self.__sc.update_dependencies_of_python_in_requirementstxt_file(development_requirements_file2, ignored_dependencies,verbosity)
2296
+ self.__sc.update_dependencies_of_python_in_requirementstxt_file(development_requirements_file2, ignored_dependencies, verbosity)
2296
2297
 
2297
2298
  @GeneralUtilities.check_arguments
2298
2299
  def update_dependencies_of_typical_dotnet_codeunit(self, update_script_file: str, verbosity: int, cmd_args: list[str]) -> None:
@@ -3292,15 +3293,15 @@ class TasksForCommonProjectStructure:
3292
3293
  self.__sc.git_commit(GeneralUtilities.resolve_relative_path("../..", folder_of_this_file), f"Updated content of {update_http_documentation_arguments.product_name} v{update_http_documentation_arguments.new_project_version} in {update_http_documentation_arguments.reference_repository_name}-submodule")
3293
3294
 
3294
3295
  @GeneralUtilities.check_arguments
3295
- def install_requirementstxt_for_codeunit(self,codeunit_folder:str,verbosity:int):
3296
+ def install_requirementstxt_for_codeunit(self, codeunit_folder: str, verbosity: int):
3296
3297
  self.__sc.install_requirementstxt_file(codeunit_folder+"/Other/requirements.txt", verbosity)
3297
3298
 
3298
3299
  @GeneralUtilities.check_arguments
3299
- def install_requirementstxt_for_repository(self, repository_folde: str,verbosity:int):
3300
+ def install_requirementstxt_for_repository(self, repository_folde: str, verbosity: int):
3300
3301
  self.__sc.install_requirementstxt_file(repository_folde+"/Other/requirements.txt", verbosity)
3301
3302
 
3302
3303
  @GeneralUtilities.check_arguments
3303
- def update_submodule(self, repository_folder: str, submodule_name:str):
3304
+ def update_submodule(self, repository_folder: str, submodule_name: str):
3304
3305
  submodule_folder = GeneralUtilities.resolve_relative_path("Other/Resources/Submodules/"+submodule_name, repository_folder)
3305
3306
  self.__sc.git_fetch(submodule_folder, "origin")
3306
3307
  self.__sc.git_checkout(submodule_folder, "main")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ScriptCollection
3
- Version: 3.5.107
3
+ Version: 3.5.109
4
4
  Summary: The ScriptCollection is the place for reusable scripts.
5
5
  Home-page: https://github.com/anionDev/ScriptCollection
6
6
  Author: Marius Göcke
@@ -24,13 +24,14 @@ Requires-Python: >=3.10
24
24
  Description-Content-Type: text/markdown
25
25
  Requires-Dist: build>=1.2.2.post1
26
26
  Requires-Dist: coverage>=7.8.0
27
- Requires-Dist: cyclonedx-bom>=5.3.0
27
+ Requires-Dist: cyclonedx-bom>=6.0.0
28
28
  Requires-Dist: defusedxml>=0.7.1
29
29
  Requires-Dist: keyboard>=0.13.5
30
30
  Requires-Dist: lcov-cobertura>=2.1.1
31
- Requires-Dist: lxml>=5.3.2
31
+ Requires-Dist: lxml>=5.4.0
32
32
  Requires-Dist: ntplib>=0.4.0
33
33
  Requires-Dist: Pillow>=11.2.1
34
+ Requires-Dist: psutil>=7.0.0
34
35
  Requires-Dist: pycdlib>=1.14.0
35
36
  Requires-Dist: Pygments>=2.19.1
36
37
  Requires-Dist: pylint>=3.3.6
@@ -1,16 +1,16 @@
1
1
  ScriptCollection/CertificateUpdater.py,sha256=pJopWFcwaLAEVljtC4O3SVrlpIpoJNUhT1V4mgiqLvE,8970
2
- ScriptCollection/Executables.py,sha256=HI9Pxs5Z9QxPGyqeJU2lWslEggFyGYANCqYVQZp6eJ0,30490
2
+ ScriptCollection/Executables.py,sha256=BsDgpqU_XKXg-7paQdmamzU14-2kcqjz9v13r96EiEk,32319
3
3
  ScriptCollection/GeneralUtilities.py,sha256=VO4a7xctkjN5dcBXc32gz0x9U07DEagasjvYVKqLmdM,44173
4
4
  ScriptCollection/ProcessesRunner.py,sha256=3mu4ZxzZleQo0Op6o9EYTCFiJfb6kx5ov2YfZfT89mU,1395
5
5
  ScriptCollection/ProgramRunnerBase.py,sha256=2kMIAqdc65UjBAddOZkzy_aFx9h5roZ5a4bQNM6RV6Y,2480
6
6
  ScriptCollection/ProgramRunnerEpew.py,sha256=4pjEd0r9Fcz3TTDv0MdTSd5KkigYXcWUVI1X43regfU,6477
7
7
  ScriptCollection/ProgramRunnerPopen.py,sha256=BPY7-ZMIlqT7JOKz8qlB5c0laF2Js-ijzqk09GxZC48,3821
8
8
  ScriptCollection/SCLog.py,sha256=Gw27Oclcb0ten7_89PD5CdNMoO-at2hGUOYbF-x1HPQ,2296
9
- ScriptCollection/ScriptCollectionCore.py,sha256=pMWerT9kxD823d4CHKLV7eyAMjRAsDdYaXEoCGDEYis,128371
10
- ScriptCollection/TasksForCommonProjectStructure.py,sha256=9rO_W6kZiy3Lgov0HeyyDhOwriorMeJXlVDvwZ3S1UE,232511
9
+ ScriptCollection/ScriptCollectionCore.py,sha256=X3jRXN0FRstFN7kPFLnzSZUONMbh_YOPr22cKzDgU78,130404
10
+ ScriptCollection/TasksForCommonProjectStructure.py,sha256=SJ7V9HilnkCBJGCCrObWwAORSI_pXW9zC4uon1O6Xbg,232705
11
11
  ScriptCollection/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
- scriptcollection-3.5.107.dist-info/METADATA,sha256=nKrrtDhb2dkTemESq47FReNNmlOsk99wSZD6GJlCvkc,7664
13
- scriptcollection-3.5.107.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
14
- scriptcollection-3.5.107.dist-info/entry_points.txt,sha256=fYCGWGNGijBQHhFe6UAO-BEpfEOxLyNJemukt5ElSzs,3644
15
- scriptcollection-3.5.107.dist-info/top_level.txt,sha256=hY2hOVH0V0Ce51WB76zKkIWTUNwMUdHo4XDkR2vYVwg,17
16
- scriptcollection-3.5.107.dist-info/RECORD,,
12
+ scriptcollection-3.5.109.dist-info/METADATA,sha256=8glqA2n3AUjhZOZV4uXRsgdyHmqgyWwcHbkLUeeKqbc,7694
13
+ scriptcollection-3.5.109.dist-info/WHEEL,sha256=ck4Vq1_RXyvS4Jt6SI0Vz6fyVs4GWg7AINwpsaGEgPE,91
14
+ scriptcollection-3.5.109.dist-info/entry_points.txt,sha256=3qMbfZEMhc_VTJj-bcLwB8AWcn9iXSB3l0AWpuu52Bs,3689
15
+ scriptcollection-3.5.109.dist-info/top_level.txt,sha256=hY2hOVH0V0Ce51WB76zKkIWTUNwMUdHo4XDkR2vYVwg,17
16
+ scriptcollection-3.5.109.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (78.1.0)
2
+ Generator: setuptools (80.0.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -15,6 +15,7 @@ sccreatehashofallfiles = ScriptCollection.Executables:CreateHashOfAllFiles
15
15
  sccreateisofilewithobfuscatedfiles = ScriptCollection.Executables:CreateISOFileWithObfuscatedFiles
16
16
  sccreatesimplemergewithoutrelease = ScriptCollection.Executables:CreateSimpleMergeWithoutRelease
17
17
  sccurrentuserhaselevatedprivileges = ScriptCollection.Executables:CurrentUserHasElevatedPrivileges
18
+ scespoc = ScriptCollection.Executables:Espoc
18
19
  scextractpdfpages = ScriptCollection.Executables:ExtractPDFPages
19
20
  scfilecontainscontent = ScriptCollection.Executables:FileContainsContent
20
21
  scfileexists = ScriptCollection.Executables:FileExists