dforge-cli 1.0.1__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.
- dforge/__init__.py +1 -0
- dforge/banner.py +31 -0
- dforge/batch.py +156 -0
- dforge/cli.py +525 -0
- dforge/config.py +38 -0
- dforge/config_manager.py +33 -0
- dforge/converter.py +167 -0
- dforge/dependencies.py +98 -0
- dforge/engine.py +236 -0
- dforge/extractor.py +201 -0
- dforge/loading.py +19 -0
- dforge/menu.py +115 -0
- dforge/operations.py +314 -0
- dforge/processor.py +251 -0
- dforge/setup.py +107 -0
- dforge/theme.py +12 -0
- dforge/utils.py +169 -0
- dforge/watcher.py +137 -0
- dforge/workflows/__init__.py +0 -0
- dforge/workflows/automation.py +21 -0
- dforge/workflows/batch.py +18 -0
- dforge/workflows/batch_ocr.py +61 -0
- dforge/workflows/common.py +133 -0
- dforge/workflows/compress.py +73 -0
- dforge/workflows/convert.py +148 -0
- dforge/workflows/decrypt.py +50 -0
- dforge/workflows/encrypt.py +50 -0
- dforge/workflows/extract.py +18 -0
- dforge/workflows/image.py +21 -0
- dforge/workflows/merge.py +109 -0
- dforge/workflows/ocr.py +104 -0
- dforge/workflows/ocr_folder.py +0 -0
- dforge/workflows/pages.py +57 -0
- dforge/workflows/rotate.py +53 -0
- dforge/workflows/searchable.py +51 -0
- dforge/workflows/settings.py +56 -0
- dforge/workflows/split.py +32 -0
- dforge/workflows/tables.py +45 -0
- dforge/workflows/watermark.py +54 -0
- dforge_cli-1.0.1.dist-info/METADATA +244 -0
- dforge_cli-1.0.1.dist-info/RECORD +44 -0
- dforge_cli-1.0.1.dist-info/WHEEL +5 -0
- dforge_cli-1.0.1.dist-info/entry_points.txt +2 -0
- dforge_cli-1.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
import questionary
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
|
|
6
|
+
from dforge.operations import compress
|
|
7
|
+
|
|
8
|
+
from dforge.workflows.common import (
|
|
9
|
+
select_single_pdf,
|
|
10
|
+
success_screen,
|
|
11
|
+
get_output_name,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
console = Console()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def compress_workflow():
|
|
18
|
+
console.print("\n[bold cyan]Compress PDF[/bold cyan]\n")
|
|
19
|
+
|
|
20
|
+
pdf = select_single_pdf()
|
|
21
|
+
|
|
22
|
+
if not pdf:
|
|
23
|
+
return
|
|
24
|
+
|
|
25
|
+
preset = questionary.select(
|
|
26
|
+
"Compression Preset",
|
|
27
|
+
choices=[
|
|
28
|
+
"screen",
|
|
29
|
+
"ebook",
|
|
30
|
+
"printer",
|
|
31
|
+
"prepress",
|
|
32
|
+
"default",
|
|
33
|
+
],
|
|
34
|
+
).ask()
|
|
35
|
+
|
|
36
|
+
output = get_output_name(
|
|
37
|
+
f"{pdf.stem}_compressed.pdf"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
if not output:
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
output_path = Path(output)
|
|
44
|
+
|
|
45
|
+
console.print(
|
|
46
|
+
"\n[bold cyan]Compressing PDF...[/bold cyan]\n"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
compress(
|
|
50
|
+
pdf,
|
|
51
|
+
output_path,
|
|
52
|
+
preset,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
success_screen(
|
|
56
|
+
"Compression Complete",
|
|
57
|
+
output_file=output_path.name,
|
|
58
|
+
extra_lines=[
|
|
59
|
+
f"Preset : {preset}",
|
|
60
|
+
f"Location : {output_path.resolve()}",
|
|
61
|
+
],
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
next_action = questionary.select(
|
|
65
|
+
"What next?",
|
|
66
|
+
choices=[
|
|
67
|
+
"Compress Another PDF",
|
|
68
|
+
"Back to PDF Tools",
|
|
69
|
+
],
|
|
70
|
+
).ask()
|
|
71
|
+
|
|
72
|
+
if next_action == "Compress Another PDF":
|
|
73
|
+
compress_workflow()
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
import questionary
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
|
|
6
|
+
from dforge.converter import (
|
|
7
|
+
convert,
|
|
8
|
+
images_to_pdf,
|
|
9
|
+
pdf_to_images,
|
|
10
|
+
)
|
|
11
|
+
from dforge.menu import conversion_menu
|
|
12
|
+
from dforge.workflows.common import success_screen
|
|
13
|
+
|
|
14
|
+
console = Console()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def markdown_to_pdf_workflow():
|
|
18
|
+
source = questionary.path(
|
|
19
|
+
"Markdown file:"
|
|
20
|
+
).ask()
|
|
21
|
+
|
|
22
|
+
if not source:
|
|
23
|
+
return
|
|
24
|
+
|
|
25
|
+
convert(Path(source), "pdf")
|
|
26
|
+
|
|
27
|
+
success_screen(
|
|
28
|
+
"Conversion Complete"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def markdown_to_docx_workflow():
|
|
33
|
+
source = questionary.path(
|
|
34
|
+
"Markdown file:"
|
|
35
|
+
).ask()
|
|
36
|
+
|
|
37
|
+
if not source:
|
|
38
|
+
return
|
|
39
|
+
|
|
40
|
+
convert(Path(source), "docx")
|
|
41
|
+
|
|
42
|
+
success_screen(
|
|
43
|
+
"Conversion Complete"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def docx_to_pdf_workflow():
|
|
48
|
+
source = questionary.path(
|
|
49
|
+
"DOCX file:"
|
|
50
|
+
).ask()
|
|
51
|
+
|
|
52
|
+
if not source:
|
|
53
|
+
return
|
|
54
|
+
|
|
55
|
+
convert(Path(source), "pdf")
|
|
56
|
+
|
|
57
|
+
success_screen(
|
|
58
|
+
"Conversion Complete"
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def docx_to_markdown_workflow():
|
|
63
|
+
source = questionary.path(
|
|
64
|
+
"DOCX file:"
|
|
65
|
+
).ask()
|
|
66
|
+
|
|
67
|
+
if not source:
|
|
68
|
+
return
|
|
69
|
+
|
|
70
|
+
convert(Path(source), "md")
|
|
71
|
+
|
|
72
|
+
success_screen(
|
|
73
|
+
"Conversion Complete"
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def images_to_pdf_workflow():
|
|
78
|
+
source = questionary.path(
|
|
79
|
+
"Image file or folder:"
|
|
80
|
+
).ask()
|
|
81
|
+
|
|
82
|
+
if not source:
|
|
83
|
+
return
|
|
84
|
+
|
|
85
|
+
images_to_pdf(
|
|
86
|
+
Path(source)
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
success_screen(
|
|
90
|
+
"Conversion Complete"
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def pdf_to_images_workflow():
|
|
95
|
+
source = questionary.path(
|
|
96
|
+
"PDF file:"
|
|
97
|
+
).ask()
|
|
98
|
+
|
|
99
|
+
if not source:
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
fmt = questionary.select(
|
|
103
|
+
"Image format:",
|
|
104
|
+
choices=[
|
|
105
|
+
"png",
|
|
106
|
+
"jpeg",
|
|
107
|
+
"tiff",
|
|
108
|
+
],
|
|
109
|
+
).ask()
|
|
110
|
+
|
|
111
|
+
if not fmt:
|
|
112
|
+
return
|
|
113
|
+
|
|
114
|
+
pdf_to_images(
|
|
115
|
+
Path(source),
|
|
116
|
+
fmt=fmt,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
success_screen(
|
|
120
|
+
"Conversion Complete"
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def conversion_workflow():
|
|
125
|
+
while True:
|
|
126
|
+
|
|
127
|
+
choice = conversion_menu()
|
|
128
|
+
|
|
129
|
+
if choice == "Markdown → PDF":
|
|
130
|
+
markdown_to_pdf_workflow()
|
|
131
|
+
|
|
132
|
+
elif choice == "Markdown → DOCX":
|
|
133
|
+
markdown_to_docx_workflow()
|
|
134
|
+
|
|
135
|
+
elif choice == "DOCX → PDF":
|
|
136
|
+
docx_to_pdf_workflow()
|
|
137
|
+
|
|
138
|
+
elif choice == "DOCX → Markdown":
|
|
139
|
+
docx_to_markdown_workflow()
|
|
140
|
+
|
|
141
|
+
elif choice == "Images → PDF":
|
|
142
|
+
images_to_pdf_workflow()
|
|
143
|
+
|
|
144
|
+
elif choice == "PDF → Images":
|
|
145
|
+
pdf_to_images_workflow()
|
|
146
|
+
|
|
147
|
+
elif choice == "⬅ Back":
|
|
148
|
+
break
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
import questionary
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
|
|
6
|
+
from dforge.operations import decrypt
|
|
7
|
+
|
|
8
|
+
from dforge.workflows.common import (
|
|
9
|
+
select_single_pdf,
|
|
10
|
+
success_screen,
|
|
11
|
+
get_output_name,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
console = Console()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def decrypt_workflow():
|
|
18
|
+
console.print("\n[bold cyan]Decrypt PDF[/bold cyan]\n")
|
|
19
|
+
|
|
20
|
+
pdf = select_single_pdf()
|
|
21
|
+
|
|
22
|
+
if not pdf:
|
|
23
|
+
return
|
|
24
|
+
|
|
25
|
+
password = questionary.password(
|
|
26
|
+
"Password:"
|
|
27
|
+
).ask()
|
|
28
|
+
|
|
29
|
+
if not password:
|
|
30
|
+
return
|
|
31
|
+
|
|
32
|
+
output = get_output_name(
|
|
33
|
+
f"{pdf.stem}_decrypted.pdf"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
if not output:
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
output_path = Path(output)
|
|
40
|
+
|
|
41
|
+
decrypt(
|
|
42
|
+
pdf,
|
|
43
|
+
password,
|
|
44
|
+
output_path,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
success_screen(
|
|
48
|
+
"Decryption Complete",
|
|
49
|
+
output_file=output_path.name,
|
|
50
|
+
)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
import questionary
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
|
|
6
|
+
from dforge.operations import encrypt
|
|
7
|
+
|
|
8
|
+
from dforge.workflows.common import (
|
|
9
|
+
select_single_pdf,
|
|
10
|
+
success_screen,
|
|
11
|
+
get_output_name,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
console = Console()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def encrypt_workflow():
|
|
18
|
+
console.print("\n[bold cyan]Encrypt PDF[/bold cyan]\n")
|
|
19
|
+
|
|
20
|
+
pdf = select_single_pdf()
|
|
21
|
+
|
|
22
|
+
if not pdf:
|
|
23
|
+
return
|
|
24
|
+
|
|
25
|
+
password = questionary.password(
|
|
26
|
+
"Password:"
|
|
27
|
+
).ask()
|
|
28
|
+
|
|
29
|
+
if not password:
|
|
30
|
+
return
|
|
31
|
+
|
|
32
|
+
output = get_output_name(
|
|
33
|
+
f"{pdf.stem}_encrypted.pdf"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
if not output:
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
output_path = Path(output)
|
|
40
|
+
|
|
41
|
+
encrypt(
|
|
42
|
+
pdf,
|
|
43
|
+
password,
|
|
44
|
+
output_path,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
success_screen(
|
|
48
|
+
"Encryption Complete",
|
|
49
|
+
output_file=output_path.name,
|
|
50
|
+
)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from dforge.menu import extract_menu
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def extract_workflow():
|
|
5
|
+
while True:
|
|
6
|
+
choice = extract_menu()
|
|
7
|
+
|
|
8
|
+
if choice == "Extract Text":
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
elif choice == "Extract Images":
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
elif choice == "Extract Metadata":
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
elif choice == "⬅ Back":
|
|
18
|
+
break
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from dforge.menu import image_menu
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def image_workflow():
|
|
5
|
+
while True:
|
|
6
|
+
choice = image_menu()
|
|
7
|
+
|
|
8
|
+
if choice == "Resize Images":
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
elif choice == "Convert Format":
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
elif choice == "Crop Images":
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
elif choice == "Watermark Images":
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
elif choice == "⬅ Back":
|
|
21
|
+
break
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
import questionary
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
|
|
6
|
+
from dforge.operations import merge
|
|
7
|
+
|
|
8
|
+
from dforge.workflows.common import (
|
|
9
|
+
select_multiple_pdfs,
|
|
10
|
+
success_screen,
|
|
11
|
+
get_output_name,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
console = Console()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def merge_workflow():
|
|
18
|
+
console.print("\n[bold cyan]Merge PDFs[/bold cyan]\n")
|
|
19
|
+
|
|
20
|
+
folder, selected = select_multiple_pdfs()
|
|
21
|
+
|
|
22
|
+
if not folder or not selected:
|
|
23
|
+
return
|
|
24
|
+
|
|
25
|
+
if len(selected) < 2:
|
|
26
|
+
console.print(
|
|
27
|
+
"[red]Select at least 2 PDFs.[/red]"
|
|
28
|
+
)
|
|
29
|
+
return
|
|
30
|
+
|
|
31
|
+
console.print("\n[bold cyan]Selected PDFs[/bold cyan]\n")
|
|
32
|
+
|
|
33
|
+
for i, pdf in enumerate(selected, start=1):
|
|
34
|
+
console.print(f"{i}. {pdf}")
|
|
35
|
+
|
|
36
|
+
console.print(
|
|
37
|
+
f"\n[green]Total PDFs:[/green] {len(selected)}\n"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
if not questionary.confirm(
|
|
41
|
+
"Continue?",
|
|
42
|
+
default=True,
|
|
43
|
+
).ask():
|
|
44
|
+
return
|
|
45
|
+
|
|
46
|
+
sort_mode = questionary.select(
|
|
47
|
+
"Sort PDFs before merging?",
|
|
48
|
+
choices=[
|
|
49
|
+
"Keep Current Order",
|
|
50
|
+
"Alphabetical",
|
|
51
|
+
"Reverse Alphabetical",
|
|
52
|
+
],
|
|
53
|
+
).ask()
|
|
54
|
+
|
|
55
|
+
if sort_mode == "Alphabetical":
|
|
56
|
+
selected.sort()
|
|
57
|
+
|
|
58
|
+
elif sort_mode == "Reverse Alphabetical":
|
|
59
|
+
selected.sort(reverse=True)
|
|
60
|
+
|
|
61
|
+
output = get_output_name(
|
|
62
|
+
f"merged_{len(selected)}_files.pdf"
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
if not output:
|
|
66
|
+
return
|
|
67
|
+
|
|
68
|
+
output_path = Path(output)
|
|
69
|
+
|
|
70
|
+
if output_path.exists():
|
|
71
|
+
|
|
72
|
+
overwrite = questionary.confirm(
|
|
73
|
+
f"{output} already exists. Overwrite?",
|
|
74
|
+
default=False,
|
|
75
|
+
).ask()
|
|
76
|
+
|
|
77
|
+
if not overwrite:
|
|
78
|
+
return
|
|
79
|
+
|
|
80
|
+
console.print(
|
|
81
|
+
"\n[bold cyan]Merging PDFs...[/bold cyan]\n"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
inputs = [
|
|
85
|
+
folder / pdf
|
|
86
|
+
for pdf in selected
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
merge(inputs, output_path)
|
|
90
|
+
|
|
91
|
+
success_screen(
|
|
92
|
+
"Merge Complete",
|
|
93
|
+
output_file=output_path.name,
|
|
94
|
+
extra_lines=[
|
|
95
|
+
f"Input Files : {len(selected)}",
|
|
96
|
+
f"Location : {output_path.resolve()}",
|
|
97
|
+
],
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
next_action = questionary.select(
|
|
101
|
+
"What next?",
|
|
102
|
+
choices=[
|
|
103
|
+
"Merge More PDFs",
|
|
104
|
+
"Back to PDF Tools",
|
|
105
|
+
],
|
|
106
|
+
).ask()
|
|
107
|
+
|
|
108
|
+
if next_action == "Merge More PDFs":
|
|
109
|
+
merge_workflow()
|
dforge/workflows/ocr.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
import questionary
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
from dforge.loading import Loader
|
|
6
|
+
from dforge.engine import ocr_image, ocr_pdf
|
|
7
|
+
from dforge.dependencies import has_poppler
|
|
8
|
+
from dforge.workflows.common import (
|
|
9
|
+
success_screen,
|
|
10
|
+
get_output_name,
|
|
11
|
+
)
|
|
12
|
+
from dforge.dependencies import (
|
|
13
|
+
check_poppler,
|
|
14
|
+
check_tesseract,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
console = Console()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def ocr_workflow():
|
|
23
|
+
if not check_tesseract():
|
|
24
|
+
return
|
|
25
|
+
|
|
26
|
+
input_file = questionary.path(
|
|
27
|
+
"Image or PDF file:"
|
|
28
|
+
).ask()
|
|
29
|
+
|
|
30
|
+
if not input_file:
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
input_file = Path(input_file)
|
|
34
|
+
|
|
35
|
+
if input_file.suffix.lower() == ".pdf":
|
|
36
|
+
if not check_poppler():
|
|
37
|
+
return
|
|
38
|
+
console.print("\n[bold cyan]OCR Image / PDF[/bold cyan]\n")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
if not input_file:
|
|
42
|
+
return
|
|
43
|
+
|
|
44
|
+
input_file = Path(input_file)
|
|
45
|
+
|
|
46
|
+
lang = questionary.text(
|
|
47
|
+
"OCR Language(s)",
|
|
48
|
+
default="eng",
|
|
49
|
+
).ask()
|
|
50
|
+
|
|
51
|
+
fmt = questionary.select(
|
|
52
|
+
"Output Format",
|
|
53
|
+
choices=[
|
|
54
|
+
"txt",
|
|
55
|
+
"json",
|
|
56
|
+
"md",
|
|
57
|
+
],
|
|
58
|
+
).ask()
|
|
59
|
+
|
|
60
|
+
output = get_output_name(
|
|
61
|
+
f"{input_file.stem}_ocr.{fmt}"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
if not output:
|
|
65
|
+
return
|
|
66
|
+
|
|
67
|
+
output_path = Path(output)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
with Loader("Running OCR..."):
|
|
71
|
+
ocr_pdf(
|
|
72
|
+
input_file,
|
|
73
|
+
output_path,
|
|
74
|
+
lang,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
if input_file.suffix.lower() == ".pdf":
|
|
78
|
+
ocr_pdf(
|
|
79
|
+
input_file,
|
|
80
|
+
output_path,
|
|
81
|
+
lang,
|
|
82
|
+
fmt,
|
|
83
|
+
)
|
|
84
|
+
else:
|
|
85
|
+
ocr_image(
|
|
86
|
+
input_file,
|
|
87
|
+
output_path,
|
|
88
|
+
lang,
|
|
89
|
+
fmt,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
success_screen(
|
|
93
|
+
"OCR Complete",
|
|
94
|
+
output_file=output_path.name,
|
|
95
|
+
extra_lines=[
|
|
96
|
+
f"Format : {fmt}",
|
|
97
|
+
f"Lang : {lang}",
|
|
98
|
+
],
|
|
99
|
+
)
|
|
100
|
+
if not has_poppler():
|
|
101
|
+
console.print(
|
|
102
|
+
"[red]Poppler not installed.[/red]"
|
|
103
|
+
)
|
|
104
|
+
return
|
|
File without changes
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
import questionary
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
|
|
6
|
+
from dforge.operations import extract_pages
|
|
7
|
+
|
|
8
|
+
from dforge.workflows.common import (
|
|
9
|
+
select_single_pdf,
|
|
10
|
+
success_screen,
|
|
11
|
+
get_output_name,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
console = Console()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def pages_workflow():
|
|
18
|
+
console.print("\n[bold cyan]Extract Pages[/bold cyan]\n")
|
|
19
|
+
|
|
20
|
+
pdf = select_single_pdf()
|
|
21
|
+
|
|
22
|
+
if not pdf:
|
|
23
|
+
return
|
|
24
|
+
|
|
25
|
+
page_range = questionary.text(
|
|
26
|
+
'Page range (Examples: 1-5, 3, 1,3,5)'
|
|
27
|
+
).ask()
|
|
28
|
+
|
|
29
|
+
if not page_range:
|
|
30
|
+
return
|
|
31
|
+
|
|
32
|
+
output = get_output_name(
|
|
33
|
+
f"{pdf.stem}_pages.pdf"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
if not output:
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
output_path = Path(output)
|
|
40
|
+
|
|
41
|
+
console.print(
|
|
42
|
+
"\n[bold cyan]Extracting Pages...[/bold cyan]\n"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
extract_pages(
|
|
46
|
+
pdf,
|
|
47
|
+
page_range,
|
|
48
|
+
output_path,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
success_screen(
|
|
52
|
+
"Page Extraction Complete",
|
|
53
|
+
output_file=output_path.name,
|
|
54
|
+
extra_lines=[
|
|
55
|
+
f"Pages : {page_range}",
|
|
56
|
+
],
|
|
57
|
+
)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
import questionary
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
|
|
6
|
+
from dforge.operations import rotate
|
|
7
|
+
|
|
8
|
+
from dforge.workflows.common import (
|
|
9
|
+
select_single_pdf,
|
|
10
|
+
success_screen,
|
|
11
|
+
get_output_name,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
console = Console()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def rotate_workflow():
|
|
18
|
+
console.print("\n[bold cyan]Rotate PDF[/bold cyan]\n")
|
|
19
|
+
|
|
20
|
+
pdf = select_single_pdf()
|
|
21
|
+
|
|
22
|
+
if not pdf:
|
|
23
|
+
return
|
|
24
|
+
|
|
25
|
+
degrees = int(
|
|
26
|
+
questionary.select(
|
|
27
|
+
"Rotation",
|
|
28
|
+
choices=["90", "180", "270"],
|
|
29
|
+
).ask()
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
output = get_output_name(
|
|
33
|
+
f"{pdf.stem}_rotated.pdf"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
if not output:
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
output_path = Path(output)
|
|
40
|
+
|
|
41
|
+
rotate(
|
|
42
|
+
pdf,
|
|
43
|
+
degrees,
|
|
44
|
+
output_path,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
success_screen(
|
|
48
|
+
"Rotation Complete",
|
|
49
|
+
output_file=output_path.name,
|
|
50
|
+
extra_lines=[
|
|
51
|
+
f"Rotation : {degrees}°",
|
|
52
|
+
],
|
|
53
|
+
)
|